jolt/.dirge/memory/MEMORY.md
Yogthos fe22fea3e4 Phase 7: LazySeq + PersistentHashSet completion
- phm.janet: LazySeq (realize-once with caching, ls-first/ls-rest/ls-seq/ls-count)
  PersistentHashSet (backed by PHM, phs-conj/phs-disj/phs-contains?/phs-count/...)
  make-lazy-seq creates lazy thunk wrapper with :jolt/lazy-seq type tag
  make-phs creates hash set with :jolt/set type tag
- evaluator.janet: :jolt/set handler in eval-form, disj/set? special forms,
  special-symbol? entries, phm import for compile-time visibility
- core.janet: lazy-seq/iterate macros, set?/disj core fns, make-phs wired
  into hash-set, core-bindings entries for set?/disj/lazy-seq/make-lazy-seq/iterate
  Set support in core-conj/core-contains?/core-count/core-empty?/core-seq/core-get
  LazySeq support in core-first/core-rest/core-count/core-seq
- 9 tests (32-33): lazy-seq basic ops, realize-once caching
  PersistentHashSet construction, conj, disj, count, set? predicate
- 315 ok, 2 fail (pre-existing, unchanged)
2026-06-02 23:24:55 -04:00

2.1 KiB

Jolt Compiler Architecture (Phases 1-6, dfa9874→1de109f): Two-phase — analyze-form (Clojure form → annotated AST) → emit-ast (→ Janet source string) or emit-expr (→ Janet data structures for eval). analyze-form takes [form bindings &opt ctx]; ctx needed for macro expansion. Symbol classification: bindings first (:local), then core-renames (:core-symbol), then plain (:symbol). Two emitter paths: string (compile-form) and data structures (compile-ast). Core fn values resolved via core-fn-values table. compile-and-eval takes [form ctx]; pass nil for no macro ctx.

Key naming/facts:

  • Clojure - → core-sub (NOT core--)
  • core-nth did not exist — had to add both the function and core-bindings entry
  • Missing from core-renames early: fn?, list, name, subs
  • Bare tuples in Janet eval → treated as function calls. Always emit (tuple ...) or ['tuple ...]
  • make-symbol: / at position 0 means unqualified symbol (was parsing empty ns)
  • raw-form->janet converter for quote: don't re-analyze quoted forms, pass through verbatim
  • emit-try-expr: Janet format is (try body ([err] handler)) not (try body (catch sym handler))
  • Loop compilation: (do (var _loop_N nil) (set _loop_N (fn [params] body)) (_loop_N init-vals...))
  • Recur compilation: rewrites to (loop-name arg1 arg2...) via :loop-name in AST

eval-string dispatch: When :compile? true, stateful forms (defmacro, ns, deftype, defmulti, defmethod, require, in-ns) use interpreter. All others (def, macros like defn) go through compile-and-eval. Macros expanded at analyze time via resolve-macro.

Remaining: syntax-quote, set! compiler support. deftype/defmulti/defmethod routed to interpreter. § Janet gotchas: (1) parse returns [form, consumed-count], NOT [form, error?] — use parser/new→consume→eof→produce pipeline. (2) :#inst is invalid keyword literal — use (keyword "#inst") dynamically. (3) Janet case works for multi-arity simulation when defn ([] body) ([x] body) fails. (4) Bare tuples in eval are function calls — always use ['tuple ...]. (5) Core - maps to core-sub NOT core--.