语法树和二义性

来源:互联网 发布:pic单片机指令集 编辑:程序博客网 时间:2024/06/04 18:29

http://jpkc.nwpu.edu.cn/jp2005/20/kcwz/wlkc/wlkc/02/2_3_2.htm

上面我们把文法G[S]的句型定义为能从S推导出来的符号串。现在,我们要引入一个重要的工具——语法树或推导树,借助于它,可更直观和更清晰地描述一个句型或句子的语法结构。
我们这里所说的“树”,是指由若干个结点所组成的有限集,在两结点之间,用一条有向边加以连接 (如○m→○n,通常我们把结点m称为结点n的直接前驱或父结点;而将结点n称为结点m的直接后继或子结点),且具有如下的性质:
(1) 在这组结点中,有一个且仅有一个没有任何前驱的结点,称之为“根”;
(2) 除根之外,每一结点都恰好有一个直接前驱;
(3) 对于每一结点,都有一条从根到此结点的通路;
(4) 若一个结点有多个直接后继,则按自左向右的顺序进行排序。
    显然,性质(2)和(3)保证了树的无回路性和连通性。通常我们把那些没有任何后继的结点称为树的叶子或末端结点。此外,设T是一棵树,ni是T中的任一结点,则由ni及其全部后继所组成的结点集合Di也构成了以ni为根的树Ti,我们将这样的Ti称为T的子树。若一个子树的根只有直接后继,而无更远代的后继,则称这种子树为直接子树。
设G=(VN,VT,P,S)为一文法,则满足下面四个条件的一棵树称之为G的一棵语法树或推导树:
(1) 每一结点均有一标记,此标记为VN∪VT中的一个符号;
(2) 树的根结点以文法的开始符号S标记;
(3) 若一结点至少有一个直接后继,则此结点上的标记为VN中的一个符号;
(4) 若一个以A为标记的结点有k个直接后继,且按从左至右的顺序,这些结点的标记分别为X1,X2,…,Xk,则A→X1X2…Xk必然是G的一个产生式。
    下面,我们以G2[E]中的句子i+i*i为例,说明如何构造与一句型推导相应的语法树。
首先,画一个标记为E的结点作为语法树的根 (如图21(a)),它实质上是只含一个结点的语法树,与句型E相关联。因为第一步推导为EE+T,所以以E为标记的根结点应有三个直接后继,且按从左到右顺序,这三个结点的标记分别为E,+和T。此时,新得到的语法树 (如图21(b))将与句型E+T相关联。仿此,在后继的各步推导中,每当句型中的一个非终结符号被某一产生式的一个候选式替换时,就从此非终结符号所标记的结点出发,向下建图21由E推导i+i*i所建立的语法树立一个直接子树,此子树的末端结点的个数与用来替换的候选式中所含符号的个数相同,并按从左到右的顺序,在这些末端结点上分别标以该候选式中的各个符号。这样,对于推导E+i+i*i,相应的语法树如图21(c)所示。
    以上所说的,是从树的根结点出发,按句型或句子的推导序列,逐步向下构造语法树的过程。自然,我们也可首先以句型或句子中的各个符号作为语法树的末端结点,并从末端结点出发,按归约的序列,逐步向上构造语法树的各个子树,最后再构造出以文法的开始符号标记的结点为根的整个语法树来。这也就是我们把逐步推导和逐步归约的过程分别称为自顶向下和自底向上的语法分析的原因。
     从上面的讨论可以看出,对于文法G中任一句型的推导序列,我们总能为它构造一棵语法树,而且将这棵语法树的末端结点上的标记从左到右依次排列起来,也就组成了所给的句型。反之,当给定G的一棵语法树时,把它的末端结点上的标记从左到右排列起来所组成的符号串也必然是G的一个句型。然而,对于给定的一棵语法树来说,它仅显示了在推导相应句型时使用了哪些产生式,这些产生式用于哪些非终结符号的推导 (据语法树的性质(4)),以及前驱结点上标记的非终结符号总是比其后继所标记的非终结符号先被相应产生式的右部替换之外,并不提供所涉及的这些产生式在使用顺序上的其它信息。因此,对于同一棵语法树而言,由于在建立时使用产生式顺序上的不同,就表征了不同的推导序列。即对应于同一语法树,将存在各种可能的推导序列。例如,图21(c)所示的语法树,就同时表征了推导序列(23)、(24)、(25),以及从E到i+i*i的一切其它可能的推导序列。不过,对于一个给定的语法树而言,它仅对应于惟一的最左推导和最右推导 (如果最左推导或最右推导存在的话)。
     但是,也存在这样的文法G,对于L(G)中的某个句子w,与w相应的语法树将不只一个,也就是说,w有多个不同的最左推导或最右推导。以后,我们将这样的文法称为有二义性的文法。如果一个文法所产生的每一句子都仅有一棵语法树,则称此文法为无二义性的。
    例如,对于下面的文法G3[E]:
