六万字长文!让你懂透编译原理(七)——第七章 语义分析和中间代码产生

六万字长文!让你懂透编译原理(七)——第七章 语义分析和中间代码产生

编译原理最后一篇,完结撒花,555,马上考试了,学不完了,同样长文预警!在这里插入图片描述
系列文章传送门:
万字长文+独家思维导图!让你懂透编译原理(一)——第一章 引论
万字长文!让你懂透编译原理(二)——第二章 高级语言及其语法描述
近三万字长文!让你懂透编译原理(三)——第三章 词法分析
三万多字长文!让你懂透编译原理(四)——第四章 语法分析—自上而下分析
六万多字长文!让你懂透编译原理(五)——第五章 语法分析—自下而上分析
三万五千字长文!让你懂透编译原理(六)——第六章 属性文法和语法制导翻译
六万字长文!让你懂透编译原理(七)——第七章 语义分析和中间代码产生

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

E→E1op E2
E是由两个子表达式通过op运算符连接而成
E.code:= E1.code || E2.code ||op
那么他的语义说的是,构造好的复杂的表达式后缀形式由op前面的子表达式E1的后缀形式和op后面的子表达式E2的后缀形式这两个后缀形式依次连接,再放上op运算符,这样的出来的就是整个表达式的后缀形式。
(E的后缀式是E1的后缀式+E2的后缀式+op)

E→ (E1)
第二条规则,从语法上看,一个表达式加上括号还是一个表达式
E.code:= E1.code
语义说的是,带括号的表达式的后缀形式,就是括号内的子表达式的后缀形式

E→id
第三条规则说的是,单独的一个标识符,也是一个表达式
E.code:=id
语义规则说的是一个标识符构成的表达式,他的后缀形式就是标识符自己

在这里插入图片描述

E→E1op E2{POST[k]:=op;k:=k+1}
说的是E的后缀式是E1的后缀式+E2的后缀式+op
当你用E1,E2归结到E的时候,E1和E2是已经分析翻译完了的,可以认为E1,E2翻译出来的后缀式已经在post里面了,这个时候要得到整个的E或者说是E1opE2的话,后缀式只要在这两个E1’和E2’的后面再放上op运算符,则整个的这个部分就是归约后的E所对应的后缀式,

在这里插入图片描述

那么对于E定义为(E1)这一条规则
根据前面后缀式的定义以及这里的语义规则
E→ (E1) E.code:= E1.code

说的是E的后缀式就是括号里面的子表达式的后缀式就是里面的E1的后缀式,所以说当你要把(E1)归约到E的时候,这个时候E的代码怎么构成?此时E1已经分析完毕,E1的代码已经在POST里面了,此时对应的语义动作是什么都不做,因为这个时候POST最后段的E1’这个后缀式就是这整个的带括号表达式的后缀式,所以什么都不做。就相当于完成了这条规则。

在这里插入图片描述

同理,对于
E→id E.code:=id
可是这个表示符本身就是自己的后缀式,所以这个时候只要把这个标识符id放到POST当前位置上,然后k+1走到后面就行了,这样这一项就是E的后缀式
E→id {POST[k]:=i;k:=k+1}

举例:输入串a+b+c的分析和翻译
在这里插入图片描述

7.1.2 图表示法

图表示法

  • DAG
  • 抽象语法树

在这里插入图片描述
在这里插入图片描述
父节点具有该运算符作用于其子节点的对应的值之后的结果,在DAG中代表公共子表达式的节点可以具有多个父节点,这就是有向无环图与抽象语法树的区别,一个子节点可能有多个父节点,因此他是图,不是树
在这里插入图片描述
赋值语句中的俩个子表达式b*(-c)在抽象语法树中对应有两棵独立的子树,这两棵子树是一样的,而在有向无环图中,这两颗子树被合并了,和并成了一个子树,消除了冗余的子树,这样一来,这个+节点他的左右两个子树都是乘法的结果,因此右边的这个图是个有向无环图,省去的都是由父节点指向子节点,本来是有箭头的,将箭头省掉了,带上箭头,这里面肯定是没有环的

在这里插入图片描述
建立一个在这里插入图片描述

7.1.3 三地址代码

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
首先,所有涉及的三元式指令放在三元式表中,一共有4条指令,重复的求负以及乘法指令在这里面没有重复保存,只留下了一个求负指令,一个乘法指令,然后在这个加法左操作数是(1)的结果,由操作数也是(1)的结果,那么整个赋值语句,的计算的顺序,体现在间接码表中,说的是先计算0号指令,再计算(1)号指令,再计算(2)号指令,再计算(3)号指令,看上去好像顺序一样,但是如果将来因为优化,要删除增加语句,或者要调整语句顺序,你只要修改间接码表,对间接码表进行增加一项,删除一项或者是调整顺序,而三元式表不改动,三元式表不改动意味着每个三元式指令的下标不会变,而且三元式内部对下标的引用也不会变。

再看一个复杂一点的例子,说明间接三元式是如何进行优化的。
在这里插入图片描述
在这里插入图片描述

7.2 赋值语句的翻译

7.2.1 简单算术表达式及赋值语句

在这里插入图片描述

在这里插入图片描述
设计上采取了递归的思想:
在这里插入图片描述

S→id:=E S.code:=E.code || gen(id.place ‘:=’ E.place)
在这里插入图片描述
整个赋值语句的三地址代码是由赋值号右边的E语法单位也就是表达式它翻译成的三地址代码再加上gen函数生成的赋值指令构成。
gen函数是将输入的各个参数拼接成一个三地址代码,这里有三个参数,一个是变量在符号表的入口id.place,然后赋值号,然后存放E的值的单元的名字/地址,这样gen函数会根据这三个参数,生成三地址指令,这里说明一下,由于可以通过名字串找到标识符在符号表中的入口,进而找到对应的地址,所以在三地址指令中,要引用一个单元,我们可以用名字,地址,甚至是符号表中的入口,都是一样的,以后就不再区分了,这是第一条规则。

在这里插入图片描述
对于E→E1+E2这条规则,语义规则如上:
先产生一个名词变量newtemp,将来这个名词变量用来存放+运算的结果,我们把该名词变量的名字记录在E.place这个属性中,注意E.place表示存放E值的单元或者是名字或者是地址,那么E的三地址代码是怎么构造出来的,语义规则是这样说的:整个大的+法表达式的三地址代码是由加法前面的子表达式E1翻译出来的三地址代码在连接上+后面的子表达式E2所翻译出来的三地址代码连接,再加上一个赋值指令构成,注意gen函数产生的是一个+法赋值指令,赋值号的左边是刚才分配的这个临时单元的名字或者地址,右边有一个+,+号左边是E1.place,E1.place里面就是说E1这个子表达式的值的存放的单元或者是名字,E2.place是说+后面表达式所计算出来的值的存放的单元或者是名字,或者是地址。
gen函数就利用这几个参数产生一个+赋值指令,这三部分连接起来,就是整个E的代码,E1.code 计算+前面的子表达式,E2.code 计算+后面的子表达式,gen(E.place ‘:=’ E1.place ‘+’ E2.place)这条指令负责将两条结果加起来送给结果单元,这就是这个+法规则的语义描述

在这里插入图片描述
同理E→E1*E2这条语义规则与E→E1+E2类似

对于E→-E1这个指令
首先也是产生这个临时单元newtemp用来存放求-的结果,然后再E1代码后面再加上一个求负赋值指令
在这里插入图片描述

对于E→ (E1)这种形式,它的语义规则说的是E.place:=E1.place; E.code:=E1.code
说的是整个的代括号的表达式E他所对应的计算结果的存放单元就是括号里面的这个子表达式的结果单元,那么带括号的这个表达式的三地址代码序列就是括号内的这个子表达式所翻译出来的三地址代码序列

最后
在这里插入图片描述
说的是E的结果单元就是变量对应的单元,单独的一个变量所对构成的表达式本身没有对应的三地址代码

