第五章语法制导的翻译.docx

上传人:b****2 文档编号:20255886 上传时间:2023-04-25 格式:DOCX 页数:50 大小:297.05KB
下载 相关 举报
第五章语法制导的翻译.docx_第1页
第1页 / 共50页
第五章语法制导的翻译.docx_第2页
第2页 / 共50页
第五章语法制导的翻译.docx_第3页
第3页 / 共50页
第五章语法制导的翻译.docx_第4页
第4页 / 共50页
第五章语法制导的翻译.docx_第5页
第5页 / 共50页
点击查看更多>>
下载资源
资源描述

第五章语法制导的翻译.docx

《第五章语法制导的翻译.docx》由会员分享,可在线阅读,更多相关《第五章语法制导的翻译.docx(50页珍藏版)》请在冰豆网上搜索。

第五章语法制导的翻译.docx

第五章语法制导的翻译

第五章语法制导的翻译

本章继续展开2.3节的主题:

使用上下文无关文法来引导对语言的翻译。

本章讨论的翻译技术将在第六章中被用于类型检查和中间代码生成。

这些技术也可以用于实现那些完成特殊任务的小型语言;本章包含了一个有关排版的例子。

如2.3.2节讨论的,我们把一些属性附加到代表语言构造的文法符号上,从而把信息和一个语言构造联系起来。

一个语法制导定义通过附加在文法规则上的语义规则来定义属性的值。

比如,一个从中缀表达式到后缀表达式的翻译器可能包含如下的产生式和规则:

产生式语义规则

E→E1+TE.code=E1.code||T.code||‘+’(5.1)

这个产生式有两个非终结符号E和T;E1的下标区分了E在产生式体中的出现和E在产生式头上的出现。

E和T都有一个字符串类型的属性code。

上面的语义规则指明E的代码是通过将E1.code、T.code和字符‘+’连接而得到的。

虽然这个规则明确指出对E的翻译结果是根据E1、T的翻译结果和’+’构造得到的,直接通过字符串操作来实现这个翻译过程可能很低效。

根据2.3.5节,一个语法制导的翻译方案在产生式体中包含了被称为语义动作的程序片段。

比如

E→E+T{print‘+’}(5.2)

按照惯例,语义动作被放在花括号之内。

(对于作为文法符号出现的花括号,我们将用单引号把它们括起来,比如‘{’和‘}’。

)一个语义动作在产生式体中的位置决定了这个动作的执行顺序。

在产生式(5.2)中的语义动作出现在所有文法符号之后的末端;一般情况下,语义动作可以出现在一个产生式体中的任何位置。

对这两种标记方法作一个比较。

语法制导定义更加易读,因此更适合作为对翻译的规约。

而翻译方案更加高效,因此更加适合用于翻译的实现。

最通用的完成语法制导翻译的方法是先构造一个语法分析树,然后通过访问这棵树的结点来计算各个属性的值。

在很多情况下,翻译可以在扫描分析过程中完成,不需要构造出明确的语法分析树。

因此,我们将研究一类被称为“L-属性翻译”(L代表从左到右)的语法制导翻译方案。

这一类方案实际上包含了所有可以在语法分析过程中完成的翻译方案。

我也研究一个较小的类别,称为“S-属性翻译方案”(S代表综合)。

这类方案可以很容易地和自底向上语法分析过程联系起来。

5.1语法制导定义

一个语法制导定义(SDD,syntax-directeddefinition)是一个上下文无关文法和属性及语义规则的结合。

属性和文法符号相联,而规则和产生式相联。

如果X是一个符号而a是X的一个属性,那么我们用X.a来表示a在某个标号为X的分析树结点上的值。

如果我们使用记录或对象来实现这个语法分析树的结点,那么X的属性可以被实现为代表X结点的记录或对象的数据域。

属性可以是多种多样的,比如数字、类型、表格引用或串。

这些串可能是很长的代码序列,比如编译器所使用的中间语言的代码。

5.1.1继承属性和综合属性

我们将处理两种非终结符号的属性:

1、综合属性。

在分析树结点N上的非终结符号A的综合属性是由N上的产生式所关联的语义规则来定义的。

请注意,这个产生式的头一定是A。

结点N上的一个综合属性只能通过N的子结点或N本身的属性值来定义。

