view src/testdir/test_vim9_disassemble.vim @ 23955:d7f49f279146

Added tag v8.2.2519 for changeset 9c8a20cfa540615c91c7b1019da172f42fb13015
author Bram Moolenaar <Bram@vim.org>
date Mon, 15 Feb 2021 21:45:03 +0100
parents 5e5780e3f75d
children 3daeb2060f25
line wrap: on
line source

" Test the :disassemble command, and compilation as a side effect

source check.vim

func NotCompiled()
  echo "not"
endfunc

let s:scriptvar = 4
let g:globalvar = 'g'
let b:buffervar = 'b'
let w:windowvar = 'w'
let t:tabpagevar = 't'

def s:ScriptFuncLoad(arg: string)
  var local = 1
  buffers
  echo
  echo arg
  echo local
  echo &lines
  echo v:version
  echo s:scriptvar
  echo g:globalvar
  echo get(g:, "global")
  echo g:auto#var
  echo b:buffervar
  echo get(b:, "buffer")
  echo w:windowvar
  echo get(w:, "window")
  echo t:tabpagevar
  echo get(t:, "tab")
  echo &tabstop
  echo $ENVVAR
  echo @z
enddef

def Test_disassemble_load()
  assert_fails('disass NoFunc', 'E1061:')
  assert_fails('disass NotCompiled', 'E1091:')
  assert_fails('disass', 'E471:')
  assert_fails('disass [', 'E475:')
  assert_fails('disass 234', 'E129:')
  assert_fails('disass <XX>foo', 'E129:')

  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 0',
        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 0',
        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 0',
        res)
enddef

def s:PutRange()
  :$-2put a
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*' ..
        '\d RETURN 0',
        res)
enddef

def s:ScriptFuncPush()
  var localbool = true
  var localspec = v:none
  var localblob = 0z1234
  if has('float')
    var localfloat = 1.234
  endif
enddef

def Test_disassemble_push()
  var res = execute('disass s:ScriptFuncPush')
  assert_match('<SNR>\d*_ScriptFuncPush.*' ..
        'localbool = true.*' ..
        ' PUSH true.*' ..
        'localspec = v:none.*' ..
        ' PUSH v:none.*' ..
        'localblob = 0z1234.*' ..
        ' PUSHBLOB 0z1234.*',
        res)
  if has('float')
    assert_match('<SNR>\d*_ScriptFuncPush.*' ..
          'localfloat = 1.234.*' ..
          ' PUSHF 1.234.*',
          res)
  endif
enddef

def s:ScriptFuncStore()
  var localnr = 1
  localnr = 2
  var localstr = 'abc'
  localstr = 'xyz'
  v:char = 'abc'
  s:scriptvar = 'sv'
  g:globalvar = 'gv'
  g:auto#var = 'av'
  b:buffervar = 'bv'
  w:windowvar = 'wv'
  t:tabpagevar = 'tv'
  &tabstop = 8
  $ENVVAR = 'ev'
  @z = 'rv'
enddef

def Test_disassemble_store()
  var res = execute('disass s:ScriptFuncStore')
  assert_match('<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.*' ..
        ' STOREOPT &tabstop.*' ..
        '$ENVVAR = ''ev''.*' ..
        ' STOREENV $ENVVAR.*' ..
        '@z = ''rv''.*' ..
        ' STOREREG @z.*',
        res)
enddef

def s:ScriptFuncStoreMember()
  var locallist: list<number> = []
  locallist[0] = 123
  var localdict: dict<number> = {}
  localdict["a"] = 456
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 STORELIST\_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\+ STOREDICT\_s*' ..
        '\d\+ RETURN 0',
        res)
enddef

def s:ScriptFuncStoreIndex()
  var d = {dd: {}}
  d.dd[0] = 0
enddef

def Test_disassemble_store_index()
  var res = execute('disass s:ScriptFuncStoreIndex')
  assert_match('<SNR>\d*_ScriptFuncStoreIndex\_s*' ..
        'var d = {dd: {}}\_s*' ..
        '\d PUSHS "dd"\_s*' ..
        '\d NEWDICT size 0\_s*' ..
        '\d NEWDICT size 1\_s*' ..
        '\d STORE $0\_s*' ..
        'd.dd\[0\] = 0\_s*' ..
        '\d PUSHNR 0\_s*' ..
        '\d PUSHNR 0\_s*' ..
        '\d LOAD $0\_s*' ..
        '\d MEMBER dd\_s*' ..
        '\d STOREINDEX\_s*' ..
        '\d\+ RETURN 0',
        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\]\_s*' ..
        '\d\+ STORE $0\_s*' ..
        '\d\+ ITEM 1\_s*' ..
        '\d\+ CHECKTYPE string stack\[-1\]\_s*' ..
        '\d\+ STORE $1\_s*' ..
        '\d\+ SLICE 2\_s*' ..
        '\d\+ STORE $2\_s*' ..
        '\d\+ RETURN 0',
        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 0',
        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 0',
        res)
