To: vim_dev@googlegroups.com Subject: Patch 8.2.2785 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.2785 Problem: Vim9: cannot redirect to local variable. Solution: Compile :redir when redirecting to a variable. Files: src/vim9compile.c, src/vim9.h, src/vim9execute.c, src/errors.h, src/evalvars.c, src/proto/evalvars.pro, src/testdir/test_vim9_cmd.vim, src/testdir/test_vim9_disassemble.vim *** ../vim-8.2.2784/src/vim9compile.c 2021-04-19 16:48:44.431055514 +0200 --- src/vim9compile.c 2021-04-19 20:32:24.923225173 +0200 *************** *** 114,119 **** --- 114,165 ---- 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,151 **** --- 192,200 ---- 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,5509 **** 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". */ --- 5509,5514 ---- *************** *** 5779,5784 **** --- 5784,5827 ---- } 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,6089 **** --- 6127,6162 ---- } /* + * 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,6474 **** /* * 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); 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) --- 6527,6535 ---- /* * Figure out the LHS type and other properties. */ ! if (compile_assign_lhs(var_start, &lhs, cmdidx, ! is_decl, heredoc, oplen, cctx) == FAIL) goto theend; if (!heredoc) { if (cctx->ctx_skip == SKIP_YES) *************** *** 6728,6766 **** // 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 (var_idx + 1 < var_count) --- 6789,6796 ---- // also in legacy script. generate_SETTYPE(cctx, lhs.lhs_type); ! if (generate_store_lhs(cctx, &lhs, instr_count) == FAIL) ! goto theend; } if (var_idx + 1 < var_count) *************** *** 8541,8546 **** --- 8571,8637 ---- 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,9087 **** --- 9173,9183 ---- } 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,9222 **** --- 9313,9328 ---- 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,9468 **** --- 9569,9575 ---- case ISN_NEWLIST: case ISN_OPANY: case ISN_OPFLOAT: + case ISN_FINISH: case ISN_OPNR: case ISN_PCALL: case ISN_PCALL_END: *************** *** 9473,9487 **** case ISN_PUSHNR: case ISN_PUSHSPEC: case ISN_PUT: 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_STOREREG: case ISN_STOREV: case ISN_STRINDEX: --- 9580,9596 ---- 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_STORENR: case ISN_STOREOUTER: + case ISN_STORERANGE: case ISN_STOREREG: case ISN_STOREV: case ISN_STRINDEX: *************** *** 9491,9497 **** case ISN_UNLETINDEX: case ISN_UNLETRANGE: case ISN_UNPACK: - case ISN_FINISH: // nothing allocated break; } --- 9600,9605 ---- *** ../vim-8.2.2784/src/vim9.h 2021-04-19 16:48:44.431055514 +0200 --- src/vim9.h 2021-04-19 19:28:26.350280274 +0200 *************** *** 169,174 **** --- 169,177 ---- 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; *** ../vim-8.2.2784/src/vim9execute.c 2021-04-19 16:48:44.431055514 +0200 --- src/vim9execute.c 2021-04-19 20:36:23.462524628 +0200 *************** *** 1409,1414 **** --- 1409,1445 ---- 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,4337 **** --- 4363,4375 ---- 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; *** ../vim-8.2.2784/src/errors.h 2021-04-17 20:44:52.442520718 +0200 --- src/errors.h 2021-04-19 19:02:39.483219835 +0200 *************** *** 405,407 **** --- 405,411 ---- 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")); *** ../vim-8.2.2784/src/evalvars.c 2021-04-18 14:12:27.707697058 +0200 --- src/evalvars.c 2021-04-19 20:17:19.809869998 +0200 *************** *** 3778,3783 **** --- 3778,3804 ---- 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,3815 **** if (redir_varname == NULL) return FAIL; ! redir_lval = ALLOC_CLEAR_ONE(lval_T); ! if (redir_lval == NULL) { var_redir_stop(); return FAIL; } // The output is stored in growarray "redir_ga" until redirection ends. ! ga_init2(&redir_ga, (int)sizeof(char), 500); // Parse the variable name (can be a dict or list entry). redir_endp = get_lval(redir_varname, NULL, redir_lval, FALSE, FALSE, 0, --- 3822,3835 ---- if (redir_varname == NULL) return FAIL; ! if (alloc_redir_lval() == FAIL) { var_redir_stop(); return FAIL; } // The output is stored in growarray "redir_ga" until redirection ends. ! 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,3927 **** --- 3942,3961 ---- } /* + * 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 *** ../vim-8.2.2784/src/proto/evalvars.pro 2021-03-14 13:21:31.785065163 +0100 --- src/proto/evalvars.pro 2021-04-19 20:17:28.213845638 +0200 *************** *** 71,77 **** 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); 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); --- 71,77 ---- 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_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,90 **** --- 82,94 ---- 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); *** ../vim-8.2.2784/src/testdir/test_vim9_cmd.vim 2021-04-19 16:48:44.435055499 +0200 --- src/testdir/test_vim9_cmd.vim 2021-04-19 20:42:21.589760771 +0200 *************** *** 1194,1198 **** --- 1194,1216 ---- 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 *** ../vim-8.2.2784/src/testdir/test_vim9_disassemble.vim 2021-04-19 16:48:44.435055499 +0200 --- src/testdir/test_vim9_disassemble.vim 2021-04-19 20:48:19.353076858 +0200 *************** *** 140,145 **** --- 140,172 ---- 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('\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 *** ../vim-8.2.2784/src/version.c 2021-04-19 16:48:44.435055499 +0200 --- src/version.c 2021-04-19 20:48:56.388995817 +0200 *************** *** 752,753 **** --- 752,755 ---- { /* Add new patch number below this line */ + /**/ + 2785, /**/ -- "Microsoft is like Coke. It's a secret formula, all the money is from distribution, and their goal is to get Coke everywhere. Open source is like selling water. There are water companies like Perrier and Poland Spring, but you're competing with something that's free." -- Carl Howe /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///