" 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) var local = 1 buffers echo echo arg echo local echo &lines echo v:version echo s:scriptvar echo g:globalvar echo get(g:, "global") echo g:auto#var echo b:buffervar echo get(b:, "buffer") echo w:windowvar echo get(w:, "window") echo t:tabpagevar echo get(t:, "tab") echo &tabstop echo $ENVVAR echo @z enddef def Test_disassemble_load() assert_fails('disass NoFunc', 'E1061:') assert_fails('disass NotCompiled', 'E1091:') assert_fails('disass', 'E471:') assert_fails('disass [', 'E475:') assert_fails('disass 234', 'E129:') assert_fails('disass foo', 'E129:') var res = execute('disass s:ScriptFuncLoad') assert_match('\d*_ScriptFuncLoad.*' .. 'buffers\_s*' .. '\d\+ EXEC \+buffers\_s*' .. 'echo\_s*' .. 'echo arg\_s*' .. '\d\+ LOAD arg\[-1\]\_s*' .. '\d\+ ECHO 1\_s*' .. 'echo local\_s*' .. '\d\+ LOAD $0\_s*' .. '\d\+ ECHO 1\_s*' .. 'echo &lines\_s*' .. '\d\+ LOADOPT &lines\_s*' .. '\d\+ ECHO 1\_s*' .. 'echo v:version\_s*' .. '\d\+ LOADV v:version\_s*' .. '\d\+ ECHO 1\_s*' .. 'echo s:scriptvar\_s*' .. '\d\+ LOADS s:scriptvar from .*test_vim9_disassemble.vim\_s*' .. '\d\+ ECHO 1\_s*' .. 'echo g:globalvar\_s*' .. '\d\+ LOADG g:globalvar\_s*' .. '\d\+ ECHO 1\_s*' .. 'echo get(g:, "global")\_s*' .. '\d\+ LOAD g:\_s*' .. '\d\+ PUSHS "global"\_s*' .. '\d\+ BCALL get(argc 2)\_s*' .. '\d\+ ECHO 1\_s*' .. 'echo g:auto#var\_s*' .. '\d\+ LOADAUTO g:auto#var\_s*' .. '\d\+ ECHO 1\_s*' .. 'echo b:buffervar\_s*' .. '\d\+ LOADB b:buffervar\_s*' .. '\d\+ ECHO 1\_s*' .. 'echo get(b:, "buffer")\_s*' .. '\d\+ LOAD b:\_s*' .. '\d\+ PUSHS "buffer"\_s*' .. '\d\+ BCALL get(argc 2).*' .. ' LOADW w:windowvar.*' .. 'echo get(w:, "window")\_s*' .. '\d\+ LOAD w:\_s*' .. '\d\+ PUSHS "window"\_s*' .. '\d\+ BCALL get(argc 2).*' .. ' LOADT t:tabpagevar.*' .. 'echo get(t:, "tab")\_s*' .. '\d\+ LOAD t:\_s*' .. '\d\+ PUSHS "tab"\_s*' .. '\d\+ BCALL get(argc 2).*' .. ' LOADENV $ENVVAR.*' .. ' LOADREG @z.*', res) enddef def s:EditExpand() var filename = "file" var filenr = 123 edit the`=filename``=filenr`.txt enddef def Test_disassemble_exec_expr() var res = execute('disass s:EditExpand') assert_match('\d*_EditExpand\_s*' .. ' var filename = "file"\_s*' .. '\d PUSHS "file"\_s*' .. '\d STORE $0\_s*' .. ' var filenr = 123\_s*' .. '\d STORE 123 in $1\_s*' .. ' edit the`=filename``=filenr`.txt\_s*' .. '\d PUSHS "edit the"\_s*' .. '\d LOAD $0\_s*' .. '\d LOAD $1\_s*' .. '\d 2STRING stack\[-1\]\_s*' .. '\d\+ PUSHS ".txt"\_s*' .. '\d\+ EXECCONCAT 4\_s*' .. '\d\+ RETURN void', res) enddef if has('python3') def s:PyHeredoc() python3 << EOF print('hello') EOF enddef def Test_disassemble_python_heredoc() var res = execute('disass s:PyHeredoc') assert_match('\d*_PyHeredoc.*' .. " python3 << EOF^@ print('hello')^@EOF\\_s*" .. '\d EXEC_SPLIT python3 << EOF^@ print(''hello'')^@EOF\_s*' .. '\d RETURN void', res) enddef endif def s:Substitute() var expr = "abc" :%s/a/\=expr/&g#c enddef def Test_disassemble_substitute() var res = execute('disass s:Substitute') assert_match('\d*_Substitute.*' .. ' var expr = "abc"\_s*' .. '\d PUSHS "abc"\_s*' .. '\d STORE $0\_s*' .. ' :%s/a/\\=expr/&g#c\_s*' .. '\d SUBSTITUTE :%s/a/\\=expr/&g#c\_s*' .. ' 0 LOAD $0\_s*' .. ' -------------\_s*' .. '\d RETURN void', res) enddef def s:SearchPair() var col = 8 searchpair("{", "", "}", "", "col('.') > col") enddef def Test_disassemble_seachpair() var res = execute('disass s:SearchPair') assert_match('\d*_SearchPair.*' .. ' var col = 8\_s*' .. '\d STORE 8 in $0\_s*' .. ' searchpair("{", "", "}", "", "col(''.'') > col")\_s*' .. '\d PUSHS "{"\_s*' .. '\d PUSHS ""\_s*' .. '\d PUSHS "}"\_s*' .. '\d PUSHS ""\_s*' .. '\d INSTR\_s*' .. ' 0 PUSHS "."\_s*' .. ' 1 BCALL col(argc 1)\_s*' .. ' 2 LOAD $0\_s*' .. ' 3 COMPARENR >\_s*' .. ' -------------\_s*' .. '\d BCALL searchpair(argc 5)\_s*' .. '\d DROP\_s*' .. '\d RETURN void', res) enddef def s:RedirVar() var result: string redir =>> result echo "text" redir END enddef def Test_disassemble_redir_var() var res = execute('disass s:RedirVar') assert_match('\d*_RedirVar.*' .. ' var result: string\_s*' .. '\d PUSHS "\[NULL\]"\_s*' .. '\d STORE $0\_s*' .. ' redir =>> result\_s*' .. '\d REDIR\_s*' .. ' echo "text"\_s*' .. '\d PUSHS "text"\_s*' .. '\d ECHO 1\_s*' .. ' redir END\_s*' .. '\d LOAD $0\_s*' .. '\d REDIR END\_s*' .. '\d CONCAT\_s*' .. '\d STORE $0\_s*' .. '\d RETURN void', res) enddef def s:Cexpr() var errors = "list of errors" cexpr errors enddef def Test_disassemble_cexpr() var res = execute('disass s:Cexpr') assert_match('\d*_Cexpr.*' .. ' var errors = "list of errors"\_s*' .. '\d PUSHS "list of errors"\_s*' .. '\d STORE $0\_s*' .. ' cexpr errors\_s*' .. '\d CEXPR pre cexpr\_s*' .. '\d LOAD $0\_s*' .. '\d CEXPR core cexpr "cexpr errors"\_s*' .. '\d RETURN void', res) enddef def s:YankRange() norm! m[jjm] :'[,']yank enddef def Test_disassemble_yank_range() var res = execute('disass s:YankRange') assert_match('\d*_YankRange.*' .. ' norm! m\[jjm\]\_s*' .. '\d EXEC norm! m\[jjm\]\_s*' .. ' :''\[,''\]yank\_s*' .. '\d EXEC :''\[,''\]yank\_s*' .. '\d RETURN void', res) enddef def s:PutExpr() :3put ="text" enddef def Test_disassemble_put_expr() var res = execute('disass s:PutExpr') assert_match('\d*_PutExpr.*' .. ' :3put ="text"\_s*' .. '\d PUSHS "text"\_s*' .. '\d PUT = 3\_s*' .. '\d RETURN void', res) enddef def s:PutRange() :$-2put a enddef def Test_disassemble_put_range() var res = execute('disass s:PutRange') assert_match('\d*_PutRange.*' .. ' :$-2put a\_s*' .. '\d RANGE $-2\_s*' .. '\d PUT a range\_s*' .. '\d RETURN void', res) enddef def s:ScriptFuncPush() var localbool = true var localspec = v:none var localblob = 0z1234 if has('float') var localfloat = 1.234 endif enddef def Test_disassemble_push() var res = execute('disass s:ScriptFuncPush') assert_match('\d*_ScriptFuncPush.*' .. 'localbool = true.*' .. ' PUSH true.*' .. 'localspec = v:none.*' .. ' PUSH v:none.*' .. 'localblob = 0z1234.*' .. ' PUSHBLOB 0z1234.*', res) if has('float') assert_match('\d*_ScriptFuncPush.*' .. 'localfloat = 1.234.*' .. ' PUSHF 1.234.*', res) endif enddef def s:ScriptFuncStore() var localnr = 1 localnr = 2 var localstr = 'abc' localstr = 'xyz' v:char = 'abc' s:scriptvar = 'sv' g:globalvar = 'gv' g:auto#var = 'av' b:buffervar = 'bv' w:windowvar = 'wv' t:tabpagevar = 'tv' &tabstop = 8 $ENVVAR = 'ev' @z = 'rv' enddef def Test_disassemble_store() var res = execute('disass s:ScriptFuncStore') assert_match('\d*_ScriptFuncStore.*' .. 'var localnr = 1.*' .. 'localnr = 2.*' .. ' STORE 2 in $0.*' .. 'var 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.*' .. 'g:auto#var = ''av''.*' .. ' STOREAUTO g:auto#var.*' .. '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:ScriptFuncStoreMember() var locallist: list = [] locallist[0] = 123 var localdict: dict = {} localdict["a"] = 456 var localblob: blob = 0z1122 localblob[1] = 33 enddef def Test_disassemble_store_member() var res = execute('disass s:ScriptFuncStoreMember') assert_match('\d*_ScriptFuncStoreMember\_s*' .. 'var locallist: list = []\_s*' .. '\d NEWLIST size 0\_s*' .. '\d SETTYPE list\_s*' .. '\d STORE $0\_s*' .. 'locallist\[0\] = 123\_s*' .. '\d PUSHNR 123\_s*' .. '\d PUSHNR 0\_s*' .. '\d LOAD $0\_s*' .. '\d STOREINDEX list\_s*' .. 'var localdict: dict = {}\_s*' .. '\d NEWDICT size 0\_s*' .. '\d SETTYPE dict\_s*' .. '\d STORE $1\_s*' .. 'localdict\["a"\] = 456\_s*' .. '\d\+ PUSHNR 456\_s*' .. '\d\+ PUSHS "a"\_s*' .. '\d\+ LOAD $1\_s*' .. '\d\+ STOREINDEX dict\_s*' .. 'var localblob: blob = 0z1122\_s*' .. '\d\+ PUSHBLOB 0z1122\_s*' .. '\d\+ STORE $2\_s*' .. 'localblob\[1\] = 33\_s*' .. '\d\+ PUSHNR 33\_s*' .. '\d\+ PUSHNR 1\_s*' .. '\d\+ LOAD $2\_s*' .. '\d\+ STOREINDEX blob\_s*' .. '\d\+ RETURN void', res) enddef def s:ScriptFuncStoreIndex() var d = {dd: {}} d.dd[0] = 0 enddef def Test_disassemble_store_index() var res = execute('disass s:ScriptFuncStoreIndex') assert_match('\d*_ScriptFuncStoreIndex\_s*' .. 'var d = {dd: {}}\_s*' .. '\d PUSHS "dd"\_s*' .. '\d NEWDICT size 0\_s*' .. '\d NEWDICT size 1\_s*' .. '\d STORE $0\_s*' .. 'd.dd\[0\] = 0\_s*' .. '\d PUSHNR 0\_s*' .. '\d PUSHNR 0\_s*' .. '\d LOAD $0\_s*' .. '\d MEMBER dd\_s*' .. '\d STOREINDEX any\_s*' .. '\d\+ RETURN void', res) enddef def s:ListAssign() var x: string var y: string var l: list [x, y; l] = g:stringlist enddef def Test_disassemble_list_assign() var res = execute('disass s:ListAssign') assert_match('\d*_ListAssign\_s*' .. 'var x: string\_s*' .. '\d PUSHS "\[NULL\]"\_s*' .. '\d STORE $0\_s*' .. 'var y: string\_s*' .. '\d PUSHS "\[NULL\]"\_s*' .. '\d STORE $1\_s*' .. 'var l: list\_s*' .. '\d NEWLIST size 0\_s*' .. '\d SETTYPE list\_s*' .. '\d STORE $2\_s*' .. '\[x, y; l\] = g:stringlist\_s*' .. '\d LOADG g:stringlist\_s*' .. '\d CHECKTYPE list stack\[-1\]\_s*' .. '\d CHECKLEN >= 2\_s*' .. '\d\+ ITEM 0\_s*' .. '\d\+ CHECKTYPE string stack\[-1\] arg 1\_s*' .. '\d\+ STORE $0\_s*' .. '\d\+ ITEM 1\_s*' .. '\d\+ CHECKTYPE string stack\[-1\] arg 2\_s*' .. '\d\+ STORE $1\_s*' .. '\d\+ SLICE 2\_s*' .. '\d\+ STORE $2\_s*' .. '\d\+ RETURN void', res) enddef def s:ListAssignWithOp() var a = 2 var b = 3 [a, b] += [4, 5] enddef def Test_disassemble_list_assign_with_op() var res = execute('disass s:ListAssignWithOp') assert_match('\d*_ListAssignWithOp\_s*' .. 'var a = 2\_s*' .. '\d STORE 2 in $0\_s*' .. 'var b = 3\_s*' .. '\d STORE 3 in $1\_s*' .. '\[a, b\] += \[4, 5\]\_s*' .. '\d\+ PUSHNR 4\_s*' .. '\d\+ PUSHNR 5\_s*' .. '\d\+ NEWLIST size 2\_s*' .. '\d\+ CHECKLEN 2\_s*' .. '\d\+ LOAD $0\_s*' .. '\d\+ ITEM 0 with op\_s*' .. '\d\+ OPNR +\_s*' .. '\d\+ STORE $0\_s*' .. '\d\+ LOAD $1\_s*' .. '\d\+ ITEM 1 with op\_s*' .. '\d\+ OPNR +\_s*' .. '\d\+ STORE $1\_s*' .. '\d\+ DROP\_s*' .. '\d\+ RETURN void', res) enddef def s:ListAdd() var l: list = [] add(l, 123) add(l, g:aNumber) enddef def Test_disassemble_list_add() var res = execute('disass s:ListAdd') assert_match('\d*_ListAdd\_s*' .. 'var l: list = []\_s*' .. '\d NEWLIST size 0\_s*' .. '\d SETTYPE list\_s*' .. '\d STORE $0\_s*' .. 'add(l, 123)\_s*' .. '\d LOAD $0\_s*' .. '\d PUSHNR 123\_s*' .. '\d LISTAPPEND\_s*' .. '\d DROP\_s*' .. 'add(l, g:aNumber)\_s*' .. '\d LOAD $0\_s*' .. '\d\+ LOADG g:aNumber\_s*' .. '\d\+ CHECKTYPE number stack\[-1\]\_s*' .. '\d\+ LISTAPPEND\_s*' .. '\d\+ DROP\_s*' .. '\d\+ RETURN void', res) enddef def s:BlobAdd() var b: blob = 0z add(b, 123) add(b, g:aNumber) enddef def Test_disassemble_blob_add() var res = execute('disass s:BlobAdd') assert_match('\d*_BlobAdd\_s*' .. 'var b: blob = 0z\_s*' .. '\d PUSHBLOB 0z\_s*' .. '\d STORE $0\_s*' .. 'add(b, 123)\_s*' .. '\d LOAD $0\_s*' .. '\d PUSHNR 123\_s*' .. '\d BLOBAPPEND\_s*' .. '\d DROP\_s*' .. 'add(b, g:aNumber)\_s*' .. '\d LOAD $0\_s*' .. '\d\+ LOADG g:aNumber\_s*' .. '\d\+ CHECKTYPE number stack\[-1\]\_s*' .. '\d\+ BLOBAPPEND\_s*' .. '\d\+ DROP\_s*' .. '\d\+ RETURN void', res) enddef def s:BlobIndexSlice() var b: blob = 0z112233 echo b[1] echo b[1 : 2] enddef def Test_disassemble_blob_index_slice() var res = execute('disass s:BlobIndexSlice') assert_match('\d*_BlobIndexSlice\_s*' .. 'var b: blob = 0z112233\_s*' .. '\d PUSHBLOB 0z112233\_s*' .. '\d STORE $0\_s*' .. 'echo b\[1\]\_s*' .. '\d LOAD $0\_s*' .. '\d PUSHNR 1\_s*' .. '\d BLOBINDEX\_s*' .. '\d ECHO 1\_s*' .. 'echo b\[1 : 2\]\_s*' .. '\d LOAD $0\_s*' .. '\d PUSHNR 1\_s*' .. '\d\+ PUSHNR 2\_s*' .. '\d\+ BLOBSLICE\_s*' .. '\d\+ ECHO 1\_s*' .. '\d\+ RETURN void', res) enddef def s:ScriptFuncUnlet() g:somevar = "value" unlet g:somevar unlet! g:somevar unlet $SOMEVAR enddef def Test_disassemble_unlet() var res = execute('disass s:ScriptFuncUnlet') assert_match('\d*_ScriptFuncUnlet\_s*' .. 'g:somevar = "value"\_s*' .. '\d PUSHS "value"\_s*' .. '\d STOREG g:somevar\_s*' .. 'unlet g:somevar\_s*' .. '\d UNLET g:somevar\_s*' .. 'unlet! g:somevar\_s*' .. '\d UNLET! g:somevar\_s*' .. 'unlet $SOMEVAR\_s*' .. '\d UNLETENV $SOMEVAR\_s*', res) enddef def s:LockLocal() var d = {a: 1} lockvar d.a enddef def Test_disassemble_locl_local() var res = execute('disass s:LockLocal') assert_match('\d*_LockLocal\_s*' .. 'var d = {a: 1}\_s*' .. '\d PUSHS "a"\_s*' .. '\d PUSHNR 1\_s*' .. '\d NEWDICT size 1\_s*' .. '\d STORE $0\_s*' .. 'lockvar d.a\_s*' .. '\d LOAD $0\_s*' .. '\d LOCKUNLOCK lockvar d.a\_s*', res) enddef def s:ScriptFuncTry() try echo "yes" catch /fail/ echo "no" finally throw "end" endtry enddef def Test_disassemble_try() var res = execute('disass s:ScriptFuncTry') assert_match('\d*_ScriptFuncTry\_s*' .. 'try\_s*' .. '\d TRY catch -> \d\+, finally -> \d\+, endtry -> \d\+\_s*' .. 'echo "yes"\_s*' .. '\d PUSHS "yes"\_s*' .. '\d ECHO 1\_s*' .. 'catch /fail/\_s*' .. '\d JUMP -> \d\+\_s*' .. '\d PUSH v:exception\_s*' .. '\d PUSHS "fail"\_s*' .. '\d COMPARESTRING =\~\_s*' .. '\d JUMP_IF_FALSE -> \d\+\_s*' .. '\d CATCH\_s*' .. 'echo "no"\_s*' .. '\d\+ PUSHS "no"\_s*' .. '\d\+ ECHO 1\_s*' .. 'finally\_s*' .. '\d\+ FINALLY\_s*' .. 'throw "end"\_s*' .. '\d\+ PUSHS "end"\_s*' .. '\d\+ THROW\_s*' .. 'endtry\_s*' .. '\d\+ ENDTRY', res) enddef def s:ScriptFuncNew() var ll = [1, "two", 333] var dd = {one: 1, two: "val"} enddef def Test_disassemble_new() var res = execute('disass s:ScriptFuncNew') assert_match('\d*_ScriptFuncNew\_s*' .. 'var ll = \[1, "two", 333\]\_s*' .. '\d PUSHNR 1\_s*' .. '\d PUSHS "two"\_s*' .. '\d PUSHNR 333\_s*' .. '\d NEWLIST size 3\_s*' .. '\d STORE $0\_s*' .. 'var dd = {one: 1, two: "val"}\_s*' .. '\d PUSHS "one"\_s*' .. '\d PUSHNR 1\_s*' .. '\d PUSHS "two"\_s*' .. '\d PUSHS "val"\_s*' .. '\d NEWDICT size 2\_s*', 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") var FuncRef = function("UserFunc") FuncRef() var FuncRefWithArg = function("UserFuncWithArg") FuncRefWithArg("bar") return "yes" enddef def Test_disassemble_call() var res = execute('disass s:ScriptFuncCall') assert_match('\d\+_ScriptFuncCall\_s*' .. 'changenr()\_s*' .. '\d BCALL changenr(argc 0)\_s*' .. '\d DROP\_s*' .. 'char2nr("abc")\_s*' .. '\d PUSHS "abc"\_s*' .. '\d BCALL char2nr(argc 1)\_s*' .. '\d DROP\_s*' .. 'Test_disassemble_new()\_s*' .. '\d DCALL Test_disassemble_new(argc 0)\_s*' .. '\d DROP\_s*' .. 'FuncWithArg(343)\_s*' .. '\d\+ PUSHNR 343\_s*' .. '\d\+ DCALL FuncWithArg(argc 1)\_s*' .. '\d\+ DROP\_s*' .. 'ScriptFuncNew()\_s*' .. '\d\+ DCALL \d\+_ScriptFuncNew(argc 0)\_s*' .. '\d\+ DROP\_s*' .. 's:ScriptFuncNew()\_s*' .. '\d\+ DCALL \d\+_ScriptFuncNew(argc 0)\_s*' .. '\d\+ DROP\_s*' .. 'UserFunc()\_s*' .. '\d\+ UCALL UserFunc(argc 0)\_s*' .. '\d\+ DROP\_s*' .. 'UserFuncWithArg("foo")\_s*' .. '\d\+ PUSHS "foo"\_s*' .. '\d\+ UCALL UserFuncWithArg(argc 1)\_s*' .. '\d\+ DROP\_s*' .. 'var FuncRef = function("UserFunc")\_s*' .. '\d\+ PUSHS "UserFunc"\_s*' .. '\d\+ BCALL function(argc 1)\_s*' .. '\d\+ STORE $0\_s*' .. 'FuncRef()\_s*' .. '\d\+ LOAD $\d\_s*' .. '\d\+ PCALL (argc 0)\_s*' .. '\d\+ DROP\_s*' .. 'var FuncRefWithArg = function("UserFuncWithArg")\_s*' .. '\d\+ PUSHS "UserFuncWithArg"\_s*' .. '\d\+ BCALL function(argc 1)\_s*' .. '\d\+ STORE $1\_s*' .. 'FuncRefWithArg("bar")\_s*' .. '\d\+ PUSHS "bar"\_s*' .. '\d\+ LOAD $\d\_s*' .. '\d\+ PCALL (argc 1)\_s*' .. '\d\+ DROP\_s*' .. 'return "yes"\_s*' .. '\d\+ PUSHS "yes"\_s*' .. '\d\+ RETURN', res) enddef def s:CreateRefs() var 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() var res = execute('disass g:Append') assert_match('\d\_s*' .. 'local ..= arg\_s*' .. '\d LOADOUTER level 1 $0\_s*' .. '\d LOAD arg\[-1\]\_s*' .. '\d CONCAT\_s*' .. '\d STOREOUTER level 1 $0\_s*' .. '\d RETURN void', res) res = execute('disass g:Get') assert_match('\d\_s*' .. 'return local\_s*' .. '\d LOADOUTER level 1 $0\_s*' .. '\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() var res = execute('disass s:ScriptPCall') assert_match('\d\+_ScriptPCall\_s*' .. 'RefThis()("text")\_s*' .. '\d DCALL RefThis(argc 0)\_s*' .. '\d PUSHS "text"\_s*' .. '\d PCALL top (argc 1)\_s*' .. '\d PCALL end\_s*' .. '\d DROP\_s*' .. '\d RETURN void', res) enddef def s:FuncWithForwardCall(): string return g:DefinedLater("yes") enddef def DefinedLater(arg: string): string return arg enddef def Test_disassemble_update_instr() var res = execute('disass s:FuncWithForwardCall') assert_match('FuncWithForwardCall\_s*' .. 'return g:DefinedLater("yes")\_s*' .. '\d PUSHS "yes"\_s*' .. '\d DCALL DefinedLater(argc 1)\_s*' .. '\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\_s*' .. 'return g:DefinedLater("yes")\_s*' .. '\d PUSHS "yes"\_s*' .. '\d DCALL DefinedLater(argc 1)\_s*' .. '\d RETURN', res) enddef def FuncWithDefault(l: number, arg: string = "default", nr = 77): string return arg .. nr enddef def Test_disassemble_call_default() var res = execute('disass FuncWithDefault') assert_match('FuncWithDefault\_s*' .. ' arg = "default"\_s*' .. '\d JUMP_IF_ARG_SET arg\[-2\] -> 3\_s*' .. '\d PUSHS "default"\_s*' .. '\d STORE arg\[-2]\_s*' .. ' nr = 77\_s*' .. '3 JUMP_IF_ARG_SET arg\[-1\] -> 6\_s*' .. '\d PUSHNR 77\_s*' .. '\d STORE arg\[-1]\_s*' .. ' return arg .. nr\_s*' .. '6 LOAD arg\[-2]\_s*' .. '\d LOAD arg\[-1]\_s*' .. '\d 2STRING stack\[-1]\_s*' .. '\d\+ CONCAT\_s*' .. '\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 HasGuiRunning() if has("gui_running") echo "yes" else echo "no" endif enddef def Test_disassemble_const_expr() assert_equal("\nyes", execute('HasEval()')) var instr = execute('disassemble HasEval') assert_match('HasEval\_s*' .. 'if has("eval")\_s*' .. 'echo "yes"\_s*' .. '\d PUSHS "yes"\_s*' .. '\d ECHO 1\_s*' .. 'else\_s*' .. 'echo "no"\_s*' .. 'endif\_s*', instr) assert_notmatch('JUMP', instr) assert_equal("\nno", execute('HasNothing()')) instr = execute('disassemble HasNothing') assert_match('HasNothing\_s*' .. 'if has("nothing")\_s*' .. 'echo "yes"\_s*' .. 'else\_s*' .. 'echo "no"\_s*' .. '\d PUSHS "no"\_s*' .. '\d ECHO 1\_s*' .. 'endif', instr) assert_notmatch('PUSHS "yes"', instr) assert_notmatch('JUMP', instr) assert_equal("\neval", execute('HasSomething()')) instr = execute('disassemble HasSomething') assert_match('HasSomething.*' .. 'if has("nothing")\_s*' .. 'echo "nothing"\_s*' .. 'elseif has("something")\_s*' .. 'echo "something"\_s*' .. 'elseif has("eval")\_s*' .. 'echo "eval"\_s*' .. '\d PUSHS "eval"\_s*' .. '\d ECHO 1\_s*' .. 'elseif has("less").*' .. 'echo "less"\_s*' .. 'endif', instr) assert_notmatch('PUSHS "nothing"', instr) assert_notmatch('PUSHS "something"', instr) assert_notmatch('PUSHS "less"', instr) assert_notmatch('JUMP', instr) var result: string var instr_expected: string if has('gui') if has('gui_running') # GUI already running, always returns "yes" result = "\nyes" instr_expected = 'HasGuiRunning.*' .. 'if has("gui_running")\_s*' .. ' echo "yes"\_s*' .. '\d PUSHS "yes"\_s*' .. '\d ECHO 1\_s*' .. 'else\_s*' .. ' echo "no"\_s*' .. 'endif' else result = "\nno" if has('unix') # GUI not running but can start later, call has() instr_expected = 'HasGuiRunning.*' .. 'if has("gui_running")\_s*' .. '\d PUSHS "gui_running"\_s*' .. '\d BCALL has(argc 1)\_s*' .. '\d COND2BOOL\_s*' .. '\d JUMP_IF_FALSE -> \d\_s*' .. ' echo "yes"\_s*' .. '\d PUSHS "yes"\_s*' .. '\d ECHO 1\_s*' .. 'else\_s*' .. '\d JUMP -> \d\_s*' .. ' echo "no"\_s*' .. '\d PUSHS "no"\_s*' .. '\d ECHO 1\_s*' .. 'endif' else # GUI not running, always return "no" instr_expected = 'HasGuiRunning.*' .. 'if has("gui_running")\_s*' .. ' echo "yes"\_s*' .. 'else\_s*' .. ' echo "no"\_s*' .. '\d PUSHS "no"\_s*' .. '\d ECHO 1\_s*' .. 'endif' endif endif else # GUI not supported, always return "no" result = "\nno" instr_expected = 'HasGuiRunning.*' .. 'if has("gui_running")\_s*' .. ' echo "yes"\_s*' .. 'else\_s*' .. ' echo "no"\_s*' .. '\d PUSHS "no"\_s*' .. '\d ECHO 1\_s*' .. 'endif' endif assert_equal(result, execute('HasGuiRunning()')) instr = execute('disassemble HasGuiRunning') assert_match(instr_expected, instr) enddef def ReturnInIf(): string if 1 < 0 return "maybe" endif if g:cond return "yes" else return "no" endif enddef def Test_disassemble_return_in_if() var instr = execute('disassemble ReturnInIf') assert_match('ReturnInIf\_s*' .. 'if 1 < 0\_s*' .. ' return "maybe"\_s*' .. 'endif\_s*' .. 'if g:cond\_s*' .. '0 LOADG g:cond\_s*' .. '1 COND2BOOL\_s*' .. '2 JUMP_IF_FALSE -> 5\_s*' .. 'return "yes"\_s*' .. '3 PUSHS "yes"\_s*' .. '4 RETURN\_s*' .. 'else\_s*' .. ' return "no"\_s*' .. '5 PUSHS "no"\_s*' .. '6 RETURN$', instr) enddef def WithFunc() var Funky1: func var Funky2: func = function("len") var Party2: func = funcref("UserFunc") enddef def Test_disassemble_function() var instr = execute('disassemble WithFunc') assert_match('WithFunc\_s*' .. 'var Funky1: func\_s*' .. '0 PUSHFUNC "\[none]"\_s*' .. '1 STORE $0\_s*' .. 'var Funky2: func = function("len")\_s*' .. '2 PUSHS "len"\_s*' .. '3 BCALL function(argc 1)\_s*' .. '4 STORE $1\_s*' .. 'var Party2: func = funcref("UserFunc")\_s*' .. '\d PUSHS "UserFunc"\_s*' .. '\d BCALL funcref(argc 1)\_s*' .. '\d STORE $2\_s*' .. '\d RETURN void', instr) enddef if has('channel') def WithChannel() var job1: job var job2: job = job_start("donothing") var chan1: channel enddef endif def Test_disassemble_channel() CheckFeature channel var instr = execute('disassemble WithChannel') assert_match('WithChannel\_s*' .. 'var job1: job\_s*' .. '\d PUSHJOB "no process"\_s*' .. '\d STORE $0\_s*' .. 'var job2: job = job_start("donothing")\_s*' .. '\d PUSHS "donothing"\_s*' .. '\d BCALL job_start(argc 1)\_s*' .. '\d STORE $1\_s*' .. 'var chan1: channel\_s*' .. '\d PUSHCHANNEL 0\_s*' .. '\d STORE $2\_s*' .. '\d RETURN void', instr) enddef def WithLambda(): string var F = (a) => "X" .. a .. "X" return F("x") enddef def Test_disassemble_lambda() assert_equal("XxX", WithLambda()) var instr = execute('disassemble WithLambda') assert_match('WithLambda\_s*' .. 'var F = (a) => "X" .. a .. "X"\_s*' .. '\d FUNCREF \d\+\_s*' .. '\d STORE $0\_s*' .. 'return F("x")\_s*' .. '\d PUSHS "x"\_s*' .. '\d LOAD $0\_s*' .. '\d PCALL (argc 1)\_s*' .. '\d RETURN', instr) var name = substitute(instr, '.*\(\d\+\).*', '\1', '') instr = execute('disassemble ' .. name) assert_match('\d\+\_s*' .. 'return "X" .. a .. "X"\_s*' .. '\d PUSHS "X"\_s*' .. '\d LOAD arg\[-1\]\_s*' .. '\d 2STRING_ANY stack\[-1\]\_s*' .. '\d CONCAT\_s*' .. '\d PUSHS "X"\_s*' .. '\d CONCAT\_s*' .. '\d RETURN', instr) enddef def LambdaWithType(): number var Ref = (a: number) => a + 10 return Ref(g:value) enddef def Test_disassemble_lambda_with_type() g:value = 5 assert_equal(15, LambdaWithType()) var instr = execute('disassemble LambdaWithType') assert_match('LambdaWithType\_s*' .. 'var Ref = (a: number) => a + 10\_s*' .. '\d FUNCREF \d\+\_s*' .. '\d STORE $0\_s*' .. 'return Ref(g:value)\_s*' .. '\d LOADG g:value\_s*' .. '\d LOAD $0\_s*' .. '\d CHECKTYPE number stack\[-2\] arg 1\_s*' .. '\d PCALL (argc 1)\_s*' .. '\d RETURN', instr) enddef def NestedOuter() def g:Inner() echomsg "inner" enddef enddef def Test_disassemble_nested_func() var instr = execute('disassemble NestedOuter') assert_match('NestedOuter\_s*' .. 'def g:Inner()\_s*' .. 'echomsg "inner"\_s*' .. 'enddef\_s*' .. '\d NEWFUNC \d\+ Inner\_s*' .. '\d RETURN void', instr) enddef def NestedDefList() def def Info def /Info def /Info/ enddef def Test_disassemble_nested_def_list() var instr = execute('disassemble NestedDefList') assert_match('NestedDefList\_s*' .. 'def\_s*' .. '\d DEF \_s*' .. 'def Info\_s*' .. '\d DEF Info\_s*' .. 'def /Info\_s*' .. '\d DEF /Info\_s*' .. 'def /Info/\_s*' .. '\d DEF /Info/\_s*' .. '\d RETURN void', 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)) var instr = execute('disassemble AndOr') assert_match('AndOr\_s*' .. 'if arg == 1 && arg != 2 || arg == 4\_s*' .. '\d LOAD arg\[-1]\_s*' .. '\d PUSHNR 1\_s*' .. '\d COMPAREANY ==\_s*' .. '\d JUMP_IF_COND_FALSE -> \d\+\_s*' .. '\d LOAD arg\[-1]\_s*' .. '\d PUSHNR 2\_s*' .. '\d COMPAREANY !=\_s*' .. '\d JUMP_IF_COND_TRUE -> \d\+\_s*' .. '\d LOAD arg\[-1]\_s*' .. '\d\+ PUSHNR 4\_s*' .. '\d\+ COMPAREANY ==\_s*' .. '\d\+ JUMP_IF_FALSE -> \d\+', instr) enddef def ForLoop(): list var res: list for i in range(3) res->add(i) endfor return res enddef def Test_disassemble_for_loop() assert_equal([0, 1, 2], ForLoop()) var instr = execute('disassemble ForLoop') assert_match('ForLoop\_s*' .. 'var res: list\_s*' .. '\d NEWLIST size 0\_s*' .. '\d SETTYPE list\_s*' .. '\d STORE $0\_s*' .. 'for i in range(3)\_s*' .. '\d STORE -1 in $1\_s*' .. '\d PUSHNR 3\_s*' .. '\d BCALL range(argc 1)\_s*' .. '\d FOR $1 -> \d\+\_s*' .. '\d STORE $2\_s*' .. 'res->add(i)\_s*' .. '\d LOAD $0\_s*' .. '\d LOAD $2\_s*' .. '\d\+ LISTAPPEND\_s*' .. '\d\+ DROP\_s*' .. 'endfor\_s*' .. '\d\+ JUMP -> \d\+\_s*' .. '\d\+ DROP', instr) enddef def ForLoopEval(): string var res = "" for str in eval('["one", "two"]') res ..= str endfor return res enddef def Test_disassemble_for_loop_eval() assert_equal('onetwo', ForLoopEval()) var instr = execute('disassemble ForLoopEval') assert_match('ForLoopEval\_s*' .. 'var res = ""\_s*' .. '\d PUSHS ""\_s*' .. '\d STORE $0\_s*' .. 'for str in eval(''\["one", "two"\]'')\_s*' .. '\d STORE -1 in $1\_s*' .. '\d PUSHS "\["one", "two"\]"\_s*' .. '\d BCALL eval(argc 1)\_s*' .. '\d FOR $1 -> \d\+\_s*' .. '\d STORE $2\_s*' .. 'res ..= str\_s*' .. '\d\+ LOAD $0\_s*' .. '\d\+ LOAD $2\_s*' .. '\d 2STRING_ANY stack\[-1\]\_s*' .. '\d\+ CONCAT\_s*' .. '\d\+ STORE $0\_s*' .. 'endfor\_s*' .. '\d\+ JUMP -> 5\_s*' .. '\d\+ DROP\_s*' .. 'return res\_s*' .. '\d\+ LOAD $0\_s*' .. '\d\+ RETURN', instr) enddef def ForLoopUnpack() for [x1, x2] in [[1, 2], [3, 4]] echo x1 x2 endfor enddef def Test_disassemble_for_loop_unpack() var instr = execute('disassemble ForLoopUnpack') assert_match('ForLoopUnpack\_s*' .. 'for \[x1, x2\] in \[\[1, 2\], \[3, 4\]\]\_s*' .. '\d\+ STORE -1 in $0\_s*' .. '\d\+ PUSHNR 1\_s*' .. '\d\+ PUSHNR 2\_s*' .. '\d\+ NEWLIST size 2\_s*' .. '\d\+ PUSHNR 3\_s*' .. '\d\+ PUSHNR 4\_s*' .. '\d\+ NEWLIST size 2\_s*' .. '\d\+ NEWLIST size 2\_s*' .. '\d\+ FOR $0 -> 16\_s*' .. '\d\+ UNPACK 2\_s*' .. '\d\+ STORE $1\_s*' .. '\d\+ STORE $2\_s*' .. 'echo x1 x2\_s*' .. '\d\+ LOAD $1\_s*' .. '\d\+ LOAD $2\_s*' .. '\d\+ ECHO 2\_s*' .. 'endfor\_s*' .. '\d\+ JUMP -> 8\_s*' .. '\d\+ DROP\_s*' .. '\d\+ RETURN void', instr) enddef def ForLoopContinue() for nr in [1, 2] try echo "ok" try echo "deeper" catch continue endtry catch echo "not ok" endtry endfor enddef def Test_disassemble_for_loop_continue() var instr = execute('disassemble ForLoopContinue') assert_match('ForLoopContinue\_s*' .. 'for nr in \[1, 2]\_s*' .. '0 STORE -1 in $0\_s*' .. '1 PUSHNR 1\_s*' .. '2 PUSHNR 2\_s*' .. '3 NEWLIST size 2\_s*' .. '4 FOR $0 -> 22\_s*' .. '5 STORE $1\_s*' .. 'try\_s*' .. '6 TRY catch -> 17, endtry -> 20\_s*' .. 'echo "ok"\_s*' .. '7 PUSHS "ok"\_s*' .. '8 ECHO 1\_s*' .. 'try\_s*' .. '9 TRY catch -> 13, endtry -> 15\_s*' .. 'echo "deeper"\_s*' .. '10 PUSHS "deeper"\_s*' .. '11 ECHO 1\_s*' .. 'catch\_s*' .. '12 JUMP -> 15\_s*' .. '13 CATCH\_s*' .. 'continue\_s*' .. '14 TRY-CONTINUE 2 levels -> 4\_s*' .. 'endtry\_s*' .. '15 ENDTRY\_s*' .. 'catch\_s*' .. '16 JUMP -> 20\_s*' .. '17 CATCH\_s*' .. 'echo "not ok"\_s*' .. '18 PUSHS "not ok"\_s*' .. '19 ECHO 1\_s*' .. 'endtry\_s*' .. '20 ENDTRY\_s*' .. 'endfor\_s*' .. '21 JUMP -> 4\_s*' .. '\d\+ DROP\_s*' .. '\d\+ RETURN void', instr) enddef let g:number = 42 def TypeCast() var l: list = [23, g:number] enddef def Test_disassemble_typecast() var instr = execute('disassemble TypeCast') assert_match('TypeCast.*' .. 'var l: list = \[23, g:number\].*' .. '\d PUSHNR 23\_s*' .. '\d LOADG g:number\_s*' .. '\d CHECKTYPE number stack\[-1\]\_s*' .. '\d NEWLIST size 2\_s*' .. '\d SETTYPE list\_s*' .. '\d STORE $0\_s*' .. '\d RETURN void\_s*', instr) enddef def Computing() var nr = 3 var nrres = nr + 7 nrres = nr - 7 nrres = nr * 7 nrres = nr / 7 nrres = nr % 7 var anyres = g:number + 7 anyres = g:number - 7 anyres = g:number * 7 anyres = g:number / 7 anyres = g:number % 7 if has('float') var fl = 3.0 var flres = fl + 7.0 flres = fl - 7.0 flres = fl * 7.0 flres = fl / 7.0 endif enddef def Test_disassemble_computing() var instr = execute('disassemble Computing') assert_match('Computing.*' .. 'var nr = 3.*' .. '\d STORE 3 in $0.*' .. 'var 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 %.*' .. 'var 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.*' .. 'var fl = 3.0.*' .. '\d PUSHF 3.0.*' .. '\d STORE $3.*' .. 'var 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() var reslist = [1, 2] + [3, 4] var resblob = 0z1122 + 0z3344 enddef def Test_disassemble_add_list_blob() var instr = execute('disassemble AddListBlob') assert_match('AddListBlob.*' .. 'var 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 $.*.*' .. 'var resblob = 0z1122 + 0z3344.*' .. '\d PUSHBLOB 0z1122.*' .. '\d PUSHBLOB 0z3344.*' .. '\d ADDBLOB.*' .. '\d STORE $.*', instr) enddef let g:aa = 'aa' def ConcatString(): string var res = g:aa .. "bb" return res enddef def Test_disassemble_concat() var instr = execute('disassemble ConcatString') assert_match('ConcatString.*' .. 'var res = g:aa .. "bb".*' .. '\d LOADG g:aa.*' .. '\d PUSHS "bb".*' .. '\d 2STRING_ANY stack\[-2].*' .. '\d CONCAT.*' .. '\d STORE $.*', instr) assert_equal('aabb', ConcatString()) enddef def StringIndex(): string var s = "abcd" var res = s[1] return res enddef def Test_disassemble_string_index() var instr = execute('disassemble StringIndex') assert_match('StringIndex\_s*' .. 'var s = "abcd"\_s*' .. '\d PUSHS "abcd"\_s*' .. '\d STORE $0\_s*' .. 'var res = s\[1]\_s*' .. '\d LOAD $0\_s*' .. '\d PUSHNR 1\_s*' .. '\d STRINDEX\_s*' .. '\d STORE $1\_s*', instr) assert_equal('b', StringIndex()) enddef def StringSlice(): string var s = "abcd" var res = s[1 : 8] return res enddef def Test_disassemble_string_slice() var instr = execute('disassemble StringSlice') assert_match('StringSlice\_s*' .. 'var s = "abcd"\_s*' .. '\d PUSHS "abcd"\_s*' .. '\d STORE $0\_s*' .. 'var res = s\[1 : 8]\_s*' .. '\d LOAD $0\_s*' .. '\d PUSHNR 1\_s*' .. '\d PUSHNR 8\_s*' .. '\d STRSLICE\_s*' .. '\d STORE $1\_s*', instr) assert_equal('bcd', StringSlice()) enddef def ListIndex(): number var l = [1, 2, 3] var res = l[1] return res enddef def Test_disassemble_list_index() var instr = execute('disassemble ListIndex') assert_match('ListIndex\_s*' .. 'var 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*' .. 'var res = l\[1]\_s*' .. '\d LOAD $0\_s*' .. '\d PUSHNR 1\_s*' .. '\d LISTINDEX\_s*' .. '\d STORE $1\_s*', instr) assert_equal(2, ListIndex()) enddef def ListSlice(): list var l = [1, 2, 3] var res = l[1 : 8] return res enddef def Test_disassemble_list_slice() var instr = execute('disassemble ListSlice') assert_match('ListSlice\_s*' .. 'var 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*' .. 'var res = l\[1 : 8]\_s*' .. '\d LOAD $0\_s*' .. '\d PUSHNR 1\_s*' .. '\d PUSHNR 8\_s*' .. '\d LISTSLICE\_s*' .. '\d STORE $1\_s*', instr) assert_equal([2, 3], ListSlice()) enddef def DictMember(): number var d = {item: 1} var res = d.item res = d["item"] return res enddef def Test_disassemble_dict_member() var instr = execute('disassemble DictMember') assert_match('DictMember\_s*' .. 'var d = {item: 1}\_s*' .. '\d PUSHS "item"\_s*' .. '\d PUSHNR 1\_s*' .. '\d NEWDICT size 1\_s*' .. '\d STORE $0\_s*' .. 'var 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) assert_equal(1, DictMember()) enddef let somelist = [1, 2, 3, 4, 5] def AnyIndex(): number var res = g:somelist[2] return res enddef def Test_disassemble_any_index() var instr = execute('disassemble AnyIndex') assert_match('AnyIndex\_s*' .. 'var res = g:somelist\[2\]\_s*' .. '\d LOADG g:somelist\_s*' .. '\d PUSHNR 2\_s*' .. '\d ANYINDEX\_s*' .. '\d STORE $0\_s*' .. 'return res\_s*' .. '\d LOAD $0\_s*' .. '\d CHECKTYPE number stack\[-1\]\_s*' .. '\d RETURN', instr) assert_equal(3, AnyIndex()) enddef def AnySlice(): list var res = g:somelist[1 : 3] return res enddef def Test_disassemble_any_slice() var instr = execute('disassemble AnySlice') assert_match('AnySlice\_s*' .. 'var res = g:somelist\[1 : 3\]\_s*' .. '\d LOADG g:somelist\_s*' .. '\d PUSHNR 1\_s*' .. '\d PUSHNR 3\_s*' .. '\d ANYSLICE\_s*' .. '\d STORE $0\_s*' .. 'return res\_s*' .. '\d LOAD $0\_s*' .. '\d CHECKTYPE list stack\[-1\]\_s*' .. '\d RETURN', instr) assert_equal([2, 3, 4], AnySlice()) enddef def NegateNumber(): number g:nr = 9 var plus = +g:nr var minus = -g:nr return minus enddef def Test_disassemble_negate_number() var instr = execute('disassemble NegateNumber') assert_match('NegateNumber\_s*' .. 'g:nr = 9\_s*' .. '\d PUSHNR 9\_s*' .. '\d STOREG g:nr\_s*' .. 'var plus = +g:nr\_s*' .. '\d LOADG g:nr\_s*' .. '\d CHECKTYPE number stack\[-1\]\_s*' .. '\d STORE $0\_s*' .. 'var minus = -g:nr\_s*' .. '\d LOADG g:nr\_s*' .. '\d CHECKTYPE number stack\[-1\]\_s*' .. '\d NEGATENR\_s*' .. '\d STORE $1\_s*', instr) assert_equal(-9, NegateNumber()) enddef def InvertBool(): bool var flag = true var invert = !flag var res = !!flag return res enddef def Test_disassemble_invert_bool() var instr = execute('disassemble InvertBool') assert_match('InvertBool\_s*' .. 'var flag = true\_s*' .. '\d PUSH true\_s*' .. '\d STORE $0\_s*' .. 'var invert = !flag\_s*' .. '\d LOAD $0\_s*' .. '\d INVERT -1 (!val)\_s*' .. '\d STORE $1\_s*' .. 'var res = !!flag\_s*' .. '\d LOAD $0\_s*' .. '\d 2BOOL -1 (!!val)\_s*' .. '\d STORE $2\_s*', instr) assert_equal(true, InvertBool()) enddef def ReturnBool(): bool var name: bool = 1 && 0 || 1 return name enddef def Test_disassemble_return_bool() var instr = execute('disassemble ReturnBool') assert_match('ReturnBool\_s*' .. 'var name: bool = 1 && 0 || 1\_s*' .. '0 PUSHNR 1\_s*' .. '1 COND2BOOL\_s*' .. '2 JUMP_IF_COND_FALSE -> 5\_s*' .. '3 PUSHNR 0\_s*' .. '4 COND2BOOL\_s*' .. '5 JUMP_IF_COND_TRUE -> 8\_s*' .. '6 PUSHNR 1\_s*' .. '7 COND2BOOL\_s*' .. '\d STORE $0\_s*' .. 'return name\_s*' .. '\d\+ LOAD $0\_s*' .. '\d\+ RETURN', instr) assert_equal(true, InvertBool()) enddef def Test_disassemble_compare() var 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'], ] var 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 = 'var aFloat = 2.2' endif var nr = 1 for case in cases # declare local variables to get a non-constant with the right type writefile(['def TestCase' .. nr .. '()', ' var isFalse = false', ' var isNull = v:null', ' var aNumber = 222', ' var aString = "yy"', ' var aBlob = 0z22', ' var aList = [3, 4]', ' var aDict = {x: 2}', floatDecl, ' if ' .. case[0], ' echo 42' ' endif', 'enddef'], 'Xdisassemble') source Xdisassemble var 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 s:FalsyOp() echo g:flag ?? "yes" echo [] ?? "empty list" echo "" ?? "empty string" enddef def Test_dsassemble_falsy_op() var res = execute('disass s:FalsyOp') assert_match('\\d*_FalsyOp\_s*' .. 'echo g:flag ?? "yes"\_s*' .. '0 LOADG g:flag\_s*' .. '1 JUMP_AND_KEEP_IF_TRUE -> 3\_s*' .. '2 PUSHS "yes"\_s*' .. '3 ECHO 1\_s*' .. 'echo \[\] ?? "empty list"\_s*' .. '4 NEWLIST size 0\_s*' .. '5 JUMP_AND_KEEP_IF_TRUE -> 7\_s*' .. '6 PUSHS "empty list"\_s*' .. '7 ECHO 1\_s*' .. 'echo "" ?? "empty string"\_s*' .. '\d\+ PUSHS "empty string"\_s*' .. '\d\+ ECHO 1\_s*' .. '\d\+ RETURN void', res) enddef def Test_disassemble_compare_const() var cases = [ ['"xx" == "yy"', false], ['"aa" == "aa"', true], ['has("eval") ? true : false', true], ['has("asdf") ? true : false', false], ] var nr = 1 for case in cases writefile(['def TestCase' .. nr .. '()', ' if ' .. case[0], ' echo 42' ' endif', 'enddef'], 'Xdisassemble') source Xdisassemble var 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 RETURN void', instr) else # condition false, function just returns assert_match('TestCase' .. nr .. '.*' .. 'if ' .. substitute(case[0], '[[~]', '\\\0', 'g') .. '[ \n]*' .. 'echo 42[ \n]*' .. 'endif[ \n]*' .. '\d RETURN void', instr) endif nr += 1 endfor delete('Xdisassemble') enddef def s:Execute() execute 'help vim9.txt' var cmd = 'help vim9.txt' execute cmd var tag = 'vim9.txt' execute 'help ' .. tag enddef def Test_disassemble_execute() var res = execute('disass s:Execute') assert_match('\\d*_Execute\_s*' .. "execute 'help vim9.txt'\\_s*" .. '\d PUSHS "help vim9.txt"\_s*' .. '\d EXECUTE 1\_s*' .. "var 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*' .. "var 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\+ RETURN void', res) enddef def s:Echomsg() echomsg 'some' 'message' echoconsole 'nothing' echoerr 'went' .. 'wrong' enddef def Test_disassemble_echomsg() var res = execute('disass s:Echomsg') assert_match('\\d*_Echomsg\_s*' .. "echomsg 'some' 'message'\\_s*" .. '\d PUSHS "some"\_s*' .. '\d PUSHS "message"\_s*' .. '\d ECHOMSG 2\_s*' .. "echoconsole 'nothing'\\_s*" .. '\d PUSHS "nothing"\_s*' .. '\d ECHOCONSOLE 1\_s*' .. "echoerr 'went' .. 'wrong'\\_s*" .. '\d PUSHS "wentwrong"\_s*' .. '\d ECHOERR 1\_s*' .. '\d RETURN void', 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() var res1 = execute('function SomeStringArg') assert_match('.* def SomeStringArg(arg: string)\_s*' .. '\d *echo arg.*' .. ' *enddef', res1) var res2 = execute('function SomeAnyArg') assert_match('.* def SomeAnyArg(arg: any)\_s*' .. '\d *echo arg\_s*' .. ' *enddef', res2) var res3 = execute('function SomeStringArgAndReturn') assert_match('.* def SomeStringArgAndReturn(arg: string): string\_s*' .. '\d *return arg\_s*' .. ' *enddef', res3) enddef def Test_vim9script_forward_func() var lines =<< trim END vim9script def FuncOne(): string return FuncTwo() enddef def FuncTwo(): string return 'two' enddef g:res_FuncOne = execute('disass FuncOne') END writefile(lines, 'Xdisassemble') source Xdisassemble # check that the first function calls the second with DCALL assert_match('\\d*_FuncOne\_s*' .. 'return FuncTwo()\_s*' .. '\d DCALL \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() var res = execute('disass s:ConcatStrings') assert_match('\d*_ConcatStrings\_s*' .. "return 'one' .. 'two' .. 'three'\\_s*" .. '\d PUSHS "onetwothree"\_s*' .. '\d RETURN', res) res = execute('disass s:ComputeConst') assert_match('\d*_ComputeConst\_s*' .. 'return 2 + 3 \* 4 / 6 + 7\_s*' .. '\d PUSHNR 11\_s*' .. '\d RETURN', res) res = execute('disass s:ComputeConstParen') assert_match('\d*_ComputeConstParen\_s*' .. 'return ((2 + 4) \* (8 / 2)) / (3 + 4)\_s*' .. '\d PUSHNR 3\>\_s*' .. '\d RETURN', res) enddef def s:CallAppend() eval "some text"->append(2) enddef def Test_shuffle() var res = execute('disass s:CallAppend') assert_match('\d*_CallAppend\_s*' .. 'eval "some text"->append(2)\_s*' .. '\d PUSHS "some text"\_s*' .. '\d PUSHNR 2\_s*' .. '\d SHUFFLE 2 up 1\_s*' .. '\d BCALL append(argc 2)\_s*' .. '\d DROP\_s*' .. '\d RETURN void', res) enddef def s:SilentMessage() silent echomsg "text" silent! echoerr "error" enddef def Test_silent() var res = execute('disass s:SilentMessage') assert_match('\d*_SilentMessage\_s*' .. 'silent echomsg "text"\_s*' .. '\d CMDMOD silent\_s*' .. '\d PUSHS "text"\_s*' .. '\d ECHOMSG 1\_s*' .. '\d CMDMOD_REV\_s*' .. 'silent! echoerr "error"\_s*' .. '\d CMDMOD silent!\_s*' .. '\d PUSHS "error"\_s*' .. '\d ECHOERR 1\_s*' .. '\d CMDMOD_REV\_s*' .. '\d\+ RETURN void', res) enddef def s:SilentIf() silent if 4 == g:five silent elseif 4 == g:five endif enddef def Test_silent_if() var res = execute('disass s:SilentIf') assert_match('\d*_SilentIf\_s*' .. 'silent if 4 == g:five\_s*' .. '\d\+ CMDMOD silent\_s*' .. '\d\+ PUSHNR 4\_s*' .. '\d\+ LOADG g:five\_s*' .. '\d\+ COMPAREANY ==\_s*' .. '\d\+ CMDMOD_REV\_s*' .. '\d\+ JUMP_IF_FALSE -> \d\+\_s*' .. 'silent elseif 4 == g:five\_s*' .. '\d\+ JUMP -> \d\+\_s*' .. '\d\+ CMDMOD silent\_s*' .. '\d\+ PUSHNR 4\_s*' .. '\d\+ LOADG g:five\_s*' .. '\d\+ COMPAREANY ==\_s*' .. '\d\+ CMDMOD_REV\_s*' .. '\d\+ JUMP_IF_FALSE -> \d\+\_s*' .. 'endif\_s*' .. '\d\+ RETURN void', res) enddef def s:SilentFor() silent for i in [0] endfor enddef def Test_silent_for() var res = execute('disass s:SilentFor') assert_match('\d*_SilentFor\_s*' .. 'silent for i in \[0\]\_s*' .. '\d CMDMOD silent\_s*' .. '\d STORE -1 in $0\_s*' .. '\d PUSHNR 0\_s*' .. '\d NEWLIST size 1\_s*' .. '\d CMDMOD_REV\_s*' .. '5 FOR $0 -> 8\_s*' .. '\d STORE $1\_s*' .. 'endfor\_s*' .. '\d JUMP -> 5\_s*' .. '8 DROP\_s*' .. '\d RETURN void\_s*', res) enddef def s:SilentWhile() silent while g:not endwhile enddef def Test_silent_while() var res = execute('disass s:SilentWhile') assert_match('\d*_SilentWhile\_s*' .. 'silent while g:not\_s*' .. '0 CMDMOD silent\_s*' .. '\d LOADG g:not\_s*' .. '\d COND2BOOL\_s*' .. '\d CMDMOD_REV\_s*' .. '\d JUMP_IF_FALSE -> 6\_s*' .. 'endwhile\_s*' .. '\d JUMP -> 0\_s*' .. '6 RETURN void\_s*', res) enddef def s:SilentReturn(): string silent return "done" enddef def Test_silent_return() var res = execute('disass s:SilentReturn') assert_match('\d*_SilentReturn\_s*' .. 'silent return "done"\_s*' .. '\d CMDMOD silent\_s*' .. '\d PUSHS "done"\_s*' .. '\d CMDMOD_REV\_s*' .. '\d RETURN', res) enddef def s:Profiled(): string # comment echo "profiled" # comment var some = "some text" return "done" enddef def Test_profiled() if !has('profile') MissingFeature 'profile' endif var res = execute('disass profile s:Profiled') assert_match('\d*_Profiled\_s*' .. '# comment\_s*' .. 'echo "profiled"\_s*' .. '\d PROFILE START line 2\_s*' .. '\d PUSHS "profiled"\_s*' .. '\d ECHO 1\_s*' .. '# comment\_s*' .. 'var some = "some text"\_s*' .. '\d PROFILE END\_s*' .. '\d PROFILE START line 4\_s*' .. '\d PUSHS "some text"\_s*' .. '\d STORE $0\_s*' .. 'return "done"\_s*' .. '\d PROFILE END\_s*' .. '\d PROFILE START line 5\_s*' .. '\d PUSHS "done"\_s*' .. '\d\+ RETURN\_s*' .. '\d\+ PROFILE END', res) enddef def Test_debugged() var res = execute('disass debug s:Profiled') assert_match('\d*_Profiled\_s*' .. '# comment\_s*' .. 'echo "profiled"\_s*' .. '\d DEBUG line 1-2 varcount 0\_s*' .. '\d PUSHS "profiled"\_s*' .. '\d ECHO 1\_s*' .. '# comment\_s*' .. 'var some = "some text"\_s*' .. '\d DEBUG line 3-4 varcount 0\_s*' .. '\d PUSHS "some text"\_s*' .. '\d STORE $0\_s*' .. 'return "done"\_s*' .. '\d DEBUG line 5-5 varcount 1\_s*' .. '\d PUSHS "done"\_s*' .. '\d RETURN\_s*', res) enddef def s:DebugElseif() var b = false if b eval 1 + 0 silent elseif !b eval 2 + 0 endif enddef def Test_debug_elseif() var res = execute('disass debug s:DebugElseif') assert_match('\d*_DebugElseif\_s*' .. 'var b = false\_s*' .. '0 DEBUG line 1-1 varcount 0\_s*' .. '1 PUSH false\_s*' .. '2 STORE $0\_s*' .. 'if b\_s*' .. '3 DEBUG line 2-2 varcount 1\_s*' .. '4 LOAD $0\_s*' .. '5 JUMP_IF_FALSE -> 10\_s*' .. 'eval 1 + 0\_s*' .. '6 DEBUG line 3-3 varcount 1\_s*' .. '7 PUSHNR 1\_s*' .. '8 DROP\_s*' .. 'silent elseif !b\_s*' .. '9 JUMP -> 20\_s*' .. '10 CMDMOD silent\_s*' .. '11 DEBUG line 4-4 varcount 1\_s*' .. '12 LOAD $0\_s*' .. '13 INVERT -1 (!val)\_s*' .. '14 CMDMOD_REV\_s*' .. '15 JUMP_IF_FALSE -> 20\_s*' .. 'eval 2 + 0\_s*' .. '16 DEBUG line 5-5 varcount 1\_s*' .. '17 PUSHNR 2\_s*' .. '18 DROP\_s*' .. 'endif\_s*' .. '19 DEBUG line 6-6 varcount 1\_s*' .. '20 RETURN void*', res) enddef def s:EchoMessages() echohl ErrorMsg | echom v:exception | echohl NONE enddef def Test_disassemble_nextcmd() # splitting commands and removing trailing blanks should not change the line var res = execute('disass s:EchoMessages') assert_match('\d*_EchoMessages\_s*' .. 'echohl ErrorMsg | echom v:exception | echohl NONE', res) enddef def Test_disassemble_after_reload() var lines =<< trim END vim9script if exists('g:ThisFunc') finish endif var name: any def g:ThisFunc(): number g:name = name return 0 enddef def g:ThatFunc(): number name = g:name return 0 enddef END lines->writefile('Xreload.vim') source Xreload.vim g:ThisFunc() g:ThatFunc() source Xreload.vim var res = execute('disass g:ThisFunc') assert_match('ThisFunc\_s*' .. 'g:name = name\_s*' .. '\d LOADSCRIPT \[deleted\] from .*/Xreload.vim\_s*' .. '\d STOREG g:name\_s*' .. 'return 0\_s*' .. '\d PUSHNR 0\_s*' .. '\d RETURN\_s*', res) res = execute('disass g:ThatFunc') assert_match('ThatFunc\_s*' .. 'name = g:name\_s*' .. '\d LOADG g:name\_s*' .. '\d STORESCRIPT \[deleted\] in .*/Xreload.vim\_s*' .. 'return 0\_s*' .. '\d PUSHNR 0\_s*' .. '\d RETURN\_s*', res) delete('Xreload.vim') delfunc g:ThisFunc delfunc g:ThatFunc enddef " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker