diff --git a/lib/go/parse.sx b/lib/go/parse.sx index 6ccc7d6e..0d2eafb9 100644 --- a/lib/go/parse.sx +++ b/lib/go/parse.sx @@ -594,6 +594,133 @@ (let ((t (gp-parse-type))) (list :type-decl name t))) :else nil))) + (define + gp-parse-decl-param-group + ;; Parses one parameter binding group inside a func decl param list. + ;; Returns (list :field NAMES TYPE). Named-greedy: collects all + ;; consecutive idents separated by commas, then a type. Fails for + ;; mixed anonymous lists like func(int, string) — flagged in plan. + (fn + () + (cond + (not (= (gp-tok-type) "ident")) + (list :field (list) (gp-parse-type)) + :else + (let ((names (list)) (candidate (gp-tok-value))) + (gp-advance!) + (define + gp-dpg-loop + (fn + () + (when (and (= (gp-tok-type) "op") (= (gp-tok-value) ",")) + (let ((saved-idx gp-idx)) + (gp-advance!) + (cond + (= (gp-tok-type) "ident") + (do + (append! names candidate) + (set! candidate (gp-tok-value)) + (gp-advance!) + (gp-dpg-loop)) + :else + (set! gp-idx saved-idx)))))) + (gp-dpg-loop) + (cond + (and (= (gp-tok-type) "op") + (or (= (gp-tok-value) ")") (= (gp-tok-value) ","))) + (list :field names (list :ty-name candidate)) + :else + (do + (append! names candidate) + (list :field names (gp-parse-type)))))))) + (define + gp-parse-func-decl-params + ;; Func-decl parameter list — comma-separated binding groups. + ;; Caller positioned BEFORE '('. Consumes ')'. + (fn + () + (when (and (= (gp-tok-type) "op") (= (gp-tok-value) "(")) + (gp-advance!)) + (let ((groups (list))) + (cond + (and (= (gp-tok-type) "op") (= (gp-tok-value) ")")) + (do (gp-advance!) groups) + :else + (do + (append! groups (gp-parse-decl-param-group)) + (define + gp-fdp-rest + (fn + () + (cond + (and (= (gp-tok-type) "op") (= (gp-tok-value) ",")) + (do + (gp-advance!) + (append! groups (gp-parse-decl-param-group)) + (gp-fdp-rest)) + (and (= (gp-tok-type) "op") (= (gp-tok-value) ")")) + (gp-advance!) + :else nil))) + (gp-fdp-rest) + groups))))) + (define + gp-skip-block! + ;; Brace-balanced skip. Caller has consumed the opening '{'. + ;; Statement parsing arrives in a later iteration; for now the + ;; body is opaque and stored as the keyword :body in the AST. + (fn + () + (let ((depth 1)) + (define + gp-block-loop + (fn + () + (cond + (= (gp-tok-type) "eof") nil + (and (= (gp-tok-type) "op") (= (gp-tok-value) "{")) + (do (set! depth (+ depth 1)) (gp-advance!) (gp-block-loop)) + (and (= (gp-tok-type) "op") (= (gp-tok-value) "}")) + (do + (set! depth (- depth 1)) + (gp-advance!) + (when (> depth 0) (gp-block-loop))) + :else (do (gp-advance!) (gp-block-loop))))) + (gp-block-loop)))) + (define + gp-parse-func-decl + ;; Caller has consumed 'func'. + ;; func NAME (params) [results] { body } + ;; func (recv) NAME (params) [results] { body } — method + ;; AST: + ;; (list :func-decl NAME PARAMS RESULTS BODY) + ;; (list :method-decl RECV NAME PARAMS RESULTS BODY) + ;; BODY is :body (opaque) if a block was present, else nil. + (fn + () + (let ((recv nil)) + (when (and (= (gp-tok-type) "op") (= (gp-tok-value) "(")) + (gp-advance!) + (set! recv (gp-parse-decl-param-group)) + (when (and (= (gp-tok-type) "op") (= (gp-tok-value) ")")) + (gp-advance!))) + (cond + (= (gp-tok-type) "ident") + (let ((name (gp-tok-value))) + (gp-advance!) + (let ((params (gp-parse-func-decl-params))) + (let ((results (gp-parse-func-type-results))) + (let ((body nil)) + (when (and (= (gp-tok-type) "op") + (= (gp-tok-value) "{")) + (gp-advance!) + (gp-skip-block!) + (set! body :body)) + (cond + (= recv nil) + (list :func-decl name params results body) + :else + (list :method-decl recv name params results body)))))) + :else nil)))) (define gp-parse-decl ;; Single declaration: package / import / var / const / type. @@ -625,6 +752,8 @@ (do (gp-advance!) (gp-parse-var-or-const :const-decl)) (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "type")) (do (gp-advance!) (gp-parse-type-decl)) + (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "func")) + (do (gp-advance!) (gp-parse-func-decl)) :else nil))) (define gp-parse-top @@ -641,7 +770,8 @@ (= (gp-tok-value) "import") (= (gp-tok-value) "var") (= (gp-tok-value) "const") - (= (gp-tok-value) "type"))) + (= (gp-tok-value) "type") + (= (gp-tok-value) "func"))) (gp-parse-decl) :else (gp-parse-expr 1)))) (gp-parse-top)))) diff --git a/lib/go/scoreboard.json b/lib/go/scoreboard.json index 7603fb28..7848829e 100644 --- a/lib/go/scoreboard.json +++ b/lib/go/scoreboard.json @@ -1,10 +1,10 @@ { "language": "go", - "total_pass": 253, - "total": 253, + "total_pass": 261, + "total": 261, "suites": [ {"name":"lex","pass":129,"total":129,"status":"ok"}, - {"name":"parse","pass":124,"total":124,"status":"ok"}, + {"name":"parse","pass":132,"total":132,"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 3ef46c1f..2c3a025c 100644 --- a/lib/go/scoreboard.md +++ b/lib/go/scoreboard.md @@ -1,11 +1,11 @@ # Go-on-SX Scoreboard -**Total: 253 / 253 tests passing** +**Total: 261 / 261 tests passing** | | Suite | Pass | Total | |---|---|---|---| | ✅ | lex | 129 | 129 | -| ✅ | parse | 124 | 124 | +| ✅ | parse | 132 | 132 | | ⬜ | types | 0 | 0 | | ⬜ | eval | 0 | 0 | | ⬜ | runtime | 0 | 0 | diff --git a/lib/go/tests/parse.sx b/lib/go/tests/parse.sx index 0f75d069..b41889b2 100644 --- a/lib/go/tests/parse.sx +++ b/lib/go/tests/parse.sx @@ -775,6 +775,74 @@ (list :ty-struct (list (list :field (list "x" "y") (list :ty-name "int")))))) +(go-parse-test + "fdecl: func main() {}" + (go-parse "func main() {}") + (list :func-decl "main" (list) (list) :body)) + +(go-parse-test + "fdecl: func add(x, y int) int { return x + y }" + (go-parse "func add(x, y int) int { return x + y }") + (list + :func-decl "add" + (list (list :field (list "x" "y") (list :ty-name "int"))) + (list (list :ty-name "int")) + :body)) + +(go-parse-test + "fdecl: func with multi-group params" + (go-parse "func mix(x int, y string) {}") + (list + :func-decl "mix" + (list + (list :field (list "x") (list :ty-name "int")) + (list :field (list "y") (list :ty-name "string"))) + (list) + :body)) + +(go-parse-test + "fdecl: func with multi-return" + (go-parse "func divmod(a, b int) (int, int) {}") + (list + :func-decl "divmod" + (list (list :field (list "a" "b") (list :ty-name "int"))) + (list (list :ty-name "int") (list :ty-name "int")) + :body)) + +(go-parse-test + "fdecl: func with no body (signature only)" + (go-parse "func sig(x int) int") + (list + :func-decl "sig" + (list (list :field (list "x") (list :ty-name "int"))) + (list (list :ty-name "int")) + nil)) + +(go-parse-test + "mdecl: method on pointer receiver" + (go-parse "func (p *Point) String() string { return p.x }") + (list + :method-decl (list :field (list "p") (list :ty-ptr (list :ty-name "Point"))) + "String" + (list) + (list (list :ty-name "string")) + :body)) + +(go-parse-test + "mdecl: method on value receiver" + (go-parse "func (s Stack) Len() int { return 0 }") + (list + :method-decl (list :field (list "s") (list :ty-name "Stack")) + "Len" + (list) + (list (list :ty-name "int")) + :body)) + +(go-parse-test + "fdecl: nested braces in body (skipped opaquely)" + (go-parse "func nested() { if true { x := 1; { y := 2 } } }") + (list :func-decl "nested" (list) (list) :body)) + (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 b5cc6ae5..4f3d7a65 100644 --- a/plans/go-on-sx.md +++ b/plans/go-on-sx.md @@ -188,20 +188,21 @@ Progress-log line → push `origin/loops/go`. context (e.g. `if cond { ... }`) my parser would WRONGLY treat the body as a composite; statement parsing will need a "no- composite-here" mode flag — to be added when statements arrive. -- [/] Declarations: `package` / `import` / `var` / `const` / `type` all - done (single-decl, ungrouped forms). `var`/`const` use the - `:field` binding-group shape from Blockers — first cross-deliverable - use of the proposed `ast-binding-group`. `func` decls (with method - receivers + named params) and parenthesized grouped decls - (`var (...)`, `import (...)`) deferred. +- [x] Declarations: `package`, `import`, `var`, `const`, `type`, `func` + (with named-greedy params + method receivers + body skipped + opaquely until statement parsing arrives). All five `:field` + consumers now exist (struct fields, var, const, func params, method + receivers) — strong signal that `ast-binding-group` belongs in the + canonical AST kit. Grouped/parenthesized decls (`var (...)`, etc.) + and variadic params deferred. Anonymous param-list disambiguation + (`func(int, string)`) is a known parser-greedy limitation, flagged. - [ ] Statements: `if`/`else`, `for` (C-style + range), `switch` (expr + type), `select`, `return`, `defer`, `go`, `break`/`continue`, assign, short-decl `:=`, send `ch <- v`, recv `<-ch`. - [ ] End-to-end: hello-world, fibonacci, FizzBuzz, goroutine ping-pong, struct + method. - **Acceptance:** parse/ suite at 80+ tests. **Acceptance bar crossed: - 124/124.** Remaining sub-items (func decls, stmts, e2e) keep - Phase 2 open ⬜. + 132/132.** Remaining sub-items (stmts, e2e) keep Phase 2 open ⬜. ### Phase 3 — Bidirectional type checker, MVP (`lib/go/types.sx`) ⬜ - **Independent implementation.** Do NOT use lib/guest/static-types- @@ -518,6 +519,23 @@ 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.: func declarations. `func f() {}`, + `func add(x, y int) int { ... }`, multi-group params, multi-return, + signature-only (no body), pointer-receiver and value-receiver methods, + nested-brace body. New `gp-parse-decl-param-group` uses a named-greedy + algorithm: collects consecutive `ident [, ident]*` then parses a + type. `gp-skip-block!` brace-balances over the body opaquely; the AST + stores `:body` as a sentinel pending statement parsing. With this, + **all five `:field` binding-group consumers now exist** (struct + fields, var, const, func params, method receivers) — strong cross- + deliverable validation of the `ast-binding-group` kit proposal. + Anonymous-param-list disambiguation (`func(int, string)`) is a known + greedy-parser limitation flagged in plan. +8 tests, parse 132/132, + total 261/261. `[shapes-static-types-bidirectional]` — the consistent + use of `:field` across decls is what the sister kit's bidirectional + checker will use to propagate types from declarations to bindings. + + Sister-plan diary update follows. - 2026-05-27 — Phase 2 cont.: declarations — `package N`, `import "p"`, `var name [TYPE] [= EXPRS]`, `const name [TYPE] [= EXPRS]`, `type NAME TYPE`. New `gp-parse-top` dispatcher routes the five diff --git a/plans/lib-guest-static-types-bidirectional.md b/plans/lib-guest-static-types-bidirectional.md index e941cff9..fe27c0fc 100644 --- a/plans/lib-guest-static-types-bidirectional.md +++ b/plans/lib-guest-static-types-bidirectional.md @@ -282,6 +282,25 @@ The kits compose; design accordingly. _Newest first. Append one dated entry per milestone landed._ +- 2026-05-27 — From Go-on-SX Phase 2 (func decls landing): parser-side + observation that's load-bearing for any bidirectional checker. Go's + parser ended up with a single shape — `(list :field NAMES TYPE)` — + that recurs in five contexts: struct fields, var decls, const decls, + func params, and method receivers. Each represents "these names are + bound to this type" — exactly the input shape `check` would consume + to seed the context with typed bindings. + + **Design insight**: the canonical bidirectional checker should accept + `:field`-shaped AST nodes uniformly across these contexts rather than + each context defining a bespoke shape. The kit's `check Γ e T` + judgment can dispatch on the enclosing form (struct vs var vs + func-param vs ...) but the local per-binding shape stays identical. + This is what statically-typed guest #2 should also produce — if it + does, the kit can ship a `field-bindings → context-extension` helper + that all consumers reuse. Cross-ref Go-on-SX plan's Blockers entry on + `ast-binding-group` for the parallel AST-kit proposal that supports + this. Source: Go-on-SX commit `parse.sx — func declarations`. + - 2026-05-26 — Plan drafted as design diary. Phase 0 unstarted. Gated on Go-on-SX (first consumer) and a TBD second consumer (recommendation: TypeScript). No code yet — kit cannot exist before two consumers do.