第六章属性文法和语法制导翻译 第七章语义分析和中间代码产生

本文介绍属性文法的基础概念,包括综合属性与继承属性,并探讨如何使用语法制导翻译实现语义分析和中间代码生成。通过具体实例解析不同类型的属性文法及其在编译过程中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第六章 属性文法和语法制导翻译

这章介绍语义分析及翻译的问题。

一,属性文法

属性文法是在上下文无关文法的基础上为每个文法符号(终结符或非终结符)配备若干个相关的“值”(称为属性)。

属性:代表与文法符号相关的信息,和变量一样,可以进行计算和传递。例:类型、值、代码序列、符号表内容等

属性计算的过程即是语义处理的过程,对于文法的每一个产生式配备一组属性的计算规则,则称为语义规则

1,属性分为综合属性继承属性

综合属性:用于“自下而上”传递信息;在语法树中,一个结点的综合属性的值,由其子结点的属性值确定;

                 S—属性文法:仅仅使用综合属性的属性文法。

继承属性:用于“自上而下”传递信息;在语法树中,一个结点的继承属性由此结点的父结点和/或兄弟结点的某些属性确定。

,2,语义规则:在一个属性文法中,对应于每个产生式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)非终结符既可以有综合属性也可以有继承属性,文法开始符号的所有继承属性作为属性计算前的初始值。
(3) 产生式右边符号的继承属性和产生式左边符号的综合属性都必须提供一个计算规则

(4) 产生式左边符号的继承属性和产生式右边符号的综合属性不由所给的产生式的属性计算规则进行计算,它们由其它产生式的属性规则计算
举例: 考虑非终结符A,B和C,其中,
A有一个继承属性a和一个综合属性e
B有综合属性b
C有继承属性c
 产生式  A → BC 应该有计算c和e的规则
 C.c := B.b + 1
 A.e := A.a + B.b

 其中属性A.a和B.b在其他地方计算。

解释:根据 产生式右边符号的继承属性和产生式左边符号的综合属性都必须提供一个计算规则,而A.e正是产生式左边符号的综合属性,C.c正是产生式右边符号的继承属性。而A.a和B.b是产生式左边符号的继承属性和右边符号的综合属性。
综合属性例:

考虑如下属性文法,它用作台式计算器程序,对每个 非终结符E、T和F都有一个综合属性——val,是一个整数值。
 每个产生式左边的综合属性val都是由右边的计算出来的。

对此3*5+4的计算过程:

解释:首先考虑最低最左边的内部节点,对应产生式F->digit,相应的语义规则是F.val=digit.lexval,因为digit.lexval=3,所以F1.val=digit.lexval=3,依次递推直到求出表达式的值

继承属性例:

说明语句的文法
  D → T L
  T → int
  T → real
  L → L1,id
  L → id
type为类型描述T的综合属性
in为变量表L的继承属性


解释:为了确定id1,id2,id3的类型,先求出根的左子节点的属性T.type,然后每项向下计算根的右子树三个L节点的属性值L.in,按红箭头方向计算。和综合属性的区别就是它这是自上而下的。


二,基于属性文法的处理方法

过程:对单词符号串进行语法分析,构造语法分析树,然后根据需要遍历语法树并在语法树的各节点处按语义规则计算。

这种由源程序的语法结构所驱动的处理办法就是语法制导翻译法。

如果在一棵语法树中一个结点的属性b依赖于属性c,那么这个结点处计算b的属性规则必须在确定c的语义规则之后使用;

在一颗语法树中的结点的继承属性和综合属性之间的相互依赖关系可以用称作依赖图的一个有向图来描述。

1,依赖图:在为一棵语法树构造依赖图以前,我们为每一个包含过程调用的语义规则引入一个虚综合属性b,这样把每一个语义规则都写成     
        
   b:= f(c1,c2, …ck)

 依赖图中为每一个属性设置一个结点,如果属性b依赖属性c,则从属性c的结点有一条有向边连到属性b的结点。

例:产生式 AXY的语义规则
      (1)A.a:=f(X.x, Y.y)
   (2)X.i:=g(A.a,Y.y)


如果一属性文法不存在属性之间的循环依赖关系,那么该文法为良定义的。为了设计编译程序,我们只处理良定义的属性文法。

 循环依赖:
 例如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以前都是可用的。

2,抽象语法树

