js-on-sx: hex-literal string→number coercion (+15 Number)

ES spec: ToNumber("0x0") == 0, ToNumber("0xFF") == 255. Our parser
returned NaN for anything with a 0x/0X prefix, failing S9.3.1_A16..A32
(every hex-literal assertion test case).

Added:
- js-hex-prefix?      — is this an 0x/0X prefix?
- js-is-hex-body?     — all remaining chars are [0-9a-fA-F]?
- js-parse-hex        — walk chars, accumulate in base 16, NaN on bad char
- js-hex-digit-value  — char → 0..15 (or -1)

js-is-numeric-string? short-circuits to the hex-body check on 0x*
prefix; js-num-from-string dispatches to js-parse-hex on same.

Unit 521/522, slice 148/148 unchanged.
Number scoreboard: 58/100 → 73/100 (+15).

Sample flipped: S9.3.1_A16 (0x0/0X0), A17..A31 (0x0..0xF and 0x10..0x1F
range coverage). Many of the remaining 22 fails are (new Number()).x
style prototype-chain introspection and MAX_VALUE precision.
This commit is contained in:
2026-04-24 12:14:47 +00:00
parent bf09055c4e
commit 00edae49e4

View File

@@ -833,7 +833,22 @@
(define
js-is-numeric-string?
(fn (s) (js-is-numeric-loop s 0 false false false)))
(fn
(s)
(if
(js-hex-prefix? s)
(js-is-hex-body? s 2 (len s))
(js-is-numeric-loop s 0 false false false))))
(define
js-is-hex-body?
(fn
(s i n)
(cond
((>= i n) (> n 2))
((>= (js-hex-digit-value (char-at s i)) 0)
(js-is-hex-body? s (+ i 1) n))
(else false))))
(define
js-is-numeric-loop
@@ -886,6 +901,51 @@
((> exp 0) (* base (js-pow-int base (- exp 1))))
(else (/ 1 (js-pow-int base (- 0 exp)))))))
(define
js-hex-prefix?
(fn
(s)
(and
(>= (len s) 2)
(= (char-at s 0) "0")
(or (= (char-at s 1) "x") (= (char-at s 1) "X")))))
(define
js-parse-hex
(fn
(s i acc)
(cond
((>= i (len s)) acc)
(else
(let
((c (char-at s i)) (d (js-hex-digit-value (char-at s i))))
(cond
((< d 0) (js-nan-value))
(else (js-parse-hex s (+ i 1) (+ (* acc 16) d)))))))))
(define
js-hex-digit-value
(fn
(c)
(cond
((= c "0") 0)
((= c "1") 1)
((= c "2") 2)
((= c "3") 3)
((= c "4") 4)
((= c "5") 5)
((= c "6") 6)
((= c "7") 7)
((= c "8") 8)
((= c "9") 9)
((or (= c "a") (= c "A")) 10)
((or (= c "b") (= c "B")) 11)
((or (= c "c") (= c "C")) 12)
((or (= c "d") (= c "D")) 13)
((or (= c "e") (= c "E")) 14)
((or (= c "f") (= c "F")) 15)
(else -1))))
(define
js-num-from-string
(fn
@@ -894,6 +954,7 @@
((trimmed (js-trim s)))
(cond
((= trimmed "") 0)
((js-hex-prefix? trimmed) (js-parse-hex trimmed 2 0))
(else
(let
((esplit (js-find-exp-char trimmed)))