From af4c00535401d9f822a005e218beb4e7aab82f31 Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Wed, 21 Feb 2018 18:03:13 -0500 Subject: [PATCH 1/3] fix #26137, parsing of unary ops on parenthesized expressions --- NEWS.md | 3 + src/julia-parser.scm | 194 +++++++++++++++++++++++++++---------------- src/julia-syntax.scm | 13 +-- test/syntax.jl | 14 ++++ 4 files changed, 140 insertions(+), 84 deletions(-) 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/julia-parser.scm b/src/julia-parser.scm index d4e877f1ef631..00f93738eb22a 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -1018,6 +1018,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 +1032,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 +1098,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 +1167,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) @@ -1918,6 +1945,89 @@ `(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 #\;) + (cons (arglist-to-tuple #t #f (parse-arglist s #\) )) + #t)) + (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)) + '->)) + (cons ex #t) + (begin (parser-depwarn lineno + (string "(" (deparse (cadr ex)) "...)") + (string "(" (deparse (cadr ex)) "...,)")) + (cons `(tuple ,ex) #t)))) + (cons ex #f))) + ((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 + ;; 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))))))))) + (define (not-eof-for delim c) (if (eof-object? c) ;; NOTE: changing this may affect code in base/client.jl @@ -2202,67 +2312,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..9d278465d0fa8 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1103,18 +1103,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 a) (list a))) ;; TODO: always use a specific special name like #anon# or _, then ignore ;; this as a local variable name. diff --git a/test/syntax.jl b/test/syntax.jl index f861b84de34b2..6aa2c6108312f 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -1306,3 +1306,17 @@ end #@test Meta.parse("\"x\" # # 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) + +@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))) From d6b75d1f5bf09d395c58e1129529eee747922348 Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Thu, 22 Feb 2018 11:12:36 -0500 Subject: [PATCH 2/3] preserve empty `parameters` in parser --- src/ast.scm | 4 ++-- src/julia-parser.scm | 17 +++++++---------- test/syntax.jl | 4 +++- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/ast.scm b/src/ast.scm index d0167ac52f7af..6c7c276ba0571 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)) diff --git a/src/julia-parser.scm b/src/julia-parser.scm index 00f93738eb22a..1e444ac5b05d3 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -1740,15 +1740,12 @@ (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)))) + (let ((params (loop '())) + (lst (if (eqv? closer #\) ) + (to-kws (reverse lst)) + (reverse lst)))) + (cons (cons 'parameters params) + lst))) (let* ((nxt (parse-eq* s)) (c (require-token s))) (cond ((eqv? c #\,) @@ -1793,7 +1790,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 diff --git a/test/syntax.jl b/test/syntax.jl index 6aa2c6108312f..9d680fd3761e9 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -180,7 +180,7 @@ macro test999_str(args...); args; end @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,;)") == 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(:tuple, Expr(:parameters, :y), :x) @@ -190,6 +190,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 From 952361f340066d4a02a34065853bf6c120c18539 Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Thu, 22 Feb 2018 14:01:05 -0500 Subject: [PATCH 3/3] improve rule for distinguishing tuples (arg lists) from parenthesized 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) add line numbers to blocks written as `(a; b; c)` --- src/ast.scm | 1 + src/julia-parser.scm | 97 ++++++++++++++++++++++++++++---------------- src/julia-syntax.scm | 16 ++++---- test/syntax.jl | 37 +++++++++++++---- 4 files changed, 98 insertions(+), 53 deletions(-) diff --git a/src/ast.scm b/src/ast.scm index 6c7c276ba0571..0430714f74ad3 100644 --- a/src/ast.scm +++ b/src/ast.scm @@ -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 1e444ac5b05d3..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)))))))))) @@ -1350,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) @@ -1374,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)) @@ -1527,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) @@ -1725,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) @@ -1739,12 +1737,17 @@ (to-kws (reverse! lst)) (reverse! lst))) (if (eqv? t #\;) - (begin (take-token s) - (let ((params (loop '())) + (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 (cons 'parameters params) + (cons `(parameters + ,@(if add-linenums + (list loc) + '()) + ,@params) lst))) (let* ((nxt (parse-eq* s)) (c (require-token s))) @@ -1910,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 @@ -1938,9 +1957,10 @@ `(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))) @@ -1986,8 +2006,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. @@ -1997,7 +2017,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)) @@ -2008,6 +2028,19 @@ (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) @@ -2017,13 +2050,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) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 9d278465d0fa8..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,7 +1102,7 @@ (set! a (cadr w)))) #f)) (argl (if (pair? a) - (tuple-to-arglist 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. @@ -1238,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"))) @@ -1893,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 @@ -3608,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 9d680fd3761e9..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, 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) @@ -1310,13 +1318,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)")