编译原理--语法制导的翻译

前言

把一些属性附加到代表语言构造的文法符号上,
以把信息和一个语言的构造联系起来.
通过语义规则来说明文法符号的属性值如何确定.
产生式						语义规则
E -> E_{1} + T				E.code = E_{1}.code || T.code || '+'
语法制导的翻译方案在产生式体中嵌入了称为语义动作的程序片段.
一个语义动作在产生式体中的位置决定了这个动作的执行顺序.
一般,语义动作可出现在产生式体中的任何位置.
E -> E_{
    
    1} + T {
    
     print '+' }
语法制导定义,更适合作为对翻译的规约.
翻译方案,更适合用于翻译的实现.

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

L属性翻译:从左到右的语法制导翻译方案.
S属性翻译方案:综合

语法制导定义

SDD--语法制导定义是一个上下文无关文法和属性及规则的结合.
属性和文法符号相关联,规则和产生式相关联.

继承属性和综合属性

1.综合属性
在分析树结点N上的非终结符号A的综合属性是由N上的产生式所关联的语义规则来定义的.
这个产生式的头是A.
结点N上的综合属性只能通过N的子结点或N本身的属性值来定义.
2.继承属性
分析树结点N上的非终结符号B的继承属性是
由N的父结点的产生式所关联的语义规则来定义的.
注意,这个产生式的体中必然包含符号B.
结点N上的继承属性只能通过N的父结点,N本身,N的兄弟结点上的属性值来定义.

终结符号可有综合属性,不能有继承属性.
终结符号的属性值是由词法分析器提供的词法值.
SDD中不含计算终结符号的属性值的语义规则.

一个例子

对一个以n作为结尾标记的表达式求值.
例子中,每个非终结符号有唯一的被称为val的综合属性,
同时假设终结符号digit有一个综合属性lexval,它是由词法分析器返回的整数值.
产生式				语义规则
L->E n				L.val = E.val
E->E_{
    
    1} + T		E.val = E_{
    
    1}.val + T.val
E->T				E.val = T.val
T->T_{
    
    1} * F		T.val = T_{
    
    1}.val * F.val
T->F				T.val = F.val
F->(E)				F.val = E.val
F->digit			F.val = digit.lexval
一个只包含综合属性的SDD称为S属性的SDD.
在一个S属性的SDD中,
每个规则都根据相应产生式的产生式体中的属性值来计算产生式头部非终结符号的一个属性.

实践中,
允许SDD具有一些副作用会带来一些方便.
一个没有副作用的SDD有时也称为属性文法,
一个属性文法的规则仅通过其他属性值和常量值来定义一个属性值.

在语法分析树的结点上对SDD求值

在语法分析树上进行求值有助于将SDD所描述的翻译方案可视化.
可以想象一下在应用一个SDD规则前先构造一棵语法分析树,
再用这些规则对这棵语法分析树上的各个结点上的所有属性进行求值.
一个显示了它的各个属性的值的语法分析树称为注释语法分析树.

对一棵语法分析树的某个结点的一个属性进行求值之前,
需先求出这个属性值所依赖的所有属性值.

对综合属性,可按照自底向上的顺序计算它们的值.
对同时具有继承属性和综合属性的SDD,一个例子
产生式			语义规则
A->B			A.s=B.i;
				B.i=A.s+1;
上述规则下,不可能先求出A.s或B.i,再求出另一个值.
一个例子

请添加图片描述

另一个例子
产生式				语义规则
T->FT'				T'.inh = F.val
					T.val = T'.syn
T'->*FT_{1}'		T_{
    
    1}'.inh = T'.inh * F.val
					T'.syn = T_{1}'.syn
T'->ε				T'.syn = T'.inh
F->digit			F.val = digit.lexval

SDD的求值顺序

依赖图是一个工具,可以确定一棵给定的语法分析树中各个属性实例的求值顺序.

依赖图

属性求值顺序
拓扑排序

S属性的定义

第一种SDD类型的定义如下:
1.如果一个SDD的每个属性都是综合属性,它就是S属性的.
此类SDD按后序遍历可为各个属性求值

L属性的定义

