语法分析1
词法分析需要在掌握原理的基础上使用递归下降法(Recursive Descent Parsing)和上下文无关法(Context-free Grammar,CFG)
递归下降法(Recursive Descent Parsing)
递归下降法比较好理解,这是一种自顶向下构造AST的算法。算法读取一个Token流,从左到右读取这个Token流,并依次判断和读取Token生成AST结点。整个程序里有很多语句也就是很多Token流,一般来说只有在遇到一个新的Token流的时候需要在AST上手动创建新的结点,Token流中后面的部分可以使用这个头部结点的add方法来添加下层结点,这样子处理一个Token流在AST构建的结构上来看也是自顶向下的。
例如: int a = 13; 可以使用 ';' 来判断这是一个Token流的结束。 这个Token流会顺序生成四个结点,int ,a, = ,13 。头部结点是int,底部是13。具体实现的时候只需要不断判断从左往右读取的时候遇到的Token是什么就可以了,比如这里遇到int 那下一步就需要获得一个变量名,如果下一步不是变量名就抛出异常,后面同理。
‘下降’的意义有了,这里的‘递归’就涉及下面的内容。
上下文无关法(Context-free Grammar,CFG)
上下文无关法可以认为是递归下降法的一个组件(正则文法是上下文无关法的一个子集),因为我们在计算复杂的算术表达式的时候经常遇到运算符的优先级问题,我们知道这里要使用递归处理,优先级高的运算在叶子节点,最后使用深度优先遍历计算。 当遇到这种需要递归的场景的时候无法改写为简单的条件判断的正则文法。
下面举例子说明上下文无关法递归的原理和问题以及解决思路:
int a = 2+3*5
对于这个Token流,我们在2+3*5前都很好处理,最后只需要计算出2+3*5的值填入叶子结点就可以。 这里计算由于存在优先级问题,需要使用递归。所有的算术运算都可以简单的划分为乘法和加法,乘法的优先级比加法高所以乘法需要在加法的叶子节点上(这里先不考虑人为添加括号改变优先级情况)。
所以先简单写出加法乘法结构:
add: (multiple) or (add plus multiple)
multiple:(int) or (multiple star int)
可以看到我们遇到一个算式首先肯定调用add,然后在add里调用multiple,符合乘法加法的优先级关系。具体分析2+3*5:
add(2+3*5)--> 先进入multiple --> 进入multiple后先进入int --> int获取2留下+3*5,还有4个Token没有获取所以进入int不行,尝试进入 multiple star int --> 进入multiple star int 后又得进入multiple 并且因为在multiple star int中首先遇到multiple所以运算符不会消耗运算符star(*),同理上面的add中一旦进入 add plus multiple, plus(+)运算符也不会消耗,程序会无限调用每一次都读取第一个2然后发现还有4个Token没处理进入add或multiple又调用自己获得2,综上,这样的设计思路会导致无限递归,这里成为左递归错误,即左侧开始递归调用,如果将递归移动到右边换为右递归就可以解决问题了
现在修改为右递归再进行分析:
add:(multiple) or (multiple plus add)
multiple: (int) or (int star multiple)
还是2+3*5的Token流:
add(2+3*5) --> multiple(2+3*5) --> int(2+3*5)获得2如果直接返回那么add就只获得一个Token,Token流没有读取完毕,还有4个,我们希望执行一次add读取完所有Token,所以退出尝试下一个 --> int star multiple(2+3*5) 这里star需要获取*但是首先遇到的是+,所以退出递归到 --> multiple plus add(2+3*5) --> multiple(2+3*5) --> 返回2 --> 2 plus add(2+3*5) --> 这里会发生plus 读取+, multiple 读取2,所以Token流变成3*5传入 add 进行递归 --> add(3*5) --> multiple(3*5),返回3没读取完所以退出尝试 int star multiple(3*5) --> int star multiple (3*5) --> int 读取3, star 读取*,所以继续递归进入 multiple(5) --> multiple(5)进入int(5)读取掉5,并逐次返回至起点2+3*5,这样这个Token流里每一个Token就全部读取完毕
Token流读取完毕,AST树也就建立完毕,只需要深度优先遍历就可以计算这个算式生成树的值并将值保存在算式生成树的顶点。
树:
+ :left leaf 2 right leaf *
* :left leaf 3 right leaf 5
上面的递归调用就是递归下降法的‘递归’含义了。