From 490515d2a8e2922d1fbf100e65a294d3b908f0e7 Mon Sep 17 00:00:00 2001 From: Simeon Schaub Date: Sat, 29 Aug 2020 13:33:52 +0200 Subject: [PATCH] allow vararg in lhs of assignment --- base/tuple.jl | 19 +++++++++ src/julia-syntax.scm | 95 ++++++++++++++++++++++++++++++-------------- test/syntax.jl | 51 ++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 29 deletions(-) diff --git a/base/tuple.jl b/base/tuple.jl index 25627aec45b43..aed333de8a7a4 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -116,6 +116,25 @@ iterate_and_index(x) = (destruct_iterate, select_first) # to make inference's life easier. iterate_and_index(::Nothing) = throw(MethodError(iterate, (nothing,))) +function _rest(itr, state, _) + @_inline_meta + state === () && return rest(itr) + return rest(itr, state[1][2]) +end +_rest(x::Union{Array,Tuple,NamedTuple,Pair}, _, i) = (@_inline_meta; rest(x, i)) + +rest(t::Tuple) = t +rest(t::NTuple{N}, i::Int) where {N} = ntuple(x -> getfield(t, x+i-1), N-i+1) +rest(a::Array, i::Int=1) = a[i:end] +# The semantics of `collect` are weird. Better to write our own +function rest(a::AbstractArray{T}, state...) where {T} + v = Vector{T}(undef, 0) + # assume only very few items are taken from the front + sizehint!(v, length(a)) + return foldl(push!, Iterators.rest(a, state...), init=v) +end +rest(itr, state...) = Iterators.rest(itr, state...) + # Use dispatch to avoid a branch in first first(::Tuple{}) = throw(ArgumentError("tuple must be non-empty")) first(t::Tuple) = t[1] diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 83b9f0311e0c8..5342390ba0414 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1422,7 +1422,8 @@ ,@(reverse after) (unnecessary (tuple ,@(reverse elts)))) (let ((L (car lhss)) - (R (car rhss))) + ;; rhss can be null iff L is a vararg + (R (if (null? rhss) '() (car rhss)))) (cond ((and (symbol-like? L) (or (not (pair? R)) (quoted? R) (equal? R '(null))) ;; overwrite var immediately if it doesn't occur elsewhere @@ -1434,6 +1435,16 @@ (cons (make-assignment L R) stmts) after (cons R elts))) + ((vararg? L) + (if (null? (cdr lhss)) + (let ((temp (make-ssavalue))) + `(block ,@(reverse stmts) + (= ,temp (tuple ,@rhss)) + ,@(reverse after) + (= ,(cadr L) ,temp) + (unnecessary (tuple ,@(reverse elts) (... ,temp))))) + (error (string "invalid \"...\" on non-final assignment location \"" + (cadr L) "\"")))) ((vararg? R) (let ((temp (make-ssavalue))) `(block ,@(reverse stmts) @@ -2035,6 +2046,7 @@ (define (sides-match? l r) ;; l and r either have equal lengths, or r has a trailing ... (cond ((null? l) (null? r)) + ((vararg? (car l)) #t) ((null? r) #f) ((vararg? (car r)) (null? (cdr r))) (else (sides-match? (cdr l) (cdr r))))) @@ -2044,34 +2056,59 @@ (expand-forms (tuple-to-assignments lhss x)) ;; (a, b, ...) = other - (let* ((xx (if (or (and (symbol? x) (not (memq x lhss))) - (ssavalue? x)) - x (make-ssavalue))) - (ini (if (eq? x xx) '() (list (sink-assignment xx (expand-forms x))))) - (n (length lhss)) - (funcs (make-ssavalue)) - (iterate (make-ssavalue)) - (index (make-ssavalue)) - (st (gensy))) - `(block - ,@ini - ,(lower-tuple-assignment - (list iterate index) - `(call (top iterate_and_index) ,xx)) - ,.(map (lambda (i lhs) - (expand-forms - `(block - (= ,st (call ,iterate - ,xx ,.(if (eq? i 0) '() `(,st)))) - ,(if (eventually-call? lhs) - (let ((val (gensy))) - `(block - (= ,val (call ,index ,st ,(+ i 1))) - (= ,lhs ,val))) - `(= ,lhs (call ,index ,st ,(+ i 1))))))) - (iota n) - lhss) - (unnecessary ,xx)))))) + (begin + ;; like memq, but if last element of lhss is (... sym), + ;; check against sym instead + (define (in-lhs? x lhss) + (if (null? lhss) + #f + (let ((l (car lhss))) + (cond ((and (pair? l) (eq? (car l) '|...|)) + (if (null? (cdr lhss)) + (eq? (cadr l) x) + (error (string "invalid \"...\" on non-final assignment location \"" + (cadr l) "\"")))) + ((eq? l x) #t) + (else (in-lhs? x (cdr lhss))))))) + (define (gensymified-assignment lhs rhs) + (if (eventually-call? lhs) + (let ((val (gensy))) + `(block + (= ,val ,rhs) + (= ,lhs ,val))) + `(= ,lhs ,rhs))) + (let* ((xx (if (or (and (not (in-lhs? x lhss)) (symbol? x)) + (ssavalue? x)) + x (make-ssavalue))) + (ini (if (eq? x xx) '() (list (sink-assignment xx (expand-forms x))))) + (n (length lhss)) + (funcs (make-ssavalue)) + (iterate (make-ssavalue)) + (index (make-ssavalue)) + (st (gensy))) + `(block + ,@ini + ,(lower-tuple-assignment + (list iterate index) + `(call (top iterate_and_index) ,xx)) + ,.(map (lambda (i lhs) + (expand-forms + (if (and (pair? lhs) (eq? (car lhs) '|...|)) + (gensymified-assignment + (cadr lhs) + `(call (top _rest) + ,xx + ,(if (eq? i 0) '(tuple) `(tuple ,st)) + ,(+ i 1))) + `(block + (= ,st (call ,iterate + ,xx ,.(if (eq? i 0) '() `(,st)))) + ,(gensymified-assignment + lhs + `(call ,index ,st ,(+ i 1))))))) + (iota n) + lhss) + (unnecessary ,xx))))))) ((typed_hcat) (error "invalid spacing in left side of indexed assignment")) ((typed_vcat) diff --git a/test/syntax.jl b/test/syntax.jl index cf8ba1aa75a8e..a4949c59f0e1c 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -2306,3 +2306,54 @@ end @test_throws ParseError("invalid operator \".<---\"") Meta.parse("1 .<--- 2") @test_throws ParseError("invalid operator \"--\"") Meta.parse("a---b") @test_throws ParseError("invalid operator \".--\"") Meta.parse("a.---b") + +@testset "slurp in assignments" begin + res = begin x, y, z... = 1:7 end + @test res == 1:7 + @test x == 1 && y == 2 + @test z == Vector(3:7) + + res = begin x, y, z... = [1, 2] end + @test res == [1, 2] + @test x == 1 && y == 2 + @test z == Int[] + + x = 1 + res = begin x..., = x end + @test res == 1 + @test x == 1 + + x, y, z... = 1:7 + res = begin y, z, x... = z..., x, y end + @test res == ((3:7)..., 1, 2) + @test y == 3 + @test z == 4 + @test x == ((5:7)..., 1, 2) + + res = begin x, _, y... = 1, 2 end + @test res == (1, 2) + @test x == 1 + @test y == () + + res = begin x, y... = 1 end + @test res == 1 + @test x == 1 + @test y == Iterators.rest(1, nothing) + + res = begin x, y, z... = 1, 2, 3:5 end + @test res == (1, 2, 3:5) + @test x == 1 && y == 2 + @test z == (3:5,) + + @test Meta.isexpr(Meta.@lower(begin a, b..., c = 1:3 end), :error) + @test Meta.isexpr(Meta.@lower(begin a, b..., c = 1, 2, 3 end), :error) + @test Meta.isexpr(Meta.@lower(begin a, b..., c... = 1, 2, 3 end), :error) + + @test_throws BoundsError begin x, y, z... = 1:1 end + @test_throws BoundsError begin x, y, _, z... = 1, 2 end + + car((a, d...)) = a + cdr((a, d...)) = d + @test car(1:3) == 1 + @test cdr(1:3) == [2, 3] +end