通过Demo来了解语法解析的过程
了解 ast 语法解析,我们先从一个简单的 demo 开始,正向看看 ast 解析代码的过程,过程虽然不复杂,但是特别繁琐。
看下面的代码,真实的环境中 32 行的 ParsePeople 是不存在的,我们要使用 ast 语法解析,通过分析 14 行的 BuildPeople 来自动生成 33 行的 BuildPeople 代码。
业务上因为之前的写法有点难看了,但这种写法的代码又特别多,想结合 ast 来自动实现替换,这样也不太容易出错(整个例子都是假设的)。
核心的步骤就是①遍历整个语法树,找到目标的位置进行处理。这个例子比较简单,IfStmt 就是我们的目标位置,fSet和fNode、以及Inspect都属于标准处理方式。
接下来解析的就是②断言类型,你会遇到各种各样的语法类型,只需要不停地做断言,不停地做case判断。IfStmt 结构体中的两个成员,一个是 Cond 属于 Expr 接口类型,一个是 Body 属于 BlockStmt 类型。
import (
"go/ast"
"go/parser"
"go/token"
"log"
)
func main() {
fSet := token.NewFileSet()
fNode, err := parser.ParseFile(fSet, "/pwd/ifstat.txt", nil, parser.ParseComments)
if err != nil {
log.Fatal(err)
}
ast.Inspect(fNode, func(node ast.Node) bool {
switch n := node.(type) {
case *ast.IfStmt:
}
return true
})
}
拿 Expr 这个接口来说,断言的时候,你完全不需要知道有哪些类型实现了这个接口,我们只需要勤劳一些,不停地做试探就可以了,我来举个例子,我们断言 Cond,只写一个 default 分支,然后打印 Cond 的类型信息
结合解析的代码可以知道,len(param) >= 2
在语法解析中的类型为 *ast.BinaryExpr
,我们继续重复这样的操作就可以了。
这个例子我们不需要解析 Cond部分,因为我们完全可以通过解析 Body 的部分来构造我们想要的代码,将 Cond 的断言更换成对 Body 的断言。结合代码输出,我们可以知道,接下来要处理的类型是 赋值语句 *ast.AssignStmt
我们获取到AssignStmt结构体之后,紧接着就获取它左边的表达式和右边的表达式,继续断言
写到这里,这种不断试探断言的方式大家应该基本了解了,后面的方式处理方式也是相同的,下面是完整的处理逻辑。
我们通过已知的结果去生成另外一种结构,通过 ast 的方式写起来真的是非常繁琐,如果只靠纯文本的方式解析,也面临很多特殊的情况。
如果我们将这两种方式结合起来,势必会有事半功倍的效果。
将表达式转换为字符串
如果我们获取到表达式,我们可以直接将其转换为原始的字符串,通过字符串的方式去解析。我们一步一步入手