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

3.9 KiB

jolt-dev

Jolt development workflow — build, test, special form patterns, Janet gotchas

Build & Test

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:

(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.