LL语法分析
父章 语法分析 用递归下降手写了预测分析:每个非终结符一个函数,靠 peek() 选分支。把「当前非终结符 + 当前向前看终结符 → 该用哪条产生式」做成一张表,运行时只查表、进栈出栈,就是表驱动的 LL(1) 分析。本节讲这张表怎么造。
构建LL语法分析表
LL(1) 表示:从左到右扫描输入(Left-to-right),最左推导(Leftmost derivation),向前看 1 个终结符即可唯一决定产生式。
FIRST 与 FOLLOW
对文法符号串 α:
- FIRST(α):从 α 能推导出的串的开头终结符集合;若 α⇒∗ε,则 ε 也记入 FIRST。
- FOLLOW(A):在某个句型中,紧跟在非终结符 A 右边可能出现的终结符集合;若 A 可以出现在句型末尾,则把结束符 $ 放入 FOLLOW(A)。
计算要点(反复直到不动):
FIRST
- 终结符 a:FIRST(a)={a}。
- 若 A→ε,则 ε∈FIRST(A)。
- 若 A→Y1Y2⋯Yk,把 FIRST(Y1) 中非 ε 的符号加入 FIRST(A);若 Y1⇒∗ε,再并入 FIRST(Y2),以此类推;若全部可空,则 ε∈FIRST(A)。
FOLLOW
- 开始符号 S:$\in\mathrm{FOLLOW}(S)$。
- 若有 A→αBβ,则 FIRST(β)∖{ε} 并入 FOLLOW(B)。
- 若有 A→αB,或 A→αBβ 且 β⇒∗ε,则 FOLLOW(A) 并入 FOLLOW(B)。
填表规则
分析表 M[A,a](行:非终结符;列:终结符或 $):
- 对每条 A→α,对每个终结符 a∈FIRST(α),令 M[A,a]=A→α。
- 若 ε∈FIRST(α),则对每个 b∈FOLLOW(A),令 M[A,b]=A→α。
- 其余格子为报错。
若某格被两条产生式写入,则出现冲突,文法不是 LL(1)。常见原因:左递归、公共左因子。需先改写文法(消左递归、提取左公因子),再算 FIRST/FOLLOW 填表。父章的递归下降能直接写,正是因为那份四则运算文法(或改写后)满足 LL(1)。
例子:消去左递归后的表达式骨架
左递归的 E→E+T∣T 不是 LL(1)。改写为:
E → T E'
E' → + T E' | ε
T → F T'
T' → * F T' | ε
F → ( E ) | id
FIRST
| 符号 | FIRST |
|---|
| F | {(,id} |
| T′ | {∗,ε} |
| T | {(,id} |
| E′ | {+,ε} |
| E | {(,id} |
FOLLOW
| 符号 | FOLLOW |
|---|
| E | {),$} |
| E′ | {),$} |
| T | {+,),$} |
| T′ | {+,),$} |
| F | {∗,+,),$} |
(由 F→(E) 得 )∈FOLLOW(E);由 E→TE′ 等规则按上面三条传播。)
LL(1) 分析表(空格表示出错):
| id | + | * | ( | ) | $ |
|---|
| E | E→TE′ | | | E→TE′ | | |
| E′ | | E′→+TE′ | | | E′→ε | E′→ε |
| T | T→FT′ | | | T→FT′ | | |
| T′ | | T′→ε | T′→∗FT′ | | T′→ε | T′→ε |
| F | F→id | | | F→(E) | | |
分析过程示意
输入 id + id,栈底为 $,开始时栈为 $ E(顶在右):
| 栈(顶在右) | 输入 | 动作 |
|---|
| $ E | id+id$ | E→TE′ |
| $ E′T | id+id$ | T→FT′ |
| $ E′T′F | id+id$ | F→id |
| $ E′T′id | id+id$ | 匹配 id |
| $ E′T′ | +id$ | T′→ε |
| $ E′ | +id$ | E′→+TE′ |
| $ E′T+ | +id$ | 匹配 + |
| … | … | 继续至栈与输入皆为 $ |
与递归下降对照:查 M[E,id] 相当于在 parseE 里看到 id/( 时调用 parseT 再处理 E';表只是把分支选择显式化,便于工具生成。
小结
| 概念 | 作用 |
|---|
| FIRST | 产生式「能以哪些终结符开头」 |
| FOLLOW | 可空产生式「在哪些向前看符号下仍可选」 |
| LL(1) 表 | M[A,a] 唯一决定 A 的展开 |
| 冲突 | 需消左递归 / 提左公因子,或改用 LR 族 |
下一篇:LR 语法分析(自底向上的分析表)。