diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index 7932213e..5d6b0f71 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -3718,7 +3718,7 @@ (define js-global-is-nan (fn (v) (js-number-is-nan (js-to-number v)))) -(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 (fn (d) (js-number-to-fixed (js-this) (if (= d nil) 0 (js-to-number d)))) :toExponential (fn (&rest args) (js-to-string (js-this))) :toLocaleString (fn () (js-to-string (js-this))) :toString (fn (&rest args) (let ((this-val (js-this)) (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)))) :toPrecision (fn (&rest args) (js-to-string (js-this))) :valueOf (fn () (js-this))} :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-this) (if (= d nil) 0 (js-to-number d)))) :length 1 :name "toFixed"} :toExponential {:__callable__ (fn (&rest args) (js-to-string (js-this))) :length 1 :name "toExponential"} :toLocaleString {:__callable__ (fn () (js-to-string (js-this))) :length 0 :name "toLocaleString"} :toString {:__callable__ (fn (&rest args) (let ((this-val (js-this)) (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-to-string (js-this))) :length 1 :name "toPrecision"} :valueOf {:__callable__ (fn () (js-this)) :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) @@ -4481,17 +4481,51 @@ (+ i 1) (str acc (char-from-code code)))))))) +(define + js-string-proto-fn-length + (fn + (name) + (cond + ((= name "concat") 1) + ((= name "indexOf") 1) + ((= name "lastIndexOf") 1) + ((= name "slice") 2) + ((= name "substring") 2) + ((= name "substr") 2) + ((= name "split") 2) + ((= name "replace") 2) + ((= name "replaceAll") 2) + ((= name "match") 1) + ((= name "matchAll") 1) + ((= name "search") 1) + ((= name "charAt") 1) + ((= name "charCodeAt") 1) + ((= name "codePointAt") 1) + ((= name "at") 1) + ((= name "padStart") 1) + ((= name "padEnd") 1) + ((= name "repeat") 1) + ((= name "startsWith") 1) + ((= name "endsWith") 1) + ((= name "includes") 1) + ((= name "localeCompare") 1) + ((= name "normalize") 0) + (else 0)))) + (define js-string-proto-fn (fn (name) - (fn - (&rest args) - (let - ((this-val (js-this))) + {:__callable__ + (fn + (&rest args) (let - ((s (cond ((= (type-of this-val) "string") this-val) ((and (= (type-of this-val) "dict") (contains? (keys this-val) "__js_string_value__")) (get this-val "__js_string_value__")) (else "[object Object]")))) - (js-invoke-method s name args)))))) + ((this-val (js-this))) + (let + ((s (cond ((= (type-of this-val) "string") this-val) ((and (= (type-of this-val) "dict") (contains? (keys this-val) "__js_string_value__")) (get this-val "__js_string_value__")) (else "[object Object]")))) + (js-invoke-method s name args)))) + :length (js-string-proto-fn-length name) + :name name})) (define String {:raw (fn (&rest args) (if (empty? args) "" (js-to-string (nth args 0)))) :prototype {:replace (js-string-proto-fn "replace") :toLocaleUpperCase (js-string-proto-fn "toLocaleUpperCase") :trimStart (js-string-proto-fn "trimStart") :includes (js-string-proto-fn "includes") :charAt (js-string-proto-fn "charAt") :match (js-string-proto-fn "match") :charCodeAt (js-string-proto-fn "charCodeAt") :slice (js-string-proto-fn "slice") :toString (js-string-proto-fn "toString") :toLocaleLowerCase (js-string-proto-fn "toLocaleLowerCase") :toUpperCase (js-string-proto-fn "toUpperCase") :trimEnd (js-string-proto-fn "trimEnd") :repeat (js-string-proto-fn "repeat") :padStart (js-string-proto-fn "padStart") :search (js-string-proto-fn "search") :substring (js-string-proto-fn "substring") :replaceAll (js-string-proto-fn "replaceAll") :trim (js-string-proto-fn "trim") :valueOf (js-string-proto-fn "valueOf") :at (js-string-proto-fn "at") :normalize (js-string-proto-fn "normalize") :split (js-string-proto-fn "split") :endsWith (js-string-proto-fn "endsWith") :indexOf (js-string-proto-fn "indexOf") :localeCompare (js-string-proto-fn "localeCompare") :toLowerCase (js-string-proto-fn "toLowerCase") :concat (js-string-proto-fn "concat") :startsWith (js-string-proto-fn "startsWith") :padEnd (js-string-proto-fn "padEnd") :codePointAt (js-string-proto-fn "codePointAt") :lastIndexOf (js-string-proto-fn "lastIndexOf")} :__callable__ (fn (&rest args) (if (= (len args) 0) "" (js-to-string (nth args 0)))) :fromCharCode js-string-from-char-code}) diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index 8a32f38c..5d835cc8 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-09 — **`Number.prototype` and `String.prototype` methods carry spec lengths and names.** Same shape as the earlier Function.prototype fix. Number.prototype.{toFixed/toExponential/toPrecision/toString/valueOf/toLocaleString} were bare `(fn ...)` lambdas → length 0 → tests assert e.g. `Number.prototype.toExponential.length === 1`. Wrapped each in a dict-with-`__callable__` with `:length` and `:name`. For String.prototype, `js-string-proto-fn` was a single helper applied to ~30 method names; added `js-string-proto-fn-length` (lookup table for spec-defined lengths: `concat:1`, `indexOf:1`, `slice:2`, `substring:2`, `replace:2`, etc.) and changed the helper to return the dict form, so all string methods now report correctly. Result: built-ins/Number/prototype 18/30 → 20/30, String/prototype 18/30 → 21/30. Number 26/30 holds, String 29/30. conformance.sh: 148/148. + - 2026-05-09 — **`Boolean.prototype.toString`/`valueOf` throw TypeError on non-Boolean receivers.** Per spec, both methods are not generic — calling them with a `this` that isn't a Boolean primitive or wrapper must throw TypeError. Was silently returning `"true"`/`"false"` based on whether the receiver was truthy (`s1.toString = Boolean.prototype.toString; s1.toString()` returned `"true"` for any non-empty string instead of throwing). Added an `else (raise (js-new-call TypeError ...))` branch to both prototype methods. Result: built-ins/Boolean 28/30 → 29/30. Object 30/30 holds. conformance.sh: 148/148. - 2026-05-09 — **`Array.prototype.reduce`/`reduceRight` callback receives `(acc, cur, idx, array)`.** Was calling `(f acc cur)` — only two args, no index, no source array. Per spec the reducer signature is `(accumulator, currentValue, currentIndex, array)`. Updated `js-list-reduce-loop` and `js-list-reduce-right-loop` to call via `js-call-with-this js-undefined f (list acc cur i arr)`. Result: built-ins/Array/prototype/reduce 6/30 → 8/30, reduceRight 6/30 → 8/30. Object 30/30 holds. conformance.sh: 148/148.