js-on-sx: Function global stub (constructor throws, prototype has stubs)
Several tests check 'new Function("return 1")' — we can't actually
implement that (would need runtime JS eval). Now Function is a dict with
__callable__ that throws TypeError, and a prototype containing call/apply/
bind/toString/length/name stubs so code that probes Function.prototype
doesn't crash with 'Undefined symbol: Function'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,12 +23,17 @@
|
|||||||
|
|
||||||
;; ── Boolean coercion (ToBoolean) ──────────────────────────────────
|
;; ── Boolean coercion (ToBoolean) ──────────────────────────────────
|
||||||
|
|
||||||
|
(define js-function-global {:__callable__ (fn (&rest args) (error "TypeError: Function constructor not supported")) :prototype {:call (fn (&rest args) :js-undefined) :length 0 :bind (fn (&rest args) (fn () :js-undefined)) :toString (fn () "function () { [native code] }") :apply (fn (&rest args) :js-undefined) :name ""}})
|
||||||
|
|
||||||
|
;; ── Numeric coercion (ToNumber) ───────────────────────────────────
|
||||||
|
|
||||||
(define
|
(define
|
||||||
js-global-eval
|
js-global-eval
|
||||||
(fn (&rest args) (if (empty? args) :js-undefined (nth args 0))))
|
(fn (&rest args) (if (empty? args) :js-undefined (nth args 0))))
|
||||||
|
|
||||||
;; ── Numeric coercion (ToNumber) ───────────────────────────────────
|
;; Parse a JS-style string to a number. For the slice we just delegate
|
||||||
|
;; to SX's number parser via `str->num`/`parse-number`. Empty string → 0
|
||||||
|
;; per JS (technically ToNumber("") === 0).
|
||||||
(define
|
(define
|
||||||
js-max-value-loop
|
js-max-value-loop
|
||||||
(fn
|
(fn
|
||||||
@@ -43,18 +48,15 @@
|
|||||||
cur
|
cur
|
||||||
(js-max-value-loop next (- steps 1)))))))
|
(js-max-value-loop next (- steps 1)))))))
|
||||||
|
|
||||||
;; Parse a JS-style string to a number. For the slice we just delegate
|
|
||||||
;; to SX's number parser via `str->num`/`parse-number`. Empty string → 0
|
|
||||||
;; per JS (technically ToNumber("") === 0).
|
|
||||||
(define js-undefined :js-undefined)
|
|
||||||
|
|
||||||
;; Safe number-parser. Tries to call an SX primitive that can parse
|
;; Safe number-parser. Tries to call an SX primitive that can parse
|
||||||
;; strings to numbers; on failure returns 0 (stand-in for NaN so the
|
;; strings to numbers; on failure returns 0 (stand-in for NaN so the
|
||||||
;; slice doesn't crash).
|
;; slice doesn't crash).
|
||||||
(define js-undefined? (fn (v) (= v :js-undefined)))
|
(define js-undefined :js-undefined)
|
||||||
|
|
||||||
;; Minimal string->number for the slice. Handles integers, negatives,
|
;; Minimal string->number for the slice. Handles integers, negatives,
|
||||||
;; and simple decimals. Returns 0 on malformed input.
|
;; and simple decimals. Returns 0 on malformed input.
|
||||||
|
(define js-undefined? (fn (v) (= v :js-undefined)))
|
||||||
|
|
||||||
(define __js_this_cell__ (dict))
|
(define __js_this_cell__ (dict))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -106,6 +108,13 @@
|
|||||||
(js-fn-length (get f "__callable__")))
|
(js-fn-length (get f "__callable__")))
|
||||||
(else 0)))))
|
(else 0)))))
|
||||||
|
|
||||||
|
;; 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-count-real-params
|
js-count-real-params
|
||||||
(fn
|
(fn
|
||||||
@@ -120,13 +129,6 @@
|
|||||||
0
|
0
|
||||||
(+ 1 (js-count-real-params (rest params)))))))))
|
(+ 1 (js-count-real-params (rest params)))))))))
|
||||||
|
|
||||||
;; 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-invoke-function-method
|
js-invoke-function-method
|
||||||
(fn
|
(fn
|
||||||
@@ -177,6 +179,8 @@
|
|||||||
(fn (&rest args) (js-invoke-function-method recv "bind" args)))
|
(fn (&rest args) (js-invoke-function-method recv "bind" args)))
|
||||||
(else :js-undefined))))
|
(else :js-undefined))))
|
||||||
|
|
||||||
|
;; ── String coercion (ToString) ────────────────────────────────────
|
||||||
|
|
||||||
(define
|
(define
|
||||||
js-invoke-number-method
|
js-invoke-number-method
|
||||||
(fn
|
(fn
|
||||||
@@ -206,8 +210,6 @@
|
|||||||
(js-to-string key)
|
(js-to-string key)
|
||||||
" is not a function (on number)"))))))
|
" is not a function (on number)"))))))
|
||||||
|
|
||||||
;; ── String coercion (ToString) ────────────────────────────────────
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
js-invoke-function-objproto
|
js-invoke-function-objproto
|
||||||
(fn
|
(fn
|
||||||
@@ -224,6 +226,9 @@
|
|||||||
((= key "toLocaleString") "function () { [native code] }")
|
((= key "toLocaleString") "function () { [native code] }")
|
||||||
(else :js-undefined))))
|
(else :js-undefined))))
|
||||||
|
|
||||||
|
;; ── Arithmetic (JS rules) ─────────────────────────────────────────
|
||||||
|
|
||||||
|
;; JS `+`: if either operand is a string → string concat, else numeric.
|
||||||
(define
|
(define
|
||||||
js-invoke-boolean-method
|
js-invoke-boolean-method
|
||||||
(fn
|
(fn
|
||||||
@@ -238,9 +243,6 @@
|
|||||||
(js-to-string key)
|
(js-to-string key)
|
||||||
" is not a function (on boolean)"))))))
|
" is not a function (on boolean)"))))))
|
||||||
|
|
||||||
;; ── Arithmetic (JS rules) ─────────────────────────────────────────
|
|
||||||
|
|
||||||
;; JS `+`: if either operand is a string → string concat, else numeric.
|
|
||||||
(define
|
(define
|
||||||
js-num-to-str-radix
|
js-num-to-str-radix
|
||||||
(fn
|
(fn
|
||||||
@@ -352,6 +354,7 @@
|
|||||||
(nth args 5)))
|
(nth args 5)))
|
||||||
(else (apply callable args))))))
|
(else (apply callable args))))))
|
||||||
|
|
||||||
|
;; Bitwise + logical-not
|
||||||
(define
|
(define
|
||||||
js-invoke-method
|
js-invoke-method
|
||||||
(fn
|
(fn
|
||||||
@@ -377,7 +380,6 @@
|
|||||||
(error
|
(error
|
||||||
(str "TypeError: " (js-to-string key) " is not a function")))))))))
|
(str "TypeError: " (js-to-string key) " is not a function")))))))))
|
||||||
|
|
||||||
;; Bitwise + logical-not
|
|
||||||
(define
|
(define
|
||||||
js-object-builtin-method?
|
js-object-builtin-method?
|
||||||
(fn
|
(fn
|
||||||
@@ -390,6 +392,9 @@
|
|||||||
(= name "valueOf")
|
(= name "valueOf")
|
||||||
(= name "toLocaleString"))))
|
(= name "toLocaleString"))))
|
||||||
|
|
||||||
|
;; ── Equality ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
;; Strict equality (===): no coercion; js-undefined matches js-undefined.
|
||||||
(define
|
(define
|
||||||
js-invoke-object-method
|
js-invoke-object-method
|
||||||
(fn
|
(fn
|
||||||
@@ -411,16 +416,13 @@
|
|||||||
((= name "toLocaleString") "[object Object]")
|
((= name "toLocaleString") "[object Object]")
|
||||||
(else js-undefined))))
|
(else js-undefined))))
|
||||||
|
|
||||||
;; ── Equality ──────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
;; Strict equality (===): no coercion; js-undefined matches js-undefined.
|
|
||||||
(define js-upper-case (fn (s) (js-case-loop s 0 "" true)))
|
(define js-upper-case (fn (s) (js-case-loop s 0 "" true)))
|
||||||
|
|
||||||
(define js-lower-case (fn (s) (js-case-loop s 0 "" false)))
|
|
||||||
|
|
||||||
;; Abstract equality (==): type coercion rules.
|
;; Abstract equality (==): type coercion rules.
|
||||||
;; Simplified: number↔string coerce both to number; null == undefined;
|
;; Simplified: number↔string coerce both to number; null == undefined;
|
||||||
;; everything else falls back to strict equality.
|
;; everything else falls back to strict equality.
|
||||||
|
(define js-lower-case (fn (s) (js-case-loop s 0 "" false)))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
js-case-loop
|
js-case-loop
|
||||||
(fn
|
(fn
|
||||||
@@ -436,6 +438,11 @@
|
|||||||
((cv (cond ((and to-upper? (>= cc 97) (<= cc 122)) (js-code-to-char (- cc 32))) ((and (not to-upper?) (>= cc 65) (<= cc 90)) (js-code-to-char (+ cc 32))) (else c))))
|
((cv (cond ((and to-upper? (>= cc 97) (<= cc 122)) (js-code-to-char (- cc 32))) ((and (not to-upper?) (>= cc 65) (<= cc 90)) (js-code-to-char (+ cc 32))) (else c))))
|
||||||
(js-case-loop s (+ i 1) (str acc cv) to-upper?))))))))
|
(js-case-loop s (+ i 1) (str acc cv) to-upper?))))))))
|
||||||
|
|
||||||
|
;; ── Relational comparisons ────────────────────────────────────────
|
||||||
|
|
||||||
|
;; Abstract relational comparison from ES5.
|
||||||
|
;; Numbers compare numerically; two strings compare lexicographically;
|
||||||
|
;; mixed types coerce both to numbers.
|
||||||
(define
|
(define
|
||||||
js-code-to-char
|
js-code-to-char
|
||||||
(fn
|
(fn
|
||||||
@@ -495,11 +502,6 @@
|
|||||||
((= code 122) "z")
|
((= code 122) "z")
|
||||||
(else ""))))
|
(else ""))))
|
||||||
|
|
||||||
;; ── Relational comparisons ────────────────────────────────────────
|
|
||||||
|
|
||||||
;; Abstract relational comparison from ES5.
|
|
||||||
;; Numbers compare numerically; two strings compare lexicographically;
|
|
||||||
;; mixed types coerce both to numbers.
|
|
||||||
(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)))
|
||||||
@@ -566,6 +568,13 @@
|
|||||||
((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))))))
|
||||||
|
|
||||||
|
;; ── Property access ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
;; obj[key] or obj.key in JS. Handles:
|
||||||
|
;; • dicts keyed by string
|
||||||
|
;; • lists indexed by number (incl. .length)
|
||||||
|
;; • strings indexed by number (incl. .length)
|
||||||
|
;; Returns js-undefined if the key is absent.
|
||||||
(define
|
(define
|
||||||
js-in-walk
|
js-in-walk
|
||||||
(fn
|
(fn
|
||||||
@@ -576,13 +585,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))))
|
||||||
|
|
||||||
;; ── Property access ───────────────────────────────────────────────
|
|
||||||
|
|
||||||
;; obj[key] or obj.key in JS. Handles:
|
|
||||||
;; • dicts keyed by string
|
|
||||||
;; • lists indexed by number (incl. .length)
|
|
||||||
;; • strings indexed by number (incl. .length)
|
|
||||||
;; Returns js-undefined if the key is absent.
|
|
||||||
(define
|
(define
|
||||||
Error
|
Error
|
||||||
(fn
|
(fn
|
||||||
@@ -619,6 +621,7 @@
|
|||||||
nil)
|
nil)
|
||||||
this))))
|
this))))
|
||||||
|
|
||||||
|
;; Setter — mutates the dict. Returns the new value (JS assignment yields rhs).
|
||||||
(define
|
(define
|
||||||
RangeError
|
RangeError
|
||||||
(fn
|
(fn
|
||||||
@@ -637,7 +640,10 @@
|
|||||||
nil)
|
nil)
|
||||||
this))))
|
this))))
|
||||||
|
|
||||||
;; 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
|
||||||
SyntaxError
|
SyntaxError
|
||||||
(fn
|
(fn
|
||||||
@@ -656,10 +662,6 @@
|
|||||||
nil)
|
nil)
|
||||||
this))))
|
this))))
|
||||||
|
|
||||||
;; ── 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
|
||||||
ReferenceError
|
ReferenceError
|
||||||
(fn
|
(fn
|
||||||
@@ -678,6 +680,9 @@
|
|||||||
nil)
|
nil)
|
||||||
this))))
|
this))))
|
||||||
|
|
||||||
|
;; ── console.log ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
;; Trivial bridge. `log-info` is available on OCaml; fall back to print.
|
||||||
(define
|
(define
|
||||||
js-function?
|
js-function?
|
||||||
(fn
|
(fn
|
||||||
@@ -690,15 +695,11 @@
|
|||||||
(= t "component")
|
(= t "component")
|
||||||
(and (= t "dict") (contains? (keys v) "__callable__"))))))
|
(and (= t "dict") (contains? (keys v) "__callable__"))))))
|
||||||
|
|
||||||
;; ── console.log ───────────────────────────────────────────────────
|
|
||||||
|
|
||||||
;; Trivial bridge. `log-info` is available on OCaml; fall back to print.
|
|
||||||
(define __js_proto_table__ (dict))
|
(define __js_proto_table__ (dict))
|
||||||
|
|
||||||
(define __js_next_id__ (dict))
|
|
||||||
|
|
||||||
;; ── Math object ───────────────────────────────────────────────────
|
;; ── Math object ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define __js_next_id__ (dict))
|
||||||
(dict-set! __js_next_id__ "n" 0)
|
(dict-set! __js_next_id__ "n" 0)
|
||||||
(define
|
(define
|
||||||
js-get-ctor-proto
|
js-get-ctor-proto
|
||||||
@@ -768,7 +769,8 @@
|
|||||||
((= v false) 0)
|
((= v false) 0)
|
||||||
((= (type-of v) "number") v)
|
((= (type-of v) "number") v)
|
||||||
((= (type-of v) "string") (js-string-to-number v))
|
((= (type-of v) "string") (js-string-to-number v))
|
||||||
(else 0))))
|
(else 0)))) ; deterministic placeholder for tests
|
||||||
|
|
||||||
(define
|
(define
|
||||||
js-string-to-number
|
js-string-to-number
|
||||||
(fn
|
(fn
|
||||||
@@ -781,16 +783,16 @@
|
|||||||
((= trimmed "+Infinity") (js-infinity-value))
|
((= trimmed "+Infinity") (js-infinity-value))
|
||||||
((= trimmed "-Infinity") (- 0 (js-infinity-value)))
|
((= trimmed "-Infinity") (- 0 (js-infinity-value)))
|
||||||
((js-is-numeric-string? trimmed) (js-parse-num-safe trimmed))
|
((js-is-numeric-string? trimmed) (js-parse-num-safe trimmed))
|
||||||
(else (js-nan-value)))))) ; deterministic placeholder for tests
|
(else (js-nan-value))))))
|
||||||
|
|
||||||
(define
|
|
||||||
js-is-numeric-string?
|
|
||||||
(fn (s) (js-is-numeric-loop s 0 false false false)))
|
|
||||||
|
|
||||||
;; 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-is-numeric-string?
|
||||||
|
(fn (s) (js-is-numeric-loop s 0 false false false)))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
js-is-numeric-loop
|
js-is-numeric-loop
|
||||||
(fn
|
(fn
|
||||||
|
|||||||
@@ -161,6 +161,7 @@
|
|||||||
((= name "Infinity") (list (js-sym "js-infinity-value")))
|
((= name "Infinity") (list (js-sym "js-infinity-value")))
|
||||||
((= name "eval") (js-sym "js-global-eval"))
|
((= name "eval") (js-sym "js-global-eval"))
|
||||||
((= name "globalThis") (js-sym "js-global"))
|
((= name "globalThis") (js-sym "js-global"))
|
||||||
|
((= name "Function") (js-sym "js-function-global"))
|
||||||
(else (js-sym name)))))
|
(else (js-sym name)))))
|
||||||
|
|
||||||
;; ── Unary ops ─────────────────────────────────────────────────────
|
;; ── Unary ops ─────────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user