Skip to content

算法Algorithm

LR(0) 项目自动机与移进归约

LR(0) parsing · LR(0) item automaton

用项目自动机总结可行前缀,并以状态栈执行不依赖向前看的移进归约。

形式陈述 ​

LR(0) 分析把“已经读过的符号可能对应哪几条产生式的哪一段”编码成有限状态,再由这个状态驱动移进和归约。它从左向右读取输入,反向构造最右推导;括号里的 0 表示归约决定不使用向前看 token 来区分上下文。

对文法加新开始规则 S′→S。LR(0) 项目在一条产生式右部放一个点,例如

A→α⋅β.

点左边是已经识别的右部,右边是尚待识别的部分。A→α⋅ 是完整项目,提示可能把栈顶 α 归约成 A。它并不单独证明当前输入的剩余部分合法。

给项目集 I 定义闭包:只要含 A→α⋅Bβ,就加入 B 的每条规则 B→⋅γ,重复到稳定。定义

goto(I,X)=closure{A→αX⋅β:A→α⋅Xβ∈I}.

从 I0=closure{S′→⋅S} 开始,枚举所有非空 goto,得到以终结符和非终结符共同作为边标签的项目自动机。若按完整 DFA的约定识别可行前缀,须把这些非空项目集设为接受态,并把所有缺边接到一个拒绝的空项目死状态;死状态在每个文法符号上自环。解析表通常省略这个死状态,把缺边直接解释为错误。这台 DFA 读取的是分析栈上的文法符号,不是直接用有限记忆识别整个上下文无关输入;处理嵌套所需的记忆仍由栈提供。

同一个驱动器,两个表 ​

若终结符边 Ii→aIj 存在,令 ACTION[i,a] 包含“移进 j”;非终结符边则填入 GOTO[i,A]=j。若 Ii 含完整项目 A→α⋅,其中 A≠S′,就在该行每个输入 token 及 $ 下加入“按此规则归约”。S′→S⋅ 只在 $ 下接受。

同一 ACTION 格不能有两个不同动作。移进与归约同时出现称移进/归约冲突,两条不同归约称归约/归约冲突。无冲突时,这张表定义 LR(0) 解析器。

状态栈初始为 [0]。移进 a 时消费输入并压入目标状态。归约 A→X1⋯Xk 时弹出 k 个状态,查看露出的状态 i,再压入 GOTO[i,A]。归约不消费输入;空规则弹零个状态。若同时存符号或语义值栈,它们也要按同一右部长度弹出,并把对应的子树接到新节点 A 下。

直觉

状态栈保存了沿“当前文法符号栈”运行 DFA 所经过的状态。移进是在这条路径末端续上一条边。归约则先撤去一段表示右部的路径,再从它之前的状态沿左部非终结符边前进。正因保留了整条状态历史,归约后不用从 I0 重新扫描整栈。

文法符号栈是某个最右句型中不越过最右句柄的前缀,称为可行前缀。闭包中的预测项目表达:若下一段打算识别 B,那么 B 的任何产生式都可开始;goto 则同时推进所有可能项目。有限项目集合足以总结前缀的解析可能性,栈深度可以继续增长。

例子与边界

七个状态完整解析 cdd ​

给产生式编号:r1:S→CC,r2:C→cC,r3:C→d。加 S′→S 后,可达项目集如下:

状态 项目
I0 S′→⋅S;S→⋅CC;C→⋅cC;C→⋅d
I1 S→C⋅C;C→⋅cC;C→⋅d
I2 S′→S⋅
I3 C→c⋅C;C→⋅cC;C→⋅d
I4 C→d⋅
I5 S→CC⋅
I6 C→cC⋅

全部表格为:

状态 c d $ S C
0 s3 s4 — 2 1
1 s3 s4 — — 5
2 — — acc — —
3 s3 s4 — — 6
4 r3 r3 r3 — —
5 r1 r1 r1 — —
6 r2 r2 r2 — —

s3 表示消费一个 token 后压状态 3,r3 表示按产生式 3 归约。输入 cdd 的每一步为:

状态栈 符号栈 剩余输入 动作
0 空 cdd$ s3
0,3 c dd$ s4
0,3,4 cd d$ C→d
0,3,6 cC d$ C→cC
0,1 C d$ s4
0,1,4 Cd $ C→d
0,1,5 CC $ S→CC
0,2 S $ acc

第二次归约弹掉的是状态 3 和 6,露出 0,再查 GOTO[0,C]=1。若误用归约前的栈顶 6 查询 GOTO,就会查不到正确后继。

输入 cd 则只形成第一个 C。栈到 [0,1] 时输入已经结束,ACTION[1,$] 为空,因此拒绝。最后读到 d 或曾经归约成 C 都不等于整句完成。

完整项目也可能过早 ​

改用 S→aS∣a。读入一个 a 后,状态中同时有 S→a⋅S 与 S→a⋅,闭包还预测能继续读 a。下一 token 为 a 时,移进和立即归约都被 LR(0) 规则放进同一格。

这个冲突没有证明语言有歧义;语言只是非空的 a 串。它说明“一个右部已经完整”还不足以决定此时该不该结束它。SLR加入 FOLLOW,LR(1)把更精确的右上下文直接附在项目上。

推论与应用

LR 驱动器的栈不变量解释了为何无需回溯:若表无冲突,选定动作对应唯一可行的句柄处理;每次归约都是反向走一条最右推导。接受时归约得到开始符号且输入恰好结束,因此生成的树确实属于文法。完整性还依赖 LR(0) 无冲突条件,不能对任意 CFG 的任意冲突消解作相同承诺。

令项目总数为 m=∑A→α(|α|+1),项目集最多有 2m 种,因此构表最坏可指数膨胀。令实际状态数为 q,稠密 ACTION/GOTO 表占 O(q(|V|+|Σ|))。固定有用的无冲突文法后,移进/归约次数随解析树大小增长,通常按输入长度记为 O(n) 时间、O(n) 栈空间;构表成本与运行成本应分开报告。

状态编号没有数学意义。本页编号由按符号排序的可达搜索确定;检查另一份表时应比较项目集和转移,而不是要求“状态3”在所有资料中都代表同一集合。

参考资料
  • Stanford CS143, Lecture 8: Bottom-Up Parsing,可行前缀、项目自动机、LR(0) 与 SLR 部分,尤其 slides 54–81
  • Cornell CS4120, LR(1) and LALR Parsing,LR(0) 状态与向前看扩展的衔接
  • Aho, Lam, Sethi, Ullman, Compilers, 2nd ed., 2007,§4.6
关系图谱13 个相邻概念 · 2 类关系

拖动节点调整位置。

显示关系

显示:依赖

  1. 前置三跳
  2. 前置二跳
  3. 前置一跳
  4. 当前条目
  5. 后续一跳
  6. 后续二跳
  7. 后续三跳
文字版关系按与当前条目的最短距离分组
类型化关系