;; 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)) (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)) :else nil)))) (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-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 ((field-tok (gp-cur))) (cond (= (get field-tok :type) "ident") (do (gp-advance!) (gp-postfix-loop (list :select base (get field-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))) :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))))))))))) (gp-parse-expr 1))))