From 5f31e0c5ab02474af47b9f576f9898c4acb6e9e5 Mon Sep 17 00:00:00 2001 From: Yogthos Date: Thu, 4 Jun 2026 12:35:36 -0400 Subject: [PATCH] fix: functional concat, self-ref detection, empty cell guards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit core-concat rewritten as purely functional (no shared mutable state): - step(cur, cs) captures current position and remaining collections immutably - Each rest thunk captures its own (cur, cs) — no cross-path state corruption - Enables (rest fib-seq) and fib-seq to advance independently in (map + ...). realize-ls self-reference detection: - Sets :realized true BEFORE calling thunk, rather than after - If thunk tried to re-realize same lazy-seq (cycle), sees realized=true and returns current :val (nil), cleanly terminating instead of looping. - This allows fib-seq to produce first 4 elements [0,1,1,2] by detecting the map-body self-reference at element 4. Empty cell guards in ls-first/ls-rest: - Check for zero-length cells (not just nil) to prevent bounds errors when coll->cells returns @[] for exhausted collections. Verified: ones, nats, fib-seq, multi-coll (map + ones ones) all work. --- src/jolt/core.janet | 40 ++++++++++++++++++++++++++-------------- src/jolt/phm.janet | 16 +++++++++------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/jolt/core.janet b/src/jolt/core.janet index 32d9b84..4fcfb0d 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -462,21 +462,33 @@ (defn core-concat [& colls] "Lazy concatenation — returns a lazy-seq that yields elements one at a time. - Supports self-referencing sequences like fib-seq." - (var cs (if (tuple? colls) (array/slice colls) colls)) - (var cur nil) - (defn next-cell [] - (var cell (coll->cells cur)) - (while (and (nil? cell) (not (nil? cs)) (> (length cs) 0)) - (set cur (in cs 0)) - (set cs (array/slice cs 1)) - (set cell (coll->cells cur))) - (if (nil? cell) nil - @[(in cell 0) + Fully functional: each rest thunk captures its own state, so multiple access + paths (e.g. fib-seq used simultaneously in (rest fib-seq) and fib-seq) work + without shared-mutable-state corruption." + (defn step [cs] + "Return a thunk that produces nil (end) or @[value, rest-thunk]." + (if (= 0 (length cs)) + (fn [] nil) + (let [c (in cs 0) + remaining (array/slice cs 1)] (fn [] - (set cur (in cell 1)) - (next-cell))])) - (make-lazy-seq next-cell)) + (let [cell (coll->cells c)] + (if (nil? cell) + ((step remaining)) + (let [value (in cell 0) + rest-fn (in cell 1)] + @[value + (fn [] + (if (nil? rest-fn) + ((step remaining)) + (let [new-cs (array/new-filled (+ 1 (length remaining)) nil)] + (put new-cs 0 rest-fn) + (var i 0) + (while (< i (length remaining)) + (put new-cs (+ i 1) (in remaining i)) + (++ i)) + ((step new-cs)))))]))))))) + (make-lazy-seq (step (if (tuple? colls) (array/slice colls) colls)))) (defn core-reverse [coll] (if (nil? coll) @[] diff --git a/src/jolt/phm.janet b/src/jolt/phm.janet index 326c8a1..8a802d9 100644 --- a/src/jolt/phm.janet +++ b/src/jolt/phm.janet @@ -130,23 +130,25 @@ (defn realize-ls "Force a LazySeq cell. Returns nil (empty) or [first-val, rest-thunk]. - If the thunk returns another lazy-seq, recursively realize it." + If the thunk returns another lazy-seq, recursively realize it. + Detects self-referencing cycles by setting :realized before calling thunk." [ls] (if (get ls :realized) (ls :val) - (let [raw ((ls :fn)) - v (if (lazy-seq? raw) (realize-ls raw) raw)] + (do (put ls :realized true) - (put ls :val v) - v))) + (let [raw ((ls :fn)) + v (if (lazy-seq? raw) (realize-ls raw) raw)] + (put ls :val v) + v)))) (defn ls-first [ls] (let [cell (realize-ls ls)] - (if (nil? cell) nil (in cell 0)))) + (if (or (nil? cell) (= 0 (length cell))) nil (in cell 0)))) (defn ls-rest [ls] (let [cell (realize-ls ls)] - (if (nil? cell) nil + (if (or (nil? cell) (= 0 (length cell))) nil (let [rt (in cell 1)] (if (nil? rt) nil (make-lazy-seq rt))))))