赞
踩
一、前提了解
语义是指源程序及其组成部分所表述的含义。语义与语法的区别在于:语法是关于程序及其组成部分的构成规则的描述,是上下文无关的,而语义是关于语法结构的含义及其使用规则的描述,是上下文有关的。在语法分析正确的基础上,需要进行语义检查。只有当源程序的语义正确了,编译程序才能用另一种形式的语言将其含义表达出来,实现翻译,即“忠实地”将源程序所表达的含义传递给目标程序。也就是说,源程序编译后的目标程序虽然在语法结构上不同,但它们所表达的语义必须是一致的,否则编译就失去了意义。因此,在进行翻译之前,必须检查这些语法正确的语法单位其内部逻辑含义是否正确,称为语义分析。
(1)、中间代码生成
中间代码是源程序的不同表现形式,也称为中间表示。对于已通过静态语义检查的程序,编译程序可将其翻译为后续的中间表示形式,即中间代码生成。中间代码生成的过程体现了如何在更低的级别诠释程序的动态语义。其作用包括:(1)搭建源语言和目标语言之间的桥梁,避开二者之间较大的语义跨度,使编译程序的逻辑结构更加简单明确;(2)有利于编译程序的重定向;(3)有利于进行与目标机无关的优化。
三地址代码(Three-Address Code, TAC)是中间代码的一种抽象形式,其比较接近汇编语言。四元式是三地址代码的一种具体实现,它是具有四个域的记录结构,具体表示为:
(op,arg1,arg2,result)
其含义为:arg1和arg2进行op指定的操作,结果存放到result中。其中,op为运算符,arg1、arg2和result分别为第一、第二运算对象和结果,它们可以是用户定义的变量或临时变量,arg1和arg2还可以是常量。如果op是单目运算符,只需要一个运算对象,则arg2用下划线“_”来代替,为它留出位置。通过静态语义检查的程序,可以将其翻译为四元式表示的中间代码。表4.1为PL/0编程语言常见的四元式代码形式,另外,标识符的声明通常存放在符号表里。
(2)、常见的四元式代码形式
四元式代码 | 含义 | |
(1) | 程序开始(system start) | |
(2) | 程序结束(system end) | |
(3) | (const,iden_,_) | 声明标识符ident(常量) |
(4) | (var,ident,_,_) | 声明标识符ident(变量) |
(5) | (procedure,ident,_,_) | 声明标识符ident(函数) |
(6) | (+,A,B,T) | 加法运算,将A+B的结果赋给T |
(7) | (-,A,B,T) | 减法运算,将A-B的结果赋给T |
(8) | (*,A,B,T) | 乘法运算,将A*B的结果赋给T |
(9) | (/,A,B,T) | 除法运算,将A/B的结果赋给T |
(10) | (<,A,B,T) | 小于,将A<B的结果赋给T |
(11) | (<=,A,B,T) | 小于或等于,将A<=B的结果赋给T |
(12) | (>,A,B,T) | 大于,将A>B的结果赋给T |
(13) | (>=,A,B,T) | 大于或等于,将A>=B的结果赋给T |
(14) | (#,A,B,T) | 不等于,将A#B的结果赋给T |
(15) | (=,A,B,T) | 等于,将A=B的结果赋给T |
(16) | (=,A,_,T) | 将A(的值)赋给常量T |
(17) | (:=,A,_,T) | 将A(的值)赋给变量T |
(18) | (jrop,A,B,P) | 当A rop B为真时跳转到四元式P |
(19) | (read,A,_,_) | 为变量A输入值 |
(20) | (write,A,_,_) | 输出A的值 |
(21) | (call,fun,_,A) | 调用fun函数,返回值存入变量A |
(22) | (call,fun,_,_) | 调用fun函数,不保存返回值 |
(23) | (ret,A,_,_) | 返回返回值A |
(24) |
二、思想
在语法分析情况下,检查语义错误。
构建并使用符号表,利用符号表进行中间代码生成,以四元式的形式展示。
1、设计流程图
2、数据结构
该程序中符号表的数据结构为单链表结构存储,分为初始化链表、添加链表节点、查找链表节点、遍历链表四个函数形式。
(1)Type数组为char字符类型,其表示标识符声明类型,0表示为const保留字的声明,1表示为var保留字的声明,2表示为procedure保留字的声明;
(2)varia数组为char字符类型,其存储着所声明的标识符;
(3)price变量为int数字类型,其表示所声明标识符的值,默认情况下为0(未赋值),但是程序字procedure的声明不包含。
(4)Type_n变量为int数字类型,其表示type字符串数组的个数;
(5)varia_n变量为int数字类型,其表示type字符串数组的个数;
(6)next链表结构则表示下一跳。
而四元式存储中间代码的数据结构为结构体数组结构存储:
如上图所示,存储四元式的中间代码是以结构体数组存储形式:
(1)id变量为int数字类型,其表示为四元式整个列表的序号;
(2)op数组为char字符类型,其表示为运算符;
(3)arg1数组为char字符类型,其表示为第一运算对象,可以为标识符或者是无符号整数;
(4)arg2数组为char字符类型,其表示为第二运算对象,可以为标识符或者是无符号整数;
(5)result数组为char字符类型,其表示为结果,可以为跳转序号或者标识符;
(6)line变量为int数字类型,其表示为四元式在输入文件中的行数,与上下文相关联;
(7)true_false变量为int数字类型,其表示为当有跳转表达式时,其给出的表达式的真假,若不是跳转表达式,则值为-1;若是且该表达式为真时,值为1,若该表达式为假时,为0。
其中,也定义了,若无输出结果,则以T的大写形式表达,例如T1、T2等。
三、符号表的分析过程
(1)语义错误分析
若再语义分析检查中,发现语义错误,一般分为三种常见类型:标识符重复定义声明、程序字调用错误、未声明的标识符应用。
在标识符声明中,会出现标识符重复定义声明错误。
而在语义分析中,另外有函数is_no()函数,来表达程序字调用错误、未声明的标识符应用该两种错误。
在函数中,首先判断是否会出现程序字-1重复读取的错误(程序字”procedure“,只有”procedure“和”call“可以调用它,故再符号表的链表中赋值为-1),若序字出现在其它地方则报错,若出现未定义变量引用则报错。
(2)标识符声明
标识符声明的类型主要就三种:“const“、”var“、”procedure“。其主要定义在函数find_signal()中。
在搜寻到该三种保留字后,首先查找下个单词是否为标识符,并在符号表的链表中查找是否存在该标识符,若存在,则为重复定义,输出语义错误和所在文件行数;若不存在,则再次查找无符号整数,将标识符和无符号整数(该标识符定义的值)添加进符号表的链表中。而明显,程序字”procedure“是没有赋值的,且它情况较为特殊,只有”procedure“和”call“可以调用它,故再符号表的链表中赋值为-1,确保再次查找符号表时,注意到程序字定义的特殊性,以免错误引用。
四、四元式的分析过程
在该程序中,中间代码分为声明标识符、加减乘除运算(不包含表达式)、函数调用、读写操作、条件判断五个部分。
(1)声明标识符
在一行的单词中,判断单词是否为"const"、"var"、"procedure",若是搜寻下一个标识符,将序号、保留字、标识符保存更新到结构体数组中。
(2)加减乘除运算(不包含表达式)
在一行的单词中,判断运算符位置,再搜寻是否有“=“或者”:=“符号,若有,则结果为其左侧标识符;若无,则没有结果。再查找第一运算对象、第二运算对象。最后将运算符、第一运算对象、第二运算对象、结果保存更新到结构体数组中。
(3)函数调用
函数调用,基本就是“call“保留字的调用程序字,若出现,则将其保存更新到结构体数组中。
(4)读写操作
在读写操作中,分为其中有表达式的一类和无表达式的一类。
在无表达式的一类中,其读写操作中出现的就是一个标识符。该语句在搜寻到读写时,再次查找是否存在运算符,若未出现则表示无表达式的一类。直接将序号、保留字、标识符保存更新到结构体数组中。
图 未含表达式的读写操作
在有表达式的一类中,将表达式浅浅的认为是标识符/无符号整数+运算符+标识符/无符号整数,其中结果值在定义中已定义,例如T1、T2等,结果值需查找是否重复,先将表达式中的序号、运算符、标识符、标识符和结果保存更新到结构体数组,后将保留字以及其中的表达式的结果保存更新到结构体数组中。
(5)条件判断
条件主要分为“if“、”while“两个保留字的判断,在该程序中,仅写入了两个符号的判断(”<=“、”#“),若需要判断多个表达式,则可持续加入符号进行判断。
在条件的表达式中,需要调用符号表来判断该表达式是否真假,若真,则将其四元式的结构体数组中的true_false变量赋值为1;若假,则将其true_false变量赋值为0,其中其它四元式中的true_false变量默认为-1。
在其利用符号表调用来判断表达式的真假时,即是利用事先声明的标识符取出值,该值再与其表达式中的无符号整数进行大小判断(即运算符判断)。
而存储在符号表中的链表里的标识符只声明并没有赋值时,判断是否大于或等于0,若小于0(即为-2)时,该表明不知真假,故而需要给出真时的四元式以及假时的四元式。若大于0,则必其可以判断该表达式为真或为假,从而确定只给出一个为真的四元式或为假的四元式。
其中,in_there()函数表示为插入生成四元式。
图 以“<=“符号的表达式真或假或真假的判断给出四元式
五、打印中间代码
(1)定义程序开始和结束的四元式
程序开始和结束时,需要定义两个四元式,即程序开始(system start):(syss,_,_,_)和程序结束(system end):(syse,_,_,_)。
(2)插入返回值ret
这里认为一个if/while后接一个begin-end,如果该begin后end结束是begin,则不属于程序字procedure的范围。
首先在每一行的单词分析中,就已经安插进了如何找到"if"或"while"或"end"或"begin"的行数。再找寻end-begin在文件中的行数,并与四元式中的line(即每个四元式在文件中的行数)比较。在找寻end-begin在文件中的行数以后,即四元式的行数以后,后退一个数组标号(不包含行数之前的数组内容)。并在该四元式开始后退的数组内容中添加返回,无返回值:(ret,_,_,_)。
图 插入返回值ret
(3)表达式真假的跳转结果
跳转操作极为复杂,需要结合符号表以及四元式整体列表进行跳转操作。
其与行数相关,找寻while/if最近的一组begin-end的end的行数,跳入:下一个四元式的行数,但不能跳入到跳出;跳出:找到相对应的四元式的行数。
由于在写入四元式时,就已经写出了真或假或真假的四元式,只剩下跳转未写入。
其用结构体中的true_false循环判断一个四元式(带有表达式的)是否为1(真)或0(假)。若该四元式为真,则查找下一个四元式,若下一个四元式还是进行真假跳转的,则再找下一个,直到一个无真假跳转的真假跳转的四元式为止。若该四元式为假,则查找该四元式的line在文件中的行数,与其后接的最近的一组begin-end的end的行数比较,若找到其值大于end的行数时,跳转到该四元式,将其四元式的序号赋值给假四元式的result结果中;若找不到,即已经找完,则跳到最后一个四元式,即为程序结束意义的四元式。
图 该四元式为真时的跳转
图 该四元式为假时的跳转
(4)打印输出四元式结果
图 含表达式的读写操作
(5)打印结果
若第一运算对象、第二运算对象、运算结果为初值0(未有值),则将其赋值为“_“。
并打印出所有四元式的(序号,运算符/保留字,第一运算对象,第二运算对象,运算结果<跳转序号/值/标识符>)。
其中,需要注意的是,若语义完全正确的情况下,才可输出中间代码(四元式)和符号表,否则,直接输出语义错误以及在文件中的行数位置。
六、输入与输出
(1)语义正确情况下输出四元式、符号表
(2)语义出现错误
剩余代码若展示过于多,故不予展示。
需要私聊。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。