这样我们就得到了一个为赋值语句生成三地址代码的S-属性文法,其中,这里面的code,place都是综合属性。

在这里插入图片描述
下面我们对照语义规则看看各产生的语义规则怎么设计:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

首先通过lookup函数在符号表中查找赋值号左边的id在符号表中是否出现,如果找到了那么lookup就会返回该表示符在符号表中的入口,此时p就不会为nil空,那么就产生一条三地址指令送到输出文件中调emit,此时调emit的时候如果被定义值的变量id这个标识符它真正的标识符是a(p是a)的话,赋值号右边的表达式E计算出来的结果存放的单元比如(E.place记录的是T单元的名字)实际上发射出来的指令就是a:=T(a赋值为T)这样一条三地址代码送到输出文件当中,如果lookup在查找赋值号左边的标识符的时候,没有找到该标识符,说明这个标识符还没有什么,属于未定义表示符就被使用了,就报错。
这是第一条语义规则的动作。

对于
在这里插入图片描述
在这里插入图片描述
首先产生一个临时变量newtemp,将来这个粮食变量就用来存放+运算结果,把该临时变量的名字、地址记录在E.place属性中,然后在生成一条加法赋值的三地址指令,这个赋值指令以用来存放E1/E2单元的地址进行加法运算,结果结果单元就是我们公共生成的这个临时变量,如果刚刚生成的临时变量的名字是T的话,赋值号右边E1和E2这两个子表达式的计算结果的存储单元分别对应的是T1和T2的话,那么emit实际上发出的指令就是T:=T1+T2这种形式。T被赋值为T1+T2,发出的这样一条指令送到输出文件当中,注意,当用E1和E2归约的时候,E1和E2对应的三地址代码已经翻译好了,输出到了输出文件中,现在只要在输出文件中再加一条+赋值指令,也就是emit生成的+赋值指令,那么就完成了这个产生式的语义动作。

在这里插入图片描述
它的语义动作也类似
在这里插入图片描述
先分配一个临时单元,然后再分配一条乘法赋值指令
注意E.place存放的是整个乘法表达式计算结果的存放单元的名字/地址
在这里插入图片描述

对于
在这里插入图片描述
它的语义动作,先分配一个临时单元,然后交给place,将来这个临时单元用来存放求负这个运算的结果,我们把该临时变量的名字/地址存放在E.place里面。然后再生成一条求负赋值指令,这个求负赋值指令引用存放E1的单元的名字/地址进行求负运算,结果就存放在刚刚分配好的临时单元,临时变量当中。
如果刚刚分配的临时变量是T而赋值号右边的E1子表达式,它的计算结果单元是T1,那么实际上发射出来的指令就是T:=-T1

在这里插入图片描述

设计出来的语义动作是
在这里插入图片描述

E1.place用的是那个单元,我E就用哪个单元。
而对于E.code:=E1.code这条语义规则,当我们把(E1)归约到E,E的代码已经翻译完毕,到冲突文件的末尾了,而三地址代码就是新归约得到E的三地址代码,所以不需要生成新的代码,这里的语义动作就只有E.place:=E1.place

最后
在这里插入图片描述
说的是E的结果单元就是变量对应的单元,单独一个变量构成的表达式本身没有对应的三地址代码,所以说它的语义动作
在这里插入图片描述

就是通过调用lookup函数在符号表中查找变量标识符id,如果找到了lookup就会返回该标识符的入口,此时p就不空,那么就将id对应的单元或者是地址用E.place保存,这里是将p交给E.place保存。
如果lookup在符号表中,查找id找不到,就说明这个标识符没有定义,就需要报错了。

再总览一下

在这里插入图片描述
在这里插入图片描述

7.3.2 数组元素的引用

在这里插入图片描述

编译程序完成了把数组元素的引用,转换成对存储单元的访问这一工作,编译程序会对数组名和下标表达式列表进行分析和翻译,将目标程序/中间语言程序中插入计算出要访问的数组元素的地址的代码,这样,将来在实际执行目标代码的时候,就会执行插入的地址计算代码,从而得到数组元素的地址,进而按照该地址完成存储单元的访问。

下面我们来学习,带有数组元素引用的赋值语句的翻译:
在这里插入图片描述
蓝色的不变信息在数组声明的时候就确定了,因此在编译的时候处理完变量,在数组变量的声明语句就可以把蓝色部分计算出来,蓝色部分不变的值对于该数组的所有数组元素的引用都可以直接使用不变部分的计算结果。

可变部分随着数组元素的下标表达式的不同而不同,但是总可以按照红色部分公式按照相同的模式来计算,就是说可以对下标表达式列表进行分析,一遍分析,一遍累计计算公式中的可变部分,也就是红色部分。

在下面的处理中,我们进一步将蓝色不变部分分成两组,一部分是base,第二个和数组声明的维数,各维的上下界,元素占用空间的大小相关,但和数组元素引用的信息无关,我们把这一部分记为C,原来部分就是Base-C
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
首先我们给出语法规则,在赋值语句中,我们让允许标识符出现的地方都允许出现数组元素的引用,我们把标识符对应的简单变量和数组元素对应的代表的下标变量都统一为L,L可以由标识符来充当也就是简单变量,也可以由一个标识符加一个表达式列表Elist对应的是表达式列表,放在方括号中间,作为下标表达式列表,那么这个叫做数组元素的引用,我们有时候也叫做下标变量。

