From 1e29bba1befbcc4840344b40317ae137dc5a0250 Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 10 May 2026 12:01:14 +0000 Subject: [PATCH] js-on-sx: globalThis self-ref, toFixed range + 1e21 fallback --- lib/js/runtime.sx | 61 ++++++++++++++++++++++++++--------------------- plans/js-on-sx.md | 2 ++ 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index c441039d..ef2612eb 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -536,36 +536,41 @@ js-number-to-fixed (fn (n digits) - (cond - ((js-number-is-nan n) "NaN") - ((= n (js-infinity-value)) "Infinity") - ((= n (- 0 (js-infinity-value))) "-Infinity") - (else - (let - ((d (js-math-trunc digits))) - (if - (< d 1) - (js-to-string (js-math-round n)) - (let - ((scale (js-pow-int 10 d))) + (let + ((d (js-math-trunc (js-to-number digits)))) + (cond + ((or (js-number-is-nan d) (< d 0) (> d 100)) + (raise + (js-new-call RangeError + (js-args "toFixed() digits argument must be between 0 and 100")))) + ((js-number-is-nan n) "NaN") + ((= n (js-infinity-value)) "Infinity") + ((= n (- 0 (js-infinity-value))) "-Infinity") + ((or (>= n 1e21) (<= n -1e21)) (js-number-to-string n)) + (else + (cond + ((< d 1) (js-to-string (js-math-round n))) + (else (let - ((scaled (js-math-round (* n scale)))) + ((scale (js-pow-int 10 d))) (let - ((abs-scaled (if (< scaled 0) (- 0 scaled) scaled)) - (sign (if (< scaled 0) "-" ""))) + ((scaled (js-math-round (* n scale)))) (let - ((int-part (js-math-trunc (/ abs-scaled scale))) - (frac-part - (- - abs-scaled - (* (js-math-trunc (/ abs-scaled scale)) scale)))) - (str - sign - (js-to-string int-part) - "." - (js-pad-int-str - (js-to-string (js-math-trunc frac-part)) - d)))))))))))) + ((abs-scaled (if (< scaled 0) (- 0 scaled) scaled)) + (sign (if (< scaled 0) "-" ""))) + (let + ((int-part (js-math-trunc (/ abs-scaled scale))) + (frac-part + (- + abs-scaled + (* (js-math-trunc (/ abs-scaled scale)) scale)))) + (str + sign + (js-to-string int-part) + "." + (js-pad-int-str + (js-to-string (js-math-trunc frac-part)) + d))))))))))))) (define js-pow-int @@ -6803,3 +6808,5 @@ (define js-global {:undefined js-undefined :JSON JSON :parseInt parseInt :Object Object :isNaN js-global-is-nan :Infinity inf :NaN 0 :String String :Boolean Boolean :Array Array :Math Math :parseFloat parseFloat :Number Number :console console :isFinite js-global-is-finite :Map Map :Set Set :Date Date :RegExp RegExp :Function js-function-global :Error Error :TypeError TypeError :RangeError RangeError :SyntaxError SyntaxError :ReferenceError ReferenceError :URIError URIError :EvalError EvalError :encodeURI encodeURI :decodeURI decodeURI :encodeURIComponent encodeURIComponent :decodeURIComponent decodeURIComponent :eval js-global-eval :Promise Promise :Symbol :js-undefined :AggregateError :js-undefined :SuppressedError :js-undefined :globalThis nil}) (set! js-global-this js-global) + +(dict-set! js-global "globalThis" js-global) diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index 939d52e3..f516f93f 100644 --- a/plans/js-on-sx.md +++ b/plans/js-on-sx.md @@ -158,6 +158,8 @@ Each item: implement → tests → update progress. Mark `[x]` when tests green. Append-only record of completed iterations. Loop writes one line per iteration: date, what was done, test count delta. +- 2026-05-10 — **`globalThis.globalThis === globalThis`; `Number.prototype.toFixed` honours digit-range and ≥1e21 fallback.** (1) `globalThis` was bound to `nil` in the global object literal (originally to dodge an inspect-cycle hang) — added `(dict-set! js-global "globalThis" js-global)` after the literal so `globalThis.globalThis === globalThis` per spec. (2) `Number.prototype.toFixed` rewrites: RangeError when fractionDigits is NaN or outside `[0,100]` (was silently producing garbage), and for `|x| >= 1e21` returns `js-number-to-string` (the value's own ToString) per spec step 9. conformance.sh: 148/148. + - 2026-05-10 — **`delete ` returns `false` instead of `true` per non-strict spec.** ES non-strict semantics: `delete x` where `x` is a declared binding (variable / function / parameter) returns `false` and does not unbind. Our transpiler was emitting `true` for any `delete ` whose argument wasn't a member or index access. Now `delete ` → `false`, and `delete ` recurses on the inner expression so `delete (1+2)` still works. Result: language/expressions/delete 14/30 → 18/30 (+4). conformance.sh: 148/148. - 2026-05-10 — **Parser rejects unary-op directly before `**` (e.g. `-1 ** 2`, `delete o.p ** 2`, `!x ** 2`, `~x ** 2`) per ES spec.** ES disallows `UnaryExpression ** ExponentiationExpression`; only `UpdateExpression ** ExponentiationExpression` and `() ** ...` are legal. Added a guard in `jp-binary-loop`: when op is `**` and the LHS is a `(js-unop ...)` node, raise SyntaxError. Parens are made transparent for everything except this check via a new `jp-paren-wrap` helper that emits `(js-paren )` only when wrapping an explicit unary op (so `(-1) ** 2` parses fine), and a new `js-paren` AST tag in `js-transpile` that just unwraps. Result: language/expressions/exponentiation 25/30 → 28/30 (+3). conformance.sh: 148/148.