在抽象语法树中,操作符和关键字都不作为叶结点出现,而是把它们作为内部结点,即这些叶结点的父结点。

产生式S→if B then S1 else S2抽象语法树表示:

3*5+4的抽象语法树:

三,S-属性文法的自下而上计算

S-属性文法只含综合属性。

例:

输入 3 * 5 + 4n


四,L-属性文法的自顶向下翻译
1,L属性文法
如果每个产生式A X1 X2 … Xn 的每条语义规则计算的属性是A的综合属性;或者是Xj 的继承属性, 1  j  n, 但它仅依赖:
该产生式中Xj左边符号X1, X2, …, Xj-1的属性;A的继承属性
S属性文法包含于L属性文法

例:变量类型声明的语法制导定义是一个L属性定义

L.in是继承属性
T. type是综合属性

2,翻译模式

翻译模式是语法制导定义的一种便于翻译的书写形式。其中属性与文法符号相对应,语义规则或语义动作用花括号{ }括起来,可被插入到产生式右部的任何合适的位置上。

例:  一个简单的翻译模式
          E→TR
          R→addop T {print(addop.lexeme)}R1|ε
          T→num{print(num.val)} 
     把带加号和减号的中缀式变成后缀式
      把语义动作看成终结符号

设计翻译模式(根据语法制导定义)
条件:语法制导定义是L-属性定义

保证语义动作不会引用还没有计算的属性值。
(1)只需要综合属性的情况
(2)既有综合属性又有继承属性
(1)只需要综合属性的情况
为每一个语义规则建立一个包含赋值的动作,并把这个动作放在相应的产生式右边的末尾。
例如: 产生式 语义规则
 T->T1*F      T·val:=T1 ·val*F ·val
 翻译模式
 T->T1*F    {  T·val:=T1 ·val*F ·val}

(2)既有综合属性又有继承属性的情况
①产生式右边的符号的继承属性必须在这个符号以前的动作中计算出来。
②一个动作不能引用这个动作右边符号的综合属性。
③产生式左边非终结符号的综合属性只有在它所引用的所有属性都计算出来以后才能计算。计算这种属性的动作通常可放在产生式右端的未尾。
例如:下面的翻译模式不满足要求:
       S->A1A2        {A1·in:=1;     A2 ·in:=2}
       A ->a              { print(A ·in)  }
in是继承属性

输入串aa的语法树

3,自顶向下翻译

1.从翻译模式中消除左递归 
对于一个翻译模式,若采用自顶向下分析,必须消除左递归和提取左公因子,在改写基本文法时考虑属性值的计算。
2.
左递归翻译模式
         A→A1Y{A.a:=g(A1.a,Y.y)}
         A→X {A.a:=f(X.x)}                  (1式)
每一个文法符号都有一个综合属性,用相应的小写字母表示,g和f是任意函数。

消除左递归,文法转换成
         A→X R
         R→Y R|ε   


第七章 语义分析和中间代码产生


一,语义分析
1,语义分析的任务
审查每一个语法结构的静态语义,即验证语法正确的结构是否有意义。
 如:赋值语句:x:=x+y,左边变量类型与右边变量类型是否一致。
在语义正确的基础上生成一种中间代码或目标代码。
2,静态语义检查:
包括类型检查:按语言的类型规则,检查运算的合法性与运算分量类型的一致性,必要时作类型转换。
控制流检查:控制流语句必须转移到合法的地方。如C中,break语句规定跳出最内层的循环或switch语句。
一致性检查:在很多场合要求对象只能被说明一次。如:pascal语言规定同一个标识符在一个分程序中只能被说明一次等。
相关名字检查:如:Ada,循环或块可以有一个名字,它出现在这些结构的开头或结尾。编译程序必须检查这两个地方用的名字是否相同。
3,语义描述工具:属性文法
4,语法制导翻译
对文法中的每个产生式都附加上一个语义动作或语义子程序。伴随着语法分析,每当使用一条产生式进行推导或归约时,就执行相应产生式的语义动作(包括:查填表格,改变变量的求值,诊察与报告错误,生成中间代码等),从而完成预定的翻译工作。
自底向上的语法制导翻译
自顶向下的语法制导翻译

二,中间代码产生

 1、逆波兰表示法
      波兰表示是一种既不须考虑优先关系、又不用括号的一种表示表达式的方法(前缀式)。
  现在我们要介绍的刚好是另一种波兰表示形式,称为后缀式,即运算符在后。
      例:    a+b → ab+
          a*(b+c) → abc+*
           -a+b*c → a@bc*+

