summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2021-03-21 20:53:29 +0100
committerBram Moolenaar <Bram@vim.org>2021-03-21 20:53:29 +0100
commit7a6eaa06f98cef61d2131c25b4b47b8262cb8f59 (patch)
tree0b8e7b4442d9f362521bcd1aa7422de3e4782509
parentf90c855c71863296859780f7b4e0386e96f1c465 (diff)
downloadvim-git-8.2.2635.tar.gz
patch 8.2.2635: Vim9: cannot define an inline functionv8.2.2635
Problem: Vim9: cannot define an inline function. Solution: Make an inline function mostly work.
-rw-r--r--src/errors.h4
-rw-r--r--src/misc2.c5
-rw-r--r--src/proto/vim9compile.pro1
-rw-r--r--src/testdir/test_vim9_expr.vim19
-rw-r--r--src/userfunc.c890
-rw-r--r--src/version.c2
-rw-r--r--src/vim9compile.c14
7 files changed, 573 insertions, 362 deletions
diff --git a/src/errors.h b/src/errors.h
index e5c9d8e99..300802034 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -377,3 +377,7 @@ EXTERN char e_import_as_name_not_supported_here[]
INIT(= N_("E1169: 'import * as {name}' not supported here"));
EXTERN char e_cannot_use_hash_curly_to_start_comment[]
INIT(= N_("E1170: Cannot use #{ to start a comment"));
+EXTERN char e_missing_end_block[]
+ INIT(= N_("E1171: Missing } after inline function"));
+EXTERN char e_cannot_use_default_values_in_lambda[]
+ INIT(= N_("E1172: Cannot use default values in a lambda"));
diff --git a/src/misc2.c b/src/misc2.c
index 70a19c127..90b8b5893 100644
--- a/src/misc2.c
+++ b/src/misc2.c
@@ -2026,8 +2026,9 @@ ga_clear_strings(garray_T *gap)
{
int i;
- for (i = 0; i < gap->ga_len; ++i)
- vim_free(((char_u **)(gap->ga_data))[i]);
+ if (gap->ga_data != NULL)
+ for (i = 0; i < gap->ga_len; ++i)
+ vim_free(((char_u **)(gap->ga_data))[i]);
ga_clear(gap);
}
diff --git a/src/proto/vim9compile.pro b/src/proto/vim9compile.pro
index 4f2fffe68..05d399867 100644
--- a/src/proto/vim9compile.pro
+++ b/src/proto/vim9compile.pro
@@ -14,6 +14,7 @@ char_u *to_name_end(char_u *arg, int use_namespace);
char_u *to_name_const_end(char_u *arg);
exprtype_T get_compare_type(char_u *p, int *len, int *type_is);
void error_white_both(char_u *op, int len);
+void fill_exarg_from_cctx(exarg_T *eap, cctx_T *cctx);
int assignment_len(char_u *p, int *heredoc);
void vim9_declare_error(char_u *name);
int check_vim9_unlet(char_u *name);
diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim
index afe5f7dab..6b9b54a58 100644
--- a/src/testdir/test_vim9_expr.vim
+++ b/src/testdir/test_vim9_expr.vim
@@ -1946,6 +1946,25 @@ def Test_expr7_lambda()
CheckScriptSuccess(lines)
enddef
+def Test_expr7_lambda_block()
+ var lines =<< trim END
+ var Func = (s: string): string => {
+ return 'hello ' .. s
+ }
+ assert_equal('hello there', Func('there'))
+
+ var ll = range(3)
+ var dll = mapnew(ll, (k, v): string => {
+ if v % 2
+ return 'yes'
+ endif
+ return 'no'
+ })
+ assert_equal(['no', 'yes', 'no'], dll)
+ END
+ CheckDefAndScriptSuccess(lines)
+enddef
+
def NewLambdaWithComments(): func
return (x) =>
# some comment
diff --git a/src/userfunc.c b/src/userfunc.c
index 4793ee555..51894dc10 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -397,6 +397,25 @@ parse_argument_types(ufunc_T *fp, garray_T *argtypes, int varargs)
return OK;
}
+ static int
+parse_return_type(ufunc_T *fp, char_u *ret_type)
+{
+ if (ret_type == NULL)
+ fp->uf_ret_type = &t_void;
+ else
+ {
+ char_u *p = ret_type;
+
+ fp->uf_ret_type = parse_type(&p, &fp->uf_type_list, TRUE);
+ if (fp->uf_ret_type == NULL)
+ {
+ fp->uf_ret_type = &t_void;
+ return FAIL;
+ }
+ }
+ return OK;
+}
+
/*
* Register function "fp" as using "current_funccal" as its scope.
*/
@@ -536,7 +555,501 @@ skip_arrow(
}
/*
- * Parse a lambda expression and get a Funcref from "*arg".
+ * Check if "*cmd" points to a function command and if so advance "*cmd" and
+ * return TRUE.
+ * Otherwise return FALSE;
+ * Do not consider "function(" to be a command.
+ */
+ static int
+is_function_cmd(char_u **cmd)
+{
+ char_u *p = *cmd;
+
+ if (checkforcmd(&p, "function", 2))
+ {
+ if (*p == '(')
+ return FALSE;
+ *cmd = p;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/*
+ * Read the body of a function, put every line in "newlines".
+ * "newlines" must already have been initialized.
+ * "eap->cmdidx" is CMD_function, CMD_def or CMD_block;
+ */
+ static int
+get_function_body(
+ exarg_T *eap,
+ garray_T *newlines,
+ char_u *line_arg_in,
+ char_u **line_to_free)
+{
+ linenr_T sourcing_lnum_top = SOURCING_LNUM;
+ linenr_T sourcing_lnum_off;
+ int saved_wait_return = need_wait_return;
+ char_u *line_arg = line_arg_in;
+ int vim9_function = eap->cmdidx == CMD_def
+ || eap->cmdidx == CMD_block;
+#define MAX_FUNC_NESTING 50
+ char nesting_def[MAX_FUNC_NESTING];
+ int nesting = 0;
+ getline_opt_T getline_options;
+ int indent = 2;
+ char_u *skip_until = NULL;
+ int ret = FAIL;
+ int is_heredoc = FALSE;
+ char_u *heredoc_trimmed = NULL;
+
+ // Detect having skipped over comment lines to find the return
+ // type. Add NULL lines to keep the line count correct.
+ sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie);
+ if (SOURCING_LNUM < sourcing_lnum_off)
+ {
+ sourcing_lnum_off -= SOURCING_LNUM;
+ if (ga_grow(newlines, sourcing_lnum_off) == FAIL)
+ goto theend;
+ while (sourcing_lnum_off-- > 0)
+ ((char_u **)(newlines->ga_data))[newlines->ga_len++] = NULL;
+ }
+
+ nesting_def[nesting] = vim9_function;
+ getline_options = vim9_function
+ ? GETLINE_CONCAT_CONTBAR : GETLINE_CONCAT_CONT;
+ for (;;)
+ {
+ char_u *theline;
+ char_u *p;
+ char_u *arg;
+
+ 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
+ {
+ vim_free(*line_to_free);
+ if (eap->getline == NULL)
+ theline = getcmdline(':', 0L, indent, getline_options);
+ else
+ theline = eap->getline(':', eap->cookie, indent,
+ getline_options);
+ *line_to_free = theline;
+ }
+ if (KeyTyped)
+ lines_left = Rows - 1;
+ if (theline == NULL)
+ {
+ // Use the start of the function for the line number.
+ SOURCING_LNUM = sourcing_lnum_top;
+ if (skip_until != NULL)
+ semsg(_(e_missing_heredoc_end_marker_str), skip_until);
+ else if (eap->cmdidx == CMD_def)
+ emsg(_(e_missing_enddef));
+ else if (eap->cmdidx == CMD_block)
+ emsg(_(e_missing_end_block));
+ else
+ emsg(_("E126: Missing :endfunction"));
+ goto theend;
+ }
+
+ // 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"/":enddef" 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)
+ {
+ VIM_CLEAR(skip_until);
+ VIM_CLEAR(heredoc_trimmed);
+ getline_options = vim9_function
+ ? GETLINE_CONCAT_CONTBAR : GETLINE_CONCAT_CONT;
+ is_heredoc = FALSE;
+ }
+ }
+ }
+ else
+ {
+ int c;
+
+ // skip ':' and blanks
+ for (p = theline; VIM_ISWHITE(*p) || *p == ':'; ++p)
+ ;
+
+ // Check for "endfunction", "enddef" or "}".
+ // When a ":" follows it must be a dict key; "enddef: value,"
+ if ((nesting == 0 && eap->cmdidx == CMD_block)
+ ? *p == '}'
+ : (checkforcmd(&p, nesting_def[nesting]
+ ? "enddef" : "endfunction", 4)
+ && *p != ':'))
+ {
+ if (nesting-- == 0)
+ {
+ char_u *nextcmd = NULL;
+
+ if (*p == '|' || *p == '}')
+ nextcmd = p + 1;
+ else if (line_arg != NULL && *skipwhite(line_arg) != NUL)
+ nextcmd = line_arg;
+ else if (*p != NUL && *p != (vim9_function ? '#' : '"')
+ && p_verbose > 0
+ && eap->cmdidx != CMD_block)
+ give_warning2(eap->cmdidx == CMD_def
+ ? (char_u *)_("W1001: Text found after :enddef: %s")
+ : (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)
+ {
+ vim_free(*eap->cmdlinep);
+ *eap->cmdlinep = *line_to_free;
+ *line_to_free = NULL;
+ }
+ }
+ break;
+ }
+ }
+
+ // Check for mismatched "endfunc" or "enddef".
+ // We don't check for "def" inside "func" thus we also can't check
+ // for "enddef".
+ // We continue to find the end of the function, although we might
+ // not find it.
+ else if (nesting_def[nesting])
+ {
+ if (checkforcmd(&p, "endfunction", 4) && *p != ':')
+ emsg(_(e_mismatched_endfunction));
+ }
+ else if (eap->cmdidx == CMD_def && checkforcmd(&p, "enddef", 4))
+ emsg(_(e_mismatched_enddef));
+
+ // Increase indent inside "if", "while", "for" and "try", decrease
+ // at "end".
+ if (indent > 2 && (*p == '}' || 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.
+ // Only recognize "def" inside "def", not inside "function",
+ // For backwards compatibility, see Test_function_python().
+ c = *p;
+ if (is_function_cmd(&p)
+ || (eap->cmdidx == CMD_def && checkforcmd(&p, "def", 3)))
+ {
+ if (*p == '!')
+ p = skipwhite(p + 1);
+ p += eval_fname_script(p);
+ vim_free(trans_function_name(&p, NULL, TRUE, 0, NULL,
+ NULL, NULL));
+ if (*skipwhite(p) == '(')
+ {
+ if (nesting == MAX_FUNC_NESTING - 1)
+ emsg(_(e_function_nesting_too_deep));
+ else
+ {
+ ++nesting;
+ nesting_def[nesting] = (c == 'd');
+ indent += 2;
+ }
+ }
+ }
+
+ // Check for ":append", ":change", ":insert". Not for :def.
+ p = skip_range(p, FALSE, NULL);
+ if (!vim9_function
+ && ((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'
+ && (!ASCII_ISALPHA(p[3])
+ || p[3] == 'e'))))))))
+ skip_until = vim_strsave((char_u *)".");
+
+ // Check for ":python <<EOF", ":tcl <<EOF", etc.
+ arg = skipwhite(skiptowhite(p));
+ if (arg[0] == '<' && arg[1] =='<'
+ && ((p[0] == 'p' && p[1] == 'y'
+ && (!ASCII_ISALNUM(p[2]) || p[2] == 't'
+ || ((p[2] == '3' || p[2] == 'x')
+ && !ASCII_ISALPHA(p[3]))))
+ || (p[0] == 'p' && p[1] == 'e'
+ && (!ASCII_ISALPHA(p[2]) || p[2] == 'r'))
+ || (p[0] == 't' && p[1] == 'c'
+ && (!ASCII_ISALPHA(p[2]) || p[2] == 'l'))
+ || (p[0] == 'l' && p[1] == 'u' && p[2] == 'a'
+ && !ASCII_ISALPHA(p[3]))
+ || (p[0] == 'r' && p[1] == 'u' && p[2] == 'b'
+ && (!ASCII_ISALPHA(p[3]) || p[3] == 'y'))
+ || (p[0] == 'm' && p[1] == 'z'
+ && (!ASCII_ISALPHA(p[2]) || p[2] == 's'))
+ ))
+ {
+ // ":python <<" continues until a dot, like ":append"
+ p = skipwhite(arg + 2);
+ if (STRNCMP(p, "trim", 4) == 0)
+ {
+ // Ignore leading white space.
+ p = skipwhite(p + 4);
+ heredoc_trimmed = vim_strnsave(theline,
+ skipwhite(theline) - theline);
+ }
+ if (*p == NUL)
+ skip_until = vim_strsave((char_u *)".");
+ else
+ skip_until = vim_strnsave(p, skiptowhite(p) - p);
+ getline_options = GETLINE_NONE;
+ is_heredoc = TRUE;
+ }
+
+ // Check for ":cmd v =<< [trim] EOF"
+ // and ":cmd [a, b] =<< [trim] EOF"
+ // and "lines =<< [trim] EOF" for Vim9
+ // Where "cmd" can be "let", "var", "final" or "const".
+ arg = skipwhite(skiptowhite(p));
+ if (*arg == '[')
+ arg = vim_strchr(arg, ']');
+ if (arg != NULL)
+ {
+ int found = (eap->cmdidx == CMD_def && arg[0] == '='
+ && arg[1] == '<' && arg[2] =='<');
+
+ if (!found)
+ // skip over the argument after "cmd"
+ arg = skipwhite(skiptowhite(arg));
+ if (found || (arg[0] == '=' && arg[1] == '<' && arg[2] =='<'
+ && (checkforcmd(&p, "let", 2)
+ || checkforcmd(&p, "var", 3)
+ || checkforcmd(&p, "final", 5)
+ || checkforcmd(&p, "const", 5))))
+ {
+ p = skipwhite(arg + 3);
+ if (STRNCMP(p, "trim", 4) == 0)
+ {
+ // Ignore leading white space.
+ p = skipwhite(p + 4);
+ heredoc_trimmed = vim_strnsave(theline,
+ skipwhite(theline) - theline);
+ }
+ skip_until = vim_strnsave(p, skiptowhite(p) - p);
+ getline_options = GETLINE_NONE;
+ is_heredoc = TRUE;
+ }
+ }
+ }
+
+ // Add the line to the function.
+ if (ga_grow(newlines, 1 + sourcing_lnum_off) == FAIL)
+ goto theend;
+
+ // 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 = vim_strsave(theline);
+ if (p == NULL)
+ goto theend;
+ ((char_u **)(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_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)
+ ret = OK;
+
+theend:
+ vim_free(skip_until);
+ vim_free(heredoc_trimmed);
+ need_wait_return |= saved_wait_return;
+ return ret;
+}
+
+/*
+ * Handle the body of a lambda. *arg points to the "{", process statements
+ * until the matching "}".
+ * When successful "rettv" is set to a funcref.
+ */
+ static int
+lambda_function_body(
+ char_u **arg,
+ typval_T *rettv,
+ evalarg_T *evalarg,
+ garray_T *newargs,
+ garray_T *argtypes,
+ int varargs,
+ garray_T *default_args,
+ char_u *ret_type)
+{
+ int evaluate = evalarg != NULL
+ && (evalarg->eval_flags & EVAL_EVALUATE);
+ ufunc_T *ufunc;
+ exarg_T eap;
+ garray_T newlines;
+ char_u *cmdline = NULL;
+ int ret = FAIL;
+ char_u *line_to_free = NULL;
+ partial_T *pt;
+ char_u *name;
+ int lnum_save = -1;
+ linenr_T sourcing_lnum_top = SOURCING_LNUM;
+
+ CLEAR_FIELD(eap);
+ eap.cmdidx = CMD_block;
+ eap.forceit = FALSE;
+ eap.arg = *arg + 1;
+ eap.cmdlinep = &cmdline;
+ eap.skip = !evaluate;
+ if (evalarg->eval_cctx != NULL)
+ fill_exarg_from_cctx(&eap, evalarg->eval_cctx);
+ else
+ {
+ eap.getline = evalarg->eval_getline;
+ eap.cookie = evalarg->eval_cookie;
+ }
+
+ ga_init2(&newlines, (int)sizeof(char_u *), 10);
+ if (get_function_body(&eap, &newlines, NULL, &line_to_free) == FAIL)
+ goto erret;
+ if (cmdline != NULL)
+ {
+ // Something comes after the "}".
+ *arg = eap.nextcmd;
+ if (evalarg->eval_cctx == NULL)
+ {
+ // Need to keep the line and free it/ later.
+ vim_free(evalarg->eval_tofree_lambda);
+ evalarg->eval_tofree_lambda = cmdline;
+ }
+ }
+ else
+ *arg = (char_u *)"";
+
+ name = get_lambda_name();
+ ufunc = alloc_clear(offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
+ if (ufunc == NULL)
+ goto erret;
+ set_ufunc_name(ufunc, name);
+ if (hash_add(&func_hashtab, UF2HIKEY(ufunc)) == FAIL)
+ {
+ vim_free(ufunc);
+ goto erret;
+ }
+ ufunc->uf_refcount = 1;
+ ufunc->uf_args = *newargs;
+ newargs->ga_data = NULL;
+ ufunc->uf_def_args = *default_args;
+ default_args->ga_data = NULL;
+ ufunc->uf_func_type = &t_func_any;
+
+ // error messages are for the first function line
+ lnum_save = SOURCING_LNUM;
+ SOURCING_LNUM = sourcing_lnum_top;
+
+ // parse argument types
+ if (parse_argument_types(ufunc, argtypes, varargs) == FAIL)
+ {
+ SOURCING_LNUM = lnum_save;
+ goto erret;
+ }
+
+ // parse the return type, if any
+ if (parse_return_type(ufunc, ret_type) == FAIL)
+ goto erret;
+
+ pt = ALLOC_CLEAR_ONE(partial_T);
+ if (pt == NULL)
+ goto erret;
+ pt->pt_func = ufunc;
+ pt->pt_refcount = 1;
+
+ ufunc->uf_lines = newlines;
+ newlines.ga_data = NULL;
+ if (sandbox)
+ ufunc->uf_flags |= FC_SANDBOX;
+ if (!ASCII_ISUPPER(*ufunc->uf_name))
+ ufunc->uf_flags |= FC_VIM9;
+ ufunc->uf_script_ctx = current_sctx;
+ ufunc->uf_script_ctx_version = current_sctx.sc_version;
+ ufunc->uf_script_ctx.sc_lnum += sourcing_lnum_top;
+ set_function_type(ufunc);
+
+ rettv->vval.v_partial = pt;
+ rettv->v_type = VAR_PARTIAL;
+ ret = OK;
+
+erret:
+ if (lnum_save >= 0)
+ SOURCING_LNUM = lnum_save;
+ vim_free(line_to_free);
+ ga_clear_strings(&newlines);
+ ga_clear_strings(newargs);
+ ga_clear_strings(default_args);
+ return ret;
+}
+
+/*
+ * Parse a lambda expression and get a Funcref from "*arg" into "rettv".
* "arg" points to the { in "{arg -> expr}" or the ( in "(arg) => expr"
* When "types_optional" is TRUE optionally take argument types.
* Return OK or FAIL. Returns NOTDONE for dict or {expr}.
@@ -554,6 +1067,7 @@ get_lambda_tv(
garray_T newlines;
garray_T *pnewargs;
garray_T argtypes;
+ garray_T default_args;
ufunc_T *fp = NULL;
partial_T *pt = NULL;
int varargs;
@@ -596,7 +1110,8 @@ get_lambda_tv(
*arg += 1;
ret = get_function_args(arg, equal_arrow ? ')' : '-', pnewargs,
types_optional ? &argtypes : NULL, types_optional, evalarg,
- &varargs, NULL, FALSE, NULL, NULL);
+ &varargs, &default_args,
+ FALSE, NULL, NULL);
if (ret == FAIL
|| (s = skip_arrow(*arg, equal_arrow, &ret_type,
equal_arrow || in_vim9script() ? &white_error : NULL)) == NULL)
@@ -624,9 +1139,15 @@ get_lambda_tv(
// Recognize "{" as the start of a function body.
if (equal_arrow && **arg == '{')
{
- // TODO: process the function body upto the "}".
- // Return type is required then.
- emsg("Lambda function body not supported yet");
+ if (lambda_function_body(arg, rettv, evalarg, pnewargs,
+ types_optional ? &argtypes : NULL, varargs,
+ &default_args, ret_type) == FAIL)
+ goto errret;
+ goto theend;
+ }
+ if (default_args.ga_len > 0)
+ {
+ emsg(_(e_cannot_use_default_values_in_lambda));
goto errret;
}
@@ -732,6 +1253,7 @@ get_lambda_tv(
hash_add(&func_hashtab, UF2HIKEY(fp));
}
+theend:
eval_lavars_used = old_eval_lavars;
if (evalarg != NULL && evalarg->eval_tofree == NULL)
evalarg->eval_tofree = tofree1;
@@ -745,6 +1267,7 @@ get_lambda_tv(
errret:
ga_clear_strings(&newargs);
ga_clear_strings(&newlines);
+ ga_clear_strings(&default_args);
if (types_optional)
ga_clear_strings(&argtypes);
vim_free(fp);
@@ -2459,7 +2982,7 @@ call_func(
{
// Check that the argument types are OK for the types of the funcref.
if (check_argument_types(funcexe->check_type, argvars, argcount,
- name) == FAIL)
+ (name != NULL) ? name : funcname) == FAIL)
error = FCERR_OTHER;
}
@@ -3006,27 +3529,6 @@ list_functions(regmatch_T *regmatch)
}
/*
- * Check if "*cmd" points to a function command and if so advance "*cmd" and
- * return TRUE.
- * Otherwise return FALSE;
- * Do not consider "function(" to be a command.
- */
- static int
-is_function_cmd(char_u **cmd)
-{
- char_u *p = *cmd;
-
- if (checkforcmd(&p, "function", 2))
- {
- if (*p == '(')
- return FALSE;
- *cmd = p;
- return TRUE;
- }
- return FALSE;
-}
-
-/*
* ":function" also supporting nested ":def".
* When "name_arg" is not NULL this is a nested function, using "name_arg" for
* the function name.
@@ -3035,12 +3537,10 @@ is_function_cmd(char_u **cmd)
ufunc_T *
define_function(exarg_T *eap, char_u *name_arg)
{
- char_u *theline;
char_u *line_to_free = NULL;
int j;
int c;
int saved_did_emsg;
- int saved_wait_return = need_wait_return;
char_u *name = name_arg;
int is_global = FALSE;
char_u *p;
@@ -3056,21 +3556,12 @@ define_function(exarg_T *eap, char_u *name_arg)
char_u *ret_type = NULL;
ufunc_T *fp = NULL;
int overwrite = FALSE;
- int indent;
- int nesting;
-#define MAX_FUNC_NESTING 50
- char nesting_def[MAX_FUNC_NESTING];
dictitem_T *v;
funcdict_T fudi;
static int func_nr = 0; // number for nameless function
int paren;
hashitem_T *hi;
- getline_opt_T getline_options;
- linenr_T sourcing_lnum_off;
linenr_T sourcing_lnum_top;
- int is_heredoc = FALSE;
- char_u *skip_until = NULL;
- char_u *heredoc_trimmed = NULL;
int vim9script = in_vim9script();
imported_T *import = NULL;
@@ -3263,7 +3754,7 @@ define_function(exarg_T *eap, char_u *name_arg)
goto ret_free;
}
- ga_init2(&newlines, (int)sizeof(char_u *), 3);
+ ga_init2(&newlines, (int)sizeof(char_u *), 10);
if (!eap->skip && name_arg == NULL)
{
@@ -3399,309 +3890,7 @@ define_function(exarg_T *eap, char_u *name_arg)
// Save the starting line number.
sourcing_lnum_top = SOURCING_LNUM;
- // Detect having skipped over comment lines to find the return
- // type. Add NULL lines to keep the line count correct.
- sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie);
- if (SOURCING_LNUM < sourcing_lnum_off)
- {
- sourcing_lnum_off -= SOURCING_LNUM;
- if (ga_grow(&newlines, sourcing_lnum_off) == FAIL)
- goto erret;
- while (sourcing_lnum_off-- > 0)
- ((char_u **)(newlines.ga_data))[newlines.ga_len++] = NULL;
- }
-
- indent = 2;
- nesting = 0;
- nesting_def[nesting] = (eap->cmdidx == CMD_def);
- getline_options = eap->cmdidx == CMD_def
- ? GETLINE_CONCAT_CONTBAR : GETLINE_CONCAT_CONT;
- 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
- {
- vim_free(line_to_free);
- if (eap->getline == NULL)
- theline = getcmdline(':', 0L, indent, getline_options);
- else
- theline = eap->getline(':', eap->cookie, indent,
- getline_options);
- line_to_free = theline;
- }
- if (KeyTyped)
- lines_left = Rows - 1;
- if (theline == NULL)
- {
- // Use the start of the function for the line number.
- SOURCING_LNUM = sourcing_lnum_top;
- if (skip_until != NULL)
- semsg(_(e_missing_heredoc_end_marker_str), skip_until);
- else if (eap->cmdidx == CMD_def)
- emsg(_(e_missing_enddef));
- else
- emsg(_("E126: Missing :endfunction"));
- goto erret;
- }
-
- // 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"/":enddef" 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)
- {
- VIM_CLEAR(skip_until);
- VIM_CLEAR(heredoc_trimmed);
- getline_options = eap->cmdidx == CMD_def
- ? GETLINE_CONCAT_CONTBAR : GETLINE_CONCAT_CONT;
- is_heredoc = FALSE;
- }
- }
- }
- else
- {
- // skip ':' and blanks
- for (p = theline; VIM_ISWHITE(*p) || *p == ':'; ++p)
- ;
-
- // Check for "endfunction" or "enddef".
- // When a ":" follows it must be a dict key; "enddef: value,"
- if (checkforcmd(&p, nesting_def[nesting]
- ? "enddef" : "endfunction", 4)
- && *p != ':')
- {
- if (nesting-- == 0)
- {
- 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(eap->cmdidx == CMD_def
- ? (char_u *)_("W1001: Text found after :enddef: %s")
- : (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)
- {
- vim_free(*eap->cmdlinep);
- *eap->cmdlinep = line_to_free;
- line_to_free = NULL;
- }
- }
- break;
- }
- }
-
- // Check for mismatched "endfunc" or "enddef".
- // We don't check for "def" inside "func" thus we also can't check
- // for "enddef".
- // We continue to find the end of the function, although we might
- // not find it.
- else if (nesting_def[nesting])
- {
- if (checkforcmd(&p, "endfunction", 4) && *p != ':')
- emsg(_(e_mismatched_endfunction));
- }
- else if (eap->cmdidx == CMD_def && checkforcmd(&p, "enddef", 4))
- emsg(_(e_mismatched_enddef));
-
- // Increase indent inside "if", "while", "for" and "try", decrease
- // at "end".
- if (indent > 2 && (*p == '}' || 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.
- // Only recognize "def" inside "def", not inside "function",
- // For backwards compatibility, see Test_function_python().
- c = *p;
- if (is_function_cmd(&p)
- || (eap->cmdidx == CMD_def && checkforcmd(&p, "def", 3)))
- {
- if (*p == '!')
- p = skipwhite(p + 1);
- p += eval_fname_script(p);
- vim_free(trans_function_name(&p, NULL, TRUE, 0, NULL,
- NULL, NULL));
- if (*skipwhite(p) == '(')
- {
- if (nesting == MAX_FUNC_NESTING - 1)
- emsg(_(e_function_nesting_too_deep));
- else
- {
- ++nesting;
- nesting_def[nesting] = (c == 'd');
- indent += 2;
- }
- }
- }
-
- // Check for ":append", ":change", ":insert". Not for :def.
- p = skip_range(p, FALSE, NULL);
- if (eap->cmdidx != CMD_def
- && ((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'
- && (!ASCII_ISALPHA(p[3])
- || p[3] == 'e'))))))))
- skip_until = vim_strsave((char_u *)".");
-
- // Check for ":python <<EOF", ":tcl <<EOF", etc.
- arg = skipwhite(skiptowhite(p));
- if (arg[0] == '<' && arg[1] =='<'
- && ((p[0] == 'p' && p[1] == 'y'
- && (!ASCII_ISALNUM(p[2]) || p[2] == 't'
- || ((p[2] == '3' || p[2] == 'x')
- && !ASCII_ISALPHA(p[3]))))
- || (p[0] == 'p' && p[1] == 'e'
- && (!ASCII_ISALPHA(p[2]) || p[2] == 'r'))
- || (p[0] == 't' && p[1] == 'c'
- && (!ASCII_ISALPHA(p[2]) || p[2] == 'l'))
- || (p[0] == 'l' && p[1] == 'u' && p[2] == 'a'
- && !ASCII_ISALPHA(p[3]))
- || (p[0] == 'r' && p[1] == 'u' && p[2] == 'b'
- && (!ASCII_ISALPHA(p[3]) || p[3] == 'y'))
- || (p[0] == 'm' && p[1] == 'z'
- && (!ASCII_ISALPHA(p[2]) || p[2] == 's'))
- ))
- {
- // ":python <<" continues until a dot, like ":append"
- p = skipwhite(arg + 2);
- if (STRNCMP(p, "trim", 4) == 0)
- {
- // Ignore leading white space.
- p = skipwhite(p + 4);
- heredoc_trimmed = vim_strnsave(theline,
- skipwhite(theline) - theline);
- }
- if (*p == NUL)
- skip_until = vim_strsave((char_u *)".");
- else
- skip_until = vim_strnsave(p, skiptowhite(p) - p);
- getline_options = GETLINE_NONE;
- is_heredoc = TRUE;
- }
-
- // Check for ":cmd v =<< [trim] EOF"
- // and ":cmd [a, b] =<< [trim] EOF"
- // and "lines =<< [trim] EOF" for Vim9
- // Where "cmd" can be "let", "var", "final" or "const".
- arg = skipwhite(skiptowhite(p));
- if (*arg == '[')
- arg = vim_strchr(arg, ']');
- if (arg != NULL)
- {
- int found = (eap->cmdidx == CMD_def && arg[0] == '='
- && arg[1] == '<' && arg[2] =='<');
-
- if (!found)
- // skip over the argument after "cmd"
- arg = skipwhite(skiptowhite(arg));
- if (found || (arg[0] == '=' && arg[1] == '<' && arg[2] =='<'
- && (checkforcmd(&p, "let", 2)
- || checkforcmd(&p, "var", 3)
- || checkforcmd(&p, "final", 5)
- || checkforcmd(&p, "const", 5))))
- {
- p = skipwhite(arg + 3);
- if (STRNCMP(p, "trim", 4) == 0)
- {
- // Ignore leading white space.
- p = skipwhite(p + 4);
- heredoc_trimmed = vim_strnsave(theline,
- skipwhite(theline) - theline);
- }
- skip_until = vim_strnsave(p, skiptowhite(p) - p);
- getline_options = GETLINE_NONE;
- is_heredoc = TRUE;
- }
- }
- }
-
- // Add the line to the function.
- if (ga_grow(&newlines, 1 + sourcing_lnum_off) == FAIL)
- goto erret;
-
- // 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 = vim_strsave(theline);
- if (p == NULL)
- goto erret;
- ((char_u **)(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_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)
+ if (get_function_body(eap, &newlines, line_arg, &line_to_free) == FAIL)
goto erret;
/*
@@ -3933,18 +4122,10 @@ define_function(exarg_T *eap, char_u *name_arg)
varargs = FALSE;
// parse the return type, if any
- if (ret_type == NULL)
- fp->uf_ret_type = &t_void;
- else
+ if (parse_return_type(fp, ret_type) == FAIL)
{
- p = ret_type;
- fp->uf_ret_type = parse_type(&p, &fp->uf_type_list, TRUE);
- if (fp->uf_ret_type == NULL)
- {
- fp->uf_ret_type = &t_void;
- SOURCING_LNUM = lnum_save;
- goto erret;
- }
+ SOURCING_LNUM = lnum_save;
+ goto erret;
}
SOURCING_LNUM = lnum_save;
}
@@ -4004,15 +4185,12 @@ errret_2:
VIM_CLEAR(fp->uf_arg_types);
ret_free:
ga_clear_strings(&argtypes);
- vim_free(skip_until);
- vim_free(heredoc_trimmed);
vim_free(line_to_free);
vim_free(fudi.fd_newkey);
if (name != name_arg)
vim_free(name);
vim_free(ret_type);
did_emsg |= saved_did_emsg;
- need_wait_return |= saved_wait_return;
return fp;
}
diff --git a/src/version.c b/src/version.c
index 2ed5acaf2..cdce8cc76 100644
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 2635,
+/**/
2634,
/**/
2633,
diff --git a/src/vim9compile.c b/src/vim9compile.c
index 238ce5d1f..09b1fce83 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -3171,7 +3171,7 @@ compile_list(char_u **arg, cctx_T *cctx, ppconst_T *ppconst)
/*
* Parse a lambda: "(arg, arg) => expr"
- * "*arg" points to the '{'.
+ * "*arg" points to the '('.
* Returns OK/FAIL when a lambda is recognized, NOTDONE if it's not a lambda.
*/
static int
@@ -5126,6 +5126,13 @@ exarg_getline(
}
}
+ void
+fill_exarg_from_cctx(exarg_T *eap, cctx_T *cctx)
+{
+ eap->getline = exarg_getline;
+ eap->cookie = cctx;
+}
+
/*
* Compile a nested :def command.
*/
@@ -5176,9 +5183,8 @@ compile_nested_function(exarg_T *eap, cctx_T *cctx)
return NULL;
eap->arg = name_end;
- eap->getline = exarg_getline;
- eap->cookie = cctx;
- eap->skip = cctx->ctx_skip == SKIP_YES;
+ fill_exarg_from_cctx(eap, cctx);
+
eap->forceit = FALSE;
lambda_name = vim_strsave(get_lambda_name());
if (lambda_name == NULL)