diff --git a/.travis.yml b/.travis.yml
index fdc8bd2..7a3b7f4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,8 @@
 language: go
 
 go:
-  - 1.8
+  - 1.14
+  - 1.15
   - tip
 
 install:
diff --git a/ast/node.go b/ast/node.go
index 244a64a..cb0e026 100644
--- a/ast/node.go
+++ b/ast/node.go
@@ -65,6 +65,7 @@ type Function struct {
 	Body        []Statement  // function body
 	Name        Expr         // function name
 	Params      []*Ident     // parameters
+	DefaultArgs []Expr       // default arguments
 	Attr        FuncAttr     // function attributes
 	EndFunction *EndFunction // :endfunction
 }
@@ -119,6 +120,7 @@ func (f *ExCall) Pos() Pos { return f.ExCall }
 func (f *ExCall) Cmd() Cmd { return *f.ExArg.Cmd }
 
 // vimlparser: LET .ea .op .left .list .rest .right
+// vimlparser: CONST .ea .op .left .list .rest .right
 type Let struct {
 	Let   Pos    // position of starting the :let
 	ExArg ExArg  // Ex command arg
@@ -341,6 +343,17 @@ type Throw struct {
 func (f *Throw) Pos() Pos { return f.Throw }
 func (f *Throw) Cmd() Cmd { return *f.ExArg.Cmd }
 
+// vimlparser: EVAL .ea .left
+// :eval {Expr}
+type Eval struct {
+	Eval  Pos   // position of starting the :eval
+	ExArg ExArg // Ex command arg
+	Expr  Expr
+}
+
+func (f *Eval) Pos() Pos { return f.Eval }
+func (f *Eval) Cmd() Cmd { return *f.ExArg.Cmd }
+
 // vimlparser: ECHO .ea .list
 // vimlparser: ECHON .ea .list
 // vimlparser: ECHOMSG .ea .list
@@ -426,6 +439,16 @@ type SliceExpr struct {
 
 func (f *SliceExpr) Pos() Pos { return f.Lbrack }
 
+// vimlparser: METHOD .left .right
+type MethodExpr struct {
+	Left   Expr   // this object
+	Method Expr   // method
+	Lparen Pos    // position of "("
+	Args   []Expr // function arguments; or nil
+}
+
+func (c *MethodExpr) Pos() Pos { return c.Lparen }
+
 // vimlparser: CALL .left .rlist
 type CallExpr struct {
 	Fun    Expr   // function expression
@@ -516,7 +539,7 @@ type Ident struct {
 func (i *Ident) Pos() Pos { return i.NamePos }
 
 // LambdaExpr node represents lambda.
-// vimlparsr: LAMBDA .rlist .left
+// vimlparser: LAMBDA .rlist .left
 // { Params -> Expr }
 type LambdaExpr struct {
 	Lcurlybrace Pos      // position of "{"
@@ -527,7 +550,7 @@ type LambdaExpr struct {
 func (i *LambdaExpr) Pos() Pos { return i.Lcurlybrace }
 
 // ParenExpr node represents a parenthesized expression.
-// vimlparsr: PARENEXPR .value
+// vimlparser: PARENEXPR .value
 type ParenExpr struct {
 	Lparen Pos  // position of "("
 	X      Expr // parenthesized expression
@@ -535,6 +558,17 @@ type ParenExpr struct {
 
 func (i *ParenExpr) Pos() Pos { return i.Lparen }
 
+// HeredocExpr node represents a heredoc expression.
+// vimlparser: HEREDOC .rlist .op .body
+type HeredocExpr struct {
+	OpPos     Pos    // position of "=<<"
+	Flags     []Expr // modifiers [trim]; or nil
+	EndMarker string // {endmarker}
+	Body      []Expr // body
+}
+
+func (i *HeredocExpr) Pos() Pos { return i.OpPos }
+
 // stmtNode() ensures that only ExComamnd and Comment nodes can be assigned to
 // an Statement.
 //
@@ -562,6 +596,7 @@ func (*Let) stmtNode()        {}
 func (*LockVar) stmtNode()    {}
 func (*Return) stmtNode()     {}
 func (*Throw) stmtNode()      {}
+func (*Eval) stmtNode()       {}
 func (*Try) stmtNode()        {}
 func (*UnLet) stmtNode()      {}
 func (*UnLockVar) stmtNode()  {}
@@ -576,6 +611,7 @@ func (*BinaryExpr) exprNode()    {}
 func (*UnaryExpr) exprNode()     {}
 func (*SubscriptExpr) exprNode() {}
 func (*SliceExpr) exprNode()     {}
+func (*MethodExpr) exprNode()    {}
 func (*CallExpr) exprNode()      {}
 func (*DotExpr) exprNode()       {}
 func (*BasicLit) exprNode()      {}
@@ -587,3 +623,4 @@ func (*CurlyNameExpr) exprNode() {}
 func (*Ident) exprNode()         {}
 func (*LambdaExpr) exprNode()    {}
 func (*ParenExpr) exprNode()     {}
+func (*HeredocExpr) exprNode()   {}
diff --git a/ast/walk.go b/ast/walk.go
index 62798bd..39d011a 100644
--- a/ast/walk.go
+++ b/ast/walk.go
@@ -135,6 +135,9 @@ func Walk(v Visitor, node Node) {
 	case *Throw:
 		Walk(v, n.Expr)
 
+	case *Eval:
+		Walk(v, n.Expr)
+
 	case *EchoCmd:
 		walkExprList(v, n.Exprs)
 
@@ -164,6 +167,11 @@ func Walk(v Visitor, node Node) {
 		Walk(v, n.Low)
 		Walk(v, n.High)
 
+	case *MethodExpr:
+		Walk(v, n.Left)
+		Walk(v, n.Method)
+		walkExprList(v, n.Args)
+
 	case *CallExpr:
 		Walk(v, n.Fun)
 		walkExprList(v, n.Args)
@@ -201,6 +209,10 @@ func Walk(v Visitor, node Node) {
 	case *ParenExpr:
 		Walk(v, n.X)
 
+	case *HeredocExpr:
+		walkExprList(v, n.Flags)
+		walkExprList(v, n.Body)
+
 	default:
 		panic(fmt.Sprintf("ast.Walk: unexpected node type %T", n))
 	}
diff --git a/autoload/vimlparser.vim b/autoload/vimlparser.vim
index 3da9a84..a7ae041 100644
--- a/autoload/vimlparser.vim
+++ b/autoload/vimlparser.vim
@@ -4,21 +4,21 @@
 "
 " License: This file is placed in the public domain.
 
-function! vimlparser#import()
+function! vimlparser#import() abort
   return s:
 endfunction
 
 " @brief Read input as VimScript and return stringified AST.
 " @param input Input filename or string of VimScript.
 " @return Stringified AST.
-function! vimlparser#test(input, ...)
+function! vimlparser#test(input, ...) abort
   try
     if a:0 > 0
       let l:neovim = a:1
     else
       let l:neovim = 0
     endif
-    let i = type(a:input) == 1 && filereadable(a:input) ? readfile(a:input) : [a:input]
+    let i = type(a:input) ==# 1 && filereadable(a:input) ? readfile(a:input) : split(a:input, "\n")
     let r = s:StringReader.new(i)
     let p = s:VimLParser.new(l:neovim)
     let c = s:Compiler.new()
@@ -28,12 +28,12 @@ function! vimlparser#test(input, ...)
   endtry
 endfunction
 
-function! s:numtoname(num)
+function! s:numtoname(num) abort
   let sig = printf("function('%s')", a:num)
   for k in keys(s:)
-    if type(s:[k]) == type({})
+    if type(s:[k]) ==# type({})
       for name in keys(s:[k])
-        if type(s:[k][name]) == type(function('tr')) && string(s:[k][name]) == sig
+        if type(s:[k][name]) ==# type(function('tr')) && string(s:[k][name]) ==# sig
           return printf('%s.%s', k, name)
         endif
       endfor
@@ -137,7 +137,12 @@ let s:NODE_REG = 89
 let s:NODE_CURLYNAMEPART = 90
 let s:NODE_CURLYNAMEEXPR = 91
 let s:NODE_LAMBDA = 92
-let s:NODE_PARENEXPR = 93
+let s:NODE_BLOB = 93
+let s:NODE_CONST = 94
+let s:NODE_EVAL = 95
+let s:NODE_HEREDOC = 96
+let s:NODE_METHOD = 97
+let s:NODE_PARENEXPR = 200
 
 let s:TOKEN_EOF = 1
 let s:TOKEN_EOL = 2
@@ -204,71 +209,75 @@ let s:TOKEN_BACKTICK = 62
 let s:TOKEN_DOTDOTDOT = 63
 let s:TOKEN_SHARP = 64
 let s:TOKEN_ARROW = 65
+let s:TOKEN_BLOB = 66
+let s:TOKEN_LITCOPEN = 67
+let s:TOKEN_DOTDOT = 68
+let s:TOKEN_HEREDOC = 69
 
 let s:MAX_FUNC_ARGS = 20
 
-function! s:isalpha(c)
+function! s:isalpha(c) abort
   return a:c =~# '^[A-Za-z]$'
 endfunction
 
-function! s:isalnum(c)
+function! s:isalnum(c) abort
   return a:c =~# '^[0-9A-Za-z]$'
 endfunction
 
-function! s:isdigit(c)
+function! s:isdigit(c) abort
   return a:c =~# '^[0-9]$'
 endfunction
 
-function! s:isodigit(c)
+function! s:isodigit(c) abort
   return a:c =~# '^[0-7]$'
 endfunction
 
-function! s:isxdigit(c)
+function! s:isxdigit(c) abort
   return a:c =~# '^[0-9A-Fa-f]$'
 endfunction
 
-function! s:iswordc(c)
+function! s:iswordc(c) abort
   return a:c =~# '^[0-9A-Za-z_]$'
 endfunction
 
-function! s:iswordc1(c)
+function! s:iswordc1(c) abort
   return a:c =~# '^[A-Za-z_]$'
 endfunction
 
-function! s:iswhite(c)
+function! s:iswhite(c) abort
   return a:c =~# '^[ \t]$'
 endfunction
 
-function! s:isnamec(c)
+function! s:isnamec(c) abort
   return a:c =~# '^[0-9A-Za-z_:#]$'
 endfunction
 
-function! s:isnamec1(c)
+function! s:isnamec1(c) abort
   return a:c =~# '^[A-Za-z_]$'
 endfunction
 
-function! s:isargname(s)
+function! s:isargname(s) abort
   return a:s =~# '^[A-Za-z_][0-9A-Za-z_]*$'
 endfunction
 
-function! s:isvarname(s)
+function! s:isvarname(s) abort
   return a:s =~# '^[vgslabwt]:$\|^\([vgslabwt]:\)\?[A-Za-z_][0-9A-Za-z_#]*$'
 endfunction
 
 " FIXME:
-function! s:isidc(c)
+function! s:isidc(c) abort
   return a:c =~# '^[0-9A-Za-z_]$'
 endfunction
 
-function! s:isupper(c)
+function! s:isupper(c) abort
   return a:c =~# '^[A-Z]$'
 endfunction
 
-function! s:islower(c)
+function! s:islower(c) abort
   return a:c =~# '^[a-z]$'
 endfunction
 
-function! s:ExArg()
+function! s:ExArg() abort
   let ea = {}
   let ea.forceit = s:FALSE
   let ea.addr_count = 0
@@ -306,6 +315,7 @@ endfunction
 "   node    rest
 "   node[]  list
 "   node[]  rlist
+"   node[]  default_args
 "   node[]  body
 "   string  op
 "   string  str
@@ -315,12 +325,13 @@ endfunction
 " TOPLEVEL .body
 " COMMENT .str
 " EXCMD .ea .str
-" FUNCTION .ea .body .left .rlist .attr .endfunction
+" FUNCTION .ea .body .left .rlist .default_args .attr .endfunction
 " ENDFUNCTION .ea
 " DELFUNCTION .ea .left
 " RETURN .ea .left
 " EXCALL .ea .left
 " LET .ea .op .left .list .rest .right
+" CONST .ea .op .left .list .rest .right
 " UNLET .ea .list
 " LOCKVAR .ea .depth .list
 " UNLOCKVAR .ea .depth .list
@@ -339,6 +350,7 @@ endfunction
 " FINALLY .ea .body
 " ENDTRY .ea
 " THROW .ea .left
+" EVAL .ea .left
 " ECHO .ea .list
 " ECHON .ea .list
 " ECHOHL .ea .str
@@ -389,12 +401,14 @@ endfunction
 " PLUS .left
 " SUBSCRIPT .left .right
 " SLICE .left .rlist
+" METHOD .left .right
 " CALL .left .rlist
 " DOT .left .right
 " NUMBER .value
 " STRING .value
 " LIST .value
 " DICT .value
+" BLOB .value
 " NESTING .left
 " OPTION .value
 " IDENTIFIER .value
@@ -404,24 +418,25 @@ endfunction
 " CURLYNAMEPART .value
 " CURLYNAMEEXPR .value
 " LAMBDA .rlist .left
+" HEREDOC .rlist .op .body
 " PARENEXPR .value
-function! s:Node(type)
+function! s:Node(type) abort
   return {'type': a:type}
 endfunction
 
-function! s:Err(msg, pos)
+function! s:Err(msg, pos) abort
   return printf('vimlparser: %s: line %d col %d', a:msg, a:pos.lnum, a:pos.col)
 endfunction
 
 let s:VimLParser = {}
 
-function! s:VimLParser.new(...)
+function! s:VimLParser.new(...) abort
   let obj = copy(self)
   call call(obj.__init__, a:000, obj)
   return obj
 endfunction
 
-function! s:VimLParser.__init__(...)
+function! s:VimLParser.__init__(...) abort
   if len(a:000) > 0
     let self.neovim = a:000[0]
   else
@@ -431,18 +446,18 @@ function! s:VimLParser.__init__(...)
   let self.find_command_cache = {}
 endfunction
 
-function! s:VimLParser.push_context(node)
+function! s:VimLParser.push_context(node) abort
   call insert(self.context, a:node)
 endfunction
 
-function! s:VimLParser.pop_context()
+function! s:VimLParser.pop_context() abort
   call remove(self.context, 0)
 endfunction
 
-function! s:VimLParser.find_context(type)
+function! s:VimLParser.find_context(type) abort
   let i = 0
   for node in self.context
-    if node.type == a:type
+    if node.type ==# a:type
       return i
     endif
     let i += 1
@@ -450,44 +465,45 @@ function! s:VimLParser.find_context(type)
   return -1
 endfunction
 
-function! s:VimLParser.add_node(node)
+function! s:VimLParser.add_node(node) abort
   call add(self.context[0].body, a:node)
 endfunction
 
-function! s:VimLParser.check_missing_endfunction(ends, pos)
-  if self.context[0].type == s:NODE_FUNCTION
+function! s:VimLParser.check_missing_endfunction(ends, pos) abort
+  if self.context[0].type ==# s:NODE_FUNCTION
     throw s:Err(printf('E126: Missing :endfunction:    %s', a:ends), a:pos)
   endif
 endfunction
 
-function! s:VimLParser.check_missing_endif(ends, pos)
-  if self.context[0].type == s:NODE_IF || self.context[0].type == s:NODE_ELSEIF || self.context[0].type == s:NODE_ELSE
+function! s:VimLParser.check_missing_endif(ends, pos) abort
+  if self.context[0].type ==# s:NODE_IF || self.context[0].type ==# s:NODE_ELSEIF || self.context[0].type ==# s:NODE_ELSE
     throw s:Err(printf('E171: Missing :endif:    %s', a:ends), a:pos)
   endif
 endfunction
 
-function! s:VimLParser.check_missing_endtry(ends, pos)
-  if self.context[0].type == s:NODE_TRY || self.context[0].type == s:NODE_CATCH || self.context[0].type == s:NODE_FINALLY
+function! s:VimLParser.check_missing_endtry(ends, pos) abort
+  if self.context[0].type ==# s:NODE_TRY || self.context[0].type ==# s:NODE_CATCH || self.context[0].type ==# s:NODE_FINALLY
     throw s:Err(printf('E600: Missing :endtry:    %s', a:ends), a:pos)
   endif
 endfunction
 
-function! s:VimLParser.check_missing_endwhile(ends, pos)
-  if self.context[0].type == s:NODE_WHILE
+function! s:VimLParser.check_missing_endwhile(ends, pos) abort
+  if self.context[0].type ==# s:NODE_WHILE
     throw s:Err(printf('E170: Missing :endwhile:    %s', a:ends), a:pos)
   endif
 endfunction
 
-function! s:VimLParser.check_missing_endfor(ends, pos)
-  if self.context[0].type == s:NODE_FOR
+function! s:VimLParser.check_missing_endfor(ends, pos) abort
+  if self.context[0].type ==# s:NODE_FOR
     throw s:Err(printf('E170: Missing :endfor:    %s', a:ends), a:pos)
   endif
 endfunction
 
-function! s:VimLParser.parse(reader)
+function! s:VimLParser.parse(reader) abort
   let self.reader = a:reader
   let self.context = []
   let toplevel = s:Node(s:NODE_TOPLEVEL)
+  let toplevel.pos = self.reader.getpos()
   let toplevel.body = []
   call self.push_context(toplevel)
   while self.reader.peek() !=# '<EOF>'
@@ -502,7 +518,7 @@ function! s:VimLParser.parse(reader)
   return toplevel
 endfunction
 
-function! s:VimLParser.parse_one_cmd()
+function! s:VimLParser.parse_one_cmd() abort
   let self.ea = s:ExArg()
 
   if self.reader.peekn(2) ==# '#!'
@@ -528,7 +544,7 @@ function! s:VimLParser.parse_one_cmd()
 endfunction
 
 " FIXME:
-function! s:VimLParser.parse_command_modifiers()
+function! s:VimLParser.parse_command_modifiers() abort
   let modifiers = []
   while s:TRUE
     let pos = self.reader.tell()
@@ -540,42 +556,42 @@ function! s:VimLParser.parse_command_modifiers()
     let k = self.reader.read_alpha()
     let c = self.reader.peekn(1)
     call self.reader.skip_white()
-    if stridx('aboveleft', k) == 0 && len(k) >= 3 " abo\%[veleft]
+    if stridx('aboveleft', k) ==# 0 && len(k) >= 3 " abo\%[veleft]
       call add(modifiers, {'name': 'aboveleft'})
-    elseif stridx('belowright', k) == 0 && len(k) >= 3 " bel\%[owright]
+    elseif stridx('belowright', k) ==# 0 && len(k) >= 3 " bel\%[owright]
       call add(modifiers, {'name': 'belowright'})
-    elseif stridx('browse', k) == 0 && len(k) >= 3 " bro\%[wse]
+    elseif stridx('browse', k) ==# 0 && len(k) >= 3 " bro\%[wse]
       call add(modifiers, {'name': 'browse'})
-    elseif stridx('botright', k) == 0 && len(k) >= 2 " bo\%[tright]
+    elseif stridx('botright', k) ==# 0 && len(k) >= 2 " bo\%[tright]
       call add(modifiers, {'name': 'botright'})
-    elseif stridx('confirm', k) == 0 && len(k) >= 4 " conf\%[irm]
+    elseif stridx('confirm', k) ==# 0 && len(k) >= 4 " conf\%[irm]
       call add(modifiers, {'name': 'confirm'})
-    elseif stridx('keepmarks', k) == 0 && len(k) >= 3 " kee\%[pmarks]
+    elseif stridx('keepmarks', k) ==# 0 && len(k) >= 3 " kee\%[pmarks]
       call add(modifiers, {'name': 'keepmarks'})
-    elseif stridx('keepalt', k) == 0 && len(k) >= 5 " keepa\%[lt]
+    elseif stridx('keepalt', k) ==# 0 && len(k) >= 5 " keepa\%[lt]
       call add(modifiers, {'name': 'keepalt'})
-    elseif stridx('keepjumps', k) == 0 && len(k) >= 5 " keepj\%[umps]
+    elseif stridx('keepjumps', k) ==# 0 && len(k) >= 5 " keepj\%[umps]
       call add(modifiers, {'name': 'keepjumps'})
-    elseif stridx('keeppatterns', k) == 0 && len(k) >= 5 " keepp\%[atterns]
+    elseif stridx('keeppatterns', k) ==# 0 && len(k) >= 5 " keepp\%[atterns]
       call add(modifiers, {'name': 'keeppatterns'})
-    elseif stridx('hide', k) == 0 && len(k) >= 3 "hid\%[e]
+    elseif stridx('hide', k) ==# 0 && len(k) >= 3 " hid\%[e]
       if self.ends_excmds(c)
         break
       endif
       call add(modifiers, {'name': 'hide'})
-    elseif stridx('lockmarks', k) == 0 && len(k) >= 3 " loc\%[kmarks]
+    elseif stridx('lockmarks', k) ==# 0 && len(k) >= 3 " loc\%[kmarks]
       call add(modifiers, {'name': 'lockmarks'})
-    elseif stridx('leftabove', k) == 0 && len(k) >= 5 " lefta\%[bove]
+    elseif stridx('leftabove', k) ==# 0 && len(k) >= 5 " lefta\%[bove]
       call add(modifiers, {'name': 'leftabove'})
-    elseif stridx('noautocmd', k) == 0 && len(k) >= 3 " noa\%[utocmd]
+    elseif stridx('noautocmd', k) ==# 0 && len(k) >= 3 " noa\%[utocmd]
       call add(modifiers, {'name': 'noautocmd'})
-    elseif stridx('noswapfile', k) == 0 && len(k) >= 3 " :nos\%[wapfile]
+    elseif stridx('noswapfile', k) ==# 0 && len(k) >= 3 " :nos\%[wapfile]
       call add(modifiers, {'name': 'noswapfile'})
-    elseif stridx('rightbelow', k) == 0 && len(k) >= 6 "rightb\%[elow]
+    elseif stridx('rightbelow', k) ==# 0 && len(k) >= 6 " rightb\%[elow]
       call add(modifiers, {'name': 'rightbelow'})
-    elseif stridx('sandbox', k) == 0 && len(k) >= 3 " san\%[dbox]
+    elseif stridx('sandbox', k) ==# 0 && len(k) >= 3 " san\%[dbox]
       call add(modifiers, {'name': 'sandbox'})
-    elseif stridx('silent', k) == 0 && len(k) >= 3 " sil\%[ent]
+    elseif stridx('silent', k) ==# 0 && len(k) >= 3 " sil\%[ent]
       if c ==# '!'
         call self.reader.get()
         call add(modifiers, {'name': 'silent', 'bang': 1})
@@ -588,13 +604,13 @@ function! s:VimLParser.parse_command_modifiers()
       else
         call add(modifiers, {'name': 'tab'})
       endif
-    elseif stridx('topleft', k) == 0 && len(k) >= 2 " to\%[pleft]
+    elseif stridx('topleft', k) ==# 0 && len(k) >= 2 " to\%[pleft]
       call add(modifiers, {'name': 'topleft'})
-    elseif stridx('unsilent', k) == 0 && len(k) >= 3 " uns\%[ilent]
+    elseif stridx('unsilent', k) ==# 0 && len(k) >= 3 " uns\%[ilent]
       call add(modifiers, {'name': 'unsilent'})
-    elseif stridx('vertical', k) == 0 && len(k) >= 4 " vert\%[ical]
+    elseif stridx('vertical', k) ==# 0 && len(k) >= 4 " vert\%[ical]
       call add(modifiers, {'name': 'vertical'})
-    elseif stridx('verbose', k) == 0 && len(k) >= 4 " verb\%[ose]
+    elseif stridx('verbose', k) ==# 0 && len(k) >= 4 " verb\%[ose]
       if d !=# ''
         call add(modifiers, {'name': 'verbose', 'count': str2nr(d, 10)})
       else
@@ -609,7 +625,7 @@ function! s:VimLParser.parse_command_modifiers()
 endfunction
 
 " FIXME:
-function! s:VimLParser.parse_range()
+function! s:VimLParser.parse_range() abort
   let tokens = []
 
   while s:TRUE
@@ -691,7 +707,7 @@ function! s:VimLParser.parse_range()
 endfunction
 
 " FIXME:
-function! s:VimLParser.parse_pattern(delimiter)
+function! s:VimLParser.parse_pattern(delimiter) abort
   let pattern = ''
   let endc = ''
   let inbracket = 0
@@ -700,7 +716,7 @@ function! s:VimLParser.parse_pattern(delimiter)
     if c ==# ''
       break
     endif
-    if c ==# a:delimiter && inbracket == 0
+    if c ==# a:delimiter && inbracket ==# 0
       let endc = c
       break
     endif
@@ -721,7 +737,7 @@ function! s:VimLParser.parse_pattern(delimiter)
   return [pattern, endc]
 endfunction
 
-function! s:VimLParser.parse_command()
+function! s:VimLParser.parse_command() abort
   call self.reader.skip_white_and_colon()
 
   let self.ea.cmdpos = self.reader.getpos()
@@ -735,7 +751,7 @@ function! s:VimLParser.parse_command()
 
   let self.ea.cmd = self.find_command()
 
-  if self.ea.cmd is s:NIL
+  if self.ea.cmd is# s:NIL
     call self.reader.setpos(self.ea.cmdpos)
     throw s:Err(printf('E492: Not an editor command: %s', self.reader.peekline()), self.ea.cmdpos)
   endif
@@ -801,105 +817,110 @@ function! s:VimLParser.parse_command()
   call self._parse_command(self.ea.cmd.parser)
 endfunction
 
+" TODO: self[a:parser]
 function! s:VimLParser._parse_command(parser) abort
-  if a:parser == 'parse_cmd_append'
+  if a:parser ==# 'parse_cmd_append'
     call self.parse_cmd_append()
-  elseif a:parser == 'parse_cmd_break'
+  elseif a:parser ==# 'parse_cmd_break'
     call self.parse_cmd_break()
-  elseif a:parser == 'parse_cmd_call'
+  elseif a:parser ==# 'parse_cmd_call'
     call self.parse_cmd_call()
-  elseif a:parser == 'parse_cmd_catch'
+  elseif a:parser ==# 'parse_cmd_catch'
     call self.parse_cmd_catch()
-  elseif a:parser == 'parse_cmd_common'
+  elseif a:parser ==# 'parse_cmd_common'
     call self.parse_cmd_common()
-  elseif a:parser == 'parse_cmd_continue'
+  elseif a:parser ==# 'parse_cmd_continue'
     call self.parse_cmd_continue()
-  elseif a:parser == 'parse_cmd_delfunction'
+  elseif a:parser ==# 'parse_cmd_delfunction'
     call self.parse_cmd_delfunction()
-  elseif a:parser == 'parse_cmd_echo'
+  elseif a:parser ==# 'parse_cmd_echo'
     call self.parse_cmd_echo()
-  elseif a:parser == 'parse_cmd_echoerr'
+  elseif a:parser ==# 'parse_cmd_echoerr'
     call self.parse_cmd_echoerr()
-  elseif a:parser == 'parse_cmd_echohl'
+  elseif a:parser ==# 'parse_cmd_echohl'
     call self.parse_cmd_echohl()
-  elseif a:parser == 'parse_cmd_echomsg'
+  elseif a:parser ==# 'parse_cmd_echomsg'
     call self.parse_cmd_echomsg()
-  elseif a:parser == 'parse_cmd_echon'
+  elseif a:parser ==# 'parse_cmd_echon'
     call self.parse_cmd_echon()
-  elseif a:parser == 'parse_cmd_else'
+  elseif a:parser ==# 'parse_cmd_else'
     call self.parse_cmd_else()
-  elseif a:parser == 'parse_cmd_elseif'
+  elseif a:parser ==# 'parse_cmd_elseif'
     call self.parse_cmd_elseif()
-  elseif a:parser == 'parse_cmd_endfor'
+  elseif a:parser ==# 'parse_cmd_endfor'
     call self.parse_cmd_endfor()
-  elseif a:parser == 'parse_cmd_endfunction'
+  elseif a:parser ==# 'parse_cmd_endfunction'
     call self.parse_cmd_endfunction()
-  elseif a:parser == 'parse_cmd_endif'
+  elseif a:parser ==# 'parse_cmd_endif'
     call self.parse_cmd_endif()
-  elseif a:parser == 'parse_cmd_endtry'
+  elseif a:parser ==# 'parse_cmd_endtry'
     call self.parse_cmd_endtry()
-  elseif a:parser == 'parse_cmd_endwhile'
+  elseif a:parser ==# 'parse_cmd_endwhile'
     call self.parse_cmd_endwhile()
-  elseif a:parser == 'parse_cmd_execute'
+  elseif a:parser ==# 'parse_cmd_execute'
     call self.parse_cmd_execute()
-  elseif a:parser == 'parse_cmd_finally'
+  elseif a:parser ==# 'parse_cmd_finally'
     call self.parse_cmd_finally()
-  elseif a:parser == 'parse_cmd_finish'
+  elseif a:parser ==# 'parse_cmd_finish'
     call self.parse_cmd_finish()
-  elseif a:parser == 'parse_cmd_for'
+  elseif a:parser ==# 'parse_cmd_for'
     call self.parse_cmd_for()
-  elseif a:parser == 'parse_cmd_function'
+  elseif a:parser ==# 'parse_cmd_function'
     call self.parse_cmd_function()
-  elseif a:parser == 'parse_cmd_if'
+  elseif a:parser ==# 'parse_cmd_if'
     call self.parse_cmd_if()
-  elseif a:parser == 'parse_cmd_insert'
+  elseif a:parser ==# 'parse_cmd_insert'
     call self.parse_cmd_insert()
-  elseif a:parser == 'parse_cmd_let'
+  elseif a:parser ==# 'parse_cmd_let'
     call self.parse_cmd_let()
-  elseif a:parser == 'parse_cmd_loadkeymap'
+  elseif a:parser ==# 'parse_cmd_const'
+    call self.parse_cmd_const()
+  elseif a:parser ==# 'parse_cmd_loadkeymap'
     call self.parse_cmd_loadkeymap()
-  elseif a:parser == 'parse_cmd_lockvar'
+  elseif a:parser ==# 'parse_cmd_lockvar'
     call self.parse_cmd_lockvar()
-  elseif a:parser == 'parse_cmd_lua'
+  elseif a:parser ==# 'parse_cmd_lua'
     call self.parse_cmd_lua()
-  elseif a:parser == 'parse_cmd_modifier_range'
+  elseif a:parser ==# 'parse_cmd_modifier_range'
     call self.parse_cmd_modifier_range()
-  elseif a:parser == 'parse_cmd_mzscheme'
+  elseif a:parser ==# 'parse_cmd_mzscheme'
     call self.parse_cmd_mzscheme()
-  elseif a:parser == 'parse_cmd_perl'
+  elseif a:parser ==# 'parse_cmd_perl'
     call self.parse_cmd_perl()
-  elseif a:parser == 'parse_cmd_python'
+  elseif a:parser ==# 'parse_cmd_python'
     call self.parse_cmd_python()
-  elseif a:parser == 'parse_cmd_python3'
+  elseif a:parser ==# 'parse_cmd_python3'
     call self.parse_cmd_python3()
-  elseif a:parser == 'parse_cmd_return'
+  elseif a:parser ==# 'parse_cmd_return'
     call self.parse_cmd_return()
-  elseif a:parser == 'parse_cmd_ruby'
+  elseif a:parser ==# 'parse_cmd_ruby'
     call self.parse_cmd_ruby()
-  elseif a:parser == 'parse_cmd_tcl'
+  elseif a:parser ==# 'parse_cmd_tcl'
     call self.parse_cmd_tcl()
-  elseif a:parser == 'parse_cmd_throw'
+  elseif a:parser ==# 'parse_cmd_throw'
     call self.parse_cmd_throw()
-  elseif a:parser == 'parse_cmd_try'
+  elseif a:parser ==# 'parse_cmd_eval'
+    call self.parse_cmd_eval()
+  elseif a:parser ==# 'parse_cmd_try'
     call self.parse_cmd_try()
-  elseif a:parser == 'parse_cmd_unlet'
+  elseif a:parser ==# 'parse_cmd_unlet'
     call self.parse_cmd_unlet()
-  elseif a:parser == 'parse_cmd_unlockvar'
+  elseif a:parser ==# 'parse_cmd_unlockvar'
     call self.parse_cmd_unlockvar()
-  elseif a:parser == 'parse_cmd_usercmd'
+  elseif a:parser ==# 'parse_cmd_usercmd'
     call self.parse_cmd_usercmd()
-  elseif a:parser == 'parse_cmd_while'
+  elseif a:parser ==# 'parse_cmd_while'
     call self.parse_cmd_while()
-  elseif a:parser == 'parse_wincmd'
+  elseif a:parser ==# 'parse_wincmd'
     call self.parse_wincmd()
-  elseif a:parser == 'parse_cmd_syntax'
+  elseif a:parser ==# 'parse_cmd_syntax'
     call self.parse_cmd_syntax()
   else
     throw printf('unknown parser: %s', string(a:parser))
   endif
 endfunction
 
-function! s:VimLParser.find_command()
+function! s:VimLParser.find_command() abort
   let c = self.reader.peekn(1)
   let name = ''
 
@@ -923,7 +944,7 @@ function! s:VimLParser.find_command()
     endif
   endif
 
-  if name == ''
+  if name ==# ''
     return s:NIL
   endif
 
@@ -934,16 +955,16 @@ function! s:VimLParser.find_command()
   let cmd = s:NIL
 
   for x in self.builtin_commands
-    if stridx(x.name, name) == 0 && len(name) >= x.minlen
+    if stridx(x.name, name) ==# 0 && len(name) >= x.minlen
       unlet cmd
       let cmd = x
       break
     endif
   endfor
 
-  if self.neovim  
+  if self.neovim
     for x in self.neovim_additional_commands
-      if stridx(x.name, name) == 0 && len(name) >= x.minlen
+      if stridx(x.name, name) ==# 0 && len(name) >= x.minlen
         unlet cmd
         let cmd = x
         break
@@ -951,16 +972,16 @@ function! s:VimLParser.find_command()
     endfor
 
     for x in self.neovim_removed_commands
-      if stridx(x.name, name) == 0 && len(name) >= x.minlen
+      if stridx(x.name, name) ==# 0 && len(name) >= x.minlen
         unlet cmd
         let cmd = s:NIL
         break
       endif
     endfor
   endif
-  
+
   " FIXME: user defined command
-  if (cmd is s:NIL || cmd.name ==# 'Print') && name =~# '^[A-Z]'
+  if (cmd is# s:NIL || cmd.name ==# 'Print') && name =~# '^[A-Z]'
     let name .= self.reader.read_alnum()
     unlet cmd
     let cmd = {'name': name, 'flags': 'USERCMD', 'parser': 'parse_cmd_usercmd'}
@@ -972,13 +993,13 @@ function! s:VimLParser.find_command()
 endfunction
 
 " TODO:
-function! s:VimLParser.parse_hashbang()
+function! s:VimLParser.parse_hashbang() abort
   call self.reader.getn(-1)
 endfunction
 
 " TODO:
 " ++opt=val
-function! s:VimLParser.parse_argopt()
+function! s:VimLParser.parse_argopt() abort
   while self.reader.p(0) ==# '+' && self.reader.p(1) ==# '+'
     let s = self.reader.peekn(20)
     if s =~# '^++bin\>'
@@ -1022,7 +1043,7 @@ endfunction
 
 " TODO:
 " +command
-function! s:VimLParser.parse_argcmd()
+function! s:VimLParser.parse_argcmd() abort
   if self.reader.peekn(1) ==# '+'
     call self.reader.getn(1)
     if self.reader.peekn(1) ==# ' '
@@ -1033,7 +1054,7 @@ function! s:VimLParser.parse_argcmd()
   endif
 endfunction
 
-function! s:VimLParser.read_cmdarg()
+function! s:VimLParser.read_cmdarg() abort
   let r = ''
   while s:TRUE
     let c = self.reader.peekn(1)
@@ -1049,7 +1070,7 @@ function! s:VimLParser.read_cmdarg()
   return r
 endfunction
 
-function! s:VimLParser.parse_comment()
+function! s:VimLParser.parse_comment() abort
   let npos = self.reader.getpos()
   let c = self.reader.get()
   if c !=# '"'
@@ -1061,7 +1082,7 @@ function! s:VimLParser.parse_comment()
   call self.add_node(node)
 endfunction
 
-function! s:VimLParser.parse_trail()
+function! s:VimLParser.parse_trail() abort
   call self.reader.skip_white()
   let c = self.reader.peek()
   if c ==# '<EOF>'
@@ -1079,7 +1100,7 @@ function! s:VimLParser.parse_trail()
 endfunction
 
 " modifier or range only command line
-function! s:VimLParser.parse_cmd_modifier_range()
+function! s:VimLParser.parse_cmd_modifier_range() abort
   let node = s:Node(s:NODE_EXCMD)
   let node.pos = self.ea.cmdpos
   let node.ea = self.ea
@@ -1088,7 +1109,7 @@ function! s:VimLParser.parse_cmd_modifier_range()
 endfunction
 
 " TODO:
-function! s:VimLParser.parse_cmd_common()
+function! s:VimLParser.parse_cmd_common() abort
   let end = self.reader.getpos()
   if self.ea.cmd.flags =~# '\<TRLBAR\>' && !self.ea.usefilter
     let end = self.separate_nextcmd()
@@ -1114,7 +1135,7 @@ function! s:VimLParser.parse_cmd_common()
   call self.add_node(node)
 endfunction
 
-function! s:VimLParser.separate_nextcmd()
+function! s:VimLParser.separate_nextcmd() abort
   if self.ea.cmd.name ==# 'vimgrep' || self.ea.cmd.name ==# 'vimgrepadd' || self.ea.cmd.name ==# 'lvimgrep' || self.ea.cmd.name ==# 'lvimgrepadd'
     call self.skip_vimgrep_pat()
   endif
@@ -1151,7 +1172,7 @@ function! s:VimLParser.separate_nextcmd()
           \   && ((self.ea.cmd.name !=# '@' && self.ea.cmd.name !=# '*')
           \       || self.reader.getpos() !=# self.ea.argpos)
           \   && (self.ea.cmd.name !=# 'redir'
-          \       || self.reader.getpos().i != self.ea.argpos.i + 1 || pc !=# '@'))
+          \       || self.reader.getpos().i !=# self.ea.argpos.i + 1 || pc !=# '@'))
       let has_cpo_bar = s:FALSE " &cpoptions =~ 'b'
       if (!has_cpo_bar || self.ea.cmd.flags !~# '\<USECTRLV\>') && pc ==# '\'
         call self.reader.get()
@@ -1170,7 +1191,7 @@ function! s:VimLParser.separate_nextcmd()
 endfunction
 
 " FIXME
-function! s:VimLParser.skip_vimgrep_pat()
+function! s:VimLParser.skip_vimgrep_pat() abort
   if self.reader.peekn(1) ==# ''
     " pass
   elseif s:isidc(self.reader.peekn(1))
@@ -1189,7 +1210,7 @@ function! s:VimLParser.skip_vimgrep_pat()
   endif
 endfunction
 
-function! s:VimLParser.parse_cmd_append()
+function! s:VimLParser.parse_cmd_append() abort
   call self.reader.setpos(self.ea.linepos)
   let cmdline = self.reader.readline()
   let lines = [cmdline]
@@ -1212,11 +1233,11 @@ function! s:VimLParser.parse_cmd_append()
   call self.add_node(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_insert()
+function! s:VimLParser.parse_cmd_insert() abort
   call self.parse_cmd_append()
 endfunction
 
-function! s:VimLParser.parse_cmd_loadkeymap()
+function! s:VimLParser.parse_cmd_loadkeymap() abort
   call self.reader.setpos(self.ea.linepos)
   let cmdline = self.reader.readline()
   let lines = [cmdline]
@@ -1234,7 +1255,7 @@ function! s:VimLParser.parse_cmd_loadkeymap()
   call self.add_node(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_lua()
+function! s:VimLParser.parse_cmd_lua() abort
   let lines = []
   call self.reader.skip_white()
   if self.reader.peekn(2) ==# '<<'
@@ -1271,43 +1292,43 @@ function! s:VimLParser.parse_cmd_lua()
   call self.add_node(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_mzscheme()
+function! s:VimLParser.parse_cmd_mzscheme() abort
   call self.parse_cmd_lua()
 endfunction
 
-function! s:VimLParser.parse_cmd_perl()
+function! s:VimLParser.parse_cmd_perl() abort
   call self.parse_cmd_lua()
 endfunction
 
-function! s:VimLParser.parse_cmd_python()
+function! s:VimLParser.parse_cmd_python() abort
   call self.parse_cmd_lua()
 endfunction
 
-function! s:VimLParser.parse_cmd_python3()
+function! s:VimLParser.parse_cmd_python3() abort
   call self.parse_cmd_lua()
 endfunction
 
-function! s:VimLParser.parse_cmd_ruby()
+function! s:VimLParser.parse_cmd_ruby() abort
   call self.parse_cmd_lua()
 endfunction
 
-function! s:VimLParser.parse_cmd_tcl()
+function! s:VimLParser.parse_cmd_tcl() abort
   call self.parse_cmd_lua()
 endfunction
 
-function! s:VimLParser.parse_cmd_finish()
+function! s:VimLParser.parse_cmd_finish() abort
   call self.parse_cmd_common()
-  if self.context[0].type == s:NODE_TOPLEVEL
+  if self.context[0].type ==# s:NODE_TOPLEVEL
     call self.reader.seek_end(0)
   endif
 endfunction
 
 " FIXME
-function! s:VimLParser.parse_cmd_usercmd()
+function! s:VimLParser.parse_cmd_usercmd() abort
   call self.parse_cmd_common()
 endfunction
 
-function! s:VimLParser.parse_cmd_function()
+function! s:VimLParser.parse_cmd_function() abort
   let pos = self.reader.tell()
   call self.reader.skip_white()
 
@@ -1328,10 +1349,10 @@ function! s:VimLParser.parse_cmd_function()
   let left = self.parse_lvalue_func()
   call self.reader.skip_white()
 
-  if left.type == s:NODE_IDENTIFIER
+  if left.type ==# s:NODE_IDENTIFIER
     let s = left.value
     let ss = split(s, '\zs')
-    if ss[0] !=# '<' && !s:isupper(ss[0]) && stridx(s, ':') == -1 && stridx(s, '#') == -1
+    if ss[0] !=# '<' && ss[0] !=# '_' && !s:isupper(ss[0]) && stridx(s, ':') ==# -1 && stridx(s, '#') ==# -1
       throw s:Err(printf('E128: Function name must start with a capital or contain a colon: %s', s), left.pos)
     endif
   endif
@@ -1350,18 +1371,19 @@ function! s:VimLParser.parse_cmd_function()
   let node.ea = self.ea
   let node.left = left
   let node.rlist = []
+  let node.default_args = []
   let node.attr = {'range': 0, 'abort': 0, 'dict': 0, 'closure': 0}
   let node.endfunction = s:NIL
   call self.reader.getn(1)
   let tokenizer = s:ExprTokenizer.new(self.reader)
-  if tokenizer.peek().type == s:TOKEN_PCLOSE
+  if tokenizer.peek().type ==# s:TOKEN_PCLOSE
     call tokenizer.get()
   else
     let named = {}
     while s:TRUE
       let varnode = s:Node(s:NODE_IDENTIFIER)
       let token = tokenizer.get()
-      if token.type == s:TOKEN_IDENTIFIER
+      if token.type ==# s:TOKEN_IDENTIFIER
         if !s:isargname(token.value) || token.value ==# 'firstline' || token.value ==# 'lastline'
           throw s:Err(printf('E125: Illegal argument: %s', token.value), token.pos)
         elseif has_key(named, token.value)
@@ -1371,28 +1393,34 @@ function! s:VimLParser.parse_cmd_function()
         let varnode.pos = token.pos
         let varnode.value = token.value
         call add(node.rlist, varnode)
+        if tokenizer.peek().type ==# s:TOKEN_EQ
+          call tokenizer.get()
+          call add(node.default_args, self.parse_expr())
+        elseif len(node.default_args) > 0
+          throw s:Err('E989: Non-default argument follows default argument', varnode.pos)
+        endif
         " XXX: Vim doesn't skip white space before comma.  F(a ,b) => E475
-        if s:iswhite(self.reader.p(0)) && tokenizer.peek().type == s:TOKEN_COMMA
+        if s:iswhite(self.reader.p(0)) && tokenizer.peek().type ==# s:TOKEN_COMMA
           throw s:Err('E475: Invalid argument: White space is not allowed before comma', self.reader.getpos())
         endif
         let token = tokenizer.get()
-        if token.type == s:TOKEN_COMMA
+        if token.type ==# s:TOKEN_COMMA
           " XXX: Vim allows last comma.  F(a, b, ) => OK
-          if tokenizer.peek().type == s:TOKEN_PCLOSE
+          if tokenizer.peek().type ==# s:TOKEN_PCLOSE
             call tokenizer.get()
             break
           endif
-        elseif token.type == s:TOKEN_PCLOSE
+        elseif token.type ==# s:TOKEN_PCLOSE
           break
         else
           throw s:Err(printf('unexpected token: %s', token.value), token.pos)
         endif
-      elseif token.type == s:TOKEN_DOTDOTDOT
+      elseif token.type ==# s:TOKEN_DOTDOTDOT
         let varnode.pos = token.pos
         let varnode.value = token.value
         call add(node.rlist, varnode)
         let token = tokenizer.get()
-        if token.type == s:TOKEN_PCLOSE
+        if token.type ==# s:TOKEN_PCLOSE
           break
         else
           throw s:Err(printf('unexpected token: %s', token.value), token.pos)
@@ -1424,12 +1452,12 @@ function! s:VimLParser.parse_cmd_function()
   call self.push_context(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_endfunction()
+function! s:VimLParser.parse_cmd_endfunction() abort
   call self.check_missing_endif('ENDFUNCTION', self.ea.cmdpos)
   call self.check_missing_endtry('ENDFUNCTION', self.ea.cmdpos)
   call self.check_missing_endwhile('ENDFUNCTION', self.ea.cmdpos)
   call self.check_missing_endfor('ENDFUNCTION', self.ea.cmdpos)
-  if self.context[0].type != s:NODE_FUNCTION
+  if self.context[0].type !=# s:NODE_FUNCTION
     throw s:Err('E193: :endfunction not inside a function', self.ea.cmdpos)
   endif
   call self.reader.getn(-1)
@@ -1440,7 +1468,7 @@ function! s:VimLParser.parse_cmd_endfunction()
   call self.pop_context()
 endfunction
 
-function! s:VimLParser.parse_cmd_delfunction()
+function! s:VimLParser.parse_cmd_delfunction() abort
   let node = s:Node(s:NODE_DELFUNCTION)
   let node.pos = self.ea.cmdpos
   let node.ea = self.ea
@@ -1448,8 +1476,8 @@ function! s:VimLParser.parse_cmd_delfunction()
   call self.add_node(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_return()
-  if self.find_context(s:NODE_FUNCTION) == -1
+function! s:VimLParser.parse_cmd_return() abort
+  if self.find_context(s:NODE_FUNCTION) ==# -1
     throw s:Err('E133: :return not inside a function', self.ea.cmdpos)
   endif
   let node = s:Node(s:NODE_RETURN)
@@ -1464,7 +1492,7 @@ function! s:VimLParser.parse_cmd_return()
   call self.add_node(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_call()
+function! s:VimLParser.parse_cmd_call() abort
   let node = s:Node(s:NODE_EXCALL)
   let node.pos = self.ea.cmdpos
   let node.ea = self.ea
@@ -1474,13 +1502,59 @@ function! s:VimLParser.parse_cmd_call()
     throw s:Err('E471: Argument required', self.reader.getpos())
   endif
   let node.left = self.parse_expr()
-  if node.left.type != s:NODE_CALL
+  if node.left.type !=# s:NODE_CALL
     throw s:Err('Not an function call', node.left.pos)
   endif
   call self.add_node(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_let()
+function! s:VimLParser.parse_heredoc() abort
+  let node = s:Node(s:NODE_HEREDOC)
+  let node.pos = self.ea.cmdpos
+  let node.op = ''
+  let node.rlist = []
+  let node.body = []
+
+  while s:TRUE
+    call self.reader.skip_white()
+    let pos = self.reader.getpos()
+    let key = self.reader.read_word()
+    if key ==# ''
+      break
+    endif
+    if !s:islower(key[0])
+      let node.op = key
+      break
+    else
+      let keynode = s:Node(s:NODE_STRING)
+      let keynode.pos = pos
+      let keynode.value = key
+      call add(node.rlist, keynode)
+    endif
+  endwhile
+  if node.op ==# ''
+    throw s:Err('E172: Missing marker', self.reader.getpos())
+  endif
+  call self.parse_trail()
+  while s:TRUE
+    if self.reader.peek() ==# '<EOF>'
+      break
+    endif
+    let pos = self.reader.getpos()
+    let line = self.reader.getn(-1)
+    if line ==# node.op
+      return node
+    endif
+    let linenode = s:Node(s:NODE_STRING)
+    let linenode.pos = pos
+    let linenode.value = line
+    call add(node.body, linenode)
+    call self.reader.get()
+  endwhile
+  throw s:Err(printf("E990: Missing end marker '%s'", node.op), self.reader.getpos())
+endfunction
+
+function! s:VimLParser.parse_cmd_let() abort
   let pos = self.reader.tell()
   call self.reader.skip_white()
 
@@ -1495,9 +1569,15 @@ function! s:VimLParser.parse_cmd_let()
   call self.reader.skip_white()
   let s1 = self.reader.peekn(1)
   let s2 = self.reader.peekn(2)
+  " TODO check scriptversion?
+  if s2 ==# '..'
+    let s2 = self.reader.peekn(3)
+  elseif s2 ==# '=<'
+    let s2 = self.reader.peekn(3)
+  endif
 
   " :let {var-name} ..
-  if self.ends_excmds(s1) || (s2 !=# '+=' && s2 !=# '-=' && s2 !=# '.=' && s1 !=# '=')
+  if self.ends_excmds(s1) || (s2 !=# '+=' && s2 !=# '-=' && s2 !=# '.=' && s2 !=# '..=' && s2 !=# '*=' && s2 !=# '/=' && s2 !=# '%=' && s2 !=# '=<<' && s1 !=# '=')
     call self.reader.seek_set(pos)
     call self.parse_cmd_common()
     return
@@ -1512,9 +1592,16 @@ function! s:VimLParser.parse_cmd_let()
   let node.list = lhs.list
   let node.rest = lhs.rest
   let node.right = s:NIL
-  if s2 ==# '+=' || s2 ==# '-=' || s2 ==# '.='
-    call self.reader.getn(2)
+  if s2 ==# '+=' || s2 ==# '-=' || s2 ==# '.=' || s2 ==# '..=' || s2 ==# '*=' || s2 ==# '/=' || s2 ==# '%='
+    call self.reader.getn(len(s2))
     let node.op = s2
+  elseif s2 ==# '=<<'
+    call self.reader.getn(len(s2))
+    call self.reader.skip_white()
+    let node.op = s2
+    let node.right = self.parse_heredoc()
+    call self.add_node(node)
+    return
   elseif s1 ==# '='
     call self.reader.getn(1)
     let node.op = s1
@@ -1525,7 +1612,42 @@ function! s:VimLParser.parse_cmd_let()
   call self.add_node(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_unlet()
+function! s:VimLParser.parse_cmd_const() abort
+  let pos = self.reader.tell()
+  call self.reader.skip_white()
+
+  " :const
+  if self.ends_excmds(self.reader.peek())
+    call self.reader.seek_set(pos)
+    call self.parse_cmd_common()
+    return
+  endif
+
+  let lhs = self.parse_constlhs()
+  call self.reader.skip_white()
+  let s1 = self.reader.peekn(1)
+
+  " :const {var-name}
+  if self.ends_excmds(s1) || s1 !=# '='
+    call self.reader.seek_set(pos)
+    call self.parse_cmd_common()
+    return
+  endif
+
+  " :const left op right
+  let node = s:Node(s:NODE_CONST)
+  let node.pos = self.ea.cmdpos
+  let node.ea = self.ea
+  call self.reader.getn(1)
+  let node.op = s1
+  let node.left = lhs.left
+  let node.list = lhs.list
+  let node.rest = lhs.rest
+  let node.right = self.parse_expr()
+  call self.add_node(node)
+endfunction
+
+function! s:VimLParser.parse_cmd_unlet() abort
   let node = s:Node(s:NODE_UNLET)
   let node.pos = self.ea.cmdpos
   let node.ea = self.ea
@@ -1533,7 +1655,7 @@ function! s:VimLParser.parse_cmd_unlet()
   call self.add_node(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_lockvar()
+function! s:VimLParser.parse_cmd_lockvar() abort
   let node = s:Node(s:NODE_LOCKVAR)
   let node.pos = self.ea.cmdpos
   let node.ea = self.ea
@@ -1547,7 +1669,7 @@ function! s:VimLParser.parse_cmd_lockvar()
   call self.add_node(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_unlockvar()
+function! s:VimLParser.parse_cmd_unlockvar() abort
   let node = s:Node(s:NODE_UNLOCKVAR)
   let node.pos = self.ea.cmdpos
   let node.ea = self.ea
@@ -1561,7 +1683,7 @@ function! s:VimLParser.parse_cmd_unlockvar()
   call self.add_node(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_if()
+function! s:VimLParser.parse_cmd_if() abort
   let node = s:Node(s:NODE_IF)
   let node.pos = self.ea.cmdpos
   let node.body = []
@@ -1574,11 +1696,11 @@ function! s:VimLParser.parse_cmd_if()
   call self.push_context(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_elseif()
-  if self.context[0].type != s:NODE_IF && self.context[0].type != s:NODE_ELSEIF
+function! s:VimLParser.parse_cmd_elseif() abort
+  if self.context[0].type !=# s:NODE_IF && self.context[0].type !=# s:NODE_ELSEIF
     throw s:Err('E582: :elseif without :if', self.ea.cmdpos)
   endif
-  if self.context[0].type != s:NODE_IF
+  if self.context[0].type !=# s:NODE_IF
     call self.pop_context()
   endif
   let node = s:Node(s:NODE_ELSEIF)
@@ -1590,11 +1712,11 @@ function! s:VimLParser.parse_cmd_elseif()
   call self.push_context(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_else()
-  if self.context[0].type != s:NODE_IF && self.context[0].type != s:NODE_ELSEIF
+function! s:VimLParser.parse_cmd_else() abort
+  if self.context[0].type !=# s:NODE_IF && self.context[0].type !=# s:NODE_ELSEIF
     throw s:Err('E581: :else without :if', self.ea.cmdpos)
   endif
-  if self.context[0].type != s:NODE_IF
+  if self.context[0].type !=# s:NODE_IF
     call self.pop_context()
   endif
   let node = s:Node(s:NODE_ELSE)
@@ -1605,11 +1727,11 @@ function! s:VimLParser.parse_cmd_else()
   call self.push_context(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_endif()
-  if self.context[0].type != s:NODE_IF && self.context[0].type != s:NODE_ELSEIF && self.context[0].type != s:NODE_ELSE
+function! s:VimLParser.parse_cmd_endif() abort
+  if self.context[0].type !=# s:NODE_IF && self.context[0].type !=# s:NODE_ELSEIF && self.context[0].type !=# s:NODE_ELSE
     throw s:Err('E580: :endif without :if', self.ea.cmdpos)
   endif
-  if self.context[0].type != s:NODE_IF
+  if self.context[0].type !=# s:NODE_IF
     call self.pop_context()
   endif
   let node = s:Node(s:NODE_ENDIF)
@@ -1619,7 +1741,7 @@ function! s:VimLParser.parse_cmd_endif()
   call self.pop_context()
 endfunction
 
-function! s:VimLParser.parse_cmd_while()
+function! s:VimLParser.parse_cmd_while() abort
   let node = s:Node(s:NODE_WHILE)
   let node.pos = self.ea.cmdpos
   let node.body = []
@@ -1630,8 +1752,8 @@ function! s:VimLParser.parse_cmd_while()
   call self.push_context(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_endwhile()
-  if self.context[0].type != s:NODE_WHILE
+function! s:VimLParser.parse_cmd_endwhile() abort
+  if self.context[0].type !=# s:NODE_WHILE
     throw s:Err('E588: :endwhile without :while', self.ea.cmdpos)
   endif
   let node = s:Node(s:NODE_ENDWHILE)
@@ -1641,7 +1763,7 @@ function! s:VimLParser.parse_cmd_endwhile()
   call self.pop_context()
 endfunction
 
-function! s:VimLParser.parse_cmd_for()
+function! s:VimLParser.parse_cmd_for() abort
   let node = s:Node(s:NODE_FOR)
   let node.pos = self.ea.cmdpos
   let node.body = []
@@ -1663,8 +1785,8 @@ function! s:VimLParser.parse_cmd_for()
   call self.push_context(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_endfor()
-  if self.context[0].type != s:NODE_FOR
+function! s:VimLParser.parse_cmd_endfor() abort
+  if self.context[0].type !=# s:NODE_FOR
     throw s:Err('E588: :endfor without :for', self.ea.cmdpos)
   endif
   let node = s:Node(s:NODE_ENDFOR)
@@ -1674,8 +1796,8 @@ function! s:VimLParser.parse_cmd_endfor()
   call self.pop_context()
 endfunction
 
-function! s:VimLParser.parse_cmd_continue()
-  if self.find_context(s:NODE_WHILE) == -1 && self.find_context(s:NODE_FOR) == -1
+function! s:VimLParser.parse_cmd_continue() abort
+  if self.find_context(s:NODE_WHILE) ==# -1 && self.find_context(s:NODE_FOR) ==# -1
     throw s:Err('E586: :continue without :while or :for', self.ea.cmdpos)
   endif
   let node = s:Node(s:NODE_CONTINUE)
@@ -1684,8 +1806,8 @@ function! s:VimLParser.parse_cmd_continue()
   call self.add_node(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_break()
-  if self.find_context(s:NODE_WHILE) == -1 && self.find_context(s:NODE_FOR) == -1
+function! s:VimLParser.parse_cmd_break() abort
+  if self.find_context(s:NODE_WHILE) ==# -1 && self.find_context(s:NODE_FOR) ==# -1
     throw s:Err('E587: :break without :while or :for', self.ea.cmdpos)
   endif
   let node = s:Node(s:NODE_BREAK)
@@ -1694,7 +1816,7 @@ function! s:VimLParser.parse_cmd_break()
   call self.add_node(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_try()
+function! s:VimLParser.parse_cmd_try() abort
   let node = s:Node(s:NODE_TRY)
   let node.pos = self.ea.cmdpos
   let node.body = []
@@ -1706,13 +1828,13 @@ function! s:VimLParser.parse_cmd_try()
   call self.push_context(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_catch()
-  if self.context[0].type == s:NODE_FINALLY
+function! s:VimLParser.parse_cmd_catch() abort
+  if self.context[0].type ==# s:NODE_FINALLY
     throw s:Err('E604: :catch after :finally', self.ea.cmdpos)
-  elseif self.context[0].type != s:NODE_TRY && self.context[0].type != s:NODE_CATCH
+  elseif self.context[0].type !=# s:NODE_TRY && self.context[0].type !=# s:NODE_CATCH
     throw s:Err('E603: :catch without :try', self.ea.cmdpos)
   endif
-  if self.context[0].type != s:NODE_TRY
+  if self.context[0].type !=# s:NODE_TRY
     call self.pop_context()
   endif
   let node = s:Node(s:NODE_CATCH)
@@ -1728,11 +1850,11 @@ function! s:VimLParser.parse_cmd_catch()
   call self.push_context(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_finally()
-  if self.context[0].type != s:NODE_TRY && self.context[0].type != s:NODE_CATCH
+function! s:VimLParser.parse_cmd_finally() abort
+  if self.context[0].type !=# s:NODE_TRY && self.context[0].type !=# s:NODE_CATCH
     throw s:Err('E606: :finally without :try', self.ea.cmdpos)
   endif
-  if self.context[0].type != s:NODE_TRY
+  if self.context[0].type !=# s:NODE_TRY
     call self.pop_context()
   endif
   let node = s:Node(s:NODE_FINALLY)
@@ -1743,11 +1865,11 @@ function! s:VimLParser.parse_cmd_finally()
   call self.push_context(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_endtry()
-  if self.context[0].type != s:NODE_TRY && self.context[0].type != s:NODE_CATCH && self.context[0].type != s:NODE_FINALLY
+function! s:VimLParser.parse_cmd_endtry() abort
+  if self.context[0].type !=# s:NODE_TRY && self.context[0].type !=# s:NODE_CATCH && self.context[0].type !=# s:NODE_FINALLY
     throw s:Err('E602: :endtry without :try', self.ea.cmdpos)
   endif
-  if self.context[0].type != s:NODE_TRY
+  if self.context[0].type !=# s:NODE_TRY
     call self.pop_context()
   endif
   let node = s:Node(s:NODE_ENDTRY)
@@ -1757,7 +1879,7 @@ function! s:VimLParser.parse_cmd_endtry()
   call self.pop_context()
 endfunction
 
-function! s:VimLParser.parse_cmd_throw()
+function! s:VimLParser.parse_cmd_throw() abort
   let node = s:Node(s:NODE_THROW)
   let node.pos = self.ea.cmdpos
   let node.ea = self.ea
@@ -1765,7 +1887,15 @@ function! s:VimLParser.parse_cmd_throw()
   call self.add_node(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_echo()
+function! s:VimLParser.parse_cmd_eval() abort
+  let node = s:Node(s:NODE_EVAL)
+  let node.pos = self.ea.cmdpos
+  let node.ea = self.ea
+  let node.left = self.parse_expr()
+  call self.add_node(node)
+endfunction
+
+function! s:VimLParser.parse_cmd_echo() abort
   let node = s:Node(s:NODE_ECHO)
   let node.pos = self.ea.cmdpos
   let node.ea = self.ea
@@ -1773,7 +1903,7 @@ function! s:VimLParser.parse_cmd_echo()
   call self.add_node(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_echon()
+function! s:VimLParser.parse_cmd_echon() abort
   let node = s:Node(s:NODE_ECHON)
   let node.pos = self.ea.cmdpos
   let node.ea = self.ea
@@ -1781,7 +1911,7 @@ function! s:VimLParser.parse_cmd_echon()
   call self.add_node(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_echohl()
+function! s:VimLParser.parse_cmd_echohl() abort
   let node = s:Node(s:NODE_ECHOHL)
   let node.pos = self.ea.cmdpos
   let node.ea = self.ea
@@ -1792,7 +1922,7 @@ function! s:VimLParser.parse_cmd_echohl()
   call self.add_node(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_echomsg()
+function! s:VimLParser.parse_cmd_echomsg() abort
   let node = s:Node(s:NODE_ECHOMSG)
   let node.pos = self.ea.cmdpos
   let node.ea = self.ea
@@ -1800,7 +1930,7 @@ function! s:VimLParser.parse_cmd_echomsg()
   call self.add_node(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_echoerr()
+function! s:VimLParser.parse_cmd_echoerr() abort
   let node = s:Node(s:NODE_ECHOERR)
   let node.pos = self.ea.cmdpos
   let node.ea = self.ea
@@ -1808,7 +1938,7 @@ function! s:VimLParser.parse_cmd_echoerr()
   call self.add_node(node)
 endfunction
 
-function! s:VimLParser.parse_cmd_execute()
+function! s:VimLParser.parse_cmd_execute() abort
   let node = s:Node(s:NODE_EXECUTE)
   let node.pos = self.ea.cmdpos
   let node.ea = self.ea
@@ -1816,11 +1946,11 @@ function! s:VimLParser.parse_cmd_execute()
   call self.add_node(node)
 endfunction
 
-function! s:VimLParser.parse_expr()
+function! s:VimLParser.parse_expr() abort
   return s:ExprParser.new(self.reader).parse()
 endfunction
 
-function! s:VimLParser.parse_exprlist()
+function! s:VimLParser.parse_exprlist() abort
   let list = []
   while s:TRUE
     call self.reader.skip_white()
@@ -1834,31 +1964,55 @@ function! s:VimLParser.parse_exprlist()
   return list
 endfunction
 
-function! s:VimLParser.parse_lvalue_func()
+function! s:VimLParser.parse_lvalue_func() abort
   let p = s:LvalueParser.new(self.reader)
   let node = p.parse()
-  if node.type == s:NODE_IDENTIFIER || node.type == s:NODE_CURLYNAME || node.type == s:NODE_SUBSCRIPT || node.type == s:NODE_DOT || node.type == s:NODE_OPTION || node.type == s:NODE_ENV || node.type == s:NODE_REG
+  if node.type ==# s:NODE_IDENTIFIER || node.type ==# s:NODE_CURLYNAME || node.type ==# s:NODE_SUBSCRIPT || node.type ==# s:NODE_DOT || node.type ==# s:NODE_OPTION || node.type ==# s:NODE_ENV || node.type ==# s:NODE_REG
     return node
   endif
   throw s:Err('Invalid Expression', node.pos)
 endfunction
 
 " FIXME:
-function! s:VimLParser.parse_lvalue()
+function! s:VimLParser.parse_lvalue() abort
+  let p = s:LvalueParser.new(self.reader)
+  let node = p.parse()
+  if node.type ==# s:NODE_IDENTIFIER
+    if !s:isvarname(node.value)
+      throw s:Err(printf('E461: Illegal variable name: %s', node.value), node.pos)
+    endif
+  endif
+  if node.type ==# s:NODE_IDENTIFIER || node.type ==# s:NODE_CURLYNAME || node.type ==# s:NODE_SUBSCRIPT || node.type ==# s:NODE_SLICE || node.type ==# s:NODE_DOT || node.type ==# s:NODE_OPTION || node.type ==# s:NODE_ENV || node.type ==# s:NODE_REG
+    return node
+  endif
+  throw s:Err('Invalid Expression', node.pos)
+endfunction
+
+" TODO: merge with s:VimLParser.parse_lvalue()
+function! s:VimLParser.parse_constlvalue() abort
   let p = s:LvalueParser.new(self.reader)
   let node = p.parse()
-  if node.type == s:NODE_IDENTIFIER
+  if node.type ==# s:NODE_IDENTIFIER
     if !s:isvarname(node.value)
       throw s:Err(printf('E461: Illegal variable name: %s', node.value), node.pos)
     endif
   endif
-  if node.type == s:NODE_IDENTIFIER || node.type == s:NODE_CURLYNAME || node.type == s:NODE_SUBSCRIPT || node.type == s:NODE_SLICE || node.type == s:NODE_DOT || node.type == s:NODE_OPTION || node.type == s:NODE_ENV || node.type == s:NODE_REG
+  if node.type ==# s:NODE_IDENTIFIER || node.type ==# s:NODE_CURLYNAME
     return node
+  elseif node.type ==# s:NODE_SUBSCRIPT || node.type ==# s:NODE_SLICE || node.type ==# s:NODE_DOT
+    throw s:Err('E996: Cannot lock a list or dict', node.pos)
+  elseif node.type ==# s:NODE_OPTION
+    throw s:Err('E996: Cannot lock an option', node.pos)
+  elseif node.type ==# s:NODE_ENV
+    throw s:Err('E996: Cannot lock an environment variable', node.pos)
+  elseif node.type ==# s:NODE_REG
+    throw s:Err('E996: Cannot lock a register', node.pos)
   endif
   throw s:Err('Invalid Expression', node.pos)
 endfunction
 
-function! s:VimLParser.parse_lvaluelist()
+
+function! s:VimLParser.parse_lvaluelist() abort
   let list = []
   let node = self.parse_expr()
   call add(list, node)
@@ -1874,25 +2028,25 @@ function! s:VimLParser.parse_lvaluelist()
 endfunction
 
 " FIXME:
-function! s:VimLParser.parse_letlhs()
+function! s:VimLParser.parse_letlhs() abort
   let lhs = {'left': s:NIL, 'list': s:NIL, 'rest': s:NIL}
   let tokenizer = s:ExprTokenizer.new(self.reader)
-  if tokenizer.peek().type == s:TOKEN_SQOPEN
+  if tokenizer.peek().type ==# s:TOKEN_SQOPEN
     call tokenizer.get()
     let lhs.list = []
     while s:TRUE
       let node = self.parse_lvalue()
       call add(lhs.list, node)
       let token = tokenizer.get()
-      if token.type == s:TOKEN_SQCLOSE
+      if token.type ==# s:TOKEN_SQCLOSE
         break
-      elseif token.type == s:TOKEN_COMMA
+      elseif token.type ==# s:TOKEN_COMMA
         continue
-      elseif token.type == s:TOKEN_SEMICOLON
+      elseif token.type ==# s:TOKEN_SEMICOLON
         let node = self.parse_lvalue()
         let lhs.rest = node
         let token = tokenizer.get()
-        if token.type == s:TOKEN_SQCLOSE
+        if token.type ==# s:TOKEN_SQCLOSE
           break
         else
           throw s:Err(printf('E475 Invalid argument: %s', token.value), token.pos)
@@ -1907,12 +2061,46 @@ function! s:VimLParser.parse_letlhs()
   return lhs
 endfunction
 
-function! s:VimLParser.ends_excmds(c)
+" TODO: merge with s:VimLParser.parse_letlhs() ?
+function! s:VimLParser.parse_constlhs() abort
+  let lhs = {'left': s:NIL, 'list': s:NIL, 'rest': s:NIL}
+  let tokenizer = s:ExprTokenizer.new(self.reader)
+  if tokenizer.peek().type ==# s:TOKEN_SQOPEN
+    call tokenizer.get()
+    let lhs.list = []
+    while s:TRUE
+      let node = self.parse_lvalue()
+      call add(lhs.list, node)
+      let token = tokenizer.get()
+      if token.type ==# s:TOKEN_SQCLOSE
+        break
+      elseif token.type ==# s:TOKEN_COMMA
+        continue
+      elseif token.type ==# s:TOKEN_SEMICOLON
+        let node = self.parse_lvalue()
+        let lhs.rest = node
+        let token = tokenizer.get()
+        if token.type ==# s:TOKEN_SQCLOSE
+          break
+        else
+          throw s:Err(printf('E475 Invalid argument: %s', token.value), token.pos)
+        endif
+      else
+        throw s:Err(printf('E475 Invalid argument: %s', token.value), token.pos)
+      endif
+    endwhile
+  else
+    let lhs.left = self.parse_constlvalue()
+  endif
+  return lhs
+endfunction
+
+function! s:VimLParser.ends_excmds(c) abort
   return a:c ==# '' || a:c ==# '|' || a:c ==# '"' || a:c ==# '<EOF>' || a:c ==# '<EOL>'
 endfunction
 
 " FIXME: validate argument
-function! s:VimLParser.parse_wincmd()
+function! s:VimLParser.parse_wincmd() abort
   let c = self.reader.getn(1)
   if c ==# ''
     throw s:Err('E471: Argument required', self.reader.getpos())
@@ -1935,17 +2123,17 @@ function! s:VimLParser.parse_wincmd()
 endfunction
 
 " FIXME: validate argument
-function! s:VimLParser.parse_cmd_syntax()
+function! s:VimLParser.parse_cmd_syntax() abort
   let end = self.reader.getpos()
   while s:TRUE
     let end = self.reader.getpos()
     let c = self.reader.peek()
-    if c == "/" || c == "'" || c == "\""
+    if c ==# '/' || c ==# "'" || c ==# '"'
       call self.reader.getn(1)
       call self.parse_pattern(c)
-    elseif c == "="
+    elseif c ==# '='
       call self.reader.getn(1)
-      call self.parse_pattern(" ")
+      call self.parse_pattern(' ')
     elseif self.ends_excmds(c)
       break
     endif
@@ -2077,7 +2265,7 @@ let s:VimLParser.builtin_commands = [
       \ {'name': 'debug', 'minlen': 3, 'flags': 'NEEDARG|EXTRA|NOTRLCOM|SBOXOK|CMDWIN', 'parser': 'parse_cmd_common'},
       \ {'name': 'debuggreedy', 'minlen': 6, 'flags': 'RANGE|NOTADR|ZEROR|TRLBAR|CMDWIN', 'parser': 'parse_cmd_common'},
       \ {'name': 'delcommand', 'minlen': 4, 'flags': 'NEEDARG|WORD1|TRLBAR|CMDWIN', 'parser': 'parse_cmd_common'},
-      \ {'name': 'delfunction', 'minlen': 4, 'flags': 'NEEDARG|WORD1|CMDWIN', 'parser': 'parse_cmd_delfunction'},
+      \ {'name': 'delfunction', 'minlen': 4, 'flags': 'BANG|NEEDARG|WORD1|CMDWIN', 'parser': 'parse_cmd_delfunction'},
       \ {'name': 'diffupdate', 'minlen': 3, 'flags': 'BANG|TRLBAR', 'parser': 'parse_cmd_common'},
       \ {'name': 'diffget', 'minlen': 5, 'flags': 'RANGE|EXTRA|TRLBAR|MODIFY', 'parser': 'parse_cmd_common'},
       \ {'name': 'diffoff', 'minlen': 5, 'flags': 'BANG|TRLBAR', 'parser': 'parse_cmd_common'},
@@ -2110,6 +2298,7 @@ let s:VimLParser.builtin_commands = [
       \ {'name': 'endtry', 'minlen': 4, 'flags': 'TRLBAR|SBOXOK|CMDWIN', 'parser': 'parse_cmd_endtry'},
       \ {'name': 'endwhile', 'minlen': 4, 'flags': 'TRLBAR|SBOXOK|CMDWIN', 'parser': 'parse_cmd_endwhile'},
       \ {'name': 'enew', 'minlen': 3, 'flags': 'BANG|TRLBAR', 'parser': 'parse_cmd_common'},
+      \ {'name': 'eval', 'minlen': 2, 'flags': 'EXTRA|NOTRLCOM|SBOXOK|CMDWIN', 'parser': 'parse_cmd_eval'},
       \ {'name': 'ex', 'minlen': 2, 'flags': 'BANG|FILE1|EDITCMD|ARGOPT|TRLBAR', 'parser': 'parse_cmd_common'},
       \ {'name': 'execute', 'minlen': 3, 'flags': 'EXTRA|NOTRLCOM|SBOXOK|CMDWIN', 'parser': 'parse_cmd_execute'},
       \ {'name': 'exit', 'minlen': 3, 'flags': 'RANGE|WHOLEFOLD|BANG|FILE1|ARGOPT|DFLALL|TRLBAR|CMDWIN', 'parser': 'parse_cmd_common'},
@@ -2185,6 +2374,7 @@ let s:VimLParser.builtin_commands = [
       \ {'name': 'left', 'minlen': 2, 'flags': 'TRLBAR|RANGE|WHOLEFOLD|EXTRA|CMDWIN|MODIFY', 'parser': 'parse_cmd_common'},
       \ {'name': 'leftabove', 'minlen': 5, 'flags': 'NEEDARG|EXTRA|NOTRLCOM', 'parser': 'parse_cmd_common'},
       \ {'name': 'let', 'minlen': 3, 'flags': 'EXTRA|NOTRLCOM|SBOXOK|CMDWIN', 'parser': 'parse_cmd_let'},
+      \ {'name': 'const', 'minlen': 4, 'flags': 'EXTRA|NOTRLCOM|SBOXOK|CMDWIN', 'parser': 'parse_cmd_const'},
       \ {'name': 'lexpr', 'minlen': 3, 'flags': 'NEEDARG|WORD1|NOTRLCOM|TRLBAR|BANG', 'parser': 'parse_cmd_common'},
       \ {'name': 'lfile', 'minlen': 2, 'flags': 'TRLBAR|FILE1|BANG', 'parser': 'parse_cmd_common'},
       \ {'name': 'lfirst', 'minlen': 4, 'flags': 'RANGE|NOTADR|COUNT|TRLBAR|BANG', 'parser': 'parse_cmd_common'},
@@ -2521,33 +2711,555 @@ let s:VimLParser.builtin_commands = [
       \ {'flags': 'EXTRA|TRLBAR|CMDWIN', 'minlen': 5, 'name': 'tmapclear', 'parser': 'parse_cmd_common'},
       \ {'flags': 'EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN', 'minlen': 3, 'name': 'tnoremap', 'parser': 'parse_cmd_common'},
       \ {'flags': 'EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN', 'minlen': 5, 'name': 'tunmap', 'parser': 'parse_cmd_common'},
+      \
+      \ {'flags': 'RANGE|COUNT|TRLBAR', 'minlen': 4, 'name': 'cabove', 'parser': 'parse_cmd_common'},
+      \ {'flags': 'RANGE|COUNT|TRLBAR', 'minlen': 3, 'name': 'cafter', 'parser': 'parse_cmd_common'},
+      \ {'flags': 'RANGE|COUNT|TRLBAR', 'minlen': 3, 'name': 'cbefore', 'parser': 'parse_cmd_common'},
+      \ {'flags': 'RANGE|COUNT|TRLBAR', 'minlen': 4, 'name': 'cbelow', 'parser': 'parse_cmd_common'},
+      \ {'flags': 'EXTRA|NOTRLCOM|SBOXOK|CMDWIN', 'minlen': 4, 'name': 'const', 'parser': 'parse_cmd_common'},
+      \ {'flags': 'RANGE|COUNT|TRLBAR', 'minlen': 3, 'name': 'labove', 'parser': 'parse_cmd_common'},
+      \ {'flags': 'RANGE|COUNT|TRLBAR', 'minlen': 3, 'name': 'lafter', 'parser': 'parse_cmd_common'},
+      \ {'flags': 'RANGE|COUNT|TRLBAR', 'minlen': 3, 'name': 'lbefore', 'parser': 'parse_cmd_common'},
+      \ {'flags': 'RANGE|COUNT|TRLBAR', 'minlen': 4, 'name': 'lbelow', 'parser': 'parse_cmd_common'},
+      \ {'flags': 'TRLBAR|CMDWIN', 'minlen': 7, 'name': 'redrawtabline', 'parser': 'parse_cmd_common'},
+      \ {'flags': 'WORD1|TRLBAR|CMDWIN', 'minlen': 7, 'name': 'scriptversion', 'parser': 'parse_cmd_common'},
+      \ {'flags': 'BANG|FILE1|TRLBAR|CMDWIN', 'minlen': 2, 'name': 'tcd', 'parser': 'parse_cmd_common'},
+      \ {'flags': 'BANG|FILE1|TRLBAR|CMDWIN', 'minlen': 3, 'name': 'tchdir', 'parser': 'parse_cmd_common'},
+      \ {'flags': 'RANGE|ZEROR|EXTRA|TRLBAR|NOTRLCOM|CTRLV|CMDWIN', 'minlen': 3, 'name': 'tlmenu', 'parser': 'parse_cmd_common'},
+      \ {'flags': 'RANGE|ZEROR|EXTRA|TRLBAR|NOTRLCOM|CTRLV|CMDWIN', 'minlen': 3, 'name': 'tlnoremenu', 'parser': 'parse_cmd_common'},
+      \ {'flags': 'RANGE|ZEROR|EXTRA|TRLBAR|NOTRLCOM|CTRLV|CMDWIN', 'minlen': 3, 'name': 'tlunmenu', 'parser': 'parse_cmd_common'},
+      \ {'flags': 'EXTRA|TRLBAR|CMDWIN', 'minlen': 2, 'name': 'xrestore', 'parser': 'parse_cmd_common'},
+      \
+      \ {'flags': 'EXTRA|BANG|SBOXOK|CMDWIN', 'minlen': 3, 'name': 'def', 'parser': 'parse_cmd_common'},
+      \ {'flags': 'EXTRA|NEEDARG|TRLBAR|CMDWIN', 'minlen': 4, 'name': 'disassemble', 'parser': 'parse_cmd_common'},
+      \ {'flags': 'TRLBAR|CMDWIN', 'minlen': 4, 'name': 'enddef', 'parser': 'parse_cmd_common'},
+      \ {'flags': 'EXTRA|NOTRLCOM', 'minlen': 3, 'name': 'export', 'parser': 'parse_cmd_common'},
+      \ {'flags': 'EXTRA|NOTRLCOM', 'minlen': 3, 'name': 'import', 'parser': 'parse_cmd_common'},
+      \ {'flags': 'BANG|RANGE|NEEDARG|EXTRA|TRLBAR', 'minlen': 7, 'name': 'spellrare', 'parser': 'parse_cmd_common'},
+      \ {'flags': '', 'minlen': 4, 'name': 'vim9script', 'parser': 'parse_cmd_common'},
+      \]
+
+" To find new builtin_functions, run the below script.
+" $ scripts/update_builtin_functions.sh /path/to/vim/src/evalfunc.c
+let s:VimLParser.builtin_functions = [
+      \ {'name': 'abs', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'acos', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'add', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'and', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'append', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_LAST'},
+      \ {'name': 'appendbufline', 'min_argc': 3, 'max_argc': 3, 'argtype': 'FEARG_LAST'},
+      \ {'name': 'argc', 'min_argc': 0, 'max_argc': 1, 'argtype': '0'},
+      \ {'name': 'argidx', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'arglistid', 'min_argc': 0, 'max_argc': 2, 'argtype': '0'},
+      \ {'name': 'argv', 'min_argc': 0, 'max_argc': 2, 'argtype': '0'},
+      \ {'name': 'asin', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'assert_beeps', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'assert_equal', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_2'},
+      \ {'name': 'assert_equalfile', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'assert_exception', 'min_argc': 1, 'max_argc': 2, 'argtype': '0'},
+      \ {'name': 'assert_fails', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'assert_false', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'assert_inrange', 'min_argc': 3, 'max_argc': 4, 'argtype': 'FEARG_3'},
+      \ {'name': 'assert_match', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_2'},
+      \ {'name': 'assert_notequal', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_2'},
+      \ {'name': 'assert_notmatch', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_2'},
+      \ {'name': 'assert_report', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'assert_true', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'atan', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'atan2', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'balloon_gettext', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'balloon_show', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'balloon_split', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'browse', 'min_argc': 4, 'max_argc': 4, 'argtype': '0'},
+      \ {'name': 'browsedir', 'min_argc': 2, 'max_argc': 2, 'argtype': '0'},
+      \ {'name': 'bufadd', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'bufexists', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'buffer_exists', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'buffer_name', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'buffer_number', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'buflisted', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'bufload', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'bufloaded', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'bufname', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'bufnr', 'min_argc': 0, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'bufwinid', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'bufwinnr', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'byte2line', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'byteidx', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'byteidxcomp', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'call', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'ceil', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'ch_canread', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'ch_close', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'ch_close_in', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'ch_evalexpr', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'ch_evalraw', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'ch_getbufnr', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'ch_getjob', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'ch_info', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'ch_log', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'ch_logfile', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'ch_open', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'ch_read', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'ch_readblob', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'ch_readraw', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'ch_sendexpr', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'ch_sendraw', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'ch_setoptions', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'ch_status', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'changenr', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'char2nr', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'chdir', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'cindent', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'clearmatches', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'col', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'complete', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_2'},
+      \ {'name': 'complete_add', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'complete_check', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'complete_info', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'confirm', 'min_argc': 1, 'max_argc': 4, 'argtype': 'FEARG_1'},
+      \ {'name': 'copy', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'cos', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'cosh', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'count', 'min_argc': 2, 'max_argc': 4, 'argtype': 'FEARG_1'},
+      \ {'name': 'cscope_connection', 'min_argc': 0, 'max_argc': 3, 'argtype': '0'},
+      \ {'name': 'cursor', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'debugbreak', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'deepcopy', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'delete', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'deletebufline', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'did_filetype', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'diff_filler', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'diff_hlID', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'echoraw', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'empty', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'environ', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'escape', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'eval', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'eventhandler', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'executable', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'execute', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'exepath', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'exists', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'exp', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'expand', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'expandcmd', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'extend', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'feedkeys', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'file_readable', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'filereadable', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'filewritable', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'filter', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'finddir', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'findfile', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'float2nr', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'floor', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'fmod', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'fnameescape', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'fnamemodify', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'foldclosed', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'foldclosedend', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'foldlevel', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'foldtext', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'foldtextresult', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'foreground', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'funcref', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'function', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'garbagecollect', 'min_argc': 0, 'max_argc': 1, 'argtype': '0'},
+      \ {'name': 'get', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'getbufinfo', 'min_argc': 0, 'max_argc': 1, 'argtype': '0'},
+      \ {'name': 'getbufline', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'getbufvar', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'getchangelist', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'getchar', 'min_argc': 0, 'max_argc': 1, 'argtype': '0'},
+      \ {'name': 'getcharmod', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'getcharsearch', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'getcmdline', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'getcmdpos', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'getcmdtype', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'getcmdwintype', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'getcompletion', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'getcurpos', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'getcwd', 'min_argc': 0, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'getenv', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'getfontname', 'min_argc': 0, 'max_argc': 1, 'argtype': '0'},
+      \ {'name': 'getfperm', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'getfsize', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'getftime', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'getftype', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'getimstatus', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'getjumplist', 'min_argc': 0, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'getline', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'getloclist', 'min_argc': 1, 'max_argc': 2, 'argtype': '0'},
+      \ {'name': 'getmatches', 'min_argc': 0, 'max_argc': 1, 'argtype': '0'},
+      \ {'name': 'getmousepos', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'getpid', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'getpos', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'getqflist', 'min_argc': 0, 'max_argc': 1, 'argtype': '0'},
+      \ {'name': 'getreg', 'min_argc': 0, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'getregtype', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'gettabinfo', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'gettabvar', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'gettabwinvar', 'min_argc': 3, 'max_argc': 4, 'argtype': 'FEARG_1'},
+      \ {'name': 'gettagstack', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'getwininfo', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'getwinpos', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'getwinposx', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'getwinposy', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'getwinvar', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'glob', 'min_argc': 1, 'max_argc': 4, 'argtype': 'FEARG_1'},
+      \ {'name': 'glob2regpat', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'globpath', 'min_argc': 2, 'max_argc': 5, 'argtype': 'FEARG_2'},
+      \ {'name': 'has', 'min_argc': 1, 'max_argc': 1, 'argtype': '0'},
+      \ {'name': 'has_key', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'haslocaldir', 'min_argc': 0, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'hasmapto', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'highlightID', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'highlight_exists', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'histadd', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_2'},
+      \ {'name': 'histdel', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'histget', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'histnr', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'hlID', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'hlexists', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'hostname', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'iconv', 'min_argc': 3, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'indent', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'index', 'min_argc': 2, 'max_argc': 4, 'argtype': 'FEARG_1'},
+      \ {'name': 'input', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'inputdialog', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'inputlist', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'inputrestore', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'inputsave', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'inputsecret', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'insert', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'interrupt', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'invert', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'isdirectory', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'isinf', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'islocked', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'isnan', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'items', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'job_getchannel', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'job_info', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'job_setoptions', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'job_start', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'job_status', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'job_stop', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'join', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'js_decode', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'js_encode', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'json_decode', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'json_encode', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'keys', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'last_buffer_nr', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'len', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'libcall', 'min_argc': 3, 'max_argc': 3, 'argtype': 'FEARG_3'},
+      \ {'name': 'libcallnr', 'min_argc': 3, 'max_argc': 3, 'argtype': 'FEARG_3'},
+      \ {'name': 'line', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'line2byte', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'lispindent', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'list2str', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'listener_add', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_2'},
+      \ {'name': 'listener_flush', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'listener_remove', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'localtime', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'log', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'log10', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'luaeval', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'map', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'maparg', 'min_argc': 1, 'max_argc': 4, 'argtype': 'FEARG_1'},
+      \ {'name': 'mapcheck', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'match', 'min_argc': 2, 'max_argc': 4, 'argtype': 'FEARG_1'},
+      \ {'name': 'matchadd', 'min_argc': 2, 'max_argc': 5, 'argtype': 'FEARG_1'},
+      \ {'name': 'matchaddpos', 'min_argc': 2, 'max_argc': 5, 'argtype': 'FEARG_1'},
+      \ {'name': 'matcharg', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'matchdelete', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'matchend', 'min_argc': 2, 'max_argc': 4, 'argtype': 'FEARG_1'},
+      \ {'name': 'matchlist', 'min_argc': 2, 'max_argc': 4, 'argtype': 'FEARG_1'},
+      \ {'name': 'matchstr', 'min_argc': 2, 'max_argc': 4, 'argtype': 'FEARG_1'},
+      \ {'name': 'matchstrpos', 'min_argc': 2, 'max_argc': 4, 'argtype': 'FEARG_1'},
+      \ {'name': 'max', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'menu_info', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'min', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'mkdir', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'mode', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'mzeval', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'nextnonblank', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'nr2char', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'or', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'pathshorten', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'perleval', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'popup_atcursor', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'popup_beval', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'popup_clear', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'popup_close', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'popup_create', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'popup_dialog', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'popup_filter_menu', 'min_argc': 2, 'max_argc': 2, 'argtype': '0'},
+      \ {'name': 'popup_filter_yesno', 'min_argc': 2, 'max_argc': 2, 'argtype': '0'},
+      \ {'name': 'popup_findinfo', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'popup_findpreview', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'popup_getoptions', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'popup_getpos', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'popup_hide', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'popup_locate', 'min_argc': 2, 'max_argc': 2, 'argtype': '0'},
+      \ {'name': 'popup_menu', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'popup_move', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'popup_notification', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'popup_setoptions', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'popup_settext', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'popup_show', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'pow', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'prevnonblank', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'printf', 'min_argc': 1, 'max_argc': 19, 'argtype': 'FEARG_2'},
+      \ {'name': 'prompt_setcallback', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'prompt_setinterrupt', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'prompt_setprompt', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'prop_add', 'min_argc': 3, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'prop_clear', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'prop_find', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'prop_list', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'prop_remove', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'prop_type_add', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'prop_type_change', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'prop_type_delete', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'prop_type_get', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'prop_type_list', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'pum_getpos', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'pumvisible', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'py3eval', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'pyeval', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'pyxeval', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'rand', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'range', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'readdir', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'readfile', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'reg_executing', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'reg_recording', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'reltime', 'min_argc': 0, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'reltimefloat', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'reltimestr', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'remote_expr', 'min_argc': 2, 'max_argc': 4, 'argtype': 'FEARG_1'},
+      \ {'name': 'remote_foreground', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'remote_peek', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'remote_read', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'remote_send', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'remote_startserver', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'remove', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'rename', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'repeat', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'resolve', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'reverse', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'round', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'rubyeval', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'screenattr', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'screenchar', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'screenchars', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'screencol', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'screenpos', 'min_argc': 3, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'screenrow', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'screenstring', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'search', 'min_argc': 1, 'max_argc': 4, 'argtype': 'FEARG_1'},
+      \ {'name': 'searchdecl', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'searchpair', 'min_argc': 3, 'max_argc': 7, 'argtype': '0'},
+      \ {'name': 'searchpairpos', 'min_argc': 3, 'max_argc': 7, 'argtype': '0'},
+      \ {'name': 'searchpos', 'min_argc': 1, 'max_argc': 4, 'argtype': 'FEARG_1'},
+      \ {'name': 'server2client', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'serverlist', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'setbufline', 'min_argc': 3, 'max_argc': 3, 'argtype': 'FEARG_3'},
+      \ {'name': 'setbufvar', 'min_argc': 3, 'max_argc': 3, 'argtype': 'FEARG_3'},
+      \ {'name': 'setcharsearch', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'setcmdpos', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'setenv', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_2'},
+      \ {'name': 'setfperm', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'setline', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_2'},
+      \ {'name': 'setloclist', 'min_argc': 2, 'max_argc': 4, 'argtype': 'FEARG_2'},
+      \ {'name': 'setmatches', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'setpos', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_2'},
+      \ {'name': 'setqflist', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'setreg', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_2'},
+      \ {'name': 'settabvar', 'min_argc': 3, 'max_argc': 3, 'argtype': 'FEARG_3'},
+      \ {'name': 'settabwinvar', 'min_argc': 4, 'max_argc': 4, 'argtype': 'FEARG_4'},
+      \ {'name': 'settagstack', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_2'},
+      \ {'name': 'setwinvar', 'min_argc': 3, 'max_argc': 3, 'argtype': 'FEARG_3'},
+      \ {'name': 'sha256', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'shellescape', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'shiftwidth', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'sign_define', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'sign_getdefined', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'sign_getplaced', 'min_argc': 0, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'sign_jump', 'min_argc': 3, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'sign_place', 'min_argc': 4, 'max_argc': 5, 'argtype': 'FEARG_1'},
+      \ {'name': 'sign_placelist', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'sign_undefine', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'sign_unplace', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'sign_unplacelist', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'simplify', 'min_argc': 1, 'max_argc': 1, 'argtype': '0'},
+      \ {'name': 'sin', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'sinh', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'sort', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'sound_clear', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'sound_playevent', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'sound_playfile', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'sound_stop', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'soundfold', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'spellbadword', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'spellsuggest', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'split', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'sqrt', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'srand', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'state', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'str2float', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'str2list', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'str2nr', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'strcharpart', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'strchars', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'strdisplaywidth', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'strftime', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'strgetchar', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'stridx', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'string', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'strlen', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'strpart', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'strptime', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'strridx', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'strtrans', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'strwidth', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'submatch', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'substitute', 'min_argc': 4, 'max_argc': 4, 'argtype': 'FEARG_1'},
+      \ {'name': 'swapinfo', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'swapname', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'synID', 'min_argc': 3, 'max_argc': 3, 'argtype': '0'},
+      \ {'name': 'synIDattr', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'synIDtrans', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'synconcealed', 'min_argc': 2, 'max_argc': 2, 'argtype': '0'},
+      \ {'name': 'synstack', 'min_argc': 2, 'max_argc': 2, 'argtype': '0'},
+      \ {'name': 'system', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'systemlist', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'tabpagebuflist', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'tabpagenr', 'min_argc': 0, 'max_argc': 1, 'argtype': '0'},
+      \ {'name': 'tabpagewinnr', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'tagfiles', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'taglist', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'tan', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'tanh', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'tempname', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'term_dumpdiff', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'term_dumpload', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'term_dumpwrite', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_2'},
+      \ {'name': 'term_getaltscreen', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'term_getansicolors', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'term_getattr', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'term_getcursor', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'term_getjob', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'term_getline', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'term_getscrolled', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'term_getsize', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'term_getstatus', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'term_gettitle', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'term_gettty', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'term_list', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'term_scrape', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'term_sendkeys', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'term_setansicolors', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'term_setapi', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'term_setkill', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'term_setrestore', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'term_setsize', 'min_argc': 3, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'term_start', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'term_wait', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'test_alloc_fail', 'min_argc': 3, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'test_autochdir', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'test_feedinput', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'test_garbagecollect_now', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'test_garbagecollect_soon', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'test_getvalue', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'test_ignore_error', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'test_null_blob', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'test_null_channel', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'test_null_dict', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'test_null_job', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'test_null_list', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'test_null_partial', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'test_null_string', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'test_option_not_set', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'test_override', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_2'},
+      \ {'name': 'test_refcount', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'test_scrollbar', 'min_argc': 3, 'max_argc': 3, 'argtype': 'FEARG_2'},
+      \ {'name': 'test_setmouse', 'min_argc': 2, 'max_argc': 2, 'argtype': '0'},
+      \ {'name': 'test_settime', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'test_srand_seed', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'test_unknown', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'test_void', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'timer_info', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'timer_pause', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'timer_start', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'timer_stop', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'timer_stopall', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'tolower', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'toupper', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'tr', 'min_argc': 3, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'trim', 'min_argc': 1, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'trunc', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'type', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'undofile', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'undotree', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'uniq', 'min_argc': 1, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'values', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'virtcol', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'visualmode', 'min_argc': 0, 'max_argc': 1, 'argtype': '0'},
+      \ {'name': 'wildmenumode', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'win_execute', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_2'},
+      \ {'name': 'win_findbuf', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'win_getid', 'min_argc': 0, 'max_argc': 2, 'argtype': 'FEARG_1'},
+      \ {'name': 'win_gettype', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'win_gotoid', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'win_id2tabwin', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'win_id2win', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'win_screenpos', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'win_splitmove', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'winbufnr', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'wincol', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'windowsversion', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'winheight', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'winlayout', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'winline', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'winnr', 'min_argc': 0, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'winrestcmd', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'winrestview', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'winsaveview', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'winwidth', 'min_argc': 1, 'max_argc': 1, 'argtype': 'FEARG_1'},
+      \ {'name': 'wordcount', 'min_argc': 0, 'max_argc': 0, 'argtype': '0'},
+      \ {'name': 'writefile', 'min_argc': 2, 'max_argc': 3, 'argtype': 'FEARG_1'},
+      \ {'name': 'xor', 'min_argc': 2, 'max_argc': 2, 'argtype': 'FEARG_1'},
       \]
 
 let s:ExprTokenizer = {}
 
-function! s:ExprTokenizer.new(...)
+function! s:ExprTokenizer.new(...) abort
   let obj = copy(self)
   call call(obj.__init__, a:000, obj)
   return obj
 endfunction
 
-function! s:ExprTokenizer.__init__(reader)
+function! s:ExprTokenizer.__init__(reader) abort
   let self.reader = a:reader
   let self.cache = {}
 endfunction
 
-function! s:ExprTokenizer.token(type, value, pos)
+function! s:ExprTokenizer.token(type, value, pos) abort
   return {'type': a:type, 'value': a:value, 'pos': a:pos}
 endfunction
 
-function! s:ExprTokenizer.peek()
+function! s:ExprTokenizer.peek() abort
   let pos = self.reader.tell()
   let r = self.get()
   call self.reader.seek_set(pos)
   return r
 endfunction
 
-function! s:ExprTokenizer.get()
+function! s:ExprTokenizer.get() abort
   " FIXME: remove dirty hack
   if has_key(self.cache, self.reader.tell())
     let x = self.cache[self.reader.tell()]
@@ -2561,7 +3273,7 @@ function! s:ExprTokenizer.get()
   return r
 endfunction
 
-function! s:ExprTokenizer.get2()
+function! s:ExprTokenizer.get2() abort
   let r = self.reader
   let pos = r.getpos()
   let c = r.peek()
@@ -2577,6 +3289,14 @@ function! s:ExprTokenizer.get2()
     let s = r.getn(3)
     let s .= r.read_xdigit()
     return self.token(s:TOKEN_NUMBER, s, pos)
+  elseif c ==# '0' && (r.p(1) ==# 'B' || r.p(1) ==# 'b') && (r.p(2) ==# '0' || r.p(2) ==# '1')
+    let s = r.getn(3)
+    let s .= r.read_bdigit()
+    return self.token(s:TOKEN_NUMBER, s, pos)
+  elseif c ==# '0' && (r.p(1) ==# 'Z' || r.p(1) ==# 'z') && r.p(2) !=# '.'
+    let s = r.getn(2)
+    let s .= r.read_blob()
+    return self.token(s:TOKEN_BLOB, s, pos)
   elseif s:isdigit(c)
     let s = r.read_digit()
     if r.p(0) ==# '.' && s:isdigit(r.p(1))
@@ -2722,9 +3442,12 @@ function! s:ExprTokenizer.get2()
     if r.p(1) ==# '.' && r.p(2) ==# '.'
       call r.seek_cur(3)
       return self.token(s:TOKEN_DOTDOTDOT, '...', pos)
+    elseif r.p(1) ==# '.'
+      call r.seek_cur(2)
+      return self.token(s:TOKEN_DOTDOT, '..', pos) " TODO check scriptversion?
     else
       call r.seek_cur(1)
-      return self.token(s:TOKEN_DOT, '.', pos)
+      return self.token(s:TOKEN_DOT, '.', pos) " TODO check scriptversion?
     endif
   elseif c ==# '*'
     call r.seek_cur(1)
@@ -2745,8 +3468,13 @@ function! s:ExprTokenizer.get2()
     call r.seek_cur(1)
     return self.token(s:TOKEN_COLON, ':', pos)
   elseif c ==# '#'
-    call r.seek_cur(1)
-    return self.token(s:TOKEN_SHARP, '#', pos)
+    if r.p(1) ==# '{'
+      call r.seek_cur(2)
+      return self.token(s:TOKEN_LITCOPEN, '#{', pos)
+    else
+      call r.seek_cur(1)
+      return self.token(s:TOKEN_SHARP, '#', pos)
+    endif
   elseif c ==# '('
     call r.seek_cur(1)
     return self.token(s:TOKEN_POPEN, '(', pos)
@@ -2806,7 +3534,7 @@ function! s:ExprTokenizer.get2()
   endif
 endfunction
 
-function! s:ExprTokenizer.get_sstring()
+function! s:ExprTokenizer.get_sstring() abort
   call self.reader.skip_white()
   let c = self.reader.p(0)
   if c !=# "'"
@@ -2834,7 +3562,7 @@ function! s:ExprTokenizer.get_sstring()
   return s
 endfunction
 
-function! s:ExprTokenizer.get_dstring()
+function! s:ExprTokenizer.get_dstring() abort
   call self.reader.skip_white()
   let c = self.reader.p(0)
   if c !=# '"'
@@ -2866,35 +3594,60 @@ function! s:ExprTokenizer.get_dstring()
   return s
 endfunction
 
+function! s:ExprTokenizer.parse_dict_literal_key() abort
+  call self.reader.skip_white()
+  let c = self.reader.peek()
+  if !s:isalnum(c) && c !=# '_' && c !=# '-'
+    throw s:Err(printf('unexpected character: %s', c), self.reader.getpos())
+  endif
+  let node = s:Node(s:NODE_STRING)
+  let s = c
+  call self.reader.seek_cur(1)
+  let node.pos = self.reader.getpos()
+  while s:TRUE
+    let c = self.reader.p(0)
+    if c ==# '<EOF>' || c ==# '<EOL>'
+      throw s:Err('unexpectd EOL', self.reader.getpos())
+    endif
+    if !s:isalnum(c) && c !=# '_' && c !=# '-'
+      break
+    endif
+    call self.reader.seek_cur(1)
+    let s .= c
+  endwhile
+  let node.value = "'" . s . "'"
+  return node
+endfunction
+
 let s:ExprParser = {}
 
-function! s:ExprParser.new(...)
+function! s:ExprParser.new(...) abort
   let obj = copy(self)
   call call(obj.__init__, a:000, obj)
   return obj
 endfunction
 
-function! s:ExprParser.__init__(reader)
+function! s:ExprParser.__init__(reader) abort
   let self.reader = a:reader
   let self.tokenizer = s:ExprTokenizer.new(a:reader)
 endfunction
 
-function! s:ExprParser.parse()
+function! s:ExprParser.parse() abort
   return self.parse_expr1()
 endfunction
 
 " expr1: expr2 ? expr1 : expr1
-function! s:ExprParser.parse_expr1()
+function! s:ExprParser.parse_expr1() abort
   let left = self.parse_expr2()
   let pos = self.reader.tell()
   let token = self.tokenizer.get()
-  if token.type == s:TOKEN_QUESTION
+  if token.type ==# s:TOKEN_QUESTION
     let node = s:Node(s:NODE_TERNARY)
     let node.pos = token.pos
     let node.cond = left
     let node.left = self.parse_expr1()
     let token = self.tokenizer.get()
-    if token.type != s:TOKEN_COLON
+    if token.type !=# s:TOKEN_COLON
       throw s:Err(printf('unexpected token: %s', token.value), token.pos)
     endif
     let node.right = self.parse_expr1()
@@ -2906,12 +3659,12 @@ function! s:ExprParser.parse_expr1()
 endfunction
 
 " expr2: expr3 || expr3 ..
-function! s:ExprParser.parse_expr2()
+function! s:ExprParser.parse_expr2() abort
   let left = self.parse_expr3()
   while s:TRUE
     let pos = self.reader.tell()
     let token = self.tokenizer.get()
-    if token.type == s:TOKEN_OROR
+    if token.type ==# s:TOKEN_OROR
       let node = s:Node(s:NODE_OR)
       let node.pos = token.pos
       let node.left = left
@@ -2926,12 +3679,12 @@ function! s:ExprParser.parse_expr2()
 endfunction
 
 " expr3: expr4 && expr4
-function! s:ExprParser.parse_expr3()
+function! s:ExprParser.parse_expr3() abort
   let left = self.parse_expr4()
   while s:TRUE
     let pos = self.reader.tell()
     let token = self.tokenizer.get()
-    if token.type == s:TOKEN_ANDAND
+    if token.type ==# s:TOKEN_ANDAND
       let node = s:Node(s:NODE_AND)
       let node.pos = token.pos
       let node.left = left
@@ -2960,185 +3713,185 @@ endfunction
 "
 "        expr5 is expr5
 "        expr5 isnot expr5
-function! s:ExprParser.parse_expr4()
+function! s:ExprParser.parse_expr4() abort
   let left = self.parse_expr5()
   let pos = self.reader.tell()
   let token = self.tokenizer.get()
-  if token.type == s:TOKEN_EQEQ
+  if token.type ==# s:TOKEN_EQEQ
     let node = s:Node(s:NODE_EQUAL)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_EQEQCI
+  elseif token.type ==# s:TOKEN_EQEQCI
     let node = s:Node(s:NODE_EQUALCI)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_EQEQCS
+  elseif token.type ==# s:TOKEN_EQEQCS
     let node = s:Node(s:NODE_EQUALCS)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_NEQ
+  elseif token.type ==# s:TOKEN_NEQ
     let node = s:Node(s:NODE_NEQUAL)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_NEQCI
+  elseif token.type ==# s:TOKEN_NEQCI
     let node = s:Node(s:NODE_NEQUALCI)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_NEQCS
+  elseif token.type ==# s:TOKEN_NEQCS
     let node = s:Node(s:NODE_NEQUALCS)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_GT
+  elseif token.type ==# s:TOKEN_GT
     let node = s:Node(s:NODE_GREATER)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_GTCI
+  elseif token.type ==# s:TOKEN_GTCI
     let node = s:Node(s:NODE_GREATERCI)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_GTCS
+  elseif token.type ==# s:TOKEN_GTCS
     let node = s:Node(s:NODE_GREATERCS)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_GTEQ
+  elseif token.type ==# s:TOKEN_GTEQ
     let node = s:Node(s:NODE_GEQUAL)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_GTEQCI
+  elseif token.type ==# s:TOKEN_GTEQCI
     let node = s:Node(s:NODE_GEQUALCI)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_GTEQCS
+  elseif token.type ==# s:TOKEN_GTEQCS
     let node = s:Node(s:NODE_GEQUALCS)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_LT
+  elseif token.type ==# s:TOKEN_LT
     let node = s:Node(s:NODE_SMALLER)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_LTCI
+  elseif token.type ==# s:TOKEN_LTCI
     let node = s:Node(s:NODE_SMALLERCI)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_LTCS
+  elseif token.type ==# s:TOKEN_LTCS
     let node = s:Node(s:NODE_SMALLERCS)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_LTEQ
+  elseif token.type ==# s:TOKEN_LTEQ
     let node = s:Node(s:NODE_SEQUAL)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_LTEQCI
+  elseif token.type ==# s:TOKEN_LTEQCI
     let node = s:Node(s:NODE_SEQUALCI)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_LTEQCS
+  elseif token.type ==# s:TOKEN_LTEQCS
     let node = s:Node(s:NODE_SEQUALCS)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_MATCH
+  elseif token.type ==# s:TOKEN_MATCH
     let node = s:Node(s:NODE_MATCH)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_MATCHCI
+  elseif token.type ==# s:TOKEN_MATCHCI
     let node = s:Node(s:NODE_MATCHCI)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_MATCHCS
+  elseif token.type ==# s:TOKEN_MATCHCS
     let node = s:Node(s:NODE_MATCHCS)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_NOMATCH
+  elseif token.type ==# s:TOKEN_NOMATCH
     let node = s:Node(s:NODE_NOMATCH)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_NOMATCHCI
+  elseif token.type ==# s:TOKEN_NOMATCHCI
     let node = s:Node(s:NODE_NOMATCHCI)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_NOMATCHCS
+  elseif token.type ==# s:TOKEN_NOMATCHCS
     let node = s:Node(s:NODE_NOMATCHCS)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_IS
+  elseif token.type ==# s:TOKEN_IS
     let node = s:Node(s:NODE_IS)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_ISCI
+  elseif token.type ==# s:TOKEN_ISCI
     let node = s:Node(s:NODE_ISCI)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_ISCS
+  elseif token.type ==# s:TOKEN_ISCS
     let node = s:Node(s:NODE_ISCS)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_ISNOT
+  elseif token.type ==# s:TOKEN_ISNOT
     let node = s:Node(s:NODE_ISNOT)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_ISNOTCI
+  elseif token.type ==# s:TOKEN_ISNOTCI
     let node = s:Node(s:NODE_ISNOTCI)
     let node.pos = token.pos
     let node.left = left
     let node.right = self.parse_expr5()
     let left = node
-  elseif token.type == s:TOKEN_ISNOTCS
+  elseif token.type ==# s:TOKEN_ISNOTCS
     let node = s:Node(s:NODE_ISNOTCS)
     let node.pos = token.pos
     let node.left = left
@@ -3153,24 +3906,31 @@ endfunction
 " expr5: expr6 + expr6 ..
 "        expr6 - expr6 ..
 "        expr6 . expr6 ..
-function! s:ExprParser.parse_expr5()
+"        expr6 .. expr6 ..
+function! s:ExprParser.parse_expr5() abort
   let left = self.parse_expr6()
   while s:TRUE
     let pos = self.reader.tell()
     let token = self.tokenizer.get()
-    if token.type == s:TOKEN_PLUS
+    if token.type ==# s:TOKEN_PLUS
       let node = s:Node(s:NODE_ADD)
       let node.pos = token.pos
       let node.left = left
       let node.right = self.parse_expr6()
       let left = node
-    elseif token.type == s:TOKEN_MINUS
+    elseif token.type ==# s:TOKEN_MINUS
       let node = s:Node(s:NODE_SUBTRACT)
       let node.pos = token.pos
       let node.left = left
       let node.right = self.parse_expr6()
       let left = node
-    elseif token.type == s:TOKEN_DOT
+    elseif token.type ==# s:TOKEN_DOTDOT " TODO check scriptversion?
+      let node = s:Node(s:NODE_CONCAT)
+      let node.pos = token.pos
+      let node.left = left
+      let node.right = self.parse_expr6()
+      let left = node
+    elseif token.type ==# s:TOKEN_DOT " TODO check scriptversion?
       let node = s:Node(s:NODE_CONCAT)
       let node.pos = token.pos
       let node.left = left
@@ -3187,24 +3947,24 @@ endfunction
 " expr6: expr7 * expr7 ..
 "        expr7 / expr7 ..
 "        expr7 % expr7 ..
-function! s:ExprParser.parse_expr6()
+function! s:ExprParser.parse_expr6() abort
   let left = self.parse_expr7()
   while s:TRUE
     let pos = self.reader.tell()
     let token = self.tokenizer.get()
-    if token.type == s:TOKEN_STAR
+    if token.type ==# s:TOKEN_STAR
       let node = s:Node(s:NODE_MULTIPLY)
       let node.pos = token.pos
       let node.left = left
       let node.right = self.parse_expr7()
       let left = node
-    elseif token.type == s:TOKEN_SLASH
+    elseif token.type ==# s:TOKEN_SLASH
       let node = s:Node(s:NODE_DIVIDE)
       let node.pos = token.pos
       let node.left = left
       let node.right = self.parse_expr7()
       let left = node
-    elseif token.type == s:TOKEN_PERCENT
+    elseif token.type ==# s:TOKEN_PERCENT
       let node = s:Node(s:NODE_REMAINDER)
       let node.pos = token.pos
       let node.left = left
@@ -3221,20 +3981,20 @@ endfunction
 " expr7: ! expr7
 "        - expr7
 "        + expr7
-function! s:ExprParser.parse_expr7()
+function! s:ExprParser.parse_expr7() abort
   let pos = self.reader.tell()
   let token = self.tokenizer.get()
-  if token.type == s:TOKEN_NOT
+  if token.type ==# s:TOKEN_NOT
     let node = s:Node(s:NODE_NOT)
     let node.pos = token.pos
     let node.left = self.parse_expr7()
     return node
-  elseif token.type == s:TOKEN_MINUS
+  elseif token.type ==# s:TOKEN_MINUS
     let node = s:Node(s:NODE_MINUS)
     let node.pos = token.pos
     let node.left = self.parse_expr7()
     return node
-  elseif token.type == s:TOKEN_PLUS
+  elseif token.type ==# s:TOKEN_PLUS
     let node = s:Node(s:NODE_PLUS)
     let node.pos = token.pos
     let node.left = self.parse_expr7()
@@ -3249,44 +4009,47 @@ endfunction
 " expr8: expr8[expr1]
 "        expr8[expr1 : expr1]
 "        expr8.name
+"        expr8->name(expr1, ...)
+"        expr8->s:user_func(expr1, ...)
+"        expr8->{lambda}(expr1, ...)
 "        expr8(expr1, ...)
-function! s:ExprParser.parse_expr8()
+function! s:ExprParser.parse_expr8() abort
   let left = self.parse_expr9()
   while s:TRUE
     let pos = self.reader.tell()
     let c = self.reader.peek()
     let token = self.tokenizer.get()
-    if !s:iswhite(c) && token.type == s:TOKEN_SQOPEN
+    if !s:iswhite(c) && token.type ==# s:TOKEN_SQOPEN
       let npos = token.pos
-      if self.tokenizer.peek().type == s:TOKEN_COLON
+      if self.tokenizer.peek().type ==# s:TOKEN_COLON
         call self.tokenizer.get()
         let node = s:Node(s:NODE_SLICE)
         let node.pos = npos
         let node.left = left
         let node.rlist = [s:NIL, s:NIL]
         let token = self.tokenizer.peek()
-        if token.type != s:TOKEN_SQCLOSE
+        if token.type !=# s:TOKEN_SQCLOSE
           let node.rlist[1] = self.parse_expr1()
         endif
         let token = self.tokenizer.get()
-        if token.type != s:TOKEN_SQCLOSE
+        if token.type !=# s:TOKEN_SQCLOSE
           throw s:Err(printf('unexpected token: %s', token.value), token.pos)
         endif
         let left = node
       else
         let right = self.parse_expr1()
-        if self.tokenizer.peek().type == s:TOKEN_COLON
+        if self.tokenizer.peek().type ==# s:TOKEN_COLON
           call self.tokenizer.get()
           let node = s:Node(s:NODE_SLICE)
           let node.pos = npos
           let node.left = left
           let node.rlist = [right, s:NIL]
           let token = self.tokenizer.peek()
-          if token.type != s:TOKEN_SQCLOSE
+          if token.type !=# s:TOKEN_SQCLOSE
             let node.rlist[1] = self.parse_expr1()
           endif
           let token = self.tokenizer.get()
-          if token.type != s:TOKEN_SQCLOSE
+          if token.type !=# s:TOKEN_SQCLOSE
             throw s:Err(printf('unexpected token: %s', token.value), token.pos)
           endif
           let left = node
@@ -3296,46 +4059,39 @@ function! s:ExprParser.parse_expr8()
           let node.left = left
           let node.right = right
           let token = self.tokenizer.get()
-          if token.type != s:TOKEN_SQCLOSE
+          if token.type !=# s:TOKEN_SQCLOSE
             throw s:Err(printf('unexpected token: %s', token.value), token.pos)
           endif
           let left = node
         endif
       endif
       unlet node
-    elseif token.type == s:TOKEN_POPEN
+    elseif token.type ==# s:TOKEN_ARROW
+      let funcname_or_lambda = self.parse_expr9()
+      let token = self.tokenizer.get()
+      if token.type !=# s:TOKEN_POPEN
+        throw s:Err('E107: Missing parentheses: lambda', token.pos)
+      endif
+      let right = s:Node(s:NODE_CALL)
+      let right.pos = token.pos
+      let right.left = funcname_or_lambda
+      let right.rlist = self.parse_rlist()
+      let node = s:Node(s:NODE_METHOD)
+      let node.pos = token.pos
+      let node.left = left
+      let node.right = right
+      let left = node
+      unlet node
+    elseif token.type ==# s:TOKEN_POPEN
       let node = s:Node(s:NODE_CALL)
       let node.pos = token.pos
       let node.left = left
-      let node.rlist = []
-      if self.tokenizer.peek().type == s:TOKEN_PCLOSE
-        call self.tokenizer.get()
-      else
-        while s:TRUE
-          call add(node.rlist, self.parse_expr1())
-          let token = self.tokenizer.get()
-          if token.type == s:TOKEN_COMMA
-            " XXX: Vim allows foo(a, b, ).  Lint should warn it.
-            if self.tokenizer.peek().type == s:TOKEN_PCLOSE
-              call self.tokenizer.get()
-              break
-            endif
-          elseif token.type == s:TOKEN_PCLOSE
-            break
-          else
-            throw s:Err(printf('unexpected token: %s', token.value), token.pos)
-          endif
-        endwhile
-      endif
-      if len(node.rlist) > s:MAX_FUNC_ARGS
-        " TODO: funcname E740: Too many arguments for function: %s
-        throw s:Err('E740: Too many arguments for function', node.pos)
-      endif
+      let node.rlist = self.parse_rlist()
       let left = node
       unlet node
-    elseif !s:iswhite(c) && token.type == s:TOKEN_DOT
+    elseif !s:iswhite(c) && token.type ==# s:TOKEN_DOT " TODO check scriptversion?
       let node = self.parse_dot(token, left)
-      if node is s:NIL
+      if node is# s:NIL
         call self.reader.seek_set(pos)
         break
       endif
@@ -3349,11 +4105,41 @@ function! s:ExprParser.parse_expr8()
   return left
 endfunction
 
+function! s:ExprParser.parse_rlist() abort
+  let rlist = []
+  let token = self.tokenizer.peek()
+  if self.tokenizer.peek().type ==# s:TOKEN_PCLOSE
+    call self.tokenizer.get()
+  else
+    while s:TRUE
+      call add(rlist, self.parse_expr1())
+      let token = self.tokenizer.get()
+      if token.type ==# s:TOKEN_COMMA
+        " XXX: Vim allows foo(a, b, ).  Lint should warn it.
+        if self.tokenizer.peek().type ==# s:TOKEN_PCLOSE
+          call self.tokenizer.get()
+          break
+        endif
+      elseif token.type ==# s:TOKEN_PCLOSE
+        break
+      else
+        throw s:Err(printf('unexpected token: %s', token.value), token.pos)
+      endif
+    endwhile
+  endif
+  if len(rlist) > s:MAX_FUNC_ARGS
+    " TODO: funcname E740: Too many arguments for function: %s
+    throw s:Err('E740: Too many arguments for function', token.pos)
+  endif
+  return rlist
+endfunction
+
 " expr9: number
 "        "string"
 "        'string'
 "        [expr1, ...]
 "        {expr1: expr1, ...}
+"        #{literal_key1: expr1, ...}
 "        {args -> expr1}
 "        &option
 "        (expr1)
@@ -3363,42 +4149,46 @@ endfunction
 "        @r
 "        function(expr1, ...)
 "        func{ti}on(expr1, ...)
-function! s:ExprParser.parse_expr9()
+function! s:ExprParser.parse_expr9() abort
   let pos = self.reader.tell()
   let token = self.tokenizer.get()
   let node = s:Node(-1)
-  if token.type == s:TOKEN_NUMBER
+  if token.type ==# s:TOKEN_NUMBER
     let node = s:Node(s:NODE_NUMBER)
     let node.pos = token.pos
     let node.value = token.value
-  elseif token.type == s:TOKEN_DQUOTE
+  elseif token.type ==# s:TOKEN_BLOB
+    let node = s:Node(s:NODE_BLOB)
+    let node.pos = token.pos
+    let node.value = token.value
+  elseif token.type ==# s:TOKEN_DQUOTE
     call self.reader.seek_set(pos)
     let node = s:Node(s:NODE_STRING)
     let node.pos = token.pos
     let node.value = '"' . self.tokenizer.get_dstring() . '"'
-  elseif token.type == s:TOKEN_SQUOTE
+  elseif token.type ==# s:TOKEN_SQUOTE
     call self.reader.seek_set(pos)
     let node = s:Node(s:NODE_STRING)
     let node.pos = token.pos
     let node.value = "'" . self.tokenizer.get_sstring() . "'"
-  elseif token.type == s:TOKEN_SQOPEN
+  elseif token.type ==# s:TOKEN_SQOPEN
     let node = s:Node(s:NODE_LIST)
     let node.pos = token.pos
     let node.value = []
     let token = self.tokenizer.peek()
-    if token.type == s:TOKEN_SQCLOSE
+    if token.type ==# s:TOKEN_SQCLOSE
       call self.tokenizer.get()
     else
       while s:TRUE
         call add(node.value, self.parse_expr1())
         let token = self.tokenizer.peek()
-        if token.type == s:TOKEN_COMMA
+        if token.type ==# s:TOKEN_COMMA
           call self.tokenizer.get()
-          if self.tokenizer.peek().type == s:TOKEN_SQCLOSE
+          if self.tokenizer.peek().type ==# s:TOKEN_SQCLOSE
             call self.tokenizer.get()
             break
           endif
-        elseif token.type == s:TOKEN_SQCLOSE
+        elseif token.type ==# s:TOKEN_SQCLOSE
           call self.tokenizer.get()
           break
         else
@@ -3406,16 +4196,17 @@ function! s:ExprParser.parse_expr9()
         endif
       endwhile
     endif
-  elseif token.type == s:TOKEN_COPEN
+  elseif token.type ==# s:TOKEN_COPEN || token.type ==# s:TOKEN_LITCOPEN
+    let is_litdict = token.type ==# s:TOKEN_LITCOPEN
     let savepos = self.reader.tell()
     let nodepos = token.pos
     let token = self.tokenizer.get()
-    let lambda = token.type == s:TOKEN_ARROW
-    if !lambda && !(token.type == s:TOKEN_SQUOTE || token.type == s:TOKEN_DQUOTE)
+    let lambda = token.type ==# s:TOKEN_ARROW
+    if !lambda && !(token.type ==# s:TOKEN_SQUOTE || token.type ==# s:TOKEN_DQUOTE)
       " if the token type is stirng, we cannot peek next token and we can
       " assume it's not lambda.
       let token2 = self.tokenizer.peek()
-      let lambda = token2.type == s:TOKEN_ARROW || token2.type == s:TOKEN_COMMA
+      let lambda = token2.type ==# s:TOKEN_ARROW || token2.type ==# s:TOKEN_COMMA
     endif
     " fallback to dict or {expr} if true
     let fallback = s:FALSE
@@ -3426,9 +4217,9 @@ function! s:ExprParser.parse_expr9()
       let node.rlist = []
       let named = {}
       while s:TRUE
-        if token.type == s:TOKEN_ARROW
+        if token.type ==# s:TOKEN_ARROW
           break
-        elseif token.type == s:TOKEN_IDENTIFIER
+        elseif token.type ==# s:TOKEN_IDENTIFIER
           if !s:isargname(token.value)
             throw s:Err(printf('E125: Illegal argument: %s', token.value), token.pos)
           elseif has_key(named, token.value)
@@ -3439,30 +4230,30 @@ function! s:ExprParser.parse_expr9()
           let varnode.pos = token.pos
           let varnode.value = token.value
           " XXX: Vim doesn't skip white space before comma.  {a ,b -> ...} => E475
-          if s:iswhite(self.reader.p(0)) && self.tokenizer.peek().type == s:TOKEN_COMMA
+          if s:iswhite(self.reader.p(0)) && self.tokenizer.peek().type ==# s:TOKEN_COMMA
             throw s:Err('E475: Invalid argument: White space is not allowed before comma', self.reader.getpos())
           endif
           let token = self.tokenizer.get()
           call add(node.rlist, varnode)
-          if token.type == s:TOKEN_COMMA
+          if token.type ==# s:TOKEN_COMMA
             " XXX: Vim allows last comma.  {a, b, -> ...} => OK
             let token = self.tokenizer.peek()
-            if token.type == s:TOKEN_ARROW
+            if token.type ==# s:TOKEN_ARROW
               call self.tokenizer.get()
               break
             endif
-          elseif token.type == s:TOKEN_ARROW
+          elseif token.type ==# s:TOKEN_ARROW
             break
           else
             throw s:Err(printf('unexpected token: %s, type: %d', token.value, token.type), token.pos)
           endif
-        elseif token.type == s:TOKEN_DOTDOTDOT
+        elseif token.type ==# s:TOKEN_DOTDOTDOT
           let varnode = s:Node(s:NODE_IDENTIFIER)
           let varnode.pos = token.pos
           let varnode.value = token.value
           call add(node.rlist, varnode)
           let token = self.tokenizer.peek()
-          if token.type == s:TOKEN_ARROW
+          if token.type ==# s:TOKEN_ARROW
             call self.tokenizer.get()
             break
           else
@@ -3477,7 +4268,7 @@ function! s:ExprParser.parse_expr9()
       if !fallback
         let node.left = self.parse_expr1()
         let token = self.tokenizer.get()
-        if token.type != s:TOKEN_CCLOSE
+        if token.type !=# s:TOKEN_CCLOSE
           throw s:Err(printf('unexpected token: %s', token.value), token.pos)
         endif
         return node
@@ -3489,14 +4280,14 @@ function! s:ExprParser.parse_expr9()
     let node.value = []
     call self.reader.seek_set(savepos)
     let token = self.tokenizer.peek()
-    if token.type == s:TOKEN_CCLOSE
+    if token.type ==# s:TOKEN_CCLOSE
       call self.tokenizer.get()
       return node
     endif
     while 1
-      let key = self.parse_expr1()
+      let key = is_litdict ? self.tokenizer.parse_dict_literal_key() : self.parse_expr1()
       let token = self.tokenizer.get()
-      if token.type == s:TOKEN_CCLOSE
+      if token.type ==# s:TOKEN_CCLOSE
         if !empty(node.value)
           throw s:Err(printf('unexpected token: %s', token.value), token.pos)
         endif
@@ -3504,54 +4295,54 @@ function! s:ExprParser.parse_expr9()
         let node = self.parse_identifier()
         break
       endif
-      if token.type != s:TOKEN_COLON
+      if token.type !=# s:TOKEN_COLON
         throw s:Err(printf('unexpected token: %s', token.value), token.pos)
       endif
       let val = self.parse_expr1()
       call add(node.value, [key, val])
       let token = self.tokenizer.get()
-      if token.type == s:TOKEN_COMMA
-        if self.tokenizer.peek().type == s:TOKEN_CCLOSE
+      if token.type ==# s:TOKEN_COMMA
+        if self.tokenizer.peek().type ==# s:TOKEN_CCLOSE
           call self.tokenizer.get()
           break
         endif
-      elseif token.type == s:TOKEN_CCLOSE
+      elseif token.type ==# s:TOKEN_CCLOSE
         break
       else
         throw s:Err(printf('unexpected token: %s', token.value), token.pos)
       endif
     endwhile
     return node
-  elseif token.type == s:TOKEN_POPEN
+  elseif token.type ==# s:TOKEN_POPEN
     let node = s:Node(s:NODE_PARENEXPR)
     let node.pos = token.pos
     let node.value = self.parse_expr1()
     let token = self.tokenizer.get()
-    if token.type != s:TOKEN_PCLOSE
+    if token.type !=# s:TOKEN_PCLOSE
       throw s:Err(printf('unexpected token: %s', token.value), token.pos)
     endif
-  elseif token.type == s:TOKEN_OPTION
+  elseif token.type ==# s:TOKEN_OPTION
     let node = s:Node(s:NODE_OPTION)
     let node.pos = token.pos
     let node.value = token.value
-  elseif token.type == s:TOKEN_IDENTIFIER
+  elseif token.type ==# s:TOKEN_IDENTIFIER
     call self.reader.seek_set(pos)
     let node = self.parse_identifier()
-  elseif s:FALSE && (token.type == s:TOKEN_COLON || token.type == s:TOKEN_SHARP)
+  elseif s:FALSE && (token.type ==# s:TOKEN_COLON || token.type ==# s:TOKEN_SHARP)
     " XXX: no parse error but invalid expression
     call self.reader.seek_set(pos)
     let node = self.parse_identifier()
-  elseif token.type == s:TOKEN_LT && self.reader.peekn(4) ==? 'SID>'
+  elseif token.type ==# s:TOKEN_LT && self.reader.peekn(4) ==? 'SID>'
     call self.reader.seek_set(pos)
     let node = self.parse_identifier()
-  elseif token.type == s:TOKEN_IS || token.type == s:TOKEN_ISCS || token.type == s:TOKEN_ISNOT || token.type == s:TOKEN_ISNOTCS
+  elseif token.type ==# s:TOKEN_IS || token.type ==# s:TOKEN_ISCS || token.type ==# s:TOKEN_ISNOT || token.type ==# s:TOKEN_ISNOTCS
     call self.reader.seek_set(pos)
     let node = self.parse_identifier()
-  elseif token.type == s:TOKEN_ENV
+  elseif token.type ==# s:TOKEN_ENV
     let node = s:Node(s:NODE_ENV)
     let node.pos = token.pos
     let node.value = token.value
-  elseif token.type == s:TOKEN_REG
+  elseif token.type ==# s:TOKEN_REG
     let node = s:Node(s:NODE_REG)
     let node.pos = token.pos
     let node.value = token.value
@@ -3564,8 +4355,8 @@ endfunction
 " SUBSCRIPT or CONCAT
 "   dict "." [0-9A-Za-z_]+ => (subscript dict key)
 "   str  "." expr6         => (concat str expr6)
-function! s:ExprParser.parse_dot(token, left)
-  if a:left.type != s:NODE_IDENTIFIER && a:left.type != s:NODE_CURLYNAME && a:left.type != s:NODE_DICT && a:left.type != s:NODE_SUBSCRIPT && a:left.type != s:NODE_CALL && a:left.type != s:NODE_DOT
+function! s:ExprParser.parse_dot(token, left) abort
+  if a:left.type !=# s:NODE_IDENTIFIER && a:left.type !=# s:NODE_CURLYNAME && a:left.type !=# s:NODE_DICT && a:left.type !=# s:NODE_SUBSCRIPT && a:left.type !=# s:NODE_CALL && a:left.type !=# s:NODE_DOT
     return s:NIL
   endif
   if !s:iswordc(self.reader.p(0))
@@ -3587,11 +4378,36 @@ function! s:ExprParser.parse_dot(token, left)
   return node
 endfunction
 
-function! s:ExprParser.parse_identifier()
+" CONCAT
+"   str  ".." expr6         => (concat str expr6)
+function! s:ExprParser.parse_concat(token, left) abort
+  if a:left.type !=# s:NODE_IDENTIFIER && a:left.type !=# s:NODE_CURLYNAME && a:left.type !=# s:NODE_DICT && a:left.type !=# s:NODE_SUBSCRIPT && a:left.type !=# s:NODE_CALL && a:left.type !=# s:NODE_DOT
+    return s:NIL
+  endif
+  if !s:iswordc(self.reader.p(0))
+    return s:NIL
+  endif
+  let pos = self.reader.getpos()
+  let name = self.reader.read_word()
+  if s:isnamec(self.reader.p(0))
+    " XXX: foo is str => ok, foo is obj => invalid expression
+    " foo.s:bar or foo.bar#baz
+    return s:NIL
+  endif
+  let node = s:Node(s:NODE_CONCAT)
+  let node.pos = a:token.pos
+  let node.left = a:left
+  let node.right = s:Node(s:NODE_IDENTIFIER)
+  let node.right.pos = pos
+  let node.right.value = name
+  return node
+endfunction
+
+function! s:ExprParser.parse_identifier() abort
   call self.reader.skip_white()
   let npos = self.reader.getpos()
   let curly_parts = self.parse_curly_parts()
-  if len(curly_parts) == 1 && curly_parts[0].type == s:NODE_CURLYNAMEPART
+  if len(curly_parts) ==# 1 && curly_parts[0].type ==# s:NODE_CURLYNAMEPART
     let node = s:Node(s:NODE_IDENTIFIER)
     let node.pos = npos
     let node.value = curly_parts[0].value
@@ -3604,7 +4420,7 @@ function! s:ExprParser.parse_identifier()
   endif
 endfunction
 
-function! s:ExprParser.parse_curly_parts()
+function! s:ExprParser.parse_curly_parts() abort
   let curly_parts = []
   let c = self.reader.peek()
   let pos = self.reader.getpos()
@@ -3649,50 +4465,50 @@ endfunction
 
 let s:LvalueParser = copy(s:ExprParser)
 
-function! s:LvalueParser.parse()
+function! s:LvalueParser.parse() abort
   return self.parse_lv8()
 endfunction
 
 " expr8: expr8[expr1]
 "        expr8[expr1 : expr1]
 "        expr8.name
-function! s:LvalueParser.parse_lv8()
+function! s:LvalueParser.parse_lv8() abort
   let left = self.parse_lv9()
   while s:TRUE
     let pos = self.reader.tell()
     let c = self.reader.peek()
     let token = self.tokenizer.get()
-    if !s:iswhite(c) && token.type == s:TOKEN_SQOPEN
+    if !s:iswhite(c) && token.type ==# s:TOKEN_SQOPEN
       let npos = token.pos
       let node = s:Node(-1)
-      if self.tokenizer.peek().type == s:TOKEN_COLON
+      if self.tokenizer.peek().type ==# s:TOKEN_COLON
         call self.tokenizer.get()
         let node = s:Node(s:NODE_SLICE)
         let node.pos = npos
         let node.left = left
         let node.rlist = [s:NIL, s:NIL]
         let token = self.tokenizer.peek()
-        if token.type != s:TOKEN_SQCLOSE
+        if token.type !=# s:TOKEN_SQCLOSE
           let node.rlist[1] = self.parse_expr1()
         endif
         let token = self.tokenizer.get()
-        if token.type != s:TOKEN_SQCLOSE
+        if token.type !=# s:TOKEN_SQCLOSE
           throw s:Err(printf('unexpected token: %s', token.value), token.pos)
         endif
       else
         let right = self.parse_expr1()
-        if self.tokenizer.peek().type == s:TOKEN_COLON
+        if self.tokenizer.peek().type ==# s:TOKEN_COLON
           call self.tokenizer.get()
           let node = s:Node(s:NODE_SLICE)
           let node.pos = npos
           let node.left = left
           let node.rlist = [right, s:NIL]
           let token = self.tokenizer.peek()
-          if token.type != s:TOKEN_SQCLOSE
+          if token.type !=# s:TOKEN_SQCLOSE
             let node.rlist[1] = self.parse_expr1()
           endif
           let token = self.tokenizer.get()
-          if token.type != s:TOKEN_SQCLOSE
+          if token.type !=# s:TOKEN_SQCLOSE
             throw s:Err(printf('unexpected token: %s', token.value), token.pos)
           endif
         else
@@ -3701,16 +4517,16 @@ function! s:LvalueParser.parse_lv8()
           let node.left = left
           let node.right = right
           let token = self.tokenizer.get()
-          if token.type != s:TOKEN_SQCLOSE
+          if token.type !=# s:TOKEN_SQCLOSE
             throw s:Err(printf('unexpected token: %s', token.value), token.pos)
           endif
         endif
       endif
       let left = node
       unlet node
-    elseif !s:iswhite(c) && token.type == s:TOKEN_DOT
+    elseif !s:iswhite(c) && token.type ==# s:TOKEN_DOT
       let node = self.parse_dot(token, left)
-      if node is s:NIL
+      if node is# s:NIL
         call self.reader.seek_set(pos)
         break
       endif
@@ -3729,28 +4545,28 @@ endfunction
 "        var{ria}ble
 "        $VAR
 "        @r
-function! s:LvalueParser.parse_lv9()
+function! s:LvalueParser.parse_lv9() abort
   let pos = self.reader.tell()
   let token = self.tokenizer.get()
   let node = s:Node(-1)
-  if token.type == s:TOKEN_COPEN
+  if token.type ==# s:TOKEN_COPEN
     call self.reader.seek_set(pos)
     let node = self.parse_identifier()
-  elseif token.type == s:TOKEN_OPTION
+  elseif token.type ==# s:TOKEN_OPTION
     let node = s:Node(s:NODE_OPTION)
     let node.pos = token.pos
     let node.value = token.value
-  elseif token.type == s:TOKEN_IDENTIFIER
+  elseif token.type ==# s:TOKEN_IDENTIFIER
     call self.reader.seek_set(pos)
     let node = self.parse_identifier()
-  elseif token.type == s:TOKEN_LT && self.reader.peekn(4) ==? 'SID>'
+  elseif token.type ==# s:TOKEN_LT && self.reader.peekn(4) ==? 'SID>'
     call self.reader.seek_set(pos)
     let node = self.parse_identifier()
-  elseif token.type == s:TOKEN_ENV
+  elseif token.type ==# s:TOKEN_ENV
     let node = s:Node(s:NODE_ENV)
     let node.pos = token.pos
     let node.value = token.value
-  elseif token.type == s:TOKEN_REG
+  elseif token.type ==# s:TOKEN_REG
     let node = s:Node(s:NODE_REG)
     let node.pos = token.pos
     let node.pos = token.pos
@@ -3763,13 +4579,13 @@ endfunction
 
 let s:StringReader = {}
 
-function! s:StringReader.new(...)
+function! s:StringReader.new(...) abort
   let obj = copy(self)
   call call(obj.__init__, a:000, obj)
   return obj
 endfunction
 
-function! s:StringReader.__init__(lines)
+function! s:StringReader.__init__(lines) abort
   let self.buf = []
   let self.pos = []
   let lnum = 0
@@ -3787,7 +4603,7 @@ function! s:StringReader.__init__(lines)
       let col = 0
       for c in split(a:lines[lnum + 1], '\zs')
         if skip
-          if c == '\'
+          if c ==# '\'
             let skip = s:FALSE
           endif
         else
@@ -3810,41 +4626,41 @@ function! s:StringReader.__init__(lines)
   let self.i = 0
 endfunction
 
-function! s:StringReader.eof()
+function! s:StringReader.eof() abort
   return self.i >= len(self.buf)
 endfunction
 
-function! s:StringReader.tell()
+function! s:StringReader.tell() abort
   return self.i
 endfunction
 
-function! s:StringReader.seek_set(i)
+function! s:StringReader.seek_set(i) abort
   let self.i = a:i
 endfunction
 
-function! s:StringReader.seek_cur(i)
+function! s:StringReader.seek_cur(i) abort
   let self.i = self.i + a:i
 endfunction
 
-function! s:StringReader.seek_end(i)
+function! s:StringReader.seek_end(i) abort
   let self.i = len(self.buf) + a:i
 endfunction
 
-function! s:StringReader.p(i)
+function! s:StringReader.p(i) abort
   if self.i >= len(self.buf)
     return '<EOF>'
   endif
   return self.buf[self.i + a:i]
 endfunction
 
-function! s:StringReader.peek()
+function! s:StringReader.peek() abort
   if self.i >= len(self.buf)
     return '<EOF>'
   endif
   return self.buf[self.i]
 endfunction
 
-function! s:StringReader.get()
+function! s:StringReader.get() abort
   if self.i >= len(self.buf)
     return '<EOF>'
   endif
@@ -3852,14 +4668,14 @@ function! s:StringReader.get()
   return self.buf[self.i - 1]
 endfunction
 
-function! s:StringReader.peekn(n)
+function! s:StringReader.peekn(n) abort
   let pos = self.tell()
   let r = self.getn(a:n)
   call self.seek_set(pos)
   return r
 endfunction
 
-function! s:StringReader.getn(n)
+function! s:StringReader.getn(n) abort
   let r = ''
   let j = 0
   while self.i < len(self.buf) && (a:n < 0 || j < a:n)
@@ -3874,17 +4690,17 @@ function! s:StringReader.getn(n)
   return r
 endfunction
 
-function! s:StringReader.peekline()
+function! s:StringReader.peekline() abort
   return self.peekn(-1)
 endfunction
 
-function! s:StringReader.readline()
+function! s:StringReader.readline() abort
   let r = self.getn(-1)
   call self.get()
   return r
 endfunction
 
-function! s:StringReader.getstr(begin, end)
+function! s:StringReader.getstr(begin, end) abort
   let r = ''
   for i in range(a:begin.i, a:end.i - 1)
     if i >= len(self.buf)
@@ -3899,16 +4715,16 @@ function! s:StringReader.getstr(begin, end)
   return r
 endfunction
 
-function! s:StringReader.getpos()
+function! s:StringReader.getpos() abort
   let [lnum, col, offset] = self.pos[self.i]
   return {'i': self.i, 'lnum': lnum, 'col': col, 'offset': offset}
 endfunction
 
-function! s:StringReader.setpos(pos)
+function! s:StringReader.setpos(pos) abort
   let self.i  = a:pos.i
 endfunction
 
-function! s:StringReader.read_alpha()
+function! s:StringReader.read_alpha() abort
   let r = ''
   while s:isalpha(self.peekn(1))
     let r .= self.getn(1)
@@ -3916,7 +4732,7 @@ function! s:StringReader.read_alpha()
   return r
 endfunction
 
-function! s:StringReader.read_alnum()
+function! s:StringReader.read_alnum() abort
   let r = ''
   while s:isalnum(self.peekn(1))
     let r .= self.getn(1)
@@ -3924,7 +4740,7 @@ function! s:StringReader.read_alnum()
   return r
 endfunction
 
-function! s:StringReader.read_digit()
+function! s:StringReader.read_digit() abort
   let r = ''
   while s:isdigit(self.peekn(1))
     let r .= self.getn(1)
@@ -3932,7 +4748,7 @@ function! s:StringReader.read_digit()
   return r
 endfunction
 
-function! s:StringReader.read_odigit()
+function! s:StringReader.read_odigit() abort
   let r = ''
   while s:isodigit(self.peekn(1))
     let r .= self.getn(1)
@@ -3940,7 +4756,24 @@ function! s:StringReader.read_odigit()
   return r
 endfunction
 
-function! s:StringReader.read_xdigit()
+function! s:StringReader.read_blob() abort
+  let r = ''
+  while 1
+    let s = self.peekn(2)
+    if s =~# '^[0-9A-Fa-f][0-9A-Fa-f]$'
+      let r .= self.getn(2)
+    elseif s =~# '^\.[0-9A-Fa-f]$'
+      let r .= self.getn(1)
+    elseif s =~# '^[0-9A-Fa-f][^0-9A-Fa-f]$'
+      throw s:Err('E973: Blob literal should have an even number of hex characters:' . s, self.getpos())
+    else
+      break
+    endif
+  endwhile
+  return r
+endfunction
+
+function! s:StringReader.read_xdigit() abort
   let r = ''
   while s:isxdigit(self.peekn(1))
     let r .= self.getn(1)
@@ -3948,16 +4781,24 @@ function! s:StringReader.read_xdigit()
   return r
 endfunction
 
-function! s:StringReader.read_integer()
+function! s:StringReader.read_bdigit() abort
+  let r = ''
+  while self.peekn(1) ==# '0' || self.peekn(1) ==# '1'
+    let r .= self.getn(1)
+  endwhile
+  return r
+endfunction
+
+function! s:StringReader.read_integer() abort
   let r = ''
   let c = self.peekn(1)
-  if c == '-' || c == '+'
+  if c ==# '-' || c ==# '+'
     let r = self.getn(1)
   endif
   return r . self.read_digit()
 endfunction
 
-function! s:StringReader.read_word()
+function! s:StringReader.read_word() abort
   let r = ''
   while s:iswordc(self.peekn(1))
     let r .= self.getn(1)
@@ -3965,7 +4806,7 @@ function! s:StringReader.read_word()
   return r
 endfunction
 
-function! s:StringReader.read_white()
+function! s:StringReader.read_white() abort
   let r = ''
   while s:iswhite(self.peekn(1))
     let r .= self.getn(1)
@@ -3973,15 +4814,17 @@ function! s:StringReader.read_white()
   return r
 endfunction
 
-function! s:StringReader.read_nonwhite()
+function! s:StringReader.read_nonwhite() abort
   let r = ''
-  while !s:iswhite(self.peekn(1))
+  let ch = self.peekn(1)
+  while !s:iswhite(ch) && ch !=# ''
     let r .= self.getn(1)
+    let ch = self.peekn(1)
   endwhile
   return r
 endfunction
 
-function! s:StringReader.read_name()
+function! s:StringReader.read_name() abort
   let r = ''
   while s:isnamec(self.peekn(1))
     let r .= self.getn(1)
@@ -3989,13 +4832,13 @@ function! s:StringReader.read_name()
   return r
 endfunction
 
-function! s:StringReader.skip_white()
+function! s:StringReader.skip_white() abort
   while s:iswhite(self.peekn(1))
     call self.seek_cur(1)
   endwhile
 endfunction
 
-function! s:StringReader.skip_white_and_colon()
+function! s:StringReader.skip_white_and_colon() abort
   while s:TRUE
     let c = self.peekn(1)
     if !s:iswhite(c) && c !=# ':'
@@ -4007,19 +4850,19 @@ endfunction
 
 let s:Compiler = {}
 
-function! s:Compiler.new(...)
+function! s:Compiler.new(...) abort
   let obj = copy(self)
   call call(obj.__init__, a:000, obj)
   return obj
 endfunction
 
-function! s:Compiler.__init__()
+function! s:Compiler.__init__() abort
   let self.indent = ['']
   let self.lines = []
 endfunction
 
-function! s:Compiler.out(...)
-  if len(a:000) == 1
+function! s:Compiler.out(...) abort
+  if len(a:000) ==# 1
     if a:000[0][0] ==# ')'
       let self.lines[-1] .= a:000[0]
     else
@@ -4030,203 +4873,215 @@ function! s:Compiler.out(...)
   endif
 endfunction
 
-function! s:Compiler.incindent(s)
+function! s:Compiler.incindent(s) abort
   call insert(self.indent, self.indent[0] . a:s)
 endfunction
 
-function! s:Compiler.decindent()
+function! s:Compiler.decindent() abort
   call remove(self.indent, 0)
 endfunction
 
-function! s:Compiler.compile(node)
-  if a:node.type == s:NODE_TOPLEVEL
+function! s:Compiler.compile(node) abort
+  if a:node.type ==# s:NODE_TOPLEVEL
     return self.compile_toplevel(a:node)
-  elseif a:node.type == s:NODE_COMMENT
+  elseif a:node.type ==# s:NODE_COMMENT
     call self.compile_comment(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_EXCMD
+  elseif a:node.type ==# s:NODE_EXCMD
     call self.compile_excmd(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_FUNCTION
+  elseif a:node.type ==# s:NODE_FUNCTION
     call self.compile_function(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_DELFUNCTION
+  elseif a:node.type ==# s:NODE_DELFUNCTION
     call self.compile_delfunction(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_RETURN
+  elseif a:node.type ==# s:NODE_RETURN
     call self.compile_return(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_EXCALL
+  elseif a:node.type ==# s:NODE_EXCALL
     call self.compile_excall(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_LET
+  elseif a:node.type ==# s:NODE_EVAL
+    call self.compile_eval(a:node)
+    return s:NIL
+  elseif a:node.type ==# s:NODE_LET
     call self.compile_let(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_UNLET
+  elseif a:node.type ==# s:NODE_CONST
+    call self.compile_const(a:node)
+    return s:NIL
+  elseif a:node.type ==# s:NODE_UNLET
     call self.compile_unlet(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_LOCKVAR
+  elseif a:node.type ==# s:NODE_LOCKVAR
     call self.compile_lockvar(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_UNLOCKVAR
+  elseif a:node.type ==# s:NODE_UNLOCKVAR
     call self.compile_unlockvar(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_IF
+  elseif a:node.type ==# s:NODE_IF
     call self.compile_if(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_WHILE
+  elseif a:node.type ==# s:NODE_WHILE
     call self.compile_while(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_FOR
+  elseif a:node.type ==# s:NODE_FOR
     call self.compile_for(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_CONTINUE
+  elseif a:node.type ==# s:NODE_CONTINUE
     call self.compile_continue(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_BREAK
+  elseif a:node.type ==# s:NODE_BREAK
     call self.compile_break(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_TRY
+  elseif a:node.type ==# s:NODE_TRY
     call self.compile_try(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_THROW
+  elseif a:node.type ==# s:NODE_THROW
     call self.compile_throw(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_ECHO
+  elseif a:node.type ==# s:NODE_ECHO
     call self.compile_echo(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_ECHON
+  elseif a:node.type ==# s:NODE_ECHON
     call self.compile_echon(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_ECHOHL
+  elseif a:node.type ==# s:NODE_ECHOHL
     call self.compile_echohl(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_ECHOMSG
+  elseif a:node.type ==# s:NODE_ECHOMSG
     call self.compile_echomsg(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_ECHOERR
+  elseif a:node.type ==# s:NODE_ECHOERR
     call self.compile_echoerr(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_EXECUTE
+  elseif a:node.type ==# s:NODE_EXECUTE
     call self.compile_execute(a:node)
     return s:NIL
-  elseif a:node.type == s:NODE_TERNARY
+  elseif a:node.type ==# s:NODE_TERNARY
     return self.compile_ternary(a:node)
-  elseif a:node.type == s:NODE_OR
+  elseif a:node.type ==# s:NODE_OR
     return self.compile_or(a:node)
-  elseif a:node.type == s:NODE_AND
+  elseif a:node.type ==# s:NODE_AND
     return self.compile_and(a:node)
-  elseif a:node.type == s:NODE_EQUAL
+  elseif a:node.type ==# s:NODE_EQUAL
     return self.compile_equal(a:node)
-  elseif a:node.type == s:NODE_EQUALCI
+  elseif a:node.type ==# s:NODE_EQUALCI
     return self.compile_equalci(a:node)
-  elseif a:node.type == s:NODE_EQUALCS
+  elseif a:node.type ==# s:NODE_EQUALCS
     return self.compile_equalcs(a:node)
-  elseif a:node.type == s:NODE_NEQUAL
+  elseif a:node.type ==# s:NODE_NEQUAL
     return self.compile_nequal(a:node)
-  elseif a:node.type == s:NODE_NEQUALCI
+  elseif a:node.type ==# s:NODE_NEQUALCI
     return self.compile_nequalci(a:node)
-  elseif a:node.type == s:NODE_NEQUALCS
+  elseif a:node.type ==# s:NODE_NEQUALCS
     return self.compile_nequalcs(a:node)
-  elseif a:node.type == s:NODE_GREATER
+  elseif a:node.type ==# s:NODE_GREATER
     return self.compile_greater(a:node)
-  elseif a:node.type == s:NODE_GREATERCI
+  elseif a:node.type ==# s:NODE_GREATERCI
     return self.compile_greaterci(a:node)
-  elseif a:node.type == s:NODE_GREATERCS
+  elseif a:node.type ==# s:NODE_GREATERCS
     return self.compile_greatercs(a:node)
-  elseif a:node.type == s:NODE_GEQUAL
+  elseif a:node.type ==# s:NODE_GEQUAL
     return self.compile_gequal(a:node)
-  elseif a:node.type == s:NODE_GEQUALCI
+  elseif a:node.type ==# s:NODE_GEQUALCI
     return self.compile_gequalci(a:node)
-  elseif a:node.type == s:NODE_GEQUALCS
+  elseif a:node.type ==# s:NODE_GEQUALCS
     return self.compile_gequalcs(a:node)
-  elseif a:node.type == s:NODE_SMALLER
+  elseif a:node.type ==# s:NODE_SMALLER
     return self.compile_smaller(a:node)
-  elseif a:node.type == s:NODE_SMALLERCI
+  elseif a:node.type ==# s:NODE_SMALLERCI
     return self.compile_smallerci(a:node)
-  elseif a:node.type == s:NODE_SMALLERCS
+  elseif a:node.type ==# s:NODE_SMALLERCS
     return self.compile_smallercs(a:node)
-  elseif a:node.type == s:NODE_SEQUAL
+  elseif a:node.type ==# s:NODE_SEQUAL
     return self.compile_sequal(a:node)
-  elseif a:node.type == s:NODE_SEQUALCI
+  elseif a:node.type ==# s:NODE_SEQUALCI
     return self.compile_sequalci(a:node)
-  elseif a:node.type == s:NODE_SEQUALCS
+  elseif a:node.type ==# s:NODE_SEQUALCS
     return self.compile_sequalcs(a:node)
-  elseif a:node.type == s:NODE_MATCH
+  elseif a:node.type ==# s:NODE_MATCH
     return self.compile_match(a:node)
-  elseif a:node.type == s:NODE_MATCHCI
+  elseif a:node.type ==# s:NODE_MATCHCI
     return self.compile_matchci(a:node)
-  elseif a:node.type == s:NODE_MATCHCS
+  elseif a:node.type ==# s:NODE_MATCHCS
     return self.compile_matchcs(a:node)
-  elseif a:node.type == s:NODE_NOMATCH
+  elseif a:node.type ==# s:NODE_NOMATCH
     return self.compile_nomatch(a:node)
-  elseif a:node.type == s:NODE_NOMATCHCI
+  elseif a:node.type ==# s:NODE_NOMATCHCI
     return self.compile_nomatchci(a:node)
-  elseif a:node.type == s:NODE_NOMATCHCS
+  elseif a:node.type ==# s:NODE_NOMATCHCS
     return self.compile_nomatchcs(a:node)
-  elseif a:node.type == s:NODE_IS
+  elseif a:node.type ==# s:NODE_IS
     return self.compile_is(a:node)
-  elseif a:node.type == s:NODE_ISCI
+  elseif a:node.type ==# s:NODE_ISCI
     return self.compile_isci(a:node)
-  elseif a:node.type == s:NODE_ISCS
+  elseif a:node.type ==# s:NODE_ISCS
     return self.compile_iscs(a:node)
-  elseif a:node.type == s:NODE_ISNOT
+  elseif a:node.type ==# s:NODE_ISNOT
     return self.compile_isnot(a:node)
-  elseif a:node.type == s:NODE_ISNOTCI
+  elseif a:node.type ==# s:NODE_ISNOTCI
     return self.compile_isnotci(a:node)
-  elseif a:node.type == s:NODE_ISNOTCS
+  elseif a:node.type ==# s:NODE_ISNOTCS
     return self.compile_isnotcs(a:node)
-  elseif a:node.type == s:NODE_ADD
+  elseif a:node.type ==# s:NODE_ADD
     return self.compile_add(a:node)
-  elseif a:node.type == s:NODE_SUBTRACT
+  elseif a:node.type ==# s:NODE_SUBTRACT
     return self.compile_subtract(a:node)
-  elseif a:node.type == s:NODE_CONCAT
+  elseif a:node.type ==# s:NODE_CONCAT
     return self.compile_concat(a:node)
-  elseif a:node.type == s:NODE_MULTIPLY
+  elseif a:node.type ==# s:NODE_MULTIPLY
     return self.compile_multiply(a:node)
-  elseif a:node.type == s:NODE_DIVIDE
+  elseif a:node.type ==# s:NODE_DIVIDE
     return self.compile_divide(a:node)
-  elseif a:node.type == s:NODE_REMAINDER
+  elseif a:node.type ==# s:NODE_REMAINDER
     return self.compile_remainder(a:node)
-  elseif a:node.type == s:NODE_NOT
+  elseif a:node.type ==# s:NODE_NOT
     return self.compile_not(a:node)
-  elseif a:node.type == s:NODE_PLUS
+  elseif a:node.type ==# s:NODE_PLUS
     return self.compile_plus(a:node)
-  elseif a:node.type == s:NODE_MINUS
+  elseif a:node.type ==# s:NODE_MINUS
     return self.compile_minus(a:node)
-  elseif a:node.type == s:NODE_SUBSCRIPT
+  elseif a:node.type ==# s:NODE_SUBSCRIPT
     return self.compile_subscript(a:node)
-  elseif a:node.type == s:NODE_SLICE
+  elseif a:node.type ==# s:NODE_SLICE
     return self.compile_slice(a:node)
-  elseif a:node.type == s:NODE_DOT
+  elseif a:node.type ==# s:NODE_DOT
     return self.compile_dot(a:node)
-  elseif a:node.type == s:NODE_CALL
+  elseif a:node.type ==# s:NODE_METHOD
+    return self.compile_method(a:node)
+  elseif a:node.type ==# s:NODE_CALL
     return self.compile_call(a:node)
-  elseif a:node.type == s:NODE_NUMBER
+  elseif a:node.type ==# s:NODE_NUMBER
     return self.compile_number(a:node)
-  elseif a:node.type == s:NODE_STRING
+  elseif a:node.type ==# s:NODE_BLOB
+    return self.compile_blob(a:node)
+  elseif a:node.type ==# s:NODE_STRING
     return self.compile_string(a:node)
-  elseif a:node.type == s:NODE_LIST
+  elseif a:node.type ==# s:NODE_LIST
     return self.compile_list(a:node)
-  elseif a:node.type == s:NODE_DICT
+  elseif a:node.type ==# s:NODE_DICT
     return self.compile_dict(a:node)
-  elseif a:node.type == s:NODE_OPTION
+  elseif a:node.type ==# s:NODE_OPTION
     return self.compile_option(a:node)
-  elseif a:node.type == s:NODE_IDENTIFIER
+  elseif a:node.type ==# s:NODE_IDENTIFIER
     return self.compile_identifier(a:node)
-  elseif a:node.type == s:NODE_CURLYNAME
+  elseif a:node.type ==# s:NODE_CURLYNAME
     return self.compile_curlyname(a:node)
-  elseif a:node.type == s:NODE_ENV
+  elseif a:node.type ==# s:NODE_ENV
     return self.compile_env(a:node)
-  elseif a:node.type == s:NODE_REG
+  elseif a:node.type ==# s:NODE_REG
     return self.compile_reg(a:node)
-  elseif a:node.type == s:NODE_CURLYNAMEPART
+  elseif a:node.type ==# s:NODE_CURLYNAMEPART
     return self.compile_curlynamepart(a:node)
-  elseif a:node.type == s:NODE_CURLYNAMEEXPR
+  elseif a:node.type ==# s:NODE_CURLYNAMEEXPR
     return self.compile_curlynameexpr(a:node)
-  elseif a:node.type == s:NODE_LAMBDA
+  elseif a:node.type ==# s:NODE_LAMBDA
     return self.compile_lambda(a:node)
-  elseif a:node.type == s:NODE_PARENEXPR
+  elseif a:node.type ==# s:NODE_HEREDOC
+    return self.compile_heredoc(a:node)
+  elseif a:node.type ==# s:NODE_PARENEXPR
     return self.compile_parenexpr(a:node)
   else
     throw printf('Compiler: unknown node: %s', string(a:node))
@@ -4234,65 +5089,80 @@ function! s:Compiler.compile(node)
   return s:NIL
 endfunction
 
-function! s:Compiler.compile_body(body)
+function! s:Compiler.compile_body(body) abort
   for node in a:body
     call self.compile(node)
   endfor
 endfunction
 
-function! s:Compiler.compile_toplevel(node)
+function! s:Compiler.compile_toplevel(node) abort
   call self.compile_body(a:node.body)
   return self.lines
 endfunction
 
-function! s:Compiler.compile_comment(node)
+function! s:Compiler.compile_comment(node) abort
   call self.out(';%s', a:node.str)
 endfunction
 
-function! s:Compiler.compile_excmd(node)
+function! s:Compiler.compile_excmd(node) abort
   call self.out('(excmd "%s")', escape(a:node.str, '\"'))
 endfunction
 
-function! s:Compiler.compile_function(node)
+function! s:Compiler.compile_function(node) abort
   let left = self.compile(a:node.left)
   let rlist = map(a:node.rlist, 'self.compile(v:val)')
-  if !empty(rlist) && rlist[-1] ==# '...'
-    let rlist[-1] = '. ...'
-  endif
-  if empty(rlist)
-    call self.out('(function (%s)', left)
-  else
-    call self.out('(function (%s %s)', left, join(rlist, ' '))
+  let default_args = map(a:node.default_args, 'self.compile(v:val)')
+  if !empty(rlist)
+    let remaining = s:FALSE
+    if rlist[-1] ==# '...'
+      call remove(rlist, -1)
+      let remaining = s:TRUE
+    endif
+    for i in range(len(rlist))
+      if i < len(rlist) - len(default_args)
+        let left .= printf(' %s', rlist[i])
+      else
+        let left .= printf(' (%s %s)', rlist[i], default_args[i + len(default_args) - len(rlist)])
+      endif
+    endfor
+    if remaining
+      let left .= ' . ...'
+    endif
   endif
+  call self.out('(function (%s)', left)
   call self.incindent('  ')
   call self.compile_body(a:node.body)
   call self.out(')')
   call self.decindent()
 endfunction
 
-function! s:Compiler.compile_delfunction(node)
+function! s:Compiler.compile_delfunction(node) abort
   call self.out('(delfunction %s)', self.compile(a:node.left))
 endfunction
 
-function! s:Compiler.compile_return(node)
-  if a:node.left is s:NIL
+function! s:Compiler.compile_return(node) abort
+  if a:node.left is# s:NIL
     call self.out('(return)')
   else
     call self.out('(return %s)', self.compile(a:node.left))
   endif
 endfunction
 
-function! s:Compiler.compile_excall(node)
+function! s:Compiler.compile_excall(node) abort
   call self.out('(call %s)', self.compile(a:node.left))
 endfunction
 
-function! s:Compiler.compile_let(node)
+function! s:Compiler.compile_eval(node) abort
+  call self.out('(eval %s)', self.compile(a:node.left))
+endfunction
+
+function! s:Compiler.compile_let(node) abort
   let left = ''
-  if a:node.left isnot s:NIL
+  if a:node.left isnot# s:NIL
     let left = self.compile(a:node.left)
   else
     let left = join(map(a:node.list, 'self.compile(v:val)'), ' ')
-    if a:node.rest isnot s:NIL
+    if a:node.rest isnot# s:NIL
       let left .= ' . ' . self.compile(a:node.rest)
     endif
     let left = '(' . left . ')'
@@ -4301,30 +5171,46 @@ function! s:Compiler.compile_let(node)
   call self.out('(let %s %s %s)', a:node.op, left, right)
 endfunction
 
-function! s:Compiler.compile_unlet(node)
+" TODO: merge with s:Compiler.compile_let() ?
+function! s:Compiler.compile_const(node) abort
+  let left = ''
+  if a:node.left isnot# s:NIL
+    let left = self.compile(a:node.left)
+  else
+    let left = join(map(a:node.list, 'self.compile(v:val)'), ' ')
+    if a:node.rest isnot# s:NIL
+      let left .= ' . ' . self.compile(a:node.rest)
+    endif
+    let left = '(' . left . ')'
+  endif
+  let right = self.compile(a:node.right)
+  call self.out('(const %s %s %s)', a:node.op, left, right)
+endfunction
+
+function! s:Compiler.compile_unlet(node) abort
   let list = map(a:node.list, 'self.compile(v:val)')
   call self.out('(unlet %s)', join(list, ' '))
 endfunction
 
-function! s:Compiler.compile_lockvar(node)
+function! s:Compiler.compile_lockvar(node) abort
   let list = map(a:node.list, 'self.compile(v:val)')
-  if a:node.depth is s:NIL
+  if a:node.depth is# s:NIL
     call self.out('(lockvar %s)', join(list, ' '))
   else
     call self.out('(lockvar %d %s)', a:node.depth, join(list, ' '))
   endif
 endfunction
 
-function! s:Compiler.compile_unlockvar(node)
+function! s:Compiler.compile_unlockvar(node) abort
   let list = map(a:node.list, 'self.compile(v:val)')
-  if a:node.depth is s:NIL
+  if a:node.depth is# s:NIL
     call self.out('(unlockvar %s)', join(list, ' '))
   else
     call self.out('(unlockvar %d %s)', a:node.depth, join(list, ' '))
   endif
 endfunction
 
-function! s:Compiler.compile_if(node)
+function! s:Compiler.compile_if(node) abort
   call self.out('(if %s', self.compile(a:node.cond))
   call self.incindent('  ')
   call self.compile_body(a:node.body)
@@ -4335,7 +5221,7 @@ function! s:Compiler.compile_if(node)
     call self.compile_body(enode.body)
     call self.decindent()
   endfor
-  if a:node.else isnot s:NIL
+  if a:node.else isnot# s:NIL
     call self.out(' else')
     call self.incindent('  ')
     call self.compile_body(a:node.else.body)
@@ -4346,7 +5232,7 @@ function! s:Compiler.compile_if(node)
   call self.decindent()
 endfunction
 
-function! s:Compiler.compile_while(node)
+function! s:Compiler.compile_while(node) abort
   call self.out('(while %s', self.compile(a:node.cond))
   call self.incindent('  ')
   call self.compile_body(a:node.body)
@@ -4354,13 +5240,13 @@ function! s:Compiler.compile_while(node)
   call self.decindent()
 endfunction
 
-function! s:Compiler.compile_for(node)
+function! s:Compiler.compile_for(node) abort
   let left = ''
-  if a:node.left isnot s:NIL
+  if a:node.left isnot# s:NIL
     let left = self.compile(a:node.left)
   else
     let left = join(map(a:node.list, 'self.compile(v:val)'), ' ')
-    if a:node.rest isnot s:NIL
+    if a:node.rest isnot# s:NIL
       let left .= ' . ' . self.compile(a:node.rest)
     endif
     let left = '(' . left . ')'
@@ -4373,20 +5259,20 @@ function! s:Compiler.compile_for(node)
   call self.decindent()
 endfunction
 
-function! s:Compiler.compile_continue(node)
+function! s:Compiler.compile_continue(node) abort
   call self.out('(continue)')
 endfunction
 
-function! s:Compiler.compile_break(node)
+function! s:Compiler.compile_break(node) abort
   call self.out('(break)')
 endfunction
 
-function! s:Compiler.compile_try(node)
+function! s:Compiler.compile_try(node) abort
   call self.out('(try')
   call self.incindent('  ')
   call self.compile_body(a:node.body)
   for cnode in a:node.catch
-    if cnode.pattern isnot s:NIL
+    if cnode.pattern isnot# s:NIL
       call self.decindent()
       call self.out(' catch /%s/', cnode.pattern)
       call self.incindent('  ')
@@ -4398,7 +5284,7 @@ function! s:Compiler.compile_try(node)
       call self.compile_body(cnode.body)
     endif
   endfor
-  if a:node.finally isnot s:NIL
+  if a:node.finally isnot# s:NIL
     call self.decindent()
     call self.out(' finally')
     call self.incindent('  ')
@@ -4408,222 +5294,226 @@ function! s:Compiler.compile_try(node)
   call self.decindent()
 endfunction
 
-function! s:Compiler.compile_throw(node)
+function! s:Compiler.compile_throw(node) abort
   call self.out('(throw %s)', self.compile(a:node.left))
 endfunction
 
-function! s:Compiler.compile_echo(node)
+function! s:Compiler.compile_echo(node) abort
   let list = map(a:node.list, 'self.compile(v:val)')
   call self.out('(echo %s)', join(list, ' '))
 endfunction
 
-function! s:Compiler.compile_echon(node)
+function! s:Compiler.compile_echon(node) abort
   let list = map(a:node.list, 'self.compile(v:val)')
   call self.out('(echon %s)', join(list, ' '))
 endfunction
 
-function! s:Compiler.compile_echohl(node)
+function! s:Compiler.compile_echohl(node) abort
   call self.out('(echohl "%s")', escape(a:node.str, '\"'))
 endfunction
 
-function! s:Compiler.compile_echomsg(node)
+function! s:Compiler.compile_echomsg(node) abort
   let list = map(a:node.list, 'self.compile(v:val)')
   call self.out('(echomsg %s)', join(list, ' '))
 endfunction
 
-function! s:Compiler.compile_echoerr(node)
+function! s:Compiler.compile_echoerr(node) abort
   let list = map(a:node.list, 'self.compile(v:val)')
   call self.out('(echoerr %s)', join(list, ' '))
 endfunction
 
-function! s:Compiler.compile_execute(node)
+function! s:Compiler.compile_execute(node) abort
   let list = map(a:node.list, 'self.compile(v:val)')
   call self.out('(execute %s)', join(list, ' '))
 endfunction
 
-function! s:Compiler.compile_ternary(node)
+function! s:Compiler.compile_ternary(node) abort
   return printf('(?: %s %s %s)', self.compile(a:node.cond), self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_or(node)
+function! s:Compiler.compile_or(node) abort
   return printf('(|| %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_and(node)
+function! s:Compiler.compile_and(node) abort
   return printf('(&& %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_equal(node)
+function! s:Compiler.compile_equal(node) abort
   return printf('(== %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_equalci(node)
+function! s:Compiler.compile_equalci(node) abort
   return printf('(==? %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_equalcs(node)
+function! s:Compiler.compile_equalcs(node) abort
   return printf('(==# %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_nequal(node)
+function! s:Compiler.compile_nequal(node) abort
   return printf('(!= %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_nequalci(node)
+function! s:Compiler.compile_nequalci(node) abort
   return printf('(!=? %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_nequalcs(node)
+function! s:Compiler.compile_nequalcs(node) abort
   return printf('(!=# %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_greater(node)
+function! s:Compiler.compile_greater(node) abort
   return printf('(> %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_greaterci(node)
+function! s:Compiler.compile_greaterci(node) abort
   return printf('(>? %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_greatercs(node)
+function! s:Compiler.compile_greatercs(node) abort
   return printf('(># %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_gequal(node)
+function! s:Compiler.compile_gequal(node) abort
   return printf('(>= %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_gequalci(node)
+function! s:Compiler.compile_gequalci(node) abort
   return printf('(>=? %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_gequalcs(node)
+function! s:Compiler.compile_gequalcs(node) abort
   return printf('(>=# %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_smaller(node)
+function! s:Compiler.compile_smaller(node) abort
   return printf('(< %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_smallerci(node)
+function! s:Compiler.compile_smallerci(node) abort
   return printf('(<? %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_smallercs(node)
+function! s:Compiler.compile_smallercs(node) abort
   return printf('(<# %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_sequal(node)
+function! s:Compiler.compile_sequal(node) abort
   return printf('(<= %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_sequalci(node)
+function! s:Compiler.compile_sequalci(node) abort
   return printf('(<=? %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_sequalcs(node)
+function! s:Compiler.compile_sequalcs(node) abort
   return printf('(<=# %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_match(node)
+function! s:Compiler.compile_match(node) abort
   return printf('(=~ %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_matchci(node)
+function! s:Compiler.compile_matchci(node) abort
   return printf('(=~? %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_matchcs(node)
+function! s:Compiler.compile_matchcs(node) abort
   return printf('(=~# %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_nomatch(node)
+function! s:Compiler.compile_nomatch(node) abort
   return printf('(!~ %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_nomatchci(node)
+function! s:Compiler.compile_nomatchci(node) abort
   return printf('(!~? %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_nomatchcs(node)
+function! s:Compiler.compile_nomatchcs(node) abort
   return printf('(!~# %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_is(node)
+function! s:Compiler.compile_is(node) abort
   return printf('(is %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_isci(node)
+function! s:Compiler.compile_isci(node) abort
   return printf('(is? %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_iscs(node)
+function! s:Compiler.compile_iscs(node) abort
   return printf('(is# %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_isnot(node)
+function! s:Compiler.compile_isnot(node) abort
   return printf('(isnot %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_isnotci(node)
+function! s:Compiler.compile_isnotci(node) abort
   return printf('(isnot? %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_isnotcs(node)
+function! s:Compiler.compile_isnotcs(node) abort
   return printf('(isnot# %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_add(node)
+function! s:Compiler.compile_add(node) abort
   return printf('(+ %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_subtract(node)
+function! s:Compiler.compile_subtract(node) abort
   return printf('(- %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_concat(node)
+function! s:Compiler.compile_concat(node) abort
   return printf('(concat %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_multiply(node)
+function! s:Compiler.compile_multiply(node) abort
   return printf('(* %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_divide(node)
+function! s:Compiler.compile_divide(node) abort
   return printf('(/ %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_remainder(node)
+function! s:Compiler.compile_remainder(node) abort
   return printf('(%% %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_not(node)
+function! s:Compiler.compile_not(node) abort
   return printf('(! %s)', self.compile(a:node.left))
 endfunction
 
-function! s:Compiler.compile_plus(node)
+function! s:Compiler.compile_plus(node) abort
   return printf('(+ %s)', self.compile(a:node.left))
 endfunction
 
-function! s:Compiler.compile_minus(node)
+function! s:Compiler.compile_minus(node) abort
   return printf('(- %s)', self.compile(a:node.left))
 endfunction
 
-function! s:Compiler.compile_subscript(node)
+function! s:Compiler.compile_subscript(node) abort
   return printf('(subscript %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_slice(node)
-  let r0 = a:node.rlist[0] is s:NIL ? 'nil' : self.compile(a:node.rlist[0])
-  let r1 = a:node.rlist[1] is s:NIL ? 'nil' : self.compile(a:node.rlist[1])
+function! s:Compiler.compile_slice(node) abort
+  let r0 = a:node.rlist[0] is# s:NIL ? 'nil' : self.compile(a:node.rlist[0])
+  let r1 = a:node.rlist[1] is# s:NIL ? 'nil' : self.compile(a:node.rlist[1])
   return printf('(slice %s %s %s)', self.compile(a:node.left), r0, r1)
 endfunction
 
-function! s:Compiler.compile_dot(node)
+function! s:Compiler.compile_dot(node) abort
   return printf('(dot %s %s)', self.compile(a:node.left), self.compile(a:node.right))
 endfunction
 
-function! s:Compiler.compile_call(node)
+function! s:Compiler.compile_method(node) abort
+  return printf('(method %s %s)', self.compile(a:node.left), self.compile(a:node.right))
+endfunction
+
+function! s:Compiler.compile_call(node) abort
   let rlist = map(a:node.rlist, 'self.compile(v:val)')
   if empty(rlist)
     return printf('(%s)', self.compile(a:node.left))
@@ -4632,15 +5522,19 @@ function! s:Compiler.compile_call(node)
   endif
 endfunction
 
-function! s:Compiler.compile_number(node)
+function! s:Compiler.compile_number(node) abort
   return a:node.value
 endfunction
 
-function! s:Compiler.compile_string(node)
+function! s:Compiler.compile_blob(node) abort
   return a:node.value
 endfunction
 
-function! s:Compiler.compile_list(node)
+function! s:Compiler.compile_string(node) abort
+  return a:node.value
+endfunction
+
+function! s:Compiler.compile_list(node) abort
   let value = map(a:node.value, 'self.compile(v:val)')
   if empty(value)
     return '(list)'
@@ -4649,7 +5543,7 @@ function! s:Compiler.compile_list(node)
   endif
 endfunction
 
-function! s:Compiler.compile_dict(node)
+function! s:Compiler.compile_dict(node) abort
   let value = map(a:node.value, '"(" . self.compile(v:val[0]) . " " . self.compile(v:val[1]) . ")"')
   if empty(value)
     return '(dict)'
@@ -4658,39 +5552,65 @@ function! s:Compiler.compile_dict(node)
   endif
 endfunction
 
-function! s:Compiler.compile_option(node)
+function! s:Compiler.compile_option(node) abort
   return a:node.value
 endfunction
 
-function! s:Compiler.compile_identifier(node)
+function! s:Compiler.compile_identifier(node) abort
   return a:node.value
 endfunction
 
-function! s:Compiler.compile_curlyname(node)
+function! s:Compiler.compile_curlyname(node) abort
   return join(map(a:node.value, 'self.compile(v:val)'), '')
 endfunction
 
-function! s:Compiler.compile_env(node)
+function! s:Compiler.compile_env(node) abort
   return a:node.value
 endfunction
 
-function! s:Compiler.compile_reg(node)
+function! s:Compiler.compile_reg(node) abort
   return a:node.value
 endfunction
 
-function! s:Compiler.compile_curlynamepart(node)
+function! s:Compiler.compile_curlynamepart(node) abort
   return a:node.value
 endfunction
 
-function! s:Compiler.compile_curlynameexpr(node)
+function! s:Compiler.compile_curlynameexpr(node) abort
   return '{' . self.compile(a:node.value) . '}'
 endfunction
 
-function! s:Compiler.compile_lambda(node)
+function! s:Compiler.escape_string(str) abort
+  let out = '"'
+  for c in split(a:str, '\zs')
+    if c ==# "\n"
+      let out .= '\n'
+    elseif c ==# "\t"
+      let out .= '\t'
+    elseif c ==# "\r"
+      let out .= '\r'
+    else
+      let out .= c
+    endif
+  endfor
+  let out .= '"'
+  return out
+endfunction
+
+function! s:Compiler.compile_lambda(node) abort
   let rlist = map(a:node.rlist, 'self.compile(v:val)')
   return printf('(lambda (%s) %s)', join(rlist, ' '), self.compile(a:node.left))
 endfunction
 
+function! s:Compiler.compile_heredoc(node) abort
+  let rlist = empty(a:node.rlist) ? '(list)'
+        \ : '(list ' . join(map(a:node.rlist, 'self.escape_string(v:val.value)'), ' ') . ')'
+  let body = empty(a:node.body) ? '(list)'
+        \ : '(list ' . join(map(a:node.body, 'self.escape_string(v:val.value)'), ' ') . ')'
+  let op = self.escape_string(a:node.op)
+  return printf('(heredoc %s %s %s)', rlist, op, body)
+endfunction
+
 function! s:Compiler.compile_parenexpr(node)
   return self.compile(a:node.value)
 endfunction
@@ -4703,24 +5623,24 @@ let s:RegexpParser.RE_NOMAGIC = 2
 let s:RegexpParser.RE_MAGIC = 3
 let s:RegexpParser.RE_VERY_MAGIC = 4
 
-function! s:RegexpParser.new(...)
+function! s:RegexpParser.new(...) abort
   let obj = copy(self)
   call call(obj.__init__, a:000, obj)
   return obj
 endfunction
 
-function! s:RegexpParser.__init__(reader, cmd, delim)
+function! s:RegexpParser.__init__(reader, cmd, delim) abort
   let self.reader = a:reader
   let self.cmd = a:cmd
   let self.delim = a:delim
   let self.reg_magic = self.RE_MAGIC
 endfunction
 
-function! s:RegexpParser.isend(c)
+function! s:RegexpParser.isend(c) abort
   return a:c ==# '<EOF>' || a:c ==# '<EOL>' || a:c ==# self.delim
 endfunction
 
-function! s:RegexpParser.parse_regexp()
+function! s:RegexpParser.parse_regexp() abort
   let prevtoken = ''
   let ntoken = ''
   let ret = []
@@ -4750,13 +5670,13 @@ function! s:RegexpParser.parse_regexp()
       endif
     elseif ntoken ==# '\^'
       " '^' is only magic as the very first character.
-      if self.reg_magic != self.RE_VERY_MAGIC && prevtoken !=# '' && prevtoken !=# '\&' && prevtoken !=# '\|' && prevtoken !=# '\n' && prevtoken !=# '\(' && prevtoken !=# '\%('
+      if self.reg_magic !=# self.RE_VERY_MAGIC && prevtoken !=# '' && prevtoken !=# '\&' && prevtoken !=# '\|' && prevtoken !=# '\n' && prevtoken !=# '\(' && prevtoken !=# '\%('
         let ntoken = '^'
       endif
     elseif ntoken ==# '\$'
       " '$' is only magic as the very last character
       let pos = self.reader.tell()
-      if self.reg_magic != self.RE_VERY_MAGIC
+      if self.reg_magic !=# self.RE_VERY_MAGIC
         while !self.isend(self.reader.peek())
           let [t, n] = self.get_token()
           " XXX: Vim doesn't check \v and \V?
@@ -4782,19 +5702,19 @@ function! s:RegexpParser.parse_regexp()
 endfunction
 
 " @return [actual_token, normalized_token]
-function! s:RegexpParser.get_token()
-  if self.reg_magic == self.RE_VERY_MAGIC
+function! s:RegexpParser.get_token() abort
+  if self.reg_magic ==# self.RE_VERY_MAGIC
     return self.get_token_very_magic()
-  elseif self.reg_magic == self.RE_MAGIC
+  elseif self.reg_magic ==# self.RE_MAGIC
     return self.get_token_magic()
-  elseif self.reg_magic == self.RE_NOMAGIC
+  elseif self.reg_magic ==# self.RE_NOMAGIC
     return self.get_token_nomagic()
-  elseif self.reg_magic == self.RE_VERY_NOMAGIC
+  elseif self.reg_magic ==# self.RE_VERY_NOMAGIC
     return self.get_token_very_nomagic()
   endif
 endfunction
 
-function! s:RegexpParser.get_token_very_magic()
+function! s:RegexpParser.get_token_very_magic() abort
   if self.isend(self.reader.peek())
     return ['<END>', '<END>']
   endif
@@ -4841,7 +5761,7 @@ function! s:RegexpParser.get_token_very_magic()
   return [c, c]
 endfunction
 
-function! s:RegexpParser.get_token_magic()
+function! s:RegexpParser.get_token_magic() abort
   if self.isend(self.reader.peek())
     return ['<END>', '<END>']
   endif
@@ -4892,7 +5812,7 @@ function! s:RegexpParser.get_token_magic()
   return [c, c]
 endfunction
 
-function! s:RegexpParser.get_token_nomagic()
+function! s:RegexpParser.get_token_nomagic() abort
   if self.isend(self.reader.peek())
     return ['<END>', '<END>']
   endif
@@ -4943,7 +5863,7 @@ function! s:RegexpParser.get_token_nomagic()
   return [c, c]
 endfunction
 
-function! s:RegexpParser.get_token_very_nomagic()
+function! s:RegexpParser.get_token_very_nomagic() abort
   if self.isend(self.reader.peek())
     return ['<END>', '<END>']
   endif
@@ -4992,17 +5912,17 @@ function! s:RegexpParser.get_token_very_nomagic()
   return [c, c]
 endfunction
 
-function! s:RegexpParser.get_token_backslash_common()
+function! s:RegexpParser.get_token_backslash_common() abort
   let cclass = 'iIkKfFpPsSdDxXoOwWhHaAlLuU'
   let c = self.reader.get()
   if c ==# '\'
     return ['\\', '\\']
-  elseif stridx(cclass, c) != -1
+  elseif stridx(cclass, c) !=# -1
     return ['\' . c, '\' . c]
-  elseif c == '_'
+  elseif c ==# '_'
     let epos = self.reader.getpos()
     let c = self.reader.get()
-    if stridx(cclass, c) != -1
+    if stridx(cclass, c) !=# -1
       return ['\_' . c, '\_ . c']
     elseif c ==# '^'
       return ['\_^', '\_^']
@@ -5014,14 +5934,14 @@ function! s:RegexpParser.get_token_backslash_common()
       return self.get_token_sq('\_[')
     endif
     throw s:Err('E63: invalid use of \_', epos)
-  elseif stridx('etrb', c) != -1
+  elseif stridx('etrb', c) !=# -1
     return ['\' . c, '\' . c]
-  elseif stridx('123456789', c) != -1
+  elseif stridx('123456789', c) !=# -1
     return ['\' . c, '\' . c]
-  elseif c == 'z'
+  elseif c ==# 'z'
     let epos = self.reader.getpos()
     let c = self.reader.get()
-    if stridx('123456789', c) != -1
+    if stridx('123456789', c) !=# -1
       return ['\z' . c, '\z' . c]
     elseif c ==# 's'
       return ['\zs', '\zs']
@@ -5031,9 +5951,9 @@ function! s:RegexpParser.get_token_backslash_common()
       return ['\z(', '\z(']
     endif
     throw s:Err('E68: Invalid character after \z', epos)
-  elseif stridx('cCmMvVZ', c) != -1
+  elseif stridx('cCmMvVZ', c) !=# -1
     return ['\' . c, '\' . c]
-  elseif c == '%'
+  elseif c ==# '%'
     let epos = self.reader.getpos()
     let c = self.reader.get()
     if c ==# 'd'
@@ -5068,7 +5988,7 @@ function! s:RegexpParser.get_token_backslash_common()
 endfunction
 
 " \{}
-function! s:RegexpParser.get_token_brace(pre)
+function! s:RegexpParser.get_token_brace(pre) abort
   let r = ''
   let minus = ''
   let comma = ''
@@ -5101,7 +6021,7 @@ function! s:RegexpParser.get_token_brace(pre)
 endfunction
 
 " \[]
-function! s:RegexpParser.get_token_sq(pre)
+function! s:RegexpParser.get_token_sq(pre) abort
   let start = self.reader.tell()
   let r = ''
   " Complement of range
@@ -5138,7 +6058,7 @@ function! s:RegexpParser.get_token_sq(pre)
       let [e, startc] = self.get_token_sq_c()
       let r .= e
     endif
-    if startc != 0 && self.reader.p(0) ==# '-' && !self.isend(self.reader.p(1)) && !(self.reader.p(1) ==# '\' && self.reader.p(2) ==# 'n')
+    if startc !=# 0 && self.reader.p(0) ==# '-' && !self.isend(self.reader.p(1)) && !(self.reader.p(1) ==# '\' && self.reader.p(2) ==# 'n')
       call self.reader.seek_cur(1)
       let r .= '-'
       let c = self.reader.p(0)
@@ -5162,7 +6082,7 @@ function! s:RegexpParser.get_token_sq(pre)
 endfunction
 
 " [c]
-function! s:RegexpParser.get_token_sq_c()
+function! s:RegexpParser.get_token_sq_c() abort
   let c = self.reader.p(0)
   if c ==# '\'
     call self.reader.seek_cur(1)
@@ -5172,20 +6092,20 @@ function! s:RegexpParser.get_token_sq_c()
       return ['\n', 0]
     elseif c ==# 'r'
       call self.reader.seek_cur(1)
-      return ['\r', char2nr("\r")]
+      return ['\r', 13]
     elseif c ==# 't'
       call self.reader.seek_cur(1)
-      return ['\t', char2nr("\t")]
+      return ['\t', 9]
     elseif c ==# 'e'
       call self.reader.seek_cur(1)
-      return ['\e', char2nr("\e")]
+      return ['\e', 27]
     elseif c ==# 'b'
       call self.reader.seek_cur(1)
-      return ['\b', char2nr("\b")]
-    elseif stridx(']^-\', c) != -1
+      return ['\b', 8]
+    elseif stridx(']^-\', c) !=# -1
       call self.reader.seek_cur(1)
       return ['\' . c, char2nr(c)]
-    elseif stridx('doxuU', c) != -1
+    elseif stridx('doxuU', c) !=# -1
       let [c, n] = self.get_token_sq_coll_char()
       return [c, n]
     else
@@ -5201,7 +6121,7 @@ function! s:RegexpParser.get_token_sq_c()
 endfunction
 
 " [\d123]
-function! s:RegexpParser.get_token_sq_coll_char()
+function! s:RegexpParser.get_token_sq_coll_char() abort
   let pos = self.reader.tell()
   let c = self.reader.get()
   if c ==# 'd'
@@ -5230,7 +6150,7 @@ function! s:RegexpParser.get_token_sq_coll_char()
 endfunction
 
 " [[.a.]]
-function! s:RegexpParser.get_token_sq_coll_element()
+function! s:RegexpParser.get_token_sq_coll_element() abort
   if self.reader.p(0) ==# '[' && self.reader.p(1) ==# '.' && !self.isend(self.reader.p(2)) && self.reader.p(3) ==# '.' && self.reader.p(4) ==# ']'
     return self.reader.getn(5)
   endif
@@ -5238,7 +6158,7 @@ function! s:RegexpParser.get_token_sq_coll_element()
 endfunction
 
 " [[=a=]]
-function! s:RegexpParser.get_token_sq_equi_class()
+function! s:RegexpParser.get_token_sq_equi_class() abort
   if self.reader.p(0) ==# '[' && self.reader.p(1) ==# '=' && !self.isend(self.reader.p(2)) && self.reader.p(3) ==# '=' && self.reader.p(4) ==# ']'
     return self.reader.getn(5)
   endif
@@ -5246,8 +6166,8 @@ function! s:RegexpParser.get_token_sq_equi_class()
 endfunction
 
 " [[:alpha:]]
-function! s:RegexpParser.get_token_sq_char_class()
-  let class_names = ["alnum", "alpha", "blank", "cntrl", "digit", "graph", "lower", "print", "punct", "space", "upper", "xdigit", "tab", "return", "backspace", "escape"]
+function! s:RegexpParser.get_token_sq_char_class() abort
+  let class_names = ['alnum', 'alpha', 'blank', 'cntrl', 'digit', 'graph', 'lower', 'print', 'punct', 'space', 'upper', 'xdigit', 'tab', 'return', 'backspace', 'escape']
   let pos = self.reader.tell()
   if self.reader.p(0) ==# '[' && self.reader.p(1) ==# ':'
     call self.reader.seek_cur(2)
@@ -5266,7 +6186,7 @@ function! s:RegexpParser.get_token_sq_char_class()
 endfunction
 
 " \@...
-function! s:RegexpParser.get_token_at(pre)
+function! s:RegexpParser.get_token_at(pre) abort
   let epos = self.reader.getpos()
   let c = self.reader.get()
   if c ==# '>'
@@ -5287,7 +6207,7 @@ function! s:RegexpParser.get_token_at(pre)
 endfunction
 
 " \%...
-function! s:RegexpParser.get_token_percent(pre)
+function! s:RegexpParser.get_token_percent(pre) abort
   let c = self.reader.get()
   if c ==# '^'
     return [a:pre . '^', '\%^']
@@ -5307,7 +6227,7 @@ function! s:RegexpParser.get_token_percent(pre)
 endfunction
 
 " \%[]
-function! s:RegexpParser.get_token_percent_sq(pre)
+function! s:RegexpParser.get_token_percent_sq(pre) abort
   let r = ''
   while s:TRUE
     let c = self.reader.peek()
@@ -5327,7 +6247,7 @@ function! s:RegexpParser.get_token_percent_sq(pre)
 endfunction
 
 " \%'m \%l \%c \%v
-function! s:RegexpParser.get_token_mlvc(pre)
+function! s:RegexpParser.get_token_mlvc(pre) abort
   let r = ''
   let cmp = ''
   if self.reader.p(0) ==# '<' || self.reader.p(0) ==# '>'
@@ -5362,15 +6282,15 @@ function! s:RegexpParser.get_token_mlvc(pre)
   throw s:Err('E71: Invalid character after %', self.reader.getpos())
 endfunction
 
-function! s:RegexpParser.getdecchrs()
+function! s:RegexpParser.getdecchrs() abort
   return self.reader.read_digit()
 endfunction
 
-function! s:RegexpParser.getoctchrs()
+function! s:RegexpParser.getoctchrs() abort
   return self.reader.read_odigit()
 endfunction
 
-function! s:RegexpParser.gethexchrs(n)
+function! s:RegexpParser.gethexchrs(n) abort
   let r = ''
   for i in range(a:n)
     let c = self.reader.peek()
diff --git a/compiler/compiler.go b/compiler/compiler.go
index 8763f05..3726bc5 100644
--- a/compiler/compiler.go
+++ b/compiler/compiler.go
@@ -129,6 +129,8 @@ func (c *Compiler) compileExcommand(node ast.ExCommand) error {
 		c.compileTry(n)
 	case *ast.Throw:
 		c.compileThrow(n)
+	case *ast.Eval:
+		c.compileEval(n)
 	case *ast.EchoCmd:
 		c.compileEchocmd(n)
 	case *ast.Echohl:
@@ -151,13 +153,22 @@ func (c *Compiler) compileFunction(node *ast.Function) {
 	if len(node.Params) > 0 {
 		c.fprint(" ")
 		ps := make([]string, 0, len(node.Params))
-		for _, p := range node.Params {
-			if p.Name == token.DOTDOTDOT.String() {
-				ps = append(ps, ". ...")
-			} else {
+		var remaining bool
+		if node.Params[len(node.Params)-1].Name == token.DOTDOTDOT.String() {
+			node.Params = node.Params[:len(node.Params)-1]
+			remaining = true
+		}
+		for i, p := range node.Params {
+			if i < len(node.Params)-len(node.DefaultArgs) {
 				ps = append(ps, p.Name)
+			} else {
+				ps = append(ps, fmt.Sprintf("(%s %s)", p.Name,
+					c.compileExpr(node.DefaultArgs[i+len(node.DefaultArgs)-len(node.Params)])))
 			}
 		}
+		if remaining {
+			ps = append(ps, ". ...")
+		}
 		c.fprint("%s", strings.Join(ps, " "))
 	}
 	c.fprintln(")")
@@ -332,6 +343,11 @@ func (c *Compiler) compileThrow(node *ast.Throw) {
 	c.fprintln("(%s %s)", cmd, c.compileExpr(node.Expr))
 }
 
+func (c *Compiler) compileEval(node *ast.Eval) {
+	cmd := node.Cmd().Name
+	c.fprintln("(%s %s)", cmd, c.compileExpr(node.Expr))
+}
+
 func (c *Compiler) compileEchocmd(node *ast.EchoCmd) {
 	cmd := node.Cmd().Name
 	exprs := make([]string, 0, len(node.Exprs))
@@ -387,6 +403,14 @@ func (c *Compiler) compileExpr(node ast.Expr) string {
 			h = c.compileExpr(n.High)
 		}
 		return fmt.Sprintf("(slice %s %s %s)", x, l, h)
+	case *ast.MethodExpr:
+		name := c.compileExpr(n.Left)
+		args := make([]string, 0, len(n.Args)+1)
+		args = append(args, c.compileExpr(n.Method))
+		for _, a := range n.Args {
+			args = append(args, c.compileExpr(a))
+		}
+		return fmt.Sprintf("(method %s (%s))", name, strings.Join(args, " "))
 	case *ast.CallExpr:
 		name := c.compileExpr(n.Fun)
 		if len(n.Args) > 0 {
@@ -440,6 +464,28 @@ func (c *Compiler) compileExpr(node ast.Expr) string {
 			params = append(params, p.Name)
 		}
 		return fmt.Sprintf("(lambda (%s) %s)", strings.Join(params, " "), c.compileExpr(n.Expr))
+	case *ast.HeredocExpr:
+		var flags string
+		if len(n.Flags) == 0 {
+			flags = "(list)"
+		} else {
+			xs := make([]string, len(n.Flags))
+			for i, f := range n.Flags {
+				xs[i] = `"` + c.compileExpr(f) + `"`
+			}
+			flags = fmt.Sprintf("(list %s)", strings.Join(xs, " "))
+		}
+		var body string
+		if len(n.Body) == 0 {
+			body = "(list)"
+		} else {
+			xs := make([]string, len(n.Body))
+			for i, p := range n.Body {
+				xs[i] = `"` + escape(c.compileExpr(p), "\n\t\r") + `"`
+			}
+			body = fmt.Sprintf("(list %s)", strings.Join(xs, " "))
+		}
+		return fmt.Sprintf(`(heredoc %s "%s" %s)`, flags, n.EndMarker, body)
 	case *ast.ParenExpr:
 		return c.compileExpr(n.X)
 	}
@@ -450,7 +496,16 @@ func escape(s string, chars string) string {
 	r := ""
 	for _, c := range s {
 		if strings.IndexRune(chars, c) != -1 {
-			r += `\` + string(c)
+			switch c {
+			case '\n':
+				r += `\n`
+			case '\t':
+				r += `\t`
+			case '\r':
+				r += `\r`
+			default:
+				r += `\` + string(c)
+			}
 		} else {
 			r += string(c)
 		}
diff --git a/go/_test/special.go b/go/_test/special.go
index df0d8cc..de05787 100644
--- a/go/_test/special.go
+++ b/go/_test/special.go
@@ -3,7 +3,8 @@ self.hoge = 1
 self.ea.range_ = 1
 xxx.x = 1
 var z = self.ea.range_
-var xs = viml_range(10)
+var xs = viml_range(0, 10 - 1)
+xs = viml_range(0, 10)
 Node()
 var type_ = 1
 var t = type_
@@ -34,6 +35,9 @@ node.attr.dict = true
 // skip
 // end skip
 // do not skip
+node.rlist = []*VimNode{}
+node.list = []*VimNode{}
+node.depth = 0
 node.list = self.parse_lvaluelist()
 node.depth = hoge
 node.rlist = []*VimNode{nil, nil}
diff --git a/go/_test/special.vim b/go/_test/special.vim
index 294a9cd..e5c0573 100644
--- a/go/_test/special.vim
+++ b/go/_test/special.vim
@@ -78,6 +78,7 @@ let self.ea.range = 1
 let xxx.x = 1
 let z = self.ea.range
 let xs = range(10)
+let xs = range(0, 10)
 
 function! s:Node()
   " skip Node definition
@@ -122,7 +123,6 @@ let self.context = {}
 let toplevel.body = {}
 
 let node.body = []
-let node.rlist = []
 let node.attr = {'range': 0, 'abort': 0, 'dict': 0}
 let node.endfunction = s:NIL
 let node.endif = s:NIL
@@ -133,14 +133,15 @@ let node.elseif = s:NIL
 let node.catch = []
 let node.finally = []
 
-let node.list = []
-let node.depth = s:NIL
 let node.pattern = s:NIL
 
 let lhs.list = []
 " end skip
 
 " do not skip
+let node.rlist = []
+let node.list = []
+let node.depth = s:NIL
 let node.list = self.parse_lvaluelist()
 let node.depth = hoge
 let node.pattern = node
diff --git a/go/_test/while.go b/go/_test/while.go
index 398be29..853a9fa 100644
--- a/go/_test/while.go
+++ b/go/_test/while.go
@@ -3,3 +3,6 @@ for b {
 for {
 	var x = 1
 }
+for {
+	var x = 1
+}
diff --git a/go/_test/while.vim b/go/_test/while.vim
index 015fd92..666ffca 100644
--- a/go/_test/while.vim
+++ b/go/_test/while.vim
@@ -3,3 +3,6 @@ endwhile
 while 1
   let x = 1
 endwhile
+while 1
+  let x = 1
+endwhile
diff --git a/go/builtin_commands.go b/go/builtin_commands.go
index 02bd2a8..9ba4b19 100644
--- a/go/builtin_commands.go
+++ b/go/builtin_commands.go
@@ -121,7 +121,7 @@ var builtin_commands = []*Cmd{
 	{flags: "NEEDARG|EXTRA|NOTRLCOM|SBOXOK|CMDWIN", minlen: 3, name: "debug", parser: "parse_cmd_common"},
 	{flags: "RANGE|NOTADR|ZEROR|TRLBAR|CMDWIN", minlen: 6, name: "debuggreedy", parser: "parse_cmd_common"},
 	{flags: "NEEDARG|WORD1|TRLBAR|CMDWIN", minlen: 4, name: "delcommand", parser: "parse_cmd_common"},
-	{flags: "NEEDARG|WORD1|CMDWIN", minlen: 4, name: "delfunction", parser: "parse_cmd_delfunction"},
+	{flags: "BANG|NEEDARG|WORD1|CMDWIN", minlen: 4, name: "delfunction", parser: "parse_cmd_delfunction"},
 	{flags: "BANG|TRLBAR", minlen: 3, name: "diffupdate", parser: "parse_cmd_common"},
 	{flags: "RANGE|EXTRA|TRLBAR|MODIFY", minlen: 5, name: "diffget", parser: "parse_cmd_common"},
 	{flags: "BANG|TRLBAR", minlen: 5, name: "diffoff", parser: "parse_cmd_common"},
@@ -154,6 +154,7 @@ var builtin_commands = []*Cmd{
 	{flags: "TRLBAR|SBOXOK|CMDWIN", minlen: 4, name: "endtry", parser: "parse_cmd_endtry"},
 	{flags: "TRLBAR|SBOXOK|CMDWIN", minlen: 4, name: "endwhile", parser: "parse_cmd_endwhile"},
 	{flags: "BANG|TRLBAR", minlen: 3, name: "enew", parser: "parse_cmd_common"},
+	{flags: "EXTRA|NOTRLCOM|SBOXOK|CMDWIN", minlen: 2, name: "eval", parser: "parse_cmd_eval"},
 	{flags: "BANG|FILE1|EDITCMD|ARGOPT|TRLBAR", minlen: 2, name: "ex", parser: "parse_cmd_common"},
 	{flags: "EXTRA|NOTRLCOM|SBOXOK|CMDWIN", minlen: 3, name: "execute", parser: "parse_cmd_execute"},
 	{flags: "RANGE|WHOLEFOLD|BANG|FILE1|ARGOPT|DFLALL|TRLBAR|CMDWIN", minlen: 3, name: "exit", parser: "parse_cmd_common"},
@@ -229,6 +230,7 @@ var builtin_commands = []*Cmd{
 	{flags: "TRLBAR|RANGE|WHOLEFOLD|EXTRA|CMDWIN|MODIFY", minlen: 2, name: "left", parser: "parse_cmd_common"},
 	{flags: "NEEDARG|EXTRA|NOTRLCOM", minlen: 5, name: "leftabove", parser: "parse_cmd_common"},
 	{flags: "EXTRA|NOTRLCOM|SBOXOK|CMDWIN", minlen: 3, name: "let", parser: "parse_cmd_let"},
+	{flags: "EXTRA|NOTRLCOM|SBOXOK|CMDWIN", minlen: 4, name: "const", parser: "parse_cmd_const"},
 	{flags: "NEEDARG|WORD1|NOTRLCOM|TRLBAR|BANG", minlen: 3, name: "lexpr", parser: "parse_cmd_common"},
 	{flags: "TRLBAR|FILE1|BANG", minlen: 2, name: "lfile", parser: "parse_cmd_common"},
 	{flags: "RANGE|NOTADR|COUNT|TRLBAR|BANG", minlen: 4, name: "lfirst", parser: "parse_cmd_common"},
@@ -564,4 +566,28 @@ var builtin_commands = []*Cmd{
 	{flags: "EXTRA|TRLBAR|CMDWIN", minlen: 5, name: "tmapclear", parser: "parse_cmd_common"},
 	{flags: "EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN", minlen: 3, name: "tnoremap", parser: "parse_cmd_common"},
 	{flags: "EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN", minlen: 5, name: "tunmap", parser: "parse_cmd_common"},
+	{flags: "RANGE|COUNT|TRLBAR", minlen: 4, name: "cabove", parser: "parse_cmd_common"},
+	{flags: "RANGE|COUNT|TRLBAR", minlen: 3, name: "cafter", parser: "parse_cmd_common"},
+	{flags: "RANGE|COUNT|TRLBAR", minlen: 3, name: "cbefore", parser: "parse_cmd_common"},
+	{flags: "RANGE|COUNT|TRLBAR", minlen: 4, name: "cbelow", parser: "parse_cmd_common"},
+	{flags: "EXTRA|NOTRLCOM|SBOXOK|CMDWIN", minlen: 4, name: "const", parser: "parse_cmd_common"},
+	{flags: "RANGE|COUNT|TRLBAR", minlen: 3, name: "labove", parser: "parse_cmd_common"},
+	{flags: "RANGE|COUNT|TRLBAR", minlen: 3, name: "lafter", parser: "parse_cmd_common"},
+	{flags: "RANGE|COUNT|TRLBAR", minlen: 3, name: "lbefore", parser: "parse_cmd_common"},
+	{flags: "RANGE|COUNT|TRLBAR", minlen: 4, name: "lbelow", parser: "parse_cmd_common"},
+	{flags: "TRLBAR|CMDWIN", minlen: 7, name: "redrawtabline", parser: "parse_cmd_common"},
+	{flags: "WORD1|TRLBAR|CMDWIN", minlen: 7, name: "scriptversion", parser: "parse_cmd_common"},
+	{flags: "BANG|FILE1|TRLBAR|CMDWIN", minlen: 2, name: "tcd", parser: "parse_cmd_common"},
+	{flags: "BANG|FILE1|TRLBAR|CMDWIN", minlen: 3, name: "tchdir", parser: "parse_cmd_common"},
+	{flags: "RANGE|ZEROR|EXTRA|TRLBAR|NOTRLCOM|CTRLV|CMDWIN", minlen: 3, name: "tlmenu", parser: "parse_cmd_common"},
+	{flags: "RANGE|ZEROR|EXTRA|TRLBAR|NOTRLCOM|CTRLV|CMDWIN", minlen: 3, name: "tlnoremenu", parser: "parse_cmd_common"},
+	{flags: "RANGE|ZEROR|EXTRA|TRLBAR|NOTRLCOM|CTRLV|CMDWIN", minlen: 3, name: "tlunmenu", parser: "parse_cmd_common"},
+	{flags: "EXTRA|TRLBAR|CMDWIN", minlen: 2, name: "xrestore", parser: "parse_cmd_common"},
+	{flags: "EXTRA|BANG|SBOXOK|CMDWIN", minlen: 3, name: "def", parser: "parse_cmd_common"},
+	{flags: "EXTRA|NEEDARG|TRLBAR|CMDWIN", minlen: 4, name: "disassemble", parser: "parse_cmd_common"},
+	{flags: "TRLBAR|CMDWIN", minlen: 4, name: "enddef", parser: "parse_cmd_common"},
+	{flags: "EXTRA|NOTRLCOM", minlen: 3, name: "export", parser: "parse_cmd_common"},
+	{flags: "EXTRA|NOTRLCOM", minlen: 3, name: "import", parser: "parse_cmd_common"},
+	{flags: "BANG|RANGE|NEEDARG|EXTRA|TRLBAR", minlen: 7, name: "spellrare", parser: "parse_cmd_common"},
+	{flags: "", minlen: 4, name: "vim9script", parser: "parse_cmd_common"},
 }
diff --git a/go/export.go b/go/export.go
index 001ed23..dae8c2c 100644
--- a/go/export.go
+++ b/go/export.go
@@ -69,6 +69,7 @@ func newAstNode(n *VimNode, filename string) ast.Node {
 			Body:        newBody(*n, filename),
 			Name:        newExprNode(n.left, filename),
 			Params:      newIdents(*n, filename),
+			DefaultArgs: newExprs(n.default_args, filename),
 			Attr:        attr,
 			EndFunction: newAstNode(n.endfunction, filename).(*ast.EndFunction),
 		}
@@ -100,13 +101,13 @@ func newAstNode(n *VimNode, filename string) ast.Node {
 			FuncCall: newAstNode(n.left, filename).(*ast.CallExpr),
 		}
 
-	case NODE_LET:
+	case NODE_LET, NODE_CONST:
 		return &ast.Let{
 			Let:   pos,
 			ExArg: newExArg(*n.ea, filename),
 			Op:    n.op,
 			Left:  newExprNode(n.left, filename),
-			List:  newList(*n, filename),
+			List:  newExprs(n.list, filename),
 			Rest:  newExprNode(n.rest, filename),
 			Right: newExprNode(n.right, filename),
 		}
@@ -115,7 +116,7 @@ func newAstNode(n *VimNode, filename string) ast.Node {
 		return &ast.UnLet{
 			UnLet: pos,
 			ExArg: newExArg(*n.ea, filename),
-			List:  newList(*n, filename),
+			List:  newExprs(n.list, filename),
 		}
 
 	case NODE_LOCKVAR:
@@ -123,7 +124,7 @@ func newAstNode(n *VimNode, filename string) ast.Node {
 			LockVar: pos,
 			ExArg:   newExArg(*n.ea, filename),
 			Depth:   n.depth,
-			List:    newList(*n, filename),
+			List:    newExprs(n.list, filename),
 		}
 
 	case NODE_UNLOCKVAR:
@@ -131,7 +132,7 @@ func newAstNode(n *VimNode, filename string) ast.Node {
 			UnLockVar: pos,
 			ExArg:     newExArg(*n.ea, filename),
 			Depth:     n.depth,
-			List:      newList(*n, filename),
+			List:      newExprs(n.list, filename),
 		}
 
 	case NODE_IF:
@@ -200,7 +201,7 @@ func newAstNode(n *VimNode, filename string) ast.Node {
 			ExArg:  newExArg(*n.ea, filename),
 			Body:   newBody(*n, filename),
 			Left:   newExprNode(n.left, filename),
-			List:   newList(*n, filename),
+			List:   newExprs(n.list, filename),
 			Rest:   newExprNode(n.rest, filename),
 			Right:  newExprNode(n.right, filename),
 			EndFor: newAstNode(n.endfor, filename).(*ast.EndFor),
@@ -275,12 +276,19 @@ func newAstNode(n *VimNode, filename string) ast.Node {
 			Expr:  newExprNode(n.left, filename),
 		}
 
+	case NODE_EVAL:
+		return &ast.Eval{
+			Eval:  pos,
+			ExArg: newExArg(*n.ea, filename),
+			Expr:  newExprNode(n.left, filename),
+		}
+
 	case NODE_ECHO, NODE_ECHON, NODE_ECHOMSG, NODE_ECHOERR:
 		return &ast.EchoCmd{
 			Start:   pos,
 			CmdName: n.ea.cmd.name,
 			ExArg:   newExArg(*n.ea, filename),
-			Exprs:   newList(*n, filename),
+			Exprs:   newExprs(n.list, filename),
 		}
 
 	case NODE_ECHOHL:
@@ -294,7 +302,7 @@ func newAstNode(n *VimNode, filename string) ast.Node {
 		return &ast.Execute{
 			Execute: pos,
 			ExArg:   newExArg(*n.ea, filename),
-			Exprs:   newList(*n, filename),
+			Exprs:   newExprs(n.list, filename),
 		}
 
 	case NODE_TERNARY:
@@ -343,11 +351,19 @@ func newAstNode(n *VimNode, filename string) ast.Node {
 			High:   newExprNode(n.rlist[1], filename),
 		}
 
+	case NODE_METHOD:
+		return &ast.MethodExpr{
+			Lparen: pos,
+			Left:   newExprNode(n.left, filename),
+			Method: newExprNode(n.right.left, filename),
+			Args:   newExprs(n.right.rlist, filename),
+		}
+
 	case NODE_CALL:
 		return &ast.CallExpr{
 			Lparen: pos,
 			Fun:    newExprNode(n.left, filename),
-			Args:   newRlist(*n, filename),
+			Args:   newExprs(n.rlist, filename),
 		}
 
 	case NODE_DOT:
@@ -446,6 +462,21 @@ func newAstNode(n *VimNode, filename string) ast.Node {
 			Expr:        newExprNode(n.left, filename),
 		}
 
+	case NODE_BLOB:
+		return &ast.BasicLit{
+			ValuePos: pos,
+			Kind:     token.BLOB,
+			Value:    n.value.(string),
+		}
+
+	case NODE_HEREDOC:
+		return &ast.HeredocExpr{
+			OpPos:     pos,
+			Flags:     newExprs(n.rlist, filename),
+			EndMarker: n.op,
+			Body:      newExprs(n.body, filename),
+		}
+
 	case NODE_PARENEXPR:
 		n := n.value.(*VimNode)
 		return &ast.ParenExpr{
@@ -541,25 +572,12 @@ func newIdents(n VimNode, filename string) []*ast.Ident {
 	return idents
 }
 
-func newRlist(n VimNode, filename string) []ast.Expr {
-	var exprs []ast.Expr
-	if n.rlist != nil {
-		exprs = make([]ast.Expr, 0, len(n.rlist))
-	}
-	for _, node := range n.rlist {
-		if node != nil { // conservative
-			exprs = append(exprs, newExprNode(node, filename))
-		}
-	}
-	return exprs
-}
-
-func newList(n VimNode, filename string) []ast.Expr {
+func newExprs(xs []*VimNode, filename string) []ast.Expr {
 	var list []ast.Expr
-	if n.list != nil {
-		list = make([]ast.Expr, 0, len(n.list))
+	if xs != nil {
+		list = make([]ast.Expr, 0, len(xs))
 	}
-	for _, node := range n.list {
+	for _, node := range xs {
 		if node != nil { // conservative
 			list = append(list, newExprNode(node, filename))
 		}
diff --git a/go/gocompiler.vim b/go/gocompiler.vim
index 28f088a..fbc0cbb 100644
--- a/go/gocompiler.vim
+++ b/go/gocompiler.vim
@@ -452,24 +452,13 @@ function s:GoCompiler.compile_let(node)
     elseif left =~ '^\v(self\.(find_command_cache|cache|buf|pos|context)|toplevel.body|lhs.list|(node\.(body|attr|else_|elseif|catch|finally|pattern|end(function|if|for|try))))$' && op == '='
       " skip initialization
       return
-    elseif left =~ '^\v(node\.(list|depth))$' && op == '='
-      if right == 'nil' || right == '[]interface{}{}'
-        return
-      endif
-      call self.out('%s %s %s', left, op, right)
+    elseif left =~ '^\v%(node\.%(r?list|default_args|body))$' && op == '='
+      call self.out('%s %s %s', left, op, substitute(right, '[]interface{}', '[]*VimNode', 'g'))
       return
-    elseif left =~ 'node.rlist' && op == '='
-      if right == '[]interface{}{}'
-        return
-      endif
-      let m = matchstr(right, '\V[]interface{}{\zs\.\*\ze}\$')
-      if m != ''
-        call self.out('%s = []*VimNode{%s}', left, m)
-      else
-        call self.out('%s = %s', left, right)
-      endif
+    elseif left == 'node.depth' && op == '=' && right == 'nil'
+      call self.out('%s %s %s', left, op, '0')
       return
-    elseif left =~ '^\v(list|curly_parts)$' && op == '=' && right == '[]interface{}{}'
+    elseif left =~ '^\v(r?list|curly_parts)$' && op == '=' && right == '[]interface{}{}'
       call self.out('var %s []*VimNode', left)
       return
     elseif left == 'cmd' && op == '=' && (right == 'nil' || right =~ '^\Vmap[string]interface{}{')
@@ -526,7 +515,7 @@ function s:GoCompiler.compile_let(node)
 endfunction
 
 function s:GoCompiler.compile_unlet(node)
-  echom 'NotImplemented: unlet'
+  " echom 'NotImplemented: unlet'
 endfunction
 
 function s:GoCompiler.compile_lockvar(node)
@@ -572,7 +561,9 @@ function s:GoCompiler.compile_while(node)
   endif
   call self.out('for %s{', cond)
   call self.incindent("\t")
+  call self.inscope()
   call self.compile_body(a:node.body)
+  call self.descope()
   call self.decindent()
   call self.out('}')
 endfunction
@@ -651,6 +642,10 @@ function s:GoCompiler.compile_ternary(node)
   let right = self.compile(a:node.right)
   if cond =~ '^node\.rlist\[\d\]' && left == '"nil"'
     return printf('func() string { if %s {return %s} else {return %s.(string)} }()', cond, left, right)
+  elseif cond =~ '^viml_empty' && left == '"(list)"'
+    return printf('func() string { if %s {return %s} else {return %s} }()', cond, left, right)
+  elseif cond == 'is_litdict'
+    return printf('func() *VimNode { if %s {return %s} else {return %s} }()', cond, left, right)
   else
     return printf('viml_ternary(%s, %s, %s)', cond, left, right)
   endif
@@ -769,7 +764,7 @@ function s:GoCompiler.compile_isci(node)
 endfunction
 
 function s:GoCompiler.compile_iscs(node)
-  throw 'NotImplemented: is#'
+  return self.compile_op2(a:node, '==')
 endfunction
 
 function s:GoCompiler.compile_isnot(node)
@@ -781,7 +776,7 @@ function s:GoCompiler.compile_isnotci(node)
 endfunction
 
 function s:GoCompiler.compile_isnotcs(node)
-  throw 'NotImplemented: isnot#'
+  return self.compile_op2(a:node, '!=')
 endfunction
 
 function s:GoCompiler.compile_add(node)
@@ -848,7 +843,6 @@ function s:GoCompiler.compile_call(node)
   let rlist = map(a:node.rlist, 'self.compile(v:val)')
   let left = self.compile(a:node.left)
   if left == 'map' && len(rlist) == 2 && rlist[1] == '"self.compile(v:val)"'
-    " throw 'NotImplemented: map()'
     return printf(join([
     \   'func() []string {',
     \   'var ss []string',
@@ -858,12 +852,26 @@ function s:GoCompiler.compile_call(node)
     \   'return ss',
     \   '}()',
     \ ], ";"), rlist[0], substitute(rlist[1][1:-2], 'v:val', 'vval', 'g'))
+  elseif left == 'map' && len(rlist) == 2 && rlist[1] == '"self.escape_string(v:val.value)"'
+    return printf(join([
+    \   'func() []string {',
+    \   'var ss []string',
+    \   'for _, vval := range %s {',
+    \   'ss = append(ss, %s)',
+    \   '}',
+    \   'return ss',
+    \   '}()',
+    \ ], ";"), rlist[0], substitute(rlist[1][1:-2], 'v:val\.value', 'vval.value.(string)', 'g'))
   elseif left == 'call' && rlist[0][0] =~ '[''"]'
     return printf('viml_%s(*%s)', rlist[0][1:-2], rlist[1])
   elseif left =~ 'ExArg'
     return printf('&%s{}', left)
+  elseif left == 'remove' && len(rlist) == 2 && rlist[1] == '-1'
+    return printf('%s = %s[:len(%s)-1]', rlist[0], rlist[0], rlist[0])
   elseif left == 'isvarname' && len(rlist) == 1 && rlist[0] == 'node.value'
     return printf('%s(%s.(string))', left, rlist[0])
+  elseif left == 'islower' && len(rlist) == 1 && rlist[0] == 'key[0]'
+    return printf('%s(key[:1])', left)
   elseif left == 'self.reader.seek_set' && len(rlist) == 1 && rlist[0] == 'x[0]'
     return printf('%s(%s.(int))', left, rlist[0])
   elseif left == 'self.compile' && len(rlist) == 1 && rlist[0] =~ '\v^node\.(left|rest)$'
@@ -882,6 +890,9 @@ function s:GoCompiler.compile_call(node)
     endif
   endif
   if left == 'range_'
+    if len(rlist) == 1
+      let rlist = ['0', rlist[0] . ' - 1']
+    endif
     let left = 'viml_range'
   endif
   return printf('%s(%s)', left, join(rlist, ', '))
@@ -991,7 +1002,7 @@ function s:GoCompiler.compile_op2(node, op)
   return printf('%s %s %s', left, a:op, right)
 endfunction
 
-let s:viml_builtin_functions = ['abs', 'acos', 'add', 'and', 'append', 'append', 'argc', 'argidx', 'argv', 'argv', 'asin', 'atan', 'atan2', 'browse', 'browsedir', 'bufexists', 'buflisted', 'bufloaded', 'bufname', 'bufnr', 'bufwinnr', 'byte2line', 'byteidx', 'call', 'ceil', 'changenr', 'char2nr', 'cindent', 'clearmatches', 'col', 'complete', 'complete_add', 'complete_check', 'confirm', 'copy', 'cos', 'cosh', 'count', 'cscope_connection', 'cursor', 'cursor', 'deepcopy', 'delete', 'did_filetype', 'diff_filler', 'diff_hlID', 'empty', 'escape', 'eval', 'eventhandler', 'executable', 'exists', 'extend', 'exp', 'expand', 'feedkeys', 'filereadable', 'filewritable', 'filter', 'finddir', 'findfile', 'float2nr', 'floor', 'fmod', 'fnameescape', 'fnamemodify', 'foldclosed', 'foldclosedend', 'foldlevel', 'foldtext', 'foldtextresult', 'foreground', 'function', 'garbagecollect', 'get', 'get', 'getbufline', 'getbufvar', 'getchar', 'getcharmod', 'getcmdline', 'getcmdpos', 'getcmdtype', 'getcwd', 'getfperm', 'getfsize', 'getfontname', 'getftime', 'getftype', 'getline', 'getline', 'getloclist', 'getmatches', 'getpid', 'getpos', 'getqflist', 'getreg', 'getregtype', 'gettabvar', 'gettabwinvar', 'getwinposx', 'getwinposy', 'getwinvar', 'glob', 'globpath', 'has', 'has_key', 'haslocaldir', 'hasmapto', 'histadd', 'histdel', 'histget', 'histnr', 'hlexists', 'hlID', 'hostname', 'iconv', 'indent', 'index', 'input', 'inputdialog', 'inputlist', 'inputrestore', 'inputsave', 'inputsecret', 'insert', 'invert', 'isdirectory', 'islocked', 'items', 'join', 'keys', 'len', 'libcall', 'libcallnr', 'line', 'line2byte', 'lispindent', 'localtime', 'log', 'log10', 'luaeval', 'map', 'maparg', 'mapcheck', 'match', 'matchadd', 'matcharg', 'matchdelete', 'matchend', 'matchlist', 'matchstr', 'max', 'min', 'mkdir', 'mode', 'mzeval', 'nextnonblank', 'nr2char', 'or', 'pathshorten', 'pow', 'prevnonblank', 'printf', 'pumvisible', 'pyeval', 'py3eval', 'range', 'readfile', 'reltime', 'reltimestr', 'remote_expr', 'remote_foreground', 'remote_peek', 'remote_read', 'remote_send', 'remove', 'remove', 'rename', 'repeat', 'resolve', 'reverse', 'round', 'screencol', 'screenrow', 'search', 'searchdecl', 'searchpair', 'searchpairpos', 'searchpos', 'server2client', 'serverlist', 'setbufvar', 'setcmdpos', 'setline', 'setloclist', 'setmatches', 'setpos', 'setqflist', 'setreg', 'settabvar', 'settabwinvar', 'setwinvar', 'sha256', 'shellescape', 'shiftwidth', 'simplify', 'sin', 'sinh', 'sort', 'soundfold', 'spellbadword', 'spellsuggest', 'split', 'sqrt', 'str2float', 'str2nr', 'strchars', 'strdisplaywidth', 'strftime', 'stridx', 'string', 'strlen', 'strpart', 'strridx', 'strtrans', 'strwidth', 'submatch', 'substitute', 'synID', 'synIDattr', 'synIDtrans', 'synconcealed', 'synstack', 'system', 'tabpagebuflist', 'tabpagenr', 'tabpagewinnr', 'taglist', 'tagfiles', 'tempname', 'tan', 'tanh', 'tolower', 'toupper', 'tr', 'trunc', 'type', 'undofile', 'undotree', 'values', 'virtcol', 'visualmode', 'wildmenumode', 'winbufnr', 'wincol', 'winheight', 'winline', 'winnr', 'winrestcmd', 'winrestview', 'winsaveview', 'winwidth', 'writefile', 'xor']
+let s:viml_builtin_functions = map(copy(s:VimLParser.builtin_functions), 'v:val.name')
 
 function! s:test()
   let vimfile = 'autoload/vimlparser.vim'
diff --git a/go/type.go b/go/type.go
index eb19360..e34d543 100644
--- a/go/type.go
+++ b/go/type.go
@@ -40,19 +40,20 @@ type Cmd struct {
 }
 
 type VimNode struct {
-	type_ int // type -> type_
-	pos   *pos
-	left  *VimNode
-	right *VimNode
-	cond  *VimNode
-	rest  *VimNode
-	list  []*VimNode
-	rlist []*VimNode
-	body  []*VimNode
-	op    string
-	str   string
-	depth int
-	value interface{}
+	type_        int // type -> type_
+	pos          *pos
+	left         *VimNode
+	right        *VimNode
+	cond         *VimNode
+	rest         *VimNode
+	list         []*VimNode
+	rlist        []*VimNode
+	default_args []*VimNode
+	body         []*VimNode
+	op           string
+	str          string
+	depth        int
+	value        interface{}
 
 	ea   *ExArg
 	attr *FuncAttr
diff --git a/go/typedefs.vim b/go/typedefs.vim
index 149690a..c53be39 100644
--- a/go/typedefs.vim
+++ b/go/typedefs.vim
@@ -61,6 +61,10 @@ call extend(s:typedefs.func, {
 \     'in': [],
 \     'out': ['*pos'],
 \   },
+\   'VimLParser.parse_heredoc': {
+\     'in': [],
+\     'out': ['*VimNode'],
+\   },
 \   'VimLParser.parse_expr': {
 \     'in': [],
 \     'out': ['*VimNode'],
@@ -77,6 +81,10 @@ call extend(s:typedefs.func, {
 \     'in': [],
 \     'out': ['*VimNode'],
 \   },
+\   'VimLParser.parse_constlvalue': {
+\     'in': [],
+\     'out': ['*VimNode'],
+\   },
 \   'VimLParser.parse_lvaluelist': {
 \     'in': [],
 \     'out': ['[]*VimNode'],
@@ -85,6 +93,10 @@ call extend(s:typedefs.func, {
 \     'in': [],
 \     'out': ['*lhs'],
 \   },
+\   'VimLParser.parse_constlhs': {
+\     'in': [],
+\     'out': ['*lhs'],
+\   },
 \   'VimLParser.ends_excmds': {
 \     'in': ['string'],
 \     'out': ['bool'],
@@ -110,6 +122,7 @@ call extend(s:typedefs.func, {
 \   'ExprTokenizer.get2': { 'in': [], 'out': ['*ExprToken'] },
 \   'ExprTokenizer.get_sstring': { 'in': [], 'out': ['string'] },
 \   'ExprTokenizer.get_dstring': { 'in': [], 'out': ['string'] },
+\   'ExprTokenizer.parse_dict_literal_key': { 'in': [], 'out': ['*VimNode'] },
 \ })
 
 call extend(s:typedefs.func, {
@@ -127,7 +140,9 @@ call extend(s:typedefs.func, {
 \   'ExprParser.parse_expr7': { 'in': [], 'out': ['*VimNode'] },
 \   'ExprParser.parse_expr8': { 'in': [], 'out': ['*VimNode'] },
 \   'ExprParser.parse_expr9': { 'in': [], 'out': ['*VimNode'] },
+\   'ExprParser.parse_rlist': { 'in': [], 'out': ['[]*VimNode'] },
 \   'ExprParser.parse_dot': { 'in': ['*ExprToken', '*VimNode'], 'out': ['*VimNode'] },
+\   'ExprParser.parse_concat': { 'in': ['*ExprToken', '*VimNode'], 'out': ['*VimNode'] },
 \   'ExprParser.parse_identifier': { 'in': [], 'out': ['*VimNode'] },
 \   'ExprParser.parse_curly_parts': { 'in': [], 'out': ['[]*VimNode'] },
 \ })
@@ -207,7 +222,9 @@ call extend(s:typedefs.func, {
 \   'StringReader.read_alnum': { 'in': [], 'out': ['string'] },
 \   'StringReader.read_digit': { 'in': [], 'out': ['string'] },
 \   'StringReader.read_odigit': { 'in': [], 'out': ['string'] },
+\   'StringReader.read_blob': { 'in': [], 'out': ['string'] },
 \   'StringReader.read_xdigit': { 'in': [], 'out': ['string'] },
+\   'StringReader.read_bdigit': { 'in': [], 'out': ['string'] },
 \   'StringReader.read_integer': { 'in': [], 'out': ['string'] },
 \   'StringReader.read_word': { 'in': [], 'out': ['string'] },
 \   'StringReader.read_white': { 'in': [], 'out': ['string'] },
@@ -242,7 +259,9 @@ call extend(s:typedefs.func, {
 \   'Compiler.compile_delfunction': { 'in': ['*VimNode'], 'out': [] },
 \   'Compiler.compile_return': { 'in': ['*VimNode'], 'out': [] },
 \   'Compiler.compile_excall': { 'in': ['*VimNode'], 'out': [] },
+\   'Compiler.compile_eval': { 'in': ['*VimNode'], 'out': [] },
 \   'Compiler.compile_let': { 'in': ['*VimNode'], 'out': [] },
+\   'Compiler.compile_const': { 'in': ['*VimNode'], 'out': [] },
 \   'Compiler.compile_unlet': { 'in': ['*VimNode'], 'out': [] },
 \   'Compiler.compile_lockvar': { 'in': ['*VimNode'], 'out': [] },
 \   'Compiler.compile_unlockvar': { 'in': ['*VimNode'], 'out': [] },
@@ -305,8 +324,10 @@ call extend(s:typedefs.func, {
 \   'Compiler.compile_subscript': { 'in': ['*VimNode'], 'out': ['string'] },
 \   'Compiler.compile_slice': { 'in': ['*VimNode'], 'out': ['string'] },
 \   'Compiler.compile_dot': { 'in': ['*VimNode'], 'out': ['string'] },
+\   'Compiler.compile_method': { 'in': ['*VimNode'], 'out': ['string'] },
 \   'Compiler.compile_call': { 'in': ['*VimNode'], 'out': ['string'] },
 \   'Compiler.compile_number': { 'in': ['*VimNode'], 'out': ['string'] },
+\   'Compiler.compile_blob': { 'in': ['*VimNode'], 'out': ['string'] },
 \   'Compiler.compile_string': { 'in': ['*VimNode'], 'out': ['string'] },
 \   'Compiler.compile_list': { 'in': ['*VimNode'], 'out': ['string'] },
 \   'Compiler.compile_dict': { 'in': ['*VimNode'], 'out': ['string'] },
@@ -317,7 +338,9 @@ call extend(s:typedefs.func, {
 \   'Compiler.compile_reg': { 'in': ['*VimNode'], 'out': ['string'] },
 \   'Compiler.compile_curlynamepart': { 'in': ['*VimNode'], 'out': ['string'] },
 \   'Compiler.compile_curlynameexpr': { 'in': ['*VimNode'], 'out': ['string'] },
+\   'Compiler.escape_string': { 'in': ['string'], 'out': ['string'] },
 \   'Compiler.compile_lambda': { 'in': ['*VimNode'], 'out': ['string'] },
+\   'Compiler.compile_heredoc': { 'in': ['*VimNode'], 'out': ['string'] },
 \   'Compiler.compile_parenexpr': { 'in': ['*VimNode'], 'out': ['string'] },
 \ })
 
diff --git a/go/vimlfunc.go b/go/vimlfunc.go
index 26cd194..8896232 100644
--- a/go/vimlfunc.go
+++ b/go/vimlfunc.go
@@ -20,31 +20,31 @@ var patVim2Go = map[string]string{
 	"\\<USECTRLV\\>":                    "\\bUSECTRLV\\b",
 	"\\<USERCMD\\>":                     "\\bUSERCMD\\b",
 	"\\<\\(XFILE\\|FILES\\|FILE1\\)\\>": "\\b(XFILE|FILES|FILE1)\\b",
-	"\\S":                                      "\\S",
-	"\\a":                                      "[A-Za-z]",
-	"\\d":                                      "\\d",
-	"\\h":                                      "[A-Za-z_]",
-	"\\s":                                      "\\s",
-	"\\v^d%[elete][lp]$":                       "^d(elete|elet|ele|el|e)[lp]$",
+	"\\S":                               "\\S",
+	"\\a":                               "[A-Za-z]",
+	"\\d":                               "\\d",
+	"\\h":                               "[A-Za-z_]",
+	"\\s":                               "\\s",
+	"\\v^d%[elete][lp]$":                "^d(elete|elet|ele|el|e)[lp]$",
 	"\\v^s%(c[^sr][^i][^p]|g|i[^mlg]|I|r[^e])": "^s(c[^sr][^i][^p]|g|i[^mlg]|I|r[^e])",
-	"\\w":        "[0-9A-Za-z_]",
-	"\\w\\|[:#]": "[0-9A-Za-z_]|[:#]",
-	"\\x":        "[0-9A-Fa-f]",
-	"^++":        "^\\+\\+",
-	"^++bad=\\(keep\\|drop\\|.\\)\\>":                       "^\\+\\+bad=(keep|drop|.)\\b",
-	"^++bad=drop":                                           "^\\+\\+bad=drop",
-	"^++bad=keep":                                           "^\\+\\+bad=keep",
-	"^++bin\\>":                                             "^\\+\\+bin\\b",
-	"^++edit\\>":                                            "^\\+\\+edit\\b",
-	"^++enc=\\S":                                            "^\\+\\+enc=\\S",
-	"^++encoding=\\S":                                       "^\\+\\+encoding=\\S",
-	"^++ff=\\(dos\\|unix\\|mac\\)\\>":                       "^\\+\\+ff=(dos|unix|mac)\\b",
-	"^++fileformat=\\(dos\\|unix\\|mac\\)\\>":               "^\\+\\+fileformat=(dos|unix|mac)\\b",
-	"^++nobin\\>":                                           "^\\+\\+nobin\\b",
-	"^[A-Z]":                                                "^[A-Z]",
-	"^\\$\\w\\+":                                            "^\\$[0-9A-Za-z_]+",
-	"^\\(!\\|global\\|vglobal\\)$":                          "^(!|global|vglobal)$",
-	"^\\(WHILE\\|FOR\\)$":                                   "^(WHILE|FOR)$",
+	"\\w":                             "[0-9A-Za-z_]",
+	"\\w\\|[:#]":                      "[0-9A-Za-z_]|[:#]",
+	"\\x":                             "[0-9A-Fa-f]",
+	"^++":                             "^\\+\\+",
+	"^++bad=\\(keep\\|drop\\|.\\)\\>": "^\\+\\+bad=(keep|drop|.)\\b",
+	"^++bad=drop":                     "^\\+\\+bad=drop",
+	"^++bad=keep":                     "^\\+\\+bad=keep",
+	"^++bin\\>":                       "^\\+\\+bin\\b",
+	"^++edit\\>":                      "^\\+\\+edit\\b",
+	"^++enc=\\S":                      "^\\+\\+enc=\\S",
+	"^++encoding=\\S":                 "^\\+\\+encoding=\\S",
+	"^++ff=\\(dos\\|unix\\|mac\\)\\>": "^\\+\\+ff=(dos|unix|mac)\\b",
+	"^++fileformat=\\(dos\\|unix\\|mac\\)\\>": "^\\+\\+fileformat=(dos|unix|mac)\\b",
+	"^++nobin\\>":                  "^\\+\\+nobin\\b",
+	"^[A-Z]":                       "^[A-Z]",
+	"^\\$\\w\\+":                   "^\\$[0-9A-Za-z_]+",
+	"^\\(!\\|global\\|vglobal\\)$": "^(!|global|vglobal)$",
+	"^\\(WHILE\\|FOR\\)$":          "^(WHILE|FOR)$",
 	"^\\(vimgrep\\|vimgrepadd\\|lvimgrep\\|lvimgrepadd\\)$": "^(vimgrep|vimgrepadd|lvimgrep|lvimgrepadd)$",
 	"^\\d":                     "^\\d",
 	"^\\h":                     "^[A-Za-z_]",
@@ -62,7 +62,11 @@ var patVim2Go = map[string]string{
 	"^[A-Z]$":                  "^[A-Z]$",
 	"^[a-z]$":                  "^[a-z]$",
 	"^[vgslabwt]:$\\|^\\([vgslabwt]:\\)\\?[A-Za-z_][0-9A-Za-z_#]*$": "^[vgslabwt]:$|^([vgslabwt]:)?[A-Za-z_][0-9A-Za-z_#]*$",
-	"^[0-7]$": "^[0-7]$",
+	"^[0-7]$":                   "^[0-7]$",
+	"^[0-9A-Fa-f][0-9A-Fa-f]$":  "^[0-9A-Fa-f][0-9A-Fa-f]$",
+	"^\\.[0-9A-Fa-f]$":          "^\\.[0-9A-Fa-f]$",
+	"^[0-9A-Fa-f][^0-9A-Fa-f]$": "^[0-9A-Fa-f][^0-9A-Fa-f]$",
+	"^[^a-z]\\S\\+$":            "^[^a-z]\\S+$",
 }
 
 var patVim2GoRegh = make(map[string]*regexp.Regexp)
diff --git a/go/vimlparser.go b/go/vimlparser.go
index d52d5fd..d642d7a 100644
--- a/go/vimlparser.go
+++ b/go/vimlparser.go
@@ -95,7 +95,12 @@ var NODE_REG = 89
 var NODE_CURLYNAMEPART = 90
 var NODE_CURLYNAMEEXPR = 91
 var NODE_LAMBDA = 92
-var NODE_PARENEXPR = 93
+var NODE_BLOB = 93
+var NODE_CONST = 94
+var NODE_EVAL = 95
+var NODE_HEREDOC = 96
+var NODE_METHOD = 97
+var NODE_PARENEXPR = 200
 var TOKEN_EOF = 1
 var TOKEN_EOL = 2
 var TOKEN_SPACE = 3
@@ -161,6 +166,10 @@ var TOKEN_BACKTICK = 62
 var TOKEN_DOTDOTDOT = 63
 var TOKEN_SHARP = 64
 var TOKEN_ARROW = 65
+var TOKEN_BLOB = 66
+var TOKEN_LITCOPEN = 67
+var TOKEN_DOTDOT = 68
+var TOKEN_HEREDOC = 69
 var MAX_FUNC_ARGS = 20
 
 func isalpha(c string) bool {
@@ -233,6 +242,7 @@ func islower(c string) bool {
 //   node    rest
 //   node[]  list
 //   node[]  rlist
+//   node[]  default_args
 //   node[]  body
 //   string  op
 //   string  str
@@ -242,12 +252,13 @@ func islower(c string) bool {
 // TOPLEVEL .body
 // COMMENT .str
 // EXCMD .ea .str
-// FUNCTION .ea .body .left .rlist .attr .endfunction
+// FUNCTION .ea .body .left .rlist .default_args .attr .endfunction
 // ENDFUNCTION .ea
 // DELFUNCTION .ea .left
 // RETURN .ea .left
 // EXCALL .ea .left
 // LET .ea .op .left .list .rest .right
+// CONST .ea .op .left .list .rest .right
 // UNLET .ea .list
 // LOCKVAR .ea .depth .list
 // UNLOCKVAR .ea .depth .list
@@ -266,6 +277,7 @@ func islower(c string) bool {
 // FINALLY .ea .body
 // ENDTRY .ea
 // THROW .ea .left
+// EVAL .ea .left
 // ECHO .ea .list
 // ECHON .ea .list
 // ECHOHL .ea .str
@@ -316,12 +328,14 @@ func islower(c string) bool {
 // PLUS .left
 // SUBSCRIPT .left .right
 // SLICE .left .rlist
+// METHOD .left .right
 // CALL .left .rlist
 // DOT .left .right
 // NUMBER .value
 // STRING .value
 // LIST .value
 // DICT .value
+// BLOB .value
 // NESTING .left
 // OPTION .value
 // IDENTIFIER .value
@@ -331,6 +345,7 @@ func islower(c string) bool {
 // CURLYNAMEPART .value
 // CURLYNAMEEXPR .value
 // LAMBDA .rlist .left
+// HEREDOC .rlist .op .body
 // PARENEXPR .value
 func (self *VimLParser) find_context(type_ int) int {
 	var i = 0
@@ -380,6 +395,7 @@ func (self *VimLParser) check_missing_endfor(ends string, pos *pos) {
 func (self *VimLParser) parse(reader *StringReader) *VimNode {
 	self.reader = reader
 	var toplevel = Node(NODE_TOPLEVEL)
+	toplevel.pos = self.reader.getpos()
 	self.push_context(toplevel)
 	for self.reader.peek() != "<EOF>" {
 		self.parse_one_cmd()
@@ -458,7 +474,7 @@ func (self *VimLParser) parse_command_modifiers() {
 			// keepp\%[atterns]
 			modifiers = append(modifiers, map[string]interface{}{"name": "keeppatterns"})
 		} else if viml_stridx("hide", k) == 0 && len(k) >= 3 {
-			//hid\%[e]
+			// hid\%[e]
 			if self.ends_excmds(c) {
 				break
 			}
@@ -476,7 +492,7 @@ func (self *VimLParser) parse_command_modifiers() {
 			// :nos\%[wapfile]
 			modifiers = append(modifiers, map[string]interface{}{"name": "noswapfile"})
 		} else if viml_stridx("rightbelow", k) == 0 && len(k) >= 6 {
-			//rightb\%[elow]
+			// rightb\%[elow]
 			modifiers = append(modifiers, map[string]interface{}{"name": "rightbelow"})
 		} else if viml_stridx("sandbox", k) == 0 && len(k) >= 3 {
 			// san\%[dbox]
@@ -690,6 +706,7 @@ func (self *VimLParser) parse_command() {
 	self._parse_command(self.ea.cmd.parser)
 }
 
+// TODO: self[a:parser]
 func (self *VimLParser) _parse_command(parser string) {
 	if parser == "parse_cmd_append" {
 		self.parse_cmd_append()
@@ -745,6 +762,8 @@ func (self *VimLParser) _parse_command(parser string) {
 		self.parse_cmd_insert()
 	} else if parser == "parse_cmd_let" {
 		self.parse_cmd_let()
+	} else if parser == "parse_cmd_const" {
+		self.parse_cmd_const()
 	} else if parser == "parse_cmd_loadkeymap" {
 		self.parse_cmd_loadkeymap()
 	} else if parser == "parse_cmd_lockvar" {
@@ -769,6 +788,8 @@ func (self *VimLParser) _parse_command(parser string) {
 		self.parse_cmd_tcl()
 	} else if parser == "parse_cmd_throw" {
 		self.parse_cmd_throw()
+	} else if parser == "parse_cmd_eval" {
+		self.parse_cmd_eval()
 	} else if parser == "parse_cmd_try" {
 		self.parse_cmd_try()
 	} else if parser == "parse_cmd_unlet" {
@@ -1199,7 +1220,7 @@ func (self *VimLParser) parse_cmd_function() {
 	if left.type_ == NODE_IDENTIFIER {
 		var s = left.value.(string)
 		var ss = viml_split(s, "\\zs")
-		if ss[0] != "<" && !isupper(ss[0]) && viml_stridx(s, ":") == -1 && viml_stridx(s, "#") == -1 {
+		if ss[0] != "<" && ss[0] != "_" && !isupper(ss[0]) && viml_stridx(s, ":") == -1 && viml_stridx(s, "#") == -1 {
 			panic(Err(viml_printf("E128: Function name must start with a capital or contain a colon: %s", s), left.pos))
 		}
 	}
@@ -1214,6 +1235,8 @@ func (self *VimLParser) parse_cmd_function() {
 	node.pos = self.ea.cmdpos
 	node.ea = self.ea
 	node.left = left
+	node.rlist = []*VimNode{}
+	node.default_args = []*VimNode{}
 	self.reader.getn(1)
 	var tokenizer = NewExprTokenizer(self.reader)
 	if tokenizer.peek().type_ == TOKEN_PCLOSE {
@@ -1233,6 +1256,12 @@ func (self *VimLParser) parse_cmd_function() {
 				varnode.pos = token.pos
 				varnode.value = token.value
 				node.rlist = append(node.rlist, varnode)
+				if tokenizer.peek().type_ == TOKEN_EQ {
+					tokenizer.get()
+					node.default_args = append(node.default_args, self.parse_expr())
+				} else if len(node.default_args) > 0 {
+					panic(Err("E989: Non-default argument follows default argument", varnode.pos))
+				}
 				// XXX: Vim doesn't skip white space before comma.  F(a ,b) => E475
 				if iswhite(self.reader.p(0)) && tokenizer.peek().type_ == TOKEN_COMMA {
 					panic(Err("E475: Invalid argument: White space is not allowed before comma", self.reader.getpos()))
@@ -1342,6 +1371,50 @@ func (self *VimLParser) parse_cmd_call() {
 	self.add_node(node)
 }
 
+func (self *VimLParser) parse_heredoc() *VimNode {
+	var node = Node(NODE_HEREDOC)
+	node.pos = self.ea.cmdpos
+	node.op = ""
+	node.rlist = []*VimNode{}
+	for true {
+		self.reader.skip_white()
+		var pos = self.reader.getpos()
+		var key = self.reader.read_word()
+		if key == "" {
+			break
+		}
+		if !islower(key[:1]) {
+			node.op = key
+			break
+		} else {
+			var keynode = Node(NODE_STRING)
+			keynode.pos = pos
+			keynode.value = key
+			node.rlist = append(node.rlist, keynode)
+		}
+	}
+	if node.op == "" {
+		panic(Err("E172: Missing marker", self.reader.getpos()))
+	}
+	self.parse_trail()
+	for true {
+		if self.reader.peek() == "<EOF>" {
+			break
+		}
+		var pos = self.reader.getpos()
+		var line = self.reader.getn(-1)
+		if line == node.op {
+			return node
+		}
+		var linenode = Node(NODE_STRING)
+		linenode.pos = pos
+		linenode.value = line
+		node.body = append(node.body, linenode)
+		self.reader.get()
+	}
+	panic(Err(viml_printf("E990: Missing end marker '%s'", node.op), self.reader.getpos()))
+}
+
 func (self *VimLParser) parse_cmd_let() {
 	var pos = self.reader.tell()
 	self.reader.skip_white()
@@ -1355,8 +1428,14 @@ func (self *VimLParser) parse_cmd_let() {
 	self.reader.skip_white()
 	var s1 = self.reader.peekn(1)
 	var s2 = self.reader.peekn(2)
+	// TODO check scriptversion?
+	if s2 == ".." {
+		s2 = self.reader.peekn(3)
+	} else if s2 == "=<" {
+		s2 = self.reader.peekn(3)
+	}
 	// :let {var-name} ..
-	if self.ends_excmds(s1) || (s2 != "+=" && s2 != "-=" && s2 != ".=" && s1 != "=") {
+	if self.ends_excmds(s1) || (s2 != "+=" && s2 != "-=" && s2 != ".=" && s2 != "..=" && s2 != "*=" && s2 != "/=" && s2 != "%=" && s2 != "=<<" && s1 != "=") {
 		self.reader.seek_set(pos)
 		self.parse_cmd_common()
 		return
@@ -1370,9 +1449,16 @@ func (self *VimLParser) parse_cmd_let() {
 	node.list = lhs.list
 	node.rest = lhs.rest
 	node.right = nil
-	if s2 == "+=" || s2 == "-=" || s2 == ".=" {
-		self.reader.getn(2)
+	if s2 == "+=" || s2 == "-=" || s2 == ".=" || s2 == "..=" || s2 == "*=" || s2 == "/=" || s2 == "%=" {
+		self.reader.getn(len(s2))
+		node.op = s2
+	} else if s2 == "=<<" {
+		self.reader.getn(len(s2))
+		self.reader.skip_white()
 		node.op = s2
+		node.right = self.parse_heredoc()
+		self.add_node(node)
+		return
 	} else if s1 == "=" {
 		self.reader.getn(1)
 		node.op = s1
@@ -1383,6 +1469,37 @@ func (self *VimLParser) parse_cmd_let() {
 	self.add_node(node)
 }
 
+func (self *VimLParser) parse_cmd_const() {
+	var pos = self.reader.tell()
+	self.reader.skip_white()
+	// :const
+	if self.ends_excmds(self.reader.peek()) {
+		self.reader.seek_set(pos)
+		self.parse_cmd_common()
+		return
+	}
+	var lhs = self.parse_constlhs()
+	self.reader.skip_white()
+	var s1 = self.reader.peekn(1)
+	// :const {var-name}
+	if self.ends_excmds(s1) || s1 != "=" {
+		self.reader.seek_set(pos)
+		self.parse_cmd_common()
+		return
+	}
+	// :const left op right
+	var node = Node(NODE_CONST)
+	node.pos = self.ea.cmdpos
+	node.ea = self.ea
+	self.reader.getn(1)
+	node.op = s1
+	node.left = lhs.left
+	node.list = lhs.list
+	node.rest = lhs.rest
+	node.right = self.parse_expr()
+	self.add_node(node)
+}
+
 func (self *VimLParser) parse_cmd_unlet() {
 	var node = Node(NODE_UNLET)
 	node.pos = self.ea.cmdpos
@@ -1395,6 +1512,8 @@ func (self *VimLParser) parse_cmd_lockvar() {
 	var node = Node(NODE_LOCKVAR)
 	node.pos = self.ea.cmdpos
 	node.ea = self.ea
+	node.depth = 0
+	node.list = []*VimNode{}
 	self.reader.skip_white()
 	if isdigit(self.reader.peekn(1)) {
 		node.depth = viml_str2nr(self.reader.read_digit(), 10)
@@ -1407,6 +1526,8 @@ func (self *VimLParser) parse_cmd_unlockvar() {
 	var node = Node(NODE_UNLOCKVAR)
 	node.pos = self.ea.cmdpos
 	node.ea = self.ea
+	node.depth = 0
+	node.list = []*VimNode{}
 	self.reader.skip_white()
 	if isdigit(self.reader.peekn(1)) {
 		node.depth = viml_str2nr(self.reader.read_digit(), 10)
@@ -1603,6 +1724,14 @@ func (self *VimLParser) parse_cmd_throw() {
 	self.add_node(node)
 }
 
+func (self *VimLParser) parse_cmd_eval() {
+	var node = Node(NODE_EVAL)
+	node.pos = self.ea.cmdpos
+	node.ea = self.ea
+	node.left = self.parse_expr()
+	self.add_node(node)
+}
+
 func (self *VimLParser) parse_cmd_echo() {
 	var node = Node(NODE_ECHO)
 	node.pos = self.ea.cmdpos
@@ -1696,6 +1825,29 @@ func (self *VimLParser) parse_lvalue() *VimNode {
 	panic(Err("Invalid Expression", node.pos))
 }
 
+// TODO: merge with s:VimLParser.parse_lvalue()
+func (self *VimLParser) parse_constlvalue() *VimNode {
+	var p = NewLvalueParser(self.reader)
+	var node = p.parse()
+	if node.type_ == NODE_IDENTIFIER {
+		if !isvarname(node.value.(string)) {
+			panic(Err(viml_printf("E461: Illegal variable name: %s", node.value), node.pos))
+		}
+	}
+	if node.type_ == NODE_IDENTIFIER || node.type_ == NODE_CURLYNAME {
+		return node
+	} else if node.type_ == NODE_SUBSCRIPT || node.type_ == NODE_SLICE || node.type_ == NODE_DOT {
+		panic(Err("E996: Cannot lock a list or dict", node.pos))
+	} else if node.type_ == NODE_OPTION {
+		panic(Err("E996: Cannot lock an option", node.pos))
+	} else if node.type_ == NODE_ENV {
+		panic(Err("E996: Cannot lock an environment variable", node.pos))
+	} else if node.type_ == NODE_REG {
+		panic(Err("E996: Cannot lock a register", node.pos))
+	}
+	panic(Err("Invalid Expression", node.pos))
+}
+
 func (self *VimLParser) parse_lvaluelist() []*VimNode {
 	var list []*VimNode
 	var node = self.parse_expr()
@@ -1744,6 +1896,39 @@ func (self *VimLParser) parse_letlhs() *lhs {
 	return lhs
 }
 
+// TODO: merge with s:VimLParser.parse_letlhs() ?
+func (self *VimLParser) parse_constlhs() *lhs {
+	var lhs = &lhs{}
+	var tokenizer = NewExprTokenizer(self.reader)
+	if tokenizer.peek().type_ == TOKEN_SQOPEN {
+		tokenizer.get()
+		for true {
+			var node = self.parse_lvalue()
+			lhs.list = append(lhs.list, node)
+			var token = tokenizer.get()
+			if token.type_ == TOKEN_SQCLOSE {
+				break
+			} else if token.type_ == TOKEN_COMMA {
+				continue
+			} else if token.type_ == TOKEN_SEMICOLON {
+				node = self.parse_lvalue()
+				lhs.rest = node
+				token = tokenizer.get()
+				if token.type_ == TOKEN_SQCLOSE {
+					break
+				} else {
+					panic(Err(viml_printf("E475 Invalid argument: %s", token.value), token.pos))
+				}
+			} else {
+				panic(Err(viml_printf("E475 Invalid argument: %s", token.value), token.pos))
+			}
+		}
+	} else {
+		lhs.left = self.parse_constlvalue()
+	}
+	return lhs
+}
+
 func (self *VimLParser) ends_excmds(c string) bool {
 	return c == "" || c == "|" || c == "\"" || c == "<EOF>" || c == "<EOL>"
 }
@@ -1798,6 +1983,8 @@ func (self *VimLParser) parse_cmd_syntax() {
 
 // To find new builtin_commands, run the below script.
 // $ scripts/update_builtin_commands.sh /path/to/vim/src/ex_cmds.h
+// To find new builtin_functions, run the below script.
+// $ scripts/update_builtin_functions.sh /path/to/vim/src/evalfunc.c
 func (self *ExprTokenizer) __init__(reader *StringReader) {
 	self.reader = reader
 }
@@ -1839,6 +2026,14 @@ func (self *ExprTokenizer) get2() *ExprToken {
 		var s = r.getn(3)
 		s += r.read_xdigit()
 		return self.token(TOKEN_NUMBER, s, pos)
+	} else if c == "0" && (r.p(1) == "B" || r.p(1) == "b") && (r.p(2) == "0" || r.p(2) == "1") {
+		var s = r.getn(3)
+		s += r.read_bdigit()
+		return self.token(TOKEN_NUMBER, s, pos)
+	} else if c == "0" && (r.p(1) == "Z" || r.p(1) == "z") && r.p(2) != "." {
+		var s = r.getn(2)
+		s += r.read_blob()
+		return self.token(TOKEN_BLOB, s, pos)
 	} else if isdigit(c) {
 		var s = r.read_digit()
 		if r.p(0) == "." && isdigit(r.p(1)) {
@@ -1984,9 +2179,14 @@ func (self *ExprTokenizer) get2() *ExprToken {
 		if r.p(1) == "." && r.p(2) == "." {
 			r.seek_cur(3)
 			return self.token(TOKEN_DOTDOTDOT, "...", pos)
+		} else if r.p(1) == "." {
+			r.seek_cur(2)
+			return self.token(TOKEN_DOTDOT, "..", pos)
+			// TODO check scriptversion?
 		} else {
 			r.seek_cur(1)
 			return self.token(TOKEN_DOT, ".", pos)
+			// TODO check scriptversion?
 		}
 	} else if c == "*" {
 		r.seek_cur(1)
@@ -2007,8 +2207,13 @@ func (self *ExprTokenizer) get2() *ExprToken {
 		r.seek_cur(1)
 		return self.token(TOKEN_COLON, ":", pos)
 	} else if c == "#" {
-		r.seek_cur(1)
-		return self.token(TOKEN_SHARP, "#", pos)
+		if r.p(1) == "{" {
+			r.seek_cur(2)
+			return self.token(TOKEN_LITCOPEN, "#{", pos)
+		} else {
+			r.seek_cur(1)
+			return self.token(TOKEN_SHARP, "#", pos)
+		}
 	} else if c == "(" {
 		r.seek_cur(1)
 		return self.token(TOKEN_POPEN, "(", pos)
@@ -2128,6 +2333,31 @@ func (self *ExprTokenizer) get_dstring() string {
 	return s
 }
 
+func (self *ExprTokenizer) parse_dict_literal_key() *VimNode {
+	self.reader.skip_white()
+	var c = self.reader.peek()
+	if !isalnum(c) && c != "_" && c != "-" {
+		panic(Err(viml_printf("unexpected character: %s", c), self.reader.getpos()))
+	}
+	var node = Node(NODE_STRING)
+	var s = c
+	self.reader.seek_cur(1)
+	node.pos = self.reader.getpos()
+	for true {
+		c = self.reader.p(0)
+		if c == "<EOF>" || c == "<EOL>" {
+			panic(Err("unexpectd EOL", self.reader.getpos()))
+		}
+		if !isalnum(c) && c != "_" && c != "-" {
+			break
+		}
+		self.reader.seek_cur(1)
+		s += c
+	}
+	node.value = "'" + s + "'"
+	return node
+}
+
 func (self *ExprParser) __init__(reader *StringReader) {
 	self.reader = reader
 	self.tokenizer = NewExprTokenizer(reader)
@@ -2407,6 +2637,7 @@ func (self *ExprParser) parse_expr4() *VimNode {
 // expr5: expr6 + expr6 ..
 //        expr6 - expr6 ..
 //        expr6 . expr6 ..
+//        expr6 .. expr6 ..
 func (self *ExprParser) parse_expr5() *VimNode {
 	var left = self.parse_expr6()
 	for true {
@@ -2424,7 +2655,15 @@ func (self *ExprParser) parse_expr5() *VimNode {
 			node.left = left
 			node.right = self.parse_expr6()
 			left = node
+		} else if token.type_ == TOKEN_DOTDOT {
+			// TODO check scriptversion?
+			var node = Node(NODE_CONCAT)
+			node.pos = token.pos
+			node.left = left
+			node.right = self.parse_expr6()
+			left = node
 		} else if token.type_ == TOKEN_DOT {
+			// TODO check scriptversion?
 			var node = Node(NODE_CONCAT)
 			node.pos = token.pos
 			node.left = left
@@ -2503,6 +2742,9 @@ func (self *ExprParser) parse_expr7() *VimNode {
 // expr8: expr8[expr1]
 //        expr8[expr1 : expr1]
 //        expr8.name
+//        expr8->name(expr1, ...)
+//        expr8->s:user_func(expr1, ...)
+//        expr8->{lambda}(expr1, ...)
 //        expr8(expr1, ...)
 func (self *ExprParser) parse_expr8() *VimNode {
 	var left = self.parse_expr9()
@@ -2556,35 +2798,29 @@ func (self *ExprParser) parse_expr8() *VimNode {
 					left = node
 				}
 			}
+		} else if token.type_ == TOKEN_ARROW {
+			var funcname_or_lambda = self.parse_expr9()
+			token = self.tokenizer.get()
+			if token.type_ != TOKEN_POPEN {
+				panic(Err("E107: Missing parentheses: lambda", token.pos))
+			}
+			var right = Node(NODE_CALL)
+			right.pos = token.pos
+			right.left = funcname_or_lambda
+			right.rlist = self.parse_rlist()
+			var node = Node(NODE_METHOD)
+			node.pos = token.pos
+			node.left = left
+			node.right = right
+			left = node
 		} else if token.type_ == TOKEN_POPEN {
 			var node = Node(NODE_CALL)
 			node.pos = token.pos
 			node.left = left
-			if self.tokenizer.peek().type_ == TOKEN_PCLOSE {
-				self.tokenizer.get()
-			} else {
-				for true {
-					node.rlist = append(node.rlist, self.parse_expr1())
-					token = self.tokenizer.get()
-					if token.type_ == TOKEN_COMMA {
-						// XXX: Vim allows foo(a, b, ).  Lint should warn it.
-						if self.tokenizer.peek().type_ == TOKEN_PCLOSE {
-							self.tokenizer.get()
-							break
-						}
-					} else if token.type_ == TOKEN_PCLOSE {
-						break
-					} else {
-						panic(Err(viml_printf("unexpected token: %s", token.value), token.pos))
-					}
-				}
-			}
-			if len(node.rlist) > MAX_FUNC_ARGS {
-				// TODO: funcname E740: Too many arguments for function: %s
-				panic(Err("E740: Too many arguments for function", node.pos))
-			}
+			node.rlist = self.parse_rlist()
 			left = node
 		} else if !iswhite(c) && token.type_ == TOKEN_DOT {
+			// TODO check scriptversion?
 			var node = self.parse_dot(token, left)
 			if node == nil {
 				self.reader.seek_set(pos)
@@ -2599,11 +2835,41 @@ func (self *ExprParser) parse_expr8() *VimNode {
 	return left
 }
 
+func (self *ExprParser) parse_rlist() []*VimNode {
+	var rlist []*VimNode
+	var token = self.tokenizer.peek()
+	if self.tokenizer.peek().type_ == TOKEN_PCLOSE {
+		self.tokenizer.get()
+	} else {
+		for true {
+			rlist = append(rlist, self.parse_expr1())
+			token = self.tokenizer.get()
+			if token.type_ == TOKEN_COMMA {
+				// XXX: Vim allows foo(a, b, ).  Lint should warn it.
+				if self.tokenizer.peek().type_ == TOKEN_PCLOSE {
+					self.tokenizer.get()
+					break
+				}
+			} else if token.type_ == TOKEN_PCLOSE {
+				break
+			} else {
+				panic(Err(viml_printf("unexpected token: %s", token.value), token.pos))
+			}
+		}
+	}
+	if len(rlist) > MAX_FUNC_ARGS {
+		// TODO: funcname E740: Too many arguments for function: %s
+		panic(Err("E740: Too many arguments for function", token.pos))
+	}
+	return rlist
+}
+
 // expr9: number
 //        "string"
 //        'string'
 //        [expr1, ...]
 //        {expr1: expr1, ...}
+//        #{literal_key1: expr1, ...}
 //        {args -> expr1}
 //        &option
 //        (expr1)
@@ -2621,6 +2887,10 @@ func (self *ExprParser) parse_expr9() *VimNode {
 		node = Node(NODE_NUMBER)
 		node.pos = token.pos
 		node.value = token.value
+	} else if token.type_ == TOKEN_BLOB {
+		node = Node(NODE_BLOB)
+		node.pos = token.pos
+		node.value = token.value
 	} else if token.type_ == TOKEN_DQUOTE {
 		self.reader.seek_set(pos)
 		node = Node(NODE_STRING)
@@ -2656,7 +2926,8 @@ func (self *ExprParser) parse_expr9() *VimNode {
 				}
 			}
 		}
-	} else if token.type_ == TOKEN_COPEN {
+	} else if token.type_ == TOKEN_COPEN || token.type_ == TOKEN_LITCOPEN {
+		var is_litdict = token.type_ == TOKEN_LITCOPEN
 		var savepos = self.reader.tell()
 		var nodepos = token.pos
 		token = self.tokenizer.get()
@@ -2673,6 +2944,7 @@ func (self *ExprParser) parse_expr9() *VimNode {
 			// lambda {token,...} {->...} {token->...}
 			node = Node(NODE_LAMBDA)
 			node.pos = nodepos
+			node.rlist = []*VimNode{}
 			var named = map[string]interface{}{}
 			for true {
 				if token.type_ == TOKEN_ARROW {
@@ -2743,7 +3015,13 @@ func (self *ExprParser) parse_expr9() *VimNode {
 			return node
 		}
 		for {
-			var key = self.parse_expr1()
+			var key = func() *VimNode {
+				if is_litdict {
+					return self.tokenizer.parse_dict_literal_key()
+				} else {
+					return self.parse_expr1()
+				}
+			}()
 			token = self.tokenizer.get()
 			if token.type_ == TOKEN_CCLOSE {
 				if !viml_empty(node.value) {
@@ -2836,6 +3114,31 @@ func (self *ExprParser) parse_dot(token *ExprToken, left *VimNode) *VimNode {
 	return node
 }
 
+// CONCAT
+//   str  ".." expr6         => (concat str expr6)
+func (self *ExprParser) parse_concat(token *ExprToken, left *VimNode) *VimNode {
+	if left.type_ != NODE_IDENTIFIER && left.type_ != NODE_CURLYNAME && left.type_ != NODE_DICT && left.type_ != NODE_SUBSCRIPT && left.type_ != NODE_CALL && left.type_ != NODE_DOT {
+		return nil
+	}
+	if !iswordc(self.reader.p(0)) {
+		return nil
+	}
+	var pos = self.reader.getpos()
+	var name = self.reader.read_word()
+	if isnamec(self.reader.p(0)) {
+		// XXX: foo is str => ok, foo is obj => invalid expression
+		// foo.s:bar or foo.bar#baz
+		return nil
+	}
+	var node = Node(NODE_CONCAT)
+	node.pos = token.pos
+	node.left = left
+	node.right = Node(NODE_IDENTIFIER)
+	node.right.pos = pos
+	node.right.value = name
+	return node
+}
+
 func (self *ExprParser) parse_identifier() *VimNode {
 	self.reader.skip_white()
 	var npos = self.reader.getpos()
@@ -3134,6 +3437,23 @@ func (self *StringReader) read_odigit() string {
 	return r
 }
 
+func (self *StringReader) read_blob() string {
+	var r = ""
+	for {
+		var s = self.peekn(2)
+		if viml_eqregh(s, "^[0-9A-Fa-f][0-9A-Fa-f]$") {
+			r += self.getn(2)
+		} else if viml_eqregh(s, "^\\.[0-9A-Fa-f]$") {
+			r += self.getn(1)
+		} else if viml_eqregh(s, "^[0-9A-Fa-f][^0-9A-Fa-f]$") {
+			panic(Err("E973: Blob literal should have an even number of hex characters:"+s, self.getpos()))
+		} else {
+			break
+		}
+	}
+	return r
+}
+
 func (self *StringReader) read_xdigit() string {
 	var r = ""
 	for isxdigit(self.peekn(1)) {
@@ -3142,6 +3462,14 @@ func (self *StringReader) read_xdigit() string {
 	return r
 }
 
+func (self *StringReader) read_bdigit() string {
+	var r = ""
+	for self.peekn(1) == "0" || self.peekn(1) == "1" {
+		r += self.getn(1)
+	}
+	return r
+}
+
 func (self *StringReader) read_integer() string {
 	var r = ""
 	var c = self.peekn(1)
@@ -3169,8 +3497,10 @@ func (self *StringReader) read_white() string {
 
 func (self *StringReader) read_nonwhite() string {
 	var r = ""
-	for !iswhite(self.peekn(1)) {
+	var ch = self.peekn(1)
+	for !iswhite(ch) && ch != "" {
 		r += self.getn(1)
+		ch = self.peekn(1)
 	}
 	return r
 }
@@ -3220,9 +3550,15 @@ func (self *Compiler) compile(node *VimNode) interface{} {
 	} else if node.type_ == NODE_EXCALL {
 		self.compile_excall(node)
 		return nil
+	} else if node.type_ == NODE_EVAL {
+		self.compile_eval(node)
+		return nil
 	} else if node.type_ == NODE_LET {
 		self.compile_let(node)
 		return nil
+	} else if node.type_ == NODE_CONST {
+		self.compile_const(node)
+		return nil
 	} else if node.type_ == NODE_UNLET {
 		self.compile_unlet(node)
 		return nil
@@ -3361,10 +3697,14 @@ func (self *Compiler) compile(node *VimNode) interface{} {
 		return self.compile_slice(node)
 	} else if node.type_ == NODE_DOT {
 		return self.compile_dot(node)
+	} else if node.type_ == NODE_METHOD {
+		return self.compile_method(node)
 	} else if node.type_ == NODE_CALL {
 		return self.compile_call(node)
 	} else if node.type_ == NODE_NUMBER {
 		return self.compile_number(node)
+	} else if node.type_ == NODE_BLOB {
+		return self.compile_blob(node)
 	} else if node.type_ == NODE_STRING {
 		return self.compile_string(node)
 	} else if node.type_ == NODE_LIST {
@@ -3387,6 +3727,8 @@ func (self *Compiler) compile(node *VimNode) interface{} {
 		return self.compile_curlynameexpr(node)
 	} else if node.type_ == NODE_LAMBDA {
 		return self.compile_lambda(node)
+	} else if node.type_ == NODE_HEREDOC {
+		return self.compile_heredoc(node)
 	} else if node.type_ == NODE_PARENEXPR {
 		return self.compile_parenexpr(node)
 	} else {
@@ -3423,14 +3765,31 @@ func (self *Compiler) compile_function(node *VimNode) {
 		}
 		return ss
 	}()
-	if !viml_empty(rlist) && rlist[len(rlist)-1] == "..." {
-		rlist[len(rlist)-1] = ". ..."
-	}
-	if viml_empty(rlist) {
-		self.out("(function (%s)", left)
-	} else {
-		self.out("(function (%s %s)", left, viml_join(rlist, " "))
+	var default_args = func() []string {
+		var ss []string
+		for _, vval := range node.default_args {
+			ss = append(ss, self.compile(vval).(string))
+		}
+		return ss
+	}()
+	if !viml_empty(rlist) {
+		var remaining = false
+		if rlist[len(rlist)-1] == "..." {
+			rlist = rlist[:len(rlist)-1]
+			remaining = true
+		}
+		for _, i := range viml_range(0, len(rlist)-1) {
+			if i < len(rlist)-len(default_args) {
+				left += viml_printf(" %s", rlist[i])
+			} else {
+				left += viml_printf(" (%s %s)", rlist[i], default_args[i+len(default_args)-len(rlist)])
+			}
+		}
+		if remaining {
+			left += " . ..."
+		}
 	}
+	self.out("(function (%s)", left)
 	self.incindent("  ")
 	self.compile_body(node.body)
 	self.out(")")
@@ -3453,6 +3812,10 @@ func (self *Compiler) compile_excall(node *VimNode) {
 	self.out("(call %s)", self.compile(node.left).(string))
 }
 
+func (self *Compiler) compile_eval(node *VimNode) {
+	self.out("(eval %s)", self.compile(node.left).(string))
+}
+
 func (self *Compiler) compile_let(node *VimNode) {
 	var left = ""
 	if node.left != nil {
@@ -3474,6 +3837,28 @@ func (self *Compiler) compile_let(node *VimNode) {
 	self.out("(let %s %s %s)", node.op, left, right)
 }
 
+// TODO: merge with s:Compiler.compile_let() ?
+func (self *Compiler) compile_const(node *VimNode) {
+	var left = ""
+	if node.left != nil {
+		left = self.compile(node.left).(string)
+	} else {
+		left = viml_join(func() []string {
+			var ss []string
+			for _, vval := range node.list {
+				ss = append(ss, self.compile(vval).(string))
+			}
+			return ss
+		}(), " ")
+		if node.rest != nil {
+			left += " . " + self.compile(node.rest).(string)
+		}
+		left = "(" + left + ")"
+	}
+	var right = self.compile(node.right)
+	self.out("(const %s %s %s)", node.op, left, right)
+}
+
 func (self *Compiler) compile_unlet(node *VimNode) {
 	var list = func() []string {
 		var ss []string
@@ -3862,6 +4247,10 @@ func (self *Compiler) compile_dot(node *VimNode) string {
 	return viml_printf("(dot %s %s)", self.compile(node.left).(string), self.compile(node.right))
 }
 
+func (self *Compiler) compile_method(node *VimNode) string {
+	return viml_printf("(method %s %s)", self.compile(node.left).(string), self.compile(node.right))
+}
+
 func (self *Compiler) compile_call(node *VimNode) string {
 	var rlist = func() []string {
 		var ss []string
@@ -3881,6 +4270,10 @@ func (self *Compiler) compile_number(node *VimNode) string {
 	return node.value.(string)
 }
 
+func (self *Compiler) compile_blob(node *VimNode) string {
+	return node.value.(string)
+}
+
 func (self *Compiler) compile_string(node *VimNode) string {
 	return node.value.(string)
 }
@@ -3905,6 +4298,23 @@ func (self *Compiler) compile_curlynamepart(node *VimNode) string {
 	return node.value.(string)
 }
 
+func (self *Compiler) escape_string(str string) string {
+	var out = "\""
+	for _, c := range viml_split(str, "\\zs") {
+		if c == "\n" {
+			out += "\\n"
+		} else if c == "\t" {
+			out += "\\t"
+		} else if c == "\r" {
+			out += "\\r"
+		} else {
+			out += c
+		}
+	}
+	out += "\""
+	return out
+}
+
 func (self *Compiler) compile_lambda(node *VimNode) string {
 	var rlist = func() []string {
 		var ss []string
@@ -3916,4 +4326,35 @@ func (self *Compiler) compile_lambda(node *VimNode) string {
 	return viml_printf("(lambda (%s) %s)", viml_join(rlist, " "), self.compile(node.left).(string))
 }
 
+func (self *Compiler) compile_heredoc(node *VimNode) string {
+	var rlist = func() string {
+		if viml_empty(node.rlist) {
+			return "(list)"
+		} else {
+			return "(list " + viml_join(func() []string {
+				var ss []string
+				for _, vval := range node.rlist {
+					ss = append(ss, self.escape_string(vval.value.(string)))
+				}
+				return ss
+			}(), " ") + ")"
+		}
+	}()
+	var body = func() string {
+		if viml_empty(node.body) {
+			return "(list)"
+		} else {
+			return "(list " + viml_join(func() []string {
+				var ss []string
+				for _, vval := range node.body {
+					ss = append(ss, self.escape_string(vval.value.(string)))
+				}
+				return ss
+			}(), " ") + ")"
+		}
+	}()
+	var op = self.escape_string(node.op)
+	return viml_printf("(heredoc %s %s %s)", rlist, op, body)
+}
+
 // TODO: under construction
diff --git a/printer/opprec.go b/printer/opprec.go
index c2c7c89..483078e 100644
--- a/printer/opprec.go
+++ b/printer/opprec.go
@@ -61,9 +61,9 @@ func opprec(op ast.Expr) int {
 		default:
 			panic(fmt.Errorf("unexpected token of UnaryExpr: %v", n.Op))
 		}
-	case *ast.SubscriptExpr, *ast.SliceExpr, *ast.CallExpr, *ast.DotExpr:
+	case *ast.SubscriptExpr, *ast.SliceExpr, *ast.CallExpr, *ast.DotExpr, *ast.MethodExpr:
 		return 8
-	case *ast.BasicLit, *ast.Ident, *ast.List, *ast.Dict, *ast.CurlyName:
+	case *ast.BasicLit, *ast.Ident, *ast.List, *ast.Dict, *ast.CurlyName, *ast.HeredocExpr:
 		return 9
 	case *ast.CurlyNameExpr, *ast.CurlyNameLit, *ast.LambdaExpr:
 		panic(fmt.Errorf("precedence is undefined for expr: %T", n))
diff --git a/test/node_position/test_position.ok b/test/node_position/test_position.ok
new file mode 100644
index 0000000..f8d5620
--- /dev/null
+++ b/test/node_position/test_position.ok
@@ -0,0 +1,10 @@
+function! F()
+  let x =
+\ 1
+
+  let x = "
+  \1
+	\2 <- tab
+  \3 マルチバイト
+  \4"
+endfunction
diff --git a/test/node_position/test_position.vim b/test/node_position/test_position.vim
new file mode 100644
index 0000000..616e3d4
--- /dev/null
+++ b/test/node_position/test_position.vim
@@ -0,0 +1,43 @@
+let s:vimlparser = vimlparser#import()
+
+function! s:run()
+  let src = [
+  \  '',
+  \  'function! F()',
+  \  '  let x =',
+  \  '\ 1',
+  \  '',
+  \  '  let x = "',
+  \  '  \1',
+  \  '	\2 <- tab',
+  \  '  \3 マルチバイト',
+  \  '  \4"',
+  \  'endfunction',
+  \  '',
+  \  '" END',
+  \]
+  let r = s:vimlparser.StringReader.new(src)
+  let p = s:vimlparser.VimLParser.new(0)
+  let c = s:vimlparser.Compiler.new()
+  let toplevel = p.parse(r)
+  let func = toplevel.body[0]
+  let body = s:extract_body(func, src)
+  call writefile(split(body, "\n"), 'test/node_position/test_position.out')
+  qall!
+endfunction
+
+function! s:extract_body(func, src)
+  let pos = a:func.pos
+
+  " FIXME calculating endpos is workaround. Ideally, it should have the end
+  " position of the node.
+
+  let endpos = a:func.endfunction.pos
+  let endfunc = a:func.endfunction.ea
+  let cmdlen = endfunc.argpos.offset - endfunc.cmdpos.offset
+  let endpos.offset += cmdlen
+
+  return join(a:src, "\n")[pos.offset : endpos.offset]
+endfunction
+
+call s:run()
diff --git a/test/run.bat b/test/run.bat
index 267e325..a2d81a7 100644
--- a/test/run.bat
+++ b/test/run.bat
@@ -1,2 +1,5 @@
 @echo off
-vim -u NONE -N --cmd "let &rtp .= ',' . getcwd()" -S test/run.vim
+vim -Nu test/vimrc -S test/run.vim
+set EXIT=%ERRORLEVEL%
+if exist test.log type test.log
+exit /b %EXIT%
diff --git a/test/run.sh b/test/run.sh
index b94c403..2a3f08d 100755
--- a/test/run.sh
+++ b/test/run.sh
@@ -1,2 +1,5 @@
 #!/bin/sh
-vim -u NONE -N --cmd "let &rtp .= ',' . getcwd()" -S test/run.vim
+vim -Nu test/vimrc -S test/run.vim
+EXIT=$?
+[ -e test.log ] && cat test.log
+exit $EXIT
diff --git a/test/run.vim b/test/run.vim
index c7d657f..bd7c029 100644
--- a/test/run.vim
+++ b/test/run.vim
@@ -1,13 +1,15 @@
-
-
 let s:vimlparser = vimlparser#import()
 
 let s:sdir = expand('<sfile>:p:h')
 
 function! s:run()
+  enew
+  set buftype=nofile
+  let ng = 0
   for vimfile in glob(s:sdir . '/test*.vim', 0, 1)
     let okfile = fnamemodify(vimfile, ':r') . '.ok'
     let outfile = fnamemodify(vimfile, ':r') . '.out'
+    let skip = filereadable(fnamemodify(vimfile, ':r') . '.skip')
     let src = readfile(vimfile)
     let r = s:vimlparser.StringReader.new(src)
     if vimfile =~# 'test_neo'
@@ -23,13 +25,28 @@ function! s:run()
     catch
       call writefile([v:exception], outfile)
     endtry
-    if system(printf('diff %s %s', shellescape(okfile), shellescape(outfile))) == ""
+    let diff = system(printf('diff -u %s %s', shellescape(okfile), shellescape(outfile)))
+    if empty(diff)
       let line = printf('%s => ok', fnamemodify(vimfile, ':.'))
+      call append(line('$'), line)
     else
-      let line = printf('%s => ng', fnamemodify(vimfile, ':.'))
+      if !skip
+        let ng += 1
+      endif
+      let line = printf('%s => ' . (skip ? 'skip' : 'ng'), fnamemodify(vimfile, ':.'))
+      call append(line('$'), line)
+      for line in split(diff, '\n')
+        call append(line('$'), '    ' . line)
+      endfor
     endif
-    call append(line('$'), line)
   endfor
+  if $CI == 'true'
+    call writefile(getline(1, '$'), 'test.log')
+    if ng == 0
+      quit!
+    endif
+    cquit!
+  endif
   syntax enable
   match Error /^.* => ng$/
 endfunction
diff --git a/test/run_command.sh b/test/run_command.sh
new file mode 100755
index 0000000..2a079d8
--- /dev/null
+++ b/test/run_command.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+if [ $# -eq 0 ]; then
+  echo "USAGE: ./test/run_command.sh <vimlparser command>"
+  echo "  Example: ./test/run_command.sh python3 py/vimlparser.py"
+  exit 1
+fi
+vimlparser=$@
+
+exit_status=0
+
+test_file() {
+  local vimfile=$1
+  local base=$(basename "$vimfile" ".vim")
+  local okfile="test/${base}.ok"
+  local outfile="test/${base}.out"
+
+  if [[ -f "test/${base}.skip" ]]; then
+    return
+  fi
+
+  local neovim=""
+  if [[ $vimfile =~ "test_neo" ]]; then
+    neovim="--neovim"
+  fi
+
+  rm -f ${outfile}
+
+  ${vimlparser} ${neovim} ${vimfile} &> ${outfile}
+
+  diffout=$(diff -u ${outfile} ${okfile})
+  if [ -n "$diffout" ]; then
+    exit_status=1
+    echo "${diffout}"
+  fi
+}
+
+for f in test/test*.vim; do
+  test_file ${f}
+done
+
+exit $exit_status
diff --git a/test/test1.ok b/test/test1.ok
index e6e3bd0..7cc25a6 100644
--- a/test/test1.ok
+++ b/test/test1.ok
@@ -1,6 +1,10 @@
 ; test1
 (function (s:foo a b . ...)
   (return 0))
+(function (s:bar (a 1) (b 2) . ...)
+  (return 0))
+(function (s:baz a (b 2) . ...)
+  (return 0))
 (if 1
   (echo "if 1")
  elseif 2
@@ -14,6 +18,8 @@
   (echo a b c))
 (delfunction s:foo)
 (call (s:foo 1 2 3))
+(eval (filter odds 'v:val % 2'))
+(eval 42)
 (let = a (dict ("x" "y")))
 (let = (a b . c) (list 1 2 3))
 (let += (a b . c) (list 1 2 3))
@@ -46,3 +52,16 @@
 (echo (subscript x 0) (subscript x y))
 (echo (slice x 1 2) (slice x 1 nil) (slice x nil 2) (slice x nil nil))
 (echo (dot x y) (dot (dot x y) z))
+(let = a 1)
+(let += a 2)
+(let *= a 3)
+(let /= a 4)
+(let %= a 5)
+(let ..= a 'foo')
+(echo (concat (concat 'foo' 'bar') 'baz'))
+(echo (concat 'foo' (concat 'bar' 'baz')))
+(echo (concat (concat 'foo' 'bar') 'baz'))
+(let = a '🐥')
+(const = a 1)
+(const = (a b) (list 1 2))
+(const = (a b . c) (list 1 2 3))
diff --git a/test/test1.vim b/test/test1.vim
index a237e5d..b5bc331 100644
--- a/test/test1.vim
+++ b/test/test1.vim
@@ -2,6 +2,12 @@
 function s:foo(a, b, ...)
   return 0
 endfunction
+function s:bar(a = 1, b = 2, ...)
+  return 0
+endfunction
+function s:baz(a, b = 2, ...)
+  return 0
+endfunction
 if 1
   echo "if 1"
 elseif 2
@@ -18,6 +24,8 @@ for [a, b; c] in d
 endfor
 delfunction s:foo
 call s:foo(1, 2, 3)
+eval filter(odds, 'v:val % 2')
+eval 42
 let a = {"x": "y"}
 let [a, b; c] = [1, 2, 3]
 let [a, b; c] += [1, 2, 3]
@@ -51,3 +59,16 @@ echo {} {"x":"y"} {"x":"y","z":"w",}
 echo x[0] x[y]
 echo x[1:2] x[1:] x[:2] x[:]
 echo x.y x.y.z
+let a = 1
+let a += 2
+let a *= 3
+let a /= 4
+let a %= 5
+let a ..= 'foo'
+echo ('foo' .. 'bar')..'baz'
+echo 'foo' .. ('bar'..'baz')
+echo 'foo' .. 'bar' .. 'baz'
+let a = '🐥'
+const a = 1
+const [a, b] = [1, 2]
+const [a, b; c] = [1, 2, 3]
diff --git a/test/test_blob.ok b/test/test_blob.ok
new file mode 100644
index 0000000..ea3d2c6
--- /dev/null
+++ b/test/test_blob.ok
@@ -0,0 +1,8 @@
+(echo 0z)
+(echo 0z11)
+(echo 0zdeadbeef)
+(echo 0Zdeadbeef)
+(echo 0zDEADBEEF)
+(echo 0ZDEADBEEF)
+(echo 0ZDEAD.BEEF)
+(echo (+ 0z00 0zFF))
diff --git a/test/test_blob.vim b/test/test_blob.vim
new file mode 100644
index 0000000..f9b3274
--- /dev/null
+++ b/test/test_blob.vim
@@ -0,0 +1,8 @@
+echo 0z
+echo 0z11
+echo 0zdeadbeef
+echo 0Zdeadbeef
+echo 0zDEADBEEF
+echo 0ZDEADBEEF
+echo 0ZDEAD.BEEF
+echo 0z00 + 0zFF
diff --git a/test/test_command.ok b/test/test_command.ok
index 415661e..2d1a6fd 100644
--- a/test/test_command.ok
+++ b/test/test_command.ok
@@ -3,3 +3,4 @@
 (excmd "normal gg")
 (excmd "Usercmd x, y z 123")
 (excmd "Usercmd! with bang")
+(excmd "write ++enc=utf-8")
diff --git a/test/test_command.vim b/test/test_command.vim
index 65d8eb4..3858de8 100644
--- a/test/test_command.vim
+++ b/test/test_command.vim
@@ -3,3 +3,4 @@ normal! gg
 normal gg
 Usercmd x, y z 123
 Usercmd! with bang
+write ++enc=utf-8
diff --git a/test/test_delfunction.ok b/test/test_delfunction.ok
new file mode 100644
index 0000000..e7828d9
--- /dev/null
+++ b/test/test_delfunction.ok
@@ -0,0 +1,2 @@
+(delfunction F)
+(delfunction F)
diff --git a/test/test_delfunction.vim b/test/test_delfunction.vim
new file mode 100644
index 0000000..fbd9129
--- /dev/null
+++ b/test/test_delfunction.vim
@@ -0,0 +1,2 @@
+delfunction F
+delfunction! F
diff --git a/test/test_err_blob.ok b/test/test_err_blob.ok
new file mode 100644
index 0000000..74b3dda
--- /dev/null
+++ b/test/test_err_blob.ok
@@ -0,0 +1 @@
+vimlparser: E973: Blob literal should have an even number of hex characters:0X: line 2 col 8
diff --git a/test/test_err_blob.vim b/test/test_err_blob.vim
new file mode 100644
index 0000000..2c715f3
--- /dev/null
+++ b/test/test_err_blob.vim
@@ -0,0 +1,4 @@
+echo 0z0
+echo 0z0X
+echo 0zAABBC
+echo 0zAAB.BCC
diff --git a/test/test_err_const1.ok b/test/test_err_const1.ok
new file mode 100644
index 0000000..70f9c97
--- /dev/null
+++ b/test/test_err_const1.ok
@@ -0,0 +1 @@
+vimlparser: E996: Cannot lock a list or dict: line 2 col 8
diff --git a/test/test_err_const1.vim b/test/test_err_const1.vim
new file mode 100644
index 0000000..a796248
--- /dev/null
+++ b/test/test_err_const1.vim
@@ -0,0 +1,2 @@
+let a = [0]
+const a[0] = 1
diff --git a/test/test_err_const2.ok b/test/test_err_const2.ok
new file mode 100644
index 0000000..70f9c97
--- /dev/null
+++ b/test/test_err_const2.ok
@@ -0,0 +1 @@
+vimlparser: E996: Cannot lock a list or dict: line 2 col 8
diff --git a/test/test_err_const2.vim b/test/test_err_const2.vim
new file mode 100644
index 0000000..307e6c7
--- /dev/null
+++ b/test/test_err_const2.vim
@@ -0,0 +1,2 @@
+let a = {}
+const a["b"] = 1
diff --git a/test/test_err_const3.ok b/test/test_err_const3.ok
new file mode 100644
index 0000000..70f9c97
--- /dev/null
+++ b/test/test_err_const3.ok
@@ -0,0 +1 @@
+vimlparser: E996: Cannot lock a list or dict: line 2 col 8
diff --git a/test/test_err_const3.vim b/test/test_err_const3.vim
new file mode 100644
index 0000000..6fbf109
--- /dev/null
+++ b/test/test_err_const3.vim
@@ -0,0 +1,2 @@
+let a = {}
+const a.b = 1
diff --git a/test/test_err_const4.ok b/test/test_err_const4.ok
new file mode 100644
index 0000000..6875a74
--- /dev/null
+++ b/test/test_err_const4.ok
@@ -0,0 +1 @@
+vimlparser: E996: Cannot lock an environment variable: line 1 col 7
diff --git a/test/test_err_const4.vim b/test/test_err_const4.vim
new file mode 100644
index 0000000..aceb1f3
--- /dev/null
+++ b/test/test_err_const4.vim
@@ -0,0 +1 @@
+const $a = 1
diff --git a/test/test_err_const5.ok b/test/test_err_const5.ok
new file mode 100644
index 0000000..800eb4a
--- /dev/null
+++ b/test/test_err_const5.ok
@@ -0,0 +1 @@
+vimlparser: E996: Cannot lock a register: line 1 col 7
diff --git a/test/test_err_const5.vim b/test/test_err_const5.vim
new file mode 100644
index 0000000..9222d9d
--- /dev/null
+++ b/test/test_err_const5.vim
@@ -0,0 +1 @@
+const @a = 1
diff --git a/test/test_err_const6.ok b/test/test_err_const6.ok
new file mode 100644
index 0000000..ac0c7b3
--- /dev/null
+++ b/test/test_err_const6.ok
@@ -0,0 +1 @@
+vimlparser: E996: Cannot lock an option: line 1 col 7
diff --git a/test/test_err_const6.vim b/test/test_err_const6.vim
new file mode 100644
index 0000000..a0dd430
--- /dev/null
+++ b/test/test_err_const6.vim
@@ -0,0 +1 @@
+const &encoding = "foo"
diff --git a/test/test_err_funcarg_default.ok b/test/test_err_funcarg_default.ok
new file mode 100644
index 0000000..fa80e1f
--- /dev/null
+++ b/test/test_err_funcarg_default.ok
@@ -0,0 +1 @@
+vimlparser: E989: Non-default argument follows default argument: line 1 col 21
diff --git a/test/test_err_funcarg_default.vim b/test/test_err_funcarg_default.vim
new file mode 100644
index 0000000..8dddfae
--- /dev/null
+++ b/test/test_err_funcarg_default.vim
@@ -0,0 +1,2 @@
+function Foo(abc=1, xyz)
+endfunction
diff --git a/test/test_err_method.ok b/test/test_err_method.ok
new file mode 100644
index 0000000..935f777
--- /dev/null
+++ b/test/test_err_method.ok
@@ -0,0 +1 @@
+vimlparser: E107: Missing parentheses: lambda: line 1 col 32
diff --git a/test/test_err_method.vim b/test/test_err_method.vim
new file mode 100644
index 0000000..734380d
--- /dev/null
+++ b/test/test_err_method.vim
@@ -0,0 +1 @@
+echo 'foo'->{s -> s .. ' bar' }
diff --git a/test/test_err_toomanyarg.ok b/test/test_err_toomanyarg.ok
index 19cac26..9791da7 100644
--- a/test/test_err_toomanyarg.ok
+++ b/test/test_err_toomanyarg.ok
@@ -1 +1 @@
-vimlparser: E740: Too many arguments for function: line 1 col 9
+vimlparser: E740: Too many arguments for function: line 1 col 83
diff --git a/test/test_funcarg_default.ok b/test/test_funcarg_default.ok
new file mode 100644
index 0000000..0eebb8a
--- /dev/null
+++ b/test/test_funcarg_default.ok
@@ -0,0 +1,2 @@
+(function (X (foo (+ 1 2)))
+  (return a:foo))
diff --git a/test/test_funcarg_default.vim b/test/test_funcarg_default.vim
new file mode 100644
index 0000000..fa9deb6
--- /dev/null
+++ b/test/test_funcarg_default.vim
@@ -0,0 +1,3 @@
+function X(foo=1+2)
+  return a:foo
+endfunction
diff --git a/test/test_funcname.ok b/test/test_funcname.ok
new file mode 100644
index 0000000..d37d05e
--- /dev/null
+++ b/test/test_funcname.ok
@@ -0,0 +1,3 @@
+(function (_F))
+(function (s:SID))
+(function (<SID>SID))
diff --git a/test/test_funcname.vim b/test/test_funcname.vim
new file mode 100644
index 0000000..b4011d2
--- /dev/null
+++ b/test/test_funcname.vim
@@ -0,0 +1,8 @@
+function _F()
+endfunction
+
+function s:SID()
+endfunction
+
+function <SID>SID()
+endfunction
diff --git a/test/test_heredoc.ok b/test/test_heredoc.ok
new file mode 100644
index 0000000..0de3f6b
--- /dev/null
+++ b/test/test_heredoc.ok
@@ -0,0 +1,3 @@
+(let =<< a (heredoc (list) "EOS" (list "hello" " world")))
+(let =<< a (heredoc (list "trim") "EOS" (list "\thello" "world")))
+(let =<< a (heredoc (list) "EOS" (list)))
diff --git a/test/test_heredoc.vim b/test/test_heredoc.vim
new file mode 100644
index 0000000..e92322b
--- /dev/null
+++ b/test/test_heredoc.vim
@@ -0,0 +1,10 @@
+let a =<< EOS
+hello
+ world
+EOS
+let a =<< trim EOS
+	hello
+world
+EOS
+let a =<< EOS
+EOS
diff --git a/test/test_litdict.ok b/test/test_litdict.ok
new file mode 100644
index 0000000..72b9d98
--- /dev/null
+++ b/test/test_litdict.ok
@@ -0,0 +1,8 @@
+(echo (dict))
+(echo (dict ('foo-bar' 42)))
+(echo (dict ('foo-' 42)))
+(echo (dict ('-bar' 42)))
+(echo (dict ('1-1' 0)))
+(echo (dict ('one' 1) ('two2' 2) ('3three' 3) ('44' 4)))
+(echo (dict ('x' (dict))))
+(echo (list (dict)))
diff --git a/test/test_litdict.vim b/test/test_litdict.vim
new file mode 100644
index 0000000..9a4e005
--- /dev/null
+++ b/test/test_litdict.vim
@@ -0,0 +1,8 @@
+echo #{}
+echo #{foo-bar: 42}
+echo #{foo-: 42}
+echo #{-bar: 42}
+echo #{1-1:0}
+echo #{one: 1, two2: 2, 3three: 3, 44: 4}
+echo #{x : {},}
+echo [#{},]
diff --git a/test/test_method.ok b/test/test_method.ok
new file mode 100644
index 0000000..7100811
--- /dev/null
+++ b/test/test_method.ok
@@ -0,0 +1,11 @@
+(echo (method 'alice' (s:hello)))
+(echo (method 'alice' (s:hello 'bob')))
+(echo (method (list 1 2 3) (map (lambda (i n) (* n 2)))))
+(echo (method 'john' ((lambda (s) (concat 'hello ' s)))))
+(echo (method 'john' ((lambda (s b) (concat (concat (concat (concat 'hello ' s) ' ... and goodbye ') b) '!')) 'bob')))
+(echo (method 'john' ((lambda (...) (concat 'hello ' (method a:000 (join ', ')))) 'bob')))
+(eval (method funcs (map (lambda () (v:val)))))
+(let = session (method (method (s:get_sessions) (filter (lambda () (==# (dot v:val name) session_name)))) (get 0 (dict))))
+(let = session (method (method (s:get_sessions) (filter (lambda () (==# (dot v:val name) session_name)))) (get 0 (dict))))
+(echo (method expr8 (s:flatmap)))
+(echo (method (method expr8 (s:flatmap)) (filter (lambda (i) (% i 2)))))
diff --git a/test/test_method.vim b/test/test_method.vim
new file mode 100644
index 0000000..df27744
--- /dev/null
+++ b/test/test_method.vim
@@ -0,0 +1,11 @@
+echo 'alice'->s:hello()
+echo 'alice'->s:hello('bob')
+echo [1,2,3]->map({i,n -> n * 2})
+echo 'john'->{s -> 'hello ' .. s }()
+echo 'john'->{s,b -> 'hello ' .. s .. ' ... and goodbye ' .. b .. '!' }('bob')
+echo 'john'->{... -> 'hello ' .. a:000->join(', ') }('bob')
+eval funcs->map({-> v:val() })
+let session = s:get_sessions()->filter({-> v:val.name ==# session_name })->get(0, {})
+let session = (s:get_sessions()->filter({-> v:val.name ==# session_name }))->get(0, {})
+echo expr8->s:flatmap()
+echo expr8->s:flatmap()->filter({i -> i % 2})
diff --git a/test/test_modifier_commands.ok b/test/test_modifier_commands.ok
new file mode 100644
index 0000000..abfa727
--- /dev/null
+++ b/test/test_modifier_commands.ok
@@ -0,0 +1,2 @@
+(call (Func))
+(call (Func))
diff --git a/test/test_modifier_commands.vim b/test/test_modifier_commands.vim
new file mode 100644
index 0000000..d05c626
--- /dev/null
+++ b/test/test_modifier_commands.vim
@@ -0,0 +1,6 @@
+aboveleft belowright botright browse confirm hide keepalt keepjumps
+	      \ keepmarks keeppatterns lockmarks noswapfile silent tab
+	      \ topleft verbose vertical call Func()
+abo bel bo bro conf hid keepalt keepj
+	      \ kee keepp loc nos sil tab
+	      \ to verb vert call Func()
diff --git a/test/test_number.ok b/test/test_number.ok
index ce0839f..ad637fa 100644
--- a/test/test_number.ok
+++ b/test/test_number.ok
@@ -2,7 +2,13 @@
 (echo 1.0)
 (echo 1.23)
 (echo 0xdeadbeef)
+(echo 0Xdeadbeef)
 (echo 033)
 (echo 1.2e-3)
 (echo 4.5E+67)
 (echo 4.5e8)
+(echo 0b1011)
+(echo 0b0)
+(echo 0B1011)
+(echo 0B0)
+(echo 0b01 23)
diff --git a/test/test_number.vim b/test/test_number.vim
index 3b2c884..1e6e42c 100644
--- a/test/test_number.vim
+++ b/test/test_number.vim
@@ -2,7 +2,13 @@ echo 1
 echo 1.0
 echo 1.23
 echo 0xdeadbeef
+echo 0Xdeadbeef
 echo 033
 echo 1.2e-3
 echo 4.5E+67
 echo 4.5e8
+echo 0b1011
+echo 0b0
+echo 0B1011
+echo 0B0
+echo 0b0123
diff --git a/test/test_xxx_colonsharp.skip b/test/test_xxx_colonsharp.skip
new file mode 100644
index 0000000..e69de29
diff --git a/test/vimrc b/test/vimrc
new file mode 100644
index 0000000..e98989a
--- /dev/null
+++ b/test/vimrc
@@ -0,0 +1,6 @@
+let &rtp .= ',' . getcwd()
+
+if $TEST_PROFILE != ''
+  execute 'profile' 'start' $TEST_PROFILE
+  profile! file ./autoload/*
+endif
diff --git a/token/token.go b/token/token.go
index 409d46b..ba38399 100644
--- a/token/token.go
+++ b/token/token.go
@@ -63,6 +63,7 @@ const (
 	CCLOSE
 	COMMA
 	NUMBER
+	BLOB
 	SQUOTE
 	DQUOTE
 	OPTION
@@ -134,6 +135,7 @@ var tokens = [...]string{
 	CCLOSE:     "}",
 	COMMA:      ",",
 	NUMBER:     "<NUMBER>",
+	BLOB:       "<BLOB>",
 	SQUOTE:     "'",
 	DQUOTE:     `"`,
 	OPTION:     "<&OPTION>",