enddef

def s:ScriptFuncUnlet()
  g:somevar = "value"
  unlet g:somevar
  unlet! g:somevar
  unlet $SOMEVAR
enddef

def Test_disassemble_unlet()
  var res = execute('disass s:ScriptFuncUnlet')
  assert_match('<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*',
        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\+\_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*' ..
        '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 FuncWithArg(arg: any)
  echo arg
enddef

func UserFunc()
  echo 'nothing'
endfunc

func UserFuncWithArg(arg)
  echo a:arg
endfunc

def s:ScriptFuncCall(): string
  changenr()
  char2nr("abc")
  Test_disassemble_new()
  FuncWithArg(343)
  ScriptFuncNew()
  s:ScriptFuncNew()
  UserFunc()
  UserFuncWithArg("foo")
  var FuncRef = function("UserFunc")
  FuncRef()
  var FuncRefWithArg = function("UserFuncWithArg")
  FuncRefWithArg("bar")
  return "yes"
enddef

def Test_disassemble_call()
  var res = execute('disass s:ScriptFuncCall')
  assert_match('<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*' ..
        'Test_disassemble_new()\_s*' ..
        '\d DCALL Test_disassemble_new(argc 0)\_s*' ..
        '\d DROP\_s*' ..
        'FuncWithArg(343)\_s*' ..
        '\d\+ PUSHNR 343\_s*' ..
        '\d\+ DCALL FuncWithArg(argc 1)\_s*' ..
        '\d\+ DROP\_s*' ..
        'ScriptFuncNew()\_s*' ..
        '\d\+ DCALL <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 UserFunc(argc 0)\_s*' ..
        '\d\+ DROP\_s*' ..
        'UserFuncWithArg("foo")\_s*' ..
        '\d\+ PUSHS "foo"\_s*' ..
        '\d\+ UCALL UserFuncWithArg(argc 1)\_s*' ..
        '\d\+ DROP\_s*' ..
        'var FuncRef = function("UserFunc")\_s*' ..
        '\d\+ PUSHS "UserFunc"\_s*' ..
        '\d\+ BCALL function(argc 1)\_s*' ..
        '\d\+ STORE $0\_s*' ..
        'FuncRef()\_s*' ..
        '\d\+ LOAD $\d\_s*' ..
        '\d\+ PCALL (argc 0)\_s*' ..
        '\d\+ DROP\_s*' ..
        'var FuncRefWithArg = function("UserFuncWithArg")\_s*' ..
        '\d\+ PUSHS "UserFuncWithArg"\_s*' ..
        '\d\+ BCALL function(argc 1)\_s*' ..
        '\d\+ STORE $1\_s*' ..
        'FuncRefWithArg("bar")\_s*' ..
        '\d\+ PUSHS "bar"\_s*' ..
        '\d\+ LOAD $\d\_s*' ..
        '\d\+ PCALL (argc 1)\_s*' ..
        '\d\+ DROP\_s*' ..
        'return "yes"\_s*' ..
        '\d\+ PUSHS "yes"\_s*' ..
        '\d\+ RETURN',
        res)
enddef


def s:CreateRefs()
  var local = 'a'
  def Append(arg: string)
    local ..= arg
  enddef
  g:Append = Append
  def Get(): string
    return local
  enddef
  g:Get = Get
enddef

def Test_disassemble_closure()
  CreateRefs()
  var res = execute('disass g:Append')
  assert_match('<lambda>\d\_s*' ..
        'local ..= arg\_s*' ..
        '\d LOADOUTER level 1 $0\_s*' ..
        '\d LOAD arg\[-1\]\_s*' ..
        '\d CONCAT\_s*' ..
        '\d STOREOUTER level 1 $0\_s*' ..
        '\d RETURN 0',
        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 EchoArg(arg: string): string
  return arg
enddef
def RefThis(): func
  return function('EchoArg')
enddef
def s:ScriptPCall()
  RefThis()("text")
enddef

def Test_disassemble_pcall()
  var res = execute('disass s:ScriptPCall')
  assert_match('<SNR>\d\+_ScriptPCall\_s*' ..
        'RefThis()("text")\_s*' ..
        '\d DCALL RefThis(argc 0)\_s*' ..
        '\d PUSHS "text"\_s*' ..
        '\d PCALL top (argc 1)\_s*' ..
        '\d PCALL end\_s*' ..
        '\d DROP\_s*' ..
        '\d RETURN 0',
        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(arg: string = 'default'): string
  return arg
enddef

def Test_disassemble_call_default()
  var res = execute('disass FuncWithDefault')
  assert_match('FuncWithDefault\_s*' ..
        '\d PUSHS "default"\_s*' ..
        '\d STORE arg\[-1]\_s*' ..
        'return arg\_s*' ..
        '\d LOAD arg\[-1]\_s*' ..
        '\d RETURN',
        res)
enddef


def HasEval()
  if has("eval")
    echo "yes"
  else
    echo "no"
  endif
enddef

def HasNothing()
  if has("nothing")
    echo "yes"
  else
    echo "no"
  endif
enddef

def HasSomething()
  if has("nothing")
    echo "nothing"
  elseif has("something")
    echo "something"
  elseif has("eval")
    echo "eval"
  elseif has("less")
    echo "less"
  endif
enddef

def HasGuiRunning()
  if has("gui_running")
    echo "yes"
  else
    echo "no"
  endif
enddef

def Test_disassemble_const_expr()
  assert_equal("\nyes", execute('HasEval()'))
  var instr = execute('disassemble HasEval')
  assert_match('HasEval\_s*' ..
        'if has("eval")\_s*' ..
        'echo "yes"\_s*' ..
        '\d PUSHS "yes"\_s*' ..
        '\d ECHO 1\_s*' ..
        'else\_s*' ..
        'echo "no"\_s*' ..
        'endif\_s*',
        instr)
  assert_notmatch('JUMP', instr)

  assert_equal("\nno", execute('HasNothing()'))
  instr = execute('disassemble HasNothing')
  assert_match('HasNothing\_s*' ..
        'if has("nothing")\_s*' ..
        'echo "yes"\_s*' ..
        'else\_s*' ..
        'echo "no"\_s*' ..
        '\d PUSHS "no"\_s*' ..
        '\d ECHO 1\_s*' ..
        'endif',
        instr)
  assert_notmatch('PUSHS "yes"', instr)
  assert_notmatch('JUMP', instr)

  assert_equal("\neval", execute('HasSomething()'))
  instr = execute('disassemble HasSomething')
  assert_match('HasSomething.*' ..
        'if has("nothing")\_s*' ..
        'echo "nothing"\_s*' ..
        'elseif has("something")\_s*' ..
        'echo "something"\_s*' ..
        'elseif has("eval")\_s*' ..
        'echo "eval"\_s*' ..
        '\d PUSHS "eval"\_s*' ..
        '\d ECHO 1\_s*' ..
        'elseif has("less").*' ..
        'echo "less"\_s*' ..
        'endif',
        instr)
  assert_notmatch('PUSHS "nothing"', instr)
  assert_notmatch('PUSHS "something"', instr)
  assert_notmatch('PUSHS "less"', instr)
  assert_notmatch('JUMP', instr)

  var result: string
  var instr_expected: string
  if has('gui')
    if has('gui_running')
      # GUI already running, always returns "yes"
      result = "\nyes"
      instr_expected = 'HasGuiRunning.*' ..
          'if has("gui_running")\_s*' ..
          '  echo "yes"\_s*' ..
          '\d PUSHS "yes"\_s*' ..
          '\d ECHO 1\_s*' ..
          'else\_s*' ..
          '  echo "no"\_s*' ..
          'endif'
    else
      result = "\nno"
      if has('unix')
        # GUI not running but can start later, call has()
        instr_expected = 'HasGuiRunning.*' ..
            'if has("gui_running")\_s*' ..
            '\d PUSHS "gui_running"\_s*' ..
            '\d BCALL has(argc 1)\_s*' ..
            '\d 2BOOL (!!val)\_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 0',
        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 0',
        instr)
enddef

def WithLambda(): string
  var F = (a) => "X" .. a .. "X"
  return F("x")
enddef

def Test_disassemble_lambda()
  assert_equal("XxX", WithLambda())
  var instr = execute('disassemble WithLambda')
  assert_match('WithLambda\_s*' ..
        'var F = (a) => "X" .. a .. "X"\_s*' ..
        '\d FUNCREF <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\_s*' ..
        '\d PUSHS "X"\_s*' ..
        '\d CONCAT\_s*' ..
        '\d RETURN',
        instr)
enddef

def LambdaWithType(): number
  var Ref = (a: number) => a + 10
  return Ref(g:value)
enddef

def Test_disassemble_lambda_with_type()
  g:value = 5
  assert_equal(15, LambdaWithType())
  var instr = execute('disassemble LambdaWithType')
  assert_match('LambdaWithType\_s*' ..
        'var Ref = (a: number) => a + 10\_s*' ..
        '\d FUNCREF <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_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 0',
        instr)
enddef

def NestedDefList()
  def
  def Info
  def /Info
  def /Info/
enddef

def Test_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 0',
        instr)
enddef

def AndOr(arg: any): string
  if arg == 1 && arg != 2 || arg == 4
    return 'yes'
  endif
  return 'no'
enddef

def Test_disassemble_and_or()
  assert_equal("yes", AndOr(1))
  assert_equal("no", AndOr(2))
  assert_equal("yes", AndOr(4))
  var instr = execute('disassemble AndOr')
  assert_match('AndOr\_s*' ..
        'if arg == 1 && arg != 2 || arg == 4\_s*' ..
        '\d LOAD arg\[-1]\_s*' ..
        '\d PUSHNR 1\_s*' ..
        '\d COMPAREANY ==\_s*' ..
        '\d JUMP_IF_COND_FALSE -> \d\+\_s*' ..
        '\d LOAD arg\[-1]\_s*' ..
        '\d PUSHNR 2\_s*' ..
        '\d COMPAREANY !=\_s*' ..
        '\d JUMP_IF_COND_TRUE -> \d\+\_s*' ..
        '\d LOAD arg\[-1]\_s*' ..
        '\d\+ PUSHNR 4\_s*' ..
        '\d\+ COMPAREANY ==\_s*' ..
        '\d\+ JUMP_IF_FALSE -> \d\+',
        instr)
enddef

def ForLoop(): list<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 $2\_s*' ..
        'res->add(i)\_s*' ..
        '\d LOAD $0\_s*' ..
        '\d LOAD $2\_s*' ..
        '\d\+ LISTAPPEND\_s*' ..
        '\d\+ DROP\_s*' ..
        'endfor\_s*' ..
        '\d\+ JUMP -> \d\+\_s*' ..
        '\d\+ DROP',
        instr)
