go语言的ast有什么作用-mile米乐体育
go语言的ast有什么作用
本篇内容介绍了“go语言的ast有什么作用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
什么是 ast 呢,我从维基百科上摘录了一段:
在计算机科学中,抽象语法树(abstract syntax tree,ast),或简称语法树(syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
核心就是说 ast 能以一种树的形式表示代码结构。有了树结构,就可以对它做遍历,能干很多事。
假定一个场景
假定一个场景:我们可以从司机平台的某个接口获取司机的各种特征,例如:年龄、订单数、收入、每天驾驶时长、驾龄、平均车速、被投诉次数……数据一般采用 json 来传递。
司机平台的运营小姐姐经常需要搞一些活动,例如选出:
订单数超过 10000,且驾龄超过 5 年的老司机
每天驾驶时小于 3 小时,且收入超过 500 的高效司机
年龄大于 40,且平均速度大于 70 的“狂野”司机
……
这些规则并不是固定的,经常在变化,但总归是各种司机特征的组合。
为了简化,我们选取 2 个特征,并用一个 driver 结构体来表示:
typedriverstruct{ordersintdrivingyearsint}
为了配合运营搞活动,我们需要根据运营给的规则来判断一个司机是否符合要求。
如果公司人多,可以安排一个 rd 专门伺候运营小姐姐,每次做活动都来手动修改代码,也不是不可以。并且其实挺简单,我们来写一个示例代码:
//从第三方获取司机特征,json表示funcgetdriverremote()[]byte{return[]byte(`{"orders":100000,"driving_years":18}`)}//判断是否为老司机funcisolddriver(d*driver)bool{ifd.orders>10000&&d.drivingyears>5{returntrue}returnfalse}funcmain(){bs:=getdriverremote()varddriverjson.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
解析成一棵这样的二叉树:
规则二叉树
其中,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
上图中,1、2、3 表示最外层的二元表达式;4、5、6 则表示左边这个二元表达式。
结合这张图,再参考 ast 包的相关结构体 代码,就非常清晰了。例如 ast.binaryexpr 的代码如下:
//abinaryexprnoderepresentsabinaryexpression.binaryexprstruct{xexpr//leftoperandoppostoken.pos//positionofopoptoken.token//operatoryexpr//rightoperand}
它有 x、y、op,甚至还解析出了 op 的位置,用 oppos 表示。
如果你还对实现感兴趣,那就继续看下面的原理分析部分,否则可以直接跳到结尾总结部分。
原理分析
还是用上面那个例子,我们直接写一个表达式:
orders>10000&&driving_years>5
接下来用 ast 来解析规则并判断真假。
funcmain(){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的值funceval(mmap[string]int64,exprstring)(bool,error){exprast,err:=parser.parseexpr(expr)iferr!=nil{returnfalse,err}//打印astfset:=token.newfileset()ast.print(fset,exprast)returnjudge(exprast,m),nil}
先将表达式解析成 expr,接着调用 judge 函数计算结果:
//dfsfuncjudge(bopast.node,mmap[string]int64)bool{//叶子结点ifisleaf(bop){//断言成二元表达式expr:=bop.(*ast.binaryexpr)x:=expr.x.(*ast.ident)//左边y:=expr.y.(*ast.basiclit)//右边//如果是">"符号ifexpr.op==token.gtr{left:=m[x.name]right,_:=strconv.parseint(y.value,10,64)returnleft>right}returnfalse}//不是叶子节点那么一定是binaryexpression(我们目前只处理二元表达式)expr,ok:=bop.(*ast.binaryexpr)if!ok{println("thiscannotbetrue")returnfalse}//递归地计算左节点和右节点的值switchexpr.op{casetoken.land:returnjudge(expr.x,m)&&judge(expr.y,m)casetoken.lor:returnjudge(expr.x,m)||judge(expr.y,m)}println("unsupportedoperator")returnfalse}
judge 使用 dfs 递归地计算表达式的值。
递归地终止条件是叶子节点:
//判断是否是叶子节点funcisleaf(bopast.node)bool{expr,ok:=bop.(*ast.binaryexpr)if!ok{returnfalse}//二元表达式的最小单位,左节点是标识符,右节点是值_,okl:=expr.x.(*ast.ident)_,okr:=expr.y.(*ast.basiclit)ifokl&&okr{returntrue}returnfalse}
“go语言的ast有什么作用”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注恰卡编程网网站,小编将为大家输出更多高质量的实用文章!