jolt/.dirge/skills/jolt-dev/SKILL.md
Yogthos fb66851e06 Phase 4: deftype/defrecord completion
- evaluator.janet: fix set! to handle (.-field obj) shorthand
  Previously only recognized (. obj -field) format
- core.janet: rewrite core-defrecord to emit (deftype ...) instead of
  plain def with array-map constructor
- core.janet: fix core-map? to recognize deftype table instances
  (and returns last truthy, not boolean true — wrap in if)
- core.janet: fix core-count to skip :jolt/deftype key for records
- core.janet: add ->TypeName arrow factory that calls TypeName.
  constructor via Clojure form emission
- 11 new tests: deftype fields/mutation/instance?, defrecord
  construction/map-behavior/fields/factories, record equality
- map->TypeName factory deferred (needs get/symbol resolution fix)
- 317 tests pass, 0 failures
2026-06-02 21:10:33 -04:00

91 lines
3.9 KiB
Markdown

# jolt-dev
Jolt development workflow — build, test, special form patterns, Janet gotchas
## 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
```
## Janet Eval Pipeline (critical)
Janet's `(parse s)` does NOT return a parsed form — it returns `[symbol, error-position]`.
For evaluating Janet source strings, use the parser pipeline:
```janet
(def p (parser/new))
(parser/consume p source)
(parser/eof p) # REQUIRED — otherwise produce returns nil
(def form (parser/produce p))
(eval form)
```
**Never** try `(eval [if true 1 2])` — Janet's `eval` doesn't recognize special forms in tuple data structures.
## `var` vs `def`
When you need to mutate a local with `set`, use `(var x nil)` not `(def x nil)`. `def` creates constants.
## Compiler (see also `jolt-compiler` skill)
`src/jolt/compiler.janet` — Clojure→Janet source compiler with macro expansion.
Key design decision: **compile-and-eval emits Janet DATA STRUCTURES, not source strings**, because Janet's `eval` doesn't see `use`-imported symbols. `core-fn-values` table resolves Janet names to actual function values at compile time.
### Adding a compiled op
1. Add match arm in `analyze-form` — maps Clojure form → AST node
2. Add `emit-*-str` for source-to-source path → arm in `emit-ast` dispatch
3. Add `emit-*-expr` for data-structure path → arm in `emit-expr` dispatch
4. Add tests in `test/compiler-test.janet`
### Emit-expr critical rules
- **Vectors**: wrap with `['tuple ...]` — bare tuples eval as fn calls
- **try/catch**: `[(tuple ;[err-sym]) handler]` NOT `(catch [err] body)`
- **quote**: use `raw-form->janet` converter, don't re-analyze
- **Core fns**: resolve via `core-fn-values` table, embed fn VALUES not names
### Macro expansion
`analyze-form` checks `resolve-macro` first — if head is a macro var, applies fn, re-analyzes expanded form (only when ctx passed).
### Loop/recur compilation
`(loop* [x 0] body)``(do (var name nil) (set name (fn [x] body)) (name 0))`
Recur rewrites to `(loop-name arg...)` via `:jolt/current-loop` binding.
## Special Form Checklist
To add a new special form to the evaluator AND compiler:
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`
The match arm receives `ctx`, `bindings`, and `form`. Use `(in form 1)` for first arg.
### Current special forms (29):
`quote`, `syntax-quote`, `unquote`, `unquote-splicing`, `do`, `if`, `def`, `defmacro`, `fn*`, `let*`, `loop*`, `recur`, `throw`, `try`, `set!`, `var`, `locking`, `instance?`, `defmulti`, `defmethod`, `deftype`, `new`, `.`, `var-get`, `var-set`, `var?`, `alter-var-root`, `find-var`, `alter-meta!`, `reset-meta!`, `intern`
## PersistentHashMap (phm.janet)
`src/jolt/phm.janet` — separate module imported via `(use ./phm)` into `core.janet`.
PHM is a table with `:jolt/deftype` tag. Has `:cnt`, `:buckets` (array of 8 flat `[k v k v ...]` arrays), `:_meta`.
16 core functions updated with `(phm? x)` branches. `phm-to-struct` converts to Janet struct for compatibility.
### Janet `break` limitation
Janet `break` cannot be used inside `let` blocks — use `(var found nil)` + `(while ... (if condition (do (set found val) (break))))` pattern.
### core-binding: use array-map, not hash-map
`core-binding` macro must emit `(array-map ...)` for the binding frame — PHM's get is incompatible with `push-thread-bindings` var-get lookup.
### core-intern gotcha
Janet does not support Clojure-style multi-arity destructuring `([a b] ...)` in `defn`. Use flat args or wrapper functions.
## Symbol representation
Jolt symbols are `{:jolt/type :symbol :ns <string-or-nil> :name <string>}` as produced by the reader.
## Janet LSP
Janet LSP produces many false positives on `.janet` files — safe to ignore.