summaryrefslogtreecommitdiffstats
path: root/src/testdir/test_vim9_disassemble.vim
diff options
context:
space:
mode:
Diffstat (limited to 'src/testdir/test_vim9_disassemble.vim')
-rw-r--r--src/testdir/test_vim9_disassemble.vim3276
1 files changed, 3276 insertions, 0 deletions
diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim
new file mode 100644
index 0000000..1a192cc
--- /dev/null
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -0,0 +1,3276 @@
+" Test the :disassemble command, and compilation as a side effect
+
+source check.vim
+import './vim9.vim' as v9
+
+func s: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 <XX>foo', 'E129:')
+ assert_fails('disass Test_disassemble_load burp', 'E488:')
+ assert_fails('disass debug debug Test_disassemble_load', 'E488:')
+ assert_fails('disass profile profile Test_disassemble_load', 'E488:')
+
+ var res = execute('disass s:ScriptFuncLoad')
+ assert_match('<SNR>\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('<SNR>\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('<SNR>\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('<SNR>\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('<SNR>\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:SubstituteExpr()
+ substitute('a', 'b', '\=123', 'g')
+enddef
+
+def Test_disassemble_substitute_expr()
+ var res = execute('disass s:SubstituteExpr')
+ assert_match('<SNR>\d*_SubstituteExpr.*' ..
+ 'substitute(''a'', ''b'', ''\\=123'', ''g'')\_s*' ..
+ '\d PUSHS "a"\_s*' ..
+ '\d PUSHS "b"\_s*' ..
+ '\d INSTR\_s*' ..
+ ' 0 PUSHNR 123\_s*' ..
+ ' -------------\_s*' ..
+ '\d PUSHS "g"\_s*' ..
+ '\d BCALL substitute(argc 4)\_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('<SNR>\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 size 2\_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('<SNR>\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('<SNR>\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('<SNR>\d*_PutExpr.*' ..
+ ' :3put ="text"\_s*' ..
+ '\d PUSHS "text"\_s*' ..
+ '\d PUT = 3\_s*' ..
+ '\d RETURN void',
+ res)
+enddef
+
+def s:PutRange()
+ :$-2put a
+ :$-3put! b
+enddef
+
+def Test_disassemble_put_range()
+ var res = execute('disass s:PutRange')
+ assert_match('<SNR>\d*_PutRange.*' ..
+ ' :$-2put a\_s*' ..
+ '\d RANGE $-2\_s*' ..
+ '\d PUT a range\_s*' ..
+
+ ' :$-3put! b\_s*' ..
+ '\d RANGE $-3\_s*' ..
+ '\d PUT b above range\_s*' ..
+ '\d RETURN void',
+ res)
+enddef
+
+def s:ScriptFuncPush()
+ var localbool = true
+ var localspec = v:none
+ var localblob = 0z1234
+ var localfloat = 1.234
+enddef
+
+def Test_disassemble_push()
+ mkdir('Xdisdir/autoload', 'pR')
+ var save_rtp = &rtp
+ exe 'set rtp^=' .. getcwd() .. '/Xdisdir'
+
+ var lines =<< trim END
+ vim9script
+ END
+ writefile(lines, 'Xdisdir/autoload/autoscript.vim')
+
+ lines =<< trim END
+ vim9script
+ import autoload 'autoscript.vim'
+
+ def AutoloadFunc()
+ &operatorfunc = autoscript.Opfunc
+ enddef
+
+ var res = execute('disass AutoloadFunc')
+ assert_match('<SNR>\d*_AutoloadFunc.*' ..
+ '&operatorfunc = autoscript.Opfunc\_s*' ..
+ '0 AUTOLOAD autoscript#Opfunc\_s*' ..
+ '1 STOREFUNCOPT &operatorfunc\_s*' ..
+ '2 RETURN void',
+ res)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ &rtp = save_rtp
+enddef
+
+def Test_disassemble_import_autoload()
+ writefile(['vim9script'], 'XimportAL.vim', 'D')
+
+ var lines =<< trim END
+ vim9script
+ import autoload './XimportAL.vim'
+
+ def AutoloadFunc()
+ echo XimportAL.SomeFunc()
+ echo XimportAL.someVar
+ XimportAL.someVar = "yes"
+ enddef
+
+ var res = execute('disass AutoloadFunc')
+ assert_match('<SNR>\d*_AutoloadFunc.*' ..
+ 'echo XimportAL.SomeFunc()\_s*' ..
+ '\d SOURCE .*/testdir/XimportAL.vim\_s*' ..
+ '\d PUSHFUNC "<80><fd>R\d\+_SomeFunc"\_s*' ..
+ '\d PCALL top (argc 0)\_s*' ..
+ '\d PCALL end\_s*' ..
+ '\d ECHO 1\_s*' ..
+
+ 'echo XimportAL.someVar\_s*' ..
+ '\d SOURCE .*/testdir/XimportAL.vim\_s*' ..
+ '\d LOADEXPORT s:someVar from .*/testdir/XimportAL.vim\_s*' ..
+ '\d ECHO 1\_s*' ..
+
+ 'XimportAL.someVar = "yes"\_s*' ..
+ '\d\+ PUSHS "yes"\_s*' ..
+ '\d\+ SOURCE .*/testdir/XimportAL.vim\_s*' ..
+ '\d\+ STOREEXPORT someVar in .*/testdir/XimportAL.vim\_s*' ..
+
+ '\d\+ RETURN void',
+ res)
+ END
+ v9.CheckScriptSuccess(lines)
+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
+ &opfunc = (t) => len(t)
+ $ENVVAR = 'ev'
+ @z = 'rv'
+enddef
+
+def Test_disassemble_store()
+ var res = execute('disass s:ScriptFuncStore')
+ assert_match('<SNR>\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\_s*' ..
+ '\d\+ PUSHNR 8\_s*' ..
+ '\d\+ STOREOPT &tabstop\_s*' ..
+ '&opfunc = (t) => len(t)\_s*' ..
+ '\d\+ FUNCREF <lambda>\d\+\_s*' ..
+ '\d\+ STOREFUNCOPT &opfunc\_s*' ..
+ '$ENVVAR = ''ev''\_s*' ..
+ '\d\+ PUSHS "ev"\_s*' ..
+ '\d\+ STOREENV $ENVVAR\_s*' ..
+ '@z = ''rv''.*' ..
+ '\d\+ STOREREG @z.*',
+ res)
+enddef
+
+def s:ScriptFuncStoreMember()
+ var locallist: list<number> = []
+ locallist[0] = 123
+ var localdict: dict<number> = {}
+ localdict["a"] = 456
+ var localblob: blob = 0z1122
+ localblob[1] = 33
+enddef
+
+def Test_disassemble_store_member()
+ var res = execute('disass s:ScriptFuncStoreMember')
+ assert_match('<SNR>\d*_ScriptFuncStoreMember\_s*' ..
+ 'var locallist: list<number> = []\_s*' ..
+ '\d NEWLIST size 0\_s*' ..
+ '\d SETTYPE list<number>\_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<number> = {}\_s*' ..
+ '\d NEWDICT size 0\_s*' ..
+ '\d SETTYPE dict<number>\_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
+
+if has('job')
+ def s:StoreNull()
+ var ss = null_string
+ var bb = null_blob
+ var dd = null_dict
+ var ll = null_list
+ var Ff = null_function
+ var Pp = null_partial
+ var jj = null_job
+ var cc = null_channel
+ var oo = null_object
+ var nc = null_class
+ enddef
+
+ def Test_disassemble_assign_null()
+ var res = execute('disass s:StoreNull')
+ assert_match('<SNR>\d*_StoreNull\_s*' ..
+ 'var ss = null_string\_s*' ..
+ '\d\+ PUSHS "\[NULL\]"\_s*' ..
+ '\d\+ STORE $\d\_s*' ..
+
+ 'var bb = null_blob\_s*' ..
+ '\d\+ PUSHBLOB 0z\_s*' ..
+ '\d\+ STORE $\d\_s*' ..
+
+ 'var dd = null_dict\_s*' ..
+ '\d\+ NEWDICT size -1\_s*' ..
+ '\d\+ STORE $\d\_s*' ..
+
+ 'var ll = null_list\_s*' ..
+ '\d\+ NEWLIST size -1\_s*' ..
+ '\d\+ STORE $\d\_s*' ..
+
+ 'var Ff = null_function\_s*' ..
+ '\d\+ PUSHFUNC "\[none\]"\_s*' ..
+ '\d\+ STORE $\d\_s*' ..
+
+ 'var Pp = null_partial\_s*' ..
+ '\d\+ NEWPARTIAL\_s*' ..
+ '\d\+ STORE $\d\_s*' ..
+
+ 'var jj = null_job\_s*' ..
+ '\d\+ PUSHJOB "no process"\_s*' ..
+ '\d\+ STORE $\d\_s*' ..
+
+ 'var cc = null_channel\_s*' ..
+ '\d\+ PUSHCHANNEL 0\_s*' ..
+ '\d\+ STORE $\d\_s*' ..
+
+ 'var oo = null_object\_s*' ..
+ '\d\+ PUSHOBJ null\_s*' ..
+ '\d\+ STORE $\d\_s*' ..
+
+ 'var nc = null_class\_s*' ..
+ '\d\+ PUSHCLASS null\_s*' ..
+ '\d\+ STORE $\d\_s*' ..
+
+ '\d\+ RETURN void',
+ res)
+ enddef
+endif
+
+def s:ScriptFuncStoreIndex()
+ var d = {dd: {}}
+ d.dd[0] = 0
+enddef
+
+def Test_disassemble_store_index()
+ var res = execute('disass s:ScriptFuncStoreIndex')
+ assert_match('<SNR>\d*_ScriptFuncStoreIndex\_s*' ..
+ 'var d = {dd: {}}\_s*' ..
+ '\d PUSHS "dd"\_s*' ..
+ '\d NEWDICT size 0\_s*' ..
+ '\d NEWDICT size 1\_s*' ..
+ '\d SETTYPE dict<dict<any>>\_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\+ USEDICT\_s*' ..
+ '\d\+ STOREINDEX any\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+def s:ListAssign()
+ var x: string
+ var y: string
+ var l: list<any>
+ [x, y; l] = g:stringlist
+enddef
+
+def Test_disassemble_list_assign()
+ var res = execute('disass s:ListAssign')
+ assert_match('<SNR>\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<any>\_s*' ..
+ '\d NEWLIST size 0\_s*' ..
+ '\d STORE $2\_s*' ..
+ '\[x, y; l\] = g:stringlist\_s*' ..
+ '\d LOADG g:stringlist\_s*' ..
+ '\d CHECKTYPE list<any> stack\[-1\]\_s*' ..
+ '\d CHECKLEN >= 2\_s*' ..
+ '\d\+ ITEM 0\_s*' ..
+ '\d\+ CHECKTYPE string stack\[-1\] var 1\_s*' ..
+ '\d\+ STORE $0\_s*' ..
+ '\d\+ ITEM 1\_s*' ..
+ '\d\+ CHECKTYPE string stack\[-1\] var 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('<SNR>\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\+ 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<number> = []
+ add(l, 123)
+ add(l, g:aNumber)
+enddef
+
+def Test_disassemble_list_add()
+ var res = execute('disass s:ListAdd')
+ assert_match('<SNR>\d*_ListAdd\_s*' ..
+ 'var l: list<number> = []\_s*' ..
+ '\d NEWLIST size 0\_s*' ..
+ '\d SETTYPE list<number>\_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('<SNR>\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('<SNR>\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
+
+ var l = [1, 2, 3]
+ unlet l[2]
+ unlet l[0 : 1]
+enddef
+
+def Test_disassemble_unlet()
+ var res = execute('disass s:ScriptFuncUnlet')
+ assert_match('<SNR>\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*' ..
+
+ 'var l = \[1, 2, 3]\_s*' ..
+ '\d\+ PUSHNR 1\_s*' ..
+ '\d\+ PUSHNR 2\_s*' ..
+ '\d\+ PUSHNR 3\_s*' ..
+ '\d\+ NEWLIST size 3\_s*' ..
+ '\d\+ SETTYPE list<number>\_s*' ..
+ '\d\+ STORE $0\_s*' ..
+
+ 'unlet l\[2]\_s*' ..
+ '\d\+ PUSHNR 2\_s*' ..
+ '\d\+ LOAD $0\_s*' ..
+ '\d\+ UNLETINDEX\_s*' ..
+
+ 'unlet l\[0 : 1]\_s*' ..
+ '\d\+ PUSHNR 0\_s*' ..
+ '\d\+ PUSHNR 1\_s*' ..
+ '\d\+ LOAD $0\_s*' ..
+ '\d\+ UNLETRANGE\_s*',
+ res)
+enddef
+
+def s:LockLocal()
+ var d = {a: 1}
+ lockvar d.a
+ const nr = 22
+enddef
+
+def Test_disassemble_lock_local()
+ var res = execute('disass s:LockLocal')
+ assert_match('<SNR>\d*_LockLocal\_s*' ..
+ 'var d = {a: 1}\_s*' ..
+ '\d PUSHS "a"\_s*' ..
+ '\d PUSHNR 1\_s*' ..
+ '\d NEWDICT size 1\_s*' ..
+ '\d SETTYPE dict<number>\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'lockvar d.a\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d LOCKUNLOCK lockvar 2 d.a\_s*' ..
+
+ 'const nr = 22\_s*' ..
+ '\d\+ PUSHNR 22\_s*' ..
+ '\d\+ LOCKCONST\_s*' ..
+ '\d\+ STORE $1',
+ 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('<SNR>\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('<SNR>\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 s:FuncWithArg(arg: any)
+ echo arg
+enddef
+
+func s:UserFunc()
+ echo 'nothing'
+endfunc
+
+func s:UserFuncWithArg(arg)
+ echo a:arg
+endfunc
+
+def s:ScriptFuncCall(): string
+ changenr()
+ char2nr("abc")
+ g: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('<SNR>\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*' ..
+ 'g:Test_disassemble_new()\_s*' ..
+ '\d DCALL Test_disassemble_new(argc 0)\_s*' ..
+ '\d DROP\_s*' ..
+ 'FuncWithArg(343)\_s*' ..
+ '\d\+ PUSHNR 343\_s*' ..
+ '\d\+ DCALL <SNR>\d\+_FuncWithArg(argc 1)\_s*' ..
+ '\d\+ DROP\_s*' ..
+ 'ScriptFuncNew()\_s*' ..
+ '\d\+ DCALL <SNR>\d\+_ScriptFuncNew(argc 0)\_s*' ..
+ '\d\+ DROP\_s*' ..
+ 's:ScriptFuncNew()\_s*' ..
+ '\d\+ DCALL <SNR>\d\+_ScriptFuncNew(argc 0)\_s*' ..
+ '\d\+ DROP\_s*' ..
+ 'UserFunc()\_s*' ..
+ '\d\+ UCALL <80><fd>R\d\+_UserFunc(argc 0)\_s*' ..
+ '\d\+ DROP\_s*' ..
+ 'UserFuncWithArg("foo")\_s*' ..
+ '\d\+ PUSHS "foo"\_s*' ..
+ '\d\+ UCALL <80><fd>R\d\+_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('<lambda>\d\_s*' ..
+ 'local ..= arg\_s*' ..
+ '\d LOADOUTER level 1 $0\_s*' ..
+ '\d LOAD arg\[-1\]\_s*' ..
+ '\d CONCAT size 2\_s*' ..
+ '\d STOREOUTER level 1 $0\_s*' ..
+ '\d RETURN void',
+ res)
+
+ res = execute('disass g:Get')
+ assert_match('<lambda>\d\_s*' ..
+ 'return local\_s*' ..
+ '\d LOADOUTER level 1 $0\_s*' ..
+ '\d RETURN',
+ res)
+
+ unlet g:Append
+ unlet g:Get
+enddef
+
+def s:ClosureArg(arg: string)
+ var Ref = () => arg .. "x"
+enddef
+
+def Test_disassemble_closure_arg()
+ var res = execute('disass s:ClosureArg')
+ assert_match('<SNR>\d\+_ClosureArg\_s*' ..
+ 'var Ref = () => arg .. "x"\_s*' ..
+ '\d FUNCREF <lambda>\d\+',
+ res)
+ var lres = execute('disass ' .. matchstr(res, '<lambda>\d\+'))
+ assert_match('<lambda>\d\+\_s*' ..
+ 'return arg .. "x"\_s*' ..
+ '\d LOADOUTER level 1 arg\[-1]\_s*' ..
+ '\d PUSHS "x"\_s*' ..
+ '\d CONCAT size 2\_s*' ..
+ '\d RETURN',
+ lres)
+enddef
+
+def s:ClosureInLoop()
+ for i in range(5)
+ var ii = i
+ continue
+ break
+ if g:val
+ return
+ endif
+ g:Ref = () => ii
+ continue
+ break
+ if g:val
+ return
+ endif
+ endfor
+enddef
+
+" Mainly check that ENDLOOP is only produced after a closure was created.
+def Test_disassemble_closure_in_loop()
+ var res = execute('disass s:ClosureInLoop')
+ assert_match('<SNR>\d\+_ClosureInLoop\_s*' ..
+ 'for i in range(5)\_s*' ..
+ '\d\+ STORE -1 in $0\_s*' ..
+ '\d\+ PUSHNR 5\_s*' ..
+ '\d\+ BCALL range(argc 1)\_s*' ..
+ '\d\+ FOR $0 -> \d\+\_s*' ..
+ '\d\+ STORE $2\_s*' ..
+
+ 'var ii = i\_s*' ..
+ '\d\+ LOAD $2\_s*' ..
+ '\d\+ STORE $3\_s*' ..
+
+ 'continue\_s*' ..
+ '\d\+ JUMP -> \d\+\_s*' ..
+
+ 'break\_s*' ..
+ '\d\+ JUMP -> \d\+\_s*' ..
+
+ 'if g:val\_s*' ..
+ '\d\+ LOADG g:val\_s*' ..
+ '\d\+ COND2BOOL\_s*' ..
+ '\d\+ JUMP_IF_FALSE -> \d\+\_s*' ..
+
+ ' return\_s*' ..
+ '\d\+ PUSHNR 0\_s*' ..
+ '\d\+ RETURN\_s*' ..
+
+ 'endif\_s*' ..
+ 'g:Ref = () => ii\_s*' ..
+ '\d\+ FUNCREF <lambda>4 vars $3-$3\_s*' ..
+ '\d\+ STOREG g:Ref\_s*' ..
+
+ 'continue\_s*' ..
+ '\d\+ ENDLOOP ref $1 save $3-$3 depth 0\_s*' ..
+ '\d\+ JUMP -> \d\+\_s*' ..
+
+ 'break\_s*' ..
+ '\d\+ ENDLOOP ref $1 save $3-$3 depth 0\_s*' ..
+ '\d\+ JUMP -> \d\+\_s*' ..
+
+ 'if g:val\_s*' ..
+ '\d\+ LOADG g:val\_s*' ..
+ '\d\+ COND2BOOL\_s*' ..
+ '\d\+ JUMP_IF_FALSE -> \d\+\_s*' ..
+
+ ' return\_s*' ..
+ '\d\+ PUSHNR 0\_s*' ..
+ '\d\+ ENDLOOP ref $1 save $3-$3 depth 0\_s*' ..
+ '\d\+ RETURN\_s*' ..
+
+ 'endif\_s*' ..
+ 'endfor\_s*' ..
+ '\d\+ ENDLOOP ref $1 save $3-$3 depth 0\_s*' ..
+ '\d\+ JUMP -> \d\+\_s*' ..
+ '\d\+ DROP\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+def EchoArg(arg: string): string
+ return arg
+enddef
+def s:RefThis(): func
+ return function('EchoArg')
+enddef
+def s:ScriptPCall()
+ RefThis()("text")
+enddef
+
+def Test_disassemble_pcall()
+ var res = execute('disass s:ScriptPCall')
+ assert_match('<SNR>\d\+_ScriptPCall\_s*' ..
+ 'RefThis()("text")\_s*' ..
+ '\d DCALL <SNR>\d\+_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 size 2\_s*' ..
+ '\d\+ RETURN',
+ res)
+enddef
+
+
+def s:HasEval()
+ if has("eval")
+ echo "yes"
+ else
+ echo "no"
+ endif
+enddef
+
+def s:HasNothing()
+ if has("nothing")
+ echo "yes"
+ else
+ echo "no"
+ endif
+enddef
+
+def s:HasSomething()
+ if has("nothing")
+ echo "nothing"
+ elseif has("something")
+ echo "something"
+ elseif has("eval")
+ echo "eval"
+ elseif has("less")
+ echo "less"
+ endif
+enddef
+
+def s:HasGuiRunning()
+ if has("gui_running")
+ echo "yes"
+ else
+ echo "no"
+ endif
+enddef
+
+def s:LenConstant(): number
+ return len("foo") + len("fighters")
+enddef
+
+def Test_disassemble_const_expr()
+ var instr = execute('disassemble LenConstant')
+ assert_match('LenConstant\_s*' ..
+ 'return len("foo") + len("fighters")\_s*' ..
+ '\d PUSHNR 11\_s*',
+ instr)
+ assert_notmatch('BCALL len', instr)
+
+ assert_equal("\nyes", execute('HasEval()'))
+ 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 s: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 <lambda>\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, '.*\(<lambda>\d\+\).*', '\1', '')
+ instr = execute('disassemble ' .. name)
+ assert_match('<lambda>\d\+\_s*' ..
+ 'return "X" .. a .. "X"\_s*' ..
+ '\d PUSHS "X"\_s*' ..
+ '\d LOAD arg\[-1\]\_s*' ..
+ '\d 2STRING_ANY stack\[-1\]\_s*' ..
+ '\d CONCAT size 2\_s*' ..
+ '\d PUSHS "X"\_s*' ..
+ '\d CONCAT size 2\_s*' ..
+ '\d RETURN',
+ instr)
+enddef
+
+def s: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 <lambda>\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 <lambda>\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 s: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 s:AndConstant(arg: any): string
+ if true && arg
+ return "yes"
+ endif
+ if false && arg
+ return "never"
+ endif
+ return "no"
+enddef
+
+def Test_disassemble_and_constant()
+ assert_equal("yes", AndConstant(1))
+ assert_equal("no", AndConstant(false))
+ var instr = execute('disassemble AndConstant')
+ assert_match('AndConstant\_s*' ..
+ 'if true && arg\_s*' ..
+ '0 LOAD arg\[-1\]\_s*' ..
+ '1 COND2BOOL\_s*' ..
+ '2 JUMP_IF_FALSE -> 5\_s*' ..
+ 'return "yes"\_s*' ..
+ '3 PUSHS "yes"\_s*' ..
+ '4 RETURN\_s*' ..
+ 'endif\_s*' ..
+ 'if false && arg\_s*' ..
+ 'return "never"\_s*' ..
+ 'endif\_s*' ..
+ 'return "no"\_s*' ..
+ '5 PUSHS "no"\_s*' ..
+ '6 RETURN',
+ instr)
+enddef
+
+def s:ForLoop(): list<number>
+ var 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())
+ var instr = execute('disassemble ForLoop')
+ assert_match('ForLoop\_s*' ..
+ 'var res: list<number>\_s*' ..
+ '\d NEWLIST size 0\_s*' ..
+ '\d SETTYPE list<number>\_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 $3\_s*' ..
+
+ 'res->add(i)\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d LOAD $3\_s*' ..
+ '\d\+ LISTAPPEND\_s*' ..
+ '\d\+ DROP\_s*' ..
+
+ 'endfor\_s*' ..
+ '\d\+ JUMP -> \d\+\_s*' ..
+ '\d\+ DROP',
+ instr)
+enddef
+
+def s: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 $3\_s*' ..
+
+ 'res ..= str\_s*' ..
+ '\d\+ LOAD $0\_s*' ..
+ '\d\+ LOAD $3\_s*' ..
+ '\d 2STRING_ANY stack\[-1\]\_s*' ..
+ '\d\+ CONCAT size 2\_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 s: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 $2\_s*' ..
+ '\d\+ STORE $3\_s*' ..
+
+ 'echo x1 x2\_s*' ..
+ '\d\+ LOAD $2\_s*' ..
+ '\d\+ LOAD $3\_s*' ..
+ '\d\+ ECHO 2\_s*' ..
+
+ 'endfor\_s*' ..
+ '\d\+ JUMP -> 8\_s*' ..
+ '\d\+ DROP\_s*' ..
+ '\d\+ RETURN void',
+ instr)
+enddef
+
+def s: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 $2\_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 s:TypeCast()
+ var l: list<number> = [23, <number>g:number]
+enddef
+
+def Test_disassemble_typecast()
+ var instr = execute('disassemble TypeCast')
+ assert_match('TypeCast.*' ..
+ 'var l: list<number> = \[23, <number>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<number>\_s*' ..
+ '\d STORE $0\_s*' ..
+ '\d RETURN void\_s*',
+ instr)
+enddef
+
+def s: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
+
+ var fl = 3.0
+ var flres = fl + 7.0
+ flres = fl - 7.0
+ flres = fl * 7.0
+ flres = fl / 7.0
+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)
+ 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)
+enddef
+
+def s: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 s: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 s: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 s: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 s: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 SETTYPE list<number>\_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 s:ListSlice(): list<number>
+ 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 SETTYPE list<number>\_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\+ SETTYPE list<number>\_s*' ..
+ '\d\+ STORE $1\_s*',
+ instr)
+ assert_equal([2, 3], ListSlice())
+enddef
+
+def s: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 SETTYPE dict<number>\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'var res = d.item\_s*' ..
+ '\d\+ LOAD $0\_s*' ..
+ '\d\+ MEMBER item\_s*' ..
+ '\d\+ USEDICT\_s*' ..
+ '\d\+ STORE $1\_s*' ..
+ 'res = d\["item"\]\_s*' ..
+ '\d\+ LOAD $0\_s*' ..
+ '\d\+ PUSHS "item"\_s*' ..
+ '\d\+ MEMBER\_s*' ..
+ '\d\+ USEDICT\_s*' ..
+ '\d\+ STORE $1\_s*',
+ instr)
+ assert_equal(1, DictMember())
+enddef
+
+let somelist = [1, 2, 3, 4, 5]
+def s: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 s:AnySlice(): list<number>
+ 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<number> stack\[-1\]\_s*' ..
+ '\d RETURN',
+ instr)
+ assert_equal([2, 3, 4], AnySlice())
+enddef
+
+def s: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 s: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 s:ReturnBool(): bool
+ var one = 1
+ var zero = 0
+ var none: number
+ var name: bool = one && zero || one
+ return name
+enddef
+
+def Test_disassemble_return_bool()
+ var instr = execute('disassemble ReturnBool')
+ assert_match('ReturnBool\_s*' ..
+ 'var one = 1\_s*' ..
+ '0 STORE 1 in $0\_s*' ..
+ 'var zero = 0\_s*' ..
+ 'var none: number\_s*' ..
+ 'var name: bool = one && zero || one\_s*' ..
+ '1 LOAD $0\_s*' ..
+ '2 COND2BOOL\_s*' ..
+ '3 JUMP_IF_COND_FALSE -> 6\_s*' ..
+ '4 LOAD $1\_s*' ..
+ '5 COND2BOOL\_s*' ..
+ '6 JUMP_IF_COND_TRUE -> 9\_s*' ..
+ '7 LOAD $0\_s*' ..
+ '8 COND2BOOL\_s*' ..
+ '9 STORE $3\_s*' ..
+ 'return name\_s*' ..
+ '\d\+ LOAD $3\_s*' ..
+ '\d\+ RETURN',
+ instr)
+ assert_equal(true, InvertBool())
+enddef
+
+def s:AutoInit()
+ var t: number
+ t = 1
+ t = 0
+enddef
+
+def Test_disassemble_auto_init()
+ var instr = execute('disassemble AutoInit')
+ assert_match('AutoInit\_s*' ..
+ 'var t: number\_s*' ..
+ 't = 1\_s*' ..
+ '\d STORE 1 in $0\_s*' ..
+ 't = 0\_s*' ..
+ '\d STORE 0 in $0\_s*' ..
+ '\d\+ RETURN void',
+ instr)
+enddef
+
+def Test_disassemble_compare()
+ var cases = [
+ ['true == isFalse', 'COMPAREBOOL =='],
+ ['true != isFalse', 'COMPAREBOOL !='],
+ ['v:none == isNull', 'COMPARESPECIAL =='],
+ ['v:none != isNull', 'COMPARESPECIAL !='],
+ ['"text" == isNull', 'COMPARENULL =='],
+ ['"text" != isNull', 'COMPARENULL !='],
+
+ ['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 = ''
+ 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'
+
+ 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_disassemble_falsy_op()
+ var res = execute('disass s:FalsyOp')
+ assert_match('\<SNR>\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('\<SNR>\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 size 2\_s*' ..
+ '\d\+ EXECUTE 1\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+def s:OnlyRange()
+ :$
+ :123
+ :'m
+enddef
+
+def Test_disassemble_range_only()
+ var res = execute('disass s:OnlyRange')
+ assert_match('\<SNR>\d*_OnlyRange\_s*' ..
+ ':$\_s*' ..
+ '\d EXECRANGE $\_s*' ..
+ ':123\_s*' ..
+ '\d EXECRANGE 123\_s*' ..
+ ':''m\_s*' ..
+ '\d EXECRANGE ''m\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+def s:StoreRange()
+ var l = [1, 2]
+ l[0 : 1] = [7, 8]
+enddef
+
+def Test_disassemble_store_range()
+ var res = execute('disass s:StoreRange')
+ assert_match('\<SNR>\d*_StoreRange\_s*' ..
+ 'var l = \[1, 2]\_s*' ..
+ '\d PUSHNR 1\_s*' ..
+ '\d PUSHNR 2\_s*' ..
+ '\d NEWLIST size 2\_s*' ..
+ '\d SETTYPE list<number>\_s*' ..
+ '\d STORE $0\_s*' ..
+
+ 'l\[0 : 1] = \[7, 8]\_s*' ..
+ '\d\+ PUSHNR 7\_s*' ..
+ '\d\+ PUSHNR 8\_s*' ..
+ '\d\+ NEWLIST size 2\_s*' ..
+ '\d\+ PUSHNR 0\_s*' ..
+ '\d\+ PUSHNR 1\_s*' ..
+ '\d\+ LOAD $0\_s*' ..
+ '\d\+ STORERANGE\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+def s:Echomsg()
+ echomsg 'some' 'message'
+ echoconsole 'nothing'
+ echoerr 'went' .. 'wrong'
+ var local = 'window'
+ echowin 'in' local
+ :5echowin 'five'
+enddef
+
+def Test_disassemble_echomsg()
+ var 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*' ..
+ "echoconsole 'nothing'\\_s*" ..
+ '\d PUSHS "nothing"\_s*' ..
+ '\d ECHOCONSOLE 1\_s*' ..
+ "echoerr 'went' .. 'wrong'\\_s*" ..
+ '\d PUSHS "wentwrong"\_s*' ..
+ '\d ECHOERR 1\_s*' ..
+ "var local = 'window'\\_s*" ..
+ '\d\+ PUSHS "window"\_s*' ..
+ '\d\+ STORE $0\_s*' ..
+ "echowin 'in' local\\_s*" ..
+ '\d\+ PUSHS "in"\_s*' ..
+ '\d\+ LOAD $0\_s*' ..
+ '\d\+ ECHOWINDOW 2\_s*' ..
+ ":5echowin 'five'\\_s*" ..
+ '\d\+ PUSHS "five"\_s*' ..
+ '\d\+ ECHOWINDOW 1 (5 sec)\_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', 'D')
+ 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)
+
+ 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('<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
+
+def s:CallAppend()
+ eval "some text"->append(2)
+enddef
+
+def Test_shuffle()
+ var res = execute('disass s:CallAppend')
+ assert_match('<SNR>\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('<SNR>\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('<SNR>\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('<SNR>\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 $2\_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('<SNR>\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 WHILE $0 -> 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('<SNR>\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('<SNR>\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('<SNR>\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:ElseifConstant()
+ if g:value
+ echo "one"
+ elseif true
+ echo "true"
+ elseif false
+ echo "false"
+ endif
+ if 0
+ echo "yes"
+ elseif 0
+ echo "no"
+ endif
+enddef
+
+def Test_debug_elseif_constant()
+ var res = execute('disass debug s:ElseifConstant')
+ assert_match('<SNR>\d*_ElseifConstant\_s*' ..
+ 'if g:value\_s*' ..
+ '0 DEBUG line 1-1 varcount 0\_s*' ..
+ '1 LOADG g:value\_s*' ..
+ '2 COND2BOOL\_s*' ..
+ '3 JUMP_IF_FALSE -> 8\_s*' ..
+ 'echo "one"\_s*' ..
+ '4 DEBUG line 2-2 varcount 0\_s*' ..
+ '5 PUSHS "one"\_s*' ..
+ '6 ECHO 1\_s*' ..
+ 'elseif true\_s*' ..
+ '7 JUMP -> 12\_s*' ..
+ '8 DEBUG line 3-3 varcount 0\_s*' ..
+ 'echo "true"\_s*' ..
+ '9 DEBUG line 4-4 varcount 0\_s*' ..
+ '10 PUSHS "true"\_s*' ..
+ '11 ECHO 1\_s*' ..
+ 'elseif false\_s*' ..
+ 'echo "false"\_s*' ..
+ 'endif\_s*' ..
+ 'if 0\_s*' ..
+ '12 DEBUG line 8-8 varcount 0\_s*' ..
+ 'echo "yes"\_s*' ..
+ 'elseif 0\_s*' ..
+ '13 DEBUG line 11-10 varcount 0\_s*' ..
+ 'echo "no"\_s*' ..
+ 'endif\_s*' ..
+ '14 RETURN void*',
+ 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('<SNR>\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:DebugFor()
+ echo "hello"
+ for a in [0]
+ echo a
+ endfor
+enddef
+
+def Test_debug_for()
+ var res = execute('disass debug s:DebugFor')
+ assert_match('<SNR>\d*_DebugFor\_s*' ..
+ 'echo "hello"\_s*' ..
+ '0 DEBUG line 1-1 varcount 0\_s*' ..
+ '1 PUSHS "hello"\_s*' ..
+ '2 ECHO 1\_s*' ..
+
+ 'for a in \[0\]\_s*' ..
+ '3 DEBUG line 2-2 varcount 0\_s*' ..
+ '4 STORE -1 in $0\_s*' ..
+ '5 PUSHNR 0\_s*' ..
+ '6 NEWLIST size 1\_s*' ..
+ '7 DEBUG line 2-2 varcount 3\_s*' ..
+ '8 FOR $0 -> 15\_s*' ..
+ '9 STORE $2\_s*' ..
+
+ 'echo a\_s*' ..
+ '10 DEBUG line 3-3 varcount 3\_s*' ..
+ '11 LOAD $2\_s*' ..
+ '12 ECHO 1\_s*' ..
+
+ 'endfor\_s*' ..
+ '13 DEBUG line 4-4 varcount 3\_s*' ..
+ '14 JUMP -> 7\_s*' ..
+ '15 DROP\_s*' ..
+ '16 RETURN void*',
+ res)
+enddef
+
+def s:TryCatch()
+ try
+ echo "try"
+ catch /error/
+ echo "caught"
+ endtry
+enddef
+
+def Test_debug_try_catch()
+ var res = execute('disass debug s:TryCatch')
+ assert_match('<SNR>\d*_TryCatch\_s*' ..
+ 'try\_s*' ..
+ '0 DEBUG line 1-1 varcount 0\_s*' ..
+ '1 TRY catch -> 7, endtry -> 17\_s*' ..
+ 'echo "try"\_s*' ..
+ '2 DEBUG line 2-2 varcount 0\_s*' ..
+ '3 PUSHS "try"\_s*' ..
+ '4 ECHO 1\_s*' ..
+ 'catch /error/\_s*' ..
+ '5 DEBUG line 3-3 varcount 0\_s*' ..
+ '6 JUMP -> 17\_s*' ..
+ '7 DEBUG line 4-3 varcount 0\_s*' ..
+ '8 PUSH v:exception\_s*' ..
+ '9 PUSHS "error"\_s*' ..
+ '10 COMPARESTRING =\~\_s*' ..
+ '11 JUMP_IF_FALSE -> 17\_s*' ..
+ '12 CATCH\_s*' ..
+ 'echo "caught"\_s*' ..
+ '13 DEBUG line 4-4 varcount 0\_s*' ..
+ '14 PUSHS "caught"\_s*' ..
+ '15 ECHO 1\_s*' ..
+ 'endtry\_s*' ..
+ '16 DEBUG line 5-5 varcount 0\_s*' ..
+ '17 ENDTRY\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+func s:Legacy() dict
+ echo 'legacy'
+endfunc
+
+def s:UseMember()
+ var d = {func: Legacy}
+ var v = d.func()
+enddef
+
+def Test_disassemble_dict_stack()
+ var res = execute('disass s:UseMember')
+ assert_match('<SNR>\d*_UseMember\_s*' ..
+ 'var d = {func: Legacy}\_s*' ..
+ '\d PUSHS "func"\_s*' ..
+ '\d PUSHFUNC "<80><fd>R\d\+_Legacy"\_s*' ..
+ '\d NEWDICT size 1\_s*' ..
+ '\d SETTYPE dict<func(...): any>\_s*' ..
+ '\d STORE $0\_s*' ..
+
+ 'var v = d.func()\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d MEMBER func\_s*' ..
+ '\d PCALL top (argc 0)\_s*' ..
+ '\d PCALL end\_s*' ..
+ '\d CLEARDICT\_s*' ..
+ '\d\+ STORE $1\_s*' ..
+ '\d\+ RETURN void*',
+ res)
+enddef
+
+def s:RetLegacy(): string
+ legacy return "yes"
+enddef
+
+def Test_disassemble_return_legacy()
+ var res = execute('disass s:RetLegacy')
+ assert_match('<SNR>\d*_RetLegacy\_s*' ..
+ 'legacy return "yes"\_s*' ..
+ '\d CMDMOD legacy\_s*' ..
+ '\d EVAL legacy "yes"\_s*' ..
+ '\d CHECKTYPE string stack\[-1]\_s*' ..
+ '\d CMDMOD_REV\_s*' ..
+ '\d RETURN',
+ 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('<SNR>\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', 'D')
+
+ 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)
+
+ delfunc g:ThisFunc
+ delfunc g:ThatFunc
+enddef
+
+def s:MakeString(x: number): string
+ return $"x={x} x^2={x * x}"
+enddef
+
+def Test_disassemble_string_interp()
+ var instr = execute('disassemble s:MakeString')
+ assert_match('MakeString\_s*' ..
+ 'return $"x={x} x^2={x \* x}"\_s*' ..
+ '0 PUSHS "x="\_s*' ..
+ '1 LOAD arg\[-1\]\_s*' ..
+ '2 2STRING stack\[-1\]\_s*' ..
+ '3 PUSHS " x^2="\_s*' ..
+ '4 LOAD arg\[-1\]\_s*' ..
+ '5 LOAD arg\[-1\]\_s*' ..
+ '6 OPNR \*\_s*' ..
+ '7 2STRING stack\[-1\]\_s*' ..
+ '8 CONCAT size 4\_s*' ..
+ '9 RETURN\_s*',
+ instr)
+enddef
+
+def BitShift()
+ var a = 1 << 2
+ var b = 8 >> 1
+ var c = a << b
+ var d = b >> a
+enddef
+
+def Test_disassemble_bitshift()
+ var instr = execute('disassemble BitShift')
+ assert_match('BitShift\_s*' ..
+ 'var a = 1 << 2\_s*' ..
+ '0 STORE 4 in $0\_s*' ..
+ 'var b = 8 >> 1\_s*' ..
+ '1 STORE 4 in $1\_s*' ..
+ 'var c = a << b\_s*' ..
+ '2 LOAD $0\_s*' ..
+ '3 LOAD $1\_s*' ..
+ '4 OPNR <<\_s*' ..
+ '5 STORE $2\_s*' ..
+ 'var d = b >> a\_s*' ..
+ '6 LOAD $1\_s*' ..
+ '7 LOAD $0\_s*' ..
+ '8 OPNR >>\_s*' ..
+ '9 STORE $3\_s*' ..
+ '10 RETURN void', instr)
+enddef
+
+def s:OneDefer()
+ defer delete("file")
+enddef
+
+def Test_disassemble_defer()
+ var instr = execute('disassemble s:OneDefer')
+ assert_match('OneDefer\_s*' ..
+ 'defer delete("file")\_s*' ..
+ '\d PUSHFUNC "delete"\_s*' ..
+ '\d PUSHS "file"\_s*' ..
+ '\d DEFER 1 args\_s*' ..
+ '\d RETURN\_s*',
+ instr)
+enddef
+
+def Test_disassemble_class_function()
+ var lines =<< trim END
+ vim9script
+
+ class Cl
+ static def Fc(): string
+ return "x"
+ enddef
+ endclass
+
+ g:instr = execute('disassemble Cl.Fc')
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_match('Fc\_s*' ..
+ 'return "x"\_s*' ..
+ '\d PUSHS "x"\_s*' ..
+ '\d RETURN\_s*',
+ g:instr)
+
+ lines =<< trim END
+ vim9script
+
+ class Cl
+ def Fo(): string
+ return "y"
+ enddef
+ endclass
+
+ g:instr = execute('disassemble Cl.Fo')
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_match('Fo\_s*' ..
+ 'return "y"\_s*' ..
+ '\d PUSHS "y"\_s*' ..
+ '\d RETURN\_s*',
+ g:instr)
+
+ unlet g:instr
+enddef
+
+" Disassemble instructions for using an interface with static and regular member
+" variables.
+def Test_disassemble_interface_static_member()
+ var lines =<< trim END
+ vim9script
+ interface I
+ var o_var: number
+ var o_var2: number
+ endinterface
+
+ class C implements I
+ public static var s_var: number
+ var o_var: number
+ public static var s_var2: number
+ var o_var2: number
+ endclass
+
+ def F1(i: I)
+ var x: number
+ x = i.o_var
+ x = i.o_var2
+ enddef
+
+ def F2(o: C)
+ var x: number
+ x = o.o_var
+ x = o.o_var2
+ enddef
+
+ g:instr1 = execute('disassemble F1')
+ g:instr2 = execute('disassemble F2')
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_match('<SNR>\d*_F1\_s*' ..
+ 'var x: number\_s*' ..
+ 'x = i.o_var\_s*' ..
+ '0 LOAD arg\[-1\]\_s*' ..
+ '1 ITF_MEMBER 0 on I\_s*' ..
+ '2 STORE $0\_s*' ..
+ 'x = i.o_var2\_s*' ..
+ '3 LOAD arg\[-1\]\_s*' ..
+ '4 ITF_MEMBER 1 on I\_s*' ..
+ '5 STORE $0\_s*' ..
+ '6 RETURN void\_s*',
+ g:instr1)
+ assert_match('<SNR>\d*_F2\_s*' ..
+ 'var x: number\_s*' ..
+ 'x = o.o_var\_s*' ..
+ '0 LOAD arg\[-1\]\_s*' ..
+ '1 OBJ_MEMBER 0\_s*' ..
+ '2 STORE $0\_s*' ..
+ 'x = o.o_var2\_s*' ..
+ '3 LOAD arg\[-1\]\_s*' ..
+ '4 OBJ_MEMBER 1\_s*' ..
+ '5 STORE $0\_s*' ..
+ '6 RETURN void',
+ g:instr2)
+
+ unlet g:instr1
+ unlet g:instr2
+enddef
+
+" Disassemble instructions for loading and storing class variables
+def Test_disassemble_class_variable()
+ var lines =<< trim END
+ vim9script
+
+ class A
+ public static var val = 10
+ def Foo(): number
+ val = 20
+ return val
+ enddef
+ endclass
+
+ g:instr = execute('disassemble A.Foo')
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_match('Foo\_s*' ..
+ 'val = 20\_s*' ..
+ '0 PUSHNR 20\_s*' ..
+ '1 STORE CLASSMEMBER A.val\_s*' ..
+ 'return val\_s*' ..
+ '2 LOAD CLASSMEMBER A.val\_s*' ..
+ '3 RETURN', g:instr)
+
+ unlet g:instr
+enddef
+
+" Disassemble instructions for METHODCALL
+def Test_disassemble_methodcall()
+ var lines =<< trim END
+ vim9script
+ interface A
+ def Foo()
+ endinterface
+ def Bar(a: A)
+ a.Foo()
+ enddef
+ g:instr = execute('disassemble Bar')
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_match('<SNR>\d*_Bar\_s*' ..
+ 'a.Foo()\_s*' ..
+ '0 LOAD arg\[-1\]\_s*' ..
+ '1 METHODCALL A.Foo(argc 0)\_s*' ..
+ '2 DROP\_s*' ..
+ '3 RETURN void', g:instr)
+
+ unlet g:instr
+enddef
+
+" Disassemble instructions for ISN_JUMP_IF_ARG_NOT_SET
+def Test_disassemble_ifargnotset()
+ var lines =<< trim END
+ vim9script
+ class A
+ var val: number = 10
+ endclass
+ g:instr = execute('disassemble A.new')
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_match('new\_s*' ..
+ '0 NEW A size \d\+\_s*' ..
+ '1 PUSHNR 10\_s*' ..
+ '2 STORE_THIS 0\_s*' ..
+ 'ifargisset 0 this.val = val\_s*' ..
+ '3 JUMP_IF_ARG_NOT_SET arg\[-1\] -> 8\_s*' ..
+ '4 LOAD arg\[-1\]\_s*' ..
+ '5 PUSHNR 0\_s*' ..
+ '6 LOAD $0\_s*' ..
+ '7 STOREINDEX object\_s*' ..
+ '8 RETURN object', g:instr)
+
+ unlet g:instr
+enddef
+
+" Disassemble instructions for ISN_COMPAREOBJECT
+def Test_disassemble_compare_class_object()
+ var lines =<< trim END
+ vim9script
+ class A
+ endclass
+ class B
+ endclass
+ def Foo(a: A, b: B)
+ if a == b
+ endif
+ enddef
+ g:instr = execute('disassemble Foo')
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_match('<SNR>\d*_Foo\_s*' ..
+ 'if a == b\_s*' ..
+ '0 LOAD arg\[-2\]\_s*' ..
+ '1 LOAD arg\[-1\]\_s*' ..
+ '2 COMPAREOBJECT ==\_s*' ..
+ '3 JUMP_IF_FALSE -> 4\_s*' ..
+ 'endif\_s*' ..
+ '4 RETURN void', g:instr)
+ unlet g:instr
+enddef
+
+" Disassemble instructions for ISN_CHECKTYPE with a float|number
+def Test_checktype_float()
+ var lines =<< trim END
+ vim9script
+ def Foo()
+ var f: float = 0.0
+ var a: any
+ f += a
+ enddef
+ g:instr = execute('disassemble Foo')
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_match('<SNR>\d*_Foo\_s*' ..
+ 'var f: float = 0.0\_s*' ..
+ '0 PUSHF 0.0\_s*' ..
+ '1 STORE $0\_s*' ..
+ 'var a: any\_s*' ..
+ 'f += a\_s*' ..
+ '2 LOAD $0\_s*' ..
+ '3 LOAD $1\_s*' ..
+ '4 CHECKTYPE float|number stack\[-1\]\_s*' ..
+ '5 OPANY +\_s*' ..
+ '6 STORE $0\_s*' ..
+ '7 RETURN void', g:instr)
+ unlet g:instr
+enddef
+
+" Disassemble instructions for ISN_FUNCREF with a class
+def Test_funcref_with_class()
+ var lines =<< trim END
+ vim9script
+ class A
+ def Foo()
+ enddef
+ endclass
+ class B extends A
+ def Foo()
+ enddef
+ endclass
+ def Bar(a: A)
+ defer a.Foo()
+ enddef
+ g:instr = execute('disassemble Bar')
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_match('<SNR>\d*_Bar\_s*' ..
+ 'defer a.Foo()\_s*' ..
+ '0 LOAD arg\[-1\]\_s*' ..
+ '1 FUNCREF A.Foo\_s*' ..
+ '2 DEFER 0 args\_s*' ..
+ '3 RETURN void', g:instr)
+ unlet g:instr
+enddef
+
+" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker