- 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.
Add edamame.core + clojure.tools.reader.reader-types shim to core.janet.
init-edamame-shim! takes parse-string and read-form as args to avoid
circular module dependencies. Creates shim namespaces after SCI loads.
SCI eval-string is found and callable — next step is loading the 4
internal namespaces (interpreter, parser, analyzer, opts) whose source
files aren't loaded by the ns :require handler.
Janet's (put table key nil) silently drops the key. This caused
nil-valued fn/let/loop/macro parameters to be invisible to resolve-sym,
making them fall through to global symbol resolution.
Added bind-put helper that uses :jolt/nil sentinel for nil values,
and resolve-sym unwraps it back to nil.