go: parse.sx — if/else, for, break/continue, inc-dec + 11 tests [nothing]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
Adds the most-used control-flow forms:
if COND { ... } [else { ... } | else if ...]
for { ... } — infinite
for COND { ... } — while-like
for INIT; COND; POST { ... } — C-style
break / continue — keyword stmts (no labels yet)
x++ / x-- — Go statement inc-dec
AST shapes:
(list :if COND THEN ELSE) — ELSE nil / :if / :block
(list :for INIT COND POST BODY) — any of INIT/COND/POST may be nil
(list :break LABEL) (list :continue LABEL)
(list :inc-dec OP EXPR) — OP is "++" / "--"
**Closes the parser-mode caveat** logged when composite literals
landed. `gp-no-comp-lit` is a re-entrant counter on the parser state;
control-flow constructs increment it before parsing their condition
and decrement after, suppressing the postfix `{` → composite-lit
interpretation so that `if Foo { ... }` correctly reads `{ ... }` as
the body, not as `Foo{}` composite literal. Verified by the test:
(go-parse "if Foo {}") → (:if (:var "Foo") (:block ()) nil)
gp-parse-control-cond is the single helper that bracket-wraps the
flag bump so future control-flow forms (switch, select, range) can't
forget to engage suppression.
switch / select / defer / go / for-range / channel-send still deferred.
parse 152/152, total 281/281.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -42,7 +42,7 @@
|
||||
(fn
|
||||
(src)
|
||||
(let
|
||||
((gp-tokens (go-tokenize src)) (gp-idx 0))
|
||||
((gp-tokens (go-tokenize src)) (gp-idx 0) (gp-no-comp-lit 0))
|
||||
(define gp-cur (fn () (nth gp-tokens gp-idx)))
|
||||
(define gp-advance! (fn () (set! gp-idx (+ gp-idx 1))))
|
||||
(define gp-tok-type (fn () (get (gp-cur) :type)))
|
||||
@@ -470,7 +470,12 @@
|
||||
;; Ident-prefixed composite literal: T{...}. The base is
|
||||
;; the AST expression for the type-name (an ast-var or a
|
||||
;; :select node); a later phase resolves it as a type.
|
||||
(and (= (get tok :type) "op") (= (get tok :value) "{"))
|
||||
;; SUPPRESSED inside control-flow conditions (if/for/switch)
|
||||
;; — Go spec: top-level composite literals must be parenthesised
|
||||
;; in those positions. gp-no-comp-lit acts as a re-entrant
|
||||
;; counter so nested constructs nest correctly.
|
||||
(and (= (get tok :type) "op") (= (get tok :value) "{")
|
||||
(= gp-no-comp-lit 0))
|
||||
(do
|
||||
(gp-advance!)
|
||||
(gp-postfix-loop
|
||||
@@ -723,6 +728,79 @@
|
||||
:else
|
||||
(list :method-decl recv name params results body))))))
|
||||
:else nil))))
|
||||
(define
|
||||
gp-parse-control-cond
|
||||
;; Parses an expression as a control-flow condition with the
|
||||
;; composite-literal '{' suppression engaged (Go spec: top-level
|
||||
;; composite literals require explicit parens in if/for/switch
|
||||
;; condition positions).
|
||||
(fn
|
||||
()
|
||||
(set! gp-no-comp-lit (+ gp-no-comp-lit 1))
|
||||
(let ((e (gp-parse-expr 1)))
|
||||
(set! gp-no-comp-lit (- gp-no-comp-lit 1))
|
||||
e)))
|
||||
(define
|
||||
gp-parse-if
|
||||
;; Caller has consumed 'if'.
|
||||
;; if COND { BODY }
|
||||
;; if COND { BODY } else { BODY }
|
||||
;; if COND { BODY } else if ... (chained, recursive ELSE)
|
||||
;; AST: (list :if COND THEN ELSE) where ELSE may be nil, a
|
||||
;; nested :if, or a :block.
|
||||
(fn
|
||||
()
|
||||
(let ((cnd (gp-parse-control-cond)) (then nil) (els nil))
|
||||
(when (and (= (gp-tok-type) "op") (= (gp-tok-value) "{"))
|
||||
(gp-advance!)
|
||||
(set! then (gp-parse-block-body)))
|
||||
;; Skip ASI semis between } and else
|
||||
(when (= (gp-tok-type) "semi") (gp-advance!))
|
||||
(when (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "else"))
|
||||
(gp-advance!)
|
||||
(cond
|
||||
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "if"))
|
||||
(do (gp-advance!) (set! els (gp-parse-if)))
|
||||
(and (= (gp-tok-type) "op") (= (gp-tok-value) "{"))
|
||||
(do (gp-advance!) (set! els (gp-parse-block-body)))))
|
||||
(list :if cnd then els))))
|
||||
(define
|
||||
gp-parse-for
|
||||
;; Caller has consumed 'for'.
|
||||
;; for { BODY } — infinite
|
||||
;; for COND { BODY } — while-like
|
||||
;; for INIT; COND; POST { BODY } — C-style
|
||||
;; for k, v := range coll { BODY } — deferred (range)
|
||||
;; AST: (list :for INIT COND POST BODY); any of INIT/COND/POST may be nil.
|
||||
(fn
|
||||
()
|
||||
(set! gp-no-comp-lit (+ gp-no-comp-lit 1))
|
||||
(let ((init nil) (cnd nil) (post nil) (body nil))
|
||||
(cond
|
||||
(and (= (gp-tok-type) "op") (= (gp-tok-value) "{"))
|
||||
nil
|
||||
:else
|
||||
(let ((first (gp-parse-stmt)))
|
||||
(cond
|
||||
(= (gp-tok-type) "semi")
|
||||
(do
|
||||
(set! init first)
|
||||
(gp-advance!)
|
||||
(when (not (and (= (gp-tok-type) "op")
|
||||
(= (gp-tok-value) ";")))
|
||||
(cond
|
||||
(= (gp-tok-type) "semi") nil
|
||||
:else (set! cnd (gp-parse-expr 1))))
|
||||
(when (= (gp-tok-type) "semi") (gp-advance!))
|
||||
(when (not (and (= (gp-tok-type) "op")
|
||||
(= (gp-tok-value) "{")))
|
||||
(set! post (gp-parse-stmt))))
|
||||
:else (set! cnd first))))
|
||||
(set! gp-no-comp-lit (- gp-no-comp-lit 1))
|
||||
(when (and (= (gp-tok-type) "op") (= (gp-tok-value) "{"))
|
||||
(gp-advance!)
|
||||
(set! body (gp-parse-block-body)))
|
||||
(list :for init cnd post body))))
|
||||
(define
|
||||
gp-stmt-assign-ops
|
||||
;; Compound assignment operators per Go spec § Assignment operations.
|
||||
@@ -756,6 +834,14 @@
|
||||
(and (= (gp-tok-type) "op") (= (gp-tok-value) "}")))
|
||||
(list :return (list))
|
||||
:else (list :return (gp-parse-expr-list))))
|
||||
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "break"))
|
||||
(do (gp-advance!) (list :break nil))
|
||||
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "continue"))
|
||||
(do (gp-advance!) (list :continue nil))
|
||||
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "if"))
|
||||
(do (gp-advance!) (gp-parse-if))
|
||||
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "for"))
|
||||
(do (gp-advance!) (gp-parse-for))
|
||||
(and (= (gp-tok-type) "op") (= (gp-tok-value) "{"))
|
||||
(do (gp-advance!) (gp-parse-block-body))
|
||||
:else
|
||||
@@ -789,6 +875,12 @@
|
||||
(gp-advance!)
|
||||
(list :assign-op op lhs-list
|
||||
(list (gp-parse-expr 1))))
|
||||
(and (= (gp-tok-type) "op")
|
||||
(or (= (gp-tok-value) "++")
|
||||
(= (gp-tok-value) "--")))
|
||||
(let ((op (gp-tok-value)))
|
||||
(gp-advance!)
|
||||
(list :inc-dec op lhs))
|
||||
:else
|
||||
;; Plain expression statement — return the single expr.
|
||||
;; (If somehow there was a comma chain without =/:=, just
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"language": "go",
|
||||
"total_pass": 270,
|
||||
"total": 270,
|
||||
"total_pass": 281,
|
||||
"total": 281,
|
||||
"suites": [
|
||||
{"name":"lex","pass":129,"total":129,"status":"ok"},
|
||||
{"name":"parse","pass":141,"total":141,"status":"ok"},
|
||||
{"name":"parse","pass":152,"total":152,"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"},
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Go-on-SX Scoreboard
|
||||
|
||||
**Total: 270 / 270 tests passing**
|
||||
**Total: 281 / 281 tests passing**
|
||||
|
||||
| | Suite | Pass | Total |
|
||||
|---|---|---|---|
|
||||
| ✅ | lex | 129 | 129 |
|
||||
| ✅ | parse | 141 | 141 |
|
||||
| ✅ | parse | 152 | 152 |
|
||||
| ⬜ | types | 0 | 0 |
|
||||
| ⬜ | eval | 0 | 0 |
|
||||
| ⬜ | runtime | 0 | 0 |
|
||||
|
||||
@@ -911,6 +911,72 @@
|
||||
(list)
|
||||
(list :block (list (ast-app (ast-var "f") (list (ast-var "x")))))))
|
||||
|
||||
(go-parse-test
|
||||
"if: if x { }"
|
||||
(go-parse "if x { }")
|
||||
(list :if (ast-var "x") (list :block (list)) nil))
|
||||
|
||||
(go-parse-test
|
||||
"if: if cond { body } else { body }"
|
||||
(go-parse "if x { y := 1 } else { z := 2 }")
|
||||
(list
|
||||
:if (ast-var "x")
|
||||
(list
|
||||
:block (list
|
||||
(list :short-decl (list (ast-var "y")) (list (ast-literal "1")))))
|
||||
(list
|
||||
:block (list
|
||||
(list :short-decl (list (ast-var "z")) (list (ast-literal "2")))))))
|
||||
|
||||
(go-parse-test
|
||||
"if: chained else-if"
|
||||
(go-parse "if a { } else if b { } else { }")
|
||||
(list
|
||||
:if (ast-var "a")
|
||||
(list :block (list))
|
||||
(list :if (ast-var "b") (list :block (list)) (list :block (list)))))
|
||||
|
||||
(go-parse-test
|
||||
"if: comparison condition"
|
||||
(go-parse "if x == 0 { return 0 }")
|
||||
(list
|
||||
:if (ast-app (ast-var "==") (list (ast-var "x") (ast-literal "0")))
|
||||
(list :block (list (list :return (list (ast-literal "0")))))
|
||||
nil))
|
||||
|
||||
(go-parse-test
|
||||
"for: infinite — for { }"
|
||||
(go-parse "for { }")
|
||||
(list :for nil nil nil (list :block (list))))
|
||||
|
||||
(go-parse-test
|
||||
"for: while-like — for cond { }"
|
||||
(go-parse "for x { }")
|
||||
(list :for nil (ast-var "x") nil (list :block (list))))
|
||||
|
||||
(go-parse-test
|
||||
"for: C-style — for i := 0; i < 10; i++ { }"
|
||||
(go-parse "for i := 0; i < 10; i++ { }")
|
||||
(list
|
||||
:for (list :short-decl (list (ast-var "i")) (list (ast-literal "0")))
|
||||
(ast-app (ast-var "<") (list (ast-var "i") (ast-literal "10")))
|
||||
(list :inc-dec "++" (ast-var "i"))
|
||||
(list :block (list))))
|
||||
|
||||
(go-parse-test "stmt: break" (go-parse "break") (list :break nil))
|
||||
|
||||
(go-parse-test "stmt: continue" (go-parse "continue") (list :continue nil))
|
||||
|
||||
(go-parse-test
|
||||
"stmt: x++ (inc-dec)"
|
||||
(go-parse "x++")
|
||||
(list :inc-dec "++" (ast-var "x")))
|
||||
|
||||
(go-parse-test
|
||||
"control: composite-lit suppression in if cond"
|
||||
(go-parse "if Foo {}")
|
||||
(list :if (ast-var "Foo") (list :block (list)) nil))
|
||||
|
||||
(go-parse-test "non-primary: '+'" (go-parse "+") nil)
|
||||
|
||||
(go-parse-test "non-primary: empty" (go-parse "") nil)
|
||||
|
||||
@@ -197,16 +197,18 @@ Progress-log line → push `origin/loops/go`.
|
||||
and variadic params deferred. Anonymous param-list disambiguation
|
||||
(`func(int, string)`) is a known parser-greedy limitation, flagged.
|
||||
- [/] 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.
|
||||
(`+=` etc.), expression stmt, block `{...}`, **if/else (with chained
|
||||
else-if), for (infinite / while-like / C-style), break, continue,
|
||||
inc-dec (`x++` / `x--`)** all done. **Composite-literal `{`
|
||||
suppression** active in control-flow conditions via
|
||||
`gp-no-comp-lit` counter — closes the parser-mode caveat flagged
|
||||
when composite literals landed. `switch`/`select`/`defer`/`go`/
|
||||
`for...range`/send `ch<-v` deferred.
|
||||
- [ ] End-to-end: hello-world, fibonacci, FizzBuzz, goroutine ping-pong,
|
||||
struct + method.
|
||||
- **Acceptance:** parse/ suite at 80+ tests. **Acceptance bar crossed:
|
||||
141/141.** Remaining sub-items (control-flow stmts, e2e) keep Phase 2
|
||||
open ⬜.
|
||||
152/152.** Remaining sub-items (switch/select/defer/go/range,
|
||||
end-to-end programs) keep Phase 2 open ⬜.
|
||||
|
||||
### Phase 3 — Bidirectional type checker, MVP (`lib/go/types.sx`) ⬜
|
||||
- **Independent implementation.** Do NOT use lib/guest/static-types-
|
||||
@@ -523,6 +525,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.: control-flow statements. `if cond { } [else
|
||||
{ }]` with chained else-if, `for { }` (infinite), `for cond { }`
|
||||
(while-like), `for init; cond; post { }` (C-style), `break`,
|
||||
`continue`, plus `x++` / `x--` inc-dec statements. **Closed the
|
||||
parser-mode caveat** flagged when composite literals landed:
|
||||
`gp-no-comp-lit` is a re-entrant counter that suppresses the postfix
|
||||
`{...}` → composite-lit interpretation inside control-flow condition
|
||||
positions, matching Go spec § Composite literals. `gp-parse-control-
|
||||
cond` wraps the increment/decrement so callers can't forget. +11
|
||||
tests, parse 152/152, total 281/281. `[nothing]` — pure Go parser
|
||||
shape work; the bidirectional-checker-relevant shapes (cond/body) are
|
||||
already covered by the earlier `:field` insight.
|
||||
- 2026-05-27 — Phase 2 cont.: statements. First slice covers
|
||||
`return [exprs]`, short-decl `lhs := exprs`, assignment `lhs = exprs`,
|
||||
compound assignment (`+= -= *= /= %= &= |= ^= <<= >>= &^=`), bare
|
||||
|
||||
Reference in New Issue
Block a user