这类SDD是在一个产生式体所关联的各个属性之间,
依赖关系箭头总是从左到右.
更准确地讲,每个属性必须要么是
1.一个综合属性,要么是
2.一个继承属性,但是它的规则具有如下限制.
设存在一个产生式A->X_{1}...X_{n},
且有一个通过这个产生式所关联的规则计算得到的继承属性X_{i}.a,
则这个规则只能使用:
a.和产生式头A关联的继承属性
b.位于X_{i}左边的X_{j}的继承属性或综合属性
c.和X_{i}自身的继承属性或综合属性.但X_{i}全部属性组成的依赖图中不存在环.

符合L属性定义的SDD的各个属性求值可以肯定不会存在环路.

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

语义规则包含一些和属性求值无关的操作,
但这些操作不会影响到属性求值.

实践中,翻译过程会出现一些副作用:
一个桌上计算器可能打印出一个结果
一个代码生成器可能把一个标识符的类型加入到符号表中

我们将按下面的方法之一来控制SDD的副作用
1.支持那些不会对属性求值产生约束的附带副作用.
2.对允许的求值顺序添加约束,
使得以任何允许的顺序求值都会产生相同的翻译结果.

语法制导翻译的应用

本节的主要应用是抽象语法树的构造.
有些编译器使用抽象语法树作为一种中间表示形式,
一种常见的SDD形式将它的输入串转换为一棵树.

为了完成到中间代码的翻译,
编译器接下来可能使用一组规则来编译这棵语法树.
	
考虑两个为表达式构造语法树的SDD.
第一个是一个S属性定义,适合在自底向上语法分析过程使用.
第二个是一个L属性定义,适合在自顶向下语法分析过程使用.

抽象语法树的构造

使用具有适当数量的字段的对象来实现一棵语法树的各个结点.
每个对象将有一个op字段,也就是这个结点的标号.
这些对象将具有如下所述的其他字段:
1.如结点是一个叶子,
则对象将有一个附加的域来存放这个叶子结点的词法值.
构造函数Leaf(op, val)创建一个叶子对象.
也可把结点看作记录,则Leaf会返回一个指向与叶子结点对应的新记录的指针
2.如结点是内部结点,
则它的附加字段的个数和该结点在语法树中的子结点个数相同.
构造函数Node带有两个或多个参数:Node(op, c_{1}, ..., c_{k}),
该函数创建一个对象,
第一个字段的值为op,
其余k个字段的值为c_{1}, ..., c_{k}.

类型的结构

当语法分析树的结构和输入的抽象语法树的结构不同时,
继承属性很有用.

语法制导的翻译方案

SDT--语法制导的翻译方案
是在其产生式体中嵌入了程序片段的一个上下文无关文法.
这些程序片段称为语义动作,可出现在产生式体中的任何地方.
按照惯例,在这些动作两边加上花括号.
如果花括号要作为文法符号出现,则要给它们加上引号.

通常,
SDT在语法分析过程中实现的.
本节中,
主要关注如何使用SDT来实现两类重要的SDD.
1.基本文法可以用LR技术分析,且SDD是S属性的.
2.基本文法可以用LL技术分析,且SDD是L属性的.
将看到,
两种情况下,一个SDD中的语义规则如何被转换成为一个带有语义动作的SDT的.
这些动作将在适当的时候执行.
语法分析过程中,产生式体中的一个动作在它左边的所有文法符号都被匹配后立刻执行.

可在语法分析过程中实现的SDT可按如下方式识别:
将每个内嵌的语义动作替换为一个独有的标记非终结符号.
每个标记非终结符号M只有一个产生式M->ε.
如带有标记非终结符号的文法可使用某个方法进行语法分析,
则这个SDT就可在语法分析过程中实现.

后缀翻译方案

最简单的实现SDD的情况是
文法可用自底向上方法来分析,且该SDD是S属性定义.
此时,
可构造出一个SDT,其中的每个动作都放在产生式的最后.
且按这个产生式将产生式体归约为产生式头时候执行这个动作.
所有动作都在产生式最右端的SDT称为后缀翻译方案.

后缀SDT的语法分析栈实现

后缀SDT可以在LR语法分析的过程中实现,
当归约发生时执行相应的语义动作.
各个文法符号的属性值可放到栈中的某个位置,
使得执行归约的时候可找到它们.

产生式内部带有语义动作的SDT

动作可以放置在产生式体中的任何位置上.
当一个动作左边的所有符号都被处理过后,
该动作立刻执行.

1.如果语法分析过程是自底向上的,
则在X的此次出现位于语法分析栈的栈顶时,
立刻执行动作a.
2.如果语法分析过程是自顶向下的,
则在试图展开Y的本次出现 [如Y是非终结符号]
或 在输入中检测Y [如Y是终结符号] 之前执行语义动作a.

不是所有SDT都可在语法分析过程中实现.
不可在语法分析中实现参考之前某些文法不能用LL,LR进行分析.
一个道理,要么分析中存在无法解决的二义性,要么存在移入-归约冲突[也算二义性一种].

任何SDT都可按下列方法实现:
1.忽略语义动作,对输入进行语法分析,并产生一棵语法分析树.
2.检查每个内部结点N,假设它的产生式是A->α.
将α中的各个动作当作N的附加子结点加入,
使得N的子结点从左到右和α中的符号及动作完全一致.
3.对这棵语法分析树进行前序遍历,
且当访问到一个以某个动作为标号的结点时,立刻执行这个动作.

上述方法给出了,对之前可以进行语法分析的文法,加入了语义动作后,
无法进行语法分析下,另一种构建语法分析树,进行分析的方法.

从SDT中消除左递归

语义动作的顺序是我们所关注的,且语义动作对属性求值无副作用下:
1.当转换文法的时候,将动作当成终结符号处理.

语义动作是计算属性的值.
如果这个SDD是S属性的,
则总是可以通过将计算属性值的动作放在新产生式中的适当位置上来构造出一个SDT.

一个例子:
一个如下的SDT
A -> A_{1}Y {A.a = g(A_{1}.a, Y.y)}
A -> X {A.a = f(X.x)}
消除左递归的等价的SDT为:
A -> X {R.i = f(X.x)} R {A.a = R.s}
R -> Y {R_{1}.i = g(R.i, Y.y)} R_{1} {R.s = R_{1}.s}
R -> ε {R.s = R.i}

等价意味着对于文法的一个句子,两个SDT执行下来,效果[这里是属性求值结果]一致.

L属性定义的SDT

设基础文法将以自顶向下的方式进行语法分析.
对于任何文法,只需将动作附加到一棵语法分析树中,
并对这棵树进行前序遍历时执行这些动作,便可实现下面的技术.
将一个L属性的SDD转换为一个SDT的规则如下:
1.把计算某个非终结符号A的继承属性的动作
插入到产生式体中紧靠在A的本次出现之前的位置上.
如A的多个继承属性相互间存在无环依赖,需拓扑排序下.
2.将计算一个产生式头的综合属性的动作放置在这个产生式体的最右端.
// while语句的SDD
S -> while(C) S_{
    
    1}	L_{
    
    1}=new();
					L_{
    
    2}=new();
					S_{
    
    1}.next=L_{
    
    1};
					C.false=S.next;
					C.true=L_{
    
    2};
					S.code=label || L_{
    
    1} || C.code 
					|| label || L_{
    
    2} || S_{
    
    1}.code
// while语句的SDT
S->while( {
    
    L_{
    
    1}=new();L_{
    
    2}=new();C.false=S.next;C.true=L_{
    
    2};}
	C)    {
    
    S_{
    
    1}.next=L_{
    
    1};}
	S_{
    
    1} {
    
    S.code=label||L_{
    
    1}||C.code||label||L_{
    
    2}||S_{
    
    1}.code;}

实现L属性的SDD

下面的方法通过遍历语法分析树来完成翻译工作
1.建立语法分析树并注释.
对任何非循环定义的SDD都有效.
2.构造语法分析树,加入动作,并按前序顺序执行这些动作.
这个方法可以处理任何L属性定义.

在语法分析过程中进行翻译的方法:
3.使用一个递归下降的语法分析器,
它为每个非终结符号都建立一个函数.
对应于非终结符号A的函数以参数的方式接收A的继承属性,并返回A的综合属性.
4.使用一个递归下降的语法分析器,以边扫描边生成的方式生成代码.
5.与LL语法分析器结合,实现一个SDT.
属性的值存放在语法分析栈中,
各个规则从栈中的已知位置获取需要的属性值.
6.与LR语法分析器结合,实现一个SDT

在递归下降语法分析过程中进行翻译

