3B.5 Unit Test及AST可视化
在了解了语法树的数据结构与基本解析思路之后,就没有必要继续钻入各个代码角落去学习解析细节了。此时更好的方式是反向学习:通过 UT 来调用解析函数,查看各种代码块的解析结果。
语法分析器很容易进行单元测试,因为其本质上只需要一段代码作为输入,对运行环境没有额外的要求,因此我们不需要做太多的初始化工作。测试文件 parsertest.go 中已经包含了很多测试用例可供参考,可以直接运行。但在运行 UT 或者构建自己的 UT 之前,我们先来看一下如何更好的查看语法树的内容,毕竟其内嵌结构可能很深。
文件 dumper.go 中的函数 Fdump
可以将一个 Node
的结构 dump 出来,而包括 File
在内,所有前面介绍过得结构体都实现了 Node
接口,所以该函数是我们可视化语法树的好工具。
下面我们通过一些 UT 来进一步学习一下语法解析的功能。
在 parser_test.go
内创建如下 UT
func 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: nil 3 . PkgName: main @ dumpsource.go:2:9 4 . DeclList: []syntax.Decl (5 entries) { 5 . . 0: *syntax.ImportDecl { 6 . . . Group: *syntax.Group {} 7 . . . Pragma: nil 8 . . . LocalPkgName: nil 9 . . . Path: *syntax.BasicLit { 10 . . . . Value: “\”fmt\“” 11 . . . . Kind: 4 12 . . . . Bad: false 13 . . . } 14 . . } 15 . . 1: *syntax.ImportDecl { 16 . . . Group: *syntax.Group {} 17 . . . Pragma: nil 18 . . . LocalPkgName: nil 19 . . . Path: *syntax.BasicLit { 20 . . . . Value: “\”net/http\“” 21 . . . . Kind: 4 22 . . . . Bad: false 23 . . . } 24 . . } 25 . . 2: *syntax.ConstDecl { 26 . . . Group: *syntax.Group {} 27 . . . Pragma: nil 28 . . . NameList: []*syntax.Name (2 entries) { 29 . . . . 0: a @ dumpsource.go:9:8 30 . . . . 1: b @ dumpsource.go:9:11 31 . . . } 32 . . . Type: nil 33 . . . Values: nil 34 . . } 35 . . 3: *syntax.VarDecl { 36 . . . Group: nil 37 . . . Pragma: nil 38 . . . NameList: []*syntax.Name (1 entries) { 39 . . . . 0: names @ dumpsource.go:10:5 40 . . . } 41 . . . Type: *syntax.SliceType { 42 . . . . Elem: string @ dumpsource.go:10:13 43 . . . } 44 . . . Values: *syntax.CallExpr { 45 . . . . Fun: make @ dumpsource.go:10:22 46 . . . . ArgList: []syntax.Expr (2 entries) { 47 . . . . . 0: *syntax.SliceType { 48 . . . . . . Elem: int @ dumpsource.go:10:29 49 . . . . . } 50 . . . . . 1: *syntax.BasicLit { 51 . . . . . . Value: “10” 52 . . . . . . Kind: 0 53 . . . . . . Bad: false 54 . . . . . } 55 . . . . } 56 . . . . HasDots: false 57 . . . } 58 . . } 59 . . 4: *syntax.FuncDecl { 60 . . . Pragma: nil 61 . . . Recv: nil 62 . . . Name: iterate @ dumpsource.go:12:6 63 . . . TParamList: []*syntax.Field (1 entries) { 64 . . . . 0: *syntax.Field { 65 . . . . . Name: T @ dumpsource.go:12:14 66 . . . . . Type: any @ dumpsource.go:12:16 67 . . . . } 68 . . . } 69 . . . Type: *syntax.FuncType { 70 . . . . ParamList: []*syntax.Field (2 entries) { 71 . . . . . 0: *syntax.Field { 72 . . . . . . Name: list @ dumpsource.go:12:21 73 . . . . . . Type: *syntax.SliceType { 74 . . . . . . . Elem: T @ dumpsource.go:12:28 75 . . . . . . } 76 . . . . . } 77 . . . . . 1: *syntax.Field { 78 . . . . . . Name: f @ dumpsource.go:12:31 79 . . . . . . Type: *syntax.FuncType { 80 . . . . . . . ParamList: []*syntax.Field (1 entries) { 81 . . . . . . . . 0: *syntax.Field { 82 . . . . . . . . . Name: nil 83 . . . . . . . . . Type: T @ dumpsource.go:12:38 84 . . . . . . . . } 85 . . . . . . . } 86 . . . . . . . ResultList: nil 87 . . . . . . } 88 . . . . . } 89 . . . . } 90 . . . . ResultList: nil 91 . . . } 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:6 99 . . . . . . . . . 1: t @ dumpsource.go:13:9 100 . . . . . . . . } 101 . . . . . . . } 102 . . . . . . . Def: true 103 . . . . . . . X: list @ dumpsource.go:13:21 104 . . . . . . } 105 . . . . . . Cond: nil 106 . . . . . . Post: nil 107 . . . . . . Body: *syntax.BlockStmt { 108 . . . . . . . List: []syntax.Stmt (1 entries) { 109 . . . . . . . . 0: *syntax.ExprStmt { 110 . . . . . . . . . X: *syntax.CallExpr { 111 . . . . . . . . . . Fun: f @ dumpsource.go:14:3 112 . . . . . . . . . . ArgList: []syntax.Expr (1 entries) { 113 . . . . . . . . . . . 0: t @ dumpsource.go:14:5 114 . . . . . . . . . . } 115 . . . . . . . . . . HasDots: false 116 . . . . . . . . . } 117 . . . . . . . . } 118 . . . . . . . } 119 . . . . . . . Rbrace: syntax.Pos {} 120 . . . . . . } 121 . . . . . } 122 . . . . . 1: *syntax.ReturnStmt { 123 . . . . . . Results: *syntax.BasicLit { 124 . . . . . . . Value: “\”unexpected\“” 125 . . . . . . . Kind: 4 126 . . . . . . . Bad: false 127 . . . . . . } 128 . . . . . } 129 . . . . } 130 . . . . Rbrace: syntax.Pos {} 131 . . . } 132 . . } 133 . } 134 . EOF: syntax.Pos {} 135 }
但仔细查看我们所解析的代码,会发现其存在如下问题:
import 的库未使用
常量 a, b 没有初始化
变量 names 声明的类型与 make 中指定的类型不一致
iterate 声明中不需要返回值,但该函数却返回了一个字符串
但解析器依然成功完成了解析,这是因为此时解析器只是负责做语法分析,只要程序符合语言的文法规范,解析器就能够顺利完成;而上述问题是语义问题,或者是代码规范问题(import 的库未使用),不属于语法解析器的管理范围。想要处理这些问题,编译器还需要将语法树继续往后传递并进行进一步处理。
最后更新于