# HG changeset patch # User Bram Moolenaar # Date 1618946104 -7200 # Node ID bfa495227ac6eb0e2b28864b0e6429b8f34bf5fb # Parent b444a9f9b46703902f868faf9b92e658c79ab8bd patch 8.2.2789: Vim9: using =expr in :substitute does not handle jumps Commit: https://github.com/vim/vim/commit/8238f08838c0b481d0b14a741bc03f2e45c211d3 Author: Bram Moolenaar Date: Tue Apr 20 21:10:48 2021 +0200 patch 8.2.2789: Vim9: using \=expr in :substitute does not handle jumps Problem: Vim9: using \=expr in :substitute does not handle jumps. Solution: Start with instruction count zero. (closes https://github.com/vim/vim/issues/8128) 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 @@ -1188,6 +1188,12 @@ def Test_substitute_expr() s/from/\=to .. '_' .. also/g#e assert_equal('one repl_also two repl_also three', getline(1)) + setline(1, 'abc abc abc') + for choice in [true, false] + :1s/abc/\=choice ? 'yes' : 'no'/ + endfor + assert_equal('yes no abc', getline(1)) + CheckDefFailure(['s/from/\="x")/'], 'E488:') CheckDefFailure(['s/from/\="x"/9'], 'E488:') 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 */ /**/ + 2789, +/**/ 2788, /**/ 2787, diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -2179,33 +2179,6 @@ generate_EXECCONCAT(cctx_T *cctx, int co return OK; } - static int -generate_substitute(char_u *cmd, int instr_start, cctx_T *cctx) -{ - isn_T *isn; - isn_T *instr; - int instr_count = cctx->ctx_instr.ga_len - instr_start; - - instr = ALLOC_MULT(isn_T, instr_count + 1); - if (instr == NULL) - return FAIL; - // Move the generated instructions into the ISN_SUBSTITUTE instructions, - // then truncate the list of instructions, so they are used only once. - mch_memmove(instr, ((isn_T *)cctx->ctx_instr.ga_data) + instr_start, - instr_count * sizeof(isn_T)); - instr[instr_count].isn_type = ISN_FINISH; - cctx->ctx_instr.ga_len = instr_start; - - if ((isn = generate_instr(cctx, ISN_SUBSTITUTE)) == NULL) - { - vim_free(instr); - return FAIL; - } - isn->isn_arg.subs.subs_cmd = vim_strsave(cmd); - isn->isn_arg.subs.subs_instr = instr; - return OK; -} - /* * Generate ISN_RANGE. Consumes "range". Return OK/FAIL. */ @@ -8522,6 +8495,17 @@ theend: return nextcmd; } + + static void +clear_instr_ga(garray_T *gap) +{ + int idx; + + for (idx = 0; idx < gap->ga_len; ++idx) + delete_instr(((isn_T *)gap->ga_data) + idx); + ga_clear(gap); +} + /* * :s/pat/repl/ */ @@ -8536,28 +8520,61 @@ compile_substitute(char_u *arg, exarg_T int delimiter = *cmd++; // There is a \=expr, find it in the substitute part. - cmd = skip_regexp_ex(cmd, delimiter, magic_isset(), - NULL, NULL, NULL); + cmd = skip_regexp_ex(cmd, delimiter, magic_isset(), NULL, NULL, NULL); if (cmd[0] == delimiter && cmd[1] == '\\' && cmd[2] == '=') { - int instr_count = cctx->ctx_instr.ga_len; - char_u *end; + garray_T save_ga = cctx->ctx_instr; + char_u *end; + int trailing_error; + int instr_count; + isn_T *instr = NULL; + isn_T *isn; cmd += 3; end = skip_substitute(cmd, delimiter); + // Temporarily reset the list of instructions so that the jumps + // labels are correct. + cctx->ctx_instr.ga_len = 0; + cctx->ctx_instr.ga_maxlen = 0; + cctx->ctx_instr.ga_data = NULL; compile_expr0(&cmd, cctx); if (end[-1] == NUL) end[-1] = delimiter; cmd = skipwhite(cmd); - if (*cmd != delimiter && *cmd != NUL) + trailing_error = *cmd != delimiter && *cmd != NUL; + + instr_count = cctx->ctx_instr.ga_len; + instr = ALLOC_MULT(isn_T, instr_count + 1); + if (trailing_error || instr == NULL) { - semsg(_(e_trailing_arg), cmd); + if (trailing_error) + semsg(_(e_trailing_arg), cmd); + clear_instr_ga(&cctx->ctx_instr); + cctx->ctx_instr = save_ga; + vim_free(instr); return NULL; } - if (generate_substitute(arg, instr_count, cctx) == FAIL) + // Move the generated instructions into the ISN_SUBSTITUTE + // instructions, then restore the list of instructions before + // adding the ISN_SUBSTITUTE instruction. + mch_memmove(instr, cctx->ctx_instr.ga_data, + instr_count * sizeof(isn_T)); + instr[instr_count].isn_type = ISN_FINISH; + + cctx->ctx_instr = save_ga; + if ((isn = generate_instr(cctx, ISN_SUBSTITUTE)) == NULL) + { + int idx; + + for (idx = 0; idx < instr_count; ++idx) + delete_instr(instr + idx); + vim_free(instr); return NULL; + } + isn->isn_arg.subs.subs_cmd = vim_strsave(arg); + isn->isn_arg.subs.subs_instr = instr; // skip over flags if (*end == '&') @@ -9285,13 +9302,10 @@ nextline: erret: if (ufunc->uf_def_status == UF_COMPILING) { - int idx; dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + ufunc->uf_dfunc_idx; - for (idx = 0; idx < instr->ga_len; ++idx) - delete_instr(((isn_T *)instr->ga_data) + idx); - ga_clear(instr); + clear_instr_ga(instr); VIM_CLEAR(dfunc->df_name); // If using the last entry in the table and it was added above, we