diff --git a/NEWS.md b/NEWS.md index 180e10b3fc415..71e80855120e7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -199,6 +199,9 @@ Language changes * `=>` now has its own precedence level, giving it strictly higher precedence than `=` and `,` ([#25391]). + * The conditions under which unary operators followed by `(` are parsed as prefix function + calls have changed ([#26154]). + * `begin` is disallowed inside indexing expressions, in order to enable the syntax `a[begin]` (for selecting the first element) in the future ([#23354]). diff --git a/src/ast.scm b/src/ast.scm index d0167ac52f7af..0430714f74ad3 100644 --- a/src/ast.scm +++ b/src/ast.scm @@ -6,8 +6,7 @@ (if (has-parameters? l) (string (string.join (map deparse (cdr l)) sep) (if (length= (cdr l) 1) "," "") - "; " - (string.join (map deparse (cdar l)) ", ")) + (deparse (car l))) (string.join (map deparse l) sep))) (define (deparse-prefix-call head args opn cls) @@ -95,6 +94,7 @@ (if (length= e 3) (deparse (caddr e)) (deparse (cons 'braces (cddr e)))))) + ((parameters) (string "; " (deparse-arglist (cdr e)))) ;; bracket forms ((tuple) (string #\( (deparse-arglist (cdr e)) @@ -406,6 +406,7 @@ (eq? (cadr (caddr x)) 'Vararg))))) (define (trans? x) (and (pair? x) (eq? (car x) '|.'|))) (define (ctrans? x) (and (pair? x) (eq? (car x) '|'|))) +(define (linenum? x) (and (pair? x) (eq? (car x) 'line))) (define (make-assignment l r) `(= ,l ,r)) (define (assignment? e) (and (pair? e) (eq? (car e) '=))) diff --git a/src/julia-parser.scm b/src/julia-parser.scm index d4e877f1ef631..24511fb6aef6d 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -675,8 +675,7 @@ (memv (peek-token s) ops)) (loop ex #f (peek-token s)) (if (and add-linenums - (not (and (pair? (car ex)) - (eq? (caar ex) 'line)))) + (not (linenum? (car ex)))) (let ((loc (line-number-node s))) (loop (list* (down s) loc ex) #f (peek-token s))) (loop (cons (down s) ex) #f (peek-token s)))))))))) @@ -1018,6 +1017,12 @@ (parse-unary-call s op (unary-op? op) spc))) (parse-juxtapose (parse-factor s) s)))) +(define (fix-syntactic-unary e) + (let ((ce (car e))) + (if (or (eq? ce '|<:|) (eq? ce '|>:|)) + e + (cons 'call e)))) + (define (parse-unary-call s op un spc) (let ((next (peek-token s))) (cond ((or (closing-token? next) (newline? next) (eq? next '=)) @@ -1026,17 +1031,25 @@ (and (not un) (eqv? next #\( ))) (ts:put-back! s op spc) (parse-factor s)) + ((eqv? next #\( ) + (take-token s) + (let* ((opspc (ts:space? s)) + (parens (with-bindings ((accept-dots-without-comma #t)) + (parse-paren- s #t)))) + (if (cdr parens) ;; found an argument list + (if opspc + (disallowed-space op #\( ) + (parse-factor-with-initial-ex + s + (fix-syntactic-unary (cons op (tuple-to-arglist (car parens)))))) + (fix-syntactic-unary + (list op (parse-factor-with-initial-ex s (car parens))))))) ((not un) (error (string "\"" op "\" is not a unary operator"))) (else - (let* ((arg (with-bindings ((accept-dots-without-comma #t)) - (parse-unary s))) - (args (if (and (pair? arg) (eq? (car arg) 'tuple)) - (cons op (cdr arg)) - (list op arg)))) - (if (or (eq? op '|<:|) (eq? op '|>:|)) - args - (cons 'call args))))))) + (let ((arg (with-bindings ((accept-dots-without-comma #t)) + (parse-unary s)))) + (fix-syntactic-unary (list op arg))))))) (define block-form? (Set '(block quote if for while let function macro abstract primitive struct try module))) @@ -1084,11 +1097,24 @@ ;; handle ^ and .^ ;; -2^3 is parsed as -(2^3), so call parse-decl for the first argument, ;; and parse-unary from then on (to handle 2^-3) -(define (parse-factor s) (parse-RtoL s parse-decl is-prec-power? #f parse-factor-after)) +(define (parse-factor s) + (parse-factor-with-initial-ex s (parse-call s))) + +(define (parse-factor-with-initial-ex s ex0) + (let* ((ex (parse-decl-with-initial-ex s ex0)) + (t (peek-token s))) + (if (is-prec-power? t) + (begin (take-token s) + (list 'call t ex (parse-factor-after s))) + ex))) + (define (parse-factor-after s) (parse-RtoL s parse-unary is-prec-power? #f parse-factor-after)) (define (parse-decl s) - (let loop ((ex (parse-call s))) + (parse-decl-with-initial-ex s (parse-call s))) + +(define (parse-decl-with-initial-ex s ex) + (let loop ((ex ex)) (let ((t (peek-token s))) (case t ((|::|) (take-token s) @@ -1140,7 +1166,7 @@ (define (disallowed-space ex t) (error (string "space before \"" t "\" not allowed in \"" - (deparse ex) " " (deparse t) "\""))) + (deparse ex) " " t "\""))) ;; string macro suffix for given delimiter t (define (macsuffix t) @@ -1323,7 +1349,7 @@ (blk (parse-block s (lambda (s) (parse-docstring s parse-eq))))) (expect-end s word) (let ((blk (if (and (length> blk 1) - (pair? (cadr blk)) (eq? (caadr blk) 'line)) + (linenum? (cadr blk))) (list* 'block loc (cddr blk)) blk))) (if (eq? word 'quote) @@ -1347,7 +1373,7 @@ (error "let variables should end in \";\" or newline")) (let* ((ex (begin0 (parse-block s) (expect-end s word))) - (ex (if (and (length= ex 2) (pair? (cadr ex)) (eq? (caadr ex) 'line)) + (ex (if (and (length= ex 2) (linenum? (cadr ex))) `(block) ;; don't need line info in an empty let block ex))) `(let ,(if (and (length= binds 1) (or (assignment? (car binds)) (decl? (car binds)) @@ -1500,8 +1526,7 @@ catch-block `(block ,loc ,var ,@(if (and (length= catch-block 2) - (pair? (cadr catch-block)) - (eq? (caadr catch-block) 'line)) + (linenum? (cadr catch-block))) '() (cdr catch-block)))) (if var? var 'false) @@ -1698,7 +1723,7 @@ ;; . an extra comma at the end is allowed ;; . expressions after a ; are enclosed in (parameters ...) ;; . an expression followed by ... becomes (... x) -(define (parse-arglist s closer) +(define (parse-arglist s closer (add-linenums #f)) (with-bindings ((range-colon-enabled #t) (space-sensitive #f) (where-enabled #t) @@ -1712,16 +1737,18 @@ (to-kws (reverse! lst)) (reverse! lst))) (if (eqv? t #\;) - (begin (take-token s) - (if (eqv? (peek-token s) closer) - ;; allow f(a, b; ) - (loop lst) - (let ((params (loop '())) - (lst (if (eqv? closer #\) ) - (to-kws (reverse lst)) - (reverse lst)))) - (cons (cons 'parameters params) - lst)))) + (begin (take-token s) (require-token s) + (let ((loc (line-number-node s)) + (params (loop '())) + (lst (if (eqv? closer #\) ) + (to-kws (reverse lst)) + (reverse lst)))) + (cons `(parameters + ,@(if add-linenums + (list loc) + '()) + ,@params) + lst))) (let* ((nxt (parse-eq* s)) (c (require-token s))) (cond ((eqv? c #\,) @@ -1766,7 +1793,7 @@ (if (eqv? (require-token s) closer) (loop lst nxt) (let ((params (parse-call-arglist s closer))) - (if (null? params) + (if (or (null? params) (equal? params '((parameters)))) (begin (parser-depwarn s (deparse `(vect (parameters) ,@(reverse lst) ,nxt)) (deparse `(vcat ,@(reverse lst) ,nxt))) ;; TODO: post 0.7, remove deprecation and change parsing to 'vect @@ -1886,20 +1913,36 @@ ;; this allows us to first parse tuples using parse-arglist (define (parameters-to-block e) (if (and (pair? e) (eq? (car e) 'parameters)) - (cond ((length= e 1) '()) - ((length= e 2) (parameters-to-block (cadr e))) - ((length= e 3) - (let ((fst (cadr e)) - (snd (caddr e))) - (if (and (pair? fst) (eq? (car fst) 'parameters)) - (let ((rec (parameters-to-block fst)) - (snd (parameters-to-block snd))) - (and rec snd - (cons (car snd) rec))) - #f))) - (else #f)) + (let ((e2 (filter (lambda (x) (not (linenum? x))) e)) + (lnum (if (and (pair? (cdr e)) + (linenum? (cadr e))) + (cadr e) + #f))) + (cond ((length= e2 1) '()) + ((length= e2 2) + (let ((rec (parameters-to-block (cadr e2)))) + (if (null? rec) + rec + (cons lnum rec)))) + ((length= e2 3) + (let ((fst (cadr e2)) + (snd (caddr e2))) + (if (and (pair? fst) (eq? (car fst) 'parameters)) + (let ((rec (parameters-to-block fst)) + (snd (parameters-to-block snd))) + (and rec snd + (append (if lnum (list lnum) '()) (cons (car snd) rec)))) + #f))) + (else #f))) (list (kw-to-= e)))) +(define (rm-linenums e) + (if (atom? e) e + (map rm-linenums + (if (eq? (car e) 'parameters) + (filter (lambda (x) (not (linenum? x))) e) + e)))) + ;; convert an arglist to a tuple or block expr ;; leading-semi? means we saw (; ...) ;; comma? means there was a comma after the first expression @@ -1914,9 +1957,100 @@ `(block)))))) ;; all semicolons inside () (and (null? first) (null? args) (not comma?) `(block)) ;; this case is (;) - (if (and (pair? args) (pair? (car args)) (eq? (caar args) 'parameters)) - `(tuple ,(car args) ,@first ,@(map kw-to-= (cdr args))) - `(tuple ,@first ,@(map kw-to-= args)))))) + (rm-linenums + (if (and (pair? args) (pair? (car args)) (eq? (caar args) 'parameters)) + `(tuple ,(car args) ,@first ,@(map kw-to-= (cdr args))) + `(tuple ,@first ,@(map kw-to-= args))))))) + +(define (tuple-to-arglist e) + (cond ((eq? (car e) 'tuple) (map =-to-kw (cdr e))) + ((eq? (car e) 'block) + (cond ((length= e 1) '()) + ((length= e 2) (list (cadr e))) + ((length= e 3) + (if (assignment? (caddr e)) + `((parameters (kw ,@(cdr (caddr e)))) ,(cadr e)) + `((parameters ,(caddr e)) ,(cadr e)))) + (else + (error "more than one semicolon in argument list")))) + (else + (list (=-to-kw e))))) + +(define (parse-paren s (checked #t)) (car (parse-paren- s checked))) + +;; return (expr . arglist) where arglist is #t iff this isn't just a parenthesized expr +(define (parse-paren- s checked) + (with-bindings + ((range-colon-enabled #t) + (space-sensitive #f) + (where-enabled #t) + (whitespace-newline #t)) + (let ((nxt (require-token s))) + (cond + ((eqv? nxt #\) ) + ;; empty tuple () + (begin (take-token s) '((tuple) . #t))) + ((syntactic-op? nxt) + ;; allow (=) etc. + (let ((tok (take-token s))) + (if (not (eqv? (require-token s) #\) )) + (error (string "invalid identifier name \"" tok "\"")) + (take-token s)) + (if checked (check-identifier tok)) + (cons tok #f))) + ;; allow :(::) as a special case + ((and (not checked) (eq? nxt '|::|) + (let ((spc (ts:space? s))) + (or (and (take-token s) (eqv? (require-token s) #\) )) + (and (ts:put-back! s '|::| spc) #f)))) + (take-token s) ;; take #\) + '(|::| . #f)) + ((eqv? nxt #\;) + (let ((ex (arglist-to-tuple #t #f (parse-arglist s #\) )))) + (cons ex (eq? (car ex) 'tuple)))) + (else + ;; here we parse the first subexpression separately, so + ;; we can look for a comma to see if it's a tuple. + ;; this lets us distinguish (x) from (x,) + (let* ((ex (parse-eq* s)) + (t (require-token s))) + (cond ((eqv? t #\) ) + (take-token s) + ;; value in parentheses (x) + (if (vararg? ex) + (let ((lineno (input-port-line (ts:port s)))) + (if (or accept-dots-without-comma (eq? (with-bindings ((whitespace-newline #f)) + (peek-token s)) + '->)) + (cons ex #t) + (begin (parser-depwarn lineno + (string "(" (deparse (cadr ex)) "...)") + (string "(" (deparse (cadr ex)) "...,)")) + (cons `(tuple ,ex) #t)))) + (cons ex #f))) + ((eqv? t #\,) + ;; tuple (x,) (x,y) etc. + (take-token s) + (cons (arglist-to-tuple #f #t (parse-arglist s #\) ) ex) + #t)) + ((eqv? t #\;) + (cons (arglist-to-tuple + #f + ;; consider `(x...; ` the start of an arglist, since it's not useful as a block + (vararg? ex) + (parse-arglist s #\) #t) + ex) + (vararg? ex))) + ((eq? t 'for) + (expect-space-before s 'for) + (take-token s) + (let ((gen (parse-generator s ex))) + (if (eqv? (require-token s) #\) ) + (take-token s) + (error "expected \")\"")) + (cons gen #f))) + (else + (error "missing comma or ) in argument list"))))))))) (define (not-eof-for delim c) (if (eof-object? c) @@ -2202,67 +2336,7 @@ ;; parens or tuple ((eqv? t #\( ) (take-token s) - (with-bindings ((range-colon-enabled #t) - (space-sensitive #f) - (where-enabled #t) - (whitespace-newline #t)) - (let ((nxt (require-token s))) - (cond - ((eqv? nxt #\) ) - ;; empty tuple () - (begin (take-token s) '(tuple))) - ((syntactic-op? nxt) - ;; allow (=) etc. - (let ((tok (take-token s))) - (if (not (eqv? (require-token s) #\) )) - (error (string "invalid identifier name \"" tok "\"")) - (take-token s)) - (if checked (check-identifier tok)) - tok)) - ;; allow :(::) as a special case - ((and (not checked) (eq? nxt '|::|) - (let ((spc (ts:space? s))) - (or (and (take-token s) (eqv? (require-token s) #\) )) - (and (ts:put-back! s '|::| spc) #f)))) - (take-token s) ;; take #\) - '|::|) - ((eqv? nxt #\;) - (arglist-to-tuple #t #f (parse-arglist s #\) ))) - (else - ;; here we parse the first subexpression separately, so - ;; we can look for a comma to see if it's a tuple. - ;; this lets us distinguish (x) from (x,) - (let* ((ex (parse-eq* s)) - (t (require-token s))) - (cond ((eqv? t #\) ) - (take-token s) - ;; value in parentheses (x) - (if (and (pair? ex) (eq? (car ex) '...)) - (let ((lineno (input-port-line (ts:port s)))) - (if (or accept-dots-without-comma (eq? (with-bindings ((whitespace-newline #f)) - (peek-token s)) - '->)) - ex - (begin (parser-depwarn lineno - (string "(" (deparse (cadr ex)) "...)") - (string "(" (deparse (cadr ex)) "...,)")) - `(tuple ,ex)))) - ex)) - ((eq? t 'for) - (expect-space-before s 'for) - (take-token s) - (let ((gen (parse-generator s ex))) - (if (eqv? (require-token s) #\) ) - (take-token s) - (error "expected \")\"")) - gen)) - (else - ;; tuple (x,) (x,y) (x...) etc. - (if (eqv? t #\, ) - (take-token s) - (if (not (eqv? t #\;)) - (error "missing comma or ) in argument list"))) - (arglist-to-tuple #f (eqv? t #\,) (parse-arglist s #\) ) ex))))))))) + (parse-paren s checked)) ;; cat expression ((eqv? t #\[ ) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 02bae99181038..b8b28ba460a9d 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -766,7 +766,7 @@ (if (and (pair? body) (eq? (car body) 'block)) (cond ((atom? (cdr body)) `(block ,stmt (null))) - ((and (pair? (cadr body)) (eq? (caadr body) 'line)) + ((linenum? (cadr body)) `(block ,(cadr body) ,stmt ,@(cddr body))) (else `(block ,stmt ,@(cdr body)))) @@ -805,8 +805,7 @@ ,(ctor-body body curlyargs)))))) (define (function-body-lineno body) - (let ((lnos (filter (lambda (e) (and (pair? e) (eq? (car e) 'line))) - body))) + (let ((lnos (filter linenum? body))) (if (null? lnos) '() (car lnos)))) ;; rewrite calls to `new( ... )` to `new` expressions on the appropriate @@ -863,7 +862,7 @@ (fields defs) (separate (lambda (x) (or (symbol? x) (decl? x))) fields0) (let* ((defs (filter (lambda (x) (not (effect-free? x))) defs)) - (locs (if (and (pair? fields0) (pair? (car fields0)) (eq? (caar fields0) 'line)) + (locs (if (and (pair? fields0) (linenum? (car fields0))) (list (car fields0)) '())) (field-names (map decl-var fields)) @@ -1103,18 +1102,7 @@ (set! a (cadr w)))) #f)) (argl (if (pair? a) - (if (eq? (car a) 'tuple) - (map =-to-kw (cdr a)) - (if (eq? (car a) 'block) - (cond ((length= a 1) '()) - ((length= a 2) (list (cadr a))) - ((length= a 3) - (if (assignment? (caddr a)) - `((parameters (kw ,@(cdr (caddr a)))) ,(cadr a)) - `((parameters ,(caddr a)) ,(cadr a)))) - (else - (error "more than one semicolon in argument list"))) - (list (=-to-kw a)))) + (tuple-to-arglist (filter (lambda (x) (not (linenum? x))) a)) (list a))) ;; TODO: always use a specific special name like #anon# or _, then ignore ;; this as a local variable name. @@ -1249,7 +1237,7 @@ (if (null? f) '() (let ((x (car f))) - (cond ((or (symbol? x) (decl? x) (and (pair? x) (eq? (car x) 'line))) + (cond ((or (symbol? x) (decl? x) (linenum? x)) (loop (cdr f))) ((and (assignment? x) (or (symbol? (cadr x)) (decl? (cadr x)))) (error (string "\"" (deparse x) "\" inside type definition is reserved"))) @@ -1904,8 +1892,7 @@ (lambda (e) (cond ((null? (cdr e)) '(null)) ((and (null? (cddr e)) - (not (and (pair? (cadr e)) - (eq? (car (cadr e)) 'line)))) + (not (linenum? (cadr e)))) (expand-forms (cadr e))) (else (cons 'block @@ -3619,7 +3606,7 @@ f(x) = yt(x) ((block body) (let* ((last-fname filename) (fnm (first-non-meta e)) - (fname (if (and (length> e 1) (pair? fnm) (eq? (car fnm) 'line) + (fname (if (and (length> e 1) (linenum? fnm) (length> fnm 2)) (caddr fnm) filename)) diff --git a/test/syntax.jl b/test/syntax.jl index f861b84de34b2..04e982cb7ebb8 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -176,13 +176,21 @@ macro test999_str(args...); args; end @test_throws ParseError Meta.parse("(,)") @test_throws ParseError Meta.parse("(;,)") @test_throws ParseError Meta.parse("(,;)") +# TODO: would be nice to make these errors, but needed to parse e.g. `(x;y,)->x` +#@test_throws ParseError Meta.parse("(1;2,)") +#@test_throws ParseError Meta.parse("(1;2,;)") +#@test_throws ParseError Meta.parse("(1;2,;3)") @test Meta.parse("(x;)") == Expr(:block, :x) @test Meta.parse("(;x)") == Expr(:tuple, Expr(:parameters, :x)) @test Meta.parse("(;x,)") == Expr(:tuple, Expr(:parameters, :x)) @test Meta.parse("(x,)") == Expr(:tuple, :x) -@test Meta.parse("(x,;)") == Expr(:tuple, :x) -@test Meta.parse("(x;y)") == Expr(:block, :x, :y) -@test Meta.parse("(x=1;y=2)") == Expr(:block, Expr(:(=), :x, 1), Expr(:(=), :y, 2)) +@test Meta.parse("(x,;)") == Expr(:tuple, Expr(:parameters), :x) +@test Meta.parse("(x;y)") == Expr(:block, :x, LineNumberNode(1,:none), :y) +@test Meta.parse("(x...;)") == Expr(:tuple, Expr(:parameters), Expr(:(...), :x)) +@test Meta.parse("(;x...)") == Expr(:tuple, Expr(:parameters, Expr(:(...), :x))) +@test Meta.parse("(x...;y)") == Expr(:tuple, Expr(:parameters, :y), Expr(:(...), :x)) +@test Meta.parse("(x;y...)") == Expr(:block, :x, LineNumberNode(1,:none), Expr(:(...), :y)) +@test Meta.parse("(x=1;y=2)") == Expr(:block, Expr(:(=), :x, 1), LineNumberNode(1,:none), Expr(:(=), :y, 2)) @test Meta.parse("(x,;y)") == Expr(:tuple, Expr(:parameters, :y), :x) @test Meta.parse("(x,;y=1)") == Expr(:tuple, Expr(:parameters, Expr(:kw, :y, 1)), :x) @test Meta.parse("(x,a;y=1)") == Expr(:tuple, Expr(:parameters, Expr(:kw, :y, 1)), :x, :a) @@ -190,6 +198,8 @@ macro test999_str(args...); args; end @test Meta.parse("(a=1, b=2)") == Expr(:tuple, Expr(:(=), :a, 1), Expr(:(=), :b, 2)) @test_throws ParseError Meta.parse("(1 2)") # issue #15248 +@test Meta.parse("f(x;)") == Expr(:call, :f, Expr(:parameters), :x) + @test Meta.parse("1 == 2|>3") == Expr(:call, :(==), 1, Expr(:call, :(|>), 2, 3)) # issue #24153 @@ -1306,3 +1316,28 @@ end #@test Meta.parse("\"x\" # # f(x) = x", 1)[1] === "x" + +# issue #26137 +# cases where parens enclose argument lists +@test Meta.parse("-()^2") == Expr(:call, :^, Expr(:call, :-), 2) +@test Meta.parse("-(x,)^2") == Expr(:call, :^, Expr(:call, :-, :x), 2) +@test Meta.parse("-(x,;)^2") == Expr(:call, :^, Expr(:call, :-, Expr(:parameters), :x), 2) +@test Meta.parse("-(;x)^2") == Expr(:call, :^, Expr(:call, :-, Expr(:parameters, :x)), 2) +@test Meta.parse("-(x,y)^2") == Expr(:call, :^, Expr(:call, :-, :x, :y), 2) +@test Meta.parse("-(x...)^2") == Expr(:call, :^, Expr(:call, :-, Expr(:(...), :x)), 2) +@test Meta.parse("-(x...;)^2") == Expr(:call, :^, Expr(:call, :-, Expr(:parameters), Expr(:(...), :x)), 2) +@test Meta.parse("-(x...;)") == Expr(:call, :-, Expr(:parameters), Expr(:(...), :x)) + +# cases where parens are just grouping +@test Meta.parse("-(x)^2") == Expr(:call, :-, Expr(:call, :^, :x, 2)) +@test Meta.parse("-(a=1)^2") == Expr(:call, :-, Expr(:call, :^, Expr(:(=), :a, 1), 2)) +@test Meta.parse("-(x;y)^2") == Expr(:call, :-, Expr(:call, :^, Expr(:block, :x, LineNumberNode(1,:none), :y), 2)) +@test Meta.parse("-(;)^2") == Expr(:call, :-, Expr(:call, :^, Expr(:block), 2)) +@test Meta.parse("-(;;;;)^2") == Expr(:call, :-, Expr(:call, :^, Expr(:block), 2)) +@test Meta.parse("-(x;;;)^2") == Expr(:call, :-, Expr(:call, :^, Expr(:block, :x), 2)) +@test Meta.parse("+((1,2))") == Expr(:call, :+, Expr(:tuple, 1, 2)) + +@test_throws ParseError("space before \"(\" not allowed in \"+ (\"") Meta.parse("1 -+ (a=1, b=2)") + +@test Meta.parse("1 -+(a=1, b=2)") == Expr(:call, :-, 1, + Expr(:call, :+, Expr(:kw, :a, 1), Expr(:kw, :b, 2)))