编译原理题型总结笔记

主要题型(根据 23 级考题)

  1. NFA、DFA 画图、转换、最小 DFA
  2. 画语法树,找短语、简单短语和句柄
  3. 求三个集合和递归下降程序编写
  4. 驻留法求符号表
  5. 写四元式并优化它

NFA,DFA 和正则表达式

概念题

与DFA相比,NFA的非确定性体现在:

A. 允许有多个开始状态

B. 允许有多个终止状态

C. 在没有任何输入的情况下允许进行状态转换

D. 一个状态可以有多个不同后继状态

选择 AC,注意 D 说的应该是在同一个输入下,一个状态可以有多个不同的后继状态。


正则表达式和有限自动机的关系以下说法正确的是:

A. 一个正则表达式只能等价于一个确定的有限自动机

B. 正则表达式、NFA和DFA在接受语言的能力上是相互等价的

C. 对任何形式正则表达式r,都存在一个NFA M,满足L(M)=L®

D. 一个正则表达式可以转化为一个等价的自动机,但自动机不一定都能表示为等价的正则表达式

选择 BC。

A,一个正则表达式可以对应多个等价的有限自动机。比如同一个正则表达式可以先构造出一个 NFA,再转成 DFA,还可以继续最小化 DFA;这些自动机结构可能不同,但接受的语言相同。题干说“只能等价于一个确定的有限自动机”,把“语言等价”误解成了“结构唯一”。

B,正则表达式、NFA、DFA 都接受正则语言,所以识别能力相同。NFA 可以通过子集构造转成 DFA,正则表达式也可以构造出等价自动机。

C,对任意正则表达式 rr,都可以构造一个 NFA MM,使得 L(M)=L(r)L(M)=L(r),这是正则表达式到自动机转换的基本结论。

D,自动机也可以转换为等价的正则表达式。

正则表达式、NFA、DFA 是同一类语言的三种表示法

正则表达式NFADFA\text{正则表达式}\Longleftrightarrow \text{NFA}\Longleftrightarrow \text{DFA}

文法相关概念

文法一般写成:

G=(VN,VT,S,P)G=(V_N,V_T,S,P)

其中:

概念含义解释
VNV_N非终结符集合可以继续展开的“语法类别”,如 E,T,FE,T,F
VTV_T终结符集合最终出现在程序里的基本符号,如 id,+,,(,)\text{id},+,*,(,)
SS开始符号推导的起点,且 SVNS\in V_N
PP产生式集合规定如何展开非终结符

文法符号全集常写作:

V=VNVT,VNVT=V=V_N\cup V_T,\quad V_N\cap V_T=\varnothing

终结符从语法分析角度看是语言中不可再分的基本符号,通常对应词法分析输出的 Token 类型。

产生式、终结符、非终结符

上下文无关文法中的产生式一般写成:

AαA\to\alpha

其中:

AVNA\in V_N

α(VTVN)\alpha\in (V_T\cup V_N)^*

AA 是产生式左部,也叫头;α\alpha 是产生式右部,也叫体;\to::=::= 读作“定义为”“推导出”。例如:

EE+TE\to E+T

TFT\to F

FiF\to i

这里 E,T,FE,T,F 是非终结符,+,i+,i 是终结符。

文法的分类

文法分类按乔姆斯基层次来记,层级从强到弱是:00\supset 11\supset 22\supset 33

类型名称产生式形式对应语言对应模型
00无限制文法 / 短语文法αβ\alpha\to\beta,且 α\alpha 中含非终结符递归可枚举语言图灵机
11上下文有关文法αAβαγβ\alpha A\beta\to\alpha\gamma\beta,通常要求长度增长或保持上下文有关语言线性有界自动机
22上下文无关文法AαA\to\alpha上下文无关语言下推自动机
33正则文法右线性 AaBA\to aBAaA\to a;左线性 ABaA\to BaAaA\to a正则语言有限自动机

编译原理中最常用的是 22 型和 33 型。通常用 33 型文法或正则表达式描述词法结构,用 22 型文法描述表达式、语句等语法结构;“文法”无特殊说明时通常指上下文无关文法。

推导、句型、句子、语言

核心关系是:

SαS\Rightarrow^*\alpha

表示从开始符号 SS 经过零步或多步推导得到 α\alpha

概念定义例子
推导用产生式把某个非终结符替换成右部EE+TE\Rightarrow E+T
句型SS 推导出来的任意符号串E+TE+Ti+Ti+Ti+ii+i
句子只含终结符的句型i+ii+i
语言文法能推出的所有句子的集合L(G)={wSw, wVT}L(G)=\{w\mid S\Rightarrow^*w,\ w\in V_T^*\}

句型可以含非终结符,句子只含终结符,语言是所有句子的集合。句子是句型的特例。

语法树

语法树是推导过程的图形表示。它的规则是:

  • 根节点标记为开始符号 SS
  • 内部节点通常是非终结符
  • 叶节点可以是终结符,也可以是非终结符

如果某个内部节点 AA 有从左到右的孩子 X1,X2,,XnX_1,X_2,\dots,X_n,那么文法中必须有产生式:AX1X2XnA\to X_1X_2\cdots X_n

语法树既反映推导过程,也反映句型的层次结构。语法树的“父节点扩展为子节点”对应一次产生式使用。


语法树的所有叶节点从左到右连起来,就是一个句型。

如果所有叶节点都是终结符,那么这个叶节点串就是句子。

子树和简单子树

  • 子树是从某个节点开始,把它下面所有后代节点都包含进去形成的树。
  • 简单子树,也叫直接子树,是某个节点和它的直接孩子组成的一层树。它直接对应一个产生式。

简单子树的特点是“一步展开”。普通子树可以包含多步展开。简单子树是最小的结构单元,直接对应文法中的一个产生式规则。

短语、简单短语、句柄

这三个概念都要放在某个具体句型和某棵语法树中理解。

短语