2、继承属性。

在分析树结点N上的一个非终结符号B的一个继承属性是由N的父结点上的产生式所关联的语义规则来定义的。

请注意,这个产生式的体中必然包含符号B。

结点N上的继承属性只能通过N的父结点、N本身、和N的兄弟结点上的属性值来定义。

我们不允许结点N上的继承属性通过N的子结点上的属性值来定义,但是我们允许结点N上的一个综合属性通过结点本身的继承属性来定义。

终结符号可以具有综合属性,但是不能有继承属性。

终结符号的属性值是由词法分析器提供的词法值;在SDD中没有计算终结符号的属性值的语义规则。

例子5.1:

图5.1中的SDD是基于我们熟悉的、带有运算符*和+的算术表达式文法。

它对一个以n作为结尾标记的表达式求值。

在这个SDD中,每个非终结符号具有唯一的被称为val的综合属性。

我们同时还假设终结符号digit具有一个综合属性lexval,它是由词法分析器返回的整数值。

产生式1,即L→En,的规则将L.val设置为E.val。

我们将看到,它就是整个表达式的值。

产生式2,即E→E1+T,也有一个规则。

它计算出E1和T的值的和,作为规则头E的val属性的值。

在任何标号为E的结点N上,E的val值是N的两个子结点(标号分别为E和T)上的val值的和。

产生式3,即E→T,有唯一的规则,它定义了E的val值和对应于T的子结点的val值相同。

产生式4和第二个产生式类似;它的规则将子结点的值相乘,而不是相加。

产生式5和6的规则和第三个产生式的规则类似,它们拷贝子结点的值。

产生式7给F.val赋予一个digit的值,即由词法分析器返回的词法单元digit的数值。

一个只包含综合属性的SDD被称为S-属性的SDD;图5.1中的SDD就具有这个性质。

在一个S-属性的SDD中,每个规则都根据相应产生式的产生式体中的属性值来计算产生式头部非终结符号的一个属性。

为简单起见,本节中的语义规则没有副作用。

在实践中,允许SDD具有有限的副作用会带来一些方便。

比如允许打印桌上计算器计算得到的结果,或者和一个符号表进行交互。

在5.2节中讨论了属性的求值顺序之后,我们将允许语义规则计算任意的函数,这些函数可能会有副作用。

一个S-属性的SDD可以和一个LR语法分析器一起自然地实现。

实际上,图5.1中的SDD是图4.58中的yacc程序的另一种表示,该程序演示了在LR语法分析过程中进行翻译的过程。

两者的区别在于,那个yacc程序在产生式1的规则中通过副作用打印了E.val的值,而不是定义属性L.val。

一个没有副作用的SDD有时也被称为属性文法。

一个属性文法的规则仅仅通过其它属性值和常量来定义一个属性。

5.1.2在一棵语法分析树的结点上对一个SDD求值

在一棵语法分析树上进行求值有助于将一个SDD所描述的翻译方案可视化,虽然一个翻译器实际上不需要构建出一棵语法分析树。

因此,我们想象一下在应用一个SDD的规则时首先构造出一棵语法分析树,然后再使用这些规则去对这棵语法分析树上的各个结点上的所有属性进行求值。

一个显示了它的各个属性的值的语法分析树被称为标注分析树。

我们如何去构造一棵标注分析树呢?

我们按照什么顺序来计算各个属性?

在我们对一棵语法分析树的某个结点的一个属性进行求值之前,我们首先必须求出这个属性值所依赖的所有属性值。

比如,如果象例子5.1中那样所有的属性都是综合属性,那么在我们可以对一个结点上的属性求值之前,我们必须求出该结点的所有子结点上的属性val的值。

对于综合属性,我们可以按照任何自底向上的顺序计算它们的值,比如对这棵语法分析树进行后跟遍历的顺序;对于S-属性定义的求值将在5.2.3节中讨论。

对于同时具有继承属性和综合属性的SDD,不能保证一定有一个顺序来对各结点上的属性进行求值。

比如考虑非终结符号A和B,它们分别具有综合属性A.s和继承属性B.i。

同时它们的产生式和规则如下:

产生式语义规则

A→BA.s=B.i;

B.i=A.s+1