为了便于按照数组元素的公式进行计算,我们这个公式特别适合于从左到右不断累积运算,累计计算红色部分,对刚才语法规则进一步改造
L定义为Elist|id
Elist定义为Elist,E|id[E
这么改造的目的是为了方便红色部分的计算,使得我们每分析完一维下标表达式,也就得到了一个新的E,我就做一次归约,就扩展计算红色公式的一部分,下回Elist又有一个,E的时候,再做一次归约,根据刚刚计算出来的E,再扩展计算红色部分,那么Elist的第二个候选,就代表数组元素引用的开始,并且分析完了第一维的表达式E,L是当Elist碰到右括号,表示数组元素引用的结束。
在这里插入图片描述

在这里插入图片描述

因为我们要从左到右分析下标表达式列表,就要知道目前处理到第几维,我们用Elist.ndim来做计数器记录我们处理到了第几维。

Elist.place:保存临时变量的名字,这些临时变量存放已形成的Elist中的下标表达式计算出来的值

Elist.array:记录数组名查找数组相关的信息时就是用数组名来查找

在这里插入图片描述
举例:这里有8个产生式,前面的产生式都是原来简单的算数表达式或者赋值语句,L代表下标变量和简单变量,Elist代表下标表达式列表,下面我们将为每个产生式配上合适的语义动作,就可以得到翻译模式,这个翻译模式适合于自上而下的翻译模式结合在一起通过一遍扫描完成语法分析和翻译

在这里插入图片描述
如果L.offset不等于null,也就是L是个下标变量是个数组元素的引用,那么也生成一条三地址指令
在这里插入图片描述
赋值号的左边是个变值访问,跟刚刚是个变量的名字/地址不一样,是个变值访问,它是利用L.place记录的地址计算的不变部分和L.offset记录的可变部分做访问,访问到的单元就是接受E的结果的数组元素所对应的存储单元。

在这里插入图片描述
动作和原来简单表达式的一样的:
首先产生一个名词变量用来存放+运算结果,将该名词/变量的结果记录在E.place里面
然后再生成一条加法赋值三地址指令,这个赋值指令引用存放E1和E2的值的单元的名字或者是地址,将他们两个做加法,结果交给刚刚生成的临时变量E.place

在这里插入图片描述
动作也和原来一样

E对应的计算结果单元就是E1的结果单元,
E对应的三地址代码序列就是E1的三地址代码序列
不需要增加新的代码,只需要记录E.place:=E1.place

在这里插入图片描述
这个动作就要区分L是简单变量还是下标变量,要做分别处理

如果L是一个简单变量,E的存放结果的单元E.place就是L.place中保留的变量的名字或者是地址或者是简单变量在这个符号表中的入口,E.place就记录成L.place的值

如果L是一个下标变量,那么也先分配一个临时单元,交给E.place然后产生一个赋值指令,注意:赋值号的右边是一个变值访问,利用L.place记录的地址计算不变部分和L.offset记录的地址计算的可变部分做变值访问,访问的单元里面的内容送到刚刚分配给E的临时单元里面。

下面讲一下Elist单元所对应的语义动作
在这里插入图片描述

在这里插入图片描述
当用这个产生式做归约的时候,应该处理到了第一维的向量表达式
在这里插入图片描述
因此可以先将可变部分红色部分那里的计算算一下,因此它的语义动作就是把存放第一维计算结果的单元信息存放到Elist.place里面,这里回顾一下:Elist代表的是从左到右已经分析完的下标列表,Elist.place里面记录了根据分析完的下标列表,计算出来的可变部分前一阶段的值,这个值总是随着下标列表的不断的识别与分析,从左到右逐步的做乘法加法,再做乘法加法累计计算出来,我们总是把Elist前面的值乘上这一维的个数再加上这一维的表达式的值,按照这种模式,不断的像滚雪球一样,把可变部分的值计算出来,这是Elist.place的语义

然后将下标表达式的个数计数器Elist.ndim置上1,表示当前已经处理完了第一维,最后把数组名标识符id的信息记录在Elist.array里面,因为后面的下标表达式的翻译还需要根据数组名查询各维的信息,所以数组名这个符号归约完了之后,从栈里面弹出去了,我们要把数组名这个信息放到新规约后的Elist这个符号的array属性里面,让他代下去,这是数组元素饭呢西刚开始时候的语义动作。

随着分析的进行
在这里插入图片描述

假设处理到了中间的某一维,第m维
在这里插入图片描述

这个时候,前面已经分析完的结果都在Elist1里面,M分析完了后归约到了E,现在把Elist1,归约到Elist,那么这个产生式就表明继续识别后面各维的下标表达式,前面识别的若干维下标表达式都归约在了Elist1里面,现在又有一维表达式已经识别出来,记为E,那么对应到的公式,我们分析到了这个地方在这里插入图片描述
前面的计算结果都有了,那么应该乘上这个E所在的第m维的长度再加上这个E自身的值,就得到了可变部分最新的结果,所以说语义动作是,首先分配一个临时变量t=newtemp,这个变量就是用来记录地址计算的可变部分的最新的值,下面生成的几条指令,就是计算可变部分,首先 m=Elist1.ndim+1根据下标计数器ndim计算出当前归约得到的E的维数,前面Elist有多少维在Elist1.ndim中,+1就是新归约后的E的维数,我们把它存在m变量里面,然后调用emit函数得到这个数组的第m维的个数emit(t':=' Elist1.place'*' limit(Elist1.array,m));也就是第m维的下标个数或者它的长度,我们在这里面引用了Elist1里面的array属性所带来的数组名,根据这个数组名和我要查的现在这个E所对应的第m维,找到这一维的limit返回这一维的长度,这长度值就是nm,limit返回值就是nm,这个
nm乘以Elist1.place(公式里面的紫色部分:已经分析完的表达式的可变部分的计算结果单元),之前都归约成了Elist1,它的计算结果都在Elist1.place里面,也就是这个紫色部分。前面的结果乘上这一维的长度就计算到了nm之后,然后emit(t ‘:=’ t ‘+’ E.place);
这个结果放在t里面,t再加上新归约后的最新的下标表达式的值,它的值在E.place里面,就是Im的值加到t里面去,结果还是给t。这样t对应的临时变量里面就有了地址计算确定可变部分最新的值,这个值就是在分配的临时单元里面。

下面Elist.place=t;
把t交给新归约后的Elist.place,所以说在任何时候Elist.place属性都记录了一个单元的名字或者是地址,这个单元就是Elist所对应的分析完了的下标表达式列表计算出来的可变部分的最新结果

Elist.array=Elist1.array;
Elist.place=t;
Elist.ndim=m
将处理完的维数m更新到Elist.ndim属性中继续传递下去,同时也要把Elist1.array中记录的数组名信息传递给Elist.array这个属性,让它继续往下传递,因为当我把Elist1,E归约到新的Elist这个符号上的时候,Elist1,E都弹出栈了,他们的属性也变成不可访问,因此我必须要把Elist1.array记录的所有数组名传递到新归约后的Elist的属性里面。

随着一维一维的分析,我们最终总会碰到,把最后一维分析完,碰到右括号,这个时候用到第5条规则:
在这里插入图片描述

此时应该采取的语义动作是,先分配一个临时单元交给L.place记录下来,前面我们说过,L.place里面放的是不变部分的名词变量名字或者是地址,这里就是给他分配一个临时单元,下面就要生成计算不变部分的指令,不变部分的指令
在这里插入图片描述
Elist.array数组名,记录了这个base(首地址),而绿色部分,涉及到的是有多少维,每一维的下界上界元素的个数以及元素的宽度,这些信息在数组声明的时候就可以按照绿色的公式给他计算出来,存放到我们编译程序的常量C里面,因此这里发射出来的指令,是Elist.array-C,放到L.place刚刚分配的临时单元里面,这里面就是不变部分的最终结果了。下面处理可变部分,将可变部分分配的临时单元记录在L.offset里面。可变部分计算的大部分工作已经完成了,计算的结果是在Elist.place属性当作,下面产生一个乘法指令,将紫色部分与w相乘给分配的临时单元L.offset中,因此发射的这条指令emit(L.offset ‘:=’ w ‘*’ Elist.place) 就是可变部分最后的计算,最后的结果就在L.offset记录的临时变量当中,这个临时变量就具有了可变部分最终的结果。注意到这,数组元素可变计算的部分和不变部分的计算代码都已经生成完毕。最后在L.place当中指明了存放不变部分的最后结果的临时变量名字,L.place当中记录了存放可变部分的最后结果的临时变量名字

最后还有个L定义为简单变量id,所以就将L.place设置为id.place,L.offset置为空

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7.3 类型转换

在这里插入图片描述
在这里插入图片描述
比之前的语义动作多了类型转换的处理:
在这里插入图片描述

首先建立一个临时变量用来存放加法运算的结果:我们把该临时变量的地址记录在E.place里面,然后检查操作数的类型,根据操作数的类型,来确定结果的类型,生成相应的代码,如果要进行类型转换,就需要添加类型转换相应的代码,此外还要注意,生成的代码中的运算符也要带上类型信息,具体看下如何根据E1,E2的类型来进行处理,如果E1是整型,E2也是整型,那么无需进行类型转换,直接产生整型+和赋值的指令,结果也是整型.

如果E1是实型,E2也是实型,那么无需进行类型转换,直接产生实型+和赋值的指令,结果也是实型.

复杂一点的是,如果E1是实型,E2是整型,那么需要对E2进行类型转换,先产生将E2的结果转换成实型,并且存放到刚刚分配的的临时单元的指令,再产生一个实型+赋值的指令,结果是实型.

如果上述四种情况均不满足就是类型错误。

在这里插入图片描述

7.4 布尔表达式的翻译

布尔表达式的两个基本作用:

  • 用于逻辑演算,计算逻辑值;
  • 用于控制语句的条件式.

产生布尔表达式的文法:
E→E or E | E andE | not E | (E) | i rop i | i
在这里插入图片描述

计算布尔表达式的两种方法

  • 数值表示法
  • 带优化的翻译法

在这里插入图片描述
在这里插入图片描述

7.4.1 数值表示法

在这里插入图片描述在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

关于布尔表达式的数值表示法的翻译模式

在这里插入图片描述
在这里插入图片描述

分配一个临时单元给or运算结果存放的地方,交给E.place属性记录。注意每个E都对应一个子布尔表达式,这个子布尔表达式的计算结果一定在一个存储单元中,这个存储的单元的地址就存放在E.place里面,然后在输出文件中发送一条这样的指令,一条定制指令,左边是刚建立的临时变量,右边是or连接的两个单元的地址,左边操作数的地址是E1.place,右边操作的地址在E2.place的两个语义变量里面。刚刚我们说了E1.place里面是两个临时单元的地址,放的是E1这一部分的子表达式的计算结果单元,E2.place里面这个单元是E2所对应的bool表达式的计算结果单元

我们可以看一下,如果我们对应的是一个自上而下的分析,当用产生式E → E1 or E2 做归约的时候,栈里面已经有了E1,E2这两个符号,这两个符号都已经分析完了,也翻译完了,那么E1所对应的代码和E2所对应的代码已经发送到了输出文件当中,而且E1.place里面,假设我们记录的临时单元是T1,E2.place里面记录的是T2,这个时候,我们要把E1和E2用产生式归约成E,那么我们就要执行刚刚说的语义动作,第一个产生一个临时单元E.place=T,然后第二个动作就发射一条指令,注意,这个E.place=T,E1.place=T1,E2.place=T2,所以说发射这一条指令,就是这种形式:T:=T1 or T2
这就是或运算产生式对应的语义动作。

类似的给出其他产生式的语义动作:
在这里插入图片描述

前面给出了翻译运算的例子,每个翻译运算给出了四条指令,现在对照这个例子我们设计语义动作来翻译这些指令:注意我们现在要写语义子程序,能够把关系运算翻译成四条指令。
在这里插入图片描述

在这里插入图片描述

如果左操作数和由操作数存在这样一个关系运算的话,goto nextstat+3
在这里插入图片描述

把0给刚刚生成的临时变量,也就是条件不满足

在这里插入图片描述

跳过goto语句的下一条指令,到达下下条指令
在这里插入图片描述

在这里插入图片描述

E→id 这样的表达式无需生成计算指令,甚至无需为其准备存放结果的临时单元,因为bool变量id的值就是整个构成的bool表达式E的值,所以bool表达式E的结果存放单元就是变量id的存储单元。

举例

将布尔表达式a<b or c<d and e<f翻译成三地址指令
在这里插入图片描述

首先将a<b按照关系运算到bool表达式的定义归约为E,就会执行,你用这个产生式E→id1 relop id2 做规约就执行相应的语义动作。首先产生一个临时单元T1就记录在E.replace里面,然后连续发送四条指令,假设我们指令的编号是从100开始,发送的第一条指令if a<b goto 103
在这里插入图片描述

接着把or移进来,把c<d移进来,对他们进行相应的归约到E,又用这个产生式做归约,归约就得执行相应的语义动作,给他分配临时单元T2就记录在E.replace里面,然后连续发送四条指令,我们指令的编号现在是从104开始,发送的第一条指令if a<b goto 107

在这里插入图片描述

接着把and移进来,e<f移进来,归约到E,又用这个产生式做归约,归约就得执行相应的语义动作,给他分配临时单元T3就记录在E.replace里面,然后连续发送四条指令,我们指令的编号现在是从108开始,发送的第一条指令if a<b goto 111,在连续发送三条指令。
在这里插入图片描述

接着这个时候栈里面有E or E and E,这个时候语法分析栈告诉我,得把E and E归约到一个更大的E,用的是最后一个产生式,执行相应的语义动作,产生一个临时单元T4就记录在归约后的E.replace里面,然后发射一条指令,emit(E.place ‘:=’ E1.place ‘and’ E2.place)
在这里插入图片描述

同样,现在栈里面有两个E再加上or的运算,把他们归约为E,用第三条产生式,执行相应的语义动作,产生一个临时单元T5就记录在归约后的E.replace里面,然后发射一条指令,emit(E.place ‘:=’ E1.place ‘or’ E2.place)

7.4.2 作为条件控制的布尔式翻译 (带优化的翻译法)

在这里插入图片描述

如果计算部分子表达式就能确定整个表达式的值的时候,那么其余的表达式就不需要计算了。

如果A是真,整个表达式就是真,就不用再算B了,而数值表达式需要把两个都计算出来。
如果A是假的话,B的结果就是整个布尔表达式的结果。
有带优化,B并不是总是都要算。

如果A成立,B的结果决定了整个布尔表达式的结果,如果A是假的话,B就不用算了,整个逻辑表达式确定为假。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

逻辑或运算 E→E1 or E2

在这里插入图片描述

E有两个属性,E1.true和E.false,分别放着的是E为真的跳转目标标号和E为假的跳转目标标号,如果E的标号都确定了
那么E1为真应该去整个表达式为真的地方。
E1.true:=E.true;

E1为假应该去哪里,E1为假应该跳转到将来要执行E2的代码,所以为E1.false产生一个新标号,将来这个标号就像这里一样要放在E2代码之前
② E1.false:=newlabel;
E.code:=E1.code || gen(E1.false ‘:’) || E2.code

E2.true代表着E2为真的时候应该去整个表达式为真的地方
E2.true:=E.true;
所以E2.true就直接引用E1.true的标号就行了

E2.false应该去整个布尔表达式为假的地方,引用E.false的标号
E2.false:=E.false;

整个或的运算构成更大的这个E的代码应该是由E1的代码或之前的代码和或之后E2的代码拼接而成,注意E2的代码之前放上E1.false的标号,这样就把代码的跳转关系串接起来,gen发射一个标号,形成一个字符串放在输出文件里面,放在E2的开头,E1的结尾

逻辑与运算E→E1 and E2

类似的给出and运算的语义规则
E1为假的时候E2就不用算了,跳到整个bool表达式为假的地方,E1为真的时候倒是要执行E2,E2的结果决定了到底是去整个表达式为真的地方还是去整个表达式为假的地方。
在这里插入图片描述
前四条计算E1和E2的继承属性,最后一条计算新得到的更大的语法单位E的综合属性code:
E1.true:=newlabel;
E1为真去的地方用一个新的标号来标记,将来这个标号放在E2的开头

E1.false:=E.false;
E1为假的去整个bool表达式为假的地方

E2.true:=E.true;
E2为真的去整个bool表达式为真的地方

E2.false:=E.fasle;
E2为假的去整个bool表达式为假的地方

E.code:=E1.code || gen(E1.true ‘:’) || E2.code
整个或的运算构成更大的这个E的代码应该是由E1的代码和E2的代码拼接而成,注意E2的代码之前放上E1.true的标号来标记,这样E1为真的时候,将来引用标号跳转就跳转到了E2的开头

在这里插入图片描述

逻辑非运算E→not E1

E1.true:=E.false;
E1为真去整个布尔表达式为假的目标
E1.false:=E.true;
E1为假去整个布尔表达式为真的目标
E.code:=E1.code
E的代码就是E1的代码

跳转目标互换就执行了逻辑非运算的效果

括号表达式E→ (E1)

E1.true:=E.true;
E1.false:=E.false;
E.code:=E1.code

括号内表达式E1的计算结果就是整个带括号的表达式E的计算结果,所以括号内的表达式E1为真或者是为假要去的目标就是,带括号的整个表达式E为真或者是为假要去的目标,而这些目标分别放在了E.true和E.false里面
在这里插入图片描述

关系运算E→id1 relop id2

现在给出关系运算表达式所对应的语义规则:
E→id1 relop id2
E.code:=gen(‘if ’ id1.place relop.op id2.place ‘goto’ E.true) || gen(‘goto’ E.false)
整个关系运算布尔表达式代码就是两条指令:
第一条,产生为真的跳转,注意,跳转目标引用到的是E的继承属性E.true(条件为真应该去的地方)
否则的话,就会 gotoE.false 从这个出口出去,因此一个关系运算,产生两条指令

常量条件表达式E→true/E→false

直接跳转到真/假
E→true
E.code:=gen(‘goto’ E.true)
E→false
E.code:=gen(‘goto’ E.false)

根据属性文法来翻译布尔表达式的例子

在这里插入图片描述
假设我们已经根据分析建立了这样一颗语法树:
根节点它的两个继承属性E.true和E.false分别置为了Ltrue和Lfalse,把每个E的两个继承属性都给他标注上。
作为属性计算的初始值,根节点继承属性先设置好。

下面根据属性文法,自上而下计算出所有语法单位E的继承属性E.true和E.false
在这里插入图片描述
下面自下而上计算出所有语法单位E的综合属性E.code,这样就可以得到整个表达式的翻译结果了
(所有关于E.code计算规则用下划线标记出来了)
按照这些规则,自下而上的计算出每个E的综合属性E.code
在这里插入图片描述

计算过程
计算橘色E的代码,它根据a<b归约来的,执行E.code:=gen(‘if ’ id1.place relop.op id2.place ‘goto’ E.true) || gen(‘goto’ E.false)
真跳转跳转到E.true=Ltrue,假跳转跳转到E.false=L1

计算蓝色E的代码,它根据c<d归约来的,执行E.code:=gen(‘if ’ id1.place relop.op id2.place ‘goto’ E.true) || gen(‘goto’ E.false)
真跳转跳转到E.true=L2,假跳转跳转到E.false=Lfalse

计算绿色E的代码,它根据e<f归约来的,执行E.code:=gen(‘if ’ id1.place relop.op id2.place ‘goto’ E.true) || gen(‘goto’ E.false)
真跳转跳转到E.true=Ltrue,假跳转跳转到E.false=Lfalse

在这里插入图片描述

这三个E的代码都已经有了,我们现在要按照语义规则拼接出更大的代码比如说先看紫色E的代码,它由蓝色的E和绿色的E构成,用的是E→E1 and E2规则,它的属性计算在E.code:=E1.code || gen(E1.true ‘:’) || E2.code说的是把蓝色E的代码和绿色E的代码拼接起来,但是要把前面E的真标号放到绿色代码之前,真标号是L2,即将L2放在绿色代码之前,然后蓝色代码与绿色代码拼接而成,这样框里面的代码就是紫色E的代码,这就是这条规则的执行效果
在这里插入图片描述

下面根据红色E和紫色E要生成黑色E的代码,那么执行的规则是E.code:=E1.code || gen(E1.true ‘:’) || E2.code说的是把橘色E的代码和紫色E的代码拼接起来,但是要把橘色E的假标号放到紫色代码开头,假标号是L1,即将L1放在紫色代码之前,然后橘色代码与紫色代码拼接而成,这样框里面的代码就是黑E的代码,这就是这条规则的执行效果。

在这里插入图片描述

最后黑色E翻译出来的代码如上,这就是翻译的结果

在这里插入图片描述
下面学习一遍扫描的翻译模式

7.4.3 一遍扫描实现布尔表达式的翻译

在这里插入图片描述

有时,四元式转移地址无法立即知道,我们只好把这个未完成的四元式地址作为E的语义值保存,待机"回填"。

在这里插入图片描述
在这里插入图片描述

对于这个程序片段,只有扫描到or的时候,才能够回填101句的目标填102,不看到or不知道101句目标在哪里。

只有扫描到then的时候,才知道100和102句他们都是条件为真应该去的地方,知道then之后下一个四元式的地址,才能够回填。才能够确定,继续往下走,你只有扫描到else的时候,才知道103句的目标是到了110。

通过分析可以发现,一个表达式分析完了后,无论这个表达式有多复杂,它产生的代码中如果有跳转目标不确定,需要回填的这样的目标的四元式的话,这样的四元式一定可以分为两类

  • 一类是跳转到为真的目标
  • 一类是跳转到为假的目标
    同一类的四元式一旦随着分析的进行,确定目标了,就可以统一回填,这两类四元式,我们要分别进行记录,把这些未完成四元式的地址分成两类记录下来,作为E的语义值进行保存,直到将来机会成熟了我们要回填的时候,就可以从这些语义值中找到需要回填的四元式。完成目标的确定。

总之,一遍扫描实现bool表达式最大的困难就在于产生四元式时,可能转移地址无法立即知道,需要等到以后扫描到特定位置时才能回头确定。因此需要准备一些语义值保存这些四元式。

在这里插入图片描述

链表里面的四元式都是需要回填的,都是需要去为真的那个目标,等到E的为真的目标确定之后,拿着这个链表就可以把他们都回填好。最后的链尾放上0作为标志,所以同一个链表中更大四元式都是需要回填的四元式。而且都是去同一个目标的四元式。

在这里插入图片描述
在这里插入图片描述

  • E → E1or E2
  • E → E1 and E2

为了使得翻译模式和自下而上的语法分析结合起来,我们希望所有的语义动作都放在产生式最右端,使得语义动作执行的时机能够放在产生式进行归约的时候,在这里,为了做到实现布尔表达式的翻译,要实现正确的回填,我们需要对布尔表达式的文法做改进,对上述两个产生式,在E1的分析、翻译过程中,肯定需要产生一些需要回填的四元式,如果我们扫描到的是or的话,这个时刻我们需要知道E1的firstlist里面的四元式要回填的目标就是下面即将要产生的四元式的编号,下面即将要产生的四元式,就是E2的第一个四元式。

如果要扫描到的是and的话,我们就知道E1的truelist里面的需要回填四元式里面的目标,现在是可以确定的,就是即将要开始生成的E2的第一个四元式的下标,所以在E2开始分析前,这样的两个位置,要执行两个语义动作的时机,在这个时刻,应当要记录下来,当时的下一个四元式的地址,也就是即将要分析产生的E2的第一个四元式的地址。如果当时不记录,等到用E1 or E2 ,E1 and E2这三个符号都已经相乘要进行归约的时候,这个时候回头再问你E2的开头的编号,就不知道了,所以说在E2的分析开始之前,要有一个动作,记录下一个四元式的地址。
在这里插入图片描述

在前面翻译模式的设计的时候,我们提到一种方法,通过引入ε产生式,将那些嵌入在产生式中间的动作都放在ε产生式的最右端,使得所有的语义动作都能够出现在产生式末尾。

比如说,这两产生式要在E2之前执行语义动作。
我们在对应的位置上引入M,M定义为ε,然后我们把它的语义动作放在ε后面,这样相当于这些语义动作就在插入M的那个位置上执行的。

这样改造后的布尔表达式文法就变成下图:
在这里插入图片描述
这样的改造是个等价性的改造,下面我们给出一遍扫描的翻译模式,为每个产生式设置合适的语义动作。
在这里插入图片描述

先看看新引入的M→ε的语义动作
(7) M→ε { M.quad:=nextquad }
就是引入下一个四元式的下标(在nextquad里面)
这个时候下一个四元式就是即将要分析产生E2的四元式:
比如说:当M形成的时候,你记录的下一个四元式的下标就是还没有生成但是马上要生成的E2的这一个四元式的下标。

a<b归约到E碰到or,要把ε归约到M,按照第7个产生式做一个这样的归约,做这个归约的目的就是为了获取一个语义动作的时机,把当时的下一个四元式的地址,也就是说,E2的四元式开头的地址记录下来,放在M.code里面,也就是后面c<d第一个四元式的地址就记录在M中,将来c<d归约到E,这个时候E第一个四元式的地址已经记录在M.code属性里面了

or

下面考虑or运算的语义动作:
在这里插入图片描述

当用产生式E→E1 or M E2做归约的时候,or之前和or之后的表达式都已经分析完了,E1的代码和E2的四元式都已经分析好了(假设输出文件朝上生成)而在分析栈中已经有E1 or M E2这样的符号(栈的方向朝上)其中,E1产生代码当中,需要回填的四元式,凡是要调转到条件为真的四元式都记录在E1.truelist里面,凡是要跳转到为假的目标的需要回填的四元式都记录在E1.falselist里面,同理,在E2翻译的四元式当作,那些回填的四元式也都分别组织到了E2的truelist和falselist里面。

or运算的语义如右图所示,根据这个图可以确定E1的falselist中间的那些需要回填的四元式它的目标是可以确定的,因为他们要去E2的开头,也就是E2的第一个四元式,这个四元式的下标已经记录在了M.code属性里面,所以有个语义动作,拿着M.code(E2开头四元式的地址)去回填E1.falselist里面每一个四元式。E1为假的话应该转到E2去执行。

我们还知道E1.truelist和E2的truelist这两个链表中需要回填的四元式原本都要去E1和E2为真要去的地方,但是,我们现在除了知道E1和E2为真需要去的地方是同一个地方外,我们并不知道这个地方到底在哪?因为or这个语义关系告诉我们E1为真E2为真都应该去整个布尔表达式为真的地方,但是这个地方具体在哪,现在不一定知道。既然两个链表的四元式相同,我们就把E1.truelist和E2的truelist通过merge函数合并,合成一个链表,并且把这个链表,交给新生成的E.truelist属性记录,注意合并后的链表中的四元式,是需要回填的,都需要去整个布尔表达式E为真应该去的地方。

最后还有E2.falselist没有处理,这个里面套跳转到E2为假四元式的地方,现在还不能确定具体地址,但是可以把E2为假的地方就是整个布尔表达式为假应该去的地方,应该把E2.falselist交给E.falselist保留下来,如果不保留的话,归约后分析栈就没有E1,E2这两个符号了,只有新归约后的E,所以这些需要记录的信息一定要交给新归约后的分析栈的E,由它属性记录下来,而且从or语义来看E2为假的地方就是整个布尔表达式为假应该去的地方,要等到后面合适的时候再回填。所以将E2.falselist交给E.falselist

and

下面考虑and运算的语义动作:
在这里插入图片描述
E1为真应该去E2的开始,所以要拿着M code(E2的第一个四元式的地址去回填E1.truelist),
E2为真时去整个布尔表达式为真的目标,但是具体是哪,现在还不能确定,所以把E2.truelist链表交给新归约后的E.truelist做记录,表明这些布尔表达式都是需要回填的,都是需要去整个布尔表达式为真的地方

最后,对于E1.falselist和E2的falselist这两个链表中需要回填的四元式原本都要去E1和E2为假要去的地方,但是,我们现在除了知道E1和E2为假需要去的地方是同一个地方外,我们并不知道这个地方到底在哪?但从and这个语义关系告诉我们E1为真E2为假都应该去整个布尔表达式为真的地方,但是这个地方具体在哪,现在不一定知道。既然两个链表的四元式相同,我们就把E1.falselist和E2的falselist通过merge函数合并,合成一个链表,并且把这个链表,交给新生成的E.falselist属性记录,注意合并后的链表中的四元式,是需要回填的,都需要去整个布尔表达式E为真应该去的地方。

对于E→not E1

在这里插入图片描述

E1为假要去的目标就是整个布尔表达式E为真要去的目标,E1为真要去的目标就是整个布尔表达式E为假要去的目标。
但是如果这些跳转目标当我们在分析E1的时候,并不能够确定,而且碰到not之后也不能够确定,所以
E.truelist交给E1.falselist保存
E.falselist交给E1.truelist保存
两个链表实行交换,这两个链表就体现了逻辑的非

E→(E1)

在这里插入图片描述

E1为假要去的目标就是整个布尔表达式E为假要去的目标,E1为真要去的目标就是整个布尔表达式E为真要去的目标。
但是如果这些跳转目标当我们在分析E1的时候,并不能够确定,而且碰到not之后也不能够确定,所以
E.truelist交给E1.truelist保存
E.falselist交给E1.falselist保存

关系表达式构成的布尔表达式E→id1 relop id2

在这里插入图片描述

语义动作
{ E.truelist:=makelist(nextquad);
E.falselist:=makelist(nextquad+1);
emit(‘j’ relop.op ‘,’ id 1.place ‘,’ id 2.place‘,’ ‘0’);
emit(‘j, -, -, 0’) }
前面看到关系运算的例子,每个关系运算对应两条指令,一条为真跳转,一条为假跳转。
而当我们生成这两条指令时,他们的跳转目标在当时都是无法确定的,所以我们都把它放到了列表里面,交给了E.truelist和 E.falselist保存,等待合适时机回填。
所以关系运算语义动作:
{ E.truelist:=makelist(nextquad);
E.falselist:=makelist(nextquad+1);
emit(‘j’ relop.op ‘,’ id 1.place ‘,’ id 2.place‘,’ ‘0’);
emit(‘j, -, -, 0’) }
创建两个链表下一个四元式构成一个链表,下下一个四元式也构成一个链表,由E.truelist和 E.falselist保存,下面产生的四元式,第一个发射出去的是真跳转,第二个发射出去的是假跳转。第四区间都是0代表需要回填的链表的链尾,这样产生的两条四元式,它的编号都放在了E.truelist和 E.falselist里面,这个是关系运算所对应的语义动作。

由一个逻辑变量构成的条件表达式E→id

在这里插入图片描述

如果条件表达式由一个逻辑变量来构成,它的语义就是直接根据这个逻辑变量中的真假值来跳转,也就是生成两条需要回填的四元式分别交给了E.truelist和 E.falselist保存,然后产生的两条指令是为真跳转和为假跳转。

在这里插入图片描述
下面对照一遍扫描的翻译模式,一遍进行语法分析,一遍进行四元式的生成(放在框框里)
在这里插入图片描述

首先把a<b放到栈里面,然后归约到E,既然用了这个产生式做规约,就执行相应的语义动作,假设下一个四元式编号是100,他就会拿着100和101建立两个链表,放在E.truelist和 E.falselist里面(我们将这两个属性打上括号,放在这两个边上,左边是E.truelist右边是 E.falselist)第三条动作说的是发射一条指令发生真跳转,第四条发射一条指令发生假跳转

在这里插入图片描述

然后把or移到栈里面
记住文法是改造过的,这个时候应该把ε归约成M:
语义动作是说要把下一个四元式首地址记录在M.code,下一个四元式编号是102,所以M.code=102放在M边上
在这里插入图片描述

下面c<d移进来c<d归约到E,同样执行四条子程序,创建两个链表分别是102(真跳转),103(假跳转),都是需要回填的
在这里插入图片描述

下面 and移进来,记住文法是改造过的,这个时候应该把ε归约成M:
语义动作是说要把下一个四元式首地址记录在M.code,下一个四元式编号是104,所以M.code=104放在M边上
在这里插入图片描述

下面e<f 移进来归约到E,同样执行相应的语义动作,创建两个链表分别是104(真跳转),105(假跳转),都是需要回填的.

在这里插入图片描述

下面栈里面出现了E or M E and M E 下面栈顶的E and M E要把它归约成E,运用产生式E→E1 and M E2执行相应的语义动作,
要拿着M记录的绿色E的开头104,也就是e<f的第一条四元式,去回填前面E的真出口,这是第一条程序,所以要把104填在102的第四渠道

第二条说的是E.truelist:=E2.truelist=104,现在104还不能回填,将104交给新出现的紫色的E,等待以后回填。
发现前面蓝色E103句和后面E的105句都是要去整个布尔表达式为假的地方,现在还不能确定在哪,但是103和105是确定同一个地方,因此把103和105合并成一个链,把103挂在105的后面,然后把105作为链头,保留在紫色的E的falselist里面E.falselist:=merge(E1.falselist,E2.falselist)
在这里插入图片描述

现在栈里面剩下E or M E,将其归约成E,用产生式E→E1 or M E2执行相应的语义动作:
首先,拿着M记录的紫色的E的开头102句回填红色E的flaselist,拿着102回填101的第四区段,然后发现前面红色E的truelist100句和后面紫色E的truelist104句,100到104句都是整个布尔表达式为真应该去的地方。现在我们知道它去同一个地方,但是去哪我们不知道。因此我们把它合成一个链,把这个链交给新归约后的黑色E.truelist保存,现在的工作就是把100和104拼成一个链,把100挂在104的下面,然后104作为链首交给新归约后的E黑色E.truelist保存

下面紫色E里面的105句,实际上不止105句,105句后面还有103句,105是个链头,它链里面的两条语句都需要回填,都是去整个布尔表达式为假应该去的地方,所以说交给新归约后的E的flaselist把它保存,等待以后回填,所以说,当整个布尔表达式都已经形成完了时候,它翻译出来代码当中,有两位需要回填的四元式,分别在truelist和falselist里面,其中truelist是104,104又指向了100,falselist指向105,105又指向了103,这就是这两个链里面的四元式,都得等到分析到包含布尔表达式更大的语法单位的时候,合适的时机才能够回填。这四个四元式要等到更大的语法单位,比如说,使用这个布尔表达式的if then else或者是其他的控制语句,到那个里面去回填

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

7.5 控制语句的翻译

在这里插入图片描述

if-then语句

在这里插入图片描述

计算E条件表达式,如果为真的话执行S1的代码,如果为假跳过then后继语句

为这个产生式设计相应的语句计算规则:
在这里插入图片描述

首先给E.true生成一个标号newlabel放到E.true里面,将来这个标号应该放到S1代码的开头的位置,标识条件为真时,跳转的位置。然后E.false也是标号,他应该放E为假的跳转目标(整个if then语句的后继语句)

if then的后继语句,也就是S的后继语句的标号,我们假设是放在父节点S的next属性里面,S.next也是继承属性,他存放了S的后继语句的标号,那么这里就声明E为假的位置就是S的后继语句

第三条规则说的是,then语句的S1它的后继语句的标号就是父节点带过来的整个if-then语句的后继语句的标号。

S.next就是整个if-then后继语句的标号就交给S1.next,S1执行完了后S1的后继就是if then的后继。

最后一条规则是来生成代码,整个if then语句的代码由E的代码和S1的代码拼接而成,但是注意要在S1代码的开头放上E.true刚刚生成的标号,这样的字符串拼接起来就是整个if then语句的代码。

上面是单分支语句的语义以及属性计算规则,下面是双分支语句:
在这里插入图片描述

S→if E then S1 else S2
语法是这样定义的:
在这里插入图片描述

如果E为真,执行S1的代码,执行S1代码之后,跳过S2代码。转去执行整个if then else语句的后继语句。

如果E为假,那么则应该跳过S1转去执行S2的代码,S2执行完了之后,就顺序执行下来了,也就是继续执行整个if then else语句的后继语句,注意:在S1,S2代码之间,一定要有goto S.next这样一条无条件跳转指令。S.next标号放在整个if then else语句的后继语句上

给产生式配上相应的计算规则:
给E.true和E.false都分配一个新标号,分别放在S1代码的开头,E.false也得声明一个标号,将来这个标号得放在S2语句的开头,E的两个属性已经计算完了,下面得处理S1.next属性,S1.next语句指的是S的后继语句的标号,把S1放在if then else大环境里面,S1的后继语句就是整个if then else的后继语句,而整个if then else的后继语句标号是放在整个if then else所对应的S的next属性里面。

所以把S.next标号也赋值一份给S1.next和S2.next

最后生成整个代码:
E的代码连接上S1的代码,S1代码之前要放上E.true刚刚生成的标号,然后要产生黑色的goto S.next指令,下面该连上S2代码。S2代码要放上E.false标号,这样几段代码拼接出来的字符串,就是整个if then else的代码。

在这里插入图片描述
在这里插入图片描述

首先计算E,E为真执行S1然后强制跳到循环投,再次测试,再次执行,直到E为假

E的代码开头准备一个标号,这个标号放在S.begin里面,然后对E计算两个继承属性E.true和E.false,分别为其分配新标号,放在S1开头和S.next(整个while do语句的后继语句)整个while do语句的后继语句),S1.next置为S.begin,S1的后继就是S的开头。(回到下一个循环的开头处)

