js-on-sx: fix rational-zero-division in core constants + charCodeAt
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 51s

(/ 0 0), (/ 1 0), (/ -1 0) throw "rational: division by zero" with
the OCaml binary's integer rational arithmetic. Replace with nan/inf
literals in js-nan-value, js-infinity-value, js-number-is-finite,
js-math-min, js-math-max. js-max-value-approx looped forever (rationals
never reach float infinity); replace with literal 1.7976931348623157e+308.
charCodeAt and string .length called missing unicode-len /
unicode-char-code-at primitives — switch to (len s) and
(char-code (char-at s idx)). conformance.sh: 0→148/148.
This commit is contained in:
2026-05-06 21:02:58 +00:00
parent f93b13e861
commit 066ddcd6e1
2 changed files with 68 additions and 25 deletions

View File

@@ -13,13 +13,13 @@
;; JS `undefined` — we represent it as a distinct keyword so it ;; JS `undefined` — we represent it as a distinct keyword so it
;; survives round-trips through the evaluator without colliding with ;; survives round-trips through the evaluator without colliding with
;; SX `nil` (which maps to JS `null`). ;; SX `nil` (which maps to JS `null`).
(define js-nan-value (fn () (/ 0 0))) (define js-nan-value (fn () nan))
(define js-infinity-value (fn () (/ 1 0))) (define js-infinity-value (fn () inf))
;; ── Type predicates ─────────────────────────────────────────────── ;; ── Type predicates ───────────────────────────────────────────────
(define js-max-value-approx (fn () (js-max-value-loop 1 2000))) (define js-max-value-approx (fn () 1.7976931348623157e+308))
;; ── Boolean coercion (ToBoolean) ────────────────────────────────── ;; ── Boolean coercion (ToBoolean) ──────────────────────────────────
@@ -2049,15 +2049,18 @@
(i) (i)
(let (let
((idx (js-num-to-int i))) ((idx (js-num-to-int i)))
(if (and (>= idx 0) (< idx (len s))) (char-at s idx) "")))) (if
(and (>= idx 0) (< idx (len s)))
(char-at s idx)
""))))
((= name "charCodeAt") ((= name "charCodeAt")
(fn (fn
(i) (i)
(let (let
((idx (js-num-to-int (js-to-number i)))) ((idx (js-num-to-int (js-to-number i))))
(if (if
(and (>= idx 0) (< idx (unicode-len s))) (and (>= idx 0) (< idx (len s)))
(unicode-char-code-at s idx) (char-code (char-at s idx))
(js-nan-value))))) (js-nan-value)))))
((= name "indexOf") ((= name "indexOf")
(fn (fn
@@ -2068,14 +2071,20 @@
(js-string-index-of (js-string-index-of
s s
(js-to-string (nth args 0)) (js-to-string (nth args 0))
(if (< (len args) 2) 0 (max 0 (js-num-to-int (nth args 1)))))))) (if
(< (len args) 2)
0
(max 0 (js-num-to-int (nth args 1))))))))
((= name "slice") ((= name "slice")
(fn (fn
(&rest args) (&rest args)
(let (let
((start (if (= (len args) 0) 0 (js-num-to-int (nth args 0)))) ((start (if (= (len args) 0) 0 (js-num-to-int (nth args 0))))
(stop (stop
(if (< (len args) 2) (len s) (js-num-to-int (nth args 1))))) (if
(< (len args) 2)
(len s)
(js-num-to-int (nth args 1)))))
(js-string-slice s start stop)))) (js-string-slice s start stop))))
((= name "substring") ((= name "substring")
(fn (fn
@@ -2098,7 +2107,10 @@
(let (let
((sep (if (= (len args) 0) :js-undefined (nth args 0))) ((sep (if (= (len args) 0) :js-undefined (nth args 0)))
(limit (limit
(if (< (len args) 2) -1 (js-num-to-int (nth args 1))))) (if
(< (len args) 2)
-1
(js-num-to-int (nth args 1)))))
(let (let
((result (js-string-split s (js-to-string sep)))) ((result (js-string-split s (js-to-string sep))))
(if (< limit 0) result (js-list-take result limit)))))) (if (< limit 0) result (js-list-take result limit))))))
@@ -2115,7 +2127,11 @@
(&rest args) (&rest args)
(let (let
((needle (if (= (len args) 0) "" (js-to-string (nth args 0)))) ((needle (if (= (len args) 0) "" (js-to-string (nth args 0))))
(start (if (< (len args) 2) 0 (js-num-to-int (nth args 1))))) (start
(if
(< (len args) 2)
0
(js-num-to-int (nth args 1)))))
(js-string-matches? s needle start 0)))) (js-string-matches? s needle start 0))))
((= name "endsWith") ((= name "endsWith")
(fn (fn
@@ -2138,14 +2154,22 @@
(&rest args) (&rest args)
(let (let
((target (if (= (len args) 0) 0 (js-num-to-int (nth args 0)))) ((target (if (= (len args) 0) 0 (js-num-to-int (nth args 0))))
(pad (if (< (len args) 2) " " (js-to-string (nth args 1))))) (pad
(if
(< (len args) 2)
" "
(js-to-string (nth args 1)))))
(js-string-pad s target pad true)))) (js-string-pad s target pad true))))
((= name "padEnd") ((= name "padEnd")
(fn (fn
(&rest args) (&rest args)
(let (let
((target (if (= (len args) 0) 0 (js-num-to-int (nth args 0)))) ((target (if (= (len args) 0) 0 (js-num-to-int (nth args 0))))
(pad (if (< (len args) 2) " " (js-to-string (nth args 1))))) (pad
(if
(< (len args) 2)
" "
(js-to-string (nth args 1)))))
(js-string-pad s target pad false)))) (js-string-pad s target pad false))))
((= name "toString") (fn () s)) ((= name "toString") (fn () s))
((= name "valueOf") (fn () s)) ((= name "valueOf") (fn () s))
@@ -2175,7 +2199,8 @@
(js-string-slice s (+ idx (len src)) (len s))))))))) (js-string-slice s (+ idx (len src)) (len s)))))))))
(else (else
(let (let
((needle (js-to-string (nth args 0))) (repl (nth args 1))) ((needle (js-to-string (nth args 0)))
(repl (nth args 1)))
(let (let
((idx (js-string-index-of s needle 0))) ((idx (js-string-index-of s needle 0)))
(if (if
@@ -2195,18 +2220,24 @@
((= (len args) 0) -1) ((= (len args) 0) -1)
((js-regex? (nth args 0)) ((js-regex? (nth args 0))
(let (let
((rx (nth args 0)) (src (get (nth args 0) "source"))) ((rx (nth args 0))
(src (get (nth args 0) "source")))
(js-string-index-of (js-string-index-of
(if (get rx "ignoreCase") (js-lower-case s) s) (if (get rx "ignoreCase") (js-lower-case s) s)
(if (get rx "ignoreCase") (js-lower-case src) src) (if (get rx "ignoreCase") (js-lower-case src) src)
0))) 0)))
(else (js-string-index-of s (js-to-string (nth args 0)) 0))))) (else
(js-string-index-of
s
(js-to-string (nth args 0))
0)))))
((= name "match") ((= name "match")
(fn (fn
(&rest args) (&rest args)
(cond (cond
((= (len args) 0) nil) ((= (len args) 0) nil)
((js-regex? (nth args 0)) (js-regex-stub-exec (nth args 0) s)) ((js-regex? (nth args 0))
(js-regex-stub-exec (nth args 0) s))
(else (else
(let (let
((needle (js-to-string (nth args 0)))) ((needle (js-to-string (nth args 0))))
@@ -2256,11 +2287,17 @@
(let (let
((default-start (- (len s) (len needle))) ((default-start (- (len s) (len needle)))
(from (from
(if (< (len args) 2) -1 (js-num-to-int (nth args 1))))) (if
(< (len args) 2)
-1
(js-num-to-int (nth args 1)))))
(js-string-last-index-of (js-string-last-index-of
s s
needle needle
(if (< from 0) default-start (min from default-start)))))))) (if
(< from 0)
default-start
(min from default-start))))))))
((= name "localeCompare") ((= name "localeCompare")
(fn (fn
(&rest args) (&rest args)
@@ -2269,7 +2306,10 @@
0 0
(let (let
((other (js-to-string (nth args 0)))) ((other (js-to-string (nth args 0))))
(cond ((< s other) -1) ((> s other) 1) (else 0)))))) (cond
((< s other) -1)
((> s other) 1)
(else 0))))))
((= name "replaceAll") ((= name "replaceAll")
(fn (fn
(&rest args) (&rest args)
@@ -2277,7 +2317,8 @@
(< (len args) 2) (< (len args) 2)
s s
(let (let
((needle-arg (nth args 0)) (repl (nth args 1))) ((needle-arg (nth args 0))
(repl (nth args 1)))
(let (let
((needle (if (js-regex? needle-arg) (get needle-arg "source") (js-to-string needle-arg)))) ((needle (if (js-regex? needle-arg) (get needle-arg "source") (js-to-string needle-arg))))
(js-string-replace-all (js-string-replace-all
@@ -2466,7 +2507,7 @@
(else js-undefined))) (else js-undefined)))
((= (type-of obj) "string") ((= (type-of obj) "string")
(cond (cond
((= key "length") (unicode-len obj)) ((= key "length") (len obj))
((= (type-of key) "number") ((= (type-of key) "number")
(if (if
(and (>= key 0) (< key (len obj))) (and (>= key 0) (< key (len obj)))
@@ -2613,7 +2654,7 @@
(fn (fn
(&rest args) (&rest args)
(cond (cond
((empty? args) (- 0 (/ 1 0))) ((empty? args) -inf)
(else (js-math-max-loop (first args) (rest args)))))) (else (js-math-max-loop (first args) (rest args))))))
(define (define
@@ -2632,7 +2673,7 @@
(fn (fn
(&rest args) (&rest args)
(cond (cond
((empty? args) (/ 1 0)) ((empty? args) inf)
(else (js-math-min-loop (first args) (rest args)))))) (else (js-math-min-loop (first args) (rest args))))))
(define (define
@@ -2738,8 +2779,8 @@
(and (and
(number? v) (number? v)
(not (js-number-is-nan v)) (not (js-number-is-nan v))
(not (= v (/ 1 0))) (not (= v inf))
(not (= v (/ -1 0)))))) (not (= v -inf)))))
(define (define
js-number-is-nan js-number-is-nan

View File

@@ -158,6 +158,8 @@ Each item: implement → tests → update progress. Mark `[x]` when tests green.
Append-only record of completed iterations. Loop writes one line per iteration: date, what was done, test count delta. Append-only record of completed iterations. Loop writes one line per iteration: date, what was done, test count delta.
- 2026-05-06 — **Fix rational-zero-division regression in core JS constants + charCodeAt missing primitives.** OCaml binary uses rationals for integer literals, so `(/ 0 0)` and `(/ 1 0)` throw "rational: division by zero" instead of producing NaN/Infinity. Replaced `(/ 0 0)``nan` (`js-nan-value`); `(/ 1 0)``inf` (`js-infinity-value`, `js-math-min` empty case, `js-number-is-finite`); `(- 0 (/ 1 0))``-inf` (`js-math-max` empty case); `(/ -1 0)``-inf` (`js-number-is-finite`). `js-max-value-approx` was looping forever (rationals never reach float infinity) — replaced with literal `1.7976931348623157e+308`. Fixed `charCodeAt` and string `.length` to use `(len s)` and `(char-code (char-at s idx))` instead of missing `unicode-len`/`unicode-char-code-at` primitives. conformance.sh: 0→148/148. Unit tests: 521/530 best run (baseline run was 417/530; both timeout-flaky).
- 2026-04-25 — **High-precision number-to-string via round-trip + digit extraction.** `js-big-int-str-loop` extracts decimal digits from integer-valued float. `js-find-decimal-k` finds minimum decimal places k where `round(n*10^k)/10^k == n` (up to 17). `js-format-decimal-digits` inserts decimal point. `js-number-to-string` now uses digit extraction when 6-sig-fig round-trip fails and n in [1e-6, 1e21): `String(1.0000001)="1.0000001"`, `String(1/3)="0.3333333333333333"`. String test262 subset: 58→62/100. 529/530 unit, 148/148 slice. - 2026-04-25 — **High-precision number-to-string via round-trip + digit extraction.** `js-big-int-str-loop` extracts decimal digits from integer-valued float. `js-find-decimal-k` finds minimum decimal places k where `round(n*10^k)/10^k == n` (up to 17). `js-format-decimal-digits` inserts decimal point. `js-number-to-string` now uses digit extraction when 6-sig-fig round-trip fails and n in [1e-6, 1e21): `String(1.0000001)="1.0000001"`, `String(1/3)="0.3333333333333333"`. String test262 subset: 58→62/100. 529/530 unit, 148/148 slice.
- 2026-04-25 — **String wrapper objects + number-to-string sci notation.** `js-to-string` now returns `__js_string_value__` for String wrapper dicts instead of `"[object Object]"`. `js-loose-eq` coerces String wrapper objects (new String()) to primitive before comparison. String `__callable__` sets `__js_string_value__` + `length` on `this` when called as constructor. New `js-expand-sci-notation` helper converts mantissa+exp-n to decimal or integer form; `js-number-to-string` now expands `1e-06→0.000001`, `1e+06→1000000`, fixes `1e21→1e+21`. String test262 subset: 45→58/100. 529/530 unit, 148/148 slice. - 2026-04-25 — **String wrapper objects + number-to-string sci notation.** `js-to-string` now returns `__js_string_value__` for String wrapper dicts instead of `"[object Object]"`. `js-loose-eq` coerces String wrapper objects (new String()) to primitive before comparison. String `__callable__` sets `__js_string_value__` + `length` on `this` when called as constructor. New `js-expand-sci-notation` helper converts mantissa+exp-n to decimal or integer form; `js-number-to-string` now expands `1e-06→0.000001`, `1e+06→1000000`, fixes `1e21→1e+21`. String test262 subset: 45→58/100. 529/530 unit, 148/148 slice.