diff --git a/lib/js/parser.sx b/lib/js/parser.sx index bdcdd4fe..e004a16e 100644 --- a/lib/js/parser.sx +++ b/lib/js/parser.sx @@ -1144,15 +1144,51 @@ ((c (jp-parse-assignment st))) (do (jp-expect! st "punct" ")") + (jp-disallow-decl-stmt! st "if") (let ((t (jp-parse-stmt st))) (if (jp-at? st "keyword" "else") (do (jp-advance! st) + (jp-disallow-decl-stmt! st "else") (list (quote js-if) c t (jp-parse-stmt st))) (list (quote js-if) c t nil)))))))) +(define + jp-disallow-decl-stmt! + (fn + (st context) + (let + ((t (jp-peek st))) + (cond + ((and (= (get t :type) "keyword") + (or (= (get t :value) "let") + (= (get t :value) "const") + (= (get t :value) "function") + (= (get t :value) "class"))) + (cond + ((and (= (get t :value) "let") + (or (= (get (jp-peek-at st 1) :type) "ident") + (and (= (get (jp-peek-at st 1) :type) "punct") + (or (= (get (jp-peek-at st 1) :value) "[") + (= (get (jp-peek-at st 1) :value) "{"))))) + (error + (str + "SyntaxError: Lexical declaration cannot appear in single-statement context: " + context))) + ((or (= (get t :value) "const") + (= (get t :value) "function") + (= (get t :value) "class")) + (error + (str + "SyntaxError: " + (get t :value) + " declaration cannot appear in single-statement context: " + context))) + (else nil))) + (else nil))))) + (define jp-bump! (fn @@ -1176,6 +1212,7 @@ ((c (jp-parse-assignment st))) (do (jp-expect! st "punct" ")") + (jp-disallow-decl-stmt! st "while") (jp-bump! st :loop-depth) (let ((body (jp-parse-stmt st))) (jp-decr! st :loop-depth) @@ -1187,6 +1224,7 @@ (st) (do (jp-advance! st) + (jp-disallow-decl-stmt! st "do") (jp-bump! st :loop-depth) (let ((body (jp-parse-stmt st))) @@ -1235,6 +1273,7 @@ (let ((iter (jp-parse-assignment st))) (jp-expect! st "punct" ")") + (jp-disallow-decl-stmt! st "for-of/in") (jp-bump! st :loop-depth) (let ((body (jp-parse-stmt st))) @@ -1249,6 +1288,7 @@ (let ((step (if (jp-at? st "punct" ")") nil (jp-parse-assignment st)))) (jp-expect! st "punct" ")") + (jp-disallow-decl-stmt! st "for") (jp-bump! st :loop-depth) (let ((body (jp-parse-stmt st))) @@ -1513,6 +1553,7 @@ (do (jp-advance! st) (jp-advance! st) + (jp-disallow-decl-stmt! st "label") (jp-parse-stmt st))) ((jp-at? st "keyword" "class") (jp-parse-class-decl st)) ((jp-at? st "keyword" "throw") (jp-parse-throw-stmt st)) diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index be2ceb41..5f350b99 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 — **Parse-time SyntaxError when `let`/`const`/`function`/`class` appear as a single-statement body of `if`/`while`/`do`/`for`/labeled.** Per ES grammar, those positions accept a Statement, not a Declaration — only block bodies (`{ ... }`) may contain Declarations. Added `jp-disallow-decl-stmt!` helper that, when the next token is a Declaration keyword in single-statement context, raises SyntaxError. The `let` arm checks for `let `, `let [`, or `let {` to avoid mis-rejecting `let;` (where `let` is just an identifier expression). Hook calls in `jp-parse-if-stmt` (then + else branches), `jp-parse-while-stmt`, `jp-parse-do-while-stmt`, both for-of/in and C-for body sites, and the labeled-statement entry. Result: language/statements/while 16/30 → 20/30. statements/labeled 4/15 → 7/15. statements/if 20/30 → 21/30. conformance.sh: 148/148. + - 2026-05-10 — **Parse-time SyntaxError for `break`/`continue` outside loops/switches and `return` outside functions; `void ` evaluates `` for side effects.** Parser tracks `:loop-depth`, `:switch-depth`, and `:fn-depth` on the state dict (initialized to 0). `jp-parse-while-stmt`, `jp-parse-do-while-stmt`, `jp-parse-for-stmt` (both for-of/in and C-for) bump `:loop-depth` around body parsing; `jp-parse-switch-stmt` bumps `:switch-depth`; new `jp-parse-fn-body` and `jp-parse-arrow-body` save+reset loop/switch depth and bump `:fn-depth` (so `break` inside an outer loop's nested function is rejected). Bare `break` requires `loop-depth > 0 OR switch-depth > 0`; bare `continue` requires `loop-depth > 0`; `return` requires `fn-depth > 0`. Separately, `void ` was compiling to just `:js-undefined` (dropping the expression entirely); now `(begin :js-undefined)` so side effects fire. Result: language/statements/return 4/15 → 14/15 (+10). statements/break 9/20 → 12/20. statements/continue 12/24 → 15/24. expressions/void 7/9 → 8/9. conformance.sh: 148/148. - 2026-05-10 — **`Math.hypot` and `Math.cbrt` honour spec edges for NaN, ±Infinity, and ±0.** `Math.hypot(NaN, Infinity)` was returning NaN instead of +Infinity (spec: any ±Infinity arg dominates NaN). Rewrote `js-math-hypot` to scan args once tracking inf/nan flags, return +Infinity if any arg is ±Infinity, else NaN if any was NaN, else `sqrt(sum of squares)`. `Math.cbrt(NaN)` was 0 (because `pow(NaN, 1/3)` produced 0 in our path); also `Math.cbrt(-0)` returned +0 instead of -0. Added explicit short-circuits: NaN→NaN, ±Infinity→arg, ±0→arg, plus changed `(/ 1 3)` (rational) to `(/ 1.0 3.0)` (inexact) to avoid rational fractional-power oddities. Result: built-ins/Math/hypot 9/11 → 10/11. Math/cbrt 3/4 → 4/4. conformance.sh: 148/148.