diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index ba77a18f..6ddeec1a 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -202,6 +202,24 @@ (js-to-string key) " is not a function (on number)")))))) +(define + js-invoke-function-objproto + (fn + (recv key args) + (cond + ((= key "hasOwnProperty") + (let + ((k (if (empty? args) "" (js-to-string (nth args 0))))) + (or (= k "name") (= k "length") (= k "prototype")))) + ((= key "toString") "function () { [native code] }") + ((= key "valueOf") recv) + ((= key "isPrototypeOf") false) + ((= key "propertyIsEnumerable") false) + ((= key "toLocaleString") "function () { [native code] }") + (else :js-undefined)))) + +;; ── String coercion (ToString) ──────────────────────────────────── + (define js-invoke-boolean-method (fn @@ -216,8 +234,6 @@ (js-to-string key) " is not a function (on boolean)")))))) -;; ── String coercion (ToString) ──────────────────────────────────── - (define js-num-to-str-radix (fn @@ -236,6 +252,9 @@ (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 @@ -247,9 +266,6 @@ ((d (mod n radix)) (rest (js-math-trunc (/ n radix)))) (js-num-to-str-radix-rec rest radix (str (js-digit-char d) acc)))))) -;; ── Arithmetic (JS rules) ───────────────────────────────────────── - -;; JS `+`: if either operand is a string → string concat, else numeric. (define js-digit-char (fn @@ -349,6 +365,8 @@ ((not (js-undefined? m)) (js-call-with-this recv m args)) ((and (js-function? recv) (js-function-method? key)) (js-invoke-function-method recv key args)) + ((and (js-function? recv) (js-object-builtin-method? key)) + (js-invoke-function-objproto recv key args)) ((and (= (type-of recv) "dict") (js-object-builtin-method? key)) (js-invoke-object-method recv key args)) (else @@ -367,6 +385,7 @@ (= name "valueOf") (= name "toLocaleString")))) +;; Bitwise + logical-not (define js-invoke-object-method (fn @@ -388,14 +407,13 @@ ((= name "toLocaleString") "[object Object]") (else js-undefined)))) -;; Bitwise + logical-not (define js-upper-case (fn (s) (js-case-loop s 0 "" true))) -(define js-lower-case (fn (s) (js-case-loop s 0 "" false))) - ;; ── Equality ────────────────────────────────────────────────────── ;; Strict equality (===): no coercion; js-undefined matches js-undefined. +(define js-lower-case (fn (s) (js-case-loop s 0 "" false))) + (define js-case-loop (fn @@ -411,6 +429,9 @@ ((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 @@ -470,13 +491,15 @@ ((= code 122) "z") (else "")))) -;; Abstract equality (==): type coercion rules. -;; Simplified: number↔string coerce both to number; null == undefined; -;; everything else falls back to strict equality. (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-call-plain (fn @@ -488,11 +511,6 @@ (js-call-with-this :js-undefined (get fn-val "__callable__") args)) (else (js-call-with-this :js-undefined fn-val args))))) -;; ── Relational comparisons ──────────────────────────────────────── - -;; Abstract relational comparison from ES5. -;; Numbers compare numerically; two strings compare lexicographically; -;; mixed types coerce both to numbers. (define js-new-call (fn @@ -572,6 +590,13 @@ 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 @@ -590,13 +615,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 RangeError (fn @@ -633,6 +651,7 @@ nil) this)))) +;; Setter — mutates the dict. Returns the new value (JS assignment yields rhs). (define ReferenceError (fn @@ -651,7 +670,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 js-function? (fn @@ -664,19 +686,17 @@ (= t "component") (and (= t "dict") (contains? (keys v) "__callable__")))))) -;; ── 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_proto_table__ (dict)) -(define __js_next_id__ (dict)) - ;; ── console.log ─────────────────────────────────────────────────── ;; Trivial bridge. `log-info` is available on OCaml; fall back to print. +(define __js_next_id__ (dict)) + (dict-set! __js_next_id__ "n" 0) +;; ── Math object ─────────────────────────────────────────────────── + (define js-get-ctor-proto (fn @@ -687,9 +707,6 @@ ((dict-has? __js_proto_table__ id) (get __js_proto_table__ id)) (else (let ((p (dict))) (begin (dict-set! __js_proto_table__ id p) p))))))) - -;; ── Math object ─────────────────────────────────────────────────── - (define js-reset-ctor-proto! (fn @@ -764,7 +781,8 @@ (else (js-nan-value)))))) (define js-is-numeric-string? - (fn (s) (js-is-numeric-loop s 0 false false false))) + (fn (s) (js-is-numeric-loop s 0 false false false))) ; deterministic placeholder for tests + (define js-is-numeric-loop (fn @@ -792,14 +810,14 @@ (or (= prev "e") (= prev "E")) (js-is-numeric-loop s (+ i 1) sawdigit sawdot sawe) false))) - (else false))))))) ; deterministic placeholder for tests - -(define js-parse-num-safe (fn (s) (cond (else (js-num-from-string s))))) + (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 js-num-from-string (fn