第5章 自底向上的分析
自底向上分析从输入串开始,通过不断归约到达开始符号,是最右推导的逆过程。LR 系列分析器(LR(0)/SLR(1)/LR(1)/LALR(1))是其主要实现方式。
5.1 自底向上分析概览
移进-归约分析(Shift-Reduce Parsing)
两种基本动作:
- 移进(shift):将输入记号压入栈顶
- 归约(reduce):将栈顶的串 α 替换为产生式左部 A(A → α)
句柄(Handle)
- 定义:右句型中与某个产生式右部匹配且归约后得到下一个右句型的串
- 句柄总是在栈顶位置
- 移进-归约分析的核心任务:确定下一个句柄及其对应的产生式
扩充文法(Augmented Grammar)
- 增加新的开始符号 S' 和产生式 S' → S
- 目的:使分析器有唯一的接受状态
5.2 LR(0) 项的有穷自动机与 LR(0) 分析
LR(0) 项目
- 形式:[A → α·β],圆点表示分析进度
- A → α·β:已完成 α,期望继续处理 β
- A → αβ·:归约项目(可归约)
项目集的构造
闭包操作:
对于项目集 I,重复:
若 [A → α·Bβ] ∈ I 且 B → γ 是产生式
则 [B → ·γ] ∈ I
转换(goto):
goto(I, X) = Closure({[A → αX·β] | [A → α·Xβ] ∈ I})
LR(0) 分析表
- action[s, a]:状态 s 在输入 a 下的动作(s/i/r/acc)
- goto[s, A]:状态 s 遇到非终结符 A 的转移
LR 分析过程示例
使用文法 E' → E, E → E + T | T, T → id 分析输入 id + id:
步骤 | 状态栈 | 符号栈 | 输入 | 动作
─────┼─────────────────┼────────────┼────────────┼────────────
1 | 0 | | id + id $ | 移进
2 | 0 3 | id | + id $ | 归约 T→id
3 | 0 2 | T | + id $ | 归约 E→T
4 | 0 1 | E | + id $ | 移进
5 | 0 1 4 | E + | id $ | 移进
6 | 0 1 4 3 | E + id | $ | 归约 T→id
7 | 0 1 4 5 | E + T | $ | 归约 E→E+T
8 | 0 1 | E | $ | 接受
LR(0) 的局限性
- 不利用向前看信息,大量移进-归约冲突
- 实际能力有限
5.3 SLR(1) 分析
改进
- 使用 Follow 集解决归约决策
- 在状态 s 包含归约项目 A → α·时:
- 仅在 lookahead ∈ Follow(A) 时归约
- 否则移进或报错
SLR(1) 分析表构造
1. 构造 LR(0) 项目集规范族
2. 构造 action 表:
- 若 [A → α·aβ] ∈ s 且 goto(s, a) = t:action[s, a] = s t(移进)
- 若 [A → α·] ∈ s:∀a ∈ Follow(A),action[s, a] = r A→α(归约)
- 若 [S' → S·] ∈ s:action[s, $] = acc(接受)
3. 构造 goto 表:
- 若 goto(s, A) = t:goto[s, A] = t
SLR(1) 冲突条件
若以下条件任一不满足,则存在冲突:
- 若状态 s 含归约项目 A → α·,则对任何 X(终结符/非终结符),不能同时存在移进项目 A → α·Xβ
- 若状态 s 含两个不同归约项目 A → α·和 B → β·,则 Follow(A) ∩ Follow(B) = ∅
5.4 LR(1) 和 LALR(1) 分析
LR(1) 项目
- 格式:[A → α·β, a](a 为向前看符号)
- 比 LR(0) 携带了更多信息以解决冲突
LR(1) 闭包规则
若 [A → α·Bβ, a] ∈ I, B → γ ∈ P
则 ∀b ∈ First(βa): [B → ·γ, b] ∈ I
LR(1) vs SLR(1) 的区别
- SLR(1):对归约项目使用全局 Follow 集
- LR(1):对每个项目使用精确的向前看信息(First(βa))
LALR(1) 分析
- 合并 LR(1) 项目集中的同心项目(去掉向前看后相同的状态)
- 能力比 SLR(1) 强,比 LR(1) 弱
- 表格大小与 LR(0) 相同
- Yacc 使用的方法
文法能力对比
LR(1) ⊇ LALR(1) ⊇ SLR(1) ⊇ LR(0)
⊇ LL(1)
5.5 Yacc:LALR(1) 分析程序的生成器
Yacc 输入文件结构
%{
C 声明(包含头文件、类型定义等)
%}
%token 记号声明
%%
语法规则和动作
规则: 选择1 { 动作1 }
| 选择2 { 动作2 }
;
%%
用户子程序(C 代码)
冲突解决
- Yacc 默认规则:
- 移进-归约冲突 → 选择移进
- 归约-归约冲突 → 选择先出现的规则
- 可通过
%left、%right、%nonassoc声明算符优先级
5.6 使用 Yacc 生成 TINY 分析程序
- TINY 的 Yacc 规范
- 需要配合 Lex 扫描程序或手写的扫描程序
5.7 自底向上分析中的错误校正
- Yacc 的默认错误处理:调用
yyerror,尝试丢弃栈状态直到找到特殊错误产生式 - 可通过
error记号在文法中指定错误恢复
考试重点
★★★ 第一梯队(必考)
| 考点 | 典型题型 |
|---|---|
| LR(0) 项目集规范族构造 | 一步步扩张状态 |
| LR(0) 分析表构造 | 填表 |
| SLR(1) 分析表构造 | 结合 Follow 集 |
| LR(1) 项目集构造 | 带向前看的闭包 |
| 给定分析表模拟分析过程 | 步骤表 |
★★☆ 第二梯队
| 考点 | 典型题型 |
|---|---|
| 识别文法是 LR(0)/SLR(1)/LR(1)/LALR(1) | 判定题 |
| 指出分析表中的冲突及原因 | 分析题 |
★☆☆ 第三梯队
| 考点 | 典型题型 |
|---|---|
| Yacc 如何解决某类冲突 | 简答 |
| LALR(1) 同心项目合并 | 概念题 |
重要对比
| 方法 | 能力 | 表大小 | 适用 |
|---|---|---|---|
| LR(0) | 最弱 | 小 | 很少实际使用 |
| SLR(1) | 中等 | 中等 | 教学常用 |
| LALR(1) | 强 | 中等 | Yacc 编译器生成器 |
| LR(1) | 最强 | 很大(状态多) | 理论价值高 |