js-on-sx: fn.length/.name/.call/.apply/.bind as properties (not just methods)
js-get-prop on a function receiver only routed .prototype before; now also handles .name (returns ''), .length (returns 0), and .call/.apply/.bind as bound function references. Previously Math.abs.length crashed with 'TypeError: length is not a function'. Similarly for arr.sort.call which is a common test262 pattern. Pass rate stable at 514/516. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -109,6 +109,19 @@
|
|||||||
((= key "length") 0)
|
((= key "length") 0)
|
||||||
(else :js-undefined))))
|
(else :js-undefined))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
js-invoke-function-bound
|
||||||
|
(fn
|
||||||
|
(recv key)
|
||||||
|
(cond
|
||||||
|
((= key "call")
|
||||||
|
(fn (&rest args) (js-invoke-function-method recv "call" args)))
|
||||||
|
((= key "apply")
|
||||||
|
(fn (&rest args) (js-invoke-function-method recv "apply" args)))
|
||||||
|
((= key "bind")
|
||||||
|
(fn (&rest args) (js-invoke-function-method recv "bind" args)))
|
||||||
|
(else :js-undefined))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
js-invoke-number-method
|
js-invoke-number-method
|
||||||
(fn
|
(fn
|
||||||
@@ -170,6 +183,13 @@
|
|||||||
(str "-" (js-num-to-str-radix-rec (- 0 int-n) radix ""))
|
(str "-" (js-num-to-str-radix-rec (- 0 int-n) radix ""))
|
||||||
(js-num-to-str-radix-rec int-n radix "")))))))
|
(js-num-to-str-radix-rec int-n radix "")))))))
|
||||||
|
|
||||||
|
;; parse a decimal number from a trimmed non-empty string.
|
||||||
|
;; s — source
|
||||||
|
;; i — cursor
|
||||||
|
;; acc — integer part so far (or total for decimals)
|
||||||
|
;; sign — 1 or -1
|
||||||
|
;; frac? — are we past the decimal point
|
||||||
|
;; fdiv — divisor used to scale fraction digits (only if frac?)
|
||||||
(define
|
(define
|
||||||
js-num-to-str-radix-rec
|
js-num-to-str-radix-rec
|
||||||
(fn
|
(fn
|
||||||
@@ -181,13 +201,6 @@
|
|||||||
((d (mod n radix)) (rest (js-math-trunc (/ n radix))))
|
((d (mod n radix)) (rest (js-math-trunc (/ n radix))))
|
||||||
(js-num-to-str-radix-rec rest radix (str (js-digit-char d) acc))))))
|
(js-num-to-str-radix-rec rest radix (str (js-digit-char d) acc))))))
|
||||||
|
|
||||||
;; parse a decimal number from a trimmed non-empty string.
|
|
||||||
;; s — source
|
|
||||||
;; i — cursor
|
|
||||||
;; acc — integer part so far (or total for decimals)
|
|
||||||
;; sign — 1 or -1
|
|
||||||
;; frac? — are we past the decimal point
|
|
||||||
;; fdiv — divisor used to scale fraction digits (only if frac?)
|
|
||||||
(define
|
(define
|
||||||
js-digit-char
|
js-digit-char
|
||||||
(fn
|
(fn
|
||||||
@@ -220,16 +233,19 @@
|
|||||||
"."
|
"."
|
||||||
(js-pad-int-str (js-to-string (js-math-trunc frac-abs)) d))))))))))
|
(js-pad-int-str (js-to-string (js-math-trunc frac-abs)) d))))))))))
|
||||||
|
|
||||||
|
;; ── String coercion (ToString) ────────────────────────────────────
|
||||||
|
|
||||||
(define
|
(define
|
||||||
js-pow-int
|
js-pow-int
|
||||||
(fn (b e) (if (<= e 0) 1 (* b (js-pow-int b (- e 1))))))
|
(fn (b e) (if (<= e 0) 1 (* b (js-pow-int b (- e 1))))))
|
||||||
|
|
||||||
;; ── String coercion (ToString) ────────────────────────────────────
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
js-pad-int-str
|
js-pad-int-str
|
||||||
(fn (s n) (if (>= (len s) n) s (js-pad-int-str (str "0" s) n))))
|
(fn (s n) (if (>= (len s) n) s (js-pad-int-str (str "0" s) n))))
|
||||||
|
|
||||||
|
;; ── Arithmetic (JS rules) ─────────────────────────────────────────
|
||||||
|
|
||||||
|
;; JS `+`: if either operand is a string → string concat, else numeric.
|
||||||
(define
|
(define
|
||||||
js-apply-fn
|
js-apply-fn
|
||||||
(fn
|
(fn
|
||||||
@@ -261,9 +277,6 @@
|
|||||||
(nth args 5)))
|
(nth args 5)))
|
||||||
(else (apply callable args))))))
|
(else (apply callable args))))))
|
||||||
|
|
||||||
;; ── Arithmetic (JS rules) ─────────────────────────────────────────
|
|
||||||
|
|
||||||
;; JS `+`: if either operand is a string → string concat, else numeric.
|
|
||||||
(define
|
(define
|
||||||
js-invoke-method
|
js-invoke-method
|
||||||
(fn
|
(fn
|
||||||
@@ -398,11 +411,11 @@
|
|||||||
((= code 122) "z")
|
((= code 122) "z")
|
||||||
(else ""))))
|
(else ""))))
|
||||||
|
|
||||||
|
;; Bitwise + logical-not
|
||||||
(define
|
(define
|
||||||
js-invoke-method-dyn
|
js-invoke-method-dyn
|
||||||
(fn (recv key args) (js-invoke-method recv key args)))
|
(fn (recv key args) (js-invoke-method recv key args)))
|
||||||
|
|
||||||
;; Bitwise + logical-not
|
|
||||||
(define
|
(define
|
||||||
js-call-plain
|
js-call-plain
|
||||||
(fn
|
(fn
|
||||||
@@ -414,6 +427,9 @@
|
|||||||
(js-call-with-this :js-undefined (get fn-val "__callable__") args))
|
(js-call-with-this :js-undefined (get fn-val "__callable__") args))
|
||||||
(else (js-call-with-this :js-undefined fn-val args)))))
|
(else (js-call-with-this :js-undefined fn-val args)))))
|
||||||
|
|
||||||
|
;; ── Equality ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
;; Strict equality (===): no coercion; js-undefined matches js-undefined.
|
||||||
(define
|
(define
|
||||||
js-new-call
|
js-new-call
|
||||||
(fn
|
(fn
|
||||||
@@ -429,9 +445,6 @@
|
|||||||
ret
|
ret
|
||||||
obj))))))
|
obj))))))
|
||||||
|
|
||||||
;; ── Equality ──────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
;; Strict equality (===): no coercion; js-undefined matches js-undefined.
|
|
||||||
(define
|
(define
|
||||||
js-instanceof
|
js-instanceof
|
||||||
(fn
|
(fn
|
||||||
@@ -445,6 +458,9 @@
|
|||||||
((proto (js-get-ctor-proto ctor)))
|
((proto (js-get-ctor-proto ctor)))
|
||||||
(js-instanceof-walk obj proto))))))
|
(js-instanceof-walk obj proto))))))
|
||||||
|
|
||||||
|
;; Abstract equality (==): type coercion rules.
|
||||||
|
;; Simplified: number↔string coerce both to number; null == undefined;
|
||||||
|
;; everything else falls back to strict equality.
|
||||||
(define
|
(define
|
||||||
js-instanceof-walk
|
js-instanceof-walk
|
||||||
(fn
|
(fn
|
||||||
@@ -460,9 +476,6 @@
|
|||||||
((not (= (type-of p) "dict")) false)
|
((not (= (type-of p) "dict")) false)
|
||||||
(else (js-instanceof-walk p proto))))))))
|
(else (js-instanceof-walk p proto))))))))
|
||||||
|
|
||||||
;; Abstract equality (==): type coercion rules.
|
|
||||||
;; Simplified: number↔string coerce both to number; null == undefined;
|
|
||||||
;; everything else falls back to strict equality.
|
|
||||||
(define
|
(define
|
||||||
js-in
|
js-in
|
||||||
(fn
|
(fn
|
||||||
@@ -471,6 +484,11 @@
|
|||||||
((not (= (type-of obj) "dict")) false)
|
((not (= (type-of obj) "dict")) false)
|
||||||
(else (js-in-walk obj (js-to-string key))))))
|
(else (js-in-walk obj (js-to-string key))))))
|
||||||
|
|
||||||
|
;; ── Relational comparisons ────────────────────────────────────────
|
||||||
|
|
||||||
|
;; Abstract relational comparison from ES5.
|
||||||
|
;; Numbers compare numerically; two strings compare lexicographically;
|
||||||
|
;; mixed types coerce both to numbers.
|
||||||
(define
|
(define
|
||||||
js-in-walk
|
js-in-walk
|
||||||
(fn
|
(fn
|
||||||
@@ -481,11 +499,6 @@
|
|||||||
((dict-has? obj "__proto__") (js-in-walk (get obj "__proto__") skey))
|
((dict-has? obj "__proto__") (js-in-walk (get obj "__proto__") skey))
|
||||||
(else false))))
|
(else false))))
|
||||||
|
|
||||||
;; ── Relational comparisons ────────────────────────────────────────
|
|
||||||
|
|
||||||
;; Abstract relational comparison from ES5.
|
|
||||||
;; Numbers compare numerically; two strings compare lexicographically;
|
|
||||||
;; mixed types coerce both to numbers.
|
|
||||||
(define
|
(define
|
||||||
Error
|
Error
|
||||||
(fn
|
(fn
|
||||||
@@ -588,8 +601,6 @@
|
|||||||
(= t "component")
|
(= t "component")
|
||||||
(and (= t "dict") (contains? (keys v) "__callable__"))))))
|
(and (= t "dict") (contains? (keys v) "__callable__"))))))
|
||||||
|
|
||||||
(define __js_proto_table__ (dict))
|
|
||||||
|
|
||||||
;; ── Property access ───────────────────────────────────────────────
|
;; ── Property access ───────────────────────────────────────────────
|
||||||
|
|
||||||
;; obj[key] or obj.key in JS. Handles:
|
;; obj[key] or obj.key in JS. Handles:
|
||||||
@@ -597,10 +608,13 @@
|
|||||||
;; • lists indexed by number (incl. .length)
|
;; • lists indexed by number (incl. .length)
|
||||||
;; • strings indexed by number (incl. .length)
|
;; • strings indexed by number (incl. .length)
|
||||||
;; Returns js-undefined if the key is absent.
|
;; Returns js-undefined if the key is absent.
|
||||||
|
(define __js_proto_table__ (dict))
|
||||||
|
|
||||||
(define __js_next_id__ (dict))
|
(define __js_next_id__ (dict))
|
||||||
|
|
||||||
(dict-set! __js_next_id__ "n" 0)
|
(dict-set! __js_next_id__ "n" 0)
|
||||||
|
|
||||||
|
;; Setter — mutates the dict. Returns the new value (JS assignment yields rhs).
|
||||||
(define
|
(define
|
||||||
js-get-ctor-proto
|
js-get-ctor-proto
|
||||||
(fn
|
(fn
|
||||||
@@ -612,7 +626,10 @@
|
|||||||
(else
|
(else
|
||||||
(let ((p (dict))) (begin (dict-set! __js_proto_table__ id p) p)))))))
|
(let ((p (dict))) (begin (dict-set! __js_proto_table__ id p) p)))))))
|
||||||
|
|
||||||
;; Setter — mutates the dict. Returns the new value (JS assignment yields rhs).
|
;; ── Short-circuit logical ops ─────────────────────────────────────
|
||||||
|
|
||||||
|
;; `a && b` in JS: if a is truthy return b else return a. The thunk
|
||||||
|
;; form defers evaluation of b — the transpiler passes (fn () b).
|
||||||
(define
|
(define
|
||||||
js-reset-ctor-proto!
|
js-reset-ctor-proto!
|
||||||
(fn
|
(fn
|
||||||
@@ -621,16 +638,15 @@
|
|||||||
((id (js-ctor-id ctor)) (p (dict)))
|
((id (js-ctor-id ctor)) (p (dict)))
|
||||||
(begin (dict-set! __js_proto_table__ id p) p))))
|
(begin (dict-set! __js_proto_table__ id p) p))))
|
||||||
|
|
||||||
;; ── Short-circuit logical ops ─────────────────────────────────────
|
|
||||||
|
|
||||||
;; `a && b` in JS: if a is truthy return b else return a. The thunk
|
|
||||||
;; form defers evaluation of b — the transpiler passes (fn () b).
|
|
||||||
(define
|
(define
|
||||||
js-set-ctor-proto!
|
js-set-ctor-proto!
|
||||||
(fn
|
(fn
|
||||||
(ctor proto)
|
(ctor proto)
|
||||||
(let ((id (js-ctor-id ctor))) (dict-set! __js_proto_table__ id proto))))
|
(let ((id (js-ctor-id ctor))) (dict-set! __js_proto_table__ id proto))))
|
||||||
|
|
||||||
|
;; ── console.log ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
;; Trivial bridge. `log-info` is available on OCaml; fall back to print.
|
||||||
(define
|
(define
|
||||||
js-ctor-id
|
js-ctor-id
|
||||||
(fn
|
(fn
|
||||||
@@ -640,9 +656,6 @@
|
|||||||
(get ctor "__ctor_id__"))
|
(get ctor "__ctor_id__"))
|
||||||
(else (inspect ctor)))))
|
(else (inspect ctor)))))
|
||||||
|
|
||||||
;; ── console.log ───────────────────────────────────────────────────
|
|
||||||
|
|
||||||
;; Trivial bridge. `log-info` is available on OCaml; fall back to print.
|
|
||||||
(define
|
(define
|
||||||
js-typeof
|
js-typeof
|
||||||
(fn
|
(fn
|
||||||
@@ -660,6 +673,8 @@
|
|||||||
"function")
|
"function")
|
||||||
(else "object"))))
|
(else "object"))))
|
||||||
|
|
||||||
|
;; ── Math object ───────────────────────────────────────────────────
|
||||||
|
|
||||||
(define
|
(define
|
||||||
js-to-boolean
|
js-to-boolean
|
||||||
(fn
|
(fn
|
||||||
@@ -671,9 +686,6 @@
|
|||||||
((= v 0) false)
|
((= v 0) false)
|
||||||
((= v "") false)
|
((= v "") false)
|
||||||
(else true))))
|
(else true))))
|
||||||
|
|
||||||
;; ── Math object ───────────────────────────────────────────────────
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
js-to-number
|
js-to-number
|
||||||
(fn
|
(fn
|
||||||
@@ -743,7 +755,8 @@
|
|||||||
(define js-trim (fn (s) (js-trim-left (js-trim-right s))))
|
(define js-trim (fn (s) (js-trim-left (js-trim-right s))))
|
||||||
(define
|
(define
|
||||||
js-trim-left
|
js-trim-left
|
||||||
(fn (s) (let ((n (len s))) (js-trim-left-at s n 0))))
|
(fn (s) (let ((n (len s))) (js-trim-left-at s n 0)))) ; deterministic placeholder for tests
|
||||||
|
|
||||||
(define
|
(define
|
||||||
js-trim-left-at
|
js-trim-left-at
|
||||||
(fn
|
(fn
|
||||||
@@ -751,16 +764,16 @@
|
|||||||
(cond
|
(cond
|
||||||
((>= i n) "")
|
((>= i n) "")
|
||||||
((js-is-space? (char-at s i)) (js-trim-left-at s n (+ i 1)))
|
((js-is-space? (char-at s i)) (js-trim-left-at s n (+ i 1)))
|
||||||
(else (substr s i n))))) ; deterministic placeholder for tests
|
(else (substr s i n)))))
|
||||||
|
|
||||||
(define
|
|
||||||
js-trim-right
|
|
||||||
(fn (s) (let ((n (len s))) (js-trim-right-at s n))))
|
|
||||||
|
|
||||||
;; The global object — lookup table for JS names that aren't in the
|
;; The global object — lookup table for JS names that aren't in the
|
||||||
;; SX env. Transpiled idents look up locally first; globals here are a
|
;; SX env. Transpiled idents look up locally first; globals here are a
|
||||||
;; fallback, but most slice programs reference `console`, `Math`,
|
;; fallback, but most slice programs reference `console`, `Math`,
|
||||||
;; `undefined` as plain symbols, which we bind as defines above.
|
;; `undefined` as plain symbols, which we bind as defines above.
|
||||||
|
(define
|
||||||
|
js-trim-right
|
||||||
|
(fn (s) (let ((n (len s))) (js-trim-right-at s n))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
js-trim-right-at
|
js-trim-right-at
|
||||||
(fn
|
(fn
|
||||||
@@ -1941,8 +1954,12 @@
|
|||||||
(js-dict-get-walk obj (js-to-string key)))
|
(js-dict-get-walk obj (js-to-string key)))
|
||||||
((and (= obj Promise) (dict-has? __js_promise_statics__ (js-to-string key)))
|
((and (= obj Promise) (dict-has? __js_promise_statics__ (js-to-string key)))
|
||||||
(get __js_promise_statics__ (js-to-string key)))
|
(get __js_promise_statics__ (js-to-string key)))
|
||||||
((and (js-function? obj) (= (js-to-string key) "prototype"))
|
((and (js-function? obj) (or (= key "prototype") (= key "name") (= key "length") (= key "call") (= key "apply") (= key "bind")))
|
||||||
(js-get-ctor-proto obj))
|
(cond
|
||||||
|
((= key "prototype") (js-get-ctor-proto obj))
|
||||||
|
((= key "name") "")
|
||||||
|
((= key "length") 0)
|
||||||
|
(else (js-invoke-function-bound obj key))))
|
||||||
(else js-undefined))))
|
(else js-undefined))))
|
||||||
(define
|
(define
|
||||||
js-dict-get-walk
|
js-dict-get-walk
|
||||||
|
|||||||
Reference in New Issue
Block a user