最后拼接得到整个语句代码S.code:=gen(S.begin ‘:’) || E.code || gen(E.true ‘:’) || S1.code || gen(‘goto’ S.begin)

在这里插入图片描述

根据属性文法翻译控制语句

假设采用某种分析方法构造语法树如下
在这里插入图片描述

现根据while do计算红色S的属性
设置根节点的继承属性Lnext
S.begin给他一个newlabel=L1

E.true分配新标号L2
E.false:=S.next=Lnext

对于第二个黑色的S不是个while语句,是个if语句,没有begin属性,但有next属性(父节点的begin),S1.next:=S.begin=L1

在这里插入图片描述
所有继承属性都已经计算完了
接着为这些表达式或者语句生成代码:
在这里插入图片描述

首先根据关系表达式:
产生两条指令:
当a<b 去E的真标号L2
再产生无条件跳转到E的假标号Lnext

后面关系表达式紫色E同理

对于绿色的赋值语句和算数表达式表达式
先把y+z给临时变量T1,T1再给x

褐色把y-z给临时变量T2,T2再给x

对于黑色S代码:
在这里插入图片描述

前面放上E.code紫色代码
在S1之前放上E.true=L3标号
在S2之前放上E.false=L4标号
他们两个代码之间生成一个跳转S.next=L1

