740 likes | 878 Views
第六章 属性文法和语法制导翻译. 主要内容 6.1 属性文法 6.2 基于属性文法的处理方法 6.3 S- 属性文法的自下而上计算 6.4 L- 属性文法和自顶向下翻译 6.5 自下而上计算继承属性. 概述. 语义分析的任务 在词法分析和语法分析的基础上,分析所写源程序的含义,在理解含义的基础上为生成相应的目标代码作好准备或直接生成目标代码。包括语义检查和语义处理。 语义检查 例:类型、运算、维数、越界 语义处理 例:变量的存储分配 例:表达式的求值 例:语句的翻译(中间代码的生成). 6.1 属性文法. 语义分析的描述
E N D
第六章 属性文法和语法制导翻译 主要内容 • 6.1 属性文法 • 6.2 基于属性文法的处理方法 • 6.3S-属性文法的自下而上计算 • 6.4 L-属性文法和自顶向下翻译 • 6.5 自下而上计算继承属性
概述 • 语义分析的任务 • 在词法分析和语法分析的基础上,分析所写源程序的含义,在理解含义的基础上为生成相应的目标代码作好准备或直接生成目标代码。包括语义检查和语义处理。 • 语义检查 • 例:类型、运算、维数、越界 • 语义处理 • 例:变量的存储分配 • 例:表达式的求值 • 例:语句的翻译(中间代码的生成)
6.1 属性文法 • 语义分析的描述 • 描述语法规则的同时,编写相应的语义动作和计算顺序 • 语义的形式化描述 • 操作语义学、公理语义学、指称语义学 • 属性文法 • 接近形式化的语义描述方法
属性文法的定义 • 三元组:A=(G,V,F) • G 是上下文无关文法 • V 属性的有穷集 • F 关于属性的断言和谓词 • 关系 • 语义信息作为终结符和非终结符的属性 • 语义分析定义为产生式的断言和谓词 • 语义规则:文法的每个产生式所对应的一组属性的计算规则。
用法 • 针对语义,为文法符号设置属性 • 终结符使用单词的属性 • 为每个产生式设置语义规则 • 描述各属性的关系 • 两种形式 • 语法制导定义:语义的抽象说明 • 翻译方案:规定实现方法(计算次序)
属性文法的作用 • 抽象描述语义分析的要求 • 语义信息及其计算关系 • 适用于各种语义分析(翻译、计算、处理) • 语义分析的任务 • 属性求值计算、断言检查
属性分类: • 综合属性 • 从其子结点的属性值计算出来的; • 用于“自下而上”传递信息; • 如:E.val、T.type • 继承属性 • 从其兄弟结点和父结点的属性值计算出来的; • 用于“自上而下”传递信息; • 如:L.in • 固有属性(单词属性)
在一个属性文法中,对应于每个产生式A→ a都有一套与之相关联的语义规则,每条规则的形式为b:=f(c1,c2,…,ck)这里,f是一个函数,而且或者 • 1)b是A的一个综合属性并且c1,c2,…,ck是产生式右边文法符号的属性;或者 • 2)b是产生式右边某个文法符号的一个继承属性并且c1,c2,…,ck是A或产生式右边任何文法符号的属性。 • 在这两种情况下,我们都说属性b依赖于属性c1,c2,…,ck。 • 特别要强调的是: • 1)终结符只有综合属性,他们由词法分析器提供; • 2)非终结符既可有综合属性也可有继承属性,文法开始符号的所有继承属性作为属性计算前的初始值。
属性的计算 • 构造语法分析树,添加响应的语义规则 • 综合属性 • 自底向上按照语义规则来计算各结点的综合属性值 • 继承属性 • 需要探讨计算次序
S-属性定义: • 仅包括综合属性 • 对于所有A → X1X2…Xn, • A的属性计算仅用X1…Xn的属性 • 如:算术表达式求值的属性文法
L-属性定义: • 其属性可用深度优先的顺序从左至右计算 • 对于所有 A→X1X2…Xn • Xi属性计算仅使用A X1X2…Xi-1的属性 • 如:说明语句的属性文法
综合属性 • 综合属性在实际中被广泛应用。在语法树中,一个结点的综合属性值由其子结点的属性值确定。因此,通常使用自底向上的方法在每一个结点处使用语义规则计算综合属性的值。仅仅使用综合属性的属性文法称S-属性文法。 • 下面的例子6.1就是用来说明综合属性的使用和计算过程。
例6-1: 计算器的设计 • 该例中的计算器读入一个可含数字、括号和+、*运算符的算术表达式,并打印表达式的值,每个输入行以n作为结束。 • 步骤如下: • 编制算术表达式的文法 • 引入属性表示语义信息 • 将值 val 作为表达式 E、项 T 和因子 F 的属性 • 用语义规则描述表达式的求值
属性文法(语法制导定义) • 例6.1 一个简单台式计算器的属性文法 • 产生式 语义规则 • L → E print( E.val ) • E → E1 + T E.val := E1.val + T.val • E → T E.val := T.val • T → T1 * F T.val := T1.val * F.val • T → F T.val := F.val • F → ( E ) F.val := E.val • F → digitF.val := digit.attr • attr是单词digit 的一个综合属性
继承属性 • 在语法树中,一个结点的继承属性由此结点的父结点和/或兄弟结点的某些属性确定。例如,可以利用一个继承属性来跟踪一个标识符,看它是出现在赋值号的左边还是右边,以确定是需要这个标识符的地址还是值。 • 方法 • 编写说明语句的文法 • 将类型信息作为类型描述 T 的属性 type 和变量表 L 的属性in。 • 目的 • 分析说明语句 D,为变量指定类型
例6.2 • 在该例子中,继承属性在说明中为各种标识符提供类型信息。 • 在表6.2中给出的属性文法中,由非终结符D所产生的说明含关键字int和real,后跟一个标识符表。非终结符T有一个综合属性type,它的值由说明中的关键字确定。与产生式D → T L相应的语义规则L.in:=T.type把说明中的类型赋值给继承属性L.in。然后,利用语义规则把继承属性L.in沿着语法树往下传。与L的产生式相应的语义规则调用过程addtype把每个标识符的类型填入符号表的相应项中(符号表入口由属性entry指明)。
表6.2 带继承属性L.in的属性文法 • 产生式 语义规则 • D → T L L.in := T.type • T → int T.type := integer • T → real T.type := real • L → L1,id L1.in := L.in • addtype( id.entry, L.in ) • L → id addtype( id.entry, L.in ) • entry 单词id 的属性 • addtype 在符号表中为变量添加类型信息
Type标识类型,综合属性 In:起到类型传递作用,继承属性 例6.2:real id1,id2,id3 的分析树和属性计算 addtype addtype addtype
例: • 利用下面的数字串的文法计算一个输入串的值。N→N1D N→D D→0 D→1 D→9解:写出的属性文法如下:
6.2基于属性文法的处理方法 • 处理过程: • 对单词符号串进行语法分析,构造语法分析树,然后根据需要遍历语法树并在语法树的各结点处按语义规则进行计算。 • 输入串→语法树→依赖图→语义规则计算次序 • 这种由源程序的语法结构所驱动的处理办法就是语法制导翻译法。语义规则的计算可能产生代码、在符号表中存放信息、给出错误信息或执行任何其它动作。对输入符号串的翻译也就是根据语义规则进行计算的结果。 • 然而,一个具体的实现并不一定按照上面的步骤。在某些情况下可用一遍扫描实现属性文法的语义规则计算。也就是说在语法分析的同时完成语义规则的计算,无须明显地构造语法树或构造属性之间的依赖图。
6.2.1 依赖图 • 依赖图:将一棵语法树中的结点的继承属性和综合属性之间的相互依赖关系用一个有向图来描述,则 称该图为依赖图。 • 如果在一棵语法树中一个结点的属性b依赖于属性c,那么这个结点处计算b的语义规则必须在确定c的语义规则之后使用。 • 在为一棵语法树构造依赖图之前,可以为每一个包含过程调用的语义规则引入一个虚综合属性b,这样每一个寓意规则都写成b:=f(c1,c2,…,ck)的形式。依赖图中为每一个属性设置一个结点,如果属性b依赖于属性c,则从属性c的结点有一条有向边连到属性b的结点。
依赖图的构造方法 for 分析树中每一结点n do for 结点n的文法符号的每一个属性a do 为a在依赖图中建立一个结点; for 分析树中每一个结点n do for 结点n所用产生式对应的每一条 语义规则 b:=f(c1,c2,…,ck ) do for i:=1 to k do 从结点ci到结点b构造一条有向边;
例如: • 假设 A.a:=f(X.x, Y.y)是对应于产生式A →XY的一个语义规则,这条语义规则确定了依赖于属性X.x和Y.y的综合属性A.a。如果在语法树中应用这个产生式,那么在依赖图中会有三个结点A.a,X.x,Y.y。由于A.a依赖于X.x,所以有一条有向边从X.x连到A.a。由于A.a也依赖于Y.y,所以还有一条有向边从Y.y连到A.a。 • 如果与产生式A →XY对应的语义规则还有X.i:=g(A.a, Y.y)那么,图中还应有两条有向边,一条从A.a连到X.i,另一条从Y.y连到X.i,因为X.i依赖于A.a和Y.y。
Val . E . val E1 . val + E2 例6.3 • 当下面的产生式应用于语法树中时,就可以像图6.4所示的那样把有向边加入到依赖图中。 • 产生式 语义规则 • E →E1+E2 E.val:=E1.val+E2.val • 依赖图中用.来标志的三个结点分别代表语法树中相应结点的综合属性E.val,E1.val和E2.val。从E1.val到E.val的有向边表明E.val依赖于E1.val,同样,E2.val到E.val的有向边表明E.val也依赖于E2.val。图6.4中的虚线表示的是语法树,它不是依赖图中的一部分。 图6.4
D T 4 type in 5 L 6 real , in 7 id3 3 entry L 8 in 9 L 10 , id2 2 entry 图6. 5 id1 1 entry 例6.4 图6.5表示的是图6.2的依赖图。依赖图中的结点由数字来标识,这些数字将在下面用到。从代表T.type的结点4有一条有向边连到代表L.in的结点5,因为根据产生式D →TL的语义规则L.in:=T.type,可知L.in依赖于T.type。根据L→L1,id的语义规则可知,有两条向下的有向边分别进入结点7和9。每一个与L产生式有关的语义规则addtype(id.entry,L.in)都产生一个虚属性,结点6,8和10都是为这些虚属性构造的。
良定义 • 很显然,一条求值规则只有在其各变元值均已求得的情况下才可以使用。但有时候可能会出现一个属性对另一个属性的循环依赖关系。例如,P、C1、C2都是属性,若有如下求值规则p:=f1(c1)、c1:=f2(c2) 、c2:=f3(p)时,就无法对P求值。 • 如果一属性文法不存在属性之间的循环依赖关系,那么称该文法为良定义的。为了设计编译程序,我们只处理良定义的属性文法。
属性的计算次序 ◆拓扑排序 一个有向非循环图的拓扑排序是图中结点的任何顺序m1,m2,…,mk,使得边必须是从序列中前面的结点指向后面的结点,也就是说,如果mi→mj是mi到mj的一条边,那么在 序列中mi必须出现在mj的前面。 若依赖图中无环,则存在一个拓扑排序,它就是属性值的计算顺序。 一个依赖图的任何拓扑排序都给出一个语法树中结点的语义规则计算的有效顺序。这就是说,在拓扑排序中,在一个结点上,语义规则b:=f(c1,c2,…,ck)中的属性c1,c2,…,ck在计算b以前都是可用的。
例6.5 图6. 5的拓扑排序是: 1, 2, 3,4,5,6,7,8,9,10 a4:=real; a5:=a4; addtype(id3entry,a5); a7:=a5; addtype(id2entry,a7); a9:=a7; addtype(id1entry,a9);
6.2.2树遍历的属性计算方法 • 通过树遍历计算属性值的方法有多种。这些方法都假设语法树已经建立起了,并且树中已带有开始符号的继承属性和终结符的综合属性。然后以某种次序遍历语法树,直至计算出所有属性。最常用的方法是深度优先。 • While 还有未被计算的属性 do • visitNode(S) /*S是开始符号 */ • Procedure VisitNode(N:Node); • Begin • if N是一个非终结符 then • /*假设它的产生式为N →x1…xm */ • for I:=1 to m do • if not xi∈ Vt then • begin • 计算Xi的所有能够计算的继承属性; • VisitNode(Xi) • end; • 计算N的所有能够计算的综合属性 • End
例6.6 • 考虑表6.3所给出的属性的文法G。其中,S有继承属性a,综合属性b;X有继承属性c、综合属性d;Y有继承属性e、综合属性f;Z有继承属性h、综合属性g。 • 表6.3语义规则中有较复杂的依赖关系
S:a=0 S:a=0 X Y Z X Y Z:h=0 g=1 x y z x y z (a) (b) S:a=0,b=0 S:a=0,b=0 X:c=1 d=2 Y Z:h=0 g=1 X:c=1 d=2 Y:e=0 f=0 Z:h=0 g=1 x y z x y z (c) (d) • 假设S.a的初始值为0,则输入串xyz的语法树如图6.6(a)所示。 图6.6 对文法G的属性计算步骤 (a)初始状态;(b)对VisitNode(s)的第一次调用后; (c) 对visitNode(s)第二次调用后; (d) 最终状态
第一次遍历的执行过程如下: • VisitNode(s) • X.c不能计算 • VisitNode(X) • X.d不能计算 • Y.e不能计算 • VisitNode(Y) • Y.f不能计算 • Z.h:=0 • VisitNode(Z) • Z.g:=1 • S.b不能计算 • 第一遍以后,树的状态如图6.6(b)所示。第二次调用VisitNode(S) 导致对X.c、X.d和S.b的依次计算,树的状态如图6.6(e)所示。最后第三遍扫描算出Y的两个属性,树的最终状态如图6.6(d)所示
6.2.3一遍扫描的处理方法 • 与树遍历的属性计算方法不同,一遍扫描的处理方法是在语法分析的同时计算属性值,而不是语法分析构造语法树之后进行属性的计算,而且无需构造实际的语法树。采用这种处理方法,当一个属性值不再用于计算其它属性值时,编译程序不必保留这个属性值。 • 因为一遍扫描的处理方法与语法分析器的相互作用,它与两个鲜明因素密切相关: • 1)所采用的语法分析方法; • 2)属性的计算次序。 • 如果按这种一遍扫描的编译程序模型来理解语法制导翻译方法的话,所谓语法制导翻译法,直观上说就是为文法中每个产生式配上一组语义规则,并且在语法分析的同时执行这些语义规则。
6.2.4抽象语法树 抽象语法(abstract syntax) 从具体语法中抽象出语言结构的本质性的东西,而不考虑语言的具体符号表示,从而可简化语义的形式描述。 在不同的语言中赋值语句有不同的写法: x=y; x:=y; y→x 等等,可以用抽象形式 assignment(variable,expression) 把前面各种具体形式统一起来。
抽象语法树 通过语法分析可以很容易构造出语法分析树,然后对语法树进行遍历完成属性的计算。因此,语法树可以作为一种合适的中间语言形式。在语法树中去掉那些对翻译不必要的信息,从而获得更有效的源程序中间表示。这种经变换后的语法树称之为抽象语法树。 语法树是常用的一种中间表示形式,把语法分析和翻译分开。语法分析过程中完成翻译有许多优点,但也有一些不足: 1.适于语法分析的文法可能不完全反映语言成分的自然层次结构; 2. 语法分析方法限制,对分析树结点的访问序和翻译需要的访问序不一致。
抽象语法树的表示 产生式s→if B then S1 else S2的语法树 if-then-else / | \ B S1 S2 赋值语句的语法树 assignment variable expression 在抽象语法树中,操作符和关键字都不在叶结点,而是在内部结点中出现。
+ * 4 3 5 例如:表达式3*5+4的抽象语法树
建立表达式的抽象语法树 • 语法制导翻译既可以基于语法分析树,也可以基于抽象语法树进行。两种情况所采用的基本方法是一样的。都可在每个结点上带上一定的属性。 • 建立表达式的抽象语法树与把表达式翻译成后缀形式类似。可为每一个运算分量或运算符号都建立一个结点来为子表达式建立子树。运算符号结点的各子结点分别是表示该运算符号的各个运算分量的子表达式组成的子树的根。 • 抽象语法树中的每一个结点可以由包含几个域的记录来实现的。在一个运算符号对应的结点中,一个域标识运算符号,其它域包含指向运算分量的结点的指针。运算符号通常叫作这个结点的标号。当进行翻译时,抽象语法树中的结点可能会用附加的域来存放结点的属性值(或指向属性值的指针)。
建立表达式的抽象语法树使用的函数 • 在这一节中,用下面的一些函数来建立表示带有二目算符的表达式的抽象语法树中的结点。每一个函数都返回一个指向新建立结点的指针。 1. mknode(op,left,right) 建立一个运算符号结点,标号是op,两个域left和right指向运算分量结点的指针。 2.mkleaf(id,entry) 建立一个标识符结点,由标号id标识,一个域entry指向标识符符号表中相应的项。 3. mkleaf(num,val) 建立一个数结点,标号为num,域val用于存放数的值。
例6.7 下面一系列函数调用建立了表达式a-4+c的抽象语法树(见图6.7)。在这个序列中,p1,p2,…, p5是指向结点的指针,entrya和entryc分别是指向符号表中的标识符a和c的指针。 • 1) p1:=mkleaf(id,entrya); • 2) p2:=mkleaf(num,4); • 3) p3:=mknode(‘-’,p1,p2); • 4) p4:=mkleaf(id,entryc); • 5) p5:=mknode(‘+’,p3,p4); • 这棵抽象语法树是自底向上构造起来的。函数调用mkleaf(id,entrya)和mkleaf(num,4)建立了叶结点a和4,指向这两个结点的指针分别用p1和p2存放。函数调用mknode(‘-’,p1,p2)建立内部结点,它以叶结点a和4为子结点。再经过两步,p5成为指向根结点的左指针。
图6.7 a-4+c的语法树 + – id to entry for c id num 4 to entry for a
表6.4 为表达式建立抽象语法树的属性文法 产生式 语义规则 EE1+T E.nptr:=mknode('+',E1.nptr,T.nptr) E E1-T E.nptr:=mknode('-',E1.nptr,T.nptr) E T E.nptr:=T.nptr T (E) T.nptr:=E.nptr T id T.nptr:=mkleaf(id,id.entry) T num T.nptr:=mkleaf(num,num.val)
+ – id to entry c id num 4 to entry a • 例6.8 一个带注释的语法分析树如图6.8所示,它用来描绘表达式a-4+c的抽象语法树的构造。语法分析树是用虚线表示的。语法分析树之中的E和T标识的结点用综合属性nptr来保存指向抽象语法树中非终结符号代表的表达式结点的指针。见书146 • 图6.8 a-4+c的抽象语法树的构造 1 .每个非终结符号有一个语法树结点的指针属性。 2 .非终结符号归约为建立结点。
6.3 S-属性文法的自下而上计算 • S-属性文法:只含有综合属性。 • 综合属性可以在分析输入符号串的同时由自下而上的分析器来计算。分析器可以保存与栈中文法符号有关的综合属性值,每当进行规约时,新的属性值就由栈中正在规约的产生式右边符号的属性值来计算。 • 在自底向上的分析方法中,可以使用一个栈来存放分析过的子树的信息。现在可以在分析栈中使用一个附加的域来存放综合属性值。
state val ... ... X X.x Y Y.y top Z Z.z ... ... 例子: • 下图表示的是一个带有一个属性值空间的分析栈的例子。假设图中的栈是由一对数组state和val来实现的。每一个state元素都是一个指向LR(1)分析表的指针(或索引)。然而,如果像第五章中那样把文法符号放入栈中时,那么当第i个state对应的符号为A时,val[i]中就存放语法树中与结点A对应的属性值。
A a X x Y y Z z A b:=f(c1,c2,…,ck) b是A的综合属性,ci(1 ik)是中符号的属性。综合属性的值在自底向上的分析过程中,每步归约时,计算相应的属性值。 AXYZ Aa:=f(X x, Y y,Z z)
state val state val ... ... ... ... top X X.x A A .a Y Y.y top Z Z.z 定义式 A .a:=f(X.x, Y.y, Z.z)(抽象)变成 val[ntop]:=f(val[top-2],val[top-1],val[top])(具体可执行代码) 在执行代码段之前执行: ntop:=top-r+1 r是句柄的长度 执行代码段后执行: top:=ntop;
例6.10 用LR分析器实现台式计算器(表6.5) 产生式 代码段(和表6.1对比) LEn printf(val[ntop]) E E+T val[ntop]:=val[top-2]+val[top] E T T T*F val[ntop]:=val[top-2]*val[top] T F F (E) val[ntop]:=val[top-1] F digit