jolt/.dirge/skills/jolt-compiler/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

3.8 KiB

jolt-compiler

Source-to-source Clojure→Janet compiler. Two-phase: analyze-form (classify + macro expand) → emit-ast (generate).

Architecture

Clojure form → analyze-form [form bindings ctx] → AST {:op ...}
                               ↓ (if head = macro var)
                          expand → re-analyze expanded form
                    ↓
              emit-ast (source string) or emit-expr (data structure)

Three public entry points:

  • (compile-form form &opt ctx) → Janet source string (debug/display)
  • (compile-ast form &opt ctx) → Janet data structure (for eval)
  • (compile-and-eval form ctx) → compile-ast + eval + ns interning

Why data structures, not source strings

Janet's eval does NOT have access to use-imported symbols. Emit Janet tuples with embedded function VALUES via core-fn-values table.

Source-to-source (compile-form + emit-ast) exists for debugging, NOT used by compile-and-eval.

Macro expansion

analyze-form checks resolve-macro before special form dispatch:

  1. Look up symbol in current ns → core ns
  2. If var-macro?, apply fn, re-analyze expanded form

Symbol classification

Order: qualified ns → local binding → core-symbol → bare symbol

core-renames MUST match actual fn names: "-""core-sub" (not "core--").

core-fn-values

Maps Janet string names to actual function values. Must be kept in sync with core-renames. Special mappings: "apply"apply (built-in), "some"core-some?, "pr-str"core-str, "nth"core-nth.

compile-and-eval interning

def/defn results are interned in the Jolt namespace via ns-intern so the interpreter can resolve bare symbols. This is critical — without it, (defn foo [x] x) followed by (foo 1) would fail with "Unable to resolve symbol: foo".

Loop/recur compilation

loop*(do (var name nil) (set name (fn [params] body)) (name init-vals...)) recur(loop-name arg...) via :jolt/current-loop binding

Throw/try compilation

  • throw(error val) in Janet
  • try(try body ([err] handler)) — Janet uses ([sym] handler), NOT (catch sym handler)

Quote — use raw-form->janet

Don't re-analyze quoted forms. Use raw-form->janet to pass Jolt reader forms verbatim to Janet's quote.

Remaining ops (interpreter only)

syntax-quote, set!, deftype, defmulti, defmethod — stateful/complex, always use interpreter.

Stateful forms (interpreter only even in compile mode)

defmacro, ns, deftype, defmulti, defmethod, require, in-ns

Note: def IS handled by compiler (macros expanded at analyze time).

eval-string dispatch (compile mode)

Stateful check: defmacro, ns, deftype, defmulti, defmethod, require, in-ns, syntax-quote, set!, var, ., new → interpreter. Everything else → compile-and-eval.

Adding a new op

  1. Add analyze-form match arm
  2. Add emit-ast + emit-expr match arms
  3. Update core-renames + core-fn-values if core fn
  4. Add tests in test/compiler-test.janet

Var system (Phase 3)

var-get, var-set, var?, alter-var-root, find-var, alter-meta!, reset-meta!, intern — all dispatched in evaluator, with Clojure wrappers in core-bindings. find-var takes a ctx + symbol, must eval args first (NOT pass raw form). core-meta handles vars via var-meta branch.

PersistentHashMap (Phase 2)

src/jolt/phm.janet — separate module, bucket-based with :jolt/deftype tag. core-binding macro uses array-map (not hash-map) to avoid PHM get() incompatibility with push-thread-bindings.

Test files

  • test/compiler-test.janet — all compiler tests (Phases 0-3)
  • test/phase6-final.janet — 47 comprehensive compile-mode tests

Run: janet test/compiler-test.janet or jpm test