Skip to content

Commit

Permalink
fix #2773, keyword arguments for anonymous functions
Browse files Browse the repository at this point in the history
  • Loading branch information
JeffBezanson committed Jan 15, 2016
1 parent f4210e2 commit 55a8b82
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 55 deletions.
91 changes: 43 additions & 48 deletions src/julia-parser.scm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand All @@ -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 #\{ )
Expand Down
24 changes: 17 additions & 7 deletions src/julia-syntax.scm
Original file line number Diff line number Diff line change
Expand Up @@ -955,8 +955,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.
Expand Down Expand Up @@ -1607,11 +1618,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
Expand Down
8 changes: 8 additions & 0 deletions test/keywordargs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
19 changes: 19 additions & 0 deletions test/parse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down

0 comments on commit 55a8b82

Please sign in to comment.