To: vim_dev@googlegroups.com Subject: Patch 9.0.0370 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 9.0.0370 Problem: Cleaning up afterwards can make a function messy. Solution: Add the :defer command. Files: runtime/doc/eval.txt, src/ex_cmds.h, src/ex_cmdidxs.h, src/userfunc.c, src/proto/userfunc.pro, src/structs.h, src/vim9compile.c, src/vim9cmds.c, src/proto/vim9cmds.pro, src/vim9.h, src/vim9instr.c, src/proto/vim9instr.pro, src/vim9expr.c, src/proto/vim9expr.pro, src/vim9execute.c, src/testdir/test_user_func.vim, src/testdir/test_vim9_func.vim, src/testdir/test_vim9_disassemble.vim *** ../vim-9.0.0369/runtime/doc/eval.txt 2022-08-29 18:16:11.574636829 +0100 --- runtime/doc/eval.txt 2022-09-03 17:36:27.213054907 +0100 *************** *** 2978,2983 **** --- 2979,3040 ---- let y = GetList()->Filter() + CLEANING UP IN A FUNCTION ~ + *:defer* + :defer {func}({args}) Call {func} when the current function is done. + {args} are evaluated here. + + Quite often a command in a function has a global effect, which must be undone + when the function finishes. Handling this in all kinds of situations can be a + hassle. Especially when an unexpected error is encountered. This can be done + with `try` / `finally` blocks, but this gets complicated when there is more + than one. + + A much simpler solution is using `defer`. It schedules a function call when + the function is returning, no matter if there is an error. Example: > + func Filter(text) + call writefile(a:text, 'Tempfile') + call system('filter < Tempfile > Outfile') + call Handle('Outfile') + call delete('Tempfile') + call delete('Outfile') + endfunc + + Here 'Tempfile' and 'Outfile' will not be deleted if something causes the + function to abort. `:defer` can be used to avoid that: > + func Filter(text) + call writefile(a:text, 'Tempfile') + defer delete('Tempfile') + defer delete('Outfile') + call system('filter < Tempfile > Outfile') + call Handle('Outfile') + endfunc + + Note that deleting "Outfile" is scheduled before calling system(), since it + can be created even when `system()` fails. + + The defered functions are called in reverse order, the last one added is + executed first. A useless example: > + func Useless() + for s in range(3) + defer execute('echomsg "number ' .. s .. '"') + endfor + endfunc + + Now `:messages` shows: + number 2 + number 1 + number 0 + + Any return value of the deferred function is discarded. The function cannot + be followed by anything, such as "->func" or ".member". Currently `:defer + GetArg()->TheFunc()` does not work, it may work in a later version. + + Errors are reported but do not cause aborting execution of deferred functions. + + No range is accepted. + + AUTOMATICALLY LOADING FUNCTIONS ~ *autoload-functions* When using many or large functions, it's possible to automatically define them *** ../vim-9.0.0369/src/ex_cmds.h 2022-08-31 17:48:05.711547579 +0100 --- src/ex_cmds.h 2022-09-03 14:45:42.389458416 +0100 *************** *** 467,472 **** --- 467,475 ---- 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), *** ../vim-9.0.0369/src/ex_cmdidxs.h 2022-08-31 17:48:05.711547579 +0100 --- src/ex_cmdidxs.h 2022-09-03 14:34:48.226709187 +0100 *************** *** 9,36 **** /* 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 }; /* --- 9,36 ---- /* b */ 21, /* c */ 45, /* d */ 112, ! /* 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,50 **** /* 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 }, /* 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 }, --- 44,50 ---- /* 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, 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,72 **** /* 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; --- 69,72 ---- /* 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 = 592; *** ../vim-9.0.0369/src/userfunc.c 2022-08-28 18:52:06.667888932 +0100 --- src/userfunc.c 2022-09-03 16:51:07.136075989 +0100 *************** *** 1728,1771 **** } /* ! * Allocate a variable for the result of a function. ! * 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); ! /* ! * Get the arguments. ! */ ! argp = *arg; ! while (argcount < MAX_FUNC_ARGS - (funcexe->fe_partial == NULL ? 0 ! : funcexe->fe_partial->pt_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) { ret = FAIL; break; } ! ++argcount; // The comma should come right after the argument, but this wasn't // checked previously, thus only enforce it in Vim9 script. if (vim9script) --- 1728,1763 ---- } /* ! * Get function arguments at "*arg" and advance it. ! * Return them in "*argvars[MAX_FUNC_ARGS + 1]" and the count in "argcount". */ ! static int ! get_func_arguments( ! char_u **arg, ! evalarg_T *evalarg, ! int partial_argc, ! typval_T *argvars, ! int *argcount) { ! char_u *argp = *arg; int ret = OK; int vim9script = in_vim9script(); int evaluate = evalarg == NULL ? FALSE : (evalarg->eval_flags & EVAL_EVALUATE); ! 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) { ret = FAIL; break; } ! ++*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,1801 **** --- 1783,1823 ---- 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,2889 **** --- 2906,2914 ---- 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,5464 **** --- 5482,5645 ---- 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,5476 **** 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; --- 5649,5654 ---- *************** *** 5482,5487 **** --- 5660,5667 ---- 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,5612 **** 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) { ! ++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; - CLEAR_FIELD(funcexe); ! funcexe.fe_firstline = eap->line1; ! funcexe.fe_lastline = eap->line2; ! funcexe.fe_doesrange = &doesrange; ! funcexe.fe_evaluate = !eap->skip; funcexe.fe_partial = partial; funcexe.fe_selfdict = fudi.fd_dict; ! funcexe.fe_check_type = type; 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; } - 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)) { // Check for trailing illegal characters and a following command. arg = skipwhite(arg); --- 5711,5739 ---- goto end; } ! if (eap->cmdidx == CMD_defer) { ! arg = startarg; ! failed = ex_defer_inner(name, &arg, &evalarg) == FAIL; } else { funcexe_T funcexe; CLEAR_FIELD(funcexe); ! funcexe.fe_check_type = type; funcexe.fe_partial = partial; funcexe.fe_selfdict = fudi.fd_dict; ! funcexe.fe_firstline = eap->line1; ! funcexe.fe_lastline = eap->line2; funcexe.fe_found_var = found_var; ! funcexe.fe_evaluate = !eap->skip; ! failed = ex_call_inner(eap, name, &arg, startarg, &funcexe, &evalarg); } // 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)) { // Check for trailing illegal characters and a following command. arg = skipwhite(arg); *** ../vim-9.0.0369/src/proto/userfunc.pro 2022-06-29 12:54:48.068572061 +0100 --- src/proto/userfunc.pro 2022-09-03 15:24:43.479993899 +0100 *************** *** 58,63 **** --- 58,64 ---- 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); *** ../vim-9.0.0369/src/structs.h 2022-08-29 18:16:11.578636822 +0100 --- src/structs.h 2022-09-03 15:58:17.286657171 +0100 *************** *** 1753,1758 **** --- 1753,1759 ---- 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,1772 **** --- 1768,1781 ---- // "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,2856 **** 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 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 --- 2859,2865 ---- 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; // 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 *** ../vim-9.0.0369/src/vim9compile.c 2022-09-03 10:52:18.395075356 +0100 --- src/vim9compile.c 2022-09-03 21:06:45.596800531 +0100 *************** *** 2373,2379 **** r = generate_PUSHBLOB(cctx, blob_alloc()); break; case VAR_FUNC: ! r = generate_PUSHFUNC(cctx, NULL, &t_func_void); break; case VAR_LIST: r = generate_NEWLIST(cctx, 0, FALSE); --- 2373,2379 ---- r = generate_PUSHBLOB(cctx, blob_alloc()); break; case VAR_FUNC: ! r = generate_PUSHFUNC(cctx, NULL, &t_func_void, TRUE); break; case VAR_LIST: r = generate_NEWLIST(cctx, 0, FALSE); *************** *** 2748,2753 **** --- 2748,2754 ---- // 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,3254 **** --- 3250,3259 ---- line = compile_eval(p, &cctx); break; + case CMD_defer: + line = compile_defer(p, &cctx); + break; + case CMD_echo: case CMD_echon: case CMD_echoconsole: *** ../vim-9.0.0369/src/vim9cmds.c 2022-09-01 16:00:49.730496296 +0100 --- src/vim9cmds.c 2022-09-03 21:11:16.524703244 +0100 *************** *** 1654,1659 **** --- 1654,1662 ---- return p; } + /* + * Compile an expression or function call. + */ char_u * compile_eval(char_u *arg, cctx_T *cctx) { *************** *** 1682,1687 **** --- 1685,1751 ---- } /* + * 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" *** ../vim-9.0.0369/src/proto/vim9cmds.pro 2022-06-27 23:15:29.000000000 +0100 --- src/proto/vim9cmds.pro 2022-09-03 19:27:40.826629675 +0100 *************** *** 21,26 **** --- 21,27 ---- 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); *** ../vim-9.0.0369/src/vim9.h 2022-09-01 16:00:49.726496301 +0100 --- src/vim9.h 2022-09-03 19:18:53.055031709 +0100 *************** *** 113,118 **** --- 113,119 ---- 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,424 **** --- 420,431 ---- 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,473 **** --- 475,481 ---- tobool_T tobool; getitem_T getitem; debug_T debug; + deferins_T defer; } isn_arg; }; *************** *** 498,503 **** --- 506,514 ---- 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,740 **** --- 746,760 ---- // 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 *** ../vim-9.0.0369/src/vim9instr.c 2022-09-01 17:26:14.163194043 +0100 --- src/vim9instr.c 2022-09-03 21:08:12.132769664 +0100 *************** *** 616,622 **** case VAR_FUNC: if (tv->vval.v_string != NULL) iemsg("non-null function constant not supported"); ! generate_PUSHFUNC(cctx, NULL, &t_func_unknown); break; case VAR_PARTIAL: if (tv->vval.v_partial != NULL) --- 616,622 ---- case VAR_FUNC: if (tv->vval.v_string != NULL) iemsg("non-null function constant not supported"); ! generate_PUSHFUNC(cctx, NULL, &t_func_unknown, TRUE); break; case VAR_PARTIAL: if (tv->vval.v_partial != NULL) *************** *** 796,804 **** /* * Generate an ISN_PUSHFUNC instruction with name "name". */ int ! generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type) { isn_T *isn; char_u *funcname; --- 796,806 ---- /* * Generate an ISN_PUSHFUNC instruction with name "name". + * When "may_prefix" is TRUE prefix "g:" unless "name" is script-local or + * autoload. */ int ! generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type, int may_prefix) { isn_T *isn; char_u *funcname; *************** *** 808,814 **** return FAIL; if (name == NULL) funcname = NULL; ! else if (*name == K_SPECIAL // script-local || vim_strchr(name, AUTOLOAD_CHAR) != NULL) // autoload funcname = vim_strsave(name); else --- 810,817 ---- return FAIL; if (name == NULL) funcname = NULL; ! else if (!may_prefix ! || *name == K_SPECIAL // script-local || vim_strchr(name, AUTOLOAD_CHAR) != NULL) // autoload funcname = vim_strsave(name); else *************** *** 1679,1684 **** --- 1682,1703 ---- } /* + * Generate an ISN_DEFER instruction. + */ + int + generate_DEFER(cctx_T *cctx, int var_idx, int argcount) + { + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr_drop(cctx, ISN_DEFER, argcount + 1)) == NULL) + return FAIL; + isn->isn_arg.defer.defer_var_idx = var_idx; + isn->isn_arg.defer.defer_argcount = argcount; + return OK; + } + + /* * Generate an ISN_STRINGMEMBER instruction. */ int *************** *** 2240,2245 **** --- 2259,2265 ---- case ISN_CONCAT: case ISN_COND2BOOL: case ISN_DEBUG: + case ISN_DEFER: case ISN_DROP: case ISN_ECHO: case ISN_ECHOCONSOLE: *************** *** 2296,2316 **** case ISN_STOREINDEX: case ISN_STORENR: case ISN_SOURCE: ! case ISN_STOREOUTER: ! case ISN_STORERANGE: ! case ISN_STOREREG: ! case ISN_STOREV: ! case ISN_STRINDEX: ! case ISN_STRSLICE: ! case ISN_THROW: ! case ISN_TRYCONT: ! case ISN_UNLETINDEX: ! case ISN_UNLETRANGE: ! case ISN_UNPACK: ! case ISN_USEDICT: // nothing allocated break; ! } } void --- 2316,2336 ---- case ISN_STOREINDEX: case ISN_STORENR: case ISN_SOURCE: ! case ISN_STOREOUTER: ! case ISN_STORERANGE: ! case ISN_STOREREG: ! case ISN_STOREV: ! case ISN_STRINDEX: ! case ISN_STRSLICE: ! case ISN_THROW: ! case ISN_TRYCONT: ! case ISN_UNLETINDEX: ! case ISN_UNLETRANGE: ! case ISN_UNPACK: ! case ISN_USEDICT: // nothing allocated break; ! } } void *** ../vim-9.0.0369/src/proto/vim9instr.pro 2022-06-27 23:15:30.000000000 +0100 --- src/proto/vim9instr.pro 2022-09-03 21:06:35.156804223 +0100 *************** *** 23,29 **** 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_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); --- 23,29 ---- 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 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,57 **** --- 52,58 ---- 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); *** ../vim-9.0.0369/src/vim9expr.c 2022-08-20 14:51:13.810510131 +0100 --- src/vim9expr.c 2022-09-03 21:07:27.052785757 +0100 *************** *** 313,319 **** // name. If a '(' follows it must be a function. Otherwise we // don't know, it can be "script.Func". if (cc == '(' || paren_follows_after_expr) ! res = generate_PUSHFUNC(cctx, auto_name, &t_func_any); else res = generate_AUTOLOAD(cctx, auto_name, &t_any); vim_free(auto_name); --- 313,319 ---- // name. If a '(' follows it must be a function. Otherwise we // don't know, it can be "script.Func". if (cc == '(' || paren_follows_after_expr) ! res = generate_PUSHFUNC(cctx, auto_name, &t_func_any, TRUE); else res = generate_AUTOLOAD(cctx, auto_name, &t_any); vim_free(auto_name); *************** *** 329,335 **** char_u sid_name[MAX_FUNC_NAME_LEN]; func_name_with_sid(exp_name, import->imp_sid, sid_name); ! res = generate_PUSHFUNC(cctx, sid_name, &t_func_any); } else res = generate_OLDSCRIPT(cctx, ISN_LOADEXPORT, exp_name, --- 329,335 ---- char_u sid_name[MAX_FUNC_NAME_LEN]; func_name_with_sid(exp_name, import->imp_sid, sid_name); ! res = generate_PUSHFUNC(cctx, sid_name, &t_func_any, TRUE); } else res = generate_OLDSCRIPT(cctx, ISN_LOADEXPORT, exp_name, *************** *** 353,359 **** if (ufunc != NULL) { // function call or function reference ! generate_PUSHFUNC(cctx, ufunc->uf_name, NULL); return OK; } return FAIL; --- 353,359 ---- if (ufunc != NULL) { // function call or function reference ! generate_PUSHFUNC(cctx, ufunc->uf_name, NULL, TRUE); return OK; } return FAIL; *************** *** 387,393 **** if (func_needs_compiling(ufunc, compile_type) && compile_def_function(ufunc, TRUE, compile_type, NULL) == FAIL) return FAIL; ! return generate_PUSHFUNC(cctx, ufunc->uf_name, ufunc->uf_func_type); } /* --- 387,393 ---- if (func_needs_compiling(ufunc, compile_type) && compile_def_function(ufunc, TRUE, compile_type, NULL) == FAIL) return FAIL; ! return generate_PUSHFUNC(cctx, ufunc->uf_name, ufunc->uf_func_type, TRUE); } /* *************** *** 610,628 **** } /* - * 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; - - /* * Compile the argument expressions. * "arg" points to just after the "(" and is advanced to after the ")" */ ! static int compile_arguments( char_u **arg, cctx_T *cctx, --- 610,619 ---- } /* * Compile the argument expressions. * "arg" points to just after the "(" and is advanced to after the ")" */ ! int compile_arguments( char_u **arg, cctx_T *cctx, *** ../vim-9.0.0369/src/proto/vim9expr.pro 2022-06-27 23:15:30.000000000 +0100 --- src/proto/vim9expr.pro 2022-09-03 19:27:49.334623659 +0100 *************** *** 4,9 **** --- 4,10 ---- 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); *** ../vim-9.0.0369/src/vim9execute.c 2022-09-01 16:00:49.730496296 +0100 --- src/vim9execute.c 2022-09-03 20:48:19.693212011 +0100 *************** *** 101,109 **** --- 101,115 ---- 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,618 **** 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; --- 616,621 ---- *************** *** 843,848 **** --- 846,934 ---- } /* + * 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; + } + + CLEAR_FIELD(funcexe); + funcexe.fe_evaluate = TRUE; + rettv.v_type = VAR_UNKNOWN; + (void)call_func(l->lv_first->li_tv.vval.v_string, -1, + &rettv, l->lv_len - 1, argvars, &funcexe); + clear_tv(&rettv); + } + } + + /* * Return from the current function. */ static int *************** *** 876,881 **** --- 962,970 ---- } #endif + if (dfunc->df_defer_var_idx > 0) + invoke_defer_funcs(ectx); + // No check for uf_refcount being zero, cannot think of a way that would // happen. --dfunc->df_ufunc->uf_calls; *************** *** 949,956 **** return OK; } - #undef STACK_TV - /* * Prepare arguments and rettv for calling a builtin or user function. */ --- 1038,1043 ---- *************** *** 1732,1747 **** int subs_status; } subs_expr_T; - // Get pointer to item in the stack. - #define STACK_TV(idx) (((typval_T *)ectx->ec_stack.ga_data) + idx) - - // Get pointer to item at the bottom of the stack, -1 is the bottom. - #undef STACK_TV_BOT - #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) - // Set when calling do_debug(). static ectx_T *debug_context = NULL; static int debug_var_count; --- 1819,1824 ---- *************** *** 3670,3675 **** --- 3747,3759 ---- } break; + // :defer func(arg) + case ISN_DEFER: + if (add_defer_func(iptr->isn_arg.defer.defer_var_idx, + iptr->isn_arg.defer.defer_argcount, ectx) == FAIL) + goto on_error; + break; + // return from a :def function call without a value case ISN_RETURN_VOID: if (GA_GROW_FAILS(&ectx->ec_stack, 1)) *************** *** 5024,5029 **** --- 5108,5121 ---- done: ret = OK; theend: + { + dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + + ectx->ec_dfunc_idx; + + if (dfunc->df_defer_var_idx > 0) + invoke_defer_funcs(ectx); + } + dict_stack_clear(dict_stack_len_at_start); ectx->ec_trylevel_at_start = save_trylevel_at_start; return ret; *************** *** 5903,5908 **** --- 5995,6004 ---- case ISN_PCALL_END: smsg("%s%4d PCALL end", pfx, current); break; + case ISN_DEFER: + smsg("%s%4d DEFER %d args", pfx, current, + (int)iptr->isn_arg.defer.defer_argcount); + break; case ISN_RETURN: smsg("%s%4d RETURN", pfx, current); break; *** ../vim-9.0.0369/src/testdir/test_user_func.vim 2022-04-09 10:59:49.000000000 +0100 --- src/testdir/test_user_func.vim 2022-09-03 21:28:20.276325774 +0100 *************** *** 529,532 **** --- 529,564 ---- 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 *** ../vim-9.0.0369/src/testdir/test_vim9_func.vim 2022-08-30 18:42:13.170331381 +0100 --- src/testdir/test_vim9_func.vim 2022-09-03 21:22:34.784454129 +0100 *************** *** 4272,4277 **** --- 4272,4307 ---- 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() *** ../vim-9.0.0369/src/testdir/test_vim9_disassemble.vim 2022-09-01 16:00:49.730496296 +0100 --- src/testdir/test_vim9_disassemble.vim 2022-09-03 21:12:37.848673708 +0100 *************** *** 2900,2903 **** --- 2900,2918 ---- '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 *** ../vim-9.0.0369/src/version.c 2022-09-03 13:58:42.210028187 +0100 --- src/version.c 2022-09-03 21:28:53.004313588 +0100 *************** *** 709,710 **** --- 709,712 ---- { /* Add new patch number below this line */ + /**/ + 370, /**/ -- Git catch 22: "merge is not possible because you have unmerged files." /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///