温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Go语言Ast的含义和作用是什么

发布时间:2021-06-16 10:46:59 来源:亿速云 阅读:825 作者:chen 栏目:编程语言

这篇文章主要介绍“Go语言Ast的含义和作用是什么 ”,在日常操作中,相信很多人在Go语言Ast的含义和作用是什么 问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Go语言Ast的含义和作用是什么 ”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

什么是 ast 呢,我从维基百科上摘录了一段:

在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax  tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。

核心就是说 ast 能以一种树的形式表示代码结构。有了树结构,就可以对它做遍历,能干很多事。

假定一个场景

假定一个场景:我们可以从司机平台的某个接口获取司机的各种特征,例如:年龄、订单数、收入、每天驾驶时长、驾龄、平均车速、被投诉次数……数据一般采用 json  来传递。

司机平台的运营小姐姐经常需要搞一些活动,例如选出:

  • 订单数超过 10000,且驾龄超过 5 年的老司机

  • 每天驾驶时小于 3 小时,且收入超过 500 的高效司机

  • 年龄大于 40,且平均速度大于 70 的“狂野”司机

  • ……

这些规则并不是固定的,经常在变化,但总归是各种司机特征的组合。

为了简化,我们选取 2 个特征,并用一个 Driver 结构体来表示:

type Driver struct {  Orders         int  DrivingYears   int }

为了配合运营搞活动,我们需要根据运营给的规则来判断一个司机是否符合要求。

如果公司人多,可以安排一个 rd 专门伺候运营小姐姐,每次做活动都来手动修改代码,也不是不可以。并且其实挺简单,我们来写一个示例代码:

// 从第三方获取司机特征,json 表示 func getDriverRemote() []byte {  return []byte(`{"orders":100000,"driving_years":18}`) }  // 判断是否为老司机 func isOldDriver(d *Driver) bool {  if d.Orders > 10000 && d.DrivingYears > 5 {   return true  }  return false }  func main() {  bs := getDriverRemote()  var d Driver  json.Unmarshal(bs, &d)  fmt.Println(isOldDriver(&d)) }

直接来看 main 函数:getDriverRemote 模拟从第三方 RPC 获取一个司机的特征数据,用 json 表示。接着  json.Unmarshal 来反序列化 Driver 结构体。最后调用 isOldDriver 函数来判断此司机是否符合运营的规则。

isOldDriver 根据 Driver 结构体的 2 个字段使用 if 语句来判断此司机是否为老司机。

确实还挺简单。

但是每次更新规则还得经过一次完整的上线流程,也挺麻烦的。有没有更简单的办法呢?使得我们可以直接解析运营小组姐给我们的一个用字符串表示的规则,并直接返回一个  bool 型的值,表示是否满足条件。

有的!

接下来就是本文的核心内容,如何使用 ast 来完成同样的功能。

直观地理解如何用 ast 解析规则

使用 ast 包提供的一些函数,我们可以非常方便地将如下的规则字符串:

orders > 10000 && driving_years > 5

解析成一棵这样的二叉树:

Go语言Ast的含义和作用是什么

规则二叉树

其中,ast.BinaryExpr 代表一个二元表达式,它由 X 和 Y 以及符号 OP 三部分组成。最上面的一个 BinaryExpr  表示规则的左半部分和右半部分相与。

很明显,左半部分就是:orders > 10000,而右半部分则是:driving_years >  5。神奇的是,左半部分和右半部分恰好又都是一个二元表达式。

左半部分的 orders > 10000 其实也是最小的叶子节点,它可以算出来一个 bool 值。把它拆开来之后,又可以分成 X、Y、OP。X 是  orders,OP 是 ">",Y 则是 "10000"。其中 X 表示一个标识符,是 ast.Ident 类型,Y 表示一个基本类型的字面量,例如  int 型、字符串型……是 ast.BasicLit 类型。

右半部分的 driving_years > 18 也可以照此拆分。

