view src/testdir/test_vim9_disassemble.vim @ 33815:08f9e1eac4cf v9.0.2123

patch 9.0.2123: Problem with initializing the length of range() lists Commit: https://github.com/vim/vim/commit/df63da98d8dc284b1c76cfe1b17fa0acbd6094d8 Author: Christian Brabandt <cb@256bit.org> Date: Thu Nov 23 20:14:28 2023 +0100 patch 9.0.2123: Problem with initializing the length of range() lists Problem: Problem with initializing the length of range() lists Solution: Set length explicitly when it shouldn't contain any items range() may cause a wrong calculation of list length, which may later then cause a segfault in list_find(). This is usually not a problem, because range_list_materialize() calculates the length, when it materializes the list. In addition, in list_find() when the length of the range was wrongly initialized, it may seem to be valid, so the check for list index out-of-bounds will not be true, because it is called before the list is actually materialized. And so we may eventually try to access a null pointer, causing a segfault. So this patch does 3 things: - In f_range(), when we know that the list should be empty, explicitly set the list->lv_len value to zero. This should happen, when start is larger than end (in case the stride is positive) or end is larger than start when the stride is negative. This should fix the underlying issue properly. However, - as a safety measure, let's check that the requested index is not out of range one more time, after the list has been materialized and return NULL in case it suddenly is. - add a few more tests to verify the behaviour. fixes: #13557 closes: #13563 Co-authored-by: Tim Pope <tpope@github.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
author Christian Brabandt <cb@256bit.org>
date Thu, 23 Nov 2023 20:30:07 +0100
parents 2fc593290679
children cd7acb9bc4fd
line wrap: on
line source

" 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<unknown>>\_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
      this.o_var: number
      this.o_var2: number
    endinterface

    class C implements I
      public static s_var: number
      this.o_var: number
      public static s_var2: number
      this.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 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
      this.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_COMPARECLASS and 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
      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 LOADSCRIPT A-0 from .*\_s*' ..
    '1 LOADSCRIPT B-1 from .*\_s*' ..
    '2 COMPARECLASS ==\_s*' ..
    '3 JUMP_IF_FALSE -> 4\_s*' ..
    'endif\_s*' ..
    'if a == b\_s*' ..
    '4 LOAD arg\[-2\]\_s*' ..
    '5 LOAD arg\[-1\]\_s*' ..
    '6 COMPAREOBJECT ==\_s*' ..
    '7 JUMP_IF_FALSE -> 8\_s*' ..
    'endif\_s*' ..
    '8 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