From 9b140fafe3c002a1c67fe35af231bbfe23302ea1 Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Sun, 9 Feb 2020 15:34:39 +0100 Subject: moved functions to user_funcs.c (no code changes) --- src/nvim/eval.c | 3589 +++----------------------------------------- src/nvim/eval/user_funcs.c | 3188 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 3389 insertions(+), 3388 deletions(-) create mode 100644 src/nvim/eval/user_funcs.c (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index ddafe1981f..98f2bf77a5 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -62,12 +62,7 @@ static char *e_letunexp = N_("E18: Unexpected characters in :let"); static char *e_missbrac = N_("E111: Missing ']'"); -static char *e_funcexts = N_( - "E122: Function %s already exists, add ! to replace it"); -static char *e_funcdict = N_("E717: Dictionary entry already exists"); -static char *e_funcref = N_("E718: Funcref required"); static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary"); -static char *e_nofunc = N_("E130: Unknown function: %s"); static char *e_illvar = N_("E461: Illegal variable name: %s"); static char *e_cannot_mod = N_("E995: Cannot modify existing variable"); @@ -88,8 +83,6 @@ static ScopeDictDictItem globvars_var; */ static hashtab_T compat_hashtab; -hashtab_T func_hashtab; - // Used for checking if local variables or arguments used in a lambda. static int *eval_lavars_used = NULL; @@ -108,49 +101,9 @@ static garray_T ga_scripts = {0, 0, sizeof(scriptvar_T *), 4, NULL}; static int echo_attr = 0; /* attributes used for ":echo" */ -// flags used in uf_flags -#define FC_ABORT 0x01 // abort function on error -#define FC_RANGE 0x02 // function accepts range -#define FC_DICT 0x04 // Dict function, uses "self" -#define FC_CLOSURE 0x08 // closure, uses outer scope variables -#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0 -#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0 -#define FC_SANDBOX 0x40 // function defined in the sandbox - // The names of packages that once were loaded are remembered. static garray_T ga_loaded = { 0, 0, sizeof(char_u *), 4, NULL }; -#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] -#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j] - -/// Short variable name length -#define VAR_SHORT_LEN 20 -/// Number of fixed variables used for arguments -#define FIXVAR_CNT 12 - -struct funccall_S { - ufunc_T *func; ///< Function being called. - int linenr; ///< Next line to be executed. - int returned; ///< ":return" used. - /// Fixed variables for arguments. - TV_DICTITEM_STRUCT(VAR_SHORT_LEN + 1) fixvar[FIXVAR_CNT]; - dict_T l_vars; ///< l: local function variables. - ScopeDictDictItem l_vars_var; ///< Variable for l: scope. - dict_T l_avars; ///< a: argument variables. - ScopeDictDictItem l_avars_var; ///< Variable for a: scope. - list_T l_varlist; ///< List for a:000. - listitem_T l_listitems[MAX_FUNC_ARGS]; ///< List items for a:000. - typval_T *rettv; ///< Return value. - 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. - proftime_T prof_child; ///< Time spent in a child. - funccall_T *caller; ///< Calling function or NULL. - int fc_refcount; ///< Number of user functions that reference this funccall. - int fc_copyID; ///< CopyID used for garbage collection. - garray_T fc_funcs; ///< List of ufunc_T* which keep a reference to "func". -}; - /* * Info used by a ":for" loop. */ @@ -495,53 +448,6 @@ void eval_clear(void) #endif -/* - * Return the name of the executed function. - */ -char_u *func_name(void *cookie) -{ - return ((funccall_T *)cookie)->func->uf_name; -} - -/* - * Return the address holding the next breakpoint line for a funccall cookie. - */ -linenr_T *func_breakpoint(void *cookie) -{ - return &((funccall_T *)cookie)->breakpoint; -} - -/* - * Return the address holding the debug tick for a funccall cookie. - */ -int *func_dbg_tick(void *cookie) -{ - return &((funccall_T *)cookie)->dbg_tick; -} - -/* - * Return the nesting level for a funccall cookie. - */ -int func_level(void *cookie) -{ - return ((funccall_T *)cookie)->level; -} - -/* pointer to funccal for currently active function */ -funccall_T *current_funccal = NULL; - -// Pointer to list of previously used funccal, still around because some -// item in it is still being used. -funccall_T *previous_funccal = NULL; - -/* - * Return TRUE when a function was ended by a ":return" command. - */ -int current_func_returned(void) -{ - return current_funccal->returned; -} - /* * Set an internal variable to a string value. Creates the variable if it does * not already exist. @@ -1216,25 +1122,6 @@ void *call_func_retlist(const char_u *func, int argc, typval_T *argv) return rettv.vval.v_list; } -/* - * Save the current function call pointer, and set it to NULL. - * Used when executing autocommands and for ":source". - */ -void *save_funccal(void) -{ - funccall_T *fc = current_funccal; - - current_funccal = NULL; - return (void *)fc; -} - -void restore_funccal(void *vfc) -{ - funccall_T *fc = (funccall_T *)vfc; - - current_funccal = fc; -} - /* * Prepare profiling for entering a child or something else that is not * counted for the script/function itself. @@ -1767,17 +1654,6 @@ static void list_script_vars(int *first) } } -/* - * List function variables, if there is a function. - */ -static void list_func_vars(int *first) -{ - if (current_funccal != NULL) { - list_hashtable_vars(¤t_funccal->l_vars.dv_hashtab, "l:", false, - first); - } -} - /* * List variables in "arg". */ @@ -2748,127 +2624,6 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx) // TODO(ZyX-I): move to eval/ex_cmds -/* - * ":1,25call func(arg1, arg2)" function call. - */ -void ex_call(exarg_T *eap) -{ - char_u *arg = eap->arg; - char_u *startarg; - char_u *name; - char_u *tofree; - int len; - typval_T rettv; - linenr_T lnum; - int doesrange; - bool failed = false; - funcdict_T fudi; - partial_T *partial = NULL; - - if (eap->skip) { - // 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. - emsg_skip++; - if (eval0(eap->arg, &rettv, &eap->nextcmd, false) != FAIL) { - tv_clear(&rettv); - } - emsg_skip--; - return; - } - - tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial); - if (fudi.fd_newkey != NULL) { - // Still need to give an error message for missing key. - EMSG2(_(e_dictkey), fudi.fd_newkey); - xfree(fudi.fd_newkey); - } - if (tofree == NULL) { - return; - } - - // Increase refcount on dictionary, it could get deleted when evaluating - // the arguments. - if (fudi.fd_dict != NULL) { - fudi.fd_dict->dv_refcount++; - } - - // If it is the name of a variable of type VAR_FUNC or VAR_PARTIAL use its - // contents. For VAR_PARTIAL get its partial, unless we already have one - // from trans_function_name(). - len = (int)STRLEN(tofree); - name = deref_func_name((const char *)tofree, &len, - partial != NULL ? NULL : &partial, false); - - // Skip white space to allow ":call func ()". Not good, but required for - // backward compatibility. - startarg = skipwhite(arg); - rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this. - - if (*startarg != '(') { - EMSG2(_("E107: Missing parentheses: %s"), eap->arg); - goto end; - } - - lnum = eap->line1; - for (; lnum <= eap->line2; lnum++) { - if (eap->addr_count > 0) { // -V560 - 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_invrange)); - break; - } - curwin->w_cursor.lnum = lnum; - curwin->w_cursor.col = 0; - curwin->w_cursor.coladd = 0; - } - arg = startarg; - if (get_func_tv(name, (int)STRLEN(name), &rettv, &arg, - eap->line1, eap->line2, &doesrange, - true, partial, fudi.fd_dict) == FAIL) { - failed = true; - break; - } - - // Handle a function returning a Funcref, Dictionary or List. - if (handle_subscript((const char **)&arg, &rettv, true, true) - == FAIL) { - failed = true; - break; - } - - tv_clear(&rettv); - if (doesrange) { - 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 (!failed) { - // Check for trailing illegal characters and a following command. - if (!ends_excmd(*arg)) { - emsg_severe = TRUE; - EMSG(_(e_trailing)); - } else { - eap->nextcmd = check_nextcmd(arg); - } - } - -end: - tv_dict_unref(fudi.fd_dict); - xfree(tofree); -} - -// TODO(ZyX-I): move to eval/ex_cmds - /* * ":unlet[!] var1 ... " command. */ @@ -5076,9 +4831,6 @@ int get_copyID(void) return current_copyID; } -// Used by get_func_tv() -static garray_T funcargs = GA_EMPTY_INIT_VALUE; - /* * Garbage collection for lists and dictionaries. * @@ -5551,27 +5303,6 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, return abort; } -/// Set "copyID" in all functions available by name. -bool set_ref_in_functions(int copyID) -{ - int todo; - hashitem_T *hi = NULL; - bool abort = false; - ufunc_T *fp; - - todo = (int)func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) { - if (!HASHITEM_EMPTY(hi)) { - todo--; - fp = HI2UF(hi); - if (!func_name_refcount(fp->uf_name)) { - abort = abort || set_ref_in_func(NULL, fp, copyID); - } - } - } - return abort; -} - /// Mark all lists and dicts referenced in given mark @@ -5620,19 +5351,6 @@ static inline bool set_ref_dict(dict_T *dict, int copyID) return false; } -static bool set_ref_in_funccal(funccall_T *fc, int copyID) -{ - bool abort = false; - - if (fc->fc_copyID != copyID) { - fc->fc_copyID = copyID; - abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL); - abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL); - abort = abort || set_ref_in_func(NULL, fc->func, copyID); - } - return abort; -} - /* * Allocate a variable for a Dictionary and fill it from "*arg". * Return OK or FAIL. Returns NOTDONE for {expr}. @@ -5735,224 +5453,6 @@ failret: return OK; } -/// Get function arguments. -static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, - int *varargs, bool skip) -{ - bool mustend = false; - char_u *arg = *argp; - char_u *p = arg; - int c; - int i; - - if (newargs != NULL) { - ga_init(newargs, (int)sizeof(char_u *), 3); - } - - if (varargs != NULL) { - *varargs = false; - } - - // Isolate the arguments: "arg1, arg2, ...)" - while (*p != endchar) { - if (p[0] == '.' && p[1] == '.' && p[2] == '.') { - if (varargs != NULL) { - *varargs = true; - } - p += 3; - mustend = true; - } else { - arg = p; - while (ASCII_ISALNUM(*p) || *p == '_') { - p++; - } - if (arg == p || isdigit(*arg) - || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0) - || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) { - if (!skip) { - EMSG2(_("E125: Illegal argument: %s"), arg); - } - break; - } - if (newargs != NULL) { - ga_grow(newargs, 1); - c = *p; - *p = NUL; - arg = vim_strsave(arg); - - // Check for duplicate argument name. - for (i = 0; i < newargs->ga_len; i++) { - if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0) { - EMSG2(_("E853: Duplicate argument name: %s"), arg); - xfree(arg); - goto err_ret; - } - } - ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg; - newargs->ga_len++; - - *p = c; - } - if (*p == ',') { - p++; - } else { - mustend = true; - } - } - p = skipwhite(p); - if (mustend && *p != endchar) { - if (!skip) { - EMSG2(_(e_invarg2), *argp); - } - break; - } - } - if (*p != endchar) { - goto err_ret; - } - p++; // skip "endchar" - - *argp = p; - return OK; - -err_ret: - if (newargs != NULL) { - ga_clear_strings(newargs); - } - return FAIL; -} - -/// Register function "fp" as using "current_funccal" as its scope. -static void register_closure(ufunc_T *fp) -{ - if (fp->uf_scoped == current_funccal) { - // no change - return; - } - funccal_unref(fp->uf_scoped, fp, false); - fp->uf_scoped = current_funccal; - current_funccal->fc_refcount++; - ga_grow(¤t_funccal->fc_funcs, 1); - ((ufunc_T **)current_funccal->fc_funcs.ga_data) - [current_funccal->fc_funcs.ga_len++] = fp; -} - -/// Parse a lambda expression and get a Funcref from "*arg". -/// -/// @return OK or FAIL. Returns NOTDONE for dict or {expr}. -static int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) -{ - garray_T newargs = GA_EMPTY_INIT_VALUE; - garray_T *pnewargs; - ufunc_T *fp = NULL; - int varargs; - int ret; - char_u *start = skipwhite(*arg + 1); - char_u *s, *e; - static int lambda_no = 0; - int *old_eval_lavars = eval_lavars_used; - int eval_lavars = false; - - // First, check if this is a lambda expression. "->" must exists. - ret = get_function_args(&start, '-', NULL, NULL, true); - if (ret == FAIL || *start != '>') { - return NOTDONE; - } - - // Parse the arguments again. - if (evaluate) { - pnewargs = &newargs; - } else { - pnewargs = NULL; - } - *arg = skipwhite(*arg + 1); - ret = get_function_args(arg, '-', pnewargs, &varargs, false); - if (ret == FAIL || **arg != '>') { - goto errret; - } - - // Set up a flag for checking local variables and arguments. - if (evaluate) { - eval_lavars_used = &eval_lavars; - } - - // Get the start and the end of the expression. - *arg = skipwhite(*arg + 1); - s = *arg; - ret = skip_expr(arg); - if (ret == FAIL) { - goto errret; - } - e = *arg; - *arg = skipwhite(*arg); - if (**arg != '}') { - goto errret; - } - (*arg)++; - - if (evaluate) { - int len, flags = 0; - char_u *p; - char_u name[20]; - partial_T *pt; - garray_T newlines; - - lambda_no++; - snprintf((char *)name, sizeof(name), "%d", lambda_no); - - fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); - pt = xcalloc(1, sizeof(partial_T)); - - ga_init(&newlines, (int)sizeof(char_u *), 1); - ga_grow(&newlines, 1); - - // Add "return " before the expression. - len = 7 + e - s + 1; - p = (char_u *)xmalloc(len); - ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p; - STRCPY(p, "return "); - STRLCPY(p + 7, s, e - s + 1); - - fp->uf_refcount = 1; - STRCPY(fp->uf_name, name); - hash_add(&func_hashtab, UF2HIKEY(fp)); - fp->uf_args = newargs; - fp->uf_lines = newlines; - if (current_funccal != NULL && eval_lavars) { - flags |= FC_CLOSURE; - register_closure(fp); - } else { - fp->uf_scoped = NULL; - } - - if (prof_def_func()) { - func_do_profile(fp); - } - if (sandbox) { - flags |= FC_SANDBOX; - } - fp->uf_varargs = true; - fp->uf_flags = flags; - fp->uf_calls = 0; - fp->uf_script_ctx = current_sctx; - fp->uf_script_ctx.sc_lnum += sourcing_lnum - newlines.ga_len; - - pt->pt_func = fp; - pt->pt_refcount = 1; - rettv->vval.v_partial = pt; - rettv->v_type = VAR_PARTIAL; - } - - eval_lavars_used = old_eval_lavars; - return OK; - -errret: - ga_clear_strings(&newargs); - xfree(fp); - eval_lavars_used = old_eval_lavars; - return FAIL; -} - /// Convert the string to a floating point number /// /// This uses strtod(). setlocale(LC_NUMERIC, "C") has been used earlier to @@ -6027,137 +5527,6 @@ static int get_env_tv(char_u **arg, typval_T *rettv, int evaluate) return OK; } -/// Return name of the function corresponding to `name` -/// -/// If `name` points to variable that is either a function or partial then -/// corresponding function name is returned. Otherwise it returns `name` itself. -/// -/// @param[in] name Function name to check. -/// @param[in,out] lenp Location where length of the returned name is stored. -/// Must be set to the length of the `name` argument. -/// @param[out] partialp Location where partial will be stored if found -/// function appears to be a partial. May be NULL if this -/// is not needed. -/// @param[in] no_autoload If true, do not source autoload scripts if function -/// was not found. -/// -/// @return name of the function. -static char_u *deref_func_name(const char *name, int *lenp, - partial_T **const partialp, bool no_autoload) - FUNC_ATTR_NONNULL_ARG(1, 2) -{ - if (partialp != NULL) { - *partialp = NULL; - } - - dictitem_T *const v = find_var(name, (size_t)(*lenp), NULL, no_autoload); - if (v != NULL && v->di_tv.v_type == VAR_FUNC) { - if (v->di_tv.vval.v_string == NULL) { // just in case - *lenp = 0; - return (char_u *)""; - } - *lenp = (int)STRLEN(v->di_tv.vval.v_string); - return v->di_tv.vval.v_string; - } - - if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) { - partial_T *const pt = v->di_tv.vval.v_partial; - - if (pt == NULL) { // just in case - *lenp = 0; - return (char_u *)""; - } - if (partialp != NULL) { - *partialp = pt; - } - char_u *s = partial_name(pt); - *lenp = (int)STRLEN(s); - return s; - } - - return (char_u *)name; -} - -/* - * Allocate a variable for the result of a function. - * Return OK or FAIL. - */ -static int -get_func_tv( - const char_u *name, // name of the function - int len, // length of "name" - typval_T *rettv, - char_u **arg, // argument, pointing to the '(' - linenr_T firstline, // first line of range - linenr_T lastline, // last line of range - int *doesrange, // return: function handled range - int evaluate, - partial_T *partial, // for extra arguments - dict_T *selfdict // Dictionary for "self" -) -{ - char_u *argp; - int ret = OK; - typval_T argvars[MAX_FUNC_ARGS + 1]; /* vars for arguments */ - int argcount = 0; /* number of arguments found */ - - /* - * Get the arguments. - */ - argp = *arg; - while (argcount < MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { - argp = skipwhite(argp + 1); // skip the '(' or ',' - if (*argp == ')' || *argp == ',' || *argp == NUL) { - break; - } - if (eval1(&argp, &argvars[argcount], evaluate) == FAIL) { - ret = FAIL; - break; - } - ++argcount; - if (*argp != ',') - break; - } - if (*argp == ')') - ++argp; - else - ret = FAIL; - - if (ret == OK) { - int i = 0; - - if (get_vim_var_nr(VV_TESTING)) { - // Prepare for calling garbagecollect_for_testing(), need to know - // what variables are used on the call stack. - if (funcargs.ga_itemsize == 0) { - ga_init(&funcargs, (int)sizeof(typval_T *), 50); - } - for (i = 0; i < argcount; i++) { - ga_grow(&funcargs, 1); - ((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i]; - } - } - ret = call_func(name, len, rettv, argcount, argvars, NULL, - firstline, lastline, doesrange, evaluate, - partial, selfdict); - - funcargs.ga_len -= i; - } else if (!aborting()) { - if (argcount == MAX_FUNC_ARGS) { - emsg_funcname(N_("E740: Too many arguments for function %s"), name); - } else { - emsg_funcname(N_("E116: Invalid arguments for function %s"), name); - } - } - - while (--argcount >= 0) { - tv_clear(&argvars[argcount]); - } - - *arg = skipwhite(argp); - return ret; -} - typedef enum { ERROR_UNKNOWN = 0, ERROR_TOOMANY, @@ -6170,316 +5539,6 @@ typedef enum { ERROR_DELETED, } FnameTransError; -#define FLEN_FIXED 40 - -/// In a script transform script-local names into actually used names -/// -/// Transforms "" and "s:" prefixes to `K_SNR {N}` (e.g. K_SNR "123") and -/// "" prefix to `K_SNR`. Uses `fname_buf` buffer that is supposed to have -/// #FLEN_FIXED + 1 length when it fits, otherwise it allocates memory. -/// -/// @param[in] name Name to transform. -/// @param fname_buf Buffer to save resulting function name to, if it fits. -/// Must have at least #FLEN_FIXED + 1 length. -/// @param[out] tofree Location where pointer to an allocated memory is saved -/// in case result does not fit into fname_buf. -/// @param[out] error Location where error type is saved, @see -/// FnameTransError. -/// -/// @return transformed name: either `fname_buf` or a pointer to an allocated -/// memory. -static char_u *fname_trans_sid(const char_u *const name, - char_u *const fname_buf, - char_u **const tofree, int *const error) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT -{ - char_u *fname; - const int llen = eval_fname_script((const char *)name); - if (llen > 0) { - fname_buf[0] = K_SPECIAL; - fname_buf[1] = KS_EXTRA; - fname_buf[2] = (int)KE_SNR; - int i = 3; - if (eval_fname_sid((const char *)name)) { // "" or "s:" - if (current_sctx.sc_sid <= 0) { - *error = ERROR_SCRIPT; - } else { - snprintf((char *)fname_buf + 3, FLEN_FIXED + 1, "%" PRId64 "_", - (int64_t)current_sctx.sc_sid); - i = (int)STRLEN(fname_buf); - } - } - if (i + STRLEN(name + llen) < FLEN_FIXED) { - STRCPY(fname_buf + i, name + llen); - fname = fname_buf; - } else { - fname = xmalloc(i + STRLEN(name + llen) + 1); - *tofree = fname; - memmove(fname, fname_buf, (size_t)i); - STRCPY(fname + i, name + llen); - } - } else { - fname = (char_u *)name; - } - - return fname; -} - -/// Mark all lists and dicts referenced through function "name" with "copyID". -/// "list_stack" is used to add lists to be marked. Can be NULL. -/// "ht_stack" is used to add hashtabs to be marked. Can be NULL. -/// -/// @return true if setting references failed somehow. -bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) -{ - ufunc_T *fp = fp_in; - funccall_T *fc; - int error = ERROR_NONE; - char_u fname_buf[FLEN_FIXED + 1]; - char_u *tofree = NULL; - char_u *fname; - bool abort = false; - if (name == NULL && fp_in == NULL) { - return false; - } - - if (fp_in == NULL) { - fname = fname_trans_sid(name, fname_buf, &tofree, &error); - fp = find_func(fname); - } - if (fp != NULL) { - for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped) { - abort = abort || set_ref_in_funccal(fc, copyID); - } - } - xfree(tofree); - return abort; -} - -/// Call a function with its resolved parameters -/// -/// "argv_func", when not NULL, can be used to fill in arguments only when the -/// invoked function uses them. It is called like this: -/// new_argcount = argv_func(current_argcount, argv, called_func_argcount) -/// -/// @return FAIL if function cannot be called, else OK (even if an error -/// occurred while executing the function! Set `msg_list` to capture -/// the error, see do_cmdline()). -int -call_func( - const char_u *funcname, // name of the function - int len, // length of "name" - typval_T *rettv, // [out] value goes here - int argcount_in, // number of "argvars" - typval_T *argvars_in, // vars for arguments, must have "argcount" - // PLUS ONE elements! - ArgvFunc argv_func, // function to fill in argvars - linenr_T firstline, // first line of range - linenr_T lastline, // last line of range - int *doesrange, // [out] function handled range - bool evaluate, - partial_T *partial, // optional, can be NULL - dict_T *selfdict_in // Dictionary for "self" -) - FUNC_ATTR_NONNULL_ARG(1, 3, 5, 9) -{ - int ret = FAIL; - int error = ERROR_NONE; - ufunc_T *fp; - char_u fname_buf[FLEN_FIXED + 1]; - char_u *tofree = NULL; - char_u *fname; - char_u *name; - int argcount = argcount_in; - typval_T *argvars = argvars_in; - dict_T *selfdict = selfdict_in; - typval_T argv[MAX_FUNC_ARGS + 1]; // used when "partial" is not NULL - int argv_clear = 0; - - // Initialize rettv so that it is safe for caller to invoke clear_tv(rettv) - // even when call_func() returns FAIL. - rettv->v_type = VAR_UNKNOWN; - - // Make a copy of the name, if it comes from a funcref variable it could - // be changed or deleted in the called function. - name = vim_strnsave(funcname, len); - - fname = fname_trans_sid(name, fname_buf, &tofree, &error); - - *doesrange = false; - - if (partial != NULL) { - // When the function has a partial with a dict and there is a dict - // argument, use the dict argument. That is backwards compatible. - // When the dict was bound explicitly use the one from the partial. - if (partial->pt_dict != NULL - && (selfdict_in == NULL || !partial->pt_auto)) { - selfdict = partial->pt_dict; - } - if (error == ERROR_NONE && partial->pt_argc > 0) { - for (argv_clear = 0; argv_clear < partial->pt_argc; argv_clear++) { - tv_copy(&partial->pt_argv[argv_clear], &argv[argv_clear]); - } - for (int i = 0; i < argcount_in; i++) { - argv[i + argv_clear] = argvars_in[i]; - } - argvars = argv; - argcount = partial->pt_argc + argcount_in; - } - } - - if (error == ERROR_NONE && evaluate) { - char_u *rfname = fname; - - /* Ignore "g:" before a function name. */ - if (fname[0] == 'g' && fname[1] == ':') { - rfname = fname + 2; - } - - rettv->v_type = VAR_NUMBER; /* default rettv is number zero */ - rettv->vval.v_number = 0; - error = ERROR_UNKNOWN; - - if (partial == vvlua_partial) { - if (len > 0) { - error = ERROR_NONE; - executor_call_lua((const char *)funcname, len, - argvars, argcount, rettv); - } - } else if (!builtin_function((const char *)rfname, -1)) { - // User defined function. - if (partial != NULL && partial->pt_func != NULL) { - fp = partial->pt_func; - } else { - fp = find_func(rfname); - } - - // Trigger FuncUndefined event, may load the function. - if (fp == NULL - && apply_autocmds(EVENT_FUNCUNDEFINED, rfname, rfname, TRUE, NULL) - && !aborting()) { - /* executed an autocommand, search for the function again */ - fp = find_func(rfname); - } - // Try loading a package. - if (fp == NULL && script_autoload((const char *)rfname, STRLEN(rfname), - true) && !aborting()) { - // Loaded a package, search for the function again. - fp = find_func(rfname); - } - - if (fp != NULL && (fp->uf_flags & FC_DELETED)) { - error = ERROR_DELETED; - } else if (fp != NULL) { - if (argv_func != NULL) { - argcount = argv_func(argcount, argvars, fp->uf_args.ga_len); - } - if (fp->uf_flags & FC_RANGE) { - *doesrange = true; - } - if (argcount < fp->uf_args.ga_len) { - error = ERROR_TOOFEW; - } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) { - error = ERROR_TOOMANY; - } else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) { - error = ERROR_DICT; - } else { - // Call the user function. - call_user_func(fp, argcount, argvars, rettv, firstline, lastline, - (fp->uf_flags & FC_DICT) ? selfdict : NULL); - error = ERROR_NONE; - } - } - } else { - // Find the function name in the table, call its implementation. - const VimLFuncDef *const fdef = find_internal_func((const char *)fname); - if (fdef != NULL) { - if (argcount < fdef->min_argc) { - error = ERROR_TOOFEW; - } else if (argcount > fdef->max_argc) { - error = ERROR_TOOMANY; - } else { - argvars[argcount].v_type = VAR_UNKNOWN; - fdef->func(argvars, rettv, fdef->data); - error = ERROR_NONE; - } - } - } - /* - * The function call (or "FuncUndefined" autocommand sequence) might - * have been aborted by an error, an interrupt, or an explicitly thrown - * exception that has not been caught so far. This situation can be - * tested for by calling aborting(). For an error in an internal - * function or for the "E132" error in call_user_func(), however, the - * throw point at which the "force_abort" flag (temporarily reset by - * emsg()) is normally updated has not been reached yet. We need to - * update that flag first to make aborting() reliable. - */ - update_force_abort(); - } - if (error == ERROR_NONE) - ret = OK; - - /* - * Report an error unless the argument evaluation or function call has been - * cancelled due to an aborting error, an interrupt, or an exception. - */ - if (!aborting()) { - switch (error) { - case ERROR_UNKNOWN: - emsg_funcname(N_("E117: Unknown function: %s"), name); - break; - case ERROR_DELETED: - emsg_funcname(N_("E933: Function was deleted: %s"), name); - break; - case ERROR_TOOMANY: - emsg_funcname(_(e_toomanyarg), name); - break; - case ERROR_TOOFEW: - emsg_funcname(N_("E119: Not enough arguments for function: %s"), - name); - break; - case ERROR_SCRIPT: - emsg_funcname(N_("E120: Using not in a script context: %s"), - name); - break; - case ERROR_DICT: - emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"), - name); - break; - } - } - - while (argv_clear > 0) { - tv_clear(&argv[--argv_clear]); - } - xfree(tofree); - xfree(name); - - return ret; -} - -/// Give an error message with a function name. Handle things. -/// -/// @param ermsg must be passed without translation (use N_() instead of _()). -/// @param name function name -static void emsg_funcname(char *ermsg, const char_u *name) -{ - char_u *p; - - if (*name == K_SPECIAL) { - p = concat_str((char_u *)"", name + 3); - } else { - p = (char_u *)name; - } - - EMSG2(_(ermsg), p); - - if (p != name) { - xfree(p); - } -} - /// Get the argument list for a given window void get_arglist_as_rettv(aentry_T *arglist, int argcount, typval_T *rettv) @@ -6828,37 +5887,6 @@ int assert_match_common(typval_T *argvars, assert_type_T atype) return 0; } -int func_call(char_u *name, typval_T *args, partial_T *partial, - dict_T *selfdict, typval_T *rettv) -{ - typval_T argv[MAX_FUNC_ARGS + 1]; - int argc = 0; - int dummy; - int r = 0; - - TV_LIST_ITER(args->vval.v_list, item, { - if (argc == MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { - EMSG(_("E699: Too many arguments")); - goto func_call_skip_call; - } - // Make a copy of each argument. This is needed to be able to set - // v_lock to VAR_FIXED in the copy without changing the original list. - tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]); - }); - - r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &dummy, true, partial, selfdict); - -func_call_skip_call: - // Free the arguments. - while (argc > 0) { - tv_clear(&argv[--argc]); - } - - return r; -} - /// Find a window: When using a Window ID in any tab page, when using a number /// in the current tab page. win_T * find_win_by_nr_or_id(typval_T *vp) @@ -9538,45 +8566,6 @@ dictitem_T *find_var_in_ht(hashtab_T *const ht, return TV_DICT_HI2DI(hi); } -// Get function call environment based on backtrace debug level -static funccall_T *get_funccal(void) -{ - funccall_T *funccal = current_funccal; - if (debug_backtrace_level > 0) { - for (int i = 0; i < debug_backtrace_level; i++) { - funccall_T *temp_funccal = funccal->caller; - if (temp_funccal) { - funccal = temp_funccal; - } else { - // backtrace level overflow. reset to max - debug_backtrace_level = i; - } - } - } - - return funccal; -} - -/// Return the hashtable used for argument in the current funccal. -/// Return NULL if there is no current funccal. -static hashtab_T *get_funccal_args_ht(void) -{ - if (current_funccal == NULL) { - return NULL; - } - return &get_funccal()->l_avars.dv_hashtab; -} - -/// Return the hashtable used for local variables in the current funccal. -/// Return NULL if there is no current funccal. -static hashtab_T *get_funccal_local_ht(void) -{ - if (current_funccal == NULL) { - return NULL; - } - return &get_funccal()->l_vars.dv_hashtab; -} - /// Finds the dict (g:, l:, s:, …) and hashtable used for a variable. /// /// @param[in] name Variable name, possibly with scope prefix. @@ -10424,2368 +9413,280 @@ static const char *find_option_end(const char **const arg, int *const opt_flags) return p; } + /* - * ":function" + * Start profiling function "fp". */ -void ex_function(exarg_T *eap) +static void func_do_profile(ufunc_T *fp) { - char_u *theline; - char_u *line_to_free = NULL; - int c; - int saved_did_emsg; - int saved_wait_return = need_wait_return; - char_u *name = NULL; - char_u *p; - char_u *arg; - char_u *line_arg = NULL; - garray_T newargs; - garray_T newlines; - int varargs = false; - int flags = 0; - ufunc_T *fp; - bool overwrite = false; - int indent; - int nesting; - dictitem_T *v; - funcdict_T fudi; - static int func_nr = 0; /* number for nameless function */ - int paren; - hashtab_T *ht; - int todo; - hashitem_T *hi; - linenr_T sourcing_lnum_off; - linenr_T sourcing_lnum_top; - bool is_heredoc = false; - char_u *skip_until = NULL; - char_u *heredoc_trimmed = NULL; - bool show_block = false; - bool do_concat = true; - - /* - * ":function" without argument: list functions. - */ - if (ends_excmd(*eap->arg)) { - if (!eap->skip) { - todo = (int)func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - --todo; - fp = HI2UF(hi); - if (message_filtered(fp->uf_name)) { - continue; - } - if (!func_name_refcount(fp->uf_name)) { - list_func_head(fp, false, false); - } - } - } - } - eap->nextcmd = check_nextcmd(eap->arg); - return; - } - - /* - * ":function /pat": list functions matching pattern. - */ - if (*eap->arg == '/') { - p = skip_regexp(eap->arg + 1, '/', TRUE, NULL); - if (!eap->skip) { - regmatch_T regmatch; + int len = fp->uf_lines.ga_len; - c = *p; - *p = NUL; - regmatch.regprog = vim_regcomp(eap->arg + 1, RE_MAGIC); - *p = c; - if (regmatch.regprog != NULL) { - regmatch.rm_ic = p_ic; - - todo = (int)func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - --todo; - fp = HI2UF(hi); - if (!isdigit(*fp->uf_name) - && vim_regexec(®match, fp->uf_name, 0)) - list_func_head(fp, false, false); - } - } - vim_regfree(regmatch.regprog); - } + if (!fp->uf_prof_initialized) { + if (len == 0) { + len = 1; // avoid getting error for allocating zero bytes } - if (*p == '/') - ++p; - eap->nextcmd = check_nextcmd(p); - return; - } - - // Get the function name. There are these situations: - // func function name - // "name" == func, "fudi.fd_dict" == NULL - // dict.func new dictionary entry - // "name" == NULL, "fudi.fd_dict" set, - // "fudi.fd_di" == NULL, "fudi.fd_newkey" == func - // dict.func existing dict entry with a Funcref - // "name" == func, "fudi.fd_dict" set, - // "fudi.fd_di" set, "fudi.fd_newkey" == NULL - // dict.func existing dict entry that's not a Funcref - // "name" == NULL, "fudi.fd_dict" set, - // "fudi.fd_di" set, "fudi.fd_newkey" == NULL - // s:func script-local function name - // g:func global function name, same as "func" - p = eap->arg; - name = trans_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL); - paren = (vim_strchr(p, '(') != NULL); - if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { - /* - * Return on an invalid expression in braces, unless the expression - * evaluation has been cancelled due to an aborting error, an - * interrupt, or an exception. - */ - if (!aborting()) { - if (fudi.fd_newkey != NULL) { - EMSG2(_(e_dictkey), fudi.fd_newkey); - } - xfree(fudi.fd_newkey); - return; - } else - eap->skip = TRUE; - } - - /* An error in a function call during evaluation of an expression in magic - * braces should not cause the function not to be defined. */ - saved_did_emsg = did_emsg; - did_emsg = FALSE; + fp->uf_tm_count = 0; + fp->uf_tm_self = profile_zero(); + fp->uf_tm_total = profile_zero(); - // - // ":function func" with only function name: list function. - // If bang is given: - // - include "!" in function head - // - exclude line numbers from function body - // - if (!paren) { - if (!ends_excmd(*skipwhite(p))) { - EMSG(_(e_trailing)); - goto ret_free; - } - eap->nextcmd = check_nextcmd(p); - if (eap->nextcmd != NULL) - *p = NUL; - if (!eap->skip && !got_int) { - fp = find_func(name); - if (fp != NULL) { - list_func_head(fp, !eap->forceit, eap->forceit); - for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) { - if (FUNCLINE(fp, j) == NULL) { - continue; - } - msg_putchar('\n'); - if (!eap->forceit) { - msg_outnum((long)j + 1); - if (j < 9) { - msg_putchar(' '); - } - if (j < 99) { - msg_putchar(' '); - } - } - msg_prt_line(FUNCLINE(fp, j), false); - ui_flush(); // show a line at a time - os_breakcheck(); - } - if (!got_int) { - msg_putchar('\n'); - msg_puts(eap->forceit ? "endfunction" : " endfunction"); - } - } else - emsg_funcname(N_("E123: Undefined function: %s"), name); + if (fp->uf_tml_count == NULL) { + fp->uf_tml_count = xcalloc(len, sizeof(int)); } - goto ret_free; - } - /* - * ":function name(arg1, arg2)" Define function. - */ - p = skipwhite(p); - if (*p != '(') { - if (!eap->skip) { - EMSG2(_("E124: Missing '(': %s"), eap->arg); - goto ret_free; + if (fp->uf_tml_total == NULL) { + fp->uf_tml_total = xcalloc(len, sizeof(proftime_T)); } - /* attempt to continue by skipping some text */ - if (vim_strchr(p, '(') != NULL) - p = vim_strchr(p, '('); - } - p = skipwhite(p + 1); - ga_init(&newargs, (int)sizeof(char_u *), 3); - ga_init(&newlines, (int)sizeof(char_u *), 3); - - if (!eap->skip) { - /* Check the name of the function. Unless it's a dictionary function - * (that we are overwriting). */ - if (name != NULL) - arg = name; - else - arg = fudi.fd_newkey; - if (arg != NULL && (fudi.fd_di == NULL || !tv_is_func(fudi.fd_di->di_tv))) { - int j = (*arg == K_SPECIAL) ? 3 : 0; - while (arg[j] != NUL && (j == 0 ? eval_isnamec1(arg[j]) - : eval_isnamec(arg[j]))) - ++j; - if (arg[j] != NUL) - emsg_funcname((char *)e_invarg2, arg); + if (fp->uf_tml_self == NULL) { + fp->uf_tml_self = xcalloc(len, sizeof(proftime_T)); } - /* Disallow using the g: dict. */ - if (fudi.fd_dict != NULL && fudi.fd_dict->dv_scope == VAR_DEF_SCOPE) - EMSG(_("E862: Cannot use g: here")); - } - - if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) { - goto errret_2; - } - - if (KeyTyped && ui_has(kUICmdline)) { - show_block = true; - ui_ext_cmdline_block_append(0, (const char *)eap->cmd); - } - // find extra arguments "range", "dict", "abort" and "closure" - for (;; ) { - p = skipwhite(p); - if (STRNCMP(p, "range", 5) == 0) { - flags |= FC_RANGE; - p += 5; - } else if (STRNCMP(p, "dict", 4) == 0) { - flags |= FC_DICT; - p += 4; - } else if (STRNCMP(p, "abort", 5) == 0) { - flags |= FC_ABORT; - p += 5; - } else if (STRNCMP(p, "closure", 7) == 0) { - flags |= FC_CLOSURE; - p += 7; - if (current_funccal == NULL) { - emsg_funcname(N_ - ("E932: Closure function should not be at top level: %s"), - name == NULL ? (char_u *)"" : name); - goto erret; - } - } else { - break; - } + fp->uf_tml_idx = -1; + fp->uf_prof_initialized = true; } - /* When there is a line break use what follows for the function body. - * Makes 'exe "func Test()\n...\nendfunc"' work. */ - if (*p == '\n') { - line_arg = p + 1; - } else if (*p != NUL && *p != '"' && !eap->skip && !did_emsg) { - EMSG(_(e_trailing)); - } + fp->uf_profiling = TRUE; +} - /* - * Read the body of the function, until ":endfunction" is found. - */ - if (KeyTyped) { - /* Check if the function already exists, don't let the user type the - * whole function before telling him it doesn't work! For a script we - * need to skip the body to be able to find what follows. */ - if (!eap->skip && !eap->forceit) { - if (fudi.fd_dict != NULL && fudi.fd_newkey == NULL) - EMSG(_(e_funcdict)); - else if (name != NULL && find_func(name) != NULL) - emsg_funcname(e_funcexts, name); - } +/* + * Dump the profiling results for all functions in file "fd". + */ +void func_dump_profile(FILE *fd) +{ + hashitem_T *hi; + int todo; + ufunc_T *fp; + ufunc_T **sorttab; + int st_len = 0; - if (!eap->skip && did_emsg) - goto erret; + todo = (int)func_hashtab.ht_used; + if (todo == 0) + return; /* nothing to dump */ - if (!ui_has(kUICmdline)) { - msg_putchar('\n'); // don't overwrite the function name - } - cmdline_row = msg_row; - } + sorttab = xmalloc(sizeof(ufunc_T *) * todo); - // Save the starting line number. - sourcing_lnum_top = sourcing_lnum; + for (hi = func_hashtab.ht_array; todo > 0; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + fp = HI2UF(hi); + if (fp->uf_prof_initialized) { + sorttab[st_len++] = fp; - indent = 2; - nesting = 0; - for (;; ) { - if (KeyTyped) { - msg_scroll = true; - saved_wait_return = false; - } - need_wait_return = false; - - if (line_arg != NULL) { - /* Use eap->arg, split up in parts by line breaks. */ - theline = line_arg; - p = vim_strchr(theline, '\n'); - if (p == NULL) - line_arg += STRLEN(line_arg); - else { - *p = NUL; - line_arg = p + 1; - } - } else { - xfree(line_to_free); - if (eap->getline == NULL) { - theline = getcmdline(':', 0L, indent, do_concat); - } else { - theline = eap->getline(':', eap->cookie, indent, do_concat); - } - line_to_free = theline; - } - if (KeyTyped) { - lines_left = Rows - 1; - } - if (theline == NULL) { - EMSG(_("E126: Missing :endfunction")); - goto erret; - } - if (show_block) { - assert(indent >= 0); - ui_ext_cmdline_block_append((size_t)indent, (const char *)theline); - } - - // Detect line continuation: sourcing_lnum increased more than one. - sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie); - if (sourcing_lnum < sourcing_lnum_off) { - sourcing_lnum_off -= sourcing_lnum; - } else { - sourcing_lnum_off = 0; - } - - if (skip_until != NULL) { - // Don't check for ":endfunc" between - // * ":append" and "." - // * ":python <uf_name[0] == K_SPECIAL) { + fprintf(fd, "FUNCTION %s()\n", fp->uf_name + 3); } else { - p = theline + STRLEN(heredoc_trimmed); - } - if (STRCMP(p, skip_until) == 0) { - XFREE_CLEAR(skip_until); - XFREE_CLEAR(heredoc_trimmed); - do_concat = true; - is_heredoc = false; - } - } - } else { - /* skip ':' and blanks*/ - for (p = theline; ascii_iswhite(*p) || *p == ':'; ++p) - ; - - /* Check for "endfunction". */ - if (checkforcmd(&p, "endfunction", 4) && nesting-- == 0) { - if (*p == '!') { - p++; - } - char_u *nextcmd = NULL; - if (*p == '|') { - nextcmd = p + 1; - } else if (line_arg != NULL && *skipwhite(line_arg) != NUL) { - nextcmd = line_arg; - } else if (*p != NUL && *p != '"' && p_verbose > 0) { - give_warning2((char_u *)_("W22: Text found after :endfunction: %s"), - p, true); - } - if (nextcmd != NULL) { - // Another command follows. If the line came from "eap" we - // can simply point into it, otherwise we need to change - // "eap->cmdlinep". - eap->nextcmd = nextcmd; - if (line_to_free != NULL) { - xfree(*eap->cmdlinep); - *eap->cmdlinep = line_to_free; - line_to_free = NULL; - } - } - break; - } - - /* Increase indent inside "if", "while", "for" and "try", decrease - * at "end". */ - if (indent > 2 && STRNCMP(p, "end", 3) == 0) - indent -= 2; - else if (STRNCMP(p, "if", 2) == 0 - || STRNCMP(p, "wh", 2) == 0 - || STRNCMP(p, "for", 3) == 0 - || STRNCMP(p, "try", 3) == 0) - indent += 2; - - /* Check for defining a function inside this function. */ - if (checkforcmd(&p, "function", 2)) { - if (*p == '!') { - p = skipwhite(p + 1); - } - p += eval_fname_script((const char *)p); - xfree(trans_function_name(&p, true, 0, NULL, NULL)); - if (*skipwhite(p) == '(') { - nesting++; - indent += 2; - } - } - - // Check for ":append", ":change", ":insert". - p = skip_range(p, NULL); - if ((p[0] == 'a' && (!ASCII_ISALPHA(p[1]) || p[1] == 'p')) - || (p[0] == 'c' - && (!ASCII_ISALPHA(p[1]) - || (p[1] == 'h' && (!ASCII_ISALPHA(p[2]) - || (p[2] == 'a' - && (STRNCMP(&p[3], "nge", 3) != 0 - || !ASCII_ISALPHA(p[6]))))))) - || (p[0] == 'i' - && (!ASCII_ISALPHA(p[1]) || (p[1] == 'n' - && (!ASCII_ISALPHA(p[2]) - || (p[2] == 's')))))) { - skip_until = vim_strsave((char_u *)"."); - } - - // heredoc: Check for ":python < 0) - ((char_u **)(newlines.ga_data))[newlines.ga_len++] = NULL; - - /* Check for end of eap->arg. */ - if (line_arg != NULL && *line_arg == NUL) - line_arg = NULL; - } - - /* Don't define the function when skipping commands or when an error was - * detected. */ - if (eap->skip || did_emsg) - goto erret; - - /* - * If there are no errors, add the function - */ - if (fudi.fd_dict == NULL) { - v = find_var((const char *)name, STRLEN(name), &ht, false); - if (v != NULL && v->di_tv.v_type == VAR_FUNC) { - emsg_funcname(N_("E707: Function name conflicts with variable: %s"), - name); - goto erret; - } - - fp = find_func(name); - if (fp != NULL) { - // Function can be replaced with "function!" and when sourcing the - // same script again, but only once. - if (!eap->forceit - && (fp->uf_script_ctx.sc_sid != current_sctx.sc_sid - || fp->uf_script_ctx.sc_seq == current_sctx.sc_seq)) { - emsg_funcname(e_funcexts, name); - goto erret; - } - if (fp->uf_calls > 0) { - emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"), - name); - goto erret; - } - if (fp->uf_refcount > 1) { - // This function is referenced somewhere, don't redefine it but - // create a new one. - (fp->uf_refcount)--; - fp->uf_flags |= FC_REMOVED; - fp = NULL; - overwrite = true; - } else { - // redefine existing function - XFREE_CLEAR(name); - func_clear_items(fp); - fp->uf_profiling = false; - fp->uf_prof_initialized = false; - } - } - } else { - char numbuf[20]; - - fp = NULL; - if (fudi.fd_newkey == NULL && !eap->forceit) { - EMSG(_(e_funcdict)); - goto erret; - } - if (fudi.fd_di == NULL) { - if (tv_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg, - TV_CSTRING)) { - // Can't add a function to a locked dictionary - goto erret; - } - } else if (tv_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg, - TV_CSTRING)) { - // Can't change an existing function if it is locked - goto erret; - } - - /* Give the function a sequential number. Can only be used with a - * Funcref! */ - xfree(name); - sprintf(numbuf, "%d", ++func_nr); - name = vim_strsave((char_u *)numbuf); - } - - if (fp == NULL) { - if (fudi.fd_dict == NULL && vim_strchr(name, AUTOLOAD_CHAR) != NULL) { - int slen, plen; - char_u *scriptname; - - /* Check that the autoload name matches the script name. */ - int j = FAIL; - if (sourcing_name != NULL) { - scriptname = (char_u *)autoload_name((const char *)name, STRLEN(name)); - p = vim_strchr(scriptname, '/'); - plen = (int)STRLEN(p); - slen = (int)STRLEN(sourcing_name); - if (slen > plen && fnamecmp(p, - sourcing_name + slen - plen) == 0) - j = OK; - xfree(scriptname); - } - if (j == FAIL) { - EMSG2(_( - "E746: Function name does not match script file name: %s"), - name); - goto erret; - } - } - - fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); - - if (fudi.fd_dict != NULL) { - if (fudi.fd_di == NULL) { - // Add new dict entry - fudi.fd_di = tv_dict_item_alloc((const char *)fudi.fd_newkey); - if (tv_dict_add(fudi.fd_dict, fudi.fd_di) == FAIL) { - xfree(fudi.fd_di); - xfree(fp); - goto erret; - } - } else { - // Overwrite existing dict entry. - tv_clear(&fudi.fd_di->di_tv); - } - fudi.fd_di->di_tv.v_type = VAR_FUNC; - fudi.fd_di->di_tv.vval.v_string = vim_strsave(name); - - /* behave like "dict" was used */ - flags |= FC_DICT; - } - - /* insert the new function in the function list */ - STRCPY(fp->uf_name, name); - if (overwrite) { - hi = hash_find(&func_hashtab, name); - hi->hi_key = UF2HIKEY(fp); - } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) { - xfree(fp); - goto erret; - } - fp->uf_refcount = 1; - } - fp->uf_args = newargs; - fp->uf_lines = newlines; - if ((flags & FC_CLOSURE) != 0) { - register_closure(fp); - } else { - fp->uf_scoped = NULL; - } - if (prof_def_func()) { - func_do_profile(fp); - } - fp->uf_varargs = varargs; - if (sandbox) { - flags |= FC_SANDBOX; - } - fp->uf_flags = flags; - fp->uf_calls = 0; - fp->uf_script_ctx = current_sctx; - fp->uf_script_ctx.sc_lnum += sourcing_lnum_top; - - goto ret_free; - -erret: - ga_clear_strings(&newargs); -errret_2: - ga_clear_strings(&newlines); -ret_free: - xfree(skip_until); - xfree(line_to_free); - xfree(fudi.fd_newkey); - xfree(name); - did_emsg |= saved_did_emsg; - need_wait_return |= saved_wait_return; - if (show_block) { - ui_ext_cmdline_block_leave(); - } -} // NOLINT(readability/fn_size) - -/// Get a function name, translating "" and "". -/// Also handles a Funcref in a List or Dictionary. -/// flags: -/// TFN_INT: internal function name OK -/// TFN_QUIET: be quiet -/// TFN_NO_AUTOLOAD: do not use script autoloading -/// TFN_NO_DEREF: do not dereference a Funcref -/// Advances "pp" to just after the function name (if no error). -/// -/// @return the function name in allocated memory, or NULL for failure. -static char_u * -trans_function_name( - char_u **pp, - bool skip, // only find the end, don't evaluate - int flags, - funcdict_T *fdp, // return: info about dictionary used - partial_T **partial // return: partial of a FuncRef -) - FUNC_ATTR_NONNULL_ARG(1) -{ - char_u *name = NULL; - const char_u *start; - const char_u *end; - int lead; - int len; - lval_T lv; - - if (fdp != NULL) - memset(fdp, 0, sizeof(funcdict_T)); - start = *pp; - - /* Check for hard coded : already translated function ID (from a user - * command). */ - if ((*pp)[0] == K_SPECIAL && (*pp)[1] == KS_EXTRA - && (*pp)[2] == (int)KE_SNR) { - *pp += 3; - len = get_id_len((const char **)pp) + 3; - return (char_u *)xmemdupz(start, len); - } - - /* A name starting with "" or "" is local to a script. But - * don't skip over "s:", get_lval() needs it for "s:dict.func". */ - lead = eval_fname_script((const char *)start); - if (lead > 2) { - start += lead; - } - - // Note that TFN_ flags use the same values as GLV_ flags. - end = get_lval((char_u *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY, - lead > 2 ? 0 : FNE_CHECK_START); - if (end == start) { - if (!skip) - EMSG(_("E129: Function name required")); - goto theend; - } - if (end == NULL || (lv.ll_tv != NULL && (lead > 2 || lv.ll_range))) { - /* - * Report an invalid expression in braces, unless the expression - * evaluation has been cancelled due to an aborting error, an - * interrupt, or an exception. - */ - if (!aborting()) { - if (end != NULL) { - emsgf(_(e_invarg2), start); - } - } else { - *pp = (char_u *)find_name_end(start, NULL, NULL, FNE_INCL_BR); - } - goto theend; - } - - if (lv.ll_tv != NULL) { - if (fdp != NULL) { - fdp->fd_dict = lv.ll_dict; - fdp->fd_newkey = lv.ll_newkey; - lv.ll_newkey = NULL; - fdp->fd_di = lv.ll_di; - } - if (lv.ll_tv->v_type == VAR_FUNC && lv.ll_tv->vval.v_string != NULL) { - name = vim_strsave(lv.ll_tv->vval.v_string); - *pp = (char_u *)end; - } else if (lv.ll_tv->v_type == VAR_PARTIAL - && lv.ll_tv->vval.v_partial != NULL) { - if (lv.ll_tv->vval.v_partial == vvlua_partial && *end == '.') { - len = check_luafunc_name((const char *)end+1, true); - if (len == 0) { - EMSG2(e_invexpr2, "v:lua"); - goto theend; + fprintf(fd, "FUNCTION %s()\n", fp->uf_name); } - name = xmallocz(len); - memcpy(name, end+1, len); - *pp = (char_u *)end+1+len; - } else { - name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial)); - *pp = (char_u *)end; - } - if (partial != NULL) { - *partial = lv.ll_tv->vval.v_partial; - } - } else { - if (!skip && !(flags & TFN_QUIET) && (fdp == NULL - || lv.ll_dict == NULL - || fdp->fd_newkey == NULL)) { - EMSG(_(e_funcref)); - } else { - *pp = (char_u *)end; - } - name = NULL; - } - goto theend; - } - - if (lv.ll_name == NULL) { - // Error found, but continue after the function name. - *pp = (char_u *)end; - goto theend; - } - - /* Check if the name is a Funcref. If so, use the value. */ - if (lv.ll_exp_name != NULL) { - len = (int)strlen(lv.ll_exp_name); - name = deref_func_name(lv.ll_exp_name, &len, partial, - flags & TFN_NO_AUTOLOAD); - if ((const char *)name == lv.ll_exp_name) { - name = NULL; - } - } else if (!(flags & TFN_NO_DEREF)) { - len = (int)(end - *pp); - name = deref_func_name((const char *)(*pp), &len, partial, - flags & TFN_NO_AUTOLOAD); - if (name == *pp) { - name = NULL; - } - } - if (name != NULL) { - name = vim_strsave(name); - *pp = (char_u *)end; - if (strncmp((char *)name, "", 5) == 0) { - // Change "" to the byte sequence. - name[0] = K_SPECIAL; - name[1] = KS_EXTRA; - name[2] = (int)KE_SNR; - memmove(name + 3, name + 5, strlen((char *)name + 5) + 1); - } - goto theend; - } - - if (lv.ll_exp_name != NULL) { - len = (int)strlen(lv.ll_exp_name); - if (lead <= 2 && lv.ll_name == lv.ll_exp_name - && lv.ll_name_len >= 2 && memcmp(lv.ll_name, "s:", 2) == 0) { - // When there was "s:" already or the name expanded to get a - // leading "s:" then remove it. - lv.ll_name += 2; - lv.ll_name_len -= 2; - len -= 2; - lead = 2; - } - } else { - // Skip over "s:" and "g:". - if (lead == 2 || (lv.ll_name[0] == 'g' && lv.ll_name[1] == ':')) { - lv.ll_name += 2; - lv.ll_name_len -= 2; - } - len = (int)((const char *)end - lv.ll_name); - } - - size_t sid_buf_len = 0; - char sid_buf[20]; - - // Copy the function name to allocated memory. - // Accept name() inside a script, translate into 123_name(). - // Accept 123_name() outside a script. - if (skip) { - lead = 0; // do nothing - } else if (lead > 0) { - lead = 3; - if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) - || eval_fname_sid((const char *)(*pp))) { - // It's "s:" or "". - if (current_sctx.sc_sid <= 0) { - EMSG(_(e_usingsid)); - goto theend; - } - sid_buf_len = snprintf(sid_buf, sizeof(sid_buf), - "%" PRIdSCID "_", current_sctx.sc_sid); - lead += sid_buf_len; - } - } else if (!(flags & TFN_INT) - && builtin_function(lv.ll_name, lv.ll_name_len)) { - EMSG2(_("E128: Function name must start with a capital or \"s:\": %s"), - start); - goto theend; - } - - if (!skip && !(flags & TFN_QUIET) && !(flags & TFN_NO_DEREF)) { - char_u *cp = xmemrchr(lv.ll_name, ':', lv.ll_name_len); - - if (cp != NULL && cp < end) { - EMSG2(_("E884: Function name cannot contain a colon: %s"), start); - goto theend; - } - } - - name = xmalloc(len + lead + 1); - if (!skip && lead > 0) { - name[0] = K_SPECIAL; - name[1] = KS_EXTRA; - name[2] = (int)KE_SNR; - if (sid_buf_len > 0) { // If it's "" - memcpy(name + 3, sid_buf, sid_buf_len); - } - } - memmove(name + lead, lv.ll_name, len); - name[lead + len] = NUL; - *pp = (char_u *)end; - -theend: - clear_lval(&lv); - return name; -} - -/* - * Return 5 if "p" starts with "" or "" (ignoring case). - * Return 2 if "p" starts with "s:". - * Return 0 otherwise. - */ -static int eval_fname_script(const char *const p) -{ - // Use mb_strnicmp() because in Turkish comparing the "I" may not work with - // the standard library function. - if (p[0] == '<' - && (mb_strnicmp((char_u *)p + 1, (char_u *)"SID>", 4) == 0 - || mb_strnicmp((char_u *)p + 1, (char_u *)"SNR>", 4) == 0)) { - return 5; - } - if (p[0] == 's' && p[1] == ':') { - return 2; - } - return 0; -} - -/// Check whether function name starts with or s: -/// -/// @warning Only works for names previously checked by eval_fname_script(), if -/// it returned non-zero. -/// -/// @param[in] name Name to check. -/// -/// @return true if it starts with or s:, false otherwise. -static inline bool eval_fname_sid(const char *const name) - FUNC_ATTR_PURE FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT - FUNC_ATTR_NONNULL_ALL -{ - return *name == 's' || TOUPPER_ASC(name[2]) == 'I'; -} - -/// List the head of the function: "name(arg1, arg2)". -/// -/// @param[in] fp Function pointer. -/// @param[in] indent Indent line. -/// @param[in] force Include bang "!" (i.e.: "function!"). -static void list_func_head(ufunc_T *fp, int indent, bool force) -{ - msg_start(); - if (indent) - MSG_PUTS(" ");