关于文法G2算符优先分析的一个坑
- [关于文法 G 2 G_2 G2 的算符优先分析疑问:为什么 a → S a \to S a→S 后不一定能立刻 S → T S \to T S→T?](#关于文法 G 2 G_2 G2 的算符优先分析疑问:为什么 a → S a \to S a→S 后不一定能立刻 S → T S \to T S→T?)
-
- [1. 问题背景](#1. 问题背景)
- [2. 算符优先分析的基本原则](#2. 算符优先分析的基本原则)
- [3. 问题出现在哪里?](#3. 问题出现在哪里?)
- [4. 为什么算符优先分析"不知道" S S S 应该变成 T T T?](#4. 为什么算符优先分析“不知道” S S S 应该变成 T T T?)
- [5. 能不能等到后面再把 S S S 变成 T T T?](#5. 能不能等到后面再把 S S S 变成 T T T?)
- [6. 为什么不能在第三步直接写 a ⇒ S ⇒ T a \Rightarrow S \Rightarrow T a⇒S⇒T?](#6. 为什么不能在第三步直接写 a ⇒ S ⇒ T a \Rightarrow S \Rightarrow T a⇒S⇒T?)
- [7. 这个文法为什么不适合直接做标准算符优先分析?](#7. 这个文法为什么不适合直接做标准算符优先分析?)
- [8. 一个较合理的分析思路](#8. 一个较合理的分析思路)
- [9. 结论](#9. 结论)
- [10. 一句话总结](#10. 一句话总结)
关于文法 G 2 G_2 G2 的算符优先分析疑问:为什么 a → S a \to S a→S 后不一定能立刻 S → T S \to T S→T?
1. 问题背景
最近在做编译原理中"算符优先分析"的题目时,遇到了一个很容易混淆的问题。
题目给出的文法为:
S → a ∣ Λ ∣ ( T ) S \to a \mid \Lambda \mid (T) S→a∣Λ∣(T)
T → T , S ∣ S T \to T,S \mid S T→T,S∣S
其中:
- S S S 表示一个表元素;
- T T T 表示由一个或多个表元素组成的序列;
- Λ \Lambda Λ 在这里表示空表元素,是终结符,不是空串 ε \varepsilon ε。
例如字符串: ( a , ( a , a ) ) (a,(a,a)) (a,(a,a))可以看成一个外层表,里面有两个元素: a a a和 ( a , a ) (a,a) (a,a)
从语法树角度看,这个串当然可以由文法推出。但是如果用算符优先分析的移进、归约过程去写,就会出现一个问题:
当 a a a 归约成 S S S 后,到底要不要马上继续把 S S S 归约成 T T T?
2. 算符优先分析的基本原则
算符优先分析在判断"移进"还是"归约"时,主要看的是:
栈中最右边的终结符 和 当前输入符号 之间的优先关系。
也就是说,它关注的是终结符之间的关系,而不是非终结符之间的关系。
例如当前状态为:
( S , ( a , a ) ) # \#(S \quad ,(a,a))\# #(S,(a,a))#
栈中最右边的终结符是: ( ( (
当前输入符号是: , , ,
如果优先关系表中有: ( ⋖ , ( \lessdot , (⋖,
那么按照算符优先分析规则,此时应该执行:
移进 \text{移进} 移进
而不是归约。
也就是说,此时不能随便把:
S ⇒ T S \Rightarrow T S⇒T
3. 问题出现在哪里?
在分析字符串:
( a , ( a , a ) ) (a,(a,a)) (a,(a,a))
时,加上界符后输入串为:
( a , ( a , a ) ) # \#(a,(a,a))\# #(a,(a,a))#
前几步通常会得到:
( a , ( a , a ) ) # \#(a \quad ,(a,a))\# #(a,(a,a))#
因为:
a ⋗ , a \gtrdot , a⋗,
所以可以归约:
a ⇒ S a \Rightarrow S a⇒S
于是得到:
( S , ( a , a ) ) # \#(S \quad ,(a,a))\# #(S,(a,a))#
接下来问题来了:
这个 S S S 要不要继续归约成 T T T?
也就是是否要做:
S ⇒ T S \Rightarrow T S⇒T
从语法树角度看,外层括号里的内容最终应该是一个 T T T,因为:
S → ( T ) S \to (T) S→(T)
而外层的 T T T 又可以推出:
T → T , S T \to T,S T→T,S
所以第一个 a a a 最终确实会变成某个 T T T 的一部分。
但是,从算符优先分析的角度看,当前状态是:
( S , ( a , a ) ) # \#(S \quad ,(a,a))\# #(S,(a,a))#
它只看栈中最右终结符和当前输入符号:
( ⋖ , ( \lessdot , (⋖,
所以它应该移进逗号,而不是直接归约:
S ⇒ T S \Rightarrow T S⇒T
4. 为什么算符优先分析"不知道" S S S 应该变成 T T T?
根本原因是文法中存在单位产生式:
T → S T \to S T→S
它的归约方向是:
S ⇒ T S \Rightarrow T S⇒T
但是这个归约只涉及非终结符 S S S 和 T T T,不涉及终结符。
而算符优先分析主要依靠终结符之间的优先关系确定句柄边界,例如:
a ⋗ , a \gtrdot , a⋗,
( ⋖ , ( \lessdot , (⋖,
, ⋖ a , \lessdot a ,⋖a
这些关系只能告诉我们什么时候移进、什么时候归约某个由终结符围起来的短语,却不能直接告诉我们:
S 现在是否应该变成 T S \text{ 现在是否应该变成 } T S 现在是否应该变成 T
所以,当 a a a 被归约成 S S S 之后,算符优先分析器并不能仅凭优先关系判断:
S ⇒ T S \Rightarrow T S⇒T
是否应该立即发生。
5. 能不能等到后面再把 S S S 变成 T T T?
可以。
一种比较合理的写法是:先只做:
a ⇒ S a \Rightarrow S a⇒S
然后继续根据优先关系移进,直到栈中出现类似:
( S , S (S,S (S,S
此时如果想把它归约成一个表元素序列,文法中没有:
T → S , S T \to S,S T→S,S
只有:
T → T , S T \to T,S T→T,S
所以必须先把左边的 S S S 通过单位归约变成 T T T:
S ⇒ T S \Rightarrow T S⇒T
然后才能归约:
T , S ⇒ T T,S \Rightarrow T T,S⇒T
也就是说,可以这样理解:
( S , S ⇒ ( T , S ⇒ ( T (S,S \Rightarrow (T,S \Rightarrow (T (S,S⇒(T,S⇒(T
这里的 S ⇒ T S \Rightarrow T S⇒T 不是由终结符优先关系直接决定的,而是为了匹配产生式:
T → T , S T \to T,S T→T,S
而补上的单位归约。
6. 为什么不能在第三步直接写 a ⇒ S ⇒ T a \Rightarrow S \Rightarrow T a⇒S⇒T?
因为文法里没有产生式:
T → a T \to a T→a
只有:
S → a S \to a S→a
和:
T → S T \to S T→S
所以严格地说, a a a 只能先归约成 S S S:
a ⇒ S a \Rightarrow S a⇒S
至于这个 S S S 后面要不要继续归约成 T T T,需要根据后续结构判断。
如果直接写:
a ⇒ S ⇒ T a \Rightarrow S \Rightarrow T a⇒S⇒T
就相当于提前假设这个 a a a 一定要作为 T T T 的一部分。
但这个判断其实来自语法树或者对整体结构的预判,并不是算符优先关系本身给出的。
所以在严格的算符优先分析过程中,不能随便把:
a ⇒ S ⇒ T a \Rightarrow S \Rightarrow T a⇒S⇒T
合并成一步。
7. 这个文法为什么不适合直接做标准算符优先分析?
标准算符优先分析希望通过终结符之间的优先关系来唯一地确定移进和归约。
但是这个文法中有:
T → S T \to S T→S
这种右部只有一个非终结符的单位产生式。
它会造成一个问题:
当栈中出现 S S S 时,分析器不知道这个 S S S 是应该停留为 S S S,还是应该进一步归约为 T T T。
例如:
( S , ( a , a ) ) # \#(S \quad ,(a,a))\# #(S,(a,a))#
从优先关系看:
( ⋖ , ( \lessdot , (⋖,
所以应该移进逗号。
但是从语法结构看,前面的 S S S 最后又需要作为 T T T 的一部分。
这就说明:该文法虽然可以求出 FIRSTVT、LASTVT 和终结符优先关系,但如果严格按照算符优先分析过程来做,可能会出现无法仅凭优先关系决定归约时机的问题。
8. 一个较合理的分析思路
对于输入串:
( a , ( a , a ) ) (a,(a,a)) (a,(a,a))
加上界符:
( a , ( a , a ) ) # \#(a,(a,a))\# #(a,(a,a))#
前几步可以写成:
| 步骤 | 符号栈 | 剩余输入串 | 动作 |
|---|---|---|---|
| 1 | # \# # | ( a , ( a , a ) ) # (a,(a,a))\# (a,(a,a))# | 移进 ( ( ( |
| 2 | # ( \#( #( | a , ( a , a ) ) # a,(a,a))\# a,(a,a))# | 移进 a a a |
| 3 | # ( a \#(a #(a | , ( a , a ) ) # ,(a,a))\# ,(a,a))# | a ⋗ , a \gtrdot , a⋗,,归约 a ⇒ S a \Rightarrow S a⇒S |
| 4 | # ( S \#(S #(S | , ( a , a ) ) # ,(a,a))\# ,(a,a))# | ( ⋖ , ( \lessdot , (⋖,,移进 , , , |
| 5 | # ( S , \#(S, #(S, | ( a , a ) ) # (a,a))\# (a,a))# | , ⋖ ( , \lessdot ( ,⋖(,移进 ( ( ( |
| 6 | # ( S , ( \#(S,( #(S,( | a , a ) ) # a,a))\# a,a))# | ( ⋖ a ( \lessdot a (⋖a,移进 a a a |
| 7 | # ( S , ( a \#(S,(a #(S,(a | , a ) ) # ,a))\# ,a))# | a ⋗ , a \gtrdot , a⋗,,归约 a ⇒ S a \Rightarrow S a⇒S |
| 8 | # ( S , ( S \#(S,(S #(S,(S | , a ) ) # ,a))\# ,a))# | ( ⋖ , ( \lessdot , (⋖,,移进 , , , |
| 9 | # ( S , ( S , \#(S,(S, #(S,(S, | a ) ) # a))\# a))# | , ⋖ a , \lessdot a ,⋖a,移进 a a a |
| 10 | # ( S , ( S , a \#(S,(S,a #(S,(S,a | ) ) # ))\# ))# | a ⋗ ) a \gtrdot ) a⋗),归约 a ⇒ S a \Rightarrow S a⇒S |
此时栈中出现:
( S , ( S , S \#(S,(S,S #(S,(S,S
为了归约内层的:
( S , S ) (S,S) (S,S)
需要利用:
T → S T \to S T→S
和:
T → T , S T \to T,S T→T,S
所以先把左边的 S S S 归约为 T T T:
( S , S ⇒ ( T , S (S,S \Rightarrow (T,S (S,S⇒(T,S
再归约:
T , S ⇒ T T,S \Rightarrow T T,S⇒T
然后移进右括号并归约:
( T ) ⇒ S (T) \Rightarrow S (T)⇒S
继续外层同理:
( S , S ⇒ ( T , S ⇒ ( T (S,S \Rightarrow (T,S \Rightarrow (T (S,S⇒(T,S⇒(T
最后:
( T ) ⇒ S (T) \Rightarrow S (T)⇒S
得到接受。
9. 结论
对于这个文法:
S → a ∣ Λ ∣ ( T ) S \to a \mid \Lambda \mid (T) S→a∣Λ∣(T)
T → T , S ∣ S T \to T,S \mid S T→T,S∣S
在分析字符串: ( a , ( a , a ) ) (a,(a,a)) (a,(a,a))时,不能简单认为: a ⇒ S a \Rightarrow S a⇒S之后一定立刻有: S ⇒ T S \Rightarrow T S⇒T
因为算符优先分析只根据终结符之间的优先关系判断移进和归约,而:
S ⇒ T S \Rightarrow T S⇒T
是一个单位归约,无法由终结符优先关系直接确定。
因此,更合理的理解是:
a ⇒ S a \Rightarrow S a⇒S
先停留为 S S S,后面如果出现需要匹配:
T → T , S T \to T,S T→T,S
的结构时,再根据产生式补上:
S ⇒ T S \Rightarrow T S⇒T
所以,这个例子说明了一个重要问题:
含有单位产生式 T → S 的文法,不适合完全机械地直接做标准算符优先分析。 \boxed{ \text{含有单位产生式 } T \to S \text{ 的文法,不适合完全机械地直接做标准算符优先分析。} } 含有单位产生式 T→S 的文法,不适合完全机械地直接做标准算符优先分析。
如果不画语法树,也不额外根据产生式结构判断,单靠优先关系表进行分析,很可能会在某些步骤不知道该不该归约,甚至导致过程写不下去。
10. 一句话总结
这个问题的本质不是"不会推导",而是:
算符优先分析依靠终结符之间的优先关系,而 T → S T \to S T→S 这种单位产生式只涉及非终结符,因此它无法仅凭优先关系判断什么时候应该做 S ⇒ T S \Rightarrow T S⇒T。
所以如果题目要求严格按照算符优先分析表来写过程,这个文法本身就不太适合直接使用标准算符优先分析方法。