From: Bram Moolenaar Date: Thu, 10 Dec 2020 18:43:40 +0000 (+0100) Subject: patch 8.2.2124: Vim9: a range cannot be computed at runtime X-Git-Tag: v8.2.2124 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=08597875b2a1e7d118b0346c652a96e7527e7d8b;p=vim 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. --- diff --git a/src/testdir/test_vim9_cmd.vim b/src/testdir/test_vim9_cmd.vim index 37fc559bf..1986c0f71 100644 --- 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 diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim index e7445d386..fabb8d261 100644 --- 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('\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 diff --git a/src/version.c b/src/version.c index 3c39f0e18..ee6be8a76 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2124, /**/ 2123, /**/ diff --git a/src/vim9.h b/src/vim9.h index 1368b3843..88cc4fce2 100644 --- 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(dfunc_T), 50, NULL}; 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 diff --git a/src/vim9compile.c b/src/vim9compile.c index da069f405..ac8fb846a 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -1888,6 +1888,26 @@ generate_EXECCONCAT(cctx_T *cctx, int count) 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) { @@ -7098,6 +7118,22 @@ compile_mult_expr(char_u *arg, int cmdidx, cctx_T *cctx) return p; } +/* + * 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, cctx_T *cctx) 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: diff --git a/src/vim9execute.c b/src/vim9execute.c index 7e8b336d0..3f61cb445 100644 --- 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;