commit a857b251d123112eda78945e163fe7fd0438ff59
parent 289c9d21cb91ec6c47496230ca49eef42a04250c
Author: zeertzjq <zeertzjq@outlook.com>
Date: Sun, 2 Feb 2025 14:24:38 +0800
vim-patch: port some userfunc.c refactorings from Vim (#32292)
Port one_function_arg() and get_function_body() from Vim.
vim-patch:8.2.2865: skipping over function body fails
Problem: Skipping over function body fails.
Solution: Do not define the function when skipping.
https://github.com/vim/vim/commit/d87c21a918d8d611750f22d68fc638bf7a79b1d5
Co-authored-by: Bram Moolenaar <Bram@vim.org>
Diffstat:
1 file changed, 401 insertions(+), 363 deletions(-)
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
@@ -104,6 +104,48 @@ hashtab_T *func_tbl_get(void)
return &func_hashtab;
}
+/// Get one function argument.
+/// Return a pointer to after the type.
+/// When something is wrong return "arg".
+static char *one_function_arg(char *arg, garray_T *newargs, bool skip)
+{
+ char *p = arg;
+
+ while (ASCII_ISALNUM(*p) || *p == '_') {
+ p++;
+ }
+ if (arg == p || isdigit((uint8_t)(*arg))
+ || (p - arg == 9 && strncmp(arg, "firstline", 9) == 0)
+ || (p - arg == 8 && strncmp(arg, "lastline", 8) == 0)) {
+ if (!skip) {
+ semsg(_("E125: Illegal argument: %s"), arg);
+ }
+ return arg;
+ }
+
+ if (newargs != NULL) {
+ ga_grow(newargs, 1);
+ uint8_t c = (uint8_t)(*p);
+ *p = NUL;
+ char *arg_copy = xstrdup(arg);
+
+ // Check for duplicate argument name.
+ for (int i = 0; i < newargs->ga_len; i++) {
+ if (strcmp(((char **)(newargs->ga_data))[i], arg_copy) == 0) {
+ semsg(_("E853: Duplicate argument name: %s"), arg_copy);
+ xfree(arg_copy);
+ return arg;
+ }
+ }
+ ((char **)(newargs->ga_data))[newargs->ga_len] = arg_copy;
+ newargs->ga_len++;
+
+ *p = (char)c;
+ }
+
+ return p;
+}
+
/// Get function arguments.
static int get_function_args(char **argp, char endchar, garray_T *newargs, int *varargs,
garray_T *default_args, bool skip)
@@ -134,36 +176,11 @@ static int get_function_args(char **argp, char endchar, garray_T *newargs, int *
mustend = true;
} else {
arg = p;
- while (ASCII_ISALNUM(*p) || *p == '_') {
- p++;
- }
- if (arg == p || isdigit((uint8_t)(*arg))
- || (p - arg == 9 && strncmp(arg, "firstline", 9) == 0)
- || (p - arg == 8 && strncmp(arg, "lastline", 8) == 0)) {
- if (!skip) {
- semsg(_("E125: Illegal argument: %s"), arg);
- }
+ p = one_function_arg(p, newargs, skip);
+ if (p == arg) {
break;
}
- if (newargs != NULL) {
- ga_grow(newargs, 1);
- uint8_t c = (uint8_t)(*p);
- *p = NUL;
- arg = xstrdup(arg);
-
- // Check for duplicate argument name.
- for (int i = 0; i < newargs->ga_len; i++) {
- if (strcmp(((char **)(newargs->ga_data))[i], arg) == 0) {
- semsg(_("E853: Duplicate argument name: %s"), arg);
- xfree(arg);
- goto err_ret;
- }
- }
- ((char **)(newargs->ga_data))[newargs->ga_len] = arg;
- newargs->ga_len++;
- *p = (char)c;
- }
if (*skipwhite(p) == '=' && default_args != NULL) {
typval_T rettv;
@@ -2186,8 +2203,6 @@ char *save_function_name(char **name, bool skip, int flags, funcdict_T *fudi)
return saved;
}
-#define MAX_FUNC_NESTING 50
-
/// List functions.
///
/// @param regmatch When NULL, all of them.
@@ -2218,349 +2233,100 @@ static void list_functions(regmatch_T *regmatch)
}
}
-/// ":function"
-void ex_function(exarg_T *eap)
+#define MAX_FUNC_NESTING 50
+
+/// Read the body of a function, put every line in "newlines".
+/// This stops at "endfunction".
+/// "newlines" must already have been initialized.
+static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in,
+ char **line_to_free, bool show_block)
{
- char *theline;
- char *line_to_free = NULL;
bool saved_wait_return = need_wait_return;
- char *arg;
- char *line_arg = NULL;
- garray_T newargs;
- garray_T default_args;
- garray_T newlines;
- int varargs = false;
- int flags = 0;
- ufunc_T *fp;
- bool overwrite = false;
- funcdict_T fudi;
- static int func_nr = 0; // number for nameless function
- hashtab_T *ht;
- bool is_heredoc = false;
+ char *line_arg = line_arg_in;
+ int indent = 2;
+ int nesting = 0;
char *skip_until = NULL;
+ int ret = FAIL;
+ bool is_heredoc = false;
char *heredoc_trimmed = NULL;
- bool show_block = false;
bool do_concat = true;
- // ":function" without argument: list functions.
- if (ends_excmd(*eap->arg)) {
- if (!eap->skip) {
- list_functions(NULL);
+ while (true) {
+ if (KeyTyped) {
+ msg_scroll = true;
+ saved_wait_return = false;
}
- eap->nextcmd = check_nextcmd(eap->arg);
- return;
- }
+ need_wait_return = false;
- // ":function /pat": list functions matching pattern.
- if (*eap->arg == '/') {
- char *p = skip_regexp(eap->arg + 1, '/', true);
- if (!eap->skip) {
- regmatch_T regmatch;
+ char *theline;
+ char *p;
+ char *arg;
- char c = *p;
- *p = NUL;
- regmatch.regprog = vim_regcomp(eap->arg + 1, RE_MAGIC);
- *p = c;
- if (regmatch.regprog != NULL) {
- regmatch.rm_ic = p_ic;
- list_functions(®match);
- vim_regfree(regmatch.regprog);
+ 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;
}
- }
- 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"
- char *p = eap->arg;
- char *name = save_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi);
- int 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) {
- semsg(_(e_dictkey), fudi.fd_newkey);
+ } else {
+ xfree(*line_to_free);
+ if (eap->ea_getline == NULL) {
+ theline = getcmdline(':', 0, indent, do_concat);
+ } else {
+ theline = eap->ea_getline(':', eap->cookie, indent, do_concat);
}
- xfree(fudi.fd_newkey);
- return;
- }
- 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.
- const int saved_did_emsg = did_emsg;
- did_emsg = false;
-
- //
- // ":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))) {
- semsg(_(e_trailing_arg), p);
- goto ret_free;
+ *line_to_free = theline;
}
- eap->nextcmd = check_nextcmd(p);
- if (eap->nextcmd != NULL) {
- *p = NUL;
+ if (KeyTyped) {
+ lines_left = Rows - 1;
}
- if (!eap->skip && !got_int) {
- fp = find_func(name);
- if (fp != NULL) {
- // Check no function was added or removed from a callback, e.g. at
- // the more prompt. "fp" may then be invalid.
- const int prev_ht_changed = func_hashtab.ht_changed;
-
- if (list_func_head(fp, !eap->forceit, eap->forceit) == OK) {
- 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(j + 1);
- if (j < 9) {
- msg_putchar(' ');
- }
- if (j < 99) {
- msg_putchar(' ');
- }
- if (function_list_modified(prev_ht_changed)) {
- break;
- }
- }
- msg_prt_line(FUNCLINE(fp, j), false);
- line_breakcheck(); // show multiple lines at a time!
- }
- if (!got_int) {
- msg_putchar('\n');
- if (!function_list_modified(prev_ht_changed)) {
- msg_puts(eap->forceit ? "endfunction" : " endfunction");
- }
- }
- }
+ if (theline == NULL) {
+ if (skip_until != NULL) {
+ semsg(_(e_missing_heredoc_end_marker_str), skip_until);
} else {
- emsg_funcname(N_("E123: Undefined function: %s"), name);
+ emsg(_("E126: Missing :endfunction"));
}
+ goto theend;
}
- goto ret_free;
- }
-
- // ":function name(arg1, arg2)" Define function.
- p = skipwhite(p);
- if (*p != '(') {
- if (!eap->skip) {
- semsg(_("E124: Missing '(': %s"), eap->arg);
- goto ret_free;
- }
- // attempt to continue by skipping some text
- if (vim_strchr(p, '(') != NULL) {
- p = vim_strchr(p, '(');
+ if (show_block) {
+ assert(indent >= 0);
+ ui_ext_cmdline_block_append((size_t)indent, theline);
}
- }
- p = skipwhite(p + 1);
-
- ga_init(&newargs, (int)sizeof(char *), 3);
- ga_init(&newlines, (int)sizeof(char *), 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;
+ // Detect line continuation: SOURCING_LNUM increased more than one.
+ linenr_T sourcing_lnum_off = get_sourced_lnum(eap->ea_getline, eap->cookie);
+ if (SOURCING_LNUM < sourcing_lnum_off) {
+ sourcing_lnum_off -= SOURCING_LNUM;
} else {
- arg = fudi.fd_newkey;
+ sourcing_lnum_off = 0;
}
- if (arg != NULL && (fudi.fd_di == NULL || !tv_is_func(fudi.fd_di->di_tv))) {
- char *name_base = arg;
- if ((uint8_t)(*arg) == K_SPECIAL) {
- name_base = vim_strchr(arg, '_');
- if (name_base == NULL) {
- name_base = arg + 3;
+
+ if (skip_until != NULL) {
+ // Don't check for ":endfunc" between
+ // * ":append" and "."
+ // * ":python <<EOF" and "EOF"
+ // * ":let {var-name} =<< [trim] {marker}" and "{marker}"
+ if (heredoc_trimmed == NULL
+ || (is_heredoc && skipwhite(theline) == theline)
+ || strncmp(theline, heredoc_trimmed,
+ strlen(heredoc_trimmed)) == 0) {
+ if (heredoc_trimmed == NULL) {
+ p = theline;
+ } else if (is_heredoc) {
+ p = skipwhite(theline) == theline
+ ? theline : theline + strlen(heredoc_trimmed);
} else {
- name_base++;
- }
- }
- int i;
- for (i = 0; name_base[i] != NUL && (i == 0
- ? eval_isnamec1(name_base[i])
- : eval_isnamec(name_base[i])); i++) {}
- if (name_base[i] != NUL) {
- emsg_funcname(e_invarg2, arg);
- goto ret_free;
- }
- }
- // Disallow using the g: dict.
- if (fudi.fd_dict != NULL && fudi.fd_dict->dv_scope == VAR_DEF_SCOPE) {
- emsg(_("E862: Cannot use g: here"));
- goto ret_free;
- }
- }
-
- if (get_function_args(&p, ')', &newargs, &varargs,
- &default_args, eap->skip) == FAIL) {
- goto errret_2;
- }
-
- if (KeyTyped && ui_has(kUICmdline)) {
- show_block = true;
- ui_ext_cmdline_block_append(0, eap->cmd);
- }
-
- // find extra arguments "range", "dict", "abort" and "closure"
- while (true) {
- 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 ? "" : name);
- goto erret;
- }
- } else {
- break;
- }
- }
-
- // 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) {
- semsg(_(e_trailing_arg), p);
- }
-
- // 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 them 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);
- }
- }
-
- if (!eap->skip && did_emsg) {
- goto erret;
- }
-
- if (!ui_has(kUICmdline)) {
- msg_putchar('\n'); // don't overwrite the function name
- }
- cmdline_row = msg_row;
- }
-
- // Save the starting line number.
- linenr_T sourcing_lnum_top = SOURCING_LNUM;
-
- int indent = 2;
- int nesting = 0;
- while (true) {
- 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->ea_getline == NULL) {
- theline = getcmdline(':', 0, indent, do_concat);
- } else {
- theline = eap->ea_getline(':', eap->cookie, indent, do_concat);
- }
- line_to_free = theline;
- }
- if (KeyTyped) {
- lines_left = Rows - 1;
- }
- if (theline == NULL) {
- if (skip_until != NULL) {
- semsg(_(e_missing_heredoc_end_marker_str), skip_until);
- } else {
- emsg(_("E126: Missing :endfunction"));
- }
- goto erret;
- }
- if (show_block) {
- assert(indent >= 0);
- ui_ext_cmdline_block_append((size_t)indent, theline);
- }
-
- // Detect line continuation: SOURCING_LNUM increased more than one.
- linenr_T sourcing_lnum_off = get_sourced_lnum(eap->ea_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 <<EOF" and "EOF"
- // * ":let {var-name} =<< [trim] {marker}" and "{marker}"
- if (heredoc_trimmed == NULL
- || (is_heredoc && skipwhite(theline) == theline)
- || strncmp(theline, heredoc_trimmed,
- strlen(heredoc_trimmed)) == 0) {
- if (heredoc_trimmed == NULL) {
- p = theline;
- } else if (is_heredoc) {
- p = skipwhite(theline) == theline
- ? theline : theline + strlen(heredoc_trimmed);
- } 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;
+ 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 {
@@ -2585,10 +2351,10 @@ void ex_function(exarg_T *eap)
// can simply point into it, otherwise we need to change
// "eap->cmdlinep".
eap->nextcmd = nextcmd;
- if (line_to_free != NULL) {
+ if (*line_to_free != NULL) {
xfree(*eap->cmdlinep);
- *eap->cmdlinep = line_to_free;
- line_to_free = NULL;
+ *eap->cmdlinep = *line_to_free;
+ *line_to_free = NULL;
}
}
break;
@@ -2705,18 +2471,18 @@ void ex_function(exarg_T *eap)
}
// Add the line to the function.
- ga_grow(&newlines, 1 + (int)sourcing_lnum_off);
+ ga_grow(newlines, 1 + (int)sourcing_lnum_off);
// Copy the line to newly allocated memory. get_one_sourceline()
// allocates 250 bytes per line, this saves 80% on average. The cost
// is an extra alloc/free.
p = xstrdup(theline);
- ((char **)(newlines.ga_data))[newlines.ga_len++] = p;
+ ((char **)(newlines->ga_data))[newlines->ga_len++] = p;
// Add NULL lines for continuation lines, so that the line count is
// equal to the index in the growarray.
while (sourcing_lnum_off-- > 0) {
- ((char **)(newlines.ga_data))[newlines.ga_len++] = NULL;
+ ((char **)(newlines->ga_data))[newlines->ga_len++] = NULL;
}
// Check for end of eap->arg.
@@ -2725,9 +2491,284 @@ void ex_function(exarg_T *eap)
}
}
- // Don't define the function when skipping commands or when an error was
- // detected.
- if (eap->skip || did_emsg) {
+ // Return OK when no error was detected.
+ if (!did_emsg) {
+ ret = OK;
+ }
+
+theend:
+ xfree(skip_until);
+ xfree(heredoc_trimmed);
+ need_wait_return |= saved_wait_return;
+ return ret;
+}
+
+/// ":function"
+void ex_function(exarg_T *eap)
+{
+ char *line_to_free = NULL;
+ char *arg;
+ char *line_arg = NULL;
+ garray_T newargs;
+ garray_T default_args;
+ garray_T newlines;
+ int varargs = false;
+ int flags = 0;
+ ufunc_T *fp;
+ bool overwrite = false;
+ funcdict_T fudi;
+ static int func_nr = 0; // number for nameless function
+ hashtab_T *ht;
+ bool show_block = false;
+
+ // ":function" without argument: list functions.
+ if (ends_excmd(*eap->arg)) {
+ if (!eap->skip) {
+ list_functions(NULL);
+ }
+ eap->nextcmd = check_nextcmd(eap->arg);
+ return;
+ }
+
+ // ":function /pat": list functions matching pattern.
+ if (*eap->arg == '/') {
+ char *p = skip_regexp(eap->arg + 1, '/', true);
+ if (!eap->skip) {
+ regmatch_T regmatch;
+
+ char c = *p;
+ *p = NUL;
+ regmatch.regprog = vim_regcomp(eap->arg + 1, RE_MAGIC);
+ *p = c;
+ if (regmatch.regprog != NULL) {
+ regmatch.rm_ic = p_ic;
+ list_functions(®match);
+ vim_regfree(regmatch.regprog);
+ }
+ }
+ 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"
+ char *p = eap->arg;
+ char *name = save_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi);
+ int 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) {
+ semsg(_(e_dictkey), fudi.fd_newkey);
+ }
+ xfree(fudi.fd_newkey);
+ return;
+ }
+ 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.
+ const int saved_did_emsg = did_emsg;
+ did_emsg = false;
+
+ //
+ // ":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))) {
+ semsg(_(e_trailing_arg), p);
+ 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) {
+ // Check no function was added or removed from a callback, e.g. at
+ // the more prompt. "fp" may then be invalid.
+ const int prev_ht_changed = func_hashtab.ht_changed;
+
+ if (list_func_head(fp, !eap->forceit, eap->forceit) == OK) {
+ 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(j + 1);
+ if (j < 9) {
+ msg_putchar(' ');
+ }
+ if (j < 99) {
+ msg_putchar(' ');
+ }
+ if (function_list_modified(prev_ht_changed)) {
+ break;
+ }
+ }
+ msg_prt_line(FUNCLINE(fp, j), false);
+ line_breakcheck(); // show multiple lines at a time!
+ }
+ if (!got_int) {
+ msg_putchar('\n');
+ if (!function_list_modified(prev_ht_changed)) {
+ msg_puts(eap->forceit ? "endfunction" : " endfunction");
+ }
+ }
+ }
+ } else {
+ emsg_funcname(N_("E123: Undefined function: %s"), name);
+ }
+ }
+ goto ret_free;
+ }
+
+ // ":function name(arg1, arg2)" Define function.
+ p = skipwhite(p);
+ if (*p != '(') {
+ if (!eap->skip) {
+ semsg(_("E124: Missing '(': %s"), eap->arg);
+ goto ret_free;
+ }
+ // 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 *), 3);
+ ga_init(&newlines, (int)sizeof(char *), 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))) {
+ char *name_base = arg;
+ if ((uint8_t)(*arg) == K_SPECIAL) {
+ name_base = vim_strchr(arg, '_');
+ if (name_base == NULL) {
+ name_base = arg + 3;
+ } else {
+ name_base++;
+ }
+ }
+ int i;
+ for (i = 0; name_base[i] != NUL && (i == 0
+ ? eval_isnamec1(name_base[i])
+ : eval_isnamec(name_base[i])); i++) {}
+ if (name_base[i] != NUL) {
+ emsg_funcname(e_invarg2, arg);
+ goto ret_free;
+ }
+ }
+ // Disallow using the g: dict.
+ if (fudi.fd_dict != NULL && fudi.fd_dict->dv_scope == VAR_DEF_SCOPE) {
+ emsg(_("E862: Cannot use g: here"));
+ goto ret_free;
+ }
+ }
+
+ if (get_function_args(&p, ')', &newargs, &varargs,
+ &default_args, eap->skip) == FAIL) {
+ goto errret_2;
+ }
+
+ if (KeyTyped && ui_has(kUICmdline)) {
+ show_block = true;
+ ui_ext_cmdline_block_append(0, eap->cmd);
+ }
+
+ // find extra arguments "range", "dict", "abort" and "closure"
+ while (true) {
+ 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 ? "" : name);
+ goto erret;
+ }
+ } else {
+ break;
+ }
+ }
+
+ // 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) {
+ semsg(_(e_trailing_arg), p);
+ }
+
+ // 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 them 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);
+ }
+ }
+
+ if (!eap->skip && did_emsg) {
+ goto erret;
+ }
+
+ if (!ui_has(kUICmdline)) {
+ msg_putchar('\n'); // don't overwrite the function name
+ }
+ cmdline_row = msg_row;
+ }
+
+ // Save the starting line number.
+ linenr_T sourcing_lnum_top = SOURCING_LNUM;
+
+ // Do not define the function when getting the body fails and when skipping.
+ if (get_function_body(eap, &newlines, line_arg, &line_to_free, show_block) == FAIL
+ || eap->skip) {
goto erret;
}
@@ -2879,13 +2920,10 @@ erret:
errret_2:
ga_clear_strings(&newlines);
ret_free:
- xfree(skip_until);
- xfree(heredoc_trimmed);
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();
}