自顶向下分析

从文法开始符号开始推导,每步获得文法的一个句型,直到产生期望的句子。

一般的自顶向下分析存在两类非确定性:

  • 每步推导中选择哪个非终结符展开
  • 展开某个非终结符时选择哪个产生式

第一种非确定性可以通过选规定最左或最右推导解决。为了消除第二类非确定性,一般采取向前查看确定数量的单词来确定选择哪个产生式。比如,对于

显然只需向前查看至多两个单词就可以确定选用哪个产生式。

但有些文法无论向前查看多少个单词都无法确定选择哪个产生式,如

LL(1) 分析

LL(1) 的含义:

  • 第一个 L:从左向右扫描单词
  • 第二个 L:最左推导
  • 1:向前查看一个单词

LL(1) 分析的前提是文法是 LL(1) 的。

First 集合

First 集合

直观上讲,如果一个句型 可以推导出 开头的句型,则

  • 一般只考虑

First 集合的计算:

  1. 重复以下步骤直到所有 First 集合不变
    1. 对于 ,若 均包含 ,则 ,否则设 为第一个不包含 的集合,
    2. 若有 ,则

Follow 集合

Follow 集合

,, 直观上讲,若 中包含一个 形式的句型,则 ,若存在一个以 结尾的句型,则

Follow 集合的计算:

  1. 重复以下步骤直到所有 Follow 集合不变:
    1. 对于
  • 总是不出现在 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) 分析过程如下:

  1. 初始时将 依次入栈
  2. 重复以下步骤:
    1. 若栈顶为终结符,判断当前读入字符是否匹配,若不匹配则报错,否则弹出
    2. 若栈顶为非终结符,根据预测分析表选择栈顶符号和当前读入字符对应的产生式,若不存在该表项则报错,否则弹出该非终结符,将产生式右部从右向左入栈

文法变换

消除左递归

消除直接左递归:

对于

可将其替换为

  • 不要漏了

对于存在间接左递归的情况,先将所有非终结符排列为 ,然后遍历:

  1. 对于 ,用 替代,其中 是其全部产生式,
  2. 消除 的直接左递归

消除左公因子

对于

可将其替换为

  • 不含左递归和左公因子的文法不一定是 LL(1) 文法

LL(1) 分析中的错误处理

同步符号

跳过输入串中的一些符号直到遇到同步符号为止。

同步符号的选择:

  • 把 Follow(A) 中的所有符号作为 A 的同步符号
  • 把 First(A) 中的所有符号作为 A 的同步符号

LL(K) 文法

  • 给定 ,一个 CFG 是否为 文法是可判定的
  • 对于一个 CFG,是否存在 ,使得该文法是 文法是不可判定的
  • 对于一个 CFG,是否存在一个阈值等价的 文法是不可判定的
  • 两个 的语言是否相等是可判定的
  • 文法是无二义的
  • 文法中不存在左递归的非终结符
  • 给定 ,不含 产生式的 文法的语言类真包含于不含 产生式的 文法的语言类