From e93e1eeab1f529c79187bbc6818928c40b9e8293 Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 10 May 2026 10:47:56 +0000 Subject: [PATCH] js-on-sx: reject unary-op directly before ** per spec (parens still allowed) --- lib/js/parser.sx | 19 +++++++++++++++++-- lib/js/transpile.sx | 1 + plans/js-on-sx.md | 2 ++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/js/parser.sx b/lib/js/parser.sx index a2664866..972ba0aa 100644 --- a/lib/js/parser.sx +++ b/lib/js/parser.sx @@ -446,14 +446,23 @@ (let ((e (jp-parse-comma-seq st))) (jp-expect! st "punct" ")") - e))) + (jp-paren-wrap e)))) (do (dict-set! st :idx saved) (jp-advance! st) (let ((e (jp-parse-comma-seq st))) (jp-expect! st "punct" ")") - e))))))) + (jp-paren-wrap e)))))))) + +(define + jp-paren-wrap + (fn + (e) + (cond + ((and (list? e) (= (first e) (quote js-unop))) + (list (quote js-paren) e)) + (else e)))) (define jp-parse-comma-seq @@ -753,6 +762,12 @@ (cond ((< prec 0) left) ((< prec min-prec) left) + ((and (= op "**") (list? left) (= (first left) (quote js-unop))) + (error + (str + "SyntaxError: Unary operator '" + (nth left 1) + "' used immediately before exponentiation expression"))) (else (do (jp-advance! st) diff --git a/lib/js/transpile.sx b/lib/js/transpile.sx index 7942d895..74ac10f8 100644 --- a/lib/js/transpile.sx +++ b/lib/js/transpile.sx @@ -98,6 +98,7 @@ (list (js-sym "js-regex-new") (nth ast 1) (nth ast 2))) ((js-tag? ast "js-null") nil) ((js-tag? ast "js-undef") (list (js-sym "quote") :js-undefined)) + ((js-tag? ast "js-paren") (js-transpile (nth ast 1))) ((js-tag? ast "js-ident") (js-transpile-ident (nth ast 1))) ((js-tag? ast "js-unop") (js-transpile-unop (nth ast 1) (nth ast 2))) diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index 1cbfac95..9251766f 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 — **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. + - 2026-05-10 — **`Math.round` / `Math.max` / `Math.min` honour spec edge cases for NaN, ±Infinity, and ±0.** `Math.round(NaN)` was returning 0 because `floor(NaN+0.5)` doesn't propagate NaN; ditto `±Infinity` paths. `Math.max({})` silently returned `-Infinity` (initial accumulator) because the first arg wasn't ToNumber'd. `Math.max(0, -0)` returned `-0` because `>` doesn't distinguish them. Rewrites: round NaN/±Infinity/±0 short-circuits; max/min ToNumber the first arg, propagate NaN immediately, and use a `js-is-positive-zero?` (rational-safe) tiebreaker so `Math.max(0, -0) === 0` per spec. Result: built-ins/Math/round 5/10 → 8/10 (+3). Math/max 6/9 → 8/9 (+2). Math/min 6/9 → 8/9 (+2). conformance.sh: 148/148. - 2026-05-10 — **`Map.prototype.*` and `Set.prototype.*` raise TypeError when called on non-Map / non-Set `this`.** All five `js-map-do-*` and four `js-set-do-*` helpers were assuming `this` had `__map_keys__` / `__set_items__`, so `Map.prototype.clear.call({})` silently returned undefined (after creating dangling state) instead of throwing. Added `js-map-check!` / `js-set-check!` guards run as the first step of each method; raise spec-correct `TypeError` instances. Result: built-ins/Map 18/30 → 22/30 (+4). built-ins/Set 15/30 → 28/30 (+13). conformance.sh: 148/148.