如果某棵子树的叶节点从左到右连起来是 β\beta,那么 β\beta 是该句型的一个短语。

形式化地说:

SαAγAβS\Rightarrow^*\alpha A\gamma\\ A\Rightarrow^*\beta

所以:

SαAγαβγS\Rightarrow^*\alpha A\gamma\Rightarrow^*\alpha\beta\gamma

那么 β\beta 是句型 αβγ\alpha\beta\gamma 相对于 AA 的短语。短语对应子树,允许多步推导

简单短语 / 直接短语

如果某棵简单子树的叶节点从左到右连起来是 β\beta,那么 β\beta 是简单短语。简单短语对应简单子树,只看一步产生式右部

句柄

句柄是当前句型中需要被归约的简单短语。按照笔记里的表述,它是最左简单子树叶节点从左到右组成的符号串;在自底向上分析中,每一步识别并归约的对象就是句柄。对于无歧义文法,句柄唯一。

记忆方式:句柄是自底向上分析中“当前该收回去”的那一段

包含关系

可以这样记:

简单短语 \subseteq 短语

句柄 \in 简单短语集合

也就是说,短语范围最大,简单短语更小,句柄是分析过程中当前要归约的那个简单短语。

更具体地说:

  • 子树 \longleftrightarrow 短语
  • 简单子树 \longleftrightarrow 简单短语
  • 最左简单子树 \longleftrightarrow 句柄
  • 整棵语法树的叶节点 \longleftrightarrow 句型或句子

三个集合的求法

First 集

找到左部是该非终结符的展开式,看得到的第一个项。如果得到的是终结符,就加入 First 集;否则继续递归查询;如果得到的是 ε\varepsilon,就也加进来,并计算下一个符号。First 可以有空串。

Follow 集

按下面的步骤来做:

  1. 输入符首先有 #\#
  2. 看右部,找到该非终结符,如果它后面跟着终结符,则把这个终结符加入 Follow 集否则加入后面这个非终结符的 First 集减去 ε\varepsilon
  3. 看右部,找到该终结符,如果它后面的非终结符能够消失变成 ε\varepsilon,或者它后面没东西,那就加入左部的 Follow 集

Follow 集不会有空串

Predict 集

对于:

AbA\to b\dots

就是求:

