1. 引言
在编译器前端的设计中,语法分析(Syntax Analysis)扮演着至关重要的角色。它的主要任务是根据词法分析器提供的记号(Tokens)流,识别出源程序的语法结构。
自顶向下语法分析(Top-Down Parsing)作为一种直观且应用广泛的分析方法,其核心思想是:从文法的开始符号出发,试图推导出与输入符号串相匹配的句子 。本文将结合编译原理的核心考点,深度剖析如何通过消除左递归、提取左公因子将文法改写为 LL(1) 文法 ,并详细讲解 FIRST 集、FOLLOW 集 的计算以及 预测分析表 的构建过程。
2. 自顶向下语法分析的核心理论回顾
在动手解题之前,我们需要牢记自顶向下语法分析的三个核心概念:
2.1 消除左递归与提取左公因子
自顶向下分析不能处理含有 左递归 的文法(会导致无限递归)。
-
消除直接左递归:
若文法产生式为 A→Aα∣βA \to A\alpha \mid \betaA→Aα∣β,其中 β\betaβ 不以 AAA 开头,可改写为:
A→βA′A \to \beta A'A→βA′
A′→αA′∣ϵA' \to \alpha A' \mid \epsilonA′→αA′∣ϵ -
提取左公因子:
若产生式为 A→αβ1∣αβ2A \to \alpha\beta_1 \mid \alpha\beta_2A→αβ1∣αβ2,通过提取公因子 α\alphaα,可改写为:
A→αA′A \to \alpha A'A→αA′
A′→β1∣β2A' \to \beta_1 \mid \beta_2A′→β1∣β2
2.2 LL(1) 文法的判别条件
一个文法是 LL(1) 文法,当且仅当对于每个非终结符 AAA 的任意两个不同的产生式 A→α∣βA \to \alpha \mid \betaA→α∣β,满足以下条件:
- FIRST(α)∩FIRST(β)=∅FIRST(\alpha) \cap FIRST(\beta) = \emptysetFIRST(α)∩FIRST(β)=∅
- 若 β⇒∗ϵ\beta \Rightarrow^* \epsilonβ⇒∗ϵ,则 FIRST(α)∩FOLLOW(A)=∅FIRST(\alpha) \cap FOLLOW(A) = \emptysetFIRST(α)∩FOLLOW(A)=∅
3. 经典习题深度拆解与实战演练
下面我们通过 5 道经典的编译原理大题,完整走一遍自顶向下语法分析的全流程。
【题目 4-1】文法推导与非递归预测分析
已知文法 G[S]G[S]G[S]:
- S→a∣∧∣(T)S \to a \mid \wedge \mid (T)S→a∣∧∣(T)
- T→T,S∣ST \to T,S \mid ST→T,S∣S
① 给出串 (a,(a,a)) 和 (((a,a), ^, (a)),a) 的最左推导过程
- 最左推导的核心: 每次总是替换推导树中最左边的非终结符。
推导1:(a,(a,a))
- S⇒(T)S \Rightarrow (T)S⇒(T)
- ⇒(T,S)\Rightarrow (T, S)⇒(T,S)
- ⇒(S,S)\Rightarrow (S, S)⇒(S,S)
- ⇒(a,S)\Rightarrow (a, S)⇒(a,S)
- ⇒(a,(T))\Rightarrow (a, (T))⇒(a,(T))
- ⇒(a,(T,S))\Rightarrow (a, (T, S))⇒(a,(T,S))
- ⇒(a,(S,S))\Rightarrow (a, (S, S))⇒(a,(S,S))
- ⇒(a,(a,S))\Rightarrow (a, (a, S))⇒(a,(a,S))
- ⇒(a,(a,a))\Rightarrow (a, (a, a))⇒(a,(a,a)) (推导成功)
推导2:(((a,a), ^, (a)),a)
- S⇒(T)S \Rightarrow (T)S⇒(T)
- ⇒(T,S)\Rightarrow (T, S)⇒(T,S)
- ⇒(S,S)\Rightarrow (S, S)⇒(S,S)
- ⇒((T),S)\Rightarrow ((T), S)⇒((T),S)
- ⇒((T,S),S)\Rightarrow ((T, S), S)⇒((T,S),S)
- ⇒((T,S,S),S)\Rightarrow ((T, S, S), S)⇒((T,S,S),S)
- ⇒((S,S,S),S)\Rightarrow ((S, S, S), S)⇒((S,S,S),S)
- ⇒(((T),S,S),S)\Rightarrow (((T), S, S), S)⇒(((T),S,S),S)
- ⇒(((T,S),S,S),S)\Rightarrow (((T, S), S, S), S)⇒(((T,S),S,S),S)
- ⇒(((S,S),S,S),S)\Rightarrow (((S, S), S, S), S)⇒(((S,S),S,S),S)
- ⇒(((a,S),S,S),S)\Rightarrow (((a, S), S, S), S)⇒(((a,S),S,S),S)
- ⇒(((a,a),S,S),S)\Rightarrow (((a, a), S, S), S)⇒(((a,a),S,S),S)
- ⇒(((a,a),∧,S),S)\Rightarrow (((a, a), \wedge, S), S)⇒(((a,a),∧,S),S)
- ⇒(((a,a),∧,(T)),S)\Rightarrow (((a, a), \wedge, (T)), S)⇒(((a,a),∧,(T)),S)
- ⇒(((a,a),∧,(S)),S)\Rightarrow (((a, a), \wedge, (S)), S)⇒(((a,a),∧,(S)),S)
- ⇒(((a,a),∧,(a)),S)\Rightarrow (((a, a), \wedge, (a)), S)⇒(((a,a),∧,(a)),S)
- ⇒(((a,a),∧,(a)),a)\Rightarrow (((a, a), \wedge, (a)), a)⇒(((a,a),∧,(a)),a) (推导成功)
② 对文法 GGG 进行改写(消除左递归),并判断改写后的文法是否为 LL(1)
原产生式中 T→T,S∣ST \to T,S \mid ST→T,S∣S 存在直接左递归,引入新非终结符 T′T'T′ 改写文法:
- S→a∣∧∣(T)S \to a \mid \wedge \mid (T)S→a∣∧∣(T)
- T→ST′T \to S T'T→ST′
- T′→,ST′∣ϵT' \to ,ST' \mid \epsilonT′→,ST′∣ϵ
LL(1) 判断:
- FIRST(a)∩FIRST(∧)∩FIRST((T))=∅FIRST(a) \cap FIRST(\wedge) \cap FIRST((T)) = \emptysetFIRST(a)∩FIRST(∧)∩FIRST((T))=∅
- FIRST(,ST′)∩FOLLOW(T′)={,}∩{),#}=∅FIRST(,ST') \cap FOLLOW(T') = \{ , \} \cap \{ ), \# \} = \emptysetFIRST(,ST′)∩FOLLOW(T′)={,}∩{),#}=∅
因此改写后的文法是 LL(1) 文法。
③ 构建预测分析表
| 非终结符 | aaa | ^ |
((( | ))) | ,,, | #\## |
|---|---|---|---|---|---|---|
| SSS | S→aS \to aS→a | S→∧S \to \wedgeS→∧ | S→(T)S \to (T)S→(T) | |||
| TTT | T→ST′T \to ST'T→ST′ | T→ST′T \to ST'T→ST′ | T→ST′T \to ST'T→ST′ | |||
| T′T'T′ | T′→ϵT' \to \epsilonT′→ϵ | T′→,ST′T' \to ,ST'T′→,ST′ | T′→ϵT' \to \epsilonT′→ϵ |
④ 给出输入串 (a,a)# 的分析过程
| 步骤 | 符号栈 | 输入串 | 所用产生式 / 动作 |
|---|---|---|---|
| 1 | #S\#S#S | (a,a)#(a,a)\#(a,a)# | S→(T)S \to (T)S→(T) |
| 2 | #)T(\#)T(#)T( | (a,a)#(a,a)\#(a,a)# | 匹配 ((( |
| 3 | #)T\#)T#)T | a,a)#a,a)\#a,a)# | T→ST′T \to ST'T→ST′ |
| 4 | #)T′S\#)T'S#)T′S | a,a)#a,a)\#a,a)# | S→aS \to aS→a |
| 5 | #)T′a\#)T'a#)T′a | a,a)#a,a)\#a,a)# | 匹配 aaa |
| 6 | #)T′\#)T'#)T′ | ,a)#,a)\#,a)# | T′→,ST′T' \to ,ST'T′→,ST′ |
| 7 | #)T′S,\#)T'S,#)T′S, | ,a)#,a)\#,a)# | 匹配 ,,, |
| 8 | #)T′S\#)T'S#)T′S | a)#a)\#a)# | S→aS \to aS→a |
| 9 | #)T′a\#)T'a#)T′a | a)#a)\#a)# | 匹配 aaa |
| 10 | #)T′\#)T'#)T′ | )#)\#)# | T′→ϵT' \to \epsilonT′→ϵ |
| 11 | #)\#)#) | )#)\#)# | 匹配 ))) |
| 12 | #\## | #\## | 接受(成功) |
【题目 4-2】FIRST 集与 FOLLOW 集计算实战
已知文法 G[E]G[E]G[E] 如下:
- E→TE′E \to TE'E→TE′
- E′→+E∣ϵE' \to +E \mid \epsilonE′→+E∣ϵ
- T→FT′T \to FT'T→FT′
- T′→T∣ϵT' \to T \mid \epsilonT′→T∣ϵ
- F→PF′F \to PF'F→PF′
- F′→∗F′∣ϵF' \to *F' \mid \epsilonF′→∗F′∣ϵ
- P→(E)∣a∣b∣∧P \to (E) \mid a \mid b \mid \wedgeP→(E)∣a∣b∣∧
① 计算该文法的 FIRST 集和 FOLLOW 集
为了彻底防止 KaTeX 渲染报错,集合中的特殊符号均采用标准 Markdown 格式表示:
| 非终结符 | FIRST 集 | FOLLOW 集 |
|---|---|---|
| EEE | { (, a, b, ^ } |
{ ), # } |
| E′E'E′ | { +, ϵ\epsilonϵ } |
{ ), # } |
| TTT | { (, a, b, ^ } |
{ +, ), # } |
| T′T'T′ | { (, a, b, ^, ϵ\epsilonϵ } |
{ +, ), # } |
| FFF | { (, a, b, ^ } |
{ (, a, b, ^, +, ), # } |
| F′F'F′ | { *, ϵ\epsilonϵ } |
{ (, a, b, ^, +, ), # } |
| PPP | { (, a, b, ^ } |
{ *, (, a, b, ^, +, ), # } |
② 构建预测分析表
根据 FIRST 集和 FOLLOW 集,可构建出如下预测分析表:
| 非终结符 | +++ | ∗*∗ | ((( | ))) | aaa | bbb | ^ |
#\## |
|---|---|---|---|---|---|---|---|---|
| EEE | E→TE′E \to TE'E→TE′ | E→TE′E \to TE'E→TE′ | E→TE′E \to TE'E→TE′ | E→TE′E \to TE'E→TE′ | ||||
| E′E'E′ | E′→+EE' \to +EE′→+E | E′→ϵE' \to \epsilonE′→ϵ | E′→ϵE' \to \epsilonE′→ϵ | |||||
| TTT | T→FT′T \to FT'T→FT′ | T→FT′T \to FT'T→FT′ | T→FT′T \to FT'T→FT′ | T→FT′T \to FT'T→FT′ | ||||
| T′T'T′ | T′→ϵT' \to \epsilonT′→ϵ | T′→TT' \to TT′→T | T′→ϵT' \to \epsilonT′→ϵ | T′→TT' \to TT′→T | T′→TT' \to TT′→T | T′→TT' \to TT′→T | T′→ϵT' \to \epsilonT′→ϵ | |
| FFF | F→PF′F \to PF'F→PF′ | F→PF′F \to PF'F→PF′ | F→PF′F \to PF'F→PF′ | F→PF′F \to PF'F→PF′ | ||||
| F′F'F′ | F′→ϵF' \to \epsilonF′→ϵ | F′→∗F′F' \to *F'F′→∗F′ | F′→ϵF' \to \epsilonF′→ϵ | F′→ϵF' \to \epsilonF′→ϵ | F′→ϵF' \to \epsilonF′→ϵ | F′→ϵF' \to \epsilonF′→ϵ | F′→ϵF' \to \epsilonF′→ϵ | F′→ϵF' \to \epsilonF′→ϵ |
| PPP | P→(E)P \to (E)P→(E) | P→aP \to aP→a | P→bP \to bP→b | P→∧P \to \wedgeP→∧ |
【题目 4-3】复杂文法分析表的构建
已知文法 G[S]G[S]G[S]:
- S→MH∣aS \to MH \mid aS→MH∣a
- H→LSo∣ϵH \to LSo \mid \epsilonH→LSo∣ϵ
- K→dML∣ϵK \to dML \mid \epsilonK→dML∣ϵ
- L→eHfL \to eHfL→eHf
- M→K∣bLMM \to K \mid bLMM→K∣bLM
① 判断是否为 LL(1) 文法
由于 FIRST(K)∩FIRST(bLM)={d,ϵ}∩{b}=∅FIRST(K) \cap FIRST(bLM) = \{ d, \epsilon \} \cap \{ b \} = \emptysetFIRST(K)∩FIRST(bLM)={d,ϵ}∩{b}=∅ 且 FIRST(K)∩FOLLOW(M)=∅FIRST(K) \cap FOLLOW(M) = \emptysetFIRST(K)∩FOLLOW(M)=∅,各候选式 FIRST 集两两不相交,因此满足 LL(1) 文法要求。
② 构建预测分析表
| 非终结符 | aaa | ddd | bbb | eee | ooo | fff | #\## |
|---|---|---|---|---|---|---|---|
| SSS | S→aS \to aS→a | S→MHS \to MHS→MH | S→MHS \to MHS→MH | S→MHS \to MHS→MH | S→MHS \to MHS→MH | S→MHS \to MHS→MH | |
| HHH | H→LSoH \to LSoH→LSo | H→ϵH \to \epsilonH→ϵ | H→ϵH \to \epsilonH→ϵ | ||||
| KKK | K→dMLK \to dMLK→dML | K→ϵK \to \epsilonK→ϵ | K→ϵK \to \epsilonK→ϵ | K→ϵK \to \epsilonK→ϵ | |||
| LLL | L→eHfL \to eHfL→eHf | ||||||
| MMM | M→KM \to KM→K | M→bLMM \to bLMM→bLM | M→ϵM \to \epsilonM→ϵ | M→ϵM \to \epsilonM→ϵ | M→ϵM \to \epsilonM→ϵ |
【题目 4-5】程序语言结构文法的 LL(1) 改写
原题目文法:
<程序> -> begin <语句表> end<语句表> -> <语句> ; <语句表> ; <语句><语句> -> <无条件语句> | <条件语句><无条件语句> -> a<条件语句> -> if b then <无条件语句> | if b then <无条件语句> else <语句>
为了避免被误识别为 HTML 标签,所有的非终结符加了反斜杠转义:
改写后的文法:
\<程序\> -> begin \<语句表\> end\<语句表\> -> \<语句\> \<语句表'\>\<语句表'\> -> ; \<语句\> \<语句表'\> | ε\<语句\> -> \<无条件语句\> | \<条件语句\>\<无条件语句\> -> a\<条件语句\> -> if b then \<语句\> \<else部分\>\<else部分\> -> else \<语句\> | ε
【题目 4-7】理论分析与文法改写
① 答疑题:对于一个文法,消除左递归、提取左公因子后是否一定是 LL(1) 文法?
答案:不一定。
消除左递归和提取左公因子是改写文法的常用技术,但不能保证文法一定是 LL(1) 文法。因为可能依然存在某个非终结符 AAA,其不同产生式的 FIRST 集交集不为空,或者当某产生式可推导出 ϵ\epsilonϵ 时,其 FIRST 集与 FOLLOW(A) 集仍存在交集(发生冲突)。
② 文法改写与判定:
(1) 已知文法:
- A→baB∣ϵA \to baB \mid \epsilonA→baB∣ϵ
- B→Abb∣aB \to Abb \mid aB→Abb∣a
改写过程:
消除隐式左递归,将 AAA 的候选式代入 BBB 中:
- B→baBbb∣bb∣aB \to baBbb \mid bb \mid aB→baBbb∣bb∣a
对 BBB 提取左公因子:
- B→bB′∣aB \to bB' \mid aB→bB′∣a
- B′→aBbb∣bB' \to aBbb \mid bB′→aBbb∣b
经计算,改写后的文法没有 FIRST/FOLLOW 冲突,是 LL(1) 文法。
(2) 已知文法:
- A→aABe∣aA \to aABe \mid aA→aABe∣a
- B→Bb∣dB \to Bb \mid dB→Bb∣d
改写过程:
首先消除 B→Bb∣dB \to Bb \mid dB→Bb∣d 的直接左递归:
- B→dB′B \to dB'B→dB′
- B′→bB′∣ϵB' \to bB' \mid \epsilonB′→bB′∣ϵ
对 A→aABe∣aA \to aABe \mid aA→aABe∣a 提取左公因子:
- A→aA′A \to aA'A→aA′
- A′→ABe∣ϵA' \to ABe \mid \epsilonA′→ABe∣ϵ
验证:
由于 FIRST(ABe)={a}FIRST(ABe) = \{ a \}FIRST(ABe)={a} 且 FOLLOW(A)FOLLOW(A)FOLLOW(A) 中包含 FIRST(Be)FIRST(Be)FIRST(Be)。由于两者的交集不为空(存在交集),故该文法不是 LL(1) 文法。
4. 总结
在进行自顶向下语法分析时,核心考点通常落在 FIRST/FOLLOW 集计算 和 LL(1) 预测分析表构建。在实际解题或考试中,可以总结出以下固定套路:
- 文法检查: 先看有没有显式或隐式的左递归,有则立刻消除。
- 提取公因子: 检查同一个非终结符的候选式是否具有相同前缀。
- 精准求集: 特别注意 FOLLOW 集计算中开始符号自带
#,以及 ϵ\epsilonϵ 产生式带来的递归传递。 - 填表判断: 如果分析表中任何一个单元格都不包含多重产生式,则该文法是 LL(1) 文法。