go: parse.sx — switch + select + 8 tests; stmts done [shapes-scheduler]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 32s

Adds Go's switch and select statements:

  switch TAG { case V1, V2: a; case V3: b; default: c }
  switch { case cond: ... }                            — tagless
  select { case x := <-ch: a; case ch <- v: b; default: c }

AST shapes:
  (list :switch TAG CASES)             — TAG nil for tagless
  (list :case VALUES BODY)             — VALUES is expr-list
  (list :select CASES)
  (list :select-case COMM-STMT BODY)   — COMM-STMT is send/recv-assign/bare-recv
  (list :default BODY)

gp-parse-case-body reads stmts until the next case/default/}/eof
without consuming the terminator — used by both switch and select.

select-case parsing reuses gp-parse-stmt for the comm-stmt, so all
four shapes (send, x := <-ch, x = <-ch, bare <-ch) fall out from the
existing stmt parser. Composite-lit suppression is engaged for the
switch tag expression.

Type-switch (`switch v := x.(type) { case int: ... }`) is the one
deferred shape; needs the `.(type)` pseudo-syntax recognised in the
expression layer. Phase 2 statement coverage is otherwise complete.

This is also a chiselling iteration for scheduler sister kit. Diary
updated with select-case design insights:

  * All four select-case shapes share (list :select-case STMT BODY)
    — kit primitive sched-select accepts a uniform list of cases.
  * Default vs no-default determines blocking semantics. Erlang's
    `receive ... after Timeout -> ...` is the analogue — both fit
    "non-blocking fallback case" in the kit API.

parse 169/169, total 298/298.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 20:29:37 +00:00
parent 171a08a2f8
commit 44fb231391
6 changed files with 253 additions and 12 deletions

View File

