“这个冲突没有证明语言有歧义;语言只是非空的 $a$ 串。它说明“一个右部已经完整”还不足以决定此时该不该结束它。SLR加入 FOLLOW,LR(1)把更精确的右上下文直接附在项目上。”
形式陈述
SLR(1) 保留LR(0)的项目自动机与状态栈,只改变归约表的填法。状态
的列中。FOLLOW 由文法集合分析求得,表示
若每个 ACTION 格至多有一个动作,称这份文法是 SLR(1) 文法,通常简称 SLR。它的运行步骤与 LR(0) 完全相同;区别在于静态构表时更少地允许归约,并不是运行时临时回溯寻找“更合适”的规则。
直觉
完整项目说“右部已经齐了”,FOLLOW 再问“它若现在结束,下一个 token 能接在它后面吗?”一个输入 token 不能跟在
但 FOLLOW 把 =,不保证它在当前项目状态中也能跟 =。所以这是一种可靠但可能过宽的上下文近似。过宽不会漏掉文法需要的归约,却可能保留冲突,使 SLR 无法给出确定动作。
例子与边界
用 FOLLOW 修复一个真实的 LR(0) 冲突
取
LR(0) 在 + 上既填移进,又因完整项目填归约
因此 SLR 只在 + 列只剩移进。六状态的全部表如下:
| 状态 | id | + | E | T | |
|---|---|---|---|---|---|
| 0 | s3 | — | — | 1 | 2 |
| 1 | — | — | acc | — | — |
| 2 | — | s4 | r2 | — | — |
| 3 | — | r3 | r3 | — | — |
| 4 | s3 | — | — | 5 | 2 |
| 5 | — | — | r1 | — | — |
其中
解析 id+id 时,状态栈依次为
动作依次是移进 id、+ 还是
FOLLOW 也可能太宽
考虑赋值文法:
从初态识别
第一个项目要求可以移进 =。另一方面,= 属于 FOLLOW(= 也属于 FOLLOW(
这个信息并非凭空产生:在 *R=R 一类句型中,内部的 =。问题是当前栈只有作为整个句子开头的
规范 LR(1)在当前完整项目后附的向前看集合只有 = 而不发生冲突。不能以“SLR有冲突”推断这份文法有歧义,也不应随意删掉一条产生式来压掉报错。
推论与应用
SLR 不增加 LR(0) 状态数。若实际自动机有
安全性来自 FOLLOW 的必要条件:合法句柄归约后面出现的 token 必在相应 FOLLOW 中,所以限制归约不会删掉一条合法最右推导所需的动作。若剩余每格唯一,驱动器便能确定地执行;若仍有冲突,算法报告的是适用条件失败,而不是凭启发式冒充同一可靠性定理。
一个实用检查顺序是:先列出冲突状态的完整项目与移进项目,再算涉及的 FOLLOW,最后问这些 FOLLOW 元素来自哪些文法出现位置。这样能区分真正的歧义与全局上下文合并造成的伪冲突,也能看清下一步是否需要 LR(1)。
参考资料
- Stanford CS143, Lecture 8: Bottom-Up Parsing,slides 79–82、102:SLR归约条件与运行
- Aho, Lam, Sethi, Ullman, Compilers, 2nd ed., 2007,§4.6:SLR构造及不能由SLR处理的文法
- Cornell CS4120, LR(1) and LALR Parsing,局部向前看为何优于无上下文区分的归约