下面根据
while do语法将E和S1代码拼接成整个while do语句的代码。
在这里插入图片描述

在E之前放上S.begin=L1标号
在S1之前放上E.true=L2标号
他们两个代码之间生成一个跳转S.begin=L1

这是多遍扫描过程,先建立语法树,再自上而下计算继承属性,再自下而上计算综合属性生成代码

下面设计一个一遍扫描的翻译模式:使语义分析和语法分析一遍完成。

在这里插入图片描述
考虑if语句的两种情况:单分支和双分支
对于条件式E他所翻译的四元式中有些需要回填,需要回填的四元式都集中在E.truelist和E.falselist这两个列表中。如果扫描到了then,这个时候我们就知道前面的E的truelist中的需要回填的四元式的跳转目标就是下一个即将要产生的S1的第一个四元式,如果扫描到else就知道,前面E的falselist中需要回填的四元式它的跳转目标就是下一个即将要产生的S2的第一个四元式。

所以当扫描到then扫描到else的时候就是要回填E.truelist和E.falselist的时候。所以在S1,S2开始分析之前,要记录下当时的下一个四元式S1,和S2第一个四元式的地址。

为此与布尔表达式的一遍扫描的翻译模式类似,我们引入ε产生式,将嵌入在产生式中的动作放在ε产生式动作最右端,使得语义动作都出现在产生式末尾。

