go/parser 是生成 go ast 的唯一可靠路径,应使用 parsefile 解析完整文件、parseexpr 解析表达式,并始终传入 *token.fileset;自定义节点须实现 ast.node 接口,修改 ast 优先用 ast.inspect。

Go 标准库的 go/parser 是生成 AST 的唯一可靠路径,别自己手写 parser —— 语法复杂度、运算符优先级、泛型、嵌套结构、错误恢复等全得重造轮子,不现实。
用 parser.ParseFile 解析源文件得到 *ast.File
这是最常用、最稳定的入口。它内部已集成 go/scanner 做词法分析,自动处理注释、行号、位置信息(token.Position),返回完整 AST 根节点。
- 必须传入
*token.FileSet:所有位置信息都依赖它,漏掉会导致ast.Print输出乱码或 panic - 第二个参数是文件名(可为空字符串),但影响错误提示的路径显示;若解析内存字符串,建议设为
"<input>" - 第四个参数是
mode,常用parser.AllErrors收集全部错误,而非遇到第一个就停 - 注意:
src必须是 UTF-8 编码的[]byte,不是string直接传 —— 虽然 Go 会隐式转换,但显式转更安全:[]byte(src)
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "<input>", src, parser.AllErrors)
if err != nil {
// err 是 *parser.ErrorList 类型,可遍历打印所有错误
}
解析字符串表达式需绕过 ParseFile,改用 parser.ParseExpr
ParseFile 强制要求完整 Go 文件结构(package 声明 + 至少一个声明),无法直接喂 "1 + 2 * 3" 或 "len(s)"。这时候要用 ParseExpr,但它只接受「表达式」语法,且返回 ast.Expr 而非完整文件树。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
-
ParseExpr不校验上下文:比如"nil"、"true"可以过,但"x"(未声明变量)不会报错 —— 它只做语法层面检查 - 若需解析语句(如
"x := 1"),得用parser.ParseStmt;解析类型(如"[]int")用parser.ParseType - 所有这些函数仍依赖
*token.FileSet,且输入仍是[]byte - 错误类型统一为
parser.ErrorList,但单个表达式出错时通常只有 1 条
expr, err := parser.ParseExpr("1 + 2 * 3")
// 注意:这行会 panic!因为没传 *token.FileSet
// 正确写法:
fset := token.NewFileSet()
expr, err := parser.ParseExprFrom(fset, "", "1 + 2 * 3", 0)
自定义 AST 节点必须实现 ast.Node 接口才能被遍历
如果你在写 DSL 或中间表示(IR),想把业务逻辑塞进 AST,不能随便定义 struct 就完事。go/ast 的遍历器(如 ast.Inspect、ast.Walk)只认实现了 ast.Node 接口的类型:
- 接口定义极简:
func Pos() token.Pos和func End() token.Pos—— 你得提供有效位置,哪怕硬编码token.NoPos - 若节点不含位置信息(如纯计算节点),
Pos()返回token.NoPos即可,但End()必须 ≥Pos(),否则ast.Print会 panic - 标准库节点(如
ast.BinaryExpr)都已实现;你继承或组合它们时,别覆盖掉这两个方法 - 别试图把自定义节点混进标准 AST(如替换
ast.CallExpr.Fun字段为你的 struct)——ast.Walk会跳过未实现接口的值
ast.Inspect 比 ast.Walk 更适合修改节点
两者都深度优先遍历,但行为关键差异在于:当你在回调中返回 false 时,Inspect 会跳过当前节点的子树,而 Walk 的 Visit 方法无此能力 —— 它只能进入或退出,不能中断。
- 修改节点字段(如重写
Ident.Name)必须用Inspect:它的回调函数签名是func(n ast.Node) bool,返回false表示“别往下走了”,返回true才继续遍历子节点 -
Walk更适合只读分析(如统计函数调用次数),因为它的Visit方法没有返回值控制权 - 注意:
Inspect回调里修改节点指针本身(如n = &ast.Ident{...})无效 —— 这只是改了局部变量,原 AST 结构没变;必须改父节点的对应字段(如stmt.(*ast.AssignStmt).Lhs[0]) - 遍历时别对切片做
append或delete,可能引发并发 panic 或逻辑错乱;先收集待删节点,遍历完再批量处理
真正难的从来不是生成 AST,而是后续怎么安全地改它 —— 位置信息错一位,go fmt 就可能把代码格式化到天上去;子节点指针被意外置空,ast.Print 直接 panic。动手前先跑通 ast.Print 看原始结构,比对着文档猜字段名靠谱得多。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










