3B.5 Unit Test及AST可视化
在了解了语法树的数据结构与基本解析思路之后,就没有必要继续钻入各个代码角落去学习解析细节了。此时更好的方式是反向学习:通过 UT 来调用解析函数,查看各种代码块的解析结果。
语法分析器很容易进行单元测试,因为其本质上只需要一段代码作为输入,对运行环境没有额外的要求,因此我们不需要做太多的初始化工作。测试文件 parsertest.go 中已经包含了很多测试用例可供参考,可以直接运行。但在运行 UT 或者构建自己的 UT 之前,我们先来看一下如何更好的查看语法树的内容,毕竟其内嵌结构可能很深。
文件 dumper.go 中的函数
Fdump
可以将一个 Node
的结构 dump 出来,而包括 File
在内,所有前面介绍过得结构体都实现了 Node
接口,所以该函数是我们可视化语法树的好工具。 下面我们通过一些 UT 来进一步学习一下语法解析的功能。
在
parser_test.go
内创建如下 UTfunc TestParser(t *testing.T) {
code := `
package main
import (
"fmt"
"net/http"
)
const (a, b)
var names []string = make([]int, 10)
func iterate[T any](list []T, f func(T)) {
for _, t := range list {
f(t)
}
}
`
ast, _ := Parse(NewFileBase("dump_source.go"), bytes.NewBuffer([]byte(code)), func(err error) {
fmt.Printf("Parsing Error: %v\n", err)
}, nil, CheckBranches|AllowGenerics) // 开启泛型支持,以便解析出类型参数
if ast != nil {
Fdump(os.Stdout, ast)
}
}
执行该 UT 得到输出结果:
1 *syntax.File {2 . Pragma: nil3 . PkgName: main @ dumpsource.go:2:94 . DeclList: []syntax.Decl (5 entries) {5 . . 0: *syntax.ImportDecl {6 . . . Group: *syntax.Group {}7 . . . Pragma: nil8 . . . LocalPkgName: nil9 . . . Path: *syntax.BasicLit {10 . . . . Value: “\”fmt\“”11 . . . . Kind: 412 . . . . Bad: false13 . . . }14 . . }15 . . 1: *syntax.ImportDecl {16 . . . Group: *syntax.Group {}17 . . . Pragma: nil18 . . . LocalPkgName: nil19 . . . Path: *syntax.BasicLit {20 . . . . Value: “\”net/http\“”21 . . . . Kind: 422 . . . . Bad: false23 . . . }24 . . }25 . . 2: *syntax.ConstDecl {26 . . . Group: *syntax.Group {}27 . . . Pragma: nil28 . . . NameList: []*syntax.Name (2 entries) {29 . . . . 0: a @ dumpsource.go:9:830 . . . . 1: b @ dumpsource.go:9:1131 . . . }32 . . . Type: nil33 . . . Values: nil34 . . }35 . . 3: *syntax.VarDecl {36 . . . Group: nil37 . . . Pragma: nil38 . . . NameList: []*syntax.Name (1 entries) {39 . . . . 0: names @ dumpsource.go:10:540 . . . }41 . . . Type: *syntax.SliceType {42 . . . . Elem: string @ dumpsource.go:10:1343 . . . }44 . . . Values: *syntax.CallExpr {45 . . . . Fun: make @ dumpsource.go:10:2246 . . . . ArgList: []syntax.Expr (2 entries) {47 . . . . . 0: *syntax.SliceType {48 . . . . . . Elem: int @ dumpsource.go:10:2949 . . . . . }50 . . . . . 1: *syntax.BasicLit {51 . . . . . . Value: “10”52 . . . . . . Kind: 053 . . . . . . Bad: false54 . . . . . }55 . . . . }56 . . . . HasDots: false57 . . . }58 . . }59 . . 4: *syntax.FuncDecl {60 . . . Pragma: nil61 . . . Recv: nil62 . . . Name: iterate @ dumpsource.go:12:663 . . . TParamList: []*syntax.Field (1 entries) {64 . . . . 0: *syntax.Field {65 . . . . . Name: T @ dumpsource.go:12:1466 . . . . . Type: any @ dumpsource.go:12:1667 . . . . }68 . . . }69 . . . Type: *syntax.FuncType {70 . . . . ParamList: []*syntax.Field (2 entries) {71 . . . . . 0: *syntax.Field {72 . . . . . . Name: list @ dumpsource.go:12:2173 . . . . . . Type: *syntax.SliceType {74 . . . . . . . Elem: T @ dumpsource.go:12:2875 . . . . . . }76 . . . . . }77 . . . . . 1: *syntax.Field {78 . . . . . . Name: f @ dumpsource.go:12:3179 . . . . . . Type: *syntax.FuncType {80 . . . . . . . ParamList: []*syntax.Field (1 entries) {81 . . . . . . . . 0: *syntax.Field {82 . . . . . . . . . Name: nil83 . . . . . . . . . Type: T @ dumpsource.go:12:3884 . . . . . . . . }85 . . . . . . . }86 . . . . . . . ResultList: nil87 . . . . . . }88 . . . . . }89 . . . . }90 . . . . ResultList: nil91 . . . }92 . . . Body: *syntax.BlockStmt {93 . . . . List: []syntax.Stmt (2 entries) {94 . . . . . 0: *syntax.ForStmt {95 . . . . . . Init: *syntax.RangeClause {96 . . . . . . . Lhs: *syntax.ListExpr {97 . . . . . . . . ElemList: []syntax.Expr (2 entries) {98 . . . . . . . . . 0: _ @ dumpsource.go:13:699 . . . . . . . . . 1: t @ dumpsource.go:13:9100 . . . . . . . . }101 . . . . . . . }102 . . . . . . . Def: true103 . . . . . . . X: list @ dumpsource.go:13:21104 . . . . . . }105 . . . . . . Cond: nil106 . . . . . . Post: nil107 . . . . . . Body: *syntax.BlockStmt {108 . . . . . . . List: []syntax.Stmt (1 entries) {109 . . . . . . . . 0: *syntax.ExprStmt {110 . . . . . . . . . X: *syntax.CallExpr {111 . . . . . . . . . . Fun: f @ dumpsource.go:14:3112 . . . . . . . . . . ArgList: []syntax.Expr (1 entries) {113 . . . . . . . . . . . 0: t @ dumpsource.go:14:5114 . . . . . . . . . . }115 . . . . . . . . . . HasDots: false116 . . . . . . . . . }117 . . . . . . . . }118 . . . . . . . }119 . . . . . . . Rbrace: syntax.Pos {}120 . . . . . . }121 . . . . . }122 . . . . . 1: *syntax.ReturnStmt {123 . . . . . . Results: *syntax.BasicLit {124 . . . . . . . Value: “\”unexpected\“”125 . . . . . . . Kind: 4126 . . . . . . . Bad: false127 . . . . . . }128 . . . . . }129 . . . . }130 . . . . Rbrace: syntax.Pos {}131 . . . }132 . . }133 . }134 . EOF: syntax.Pos {}135 }
但仔细查看我们所解析的代码,会发现其存在如下问题:
- 1.import 的库未使用
- 2.常量 a, b 没有初始化
- 3.变量 names 声明的类型与 make 中指定的类型不一致
- 4.iterate 声明中不需要返回值,但该函数却返回了一个字符串
但解析器依然成功完成了解析,这是因为此时解析器只是负责做语法分析,只要程序符合语言的文法规范,解析器就能够顺利完成;而上述问题是语义问题,或者是代码规范问题(import 的库未使用),不属于语法解析器的管理范围。想要处理这些问题,编译器还需要将语法树继续往后传递并进行进一步处理。