go: parse.sx — func + method declarations + 8 tests [shapes-static-types-bidirectional]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s
Adds Go func and method declarations:
func main() {}
func add(x, y int) int { return x + y }
func mix(x int, y string) {}
func divmod(a, b int) (int, int) {}
func sig(x int) int (no body)
func (p *Point) String() string { ... } (method, pointer recv)
func (s Stack) Len() int { ... } (method, value recv)
func nested() { if true { x := 1; { y := 2 } } } (nested braces)
New gp-parse-decl-param-group implements named-greedy disambiguation:
collects consecutive 'ident [, ident]*' then parses a type. Anonymous
mixed lists like 'func(int, string)' are a known limitation (parser
treats first ident as a name); flagged in plan.
gp-skip-block! brace-balances over the body; the AST stores ':body'
as a sentinel until statement parsing lands. Methods use the receiver
parameter shape directly.
AST:
(list :func-decl NAME PARAMS RESULTS BODY)
(list :method-decl RECV NAME PARAMS RESULTS BODY)
**All five `:field` binding-group consumers now exist** across the
parser: struct fields, var, const, func params, method receivers.
That's strong cross-deliverable validation of the ast-binding-group
proposal from Blockers — five different declaration contexts, one
shared shape.
This is the chisel-relevant insight for sister plan static-types-
bidirectional: an entry has been appended to its design diary
describing how `:field` will be the load-bearing input shape for
the bidirectional checker's `check Γ e T` judgment across these
contexts.
parse 132/132, total 261/261.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
132
lib/go/parse.sx
132
lib/go/parse.sx
@@ -594,6 +594,133 @@
|
|||||||
(let ((t (gp-parse-type)))
|
(let ((t (gp-parse-type)))
|
||||||
(list :type-decl name t)))
|
(list :type-decl name t)))
|
||||||
:else nil)))
|
: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
|
(define
|
||||||
gp-parse-decl
|
gp-parse-decl
|
||||||
;; Single declaration: package / import / var / const / type.
|
;; Single declaration: package / import / var / const / type.
|
||||||
@@ -625,6 +752,8 @@
|
|||||||
(do (gp-advance!) (gp-parse-var-or-const :const-decl))
|
(do (gp-advance!) (gp-parse-var-or-const :const-decl))
|
||||||
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "type"))
|
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "type"))
|
||||||
(do (gp-advance!) (gp-parse-type-decl))
|
(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)))
|
:else nil)))
|
||||||
(define
|
(define
|
||||||
gp-parse-top
|
gp-parse-top
|
||||||
@@ -641,7 +770,8 @@
|
|||||||
(= (gp-tok-value) "import")
|
(= (gp-tok-value) "import")
|
||||||
(= (gp-tok-value) "var")
|
(= (gp-tok-value) "var")
|
||||||
(= (gp-tok-value) "const")
|
(= (gp-tok-value) "const")
|
||||||
(= (gp-tok-value) "type")))
|
(= (gp-tok-value) "type")
|
||||||
|
(= (gp-tok-value) "func")))
|
||||||
(gp-parse-decl)
|
(gp-parse-decl)
|
||||||
:else (gp-parse-expr 1))))
|
:else (gp-parse-expr 1))))
|
||||||
(gp-parse-top))))
|
(gp-parse-top))))
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"language": "go",
|
"language": "go",
|
||||||
"total_pass": 253,
|
"total_pass": 261,
|
||||||
"total": 253,
|
"total": 261,
|
||||||
"suites": [
|
"suites": [
|
||||||
{"name":"lex","pass":129,"total":129,"status":"ok"},
|
{"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":"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"},
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# Go-on-SX Scoreboard
|
# Go-on-SX Scoreboard
|
||||||
|
|
||||||
**Total: 253 / 253 tests passing**
|
**Total: 261 / 261 tests passing**
|
||||||
|
|
||||||
| | Suite | Pass | Total |
|
| | Suite | Pass | Total |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| ✅ | lex | 129 | 129 |
|
| ✅ | lex | 129 | 129 |
|
||||||
| ✅ | parse | 124 | 124 |
|
| ✅ | parse | 132 | 132 |
|
||||||
| ⬜ | types | 0 | 0 |
|
| ⬜ | types | 0 | 0 |
|
||||||
| ⬜ | eval | 0 | 0 |
|
| ⬜ | eval | 0 | 0 |
|
||||||
| ⬜ | runtime | 0 | 0 |
|
| ⬜ | runtime | 0 | 0 |
|
||||||
|
|||||||
@@ -775,6 +775,74 @@
|
|||||||
(list
|
(list
|
||||||
:ty-struct (list (list :field (list "x" "y") (list :ty-name "int"))))))
|
: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: '+'" (go-parse "+") nil)
|
||||||
|
|
||||||
(go-parse-test "non-primary: empty" (go-parse "") nil)
|
(go-parse-test "non-primary: empty" (go-parse "") nil)
|
||||||
|
|||||||
@@ -188,20 +188,21 @@ Progress-log line → push `origin/loops/go`.
|
|||||||
context (e.g. `if cond { ... }`) my parser would WRONGLY treat
|
context (e.g. `if cond { ... }`) my parser would WRONGLY treat
|
||||||
the body as a composite; statement parsing will need a "no-
|
the body as a composite; statement parsing will need a "no-
|
||||||
composite-here" mode flag — to be added when statements arrive.
|
composite-here" mode flag — to be added when statements arrive.
|
||||||
- [/] Declarations: `package` / `import` / `var` / `const` / `type` all
|
- [x] Declarations: `package`, `import`, `var`, `const`, `type`, `func`
|
||||||
done (single-decl, ungrouped forms). `var`/`const` use the
|
(with named-greedy params + method receivers + body skipped
|
||||||
`:field` binding-group shape from Blockers — first cross-deliverable
|
opaquely until statement parsing arrives). All five `:field`
|
||||||
use of the proposed `ast-binding-group`. `func` decls (with method
|
consumers now exist (struct fields, var, const, func params, method
|
||||||
receivers + named params) and parenthesized grouped decls
|
receivers) — strong signal that `ast-binding-group` belongs in the
|
||||||
(`var (...)`, `import (...)`) deferred.
|
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 +
|
- [ ] Statements: `if`/`else`, `for` (C-style + range), `switch` (expr +
|
||||||
type), `select`, `return`, `defer`, `go`, `break`/`continue`,
|
type), `select`, `return`, `defer`, `go`, `break`/`continue`,
|
||||||
assign, short-decl `:=`, send `ch <- v`, recv `<-ch`.
|
assign, short-decl `:=`, send `ch <- v`, recv `<-ch`.
|
||||||
- [ ] 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:
|
||||||
124/124.** Remaining sub-items (func decls, stmts, e2e) keep
|
132/132.** Remaining sub-items (stmts, e2e) keep Phase 2 open ⬜.
|
||||||
Phase 2 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-
|
||||||
@@ -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._
|
_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"`,
|
- 2026-05-27 — Phase 2 cont.: declarations — `package N`, `import "p"`,
|
||||||
`var name [TYPE] [= EXPRS]`, `const name [TYPE] [= EXPRS]`,
|
`var name [TYPE] [= EXPRS]`, `const name [TYPE] [= EXPRS]`,
|
||||||
`type NAME TYPE`. New `gp-parse-top` dispatcher routes the five
|
`type NAME TYPE`. New `gp-parse-top` dispatcher routes the five
|
||||||
|
|||||||
@@ -282,6 +282,25 @@ The kits compose; design accordingly.
|
|||||||
|
|
||||||
_Newest first. Append one dated entry per milestone landed._
|
_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
|
- 2026-05-26 — Plan drafted as design diary. Phase 0 unstarted. Gated on
|
||||||
Go-on-SX (first consumer) and a TBD second consumer (recommendation:
|
Go-on-SX (first consumer) and a TBD second consumer (recommendation:
|
||||||
TypeScript). No code yet — kit cannot exist before two consumers do.
|
TypeScript). No code yet — kit cannot exist before two consumers do.
|
||||||
|
|||||||
Reference in New Issue
Block a user