changeset 24590:2818f846f099 v8.2.2834

patch 8.2.2834: Vim9: :cexpr does not work with local variables Commit: https://github.com/vim/vim/commit/5f7d4c049e934dbc8d2c3f2720797c10ee3c55c2 Author: Bram Moolenaar <Bram@vim.org> Date: Wed May 5 21:31:39 2021 +0200 patch 8.2.2834: Vim9: :cexpr does not work with local variables Problem: Vim9: :cexpr does not work with local variables. Solution: Compile :cexpr.
author Bram Moolenaar <Bram@vim.org>
date Wed, 05 May 2021 21:45:04 +0200
parents c770bde164ea
children 760d7c2f6bf2
files src/proto/quickfix.pro src/quickfix.c src/testdir/test_quickfix.vim src/testdir/test_vim9_disassemble.vim src/version.c src/vim9.h src/vim9compile.c src/vim9execute.c
diffstat 8 files changed, 207 insertions(+), 55 deletions(-) [+]
line wrap: on
line diff
--- a/src/proto/quickfix.pro
+++ b/src/proto/quickfix.pro
@@ -30,6 +30,9 @@ void ex_vimgrep(exarg_T *eap);
 int set_errorlist(win_T *wp, list_T *list, int action, char_u *title, dict_T *what);
 int set_ref_in_quickfix(int copyID);
 void ex_cbuffer(exarg_T *eap);
+char_u *cexpr_get_auname(cmdidx_T cmdidx);
+int trigger_cexpr_autocmd(int cmdidx);
+int cexpr_core(exarg_T *eap, typval_T *tv);
 void ex_cexpr(exarg_T *eap);
 void ex_helpgrep(exarg_T *eap);
 void f_getloclist(typval_T *argvars, typval_T *rettv);
--- a/src/quickfix.c
+++ b/src/quickfix.c
@@ -7864,7 +7864,7 @@ ex_cbuffer(exarg_T *eap)
 /*
  * Return the autocmd name for the :cexpr Ex commands.
  */
-    static char_u *
+    char_u *
 cexpr_get_auname(cmdidx_T cmdidx)
 {
     switch (cmdidx)
@@ -7879,32 +7879,83 @@ cexpr_get_auname(cmdidx_T cmdidx)
     }
 }
 
