这篇主要写上下文无关法的书写格式和生成AST遇到的问题(右递归规避了无限递归但将算式变为了右结合,可以在左递归的基础上使用自底向上的LR算法而非递归下降法实现规避无限递归的同时保留算式左结合的特性,也可以使用标准写法改写为非左递归文法)
上下文无关法的书写法则
add -> mul | add + mul
mul -> pri | mul * pri
pri -> Id | Num | (add)
这种是一般形式,pri代表初级单元,也就是优先级最高单元,可以是()即括号括起来的单元,这样子就保证了人为使用括号来提高优先级的能力
以2+3*5为例:
add(2+3*5) -> mul(2+3*5) -> pri(2+3*5) -> Id(2+3*5)这里退出重进Num(2+3*5)返回2 ->一直返回到mul发现还有4个Token没处理 所以换到add+mul(2+3*5) -> 同理进入add(2+3*5)返回了2 -> 2 + mul(3*5) -> pri(3*5) -> Num(3*5)返回3发现还有2个Token 所以不行 继续尝试 mul * pri(3*5) -> 同理递归调用 mul(3*5) 返回3 -> 3 * mul(5) -> 3 * 5至此所有Token接收完毕 形成树状结构:
root:add|left leaf : mul->only leaf: pri->only leaf: num:2
|+
|right leaf: mul|left leaf:mul->only leaf: pri->only leaf: num: 3
|*
|right leaf: pri -> only leaf: 5
这里面可以清晰地看到,add->mul->pri->num->2在不断的转换,最后会被替换为num,这里面的num就叫做终结符,也就是最后生成的Token,前面在递归中用到的pri->mul这些状态可以称为非终结符,文法的推导过程就是不断替换非终结符,使得最后生成的AST叶子节点都是终结符,也可以说是把Token流里的Token全部read掉消耗掉
巴科斯范式(BNF)
实际中经常使用巴科斯范式表示文法规则:
add ::= mul | add + mul
mul ::= pri | mul * pri
pri ::= Id | Num | (add)
还有扩展巴科斯范式(EBNF):
add -> mul (+ mul)*
这里用到了正则表达式*代表多次重复,也就是递归,这一句显然和add ::= mul | add + mul等价。
确保优先级正确
在写BNF的时候从上到下优先级依次递增,保证这个就可以
确保左结合律的同时规避进入无限递归
直接的思路就是改写左递归的文法,改写后虽然生成的AST还是右递归的但是在最后运算的时候可以做特殊处理,即改写后文法是利用循环来生成AST那么也利用循环来输出AST计算结果,这样子可以通过自己的编程来保证左结合
add ::= mul | add + mul -> mul ::= pri | mul * pri 这里是一个左递归文法, 可以改写为add ::= mul add' -> add' ::= + mul add' | ε 再进一步改写为 add -> mul (+ mul)*
虽然将左递归写为了右递归(add'还是一个右递归),但可以看到(+ mul)* 是一个循环, 在编程的时候使用循环替代掉递归运算,那么在生成树的时候每一轮循环生成结点就是可控的