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.
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.
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.
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).
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.
- types.janet: ns? now accepts both structs and tables
(defensive for namespace-like tables created via @{...})
- core.janet: wire comment macro into core-bindings
(was in core-macro-names but missing from core-bindings)
- sci/lang_stubs.clj: minimal SCI type stubs for bootstrap
(IBox, HasName, IVar, DynVar protocols, SciUnbound/Var/Namespace deftypes)
- test-load-sci.janet: load stubs before SCI source files,
removed broken preprocessor
Before: 315 ok, 2 fail (SciVar + array/buffer)
After: 316 ok, 1 fail (only deftype in lang.cljc remains)
The remaining failure is lang.cljc's SciVar deftype with #?@ inserts
for JVM/CLJS-specific protocol implementations — a known limitation
for Phase 15 (SCI bootstrap).
- fn* form inside extend-type/extend-protocol must be @[...] (array)
so eval-list dispatches fn* special form (tuples hit tuple? → map eval-form)
- call forms stay @[...] (array) for correct special form dispatch via eval-list
- All 4 protocol tests pass: defprotocol, extend-type, extend-protocol, satisfies?
- 315 ok, 2 fail (pre-existing, unchanged)
Root cause: fn* multi-arity detection checks (array? (in form 1))
— defprotocol used @[...] for args, triggering multi-arity path
that treated the body form as a second arity pair.
Fixes applied:
- defprotocol: change fn* args from @[...] to [...] (tuple)
so single-arity fn* path is used with [this & rest-args]
- defprotocol: remove quote wrappers from protocol-dispatch call
(protocol-name and method-name passed directly as symbols)
- extend-type/extend-protocol: remove quote wrappers from
register-method call (symbols passed directly, not via quote)
- protocol-dispatch: use (in form ...) directly for proto/method
symbols (no eval-form needed after quote removal)
- satisfy?: extract name string from protocol value's :name field
(which is a symbol struct, not a plain string)
- make-reified: remove spurious (apply fn []) call
- core-reify: fix (keyword struct) → extract :name string first
- core-extend-protocol: handle single method spec vs multi
5 test sections (35-39) covering:
defprotocol, extend-type, extend-protocol,
satisfies?, reify — all pass
315 ok, 2 fail (pre-existing, unchanged)
- phm.janet: LazySeq (realize-once with caching, ls-first/ls-rest/ls-seq/ls-count)
PersistentHashSet (backed by PHM, phs-conj/phs-disj/phs-contains?/phs-count/...)
make-lazy-seq creates lazy thunk wrapper with :jolt/lazy-seq type tag
make-phs creates hash set with :jolt/set type tag
- evaluator.janet: :jolt/set handler in eval-form, disj/set? special forms,
special-symbol? entries, phm import for compile-time visibility
- core.janet: lazy-seq/iterate macros, set?/disj core fns, make-phs wired
into hash-set, core-bindings entries for set?/disj/lazy-seq/make-lazy-seq/iterate
Set support in core-conj/core-contains?/core-count/core-empty?/core-seq/core-get
LazySeq support in core-first/core-rest/core-count/core-seq
- 9 tests (32-33): lazy-seq basic ops, realize-once caching
PersistentHashSet construction, conj, disj, count, set? predicate
- 315 ok, 2 fail (pre-existing, unchanged)
- reader.janet: read-reader-conditional now falls back to :default
when :clj branch is not matched (iterates clauses a second time)
- test/phase6-test.janet: restore :default tests for #? and #?@
(#? :default fallback, #?@ :default fallback) — all 6 assertions pass
- evaluator.janet: add jolt/tagged form handler in eval-form
resolves tag via :data-readers from context env
- types.janet: :data-readers in make-ctx env with #inst/#uuid
passthrough readers (return strings on Janet, no java.time/UUID)
- Dynamic table construction for data-readers keys
(:#inst keyword containing # is invalid Janet literal syntax)
- test/phase6-test.janet: 4 test sections (28-31)
#inst, #uuid, #? conditionals, #?@ splicing — all pass
- 315 ok, 2 fail (pre-existing, unchanged)
- types.janet: find-var (ctx-based var resolution), alter-meta!, reset-meta!
- evaluator.janet: 10 new special-form dispatch arms for var ops
- core.janet: 6 new Clojure-level wrappers in core-bindings
- core-meta: add var-aware branch (was struct-only)
- core-binding macro: use array-map instead of hash-map (PHM incompatibility)
- 8 new tests (17: var system, 18: var metadata) — all pass
- 317 total tests, 0 failures
Root causes:
- core-binding used tuples instead of arrays for call forms.
Evaluator treats tuples as literal vectors, so calls failed.
Fixed with @[...] and proper (push-thread-bindings frame) forms.
- try handler: finally only ran on error, not success.
Fixed to run finally on both paths.
- ns accessors: all-ns, remove-ns, create-ns, the-ns, ns-interns,
ns-aliases, ns-imports-fn added to types.janet
- ns form: :require/:refer, :use, :refer-clojure/:exclude, :import
- eval-require: :refer support
- All 317 tests pass, 0 failures