| author | Alan Dipert
<alan@dipert.org> 2026-11-07 05:34:20 UTC |
| committer | Alan Dipert
<alan@dipert.org> 2026-11-07 05:34:20 UTC |
| parent | 31924cf3d63e8aa766681aa372f3808e451f87a7 |
| doc/compiler.md | +6 | -0 |
| doc/nanopass.md | +28 | -2 |
| src/com/tailrecursion/blacktide/compiler.clj | +27 | -25 |
| src/com/tailrecursion/blacktide/compiler/languages.clj | +1 | -0 |
| src/com/tailrecursion/blacktide/compiler/passes/assignment.clj | +11 | -0 |
| src/com/tailrecursion/blacktide/compiler/passes/boundaries.clj | +6 | -0 |
| src/com/tailrecursion/blacktide/compiler/passes/cases.clj | +6 | -0 |
| src/com/tailrecursion/blacktide/compiler/passes/collections.clj | +6 | -0 |
| src/com/tailrecursion/blacktide/compiler/passes/lower.clj | +5 | -0 |
| src/com/tailrecursion/blacktide/compiler/passes/order.clj | +5 | -0 |
| src/com/tailrecursion/blacktide/compiler/passes/prepare.clj | +6 | -0 |
| src/com/tailrecursion/blacktide/compiler/passes/source_values.clj | +6 | -0 |
| src/com/tailrecursion/nanopass/pipeline.clj | +11 | -0 |
| test/com/tailrecursion/blacktide/annotations.clj | +2 | -0 |
| test/com/tailrecursion/blacktide/check.clj | +16 | -1 |
| test/com/tailrecursion/nanopass/annotations.clj | +2 | -0 |
| test/com/tailrecursion/nanopass/check.clj | +22 | -0 |
| test/com/tailrecursion/nanopass/pass_test.clj | +17 | -0 |
diff --git a/doc/compiler.md b/doc/compiler.md index 5cb50a5..beec5b4 100644 --- a/doc/compiler.md +++ b/doc/compiler.md @@ -28,6 +28,10 @@ Bound → Classified → Sequential → Assigned → Cases → Collections → Prepared → Ordered → Symbolic → CFG → Machine ``` +The linear driver uses ordinary `->` composition of unary stages. Each tree language has a `Compilation` nonterminal: a `[supply tree]` pair containing the immutable fresh-name supply and that language's `Expr`. Stage entry points live beside their recursive processors. They use `pipeline/map-value` to preserve the supply or `apply` to pass both components to a processor that allocates names. Graph construction consumes the pair and returns a graph. + +Typed Clojure checks each stage's input and output, including the tree inside `Compilation`. Incompatible language transitions are type errors; semantic ordering constraints within the same language still require tests. Recursive processors keep their own precise arguments and results. + The outer bindings enter as one lexical scope. `Native` contains analyzed subtrees without DSL operations. Both backends share graph validation: ```text @@ -35,6 +39,8 @@ Dispatcher: Machine → Code Structured: Machine → Analysis → Planned → Joined → Stored → Compact → Code ``` +Backend selection keeps its branching and shared analyses explicit. Compaction returns each simplified subtree with its free-read counts, avoiding repeated analysis of unchanged bodies. Emitters complete their compiler work before returning generated forms; quoted source data remains opaque. + Language derivation describes how grammars relate. It does not set pass order: `Symbolic` extends `CFG`, which extends `Machine`, while lowering visits `Symbolic`, then `CFG`, then `Machine`. ## Runtime boundaries diff --git a/doc/nanopass.md b/doc/nanopass.md index 8ad08c4..0c8a217 100644 --- a/doc/nanopass.md +++ b/doc/nanopass.md @@ -11,7 +11,8 @@ A nonterminal is a named grammar type, such as `Expr`. When a language is derive '[typed.clojure :as-alias t]) (np/deflanguage Source - {Expr (t/U (t/HMap :mandatory {:op ':literal :value t/Any} :complete? true) + {Compilation (t/HVec [t/Int Expr]) + Expr (t/U (t/HMap :mandatory {:op ':literal :value t/Any} :complete? true) (t/HMap :mandatory {:op ':negate :value Expr} :complete? true))}) (np/derive-language Core Source @@ -66,6 +67,31 @@ Write explicit clauses when lexical context changes. Passes may return results o Use qualified aliases such as `languages/Core-Expr` in `:args` and explicit result types. Aliases used only for checking need not exist as runtime Vars. +## Compose passes + +Use ordinary `->` to compose unary stage entry points. A `Compilation` nonterminal packages auxiliary state with the tree; derivation rebinds its `Expr` to each language. Recursive processors need not share the stage interface. + +In this example, the state is an integer counter. `pipeline/map-value` adapts the existing `lower` processor, preserving the counter: + +```clojure +(require '[com.tailrecursion.nanopass.pipeline :as pipeline]) + +(np/defpass lower-stage + {:from [Source Compilation] :to [Core Compilation] :body true} + [compilation] + (pipeline/map-value lower compilation)) + +(-> [0 {:op :negate :value {:op :literal :value 7}}] + lower-stage + second) +;; => {:op :subtract :left {:op :literal :value 0} +;; :right {:op :literal :value 7}} +``` + +For a processor that consumes or updates state, the entry point can use `(apply processor compilation)`. A stage can also leave the pair representation, for example by returning a control-flow graph. Keep branching and shared analysis results explicit with ordinary `let` and `if`. + +The helper preserves distinct types for state, input and output. Register its signature with `(np/ann-passes pipeline/map-value)` when checking a client. + ## Traversal rules Structural traversal: @@ -77,7 +103,7 @@ Structural traversal: ## Check languages and pass bodies 1. In an annotation-only namespace, require `typed.clojure` and call `(np/ann-languages Source Core)`. This generates aliases `Source-Expr` and `Core-Expr` in the languages' defining namespace. -2. Register `(np/ann-passes lower)` in the same checking-only namespace. +2. Register `(np/ann-passes lower lower-stage pipeline/map-value)` in the same checking-only namespace. 3. Check the implementation namespace with `typed.clojure/check-ns-clj`. Ordinary language loading does not register types. Generated signatures state the contracts; function bodies must also be checked. Blacktide's check runner discovers its compiler languages and passes and registers them automatically. diff --git a/src/com/tailrecursion/blacktide/compiler.clj b/src/com/tailrecursion/blacktide/compiler.clj index c4e3f66..62d8a1e 100644 --- a/src/com/tailrecursion/blacktide/compiler.clj +++ b/src/com/tailrecursion/blacktide/compiler.clj @@ -31,33 +31,36 @@ (np/defpass compile-program {:from [l/Machine Program] :to [l/Machine Code] :body true} [machine] - (let [cfg (feasible/program {:entry (:entry machine) :blocks (:blocks machine)}) - reduced (graph/analyze cfg) - analysis (value-facts/analyze reduced) - planned (joins/plan analysis) - decision (structure/recover planned)] + (let [analysis (-> {:entry (:entry machine) :blocks (:blocks machine)} + feasible/program + graph/analyze + value-facts/analyze) + decision (-> analysis joins/plan structure/recover)] (if (= :structured (:op decision)) - (let [joined (invariants/expression #{} (:body decision)) - stored (storage/select-storage analysis joined) - compact (compact/program stored)] + (let [compact (->> (:body decision) + (invariants/expression #{}) + (storage/select-storage analysis) + compact/program)] (structured/expression (:types compact) (:body compact))) (emit/program-form machine)))) (np/defpass compile-tree - {:from [l/Bound Expr] :to [l/Machine Code] :args [l/Bound-Supply] :body true} - [supply expr] - (let [classified (boundaries/classify expr) - values (source-values/expression classified) - sequential (assignment/sequential values) - [supply assigned] (assignment/parallel supply sequential) - selected (cases/normalize assigned) - constructed (collections/normalize selected) - prepared (prepare/prepare constructed) - [supply ordered] (order/normalize supply prepared) - symbolic (lower/compile-graph supply ordered) - cfg (transfers/resolve-program symbolic) - machine (graph/analyze cfg)] - (host/box-form (compile-program machine)))) + {:from [l/Bound Compilation] :to [l/Machine Code] :body true} + [compilation] + (-> compilation + boundaries/stage + source-values/stage + assignment/sequential-stage + assignment/parallel-stage + cases/stage + collections/stage + prepare/stage + order/stage + lower/stage + transfers/resolve-program + graph/analyze + compile-program + host/box-form)) (np/defpass caller-locals {:from [l/Bound Context] :to [l/Bound Context] :body true} @@ -74,6 +77,5 @@ (let [_ (assert (seq? form) "Expected a loophole macro form") site (errors/site {:form form :file *file* :line nil :column nil} form) ctx (caller-locals {:locals {} :macro-env env :markers markers :outer? true :transfer? true - :recur nil :site site :statement? true :tail? false}) - [supply expr] (source/parse-scope {:prefix (str (gensym "loophole__")) :next 0} ctx true (vec form))] - (compile-tree supply expr))) + :recur nil :site site :statement? true :tail? false})] + (compile-tree (source/parse-scope {:prefix (str (gensym "loophole__")) :next 0} ctx true (vec form))))) diff --git a/src/com/tailrecursion/blacktide/compiler/languages.clj b/src/com/tailrecursion/blacktide/compiler/languages.clj index 75fe9fb..e8eb822 100644 --- a/src/com/tailrecursion/blacktide/compiler/languages.clj +++ b/src/com/tailrecursion/blacktide/compiler/languages.clj @@ -84,6 +84,7 @@ (t/HMap :mandatory {:op ':loop :id t/Sym :bindings (t/Vec Binding) :body Expr} :complete? true) (t/HMap :mandatory {:op ':recur :target RecurTarget :args (t/Vec Expr)} :complete? true)) Supply (t/HMap :mandatory {:prefix t/Str :next t/Int} :complete? true) + Compilation (t/HVec [Supply Expr]) Context (t/HMap :mandatory {:locals (t/Map t/Sym Local) :macro-env (t/Map t/Sym Raw) :markers (t/Map t/AnyVar t/Kw) :outer? t/Bool :transfer? t/Bool :site Site diff --git a/src/com/tailrecursion/blacktide/compiler/passes/assignment.clj b/src/com/tailrecursion/blacktide/compiler/passes/assignment.clj index 118510f..e53aa06 100644 --- a/src/com/tailrecursion/blacktide/compiler/passes/assignment.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/assignment.clj @@ -2,6 +2,7 @@ "Lower sequential batches to writes, then parallel batches to saved RHSs and writes. Pending parallel writes do not execute if an RHS abandons evaluation." (:require [com.tailrecursion.nanopass :as np] + [com.tailrecursion.nanopass.pipeline :as pipeline] [com.tailrecursion.blacktide.compiler.languages :as l] [com.tailrecursion.blacktide.compiler.passes.source :as source] [typed.clojure :as-alias t])) @@ -121,3 +122,13 @@ :args [l/Bound-Supply] :body true} [supply expr] (let [[supply node] (parallel-node supply (:node expr))] [supply (assoc expr :node node)]))) + +(np/defpass sequential-stage + {:from [l/Classified Compilation] :to [l/Sequential Compilation] :body true} + [compilation] + (pipeline/map-value sequential compilation)) + +(np/defpass parallel-stage + {:from [l/Sequential Compilation] :to [l/Assigned Compilation] :body true} + [compilation] + (apply parallel compilation)) diff --git a/src/com/tailrecursion/blacktide/compiler/passes/boundaries.clj b/src/com/tailrecursion/blacktide/compiler/passes/boundaries.clj index ef7596d..cfc82f1 100644 --- a/src/com/tailrecursion/blacktide/compiler/passes/boundaries.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/boundaries.clj @@ -3,6 +3,7 @@ exclude DSL effects; lowered loops materialize their source representations." (:require [com.tailrecursion.blacktide.compiler.passes.effects :refer [effects]] [com.tailrecursion.nanopass :as np] + [com.tailrecursion.nanopass.pipeline :as pipeline] [com.tailrecursion.blacktide.compiler.languages :as l] [com.tailrecursion.blacktide.compiler.passes.loop-facts :as loops] [typed.clojure :as-alias t])) @@ -74,3 +75,8 @@ (assoc expr :node {:op :native :value (native expr)}) (= :loop (:op node)) (let [expr (loops/lower expr)] (assoc expr :node (classify-node (:node expr)))) :else (assoc expr :node (classify-node node)))))) + +(np/defpass stage + {:from [l/Bound Compilation] :to [l/Classified Compilation] :body true} + [compilation] + (pipeline/map-value classify compilation)) diff --git a/src/com/tailrecursion/blacktide/compiler/passes/cases.clj b/src/com/tailrecursion/blacktide/compiler/passes/cases.clj index 2718523..d2150b5 100644 --- a/src/com/tailrecursion/blacktide/compiler/passes/cases.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/cases.clj @@ -2,6 +2,7 @@ "Separate source case from ordinal arm selection after native constant dispatch. Each source arm occurs once, including grouped and hash-collision alternatives." (:require [com.tailrecursion.nanopass :as np] + [com.tailrecursion.nanopass.pipeline :as pipeline] [com.tailrecursion.blacktide.compiler.languages :as l])) (np/defpasses @@ -23,3 +24,8 @@ (normalize {:from [l/Assigned Expr] :to [l/Cases Expr] :body true} [expr] (assoc expr :node (normalize-node (:node expr))))) + +(np/defpass stage + {:from [l/Assigned Compilation] :to [l/Cases Compilation] :body true} + [compilation] + (pipeline/map-value normalize compilation)) diff --git a/src/com/tailrecursion/blacktide/compiler/passes/collections.clj b/src/com/tailrecursion/blacktide/compiler/passes/collections.clj index 04fbc12..d11a478 100644 --- a/src/com/tailrecursion/blacktide/compiler/passes/collections.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/collections.clj @@ -2,6 +2,7 @@ "Preserve native constant collections and construction checks. Metadata keeps construction dynamic and is evaluated only after that construction succeeds." (:require [com.tailrecursion.nanopass :as np] + [com.tailrecursion.nanopass.pipeline :as pipeline] [com.tailrecursion.blacktide.compiler.languages :as l] [com.tailrecursion.blacktide.compiler.passes.host-emit :as host] [typed.clojure :as-alias t])) @@ -67,3 +68,8 @@ {:from [l/Cases Expr] :to [l/Collections Expr] :body true} [expr] (assoc expr :node (normalize-node (:node expr))))) + +(np/defpass stage + {:from [l/Cases Compilation] :to [l/Collections Compilation] :body true} + [compilation] + (pipeline/map-value normalize compilation)) diff --git a/src/com/tailrecursion/blacktide/compiler/passes/lower.clj b/src/com/tailrecursion/blacktide/compiler/passes/lower.clj index b170b65..4e4efc4 100644 --- a/src/com/tailrecursion/blacktide/compiler/passes/lower.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/lower.clj @@ -137,3 +137,8 @@ [supply expr] (let [[builder value] (lower {:supply supply :ops [] :next 1 :current 0 :blocks {} :labels {} :loops {}} expr)] {:entry 0 :labels (:labels builder) :blocks (:blocks (seal builder {:op :return :value value}))})) + +(np/defpass stage + {:from [l/Ordered Compilation] :to [l/Symbolic Program] :body true} + [compilation] + (apply compile-graph compilation)) diff --git a/src/com/tailrecursion/blacktide/compiler/passes/order.clj b/src/com/tailrecursion/blacktide/compiler/passes/order.clj index 1d9d83c..0ffe501 100644 --- a/src/com/tailrecursion/blacktide/compiler/passes/order.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/order.clj @@ -113,3 +113,8 @@ :args [l/Bound-Supply] :body true} [supply expr] (normalize-node supply expr (:node expr)))) + +(np/defpass stage + {:from [l/Prepared Compilation] :to [l/Ordered Compilation] :body true} + [compilation] + (apply normalize compilation)) diff --git a/src/com/tailrecursion/blacktide/compiler/passes/prepare.clj b/src/com/tailrecursion/blacktide/compiler/passes/prepare.clj index b524474..c7acd77 100644 --- a/src/com/tailrecursion/blacktide/compiler/passes/prepare.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/prepare.clj @@ -2,6 +2,7 @@ "Make function casts and resolved JVM argument conversions explicit before ordering operands. A failing conversion must prevent subsequent evaluation." (:require [com.tailrecursion.nanopass :as np] + [com.tailrecursion.nanopass.pipeline :as pipeline] [com.tailrecursion.blacktide.compiler.languages :as l] [com.tailrecursion.blacktide.compiler.jvm :as jvm] [typed.clojure :as-alias t])) @@ -42,3 +43,8 @@ {:from [l/Collections Expr] :to [l/Prepared Expr] :body true} [expr] (assoc expr :node (prepare-node (:node expr))))) + +(np/defpass stage + {:from [l/Collections Compilation] :to [l/Prepared Compilation] :body true} + [compilation] + (pipeline/map-value prepare compilation)) diff --git a/src/com/tailrecursion/blacktide/compiler/passes/source_values.clj b/src/com/tailrecursion/blacktide/compiler/passes/source_values.clj index 2705187..77fa5ed 100644 --- a/src/com/tailrecursion/blacktide/compiler/passes/source_values.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/source_values.clj @@ -4,6 +4,7 @@ slot must not turn those reads into aliases of one box. Native trees already preserve this behavior, and mutable locals have reference semantics." (:require [com.tailrecursion.nanopass :as np] + [com.tailrecursion.nanopass.pipeline :as pipeline] [com.tailrecursion.blacktide.compiler.languages :as l] [typed.clojure :as-alias t])) @@ -36,3 +37,8 @@ (assoc expr :node {:op :host :operation {:op :static :class clojure.lang.RT :member (symbol (str (.getName ^Class type) "Cast")) :tags [type]} :args [expr]}) expr)))) + +(np/defpass stage + {:from [l/Classified Compilation] :to [l/Classified Compilation] :body true} + [compilation] + (pipeline/map-value expression compilation)) diff --git a/src/com/tailrecursion/nanopass/pipeline.clj b/src/com/tailrecursion/nanopass/pipeline.clj new file mode 100644 index 0000000..be92406 --- /dev/null +++ b/src/com/tailrecursion/nanopass/pipeline.clj @@ -0,0 +1,11 @@ +(ns ^:typed.clojure com.tailrecursion.nanopass.pipeline + "Adapt a value transformation to a [state value] compilation pair. + Typed unary stages compose with ordinary ->.") + +(defn map-value + "Transform the value, preserving state. Each stage retains its own value type." + {:com.tailrecursion.nanopass/signature + '(typed.clojure/All [s a b] + [[a :-> b] (typed.clojure/HVec [s a]) :-> (typed.clojure/HVec [s b])])} + [pass [state value]] + [state (pass value)]) diff --git a/test/com/tailrecursion/blacktide/annotations.clj b/test/com/tailrecursion/blacktide/annotations.clj index 9810341..abed95e 100644 --- a/test/com/tailrecursion/blacktide/annotations.clj +++ b/test/com/tailrecursion/blacktide/annotations.clj @@ -4,6 +4,7 @@ (:require [clojure.java.io :as io] [typed.clojure] [clojure.core.typed :as ct] + [com.tailrecursion.nanopass.pipeline :as pipeline] [com.tailrecursion.nanopass :as np])) (def compiler-namespaces @@ -25,6 +26,7 @@ (np/ann-passes ~@(names ::np/signature))))) (annotate-compiler) +(np/ann-passes pipeline/map-value) ;; Var.toSymbol delegates to Symbol.create; its successful result is non-null. (ct/non-nil-return clojure.lang.Var/toSymbol :all) diff --git a/test/com/tailrecursion/blacktide/check.clj b/test/com/tailrecursion/blacktide/check.clj index 7779b34..a850174 100644 --- a/test/com/tailrecursion/blacktide/check.clj +++ b/test/com/tailrecursion/blacktide/check.clj @@ -3,13 +3,28 @@ (:require [clojure.test :refer [deftest is]] [typed.clojure :as t] [com.tailrecursion.nanopass.check :as probes] - [com.tailrecursion.blacktide.annotations :as annotations])) + [com.tailrecursion.blacktide.annotations :as annotations] + [com.tailrecursion.blacktide.compiler.languages :as-alias l] + [com.tailrecursion.blacktide.compiler.passes.boundaries :as boundaries] + [com.tailrecursion.blacktide.compiler.passes.assignment :as assignment])) (deftest compiler-passes (doseq [namespace annotations/compiler-namespaces :when (not= namespace 'com.tailrecursion.blacktide.compiler.languages)] (is (t/check-ns-clj namespace) (str namespace)))) +(deftest compilation-stage-contracts + (doseq [[accepted stages] + [[true `[boundaries/stage assignment/sequential-stage assignment/parallel-stage]] + [false `[boundaries/stage assignment/parallel-stage assignment/sequential-stage]] + [false `[boundaries/stage assignment/sequential-stage]]]] + (let [form `(fn [compilation#] (-> compilation# ~@stages)) + {:keys [ex delayed-errors]} + (probes/check-form form `[l/Bound-Compilation :-> l/Assigned-Compilation]) + errors (concat delayed-errors (:errors (ex-data ex)))] + (is (or (nil? ex) (= :top-level-error (:type-error (ex-data ex)))) (str ex)) + (is (= accepted (empty? errors)) (pr-str [form errors]))))) + (deftest assignment-stage-exclusions (let [expr (fn [node] {:site {:form nil :file nil :line nil :column nil} :jvm {:hint nil :var-meta {} :type :unknown :primitive? false :hint-error nil} diff --git a/test/com/tailrecursion/nanopass/annotations.clj b/test/com/tailrecursion/nanopass/annotations.clj index b7aa96e..1c5a9dd 100644 --- a/test/com/tailrecursion/nanopass/annotations.clj +++ b/test/com/tailrecursion/nanopass/annotations.clj @@ -2,6 +2,7 @@ "Checking-only declarations, generated from the ordinary language values." (:require [typed.clojure] [com.tailrecursion.nanopass :as np] + [com.tailrecursion.nanopass.pipeline :as pipeline] [com.tailrecursion.nanopass.fixtures :as f] [com.tailrecursion.nanopass.passes :as p] [com.tailrecursion.nanopass.clients.assignment :as assignment] @@ -9,6 +10,7 @@ [com.tailrecursion.nanopass.clients.flow-languages :as flow-languages])) (np/ann-languages f/Source f/Core f/Literals f/Mutual f/Stopped) +(np/ann-passes pipeline/map-value) (np/ann-passes p/lower p/lower-binding p/rename p/rename-binding p/count-step p/lower-envelope) (np/ann-languages assignment/Source assignment/Core) (np/ann-passes assignment/lower assignment/sequential-writes assignment/parallel-writes) diff --git a/test/com/tailrecursion/nanopass/check.clj b/test/com/tailrecursion/nanopass/check.clj index 8c8149a..9bdfb0d 100644 --- a/test/com/tailrecursion/nanopass/check.clj +++ b/test/com/tailrecursion/nanopass/check.clj @@ -4,6 +4,7 @@ [clojure.core.typed :as checker] [typed.clojure :as t] [com.tailrecursion.nanopass :as np] + [com.tailrecursion.nanopass.pipeline :as pipeline] [com.tailrecursion.nanopass.annotations] [com.tailrecursion.nanopass.fixtures :as f] [com.tailrecursion.nanopass.passes :as p] @@ -55,10 +56,31 @@ (accepts? 'f/Stopped-A {:op :a :next {:op :b :next nil}} false)) (deftest checked-passes + (is (t/check-ns-clj 'com.tailrecursion.nanopass.pipeline)) (is (t/check-ns-clj 'com.tailrecursion.nanopass.passes)) (is (t/check-ns-clj 'com.tailrecursion.nanopass.clients.assignment)) (is (t/check-ns-clj 'com.tailrecursion.nanopass.clients.flow))) +(deftest pipeline-contracts + (doseq [[accepted form] + [[true '(fn [state tree] + (->> [state tree] + (pipeline/map-value p/lower) + (apply p/count-step)))] + [false '(fn [state tree] + (->> [state tree] + (pipeline/map-value p/lower) + (pipeline/map-value p/lower)))] + [false '(fn [state tree] + (->> [state tree] + (pipeline/map-value p/lower) + (apply (fn [state tree] [tree state]))))]]] + (let [{:keys [ex delayed-errors]} + (check-form form '[t/Int f/Source-Expr :-> (t/HVec [t/Int f/Core-Expr])]) + errors (concat delayed-errors (:errors (ex-data ex)))] + (is (or (nil? ex) (= :top-level-error (:type-error (ex-data ex)))) (str ex)) + (is (= accepted (empty? errors)) (pr-str [form errors]))))) + (defn generated-function [clause] (let [[_ _ _ args body] (binding [*ns* (the-ns 'com.tailrecursion.nanopass.passes)] diff --git a/test/com/tailrecursion/nanopass/pass_test.clj b/test/com/tailrecursion/nanopass/pass_test.clj index 2a835da..20f6e27 100644 --- a/test/com/tailrecursion/nanopass/pass_test.clj +++ b/test/com/tailrecursion/nanopass/pass_test.clj @@ -2,8 +2,25 @@ "Generated traversal must preserve structure, terminal data, and context." (:require [clojure.test :refer [deftest is]] [com.tailrecursion.nanopass :as np] + [com.tailrecursion.nanopass.pipeline :as pipeline] [com.tailrecursion.nanopass.passes :as p])) +(deftest pipeline-state-and-evaluation + (let [events (atom []) + step (fn [state value] + (swap! events conj [state value]) + [(inc state) (conj value state)])] + (is (= [2 [0 1]] + (->> [0 []] + (apply step) + (pipeline/map-value identity) + (apply step)))) + (is (= [[0 []] [1 [0]]] @events))) + (doseq [value [nil false]] + (is (= [:state value] (pipeline/map-value identity [:state value])))) + (is (thrown-with-msg? clojure.lang.ExceptionInfo #"pass failed" + (pipeline/map-value (fn [_] (throw (ex-info "pass failed" {}))) [nil nil])))) + (deftest recursive-traversal (let [literal {:op :literal :value '{:op :assign :name x}} input (with-meta {:op :many :items {:a [literal {:op :read :name 'x}]}