Chez numeric tower: exact ints / Ratio / double for JVM parity (jolt-n6al)

jolt was all-flonum (one :number type, inherited from Janet whose only number
type is a double). The Chez runtime has a full numeric tower, so the zero-Janet
path now carries it = JVM Clojure semantics:

  (/ 1 2)      => 1/2      (exact Ratio, was 0.5)
  (integer? 3) => true   (integer? 3.0) => false   (float? 3.0) => true
  (ratio? (/ 1 2)) => true   (= 3 3.0) => false   (== 3 3.0) => true
  (+ 1 2) => 3 (exact)   (/ 1.0 2) => 0.5 (double)

jolt= was already exactness-aware (values.ss) and == is value-equality, so
=/== match the JVM split. The reader preserves exactness (integer literals exact,
a/b ratios exact rationals, decimals/exponents flonums); backend_scheme emit-const
renders exact ints/ratios and flonums faithfully; the value-position arithmetic,
count, int, compare, bit ops, parseLong, string .length/.indexOf, range,
timestamps, and array bytes return exact integers (= JVM int/long) instead of
coercing to flonum. double/parseDouble/clojure.math floor|ceil|signum stay double.

Only the zero-Janet path carries the tower (the Janet reader loses exactness into
a double before emit). The prelude/all-flonum path is unaffected for compiled code;
the runtime reader is shared, so a couple of all-flonum reader assertions become
value (==) assertions. ~16 numeric corpus cases now give the JVM tower value vs the
Janet-era :expected and are allowlisted as tower divergences (Chez == reference
JVM) pending the corpus flip to JVM (jolt-ecz0). No BigDecimal type (1M).

Re-minted. zero-janet 2682 (floor 2698->2682, the reclassified tower cases), 0 new
divergences; fixpoint 10/10, bootstrap 6/6, spine 35/35, cli 49/49; Janet gate 155
files 0 failed.
This commit is contained in:
Yogthos 2026-06-20 23:09:27 -04:00
parent eb1c3298a4
commit 467ad75ff7
20 changed files with 291 additions and 210 deletions

View file

