- types.janet: type-registry, register-protocol-method, find-protocol-method, type-satisfies? - core.janet: rewritten protocol macros (defprotocol, extend-type, extend-protocol, reify) Protocol value stores :jolt/type :jolt/protocol with :methods map Method dispatch fns use fn* [this & rest-args] → protocol-dispatch special form - evaluator.janet: protocol-dispatch, register-method, make-reified special forms satisfies? special form with type registry lookup special-symbol? entries for all 3 protocol ops + satisfies? - 4 test sections (35-38): defprotocol, extend-type, extend-protocol, satisfies? extend-type: basic dispatch works (42 constant), .-field accessor needs further debug satisfies?: fully functional with type registry - 315 ok, 2 fail (pre-existing, unchanged)
3.4 KiB
jolt-dev
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
Special Form Checklist
To add a new special form to the evaluator:
- Add the name to
special-symbol?insrc/jolt/evaluator.janet - Add a match arm in
eval-list(the match onname) - 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 (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, intern, alter-meta!, reset-meta!, disj, set?
Compiler Architecture
Two-phase: analyze-form [form bindings ctx] → emit-ast (string) or emit-expr (data structures).
Why data structures: Janet's eval can't see use-imported symbols. Embed function VALUES directly via core-fn-values table.
eval-string dispatch (compile mode): stateful forms → interpreter; everything else → compile-and-eval. Macros expand at analyze time.
PersistentHashMap Gotchas
core-map?:(if (and (table? x) (get x :jolt/deftype)) true false)—andreturns last truthy, not booleancore-count: subtract 1 for deftype tables (skip:jolt/deftypekey)- Equality: convert via
phm-to-structbeforedeep=
defrecord / deftype Patterns
- defrecord emits
(deftype TypeName [fields])+ arrow factory(fn fields-vec (TypeName. field1 field2...)) - Records are tables with
:jolt/deftype= type name string set!field mutation:(set! (.-x obj) val)parses as array with.-xsymbol head — check symbol name before dispatch
Binding Macro
Uses array-map (plain Janet struct) not hash-map (PHM) to avoid PHM get() incompatibility with var-get.
Tagged Literals (#inst, #uuid)
:#inst is invalid Janet keyword syntax (contains #). Use dynamic table construction:
(let [dr @{}] (put dr (keyword "#inst") (fn [s] s)) dr)
LazySeq Patterns
- Use
indexed?nottuple?for realized sequences (may be arrays fromcons/concat) - Avoid
val'(apostrophe in symbol names) — causes Janet parse errors; usevfinstead ls-first/ls-rest/ls-seqall callrealize-lsfirst (caches result, realizes once)
Janet Gotchas
defcreates constants; use(var x nil)for mutable locals- Bare tuples in
evalare function calls:[1 2 3]tries to call1. Use['tuple 1 2 3] tryformat:(try body ([err] handler))NOT(try body (catch sym handler))- core-renames MUST match actual fn names:
"-"→"core-sub"(not"core--") - Janet
parsevsparser/new: useparser/new+parser/consume+parser/eof+parser/producefor full source parsing (break val)breaks from a while loop returning val — useful in bucket search patterns