编译原理实践一、实验项目名称:符号表管理程序的设计与实现+PL/0编译器的整合二、实验次数:1次实验课三、实验目的、内容与要求:(一)实验目的:1.了解符号表的作用2.掌握符号表的内容设计3.掌握符号表的结构安排4.掌握符号表的相关操作5.完成PL/0编译器,体会整个编译过程。(二)实验内容:1.了解语义分析阶段需要进行哪些语义检查同时要掌握符号的作用域原则2.确定PL/0语言中有多少种符号,哪些符号需要填入符号表,这些符号都包含哪些属性,设计符号表项的数据结构,可采用统一的数据项结构,也可不同类别符号采用不同结构3.确定符号表的组织方式:由于标识符有作用域,在组织符号表时,应该保证标识符作用域的有效性。随着分析程序的运行,会不断地进入或退出一些作用域,当退出某个作用域时,应该让这个作用域内的符号表项都作废。一般地,有两种作废方法,真删除法和加标记法法。4.定义符号表上的操作:符号表的创建、查找和删除5.扫描程序生成符号表。假设符号表初始为空,在编译过程中,每当遇到对某个符号的定义,编译程序首先检查符号表,检查这个符号是否已经存在。若是属于重复定义,报告错误信息;若是一个新的名字,则填入符号表中。在编译过程中遇到对某个符号的使用时,编译器要检查符号表,检查该符号是否已经定义,并按照符号表中的定义来使用符号。6.将词法分析、语法分析、语义分析、符号表等各部分模块整合调试。(三)实验要求:1.了解符号表在编译过程中的重要作用2.掌握符号表应包含的符号的属性信息3.了解符号表的组织原则4.掌握符号表的操作5.掌握符号表的可见性问题6.完成并提交一个完整的PL/0编译器。7.注意:此次实验结束后整个编译原理的实验也就完成了,所以这次实验报告在最后写一下整体的实验总结,也就是整个PL/0编译器完成过程中的经验教训、个人感想之类的。1.输入要求:一个PL/0文件。示例(仅供参考),文件名称test.pl0,文件内容如下:consta=10;varb,c;procedurep;beginc:=b+a;end;beginread(b);whileb#0dobegincallp;write(c);read(b);endend.2.输出要求:各模块的结果输出,以及最后程序运行的示例。示例,test.pl0运行结果如下:请输入PL/0文件:test.txt0error,Success!是否输出汇编代码?(Y/N):y汇编代码:0jmp081jmp022int033lod134lit0105opr026sto147opr008int059opr01610sto0311lod0312lit0013opr0914jpc02215cal0216lod0417opr01418opr01519opr01620sto0321jmp01122opr00是否输出符号表?(Y/N):y符号表:1constaval=102varblev=0addr=33varclev=0addr=44procplev=0addr=2size=3运行程序:输入程序中变量值(0:退出):313输入程序中变量值(0:退出):515输入程序中变量值(0:退出):0请按任意键继续...四、实验详细设计说明:符号表的作用:符号表是用来存放语言程序中出现的有关标识符的属性信息,这些信息集中反映了标识符的语义特征属性。在词法分析及语法分析过程中不断积累和更新表中的信息,并在词法分析到代码生成的各阶段,按各自的需要从表中获取不同的属性信息。不论编译策略是否分趟,符号表的作用和地位是完全一致的。1.符号表的设计规则1)符号名符号表中设置一个符号名域,存放该标识符,该域通常就是符号表的关键字域。通常在语言程序中标识符字符串是一个变量、函数或过程的唯一标志,因此在符号表中符号名作为表项之间的唯一区别一般不允许重名。从而该符号名与它在符号表中的位置建立起一一对应之关系,使得我们可以用一个符号在表中的位置(通常是一个整数)来替换该符号名。通常把一个标识符在符号表中的位置的整数值称之谓该标识符的内部代码。在经过分析处理的语言程序中标识符不再是一个字符串而是一个整数值,这不但便于识别比较而且缩短了表达的长度。2)符号的类型标识符中除过程标识符之外,函数和变量标识符都具有数据类型属性。对于函数的数据类型指的是该函数值的数据类型。基本数据类型有整型、实型、字符型、逻辑型(布尔型)等,符号的类型属性从程序中该符号的定义中得到。变量符号的类型属性决定了该变量的数据在存储空间的存储格式,还决定了在该变量上可以施加的运算。3)符号的作用域及可视性一般来说,定义该符号的位置及存储类关键字决定了该符号的作用域。C语言中一个外部变量的作用域是整个程序,因此一个外部变量符号的定义在整个程序中只能出现一次。一般来说一个变量的作用域就是该变量可以出现的场合,也就是说在某个变量作用域范围内该变量是可引用的,这就是变量可视性的作用域规则。PL/0语言允许过程嵌套定义,即外层变量和程序对内层可见,内层变量和函数对外层不可见,其中内层函数或变量将使外层的同名函数或变量不可见。4)符号表操作在整个编译期间,对于符号表的操作大致可归纳为五类:•对给定名字,查询名字是否已在表中;•往表中填入一个新的名字;•对给定名字,访问它的某些信息;•对给定名字,填写或更新它的某些信息;•删除一个或一组无用的项。不同种类的表格所涉及的操作往往也是不同的。上述五个方面只是一些基本的共同操作。5)符号表的数据结构符号表项的组织传统上采用三种构造方法:即线性组织(队、栈),有序组织及散列组织。2.PL/0符号表设计基于上述规则,构造PL/0名字表如下:考虑本编译程序限于源代码数不超过200,且嵌套不超过3层,故采用线性结构的符号表组织,简化符号表的管理过程。所造名字表放在全程量一维数组TABLE表中。TX为索引表的指针,表中的每个元素为记录型数据。LEV给出层次,DX给出每层局部量当前已分配到的相对位置,可称地址指示器,每说明完一个变量后DX指示器加1。PL/0编译程序源代码中对名字表定义有:说明类型的定义://名字表中的类型enumobject{constant,variable,procedur,};名字表的定义:structtablestruct{charname[10];//名字enumobjectkind;//类型:const,varorprocedurintval;//数值,仅const使用intlevel;//所处层,仅const不使用intadr;//地址,仅const不使用intsize;//需要分配的数据空间,仅procedur使用};structtablestructtable[txmax];//名字表例如:一个过程的说明部分为:CONSTA=35,B=49;VARC,D,E;PROCEDUREP;VARG,...对常量,变量和过程说明分析后,在TABLE表中的信息如下表所示。:NAMEKINDVAL/LEVELADRSIZEABCDEPCONSTANTCONSTANTVALUABLEVALUABLEVALUABLEPROCEDUR3549LEVLEVLEVLEVDXDX+!DX+24…G…VALUABLE…LEV+1…D…当说明语句被扫描后生成以上TABLE表,在TABLE表中,过程名P的ADR域,需要等到过程体的目标代码生成后,才能反填过程体的入口地址。P过程所在的层次为LEV,P过程定义的变量名G的层次为LEV+1。对过程还有一项数据SIZE,是记录该过程体运行时所需的数据空间。TABLE表的表头索引TX和层次单元LEV都以BLOCK的参数形式出现。在主程序调用BLOCK时实参值都为0。每个过程中变量的相对起始位置在BLOCK内置初值DX∶=3。BLOCK中对符号表中TX,DX,CX的初始化:/*编译程序主体lev:当前分程序所在层tx:名字表当前尾指针fsys:当前模块后跟符号集合*/intblock(intlev,inttx,bool*fsys){TX0TX…intdx;//名字分配到的相对地址inttx0;//保留初始txintcx0;//保留初始cx…dx=3;//为该过程变量分配存储空间的起始位置,也就是相对基地址的偏移量tx0=tx;//记录本层名字的初始位置table[tx].adr=cx;//保留当前code指针值到过程名的adr域…code[table[tx0].adr].a=cx;//开始生成当前过程代码table[tx0].adr=cx;//当前过程代码地址table[tx0].size=dx;//身部分中每增加一条声明都会给dx增加1,声明部分已经结束,dx就是当前过程数据的sizecx0=cx;…}其中:cx已保留在过程名的adr域,等生成过程体入口的指令时,再由table[tx].adr中找到cx将过程体入口返填到cx中,即(jmp,0,0)的第3区域。同时将过程体入口填到过程名的table[tx].adr中。3.符号表的管理1)登录名字表对每个过程(含主程序)说明的对象(变量,常量和过程)造符号表,登录标识符的属性,包括标识符的名字,种类,所在层次/值和分配的相对位置。登录信息由ENTER过程完成。/*在名字表中加入一项k:名字种类const,var,procedureptx:名字表尾指针的指针,为了可以改变名字表尾指针的值lev:名字所在的层次pdx:dx为当前应分配的变量的相对地址,分配后要增加1*/voidenter(enumobjectk,int*ptx,intlev,int*pdx){(*ptx)++;strcpy(table[(*ptx)].name,id);//全局变量id中已存有当前名字的名字table[(*ptx)].kind=k;switch(k){caseconstant://常量名字if(numamax){error(31);//越界num=0;}table[(*ptx)].val=num;break;casevariable://变量名字table[(*ptx)].level=lev;table[(*ptx)].adr=(*pdx);(*pdx)++;break;caseprocedur://过程名字table[(*ptx)].level=lev;break;}}2)名字表的查找/*查找名字的位置找到则返回在名字表中的位置,否则返回0idt:要查找的名字tx:当前名字表尾指针*/intposition(char*idt,inttx){inti;strcpy(table[0].name,idt);i=tx;while(strcmp(table[i].name,idt)!=0){i--;}returni;}本编译程序仅实现了符号表的简单管理,有很多功能尚未实现,比如退出某作用域时要即时废除该作用域的符号表项等,同学们可以自己实现。