+    int
+trigger_cexpr_autocmd(int cmdidx)
+{
+    char_u	*au_name = cexpr_get_auname(cmdidx);
+
+    if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name,
+					       curbuf->b_fname, TRUE, curbuf))
+    {
+	if (aborting())
+	    return FAIL;
+    }
+    return OK;
+}
+
+    int
+cexpr_core(exarg_T *eap, typval_T *tv)
+{
+    qf_info_T	*qi;
+    win_T	*wp = NULL;
+
+    qi = qf_cmd_get_or_alloc_stack(eap, &wp);
+    if (qi == NULL)
+	return FAIL;
+
+    if ((tv->v_type == VAR_STRING && tv->vval.v_string != NULL)
+	    || (tv->v_type == VAR_LIST && tv->vval.v_list != NULL))
+    {
+	int	res;
+	int_u	save_qfid;
+	char_u	*au_name = cexpr_get_auname(eap->cmdidx);
+
+	incr_quickfix_busy();
+	res = qf_init_ext(qi, qi->qf_curlist, NULL, NULL, tv, p_efm,
+			(eap->cmdidx != CMD_caddexpr
+			 && eap->cmdidx != CMD_laddexpr),
+			     (linenr_T)0, (linenr_T)0,
+			     qf_cmdtitle(*eap->cmdlinep), NULL);
+	if (qf_stack_empty(qi))
+	{
+	    decr_quickfix_busy();
+	    return FAIL;
+	}
+	if (res >= 0)
+	    qf_list_changed(qf_get_curlist(qi));
+
+	// Remember the current quickfix list identifier, so that we can
+	// check for autocommands changing the current quickfix list.
+	save_qfid = qf_get_curlist(qi)->qf_id;
+	if (au_name != NULL)
+	    apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name,
+					    curbuf->b_fname, TRUE, curbuf);
+
+	// Jump to the first error for a new list and if autocmds didn't
+	// free the list.
+	if (res > 0 && (eap->cmdidx == CMD_cexpr || eap->cmdidx == CMD_lexpr)
+		&& qflist_valid(wp, save_qfid))
+	    // display the first error
+	    qf_jump_first(qi, save_qfid, eap->forceit);
+	decr_quickfix_busy();
+	return OK;
+    }
+
+    emsg(_("E777: String or List expected"));
+    return FAIL;
+}
+
 /*
  * ":cexpr {expr}", ":cgetexpr {expr}", ":caddexpr {expr}" command.
  * ":lexpr {expr}", ":lgetexpr {expr}", ":laddexpr {expr}" command.
+ * Also: ":caddexpr", ":cgetexpr", "laddexpr" and "laddexpr".
  */
     void
 ex_cexpr(exarg_T *eap)
 {
     typval_T	*tv;
-    qf_info_T	*qi;
-    char_u	*au_name = NULL;
-    int		res;
-    int_u	save_qfid;
-    win_T	*wp = NULL;
-
-    au_name = cexpr_get_auname(eap->cmdidx);
-    if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name,
-					       curbuf->b_fname, TRUE, curbuf))
-    {
-#ifdef FEAT_EVAL
-	if (aborting())
-	    return;
-#endif
-    }
-
-    qi = qf_cmd_get_or_alloc_stack(eap, &wp);
-    if (qi == NULL)
+
+    if (trigger_cexpr_autocmd(eap->cmdidx) == FAIL)
 	return;
 
     // Evaluate the expression.  When the result is a string or a list we can
@@ -7912,42 +7963,7 @@ ex_cexpr(exarg_T *eap)
     tv = eval_expr(eap->arg, eap);
     if (tv != NULL)
     {
-	if ((tv->v_type == VAR_STRING && tv->vval.v_string != NULL)
-		|| (tv->v_type == VAR_LIST && tv->vval.v_list != NULL))
-	{
-	    incr_quickfix_busy();
-	    res = qf_init_ext(qi, qi->qf_curlist, NULL, NULL, tv, p_efm,
-			    (eap->cmdidx != CMD_caddexpr
-			     && eap->cmdidx != CMD_laddexpr),
-				 (linenr_T)0, (linenr_T)0,
-				 qf_cmdtitle(*eap->cmdlinep), NULL);
-	    if (qf_stack_empty(qi))
-	    {
-		decr_quickfix_busy();
-		goto cleanup;
-	    }
-	    if (res >= 0)
-		qf_list_changed(qf_get_curlist(qi));
-
-	    // Remember the current quickfix list identifier, so that we can
-	    // check for autocommands changing the current quickfix list.
-	    save_qfid = qf_get_curlist(qi)->qf_id;
-	    if (au_name != NULL)
-		apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name,
-						curbuf->b_fname, TRUE, curbuf);
-
-	    // Jump to the first error for a new list and if autocmds didn't
-	    // free the list.
-	    if (res > 0 && (eap->cmdidx == CMD_cexpr
-						   || eap->cmdidx == CMD_lexpr)
-		    && qflist_valid(wp, save_qfid))
-		// display the first error
-		qf_jump_first(qi, save_qfid, eap->forceit);
-	    decr_quickfix_busy();
-	}
-	else
-	    emsg(_("E777: String or List expected"));
-cleanup:
+	(void)cexpr_core(eap, tv);
 	free_tv(tv);
     }
 }
--- a/src/testdir/test_quickfix.vim
+++ b/src/testdir/test_quickfix.vim
@@ -722,6 +722,22 @@ def Test_helpgrep_vim9_restore_cpo()
   helpclose
 enddef
 
