From dbd1574aa0f8f8de3840dedb859a3ee984e4ccb0 Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Fri, 1 Sep 2017 16:54:57 -0400 Subject: [PATCH] parse `let` the same as `for`. part of #21774 --- NEWS.md | 4 ++++ base/broadcast.jl | 4 ++-- base/distributed/macros.jl | 2 +- base/printf.jl | 4 ++-- base/show.jl | 5 +---- base/subarray.jl | 13 +++++++------ doc/src/devdocs/ast.md | 3 ++- src/julia-parser.scm | 28 +++++++++++++++------------- src/julia-syntax.scm | 30 ++++++++++++++++++++++-------- src/macroexpand.scm | 32 +++++++++++++++++--------------- test/parse.jl | 2 +- 11 files changed, 74 insertions(+), 53 deletions(-) diff --git a/NEWS.md b/NEWS.md index 97deb4df01f46..f434689a8d2c1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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: diff --git a/base/broadcast.jl b/base/broadcast.jl index 1b847aa7ddb6d..2ec5d451512bd 100644 --- a/base/broadcast.jl +++ b/base/broadcast.jl @@ -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) && diff --git a/base/distributed/macros.jl b/base/distributed/macros.jl index aa3f52cedc2d4..d3b72cb791682 100644 --- a/base/distributed/macros.jl +++ b/base/distributed/macros.jl @@ -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]) diff --git a/base/printf.jl b/base/printf.jl index c2ca443ba8bc0..dff3d28412df7 100644 --- a/base/printf.jl +++ b/base/printf.jl @@ -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 """ @@ -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 diff --git a/base/show.jl b/base/show.jl index ab517f9c44795..801d6d8d1bc6f 100644 --- a/base/show.jl +++ b/base/show.jl @@ -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") @@ -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") diff --git a/base/subarray.jl b/base/subarray.jl index b695444563c67..5db883f964c06 100644 --- a/base/subarray.jl +++ b/base/subarray.jl @@ -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 @@ -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 @@ -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 diff --git a/doc/src/devdocs/ast.md b/doc/src/devdocs/ast.md index ae9622fbb7866..a13e6f4bf2660 100644 --- a/doc/src/devdocs/ast.md +++ b/doc/src/devdocs/ast.md @@ -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: diff --git a/src/julia-parser.scm b/src/julia-parser.scm index 3a17ab7346eb8..53e36ec03eeaf 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -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 @@ -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) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index c74c0c1a0aeff..c096f27fa9d84 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -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)) @@ -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) @@ -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 @@ -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) diff --git a/src/macroexpand.scm b/src/macroexpand.scm index 8161be6fff2ae..a7535920d24b0 100644 --- a/src/macroexpand.scm +++ b/src/macroexpand.scm @@ -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) @@ -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)) diff --git a/test/parse.jl b/test/parse.jl index 7d5aaad45964e..19bac9550cae3 100644 --- a/test/parse.jl +++ b/test/parse.jl @@ -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