【编译原理复习Part_2】语法分析
Chapter 2 语法分析
我们来练习一下这些东西吧!
(1)语法的左递归消除
假设终结符集合为{a,b,...,y,z}, 首字母大写(非终结符),且具有相同前缀的单词可以推导出更长的一个单词,大写字母(非终结符)可以推导出对应的小写字母(终结符),比如
Pine -> Pineapple
如何对这个语句进行左递归消除呢?
Nice!
Pine -> apple Pine' Pine' -> apple Pine' | \epsilon
(看起来我理解错了,左边必须是一个非终结符)
(2)左公因子提取
假设我们有下面的规则:
A -> a A -> aback A -> abandon A -> abcent A -> accent A -> ad A -> advertisement
假设终结符集合为{a,b,...,y,z}, 大写字母为非终结符,那么上面的文法在用于LL(1)预测分析时会遇到什么问题?如何解决这个问题?
A -> aA' A' -> bB' A' -> cC' A' -> dD' B' -> aA'' B' -> cC'' A'' -> ... C' -> ... D' -> ... A'' -> ... C'' -> ...
(3)计算First和Follow
注意FIRST和FOLLOW的定义
假设一个线性方程组可以表示为:
终结符:{a,b,x,+}
G -> E G' G' -> E G' | ε E -> a x E' E' -> + b x E' | ε
S1:
FIRST(G): {E}
FIRST(G'): {E,ε}
FIRST(E): {a}
FIRST(E'): {+,ε}
S2:代入FIRST(E):
FIRST(G): {a}
FIRST(G'): {a,ε}
FIRST(E): {a}
FIRST(E'): {+,ε}
首先, FOLLOW(G) = {$}
FOLLOW(E) = (FIRST(G') - {ε}) + FOLLOW(G) #因为G'能推导出空串 = {a,$}
FOLLOW(G') = FOLLOW(G) = {$} #由第一个产生式
FOLLOW(E') = FOLLOW(E) = {a,$}
gpt-4的解释:
您的推理是正确的。让我们重新计算FIRST和FOLLOW集合,并确保我们的推理是基于正确的定义和规则。
首先,我们计算每个非终结符的FIRST集合:
-
FIRST(G)
- 从
G -> E G'
得到 FIRST(E) - 因此,FIRST(G) = FIRST(E) =
- 从
-
FIRST(G')
- 从
G' -> E G'
得到 FIRST(E) = - 从
G' -> ε
得到 ε - 因此,FIRST(G') =
- 从
-
FIRST(E)
- 从
E -> a x E'
得到a
- 因此,FIRST(E) =
- 从
-
FIRST(E')
- 从
E' -> + b x E'
得到+
- 从
E' -> ε
得到 ε - 因此,FIRST(E') =
- 从
接下来,我们计算每个非终结符的FOLLOW集合:
-
FOLLOW(G)
- 由于
G -> E G'
,G' 可能为 ε,所以 FOLLOW(G) 包含 FOLLOW(G') - 通常,FOLLOW(G) 包含结束符号
$
,表示文法的结束 - 因此,FOLLOW(G) =
- 由于
-
FOLLOW(G')
- 由于
G -> E G'
,G' 后面可能跟着 FOLLOW(G) - 因此,FOLLOW(G') = FOLLOW(G) =
- 由于
-
FOLLOW(E)
- 由于
G -> E G'
,E 后面可能跟着 FIRST(G'),但因为 G' 可以推导出 ε,所以 FOLLOW(E) 也包含 FOLLOW(G') - 因此,FOLLOW(E) = (FIRST(G') - {ε}) ∪ FOLLOW(G') = {a} ∪ {$} =
- 由于
-
FOLLOW(E')
- 由于
E -> a x E'
,E' 后面可能跟着 FOLLOW(E) - 因此,FOLLOW(E') = FOLLOW(E) =
- 由于
总结来说,我们计算了每个非终结符的FIRST和FOLLOW集合,并根据文法规则推导出了它们。实际的FOLLOW集合需要根据文法的完整上下文来确定。您的推理是正确的,FOLLOW(G) 应该包含结束符号 $
,而 FOLLOW(E) 应该包含从 FIRST(G') 中去除 ε 后的集合以及 FOLLOW(G')。
(3)LL(1)预测分析表构造:
假设还是以上边的“线性方程组”进行分析
G -> E G' G' -> E G' | ε E -> a x E' E' -> + b x E' | ε
首先列表:
列 = 非终结符,行 = 终结符 + '$'
FIRST(G): {a} FIRST(G'): {a,ε} FIRST(E): {a} FIRST(E'): {+,ε} FOLLOW(G): {$} FOLLOW(G'): {$} FOLLOW(E): {a,$} FOLLOW(E'): {a,$}
a | b | x | + | $ | |
---|---|---|---|---|---|
G | |||||
G' | |||||
E | |||||
E' |
然后,根据下一个终结符的可能情况,选择适当的产生式填表
(1)填入FIRST中的非ε元素:
a | b | x | + | $ | |
---|---|---|---|---|---|
G | G->EG' | ||||
G' | G'->EG' | ||||
E | E->axE' | ||||
E' | E'->+bxE' |
(2)考虑ε元素,填入FOLLOW:
a | b | x | + | $ | |
---|---|---|---|---|---|
G | G->EG' | ||||
G' | G'->EG' | G'->ε | |||
E | E->axE' | ||||
E' | E'->ε | E'->+bxE' | E'->ε |
(3)在空白处填入error
a | b | x | + | $ | |
---|---|---|---|---|---|
G | G->EG' | err | err | err | |
G' | G'->EG' | err | err | err | G'->ε |
E | E->axE' | err | err | err | |
E' | E'->ε | err | err | E'->+bxE' | E'->ε |
如果我们用上面的分析表来解析这样一个字符串
axax+bx
那么如何给出具体的分析过程呢?
已匹配 | 栈 | 输入 | 动作 |
---|---|---|---|
G | axax+bx$ | ||
EG' | axax+bx$ | G->EG' | |
axE'G' | axax+bx$ | E->axE' | |
ax | E'G' | ax+bx¥ | 匹配ax |
ax | G' | ax+bx$ | E'->ε |
ax | EG' | ax+bx$ | G'->EG' |
ax | axE'G' | ax+bx$ | E->axE' |
axax | E'G' | +bx$ | 匹配ax |
axax | +bxE'G' | $ | E' -> +bxE' |
axax+bx | E'G' | $ | 匹配+bx |
axax+bx | G' | $ | E' -> ε |
axax+bx | $ | G' -> ε |
这样就完成了全过程~
接下来就是LR文法啦!
(4)LR(0)项集族构建
由于文法中存在右递归,所以可能还要把原来的文法修改一下:
G -> E G' G' -> E G' | ε E -> a x E' E' -> + b x E' | ε
这里为了方便叙述,我们把a和b合并,统称为a(即未知数x前的系数)
G -> G E | E E -> E + a x | a x
那么如何构建它的LR项集族呢?
首先,我们增广文法,设置一个新的非终结符G'和产生式G' -> G
;
0:G' -> G 1:G -> G E 2:E -> E + a x 3:E -> a x 4:G -> E
I0:
G' -> •G
G -> •GE
G -> •E
E -> •E+ax
E -> •ax
I1:
G' -> G•
G -> G•E
E -> •E+ax
E -> •ax
I2:
G -> E•
E -> E•+ax
I3:
E -> a•x
I4:
G -> GE•
E -> E•+ax
I5:
E -> E+•ax
I6:
E -> ax•
I7:
E -> E+a•x
I8:
E -> E+ax•
(注意这里我把闭包生成的产生式用反引号标注出来了,这样便于区分)
为了填写ACTION-GOTO表,我们需要根据每个状态中的项来确定ACTION(移进或规约)和GOTO(转移到的状态)。以下是根据您提供的状态构建的ACTION-GOTO表:
状态 | ACTION | GOTO | ||||
---|---|---|---|---|---|---|
+ | a | x | $ | G | E | |
0 | s3 | 1 | 2 | |||
1 | s3 | acc | 4 | |||
2 | s5 | r4 | r4 | r4 | ||
3 | s6 | |||||
4 | s5 | r1 | r1 | r1 | ||
5 | s7 | |||||
6 | r3 | r3 | r3 | r3 | ||
7 | s8 | |||||
8 | r2 | r2 | r2 | r2 |
这里的"s"表示移进(shift),"r"表示规约(reduce),"acc"表示接受(accept)。数字表示移进或规约后进入的状态。例如,状态0在遇到终结符"a"时移进到状态3,在遇到终结符"x"时移进到状态4,在遇到非终结符"G"时转移到状态1,在遇到非终结符"E"时转移到状态2。
请注意,这里的规约动作是根据产生式进行的。例如,状态2表示使用产生式E -> E + a x
进行规约,状态6和状态8表示使用产生式E -> a x
进行规约。状态1表示接受,意味着分析栈为空,输入缓冲区中只剩下结束符,分析成功。
(5)SLR
为了将其转换为SLR文法,我们需要计算原来文法的FOLLOW集合。
G -> G E | E E -> E + a x | a x
首先,FIRST(G) = FIRST(E)(由产生式1); FIRST(E) = {a}(由产生式2),所以FIRST(G) = {a}.
FOLLOW(G) = {$} + FIRST(E) (产生式1,第一条规则) + FOLLOW(E) (产生式1,第二条规则);
FOLLOW(E) = {$,a,+} (产生式2的第一条规则:FOLLOW(E)中含有{+},产生式1中第二条规则:FOLLOW(E)包含FOLLOW(G)).
FOLLOW(G) = {$,a,+}
然后,对于规约到G的所有操作(r1,r4),只有下一个字符属于FOLLOW(G)才执行;对于规约到E的所有操作(r2,r3),只有下一个字符属于FOLLOW(E)才执行。
状态 | ACTION | GOTO | ||||
---|---|---|---|---|---|---|
+ | a | x | $ | G | E | |
0 | s3 | 1 | 2 | |||
1 | s3 | acc | 4 | |||
2 | r4 | r4 | r4 | |||
3 | s6 | |||||
4 | s5 | r1 | r1 | |||
5 | s7 | |||||
6 | r3 | r3 | r3 | |||
7 | s8 | |||||
8 | r2 | r2 | r2 |
(6)LR(1)
如果我们需要构造LR(1)文法的话,我们需要向前看一个符号,这也就导致之前的状态会有所增加。(SLR:8个状态,LR(1):?个状态)
LR(1)项集族也叫做规范LR项集族
FIRST(G) = {a}
FIRST(E) = {a}
FOLLOW(G) = {$,a,+}
FOLLOW(E) = {$,a,+}
首先还是增广文法:
0:G' -> G 1:G -> G E 2:E -> E + a x 3:E -> a x 4:G -> E
然后,从一开始的状态开始,一步一步往下写状态:
注意LR(1)和LR(0)的不同点是,我们要对First(βa)的所有字符将闭包生成的集合放进去
关键:B是紧跟在•后边的下一个非终结符, 向前看符号由上一步闭包的First(βa)确定
LR能正确处理左递归,但是左递归在计算闭包过程中会出现闭包产生自身的情况,如果被忽略的话很容易漏掉一部分向前看符号(比如说这里就漏掉了a)
I0:
G' -> •G,$ `G -> •GE,$` #FIRST(\epsilon) = $(闭包的上一步是G' -> •G) `G -> •E,$/a` #FIRST(\epsilon) = $,不过这个表达式也可以由`G -> •GE,$`产生,所以还要加上FIRST(E), 即{a}. `E -> •E+ax,$/+/a` #由`G -> •E,$`产生,所以FIRST(\epsilon)仍然是\$. `E -> •ax,$/+/a` #可以由`G -> •E,$`和`E -> •E+ax,$`产生,所以向前看符号可以是$,+
I1: (I0->G)
G' -> G•,$ G -> G•E,$ `E -> •E+ax,$/+` #FIRST(\epsilon) = {$},但是闭包也有可能推出自身,所以FIRST(+ax) = {+}也添加进去了 `E -> •ax,$/+` #FIRST(\epsilon) = {$}, FIRST(+ax) = {+}
I2: (I0->E)
G -> E•,$/a E -> E•+ax,$/+/a
I3: (I0->a)
E -> a•x,$/+/a
I4: (I1->a) #新增状态
E -> a•x,$/+
I5: (I1->E)
G -> GE•,\$ E -> E•+ax,$/+
I6: (I2->+),
E -> E+•ax,$/+/a
I7: (I3->x)
E -> ax•,$/+/a
I8: (I4->x)
E -> ax•,$/+
I9: (I5->+)
E -> E+•ax,$/+
I10: (I6->a)
E -> E+a•x,$/+/a
I11: (I9->a)
E -> E+a•x,$/+
I12: (I10->x)
E -> E+ax•,$/+/a
I13: (I11->x)
E -> E+ax•,$/+
GOTO和ACTION表构造
(构造时可以先填入状态转换部分,即移入部分;然后填写规约和acc)
状态 | ACTION | GOTO | ||||
---|---|---|---|---|---|---|
a | + | x | $ | G | E | |
0 | s3 | 1 | 2 | |||
1 | s4 | acc | 5 | |||
2 | r4 | s6 | r4 | |||
3 | s7 | |||||
4 | s8 | |||||
5 | s9 | r1 | ||||
6 | s10 | |||||
7 | r3 | r3 | r3 | |||
8 | r3 | r3 | ||||
9 | s11 | |||||
10 | s12 | |||||
11 | s13 | |||||
12 | r2 | r2 | r2 | |||
13 | r2 | r2 |
Another Example:
假设一个文法是这样的(已经经过增广文法步骤)
S' -> S
S -> L = R | R
L -> * R | id
R -> L
如何构造其LR(1)项集族的第一个状态I0呢?
好的,让我们构造这个文法的LR(1)项集族的第一个状态 ( I_0 )。文法是:
S' -> S S -> L = R | R L -> * R | id R -> L
增广文法
我们从增广文法开始,这里已经给出了增广文法:
0: S' -> S 1: S -> L = R 2: S -> R 3: L -> * R 4: L -> id 5: R -> L
构造项集 ( I_0 )
构造项集 ( I_0 ) 的步骤如下:
- 初始项目:将初始项目
S' -> •S, $
放入 ( I_0 )。 - 闭包计算:根据初始项目,逐步计算其闭包,加入所有可以由点后面非终结符推出的项目。
初始项目
I0: S' -> •S, $
闭包计算
我们看到 S
后面是点,因此我们要加入所有 S
的产生式项目,并且需要添加对应的向前看符号。
-
处理
S
:S -> •L = R, $ S -> •R, $ - 对于
S -> •L = R, $
,由于=
是一个终结符,所以向前看符号是$
。 - 对于
S -> •R, $
,由于没有更多的产生式,向前看符号是$
。
- 对于
-
处理
L
:L -> •* R, = L -> •id, = - 对于
L -> •* R, =
,由于*
是一个终结符,所以向前看符号是=
。 - 对于
L -> •id, =
,由于id
是一个终结符,所以向前看符号是=
。
- 对于
-
处理
R
:R -> •L, $ - 对于
R -> •L, $
,由于没有更多的产生式,向前看符号是$
。
- 对于
-
处理
L
(从R -> •L, $
推出):L -> •* R, $ L -> •id, $ - 对于
L -> •* R, $
,由于*
是一个终结符,所以向前看符号是$
。 - 对于
L -> •id, $
,由于id
是一个终结符,所以向前看符号是$
。
- 对于
合并项目
我们现在将所有这些项目合并到一起,形成 ( I_0 ):
I0: S' -> •S, $ S -> •L = R, $ S -> •R, $ L -> •* R, =/$ L -> •id, =/$ R -> •L, $
解释
- 项目
S' -> •S, $
表示起始项目。 - 项目
S -> •L = R, $
和S -> •R, $
来自于文法规则S
。 - 项目
L -> •* R, =/$
和L -> •id, =/$
来自于文法规则L
。 - 项目
R -> •L, $
来自于文法规则R
。
通过这些步骤,我们完成了LR(1)项集族的第一个状态 ( I_0 ) 的构造。
(7)LALR(1)
需要将LR(1)中相同核心(或者内核)的项合并。
I0:
- `G -> •GE,$` #FIRST(\epsilon) = $(闭包的上一步是G' -> •G) - `G -> •E,$/a` #FIRST(\epsilon) = $,不过这个表达式也可以由`G -> •GE,$`产生,所以还要加上FIRST(E), 即{a}. - `E -> •E+ax,$/+/a` #由`G -> •E,$`产生,所以FIRST(\epsilon)仍然是\$. - `E -> •ax,$/+/a` #可以由`G -> •E,$`和`E -> •E+ax,$`产生,所以向前看符号可以是\$,+
I1: (I0->G)
G' -> G•,\$ G -> G•E,\$ `E -> •E+ax,$/+` #FIRST(\epsilon) = {\$},但是闭包也有可能推出自身,所以FIRST(+ax) = {+}也添加进去了 `E -> •ax,$/+` #FIRST(\epsilon) = {$}, FIRST(+ax) = {+}
I2: (I0->E)
G -> E•,\$/a E -> E•+ax,\$/+/a
I3: (I0->a)
E -> a•x,$/+/a
I4: (I1->a) #新增状态
E -> a•x,$/+
I5: (I1->E)
G -> GE•,\$ E -> E•+ax,$/+
I6: (I2->+),
E -> E+•ax,$/+/a
I7: (I3->x)
E -> ax•,$/+/a
I8: (I4->x)
E -> ax•,$/+
I9: (I5->+)
E -> E+•ax,$/+
I10: (I6->a)
E -> E+a•x,$/+/a
I11: (I9->a)
E -> E+a•x,$/+
I12: (I10->x)
E -> E+ax•,$/+/a
I13: (I11->x)
E -> E+ax•,$/+
我们可以看到: I3
和I4
,I6
和I9
,I7
和I8
,I10
和I11
,I12
和I13
都是具有相同核心的项!那么...
状态 | ACTION | GOTO | ||||
---|---|---|---|---|---|---|
a | + | x | $ | G | E | |
0 | s34 | 1 | 2 | |||
1 | s34 | acc | 5 | |||
2 | r4 | s69 | r4 | |||
3,4 | s78 | |||||
5 | s69 | r1 | ||||
6,9 | s1011 | |||||
7,8 | r3 | r3 | r3 | |||
10,11 | s1213 | |||||
12,13 | r2 | r2 | r2 |
我们可以构造一个这样的LALR分析表!
对比之前的SLR分析表,可以发现SLR和LALR(1)都是只有9个状态:
状态 | ACTION | GOTO | ||||
---|---|---|---|---|---|---|
+ | a | x | $ | G | E | |
0 | s3 | 1 | 2 | |||
1 | s3 | acc | 4 | |||
2 | r4 | r4 | r4 | |||
3 | s6 | |||||
4 | s5 | r1 | r1 | |||
5 | s7 | |||||
6 | r3 | r3 | r3 | |||
7 | s8 | |||||
8 | r2 | r2 | r2 |
如何用上面的SLR和LALR(1)分析表分别给出它们解析axax+ax
的过程呢?
SLR的分析过程
状态栈 | 符号 | 输入 | 动作 |
---|---|---|---|
0 | axax+ax | s3 | |
0 3 | a | xax+ax | s6 |
0 3 6 | ax | ax+ax | r3 |
0 2 | E | ax+ax | r4 |
0 1 | G | ax+ax | s3 |
0 1 3 | Ga | x+ax | s6 |
0 1 3 6 | Gax | +ax | r3 |
0 1 4 | GE | +ax | s5 |
0 1 4 5 | GE+ | ax | s7 |
0 1 4 5 7 | GE+a | x | s8 |
0 1 4 5 7 8 | GE+ax | r2 | |
0 1 4 | GE | r1 | |
0 1 | G | acc |
LALR的分析过程
状态栈 | 符号 | 输入 | 动作 |
---|---|---|---|
0 | axax+ax | s34 | |
0 34 | a | xax+ax | s78 |
0 34 78 | ax | ax+ax | r3 |
0 2 | E | ax+ax | r4 |
0 1 | G | ax+ax | s34 |
0 1 34 | Ga | x+ax | s78 |
0 1 34 78 | Gax | +ax | r3 |
0 1 5 | GE | +ax | s69 |
0 1 5 69 | GE+ | ax | s1011 |
0 1 5 69 1011 | GE+a | x | s1213 |
0 1 5 69 1011 1213 | GE+ax | r2 | |
0 1 5 | GE | r1 | |
0 1 | G | acc |
发现它们的分析过程基本是一样的!
GPT的解释:
在这个特定的例子中,SLR和LALR分析表确实产生了相同的分析过程,这是因为文法在这个情况下没有冲突,且状态合并后没有产生新的冲突。然而,这并不意味着SLR和LALR总是等效的。在更复杂的文法中,LALR分析表可能会比SLR分析表更小,因为它能够合并一些状态,从而减少表的大小和分析器的复杂性。
LALR(1)分析表的一个主要优势是它通常比SLR(1)分析表更小,因为它能够合并一些看起来相似的状态,只要这些状态的向前看符号集合是相同的。这种合并可以显著减少分析表的大小,从而减少分析器的内存需求。然而,这种合并有时会导致新的移进-规约冲突或规约-规约冲突,这在SLR分析表中可能不会出现。
在实际应用中,LALR(1)分析器通常被认为是一个很好的折中方案,因为它既能够处理比SLR(1)更广泛的文法,又不会像LR(1)分析器那样需要大量的状态。因此,即使在这个例子中SLR和LALR分析过程相同,LALR(1)仍然是一个更通用的选择,特别是在处理更复杂的编程语言文法时。
(8) 一些相关概念
8.1 句型、句子和句柄
在语法分析中,句型、句子和句柄是三个重要的概念,它们描述了文法产生式和分析过程中的不同阶段和结构。
-
句型(Sentential Form):
句型是指在语法分析过程中,从文法的开始符号出发,通过一系列产生式推导出的中间形式。它是一个字符串,可能包含终结符、非终结符以及可能的语法分析器状态信息(如点记号.
在LR分析中的位置)。句型不一定是合法的句子,因为它可能还在推导的过程中,包含未展开的非终结符。 -
句子(Sentence):
句子是指从文法的开始符号出发,通过一系列产生式推导出的仅包含终结符的字符串。换句话说,句子是句型的一种特殊情况,其中所有的非终结符都已经被替换为终结符。句子是文法定义的语言中的合法成员,它代表了语法分析过程的最终结果。 -
句柄(Handle):
句柄是指在句型中的一个最左边的产生式,它可以被规约以向文法的开始符号靠近。在自底向上的语法分析(如LR分析)中,句柄是当前句型中需要被规约的部分,以便继续分析过程。句柄的确切位置取决于分析器的当前状态和输入。在LR分析中,句柄通常是由分析表指示的,当分析器处于某个特定状态时,它会识别出当前句型中的句柄,并执行规约操作。
例如,考虑以下文法:
S -> E E -> E + T E -> T T -> a
对于输入a + a
,分析过程中的句型可能如下:
- 初始句型:
S
- 推导后的句型:
E
- 进一步推导:
E + T
- 规约后的句型:
E + a
- 最终句子:
a + a
在这个过程中,a
、T
、E + T
和E + a
都是句型,而a + a
是句子。在自底向上的分析中,a
、T
和E + T
都是句柄,因为它们是当前句型中最左边的可规约部分。
8.2 最左推导和最右推导
最左推导:在每一步推导中,替换最左边的非终结符
最右推导:在每一步推导中,替换最右边的非终结符
如果一个文法定义了加法和取模操作:
M -> M mod M M -> M + M M -> (M) M -> id
如何给出一个具有两个最左推导的串来说明它的二义性?如何消除二义性?
为了说明这个文法的二义性,我们需要找到一个串,它可以通过两种不同的最左推导方式得到,这意味着存在两种不同的语法树。
考虑串 id + id mod id
,我们可以通过两种不同的最左推导方式得到它:
最左推导1:
M -> M + M -> id + M -> id + M mod M -> id + id mod id
最左推导2:
M -> M mod M -> M + M mod M -> id + id mod id
这两种推导方式说明了文法的二义性,因为同一个串 id + id mod id
可以通过两种不同的语法树得到。
为了消除二义性,我们需要修改文法,引入优先级和结合性规则。例如,我们可以定义取模操作的优先级高于加法操作,并且所有操作都是左结合的。以下是一个消除二义性的文法示例:
E -> E + T E -> T T -> T mod F T -> F F -> (E) F -> id
在这个文法中,E
表示表达式,T
表示项,F
表示因子。取模操作的优先级高于加法操作,因为取模操作在项 T
的产生式中,而加法操作在表达式 E
的产生式中。这样,id + id mod id
将只有一种最左推导方式,从而消除了二义性。
如果一个文法定义如下(后缀表达式):
S -> S S + | S S * | id
如何说明它是没有二义性的呢?
要说明这个后缀表达式文法没有二义性,我们需要证明对于任何给定的后缀表达式,只存在一种最左推导方式。后缀表达式的一个关键特性是运算符总是出现在它们的操作数之后,这意味着不存在运算符优先级或结合性的问题。
考虑后缀表达式 id id + id *
,我们可以通过以下唯一的最左推导方式得到它:
- 从
S
开始,替换最左边的非终结符S
为S S +
,得到S S +
。 - 再次替换最左边的非终结符
S
为id
,得到id S +
。 - 然后替换最左边的非终结符
S
为id
,得到id id +
。 - 最后替换最左边的非终结符
S
为S S *
,得到id id + S S *
。 - 再次替换最左边的非终结符
S
为id
,得到id id + id S *
。 - 最后替换最左边的非终结符
S
为id
,得到id id + id *
。
这个推导过程表明,对于给定的后缀表达式,只存在一种最左推导方式。因此,这个文法是没有二义性的。
后缀表达式的文法之所以没有二义性,是因为它的结构保证了每个运算符都紧跟在其操作数之后,不会出现多种解释的可能性。在解析后缀表达式时,我们可以从左到右扫描表达式,每当遇到一个运算符时,它前面的两个操作数就是它的操作数,这种结构是唯一确定的。
8.3 语法分析器的错误恢复
(1)LL(1)分析表的恐慌模式
以上面的文法为例:
G -> E G' G' -> E G' | ε E -> a x E' E' -> + b x E' | ε
我们构造的LL(1)分析表如下:
a | b | x | + | $ | |
---|---|---|---|---|---|
G | G->EG' | err | err | err | err |
G' | G'->EG' | err | err | err | G'->ε |
E | E->axE' | err | err | err | err |
E' | E'->ε | err | err | E'->+bxE' | E'->ε |
如果LL(1)分析表带上恐慌模式是什么样的呢?
将FOLLOW(A)所有符号放在A的同步集合中:
a | b | x | + | $ | |
---|---|---|---|---|---|
G | G->EG' | err | err | err | syn |
G' | G'->EG' | err | err | err | G'->ε |
E | E->axE' | err | err | err | syn |
E' | E'->ε | err | err | E'->+bxE' | E'->ε |
本文作者:Yuzu_OvO(喵露露版)
本文链接:https://www.cnblogs.com/yuzusbase/p/18270583
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步