这些规则是循环定义的;不可能首先求出结点N上的A.s或N的子结点上的B.i中的某一个的值,然后再求出另一个的值。

一棵语法分析树的某个结点对偶上的A.s和B.i之间的循环依赖关系如图5.2所示。

从计算量的角度看,给定一个SDD,很难确定是否存在某棵语法分析树使得SDD的属性值之间具有循环依赖关系。

幸运的是存在一个SDD的有用子集,它们能够保证对每棵分析树都存在一个求值顺序。

我们将在5.2节中看到这类SDD。

例子5.2:

图5.3显示了一个对应于输入串3*5+4n的标注分析树,该分析树通过图5.1的文法和规则构造得到。

我们假定lexval的值由词法分析器提供。

对应于非终结符号的每个结点都有一个自底向上计算得到的val属性。

在图中我们可以看到每个结点都关联了一个结果值。

比如,在图中结点*的父结点上,在计算得到它的第一和第三个子结点上的T.val=3和F.val=5之后,我们应用了相应的规则,指明T.val就是这两个值的乘积,即15。

当一棵语法分析树的结构和源代码的抽象语法不“匹配”时,继承属性是很有用的。

因为文法不是为了翻译而定义的,而是以语法分析为目的进行定义的,因此可能会产生这种不匹配的情况。

下面的例子显示了如何使用继承属性来解决这个问题。

例子5.3:

图5.4中的SDD计算了诸如3*5和3*5*7的项。

处理输入3*5的自顶向下语法分析过程首先使用了产生式T→FT’。

这里F生成了数值3,但是运算符*由F’生成。

因此,左运算分量3和运算符*位于不同的子树中。

因此我们将使用一个继承属性来把这个运算分量传递给这个运算符。

这个例子中的文法摘录自常见的表达式文法的无左递归版本;我们在4.4节中使用这个文法作为演示自顶向下语法分析的例子。

非终结符号T和F各自有一个综合属性val;终结符号digit有一个综合属性lexval。

非终结符号T’具有两个属性:

一个继承属性inh和一个综合属性syn。

这些语义规则是基于如下思想:

运算符*的左运算分量是通过继承得到的。

更精确地说,产生式T’→T*F’1的头T’继承了产生式体中*的左运算分量。

给定一个项x*y*z,对应于*y*z的子树的根结点继承了x的值。

然后对应于*z的子树的根结点继承了x*y的值。

如果项中还有更多的因子,我们可以继续这样的处理过程。

当所有的因子都处理完毕后,这个结果就通过综合属性向上传递到树的根部。

为了了解如何使用这些语义规则,考虑图5.5中对应于3*5的标注分析树。

这棵语法分析树中最左边的、标号为digit的叶子结点具有属性值lexval=3,其中的3是由词法分析程序提供的。

它的父结点对应于产生式4:

F→digit。

和这个产生式相关的唯一语义规则定义F.val=l,等于3。

在根结点的第二个子结点上,继承属性T’.inh根据和产生式1关联的语义规则T’.inh=F.val定义。

因此,运算符*的左运算分量3从根结点的左子结点传递到右子结点。

对应于T’的结点的产生式是T’→*FT’1。

(我们保留了标注分析树中的下标1,以区别树中的两个T’结点。

)继承属性T’1.inh是由语义规则T’1.inh=T’.inh×F.val定义的,这个规则和产生式2相关联。

已知T’.inh=3和F.val=5,我们得到T’1.inh=15。

在较低的T’结点上的产生式是T’→ε。

相应的语义规则T’.syn=T’.inh定义了T’1.syn=15。

各个T’结点上的属性syn将值15沿着树向上传递到T结点,使得T.val=15。

5.1.35.1节的练习

练习5.1.1:

对于图5.1中的SDD,给出下列表达式的标注分析树:

a)(3+4)*(5+6)n。

b)1*2*3*(4+5)n。

c)(9+8*(7+6)+5)*4n。

练习5.1.2:

扩展图5.4中的SDD,使它可以象图5.1中那样处理表达式。

练习5.1.3:

使用你在练习5.1.2中得到的SDD,重复练习5.1.1。

5.2SDD的求值顺序

“依赖图”是一个有用的SDD求值工具,它可以确定一棵给定的语法分析树中各个属性实例的求值顺序。

