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

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:
2026-05-27 20:17:40 +00:00
parent 5f6d62f45b
commit ba41f8a580
5 changed files with 186 additions and 14 deletions

View File

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