diff --git a/lib/go/parse.sx b/lib/go/parse.sx index 05c328f8..6ccc7d6e 100644 --- a/lib/go/parse.sx +++ b/lib/go/parse.sx @@ -529,4 +529,119 @@ (ast-var (get tok :value)) (list left right)) 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)))) diff --git a/lib/go/scoreboard.json b/lib/go/scoreboard.json index 4e21cc98..7603fb28 100644 --- a/lib/go/scoreboard.json +++ b/lib/go/scoreboard.json @@ -1,10 +1,10 @@ { "language": "go", - "total_pass": 243, - "total": 243, + "total_pass": 253, + "total": 253, "suites": [ {"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":"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 9579cd8e..3ef46c1f 100644 --- a/lib/go/scoreboard.md +++ b/lib/go/scoreboard.md @@ -1,11 +1,11 @@ # Go-on-SX Scoreboard -**Total: 243 / 243 tests passing** +**Total: 253 / 253 tests passing** | | Suite | Pass | Total | |---|---|---|---| | ✅ | lex | 129 | 129 | -| ✅ | parse | 114 | 114 | +| ✅ | parse | 124 | 124 | | ⬜ | types | 0 | 0 | | ⬜ | eval | 0 | 0 | | ⬜ | runtime | 0 | 0 | diff --git a/lib/go/tests/parse.sx b/lib/go/tests/parse.sx index ee009125..0f75d069 100644 --- a/lib/go/tests/parse.sx +++ b/lib/go/tests/parse.sx @@ -714,6 +714,67 @@ :composite (ast-var "Point") (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: empty" (go-parse "") nil) diff --git a/plans/go-on-sx.md b/plans/go-on-sx.md index 4fd718f9..b5cc6ae5 100644 --- a/plans/go-on-sx.md +++ b/plans/go-on-sx.md @@ -188,15 +188,20 @@ 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`, `func` - (including methods, parameter lists, return types). +- [/] 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. - [ ] 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: - 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`) ⬜ - **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._ +- 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}`, `T{X: 1, Y: 2}`, `[]T{...}`, `[N]T{...}`, `map[K]V{...}`, `pkg.T{...}`, nested composites. AST shape