diff --git a/host/chez/emit.janet b/host/chez/emit.janet index 28cc70f..1d71517 100644 --- a/host/chez/emit.janet +++ b/host/chez/emit.janet @@ -138,6 +138,35 @@ (string "(integer->char " (get v :ch) ")") (errorf "emit-const: unsupported literal %p" v))) +# Quoted literals (jolt-u8j7). A :quote node's :form is the RAW reader form (a +# Janet value): scalars are Janet natives, a symbol is {:jolt/type :symbol …}, a +# list is an array, a vector a tuple, a map a struct/phm, a set a tagged struct. +# Reconstruct each as the matching Chez RT constructor — the runtime value of a +# quote is just that literal data (the interpreter returns the reader form +# verbatim; the Janet backend Janet-quotes it; here we rebuild it on the RT). +(var emit-quoted nil) +(defn- emit-quoted-map [m] + (def flat @[]) + (eachp [k v] m (array/push flat (emit-quoted k)) (array/push flat (emit-quoted v))) + (string "(jolt-hash-map " (string/join flat " ") ")")) +(set emit-quoted (fn emit-quoted [form] + (cond + # scalars emit-const already lowers (nil/bool/number/string/keyword/char) + (or (nil? form) (boolean? form) (number? form) (string? form) (keyword? form)) + (emit-const form) + (and (struct? form) (= :symbol (get form :jolt/type))) + (let [ns (get form :ns)] + (string "(jolt-symbol " (if ns (string/format "%j" ns) "#f") " " + (string/format "%j" (get form :name)) ")")) + (and (struct? form) (= :jolt/char (get form :jolt/type))) (emit-const form) + (and (struct? form) (= :jolt/set (get form :jolt/type))) + (string "(jolt-hash-set " (string/join (map emit-quoted (get form :value)) " ") ")") + (array? form) (string "(jolt-list " (string/join (map emit-quoted form) " ") ")") + (tuple? form) (string "(jolt-vector " (string/join (map emit-quoted form) " ") ")") + (phm/phm? form) (emit-quoted-map (phm/phm-to-struct form)) + (or (struct? form) (table? form)) (emit-quoted-map form) + (errorf "emit-quoted: unsupported quoted form %p" form)))) + (defn- emit-binding [b] (def b (vv b)) (string "(" (munge (get b 0)) " " (emit (get b 1)) ")")) @@ -331,6 +360,7 @@ :recur (emit-recur node) :throw (string "(jolt-throw " (emit (get node :expr)) ")") :try (emit-try node) + :quote (emit-quoted (get node :form)) :fn (emit-fn node) :def (string "(def-var! " (string/format "%j" (get node :ns)) " " (string/format "%j" (get node :name)) " " (emit (get node :init)) ")") diff --git a/host/chez/seq.ss b/host/chez/seq.ss index 781a1dd..557da91 100644 --- a/host/chez/seq.ss +++ b/host/chez/seq.ss @@ -107,11 +107,15 @@ ;; map / filter / reduce / into / remove + range / take / concat / apply ;; ============================================================================ (define (any-nil? seqs) (and (pair? seqs) (or (jolt-nil? (car seqs)) (any-nil? (cdr seqs))))) +;; An EMPTY seq result is () (jolt-empty-list), NOT nil — Clojure's (map f []) is +;; an empty seq, so (= () (map f [])) is true and (nil? (map f [])) is false. +;; jolt-empty-list seqs back to nil, so it stays a valid lazy-tail terminator for +;; the non-empty case (printing / seq= / reduce all walk via jolt-seq). (define (map-seq f s) - (if (jolt-nil? s) jolt-nil + (if (jolt-nil? s) jolt-empty-list (cseq-lazy (jolt-invoke f (seq-first s)) (lambda () (map-seq f (jolt-seq (seq-more s))))))) (define (map-seq* f seqs) ; multi-collection map; stops at the shortest - (if (any-nil? seqs) jolt-nil + (if (any-nil? seqs) jolt-empty-list (cseq-lazy (apply jolt-invoke f (map seq-first seqs)) (lambda () (map-seq* f (map (lambda (s) (jolt-seq (seq-more s))) seqs)))))) (define (jolt-map f . colls) @@ -121,7 +125,7 @@ (define (filter-seq pred s keep) (let loop ((s s)) - (cond ((jolt-nil? s) jolt-nil) + (cond ((jolt-nil? s) jolt-empty-list) ; empty result is () (see map-seq) ((eq? keep (jolt-truthy? (jolt-invoke pred (seq-first s)))) (cseq-lazy (seq-first s) (lambda () (filter-seq pred (jolt-seq (seq-more s)) keep)))) (else (loop (jolt-seq (seq-more s))))))) diff --git a/test/chez/README.md b/test/chez/README.md index 90535c9..f635012 100644 --- a/test/chez/README.md +++ b/test/chez/README.md @@ -48,12 +48,13 @@ compile-time signal) and are counted "out of subset", not as divergences. JOLT_CHEZ_CORPUS=1 janet test/chez/run-corpus-chez.janet -Baseline after inc 3e (throw/try + ex-info): **632/632 compiled cases pass**, 0 -divergences; 2023/2655 out of subset (await clojure.core on Chez). Inc 3c -(multi-arity + variadic fns) brought this to 619/619 — `emit-fn` lowers -multi-arity fns to a Scheme `case-lambda` and variadic fns to a rest-arg lambda -(the Scheme rest list is coerced to a jolt seq, nil when empty); inc 3e's -throw/try/ex-info support pulled 13 more corpus cases into the subset. +Baseline after inc 3f (quoted literals): **664/664 compiled cases pass**, 0 +divergences; 1994/2658 out of subset (await clojure.core on Chez). Inc 3e +(throw/try + ex-info) was 632/632; inc 3f's quote support + a seq.ss fix (empty +`map`/`filter` results are `()` not nil, matching Clojure) pulled 32 more corpus +cases into the subset. `emit-fn` lowers multi-arity fns to a Scheme `case-lambda` +and variadic fns to a rest-arg lambda (rest list coerced to a jolt seq, nil when +empty). ## Phase 1 — clojure.core prelude emission (inc 3d, jolt-ocvi) The `-e`-capable jolt-chez path: emit the clojure.core tiers @@ -70,15 +71,16 @@ catalogs the gaps; macros are skipped (analyze-time only, not a runtime value): JOLT_CHEZ_PRELUDE=1 janet test/chez/core-prelude-probe.janet -Baseline after inc 3e (throw/try + ex-info): **334/355 non-macro core forms emit** -to Scheme (was 303 at inc 3d). `:throw` lowers to `jolt-throw` (Scheme `raise` of -the raw jolt value, like the Janet compiled back end); `:try` lowers to `guard` -(catch, class dropped → catch-all) + `dynamic-wind` (finally); `ex-info` is a -native-op building the tagged jolt map, so the ex-data/ex-message/ex-cause tier -fns read it over `get` for free. Remaining 21 gaps: `:quote` (8, needs RT -symbols/lists), Java host interop `.write`/`.isDirectory` (6, io tier), `letfn` -(4), `declare`/def-no-init (2), one edge (`parse-uuid`). Each is a clean -next-increment target. The probe has a regression floor (raise it as gaps close). +Baseline after inc 3f (quoted literals): **342/355 non-macro core forms emit** to +Scheme (was 334 at inc 3e, 303 at inc 3d). A `:quote` node reconstructs the raw +reader form as RT constructor calls — symbol → `jolt-symbol`, list → `jolt-list`, +vector → `jolt-vector`, map/set → `jolt-hash-map`/`jolt-hash-set`, scalars via +`emit-const`. Prior: `:throw` → `jolt-throw` (Scheme `raise` of the raw jolt +value); `:try` → `guard` (catch, class dropped → catch-all) + `dynamic-wind` +(finally); `ex-info` native-op building the tagged jolt map, so the tier +ex-data/ex-message/ex-cause read it over `get` for free. Remaining 13 gaps: Java +host interop `.write`/`.isDirectory` (6, io tier), `letfn` (4), `declare`/ +def-no-init (2), one edge (`parse-uuid`). The probe has a regression floor. Prior, inc 3b (seq tier + dynamic IFn, jolt-5pso): 595/595 compiled, 0 divergences, 2060/2655 out of subset. The seq tier brought up a list/lazy-seq type with diff --git a/test/chez/core-prelude-probe.janet b/test/chez/core-prelude-probe.janet index d9e2376..6baa673 100644 --- a/test/chez/core-prelude-probe.janet +++ b/test/chez/core-prelude-probe.janet @@ -98,7 +98,7 @@ # Regression floor (raise it as new IR ops / RT shims land, like the suite # baseline). Fails if prelude emit reach drops below the recorded baseline. -(def reach-floor 334) +(def reach-floor 342) (when (< compiled reach-floor) (printf "REGRESSION: prelude emit reach %d < floor %d" compiled reach-floor) (os/exit 1)) diff --git a/test/chez/corpus.edn b/test/chez/corpus.edn index 10bda64..69a4f6a 100644 --- a/test/chez/corpus.edn +++ b/test/chez/corpus.edn @@ -152,6 +152,9 @@ {:suite "exceptions / try-catch" :label "finally runs on ok" :expected "2" :actual "(let [a (atom 0)] (try 2 (finally (reset! a 9))) )"} {:suite "exceptions / try-catch" :label "finally runs on throw" :expected "9" :actual "(let [a (atom 0)] (try (throw (ex-info \"x\" {})) (catch :default e nil) (finally (reset! a 9))) @a)"} {:suite "exceptions / try-catch" :label "catch value of body" :expected "5" :actual "(try (+ 2 3) (catch :default e 0))"} + {:suite "exceptions / try-catch" :label "malformed catch: non-symbol binding" :expected :throws :actual "(try 1 (catch e (* e 10)))"} + {:suite "exceptions / try-catch" :label "malformed catch: literal binding" :expected :throws :actual "(try 1 (catch e 5))"} + {:suite "exceptions / try-catch" :label "malformed catch: too short" :expected :throws :actual "(try 1 (catch Exception))"} {:suite "exceptions / assert" :label "assert true -> ok" :expected ":ok" :actual "(do (assert true) :ok)"} {:suite "exceptions / assert" :label "assert expr -> ok" :expected ":ok" :actual "(do (assert (= 1 1)) :ok)"} {:suite "exceptions / assert" :label "assert false throws" :expected :throws :actual "(assert false)"} diff --git a/test/chez/emit-test.janet b/test/chez/emit-test.janet index 565f2d5..31deb21 100644 --- a/test/chez/emit-test.janet +++ b/test/chez/emit-test.janet @@ -236,6 +236,29 @@ (ok "throw: uncaught exits non-zero" (not= code 0) (string "code=" code " out=" out))) +# 3j) quoted literals (inc 3f): a :quote node reconstructs the reader form as RT +# values — symbols, lists, vectors, maps, sets, nested. Value parity vs the CLI. +(each src ["'foo" + "'foo/bar" + "':kw" + "'(1 2 3)" + "'[1 2 3]" + "'(a b c)" + "'{:a 1}" + "'(1 (2 3) 4)" + "(first '(10 20 30))" + "(count '[1 2 3])" + "(rest '(1 2 3))" + "(= 'foo 'foo)" + "(= 'a 'b)" + "(map inc '(1 2 3))" + "(conj '[1 2] 3)" + "(get '{:a 7} :a)"] + (let [[code out err] (d/run-on-chez ctx src) + want (cli-oracle src)] + (ok (string "quote: " src) (and (= code 0) (= out want)) + (string "chez=" out " janet=" want " | " err)))) + # 3h) prelude mode (inc 3d): emitting clojure.core ITSELF, a core->core ref must # lower to a runtime var-deref instead of being rejected as "out of subset". # `frequencies` is a core fn but not a native-op, so it exercises the switch.