summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2021-04-19 20:50:03 +0200
committerBram Moolenaar <Bram@vim.org>2021-04-19 20:50:03 +0200
commit2d1c57ed3dd25c44b41b9ddd4cf63c01ae89007e (patch)
tree3ffc5635d92db32e5310f46c879ca0424223d21e /src
parent4c13721482d7786f92f5a56e43b0f5c499264b7e (diff)
patch 8.2.2785: Vim9: cannot redirect to local variablev8.2.2785
Problem: Vim9: cannot redirect to local variable. Solution: Compile :redir when redirecting to a variable.
Diffstat (limited to 'src')
-rw-r--r--src/errors.h4
-rw-r--r--src/evalvars.c40
-rw-r--r--src/proto/evalvars.pro6
-rw-r--r--src/testdir/test_vim9_cmd.vim18
-rw-r--r--src/testdir/test_vim9_disassemble.vim27
-rw-r--r--src/version.c2
-rw-r--r--src/vim9.h3
-rw-r--r--src/vim9compile.c294
-rw-r--r--src/vim9execute.c38
9 files changed, 335 insertions, 97 deletions
diff --git a/src/errors.h b/src/errors.h
index 1e091867f2..0781ca150f 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -405,3 +405,7 @@ EXTERN char e_cannot_use_range_with_assignment_operator_str[]
INIT(= N_("E1183: Cannot use a range with an assignment operator: %s"));
EXTERN char e_blob_not_set[]
INIT(= N_("E1184: Blob not set"));
+EXTERN char e_cannot_nest_redir[]
+ INIT(= N_("E1185: Cannot nest :redir"));
+EXTERN char e_missing_redir_end[]
+ INIT(= N_("E1185: Missing :redir END"));
diff --git a/src/evalvars.c b/src/evalvars.c
index 9952ffcadf..1b0b7d0c98 100644
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -3778,6 +3778,27 @@ static garray_T redir_ga; // only valid when redir_lval is not NULL
static char_u *redir_endp = NULL;
static char_u *redir_varname = NULL;
+ int
+alloc_redir_lval(void)
+{
+ redir_lval = ALLOC_CLEAR_ONE(lval_T);
+ if (redir_lval == NULL)
+ return FAIL;
+ return OK;
+}
+
+ void
+clear_redir_lval(void)
+{
+ VIM_CLEAR(redir_lval);
+}
+
+ void
+init_redir_ga(void)
+{
+ ga_init2(&redir_ga, (int)sizeof(char), 500);
+}
+
/*
* Start recording command output to a variable
* When "append" is TRUE append to an existing variable.
@@ -3801,15 +3822,14 @@ var_redir_start(char_u *name, int append)
if (redir_varname == NULL)
return FAIL;
- redir_lval = ALLOC_CLEAR_ONE(lval_T);
- if (redir_lval == NULL)
+ if (alloc_redir_lval() == FAIL)
{
var_redir_stop();
return FAIL;
}
// The output is stored in growarray "redir_ga" until redirection ends.
- ga_init2(&redir_ga, (int)sizeof(char), 500);
+ init_redir_ga();
// Parse the variable name (can be a dict or list entry).
redir_endp = get_lval(redir_varname, NULL, redir_lval, FALSE, FALSE, 0,
@@ -3922,6 +3942,20 @@ var_redir_stop(void)
}
/*
+ * Get the collected redirected text and clear redir_ga.
+ */
+ char_u *
+get_clear_redir_ga(void)
+{
+ char_u *res;
+
+ ga_append(&redir_ga, NUL); // Append the trailing NUL.
+ res = redir_ga.ga_data;
+ redir_ga.ga_data = NULL;
+ return res;
+}
+
+/*
* "gettabvar()" function
*/
void
diff --git a/src/proto/evalvars.pro b/src/proto/evalvars.pro
index c75f80a863..58c1222956 100644
--- a/src/proto/evalvars.pro
+++ b/src/proto/evalvars.pro
@@ -71,7 +71,7 @@ void vars_clear(hashtab_T *ht);
void vars_clear_ext(hashtab_T *ht, int free_val);
void delete_var(hashtab_T *ht, hashitem_T *hi);
void set_var(char_u *name, typval_T *tv, int copy);
-void set_var_const(char_u *name, type_T *type, typval_T *tv_arg, int copy, int flags, int var_idx);
+void set_var_const(char_u *name, type_T *type, typval_T *tv_arg, int copy, int flags_arg, int var_idx);
int var_check_permission(dictitem_T *di, char_u *name);
int var_check_ro(int flags, char_u *name, int use_gettext);
int var_check_lock(int flags, char_u *name, int use_gettext);
@@ -82,9 +82,13 @@ int valid_varname(char_u *varname, int autoload);
void reset_v_option_vars(void);
void assert_error(garray_T *gap);
int var_exists(char_u *var);
+int alloc_redir_lval(void);
+void clear_redir_lval(void);
+void init_redir_ga(void);
int var_redir_start(char_u *name, int append);
void var_redir_str(char_u *value, int value_len);
void var_redir_stop(void);
+char_u *get_clear_redir_ga(void);
void f_gettabvar(typval_T *argvars, typval_T *rettv);
void f_gettabwinvar(typval_T *argvars, typval_T *rettv);
void f_getwinvar(typval_T *argvars, typval_T *rettv);
diff --git a/src/testdir/test_vim9_cmd.vim b/src/testdir/test_vim9_cmd.vim
index 15c9a59870..be33f74326 100644
--- a/src/testdir/test_vim9_cmd.vim
+++ b/src/testdir/test_vim9_cmd.vim
@@ -1194,5 +1194,23 @@ def Test_substitute_expr()
bwipe!
enddef
+def Test_redir_to_var()
+ var result: string
+ redir => result
+ echo 'something'
+ redir END
+ assert_equal("\nsomething", result)
+
+ redir =>> result
+ echo 'more'
+ redir END
+ assert_equal("\nsomething\nmore", result)
+
+ var lines =<< trim END
+ redir => notexist
+ END
+ CheckDefFailure(lines, 'E1089:')
+enddef
+
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim
index 8a96bf2732..bf88d9396a 100644
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -140,6 +140,33 @@ def Test_disassemble_substitute()
res)
enddef
+def s:RedirVar()
+ var result: string
+ redir =>> result
+ echo "text"
+ redir END
+enddef
+
+def Test_disassemble_redir_var()
+ var res = execute('disass s:RedirVar')
+ assert_match('<SNR>\d*_RedirVar.*' ..
+ ' var result: string\_s*' ..
+ '\d PUSHS "\[NULL\]"\_s*' ..
+ '\d STORE $0\_s*' ..
+ ' redir =>> result\_s*' ..
+ '\d REDIR\_s*' ..
+ ' echo "text"\_s*' ..
+ '\d PUSHS "text"\_s*' ..
+ '\d ECHO 1\_s*' ..
+ ' redir END\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d REDIR END\_s*' ..
+ '\d CONCAT\_s*' ..
+ '\d STORE $0\_s*' ..
+ '\d RETURN 0',
+ res)
+enddef
+
def s:YankRange()
norm! m[jjm]
:'[,']yank
diff --git a/src/version.c b/src/version.c
index a43a2f432d..22a2751bd3 100644
--- 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 */
/**/
+ 2785,
+/**/
2784,
/**/
2783,
diff --git a/src/vim9.h b/src/vim9.h
index 52b7c6dbff..bbed384c23 100644
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -169,6 +169,9 @@ typedef enum {
ISN_SHUFFLE, // move item on stack up or down
ISN_DROP, // pop stack and discard value
+ ISN_REDIRSTART, // :redir =>
+ ISN_REDIREND, // :redir END, isn_arg.number == 1 for append
+
ISN_FINISH // end marker in list of instructions
} isntype_T;
diff --git a/src/vim9compile.c b/src/vim9compile.c
index 71a8831623..027f79d95c 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -114,6 +114,52 @@ typedef struct {
int lv_arg; // when TRUE this is an argument
} lvar_T;
+// Destination for an assignment or ":unlet" with an index.
+typedef enum {
+ dest_local,
+ dest_option,
+ dest_env,
+ dest_global,
+ dest_buffer,
+ dest_window,
+ dest_tab,
+ dest_vimvar,
+ dest_script,
+ dest_reg,
+ dest_expr,
+} assign_dest_T;
+
+// Used by compile_lhs() to store information about the LHS of an assignment
+// and one argument of ":unlet" with an index.
+typedef struct {
+ assign_dest_T lhs_dest; // type of destination
+
+ char_u *lhs_name; // allocated name including
+ // "[expr]" or ".name".
+ size_t lhs_varlen; // length of the variable without
+ // "[expr]" or ".name"
+ char_u *lhs_dest_end; // end of the destination, including
+ // "[expr]" or ".name".
+
+ int lhs_has_index; // has "[expr]" or ".name"
+
+ int lhs_new_local; // create new local variable
+ int lhs_opt_flags; // for when destination is an option
+ int lhs_vimvaridx; // for when destination is a v:var
+
+ lvar_T lhs_local_lvar; // used for existing local destination
+ lvar_T lhs_arg_lvar; // used for argument destination
+ lvar_T *lhs_lvar; // points to destination lvar
+ int lhs_scriptvar_sid;
+ int lhs_scriptvar_idx;
+
+ int lhs_has_type; // type was specified
+ type_T *lhs_type;
+ type_T *lhs_member_type;
+
+ int lhs_append; // used by ISN_REDIREND
+} lhs_T;
+
/*
* Context for compiling lines of Vim script.
* Stores info about the local variables and condition stack.
@@ -146,6 +192,9 @@ struct cctx_S {
garray_T *ctx_type_list; // list of pointers to allocated types
int ctx_has_cmdmod; // ISN_CMDMOD was generated
+
+ lhs_T ctx_redir_lhs; // LHS for ":redir => var", valid when
+ // lhs_name is not NULL
};
static void delete_def_function_contents(dfunc_T *dfunc, int mark_deleted);
@@ -5460,50 +5509,6 @@ static char *reserved[] = {
NULL
};
-// Destination for an assignment or ":unlet" with an index.
-typedef enum {
- dest_local,
- dest_option,
- dest_env,
- dest_global,
- dest_buffer,
- dest_window,
- dest_tab,
- dest_vimvar,
- dest_script,
- dest_reg,
- dest_expr,
-} assign_dest_T;
-
-// Used by compile_lhs() to store information about the LHS of an assignment
-// and one argument of ":unlet" with an index.
-typedef struct {
- assign_dest_T lhs_dest; // type of destination
-
- char_u *lhs_name; // allocated name including
- // "[expr]" or ".name".
- size_t lhs_varlen; // length of the variable without
- // "[expr]" or ".name"
- char_u *lhs_dest_end; // end of the destination, including
- // "[expr]" or ".name".
-
- int lhs_has_index; // has "[expr]" or ".name"
-
- int lhs_new_local; // create new local variable
- int lhs_opt_flags; // for when destination is an option
- int lhs_vimvaridx; // for when destination is a v:var
-
- lvar_T lhs_local_lvar; // used for existing local destination
- lvar_T lhs_arg_lvar; // used for argument destination
- lvar_T *lhs_lvar; // points to destination lvar
- int lhs_scriptvar_sid;
- int lhs_scriptvar_idx;
-
- int lhs_has_type; // type was specified
- type_T *lhs_type;
- type_T *lhs_member_type;
-} lhs_T;
-
/*
* Generate the load instruction for "name".
*/
@@ -5779,6 +5784,44 @@ generate_store_var(
}
static int
+generate_store_lhs(cctx_T *cctx, lhs_T *lhs, int instr_count)
+{
+ if (lhs->lhs_dest != dest_local)
+ return generate_store_var(cctx, lhs->lhs_dest,
+ lhs->lhs_opt_flags, lhs->lhs_vimvaridx,
+ lhs->lhs_scriptvar_idx, lhs->lhs_scriptvar_sid,
+ lhs->lhs_type, lhs->lhs_name);
+
+ if (lhs->lhs_lvar != NULL)
+ {
+ garray_T *instr = &cctx->ctx_instr;
+ isn_T *isn = ((isn_T *)instr->ga_data) + instr->ga_len - 1;
+
+ // optimization: turn "var = 123" from ISN_PUSHNR + ISN_STORE into
+ // ISN_STORENR
+ if (lhs->lhs_lvar->lv_from_outer == 0
+ && instr->ga_len == instr_count + 1
+ && isn->isn_type == ISN_PUSHNR)
+ {
+ varnumber_T val = isn->isn_arg.number;
+ garray_T *stack = &cctx->ctx_type_stack;
+
+ isn->isn_type = ISN_STORENR;
+ isn->isn_arg.storenr.stnr_idx = lhs->lhs_lvar->lv_idx;
+ isn->isn_arg.storenr.stnr_val = val;
+ if (stack->ga_len > 0)
+ --stack->ga_len;
+ }
+ else if (lhs->lhs_lvar->lv_from_outer > 0)
+ generate_STOREOUTER(cctx, lhs->lhs_lvar->lv_idx,
+ lhs->lhs_lvar->lv_from_outer);
+ else
+ generate_STORE(cctx, ISN_STORE, lhs->lhs_lvar->lv_idx, NULL);
+ }
+ return OK;
+}
+
+ static int
is_decl_command(int cmdidx)
{
return cmdidx == CMD_let || cmdidx == CMD_var
@@ -6084,6 +6127,36 @@ compile_lhs(
}
/*
+ * Figure out the LHS and check a few errors.
+ */
+ static int
+compile_assign_lhs(
+ char_u *var_start,
+ lhs_T *lhs,
+ int cmdidx,
+ int is_decl,
+ int heredoc,
+ int oplen,
+ cctx_T *cctx)
+{
+ if (compile_lhs(var_start, lhs, cmdidx, heredoc, oplen, cctx) == FAIL)
+ return FAIL;
+
+ if (!lhs->lhs_has_index && lhs->lhs_lvar == &lhs->lhs_arg_lvar)
+ {
+ semsg(_(e_cannot_assign_to_argument), lhs->lhs_name);
+ return FAIL;
+ }
+ if (!is_decl && lhs->lhs_lvar != NULL
+ && lhs->lhs_lvar->lv_const && !lhs->lhs_has_index)
+ {
+ semsg(_(e_cannot_assign_to_constant), lhs->lhs_name);
+ return FAIL;
+ }
+ return OK;
+}
+
+/*
* For an assignment with an index, compile the "idx" in "var[idx]" or "key" in
* "var.key".
*/
@@ -6454,21 +6527,9 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
/*
* Figure out the LHS type and other properties.
*/
- if (compile_lhs(var_start, &lhs, cmdidx, heredoc, oplen, cctx) == FAIL)
- goto theend;
-
- if (!lhs.lhs_has_index && lhs.lhs_lvar == &lhs.lhs_arg_lvar)
- {
- semsg(_(e_cannot_assign_to_argument), lhs.lhs_name);
+ if (compile_assign_lhs(var_start, &lhs, cmdidx,
+ is_decl, heredoc, oplen, cctx) == FAIL)
goto theend;
- }
- if (!is_decl && lhs.lhs_lvar != NULL
- && lhs.lhs_lvar->lv_const && !lhs.lhs_has_index)
- {
- semsg(_(e_cannot_assign_to_constant), lhs.lhs_name);
- goto theend;
- }
-
if (!heredoc)
{
if (cctx->ctx_skip == SKIP_YES)
@@ -6728,39 +6789,8 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
// also in legacy script.
generate_SETTYPE(cctx, lhs.lhs_type);
- if (lhs.lhs_dest != dest_local)
- {
- if (generate_store_var(cctx, lhs.lhs_dest,
- lhs.lhs_opt_flags, lhs.lhs_vimvaridx,
- lhs.lhs_scriptvar_idx, lhs.lhs_scriptvar_sid,
- lhs.lhs_type, lhs.lhs_name) == FAIL)
- goto theend;
- }
- else if (lhs.lhs_lvar != NULL)
- {
- isn_T *isn = ((isn_T *)instr->ga_data)
- + instr->ga_len - 1;
-
- // optimization: turn "var = 123" from ISN_PUSHNR +
- // ISN_STORE into ISN_STORENR
- if (lhs.lhs_lvar->lv_from_outer == 0
- && instr->ga_len == instr_count + 1
- && isn->isn_type == ISN_PUSHNR)
- {
- varnumber_T val = isn->isn_arg.number;
-
- isn->isn_type = ISN_STORENR;
- isn->isn_arg.storenr.stnr_idx = lhs.lhs_lvar->lv_idx;
- isn->isn_arg.storenr.stnr_val = val;
- if (stack->ga_len > 0)
- --stack->ga_len;
- }
- else if (lhs.lhs_lvar->lv_from_outer > 0)
- generate_STOREOUTER(cctx, lhs.lhs_lvar->lv_idx,
- lhs.lhs_lvar->lv_from_outer);
- else
- generate_STORE(cctx, ISN_STORE, lhs.lhs_lvar->lv_idx, NULL);
- }
+ if (generate_store_lhs(cctx, &lhs, instr_count) == FAIL)
+ goto theend;
}
if (var_idx + 1 < var_count)
@@ -8541,6 +8571,67 @@ compile_substitute(char_u *arg, exarg_T *eap, cctx_T *cctx)
return compile_exec(arg, eap, cctx);
}
+ static char_u *
+compile_redir(char_u *line, exarg_T *eap, cctx_T *cctx)
+{
+ char_u *arg = eap->arg;
+
+ if (cctx->ctx_redir_lhs.lhs_name != NULL)
+ {
+ if (STRNCMP(arg, "END", 3) == 0)
+ {
+ if (cctx->ctx_redir_lhs.lhs_append)
+ {
+ if (compile_load_lhs(&cctx->ctx_redir_lhs,
+ cctx->ctx_redir_lhs.lhs_name, NULL, cctx) == FAIL)
+ return NULL;
+ if (cctx->ctx_redir_lhs.lhs_has_index)
+ emsg("redir with index not implemented yet");
+ }
+
+ // Gets the redirected text and put it on the stack, then store it
+ // in the variable.
+ generate_instr_type(cctx, ISN_REDIREND, &t_string);
+
+ if (cctx->ctx_redir_lhs.lhs_append)
+ generate_instr_drop(cctx, ISN_CONCAT, 1);
+
+ if (generate_store_lhs(cctx, &cctx->ctx_redir_lhs, -1) == FAIL)
+ return NULL;
+
+ VIM_CLEAR(cctx->ctx_redir_lhs.lhs_name);
+ return arg + 3;
+ }
+ emsg(_(e_cannot_nest_redir));
+ return NULL;
+ }
+
+ if (arg[0] == '=' && arg[1] == '>')
+ {
+ int append = FALSE;
+
+ // redirect to a variable is compiled
+ arg += 2;
+ if (*arg == '>')
+ {
+ ++arg;
+ append = TRUE;
+ }
+ arg = skipwhite(arg);
+
+ if (compile_assign_lhs(arg, &cctx->ctx_redir_lhs, CMD_redir,
+ FALSE, FALSE, 1, cctx) == FAIL)
+ return NULL;
+ generate_instr(cctx, ISN_REDIRSTART);
+ cctx->ctx_redir_lhs.lhs_append = append;
+
+ return arg + cctx->ctx_redir_lhs.lhs_varlen;
+ }
+
+ // other redirects are handled like at script level
+ return compile_exec(line, eap, cctx);
+}
+
/*
* Add a function to the list of :def functions.
* This sets "ufunc->uf_dfunc_idx" but the function isn't compiled yet.
@@ -9082,6 +9173,11 @@ compile_def_function(
}
break;
+ case CMD_redir:
+ ea.arg = p;
+ line = compile_redir(line, &ea, &cctx);
+ break;
+
// TODO: any other commands with an expression argument?
case CMD_append:
@@ -9217,6 +9313,16 @@ erret:
emsg(_(e_compiling_def_function_failed));
}
+ if (cctx.ctx_redir_lhs.lhs_name != NULL)
+ {
+ if (ret == OK)
+ {
+ emsg(_(e_missing_redir_end));
+ ret = FAIL;
+ }
+ vim_free(cctx.ctx_redir_lhs.lhs_name);
+ }
+
current_sctx = save_current_sctx;
estack_compiling = save_estack_compiling;
if (do_estack_push)
@@ -9463,6 +9569,7 @@ delete_instr(isn_T *isn)
case ISN_NEWLIST:
case ISN_OPANY:
case ISN_OPFLOAT:
+ case ISN_FINISH:
case ISN_OPNR:
case ISN_PCALL:
case ISN_PCALL_END:
@@ -9473,15 +9580,17 @@ delete_instr(isn_T *isn)
case ISN_PUSHNR:
case ISN_PUSHSPEC:
case ISN_PUT:
+ case ISN_REDIREND:
+ case ISN_REDIRSTART:
case ISN_RETURN:
case ISN_RETURN_ZERO:
case ISN_SHUFFLE:
case ISN_SLICE:
case ISN_STORE:
case ISN_STOREINDEX:
- case ISN_STORERANGE:
case ISN_STORENR:
case ISN_STOREOUTER:
+ case ISN_STORERANGE:
case ISN_STOREREG:
case ISN_STOREV:
case ISN_STRINDEX:
@@ -9491,7 +9600,6 @@ delete_instr(isn_T *isn)
case ISN_UNLETINDEX:
case ISN_UNLETRANGE:
case ISN_UNPACK:
- case ISN_FINISH:
// nothing allocated
break;
}
diff --git a/src/vim9execute.c b/src/vim9execute.c
index 6a11e8a6bc..f471454f80 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -1409,6 +1409,37 @@ exec_instructions(ectx_T *ectx)
case ISN_FINISH:
goto done;
+ case ISN_REDIRSTART:
+ // create a dummy entry for var_redir_str()
+ if (alloc_redir_lval() == FAIL)
+ goto on_error;
+
+ // The output is stored in growarray "redir_ga" until
+ // redirection ends.
+ init_redir_ga();
+ redir_vname = 1;
+ break;
+
+ case ISN_REDIREND:
+ {
+ char_u *res = get_clear_redir_ga();
+
+ // End redirection, put redirected text on the stack.
+ clear_redir_lval();
+ redir_vname = 0;
+
+ if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
+ {
+ vim_free(res);
+ return FAIL;
+ }
+ tv = STACK_TV_BOT(0);
+ tv->v_type = VAR_STRING;
+ tv->vval.v_string = res;
+ ++ectx->ec_stack.ga_len;
+ }
+ break;
+
// execute Ex command from pieces on the stack
case ISN_EXECCONCAT:
{
@@ -4332,6 +4363,13 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc)
case ISN_EXEC:
smsg("%s%4d EXEC %s", pfx, current, iptr->isn_arg.string);
break;
+ case ISN_REDIRSTART:
+ smsg("%s%4d REDIR", pfx, current);
+ break;
+ case ISN_REDIREND:
+ smsg("%s%4d REDIR END%s", pfx, current,
+ iptr->isn_arg.number ? " append" : "");
+ break;
case ISN_SUBSTITUTE:
{
subs_T *subs = &iptr->isn_arg.subs;