From 094945d86add0802634c4ca87d3483669497a5ac Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 24 Apr 2026 09:39:28 +0000 Subject: [PATCH] js-on-sx: globalThis + eval stub transpile-time mappings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- lib/js/runtime.sx | 104 +++++++++++++++++++++++--------------------- lib/js/transpile.sx | 2 + 2 files changed, 56 insertions(+), 50 deletions(-) diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index 6ddeec1a..278878a5 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -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 diff --git a/lib/js/transpile.sx b/lib/js/transpile.sx index 702e1281..3124f8e2 100644 --- a/lib/js/transpile.sx +++ b/lib/js/transpile.sx @@ -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 ─────────────────────────────────────────────────────