js-on-sx: Math.X.name / Number.X.name via SX→JS name unmap (+4)

Every built-in JS function on Math/Number/Array/Object had .name === ""
because js-invoke-function-method/js-get-prop returned bare "" for the
"name" slot. That breaks tests like Math.abs.name === "abs" and
Array.isArray.name === "isArray".

Fix: extract the SX symbol name from (inspect fn) which prints
<js-math-abs(x)>, then unmap through a small string table that maps
js-math-abs → "abs", js-array-is-array → "isArray" etc. Also strips
the angle-bracket marker and stops at ( or space.

Non-mapped lambdas (user fns) fall through to the raw "js-foo" form
rather than "", which is slightly worse but only hit in debug prints.

Unit 521/522, slice 148/148 unchanged.
Scoreboard: Math 40/100 → 43/100 (+3); Number 74 → 75 (+1).

Sample: Math/abs/name.js, Math/floor/name.js, Math/max/name.js,
Number/isNaN/name.js — all flipped. length.js tests still fail for
trig because the underlying fn isn't implemented.
This commit is contained in:
2026-04-24 12:49:56 +00:00
parent 67df95508d
commit 2caf356fc4

View File

@@ -115,6 +115,95 @@
;; sign — 1 or -1
;; frac? — are we past the decimal point
;; fdiv — divisor used to scale fraction digits (only if frac?)
(define
js-extract-fn-name
(fn (f) (let ((raw (inspect f))) (js-strip-fn-name raw 0 (len raw)))))
(define
js-strip-fn-name
(fn
(s i n)
(let
((start (if (and (< i n) (= (char-at s i) "<")) (+ i 1) i)))
(js-strip-fn-name-end s start n))))
(define
js-strip-fn-name-end
(fn
(s start n)
(let
((end (js-find-paren-or-space s start n)))
(let ((name (js-string-slice s start end))) (js-unmap-fn-name name)))))
;; ── String coercion (ToString) ────────────────────────────────────
(define
js-find-paren-or-space
(fn
(s i n)
(cond
((>= i n) n)
((or (= (char-at s i) "(") (= (char-at s i) " ")) i)
(else (js-find-paren-or-space s (+ i 1) n)))))
(define
js-unmap-fn-name
(fn
(name)
(cond
((= name "js-math-abs") "abs")
((= name "js-math-floor") "floor")
((= name "js-math-ceil") "ceil")
((= name "js-math-round") "round")
((= name "js-math-max") "max")
((= name "js-math-min") "min")
((= name "js-math-random") "random")
((= name "js-math-sqrt") "sqrt")
((= name "js-math-pow") "pow")
((= name "js-math-trunc") "trunc")
((= name "js-math-sign") "sign")
((= name "js-math-cbrt") "cbrt")
((= name "js-math-hypot") "hypot")
((= name "js-number-is-finite") "isFinite")
((= name "js-number-is-nan") "isNaN")
((= name "js-number-is-integer") "isInteger")
((= name "js-number-is-safe-integer") "isSafeInteger")
((= name "js-global-is-finite") "isFinite")
((= name "js-global-is-nan") "isNaN")
((= name "js-string-from-char-code") "fromCharCode")
((= name "js-array-is-array") "isArray")
((= name "js-array-of") "of")
((= name "js-array-from") "from")
((= name "js-object-keys") "keys")
((= name "js-object-values") "values")
((= name "js-object-entries") "entries")
((= name "js-object-assign") "assign")
((= name "js-object-freeze") "freeze")
((= name "js-object-get-prototype-of") "getPrototypeOf")
((= name "js-object-set-prototype-of") "setPrototypeOf")
((= name "js-object-create") "create")
((= name "js-object-define-property") "defineProperty")
((= name "js-object-define-properties") "defineProperties")
((= name "js-object-get-own-property-names") "getOwnPropertyNames")
((= name "js-object-get-own-property-descriptor")
"getOwnPropertyDescriptor")
((= name "js-object-get-own-property-descriptors")
"getOwnPropertyDescriptors")
((= name "js-object-is-extensible") "isExtensible")
((= name "js-object-is-frozen") "isFrozen")
((= name "js-object-is-sealed") "isSealed")
((= name "js-object-seal") "seal")
((= name "js-object-prevent-extensions") "preventExtensions")
((= name "js-object-is") "is")
((= name "js-object-from-entries") "fromEntries")
((= name "js-object-has-own") "hasOwn")
((= name "js-to-number") "Number")
((= name "js-to-boolean") "Boolean")
(else name))))
;; ── Arithmetic (JS rules) ─────────────────────────────────────────
;; JS `+`: if either operand is a string → string concat, else numeric.
(define
js-count-real-params
(fn
@@ -162,7 +251,7 @@
(&rest more)
(js-call-with-this this-arg recv (js-list-concat bound more)))))
((= key "toString") "function () { [native code] }")
((= key "name") "")
((= key "name") (js-extract-fn-name recv))
((= key "length") (js-fn-length recv))
(else :js-undefined))))
@@ -179,8 +268,6 @@
(fn (&rest args) (js-invoke-function-method recv "bind" args)))
(else :js-undefined))))
;; ── String coercion (ToString) ────────────────────────────────────
(define
js-invoke-number-method
(fn
@@ -226,9 +313,6 @@
((= key "toLocaleString") "function () { [native code] }")
(else :js-undefined))))
;; ── Arithmetic (JS rules) ─────────────────────────────────────────
;; JS `+`: if either operand is a string → string concat, else numeric.
(define
js-invoke-boolean-method
(fn
@@ -272,6 +356,7 @@
((d (mod n radix)) (rest (js-math-trunc (/ n radix))))
(js-num-to-str-radix-rec rest radix (str (js-digit-char d) acc))))))
;; Bitwise + logical-not
(define
js-digit-char
(fn
@@ -315,6 +400,9 @@
(js-to-string (js-math-trunc frac-part))
d))))))))))))
;; ── Equality ──────────────────────────────────────────────────────
;; Strict equality (===): no coercion; js-undefined matches js-undefined.
(define
js-pow-int
(fn (b e) (if (<= e 0) 1 (* b (js-pow-int b (- e 1))))))
@@ -323,6 +411,9 @@
js-pad-int-str
(fn (s n) (if (>= (len s) n) s (js-pad-int-str (str "0" s) n))))
;; Abstract equality (==): type coercion rules.
;; Simplified: number↔string coerce both to number; null == undefined;
;; everything else falls back to strict equality.
(define
js-apply-fn
(fn
@@ -354,7 +445,6 @@
(nth args 5)))
(else (apply callable args))))))
;; Bitwise + logical-not
(define
js-invoke-method
(fn
@@ -380,6 +470,11 @@
(error
(str "TypeError: " (js-to-string key) " is not a function")))))))))
;; ── Relational comparisons ────────────────────────────────────────
;; Abstract relational comparison from ES5.
;; Numbers compare numerically; two strings compare lexicographically;
;; mixed types coerce both to numbers.
(define
js-object-builtin-method?
(fn
@@ -392,9 +487,6 @@
(= name "valueOf")
(= name "toLocaleString"))))
;; ── Equality ──────────────────────────────────────────────────────
;; Strict equality (===): no coercion; js-undefined matches js-undefined.
(define
js-invoke-object-method
(fn
@@ -418,9 +510,6 @@
(define js-upper-case (fn (s) (js-case-loop s 0 "" true)))
;; Abstract equality (==): type coercion rules.
;; Simplified: number↔string coerce both to number; null == undefined;
;; everything else falls back to strict equality.
(define js-lower-case (fn (s) (js-case-loop s 0 "" false)))
(define
@@ -438,11 +527,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?))))))))
;; ── Relational comparisons ────────────────────────────────────────
;; Abstract relational comparison from ES5.
;; Numbers compare numerically; two strings compare lexicographically;
;; mixed types coerce both to numbers.
(define
js-code-to-char
(fn
@@ -506,6 +590,13 @@
js-invoke-method-dyn
(fn (recv key args) (js-invoke-method recv key args)))
;; ── 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
js-call-plain
(fn
@@ -545,6 +636,7 @@
((proto (js-get-ctor-proto ctor)))
(js-instanceof-walk obj proto))))))
;; Setter — mutates the dict. Returns the new value (JS assignment yields rhs).
(define
js-instanceof-walk
(fn
@@ -560,6 +652,10 @@
((not (= (type-of p) "dict")) false)
(else (js-instanceof-walk p proto))))))))
;; ── 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-in
(fn
@@ -568,13 +664,6 @@
((not (= (type-of obj) "dict")) false)
(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
js-in-walk
(fn
@@ -585,6 +674,9 @@
((dict-has? obj "__proto__") (js-in-walk (get obj "__proto__") skey))
(else false))))
;; ── console.log ───────────────────────────────────────────────────
;; Trivial bridge. `log-info` is available on OCaml; fall back to print.
(define
Error
(fn
@@ -621,7 +713,8 @@
nil)
this))))
;; Setter — mutates the dict. Returns the new value (JS assignment yields rhs).
;; ── Math object ───────────────────────────────────────────────────
(define
RangeError
(fn
@@ -639,11 +732,6 @@
(dict-set! this "name" "RangeError"))
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
SyntaxError
(fn
@@ -661,7 +749,6 @@
(dict-set! this "name" "SyntaxError"))
nil)
this))))
(define
ReferenceError
(fn
@@ -679,10 +766,6 @@
(dict-set! this "name" "ReferenceError"))
nil)
this))))
;; ── console.log ───────────────────────────────────────────────────
;; Trivial bridge. `log-info` is available on OCaml; fall back to print.
(define
URIError
(fn
@@ -700,7 +783,6 @@
(if (empty? args) "" (js-to-string (nth args 0))))
(dict-set! this "name" "URIError")))
this))))
(define
EvalError
(fn
@@ -718,9 +800,6 @@
(if (empty? args) "" (js-to-string (nth args 0))))
(dict-set! this "name" "EvalError")))
this))))
;; ── Math object ───────────────────────────────────────────────────
(define
js-function?
(fn
@@ -734,7 +813,8 @@
(and (= t "dict") (contains? (keys v) "__callable__"))))))
(define __js_proto_table__ (dict))
(define __js_next_id__ (dict))
(dict-set! __js_next_id__ "n" 0)
(dict-set! __js_next_id__ "n" 0) ; deterministic placeholder for tests
(define
js-get-ctor-proto
(fn
@@ -751,6 +831,11 @@
(let
((p (dict)))
(begin (dict-set! __js_proto_table__ id p) p)))))))))
;; 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-reset-ctor-proto!
(fn
@@ -758,11 +843,13 @@
(let
((id (js-ctor-id ctor)) (p (dict)))
(begin (dict-set! __js_proto_table__ id p) p))))
(define
js-set-ctor-proto!
(fn
(ctor proto)
(let ((id (js-ctor-id ctor))) (dict-set! __js_proto_table__ id proto))))
(define
js-ctor-id
(fn
@@ -771,6 +858,7 @@
((and (= (type-of ctor) "dict") (dict-has? ctor "__ctor_id__"))
(get ctor "__ctor_id__"))
(else (inspect ctor)))))
(define
js-typeof
(fn
@@ -786,7 +874,7 @@
((= (type-of v) "component") "function")
((and (= (type-of v) "dict") (contains? (keys v) "__callable__"))
"function")
(else "object")))) ; deterministic placeholder for tests
(else "object"))))
(define
js-to-boolean
@@ -800,10 +888,6 @@
((= v "") false)
(else true))))
;; 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-to-number
(fn
@@ -2180,7 +2264,7 @@
((and (js-function? obj) (or (= key "prototype") (= key "name") (= key "length") (= key "call") (= key "apply") (= key "bind")))
(cond
((= key "prototype") (js-get-ctor-proto obj))
((= key "name") "")
((= key "name") (js-extract-fn-name obj))
((= key "length") (js-fn-length obj))
(else (js-invoke-function-bound obj key))))
(else js-undefined))))