enddef

def ForLoopEval(): string
  var res = ""
  for str in eval('["one", "two"]')
    res ..= str
  endfor
  return res
enddef

def Test_disassemble_for_loop_eval()
  assert_equal('onetwo', ForLoopEval())
  var instr = execute('disassemble ForLoopEval')
  assert_match('ForLoopEval\_s*' ..
        'var res = ""\_s*' ..
        '\d PUSHS ""\_s*' ..
        '\d STORE $0\_s*' ..
        'for str in eval(''\["one", "two"\]'')\_s*' ..
        '\d STORE -1 in $1\_s*' ..
        '\d PUSHS "\["one", "two"\]"\_s*' ..
        '\d BCALL eval(argc 1)\_s*' ..
        '\d CHECKTYPE list<any> stack\[-1\]\_s*' ..
        '\d FOR $1 -> \d\+\_s*' ..
        '\d STORE $2\_s*' ..
        'res ..= str\_s*' ..
        '\d\+ LOAD $0\_s*' ..
        '\d\+ LOAD $2\_s*' ..
        '\d\+ CHECKTYPE string stack\[-1\]\_s*' ..
        '\d\+ CONCAT\_s*' ..
        '\d\+ STORE $0\_s*' ..
        'endfor\_s*' ..
        '\d\+ JUMP -> 6\_s*' ..
        '\d\+ DROP\_s*' ..
        'return res\_s*' ..
        '\d\+ LOAD $0\_s*' ..
        '\d\+ RETURN',
        instr)
