Post

语法分析

语法分析

语法分析要做什么事?

截屏2026-03-23 19.25.20

在词法分析的时候,我们的output是词法单元序列

在语法分析的时候,我们就需要以这种词法单元序列作为input,判断这个由词法单元token作为“字符”的“字符串”能不能由文法生成,如果可以,用生产式把字符串还原成一棵语法树【这棵树就是语法分析的output】

🌰举个栗子:

original input : a + b * 3 — 实际上是字符流

output of lexer : (id, a) (+) (id, b) (*) (num, 3)

output of parser :

1
2
3
4
5
          +
        /   \
       a     *
           /   \
          b     3

上下文无关文法

语法(syntax):程序的合法结构规则

文法(grammar):用形式化语言来描述这些结构规则

上下文无关文法(Context Free Grammar, CFG)是一种四元组形式的形式文法,通常记作 \(G=(V,Σ,P,S)\)

  • $V$ 为非终结符(变量)的有限集合
  • $Σ$ 为终结符的有限集合,且 $V∩Σ=∅$
  • $S∈V$ 为开始符号
  • $P$ 为产生式的有限集合,每个产生式形如 $A→α, A∈V, α∈(V∪Σ)∗$

在 CFG 中,每条产生式的左部只能是单个非终结符,而右部可以是任意长度的终结符与非终结符串

最后,我们就是要把tokens转化为只有终结符的语法树

上下文无关

上下文:在应用一个产生式进行推导时,前后已经推导出的部分结果就是上下文

无关:如果有产生式是$A → γ$,只要看到单个的非终结符A,就可以替换,而不是非得A 处于特定上下文 α、β 之中时,才能被替换为 γ

分析基础

最左推导(Leftmost Derivation):每一步总是展开最左边的非终结符 — 自顶向下

最右推导(Rightmost Derivation):每一步总是展开最右边的非终结符 — 自底向上是最右推导的逆过程

消除二义性

二义性,即文法可以为一个句子生成多棵不同的树

id + id * id

截屏2026-03-23 20.23.45

消除左递归

左递归:文法中一个非终结符号A使得对某个串α,存在一个推导$A \xrightarrow{+} A \alpha$,则称这个文法是左递归的

在自顶向下的分析中,左递归会导致无限递归!

直接(立即)左递归

即直接存在A → A α

直接左递归改写方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
A → Aα1 | Aα2 | … | Aαm | β1 | β2 | … | βn

step1:分组
A → Aα1 | Aα2 | … | Aαm | β1 | β2| … | βn
    --------组1---------  -----组2--------
    
step2:替换
A → β1 A’ | β2 A’ | … | βn A’
A’ → α1A’ | α2A’ | … | αmA’ | ε


本质上就是:
A → A α | β 替换成:

A  → β A'
A' → α A' | ε

注意:必须要有ε,否则无法匹配β

间接左递归

如:

1
2
A → B α
B → A β

算法:

1
2
3
4
5
将非终结符排序 A₁, A₂, ..., Aₙ
for i = 1 to n:
    for j = 1 to i-1:
        替换 Aᵢ → Aⱼ γ
    消除 Aᵢ 的直接左递归

其实就是先消除间接,然后再处理直接

🌰举例:id + id * id

规则:

\[\begin{aligned} E &\to T \, E' \\ E' &\to + \, T \, E' \mid \epsilon \\ T &\to F \, T' \\ T' &\to * \, F \, T' \mid \epsilon \\ F &\to (E) \mid id \end{aligned}\]

截屏2026-03-24 10.32.27

提取左公因子

消除左递归解决的是死循环问题,而提取左公因子解决的是“选不出来”的问题

如果 A → a b | a c,我该怎么判断到底应该选择ab还是ac呢?

很简单,改写一下就好了:

1
2
A → a A'
A' → b | c

本质上就是分层判断

🌰举个例子(dangling else):

1
2
S → if E then S else S
   | if E then S

提取后:

1
2
S  → if E then S S'
S' → else S | ε

自顶向下的语法分析技术

自顶向下分析可以被看作是为输入串构造语法分析树的问题,也可以看作一个寻找输入串的最左推导的过程

问题: 在推导的每一步,对非终结符号A,应用哪个产生式,以可能产生于输入串相匹配的终结符号串

通用的递归下降分析框架

