1、关于AST的一些基础概念
1.1 AST概念和作用
用编程语言编写的源代码的抽象语法结构的树状表示。树的每个节点都表示源代码中出现的一个构造。
(AST的构建过程)
一个词法分析器(Lexer)对文本(Source Code)进行词法分析,生成Token。一般接下来是将它传给一个解析器,然后检索生成AST.
废话少说:学一样东西,就想在某天用得上,ast可以帮助我们做代码分析,发现潜在问题,也可以帮助我们实现代码生成器,golang直到如今还没有AOP功能,带我们基于ast就可以实现此功能,此功能在当代项目工程中尤为重要,后续的文章我们将实现一套简洁的AOP工具.
1.2 ast里面的接口
所有的AST节点都实现了ast.Node接口,它只是返回AST中的一个位置。
另外,还有3个主要接口扩展了ast.Node。
- ast.Expr - expression表达式节点
- ast.Stmt - statement描述节点
- ast.Decl - declaration声明节点
具体源代码:[ ]( go/ast.go at 0b7c202e98949b530f7f4011efd454164356ba69 · golang/go (github.com))
// All node types implement the Node interface.
type Node interface {
Pos() token.Pos // position of first character belonging to the node
End() token.Pos // position of first character immediately after the node
}
// All expression nodes implement the Expr interface.
type Expr interface {
Node
exprNode()
}
// All statement nodes implement the Stmt interface.
type Stmt interface {
Node
stmtNode()
}
// All declaration nodes implement the Decl interface.
type Decl interface {
Node
declNode()
}
2.如何分析
使用ast.Print(一个强大的API)来实现对AST的人工读取。
代码如下:
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
)
func main() {
fset := token.NewFileSet()
f, _ := parser.ParseFile(fset, "dummy.go", src, parser.ParseComments)
ast.Inspect(f, func(n ast.Node) bool {
// Called recursively.
ast.Print(fset, n)
return true
})
}
var src = `package hello
import "fmt"
func greet() {
fmt.Println("Hello, World")
}
`
ast.File
第一个要访问的节点是*ast.File,它是所有AST节点的根。它只实现了ast.Node接口。
ast.File有引用包名、导入声明和函数声明作为子节点。
准确地说,它还有Comments等,但为了简单起见,我省略了它们。
让我们从包名开始。
注意,带nil值的字段会被省略。每个节点类型的完整字段列表请参见文档。
Starting: G:\gopath\bin\dlv.exe dap --listen=127.0.0.1:51511 from f:\goprjs\s3
DAP server listening at: 127.0.0.1:51511
Type 'dlv help' for list of commands.
0 *ast.File {
1 . Package: dummy.go:1:1
2 . Name: *ast.Ident {
3 . . NamePos: dummy.go:1:9
4 . . Name: "hello"
5 . }
6 . Decls: []ast.Decl (len = 2) {
7 . . 0: *ast.GenDecl {
8 . . . TokPos: dummy.go:3:1
9 . . . Tok: import
10 . . . Lparen: -
11 . . . Specs: []ast.Spec (len = 1) {
12 . . . . 0: *ast.ImportSpec {
13 . . . . . Path: *ast.BasicLit {
14 . . . . . . ValuePos: dummy.go:3:8
15 . . . . . . Kind: STRING
16 . . . . . . Value: "\"fmt\""
17 . . . . . }
18 . . . . . EndPos: -
19 . . . . }
20 . . . }
21 . . . Rparen: -
22 . . }
23 . . 1: *ast.FuncDecl {
24 . . . Name: *ast.Ident {
25 . . . . NamePos: dummy.go:5:6
26 . . . . Name: "greet"
27 . . . . Obj: *ast.Object {
28 . . . . . Kind: func
29 . . . . . Name: "greet"
30 . . . . . Decl: *(obj @ 23)
31 . . . . }
32 . . . }
33 . . . Type: *ast.FuncType {
34 . . . . Func: dummy.go:5:1
35 . . . . Params: *ast.FieldList {
36 . . . . . Opening: dummy.go:5:11
37 . . . . . Closing: dummy.go:5:12
38 . . . . }
39 . . . }
40 . . . Body: *ast.BlockStmt {
41 . . . . Lbrace: dummy.go:5:14
42 . . . . List: []ast.Stmt (len = 1) {
43 . . . . . 0: *ast.ExprStmt {
44 . . . . . . X: *ast.CallExpr {
45 . . . . . . . Fun: *ast.SelectorExpr {
46 . . . . . . . . X: *ast.Ident {
47 . . . . . . . . . NamePos: dummy.go:6:2
48 . . . . . . . . . Name: "fmt"
49 . . . . . . . . }
50 . . . . . . . . Sel: *ast.Ident {
51 . . . . . . . . . NamePos: dummy.go:6:6
52 . . . . . . . . . Name: "Println"
53 . . . . . . . . }
54 . . . . . . . }
55 . . . . . . . Lparen: dummy.go:6:13
56 . . . . . . . Args: []ast.Expr (len = 1) {
57 . . . . . . . . 0: *ast.BasicLit {
58 . . . . . . . . . ValuePos: dummy.go:6:14
59 . . . . . . . . . Kind: STRING
60 . . . . . . . . . Value: "\"Hello, World\""
61 . . . . . . . . }
62 . . . . . . . }
63 . . . . . . . Ellipsis: -
64 . . . . . . . Rparen: dummy.go:6:28
65 . . . . . . }
66 . . . . . }
67 . . . . }
68 . . . . Rbrace: dummy.go:7:1
69 . . . }
70 . . }
71 . }
72 . FileStart: dummy.go:1:1
73 . FileEnd: dummy.go:7:3
74 . Scope: *ast.Scope {
75 . . Objects: map[string]*ast.Object (len = 1) {
76 . . . "greet": *(obj @ 27)
77 . . }
78 . }
79 . Imports: []*ast.ImportSpec (len = 1) {
80 . . 0: *(obj @ 12)
81 . }
82 . Unresolved: []*ast.Ident (len = 1) {
83 . . 0: *(obj @ 46)
84 . }
85 }
0 *ast.Ident {
1 . NamePos: dummy.go:1:9
2 . Name: "hello"
3 }
0 nil
0 *ast.GenDecl {
1 . TokPos: dummy.go:3:1
2 . Tok: import
3 . Lparen: -
4 . Specs: []ast.Spec (len = 1) {
5 . . 0: *ast.ImportSpec {
6 . . . Path: *ast.BasicLit {
7 . . . . ValuePos: dummy.go:3:8
8 . . . . Kind: STRING
9 . . . . Value: "\"fmt\""
10 . . . }
11 . . . EndPos: -
12 . . }
13 . }
14 . Rparen: -
15 }
0 *ast.ImportSpec {
1 . Path: *ast.BasicLit {
2 . . ValuePos: dummy.go:3:8
3 . . Kind: STRING
4 . . Value: "\"fmt\""
5 . }
6 . EndPos: -
7 }
0 *ast.BasicLit {
1 . ValuePos: dummy.go:3:8
2 . Kind: STRING
3 . Value: "\"fmt\""
4 }
0 nil
0 nil
0 nil
0 *ast.FuncDecl {
1 . Name: *ast.Ident {
2 . . NamePos: dummy.go:5:6
3 . . Name: "greet"
4 . . Obj: *ast.Object {
5 . . . Kind: func
6 . . . Name: "greet"
7 . . . Decl: *(obj @ 0)
8 . . }
9 . }
10 . Type: *ast.FuncType {
11 . . Func: dummy.go:5:1
12 . . Params: *ast.FieldList {
13 . . . Opening: dummy.go:5:11
14 . . . Closing: dummy.go:5:12
15 . . }
16 . }
17 . Body: *ast.BlockStmt {
18 . . Lbrace: dummy.go:5:14
19 . . List: []ast.Stmt (len = 1) {
20 . . . 0: *ast.ExprStmt {
21 . . . . X: *ast.CallExpr {
22 . . . . . Fun: *ast.SelectorExpr {
23 . . . . . . X: *ast.Ident {
24 . . . . . . . NamePos: dummy.go:6:2
25 . . . . . . . Name: "fmt"
26 . . . . . . }
27 . . . . . . Sel: *ast.Ident {
28 . . . . . . . NamePos: dummy.go:6:6
29 . . . . . . . Name: "Println"
30 . . . . . . }
31 . . . . . }
32 . . . . . Lparen: dummy.go:6:13
33 . . . . . Args: []ast.Expr (len = 1) {
34 . . . . . . 0: *ast.BasicLit {
35 . . . . . . . ValuePos: dummy.go:6:14
36 . . . . . . . Kind: STRING
37 . . . . . . . Value: "\"Hello, World\""
38 . . . . . . }
39 . . . . . }
40 . . . . . Ellipsis: -
41 . . . . . Rparen: dummy.go:6:28
42 . . . . }
43 . . . }
44 . . }
45 . . Rbrace: dummy.go:7:1
46 . }
47 }
0 *ast.Ident {
1 . NamePos: dummy.go:5:6
2 . Name: "greet"
3 . Obj: *ast.Object {
4 . . Kind: func
5 . . Name: "greet"
6 . . Decl: *ast.FuncDecl {
7 . . . Name: *(obj @ 0)
8 . . . Type: *ast.FuncType {
9 . . . . Func: dummy.go:5:1
10 . . . . Params: *ast.FieldList {
11 . . . . . Opening: dummy.go:5:11
12 . . . . . Closing: dummy.go:5:12
13 . . . . }
14 . . . }
15 . . . Body: *ast.BlockStmt {
16 . . . . Lbrace: dummy.go:5:14
17 . . . . List: []ast.Stmt (len = 1) {
18 . . . . . 0: *ast.ExprStmt {
19 . . . . . . X: *ast.CallExpr {
20 . . . . . . . Fun: *ast.SelectorExpr {
21 . . . . . . . . X: *ast.Ident {
22 . . . . . . . . . NamePos: dummy.go:6:2
23 . . . . . . . . . Name: "fmt"
24 . . . . . . . . }
25 . . . . . . . . Sel: *ast.Ident {
26 . . . . . . . . . NamePos: dummy.go:6:6
27 . . . . . . . . . Name: "Println"
28 . . . . . . . . }
29 . . . . . . . }
30 . . . . . . . Lparen: dummy.go:6:13
31 . . . . . . . Args: []ast.Expr (len = 1) {
32 . . . . . . . . 0: *ast.BasicLit {
33 . . . . . . . . . ValuePos: dummy.go:6:14
34 . . . . . . . . . Kind: STRING
35 . . . . . . . . . Value: "\"Hello, World\""
36 . . . . . . . . }
37 . . . . . . . }
38 . . . . . . . Ellipsis: -
39 . . . . . . . Rparen: dummy.go:6:28
40 . . . . . . }
41 . . . . . }
42 . . . . }
43 . . . . Rbrace: dummy.go:7:1
44 . . . }
45 . . }
46 . }
47 }
0 nil
0 *ast.FuncType {
1 . Func: dummy.go:5:1
2 . Params: *ast.FieldList {
3 . . Opening: dummy.go:5:11
4 . . Closing: dummy.go:5:12
5 . }
6 }
0 *ast.FieldList {
1 . Opening: dummy.go:5:11
2 . Closing: dummy.go:5:12
3 }
0 nil
0 nil
0 *ast.BlockStmt {
1 . Lbrace: dummy.go:5:14
2 . List: []ast.Stmt (len = 1) {
3 . . 0: *ast.ExprStmt {
4 . . . X: *ast.CallExpr {
5 . . . . Fun: *ast.SelectorExpr {
6 . . . . . X: *ast.Ident {
7 . . . . . . NamePos: dummy.go:6:2
8 . . . . . . Name: "fmt"
9 . . . . . }
10 . . . . . Sel: *ast.Ident {
11 . . . . . . NamePos: dummy.go:6:6
12 . . . . . . Name: "Println"
13 . . . . . }
14 . . . . }
15 . . . . Lparen: dummy.go:6:13
16 . . . . Args: []ast.Expr (len = 1) {
17 . . . . . 0: *ast.BasicLit {
18 . . . . . . ValuePos: dummy.go:6:14
19 . . . . . . Kind: STRING
20 . . . . . . Value: "\"Hello, World\""
21 . . . . . }
22 . . . . }
23 . . . . Ellipsis: -
24 . . . . Rparen: dummy.go:6:28
25 . . . }
26 . . }
27 . }
28 . Rbrace: dummy.go:7:1
29 }
0 *ast.ExprStmt {
1 . X: *ast.CallExpr {
2 . . Fun: *ast.SelectorExpr {
3 . . . X: *ast.Ident {
4 . . . . NamePos: dummy.go:6:2
5 . . . . Name: "fmt"
6 . . . }
7 . . . Sel: *ast.Ident {
8 . . . . NamePos: dummy.go:6:6
9 . . . . Name: "Println"
10 . . . }
11 . . }
12 . . Lparen: dummy.go:6:13
13 . . Args: []ast.Expr (len = 1) {
14 . . . 0: *ast.BasicLit {
15 . . . . ValuePos: dummy.go:6:14
16 . . . . Kind: STRING
17 . . . . Value: "\"Hello, World\""
18 . . . }
19 . . }
20 . . Ellipsis: -
21 . . Rparen: dummy.go:6:28
22 . }
23 }
0 *ast.CallExpr {
1 . Fun: *ast.SelectorExpr {
2 . . X: *ast.Ident {
3 . . . NamePos: dummy.go:6:2
4 . . . Name: "fmt"
5 . . }
6 . . Sel: *ast.Ident {
7 . . . NamePos: dummy.go:6:6
8 . . . Name: "Println"
9 . . }
10 . }
11 . Lparen: dummy.go:6:13
12 . Args: []ast.Expr (len = 1) {
13 . . 0: *ast.BasicLit {
14 . . . ValuePos: dummy.go:6:14
15 . . . Kind: STRING
16 . . . Value: "\"Hello, World\""
17 . . }
18 . }
19 . Ellipsis: -
20 . Rparen: dummy.go:6:28
21 }
0 *ast.SelectorExpr {
1 . X: *ast.Ident {
2 . . NamePos: dummy.go:6:2
3 . . Name: "fmt"
4 . }
5 . Sel: *ast.Ident {
6 . . NamePos: dummy.go:6:6
7 . . Name: "Println"
8 . }
9 }
0 *ast.Ident {
1 . NamePos: dummy.go:6:2
2 . Name: "fmt"
3 }
0 nil
0 *ast.Ident {
1 . NamePos: dummy.go:6:6
2 . Name: "Println"
3 }
0 nil
0 nil
0 *ast.BasicLit {
1 . ValuePos: dummy.go:6:14
2 . Kind: STRING
3 . Value: "\"Hello, World\""
4 }
0 nil
0 nil
0 nil
0 nil
0 nil
0 nil
2.1 包名
ast.Indent
*ast.Ident {
. NamePos: dummy.go:1:9
. Name: "hello"
}
BASH 复制 全屏
一个包名可以用AST节点类型*ast.Ident来表示,它实现了ast.Expr接口。
所有的标识符都由这个结构来表示,它主要包含了它的名称和在文件集中的源位置。
从上述所示的代码中,我们可以看到包名是hello,并且是在dummy.go的第一行声明的。
对于这个节点我们不会再深入研究了,让我们再回到
*ast.File.Go中。
2.2导入声明
ast.GenDecl
*ast.GenDecl {
. TokPos: dummy.go:3:1
. Tok: import
. Lparen: -
. Specs: []ast.Spec (len = 1) {
. . 0: *ast.ImportSpec {/* Omission */}
. }
. Rparen: -
}
ast.GenDecl代表除函数以外的所有声明,即import、const、var和type。
Tok代表一个词性标记--它指定了声明的内容(import或const或type或var)。
这个AST节点告诉我们,import声明在dummy.go的第3行。
让我们从上到下深入地看一下ast.GenDecl的下一个节点*ast.ImportSpec。
ast.ImportSpec
*ast.ImportSpec {
. Path: *ast.BasicLit {/* Omission */}
. EndPos: -
}
一个ast.ImportSpec节点对应一个导入声明。它实现了ast.Spec接口,访问路径可以让导入路径更有意义。
ast.BasicLit
*ast.BasicLit {
. ValuePos: dummy.go:3:8
. Kind: STRING
. Value: ""fmt""
}
一个ast.BasicLit节点表示一个基本类型的文字,它实现了ast.Expr接口。
它包含一个token类型,可以使用token.INT、token.FLOAT、token.IMAG、token.CHAR或token.STRING。
从ast.ImportSpec和ast.BasicLit中,我们可以看到它导入了名为"fmt "的包。
我们不再深究了,让我们再回到顶层。
2.3函数声明
ast.FuncDecl
*ast.FuncDecl {
. Name: *ast.Ident {/* Omission */}
. Type: *ast.FuncType {/* Omission */}
. Body: *ast.BlockStmt {/* Omission */}
}
一个ast.FuncDecl节点代表一个函数声明,但它只实现了ast.Node接口。我们从代表函数名的Name开始,依次看一下。
ast.Ident
*ast.Ident {
. NamePos: dummy.go:5:6
. Name: "greet"
. Obj: *ast.Object {
. . Kind: func
. . Name: "greet"
. . Decl: *(obj @ 0)
. }
}
第二次出现这种情况,我就不做基本解释了。
值得注意的是*ast.Object,它代表了标识符所指的对象,但为什么需要这个呢?
大家知道,GoLang有一个scope的概念,就是源文本的scope,其中标识符表示指定的常量、类型、变量、函数、标签或包。
Decl字段表示标识符被声明的位置,这样就确定了标识符的scope。指向相同对象的标识符共享相同的*ast.Object.Label。
ast.FuncType
*ast.FuncType {
. Func: dummy.go:5:1
. Params: *ast.FieldList {/* Omission */}
}
一个 ast.FuncType 包含一个函数签名,包括参数、结果和 "func "关键字的位置。
ast.FieldList
*ast.FieldList {
. Opening: dummy.go:5:11
. List: nil
. Closing: dummy.go:5:12
}
ast.FieldList节点表示一个Field的列表,用括号或大括号括起来。如果定义了函数参数,这里会显示,但这次没有,所以没有信息。
列表字段是*ast.Field的一个切片,包含一对标识符和类型。它的用途很广,用于各种Nodes,包括*ast.StructType、*ast.InterfaceType和本文中使用示例。
也就是说,当把一个类型映射到一个标识符时,需要用到它(如以下的代码):
foot int
bar string
让我们再次回到*ast.FuncDecl,再深入了解一下最后一个字段Body。
ast.BlockStmt
*ast.BlockStmt {
. Lbrace: dummy.go:5:14
. List: []ast.Stmt (len = 1) {
. . 0: *ast.ExprStmt {/* Omission */}
. }
. Rbrace: dummy.go:7:1
}
一个ast.BlockStmt节点表示一个括号内的语句列表,它实现了ast.Stmt接口。
ast.ExprStmt
*ast.ExprStmt {
. X: *ast.CallExpr {/* Omission */}
}
ast.ExprStmt在语句列表中表示一个表达式,它实现了ast.Stmt接口,并包含一个ast.Expr。
ast.CallExpr
*ast.CallExpr {
. Fun: *ast.SelectorExpr {/* Omission */}
. Lparen: dummy.go:6:13
. Args: []ast.Expr (len = 1) {
. . 0: *ast.BasicLit {/* Omission */}
. }
. Ellipsis: -
. Rparen: dummy.go:6:28
}
ast.CallExpr表示一个调用函数的表达式,要查看的字段是:
- Fun
- 要调用的函数和Args
- 要传递给它的参数列表
ast.SelectorExpr
*ast.SelectorExpr {
. X: *ast.Ident {
. . NamePos: dummy.go:6:2
. . Name: "fmt"
. }
. Sel: *ast.Ident {
. . NamePos: dummy.go:6:6
. . Name: "Println"
. }
}
ast.SelectorExpr表示一个带有选择器的表达式。简单地说,它的意思是fmt.Println。
ast.BasicLit
*ast.BasicLit {
. ValuePos: dummy.go:6:14
. Kind: STRING
. Value: ""Hello, World""
}
这个就不需要多解释了,就是简单的"Hello, World。