feat: read N/M/ratio/radix/exponent number literals; clean suite measurement
Reader gaps caused the clojure-test-suite worker to crash whole deftests on literals it could not parse (0N, 1.5M, 2r1010, 1/2), losing every assertion in the file. read-number now handles: - N (bigint) / M (bigdec) suffixes -> plain number (Jolt has no bignum/bigdec) - ratios a/b -> double quotient - radix integers NrDDD (2r1010, 16rFF, 36rZ) parsed by base - exponents (1e3, 1.5e-2) and 0X hex Also fixed suite measurement: when-var-exists now skips silently (its SKIP print to stdout was corrupting the worker's count line, dropping whole files), and the worker emits counts on an @@COUNTS sentinel line (robust against test bodies that print, e.g. with-out-str). Runner parses the sentinel; deftest crashes now report the underlying message. Impact: clojure-test-suite 210->231 files run, pass 1955->3535, clean files 24->39. Baseline raised to 3450/38. spec: numbers/literal-syntax (13 cases). jpm test green.
This commit is contained in:
parent
20ab88dd0c
commit
acfcf2f94b
5 changed files with 106 additions and 39 deletions
|
|
@ -159,41 +159,78 @@
|
||||||
(read-fractional s pos (+ end 1))
|
(read-fractional s pos (+ end 1))
|
||||||
end))
|
end))
|
||||||
|
|
||||||
|
# Value of an alphanumeric digit for radix parsing (0-9, a-z/A-Z = 10-35).
|
||||||
|
(defn- radix-digit-val [c]
|
||||||
|
(cond
|
||||||
|
(and (>= c 48) (<= c 57)) (- c 48) # 0-9
|
||||||
|
(and (>= c 97) (<= c 122)) (+ 10 (- c 97)) # a-z
|
||||||
|
(and (>= c 65) (<= c 90)) (+ 10 (- c 65)) # A-Z
|
||||||
|
nil))
|
||||||
|
(defn- read-alnum [s pos end]
|
||||||
|
(if (and (< end (length s)) (not (nil? (radix-digit-val (s end)))))
|
||||||
|
(read-alnum s pos (+ end 1))
|
||||||
|
end))
|
||||||
|
(defn- read-exponent
|
||||||
|
"If s[end] is e/E (optionally with sign) followed by digits, return the index
|
||||||
|
past the exponent; else end."
|
||||||
|
[s end]
|
||||||
|
(let [len (length s)]
|
||||||
|
(if (and (< end len) (or (= (s end) 101) (= (s end) 69))) # e / E
|
||||||
|
(let [p (if (and (< (+ end 1) len) (or (= (s (+ end 1)) 43) (= (s (+ end 1)) 45))) (+ end 2) (+ end 1))
|
||||||
|
de (read-digits s p p)]
|
||||||
|
(if (> de p) de end))
|
||||||
|
end)))
|
||||||
|
|
||||||
|
# Jolt has no true bignum/ratio types (see README): an integer/float literal
|
||||||
|
# suffixed N (bigint) or M (bigdec) reads as the plain number, a ratio a/b reads
|
||||||
|
# as the double quotient, and radixed integers (2r101, 16rFF) are parsed by base.
|
||||||
(defn read-number [s pos]
|
(defn read-number [s pos]
|
||||||
(var start pos) # start is mutable for sign handling
|
(var start pos) # start is mutable for sign handling
|
||||||
(var neg false)
|
(var neg false)
|
||||||
|
(def len (length s))
|
||||||
# optional sign
|
# optional sign
|
||||||
(if (and (< pos (length s)) (= (s pos) 45))
|
(if (and (< pos len) (= (s pos) 45))
|
||||||
(do (set start (+ pos 1)) (set neg true)))
|
(do (set start (+ pos 1)) (set neg true)))
|
||||||
|
|
||||||
(let [pos start
|
(let [pos start
|
||||||
hex? (and (< (+ pos 1) (length s))
|
hex? (and (< (+ pos 1) len)
|
||||||
(= (s pos) 48) (= (s (+ pos 1)) 120))
|
(= (s pos) 48) (or (= (s (+ pos 1)) 120) (= (s (+ pos 1)) 88)))] # 0x / 0X
|
||||||
start (if hex? (+ pos 2) pos)
|
(if hex?
|
||||||
end (if hex?
|
(let [hs (+ pos 2) he (read-hex-digits s hs hs)]
|
||||||
(read-hex-digits s start start)
|
(if (= he hs) (error (string "Expected hex digits at " pos)))
|
||||||
(read-digits s start start))]
|
(let [he2 (if (and (< he len) (= (s he) 78)) (+ he 1) he) # trailing N
|
||||||
(if (= end start) (error (string "Expected number at " pos)))
|
val (scan-number (string "0x" (string/slice s hs he)))]
|
||||||
|
[(if neg (- val) val) he2]))
|
||||||
# check for fractional part
|
(let [iend (read-digits s pos pos)]
|
||||||
(if (and (not hex?)
|
(if (= iend pos) (error (string "Expected number at " pos)))
|
||||||
(< end (length s))
|
(cond
|
||||||
(= (s end) 46))
|
# radix integer: <base>r<digits>, e.g. 2r1010, 16rFF, 36rZ
|
||||||
(let [frac-start (+ end 1)
|
(and (< iend len) (or (= (s iend) 114) (= (s iend) 82)))
|
||||||
frac-end (read-fractional s frac-start frac-start)]
|
(let [base (scan-number (string/slice s pos iend))
|
||||||
(if (= frac-end frac-start) (error "Expected digit after ."))
|
ds (+ iend 1)
|
||||||
(let [num-str (string/slice s start frac-end)
|
de (read-alnum s ds ds)]
|
||||||
val (scan-number num-str)]
|
(if (= de ds) (error (string "Expected radix digits at " ds)))
|
||||||
[(if neg (- val) val) frac-end]))
|
(var acc 0)
|
||||||
|
(var i ds)
|
||||||
# integer or hex
|
(while (< i de) (set acc (+ (* acc base) (radix-digit-val (s i)))) (++ i))
|
||||||
(let [num-str (string/slice s start end)
|
[(if neg (- acc) acc) de])
|
||||||
val (if hex?
|
# ratio: <int>/<int> (only when a digit follows the slash)
|
||||||
(string/format "0x%s" num-str)
|
(and (< (+ iend 1) len) (= (s iend) 47) (digit? (s (+ iend 1))))
|
||||||
num-str)
|
(let [ds (+ iend 1) de (read-digits s ds ds)
|
||||||
val (scan-number val)]
|
numr (scan-number (string/slice s pos iend))
|
||||||
[(if neg (- val) val) end]))))
|
den (scan-number (string/slice s ds de))]
|
||||||
|
[(if neg (- (/ numr den)) (/ numr den)) de])
|
||||||
|
# fractional and/or exponent, optional trailing N/M
|
||||||
|
(let [frac-end (if (and (< iend len) (= (s iend) 46))
|
||||||
|
(let [fs (+ iend 1) fe (read-fractional s fs fs)]
|
||||||
|
(if (= fe fs) (error "Expected digit after .")) fe)
|
||||||
|
iend)
|
||||||
|
exp-end (read-exponent s frac-end)
|
||||||
|
val (scan-number (string/slice s start exp-end))
|
||||||
|
# consume a trailing N (bigint) or M (bigdec) suffix
|
||||||
|
fin (if (and (< exp-end len) (or (= (s exp-end) 78) (= (s exp-end) 77)))
|
||||||
|
(+ exp-end 1) exp-end)]
|
||||||
|
[(if neg (- val) val) fin]))))))
|
||||||
|
|
||||||
(defn read-list [s pos]
|
(defn read-list [s pos]
|
||||||
# pos is at opening paren
|
# pos is at opening paren
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@
|
||||||
|
|
||||||
# Baseline: assertions Jolt currently passes across the suite. Raise as Jolt
|
# Baseline: assertions Jolt currently passes across the suite. Raise as Jolt
|
||||||
# improves so a regression (previously-passing assertion breaking) is caught.
|
# improves so a regression (previously-passing assertion breaking) is caught.
|
||||||
(def baseline-pass 1900)
|
(def baseline-pass 3450)
|
||||||
# A file is "clean" when it ran with zero failures AND zero errors.
|
# A file is "clean" when it ran with zero failures AND zero errors.
|
||||||
(def baseline-clean-files 22)
|
(def baseline-clean-files 38)
|
||||||
# Per-file wall-clock budget (seconds). Normal files finish in well under 1s;
|
# Per-file wall-clock budget (seconds). Normal files finish in well under 1s;
|
||||||
# this only fires on infinite-sequence hangs.
|
# this only fires on infinite-sequence hangs.
|
||||||
(def per-file-timeout 6)
|
(def per-file-timeout 6)
|
||||||
|
|
@ -55,11 +55,15 @@
|
||||||
(if (and ok data) (string data) nil))
|
(if (and ok data) (string data) nil))
|
||||||
|
|
||||||
(defn- parse-counts [s]
|
(defn- parse-counts [s]
|
||||||
# s is "pass fail error"
|
# Find the "@@COUNTS p f e" sentinel line (a test body may have printed other
|
||||||
(def parts (string/split " " (string/trim s)))
|
# lines to stdout, e.g. with-out-str tests).
|
||||||
(if (= 3 (length parts))
|
(var result nil)
|
||||||
[(scan-number (parts 0)) (scan-number (parts 1)) (scan-number (parts 2))]
|
(each line (string/split "\n" s)
|
||||||
nil))
|
(when (string/has-prefix? "@@COUNTS " line)
|
||||||
|
(let [parts (string/split " " (string/trim line))]
|
||||||
|
(when (= 4 (length parts))
|
||||||
|
(set result [(scan-number (parts 1)) (scan-number (parts 2)) (scan-number (parts 3))])))))
|
||||||
|
result)
|
||||||
|
|
||||||
(if (not (os/stat suite-dir))
|
(if (not (os/stat suite-dir))
|
||||||
(print "clojure-test-suite: ~/src/clojure-test-suite not present — skipped")
|
(print "clojure-test-suite: ~/src/clojure-test-suite not present — skipped")
|
||||||
|
|
|
||||||
|
|
@ -37,4 +37,10 @@
|
||||||
(def p (eval-string ctx "(clojure.test/n-pass)"))
|
(def p (eval-string ctx "(clojure.test/n-pass)"))
|
||||||
(def f (eval-string ctx "(clojure.test/n-fail)"))
|
(def f (eval-string ctx "(clojure.test/n-fail)"))
|
||||||
(def e (eval-string ctx "(clojure.test/n-error)"))
|
(def e (eval-string ctx "(clojure.test/n-error)"))
|
||||||
(printf "%d %d %d" (if (number? p) p 0) (if (number? f) f 0) (if (number? e) e 0)))
|
# A "dump" 2nd arg (or SUITE_DUMP env) also prints each failure/error message
|
||||||
|
# (one DUMP line each) for triage.
|
||||||
|
(when (or (os/getenv "SUITE_DUMP") (= "dump" (get (dyn :args) 2)))
|
||||||
|
(eval-string ctx "(doseq [m (clojure.test/failures)] (println (str \"DUMP \" m)))"))
|
||||||
|
# Counts on a sentinel line so parsers find it even if a test body printed to
|
||||||
|
# stdout (e.g. with-out-str / println-str tests).
|
||||||
|
(printf "@@COUNTS %d %d %d" (if (number? p) p 0) (if (number? f) f 0) (if (number? e) e 0)))
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,24 @@
|
||||||
["int? ##Inf false" "false" "(int? ##Inf)"]
|
["int? ##Inf false" "false" "(int? ##Inf)"]
|
||||||
["pos-int? ##Inf" "false" "(pos-int? ##Inf)"])
|
["pos-int? ##Inf" "false" "(pos-int? ##Inf)"])
|
||||||
|
|
||||||
|
# Numeric literal syntaxes. Jolt has no true bignum/ratio/bigdec types, so the
|
||||||
|
# N (bigint) and M (bigdec) suffixes read as the plain number, ratios as the
|
||||||
|
# double quotient; radix integers (NrDDD) are parsed by base.
|
||||||
|
(defspec "numbers / literal syntax"
|
||||||
|
["bigint suffix N" "42" "42N"]
|
||||||
|
["bigint zero" "0" "0N"]
|
||||||
|
["bigdec suffix M" "1.5" "1.5M"]
|
||||||
|
["bigdec int M" "0" "0.0M"]
|
||||||
|
["ratio -> double" "0.5" "1/2"]
|
||||||
|
["ratio 3/4" "0.75" "3/4"]
|
||||||
|
["neg ratio" "-0.5" "-1/2"]
|
||||||
|
["radix binary" "10" "2r1010"]
|
||||||
|
["radix hex-ish" "255" "16rFF"]
|
||||||
|
["radix base36" "35" "36rZ"]
|
||||||
|
["hex" "255" "0xFF"]
|
||||||
|
["exponent" "1000.0" "1e3"]
|
||||||
|
["exponent neg" "0.015" "1.5e-2"])
|
||||||
|
|
||||||
(defspec "numbers / bit-ops & math"
|
(defspec "numbers / bit-ops & math"
|
||||||
["bit-and" "4" "(bit-and 12 6)"]
|
["bit-and" "4" "(bit-and 12 6)"]
|
||||||
["bit-or" "14" "(bit-or 12 6)"]
|
["bit-or" "14" "(bit-or 12 6)"]
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@
|
||||||
;; Run every deftest registered since the last reset, isolating crashes.
|
;; Run every deftest registered since the last reset, isolating crashes.
|
||||||
(defn run-registered []
|
(defn run-registered []
|
||||||
(doseq [t @registry]
|
(doseq [t @registry]
|
||||||
(try (t) (catch :default e (clojure.test/err! "deftest crashed"))))
|
(try (t) (catch :default e (clojure.test/err! (str "deftest crashed: " (clojure.core/ex-message e))))))
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
;; clojure.test entry points the suite may call — no-ops; the Janet runner
|
;; clojure.test entry points the suite may call — no-ops; the Janet runner
|
||||||
|
|
@ -96,10 +96,12 @@
|
||||||
;; Gate a test on whether its target var exists in this dialect. Jolt only
|
;; Gate a test on whether its target var exists in this dialect. Jolt only
|
||||||
;; implements a subset of clojure.core, so unimplemented fns get skipped
|
;; implements a subset of clojure.core, so unimplemented fns get skipped
|
||||||
;; cleanly rather than erroring.
|
;; cleanly rather than erroring.
|
||||||
|
;; Skips silently (no stdout) so the worker's stdout stays just the count line;
|
||||||
|
;; an unimplemented var simply contributes no assertions.
|
||||||
(defmacro when-var-exists [var-sym & body]
|
(defmacro when-var-exists [var-sym & body]
|
||||||
(if (resolve var-sym)
|
(if (resolve var-sym)
|
||||||
`(do ~@body)
|
`(do ~@body)
|
||||||
`(println "SKIP -" '~var-sym)))
|
nil))
|
||||||
|
|
||||||
;; `(thrown? body)` — true iff evaluating body throws. The suite always uses
|
;; `(thrown? body)` — true iff evaluating body throws. The suite always uses
|
||||||
;; the single-arg (no exception-class) form via this portability helper.
|
;; the single-arg (no exception-class) form via this portability helper.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue