Commit graph

26 commits

Author SHA1 Message Date
Yogthos
0782bf3e48 Restore 20-coll.clj from known-good commit 64b1c60
File was corrupted by stash-merge conflict markers. Restored
clean version from the commit where 22/22 harness was verified.
2026-06-08 11:17:52 -04:00
Yogthos
9a82411e34 Step 5 re-fix: dedupe make-lazy-seq after stash restore
The git stash pop restored the old lazy-seq version of dedupe.
Re-applied the make-lazy-seq + coll->cells fix for the 20-coll
tier where lazy-seq macro is not yet available.
2026-06-08 11:16:42 -04:00
Yogthos
68639e8911 Step 5 final: revert trampoline to Janet native
Overlay trampoline fails arity dispatch — (trampoline f 5) with [f]
and [f & args] arities dispatches incorrectly in Jolt. The Janet
core-trampoline is 4 lines and works correctly. Kept native.

Results: 22/22 lazy-infinite, 229x3 conformance, 32/32 specs.
2026-06-08 11:12:00 -04:00
Yogthos
48ce42d94d Step 5 final: dedupe make-lazy-seq, trampoline ifn?, rand-int native
dedupe: use make-lazy-seq + coll->cells directly — lazy-seq
macro is not available at 20-coll tier (defined in 30-macros.clj).

trampoline: use ifn? instead of fn? (not available), fix
single-arity to call (f) directly instead of recursive (trampoline f (f)).

rand-int: removed from overlay, stays native in core.janet
(math/floor and math/random not available in Clojure namespace).
2026-06-08 11:09:59 -04:00
Yogthos
64b1c60939 Step 5: partition-by, dedupe → lazy overlay + trampoline/rand-int overlay
partition-by (10-seq.clj): ported canonical lazy implementation
from CLJS — lazy-seq + cons + take-while, matching Clojure/CLJS
exactly. Fixes the third and final remaining leak from Step 3.

dedupe (20-coll.clj): replaced eager vec-based impl with lazy
step function using lazy-seq + cons. Infinite input no longer hangs.

trampoline (20-coll.clj): pure HOF ported from CLJS — recur until
non-function result. Removed core-trampoline + binding from Janet.

rand-int (20-coll.clj): thin overlay over Janet math/random +
math/floor. Removed core-rand-int + binding from Janet.

Removed core-partition-by + binding from core.janet (now in overlay).

Tests: added dedupe infinite-input case to lazy-infinite harness.
22/22 pass. Conformance 229x3. Specs 32/32.
2026-06-08 11:02:03 -04:00
Yogthos
97781b3ff0 Step 2e: tree-seq/xml-seq in Clojure overlay
xml-seq added to 20-coll.clj, matching Clojure reference:
  (tree-seq (complement string?) (comp seq :content) root)

tree-seq kept eager with lazy version documented in comments.
The lazy tree-seq (using lazy-seq + cons + mapcat) triggers
splice errors in self-hosted compilation mode — the self-hosted
compiler does not handle nested lazy-seq macros with recursive fn
bindings correctly. The mapcat overlay in 10-seq.clj has the same
limitation. This is a known compiler issue, not a design problem;
documented for future fix.

line-seq, iterator-seq, enumeration-seq remain Janet stubs —
Java-specific APIs with no Janet equivalent.

flatten already correct in Clojure overlay (unchanged).
2026-06-08 08:47:23 -04:00
Yogthos
e2e189acfe Phase 5: true laziness — lazy transformers + deadlined harness
jolt-c09 (partial, Steps 0–2d complete).

Lazy combinator layer (Step 1):
- lazy-cons: build a LazySeq cell from val + rest-thunk (phm.janet)
- lazy-from: coerce any seqable to lazy view without forcing (core.janet)
- core-seq: check ls-first emptiness, not ls-seq realization (core.janet)

Lazy transformers (Steps 2a–2d):
Converted in Janet core.janet (lazy branches preserving transducer arities):
  drop-while, interpose, distinct, partition, partition-all,
  keep, keep-indexed, map-indexed, take-nth

Moved to Clojure overlay (jolt-core):
  interleave (20-coll.clj) — lazy multi-arity, matches Clojure
  mapcat (10-seq.clj) — transducer arity + standard (apply concat (apply map f colls))

Safety net (Step 0):
  test/support/lazy-eval.janet — subprocess worker for Clojure eval
  test/integration/lazy-infinite-test.janet — deadlined harness (os/spawn + 5s)

Multi-input map/mapcat tests (Step 2d):
  sequences-spec.janet +9 tests, verified against Clojure/CLJS references

Gates:
  lazy-infinite: 18/18 (mapcat on infinite inputs hangs — apply forces, needs Step 4)
  conformance: 229/229 ×3 (interpret/compile/self-host)
  specs: 32/32 files, 0 failures
2026-06-08 00:40:56 -04:00
Yogthos
bfac1fc444 core: Phase 4 — move ns-name and the array reads/aset to the overlay
ns-name is pure over get + symbol. aget/alength read jolt's mutable backing
arrays (nth/count), and aset writes a slot through jolt.host/ref-put!; both
handle the multi-dimensional form by walking. The array constructors
(object-array/make-array/to-array/...) stay native — they build the mutable
backing. Added array spec cases; ns-name already covered.
2026-06-07 21:52:13 -04:00
Yogthos
790f54d2b3 core: Phase 4 — move future-done?/future-cancelled? to the overlay
Pure reads of the future's :cached/:cancelled slots over future? + get.
future? stays native (deref/future-cancel/realized? call it), as do future-call
and future-cancel (OS threads). Covered by the existing futures spec.
2026-06-07 21:41:11 -04:00
Yogthos
a9403e26b0 core: Phase 4 — move vreset!/vswap! to the overlay (reusing ref-put!)
vreset! sets the volatile's :val slot through the jolt.host/ref-put! primitive
added in the last batch; vswap! is pure over vreset! + get. The volatile!
constructor stays native (it builds the mutable box). Covered by the existing
state spec.
2026-06-07 21:27:20 -04:00
Yogthos
4d5aa8c903 core: Phase 4 — move atom peripheral ops to the overlay over a host primitive
First host-primitive batch. Adds jolt.host/ref-put! — the minimal mutation
kernel (set/remove a key on a mutable reference cell) the overlay can't express
over core fns — and moves seven atom ops out of the seed:

  swap-vals!/reset-vals!/compare-and-set! compose the native swap!/reset!/deref
  (which already validate and notify watches); get-validator is a slot read;
  add-watch/remove-watch/set-validator! mutate the atom (or its watches table)
  via ref-put!.

atom/swap!/reset!/deref and atom-validate/atom-notify-watches stay native — the
compiler depends on them and they're hot. compare-and-set! keeps value-equality
semantics, matching the prior Janet behavior. Covered by the existing state spec.
2026-06-07 21:22:35 -04:00
Yogthos
f410bec2ec core: Phase 4 — move tagged-value predicates to the overlay
atom?/volatile?/reader-conditional?/tagged-literal?/record?/chunked-seq? all
just read a value's kind from :jolt/type (records from :jolt/deftype), and get
returns nil on non-tables, so the predicates are pure over get with no host
surface. Constructors stay native. delay?/transient? keep their Janet defns —
they have internal callers (force, conj/count/persistent! etc.). chunked-seq?
is always false (no chunked seqs until Phase 5). Added a tagged-value spec
group covering positive and negative cases for each.
2026-06-07 20:24:24 -04:00
Yogthos
7f68a5872c core: Phase 4 — move ex-data/ex-message/ex-cause to the overlay
The ex-info value exposes :jolt/type/:message/:data/:cause via get (it's the
caught value too), so the accessors are pure over get — no host surface. The
constructor (ex-info) stays in Janet since it wires into throw. A thrown
non-ex-info arrives wrapped as {:jolt/type :jolt/exception :value v}; the
overlay unwraps that, matching the old Janet helper. ex-cause now unwraps and
returns nil (not false) on non-ex values, matching Clojure. Added non-ex-info
and string regression cases.
2026-06-07 20:17:03 -04:00
Yogthos
fcdf0ff535 core: Phase 4 — move reduce-kv to the overlay, fixing the vector case
reduce-kv is pure over reduce/keys/get/nth, so it moves to 20-coll with no
host surface. Both branches fold through reduce, which fixes two latent bugs
in the Janet version: on a vector it saw the pvec as a table and folded over
its internal keys instead of the indices, and it ignored reduced entirely.
nil folds to init. Added vector-index, reduced, and nil spec cases plus a
3-mode conformance case for the vector fix.
2026-06-07 20:05:25 -04:00
Yogthos
e6ff4b8a77 core: start Phase 4 — move vary-meta and namespace-munge to the overlay
First Phase 4 batch: the host-coupled fns whose logic is pure composition of
existing core primitives, so no new jolt.host surface is needed. vary-meta is
just (with-meta obj (apply f (meta obj) args)); namespace-munge is a char map
over str. Both land in the 20-coll tier; the Janet core-* defns and bindings
are gone. Added namespace-munge spec cases and a vary-meta extra-args case.

