编译原理课程设计报告PL0编译程序改进及完善.docx
《编译原理课程设计报告PL0编译程序改进及完善.docx》由会员分享,可在线阅读,更多相关《编译原理课程设计报告PL0编译程序改进及完善.docx(30页珍藏版)》请在冰豆网上搜索。
编译原理课程设计报告PL0编译程序改进及完善
燕山大学
《编译原理课程设计》
题目:
《PL/0编译程序改进及完善》
姓名:
简越
班级:
06级计算机应用3班
学号:
060104010084
日期:
2009年7月15日
设计题目:
PL/0编译程序改进及完善。
设计目的:
阅读研究,改进设计和调试一个简单的编译程序。
加深对编译理论和过程的了解。
设计要求:
1.有选择的对PL/0编译源程序补充,完善.
2.设计编译典型的运行实例,以便反应出自己作出改进后的编具有的功能。
设计思想:
PL/0语言可以看成PASCAL语言的子集,它的编译程序是一个编译解释执行系统。
PL/0的目标程序为假想栈式计算机的汇编语言,与具体计算机无关。
PL/0的编译程序和目标程序的解释执行程序都是用PASCAL语言书写的,因此PL/0语言可在配备PASCAL语言的任何机器上实现。
其编译过程采用一趟扫描方式,以语法分析程序为核心,词法分析和代码生成程序都作为一个独立的过程,当语法分析需要读单词时就调用词法分析程序,而当语法分析正确需要生成相应的目标代码时,则调用代码生成程序。
用表格管理程序建立变量、常量和过程表示符的说明与引用之间的信息联系。
当源程序编译正确时,PL/0编译程序自动调用解释执行程序,对目标代码进行解释执行,并按用户程序的要求输入数据和输出运行结果。
主要变量说明:
/*变量说明*/
FILE*fas;/*输出名字表*/
FILE*fa;/*输出虚拟机代码*/
FILE*fa1;/*输出源文件及其各行对应的首地址*/
FILE*fa2;/*输出结果*/
boollistswitch;/*显示虚拟机代码与否*/
booltableswitch;/*显示名字表与否*/
charch;/*获取字符的缓冲区,getch使用*/
enumsymbolsym;/*当前的符号*/
charid[al+1];/*当前ident,多出的一个字节用于存放0*/
intnum;/*当前number*/
intcc,ll;/*getch使用的计数器,cc表示当前字符(ch)的位置*/
intcx;/*虚拟机代码指针,取值范围[0,cxmax-1]*/
charline[81];/*读取行缓冲区*/
chara[al+1];/*临时符号,多出的一个字节用于存放0*/
structinstructioncode[cxmax];/*存放虚拟机代码的数组*/
charword[norw][al];/*保留字*/
enumsymbolwsym[norw];/*保留字对应的符号值*/
enumsymbolssym[256];/*单字符的符号值*/
charmnemonic[fctnum][5];/*虚拟机代码指令名称*/
booldeclbegsys[symnum];/*表示声明开始的符号集合*/
boolstatbegsys[symnum];/*表示语句开始的符号集合*/
boolfacbegsys[symnum];/*表示因子开始的符号集合*/
/*目标指令*/
1、LIT:
将常量值取到运行栈顶.
2、LOD:
将变量放到运行栈顶.
3、STO:
将栈顶的内容送入某变量单元中.
4、CAL:
调用过程的指令.
5、INT:
为被调用的过程(或主程序)在运行栈中开辟数据区.
6、JMP:
无条件转移指令.
7、JPC:
条件转移指令,当栈顶的布尔值为真时,顺序执行,否则转向域的地址.
8、OPR:
系运算符和算术运算指令.将栈顶和次栈顶的内容进行运算,结果存放栈顶.
/*函数说明*/
voiderror(intn,intline)
说明:
出错处理函数,打印出错信息,错误总数加1。
intgetch()
说明:
读取字符函数,返回字符。
intgetsym()
说明:
读取下一单词符号
intposition(char*idt,inttx);
说明:
字符在符号表中位置查询函数
返回值:
返回标识符在符号表中的索引
intgen(enumfctx,inty,intz);
说明:
生成P代码指令
inttest(bool*s1,bool*s2,intn);
说明:
测试当前符号是否合法,若不合法,打印出错信息并进行跳读
voidenter(enumobjectk,int*ptx,intlev,int*pdx);
说明:
在符号表中登录分程序说明部分出现的名字
intconstdeclaration(int*ptx,intlev,int*pdx);
说明:
处理常量说明,并将常量名及相应信息填入符号表
intvardeclaration(int*ptx,intlev,int*pdx);
说明:
处理变量说明,并将变量名及相应信息填入符号表
intstatement(bool*fsys,int*ptx,intlev);
说明:
分析处理各种语句
intcondition(bool*fsys,int*ptx,intlev);
说明:
分析处理条件式
返回值:
由参数x返回求值结果的类型
voidlistcode(intcx0);
说明:
打印P代码
intblock(intlev,inttx,bool*fsys);
说明:
PL0编译器对外的接口,由main()调用
voidinterpret();
说明:
解释执行P代码
intbase(intl,int*s,intb);
说明:
基地址处理函数
返回值:
返回变量的基地址值
intexpression(bool*fsys,int*ptx,intlev)
说明:
表达式处理,由参数返回结果类型
intterm(bool*fsys,int*ptx,intlev);
说明:
项处理,由参数返回结果类型
intfactor(bool*fsys,int*ptx,intlev);
说明:
因子处理,由参数返回结果类型
intinset(inte,bool*s);
intaddset(bool*sr,bool*s1,bool*s2,intn);
intsubset(bool*sr,bool*s1,bool*s2,intn);
intmulset(bool*sr,bool*s1,bool*s2,intn);
说明:
使用数组实现集合的集合运算
算法描述:
在原算法基础上我进行了一下修改
1.增加单词:
保留字ELSE
2.运算符+=,-=,++,――
3.增加条件语句的ELSE子句
为此添加了ELSESYM,为了实现如上要求的扩充,必须再正确地添加JIAJIA,JIANJIAN,JIADENG,JIANDENG等几个个SYMBOL,具体必须包括以下三个方面的修改或添加:
在枚举变量SYMBOL的定义内添加JIAJIA,JIANJIAN,JIADENG,JIANDENG和ELSESYM;然后在在源程序文件中加上保留字ELSE,为了识别例如A:
=++A之类的功能,还需在因子开始符号中加入JIAJIA,JIANJIAN。
这样,我们就正确地加入了五个将用来作为扩充语言功能的SYM。
为了实现功能首先需要在词法分析中加入识别单词功能
增加JIAJIA(++)、JIADENG(+=)、JIANJIAN(——)、JIANDENG(—=)的词法分析,代码修改如下:
if(ch=='+')
{
getchdo;
if(ch=='=')
{
sym=jiadeng;
getchdo;
}
else
{
if(ch=='+')
{
sym=jiajia;
getchdo;
}
else
{
sym=plus;
}
}
}
else
{
if(ch=='-')
{
getchdo;
if(ch=='=')
{
sym=jiandeng;
getchdo;
}
else
{
if(ch=='-')
{
sym=jianjian;
getchdo;
}
else
{
sym=minus;
}
}
}
这样,我们就完成了对词法分析器的修改。
然后JIAJIA.,JIANJIAN操作的语法图有如下两个:
语句
因子
根据以上语法图,我们只要对语句处理程序和因子处理程序进行修改添加,即可实现增加INC和DEC操作,首先对语句处理程序进行如下修改:
语句中形如++A,——A修改:
if(sym==jiajia||sym==jianjian)//++a,--a
{
if(sym==jiajia)
{
getsymdo;
if(sym==ident)
{
i=position(id,*ptx);
if(i==0)
{
error(11);
}
else
{
if(table[i].kind!
=variable)
{
error(12);
i=0;
}
else
{
gendo(lod,lev-table[i].level,table[i].adr);//生成指令,将变量放到栈顶
gendo(lit,0,1);//生成指令,把常量取到运行栈顶
gendo(opr,0,2);//生成指令,栈顶加次栈顶
gendo(sto,lev-table[i].level,table[i].adr);//生成指令,结果写回变量地址单元
getsymdo;
}
}
}
}
else//--a
{
getsymdo;
if(sym==ident)
{
i=position(id,*ptx);
if(i==0)
{
error(11);
}
else
{
if(table[i].kind!
=variable)
{
error(12);
i=0;
}
else
{
gendo(lod,lev-table[i].level,table[i].adr);//生成指令,取变量放大栈顶
gendo(lit,0,1);//生成指令,取常数到栈顶
gendo(opr,0,3);//生成指令,栈顶减次栈顶
gendo(sto,lev-table[i].level,table[i].adr);//生成指令,结果写回变量地址单元
getsymdo;
}
}
}
}
随后修改如A++,A——
if(sym==jiajia||sym==jianjian)//a++,a--
{
addop=sym;
gendo(lod,lev-table[i].level,table[i].adr);//把变量的值压入栈
gendo(lit,0,1);
if(addop==jiajia)
{
gendo(opr,0,2);
gendo(sto,lev-table[i].level,table[i].adr);
}
else
{
gendo(opr,0,3);
gendo(sto,lev-table[i].level,table[i].adr);
}
getsymdo;
}
这样,对JIAJIA和JIANJIAN操作就扩充完成。
扩充+=和-=操作
这两个操作都是一种对变量进行赋值的形式,其合法的语句形式的语法图如下所示:
根据图3,在已经处理部分的基础上,添加对+=运算和-=运算的扩充,相关代码添加如下:
if(sym==jiadeng||sym==jiandeng)
{
addop=sym;
getsymdo;
gendo(lod,lev-table[i].level,table[i].adr);/*把变量的值压入栈*/
factordo(nxtlev,ptx,lev);
if(addop==jiadeng)
{
gendo(opr,0,2);/*生成加法指令*/
}
else
{
gendo(opr,0,3);/*生成减法指令*/
}
}
这样就完成了对+=运算和-=运算的添加。
增加条件语句else
该语句的语法图如
只要在STATEMENT修改如下代码:
if(sym==elsesym){
cx2=cx;
gendo(jmp,0,0);
code[cx1].a=cx;
getsymdo;
statementdo(nxtlev,ptx,lev);/*处理else后的语句*/
code[cx2].a=cx;
}
else
{
code[cx1].a=cx;
}
这样ELSE语句扩充完毕
为了显示错误更清晰我增加了显示错误原因的语句
具体代码在ERROR中实现
switch(n)
{
case1:
printf("常数说明中的“=”写成了“:
=”。
\n");
break;
case2:
printf("常数说明中的“=”后应是数字。
\n");
break;
case3:
printf("常数说明中的标识符后应是“=”。
\n");
break;
case4:
printf("const,var,procedure后应为标识符。
\n");
break;
case5:
printf("漏掉了“,”或“;”。
\n");
break;
case6:
printf("过程说明后的符号不正确(应是语句开始符,或过程定义符)。
\n");
break;
case7:
printf("应是语句开始符。
\n");
break;
case8:
printf("程序体内语句部分的后跟符号不正确。
\n");
break;
case9:
printf("程序结尾丢了句号“.”。
\n");
break;
case10:
printf("语句之间漏了“;”。
\n");
break;
case11:
printf("标志符未说明。
\n");
break;
case12:
printf("在“:
=”或“+=”或“-=”左部标志符属性应是变量。
\n");
break;
case13:
printf("缺少“:
=”或“+=”或“-=”。
\n");
break;
case14:
printf("call后应为标志符。
\n");
break;
case15:
printf("call后标志符属性应为过程。
\n");
break;
case16:
printf("条件语句中丢了“then”。
\n");
break;
case17:
printf("丢了“end”或“;”。
\n");
break;
case18:
printf("while型循环语句中丢了“do”。
\n");
break;
case19:
printf("语句后的符号不正确。
\n");
break;
case20:
printf("应为关系运算符。
\n");
break;
case21:
printf("表达式内标志符属性不能是过程。
\n");
break;
case22:
printf("表达式内中漏掉右括号“)”。
\n");
break;
case23:
printf("因子后的非法符号。
\n");
break;
case24:
printf("表达式的开始符不能是此符号。
\n");
break;
case30:
printf("number的最大位数为。
\n");
break;
case31:
printf("数越界。
\n");
break;
case32:
printf("最大的嵌套层数不能大于层。
\n");
break;
case33:
printf("格式错误,应是右括号。
\n");
break;
case34:
printf("格式错误,应是左括号。
\n");
break;
case35:
printf("read()中的变量名没有声明过。
\n");
break;
case36:
printf("“;”写成了“,”。
\n");
break;
}
这样代码修改完毕
我还改进了一个形如vara;b;或者consta;b;语句错误
添加错误编号36(“;”写成了“,”)。
if(sym==semicolon)
{
getsymdo
if(sym==ident)
{
error(36);
}
改变了声明符号时无法重复或无法改变声明顺序。
具体方法是讲源代码中
memcpy(nxtlev,statbegsys,sizeof(bool)*symnum);
nxtlev[ident]=true;
nxtlev[period]=true;
testdo(nxtlev,declbegsys,7);
增加VARSYM与CONSTSYM后跟符号集
修改后变为
memcpy(nxtlev,statbegsys,sizeof(bool)*symnum);
nxtlev[ident]=true;
nxtlev[period]=true;
nxtlev[constsym]=true;
nxtlev[varsym]=true;
testdo(nxtlev,declbegsys,7);
最后为了实行人性化操作我增加了编译程序循环执行的方法
具体方法是在主函数中加入
while
(1)
{//保证用户连续运行程序
..............
..............
..............
getchar();//退出程序的字符标号
printf("\n退出程序请按q键,继续运行请按其它键!
\t");
scanf("%c",&ch);
if(ch=='q')
break;//退出程序
else
system("cls");//清屏*/
}
调试情况:
形如vara;b;或者consta;b;语句错误
文件名为test1.txt
vara;
constd=2;
varb;
constc=1;
begin
a:
=d;
b:
=c;
write(a);
write(b)
end.
改动前程序执行情况
改动后程序执行情况
我还改进了一个形如vara;b;或者consta;b;语句错误
添加错误编号36(“;”写成了“,”)。
文件名为test2.txt
vara;b;
constc=1;b:
=2;
begin
a:
=c;
write(a)
end.
程序执行情况:
调试ifthenelse
文件名为else.txt
vara,b;
begin
read(a);
ifa<0
then
b:
=-1
else
b:
=1;
write(a);
write(b)
end.
运行结果:
调试++,――
文件名为++.txt
vara,b,c,d;
begin
a:
=1;
b:
=2;
c:
=9;
c++;
c--;
--c;
++c;
a+=--c;
a-=c--;
d:
=(++b)+c;
write(b);
write(c);
write(d);
write(a)
end.
运行结果:
设计技巧:
万事无捷径,只有一步一步脚踏实地。
只有相对的技巧,就是对原程序的错误编号,标记。
对改动的错误利用编译程序的断点功能检查错误。
书写程序的时候,首先画语法图列出框架,在根据框架书写程序。
心得体会:
这次改进还有不完善的地方,例如检查形为CONSTA=1;B=2;时的错误的时候,如果把语句放入BEGIN语句体内会出现新的错误。
所以完善此编译程序还得考虑的更加仔细