jolt/.dirge/skills/jolt-dev/SKILL.md
Yogthos 979486600a fix: persistent hash map — working with simple array implementation
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.
2026-06-02 13:25:20 -04:00

2.2 KiB

Persistent Data Structures

Load .clj source files into a context via the reader/evaluator:

(use ./src/jolt/api) (use ./src/jolt/reader) (use ./src/jolt/evaluator)
(def ctx (init))
(def s (slurp "src/jolt/clojure/lang/persistent_vector.clj"))
(var cur s)
(while (> (length (string/trim cur)) 0)
  (def [form rest] (parse-next cur))
  (set cur rest)
  (when (not (nil? form))
    (try (eval-form ctx @{} form) ([err] nil))))

:mutable? flag: (init) loads persistent structures by default. Pass {:mutable? true} to use Janet native mutable types instead: (def ctx (init {:mutable? true})).

PersistentVector (17 forms, fully working)

src/jolt/clojure/lang/persistent_vector.clj — 32-way branching trie with tail optimization.

PersistentHashMap (18 forms, bitmap WIP)

src/jolt/clojure/lang/persistent_hash_map.clj — HAMT-based persistent hash map. 328-closing-parens balanced. bmn-assoc structural logic correct — vector/bitpos/index/hash all work in isolation. The < operator was missing from core-bindings causing loop conditions to fail silently.

Gotchas (Critical)

Missing comparison operators

<, >, <=, >= are NOT in core-bindings by default. Add them before any Clojure code with loop conditions:

"<" core-<   ">" core->   "<=" core-<=   ">=" core->=

Symptom: (loop [i 0] (if (< i 3) (recur (inc i)) i)) returns nil because < resolves to nil → apply fails silently.

struct? vs tables

Janet struct? returns false for deftype instances (tables). Use (get val :jolt/deftype) for instance? checks, not (and (struct? val) ...).

defrecord macro

Builds key-value pairs at expansion time: (array-map :a a, :b b). Does NOT use interleave at eval time.

and/or macros

(and x y)(let* [and__x x] (if and__x (and y) and__x)). (or x y)(let* [or__x x] (if or__x or__x (or y))). Registered as macros.

loop macro

Explicit macro: (defn core-loop [bindings & body] (list* (sym "loop*") bindings ...)) + "loop" core-loop in core-bindings + "loop" true in core-macro-names.

. special form field access

For deftype instances: (.-cnt obj)(get obj :cnt). The - prefix is stripped.