enddef

def ForLoopUnpack()
  for [x1, x2] in [[1, 2], [3, 4]]
    echo x1 x2
  endfor
enddef

def Test_disassemble_for_loop_unpack()
  var instr = execute('disassemble ForLoopUnpack')
  assert_match('ForLoopUnpack\_s*' ..
        'for \[x1, x2\] in \[\[1, 2\], \[3, 4\]\]\_s*' ..
        '\d\+ STORE -1 in $0\_s*' ..
        '\d\+ PUSHNR 1\_s*' ..
        '\d\+ PUSHNR 2\_s*' ..
        '\d\+ NEWLIST size 2\_s*' ..
        '\d\+ PUSHNR 3\_s*' ..
        '\d\+ PUSHNR 4\_s*' ..
        '\d\+ NEWLIST size 2\_s*' ..
        '\d\+ NEWLIST size 2\_s*' ..
        '\d\+ FOR $0 -> 16\_s*' ..
        '\d\+ UNPACK 2\_s*' ..
        '\d\+ STORE $1\_s*' ..
        '\d\+ STORE $2\_s*' ..
        'echo x1 x2\_s*' ..
        '\d\+ LOAD $1\_s*' ..
        '\d\+ LOAD $2\_s*' ..
        '\d\+ ECHO 2\_s*' ..
        'endfor\_s*' ..
        '\d\+ JUMP -> 8\_s*' ..
        '\d\+ DROP\_s*' ..
        '\d\+ RETURN 0',
        instr)
enddef

def ForLoopContinue()
  for nr in [1, 2]
    try
      echo "ok"
      try
        echo "deeper"
      catch
        continue
      endtry
    catch
      echo "not ok"
    endtry
  endfor
enddef

def Test_disassemble_for_loop_continue()
  var instr = execute('disassemble ForLoopContinue')
  assert_match('ForLoopContinue\_s*' ..
        'for nr in \[1, 2]\_s*' ..
        '0 STORE -1 in $0\_s*' ..
        '1 PUSHNR 1\_s*' ..
        '2 PUSHNR 2\_s*' ..
        '3 NEWLIST size 2\_s*' ..
        '4 FOR $0 -> 22\_s*' ..
        '5 STORE $1\_s*' ..
        'try\_s*' ..
        '6 TRY catch -> 17, end -> 20\_s*' ..
        'echo "ok"\_s*' ..
        '7 PUSHS "ok"\_s*' ..
        '8 ECHO 1\_s*' ..
        'try\_s*' ..
        '9 TRY catch -> 13, end -> 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 0',
        instr)