@@ -728,6 +728,126 @@
:else :else
(list :method-decl recv name params results body)))))) (list :method-decl recv name params results body))))))
:else nil)))) :else nil))))
(define
gp-parse-case-body
;; Stmts inside a switch/select case clause. Reads until the next
;; 'case'/'default'/'}'/eof without consuming those terminators.
(fn
()
(let ((stmts (list)))
(define
gp-cb-loop
(fn
()
(cond
(= (gp-tok-type) "eof") nil
(and (= (gp-tok-type) "op") (= (gp-tok-value) "}")) nil
(and (= (gp-tok-type) "keyword")
(or (= (gp-tok-value) "case")
(= (gp-tok-value) "default")))
nil
(= (gp-tok-type) "semi") (do (gp-advance!) (gp-cb-loop))
:else
(do
(let ((saved-idx gp-idx))
(let ((s (gp-parse-stmt)))
(when (not (= s nil)) (append! stmts s)))
(when (= gp-idx saved-idx) (gp-advance!)))
(gp-cb-loop)))))
(gp-cb-loop)
stmts)))
(define
gp-parse-switch
;; Caller has consumed 'switch'. Two shapes:
;; switch { ...cases... } — tagless (each case is a bool)
;; switch TAG { ...cases... } — tagged (match against TAG)
;; AST: (list :switch TAG CASES) — TAG may be nil.
;; Each case: (list :case VALUES BODY) or (list :default BODY).
;; Type-switch (`switch v := x.(type)`) deferred to a follow-up.
(fn
()
(set! gp-no-comp-lit (+ gp-no-comp-lit 1))
(let ((tag nil) (cases (list)))
(when (not (and (= (gp-tok-type) "op") (= (gp-tok-value) "{")))
(set! tag (gp-parse-expr 1)))
(set! gp-no-comp-lit (- gp-no-comp-lit 1))
(when (and (= (gp-tok-type) "op") (= (gp-tok-value) "{"))
(gp-advance!))
(define
gp-sw-loop
(fn
()
(cond
(= (gp-tok-type) "semi")
(do (gp-advance!) (gp-sw-loop))
(and (= (gp-tok-type) "op") (= (gp-tok-value) "}"))
(gp-advance!)
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "case"))
(do
(gp-advance!)
(let ((vals (gp-parse-expr-list)))
(when (and (= (gp-tok-type) "op")
(= (gp-tok-value) ":"))
(gp-advance!))
(append! cases
(list :case vals (gp-parse-case-body))))
(gp-sw-loop))
(and (= (gp-tok-type) "keyword")
(= (gp-tok-value) "default"))
(do
(gp-advance!)
(when (and (= (gp-tok-type) "op")
(= (gp-tok-value) ":"))
(gp-advance!))
(append! cases
(list :default (gp-parse-case-body)))
(gp-sw-loop))
:else (do (gp-advance!) (gp-sw-loop)))))
(gp-sw-loop)
(list :switch tag cases))))
(define
gp-parse-select
;; Caller has consumed 'select'. Each case is a communication stmt
;; (send / recv) or a recv-assignment.
;; AST: (list :select CASES).
;; Each case: (list :select-case COMM-STMT BODY) or (list :default BODY).
(fn
()
(let ((cases (list)))
(when (and (= (gp-tok-type) "op") (= (gp-tok-value) "{"))
(gp-advance!))
(define
gp-sel-loop
(fn
()
(cond
(= (gp-tok-type) "semi")
(do (gp-advance!) (gp-sel-loop))
(and (= (gp-tok-type) "op") (= (gp-tok-value) "}"))
(gp-advance!)
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "case"))
(do
(gp-advance!)
(let ((comm (gp-parse-stmt)))
(when (and (= (gp-tok-type) "op")
(= (gp-tok-value) ":"))
(gp-advance!))
(append! cases
(list :select-case comm (gp-parse-case-body))))
(gp-sel-loop))
(and (= (gp-tok-type) "keyword")
(= (gp-tok-value) "default"))
(do
(gp-advance!)
(when (and (= (gp-tok-type) "op")
(= (gp-tok-value) ":"))
(gp-advance!))
(append! cases
(list :default (gp-parse-case-body)))
(gp-sel-loop))
:else (do (gp-advance!) (gp-sel-loop)))))
(gp-sel-loop)
(list :select cases))))
(define (define
gp-parse-control-cond gp-parse-control-cond
;; Parses an expression as a control-flow condition with the ;; Parses an expression as a control-flow condition with the
@@ -915,6 +1035,10 @@
(do (gp-advance!) (gp-parse-if)) (do (gp-advance!) (gp-parse-if))
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "for")) (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "for"))
(do (gp-advance!) (gp-parse-for)) (do (gp-advance!) (gp-parse-for))
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "switch"))
(do (gp-advance!) (gp-parse-switch))
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "select"))
(do (gp-advance!) (gp-parse-select))
(and (= (gp-tok-type) "op") (= (gp-tok-value) "{")) (and (= (gp-tok-type) "op") (= (gp-tok-value) "{"))
(do (gp-advance!) (gp-parse-block-body)) (do (gp-advance!) (gp-parse-block-body))
:else :else

View File