一棵标注分析树显示了各个属性的值,而一个依赖图可以帮助我们确定如何计算这些值。

在本节中,除了依赖图我们还定义了两个重要的SDD类型:

“S-属性”SDD和更加通用的“L-属性”SDD。

使用这两类SDD描述的翻译方案可以和我们已经研究过的语法分析方法很好地结合在一起。

并且在实践中遇到的大部分翻译方案可以按照这两类SDD中的至少一类的要求写出来。

5.2.1依赖图

一个依赖图表示了一个特定语法分析树中的属性实例之间的信息流;从一个属性实例到另一个实例的边表示计算第二个属性实例时需要第一个的值。

图中的边表示了语义规则所蕴含的约束。

更详细地说:

●对于每个语法分析树的结点,比如一个标号为文法符号X的结点,和X关联的每个属性都在依赖图中有一个结点。

●假设和产生式p关联的一个语义规则通过X.c的值定义了综合属性A.b的值(这个规则定义A.b时可能还使用了X.c之外的其它属性)。

那么,相应的依赖图中有一条从X.c到A.b的边。

更精确地讲,在每个标号为A且应用了产生式p的结点N上,创建一条从该产生式体中的符号X所对应的N的子结点上的属性c到N上的属性b的边。

●假设和产生式p关联的一个语义规则通过X.a的值定义了继承属性B.c的值。

那么,在相应的依赖图中有一条从X.a到B.c的边。

对于每个标号为B、对应于产生式p中的这个B的结点N,创建一条从结点M上的属性a到N上的属性c的边。

这里的M对应于这个X。

请注意,M可以是N的父结点或者兄弟结点。

例子5.4:

考虑下面的产生式和规则:

产生式语义规则

E→E1+TE.val=E1.val+T.val

在每个标号为E,且其子结点对应于这个产生式体的结点N上,N上的综合属性val使用两个标子结点(标号分别为E和T)上的val值计算得到。

因此,对于每个使用了这个产生式的语法分析树,该树的依赖图中有一部分如图5.6所示。

作为惯例,我们将把语法分析树的边显示为虚线,而依赖图的边显示为实线。

例子5.5:

一个完整的依赖图的例子显示在图5.7中。

这个依赖图的结点用数字1到9表示,对应于图5.5中的标注分析树中的各个属性。

图5.7:

对应于图5.5中的标注分析树的依赖图

结点1和2表示了和其标号为digit的两个叶子结点相关联的属性lexval。

结点3和4表示和其标号为F的两个结点相关联的属性val。

从结点1到结点3的边,以及从结点2到结点4的边是根据那个通过l定义F.val的语义规则得到的。

实际上,F.val等于l,但依赖图中的边表示的是依赖关系,而不是等于关系。

结点5和6表示了和非终结符号T’的各次出现相关联的继承属性T’.inh。

从结点3到结点5的边是根据规则T’.inh=F.val得到的,这个规则根据根的左子结点上的F.val定义了右子结点上的T’.inh。

我们看到了从结点5到结点6的、代表T’.inh的边和从结点4到结点5的、代表F.val的边,因为这两个值相乘后得到了结点6上的属性inh的值。

结点7和8表示了和T的各次出现相关联的综合属性syn。

从结点6到7的边是根据图5.4中的产生式3所关联的规则T’.syn=T’.inh而得到的。

从结点7到结点8的边是根据产生式2所关联的语义规则得到的。

最后,结点9表示了属性T.val。

从结点8到9的边是根据产生式1所关联的语义规则T.val=T’.syn而得到的。

5.2.2属性求值的顺序

依赖图刻画了我们对一个语法分析树中不同结点上的属性求值时可能采取的顺序。

如果依赖图中有一条从结点M到结点N的边,那么M对应的属性必须先于N对应的属性进行求值。

因此,所有的可行求值顺序就是满足下列条件的结点顺序N1、N2、…、Nk:

如果有一条从结点Ni到Nj的依赖图的边,那么i

这样的排序将一个有向图变成了一个线性排序,被称为这个图的拓扑排序。

如果这个图中存在任意一个环,那么就不存在拓扑排序;就是说,没有办法在这个语法分析树上对相应的SDD求值。

然而,如果图中没有环,那么总是至少存在一个拓扑排序。

下面说明一下为什么存在拓扑排序。

因为没有环,我们一定能够找到一个没有边进入的结点。

因为假设没有这样的结点,那么我们就可以不断地从一个前驱结点到达另一个前驱,直到我们回到某个已经访问过的结点,从而形成了一个环。

令这个没有进入边的结点作为拓扑排序的第一个结点,从依赖图中删除这个点,并对其余的结点重复上面的过程。

(最终就可以得到一个拓扑排序,译者注)

例子5.6:

图5.7中的依赖图没有环。

它的拓扑排序之一是这些结点的编码的顺序:

1、2、…、9。

请注意这个图的每条边都是从编号较高的结点到达编号较低的结点,因此这个排序一定是一个拓扑排序。

还有其它的拓扑排序,比如1、3、5、2、4、6、7、8、9。

5.2.3:

S-属性定义

如前面提到的,给定一个SDD,很难判定是否存在一棵语法分析树使得其依赖图包含环。

在实践中,翻译过程可以使用某些特定类型的SDD来实现。

这些类型的SDD一定有一个求值顺序,因为它们不允许带有环的依赖图。

不仅如此,在这一节中介绍的两类SDD可以和自顶向下及自底向上的语法分析过程一起高效地实现。

第一种SDD类型定义如下:

●如果一个SDD的每个属性都是综合属性,它就是S-属性定义。

例子5.7:

图5.1中的SDD是一个S-属性定义的例子。

每个属性,L.val、E.val和F.val,都是综合属性。

如果一个SDD是S-属性的,我们可以按照语法分析树结点的任何自底向上顺序来计算它的各个属性值。

对语法分析树进行后根遍历并对属性求值常常会非常简单,一个结点N上的值可以在遍历中最后一次离开N时进行计算。

就是说,我们可以把下面定义的函数postorder作用到语法分析树上(另见2.3.4节中的方框“先根遍历和后根遍历”):

postorder(N){

for(从左边开始,对N的每个子结点C)postorder(C);

对N关联的各个属性求值;

}

S-属性定义可以在自底向上语法分析的过程中实现,因为一个自底向上的语法分析过程就对应于一次后根遍历。

特别地,后根次序精确地对应于一个LR分析器将一个产生式体归约成为它的头的过程。

这个性质将在5.4.2节中被用于LR语法分析过程中的综合属性求值,这些值将被存放在分析栈中。

这个过程不会显式地生成语法分析树的结点。

5.2.4L-属性定义

第二种SDD被称为L-属性定义。

这类SDD背后的思想是使得在一个规则体所关联的各个属性之间,依赖图的边总是从左到右,而不能从右到左(因此称为L-属性的)。

更明确地讲,每个属性必须要么是

●一个综合属性,要么是

●一个继承属性,但是它的规则具有如下限制。

假设存在一个产生式A→X1X2…Xn,并且有一个继承属性Xi.a通过这个产生式所关联的一个规则进行计算。

那么这个规则只能使用:

(a)和产生式头A关联的继承属性。

(b)位于Xi的左边的文法符号实例X1、X2、…、Xi-1所关联的继承属性或者综合属性。

(c)和这个Xi的实例本身相关联的继承属性或综合属性,但是在由这个Xi的属性组成的依赖图中不存在环。

例子5.8:

图5.4中的SDD是L-属性的。

要知道为什么,考虑对应于继承属性的语义规则。

为方便起见,我们在这里复述一下这些规则:

产生式语义规则

T→FT’T’.inh=F.val

T’→*FT1’T1’.inh=T’.inh×F.val

其中的第一个规则定义继承属性T’.inh时只使用了F.val,且F在相应产生式体中出现在T’的左部,因此满足L-属性的要求。

第二个规则定义T1’.inh时使用了和产生式头相关联的继承属性T’.inh及F.val,其中F在这个规则体中出现在T1’的左边。

在每一种情况中,这些规则使用的信息“来自于上边或左边”,因此满足这一类SDD的要求。

其余的属性是综合属性,因此这个SDD是L-属性的。

例子5.9:

任何包含了如下产生式和规则的SDD都不是L-属性的:

产生式语义规则

A→BCA.s=B.b;

B.i=f(C.c,A.s)

第一个规则A.s=B.b在S-属性SDD或L-属性SDD中都是一个合法的规则。

它通过一个子结点(也就是说产生式体中的一个符号)的属性定义了综合属性A.s。

第二个规则定义了一个继承属性B.i,因此整个SDD不可能是S-属性的。

不仅如此,虽然这个规则是合法的,这个SDD也不可能是L-属性的,因为属性C.c被用来定义B.i,并且C在这个产生式体中位于B的右边。

虽然在L-属性的SDD中可以使用语法分析树中的兄弟结点的属性,这些结点必须位于被定义属性的符号的左边。

5.2.5具有受控副作用的语义规则

在实践中,翻译过程会涉及到副作用:

一个桌上计算器可能打印出一个结果;一个代码生成器可能把一个标识符的类型加入到符号表中。

对于SDD,我们在属性文法和翻译方案之间找到了一个平衡点。

属性文法没有副作用,并允许任何与依赖图一致的求值顺序。

翻译方案要求从左到右的求值,并允许语义动作包含任何程序片段;翻译方案在5.4节中讨论。

我们将按照下面的方法之一来控制SDD中的副作用:

●允许那些不会对属性求值产生约束的附带副作用。

换句话说,如果按照依赖图的任何拓扑顺序进行属性求值时都可以产生“正确的”翻译结果,我们就允许副作用的存在。

这里的“正确”要视具体应用而定。

●对允许的求值顺序添加额外的约束,使得对于任何允许的顺序都会产生相同的翻译结果。

这些约束可以被看作隐含加入到依赖图中的边。

作为附带副作用的一个例子,让我们修改例子5.1的桌面计算器,使它打印出答案。

我们不使用规则L.val=E.val,这个规则将结果保存到综合属性L.val中。

我们考虑:

产生式语义规则

1)L→Enprint(E.val)

象printf(E.val)这样的语义规则的目的就是执行它们的副作用。

它们将会被看作关联于相应产生式头上的哑综合属性的定义。

这个经过修改的SDD在任何拓扑顺序下都产生相同的值,因为这个打印语句在结果被计算到E.val中之后才会被执行。

例子5.10:

图5.8中的SDD处理了简单的声明D。

该声明中包含一个基本类型T,然后是一个标识符的列表L。

T可以是int或float。

对于列表中的每个标识符,这个类型被登录到这个标识符的符号表条目中。

我们假设登录一个标识符的类型不会影响其它符号表的条目。

这样,这些条目可以按照任何顺序进行修改。

这个SDD没有检查是否一个标识符被声明了多次;我们也可以修改这个SDD使它这样做。

非终结符号D表示了一个声明。

根据产生式1可知,这个声明包含了一个类型T,然后是一个标识符的列表。

T有一个属性T.type,它是声明D中的类型。

非终结符号L也有一个属性,我们将它称为inh,以强调它是一个继承属性。

L.inh的目的是将声明的类型沿着标识符列表向下传递,使得它可以被加入到正确的符号表条目中。

产生式2和3都计算了综合属性T.type,赋予它正确的值:

integer或float。

这个类型值在产生式1的规则中被传递给属性L.inh。

产生式4将L.inh沿着语法分析树向下传递。

就是说,在一个分析树结点上,值L1.inh是通过拷贝该结点的父结点的L.inh值而得到的;这个父结点对应于此产生式的头。

产生式4和5还包含另一个规则。

该规则中用如下两个参数调用了一个函数addType:

●id.entry,在词法分析过程中得到的、一个指向某个符号表条目的值,以及

●L.inh,被赋给列表中各个标识符的类型值。

我们假设函数addType正确地将id所代表的标识符的类型设置为L.inh值。

输入串floatid1,id2,id3的依赖图显示在图5.9中。

数字1到10表示了这个依赖图中的结点。

结点1、2和3表示了和各个标号为id的叶子结点相关联的属性entry。

结点6,8和10时表示函数addType的应用于一个类型和这些entry值之一的哑属性。

结点4表示属性T.type,它实际上是属性求值过程开始的地方。

然后这个类型被传递到结点5、7和9。

这些结点表示和非终结符号L的各次出现相关联的L.inh。

5.2.65.2节的练习

练习5.2.1:

图5.7中的依赖图的全部拓扑排序有哪些?

练习5.2.2:

对于

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 医药卫生 > 基础医学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1