From 206fe94a95701748e8a422e2e3ca3dfa3fce4e11 Mon Sep 17 00:00:00 2001 From: Yogthos Date: Fri, 19 Jun 2026 18:01:22 -0400 Subject: [PATCH] Chez Phase 3 inc 2: quote + collection literals in jolt.backend-scheme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds :vector/:map/:set (emit-ordered, left-to-right element eval) and :quote to the portable Clojure emitter. Collection-literal nodes carry already-analyzed IR items so they just recurse; quote walks the RAW reader form. emit-quoted walks the reader form via the jolt.host form-* contract (form-list?/ form-elements/form-vec-items/form-map-pairs/form-set-items/form-sym-*), the same portable seam the analyzer uses — not host-native predicates, so it works unchanged whether the form came from the Janet reader or the Chez reader. Reader forms are raw host representations (Janet list=array, vec=tuple), so native list?/vector? would not see them; the contract is the correct abstraction and keeps the emitter host-neutral. Quoted-symbol metadata and def-meta still defer to inc 3. Surfaced a latent Janet-host bug (jolt-tg9s): (quote #{...}) evaluates to the raw reader form instead of a reconstructed set, so (contains? (quote #{:p :q}) :p) is false on build/jolt. The Chez emitter is correct (real Clojure: true); the parity test asserts the verified value for those two cases. Gate: emit-parity 42/42 (incl vector/map/set literals, coll/kw-as-fn, quoted list/vec/map/set/symbol/nested). emit-test 331/331, conformance 355x3. jolt-7jvp. --- jolt-core/jolt/backend_scheme.clj | 65 ++++++++++++++++++++++++++++--- test/chez/emit-parity.janet | 50 ++++++++++++++++++++++-- 2 files changed, 106 insertions(+), 9 deletions(-) diff --git a/jolt-core/jolt/backend_scheme.clj b/jolt-core/jolt/backend_scheme.clj index 2ea1083..906b109 100644 --- a/jolt-core/jolt/backend_scheme.clj +++ b/jolt-core/jolt/backend_scheme.clj @@ -12,11 +12,17 @@ Janet emitter while the port is in flight. INCREMENT 1 (jolt-hg7z): const/local/var/the-var/if/do/let/loop/recur/invoke - (+ native-ops)/fn/def + the escaping/flonum/munge helpers — enough to compile - fib/mandelbrot-shaped code end to end. Quote, collection literals, try/throw, - host interop, regex/inst/uuid and program assembly land in later increments - (they throw `:not-yet-ported` here so an accidental hit is loud)." - (:require [clojure.string :as str])) + (+ native-ops)/fn/def + the escaping/flonum/munge helpers. + INCREMENT 2 (jolt-7jvp): collection literals (vector/map/set, emit-ordered) + + quote (emit-quoted, walks the raw reader form via the portable jolt.host form-* + contract — same seam the analyzer uses, so it stays host-neutral). Quoted symbol + metadata, def-meta, try/throw, host interop, regex/inst/uuid and program + assembly land in later increments (they throw not-yet-ported so a hit is loud)." + (:require [clojure.string :as str] + [jolt.host :refer [form-sym? form-sym-name form-sym-ns form-sym-meta + form-list? form-vec? form-map? form-set? form-char? + form-literal? form-elements form-vec-items + form-map-pairs form-set-items]])) ;; Hot clojure.core primitives lowered to native Scheme, mirroring the Janet ;; backend's native-ops. `=` is the exactness-aware jolt= from values.ss; inc/dec/ @@ -139,6 +145,46 @@ (str "(integer->char " (:ch v) ")") :else (throw (ex-info (str "emit-const: unsupported literal " (pr-str v)) {})))) +;; Emit a call `(ctor a0 a1 ...)` with the args evaluated LEFT-TO-RIGHT. Chez's +;; procedure-argument evaluation order is unspecified (in practice right-to-left), +;; but Clojure evaluates collection-literal elements left to right, so a literal +;; like [(read r) (read r)] over side-effecting reads must bind in source order. +;; Bind each arg to a fresh temp in a let* then construct. Only wraps at >= 2 args. +(defn- emit-ordered [ctor arg-strs] + (if (< (count arg-strs) 2) + (str "(" ctor (if (empty? arg-strs) "" (str " " (str/join " " arg-strs))) ")") + (let [tmps (map (fn [_] (fresh-label "_o$")) arg-strs) + binds (str/join " " (map (fn [t a] (str "(" t " " a ")")) tmps arg-strs))] + (str "(let* (" binds ") (" ctor " " (str/join " " tmps) "))")))) + +;; Quoted literals (jolt-u8j7). A :quote node's :form is the RAW reader form; +;; reconstruct each as the matching Chez RT constructor — the runtime value of a +;; quote is just that literal data. The form is walked via the jolt.host form-* +;; contract (the portable seam the analyzer uses), NOT host-native predicates, so +;; this stays host-neutral: on Janet the contract walks Janet reader forms, on +;; Chez it walks Chez reader forms. +(declare emit-quoted) +(defn- emit-quoted-map [pairs] + ;; pairs: a jolt vector of [k-form v-form] pairs (form-map-pairs) + (str "(jolt-hash-map " + (str/join " " (mapcat (fn [p] [(emit-quoted (nth p 0)) (emit-quoted (nth p 1))]) pairs)) + ")")) +(defn- emit-quoted [form] + (cond + (form-char? form) (emit-const form) + (form-literal? form) (emit-const form) + (form-sym? form) + (let [m (form-sym-meta form)] + (when (and m (pos? (count m))) + (throw (ex-info "emit-quoted: quoted symbol with metadata not yet ported (inc 3)" {}))) + (let [sns (form-sym-ns form) nm (form-sym-name form)] + (str "(jolt-symbol " (if sns (chez-str-lit sns) "#f") " " (chez-str-lit nm) ")"))) + (form-set? form) (str "(jolt-hash-set " (str/join " " (map emit-quoted (form-set-items form))) ")") + (form-list? form) (str "(jolt-list " (str/join " " (map emit-quoted (form-elements form))) ")") + (form-vec? form) (str "(jolt-vector " (str/join " " (map emit-quoted (form-vec-items form))) ")") + (form-map? form) (emit-quoted-map (form-map-pairs form)) + :else (throw (ex-info (str "emit-quoted: unsupported quoted form " (pr-str form)) {})))) + ;; A def's :meta is a jolt map value. Non-empty? (a plain def carries {}). (defn- jmeta-nonempty? [m] (and (map? m) (pos? (count m)))) @@ -305,6 +351,13 @@ :do (str "(begin " (str/join " " (map emit (:statements node))) (if (empty? (:statements node)) "" " ") (emit (:ret node)) ")") :invoke (emit-invoke node) + ;; collection literals -> rt constructors (collections.ss). Elements are + ;; already-analyzed IR nodes; evaluate LEFT-TO-RIGHT (emit-ordered). + :vector (emit-ordered "jolt-vector" (map emit (:items node))) + :set (emit-ordered "jolt-hash-set" (map emit (:items node))) + :map (emit-ordered "jolt-hash-map" + (mapcat (fn [p] [(emit (nth p 0)) (emit (nth p 1))]) (:pairs node))) + :quote (emit-quoted (:form node)) :let (emit-let node) :loop (emit-loop node) :recur (emit-recur node) @@ -315,7 +368,7 @@ (:no-init node) (str "(declare-var! " (chez-str-lit (:ns node)) " " (chez-str-lit (:name node)) ")") (jmeta-nonempty? (:meta node)) - (throw (ex-info "emit: def with non-empty meta not yet ported (inc 2)" {})) + (throw (ex-info "emit: def with non-empty meta not yet ported (inc 3)" {})) :else (str "(def-var! " (chez-str-lit (:ns node)) " " (chez-str-lit (:name node)) " " (emit (:init node)) ")")) diff --git a/test/chez/emit-parity.janet b/test/chez/emit-parity.janet index ed037d8..67d3137 100644 --- a/test/chez/emit-parity.janet +++ b/test/chez/emit-parity.janet @@ -88,11 +88,26 @@ [code (string/trim out) (string/trim err)]) # A case passes when the Clojure emitter's Chez output equals the Janet oracle. +# An emit-time throw (out-of-subset op) is a clean FAIL, not a crash. (defn check [name src] (def want (cli-oracle src)) - (def [code out err] (run-clj src)) - (ok name (and (= code 0) (= out want)) - (string "chez=" out " oracle=" want " code=" code " | " err))) + (def res (protect (run-clj src))) + (if (not (res 0)) + (ok name false (string "emit/run threw: " (res 1))) + (let [[code out err] (res 1)] + (ok name (and (= code 0) (= out want)) + (string "chez=" out " oracle=" want " code=" code " | " err))))) + +# For cases where the Janet oracle carries a known latent bug (e.g. quoted-set +# literals don't reconstruct, jolt-tg9s) but Chez is correct: assert Chez output +# against the hand-verified real-Clojure value directly. +(defn check-exact [name src want] + (def res (protect (run-clj src))) + (if (not (res 0)) + (ok name false (string "emit/run threw: " (res 1))) + (let [[code out err] (res 1)] + (ok name (and (= code 0) (= out want)) + (string "chez=" out " want=" want " code=" code " | " err))))) # --- inc 1 subset: const/local/var/if/do/let/loop/recur/invoke/fn/def ---------- @@ -113,6 +128,35 @@ (check "truthy local" "(defn t [x] (if x 1 2)) (t false)") (check "mod rem quot" "(+ (mod 17 5) (rem 17 5) (quot 17 5))") (check "min max" "(+ (min 3 1 2) (max 3 1 2))") +# --- inc 2 subset: collection literals (vector/map/set) + quote ----------------- + +(check "vector literal" "[1 2 3]") +(check "nested vector" "[1 [2 3] [4 [5 6]]]") +(check "vector of exprs" "[(+ 1 2) (* 3 4)]") +(check "map literal eq" "(= {:a 1 :b 2} {:a 1 :b 2})") +(check "map get" "(get {:a 1 :b 2} :b)") +(check "set literal eq" "(= #{1 2 3} #{3 2 1})") +(check "set contains" "(contains? #{1 2 3} 2)") +(check "vector count" "(count [10 20 30 40])") +(check "conj vector" "(conj [1 2] 3 4)") +(check "assoc map" "(get (assoc {:a 1} :b 2) :b)") +(check "coll as fn" "({:a 1 :b 2} :a)") +(check "kw as fn" "(:b {:a 1 :b 2})") +(check "kw as fn default" "(:z {:a 1} 99)") +(check "quote list" "(count (quote (a b c d)))") +(check "quote vector eq" "(= (quote [1 2 3]) [1 2 3])") +(check "quote symbol eq" "(= (quote foo) (quote foo))") +(check "quote ns symbol eq" "(= (quote my.ns/bar) (quote my.ns/bar))") +(check "quote nested count" "(count (quote (a [1 2] {:k v})))") +(check "quote keyword in list" "(first (quote (:a :b :c)))") +(check "quote map" "(get (quote {:x 1 :y 2}) :y)") +# Janet oracle is buggy here (jolt-tg9s): quoted-set doesn't reconstruct. Chez is +# correct (real Clojure: true) — assert against the verified value. +(check-exact "quote set contains" "(contains? (quote #{:p :q}) :p)" "true") +(check-exact "quote set eq literal" "(= (quote #{1 2 3}) #{1 2 3})" "true") +(check "vector map filter" "(into [] (filter even? (map inc [1 2 3 4 5])))") +(check "map over literal" "(reduce + (vals {:a 1 :b 2 :c 3}))") + (check "mandelbrot run(20)" (string `` (defn count-point [cr ci cap]