自顶向下分析
从文法开始符号开始推导,每步获得文法的一个句型,直到产生期望的句子。
一般的自顶向下分析存在两类非确定性:
- 每步推导中选择哪个非终结符展开
- 展开某个非终结符时选择哪个产生式
第一种非确定性可以通过选规定最左或最右推导解决。为了消除第二类非确定性,一般采取向前查看确定数量的单词来确定选择哪个产生式。比如,对于
显然只需向前查看至多两个单词就可以确定选用哪个产生式。
但有些文法无论向前查看多少个单词都无法确定选择哪个产生式,如
LL(1) 分析
LL(1) 的含义:
- 第一个 L:从左向右扫描单词
- 第二个 L:最左推导
- 1:向前查看一个单词
LL(1) 分析的前提是文法是 LL(1) 的。
First 集合
First 集合
对 , 直观上讲,如果一个句型 可以推导出 开头的句型,则
- 一般只考虑
First 集合的计算:
- 对
- 重复以下步骤直到所有 First 集合不变
- 对于 ,若 均包含 ,则 ,否则设 为第一个不包含 的集合,
- 若有 ,则
Follow 集合
Follow 集合
对 ,, 直观上讲,若 中包含一个 形式的句型,则 ,若存在一个以 结尾的句型,则
Follow 集合的计算:
- 重复以下步骤直到所有 Follow 集合不变:
- 对于 ,
- 若 ,
- 总是不出现在 Follow 集合中
- 手动计算 First 集合(Follow 集合)时,可以重复扫描每一个非终结符来实现,对于每个非终结符,找它出现在左侧(右侧)的产生式并更新,直到没有更新为止
LL(1) 文法
预测集合
对任何 ,定义预测集合 :
- 若 ,则
- 若 ,则
直观上讲,预测集合中的单词代表如果选用这条产生式可能得到的第一个单词
LL(1) 文法
定义文法 是 LL(1) 的当且仅当对于任意 ,有 直观上讲,表示仅向前查看一个单词就可以确定选用哪条产生式
LL(1) 分析的实现
递归下降
每个非终结符对应一个子程序,对于一个产生式右部,如果遇到终结符则判断当前单词是否匹配,若不匹配则报错,如果遇到非终结符则继续调用它的子程序。一般结构如下:
void ParseA() {
switch (lookahead) {
case PS(A_to_u1):
...
break;
case PS(A_to_u2):
...
break;
...
default:
printf("Syntax error!\n")
exit(0);
}
}这种方法比较直观,缺点也显而易见,效率较低。
表驱动
预先构建一个预测分析表:
- 每行对应一个非终结符
- 每列对应一个终结符或结束符号#
- 每项对应一个产生式集合,若 中包含 ,则将 加入 预测分析表的每个表项只含一个产生式当且仅当 为 LL(1) 文法。
表驱动的 LL(1) 分析过程如下:
- 初始时将 依次入栈
- 重复以下步骤:
- 若栈顶为终结符,判断当前读入字符是否匹配,若不匹配则报错,否则弹出
- 若栈顶为非终结符,根据预测分析表选择栈顶符号和当前读入字符对应的产生式,若不存在该表项则报错,否则弹出该非终结符,将产生式右部从右向左入栈
文法变换
消除左递归
消除直接左递归:
对于
可将其替换为
- 不要漏了
对于存在间接左递归的情况,先将所有非终结符排列为 ,然后遍历:
- 对于 ,用 替代,其中 是其全部产生式,。
- 消除 的直接左递归
消除左公因子
对于
可将其替换为
- 不含左递归和左公因子的文法不一定是 LL(1) 文法
LL(1) 分析中的错误处理
同步符号
跳过输入串中的一些符号直到遇到同步符号为止。
同步符号的选择:
- 把 Follow(A) 中的所有符号作为 A 的同步符号
- 把 First(A) 中的所有符号作为 A 的同步符号
LL(K) 文法
- 给定 ,一个 CFG 是否为 文法是可判定的
- 对于一个 CFG,是否存在 ,使得该文法是 文法是不可判定的
- 对于一个 CFG,是否存在一个阈值等价的 文法是不可判定的
- 两个 的语言是否相等是可判定的
- 文法是无二义的
- 文法中不存在左递归的非终结符
- 给定 ,不含 产生式的 文法的语言类真包含于不含 产生式的 文法的语言类