;; 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 "int") (= ty "float") (= ty "imag") (= ty "string") (= ty "rune")) (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-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!) (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))) (cond (= recv nil) (list :func-decl name params results body) :else (list :method-decl recv name params results body)))))) :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))))