From ae86579ae8ed28442a9d85502d2a4a747213c2b6 Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 25 Apr 2026 11:53:33 +0000 Subject: [PATCH] =?UTF-8?q?js-on-sx:=20ASI=20=E2=80=94=20:nl=20token=20fla?= =?UTF-8?q?g=20+=20return=20restricted=20production=20(525/526=20unit,=201?= =?UTF-8?q?48/148=20slice)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lexer: adds :nl (newline-before) boolean to every token. scan! resets the flag before each skip-ws! call; skip-ws! sets it true when it consumes \n or \r. Parser: jp-token-nl? reads the flag; jp-parse-return-stmt stops before the expression when a newline precedes it (return\n42 → return undefined). Four new tests cover the restricted production and the raw flag. Co-Authored-By: Claude Sonnet 4.6 --- lib/js/lexer.sx | 17 ++++++++++------- lib/js/parser.sx | 7 +++++++ lib/js/test.sh | 16 ++++++++++++++++ 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/lib/js/lexer.sx b/lib/js/lexer.sx index 1e72bce1..abf28b75 100644 --- a/lib/js/lexer.sx +++ b/lib/js/lexer.sx @@ -94,7 +94,7 @@ (fn (src) (let - ((tokens (list)) (pos 0) (src-len (len src))) + ((tokens (list)) (pos 0) (src-len (len src)) (nl-before false)) (define js-peek (fn @@ -109,11 +109,7 @@ (let ((sl (len s))) (and (<= (+ pos sl) src-len) (= (slice src pos (+ pos sl)) s))))) - (define - js-emit! - (fn - (type value start) - (append! tokens (js-make-token type value start)))) + (define js-emit! (fn (type value start) (append! tokens {:pos start :value value :type type :nl nl-before}))) (define skip-line-comment! (fn @@ -136,7 +132,13 @@ () (cond ((>= pos src-len) nil) - ((js-ws? (cur)) (do (advance! 1) (skip-ws!))) + ((js-ws? (cur)) + (do + (when + (or (= (cur) "\n") (= (cur) "\r")) + (set! nl-before true)) + (advance! 1) + (skip-ws!))) ((and (= (cur) "/") (< (+ pos 1) src-len) (= (js-peek 1) "/")) (do (advance! 2) (skip-line-comment!) (skip-ws!))) ((and (= (cur) "/") (< (+ pos 1) src-len) (= (js-peek 1) "*")) @@ -568,6 +570,7 @@ (fn () (do + (set! nl-before false) (skip-ws!) (when (< pos src-len) diff --git a/lib/js/parser.sx b/lib/js/parser.sx index a3cc71ce..5b1d1bb7 100644 --- a/lib/js/parser.sx +++ b/lib/js/parser.sx @@ -835,6 +835,12 @@ jp-eat-semi (fn (st) (if (jp-at? st "punct" ";") (do (jp-advance! st) nil) nil))) +(define + jp-token-nl? + (fn + (st) + (let ((tok (jp-peek st))) (if tok (= (get tok :nl) true) false)))) + (define jp-parse-vardecl (fn @@ -1166,6 +1172,7 @@ (or (jp-at? st "punct" ";") (jp-at? st "punct" "}") + (jp-token-nl? st) (jp-at? st "eof" nil)) (do (jp-eat-semi st) (list (quote js-return) nil)) (let diff --git a/lib/js/test.sh b/lib/js/test.sh index de6caea5..751da07b 100755 --- a/lib/js/test.sh +++ b/lib/js/test.sh @@ -1323,6 +1323,16 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 3505) (eval "(js-eval \"var a = {length: 3, 0: 10, 1: 20, 2: 30}; var sum = 0; Array.prototype.forEach.call(a, function(x){sum += x;}); sum\")") +;; ── Phase 1.ASI: automatic semicolon insertion ───────────────── +(epoch 4200) +(eval "(js-eval \"function f() { return\n42\n} f()\")") +(epoch 4201) +(eval "(js-eval \"function g() { return 42 } g()\")") +(epoch 4202) +(eval "(let ((toks (js-tokenize \"a\nb\"))) (get (nth toks 1) :nl))") +(epoch 4203) +(eval "(let ((toks (js-tokenize \"a b\"))) (get (nth toks 1) :nl))") + EPOCHS @@ -2042,6 +2052,12 @@ check 3503 "indexOf.call arrLike" '1' check 3504 "filter.call arrLike" '"2,3"' check 3505 "forEach.call arrLike sum" '60' +# ── Phase 1.ASI: automatic semicolon insertion ──────────────────── +check 4200 "return+newline → undefined" '"js-undefined"' +check 4201 "return+space+val → val" '42' +check 4202 "nl-before flag set after newline" 'true' +check 4203 "nl-before flag false on same line" 'false' + TOTAL=$((PASS + FAIL)) if [ $FAIL -eq 0 ]; then echo "✓ $PASS/$TOTAL JS-on-SX tests passed"