diff --git a/autoload/vimlparser.vim b/autoload/vimlparser.vim index 6adccf7..755cfbb 100644 --- a/autoload/vimlparser.vim +++ b/autoload/vimlparser.vim @@ -136,6 +136,7 @@ let s:NODE_ENV = 88 let s:NODE_REG = 89 let s:NODE_CURLYNAMEPART = 90 let s:NODE_CURLYNAMEEXPR = 91 +let s:NODE_LAMBDA = 92 let s:TOKEN_EOF = 1 let s:TOKEN_EOL = 2 @@ -201,6 +202,7 @@ let s:TOKEN_SEMICOLON = 61 let s:TOKEN_BACKTICK = 62 let s:TOKEN_DOTDOTDOT = 63 let s:TOKEN_SHARP = 64 +let s:TOKEN_ARROW = 65 let s:MAX_FUNC_ARGS = 20 @@ -400,6 +402,7 @@ endfunction " REG .value " CURLYNAMEPART .value " CURLYNAMEEXPR .value +" LAMBDA .rlist .left function! s:Node(type) return {'type': a:type} endfunction @@ -2652,8 +2655,13 @@ function! s:ExprTokenizer.get2() call r.seek_cur(1) return self.token(s:TOKEN_PLUS, '+', pos) elseif c ==# '-' - call r.seek_cur(1) - return self.token(s:TOKEN_MINUS, '-', pos) + if r.p(1) ==# '>' + call r.seek_cur(2) + return self.token(s:TOKEN_ARROW, '->', pos) + else + call r.seek_cur(1) + return self.token(s:TOKEN_MINUS, '-', pos) + endif elseif c ==# '.' if r.p(1) ==# '.' && r.p(2) ==# '.' call r.seek_cur(3) @@ -3290,6 +3298,7 @@ endfunction " 'string' " [expr1, ...] " {expr1: expr1, ...} +" {args -> expr1} " &option " (expr1) " variable @@ -3342,13 +3351,20 @@ function! s:ExprParser.parse_expr9() endwhile endif elseif token.type == s:TOKEN_COPEN - let node = s:Node(s:NODE_DICT) - let node.pos = token.pos - let node.value = [] + let node = s:Node(-1) + let p = token.pos let token = self.tokenizer.peek() if token.type == s:TOKEN_CCLOSE + " dict call self.tokenizer.get() - else + let node = s:Node(s:NODE_DICT) + let node.pos = p + let node.value = [] + elseif token.type == s:TOKEN_DQUOTE || token.type == s:TOKEN_SQUOTE + " dict + let node = s:Node(s:NODE_DICT) + let node.pos = p + let node.value = [] while s:TRUE let key = self.parse_expr1() let token = self.tokenizer.get() @@ -3377,6 +3393,72 @@ function! s:ExprParser.parse_expr9() throw s:Err(printf('unexpected token: %s', token.value), token.pos) endif endwhile + else + " lambda ref: s:NODE_FUNCTION + let node = s:Node(s:NODE_LAMBDA) + let node.pos = p + let node.rlist = [] + let named = {} + while 1 + let token = self.tokenizer.get() + if token.type == s:TOKEN_ARROW + break + 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) + throw s:Err(printf('E853: Duplicate argument name: %s', token.value), token.pos) + endif + let named[token.value] = 1 + let varnode = s:Node(s:NODE_IDENTIFIER) + 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 + throw s:Err('E475: Invalid argument: White space is not allowed before comma', self.reader.getpos()) + endif + let token = self.tokenizer.get() + " handle curly_parts + if token.type == s:TOKEN_COPEN || token.type == s:TOKEN_CCLOSE + if !empty(node.rlist) + throw s:Err(printf('unexpected token: %s', token.value), token.pos) + endif + call self.reader.seek_set(pos) + let node = self.parse_identifier() + return node + endif + call add(node.rlist, varnode) + if token.type == s:TOKEN_COMMA + " XXX: Vim allows last comma. {a, b, -> ...} => OK + if self.reader.peekn(2) == '->' + call self.tokenizer.get() + break + endif + 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 + 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.get() + if token.type == s:TOKEN_ARROW + break + else + throw s:Err(printf('unexpected token: %s', token.value), token.pos) + endif + else + throw s:Err(printf('unexpected token: %s', token.value), token.pos) + endif + endwhile + let node.left = self.parse_expr1() + let token = self.tokenizer.get() + if token.type != s:TOKEN_CCLOSE + throw s:Err(printf('unexpected token: %s', token.value), token.pos) + endif endif elseif token.type == s:TOKEN_POPEN let node = self.parse_expr1() @@ -4048,6 +4130,8 @@ function! s:Compiler.compile(node) return self.compile_curlynamepart(a:node) elseif a:node.type == s:NODE_CURLYNAMEEXPR return self.compile_curlynameexpr(a:node) + elseif a:node.type == s:NODE_LAMBDA + return self.compile_lambda(a:node) else throw printf('Compiler: unknown node: %s', string(a:node)) endif @@ -4506,6 +4590,11 @@ function! s:Compiler.compile_curlynameexpr(node) return '{' . self.compile(a:node.value) . '}' endfunction +function! s:Compiler.compile_lambda(node) + let rlist = map(a:node.rlist, 'self.compile(v:val)') + return printf('(lambda (%s) %s)', join(rlist, ' '), self.compile(a:node.left)) +endfunction + " TODO: under construction let s:RegexpParser = {} diff --git a/go/typedefs.vim b/go/typedefs.vim index 268d725..b63daf2 100644 --- a/go/typedefs.vim +++ b/go/typedefs.vim @@ -321,6 +321,7 @@ 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.compile_lambda': { 'in': ['*VimNode'], 'out': ['string'] }, \ }) function! ImportTypedefs() abort diff --git a/go/vimlparser.go b/go/vimlparser.go index a092aaa..cd8d9e3 100644 --- a/go/vimlparser.go +++ b/go/vimlparser.go @@ -93,6 +93,7 @@ var NODE_ENV = 88 var NODE_REG = 89 var NODE_CURLYNAMEPART = 90 var NODE_CURLYNAMEEXPR = 91 +var NODE_LAMBDA = 92 var TOKEN_EOF = 1 var TOKEN_EOL = 2 var TOKEN_SPACE = 3 @@ -157,6 +158,7 @@ var TOKEN_SEMICOLON = 61 var TOKEN_BACKTICK = 62 var TOKEN_DOTDOTDOT = 63 var TOKEN_SHARP = 64 +var TOKEN_ARROW = 65 var MAX_FUNC_ARGS = 20 func isalpha(c string) bool { return viml_eqregh(c, "^[A-Za-z]$") @@ -325,6 +327,7 @@ func islower(c string) bool { // REG .value // CURLYNAMEPART .value // CURLYNAMEEXPR .value +// LAMBDA .rlist .left func Err(msg string, pos *pos) string { return viml_printf("vimlparser: %s: line %d col %d", msg, pos.lnum, pos.col) } @@ -1942,8 +1945,13 @@ func (self *ExprTokenizer) get2() *ExprToken { r.seek_cur(1) return self.token(TOKEN_PLUS, "+", pos) } else if c == "-" { - r.seek_cur(1) - return self.token(TOKEN_MINUS, "-", pos) + if r.p(1) == ">" { + r.seek_cur(2) + return self.token(TOKEN_ARROW, "->", pos) + } else { + r.seek_cur(1) + return self.token(TOKEN_MINUS, "-", pos) + } } else if c == "." { if r.p(1) == "." && r.p(2) == "." { r.seek_cur(3) @@ -2568,6 +2576,7 @@ func (self *ExprParser) parse_expr8() *VimNode { // 'string' // [expr1, ...] // {expr1: expr1, ...} +// {args -> expr1} // &option // (expr1) // variable @@ -2620,13 +2629,20 @@ func (self *ExprParser) parse_expr9() *VimNode { } } } else if token.type_ == TOKEN_COPEN { - node = Node(NODE_DICT) - node.pos = token.pos - node.value = []interface{}{} + node = Node(-1) + var p = token.pos token = self.tokenizer.peek() if token.type_ == TOKEN_CCLOSE { + // dict self.tokenizer.get() - } else { + node = Node(NODE_DICT) + node.pos = p + node.value = []interface{}{} + } else if token.type_ == TOKEN_DQUOTE || token.type_ == TOKEN_SQUOTE { + // dict + node = Node(NODE_DICT) + node.pos = p + node.value = []interface{}{} for true { var key = self.parse_expr1() token = self.tokenizer.get() @@ -2655,6 +2671,71 @@ func (self *ExprParser) parse_expr9() *VimNode { panic(Err(viml_printf("unexpected token: %s", token.value), token.pos)) } } + } else { + // lambda ref: s:NODE_FUNCTION + node = Node(NODE_LAMBDA) + node.pos = p + var named = map[string]interface{}{} + for { + token = self.tokenizer.get() + if token.type_ == TOKEN_ARROW { + break + } else if token.type_ == TOKEN_IDENTIFIER { + if !isargname(token.value) { + panic(Err(viml_printf("E125: Illegal argument: %s", token.value), token.pos)) + } else if viml_has_key(named, token.value) { + panic(Err(viml_printf("E853: Duplicate argument name: %s", token.value), token.pos)) + } + named[token.value] = 1 + var varnode = Node(NODE_IDENTIFIER) + varnode.pos = token.pos + varnode.value = token.value + // XXX: Vim doesn't skip white space before comma. {a ,b -> ...} => E475 + if iswhite(self.reader.p(0)) && self.tokenizer.peek().type_ == TOKEN_COMMA { + panic(Err("E475: Invalid argument: White space is not allowed before comma", self.reader.getpos())) + } + token = self.tokenizer.get() + // handle curly_parts + if token.type_ == TOKEN_COPEN || token.type_ == TOKEN_CCLOSE { + if !viml_empty(node.rlist) { + panic(Err(viml_printf("unexpected token: %s", token.value), token.pos)) + } + self.reader.seek_set(pos) + node = self.parse_identifier() + return node + } + node.rlist = append(node.rlist, varnode) + if token.type_ == TOKEN_COMMA { + // XXX: Vim allows last comma. {a, b, -> ...} => OK + if self.reader.peekn(2) == "->" { + self.tokenizer.get() + break + } + } else if token.type_ == TOKEN_ARROW { + break + } else { + panic(Err(viml_printf("unexpected token: %s, type: %d", token.value, token.type_), token.pos)) + } + } else if token.type_ == TOKEN_DOTDOTDOT { + var varnode = Node(NODE_IDENTIFIER) + varnode.pos = token.pos + varnode.value = token.value + node.rlist = append(node.rlist, varnode) + token = self.tokenizer.get() + if token.type_ == TOKEN_ARROW { + break + } else { + panic(Err(viml_printf("unexpected token: %s", token.value), token.pos)) + } + } else { + panic(Err(viml_printf("unexpected token: %s", token.value), token.pos)) + } + } + node.left = self.parse_expr1() + token = self.tokenizer.get() + if token.type_ != TOKEN_CCLOSE { + panic(Err(viml_printf("unexpected token: %s", token.value), token.pos)) + } } } else if token.type_ == TOKEN_POPEN { node = self.parse_expr1() @@ -3245,6 +3326,8 @@ func (self *Compiler) compile(node *VimNode) interface{} { return self.compile_curlynamepart(node) } else if node.type_ == NODE_CURLYNAMEEXPR { return self.compile_curlynameexpr(node) + } else if node.type_ == NODE_LAMBDA { + return self.compile_lambda(node) } else { panic(viml_printf("Compiler: unknown node: %s", viml_string(node))) } @@ -3677,4 +3760,9 @@ func (self *Compiler) compile_curlynamepart(node *VimNode) string { return node.value.(string) } +func (self *Compiler) compile_lambda(node *VimNode) string { + var rlist = func() []string {;var ss []string;for _, vval := range node.rlist {;ss = append(ss, self.compile(vval).(string));};return ss;}() + return viml_printf("(lambda (%s) %s)", viml_join(rlist, " "), self.compile(node.left).(string)) +} + // TODO: under construction diff --git a/node.go b/node.go index 6b02269..8029d2d 100644 --- a/node.go +++ b/node.go @@ -92,4 +92,5 @@ const ( NodeReg = 89 NodeCurlynamepart = 90 NodeCurlynameexpr = 91 + NodeLambda = 92 ) diff --git a/test/test_err_lambdaarg.ok b/test/test_err_lambdaarg.ok new file mode 100644 index 0000000..461721f --- /dev/null +++ b/test/test_err_lambdaarg.ok @@ -0,0 +1 @@ +vimlparser: E125: Illegal argument: a:bar: line 1 col 37 diff --git a/test/test_err_lambdaarg.vim b/test/test_err_lambdaarg.vim new file mode 100644 index 0000000..9359939 --- /dev/null +++ b/test/test_err_lambdaarg.vim @@ -0,0 +1 @@ +echo {a, b, C, d_e, _f, g_, h0, i1, a:bar -> 1} diff --git a/test/test_err_lambdaarg_duplicate.ok b/test/test_err_lambdaarg_duplicate.ok new file mode 100644 index 0000000..8a65359 --- /dev/null +++ b/test/test_err_lambdaarg_duplicate.ok @@ -0,0 +1 @@ +vimlparser: E853: Duplicate argument name: b: line 1 col 13 diff --git a/test/test_err_lambdaarg_duplicate.vim b/test/test_err_lambdaarg_duplicate.vim new file mode 100644 index 0000000..e646333 --- /dev/null +++ b/test/test_err_lambdaarg_duplicate.vim @@ -0,0 +1 @@ +echo {a, b, b, c ->1} diff --git a/test/test_lambda.ok b/test/test_lambda.ok new file mode 100644 index 0000000..3ba053c --- /dev/null +++ b/test/test_lambda.ok @@ -0,0 +1,5 @@ +; test_lambda +(echo (lambda () (+ 1 1))) +(echo (lambda (i v) (>= v s:x))) +(echo (lambda (...) a:000)) +(echo ((lambda (x) (* x 2)) 14)) diff --git a/test/test_lambda.vim b/test/test_lambda.vim new file mode 100644 index 0000000..72cc6f6 --- /dev/null +++ b/test/test_lambda.vim @@ -0,0 +1,5 @@ +" test_lambda +echo {->1 + 1} +echo {i, v -> v >= s:x} +echo {...->a:000} +echo {x->x*2}(14) diff --git a/test/test_xxx_err_lambdaarg_space_comma.ok b/test/test_xxx_err_lambdaarg_space_comma.ok new file mode 100644 index 0000000..0cca847 --- /dev/null +++ b/test/test_xxx_err_lambdaarg_space_comma.ok @@ -0,0 +1 @@ +vimlparser: E475: Invalid argument: White space is not allowed before comma: line 10 col 8 diff --git a/test/test_xxx_err_lambdaarg_space_comma.vim b/test/test_xxx_err_lambdaarg_space_comma.vim new file mode 100644 index 0000000..f020686 --- /dev/null +++ b/test/test_xxx_err_lambdaarg_space_comma.vim @@ -0,0 +1,10 @@ +echo {a->'OK1'} +echo { a->'OK2'} +echo {a ->'OK3'} +echo { a ->'OK4'} +echo {a,b->'OK5'} +echo {a, b->'OK6'} +echo { a, b->'OK7'} +echo {a, b ->'OK8'} +echo { a, b ->'OK9'} +echo {a ,b->'NG'} diff --git a/test/test_xxx_lambdaarg_last_comma.ok b/test/test_xxx_lambdaarg_last_comma.ok new file mode 100644 index 0000000..4a76285 --- /dev/null +++ b/test/test_xxx_lambdaarg_last_comma.ok @@ -0,0 +1,2 @@ +(echo (lambda (a) 0)) +(echo (lambda (a) 0)) diff --git a/test/test_xxx_lambdaarg_last_comma.vim b/test/test_xxx_lambdaarg_last_comma.vim new file mode 100644 index 0000000..00f96e5 --- /dev/null +++ b/test/test_xxx_lambdaarg_last_comma.vim @@ -0,0 +1,2 @@ +echo {a,->0} +echo {a, ->0}