go ast语义分析实现指标计算器

什么是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语句并做一些审计等等

相关推荐
fasewer4 分钟前
第五章 linux实战-挖矿 二
linux·运维·服务器
楚灵魈30 分钟前
[Linux]从零开始的网站搭建教程
linux·运维·服务器
小小不董33 分钟前
《Linux从小白到高手》理论篇:深入理解Linux的网络管理
linux·运维·服务器·数据库·php·dba
豆豆1 小时前
为什么用PageAdmin CMS建设网站?
服务器·开发语言·前端·php·软件构建
sukalot1 小时前
windows C++-使用任务和 XML HTTP 请求进行连接(一)
c++·windows
DY009J1 小时前
深度探索Kali Linux的精髓与实践应用
linux·运维·服务器
ぃ扶摇ぅ2 小时前
Windows系统编程(三)进程与线程二
c++·windows
什么鬼昵称2 小时前
Pikachu- Over Permission-垂直越权
运维·服务器
码农小白2 小时前
linux驱动:(22)中断节点和中断函数
linux·运维·服务器
虽千万人 吾往矣3 小时前
golang gorm
开发语言·数据库·后端·tcp/ip·golang