Mercurial > vim
diff src/userfunc.c @ 30065:6cf788ab844c v9.0.0370
patch 9.0.0370: cleaning up afterwards can make a function messy
Commit: https://github.com/vim/vim/commit/1d84f7608f1e41dad03b8cc7925895437775f7c0
Author: Bram Moolenaar <Bram@vim.org>
Date: Sat Sep 3 21:35:53 2022 +0100
patch 9.0.0370: cleaning up afterwards can make a function messy
Problem: Cleaning up afterwards can make a function messy.
Solution: Add the :defer command.
author | Bram Moolenaar <Bram@vim.org> |
---|---|
date | Sat, 03 Sep 2022 22:45:03 +0200 |
parents | cd573d7bc30d |
children | d45ee1f829ba |
line wrap: on
line diff
--- 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) - { - ++emsg_skip; - lnum = eap->line2; // do it once, also with an invalid range + if (eap->cmdidx == CMD_defer) + { + 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_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_doesrange = &doesrange; + funcexe.fe_found_var = found_var; 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; + 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)) + if ((!aborting() || did_throw) && (!failed || eap->cstack->cs_trylevel > 0)) { // Check for trailing illegal characters and a following command. arg = skipwhite(arg);