]> granicus.if.org Git - vim/commitdiff
patch 8.2.2785: Vim9: cannot redirect to local variable v8.2.2785
authorBram Moolenaar <Bram@vim.org>
Mon, 19 Apr 2021 18:50:03 +0000 (20:50 +0200)
committerBram Moolenaar <Bram@vim.org>
Mon, 19 Apr 2021 18:50:03 +0000 (20:50 +0200)
Problem:    Vim9: cannot redirect to local variable.
Solution:   Compile :redir when redirecting to a variable.

src/errors.h
src/evalvars.c
src/proto/evalvars.pro
src/testdir/test_vim9_cmd.vim
src/testdir/test_vim9_disassemble.vim
src/version.c
src/vim9.h
src/vim9compile.c
src/vim9execute.c

index 1e091867f211bd00495d145701213334a5ab68c7..0781ca150f655a71f4f60f4e9587ab22416ce829 100644 (file)
@@ -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"));
index 9952ffcadfe9b0e392e054957d8a9cb37604147a..1b0b7d0c98c48083f35ce944de4f383c92f11c8e 100644 (file)
@@ -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,
@@ -3921,6 +3941,20 @@ var_redir_stop(void)
     VIM_CLEAR(redir_varname);
 }
 
+/*
+ * 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
  */
index c75f80a86378b0f4c7c862efbc02675a92440889..58c12229569e8e1fcb3a889835498e87a5a3bf29 100644 (file)
@@ -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);
index 15c9a59870f67fb447c32edf256401139b2d9d4e..be33f74326c87e9f6f77b62d8e61c5656f9a4216 100644 (file)
@@ -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
index 8a96bf27326e9c8316ce6fbc829c2bf97d9a729d..bf88d9396a6da1bf425ecbcc33d0ac95e43303e6 100644 (file)
@@ -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
index a43a2f432d03e4aba72f269ec602fcea08323c46..22a2751bd3802738f863a718238b223f884b6a62 100644 (file)
@@ -750,6 +750,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    2785,
 /**/
     2784,
 /**/
index 52b7c6dbff8aef483bf8ec707aff1a0122a66f2d..bbed384c23f06c538689219d14ba8589771d222e 100644 (file)
@@ -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;
 
index 71a8831623d934e3c5e61af07740a024453bbfeb..027f79d95cf775efcfcf61fc750db770f05b7256 100644 (file)
@@ -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".
  */
@@ -5778,6 +5783,44 @@ generate_store_var(
     return FAIL;
 }
 
+    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)
 {
@@ -6083,6 +6126,36 @@ compile_lhs(
     return OK;
 }
 
+/*
+ * 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;
     }
index 6a11e8a6bc9292e859cf0543a84fc5a21dd9f1e1..f471454f802df835e3bfe2d72013d9a77777f036 100644 (file)
@@ -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;