To: vim_dev@googlegroups.com Subject: Patch 8.2.1155 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.1155 Problem: Vim9: cannot handle line break inside lambda. Solution: Pass the compilation context through. (closes #6407, closes #6409) Files: src/structs.h, src/vim9compile.c, src/proto/vim9compile.pro, src/eval.c, src/testdir/test_vim9_func.vim *** ../vim-8.2.1154/src/structs.h 2020-07-08 15:16:15.534128895 +0200 --- src/structs.h 2020-07-08 15:25:39.568322556 +0200 *************** *** 1765,1770 **** --- 1765,1773 ---- 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. *** ../vim-8.2.1154/src/vim9compile.c 2020-07-07 22:50:08.831459969 +0200 --- src/vim9compile.c 2020-07-08 17:13:23.770249096 +0200 *************** *** 2397,2404 **** * comment. Skips over white space. * Returns NULL if there is none. */ ! static char_u * ! peek_next_line(cctx_T *cctx) { int lnum = cctx->ctx_lnum; --- 2397,2404 ---- * comment. Skips over white space. * Returns NULL if there is none. */ ! char_u * ! peek_next_line_from_context(cctx_T *cctx) { int lnum = cctx->ctx_lnum; *************** *** 2430,2436 **** *nextp = NULL; if (*p == NUL || (VIM_ISWHITE(*arg) && comment_start(p))) { ! *nextp = peek_next_line(cctx); if (*nextp != NULL) return *nextp; } --- 2430,2436 ---- *nextp = NULL; if (*p == NUL || (VIM_ISWHITE(*arg) && comment_start(p))) { ! *nextp = peek_next_line_from_context(cctx); if (*nextp != NULL) return *nextp; } *************** *** 2442,2448 **** * Skips over empty lines. Skips over comment lines if "skip_comment" is TRUE. * Returns NULL when at the end. */ ! static char_u * next_line_from_context(cctx_T *cctx, int skip_comment) { char_u *line; --- 2442,2448 ---- * Skips over empty lines. Skips over comment lines if "skip_comment" is TRUE. * Returns NULL when at the end. */ ! char_u * next_line_from_context(cctx_T *cctx, int skip_comment) { char_u *line; *************** *** 3079,3087 **** { typval_T rettv; ufunc_T *ufunc; // Get the funcref in "rettv". ! if (get_lambda_tv(arg, &rettv, &EVALARG_EVALUATE) != OK) return FAIL; ufunc = rettv.vval.v_partial->pt_func; --- 3079,3092 ---- { 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) != OK) return FAIL; ufunc = rettv.vval.v_partial->pt_func; *************** *** 3535,3540 **** --- 3540,3546 ---- /* * Compile whatever comes after "name" or "name()". + * Advances "*arg" only when something was recognized. */ static int compile_subscript( *************** *** 3550,3556 **** if (*p == NUL || (VIM_ISWHITE(**arg) && comment_start(p))) { ! char_u *next = peek_next_line(cctx); // If a following line starts with "->{" or "->X" advance to that // line, so that a line break before "->" is allowed. --- 3556,3562 ---- if (*p == NUL || (VIM_ISWHITE(**arg) && comment_start(p))) { ! 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,3570 **** next = next_line_from_context(cctx, TRUE); if (next == NULL) return FAIL; ! *arg = skipwhite(next); } } ! if (**arg == '(') { garray_T *stack = &cctx->ctx_type_stack; type_T *type; --- 3566,3577 ---- next = next_line_from_context(cctx, TRUE); if (next == NULL) return FAIL; ! *arg = next; ! p = skipwhite(*arg); } } ! if (*p == '(') { garray_T *stack = &cctx->ctx_type_stack; type_T *type; *************** *** 3576,3588 **** // funcref(arg) type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; ! *arg = skipwhite(*arg + 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] == '>') { if (generate_ppconst(cctx, ppconst) == FAIL) return FAIL; --- 3583,3595 ---- // funcref(arg) type = ((type_T **)stack->ga_data)[stack->ga_len - 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 (*p == '-' && p[1] == '>') { if (generate_ppconst(cctx, ppconst) == FAIL) return FAIL; *************** *** 3594,3600 **** return FAIL; *start_leader = end_leader; // don't apply again later ! p = *arg + 2; *arg = skipwhite(p); if (may_get_next_line(p, arg, cctx) == FAIL) return FAIL; --- 3601,3607 ---- return FAIL; *start_leader = end_leader; // don't apply again later ! p += 2; *arg = skipwhite(p); if (may_get_next_line(p, arg, cctx) == FAIL) return FAIL; *************** *** 3622,3628 **** return FAIL; } } ! else if (**arg == '[') { garray_T *stack = &cctx->ctx_type_stack; type_T **typep; --- 3629,3635 ---- return FAIL; } } ! else if (*p == '[') { garray_T *stack = &cctx->ctx_type_stack; type_T **typep; *************** *** 3635,3641 **** if (generate_ppconst(cctx, ppconst) == FAIL) return FAIL; ! p = *arg + 1; *arg = skipwhite(p); if (may_get_next_line(p, arg, cctx) == FAIL) return FAIL; --- 3642,3648 ---- if (generate_ppconst(cctx, ppconst) == FAIL) return FAIL; ! ++p; *arg = skipwhite(p); if (may_get_next_line(p, arg, cctx) == FAIL) return FAIL; *************** *** 3671,3682 **** return FAIL; } } ! else if (**arg == '.' && (*arg)[1] != '.') { if (generate_ppconst(cctx, ppconst) == FAIL) return FAIL; ! ++*arg; if (may_get_next_line(*arg, arg, cctx) == FAIL) return FAIL; // dictionary member: dict.name --- 3678,3689 ---- return FAIL; } } ! else if (*p == '.' && p[1] != '.') { if (generate_ppconst(cctx, ppconst) == FAIL) return FAIL; ! *arg = p + 1; if (may_get_next_line(*arg, arg, cctx) == FAIL) return FAIL; // dictionary member: dict.name *** ../vim-8.2.1154/src/proto/vim9compile.pro 2020-07-06 21:53:14.472719363 +0200 --- src/proto/vim9compile.pro 2020-07-08 13:38:49.964316011 +0200 *************** *** 8,13 **** --- 8,15 ---- 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); *** ../vim-8.2.1154/src/eval.c 2020-07-04 14:14:55.633073475 +0200 --- src/eval.c 2020-07-08 17:31:11.235141345 +0200 *************** *** 390,400 **** 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; } --- 390,401 ---- garray_T *gap = &evalarg->eval_ga; int save_flags = evalarg == NULL ? 0 : evalarg->eval_flags; ! 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) ++gap->ga_len; } *************** *** 406,437 **** 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. * Return pointer to allocated memory, or NULL for failure. --- 407,455 ---- if (evalarg != NULL) evalarg->eval_flags = save_flags; ! if (vim9script ! && (evalarg->eval_cookie != NULL || evalarg->eval_cctx != NULL)) { ! 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. 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,1888 **** *getnext = FALSE; if (current_sctx.sc_version == SCRIPT_VERSION_VIM9 && evalarg != NULL ! && evalarg->eval_cookie != NULL && (*arg == NUL || (VIM_ISWHITE(arg[-1]) && *arg == '#' && arg[1] != '{'))) { ! char_u *p = getline_peek(evalarg->eval_getline, evalarg->eval_cookie); if (p != NULL) { --- 1896,1911 ---- *getnext = FALSE; if (current_sctx.sc_version == SCRIPT_VERSION_VIM9 && evalarg != NULL ! && (evalarg->eval_cookie != NULL || evalarg->eval_cctx != NULL) && (*arg == NUL || (VIM_ISWHITE(arg[-1]) && *arg == '#' && arg[1] != '{'))) { ! 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,1908 **** garray_T *gap = &evalarg->eval_ga; char_u *line; ! line = evalarg->eval_getline(0, evalarg->eval_cookie, 0, TRUE); ++evalarg->eval_break_count; if (gap->ga_itemsize > 0 && ga_grow(gap, 1) == OK) { --- 1925,1934 ---- garray_T *gap = &evalarg->eval_ga; char_u *line; ! 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,5068 **** 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 = eval_next_non_blank(*arg, evalarg, &getnext); ! ! if (getnext && *p == '.' && ASCII_ISALPHA(p[1])) { *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 == '(') { ret = call_func_rettv(arg, evalarg, rettv, evaluate, selfdict, NULL); --- 5060,5086 ---- int ret = OK; dict_T *selfdict = NULL; int check_white = TRUE; + int getnext; + char_u *p; ! while (ret == OK) { ! // 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; } ! 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,5085 **** dict_unref(selfdict); selfdict = NULL; } ! else if (**arg == '-') { if (ret == OK) { --- 5097,5103 ---- dict_unref(selfdict); selfdict = NULL; } ! else if (**arg == '-' && (*arg)[1] == '>') { if (ret == OK) { *************** *** 5091,5097 **** ret = eval_method(arg, rettv, evalarg, verbose); } } ! else // **arg == '[' || **arg == '.' { dict_unref(selfdict); if (rettv->v_type == VAR_DICT) --- 5109,5121 ---- ret = eval_method(arg, rettv, evalarg, verbose); } } ! // "." 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,5113 **** --- 5132,5139 ---- ret = FAIL; } } + else + break; } // Turn "dict.Func" into a partial for "Func" bound to "dict". *** ../vim-8.2.1154/src/testdir/test_vim9_func.vim 2020-07-05 21:10:20.869634742 +0200 --- src/testdir/test_vim9_func.vim 2020-07-08 16:51:18.386260987 +0200 *************** *** 965,970 **** --- 965,982 ---- 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 *** ../vim-8.2.1154/src/version.c 2020-07-08 15:16:15.534128895 +0200 --- src/version.c 2020-07-08 17:35:35.026243838 +0200 *************** *** 756,757 **** --- 756,759 ---- { /* Add new patch number below this line */ + /**/ + 1155, /**/ -- For society, it's probably a good thing that engineers value function over appearance. For example, you wouldn't want engineers to build nuclear power plants that only _look_ like they would keep all the radiation inside. (Scott Adams - The Dilbert principle) /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///