Links

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 }
但仔细查看我们所解析的代码,会发现其存在如下问题:
  1. 1.
    import 的库未使用
  2. 2.
    常量 a, b 没有初始化
  3. 3.
    变量 names 声明的类型与 make 中指定的类型不一致
  4. 4.
    iterate 声明中不需要返回值,但该函数却返回了一个字符串
但解析器依然成功完成了解析,这是因为此时解析器只是负责做语法分析,只要程序符合语言的文法规范,解析器就能够顺利完成;而上述问题是语义问题,或者是代码规范问题(import 的库未使用),不属于语法解析器的管理范围。想要处理这些问题,编译器还需要将语法树继续往后传递并进行进一步处理。