Skip to content

Commit

Permalink
fix some issues with backquote and simplify its implementation
Browse files Browse the repository at this point in the history
- backquote should yield QuoteNode and LineNumberNode like the parser does
- splatting with no enclosing expression should be an error
- multi-unquoted splat (`$$(xs...)`) didn't work properly
  • Loading branch information
JeffBezanson committed Sep 28, 2017
1 parent 9b415a0 commit 6c4fb39
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 41 deletions.
3 changes: 0 additions & 3 deletions base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,6 @@ struct VecElement{T}
end
VecElement(arg::T) where {T} = VecElement{T}(arg)

# used by lowering of splicing unquote
splicedexpr(hd::Symbol, args::Array{Any,1}) = (e=Expr(hd); e.args=args; e)

_new(typ::Symbol, argty::Symbol) = eval(Core, :($typ(@nospecialize n::$argty) = $(Expr(:new, typ, :n))))
_new(:LabelNode, :Int)
_new(:GotoNode, :Int)
Expand Down
2 changes: 1 addition & 1 deletion base/essentials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,7 @@ end

Val(x) = (@_pure_meta; Val{x}())

# used by interpolating quote and some other things in the front end
# used by keyword arg call lowering
function vector_any(@nospecialize xs...)
n = length(xs)
a = Vector{Any}(n)
Expand Down
74 changes: 37 additions & 37 deletions src/macroexpand.scm
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,49 @@

;; backquote expansion

(define (splice-expr? e)
;; ($ (tuple (... x)))
(and (length= e 2) (eq? (car e) '$)
(length= (cadr e) 2) (eq? (caadr e) 'tuple)
(vararg? (cadadr e))))

(define (wrap-with-splice x)
`(call (core _expr) (inert $)
(call (core _expr) (inert tuple)
(call (core _expr) (inert |...|) ,x))))

(define (julia-bq-bracket x d)
(if (splice-expr? x)
(if (= d 0)
(cadr (cadr (cadr x)))
(list 'call '(top vector_any)
(wrap-with-splice (julia-bq-expand (cadr (cadr (cadr x))) (- d 1)))))
(list 'call '(top vector_any) (julia-bq-expand x d))))

(define (julia-bq-expand x d)
(define splat-token '(__splat__))

(define (bq-expand-arglist lst d)
(let loop ((lst lst)
(out '()))
(if (null? lst)
(reverse! out)
(let ((nxt (julia-bq-expand- (car lst) d)))
(if (and (pair? nxt) (eq? (car nxt) splat-token))
(loop (cdr lst) (revappend (cdr nxt) out))
(loop (cdr lst) (cons nxt out)))))))

(define (julia-bq-expand- x d)
(cond ((or (eq? x 'true) (eq? x 'false)) x)
((or (symbol? x) (ssavalue? x)) (list 'inert x))
((atom? x) x)
((eq? (car x) 'quote)
`(call (core _expr) (inert quote) ,(julia-bq-expand (cadr x) (+ d 1))))
((eq? (car x) '$)
(if (and (= d 0) (length= x 2))
(cadr x)
(if (splice-expr? (cadr x))
`(call (core splicedexpr) (inert $)
(call (top append_any) ,(julia-bq-bracket (cadr x) (- d 1))))
`(call (core _expr) (inert $) ,(julia-bq-expand (cadr x) (- d 1))))))
((and (= d 0) (eq? (car x) '$))
(if (length= x 2)
(if (and (length= (cadr x) 2) (eq? (caadr x) 'tuple)
(vararg? (cadadr x)))
;; splice expr ($ (tuple (... x)))
`(... ,(cadr (cadr (cadr x))))
;; otherwise normal interpolation
(cadr x))
;; in e.g. `quote quote $$(x...) end end` multiple expressions can be
;; spliced into `$`, which then need to be spliced into the enclosing
;; expression in the next stage.
(cons splat-token (cdr x))))
((not (contains (lambda (e) (and (pair? e) (eq? (car e) '$))) x))
`(copyast (inert ,x)))
((not (any splice-expr? x))
`(call (core _expr) ,.(map (lambda (ex) (julia-bq-expand ex d)) x)))
(else
(let loop ((p (cdr x)) (q '()))
(if (null? p)
(let ((forms (reverse q)))
`(call (core splicedexpr) ,(julia-bq-expand (car x) d)
(call (top append_any) ,@forms)))
(loop (cdr p) (cons (julia-bq-bracket (car p) d) q)))))))
(case (car x)
((inert) `(call (core QuoteNode) ,@(bq-expand-arglist (cdr x) d)))
((line) `(call (core LineNumberNode) ,@(bq-expand-arglist (cdr x) d)))
((quote) `(call (core _expr) ,@(bq-expand-arglist x (+ d 1))))
(($) `(call (core _expr) ,@(bq-expand-arglist x (- d 1))))
(else `(call (core _expr) ,@(bq-expand-arglist x d)))))))

(define (julia-bq-expand x d)
(let ((e (julia-bq-expand- x d)))
(if (and (pair? e) (eq? (car e) splat-token))
'(error "\"...\" expression outside call")
e)))

;; hygiene

Expand Down
20 changes: 20 additions & 0 deletions test/parse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1346,3 +1346,23 @@ end
x::Int -> 2
end
end) == Expr(:error, "local variable Int cannot be used in closure declaration")

# some issues with backquote
# preserve QuoteNode and LineNumberNode
@test eval(Expr(:quote, QuoteNode(Expr(:tuple, 1, Expr(:$, :(1+2)))))) == QuoteNode(Expr(:tuple, 1, 3))
@test eval(Expr(:quote, Expr(:line, Expr(:$, :(1+2))))) === LineNumberNode(3, nothing)
# splicing at the top level should be an error
xs23917 = [1,2,3]
@test_throws ErrorException eval(:(:($(xs23917...))))
let ex2 = eval(:(:(:($$(xs23917...)))))
@test ex2 isa Expr
@test_throws ErrorException eval(ex2)
@test eval(:($(xs23917...),)) == (1,2,3) # adding a comma gives a tuple
end
# multi-unquote of splice in nested quote
let xs = [:(1+2), :(3+4), :(5+6)]
ex = quote quote $$(xs...) end end
@test ex.args[2].args[1].args[2].args[2] == :(3 + 4)
ex2 = eval(ex)
@test ex2.args[2:end] == [3,7,11]
end

0 comments on commit 6c4fb39

Please sign in to comment.