Resolve symbols that were unbound or leaking to broken Janet builtins:
- bit-clear/bit-set/bit-flip/bit-test/bit-and-not
- get-method/methods (multimethod introspection)
- second/ffirst/nfirst/fnext/nnext, last (was leaking to Janet, broke on pvec),
drop-last/take-last
- ident?/simple-symbol?/qualified-keyword?/simple-keyword?/simple-ident?/
qualified-ident?/inst?/inst-ms/uri?/uuid?/bytes?/tagged-literal?
- clojure.string/re-quote-replacement
Advances the SCI bootstrap; conformance 206/206, features 71/71.
Round 2 of the persistent-collections work.
Add a real 32-way branching-trie persistent vector (src/jolt/pv.janet) with a
tail buffer: O(log32 n) conj/assoc/nth/pop, with unchanged subtrees shared by
identity. Vector literals and vec/vector/conj/assoc/subvec/etc. now produce and
maintain these in the default (immutable) build, replacing the old tuple-based
vectors. Every core seq op, the destructurer, IFn application, the printers, =,
and the evaluator's literal/splice paths were taught to handle the pvec type.
Define several Clojure seq fns that were silently leaking to Janet builtins
(some, keep, interleave, flatten, mapcat, interpose) and broke once vectors
became tables; normalize collections through realize-for-iteration everywhere.
Build-time JOLT_MUTABLE flag now selects fast Janet-native mutable collections:
in that mode vectors are arrays (conj appends in place, vector? true, print []),
sharing one representation with lists. Default build is immutable.
Tests: conformance 206/206, features 71/71, jank 119 (baseline). Test helpers
normalized so Janet-level = compares against tuple literals regardless of repr.
The 2 test-load-sci failures (bit-clear/get-method) pre-date this work.
Add src/jolt/config.janet exposing a build-time `mutable?` constant read from
JOLT_MUTABLE at compile time (jpm build). Default is immutable.
Fix correctness bug: conj on a list (Janet array) mutated the original in
place. In immutable mode conj now copies first; in mutable mode it mutates.
Round 1 of persistent-collections work.
core-concat rewritten as functional: step(cs) returns pure thunk,
each rest-thunk captures its own (cs) state. No shared mutable
state — parallel paths through the same concat are independent.
core-map multi-coll rewritten as stateless: step(cs,idxs,reals)
returns thunk with fresh copies of all cursor arrays. No shared
mutable cursors — each invocation of the map step is independent.
ls-first/ls-rest handle :jolt/pending sentinel as nil.
Empty-cell guards prevent bounds errors on exhausted collections.
Verified: ones (infinite), map+ on ones, concat, all existing
tests pass. fib-seq produces [0 1] (2 elements) with clean cycle
break via sentinel. Full infinite self-referencing needs deeper
lazy-seq architecture (per-element LazySeq with sv sentinel).
- realize-ls: sets :val to :jolt/pending sentinel before calling thunk
to detect self-referencing cycles; if thunk re-enters same lazy-seq,
returns the sentinel instead of looping infinitely
- ls-first: handles :jolt/pending as nil (empty)
- coll->cells: returns nil when realize-ls gives :jolt/pending
- Empty-cell guards in ls-first/ls-rest prevent bounds errors
Verified working:
- ones (infinite self-referencing lazy-seq)
- map+ on ones (multi-coll lazy map)
- concat on lazy-seqs
- first 3 fib-seq elements [0 1 1]
Known limitation: fib-seq produces only 3 elements because the
multi-coll map's shared cursors clash with concat's shared state
during self-reference. Full infinite self-referencing needs
per-element lazy-seq generation (future work).
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.
Three bugs discovered while testing Clojure lazy-seq patterns (fib-seq, ones):
1. coll->cells did not recognize cons cells ([$val, fn]) as already-complete
cells. It treated them as regular indexed collections and re-wrapped the
rest function, causing double-wrapping that returned functions instead of
values. Fix: check (and (= 2 (length c)) (function? (in c 1))) before
splitting an indexed collection.
2. realize-ls cached thunk results directly without checking if the result
was itself a lazy-seq. When a rest thunk returned a lazy-seq table,
ls-first would then call (in table 0) which returned nil or wrong values.
Fix: recursively realize-ls when the thunk returns a lazy-seq.
3. core-map multi-collection path built all results eagerly in a while loop,
hanging on infinite sequences like (map + ones ones). Fix: return a
lazy-seq via make-lazy-seq/build-cell, interleaving one element at a time
from each collection.