Go 抽象語法樹
抽象語法樹 go/ast 庫使用
推薦背景
Go 語言在編譯過程中經過詞法分析和語法分析之後,就到了抽象語法樹的構建階段,經歷這一階段之後,語句就真正組織成了程式程式碼。利用抽象語法樹解析庫,我們可以完成程式碼的自動化分析和自動化生成,因此通常用於做一些自動化的工具,例如 wire。
使用案例
package main
import (
"go/ast"
"go/parser"
"go/token"
)
var src = `
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
`
func main() {
fset := token.NewFileSet() // positions are relative to fset
f, err := parser.ParseFile(fset, "", src, 0)
if err != nil {
panic(err)
}
// Print the AST.
ast.Print(fset, f)
}
- fset 是檔案字符集用於定位 ast.Node 的檔案位置
- parser.ParserFile 的第二引數為檔名,第三引數為字串,前者是待解析的檔案路徑,後者為待解析的字串。前者優先順序高於後者。第四引數為解析模式 (可以使用"|"來結合多種解析模式)
執行結果如下
0 *ast.File {
1 . Package: 2:1
2 . Name: *ast.Ident {
3 . . NamePos: 2:9
4 . . Name: "main"
5 . }
6 . Decls: []ast.Decl (len = 2) {
7 . . 0: *ast.GenDecl {
8 . . . TokPos: 4:1
9 . . . Tok: import
10 . . . Lparen: -
11 . . . Specs: []ast.Spec (len = 1) {
12 . . . . 0: *ast.ImportSpec {
13 . . . . . Path: *ast.BasicLit {
14 . . . . . . ValuePos: 4: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: 6:6
26 . . . . Name: "main"
27 . . . . Obj: *ast.Object {
28 . . . . . Kind: func
29 . . . . . Name: "main"
30 . . . . . Decl: *(obj @ 23)
31 . . . . }
32 . . . }
33 . . . Type: *ast.FuncType {
34 . . . . Func: 6:1
35 . . . . Params: *ast.FieldList {
36 . . . . . Opening: 6:10
37 . . . . . Closing: 6:11
38 . . . . }
39 . . . }
40 . . . Body: *ast.BlockStmt {
41 . . . . Lbrace: 6:13
42 . . . . List: []ast.Stmt (len = 1) {
43 . . . . . 0: *ast.ExprStmt {
44 . . . . . . X: *ast.CallExpr {
45 . . . . . . . Fun: *ast.SelectorExpr {
46 . . . . . . . . X: *ast.Ident {
47 . . . . . . . . . NamePos: 7:5
48 . . . . . . . . . Name: "fmt"
49 . . . . . . . . }
50 . . . . . . . . Sel: *ast.Ident {
51 . . . . . . . . . NamePos: 7:9
52 . . . . . . . . . Name: "Println"
53 . . . . . . . . }
54 . . . . . . . }
55 . . . . . . . Lparen: 7:16
56 . . . . . . . Args: []ast.Expr (len = 1) {
57 . . . . . . . . 0: *ast.BasicLit {
58 . . . . . . . . . ValuePos: 7:17
59 . . . . . . . . . Kind: STRING
60 . . . . . . . . . Value: "\"Hello World!\""
61 . . . . . . . . }
62 . . . . . . . }
63 . . . . . . . Ellipsis: -
64 . . . . . . . Rparen: 7:31
65 . . . . . . }
66 . . . . . }
67 . . . . }
68 . . . . Rbrace: 8:1
69 . . . }
70 . . }
71 . }
72 . Scope: *ast.Scope {
74 . . Objects: map[string]*ast.Object (len = 1) {
74 . . . "main": *(obj @ 27)
75 . . }
76 . }
77 . Imports: []*ast.ImportSpec (len = 1) {
78 . . 0: *(obj @ 12)
79 . }
80 . Unresolved: []*ast.Ident (len = 1) {
81 . . 0: *(obj @ 46)
82 . }
83 }
ast.File 內容
type File struct {
Doc *CommentGroup // associated documentation; or nil
Package token.Pos // position of "package" keyword
Name *Ident // package name
Decls []Decl // top-level declarations; or nil
Scope *Scope // package scope (this file only)
Imports []*ImportSpec // imports in this file
Unresolved []*Ident // unresolved identifiers in this file
Comments []*CommentGroup // list of all comments in the source file
}
其中 Decls 成員表示的就是檔案中的頂級宣告。接下來我們主要是關注它的內容。
包宣告
Package: 2:1
Name: *ast.Ident {
. NamePos: 2:9
. Name: "main"
}
對應檔案中的 "package main"語句,記錄了語句的位置以及包名 (main) 字串的位置資訊。
引入宣告
0: *ast.GenDecl {
. TokPos: 4:1
. Tok: import
. Lparen: -
. Specs: []ast.Spec (len = 1) {
. . 0: *ast.ImportSpec {
. . . Path: *ast.BasicLit {
. . . . ValuePos: 4:8
. . . . Kind: STRING
. . . . Value: "\"fmt\""
. . . }
. . . EndPos: -
. . }
. }
. Rparen: -
}
在 ast.GenDecl 中記錄了 import 語句的位置資訊。Specs 為一個 ast.Spec 的陣列,記錄了每一個 import 的包名及位置資訊。
函式宣告
*ast.FuncDecl{
Name:*ast.Index{...}
Type:*ast.FuncType{
Params:*ast.FieldList{...}
Results: *ast.FieldList{...}
},
Body:*ast.BlockStmt{...}
}
- ast.FuncDecl 標明定義的是一個函式。
- Name 記錄函式名
- Type 是函式型別,其中 Params 表示引數資訊,這裡為空;Results 表示返回值資訊,這裡也為空。
- Body 則為函式體資訊。
表示式
List: []ast.Stmt (len = 1) {
. 0: *ast.ExprStmt {
. . X: *ast.CallExpr {
. . . Fun: *ast.SelectorExpr {
. . . . X: *ast.Ident {
. . . . . NamePos: 7:5
. . . . . Name: "fmt"
. . . . }
. . . . Sel: *ast.Ident {
. . . . . NamePos: 7:9
. . . . . Name: "Println"
. . . . }
. . . }
. . . Lparen: 7:16
. . . Args: []ast.Expr (len = 1
. . . . 0: *ast.BasicLit {
. . . . . ValuePos: 7:17
. . . . . Kind: STRING
. . . . . Value: "\"Hello Wor
. . . . }
. . . }
. . . Ellipsis: -
. . . Rparen: 7:31
. . }
. }
}
Rbrace: 8:1
函式體中的表示式描述了函式的內容,例子中的 fmt.Println("hello world")。
ast.CallExpr 表示函式呼叫,其中 SelectorExpr 描述了呼叫函式的包名及函式名,Args 則描述了引數資訊。
遍歷 AST 樹
ast 庫提供了可以深度優先遍歷 AST 的方法:func Inspect(node Node, f func(Node) bool)。 其中 node 為根節點,f 為處理節點的方法。
ast.Inspect(f, func(n ast.Node) bool {
var s string
switch x := n.(type) {
case *ast.BasicLit:
s = x.Value
case *ast.Ident:
s = x.Name
}
if s != "" {
fmt.Printf("%s:\t%s\n", fset.Position(n.Pos()), s)
}
return true
})
此函式遍歷 f(ast.File) 節點列印所有的識別符號和文字。
更多相關型別,可以通過命令 go doc ast |grep "^type .* struct"
檢視。
進階使用
利用 AST 對 go 檔案進行分析,我們可以實現程式碼的自動生成,其中包括以下幾個常見使用領域:
- 程式碼注入: wire 使用 AST 實現建構函式程式碼生成。
- DeepCopy: 結合 AST 生成結構體的深拷貝函式程式碼
- 物件賦值: 在領域程式設計中,常常需要在不同的領域物件中進行資料轉換,利用 AST 的解析結果,可以自動生成指定領域物件間的轉換函式檔案。
小結
抽象語法樹的生成屬於程式編譯流程中的一員,利用 AST 及其相關庫提供到方法,我們可以很方便的解析一個 go 檔案,把檔案內容結構化,以便做進一步的分析和使用。AST 廣泛應用於程式碼自動生成的功能中,例如go generate
命令,wire 工具等等。其中不少企業也會在開源庫中,使用 Comment 的特殊格式,來自定義框架程式碼的自動生成命令。
參考連線
https://www.cnblogs.com/double12gzh/p/13632267.html https://cloud.tencent.com/developer/section/1142075
實驗工具
https://greyireland.gitee.io/goast-viewer
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- AST抽象語法樹AST抽象語法樹
- Go編譯原理系列5(抽象語法樹構建)Go編譯原理抽象語法樹
- Javascript與抽象語法樹JavaScript抽象語法樹
- Javascrip—AST抽象語法樹(8)JavaAST抽象語法樹
- 抽象語法樹 Abstract syntax tree抽象語法樹
- 「譯」什麼是抽象語法樹抽象語法樹
- 前端進階之 JS 抽象語法樹前端JS抽象語法樹
- 前端進階之 Javascript 抽象語法樹前端JavaScript抽象語法樹
- babel外掛入門-AST(抽象語法樹)BabelAST抽象語法樹
- 13 個示例快速入門 JS 抽象語法樹JS抽象語法樹
- 前端碼農之蛻變 — AST(抽象語法樹)前端AST抽象語法樹
- 高階前端基礎-JavaScript抽象語法樹AST前端JavaScript抽象語法樹AST
- babel 修改抽象語法樹——入門與實踐Babel抽象語法樹
- 以 Golang 為例詳解 AST 抽象語法樹GolangAST抽象語法樹
- 從Babel開始認識AST抽象語法樹BabelAST抽象語法樹
- SQL 抽象語法樹及改寫場景應用SQL抽象語法樹
- SQL抽象語法樹及改寫場景應用SQL抽象語法樹
- [原始碼-webpack01-前置知識] AST抽象語法樹原始碼WebAST抽象語法樹
- JavaScript的工作原理:解析、抽象語法樹(AST)+ 提升編譯速度5個技巧JavaScript抽象語法樹AST編譯
- 通過開發 Babel 外掛來理解什麼是抽象語法樹(AST)Babel抽象語法樹AST
- 【譯】Go語言宣告語法Go
- go 基礎語法Go
- 《8分鐘學會 Vue.js 原理》:一、template 字串編譯為抽象語法樹 ASTVue.js字串編譯抽象語法樹AST
- go 語法快速入門Go
- Go 語言的詞法分析和語法分析(1)Go詞法分析語法分析
- Go語言基礎語法總結Go
- JavaScript 工作原理之十四-解析,語法抽象樹及最小化解析時間的 5 條小技巧JavaScript抽象
- go 奇葩語法總結篇Go
- vim設定go語法高亮Go
- Go 學習筆記 - Go 基礎語法(2)Go筆記
- Go學習筆記 - Go 基礎語法(1)Go筆記
- go 模板(template)的常用基本語法Go
- 從零開始——GO語言基礎語法Go
- 小白學習Golang(三)Go語言基本語法Golang
- Go 語言的詞法分析和語法分析(2)—Import宣告的解析Go詞法分析語法分析Import
- go 學習記錄--基礎語法Go
- Go編譯原理系列4(語法分析)Go編譯原理語法分析
- 帶讀 |《Go in Action》(中文:Go語言實戰) 語法和語言結構概覽(三)Go