golang抽象语法树

548 阅读8分钟

1、关于AST的一些基础概念

1.1 AST概念和作用

用编程语言编写的源代码的抽象语法结构的树状表示。树的每个节点都表示源代码中出现的一个构造。

image.png

(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值的字段会被省略。每个节点类型的完整字段列表请参见文档。

image.png

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代表除函数以外的所有声明,即importconstvartype

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.ImportSpecast.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。

扩展阅读1 goast-viewer