To: vim_dev@googlegroups.com Subject: Patch 8.2.0730 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.0730 Problem: Vim9: Assignment to dict member does not work. Solution: Parse dict assignment. Implement getting dict member. Files: src/vim9.h, src/vim9compile.c, src/vim9execute.c, src/globals.h, src/testdir/test_vim9_expr.vim, src/testdir/test_vim9_cmd.vim, src/testdir/test_vim9_script.vim *** ../vim-8.2.0729/src/vim9.h 2020-05-06 21:06:26.421435642 +0200 --- src/vim9.h 2020-05-10 18:30:53.796003961 +0200 *************** *** 48,53 **** --- 48,55 ---- // 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,116 **** // expression operations ISN_CONCAT, ISN_INDEX, // [expr] list index ! ISN_MEMBER, // 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 --- 112,119 ---- // expression operations ISN_CONCAT, ISN_INDEX, // [expr] list index ! 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 *** ../vim-8.2.0729/src/vim9compile.c 2020-05-09 22:50:04.751323784 +0200 --- src/vim9compile.c 2020-05-10 19:03:25.626411542 +0200 *************** *** 1558,1574 **** } /* ! * Generate an ISN_MEMBER instruction. */ static int ! generate_MEMBER(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) return FAIL; isn->isn_arg.string = vim_strnsave(name, (int)len); --- 1558,1574 ---- } /* ! * Generate an ISN_STRINGMEMBER instruction. */ static int ! 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_STRINGMEMBER)) == NULL) return FAIL; isn->isn_arg.string = vim_strnsave(name, (int)len); *************** *** 3485,3499 **** } else if (**arg == '[') { ! garray_T *stack; type_T **typep; 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; --- 3485,3501 ---- } else if (**arg == '[') { ! 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; *arg = skipwhite(*arg + 1); if (compile_expr0(arg, cctx) == FAIL) return FAIL; *************** *** 3505,3521 **** } *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) { ! emsg(_(e_listreq)); ! return FAIL; } ! if ((*typep)->tt_type == VAR_LIST) *typep = (*typep)->tt_member; } else if (**arg == '.' && (*arg)[1] != '.') { --- 3507,3533 ---- } *arg = *arg + 1; ! 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) ! { *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; + } } else if (**arg == '.' && (*arg)[1] != '.') { *************** *** 3535,3541 **** semsg(_(e_syntax_at), *arg); return FAIL; } ! if (generate_MEMBER(cctx, *arg, p - *arg) == FAIL) return FAIL; *arg = p; } --- 3547,3553 ---- semsg(_(e_syntax_at), *arg); return FAIL; } ! if (generate_STRINGMEMBER(cctx, *arg, p - *arg) == FAIL) return FAIL; *arg = p; } *************** *** 4501,4518 **** --- 4513,4586 ---- } 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,4536 **** int oplen = 0; int heredoc = FALSE; type_T *type = &t_any; lvar_T *lvar = NULL; char_u *name; char_u *sp; int has_type = 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) return NULL; if (var_count > 0) { --- 4590,4606 ---- 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; ! var_end = skip_var_list(arg, FALSE, &var_count, &semicolon); ! if (var_end == NULL) return NULL; if (var_count > 0) { *************** *** 4539,4545 **** --- 4609,4620 ---- 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,4723 **** --- 4790,4807 ---- 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,4779 **** --- 4858,4890 ---- 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,4848 **** } generate_NEWLIST(cctx, l->lv_len); 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) { ! 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; } } --- 4903,4926 ---- } 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; // for "+=", "*=", "..=" etc. first load the current value if (*op != '=') { ! generate_loadvar(cctx, dest, name, lvar, type); ! ! if (has_index) { ! // TODO: get member from list or dict ! emsg("Index with operation not supported yet"); ! goto theend; } } *************** *** 4860,4866 **** if (cctx->ctx_skip != TRUE) { ! stack = &cctx->ctx_type_stack; stacktype = stack->ga_len == 0 ? &t_void : ((type_T **)stack->ga_data)[stack->ga_len - 1]; if (lvar != NULL && (is_decl || !has_type)) --- 4938,4945 ---- if (cctx->ctx_skip != TRUE) { ! 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,4893 **** lvar->lv_type = stacktype; } } ! else if (need_type(stacktype, lvar->lv_type, -1, cctx) == FAIL) ! goto theend; } ! else if (*p != '=' && check_type(type, stacktype, TRUE) == FAIL) goto theend; } } --- 4963,4984 ---- lvar->lv_type = stacktype; } } ! 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(member_type, stacktype, TRUE) ! == FAIL) goto theend; } } *************** *** 4906,4912 **** // variables are always initialized if (ga_grow(instr, 1) == FAIL) goto theend; ! switch (type->tt_type) { case VAR_BOOL: generate_PUSHBOOL(cctx, VVAL_FALSE); --- 4997,5003 ---- // variables are always initialized if (ga_grow(instr, 1) == FAIL) goto theend; ! switch (member_type->tt_type) { case VAR_BOOL: generate_PUSHBOOL(cctx, VVAL_FALSE); *************** *** 4947,4957 **** break; } } 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 --- 5038,5048 ---- break; } } + end = p; if (oplen > 0 && *op != '=') { type_T *expected = &t_number; type_T *stacktype; // TODO: if type is known use float or any operation *************** *** 4981,5083 **** } } ! 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] != ':') ! { ! 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() 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) { ! 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; } ! 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; theend: vim_free(name); --- 5072,5229 ---- } } ! 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: { ! char_u *rawname = name + (name[1] == ':' ? 2 : 0); ! imported_T *import = NULL; ! int sid = current_sctx.sc_sid; ! int idx; if (name[1] != ':') { ! 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() ! 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) ! { ! 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); } ! break; ! } } ! ret = end; theend: vim_free(name); *************** *** 6252,6258 **** for (;;) { exarg_T ea; ! int is_ex_command = FALSE; // Bail out on the first error to avoid a flood of errors and report // the right line number when inside try/catch. --- 6398,6404 ---- for (;;) { exarg_T ea; ! 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,6333 **** break; case ':': ! is_ex_command = TRUE; break; } --- 6473,6479 ---- break; case ':': ! starts_with_colon = TRUE; break; } *************** *** 6347,6366 **** if (checkforcmd(&ea.cmd, "call", 3)) ea.cmd = skipwhite(ea.cmd); ! if (!is_ex_command) { // 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 == '@') ? ea.cmd + 1 : ea.cmd; ! p = to_name_end(p, TRUE); if (p > ea.cmd && *p != NUL) { ! int oplen; ! int heredoc; ! ! oplen = assignment_len(skipwhite(p), &heredoc); if (oplen > 0) { // Recognize an assignment if we recognize the variable --- 6493,6518 ---- if (checkforcmd(&ea.cmd, "call", 3)) ea.cmd = skipwhite(ea.cmd); ! if (!starts_with_colon) { + char_u *pskip; + // Assuming the command starts with a variable or function name, ! // 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(pskip, TRUE); if (p > ea.cmd && *p != NUL) { ! 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,6399 **** * COMMAND after range */ ea.cmd = skip_range(ea.cmd, NULL); ! p = find_ex_command(&ea, NULL, is_ex_command ? NULL : (void *(*)(char_u *, size_t, cctx_T *))lookup_local, &cctx); --- 6545,6551 ---- * COMMAND after range */ ea.cmd = skip_range(ea.cmd, NULL); ! p = find_ex_command(&ea, NULL, starts_with_colon ? NULL : (void *(*)(char_u *, size_t, cctx_T *))lookup_local, &cctx); *************** *** 6675,6681 **** case ISN_LOADW: case ISN_LOADT: case ISN_LOADOPT: ! case ISN_MEMBER: case ISN_PUSHEXC: case ISN_PUSHS: case ISN_STOREENV: --- 6827,6833 ---- case ISN_LOADW: case ISN_LOADT: case ISN_LOADOPT: ! case ISN_STRINGMEMBER: case ISN_PUSHEXC: case ISN_PUSHS: case ISN_STOREENV: *************** *** 6758,6763 **** --- 6910,6916 ---- case ISN_EXECUTE: case ISN_FOR: case ISN_INDEX: + case ISN_MEMBER: case ISN_JUMP: case ISN_LOAD: case ISN_LOADOUTER: *************** *** 6783,6788 **** --- 6936,6943 ---- case ISN_STORENR: case ISN_STOREREG: case ISN_STORESCRIPT: + case ISN_STOREDICT: + case ISN_STORELIST: case ISN_THROW: case ISN_TRY: // nothing allocated *** ../vim-8.2.0729/src/vim9execute.c 2020-05-09 23:20:16.997309756 +0200 --- src/vim9execute.c 2020-05-10 18:36:59.071020484 +0200 *************** *** 1252,1257 **** --- 1252,1327 ---- 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,2028 **** } break; - // dict member with string key case ISN_MEMBER: { dict_T *dict; dictitem_T *di; tv = STACK_TV_BOT(-1); --- 2089,2132 ---- } 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_STRINGMEMBER: + { + dict_T *dict; dictitem_T *di; tv = STACK_TV_BOT(-1); *************** *** 2380,2385 **** --- 2484,2497 ---- 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,2662 **** // 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, iptr->isn_arg.string); break; case ISN_NEGATENR: smsg("%4d NEGATENR", current); break; --- 2768,2775 ---- // expression operations case ISN_CONCAT: smsg("%4d CONCAT", current); break; case ISN_INDEX: smsg("%4d INDEX", current); break; ! 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; *** ../vim-8.2.0729/src/globals.h 2020-05-07 18:37:00.128512594 +0200 --- src/globals.h 2020-05-10 18:29:33.540180038 +0200 *************** *** 1673,1678 **** --- 1673,1679 ---- 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")); *** ../vim-8.2.0729/src/testdir/test_vim9_expr.vim 2020-05-09 15:43:58.322878209 +0200 --- src/testdir/test_vim9_expr.vim 2020-05-10 18:51:05.024719168 +0200 *************** *** 644,652 **** 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:') if has('float') ! call CheckDefFailure(["let x = 0.7[1]"], 'E714:') endif endfunc --- 644,652 ---- call CheckDefFailure(["let x = #{one: 1} / #{two: 2}"], 'E1036:') call CheckDefFailure(["let x = #{one: 1} % #{two: 2}"], 'E1035:') ! call CheckDefFailure(["let x = 0xff[1]"], 'E1090:') if has('float') ! call CheckDefFailure(["let x = 0.7[1]"], 'E1090:') endif endfunc *** ../vim-8.2.0729/src/testdir/test_vim9_cmd.vim 2020-04-26 13:50:38.436096831 +0200 --- src/testdir/test_vim9_cmd.vim 2020-05-10 18:43:25.457831506 +0200 *************** *** 44,48 **** --- 44,70 ---- 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 *** ../vim-8.2.0729/src/testdir/test_vim9_script.vim 2020-05-10 15:24:41.392262069 +0200 --- src/testdir/test_vim9_script.vim 2020-05-10 19:01:44.766743159 +0200 *************** *** 38,44 **** call CheckDefFailure(['let x:string'], 'E1069:') call CheckDefFailure(['let x:string = "x"'], 'E1069:') ! call CheckDefFailure(['let a:string = "x"'], 'E1082:') let a: number = 6 assert_equal(6, a) --- 38,44 ---- call CheckDefFailure(['let x:string'], 'E1069:') call CheckDefFailure(['let x:string = "x"'], 'E1069:') ! call CheckDefFailure(['let a:string = "x"'], 'E1069:') let a: number = 6 assert_equal(6, a) *** ../vim-8.2.0729/src/version.c 2020-05-10 15:24:41.392262069 +0200 --- src/version.c 2020-05-10 17:31:58.034861889 +0200 *************** *** 748,749 **** --- 748,751 ---- { /* Add new patch number below this line */ + /**/ + 730, /**/ -- From "know your smileys": :^[/ mean-smiley-with-cigarette /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///