「GoCN酷Go推薦」抽象語(yǔ)法樹(shù)go/ast庫(kù)使用
推薦背景
Go語(yǔ)言在編譯過(guò)程中經(jīng)過(guò)詞法分析和語(yǔ)法分析之后,就到了抽象語(yǔ)法樹(shù)的構(gòu)建階段,經(jīng)歷這一階段之后,語(yǔ)句就真正組織成了程序代碼。利用抽象語(yǔ)法樹(shù)解析庫(kù),我們可以完成代碼的自動(dòng)化分析和自動(dòng)化生成,因此通常用于做一些自動(dòng)化的工具,例如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的第二參數(shù)為文件名,第三參數(shù)為字符串,前者是待解析的文件路徑,后者為待解析的字符串。前者優(yōu)先級(jí)高于后者。第四參數(shù)為解析模式(可以使用"|"來(lái)結(jié)合多種解析模式)
運(yùn)行結(jié)果如下
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內(nèi)容
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成員表示的就是文件中的頂級(jí)聲明。接下來(lái)我們主要是關(guān)注它的內(nèi)容。
包聲明
??Package:?2:1
??Name:?*ast.Ident?{
??.??NamePos:?2:9
??.??Name:?"main"
??}
對(duì)應(yīng)文件中的 "package main"語(yǔ)句,記錄了語(yǔ)句的位置以及包名(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語(yǔ)句的位置信息。Specs為一個(gè)ast.Spec的數(shù)組,記錄了每一個(gè)import的包名及位置信息。
函數(shù)聲明
?*ast.FuncDecl{
??Name:*ast.Index{...}?
??Type:*ast.FuncType{
???Params:*ast.FieldList{...}
???Results:?*ast.FieldList{...}
??},
??Body:*ast.BlockStmt{...}
?}
ast.FuncDecl標(biāo)明定義的是一個(gè)函數(shù)。 Name記錄函數(shù)名 Type是函數(shù)類(lèi)型,其中Params表示參數(shù)信息,這里為空;Results表示返回值信息,這里也為空。 Body則為函數(shù)體信息。
表達(dá)式
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
函數(shù)體中的表達(dá)式描述了函數(shù)的內(nèi)容,例子中的fmt.Println("hello world")。
ast.CallExpr表示函數(shù)調(diào)用,其中SelectorExpr描述了調(diào)用函數(shù)的包名及函數(shù)名,Args則描述了參數(shù)信息。
遍歷AST樹(shù)
ast庫(kù)提供了可以深度優(yōu)先遍歷AST的方法:func Inspect(node Node, f func(Node) bool)。其中node為根節(jié)點(diǎn),f為處理節(jié)點(diǎn)的方法。
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
?})
此函數(shù)遍歷f(ast.File)節(jié)點(diǎn)打印所有的標(biāo)識(shí)符和文字。
更多相關(guān)類(lèi)型,可以通過(guò)命令 go doc ast |grep "^type .* struct"查看。
進(jìn)階使用
利用AST對(duì)go文件進(jìn)行分析,我們可以實(shí)現(xiàn)代碼的自動(dòng)生成,其中包括以下幾個(gè)常見(jiàn)使用領(lǐng)域:
代碼注入: wire使用AST實(shí)現(xiàn)構(gòu)造函數(shù)代碼生成。 DeepCopy: 結(jié)合AST生成結(jié)構(gòu)體的深拷貝函數(shù)代碼 對(duì)象賦值: 在領(lǐng)域編程中,常常需要在不同的領(lǐng)域?qū)ο笾羞M(jìn)行數(shù)據(jù)轉(zhuǎn)換,利用AST的解析結(jié)果,可以自動(dòng)生成指定領(lǐng)域?qū)ο箝g的轉(zhuǎn)換函數(shù)文件。
小結(jié)
抽象語(yǔ)法樹(shù)的生成屬于程序編譯流程中的一員,利用AST及其相關(guān)庫(kù)提供到方法,我們可以很方便的解析一個(gè)go文件,把文件內(nèi)容結(jié)構(gòu)化,以便做進(jìn)一步的分析和使用。AST廣泛應(yīng)用于代碼自動(dòng)生成的功能中,例如go generate命令,wire工具等等。其中不少企業(yè)也會(huì)在開(kāi)源庫(kù)中,使用Comment的特殊格式,來(lái)自定義框架代碼的自動(dòng)生成命令。
參考資料
https://www.cnblogs.com/double12gzh/p/13632267.html https://cloud.tencent.com/developer/section/1142075
實(shí)驗(yàn)工具
https://astexplorer.net
https://greyireland.gitee.io/goast-viewer
《酷Go推薦》招募:
各位Gopher同學(xué),最近我們社區(qū)打算推出一個(gè)類(lèi)似GoCN每日新聞的新欄目《酷Go推薦》,主要是每周推薦一個(gè)庫(kù)或者好的項(xiàng)目,然后寫(xiě)一點(diǎn)這個(gè)庫(kù)使用方法或者優(yōu)點(diǎn)之類(lèi)的,這樣可以真正的幫助到大家能夠?qū)W習(xí)到
新的庫(kù),并且知道怎么用。
大概規(guī)則和每日新聞?lì)愃疲绻麍?bào)名人多的話每個(gè)人一個(gè)月輪到一次,歡迎大家報(bào)名!戳「閱讀原文」,即可報(bào)名
掃碼也可以加入 GoCN 的大家族喲~