enddef

let g:number = 42

def 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 0\_s*',
        instr)
enddef

def Computing()
  var nr = 3
  var nrres = nr + 7
  nrres = nr - 7
  nrres = nr * 7
  nrres = nr / 7
  nrres = nr % 7

  var anyres = g:number + 7
  anyres = g:number - 7
  anyres = g:number * 7
  anyres = g:number / 7
  anyres = g:number % 7

  if has('float')
    var fl = 3.0
    var flres = fl + 7.0
    flres = fl - 7.0
    flres = fl * 7.0
    flres = fl / 7.0
  endif
enddef

def Test_disassemble_computing()
  var instr = execute('disassemble Computing')
  assert_match('Computing.*' ..
        'var nr = 3.*' ..
        '\d STORE 3 in $0.*' ..
        'var nrres = nr + 7.*' ..
        '\d LOAD $0.*' ..
        '\d PUSHNR 7.*' ..
        '\d OPNR +.*' ..
        '\d STORE $1.*' ..
        'nrres = nr - 7.*' ..
        '\d OPNR -.*' ..
        'nrres = nr \* 7.*' ..
        '\d OPNR \*.*' ..
        'nrres = nr / 7.*' ..
        '\d OPNR /.*' ..
        'nrres = nr % 7.*' ..
        '\d OPNR %.*' ..
        'var anyres = g:number + 7.*' ..
        '\d LOADG g:number.*' ..
        '\d PUSHNR 7.*' ..
        '\d OPANY +.*' ..
        '\d STORE $2.*' ..
        'anyres = g:number - 7.*' ..
        '\d OPANY -.*' ..
        'anyres = g:number \* 7.*' ..
        '\d OPANY \*.*' ..
        'anyres = g:number / 7.*' ..
        '\d OPANY /.*' ..
        'anyres = g:number % 7.*' ..
        '\d OPANY %.*',
        instr)
  if has('float')
    assert_match('Computing.*' ..
        'var fl = 3.0.*' ..
        '\d PUSHF 3.0.*' ..
        '\d STORE $3.*' ..
        'var flres = fl + 7.0.*' ..
        '\d LOAD $3.*' ..
        '\d PUSHF 7.0.*' ..
        '\d OPFLOAT +.*' ..
        '\d STORE $4.*' ..
        'flres = fl - 7.0.*' ..
        '\d OPFLOAT -.*' ..
        'flres = fl \* 7.0.*' ..
        '\d OPFLOAT \*.*' ..
        'flres = fl / 7.0.*' ..
        '\d OPFLOAT /.*',
        instr)
  endif
enddef

def AddListBlob()
  var reslist = [1, 2] + [3, 4]
  var resblob = 0z1122 + 0z3344
enddef

def Test_disassemble_add_list_blob()
  var instr = execute('disassemble AddListBlob')
  assert_match('AddListBlob.*' ..
        'var reslist = \[1, 2] + \[3, 4].*' ..
        '\d PUSHNR 1.*' ..
        '\d PUSHNR 2.*' ..
        '\d NEWLIST size 2.*' ..
        '\d PUSHNR 3.*' ..
        '\d PUSHNR 4.*' ..
        '\d NEWLIST size 2.*' ..
        '\d ADDLIST.*' ..
        '\d STORE $.*.*' ..
        'var resblob = 0z1122 + 0z3344.*' ..
        '\d PUSHBLOB 0z1122.*' ..
        '\d PUSHBLOB 0z3344.*' ..
        '\d ADDBLOB.*' ..
        '\d STORE $.*',
        instr)
enddef

let g:aa = 'aa'
def ConcatString(): string
  var res = g:aa .. "bb"
  return res
enddef

def Test_disassemble_concat()
  var instr = execute('disassemble ConcatString')
  assert_match('ConcatString.*' ..
        'var res = g:aa .. "bb".*' ..
        '\d LOADG g:aa.*' ..
        '\d PUSHS "bb".*' ..
        '\d 2STRING_ANY stack\[-2].*' ..
        '\d CONCAT.*' ..
        '\d STORE $.*',
        instr)
  assert_equal('aabb', ConcatString())
enddef

def StringIndex(): string
  var s = "abcd"
  var res = s[1]
  return res
enddef

def Test_disassemble_string_index()
  var instr = execute('disassemble StringIndex')
  assert_match('StringIndex\_s*' ..
        'var s = "abcd"\_s*' ..
        '\d PUSHS "abcd"\_s*' ..
        '\d STORE $0\_s*' ..
        'var res = s\[1]\_s*' ..
        '\d LOAD $0\_s*' ..
        '\d PUSHNR 1\_s*' ..
        '\d STRINDEX\_s*' ..
        '\d STORE $1\_s*',
        instr)
  assert_equal('b', StringIndex())
enddef

