Skip to content

Commit

Permalink
fix #27641, give juxtaposition lower precedence than unary operators
Browse files Browse the repository at this point in the history
  • Loading branch information
JeffBezanson committed Jun 21, 2018
1 parent c1eb3e8 commit 296d5d4
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 57 deletions.
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]).

Expand Down
8 changes: 6 additions & 2 deletions doc/src/manual/integers-and-floating-point-numbers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

This comment has been minimized.

Copy link
@pablosanjose

pablosanjose Jun 26, 2018

Contributor

(Thanks for doing this!)
Might be good to clarify also how 1/2x behaves?

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:

Expand Down
103 changes: 51 additions & 52 deletions src/julia-parser.scm
Original file line number Diff line number Diff line change
Expand Up @@ -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 '(|::|))
Expand Down Expand Up @@ -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))
Expand All @@ -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)
Expand Down Expand Up @@ -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)))
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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)))
Expand Down
16 changes: 13 additions & 3 deletions test/syntax.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down

0 comments on commit 296d5d4

Please sign in to comment.