From ba41f8a5800cbf08b27205f8e96c30bca12f684f Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 27 May 2026 20:17:40 +0000 Subject: [PATCH] =?UTF-8?q?go:=20parse.sx=20=E2=80=94=20if/else,=20for,=20?= =?UTF-8?q?break/continue,=20inc-dec=20+=2011=20tests=20[nothing]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the most-used control-flow forms: if COND { ... } [else { ... } | else if ...] for { ... } — infinite for COND { ... } — while-like for INIT; COND; POST { ... } — C-style break / continue — keyword stmts (no labels yet) x++ / x-- — Go statement inc-dec AST shapes: (list :if COND THEN ELSE) — ELSE nil / :if / :block (list :for INIT COND POST BODY) — any of INIT/COND/POST may be nil (list :break LABEL) (list :continue LABEL) (list :inc-dec OP EXPR) — OP is "++" / "--" **Closes the parser-mode caveat** logged when composite literals landed. `gp-no-comp-lit` is a re-entrant counter on the parser state; control-flow constructs increment it before parsing their condition and decrement after, suppressing the postfix `{` → composite-lit interpretation so that `if Foo { ... }` correctly reads `{ ... }` as the body, not as `Foo{}` composite literal. Verified by the test: (go-parse "if Foo {}") → (:if (:var "Foo") (:block ()) nil) gp-parse-control-cond is the single helper that bracket-wraps the flag bump so future control-flow forms (switch, select, range) can't forget to engage suppression. switch / select / defer / go / for-range / channel-send still deferred. parse 152/152, total 281/281. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/go/parse.sx | 96 +++++++++++++++++++++++++++++++++++++++++- lib/go/scoreboard.json | 6 +-- lib/go/scoreboard.md | 4 +- lib/go/tests/parse.sx | 66 +++++++++++++++++++++++++++++ plans/go-on-sx.md | 28 +++++++++--- 5 files changed, 186 insertions(+), 14 deletions(-) diff --git a/lib/go/parse.sx b/lib/go/parse.sx index 55e59c21..8c87b780 100644 --- a/lib/go/parse.sx +++ b/lib/go/parse.sx @@ -42,7 +42,7 @@ (fn (src) (let - ((gp-tokens (go-tokenize src)) (gp-idx 0)) + ((gp-tokens (go-tokenize src)) (gp-idx 0) (gp-no-comp-lit 0)) (define gp-cur (fn () (nth gp-tokens gp-idx))) (define gp-advance! (fn () (set! gp-idx (+ gp-idx 1)))) (define gp-tok-type (fn () (get (gp-cur) :type))) @@ -470,7 +470,12 @@ ;; Ident-prefixed composite literal: T{...}. The base is ;; the AST expression for the type-name (an ast-var or a ;; :select node); a later phase resolves it as a type. - (and (= (get tok :type) "op") (= (get tok :value) "{")) + ;; SUPPRESSED inside control-flow conditions (if/for/switch) + ;; — Go spec: top-level composite literals must be parenthesised + ;; in those positions. gp-no-comp-lit acts as a re-entrant + ;; counter so nested constructs nest correctly. + (and (= (get tok :type) "op") (= (get tok :value) "{") + (= gp-no-comp-lit 0)) (do (gp-advance!) (gp-postfix-loop @@ -723,6 +728,79 @@ :else (list :method-decl recv name params results body)))))) :else nil)))) + (define + gp-parse-control-cond + ;; Parses an expression as a control-flow condition with the + ;; composite-literal '{' suppression engaged (Go spec: top-level + ;; composite literals require explicit parens in if/for/switch + ;; condition positions). + (fn + () + (set! gp-no-comp-lit (+ gp-no-comp-lit 1)) + (let ((e (gp-parse-expr 1))) + (set! gp-no-comp-lit (- gp-no-comp-lit 1)) + e))) + (define + gp-parse-if + ;; Caller has consumed 'if'. + ;; if COND { BODY } + ;; if COND { BODY } else { BODY } + ;; if COND { BODY } else if ... (chained, recursive ELSE) + ;; AST: (list :if COND THEN ELSE) where ELSE may be nil, a + ;; nested :if, or a :block. + (fn + () + (let ((cnd (gp-parse-control-cond)) (then nil) (els nil)) + (when (and (= (gp-tok-type) "op") (= (gp-tok-value) "{")) + (gp-advance!) + (set! then (gp-parse-block-body))) + ;; Skip ASI semis between } and else + (when (= (gp-tok-type) "semi") (gp-advance!)) + (when (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "else")) + (gp-advance!) + (cond + (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "if")) + (do (gp-advance!) (set! els (gp-parse-if))) + (and (= (gp-tok-type) "op") (= (gp-tok-value) "{")) + (do (gp-advance!) (set! els (gp-parse-block-body))))) + (list :if cnd then els)))) + (define + gp-parse-for + ;; Caller has consumed 'for'. + ;; for { BODY } — infinite + ;; for COND { BODY } — while-like + ;; for INIT; COND; POST { BODY } — C-style + ;; for k, v := range coll { BODY } — deferred (range) + ;; AST: (list :for INIT COND POST BODY); any of INIT/COND/POST may be nil. + (fn + () + (set! gp-no-comp-lit (+ gp-no-comp-lit 1)) + (let ((init nil) (cnd nil) (post nil) (body nil)) + (cond + (and (= (gp-tok-type) "op") (= (gp-tok-value) "{")) + nil + :else + (let ((first (gp-parse-stmt))) + (cond + (= (gp-tok-type) "semi") + (do + (set! init first) + (gp-advance!) + (when (not (and (= (gp-tok-type) "op") + (= (gp-tok-value) ";"))) + (cond + (= (gp-tok-type) "semi") nil + :else (set! cnd (gp-parse-expr 1)))) + (when (= (gp-tok-type) "semi") (gp-advance!)) + (when (not (and (= (gp-tok-type) "op") + (= (gp-tok-value) "{"))) + (set! post (gp-parse-stmt)))) + :else (set! cnd first)))) + (set! gp-no-comp-lit (- gp-no-comp-lit 1)) + (when (and (= (gp-tok-type) "op") (= (gp-tok-value) "{")) + (gp-advance!) + (set! body (gp-parse-block-body))) + (list :for init cnd post body)))) (define gp-stmt-assign-ops ;; Compound assignment operators per Go spec § Assignment operations. @@ -756,6 +834,14 @@ (and (= (gp-tok-type) "op") (= (gp-tok-value) "}"))) (list :return (list)) :else (list :return (gp-parse-expr-list)))) + (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "break")) + (do (gp-advance!) (list :break nil)) + (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "continue")) + (do (gp-advance!) (list :continue nil)) + (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "if")) + (do (gp-advance!) (gp-parse-if)) + (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "for")) + (do (gp-advance!) (gp-parse-for)) (and (= (gp-tok-type) "op") (= (gp-tok-value) "{")) (do (gp-advance!) (gp-parse-block-body)) :else @@ -789,6 +875,12 @@ (gp-advance!) (list :assign-op op lhs-list (list (gp-parse-expr 1)))) + (and (= (gp-tok-type) "op") + (or (= (gp-tok-value) "++") + (= (gp-tok-value) "--"))) + (let ((op (gp-tok-value))) + (gp-advance!) + (list :inc-dec op lhs)) :else ;; Plain expression statement — return the single expr. ;; (If somehow there was a comma chain without =/:=, just diff --git a/lib/go/scoreboard.json b/lib/go/scoreboard.json index 70e65aa6..cb8cf568 100644 --- a/lib/go/scoreboard.json +++ b/lib/go/scoreboard.json @@ -1,10 +1,10 @@ { "language": "go", - "total_pass": 270, - "total": 270, + "total_pass": 281, + "total": 281, "suites": [ {"name":"lex","pass":129,"total":129,"status":"ok"}, - {"name":"parse","pass":141,"total":141,"status":"ok"}, + {"name":"parse","pass":152,"total":152,"status":"ok"}, {"name":"types","pass":0,"total":0,"status":"pending"}, {"name":"eval","pass":0,"total":0,"status":"pending"}, {"name":"runtime","pass":0,"total":0,"status":"pending"}, diff --git a/lib/go/scoreboard.md b/lib/go/scoreboard.md index f4f99e5e..7d66514e 100644 --- a/lib/go/scoreboard.md +++ b/lib/go/scoreboard.md @@ -1,11 +1,11 @@ # Go-on-SX Scoreboard -**Total: 270 / 270 tests passing** +**Total: 281 / 281 tests passing** | | Suite | Pass | Total | |---|---|---|---| | ✅ | lex | 129 | 129 | -| ✅ | parse | 141 | 141 | +| ✅ | parse | 152 | 152 | | ⬜ | types | 0 | 0 | | ⬜ | eval | 0 | 0 | | ⬜ | runtime | 0 | 0 | diff --git a/lib/go/tests/parse.sx b/lib/go/tests/parse.sx index e314c71b..80ed3e44 100644 --- a/lib/go/tests/parse.sx +++ b/lib/go/tests/parse.sx @@ -911,6 +911,72 @@ (list) (list :block (list (ast-app (ast-var "f") (list (ast-var "x"))))))) +(go-parse-test + "if: if x { }" + (go-parse "if x { }") + (list :if (ast-var "x") (list :block (list)) nil)) + +(go-parse-test + "if: if cond { body } else { body }" + (go-parse "if x { y := 1 } else { z := 2 }") + (list + :if (ast-var "x") + (list + :block (list + (list :short-decl (list (ast-var "y")) (list (ast-literal "1"))))) + (list + :block (list + (list :short-decl (list (ast-var "z")) (list (ast-literal "2"))))))) + +(go-parse-test + "if: chained else-if" + (go-parse "if a { } else if b { } else { }") + (list + :if (ast-var "a") + (list :block (list)) + (list :if (ast-var "b") (list :block (list)) (list :block (list))))) + +(go-parse-test + "if: comparison condition" + (go-parse "if x == 0 { return 0 }") + (list + :if (ast-app (ast-var "==") (list (ast-var "x") (ast-literal "0"))) + (list :block (list (list :return (list (ast-literal "0"))))) + nil)) + +(go-parse-test + "for: infinite — for { }" + (go-parse "for { }") + (list :for nil nil nil (list :block (list)))) + +(go-parse-test + "for: while-like — for cond { }" + (go-parse "for x { }") + (list :for nil (ast-var "x") nil (list :block (list)))) + +(go-parse-test + "for: C-style — for i := 0; i < 10; i++ { }" + (go-parse "for i := 0; i < 10; i++ { }") + (list + :for (list :short-decl (list (ast-var "i")) (list (ast-literal "0"))) + (ast-app (ast-var "<") (list (ast-var "i") (ast-literal "10"))) + (list :inc-dec "++" (ast-var "i")) + (list :block (list)))) + +(go-parse-test "stmt: break" (go-parse "break") (list :break nil)) + +(go-parse-test "stmt: continue" (go-parse "continue") (list :continue nil)) + +(go-parse-test + "stmt: x++ (inc-dec)" + (go-parse "x++") + (list :inc-dec "++" (ast-var "x"))) + +(go-parse-test + "control: composite-lit suppression in if cond" + (go-parse "if Foo {}") + (list :if (ast-var "Foo") (list :block (list)) nil)) + (go-parse-test "non-primary: '+'" (go-parse "+") nil) (go-parse-test "non-primary: empty" (go-parse "") nil) diff --git a/plans/go-on-sx.md b/plans/go-on-sx.md index 96fad074..9a6ad086 100644 --- a/plans/go-on-sx.md +++ b/plans/go-on-sx.md @@ -197,16 +197,18 @@ Progress-log line → push `origin/loops/go`. and variadic params deferred. Anonymous param-list disambiguation (`func(int, string)`) is a known parser-greedy limitation, flagged. - [/] Statements: `return`, short-decl `:=`, assign `=`, compound assign - (`+=` etc.), expression stmt, block `{...}` all done. `gp-parse-stmt` - replaces the func-body `:body` stub with real `(:block STMTS)`. - Progress guards added to block-body and composite-elems loops. - `if`/`for`/`switch`/`select`/`defer`/`go`/`break`/`continue`/send - deferred to next slice. + (`+=` etc.), expression stmt, block `{...}`, **if/else (with chained + else-if), for (infinite / while-like / C-style), break, continue, + inc-dec (`x++` / `x--`)** all done. **Composite-literal `{` + suppression** active in control-flow conditions via + `gp-no-comp-lit` counter — closes the parser-mode caveat flagged + when composite literals landed. `switch`/`select`/`defer`/`go`/ + `for...range`/send `ch<-v` deferred. - [ ] End-to-end: hello-world, fibonacci, FizzBuzz, goroutine ping-pong, struct + method. - **Acceptance:** parse/ suite at 80+ tests. **Acceptance bar crossed: - 141/141.** Remaining sub-items (control-flow stmts, e2e) keep Phase 2 - open ⬜. + 152/152.** Remaining sub-items (switch/select/defer/go/range, + end-to-end programs) keep Phase 2 open ⬜. ### Phase 3 — Bidirectional type checker, MVP (`lib/go/types.sx`) ⬜ - **Independent implementation.** Do NOT use lib/guest/static-types- @@ -523,6 +525,18 @@ Minimal repro: see `lib/go/lex.sx#gl-oct-digit?` and `#gl-match-op`. _Newest first. Append one dated entry per commit._ +- 2026-05-27 — Phase 2 cont.: control-flow statements. `if cond { } [else + { }]` with chained else-if, `for { }` (infinite), `for cond { }` + (while-like), `for init; cond; post { }` (C-style), `break`, + `continue`, plus `x++` / `x--` inc-dec statements. **Closed the + parser-mode caveat** flagged when composite literals landed: + `gp-no-comp-lit` is a re-entrant counter that suppresses the postfix + `{...}` → composite-lit interpretation inside control-flow condition + positions, matching Go spec § Composite literals. `gp-parse-control- + cond` wraps the increment/decrement so callers can't forget. +11 + tests, parse 152/152, total 281/281. `[nothing]` — pure Go parser + shape work; the bidirectional-checker-relevant shapes (cond/body) are + already covered by the earlier `:field` insight. - 2026-05-27 — Phase 2 cont.: statements. First slice covers `return [exprs]`, short-decl `lhs := exprs`, assignment `lhs = exprs`, compound assignment (`+= -= *= /= %= &= |= ^= <<= >>= &^=`), bare