在这里插入图片描述
M就是语义动作记录下一个四元式下标
N的动作就是产生S1,和S2之间的跳转指令,使得S1执行完之后能够跳过S2转去执行if then else后继语句,但这个时候我们并不知到if then else后继语句的具体地址,所以产生一个回填链表,这个链表交给新归约回填的N的nextlist属性保存,等到以后合适时机回填,这个链表里面就一个四元式,产生无条件跳转,目标不知道。
注意:N.nextlist的四元式都是需要回填的,跳转到if then else的后继语句的四元式。
在这里插入图片描述

if then语句的回填动作:
当用这个产生式生成语义动作时,E的代码和S1代码都已经生成了,前面我们在将布尔表达式翻译时提到过,在E产生代码中,需要回填的四元式凡是跳转到为真的目标的都记录在E.truelist里面,凡是跳转到为假的目标的都记录在E.falselist里面。这两个链表要等到分析包含布尔表达式的更大的语法单位时,才能回填,现在分析的控制语句,if then语句就是包含bool表达式的更大的语法单位,就是回填这两个链表的时机。

根据语义图 E.truelist里面的跳转目标可以确定了就是S1的开头,而S1开头四元式的地址我们已经记录在M.quad属性里面。所以对应的语义动作是拿着M记录的S1的第一个四元式的地址,回填E.truelist,而E.falselist链表中,那些跳转条件为假的目标的四元式此时还不能确定他的目标,但是我们知道,对于if then语句来说,条件为假,跳转目标应该是整个if then语句的后继语句。
而S1也有个属性nextlist(里面也是需要回填的四元式,都是要跳到S1的后继语句的四元式)S1的后继语句实际上就是整个if then的后继语句。所以把E.falselist和S.nextlist这两个链表四元式合并成一个链表。因为他们都是要去整个if then的后继语句,我们把它合并成一个链表。存放到S.nextlist里面,注意:S.nextlist这个属性的意义就是保存S翻译代码中需要生成的需要跳转到S的后继语句的那些四元式

