1. 课程设计简介
任务: 扩展PL/0编译程序功能
目的:扩充PL/0编译程序功能,
要求:
- 阅读、研究PL/0编译程序源文件。
- 在上述工作基础上,可有选择地补充、完善其中词法分析、语法分析、语义分析、目标代码生成、
目标代码解释执行等部分的功能。如以语法分析部分为例,则可以增加处 理更多语法成分的功能,
如可处理一维数组、++、–、+=、-=、*=、/=、%(取余)、!(取反)、repeat、for、else、
开方、处理注释、错误提示、标示符或变量中可以有下划线等。 - 设计编制典型的运行实例,以便能反应出自己所作的改进。
2. PL/0编译程序解读
程序总体实现
主要功能简介
- 词法分析
词法分析子程序名为getsym,功能是从源程序中读出一一个单词符号(token), 把它的信息放入全局变量sym、id和num中,语法分析器需要单词时, 直接从这三个变量中获得。
语法分析
语法分析使用自上向下的递归子程序法语法分析主要由分程序分析过程block,常量定义分析过程constdeclaration, 变量定义分析过程vardeclaration,语句处理过程statement,表达式处理过程expressio,项处理过程term, 因子处理过程factor,条件处理过程condition构成,还有出错报告过程error,代码生成过程gen, 测试代码合法性及错误恢复过程test,查询名字表函数position,输出目标代码清单listcode。
- 目标代码结构和代码生成
PL/0编译程序所产生的目标代码是一种栈式机的语言,此类栈式机没有累加器和通用寄存器, 有一个栈式存储器,有四个控制寄存器(指令寄存器 I,指令地址寄存器 P,栈顶寄存器 T 和基址寄存器 B),算术逻辑运算都在栈顶进行。
- 错误处理的原理和技术
首先,错误处理的原则一是尽可能准确指出错误位置和错误属性,二是尽可能进行校正。 PL/0编译程序根据这两种原则,对语法错误的处理采用了两种办法: (1)对于丢失标点类的等容易矫正的错误,就指出出错位置,并加以矫正。 (2) 对于难以矫正的错误,为了使该错误不影响到整个程序后续的编译, 把错误尽量的局限在一个局部的语法单位中。
3. PL/0功能扩充
3.1 增加语句for(<语句>;<条件>;<语句>);
Step1: 在pl0.h中修改
pl0.h中在enum symbol { } 符号枚举集中加入
forsym, 并更改符号的个数 symnum
Step2: 增加保留字名字并增加对应的保留字符号
名字: strcpy(&(word[6][0]),"for");
符号: wsym[6]=forsym;
(注: 顺序按26个字母的顺序重新排列)
Step3: 增加语句开始符号集
statbegsys[forsym]=true;
Step4: 语句处理函数内添加关于for语句的处理
- int i , cx1 , cx2 , cx3 , cx4 , cx5; (cx3,cx4,cx5为新增加的地址)
if(sym == forsym)
{
getsymdo;
if(sym != lparen) error(34);//没有左括号出错
else
{
getsymdo;
statementdo(nxtlev, ptx, lev); //S1代码
//语句缺少分号出错
if(sym != semicolon) error(10);
else
{
/*cx是当前指令的地址 保存判断条件操作的 位置 */
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;
//S2代码
statementdo(nxtlev, ptx, lev);
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; // 反填跳出循环的地址,与if类似
}
}
}
}
}
3.2 扩充语句if <条件> then <语句> else <语句>;
Step1: 在pl0.h中修改
pl0.h中在enum symbol { } 符号枚举集中加入
elsesym, 并更改符号的个数 symnum
Step2: 增加保留字名字并增加对应的保留字符号
名字: strcpy(&(word[4][0]),"else");
符号: wsym[4]=elsesym;
(注: 顺序按26个字母的顺序重新排列)
Step3: 语句处理函数内添加关于if语句的处理
if(sym==ifsym) /*准备按照if语句处理*/
{
getsymdo;
memcpy(nxtlev,fsys,sizeof(bool)*symnum);
nxtlev[thensym]=true;
nxtlev[dosym]=true; /*后跟符号为then或do*/
conditiondo(nxtlev,ptx,lev); /*调用条件处理(逻辑运算)函数*/
if(sym==thensym)
{
getsymdo;
}
else
{
error(16); /*缺少then*/
}
cx1=cx; /*保存当前指令地址*/
gendo(jpc,0,0); /*生成条件跳转指令,跳转地址暂写0*/
statementdo(fsys,ptx,lev); /*处理then后的语句*/
if(sym==semicolon)
{
getsymdo;
if(sym==elsesym) /*then语句后出现else*/
{
getsymdo;
cx2=cx;
/*cx为当前的指令地址,cx+1即为then语句执行后的else语句的位置,回填地址*/
code[cx1].a=cx+1;
gendo(jmp,0,0);
statementdo(fsys,ptx,lev);
/*经statement处理后,cx为else后语句执行
完的位置,它正是前面未定的跳转地址,回填地址*/
code[cx2].a=cx;
}
else
{
/*经statement处理后,cx为then后语句执行完的位置,它正是前面未定的跳转地址*/
code[cx1].a=cx;
}
}
else
{
error(5);
}
}
3.3 增加语句repeat <语句> until <条件>;
Step1: 在pl0.h中修改
pl0.h中在enum symbol { } 符号枚举集中加入
repeatsym, untilsym, 并更改符号的个数 symnum
Step2: 增加保留字名字并增加对应的保留字符号
名字: strcpy(&(word[11][0]),"repeat");
strcpy(&(word[13][0]),"until");
符号: wsym[11]=repeatsym;
wsym[13]=untilsym;
(注: 顺序按26个字母的顺序重新排列)
Step3: 增加语句开始符号集
statbegsys[repeatsym]=true;
Step4: 语句处理函数内添加关于repeat语句的处理
else if(sym==repeatsym)
{
cx1=cx; /*保存当前指令地址*/
getsymdo;
memcpy(nxtlev,fsys,sizeof(bool)*symnum);
nxtlev[untilsym]=true;
statementdo(fsys,ptx,lev);
if(sym==semicolon)
{
getsymdo;
if(sym==untilsym)
{
getsymdo;
conditiondo(fsys,ptx,lev);
/*经condition处理后,cx1为repeat后循环语句的位置,条件为假时一直循环*/
gendo(jpc,0,cx1);
}
}
else
{
error(5);
}
}
3..4. 增加自增自减运算
对于++和–的扩充,分为两种情况:
- 作为语句的情况
例如: ++i,--i,i++,i--;
作为表达式的因子的情况
例如: b:=a++;b:=a--; b:=++a;b:=--a;
第一种作为语句情况的扩充:
Step1: 在pl0.h中修改pl0.h中在enum symbol { } 符号枚举集中加入 addadd, subsub, 并更改符号的个数 symnum
Step2: 语句处理函数内添加关于++ 和–的处理
else if(sym==addadd) /*检测到后置++符号*/ { getsymdo; // 如果++后面跟的量可以在名字表中找到 if(i!=0) { 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); } } else if(sym==subsub) /*检测到后置--符号*/ { getsymdo; // 如果 -- 后面跟的量可以在名字表中找到 if(i!=0) { 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); } } if(sym==addadd) /*检测到前置++符号*/ { getsymdo; if(sym==ident) /*后面跟的是变量*/ { i=position(id,*ptx); if(i==0) { error(11); } else { if(table[i].kind!=variable) { /*++后没跟变量,出错*/ error(12); i=0; } else { /*++后跟变量,处理生成中间代码*/ getsymdo; /*先取 值到栈顶*/ gendo(lod,lev-table[i].level,table[i].adr); gendo(lit,0,1); /*将1放到栈顶*/ gendo(opr,0,2); /*加法,即+1,栈顶加次栈顶*/ /*出栈取值到内存*/ gendo(sto,lev-table[i].level,table[i].adr); } } } } else if(sym==subsub) /*检测到前置--符号*/ { getsymdo; if(sym==ident) /*后面跟的是变量*/ { i=position(id,*ptx); if(i==0) { error(11); } else { /*--后没跟变量,出错*/ if(table[i].kind!=variable) { error(12); i=0; } else /*--后跟变量,处理生成中间代码*/ { if(table[i].kind==variable) /*后跟变量*/ { getsymdo; /*先取值到栈顶*/ gendo(lod,lev-table[i].level,table[i].adr); gendo(lit,0,1); /*将1放到栈顶*/ gendo(opr,0,3); /*减法,即-1,栈顶减次栈顶*/ /*出栈取值到内存*/ gendo(sto,lev-table[i].level,table[i].adr); } } } } }
第二种作为语句情况的扩充:
Step1: 添加因子开始符号集
facbegsys[addadd]=true; /*前置++*/
facbegsys[subsub]=true; /*前置--*/
Step2: 在因子处理函数factor中添加 ++和–相关处理
if(sym==addadd) /*因子出2现b:=a++类型*/
{
gendo(lit,lev-table[i].level,1); /*将值入栈*/
/*加法,即+1,栈顶加次栈顶*/
gendo(opr,lev-table[i].level,2);
/*出栈取值到内存*/
gendo(sto,lev-table[i].level,table[i].adr);
/*取值到栈顶*/
gendo(lod,lev-table[i].level,table[i].adr);
gendo(lit,0,1);
gendo(opr,0,3); /*栈顶值减*/
getsymdo;
}
else if(sym==subsub) /*因子出现b:=a--类型*/
{
gendo(lit,lev-table[i].level,1); /*将值入栈*/
/*减法,即-1,栈顶减次栈顶*/
gendo(opr,lev-table[i].level,3);
/*出栈取值到内存*/
gendo(sto,lev-table[i].level,table[i].adr);
gendo(lod,lev-table[i].level,table[i].adr);
gendo(lit,0,1);
gendo(opr,0,2); /*栈顶值加*/
getsymdo;
}
else if(sym==addadd) /*因子出现b:=++a类型*/
{
getsymdo;
if(sym==ident)
{
getsymdo;
i=position(id,*ptx);
if(i==0)
{
error(11);
}
else
{
if(table[i].kind==variable) /*变量*/
{
/*先加后再用a*/
/*先取值到栈顶*/
gendo(lod,lev-table[i].level,table[i].adr);
gendo(lit,0,1);/*将值入栈*/
gendo(opr,0,2);/*加法,即+1,栈顶加次栈顶*/
/*出栈取值到内存*/
gendo(sto,lev-table[i].level,table[i].adr);
/*取值到栈顶*/
gendo(lod,lev-table[i].level,table[i].adr);
}
}
}
}
else if(sym==subsub) /*因子出现b:=--a类型*/
{
getsymdo;
if(sym==ident)
{
getsymdo;
i=position(id,*ptx);
if(i==0)
{
error(11);
}
else
{
if(table[i].kind==variable) /*变量*/
{
/*先减后再用a*/
/*先取值到栈顶*/
gendo(lod,lev-table[i].level,table[i].adr);
gendo(lit,0,1); /*将值入栈*/
gendo(opr,0,3); /*减法,即-1,栈顶减次栈顶*/
/*出栈取值到内存*/
gendo(sto,lev-table[i].level,table[i].adr);
/*取值到栈顶*/
gendo(lod,lev-table[i].level,table[i].adr);
}
}
}
}
3.5. 增加+=,-=,*=,/=运算;
Step1: 在pl0.h中修改
pl0.h中在enum symbol { } 符号枚举集中加入
addequal,subequal, timeseql, slasheql,, 并更
改符号的个数 symnum
Step2: 在getSym()函数中处理:
if(ch=='+')
{
getchdo;
if(ch=='=')
{
sym=addequal;
getchdo;
}
else{
sym=plus;
}
}
else
{
if(ch=='-')
{
getchdo;
if(ch=='=')
{
sym=subequal;
getchdo;
}else{
sym=minus;
}
}
else
{
if(ch=='*')
{
getchdo;
if(ch=='=')
{
sym=timeseql;
getchdo;
}
else
{
sym=times;
}
}
else
{
if(ch=='/')
{
getchdo;
if(ch=='=')
{
sym=slasheql;
getchdo;
}
getchdo;
} else{
sym=slash;
}
}else{
/*当符号不满足上述条件时,全部按照单字符号处理*/
sym=ssym[ch];
if(sym!=period)
{
getchdo;
}
}
}
}
Step3: 在语句处理函数中添加处理
else if(sym==addequal) /*检测到+=符号*/
{
getsymdo;
/*找到变量地址并将其值入栈*/
gendo(lod,lev-table[i].level,table[i].adr);
if(sym==semicolon)
{
getsymdo;
printf("+=后面直接跟了分号");
}
memcpy(nxtlev,fsys,sizeof(bool)* symnum);
expressiondo(nxtlev,ptx,lev);
gendo(opr,0,2);
if(i!=0)
{
gendo(sto,lev-table[i].level,table[i].adr);
}
}
else if(sym==subequal) /*检测到-=符号*/
{
getsymdo;
/*找到变量地址并将其值入栈*/
gendo(lod,lev-table[i].level,table[i].adr);
if(sym==semicolon)
{
getsymdo;
}
memcpy(nxtlev,fsys,sizeof(bool)* symnum);
expressiondo(nxtlev,ptx,lev);
gendo(opr,0,3);
if(i!=0)
{
gendo(sto,lev-table[i].level,table[i].adr);
}
}
else if(sym==timeseql) /*检测到*=符号*/
{
getsymdo;
/*找到变量地址并将其值入栈*/
gendo(lod,lev-table[i].level,table[i].adr);
if(sym==semicolon)
{
getsymdo;
}
memcpy(nxtlev,fsys,sizeof(bool)* symnum);
expressiondo(nxtlev,ptx,lev);
gendo(opr,0,4);
if(i!=0)
{
gendo(sto,lev-table[i].level,table[i].adr);
}
}
else if(sym==slasheql) /*检测到/=符号*/
{
getsymdo;
/*找到变量地址并将其值入栈*/
gendo(lod,lev-table[i].level,table[i].adr);
if(sym==semicolon)
{
getsymdo;
}
memcpy(nxtlev,fsys,sizeof(bool)* symnum);
expressiondo(nxtlev,ptx,lev);
gendo(opr,0,5);
if(i!=0)
{
gendo(sto,lev-table[i].level,table[i].adr);
}
}
3.6 增加一维数组的处理 (例如: a[1:2] )
在添加数组处理时, 将数组看做变量的一种,由var声明函数调用array声明函数完成数组声明,
这样就处加入文件输出的相关语句 外,可以完全保留block函数和enter函数;通过改写
factor函数使数组因子包括了后缀的索引号,这样就可以调用通用的表达式函数赋值数组了。
为了方便完成数组相关功能,扩充了虚拟机处理代码。
Step1: 在pl0.h中修改
/* 定义两个全局变量,用来保存数组定义的下界和容量 */
static int g_arrBase = 0;
static int g_arrSize = 0;
/* 虚拟机代码 */
增加lda,sta专门由于数组的处理
enum fct {...lda, sta }
这两个虚拟机指令lda,sta,分别用来从数组中取数和存到数组中
数组元素的访问和存储,是将()后的当成表达式,先处理,得到元素的索引,放在栈顶
最后根据数组的首地址,得到某个元素的地址
/* 扩充名字表结构,增加一个data域保存数组的下界 */
struct tablestruct{
...int data; /* 其他数据,对arrays来说是下界*/
}
/* 名字表中的类型*/
enum object {...arrays //添加数组类型}
/* 数组声明处理, 下界和上界允许已经定义过的常量标识符 */
int arraydeclaration(int* ptx, int lev, int* pdx);
/* 数组元素索引计算与“虚拟机”生成 */
int arraycoef(bool *fsys,int *ptx,int lev);
Step2:
pl0.cpp中添加相关arraydeclaration,arraycoef数组处理函数
int arraydeclaration(int* ptx, int lev, int* pdx)
{
/* 暂存数组标识名,避免被覆盖 */
char arrId[al];
int cstId; /* 常量标识符的位置 */
int arrBase=-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[].val:-1;
}
else
{
arrBase=(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;
}
else
{
arrTop=(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);
return 1;
}
return 0;
}
/*
* 数组元素索引计算与“虚拟机”生成
*/
int arraycoef(bool *fsys,int *ptx,int lev)
{
bool nxtlev[symnum];
int i = 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); /* 系数修正,减去下界的值 */
return 0;
}
else
{
error(22); /* 缺少右括号 */
}
}
else
{
error(51); /* 数组访问错误 */
}
return -1;
}
Step3: 修改函数enter,block,vardeclaration,factor及statement,使其具备处理数组的功能:
/将数组变量登陆名字表
void enter(enum object k, int * ptx, int lev, int * pdx){
...
case arrays: /* 数组名,进行记录下界等*/
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
int block(int lev, int tx, bool * fsys){
...
case arrays:
printf("%d array %s ", i, table[i].name);
printf("lev=%d addr=%d size=%d\n", table[i].level, table[i].adr, table[i].size);
fprintf(fas, "%d array %s ", i, table[i].name);
fprintf(fas, "lev=%d addr=%d size=%d\n", table[i].level, table[i].adr, table[i].size);
//加入数组声明
int vardeclaration(int * ptx, int lev, int * pdx){
int arrayRet=-1;
if (sym==ident){
/* 先判断数组*/
arrayRet=arraydeclaration(ptx,lev,pdx);
switch(arrayRet){
case 1:
enter(arrays,ptx,lev,pdx); // 填写数组名
getsymdo;
break;
case 0:
enter(variable,ptx,lev,pdx); // 填写名字表
break;
default:
return -1; /* 数组定义解析出错*/
}
}
else error(4); /* var后应是标识*/
return 0;
}
/*当因子是数组型变量时,调用arraycodefdo将数组的索引入栈顶, 之后按vatiabler变量操作*/
int factor(bool * fsys, int * ptx, int lev){
...
switch (table[i].kind)
{
...
case arrays: /* 名字为数组名*/
arraycoefdo(fsys,ptx,lev);
/* 找到变量地址并将其值入栈*/
gendo(lda,lev-table[i].level,table[i].adr);
... }
... }
int statement(bool * fsys, int * ptx, int lev)
{
...
if (sym == ident){
...
if ((table[i].kind != variable)&&(table[i].kind != arrays))
{
error(12);
i = 0;
}
else{
enum fct fct1 = sto;
switch(table[i].kind)
{
case arrays:
arraycoefdo(fsys, ptx, lev);
fct1 = sta; /* 数组保存,要多读一个栈*/
case variable:{...}
}
}
}
//增加的两个虚拟机代码的处理:lda,sta
void interpret(){
...
case lda: /* 数组元素访问,当前栈顶为元素索引,执行后,栈顶变成元素的值*/
s[t-1] = s[base(i.l,s,b) + i.a + s[t-1]];
break;
case sta: /* 栈顶的值存到数组中,索引为次栈顶*/
t-=2;
s[base(i.l,s,b) + i.a + s[t]] = s[t+1];
break; ...
}
3.7 增加取反功能(算术取反和逻辑取反)
对于取反的扩充大体上分为两种方式,一种是算术取反,
一种是逻辑取反,两种取反分别用 ! 和 @ 来实现
对于每种取反的实现,也分为两种情况:
(1) 作为语句的情况
例如: !a 和 @a
(2)作为表达式的因子的情况
例如: b:=!a; b:= @a;
作为语句情况的扩充:
Step1: 在pl0.h中修改
pl0.h中在enum symbol { } 符号枚举集中加入
not, logic 并更改符号的个数 symnum
( not代表! , logic代表@ )
Step2: 初始化
ssym['!'] = not;
ssym['@'] = logic;
Step3: 在getSym( )中添加
if(ch=='@')
{
sym=logic;
}
if(ch=='!')
{
sym=not;
}
Step4: 在statement中添加
if(sym==logic) /*检测逻辑取反符号@*/
{
getsymdo;
if(sym==ident)
{
i=position(id,*ptx);
if(i==0)
{
error(11);
}
else
{
if(table[i].kind!=variable)
{
/*++后没跟变量,出错*/
error(12);
i=0;
}
else
{
/*++后跟变量,处理生成中间代码*/
getsymdo;
gendo(lod,lev-table[i].level,table[i].adr);
gendo(lit,0,0);
gendo(opr,0,8);
gendo(sto,lev-table[i].level,table[i].adr);
}
}
}
}
if(sym==not)
{
getsymdo;
if(sym==ident)
{
i=position(id,*ptx);
if(i==0)
{
error(11);
}
else
{
if(table[i].kind!=variable)
{
/*++后没跟变量,出错*/
error(12);
i=0;
}
else
{
/*++后跟变量,处理生成中间代码*/
getsymdo;
gendo(lod,lev-table[i].level,table[i].adr);
gendo(opr,0,1);
gendo(sto,lev-table[i].level,table[i].adr);
}
}
}
作为表达式的因子情况的扩充:
Step1: 增加因子开始符号集
facbegsys[not]=true;
facbegsys[logic]=true;
Step2: 在因子处理函数factor()中添加
else if(sym==logic)
{
getsymdo;
if(sym==ident)
{
i=position(id,*ptx);
if(i==0)
{
error(11);
}
else
{
/*先取值到栈顶*/
gendo(lod,lev-table[i].level,table[i].adr);
gendo(lit,0,0);
gendo(opr,0,8);/*栈顶和次栈顶相比较*/
/*出栈取值到内存*/
gendo(sto,lev-table[i].level,table[i].adr);
}
}
}
else if(sym==not)
{
getsymdo;
if(sym==ident)
{
i=position(id,*ptx);
if(i==0)
{
error(11);
}
else
{
/*先取值到栈顶*/
gendo(lod,lev-table[i].level,table[i].adr);
gendo(opr,0,1);/*取反*/
/*出栈取值到内存*/
gendo(sto,lev-table[i].level,table[i].adr);
}
}
}
3.8 增加错误提示
Step1:
在出错处理error()中加入switch语句,打印错误信息:
switch(n)
{
case 1:
printf("****常数说明中的“=”写成“:=”。\n");
break;
case 2:
printf("****常数说明中的“=”后应是数字。\n");
break;
case 3:
printf("****常数说明中的标识符后应是“=”。\n");
break;
case 4:
printf("****const,var,procedure后应为标识符。\n");
break;
case 5:
printf("****漏掉了“,”或“;“。\n");
break;
case 6:
printf("****过程说明后的符号不正确(应是语句开始符,或过程定义符)\n");
break;
case 7:
printf("****应是语句开始符。\n");
break;
case 8:
printf("****程序体内语句部分的后跟符不正确。\n");
break;
case 9:
printf("****程序结尾丢了句号“.”\n");
break;
case 10:
printf("****语句之间漏了“;”。\n");
break;
case 11:
printf("****标识符未说明。\n");
break;
case 12:
printf("****赋值语句中,赋值号左部标识符属性应是变量。\n");
break;
case 13:
printf("****赋值语句左部标识符后应是赋值号“:=”。\n");
break;
case 14:
printf("****call后应为标识符。\n");
break;
case 15:
printf("****call后标识符属性应为过程。\n");
break;
case 16:
printf("****条件语句中丢了“then”。\n");
break;
case 17:
printf("****丢了“end”或“;”。\n");
break;
case 18:
printf("****while型循环语句中丢了“do”。\n");
break;
case 19:
printf("****语句后的符号不正确。\n");
break;
case 20:
printf("****应为关系运算符。\n");
break;
case 21:
printf("****表达式内标识符属性不能是过程。\n");
break;
case 22:
printf("****表达式中漏掉右括号“)”。\n");
break;
case 23:
printf("****因子后的非法符号。\n");
break;
case 24:
printf("****表达式的开始符不能是此符号。\n");
break;
case 30:
printf("****常数越界。\n");
break;
case 31:
printf("****表达式内常数越界。\n");
break;
case 32:
printf("****嵌套深度超过允许值。\n");
break;
case 33:
printf("****read或write或for语句中缺“)”。\n");
break;
case 34:
printf("****read或write或for语句中缺“(”。\n");
break;
case 35:
printf("****read语句括号中的标识符不是变量。\n");
break;
}
3.9 增加注释处理功能
Step1: 在getSym()函数中添加
if(ch=='/')
{
getchdo;
if(ch=='=')
{
sym=slasheql;
getchdo;
}
else if(ch=='*')/*添加注释*/
{
getchdo;
printf("注释/*%c",ch);
while(1)
{
getchdo;
printf("%c",ch);
if(ch=='*')
{
getchdo;
if(ch=='/')
{
printf("%c注释\n",ch);
break;
}
continue;
}
}
getchdo;
}
}
else
{
sym=slash;
}
3.10 增加取余(%)运算
在提供的指令中没有直接可以实现取余的指令,但是取余运算可以
通过 - , / 和 * 的组合来实现
a % b = a - (a/b)*b ;
例如:
3 % 5 = 3 - (3/5)*5 = 3-0*5 = 3
14 % 5 = 14 - (14/5)*5 = 14 - 2*5 = 4
Step1: 在pl0.h中修改
pl0.h中在enum symbol { } 符号枚举集中加入
mod, 并更改符号的个数 symnum
Step2: 初始化取余%符号
ssym['%'] = mod;
Step3: 在getSym()函数中添加关于%的识别处理
if(ch=='%')
{
sym=mod;
}
Step4: 在语句处理函数中添加关于取余运算的处理
else if(sym==mod) /*检测到%符号*/
{
getsymdo;
//例如a%b = a - (a/b)*b
//将a的值入栈
/*找到变量地址并将其值入栈*/
gendo(lod,lev-table[i].level,table[i].adr);
if(sym==semicolon)
{
getsymdo;
}
memcpy(nxtlev,fsys,sizeof(bool)* symnum);
expressiondo(nxtlev,ptx,lev);
//将a和b的值相除
gendo(opr,0,5);
/再取b的值到栈顶
gendo(lod,lev-table[i+1].level,table[i+1].adr);
gendo(opr,0,4);
gendo(lod,lev-table[i].level,table[i].adr);
gendo(opr,0,3);
gendo(opr,0,1);
if(i!=0)
{
gendo(fct1,lev-table[i].level,table[i].adr);
}
}
4. 扩充功能的测试和运行结果
4.1 测试for循环
(1) 测试文件
var a,i;
begin
a:=0;
for(i:=1;i<=3;i:=i+2)
begin
a:=a+i;
end;
write(a);
end.
第一次循环,i=1, 1<3, a=0+1 = 1
第二次循环,i=3, 3=3, a=1+3 = 4
第三次,i=5 >3 跳出循环
故a的正确值应为 4
(2) 运行结果
4.2 测试if-then-else循环
(1) 测试文件
var a,b;
begin
a:=0;
read(b);
if b<=3
then
write(b);
else
write(a);
end.
当输入的b的值小于等于3时,输出b的值,
否则输出a的值,即0
(2) 运行结果
当输入的值小于等于3时
当输入的值大于3时
4.3 测试repeat-until循环
(1) 测试文件
var x;
begin
x:=1;
repeat
x:=x+1;
until x>5;
write(x);
end.
当x的值大于5时才输出, 应输出6
(2) 运行结果
4.4 测试++,–功能
(1) 测试文件
var i,a;
begin
i:=1;
i++;
write(i);
++i;
write(i);
i--;
write(i);
--i;
write(i);
a:=i++;
write(a);
a:=++i;
write(a);
a:=i--;
write(a);
a:=--i;
write(a);
end
i初始值为1, 执行i++后, i的值变为2
执行++i后, i的值又变为3
执行i - - 后, i的值变为 2
执行 - - i后, i的值变为1
执行a:=i++ 后,a的值变为1, i的值变为2
执行a:=++i 后,a的值变为3, i的值变为3
执行a:=i-- 后,a的值变为3, i的值变为2
执行a:= --i后,a的值变为1, i的值变为1
(2) 运行结果
4.5 测试+=、-=、*=、/=功能
(1) 测试文件
var a,b,c;
begin
a:=1;
b:=2;
c:=3;
b+=a;
c-=b;
write(b);
write(c);
a+=1;
b*=a;
write(b);
c+=b;
c/=a;
write(c);
end.
初始值a为1, b为2, c为3
执行b+=a后, b的值变为3
执行c - = b后, c的值变为0
执行a+=1后, a的值变为2
执行b*= a后, b的值变为6
执行c+=b后, c的值变为6
执行c/=a后, c的值变为3
(2) 运行结果
4.6 测试一维数组处理功能
(1) 测试文件
var a[1:2];
begin
a[1]:=1;
a[2]:=2;
a[1]:=a[1]*3;
write(a[1]);
write(a[2]);
end.
初始化数组a[1:2], a[1]初始值为1, a[2]初始值为2,
执行a[1]:=a[1]*3 后a[1]的值变为3
(2) 运行结果
4.7 测试取反功能(算术取反和逻辑取反)
算术取反:
(1) 测试文件
var a,b,c;
begin
a:= -5;
c:=3;
b:=!a;
!c;
write(b);
write(c);
end.
a的初始值为 -5, c的初始值为3, 执行b=!a 后,
b的值变为5, 执行 !c 后, c的值变为 - 3
(2) 运行结果
逻辑取反:
(1) 测试文件
var a,b,c;
begin
a:=0;
b:= -3;
c:=@b;
@a;
@b;
write(a);
write(b);
write(c);
end.
a的初始值为0 , b的初始值为-3, 执行c:=@b 后,
b的值变为0 , c的值也变为0 , 执行@a 后, a的值
变为1, 执行@b 后, b的值变为1, 故最后应输出 1, 1 , 0
(2) 运行结果
4.8 测试错误提示功能
(1) 测试文件
var a,b
begin
a=-5;
b:=3;
write(a);
write(b);
end.
第一句定义变量的语句缺少分号,
给变量a赋值的语句 := 写成了=
(2) 运行结果
4.9 测试注释处理功能
(1) 测试文件
var x;
begin
x:=1;
repeat
x:=x+1;
until x>5; /* 这是注释 */
write(x);
end.
(2) 运行结果
5.10 测试取余(%)运算
(1) 测试文件
var a,b,c,;
begin
a:=16;
b:=3;
c:= -2;
a%b;
c%b;
write(a);
write(c);
end.
a的初始值为16 , b的初始值为3, c的初始值为 -2 , 执行
a%b后, a的值变为1,执行c%b后, c的值为- 2,所以应该输
出1, -2
(2) 运行结果
5. 项目源代码
百度网盘下载 ( 包含项目报告 )
- 百度网盘链接: 链接:https://pan.baidu.com/s/1-1Ir02jDdBmVo6NUiU4wsg
- 提取码:vkwm
github下载 ( 只含源代码 )
- github下载地址: https://github.com/fyf2016/PL-0.git
注: 两个地址都可以下载