- 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).
Janet's struct? returns true for tuples, causing print-value's
symbol-struct check to call (get tuple :jolt/type) which fails with
'expected integer key for tuple...got :name'.
Fix: reorder cond in print-value — check tuple? and array? before
struct? checks. Split (or tuple? array? struct? table?) into
individual arms so cond stops at the first match.
( range 10) now renders [0 1 2 3 4 5 6 7 8 9] instead of error.
315 ok, 2 fail (pre-existing, unchanged)
- 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)
- print-value now renders tuples as [1 2 3], arrays as (1 2 3),
structs/tables as {:k v}, sets as #{v}, keywords as :kw,
Jolt symbols as name or ns/name
- print-value uses prin (no trailing newline) for all scalars;
only adds newline at end of collection output
- print-collection recursively renders nested values
- ctx-set-current-ns "user" after init so REPL starts in user ns
- Forward var declaration for mutual recursion between
print-collection and print-value
Note: tuple/struct eval in piped REPL fails due to pre-existing
evaluator issue ("expected integer key for tuple") — this is
NOT caused by the print changes. Scalars render correctly.
315 tests pass, 0 regressions.
- print-value now renders tuples as [val1 val2 ...]
- print-value now renders arrays as (val1 val2 ...)
- print-value now renders structs/tables as {key val ...}
- print-value now renders sets as #{val1 val2 ...}
- print-value renders keywords with : prefix
- Forward var declaration for mutual recursion (print-collection → print-value)
- 315 tests pass, 0 regressions
- 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
Root cause: compile-and-eval for def created Janet global but never
interned the var in Jolt's namespace. Bare symbols fell through to
interpreter which couldn't find them.
Fixes:
- eval-string: bare symbols and tuples now go through compile path
- compile-and-eval: def/defn/defn- forms intern result in Jolt namespace
- New tests: defn/def integration (4 assertions)
All 317 tests pass.
Compile-time implementation of syntax-quote proved too complex for the
current emission pipeline (qualified symbol handling across both
string and data-structure paths). These ops require runtime context
(var lookup, field mutation, deftype construction) and are now correctly
routed to the interpreter via the stateful? check in eval-string.
Also fixed:
- raw-form->janet handles namespace-qualified symbols correctly
- emit-quote-str and emit-quote-expr use raw-form->janet consistently
- Duplicate function definitions removed
All 317 tests pass, 0 failures.
47 new compile-mode tests: collections, math, predicates, comparison,
seq operations (map/filter/reduce/take/drop), special forms (let/if/loop/
try/quote), macros (defn/when/and/or/fn/if-let), complex nesting.
Bugs fixed:
- emit-vector-expr: use (tuple ...) instead of bare tuple
(Janet eval treats bare tuples as function calls)
- make-symbol: / at position 0 → unqualified symbol
(was parsing as {:ns "" :name ""})
- core-renames: add missing fn? entry
quote expressions were returning AST structs instead of the
actual quoted values through compile-ast. Added raw-form->janet
converter that passes Jolt reader forms through verbatim
to Janet's quote special form.
- throw: emit (error val) in Janet
- try/catch/finally: map to Janet (try body ([err] handler))
- loop*/recur: emit closure-based loop (do (var f nil) (set f (fn [args] body)) (f init-vals))
- recur rewrites to loop function call at emit time
- All 317 tests pass + 5 new Phase 5 tests
- loader.janet: load-ns with compile? dispatch, compiled-cache, compiled?, clear-compiled-cache
- compiler.janet: compile-ast (Janet data structures for direct eval), compile-and-eval
- api.janet: compile-string, compile-file, eval-string dispatches to compile-and-eval when :compile? is true
- types.janet: :compile? flag and :compiled-cache table in context env
- Stateful forms (def, defmacro, ns, deftype, defmulti, defmethod) fall back to interpreter
- All 317 tests pass + 16 new Phase 3 tests
Root cause of HAMT failure: Janet uses 64-bit doubles, bit operations
require 32-bit signed ints. Hash values from Janet exceed this range.
Solution: Replaced bit-trie HAMT with simple array-based implementation:
- find-key-index: linear scan for key lookup
- node-assoc: append-to-array on insert, clone+update on replace
- node-find: linear scan for value retrieval
All operations work correctly:
(hash-map :a 1) → {:root [:a 1], :count 1}
(hash-map :a 1 :b 2) → {:root [:a 1 :b 2], :count 2}
phm-assoc / phm-get / phm-count all verified
O(n) lookup (acceptable for small maps). HAMT can be reintroduced
once Janet gets proper 32-bit int support or we implement bit ops
in pure Janet.
Root cause of bmn-assoc failure: < operator missing from core-bindings.
All loop conditions in HAMT hash map use (< i idx) which silently
failed, causing loops to return nil. Added core-<, core->, core-<=,
core->= bound to Janet's <, >, <=, >=.
Also removed leftover test files from debugging session.
Add binding-get helper that walks Janet table prototype chain.
resolve-sym now uses two-stage lookup: direct get first,
then prototype walk as fallback. This fixes access to outer
let bindings from inner let forms without breaking fn parameter
resolution (which uses direct table keys).
All 9 test suites pass including macro-test.
Added 22 new clojure.core bindings needed to implement persistent
data structures as Clojure source files:
Array operations: alength, aget, aset, aclone, object-array, int-array,
to-array (host interop for trie arrays)
Bit operations: bit-and, bit-or, bit-xor, bit-not, bit-shift-left,
bit-shift-right, unsigned-bit-shift-right (for 32-way trie indexing)
Integer ops: int (trunc), unchecked-inc/dec/add/subtract
Other: hash, namespace, set! support for (set! (. obj -field) val)
instance? support for deftype types via :jolt/deftype tag
All 9 test suites pass. 317/317 SCI forms load.
- Add SCI as git submodule at vendor/sci (replaces absolute path)
- Fix defrecord macro: emit array-map at expansion time, no interleave dep
- Remove stale test files (test-ctor, test-parser, edamame_shim)
- All 317 SCI forms load with 0 failures, 9 test suites green
Replace SCI's internal interpreter/parser/analyzer pipeline with
Jolt-native eval-string that delegates to Jolt's reader and evaluator.
All 317 SCI source forms load, 9 namespaces populated, 46 total ns.
eval-string result: (+ 1 2 3) → 6, (def x 42) x → 42
Removed edamame/core shim — no longer needed since we bypass
the SCI internal eval pipeline entirely.