@@ -1,10 +1,10 @@
{ {
"language": "go", "language": "go",
"total_pass": 290, "total_pass": 298,
"total": 290, "total": 298,
"suites": [ "suites": [
{"name":"lex","pass":129,"total":129,"status":"ok"}, {"name":"lex","pass":129,"total":129,"status":"ok"},
{"name":"parse","pass":161,"total":161,"status":"ok"}, {"name":"parse","pass":169,"total":169,"status":"ok"},
{"name":"types","pass":0,"total":0,"status":"pending"}, {"name":"types","pass":0,"total":0,"status":"pending"},
{"name":"eval","pass":0,"total":0,"status":"pending"}, {"name":"eval","pass":0,"total":0,"status":"pending"},
{"name":"runtime","pass":0,"total":0,"status":"pending"}, {"name":"runtime","pass":0,"total":0,"status":"pending"},

View File

@@ -1,11 +1,11 @@
# Go-on-SX Scoreboard # Go-on-SX Scoreboard
**Total: 290 / 290 tests passing** **Total: 298 / 298 tests passing**
| | Suite | Pass | Total | | | Suite | Pass | Total |
|---|---|---|---| |---|---|---|---|
| ✅ | lex | 129 | 129 | | ✅ | lex | 129 | 129 |
| ✅ | parse | 161 | 161 | | ✅ | parse | 169 | 169 |
| ⬜ | types | 0 | 0 | | ⬜ | types | 0 | 0 |
| ⬜ | eval | 0 | 0 | | ⬜ | eval | 0 | 0 |
| ⬜ | runtime | 0 | 0 | | ⬜ | runtime | 0 | 0 |

View File

@@ -1047,6 +1047,86 @@
(list :defer (ast-app (ast-var "cleanup") (list))) (list :defer (ast-app (ast-var "cleanup") (list)))
(list :go (ast-app (ast-var "worker") (list))))))) (list :go (ast-app (ast-var "worker") (list)))))))
(go-parse-test
"switch: tagged with two cases"
(go-parse "switch x { case 1: a; case 2: b }")
(list
:switch (ast-var "x")
(list
(list :case (list (ast-literal "1")) (list (ast-var "a")))
(list :case (list (ast-literal "2")) (list (ast-var "b"))))))
(go-parse-test
"switch: multi-value case"
(go-parse "switch x { case 1, 2: a; case 3: b }")
(list
:switch (ast-var "x")
(list
(list
:case (list (ast-literal "1") (ast-literal "2"))
(list (ast-var "a")))
(list :case (list (ast-literal "3")) (list (ast-var "b"))))))
(go-parse-test
"switch: tagless (if-else chain)"
(go-parse "switch { case x > 0: a; case x < 0: b; default: c }")
(list
:switch nil
(list
(list
:case (list
(ast-app (ast-var ">") (list (ast-var "x") (ast-literal "0"))))
(list (ast-var "a")))
(list
:case (list
(ast-app (ast-var "<") (list (ast-var "x") (ast-literal "0"))))
(list (ast-var "b")))
(list :default (list (ast-var "c"))))))
(go-parse-test
"switch: with default only"
(go-parse "switch x { default: y }")
(list :switch (ast-var "x") (list (list :default (list (ast-var "y"))))))
(go-parse-test
"select: recv-into-var case"
(go-parse "select { case x := <-ch: a }")
(list
:select (list
(list
:select-case (list
:short-decl (list (ast-var "x"))
(list (ast-app (ast-var "<-") (list (ast-var "ch")))))
(list (ast-var "a"))))))
(go-parse-test
"select: send case"
(go-parse "select { case ch <- v: done() }")
(list
:select (list
(list
:select-case (list :send (ast-var "ch") (ast-var "v"))
(list (ast-app (ast-var "done") (list)))))))
(go-parse-test
"select: recv (discard) case"
(go-parse "select { case <-ch: a }")
(list
:select (list
(list
:select-case (ast-app (ast-var "<-") (list (ast-var "ch")))
(list (ast-var "a"))))))
(go-parse-test
"select: with default (non-blocking)"
(go-parse "select { case <-ch: a; default: b }")
(list
:select (list
(list
:select-case (ast-app (ast-var "<-") (list (ast-var "ch")))
(list (ast-var "a")))
(list :default (list (ast-var "b"))))))
(go-parse-test "non-primary: '+'" (go-parse "+") nil) (go-parse-test "non-primary: '+'" (go-parse "+") nil)
(go-parse-test "non-primary: empty" (go-parse "") nil) (go-parse-test "non-primary: empty" (go-parse "") nil)

View File

