changeset 23156:6aa8ddf7a3fa v8.2.2124

patch 8.2.2124: Vim9: a range cannot be computed at runtime Commit: https://github.com/vim/vim/commit/08597875b2a1e7d118b0346c652a96e7527e7d8b Author: Bram Moolenaar <Bram@vim.org> Date: Thu Dec 10 19:43:40 2020 +0100 patch 8.2.2124: Vim9: a range cannot be computed at runtime Problem: Vim9: a range cannot be computed at runtime. Solution: Add the ISN_RANGE instruction.
author Bram Moolenaar <Bram@vim.org>
date Thu, 10 Dec 2020 19:45:04 +0100
parents 1172003ef1bf
children 4dcb8c79c910
files src/testdir/test_vim9_cmd.vim src/testdir/test_vim9_disassemble.vim src/version.c src/vim9.h src/vim9compile.c src/vim9execute.c
diffstat 6 files changed, 128 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/src/testdir/test_vim9_cmd.vim
+++ b/src/testdir/test_vim9_cmd.vim
@@ -614,6 +614,17 @@ def Test_put_command()
   assert_equal('above', getline(3))
   assert_equal('below', getline(4))
 
+  # compute range at runtime
+  setline(1, range(1, 8))
+  @a = 'aaa'
+  :$-2put a
+  assert_equal('aaa', getline(7))
+
+  setline(1, range(1, 8))
+  :2
+  :+2put! a
+  assert_equal('aaa', getline(4))
+
   bwipe!
 enddef
 
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -133,6 +133,21 @@ def Test_disassemble_put_expr()
         res)
 enddef
 
