# HG changeset patch # User Bram Moolenaar # Date 1651755604 -7200 # Node ID bfd8e25fa207f39f25937f009d60655399e5b2b3 # Parent ca54846af86141f9a0bf19b11b16d3a5cb34ff40 patch 8.2.4870: Vim9: expression in :substitute is not compiled Commit: https://github.com/vim/vim/commit/f3b4895f2727e3849ca10030b251cccd9d1383f3 Author: LemonBoy Date: Thu May 5 13:53:03 2022 +0100 patch 8.2.4870: Vim9: expression in :substitute is not compiled Problem: Vim9: expression in :substitute is not compiled. Solution: Use an INSTR instruction if possible. (closes https://github.com/vim/vim/issues/10334) diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -9966,7 +9966,9 @@ f_substitute(typval_T *argvars, typval_T pat = tv_get_string_buf_chk(&argvars[1], patbuf); flg = tv_get_string_buf_chk(&argvars[3], flagsbuf); - if (argvars[2].v_type == VAR_FUNC || argvars[2].v_type == VAR_PARTIAL) + if (argvars[2].v_type == VAR_FUNC + || argvars[2].v_type == VAR_PARTIAL + || argvars[2].v_type == VAR_INSTR) expr = &argvars[2]; else sub = tv_get_string_buf_chk(&argvars[2], subbuf); diff --git a/src/regexp.c b/src/regexp.c --- a/src/regexp.c +++ b/src/regexp.c @@ -2004,6 +2004,10 @@ vim_regsub_both( funcexe.fe_partial = partial; call_func(s, -1, &rettv, 1, argv, &funcexe); } + else if (expr->v_type == VAR_INSTR) + { + exe_typval_instr(expr, &rettv); + } if (matchList.sl_list.lv_len > 0) // fill_submatch_list() was called clear_submatch_list(&matchList); diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -4078,6 +4078,11 @@ def Test_substitute() v9.CheckDefAndScriptFailure(['substitute("a", 2, "1", "d")'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2']) v9.CheckDefAndScriptFailure(['substitute("a", "b", "1", 4)'], ['E1013: Argument 4: type mismatch, expected string but got number', 'E1174: String required for argument 4']) substitute('', '', '', '')->assert_equal('') + + var lines =<< trim END + assert_equal("4", substitute("3", '\d', '\=str2nr(submatch(0)) + 1', 'g')) + END + v9.CheckDefAndScriptSuccess(lines) enddef def Test_swapinfo() 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 @@ -187,6 +187,26 @@ def Test_disassemble_seachpair() enddef +def s:SubstituteExpr() + substitute('a', 'b', '\=123', 'g') +enddef + +def Test_disassemble_substitute_expr() + var res = execute('disass s:SubstituteExpr') + assert_match('\d*_SubstituteExpr.*' .. + 'substitute(''a'', ''b'', ''\\=123'', ''g'')\_s*' .. + '\d PUSHS "a"\_s*' .. + '\d PUSHS "b"\_s*' .. + '\d INSTR\_s*' .. + ' 0 PUSHNR 123\_s*' .. + ' -------------\_s*' .. + '\d PUSHS "g"\_s*' .. + '\d BCALL substitute(argc 4)\_s*' .. + '\d DROP\_s*' .. + '\d RETURN void', + res) +enddef + def s:RedirVar() var result: string redir =>> result diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -747,6 +747,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 4870, +/**/ 4869, /**/ 4868, diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -5010,6 +5010,10 @@ exe_typval_instr(typval_T *tv, typval_T int save_iidx = ectx->ec_iidx; int res; + // Initialize rettv so that it is safe for caller to invoke clear_tv(rettv) + // even when the compilation fails. + rettv->v_type = VAR_UNKNOWN; + ectx->ec_instr = tv->vval.v_instr->instr_instr; res = exec_instructions(ectx); if (res == OK) diff --git a/src/vim9expr.c b/src/vim9expr.c --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -567,12 +567,13 @@ theend: /* * Compile a string in a ISN_PUSHS instruction into an ISN_INSTR. + * "str_offset" is the number of leading bytes to skip from the string. * Returns FAIL if compilation fails. */ static int -compile_string(isn_T *isn, cctx_T *cctx) +compile_string(isn_T *isn, cctx_T *cctx, int str_offset) { - char_u *s = isn->isn_arg.string; + char_u *s = isn->isn_arg.string + str_offset; garray_T save_ga = cctx->ctx_instr; int expr_res; int trailing_error; @@ -616,11 +617,24 @@ compile_string(isn_T *isn, cctx_T *cctx) } /* + * List of special functions for "compile_arguments". + */ +typedef enum { + CA_NOT_SPECIAL, + CA_SEARCHPAIR, // {skip} in searchpair() and searchpairpos() + CA_SUBSTITUTE, // {sub} in substitute(), when prefixed with \= +} ca_special_T; + +/* * Compile the argument expressions. * "arg" points to just after the "(" and is advanced to after the ")" */ static int -compile_arguments(char_u **arg, cctx_T *cctx, int *argcount, int is_searchpair) +compile_arguments( + char_u **arg, + cctx_T *cctx, + int *argcount, + ca_special_T special_fn) { char_u *p = *arg; char_u *whitep = *arg; @@ -647,14 +661,25 @@ compile_arguments(char_u **arg, cctx_T * return FAIL; ++*argcount; - if (is_searchpair && *argcount == 5 + if (special_fn == CA_SEARCHPAIR && *argcount == 5 && cctx->ctx_instr.ga_len == instr_count + 1) { isn_T *isn = ((isn_T *)cctx->ctx_instr.ga_data) + instr_count; // {skip} argument of searchpair() can be compiled if not empty if (isn->isn_type == ISN_PUSHS && *isn->isn_arg.string != NUL) - compile_string(isn, cctx); + compile_string(isn, cctx, 0); + } + else if (special_fn == CA_SUBSTITUTE && *argcount == 3 + && cctx->ctx_instr.ga_len == instr_count + 1) + { + isn_T *isn = ((isn_T *)cctx->ctx_instr.ga_data) + instr_count; + + // {sub} argument of substitute() can be compiled if it starts + // with \= + if (isn->isn_type == ISN_PUSHS && isn->isn_arg.string[0] == '\\' + && isn->isn_arg.string[1] == '=') + compile_string(isn, cctx, 2); } if (*p != ',' && *skipwhite(p) == ',') @@ -706,7 +731,7 @@ compile_call( int res = FAIL; int is_autoload; int has_g_namespace; - int is_searchpair; + ca_special_T special_fn; imported_T *import; if (varlen >= sizeof(namebuf)) @@ -776,13 +801,18 @@ compile_call( // We handle the "skip" argument of searchpair() and searchpairpos() // differently. - is_searchpair = (varlen == 6 && STRNCMP(*arg, "search", 6) == 0) - || (varlen == 9 && STRNCMP(*arg, "searchpos", 9) == 0) - || (varlen == 10 && STRNCMP(*arg, "searchpair", 10) == 0) - || (varlen == 13 && STRNCMP(*arg, "searchpairpos", 13) == 0); + if ((varlen == 6 && STRNCMP(*arg, "search", 6) == 0) + || (varlen == 9 && STRNCMP(*arg, "searchpos", 9) == 0) + || (varlen == 10 && STRNCMP(*arg, "searchpair", 10) == 0) + || (varlen == 13 && STRNCMP(*arg, "searchpairpos", 13) == 0)) + special_fn = CA_SEARCHPAIR; + else if (varlen == 10 && STRNCMP(*arg, "substitute", 10) == 0) + special_fn = CA_SUBSTITUTE; + else + special_fn = CA_NOT_SPECIAL; *arg = skipwhite(*arg + varlen + 1); - if (compile_arguments(arg, cctx, &argcount, is_searchpair) == FAIL) + if (compile_arguments(arg, cctx, &argcount, special_fn) == FAIL) goto theend; is_autoload = vim_strchr(name, AUTOLOAD_CHAR) != NULL; @@ -1717,7 +1747,7 @@ compile_subscript( type = get_type_on_stack(cctx, 0); *arg = skipwhite(p + 1); - if (compile_arguments(arg, cctx, &argcount, FALSE) == FAIL) + if (compile_arguments(arg, cctx, &argcount, CA_NOT_SPECIAL) == FAIL) return FAIL; if (generate_PCALL(cctx, argcount, name_start, type, TRUE) == FAIL) return FAIL; @@ -1848,7 +1878,8 @@ compile_subscript( expr_isn_end = cctx->ctx_instr.ga_len; *arg = skipwhite(*arg + 1); - if (compile_arguments(arg, cctx, &argcount, FALSE) == FAIL) + if (compile_arguments(arg, cctx, &argcount, CA_NOT_SPECIAL) + == FAIL) return FAIL; // Move the instructions for the arguments to before the