E→E+E|E*E| (E)|i(26)
符号串i+i*i是L(G3)中的句子,且有如下两个不同的最右推导:
EE+EE+E*EE+E*i
E+i*ii+i*i(27)
EE*EE*iE+E*i
E+i*ii+i*i(28)
    推导序列(27)和(28)分别对应两棵不同的语法树,如图22(a)及图22(b)所示。

    

故可知文法G3[E]是二义性的。
    图22G3[E]句子i+i*i的两棵语法树再如,在PASCAL语言中将if语句定义为:
C→if B then C
C→if B then C else C(29)
C→S
    其中,B代表布尔表达式,S代表语句。显然,符号串
if B1 then if B2 then S1 else S2(210)
    应是上述语言中的一个合法语句。但是,式(210)却存在两种不同的理解 (即存在两种不同的最右推导):
if B1 then (if B2 then S1 else S2)

if B1 then (if B2 then S1) else S2
    对应于这两种理解,将存在两棵不同的语法树,如图23(a)及23(b)所示。因此,若把式(29)视为一个文法G[C],则此文法是二义性的。C语言中的if语句亦有类似的情况。

    由此可见,二义性是一种常见的现象。显然,对于编译程序而言,我们自然要求它们所处理的语言是用无二义性的文法来定义的。这是因为,如果一个文法是二义性的,那么,在它所描述的语言中,必然至少含有这样的句子,当编译程序对它的结构进行语法分析时,就会产生两种甚至更多种不同的理解,由于语法结构分析上的不确定性,将必然会导致语义处理上的不确定性,归根到底,也就会出现对同一源程序符号串在目标代码生成上的不确定性。例如,对于文法G3[E]。如果我们把终结符号+和*分别视为加法运算符和乘法运算符,而将终结符号i视为运算对象,那么,对于句子i+i*i,当按图22(a)进行语法分析时,符号串E*E比符号串E+E先进行归约,故在语义分析和目标代码生成时,先产生乘法运算的指令,后产生加法运算的指令,从而在目标程序运行时,将先进行乘法运算,再进行加法运算;当按图22(b)进行语法分析时,则情况恰好相反,即先产生加法运算指令,后产生乘法运算指令,而当程序运行时,加法运算将先于乘法运算执行。对于文法(29)所定义的句子(210),则相应于图23(a)及图23(b)这两种不同的语法分析,将分别按图24(a)及图24(b)所示的两种完全不同的逻辑结构来产生目标代码。

     因此,在设计一个编译程序时,应事先设法弄清所处理的语言是否由二义性文法定义的。但遗憾的是,并不存在这样的一个算法,它能判断任一前后文无关文法G是否为二义性文法,即前后文无关文法是否具有二义性是不可判定的。这是早在1962年至1963年就为Floyd, Contor和Chomsky等人证明了的事实。虽然如此,我们却不必为此感到担心,因为这仅仅是前后文无关文法类中的一个最一般性的结论,对于某些具体的文法而言,我们仍可判断它们的二义性或无二义性。例如,在第4章中将要讨论的LL(1), LR(0),LR(1)等几类重要的文法都是无二义性的。同时,还存在这样一些算法,它们可分别用来判定任一前后文无关文法是否为LL(1),LR(0)或LR(1)文法[16]。不过,文法的LL性或LR性仅仅是文法无二义性的充分条件,故当判明一个文法不是LL文法或LR文法时,此文法就有可能是二义性的。另一方面,也还存在一些用来检查文法二义性的其它充分条件。例如,若一个文法G含有既是左递归亦是右递归的非终结符号A,即有
A+[]GAυAυ∈V*
或A+A
或A+Aα及A+βA
则G必定是二义性文法。一个文法兼有左递归和右递归是导致其二义性的最常见原因之一。例如,对于文法G3[E],因为其中含有产生式E→E+E及E→E*E,故由此即可断言,它是二义性的文法。
由于同一个语言可能由若干个等价的文法来定义。因此,若一个语言L由某一文法G定义,而G是一个二义性文法,则往往可对G进行改写,以期求得另一文法G′,使L(G′)=L(G)=L,而G′是无二义性的。例如,文法G3[E]与文法G2[E]等价,而后者是无义性的。至于文法(29),在PASCAL和C中,二义性的消除则是通过如下的约定: 在复合if语句中,else子句总是属于最靠近的、尚无else子句的那个if语句。在这种解释下,符号串(210)将对应于惟一的语法树,即图23(a)。不过,并非所有的二义文法都能通过等价改造来消除其二义性。换言之,也就是存在这样的前后文无关语言,用来定义该语言的一切文法都是二义性的。通常我们把这样的语言称为先天或固有二义性的语言。例如,可以证明,前后文无关语言
L={anbncmdm | n≥1, m≥1}∪{anbmcmdn | n≥1,m≥1} (211)
为一先天二义性语言[8]。同样,业已证明,前后文无关语言的先天二义性也是不可判定的。
由以上的讨论可清楚地看到,文法的二义性和语言的二义性是既有联系而又有区别的两个概念。由于在程序设计语言中并不涉及语言的先天二义性,故通常我们只谈及文法的二义性。


0 0