diff options
author | Bram Moolenaar <Bram@vim.org> | 2022-09-03 21:35:53 +0100 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2022-09-03 21:35:53 +0100 |
commit | 1d84f7608f1e41dad03b8cc7925895437775f7c0 (patch) | |
tree | cc276825566021a35b96f93e178e5f2ac621a84b /src | |
parent | 06d32a0c177e4166ff0491668cd459464bc2ef45 (diff) |
patch 9.0.0370: cleaning up afterwards can make a function messyv9.0.0370
Problem: Cleaning up afterwards can make a function messy.
Solution: Add the :defer command.
Diffstat (limited to 'src')
-rw-r--r-- | src/ex_cmdidxs.h | 48 | ||||
-rw-r--r-- | src/ex_cmds.h | 3 | ||||
-rw-r--r-- | src/proto/userfunc.pro | 1 | ||||
-rw-r--r-- | src/proto/vim9cmds.pro | 1 | ||||
-rw-r--r-- | src/proto/vim9expr.pro | 1 | ||||
-rw-r--r-- | src/proto/vim9instr.pro | 3 | ||||
-rw-r--r-- | src/structs.h | 11 | ||||
-rw-r--r-- | src/testdir/test_user_func.vim | 32 | ||||
-rw-r--r-- | src/testdir/test_vim9_disassemble.vim | 15 | ||||
-rw-r--r-- | src/testdir/test_vim9_func.vim | 30 | ||||
-rw-r--r-- | src/userfunc.c | 299 | ||||
-rw-r--r-- | src/version.c | 2 | ||||
-rw-r--r-- | src/vim9.h | 20 | ||||
-rw-r--r-- | src/vim9cmds.c | 64 | ||||
-rw-r--r-- | src/vim9compile.c | 7 | ||||
-rw-r--r-- | src/vim9execute.c | 126 | ||||
-rw-r--r-- | src/vim9expr.c | 19 | ||||
-rw-r--r-- | src/vim9instr.c | 52 |
18 files changed, 576 insertions, 158 deletions
diff --git a/src/ex_cmdidxs.h b/src/ex_cmdidxs.h index 065bf056f5..fd4b1550a2 100644 --- a/src/ex_cmdidxs.h +++ b/src/ex_cmdidxs.h @@ -9,28 +9,28 @@ static const unsigned short cmdidxs1[26] = /* b */ 21, /* c */ 45, /* d */ 112, - /* e */ 137, - /* f */ 166, - /* g */ 183, - /* h */ 189, - /* i */ 199, - /* j */ 219, - /* k */ 221, - /* l */ 226, - /* m */ 289, - /* n */ 307, - /* o */ 327, - /* p */ 339, - /* q */ 378, - /* r */ 381, - /* s */ 401, - /* t */ 471, - /* u */ 517, - /* v */ 528, - /* w */ 549, - /* x */ 563, - /* y */ 573, - /* z */ 574 + /* e */ 138, + /* f */ 167, + /* g */ 184, + /* h */ 190, + /* i */ 200, + /* j */ 220, + /* k */ 222, + /* l */ 227, + /* m */ 290, + /* n */ 308, + /* o */ 328, + /* p */ 340, + /* q */ 379, + /* r */ 382, + /* s */ 402, + /* t */ 472, + /* u */ 518, + /* v */ 529, + /* w */ 550, + /* x */ 564, + /* y */ 574, + /* z */ 575 }; /* @@ -44,7 +44,7 @@ static const unsigned char cmdidxs2[26][26] = /* a */ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 0, 0, 8, 17, 0, 18, 0, 0, 0, 0, 0 }, /* b */ { 2, 0, 0, 5, 6, 8, 0, 0, 0, 0, 0, 9, 10, 11, 12, 13, 0, 14, 0, 0, 0, 0, 23, 0, 0, 0 }, /* c */ { 3, 12, 16, 18, 20, 22, 25, 0, 0, 0, 0, 33, 38, 41, 47, 57, 59, 60, 61, 0, 63, 0, 66, 0, 0, 0 }, - /* d */ { 0, 0, 0, 0, 0, 0, 0, 0, 8, 18, 0, 19, 0, 0, 20, 0, 0, 22, 23, 0, 0, 0, 0, 0, 0, 0 }, + /* d */ { 0, 0, 0, 0, 0, 0, 0, 0, 9, 19, 0, 20, 0, 0, 21, 0, 0, 23, 24, 0, 0, 0, 0, 0, 0, 0 }, /* e */ { 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 9, 11, 12, 0, 0, 0, 0, 0, 0, 0, 23, 0, 24, 0, 0 }, /* f */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0 }, /* g */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 4, 5, 0, 0, 0, 0 }, @@ -69,4 +69,4 @@ static const unsigned char cmdidxs2[26][26] = /* z */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }; -static const int command_count = 591; +static const int command_count = 592; diff --git a/src/ex_cmds.h b/src/ex_cmds.h index 8da0da1c9a..8f7c48f8b4 100644 --- a/src/ex_cmds.h +++ b/src/ex_cmds.h @@ -467,6 +467,9 @@ EXCMD(CMD_def, "def", ex_function, EXCMD(CMD_defcompile, "defcompile", ex_defcompile, EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK|EX_TRLBAR|EX_EXTRA, ADDR_NONE), +EXCMD(CMD_defer, "defer", ex_call, + EX_NEEDARG|EX_EXTRA|EX_NOTRLCOM|EX_EXPR_ARG|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK, + ADDR_NONE), EXCMD(CMD_delcommand, "delcommand", ex_delcommand, EX_NEEDARG|EX_WORD1|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK, ADDR_NONE), diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro index 427d47b241..3e417dda50 100644 --- a/src/proto/userfunc.pro +++ b/src/proto/userfunc.pro @@ -58,6 +58,7 @@ void func_ptr_unref(ufunc_T *fp); void func_ref(char_u *name); void func_ptr_ref(ufunc_T *fp); void ex_return(exarg_T *eap); +void handle_defer(void); void ex_call(exarg_T *eap); int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv); void discard_pending_return(void *rettv); diff --git a/src/proto/vim9cmds.pro b/src/proto/vim9cmds.pro index 7529d63d62..10fc2c78d2 100644 --- a/src/proto/vim9cmds.pro +++ b/src/proto/vim9cmds.pro @@ -21,6 +21,7 @@ char_u *compile_finally(char_u *arg, cctx_T *cctx); char_u *compile_endtry(char_u *arg, cctx_T *cctx); char_u *compile_throw(char_u *arg, cctx_T *cctx); char_u *compile_eval(char_u *arg, cctx_T *cctx); +char_u *compile_defer(char_u *arg_start, cctx_T *cctx); char_u *compile_mult_expr(char_u *arg, int cmdidx, cctx_T *cctx); char_u *compile_put(char_u *arg, exarg_T *eap, cctx_T *cctx); char_u *compile_exec(char_u *line_arg, exarg_T *eap, cctx_T *cctx); diff --git a/src/proto/vim9expr.pro b/src/proto/vim9expr.pro index 233cfa2267..f58e346252 100644 --- a/src/proto/vim9expr.pro +++ b/src/proto/vim9expr.pro @@ -4,6 +4,7 @@ void clear_ppconst(ppconst_T *ppconst); int compile_member(int is_slice, int *keeping_dict, cctx_T *cctx); int compile_load_scriptvar(cctx_T *cctx, char_u *name, char_u *start, char_u **end); int compile_load(char_u **arg, char_u *end_arg, cctx_T *cctx, int is_expr, int error); +int compile_arguments(char_u **arg, cctx_T *cctx, int *argcount, ca_special_T special_fn); char_u *to_name_end(char_u *arg, int use_namespace); char_u *to_name_const_end(char_u *arg); int get_lambda_tv_and_compile(char_u **arg, typval_T *rettv, int types_optional, evalarg_T *evalarg); diff --git a/src/proto/vim9instr.pro b/src/proto/vim9instr.pro index 8a3254b9ee..b0f3aa4762 100644 --- a/src/proto/vim9instr.pro +++ b/src/proto/vim9instr.pro @@ -23,7 +23,7 @@ int generate_PUSHS(cctx_T *cctx, char_u **str); int generate_PUSHCHANNEL(cctx_T *cctx); int generate_PUSHJOB(cctx_T *cctx); int generate_PUSHBLOB(cctx_T *cctx, blob_T *blob); -int generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type); +int generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type, int may_prefix); int generate_AUTOLOAD(cctx_T *cctx, char_u *name, type_T *type); int generate_GETITEM(cctx_T *cctx, int index, int with_op); int generate_SLICE(cctx_T *cctx, int count); @@ -52,6 +52,7 @@ int generate_BLOBAPPEND(cctx_T *cctx); int generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount); int generate_UCALL(cctx_T *cctx, char_u *name, int argcount); int generate_PCALL(cctx_T *cctx, int argcount, char_u *name, type_T *type, int at_top); +int generate_DEFER(cctx_T *cctx, int var_idx, int argcount); int generate_STRINGMEMBER(cctx_T *cctx, char_u *name, size_t len); int generate_ECHO(cctx_T *cctx, int with_white, int count); int generate_MULT_EXPR(cctx_T *cctx, isntype_T isn_type, int count); diff --git a/src/structs.h b/src/structs.h index b8627dddf2..8fab297b59 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1753,6 +1753,7 @@ struct funccall_S linenr_T breakpoint; // next line with breakpoint or zero int dbg_tick; // debug_tick when breakpoint was set int level; // top nesting level of executed function + garray_T fc_defer; // functions to be called on return #ifdef FEAT_PROFILE proftime_T prof_child; // time spent in a child #endif @@ -1767,6 +1768,14 @@ struct funccall_S // "func" }; +// structure used as item in "fc_defer" +typedef struct +{ + char_u *dr_name; // function name, allocated + typval_T dr_argvars[MAX_FUNC_ARGS + 1]; + int dr_argcount; +} defer_T; + /* * Struct used by trans_function_name() */ @@ -2850,7 +2859,7 @@ struct file_buffer int b_u_synced; // entry lists are synced long b_u_seq_last; // last used undo sequence number long b_u_save_nr_last; // counter for last file write - long b_u_seq_cur; // hu_seq of header below which we are now + long b_u_seq_cur; // uh_seq of header below which we are now time_T b_u_time_cur; // uh_time of header below which we are now long b_u_save_nr_cur; // file write nr after which we are now diff --git a/src/testdir/test_user_func.vim b/src/testdir/test_user_func.vim index f6e25e3b5a..af05812c6b 100644 --- a/src/testdir/test_user_func.vim +++ b/src/testdir/test_user_func.vim @@ -529,4 +529,36 @@ func Test_funcdef_alloc_failure() bw! endfunc +func AddDefer(arg) + call extend(g:deferred, [a:arg]) +endfunc + +func WithDeferTwo() + call extend(g:deferred, ['in Two']) + for nr in range(3) + defer AddDefer('Two' .. nr) + endfor + call extend(g:deferred, ['end Two']) +endfunc + +func WithDeferOne() + call extend(g:deferred, ['in One']) + call writefile(['text'], 'Xfuncdefer') + defer delete('Xfuncdefer') + defer AddDefer('One') + call WithDeferTwo() + call extend(g:deferred, ['end One']) +endfunc + +func Test_defer() + let g:deferred = [] + call WithDeferOne() + + call assert_equal(['in One', 'in Two', 'end Two', 'Two2', 'Two1', 'Two0', 'end One', 'One'], g:deferred) + unlet g:deferred + + call assert_equal('', glob('Xfuncdefer')) +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim index 12be156483..57b385bd50 100644 --- a/src/testdir/test_vim9_disassemble.vim +++ b/src/testdir/test_vim9_disassemble.vim @@ -2900,4 +2900,19 @@ def Test_disassemble_bitshift() '10 RETURN void', instr) enddef +def s:OneDefer() + defer delete("file") +enddef + +def Test_disassemble_defer() + var instr = execute('disassemble s:OneDefer') + assert_match('OneDefer\_s*' .. + 'defer delete("file")\_s*' .. + '\d PUSHFUNC "delete"\_s*' .. + '\d PUSHS "file"\_s*' .. + '\d DEFER 1 args\_s*' .. + '\d RETURN\_s*', + instr) +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim index 0fa74df259..c616e1890d 100644 --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -4272,6 +4272,36 @@ def Test_cexpr_errmsg_line_number() v9.CheckScriptFailure(lines, 'E777', 2) enddef +def AddDefer(s: string) + g:deferred->extend([s]) +enddef + +def DeferTwo() + g:deferred->extend(['in Two']) + for n in range(3) + defer g:AddDefer('two' .. n) + endfor + g:deferred->extend(['end Two']) +enddef + +def DeferOne() + g:deferred->extend(['in One']) + defer g:AddDefer('one') + g:DeferTwo() + g:deferred->extend(['end One']) + + writefile(['text'], 'XdeferFile') + defer delete('XdeferFile') +enddef + +def Test_defer() + g:deferred = [] + g:DeferOne() + assert_equal(['in One', 'in Two', 'end Two', 'two2', 'two1', 'two0', 'end One', 'one'], g:deferred) + unlet g:deferred + assert_equal('', glob('XdeferFile')) +enddef + " The following messes up syntax highlight, keep near the end. if has('python3') def Test_python3_command() diff --git a/src/userfunc.c b/src/userfunc.c index 00c17b8c00..c33ec63f9a 100644 --- a/src/userfunc.c +++ b/src/userfunc.c @@ -1728,44 +1728,36 @@ emsg_funcname(char *ermsg, char_u *name) } /* - * Allocate a variable for the result of a function. - * Return OK or FAIL. + * Get function arguments at "*arg" and advance it. + * Return them in "*argvars[MAX_FUNC_ARGS + 1]" and the count in "argcount". */ - int -get_func_tv( - char_u *name, // name of the function - int len, // length of "name" or -1 to use strlen() - typval_T *rettv, - char_u **arg, // argument, pointing to the '(' - evalarg_T *evalarg, // for line continuation - funcexe_T *funcexe) // various values + static int +get_func_arguments( + char_u **arg, + evalarg_T *evalarg, + int partial_argc, + typval_T *argvars, + int *argcount) { - char_u *argp; + char_u *argp = *arg; int ret = OK; - typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments - int argcount = 0; // number of arguments found int vim9script = in_vim9script(); int evaluate = evalarg == NULL ? FALSE : (evalarg->eval_flags & EVAL_EVALUATE); - /* - * Get the arguments. - */ - argp = *arg; - while (argcount < MAX_FUNC_ARGS - (funcexe->fe_partial == NULL ? 0 - : funcexe->fe_partial->pt_argc)) + while (*argcount < MAX_FUNC_ARGS - partial_argc) { // skip the '(' or ',' and possibly line breaks argp = skipwhite_and_linebreak(argp + 1, evalarg); if (*argp == ')' || *argp == ',' || *argp == NUL) break; - if (eval1(&argp, &argvars[argcount], evalarg) == FAIL) + if (eval1(&argp, &argvars[*argcount], evalarg) == FAIL) { ret = FAIL; break; } - ++argcount; + ++*argcount; // The comma should come right after the argument, but this wasn't // checked previously, thus only enforce it in Vim9 script. if (vim9script) @@ -1791,11 +1783,41 @@ get_func_tv( break; } } + argp = skipwhite_and_linebreak(argp, evalarg); if (*argp == ')') ++argp; else ret = FAIL; + *arg = argp; + return ret; +} + +/* + * Call a function and put the result in "rettv". + * Return OK or FAIL. + */ + int +get_func_tv( + char_u *name, // name of the function + int len, // length of "name" or -1 to use strlen() + typval_T *rettv, + char_u **arg, // argument, pointing to the '(' + evalarg_T *evalarg, // for line continuation + funcexe_T *funcexe) // various values +{ + char_u *argp; + int ret = OK; + typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments + int argcount = 0; // number of arguments found + int vim9script = in_vim9script(); + int evaluate = evalarg == NULL + ? FALSE : (evalarg->eval_flags & EVAL_EVALUATE); + + argp = *arg; + ret = get_func_arguments(&argp, evalarg, + (funcexe->fe_partial == NULL ? 0 : funcexe->fe_partial->pt_argc), + argvars, &argcount); if (ret == OK) { @@ -2884,6 +2906,9 @@ call_user_func( do_cmdline(NULL, get_func_line, (void *)fc, DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); + // Invoke functions added with ":defer". + handle_defer(); + --RedrawingDisabled; // when the function was aborted because of an error, return -1 @@ -5457,8 +5482,164 @@ ex_return(exarg_T *eap) clear_evalarg(&evalarg, eap); } + static int +ex_call_inner( + exarg_T *eap, + char_u *name, + char_u **arg, + char_u *startarg, + funcexe_T *funcexe_init, + evalarg_T *evalarg) +{ + linenr_T lnum; + int doesrange; + typval_T rettv; + int failed = FALSE; + + /* + * When skipping, evaluate the function once, to find the end of the + * arguments. + * When the function takes a range, this is discovered after the first + * call, and the loop is broken. + */ + if (eap->skip) + { + ++emsg_skip; + lnum = eap->line2; // do it once, also with an invalid range + } + else + lnum = eap->line1; + for ( ; lnum <= eap->line2; ++lnum) + { + funcexe_T funcexe; + + if (!eap->skip && eap->addr_count > 0) + { + if (lnum > curbuf->b_ml.ml_line_count) + { + // If the function deleted lines or switched to another buffer + // the line number may become invalid. + emsg(_(e_invalid_range)); + break; + } + curwin->w_cursor.lnum = lnum; + curwin->w_cursor.col = 0; + curwin->w_cursor.coladd = 0; + } + *arg = startarg; + + funcexe = *funcexe_init; + funcexe.fe_doesrange = &doesrange; + rettv.v_type = VAR_UNKNOWN; // clear_tv() uses this + if (get_func_tv(name, -1, &rettv, arg, evalarg, &funcexe) == FAIL) + { + failed = TRUE; + break; + } + if (has_watchexpr()) + dbg_check_breakpoint(eap); + + // Handle a function returning a Funcref, Dictionary or List. + if (handle_subscript(arg, NULL, &rettv, + eap->skip ? NULL : &EVALARG_EVALUATE, TRUE) == FAIL) + { + failed = TRUE; + break; + } + + clear_tv(&rettv); + if (doesrange || eap->skip) + break; + + // Stop when immediately aborting on error, or when an interrupt + // occurred or an exception was thrown but not caught. + // get_func_tv() returned OK, so that the check for trailing + // characters below is executed. + if (aborting()) + break; + } + if (eap->skip) + --emsg_skip; + return failed; +} + +/* + * Core part of ":defer func(arg)". "arg" points to the "(" and is advanced. + * Returns FAIL or OK. + */ + static int +ex_defer_inner(char_u *name, char_u **arg, evalarg_T *evalarg) +{ + typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments + int argcount = 0; // number of arguments found + defer_T *dr; + int ret = FAIL; + char_u *saved_name; + + if (current_funccal == NULL) + { + semsg(_(e_str_not_inside_function), "defer"); + return FAIL; + } + if (get_func_arguments(arg, evalarg, FALSE, argvars, &argcount) == FAIL) + goto theend; + saved_name = vim_strsave(name); + if (saved_name == NULL) + goto theend; + + if (current_funccal->fc_defer.ga_itemsize == 0) + ga_init2(¤t_funccal->fc_defer, sizeof(defer_T), 10); + if (ga_grow(¤t_funccal->fc_defer, 1) == FAIL) + goto theend; + dr = ((defer_T *)current_funccal->fc_defer.ga_data) + + current_funccal->fc_defer.ga_len++; + dr->dr_name = saved_name; + dr->dr_argcount = argcount; + while (argcount > 0) + { + --argcount; + dr->dr_argvars[argcount] = argvars[argcount]; + } + ret = OK; + +theend: + while (--argcount >= 0) + clear_tv(&argvars[argcount]); + return ret; +} + +/* + * Invoked after a functions has finished: invoke ":defer" functions. + */ + void +handle_defer(void) +{ + int idx; + + for (idx = current_funccal->fc_defer.ga_len - 1; idx >= 0; --idx) + { + funcexe_T funcexe; + typval_T rettv; + defer_T *dr = ((defer_T *)current_funccal->fc_defer.ga_data) + idx; + int i; + + CLEAR_FIELD(funcexe); + funcexe.fe_evaluate = TRUE; + + rettv.v_type = VAR_UNKNOWN; // clear_tv() uses this + call_func(dr->dr_name, -1, &rettv, + dr->dr_argcount, dr->dr_argvars, &funcexe); + clear_tv(&rettv); + vim_free(dr->dr_name); + for (i = dr->dr_argcount - 1; i >= 0; --i) + clear_tv(&dr->dr_argvars[i]); + } + ga_clear(¤t_funccal->fc_defer); +} + /* * ":1,25call func(arg1, arg2)" function call. + * ":defer func(arg1, arg2)" deferred function call. */ void ex_call(exarg_T *eap) @@ -5468,9 +5649,6 @@ ex_call(exarg_T *eap) char_u *name; char_u *tofree; int len; - typval_T rettv; - linenr_T lnum; - int doesrange; int failed = FALSE; funcdict_T fudi; partial_T *partial = NULL; @@ -5482,6 +5660,8 @@ ex_call(exarg_T *eap) fill_evalarg_from_eap(&evalarg, eap, eap->skip); if (eap->skip) { + typval_T rettv; + // trans_function_name() doesn't work well when skipping, use eval0() // instead to skip to any following command, e.g. for: // :if 0 | call dict.foo().bar() | endif @@ -5531,82 +5711,29 @@ ex_call(exarg_T *eap) goto end; } - /* - * When skipping, evaluate the function once, to find the end of the - * arguments. - * When the function takes a range, this is discovered after the first - * call, and the loop is broken. - */ - if (eap->skip) + if (eap->cmdidx == CMD_defer) { - ++emsg_skip; - lnum = eap->line2; // do it once, also with an invalid range + arg = startarg; + failed = ex_defer_inner(name, &arg, &evalarg) == FAIL; } else - lnum = eap->line1; - for ( ; lnum <= eap->line2; ++lnum) { funcexe_T funcexe; - if (!eap->skip && eap->addr_count > 0) - { - if (lnum > curbuf->b_ml.ml_line_count) - { - // If the function deleted lines or switched to another buffer - // the line number may become invalid. - emsg(_(e_invalid_range)); - break; - } - curwin->w_cursor.lnum = lnum; - curwin->w_cursor.col = 0; - curwin->w_cursor.coladd = 0; - } - arg = startarg; - CLEAR_FIELD(funcexe); - funcexe.fe_firstline = eap->line1; - funcexe.fe_lastline = eap->line2; - funcexe.fe_doesrange = &doesrange; - funcexe.fe_evaluate = !eap->skip; + funcexe.fe_check_type = type; funcexe.fe_partial = partial; funcexe.fe_selfdict = fudi.fd_dict; - funcexe.fe_check_type = type; + funcexe.fe_firstline = eap->line1; + funcexe.fe_lastline = eap->line2; funcexe.fe_found_var = found_var; - rettv.v_type = VAR_UNKNOWN; // clear_tv() uses this - if (get_func_tv(name, -1, &rettv, &arg, &evalarg, &funcexe) == FAIL) - { - failed = TRUE; - break; - } - if (has_watchexpr()) - dbg_check_breakpoint(eap); - - // Handle a function returning a Funcref, Dictionary or List. - if (handle_subscript(&arg, NULL, &rettv, - eap->skip ? NULL : &EVALARG_EVALUATE, TRUE) == FAIL) - { - failed = TRUE; - break; - } - - clear_tv(&rettv); - if (doesrange || eap->skip) - break; - - // Stop when immediately aborting on error, or when an interrupt - // occurred or an exception was thrown but not caught. - // get_func_tv() returned OK, so that the check for trailing - // characters below is executed. - if (aborting()) - break; + funcexe.fe_evaluate = !eap->skip; + failed = ex_call_inner(eap, name, &arg, startarg, &funcexe, &evalarg); } - if (eap->skip) - --emsg_skip; // When inside :try we need to check for following "| catch" or "| endtry". // Not when there was an error, but do check if an exception was thrown. - if ((!aborting() || did_throw) - && (!failed || eap->cstack->cs_trylevel > 0)) + if ((!aborting() || did_throw) && (!failed || eap->cstack->cs_trylevel > 0)) { // Check for trailing illegal characters and a following command. arg = skipwhite(arg); diff --git a/src/version.c b/src/version.c index 9eba6bf335..b438a46297 100644 --- a/src/version.c +++ b/src/version.c @@ -708,6 +708,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 370, +/**/ 369, /**/ 368, diff --git a/src/vim9.h b/src/vim9.h index b32dbeab44..22a3f3990e 100644 --- a/src/vim9.h +++ b/src/vim9.h @@ -113,6 +113,7 @@ typedef enum { ISN_FUNCREF, // push a function ref to dfunc isn_arg.funcref ISN_NEWFUNC, // create a global function from a lambda function ISN_DEF, // list functions + ISN_DEFER, // :defer argument count is isn_arg.number // expression operations ISN_JUMP, // jump if condition is matched isn_arg.jump @@ -419,6 +420,12 @@ typedef struct { int dbg_break_lnum; // first line to break after } debug_T; +// arguments to ISN_DEFER +typedef struct { + int defer_var_idx; // local variable index for defer list + int defer_argcount; // number of arguments +} deferins_T; + /* * Instruction */ @@ -468,6 +475,7 @@ struct isn_S { tobool_T tobool; getitem_T getitem; debug_T debug; + deferins_T defer; } isn_arg; }; @@ -498,6 +506,9 @@ struct dfunc_S { int df_varcount; // number of local variables int df_has_closure; // one if a closure was created + int df_defer_var_idx; // index of local variable that has a list + // of deferred function calls; zero if not + // set }; // Number of entries used by stack frame for a function call. @@ -735,6 +746,15 @@ struct cctx_S { // lhs_name is not NULL }; +/* + * List of special functions for "compile_arguments()". + */ +typedef enum { + CA_NOT_SPECIAL, + CA_SEARCHPAIR, // {skip} in searchpair() and searchpairpos() + CA_SUBSTITUTE, // {sub} in substitute(), when prefixed with \= +} ca_special_T; + // flags for typval2type() #define TVTT_DO_MEMBER 1 #define TVTT_MORE_SPECIFIC 2 // get most specific type for member diff --git a/src/vim9cmds.c b/src/vim9cmds.c index 4ad87bc264..9651801ed0 100644 --- a/src/vim9cmds.c +++ b/src/vim9cmds.c @@ -1654,6 +1654,9 @@ compile_throw(char_u *arg, cctx_T *cctx UNUSED) return p; } +/* + * Compile an expression or function call. + */ char_u * compile_eval(char_u *arg, cctx_T *cctx) { @@ -1682,6 +1685,67 @@ compile_eval(char_u *arg, cctx_T *cctx) } /* + * Compile "defer func(arg)". + */ + char_u * +compile_defer(char_u *arg_start, cctx_T *cctx) +{ + char_u *p; + char_u *arg = arg_start; + int argcount = 0; + dfunc_T *dfunc; + type_T *type; + int func_idx; + + // Get a funcref for the function name. + // TODO: better way to find the "(". + p = vim_strchr(arg, '('); + if (p == NULL) + { + semsg(_(e_missing_parenthesis_str), arg); + return NULL; + } + *p = NUL; + func_idx = find_internal_func(arg); + if (func_idx >= 0) + // TODO: better type + generate_PUSHFUNC(cctx, (char_u *)internal_func_name(func_idx), + &t_func_any, FALSE); + else if (compile_expr0(&arg, cctx) == FAIL) + return NULL; + *p = '('; + + // check for function type + type = get_type_on_stack(cctx, 0); + if (type->tt_type != VAR_FUNC) + { + emsg(_(e_function_name_required)); + return NULL; + } + + // compile the arguments + arg = skipwhite(p + 1); + if (compile_arguments(&arg, cctx, &argcount, CA_NOT_SPECIAL) == FAIL) + return NULL; + + // TODO: check argument count with "type" + + dfunc = ((dfunc_T *)def_functions.ga_data) + cctx->ctx_ufunc->uf_dfunc_idx; + if (dfunc->df_defer_var_idx == 0) + { + lvar_T *lvar = reserve_local(cctx, (char_u *)"@defer@", 7, + TRUE, &t_list_any); + if (lvar == NULL) + return NULL; + dfunc->df_defer_var_idx = lvar->lv_idx + 1; + } + if (generate_DEFER(cctx, dfunc->df_defer_var_idx - 1, argcount) == FAIL) + return NULL; + + return skipwhite(arg); +} + +/* * compile "echo expr" * compile "echomsg expr" * compile "echoerr expr" diff --git a/src/vim9compile.c b/src/vim9compile.c index a785ba98db..02cbb25df5 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -2373,7 +2373,7 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx) r = generate_PUSHBLOB(cctx, blob_alloc()); break; case VAR_FUNC: - r = generate_PUSHFUNC(cctx, NULL, &t_func_void); + r = generate_PUSHFUNC(cctx, NULL, &t_func_void, TRUE); break; case VAR_LIST: r = generate_NEWLIST(cctx, 0, FALSE); @@ -2748,6 +2748,7 @@ compile_def_function( // Was compiled in this mode before: Free old instructions. delete_def_function_contents(dfunc, FALSE); ga_clear_strings(&dfunc->df_var_names); + dfunc->df_defer_var_idx = 0; } else { @@ -3249,6 +3250,10 @@ compile_def_function( line = compile_eval(p, &cctx); break; + case CMD_defer: + line = compile_defer(p, &cctx); + break; + case CMD_echo: case CMD_echon: case CMD_echoconsole: diff --git a/src/vim9execute.c b/src/vim9execute.c index a1311ee177..3e0a377738 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -101,9 +101,15 @@ struct ectx_S { static garray_T profile_info_ga = {0, 0, sizeof(profinfo_T), 20, NULL}; #endif +// Get pointer to item in the stack. +#define STACK_TV(idx) (((typval_T *)ectx->ec_stack.ga_data) + idx) + // Get pointer to item relative to the bottom of the stack, -1 is the last one. #define STACK_TV_BOT(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_stack.ga_len + (idx)) +// Get pointer to a local variable on the stack. Negative for arguments. +#define STACK_TV_VAR(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_frame_idx + STACK_FRAME_SIZE + idx) + void to_string_error(vartype_T vartype) { @@ -610,9 +616,6 @@ call_dfunc( return OK; } -// Get pointer to item in the stack. -#define STACK_TV(idx) (((typval_T *)ectx->ec_stack.ga_data) + idx) - // Double linked list of funcstack_T in use. static funcstack_T *first_funcstack = NULL; @@ -843,6 +846,89 @@ set_ref_in_funcstacks(int copyID) } /* + * Handle ISN_DEFER. Stack has a function reference and "argcount" arguments. + * The local variable that lists deferred functions is "var_idx". + * Returns OK or FAIL. + */ + static int +add_defer_func(int var_idx, int argcount, ectx_T *ectx) +{ + typval_T *defer_tv = STACK_TV_VAR(var_idx); + list_T *defer_l; + typval_T *func_tv; + list_T *l; + int i; + typval_T listval; + + if (defer_tv->v_type != VAR_LIST) + { + // first one, allocate the list + if (rettv_list_alloc(defer_tv) == FAIL) + return FAIL; + } + defer_l = defer_tv->vval.v_list; + + l = list_alloc_with_items(argcount + 1); + if (l == NULL) + return FAIL; + listval.v_type = VAR_LIST; + listval.vval.v_list = l; + listval.v_lock = 0; + if (list_insert_tv(defer_l, &listval, + defer_l == NULL ? NULL : defer_l->lv_first) == FAIL) + { + vim_free(l); + return FAIL; + } + + func_tv = STACK_TV_BOT(-argcount - 1); + // TODO: check type is a funcref + list_set_item(l, 0, func_tv); + + for (i = 1; i <= argcount; ++i) + list_set_item(l, i, STACK_TV_BOT(-argcount + i - 1)); + ectx->ec_stack.ga_len -= argcount + 1; + return OK; +} + +/* + * Invoked when returning from a function: Invoke any deferred calls. + */ + static void +invoke_defer_funcs(ectx_T *ectx) +{ + dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + + ectx->ec_dfunc_idx; + typval_T *defer_tv = STACK_TV_VAR(dfunc->df_defer_var_idx - 1); + listitem_T *li; + + if (defer_tv->v_type != VAR_LIST) + return; // no function added + for (li = defer_tv->vval.v_list->lv_first; li != NULL; li = li->li_next) + { + list_T *l = li->li_tv.vval.v_list; + typval_T rettv; + typval_T argvars[MAX_FUNC_ARGS]; + int i; + listitem_T *arg_li = l->lv_first; + funcexe_T funcexe; + + for (i = 0; i < l->lv_len - 1; ++i) + { + arg_li = arg_li->li_next; + argvars[i] = arg_li->li_tv; |