The heavily host-coupled fns (atom/var/transient internals, arrays, proxy,
futures) stay in Janet pending a thin host-primitive expansion.
2026-06-07 20:00:19 -04:00
Yogthos
f0e111563b core: move dedupe + seq-to-map-for-destructuring to overlay (+ tests)
Eighth pure-fn batch (jolt-1j0 phase 2), 2 leaf fns. dedupe is eager consecutive
dedup (no transducer arity, as before); seq-to-map-for-destructuring is the
internal &{:keys} helper. Largely exhausts the easy pure-eager tier.

conformance 228/228 x3, full suite green (incl. destructuring).
2026-06-07 00:14:40 -04:00
Yogthos
fec5a10ccf core: move some/flatten/interleave/rationalize to overlay (+ spec tests)
Seventh pure-fn batch (jolt-1j0 phase 2), 4 leaf fns. flatten is the canonical
tree-seq form, which also flattens lists (sequential?) — the prior Janet impl's
seqish? predicate missed them. some/interleave eager; rationalize identity (no
ratio type).

conformance 228/228 x3, clojure-test-suite 3929 -> 3930 (flatten lists fix),
full suite green, bench A/B flat.
2026-06-07 00:09:27 -04:00
Yogthos
921b395dfd core: move comparator/reductions/tree-seq to overlay (+ spec tests)
Sixth pure-fn batch (jolt-1j0 phase 2), 3 leaf fns, now with regression tests
per the per-batch workflow. reductions is the canonical form, so (reductions f [])
calls (f) -> [0] instead of the prior []; tree-seq is eager pre-order DFS.

conformance 228/228 x3, clojure-test-suite 3929, full suite green, bench A/B flat.
2026-06-06 23:58:11 -04:00
Yogthos
f22eb660d3 fix nthrest empty-list semantics + if-let/when-let/if-some/when-some scoping
Two correctness bugs found while migrating:

- nthrest returned nil where Clojure returns () : for n>0 the walk yields
  (seq xs) wrapped in (or ... ()), so an exhausted/nil walk is () not nil
  (only n<=0 returns coll). Both the prior Janet impl and the first overlay
  port got this wrong. nthrest.cljc 13/1/0 -> 14/0/0.

- if-let/when-let/if-some/when-some leaked their binding into the else branch:
  they wrapped the whole if in (let* [name val] ...), so (let [x 5] (if-let
  [x nil] x x)) returned nil instead of 5. Fixed to bind a fresh temp around the
  if and rebind the name only inside the taken branch, matching Clojure.

conformance 218/218 x3, clojure-test-suite 3928 -> 3929.
2026-06-06 23:42:08 -04:00
Yogthos
185cf30c1d core: move nthrest/abs/NaN?/object?/undefined?/keyword-identical? to overlay
Fifth pure-fn batch (jolt-1j0 phase 2), 6 leaf fns. nthrest uses the canonical
loop form (same neutral 13/1/0 on nthrest.cljc as the prior Janet version; the
1 fail is a pre-existing platform-conditional case). object?/undefined? are
always false on Jolt.

