diff --git a/.dirge/memory/MEMORY.md b/.dirge/memory/MEMORY.md index 0a113b0..da4195a 100644 --- a/.dirge/memory/MEMORY.md +++ b/.dirge/memory/MEMORY.md @@ -1,9 +1,7 @@ -`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 in core-macro-names. `defrecord` macro builds key-value pairs at expansion time using `array-map` constructor, not `interleave` at eval time. -§ -SCI added as git submodule at `vendor/sci` (https://github.com/borkdude/sci.git). Clone with `git submodule update --init`. 317/317 forms load from 9 core files. Internal namespaces (interop, parser, opts) partially loaded — parser.cljc needs `utils/new-var` (calls `sci.lang.Var.` constructor) and `edamame/normalize-opts` stubs. -§ `:mutable?` compile flag in `api.janet` `init`: persistent Clojure data structures loaded by default. Pass `{:mutable? true}` to use Janet native mutable tuples/tables instead. `load-persistent-structures` function in api.janet loads `src/jolt/clojure/lang/persistent_vector.clj` (17 forms) and swaps `vec`/`vector`/`vector?` bindings in `clojure.core`. PersistentHashMap WIP in `src/jolt/clojure/lang/persistent_hash_map.clj` (24 forms, 328 parens balanced, bitmap calculation broken — inner let expression returns 0 instead of computed value). § SCI 9 core files (macros, protocols, types, unrestrict, vars, lang, utils, namespaces, core) load with 0 failures in order: macros→protocols→types→unrestrict→vars→lang→utils→namespaces→core. 46 namespaces populated. Internal SCI files (interop, opts, parser, analyzer, interpreter) need edamame/tools.reader stubs but public API works via Jolt-native eval-string that bypasses SCI's eval pipeline. § Janet `get` DOES follow table prototype chain — confirmed working: `(get child :key :not-found)` returns parent's value if child has prototype set via `table/setproto`. Earlier confusion about `get` not following prototypes was incorrect. The `binding-get` helper that walked prototypes manually was unnecessary and was reverted. Current `resolve-sym` uses: `(get bindings name :jolt/not-found-1)` then falls back to `binding-get` only if `:jolt/not-found-1` returned. +§ +Persistent data structures use simple array-based implementation (node-assoc/node-find/find-key-index), NOT HAMT bit-trie. HAMT failed because Janet uses 64-bit doubles and bit operations (brshift, brushift, band) require 32-bit signed ints — hash values from Janet's (hash) function exceed the 32-bit range. Array approach uses linear scan: find-key-index searches key-value pairs, node-assoc appends to array on insert or clones+updates on replace. O(n) lookup, acceptable for small maps. diff --git a/.dirge/memory/PITFALLS.md b/.dirge/memory/PITFALLS.md index f8ccbd3..34e1bb2 100644 --- a/.dirge/memory/PITFALLS.md +++ b/.dirge/memory/PITFALLS.md @@ -1,5 +1,5 @@ -`binding-get` uses `return` from inside while loop within fn body — this signals an error in Janet (user0 tuple). Must use `(var result :jolt/not-found)` + `(when (in t name) (set result ...) (set t nil) (break))` pattern. Cannot use `return` for early exit from loops in Janet. -§ Janet `struct?` returns false for tables. Deftypes created by Jolt's `deftype` special form are tables (via `@{}`), not structs. So `(struct? val)` fails for deftype instances but `(get val :jolt/deftype)` works. This broke `instance?` check for persistent vector — fixed by changing from `(and (struct? val) ...)` to `(get val :jolt/deftype)`. The `.` special form for deftype field access strips `-` prefix: `(.-cnt obj)` → `(get obj :cnt)`. § Missing comparison operators: `<`, `>`, `<=`, `>=` were NOT in `core-bindings`, causing silent nil returns in loop conditions. Each must be added as both a function binding (in `core-bindings` map) AND if it's a comparison used inside Clojure macros, it may need special handling. Symptom: `(loop [i 0] (if (< i 3) (recur (inc i)) i))` returns nil because `<` resolves to nothing → apply fails → returns nil. +§ +Bit operations in Janet (brshift, brushift, band, bor, bxor) use 32-bit signed integers. Values from (hash key) can exceed 32-bit range (>2^31). brshift with out-of-range value fails with 'rhs must be valid 32-bit signed integer'. Use (band x 0xFFFFFFFF) before shifting, or use arithmetic approaches (mod, /, pow) instead of bit ops for hash-based data structures. diff --git a/.dirge/skills/.usage.json b/.dirge/skills/.usage.json index dc0aa31..38887ad 100644 --- a/.dirge/skills/.usage.json +++ b/.dirge/skills/.usage.json @@ -1,12 +1,24 @@ { + "jolt-bootstrap": { + "created_by": "agent", + "use_count": 8, + "view_count": 18, + "patch_count": 10, + "last_used_at": "2026-06-02T03:43:48.038435+00:00", + "last_viewed_at": "2026-06-02T03:43:48.028512+00:00", + "last_patched_at": "2026-06-02T03:44:45.935676+00:00", + "created_at": "2026-06-01T21:49:51.101718+00:00", + "state": "active", + "pinned": false + }, "jolt-dev": { "created_by": "agent", - "use_count": 19, - "view_count": 29, - "patch_count": 29, - "last_used_at": "2026-06-02T16:56:23.616413+00:00", - "last_viewed_at": "2026-06-02T16:56:23.607520+00:00", - "last_patched_at": "2026-06-02T16:56:42.437173+00:00", + "use_count": 20, + "view_count": 30, + "patch_count": 30, + "last_used_at": "2026-06-02T17:27:04.417433+00:00", + "last_viewed_at": "2026-06-02T17:27:04.404716+00:00", + "last_patched_at": "2026-06-02T17:27:24.746485+00:00", "created_at": "2026-06-01T21:26:06.614465+00:00", "state": "active", "pinned": false @@ -20,17 +32,5 @@ "created_at": "2026-06-01T20:56:39.144222+00:00", "state": "active", "pinned": false - }, - "jolt-bootstrap": { - "created_by": "agent", - "use_count": 8, - "view_count": 18, - "patch_count": 10, - "last_used_at": "2026-06-02T03:43:48.038435+00:00", - "last_viewed_at": "2026-06-02T03:43:48.028512+00:00", - "last_patched_at": "2026-06-02T03:44:45.935676+00:00", - "created_at": "2026-06-01T21:49:51.101718+00:00", - "state": "active", - "pinned": false } } \ No newline at end of file diff --git a/.dirge/skills/jolt-dev/SKILL.md b/.dirge/skills/jolt-dev/SKILL.md index fbd80bb..6af2475 100644 --- a/.dirge/skills/jolt-dev/SKILL.md +++ b/.dirge/skills/jolt-dev/SKILL.md @@ -1,47 +1,31 @@ -## Persistent Data Structures +# jolt-dev -Load `.clj` source files into a context via the reader/evaluator: +Jolt development workflow — build, test, special form patterns, Janet gotchas -```janet -(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)))) +# Jolt Development + +## Build & Test + +```bash +cd /Users/yogthos/src/jolt +jpm build # produces build/jolt +jpm test # runs all tests +janet test/foo.janet # run a single test file from project root ``` -**`:mutable?` flag:** `(init)` loads persistent structures by default. Pass `{:mutable? true}` to use Janet native mutable types instead: `(def ctx (init {:mutable? true}))`. +## Special Form Checklist -### PersistentVector (17 forms, fully working) -`src/jolt/clojure/lang/persistent_vector.clj` — 32-way branching trie with tail optimization. +To add a new special form to the evaluator: -### 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. +1. Add the name to `special-symbol?` in `src/jolt/evaluator.janet` +2. Add a match arm in `eval-list` (the match on `name`) +3. Add tests in `test/evaluator-test.janet` -## Gotchas (Critical) +The match arm receives `ctx`, `bindings`, and `form` (the full list). Use `(in form 1)` for first arg, etc. -### 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. +**Non-symbol heads** (keywords, etc.): `eval-list` first checks `(and (struct? first-form) (= :symbol (...)))` before extracting `name`. If not a symbol, falls through to default function application. -### `struct?` vs tables -Janet `struct?` returns **false** for deftype instances (tables). Use `(get val :jolt/deftype)` for `instance?` checks, not `(and (struct? val) ...)`. +### Current special forms (22): +`quote`, `syntax-quote`, `unquote`, `unquote-splicing`, `do`, `if`, `def`, `defmacro`, `fn*`, `let*`, `loop*`, `recur`, `throw`, `try`, `set!`, `var`, `locking`, `instance?`, `defmulti`, `defmethod`, `deftype`, `new`, `.` -### `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. \ No newline at end of file +## Pers… (truncated, 34945 bytes total) \ No newline at end of file