# HG changeset patch # User Bram Moolenaar # Date 1638372604 -3600 # Node ID a3a0885d9dd8edcb1c2c90fd1b6fddabed5451c5 # Parent da56c576e58351264e3ddf72ea9936d4e3eb29e2 patch 8.2.3716: Vim9: range without a command is not compiled Commit: https://github.com/vim/vim/commit/e4eed8c6db693a9183b776032570ce2f89dcffb6 Author: Bram Moolenaar Date: Wed Dec 1 15:22:56 2021 +0000 patch 8.2.3716: Vim9: range without a command is not compiled Problem: Vim9: range without a command is not compiled. Solution: Add the ISN_EXECRANGE byte code. diff --git a/src/ex_docmd.c b/src/ex_docmd.c --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -1977,43 +1977,7 @@ do_one_cmd( */ if (ea.skip) // skip this if inside :if goto doend; - if ((*ea.cmd == '|' || (exmode_active && ea.line1 != ea.line2)) -#ifdef FEAT_EVAL - && !vim9script -#endif - ) - { - ea.cmdidx = CMD_print; - ea.argt = EX_RANGE+EX_COUNT+EX_TRLBAR; - if ((errormsg = invalid_range(&ea)) == NULL) - { - correct_range(&ea); - ex_print(&ea); - } - } - else if (ea.addr_count != 0) - { - if (ea.line2 > curbuf->b_ml.ml_line_count) - { - // With '-' in 'cpoptions' a line number past the file is an - // error, otherwise put it at the end of the file. - if (vim_strchr(p_cpo, CPO_MINUS) != NULL) - ea.line2 = -1; - else - ea.line2 = curbuf->b_ml.ml_line_count; - } - - if (ea.line2 < 0) - errormsg = _(e_invalid_range); - else - { - if (ea.line2 == 0) - curwin->w_cursor.lnum = 1; - else - curwin->w_cursor.lnum = ea.line2; - beginline(BL_SOL | BL_FIX); - } - } + errormsg = ex_range_without_command(&ea); goto doend; } @@ -2708,6 +2672,55 @@ ex_errmsg(char *msg, char_u *arg) } /* + * Handle a range without a command. + * Returns an error message on failure. + */ + char * +ex_range_without_command(exarg_T *eap) +{ + char *errormsg = NULL; + + if ((*eap->cmd == '|' || (exmode_active && eap->line1 != eap->line2)) +#ifdef FEAT_EVAL + && !in_vim9script() +#endif + ) + { + eap->cmdidx = CMD_print; + eap->argt = EX_RANGE+EX_COUNT+EX_TRLBAR; + if ((errormsg = invalid_range(eap)) == NULL) + { + correct_range(eap); + ex_print(eap); + } + } + else if (eap->addr_count != 0) + { + if (eap->line2 > curbuf->b_ml.ml_line_count) + { + // With '-' in 'cpoptions' a line number past the file is an + // error, otherwise put it at the end of the file. + if (vim_strchr(p_cpo, CPO_MINUS) != NULL) + eap->line2 = -1; + else + eap->line2 = curbuf->b_ml.ml_line_count; + } + + if (eap->line2 < 0) + errormsg = _(e_invalid_range); + else + { + if (eap->line2 == 0) + curwin->w_cursor.lnum = 1; + else + curwin->w_cursor.lnum = eap->line2; + beginline(BL_SOL | BL_FIX); + } + } + return errormsg; +} + +/* * Check for an Ex command with optional tail. * If there is a match advance "pp" to the argument and return TRUE. * If "noparen" is TRUE do not recognize the command followed by "(". 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 @@ -7,6 +7,7 @@ int getline_equal(char_u *(*fgetline)(in void *getline_cookie(char_u *(*fgetline)(int, void *, int, getline_opt_T), void *cookie); char_u *getline_peek(char_u *(*fgetline)(int, void *, int, getline_opt_T), void *cookie); char *ex_errmsg(char *msg, char_u *arg); +char *ex_range_without_command(exarg_T *eap); int checkforcmd(char_u **pp, char *cmd, int len); int checkforcmd_noparen(char_u **pp, char *cmd, int len); int parse_command_modifiers(exarg_T *eap, char **errormsg, cmdmod_T *cmod, int skip_only); @@ -22,7 +23,7 @@ int cmd_exists(char_u *name); void f_fullcommand(typval_T *argvars, typval_T *rettv); cmdidx_T excmd_get_cmdidx(char_u *cmd, int len); long excmd_get_argt(cmdidx_T idx); -char_u *skip_range(char_u *cmd, int skip_star, int *ctx); +char_u *skip_range(char_u *cmd_start, int skip_star, int *ctx); void ex_ni(exarg_T *eap); int expand_filename(exarg_T *eap, char_u **cmdlinep, char **errormsgp); void separate_nextcmd(exarg_T *eap); 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 @@ -1999,6 +1999,25 @@ def Test_disassemble_execute() res) enddef +def s:OnlyRange() + :$ + :123 + :'m +enddef + +def Test_disassemble_range_only() + var res = execute('disass s:OnlyRange') + assert_match('\\d*_OnlyRange\_s*' .. + ':$\_s*' .. + '\d EXECRANGE $\_s*' .. + ':123\_s*' .. + '\d EXECRANGE 123\_s*' .. + ':''m\_s*' .. + '\d EXECRANGE ''m\_s*' .. + '\d\+ RETURN void', + res) +enddef + def s:Echomsg() echomsg 'some' 'message' echoconsole 'nothing' diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -754,6 +754,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 3716, +/**/ 3715, /**/ 3714, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -15,6 +15,7 @@ typedef enum { ISN_EXEC, // execute Ex command line isn_arg.string ISN_EXECCONCAT, // execute Ex command from isn_arg.number items on stack ISN_EXEC_SPLIT, // execute Ex command from isn_arg.string split at NL + ISN_EXECRANGE, // execute EX command that is only a range ISN_LEGACY_EVAL, // evaluate expression isn_arg.string with legacy syntax. ISN_ECHO, // :echo with isn_arg.echo.echo_count items on top of stack ISN_EXECUTE, // :execute with isn_arg.number items on top of stack diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -2275,8 +2275,12 @@ generate_PUT(cctx_T *cctx, int regname, return OK; } - static int -generate_EXEC(cctx_T *cctx, isntype_T isntype, char_u *line) +/* + * Generate an EXEC instruction that takes a string argument. + * A copy is made of "line". + */ + static int +generate_EXEC_copy(cctx_T *cctx, isntype_T isntype, char_u *line) { isn_T *isn; @@ -2287,6 +2291,29 @@ generate_EXEC(cctx_T *cctx, isntype_T is return OK; } +/* + * Generate an EXEC instruction that takes a string argument. + * "str" must be allocated, it is consumed. + */ + static int +generate_EXEC(cctx_T *cctx, isntype_T isntype, char_u *str) +{ + isn_T *isn; + + if (cctx->ctx_skip == SKIP_YES) + { + vim_free(str); + return OK; + } + if ((isn = generate_instr(cctx, isntype)) == NULL) + { + vim_free(str); + return FAIL; + } + isn->isn_arg.string = str; + return OK; +} + static int generate_LEGACY_EVAL(cctx_T *cctx, char_u *line) { @@ -7552,7 +7579,7 @@ compile_lock_unlock( vim_snprintf((char *)buf, len, "%s %s", eap->cmdidx == CMD_lockvar ? "lockvar" : "unlockvar", p); - ret = generate_EXEC(cctx, isn, buf); + ret = generate_EXEC_copy(cctx, isn, buf); vim_free(buf); *name_end = cc; @@ -9248,7 +9275,7 @@ compile_exec(char_u *line_arg, exarg_T * generate_EXECCONCAT(cctx, count); } else - generate_EXEC(cctx, ISN_EXEC, line); + generate_EXEC_copy(cctx, ISN_EXEC, line); theend: if (*nextcmd != NUL) @@ -9874,10 +9901,12 @@ compile_def_function( if (ends_excmd2(line, ea.cmd)) { // A range without a command: jump to the line. - // TODO: compile to a more efficient command, possibly - // calling parse_cmd_address(). - ea.cmdidx = CMD_SIZE; - line = compile_exec(line, &ea, &cctx); + line = skipwhite(line); + while (*line == ':') + ++line; + generate_EXEC(&cctx, ISN_EXECRANGE, + vim_strnsave(line, ea.cmd - line)); + line = ea.cmd; goto nextline; } } @@ -10350,6 +10379,7 @@ delete_instr(isn_T *isn) { case ISN_DEF: case ISN_EXEC: + case ISN_EXECRANGE: case ISN_EXEC_SPLIT: case ISN_LEGACY_EVAL: case ISN_LOADAUTO: diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -1774,6 +1774,28 @@ exec_instructions(ectx_T *ectx) } break; + // execute Ex command line that is only a range + case ISN_EXECRANGE: + { + exarg_T ea; + char *error = NULL; + + CLEAR_FIELD(ea); + ea.cmdidx = CMD_SIZE; + ea.addr_type = ADDR_LINES; + ea.cmd = iptr->isn_arg.string; + parse_cmd_address(&ea, &error, FALSE); + if (error == NULL) + error = ex_range_without_command(&ea); + if (error != NULL) + { + SOURCING_LNUM = iptr->isn_lnum; + emsg(error); + goto on_error; + } + } + break; + // Evaluate an expression with legacy syntax, push it onto the // stack. case ISN_LEGACY_EVAL: @@ -5068,6 +5090,9 @@ list_instructions(char *pfx, isn_T *inst case ISN_EXEC_SPLIT: smsg("%s%4d EXEC_SPLIT %s", pfx, current, iptr->isn_arg.string); break; + case ISN_EXECRANGE: + smsg("%s%4d EXECRANGE %s", pfx, current, iptr->isn_arg.string); + break; case ISN_LEGACY_EVAL: smsg("%s%4d EVAL legacy %s", pfx, current, iptr->isn_arg.string);