Parser & AST — Specification¶
A reference for the grammar, the node hierarchy, the parser API surface, parse modes, and position semantics. Names follow the standard library (go/parser, go/ast, go/token). This is a lookup document — skim the tables, then jump to the section you need.
1. The Go grammar (EBNF excerpts)¶
The language is defined by a context-free grammar in the spec, written in EBNF: | alternation, [] optional, {} zero-or-more, () grouping. Selected productions:
SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" } .
TopLevelDecl = Declaration | FunctionDecl | MethodDecl .
Declaration = ConstDecl | TypeDecl | VarDecl .
FunctionDecl = "func" FunctionName Signature [ FunctionBody ] .
MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] .
Block = "{" StatementList "}" .
Statement = Declaration | LabeledStmt | SimpleStmt | ReturnStmt |
IfStmt | SwitchStmt | ForStmt | ... .
IfStmt = "if" [ SimpleStmt ";" ] Expression Block
[ "else" ( IfStmt | Block ) ] .
Expression = UnaryExpr | Expression binary_op Expression .
UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
PrimaryExpr = Operand | PrimaryExpr Selector | PrimaryExpr Index |
PrimaryExpr Arguments | ... .
Operator precedence (binary), highest binds tightest:
| Precedence | Operators |
|---|---|
| 5 | * / % << >> & &^ |
| 4 | + - | ^ |
| 3 | == != < <= > >= |
| 2 | && |
| 1 | || |
Unary operators (+ - ! ^ * & <-) bind tighter than any binary operator. All binary operators are left-associative.
2. Node interface hierarchy¶
Every node satisfies ast.Node:
type Node interface {
Pos() token.Pos // position of first character of the node
End() token.Pos // position of first character immediately after the node
}
Three sub-interfaces partition most nodes (each adds an unexported marker method so the sets stay disjoint):
type Expr interface { Node; exprNode() } // produces a value
type Stmt interface { Node; stmtNode() } // performs an action
type Decl interface { Node; declNode() } // declares names
Expressions (ast.Expr)¶
| Node | Meaning |
|---|---|
*ast.BadExpr | placeholder for a parse error |
*ast.Ident | identifier (Name string, Obj *Object) |
*ast.Ellipsis | ...T in params / [...]T arrays |
*ast.BasicLit | literal: Kind token.Token, Value string |
*ast.FuncLit | func(...){...} value |
*ast.CompositeLit | T{...} |
*ast.ParenExpr | ( X ) |
*ast.SelectorExpr | X.Sel |
*ast.IndexExpr | X[Index] |
*ast.IndexListExpr | X[A, B] (generics, Go 1.18+) |
*ast.SliceExpr | X[lo:hi:max] |
*ast.TypeAssertExpr | X.(T) |
*ast.CallExpr | Fun(Args...) |
*ast.StarExpr | *X (deref and pointer type) |
*ast.UnaryExpr | op X |
*ast.BinaryExpr | X op Y |
*ast.KeyValueExpr | Key: Value in composite lits |
Type-expression nodes (used where a type is written): *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType.
Statements (ast.Stmt)¶
*ast.BadStmt, *ast.DeclStmt, *ast.EmptyStmt, *ast.LabeledStmt, *ast.ExprStmt, *ast.SendStmt, *ast.IncDecStmt, *ast.AssignStmt, *ast.GoStmt, *ast.DeferStmt, *ast.ReturnStmt, *ast.BranchStmt (break/continue/goto/fallthrough), *ast.BlockStmt, *ast.IfStmt, *ast.CaseClause, *ast.SwitchStmt, *ast.TypeSwitchStmt, *ast.CommClause, *ast.SelectStmt, *ast.ForStmt, *ast.RangeStmt.
Declarations (ast.Decl)¶
| Node | Meaning |
|---|---|
*ast.BadDecl | parse-error placeholder |
*ast.GenDecl | import / const / type / var (holds Specs []Spec) |
*ast.FuncDecl | function or method (Recv non-nil for methods) |
Specs: *ast.ImportSpec, *ast.ValueSpec, *ast.TypeSpec. Support nodes: *ast.Field, *ast.FieldList, *ast.Comment, *ast.CommentGroup, *ast.File, *ast.Package.
Selected node fields¶
The fields you reach for most often:
type File struct {
Doc *CommentGroup // package doc, or nil
Name *Ident // package name
Decls []Decl // top-level declarations
Imports []*ImportSpec // all imports
Comments []*CommentGroup // all comments (with ParseComments)
}
type FuncDecl struct {
Doc *CommentGroup
Recv *FieldList // receiver (nil → function, non-nil → method)
Name *Ident
Type *FuncType // signature: params and results
Body *BlockStmt // nil for forward/external decls
}
type CallExpr struct {
Fun Expr // function expression
Lparen token.Pos
Args []Expr // arguments
Ellipsis token.Pos // position of "..." (or NoPos)
Rparen token.Pos
}
type BasicLit struct {
ValuePos token.Pos
Kind token.Token // INT, FLOAT, IMAG, CHAR, STRING
Value string // RAW source text, e.g. "42" or `"hi"`
}
Note BasicLit.Value is the literal source text (quotes and all), not the decoded value.
3. Parser API surface (go/parser)¶
func ParseFile(fset *token.FileSet, filename string, src any, mode Mode) (*ast.File, error)
func ParseDir(fset *token.FileSet, path string, filter func(fs.FileInfo) bool, mode Mode) (map[string]*ast.Package, error)
func ParseExpr(x string) (ast.Expr, error)
func ParseExprFrom(fset *token.FileSet, filename string, src any, mode Mode) (ast.Expr, error)
srcmay bestring,[]byte,io.Reader, ornil(read file from disk).ParseFilereturns a partial*ast.Fileeven on error whenmodepermits, so tools can work on broken code.ParseExprparses a standalone expression (no package context) — convenient for templates.
4. Parse modes (parser.Mode)¶
A bitset passed as the mode argument:
| Mode | Effect |
|---|---|
parser.PackageClauseOnly | stop after the package clause |
parser.ImportsOnly | stop after the import declarations |
parser.ParseComments | retain comments in *ast.File.Comments and .Doc/.Comment |
parser.Trace | print a parse trace to stdout (debugging) |
parser.DeclarationErrors | report declaration errors |
parser.SpuriousErrors | (deprecated) report extra errors |
parser.SkipObjectResolution | do not build *ast.Object resolution (Ident.Obj stays nil) — faster, less memory |
parser.AllErrors | report all errors, not just the first 10 |
Combine with |, e.g. parser.ParseComments | parser.SkipObjectResolution.
Object resolution (Ident.Obj, File.Scope, File.Unresolved) is a legacy, file-local name-binding pass — it is not real type checking and is widely skipped in favour of go/types.
Typical combinations:
// Tooling that does its own type checking:
parser.SkipObjectResolution
// Comment-preserving codemod:
parser.ParseComments | parser.SkipObjectResolution
// Fast import-graph scan:
parser.ImportsOnly | parser.SkipObjectResolution
// Maximum diagnostics:
parser.AllErrors | parser.ParseComments
5. Position semantics (go/token)¶
type Pos int // opaque offset into a FileSet; 0 == token.NoPos
type Position struct { // resolved, human-readable
Filename string
Offset int // 0-based byte offset
Line int // 1-based
Column int // 1-based, in bytes
}
Resolution and registry:
fset := token.NewFileSet()
f := fset.AddFile(name, -1, size) // usually done by the parser
pos := fset.Position(node.Pos()) // Pos -> Position
Rules:
Pos()returns the first byte;End()returns the byte after the last — so a node spans[Pos(), End()).token.NoPos(zero) means "no position"; common on synthesised nodes.Position{}.IsValid()is false whenLine == 0.
Common position-bearing sub-token fields (useful for precise edits):
| Node | Sub-position field(s) |
|---|---|
*ast.CallExpr | Lparen, Ellipsis, Rparen |
*ast.BasicLit | ValuePos |
*ast.BinaryExpr | OpPos |
*ast.BlockStmt | Lbrace, Rbrace |
*ast.GenDecl | TokPos, Lparen, Rparen |
*ast.ReturnStmt | Return |
- Positions are only meaningful relative to the FileSet that produced them. Sharing one FileSet across all parsed files gives every node a globally unique Pos. | |
- A BasicLit's ValuePos, a CallExpr's Lparen/Rparen, etc., let you locate sub-tokens precisely (used by printers and refactoring tools). |
6. Quick API recall¶
| Want | Use |
|---|---|
| parse a file | parser.ParseFile |
| parse one expression | parser.ParseExpr |
| keep comments | mode parser.ParseComments |
| skip name binding (faster) | mode parser.SkipObjectResolution |
| walk every node | ast.Inspect / ast.Walk |
| dump the tree | ast.Print / ast.Fprint |
resolve a Pos | fset.Position(pos) |
| associate comments | ast.NewCommentMap |
| re-emit source | format.Node / printer.Fprint |
6b. Worked grammar→node example¶
The statement if n > 0 { return n } maps onto nodes as:
*ast.IfStmt
├── Init: nil
├── Cond: *ast.BinaryExpr { X: Ident "n", Op: GTR (>), Y: BasicLit "0" }
├── Body: *ast.BlockStmt
│ └── List[0]: *ast.ReturnStmt
│ └── Results[0]: *ast.Ident "n"
└── Else: nil
Each field corresponds directly to a slot in the IfStmt grammar production: Init is the optional SimpleStmt, Cond the Expression, Body the Block, Else the optional else branch.
6c. Walking and printing API¶
// go/ast — traversal & dumping
func Inspect(node Node, f func(Node) bool)
func Walk(v Visitor, node Node) // Visitor.Visit(Node) Visitor
func Print(fset *token.FileSet, x any) error
func Fprint(w io.Writer, fset *token.FileSet, x any, f FieldFilter) error
func NewCommentMap(fset *token.FileSet, node Node, comments []*CommentGroup) CommentMap
// go/printer — render AST back to source
func Fprint(output io.Writer, fset *token.FileSet, node any) error
type Config struct { Mode Mode; Tabwidth int; Indent int }
// go/format — gofmt-formatted rendering (wraps printer)
func Node(dst io.Writer, fset *token.FileSet, node any) error
func Source(src []byte) ([]byte, error)
printer.Config lets you tune output (e.g. printer.UseSpaces, printer.TabIndent); format.Node is the canonical, gofmt-equivalent choice for tools.
7. Summary¶
Go's grammar is EBNF with five binary precedence levels (all left-associative) and tighter-binding unary operators. The AST is rooted at *ast.File; every node implements ast.Node (with Pos/End), and most also implement ast.Expr, ast.Stmt, or ast.Decl, kept disjoint by marker methods. The parser API centres on ParseFile/ParseExpr, tuned by a parser.Mode bitset (ParseComments, SkipObjectResolution, AllErrors, ...). Positions are opaque token.Pos offsets resolved to token.Position through a FileSet; a node spans [Pos(), End()), and NoPos marks the absence of a position.
Further reading¶
- Go spec (full grammar): https://go.dev/ref/spec
go/astnode reference: https://pkg.go.dev/go/astgo/parser(modes, ParseFile): https://pkg.go.dev/go/parsergo/token(Pos, Position, FileSet): https://pkg.go.dev/go/tokenparser.Mode: https://pkg.go.dev/go/parser#Mode