LR (1) 文法
LR (k) 项
形式:[A→α⋅β,a1a2…ak]
- 当 β=ε 时,为移进或待归约项,a1a2…ak 不直接起作用
- 当 β=ε 时,即为归约项 [A→α⋅,a1a2…ak],仅当前输入符串前 k 个符号是 a1a2…ak 时,才能用 A→α 进行归约
- a1a2…ak 称为向前搜索符号串(展望项)
LR (1) 有效项
LR (1) 项 [A→α⋅β,a] 对于一个可行前缀 γ 有效的条件是存在一个推导:
S⇒rm∗δAw⇒rmγδαβw其中:
- γ=δα
- 要么 a 是 w 的第一个符号,要么 w 为 ε 且 a 等于 $
考虑文法 G:S→CCC→cC∣d
规范推导:
S⇒rm∗ccCcd⇒rmcccCcd项 [C→c⋅C,c] 对可行前缀 ccc 是有效的。
LR (1) 有效项的推导
若项 [A→α⋅Bβ,a] 对可行前缀 γ=δα 是有效的,则存在一个规范推导:
S⇒rm∗δAax⇒rmδαBβax假定 βax⇒rm∗by,则对每一个形如 B→ξ 的产生式,有规范推导:
S⇒rm∗δαBβax⇒rm∗δαBby⇒rmδαξby从而项 [B→⋅ξ,b] 对于可行前缀 γ=δα 也是有效的。
注意到 b 必然属于二者之一:
- 从 β 推出的第一个终结符号
- β⇒rm∗ε 而 b=a
这两种可能性结合在一起,则 b∈First(βa)。
LR (1) 项集的构造
构造有效 LR (1) 项集族的方法实质上和构造规范 LR (0) 项集族的方法相同。
我们只需要修改两个过程:Closure 和 Goto。
Closure
设 I 是 G 的一个 LR (1) 项集,Closure(I) 是从 I 出发用以下三条规则构造的项集:
- 每一个 I 中的项都属于 Closure(I)
- 若项 [A→α⋅Bβ,a] 属于 Closure(I) 且 B→γ∈P,则对任何 b∈First(βa),把 [B→⋅γ,b] 加到 Closure(I) 中
- 重复执行 (2) 直到 Closure(I) 不再增大为止
Goto
设 I 是 G 的一个 LR (1) 项集,X 是一个文法符号,定义:
Goto(I,X)=Closure(J)其中 J={[A→αX⋅β,a]∣[A→α⋅Xβ,a]∈I}
LR (1) 项集族的构造方法
输入:一个增广文法 G′。
输出:LR (1) 项集族,其中的每个项集对文法 G′ 的一个或多个可行前缀有效。
方法:过程 Closure 和 Goto,以及用于构造项集的主例程 items
。
过程 Closure
SetOfItems Closure(I){repeatfor ([A→α⋅Bβ,a]∈I)for (B→γ∈G′)for (b∈First(βa))将 [B→⋅γ,b] 加入 I 中;until 不能向 I 中加入更多的项;return I;}过程 Goto
SetOfItems Goto(I,X){J←∅;for ([A→α⋅Xβ,a]∈I)将 [A→αX⋅β,a] 加入 J 中;return Closure(J);}最后执行 Closure 的原因是,Goto 函数返回的是一个新的项集,需要对其进行闭包操作。
项集族 C
void items(G′){C←{Closure({[S′→⋅S,$]})};repeatfor (每个项集 I∈C)for (每个文法符号 X)if (Goto(I,X)=∅ 且不在 C 中)将 Goto(I,X) 加入 C 中;until 不再有新的项集加入到 C 中;}构造 LR (1) 分析表
DFA 状态对应分析表行:
DFA 中的每个状态对应分析表中的一行。
DFA 状态转移:
对于 DFA 中的每一个从状态 i 到状态 j 的转移:
- 如果转移符号为终结符 a:在表项 M[i,a] 中填写 移进动作 Sj (Shift,Action 列)
- 如果转移符号为非终结符 A:在表项 M[i,A] 中填写 转移到状态 j (Goto 列)
包含归约项 [A→α⋅,a] 的状态 i:
在表项 M[i,a] 中填写归约动作 rk(Reduce),其中 k 是产生式 A→α 的编号
注意:如果每个单元格中只包含一个动作,则分析表合法。
LR(1)分析表举例
文法:
S′→SS→CCC→cCC→d项集族:
分析表:
LALR 文法
LALR:Look-Ahead LR
LR (1) 分析表:状态多,实际使用较少。
同心集:两个 LR (1) 项集 去掉搜索符后相同,称为 同心。
LALR (1) 分析表:合并同心集(合并搜索符串)后构造出的 LR 分析表。
合并同心项集不会产生移进 / 归约冲突,但是有可能产生归约 / 归约冲突。
因为合并的时候合并的是同心项的展望符,而展望符只在规约的时候起作用,在移入的时候是不起作用的,只要合并前各个同心项目集本身是没有移进 / 归约冲突的,就不会有移进 / 归约冲突(后文有证明)。
LALR 分析表的高效构造算法
通过先构造 LR (1) 分析表再合并得到 LALR (1) 分析表的过程太慢了。
内核项表示:
使用内核项表示 LR (0) 或 LR (1) 项集。
内核项:[S′→⋅S] 或 [S′→⋅S,$],以及 ⋅ 不在最左边的项(这些项代表对于已经读入的符号完全没有要求)。
传播和自发生成:
通过传播和自发生成,获得向前看符号,得到 LALR (1) 内核项。
传播 / 自发生成:向前看符号的传递过程。
对于某个项 [A→α⋅Bβ,a] 执行闭包:
传播:假设向前看符号是一个不在文法中的符号 #,即对 [A→α⋅Bβ,#] 进行闭包,若得到的某些项的向前看符号 就是 #,那么就认为这些项的向前看符号是传播得到的,直接复制 a,就行;
自发生成:假设向前看符号是一个不在文法中的符号 #,即对 [A→α⋅Bβ,#] 进行闭包,若有些项的向前看符号 不是 #,那么就认为这些项的向前看符号是传播得到的,不改动这些项的向前看符号;
Closure 函数:
使用 Closure 函数求出内核项的闭包,得到 LALR 分析表。
由于传播和自发生成的表述比较抽象,这里给一个例子(书 P175)来自己悟:
文法:
S′SLR→S→L=R∣R→∗R∣id→L直接根据产生式,构建出只有内核项的项集族:
I0I1I2I3I4I5I6I7I8I9:{S′→⋅S}:{S′→S⋅}:{S→L=⋅R,R→L⋅}:{S→R⋅}:{L→∗⋅R}:{L→id⋅}:{S→L=⋅R}:{L→∗R⋅}:{R→L⋅}:{S→L=R⋅}使用如下算法来确定向前看符号:
输入:一个 LR (0) 项集 I 的内核 K 以及一个文法符号 X。# 是一个不在文法中的符号。
输出:由 I 中的项为 Goto(I,X) 中内核项自生成的向前看符号,以及 I 中将其向前看符号传播到 Goto(I,X) 中内核项的项。
for (K中的每个项 A→α⋅β){J:=Closure({[A→α⋅β,#]});if ([B→γ⋅Xδ,a] 在 J 中,并且 a=#)断定 Goto(I,X)中的项 B→γX⋅δ的向前看符号 a 是自发生成的;if ([B→γ⋅Xδ,#] 在 J 中)断定向前看符号从 I中的项 A→α⋅β 传播到了 Goto(I,X)中的项 B→γX⋅δ上;}当我们将算法应用于项集 I0 的内核时,我们首先计算 Closure({[S′→⋅S,#]}),得到:
S′→⋅S,#S→⋅L=R,#S→⋅R,#L→⋅∗R,#/=L→⋅id,#/=R→⋅L,#在这个闭包的项中,我们看到有两个项中的向前看符号 = 是自发生成的;
而对于这个闭包结果,把 # 替换为真实的闭包前原始项 [S′→⋅S,$] 的向前看符号,即 $,我们认为此时的向前看符号 $ 是传播得到的。
LALR (1) 的讨论
核心依赖性:
由于 Goto(I,X) 仅依赖于 I 的核心,因此 LALR(1)项集合并后的转换函数 Goto(I,X) 随自身的合并而得到
动作修改:
动作 action 应当进行修改,以反映各被合并集合的既定动作
归约 - 归约冲突:
项集合合并时,可能会导致冲突。这种冲突不会是移进 - 归约冲突:
Ik:Ij:Ikj:{[A→α⋅,u1][B→β⋅ay,b]}a∩u1=∅{[A→α⋅,u2][B→β⋅ay,c]}a∩u2=∅{[A→α⋅,u1∪u2][B→β⋅ay,b/c]}a∩(u1∪u2)=∅但可能引起归约 - 归约冲突:
Ik:Ij:Ikj:{[A→α⋅,u1][B→β⋅,u2]}{[A→α⋅,u2][B→β⋅,u1]}{[A→α⋅,u1∪u2][B→β⋅,u1∪u2]}此时,有两个展望符号相同、核心也相同的归约项,可能产生归约 - 归约冲突。
二义性文法的使用
- 二义性文法不是 LR 的
- 有用的二义性文法:
- 简洁描述某些结构
- 隔离某些语法结构,对其进行特殊处理
- 处理某些二义性文法:
- 通过消除二义性规则,保证每个句子只有一棵语法分析树
- 可以在 LR 分析器中实现这一规则
利用优先级 / 结合性消除冲突
- 二义性文法:
- E→E+E∣E∗E∣(E)∣id
- 等价于:E→E+T∣TT→T∗F∣FF→(E)∣id
- 二义性文法的优点:
- 容易:修改算符的优先级和结合性
- 简洁:多优先级无需引入大量非终结符
- 高效:不需处理 E→T 这样的归约
四种 LR 解析的对比
- 如果构造 LR (0) 的 DFA
- 没有归约冲突就是 LR (0) 文法
- 有冲突但可以通过 Follow 集合解决冲突就是 SLR 文法
- 否则不是 SLR 文法
- 如果构造 LR (1) 的 DFA
- 没有冲突就是 LR (1) 文法
- 如果合并同心集之后也没有冲突,那么就是 LALR (1) 文法
- 包含关系
- LR(0)<SLR<LALR<LR(1)
用途比较:
- LR(0):最简单,但只能用于最简单的文法
- SLR:构造简单,易于实现,实用价值高(大多数上下文无关文法均可构造 SLR 分析表)
- LR(1):适用文法类最大(几乎所有上下文无关文法),但分析表体积过大,使用价值不大
- LALR(1):介于 SLR 和 LR(1) 之间,最实用(比 SLR 适用更多,比 LR(1) 更简单)