if then else语句的回填动作:
当用这个产生式生成语义动作时,E的代码和S1代码S2代码甚至N的代码都已经生成了,在E产生代码中,需要回填的四元式凡是跳转到为真的目标的都记录在E.truelist里面,凡是跳转到为假的目标的都记录在E.falselist里面。根据图可以确定,此时E.truelist里面四元式都可以回填了,应该回填S1的开头,而S1开头四元式的地址我们已经记录在M.quad属性里面。所以对应的语义动作是拿着M1记录的S1的第一个四元式的地址,回填E.truelist,同样我们拿着拿着M1记录的S2的第一个四元式的地址,回填E.falselist链表。

而S1也有个属性nextlist(里面也是需要回填的四元式,都是要跳到S1的后继语句的四元式)S1的后继语句实际上就是整个if then的后继语句。所以把E.falselist和S.nextlist这两个链表四元式合并成一个链表。因为他们都是要去整个if then的后继语句,我们把它合并成一个链表。存放到S.nextlist里面,注意:S.nextlist这个属性的意义就是保存S翻译代码中需要生成的需要跳转到S的后继语句的那些四元式

S1,S2,N这三个符号里面他们产生四元式中可能都有需要回填的四元式,这些四元式都是要跳转到S1后继,S2后继以及整个S的后继,在这个语句里面都是整个if then else语句的后继,所以将三者S1.nextlist N.nextlist S2.nextlist 合并,保存在S.nextlist。

