js-on-sx: wrapper constructor-detection, Array.prototype.toString, >>> operator
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 11s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 11s
Number.__callable__ and String.__callable__ now check this.__proto__ === Number/String.prototype before writing wrapper slots, preventing false-positive mutation when called as plain function. js-to-number extended to unwrap wrapper dicts and call valueOf/toString for plain objects. Array.prototype.toString replaced with a direct js-list-join implementation (eliminates infinite recursion via js-invoke-method on dict-based arrays). >>> added to transpiler + runtime. String test262 subset: 62→66/100. 529/530 unit, 147/148 slice. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -904,6 +904,36 @@
|
||||
((= v false) 0)
|
||||
((= (type-of v) "number") v)
|
||||
((= (type-of v) "string") (js-string-to-number v))
|
||||
((= (type-of v) "dict")
|
||||
(cond
|
||||
((contains? (keys v) "__js_number_value__")
|
||||
(get v "__js_number_value__"))
|
||||
((contains? (keys v) "__js_boolean_value__")
|
||||
(if (get v "__js_boolean_value__") 1 0))
|
||||
((contains? (keys v) "__js_string_value__")
|
||||
(js-string-to-number (get v "__js_string_value__")))
|
||||
(else
|
||||
(let
|
||||
((valueof-fn (js-get-prop v "valueOf")))
|
||||
(if
|
||||
(= (type-of valueof-fn) "lambda")
|
||||
(let
|
||||
((result (js-call-with-this v valueof-fn ())))
|
||||
(if
|
||||
(not (= (type-of result) "dict"))
|
||||
(js-to-number result)
|
||||
(let
|
||||
((tostr-fn (js-get-prop v "toString")))
|
||||
(if
|
||||
(= (type-of tostr-fn) "lambda")
|
||||
(let
|
||||
((result2 (js-call-with-this v tostr-fn ())))
|
||||
(if
|
||||
(not (= (type-of result2) "dict"))
|
||||
(js-to-number result2)
|
||||
(js-nan-value)))
|
||||
(js-nan-value)))))
|
||||
(js-nan-value))))))
|
||||
(else 0))))
|
||||
|
||||
(define
|
||||
@@ -1172,10 +1202,35 @@
|
||||
(else
|
||||
(if
|
||||
(= (type-of v) "dict")
|
||||
(if
|
||||
(contains? (keys v) "__js_string_value__")
|
||||
(get v "__js_string_value__")
|
||||
"[object Object]")
|
||||
(cond
|
||||
((contains? (keys v) "__js_string_value__")
|
||||
(get v "__js_string_value__"))
|
||||
((contains? (keys v) "__js_number_value__")
|
||||
(js-number-to-string (get v "__js_number_value__")))
|
||||
((contains? (keys v) "__js_boolean_value__")
|
||||
(if (get v "__js_boolean_value__") "true" "false"))
|
||||
(else
|
||||
(let
|
||||
((tostr-fn (js-get-prop v "toString")))
|
||||
(if
|
||||
(= (type-of tostr-fn) "lambda")
|
||||
(let
|
||||
((result (js-call-with-this v tostr-fn ())))
|
||||
(if
|
||||
(= (type-of result) "dict")
|
||||
(let
|
||||
((valueof-fn (js-get-prop v "valueOf")))
|
||||
(if
|
||||
(= (type-of valueof-fn) "lambda")
|
||||
(let
|
||||
((result2 (js-call-with-this v valueof-fn ())))
|
||||
(if
|
||||
(= (type-of result2) "dict")
|
||||
"[object Object]"
|
||||
(js-to-string result2)))
|
||||
"[object Object]"))
|
||||
(js-to-string result)))
|
||||
"[object Object]"))))
|
||||
(str v))))))
|
||||
|
||||
(define
|
||||
@@ -1348,6 +1403,15 @@
|
||||
|
||||
(define js-mod (fn (a b) (mod (js-to-number a) (js-to-number b))))
|
||||
|
||||
(define
|
||||
js-unsigned-rshift
|
||||
(fn
|
||||
(l r)
|
||||
(let
|
||||
((lu32 (modulo (js-math-trunc (js-to-number l)) 4294967296))
|
||||
(shift (modulo (js-math-trunc (js-to-number r)) 32)))
|
||||
(floor (/ lu32 (js-math-pow 2 shift))))))
|
||||
|
||||
(define js-pow (fn (a b) (pow (js-to-number a) (js-to-number b))))
|
||||
|
||||
(define js-neg (fn (a) (- 0 (js-to-number a))))
|
||||
@@ -1990,11 +2054,11 @@
|
||||
(fn
|
||||
(i)
|
||||
(let
|
||||
((idx (js-num-to-int i)))
|
||||
((idx (js-num-to-int (js-to-number i))))
|
||||
(if
|
||||
(and (>= idx 0) (< idx (len s)))
|
||||
(char-code (char-at s idx))
|
||||
0))))
|
||||
(and (>= idx 0) (< idx (unicode-len s)))
|
||||
(unicode-char-code-at s idx)
|
||||
(js-nan-value)))))
|
||||
((= name "indexOf")
|
||||
(fn
|
||||
(&rest args)
|
||||
@@ -2402,7 +2466,7 @@
|
||||
(else js-undefined)))
|
||||
((= (type-of obj) "string")
|
||||
(cond
|
||||
((= key "length") (len obj))
|
||||
((= key "length") (unicode-len obj))
|
||||
((= (type-of key) "number")
|
||||
(if
|
||||
(and (>= key 0) (< key (len obj)))
|
||||
@@ -2709,6 +2773,52 @@
|
||||
|
||||
(dict-set! (get Number "prototype") "constructor" Number)
|
||||
|
||||
(dict-set!
|
||||
Number
|
||||
"__callable__"
|
||||
(fn
|
||||
(&rest args)
|
||||
(let
|
||||
((raw (if (= (len args) 0) 0 (js-to-number (nth args 0)))))
|
||||
(let
|
||||
((this-val (js-this)))
|
||||
(if
|
||||
(and
|
||||
(dict? this-val)
|
||||
(contains? (keys this-val) "__proto__")
|
||||
(= (get this-val "__proto__") (get Number "prototype")))
|
||||
(begin (dict-set! this-val "__js_number_value__" raw) this-val)
|
||||
raw)))))
|
||||
|
||||
(dict-set!
|
||||
(get Number "prototype")
|
||||
"valueOf"
|
||||
(fn
|
||||
()
|
||||
(let
|
||||
((this-val (js-this)))
|
||||
(if
|
||||
(and
|
||||
(dict? this-val)
|
||||
(contains? (keys this-val) "__js_number_value__"))
|
||||
(get this-val "__js_number_value__")
|
||||
this-val))))
|
||||
|
||||
(dict-set!
|
||||
(get Number "prototype")
|
||||
"toString"
|
||||
(fn
|
||||
(&rest args)
|
||||
(let
|
||||
((this-raw (js-this)))
|
||||
(let
|
||||
((this-val (if (and (dict? this-raw) (contains? (keys this-raw) "__js_number_value__")) (get this-raw "__js_number_value__") this-raw)))
|
||||
(let
|
||||
((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)))))))
|
||||
|
||||
(define isFinite js-global-is-finite)
|
||||
|
||||
(define isNaN js-global-is-nan)
|
||||
@@ -3167,6 +3277,17 @@
|
||||
|
||||
(dict-set! Array "name" "Array")
|
||||
|
||||
(dict-set!
|
||||
(get Array "prototype")
|
||||
"toString"
|
||||
(fn
|
||||
(&rest args)
|
||||
(let
|
||||
((this-val (js-this)))
|
||||
(let
|
||||
((items (cond ((list? this-val) this-val) ((and (dict? this-val) (contains? (keys this-val) "length")) (js-arraylike-to-list this-val)) (else (list)))))
|
||||
(js-list-join items ",")))))
|
||||
|
||||
(define
|
||||
js-string-from-char-code
|
||||
(fn (&rest args) (js-string-from-char-code-loop args 0 "")))
|
||||
@@ -3207,10 +3328,14 @@
|
||||
(if
|
||||
(>= i (len args))
|
||||
acc
|
||||
(js-string-from-char-code-loop
|
||||
args
|
||||
(+ i 1)
|
||||
(str acc (js-code-to-char (js-num-to-int (nth args i))))))))
|
||||
(let
|
||||
((n (js-to-number (nth args i))))
|
||||
(let
|
||||
((code (if (js-global-is-nan n) 0 (modulo (js-math-trunc n) 65536))))
|
||||
(js-string-from-char-code-loop
|
||||
args
|
||||
(+ i 1)
|
||||
(str acc (char-from-code code))))))))
|
||||
|
||||
(define
|
||||
js-string-proto-fn
|
||||
@@ -3220,7 +3345,9 @@
|
||||
(&rest args)
|
||||
(let
|
||||
((this-val (js-this)))
|
||||
(js-invoke-method (js-to-string this-val) name 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))))))
|
||||
|
||||
(define String {:fromCharCode js-string-from-char-code :__callable__ (fn (&rest args) (if (= (len args) 0) "" (js-to-string (nth args 0)))) :prototype {: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") :indexOf (js-string-proto-fn "indexOf") :localeCompare (js-string-proto-fn "localeCompare") :split (js-string-proto-fn "split") :endsWith (js-string-proto-fn "endsWith") :trim (js-string-proto-fn "trim") :valueOf (js-string-proto-fn "valueOf") :at (js-string-proto-fn "at") :normalize (js-string-proto-fn "normalize") :substring (js-string-proto-fn "substring") :replaceAll (js-string-proto-fn "replaceAll") :repeat (js-string-proto-fn "repeat") :padStart (js-string-proto-fn "padStart") :search (js-string-proto-fn "search") :toUpperCase (js-string-proto-fn "toUpperCase") :trimEnd (js-string-proto-fn "trimEnd") :toString (js-string-proto-fn "toString") :toLocaleLowerCase (js-string-proto-fn "toLocaleLowerCase") :charCodeAt (js-string-proto-fn "charCodeAt") :slice (js-string-proto-fn "slice") :charAt (js-string-proto-fn "charAt") :match (js-string-proto-fn "match") :includes (js-string-proto-fn "includes") :trimStart (js-string-proto-fn "trimStart") :toLocaleUpperCase (js-string-proto-fn "toLocaleUpperCase") :replace (js-string-proto-fn "replace")} :raw (fn (&rest args) (if (empty? args) "" (js-to-string (nth args 0))))})
|
||||
|
||||
@@ -3232,6 +3359,8 @@
|
||||
|
||||
(dict-set! String "fromCodePoint" js-string-from-code-point)
|
||||
|
||||
(dict-set! String "fromCharCode" js-string-from-char-code)
|
||||
|
||||
(dict-set!
|
||||
String
|
||||
"__callable__"
|
||||
@@ -3242,7 +3371,10 @@
|
||||
(let
|
||||
((this-val (js-this)))
|
||||
(if
|
||||
(dict? this-val)
|
||||
(and
|
||||
(dict? this-val)
|
||||
(contains? (keys this-val) "__proto__")
|
||||
(= (get this-val "__proto__") (get String "prototype")))
|
||||
(begin
|
||||
(dict-set! this-val "__js_string_value__" raw)
|
||||
(dict-set! this-val "length" (len raw))
|
||||
@@ -3260,6 +3392,50 @@
|
||||
|
||||
(dict-set! Boolean "name" "Boolean")
|
||||
|
||||
(dict-set! Boolean "prototype" {:constructor Boolean})
|
||||
|
||||
(dict-set!
|
||||
Boolean
|
||||
"__callable__"
|
||||
(fn
|
||||
(&rest args)
|
||||
(let
|
||||
((val (if (> (len args) 0) (js-to-boolean (nth args 0)) false)))
|
||||
(let
|
||||
((this-val (js-this)))
|
||||
(if
|
||||
(dict? this-val)
|
||||
(begin
|
||||
(dict-set! this-val "__js_boolean_value__" val)
|
||||
(dict-set! this-val "__proto__" (get Boolean "prototype"))
|
||||
this-val)
|
||||
(if val true false))))))
|
||||
|
||||
(dict-set!
|
||||
(get Boolean "prototype")
|
||||
"valueOf"
|
||||
(fn
|
||||
(&rest args)
|
||||
(let
|
||||
((this-val (js-this)))
|
||||
(if
|
||||
(and
|
||||
(= (type-of this-val) "dict")
|
||||
(contains? (keys this-val) "__js_boolean_value__"))
|
||||
(get this-val "__js_boolean_value__")
|
||||
this-val))))
|
||||
|
||||
(dict-set!
|
||||
(get Boolean "prototype")
|
||||
"toString"
|
||||
(fn
|
||||
(&rest args)
|
||||
(let
|
||||
((this-val (js-this)))
|
||||
(let
|
||||
((b (if (and (= (type-of this-val) "dict") (contains? (keys this-val) "__js_boolean_value__")) (get this-val "__js_boolean_value__") this-val)))
|
||||
(if b "true" "false")))))
|
||||
|
||||
(define
|
||||
parseInt
|
||||
(fn
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
{
|
||||
"totals": {
|
||||
"pass": 62,
|
||||
"fail": 29,
|
||||
"pass": 66,
|
||||
"fail": 25,
|
||||
"skip": 1130,
|
||||
"timeout": 9,
|
||||
"total": 1230,
|
||||
"runnable": 100,
|
||||
"pass_rate": 62.0
|
||||
"pass_rate": 66.0
|
||||
},
|
||||
"categories": [
|
||||
{
|
||||
"category": "built-ins/String",
|
||||
"total": 1223,
|
||||
"pass": 62,
|
||||
"fail": 29,
|
||||
"pass": 66,
|
||||
"fail": 25,
|
||||
"skip": 1123,
|
||||
"timeout": 9,
|
||||
"pass_rate": 62.0,
|
||||
"pass_rate": 66.0,
|
||||
"top_failures": [
|
||||
[
|
||||
"Test262Error (assertion failed)",
|
||||
24
|
||||
14
|
||||
],
|
||||
[
|
||||
"Timeout",
|
||||
9
|
||||
],
|
||||
[
|
||||
"Unhandled: Not callable: \\\\\\",
|
||||
1
|
||||
"TypeError: not a function",
|
||||
6
|
||||
],
|
||||
[
|
||||
"ReferenceError (undefined symbol)",
|
||||
1
|
||||
2
|
||||
],
|
||||
[
|
||||
"SyntaxError (parse/unsupported syntax)",
|
||||
1
|
||||
"Unhandled: Not callable: \\\\\\",
|
||||
2
|
||||
]
|
||||
]
|
||||
},
|
||||
@@ -54,34 +54,30 @@
|
||||
"top_failure_modes": [
|
||||
[
|
||||
"Test262Error (assertion failed)",
|
||||
24
|
||||
14
|
||||
],
|
||||
[
|
||||
"Timeout",
|
||||
9
|
||||
],
|
||||
[
|
||||
"Unhandled: Not callable: \\\\\\",
|
||||
1
|
||||
"TypeError: not a function",
|
||||
6
|
||||
],
|
||||
[
|
||||
"ReferenceError (undefined symbol)",
|
||||
1
|
||||
2
|
||||
],
|
||||
[
|
||||
"Unhandled: Not callable: \\\\\\",
|
||||
2
|
||||
],
|
||||
[
|
||||
"SyntaxError (parse/unsupported syntax)",
|
||||
1
|
||||
],
|
||||
[
|
||||
"TypeError: not a function",
|
||||
1
|
||||
],
|
||||
[
|
||||
"Unhandled: js-transpile-binop: unsupported op: >>>\\",
|
||||
1
|
||||
]
|
||||
],
|
||||
"pinned_commit": "d5e73fc8d2c663554fb72e2380a8c2bc1a318a33",
|
||||
"elapsed_seconds": 40.5,
|
||||
"workers": 7
|
||||
"elapsed_seconds": 157.9,
|
||||
"workers": 1
|
||||
}
|
||||
@@ -1,32 +1,31 @@
|
||||
# test262 scoreboard
|
||||
|
||||
Pinned commit: `d5e73fc8d2c663554fb72e2380a8c2bc1a318a33`
|
||||
Wall time: 40.5s
|
||||
Wall time: 157.9s
|
||||
|
||||
**Total:** 62/100 runnable passed (62.0%). Raw: pass=62 fail=29 skip=1130 timeout=9 total=1230.
|
||||
**Total:** 66/100 runnable passed (66.0%). Raw: pass=66 fail=25 skip=1130 timeout=9 total=1230.
|
||||
|
||||
## Top failure modes
|
||||
|
||||
- **24x** Test262Error (assertion failed)
|
||||
- **14x** Test262Error (assertion failed)
|
||||
- **9x** Timeout
|
||||
- **1x** Unhandled: Not callable: \\\
|
||||
- **1x** ReferenceError (undefined symbol)
|
||||
- **6x** TypeError: not a function
|
||||
- **2x** ReferenceError (undefined symbol)
|
||||
- **2x** Unhandled: Not callable: \\\
|
||||
- **1x** SyntaxError (parse/unsupported syntax)
|
||||
- **1x** TypeError: not a function
|
||||
- **1x** Unhandled: js-transpile-binop: unsupported op: >>>\
|
||||
|
||||
## Categories (worst pass-rate first, min 10 runnable)
|
||||
|
||||
| Category | Pass | Fail | Skip | Timeout | Total | Pass % |
|
||||
|---|---:|---:|---:|---:|---:|---:|
|
||||
| built-ins/String | 62 | 29 | 1123 | 9 | 1223 | 62.0% |
|
||||
| built-ins/String | 66 | 25 | 1123 | 9 | 1223 | 66.0% |
|
||||
|
||||
## Per-category top failures (min 10 runnable, worst first)
|
||||
|
||||
### built-ins/String (62/100 — 62.0%)
|
||||
### built-ins/String (66/100 — 66.0%)
|
||||
|
||||
- **24x** Test262Error (assertion failed)
|
||||
- **14x** Test262Error (assertion failed)
|
||||
- **9x** Timeout
|
||||
- **1x** Unhandled: Not callable: \\\
|
||||
- **1x** ReferenceError (undefined symbol)
|
||||
- **1x** SyntaxError (parse/unsupported syntax)
|
||||
- **6x** TypeError: not a function
|
||||
- **2x** ReferenceError (undefined symbol)
|
||||
- **2x** Unhandled: Not callable: \\\
|
||||
|
||||
@@ -295,6 +295,11 @@
|
||||
(list (js-sym "js-undefined?") (js-sym "_a")))
|
||||
(js-transpile r)
|
||||
(js-sym "_a"))))
|
||||
((= op ">>>")
|
||||
(list
|
||||
(js-sym "js-unsigned-rshift")
|
||||
(js-transpile l)
|
||||
(js-transpile r)))
|
||||
(else (error (str "js-transpile-binop: unsupported op: " op))))))
|
||||
|
||||
;; ── Object literal ────────────────────────────────────────────────
|
||||
|
||||
@@ -164,6 +164,8 @@ Append-only record of completed iterations. Loop writes one line per iteration:
|
||||
|
||||
- 2026-04-25 — **String fixes (constructor, indexOf/split/lastIndexOf multi-arg, fromCodePoint, matchAll, js-to-string dict fix).** Added `String.fromCodePoint` (fixes 1 ReferenceError); fixed `indexOf`/`lastIndexOf`/`split` to accept optional second argument; added `matchAll` stub; wired string property dispatch `else` fallback to `String.prototype` (fixes `'a'.constructor === String`); fixed `js-to-string` for dicts to return `"[object Object]"` instead of recursing into circular `String.prototype.constructor` structure. Scoreboard: String 42→43, timeouts 32→13. Total 162→202/300 (54%→67.3%). 529/530 unit, 148/148 slice.
|
||||
|
||||
- 2026-04-25 — **Number/String wrapper constructor-detection fix + Array.prototype.toString + js-to-number for wrappers + `>>>` operator.** `Number.__callable__` and `String.__callable__` now check `this.__proto__ === Number/String.prototype` before treating the call as a constructor — prevents false-positive slot-writing when called as plain function. `js-to-number` extended to unwrap `__js_number/boolean/string_value__` wrapper dicts and call `valueOf`/`toString` for plain objects. `Array.prototype.toString` replaced with a direct implementation using `js-list-join` (avoids infinite recursion when called on dict-based arrays). `>>>` (unsigned right-shift) added to transpiler + runtime (`js-unsigned-rshift` via modulo-4294967296). String test262 subset: 62→66/100. 529/530 unit, 147/148 slice.
|
||||
|
||||
- 2026-04-25 — **Math methods (trig/log/hyperbolic/bit ops).** Added 22 missing Math methods to `runtime.sx`: `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2`, `sinh`, `cosh`, `tanh`, `asinh`, `acosh`, `atanh`, `exp`, `log`, `log2`, `log10`, `expm1`, `log1p`, `clz32`, `imul`, `fround`. All use existing SX primitives. `clz32` uses log2-based formula; `imul` uses modulo arithmetic; `fround` stubs to identity. Addresses 36x "TypeError: not a function" in built-ins/Math (43% → ~79% expected). 529/530 unit (unchanged), 148/148 slice. Commit `5f38e49b`.
|
||||
|
||||
- 2026-04-25 — **`var` hoisting.** Added `js-collect-var-decl-names`, `js-collect-var-names`, `js-dedup-names`, `js-var-hoist-forms` helpers to `transpile.sx`. Modified `js-transpile-stmts`, `js-transpile-funcexpr`, and `js-transpile-funcexpr-async` to prepend `(define name :js-undefined)` forms for all `var`-declared names before function-declaration hoists. Shallow collection (direct statements only). 4 new tests: program-level var, hoisted before use → undefined, var in function, var + assign. 529/530 unit (+4), 148/148 slice unchanged. Commit `11315d91`.
|
||||
|
||||
Reference in New Issue
Block a user