编译原理:自上而下语法分析
自上而下分析遇到的问题
- 左递归
- 回溯
消除左递归
直接左递归
文法
也就是会进入不断地递归,但是这个文法结束的时候一定是开头为
1 | void ParseP() { |
左递归转右递归
既然能够确定
于是递归就转换到了产生式右边的结尾。
在进行语法分析时,产生式将不断地将源程序读入与终结符
1 | void ParseP() { |
左递归文法:
转化为右递归:
例题:
间接左递归
直接看产生式没有左递归,但是在推导过程中产生了左递归
文法消除左递归的条件:
- 不含以
为右部的产生式 - 不含回路,,即不含
消除思路:
- 将文法 G 的所有非终结符按照任一种顺序排列
- 把
的规则改造成 … ,即产生式右部若以非终结符开头,一定要是按照第 1 步的顺序 往后的非终结符,如果不是,则用其定义进行替换 - 化简,删除无用的产生式
由于排序不同,得到的产生式可能不一样,但是是等价的
消除回溯
A 可能的两个候选都是非终结符开头,若选择用 B 去匹配,若源程序为 “abcd”,则匹配到 ‘c’ 的时候发现不符合 B 的文法规则,于是需要回溯到 ‘a’,再使用 C 进行匹配。
解决思路
终结首符集 (FIRST 集合):将来若干步推导后在开头出现的终结符集合
可能包含空字
FIRST(
要根据 FIRST(
提取公共左因子
引进非终结符和新的产生式
可改写为
候选含有空字
若此时遇到了要匹配的字符 a,是否应该使用 A 的空字去匹配,应该取决于 A 之后是否有其他符号可以匹配 a,所以需要有 FOLLOW 集合(S 为开始符号)
就是在某个句型里,可能跟在 A 后面的终结符
若存在
LL(1) 文法
含义
L: 从左到右
L: 最左推导
1: 根据当前单词分析
规则
-
不含左递归
-
所有产生式的所有候选的 FIRST 集合不相交
-
对每个非终结符 A 如果候选的 FIRST 集合包含空字,则
也就是当需要使用
的时候,一定是不得已而为之(候选 FIRST 集合内没有当前这个输入字符),且之后有其它推导可以匹配当前的这个字符,如果之后也没有,则报错。
分析
-
若
,则指派 执行任务 -
若
: 且 ,则让 A 与 匹配 - 否则出错
SELECT 集合
代表的是当遇到某个字符 a 的时候,如果
FIRST 与 FOLLOW 的构造
对每一个
- 若 X 本身就是终结符,则 FIRST(X) =
- 若
,则 a 加入 FIRST(X) 中,若推出空字也加入 - 若
是一个产生式,则 FIRST(Y) 的所有非空字(因为 Y 后可能还有其他符号)元素加入 FIRST(X) - 若 X 可以推出很长的符号串,且前 j 个符号都是非终结符,且都能推出
,则需要将第 j + 1个符号的 FIRST 集合加入 FIRST(X) - 如果 X 可以推出的符号串中的所有符号都可以推出
,则 应该加入 FIRST(X)
对每个非终结符 A
- 对于文法的开始符号,将 # 加入 FOLLOW(S)
- 若
是一个产生式,则把 加入 FOLLOW(B)( 的第一个字符就是 B 的后继字符) - 若
是一个产生式,或 是一个产生式而 ,则把 FOLLOW(A) 加入 FOLLOW(B) 中(B 在 A 产生式的最右边,所以跟在 A 后面的元素也可能跟在 B 后面)
总结:
- 看有没有产生式是 一个非终结符跟在另一个非终结符后面的,有的话,就要考虑将后面的 FIRST 加入前面 FOLLOW
- 看有没有位于产生式末尾的非终结符,或者位于产生式末尾的非终结符可能推出
,有的话,则考虑将左部的 FOLLOW 加入末尾的 FOLLOW
预测分析
- 总控程序
- 分析表:
矩阵, - 分析栈:
- 初始状态:#E
- 读入新的符号 a,查表 M[E, a],并将查询结果压入分析栈
构造预测分析表
- 构造 FIRST 集合和 FOLLOW 集合
- 构造分析表
应该放到分析表中的 A 行,放到
应该放到分析表中的 A 行,放到
所有没有定义的格子上放上出错标志
若文法满足 LL(1),其上不会有多重定义入口(也就是表格内的一个单元格有两个值)
- 若含左递归 / 二义性则有多重定义入口