js-on-sx: globalThis + eval stub transpile-time mappings
JS 'globalThis' now rewrites to SX (js-global) — the global object dict. JS 'eval' rewrites to js-global-eval, a no-op stub that echoes its first arg. Many test262 tests probe eval's existence or pass simple literals through it; a no-op is better than 'Undefined symbol: eval'. A full eval would require plumbing js-eval into the runtime with access to the enclosing lexical scope — non-trivial. The stub unblocks tests that just need eval to be callable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,12 @@
|
||||
|
||||
;; ── Boolean coercion (ToBoolean) ──────────────────────────────────
|
||||
|
||||
(define
|
||||
js-global-eval
|
||||
(fn (&rest args) (if (empty? args) :js-undefined (nth args 0))))
|
||||
|
||||
;; ── Numeric coercion (ToNumber) ───────────────────────────────────
|
||||
|
||||
(define
|
||||
js-max-value-loop
|
||||
(fn
|
||||
@@ -37,22 +43,20 @@
|
||||
cur
|
||||
(js-max-value-loop next (- steps 1)))))))
|
||||
|
||||
;; ── Numeric coercion (ToNumber) ───────────────────────────────────
|
||||
|
||||
(define js-undefined :js-undefined)
|
||||
|
||||
;; 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? (fn (v) (= v :js-undefined)))
|
||||
(define js-undefined :js-undefined)
|
||||
|
||||
;; 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
|
||||
;; slice doesn't crash).
|
||||
(define __js_this_cell__ (dict))
|
||||
(define js-undefined? (fn (v) (= v :js-undefined)))
|
||||
|
||||
;; Minimal string->number for the slice. Handles integers, negatives,
|
||||
;; and simple decimals. Returns 0 on malformed input.
|
||||
(define __js_this_cell__ (dict))
|
||||
|
||||
(define
|
||||
js-this
|
||||
(fn
|
||||
@@ -116,6 +120,13 @@
|
||||
0
|
||||
(+ 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
|
||||
js-invoke-function-method
|
||||
(fn
|
||||
@@ -153,13 +164,6 @@
|
||||
((= key "length") (js-fn-length recv))
|
||||
(else :js-undefined))))
|
||||
|
||||
;; 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
|
||||
js-invoke-function-bound
|
||||
(fn
|
||||
@@ -202,6 +206,8 @@
|
||||
(js-to-string key)
|
||||
" is not a function (on number)"))))))
|
||||
|
||||
;; ── String coercion (ToString) ────────────────────────────────────
|
||||
|
||||
(define
|
||||
js-invoke-function-objproto
|
||||
(fn
|
||||
@@ -218,8 +224,6 @@
|
||||
((= key "toLocaleString") "function () { [native code] }")
|
||||
(else :js-undefined))))
|
||||
|
||||
;; ── String coercion (ToString) ────────────────────────────────────
|
||||
|
||||
(define
|
||||
js-invoke-boolean-method
|
||||
(fn
|
||||
@@ -234,6 +238,9 @@
|
||||
(js-to-string key)
|
||||
" is not a function (on boolean)"))))))
|
||||
|
||||
;; ── Arithmetic (JS rules) ─────────────────────────────────────────
|
||||
|
||||
;; JS `+`: if either operand is a string → string concat, else numeric.
|
||||
(define
|
||||
js-num-to-str-radix
|
||||
(fn
|
||||
@@ -252,9 +259,6 @@
|
||||
(str "-" (js-num-to-str-radix-rec (- 0 int-n) radix ""))
|
||||
(js-num-to-str-radix-rec int-n radix "")))))))
|
||||
|
||||
;; ── Arithmetic (JS rules) ─────────────────────────────────────────
|
||||
|
||||
;; JS `+`: if either operand is a string → string concat, else numeric.
|
||||
(define
|
||||
js-num-to-str-radix-rec
|
||||
(fn
|
||||
@@ -373,6 +377,7 @@
|
||||
(error
|
||||
(str "TypeError: " (js-to-string key) " is not a function")))))))))
|
||||
|
||||
;; Bitwise + logical-not
|
||||
(define
|
||||
js-object-builtin-method?
|
||||
(fn
|
||||
@@ -385,7 +390,6 @@
|
||||
(= name "valueOf")
|
||||
(= name "toLocaleString"))))
|
||||
|
||||
;; Bitwise + logical-not
|
||||
(define
|
||||
js-invoke-object-method
|
||||
(fn
|
||||
@@ -407,13 +411,16 @@
|
||||
((= name "toLocaleString") "[object Object]")
|
||||
(else js-undefined))))
|
||||
|
||||
(define js-upper-case (fn (s) (js-case-loop s 0 "" true)))
|
||||
|
||||
;; ── Equality ──────────────────────────────────────────────────────
|
||||
|
||||
;; Strict equality (===): no coercion; js-undefined matches js-undefined.
|
||||
(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.
|
||||
;; Simplified: number↔string coerce both to number; null == undefined;
|
||||
;; everything else falls back to strict equality.
|
||||
(define
|
||||
js-case-loop
|
||||
(fn
|
||||
@@ -429,9 +436,6 @@
|
||||
((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?))))))))
|
||||
|
||||
;; Abstract equality (==): type coercion rules.
|
||||
;; Simplified: number↔string coerce both to number; null == undefined;
|
||||
;; everything else falls back to strict equality.
|
||||
(define
|
||||
js-code-to-char
|
||||
(fn
|
||||
@@ -491,15 +495,15 @@
|
||||
((= code 122) "z")
|
||||
(else ""))))
|
||||
|
||||
(define
|
||||
js-invoke-method-dyn
|
||||
(fn (recv key args) (js-invoke-method recv key args)))
|
||||
|
||||
;; ── Relational comparisons ────────────────────────────────────────
|
||||
|
||||
;; Abstract relational comparison from ES5.
|
||||
;; Numbers compare numerically; two strings compare lexicographically;
|
||||
;; mixed types coerce both to numbers.
|
||||
(define
|
||||
js-invoke-method-dyn
|
||||
(fn (recv key args) (js-invoke-method recv key args)))
|
||||
|
||||
(define
|
||||
js-call-plain
|
||||
(fn
|
||||
@@ -572,6 +576,13 @@
|
||||
((dict-has? obj "__proto__") (js-in-walk (get obj "__proto__") skey))
|
||||
(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
|
||||
Error
|
||||
(fn
|
||||
@@ -590,13 +601,6 @@
|
||||
nil)
|
||||
this))))
|
||||
|
||||
;; ── 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
|
||||
TypeError
|
||||
(fn
|
||||
@@ -633,6 +637,7 @@
|
||||
nil)
|
||||
this))))
|
||||
|
||||
;; Setter — mutates the dict. Returns the new value (JS assignment yields rhs).
|
||||
(define
|
||||
SyntaxError
|
||||
(fn
|
||||
@@ -651,7 +656,10 @@
|
||||
nil)
|
||||
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
|
||||
ReferenceError
|
||||
(fn
|
||||
@@ -670,10 +678,6 @@
|
||||
nil)
|
||||
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
|
||||
js-function?
|
||||
(fn
|
||||
@@ -686,17 +690,16 @@
|
||||
(= t "component")
|
||||
(and (= t "dict") (contains? (keys v) "__callable__"))))))
|
||||
|
||||
(define __js_proto_table__ (dict))
|
||||
|
||||
;; ── console.log ───────────────────────────────────────────────────
|
||||
|
||||
;; Trivial bridge. `log-info` is available on OCaml; fall back to print.
|
||||
(define __js_next_id__ (dict))
|
||||
(define __js_proto_table__ (dict))
|
||||
|
||||
(dict-set! __js_next_id__ "n" 0)
|
||||
(define __js_next_id__ (dict))
|
||||
|
||||
;; ── Math object ───────────────────────────────────────────────────
|
||||
|
||||
(dict-set! __js_next_id__ "n" 0)
|
||||
(define
|
||||
js-get-ctor-proto
|
||||
(fn
|
||||
@@ -778,11 +781,16 @@
|
||||
((= trimmed "+Infinity") (js-infinity-value))
|
||||
((= trimmed "-Infinity") (- 0 (js-infinity-value)))
|
||||
((js-is-numeric-string? trimmed) (js-parse-num-safe trimmed))
|
||||
(else (js-nan-value))))))
|
||||
(else (js-nan-value)))))) ; deterministic placeholder for tests
|
||||
|
||||
(define
|
||||
js-is-numeric-string?
|
||||
(fn (s) (js-is-numeric-loop s 0 false false false))) ; deterministic placeholder for tests
|
||||
(fn (s) (js-is-numeric-loop s 0 false false false)))
|
||||
|
||||
;; 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
|
||||
;; fallback, but most slice programs reference `console`, `Math`,
|
||||
;; `undefined` as plain symbols, which we bind as defines above.
|
||||
(define
|
||||
js-is-numeric-loop
|
||||
(fn
|
||||
@@ -812,10 +820,6 @@
|
||||
false)))
|
||||
(else false)))))))
|
||||
|
||||
;; 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
|
||||
;; fallback, but most slice programs reference `console`, `Math`,
|
||||
;; `undefined` as plain symbols, which we bind as defines above.
|
||||
(define js-parse-num-safe (fn (s) (cond (else (js-num-from-string s)))))
|
||||
|
||||
(define
|
||||
|
||||
@@ -159,6 +159,8 @@
|
||||
((= name "undefined") (list (js-sym "quote") :js-undefined))
|
||||
((= name "NaN") (list (js-sym "js-nan-value")))
|
||||
((= name "Infinity") (list (js-sym "js-infinity-value")))
|
||||
((= name "eval") (js-sym "js-global-eval"))
|
||||
((= name "globalThis") (js-sym "js-global"))
|
||||
(else (js-sym name)))))
|
||||
|
||||
;; ── Unary ops ─────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user