PL0编译器扩充(注释 else 数组)
注释的扩充
注释的扩充写在getch中,就是词法分析,在getch中每次会读取一行放入数组line中然后进行读取,那么注释的基本思想就是遇到注释就不放入line中,直接跳过就好。
int getch()
{
if (cc == ll) /* getch使用的计数器,cc表示当前字符(ch)的位置 */
{
/* feof()只用于测试流文件的结束,当到达结尾时,返回非0;
当文件内部位置指针指向文件结束时,并未立即置位FILE结构中的文件结束标记,
只有再执行一次读文件操作,才会置位结束标志,此后调用feof才会返回为真。*/
//如果到达文件结尾返回-1
if (feof(fin))
{
printf("program incomplete");
return -1;
}
ll=0;
cc=0;
printf("%d ", cx);/*cx 虚拟机代码指针, 取值范围[0, cxmax-1] 输出序号*/
fprintf(fa1,"%d ", cx); /* 输出源文件及其各行对应的首地址 */
ch = ' ';
//回车
while (ch != 10)
{
//fscanf(fin,"%c", &ch)
//richard
if (EOF == fscanf(fin,"%c", &ch))
{
line[ll] = 0;
break;
}
//end richard
//注释扩充
if(ch=='{'){
//line[ll-1]='';
//遇到注释输出并跳过
printf("%c", ch);
while(ch!='}'){
if (EOF == fscanf(fin,"%c", &ch))
{
error(31);//如果文件结束也没有读到右括号,报错
line[ll] = 0;
break;
}
printf("%c", ch);
}
}//注释扩充结束
else{
printf("%c", ch);
//printf("%c", '#');
fprintf(fa1, "%c", ch);//把ch写入fal
line[ll] = ch;
ll++;
}
}
printf("\n");
fprintf(fa1, "\n");
}
ch = line[cc];
cc++;
return 0;
}
扩充else
else 在statement 语句处理中,初始化init和getsym中需要添加的东西比较简单,大家自己加就行。在statement 原本的if是通过jpc条件跳转指令来查看是否需要跳过then语句,gen函数主要作用就是存储指令,cx记录指令code中指令位置,增加else语句之后我们如果条件成立就直接跳到else语句,不成立的话肯定会顺序执行,那么就需要在then语句之后增加一条无条件跳转指令jmp用于跳过else语句。所以用cx1记录jpc指令位置,cx2记录语句执行完的位置。就像下面的一段指令:
7和8是then语句jpc如果条件成立就会直接跳到10执行else语句,要不就顺序执行到jmp指令跳过else语句从27继续执行。
6 jpc 0 10
7 lod 1 3
8 sto 1 4
9 jmp 0 27
10 lod 1 3
11 lit 0 10
12 opr 0 10
13 jpc 0 21
14 lit 0 2
15 lod 1 3
16 opr 0 4
17 lit 0 1
18 opr 0 3
19 sto 1 4
20 jmp 0 27
21 lit 0 3
22 lod 1 3
23 opr 0 4
24 lit 0 11
25 opr 0 3
26 sto 1 4
27 opr 0 0
if (sym == ifsym) /* 准备按照if语句处理 */
{
getsymdo;
memcpy(nxtlev,fsys,sizeof(bool)*symnum);
nxtlev[thensym]=true;
nxtlev[dosym]=true; /*后跟符号为then或do*/
nxtlev[elsesym] = true;/* 后跟符号为else */
conditiondo(nxtlev,ptx,lev); /*调用条件处理(逻辑运算)函数*/
if(sym==thensym)
{
getsymdo;
}
else
{
error(16); /*缺少then*/
}
cx1=cx; /*保存当前指令地址*/
gendo(jpc,0,0); /*生成条件跳转指令,跳转地址暂写0*/
/*指令 “JPC 0 A”
条件转移指令
若栈顶为 0,则转移至地址 A,即置指令地址寄存
器为A ;T 减1
*/
statementdo(fsys,ptx,lev); /*处理then后的语句*/
//添加处理else语句
if(sym == elsesym){
cx2=cx;//记录jmp指令位置
gendo(jmp,0,0);//将来会直接跳转到else语句后面
code[cx1].a=cx;/* 经statement处理后,cx为then后语句执行完的位置,它正是前面未定的跳转地址 *///为jpc指令赋值
getsymdo;
statementdo(fsys,ptx,lev);
code[cx2].a=cx; //当前是else后面的语句结束位置,if语句执行后应当跳转至此 为jmp指令最后一个赋值
}
else
code[cx1].a = cx;
数组扩充
数组扩充分为三块:(1)数组是变量 那么数组的声明需要在 vardeclaration中进行补充,声明处理主要是向名字表中添加。 名字表中原来的变量添加是
case variable: /* 变量名字 */
table[(*ptx)].level = lev;//层数
table[(*ptx)].adr = (*pdx);//基地址
(*pdx)++;//偏移量加一
break;
对于数组偏移量是(上界-下界+1)那么原来加入名字表的函数肯定不能用
因为我们的pdx需要增加的偏移量是(上界-下界+1),所以需要增加新的变量名称array和新的增加名字表函数enterArray。
int vardeclaration(int* ptx,int lev,int* pdx)
{
if (sym == ident)
{
int low=0,/*下界*/upper=0;/*上界*/
bool flag=false;//判断是否找到下界
getsymdo;
if(sym==lparen){//如果下一个字符是左括号则可能是数组
char array_name[11];//存储变量名字,用于填写名字表
memcpy(array_name,id,11);//将变量名放入
getsymdo;//继续分析
if(sym==number){//下一个是数字//下界
low=num;
}
else if(sym==ident){//判断下一个是否为常量
low=isConst(position(id,*ptx));
if(low==-1){
flag=true;
error(31);//没有找到常量
}
}
else{
flag=true;
error(31);//既不是数字也不是常量
}
if(!flag){//找到下界后继续分析
getsymdo;
if(sym==colon){//下一个是“:”
getsymdo;
if(sym==number){
upper=num;
}
else if(sym==ident){
upper=isConst(position(id,*ptx));
if(upper==-1){
flag=true;
error(31);
}
}
else{
flag=true;
error(31);
}
if(!flag){
if(low<upper){//下界小于上界
getsymdo;
if(sym==rparen){//数组最后一个是右括号 完美
enterArray(ptx,lev,pdx,low,upper,array_name);//加入名字表·
}
else{
error(31);//缺少右括号
}
}
else{
error(31);//下界大于上界
}
}
}
else{
error(31);//不是冒号
}
getsymdo;
}
}
else{
enter(variable, ptx, lev, pdx); // 填写名字表
}
}
else
{
error(4); /* var后应是标识 */
}
return 0;
}
(2)语句处理,即读、写和赋值在statement语句处理中添加。
(1)read处理
例如read(a(1+2))那么中间的需要通过表达式处理进行分析
expressiondo这个函数会把表达式的值放到栈顶,这并不符合我们的要求,我们希望他给我们返回一个值,
然后使用sto指令,把地址传进去。那我们怎么解决这个问题?答案就是我们也把处理延后。
既然偏移量在栈顶,我们把基地址也放到栈顶,然后相加,那么栈顶的就是真实地址,
然后我们就会发现sto指令并不能满足我们,因为它使用的是名字表的地址,而我们的地址在栈顶,
所以我们自己再写一个指令sto2来完成我们需要的功能*/
if (sym == readsym) /* 准备按照read语句处理 */
{
getsymdo;
if (sym != lparen)//左括号
{
error(34); /* 格式错误,应是左括号 */
}
else
{
do {
getsymdo;
if (sym == ident)
{
i = position(id, *ptx); /* 查找要读的变量 */
}
else
{
i=0;
}
if (i == 0)
{
error(35); /* read()中应是声明过的变量名 */
}
else if (table[i].kind != variable && table[i].kind!=array)//增加数组
{
error(32); /* read()参数表的标识符不是变量, thanks to amd */
}
else
{
if(table[i].kind == variable)//如果是普通变量read(abc)
{
gendo(opr, 0, 16); /* 生成输入指令,读取值到栈顶 */
gendo(sto, lev-table[i].level, table[i].adr); /* 储存到变量 */
getsymdo;
}
else if(table[i].kind == array){//read(a(1))
/*现在考虑我们读入数组,假如我们有定义a(0:3),将来要read(a(1))。
我们在名字表中能查找到的只有a,也就是a(0)的地址,那我们怎么知道a(1)在哪里存储呢?
其实就是基地址+偏移量。基地址就是数组首地址,即a的地址,偏移量就是1,a(1)的地址就是a.addres+1。
考虑到可能有read(a(0+1))这样的情况,我们使用原有的表达式处理函数。
expressiondo这个函数会把表达式的值放到栈顶,这并不符合我们的要求,我们希望他给我们返回一个值,
然后使用sto指令,把地址传进去。那我们怎么解决这个问题?答案就是我们也把处理延后。
既然偏移量在栈顶,我们把基地址也放到栈顶,然后相加,那么栈顶的就是真实地址,
然后我们就会发现sto指令并不能满足我们,因为它使用的是名字表的地址,而我们的地址在栈顶,
所以我们自己再写一个指令sto2来完成我们需要的功能*/
getsymdo;//左括号
expressiondo(nxtlev, ptx,true,lev,i);//括号内的表达式,将偏移量放到栈顶
gendo(lit, 0, table[i].adr);//基地址 将数组首地址放到栈顶
gendo(opr, 0, 2);//当前栈顶是真实地址 次栈顶与栈顶相加
gendo(opr, 0, 16); /* 生成输入指令,读取值到栈顶 */
gendo(sto2, lev - table[i].level, 0);
}
}
} while (sym == comma); /* 一条read语句可读多个变量 */
}
if(sym != rparen)
{
error(33); /* 格式错误,应是右括号 */
while (!inset(sym, fsys)) /* 出错补救,直到收到上层函数的后跟符号 */
{
getsymdo;
}
}
else
{
getsymdo;
}
}
int expression(bool* fsys, int* ptx,bool isArray, int lev,int index)
{
enum symbol addop; /* 用于保存正负号 */
bool nxtlev[symnum];
if(sym==plus || sym==minus) /* 开头的正负号,此时当前表达式被看作一个正的或负的项 */
{
addop = sym; /* 保存开头的正负号 */
getsymdo;
memcpy(nxtlev, fsys, sizeof(bool)*symnum);
nxtlev[plus] = true;
nxtlev[minus] = true;
termdo(nxtlev, ptx, lev); /* 处理项 */
if (addop == minus)
{
gendo(opr,0,1); /* 如果开头为负号生成取负指令 */
}
}
else /* 此时表达式被看作项的加减 */
{
memcpy(nxtlev, fsys, sizeof(bool)*symnum);
nxtlev[plus] = true;
nxtlev[minus] = true;
termdo(nxtlev, ptx, lev); /* 处理项 */
}
while (sym==plus || sym==minus)
{
addop = sym;
getsymdo;
memcpy(nxtlev, fsys, sizeof(bool)*symnum);
nxtlev[plus] = true;
nxtlev[minus] = true;
termdo(nxtlev, ptx, lev); /* 处理项 */
if (addop == plus)
{
gendo(opr, 0, 2); /* 生成加法指令 */
}
else
{
gendo(opr, 0, 3); /* 生成减法指令 */
}
}
//如果是数组,下标应该减去下界值才能得到真实的地址值
if (isArray == true){
gendo(lit, 0, table[index].low);//取数组下界到栈顶
gendo(opr, 0, 3);/*次栈顶的值减去栈顶的值,结果存入次栈顶 T 减 1*/
}
return 0;
}
对于写的对应处理并不是在statement中处理的。而是在因子处理中进行的。
int factor(bool* fsys, int* ptx, int lev)
{
int i;
bool nxtlev[symnum];
testdo(facbegsys, fsys, 24); /* 检测因子的开始符号 */
/* while(inset(sym, facbegsys)) */ /* 循环直到不是因子开始符号 */
if(inset(sym,facbegsys)) /* BUG: 原来的方法var1(var2+var3)会被错误识别为因子 */
{
if(sym == ident) /* 因子为常量或变量 */
{
bool ar = false;
i = position(id, *ptx); /* 查找名字 */
if (i == 0)
{
error(11); /* 标识符未声明 */
}
else
{
switch (table[i].kind)
{
case constant: /* 名字为常量 */
gendo(lit, 0, table[i].val); /* 直接把常量的值入栈 */
break;
case variable: /* 名字为变量 */
gendo(lod, lev-table[i].level, table[i].adr); /* 找到变量地址并将其值入栈 */
break;
case procedur: /* 名字为过程 */
error(21); /* 不能为过程 */
break;
case array: //数组 a(1) , a(1*2+3)等等
getsymdo; //忽略左小括号(
getsymdo; //获取数组下标
expressiondo(nxtlev, ptx, true,lev,i); //下标的表达式,将相对偏移量(已经减掉下界值)放到栈顶
gendo(lit, 0, table[i].adr); //将基地址放到栈顶
gendo(opr, 0, 2); //下标的相对偏移量+基地址=相对地址
gendo(lod2, lev - table[i].level, 0);
break;
}
}
getsymdo;
}
else
{
if(sym == number) /* 因子为数 */
{
if (num > amax)
{
error(31);
num = 0;
}
gendo(lit, 0, num);
getsymdo;
}
else
{
if (sym == lparen) /* 因子为表达式 */
{
getsymdo;
memcpy(nxtlev, fsys, sizeof(bool)*symnum);
nxtlev[rparen] = true;
expressiondo(nxtlev, ptx,false, lev,0);
if (sym == rparen)
{
getsymdo;
}
else
{
error(22); /* 缺少右括号 */
}
}
testdo(fsys, facbegsys, 23); /* 因子后有非法符号 */
}
}
}
return 0;
}
赋值和read中写的差不多
```cpp
if (sym == ident) /* 准备按照赋值语句处理 */
{
/*
* 查找名字的位置.
* 找到则返回在名字表中的位置,否则返回0.
*
* idt: 要查找的名字
* tx: 当前名字表尾指针
*/
i = position(id, *ptx);
if (i == 0)
{
error(11); /* 变量未找到 */
}
else
{
if(table[i].kind != variable&& table[i].kind != array)
{
error(12); /* 赋值语句格式错误 */
i = 0;
}
else
{
if (table[i].kind == variable){
getsymdo;
if (sym == becomes)
{
getsymdo;
}
else
{
error(13); /* 没有检测到赋值符号 */
}
memcpy(nxtlev, fsys, sizeof(bool)*symnum);
expressiondo(nxtlev, ptx,false, lev,i); /* 处理赋值符号右侧表达式 */
if (i != 0)
{
/* expression将执行一系列指令,但最终结果将会保存在栈顶,执行sto命令完成赋值 */
gendo(sto, lev - table[i].level, table[i].adr);
}
}
else
{
getsymdo;
expressiondo(nxtlev, ptx, true,lev,i);//下标的表达式,将偏移量(已经减掉下界值)放到栈顶
gendo(lit, 0, table[i].adr);//将基地址放到栈顶
gendo(opr, 0, 2);//当前栈顶是真实地址
if (sym == becomes){
getsymdo;
}
else{
error(13);
}
memcpy(nxtlev, fsys, sizeof(bool)*symnum);
expressiondo(nxtlev, ptx,false, lev,i); /* 处理赋值符号右侧表达式 */
if (i != 0)
{
/* expression将执行一系列指令,但最终结果将会保存在栈顶,执行sto命令完成赋值 */
gendo(sto2, lev - table[i].level, 0);
}
}
}
}//if (i == 0)
}
扩充好的资源已经上传至https://download.csdn.net/download/qq_41979507/12097302