diff --git a/lib/go/parse.sx b/lib/go/parse.sx index c94a6d43..7d49f72f 100644 --- a/lib/go/parse.sx +++ b/lib/go/parse.sx @@ -95,6 +95,49 @@ :else nil))) (gp-args-rest) args))))) + (define + gp-parse-struct-fields + ;; Caller positioned BEFORE '{'. Parses fields until '}'. + ;; field := name [, name]* TYPE + ;; Tolerates ASI-inserted semis between fields. Embedded fields + ;; (anonymous type without preceding names) and field tags are + ;; deferred. Returns a list of (list :field NAMES TYPE). + (fn + () + (when (and (= (gp-tok-type) "op") (= (gp-tok-value) "{")) + (gp-advance!)) + (let ((fields (list))) + (define + gp-struct-loop + (fn + () + (cond + (= (gp-tok-type) "semi") + (do (gp-advance!) (gp-struct-loop)) + (and (= (gp-tok-type) "op") (= (gp-tok-value) "}")) + (gp-advance!) + (= (gp-tok-type) "ident") + (do + (let ((names (list (gp-tok-value)))) + (gp-advance!) + (define + gp-names-rest + (fn + () + (when (and (= (gp-tok-type) "op") + (= (gp-tok-value) ",")) + (gp-advance!) + (when (= (gp-tok-type) "ident") + (append! names (gp-tok-value)) + (gp-advance!)) + (gp-names-rest)))) + (gp-names-rest) + (let ((ty (gp-parse-type))) + (append! fields (list :field names ty)))) + (gp-struct-loop)) + :else nil))) + (gp-struct-loop) + fields))) (define gp-parse-func-type-params ;; Anonymous-only func-type params: caller is positioned BEFORE @@ -204,6 +247,10 @@ (let ((params (gp-parse-func-type-params))) (let ((results (gp-parse-func-type-results))) (list :ty-func params results)))) + (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "struct")) + (do + (gp-advance!) + (list :ty-struct (gp-parse-struct-fields))) (= (gp-tok-type) "ident") (let ((name (gp-tok-value))) (gp-advance!) diff --git a/lib/go/scoreboard.json b/lib/go/scoreboard.json index dfb6a997..1e97f9af 100644 --- a/lib/go/scoreboard.json +++ b/lib/go/scoreboard.json @@ -1,10 +1,10 @@ { "language": "go", - "total_pass": 219, - "total": 219, + "total_pass": 227, + "total": 227, "suites": [ {"name":"lex","pass":129,"total":129,"status":"ok"}, - {"name":"parse","pass":90,"total":90,"status":"ok"}, + {"name":"parse","pass":98,"total":98,"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 f4e9712a..c435cc94 100644 --- a/lib/go/scoreboard.md +++ b/lib/go/scoreboard.md @@ -1,11 +1,11 @@ # Go-on-SX Scoreboard -**Total: 219 / 219 tests passing** +**Total: 227 / 227 tests passing** | | Suite | Pass | Total | |---|---|---|---| | ✅ | lex | 129 | 129 | -| ✅ | parse | 90 | 90 | +| ✅ | parse | 98 | 98 | | ⬜ | types | 0 | 0 | | ⬜ | eval | 0 | 0 | | ⬜ | runtime | 0 | 0 | diff --git a/lib/go/tests/parse.sx b/lib/go/tests/parse.sx index 3cf5db2c..d695540c 100644 --- a/lib/go/tests/parse.sx +++ b/lib/go/tests/parse.sx @@ -507,6 +507,76 @@ :ty-chan :both (list :ty-func (list) (list (list :ty-name "int")))))) +(go-parse-test + "ty: struct {} (empty)" + (go-parse "v.(struct {})") + (list :assert (ast-var "v") (list :ty-struct (list)))) + +(go-parse-test + "ty: struct { x int }" + (go-parse "v.(struct { x int })") + (list + :assert (ast-var "v") + (list :ty-struct (list (list :field (list "x") (list :ty-name "int")))))) + +(go-parse-test + "ty: struct { x int; y string } (multiple fields)" + (go-parse "v.(struct { x int; y string })") + (list + :assert (ast-var "v") + (list + :ty-struct (list + (list :field (list "x") (list :ty-name "int")) + (list :field (list "y") (list :ty-name "string")))))) + +(go-parse-test + "ty: struct { x, y int } (shared type)" + (go-parse "v.(struct { x, y int })") + (list + :assert (ast-var "v") + (list + :ty-struct (list (list :field (list "x" "y") (list :ty-name "int")))))) + +(go-parse-test + "ty: struct { p *T } (pointer field)" + (go-parse "v.(struct { p *T })") + (list + :assert (ast-var "v") + (list + :ty-struct (list (list :field (list "p") (list :ty-ptr (list :ty-name "T"))))))) + +(go-parse-test + "ty: struct { items []int } (slice field)" + (go-parse "v.(struct { items []int })") + (list + :assert (ast-var "v") + (list + :ty-struct (list + (list :field (list "items") (list :ty-slice (list :ty-name "int"))))))) + +(go-parse-test + "ty: struct { a int; b, c string; d *T } (mixed)" + (go-parse "v.(struct { a int; b, c string; d *T })") + (list + :assert (ast-var "v") + (list + :ty-struct (list + (list :field (list "a") (list :ty-name "int")) + (list :field (list "b" "c") (list :ty-name "string")) + (list :field (list "d") (list :ty-ptr (list :ty-name "T"))))))) + +(go-parse-test + "ty: nested struct { inner struct { x int } }" + (go-parse "v.(struct { inner struct { x int } })") + (list + :assert (ast-var "v") + (list + :ty-struct (list + (list + :field (list "inner") + (list + :ty-struct (list (list :field (list "x") (list :ty-name "int"))))))))) + (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 38384bae..ec65c7e8 100644 --- a/plans/go-on-sx.md +++ b/plans/go-on-sx.md @@ -176,9 +176,10 @@ Progress-log line → push `origin/loops/go`. `*T` / `**T`); full type grammar still pending below. - [/] Type expressions: **slice `[]T`, array `[N]T`, map `map[K]V`, chan `chan T` / `chan<- T` / `<-chan T`, pointer `*T`, named `T`, - qualified `pkg.T`, func `func(...) ...` (anonymous params, single - or multi return)** all done — kit has no type primitives. - struct / interface / variadic / named-params / generics deferred. + qualified `pkg.T`, func `func(...) ...`, struct `struct{...}` with + shared-type field rows (`x, y int`)** all done — kit has no type + primitives. Interface, embedded fields, field tags, variadic, + named func-params, generics deferred. - [ ] Composite literals: `T{...}`, `[]T{...}`, `map[K]V{...}`, `struct{...}{...}`. - [ ] Declarations: `package`, `import`, `var`, `const`, `type`, `func` @@ -189,8 +190,8 @@ Progress-log line → push `origin/loops/go`. - [ ] End-to-end: hello-world, fibonacci, FizzBuzz, goroutine ping-pong, struct + method. - **Acceptance:** parse/ suite at 80+ tests. **Acceptance bar crossed: - 90/90.** Remaining sub-items (struct/interface types, composite - literals, decls, stmts, e2e) still keep Phase 2 open ⬜. + 98/98.** Remaining sub-items (interface types, composite literals, + decls, stmts, e2e) still keep Phase 2 open ⬜. ### Phase 3 — Bidirectional type checker, MVP (`lib/go/types.sx`) ⬜ - **Independent implementation.** Do NOT use lib/guest/static-types- @@ -450,7 +451,22 @@ Observed from building the Go parser: Minimal repro: see `lib/go/parse.sx#gp-parse-postfix` + `gp-parse-bracket`. -4. **No type-expression primitives.** Every statically-typed guest needs +4. **No "named binding(s) of a type" node.** Building struct types + surfaced a shape that recurs everywhere: + + ``` + (list :field NAMES TYPE) + ``` + + Same shape appears in: struct fields (`x, y int`), func parameters + (`func(a, b int, c string)`), method receivers (`m(a, b int)`), + variable declarations (`var x, y int`). Three Phase-2 sub-deliverables + (struct fields, func decls, var decls) all want this shape. Promoting + it once means Rust struct fields, Swift parameters, TS class fields, + Java method signatures all get a free home. Candidate canonical name: + `ast-binding-group` or `ast-named-of-type`. + +5. **No type-expression primitives.** Every statically-typed guest needs to express types in source. Proposed canonical shapes: ``` @@ -492,6 +508,15 @@ 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.: struct-type expressions. `struct {}`, + `struct { x int }`, `struct { x int; y string }`, `struct { x, y int }` + (shared type), nested struct fields. `gp-parse-struct-fields` walks + field rows tolerating ASI semis; each row is a name list + type. AST: + `(list :ty-struct FIELDS)` with each field `(list :field NAMES TYPE)`. + Embedded fields, tags, and methods deferred. +8 tests, parse 98/98, + total 227/227. `[proposes-ast]` — the `:field` shape (NAMES + TYPE) + recurs in func params, method receivers, var decls; flagged in + Blockers as a unified `ast-binding-group` candidate for the kit. - 2026-05-27 — Phase 2 cont.: func-type expressions. `func()`, `func() int`, `func(int, string)`, `func(int) string`, `func() (int, error)`. AST shape `(list :ty-func PARAMS RESULTS)`