Final spec areas. Bugs caught and fixed:
- (def name docstring value) used the docstring as the value; now the 3-arg
docstring form binds the value and records :doc meta
- resolve was a nil stub; now a special form resolving a symbol to its var
(nil if unresolved). Added find-ns (non-creating lookup) and ns-name.
- in-ns didn't evaluate its arg, so (in-ns 'foo) failed; now evaluates it per
Clojure (the integration test's unquoted form updated to the quoted idiom)
- #(... %& ...) built %& as a positional param instead of a & rest param;
now emits (fn* [... & gen] ...) so %& captures the rest
Full public-API spec layer now in place. conformance 218/218, jpm test green.
Add spec suites covering lazy-seq/lazy-cat laziness, infinite + self-referential
seqs (ones/nats/fib), realized?; transducers (map/filter/take/cat/keep/mapcat as
xforms, comp, into/transduce/sequence/eduction/completing); and metadata
(with-meta/meta/vary-meta/^ reader). All passed against jolt as written — no
implementation changes needed. jpm test green.
Add spec suites for stateful refs (atoms/volatiles/delays/promises), multimethods
(dispatch + hierarchies), and protocols/types/records. Bugs caught and fixed:
- reify with multiple methods registered only the first (stepped i by 2 over
one-element specs); now collects every method spec
- get-method/methods/remove-method evaluated their arg to the dispatch fn, but a
multimethod's methods live on its var — now resolve the var (get-method and
methods are special forms; remove-method fixed). get-method/methods added.
- no global hierarchy: 2-arg derive modified a throwaway, isa? was hardcoded
false, parents missing, ancestors/descendants returned arrays (so contains?
failed), and dispatch ignored derive. Now a global hierarchy backs the 1/2-arg
derive/isa?/parents/ancestors/descendants (returning sets) and multimethod
dispatch falls back to it.
Corrected a phase5 port assertion that encoded the old isa? bug.
conformance 218/218, jpm test green.
Add spec suites for conditionals/logic/let/loop/iteration/threading, functions
(definition, application, combinators), and destructuring across let/fn/loop/
doseq/for. Bug caught and fixed: loop* assumed simple-symbol bindings, so
(loop [[a b] [1 2]] ...) errored; it now destructures each binding via
destructure-bind, supporting patterns in loop bindings (and recur). jpm test green.
Add spec suites for numbers/arithmetic, strings (str + clojure.string), and
type/value predicates. Bugs they caught and fixed:
- <, >, <=, >= were binary-only; now variadic ((< 1 2 3) chains)
- seq? was true for vectors; vectors are not ISeq in Clojure, so (seq? [1])
is now false (true only for lists/lazy-seqs)
- clojure.string/index-of and last-index-of were 1-based (stray inc); now
0-based per Clojure
Corrected a systematic-coverage assertion that encoded the old seq? bug.
jpm test green, conformance 218/218.
Add behavioral spec suites for the collection API (~180 cases). Writing them
surfaced and fixed real bugs:
- (count nil) errored; now 0 (Clojure semantics)
- (repeat x) and (repeatedly f) — the infinite 1-arg arities — were unsupported;
now return lazy infinite seqs
- set equality used deep= (representation-sensitive), so a set of map literals
could differ from an equal set built differently; now value-based (each
element value-equal to some element of the other)
Known limitation filed: phm-typed maps (from hash-map) used as set elements /
map keys hash by identity (map *literals* are structs and work).
jpm test green.
Reorganize the flat 49-file test/ into three layers (jpm test recurses, so all
are still discovered):
- test/unit/ white-box component tests (reader, evaluator, types,
persistent-map, lazy-seq, macro, interop, compiler)
- test/integration/ cross-cutting + regression batteries (conformance, jank,
sci-bootstrap/runtime, features, systematic-coverage, api,
core, namespaces, ported clojure suites) and
.../ports/ ported clojure/cljs test batches pending consolidation
- test/spec/ the behavioral contract (built out in following commits)
- test/support/harness.janet shared defspec table runner (cases compared via
Jolt's own =, with a :throws sentinel) + expect= helpers
Files moved with git mv (history preserved) and import paths fixed for depth.
jpm test green. README Test section updated.
Next: build out test/spec/ to cover the public API area-by-area, mining the
integration batteries and filling gaps.
Update stale claims: bigint/biginteger (int/s64), +'/*'/inc' aliases, transients
(correctness-only), and the byte-array-via-buffer / array family are now
implemented. Note clojure.repl/template and JVM reflection/proxy remain out.
The last two keystone forms in namespaces.cljc now load, so the entire SCI
source in the harness's load order evaluates with ZERO failures (was 420/2).
Two real bugs fixed to get there:
- with-meta crashed on non-collections: (with-meta a-fn {...}) did (keys fn) ->
'expected iterable, got function'. SCI marks core fns private this way. Now
returns functions/scalars unchanged (Jolt can't attach meta to a raw fn).
- select-keys ignored a vector key-list: (each k ks) iterated the pvec's table
instead of its elements, so (select-keys m [:a]) returned {}. Now realizes ks.
Plus: add clojure.set/join (the only missing clojure.set fn), assert 0 failures
in test-load-sci so it's a real regression guard, drop debug instrumentation.
Jolt now fully loads SCI's reader-independent implementation: every pure-Clojure
module, the entire clojure.core aggregation map, and the clojure.* namespace
wiring. conformance 218/218, features 78/78, jank 120.
Raw (deftype T [fields] Proto (method [this] body) ...) silently ignored its
protocol body — only the constructor was created, so protocol dispatch on a
deftype instance always failed (No method ... for T). defrecord worked only
because its macro expanded the body into extend-type forms.
deftype now does the same: emit an extend-type per protocol, wrapping each
method body in a let that binds the type's fields from the instance. This is a
real correctness fix (any deftype-with-protocols was broken) and unblocks SCI's
sci.lang.Var (a deftype implementing vars/IVar) — the clojure.core aggregation
map now gets past protocol dispatch.
conformance 218/218, features 78/78, jank 120.
Push SCI loading much further: the giant clojure.core aggregation map in
namespaces.cljc now resolves ALL its symbols (it previously failed on dozens).
- host_stubs.clj: forward-decl no-op fns for SCI's host-layer modules
(parser/read/load/macroexpand/resolve/proxy/reify) — Jolt supplies its own
reader/loader, so these only need to resolve when the binding map is built.
Stubs are non-nil fns so :refer re-interns them (a nil-valued var is dropped).
- Load Jolt's clojure.string/set/walk/edn before namespaces.cljc.
- Add real clojure.core: ==, print-str, eduction/->Eduction,
seq-to-map-for-destructuring, split-lines, postwalk-demo/prewalk-demo,
+'/-'/*'/inc'/dec' (Jolt doesn't overflow, so == non-quoted), proxy-super
and friends (resolve-only, JVM-specific).
Fix a real require bug: eval-require stopped at the first option, so
[ns :as x :refer [...]] silently dropped the :refer. Now processes all options.
SCI load: clojure.core map now builds; only two forms remain (a Var-protocol
method on sci.lang.Var, and the clojure.repl namespace). conformance 218/218,
features 78/78, jank 120.
Load SCI's macro/expander modules from real source (destructure, doseq_macro,
for_macro, fns, multimethods) in the test harness — all load with 0 failures,
which resolved the sci-internal refs (doseq-macro/expand-doseq, fns/fn**, ...)
that previously blocked namespaces.cljc.
Add the remaining clojure.core vars, adapting JVM-isms to the Janet runtime:
enumeration-seq/iterator-seq -> plain seq, xml-seq -> tree walk, subseq/rsubseq
over sorted colls, char-escape-string/char-name-string, line-seq; resolve-only
for genuinely-JVM bean/proxy/print-method/print-dup/undefined?.
Fix a real defmethod bug: it errored when the multimethod symbol resolved to a
plain fn with no method table (e.g. a copy-core-var'd print-method) — now it
initializes the table, so (defmethod print-method T ...) works.
SCI load: 420/422 forms (was 390/392); only the two keystone aggregation maps
in namespaces.cljc remain. conformance 218/218, features 78/78, jank 120.
Replace the would-be error-stubs with real implementations backed by Janet's
C primitives, per the principle that these aren't things Jolt can't do — Janet
does them natively:
- Byte arrays -> Janet buffers (contiguous, C-backed, O(1) indexed get/put):
byte-array/bytes/aset-byte/aget/alength/aclone. Genuinely fast for byte work.
- Object & numeric arrays -> Janet arrays: object-array/int-array/long-array/
double-array/float-array/short-array/char-array/boolean-array, make-array,
into-array, to-array(-2d), multi-dim aget/aset, typed aset-*.
- 64-bit ints -> Janet int/s64 (bigint/biginteger); bigdec -> double.
- Casts: byte/short/unchecked-byte/short/char/float/double, bytes/ints/longs/...
- Chunked seqs: simple eager equivalents (Jolt does not chunk).
- Plus boolean, cat (transducer), disj!, random-sample, reader-conditional?,
sorted-map-by/sorted-set-by, array-seq, class, munge, clojure-version,
supers, test, rationalize, seque.
seq/reduce/map/count now work over byte-array buffers (realize-for-iteration +
core-seq handle buffers).
This took the SCI clojure.core aggregation map from 106 -> 21 missing core vars
(the rest are CLJS / Java-reflection / proxy — not applicable to a Janet host).
conformance 218/218, features 78/78, jank 120.
More clojure.core coverage (real implementations, not stubs):
- transient/persistent!/conj!/assoc!/dissoc!/pop! as correctness-preserving
aliases (Jolt collections are persistent, so no in-place speedup but correct)
- unchecked-* arithmetic (Jolt numbers don't overflow -> plain ops)
- hash-combine/hash-ordered-coll/hash-unordered-coll (24-bit masked to stay
in integer range), ex-cause (ex-info now records a cause), prefers, random-uuid
Fix a pre-existing REPL bug: the set printer in main.janet iterated the backing
phm's table keys (:buckets/:cnt) instead of the elements, so every set printed
as #{:buckets :cnt} at the REPL (pr-str was already correct). Now uses phs-seq.
conformance 218/218, features 78/78, jank 120.
While digging into test-load-sci, add genuinely-missing clojure.core fns:
doall/dorun/run!/tree-seq, key/val/map-entry?, rand-nth/replicate/
bounded-count/counted?/reversible?/seqable?, nat-int?/pos-int?/neg-int?/
double?/float?/ratio?/decimal?/rational?/numerator/denominator, list*,
special-symbol?/record?, promise/deliver, comparator/completing/halt-when/
ensure-reduced/unreduced, keyword-identical?/object?/tagged-literal/re-groups.
Fix a real pre-existing bug: iterating a map (first/seq/map/filter/reduce/vec/
realize-for-iteration) returned keys or values instead of [k v] entries.
Map literals are Janet structs (not phm), which several paths mishandled.
Now first/seq/realize-for-iteration yield entries for both structs and phms,
matching Clojure — e.g. (vec {:a 1}) => [[:a 1]], (map key m), (reduce f init m).
Tests: conformance 218/218 (+12), features 78/78, jank 120. Advances the SCI
bootstrap further (its clojure.core aggregation map resolves more symbols).
Round 3 of the persistent-collections work. Lists were immutable Janet arrays,
so conj/cons-prepend was an O(n) copy (O(n^2) to build a list) — a large perf
gap vs Clojure's PersistentList.
Add src/jolt/plist.janet: an immutable cons-cell list (first/rest/count), same
algorithm as Clojure/CLJS/jank PersistentList. conj/cons onto a list now creates
an O(1) node that shares the existing list as its tail (no copy), with a cached
O(1) count. Repeated conj is O(n) total instead of O(n^2).
Hooked plist through first/rest/next/seq/count/peek/pop/nth/empty/empty?, the
predicates (list?/seq?/coll?/sequential?), realize-for-iteration, =, coll->cells
(concat/lazy), both printers, destructuring, and instance? tags. (list ...) and
quoted lists stay arrays; only conj/cons introduce plist nodes, so the surface
and risk stay small.
Verified: reduce-conj of 200k elements runs in ~0.4s (was effectively O(n^2)).
conformance 206/206, features 78/78 (+7 list regressions), jank 120 (+1).
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).