文章目录
前言
在编译器设计领域,解析(Parsing)是一个核心过程,它负责将源代码转换成一系列可以被编译器理解的结构。SLR(简单LR分析)和LR(1)分析表是构建解析器时常用的两种技术。本文将用通俗易懂的方式,结合笔者自身理解,探讨这两种技术的原理、构建方法。
本文结合龙书第2版习题4.6.2和4.7.1进行讲解。
首先给出贯穿本文的语法:
S
→
S
S
+
∣
S
S
∗
∣
a
S \rightarrow SS+ | SS* | a
S→SS+∣SS∗∣a
一、SLR分析表的构建
1. 基本概念
1.1 增广文法
如果某个文法 G 的开始符号是 S,那么它的增广文法 G’ 就是原来文法 G 中的产生式再加上新的产生式“S’ → S”。本例中的增广文法
G
′
G'
G′为
0.
S
′
→
S
0. S' \rightarrow S
0.S′→S
1.
S
→
S
S
+
1.S\rightarrow SS+
1.S→SS+
2.
S
→
S
S
∗
2.S\rightarrow SS*
2.S→SS∗
3.
S
→
a
3.S\rightarrow a
3.S→a
1.2 项集的闭包
对于项集
I
I
I ,首先把它里面的所有项放到它的闭包
C
(
I
)
C(I)
C(I) 中;接着遍历
I
I
I中的每一项。如果遍历到的某一项点号右边恰好是非终结符,我们就把这个非终结符对应的若干产生式,做成“LR(0) 项”(点号放在产生式体最左边),再全部添加到
C
(
I
)
C(I)
C(I)中。反复遍历,直到没有新项被添加到
C
(
I
)
C(I)
C(I)中为止。此时的
C
(
I
)
C(I)
C(I) 就叫做“项集的闭包”。
I
0
I_0
I0即为初态
S
′
→
⋅
S
S' \rightarrow \cdot S
S′→⋅S 的闭包。
对于本例,项集
I
0
I0
I0的构造过程如下:
首先将初态加入
S
′
→
⋅
S
S' \rightarrow \cdot S
S′→⋅S
此时,点的右边为
S
S
S,是非终结符,将其所有产生式加入,此时闭包中包含:
S
′
→
⋅
S
S' \rightarrow \cdot S
S′→⋅S
S
→
⋅
S
S
+
S\rightarrow\cdot SS+
S→⋅SS+
S
→
⋅
S
S
∗
S\rightarrow\cdot SS*
S→⋅SS∗
S
→
⋅
a
S\rightarrow\cdot a
S→⋅a
再次观察新添加的项中是否含有
⋅
\cdot
⋅后是除刚加入的
S
S
S外的非终结符。没有,则完成。此时即为LR(0)自动机初态的项集
I
0
I_0
I0。
之后如何更新LR(0)自动机的状态呢?需要使用第三个关键点 GOTO函数。
1.3 GOTO函数
GOTO函数相当于状态转移函数,
G
O
T
O
(
I
i
,
X
)
GOTO(I_i, X)
GOTO(Ii,X)代表对项集
I
i
I_i
Ii输入语法符号
X
X
X后的项集。
具体的计算方法如下:
- 新建空项集 I j I_{j} Ij
- 遍历项集 I i I_i Ii中的项,看点号右边是否为 X X X
- 如果是,则将其点号右移,并加入 I j I_{j} Ij中;否则,丢弃
- 遍历完成,求此时 I j I_{j} Ij的闭包,得到最终的 I j I_{j} Ij。
例如
G
O
T
O
(
I
0
,
S
)
GOTO(I_0, S)
GOTO(I0,S):
首先挑选出全部点后为
S
S
S的项:
S
′
→
⋅
S
S' \rightarrow \cdot S
S′→⋅S
S
→
⋅
S
S
+
S\rightarrow\cdot SS+
S→⋅SS+
S
→
⋅
S
S
∗
S\rightarrow\cdot SS*
S→⋅SS∗
将点右移一位:
S
′
→
S
⋅
S' \rightarrow S\cdot
S′→S⋅
S
→
S
⋅
S
+
S\rightarrow S\cdot S+
S→S⋅S+
S
→
S
⋅
S
∗
S\rightarrow S\cdot S*
S→S⋅S∗
然后,求上述项集的闭包:
(以下再次回顾求闭包的过程)
遍历所有项,发现点后有非终结符
S
S
S,将其产生式全部加入,此时项集中包含:
S
′
→
S
⋅
S' \rightarrow S\cdot
S′→S⋅
S
→
S
⋅
S
+
S\rightarrow S\cdot S+
S→S⋅S+
S
→
S
⋅
S
∗
S\rightarrow S\cdot S*
S→S⋅S∗
——————
S
→
⋅
S
S
+
S\rightarrow\cdot SS+
S→⋅SS+
S
→
⋅
S
S
∗
S\rightarrow\cdot SS*
S→⋅SS∗
S
→
⋅
a
S\rightarrow\cdot a
S→⋅a
现在,项集中不再含有点后除
S
S
S外的非终结符了。此时,GOTO函数完成,将该状态命名为
I
2
I_2
I2。
有了以上基础我们就可以进行SLR分析表的构建了。
2 SLR分析表的构建
2.1 找到全部的项集
首先,按照上述方法,找到这个语法对应的全部项集。
在确定初态后,不断调用GOTO函数,生成新的项集,直到GOTO函数不再能生成新的项集为止。(规范-LR(0) 项集族)
将所有的项集及其对应的生成关系画图表示如下(LR(0)自动机):
2.2 分析表的构建
SLR 解析表是基于上面的 LR(0) 自动机制作的。
SLR 表有两个部分:
- Action 表
- Goto 表。
构造 SLR 解析表中的 Action 表需要三步:
- 按照上面的方法生成“规范-LR(0) 项集族”。
- 接下来,遍历项集族中的项集 。
- 对于其中的一个项集
I
i
I_{i}
Ii ,遍历其中的每一个 LR(0) 项:
- 如果这个项的点号右边是终结符 ,并且 G O T O ( I i , a ) = I j GOTO(I_i, a)=I_j GOTO(Ii,a)=Ij,就把 Action[i, a] 设成 S j S_j Sj”。
- 如果这个项的点号在整个产生式的最右边(形如 A → α ⋅ A\rightarrow \alpha \cdot A→α⋅)并且不是增广文法的开始符号(比如上面的 S’),就找出 A A A 的 FOLLOW 集,遍历其中的终结符 ,把 Action[i, a] 设成“归约 i”——相当于我们已经处理完了 A 这个非终结符,可以开始处理下一个了。
- 如果这个项是 S ′ → S ⋅ S'\rightarrow S\cdot S′→S⋅ (S 可以是任意开始符),就把 Action[i, eof] 设成“接受”。
构造 Goto 表的过程要简单一些:我们遍历所有非终结符。如果对于某个非终结符 A A A ,有 G O T O ( I i , A ) = I j GOTO(I_i, A)=I_j GOTO(Ii,A)=Ij ,那么我们就把 GOTO[i, A] 设成 j——这样告诉解析器,归约了 A A A 之后,要切换到状态 j 接受新的输入。这样 Goto 和 Action 的构造就完成了。
按上述步骤,构造出的SLR分析表如下:
二、LR(1)分析表的构建
LR(1)分析表的构建与SLR分析表最大的区别是引入向前看符号。顾名思义,向前看符号就是要在本步骤的基础上再看下一步会是怎样的。不理解没关系,直接来看怎么做。(很简单的套公式)
1 向前看符号
首先,对于增广文法的开始项
S
′
→
S
S'\rightarrow S
S′→S,其向前看符号为#(句子的结束符),该项变为
S
′
→
S
,
#
S'\rightarrow S , \#
S′→S,#
对于直接右移点的项,向前看符号不变,然后求对应的闭包。
对于这些新增加的项,口诀先看前,再看后。先看左侧是什么,然后再看其它项中点后有相同符号的情况。比如
已知
A
→
α
⋅
B
β
,
a
A\rightarrow \alpha \cdot B\beta , a
A→α⋅Bβ,a
求
B
′
→
?
B'\rightarrow ?
B′→?的向前看符号。
(这里的问号是指可以是任何左侧是B的项,因为它们会获得相同的向前看符号)
左侧为B,则看所有式子中点后紧接是B的项。
然后分两种情况:
- β \beta β为空,直接抄,将原来项的向前看符号抄过来即可
- β \beta β不空,若其为终结符,直接将其加入;若为非终结符,则加入 F I R S T ( β ) FIRST(\beta) FIRST(β)
比如本例中的
I
0
I_0
I0:
首先,初态的向前看符号加入
S
′
→
⋅
S
,
#
S' \rightarrow \cdot S,\#
S′→⋅S,#
求闭包,将S的产生式全部加入
S
′
→
⋅
S
,
#
S' \rightarrow \cdot S,\#
S′→⋅S,#
S
→
⋅
S
S
+
S\rightarrow\cdot SS+
S→⋅SS+
S
→
⋅
S
S
∗
S\rightarrow\cdot SS*
S→⋅SS∗
S
→
⋅
a
S\rightarrow\cdot a
S→⋅a
此时,先看前要求的是S,再看后根据点后紧接着S的全部项求向前看符号。这里除最后一项外均为点后紧接S。
- 第一项 S ′ → ⋅ S , # S' \rightarrow \cdot S,\# S′→⋅S,#,S后为空,符合情况1,直接抄,#
- 第二项和第三项,S后不空,符合情况2,且其后紧接的S非终结符,故加入 F I R S T ( S ) FIRST(S) FIRST(S)。
又
F
I
R
S
T
(
S
)
=
a
FIRST(S)={a}
FIRST(S)=a,因此,加入向前看符号后,最终的
I
0
I_0
I0为:
S
′
→
⋅
S
,
#
S' \rightarrow \cdot S,\#
S′→⋅S,#
S
→
⋅
S
S
+
,
#
/
a
S\rightarrow\cdot SS+,\#/a
S→⋅SS+,#/a
S
→
⋅
S
S
∗
,
#
/
a
S\rightarrow\cdot SS*,\#/a
S→⋅SS∗,#/a
S
→
⋅
a
,
#
/
a
S\rightarrow\cdot a,\#/a
S→⋅a,#/a
2 结果
然后不断调用GOTO函数,即可得到全部项集。
画出状态转换图如下:
生成LR(1)分析表如下:
总结
本文介绍了SLR和LR(1)分析表的构建方法,虽然方法简单,但仅停留于表面,适合做题目使用,并不适合彻底了解二者的机制。今后有时间会补充相关内容的完整知识。
LR(1)分析器部分强推第二个链接中B站的讲解视频!非常清晰!
特别鸣谢:
SLR分析表详解
LR(1)分析表视频讲解