go: parse.sx — package/import/var/const/type declarations + 10 tests [consumes-ast]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 33s

First slice of Phase 2 declarations:
  package main                      →  (list :package "main")
  import "fmt"                      →  (ast-import "fmt")    [from kit]
  var x int                         →  var-decl + :field binding
  var x = 5                         →  init only (type inferred)
  var x int = 5                     →  both type and init
  var x, y int = 1, 2               →  multi-name shared type
  const Pi = 3.14                   →  const-decl
  const C int = 42                  →  typed const
  type T int                        →  named alias
  type Point struct { x, y int }    →  named struct

New gp-parse-top dispatches on the leading keyword: routes
package/import/var/const/type to gp-parse-decl; everything else
still goes through gp-parse-expr. Existing expression tests are
unaffected (cur won't be a decl keyword at expression start).

var/const decls use the (:field NAMES TYPE) shape from the
ast-binding-group proposal — first concrete cross-deliverable use:
struct fields, var decls, const decls all envelope through the
same node. That's the smell test for whether the kit shape is
right; so far it's clean.

import uses the canonical ast-import from lib/guest/ast.sx — first
direct use of a kit constructor for a declaration shape.

Grouped/parenthesized decls (var (...), import (...), const (...),
type (...)) and func decls (with method receivers + named params)
deferred to subsequent iterations.

parse 124/124, total 253/253.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 19:44:24 +00:00
parent 632e06d3cf
commit 4922b6e987
5 changed files with 202 additions and 9 deletions

View File

@@ -529,4 +529,119 @@
(ast-var (get tok :value)) (ast-var (get tok :value))
(list left right)) (list left right))
min-prec))))))))))) min-prec)))))))))))
(gp-parse-expr 1)))) (define
gp-parse-expr-list
;; Comma-separated expressions; reused by var/const initialisers.
(fn
()
(let ((exprs (list)))
(let ((first (gp-parse-expr 1)))
(when (not (= first nil)) (append! exprs first)))
(define
gp-exprs-rest
(fn
()
(when (and (= (gp-tok-type) "op") (= (gp-tok-value) ","))
(gp-advance!)
(let ((e (gp-parse-expr 1)))
(when (not (= e nil)) (append! exprs e)))
(gp-exprs-rest))))
(gp-exprs-rest)
exprs)))
(define
gp-parse-var-or-const
;; Caller has consumed 'var' or 'const'. TAG is :var-decl or :const-decl.
;; Shape: TAG (list :field NAMES TYPE-OR-NIL) EXPRS-OR-NIL
;; Both type and init are optional (must have at least one in Go;
;; lexer is permissive).
(fn
(tag)
(let ((names (list)))
(when (= (gp-tok-type) "ident")
(append! names (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 nil) (exprs nil))
(when (and (not (= (gp-tok-type) "eof"))
(not (= (gp-tok-type) "semi"))
(not (and (= (gp-tok-type) "op")
(= (gp-tok-value) "="))))
(set! ty (gp-parse-type)))
(when (and (= (gp-tok-type) "op") (= (gp-tok-value) "="))
(gp-advance!)
(set! exprs (gp-parse-expr-list)))
(list tag (list :field names ty) exprs)))))
(define
gp-parse-type-decl
;; Caller has consumed 'type'. Single-decl form only:
;; type NAME TYPE → (list :type-decl "NAME" TYPE)
(fn
()
(cond
(= (gp-tok-type) "ident")
(let ((name (gp-tok-value)))
(gp-advance!)
(let ((t (gp-parse-type)))
(list :type-decl name t)))
:else nil)))
(define
gp-parse-decl
;; Single declaration: package / import / var / const / type.
;; Grouped/parenthesized forms and func decls are deferred.
(fn
()
(cond
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "package"))
(do
(gp-advance!)
(cond
(= (gp-tok-type) "ident")
(let ((name (gp-tok-value)))
(gp-advance!)
(list :package name))
:else nil))
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "import"))
(do
(gp-advance!)
(cond
(= (gp-tok-type) "string")
(let ((path (gp-tok-value)))
(gp-advance!)
(ast-import path))
:else nil))
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "var"))
(do (gp-advance!) (gp-parse-var-or-const :var-decl))
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "const"))
(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))
:else nil)))
(define
gp-parse-top
;; Top-level dispatch: declaration keywords go to gp-parse-decl,
;; everything else is parsed as an expression. ASI semis at the
;; start are skipped.
(fn
()
(cond
(= (gp-tok-type) "semi")
(do (gp-advance!) (gp-parse-top))
(and (= (gp-tok-type) "keyword")
(or (= (gp-tok-value) "package")
(= (gp-tok-value) "import")
(= (gp-tok-value) "var")
(= (gp-tok-value) "const")
(= (gp-tok-value) "type")))
(gp-parse-decl)
:else (gp-parse-expr 1))))
(gp-parse-top))))

View File

@@ -1,10 +1,10 @@
{ {
"language": "go", "language": "go",
"total_pass": 243, "total_pass": 253,
"total": 243, "total": 253,
"suites": [ "suites": [
{"name":"lex","pass":129,"total":129,"status":"ok"}, {"name":"lex","pass":129,"total":129,"status":"ok"},
{"name":"parse","pass":114,"total":114,"status":"ok"}, {"name":"parse","pass":124,"total":124,"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: 243 / 243 tests passing** **Total: 253 / 253 tests passing**
| | Suite | Pass | Total | | | Suite | Pass | Total |
|---|---|---|---| |---|---|---|---|
| ✅ | lex | 129 | 129 | | ✅ | lex | 129 | 129 |
| ✅ | parse | 114 | 114 | | ✅ | parse | 124 | 124 |
| ⬜ | types | 0 | 0 | | ⬜ | types | 0 | 0 |
| ⬜ | eval | 0 | 0 | | ⬜ | eval | 0 | 0 |
| ⬜ | runtime | 0 | 0 | | ⬜ | runtime | 0 | 0 |

View File

@@ -714,6 +714,67 @@
:composite (ast-var "Point") :composite (ast-var "Point")
(list (ast-literal "3") (ast-literal "4")))))) (list (ast-literal "3") (ast-literal "4"))))))
(go-parse-test
"decl: package main"
(go-parse "package main")
(list :package "main"))
(go-parse-test
"decl: import \"fmt\""
(go-parse "import \"fmt\"")
(ast-import "fmt"))
(go-parse-test
"decl: var x int (type only, no init)"
(go-parse "var x int")
(list :var-decl (list :field (list "x") (list :ty-name "int")) nil))
(go-parse-test
"decl: var x = 5 (init only, type inferred)"
(go-parse "var x = 5")
(list :var-decl (list :field (list "x") nil) (list (ast-literal "5"))))
(go-parse-test
"decl: var x int = 5 (both type and init)"
(go-parse "var x int = 5")
(list
:var-decl (list :field (list "x") (list :ty-name "int"))
(list (ast-literal "5"))))
(go-parse-test
"decl: var x, y int = 1, 2 (multi-name shared type)"
(go-parse "var x, y int = 1, 2")
(list
:var-decl (list :field (list "x" "y") (list :ty-name "int"))
(list (ast-literal "1") (ast-literal "2"))))
(go-parse-test
"decl: const Pi = 3.14"
(go-parse "const Pi = 3.14")
(list
:const-decl (list :field (list "Pi") nil)
(list (ast-literal "3.14"))))
(go-parse-test
"decl: const C int = 42 (typed const)"
(go-parse "const C int = 42")
(list
:const-decl (list :field (list "C") (list :ty-name "int"))
(list (ast-literal "42"))))
(go-parse-test
"decl: type T int (named type)"
(go-parse "type T int")
(list :type-decl "T" (list :ty-name "int")))
(go-parse-test
"decl: type Point struct { x, y int }"
(go-parse "type Point struct { x, y int }")
(list
:type-decl "Point"
(list
:ty-struct (list (list :field (list "x" "y") (list :ty-name "int"))))))
(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

@@ -188,15 +188,20 @@ 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`, `func` - [/] Declarations: `package` / `import` / `var` / `const` / `type` all
(including methods, parameter lists, return types). 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.
- [ ] 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:
114/114.** Remaining sub-items (decls, stmts, e2e) keep Phase 2 open ⬜. 124/124.** Remaining sub-items (func decls, stmts, e2e) keep
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-
@@ -513,6 +518,18 @@ 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.: declarations — `package N`, `import "p"`,
`var name [TYPE] [= EXPRS]`, `const name [TYPE] [= EXPRS]`,
`type NAME TYPE`. New `gp-parse-top` dispatcher routes the five
decl keywords to `gp-parse-decl` while preserving expression parsing
for everything else. `var` and `const` reuse the `:field` binding-
group shape from Blockers — **first cross-deliverable use of the
proposed kit shape**: struct fields, func params, and now var/const
decls all share the same `(list :field NAMES TYPE)` envelope. `import`
uses canonical `ast-import` directly. Grouped forms (`var (...)`)
and `func` decls deferred. +10 tests, parse 124/124, total 253/253.
`[consumes-ast]` — first concrete use of `ast-import` from the kit;
also validates the `:field` shape across three contexts.
- 2026-05-27 — Phase 2 cont.: composite literals. `T{}`, `T{1, 2}`, - 2026-05-27 — Phase 2 cont.: composite literals. `T{}`, `T{1, 2}`,
`T{X: 1, Y: 2}`, `[]T{...}`, `[N]T{...}`, `map[K]V{...}`, `T{X: 1, Y: 2}`, `[]T{...}`, `[N]T{...}`, `map[K]V{...}`,
`pkg.T{...}`, nested composites. AST shape `pkg.T{...}`, nested composites. AST shape