Predict(Ab)={First(b),εFirst(b)[First(b)ε]Follow(A),εFirst(b)\text{Predict}(A\to b\dots)=\begin{cases}\text{First}(b)&, \varepsilon\notin\text{First}(b)\\ [\text{First}(b)-\varepsilon]\cup\text{Follow}(A)&, \varepsilon\in\text{First}(b) \end{cases}

例题

首先需要包含 TT' 的 First 集减去 ε\varepsilon

TT' 的 First 集是 {ε,}\{\varepsilon, *\}。可以是空串,所以要加入左部的 Follow 集。

然后,加入 TT 的 Follow 集。现在开始求 TT 的 Follow 集。它应该首先包含 EE' 的 First 集,也就是 {ε,+}\{\varepsilon, +\},又因为 EE' 可以是空,所以要加入 EEEE' 的 Follow 集。

EE 的 Follow 集显然是 {#,)}\lbrace\#, )\rbraceFollow(E)Follow(E)\text{Follow}(E')\subset \text{Follow}(E) ,所以综上所述,应该选择 C。

LL(1) 分析表

标准构表流程是:先求每个产生式右部的 FIRST\text{FIRST},再求各非终结符的 FOLLOW\text{FOLLOW},最后按规则把产生式填入表格。

终结符集合是:a,b,d,e,#{a,b,d,e,\# }

非终结符集合是:S,T,R,D{S,T,R,D}


先求 FIRST\text{FIRST} 集。

DabdD\to a\mid bd,所以:FIRST(D)=a,b\text{FIRST}(D)={a,b}

RdRεR\to dR\mid \varepsilon,所以:FIRST(R)=d,ε\text{FIRST}(R)={d,\varepsilon}

TDRεT\to DR\mid \varepsilon,因为 FIRST(D)=a,b\text{FIRST}(D)={a,b},所以:FIRST(DR)=a,b\text{FIRST}(DR)={a,b}

再加上 TεT\to\varepsilonFIRST(T)=a,b,ε\text{FIRST}(T)={a,b,\varepsilon}SeTRTS\to eT\mid RT

其中:FIRST(eT)=e\text{FIRST}(eT)={e}

再看 RTRTFIRST(R)=d,ε\text{FIRST}(R)={d,\varepsilon}

因为 RR 可以推出 ε\varepsilon,所以还要看 TTFIRST(T)=a,b,ε\text{FIRST}(T)={a,b,\varepsilon}

因此:FIRST(RT)=d,a,b,ε\text{FIRST}(RT)={d,a,b,\varepsilon}

所以:FIRST(S)=e,d,a,b,ε\text{FIRST}(S)={e,d,a,b,\varepsilon}


再求 FOLLOW\text{FOLLOW} 集。

开始符号是 SS,所以:#FOLLOW(S)\#\in \text{FOLLOW}(S)

因此:FOLLOW(S)={#}\text{FOLLOW}(S)=\lbrace\#\rbrace

看产生式:SeTS\to eT

TT 在产生式末尾,所以:FOLLOW(S)FOLLOW(T)\text{FOLLOW}(S)\subseteq \text{FOLLOW}(T),得到:#FOLLOW(T)\#\in \text{FOLLOW}(T)

再看:SRTS\to RTRR 后面跟着 TT,所以:FIRST(T)εFOLLOW(R)\text{FIRST}(T)-{\varepsilon}\subseteq \text{FOLLOW}(R)

因为:FIRST(T)=a,b,ε\text{FIRST}(T)={a,b,\varepsilon},所以:a,bFOLLOW(R){a,b}\subseteq \text{FOLLOW}(R)

又因为 TεT\Rightarrow^*\varepsilon,所以:FOLLOW(S)FOLLOW(R)\text{FOLLOW}(S)\subseteq \text{FOLLOW}(R),得到:#FOLLOW(R)\#\in \text{FOLLOW}(R)

同时 TT 在末尾,所以:FOLLOW(S)FOLLOW(T)\text{FOLLOW}(S)\subseteq \text{FOLLOW}(T),因此:FOLLOW(T)={#}\text{FOLLOW}(T)=\lbrace\#\rbrace

再看:TDRT\to DR,DD 后面跟着 RR,所以:FIRST(R)εFOLLOW(D)\text{FIRST}(R)-{\varepsilon}\subseteq \text{FOLLOW}(D),因为:FIRST(R)=d,ε\text{FIRST}(R)={d,\varepsilon},所以:dFOLLOW(D)d\in \text{FOLLOW}(D)

又因为 RεR\Rightarrow^*\varepsilon,所以:FOLLOW(T)FOLLOW(D)\text{FOLLOW}(T)\subseteq \text{FOLLOW}(D),得到:#FOLLOW(D)\#\in \text{FOLLOW}(D)

因此最终为:

FOLLOW(S)={#}\text{FOLLOW}(S)=\lbrace\#\rbrace

FOLLOW(T)={#}\text{FOLLOW}(T)=\lbrace\#\rbrace

FOLLOW(R)=a,b,#\text{FOLLOW}(R)={a,b,\# }

FOLLOW(D)=d,#\text{FOLLOW}(D)={d,\# }


构造 LL(1) 分析表的规则是:

对于产生式 AαA\to\alpha

如果 xFIRST(α)x\in \text{FIRST}(\alpha),且 xεx\neq \varepsilon,则把 AαA\to\alpha 填入 M[A,x]M[A,x]

如果 εFIRST(α)\varepsilon\in \text{FIRST}(\alpha),则对所有 yFOLLOW(A)y\in \text{FOLLOW}(A),把 AαA\to\alpha 填入 M[A,y]M[A,y]


逐条填表。

SeTS\to eT

FIRST(eT)=e\text{FIRST}(eT)={e}

所以填:

M[S,e]=SeTM[S,e]=S\to eT

SRTS\to RT

FIRST(RT)=d,a,b,ε\text{FIRST}(RT)={d,a,b,\varepsilon}

所以先填:

M[S,d]=SRTM[S,d]=S\to RT

M[S,a]=SRTM[S,a]=S\to RT

M[S,b]=SRTM[S,b]=S\to RT

因为 εFIRST(RT)\varepsilon\in \text{FIRST}(RT),还要看:

FOLLOW(S)={#}\text{FOLLOW}(S)=\lbrace\#\rbrace

所以填:

M[S,#]=SRTM[S,\#]=S\to RT

TDRT\to DR

FIRST(DR)=a,b\text{FIRST}(DR)={a,b}

所以填:

M[T,a]=TDRM[T,a]=T\to DR

M[T,b]=TDRM[T,b]=T\to DR

TεT\to\varepsilon

因为右部是 ε\varepsilon,看:

FOLLOW(T)={#}\text{FOLLOW}(T)=\lbrace\#\rbrace

所以填:

M[T,#]=TεM[T,\#]=T\to\varepsilon

RdRR\to dR

FIRST(dR)=d\text{FIRST}(dR)={d}

所以填:

M[R,d]=RdRM[R,d]=R\to dR

RεR\to\varepsilon

看:

FOLLOW(R)=a,b,#\text{FOLLOW}(R)={a,b,\# }

所以填:

M[R,a]=RεM[R,a]=R\to\varepsilon

M[R,b]=RεM[R,b]=R\to\varepsilon

M[R,#]=RεM[R,\#]=R\to\varepsilon

DaD\to a

M[D,a]=DaM[D,a]=D\to a

DbdD\to bd

M[D,b]=DbdM[D,b]=D\to bd


最终 LL(1) 分析表是:

非终结符aabbddee#\#
SSSRTS\to RTSRTS\to RTSRTS\to RTSeTS\to eTSRTS\to RT
TTTDRT\to DRTDRT\to DRTεT\to\varepsilon
RRRεR\to\varepsilonRεR\to\varepsilonRdRR\to dRRεR\to\varepsilon
DDDaD\to aDbdD\to bd

空白格表示分析到对应“非终结符 + 当前输入符号”组合时出错。

自顶向下分析

在这门课、这类题的语境里,LL(1) 文法和不带回溯的基本可以当成一样:构造“不带回溯的自顶向下语法分析器”的判定条件,就是文法满足 LL(1) 的条件。标准判据是:对于同一非终结符 AA 的任意两个不同候选式:

AαiαjA\to \alpha_i\mid \alpha_j

要求:

PREDICT(Aαi)PREDICT(Aαj)=,ij\text{PREDICT}(A\to\alpha_i)\cap \text{PREDICT}(A\to\alpha_j)=\varnothing,\quad i\neq j

展开成常见的两条条件就是:

  1. 候选式的开头集合互斥

FIRST(αi)FIRST(αj)=,ij\text{FIRST}(\alpha_i)\cap \text{FIRST}(\alpha_j)=\varnothing,\quad i\neq j

  1. 如果某个候选式能推出空串

αiε\alpha_i\Rightarrow^*\varepsilon

则它和其他候选式之间还要满足:

FIRST(αj)FOLLOW(A)=,ij\text{FIRST}(\alpha_j)\cap \text{FOLLOW}(A)=\varnothing,\quad i\neq j

原因是:当分析栈顶是 AA,当前输入符号是 aa 时,分析器需要根据 aa 唯一选择一条 AA 的产生式。PREDICT\text{PREDICT} 集两两不相交,就能唯一选择;有交集,就会出现多个候选式都可选,需要回溯或报冲突。

更精确地说:

  • “不带回溯”描述的是分析器行为:看当前输入符号即可决定规则。
  • “LL(1)”描述的是文法性质:从左到右扫描输入,构造最左推导,只向前看 11 个符号即可决定规则。

判断 LL(1) 文法

快速判断 LL(1) 文法,先看三件事:

第一,看有没有左递归。有左递归基本直接排除 LL(1)。例如 SSabS\to Sab,这是直接左递归,因为右部一开始就是 SS。所以 B 排除。

第二,看同一个非终结符的多个候选式,开头符号是否冲突。也就是判断 FIRST(αi)FIRST(αj)=\text{FIRST}(\alpha_i)\cap \text{FIRST}(\alpha_j)=\varnothing,如果两个候选式都能以同一个终结符开头,就无法根据一个输入符号决定选哪条产生式。

第三,如果有 ε\varepsilon 产生式,再额外看 FIRST(αj)FOLLOW(A)=\text{FIRST}(\alpha_j)\cap \text{FOLLOW}(A)=\varnothing


这道题没有 ε\varepsilon,所以只看前两步就够了。


逐项看:

A:SaSbabS\to aSb\mid ab

两个候选式都是以 aa 开头:

FIRST(aSb)=a\text{FIRST}(aSb)={a}

FIRST(ab)=a\text{FIRST}(ab)={a}

所以:

FIRST(aSb)FIRST(ab)=a\text{FIRST}(aSb)\cap \text{FIRST}(ab)={a}

有冲突,排除。

B:SabSabS\to ab\mid Sab

这里有直接左递归:

SSabS\to Sab

直接排除。

C:SaSbbS\to aSb\mid b

两个候选式开头分别是 aabb

FIRST(aSb)=a\text{FIRST}(aSb)={a}

FIRST(b)=b\text{FIRST}(b)={b}

所以:

FIRST(aSb)FIRST(b)=\text{FIRST}(aSb)\cap \text{FIRST}(b)=\varnothing

没有 ε\varepsilon 产生式,也没有左递归,所以它是 LL(1) 文法。

D:SaSaS\to aS\mid a

两个候选式都是以 aa 开头:

FIRST(aS)=a\text{FIRST}(aS)={a}

FIRST(a)=a\text{FIRST}(a)={a}

所以:

FIRST(aS)FIRST(a)=a\text{FIRST}(aS)\cap \text{FIRST}(a)={a}

有冲突,排除。

最终选 C


下列文法中属于LL(1)文法的是:
A. G[S]:

S → ABc

A → a | ε

B → b | ε

B. G[S]:

S → Ab

A → a | B | ε

B → b | ε

C. G[S]:

S → ABBA

A → a | ε

B → b | ε

D. G[S]:

S → aSe | B

B → bBe | C

C → cCe | d

正确答案AD,

判断 LL(1) 文法最稳的方法是:只比较同一个非终结符的不同候选式,看它们的 PREDICT\text{PREDICT} 集是否两两不相交。

规则:

AαA\to\alpha,且 α\alpha 不能推出空串:

PREDICT(Aα)=FIRST(α)\text{PREDICT}(A\to\alpha)=\text{FIRST}(\alpha)

AαA\to\alpha,且 αε\alpha\Rightarrow^*\varepsilon

PREDICT(Aα)=(FIRST(α)ε)FOLLOW(A)\text{PREDICT}(A\to\alpha)=(\text{FIRST}(\alpha)-{\varepsilon})\cup \text{FOLLOW}(A)

然后检查:

PREDICT(Aαi)PREDICT(Aαj)=,ij\text{PREDICT}(A\to\alpha_i)\cap \text{PREDICT}(A\to\alpha_j)=\varnothing,\quad i\neq j


A 是 LL(1)。

文法:

SABcS\to ABc

AaεA\to a\mid\varepsilon

BbεB\to b\mid\varepsilon

只需要检查 AABB 的候选式。

因为 AA 后面是 BcBc,所以:

FOLLOW(A)=FIRST(Bc)=b,c\text{FOLLOW}(A)=\text{FIRST}(Bc)={b,c}

因此:

PREDICT(Aa)=a\text{PREDICT}(A\to a)={a}

PREDICT(Aε)=FOLLOW(A)=b,c\text{PREDICT}(A\to\varepsilon)=\text{FOLLOW}(A)={b,c}

两者不相交。

因为 BB 后面是 cc,所以:

FOLLOW(B)=c\text{FOLLOW}(B)={c}

因此:

PREDICT(Bb)=b\text{PREDICT}(B\to b)={b}

PREDICT(Bε)=FOLLOW(B)=c\text{PREDICT}(B\to\varepsilon)=\text{FOLLOW}(B)={c}

两者不相交。

所以 A 满足 LL(1)。


B 不是 LL(1)。

文法:

SAbS\to Ab

AaBεA\to a\mid B\mid\varepsilon

BbεB\to b\mid\varepsilon

先看 BB

PREDICT(Bb)=b\text{PREDICT}(B\to b)={b}

PREDICT(Bε)=FOLLOW(B)\text{PREDICT}(B\to\varepsilon)=\text{FOLLOW}(B)

BB 出现在 ABA\to B 的末尾,所以:

FOLLOW(A)FOLLOW(B)\text{FOLLOW}(A)\subseteq\text{FOLLOW}(B)

又因为 SAbS\to Ab,所以:

bFOLLOW(A)b\in\text{FOLLOW}(A)

于是:

bFOLLOW(B)b\in\text{FOLLOW}(B)

所以:

PREDICT(Bε)\text{PREDICT}(B\to\varepsilon) 中也含有 bb

于是:

PREDICT(Bb)PREDICT(Bε)\text{PREDICT}(B\to b)\cap\text{PREDICT}(B\to\varepsilon)\neq\varnothing

冲突,B 排除。

也可以从 AA 看出冲突:ABA\to B 可以推出 bbAεA\to\varepsilon 也会在后继符号 bb 时使用,看到 bb 时无法确定选 ABA\to B 还是 AεA\to\varepsilon


C 不是 LL(1)。

文法:

SABBAS\to ABBA

AaεA\to a\mid\varepsilon

BbεB\to b\mid\varepsilon

问题出在第一个 BB

SABBAS\to ABBA 中,第一个 BB 后面跟着 BABA

所以:

FOLLOW(B)\text{FOLLOW}(B) 至少包含 FIRST(BA)ε\text{FIRST}(BA)-{\varepsilon}

因为:

FIRST(B)=b,ε\text{FIRST}(B)={b,\varepsilon}

FIRST(A)=a,ε\text{FIRST}(A)={a,\varepsilon}

所以:

FIRST(BA)=b,a,ε\text{FIRST}(BA)={b,a,\varepsilon}

因此:

bFOLLOW(B)b\in\text{FOLLOW}(B)

再看 BB 的两个候选式:

PREDICT(Bb)=b\text{PREDICT}(B\to b)={b}

PREDICT(Bε)=FOLLOW(B)\text{PREDICT}(B\to\varepsilon)=\text{FOLLOW}(B)

由于 bFOLLOW(B)b\in\text{FOLLOW}(B),所以:

bPREDICT(Bε)b\in\text{PREDICT}(B\to\varepsilon)

于是:

PREDICT(Bb)PREDICT(Bε)=b\text{PREDICT}(B\to b)\cap\text{PREDICT}(B\to\varepsilon)={b}

有冲突,C 排除。

直观理解:在 ABBAABBA 里,第一个 BB 后面还能跟另一个 BB,而 BB 自己又能推出 ε\varepsilon。看到输入符号 bb 时,分析器无法判断当前这个 bb 是属于第一个 BB,还是第一个 BB 取空、交给后面的第二个 BB


D 是 LL(1)。

文法:

SaSeBS\to aSe\mid B

BbBeCB\to bBe\mid C

CcCedC\to cCe\mid d

没有 ε\varepsilon 产生式,所以只看同一左部候选式的 FIRST\text{FIRST} 是否相交。

SS

FIRST(aSe)=a\text{FIRST}(aSe)={a}

FIRST(B)=FIRST(bBeC)=b,c,d\text{FIRST}(B)=\text{FIRST}(bBe\mid C)={b,c,d}

所以:

ab,c,d={a}\cap{b,c,d}=\varnothing

BB

FIRST(bBe)=b\text{FIRST}(bBe)={b}

FIRST(C)=c,d\text{FIRST}(C)={c,d}

所以:

bc,d={b}\cap{c,d}=\varnothing

CC

FIRST(cCe)=c\text{FIRST}(cCe)={c}

FIRST(d)=d\text{FIRST}(d)={d}

所以:

cd={c}\cap{d}=\varnothing

所有同左部候选式都能用一个向前看符号唯一选择,所以 D 是 LL(1)。


最终答案:A、D

这题的关键不是“文法里有没有 ε\varepsilon”,而是“有 ε\varepsilon 时,ε\varepsilon 产生式的 FOLLOW\text{FOLLOW} 会不会和其他候选式的 FIRST\text{FIRST} 冲突”。A 没冲突,B 和 C 有冲突,D 没有 ε\varepsilon 且首符号完全分开。

语法分析大题

例题 1

设有文法 G

S→SaA | bB

A→aB | c

B→Bb | d

(1)消除该文法的左递归;

(2)给出修改后文法每条规则的 Predict 集;

(3)试写出改造后的文法的递归下降语法分析程序。

关于 (1):

左递归指某个非终结符可以推出以自己开头的形式:

A+AαA\Rightarrow^+ A\alpha

其中最常见的是直接左递归:

AAαβA\to A\alpha\mid \beta

这里的规则是:

AAαβA\to A\alpha\mid \beta

消除后变成:

AβAA\to \beta A'(最后一定是会有一个 β\beta 在前面的)

AαAεA'\to \alpha A'\mid \varepsilon

含义是:原来左递归表示“先有一个 AA,再不断往右追加 α\alpha”;改写后变成“先生成一个基础部分 β\beta,再用 AA' 不断追加 α\alpha”。

SS

SSaAbBS\to SaA\mid bB

这里:

α=aA\alpha=aA

β=bB\beta=bB

所以改写为:

SbBSS\to bBS'

SaASεS'\to aAS'\mid \varepsilon

AA

AaBcA\to aB\mid c

右部没有以 AA 开头的候选式,所以不用改。

BB

BBbdB\to Bb\mid d

这里:

α=b\alpha=b

β=d\beta=d

所以改写为:

BdBB\to dB'

BbBεB'\to bB'\mid \varepsilon

最终消除左递归后的文法是:

SbBSS\to bBS'

SaASεS'\to aAS'\mid \varepsilon

AaBcA\to aB\mid c

BdBB\to dB'

BbBεB'\to bB'\mid \varepsilon

快速做法:看到 XXαβX\to X\alpha\mid\beta,直接改成 XβXX\to\beta X'XαXεX'\to\alpha X'\mid\varepsilon。这题分别对 SSBB 套模板即可。

标准答案:

(1)消除左递归后的文法为:

​ S→bBS’

​ S’→aAS’ | ε

​ A→aB | c

​ B→dB’

​ B’→bB’ | ε

​ (2) 消除左递归后每个产生式的Predict集合为

​ Predict(S→bBS’)={b}

​ Predict (S’→aAS’)={a}

​ Predict (S’→ε)=

(3)消除左递归后的文法对应的递归下降语法分析程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    void S()

{ if(token==b) {Match(b); B(); S’();}

else Error();}

void S’()

{ if(token==a) {Match(a); A(); S’();}

else if(token==#) {skip;}

else Error();}

void A()

{ if(token==a) {Match(a); B();}

else if (token==c) {Match(c);}

else Error();}

void B()

{ if(token==d) {Match(d); B’();}

else Error();}

void B’()

{ if(token==b) {Match(b); B’();}

else if (token==a | #) {skip;}

else Error();}

层数和偏移量的计算

做法固定为三步:

  1. 先确定变量使用位置所在的局部化单位
  2. 再按“由内到外”查找同名变量
  3. 最后用该声明所在层的起始偏移和类型大小计算偏移量

使用性标识符查表时要从当前层开始,逐层向外查找;声明性标识符要登记名字、种类、类型、层数、偏移等属性。

一般规则:

  • 进入一个新的局部化单位,也就是新的 { ... } 或函数体,层数加深一层。题目约定“每个局部化单位的起始偏移为 00”,所以每一层自己的局部变量都从偏移 00 开始分配。
  • 同一局部化单位内,声明变量时按顺序分配偏移。整型、字符型占 11 个单元,实型占 22 个单元。变量的偏移量就是它在本层局部区中的起始位置。
  • 使用变量时,采用近处优先原则:当前层有同名变量就用当前层的;当前层没有,再向外层查找。笔记中的例子也说明,内层变量会遮蔽外层同名变量。

约定每个局部化单位的起始偏移为 0,整型、字符型占 1 个单元,实型占 2 个单元。在下面的程序中,第 5 行代码中变量 i 的层数和偏移量是_______.

1
2
3
4
5
6
7
8
int  i , j ;
int main()
{ char i;
i=’A’;
{ float i;
i = 0; }
i = ‘B’;
}

A. 1,0 B. 1,1 C. 2,0 D. 2,1

选择 B,因为 C 语言只有 0 层和 1 层之分。

四元式

类别四元式高级语言含义
算术运算(ADDI,a,b,t)(\text{ADDI},a,b,t)(ADDF,a,b,t)(\text{ADDF},a,b,t)t=a+bt=a+b
算术运算(SUBI,a,b,t)(\text{SUBI},a,b,t)t=abt=a-b
算术运算(MULTI,a,b,t)(\text{MULTI},a,b,t)(MULTF,a,b,t)(\text{MULTF},a,b,t)t=abt=a*b
算术运算(DIVF,a,b,t)(\text{DIVF},a,b,t)t=a/bt=a/b
类型转换(FLOAT,x,,t)(\text{FLOAT},x,-,t)(int-to-real,x,,t)(\text{int-to-real},x,-,t)t=(float)xt=(float)x
赋值(ASSIG,x,,y)(\text{ASSIG},x,-,y)(:=,x,,y)(:=,x,-,y)y=xy=x
地址计算(AADD,A,t,t)(\text{AADD},A,t,t')t=Addr(A)+tt'=\text{Addr}(A)+t
无条件跳转(JMP,,,L)(\text{JMP},-,-,L)goto L;
真跳转(JMP1,E,,L)(\text{JMP1},E,-,L)if (E) goto L;
假跳转(JMP0,E,,L)(\text{JMP0},E,-,L)if (!E) goto L;
if 条件(THEN,E,,)(\text{THEN},E,-,-)EE 为假时跳过 then
else 分界(ELSE,,,)(\text{ELSE},-,-,-)then 执行完后跳过 else
if 结束(ENDIF,,,)(\text{ENDIF},-,-,-)if 结束定位
while 开始(WHILE,,,)(\text{WHILE},-,-,-)循环入口
while 条件(DO,E,,)(\text{DO},E,-,-)EE 为假时跳出循环
while 结束(ENDWHILE,,,)(\text{ENDWHILE},-,-,-)跳回循环开始
标号(LABEL,,,L)(\text{LABEL},-,-,L)L:
子程序入口(ENTRY,Label,Size,Level)(\text{ENTRY},\text{Label},\text{Size},\text{Level})函数/过程入口
过程返回(ENDPROC,,,)(\text{ENDPROC},-,-,-)过程结束返回
函数返回(ENDFUNC,,,)(\text{ENDFUNC},-,-,-)函数结束返回

四元式统一格式是:(op,ARG1,ARG2,RESULT)(\text{op},\text{ARG1},\text{ARG2},\text{RESULT})

其中 op\text{op} 是操作码,ARG1\text{ARG1}ARG2\text{ARG2} 是操作分量,RESULT\text{RESULT} 是结果分量;这些分量可以是常量、变量名、临时变量,也可以是带层数、偏移、访问方式的抽象地址。笔记中明确用四元式表示中间代码,并给出 (op,ARG1,ARG2,RESULT)(\text{op},\text{ARG1},\text{ARG2},\text{RESULT}) 这个格式。

表达式与赋值类四元式

四元式形式含义对应高级语言写法
(+,a,b,t)(+,a,b,t)计算 a+ba+b,结果放入 ttt=a+bt=a+b
(,a,b,t)(*,a,b,t)计算 aba*b,结果放入 ttt=abt=a*b
(:=,t,,x)(:=,t,-,x)tt 赋给 xxx=tx=t
(int-to-real,10,,t1)(\text{int-to-real},10,-,t_1)把整数 1010 转成实数,放入 t1t_1t1=(real)10t_1=(real)10
(FLOAT,x,,t)(\text{FLOAT},x,-,t)把整数型 xx 转成实型,放入 ttt=(float)xt=(float)x

可以把:

sum:=first+count10\text{sum}:=\text{first}+\text{count}*10

翻译成:

(int-to-real,10,,t1)(\text{int-to-real},10,-,t_1)

(,count,t1,t2)(*,\text{count},t_1,t_2)

(+,first,t2,t3)(+,\text{first},t_2,t_3)

(:=,t3,,sum)(:=,t_3,-,\text{sum})

意思是先做类型转换,再做乘法,再做加法,最后赋值。

也可以把整数运算和实数运算区分开,例如:

四元式形式含义高级语言写法
(ADDI,a,b,t)(\text{ADDI},a,b,t)整数加法t=a+bt=a+b
(ADDF,a,b,t)(\text{ADDF},a,b,t)实数加法t=a+bt=a+b
(MULTI,a,b,t)(\text{MULTI},a,b,t)整数乘法t=abt=a*b
(MULTF,a,b,t)(\text{MULTF},a,b,t)实数乘法t=abt=a*b
(DIVF,a,b,t)(\text{DIVF},a,b,t)实数除法t=a/bt=a/b
(SUBI,a,b,t)(\text{SUBI},a,b,t)整数减法t=abt=a-b

例如表达式:

X2+A(i+1)/(j+1)X*2+A*(i+1)/(j+1)

给出的四元式包含:

(FLOAT,2,,t1)(\text{FLOAT},2,-,t_1)

(MULTF,X,t1,t2)(\text{MULTF},X,t_1,t_2)

(ADDI,i,1,t3)(\text{ADDI},i,1,t_3)

(FLOAT,t3,,t4)(\text{FLOAT},t_3,-,t_4)

(MULTF,A,t4,t5)(\text{MULTF},A,t_4,t_5)

(ADDI,j,1,t6)(\text{ADDI},j,1,t_6)

(FLOAT,t6,,t7)(\text{FLOAT},t_6,-,t_7)

(DIVF,t5,t7,t8)(\text{DIVF},t_5,t_7,t_8)

(ADDF,t2,t8,t9)(\text{ADDF},t_2,t_8,t_9)

这里可以看出:整数表达式用 ADDI\text{ADDI},实数表达式用 ADDF,MULTF,DIVF\text{ADDF},\text{MULTF},\text{DIVF},类型不匹配时先生成 FLOAT\text{FLOAT} 转换四元式。

数组下标变量与地址计算类四元式

数组下标变量的中间代码重点是计算元素地址

数组:A[i1][i2][in]A[i_1][i_2]\cdots[i_n]

地址公式是:

Addr(A[i1][i2][in])=Addr(A)+(i1L1)S1+(i2L2)S2++(inLn)Sn\text{Addr}(A[i_1][i_2]\cdots[i_n])=\text{Addr}(A)+(i_1-L_1)S_1+(i_2-L_2)S_2+\cdots+(i_n-L_n)S_n

其中:

α=size(T)\alpha=\text{size}(T)

Di=UiLi+1D_i=U_i-L_i+1

Si=Di+1Di+2DnαS_i=D_{i+1}D_{i+2}\cdots D_n\alpha

用这个公式讲下标变量地址计算。

常见四元式如下:

四元式形式含义对应高级语言含义
(SUBI,i,L1,t1)(\text{SUBI},i,L_1,t_1)计算下标差 iL1i-L_1数组第 1 维偏移起点
(MULTI,t1,S1,t2)(\text{MULTI},t_1,S_1,t_2)计算 (iL1)S1(i-L_1)S_1第 1 维贡献的地址偏移
(AADD,A,t2,t3)(\text{AADD},A,t_2,t_3)地址加法,得到 Addr(A)+t2\text{Addr}(A)+t_2得到数组元素或部分地址
(AADD,t3,t5,t6)(\text{AADD},t_3,t_5,t_6)继续累加下一维偏移得到多维数组元素地址

例如 A[i]A[i]可能生成:

$ (\text{SUBI},i,L_1,t_1)$

(MULTI,t1,α,t2)(\text{MULTI},t_1,\alpha,t_2)

(AADD,A,t2,t3)(\text{AADD},A,t_2,t_3)

最终 t3t_3 表示 A[i]A[i] 的地址。

例如 A[i][j]A[i][j] 可能生成:

(SUBI,i,L1,t1)(\text{SUBI},i,L_1,t_1)

(MULTI,t1,S1,t2)(\text{MULTI},t_1,S_1,t_2)

(AADD,A,t2,t3)(\text{AADD},A,t_2,t_3)

(SUBI,j,L2,t4)(\text{SUBI},j,L_2,t_4)

(MULTI,t4,S2,t5)(\text{MULTI},t_4,S_2,t_5)

(AADD,t3,t5,t6)(\text{AADD},t_3,t_5,t_6)

最终 t6t_6 表示 A[i][j]A[i][j] 的地址。

所以数组类题要抓住:SUBI\text{SUBI} 算下标差,MULTI\text{MULTI} 算偏移量,AADD\text{AADD} 算地址。

无条件与条件跳转类四元式

转移性四元式会在目标代码中生成跳转指令。

四元式形式含义对应高级语言写法
(JMP,,,L)(\text{JMP},-,-,L)无条件跳转到标号 LLgoto L;
(JMP1,E,,L)(\text{JMP1},E,-,L)E=1E=1,跳转到 LLif (E) goto L;
(JMP0,E,,L)(\text{JMP0},E,-,L)E=0E=0,跳转到 LLif (!E) goto L;

这三个是最通用的跳转四元式:

(JMP,,,L)(\text{JMP},-,-,L):直接跳。

(JMP1,E,,L)(\text{JMP1},E,-,L):条件为真跳。

(JMP0,E,,L)(\text{JMP0},E,-,L):条件为假跳。

if 语句相关四元式

if-else 的结构是:

(THEN,E.FORM,,)(\text{THEN},E.\text{FORM},-,-)

S1S_1 的中间代码

(ELSE,,,)(\text{ELSE},-,-,-)

S2S_2 的中间代码

(ENDIF,,,)(\text{ENDIF},-,-,-)

并且 THEN\text{THEN}ELSE\text{ELSE} 的跳转目标地址需要回填。

四元式形式含义对应高级语言写法
(THEN,E,,)(\text{THEN},E,-,-)if 条件控制;按笔记/题库口径,EE 为假时跳到 else 或 endifif (E) then S1 中的条件判断
(ELSE,,,)(\text{ELSE},-,-,-)then 子句结束后,跳过 else 子句else 分界
(ENDIF,,,)(\text{ENDIF},-,-,-)if 语句结束定位if 语句结束

例子:

1
2
3
4
if (a < b)
x = 1;
else
x = 2;

可以抽象成:

(LT,a,b,t1)(\text{LT},a,b,t_1)

(THEN,t1,,)(\text{THEN},t_1,-,-)

(ASSIG,1,,x)(\text{ASSIG},1,-,x)

(ELSE,,,)(\text{ELSE},-,-,-)

(ASSIG,2,,x)(\text{ASSIG},2,-,x)

(ENDIF,,,)(\text{ENDIF},-,-,-)

回填后,THEN\text{THEN} 跳到 else 分支,ELSE\text{ELSE} 跳到 endif 位置。

没有 else 时:

1
2
if (a < b)
x = 1;

结构是:

(LT,a,b,t1)(\text{LT},a,b,t_1)

(THEN,t1,,)(\text{THEN},t_1,-,-)

(ASSIG,1,,x)(\text{ASSIG},1,-,x)

(ENDIF,,,)(\text{ENDIF},-,-,-)

此时 THEN\text{THEN} 的假出口回填到 ENDIF\text{ENDIF}

while 语句相关四元式

while 的中间代码结构是:

(WHILE,,,)(\text{WHILE},-,-,-)

EE 的中间代码

(DO,E.FORM,,)(\text{DO},E.\text{FORM},-,-)

SS 的中间代码

(ENDWHILE,,,)(\text{ENDWHILE},-,-,-)

其中 WHILE\text{WHILE} 标记循环开始,DO\text{DO} 做条件转移,ENDWHILE\text{ENDWHILE} 跳回循环头。

四元式形式含义对应高级语言写法
(WHILE,,,)(\text{WHILE},-,-,-)定位循环开始入口while 的循环头
(DO,E,,)(\text{DO},E,-,-)条件转移;EE 为假时跳出循环while (E) do 的条件判断
(ENDWHILE,,,)(\text{ENDWHILE},-,-,-)循环体结束,跳回 WHILE\text{WHILE}循环末尾回跳

例子:

1
2
3
while (a < b) {
a = a + 1;
}

可抽象成:

(WHILE,,,)(\text{WHILE},-,-,-)

(LT,a,b,t1)(\text{LT},a,b,t_1)

(DO,t1,,)(\text{DO},t_1,-,-)

(ADDI,a,1,t2)(\text{ADDI},a,1,t_2)

(ASSIG,t2,,a)(\text{ASSIG},t_2,-,a)

(ENDWHILE,,,)(\text{ENDWHILE},-,-,-)

这里 DO\text{DO} 的假出口通常要回填到循环后继位置。

标号与 goto 类四元式

标号定位和 goto 语句主要用:

四元式形式含义对应高级语言写法
(LABEL,,,L)(\text{LABEL},-,-,L)定义标号位置L: statement
(JMP,,,L)(\text{JMP},-,-,L)无条件跳转到标号 LLgoto L;

LABEL 是定位性四元式,本身不产生跳转;JMP 是转移性四元式,会生成跳转指令。在基本块划分中也把 LABEL 归为标号性/定位性四元式,把 JMP 归为转移性四元式。

例子:

1
2
3
4
goto L1;
x = 1;
L1:
x = 2;

可抽象成:

(JMP,,,L1)(\text{JMP},-,-,L_1)

(ASSIG,1,,x)(\text{ASSIG},1,-,x)

(LABEL,,,L1)(\text{LABEL},-,-,L_1)

(ASSIG,2,,x)(\text{ASSIG},2,-,x)

如果 goto L1 出现时还不知道 L1L_1 的具体内部标号,就先生成缺目标地址的跳转四元式,后面遇到 L1: 时再回填。

过程、函数入口与返回类四元式

在基本块划分里列出:

四元式形式含义对应高级语言写法
(ENTRY,Label,Size,Level)(\text{ENTRY},\text{Label},\text{Size},\text{Level})子程序入口,记录入口标号、活动记录大小、层数函数/过程定义入口
(ENDPROC,,,)(\text{ENDPROC},-,-,-)过程返回,跳转到过程调用后的下一句return; 或过程结束
(ENDFUNC,,,)(\text{ENDFUNC},-,-,-)函数返回,跳转到函数调用处后继位置return expr; 或函数结束

ENDPROC\text{ENDPROC}ENDFUNC\text{ENDFUNC} 归为转移性四元式,把 ENTRY\text{ENTRY} 归为标号性四元式。

另外,运行时存储部分提到:遇到 call 四元式时申请新的活动记录,遇到 return 或函数结束时释放当前活动记录。也就是说,过程调用类四元式会触发活动记录申请和返回恢复。

基本块划分中特别提到的四元式类别

可以把四元式按基本块作用分成几类。

转移性四元式

这些四元式会导致当前基本块结束:

(JMP,,,L)(\text{JMP},-,-,L)

(JMP1,E,,L)(\text{JMP1},E,-,L)

(JMP0,E,,L)(\text{JMP0},E,-,L)

(ENDPROC,,,)(\text{ENDPROC},-,-,-)

(ENDFUNC,,,)(\text{ENDFUNC},-,-,-)

(THEN,E,,)(\text{THEN},E,-,-)

(ELSE,,,)(\text{ELSE},-,-,-)

(DO,E,,)(\text{DO},E,-,-)

(ENDWHILE,,,)(\text{ENDWHILE},-,-,-)

标号性 / 定位性四元式

这些四元式通常作为新基本块入口:

(LABEL,,,L)(\text{LABEL},-,-,L)

(ENTRY,Label,Size,Level)(\text{ENTRY},\text{Label},\text{Size},\text{Level})

(WHILE,,,)(\text{WHILE},-,-,-)

(ENDIF,,,)(\text{ENDIF},-,-,-)


编译原理题型总结笔记
https://blog.kisechan.space/2026/notes-compiler-solutions/
作者
Kisechan
发布于
2026年6月23日
更新于
2026年6月26日
许可协议