+def Test_vim9_cexpr()
+  var text = 'somefile:95:error'
+  cexpr text
+  var l = getqflist()
+  assert_equal(1, l->len())
+  assert_equal(95, l[0].lnum)
+  assert_equal('error', l[0].text)
+
+  text = 'somefile:77:warning'
+  caddexpr text
+  l = getqflist()
+  assert_equal(2, l->len())
+  assert_equal(77, l[1].lnum)
+  assert_equal('warning', l[1].text)
+enddef
+
 func Test_errortitle()
   augroup QfBufWinEnter
     au!
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -167,6 +167,25 @@ def Test_disassemble_redir_var()
         res)
 enddef
 
+def s:Cexpr()
+  var errors = "list of errors"
+  cexpr errors
+enddef
+
+def Test_disassemble_cexpr()
+  var res = execute('disass s:Cexpr')
+  assert_match('<SNR>\d*_Cexpr.*' ..
+        ' var errors = "list of errors"\_s*' ..
+        '\d PUSHS "list of errors"\_s*' ..
+        '\d STORE $0\_s*' ..
+        ' cexpr errors\_s*' ..
+        '\d CEXPR pre cexpr\_s*' ..
+        '\d LOAD $0\_s*' ..
+        '\d CEXPR core cexpr "cexpr errors"\_s*' ..
+        '\d RETURN 0',
+        res)
+enddef
+
 def s:YankRange()
   norm! m[jjm]
   :'[,']yank
--- 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 */
 /**/
+    2834,
+/**/
     2833,
 /**/
     2832,
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -172,6 +172,9 @@ typedef enum {
     ISN_REDIRSTART, // :redir =>
     ISN_REDIREND,   // :redir END, isn_arg.number == 1 for append
 
+    ISN_CEXPR_AUCMD, // first part of :cexpr  isn_arg.number is cmdidx
+    ISN_CEXPR_CORE,  // second part of :cexpr, uses isn_arg.cexpr
+
     ISN_FINISH	    // end marker in list of instructions
 } isntype_T;
 
@@ -352,6 +355,18 @@ typedef struct {
     isn_T	*subs_instr;	// sequence of instructions
 } subs_T;
 
+// indirect arguments to ISN_TRY
+typedef struct {
+    int		cer_cmdidx;
+    char_u	*cer_cmdline;
+    int		cer_forceit;
+} cexprref_T;
+
+// arguments to ISN_CEXPR_CORE
+typedef struct {
+    cexprref_T *cexpr_ref;
+} cexpr_T;
+
 /*
  * Instruction
  */
@@ -395,6 +410,7 @@ struct isn_S {
 	unpack_T	    unpack;
 	isn_outer_T	    outer;
 	subs_T		    subs;
+	cexpr_T		    cexpr;
     } isn_arg;
 };
 
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -8704,6 +8704,34 @@ compile_redir(char_u *line, exarg_T *eap
     return compile_exec(line, eap, cctx);
 }
 
