Skip to content

Commit

Permalink
improve rule for distinguishing tuples (arg lists) from parenthesized…
Browse files Browse the repository at this point in the history
… blocks

a parenthesized expression is a tuple when at least one of these is true:

- it is empty
- there is a comma
- it begins with `x...` and isn't just `(x...)`
- it begins with `(;` (corresponding to the positional part being empty)

anything beginning with `(x;` will be considered a block; you need a
comma (`(x,;)`) to get tuple parsing.

add line numbers to blocks written as `(a; b; c)`
  • Loading branch information
JeffBezanson committed Feb 22, 2018
1 parent be556a9 commit 44557d8
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 33 deletions.
59 changes: 36 additions & 23 deletions src/julia-parser.scm
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,9 @@
(parse-Nary s down '(#\newline #\;) 'block
(lambda (x) (memq x '(end else elseif catch finally))) #t))

(define (parse-parenthesized-block s)
(parse-Nary s parse-eq '(#\;) 'block (lambda (x) (eqv? x #\) )) #t))

;; ";" at the top level produces a sequence of top level expressions
(define (parse-stmts s)
(let ((ex (parse-Nary s (lambda (s) (parse-docstring s parse-eq))
Expand Down Expand Up @@ -1928,19 +1931,15 @@
;; 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 ,@(map kw-to-= (cdr args)))
`(tuple ,@first ,@(map kw-to-= args))))))
(or (and leading-semi? (not comma?) (length= args 1) (pair? (car args)) (eq? (caar args) 'parameters)
(let ((blk (parameters-to-block (car args))))
(and blk (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 ,@(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)))
Expand Down Expand Up @@ -1986,8 +1985,8 @@
(take-token s) ;; take #\)
'(|::| . #f))
((eqv? nxt #\;)
(cons (arglist-to-tuple #t #f (parse-arglist s #\) ))
#t))
(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.
Expand All @@ -1997,7 +1996,7 @@
(cond ((eqv? t #\) )
(take-token s)
;; value in parentheses (x)
(if (and (pair? ex) (eq? (car ex) '...))
(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))
Expand All @@ -2008,6 +2007,26 @@
(string "(" (deparse (cadr ex)) "...,)"))
(cons `(tuple ,ex) #t))))
(cons ex #f)))
((eqv? t #\,)
;; tuple (x,) (x,y) (x...) etc.
(take-token s)
(cons (arglist-to-tuple #f #t (parse-arglist s #\) ) ex)
#t))
((eqv? t #\;)
(if (vararg? ex)
;; consider `(x...; ` the start of an arglist, since it's not useful as a block
(cons (arglist-to-tuple #f #f (parse-arglist s #\) ) ex)
#t)
(begin
(take-token s)
(let ((blk (parse-parenthesized-block s)))
(if (eqv? (require-token s) #\) )
(take-token s)
(error "expected \")\""))
(cons (if (and (length= blk 2) (pair? (cadr blk)) (eq? (caadr blk) 'line))
`(block ,ex)
(list* 'block ex (cdr blk)))
#f)))))
((eq? t 'for)
(expect-space-before s 'for)
(take-token s)
Expand All @@ -2017,13 +2036,7 @@
(error "expected \")\""))
(cons gen #f)))
(else
;; tuple (x,) (x,y) (x...) etc.
(if (eqv? t #\, )
(take-token s)
(if (not (eqv? t #\;))
(error "missing comma or ) in argument list")))
(cons (arglist-to-tuple #f (eqv? t #\,) (parse-arglist s #\) ) ex)
#t)))))))))
(error "missing comma or ) in argument list")))))))))

(define (not-eof-for delim c)
(if (eof-object? c)
Expand Down
2 changes: 1 addition & 1 deletion src/julia-syntax.scm
Original file line number Diff line number Diff line change
Expand Up @@ -1103,7 +1103,7 @@
(set! a (cadr w))))
#f))
(argl (if (pair? a)
(tuple-to-arglist a)
(tuple-to-arglist (filter (lambda (x) (not (and (pair? x) (eq? (car x) 'line)))) a))
(list a)))
;; TODO: always use a specific special name like #anon# or _, then ignore
;; this as a local variable name.
Expand Down
36 changes: 27 additions & 9 deletions test/syntax.jl
Original file line number Diff line number Diff line change
Expand Up @@ -176,13 +176,20 @@ macro test999_str(args...); args; end
@test_throws ParseError Meta.parse("(,)")
@test_throws ParseError Meta.parse("(;,)")
@test_throws ParseError Meta.parse("(,;)")
@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, Expr(:parameters), :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;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)
Expand Down Expand Up @@ -1310,13 +1317,24 @@ end
# f(x) = x", 1)[1] === "x"

# issue #26137
@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, :-, :x), 2)
@test Meta.parse("-(x,y)^2") == Expr(:call, :^, Expr(:call, :-, :x, :y), 2)
@test Meta.parse("+((1,2))") == Expr(:call, :+, Expr(:tuple, 1, 2))
@test Meta.parse("-(x;y)^2") == Expr(:call, :^, Expr(:call, :-, Expr(:parameters, :y), :x), 2)
@test Meta.parse("-(x...)^2") == Expr(:call, :^, Expr(:call, :-, Expr(:(...), :x)), 2)
# 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)")

Expand Down

0 comments on commit 44557d8

Please sign in to comment.