# HG changeset patch # User Bram Moolenaar # Date 1593274503 -7200 # Node ID d9e0db9b2b9961f4ff6ef56932d9a0d7d88fbb6d # Parent 4754f242bd14a1f3c901c77b3c8f2e7e09fcef18 patch 8.2.1071: Vim9: no line break allowed inside a lambda Commit: https://github.com/vim/vim/commit/e40fbc2ca9fda07332a4da5af1fcaba91bed865b Author: Bram Moolenaar Date: Sat Jun 27 18:06:45 2020 +0200 patch 8.2.1071: Vim9: no line break allowed inside a lambda Problem: Vim9: no line break allowed inside a lambda. Solution: Handle line break inside a lambda in Vim9 script. diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -325,8 +325,7 @@ eval_to_string_skip( if (skip) ++emsg_skip; - if (eval0(arg, &tv, eap, skip ? NULL : &EVALARG_EVALUATE) - == FAIL || skip) + if (eval0(arg, &tv, eap, skip ? NULL : &EVALARG_EVALUATE) == FAIL || skip) retval = NULL; else { @@ -353,6 +352,61 @@ skip_expr(char_u **pp) } /* + * Skip over an expression at "*pp". + * If in Vim9 script and line breaks are encountered, the lines are + * concatenated. "evalarg->eval_tofree" will be set accordingly. + * Return FAIL for an error, OK otherwise. + */ + int +skip_expr_concatenate(char_u **start, char_u **end, evalarg_T *evalarg) +{ + typval_T rettv; + int res; + int vim9script = current_sctx.sc_version == SCRIPT_VERSION_VIM9; + garray_T *gap = &evalarg->eval_ga; + int save_flags = evalarg == NULL ? 0 : evalarg->eval_flags; + + if (vim9script && evalarg->eval_cookie != NULL) + { + ga_init2(gap, sizeof(char_u *), 10); + if (ga_grow(gap, 1) == OK) + // leave room for "start" + ++gap->ga_len; + } + + // Don't evaluate the expression. + if (evalarg != NULL) + evalarg->eval_flags &= ~EVAL_EVALUATE; + *end = skipwhite(*end); + res = eval1(end, &rettv, evalarg); + if (evalarg != NULL) + evalarg->eval_flags = save_flags; + + if (vim9script && evalarg->eval_cookie != NULL + && evalarg->eval_ga.ga_len > 1) + { + char_u *p; + size_t endoff = STRLEN(*end); + + // Line breaks encountered, concatenate all the lines. + *((char_u **)gap->ga_data) = *start; + p = ga_concat_strings(gap, ""); + *((char_u **)gap->ga_data) = NULL; + ga_clear_strings(gap); + gap->ga_itemsize = 0; + if (p == NULL) + return FAIL; + *start = p; + vim_free(evalarg->eval_tofree); + evalarg->eval_tofree = p; + // Compute "end" relative to the end. + *end = *start + STRLEN(*start) - endoff; + } + + return res; +} + +/* * Top level evaluation function, returning a string. * When "convert" is TRUE convert a List into a sequence of lines and convert * a Float to a String. @@ -1794,14 +1848,27 @@ eval_next_non_blank(char_u *arg, evalarg } /* - * To be called when eval_next_non_blank() sets "getnext" to TRUE. + * To be called after eval_next_non_blank() sets "getnext" to TRUE. */ char_u * eval_next_line(evalarg_T *evalarg) { - vim_free(evalarg->eval_tofree); - evalarg->eval_tofree = getsourceline(0, evalarg->eval_cookie, 0, TRUE); - return skipwhite(evalarg->eval_tofree); + garray_T *gap = &evalarg->eval_ga; + char_u *line; + + line = getsourceline(0, evalarg->eval_cookie, 0, TRUE); + if (gap->ga_itemsize > 0 && ga_grow(gap, 1) == OK) + { + // Going to concatenate the lines after parsing. + ((char_u **)gap->ga_data)[gap->ga_len] = line; + ++gap->ga_len; + } + else + { + vim_free(evalarg->eval_tofree); + evalarg->eval_tofree = line; + } + return skipwhite(line); } /* @@ -1831,8 +1898,6 @@ eval0( int called_emsg_before = called_emsg; int flags = evalarg == NULL ? 0 : evalarg->eval_flags; - if (evalarg != NULL) - evalarg->eval_tofree = NULL; p = skipwhite(arg); ret = eval1(&p, rettv, evalarg); @@ -1857,22 +1922,15 @@ eval0( if (eap != NULL) eap->nextcmd = check_nextcmd(p); - if (evalarg != NULL) + if (evalarg != NULL && eap != NULL && evalarg->eval_tofree != NULL) { - if (eap != NULL) - { - if (evalarg->eval_tofree != NULL) - { - // We may need to keep the original command line, e.g. for - // ":let" it has the variable names. But we may also need the - // new one, "nextcmd" points into it. Keep both. - vim_free(eap->cmdline_tofree); - eap->cmdline_tofree = *eap->cmdlinep; - *eap->cmdlinep = evalarg->eval_tofree; - } - } - else - vim_free(evalarg->eval_tofree); + // We may need to keep the original command line, e.g. for + // ":let" it has the variable names. But we may also need the + // new one, "nextcmd" points into it. Keep both. + vim_free(eap->cmdline_tofree); + eap->cmdline_tofree = *eap->cmdlinep; + *eap->cmdlinep = evalarg->eval_tofree; + evalarg->eval_tofree = NULL; } return ret; @@ -2797,7 +2855,7 @@ eval7( * Lambda: {arg, arg -> expr} * Dictionary: {'key': val, 'key': val} */ - case '{': ret = get_lambda_tv(arg, rettv, evaluate); + case '{': ret = get_lambda_tv(arg, rettv, evalarg); if (ret == NOTDONE) ret = eval_dict(arg, rettv, evalarg, FALSE); break; @@ -2884,7 +2942,7 @@ eval7( // Handle following '[', '(' and '.' for expr[expr], expr.name, // expr(expr), expr->name(expr) if (ret == OK) - ret = handle_subscript(arg, rettv, flags, TRUE); + ret = handle_subscript(arg, rettv, evalarg, TRUE); /* * Apply logical NOT and unary '-', from right to left, ignore '+'. @@ -3031,9 +3089,11 @@ call_func_rettv( eval_lambda( char_u **arg, typval_T *rettv, - int evaluate, + evalarg_T *evalarg, int verbose) // give error messages { + int evaluate = evalarg != NULL + && (evalarg->eval_flags & EVAL_EVALUATE); typval_T base = *rettv; int ret; @@ -3041,7 +3101,7 @@ eval_lambda( *arg += 2; rettv->v_type = VAR_UNKNOWN; - ret = get_lambda_tv(arg, rettv, evaluate); + ret = get_lambda_tv(arg, rettv, evalarg); if (ret != OK) return FAIL; else if (**arg != '(') @@ -3136,10 +3196,11 @@ eval_method( eval_index( char_u **arg, typval_T *rettv, - int flags, + evalarg_T *evalarg, int verbose) // give error messages { - int evaluate = flags & EVAL_EVALUATE; + int evaluate = evalarg != NULL + && (evalarg->eval_flags & EVAL_EVALUATE); int empty1 = FALSE, empty2 = FALSE; typval_T var1, var2; long i; @@ -3200,11 +3261,6 @@ eval_index( } else { - evalarg_T evalarg; - - CLEAR_FIELD(evalarg); - evalarg.eval_flags = flags; - /* * something[idx] * @@ -3213,7 +3269,7 @@ eval_index( *arg = skipwhite(*arg + 1); if (**arg == ':') empty1 = TRUE; - else if (eval1(arg, &var1, &evalarg) == FAIL) // recursive! + else if (eval1(arg, &var1, evalarg) == FAIL) // recursive! return FAIL; else if (evaluate && tv_get_string_chk(&var1) == NULL) { @@ -3231,7 +3287,7 @@ eval_index( *arg = skipwhite(*arg + 1); if (**arg == ']') empty2 = TRUE; - else if (eval1(arg, &var2, &evalarg) == FAIL) // recursive! + else if (eval1(arg, &var2, evalarg) == FAIL) // recursive! { if (!empty1) clear_tv(&var1); @@ -4884,10 +4940,11 @@ eval_isnamec1(int c) handle_subscript( char_u **arg, typval_T *rettv, - int flags, // do more than finding the end + evalarg_T *evalarg, int verbose) // give error messages { - int evaluate = flags & EVAL_EVALUATE; + int evaluate = evalarg != NULL + && (evalarg->eval_flags & EVAL_EVALUATE); int ret = OK; dict_T *selfdict = NULL; @@ -4926,7 +4983,7 @@ handle_subscript( { if ((*arg)[2] == '{') // expr->{lambda}() - ret = eval_lambda(arg, rettv, evaluate, verbose); + ret = eval_lambda(arg, rettv, evalarg, verbose); else // expr->name() ret = eval_method(arg, rettv, evaluate, verbose); @@ -4943,7 +5000,7 @@ handle_subscript( } else selfdict = NULL; - if (eval_index(arg, rettv, flags, verbose) == FAIL) + if (eval_index(arg, rettv, evalarg, verbose) == FAIL) { clear_tv(rettv); ret = FAIL; diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -797,12 +797,14 @@ ex_let(exarg_T *eap) if (eap->skip) ++emsg_skip; + CLEAR_FIELD(evalarg); evalarg.eval_flags = eap->skip ? 0 : EVAL_EVALUATE; evalarg.eval_cookie = eap->getline == getsourceline ? eap->cookie : NULL; i = eval0(expr, &rettv, eap, &evalarg); if (eap->skip) --emsg_skip; + vim_free(evalarg.eval_tofree); } if (eap->skip) { @@ -1125,7 +1127,7 @@ list_arg_vars(exarg_T *eap, char_u *arg, { // handle d.key, l[idx], f(expr) arg_subsc = arg; - if (handle_subscript(&arg, &tv, EVAL_EVALUATE, TRUE) + if (handle_subscript(&arg, &tv, &EVALARG_EVALUATE, TRUE) == FAIL) error = TRUE; else @@ -3341,7 +3343,7 @@ var_exists(char_u *var) if (n) { // handle d.key, l[idx], f(expr) - n = (handle_subscript(&var, &tv, EVAL_EVALUATE, FALSE) == OK); + n = (handle_subscript(&var, &tv, &EVALARG_EVALUATE, FALSE) == OK); if (n) clear_tv(&tv); } diff --git a/src/ex_eval.c b/src/ex_eval.c --- a/src/ex_eval.c +++ b/src/ex_eval.c @@ -897,6 +897,7 @@ ex_eval(exarg_T *eap) typval_T tv; evalarg_T evalarg; + CLEAR_FIELD(evalarg); evalarg.eval_flags = eap->skip ? 0 : EVAL_EVALUATE; evalarg.eval_cookie = eap->getline == getsourceline ? eap->cookie : NULL; diff --git a/src/globals.h b/src/globals.h --- a/src/globals.h +++ b/src/globals.h @@ -1883,7 +1883,11 @@ EXTERN char windowsVersion[20] INIT(= {0 EXTERN listitem_T range_list_item; // Passed to an eval() function to enable evaluation. -EXTERN evalarg_T EVALARG_EVALUATE INIT3(EVAL_EVALUATE, NULL, NULL); +EXTERN evalarg_T EVALARG_EVALUATE +# ifdef DO_INIT + = {EVAL_EVALUATE, NULL, {0, 0, 0, 0, NULL}, NULL} +# endif + ; #endif #ifdef MSWIN diff --git a/src/popupwin.c b/src/popupwin.c --- a/src/popupwin.c +++ b/src/popupwin.c @@ -384,7 +384,7 @@ popup_add_timeout(win_T *wp, int time) vim_snprintf((char *)cbbuf, sizeof(cbbuf), "{_ -> popup_close(%d)}", wp->w_id); - if (get_lambda_tv(&ptr, &tv, TRUE) == OK) + if (get_lambda_tv(&ptr, &tv, &EVALARG_EVALUATE) == OK) { wp->w_popup_timer = create_timer(time, 0); wp->w_popup_timer->tr_callback = get_callback(&tv); diff --git a/src/proto/eval.pro b/src/proto/eval.pro --- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -9,6 +9,7 @@ int eval_expr_typval(typval_T *expr, typ int eval_expr_to_bool(typval_T *expr, int *error); char_u *eval_to_string_skip(char_u *arg, exarg_T *eap, int skip); int skip_expr(char_u **pp); +int skip_expr_concatenate(char_u **start, char_u **end, evalarg_T *evalarg); char_u *eval_to_string(char_u *arg, int convert); char_u *eval_to_string_safe(char_u *arg, int use_sandbox); varnumber_T eval_to_number(char_u *expr); @@ -53,7 +54,7 @@ int get_name_len(char_u **arg, char_u ** char_u *find_name_end(char_u *arg, char_u **expr_start, char_u **expr_end, int flags); int eval_isnamec(int c); int eval_isnamec1(int c); -int handle_subscript(char_u **arg, typval_T *rettv, int flags, int verbose); +int handle_subscript(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int verbose); int item_copy(typval_T *from, typval_T *to, int deep, int copyID); void echo_one(typval_T *rettv, int with_space, int *atstart, int *needclr); void ex_echo(exarg_T *eap); diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro --- a/src/proto/userfunc.pro +++ b/src/proto/userfunc.pro @@ -3,8 +3,8 @@ void func_init(void); hashtab_T *func_tbl_get(void); int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, garray_T *argtypes, int *varargs, garray_T *default_args, int skip, exarg_T *eap, char_u **line_to_free); char_u *get_lambda_name(void); -int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate); -char_u *register_cfunc(cfunc_T cb, cfunc_free_T free_cb, void *state); +char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state); +int get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg); char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload); void emsg_funcname(char *ermsg, char_u *name); int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, funcexe_T *funcexe); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1763,6 +1763,11 @@ typedef struct { // copied from exarg_T when "getline" is "getsourceline". Can be NULL. void *eval_cookie; // argument for getline() + // Used to collect lines while parsing them, so that they can be + // concatenated later. Used when "eval_ga.ga_itemsize" is not zero. + // "eval_ga.ga_data" is a list of pointers to lines. + garray_T eval_ga; + // pointer to the line obtained with getsourceline() char_u *eval_tofree; } evalarg_T; diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim --- a/src/testdir/test_vim9_expr.vim +++ b/src/testdir/test_vim9_expr.vim @@ -1017,6 +1017,18 @@ def Test_expr7_lambda() assert_equal([1, 3, 5], [1, 2, 3]->map({key, val -> key + val})) enddef +def Test_expr7_lambda_vim9script() + let lines =<< trim END + vim9script + let v = 10->{a -> + a + + 2 + }() + assert_equal(12, v) + END + CheckScriptSuccess(lines) +enddef + def Test_expr7_dict() " dictionary assert_equal(g:dict_empty, {}) diff --git a/src/userfunc.c b/src/userfunc.c --- a/src/userfunc.c +++ b/src/userfunc.c @@ -391,8 +391,10 @@ errret: * Return OK or FAIL. Returns NOTDONE for dict or {expr}. */ int -get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) +get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg) { + int evaluate = evalarg != NULL + && (evalarg->eval_flags & EVAL_EVALUATE); garray_T newargs; garray_T newlines; garray_T *pnewargs; @@ -404,6 +406,8 @@ get_lambda_tv(char_u **arg, typval_T *re char_u *s, *e; int *old_eval_lavars = eval_lavars_used; int eval_lavars = FALSE; + int getnext; + char_u *tofree = NULL; ga_init(&newargs); ga_init(&newlines); @@ -432,12 +436,25 @@ get_lambda_tv(char_u **arg, typval_T *re // Get the start and the end of the expression. *arg = skipwhite(*arg + 1); + eval_next_non_blank(*arg, evalarg, &getnext); + if (getnext) + *arg = eval_next_line(evalarg); s = *arg; - ret = skip_expr(arg); + ret = skip_expr_concatenate(&s, arg, evalarg); if (ret == FAIL) goto errret; + if (evalarg != NULL) + { + // avoid that the expression gets freed when another line break follows + tofree = evalarg->eval_tofree; + evalarg->eval_tofree = NULL; + } + e = *arg; *arg = skipwhite(*arg); + eval_next_non_blank(*arg, evalarg, &getnext); + if (getnext) + *arg = eval_next_line(evalarg); if (**arg != '}') { semsg(_("E451: Expected }: %s"), *arg); @@ -447,7 +464,8 @@ get_lambda_tv(char_u **arg, typval_T *re if (evaluate) { - int len, flags = 0; + int len; + int flags = 0; char_u *p; char_u *name = get_lambda_name(); @@ -464,7 +482,7 @@ get_lambda_tv(char_u **arg, typval_T *re goto errret; // Add "return " before the expression. - len = 7 + e - s + 1; + len = 7 + (int)(e - s) + 1; p = alloc(len); if (p == NULL) goto errret; @@ -510,6 +528,7 @@ get_lambda_tv(char_u **arg, typval_T *re } eval_lavars_used = old_eval_lavars; + vim_free(tofree); return OK; errret: @@ -517,6 +536,7 @@ errret: ga_clear_strings(&newlines); vim_free(fp); vim_free(pt); + vim_free(tofree); eval_lavars_used = old_eval_lavars; return FAIL; } @@ -3925,8 +3945,8 @@ ex_call(exarg_T *eap) dbg_check_breakpoint(eap); // Handle a function returning a Funcref, Dictionary or List. - if (handle_subscript(&arg, &rettv, eap->skip ? 0 : EVAL_EVALUATE, - TRUE) == FAIL) + if (handle_subscript(&arg, &rettv, + eap->skip ? NULL : &EVALARG_EVALUATE, TRUE) == FAIL) { failed = TRUE; break; diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -755,6 +755,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1071, +/**/ 1070, /**/ 1069, diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -3001,7 +3001,7 @@ to_name_const_end(char_u *arg) } else if (p == arg && *arg == '{') { - int ret = get_lambda_tv(&p, &rettv, FALSE); + int ret = get_lambda_tv(&p, &rettv, NULL); // Can be "{x -> ret}()". // Can be "{'a': 1}->Func()". @@ -3065,7 +3065,7 @@ compile_lambda(char_u **arg, cctx_T *cct ufunc_T *ufunc; // Get the funcref in "rettv". - if (get_lambda_tv(arg, &rettv, TRUE) != OK) + if (get_lambda_tv(arg, &rettv, &EVALARG_EVALUATE) != OK) return FAIL; ufunc = rettv.vval.v_partial->pt_func; @@ -3095,7 +3095,7 @@ compile_lambda_call(char_u **arg, cctx_T int ret = FAIL; // Get the funcref in "rettv". - if (get_lambda_tv(arg, &rettv, TRUE) == FAIL) + if (get_lambda_tv(arg, &rettv, &EVALARG_EVALUATE) == FAIL) return FAIL; if (**arg != '(')