广东工业大学2015编译原理课程设计(共16页).docx
精选优质文档-倾情为你奉上 编译原理课程设计报告 课程名称_编译原理课程设计_ 题目名称 PL/0编译器的扩充 学生学院_ 计算机学院_ 专业班级_ 学 号_ _ _学生姓名_ _ 指导教师_ 张巍 2015年 12 月 27日一 课程设计目的及要求基本内容(成绩范围:“中”、“及格”或“不及格”)(1)扩充赋值运算:+=,-=, *= 和 /=(2)扩充语句(Pascal的FOR语句): FOR <变量>:=<表达式>STEP<表达式> UNTIL<表达式>Do<语句>选做内容(成绩评定范围扩大到:“优”和“良”)增加 注释; 注释由/*和*/包含;二概述1、源语言:PL/0语言,PL/0语言是PASCAL语言的子集,它的编译程序是一个编译解析执行系统,后缀名为.PL0;2、目标语言:生成文件后缀为*.COD的目标代码3、实现平台(平台):Borland C+Builder 64、运行平台:Windows xp三结构设计说明各功能模块概述过程或函数名简要功能说明pl0主程序error出错处理,打印出错位置和错误编码getsym词法分析,读取一个单词getch漏掉空格,读取一个字符gen生成目标代码,并送入目标程序区test测试当前单词符号是否合法block分程序分析处理过程enter登录名字表position查找标识符在名字表中的位置constdeclaration常量定义处理vardeclaration变量说明处理listode列出目标代码清单statement语句处理expression表达式处理term项处理factor因子处理condition条件处理interpret对目标代码的解释执行程序base(函数)通过静态链求出数据区的基地址四主要成分描述(1)符号表 为了组成一条指令,编译程序必须知道其操作码及其参数(数或地址)。这些值是由编译程序本身联系到相应标识符上去的。这种联系是在处理常数、变量和过程说明完成的。为此,标识符表应包含每一标识符所联系的属性;如果标识符被说明为常数,其属性值为常数值;如果标识符被说明成变量,其属性就是由层次和修正量(偏移量)组成的地址;如果标识符被说明为过程,其属性就是过程的入口地址及层次。常数的值由程序正文提供,编译的任务就是确定存放该值的地址。我们选择顺序分配变量和代码的方法;每遇到一个变量说明,就将数据单元的下标加一(PL/0 机中,每个变量占一个存贮单元)。开始编译一个过程时,要对数据单元的下标dx 赋初值,表示新开辟一个数据区。dx 的初值为3,因为每个数据区包含三个内部变量RA,DL 和SL。 (2)运行时的存储组织和管理 当源程序经过语法分析,如果未发现错误时,由编译程序调用解释程序,对存放在CODE中的代码CODE0开始进行解释执行.当废弃结束后,记录源程序中标识符的TABLE表已没有作用.因此存储区只需以数组CODE存主的只读目标程序和运行机制时的数据区S,S是由解释程序定义的一维整数型数组. 解释执行时的数据空间S为栈式计算机的在座空间,遵循后进先出规则,对每个过程(包括主程序)当调用 时,才分配数据空间,退出过程进, 则所分配原则的数据空间被释放.解释程序还定义了4个寄存器:1、指令寄存器.存放当前正在解释的一条目标指令2、程序地址寄存器.指向下一条要执行的目标程序的地址3、栈顶寄存器.4、基址寄存器.指向每个过程被调用时,在数据区S中给它分配原则的数据段起始地址,也称基地址.为了实现过程被调用时给它分配数据段,过程运行结束后释放数据段以及嵌套过程之间结标志符引用的问题,当过程被调用时,在栈顶分配三个联系单元,这三个联系单元存放的内容分别为:SL静态链,动态链DL,RA返回地址。 (3)语法分析方法 语法分析的任务是识别由词法分析给出的单词符号序列在结构上是否符合给定的文法规则.PL/0编译程序的语法分析采用了自顶向下的递归子程序法.粗略地说:就是对应每个非终结符语法单元,编一个独立的处理过程(或子程序).语法分析研究从读入第一个单词开始由非终结符程序即开始符出发,沿语法描述图箭头所指出的方向进行分析.当遇到非终结符时,则调用相应的处理过程,从语法描述图看也就进入了一个语法单元,再沿当前所进入的语法描述图的箭头方向进行分析,当遇到描述图中是终结符时,则判断当前读入的单词是否与图中的终结符相匹配,若匹配,则执行相应的语义程序(就是翻译程序).再读取下一个单词继续分析.遇到分支点时将当前的单词与分支点上的多个终结符逐个相比较,若都不匹配时可能是进入下一非终结符语法单位或是出错. 如果一个PL/0语言程序的单词序列在整修语法分析中,都能逐个得到匹配,直到程序结束.,这时就说所输入的程序是正确的.对于正确的语法分析做相应的语义翻译,最终得出目标程序. <程序> <分程序> . <变量说明部分> <语句> VAR <标识符> ; <复合语句> A BEGIN <语句> END <读语句> READ ( <标识符> ) APL/0编译程序对语法错误的处理采用两种办法:(1)对于一些易于校正的错误,如丢了逗号、分号等,指出出错的位置,加以校正,继续进行分析。 (2)对于难于校正的错误,给出错误的位置与性质,跳过后面一些单词,直到下一个可以进行正常语法分析的语法单位。错误类型如下0 过程开始部分说明不正确1 常数说明中"="写成":="2 常数说明中"="后应为整数或实数或字符3 常数说明中的标识符后应是"="4 const, var, procedure后应为标识符5 漏掉了","或""6 过程说明后的符号不正确(应该是语句开始符,或过程定义符)7 应是语句开始符8 程序体内语句部分的后跟符不正确9 程序结尾丢了句号"."10 语句间漏了""11 标识符未说明12 赋值语句中,赋值号左部标识符属性应是变量13 变量后不能是此符号14 call后应为标识符15 call后标识符属性应为过程16 条件语句中丢了"then"17 丢了"end"或""18 while型循环语句丢了"do"19 语句后的符号不正确20 应为关系运算符21 表达式内标识符属性不能是过程22 表达式中漏掉右括号"("23 因子后的非法符号24 表达式的开始符不能是此符号31 数越界(4)中间代码表示 PL/0编译程序所产生的目标代码是一个假想栈式计算机的汇编语言,可称为类pcode指令代码,它不依赖任何实际计算机,其指令集极为简单,指令格式如下: fla其中f代表功能码,l表示层次差,也就是引用变量胡哦哦过程的分程序与说明该变量或过程的分程序之间的层次差。A的含义对不同的指令有所区别。对每条指令的解释说明如下。目标指令有8条: LIT 将常数值取到栈顶,a为常数值 LOD 将变量值取到栈顶,a为偏移量,l为层差 STO 将栈顶内容送入某变量单元中,a为偏移量,l为层差 CAL 调用过程,a为过程地址,l为层差 INT 在运行栈中为被调用的过程开辟a个单元的数据区 JMP无条件跳转至a地址 JPC 条件跳转,当栈顶布尔值非真则跳转至a地址,否则顺序执行 OPR的l域为0,当栈顶的布尔值为非真时,转向a域的地址,否则顺序执行opr 0 0 过程调用结束后,返回调用点并退栈 opr 0 1 栈顶元素取反 opr 0 2 次栈顶与栈顶相加,退两个栈元素,结果值进栈 opr 0 3 次栈顶减去栈顶,退两个栈元素,结果值进栈 opr 0 4 次栈顶乘以栈顶,退两个栈元素,结果值进栈 opr 0 5 次栈顶除以栈顶,退两个栈元素,结果值进栈 opr 0 6 栈顶元素的奇偶判断,结果值在栈顶 opr 0 7 opr 0 8 次栈顶与栈顶是否相等,退两个栈元素,结果值进栈 opr 0 9 次栈顶与栈顶是否不等,退两个栈元素,结果值进栈 opr 0 10 次栈顶是否小于栈顶,退两个栈元素,结果值进栈 opr 0 11 次栈顶是否大于等于栈顶,退两个栈元素,结果值进栈 opr 0 12 次栈顶是否大于栈顶,退两个栈元素,结果值进栈 opr 0 13 次栈顶是否小于等于栈顶,退两个栈元素,结果值进栈 opr 0 14 栈顶值输出至屏幕 opr 0 15 屏幕输出换行 opr 0 16 从命令行读入一个输入置于栈顶五测试用例本次课设的测试用例共分为三个Test1,用于测试扩充赋值运算:+=,-=, *= 和 /=Test2用于测试扩充语句(Pascal的FOR语句): FOR <变量>:=<表达式>STEP<表达式> UNTIL<表达式>Do<语句>Test3用于测试/* */六开发过程和完成情况1.扩充赋值运算: +=,-=,*=和/=代码修改:else if (SYM = PLUSEQL)/ +=语义分析 GetSym();GEN(LOD, LEV-TABLEi.vp.LEVEL,TABLEi.vp.ADR);/左值入栈EXPRESSION(FSYS,LEV,TX);/右表达式结果入栈GEN(OPR, 0, 2) ;/加法运算GEN(STO,LEV-TABLEi.vp.LEVEL,TABLEi.vp.ADR);/将结果送回左值else if (SYM = MINUSEQL)/ -=语义分析 GetSym();GEN(LOD, LEV-TABLEi.vp.LEVEL,TABLEi.vp.ADR);/左值入栈EXPRESSION(FSYS,LEV,TX);/右表达式结果入栈GEN(OPR, 0, 3) ;/乘法运算GEN(STO,LEV-TABLEi.vp.LEVEL,TABLEi.vp.ADR);/将结果送回左值else if (SYM = MULSEQL)/ *=语义分析 GetSym();GEN(LOD, LEV-TABLEi.vp.LEVEL,TABLEi.vp.ADR);/左值入栈EXPRESSION(FSYS,LEV,TX);/右表达式结果入栈GEN(OPR, 0, 4) ;/乘法运算GEN(STO,LEV-TABLEi.vp.LEVEL,TABLEi.vp.ADR);/将结果送回左值else if(SYM = DIVSEQL)/ /=语义分析GetSym();GEN(LOD, LEV-TABLEi.vp.LEVEL,TABLEi.vp.ADR);/左值入栈EXPRESSION(FSYS,LEV,TX);/右表达式结果入栈GEN(OPR, 0, 5) ;/除法运算,注意原始的是求余运算!GEN(STO,LEV-TABLEi.vp.LEVEL,TABLEi.vp.ADR);/将结果送回左值语法树:;ident表达式+=表达式-=表达式*=表达式/=2.扩充语句(Pascal的FOR语句): FOR <变量>:=<表达式>STEP<表达式> UNTIL<表达式>Do<语句>代码修改:case FORSYM: GetSym(); if(SYM != IDENT) /for后面必须跟变量来进行赋值 Error(47); else i=POSITION(ID,TX); /查找标识符在名字表中的位置并赋值给i if(i = 0) /对i进行检查是否存在于符号表中 Error(11); else if (TABLEi.KIND!=VARIABLE) /不是变量类型,错误 /*ASSIGNMENT TO NON-VARIABLE*/ Error(12); i=0; GetSym(); /读取下一个符号:= if(SYM = BECOMES) /读取到:= GetSym(); else Error(13); EXPRESSION(SymSetUnion(SymSetNew(STEPSYM),FSYS),LEV,TX); /设置后跟符为step if(i != 0) /变量存在 GEN(STO,LEV-TABLEi.vp.LEVEL,TABLEi.vp.ADR); /将栈顶的值传给变量 if(SYM=STEPSYM) /读取到step GetSym(); else /没有读到step,出错 Error(46); CX1=CX; /记录此处地址 GEN(JMP,0,0); /条件跳转,地址未知,后面回填 CX3=CX; EXPRESSION(SymSetUnion(SymSetNew(UNTILSYM),FSYS),LEV,TX); /设置后跟符为until GEN(LOD,LEV-TABLEi.vp.LEVEL,TABLEi.vp.ADR); /将变量送回栈顶 GEN(OPR,0,2); /栈顶与次栈顶相加,即for语句的变量加上step中的步数 GEN(STO,LEV-TABLEi.vp.LEVEL,TABLEi.vp.ADR);/把栈顶的值送到变量 if(SYM=UNTILSYM) /读取到until GetSym(); else Error(47); CODECX1.A=CX; /地址回填 EXPRESSION(SymSetUnion(SymSetNew(DOSYM),FSYS),LEV,TX); /设置后跟符为do,终止条件 GEN(LOD,LEV-TABLEi.vp.LEVEL,TABLEi.vp.ADR); /将变量送回栈顶 GEN(OPR,0,11); /比较次栈顶与栈顶的大小,即比较条件是否还满足循环条件 CX2=CX; GEN(JPC,0,0); /条件跳转,地址未知,后面回填地址 if(SYM=DOSYM) /读取到do GetSym(); else Error(48); STATEMENT(FSYS,LEV,TX); /对do语句的内容进行处理 GEN(JMP,0,CX3); /执行完后无条件跳转到cx3处的地址,即对循环条件进行比较,是否还继续执行循环 CODECX2.A=CX; /地址回调 break;增加 注释; 注释由/*和*/包含;else if (CH='*') / /*/注释的实现 while(1) /处于此循环内,直到获取到*/之前不做任何处理 GetCh(); if (CH='*') GetCh(); if(CH='/') /右边的*/读取完毕,退出循环 GetCh(); break; 此段代码位于分析/内,对/* */截获内容不做处理七实验结果1.扩充赋值运算: +=,-=,*=和/=2. 扩充语句(Pascal的FOR语句): FOR <变量>:=<表达式>STEP<表达式> UNTIL<表达式>Do<语句>增加 注释; 注释由/*和*/包含;八心得体会不得不说,编译原理这门课程是我上大学以来觉得最难的一门课程了,并且也是专业课,也就显得极其重要和尤为有意义了。编译原理的课程设计,给了我们实践课内知识最好的一个平台了。相比之前的编译原理实验课,课程设计要求实现的功能更多,难度更高。有了上次的编译原理实验的体验,这次课程设计显然是熟练了很多,也没有摸不着头脑的感觉,脉络还是比较清晰。对于+=,-=,*=,/=的语义分析,由于在之前的实验课*=,/=已经完成了,所以这次实现起来也比较容易,实现的步骤几乎和之前完全一样。但是比较难的就是for语义分析的实现,花了较多时间去调试和测试结果,实现起来也是比较繁琐的。而对于/*/注释的实现,一开始我是想在getch内实现的,试了好多次还是不行,后来还是和同学交流了一下在以/符号开始的词法分析内内部实现,才成功了。对于本次的课程设计,需要理解编译程序的核心和基本思想,包括基本的语法分析和句法分析以及中间代码的生成,还有十分重要的堆栈应用。只有把这些基础知识联系串联起来,才能更顺利的完成后边的设计内容。我觉得课程设计是一个巩固课内知识的好方法,只有自己动手去做,去想,才能真正的去理解课程的内容及消化。最后谢谢老师本学期的编译原理课程的指导!专心-专注-专业