+    static char_u *
+compile_cexpr(char_u *line, exarg_T *eap, cctx_T *cctx)
+{
+    isn_T	*isn;
+    char_u	*p;
+
+    isn = generate_instr(cctx, ISN_CEXPR_AUCMD);
+    if (isn == NULL)
+	return NULL;
+    isn->isn_arg.number = eap->cmdidx;
+
+    p = eap->arg;
+    if (compile_expr0(&p, cctx) == FAIL)
+	return NULL;
+
+    isn = generate_instr(cctx, ISN_CEXPR_CORE);
+    if (isn == NULL)
+	return NULL;
+    isn->isn_arg.cexpr.cexpr_ref = ALLOC_ONE(cexprref_T);
+    if (isn->isn_arg.cexpr.cexpr_ref == NULL)
+	return NULL;
+    isn->isn_arg.cexpr.cexpr_ref->cer_cmdidx = eap->cmdidx;
+    isn->isn_arg.cexpr.cexpr_ref->cer_forceit = eap->forceit;
+    isn->isn_arg.cexpr.cexpr_ref->cer_cmdline = vim_strsave(skipwhite(line));
+
+    return p;
+}
+
 /*
  * Add a function to the list of :def functions.
  * This sets "ufunc->uf_dfunc_idx" but the function isn't compiled yet.
@@ -9262,6 +9290,16 @@ compile_def_function(
 		    line = compile_redir(line, &ea, &cctx);
 		    break;
 
+	    case CMD_cexpr:
+	    case CMD_lexpr:
+	    case CMD_caddexpr:
+	    case CMD_laddexpr:
+	    case CMD_cgetexpr:
+	    case CMD_lgetexpr:
+		    ea.arg = p;
+		    line = compile_cexpr(line, &ea, &cctx);
+		    break;
+
 	    // TODO: any other commands with an expression argument?
 
 	    case CMD_append:
@@ -9602,6 +9640,10 @@ delete_instr(isn_T *isn)
 	    vim_free(isn->isn_arg.try.try_ref);
 	    break;
 
+	case ISN_CEXPR_CORE:
+	    vim_free(isn->isn_arg.cexpr.cexpr_ref);
+	    break;
+
 	case ISN_2BOOL:
 	case ISN_2STRING:
 	case ISN_2STRING_ANY:
@@ -9614,6 +9656,7 @@ delete_instr(isn_T *isn)
 	case ISN_BLOBINDEX:
 	case ISN_BLOBSLICE:
 	case ISN_CATCH:
+	case ISN_CEXPR_AUCMD:
 	case ISN_CHECKLEN:
 	case ISN_CHECKNR:
 	case ISN_CMDMOD_REV:
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -1442,6 +1442,29 @@ exec_instructions(ectx_T *ectx)
 		}
 		break;
 
+	    case ISN_CEXPR_AUCMD:
+		if (trigger_cexpr_autocmd(iptr->isn_arg.number) == FAIL)
+		    goto on_error;
+		break;
+
+	    case ISN_CEXPR_CORE:
+		{
+		    exarg_T ea;
+		    int	    res;
+
+		    CLEAR_FIELD(ea);
+		    ea.cmdidx = iptr->isn_arg.cexpr.cexpr_ref->cer_cmdidx;
+		    ea.forceit = iptr->isn_arg.cexpr.cexpr_ref->cer_forceit;
+		    ea.cmdlinep = &iptr->isn_arg.cexpr.cexpr_ref->cer_cmdline;
+		    --ectx->ec_stack.ga_len;
+		    tv = STACK_TV_BOT(0);
+		    res = cexpr_core(&ea, tv);
+		    clear_tv(tv);
+		    if (res == FAIL)
+			goto on_error;
+		}
+		break;
+
 	    // execute Ex command from pieces on the stack
 	    case ISN_EXECCONCAT:
 		{
@@ -4391,6 +4414,20 @@ list_instructions(char *pfx, isn_T *inst
 		smsg("%s%4d REDIR END%s", pfx, current,
 					iptr->isn_arg.number ? " append" : "");
 		break;
+	    case ISN_CEXPR_AUCMD:
+		smsg("%s%4d CEXPR pre %s", pfx, current,
+				       cexpr_get_auname(iptr->isn_arg.number));
+		break;
+	    case ISN_CEXPR_CORE:
+		{
+		    cexprref_T	    *cer = iptr->isn_arg.cexpr.cexpr_ref;
+
+		    smsg("%s%4d CEXPR core %s%s \"%s\"", pfx, current,
+				       cexpr_get_auname(cer->cer_cmdidx),
+				       cer->cer_forceit ? "!" : "",
+				       cer->cer_cmdline);
+		}
+		break;
 	    case ISN_SUBSTITUTE:
 		{
 		    subs_T *subs = &iptr->isn_arg.subs;