1、属性文法 语法制导翻译概论 中间代码的形式 简单赋值语句的翻译 布尔表达式的翻译,第八章,语法制导翻译和 中间代码生成,翻译程序的任务是把源程序翻译成目标程序,这个目标程序必须和源程序的语义等同。通常,在词法分析程序和语法分析程序对源程序的语法结构进行分析之后,要么由语法分析程序直接调用相应的语义子程序进行语义处理,要么首先生成语法树或该结构的某种表示,再进行语义处理。编译中的语义处理指两个功能:第一是审查每个语法结构的静态语义,即验证语法结构合法的程序是否真正有意义(静态语义分析或静态审查);第二是在静态语义正确,语义处理要执行真正的翻译,即要么生成程序的一种中间表示形式(中间代码),要么生
2、成实际的目标代码。,有的编译程序直接生成目标代码,有的编译程序采用中间代码。中间代码也称中间语言,是复杂性介于源程序语言和机器语言的一种表示形式。一般,快速编译程序直接生成目标代码,省掉了将中间代码翻译成目标代码的额外开销。但为了使编译程序结构在逻辑上更为简单明确,常采用中间代码,这样可将与机器相关的某些实现细节置于代码生成阶段仔细处理,并且可以在中间代码一级进行优化工作,使得代码优化比较容易实现。,现在很多编译程序采用比较接近形式化的语法制导翻译方法对语义处理工作进行比较规范和抽象的描述。这种方法使用属性文法为工具来说明程序设计语言的语义。一个属性文法包含一个上下文无关文法和一系列语义规则,
3、这些语义规则附在文法的每个产生式上,所谓语法制导翻译是指在语法分析过程中,完成附加在所使用的产生式上的语义规则描述的动作,从而实现语义处理。,属性文法,属性常用来描述事物的特征等。对编译程序使用的语法树的结点,可以用“类型”、“值”或“存储位置”等属性来描述。形式上讲,一个属性文法是一个三元组A=(G,V,F):一个上下文无关文法G,一个属性的有穷集V,一个关于属性的断言或谓词的有穷集F。每个属性与文法的某个非终结符或终结符相联。每个断言与文法的某产生式相联。如果对G中的某一输入串,A中的所有断言对该输入串的语法树结点的属性全为真,则该串是A语言的句子。编译程序的静态语义审查工作就是验证关于所
4、编译的程序的断言是否全部为真。,属性文法,属性文法最早出自克努特笔下,他把属性分为两类:继承属性和综合属性。在分析树中,一个结点的综合属性值是从其子结点的属性值计算出来的;而一个结点的继承属性值是由该结点兄弟结点和父结点的属性值计算出来的。这里不对属性文法进行理论上的研究,仅将它作为工具描述语义分析。在编译的许多实际应用中,属性和断言以多种形式出现,即与每个文法符号相联的可以是各种属性、断言、以及语义规则,或者某种程序设计语言的程序段等等。,属性文法,例:有文法G:E-T1+T2|T1orT2 T-num|true|false对输入串3+4的语法树如图:属性文法记号中常用N.t的形式表示与非终
5、结符N相联的属性t。比如可把完成对上面表达式的类型检查的属性文法写成如下图形式。与每个非终结符T相联的有属性t,t要么是int,要么是bool。与非终结符E的产生式相联的断言指明:两个T的属性必须相同。下图是上面输入串的语法树结点带有语义信息的表示。,属性文法,属性文法中,对应于每个产生式A-都有一套与之相关联的语义规则,每条规则的形式为b:=f(c1,c2,ck)。f是一个函数,b和c1,c2,ck是该产生式文法符号的属性。 1)如果b是A的一个属性,c1,c2,ck是产生式右部文法符号的属性或A的其他属性,则称b是A的综合属性。 2)如果b是产生式右部某个文法符号X的一个属性,并且c1,c
6、2,ck是A或产生式右边任何文法符号的属性,则称b是文法符号X的继承属性。上述两种情况都说属性b依赖于属性c1,c2,ck。 注意:1)非终结符既可有综合属性也可有继承属性,但文法开始符号没有继承属性;2)终结符只有综合属性,它们由词法分析程序提供。,例1:简单算术表达式求值的语义描述。产生式 语义规则 0)L-E print(E.val) 1)E-E1+T E.val:=E1.val+T.val 2)E-T E.val:=T.val 3)T-T1*F T.val:=T1.val*F.val 4)T-F T.val:=F.val 5)F-(E) F.val:=E.val 6)F-digit F
7、.val:=digit.lexval,属性文法,在该描述中,每个非终结符都有一个属性:一个整数值的称作val的属性。按照语义规则对每个产生式来说,它的左部E、T和F的属性值的计算来自它的右部的非终结符,这种属性称作综合属性。单词digit仅有综合属性,它的值是由词法分析程序提供的。和产生式L-E相联的语义规则是一个过程,打印E产生的表达式的值,可以理解为L的属性是空的或是虚的。,例2:描述说明语句中各种变量的类型信息的语义规则。产生式 语义规则 0)D-TL L.in:=T.type 1)T-int T.type:=integer 2)T-real T.type:=real 3)L-L1,id
8、 L1.in:=L.inaddtype(id.entry,L.in) 4)L-id addtype(id.entry,L.in) 注:例中与L产生式相联的语义规则中有一过程调用,过程addtype的功能是把每个标识符的类型信息登录在符号表的相关项中。,属性文法,该文法定义了一种说明语句,该说明语句的形式是由关键字int或real开头,后跟一标识符表,每个标识符间由逗号隔开。非终结符T有一综合属性type,它的值由关键字决定(int或real)。与产生式D-TL相联的语义规则L.in:=T.type将L.in的属性值置为该说明语句指定的类型。属性L.in是继承属性,将被沿着语法树传递到下边的结点
9、使用,与L产生式相联的规则里使用了它。下图是句子int id1,id2的语法树,使用=表示属性的传递情况。,语法制导翻译概论,从概念上讲,基于属性文法的处理过程即语法制导翻译通常是这样的:对单词符号串进行语法分析,构造语法分析树,然后根据需要构造属性依赖图,遍历语法树并在语法树的各个结点处按语义规则进行计算。,计算语义规则,所谓依赖图,是一个有向图,用于描述分析树中的属性和属性间的相互依赖关系。依赖图的构造算法如下。例2的变量说明语句Real id1,id2,id3分析树的依赖图(图中结点用数字表示)如下图。属性计算有树遍历的和一遍扫描的方法。,树遍历:设语法树已经建立好,并且树中已经带有开始
10、符号的继承属性和终结符的综合属性。然后以某种次序遍历语法树,直至计算出所有属性。最常用的遍历方法是深度优先,从左到右的遍历方法。若需要可使用多次遍历。,一遍扫描:是在语法分析的同时计算属性值,而不是语法分析构造语法树之后进行属性的计算,而且无需构造实际的语法树。对于编译程序来说,在单遍扫描中完成语义翻译工作很重要。,S-属性文法和自下而上翻译,一个一般的属性文法的翻译器可能很难建立,但L-属性文法的翻译器是很容易建立的。S-属性文法是L-属性文法的特例,是指只含有综合属性的属性文法。综合属性可以在分析输入符号串的同时自下而上的来计算。S-属性文法的翻译器通常可借助于LR分析器实现。分析器可以保
11、存与栈中文法符号有关的综合属性值,每当进行归约时,新的属性值就由栈中正在归约的产生式右边符号的属性值来计算。,S-属性文法和自下而上翻译,例:输入串是2+3*5,其语法树如下图,在第一步归约用产生式6),执行语义动作是置F.val的值为单词digit值,把语法树中每个结点的语义值括在该结点处。那么第一次归约并完成语义动作后的情形如下图。继续进行分析,第七次归约后的情形如下图,归约至E,它的值17就计算出来了。,S-属性文法和自下而上翻译,语法制导翻译的具体实现途径不难。假定有一个LR语法分析器,现在把它的分析栈扩充,使得每个文法符号都跟有语义值,即把栈的结构看成如下图所示。同时把LR分析器的功
12、能扩大,使它不仅执行语法分析任务,且能在用某个产生式进行归约的同时调用相应的语义子程序,完成在前面介绍的表达式的属性文法中描述的语义动作。每步工作后的语义值保存在扩充的分析栈里“语义值栈”栏中。采用的LR分析表如下图,其中d代替digit。分析和计算2+3*5的过程如下图所示。,按照上述实现办法,若把语义子程序改为产生某种中间代码的动作,那么则可在语法分析的制导下,随着分析的进展逐步生成中间代码。,L-属性文法在自上而下分析中的实现,一个属性文法称为L-属性文法,如果对于每个产生式A-X1X2Xn,其每个语义规则中的每个属性或者是综合属性,或者是Xj(1=j=n)的一个继承属性且这个继承属性仅
13、依赖于: 1)产生式中在Xj左边的符号X1,X2,Xj-1的属性; 2)A的继承属性。例7.2描述变量说明语句中类型信息的属性文法是L-属性文法。S-属性文法一定是L-属性文法,因为1)、2)限制只用于继承属性。L-属性文法允许一次遍历就计算出所有属性值。,L-属性文法在自上而下分析中的实现,例3. 将中缀表达式翻译成相应后缀表达式的属性文法,其中addop表示+或-。 E-EaddopT print(addop.Lexeme) E-T T-num print(num.val) 如果采用LR分析方法,用例3的属性计算很容易实现,比如在对串2+3-5的分析同时执行语义动作,打印出23+5-。语法
14、分析树中加上虚线连结的语义结点,形成一个可说明语义动作的树,如下图。,L-属性文法在自上而下分析中的实现,LL(1)这种自上而下分析方法的分析过程,从概念上说可以看成是深度优先建立语法树的过程,因此可以在自上而下语法分析的同时实现L-属性文法的计算。注意,例3是一个含左递归规则的文法,采用LL(1)分析必须改写文法如下: E-TR R-addop T R| T-num这时2+3-5的语法树如下图,将后缀式23+5-输出的动作在语法树中应出现的位置如下图。,L-属性文法在自上而下分析中的实现,例4. (中缀表达式翻译成相应的后缀表达式) E-TR R-addopT print(addop.Lex
15、eme) R | T-num print(num.val)例4给出了可使用LL(1)分析方法的语义描述,不同的是,语义动作不是附在产生式右部的末尾,而是嵌在了右部文法符号T和R之间。把这种语义处理的描述形式称为翻译模式。,L-属性文法在自上而下分析中的实现,翻译模式是适合语法制导翻译的另一种描述形式。翻译模式给出了使用语义规则进行计算的次序,可把某些实现细节表示出来。在翻译模式中,和文法符号相关的属性和语义规则,用花括号括起来,插入到产生式右部的合适位置上。,L-属性文法在自上而下分析中的实现,一般转换左递归翻译模式的方法如下:假设翻译模式为:A-A1YA.a:=g(A1.a,Y.y)A-X
16、A.a:=f(X.x)每个文法符号都有一个综合属性,用相应的小写字母表示,g和f为任意函数。消除左递归,文法转换为:A-XR R-YR|再考虑语义动作,则翻译模式为:A-XR.i:=f(X.x) RA.a:=R.sR-YR1.i:=g(R.i,Y.y) R1R.s:=R1.sR- R.s:=R.i其中使用了R的继承属性i和综合属性s。,L-属性文法在自下而上分析中的实现,计算继承属性的办法有两种:1)从翻译模式中去掉嵌入在产生式中间的动作;2)用综合属性代替继承属性。为了使所有嵌入的动作都出现在产生式的末尾,可以自下而上处理继承属性,从翻译模式中去掉嵌入在产生式中间的动作,方法是:引入新的非终
17、结符N和产生式N-,把嵌入在产生式中间的动作用非终结符N代替,并把这个动作放在产生式后面。 例,L-属性文法在自下而上分析中的实现,有时为了用综合属性代替继承属性,改变基础文法是一种可能的办法。例如,一个Pascal的说明由一标识符序列后跟类型组成,如:m,n:integer。这种说明语句的文法可由下面形式的产生式构成:D-L:T T-integer|char L-L,id|id因为标识符由L产生而类型不在L的子树中,不能仅仅使用综合属性就把类型与标识符联系起来。事实上,若非终结符L从第一个产生式中它右边的T中继承了类型,则得到的属性文法就不是L属性的,因此,基于这个属性文法的翻译工作不能在语
18、法分析的同时进行。,L-属性文法在自下而上分析中的实现,一个解决的办法是重新构造文法,借以实现用综合属性代替继承属性。比如一个等价文法如下:D-idL L-,idL|:T T-integer|char这样,使类型作为标识符表的最后一个元素,类型可以通过综合属性L.type进行传递,当通过L产生每个标识符时,它的类型可以填入到符号表中。属性文法如下: D-idLaddtype(id.entry,L.type) L-,idL1L.type:=L1.type;addtype(id.entry,L.type)|:TL.type:=T.type T-integerT.type:=int|charT.ty
19、pe:=ch,中间代码的形式,编译程序使用的中间代码形式常见的有:逆波兰记号、三元式、四元式和树形表示。,逆波兰记号,逆波兰记号表示法,将运算对象写在前面,把运算符写在后面,比如把a+b写成ab+,把a*b写成ab*,用这种表示法表示的表达式也称为后缀式。 例后缀表示法表示表达式,最大优点是易于计算机处理表达式。利用一个栈,自左至右扫描算术表达式(后缀表示)。遇到运算对象则进栈,遇到运算符,若该运算符是二目的,则对栈顶部的两个运算对象实施该运算,并将运算结果代替这两个运算对象而进栈,若是一目运算符,则对栈顶元素执行该运算,并以运算结果代替该元素进栈。最后的结果留在栈顶。 例,逆波兰记号,后缀式
20、表示上的简洁和计值的方便,特别适用于解释执行的程序设计语言的中间表示,也方便具有堆栈体系的计算机的目标代码生成。逆波兰表示很容易扩充到表达式以外的范围。只要遵守运算对象后直接跟它们的运算符这条规则即可。比如把跳转语句GOTO L写为“L jump”,再比如条件语句if E then S1 else S2可表示为:ES1S2,把if-then-else看成三目运算符,用表示。又如数组元素A,可表示为: A subs,运算符subs表示求数组的下标。,但是要注意,这些扩充的后缀表示的计值远比后缀表达式的计值复杂的多,要对新添加的运算符的含义正确处理。以上图中的赋值语句为例,当计算到:=时,执行的是
21、将表达式b*c+b*d的值送给变量a,所以,在执行完赋值后,栈中并不产生结果值,这与算术的二目运算符不一样,另外,因为需要的是a的地址而不是a的值,这也必须注意。,三元式和树形表示,三元式表示,把表达式及各种语句表示成一组三元式。每个三元式三个组成部分是:算符op,第一运算对象ARG1,和第二运算对象ARG2。例三元式中含有对中间计算结果的显示引用,比如三元式(1)表示的是b*c的结果。三元式(3)中的(1)和(2)分别表示第一个三元式和第二个三元式的结果。对于一目算符op,只需选用一个运算对象,不妨规定只用ARG1。至于多目算符,可用若干个相继的三元式表示。,三元式和树形表示,树形表示是三元
22、式表示的另一种形式。上述三元式可表示成下面的树表示:表达式的树形表示很容易实现:简单变量或常数的树就是该变量或常数自身,如果表达式e1或e2的树分别为T1和T2,那么e1+e2,e1*e2,-e1的树分别为:二目运算对应二叉子树,多目运算对应多叉子树。为了便于安排存储空间,一棵多叉子树可以通过引进新结点而表示成一棵二叉子树。,四元式,四元式是一种比较普遍采用的中间代码形式,格式为: (op,,ARG1,ARG2,RESULT),分别表示算符、第一运算对象、第二运算对象和运算结果。运算对象和运算结果有时指用户自己定义的变量,有时指编译程序引进的临时变量。 例四元式和三元式的主要不同在于,四元式对
23、中间结果的引用必须通过给定的名字,而三元式是通过产生中间结果的三元式编号,即四元式之间的联系是通过临时变量实现的。四元式表示很类似于三地址指令,有时把这类中间表示称为“三地址代码”,因为这种表示可看作一种虚拟三地址机的通用汇编码。这种表示对于代码优化和目标代码生成都较有利,四元式,为了直观,有时也把四元式的形式写成简单赋值形式或更易于理解的形式。比如把上述四元式序列写成如下形式: (1)t1:=b*c (2)t2:=b*d (3)t3:=t1+t2 (4)a:=t3把(jump,-,-,L)写成goto L把(jrop,B,C,L)写成if B rop C goto L至于如何用四元式表示各种
24、语句,以及翻译各种语句的语义描述,在后面我们会给出一些讨论。,简单赋值语句的翻译,在编译程序的实际实现中,四元式中的运算对象和结果,或者是一个指针,指向符号表的某一登录项,或者是一个临时变量的整数码。在对赋值语句翻译为四元式的描述中,将会表明怎样查找这样的符号表登录项。首先对id表示的单词定义一属性id.name,用做语义变量,用Lookup(id.name)表示审查id.name是否出现在符号表中,如在,则返回一指向该登录项的指针,否则返回nil。语义过程emit表示输出四元式到输出文件上。语义过程newtemp表示生成一临时变量,每调用一次生成一个新的临时变量。语义变量E.palce表示存
25、放E值的变量名在符号表的登录项或一整数码(如此变量是一个临时变量)。 例,简单赋值语句的翻译,实际上,在一个表达式中可能出现各种不同类型变量或常数,而不是象上例中的id假定为都是同一类型。即编译程序还应执行类型检查的语义动作,如不能接受不同类型运算对象的混合运算,应指出错误;如能接受混合运算,则应执行类型转换的语义处理。假定上例中的表达式可以有混合运算,id可以是实型或整型,且当两个不同类型的量进行运算时,规定首先将整型量转换为实型量。为进行类型转换的语义处理,增加语义变量,用E.type表示E的类型信息,E.type的值为int或real。此外,为区别整型加/乘和实型加/乘,把+、*分别写成
26、+i(*i)和+r(*r)。用一目运算符itr表示将整型运算对象转换成实型。 例,在上例中,与非终结符号E相联的语义值有E.place,还有E.type。语义值的设计是与语义处理的描述相关的。赋值语句的语义处理,对左部的标识符,检查它的种类(kind),若不是变量名,则指出错误,若是变量名,才生成赋值运算的代码。对右部表达式中作为运算对象的标识符,检查是否变量名或常量名,是则生成相应代码,不是(即是过程名),则指出错误。这一点若用语义规则描述的话,还应增加一语义值,与非终结符相联,比如用E.kind表示。赋值语句中含有复杂数据类型,如数组元素或记录的引用,这种情况的翻译工作更复杂。,布尔表达式
27、的翻译,程序设计语言中的布尔表达式有两个作用:计算逻辑值和用做改变控制流语句中的条件表达式。为简单,这里只考虑如下文法生成的布尔表达式:E-E and E| E or E| not E|id rop id|true|false按习惯,布尔算符的优先顺序从高到低为:非、与、或,并且与、或运算服从左结合。,布尔表达式的翻译,通常,计算布尔表达式的值有两种方法,一是如同计算算术表达式一样,一步步计算出各部分的真假值,最后计算出整个表达式的值。例 另一种方法是,采取某种优化措施,只计算部分表达式,例如要计算A or B,若计算出A的值为1,那么B的值就无需再计算了。上述两种方法对于不包含布尔函数调用的
28、表达式没什么差别。但是,若一个布尔表达式中会有布尔函数调用,并且这种调用引起副作用(如对全局变量的赋值)时,这两种方法未必等价。采用哪种方法取决于程序设计语言的语义,有语言规定,函数过程调用应不影响这个调用处环境的计值,或说函数过程的工作不许产生副作用,在这种规定下,可任选其中一种。,布尔表达式的翻译,若按第一种方法计算布尔表达式,则布尔表达式a or b and not c翻译成的四元式序列如下图。对于ab这样的关系表达式,可看成等价的条件语句if ab then 1 else 0,它翻译成的四元式序列如下图。下图给出了按第一种方法计算布尔表达式的值,将布尔表达式翻译成四元式的描述,其中ne
29、xtstat给出在输出序列中下一四元式序号。emit过程每被调用一次,nextstat增加1。,控制语句中布尔表达式的翻译,这里讨论出现在if-then、if-then-else和while-do等语句中的布尔表达式的翻译。这三种语句的语法为:S-if E then S1 | if E then S1 else S2 | while E do S1这些语句的代码结构示意分别如下图的(a),(b),(c) 所示,其中使用.和。两个出口分别表示E为真和假时控制流向的转移,分别叫真出口和假出口。,控制语句中布尔表达式的翻译,作为条件转移的E,仅把E翻译成代码是一串条件转和无条件转四元式。翻译的基本思
30、路是,对于E为a rop b的形式生成代码为:if a rop b goto E.true和goto E.false其中使用E.true和E.false分别表示E的真和假出口转移目标,在后面会多处使用它们。对于E为E1 or E2的形式,若E1为真,则可知E为真,即E1的真出口(即E1.true)和E的真出口(即E.true)一样。若E1是假,那么需计算E2,E1的假出口应是E2代码的第一个四元式标号,这时E2的真出口和假出口分别与E的真出口和假出口一样。 类似的考虑适用于E1 and E2。not E1的翻译更容易,只需调换E1真假出口即可得到E的真假出口。,控制语句中布尔表达式的翻译,例:
31、布尔表达式af翻译为如下四元式序列: (1)if af goto E.true (6)goto E.false当然上面生成的四元式显然不是最优先的,如(2)是不需要的,这个问题可留待代码优化阶段解决。,控制语句中布尔表达式的翻译,在上例中,使用E.true和E.false分别表示整个表达式af的真、假出口,而E.true和E.false的值并不能在产生四元式的同时就能确定的。为了看清楚这一点,不妨把该表达式放在条件语句中考虑,如语句:if af then S1 else S2的四元式序列如下图。,控制语句中布尔表达式的翻译,上述的拉链方式是如下组织的,若有四元式序列: (10) goto E.
32、true (20) goto E.true (30) goto E.true则链成 (10) goto (0) (20) goto (10) (30) goto (20),把地址(30)称作链首,地址(10)为链尾。0为链尾标志.,控制语句中布尔表达式的翻译,仍使用E.true和E.false,不过现在分别表示“真”链和“假”链;仍用nextstat指向下一四元式的地址,emit的用法也同上,还需要下面两个函数:合并merge和回填backpatch。merge(p1,p2)表示把p1和p2为链首的两条链合并为一条,返回合并后的链首值,即将p2的链尾第四区段改为p1,合并后的链首为p2,除非p
33、2为空链。backpatch(p,t)表示把p所链接的每个四元式的第四区段都填为t。其中使用语义值codebegin与非终结符E相连,表示表达式E的第一个四元式语句序号。自下而上的分析中,布尔表达式的一种翻译方法如下图。,控制语句中布尔表达式的翻译,根据上述语义动作的描述,当整个布尔表达式所对应的四元式全都产生之后,做为整个表达式的真、假出口(转移目标)仍没填上。她们分别由“真”链和“假”链E.true和E.false记录。仍以表达式ab or cd and ef为例,分析产生语法树时的语义动作结果,“真”“假”链情况注释在相应结点处,如下图所示(注意:前述的布尔表达式文法是二义的,该图的语法
34、树是借助于关系运算符的优先级形成的)。,控制语句中布尔表达式的翻译,在归约ab到E(使用产生式(5))时,产生2个四元式: 100:if ab goto - 101:goto -这里假定四元式编号从100开始,即nextstat的初值为100。这时E.codebegin为100。E.true和E.false的值分别为100和101。,如下图所示,控制语句中布尔表达式的翻译,由cE1 and E2中完成了E1,E.true和E.false的值分别为102和103,E2.codebegin将为104。继续由产生式(5)把eE1 and E2归约,完成的一个语义动作是backpatch(102,10
35、4),另外一个语义动作是merge(103,105),即合并E1和E2的“假”链,于是产生 的6个语句如下:,如下图所示,控制语句中布尔表达式的翻译,100:if aE1 or E2归约,实现backpatch(101,102),实现merge(100,104),即合并E1,E2的“真”链等产生的四元式如下:,控制语句中布尔表达式的翻译,100:if ab goto - 101:goto 102 102:if cd goto 104 103:goto 104:if ef goto 100 105:goto 103“真”链首E.true为104“假”链首E.false为105,一旦整个布尔表达式
36、的真假出口确定后,则可沿“真”“假”链为相应的四元式填上。如果布尔表达式是做为if-then-else语句的条件表达式,那么当分别扫描到then和else之后,便可回填真假出口。,E-T1+T2T1.t=int AND T2.t=intE-T1 or T2T1.t=bool AND T2.t=boolT-numT.t:=intT-trueT.t:=boolT-falseT.t:=bool,类型检查的属性文法,静态语义检查,属性信息传递情况,语法制导方法计算表达式,语法制导方法计算表达式,语法制导方法计算表达式,扩充的分析栈,LR分析表,步骤 状态栈 符号栈 输入串 ACTION GOTO 语义
37、栈 1) 0 # 2+3*5# S5 - 2) 05 #2 +3*5# r6 3 - 3) 03 #F +3*5# r4 2 -202 #T +3*5# r2 1 -201 #E +3*5# S6 -2016 #E+ 3*5# S5 -2-0165 #E+3 *5# r6 3 -2-0163 #E+F *5# r4 9 -2-30169 #E+T *5# S7 -2-301697 #E+T* 5# S5 -2-3-016975 #E+T*5 # r6 10 -2-3-01697(10) #E+T*F # r3 9 -2-3-50169 #E+T # r1 1 -2-(15)01 #E # ac
38、c -(17),程序设计语言中的表示 逆波兰表示a+b ab+a+b*c abc*+(a+b)*c ab+c*a:=b*c+b*d abc*bd*+:=,例:BCD*+(它的中缀表示为-B+C*D,使用表示一目减)的计值过程为: 1)B进栈; 2)对栈顶元素施加一目运算,并将结果代替栈顶,即-B置于栈顶; 3)C进栈; 4)D进栈; 5)栈顶两元素相乘,两元素退栈,相乘结果置栈顶; 6)栈顶两元素相加,两元素退栈,相加结果进栈,现在栈顶存放的是整个表达式的值。,例如:a:=b*c+b*d的表示为: (1) (* , b , c) (2) (* , b , d) (3) (+ , (1) , (
39、2) ) (4) (:= , (3) , a),-e1,e1+e2,e1*e2,例如:a:=b*c+b*d的四元式表示如下: (* , b , c , t1) (* , b , d, t2) (+ , t1 , t2 , t3) (:= , t3 , - , a),下面给出了翻译赋值语句到四元式的语义描述,这里的语义的工作包括对变量进行先定义后使用”的检查。 (1)S-id:=E p:=lookup(id.name);if pnil then emit(p :=E.place) else error; (2)E-E1+E2 E.place:=newtemp;emit(E.place:=E1.p
40、lace+E2.palce); (3)E-E1*E2 E.place:=newtemp;emit(E.place:=E1.place*E2.place); (4)E-E1 E.place:=newtemp;emit(E.place:=uminusE1.place); (5)E-(E1) E.place:=newtemp; E.place:=E1.place; (6)E-id p:=lookup(id.name);if pnil then Begin E.place:=newtemp; E.place:=p Endelse error; ,比如对如下赋值语句: a:=-d*(b+c) 按上述属性
41、文法翻译后得到四元式序列如下: (uminus,d,-,E1.place) (+,b,c,E2.place) (*,E1,E2,E3.place) (:=,E3,-,p) /p表示标识符a在符号表中的存储项,上例中第三条产生式及其有关语义描述如下图: E-E1*E2 E.place:=newtemp;if E1.type=int AND E2.type=int thenbegin emit(E.place, :=,E1.place,*i,E2.place);E.type:=int endelse if E1.type=real AND E2.type=real thenbegin emit(E
42、.place,:=,E1.place,*r,E2.place); E.type:=real endelse if E1.type=int thenbegin t:=newtemp;emit(t,:=,itr,E1.place);emit(E.place,:=,t,*r ,E2.place);E.type:=real; endelse begin t:=newtemp; emit(t,:=,itr,E2.place);emit(E.place,:=, E1.place,*r,t);E.type:=real; end ,1 or (not 0 and 0) or 0 =1 or (1 and 0)
43、 or 0 =1 or 0 or 0 =1 or 0 =1,t1:=not c t2:=b and t1 t3:=a or t2,布尔表达式a or b and not c翻译成的四元式序列如下:,这是四元式的直观表示形式,if ab goto (4)t:=0goto (5)t:=1,其中用临时变量t存放布尔表达式ab的值,(5)为后续的四元式序号。,(1)E-E1 or E2 E.place:=newtemp;emit(E.place:=E1.placeorE2.palce); (2)E-E1 and E2 E.place:=newtemp;emit(E.place:=E1.placeand
44、E2.place); (3)E-not E1 E.place:=newtemp;emit(E.place:=notE1.place); (4)E-(E1) E.place:=newtemp; E.place:=E1.place; (5)E-id1 rop id2 E.place:=newtemp;emit(ifid1.place rop id2.place goto nextstat+3);emit(E.place:= 0);emit(goto nextstat+2);emit(E.place:= 1); (6)E-true E.place:=newtemp;emit(E.place:= 1)
45、; (7)E-false E.place:=newtemp;emit(E.place:= 0); ,(a),(b),(c),out:,begin:,(1)if af goto E.True (6)goto E.False (7)(关于S1的四元式) (p) goto (q) (p+1) (关于S2的四元式) (q),上述四元式(1)和(5),(4)和(6)的转移地址并不能在产生这些四元式的同时得知。例如(1)和(5)的转移地址是在整个布尔表达式的四元式产生完毕之后才得知的。因此需回填这个地址。为了记录需回填地址的四元式,常采用一种“拉链”的办法。把需回填E.true的四元式拉成一条链,把需回填
46、E.false的四元式拉成一条链,分别称为“真”链和“假”链。,(7),(p+1),(7),(p+1),(1)E-E1 or E2 backpatch(E1.false,E2.codebegin);E.codebegin:=E1.codebegin;E.true:=merge(E1.true,E2.true);E.false:=E2.false; (2)E-E1 and E2 backpatch(E1.true,E2.codebegin);E.codebegin:=E1.codebegin; E.true:=E2.true; E.false:=merge(E1.false,E2.false);
47、 (3)E-not E1 E.true:=E1.false;E.codebegin:=E1.codebegin;E.false:=E1.true; (4)E-(E1) E.true:=E1.true;E.codebegin:=E1.codebegin;E.false:=E1.false; ,(5)E-id1 rop id2 E.true:=nextstat;E.codebegin:=nextstat;E.false:=nextstat+1;emit(if id1.place rop id2.place goto-);emit(goto -); (6)E-true E.true:=nextstat;E.codebegin:=nextstat;emit(goto-); (7)E-false E.false:=nextstat;E.codebegin:=nextstat;emit(goto-); ,