def StringSlice(): string
  var s = "abcd"
  var res = s[1 : 8]
  return res
enddef

def Test_disassemble_string_slice()
  var instr = execute('disassemble StringSlice')
  assert_match('StringSlice\_s*' ..
        'var s = "abcd"\_s*' ..
        '\d PUSHS "abcd"\_s*' ..
        '\d STORE $0\_s*' ..
        'var res = s\[1 : 8]\_s*' ..
        '\d LOAD $0\_s*' ..
        '\d PUSHNR 1\_s*' ..
        '\d PUSHNR 8\_s*' ..
        '\d STRSLICE\_s*' ..
        '\d STORE $1\_s*',
        instr)
  assert_equal('bcd', StringSlice())
enddef

def ListIndex(): number
  var l = [1, 2, 3]
  var res = l[1]
  return res
enddef

def Test_disassemble_list_index()
  var instr = execute('disassemble ListIndex')
  assert_match('ListIndex\_s*' ..
        'var l = \[1, 2, 3]\_s*' ..
        '\d PUSHNR 1\_s*' ..
        '\d PUSHNR 2\_s*' ..
        '\d PUSHNR 3\_s*' ..
        '\d NEWLIST size 3\_s*' ..
        '\d STORE $0\_s*' ..
        'var res = l\[1]\_s*' ..
        '\d LOAD $0\_s*' ..
        '\d PUSHNR 1\_s*' ..
        '\d LISTINDEX\_s*' ..
        '\d STORE $1\_s*',
        instr)
  assert_equal(2, ListIndex())
enddef

def ListSlice(): list<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 STORE $0\_s*' ..
        'var res = l\[1 : 8]\_s*' ..
        '\d LOAD $0\_s*' ..
        '\d PUSHNR 1\_s*' ..
        '\d PUSHNR 8\_s*' ..
        '\d LISTSLICE\_s*' ..
        '\d STORE $1\_s*',
        instr)
  assert_equal([2, 3], ListSlice())
enddef

def DictMember(): number
  var d = {item: 1}
  var res = d.item
  res = d["item"]
  return res
enddef

def Test_disassemble_dict_member()
  var instr = execute('disassemble DictMember')
  assert_match('DictMember\_s*' ..
        'var d = {item: 1}\_s*' ..
        '\d PUSHS "item"\_s*' ..
        '\d PUSHNR 1\_s*' ..
        '\d NEWDICT size 1\_s*' ..
        '\d STORE $0\_s*' ..
        'var res = d.item\_s*' ..
        '\d\+ LOAD $0\_s*' ..
        '\d\+ MEMBER item\_s*' ..
        '\d\+ STORE $1\_s*' ..
        'res = d\["item"\]\_s*' ..
        '\d\+ LOAD $0\_s*' ..
        '\d\+ PUSHS "item"\_s*' ..
        '\d\+ MEMBER\_s*' ..
        '\d\+ STORE $1\_s*',
        instr)
  assert_equal(1, DictMember())
enddef

let somelist = [1, 2, 3, 4, 5]
def AnyIndex(): number
  var res = g:somelist[2]
  return res
enddef

def Test_disassemble_any_index()
  var instr = execute('disassemble AnyIndex')
  assert_match('AnyIndex\_s*' ..
        'var res = g:somelist\[2\]\_s*' ..
        '\d LOADG g:somelist\_s*' ..
        '\d PUSHNR 2\_s*' ..
        '\d ANYINDEX\_s*' ..
        '\d STORE $0\_s*' ..
        'return res\_s*' ..
        '\d LOAD $0\_s*' ..
        '\d CHECKTYPE number stack\[-1\]\_s*' ..
        '\d RETURN',
        instr)
  assert_equal(3, AnyIndex())
enddef

def AnySlice(): list<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 NegateNumber(): number
  var nr = 9
  var plus = +nr
  var res = -nr
  return res
enddef

def Test_disassemble_negate_number()
  var instr = execute('disassemble NegateNumber')
  assert_match('NegateNumber\_s*' ..
        'var nr = 9\_s*' ..
        '\d STORE 9 in $0\_s*' ..
        'var plus = +nr\_s*' ..
        '\d LOAD $0\_s*' ..
        '\d CHECKNR\_s*' ..
        '\d STORE $1\_s*' ..
        'var res = -nr\_s*' ..
        '\d LOAD $0\_s*' ..
        '\d NEGATENR\_s*' ..
        '\d STORE $2\_s*',
        instr)
  assert_equal(-9, NegateNumber())
enddef

def InvertBool(): bool
  var flag = true
  var invert = !flag
  var res = !!flag
  return res
enddef

def Test_disassemble_invert_bool()
  var instr = execute('disassemble InvertBool')
  assert_match('InvertBool\_s*' ..
        'var flag = true\_s*' ..
        '\d PUSH true\_s*' ..
        '\d STORE $0\_s*' ..
        'var invert = !flag\_s*' ..
        '\d LOAD $0\_s*' ..
        '\d INVERT (!val)\_s*' ..
        '\d STORE $1\_s*' ..
        'var res = !!flag\_s*' ..
        '\d LOAD $0\_s*' ..
        '\d 2BOOL (!!val)\_s*' ..
        '\d STORE $2\_s*',
        instr)
  assert_equal(true, InvertBool())
