From a857b251d123112eda78945e163fe7fd0438ff59 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 2 Feb 2025 14:24:38 +0800 Subject: [PATCH] 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 --- src/nvim/eval/userfunc.c | 596 +++++++++++++++++++++------------------ 1 file changed, 317 insertions(+), 279 deletions(-) diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index a24a3d5622..2e549fcf37 100644 --- 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,12 +2233,280 @@ static void list_functions(regmatch_T *regmatch) } } +#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) +{ + bool saved_wait_return = need_wait_return; + 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 do_concat = true; + + while (true) { + if (KeyTyped) { + msg_scroll = true; + saved_wait_return = false; + } + need_wait_return = false; + + char *theline; + char *p; + char *arg; + + 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 theend; + } + 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 < 0) { + swmsg(true, _("W22: Text found after :endfunction: %s"), p); + } + 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(p); + xfree(trans_function_name(&p, true, 0, NULL, NULL)); + if (*skipwhite(p) == '(') { + if (nesting == MAX_FUNC_NESTING - 1) { + emsg(_(e_function_nesting_too_deep)); + } else { + 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 = xstrdup("."); + } + + // heredoc: Check for ":python <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; + } + + // Check for end of eap->arg. + if (line_arg != NULL && *line_arg == NUL) { + line_arg = NULL; + } + } + + // 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 *theline; char *line_to_free = NULL; - bool saved_wait_return = need_wait_return; char *arg; char *line_arg = NULL; garray_T newargs; @@ -2236,11 +2519,7 @@ void ex_function(exarg_T *eap) funcdict_T fudi; static int func_nr = 0; // number for nameless function hashtab_T *ht; - bool is_heredoc = false; - char *skip_until = NULL; - char *heredoc_trimmed = NULL; bool show_block = false; - bool do_concat = true; // ":function" without argument: list functions. if (ends_excmd(*eap->arg)) { @@ -2487,247 +2766,9 @@ void ex_function(exarg_T *eap) // 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 < 0) { - swmsg(true, _("W22: Text found after :endfunction: %s"), p); - } - 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(p); - xfree(trans_function_name(&p, true, 0, NULL, NULL)); - if (*skipwhite(p) == '(') { - if (nesting == MAX_FUNC_NESTING - 1) { - emsg(_(e_function_nesting_too_deep)); - } else { - 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 = xstrdup("."); - } - - // heredoc: Check for ":python < 0) { - ((char **)(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) { + // 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(); }