+def s:PutRange()
+  :$-2put a
+enddef
+
+def Test_disassemble_put_range()
+  var res = execute('disass s:PutRange')
+  assert_match('<SNR>\d*_PutRange.*' ..
+        ' :$-2put a\_s*' ..
+        '\d RANGE $-2\_s*' ..
+        '\d PUT a range\_s*' ..
+        '\d PUSHNR 0\_s*' ..
+        '\d RETURN',
+        res)
+enddef
+
 def s:ScriptFuncPush()
   var localbool = true
   var localspec = v:none
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    2124,
+/**/
     2123,
 /**/
     2122,
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -18,6 +18,7 @@ typedef enum {
     ISN_EXECUTE,    // execute Ex commands isn_arg.number items on top of stack
     ISN_ECHOMSG,    // echo Ex commands isn_arg.number items on top of stack
     ISN_ECHOERR,    // echo Ex commands isn_arg.number items on top of stack
+    ISN_RANGE,	    // compute range from isn_arg.string, push to stack
 
     // get and set variables
     ISN_LOAD,	    // push local variable isn_arg.number
@@ -366,3 +367,8 @@ garray_T def_functions = {0, 0, sizeof(d
 extern garray_T def_functions;
 #endif
 
+// Used for "lnum" when a range is to be taken from the stack.
+#define LNUM_VARIABLE_RANGE -999
+
+// Used for "lnum" when a range is to be taken from the stack and "!" is used.
+#define LNUM_VARIABLE_RANGE_ABOVE -888
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -1888,6 +1888,26 @@ generate_EXECCONCAT(cctx_T *cctx, int co
     return OK;
 }
 
+/*
+ * Generate ISN_RANGE.  Consumes "range".  Return OK/FAIL.
+ */
+    static int
+generate_RANGE(cctx_T *cctx, char_u *range)
+{
+    isn_T	*isn;
+    garray_T	*stack = &cctx->ctx_type_stack;
+
+    if ((isn = generate_instr(cctx, ISN_RANGE)) == NULL)
+	return FAIL;
+    isn->isn_arg.string = range;
+
+    if (ga_grow(stack, 1) == FAIL)
+	return FAIL;
+    ((type_T **)stack->ga_data)[stack->ga_len] = &t_number;
+    ++stack->ga_len;
+    return OK;
+}
+
     static int
 generate_UNPACK(cctx_T *cctx, int var_count, int semicolon)
 {
@@ -7099,6 +7119,22 @@ compile_mult_expr(char_u *arg, int cmdid
 }
 
 /*
+ * If "eap" has a range that is not a contstant generate an ISN_RANGE
+ * instruction to compute it and return OK.
+ * Otherwise return FAIL, the caller must deal with any range.
+ */
+    static int
+compile_variable_range(exarg_T *eap, cctx_T *cctx)
+{
+    char_u *range_end = skip_range(eap->cmd, TRUE, NULL);
+    char_u *p = skipdigits(eap->cmd);
+
+    if (p == range_end)
+	return FAIL;
+    return generate_RANGE(cctx, vim_strnsave(eap->cmd, range_end - eap->cmd));
+}
+
+/*
  * :put r
  * :put ={expr}
  */
@@ -7123,17 +7159,23 @@ compile_put(char_u *arg, exarg_T *eap, c
     else if (eap->regname != NUL)
 	++line;
 
-    // "errormsg" will not be set because the range is ADDR_LINES.
-    // TODO: if the range contains something like "$" or "." need to evaluate
-    // at runtime
-    if (parse_cmd_address(eap, &errormsg, FALSE) == FAIL)
-	return NULL;
-    if (eap->addr_count == 0)
-	lnum = -1;
+    if (compile_variable_range(eap, cctx) == OK)
+    {
+	lnum = above ? LNUM_VARIABLE_RANGE_ABOVE : LNUM_VARIABLE_RANGE;
+    }
     else
-	lnum = eap->line2;
-    if (above)
-	--lnum;
+    {
+	// Either no range or a number.
+	// "errormsg" will not be set because the range is ADDR_LINES.
+	if (parse_cmd_address(eap, &errormsg, FALSE) == FAIL)
+	    return NULL;
+	if (eap->addr_count == 0)
+	    lnum = -1;
+	else
+	    lnum = eap->line2;
+	if (above)
+	    --lnum;
+    }
 
     generate_PUT(cctx, eap->regname, lnum);
     return line;
@@ -7960,6 +8002,7 @@ delete_instr(isn_T *isn)
 	case ISN_PUSHEXC:
 	case ISN_PUSHFUNC:
 	case ISN_PUSHS:
+	case ISN_RANGE:
 	case ISN_STOREB:
 	case ISN_STOREENV:
 	case ISN_STOREG:
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -2861,6 +2861,26 @@ call_def_function(
 		}
 		break;
 
+	    case ISN_RANGE:
+		{
+		    exarg_T	ea;
+		    char	*errormsg;
+
+		    if (GA_GROW(&ectx.ec_stack, 1) == FAIL)
+			goto failed;
+		    ++ectx.ec_stack.ga_len;
+		    tv = STACK_TV_BOT(-1);
+		    ea.addr_type = ADDR_LINES;
+		    ea.cmd = iptr->isn_arg.string;
+		    if (parse_cmd_address(&ea, &errormsg, FALSE) == FAIL)
+			goto failed;
+		    if (ea.addr_count == 0)
+			tv->vval.v_number = curwin->w_cursor.lnum;
+		    else
+			tv->vval.v_number = ea.line2;
+		}
+		break;
+
 	    case ISN_PUT:
 		{
 		    int		regname = iptr->isn_arg.put.put_regname;
@@ -2880,7 +2900,16 @@ call_def_function(
 			}
 			--ectx.ec_stack.ga_len;
 		    }
-		    if (lnum == -2)
+		    if (lnum < -2)
+		    {
+			// line number was put on the stack by ISN_RANGE
+			tv = STACK_TV_BOT(-1);
+			curwin->w_cursor.lnum = tv->vval.v_number;
+			if (lnum == LNUM_VARIABLE_RANGE_ABOVE)
+			    dir = BACKWARD;
+			--ectx.ec_stack.ga_len;
+		    }
+		    else if (lnum == -2)
 			// :put! above cursor
 			dir = BACKWARD;
 		    else if (lnum >= 0)
@@ -3690,8 +3719,18 @@ ex_disassemble(exarg_T *eap)
 	    case ISN_2STRING_ANY: smsg("%4d 2STRING_ANY stack[%lld]", current,
 					 (long long)(iptr->isn_arg.number));
 			      break;
+	    case ISN_RANGE: smsg("%4d RANGE %s", current, iptr->isn_arg.string);
+			    break;
 	    case ISN_PUT:
-		smsg("%4d PUT %c %ld", current, iptr->isn_arg.put.put_regname,
+	        if (iptr->isn_arg.put.put_lnum == LNUM_VARIABLE_RANGE_ABOVE)
+		    smsg("%4d PUT %c above range",
+				       current, iptr->isn_arg.put.put_regname);
+		else if (iptr->isn_arg.put.put_lnum == LNUM_VARIABLE_RANGE)
+		    smsg("%4d PUT %c range",
+				       current, iptr->isn_arg.put.put_regname);
+		else
+		    smsg("%4d PUT %c %ld", current,
+						 iptr->isn_arg.put.put_regname,
 					     (long)iptr->isn_arg.put.put_lnum);
 		break;