什么是AST
首先我们要知道AST是什么(Abstract Syntax Tree,AST),简称为语法树,是go语言源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
上demo代码
go
func main() {
expr, _ := parser.ParseExpr("1 + 2 * (3 + 4) + 3 * 4.1")
ast.Print(token.NewFileSet(), expr)
}
// 输出结果
0 *ast.BinaryExpr {
1 . X: *ast.BinaryExpr {
2 . . X: *ast.BasicLit {
3 . . . ValuePos: -
4 . . . Kind: INT
5 . . . Value: "1"
6 . . }
7 . . OpPos: -
8 . . Op: +
9 . . Y: *ast.BinaryExpr {
10 . . . X: *ast.BasicLit {
11 . . . . ValuePos: -
12 . . . . Kind: INT
13 . . . . Value: "2"
14 . . . }
15 . . . OpPos: -
16 . . . Op: *
17 . . . Y: *ast.ParenExpr {
18 . . . . Lparen: -
19 . . . . X: *ast.BinaryExpr {
20 . . . . . X: *ast.BasicLit {
21 . . . . . . ValuePos: -
22 . . . . . . Kind: INT
23 . . . . . . Value: "3"
24 . . . . . }
25 . . . . . OpPos: -
26 . . . . . Op: +
27 . . . . . Y: *ast.BasicLit {
28 . . . . . . ValuePos: -
29 . . . . . . Kind: INT
30 . . . . . . Value: "4"
31 . . . . . }
32 . . . . }
33 . . . . Rparen: -
34 . . . }
35 . . }
36 . }
37 . OpPos: -
38 . Op: +
39 . Y: *ast.BinaryExpr {
40 . . X: *ast.BasicLit {
41 . . . ValuePos: -
42 . . . Kind: INT
43 . . . Value: "3"
44 . . }
45 . . OpPos: -
46 . . Op: *
47 . . Y: *ast.BasicLit {
48 . . . ValuePos: -
49 . . . Kind: FLOAT
50 . . . Value: "4.1"
51 . . }
52 . }
53 }
我们通过上面的代码输出了一砣子非常复杂的结构体,那么我们来看看这些都是些上面东西。
我们来看看源码
go
// A BinaryExpr node represents a binary expression.
type BinaryExpr struct {
X Expr // left operand
OpPos token.Pos // position of Op
Op token.Token // operator
Y Expr // right operand
}
我们通过这个结构体就可以看出来X和Y分别是两个值,Op是存储的我们的操作符号,OpPos 代表操作符在表达式中的偏移,这里的 Op 类型是token.Token,实际上是个枚举值。
go
// The list of tokens.
const (
// Special tokens
ILLEGAL Token = iota
EOF
COMMENT
literal_beg
// Identifiers and basic type literals
// (these tokens stand for classes of literals)
IDENT // main
INT // 12345
FLOAT // 123.45
IMAG // 123.45i
CHAR // 'a'
STRING // "abc"
literal_end
operator_beg
// Operators and delimiters
ADD // +
SUB // -
MUL // *
QUO // /
REM // %
AND // &
OR // |
XOR // ^
SHL // <<
SHR // >>
AND_NOT // &^
ADD_ASSIGN // +=
SUB_ASSIGN // -=
MUL_ASSIGN // *=
QUO_ASSIGN // /=
REM_ASSIGN // %=
AND_ASSIGN // &=
OR_ASSIGN // |=
XOR_ASSIGN // ^=
SHL_ASSIGN // <<=
SHR_ASSIGN // >>=
AND_NOT_ASSIGN // &^=
LAND // &&
LOR // ||
ARROW // <-
INC // ++
DEC // --
EQL // ==
LSS // <
GTR // >
ASSIGN // =
NOT // !
NEQ // !=
LEQ // <=
GEQ // >=
DEFINE // :=
ELLIPSIS // ...
LPAREN // (
LBRACK // [
LBRACE // {
COMMA // ,
PERIOD // .
RPAREN // )
RBRACK // ]
RBRACE // }
SEMICOLON // ;
COLON // :
operator_end
keyword_beg
// Keywords
BREAK
CASE
CHAN
CONST
CONTINUE
DEFAULT
DEFER
ELSE
FALLTHROUGH
FOR
FUNC
GO
GOTO
IF
IMPORT
INTERFACE
MAP
PACKAGE
RANGE
RETURN
SELECT
STRUCT
SWITCH
TYPE
VAR
keyword_end
additional_beg
// additional tokens, handled in an ad-hoc manner
TILDE
additional_end
)
那么token 连存储的就是这些, 他会根据我们输入的表达式,给去OpPos解析匹配这些符号。
我们可以从上图可以看出ast 代码树,因为表达式中有() ,我们发现了一个新的结构体ParenExpr ,我们查看源码,发现这个结构体是专门用于计算时如果有()时使用的。我们通过该树就可以看出来计算的先后顺序,就完美的完成了一个计算器功能。
go
// A ParenExpr node represents a parenthesized expression.
type ParenExpr struct {
Lparen token.Pos // position of "("
X Expr // parenthesized expression
Rparen token.Pos // position of ")"
}
我们了解了ast代码树以后,我们就可以借助该解析代码树做很多东西,例如我们可以做一个计算器。
go
type AstParser struct {
ExpStr string
}
func NewAstParser(expStr string) *AstParser {
return &AstParser{ExpStr: expStr}
}
func (a *AstParser) Parse() (float64, error) {
expr, err := parser.ParseExpr(a.ExpStr)
if err != nil {
return 0, err
}
val, err := a.Eval(expr)
if err != nil {
return 0, err
}
return val, nil
}
func (a *AstParser) Eval(expr ast.Expr) (float64, error) {
switch e := expr.(type) {
case *ast.BinaryExpr:
return a.EvalBinaryExpr(e)
case *ast.ParenExpr:
return a.Eval(e.X)
case *ast.BasicLit:
switch e.Kind {
case token.INT, token.FLOAT:
i, err := strconv.ParseFloat(e.Value, 64)
if err != nil {
return 0, err
}
return i, nil
}
}
return 0, nil
}
func (a *AstParser) EvalBinaryExpr(e *ast.BinaryExpr) (float64, error) {
left, err := a.Eval(e.X)
if err != nil {
return 0, err
}
right, err := a.Eval(e.Y)
if err != nil {
return 0, err
}
switch e.Op {
case token.ADD:
return left + right, nil
case token.SUB:
return left - right, nil
case token.MUL:
return left * right, nil
case token.QUO:
return left / right, nil
}
return 0.0, nil
}
总结:
我们了解了ast代码树后,利用它可以做成一个非常简单的规则引擎。
其实利用 ast 包还可以做更多有意思的事情。例如将文件转成proto,或者解析sql语句并做一些审计等等