Chez Phase 3: fix +N reader literal in jolt.reader only (not the doomed Janet seed)

fix-bugs-dont-reproduce, scoped per the keeper rule: jolt-if19 (a leading + on a
numeric literal errored instead of reading as the positive number) is fixed in
jolt.reader (read-number* now strips a leading + like -, positive), the code we
keep. The Janet seed reader (reader.janet) is left untouched — it's deleted in
Phase 5, so fixing it is wasted work.

Since the seed reader stays buggy, reader-parity can't use it as the oracle for
these inputs: added check-correct to assert the portable reader against the hand-
verified value (+5 => 5, +42, +0xff => 255, +3.5). reader-parity 67/67. No Janet
binary/gate impact (jolt.reader is not yet in the binary path). jolt-if19.
This commit is contained in:
Yogthos 2026-06-19 19:29:53 -04:00
parent 4de586089c
commit afada6d4ff
2 changed files with 25 additions and 5 deletions

View file

@ -117,8 +117,11 @@
;; number, a ratio a/b reads as the double quotient, radixed ints by base. ;; number, a ratio a/b reads as the double quotient, radixed ints by base.
(defn- read-number* [s pos] (defn- read-number* [s pos]
(let [length (len s) (let [length (len s)
;; optional leading sign: - negates; + is a positive no-op (Clojure reads
;; +5 as 5). read-form only dispatches +digit/-digit, so the sign is real.
neg (and (< pos length) (= (cp s pos) 45)) neg (and (< pos length) (= (cp s pos) 45))
start (if neg (inc pos) pos) plus (and (< pos length) (= (cp s pos) 43))
start (if (or neg plus) (inc pos) pos)
hex? (and (< (inc start) length) (= (cp s start) 48) hex? (and (< (inc start) length) (= (cp s start) 48)
(let [c1 (cp s (inc start))] (or (= c1 120) (= c1 88))))] ; 0x / 0X (let [c1 (cp s (inc start))] (or (= c1 120) (= c1 88))))] ; 0x / 0X
(if hex? (if hex?

View file

@ -37,15 +37,26 @@
(def w (protect (in (r/parse-next input) 0))) # Janet seed reader (oracle) (def w (protect (in (r/parse-next input) 0))) # Janet seed reader (oracle)
(def g (protect (read-one input))) # portable Clojure reader (def g (protect (read-one input))) # portable Clojure reader
(cond (cond
# both readers throw on the same input = faithful parity (e.g. the +N latent # both readers throw on the same input = faithful parity (safety net for
# bug, jolt-if19 — reader.janet dispatches +digit to read-number but never # genuinely-invalid input). We FIX latent bugs rather than reproduce them
# strips the +, so "+5" errors; the port reproduces it). # (fix-bugs-dont-reproduce), so this should be rare.
(and (not (w 0)) (not (g 0))) (ok input true) (and (not (w 0)) (not (g 0))) (ok input true)
(not (w 0)) (ok input false (string "janet threw, clj didn't: clj=" (string/format "%p" (g 1)))) (not (w 0)) (ok input false (string "janet threw, clj didn't: clj=" (string/format "%p" (g 1))))
(not (g 0)) (ok input false (string "clj threw, janet ok: " (string (g 1)))) (not (g 0)) (ok input false (string "clj threw, janet ok: " (string (g 1))))
(ok input (jeq (w 1) (g 1)) (ok input (jeq (w 1) (g 1))
(string "clj=" (string/format "%p" (g 1)) " janet=" (string/format "%p" (w 1)))))) (string "clj=" (string/format "%p" (g 1)) " janet=" (string/format "%p" (w 1))))))
# For inputs where the Janet seed reader is a BUGGY oracle (a latent bug we FIX in
# the port rather than reproduce, fix-bugs-dont-reproduce), assert the portable
# reader against the hand-verified correct value. The Janet seed isn't fixed —
# it's deleted in Phase 5 — so we don't compare against it here.
(defn check-correct [input expected]
(def g (protect (read-one input)))
(if (not (g 0))
(ok input false (string "clj threw: " (string (g 1))))
(ok input (jeq expected (g 1))
(string "clj=" (string/format "%p" (g 1)) " want=" (string/format "%p" expected)))))
# --- inc 5a: atoms ------------------------------------------------------------- # --- inc 5a: atoms -------------------------------------------------------------
# nil / bool # nil / bool
(each i ["nil" "true" "false"] (check i)) (each i ["nil" "true" "false"] (check i))
@ -56,9 +67,15 @@
# strings (escapes) # strings (escapes)
(each i [`"hello"` `"with space"` `"tab\there"` `"nl\nhere"` `"q\"q"` `"back\\slash"` `""`] (check i)) (each i [`"hello"` `"with space"` `"tab\there"` `"nl\nhere"` `"q\"q"` `"back\\slash"` `""`] (check i))
# integers / signs / hex / radix # integers / signs / hex / radix
(each i ["0" "42" "-7" "+5" "123456" "0xFF" "0x10" "-0xff" "2r1010" "16rFF" "36rZ" "8r17"] (check i)) (each i ["0" "42" "-7" "123456" "0xFF" "0x10" "-0xff" "2r1010" "16rFF" "36rZ" "8r17"] (check i))
# floats / exponent / ratio / N|M suffix # floats / exponent / ratio / N|M suffix
(each i ["3.14" "-2.5" "0.0" "1e10" "1.5e-3" "2E5" "10N" "3.14M" "1/2" "-3/4" "22/7"] (check i)) (each i ["3.14" "-2.5" "0.0" "1e10" "1.5e-3" "2E5" "10N" "3.14M" "1/2" "-3/4" "22/7"] (check i))
# leading + reads as the positive number (jolt-if19 fixed in the port; the Janet
# seed reader still errors on these, so assert against the correct value).
(check-correct "+5" 5)
(check-correct "+42" 42)
(check-correct "+0xff" 255)
(check-correct "+3.5" 3.5)
# characters # characters
(each i [`\a` `\Z` `\0` `\newline` `\tab` `\space` `\return` `\\` `\(` `\{` `\%` `A` `\o101`] (check i)) (each i [`\a` `\Z` `\0` `\newline` `\tab` `\space` `\return` `\\` `\(` `\{` `\%` `A` `\o101`] (check i))