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. import 的库未使用

  2. 常量 a, b 没有初始化

  3. 变量 names 声明的类型与 make 中指定的类型不一致

  4. iterate 声明中不需要返回值,但该函数却返回了一个字符串

但解析器依然成功完成了解析,这是因为此时解析器只是负责做语法分析,只要程序符合语言的文法规范,解析器就能够顺利完成;而上述问题是语义问题,或者是代码规范问题(import 的库未使用),不属于语法解析器的管理范围。想要处理这些问题,编译器还需要将语法树继续往后传递并进行进一步处理。

最后更新于