diff --git a/src/julia-parser.scm b/src/julia-parser.scm index c1d889a686a31..d0ad7fc3a3398 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -1599,32 +1599,43 @@ (else (parse-matrix s first closer #f)))))))))) -; for sequenced evaluation inside expressions: e.g. (a;b, c;d) -(define (parse-stmts-within-expr s) - (parse-Nary s parse-eq* '(#\;) 'block '(#\, #\) ) #t)) - -(define (parse-tuple s first) - (let loop ((lst '()) - (nxt first)) - (case (require-token s) - ((#\)) - (take-token s) - (cons 'tuple (reverse (cons nxt lst)))) - ((#\,) - (take-token s) - (if (eqv? (require-token s) #\)) - ;; allow ending with , - (begin (take-token s) - (cons 'tuple (reverse (cons nxt lst)))) - (loop (cons nxt lst) (parse-eq* s)))) - ((#\;) - (error "unexpected semicolon in tuple")) - #;((#\newline) - (error "unexpected line break in tuple")) - ((#\] #\}) - (error (string "unexpected \"" (peek-token s) "\" in tuple"))) - (else - (error "missing separator in tuple"))))) +;; translate nested (parameters ...) expressions to a statement block if possible +;; 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)) + (list (if (kwarg? e) + (cons '= (cdr 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 +(define (arglist-to-tuple leading-semi? comma? args . first) + (if (and (pair? first) (null? args) (not leading-semi?) (not comma?)) + `(block ,@first) ;; this case is (x;) + (or (and (not comma?) (length= args 1) (pair? (car args)) (eq? (caar args) 'parameters) + (let ((blk (parameters-to-block (car args)))) + (and blk (or (and (not leading-semi?) + `(block ,@first ,@blk)) + (and (null? first) (null? blk) + `(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 ,@(cdr args)) + `(tuple ,@first ,@args))))) (define (not-eof-2 c) (if (eof-object? c) @@ -1924,6 +1935,8 @@ (error (string "invalid identifier name \"" tok "\"")) (take-token s)) tok)) + ((eqv? (peek-token s) #\;) + (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. @@ -1937,29 +1950,11 @@ `(tuple ,ex) ;; value in parentheses (x) ex)) - ((eqv? t #\, ) - ;; tuple (x,) (x,y) (x...) etc. - (parse-tuple s ex)) - ((eqv? t #\;) - ;; parenthesized block (a;b;c) - (take-token s) - (if (eqv? (require-token s) #\)) - ;; (ex;) - (begin (take-token s) `(block ,ex)) - (let* ((blk (parse-stmts-within-expr s)) - (tok (require-token s))) - (if (eqv? tok #\,) - (error "unexpected comma in statement block")) - (if (not (eqv? tok #\))) - (error "missing separator in statement block")) - (take-token s) - `(block ,ex ,blk)))) - #;((eqv? t #\newline) - (error "unexpected line break in tuple")) - ((memv t '(#\] #\})) - (error (string "unexpected \"" t "\" in tuple"))) (else - (error "missing separator in tuple"))))))))) + ;; tuple (x,) (x,y) (x...) etc. + (if (eqv? t #\, ) + (take-token s)) + (arglist-to-tuple #f (eqv? t #\,) (parse-arglist s #\) ) ex))))))))) ;; cell expression ((eqv? t #\{ ) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 4e81db744523a..cea3072d1c881 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -961,8 +961,19 @@ ((->) (let ((a (cadr e)) (body (caddr e))) - (let ((argl (if (and (pair? a) (eq? (car a) 'tuple)) - (cdr a) + (let ((argl (if (pair? a) + (if (eq? (car a) 'tuple) + (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 a))) (list a))) ;; TODO: always use a specific special name like #anon# or _, then ignore ;; this as a local variable name. @@ -1616,11 +1627,10 @@ 'tuple (lambda (e) - (for-each (lambda (x) - ;; assignment inside tuple looks like a keyword argument - (if (assignment? x) - (error "assignment not allowed inside tuple"))) - (cdr e)) + (if (and (length> e 1) (pair? (cadr e)) (eq? (caadr e) 'parameters)) + (error "unexpected semicolon in tuple")) + (if (any assignment? (cdr e)) + (error "assignment not allowed inside tuple")) (expand-forms `(call (top tuple) ,@(cdr e)))) 'dict diff --git a/test/keywordargs.jl b/test/keywordargs.jl index 5ffa4aeea3980..5d1d868ab6194 100644 --- a/test/keywordargs.jl +++ b/test/keywordargs.jl @@ -185,3 +185,11 @@ let @test kwf1(0; p, q) == 310 @test kwf1(0; q, hundreds=4) == 410 end + +# with anonymous functions, issue #2773 +let f = (x;a=1,b=2)->(x, a, b) + @test f(0) === (0, 1, 2) + @test f(1,a=10,b=20) === (1,10,20) + @test f(0,b=88) === (0, 1, 88) + @test_throws ErrorException f(0,z=1) +end diff --git a/test/parse.jl b/test/parse.jl index 6c57b27b943ed..b9ea7d3a4ca38 100644 --- a/test/parse.jl +++ b/test/parse.jl @@ -151,6 +151,25 @@ macro f(args...) end; @f "" Expr(:macro, Expr(:call, :f, Expr(:..., :args)), Expr(:block,)), Expr(:macrocall, symbol("@f"), "")) +# blocks vs. tuples +@test parse("()") == Expr(:tuple) +@test parse("(;)") == Expr(:block) +@test parse("(;;;;)") == Expr(:block) +@test_throws ParseError parse("(,)") +@test_throws ParseError parse("(;,)") +@test_throws ParseError parse("(,;)") +@test parse("(x;)") == Expr(:block, :x) +@test parse("(;x)") == Expr(:tuple, Expr(:parameters, :x)) +@test parse("(;x,)") == Expr(:tuple, Expr(:parameters, :x)) +@test parse("(x,)") == Expr(:tuple, :x) +@test parse("(x,;)") == Expr(:tuple, :x) +@test parse("(x;y)") == Expr(:block, :x, :y) +@test parse("(x=1;y=2)") == Expr(:block, Expr(:(=), :x, 1), Expr(:(=), :y, 2)) +@test parse("(x,;y)") == Expr(:tuple, Expr(:parameters, :y), :x) +@test parse("(x,;y=1)") == Expr(:tuple, Expr(:parameters, Expr(:kw, :y, 1)), :x) +@test parse("(x,a;y=1)") == Expr(:tuple, Expr(:parameters, Expr(:kw, :y, 1)), :x, :a) +@test parse("(x,a;y=1,z=2)") == Expr(:tuple, Expr(:parameters, Expr(:kw,:y,1), Expr(:kw,:z,2)), :x, :a) + # integer parsing @test is(parse(Int32,"0",36),Int32(0)) @test is(parse(Int32,"1",36),Int32(1))