• 沒有找到結果。

二义性

在文檔中 第1章 概 论 (頁 83-93)

在这里我们将保留的记号 i f和o t h e r用作标记以区分语法树中的语句类型。利用枚举类型则 会更为恰当一些。例如,利用一个 C声明的集合将本例的语句结构和表达式相应地表示如下:

3.4 二义性

3.4.1 二义性文法

分析树和语法树唯一地表达着语法结构,它们与表达最左和最右推导一样,但并不是对于 所有推导都可以。不幸的是,文法有可能允许一个串有多于一个的分析树。例如在前面作为标 准示例的简单整型算术文法中

exp → exp op exp | (exp )| n u m b e r op →

+

| -| *

和串3 4 - 3 * 4 2,这个串有两个不同的分析树:

它们与两个最左推导相对应:

第 3章 上下文无关文法及分析

8 3

下载

则相应的语法树为:

可生成带有两个不同分析树的串的文法称作二义性文法( ambiguous grammar)。由于这个文法 并不能准确地指出程序的语法结构(即使是完全确定正规串本身(它是文法的语言成员)),所 以它是分析程序表示的一个严重问题。在某种意义上,二义性文法就像是一个非确定的自动机,

此时两个不同的路径都可接收相同的串。但是因为没有一个合适的算法,因此就不能像自动机 中的情形一样(第2章中讨论过的子集构造),文法中的二义性就不能如有穷自动机中的非确定 性一样轻易地被解决 。

所以必须将二义性文法认为是一种语言语法的不完善说明,而且也应避免它。幸运的是,

二义性文法在后面将介绍到的标准分析算法的测试中总是失败的,而且也开发出了标准技术体 系来解决在程序设计语言中遇到的典型二义性。

有两个解决二义性的基本方法。其一是:设置一个规则,该规则可在每个二义性情况下指 出哪一个分析树(或语法树)是正确的。这样的规则称作消除二义性规则(disambiguating rule)。

这样的规则的用处在于:它无需修改文法(可能会很复杂)就可消除二义性;它的缺点在于语 言的语法结构再也不能由文法单独提供了。另一种方法是将文法改变成一个强制正确分析树的 构造的格式,这样就可以解决二义性了。当然在这两种办法中,都必须确定在二义性情况下哪 一个树是正确的。这就再一次涉及到语法制导翻译原则了。我们所需的分析(或语法)树应能 够正确地反映将来应用到构造的意义,以便将其翻译成目标代码。

在前面的两个语法树中,哪一个是串

3 4 - 3 * 4 2的正确解释呢?第 1个树通过把减法节点作

为乘法节点的孩子,指出可将这个表达式等于:先做减法 ( 3 4-3 = 3 1 ),然后再做乘法 (31*42 = 1 3 0 2 )。相反地,第2个树指出先做乘法 (3*42 =126)然后再做减法( 3 4-1 2 6 =-9 2 )。选择哪个树取 决于我们认为哪一个计算是正确的。人们认为乘法比减法优先( p r e c e d e n c e)。通常地,乘法和 除法比加法和减法优先。

为了去除在这个简单表达式文法中的二义性,现在可以只需设置消除二义性规则,它建立 了3个运算相互之间的优先关系。其标准解决办法是给予加法和减法相同的优先权,而乘法和 除法则有高一级的优先权。

8 4

编译原理及实践

下载

情况甚至更糟,这是因为没有算法可以在一开始时就确定一个文法是否是二义性的,参见第 3 . 2 . 7节。

不幸的是,这个规则仍然不能完全地去除掉文法中的二义性。例如串

3 4 - 3 - 4 2,这个串

也有两种可能的语法树:

第1个语法树表示计算 ( 3 4-3 )-4 2 =-11,而第2个表示计算 3 4-( 3-4 2 ) = 7 3。而哪一个算式正 确又是一个惯例的问题,标准数学规定第 1种选择是正确的。这是由于规定减法为左结合( l e f t a s s o c i a t i v e),也就是认为一个减法序列的运算是自左向右的。

因此,这又要求有一个消除二义性的规则:它能处理每一个加法、减法和乘法的结合性。

这个消除二义性的规则通常都规定它们为左结合,它确实消除了简单表达式文法中其他的二义 性问题(在后面再证明它)。

因 为 在 表 达 式 中 不 允 许 有 超 过 一 个 算 符 的 序 列 , 有 时 还 需 要 规 定 运 算 是 非 结 合 性 的

