Skip to content

Commit

Permalink
Merge pull request JuliaLang#23553 from JuliaLang/jb/parselet
Browse files Browse the repository at this point in the history
parse `let` the same as `for`. part of JuliaLang#21774
  • Loading branch information
JeffBezanson committed Sep 5, 2017
2 parents 8183c0f + dbd1574 commit 2bdcd5f
Show file tree
Hide file tree
Showing 11 changed files with 74 additions and 53 deletions.
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ Language changes
* Nested `if` expressions that arise from the keyword `elseif` now use `elseif`
as their expression head instead of `if` ([#21774]).

* `let` blocks now parse the same as `for` loops; the first argument is either an
assignment or `block` of assignments, and the second argument is a block of
statements ([#21774]).

* Parsed and lowered forms of type definitions have been synchronized with their
new keywords ([#23157]). Expression heads are renamed as follows:

Expand Down
4 changes: 2 additions & 2 deletions base/broadcast.jl
Original file line number Diff line number Diff line change
Expand Up @@ -564,8 +564,8 @@ function __dot__(x::Expr)
Expr(:., dotargs[1], Expr(:tuple, dotargs[2:end]...))
elseif x.head == :$
x.args[1]
elseif x.head == :let # don't add dots to "let x=... assignments
Expr(:let, dotargs[1], map(undot, dotargs[2:end])...)
elseif x.head == :let # don't add dots to `let x=...` assignments
Expr(:let, undot(dotargs[1]), dotargs[2])
elseif x.head == :for # don't add dots to for x=... assignments
Expr(:for, undot(dotargs[1]), dotargs[2])
elseif (x.head == :(=) || x.head == :function || x.head == :macro) &&
Expand Down
2 changes: 1 addition & 1 deletion base/distributed/macros.jl
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ function extract_imports!(imports, ex::Expr)
if Meta.isexpr(ex, (:import, :using))
return push!(imports, ex.args[1])
elseif Meta.isexpr(ex, :let)
return extract_imports!(imports, ex.args[1])
return extract_imports!(imports, ex.args[2])
elseif Meta.isexpr(ex, (:toplevel, :block))
for i in eachindex(ex.args)
extract_imports!(imports, ex.args[i])
Expand Down
4 changes: 2 additions & 2 deletions base/printf.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1188,7 +1188,7 @@ function _printf(macroname, io, fmt, args)
end

unshift!(blk.args, :(out = $io))
Expr(:let, blk)
Expr(:let, Expr(:block), blk)
end

"""
Expand Down Expand Up @@ -1241,7 +1241,7 @@ macro sprintf(args...)
isa(args[1], AbstractString) || is_str_expr(args[1]) ||
throw(ArgumentError("@sprintf: first argument must be a format string"))
letexpr = _printf("@sprintf", :(IOBuffer()), args[1], args[2:end])
push!(letexpr.args[1].args, :(String(take!(out))))
push!(letexpr.args[2].args, :(String(take!(out))))
letexpr
end

Expand Down
5 changes: 1 addition & 4 deletions base/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -959,7 +959,7 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int)
print(io, "function ", args[1], " end")

# block with argument
elseif head in (:for,:while,:function,:if,:elseif) && nargs==2
elseif head in (:for,:while,:function,:if,:elseif,:let) && nargs==2
show_block(io, head, args[1], args[2], indent)
print(io, "end")

Expand Down Expand Up @@ -1043,9 +1043,6 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int)
end
print(io, "end")

elseif head === :let && nargs >= 1
show_block(io, "let", args[2:end], args[1], indent); print(io, "end")

elseif head === :block || head === :body
show_block(io, "begin", ex, indent); print(io, "end")

Expand Down
13 changes: 7 additions & 6 deletions base/subarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ function replace_ref_end_!(ex, withex)
if used_S && S !== ex.args[1]
S0 = ex.args[1]
ex.args[1] = S
ex = Expr(:let, ex, :($S = $S0))
ex = Expr(:let, :($S = $S0), ex)
end
else
# recursive search
Expand Down Expand Up @@ -471,8 +471,8 @@ macro view(ex)
if Meta.isexpr(ex, :ref)
ex = Expr(:call, view, ex.args...)
else # ex replaced by let ...; foo[...]; end
assert(Meta.isexpr(ex, :let) && Meta.isexpr(ex.args[1], :ref))
ex.args[1] = Expr(:call, view, ex.args[1].args...)
assert(Meta.isexpr(ex, :let) && Meta.isexpr(ex.args[2], :ref))
ex.args[2] = Expr(:call, view, ex.args[2].args...)
end
Expr(:&&, true, esc(ex))
else
Expand Down Expand Up @@ -532,12 +532,13 @@ function _views(ex::Expr)
end

Expr(:let,
Expr(:block,
:($a = $(_views(lhs.args[1]))),
[:($(i[k]) = $(_views(lhs.args[k+1]))) for k=1:length(i)]...),
Expr(first(h) == '.' ? :(.=) : :(=), :($a[$(I...)]),
Expr(:call, Symbol(h[1:end-1]),
:($maybeview($a, $(I...))),
_views.(ex.args[2:end])...)),
:($a = $(_views(lhs.args[1]))),
[:($(i[k]) = $(_views(lhs.args[k+1]))) for k=1:length(i)]...)
_views.(ex.args[2:end])...)))
else
Expr(ex.head, _views.(ex.args)...)
end
Expand Down
3 changes: 2 additions & 1 deletion doc/src/devdocs/ast.md
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,8 @@ they are parsed as a block: `(for (block (= v1 iter1) (= v2 iter2)) body)`.

`break` and `continue` are parsed as 0-argument expressions `(break)` and `(continue)`.

`let` is parsed as `(let body (= var1 val1) (= var2 val2) ...)`.
`let` is parsed as `(let (= var val) body)` or `(let (block (= var1 val1) (= var2 val2) ...) body)`,
like `for` loops.

A basic function definition is parsed as `(function (call f x) body)`. A more complex example:

Expand Down
28 changes: 15 additions & 13 deletions src/julia-parser.scm
Original file line number Diff line number Diff line change
Expand Up @@ -1303,6 +1303,21 @@
`(for ,(if (length= ranges 1) (car ranges) (cons 'block ranges))
,body)))

((let)
(let ((binds (if (memv (peek-token s) '(#\newline #\;))
'()
(parse-comma-separated-assignments s))))
(if (not (or (eof-object? (peek-token s))
(memv (peek-token s) '(#\newline #\; end))))
(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))
`(block) ;; don't need line info in an empty let block
ex)))
`(let ,(if (length= binds 1) (car binds) (cons 'block binds))
,ex))))

((if elseif)
(if (newline? (peek-token s))
(error (string "missing condition in \"if\" at " current-filename
Expand Down Expand Up @@ -1331,19 +1346,6 @@
(begin0 (list word test then (parse-block s))
(expect-end s 'if)))
(else (error (string "unexpected \"" nxt "\""))))))
((let)
(let ((binds (if (memv (peek-token s) '(#\newline #\;))
'()
(parse-comma-separated-assignments s))))
(if (not (or (eof-object? (peek-token s))
(memv (peek-token s) '(#\newline #\; end))))
(error "let variables should end in \";\" or newline"))
(let ((ex (parse-block s)))
(expect-end s word)
;; don't need line info in an empty let block
(if (and (length= ex 2) (pair? (cadr ex)) (eq? (caadr ex) 'line))
`(let (block) ,@binds)
`(let ,ex ,@binds)))))

((global local)
(let* ((const (and (eq? (peek-token s) 'const)
Expand Down
30 changes: 22 additions & 8 deletions src/julia-syntax.scm
Original file line number Diff line number Diff line change
Expand Up @@ -359,9 +359,9 @@
(define (scopenest names vals expr)
(if (null? names)
expr
`(let (block
,(scopenest (cdr names) (cdr vals) expr))
(= ,(car names) ,(car vals)))))
`(let (= ,(car names) ,(car vals))
(block
,(scopenest (cdr names) (cdr vals) expr)))))

(define empty-vector-any '(call (core AnyVector) 0))

Expand Down Expand Up @@ -1120,9 +1120,23 @@
`(call ,name ,@argl))
,body)))))

(define (let-binds e)
(if (and (pair? (cadr e))
(eq? (car (cadr e)) 'block))
(cdr (cadr e))
(list (cadr e))))

(define (expand-let e)
(let ((ex (cadr e))
(binds (cddr e)))
(if (length= e 2)
(begin (deprecation-message (string "The form `Expr(:let, ex)` is deprecated. "
"Use `Expr(:let, Expr(:block), ex)` instead." #\newline))
(return (expand-let `(let (block) ,(cadr e))))))
(if (length> e 3)
(begin (deprecation-message (string "The form `Expr(:let, ex, binds...)` is deprecated. "
"Use `Expr(:let, Expr(:block, binds...), ex)` instead." #\newline))
(return (expand-let `(let (block ,@(cddr e)) ,(cadr e))))))
(let ((ex (caddr e))
(binds (let-binds e)))
(expand-forms
(if
(null? binds)
Expand Down Expand Up @@ -1803,7 +1817,7 @@
`(fuse _ ,(cdadr (cadr arg)))
oldarg))
fargs args)))
(let ,fbody ,@(reverse (fuse-lets fargs args '()))))))
(let (block ,@(reverse (fuse-lets fargs args '()))) ,fbody))))
(define (dot-to-fuse e) ; convert e == (. f (tuple args)) to (fuse f args)
(define (make-fuse f args) ; check for nested (fuse f args) exprs and combine
(define (split-kwargs args) ; return (cons keyword-args positional-args) extracted from args
Expand Down Expand Up @@ -1891,8 +1905,8 @@
(define (expand-where body var)
(let* ((bounds (analyze-typevar var))
(v (car bounds)))
`(let (call (core UnionAll) ,v ,body)
(= ,v ,(bounds-to-TypeVar bounds)))))
`(let (= ,v ,(bounds-to-TypeVar bounds))
(call (core UnionAll) ,v ,body))))

(define (expand-wheres body vars)
(if (null? vars)
Expand Down
32 changes: 17 additions & 15 deletions src/macroexpand.scm
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@
(cons 'varlist (typevar-names vars)))

;; let
(pattern-lambda (let ex . binds)
(let loop ((binds binds)
(pattern-lambda (let binds ex)
(let loop ((binds (let-binds __))
(vars '()))
(if (null? binds)
(cons 'varlist vars)
Expand Down Expand Up @@ -360,19 +360,21 @@

((let)
(let* ((newenv (new-expansion-env-for e env))
(body (resolve-expansion-vars- (cadr e) newenv m parent-scope inarg)))
`(let ,body
,@(map
(lambda (bind)
(if (assignment? bind)
(make-assignment
;; expand binds in old env with dummy RHS
(cadr (resolve-expansion-vars- (make-assignment (cadr bind) 0)
newenv m parent-scope inarg))
;; expand initial values in old env
(resolve-expansion-vars- (caddr bind) env m parent-scope inarg))
bind))
(cddr e)))))
(body (resolve-expansion-vars- (caddr e) newenv m parent-scope inarg))
(binds (let-binds e)))
`(let (block
,@(map
(lambda (bind)
(if (assignment? bind)
(make-assignment
;; expand binds in old env with dummy RHS
(cadr (resolve-expansion-vars- (make-assignment (cadr bind) 0)
newenv m parent-scope inarg))
;; expand initial values in old env
(resolve-expansion-vars- (caddr bind) env m parent-scope inarg))
bind))
binds))
,body)))
((hygienic-scope) ; TODO: move this lowering to resolve-scopes, instead of reimplementing it here badly
(let ((parent-scope (cons (list env m) parent-scope))
(body (cadr e))
Expand Down
2 changes: 1 addition & 1 deletion test/parse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ let b = IOBuffer("""
end
f()
""")
@test Base.parse_input_line(b) == Expr(:let, Expr(:block, LineNumberNode(2, :none), :x), Expr(:(=), :x, :x))
@test Base.parse_input_line(b) == Expr(:let, Expr(:(=), :x, :x), Expr(:block, LineNumberNode(2, :none), :x))
@test Base.parse_input_line(b) == Expr(:call, :f)
@test Base.parse_input_line(b) === nothing
end
Expand Down

0 comments on commit 2bdcd5f

Please sign in to comment.