enddef

def ReturnBool(): bool
  var name: bool = 1 && 0 || 1
  return name
enddef

def Test_disassemble_return_bool()
  var instr = execute('disassemble ReturnBool')
  assert_match('ReturnBool\_s*' ..
        'var name: bool = 1 && 0 || 1\_s*' ..
        '0 PUSHNR 1\_s*' ..
        '1 2BOOL (!!val)\_s*' ..
        '2 JUMP_IF_COND_FALSE -> 5\_s*' ..
        '3 PUSHNR 0\_s*' ..
        '4 2BOOL (!!val)\_s*' ..
        '5 JUMP_IF_COND_TRUE -> 8\_s*' ..
        '6 PUSHNR 1\_s*' ..
        '7 2BOOL (!!val)\_s*' ..
        '\d STORE $0\_s*' ..
        'return name\_s*' ..
        '\d\+ LOAD $0\_s*' ..   
        '\d\+ RETURN',
        instr)
  assert_equal(true, InvertBool())
enddef

def Test_disassemble_compare()
  var cases = [
        ['true == isFalse', 'COMPAREBOOL =='],
        ['true != isFalse', 'COMPAREBOOL !='],
        ['v:none == isNull', 'COMPARESPECIAL =='],
        ['v:none != isNull', 'COMPARESPECIAL !='],

        ['111 == aNumber', 'COMPARENR =='],
        ['111 != aNumber', 'COMPARENR !='],
        ['111 > aNumber', 'COMPARENR >'],
        ['111 < aNumber', 'COMPARENR <'],
        ['111 >= aNumber', 'COMPARENR >='],
        ['111 <= aNumber', 'COMPARENR <='],
        ['111 =~ aNumber', 'COMPARENR =\~'],
        ['111 !~ aNumber', 'COMPARENR !\~'],

        ['"xx" != aString', 'COMPARESTRING !='],
        ['"xx" > aString', 'COMPARESTRING >'],
        ['"xx" < aString', 'COMPARESTRING <'],
        ['"xx" >= aString', 'COMPARESTRING >='],
        ['"xx" <= aString', 'COMPARESTRING <='],
        ['"xx" =~ aString', 'COMPARESTRING =\~'],
        ['"xx" !~ aString', 'COMPARESTRING !\~'],
        ['"xx" is aString', 'COMPARESTRING is'],
        ['"xx" isnot aString', 'COMPARESTRING isnot'],

        ['0z11 == aBlob', 'COMPAREBLOB =='],
        ['0z11 != aBlob', 'COMPAREBLOB !='],
        ['0z11 is aBlob', 'COMPAREBLOB is'],
        ['0z11 isnot aBlob', 'COMPAREBLOB isnot'],

        ['[1, 2] == aList', 'COMPARELIST =='],
        ['[1, 2] != aList', 'COMPARELIST !='],
        ['[1, 2] is aList', 'COMPARELIST is'],
        ['[1, 2] isnot aList', 'COMPARELIST isnot'],

        ['{a: 1} == aDict', 'COMPAREDICT =='],
        ['{a: 1} != aDict', 'COMPAREDICT !='],
        ['{a: 1} is aDict', 'COMPAREDICT is'],
        ['{a: 1} isnot aDict', 'COMPAREDICT isnot'],

        ['(() => 33) == (() => 44)', 'COMPAREFUNC =='],
        ['(() => 33) != (() => 44)', 'COMPAREFUNC !='],
        ['(() => 33) is (() => 44)', 'COMPAREFUNC is'],
        ['(() => 33) isnot (() => 44)', 'COMPAREFUNC isnot'],

        ['77 == g:xx', 'COMPAREANY =='],
        ['77 != g:xx', 'COMPAREANY !='],
        ['77 > g:xx', 'COMPAREANY >'],
        ['77 < g:xx', 'COMPAREANY <'],
        ['77 >= g:xx', 'COMPAREANY >='],
        ['77 <= g:xx', 'COMPAREANY <='],
        ['77 =~ g:xx', 'COMPAREANY =\~'],
        ['77 !~ g:xx', 'COMPAREANY !\~'],
        ['77 is g:xx', 'COMPAREANY is'],
        ['77 isnot g:xx', 'COMPAREANY isnot'],
        ]
  var floatDecl = ''
  if has('float')
    cases->extend([
        ['1.1 == aFloat', 'COMPAREFLOAT =='],
        ['1.1 != aFloat', 'COMPAREFLOAT !='],
        ['1.1 > aFloat', 'COMPAREFLOAT >'],
        ['1.1 < aFloat', 'COMPAREFLOAT <'],
        ['1.1 >= aFloat', 'COMPAREFLOAT >='],
        ['1.1 <= aFloat', 'COMPAREFLOAT <='],
        ['1.1 =~ aFloat', 'COMPAREFLOAT =\~'],
        ['1.1 !~ aFloat', 'COMPAREFLOAT !\~'],
        ])
    floatDecl = 'var aFloat = 2.2'
  endif

  var nr = 1
  for case in cases
    # declare local variables to get a non-constant with the right type
    writefile(['def TestCase' .. nr .. '()',
             '  var isFalse = false',
             '  var isNull = v:null',
             '  var aNumber = 222',
             '  var aString = "yy"',
             '  var aBlob = 0z22',
             '  var aList = [3, 4]',
             '  var aDict = {x: 2}',
             floatDecl,
             '  if ' .. case[0],
             '    echo 42'
             '  endif',
             'enddef'], 'Xdisassemble')
    source Xdisassemble
    var instr = execute('disassemble TestCase' .. nr)
    assert_match('TestCase' .. nr .. '.*' ..
        'if ' .. substitute(case[0], '[[~]', '\\\0', 'g') .. '.*' ..
        '\d \(PUSH\|FUNCREF\).*' ..
        '\d \(PUSH\|FUNCREF\|LOAD\).*' ..
        '\d ' .. case[1] .. '.*' ..
        '\d JUMP_IF_FALSE -> \d\+.*',
        instr)

    nr += 1
  endfor

  delete('Xdisassemble')