@ -252,10 +252,11 @@
((or (cseq? coll) (empty-list-t? coll)) (seq-nth coll i #t d)) ((or (cseq? coll) (empty-list-t? coll)) (seq-nth coll i #t d))
(else d)))))) (else d))))))
;; jolt models every number as a double, so a count is a flonum — else ;; a count is an exact integer (JVM parity: count returns a long). jolt= is
;; (= 2 (count m)) is false (jolt= is exactness-aware: 2.0 vs exact 2). ;; exactness-aware, so this must be exact to match an exact integer literal:
;; (= 2 (count m)) -> 2 vs exact 2 -> true.
(define (jolt-count coll) (define (jolt-count coll)
(exact->inexact (begin
(cond ((pvec? coll) (pvec-count coll)) (cond ((pvec? coll) (pvec-count coll))
((pmap? coll) (pmap-cnt coll)) ((pmap? coll) (pmap-cnt coll))
((pset? coll) (pset-count coll)) ((pset? coll) (pset-count coll))

View file

@ -90,28 +90,31 @@
(string-append (if (string? p) p (jolt-str-render-one p)) (string-append (if (string? p) p (jolt-str-render-one p))
(number->string jolt-gensym-counter))))) (number->string jolt-gensym-counter)))))
(define (jolt-int x) (if (char? x) (exact->inexact (char->integer x)) (truncate x))) ;; int/long: truncate toward zero to an EXACT integer (= JVM long). char -> code
;; point (exact). double: always a flonum (= JVM double).
(define (jolt-int x) (if (char? x) (char->integer x) (exact (truncate x))))
(define (jolt-double x) (if (char? x) (exact->inexact (char->integer x)) (exact->inexact x))) (define (jolt-double x) (if (char? x) (exact->inexact (char->integer x)) (exact->inexact x)))
;; compare: 3-way, ported from core_io.janet core-compare. ;; compare: 3-way, returns an EXACT integer (= JVM compare -> int).
(define (jolt-cmp3 x y) (cond ((< x y) -1.0) ((> x y) 1.0) (else 0.0))) (define (jolt-cmp3 x y) (cond ((< x y) -1) ((> x y) 1) (else 0)))
(define (jolt-strcmp a b) (cond ((string<? a b) -1.0) ((string>? a b) 1.0) (else 0.0))) (define (jolt-strcmp a b) (cond ((string<? a b) -1) ((string>? a b) 1) (else 0)))
(define (jolt-kw->string k) (define (jolt-kw->string k)
(let ((ns (keyword-t-ns k))) (if ns (string-append ns "/" (keyword-t-name k)) (keyword-t-name k)))) (let ((ns (keyword-t-ns k))) (if ns (string-append ns "/" (keyword-t-name k)) (keyword-t-name k))))
(define (jolt-sym-ns-string s) (define (jolt-sym-ns-string s)
(let ((n (symbol-t-ns s))) (if (or (jolt-nil? n) (not n) (eq? n '())) "" n))) (let ((n (symbol-t-ns s))) (if (or (jolt-nil? n) (not n) (eq? n '())) "" n)))
;; compare returns an EXACT integer -1/0/1 (= JVM compare -> int).
(define (jolt-compare a b) (define (jolt-compare a b)
(cond (cond
((and (jolt-nil? a) (jolt-nil? b)) 0.0) ((and (jolt-nil? a) (jolt-nil? b)) 0)
((jolt-nil? a) -1.0) ((jolt-nil? a) -1)
((jolt-nil? b) 1.0) ((jolt-nil? b) 1)
((and (number? a) (number? b)) (jolt-cmp3 a b)) ((and (number? a) (number? b)) (jolt-cmp3 a b))
((and (string? a) (string? b)) (jolt-strcmp a b)) ((and (string? a) (string? b)) (jolt-strcmp a b))
((and (keyword? a) (keyword? b)) (jolt-strcmp (jolt-kw->string a) (jolt-kw->string b))) ((and (keyword? a) (keyword? b)) (jolt-strcmp (jolt-kw->string a) (jolt-kw->string b)))
((and (jolt-symbol? a) (jolt-symbol? b)) ((and (jolt-symbol? a) (jolt-symbol? b))
(let ((r (jolt-strcmp (jolt-sym-ns-string a) (jolt-sym-ns-string b)))) (let ((r (jolt-strcmp (jolt-sym-ns-string a) (jolt-sym-ns-string b))))
(if (= r 0.0) (jolt-strcmp (symbol-t-name a) (symbol-t-name b)) r))) (if (= r 0) (jolt-strcmp (symbol-t-name a) (symbol-t-name b)) r)))
((and (boolean? a) (boolean? b)) (cond ((eq? a b) 0.0) ((eq? a #f) -1.0) (else 1.0))) ((and (boolean? a) (boolean? b)) (cond ((eq? a b) 0) ((eq? a #f) -1) (else 1)))
((and (char? a) (char? b)) (jolt-cmp3 (char->integer a) (char->integer b))) ((and (char? a) (char? b)) (jolt-cmp3 (char->integer a) (char->integer b)))
((and (pvec? a) (pvec? b)) ((and (pvec? a) (pvec? b))
(let ((la (pvec-count a)) (lb (pvec-count b))) (let ((la (pvec-count a)) (lb (pvec-count b)))
@ -119,9 +122,9 @@
(jolt-cmp3 la lb) (jolt-cmp3 la lb)
(let loop ((i 0)) (let loop ((i 0))
(if (>= i la) (if (>= i la)
0.0 0
(let ((r (jolt-compare (pvec-nth-d a i jolt-nil) (pvec-nth-d b i jolt-nil)))) (let ((r (jolt-compare (pvec-nth-d a i jolt-nil) (pvec-nth-d b i jolt-nil))))
(if (= r 0.0) (loop (+ i 1)) r))))))) (if (= r 0) (loop (+ i 1)) r)))))))
(else (error #f "compare: cannot compare these types" a b)))) (else (error #f "compare: cannot compare these types" a b))))
(def-var! "clojure.core" "str" jolt-str) (def-var! "clojure.core" "str" jolt-str)

View file

@ -7,9 +7,9 @@
;; *clojure-version* — a jolt map {:major 1 :minor 11 :incremental 0 :qualifier nil} ;; *clojure-version* — a jolt map {:major 1 :minor 11 :incremental 0 :qualifier nil}
;; (jolt is all-flonum, so the numbers are flonums). ;; (jolt is all-flonum, so the numbers are flonums).
(def-var! "clojure.core" "*clojure-version*" (def-var! "clojure.core" "*clojure-version*"
(jolt-hash-map (keyword #f "major") 1.0 (jolt-hash-map (keyword #f "major") 1
(keyword #f "minor") 11.0 (keyword #f "minor") 11
(keyword #f "incremental") 0.0 (keyword #f "incremental") 0
(keyword #f "qualifier") jolt-nil)) (keyword #f "qualifier") jolt-nil))
;; *unchecked-math* — jolt does no unchecked-math elision; the var reads false. ;; *unchecked-math* — jolt does no unchecked-math elision; the var reads false.

View file

@ -108,7 +108,8 @@
(error #f (string-append "No constructor for class " class)))))))) (error #f (string-append "No constructor for class " class))))))))
;; ---- coercion helpers ------------------------------------------------------- ;; ---- coercion helpers -------------------------------------------------------
(define (->num x) (exact->inexact x)) ;; numeric tower (jolt-n6al): currentTimeMillis/nanoTime are exact longs (JVM).
(define (->num x) x)
(define (jnum->exact n) (exact (truncate n))) (define (jnum->exact n) (exact (truncate n)))
;; parse an integer string in radix; #f on failure (matches the seed's scan-number) ;; parse an integer string in radix; #f on failure (matches the seed's scan-number)
(define (parse-int-str s radix) (define (parse-int-str s radix)
@ -291,7 +292,7 @@
(register-host-methods! "string-reader" (register-host-methods! "string-reader"
(list (cons "read" (lambda (self) (list (cons "read" (lambda (self)
(let ((s (sr-s self)) (p (sr-pos self))) (let ((s (sr-s self)) (p (sr-pos self)))
(if (>= p (string-length s)) -1.0 (if (>= p (string-length s)) -1 ; EOF -> exact int -1 (= JVM)
(begin (sr-pos! self (+ p 1)) (->num (char->integer (string-ref s p)))))))) (begin (sr-pos! self (+ p 1)) (->num (char->integer (string-ref s p))))))))
(cons "mark" (lambda (self . _) (vector-set! (jhost-state self) 2 (sr-pos self)) jolt-nil)) (cons "mark" (lambda (self . _) (vector-set! (jhost-state self) 2 (sr-pos self)) jolt-nil))
(cons "reset" (lambda (self) (sr-pos! self (vector-ref (jhost-state self) 2)) jolt-nil)) (cons "reset" (lambda (self) (sr-pos! self (vector-ref (jhost-state self) 2)) jolt-nil))

View file

@ -70,7 +70,7 @@
(define %h-seq jolt-seq) (define %h-seq jolt-seq)
(set! jolt-seq (lambda (x) (if (htable-sorted? x) (sc-call x kw-op-seq) (%h-seq x)))) (set! jolt-seq (lambda (x) (if (htable-sorted? x) (sc-call x kw-op-seq) (%h-seq x))))
(define %h-count jolt-count) (define %h-count jolt-count)
(set! jolt-count (lambda (coll) (if (htable-sorted? coll) (exact->inexact (sc-call coll kw-op-count)) (%h-count coll)))) (set! jolt-count (lambda (coll) (if (htable-sorted? coll) (sc-call coll kw-op-count) (%h-count coll))))
(define %h-get jolt-get) (define %h-get jolt-get)
(set! jolt-get (case-lambda (set! jolt-get (case-lambda
((coll k) (if (htable-sorted? coll) (sc-call coll kw-op-get k jolt-nil) (%h-get coll k))) ((coll k) (if (htable-sorted? coll) (sc-call coll kw-op-get k jolt-nil) (%h-get coll k)))

View file

@ -88,7 +88,7 @@
(else (fail))))) (else (fail)))))
(unless (= i len) (fail)) (unless (= i len) (fail))
(let ((base-s (+ (* (days-from-civil year month day) 86400) (* hh 3600) (* mm 60) ss))) (let ((base-s (+ (* (days-from-civil year month day) 86400) (* hh 3600) (* mm 60) ss)))
(make-jinst (exact->inexact (- (+ (* base-s 1000) frac-ms) (* off-s 1000))))))) (make-jinst (- (+ (* base-s 1000) frac-ms) (* off-s 1000))))))
;; --- canonical print form: yyyy-MM-ddThh:mm:ss.fff-00:00 (UTC) --------------- ;; --- canonical print form: yyyy-MM-ddThh:mm:ss.fff-00:00 (UTC) ---------------
(define (pad2 n) (if (< n 10) (string-append "0" (number->string n)) (number->string n))) (define (pad2 n) (if (< n 10) (string-append "0" (number->string n)) (number->string n)))
@ -223,7 +223,9 @@
(define (mk-local ms) (make-jhost "local-dt" (vector ms))) (define (mk-local ms) (make-jhost "local-dt" (vector ms)))
(define (mk-formatter pat) (make-jhost "dt-formatter" (vector pat))) (define (mk-formatter pat) (make-jhost "dt-formatter" (vector pat)))
(define (fmt-pat f) (vector-ref (jhost-state f) 0)) (define (fmt-pat f) (vector-ref (jhost-state f) 0))
(define (now-ms) (exact->inexact (now-millis))) ; now-millis from host-static.ss (define (now-ms) (now-millis)) ; exact ms (= JVM long); now-millis from host-static.ss
;; coerce a user-supplied ms (exact or flonum) to an exact integer for storage.
(define (ms->exact ms) (exact (round ms)))
(register-host-methods! "instant" (register-host-methods! "instant"
(list (cons "atZone" (lambda (self zone) (mk-zoned (ms-of self)))) (list (cons "atZone" (lambda (self zone) (mk-zoned (ms-of self))))
@ -262,7 +264,7 @@
(cons "ofLocalizedTime" (lambda (fs) (style-fmt 'time fs))) (cons "ofLocalizedTime" (lambda (fs) (style-fmt 'time fs)))
(cons "ofLocalizedDateTime" (lambda (fs) (style-fmt 'datetime fs))))) (cons "ofLocalizedDateTime" (lambda (fs) (style-fmt 'datetime fs)))))
(register-class-statics! "Instant" (register-class-statics! "Instant"
(list (cons "ofEpochMilli" (lambda (ms) (mk-instant (exact->inexact ms)))) (list (cons "ofEpochMilli" (lambda (ms) (mk-instant (ms->exact ms))))
(cons "now" (lambda () (mk-instant (now-ms)))))) (cons "now" (lambda () (mk-instant (now-ms))))))
(register-class-statics! "ZoneId" (register-class-statics! "ZoneId"
(list (cons "systemDefault" (lambda () (make-jhost "zone-id" (vector "system")))) (list (cons "systemDefault" (lambda () (make-jhost "zone-id" (vector "system"))))
@ -283,7 +285,7 @@
;; or (Date. another-date) -> a jinst (ms-of accepts a number / jinst / instant), so ;; or (Date. another-date) -> a jinst (ms-of accepts a number / jinst / instant), so
;; .getTime / inst? / instance? Date|Timestamp work. (jolt-dcmm) ;; .getTime / inst? / instance? Date|Timestamp work. (jolt-dcmm)
(define (date-ctor . args) (define (date-ctor . args)
(make-jinst (if (null? args) (now-ms) (exact->inexact (ms-of (car args)))))) (make-jinst (if (null? args) (now-ms) (ms->exact (ms-of (car args))))))
(register-class-ctor! "Date" date-ctor) (register-class-ctor! "Date" date-ctor)
(register-class-ctor! "java.util.Date" date-ctor) (register-class-ctor! "java.util.Date" date-ctor)
(register-class-ctor! "Timestamp" date-ctor) (register-class-ctor! "Timestamp" date-ctor)

View file

@ -22,7 +22,8 @@
(- (expt (- x) (/ 1.0 3.0))) (- (expt (- x) (/ 1.0 3.0)))
(expt x (/ 1.0 3.0)))) (expt x (/ 1.0 3.0))))
(define (jolt-math-round x) (floor (+ x 0.5))) ;; clojure.math/round returns a long (exact); floor/ceil/signum/rint return doubles.
(define (jolt-math-round x) (exact (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-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-degrees r) (/ (* r 180.0) jolt-math-pi))
(define (jolt-math-to-radians d) (/ (* d jolt-math-pi) 180.0)) (define (jolt-math-to-radians d) (/ (* d jolt-math-pi) 180.0))

View file

@ -17,9 +17,9 @@
(make-jolt-array (make-vector (exact (na-idx a)) (if (pair? rest) (car rest) init)) kind) (make-jolt-array (make-vector (exact (na-idx a)) (if (pair? rest) (car rest) init)) kind)
(na-from-seq a kind))) (na-from-seq a kind)))
;; jolt numbers are flonums — array element defaults / masked bytes / count must be ;; numeric tower (jolt-n6al): array element defaults / masked bytes / count are
;; INEXACT or jolt's exactness-aware = fails (= 3.0 (exact 3) -> false). ;; EXACT integers (= JVM byte/short/int), matching exact integer literals.
(define (na-byte-of v) (exact->inexact (bitwise-and (exact (floor v)) #xff))) (define (na-byte-of v) (bitwise-and (exact (floor v)) #xff))
;; --- constructors ----------------------------------------------------------- ;; --- constructors -----------------------------------------------------------
(define (na-object-array a . rest) (na-num-array a rest jolt-nil 'object)) (define (na-object-array a . rest) (na-num-array a rest jolt-nil 'object))
@ -60,9 +60,9 @@
(define (na-bytes? x) (and (jolt-array? x) (eq? (jolt-array-kind x) 'byte))) (define (na-bytes? x) (and (jolt-array? x) (eq? (jolt-array-kind x) 'byte)))
(define (na-identity x) x) (define (na-identity x) x)
(define (na-byte x) (define (na-byte x)
(let ((b (bitwise-and (exact (floor x)) #xff))) (exact->inexact (if (>= b 128) (- b 256) b)))) (let ((b (bitwise-and (exact (floor x)) #xff))) (if (>= b 128) (- b 256) b)))
(define (na-short x) (define (na-short x)
(let ((s (bitwise-and (exact (floor x)) #xffff))) (exact->inexact (if (>= s #x8000) (- s #x10000) s)))) (let ((s (bitwise-and (exact (floor x)) #xffff))) (if (>= s #x8000) (- s #x10000) s)))
;; --- chunked seqs (Jolt does not chunk; eager equivalents over a buffer) ----- ;; --- chunked seqs (Jolt does not chunk; eager equivalents over a buffer) -----
(define-record-type jolt-chunkbuf (fields (mutable items)) (nongenerative jolt-chunkbuf-v1)) (define-record-type jolt-chunkbuf (fields (mutable items)) (nongenerative jolt-chunkbuf-v1))
@ -76,7 +76,7 @@
;; --- extend the collection dispatchers to see a jolt-array ------------------ ;; --- extend the collection dispatchers to see a jolt-array ------------------
(define %na-count jolt-count) (define %na-count jolt-count)
(set! jolt-count (lambda (c) (if (jolt-array? c) (exact->inexact (vector-length (jolt-array-vec c))) (%na-count c)))) (set! jolt-count (lambda (c) (if (jolt-array? c) (vector-length (jolt-array-vec c)) (%na-count c))))
(define %na-seq jolt-seq) (define %na-seq jolt-seq)
(set! jolt-seq (lambda (c) (if (jolt-array? c) (list->cseq (vector->list (jolt-array-vec c))) (%na-seq c)))) (set! jolt-seq (lambda (c) (if (jolt-array? c) (list->cseq (vector->list (jolt-array-vec c))) (%na-seq c))))
(define %na-nth jolt-nth) (define %na-nth jolt-nth)

View file

@ -4,24 +4,25 @@
;; to an exact integer, operate, and return a flonum. parse-* match the seed's ;; to an exact integer, operate, and return a flonum. parse-* match the seed's
;; strict shapes (Clojure 1.11: nil on malformed, throw on a non-string). ;; strict shapes (Clojure 1.11: nil on malformed, throw on a non-string).
;; bit ops return EXACT integers (= JVM long). ->int coerces the operand.
(define (->int x) (exact (truncate x))) (define (->int x) (exact (truncate x)))
(define (jolt-bit-and a b) (exact->inexact (bitwise-and (->int a) (->int b)))) (define (jolt-bit-and a b) (bitwise-and (->int a) (->int b)))
(define (jolt-bit-or a b) (exact->inexact (bitwise-ior (->int a) (->int b)))) (define (jolt-bit-or a b) (bitwise-ior (->int a) (->int b)))
(define (jolt-bit-xor a b) (exact->inexact (bitwise-xor (->int a) (->int b)))) (define (jolt-bit-xor a b) (bitwise-xor (->int a) (->int b)))
(define (jolt-bit-and-not a b) (exact->inexact (bitwise-and (->int a) (bitwise-not (->int b))))) (define (jolt-bit-and-not a b) (bitwise-and (->int a) (bitwise-not (->int b))))
(define (jolt-bit-not a) (exact->inexact (bitwise-not (->int a)))) (define (jolt-bit-not a) (bitwise-not (->int a)))
(define (jolt-bit-shift-left x n) (exact->inexact (bitwise-arithmetic-shift-left (->int x) (->int n)))) (define (jolt-bit-shift-left x n) (bitwise-arithmetic-shift-left (->int x) (->int n)))
(define (jolt-bit-shift-right x n) (exact->inexact (bitwise-arithmetic-shift-right (->int x) (->int n)))) (define (jolt-bit-shift-right x n) (bitwise-arithmetic-shift-right (->int x) (->int n)))
(define (bit-mask n) (bitwise-arithmetic-shift-left 1 (->int n))) (define (bit-mask n) (bitwise-arithmetic-shift-left 1 (->int n)))
(define (jolt-bit-set x n) (exact->inexact (bitwise-ior (->int x) (bit-mask n)))) (define (jolt-bit-set x n) (bitwise-ior (->int x) (bit-mask n)))
(define (jolt-bit-clear x n) (exact->inexact (bitwise-and (->int x) (bitwise-not (bit-mask n))))) (define (jolt-bit-clear x n) (bitwise-and (->int x) (bitwise-not (bit-mask n))))
(define (jolt-bit-flip x n) (exact->inexact (bitwise-xor (->int x) (bit-mask n)))) (define (jolt-bit-flip x n) (bitwise-xor (->int x) (bit-mask n)))
(define (jolt-bit-test x n) (not (zero? (bitwise-and (->int x) (bit-mask n))))) (define (jolt-bit-test x n) (not (zero? (bitwise-and (->int x) (bit-mask n)))))
;; unsigned-bit-shift-right: logical shift over 64-bit longs. For the common ;; unsigned-bit-shift-right: logical shift over 64-bit longs. For the common
;; non-negative operand it equals the arithmetic shift; the negative-operand ;; non-negative operand it equals the arithmetic shift; the negative-operand
;; 64-bit-window case is not modeled (no fixed-width longs on the all-flonum side). ;; 64-bit-window case is not modeled.
(define (jolt-unsigned-bit-shift-right x n) (define (jolt-unsigned-bit-shift-right x n)
(exact->inexact (bitwise-arithmetic-shift-right (->int x) (->int n)))) (bitwise-arithmetic-shift-right (->int x) (->int n)))
;; ---- string->scalar parsers ------------------------------------------------- ;; ---- string->scalar parsers -------------------------------------------------
(define (ascii-digit? c) (and (char>=? c #\0) (char<=? c #\9))) (define (ascii-digit? c) (and (char>=? c #\0) (char<=? c #\9)))
@ -34,7 +35,7 @@
(define (jolt-parse-long s) (define (jolt-parse-long s)
(if (not (string? s)) (error #f "parse-long requires a string" s) (if (not (string? s)) (error #f "parse-long requires a string" s)
(if (parse-long-shape? s) (exact->inexact (string->number s)) jolt-nil))) (if (parse-long-shape? s) (string->number s) jolt-nil))) ; exact long
;; strict float shape: [+-]? ( D+ (. D*)? | . D+ ) ([eE][+-]? D+)? fully anchored. ;; strict float shape: [+-]? ( D+ (. D*)? | . D+ ) ([eE][+-]? D+)? fully anchored.
(define (parse-double-shape? s) (define (parse-double-shape? s)

View file

@ -91,18 +91,17 @@
((string=? method "toLowerCase") (ascii-string-down s)) ((string=? method "toLowerCase") (ascii-string-down s))
((string=? method "toUpperCase") (ascii-string-up s)) ((string=? method "toUpperCase") (ascii-string-up s))
((string=? method "trim") (str-trim s)) ((string=? method "trim") (str-trim s))
((string=? method "length") (exact->inexact (string-length s))) ((string=? method "length") (string-length s)) ; exact int (= JVM)
((string=? method "isEmpty") (fx=? (string-length s) 0)) ((string=? method "isEmpty") (fx=? (string-length s) 0))
((string=? method "charAt") (string-ref s (jolt->idx (arg 0)))) ((string=? method "charAt") (string-ref s (jolt->idx (arg 0))))
((string=? method "substring") ((string=? method "substring")
(substring s (jolt->idx (arg 0)) (substring s (jolt->idx (arg 0))
(if (fx>? (length rest) 1) (jolt->idx (arg 1)) (string-length s)))) (if (fx>? (length rest) 1) (jolt->idx (arg 1)) (string-length s))))
((string=? method "indexOf") ((string=? method "indexOf")
(exact->inexact (str-index-of s (str-needle (arg 0))
(str-index-of s (str-needle (arg 0)) (if (fx>? (length rest) 1) (jolt->idx (arg 1)) 0)))
(if (fx>? (length rest) 1) (jolt->idx (arg 1)) 0))))
((string=? method "lastIndexOf") ((string=? method "lastIndexOf")
(exact->inexact (str-last-index-of s (str-needle (arg 0))))) (str-last-index-of s (str-needle (arg 0))))
((string=? method "startsWith") ((string=? method "startsWith")
(let ((p (arg 0))) (and (fx>=? (string-length s) (string-length p)) (let ((p (arg 0))) (and (fx>=? (string-length s) (string-length p))
(string=? (substring s 0 (string-length p)) p)))) (string=? (substring s 0 (string-length p)) p))))
@ -153,10 +152,10 @@
(define (str-lower s) (ascii-string-down s)) (define (str-lower s) (ascii-string-down s))
(define (str-reverse-b s) (list->string (reverse (string->list s)))) (define (str-reverse-b s) (list->string (reverse (string->list s))))
;; (str-find needle haystack) -> flonum index of first occurrence, or nil. ;; (str-find needle haystack) -> exact int index of first occurrence, or nil.
(define (str-find needle s) (define (str-find needle s)
(let ((i (str-index-of s needle 0))) (let ((i (str-index-of s needle 0)))
(if (fx<? i 0) jolt-nil (exact->inexact i)))) (if (fx<? i 0) jolt-nil i)))
;; (str-join coll [sep]) -> stringify each element (Clojure str), join by sep. ;; (str-join coll [sep]) -> stringify each element (Clojure str), join by sep.
(define (str-join coll . opt) (define (str-join coll . opt)

View file

@ -80,3 +80,17 @@
(def-var! "clojure.core" "line-seq" (def-var! "clojure.core" "line-seq"
(lambda (rdr) (lambda (rdr)
(if (reader-jhost? rdr) (chez-line-seq rdr) (jolt-invoke overlay-line-seq rdr))))) (if (reader-jhost? rdr) (chez-line-seq rdr) (jolt-invoke overlay-line-seq rdr)))))
;; JVM-parity numeric tower (jolt-n6al): the overlay (20-coll.clj) carries an
;; all-flonum number-predicate web with no Ratio concept (ratio? -> false,
;; double? -> not-integer, float? -> double?, rational? -> int?), which
;; misclassifies exact rationals on the Chez tower (e.g. (double? 1/2) -> true).
;; Re-assert the native tower-correct versions (predicates.ss) so they win over
;; the overlay defs. int?/double? alias integer?/float?. == is value-equality.
(def-var! "clojure.core" "integer?" jolt-integer?)
(def-var! "clojure.core" "int?" jolt-integer?)
(def-var! "clojure.core" "float?" jolt-float?)
(def-var! "clojure.core" "double?" jolt-float?)
(def-var! "clojure.core" "ratio?" jolt-ratio?)
(def-var! "clojure.core" "rational?" jolt-rational?)
(def-var! "clojure.core" "decimal?" jolt-decimal?)
(def-var! "clojure.core" "==" jolt-num-equiv)

View file

@ -22,8 +22,16 @@
(define (jolt-number? x) (number? x)) (define (jolt-number? x) (number? x))
(define (jolt-string? x) (string? x)) (define (jolt-string? x) (string? x))
(define (jolt-char-pred? x) (char? x)) (define (jolt-char-pred? x) (char? x))
;; finite integral number — Chez integer? already rejects the infinities and NaN. ;; JVM-parity number-type predicates over the Chez numeric tower. integer? is the
(define (jolt-integer? x) (and (number? x) (integer? x))) ;; INTEGER TYPE (exact integer = Long/BigInt), NOT integer-VALUED: (integer? 3.0)
;; is false on the JVM (3.0 is a Double). float? = flonum (double). ratio? = exact
;; non-integer (= JVM Ratio). rational? = exact (integer or ratio; jolt has no
;; BigDecimal). decimal? is always false (no BigDecimal type).
(define (jolt-integer? x) (and (number? x) (exact? x) (integer? x)))
(define (jolt-float? x) (and (number? x) (flonum? x)))
(define (jolt-ratio? x) (and (number? x) (exact? x) (rational? x) (not (integer? x))))
(define (jolt-rational? x) (and (number? x) (exact? x)))
(define (jolt-decimal? x) #f)
(define (jolt-fn? x) (procedure? x)) (define (jolt-fn? x) (procedure? x))
(define (jolt-boolean-pred? x) (boolean? x)) (define (jolt-boolean-pred? x) (boolean? x))
@ -50,6 +58,20 @@
(def-var! "clojure.core" "string?" jolt-string?) (def-var! "clojure.core" "string?" jolt-string?)
(def-var! "clojure.core" "char?" jolt-char-pred?) (def-var! "clojure.core" "char?" jolt-char-pred?)
(def-var! "clojure.core" "integer?" jolt-integer?) (def-var! "clojure.core" "integer?" jolt-integer?)
(def-var! "clojure.core" "float?" jolt-float?)
(def-var! "clojure.core" "ratio?" jolt-ratio?)
(def-var! "clojure.core" "rational?" jolt-rational?)
(def-var! "clojure.core" "decimal?" jolt-decimal?)
;; == numeric value-equality (ignores exactness, unlike =): (== 3 3.0) -> true.
;; 1-arity is trivially true; 2+ args must all be numbers (Numbers.equiv throws
;; otherwise). Uses Scheme = (value across the tower), not jolt= (category-aware).
(define (jolt-num-equiv . xs)
(let all-num? ((ys xs))
(cond
((null? ys) (or (null? xs) (null? (cdr xs)) (apply = xs)))
((number? (car ys)) (all-num? (cdr ys)))
(else (error #f "== requires numbers" xs)))))
(def-var! "clojure.core" "==" jolt-num-equiv)
(def-var! "clojure.core" "symbol?" jolt-symbol?) (def-var! "clojure.core" "symbol?" jolt-symbol?)
(def-var! "clojure.core" "keyword?" keyword?) (def-var! "clojure.core" "keyword?" keyword?)
(def-var! "clojure.core" "map?" jolt-map?) (def-var! "clojure.core" "map?" jolt-map?)

View file

@ -73,9 +73,13 @@
;; jolt models EVERY number as a double (emit-const lowers integer literals to ;; jolt models EVERY number as a double (emit-const lowers integer literals to
;; flonums too), so the reader coerces every parsed number to inexact — else a ;; flonums too), so the reader coerces every parsed number to inexact — else a
;; read int (exact) is not jolt= to a source int literal (flonum). ;; read int (exact) is not jolt= to a source int literal (flonum).
;; Preserve exactness for the Chez numeric tower (JVM parity): integer literals
;; read as exact integers (= Long/BigInt, arbitrary precision), a/b ratios as
;; exact rationals (= Ratio), and decimal/exponent literals as flonums (= double).
;; Only the zero-Janet path (this reader) carries exactness through to runtime;
;; the Janet-reader prelude path stays all-flonum (Janet has only doubles).
(define (rdr-try-number tok) (define (rdr-try-number tok)
(let ((raw (rdr-try-number-raw tok))) (rdr-try-number-raw tok))
(and raw (exact->inexact raw))))
(define (rdr-try-number-raw tok) (define (rdr-try-number-raw tok)
(let ((len (string-length tok))) (let ((len (string-length tok)))
@ -108,12 +112,13 @@
(blen (string-length body)) (blen (string-length body))
(slash (rdr-string-index-char body #\/))) (slash (rdr-string-index-char body #\/)))
(cond (cond
;; ratio a/b -> flonum (the seed has no exact ratios) ;; ratio a/b -> exact rational (= JVM Ratio); reduces to an exact integer
;; when d divides n.
(slash (slash
(let ((n (string->number (substring body 0 slash))) (let ((n (string->number (substring body 0 slash)))
(d (string->number (substring body (+ slash 1) blen)))) (d (string->number (substring body (+ slash 1) blen))))
(and (integer? n) (integer? d) (not (= d 0)) (and (integer? n) (integer? d) (not (= d 0))
(* sign (exact->inexact (/ n d)))))) (* sign (/ n d)))))
;; hex 0x.. ;; hex 0x..
((and (>= blen 2) (char=? (string-ref body 0) #\0) ((and (>= blen 2) (char=? (string-ref body 0) #\0)
(or (char=? (string-ref body 1) #\x) (char=? (string-ref body 1) #\X))) (or (char=? (string-ref body 1) #\x) (char=? (string-ref body 1) #\X)))
@ -137,9 +142,8 @@
(and n (exact->inexact (* sign n))))) (and n (exact->inexact (* sign n)))))
(else (else
(let ((n (string->number tok))) ; tok carries its own sign (let ((n (string->number tok))) ; tok carries its own sign
(and (number? n) (real? n) ;; keep exactness: "42" -> exact int, "3.14"/"1e3" -> flonum.
;; never surface an exact non-integer ratio (and (number? n) (real? n) n))))))
(if (and (exact? n) (not (integer? n))) (exact->inexact n) n)))))))
;; --- string / char literals ------------------------------------------------- ;; --- string / char literals -------------------------------------------------
(define (rdr-hex->int s i n) ; n hex digits at i -> (values int j) (define (rdr-hex->int s i n) ; n hex digits at i -> (values int j)

View file

@ -66,7 +66,7 @@
((coll k) (if (jrec? coll) (jrec-lookup coll k jolt-nil) (%r-jolt-get coll k))) ((coll k) (if (jrec? coll) (jrec-lookup coll k jolt-nil) (%r-jolt-get coll k)))
((coll k d) (if (jrec? coll) (jrec-lookup coll k d) (%r-jolt-get coll k d))))) ((coll k d) (if (jrec? coll) (jrec-lookup coll k d) (%r-jolt-get coll k d)))))
(define %r-jolt-count jolt-count) (define %r-jolt-count jolt-count)
(set! jolt-count (lambda (coll) (if (jrec? coll) (exact->inexact (length (jrec-pairs coll))) (%r-jolt-count coll)))) (set! jolt-count (lambda (coll) (if (jrec? coll) (length (jrec-pairs coll)) (%r-jolt-count coll))))
(define %r-jolt-contains? jolt-contains?) (define %r-jolt-contains? jolt-contains?)
(set! jolt-contains? (lambda (coll k) (if (jrec? coll) (jrec-has? coll k) (%r-jolt-contains? coll k)))) (set! jolt-contains? (lambda (coll k) (if (jrec? coll) (jrec-has? coll k) (%r-jolt-contains? coll k))))
(define %r-jolt-assoc1 jolt-assoc1) (define %r-jolt-assoc1 jolt-assoc1)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -113,14 +113,15 @@
((fx=? i 0) (seq-first s)) ((fx=? i 0) (seq-first s))
(else (loop (jolt-seq (seq-more s)) (fx- i 1))))))) (else (loop (jolt-seq (seq-more s)) (fx- i 1)))))))
;; value-position arithmetic: jolt models every number as a double, so the ;; value-position arithmetic (the higher-order forms: (reduce + []), (apply * xs)).
;; higher-order forms ((reduce + []), (apply * xs)) must coerce — Scheme's (+)/(*) ;; Scheme's +/-/*// already implement the JVM-parity numeric tower: exact+exact ->
;; identities are EXACT 0/1, which aren't jolt= to the double 0.0/1.0. The hot ;; exact, exact/exact -> Ratio, any flonum -> flonum. Identities (+)=0 / (*)=1 are
;; path uses the inlined native ops, not these. ;; exact, matching exact integer arithmetic. The hot path uses the inlined native
(define (jolt-add . xs) (exact->inexact (apply + xs))) ;; ops, not these.
(define (jolt-sub . xs) (exact->inexact (apply - xs))) (define (jolt-add . xs) (apply + xs))
(define (jolt-mul . xs) (exact->inexact (apply * xs))) (define (jolt-sub . xs) (apply - xs))
(define (jolt-div . xs) (exact->inexact (apply / xs))) (define (jolt-mul . xs) (apply * xs))
(define (jolt-div . xs) (apply / xs))
;; ============================================================================ ;; ============================================================================
;; IFn dispatch — the dynamic "value as fn" fallback. A callee that the emitter ;; IFn dispatch — the dynamic "value as fn" fallback. A callee that the emitter
@ -181,16 +182,18 @@
(define (jolt-into to from) (reduce-seq (lambda (acc x) (jolt-conj1 acc x)) to (jolt-seq from))) (define (jolt-into to from) (reduce-seq (lambda (acc x) (jolt-conj1 acc x)) to (jolt-seq from)))
(define (range-from n) (cseq-lazy n (lambda () (range-from (+ n 1.0))))) (define (range-from n) (cseq-lazy n (lambda () (range-from (+ n 1)))))
(define (range-bounded n end step) (define (range-bounded n end step)
(if (if (> step 0.0) (< n end) (> n end)) (if (if (> step 0.0) (< n end) (> n end))
(cseq-lazy n (lambda () (range-bounded (+ n step) end step))) (cseq-lazy n (lambda () (range-bounded (+ n step) end step)))
jolt-nil)) jolt-nil))
;; numeric tower (jolt-n6al): exact 0/1 defaults so (range 3) yields exact ints
;; (= JVM longs); flonum args still produce flonums (Scheme arithmetic preserves).
(define jolt-range (define jolt-range
(case-lambda (case-lambda
(() (range-from 0.0)) (() (range-from 0))
((end) (range-bounded 0.0 end 1.0)) ((end) (range-bounded 0 end 1))
((start end) (range-bounded start end 1.0)) ((start end) (range-bounded start end 1))
((start end step) (range-bounded start end step)))) ((start end step) (range-bounded start end step))))
(define (jolt-take n coll) (define (jolt-take n coll)

View file

@ -133,16 +133,18 @@
(cond (cond
(nil? v) "jolt-nil" (nil? v) "jolt-nil"
(boolean? v) (if v "#t" "#f") (boolean? v) (if v "#t" "#f")
;; jolt models every number as a double. Emit flonums so arithmetic matches ;; Numeric tower (jolt-n6al): emit a literal Chez re-reads as the SAME number.
;; the Janet host and Chez doesn't fall into exploding exact rationals. ;; Exact integers -> "42", exact ratios -> "1/2" (str renders both faithfully);
;; ##Inf/##-Inf/##NaN -> Chez's flonum literals (Janet stringifies them as ;; a flonum must carry a decimal point/exponent or Chez reads it back as exact,
;; inf/-inf/nan, unbound symbols in Chez). ;; so a whole flonum (str drops its .0) gets ".0" appended. ##Inf/##-Inf/##NaN
;; -> Chez's flonum literals.
(number? v) (cond (number? v) (cond
(= v ##Inf) "+inf.0" (= v ##Inf) "+inf.0"
(= v ##-Inf) "-inf.0" (= v ##-Inf) "-inf.0"
(not= v v) "+nan.0" (not= v v) "+nan.0"
:else (let [s (str v)] (float? v) (let [s (str v)]
(if (or (str/includes? s ".") (str/includes? s "e")) s (str s ".0")))) (if (or (str/includes? s ".") (str/includes? s "e")) s (str s ".0")))
:else (str v))
(string? v) (chez-str-lit v) (string? v) (chez-str-lit v)
;; keyword literal -> (keyword ns name) ;; keyword literal -> (keyword ns name)
(keyword? v) (if-let [kns (namespace v)] (keyword? v) (if-let [kns (namespace v)]
@ -310,9 +312,9 @@
kind (ifn-kind fnode) kind (ifn-kind fnode)
default (if (> (count args) 1) (str " " (nth args 1)) "")] default (if (> (count args) 1) (str " " (nth args 1)) "")]
(cond (cond
;; zero-arg + / * : flonum identity to keep the all-double model. ;; zero-arg + / * : exact integer identity (= JVM long: (+) -> 0, (*) -> 1).
(and nop (empty? args) (= nop "+")) "0.0" (and nop (empty? args) (= nop "+")) "0"
(and nop (empty? args) (= nop "*")) "1.0" (and nop (empty? args) (= nop "*")) "1"
(and nop (= 1 (count args)) (cmp1-ops nop)) (str "(begin " (first args) " #t)") (and nop (= 1 (count args)) (cmp1-ops nop)) (str "(begin " (first args) " #t)")
nop (str "(" nop " " (str/join " " args) ")") nop (str "(" nop " " (str/join " " args) ")")
;; (:k coll [default]) -> (jolt-get coll :k [default]) ;; (:k coll [default]) -> (jolt-get coll :k [default])

View file

@ -14,9 +14,12 @@
[# --- scalars + collections (value equality, order-independent) --- [# --- scalars + collections (value equality, order-independent) ---
["(= 42 (read-string \"42\"))" "true"] ["(= 42 (read-string \"42\"))" "true"]
["(= 42 (read-string \"0x2A\"))" "true"] ["(= 42 (read-string \"0x2A\"))" "true"]
["(= 0.5 (read-string \"1/2\"))" "true"] # numeric tower (jolt-n6al): "1/2" reads as an exact Ratio (= JVM), so a
# category-aware = against the double 0.5 is false; assert numeric value (==),
# which holds in both the all-flonum and tower models.
["(== 0.5 (read-string \"1/2\"))" "true"]
["(= -3.5 (read-string \"-3.5\"))" "true"] ["(= -3.5 (read-string \"-3.5\"))" "true"]
["(= 1000.0 (read-string \"1e3\"))" "true"] ["(== 1000.0 (read-string \"1e3\"))" "true"]
["(= 1 (read-string \"1N\"))" "true"] ["(= 1 (read-string \"1N\"))" "true"]
["(integer? (read-string \"7\"))" "true"] ["(integer? (read-string \"7\"))" "true"]
["(= :foo (read-string \":foo\"))" "true"] ["(= :foo (read-string \":foo\"))" "true"]

View file

@ -110,7 +110,27 @@
# unquoted value forms eval in hash order, not source order — a minor ordering # unquoted value forms eval in hash order, not source order — a minor ordering
# gap (pmap has no insertion order; the reader's source-order table doesn't cover # gap (pmap has no insertion order; the reader's source-order table doesn't cover
# syntax-quote-built maps). 1 case; the non-syntax-quote map-order cases pass. # syntax-quote-built maps). 1 case; the non-syntax-quote map-order cases pass.
"source order through syntax-quote" true}) "source order through syntax-quote" true
# numeric tower (jolt-n6al): the zero-Janet path now carries the Chez numeric
# tower (exact ints / Ratio / double) = JVM parity, while the corpus :expected
# is the all-flonum Janet value (the Janet host has only doubles). So Chez gives
# the JVM value and the Janet-era corpus diverges — flipped to JVM at Phase 5
# (jolt-ecz0), these stay allowlisted during the transition. Each verified
# Chez == reference JVM Clojure.
"divide to fraction" true # (/ 1 2) -> 1/2 (JVM Ratio), corpus 0.5
"ratio 3/4" true # 3/4 literal -> Ratio
"neg ratio" true # -1/2 -> Ratio
"ratio -> double" true # ratio result, corpus double
"/ ratio-as-double" true
"native unary div" true # (/ 2) -> 1/2
"double" true # (double 3) -> 3.0 (double != exact 3)
"doubles" true "floats" true
"unchecked-double" true "unchecked-float" true
"floor" true # clojure.math/floor -> 2.0 (JVM double)
"signum" true # clojure.math/signum -> -1.0 (JVM double)
"Math/sqrt" true # -> 3.0 (double)
"Math/pow" true # -> 8.0 (double)
"bigdec int M" true}) # 1M: no BigDecimal type on Chez (documented gap)
# Cases that BLOCK forever on a shared-heap / JVM host (profile.edn :bucket # Cases that BLOCK forever on a shared-heap / JVM host (profile.edn :bucket
# :timeout) — skip them, like :throws: a single hung case would stall the whole # :timeout) — skip them, like :throws: a single hung case would stall the whole
@ -226,7 +246,12 @@
# date/time (Date/Timestamp/SimpleDateFormat/TimeZone) + clojure.edn/read over a # date/time (Date/Timestamp/SimpleDateFormat/TimeZone) + clojure.edn/read over a
# reader -> 2692; STM stub + portable line-seq -> 2696; realized? on a lazy-seq + # reader -> 2692; STM stub + portable line-seq -> 2696; realized? on a lazy-seq +
# conj! 1-arity (transducer-completion identity) -> 2698. # conj! 1-arity (transducer-completion identity) -> 2698.
(def base-floor (scan-number (or (os/getenv "JOLT_CHEZ_ZJ_FLOOR") "2698"))) # numeric tower (jolt-n6al): 2698 -> 2682. ~16 numeric cases that the all-flonum
# model coincidentally passed (corpus :expected is the Janet double value) now give
# the JVM tower value (1/2, 3.0, ...) and are reclassified as known tower
# divergences (Chez == reference JVM). The floor rises back when the corpus flips
# to JVM values (jolt-ecz0, Phase 5).
(def base-floor (scan-number (or (os/getenv "JOLT_CHEZ_ZJ_FLOOR") "2682")))
(def floor (if (os/getenv "JOLT_CORPUS_LIMIT") 0 base-floor)) (def floor (if (os/getenv "JOLT_CORPUS_LIMIT") 0 base-floor))
(when (or (> (length diverged) 0) (< pass floor)) (when (or (> (length diverged) 0) (< pass floor))
(printf "REGRESSION: pass %d < floor %d or %d new divergence(s)" pass floor (length diverged))) (printf "REGRESSION: pass %d < floor %d or %d new divergence(s)" pass floor (length diverged)))