diff src/vim9compile.c @ 24488:f293bb501b30 v8.2.2784

patch 8.2.2784: Vim9: cannot use =expr in :substitute Commit: https://github.com/vim/vim/commit/4c13721482d7786f92f5a56e43b0f5c499264b7e Author: Bram Moolenaar <Bram@vim.org> Date: Mon Apr 19 16:48:48 2021 +0200 patch 8.2.2784: Vim9: cannot use \=expr in :substitute Problem: Vim9: cannot use \=expr in :substitute. Solution: Compile the expression into instructions and execute them when invoked.
author Bram Moolenaar <Bram@vim.org>
date Mon, 19 Apr 2021 17:00:04 +0200
parents 943e9b1d2d16
children 08050e45bd06
line wrap: on
line diff
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -2130,6 +2130,33 @@ 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.
  */
@@ -8466,6 +8493,55 @@ theend:
 }
 
 /*
+ * :s/pat/repl/
+ */
+    static char_u *
+compile_substitute(char_u *arg, exarg_T *eap, cctx_T *cctx)
+{
+    char_u  *cmd = eap->arg;
+    char_u  *expr = (char_u *)strstr((char *)cmd, "\\=");
+
+    if (expr != NULL)
+    {
+	int delimiter = *cmd++;
+
+	// There is a \=expr, find it in the substitute part.
+	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;
+
+	    cmd += 3;
+	    end = skip_substitute(cmd, delimiter);
+
+	    compile_expr0(&cmd, cctx);
+	    if (end[-1] == NUL)
+		end[-1] = delimiter;
+	    cmd = skipwhite(cmd);
+	    if (*cmd != delimiter && *cmd != NUL)
+	    {
+		semsg(_(e_trailing_arg), cmd);
+		return NULL;
+	    }
+
+	    if (generate_substitute(arg, instr_count, cctx) == FAIL)
+		return NULL;
+
+	    // skip over flags
+	    if (*end == '&')
+		++end;
+	    while (ASCII_ISALPHA(*end) || *end == '#')
+		++end;
+	    return end;
+	}
+    }
+
+    return compile_exec(arg, eap, cctx);
+}
+
+/*
  * Add a function to the list of :def functions.
  * This sets "ufunc->uf_dfunc_idx" but the function isn't compiled yet.
  */
@@ -8996,6 +9072,16 @@ compile_def_function(
 		    line = compile_put(p, &ea, &cctx);
 		    break;
 
+	    case CMD_substitute:
+		    if (cctx.ctx_skip == SKIP_YES)
+			line = (char_u *)"";
+		    else
+		    {
+			ea.arg = p;
+			line = compile_substitute(line, &ea, &cctx);
+		    }
+		    break;
+
 	    // TODO: any other commands with an expression argument?
 
 	    case CMD_append:
@@ -9223,6 +9309,11 @@ delete_instr(isn_T *isn)
 	    vim_free(isn->isn_arg.string);
 	    break;
 
+	case ISN_SUBSTITUTE:
+	    vim_free(isn->isn_arg.subs.subs_cmd);
+	    vim_free(isn->isn_arg.subs.subs_instr);
+	    break;
+
 	case ISN_LOADS:
 	case ISN_STORES:
 	    vim_free(isn->isn_arg.loadstore.ls_name);
@@ -9400,6 +9491,7 @@ delete_instr(isn_T *isn)
 	case ISN_UNLETINDEX:
 	case ISN_UNLETRANGE:
 	case ISN_UNPACK:
+	case ISN_FINISH:
 	    // nothing allocated
 	    break;
     }