enddef

def s:FalsyOp()
  echo g:flag ?? "yes"
  echo [] ?? "empty list"
  echo "" ?? "empty string"
enddef

def Test_dsassemble_falsy_op()
  var res = execute('disass s:FalsyOp')
  assert_match('\<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 0',
      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 0',
          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 0',
          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\_s*' ..
        '\d\+ EXECUTE 1\_s*' ..
        '\d\+ RETURN 0',
        res)
enddef

def s:Echomsg()
  echomsg 'some' 'message'
  echoerr 'went' .. 'wrong'
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*' ..
        "echoerr 'went' .. 'wrong'\\_s*" ..
        '\d PUSHS "wentwrong"\_s*' ..
        '\d ECHOERR 1\_s*' ..
        '\d RETURN 0',
        res)
enddef

def SomeStringArg(arg: string)
  echo arg
enddef

def SomeAnyArg(arg: any)
  echo arg
enddef

def SomeStringArgAndReturn(arg: string): string
  return arg
enddef

def Test_display_func()
  var res1 = execute('function SomeStringArg')
  assert_match('.* def SomeStringArg(arg: string)\_s*' ..
        '\d *echo arg.*' ..
        ' *enddef',
        res1)

  var res2 = execute('function SomeAnyArg')
  assert_match('.* def SomeAnyArg(arg: any)\_s*' ..
        '\d *echo arg\_s*' ..
        ' *enddef',
        res2)

  var res3 = execute('function SomeStringArgAndReturn')
  assert_match('.* def SomeStringArgAndReturn(arg: string): string\_s*' ..
        '\d *return arg\_s*' ..
        ' *enddef',
        res3)
enddef

def Test_vim9script_forward_func()
  var lines =<< trim END
    vim9script
    def FuncOne(): string
      return FuncTwo()
    enddef
    def FuncTwo(): string
      return 'two'
    enddef
    g:res_FuncOne = execute('disass FuncOne')
  END
  writefile(lines, 'Xdisassemble')
  source Xdisassemble

  # check that the first function calls the second with DCALL
  assert_match('\<SNR>\d*_FuncOne\_s*' ..
        'return FuncTwo()\_s*' ..
        '\d DCALL <SNR>\d\+_FuncTwo(argc 0)\_s*' ..
        '\d RETURN',
        g:res_FuncOne)

  delete('Xdisassemble')
  unlet g:res_FuncOne
enddef

def s:ConcatStrings(): string
  return 'one' .. 'two' .. 'three'
enddef

def s:ComputeConst(): number
  return 2 + 3 * 4 / 6 + 7
enddef

def s:ComputeConstParen(): number
  return ((2 + 4) * (8 / 2)) / (3 + 4)
enddef

def Test_simplify_const_expr()
  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 0',
        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 0',
        res)
enddef

def s:Profiled(): string
  echo "profiled"
  return "done"
enddef

def Test_profiled()
  if !has('profile')
    MissingFeature 'profile'
  endif
  var res = execute('disass! s:Profiled')
  assert_match('<SNR>\d*_Profiled\_s*' ..
        'echo "profiled"\_s*' ..
        '\d PROFILE START line 1\_s*' ..
        '\d PUSHS "profiled"\_s*' ..
        '\d ECHO 1\_s*' ..
        'return "done"\_s*' ..
        '\d PROFILE END\_s*' ..
        '\d PROFILE START line 2\_s*' ..
        '\d PUSHS "done"\_s*' ..
        '\d RETURN\_s*' ..
        '\d PROFILE END',
        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

" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker