From 296d5d4f2939f9e11751ec62d2662ba53f067426 Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Thu, 21 Jun 2018 17:32:18 -0400 Subject: [PATCH] fix #27641, give juxtaposition lower precedence than unary operators --- NEWS.md | 3 + .../integers-and-floating-point-numbers.md | 8 +- src/julia-parser.scm | 103 +++++++++--------- test/syntax.jl | 16 ++- 4 files changed, 73 insertions(+), 57 deletions(-) diff --git a/NEWS.md b/NEWS.md index ec04c0ab218ed..e5dac25a4e606 100644 --- a/NEWS.md +++ b/NEWS.md @@ -72,6 +72,9 @@ Language changes * Juxtaposing binary, octal, and hexadecimal literals is deprecated, since it can lead to confusing code such as `0xapi == 0xa * pi` ([#16356]). + * Numeric literal juxtaposition now has slighty lower precedence than unary operators, + so for example `√2x` parses as `(√2) * x` ([#27641]). + * Declaring arguments as `x::ANY` to avoid specialization has been replaced by `@nospecialize x`. ([#22666]). diff --git a/doc/src/manual/integers-and-floating-point-numbers.md b/doc/src/manual/integers-and-floating-point-numbers.md index ab2f2a6733cf1..b218ab8d582e4 100644 --- a/doc/src/manual/integers-and-floating-point-numbers.md +++ b/doc/src/manual/integers-and-floating-point-numbers.md @@ -618,8 +618,12 @@ julia> 2^2x 64 ``` -The precedence of numeric literal coefficients is the same as that of unary operators such as -negation. So `2^3x` is parsed as `2^(3x)`, and `2x^3` is parsed as `2*(x^3)`. +The precedence of numeric literal coefficients is slightly lower than that of +unary operators such as negation. +So `-2x` is parsed as `(-2) * x` and `√2x` is parsed as `(√2) * x`. +However, numeric literal coefficients parse similarly to unary operators when +combined with exponentiation. +For example `2^3x` is parsed as `2^(3x)`, and `2x^3` is parsed as `2*(x^3)`. Numeric literals also work as coefficients to parenthesized expressions: diff --git a/src/julia-parser.scm b/src/julia-parser.scm index 16ba21c6805ba..a4c4de20886b8 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -29,6 +29,7 @@ (define prec-times (add-dots '(* / ÷ % & ⋅ ∘ × |\\| ∩ ∧ ⊗ ⊘ ⊙ ⊚ ⊛ ⊠ ⊡ ⊓ ∗ ∙ ∤ ⅋ ≀ ⊼ ⋄ ⋆ ⋇ ⋉ ⋊ ⋋ ⋌ ⋏ ⋒ ⟑ ⦸ ⦼ ⦾ ⦿ ⧶ ⧷ ⨇ ⨰ ⨱ ⨲ ⨳ ⨴ ⨵ ⨶ ⨷ ⨸ ⨻ ⨼ ⨽ ⩀ ⩃ ⩄ ⩋ ⩍ ⩎ ⩑ ⩓ ⩕ ⩘ ⩚ ⩜ ⩞ ⩟ ⩠ ⫛ ⊍ ▷ ⨝ ⟕ ⟖ ⟗))) (define prec-rational (add-dots '(//))) ;; `where` +;; implicit multiplication (juxtaposition) ;; unary (define prec-power (add-dots '(^ ↑ ↓ ⇵ ⟰ ⟱ ⤈ ⤉ ⤊ ⤋ ⤒ ⤓ ⥉ ⥌ ⥍ ⥏ ⥑ ⥔ ⥕ ⥘ ⥙ ⥜ ⥝ ⥠ ⥡ ⥣ ⥥ ⥮ ⥯ ↑ ↓))) (define prec-decl '(|::|)) @@ -950,13 +951,13 @@ ;; parse <:{T}(x::T) or <:(x::T) like other unary operators ((or (eqv? next #\{) (eqv? next #\( )) (ts:put-back! s op spc) - (parse-where s parse-unary)) + (parse-where s parse-juxtapose)) (else - (let ((arg (parse-where s parse-unary))) + (let ((arg (parse-where s parse-juxtapose))) (if (and (pair? arg) (eq? (car arg) 'tuple)) (cons op (cdr arg)) (list op arg))))))) - (parse-where s parse-unary)))) + (parse-where s parse-juxtapose)))) (define (parse-where-chain s first) (with-bindings ((where-enabled #f)) @@ -979,6 +980,46 @@ (parse-where-chain s ex) ex))) +;; given an expression and the next token, is there a juxtaposition +;; operator between them? +(define (juxtapose? s expr t) + (and (or (number? expr) + (large-number? expr) + (and (not (number? t)) ;; disallow "x.3" and "sqrt(2)2" + (not (eqv? t #\@)) ;; disallow "x@time" + ;; issue #16427, disallow juxtaposition with block forms + (not (and (pair? expr) (or (block-form? (car expr)) + (syntactic-unary-op? (car expr)) + (initial-reserved-word? (car expr)))))) + ;; to allow x'y as a special case + #;(and (pair? expr) (memq (car expr) '(|'| |.'|)) + (not (memv t '(#\( #\[ #\{)))) + ) + (not (ts:space? s)) + (not (operator? t)) + (not (closing-token? t)) + (not (newline? t)) + (or (and (not (string? expr)) (not (eqv? t #\"))) + ;; issue #20575 + (error "cannot juxtapose string literal")) + (not (initial-reserved-word? t)) + ;; TODO: this would disallow juxtaposition with 0, which is ambiguous + ;; with e.g. hex literals `0x...`. however this is used for `0im`, which + ;; we might not want to break. + #;(or (not (and (eq? expr 0) + (symbol? t))) + (error (string "invalid numeric constant \"" expr t "\""))))) + +(define (parse-juxtapose s) + (let ((ex (parse-unary s))) + (let ((next (peek-token s))) + (if (juxtapose? s ex next) + (begin + #;(if (and (number? ex) (= ex 0)) + (error "juxtaposition with literal \"0\"")) + `(call * ,ex ,(parse-factor s))) + ex)))) + (define (maybe-negate op num) (if (eq? op '-) (if (large-number? num) @@ -1012,10 +1053,10 @@ ;; unary negation; -2^x parsed as (- (^ 2 x)). (begin (ts:put-back! s (maybe-negate op num) spc) (list 'call op (parse-factor s))) - (parse-juxtapose num s))) + num)) (parse-unary-call s op #t spc))) (parse-unary-call s op (unary-op? op) spc))) - (parse-juxtapose (parse-factor s) s)))) + (parse-factor s)))) (define (fix-syntactic-unary e) (let ((ce (car e))) @@ -1039,13 +1080,11 @@ (if (cdr parens) ;; found an argument list (if opspc (disallowed-space op #\( ) - (parse-juxtapose - (parse-factor-with-initial-ex - s - (fix-syntactic-unary (cons op (tuple-to-arglist (car parens))))) - s)) + (parse-factor-with-initial-ex + s + (fix-syntactic-unary (cons op (tuple-to-arglist (car parens)))))) (fix-syntactic-unary - (list op (parse-juxtapose (parse-factor-with-initial-ex s (car parens)) s)))))) + (list op (parse-factor-with-initial-ex s (car parens))))))) ((not un) (error (string "\"" op "\" is not a unary operator"))) (else @@ -1056,46 +1095,6 @@ (define block-form? (Set '(block quote if for while let function macro abstract primitive struct try module))) -;; given an expression and the next token, is there a juxtaposition -;; operator between them? -(define (juxtapose? s expr t) - (and (or (number? expr) - (large-number? expr) - (and (not (number? t)) ;; disallow "x.3" and "sqrt(2)2" - (not (eqv? t #\@)) ;; disallow "x@time" - ;; issue #16427, disallow juxtaposition with block forms - (not (and (pair? expr) (or (block-form? (car expr)) - (syntactic-unary-op? (car expr)) - (initial-reserved-word? (car expr)))))) - ;; to allow x'y as a special case - #;(and (pair? expr) (memq (car expr) '(|'| |.'|)) - (not (memv t '(#\( #\[ #\{)))) - ) - (not (ts:space? s)) - (not (operator? t)) - (not (closing-token? t)) - (not (newline? t)) - (or (and (not (string? expr)) (not (eqv? t #\"))) - ;; issue #20575 - (error "cannot juxtapose string literal")) - (not (initial-reserved-word? t)) - ;; TODO: this would disallow juxtaposition with 0, which is ambiguous - ;; with e.g. hex literals `0x...`. however this is used for `0im`, which - ;; we might not want to break. - #;(or (not (and (eq? expr 0) - (symbol? t))) - (error (string "invalid numeric constant \"" expr t "\""))))) - -(define (parse-juxtapose ex s) - (let ((next (peek-token s))) - ;; numeric literal juxtaposition is a unary operator - (cond ((juxtapose? s ex next) - (begin - #;(if (and (number? ex) (= ex 0)) - (error "juxtaposition with literal \"0\"")) - `(call * ,ex ,(parse-unary s)))) - (else ex)))) - ;; 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) @@ -1110,7 +1109,7 @@ (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-factor-after s) (parse-RtoL s parse-juxtapose is-prec-power? #f parse-factor-after)) (define (parse-decl s) (parse-decl-with-initial-ex s (parse-call s))) diff --git a/test/syntax.jl b/test/syntax.jl index 30214e0116dd2..7a7b93139feb6 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -1333,16 +1333,26 @@ end @test Meta.parse("1 -+(a=1, b=2)") == Expr(:call, :-, 1, Expr(:call, :+, Expr(:kw, :a, 1), Expr(:kw, :b, 2))) -@test Meta.parse("-(2)(x)") == Expr(:call, :-, Expr(:call, :*, 2, :x)) -@test Meta.parse("-(x)y") == Expr(:call, :-, Expr(:call, :*, :x, :y)) +@test Meta.parse("-(2)(x)") == Expr(:call, :*, Expr(:call, :-, 2), :x) +@test Meta.parse("-(x)y") == Expr(:call, :*, Expr(:call, :-, :x), :y) @test Meta.parse("-(x,)y") == Expr(:call, :*, Expr(:call, :-, :x), :y) @test Meta.parse("-(f)(x)") == Expr(:call, :-, Expr(:call, :f, :x)) -@test Meta.parse("-(2)(x)^2") == Expr(:call, :-, Expr(:call, :*, 2, Expr(:call, :^, :x, 2))) +@test Meta.parse("-(2)(x)^2") == Expr(:call, :*, Expr(:call, :-, 2), Expr(:call, :^, :x, 2)) @test Meta.parse("Y <- (x->true)(X)") == Expr(:call, :<, :Y, Expr(:call, :-, Expr(:call, Expr(:->, :x, Expr(:block, LineNumberNode(1,:none), true)), :X))) +# issue #27641 +@test Meta.parse("√3x") == Expr(:call, :*, Expr(:call, :√, 3), :x) +@test Meta.parse("2^√3x") == Expr(:call, :^, 2, Expr(:call, :*, Expr(:call, :√, 3), :x)) +@test Meta.parse("√2^3") == Expr(:call, :√, Expr(:call, :^, 2, 3)) +@test Meta.parse("-√2") == Expr(:call, :-, Expr(:call, :√, 2)) +@test Meta.parse("√3x^2") == Expr(:call, :*, Expr(:call, :√, 3), Expr(:call, :^, :x, 2)) +@test Meta.parse("-3x^2") == Expr(:call, :*, -3, Expr(:call, :^, :x, 2)) +@test_throws ParseError Meta.parse("2!3") +@test_throws ParseError Meta.parse("2√3") + @test_throws ParseError Meta.parse("a.: b") @test Meta.parse("a.:end") == Expr(:., :a, QuoteNode(:end)) @test Meta.parse("a.:catch") == Expr(:., :a, QuoteNode(:catch))