编译原理课设报告最终版.docx
《编译原理课设报告最终版.docx》由会员分享,可在线阅读,更多相关《编译原理课设报告最终版.docx(22页珍藏版)》请在冰豆网上搜索。
![编译原理课设报告最终版.docx](https://file1.bdocx.com/fileroot1/2022-11/25/c1cf4d5a-dfeb-4b86-b108-62d5d5f48176/c1cf4d5a-dfeb-4b86-b108-62d5d5f481761.gif)
编译原理课设报告最终版
编译原理课程设计
PL0ExperimentReport
(燕山大学信息科学与工程学院)
姓名班级:
计算机科学与技术
学生学号:
课程名称:
编译原理
指导教师:
2015年12月24日
一、设计目的
研究、改进或自行设计、开发一个简单的编译程序或其部分功能,加深对编译理论和编译过程的理解。
编程语言不限。
二、设计任务
扩展PL/0编译程序功能
目的:
扩充PL/0编译程序功能
要求:
(1)阅读、研究PL/0编译程序源文件。
(2)在上述工作基础上,可有选择地补充、完善其中词法分析、语法分析、语义分析、目标代码生成、目标代码解释执行等部分的功能。
如以语法分析部分为例,则可以增加处理更多语法成分的功能,如可处理一维数组、++、--、+=、-=、*=、/=、%(取余)、!
(取反)、repeat、for、else、开方、处理注释、错误提示、标示符或变量中可以有下划线等。
还可以增加类型,如增加字符类型、实数类型;扩充函数如有返回值和返回语句的,有参数函数等;
(3)设计编制典型的运行实例,以便能反映出自己所作的改进。
三、设计思想:
PL/0语言可以看成PASCAL语言的子集,它的编译程序是一个编译解释执行系统。
PL/0的目标程序为假想栈式计算机的汇编语言,与具体计算机无关。
PL/0的编译程序和目标程序的解释执行程序都是用PASCAL语言书写的,因此PL/0语言可在配备PASCAL语言的任何机器上实现。
其编译过程采用一趟扫描方式,以语法分析程序为核心,词法分析和代码生成程序都作为一个独立的过程,当语法分析需要读单词时就调用词法分析程序,而当语法分析正确需要生成相应的目标代码时,则调用代码生成程序。
用表格管理程序建立变量、常量和过程表示符的说明与引用之间的信息联系。
当源程序编译正确时,PL/0编译程序自动调用解释执行程序,对目标代码进行解释执行,并按用户程序的要求输入数据和输出运行结果。
四、设计内容:
1扩充语句for(<语句>;<条件>;<语句>)<语句>;
2扩充语句if<条件>then<语句>else<语句>;
3扩充语句repeat<语句>;until<条件>;
4增加自增自减运算++和—和+=,-=运算;
5修改不等号#,为!
=;
6增加一维数组
声明格式:
[/:
/];
赋值格式:
[]:
=<表达式>;
调用格式:
[]
五、程序结构:
PL/0源程序
图1编译程序结构图2功能模块调用
1.功能模块作用如下:
Pl0.c:
主程序
Error:
出错处理,打印出错位置和错误编码
Getsym:
词法分析,读取一个单词
Getch:
漏掉空格,读取一个字符
Gen:
生成目标代码,并送入目标程序区
Test:
测试当前符号是否合法
Block:
分程序分析处理过程,词法语法分析
Enter:
登陆名字表
Position:
查找标识符在名字表中的位置
Constdeclaration:
常量定义处理
Vardeclaraction:
变量说明处理
Listcode:
列出目标代码清单
Statement:
语句处理
Expression:
表达式处理
Term:
项处理
Factor:
因子处理
Condition:
条件处理
Interpret:
对目标代码的解释执行程序
Base:
通过静态链求出数据取得基地址
增加两个功能:
Arraydeclaration:
数组声明处理
Arraycoef:
数组索引计算和“虚拟机”动作生成
2.保留字:
enumsymbol{nul,ident,number,plus,minus,
times,slash,oddsym,eql,neq,
lss,leq,gtr,geq,lparen,
rparen,comma,semicolon,period,becomes,
beginsym,endsym,ifsym,thensym,elsesym,
forsym,inc,dec,whilesym,writesym,
readsym,dosym,callsym,constsym,varsym,
procsym,repeatsym,untilsym,plusbk,minusbk,
lbrack,rbrack,colon,}
共43个,其中补充保留字为:
else,for,repeat,until,plusbk,minusbk,
Lbrack,rbrack,colon
3.名字表中的类型
enumobject{constant,variable,procedure,arrays,}
共4个,扩充arrays,以便实现数组
4.虚拟机代码
enumfct{lit,opr,lod,sto,cal,inte,jmp,jpc,
lda,sta,}
共10个,补充的lda,sta用于数组操作
6.错误信息
(1)const,var,procedure后应为标识符
(2)常数说明中的=后应是数字
(3)常数说明中的标识符后应是=
(4)常数说明中的=写成了:
=
(5)漏掉了,或;
(6)过程说明后的符号不正确(应是语句开始符,或过程定义符)
(7)应是语句开始符
(8)标识符未说明
(9)程序结尾丢了句号。
(10)语句之间漏了;
(11)call后应为标识符
(12)赋值语句中,赋值号左部标识符属性应是变量
(13)赋值号左部标识符属性应是赋值号
(14)程序体内语句部分的后跟符不正确
(15)call后标识符属性应为过程
(16)条件语句中丢了then
(17)丢了end或;
(18)while循环语句中丢了do
(19)语句后的符号不正确
6.名字表结构
structtablestruct{
charname[al];enumobjectkind;intval;intlevel;intadr;intsize;
//扩充名字表结构,增加一个data域保存数组的下界
intdata;/*其他数据,对arrays来说是下界*/}
7.语法描述图:
图3程序语法描述图
图4分程序语法描述图
图5语句语法描述图
图6条件语法描述图
图7表达式语法描述图
图8项语法描述图
图9因子语法描述图
四、功能扩充
1.语句处理中加入for循环语句
if(sym==forsym){
getsymdo;
if(sym!
=lparen)error(34);//没有左括号出错
else{
getsymdo;
statementdo(nxtlev,ptx,lev);//S1代码
if(sym!
=semicolon)error(10);//语句缺少分号出错
else{
cx1=cx;
getsymdo;
conditiondo(nxtlev,ptx,lev);//E代码
if(sym!
=semicolon)error(10);//语句缺少分号出错
else{
cx2=cx;
gendo(jpc,0,0);
cx3=cx;
gendo(jmp,0,0);
getsymdo;
cx4=cx;
statementdo(nxtlev,ptx,lev);//S2代码
if(sym!
=rparen)error(22);//缺少右括号出错
else{
gendo(jmp,0,cx1);
getsymdo;
cx5=cx;
statementdo(nxtlev,ptx,lev);//S3代码
code[cx3].a=cx5;
gendo(jmp,0,cx4);
code[cx2].a=cx;
}}}}}
2.在语句处理中增加repeat-until语句
if(sym==repeatsym){
cx1=cx;
getsymdo;
statementdo(nxtlev,ptx,lev);
if(sym==untilsym){
getsymdo;
conditiondo(nxtlev,ptx,lev);
cx2=cx;
gendo(jpc,0,0);
code[cx2].a=cx1;}
elseerror(33);//没有写until出错
}}
3.扩充++和—运算符
对于++和--运算符,扩充时要注意存在两个情况:
1)作为语句的时候;2)作为表达式中的因子的时候。
注意:
扩充时增加因子开始符facbegsys[incs]=true和facbegsys[decs]=true。
扩充的语法描述见结构设计中的PL/0分程序和主要语句的语法描述中的描述图,详细代码见程序。
1)作为语句的时候,有四种情况:
a++;a--;++a;--a;文法的EBNF表示形式为:
<自增自减语句>:
:
=<标识符>[++|--]|[++|--]<标识符>
文法分析过程大体如下图:
++a和—aa++和a—
生成中间代码对于a++;++a;和a--;--a;语句的处理如下:
先将变量的值取出放在栈顶,后将1入栈,后执行加法或减法运算oprv指令的2(加法)、3(减法),后将运算后的栈顶值存回变量。
a++;和++a;语句的中间代码:
lod03;lit01;opr02;sto03;
a--;和--a;语句的中间代码:
lod03;lit01;opr03;sto03;
2)作为因子的时候,有两种情况:
a++和a--作为因子,比如:
b:
=a++*a--;语句++a和--a作为因子,比如:
b:
=--a+2*++a;语句
文法的EBNF表示形式为:
<表达式>:
:
=...[++|--]<标识符>|<标识符>[++|--]...其中的...表示前后都可以有其他的项或因子生成中间代码
A对于因子++a和--a的中间代码生成处理和a++;等语句处理一样;B对于因子a++和a—的中间代码生成处理如下:
a++:
lod03;lit01;opr02;sto03;lod03;lit01;opr03;
a--:
lod03;lit01;opr03;sto03;lod03;lit01;opr02;先将变量的值取出放在栈顶,后将1入栈,后执行加法或减法运算opr指令的2(加法)、3(减法),后将运算后的栈顶值存回变量,后将变量的值又取出来放入栈顶,后将1入栈,如果是a++就执行减法,如果是a—就执行加法,以实现先用a的值后再加1。
4.语句处理中加入if-then-else语句
在原有程序if(sym==then){...}后加入下列代码:
cx1=cx;
gendo(jpc,0,0);
statementdo(fsys,ptx,lev);
if(sym==elsesym){
getsymdo;
cx2=cx;
gendo(jmp,0,0);
code[cx1].a=cx;
statementdo(fsys,ptx,lev);
code[cx2].a=cx;}
else
code[cx1].a=cx;
5.修改不等号#为!
=
注释源程序中的ssym['#']=neq语句,在getsym中加入下列代码:
//修改不等号为!
=
elseif(ch=='!
'){
getchdo;
if(ch=='='){
sym=neq;
getchdo;}
elsesym=nul;}
6.加入对一维数组的支持
本程序将数组看做变量的一种,由var声明函数调用array声明函数完成数组声明,这样就处加入文件输出的相关语句外,可以完全保留block函数和enter函数;通过改写factor函数使数组因子包括了后缀的索引号,这样就可以调用通用的表达式函数赋值数组了。
为了方便完成数组相关功能,扩充了虚拟机处理代码。
数组的越界及非法调用错误处理没有完善,仅给出了错误代码。
在头文件pl0.h中:
/*定义两个全局变量,用来保存数组定义的下界和容量*/
staticintg_arrBase=0;
staticintg_arrSize=0;
/*虚拟机代码*///增加lda,sta专门由于数组的处理
//增加两个虚拟机指令lda,sta,分别用来从数组中取数和存到数组中
//数组元素的访问和存储,是将()后的当成表达式,先处理,得到元素的索引,放在栈顶
//最后根据数组的首地址,得到某个元素的地址
enumfct{...lda,sta}
//扩充名字表结构,增加一个data域保存数组的下界
structtablestruct{...intdata;/*其他数据,对arrays来说是下界*/}
/*名字表中的类型*/
enumobject{...arrays//添加数组类型}
//数组声明处理,下界和上界允许已经定义过的常量标识符
intarraydeclaration(int*ptx,intlev,int*pdx);
//数组元素索引计算与“虚拟机”生成
intarraycoef(bool*fsys,int*ptx,intlev);
在源程序文件pl0.c中:
编写相关的arraydeclaration,arraycoef两个功能函数:
/*数组声明处理,下界和上界允许已经定义过的常量标识符*/
intarraydeclaration(int*ptx,intlev,int*pdx){
chararrId[al];/*暂存数组标识名,避免被覆盖*/
intcstId;/*常量标识符的位置*/
intarrBase=-1,arrTop=-1;/*数组下界、上界的数值*/
getsymdo;
if(sym==lbrack){/*标识符之后是'[',则识别为数组*/
strcpy(arrId,id);
/*检查下界*/
getsymdo;
if(sym==ident){
if((cstId=position(id,(*ptx)))!
=0)
arrBase=(constant==table[cstId].kind)?
table[cstId].val:
-1;}
elsearrBase=(sym==number)?
num:
-1;
if(-1==arrBase){
error(50);
return-1;}
/*检查冒号*/
getsymdo;
if(sym!
=colon){
error(50);
return-1;}
/*检查上界*/
getsymdo;
if(sym==ident){
if((cstId=position(id,(*ptx)))!
=0)
arrTop=(constant==table[cstId].kind)?
table[cstId].val:
-1;}
elsearrTop=(number==sym)?
num:
-1;
if(arrTop==-1){
error(50);//随意指定,因为原程序对错误号的规划极差!
return-1;}
/*检查']'*/
getsymdo;
if(sym!
=rbrack){
error(50);
return-1;}
/*上下界是否符合条件检查*/
g_arrSize=arrTop-arrBase+1;
g_arrBase=arrBase;
if(g_arrSize<=0){
error(50);
return-1;}
/*恢复数组的标识符*/
strcpy(id,arrId);
return1;}
return0;}
/*数组元素索引计算与“虚拟机”生成*/
intarraycoef(bool*fsys,int*ptx,intlev){
boolnxtlev[symnum];
inti=position(id,*ptx);
getsymdo;
if(sym==lbrack){/*索引是括号内的表达式*/
getsymdo;
memcpy(nxtlev,fsys,sizeof(bool)*symnum);
nxtlev[rbrack]=true;
expressiondo(nxtlev,ptx,lev);
if(sym==rbrack){
gendo(lit,0,table[i].data);
gendo(opr,0,3);/*系数修正,减去下界的值*/
return0;}
elseerror(22);/*缺少右括号*/
}
elseerror(51);/*数组访问错误*/
return-1;}
修改函数enter,block,vardeclaration,factor及statement,使其具备处理数组的功能:
//将数组变量登陆名字表
voidenter(enumobjectk,int*ptx,intlev,int*pdx){
...
casearrays:
/*数组名,进行记录下界等*/
table[(*ptx)].level=lev;
table[(*ptx)].adr=(*pdx);
table[(*ptx)].data=g_arrBase;
table[(*ptx)].size=g_arrSize;
*pdx=(*pdx)+g_arrSize;
break;
...}
//输出数组名字表到控制台和文件fas.tmp
intblock(intlev,inttx,bool*fsys){
...
casearrays:
printf("%darray%s",i,table[i].name);
printf("lev=%daddr=%dsize=%d\n",table[i].level,table[i].adr,table[i].size);
fprintf(fas,"%darray%s",i,table[i].name);
fprintf(fas,"lev=%daddr=%dsize=%d\n",table[i].level,table[i].adr,table[i].size);
//加入数组声明
intvardeclaration(int*ptx,intlev,int*pdx){
intarrayRet=-1;
if(sym==ident){
arrayRet=arraydeclaration(ptx,lev,pdx);/*先判断数组*/
switch(arrayRet){
case1:
enter(arrays,ptx,lev,pdx);//填写数组名
getsymdo;
break;
case0:
enter(variable,ptx,lev,pdx);//填写名字表
//getsymdo;
break;
default:
return-1;/*数组定义解析出错*/}
}
elseerror(4);/*var后应是标识*/
return0;}
/*当因子是数组型变量时,调用arraycodefdo将数组的索引入栈顶,之后按vatiabler变量操作*/
intfactor(bool*fsys,int*ptx,intlev){
...
switch(table[i].kind){
...
casearrays:
/*名字为数组名*/
arraycoefdo(fsys,ptx,lev);
gendo(lda,lev-table[i].level,table[i].adr);/*找到变量地址并将其值入栈*/
...}...}
intstatement(bool*fsys,int*ptx,intlev){
...
if(sym==ident){
...
if((table[i].kind!
=variable)&&(table[i].kind!
=arrays)){
error(12);i=0;}
else{
enumfctfct1=sto;switch(table[i].kind){
casearrays:
arraycoefdo(fsys,ptx,lev);
fct1=sta;/*数组保存,要多读一个栈*/
casevariable:
{...}
}}}
//增加的两个虚拟机代码的处理:
lda,sta
voidinterpret(){
...
caselda:
/*数组元素访问,当前栈顶为元素索引,执行后,栈顶变成元素的值*/
s[t-1]=s[base(i.l,s,b)+i.a+s[t-1]];
break;
casesta:
/*栈顶的值存到数组中,索引为次栈顶*/
t-=2;
s[base(i.l,s,b)+i.a+s[t]]=s[t+1];
break;...}
五、调试及运行结果
1.测试repeat...until...语句功能测试文件:
4.txt测试结果:
vara,b,n;
begin
b:
=4;
a:
=1;
read(n);
repeat
a:
=a+1;
b:
=b+1;
untila>n;
write(a);
write(b);
end.
当输入的n为3时,repeat...until...语句中的循环体执行3次,所以a=4,b=72.测试增加的++,--功能
测试文件:
2.txt测试结果:
vara,b;
begin
a:
=1;
b:
=3;
a++;
b--;
write(a);
write(b);