Go抽象语法树(AST)实用指南 (一)
源代码/数据集已上传到
Github - posts
AST抽象语法树在平时开发一般不太能用到。较多使用场景为代码生成,代码动态解析等等。
抽象到具体场景
本文以另一个使用场景呈现AST的应用场景。
Java下枚举
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package com.demo.risk.demo;
import lombok.NoArgsConstructor;
@NoArgsConstructor public enum Status {
StatusProcessing(1, "流程中"), StatusPass(2, "通过"), StatusReject(3, "拒绝");
public int Id; public String Name;
Status(int id, String name) { this.Id = id; this.Name = name; } }
|
以上代码定义了三个枚举变量,可以很方便地获取到枚举内的id和name
1 2 3 4 5
| public void Demo() { Status process = Status.StatusProcessing; int id = process.Id; String name = process.Name; }
|
Go常规枚举定义与实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const ( StatusBeg = iota StatusProcessing StatusPass StatusReject StatusEnd )
var StatusName = map[int]string{ StatusBeg: "Unknown", StatusProcessing: "流程中", StatusPass: "通过", StatusReject: "拒绝", StatusEnd: "Unknown", }
|
如果想要实现则需要去手写一套这样的代码,去实现功能。
实际上以上的功能,通过AST可以非常简单地解决掉这个问题。
可能实现的方式不会有太大的变化,但是通过AST可以通过解析注释,动态地解析生成对应的代码。
实现原理
1 2 3 4 5 6 7 8 9 10
| package main
const ( StatusBeg = 0 StatusProcessing = 1 StatusPass = 2 StatusReject = 3 StatusEnd = 4 )
|
有上面一段代码,用来定义状态流转枚举。
通过AST抽象语法树解析得到如下结果:
(下面代码块中//#
后的为作者注释)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
| 0 *ast.File { 1 . Package: code.go:1:1 2 . Name: *ast.Ident { 3 . . NamePos: code.go:1:9 4 . . Name: "main" 5 . } 6 . Decls: []ast.Decl (len = 1) { 7 . . 0: *ast.GenDecl { 8 . . . Doc: *ast.CommentGroup { 9 . . . . List: []*ast.Comment (len = 1) { 10 . . . . . 0: *ast.Comment { 11 . . . . . . Slash: code.go:3:1 12 . . . . . . Text: "//go:generate gen-const-status" 13 . . . . . } 14 . . . . } 15 . . . } 16 . . . TokPos: code.go:4:1 17 . . . Tok: const 18 . . . Lparen: code.go:4:7 19 . . . Specs: []ast.Spec (len = 5) { 20 . . . . 0: *ast.ValueSpec { 21 . . . . . Names: []*ast.Ident (len = 1) { 22 . . . . . . 0: *ast.Ident { 23 . . . . . . . NamePos: code.go:5:2 24 . . . . . . . Name: "StatusBeg" 25 . . . . . . . Obj: *ast.Object { 26 . . . . . . . . Kind: const 27 . . . . . . . . Name: "StatusBeg" 28 . . . . . . . . Decl: *(obj @ 20) 29 . . . . . . . . Data: 0 30 . . . . . . . } 31 . . . . . . } 32 . . . . . } 33 . . . . . Values: []ast.Expr (len = 1) { 34 . . . . . . 0: *ast.BasicLit { 35 . . . . . . . ValuePos: code.go:5:21 36 . . . . . . . Kind: INT 37 . . . . . . . Value: "0" 38 . . . . . . } 39 . . . . . } 40 . . . . . Comment: *ast.CommentGroup { 41 . . . . . . List: []*ast.Comment (len = 1) { 42 . . . . . . . 0: *ast.Comment { 43 . . . . . . . . Slash: code.go:5:23 44 . . . . . . . . Text: "// Unknown" 45 . . . . . . . } 46 . . . . . . } 47 . . . . . } 48 . . . . } 49 . . . . 1: *ast.ValueSpec { 50 . . . . . Names: []*ast.Ident (len = 1) { 51 . . . . . . 0: *ast.Ident { 52 . . . . . . . NamePos: code.go:6:2 53 . . . . . . . Name: "StatusProcessing" 54 . . . . . . . Obj: *ast.Object { 55 . . . . . . . . Kind: const 56 . . . . . . . . Name: "StatusProcessing" 57 . . . . . . . . Decl: *(obj @ 49) 58 . . . . . . . . Data: 1 59 . . . . . . . } 60 . . . . . . } 61 . . . . . } 62 . . . . . Values: []ast.Expr (len = 1) { 63 . . . . . . 0: *ast.BasicLit { 64 . . . . . . . ValuePos: code.go:6:21 65 . . . . . . . Kind: INT 66 . . . . . . . Value: "1" 67 . . . . . . } 68 . . . . . } 69 . . . . . Comment: *ast.CommentGroup { 70 . . . . . . List: []*ast.Comment (len = 1) { 71 . . . . . . . 0: *ast.Comment { 72 . . . . . . . . Slash: code.go:6:23 73 . . . . . . . . Text: "// 流程中" 74 . . . . . . . } 75 . . . . . . } 76 . . . . . } 77 . . . . } 78 . . . . 2: *ast.ValueSpec { 79 . . . . . Names: []*ast.Ident (len = 1) { 80 . . . . . . 0: *ast.Ident { 81 . . . . . . . NamePos: code.go:7:2 82 . . . . . . . Name: "StatusPass" 83 . . . . . . . Obj: *ast.Object { 84 . . . . . . . . Kind: const 85 . . . . . . . . Name: "StatusPass" 86 . . . . . . . . Decl: *(obj @ 78) 87 . . . . . . . . Data: 2 88 . . . . . . . } 89 . . . . . . } 90 . . . . . } 91 . . . . . Values: []ast.Expr (len = 1) { 92 . . . . . . 0: *ast.BasicLit { 93 . . . . . . . ValuePos: code.go:7:21 94 . . . . . . . Kind: INT 95 . . . . . . . Value: "2" 96 . . . . . . } 97 . . . . . } 98 . . . . . Comment: *ast.CommentGroup { 99 . . . . . . List: []*ast.Comment (len = 1) { 100 . . . . . . . 0: *ast.Comment { 101 . . . . . . . . Slash: code.go:7:23 102 . . . . . . . . Text: "// 通过" 103 . . . . . . . } 104 . . . . . . } 105 . . . . . } 106 . . . . } 107 . . . . 3: *ast.ValueSpec { 108 . . . . . Names: []*ast.Ident (len = 1) { 109 . . . . . . 0: *ast.Ident { 110 . . . . . . . NamePos: code.go:8:2 111 . . . . . . . Name: "StatusReject" 112 . . . . . . . Obj: *ast.Object { 113 . . . . . . . . Kind: const 114 . . . . . . . . Name: "StatusReject" 115 . . . . . . . . Decl: *(obj @ 107) 116 . . . . . . . . Data: 3 117 . . . . . . . } 118 . . . . . . } 119 . . . . . } 120 . . . . . Values: []ast.Expr (len = 1) { 121 . . . . . . 0: *ast.BasicLit { 122 . . . . . . . ValuePos: code.go:8:21 123 . . . . . . . Kind: INT 124 . . . . . . . Value: "3" 125 . . . . . . } 126 . . . . . } 127 . . . . . Comment: *ast.CommentGroup { 128 . . . . . . List: []*ast.Comment (len = 1) { 129 . . . . . . . 0: *ast.Comment { 130 . . . . . . . . Slash: code.go:8:23 131 . . . . . . . . Text: "// 拒绝" 132 . . . . . . . } 133 . . . . . . } 134 . . . . . } 135 . . . . } 136 . . . . 4: *ast.ValueSpec { 137 . . . . . Names: []*ast.Ident (len = 1) { 138 . . . . . . 0: *ast.Ident { 139 . . . . . . . NamePos: code.go:9:2 140 . . . . . . . Name: "StatusEnd" 141 . . . . . . . Obj: *ast.Object { 142 . . . . . . . . Kind: const 143 . . . . . . . . Name: "StatusEnd" 144 . . . . . . . . Decl: *(obj @ 136) 145 . . . . . . . . Data: 4 146 . . . . . . . } 147 . . . . . . } 148 . . . . . } 149 . . . . . Values: []ast.Expr (len = 1) { 150 . . . . . . 0: *ast.BasicLit { 151 . . . . . . . ValuePos: code.go:9:21 152 . . . . . . . Kind: INT 153 . . . . . . . Value: "4" 154 . . . . . . } 155 . . . . . } 156 . . . . . Comment: *ast.CommentGroup { 157 . . . . . . List: []*ast.Comment (len = 1) { 158 . . . . . . . 0: *ast.Comment { 159 . . . . . . . . Slash: code.go:9:23 160 . . . . . . . . Text: "// Unknown" 161 . . . . . . . } 162 . . . . . . } 163 . . . . . } 164 . . . . } 165 . . . } 166 . . . Rparen: code.go:10:1 167 . . } 168 . } 169 . Scope: *ast.Scope { 170 . . Objects: map[string]*ast.Object (len = 5) { 171 . . . "StatusBeg": *(obj @ 25) 172 . . . "StatusProcessing": *(obj @ 54) 173 . . . "StatusPass": *(obj @ 83) 174 . . . "StatusReject": *(obj @ 112) 175 . . . "StatusEnd": *(obj @ 141) 176 . . } 177 . } 178 . Comments: []*ast.CommentGroup (len = 6) { 179 . . 0: *(obj @ 8) 180 . . 1: *(obj @ 40) 181 . . 2: *(obj @ 69) 182 . . 3: *(obj @ 98) 183 . . 4: *(obj @ 127) 184 . . 5: *(obj @ 156) 185 . } 186 }
|
简单实现通过解析树解析定义的数据
通过抽象解析树,可以非常清晰地知道代码的整体结构。也可以通过解析抽象树来实现常量值与常量注释的映射。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package main
import ( "fmt" "go/ast" "go/parser" "go/token" "strings" )
func main() { t := token.NewFileSet() f, err := parser.ParseFile(t, "code.go", nil, parser.ParseComments) if err != nil { panic(err) } for _, object := range f.Scope.Objects { obj := object.Decl.(*ast.ValueSpec) constVal := obj.Values[0].(*ast.BasicLit) fmt.Printf("常量名:%s, 常量值:%s, 常量类型:%s, 常量注释:%s\n", obj.Names, constVal.Value, constVal.Kind.String(), strings.Trim(obj.Comment.Text(), " \n\t")) }
}
|
扩展
可以试试看以下的数据定义,尝试是否可以通过ast解析出来
1 2 3 4 5 6 7 8 9 10
| package main
const ( StatusBeg = iota StatusProcessing StatusPass StatusReject StatusEnd )
|
edit this page
last updated at 2024-04-22