2、图表示法
抽象语法树:


无循环有向图(DAG):



已知抽象语法树求DAG图:合并公共结点

3,三元式

1.三元式由三个部分组成:
           算符:OP
      第一运算分量:ARG1
      第二运算分量:ARG2

2.各种语句都可表示成一组三元式
     例1:              OP   ARG1   ARG2
      x+y*z      (1)     *     y      z
                     (2)     +     x     (1)

在三元式的基础上附加一张指示器表─间接码表,按运算的先后顺序列出有关三元式在三元式表中的位置。这种表示方法称为间接三元式

3.四元式

一个四元式是一个带有四个域的记录结构:op,arg1,arg2及result。它实际上就是一条三地址的指令。
    例:A+B*(C-D)-E/F↑G的四元式为:
          OP   ARG1  ARG2  RESULT
      ①  -     C     D      T1
     ②  *     B     T1     T2
      ③  +     A     T2     T3
      ④ ↑     F     G      T4 
      ⑤  /     E     T4     T5
      ⑥  -     T3    T5     T6

有时将四元式表示成更直观的形式-三地址代码
三地址代码形式:
 x:=a op b         (赋值形式)
与赋值语句的区别:其右边最多只能有一个运算符。

如:四元式(<,B,D, T1),三地址码T1:=B<D


 

三,常用语句的翻译
1,说明语句

 为局部名字建立符号表条目
为它分配存储单元
符号表中包含名字的类型和分配给它的存储单元的相对地址等信息 

  1.简单说明句:用一个基本字来定义

一串名字,其语法描述一般为:
     D→integer namelist∣real namelist
     namelist→namelist,i∣i
  2.数组说明
 例如,array[l1:u1,l2:u2,…,ln:un]
                            di=ui-li+1所求地址:                                                                
                            D=CONSPART+VARPART
                              CONSPART=a-C
C=(…((l1d2+l2)d3+l3)d4+…)+ln-1)dn+ln

    

3,赋值语句的翻译
    1)简单算术表达式的赋值语句:
    不考虑数组元素、记录、函数的引用等情况。

   假如所有分量都是相同类型的情况,如都是整型。可用如下的二义文法来进行描述:
     S→id:=E  
    E→E1+E2∣E1*E2∣(E1)∣-E1∣id

   2)类型转换

   我们可以把类型信息反映到运算符中,例如用+i,*i表示定点+、*,用+r,*r表示浮点+、*。有的程序设计语言允许混合运算,有   的不允许。如果不允许,则发现有类型不相同的运算分量就应该报错。如果允许,就要进行类型转换。

   整、实运算要全部转成实型:给每个非终结符添加一个类型信息,如我们用E.MODE表示E的类型,其值为r或者i。

    例:利用翻译模式,给出赋值语句 a:=b*-c+b*-c 的三地址代码序列。

    T1=-c
    T2=b*T1
    T3=-c
    T4=b*T3
    T5=T2+T4
    a=T5
   3)数组元素的赋值

   (1)  数组元素的地址计算公式
           若数组A的元素存放在一片连续单元里,则可以较容易的访问数组的每个元素。假定数组的每个元素的宽度为w,则一维 数组A[i] 这个元素的起始地址为:
                            base + (i – low)*w
 其中,low为数组下标的下界, base是分配给数组的相对地址,即base为A的第一个元素A[low]的相对地址。
        base + (i – low)*w   可整理为:  i*w + (base –low*w)
  其中:
     (1) i*w  是随数组下标变量而变化的部分,记为VARPART ;
    (2)(base – low*w)是在数组中不变化的常数记为CONSPART
  ( 2) 二维数组
   若二维数组A按行存放,则可用如下公式计算A[i1,i2]的相对地址:
    base + (( i1 – l1)*d2 + i2 – l2) *w
    其中,l1、l2分别为i1、i2的下界;di界差。若ui为i的上界,则di=ui – li +1.
