Go无通用字符串到AST解析器,需自定义语法及lexer/parser,或用go/parser(仅限Go源码)及第三方库;strconv/json.Unmarshal仅支持类型转换,go/parser返回固定ast.Node不可扩展,手写parser最可控但需处理优先级与错误提示,peg库可声明式生成parser。

Go 本身不提供通用字符串到 AST 的自动解析器,你必须自己定义语法、写词法分析(lexer)和语法分析(parser),或借助 go/parser(仅限 Go 源码)或第三方库(如 gocc、peg)。直接用 strconv 或 json.Unmarshal 无法生成自定义 AST —— 它们只做类型转换或标准格式解析。
用 go/parser 解析 Go 代码字符串时,AST 节点类型固定,不能自定义
如果你只是想把一段合法 Go 代码转成 AST,go/parser 是最轻量的选择,但它返回的是 ast.Node 接口的官方节点(如 *ast.CallExpr、*ast.BinaryExpr),无法插入自定义字段或行为:
src := `fmt.Println("hello")`
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", src, 0)
// f.Syntax is *ast.File —— 结构由 go/ast 包约定,不可替换
- 适用场景:静态分析 Go 源码、提取函数调用、检查 import 等
- 不适用场景:解析 DSL(如配置表达式
"a + b * 2 > 10")、带业务语义的结构(如Rule{Condition: And{...}, Action: Notify{...}}) - 坑:传入非法 Go 语法会 panic 或返回
nil+ error;go/parser不支持自定义 token 或扩展语法
手写 lexer + parser 构建自定义 AST:从字符串到你的结构体
这是最常见也最可控的方式。核心是三步:定义 AST 结构体 → 写 tokenizer(切分 token)→ 写递归下降 parser(组合 token 成树)。例如解析简单算术表达式:
type BinaryExpr struct {
Op string
Left Expr
Right Expr
}
type NumberLit struct{ Value float64 }
type Expr interface{}
- lexer 示例:用
strings.FieldsFunc或正则切分"1 + 2 * 3"→[]string{"1", "+", "2", "*", "3"},再映射为token.Token{Type: token.NUMBER, Lit: "1", Val: 1.0} - parser 关键:优先级处理。先 parse low-precedence
+/-,内部调用 parse high-precedence*//,避免1+2*3错误解析为(1+2)*3 - 性能注意:避免反复
strings.Split或正则全量匹配;token 流应一次性遍历,用索引或迭代器推进 - 错误提示弱:手写 parser 默认只报“unexpected token”,需手动在每个 parse 函数里加
fmt.Errorf("at pos %d: expected number, got %s", p.pos, tok)
用 peg 库生成 parser:声明式语法 + 自动 AST 映射
peg(https://github.com/pointlander/peg)允许你用类似 EBNF 的语法描述规则,并通过注释标记 AST 构造逻辑,比手写更可靠:
立即学习“go语言免费学习笔记(深入)”;
// expr.peg
Expression <- Term (AddOp Term)*
Term <- Factor (MulOp Factor)*
Factor <- [0-9]+ { &NumberLit{Value: parseFloat(text)} }
AddOp <- "+" { "+" } / "-" { "-" }
MulOp <- "*" { "*" } / "/" { "/" }
- 运行
peg -inline expr.peg生成expr.go,其中Parse函数返回你注释里指定的结构体指针 - 优势:语法清晰、优先级自动处理、错误位置精确(含行号列号)
- 坑:生成代码体积较大;调试困难 —— 错误常出现在生成的
parseXXX函数里,而非你的 .peg 文件;不支持运行时加载语法 - 兼容性:生成的 parser 依赖
peg运行时,但无 CGO,可交叉编译
真正难的不是“怎么解析”,而是定义清楚你的语言边界:空格是否敏感?是否支持括号嵌套?变量名能否以数字开头?这些决策会直接影响 lexer 的正则和 parser 的递归逻辑。很多人卡在“为什么 "if a { b() }" 总是解析失败”,其实问题出在没先写出完整的 BNF 草稿,而是边写边改 token 规则。


















