# HG changeset patch # User Bram Moolenaar # Date 1593352804 -7200 # Node ID a7c202f5cbe91b7c56aab92a72812a3a070d89cd # Parent 2fd74612d57bcf4f0aefd2bbea044be404522235 patch 8.2.1079: Vim9: no line break allowed in a while loop Commit: https://github.com/vim/vim/commit/d5053d015a957b343ad9c9e45e0abd2978f10cf0 Author: Bram Moolenaar Date: Sun Jun 28 15:51:16 2020 +0200 patch 8.2.1079: Vim9: no line break allowed in a while loop Problem: Vim9: no line break allowed in a while loop. Solution: Update stored loop lines when finding line breaks. diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -170,8 +170,11 @@ eval_to_bool( CLEAR_FIELD(evalarg); evalarg.eval_flags = skip ? 0 : EVAL_EVALUATE; - evalarg.eval_cookie = eap != NULL && eap->getline == getsourceline - ? eap->cookie : NULL; + if (eap != NULL && getline_equal(eap->getline, eap->cookie, getsourceline)) + { + evalarg.eval_getline = eap->getline; + evalarg.eval_cookie = eap->cookie; + } if (skip) ++emsg_skip; @@ -1840,10 +1843,9 @@ eval_next_non_blank(char_u *arg, evalarg && evalarg != NULL && evalarg->eval_cookie != NULL && (*arg == NUL || (VIM_ISWHITE(arg[-1]) - && (*arg == '"' || *arg == '#'))) - && source_nextline(evalarg->eval_cookie) != NULL) + && (*arg == '"' || *arg == '#')))) { - char_u *p = source_nextline(evalarg->eval_cookie); + char_u *p = getline_peek(evalarg->eval_getline, evalarg->eval_cookie); if (p != NULL) { @@ -1863,7 +1865,7 @@ eval_next_line(evalarg_T *evalarg) garray_T *gap = &evalarg->eval_ga; char_u *line; - line = getsourceline(0, evalarg->eval_cookie, 0, TRUE); + line = evalarg->eval_getline(0, evalarg->eval_cookie, 0, TRUE); if (gap->ga_itemsize > 0 && ga_grow(gap, 1) == OK) { // Going to concatenate the lines after parsing. @@ -5206,7 +5208,11 @@ ex_echo(exarg_T *eap) CLEAR_FIELD(evalarg); evalarg.eval_flags = eap->skip ? 0 : EVAL_EVALUATE; - evalarg.eval_cookie = eap->getline == getsourceline ? eap->cookie : NULL; + if (getline_equal(eap->getline, eap->cookie, getsourceline)) + { + evalarg.eval_getline = eap->getline; + evalarg.eval_cookie = eap->cookie; + } if (eap->skip) ++emsg_skip; diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -799,8 +799,11 @@ ex_let(exarg_T *eap) ++emsg_skip; CLEAR_FIELD(evalarg); evalarg.eval_flags = eap->skip ? 0 : EVAL_EVALUATE; - evalarg.eval_cookie = eap->getline == getsourceline - ? eap->cookie : NULL; + if (getline_equal(eap->getline, eap->cookie, getsourceline)) + { + evalarg.eval_getline = eap->getline; + evalarg.eval_cookie = eap->cookie; + } i = eval0(expr, &rettv, eap, &evalarg); if (eap->skip) --emsg_skip; diff --git a/src/ex_docmd.c b/src/ex_docmd.c --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -629,6 +629,7 @@ do_cmdline( cstack_T cstack; // conditional stack garray_T lines_ga; // keep lines for ":while"/":for" int current_line = 0; // active line in lines_ga + int current_line_before = 0; char_u *fname = NULL; // function or script name linenr_T *breakpoint = NULL; // ptr to breakpoint field in cookie int *dbg_tick = NULL; // ptr to dbg_tick field in cookie @@ -851,27 +852,6 @@ do_cmdline( } # endif } - - if (cstack.cs_looplevel > 0) - { - // Inside a while/for loop we need to store the lines and use them - // again. Pass a different "fgetline" function to do_one_cmd() - // below, so that it stores lines in or reads them from - // "lines_ga". Makes it possible to define a function inside a - // while/for loop. - cmd_getline = get_loop_line; - cmd_cookie = (void *)&cmd_loop_cookie; - cmd_loop_cookie.lines_gap = &lines_ga; - cmd_loop_cookie.current_line = current_line; - cmd_loop_cookie.getline = fgetline; - cmd_loop_cookie.cookie = cookie; - cmd_loop_cookie.repeating = (current_line < lines_ga.ga_len); - } - else - { - cmd_getline = fgetline; - cmd_cookie = cookie; - } #endif // 2. If no line given, get an allocated line with fgetline(). @@ -929,21 +909,44 @@ do_cmdline( #ifdef FEAT_EVAL /* - * Save the current line when inside a ":while" or ":for", and when - * the command looks like a ":while" or ":for", because we may need it - * later. When there is a '|' and another command, it is stored - * separately, because we need to be able to jump back to it from an + * Inside a while/for loop, and when the command looks like a ":while" + * or ":for", the line is stored, because we may need it later when + * looping. + * + * When there is a '|' and another command, it is stored separately, + * because we need to be able to jump back to it from an * :endwhile/:endfor. + * + * Pass a different "fgetline" function to do_one_cmd() below, + * that it stores lines in or reads them from "lines_ga". Makes it + * possible to define a function inside a while/for loop and handles + * line continuation. */ - if (current_line == lines_ga.ga_len - && (cstack.cs_looplevel || has_loop_cmd(next_cmdline))) - { - if (store_loop_line(&lines_ga, next_cmdline) == FAIL) + if ((cstack.cs_looplevel > 0 || has_loop_cmd(next_cmdline))) + { + cmd_getline = get_loop_line; + cmd_cookie = (void *)&cmd_loop_cookie; + cmd_loop_cookie.lines_gap = &lines_ga; + cmd_loop_cookie.current_line = current_line; + cmd_loop_cookie.getline = fgetline; + cmd_loop_cookie.cookie = cookie; + cmd_loop_cookie.repeating = (current_line < lines_ga.ga_len); + + // Save the current line when encountering it the first time. + if (current_line == lines_ga.ga_len + && store_loop_line(&lines_ga, next_cmdline) == FAIL) { retval = FAIL; break; } - } + current_line_before = current_line; + } + else + { + cmd_getline = fgetline; + cmd_cookie = cookie; + } + did_endif = FALSE; #endif @@ -1078,7 +1081,7 @@ do_cmdline( else if (cstack.cs_lflags & CSL_HAD_LOOP) { cstack.cs_lflags &= ~CSL_HAD_LOOP; - cstack.cs_line[cstack.cs_idx] = current_line - 1; + cstack.cs_line[cstack.cs_idx] = current_line_before; } } @@ -1515,7 +1518,7 @@ getline_cookie( { #ifdef FEAT_EVAL char_u *(*gp)(int, void *, int, int); - struct loop_cookie *cp; + struct loop_cookie *cp; // When "fgetline" is "get_loop_line()" use the "cookie" to find the // cookie that's originally used to obtain the lines. This may be nested @@ -1533,6 +1536,41 @@ getline_cookie( #endif } +#if defined(FEAT_EVAL) || defined(PROT) +/* + * Get the next line source line without advancing. + */ + char_u * +getline_peek( + char_u *(*fgetline)(int, void *, int, int) UNUSED, + void *cookie) // argument for fgetline() +{ + char_u *(*gp)(int, void *, int, int); + struct loop_cookie *cp; + wcmd_T *wp; + + // When "fgetline" is "get_loop_line()" use the "cookie" to find the + // cookie that's originally used to obtain the lines. This may be nested + // several levels. + gp = fgetline; + cp = (struct loop_cookie *)cookie; + while (gp == get_loop_line) + { + if (cp->current_line + 1 < cp->lines_gap->ga_len) + { + // executing lines a second time, use the stored copy + wp = (wcmd_T *)(cp->lines_gap->ga_data) + cp->current_line + 1; + return wp->line; + } + gp = cp->getline; + cp = cp->cookie; + } + if (gp == getsourceline) + return source_nextline(cp); + return NULL; +} +#endif + /* * Helper function to apply an offset for buffer commands, i.e. ":bdelete", diff --git a/src/globals.h b/src/globals.h --- a/src/globals.h +++ b/src/globals.h @@ -1885,7 +1885,7 @@ EXTERN listitem_T range_list_item; // Passed to an eval() function to enable evaluation. EXTERN evalarg_T EVALARG_EVALUATE # ifdef DO_INIT - = {EVAL_EVALUATE, NULL, {0, 0, 0, 0, NULL}, NULL} + = {EVAL_EVALUATE, NULL, NULL, {0, 0, 0, 0, NULL}, NULL} # endif ; #endif diff --git a/src/proto/ex_docmd.pro b/src/proto/ex_docmd.pro --- a/src/proto/ex_docmd.pro +++ b/src/proto/ex_docmd.pro @@ -4,10 +4,11 @@ int do_cmdline_cmd(char_u *cmd); int do_cmdline(char_u *cmdline, char_u *(*fgetline)(int, void *, int, int), void *cookie, int flags); int getline_equal(char_u *(*fgetline)(int, void *, int, int), void *cookie, char_u *(*func)(int, void *, int, int)); void *getline_cookie(char_u *(*fgetline)(int, void *, int, int), void *cookie); +char_u *getline_peek(char_u *(*fgetline)(int, void *, int, int), void *cookie); int parse_command_modifiers(exarg_T *eap, char **errormsg, int skip_only); int parse_cmd_address(exarg_T *eap, char **errormsg, int silent); int checkforcmd(char_u **pp, char *cmd, int len); -char_u *find_ex_command(exarg_T *eap, int *full, void *((*lookup)(char_u *, size_t, cctx_T *)), cctx_T *cctx); +char_u *find_ex_command(exarg_T *eap, int *full, void *(*lookup)(char_u *, size_t, cctx_T *), cctx_T *cctx); int modifier_len(char_u *cmd); int cmd_exists(char_u *name); cmdidx_T excmd_get_cmdidx(char_u *cmd, int len); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1761,7 +1761,8 @@ typedef struct { int eval_flags; // EVAL_ flag values below // copied from exarg_T when "getline" is "getsourceline". Can be NULL. - void *eval_cookie; // argument for getline() + char_u *(*eval_getline)(int, void *, int, int); + void *eval_cookie; // argument for eval_getline() // Used to collect lines while parsing them, so that they can be // concatenated later. Used when "eval_ga.ga_itemsize" is not zero. diff --git a/src/testdir/test_vim9_cmd.vim b/src/testdir/test_vim9_cmd.vim --- a/src/testdir/test_vim9_cmd.vim +++ b/src/testdir/test_vim9_cmd.vim @@ -131,12 +131,29 @@ def Test_if_linebreak() enddef def Test_while_linebreak() - " TODO: line break in :while expression doesn't work yet let lines =<< trim END vim9script let nr = 0 - while nr < 10 + 3 - nr = nr + 4 + while nr < + 10 + 3 + nr = nr + + 4 + endwhile + assert_equal(16, nr) + END + CheckScriptSuccess(lines) + + lines =<< trim END + vim9script + let nr = 0 + while nr + < + 10 + + + 3 + nr = nr + + + 4 endwhile assert_equal(16, nr) END 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 */ /**/ + 1079, +/**/ 1078, /**/ 1077,