logo资料库

PL/0编译程序的研究与改进.docx

第1页 / 共28页
第2页 / 共28页
第3页 / 共28页
第4页 / 共28页
第5页 / 共28页
第6页 / 共28页
第7页 / 共28页
第8页 / 共28页
资料共28页,剩余部分请下载后查看
1前言
2实训目的
3项目任务
3.1 PL/0编译程序的研究
3.2 PL/0编译程序的改进
4项目主要内容
4.1 对PL/0编译程序的研究
5 运行结果
6 参考文献
PL/0 编译程序的研究与改进 (燕山大学 信息科学与工程学院) 摘 要: 本小组围绕 PL/0 编译程序进行研究,通过查阅资料、小组讨论等方式, 明确了 PL/0 编译程序的系统构成、涉及到的语言、组织结构等基础知识,对 PL/0 编译程序 的词法分析、语法分析、语义分析、目标代码生成、错误处理等内容有了深入的了解。通 过对 PL/0 编译程序的功能扩充,提高了学以致用的水平,锻炼了实践能力。 1 前言 “PL/0 编译程序的研究与改进”项目是《编译原理》课程学习的一个重要组成 部分。通过课程研究项目的实施,使我们在掌握编译原理基本内容和基本技术的基础 上,结合一种比较简单的程序设计语言 PL/0 的编译程序的开发深入理解并掌握编译 程序的相关知识、设计与开发方法,更好地培养计算机专业学生的专业技术能力和综 合素质。 2 实训目的 1、掌握编译程序的设计原理和方法; 2、掌握编译程序的开发技术; 3、掌握编程规范; 4、掌握软件文档的撰写方法; 5、培养团队合作精神、项目组织与管理、交流表达能力; 6、培养文件管理、资料保存、备份能力及意识。 3 项目任务 3.1 PL/0 编译程序的研究 1、研究 PL/0 编译程序的总体结构、头文件及用到的数据结构和变量; 2、研究词法分析功能的实现原理; 3、研究语法语义分析功能的实现原理和技术; 4、研究目标代码结构和代码生成的实现原理和技术; 5、研究错误处理的原理和技术; 6、研究目标代码解释执行时的存储分配。 第 1 页
3.2 PL/0 编译程序的改进 扩充 PL/0 编译程序的功能。以语法分析部分为例,可以增加处理更多语法成分 的功能,如可处理一维数组、++、--、+=、-=、*=、/=、%(取余)、!(取反)、 repeat、for、else、开方、处理注释、错误提示、标示符或变量中可以有下划线等。 4 项目主要内容 4.1 对 PL/0 编译程序的研究 4.1.1 PL/0 编译程序的总体结构、头文件及用到的数据结构和变量 1、总体结构: PL/0 语言可看成是 PASCAL 语言的子集,它的编译程序是一个编译解释执行系统。 PL/0 的目标程序为假想栈式计算机的汇编语言,与具体计算机无关。其编译过程采 用一趟扫描方式,以语法分析程序为核心,词法分析程序和代码生成程序都作为一个 独立的过程,当语法分析需要读单词时就调用词法分析程序,而当语法分析正确需生 成相应的目标代码时,则调用代码生成程序。此外,用表格管理程序建立变量、常量 和过程标识符的说明与引用之间的信息联系;用出错处理程序对词法和语法分析遇到 的错误给出在源程序中出错的位置和错误性质。当源程序编译正确时,PL/0 编译程 序自动调用解释执行程序,对目标代码进行解释执行,并按用户程序要求输入数据和 输出运行结果。 图一 PL/0 编译系统构成 第 2 页
2、头文件 # define norw 13 # define txmax 100 # define nmax 14 # define al 10 # define amax 2047 # define levmax 3 # define cxmax 200 /*符号*/ enum symbol{ /*PL/0 编译系统 C 版本头文件 pl0.h*/ /*关键字个数*/ /*名字表容量*/ /*number 的最大位数*/ /*符号的最大长度*/ /*地址上界*/ /*最大允许过程嵌套声明层数[0,lexmax]*/ /*最多的虚拟机代码数*/ plus, number, oddsym, gtr, semicolon,period, eql, geq, ident, slash, leq, comma, nul, times, lss, rparen, beginsym, endsym, ifsym, writesym, readsym, procsym, varsym, minus, neq, lparen, becomes, thensym, whilesym, callsym, constsym, dosym, }; #define symnum 32 /*名字表中的类型*/ enum object{ constant, variable, procedur, }; /*虚拟机代码*/ enum fct{ lit, opr, lod, sto, cal, inte, jmp, jpc, }; #define fctnum 8 /*虚拟机代码结构*/ struct instruction 第 3 页
{ }; enum fct f; int l; int a; FILE * fas; FILE * fa; FILE * fa1; FILE * fa2; bool tableswitch; bool listswitch; char ch; enum symbol sym; char id[al+1]; int num; int cc,ll; int cx; char line[81]; char a[al+1]; struct instruction code[cxmax]; char word[norw][al]; enum symbol wsym[norw]; enum symbol ssym[256]; char mnemonic[fctnum][5]; bool declbegsys[symnum]; bool statbegsys[symnum]; bool facbegsys[symnum]; /*------------------------------*/ /*名字表结构*/ struct tablestruct /*虚拟机指令代码*/ /*引用层与声明层的层次差*/ /*根据 f 的不同而不同*/ /*输出名字表*/ /*输出虚拟机代码*/ /*输出源文件及其各行对应的首地址*/ /*输出结果*/ /*显示虚拟机代码与否*/ /*显示名字表与否*/ /*获取字符的缓冲区,getch 函数使用*/ /*当前的符号*/ /*当前 ident,多出的一个字节用于存放 0*/ /*当前 number*/ /*getch 使用的计数器,cc 表示当前字符 ch 的位置*/ /*虚拟机代码指针,取值范围[0,cxmax-1]*/ /*读取行缓冲区*/ /*临时符号,多出的一个字节用于存放 0*/ /*存放虚拟机代码的数组*/ /*保留字*/ /*保留字对应的符号值*/ /*单字符的符号值*/ /*虚拟机代码指令名称*/ /*表示声明开始的符号集合*/ /*表示语句开始的符号集合*/ /*表示因子开始的符号集合*/ 第 4 页
{ char name[al]; enum object kind; int val; int level; int adr; int size; }; struct tablestruct table[txmax]; FILE * fin; FILE* fout; char fname[al]; int err; /*名字*/ /*类型:const,var,array,procedure*/ /*数值,仅 const 使用*/ /*所处层,仅 const 不使用*/ /*地址,仅 const 不使用*/ /*需要分配的数据区空间,仅 procedure 使用*/ /*名字表*/ /*错误计数器*/ if(-1==getsym())return -1 if(-1==getch())return -1 if(-1==test(a,b,c))return -1 if(-1==gen(a,b,c))return -1 if(-1==expression(a,b,c))return -1 if(-1==factor(a,b,c))return -1 if(-1==term(a,b,c))return -1 if(-1==condition(a,b,c))return -1 if(-1==statement(a,b,c))return -1 if(-1==constdeclaration(a,b,c))return -1 if(-1==vardeclaration(a,b,c))return -1 /*当函数中会发生 fatal error 时,返回-1 告知调用它的函数,最终退出程序*/ #define getsymdo #define getchdo #define testdo(a,b,c) #define gendo(a,b,c) #define expressiondo(a,b,c) #define factordo(a,b,c) #define termdo(a,b,c) #define conditiondo(a,b,c) #define statementdo(a,b,c) #define constdeclarationdo(a,b,c) #define vardeclarationdo(a,b,c) void error(int n); int getsym(); int getch(); void init(); int gen(enum fct x,int y,int z); int test(bool*s1,bool*s2,int n); int inset(int e,bool*s); 第 5 页
int addset(bool*sr,bool*s1,bool*s2,int n); int subset(bool*sr,bool*s1,bool*s2,int n); int mulset(bool*sr,bool*s1,bool*s2,int n); int block(int lev,int tx,bool* fsys); void interpret(); int factor(bool* fsys,int* ptx,int lev); int term(bool*fsys,int*ptx,int lev); int condition(bool*fsys,int*ptx,int lev); int expression(bool*fsys,int*ptx,int lev); int statement(bool*fsys,int*ptx,int lev); void listcode(int cx0); int vardeclaration(int* ptx,int lev, int* pdx); int constdeclaration(int* ptx,int lev, int* pdx); int position(char* idt,int tx); void enter(enum object k,int* ptx,int lev,int* pdx); int base(int l,int* s,int b); 3、数据结构 由于类 P-code 虚拟机是一种简单的纯栈式结构的机器,所以 PL/0 编译程序用到 的数据结构主要是栈。 4.1.2 词法分析功能的实现原理 词法分析子程序名为 getsym,功能是从源程序中读出一个单词符号(token), 把它的信息放入全局变量 sym、id 和 num 中,语法分析器需要单词时,直接从这三个 变量中获得。语法分析器每次用完这三个变量的值就立即调用 getsym 子程序获取新 的单词供下一次使用。getsym 过程通过反复调用 getch 子过程从源程序过获取字符, 并把它们拼成单词。getch 过程中使用了行缓冲区技术以提高程序运行效率。 调用 getsym 时,它通过 getch 过程从源程序中获得一个字符: 如果这个字符是字母,则继续获取字符或数字,最终可以拼成一个单词,查保留 字表,如果查到为保留字,则把 sym 变量赋成相应的保留字类型值;如果没有查到, 则这个单词应是一个用户自定义的标识符,把 sym 置为 ident,并把这个单词存入 id 变量。(查保留字表时使用了二分法查找以提高效率) 如果 getch 获得的字符是数字,则继续用 getch 获取数字,并把它们拼成一个整 数,然后把 sym 置为 number,并把拼成的数值放入 num 变量。 如果识别出其它合法的符号,例如赋值号、大于号、小于等于号等,则把 sym 置 第 6 页
成相应的类型。 如果遇到不合法的字符,把 sym 置成 nul。 4.1.3 语法语义分析功能的实现原理和技术 语法分析子程序采用了自顶向下的递归子程序法,语法分析同时也根据程序的语 意生成相应的代码,并提供了出错处理的机制。语法分析主要由分程序分析过程 ( block ) 、 常 量 定 义 分 析 过 程 ( constdeclaration ) 、 变 量 定 义 分 析 过 程 (vardeclaration)、语句分析过程(statement)、表达式处理过程 (expression)、 项处理过程(term)、因子处理过程(factor)和条件处理过程(condition)构成。 这些过程在结构上构成一个嵌套的层次结构。除此之外,还有出错报告过程(error)、 代码生成过程(gen)、测试单词合法性及出错恢复过程(test)、登录名字表过程 (enter)、查询名字表函数(position)以及列出类 PCODE 代码过程(listcode) 作过语法分析的辅助过程。 一个完整的 PL/0 程序是由分程序和句号构成的。因此,PL/0 编译程序在运行时, 通过主程序中调用分程序处理过程 block 来分析分程序部分(分程序分析过程中还可 能会递归调用 block 过程),然后判断最后读入的符号是否为句号。如果是句号且 分程序分析中未出错,则是一个合法的 PL/0 程序,可以运行生成的代码,否则就说 明源 PL/0 程序是不合法的,输出出错提示即可。 下面按各语法单元分析 PL/0 编译程序的运行机制: (1)分程序处理过程: 语法分析开始后,首先调用分程序处理过程(block)处理分程序。过程入口参 数置为:0 层、符号表位置 0、出错恢复单词集合为句号、声明符或语句开始符。进 入 block 过程后,首先把局部数据段分配指针设为 3,准备分配 3 个单元供运行期存 放静态链 SL、动态链 DL 和返回地址 RA。然后用 tx0 记录下当前符号表位置并产生 一条 jmp 指令,准备跳转到主程序的开始位置,由于当前还未知主程序究竟在何处开 始,所以 jmp 的目标暂时填为 0,稍后再改。同时在符号表的当前位置记录下这个 jmp 指令在代码段中的位置。在判断了嵌套层数没有超过规定的层数后,开始分析源 程序。 首先判断是否遇到了常量声明,如果遇到则开始常量定义,把常量存入符号表。 接下去用同样的方法分析变量声明,变量定义过程中会用 dx 变量记录下局部数据段 分配的空间个数。然后如果遇到 procedure 保留字则进行过程声明和定义,声明的方 法是把过程的名字和所在的层次记入符号表,过程定义的方法就是通过递归调用 block 过程,因为每个过程都是一个分程序。由于这是分程序中的分程序,因此调用 block 时需把当前的层次号 lev 加一传递给 block 过程。 第 7 页
分程序声明部分完成后,即将进入语句的处理,这时的代码分配指针 cx 的值正 好指向语句的开始位置,这个位置正是前面的 jmp 指令需要跳转到的位置。 于是通 过前面记录下来的地址值,把这个 jmp 指令的跳转位置改成当前 cx 的位置。并在符 号表中记录下当前的代码段分配地址和局部数据段要分配的大小(dx 的值)。生成 一条 int 指令,分配 dx 个空间,作为这个分程序段的第一条指令。下面再调用语句 处理过程 statement 分析语句。分析完成后,生成操作数为 0 的 opr 指令,用于从分 程序返回(对于 0 层的主程序来说,就是程序运行完成,退出)。 (2)常量定义过程: 通过循环,反复获得标识符和对应的值,存入符号表。符号表中记录下标识符的 名字和它对应的值。 (3)变量定义过程: 与常量定义类似,通过循环,反复获得标识符,存入符号表。符号表中记录下标 识符的名字、它所在的层及它在所在层中的偏移地址。 (4)语句处理过程: 语句处理过程是一个嵌套子程序,通过调用表达式处理、项处理、因子处理等过 程及递归调用自己来实现对语句的分析。语句处理过程可以识别的语句包括赋值语 句、read 语句、write 语句、call 语句、if 语句、while 语句。当遇到 begin/end 语句时,就递归调用自己来分析。分析的同时生成相应的类 PCODE 指令。 ①赋值语句的处理: 首先获取赋值号左边的标识符,从符号表中找到它的信息,并确认这个标识符确 为变量名。然后通过调用表达式处理过程算得赋值号右部的表达式的值并生成相应的 指令保证这个值放在运行期的数据栈顶。最后通过前面查到的左部变量的位置信息, 生成相应的 sto 指令,把栈顶值存入指定的变量的空间,实现了赋值操作。 ②read 语句的处理: 确定 read 语句语法合理的前提下(否则报错),生成相应的指令:第一条是 16 号操作的 opr 指令,实现从标准输入设备上读一个整数值,放在数据栈顶。第二条是 sto 指令,把栈顶的值存入 read 语句括号中的变量所在的单元。 ③write 语句的处理: 在语法正确的前提下,生成指令:通过循环调用表达式处理过程分析 write 语句 括号中的每一个表达式,生成相应指令,保证把表达式的值算出并放到数据栈顶并生 成 14 号操作的 opr 指令,输出表达式的值。最后生成 15 号操作的 opr 指令输出一个 换行。 ④call 语句的处理: 第 8 页
分享到:
收藏