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