(n o n a s s o c i a t i v e)。例如,可将简单表达式文法用下面的格式写出:

exp → factor op factor | f a c t o r factor →

(

e x p

)

| n u m b e r op →

+

|

-

|

*

在这种情况中,诸如

3 4 - 3 - 4 2甚至于 3 4 - 3 * 4 2的表达式都是正规的,但它们必须带上括

号,例如( 3 4 - 3 ) - 4 2和3 4 - ( 3 * 4 2 )。这样的完全括号表达式( fully parenthesized expression)

就没有必要说明其结合性或优先权了。上面的文法正如所写的一样去除了二义性。当然,我们 不仅改变了文法,还更改了正被识别的语言。

我们不再讨论消除二义性的规则,现在来谈谈重写文法以消除二义性的方法。请注意,必 须找到无需改变正被识别的基本串的办法(正如完全括号表达式的示例所做的一样)。

3.4.2 优先权和结合性

为了处理文法中的运算优先权问题,就必须把具有相同优先权的算符归纳在一组中,并为每一 种优先权规定不同的规则。例如,可把乘法比加法和减法优先添加到简单表达式文法,如下所示:

exp → exp addop exp | t e r m addop →

+

|

-term → -term mulop -term | f a c t o r mulop →

*

factor →

(

e x p

)

| n u m b e r

在这个文法中,乘法被归在 t e r m规则下,而加法和减法则被归在 e x p规则之下。由于 e x p的 第 3章 上下文无关文法及分析

8 5

下载

基本情况是t e r m,这就意味着加法和减法在分析树和语法树中将被表现地“更高一些”(也就是,

更接近于根),由此也就接受了更低一级的优先权。这样将算符放在不同的优先权级别中的办法 是在语法说明中使用B N F的一个标准方法。这种分组称作优先级联(precedence cascade)。

简单算术表达式的最后一种文法仍未指出算符的结合性而且仍有二义性。它的原因在于算 符两边的递归都允许每一边匹配推导(因此也在分析树和语法树)中的算符重复。解决方法是 用基本情况代替递归,强制重复算符匹配一边的递归。这样将规则

exp → exp addop exp | t e r m 替换为

exp → exp addop term | t e r m 使得加法和减法左结合,而

exp → term addop exp | t e r m

却使得它们右结合。换而言之,左递归规则使得它的算符在左边结合,而右递归规则使得它们 在右边结合。

为了消除简单算术表达式B N F规则中的二义性,重写规则使得所有的运算都左结合:

exp → exp addop term | t e r m addop →

+

|

-term → -term mulop factor | f a c t o r mulop →

*

factor →

(

exp

)

| n u m b e r 现在表达式3 4 - 3 * 4 2的分析树就是

表达式3 4 - 3 - 4 2的分析树是:

8 6

编译原理及实践

下载

注意,优先级联使得分析树更为复杂。但是语法树并不受影响。

3.4.3 悬挂e l s e问题

考虑例3 . 4中的文法:

statement → if-stmt | o t h e r if-stmt →

i f (

e x p

)

s t a t e m e n t

| i f (exp )statement e l s es t a t e m e n t exp →

0

| 1

由于可选的e l s e的影响,这个文法有二义性。为了看清它,可考虑下面的串:

i f ( 0 ) i f ( 1 ) o t h e r e l s e o t h e r

这个串有两个分析树:

哪一个是正确的则取决于将单个 e l s e部分与第 1个或第2个i f语句结合:第 1个分析树将 e l s e部分 与第 1个i f语句结合;第 2个分析树将它与第 2个i f语句结合。这种二义性称作悬挂 e l s e问题

(dangling else problem)。为了分清哪一个分析树是正确的,我们必须考虑 i f语句的隐含意思,

请看下面的一段C代码:

第 3章 上下文无关文法及分析

8 7

下载

i f ( x ! = 0 )

i f ( y = = 1 / x ) o k = T R U E e l s e z = 1 / x

在这个代码中,如果 e l s e部分与第1个i f语句结合,只要 x等于0,则会发生一个除以零的错 误。因此这个代码的含义(实际是 e l s e部分缩排的含义)是指一个 e l s e部分应总是与没有 e l s e部 分的最近的 i f语句结合。这个消除二义性的规则被称为用于悬挂 e l s e 问题的最近嵌套规则

(most closely nested rule),这就意味着上面第2个分析树是正确的。请注意:如要将 e l s e部分与 第1个i f语句相结合,则要用C中的{ . . . },如在