假定i1,i2是编译时唯一尚未知道的值,我们可以重写上述表达式为:( base –( (l1 *d2) + l2) *w)+ ( (i1*d2) + i2) *w         
                  CONSPART                   VARPART
       其中:前一项子表达式( base – ((l1 *d2) + l2) *w )的值是可以在编译时确定的记为常数CONSPART。
      后一子项随i1, i2 而改变是一个变数记VARPA

 4) 含数组元素的赋值语句的翻译

   数组的翻译方案是:
      产生两组计算数组元素地址的四元式:
      一组是计算VARPART,并将计算结果放入临时变量T1,
      另一组是计算CONSPART,并将计算结果放入临时变量T2,同时用T2[T1]表示数组元素的地址。
   例: 令A是一个10*20的数组即 d1=10, d2=20,求:
     (1) 赋值语句 X:= A[I, J ]四元式序列。
     (2) 赋值语句A[ I+2 , J+1 ] := M+N四元式序列。
   解:(1)
       ( * ,I,   20 , T1 )
        (+  ,  J  , T1 ,   T1 )
        ( - ,   A , 21 , T2 )
        ( =[ ] , T2[T1] , , T3)
        ( :=  ,  T3  ,   , X    )
    解:(2)
         (  +  , I  ,  2 ,  T1   )
         (  + ,  J , 1   ,  T2  )
         (  *  , T1, 20 ,  T3 )
         (  + , T2 , T3 , T3 )
         (  -  ,   A,  21 , T4 )
         ( +  ,  M,  N ,  T5 )

         ( [ ]=, T5,      , T4[T3] )

 4,控制流语句的翻译
  仿照算术表达式的翻译来进行。
  例如 A∨B∧C=D可翻译成如下四元式序列:
      (=,C,D,T1)
      (∧,B,T1,T2)
      (∨,A,T2,T3)

  1) 条件语句中布尔表达式的翻译
  出现在条件语句if E then S1 else S2
  中的布尔表达式E,它的作用仅在于控制对S1或S2的选择,亦即提供“真”“假”出口,所以其值无需一直保留。
    A∨B  可理解为 if A then true else B
      A∧B  可理解为 if A then B else false
      ┐A   可理解为 if A then false else true
 
    可以把作为控制条件的任何布尔表达式表示成仅含下列三种形式的四元式的序列:
   (jnz,a,--,p)  表示 if  a goto  p
   (jrop,x,y,p)  表示 if  x rop y  goto p
   (j,--,--,p)   表示 goto  p
例: if A∨B∧C=D then S1 else S2 的四元式:
        1.(jnz,A,--,7)
        2.(j,--,--,3)
        3.(jnz,B,--,5)
        4.(j,--,--,p+1)
        5.(j=,C,D,7)
        6.(j,--,--,p+1)
        7.(S1的四元式序列  
           … … )     

       p.(j,--,--,q)
        p+1.(S2的四元式序列
             … … )
        q. … …

 2)标号和无条件转移的翻译

  (1)对于说明性出现的标号,很容易处理:
                L: S
     当这种语句被处理之后,标号L被称为“定义了”的。也就是,在符号表中,标号L的“地址”栏将登记上语句S的第一个四元式的地址(编号)。
   (2)对于先定义后应用的无条件转移(向后转移的goto L ),也很容易处理。对L查表得到它的定义地址p,就可生成goto L的四元式(j,--,--,p)。
   (3)对于先应用后定义的情况(前向转移goto  L ):
     拉链返填:把所有以L为转移目标的四元式串在一起。链的首地址放在符号表中L的“地址”栏中。

  回填:先产生暂时没有填写目标标号的转移指令,对于每一条这样的指令作适当的记录,一旦目标标号被确定下来,再将它“回填”到相应的指令中。


 3)循环与分情况语句的翻译
  (1)循环语句
     大多数程序语言中都有如下形式的循环句:
      S→for i:=E1 step E2 until E3 do S1
   有条件循环语句和计数循环语句等:

 例while a<b do
    if c<d then
       x:= y+z
    else
       x:=y-z
 四元式: L1 : if a<b goto L2
               goto Lnext
    L2 : if c<d goto L3
               goto L4
    L3 : t1 := y+z
               x:=t1
               goto L1 (L5)
    L4 : t2:=y-z
               x:=t2
   (L5)     goto L1
    Lnext:


     

    以上是第六章和第七章的重点,虽然有很多所学内容的删减。这是编译过程中的重要环节,至此对整个编译过程从词法分析、语法分析到语义分析和中间代码产生有了整体认识,虽然没有优化,也完成了主要步骤。之后会有一篇总结。
  




          






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值