然后,从 json 中取出这个司机的 orders 字段的值为 100000,它比 10000 大,所以左半部分算出来为  true。同理,右半部分算出来也为 true。最后,再算最外层的 "&&",结果仍然为 true。

至此,直接根据规则字符串,我们就可以算出来结果。

如果写成程序的话,就是一个 dfs 的遍历过程。如果不是叶子结点,那就是二元表达式结点,那就一定有 X、Y、OP 部分。递归地遍历 X,如果 X  是叶子结点,那就结束递归,并计算出 X 的值。

这里再展示一个用 ast 包打印出来的抽象语法树:

Go语言Ast的含义和作用是什么

Go 打印 ast

上图中,1、2、3 表示最外层的二元表达式;4、5、6 则表示左边这个二元表达式。

结合这张图,再参考 ast 包的相关结构体 代码,就非常清晰了。例如 ast.BinaryExpr 的代码如下:

// A BinaryExpr node represents a binary expression. BinaryExpr struct {  X     Expr        // left operand  OpPos token.Pos   // position of Op  Op    token.Token // operator  Y     Expr        // right operand }

它有 X、Y、OP,甚至还解析出了 Op 的位置,用 OpPos 表示。

如果你还对实现感兴趣,那就继续看下面的原理分析部分,否则可以直接跳到结尾总结部分。

原理分析

还是用上面那个例子,我们直接写一个表达式:

orders > 10000 && driving_years > 5

接下来用 ast 来解析规则并判断真假。

func main() {  m := map[string]int64{"orders": 100000, "driving_years": 18}  rule := `orders > 10000 && driving_years > 5`  fmt.Println(Eval(m, rule)) }

为了简单,我们直接用 map 来代替 json,道理是一样的,仅仅为了方便。

Eval 函数判断 rule 的真假:

// Eval : 计算 expr 的值 func Eval(m map[string]int64, expr string) (bool, error) {  exprAst, err := parser.ParseExpr(expr)  if err != nil {   return false, err  }   // 打印 ast  fset := token.NewFileSet()  ast.Print(fset, exprAst)   return judge(exprAst, m), nil }

先将表达式解析成 Expr,接着调用 judge 函数计算结果:

// dfs func judge(bop ast.Node, m map[string]int64) bool {     // 叶子结点  if isLeaf(bop) {   // 断言成二元表达式   expr := bop.(*ast.BinaryExpr)   x := expr.X.(*ast.Ident) // 左边   y := expr.Y.(*ast.BasicLit) // 右边    // 如果是 ">" 符号   if expr.Op == token.GTR {    left := m[x.Name]    right, _ := strconv.ParseInt(y.Value, 10, 64)    return left > right   }   return false  }   // 不是叶子节点那么一定是 binary expression(我们目前只处理二元表达式)  expr, ok := bop.(*ast.BinaryExpr)  if !ok {   println("this cannot be true")   return false  }   // 递归地计算左节点和右节点的值  switch expr.Op {  case token.LAND:   return judge(expr.X, m) && judge(expr.Y, m)  case token.LOR:   return judge(expr.X, m) || judge(expr.Y, m)  }   println("unsupported operator")  return false }

judge 使用 dfs 递归地计算表达式的值。

递归地终止条件是叶子节点:

// 判断是否是叶子节点 func isLeaf(bop ast.Node) bool {  expr, ok := bop.(*ast.BinaryExpr)  if !ok {   return false  }   // 二元表达式的最小单位,左节点是标识符,右节点是值  _, okL := expr.X.(*ast.Ident)  _, okR := expr.Y.(*ast.BasicLit)  if okL && okR {   return true  }   return false }

总结

今天这篇文章主要讲了如何用 ast 包和 parser 包解析一个二元表达式,并见识到了它的威力,利用它可以做成一个非常简单的规则引擎。

其实利用 ast 包还可以做更多有意思的事情。例如批量把 thrift 文件转化成 proto 文件、解析 sql 语句并做一些审计。

到此,关于“Go语言Ast的含义和作用是什么 ”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI