4 、语法分析
一、上下文无关文法(CFG)🧀
1、基本概念🧀
CFG(Context-Free Grammar)用于描述程序语言的语法规则,写作 $$ G=(V,T,P,S) $$
- \(V\) 为非终结符,可以继续展开
- \(T\) 为终结符,不能继续展开
- \(P\) 为产生式,即替换规则
- \(S\) 为开始符号
例如
E、T、F 是非终结符,+、*、id 等是终结符。| 表示或者。
2、推导与语法树🧀
考虑文法
要生成 aab,可以这样推导 $$ S\Rightarrow aS\Rightarrow aaS\Rightarrow aab $$ 不能一开始就选择 S → b,因为这样只能得到 b,无法继续生成 aab。
对应的语法树如下
flowchart TD
S0((S)) --> A1[a]
S0 --> S1((S))
S1 --> A2[a]
S1 --> S2((S))
S2 --> B[b] 从左到右读取叶子节点,就得到 aab。
语法分析就是反过来,给定输入 aab,判断它能否由文法生成。
二、自顶向下分析🧀
自顶向下从开始符号出发,不断选择产生式,尝试生成输入串,也就是从语法树的根向叶子分析。
1、递归下降🧀
递归下降把每个非终结符写成一个函数。
例如
对应
match() 负责匹配并消费一个 Token,S() 则继续分析非终结符。整个输入还必须恰好消费完毕。
递归下降的问题在于,有时无法立即决定使用哪条产生式。
例如
看到 a 时,两条规则都有可能成立,某些实现就需要回溯。
2、LL 分析🧀
LL 是一种自顶向下分析方法。
- 第一个 L 表示从左到右扫描输入
- 第二个 L 表示最左推导,每次展开最左边的非终结符
- LL(1) 中的 1 表示只向前看一个 Token
例如
| 下一个 Token | 选择的产生式 |
|---|---|
a | S → aA |
b | S → bB |
只看一个 Token 就能确定规则,因此不需要回溯。
LL(1) 使用 FIRST、FOLLOW 和预测分析表来完成选择。
(1)FIRST 集
表示某个符号能够推导出的字符串,开头可能是什么。
因此 $$ FIRST(A)={a,b} $$ (2)FOLLOW 集
表示某个非终结符后面可能紧跟哪些终结符。
因此 b 属于 FOLLOW(A)。
如果一个非终结符可以推导为空串 ε,FOLLOW 就能帮助判断什么时候应该选择空产生式。
(3)LL(1) 分析表
分析器根据当前非终结符和下一个 Token 查表,确定使用哪条产生式。如果同一个表格位置需要填入两条不同的产生式,就产生冲突,说明该文法不能直接进行 LL(1) 分析。
3、左递归🧀
普通的 LL 预测分析不能直接处理左递归。
例如
分析 E 时又立即分析 E,会无限递归。
可以改写为
其中 ε 表示空串,即什么都不生成。
改写后相当于先识别一个 T,再识别零个或多个 + T。
三、自底向上分析🧀
自底向上与前面相反,从输入串出发,不断归约,最终得到开始符号。
例如
输入为 aab,归约过程是 $$ aab\Rightarrow aaS\Rightarrow aS\Rightarrow S $$ 也就是从语法树的叶子向根分析。
1、LR 分析🧀
LR 是典型的自底向上分析算法。
- L 表示从左到右扫描输入
- R 表示最右推导的逆过程
LR 主要有两个操作。
(1)Shift(移进)
从输入中读取一个 Token,放入分析栈。
(2)Reduce(归约)
根据产生式,把栈顶已经识别出的部分替换成非终结符。
例如
可以把栈顶的 id 归约为 T。
2、LR 分析过程🧀
仍然使用
输入为 aab。
| 步骤 | 栈(省略状态) | 剩余输入 | 操作 |
|---|---|---|---|
| 1 | 空 | aab$ | 开始 |
| 2 | a | ab$ | Shift |
| 3 | aa | b$ | Shift |
| 4 | aab | $ | Shift |
| 5 | aaS | $ | Reduce S → b |
| 6 | aS | $ | Reduce S → aS |
| 7 | S | $ | Reduce S → aS |
| 8 | S | $ | Accept |
$ 表示输入结束。
实际 LR 分析器还会通过 ACTION 表决定何时 Shift、Reduce 或 Accept,通过 GOTO 表决定归约后的状态,而不是看到匹配的符号就立即归约。
3、为什么叫最右推导的逆过程?🧀
例如
最右推导是每次展开最右边的非终结符。
LR 的归约则按相反的顺序进行
注意,LR 仍然从左向右读取输入,R 并不表示从右向左扫描。
四、LL 与 LR 的比较🧀
| 对比 | LL | LR |
|---|---|---|
| 分析方向 | 自顶向下 | 自底向上 |
| 出发点 | 开始符号 | 输入串 |
| 核心操作 | 预测产生式 | Shift / Reduce |
| 推导方式 | 最左推导 | 最右推导的逆过程 |
| 左递归 | 不能直接处理 | 可以处理 |
| 分析能力 | 相对较弱 | 通常更强 |
| 实现难度 | 较简单 | 较复杂 |
flowchart TD
A["语法分析"]
B["自顶向下 LL"]
C["自底向上 LR"]
D["从 S 出发,展开产生式"]
E["从输入出发,Shift / Reduce"]
A --> B --> D
A --> C --> E LL 与 LR 的主要区别在于,LL 较早地预测该使用哪条产生式,而 LR 先读取输入,再结合已有结构决定何时归约。