forked from aniani/vim
Problem: Vim9: no test for MEMBER instruction. Solution: Add a test. Make matches stricter.
1115 lines
28 KiB
VimL
1115 lines
28 KiB
VimL
" Test the :disassemble command, and compilation as a side effect
|
|
|
|
source check.vim
|
|
|
|
func NotCompiled()
|
|
echo "not"
|
|
endfunc
|
|
|
|
let s:scriptvar = 4
|
|
let g:globalvar = 'g'
|
|
let b:buffervar = 'b'
|
|
let w:windowvar = 'w'
|
|
let t:tabpagevar = 't'
|
|
|
|
def s:ScriptFuncLoad(arg: string)
|
|
let local = 1
|
|
buffers
|
|
echo arg
|
|
echo local
|
|
echo &lines
|
|
echo v:version
|
|
echo s:scriptvar
|
|
echo g:globalvar
|
|
echo b:buffervar
|
|
echo w:windowvar
|
|
echo t:tabpagevar
|
|
echo &tabstop
|
|
echo $ENVVAR
|
|
echo @z
|
|
enddef
|
|
|
|
def Test_disassemble_load()
|
|
assert_fails('disass NoFunc', 'E1061:')
|
|
assert_fails('disass NotCompiled', 'E1062:')
|
|
assert_fails('disass', 'E471:')
|
|
assert_fails('disass [', 'E475:')
|
|
assert_fails('disass 234', 'E475:')
|
|
assert_fails('disass <XX>foo', 'E475:')
|
|
|
|
let res = execute('disass s:ScriptFuncLoad')
|
|
assert_match('<SNR>\d*_ScriptFuncLoad.*' ..
|
|
'buffers.*' ..
|
|
' EXEC \+buffers.*' ..
|
|
' LOAD arg\[-1\].*' ..
|
|
' LOAD $0.*' ..
|
|
' LOADOPT &lines.*' ..
|
|
' LOADV v:version.*' ..
|
|
' LOADS s:scriptvar from .*test_vim9_disassemble.vim.*' ..
|
|
' LOADG g:globalvar.*' ..
|
|
' LOADB b:buffervar.*' ..
|
|
' LOADW w:windowvar.*' ..
|
|
' LOADT t:tabpagevar.*' ..
|
|
' LOADENV $ENVVAR.*' ..
|
|
' LOADREG @z.*',
|
|
res)
|
|
enddef
|
|
|
|
def s:EditExpand()
|
|
let filename = "file"
|
|
let filenr = 123
|
|
edit the`=filename``=filenr`.txt
|
|
enddef
|
|
|
|
def Test_disassemble_exec_expr()
|
|
let res = execute('disass s:EditExpand')
|
|
assert_match('<SNR>\d*_EditExpand.*' ..
|
|
' let filename = "file".*' ..
|
|
'\d PUSHS "file".*' ..
|
|
'\d STORE $0.*' ..
|
|
' let filenr = 123.*' ..
|
|
'\d STORE 123 in $1.*' ..
|
|
' edit the`=filename``=filenr`.txt.*' ..
|
|
'\d PUSHS "edit the".*' ..
|
|
'\d LOAD $0.*' ..
|
|
'\d LOAD $1.*' ..
|
|
'\d 2STRING stack\[-1\].*' ..
|
|
'\d PUSHS ".txt".*' ..
|
|
'\d EXECCONCAT 4.*' ..
|
|
'\d PUSHNR 0.*' ..
|
|
'\d RETURN',
|
|
res)
|
|
enddef
|
|
|
|
def s:ScriptFuncPush()
|
|
let localbool = true
|
|
let localspec = v:none
|
|
let localblob = 0z1234
|
|
if has('float')
|
|
let localfloat = 1.234
|
|
endif
|
|
enddef
|
|
|
|
def Test_disassemble_push()
|
|
let res = execute('disass s:ScriptFuncPush')
|
|
assert_match('<SNR>\d*_ScriptFuncPush.*' ..
|
|
'localbool = true.*' ..
|
|
' PUSH v:true.*' ..
|
|
'localspec = v:none.*' ..
|
|
' PUSH v:none.*' ..
|
|
'localblob = 0z1234.*' ..
|
|
' PUSHBLOB 0z1234.*',
|
|
res)
|
|
if has('float')
|
|
assert_match('<SNR>\d*_ScriptFuncPush.*' ..
|
|
'localfloat = 1.234.*' ..
|
|
' PUSHF 1.234.*',
|
|
res)
|
|
endif
|
|
enddef
|
|
|
|
def s:ScriptFuncStore()
|
|
let localnr = 1
|
|
localnr = 2
|
|
let localstr = 'abc'
|
|
localstr = 'xyz'
|
|
v:char = 'abc'
|
|
s:scriptvar = 'sv'
|
|
g:globalvar = 'gv'
|
|
b:buffervar = 'bv'
|
|
w:windowvar = 'wv'
|
|
t:tabpagevar = 'tv'
|
|
&tabstop = 8
|
|
$ENVVAR = 'ev'
|
|
@z = 'rv'
|
|
enddef
|
|
|
|
def Test_disassemble_store()
|
|
let res = execute('disass s:ScriptFuncStore')
|
|
assert_match('<SNR>\d*_ScriptFuncStore.*' ..
|
|
'let localnr = 1.*' ..
|
|
'localnr = 2.*' ..
|
|
' STORE 2 in $0.*' ..
|
|
'let localstr = ''abc''.*' ..
|
|
'localstr = ''xyz''.*' ..
|
|
' STORE $1.*' ..
|
|
'v:char = ''abc''.*' ..
|
|
'STOREV v:char.*' ..
|
|
's:scriptvar = ''sv''.*' ..
|
|
' STORES s:scriptvar in .*test_vim9_disassemble.vim.*' ..
|
|
'g:globalvar = ''gv''.*' ..
|
|
' STOREG g:globalvar.*' ..
|
|
'b:buffervar = ''bv''.*' ..
|
|
' STOREB b:buffervar.*' ..
|
|
'w:windowvar = ''wv''.*' ..
|
|
' STOREW w:windowvar.*' ..
|
|
't:tabpagevar = ''tv''.*' ..
|
|
' STORET t:tabpagevar.*' ..
|
|
'&tabstop = 8.*' ..
|
|
' STOREOPT &tabstop.*' ..
|
|
'$ENVVAR = ''ev''.*' ..
|
|
' STOREENV $ENVVAR.*' ..
|
|
'@z = ''rv''.*' ..
|
|
' STOREREG @z.*',
|
|
res)
|
|
enddef
|
|
|
|
def s:ScriptFuncUnlet()
|
|
g:somevar = "value"
|
|
unlet g:somevar
|
|
unlet! g:somevar
|
|
unlet $SOMEVAR
|
|
enddef
|
|
|
|
def Test_disassemble_unlet()
|
|
let res = execute('disass s:ScriptFuncUnlet')
|
|
assert_match('<SNR>\d*_ScriptFuncUnlet.*' ..
|
|
'g:somevar = "value".*' ..
|
|
'\d PUSHS "value".*' ..
|
|
'\d STOREG g:somevar.*' ..
|
|
'unlet g:somevar.*' ..
|
|
'\d UNLET g:somevar.*' ..
|
|
'unlet! g:somevar.*' ..
|
|
'\d UNLET! g:somevar.*' ..
|
|
'unlet $SOMEVAR.*' ..
|
|
'\d UNLETENV $SOMEVAR.*',
|
|
res)
|
|
enddef
|
|
|
|
def s:ScriptFuncTry()
|
|
try
|
|
echo 'yes'
|
|
catch /fail/
|
|
echo 'no'
|
|
finally
|
|
throw 'end'
|
|
endtry
|
|
enddef
|
|
|
|
def Test_disassemble_try()
|
|
let res = execute('disass s:ScriptFuncTry')
|
|
assert_match('<SNR>\d*_ScriptFuncTry.*' ..
|
|
'try.*' ..
|
|
'TRY catch -> \d\+, finally -> \d\+.*' ..
|
|
'catch /fail/.*' ..
|
|
' JUMP -> \d\+.*' ..
|
|
' PUSH v:exception.*' ..
|
|
' PUSHS "fail".*' ..
|
|
' COMPARESTRING =\~.*' ..
|
|
' JUMP_IF_FALSE -> \d\+.*' ..
|
|
' CATCH.*' ..
|
|
'finally.*' ..
|
|
' PUSHS "end".*' ..
|
|
' THROW.*' ..
|
|
'endtry.*' ..
|
|
' ENDTRY.*',
|
|
res)
|
|
enddef
|
|
|
|
def s:ScriptFuncNew()
|
|
let ll = [1, "two", 333]
|
|
let dd = #{one: 1, two: "val"}
|
|
enddef
|
|
|
|
def Test_disassemble_new()
|
|
let res = execute('disass s:ScriptFuncNew')
|
|
assert_match('<SNR>\d*_ScriptFuncNew.*' ..
|
|
'let ll = \[1, "two", 333].*' ..
|
|
'PUSHNR 1.*' ..
|
|
'PUSHS "two".*' ..
|
|
'PUSHNR 333.*' ..
|
|
'NEWLIST size 3.*' ..
|
|
'let dd = #{one: 1, two: "val"}.*' ..
|
|
'PUSHS "one".*' ..
|
|
'PUSHNR 1.*' ..
|
|
'PUSHS "two".*' ..
|
|
'PUSHS "val".*' ..
|
|
'NEWDICT size 2.*',
|
|
res)
|
|
enddef
|
|
|
|
def FuncWithArg(arg: any)
|
|
echo arg
|
|
enddef
|
|
|
|
func UserFunc()
|
|
echo 'nothing'
|
|
endfunc
|
|
|
|
func UserFuncWithArg(arg)
|
|
echo a:arg
|
|
endfunc
|
|
|
|
def s:ScriptFuncCall(): string
|
|
changenr()
|
|
char2nr("abc")
|
|
Test_disassemble_new()
|
|
FuncWithArg(343)
|
|
ScriptFuncNew()
|
|
s:ScriptFuncNew()
|
|
UserFunc()
|
|
UserFuncWithArg("foo")
|
|
let FuncRef = function("UserFunc")
|
|
FuncRef()
|
|
let FuncRefWithArg = function("UserFuncWithArg")
|
|
FuncRefWithArg("bar")
|
|
return "yes"
|
|
enddef
|
|
|
|
def Test_disassemble_call()
|
|
let res = execute('disass s:ScriptFuncCall')
|
|
assert_match('<SNR>\d\+_ScriptFuncCall.*' ..
|
|
'changenr().*' ..
|
|
' BCALL changenr(argc 0).*' ..
|
|
'char2nr("abc").*' ..
|
|
' PUSHS "abc".*' ..
|
|
' BCALL char2nr(argc 1).*' ..
|
|
'Test_disassemble_new().*' ..
|
|
' DCALL Test_disassemble_new(argc 0).*' ..
|
|
'FuncWithArg(343).*' ..
|
|
' PUSHNR 343.*' ..
|
|
' DCALL FuncWithArg(argc 1).*' ..
|
|
'ScriptFuncNew().*' ..
|
|
' DCALL <SNR>\d\+_ScriptFuncNew(argc 0).*' ..
|
|
's:ScriptFuncNew().*' ..
|
|
' DCALL <SNR>\d\+_ScriptFuncNew(argc 0).*' ..
|
|
'UserFunc().*' ..
|
|
' UCALL UserFunc(argc 0).*' ..
|
|
'UserFuncWithArg("foo").*' ..
|
|
' PUSHS "foo".*' ..
|
|
' UCALL UserFuncWithArg(argc 1).*' ..
|
|
'let FuncRef = function("UserFunc").*' ..
|
|
'FuncRef().*' ..
|
|
' LOAD $\d.*' ..
|
|
' PCALL (argc 0).*' ..
|
|
'let FuncRefWithArg = function("UserFuncWithArg").*' ..
|
|
'FuncRefWithArg("bar").*' ..
|
|
' PUSHS "bar".*' ..
|
|
' LOAD $\d.*' ..
|
|
' PCALL (argc 1).*' ..
|
|
'return "yes".*' ..
|
|
' PUSHS "yes".*' ..
|
|
' RETURN.*',
|
|
res)
|
|
enddef
|
|
|
|
def s:CreateRefs()
|
|
let local = 'a'
|
|
def Append(arg: string)
|
|
local ..= arg
|
|
enddef
|
|
g:Append = Append
|
|
def Get(): string
|
|
return local
|
|
enddef
|
|
g:Get = Get
|
|
enddef
|
|
|
|
def Test_disassemble_closure()
|
|
CreateRefs()
|
|
let res = execute('disass g:Append')
|
|
assert_match('<lambda>\d.*' ..
|
|
'local ..= arg.*' ..
|
|
'\d LOADOUTER $0.*' ..
|
|
'\d LOAD arg\[-1\].*' ..
|
|
'\d CONCAT.*' ..
|
|
'\d STOREOUTER $0.*' ..
|
|
'\d PUSHNR 0.*' ..
|
|
'\d RETURN.*',
|
|
res)
|
|
|
|
res = execute('disass g:Get')
|
|
assert_match('<lambda>\d.*' ..
|
|
'return local.*' ..
|
|
'\d LOADOUTER $0.*' ..
|
|
'\d RETURN.*',
|
|
res)
|
|
|
|
unlet g:Append
|
|
unlet g:Get
|
|
enddef
|
|
|
|
|
|
def EchoArg(arg: string): string
|
|
return arg
|
|
enddef
|
|
def RefThis(): func
|
|
return function('EchoArg')
|
|
enddef
|
|
def s:ScriptPCall()
|
|
RefThis()("text")
|
|
enddef
|
|
|
|
def Test_disassemble_pcall()
|
|
let res = execute('disass s:ScriptPCall')
|
|
assert_match('<SNR>\d\+_ScriptPCall.*' ..
|
|
'RefThis()("text").*' ..
|
|
'\d DCALL RefThis(argc 0).*' ..
|
|
'\d PUSHS "text".*' ..
|
|
'\d PCALL top (argc 1).*' ..
|
|
'\d PCALL end.*' ..
|
|
'\d DROP.*' ..
|
|
'\d PUSHNR 0.*' ..
|
|
'\d RETURN.*',
|
|
res)
|
|
enddef
|
|
|
|
|
|
def s:FuncWithForwardCall(): string
|
|
return g:DefinedLater("yes")
|
|
enddef
|
|
|
|
def DefinedLater(arg: string): string
|
|
return arg
|
|
enddef
|
|
|
|
def Test_disassemble_update_instr()
|
|
let res = execute('disass s:FuncWithForwardCall')
|
|
assert_match('FuncWithForwardCall.*' ..
|
|
'return g:DefinedLater("yes").*' ..
|
|
'\d PUSHS "yes".*' ..
|
|
'\d UCALL g:DefinedLater(argc 1).*' ..
|
|
'\d CHECKTYPE string stack\[-1].*' ..
|
|
'\d RETURN.*',
|
|
res)
|
|
|
|
" Calling the function will change UCALL into the faster DCALL
|
|
assert_equal('yes', FuncWithForwardCall())
|
|
|
|
res = execute('disass s:FuncWithForwardCall')
|
|
assert_match('FuncWithForwardCall.*' ..
|
|
'return g:DefinedLater("yes").*' ..
|
|
'\d PUSHS "yes".*' ..
|
|
'\d DCALL DefinedLater(argc 1).*' ..
|
|
'\d CHECKTYPE string stack\[-1].*' ..
|
|
'\d RETURN.*',
|
|
res)
|
|
enddef
|
|
|
|
|
|
def FuncWithDefault(arg: string = 'default'): string
|
|
return arg
|
|
enddef
|
|
|
|
def Test_disassemble_call_default()
|
|
let res = execute('disass FuncWithDefault')
|
|
assert_match('FuncWithDefault.*' ..
|
|
'\d PUSHS "default".*' ..
|
|
'\d STORE arg\[-1].*' ..
|
|
'return arg.*' ..
|
|
'\d LOAD arg\[-1].*' ..
|
|
'\d RETURN.*',
|
|
res)
|
|
enddef
|
|
|
|
|
|
def HasEval()
|
|
if has("eval")
|
|
echo "yes"
|
|
else
|
|
echo "no"
|
|
endif
|
|
enddef
|
|
|
|
def HasNothing()
|
|
if has("nothing")
|
|
echo "yes"
|
|
else
|
|
echo "no"
|
|
endif
|
|
enddef
|
|
|
|
def HasSomething()
|
|
if has("nothing")
|
|
echo "nothing"
|
|
elseif has("something")
|
|
echo "something"
|
|
elseif has("eval")
|
|
echo "eval"
|
|
elseif has("less")
|
|
echo "less"
|
|
endif
|
|
enddef
|
|
|
|
def Test_disassemble_const_expr()
|
|
assert_equal("\nyes", execute('call HasEval()'))
|
|
let instr = execute('disassemble HasEval')
|
|
assert_match('HasEval.*' ..
|
|
'if has("eval").*' ..
|
|
' PUSHS "yes".*',
|
|
instr)
|
|
assert_notmatch('JUMP', instr)
|
|
|
|
assert_equal("\nno", execute('call HasNothing()'))
|
|
instr = execute('disassemble HasNothing')
|
|
assert_match('HasNothing.*' ..
|
|
'if has("nothing").*' ..
|
|
'else.*' ..
|
|
' PUSHS "no".*',
|
|
instr)
|
|
assert_notmatch('PUSHS "yes"', instr)
|
|
assert_notmatch('JUMP', instr)
|
|
|
|
assert_equal("\neval", execute('call HasSomething()'))
|
|
instr = execute('disassemble HasSomething')
|
|
assert_match('HasSomething.*' ..
|
|
'if has("nothing").*' ..
|
|
'elseif has("something").*' ..
|
|
'elseif has("eval").*' ..
|
|
' PUSHS "eval".*' ..
|
|
'elseif has("less").*',
|
|
instr)
|
|
assert_notmatch('PUSHS "nothing"', instr)
|
|
assert_notmatch('PUSHS "something"', instr)
|
|
assert_notmatch('PUSHS "less"', instr)
|
|
assert_notmatch('JUMP', instr)
|
|
enddef
|
|
|
|
def WithFunc()
|
|
let Funky1: func
|
|
let Funky2: func = function("len")
|
|
let Party2: func = funcref("UserFunc")
|
|
enddef
|
|
|
|
def Test_disassemble_function()
|
|
let instr = execute('disassemble WithFunc')
|
|
assert_match('WithFunc.*' ..
|
|
'let Funky1: func.*' ..
|
|
'0 PUSHFUNC "\[none]".*' ..
|
|
'1 STORE $0.*' ..
|
|
'let Funky2: func = function("len").*' ..
|
|
'2 PUSHS "len".*' ..
|
|
'3 BCALL function(argc 1).*' ..
|
|
'4 STORE $1.*' ..
|
|
'let Party2: func = funcref("UserFunc").*' ..
|
|
'\d PUSHS "UserFunc".*' ..
|
|
'\d BCALL funcref(argc 1).*' ..
|
|
'\d STORE $2.*' ..
|
|
'\d PUSHNR 0.*' ..
|
|
'\d RETURN.*',
|
|
instr)
|
|
enddef
|
|
|
|
if has('channel')
|
|
def WithChannel()
|
|
let job1: job
|
|
let job2: job = job_start("donothing")
|
|
let chan1: channel
|
|
enddef
|
|
endif
|
|
|
|
def Test_disassemble_channel()
|
|
CheckFeature channel
|
|
|
|
let instr = execute('disassemble WithChannel')
|
|
assert_match('WithChannel.*' ..
|
|
'let job1: job.*' ..
|
|
'\d PUSHJOB "no process".*' ..
|
|
'\d STORE $0.*' ..
|
|
'let job2: job = job_start("donothing").*' ..
|
|
'\d PUSHS "donothing".*' ..
|
|
'\d BCALL job_start(argc 1).*' ..
|
|
'\d STORE $1.*' ..
|
|
'let chan1: channel.*' ..
|
|
'\d PUSHCHANNEL 0.*' ..
|
|
'\d STORE $2.*' ..
|
|
'\d PUSHNR 0.*' ..
|
|
'\d RETURN.*',
|
|
instr)
|
|
enddef
|
|
|
|
def WithLambda(): string
|
|
let F = {a -> "X" .. a .. "X"}
|
|
return F("x")
|
|
enddef
|
|
|
|
def Test_disassemble_lambda()
|
|
assert_equal("XxX", WithLambda())
|
|
let instr = execute('disassemble WithLambda')
|
|
assert_match('WithLambda.*' ..
|
|
'let F = {a -> "X" .. a .. "X"}.*' ..
|
|
' FUNCREF <lambda>\d\+.*' ..
|
|
'PUSHS "x".*' ..
|
|
' LOAD $0.*' ..
|
|
' PCALL (argc 1).*' ..
|
|
' CHECKTYPE string stack\[-1].*',
|
|
instr)
|
|
enddef
|
|
|
|
def AndOr(arg: any): string
|
|
if arg == 1 && arg != 2 || arg == 4
|
|
return 'yes'
|
|
endif
|
|
return 'no'
|
|
enddef
|
|
|
|
def Test_disassemble_and_or()
|
|
assert_equal("yes", AndOr(1))
|
|
assert_equal("no", AndOr(2))
|
|
assert_equal("yes", AndOr(4))
|
|
let instr = execute('disassemble AndOr')
|
|
assert_match('AndOr.*' ..
|
|
'if arg == 1 && arg != 2 || arg == 4.*' ..
|
|
'\d LOAD arg\[-1].*' ..
|
|
'\d PUSHNR 1.*' ..
|
|
'\d COMPAREANY ==.*' ..
|
|
'\d JUMP_AND_KEEP_IF_FALSE -> \d\+.*' ..
|
|
'\d LOAD arg\[-1].*' ..
|
|
'\d PUSHNR 2.*' ..
|
|
'\d COMPAREANY !=.*' ..
|
|
'\d JUMP_AND_KEEP_IF_TRUE -> \d\+.*' ..
|
|
'\d LOAD arg\[-1].*' ..
|
|
'\d PUSHNR 4.*' ..
|
|
'\d COMPAREANY ==.*' ..
|
|
'\d JUMP_IF_FALSE -> \d\+.*',
|
|
instr)
|
|
enddef
|
|
|
|
def ForLoop(): list<number>
|
|
let res: list<number>
|
|
for i in range(3)
|
|
res->add(i)
|
|
endfor
|
|
return res
|
|
enddef
|
|
|
|
def Test_disassemble_for_loop()
|
|
assert_equal([0, 1, 2], ForLoop())
|
|
let instr = execute('disassemble ForLoop')
|
|
assert_match('ForLoop.*' ..
|
|
'let res: list<number>.*' ..
|
|
' NEWLIST size 0.*' ..
|
|
'\d STORE $0.*' ..
|
|
'for i in range(3).*' ..
|
|
'\d STORE -1 in $1.*' ..
|
|
'\d PUSHNR 3.*' ..
|
|
'\d BCALL range(argc 1).*' ..
|
|
'\d FOR $1 -> \d\+.*' ..
|
|
'\d STORE $2.*' ..
|
|
'res->add(i).*' ..
|
|
'\d LOAD $0.*' ..
|
|
'\d LOAD $2.*' ..
|
|
'\d BCALL add(argc 2).*' ..
|
|
'\d DROP.*' ..
|
|
'endfor.*' ..
|
|
'\d JUMP -> \d\+.*' ..
|
|
'\d DROP.*',
|
|
instr)
|
|
enddef
|
|
|
|
let g:number = 42
|
|
|
|
def Computing()
|
|
let nr = 3
|
|
let nrres = nr + 7
|
|
nrres = nr - 7
|
|
nrres = nr * 7
|
|
nrres = nr / 7
|
|
nrres = nr % 7
|
|
|
|
let anyres = g:number + 7
|
|
anyres = g:number - 7
|
|
anyres = g:number * 7
|
|
anyres = g:number / 7
|
|
anyres = g:number % 7
|
|
|
|
if has('float')
|
|
let fl = 3.0
|
|
let flres = fl + 7.0
|
|
flres = fl - 7.0
|
|
flres = fl * 7.0
|
|
flres = fl / 7.0
|
|
endif
|
|
enddef
|
|
|
|
def Test_disassemble_computing()
|
|
let instr = execute('disassemble Computing')
|
|
assert_match('Computing.*' ..
|
|
'let nr = 3.*' ..
|
|
'\d STORE 3 in $0.*' ..
|
|
'let nrres = nr + 7.*' ..
|
|
'\d LOAD $0.*' ..
|
|
'\d PUSHNR 7.*' ..
|
|
'\d OPNR +.*' ..
|
|
'\d STORE $1.*' ..
|
|
'nrres = nr - 7.*' ..
|
|
'\d OPNR -.*' ..
|
|
'nrres = nr \* 7.*' ..
|
|
'\d OPNR \*.*' ..
|
|
'nrres = nr / 7.*' ..
|
|
'\d OPNR /.*' ..
|
|
'nrres = nr % 7.*' ..
|
|
'\d OPNR %.*' ..
|
|
'let anyres = g:number + 7.*' ..
|
|
'\d LOADG g:number.*' ..
|
|
'\d PUSHNR 7.*' ..
|
|
'\d OPANY +.*' ..
|
|
'\d STORE $2.*' ..
|
|
'anyres = g:number - 7.*' ..
|
|
'\d OPANY -.*' ..
|
|
'anyres = g:number \* 7.*' ..
|
|
'\d OPANY \*.*' ..
|
|
'anyres = g:number / 7.*' ..
|
|
'\d OPANY /.*' ..
|
|
'anyres = g:number % 7.*' ..
|
|
'\d OPANY %.*',
|
|
instr)
|
|
if has('float')
|
|
assert_match('Computing.*' ..
|
|
'let fl = 3.0.*' ..
|
|
'\d PUSHF 3.0.*' ..
|
|
'\d STORE $3.*' ..
|
|
'let flres = fl + 7.0.*' ..
|
|
'\d LOAD $3.*' ..
|
|
'\d PUSHF 7.0.*' ..
|
|
'\d OPFLOAT +.*' ..
|
|
'\d STORE $4.*' ..
|
|
'flres = fl - 7.0.*' ..
|
|
'\d OPFLOAT -.*' ..
|
|
'flres = fl \* 7.0.*' ..
|
|
'\d OPFLOAT \*.*' ..
|
|
'flres = fl / 7.0.*' ..
|
|
'\d OPFLOAT /.*',
|
|
instr)
|
|
endif
|
|
enddef
|
|
|
|
def AddListBlob()
|
|
let reslist = [1, 2] + [3, 4]
|
|
let resblob = 0z1122 + 0z3344
|
|
enddef
|
|
|
|
def Test_disassemble_add_list_blob()
|
|
let instr = execute('disassemble AddListBlob')
|
|
assert_match('AddListBlob.*' ..
|
|
'let reslist = \[1, 2] + \[3, 4].*' ..
|
|
'\d PUSHNR 1.*' ..
|
|
'\d PUSHNR 2.*' ..
|
|
'\d NEWLIST size 2.*' ..
|
|
'\d PUSHNR 3.*' ..
|
|
'\d PUSHNR 4.*' ..
|
|
'\d NEWLIST size 2.*' ..
|
|
'\d ADDLIST.*' ..
|
|
'\d STORE $.*.*' ..
|
|
'let resblob = 0z1122 + 0z3344.*' ..
|
|
'\d PUSHBLOB 0z1122.*' ..
|
|
'\d PUSHBLOB 0z3344.*' ..
|
|
'\d ADDBLOB.*' ..
|
|
'\d STORE $.*',
|
|
instr)
|
|
enddef
|
|
|
|
let g:aa = 'aa'
|
|
def ConcatString(): string
|
|
let res = g:aa .. "bb"
|
|
return res
|
|
enddef
|
|
|
|
def Test_disassemble_concat()
|
|
let instr = execute('disassemble ConcatString')
|
|
assert_match('ConcatString.*' ..
|
|
'let res = g:aa .. "bb".*' ..
|
|
'\d LOADG g:aa.*' ..
|
|
'\d PUSHS "bb".*' ..
|
|
'\d 2STRING stack\[-2].*' ..
|
|
'\d CONCAT.*' ..
|
|
'\d STORE $.*',
|
|
instr)
|
|
assert_equal('aabb', ConcatString())
|
|
enddef
|
|
|
|
def ListIndex(): number
|
|
let l = [1, 2, 3]
|
|
let res = l[1]
|
|
return res
|
|
enddef
|
|
|
|
def Test_disassemble_list_index()
|
|
let instr = execute('disassemble ListIndex')
|
|
assert_match('ListIndex\_s*' ..
|
|
'let l = \[1, 2, 3]\_s*' ..
|
|
'\d PUSHNR 1\_s*' ..
|
|
'\d PUSHNR 2\_s*' ..
|
|
'\d PUSHNR 3\_s*' ..
|
|
'\d NEWLIST size 3\_s*' ..
|
|
'\d STORE $0\_s*' ..
|
|
'let res = l\[1]\_s*' ..
|
|
'\d LOAD $0\_s*' ..
|
|
'\d PUSHNR 1\_s*' ..
|
|
'\d INDEX\_s*' ..
|
|
'\d STORE $1\_s*',
|
|
instr)
|
|
assert_equal(2, ListIndex())
|
|
enddef
|
|
|
|
def DictMember(): number
|
|
let d = #{item: 1}
|
|
let res = d.item
|
|
res = d["item"]
|
|
return res
|
|
enddef
|
|
|
|
def Test_disassemble_dict_member()
|
|
let instr = execute('disassemble DictMember')
|
|
assert_match('DictMember\_s*' ..
|
|
'let d = #{item: 1}\_s*' ..
|
|
'\d PUSHS "item"\_s*' ..
|
|
'\d PUSHNR 1\_s*' ..
|
|
'\d NEWDICT size 1\_s*' ..
|
|
'\d STORE $0\_s*' ..
|
|
'let res = d.item\_s*' ..
|
|
'\d\+ LOAD $0\_s*' ..
|
|
'\d\+ MEMBER item\_s*' ..
|
|
'\d\+ STORE $1\_s*' ..
|
|
'res = d\["item"\]\_s*' ..
|
|
'\d\+ LOAD $0\_s*' ..
|
|
'\d\+ PUSHS "item"\_s*' ..
|
|
'\d\+ MEMBER\_s*' ..
|
|
'\d\+ STORE $1\_s*',
|
|
instr)
|
|
call assert_equal(1, DictMember())
|
|
enddef
|
|
|
|
def NegateNumber(): number
|
|
let nr = 9
|
|
let plus = +nr
|
|
let res = -nr
|
|
return res
|
|
enddef
|
|
|
|
def Test_disassemble_negate_number()
|
|
let instr = execute('disassemble NegateNumber')
|
|
assert_match('NegateNumber\_s*' ..
|
|
'let nr = 9\_s*' ..
|
|
'\d STORE 9 in $0\_s*' ..
|
|
'let plus = +nr\_s*' ..
|
|
'\d LOAD $0\_s*' ..
|
|
'\d CHECKNR\_s*' ..
|
|
'\d STORE $1\_s*' ..
|
|
'let res = -nr\_s*' ..
|
|
'\d LOAD $0\_s*' ..
|
|
'\d NEGATENR\_s*' ..
|
|
'\d STORE $2\_s*',
|
|
instr)
|
|
call assert_equal(-9, NegateNumber())
|
|
enddef
|
|
|
|
def InvertBool(): bool
|
|
let flag = true
|
|
let invert = !flag
|
|
let res = !!flag
|
|
return res
|
|
enddef
|
|
|
|
def Test_disassemble_invert_bool()
|
|
let instr = execute('disassemble InvertBool')
|
|
assert_match('InvertBool\_s*' ..
|
|
'let flag = true\_s*' ..
|
|
'\d PUSH v:true\_s*' ..
|
|
'\d STORE $0\_s*' ..
|
|
'let invert = !flag\_s*' ..
|
|
'\d LOAD $0\_s*' ..
|
|
'\d INVERT (!val)\_s*' ..
|
|
'\d STORE $1\_s*' ..
|
|
'let res = !!flag\_s*' ..
|
|
'\d LOAD $0\_s*' ..
|
|
'\d 2BOOL (!!val)\_s*' ..
|
|
'\d STORE $2\_s*',
|
|
instr)
|
|
call assert_equal(true, InvertBool())
|
|
enddef
|
|
|
|
def Test_disassemble_compare()
|
|
let cases = [
|
|
['true == isFalse', 'COMPAREBOOL =='],
|
|
['true != isFalse', 'COMPAREBOOL !='],
|
|
['v:none == isNull', 'COMPARESPECIAL =='],
|
|
['v:none != isNull', 'COMPARESPECIAL !='],
|
|
|
|
['111 == aNumber', 'COMPARENR =='],
|
|
['111 != aNumber', 'COMPARENR !='],
|
|
['111 > aNumber', 'COMPARENR >'],
|
|
['111 < aNumber', 'COMPARENR <'],
|
|
['111 >= aNumber', 'COMPARENR >='],
|
|
['111 <= aNumber', 'COMPARENR <='],
|
|
['111 =~ aNumber', 'COMPARENR =\~'],
|
|
['111 !~ aNumber', 'COMPARENR !\~'],
|
|
|
|
['"xx" != aString', 'COMPARESTRING !='],
|
|
['"xx" > aString', 'COMPARESTRING >'],
|
|
['"xx" < aString', 'COMPARESTRING <'],
|
|
['"xx" >= aString', 'COMPARESTRING >='],
|
|
['"xx" <= aString', 'COMPARESTRING <='],
|
|
['"xx" =~ aString', 'COMPARESTRING =\~'],
|
|
['"xx" !~ aString', 'COMPARESTRING !\~'],
|
|
['"xx" is aString', 'COMPARESTRING is'],
|
|
['"xx" isnot aString', 'COMPARESTRING isnot'],
|
|
|
|
['0z11 == aBlob', 'COMPAREBLOB =='],
|
|
['0z11 != aBlob', 'COMPAREBLOB !='],
|
|
['0z11 is aBlob', 'COMPAREBLOB is'],
|
|
['0z11 isnot aBlob', 'COMPAREBLOB isnot'],
|
|
|
|
['[1, 2] == aList', 'COMPARELIST =='],
|
|
['[1, 2] != aList', 'COMPARELIST !='],
|
|
['[1, 2] is aList', 'COMPARELIST is'],
|
|
['[1, 2] isnot aList', 'COMPARELIST isnot'],
|
|
|
|
['#{a: 1} == aDict', 'COMPAREDICT =='],
|
|
['#{a: 1} != aDict', 'COMPAREDICT !='],
|
|
['#{a: 1} is aDict', 'COMPAREDICT is'],
|
|
['#{a: 1} isnot aDict', 'COMPAREDICT isnot'],
|
|
|
|
['{->33} == {->44}', 'COMPAREFUNC =='],
|
|
['{->33} != {->44}', 'COMPAREFUNC !='],
|
|
['{->33} is {->44}', 'COMPAREFUNC is'],
|
|
['{->33} isnot {->44}', 'COMPAREFUNC isnot'],
|
|
|
|
['77 == g:xx', 'COMPAREANY =='],
|
|
['77 != g:xx', 'COMPAREANY !='],
|
|
['77 > g:xx', 'COMPAREANY >'],
|
|
['77 < g:xx', 'COMPAREANY <'],
|
|
['77 >= g:xx', 'COMPAREANY >='],
|
|
['77 <= g:xx', 'COMPAREANY <='],
|
|
['77 =~ g:xx', 'COMPAREANY =\~'],
|
|
['77 !~ g:xx', 'COMPAREANY !\~'],
|
|
['77 is g:xx', 'COMPAREANY is'],
|
|
['77 isnot g:xx', 'COMPAREANY isnot'],
|
|
]
|
|
let floatDecl = ''
|
|
if has('float')
|
|
cases->extend([
|
|
['1.1 == aFloat', 'COMPAREFLOAT =='],
|
|
['1.1 != aFloat', 'COMPAREFLOAT !='],
|
|
['1.1 > aFloat', 'COMPAREFLOAT >'],
|
|
['1.1 < aFloat', 'COMPAREFLOAT <'],
|
|
['1.1 >= aFloat', 'COMPAREFLOAT >='],
|
|
['1.1 <= aFloat', 'COMPAREFLOAT <='],
|
|
['1.1 =~ aFloat', 'COMPAREFLOAT =\~'],
|
|
['1.1 !~ aFloat', 'COMPAREFLOAT !\~'],
|
|
])
|
|
floatDecl = 'let aFloat = 2.2'
|
|
endif
|
|
|
|
let nr = 1
|
|
for case in cases
|
|
" declare local variables to get a non-constant with the right type
|
|
writefile(['def TestCase' .. nr .. '()',
|
|
' let isFalse = false',
|
|
' let isNull = v:null',
|
|
' let aNumber = 222',
|
|
' let aString = "yy"',
|
|
' let aBlob = 0z22',
|
|
' let aList = [3, 4]',
|
|
' let aDict = #{x: 2}',
|
|
floatDecl,
|
|
' if ' .. case[0],
|
|
' echo 42'
|
|
' endif',
|
|
'enddef'], 'Xdisassemble')
|
|
source Xdisassemble
|
|
let instr = execute('disassemble TestCase' .. nr)
|
|
assert_match('TestCase' .. nr .. '.*' ..
|
|
'if ' .. substitute(case[0], '[[~]', '\\\0', 'g') .. '.*' ..
|
|
'\d \(PUSH\|FUNCREF\).*' ..
|
|
'\d \(PUSH\|FUNCREF\|LOAD\).*' ..
|
|
'\d ' .. case[1] .. '.*' ..
|
|
'\d JUMP_IF_FALSE -> \d\+.*',
|
|
instr)
|
|
|
|
nr += 1
|
|
endfor
|
|
|
|
delete('Xdisassemble')
|
|
enddef
|
|
|
|
def Test_disassemble_compare_const()
|
|
let cases = [
|
|
['"xx" == "yy"', false],
|
|
['"aa" == "aa"', true],
|
|
['has("eval") ? true : false', true],
|
|
['has("asdf") ? true : false', false],
|
|
]
|
|
|
|
let nr = 1
|
|
for case in cases
|
|
writefile(['def TestCase' .. nr .. '()',
|
|
' if ' .. case[0],
|
|
' echo 42'
|
|
' endif',
|
|
'enddef'], 'Xdisassemble')
|
|
source Xdisassemble
|
|
let instr = execute('disassemble TestCase' .. nr)
|
|
if case[1]
|
|
" condition true, "echo 42" executed
|
|
assert_match('TestCase' .. nr .. '.*' ..
|
|
'if ' .. substitute(case[0], '[[~]', '\\\0', 'g') .. '.*' ..
|
|
'\d PUSHNR 42.*' ..
|
|
'\d ECHO 1.*' ..
|
|
'\d PUSHNR 0.*' ..
|
|
'\d RETURN.*',
|
|
instr)
|
|
else
|
|
" condition false, function just returns
|
|
assert_match('TestCase' .. nr .. '.*' ..
|
|
'if ' .. substitute(case[0], '[[~]', '\\\0', 'g') .. '[ \n]*' ..
|
|
'echo 42[ \n]*' ..
|
|
'endif[ \n]*' ..
|
|
'\s*\d PUSHNR 0.*' ..
|
|
'\d RETURN.*',
|
|
instr)
|
|
endif
|
|
|
|
nr += 1
|
|
endfor
|
|
|
|
delete('Xdisassemble')
|
|
enddef
|
|
|
|
def s:Execute()
|
|
execute 'help vim9.txt'
|
|
let cmd = 'help vim9.txt'
|
|
execute cmd
|
|
let tag = 'vim9.txt'
|
|
execute 'help ' .. tag
|
|
enddef
|
|
|
|
def Test_disassemble_execute()
|
|
let res = execute('disass s:Execute')
|
|
assert_match('\<SNR>\d*_Execute\_s*' ..
|
|
"execute 'help vim9.txt'\\_s*" ..
|
|
'\d PUSHS "help vim9.txt"\_s*' ..
|
|
'\d EXECUTE 1\_s*' ..
|
|
"let cmd = 'help vim9.txt'\\_s*" ..
|
|
'\d PUSHS "help vim9.txt"\_s*' ..
|
|
'\d STORE $0\_s*' ..
|
|
'execute cmd\_s*' ..
|
|
'\d LOAD $0\_s*' ..
|
|
'\d EXECUTE 1\_s*' ..
|
|
"let tag = 'vim9.txt'\\_s*" ..
|
|
'\d PUSHS "vim9.txt"\_s*' ..
|
|
'\d STORE $1\_s*' ..
|
|
"execute 'help ' .. tag\\_s*" ..
|
|
'\d\+ PUSHS "help "\_s*' ..
|
|
'\d\+ LOAD $1\_s*' ..
|
|
'\d\+ CONCAT\_s*' ..
|
|
'\d\+ EXECUTE 1\_s*' ..
|
|
'\d\+ PUSHNR 0\_s*' ..
|
|
'\d\+ RETURN',
|
|
res)
|
|
enddef
|
|
|
|
def s:Echomsg()
|
|
echomsg 'some' 'message'
|
|
echoerr 'went' .. 'wrong'
|
|
enddef
|
|
|
|
def Test_disassemble_echomsg()
|
|
let res = execute('disass s:Echomsg')
|
|
assert_match('\<SNR>\d*_Echomsg\_s*' ..
|
|
"echomsg 'some' 'message'\\_s*" ..
|
|
'\d PUSHS "some"\_s*' ..
|
|
'\d PUSHS "message"\_s*' ..
|
|
'\d ECHOMSG 2\_s*' ..
|
|
"echoerr 'went' .. 'wrong'\\_s*" ..
|
|
'\d PUSHS "wentwrong"\_s*' ..
|
|
'\d ECHOERR 1\_s*' ..
|
|
'\d PUSHNR 0\_s*' ..
|
|
'\d RETURN',
|
|
res)
|
|
enddef
|
|
|
|
def SomeStringArg(arg: string)
|
|
echo arg
|
|
enddef
|
|
|
|
def SomeAnyArg(arg: any)
|
|
echo arg
|
|
enddef
|
|
|
|
def SomeStringArgAndReturn(arg: string): string
|
|
return arg
|
|
enddef
|
|
|
|
def Test_display_func()
|
|
let res1 = execute('function SomeStringArg')
|
|
assert_match('.* def SomeStringArg(arg: string)\_s*' ..
|
|
'\d *echo arg.*' ..
|
|
' *enddef',
|
|
res1)
|
|
|
|
let res2 = execute('function SomeAnyArg')
|
|
assert_match('.* def SomeAnyArg(arg: any)\_s*' ..
|
|
'\d *echo arg\_s*' ..
|
|
' *enddef',
|
|
res2)
|
|
|
|
let res3 = execute('function SomeStringArgAndReturn')
|
|
assert_match('.* def SomeStringArgAndReturn(arg: string): string\_s*' ..
|
|
'\d *return arg\_s*' ..
|
|
' *enddef',
|
|
res3)
|
|
enddef
|
|
|
|
def Test_vim9script_forward_func()
|
|
let lines =<< trim END
|
|
vim9script
|
|
def FuncOne(): string
|
|
return FuncTwo()
|
|
enddef
|
|
def FuncTwo(): string
|
|
return 'two'
|
|
enddef
|
|
let g:res_FuncOne: string = execute('disass FuncOne')
|
|
END
|
|
writefile(lines, 'Xdisassemble')
|
|
source Xdisassemble
|
|
|
|
" check that the first function calls the second with DCALL
|
|
assert_match('\<SNR>\d*_FuncOne\_s*' ..
|
|
'return FuncTwo()\_s*' ..
|
|
'\d DCALL <SNR>\d\+_FuncTwo(argc 0)\_s*' ..
|
|
'\d RETURN',
|
|
g:res_FuncOne)
|
|
|
|
delete('Xdisassemble')
|
|
unlet g:res_FuncOne
|
|
enddef
|
|
|
|
def s:ConcatStrings(): string
|
|
return 'one' .. 'two' .. 'three'
|
|
enddef
|
|
|
|
def s:ComputeConst(): number
|
|
return 2 + 3 * 4 / 6 + 7
|
|
enddef
|
|
|
|
def s:ComputeConstParen(): number
|
|
return ((2 + 4) * (8 / 2)) / (3 + 4)
|
|
enddef
|
|
|
|
def Test_simplify_const_expr()
|
|
let res = execute('disass s:ConcatStrings')
|
|
assert_match('<SNR>\d*_ConcatStrings\_s*' ..
|
|
"return 'one' .. 'two' .. 'three'\\_s*" ..
|
|
'\d PUSHS "onetwothree"\_s*' ..
|
|
'\d RETURN',
|
|
res)
|
|
|
|
res = execute('disass s:ComputeConst')
|
|
assert_match('<SNR>\d*_ComputeConst\_s*' ..
|
|
'return 2 + 3 \* 4 / 6 + 7\_s*' ..
|
|
'\d PUSHNR 11\_s*' ..
|
|
'\d RETURN',
|
|
res)
|
|
|
|
res = execute('disass s:ComputeConstParen')
|
|
assert_match('<SNR>\d*_ComputeConstParen\_s*' ..
|
|
'return ((2 + 4) \* (8 / 2)) / (3 + 4)\_s*' ..
|
|
'\d PUSHNR 3\>\_s*' ..
|
|
'\d RETURN',
|
|
res)
|
|
enddef
|
|
|
|
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
|