i f ( x ! = 0 )

{ i f ( y = = 1 / x ) o k = T R U E ; } e l s e z = 1 / x ;

解决这个位于B N F本身中的悬挂e l s e二义性要比处理前面的二义性困难。方法如下:

statement → matched-stmt | u n m a t c h e d - s t m t

matched-stmt →

i f (

exp ) matched-stmt e l s ematched-stmt | o t h e r unmatched-stmt →

i f (

exp ) s t a t e m e n t

| i f (exp ) m a t c h e d - s t m t

e l s e

u n m a t c h e d - s t m t exp →

0

| 1

它允许在 i f语句中,只有一个 m a t c h e d - s t m t出现在e l s e之前,这样就迫使尽可能快地匹配所有 的e l s e部分。例如,经过结合了的简单串的分析树现在就变成了:

它确实将else 部分与第2个if 语句相连。

通常不能在B N F中建立最近嵌套规则,实际上经常所用的是消除二义性的规则。原因之一 是它增加了新文法的复杂性,但主要原因却是分析办法很容易按照遵循最近嵌套规则的方法来 配置(在无需重写文法时自动获得优先权和结合性有一点困难)。

悬挂e l s e问题来源于A l g o l 6 0的语法。我们还是有可能按照悬挂 e l s e问题不出现的方法来设 计语法。办法之一是要求出现 e l s e部分,而该办法已在 L I S P和其他函数语言中用到了(但须返 回一个值)。另一种办法是为 i f语句使用一个带括号关键字( bracketing keyword)。使用这种方 法的语言包括了 A l g o l 6 8和A d a。例如,程序员写

i f x / = 0 t h e n

i f y = 1 / x t h e n o k : = t r u e ;

8 8

编译原理及实践

下载

e l s e z : = 1 / x ;

e l s e

statement-sequence end if

因此两个关键字end if就是A d a的括号关键字。在 A l g o l 6 8中,括号关键字是

f i(反着写

stmt-sequence → stmt-sequence ;stmt-sequence | s t m t stmt →

s

指出,其中

α

β

是终结符和非终结符的任意串,且在第 1个规则中

β

不以A开始,在第2个规则

β

不以A结束。

重复有可能使用与正则表达式所用相同的表示法,即:星号 *(在正则表达式中也称作 K l e e n e闭包)。则这两个规则可被写作非递归规则

A→β α*

A→α*β

相反地,E B N F选择使用花括号{ . . . }来表示重复(因此清晰地表达出被重复的串的范围),且可 为规则写出

A→β{α}

A→{α}β

使用重复表示法的问题是:它使得分析树的构造不清楚,但是正如所看到的一样,我们对此并 不在意,例如语句序列的情形(例 3 . 9)。在右递归格式中写出如下文法:

stmt-sequence → stmt ;stmt-sequence | s t m t stmt →

s

这个规则具有格式A→

α

A

| β

,且A = s t m t - s e q u e n c e,

α= stmt ; β= s t m t。在E B N F中,它表现为:

stmt-sequence → { stmt ;} stmt (右递归格式)

同样也可使用一个左递归规则并得到 E B N F

stmt-sequence → stmt {

;

stmt } (左递归格式)

实际上,第2个格式是通常所用的(原因在下一章再讲)。

出现在结合性中的更大的一个问题是发生在诸如二进制运算的减法和除法中。例如,前面 减法的简单表达式文法中的第 1个文法规则:

exp → exp addop term | t e r m

它使得A → Aα | β,且A = e x p,α = addop term,β = t e r m。因此,将这个规则在E B N F中写作:

exp → term { addop term }

尽管规则本身并未明显地说明出来,但现在仍可假设它暗示了左结合。我们还可假设通 过写出

exp →{ term addop } t e r m 来暗示一个右结合规则,但事实并不是这样。相反地,诸如

stmt-sequence → stmt ;stmt-sequence | s t m t 的右递归规则可被看作后接一个可选的分号和 s t m t - s e q u e n c e的s t m t。

E B N F中的可选结构可通过前后用方括号 [. . .] 表示出来。这同将问号放在可选部分之后的 正则表达式惯例本质上是一样的,但它另有无需括号就可将可选部分围起来的优点。例如,用 带有可选的e l s e部分的i f语句(例3 . 4和例3 . 6)的文法规则在E B N F中写作:

statement → if-stmt | o t h e r

statement → if-stmt | o t h e r

在文檔中 第1章 概 论 (頁 83-93)

相關文件