;; lib/go/parse.sx — Go parser. Tokenises via go-tokenize (lib/go/lex.sx),
;; builds canonical AST nodes per lib/guest/ast.sx, and uses
;; pratt-op-lookup from lib/guest/pratt.sx for operator-precedence climbing.
;;
;; Slices so far:
;;   1. Primary expressions — literal / identifier → ast-literal / ast-var
;;   2. Binary operators    — Pratt precedence climbing against
;;                            go-precedence-table; binary application
;;                            emitted as (ast-app (ast-var OP) [LHS RHS]).
;;
;; The climbing loop is per-language (see lib/guest/pratt.sx header on why)
;; but the entry shape and lookup are shared.
;;
;; All scanner locals are gp- prefixed: SX host primitives silently shadow
;; guest-language defines.

(define
  go-precedence-table
  (list
    (list "*" 5 :left)
    (list "/" 5 :left)
    (list "%" 5 :left)
    (list "<<" 5 :left)
    (list ">>" 5 :left)
    (list "&" 5 :left)
    (list "&^" 5 :left)
    (list "+" 4 :left)
    (list "-" 4 :left)
    (list "|" 4 :left)
    (list "^" 4 :left)
    (list "==" 3 :left)
    (list "!=" 3 :left)
    (list "<" 3 :left)
    (list "<=" 3 :left)
    (list ">" 3 :left)
    (list ">=" 3 :left)
    (list "&&" 2 :left)
    (list "||" 1 :left)))

