# HG changeset patch # User Bram Moolenaar # Date 1589130903 -7200 # Node ID e29b2ec8d4d2252c7200360fd7da545c4432e8bd # Parent 25253a43acdfe475f48734f56268a531039474fb patch 8.2.0730: Vim9: Assignment to dict member does not work Commit: https://github.com/vim/vim/commit/1cc2a94f80ba982f83d1e2d37c4726867e36bd9f Author: Bram Moolenaar Date: Sun May 10 19:10:31 2020 +0200 patch 8.2.0730: Vim9: Assignment to dict member does not work Problem: Vim9: Assignment to dict member does not work. Solution: Parse dict assignment. Implement getting dict member. diff --git a/src/globals.h b/src/globals.h --- a/src/globals.h +++ b/src/globals.h @@ -1673,6 +1673,7 @@ EXTERN char e_toofewarg[] INIT(= N_("E11 EXTERN char e_func_deleted[] INIT(= N_("E933: Function was deleted: %s")); EXTERN char e_dictkey[] INIT(= N_("E716: Key not present in Dictionary: %s")); EXTERN char e_listreq[] INIT(= N_("E714: List required")); +EXTERN char e_listdictblobreq[] INIT(= N_("E1090: List, Dict or Blob required")); EXTERN char e_listblobreq[] INIT(= N_("E897: List or Blob required")); EXTERN char e_list_end[] INIT(= N_("E697: Missing end of List ']': %s")); EXTERN char e_listdictarg[] INIT(= N_("E712: Argument of %s must be a List or Dictionary")); diff --git a/src/testdir/test_vim9_cmd.vim b/src/testdir/test_vim9_cmd.vim --- a/src/testdir/test_vim9_cmd.vim +++ b/src/testdir/test_vim9_cmd.vim @@ -44,5 +44,27 @@ def Test_syn_include_wildcards() delete('Xthemine.vim') enddef +def Test_assign_list() + let l: list = [] + l[0] = 'value' + assert_equal('value', l[0]) + + l[1] = 'asdf' + assert_equal('value', l[0]) + assert_equal('asdf', l[1]) + assert_equal('asdf', l[-1]) + assert_equal('value', l[-2]) +enddef + +def Test_assign_dict() + let d: dict = {} + d['key'] = 'value' + assert_equal('value', d['key']) + + d[123] = 'qwerty' + assert_equal('qwerty', d[123]) + assert_equal('qwerty', d['123']) +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim --- a/src/testdir/test_vim9_expr.vim +++ b/src/testdir/test_vim9_expr.vim @@ -644,9 +644,9 @@ func Test_expr6_fails() call CheckDefFailure(["let x = #{one: 1} / #{two: 2}"], 'E1036:') call CheckDefFailure(["let x = #{one: 1} % #{two: 2}"], 'E1035:') - call CheckDefFailure(["let x = 0xff[1]"], 'E714:') + call CheckDefFailure(["let x = 0xff[1]"], 'E1090:') if has('float') - call CheckDefFailure(["let x = 0.7[1]"], 'E714:') + call CheckDefFailure(["let x = 0.7[1]"], 'E1090:') endif endfunc diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim --- a/src/testdir/test_vim9_script.vim +++ b/src/testdir/test_vim9_script.vim @@ -38,7 +38,7 @@ def Test_assignment() call CheckDefFailure(['let x:string'], 'E1069:') call CheckDefFailure(['let x:string = "x"'], 'E1069:') - call CheckDefFailure(['let a:string = "x"'], 'E1082:') + call CheckDefFailure(['let a:string = "x"'], 'E1069:') let a: number = 6 assert_equal(6, a) diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -747,6 +747,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 730, +/**/ 729, /**/ 728, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -48,6 +48,8 @@ typedef enum { // ISN_STOREOTHER, // pop into other script variable isn_arg.other. ISN_STORENR, // store number into local variable isn_arg.storenr.stnr_idx + ISN_STORELIST, // store into list, value/index/varable on stack + ISN_STOREDICT, // store into dictionary, value/index/variable on stack ISN_UNLET, // unlet variable isn_arg.unlet.ul_name ISN_UNLETENV, // unlet environment variable isn_arg.unlet.ul_name @@ -110,7 +112,8 @@ typedef enum { // expression operations ISN_CONCAT, ISN_INDEX, // [expr] list index - ISN_MEMBER, // dict.member using isn_arg.string + ISN_MEMBER, // dict[member] + ISN_STRINGMEMBER, // dict.member using isn_arg.string ISN_2BOOL, // convert value to bool, invert if isn_arg.number != 0 ISN_2STRING, // convert value to string at isn_arg.number on stack ISN_NEGATENR, // apply "-" to number diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -1558,17 +1558,17 @@ generate_PCALL( } /* - * Generate an ISN_MEMBER instruction. + * Generate an ISN_STRINGMEMBER instruction. */ static int -generate_MEMBER(cctx_T *cctx, char_u *name, size_t len) +generate_STRINGMEMBER(cctx_T *cctx, char_u *name, size_t len) { isn_T *isn; garray_T *stack = &cctx->ctx_type_stack; type_T *type; RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_MEMBER)) == NULL) + if ((isn = generate_instr(cctx, ISN_STRINGMEMBER)) == NULL) return FAIL; isn->isn_arg.string = vim_strnsave(name, (int)len); @@ -3485,15 +3485,17 @@ compile_subscript( } else if (**arg == '[') { - garray_T *stack; + garray_T *stack = &cctx->ctx_type_stack; type_T **typep; + // list index: list[123] + // list member: dict[key] + // TODO: blob index + // TODO: more arguments + // TODO: recognize list or dict at runtime if (generate_ppconst(cctx, ppconst) == FAIL) return FAIL; - // list index: list[123] - // TODO: more arguments - // TODO: dict member dict['name'] *arg = skipwhite(*arg + 1); if (compile_expr0(arg, cctx) == FAIL) return FAIL; @@ -3505,17 +3507,27 @@ compile_subscript( } *arg = *arg + 1; - if (generate_instr_drop(cctx, ISN_INDEX, 1) == FAIL) - return FAIL; - stack = &cctx->ctx_type_stack; - typep = ((type_T **)stack->ga_data) + stack->ga_len - 1; - if ((*typep)->tt_type != VAR_LIST && *typep != &t_any) + typep = ((type_T **)stack->ga_data) + stack->ga_len - 2; + if ((*typep)->tt_type == VAR_LIST || (*typep) == &t_any) + { + if ((*typep)->tt_type == VAR_LIST) + *typep = (*typep)->tt_member; + if (generate_instr_drop(cctx, ISN_INDEX, 1) == FAIL) + return FAIL; + } + else if ((*typep)->tt_type == VAR_DICT) { - emsg(_(e_listreq)); + *typep = (*typep)->tt_member; + if (may_generate_2STRING(-1, cctx) == FAIL) + return FAIL; + if (generate_instr_drop(cctx, ISN_MEMBER, 1) == FAIL) + return FAIL; + } + else + { + emsg(_(e_listdictblobreq)); return FAIL; } - if ((*typep)->tt_type == VAR_LIST) - *typep = (*typep)->tt_member; } else if (**arg == '.' && (*arg)[1] != '.') { @@ -3535,7 +3547,7 @@ compile_subscript( semsg(_(e_syntax_at), *arg); return FAIL; } - if (generate_MEMBER(cctx, *arg, p - *arg) == FAIL) + if (generate_STRINGMEMBER(cctx, *arg, p - *arg) == FAIL) return FAIL; *arg = p; } @@ -4501,18 +4513,74 @@ typedef enum { } assign_dest_T; /* + * Generate the load instruction for "name". + */ + static void +generate_loadvar( + cctx_T *cctx, + assign_dest_T dest, + char_u *name, + lvar_T *lvar, + type_T *type) +{ + switch (dest) + { + case dest_option: + // TODO: check the option exists + generate_LOAD(cctx, ISN_LOADOPT, 0, name, type); + break; + case dest_global: + generate_LOAD(cctx, ISN_LOADG, 0, name + 2, type); + break; + case dest_buffer: + generate_LOAD(cctx, ISN_LOADB, 0, name + 2, type); + break; + case dest_window: + generate_LOAD(cctx, ISN_LOADW, 0, name + 2, type); + break; + case dest_tab: + generate_LOAD(cctx, ISN_LOADT, 0, name + 2, type); + break; + case dest_script: + compile_load_scriptvar(cctx, + name + (name[1] == ':' ? 2 : 0), NULL, NULL, TRUE); + break; + case dest_env: + // Include $ in the name here + generate_LOAD(cctx, ISN_LOADENV, 0, name, type); + break; + case dest_reg: + generate_LOAD(cctx, ISN_LOADREG, name[1], NULL, &t_string); + break; + case dest_vimvar: + generate_LOADV(cctx, name + 2, TRUE); + break; + case dest_local: + if (lvar->lv_from_outer) + generate_LOAD(cctx, ISN_LOADOUTER, lvar->lv_idx, + NULL, type); + else + generate_LOAD(cctx, ISN_LOAD, lvar->lv_idx, NULL, type); + break; + } +} + +/* * compile "let var [= expr]", "const var = expr" and "var = expr" * "arg" points to "var". */ static char_u * compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx) { + char_u *var_end; char_u *p; + char_u *end = arg; char_u *ret = NULL; int var_count = 0; int semicolon = 0; size_t varlen; garray_T *instr = &cctx->ctx_instr; + garray_T *stack = &cctx->ctx_type_stack; int new_local = FALSE; char_u *op; int opt_type; @@ -4522,15 +4590,17 @@ compile_assignment(char_u *arg, exarg_T int oplen = 0; int heredoc = FALSE; type_T *type = &t_any; + type_T *member_type = &t_any; lvar_T *lvar = NULL; char_u *name; char_u *sp; int has_type = FALSE; + int has_index = FALSE; int is_decl = cmdidx == CMD_let || cmdidx == CMD_const; int instr_count = -1; - p = skip_var_list(arg, FALSE, &var_count, &semicolon); - if (p == NULL) + var_end = skip_var_list(arg, FALSE, &var_count, &semicolon); + if (var_end == NULL) return NULL; if (var_count > 0) { @@ -4539,7 +4609,12 @@ compile_assignment(char_u *arg, exarg_T return NULL; } + p = (*arg == '&' || *arg == '$' || *arg == '@') ? arg + 1 : arg; + p = to_name_end(p, TRUE); + // "a: type" is declaring variable "a" with a type, not "a:". + if (is_decl && var_end == arg + 2 && var_end[-1] == ':') + --var_end; if (is_decl && p == arg + 2 && p[-1] == ':') --p; @@ -4715,9 +4790,18 @@ compile_assignment(char_u *arg, exarg_T semsg(_("E1082: Cannot use a namespaced variable: %s"), name); goto theend; } + else if (!is_decl) + { + semsg(_("E1089: unknown variable: %s"), name); + goto theend; + } } } + // handle "a:name" as a name, not index "name" on "a" + if (varlen > 1 || arg[varlen] != ':') + p = var_end; + if (dest != dest_option) { if (is_decl && *p == ':') @@ -4774,6 +4858,33 @@ compile_assignment(char_u *arg, exarg_T new_local = TRUE; } + member_type = type; + if (var_end > arg + varlen) + { + if (is_decl) + { + emsg(_("E1087: cannot use an index when declaring a variable")); + goto theend; + } + + // Something follows after the variable: "var[idx]". + if (arg[varlen] == '[') + { + has_index = TRUE; + if (type->tt_member == NULL) + { + semsg(_("E1088: cannot use an index on %s"), name); + goto theend; + } + member_type = type->tt_member; + } + else + { + semsg("Not supported yet: %s", arg); + goto theend; + } + } + if (heredoc) { list_T *l; @@ -4792,57 +4903,24 @@ compile_assignment(char_u *arg, exarg_T } generate_NEWLIST(cctx, l->lv_len); type = &t_list_string; + member_type = &t_list_string; list_free(l); p += STRLEN(p); } else if (oplen > 0) { int r; - type_T *stacktype; - garray_T *stack; // for "+=", "*=", "..=" etc. first load the current value if (*op != '=') { - switch (dest) + generate_loadvar(cctx, dest, name, lvar, type); + + if (has_index) { - case dest_option: - // TODO: check the option exists - generate_LOAD(cctx, ISN_LOADOPT, 0, name, type); - break; - case dest_global: - generate_LOAD(cctx, ISN_LOADG, 0, name + 2, type); - break; - case dest_buffer: - generate_LOAD(cctx, ISN_LOADB, 0, name + 2, type); - break; - case dest_window: - generate_LOAD(cctx, ISN_LOADW, 0, name + 2, type); - break; - case dest_tab: - generate_LOAD(cctx, ISN_LOADT, 0, name + 2, type); - break; - case dest_script: - compile_load_scriptvar(cctx, - name + (name[1] == ':' ? 2 : 0), NULL, NULL, TRUE); - break; - case dest_env: - // Include $ in the name here - generate_LOAD(cctx, ISN_LOADENV, 0, name, type); - break; - case dest_reg: - generate_LOAD(cctx, ISN_LOADREG, arg[1], NULL, &t_string); - break; - case dest_vimvar: - generate_LOADV(cctx, name + 2, TRUE); - break; - case dest_local: - if (lvar->lv_from_outer) - generate_LOAD(cctx, ISN_LOADOUTER, lvar->lv_idx, - NULL, type); - else - generate_LOAD(cctx, ISN_LOAD, lvar->lv_idx, NULL, type); - break; + // TODO: get member from list or dict + emsg("Index with operation not supported yet"); + goto theend; } } @@ -4860,7 +4938,8 @@ compile_assignment(char_u *arg, exarg_T if (cctx->ctx_skip != TRUE) { - stack = &cctx->ctx_type_stack; + type_T *stacktype; + stacktype = stack->ga_len == 0 ? &t_void : ((type_T **)stack->ga_data)[stack->ga_len - 1]; if (lvar != NULL && (is_decl || !has_type)) @@ -4884,10 +4963,22 @@ compile_assignment(char_u *arg, exarg_T lvar->lv_type = stacktype; } } - else if (need_type(stacktype, lvar->lv_type, -1, cctx) == FAIL) - goto theend; + else + { + type_T *use_type = lvar->lv_type; + + if (has_index) + { + use_type = use_type->tt_member; + if (use_type == NULL) + use_type = &t_void; + } + if (need_type(stacktype, use_type, -1, cctx) == FAIL) + goto theend; + } } - else if (*p != '=' && check_type(type, stacktype, TRUE) == FAIL) + else if (*p != '=' && check_type(member_type, stacktype, TRUE) + == FAIL) goto theend; } } @@ -4906,7 +4997,7 @@ compile_assignment(char_u *arg, exarg_T // variables are always initialized if (ga_grow(instr, 1) == FAIL) goto theend; - switch (type->tt_type) + switch (member_type->tt_type) { case VAR_BOOL: generate_PUSHBOOL(cctx, VVAL_FALSE); @@ -4947,11 +5038,11 @@ compile_assignment(char_u *arg, exarg_T break; } } + end = p; if (oplen > 0 && *op != '=') { type_T *expected = &t_number; - garray_T *stack = &cctx->ctx_type_stack; type_T *stacktype; // TODO: if type is known use float or any operation @@ -4981,103 +5072,158 @@ compile_assignment(char_u *arg, exarg_T } } - switch (dest) - { - case dest_option: - generate_STOREOPT(cctx, name + 1, opt_flags); - break; - case dest_global: - // include g: with the name, easier to execute that way - generate_STORE(cctx, ISN_STOREG, 0, name); - break; - case dest_buffer: - // include b: with the name, easier to execute that way - generate_STORE(cctx, ISN_STOREB, 0, name); - break; - case dest_window: - // include w: with the name, easier to execute that way - generate_STORE(cctx, ISN_STOREW, 0, name); - break; - case dest_tab: - // include t: with the name, easier to execute that way - generate_STORE(cctx, ISN_STORET, 0, name); - break; - case dest_env: - generate_STORE(cctx, ISN_STOREENV, 0, name + 1); - break; - case dest_reg: - generate_STORE(cctx, ISN_STOREREG, name[1], NULL); - break; - case dest_vimvar: - generate_STORE(cctx, ISN_STOREV, vimvaridx, NULL); - break; - case dest_script: - { - char_u *rawname = name + (name[1] == ':' ? 2 : 0); - imported_T *import = NULL; - int sid = current_sctx.sc_sid; - int idx; - - if (name[1] != ':') + if (has_index) + { + int r; + + // Compile the "idx" in "var[idx]". + if (new_local) + --cctx->ctx_locals.ga_len; + p = skipwhite(arg + varlen + 1); + r = compile_expr0(&p, cctx); + if (new_local) + ++cctx->ctx_locals.ga_len; + if (r == FAIL) + goto theend; + if (*skipwhite(p) != ']') + { + emsg(_(e_missbrac)); + goto theend; + } + if (type->tt_type == VAR_DICT + && may_generate_2STRING(-1, cctx) == FAIL) + goto theend; + if (type->tt_type == VAR_LIST + && ((type_T **)stack->ga_data)[stack->ga_len - 1]->tt_type + != VAR_NUMBER) + { + emsg(_(e_number_exp)); + goto theend; + } + + // Load the dict or list. On the stack we then have: + // - value + // - index + // - variable + generate_loadvar(cctx, dest, name, lvar, type); + + if (type->tt_type == VAR_LIST) + { + if (generate_instr_drop(cctx, ISN_STORELIST, 3) == FAIL) + return FAIL; + } + else if (type->tt_type == VAR_DICT) + { + if (generate_instr_drop(cctx, ISN_STOREDICT, 3) == FAIL) + return FAIL; + } + else + { + emsg(_(e_listreq)); + goto theend; + } + } + else + { + switch (dest) + { + case dest_option: + generate_STOREOPT(cctx, name + 1, opt_flags); + break; + case dest_global: + // include g: with the name, easier to execute that way + generate_STORE(cctx, ISN_STOREG, 0, name); + break; + case dest_buffer: + // include b: with the name, easier to execute that way + generate_STORE(cctx, ISN_STOREB, 0, name); + break; + case dest_window: + // include w: with the name, easier to execute that way + generate_STORE(cctx, ISN_STOREW, 0, name); + break; + case dest_tab: + // include t: with the name, easier to execute that way + generate_STORE(cctx, ISN_STORET, 0, name); + break; + case dest_env: + generate_STORE(cctx, ISN_STOREENV, 0, name + 1); + break; + case dest_reg: + generate_STORE(cctx, ISN_STOREREG, name[1], NULL); + break; + case dest_vimvar: + generate_STORE(cctx, ISN_STOREV, vimvaridx, NULL); + break; + case dest_script: { - import = find_imported(name, 0, cctx); - if (import != NULL) - sid = import->imp_sid; - } - - idx = get_script_item_idx(sid, rawname, TRUE); - // TODO: specific type - if (idx < 0) - { - char_u *name_s = name; - - // Include s: in the name for store_var() + char_u *rawname = name + (name[1] == ':' ? 2 : 0); + imported_T *import = NULL; + int sid = current_sctx.sc_sid; + int idx; + if (name[1] != ':') { - int len = (int)STRLEN(name) + 3; - - name_s = alloc(len); - if (name_s == NULL) - name_s = name; - else - vim_snprintf((char *)name_s, len, "s:%s", name); + import = find_imported(name, 0, cctx); + if (import != NULL) + sid = import->imp_sid; } - generate_OLDSCRIPT(cctx, ISN_STORES, name_s, sid, &t_any); - if (name_s != name) - vim_free(name_s); - } - else - generate_VIM9SCRIPT(cctx, ISN_STORESCRIPT, + + idx = get_script_item_idx(sid, rawname, TRUE); + // TODO: specific type + if (idx < 0) + { + char_u *name_s = name; + + // Include s: in the name for store_var() + if (name[1] != ':') + { + int len = (int)STRLEN(name) + 3; + + name_s = alloc(len); + if (name_s == NULL) + name_s = name; + else + vim_snprintf((char *)name_s, len, "s:%s", name); + } + generate_OLDSCRIPT(cctx, ISN_STORES, name_s, sid, + &t_any); + if (name_s != name) + vim_free(name_s); + } + else + generate_VIM9SCRIPT(cctx, ISN_STORESCRIPT, sid, idx, &t_any); - } - break; - case dest_local: - if (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 (!lvar->lv_from_outer && instr->ga_len == instr_count + 1 - && isn->isn_type == ISN_PUSHNR) + } + break; + case dest_local: + if (lvar != NULL) { - 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 = lvar->lv_idx; - isn->isn_arg.storenr.stnr_val = val; - if (stack->ga_len > 0) - --stack->ga_len; + 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 (!lvar->lv_from_outer && 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 = lvar->lv_idx; + isn->isn_arg.storenr.stnr_val = val; + if (stack->ga_len > 0) + --stack->ga_len; + } + else if (lvar->lv_from_outer) + generate_STORE(cctx, ISN_STOREOUTER, lvar->lv_idx, + NULL); + else + generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL); } - else if (lvar->lv_from_outer) - generate_STORE(cctx, ISN_STOREOUTER, lvar->lv_idx, NULL); - else - generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL); - } - break; - } - ret = p; + break; + } + } + ret = end; theend: vim_free(name); @@ -6252,7 +6398,7 @@ compile_def_function(ufunc_T *ufunc, int for (;;) { exarg_T ea; - int is_ex_command = FALSE; + int starts_with_colon = FALSE; // Bail out on the first error to avoid a flood of errors and report // the right line number when inside try/catch. @@ -6327,7 +6473,7 @@ compile_def_function(ufunc_T *ufunc, int break; case ':': - is_ex_command = TRUE; + starts_with_colon = TRUE; break; } @@ -6347,20 +6493,26 @@ compile_def_function(ufunc_T *ufunc, int if (checkforcmd(&ea.cmd, "call", 3)) ea.cmd = skipwhite(ea.cmd); - if (!is_ex_command) + if (!starts_with_colon) { + char_u *pskip; + // Assuming the command starts with a variable or function name, - // find what follows. Also "&opt = val", "$ENV = val" and "@r = - // val". - p = (*ea.cmd == '&' || *ea.cmd == '$' || *ea.cmd == '@') + // find what follows. + // Skip over "var.member", "var[idx]" and the like. + // Also "&opt = val", "$ENV = val" and "@r = val". + pskip = (*ea.cmd == '&' || *ea.cmd == '$' || *ea.cmd == '@') ? ea.cmd + 1 : ea.cmd; - p = to_name_end(p, TRUE); + p = to_name_end(pskip, TRUE); if (p > ea.cmd && *p != NUL) { - int oplen; - int heredoc; - - oplen = assignment_len(skipwhite(p), &heredoc); + char_u *var_end; + int oplen; + int heredoc; + + var_end = find_name_end(pskip, NULL, NULL, + FNE_CHECK_START | FNE_INCL_BR); + oplen = assignment_len(skipwhite(var_end), &heredoc); if (oplen > 0) { // Recognize an assignment if we recognize the variable @@ -6393,7 +6545,7 @@ compile_def_function(ufunc_T *ufunc, int * COMMAND after range */ ea.cmd = skip_range(ea.cmd, NULL); - p = find_ex_command(&ea, NULL, is_ex_command ? NULL + p = find_ex_command(&ea, NULL, starts_with_colon ? NULL : (void *(*)(char_u *, size_t, cctx_T *))lookup_local, &cctx); @@ -6675,7 +6827,7 @@ delete_instr(isn_T *isn) case ISN_LOADW: case ISN_LOADT: case ISN_LOADOPT: - case ISN_MEMBER: + case ISN_STRINGMEMBER: case ISN_PUSHEXC: case ISN_PUSHS: case ISN_STOREENV: @@ -6758,6 +6910,7 @@ delete_instr(isn_T *isn) case ISN_EXECUTE: case ISN_FOR: case ISN_INDEX: + case ISN_MEMBER: case ISN_JUMP: case ISN_LOAD: case ISN_LOADOUTER: @@ -6783,6 +6936,8 @@ delete_instr(isn_T *isn) case ISN_STORENR: case ISN_STOREREG: case ISN_STORESCRIPT: + case ISN_STOREDICT: + case ISN_STORELIST: case ISN_THROW: case ISN_TRY: // nothing allocated diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -1252,6 +1252,76 @@ call_def_function( tv->vval.v_number = iptr->isn_arg.storenr.stnr_val; break; + // store value in list variable + case ISN_STORELIST: + { + typval_T *tv_idx = STACK_TV_BOT(-2); + varnumber_T lidx = tv_idx->vval.v_number; + typval_T *tv_list = STACK_TV_BOT(-1); + list_T *list = tv_list->vval.v_list; + + if (lidx < 0 && list->lv_len + lidx >= 0) + // negative index is relative to the end + lidx = list->lv_len + lidx; + if (lidx < 0 || lidx > list->lv_len) + { + semsg(_(e_listidx), lidx); + goto failed; + } + tv = STACK_TV_BOT(-3); + if (lidx < list->lv_len) + { + listitem_T *li = list_find(list, lidx); + + // overwrite existing list item + clear_tv(&li->li_tv); + li->li_tv = *tv; + } + else + { + // append to list + if (list_append_tv(list, tv) == FAIL) + goto failed; + clear_tv(tv); + } + clear_tv(tv_idx); + clear_tv(tv_list); + } + break; + + // store value in dict variable + case ISN_STOREDICT: + { + typval_T *tv_key = STACK_TV_BOT(-2); + char_u *key = tv_key->vval.v_string; + typval_T *tv_dict = STACK_TV_BOT(-1); + dict_T *dict = tv_dict->vval.v_dict; + dictitem_T *di; + + if (key == NULL || *key == NUL) + { + emsg(_(e_emptykey)); + goto failed; + } + tv = STACK_TV_BOT(-3); + di = dict_find(dict, key, -1); + if (di != NULL) + { + clear_tv(&di->di_tv); + di->di_tv = *tv; + } + else + { + // add to dict + if (dict_add_tv(dict, (char *)key, tv) == FAIL) + goto failed; + clear_tv(tv); + } + clear_tv(tv_key); + clear_tv(tv_dict); + } + break; + // push constant case ISN_PUSHNR: case ISN_PUSHBOOL: @@ -2019,8 +2089,42 @@ call_def_function( } break; + case ISN_MEMBER: + { + dict_T *dict; + char_u *key; + dictitem_T *di; + + // dict member: dict is at stack-2, key at stack-1 + tv = STACK_TV_BOT(-2); + if (tv->v_type != VAR_DICT) + { + emsg(_(e_dictreq)); + goto failed; + } + dict = tv->vval.v_dict; + + tv = STACK_TV_BOT(-1); + if (tv->v_type != VAR_STRING) + { + emsg(_(e_stringreq)); + goto failed; + } + key = tv->vval.v_string; + if ((di = dict_find(dict, key, -1)) == NULL) + { + semsg(_(e_dictkey), key); + goto failed; + } + --ectx.ec_stack.ga_len; + clear_tv(tv); + clear_tv(STACK_TV_BOT(-1)); + copy_tv(&di->di_tv, STACK_TV_BOT(-1)); + } + break; + // dict member with string key - case ISN_MEMBER: + case ISN_STRINGMEMBER: { dict_T *dict; dictitem_T *di; @@ -2380,6 +2484,14 @@ ex_disassemble(exarg_T *eap) iptr->isn_arg.storenr.stnr_idx); break; + case ISN_STORELIST: + smsg("%4d STORELIST", current); + break; + + case ISN_STOREDICT: + smsg("%4d STOREDICT", current); + break; + // constants case ISN_PUSHNR: smsg("%4d PUSHNR %lld", current, @@ -2656,7 +2768,8 @@ ex_disassemble(exarg_T *eap) // expression operations case ISN_CONCAT: smsg("%4d CONCAT", current); break; case ISN_INDEX: smsg("%4d INDEX", current); break; - case ISN_MEMBER: smsg("%4d MEMBER %s", current, + case ISN_MEMBER: smsg("%4d MEMBER", current); break; + case ISN_STRINGMEMBER: smsg("%4d MEMBER %s", current, iptr->isn_arg.string); break; case ISN_NEGATENR: smsg("%4d NEGATENR", current); break;