go: parse.sx — struct type expressions + 8 tests [proposes-ast]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s

Adds Go struct types to gp-parse-type:
  struct {}                       →  (list :ty-struct ())
  struct { x int }                →  (list :ty-struct [(:field [x] (:ty-name int))])
  struct { x int; y string }      →  multiple field rows
  struct { x, y int }             →  shared-type row (NAMES is a list)
  struct { inner struct { x int } }  →  nested struct types

gp-parse-struct-fields walks field rows tolerating ASI-inserted semis
(from newlines between fields). Each row collects 1+ names separated
by commas, then a single type that all the names share. Embedded
fields, field tags, and methods are deferred.

The :field shape (NAMES + TYPE) is a recurring multi-language pattern —
struct fields, func params, method receivers, var decls all map to it.
Logged in Blockers as a canonical-AST candidate
(ast-binding-group / ast-named-of-type); worth promoting once a second
consumer (parser of another statically-typed guest, or Go func decls)
exercises the same shape.

parse 98/98, total 227/227.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 08:12:07 +00:00
parent 9acdbcb8d8
commit a94ffa0feb
5 changed files with 153 additions and 11 deletions

View File

@@ -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!)

View File

@@ -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"},

View File

@@ -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 |

View File

@@ -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)