The clojure.core type predicates bottom out at host tests that overlay
Clojure can't reach. Expose them under jolt.host so the predicate web can
be built as pure compositions that lower to exactly these calls:
numeric tower: exact? flonum? integer-type? rational-type?
collection reps: pvec? pmap? pset? cseq? empty-list? cseq-list? lazyseq?
exact? is wrapped to be total (Chez's raw exact? errors on a non-number;
the others return #f for a non-match). lazyseq? is exposed in
lazy-bridge.ss because jolt-lazyseq? is defined there, after predicates.ss.
map?/set?/seq? are deliberately not reduced to a single rep test: they are
extended at runtime with sorted-collection/record/lazy arms, so only the
rep predicates are exposed, not those unions. Additive only (new bindings,
nothing references them yet); bench unchanged within noise.
The rest = more() change made (rest coll) return a jolt-lazyseq, so the very
common (conj (rest xs) y) hit jolt-conj1's base case, which doesn't recognize a
lazyseq, and threw "conj: unsupported collection" (caught by core.match's
seq-pattern compiler). conj on a lazy-seq now prepends like conj on any seq.
The corpus had no row exercising a collection op on a rest-derived seq, so the
class slipped past the gate; add a seqs/lazy-seq-interop suite (conj/into/first/
count/nth/reduce/map/filter/apply/cons/=/empty?/seq over (rest …) and lazy-seq),
all JVM-certified.
jolt's seq layer realized one element ahead of Clojure, so a side-effecting
lazy seq ran its producer too eagerly. Four changes bring it in line:
- rest is Clojure's more(): it returns the tail without realizing it. An
unforced tail (vector / string / lazy-seq cell) comes back as a deferred
seq, so (rest (iterate f x)) does not call f. next still realizes one.
- iterate applies f lazily, inside the tail thunk, so (first (iterate f x))
is x with no call to f (clojure.lang.Iterate parity).
- take realizes exactly n: the last element terminates without touching the
rest, instead of forcing one more element of the source.
- an empty realized lazy seq is still a sequence value, printing "()" not
"nil" (a JVM LazySeq is never nil).
Also: the map transducer's step fn now takes multiple inputs
([result input & inputs]) so a multi-collection transduce applies f across
all of them. Fixes medley's join/window/sequence-padded laziness and
multi-input transducer tests (now 293/293). The rest change also fixed a
latent overrun in distinct/dedupe over a map's empty tail.
iterate is a seed source, re-minted.
The printer's two entry points (jolt-pr-str in rt.ss, jolt-pr-readable in printing.ss)
get register-pr-str-arm! / register-pr-readable-arm!, plus register-pr-arm! for the
types whose str and readable forms match (bigdec/inst/uuid/tagged/record/ns/var). The
normalize arms (sorted, lazy-seq, queue) and the uri readable arm register per-printer.
Also folds in the hash (dyn-binding var-cell), class (io uri/uuid/file), and get
(transients) arms missed earlier.
natives-array's get stays a case-lambda wrapper on purpose: its 2-arg path errors on
an out-of-bounds index while the 3-arg path returns the default, an arity distinction
the (coll k d) registry collapses — left as-is to preserve behaviour.
Completes jolt-lmot: all six dispatchers (hash/class/get/=/pr-str/pr-readable) off the
set!-rebind chains. make test green, 0 new corpus divergences; pr-str/str of inst,
uuid, bigdec, sorted-map, record-with-lazyseq, queue all verified.
jolt-str-render-one and instance-check were each extended by a chain of
set!-wrapping closures spread across ~10 and ~5 host files, so the real
behavior of either was scattered and load-order-dependent. Give each a
registry the base file owns: converters.ss/records-interop.ss define the
registry plus a register-* helper, and each extending file registers one arm
instead of capturing %prev and set!-ing the global.
str-render arms are type-disjoint; instance-check arms run newest-first (the
old outermost-wins order) and may return 'pass to defer. The string-token ->
symbol normalization the natives-array arm did for every inner arm moves to
the dispatcher head; array tokens stay strings for that arm to decide.
jolt-ogib.14. Runtime-only shims, no re-mint.
Rename src/jolt -> stdlib (the runtime-loaded layer; jolt-core stays the
seed-baked layer) and update the loader / emit-image / doc paths. Drop dead
code: the spike/ experiments, the duplicate clojuredocs-export.edn (json moves
to tools/), the Janet-era jolt.http binding, and the orphaned
persistent_vector.clj whose ns/path didn't even match.
Strip porting residue from comments and docstrings across host/chez, jolt-core,
stdlib, tests, and docs: internal issue ids, "Phase N" markers, and the "vs
Janet" historical exposition, leaving present-tense descriptions and the real
JVM-Clojure semantic contrasts. Same pass over the corpus suite labels. The seed
is unchanged (docstrings/comments aren't emitted), so the self-host fixpoint and
corpus are untouched.
Port tools/spec_coverage.py off the dead janet probe to bin/joltc and regenerate
coverage.md; drop the dead :host/janet rule from certify.clj and regenerate the
conformance profile. Add docs/host-interop.md (the JVM shims and how to register
your own host class from a library) and a writing-style note in CLAUDE.md.
Stabilize the four racy concurrency corpus cases (future-cancel and agent
send/send-off): give the future a sleeping body and the agent a slow action, so
cancel reliably catches an in-flight future and deref reliably reads the
pre-update snapshot. They certify deterministically now, so drop their :flaky
allowlist entries and the orphaned legend.
Rephrase comments that pointed at deleted Janet files (emit.janet, the seed
sources, 'the Janet back end punts ...') to present-tense descriptions of the
Chez behavior. Comment/docstring-only; the self-host fixpoint is unchanged
(comments don't affect the compiled seed).
Delete five files that were Janet-host shims with no Chez path: clojure.java.io
(provided natively by host/chez/io.ss), and jolt.{nrepl,png,interop,shell}
(the janet.* bridge, os/shell, janet.net — none exist on Chez).
jolt-cf1q.6
Add the dynamic-binding cluster on the Chez RT: a per-thread binding stack
(host/chez/dyn-binding.ss) backing binding / with-bindings* / var-set /
thread-bound? / with-local-vars / with-redefs / bound-fn* /
get-thread-bindings / alter-var-root + the __local-var seam.
Frames are stored innermost-first as identity-keyed alists of mutable
(cell . value) pairs, so var-set updates the current binding in place. The
two var-read paths — var-deref (compiled code) and jolt-var-get (var-get /
deref on a cell) — are chained onto the stack so a `binding` frame is seen
by every read, with a fast path when the stack is empty. var-cells now hash
by ns/name so a var works as a map key (with-redefs builds (hash-map (var f)
v); get-thread-bindings returns a var-keyed map).
Fix a latent bug exposed once with-in-str could bind *in*: seq? didn't
recognize a lazy-seq. predicates.ss's jolt-seq? predates the lazyseq record,
and unlike the native-op dispatchers it's reached via var-deref, so the
patch must re-def-var! the var, not just set! the top-level binding.
Note: with-bindings* over a hash-map literal now returns the correct Clojure
value where the seed returns a stale one — the seed's PHM can't find a var
key (which is why its `binding` uses array-map); on Chez frames look up by
cell identity.
Prelude corpus parity 1972 -> 2000, floor raised. Gate: _dynbind 24/24,
prelude corpus 0 divergences, full jpm test, conformance 355x3.
The lazy-seq macro expands to (make-lazy-seq (fn* [] (coll->cells body)))
and lazy-cat to (concat (lazy-seq c) ...); both seed natives were nil on
the prelude, so every overlay fn built on lazy-seq — repeat/iterate/
cycle/dedupe/take-nth/keep/interpose/reductions/map-indexed/distinct/
interleave/tree-seq(->flatten)/partition-all/lazy-cat — hit apply-jolt-nil.
lazy-bridge.ss bridges to the cseq model: a jolt-lazyseq is a deferred
seq forced once by an extended jolt-seq; jolt-cons defers a lazyseq tail
so an infinite (repeat/iterate/cycle) stays lazy. A lazyseq is a new
value type, so the dispatchers that don't route through jolt-seq learn it
(sequential? for =/hash, plus count/empty?/nth/printers) or a raw
unrealized lazyseq escapes — the corpus compares (= [1 3 5] (take-nth …))
against it directly.
seq.ss: jolt-concat is now fully lazy (the rest isn't forced until the
first coll is exhausted), so a self-referential lazy-cat — fib =
(lazy-cat [0 1] (map + (rest fib) fib)) — no longer memoizes its tail as
empty by reading fib before its def binds.
Prelude parity 1837 -> 1886, 0 new divergences. Floor raised to 1886.