diff --git a/jolt-core/clojure/core/10-seq.clj b/jolt-core/clojure/core/10-seq.clj index 78a27a7..f5c0fa3 100644 --- a/jolt-core/clojure/core/10-seq.clj +++ b/jolt-core/clojure/core/10-seq.clj @@ -22,8 +22,3 @@ (if (next s) (recur (conj ret (first s)) (next s)) (seq ret)))) - -(defn mapcat - ([f] (comp (map f) cat)) - ([f & colls] - (apply concat (apply map f colls)))) diff --git a/src/jolt/compiler.janet b/src/jolt/compiler.janet index 799dceb..83f73aa 100644 --- a/src/jolt/compiler.janet +++ b/src/jolt/compiler.janet @@ -97,6 +97,7 @@ "take-while" "core-take-while" "drop-while" "core-drop-while" "nth" "core-nth" + "mapcat" "core-mapcat" "list" "core-list" "name" "core-name" "subs" "core-subs" diff --git a/src/jolt/core.janet b/src/jolt/core.janet index 6de2877..58fe11d 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -1134,17 +1134,44 @@ (each x (realize-for-iteration (f (a 1))) (set acc (rf acc x))) acc)))) - # collection arity: map f over colls, then concatenate. A non-seqable - # result counts as a single element (this leniency is what jolt's `for` - # expansion relies on for :let on the last binding, whose body yields a - # scalar rather than a seq). - (let [mapped (realize-for-iteration (core-apply core-map f colls)) - seqs (map (fn [item] - (if (or (tuple? item) (array? item) (pvec? item) - (lazy-seq? item) (set? item)) - item (tuple item))) - mapped)] - (core-apply core-concat seqs)))) + # collection arity: direct lazy implementation. Pull one element + # from each input coll, apply f, then yield elements from f's result. + # No apply-forcing — walk input colls lazily element-by-element. + (do + (var n (length colls)) + (var init-cs @[]) + (var i 0) + (while (< i n) + (array/push init-cs (lazy-from (in colls i))) + (++ i)) + (defn step [cs res] + (fn [] + (var cursors cs) (var cur-res res) (var hit nil) (var ok false) + (while (not ok) + (if (nil? cur-res) + (do + (var args @[]) (var next-cs @[]) (var exhausted false) (var j 0) + (while (and (< j n) (not exhausted)) + (let [c (in cursors j)] + (if (seq-done? c) (set exhausted true) + (do + (array/push args (ls-first c)) + (array/push next-cs (ls-rest c))))) + (++ j)) + (if exhausted (break)) + (let [r (apply f args)] + (set cursors next-cs) + (set cur-res (if (or (nil? r) (tuple? r) (array? r) + (lazy-seq? r) (pvec? r) (set? r) (plist? r)) + (lazy-from r) + (lazy-from (tuple r)))))) + (if (seq-done? cur-res) + (set cur-res nil) + (let [val (ls-first cur-res) rest (ls-rest cur-res)] + (set hit @[val (step cursors rest)]) + (set ok true))))) + (if ok hit nil))) + (make-lazy-seq (step init-cs nil))))) (defn core-reverse [coll] (if (nil? coll) @[] diff --git a/src/jolt/evaluator.janet b/src/jolt/evaluator.janet index 4beeaff..61b6326 100644 --- a/src/jolt/evaluator.janet +++ b/src/jolt/evaluator.janet @@ -549,14 +549,24 @@ (while (< di n) (let [elem (in pat di)] (cond - # & rest - (and (struct? elem) (= :symbol (elem :jolt/type)) (= "&" (elem :name))) - (do - # rest binds a seq (jolt list = array), per Clojure semantics - (destructure-bind ctx bindings (in pat (+ di 1)) - (if (and seqable? (< vi (length rv))) - (array/slice (if (tuple? rv) (array/slice rv) rv) vi) - @[])) + # & rest + (and (struct? elem) (= :symbol (elem :jolt/type)) (= "&" (elem :name))) + (do + # rest binds a seq (jolt list = array), per Clojure semantics. + # For lazy-seqs, preserve laziness: walk vi steps via ls-rest + # instead of slicing the eagerly-realized array. + (destructure-bind ctx bindings (in pat (+ di 1)) + (if (lazy-seq? val) + (do + (var c val) (var i 0) + (while (< i vi) + (let [nxt (ls-rest c)] + (if (nil? nxt) (break) + (do (set c nxt) (++ i))))) + c) + (if (and seqable? (< vi (length rv))) + (array/slice (if (tuple? rv) (array/slice rv) rv) vi) + @[]))) (set di (+ di 2))) # :as whole (= elem :as) diff --git a/test/integration/lazy-infinite-test.janet b/test/integration/lazy-infinite-test.janet index a26d369..982f305 100644 --- a/test/integration/lazy-infinite-test.janet +++ b/test/integration/lazy-infinite-test.janet @@ -52,8 +52,9 @@ ["take 3 partition-all 2 range" "(quote ((0 1) (2 3) (4 5)))" "(take 3 (partition-all 2 (range)))"] ["take 3 map-indexed vector range" "(quote ([0 0] [1 1] [2 2]))" "(take 3 (map-indexed vector (range)))"] ["take 3 distinct cycle" "(quote (1 2 3))" "(take 3 (distinct (cycle [1 2 1 3 1])))"] - # mapcat on infinite inputs hangs (apply forces lazy result, needs Step 4). - # ["take 6 mapcat dup range" "(quote (0 0 1 1 2 2))" "(take 6 (mapcat (fn [x] [x x]) (range)))"] + ["take 6 mapcat dup range" "(quote (0 0 1 1 2 2))" "(take 6 (mapcat (fn [x] [x x]) (range)))"] + ["first rest lazy" "1" "(let [[a & r] (range)] (first r))"] + ["take 3 rest lazy" "(quote (1 2 3))" "(let [[a & r] (range)] (take 3 r))"] ["take 3 take-nth 2 range" "(quote (0 2 4))" "(take 3 (take-nth 2 (range)))"] ["take 3 interpose :x range" "(quote (0 :x 1))" "(take 3 (interpose :x (range)))"] ["take 3 map vector range iterate" "(quote ([0 100] [1 101] [2 102]))" "(take 3 (map vector (range) (iterate inc 100)))"]