算符优先算法java实现,算符优先算法

通常,操作符的优先级越高,位置就越低。例如在3+45这个表达式中,*的优先级高于+,那么就是+节点的子节点。但如果3+4添加了(),即:(3+4)*5,两个符号的优先级就颠倒回来。

9edf78ee4756980ee5d62723847fac5f.png

如果我们用nearyley(一种使用类BNF语法表达的parser生成器)表达,就有如下代码,可以看到每一种不同优先级的运算符,对应了一条语法规则。 当不同优先级的运算符增加,我们要写的语法规则也会相应增加:expression -> additive {% d => d[0] %}

additive ->

additive "+" multiple {% d => d[0] + d[2] %}

| additive "-" multiple {% d => d[0] - d[2] %}

| multiple {% d => d[0] %}

multiple ->

multiple "*" factor {% d => d[0] * d[2] %}

| multiple "/" factor {% d => d[0] / d[2] %}

| factor {% d => d[0] %}

factor -> "(" additive ")" {% d => d[1] %}

| %number {% d => Number(d[0]) %}

这样新增一个运算符,就会给我们的parser实现带来一定的成本,那么有没有一种通用的方式处理我们的运算符优先级呢?算符优先算法就是为了这个目的而生的。

下面是算符优先算法的代码,为了方便学习,这里我们lexer和parser,写的尽可能的简单,所以我们的parser不允许符号之间有空格,且只支持解析个位数数(即支持3+4,不支持33+4):class BinaryExpr{

constructor(left, op, right){

this.left = left

this.op = op

this.right = right

}

}

class OpPrecedenceParser{

constructor(lexer, precedenceMap){

this.lexer = lexer

this.precedenceMap = precedenceMap

}

expression(){

let right = this.factor()

let next;

while(next = this.nextOperator()){

right = this.doShift(right)

}

return right

}

doShift(left){

const op = this.lexer.read()

let right = this.factor()

let next;

while((next = this.nextOperator()) && this.rightIsExpr(op, next)){

right = this.doShift(right)

}

return new BinaryExpr(left, op, right)

}

nextOperator(){

const op = this.lexer.nextToken()

if(this.precedenceMap.get(op)) return op

return null

}

factor(){

if(this.lexer.nextToken() === "("){

this.token()

const exp = this.expression()

this.token()

return exp

} else {

return this.lexer.read()

}

}

token(){

return this.lexer.read()

}

rightIsExpr(op1, op2){

return this.precedenceMap.get(op1) < this.precedenceMap.get(op2)

}

}

class Lexer{

constructor(content){

this.currentIndex = 0

this.content = content || ""

this.tokens = []

}

peek(index){

return this.content[this.currentIndex + index]

}

nextToken(){

if(this.content[this.currentIndex])

return this.peek(0)

}

read(){

return this.content[this.currentIndex++]

}

}

function parse(expr){

const precedenceMap = new Map()

precedenceMap.set("+", 2)

precedenceMap.set("-", 2)

precedenceMap.set("*", 3)

precedenceMap.set("/", 3)

const lexer = new Lexer(expr)

const parser = new OpPrecedenceParser(lexer, precedenceMap)

const tree = parser.expression()

return tree

console.log(ast)

}

const ast = parse("3+4*5")

console.log(ast)

上述代码的打印结果是:BinaryExpr {

left: '3',

op: '+',

right: BinaryExpr { left: '4', op: '*', right: '5' }

}

我们改变一下入参为:(3+4)*5,打印结果为:BinaryExpr {

left: BinaryExpr { left: '3', op: '+', right: '4' },

op: '*',

right: '5'

}

可以看到结果是符合我们预期的,优先级低的操作符更靠近根节点。那如果我们需要增加新的符号呢?只需这样做:precedenceMap.set("

precedenceMap.set(">", 1)

当我们执行parse(3+4>5)时,得到的结果为:BinaryExpr {

left: BinaryExpr { left: '3', op: '+', right: '4' },

op: '>',

right: '5'

}

现在我们领略了算符优先算法的强大,增加符号只需要往规则map里面增加规则及优先级即可。让我们分析一下算符优先算法。OpPrecedenceParser中expression和factor方法比较容易理解,只是根据我们的文法做自顶向下的匹配。核心在于doShift函数:doShift(left){

const op = this.lexer.read()

let right = this.factor()

let next;

while((next = this.nextOperator()) && this.rightIsExpr(op, next)){

right = this.doShift(right)

}

return new BinaryExpr(left, op, right)

}

假设有NUMBER1+NUMBER2*NUMBER3这样的表达式,doshift函数向后读取,拿到符号,然后将+和进行比较,如果前面的符号优先级更高,则构造出Expr,并返回。如果后面的符号优先级更高,则对右侧的表达式做doShift。

可以看出,算符优先算法对于parse表达式,真的是一件利器,当我们使用LL算法做parser时,不妨在表达式解析上,使用算符优先算法。

参考:《编程语言实现模式》

《两周自制脚本语言》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值