From 5f6d62f45b447683c6361e78515c90c0596fce16 Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 27 May 2026 20:11:01 +0000 Subject: [PATCH] =?UTF-8?q?go:=20parse.sx=20=E2=80=94=20statements=20(retu?= =?UTF-8?q?rn=20/=20short-decl=20/=20assign=20/=20block)=20+=209=20tests?= =?UTF-8?q?=20[nothing]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First slice of Phase 2 statements. Replaces the func-decl ':body' sentinel with real (:block STMTS) parsing. gp-parse-stmt dispatches on the leading token: return [exprs] — (list :return EXPRS) { ... } — nested block (recurses into block-body) lhs := exprs — (list :short-decl LHS-LIST EXPRS) lhs = exprs — (list :assign LHS-LIST EXPRS) lhs OP= expr — (list :assign-op OP LHS-LIST [EXPR]) expr — bare expression statement var/const/type/func keywords — fall through to gp-parse-decl LHS may be a comma-separated list. Compound-assign covers all 11 Go forms (+= -= *= /= %= &= |= ^= <<= >>= &^=). gp-parse-block-body iterates: skips semis, terminates on '}', and for non-trivial tokens calls gp-parse-stmt. **Two progress guards** added to avoid infinite loops on unsupported syntax: * gp-block-body-loop force-advances one token if gp-parse-stmt returns nil without consuming. * gp-parse-composite-elems does the same when its expr parser returns nil — fixes a hang on '`if true {`x := 1`}`' where the parser was misreading `if true{...}` as a composite literal then spinning on `:=` inside the brace body. Existing func/method decl tests updated from the ':body' sentinel to the new (:block STMTS) shape. Old `gp-skip-block!` left as dead code (removed once control-flow stmts make the misinterpretation issue moot). Control-flow stmts (if/for/switch/select/defer/go/break/continue) and channel send (`ch <- v`) deferred to subsequent iterations. parse 141/141, total 270/270. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/go/parse.sx | 137 ++++++++++++++++++++++++++++++++++++----- lib/go/scoreboard.json | 6 +- lib/go/scoreboard.md | 4 +- lib/go/tests/parse.sx | 86 +++++++++++++++++++++++--- plans/go-on-sx.md | 26 ++++++-- 5 files changed, 225 insertions(+), 34 deletions(-) diff --git a/lib/go/parse.sx b/lib/go/parse.sx index 0d2eafb9..55e59c21 100644 --- a/lib/go/parse.sx +++ b/lib/go/parse.sx @@ -99,19 +99,22 @@ (gp-advance!) :else (do - (let ((first (gp-parse-expr 1))) - (cond - (and (= (gp-tok-type) "op") - (= (gp-tok-value) ":")) - (do - (gp-advance!) - (let ((val (gp-parse-expr 1))) - (append! elems (list :kv first val)))) - :else - (append! elems first))) - (when (and (= (gp-tok-type) "op") - (= (gp-tok-value) ",")) - (gp-advance!)) + (let ((saved-idx gp-idx)) + (let ((first (gp-parse-expr 1))) + (cond + (and (= (gp-tok-type) "op") + (= (gp-tok-value) ":")) + (do + (gp-advance!) + (let ((val (gp-parse-expr 1))) + (append! elems (list :kv first val)))) + :else + (when (not (= first nil)) + (append! elems first)))) + (when (and (= (gp-tok-type) "op") + (= (gp-tok-value) ",")) + (gp-advance!)) + (when (= gp-idx saved-idx) (gp-advance!))) (gp-comp-loop))))) (gp-comp-loop) elems))) @@ -713,14 +716,116 @@ (when (and (= (gp-tok-type) "op") (= (gp-tok-value) "{")) (gp-advance!) - (gp-skip-block!) - (set! body :body)) + (set! body (gp-parse-block-body))) (cond (= recv nil) (list :func-decl name params results body) :else (list :method-decl recv name params results body)))))) :else nil)))) + (define + gp-stmt-assign-ops + ;; Compound assignment operators per Go spec § Assignment operations. + (list "+=" "-=" "*=" "/=" "%=" "&=" "|=" "^=" + "<<=" ">>=" "&^=")) + (define + gp-parse-stmt + ;; Parses one Go statement. Recognises: + ;; return [exprs] + ;; { ... } — nested block + ;; lhs := exprs — short declaration + ;; lhs = exprs — assignment + ;; lhs OP= expr — compound assignment + ;; expr — expression statement + ;; LHS may be a comma-separated list. Block-level declarations + ;; (var/const/type/func) route through gp-parse-decl. + (fn + () + (cond + (= (gp-tok-type) "semi") + (do (gp-advance!) (gp-parse-stmt)) + (and (= (gp-tok-type) "keyword") + (or (= (gp-tok-value) "var") (= (gp-tok-value) "const") + (= (gp-tok-value) "type") (= (gp-tok-value) "func"))) + (gp-parse-decl) + (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "return")) + (do + (gp-advance!) + (cond + (or (= (gp-tok-type) "semi") (= (gp-tok-type) "eof") + (and (= (gp-tok-type) "op") (= (gp-tok-value) "}"))) + (list :return (list)) + :else (list :return (gp-parse-expr-list)))) + (and (= (gp-tok-type) "op") (= (gp-tok-value) "{")) + (do (gp-advance!) (gp-parse-block-body)) + :else + (let ((lhs (gp-parse-expr 1))) + (cond + (= lhs nil) nil + :else + (let ((lhs-list (list lhs))) + (define + gp-stmt-lhs-rest + (fn + () + (when (and (= (gp-tok-type) "op") + (= (gp-tok-value) ",")) + (gp-advance!) + (let ((e (gp-parse-expr 1))) + (when (not (= e nil)) (append! lhs-list e))) + (gp-stmt-lhs-rest)))) + (gp-stmt-lhs-rest) + (cond + (and (= (gp-tok-type) "op") (= (gp-tok-value) ":=")) + (do (gp-advance!) + (list :short-decl lhs-list (gp-parse-expr-list))) + (and (= (gp-tok-type) "op") (= (gp-tok-value) "=")) + (do (gp-advance!) + (list :assign lhs-list (gp-parse-expr-list))) + (and (= (gp-tok-type) "op") + (some (fn (o) (= o (gp-tok-value))) + gp-stmt-assign-ops)) + (let ((op (gp-tok-value))) + (gp-advance!) + (list :assign-op op lhs-list + (list (gp-parse-expr 1)))) + :else + ;; Plain expression statement — return the single expr. + ;; (If somehow there was a comma chain without =/:=, just + ;; return the first expr; permissive.) + (cond + (= (len lhs-list) 1) lhs + :else lhs)))))))) + (define + gp-parse-block-body + ;; Caller has consumed '{'. Parses statements (and possibly nested + ;; declarations) until '}'. Returns (list :block STMTS). + (fn + () + (let ((stmts (list))) + (define + gp-block-body-loop + (fn + () + (cond + (= (gp-tok-type) "eof") nil + (and (= (gp-tok-type) "op") (= (gp-tok-value) "}")) + (gp-advance!) + (= (gp-tok-type) "semi") + (do (gp-advance!) (gp-block-body-loop)) + :else + (do + ;; Progress guard: if gp-parse-stmt returns nil without + ;; advancing, force one token forward to avoid spinning + ;; on unsupported syntax (e.g., 'if' before stmt parser + ;; learns it). Belt-and-braces against future bugs too. + (let ((saved-idx gp-idx)) + (let ((s (gp-parse-stmt))) + (when (not (= s nil)) (append! stmts s))) + (when (= gp-idx saved-idx) (gp-advance!))) + (gp-block-body-loop))))) + (gp-block-body-loop) + (list :block stmts)))) (define gp-parse-decl ;; Single declaration: package / import / var / const / type. @@ -773,5 +878,5 @@ (= (gp-tok-value) "type") (= (gp-tok-value) "func"))) (gp-parse-decl) - :else (gp-parse-expr 1)))) + :else (gp-parse-stmt)))) (gp-parse-top)))) diff --git a/lib/go/scoreboard.json b/lib/go/scoreboard.json index 7848829e..70e65aa6 100644 --- a/lib/go/scoreboard.json +++ b/lib/go/scoreboard.json @@ -1,10 +1,10 @@ { "language": "go", - "total_pass": 261, - "total": 261, + "total_pass": 270, + "total": 270, "suites": [ {"name":"lex","pass":129,"total":129,"status":"ok"}, - {"name":"parse","pass":132,"total":132,"status":"ok"}, + {"name":"parse","pass":141,"total":141,"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 2c3a025c..f4f99e5e 100644 --- a/lib/go/scoreboard.md +++ b/lib/go/scoreboard.md @@ -1,11 +1,11 @@ # Go-on-SX Scoreboard -**Total: 261 / 261 tests passing** +**Total: 270 / 270 tests passing** | | Suite | Pass | Total | |---|---|---|---| | ✅ | lex | 129 | 129 | -| ✅ | parse | 132 | 132 | +| ✅ | parse | 141 | 141 | | ⬜ | types | 0 | 0 | | ⬜ | eval | 0 | 0 | | ⬜ | runtime | 0 | 0 | diff --git a/lib/go/tests/parse.sx b/lib/go/tests/parse.sx index b41889b2..e314c71b 100644 --- a/lib/go/tests/parse.sx +++ b/lib/go/tests/parse.sx @@ -778,7 +778,7 @@ (go-parse-test "fdecl: func main() {}" (go-parse "func main() {}") - (list :func-decl "main" (list) (list) :body)) + (list :func-decl "main" (list) (list) (list :block (list)))) (go-parse-test "fdecl: func add(x, y int) int { return x + y }" @@ -787,7 +787,11 @@ :func-decl "add" (list (list :field (list "x" "y") (list :ty-name "int"))) (list (list :ty-name "int")) - :body)) + (list :block + (list + (list :return + (list + (ast-app (ast-var "+") (list (ast-var "x") (ast-var "y"))))))))) (go-parse-test "fdecl: func with multi-group params" @@ -798,7 +802,7 @@ (list :field (list "x") (list :ty-name "int")) (list :field (list "y") (list :ty-name "string"))) (list) - :body)) + (list :block (list)))) (go-parse-test "fdecl: func with multi-return" @@ -807,7 +811,7 @@ :func-decl "divmod" (list (list :field (list "a" "b") (list :ty-name "int"))) (list (list :ty-name "int") (list :ty-name "int")) - :body)) + (list :block (list)))) (go-parse-test "fdecl: func with no body (signature only)" @@ -826,7 +830,8 @@ "String" (list) (list (list :ty-name "string")) - :body)) + (list :block + (list (list :return (list (list :select (ast-var "p") "x"))))))) (go-parse-test "mdecl: method on value receiver" @@ -836,12 +841,75 @@ "Len" (list) (list (list :ty-name "int")) - :body)) + (list :block (list (list :return (list (ast-literal "0"))))))) (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)) + "fdecl: body with return" + (go-parse "func ret() { return 42 }") + (list :func-decl "ret" (list) (list) + (list :block (list (list :return (list (ast-literal "42"))))))) + +(go-parse-test + "stmt: short-decl x := 5" + (go-parse "x := 5") + (list :short-decl (list (ast-var "x")) (list (ast-literal "5")))) + +(go-parse-test + "stmt: short-decl multi a, b := 1, 2" + (go-parse "a, b := 1, 2") + (list + :short-decl (list (ast-var "a") (ast-var "b")) + (list (ast-literal "1") (ast-literal "2")))) + +(go-parse-test + "stmt: assign x = 5" + (go-parse "x = 5") + (list :assign (list (ast-var "x")) (list (ast-literal "5")))) + +(go-parse-test + "stmt: compound assign x += 1" + (go-parse "x += 1") + (list :assign-op "+=" (list (ast-var "x")) (list (ast-literal "1")))) + +(go-parse-test + "stmt: return (no value)" + (go-parse "return") + (list :return (list))) + +(go-parse-test + "stmt: return x + y" + (go-parse "return x + y") + (list + :return (list (ast-app (ast-var "+") (list (ast-var "x") (ast-var "y")))))) + +(go-parse-test + "stmt: return multi a, b" + (go-parse "return a, b") + (list :return (list (ast-var "a") (ast-var "b")))) + +(go-parse-test + "stmt: function body with multiple stmts" + (go-parse "func f() { x := 1; y := 2; return x + y }") + (list + :func-decl "f" + (list) + (list) + (list + :block (list + (list :short-decl (list (ast-var "x")) (list (ast-literal "1"))) + (list :short-decl (list (ast-var "y")) (list (ast-literal "2"))) + (list + :return (list + (ast-app (ast-var "+") (list (ast-var "x") (ast-var "y"))))))))) + +(go-parse-test + "stmt: expression statement (just a call)" + (go-parse "func g() { f(x) }") + (list + :func-decl "g" + (list) + (list) + (list :block (list (ast-app (ast-var "f") (list (ast-var "x"))))))) (go-parse-test "non-primary: '+'" (go-parse "+") nil) diff --git a/plans/go-on-sx.md b/plans/go-on-sx.md index 4f3d7a65..96fad074 100644 --- a/plans/go-on-sx.md +++ b/plans/go-on-sx.md @@ -196,13 +196,17 @@ Progress-log line → push `origin/loops/go`. 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 + - type), `select`, `return`, `defer`, `go`, `break`/`continue`, - assign, short-decl `:=`, send `ch <- v`, recv `<-ch`. +- [/] Statements: `return`, short-decl `:=`, assign `=`, compound assign + (`+=` etc.), expression stmt, block `{...}` all done. `gp-parse-stmt` + replaces the func-body `:body` stub with real `(:block STMTS)`. + Progress guards added to block-body and composite-elems loops. + `if`/`for`/`switch`/`select`/`defer`/`go`/`break`/`continue`/send + deferred to next slice. - [ ] End-to-end: hello-world, fibonacci, FizzBuzz, goroutine ping-pong, struct + method. - **Acceptance:** parse/ suite at 80+ tests. **Acceptance bar crossed: - 132/132.** Remaining sub-items (stmts, e2e) keep Phase 2 open ⬜. + 141/141.** Remaining sub-items (control-flow 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- @@ -519,6 +523,20 @@ 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.: statements. First slice covers + `return [exprs]`, short-decl `lhs := exprs`, assignment `lhs = exprs`, + compound assignment (`+= -= *= /= %= &= |= ^= <<= >>= &^=`), bare + expression statements, and nested blocks `{ ... }`. New `gp-parse-stmt` + dispatches on the leading token; `gp-parse-block-body` replaces the + func-decl `:body` sentinel with real `(:block STMTS)`. Existing + func/method tests updated to the new body shape. **Progress guards** + added to `gp-block-body-loop` and `gp-parse-composite-elems` — + unsupported syntax (`if`, `for`, etc.) now advances one token instead + of spinning. `gp-skip-block!` left as dead code; will be deleted once + control-flow stmts land. +9 tests, parse 141/141, total 270/270. + `[nothing]` — pure Go parser work; the cross-language statement + shapes will become a chiselling target once a second statically-typed + guest hits them. - 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,