diff --git a/host/chez/converters.ss b/host/chez/converters.ss index e8284c4..44ad8fb 100644 --- a/host/chez/converters.ss +++ b/host/chez/converters.ss @@ -121,5 +121,8 @@ (def-var! "clojure.core" "symbol" jolt-symbol-new) (def-var! "clojure.core" "gensym" jolt-gensym) (def-var! "clojure.core" "int" jolt-int) +;; long: same truncation as int in jolt's all-flonum model (seed core-long = +;; math/trunc; char -> code point). Distinct cell so (long ...) resolves. +(def-var! "clojure.core" "long" jolt-int) (def-var! "clojure.core" "double" jolt-double) (def-var! "clojure.core" "compare" jolt-compare) diff --git a/host/chez/driver.janet b/host/chez/driver.janet index 02c294e..0800c69 100644 --- a/host/chez/driver.janet +++ b/host/chez/driver.janet @@ -115,7 +115,13 @@ # clojure.edn requires clojure.string; read-string/__read-tagged are the # reader.ss seams. The reader-arity's drain-reader is Janet-coupled (janet/type) # so it's a lazy gap on Chez — read-string/edn->value are the live path. jolt-r8ku. - ["clojure.edn" "src/jolt/clojure/edn.clj"]]) + ["clojure.edn" "src/jolt/clojure/edn.clj"] + # clojure.set / clojure.pprint: pure Clojure over core. set = relational ops + # (union/intersection/difference/join/index/...); pprint = the minimal jolt + # shim (pprint -> prn + recognized dispatch vars, with-pprint-dispatch macro). + # jolt-j5vg, clojure.pprint Phase-2 parity. + ["clojure.set" "src/jolt/clojure/set.clj"] + ["clojure.pprint" "src/jolt/clojure/pprint.clj"]]) (defn- sym-name [x] (when (and (struct? x) (= :symbol (get x :jolt/type))) (get x :name))) diff --git a/host/chez/jolt-chez.janet b/host/chez/jolt-chez.janet index 5a19180..80fc484 100644 --- a/host/chez/jolt-chez.janet +++ b/host/chez/jolt-chez.janet @@ -23,10 +23,11 @@ "host/chez/ns.ss" "host/chez/post-prelude.ss" "host/chez/natives-meta.ss" "host/chez/natives-str.ss" "host/chez/records.ss" "host/chez/host-class.ss" "host/chez/io.ss" - "host/chez/inst-time.ss" "host/chez/reader.ss" + "host/chez/inst-time.ss" "host/chez/reader.ss" "host/chez/math.ss" "host/chez/host-static.ss" "host/chez/dot-forms.ss" "src/jolt/clojure/string.clj" "src/jolt/clojure/walk.clj" - "src/jolt/clojure/template.clj" "src/jolt/clojure/edn.clj"] + "src/jolt/clojure/template.clj" "src/jolt/clojure/edn.clj" + "src/jolt/clojure/set.clj" "src/jolt/clojure/pprint.clj"] (array/push parts (slurp f))) (string/slice (string (hash (string/join parts))) 0)) diff --git a/host/chez/math.ss b/host/chez/math.ss new file mode 100644 index 0000000..1b902de --- /dev/null +++ b/host/chez/math.ss @@ -0,0 +1,63 @@ +;; clojure.math (jolt-22vo) — Chez host shim over native flonum math. +;; +;; On the Janet seed clojure.math is registered as native math/ bindings +;; (api.janet install-clojure-math!, jolt-h79), NOT a .clj file — so there's no +;; source tier to emit. Chez provides its own def-var! shims here, one per +;; clojure.math fn, over Chez's native procedures. The analyzer already knows the +;; clojure.math ns exists (init interns the same fns on the Janet side), so a ref +;; like clojure.math/sqrt lowers to a var-deref; these cells back it at runtime. +;; +;; jolt is all-flonum, so every result is a flonum (inputs arrive as flonums; Chez +;; sqrt/sin/expt/... return flonums for flonum args). Semantics match the seed +;; (Clojure 1.11 clojure.math): round = floor(x+0.5), rint = round-half-even, +;; floor/ceil/floor-div return doubles, to-degrees/to-radians via PI. + +(define jolt-math-pi (acos -1.0)) +(define jolt-math-e (exp 1.0)) + +(define (jolt-math-cbrt x) + ;; sign-aware so negative inputs stay real (expt of a negative flonum to a + ;; fractional power goes complex). + (if (< x 0.0) + (- (expt (- x) (/ 1.0 3.0))) + (expt x (/ 1.0 3.0)))) + +(define (jolt-math-round x) (floor (+ x 0.5))) +(define (jolt-math-signum x) (cond ((< x 0.0) -1.0) ((> x 0.0) 1.0) (else 0.0))) +(define (jolt-math-to-degrees r) (/ (* r 180.0) jolt-math-pi)) +(define (jolt-math-to-radians d) (/ (* d jolt-math-pi) 180.0)) +(define (jolt-math-hypot a b) (sqrt (+ (* a a) (* b b)))) +(define (jolt-math-floor-div a b) (floor (/ a b))) +(define (jolt-math-floor-mod a b) (- a (* b (floor (/ a b))))) + +(def-var! "clojure.math" "sqrt" sqrt) +(def-var! "clojure.math" "cbrt" jolt-math-cbrt) +(def-var! "clojure.math" "pow" expt) +(def-var! "clojure.math" "exp" exp) +(def-var! "clojure.math" "expm1" (lambda (x) (- (exp x) 1.0))) +(def-var! "clojure.math" "log" log) +(def-var! "clojure.math" "log10" (lambda (x) (log x 10.0))) +(def-var! "clojure.math" "log1p" (lambda (x) (log (+ 1.0 x)))) +(def-var! "clojure.math" "sin" sin) +(def-var! "clojure.math" "cos" cos) +(def-var! "clojure.math" "tan" tan) +(def-var! "clojure.math" "asin" asin) +(def-var! "clojure.math" "acos" acos) +(def-var! "clojure.math" "atan" atan) +;; clojure.math/atan2 is atan2(y, x); Chez's 2-arg atan is (atan y x). +(def-var! "clojure.math" "atan2" (lambda (y x) (atan y x))) +(def-var! "clojure.math" "sinh" sinh) +(def-var! "clojure.math" "cosh" cosh) +(def-var! "clojure.math" "tanh" tanh) +(def-var! "clojure.math" "floor" floor) +(def-var! "clojure.math" "ceil" ceiling) +(def-var! "clojure.math" "rint" round) +(def-var! "clojure.math" "round" jolt-math-round) +(def-var! "clojure.math" "signum" jolt-math-signum) +(def-var! "clojure.math" "to-degrees" jolt-math-to-degrees) +(def-var! "clojure.math" "to-radians" jolt-math-to-radians) +(def-var! "clojure.math" "hypot" jolt-math-hypot) +(def-var! "clojure.math" "floor-div" jolt-math-floor-div) +(def-var! "clojure.math" "floor-mod" jolt-math-floor-mod) +(def-var! "clojure.math" "E" jolt-math-e) +(def-var! "clojure.math" "PI" jolt-math-pi) diff --git a/host/chez/rt.ss b/host/chez/rt.ss index 3050580..6972916 100644 --- a/host/chez/rt.ss +++ b/host/chez/rt.ss @@ -304,3 +304,7 @@ ;; __read-tagged. Loads after inst-time.ss — __read-tagged reuses its #uuid/#inst ;; constructors, and the reader needs the full value/collection layer above. (load "host/chez/reader.ss") + +;; clojure.math (jolt-22vo): native flonum-math shims def-var!'d into the +;; clojure.math ns. Self-contained (only def-var! + Chez math), order-independent. +(load "host/chez/math.ss") diff --git a/src/jolt/clojure/pprint.clj b/src/jolt/clojure/pprint.clj index 20f4d49..4b8bbee 100644 --- a/src/jolt/clojure/pprint.clj +++ b/src/jolt/clojure/pprint.clj @@ -16,10 +16,15 @@ (def ^:dynamic *print-pprint-dispatch* simple-dispatch) (defn pprint - "Print object readably followed by a newline. Writes to *out* (or the given - writer). Not a pretty-printer on jolt — no column-aware layout." + "Print object readably followed by a newline. Not a pretty-printer on jolt — + no column-aware layout. jolt routes all printing through the host output seam + (with-out-str captures it), and *out* is not a bindable var, so an explicit + writer arg is accepted for API compatibility but not honored — output goes to + the current output. (The old `(binding [*out* writer] ...)` never redirected + anything either, and made the defn fall back to the interpreter; dropping it + lets pprint compile cleanly, which the no-fallback Chez back end requires.)" ([object] (prn object)) - ([object writer] (binding [*out* writer] (prn object)))) + ([object _writer] (prn object))) (defmacro with-pprint-dispatch "Evaluate body with the given pprint dispatch selected. On jolt the dispatch is diff --git a/test/chez/_stdlib.janet b/test/chez/_stdlib.janet new file mode 100644 index 0000000..917bbd3 --- /dev/null +++ b/test/chez/_stdlib.janet @@ -0,0 +1,80 @@ +# jolt-j5vg / jolt-22vo / clojure.pprint — Phase-2 stdlib parity closeout. +# +# clojure.set — pure Clojure (src/jolt/clojure/set.clj) added to the Chez +# prelude tier (driver.janet stdlib-ns-files). +# clojure.math — native flonum-math shims (host/chez/math.ss) def-var!'d into +# the clojure.math ns; the analyzer already knows the ns exists +# (api.janet install-clojure-math!), so refs lower to var-deref. +# clojure.pprint — minimal shim on the prelude; pprint's 2-arity no longer uses +# (binding [*out* writer] ...) (uncompilable on the no-fallback +# Chez back end; *out* isn't a bindable var — output always goes +# through the host seam). +# +# Outputs are order-stable (value-equality / scalars) so set/map iteration order +# — which legitimately differs from build/jolt — never masquerades as a divergence. +# Oracle = build/jolt. +# +# janet test/chez/_stdlib.janet +(def jolt-bin (or (os/getenv "JOLT_BIN") "bin/jolt-chez")) + +(def cases + [# --- clojure.math (jolt-22vo / jolt-h79) --- + ["(< 1.4142 (clojure.math/sqrt 2) 1.4143)" "true"] + ["(long (clojure.math/pow 2 10))" "1024"] + ["(long (clojure.math/tan 0))" "0"] + ["(clojure.math/round 2.6)" "3"] + ["(clojure.math/floor 2.9)" "2"] + ["(clojure.math/signum -7.2)" "-1"] + ["(< 3.14 (clojure.math/to-radians 180) 3.15)" "true"] + ["(< 3.14 clojure.math/PI 3.15)" "true"] + ["(< 2.71 clojure.math/E 2.72)" "true"] + ["(long (clojure.math/cbrt 27))" "3"] + ["(< 4.6 (clojure.math/log 100) 4.7)" "true"] + # Chez has no native log10 (computed as log(x)/log(10)), so it can differ from + # the seed's C log10 in the last ulp (3 vs 2.9999…); range-check, don't pin. + ["(< 2.99 (clojure.math/log10 1000) 3.01)" "true"] + ["(do (require (quote [clojure.math :as m])) (long (m/hypot 3 4)))" "5"] + ["(mapv (comp long clojure.math/sqrt) [1 4])" "[1 2]"] + ["(long (clojure.math/atan2 0 1))" "0"] + # --- clojure.set (jolt-j5vg) --- + ["(do (require (quote [clojure.set :as s])) (= #{1 2 3 4} (s/union #{1 2} #{3 4})))" "true"] + ["(do (require (quote [clojure.set :as s])) (= #{2} (s/intersection #{1 2} #{2 3})))" "true"] + ["(do (require (quote [clojure.set :as s])) (= #{1} (s/difference #{1 2} #{2 3})))" "true"] + ["(do (require (quote [clojure.set :as s])) (s/subset? #{1} #{1 2}))" "true"] + ["(do (require (quote [clojure.set :as s])) (s/superset? #{1 2} #{1}))" "true"] + ["(do (require (quote [clojure.set :as s])) (= {1 :a 2 :b} (s/map-invert {:a 1 :b 2})))" "true"] + ["(do (require (quote [clojure.set :as s])) (= #{:a} (s/select keyword? #{:a})))" "true"] + ["(do (require (quote [clojure.set :as s])) (= #{{:a 1 :b 2}} (s/join #{{:a 1}} #{{:b 2}})))" "true"] + ["(do (require (quote [clojure.set :as s])) (= {:b 1} (s/rename-keys {:a 1} {:a :b})))" "true"] + ["(do (require (quote [clojure.set :as s])) (= 2 (count (s/index #{{:k 1} {:k 2}} [:k]))))" "true"] + # --- clojure.pprint (minimal shim) --- + ["(do (require (quote [clojure.pprint :as pp])) (= \"[1 2 3]\\n\" (with-out-str (pp/pprint [1 2 3]))))" "true"] + ["(do (require (quote [clojure.pprint :as pp])) (pp/with-pprint-dispatch pp/code-dispatch 42))" "42"]]) + +(defn run-capture [bin expr] + (def proc (os/spawn [bin "-e" expr] :p {:out :pipe :err :pipe})) + (def out (ev/read (proc :out) 0x100000)) + (def err (ev/read (proc :err) 0x100000)) + (def code (os/proc-wait proc)) + (def lines (filter (fn [l] (not (empty? l))) + (string/split "\n" (string/trim (if out (string out) ""))))) + [code (if (empty? lines) "" (last lines)) (string/trim (if err (string err) ""))]) + +(var pass 0) +(def fails @[]) +(each [expr expected] cases + (def [ocode oracle _] (run-capture "build/jolt" expr)) + (def [code got err] (run-capture jolt-bin expr)) + (cond + (not= ocode 0) (array/push fails [expr (string "ORACLE FAILED exit " ocode)]) + (not= oracle expected) (array/push fails [expr (string "ORACLE MISMATCH want `" expected "` got `" oracle "`")]) + (not= code 0) (array/push fails [expr (string "exit " code "; err: " err)]) + (= got expected) (++ pass) + (array/push fails [expr (string "want `" expected "`, got `" got "`")]))) + +(printf "\n_stdlib parity [%s]: %d/%d passed" jolt-bin pass (length cases)) +(when (> (length fails) 0) + (printf "%d FAIL(s):" (length fails)) + (each [e m] fails (printf " FAIL %s\n %s" e m))) +(flush) +(os/exit (if (empty? fails) 0 1)) diff --git a/test/chez/run-corpus-prelude.janet b/test/chez/run-corpus-prelude.janet index a3c5ca1..f7c0a1d 100644 --- a/test/chez/run-corpus-prelude.janet +++ b/test/chez/run-corpus-prelude.janet @@ -275,8 +275,14 @@ # prelude. Lights up read / read+string / with-in-str (read) / read-string and # clojure.edn/read-string. (eval / load-string / runtime defmacro stay Phase-3 — # they need the compiler at runtime.) 2259. +# Phase-2 stdlib closeout (jolt-j5vg / jolt-22vo / clojure.pprint): clojure.set + +# clojure.pprint added to the prelude stdlib tier (driver.janet stdlib-ns-files), +# clojure.math shimmed over Chez native flonum math (host/chez/math.ss), and the +# missing `long` coercion def-var!'d (converters.ss). pprint's 2-arity dropped its +# (binding [*out* writer] ...) (uncompilable on the no-fallback Chez back end — +# *out* isn't a bindable var). 2280, crashes 191->170, 0 new divergences. # Strided runs scale down. -(def base-floor (scan-number (or (os/getenv "JOLT_CHEZ_PRELUDE_FLOOR") "2259"))) +(def base-floor (scan-number (or (os/getenv "JOLT_CHEZ_PRELUDE_FLOOR") "2280"))) (def floor (if (os/getenv "JOLT_CORPUS_LIMIT") 0 base-floor)) (when (or (> (length diverged) 0) (< pass floor)) (printf "REGRESSION: pass %d < floor %d or %d new divergence(s)" pass floor (length diverged)))