# HG changeset patch # User Bram Moolenaar # Date 1607625904 -3600 # Node ID 6aa8ddf7a3fabd71e6ce25798083b1ce2ed43a11 # Parent 1172003ef1bff9f2ba41093ada454ee219dcb800 patch 8.2.2124: Vim9: a range cannot be computed at runtime Commit: https://github.com/vim/vim/commit/08597875b2a1e7d118b0346c652a96e7527e7d8b Author: Bram Moolenaar 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. 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 @@ -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 --- 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 --- 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, diff --git a/src/vim9.h b/src/vim9.h --- 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 diff --git a/src/vim9compile.c b/src/vim9compile.c --- 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: diff --git a/src/vim9execute.c b/src/vim9execute.c --- 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;