(define
  go-parse
  (fn
    (src)
    (let
      ((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)))
      (define gp-tok-value (fn () (get (gp-cur) :value)))
      (define
        gp-parse-primary
        (fn
          ()
          (let
            ((ty (gp-tok-type)) (v (gp-tok-value)))
            (cond
              (or (= ty "string") (= ty "rune"))
              ;; Quoted/rune literals: tag distinctly so eval/types
              ;; can dispatch without re-classifying by first-char
              ;; (which conflates `42` and `"42"`). See lib/go/types.sx
              ;; `go-classify-literal-string` for the heuristic this
              ;; tag obviates.
              (do (gp-advance!) (list :literal-string v))
              (or
                (= ty "int")
                (= ty "float")
                (= ty "imag"))
              (do (gp-advance!) (ast-literal v))
              (= ty "ident")
              (do (gp-advance!) (ast-var v))
              ;; Type-prefixed composite literal starters: [, map, struct.
              ;; We parse a full type, then if '{' follows it's a composite
              ;; literal; otherwise the type is the operand (the caller
              ;; decides what to do — currently statement parsing isn't here).
              (or (and (= ty "op") (= v "["))
                  (and (= ty "keyword")
                       (or (= v "map") (= v "struct"))))
              (let ((tytree (gp-parse-type)))
                (cond
                  (and (= (gp-tok-type) "op") (= (gp-tok-value) "{"))
                  (do
                    (gp-advance!)
                    (list :composite tytree (gp-parse-composite-elems)))
                  :else tytree))
              :else nil))))
      (define
        gp-parse-composite-elems
        ;; Caller has consumed '{'. Parses elements until '}'.
        ;; Each element: either an expression, or KEY ':' VALUE.
        ;; KEY can be an ident (struct field name) or an expression
        ;; (map key) — parser is permissive, types phase disambiguates.
        ;; Returns a list of expression nodes or (list :kv KEY VALUE).
        (fn
          ()
          (let ((elems (list)))
            (define
              gp-comp-loop
              (fn
                ()
                (cond
                  (= (gp-tok-type) "semi")
                  (do (gp-advance!) (gp-comp-loop))
                  (and (= (gp-tok-type) "op") (= (gp-tok-value) "}"))
                  (gp-advance!)
                  :else
                  (do
                    (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)))
      (define
        gp-parse-call-args
        ;; Parse comma-separated args inside (...). Caller has already
        ;; consumed the opening "(". Consumes the closing ")".
        ;; Returns a list of argument AST nodes.
        (fn
          ()
          (let ((args (list)))
            (cond
              (and (= (gp-tok-type) "op") (= (gp-tok-value) ")"))
              (do (gp-advance!) args)
              :else
              (do
                (let ((first (gp-parse-expr 1)))
                  (when (not (= first nil)) (append! args first)))
                (define
                  gp-args-rest
                  (fn
                    ()
                    (cond
                      (and (= (gp-tok-type) "op") (= (gp-tok-value) ","))
                      (do
                        (gp-advance!)
                        (let ((arg (gp-parse-expr 1)))
                          (when (not (= arg nil)) (append! args arg)))
                        (gp-args-rest))
                      (and (= (gp-tok-type) "op") (= (gp-tok-value) ")"))
                      (gp-advance!)
                      :else nil)))
                (gp-args-rest)
                args)))))
      (define
        gp-parse-interface-elems
        ;; Caller positioned BEFORE '{'. Parses elements until '}'.
        ;; Two element shapes:
        ;;   M(params) [results]   →  (list :method "M" PARAMS RESULTS)
        ;;   T  or  pkg.T          →  (list :embed TYPE)
        ;; Type sets (Go 1.18+: ~int | ~float64) deferred.
        (fn
          ()
          (when (and (= (gp-tok-type) "op") (= (gp-tok-value) "{"))
            (gp-advance!))
          (let ((elems (list)))
            (define
              gp-iface-loop
              (fn
                ()
                (cond
                  (= (gp-tok-type) "semi")
                  (do (gp-advance!) (gp-iface-loop))
                  (and (= (gp-tok-type) "op") (= (gp-tok-value) "}"))
                  (gp-advance!)
                  (= (gp-tok-type) "ident")
                  (do
                    (let ((name (gp-tok-value)))
                      (gp-advance!)
                      (cond
                        (and (= (gp-tok-type) "op")
                             (= (gp-tok-value) "("))
                        (let ((params (gp-parse-func-type-params)))
                          (let ((results (gp-parse-func-type-results)))
                            (append! elems
                              (list :method name params results))))
                        (and (= (gp-tok-type) "op")
                             (= (gp-tok-value) "."))
                        (do
                          (gp-advance!)
                          (cond
                            (= (gp-tok-type) "ident")
                            (let ((sel-name (gp-tok-value)))
                              (gp-advance!)
                              (append! elems
                                (list :embed
                                  (list :ty-sel name sel-name))))
                            :else
                            (append! elems
                              (list :embed (list :ty-name name)))))
                        :else
                        (append! elems (list :embed (list :ty-name name)))))
                    (gp-iface-loop))
                  :else nil)))
            (gp-iface-loop)
            elems)))
      (define
        gp-parse-struct-fields
        ;; Caller positioned BEFORE '{'. Parses fields until '}'.
        ;;   field := name [, name]* TYPE
        ;; Tolerates ASI-inserted semis between fields. Embedded fields
        ;; (anonymous type without preceding names) and field tags are
        ;; deferred. Returns a list of (list :field NAMES TYPE).
        (fn
          ()
          (when (and (= (gp-tok-type) "op") (= (gp-tok-value) "{"))
            (gp-advance!))
          (let ((fields (list)))
            (define
              gp-struct-loop
              (fn
                ()
                (cond
                  (= (gp-tok-type) "semi")
                  (do (gp-advance!) (gp-struct-loop))
                  (and (= (gp-tok-type) "op") (= (gp-tok-value) "}"))
                  (gp-advance!)
                  (= (gp-tok-type) "ident")
                  (do
                    (let ((names (list (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 (gp-parse-type)))
                        (append! fields (list :field names ty))))
                    (gp-struct-loop))
                  :else nil)))
            (gp-struct-loop)
            fields)))
      (define
        gp-parse-func-type-params
        ;; Anonymous-only func-type params: caller is positioned BEFORE
        ;; the opening "(". Returns a list of type AST nodes.
        ;; Named params (a int, b string) are deferred — they're needed
        ;; for func DECLARATIONS, not pure func-type expressions.
        (fn
          ()
          (let ((params (list)))
            (when (and (= (gp-tok-type) "op") (= (gp-tok-value) "("))
              (gp-advance!))
            (cond
              (and (= (gp-tok-type) "op") (= (gp-tok-value) ")"))
              (do (gp-advance!) params)
              :else
              (do
                (let ((first (gp-parse-type)))
                  (when (not (= first nil)) (append! params first)))
                (define
                  gp-params-rest
                  (fn
                    ()
                    (cond
                      (and (= (gp-tok-type) "op") (= (gp-tok-value) ","))
                      (do
                        (gp-advance!)
                        (let ((t (gp-parse-type)))
                          (when (not (= t nil)) (append! params t)))
                        (gp-params-rest))
                      (and (= (gp-tok-type) "op") (= (gp-tok-value) ")"))
                      (gp-advance!)
                      :else nil)))
                (gp-params-rest)
                params)))))
      (define
        gp-parse-func-type-results
        ;; Zero, one, or many return types. Caller is positioned after
        ;; the closing ')' of params.
        ;;   no return        — next token is not a type-starter
        ;;   single return    — bare type follows
        ;;   multi return     — '(' T, T, ... ')'
        (fn
          ()
          (cond
            (and (= (gp-tok-type) "op") (= (gp-tok-value) "("))
            (gp-parse-func-type-params)
            :else
            (let ((t (gp-parse-type)))
              (cond
                (= t nil) (list)
                :else (list t))))))
      (define
        gp-parse-type
        ;; Go type-expression parser. Covers:
        ;;   *T              →  (list :ty-ptr T)
        ;;   name            →  (list :ty-name "name")
        ;;   pkg.Name        →  (list :ty-sel "pkg" "Name")
        ;;   []T             →  (list :ty-slice T)
        ;;   [N]T            →  (list :ty-array N T)
        ;;   map[K]V         →  (list :ty-map K V)
        ;;   chan T          →  (list :ty-chan :both T)
        ;;   chan<- T        →  (list :ty-chan :send T)
        ;;   <-chan T        →  (list :ty-chan :recv T)
        ;; Struct, interface, func types are deferred to a later slice.
        (fn
          ()
          (cond
            (and (= (gp-tok-type) "op") (= (gp-tok-value) "*"))
            (do (gp-advance!) (list :ty-ptr (gp-parse-type)))
            (and (= (gp-tok-type) "op") (= (gp-tok-value) "["))
            (do
              (gp-advance!)
              (cond
                (and (= (gp-tok-type) "op") (= (gp-tok-value) "]"))
                (do (gp-advance!) (list :ty-slice (gp-parse-type)))
                :else
                (let ((sz (gp-parse-expr 1)))
                  (when (and (= (gp-tok-type) "op") (= (gp-tok-value) "]"))
                    (gp-advance!))
                  (list :ty-array sz (gp-parse-type)))))
            (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "map"))
            (do
              (gp-advance!)
              (when (and (= (gp-tok-type) "op") (= (gp-tok-value) "["))
                (gp-advance!))
              (let ((k (gp-parse-type)))
                (when (and (= (gp-tok-type) "op") (= (gp-tok-value) "]"))
                  (gp-advance!))
                (let ((v (gp-parse-type)))
                  (list :ty-map k v))))
            (and (= (gp-tok-type) "op") (= (gp-tok-value) "<-"))
            (do
              (gp-advance!)
              (when (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "chan"))
                (gp-advance!))
              (list :ty-chan :recv (gp-parse-type)))
            (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "chan"))
            (do
              (gp-advance!)
              (cond
                (and (= (gp-tok-type) "op") (= (gp-tok-value) "<-"))
                (do (gp-advance!) (list :ty-chan :send (gp-parse-type)))
                :else (list :ty-chan :both (gp-parse-type))))
            (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "func"))
            (do
              (gp-advance!)
              (let ((params (gp-parse-func-type-params)))
                (let ((results (gp-parse-func-type-results)))
                  (list :ty-func params results))))
            (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "struct"))
            (do
              (gp-advance!)
              (list :ty-struct (gp-parse-struct-fields)))
            (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "interface"))
            (do
              (gp-advance!)
              (list :ty-interface (gp-parse-interface-elems)))
            (= (gp-tok-type) "ident")
            (let ((name (gp-tok-value)))
              (gp-advance!)
              (cond
                (and (= (gp-tok-type) "op") (= (gp-tok-value) "."))
                (do
                  (gp-advance!)
                  (cond
                    (= (gp-tok-type) "ident")
                    (let ((sel-name (gp-tok-value)))
                      (gp-advance!)
                      (list :ty-sel name sel-name))
                    :else (list :ty-name name)))
                :else (list :ty-name name)))
            :else nil)))
      (define
        gp-parse-bracket-expr
        ;; Optional expression inside brackets — returns nil if next token
        ;; is ':' or ']' (the slice "omitted" cases).
        (fn
          ()
          (cond
            (and (= (gp-tok-type) "op")
                 (or (= (gp-tok-value) ":") (= (gp-tok-value) "]")))
            nil
            :else (gp-parse-expr 1))))
      (define
        gp-parse-bracket
        ;; Caller has consumed '['. Parses index or slice and ']'.
        ;;   x[i]         →  (list :index BASE i)
        ;;   x[a:b]       →  (list :slice BASE LOW HIGH nil)     (LOW/HIGH may be nil)
        ;;   x[a:b:c]     →  (list :slice BASE LOW HIGH MAX)
        ;; Returns the AST node based on BASE.
        (fn
          (base)
          (let ((low (gp-parse-bracket-expr)))
            (cond
              (and (= (gp-tok-type) "op") (= (gp-tok-value) "]"))
              (do (gp-advance!) (list :index base low))
              (and (= (gp-tok-type) "op") (= (gp-tok-value) ":"))
              (do
                (gp-advance!)
                (let ((high (gp-parse-bracket-expr)))
                  (cond
                    (and (= (gp-tok-type) "op") (= (gp-tok-value) "]"))
                    (do (gp-advance!) (list :slice base low high nil))
                    (and (= (gp-tok-type) "op") (= (gp-tok-value) ":"))
                    (do
                      (gp-advance!)
                      (let ((maxe (gp-parse-bracket-expr)))
                        (when (and (= (gp-tok-type) "op")
                                   (= (gp-tok-value) "]"))
                          (gp-advance!))
                        (list :slice base low high maxe)))
                    :else (list :slice base low high nil))))
              :else base))))
      (define
        gp-parse-postfix
        ;; Left-associative postfix loop on top of gp-parse-primary:
        ;;   x.field    →  (list :select x "field")     — Go-specific
        ;;   f(args...) →  (ast-app f args)             — canonical
        ;;   x[i]       →  (list :index x i)            — Go-specific
        ;;   x[a:b]     →  (list :slice x low high max) — Go-specific
        (fn
          ()
          (let ((base (gp-parse-primary)))
            (gp-postfix-loop base))))
      (define
        gp-postfix-loop
        (fn
          (base)
          (cond
            (= base nil) nil
            :else
            (let ((tok (gp-cur)))
              (cond
                (and (= (get tok :type) "op") (= (get tok :value) "."))
                (do
                  (gp-advance!)
                  (let ((next-tok (gp-cur)))
                    (cond
                      ;; .(T) — type assertion
                      (and (= (get next-tok :type) "op")
                           (= (get next-tok :value) "("))
                      (do
                        (gp-advance!)
                        (let ((ty (gp-parse-type)))
                          (when (and (= (gp-tok-type) "op")
                                     (= (gp-tok-value) ")"))
                            (gp-advance!))
                          (gp-postfix-loop (list :assert base ty))))
                      ;; .ident — selector / member access
                      (= (get next-tok :type) "ident")
                      (do
                        (gp-advance!)
                        (gp-postfix-loop
                          (list :select base (get next-tok :value))))
                      :else base)))
                (and (= (get tok :type) "op") (= (get tok :value) "("))
                (do
                  (gp-advance!)
                  (gp-postfix-loop (ast-app base (gp-parse-call-args))))
                (and (= (get tok :type) "op") (= (get tok :value) "["))
                (do
                  (gp-advance!)
                  (gp-postfix-loop (gp-parse-bracket base)))
                ;; 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.
                ;; 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
                    (list :composite base (gp-parse-composite-elems))))
                :else base)))))
      (define
        gp-unary-ops
        ;; Go spec § Operators: prefix unary, all higher precedence than
        ;; any binary operator. <- is the channel receive form (send is a
        ;; statement, not an expression, so never appears here as binary).
        (list "+" "-" "!" "^" "*" "&" "<-"))
      (define
        gp-parse-unary
        (fn
          ()
          (let ((tok (gp-cur)))
            (cond
              (and (= (get tok :type) "op")
                   (some (fn (u) (= u (get tok :value))) gp-unary-ops))
              (do
                (gp-advance!)
                (let ((operand (gp-parse-unary)))
                  (cond
                    (= operand nil) nil
                    :else (ast-app (ast-var (get tok :value)) (list operand)))))
              :else (gp-parse-postfix)))))
      (define
        gp-parse-expr
        (fn
          (min-prec)
          (let ((left (gp-parse-unary))) (gp-pratt-loop left min-prec))))
      (define
        gp-pratt-loop
        (fn
          (left min-prec)
          (cond
            (= left nil) nil
            :else
          (let
            ((tok (gp-cur)))
            (cond
              (not (= (get tok :type) "op"))
              left
              :else (let
                ((entry (pratt-op-lookup go-precedence-table (get tok :value))))
                (cond
                  (= entry nil)
                  left
                  (< (pratt-op-prec entry) min-prec)
                  left
                  :else (do
                    (gp-advance!)
                    (let
                      ((next-min (if (= (pratt-op-assoc entry) :left) (+ (pratt-op-prec entry) 1) (pratt-op-prec entry))))
                      (let
                        ((right (gp-parse-expr next-min)))
                        (gp-pratt-loop
                          (ast-app
                            (ast-var (get tok :value))
                            (list left right))
                          min-prec)))))))))))
      (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-param-group
        ;; Parses one parameter binding group inside a func decl param list.
        ;; Returns (list :field NAMES TYPE). Named-greedy: collects all
        ;; consecutive idents separated by commas, then a type. Fails for
        ;; mixed anonymous lists like func(int, string) — flagged in plan.
        (fn
          ()
          (cond
            (not (= (gp-tok-type) "ident"))
            (list :field (list) (gp-parse-type))
            :else
            (let ((names (list)) (candidate (gp-tok-value)))
              (gp-advance!)
              (define
                gp-dpg-loop
                (fn
                  ()
                  (when (and (= (gp-tok-type) "op") (= (gp-tok-value) ","))
                    (let ((saved-idx gp-idx))
                      (gp-advance!)
                      (cond
                        (= (gp-tok-type) "ident")
                        (do
                          (append! names candidate)
                          (set! candidate (gp-tok-value))
                          (gp-advance!)
                          (gp-dpg-loop))
                        :else
                        (set! gp-idx saved-idx))))))
              (gp-dpg-loop)
              (cond
                (and (= (gp-tok-type) "op")
                     (or (= (gp-tok-value) ")") (= (gp-tok-value) ",")))
                (list :field names (list :ty-name candidate))
                :else
                (do
                  (append! names candidate)
                  (list :field names (gp-parse-type))))))))
      (define
        gp-parse-func-decl-params
        ;; Func-decl parameter list — comma-separated binding groups.
        ;; Caller positioned BEFORE '('. Consumes ')'.
        (fn
          ()
          (when (and (= (gp-tok-type) "op") (= (gp-tok-value) "("))
            (gp-advance!))
          (let ((groups (list)))
            (cond
              (and (= (gp-tok-type) "op") (= (gp-tok-value) ")"))
              (do (gp-advance!) groups)
              :else
              (do
                (append! groups (gp-parse-decl-param-group))
                (define
                  gp-fdp-rest
                  (fn
                    ()
                    (cond
                      (and (= (gp-tok-type) "op") (= (gp-tok-value) ","))
                      (do
                        (gp-advance!)
                        (append! groups (gp-parse-decl-param-group))
                        (gp-fdp-rest))
                      (and (= (gp-tok-type) "op") (= (gp-tok-value) ")"))
                      (gp-advance!)
                      :else nil)))
                (gp-fdp-rest)
                groups)))))
      (define
        gp-skip-block!
        ;; Brace-balanced skip. Caller has consumed the opening '{'.
        ;; Statement parsing arrives in a later iteration; for now the
        ;; body is opaque and stored as the keyword :body in the AST.
        (fn
          ()
          (let ((depth 1))
            (define
              gp-block-loop
              (fn
                ()
                (cond
                  (= (gp-tok-type) "eof") nil
                  (and (= (gp-tok-type) "op") (= (gp-tok-value) "{"))
                  (do (set! depth (+ depth 1)) (gp-advance!) (gp-block-loop))
                  (and (= (gp-tok-type) "op") (= (gp-tok-value) "}"))
                  (do
                    (set! depth (- depth 1))
                    (gp-advance!)
                    (when (> depth 0) (gp-block-loop)))
                  :else (do (gp-advance!) (gp-block-loop)))))
            (gp-block-loop))))
      (define
        gp-parse-type-params
        ;; Optional [...] preceding a func/type decl's regular params.
        ;; Each group is `NAMES constraint-type` (re-uses the regular
        ;; param-group parser). Returns a list of (:field NAMES TY)
        ;; records, or nil if no `[` is present. Type-set constraints
        ;; (`T int | float64`) deferred.
        (fn ()
          (cond
            (not (and (= (gp-tok-type) "op") (= (gp-tok-value) "[")))
            nil
            :else
            (do
              (gp-advance!)
              (let ((groups (list)))
                (define
                  gp-tp-loop
                  (fn ()
                    (cond
                      (and (= (gp-tok-type) "op") (= (gp-tok-value) "]"))
                      (gp-advance!)
                      :else
                      (let ((group (gp-parse-decl-param-group)))
                        (cond
                          (= group nil)
                          (do (gp-advance!) (gp-tp-loop))
                          :else
                          (do
                            (append! groups group)
                            (cond
                              (and (= (gp-tok-type) "op")
                                   (= (gp-tok-value) ","))
                              (do (gp-advance!) (gp-tp-loop))
                              :else (gp-tp-loop))))))))
                (gp-tp-loop)
                groups)))))
      (define
        gp-parse-func-decl
        ;; Caller has consumed 'func'.
        ;;   func NAME (params) [results] { body }
        ;;   func (recv) NAME (params) [results] { body }   — method
        ;; AST:
        ;;   (list :func-decl NAME PARAMS RESULTS BODY)
        ;;   (list :method-decl RECV NAME PARAMS RESULTS BODY)
        ;; BODY is :body (opaque) if a block was present, else nil.
        (fn
          ()
          (let ((recv nil))
            (when (and (= (gp-tok-type) "op") (= (gp-tok-value) "("))
              (gp-advance!)
              (set! recv (gp-parse-decl-param-group))
              (when (and (= (gp-tok-type) "op") (= (gp-tok-value) ")"))
                (gp-advance!)))
            (cond
              (= (gp-tok-type) "ident")
              (let ((name (gp-tok-value)))
                (gp-advance!)
                ;; Type parameters: [T any] / [T, U any] / [T any, U comparable].
                ;; Same shape as a regular param group — (:field NAMES TY).
                ;; Type-set constraints (T int | float64) deferred.
                (let ((type-params (gp-parse-type-params)))
                  (let ((params (gp-parse-func-decl-params)))
                    (let ((results (gp-parse-func-type-results)))
                      (let ((body nil))
                        (when (and (= (gp-tok-type) "op")
                                   (= (gp-tok-value) "{"))
                          (gp-advance!)
                          (set! body (gp-parse-block-body)))
                        ;; Keep the legacy 5-slot shape when there are
                        ;; no type params so existing AST consumers
                        ;; (parse tests, types/eval pattern matchers)
                        ;; stay compatible. Only add the 6th slot when
                        ;; a `[...]` clause was actually present.
                        (cond
                          (and (= recv nil) (= type-params nil))
                          (list :func-decl name params results body)
                          (= recv nil)
                          (list :func-decl name params results body type-params)
                          (= type-params nil)
                          (list :method-decl recv name params results body)
                          :else
                          (list :method-decl recv name params results body type-params)))))))
              :else nil))))
      (define
        gp-parse-case-body
        ;; Stmts inside a switch/select case clause. Reads until the next
        ;; 'case'/'default'/'}'/eof without consuming those terminators.
        (fn
          ()
          (let ((stmts (list)))
            (define
              gp-cb-loop
              (fn
                ()
                (cond
                  (= (gp-tok-type) "eof") nil
                  (and (= (gp-tok-type) "op") (= (gp-tok-value) "}")) nil
                  (and (= (gp-tok-type) "keyword")
                       (or (= (gp-tok-value) "case")
                           (= (gp-tok-value) "default")))
                  nil
                  (= (gp-tok-type) "semi") (do (gp-advance!) (gp-cb-loop))
                  :else
                  (do
                    (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-cb-loop)))))
            (gp-cb-loop)
            stmts)))
      (define
        gp-parse-switch
        ;; Caller has consumed 'switch'. Two shapes:
        ;;   switch { ...cases... }       — tagless (each case is a bool)
        ;;   switch TAG { ...cases... }   — tagged (match against TAG)
        ;; AST: (list :switch TAG CASES) — TAG may be nil.
        ;; Each case: (list :case VALUES BODY) or (list :default BODY).
        ;; Type-switch (`switch v := x.(type)`) deferred to a follow-up.
        (fn
          ()
          (set! gp-no-comp-lit (+ gp-no-comp-lit 1))
          (let ((tag nil) (cases (list)))
            (when (not (and (= (gp-tok-type) "op") (= (gp-tok-value) "{")))
              (set! tag (gp-parse-expr 1)))
            (set! gp-no-comp-lit (- gp-no-comp-lit 1))
            (when (and (= (gp-tok-type) "op") (= (gp-tok-value) "{"))
              (gp-advance!))
            (define
              gp-sw-loop
              (fn
                ()
                (cond
                  (= (gp-tok-type) "semi")
                  (do (gp-advance!) (gp-sw-loop))
                  (and (= (gp-tok-type) "op") (= (gp-tok-value) "}"))
                  (gp-advance!)
                  (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "case"))
                  (do
                    (gp-advance!)
                    (let ((vals (gp-parse-expr-list)))
                      (when (and (= (gp-tok-type) "op")
                                 (= (gp-tok-value) ":"))
                        (gp-advance!))
                      (append! cases
                        (list :case vals (gp-parse-case-body))))
                    (gp-sw-loop))
                  (and (= (gp-tok-type) "keyword")
                       (= (gp-tok-value) "default"))
                  (do
                    (gp-advance!)
                    (when (and (= (gp-tok-type) "op")
                               (= (gp-tok-value) ":"))
                      (gp-advance!))
                    (append! cases
                      (list :default (gp-parse-case-body)))
                    (gp-sw-loop))
                  :else (do (gp-advance!) (gp-sw-loop)))))
            (gp-sw-loop)
            (list :switch tag cases))))
      (define
        gp-parse-select
        ;; Caller has consumed 'select'. Each case is a communication stmt
        ;; (send / recv) or a recv-assignment.
        ;; AST: (list :select CASES).
        ;; Each case: (list :select-case COMM-STMT BODY) or (list :default BODY).
        (fn
          ()
          (let ((cases (list)))
            (when (and (= (gp-tok-type) "op") (= (gp-tok-value) "{"))
              (gp-advance!))
            (define
              gp-sel-loop
              (fn
                ()
                (cond
                  (= (gp-tok-type) "semi")
                  (do (gp-advance!) (gp-sel-loop))
                  (and (= (gp-tok-type) "op") (= (gp-tok-value) "}"))
                  (gp-advance!)
                  (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "case"))
                  (do
                    (gp-advance!)
                    (let ((comm (gp-parse-stmt)))
                      (when (and (= (gp-tok-type) "op")
                                 (= (gp-tok-value) ":"))
                        (gp-advance!))
                      (append! cases
                        (list :select-case comm (gp-parse-case-body))))
                    (gp-sel-loop))
                  (and (= (gp-tok-type) "keyword")
                       (= (gp-tok-value) "default"))
                  (do
                    (gp-advance!)
                    (when (and (= (gp-tok-type) "op")
                               (= (gp-tok-value) ":"))
                      (gp-advance!))
                    (append! cases
                      (list :default (gp-parse-case-body)))
                    (gp-sel-loop))
                  :else (do (gp-advance!) (gp-sel-loop)))))
            (gp-sel-loop)
            (list :select cases))))
      (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-for-find-range
        ;; Scan tokens from current idx looking for the `range` keyword;
        ;; stops at '{' or eof. Restores idx before returning. Returns
        ;; true iff the for-header contains a range clause.
        (fn
          ()
          (let ((saved-idx gp-idx) (found false))
            (define
              gp-scan-rng
              (fn
                ()
                (cond
                  (or (= (gp-tok-type) "eof")
                      (and (= (gp-tok-type) "op")
                           (= (gp-tok-value) "{")))
                  nil
                  (and (= (gp-tok-type) "keyword")
                       (= (gp-tok-value) "range"))
                  (set! found true)
                  :else (do (gp-advance!) (gp-scan-rng)))))
            (gp-scan-rng)
            (set! gp-idx saved-idx)
            found)))
      (define
        gp-parse-for-range
        ;; Range form:
        ;;   for range COLL { ... }
        ;;   for k := range COLL { ... }
        ;;   for k, v := range COLL { ... }
        ;;   for k, v = range COLL { ... }     (reuse existing vars)
        ;; AST: (list :range-for DECL-KIND KEY VALUE COLL BODY)
        ;;   DECL-KIND  : :short-decl | :assign | nil (no kv)
        ;;   KEY/VALUE  : ast-var nodes or nil
        (fn
          ()
          (let ((decl-kind nil) (key nil) (value nil)
                (coll nil) (body nil))
            (cond
              (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "range"))
              nil
              :else
              (do
                (when (= (gp-tok-type) "ident")
                  (set! key (ast-var (gp-tok-value)))
                  (gp-advance!))
                (when (and (= (gp-tok-type) "op") (= (gp-tok-value) ","))
                  (gp-advance!)
                  (when (= (gp-tok-type) "ident")
                    (set! value (ast-var (gp-tok-value)))
                    (gp-advance!)))
                (cond
                  (and (= (gp-tok-type) "op") (= (gp-tok-value) ":="))
                  (do (gp-advance!) (set! decl-kind :short-decl))
                  (and (= (gp-tok-type) "op") (= (gp-tok-value) "="))
                  (do (gp-advance!) (set! decl-kind :assign)))))
            (when (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "range"))
              (gp-advance!))
            (set! coll (gp-parse-expr 1))
            (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 :range-for decl-kind key value coll body))))
      (define
        gp-parse-for-c-style
        ;; for COND { ... }   OR   for INIT; COND; POST { ... }
        ;; AST: (list :for INIT COND POST BODY).
        (fn
          ()
          (let ((init nil) (cnd nil) (post nil) (body nil))
            (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-parse-for
        ;; Caller has consumed 'for'. Dispatches on header shape.
        (fn
          ()
          (set! gp-no-comp-lit (+ gp-no-comp-lit 1))
          (cond
            (and (= (gp-tok-type) "op") (= (gp-tok-value) "{"))
            (do
              (set! gp-no-comp-lit (- gp-no-comp-lit 1))
              (gp-advance!)
              (list :for nil nil nil (gp-parse-block-body)))
            (gp-for-find-range) (gp-parse-for-range)
            :else (gp-parse-for-c-style))))
      (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) "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) "go"))
            (do (gp-advance!) (list :go (gp-parse-expr 1)))
            (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "defer"))
            (do (gp-advance!) (list :defer (gp-parse-expr 1)))
            (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) "keyword") (= (gp-tok-value) "switch"))
            (do (gp-advance!) (gp-parse-switch))
            (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "select"))
            (do (gp-advance!) (gp-parse-select))
            (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))))
                    (and (= (gp-tok-type) "op")
                         (or (= (gp-tok-value) "++")
                             (= (gp-tok-value) "--")))
                    (let ((op (gp-tok-value)))
                      (gp-advance!)
                      (list :inc-dec op lhs))
                    ;; Channel send statement: ch <- v (Go spec § Send
                    ;; statements). Only valid when LHS is a single expr.
                    (and (= (gp-tok-type) "op") (= (gp-tok-value) "<-")
                         (= (len lhs-list) 1))
                    (do
                      (gp-advance!)
                      (list :send lhs (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.
        ;; 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))
            (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "func"))
            (do (gp-advance!) (gp-parse-func-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-tok-value) "func")))
            (gp-parse-decl)
            :else (gp-parse-stmt))))
      (define
        gp-parse-all
        ;; Parse all top-level forms until eof. Returns:
        ;;   nil                 — empty input
        ;;   single form         — backward-compatible with single-stmt
        ;;                          /single-decl tests; ~169 of them.
        ;;   (list :file FORMS)  — multiple forms (canonical Go file shape)
        (fn
          ()
          (let ((forms (list)))
            (define
              gp-all-loop
              (fn
                ()
                (cond
                  (= (gp-tok-type) "eof") nil
                  (= (gp-tok-type) "semi")
                  (do (gp-advance!) (gp-all-loop))
                  :else
                  (do
                    (let ((saved-idx gp-idx))
                      (let ((d (gp-parse-top)))
                        (when (not (= d nil)) (append! forms d)))
                      (when (= gp-idx saved-idx) (gp-advance!)))
                    (gp-all-loop)))))
            (gp-all-loop)
            (cond
              (= (len forms) 0) nil
              (= (len forms) 1) (first forms)
              :else (list :file forms)))))
      (gp-parse-all))))