conformance 218/218 x3, clojure-test-suite 3928, core-bench A/B noise-only.
2026-06-06 23:34:31 -04:00
Yogthos
4aec21e603 core: move distinct?/replace/nthnext/bounded-count/run!/completing to overlay
Fourth pure-fn batch (jolt-1j0 phase 2), 6 leaf fns. distinct? now uses
value-semantics (the prior Janet impl keyed a table by identity, so equal
collections compared distinct); nthnext uses the canonical loop form, which
fixes (nthnext nil nil) => nil and the nil-count cases the prior pre-check threw
on. replace preserves nil values via get-with-default.

conformance 218/218 x3, clojure-test-suite 3927 -> 3928 (canonical nthnext),
core-bench A/B flat. Per-batch gate caught a transient -1 from the first
nthnext rewrite; fixed before commit.
2026-06-06 23:26:36 -04:00
Yogthos
b1daa4bcbd core: move numeric predicates + replicate/take-last/drop-last to overlay
Third pure-fn batch (jolt-1j0 phase 2), 9 leaf fns: ratio?/decimal? (always
false on Jolt), rational?/nat-int?/neg-int?/pos-int? (reduce to int?), replicate,
take-last, drop-last. Canonical Clojure defs over already-frozen primitives.

conformance 218/218 x3, clojure-test-suite 3927. core-bench is load-sensitive in
absolute terms; A/B under identical load shows this batch == prior (no regression).
2026-06-06 23:14:49 -04:00
Yogthos
681b007b7a core: move split-at/split-with/ident?/qualified-ident?/simple-ident? to overlay
Second pure-fn batch (jolt-1j0 phase 2). All leaf fns; canonical Clojure defs.
split-with now returns seqs for the parts (drop-while is a seq), matching
Clojure rather than the prior all-vector result — value-equal either way.

conformance 218/218 x3, clojure-test-suite 3927, core-bench 2333ms (~baseline).
2026-06-06 23:05:24 -04:00
Yogthos
16cd617bd6 core: move not-any?/not-every? to the Clojure overlay (jolt-1j0 phase 2)
First pure-fn migration batch. Both are leaf fns (no internal Janet callers);
the Clojure defs (not . some / not . every?) match the prior Janet behavior.
Removed the core-* defns and their core-bindings entries.

conformance 218/218 x3, clojure-test-suite 3927, core-bench 2340ms vs 2336ms
baseline (noise).
2026-06-06 23:01:25 -04:00
Yogthos
3638521e2d core: move juxt/every-pred/some-fn to Clojure
Three pure function combinators into the collection tier, as compositions of
mapv/every?/some/apply. Three more core-* primitives + table entries gone.
Battery 3916 -> 3920 (the canonical defs are more correct than the prior Janet
ones — some-fn returns the matching value, every-pred is properly boolean).
Conformance 218/218 all modes.
2026-06-06 18:09:42 -04:00
Yogthos
57b8ffcb9b core: move frequencies/group-by/not-empty/filterv/max-key/min-key to Clojure
New collection tier (core/20-coll.clj): six pure, eager fns expressed as
compositions of frozen primitives (reduce/assoc/get/conj/filter/vec). Six more
core-* primitives + table entries gone from the Janet kernel.

Two semantics to preserve, caught by the suite:
- max-key/min-key use the canonical multi-arity form (strict </> on the first
  pair, <=/>= in the fold) to reproduce the JVM IEEE-754 NaN behavior; a single
  reduce got the NaN cases wrong.
- frequencies/group-by base on (hash-map), not the {} literal: a struct map
  doesn't canonicalize collection keys across representations ({:a 1} literal vs
  (hash-map :a 1)), a PHM does — same reason the Janet impl used make-phm.

Conformance 218/218 all modes; battery holds 3916.
2026-06-06 18:02:34 -04:00