while do语句的一遍扫描
在这里插入图片描述

为了方便计算,我们需要记录E和S第一个四元式的地址S.begin和E.true,在E和S1的开头要做语义动作记录讲讲要生成的下一个四元式的地址,所以在E,S1引入非终结符M1,M2.

给出这两个产生式的语义动作:
先给出ε产生式语义动作:记录下一个四元式的下标M.quad:=nextquad 也就是马上要分析和翻译的E和S1的开头第一个四元式的下标。

当用这个产生式归约生成S时,E的代码和S1代码的代码都已经生成了,在E产生代码中,需要回填的四元式凡是跳转到为真的目标的都记录在E.truelist里面,凡是跳转到为假的目标的都记录在E.falselist里面。

根据图可以确定,此时E.truelist里面四元式都可以回填了,应该回填S1的开头,而S1开头四元式的地址我们已经记录在M2.quad属性里面。所以对应的语义动作backpatch(E.truelist, M2.quad);是拿着M2记录的S1的第一个四元式的地址,回填E.truelist,

另外S1代码中也有需要回填的四元式,他们都记录在S1.next属性中,保存了S1翻译时代码中需要回填的要跳转到S1后继语句的四元式,S1后继语句就是循环条件E的开头第一个四元式,这个四元式的下标记录在M1.quad里面,所以对应的语义动作backpatch(S1.nextlist, M1.quad);是拿着M1记录的E的第一个四元式的地址,回填S1.truelist,

E.falselist跳转到while do后续语句,保存下来等待以后回填,放在新归约后的S.nextlist里S.nextlist:=E.falselist

最后生成无条件跳转指令,跳转到M1记录的E的开头第一个四元式emit(‘j,-,-,’ M1.quad)

复合语句的一遍扫描

在这里插入图片描述

一遍扫描翻译控制语句示例

while do 语句 if then 语句 布尔表达式以及算数表达式和赋值语句的一遍扫描的翻译模式。
在这里插入图片描述

首先把while移到栈里面相关的翻译模式我们列在左上角。while完了后我们有个ε归约。ε归约成M1,做的动作就是归约成下一个四元式的地址。假设下一个四元式地址是100,记录在M1.quad里面,我们把M1.quad属性放在他的边上标注出来

然后开始循环控制条件a<b的分析:
在这里插入图片描述

关系运算构成bool表达式,生成两个需要回填的四元式
把两个需要回填的四元式记录在两个链表E.truelist和E.falselist里面,将这两个属性标注在E的两边。
在这里插入图片描述

把do移进来,do完了之后要做一个ε归约成M2,做的动作就是归约成下一个四元式的地址。假设下一个四元式地址是102,记录在M2.quad里面,我们把M2.quad属性放在他的边上标注出来

在这里插入图片描述

然后接着开始if语句的分析,把if移进来,c<d归约成E,关系运算构成bool表达式,生成两个需要回填的四元式102,103,把两个需要回填的四元式记录在两个链表E.truelist和E.falselist里面,将这两个属性标注在E的两边。

在这里插入图片描述

then移进来,下面在then之前需要进行ε归约成M,做的动作就是归约成下一个四元式的地址。下一个四元式地址是104,记录在M.quad里面,我们把M.quad属性放在他的边上标注出来
在这里插入图片描述

下面进行算数表达式和赋值语句的语义动作,下面把y+z归约到E,以及E+E归约到E,执行相应的语义动作E→E1+E2 { E.place:=newtemp; emit(E.place ‘:=’ E1.place ‘+’ E2.place)}把y+z的结果送到刚分配的临时单元T里面
在这里插入图片描述

然后他们归约成赋值语句S,赋值语句S也要产生一个定值指令,把T给x
在这里插入图片描述

这时候栈里面有 while M E do M if E then M S,将 if E then M S归约成S,归约生成if then语句,归约生成的黑色S执行俩个动作

backpatch(E.truelist, M.quad);

首先将M记录的绿色S的开头104句回填红色E的真出口102句,也就是说当条件成立的时候,执行then后面的语句,后面的语句地址记录在紫色M。quad里面所以102句的第四区会填上104

S.nextlist:=merge(E.falselist, S1.nextlist)
然后第二个动作,要把红色E的假链表103和绿色S1.nextlist合并,绿色S没有nextlist,因此只有103句这个链表是需要回填的,回填给黑色S.nextlist。

下面栈里面while M E do M S
在这里插入图片描述

把它按照while产生式进行归约,这个里面while do产生式应该规约为四个语义动作,说的是:

backpatch(E.truelist, M2.quad);首先拿着M2(紫色102)记录的S的开头也就是黑色S的开头,回填蓝色E的真出口(100句)当条件成立的时候·应该转去执行102

backpatch(S1.nextlist, M1.quad);然后拿着M1(紫色100)记录的E的开头也就是蓝色E的开头,回填黑色S.nextlist(103句)当条件c<d不成立的时候·应该转去执行 if then 的后续语句,现在if then是在while语句后继就是循环头100所以103回填上100

S.nextlist:=E.falselist(整个while的后继语句)=101

最后产生一个无条件跳转到循环头的语句
emit(‘j,-,-,’ M1.quad)
在这里插入图片描述

下面继续扫描,如果碰到了;就可以按照符合语句的翻译模式;前的所有需要回填的四元式比如说S的101句他都是要去他的后继,碰到;就知道所以下一给要产生的四元式107句就是它的后继,这时候回填101句的目标,下一个四元式107,只有碰到分号才能解决;前所有需要回填的四元式。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

7.5.2 标号与goto语句

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7.5.3 CASE语句的翻译

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7.6 过程调用的处理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 39
    点赞
  • 118
    收藏
    觉得还不错? 一键收藏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值