简易的编译器——语义分析

一、前提了解

        语义是指源程序及其组成部分所表述的含义。语义与语法的区别在于:语法是关于程序及其组成部分的构成规则的描述,是上下文无关的,而语义是关于语法结构的含义及其使用规则的描述,是上下文有关的。在语法分析正确的基础上,需要进行语义检查。只有当源程序的语义正确了,编译程序才能用另一种形式的语言将其含义表达出来,实现翻译,即“忠实地”将源程序所表达的含义传递给目标程序。也就是说,源程序编译后的目标程序虽然在语法结构上不同,但它们所表达的语义必须是一致的,否则编译就失去了意义。因此,在进行翻译之前,必须检查这些语法正确的语法单位其内部逻辑含义是否正确,称为语义分析。

(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)

(syss,_,_,_)

程序开始(system start)

(2)

(syse,_,_,_)

程序结束(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)

(ret,_,_,_)

返回,无返回值

二、思想

在语法分析情况下,检查语义错误。

构建并使用符号表,利用符号表进行中间代码生成,以四元式的形式展示。

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)语义出现错误

 

剩余代码若展示过于多,故不予展示。

需要私聊。

  • 6
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值