From 058dcd5600b4a633f31d8010b31db450080a6d89 Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 10 May 2026 03:01:02 +0000 Subject: [PATCH] js-on-sx: ** / Math.pow spec edges (NaN exp, abs(base)=1+inf), Number.valueOf ignores args --- lib/js/runtime.sx | 22 +++++++++++++++++++--- plans/js-on-sx.md | 2 ++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index eaa64e08..c8930c5d 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -2048,7 +2048,23 @@ (sh (modulo (js-math-trunc (js-to-number b)) 32))) (if (= sh 0) ai (floor (/ ai (js-math-pow 2 sh))))))) -(define js-pow (fn (a b) (pow (js-to-number a) (js-to-number b)))) +(define + js-pow-spec + (fn + (b e) + (let + ((bn (js-to-number b)) (en (js-to-number e))) + (let + ((inf (js-infinity-value)) (abs-b (if (< bn 0) (- 0 bn) bn))) + (cond + ((js-number-is-nan en) (js-nan-value)) + ((= en 0) 1) + ((js-number-is-nan bn) (js-nan-value)) + ((and (= abs-b 1) (or (= en inf) (= en (- 0 inf)))) + (js-nan-value)) + (else (pow bn en))))))) + +(define js-pow (fn (a b) (js-pow-spec a b))) (define js-neg (fn (a) (* -1 (exact->inexact (js-to-number a))))) @@ -3653,7 +3669,7 @@ (define js-math-sqrt (fn (x) (sqrt (js-to-number x)))) -(define js-math-pow (fn (a b) (pow (js-to-number a) (js-to-number b)))) +(define js-math-pow (fn (a b) (js-pow-spec a b))) (define js-math-trunc @@ -3788,7 +3804,7 @@ (else (raise (js-new-call TypeError (js-args "Number.prototype method requires a Number")))))))) -(define Number {:MIN_SAFE_INTEGER -9007199254740991 :MIN_VALUE 4.94066e-324 :isNaN js-number-is-nan :isSafeInteger js-number-is-safe-integer :NEGATIVE_INFINITY (- 0 (js-infinity-value)) :NaN (js-nan-value) :prototype {:toFixed {:__callable__ (fn (d) (js-number-to-fixed (js-number-this-val) (if (= d nil) 0 (js-to-number d)))) :length 1 :name "toFixed"} :toExponential {:__callable__ (fn (&rest args) (js-number-to-string (js-number-this-val))) :length 1 :name "toExponential"} :toLocaleString {:__callable__ (fn () (js-number-to-string (js-number-this-val))) :length 0 :name "toLocaleString"} :toString {:__callable__ (fn (&rest args) (let ((this-val (js-number-this-val)) (radix (if (empty? args) 10 (js-to-number (nth args 0))))) (js-num-to-str-radix this-val (if (or (= radix nil) (js-undefined? radix)) 10 radix)))) :length 1 :name "toString"} :toPrecision {:__callable__ (fn (&rest args) (js-number-to-string (js-number-this-val))) :length 1 :name "toPrecision"} :valueOf {:__callable__ (fn () (js-number-this-val)) :length 0 :name "valueOf"}} :isInteger js-number-is-integer :__callable__ js-to-number :MAX_VALUE (js-max-value-approx) :POSITIVE_INFINITY (js-infinity-value) :isFinite js-number-is-finite :MAX_SAFE_INTEGER 9007199254740991 :EPSILON 2.22045e-16}) +(define Number {:MIN_SAFE_INTEGER -9007199254740991 :MIN_VALUE 4.94066e-324 :isNaN js-number-is-nan :isSafeInteger js-number-is-safe-integer :NEGATIVE_INFINITY (- 0 (js-infinity-value)) :NaN (js-nan-value) :prototype {:toFixed {:__callable__ (fn (d) (js-number-to-fixed (js-number-this-val) (if (= d nil) 0 (js-to-number d)))) :length 1 :name "toFixed"} :toExponential {:__callable__ (fn (&rest args) (js-number-to-string (js-number-this-val))) :length 1 :name "toExponential"} :toLocaleString {:__callable__ (fn () (js-number-to-string (js-number-this-val))) :length 0 :name "toLocaleString"} :toString {:__callable__ (fn (&rest args) (let ((this-val (js-number-this-val)) (radix (if (empty? args) 10 (js-to-number (nth args 0))))) (js-num-to-str-radix this-val (if (or (= radix nil) (js-undefined? radix)) 10 radix)))) :length 1 :name "toString"} :toPrecision {:__callable__ (fn (&rest args) (js-number-to-string (js-number-this-val))) :length 1 :name "toPrecision"} :valueOf {:__callable__ (fn (&rest args) (js-number-this-val)) :length 0 :name "valueOf"}} :isInteger js-number-is-integer :__callable__ js-to-number :MAX_VALUE (js-max-value-approx) :POSITIVE_INFINITY (js-infinity-value) :isFinite js-number-is-finite :MAX_SAFE_INTEGER 9007199254740991 :EPSILON 2.22045e-16}) (dict-set! Number "length" 1) diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index e941f61e..c8a4bb70 100644 --- a/plans/js-on-sx.md +++ b/plans/js-on-sx.md @@ -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-10 — **`**` / `Math.pow` honour JS spec edge cases for NaN, ±0, abs(base)=1+Infinity, plus `Number.prototype.valueOf` accepts ignored args.** (1) New `js-pow-spec` shared by `js-pow` (operator) and `js-math-pow`: NaN exponent → NaN, exponent 0 → 1 (even with NaN base), NaN base + non-zero exp → NaN, abs(base)=1 with exp=±Infinity → NaN. Underlying `pow` handles the rest. (2) Number.prototype.valueOf was `(fn () ...)` and rejected the spec-allowed extra arg with "lambda expects 0 args, got 1"; now `(fn (&rest args) ...)`. Result: language/expressions/exponentiation 23/30 → 25/30 (+2). built-ins/Math/pow 27/27 holds. conformance.sh: 148/148. + - 2026-05-10 — **`Number.prototype.toString(radix)` no longer crashes on rational division-by-zero.** `js-num-to-str-radix` was probing for ±Infinity by comparing against `(/ 1 0)` / `(/ -1 0)` — but on the rational arithmetic path that throws "rational: division by zero" before the comparison ever happens, so every `Number(x).toString(radix)` call exploded. Replaced the probes with `(js-infinity-value)` / `(- 0 (js-infinity-value))` and the NaN check with `js-number-is-nan`. Result: built-ins/Number/prototype/toString 0/30 → 29/30 (+29). Number 26/30. conformance.sh: 148/148. - 2026-05-10 — **Array literal elision (holes), `list instanceof Array`, `array.toString` identity.** Three coupled fixes for `language/expressions/array`. (1) Parser: `jp-array-loop` accepts a leading or interior `,` as elision and pushes `(js-undef)`, so `[,]`, `[,,3,,,]`, `[1,,3]` parse and produce length 1, 5, 3. (2) Runtime: `js-instanceof` adds a `(list? obj)` arm that returns true when the right-hand side is `Array` (or `Object`). (3) Runtime: `js-get-prop` for `key="toString"` on a list returns the actual `Array.prototype.toString` slot via `js-dict-get-walk` instead of a fresh `js-array-method` callable, so `[1,2,3].toString === Array.prototype.toString`. `toLocaleString` left on the legacy arm — its proto entry is a dict-with-`__callable__` whose body re-enters `js-invoke-method`, which would loop. Result: language/expressions/array 13/30 → 21/30 (+8). conformance.sh: 148/148.