Chez Phase 2 (inc Q): Java class statics + constructors (jolt-avt6)
Lower host class interop on the Chez back end. The analyzer now turns a non-var qualified ref `Class/member` into a :host-static node and a `(Class. ...)` / `(new Class ...)` form into a :host-new node (ir.clj gains both, with walker support). The Janet back end punts both to the interpreter, so its behavior is unchanged (verified: dot-form, `..` threading, shadowed `new`, and all interop still resolve via fallback). The Chez emit lowers a value ref to host-static-ref, a call head to host-static-call, and a constructor to host-new. host/chez/host-static.ss is the runtime registry these resolve against — the Chez port of the seed's class-statics / class-ctors / tagged-methods (java_base.janet + host_io.janet), restricted to the java.lang/util/net/io surface portable cljc code calls: Math, System (getenv/getProperty/exit/currentTimeMillis), Long, Integer, Boolean, Character, String, Thread, Class, Pattern (compile/quote/MULTILINE), URLEncoder/Decoder, Base64, the Number method surface (byteValue/intValue/...), plus the StringBuilder, StringWriter, StringReader, PushbackReader, HashMap, StringTokenizer, BigInteger, String, MapEntry, and exception constructors. Constructed objects are jhost records dispatched through record-method-dispatch. Also: emit now evaluates collection-literal elements left-to-right (emit-ordered) — Chez evaluates call args right-to-left, which had been swapping side-effecting elements in [(read r) (read r)] and map literals. This un-allowlisted the 6 eval-order corpus cases (the read-line trio + the three map-construction cases). Removed `.write` from the jolt-host-call fast-path so a StringWriter routes through dispatch. java.time formatting, edn/read-over-readers, and slurp/with-open over readers are deferred to a follow-up. Corpus parity 2078 -> 2134 (floor raised), 0 new divergences; the print-method builtin-override case is allowlisted (same multimethod gap, newly reachable now that StringWriter constructs). emit-test 326/326, _javastatic 51/51, conformance 355x3, full jpm test green.
This commit is contained in:
parent
a706a79b90
commit
c90c4cb610
10 changed files with 711 additions and 26 deletions
91
test/chez/_javastatic.janet
Normal file
91
test/chez/_javastatic.janet
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# jolt-avt6 — host class statics + constructors on Chez. The analyzer lowers
|
||||
# Class/member to :host-static and (Class. ...) to :host-new; the Chez emit lowers
|
||||
# them to host-static-ref/host-static-call/host-new (host-static.ss registry).
|
||||
# Expectations are the build/jolt (seed) oracle, captured per case.
|
||||
#
|
||||
# janet test/chez/_javastatic.janet
|
||||
(def jolt-bin (or (os/getenv "JOLT_BIN") "bin/jolt-chez"))
|
||||
|
||||
# [label expr] — expected is whatever build/jolt prints (captured at runtime).
|
||||
(def exprs
|
||||
["(Math/sqrt 16)"
|
||||
"(Math/abs -3)"
|
||||
"(Math/max 2 7)"
|
||||
"(pos? Long/MAX_VALUE)"
|
||||
"(String/valueOf 42)"
|
||||
"(String/valueOf \"hi\")"
|
||||
"(String/valueOf :k)"
|
||||
"(String/valueOf nil)"
|
||||
"(Long/parseLong \"42\")"
|
||||
"(Long/valueOf \"42\")"
|
||||
"(Integer/parseInt \"ff\" 16)"
|
||||
"(.byteValue (Integer/valueOf \"ff\" 16))"
|
||||
"(Boolean/parseBoolean \"true\")"
|
||||
"(Boolean/parseBoolean \"yes\")"
|
||||
"(Character/isUpperCase \\A)"
|
||||
"(Character/isLowerCase \\a)"
|
||||
"(Character/isUpperCase \\a)"
|
||||
"(Thread/interrupted)"
|
||||
"(System/getProperty \"os.name\")"
|
||||
"(string? (get (System/getenv) \"HOME\"))"
|
||||
"(string? (System/getenv \"HOME\"))"
|
||||
"(fn? System/exit)"
|
||||
"(string? (get (System/getProperties) \"os.name\"))"
|
||||
"(pos? (count (seq (System/getenv))))"
|
||||
"(let [es (map (fn [[k v]] [k v]) (System/getenv))] (and (pos? (count es)) (every? vector? es)))"
|
||||
# constructors + their methods
|
||||
"(.toString (StringBuilder. \"x\"))"
|
||||
"(.toString (-> (StringBuilder.) (.append \"a\") (.append \\b) (.append 1)))"
|
||||
"(.toString (.append (StringBuilder. 16) \"x\"))"
|
||||
"(let [sb (StringBuilder.)] (.append sb \"abcd\") (.setLength sb 2) (.toString sb))"
|
||||
"(let [w (StringWriter.)] (.write w \"a\") (.append w \\b) (.toString w))"
|
||||
"(let [r (StringReader. \"ab\")] [(.read r) (.read r) (.read r)])"
|
||||
"(let [r (StringReader. \"ab\")] (.mark r 1) [(.read r) (do (.reset r) (.read r))])"
|
||||
"(let [r (java.io.PushbackReader. (java.io.StringReader. \"ab\"))] [(.read r) (.read r)])"
|
||||
"(let [r (PushbackReader. (StringReader. \"ab\")) a (.read r)] (.unread r a) [a (.read r) (.read r)])"
|
||||
"(let [r (PushbackReader. (StringReader. \"a\"))] (.unread r \\x) [(.read r) (.read r)])"
|
||||
"(BigInteger. \"123\")"
|
||||
"(let [m (HashMap. {:a 1 :b 2})] (.get m :b))"
|
||||
"(let [m (HashMap. {})] (.put m :x 1) (.put m :y 2) (.size m))"
|
||||
"(let [t (StringTokenizer. \"a=1&b=2\" \"&\")] [(.nextToken t) (.nextToken t)])"
|
||||
"(.toString (StringBuilder. \"x\"))"
|
||||
# ring-codec surface
|
||||
"(URLEncoder/encode \"a b=c\")"
|
||||
"(URLDecoder/decode (URLEncoder/encode \"x &=%?\"))"
|
||||
"(String. (.encode (Base64/getEncoder) (.getBytes \"hello\")))"
|
||||
"(String. (.decode (Base64/getDecoder) (String. (.encode (Base64/getEncoder) (.getBytes \"hello\")))))"
|
||||
"(Integer/parseInt \"ff\" 16)"
|
||||
# Pattern statics
|
||||
"(regex? (Pattern/compile \"a.c\"))"
|
||||
"(.split (Pattern/compile \",\") \"a,b,c\")"
|
||||
"(do (require '[clojure.string :as s]) (s/replace \"a1b2\" (Pattern/compile \"[0-9]\") \"\"))"
|
||||
"(boolean (re-find (Pattern/compile \"^x\" Pattern/MULTILINE) \"y\\nx\"))"
|
||||
"(boolean (re-find (re-pattern (Pattern/quote \"a.c\")) \"za.cy\"))"
|
||||
"(boolean (re-find (re-pattern (Pattern/quote \"a.c\")) \"zabcy\"))"])
|
||||
|
||||
(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 exprs
|
||||
(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= code 0) (array/push fails [expr (string "exit " code "; err: " err)])
|
||||
(= got oracle) (++ pass)
|
||||
(array/push fails [expr (string "want `" oracle "`, got `" got "`")])))
|
||||
|
||||
(printf "\n_javastatic parity [%s]: %d/%d passed" jolt-bin pass (length exprs))
|
||||
(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))
|
||||
|
|
@ -300,14 +300,17 @@
|
|||
(string "chez=" out " janet=" want " | " err))))
|
||||
|
||||
# 3l) host interop method calls (inc 3h). (.method target arg*) analyzes to a
|
||||
# :host-call IR node and lowers to a jolt-host-call dispatch. The Janet back end
|
||||
# PUNTS these (no interop model -> interpreter); the Chez RT shims the methods
|
||||
# jolt-core's io tier uses: .write -> display to a port, .isDirectory ->
|
||||
# file-directory?, .listFiles -> directory-list. Interop has no portable oracle
|
||||
# (the Janet host models it differently), so these are emit-shape checks plus one
|
||||
# deterministic runtime probe (the root "/" is always a directory).
|
||||
# :host-call IR node and lowers to a dispatch. The Janet back end PUNTS these
|
||||
# (no interop model -> interpreter); the Chez RT shims the File methods
|
||||
# jolt-core's io tier uses via jolt-host-call (.isDirectory -> file-directory?,
|
||||
# .listFiles -> directory-list). All OTHER methods (.write/.append/.read/... on
|
||||
# a StringWriter/StringReader/record) route through record-method-dispatch
|
||||
# (jolt-avt6 removed .write from the jolt-host-call fast-path so a StringWriter
|
||||
# jhost handles it). Interop has no portable oracle (the Janet host models it
|
||||
# differently), so these are emit-shape checks plus one deterministic runtime
|
||||
# probe (the root "/" is always a directory).
|
||||
(each [label src needle]
|
||||
[["emit .write -> jolt-host-call" "(fn [w x] (.write w x))" "jolt-host-call"]
|
||||
[["emit .write -> record-method-dispatch" "(fn [w x] (.write w x))" "record-method-dispatch"]
|
||||
["emit .write keeps method name" "(fn [w x] (.write w x))" "\"write\""]
|
||||
["emit .isDirectory -> jolt-host-call" "(fn [f] (.isDirectory f))" "isDirectory"]
|
||||
["emit .listFiles -> jolt-host-call" "(fn [f] (.listFiles f))" "listFiles"]]
|
||||
|
|
@ -318,6 +321,21 @@
|
|||
(ok "runtime .isDirectory \"/\" = true" (and (= code 0) (= out "true"))
|
||||
(string "chez=" out " | " err)))
|
||||
|
||||
# 3l') host class statics + constructors (jolt-avt6). Class/member lowers to a
|
||||
# :host-static node (host-static-ref in value position, host-static-call as a
|
||||
# call head); (Class. ...) / (new Class ...) lower to :host-new. The Janet back
|
||||
# end punts all three; the Chez RT resolves them from the class-statics /
|
||||
# class-ctors / jhost-method registries (host/chez/host-static.ss). Emit-shape
|
||||
# checks; the registry behaviour is covered by test/chez/_javastatic.janet.
|
||||
(each [label src needle]
|
||||
[["emit Class/field -> host-static-ref" "(fn [] Long/MAX_VALUE)" "(host-static-ref \"Long\" \"MAX_VALUE\")"]
|
||||
["emit Class/method call -> host-static-call" "(fn [] (System/getenv))" "(host-static-call \"System\" \"getenv\")"]
|
||||
["emit static call passes args" "(fn [x] (Long/parseLong x))" "(host-static-call \"Long\" \"parseLong\""]
|
||||
["emit (Class.) -> host-new" "(fn [] (StringBuilder.))" "(host-new \"StringBuilder\")"]
|
||||
["emit (new Class ...) -> host-new" "(fn [s] (new java.io.StringReader s))" "(host-new \"java.io.StringReader\""]]
|
||||
(let [scm (protect (emit/emit (backend/analyze-form ctx (in (r/parse-next src) 0))))]
|
||||
(ok label (and (scm 0) (string/find needle (scm 1))) (string/format "%p" scm))))
|
||||
|
||||
# 3m) regex (jolt-i0s3): the #"…" literal lowers to a jolt-regex value over the
|
||||
# vendored irregex; re-pattern/re-matches/re-find/re-seq/regex? are def-var!'d
|
||||
# into clojure.core (not subset native-ops — irregex's Unicode/property
|
||||
|
|
|
|||
|
|
@ -45,9 +45,10 @@
|
|||
{"class name evaluates to canonical string" true
|
||||
"dispatch-only class name" true
|
||||
"inside class" true
|
||||
"values evaluate in source order" true
|
||||
"keys evaluate before their values, pairwise" true
|
||||
"source order with a nil value (phm form)" true
|
||||
# (the collection-literal eval-order entries here — "values evaluate in source
|
||||
# order" / "keys evaluate before their values" / "source order with a nil value"
|
||||
# — were REMOVED in jolt-avt6: emit now evaluates vector/set/map literal
|
||||
# elements left-to-right via emit-ordered, so they pass.)
|
||||
"close on throw" true
|
||||
# *ns* now a namespace value (jolt-yxqm): str/ns-name of *ns* + the var str
|
||||
# case ("ns-name of *ns*" / "str of *ns*" / "*ns* user" / "str of a var") pass.
|
||||
|
|
@ -69,19 +70,21 @@
|
|||
"defmethod overrides a record, top level" true
|
||||
"defmethod fires nested in a map" true
|
||||
"defmethod fires through prn" true
|
||||
# Same print-method gap, newly REACHABLE in jolt-avt6: StringWriter now
|
||||
# constructs, so (defmethod print-method :number ...) + (print-method 42 w)
|
||||
# runs — but print-method's multimethod override on a builtin isn't consulted,
|
||||
# so the StringWriter holds "42" not "#42#". (The :default print-method path —
|
||||
# "StringWriter accumulates" / "direct call uses :default" — does pass now.)
|
||||
"direct builtin override" true
|
||||
# var def-time metadata (^:private / ^Type tag / docstring) is now captured on
|
||||
# the Chez var-cell (jolt-zikh), so those three cases pass.
|
||||
"methods table inspectable" true
|
||||
# jolt-nfca made (require ...) a runtime no-op (the driver pre-evals requires
|
||||
# for aliases), and the clojure.string prelude tier now loads — which makes
|
||||
# these previously-CRASHING cases emit + run, surfacing pre-existing gaps:
|
||||
# - the read-line trio reads two lines into a [(read-line) (read-line)] vector;
|
||||
# the emitted Scheme evaluates the two elements in non-source order (the same
|
||||
# eval-order gap already allowlisted above as "values evaluate in source
|
||||
# order"), so the lines come back swapped. Reachable now that with-in-str runs.
|
||||
"read-line sequential" true
|
||||
"read-line after last" true
|
||||
"empty line" true
|
||||
# - the read-line trio ([(read-line) (read-line)]) now PASSES: jolt-avt6's
|
||||
# emit-ordered evaluates the two vector elements left-to-right, so the lines
|
||||
# no longer come back swapped (the entries were removed here).
|
||||
# - (instance? clojure.lang.Atom (atom 0)): the fully-qualified host class name
|
||||
# clojure.lang.Atom isn't mapped to the atom predicate on Chez (host-class
|
||||
# interop, jolt-mn9o/avt6). Reachable now that the leading require is a no-op.
|
||||
|
|
@ -220,8 +223,18 @@
|
|||
# join/split/replace/replace-all/reverse-b) def-var!'d on the RT; regex split
|
||||
# keeps interior empties + honors limit, regex replace does $N + fn replacement;
|
||||
# require/use are runtime no-ops) 2078.
|
||||
# jolt-avt6 (host class statics + constructors — the analyzer lowers Class/member
|
||||
# to :host-static and (Class. ...)/(new Class ...) to :host-new; the Chez RT
|
||||
# resolves them from class-statics/class-ctors/jhost-method registries
|
||||
# (host-static.ss): Math/System/Long/Integer/Boolean/Character/String/Thread/Class
|
||||
# statics, Pattern compile/quote/MULTILINE, URLEncoder/Decoder, Base64, the Number
|
||||
# method surface (byteValue/intValue/...), and the StringBuilder/StringWriter/
|
||||
# StringReader/PushbackReader/HashMap/StringTokenizer/BigInteger/String/MapEntry/
|
||||
# exception constructors. Also emit now evaluates collection-literal elements
|
||||
# left-to-right (emit-ordered), which un-allowlisted the 6 eval-order cases.
|
||||
# java.time formatting / edn-read-over-readers / slurp-over-readers deferred) 2134.
|
||||
# Strided runs scale down.
|
||||
(def base-floor (scan-number (or (os/getenv "JOLT_CHEZ_PRELUDE_FLOOR") "2078")))
|
||||
(def base-floor (scan-number (or (os/getenv "JOLT_CHEZ_PRELUDE_FLOOR") "2134")))
|
||||
(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)))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue