jolt/.dirge/skills/jolt-dev/SKILL.md
Yogthos a8c453183f Phase 4: Macro expansion in compiler
- resolve-macro: resolve symbols to macro vars via ctx
- Macro expansion in analyze-form: detects macro heads, expands, re-analyzes
- compile-ast: emits Janet data structures with resolved core fn values
- compile-and-eval uses compile-ast (no source parse roundtrip)
- eval-string routes macros through compiler (expanded at analyze time)
- Fix - mapping: core-sub (core-- doesn't exist)
- All 317 tests pass + 6 new Phase 4 macro tests
2026-06-02 15:43:08 -04:00

3.2 KiB

name description
jolt-dev Jolt development workflow — build, test, special form patterns, Janet gotchas

Jolt Development

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

Compiler Development

See jolt-compiler skill for the Clojure→Janet source-to-source compiler workflow.

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. test/compiler-test.janet — 11 test groups covering all ops.

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.

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 (the full list). Use (in form 1) for first arg, etc.

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.

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

Persistent Data Structures

Located in:

  • src/jolt/clojure/lang/persistent_vector.clj
  • src/jolt/clojure/lang/persistent_hash_map.clj

Loaded at init time by load-persistent-structures in api.janet. Use {:mutable? true} to skip and use Janet-native types.

Implementation detail

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 require 32-bit signed ints.

Janet Gotchas

  • Bit operations (brshift, brushift, band) use 32-bit signed integers. Hash values can exceed 32-bit range. Use (band x 0xFFFFFFFF) before shifting.
  • deftype creates tables, not structs. struct? returns false.
  • (get child :key) DOES follow table prototype chain — resolved and confirmed working.
  • Janet LSP produces many false positives on .janet files — safe to ignore.

Symbol representation

Jolt symbols are {:jolt/type :symbol :ns <string-or-nil> :name <string>} as produced by the reader.