Mercurial > vim
diff src/vim9compile.c @ 20349:e29b2ec8d4d2 v8.2.0730
patch 8.2.0730: Vim9: Assignment to dict member does not work
Commit: https://github.com/vim/vim/commit/1cc2a94f80ba982f83d1e2d37c4726867e36bd9f
Author: Bram Moolenaar <Bram@vim.org>
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.
author | Bram Moolenaar <Bram@vim.org> |
---|---|
date | Sun, 10 May 2020 19:15:03 +0200 |
parents | 7587d892c00c |
children | fc2d76e0994c |
line wrap: on
line diff
--- 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