# HG changeset patch # User Bram Moolenaar # Date 1580679004 -3600 # Node ID 9dc843109c97800bdec752076a94092d76935b1d # Parent 0d513180baa1b6a39ffe8d9e37242d47c60985c3 patch 8.2.0200: Vim9 script commands not sufficiently tested Commit: https://github.com/vim/vim/commit/b283a8a6802ef8a46b17cb439f9514840c03698f Author: Bram Moolenaar Date: Sun Feb 2 22:24:04 2020 +0100 patch 8.2.0200: Vim9 script commands not sufficiently tested Problem: Vim9 script commands not sufficiently tested. Solution: Add more tests. Fix storing global variable. Make script variables work. diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -1206,14 +1206,7 @@ ex_let_one( } if (p != NULL) { - vim_setenv(name, p); - if (STRICMP(name, "HOME") == 0) - init_homedir(); - else if (didset_vim && STRICMP(name, "VIM") == 0) - didset_vim = FALSE; - else if (didset_vimruntime - && STRICMP(name, "VIMRUNTIME") == 0) - didset_vimruntime = FALSE; + vim_setenv_ext(name, p); arg_end = arg; } name[len] = c1; @@ -1967,6 +1960,24 @@ get_vim_var_tv(int idx) } /* + * Set v: variable to "tv". Only accepts the same type. + * Takes over the value of "tv". + */ + int +set_vim_var_tv(int idx, typval_T *tv) +{ + if (vimvars[idx].vv_type != tv->v_type) + { + emsg(_("E1063: type mismatch for v: variable")); + clear_tv(tv); + return FAIL; + } + clear_tv(&vimvars[idx].vv_di.di_tv); + vimvars[idx].vv_di.di_tv = *tv; + return OK; +} + +/* * Get number v: variable value. */ varnumber_T diff --git a/src/misc1.c b/src/misc1.c --- a/src/misc1.c +++ b/src/misc1.c @@ -1854,6 +1854,22 @@ vim_unsetenv(char_u *var) /* + * Set environment variable "name" and take care of side effects. + */ + void +vim_setenv_ext(char_u *name, char_u *val) +{ + vim_setenv(name, val); + if (STRICMP(name, "HOME") == 0) + init_homedir(); + else if (didset_vim && STRICMP(name, "VIM") == 0) + didset_vim = FALSE; + else if (didset_vimruntime + && STRICMP(name, "VIMRUNTIME") == 0) + didset_vimruntime = FALSE; +} + +/* * Our portable version of setenv. */ void diff --git a/src/proto/evalvars.pro b/src/proto/evalvars.pro --- a/src/proto/evalvars.pro +++ b/src/proto/evalvars.pro @@ -33,6 +33,7 @@ void set_vim_var_type(int idx, vartype_T void set_vim_var_nr(int idx, varnumber_T val); char *get_vim_var_name(int idx); typval_T *get_vim_var_tv(int idx); +int set_vim_var_tv(int idx, typval_T *tv); varnumber_T get_vim_var_nr(int idx); char_u *get_vim_var_str(int idx); list_T *get_vim_var_list(int idx); diff --git a/src/proto/misc1.pro b/src/proto/misc1.pro --- a/src/proto/misc1.pro +++ b/src/proto/misc1.pro @@ -31,6 +31,7 @@ void expand_env(char_u *src, char_u *dst void expand_env_esc(char_u *srcp, char_u *dst, int dstlen, int esc, int one, char_u *startstr); char_u *vim_getenv(char_u *name, int *mustfree); void vim_unsetenv(char_u *var); +void vim_setenv_ext(char_u *name, char_u *val); void vim_setenv(char_u *name, char_u *val); char_u *get_env_name(expand_T *xp, int idx); char_u *get_users(expand_T *xp, int idx); 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 @@ -42,6 +42,13 @@ def Test_assignment() let dict1: dict = #{key: 'value'} let dict2: dict = #{one: 1, two: 2} + + v:char = 'abc' + call assert_equal('abc', v:char) + + $ENVVAR = 'foobar' + call assert_equal('foobar', $ENVVAR) + $ENVVAR = '' enddef func Test_assignment_failure() @@ -106,7 +113,7 @@ def Test_call_ufunc_count() Increment() " works with and without :call assert_equal(4, g:counter) - assert_equal(4, g:counter) + call assert_equal(4, g:counter) unlet g:counter enddef @@ -311,7 +318,11 @@ def Test_import_absolute() let import_lines = [ \ 'vim9script', \ 'import exported from "' .. escape(getcwd(), '\') .. '/Xexport_abs.vim"', - \ 'g:imported_abs = exported', + \ 'def UseExported()', + \ ' g:imported_abs = exported', + \ 'enddef', + \ 'UseExported()', + \ 'g:import_disassabled = execute("disass UseExported")', \ ] writefile(import_lines, 'Ximport_abs.vim') writefile(s:export_script_lines, 'Xexport_abs.vim') @@ -319,7 +330,12 @@ def Test_import_absolute() source Ximport_abs.vim assert_equal(9876, g:imported_abs) + assert_match('\d\+_UseExported.*' + \ .. 'g:imported_abs = exported.*' + \ .. '0 LOADSCRIPT exported from .*Xexport_abs.vim.*' + \ .. '1 STOREG g:imported_abs', g:import_disassabled) unlet g:imported_abs + unlet g:import_disassabled delete('Ximport_abs.vim') delete('Xexport_abs.vim') @@ -405,7 +421,7 @@ endfunc let s:scriptvar = 4 let g:globalvar = 'g' -def s:ScriptFunc(arg: string) +def s:ScriptFuncLoad(arg: string) let local = 1 buffers echo arg @@ -418,12 +434,25 @@ def s:ScriptFunc(arg: string) echo @z enddef +def s:ScriptFuncStore() + let localnr = 1 + localnr = 2 + let localstr = 'abc' + localstr = 'xyz' + v:char = 'abc' + s:scriptvar = 'sv' + g:globalvar = 'gv' + &tabstop = 8 + $ENVVAR = 'ev' + @z = 'rv' +enddef + def Test_disassemble() assert_fails('disass NoFunc', 'E1061:') assert_fails('disass NotCompiled', 'E1062:') - let res = execute('disass s:ScriptFunc') - assert_match('\d*_ScriptFunc.*' + let res = execute('disass s:ScriptFuncLoad') + assert_match('\d*_ScriptFuncLoad.*' \ .. 'buffers.*' \ .. ' EXEC \+buffers.*' \ .. ' LOAD arg\[-1\].*' @@ -432,7 +461,31 @@ def Test_disassemble() \ .. ' LOADS s:scriptvar from .*test_vim9_script.vim.*' \ .. ' LOADG g:globalvar.*' \ .. ' LOADENV $ENVVAR.*' - \ .. ' LOADREG @z.*', res) + \ .. ' LOADREG @z.*' + \, res) + + " TODO: + " v:char = + " s:scriptvar = + res = execute('disass s:ScriptFuncStore') + assert_match('\d*_ScriptFuncStore.*' + \ .. 'localnr = 2.*' + \ .. ' STORE 2 in $0.*' + \ .. 'localstr = ''xyz''.*' + \ .. ' STORE $1.*' + \ .. 'v:char = ''abc''.*' + \ .. 'STOREV v:char.*' + \ .. 's:scriptvar = ''sv''.*' + \ .. ' STORES s:scriptvar in .*test_vim9_script.vim.*' + \ .. 'g:globalvar = ''gv''.*' + \ .. ' STOREG g:globalvar.*' + \ .. '&tabstop = 8.*' + \ .. ' STOREOPT &tabstop.*' + \ .. '$ENVVAR = ''ev''.*' + \ .. ' STOREENV $ENVVAR.*' + \ .. '@z = ''rv''.*' + \ .. ' STOREREG @z.*' + \, res) enddef diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -743,6 +743,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 200, +/**/ 199, /**/ 198, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -18,17 +18,21 @@ typedef enum { // get and set variables ISN_LOAD, // push local variable isn_arg.number ISN_LOADV, // push v: variable isn_arg.number + ISN_LOADG, // push g: variable isn_arg.string + ISN_LOADS, // push s: variable isn_arg.loadstore ISN_LOADSCRIPT, // push script-local variable isn_arg.script. - ISN_LOADS, // push s: variable isn_arg.string - ISN_LOADG, // push g: variable isn_arg.string ISN_LOADOPT, // push option isn_arg.string ISN_LOADENV, // push environment variable isn_arg.string ISN_LOADREG, // push register isn_arg.number ISN_STORE, // pop into local variable isn_arg.number + ISN_STOREV, // pop into v: variable isn_arg.number ISN_STOREG, // pop into global variable isn_arg.string + ISN_STORES, // pop into scirpt variable isn_arg.loadstore ISN_STORESCRIPT, // pop into scirpt variable isn_arg.script ISN_STOREOPT, // pop into option isn_arg.string + ISN_STOREENV, // pop into environment variable isn_arg.string + ISN_STOREREG, // pop into register isn_arg.number // ISN_STOREOTHER, // pop into other script variable isn_arg.other. ISN_STORENR, // store number into local variable isn_arg.storenr.str_idx @@ -180,13 +184,13 @@ typedef struct { int so_flags; } storeopt_T; -// arguments to ISN_LOADS +// arguments to ISN_LOADS and ISN_STORES typedef struct { char_u *ls_name; // variable name int ls_sid; // script ID -} loads_T; +} loadstore_T; -// arguments to ISN_LOADSCRIPT +// arguments to ISN_LOADSCRIPT and ISN_STORESCRIPT typedef struct { int script_sid; // script ID int script_idx; // index in sn_var_vals @@ -217,7 +221,7 @@ typedef struct { checktype_T type; storenr_T storenr; storeopt_T storeopt; - loads_T loads; + loadstore_T loadstore; script_T script; } isn_arg; } isn_T; diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -335,7 +335,7 @@ check_number_or_float(vartype_T type1, v || type2 == VAR_UNKNOWN))) { if (*op == '+') - semsg(_("E1035: wrong argument type for +")); + emsg(_("E1035: wrong argument type for +")); else semsg(_("E1036: %c requires number or float arguments"), *op); return FAIL; @@ -721,20 +721,49 @@ generate_LOAD( } /* + * Generate an ISN_LOADV instruction. + */ + static int +generate_LOADV( + cctx_T *cctx, + char_u *name, + int error) +{ + // load v:var + int vidx = find_vim_var(name); + + if (vidx < 0) + { + if (error) + semsg(_(e_var_notfound), name); + return FAIL; + } + + // TODO: get actual type + return generate_LOAD(cctx, ISN_LOADV, vidx, NULL, &t_any); +} + +/* * Generate an ISN_LOADS instruction. */ static int -generate_LOADS( +generate_OLDSCRIPT( cctx_T *cctx, + isntype_T isn_type, char_u *name, - int sid) + int sid, + type_T *type) { isn_T *isn; - if ((isn = generate_instr_type(cctx, ISN_LOADS, &t_any)) == NULL) + if (isn_type == ISN_LOADS) + isn = generate_instr_type(cctx, isn_type, type); + else + isn = generate_instr_drop(cctx, isn_type, 1); + if (isn == NULL) return FAIL; - isn->isn_arg.loads.ls_name = vim_strsave(name); - isn->isn_arg.loads.ls_sid = sid; + isn->isn_arg.loadstore.ls_name = vim_strsave(name); + isn->isn_arg.loadstore.ls_sid = sid; return OK; } @@ -743,7 +772,7 @@ generate_LOADS( * Generate an ISN_LOADSCRIPT or ISN_STORESCRIPT instruction. */ static int -generate_SCRIPT( +generate_VIM9SCRIPT( cctx_T *cctx, isntype_T isn_type, int sid, @@ -1476,13 +1505,14 @@ compile_load_scriptvar(cctx_T *cctx, cha if (idx == -1) { // variable exists but is not in sn_var_vals: old style script. - return generate_LOADS(cctx, name, current_sctx.sc_sid); + return generate_OLDSCRIPT(cctx, ISN_LOADS, name, current_sctx.sc_sid, + &t_any); } if (idx >= 0) { svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + idx; - generate_SCRIPT(cctx, ISN_LOADSCRIPT, + generate_VIM9SCRIPT(cctx, ISN_LOADSCRIPT, current_sctx.sc_sid, idx, sv->sv_type); return OK; } @@ -1491,7 +1521,7 @@ compile_load_scriptvar(cctx_T *cctx, cha if (import != NULL) { // TODO: check this is a variable, not a function - generate_SCRIPT(cctx, ISN_LOADSCRIPT, + generate_VIM9SCRIPT(cctx, ISN_LOADSCRIPT, import->imp_sid, import->imp_var_vals_idx, import->imp_type); @@ -1523,18 +1553,7 @@ compile_load(char_u **arg, char_u *end, if (**arg == 'v') { - // load v:var - int vidx = find_vim_var(name); - - if (vidx < 0) - { - if (error) - semsg(_(e_var_notfound), name); - goto theend; - } - - // TODO: get actual type - res = generate_LOAD(cctx, ISN_LOADV, vidx, NULL, &t_any); + res = generate_LOADV(cctx, name, error); } else if (**arg == 'g') { @@ -3071,6 +3090,9 @@ compile_assignment(char_u *arg, exarg_T int opt_type; int opt_flags = 0; int global = FALSE; + int env = FALSE; + int reg = FALSE; + int vimvaridx = -1; int script = FALSE; int oplen = 0; int heredoc = FALSE; @@ -3135,6 +3157,29 @@ compile_assignment(char_u *arg, exarg_T else type = &t_number; // both number and boolean option } + else if (*arg == '$') + { + env = TRUE; + if (is_decl) + { + semsg(_("E1065: Cannot declare an environment variable: %s"), name); + goto theend; + } + } + else if (*arg == '@') + { + if (!valid_yank_reg(arg[1], TRUE)) + { + emsg_invreg(arg[1]); + return FAIL; + } + reg = TRUE; + if (is_decl) + { + semsg(_("E1066: Cannot declare a register: %s"), name); + goto theend; + } + } else if (STRNCMP(arg, "g:", 2) == 0) { global = TRUE; @@ -3144,6 +3189,20 @@ compile_assignment(char_u *arg, exarg_T goto theend; } } + else if (STRNCMP(arg, "v:", 2) == 0) + { + vimvaridx = find_vim_var(name + 2); + if (vimvaridx < 0) + { + semsg(_(e_var_notfound), arg); + goto theend; + } + if (is_decl) + { + semsg(_("E1064: Cannot declare a v: variable: %s"), name); + goto theend; + } + } else { for (idx = 0; reserved[idx] != NULL; ++idx) @@ -3171,7 +3230,9 @@ compile_assignment(char_u *arg, exarg_T } } } - else if (lookup_script(arg, varlen) == OK) + else if ((STRNCMP(arg, "s:", 2) == 0 + ? lookup_script(arg + 2, varlen - 2) + : lookup_script(arg, varlen)) == OK) { script = TRUE; if (is_decl) @@ -3226,7 +3287,7 @@ compile_assignment(char_u *arg, exarg_T } // +=, /=, etc. require an existing variable - if (idx < 0 && !global && !option) + if (idx < 0 && !global && !env && !reg && !option) { if (oplen > 1 && !heredoc) { @@ -3272,6 +3333,13 @@ compile_assignment(char_u *arg, exarg_T generate_LOAD(cctx, ISN_LOADOPT, 0, name + 1, type); else if (global) generate_LOAD(cctx, ISN_LOADG, 0, name + 2, type); + else if (env) + // Include $ in the name here + generate_LOAD(cctx, ISN_LOADENV, 0, name, type); + else if (reg) + generate_LOAD(cctx, ISN_LOADREG, arg[1], NULL, &t_string); + else if (vimvaridx >= 0) + generate_LOADV(cctx, name + 2, TRUE); else generate_LOAD(cctx, ISN_LOAD, idx, NULL, type); } @@ -3362,12 +3430,25 @@ compile_assignment(char_u *arg, exarg_T if (option) generate_STOREOPT(cctx, name + 1, opt_flags); else if (global) - generate_STORE(cctx, ISN_STOREG, 0, name + 2); + // include g: with the name, easier to execute that way + generate_STORE(cctx, ISN_STOREG, 0, name); + else if (env) + generate_STORE(cctx, ISN_STOREENV, 0, name + 1); + else if (reg) + generate_STORE(cctx, ISN_STOREREG, name[1], NULL); + else if (vimvaridx >= 0) + generate_STORE(cctx, ISN_STOREV, vimvaridx, NULL); else if (script) { - idx = get_script_item_idx(current_sctx.sc_sid, name, TRUE); + char_u *rawname = name + (name[1] == ':' ? 2 : 0); + + idx = get_script_item_idx(current_sctx.sc_sid, rawname, TRUE); // TODO: specific type - generate_SCRIPT(cctx, ISN_STORESCRIPT, + if (idx < 0) + generate_OLDSCRIPT(cctx, ISN_STORES, rawname, + current_sctx.sc_sid, &t_any); + else + generate_VIM9SCRIPT(cctx, ISN_STORESCRIPT, current_sctx.sc_sid, idx, &t_any); } else @@ -4527,8 +4608,9 @@ compile_def_function(ufunc_T *ufunc, int ea.cmd = skipwhite(ea.cmd); // Assuming the command starts with a variable or function name, find - // what follows. Also "&opt = value". - p = (*ea.cmd == '&') ? ea.cmd + 1 : ea.cmd; + // 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); if (p > ea.cmd && *p != NUL) { @@ -4554,7 +4636,11 @@ compile_def_function(ufunc_T *ufunc, int // "g:var = expr" // "var = expr" where "var" is a local var name. // "&opt = expr" + // "$ENV = expr" + // "@r = expr" if (*ea.cmd == '&' + || *ea.cmd == '$' + || *ea.cmd == '@' || ((p - ea.cmd) > 2 && ea.cmd[1] == ':') || lookup_local(ea.cmd, p - ea.cmd, &cctx) >= 0 || lookup_script(ea.cmd, p - ea.cmd) == OK) @@ -4776,12 +4862,14 @@ delete_instr(isn_T *isn) case ISN_MEMBER: case ISN_PUSHEXC: case ISN_PUSHS: + case ISN_STOREENV: case ISN_STOREG: vim_free(isn->isn_arg.string); break; case ISN_LOADS: - vim_free(isn->isn_arg.loads.ls_name); + case ISN_STORES: + vim_free(isn->isn_arg.loadstore.ls_name); break; case ISN_STOREOPT: @@ -4841,7 +4929,9 @@ delete_instr(isn_T *isn) case ISN_PUSHSPEC: case ISN_RETURN: case ISN_STORE: + case ISN_STOREV: case ISN_STORENR: + case ISN_STOREREG: case ISN_STORESCRIPT: case ISN_THROW: case ISN_TRY: diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -488,7 +488,7 @@ call_def_function( ++ectx.ec_stack.ga_len; break; - // load s: variable in vim9script + // load s: variable in Vim9 script case ISN_LOADSCRIPT: { scriptitem_T *si = @@ -507,12 +507,13 @@ call_def_function( // load s: variable in old script case ISN_LOADS: { - hashtab_T *ht = &SCRIPT_VARS(iptr->isn_arg.loads.ls_sid); - char_u *name = iptr->isn_arg.loads.ls_name; + hashtab_T *ht = &SCRIPT_VARS( + iptr->isn_arg.loadstore.ls_sid); + char_u *name = iptr->isn_arg.loadstore.ls_name; dictitem_T *di = find_var_in_ht(ht, 0, name, TRUE); if (di == NULL) { - semsg(_("E121: Undefined variable: s:%s"), name); + semsg(_(e_undefvar), name); goto failed; } else @@ -601,7 +602,26 @@ call_def_function( *tv = *STACK_TV_BOT(0); break; - // store script-local variable + // store s: variable in old script + case ISN_STORES: + { + hashtab_T *ht = &SCRIPT_VARS( + iptr->isn_arg.loadstore.ls_sid); + char_u *name = iptr->isn_arg.loadstore.ls_name; + dictitem_T *di = find_var_in_ht(ht, 0, name, TRUE); + + if (di == NULL) + { + semsg(_(e_undefvar), name); + goto failed; + } + --ectx.ec_stack.ga_len; + clear_tv(&di->di_tv); + di->di_tv = *STACK_TV_BOT(0); + } + break; + + // store script-local variable in Vim9 script case ISN_STORESCRIPT: { scriptitem_T *si = SCRIPT_ITEM( @@ -648,6 +668,32 @@ call_def_function( } break; + // store $ENV + case ISN_STOREENV: + --ectx.ec_stack.ga_len; + vim_setenv_ext(iptr->isn_arg.string, + tv_get_string(STACK_TV_BOT(0))); + break; + + // store @r + case ISN_STOREREG: + { + int reg = iptr->isn_arg.number; + + --ectx.ec_stack.ga_len; + write_reg_contents(reg == '@' ? '"' : reg, + tv_get_string(STACK_TV_BOT(0)), -1, FALSE); + } + break; + + // store v: variable + case ISN_STOREV: + --ectx.ec_stack.ga_len; + if (set_vim_var_tv(iptr->isn_arg.number, STACK_TV_BOT(0)) + == FAIL) + goto failed; + break; + // store g: variable case ISN_STOREG: { @@ -1583,7 +1629,8 @@ ex_disassemble(exarg_T *eap) break; case ISN_LOADS: { - scriptitem_T *si = SCRIPT_ITEM(iptr->isn_arg.loads.ls_sid); + scriptitem_T *si = SCRIPT_ITEM( + iptr->isn_arg.loadstore.ls_sid); smsg("%4d LOADS s:%s from %s", current, iptr->isn_arg.string, si->sn_name); @@ -1605,8 +1652,21 @@ ex_disassemble(exarg_T *eap) case ISN_STORE: smsg("%4d STORE $%lld", current, iptr->isn_arg.number); break; + case ISN_STOREV: + smsg("%4d STOREV v:%s", current, + get_vim_var_name(iptr->isn_arg.number)); + break; case ISN_STOREG: - smsg("%4d STOREG g:%s", current, iptr->isn_arg.string); + smsg("%4d STOREG %s", current, iptr->isn_arg.string); + break; + case ISN_STORES: + { + scriptitem_T *si = SCRIPT_ITEM( + iptr->isn_arg.loadstore.ls_sid); + + smsg("%4d STORES s:%s in %s", current, + iptr->isn_arg.string, si->sn_name); + } break; case ISN_STORESCRIPT: { @@ -1623,7 +1683,12 @@ ex_disassemble(exarg_T *eap) smsg("%4d STOREOPT &%s", current, iptr->isn_arg.storeopt.so_name); break; - + case ISN_STOREENV: + smsg("%4d STOREENV $%s", current, iptr->isn_arg.string); + break; + case ISN_STOREREG: + smsg("%4d STOREREG @%c", current, iptr->isn_arg.number); + break; case ISN_STORENR: smsg("%4d STORE %lld in $%d", current, iptr->isn_arg.storenr.str_val,