一个递归下降的语法分析器对每个非终结符号A有一个函数A.
可按如下方法把这个语法分析器扩展为一个翻译器:
1.函数A的参数是非终结符号A的继承属性
2.函数A的返回值是非终结符号A的综合属性的集合
在函数A的函数体中,要进行语法分析并处理属性:
1.决定用哪个产生式来展开A
2.需要读入一个终结符号时,在输入中检查这些符号是否出现.
3.在局部变量中保存所有必要的属性值,
这些值用于计算产生式体中非终结符号的继承属性,或产生式头部的非终结符号的综合属性
4.调对应于被选定产生式体中的非终结符号的函数,
向它们提供正确的参数.
// 用一个递归下降语法分析器实现while语句的翻译
string S(label next)
{
    
    
	string Scode, Ccode;
	label L1, L2;
	if(当前输入 == 词法单元while)
	{
    
    
		读取输入;
		检查'('是下一个输入符号,并读取输入;
		L1 = new();
		L2 = new();
		Ccode = C(next, L2);
		检查')'是下一个输入符号,并读取输入;
		Scode = S(L1);
		return ("label"||L1||Ccode||"label"||L2||Scode);
	}
	else
	{
    
    
		...
	}
}

边扫描边生成代码

通常,比如在代码生成例子中,可通过执行一个SDT中的语义动作,
逐步把各个代码片段添加到一个数组或输出文件.
要保证此技术正确应用,下列要素不可少:
1.存在一个[一个或多个非终结符号的]主属性.
设主属性以字符串为值.
2.主属性是综合属性
3.对主属性求值的规则保证
a.主属性是将相关产生式体中的非终结符号的主属性值连接起来得到的.
连接时也可能包括其他非主属性的元素.
b.各个非终结符号的主属性值在连接运算中出现的顺序和其在产生式体中出现顺序相同.
// while语句的on-the-fly的递归下降代码生成
void S(label next)
{
    
    
	label L1, L2;
	if(当前输入 == 词法单元while)
	{
    
    
		读入输入;
		检查'('是下一个输入符号,并读取输入;
		L1 = new();
		L2 = new();
		print("label", L1);
		C(next, L2);
		检查')'是下一个输入符号,并读取输入;
		print("label", L2);
		S(L1);
	}
	else
	{
    
    
		...
	}
}

// 边扫描边生成while语句的代码的SDT
S-> while(  
{
    
    L1=new();L2=new();C.false=S.next;C.true=L2;print("label",L1);}
C) {
    
    S_{
    
    1}.next=L1;print("label", L2);}
S_{
    
    1}

L属性的SDD和LL语法分析

设一个L属性SDD的基础文法是一个LL文法,
且已经转换为SDT,语义动作被嵌入到各个产生式中.
然后,
可在LL语法分析过程中完成翻译过程,其中的语法分析栈需要扩展,
以存放语义动作和属性求值所需的某些数据项.

除代表终结符号和非终结符号的记录外,
语法分析栈中还将保存动作记录,综合记录.
动作记录表示即将被执行的语义动作,
综合记录保存非终结符号的综合属性值.

用下列两个原则来管理栈中的属性:
1.非终结符号A的继承属性放在表示这个非终结符号的栈记录中.
对这些属性求值的代码常使用紧靠在A的栈记录之上的动作记录来表示.
从L属性的SDD到SDT的转换方法保证了动作记录将紧靠在A的上面.
2.非终结符号A的综合属性放在一个单独的综合记录中,
它在栈中紧靠在A的记录之下.

请添加图片描述

请添加图片描述

请添加图片描述

L属性的SDD的自底向上语法分析

给定一个以LL文法为基础的L属性SDD,
可修改这个文法,
并在LR语法分析过程中计算这个新文法之上的SDD.
1.构造得到SDT
SDT在各个非终结符号之前放置语义动作来计算它的继承属性,
在产生式后端放置一个动作来计算综合属性.
2.对每个内嵌的语义动作,
向这个文法中引入一个标记非终结符号来替换它.
每个这样的位置都有一个不同的标记,
且对任意一个标记M有一个产生式M->ε
3.如标记非终结符号M在某个产生式A->α{a}β中替换了语义动作a,
对a进行修改得到a',将a'关联到M->ε上.这个动作a'
a.将动作a需要的A或α中符号的任何属性作为M的继承属性进行拷贝.
b.按a中的方法计算各个属性,将计算得到的这些属性作为M的综合属性.

猜你喜欢

转载自blog.csdn.net/x13262608581/article/details/123726248
今日推荐