# HG changeset patch # User Bram Moolenaar # Date 1594223106 -7200 # Node ID 09377fd59b2e1e5847b10dbdd75a16f0f66a08aa # Parent 2a1156d4950fcffcd1daf17c095623f7c0a8f82c patch 8.2.1155: Vim9: cannot handle line break inside lambda Commit: https://github.com/vim/vim/commit/7a4b8980ea5ecaea061caae7816ea62cc4940011 Author: Bram Moolenaar Date: Wed Jul 8 17:36:21 2020 +0200 patch 8.2.1155: Vim9: cannot handle line break inside lambda Problem: Vim9: cannot handle line break inside lambda. Solution: Pass the compilation context through. (closes https://github.com/vim/vim/issues/6407, closes https://github.com/vim/vim/issues/6409) diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -390,11 +390,12 @@ skip_expr_concatenate(char_u **start, ch garray_T *gap = &evalarg->eval_ga; int save_flags = evalarg == NULL ? 0 : evalarg->eval_flags; - if (vim9script && evalarg->eval_cookie != NULL) + if (vim9script + && (evalarg->eval_cookie != NULL || evalarg->eval_cctx != NULL)) { ga_init2(gap, sizeof(char_u *), 10); + // leave room for "start" if (ga_grow(gap, 1) == OK) - // leave room for "start" ++gap->ga_len; } @@ -406,32 +407,49 @@ skip_expr_concatenate(char_u **start, ch if (evalarg != NULL) evalarg->eval_flags = save_flags; - if (vim9script && evalarg->eval_cookie != NULL - && evalarg->eval_ga.ga_len > 1) + if (vim9script + && (evalarg->eval_cookie != NULL || evalarg->eval_cctx != NULL)) { - 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; + if (evalarg->eval_ga.ga_len == 1) + { + // just one line, no need to concatenate + ga_clear(gap); + gap->ga_itemsize = 0; + } + else + { + 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, ""); + + // free the lines only when using getsourceline() + if (evalarg->eval_cookie != NULL) + { + *((char_u **)gap->ga_data) = NULL; + ga_clear_strings(gap); + } + else + ga_clear(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. + * Top level evaluation function, returning a string. Does not handle line + * breaks. * When "convert" is TRUE convert a List into a sequence of lines and convert * a Float to a String. * Return pointer to allocated memory, or NULL for failure. @@ -1878,11 +1896,16 @@ eval_next_non_blank(char_u *arg, evalarg *getnext = FALSE; if (current_sctx.sc_version == SCRIPT_VERSION_VIM9 && evalarg != NULL - && evalarg->eval_cookie != NULL + && (evalarg->eval_cookie != NULL || evalarg->eval_cctx != NULL) && (*arg == NUL || (VIM_ISWHITE(arg[-1]) && *arg == '#' && arg[1] != '{'))) { - char_u *p = getline_peek(evalarg->eval_getline, evalarg->eval_cookie); + char_u *p; + + if (evalarg->eval_cookie != NULL) + p = getline_peek(evalarg->eval_getline, evalarg->eval_cookie); + else + p = peek_next_line_from_context(evalarg->eval_cctx); if (p != NULL) { @@ -1902,7 +1925,10 @@ eval_next_line(evalarg_T *evalarg) garray_T *gap = &evalarg->eval_ga; char_u *line; - line = evalarg->eval_getline(0, evalarg->eval_cookie, 0, TRUE); + if (evalarg->eval_cookie != NULL) + line = evalarg->eval_getline(0, evalarg->eval_cookie, 0, TRUE); + else + line = next_line_from_context(evalarg->eval_cctx, TRUE); ++evalarg->eval_break_count; if (gap->ga_itemsize > 0 && ga_grow(gap, 1) == OK) { @@ -5034,35 +5060,27 @@ handle_subscript( int ret = OK; dict_T *selfdict = NULL; int check_white = TRUE; - - // When at the end of the line and ".name" follows in the next line then - // consume the line break. Only when rettv is a dict. - if (rettv->v_type == VAR_DICT) + int getnext; + char_u *p; + + while (ret == OK) { - int getnext; - char_u *p = eval_next_non_blank(*arg, evalarg, &getnext); - - if (getnext && *p == '.' && ASCII_ISALPHA(p[1])) + // When at the end of the line and ".name" or "->{" or "->X" follows in + // the next line then consume the line break. + p = eval_next_non_blank(*arg, evalarg, &getnext); + if (getnext + && ((rettv->v_type == VAR_DICT && *p == '.' + && ASCII_ISALPHA(p[1])) + || (*p == '-' && p[1] == '>' + && (p[2] == '{' || ASCII_ISALPHA(p[2]))))) { *arg = eval_next_line(evalarg); check_white = FALSE; } - } - - // "." is ".name" lookup when we found a dict or when evaluating and - // scriptversion is at least 2, where string concatenation is "..". - while (ret == OK - && (((**arg == '[' - || (**arg == '.' && (rettv->v_type == VAR_DICT - || (!evaluate - && (*arg)[1] != '.' - && current_sctx.sc_version >= 2))) - || (**arg == '(' && (!evaluate || rettv->v_type == VAR_FUNC - || rettv->v_type == VAR_PARTIAL))) - && (!check_white || !VIM_ISWHITE(*(*arg - 1)))) - || (**arg == '-' && (*arg)[1] == '>'))) - { - if (**arg == '(') + + if ((**arg == '(' && (!evaluate || rettv->v_type == VAR_FUNC + || rettv->v_type == VAR_PARTIAL)) + && (!check_white || !VIM_ISWHITE(*(*arg - 1)))) { ret = call_func_rettv(arg, evalarg, rettv, evaluate, selfdict, NULL); @@ -5079,7 +5097,7 @@ handle_subscript( dict_unref(selfdict); selfdict = NULL; } - else if (**arg == '-') + else if (**arg == '-' && (*arg)[1] == '>') { if (ret == OK) { @@ -5091,7 +5109,13 @@ handle_subscript( ret = eval_method(arg, rettv, evalarg, verbose); } } - else // **arg == '[' || **arg == '.' + // "." is ".name" lookup when we found a dict or when evaluating and + // scriptversion is at least 2, where string concatenation is "..". + else if (**arg == '[' + || (**arg == '.' && (rettv->v_type == VAR_DICT + || (!evaluate + && (*arg)[1] != '.' + && current_sctx.sc_version >= 2)))) { dict_unref(selfdict); if (rettv->v_type == VAR_DICT) @@ -5108,6 +5132,8 @@ handle_subscript( ret = FAIL; } } + else + break; } // Turn "dict.Func" into a partial for "Func" bound to "dict". diff --git a/src/proto/vim9compile.pro b/src/proto/vim9compile.pro --- a/src/proto/vim9compile.pro +++ b/src/proto/vim9compile.pro @@ -8,6 +8,8 @@ char *vartype_name(vartype_T type); char *type_name(type_T *type, char **tofree); int get_script_item_idx(int sid, char_u *name, int check_writable); imported_T *find_imported(char_u *name, size_t len, cctx_T *cctx); +char_u *peek_next_line_from_context(cctx_T *cctx); +char_u *next_line_from_context(cctx_T *cctx, int skip_comment); char_u *to_name_const_end(char_u *arg); int assignment_len(char_u *p, int *heredoc); void vim9_declare_error(char_u *name); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1765,6 +1765,9 @@ typedef struct { char_u *(*eval_getline)(int, void *, int, int); void *eval_cookie; // argument for eval_getline() + // used when compiling a :def function, NULL otherwise + cctx_T *eval_cctx; + // 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. diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -965,6 +965,18 @@ def Test_line_continuation_in_def() assert_equal('full', Line_continuation_in_def('.')) enddef +def Line_continuation_in_lambda(): list + let x = range(97, 100) + ->map({_,v -> nr2char(v) + ->toupper()}) + ->reverse() + return x +enddef + +def Test_line_continuation_in_lambda() + assert_equal(['D', 'C', 'B', 'A'], Line_continuation_in_lambda()) +enddef + func Test_silent_echo() CheckScreendump 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 */ /**/ + 1155, +/**/ 1154, /**/ 1153, diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -2397,8 +2397,8 @@ comment_start(char_u *p) * comment. Skips over white space. * Returns NULL if there is none. */ - static char_u * -peek_next_line(cctx_T *cctx) + char_u * +peek_next_line_from_context(cctx_T *cctx) { int lnum = cctx->ctx_lnum; @@ -2430,7 +2430,7 @@ may_peek_next_line(cctx_T *cctx, char_u *nextp = NULL; if (*p == NUL || (VIM_ISWHITE(*arg) && comment_start(p))) { - *nextp = peek_next_line(cctx); + *nextp = peek_next_line_from_context(cctx); if (*nextp != NULL) return *nextp; } @@ -2442,7 +2442,7 @@ may_peek_next_line(cctx_T *cctx, char_u * Skips over empty lines. Skips over comment lines if "skip_comment" is TRUE. * Returns NULL when at the end. */ - static char_u * + char_u * next_line_from_context(cctx_T *cctx, int skip_comment) { char_u *line; @@ -3079,9 +3079,14 @@ compile_lambda(char_u **arg, cctx_T *cct { typval_T rettv; ufunc_T *ufunc; + evalarg_T evalarg; + + CLEAR_FIELD(evalarg); + evalarg.eval_flags = EVAL_EVALUATE; + evalarg.eval_cctx = cctx; // Get the funcref in "rettv". - if (get_lambda_tv(arg, &rettv, &EVALARG_EVALUATE) != OK) + if (get_lambda_tv(arg, &rettv, &evalarg) != OK) return FAIL; ufunc = rettv.vval.v_partial->pt_func; @@ -3535,6 +3540,7 @@ compile_leader(cctx_T *cctx, char_u *sta /* * Compile whatever comes after "name" or "name()". + * Advances "*arg" only when something was recognized. */ static int compile_subscript( @@ -3550,7 +3556,7 @@ compile_subscript( if (*p == NUL || (VIM_ISWHITE(**arg) && comment_start(p))) { - char_u *next = peek_next_line(cctx); + char_u *next = peek_next_line_from_context(cctx); // If a following line starts with "->{" or "->X" advance to that // line, so that a line break before "->" is allowed. @@ -3560,11 +3566,12 @@ compile_subscript( next = next_line_from_context(cctx, TRUE); if (next == NULL) return FAIL; - *arg = skipwhite(next); + *arg = next; + p = skipwhite(*arg); } } - if (**arg == '(') + if (*p == '(') { garray_T *stack = &cctx->ctx_type_stack; type_T *type; @@ -3576,13 +3583,13 @@ compile_subscript( // funcref(arg) type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; - *arg = skipwhite(*arg + 1); + *arg = skipwhite(p + 1); if (compile_arguments(arg, cctx, &argcount) == FAIL) return FAIL; if (generate_PCALL(cctx, argcount, end_leader, type, TRUE) == FAIL) return FAIL; } - else if (**arg == '-' && (*arg)[1] == '>') + else if (*p == '-' && p[1] == '>') { if (generate_ppconst(cctx, ppconst) == FAIL) return FAIL; @@ -3594,7 +3601,7 @@ compile_subscript( return FAIL; *start_leader = end_leader; // don't apply again later - p = *arg + 2; + p += 2; *arg = skipwhite(p); if (may_get_next_line(p, arg, cctx) == FAIL) return FAIL; @@ -3622,7 +3629,7 @@ compile_subscript( return FAIL; } } - else if (**arg == '[') + else if (*p == '[') { garray_T *stack = &cctx->ctx_type_stack; type_T **typep; @@ -3635,7 +3642,7 @@ compile_subscript( if (generate_ppconst(cctx, ppconst) == FAIL) return FAIL; - p = *arg + 1; + ++p; *arg = skipwhite(p); if (may_get_next_line(p, arg, cctx) == FAIL) return FAIL; @@ -3671,12 +3678,12 @@ compile_subscript( return FAIL; } } - else if (**arg == '.' && (*arg)[1] != '.') + else if (*p == '.' && p[1] != '.') { if (generate_ppconst(cctx, ppconst) == FAIL) return FAIL; - ++*arg; + *arg = p + 1; if (may_get_next_line(*arg, arg, cctx) == FAIL) return FAIL; // dictionary member: dict.name