@@ -196,17 +196,18 @@ Progress-log line → push `origin/loops/go`.
canonical AST kit. Grouped/parenthesized decls (`var (...)`, etc.) canonical AST kit. Grouped/parenthesized decls (`var (...)`, etc.)
and variadic params deferred. Anonymous param-list disambiguation and variadic params deferred. Anonymous param-list disambiguation
(`func(int, string)`) is a known parser-greedy limitation, flagged. (`func(int, string)`) is a known parser-greedy limitation, flagged.
- [/] Statements: return, short-decl, assign, compound assign, expr stmt, - [x] Statements: return, short-decl, assign, compound assign, expr stmt,
block, if/else (chained), for (4 shapes incl. range), break, block, if/else (chained), for (4 shapes incl. range), break,
continue, inc-dec, **`go EXPR`, `defer EXPR`, send `ch <- v`, continue, inc-dec, go, defer, send, **switch (tagged / tagless,
`for k, v := range coll`** all done. Composite-literal `{` multi-value cases, default), select (recv-into-var / send /
suppression active in control-flow conditions. `switch` and bare-recv / default)** all done. Composite-literal `{` suppression
`select` deferred. active in control-flow conditions. Type-switch (`switch v :=
x.(type)`) deferred to a follow-up.
- [ ] End-to-end: hello-world, fibonacci, FizzBuzz, goroutine ping-pong, - [ ] End-to-end: hello-world, fibonacci, FizzBuzz, goroutine ping-pong,
struct + method. struct + method.
- **Acceptance:** parse/ suite at 80+ tests. **Acceptance bar crossed: - **Acceptance:** parse/ suite at 80+ tests. **Acceptance bar crossed:
161/161.** Remaining sub-items (switch/select, end-to-end programs) 169/169.** Remaining sub-item (end-to-end programs) keeps Phase 2
keep Phase 2 open ⬜. open ⬜.
### Phase 3 — Bidirectional type checker, MVP (`lib/go/types.sx`) ⬜ ### Phase 3 — Bidirectional type checker, MVP (`lib/go/types.sx`) ⬜
- **Independent implementation.** Do NOT use lib/guest/static-types- - **Independent implementation.** Do NOT use lib/guest/static-types-
@@ -523,6 +524,24 @@ Minimal repro: see `lib/go/lex.sx#gl-oct-digit?` and `#gl-match-op`.
_Newest first. Append one dated entry per commit._ _Newest first. Append one dated entry per commit._
- 2026-05-27 — Phase 2 cont.: `switch` and `select` statements.
Tagged + tagless switch, multi-value cases, `default`, and select
with recv-into-var / send / bare-recv / default cases. New
`gp-parse-case-body` reads stmts until the next `case`/`default`/`}`
without consuming the terminator. AST shapes:
`(list :switch TAG CASES)`,
`(list :case VALUES BODY)`,
`(list :select CASES)`,
`(list :select-case COMM-STMT BODY)`,
`(list :default BODY)`. With this, **Phase 2 statement coverage
is complete** — type-switch is the one remaining shape (deferred).
+8 tests, parse 169/169, total 298/298. `[shapes-scheduler]` —
sister-plan diary updated with the `:select-case` uniform shape
insight (single kit primitive covers all four Go case kinds; default
vs no-default determines blocking semantics; cross-references to
Erlang's `receive ... after`).
Sister-plan diary update follows.
- 2026-05-27 — Phase 2 cont.: concurrency + iteration statements. - 2026-05-27 — Phase 2 cont.: concurrency + iteration statements.
`go EXPR`, `defer EXPR`, channel send `ch <- v`, and the four `go EXPR`, `defer EXPR`, channel send `ch <- v`, and the four
`for ... range` shapes (no-kv / k-only / k,v / assign-form). New `for ... range` shapes (no-kv / k-only / k,v / assign-form). New

View File

@@ -231,6 +231,24 @@ real result.
_Newest first. Append one dated entry per milestone landed._ _Newest first. Append one dated entry per milestone landed._
- 2026-05-27 — Follow-up from same Phase 2 work: **`select` AST shape**
landed. Each case is `(list :select-case COMM-STMT BODY)` where
COMM-STMT is one of `:send`, `:short-decl` (recv into new var),
`:assign` (recv into existing var), or a bare receive expression
`(:app (:var "<-") [chan])`. The shape is uniform across all four
comm-stmt kinds — the kit's `sched-select` primitive should accept a
list of cases each described by `(direction chan value-target?)` and
let the kit's runtime pick a ready case. That uniformity is what
makes a single kit primitive cover all four Go case shapes.
Also: Go's `select` with `default` makes the multiplexer non-blocking;
without default it blocks until a case is ready. The kit primitive
should mirror this — present-or-absent default determines blocking
semantics. Erlang's `receive ... after Timeout -> ...` is a similar
pattern with a timeout case rather than default; the kit primitive
should handle both as instances of "non-blocking-fallback case."
Source: Go-on-SX commit `parse.sx — switch + select`.
- 2026-05-27 — From Go-on-SX Phase 2 (parser side, ahead of scheduler - 2026-05-27 — From Go-on-SX Phase 2 (parser side, ahead of scheduler
implementation): the **parsed AST shapes** for Go's concurrency implementation): the **parsed AST shapes** for Go's concurrency
primitives have landed and are worth recording before Phase 5 builds primitives have landed and are worth recording before Phase 5 builds