1
2
3
4
5
6
7
8
9
10
void A() {
    选择一个 A 产生式,A → X1 X2 ... Xk;
    for ( i = 1 to k ) {
        if ( Xi 是一个非终结符号 )
            调用过程 Xi();
        else if ( Xi 等于当前的输入符号 a )
            读入下一个输入符号;
        else /* 发生了一个错误 */;
    }
}

预测分析法Predictive Parsing — 无回溯!

核心:根据当前输入符号和预测表(Parsing Table,由FIRST + FOLLOW 构造),唯一确定使用哪条产生式

基本要求:文法必须是 LL(1) 文法

有多个可能的产生式时预测分析法无能为力,即:在任意一个非终结符的选择分支中,前瞻符号能唯一确定选择的产生式

LL(1):

第一个L:从左到右扫描输入(Left-to-right)

第二个L:最左推导(Leftmost derivation)

1:只需要一个前瞻符号 Lookahead

核心结构:二维表M[A,a] ,含义:当栈顶为A,当前input为a,该用哪个产生式呢?

三种情况:

1
2
栈顶 X
输入 a
  1. X是终结符 — X == a → match — 栈弹出,输入前进
  2. X是非终结符A — 查 M[A, a],如果M[A, a] = A → α — 弹出 A,压入 α(逆序)
  3. 查不到 — 语法错误

FIRST

对符号串 $ \alpha \in (V \cup \Sigma)^* $:

\[FIRST(\alpha) = \{ a \in \Sigma \mid \alpha \Rightarrow^* a\beta \} \cup \{ \epsilon \mid \alpha \Rightarrow^* \epsilon \}\]

特例:

终结符a:FIRST(a)={a}

空串 ϵ:FIRST(ϵ)={ϵ}

递推规则:

  • FIRST(a)={a}(a 为终结符);FIRST(ϵ)={ϵ}
  • FIRST(A)(A 为非终结符):对每个产生式 A→α,把 FIRST(α)- {ϵ}加入FIRST(A);若 α⇒∗ϵ,再把 ϵ 加入

FOLLOW

对非终结符 ( A ): \(FOLLOW(A) = \{ a \in \Sigma \mid \exists S \Rightarrow^* \alpha A a \beta \} \cup (\{ \$ \} \text{ 若 } A \text{ 能出现在句子末尾})\)

直观理解:能紧跟在A之后出现的终结符集合;起始符号S的FOLLOW 集含输入结束标记 $

递推规则:

  • 将 $ 加入 FOLLOW(S)(S 为开始符号)
  • 对每个产生式 A→αBβ:
    • 把 FIRST(β)- {ϵ} 加入 FOLLOW(B)
    • 若 ϵ∈FIRST(β) 或 β=ϵ,把 FOLLOW(A) 加入 FOLLOW(B)

构造预测表

\[SELECT(A \to \alpha) = FIRST(\alpha) \cup \begin{cases} FOLLOW(A) & \text{if } \epsilon \in FIRST(\alpha) \\ \varnothing & \text{otherwise} \end{cases}\]

对于一个产生式 A→α,SELECT(A→α)含义:在什么情况下,预测分析表会用这条产生式

为什么用SELECT?

  • 在构造预测分析表 M[A, a] 时:对于每一条产生式 A→α,对于所有 a∈SELECT(A→α),填入 M[A,a]=A→α;换句话说,SELECT 集合决定了“当下一个输入符号是 a 时,要不要选择这条产生式”
  • LL(1) 的判别条件:文法是 LL(1) 的条件之一就是:对于同一非终结符 A的不同产生式,它们的SELECT集合必须两两不相交,这样,当下一个输入符号是 a 时,表格里只有唯一一条产生式,没有冲突

‼️如果 SELECT 集合有交集 ⇒ 预测表冲突 ⇒ 不是 LL(1)‼️

用 FIRST/FOLLOW 构造 LL(1) 预测表
  • 对每个产生式 A→α:
    • 对所有 a∈FIRST(α)- {ϵ} :填入 M[A,a]=A→α
    • 若 ϵ∈FIRST(α) ,则对所有 b∈FOLLOW(A) :填入M[A,b]=A→α
    • 若某表项出现冲突(同一格两条以上产生式),文法非 LL(1)
  • 对于LL(1)文法,预测表中每个条目都唯一地指定了一个产生式,或者标明Error

截屏2026-03-24 18.34.10

🍐一个完整的例子:

2d99328abebb3b1101e974836d849466_720

This post is licensed under CC BY 4.0 by the author.

Trending Tags