changeset 21056:a7c202f5cbe9 v8.2.1079

patch 8.2.1079: Vim9: no line break allowed in a while loop Commit: https://github.com/vim/vim/commit/d5053d015a957b343ad9c9e45e0abd2978f10cf0 Author: Bram Moolenaar <Bram@vim.org> 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.
author Bram Moolenaar <Bram@vim.org>
date Sun, 28 Jun 2020 16:00:04 +0200
parents 2fd74612d57b
children 77d9be8bbf27
files src/eval.c src/evalvars.c src/ex_docmd.c src/globals.h src/proto/ex_docmd.pro src/structs.h src/testdir/test_vim9_cmd.vim src/version.c
diffstat 8 files changed, 115 insertions(+), 47 deletions(-) [+]
line wrap: on
line diff
--- 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;
--- 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;
--- 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",
--- 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
--- 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);
--- 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.
--- 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
--- 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,