内容有点长,代码量7000行+,创造不易,点赞关注加三连!!!有错误可指出,谢谢大咖~
一、实验介绍
1. PL0文法说明
PL0 编译程序总体结构如图所示,该程序的主要语法分析过程采用递归下降子程序法实现。为了将上下文有关文法转化为上下文无关文法,定义符号表进行管理,最终转化为虚拟机代码进行解释执行。PL0 语言编译程序采用以语法分析为核心、一遍扫描的编译方法。词法分析和代码生成作为独立的子程序供语法分析程序调用。语法分析的同时,提供了出错报告和出错恢复的功能。在源程序没有错误编译通过的情况下,调用类 P-code 解释程序解释执行生成的类 P-code代码。
PL/0 的文法分析部分是在编译源程序的 statement 部分,这部分的 EBNF 表示如下:
<语句>::=<赋值语句>|<条件语句>|<当型循环语句>|<过程调用语句>|<读语句>|<写语句>|<复合语句>|< 空>
<赋值语句>::=<标识符>:=<表达式>
<复合语句>::=BRGIN<语句>{<语句>}END
<条件>::=<表达式><关系运算符><表达式>|ODD<表达式>
<表达式>::=[+|-]<项>{<加法运算符><项>}
<项>::=<因子>{<乘法运算符><因子>}
<因子>::=<标识符>|<无符号整数>|‘(’<表达式>‘)’
<条件语句>::=IF<条件>THEN<语句>
<过程调用语句>::=CALL<标识符>
<当型循环语句>::=WHILE<条件>DO<语句>
<读语句>::=READ‘(’<标识符>{,<标识符>}‘)’
<写语句>::=WRITE‘(’<表达式>{,<表达式>}‘)’
2. PL0主函数
PL0主函数 main()执行流程下:首先打开一个文件,返回一个文件指针。如果可以成功打开的话,开始读取字符,如果不是文件结束符时,进入 block()函数,进行语法分析。遇到程序结束符时,统计程序中的语法错误,最后给出相应的提示,并关闭打开的文件,防止文件数据的丢失。
int main()
{
bool nxtlev[symnum];
printf("Input pl/0 file: ");
scanf("%s", fname);
fin = fopen(fname, "r");
if (fin)
{
fname[0]='y';
listswitch = (fname[0]=='y' || fname[0]=='Y');
fname[0]='y';
tableswitch = (fname[0]=='y' || fname[0]=='Y');
fa1 = fopen("fa1.tmp", "w");
fprintf(fa1,"Input pl/0 file: ");
fprintf(fa1,"%s\n",fname);
init();
err = 0;
cc = cx = ll = 0;
ch = ' ';
if(-1 != getsym())
{
fa = fopen("fa.tmp", "w");
fas = fopen("fas.tmp", "w");
addset(nxtlev, declbegsys, statbegsys, symnum);
nxtlev[period] = true;
if(-1 == block(0, 0, nxtlev))
{
fclose(fa);
fclose(fa1);
fclose(fas);
fclose(fin);
printf("\n");
return 0;
}
fclose(fa);
fclose(fa1);
fclose(fas);
if (sym != period)
{
error(9);
}
if (err == 0)
{
fa2 = fopen("fa2.tmp", "w");
interpret();
fclose(fa2);
}
else
{
printf("Errors in pl/0 program");
}
}
fclose(fin);
}
else
{
printf("Can't open file!\n");
}
printf("\n");
return 0;
}
3. 词法分析函数
Getsym()函数用于对词法进行分析。调用 getsym 时,它通过 getch 过程从源程序中获得一个字符。如果这个字符是字母,则继续获取字符或数字,最终可以拼成一个单词,查保留字表,如果查到则为保留字,把 sym 变量赋成相应的保留字类型值;如果没有查到,则这个单词应是一个用户自定义的标识符(可能是变量名、常量名或是过程的名字),把 sym 置为 ident,把这个单词存入 id 变量。查保留字表时使用了二分法查找以提高效率。如果 getch 获得的字符是数字,则继续用 getch 获取数字,并把它们拼成一个整数,然后把 sym 置为 number,并把拼成的数值放入 num 变量。如果识别出其它合法的符号(比如:赋值号、大于号、小于等于号等),则把 sym 则成相应的类型。如果遇到不合法的字符,把 sym 置成 nul 。
int getsym()
{
int i,j,k;
while (ch==' ' || ch==10 || ch==9)
{
getchdo;
}
if (ch>='a' && ch<='z')
{
k = 0;
do {
if(k<al)
{
a[k] = ch;
k++;
}
getchdo;
} while (ch>='a' && ch<='z' || ch>='0' && ch<='9');
a[k] = 0;
strcpy(id, a);
i = 0;
j = norw-1;
do {
k = (i+j)/2;
if (strcmp(id,word[k]) <= 0)
{
j = k - 1;
}
if (strcmp(id,word[k]) >= 0)
{
i = k + 1;
}
} while (i <= j);
if (i-1 > j)
{
sym = wsym[k];
}
else
{
sym = ident;
}
}
else
{
if (ch>='0' && ch<='9')
{
k = 0;
num = 0;
sym = number;
do {
num = 10*num + ch - '0';
k++;
getchdo;
} while (ch>='0' && ch<='9');
k--;
if (k > nmax)
{
error(30);
}
}
else
{
if (ch == ':')
{
getchdo;
if (ch == '=')
{
sym = becomes;
getchdo;
}
else
{
sym=colon;
}
}
else
{
if (ch == '<')
{
getchdo;
if (ch == '=')
{
sym = leq;
getchdo;
}
else
{
sym = lss;
}
}
else
{
if (ch=='>')
{
getchdo;
if (ch == '=')
{
sym = geq;
getchdo;
}
else
{
sym = gtr;
}
}
else if(ch=='+')
{
getchdo;
if(ch=='+')
{
sym=plusplus;
getchdo;
}
else if(ch=='=')
{
sym=plusbk;
getchdo;
}
else
{
sym=plus;
}
}
else if(ch=='-')
{
getchdo;
if(ch=='-')
{
sym=minusminus;
getchdo;
}
else if(ch=='=')
{
sym=minusbk;
getchdo;
}
else
{
sym=minus;
}
}
else if(ch=='/')
{
getchdo;
if(ch=='*')
{
getchdo;
while(1)
{
while(ch!='*')
getchdo;
getchdo;
if(ch=='/')
break;
}
getchdo;
getsym();
}
else if(ch=='/')
{
cc=ll;
ch=' ';
getsym();
}
else
{
sym=slash;
}
}
else
{
sym = ssym[ch];
if (sym != period)
{
getchdo;
}
}
}
}
}
}
return 0;
}
4. 语法语义分析
(1)Block()函数主要用来对常量、变量和过程申明的处理,并进行填表工作。在判断了嵌套层数没有超过规定的层数后,开始分析源程序。首先判断是否遇到了常量声明,如果遇到则开始常量定义,把常量存入符号表。接下去用同样的方法分析变量声明,变量定义过程中会用 dx 变量记录下局部数据段分配的空间个数。然后如果遇到 procedure 保留字则进行过程声明和定义,声明的方法是把过程的名字和所在的层次记入符号表,过程定义的方法就是通过递归调用 block 过程,因为每个过程都是一个分程序。
int block(int lev, int tx, bool* fsys)
{
int i;
int dx;
int tx0;
int cx0;
bool nxtlev[symnum];
dx = 3;
tx0 = tx;
table[tx].adr = cx;
gendo(jmp, 0, 0);
if (lev > levmax)
{
error(32);
}
do {
if (sym == constsym)
{
getsymdo;
do {
constdeclarationdo(&tx, lev, &dx);
while (sym == comma)
{
getsymdo;
constdeclarationdo(&tx, lev, &dx);
}
if (sym == semicolon)
{
getsymdo;
}
else
{
error(5);
}
} while (sym == ident);
}
if (sym == varsym)
{
getsymdo;
do {
vardeclarationdo(&tx, lev, &dx);
while (sym == comma)
{
getsymdo;
vardeclarationdo(&tx, lev, &dx);
}
if (sym == semicolon)
{
getsymdo;
}
else
{
error(5);
}
}while (sym == ident);
}
while (sym == procsym)
{
getsymdo;
if (sym == ident)
{
enter(procedur, &tx, lev, &dx);
getsymdo;
}
else
{
error(4);
}
if (sym == semicolon)
{
getsymdo;
}
else
{
error(5);
}
memcpy(nxtlev, fsys, sizeof(bool)*symnum);
nxtlev[semicolon] = true;
if (-1 == block(lev+1, tx, nxtlev))
{
return -1;
}
if(sym == semicolon)
{
getsymdo;
memcpy(nxtlev, statbegsys, sizeof(bool)*symnum);
nxtlev[ident] = true;
nxtlev[procsym] = true;
testdo(nxtlev, fsys, 6);
}
else
{
error(5);
}
}
memcpy(nxtlev, statbegsys, sizeof(bool)*symnum);
nxtlev[ident] = true;
nxtlev[period]=true;
testdo(nxtlev, declbegsys, 7);
} while (inset(sym, declbegsys));
code[table[tx0].adr].a = cx;
table[tx0].adr = cx;
table[tx0].size = dx;
cx0 = cx;
gendo(inte, 0, dx);
if (tableswitch)
{
printf("TABLE:\n");
if (tx0+1 > tx)
{
printf(" NULL\n");
}
for (i=tx0+1; i<=tx; i++)
{
switch (table[i].kind)
{
case constant:
printf(" %d const %s ", i, table[i].name);
printf("val=%d\n", table[i].val);
fprintf(fas, " %d const %s ", i, table[i].name);
fprintf(fas, "val=%d\n", table[i].val);
break;
case variable:
printf(" %d var %s ", i, table[i].name);
printf("lev=%d addr=%d\n", table[i].level, table[i].adr);
fprintf(fas, " %d var %s ", i, table[i].name);
fprintf(fas, "lev=%d addr=%d\n", table[i].level, table[i].adr);
break;
case procedur:
printf(" %d proc %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 proc %s ", i, table[i].name);
fprintf(fas,"lev=%d addr=%d size=%d\n", table[i].level, table[i].adr, table[i].size);
break;
case array:
printf("%d arr %s ", i, table[i].name);
printf("lev=%d addr=%d size=%d startid=%d\n", table[i].level, table[i].adr, table[i].size,table[i].startid);
fprintf(fas, "%d array %s ", i, table[i].name);
fprintf(fas, "lev=%d addr=%d size=%d startid=%d\n", table[i].level, table[i].adr, table[i].size,table[i].startid);
break;
}
}
printf("\n");
}
memcpy(nxtlev, fsys, sizeof(bool)*symnum);
nxtlev[semicolon] = true;
nxtlev[endsym] = true;
statementdo(nxtlev, &tx, lev);
gendo(opr, 0, 0);
memset(nxtlev, 0, sizeof(bool)*symnum);
testdo(fsys, nxtlev, 8);
listcode(cx0);
return 0;
}
(2)Statement()函数主要用来进行基本语句的判断。判断文法为: <语句>::=<赋值语句>|<条件语句>|<当型循环语句>|<过程调用语句>|<读语句>|<写语句>|<复合语句>|
int statement(bool* fsys, int* ptx, int lev)
{
int i, cx1, cx2,cx3,cx4,cx5;
bool nxtlev[symnum];
if (sym == ident)
{
i = position(id, *ptx);
if (i == 0)
{
error(11);
}
else
{
if(table[i].kind != variable&&(table[i].kind != array))
{
error(12);
i = 0;
}
else
{
enum fct fct1 = sto;
getsymdo;
if(sym == becomes)
{
getsymdo;
memcpy(nxtlev, fsys, sizeof(bool)*symnum);
expressiondo(nxtlev, ptx, lev);
if(i != 0)
{
gendo(sto, lev-table[i].level, table[i].adr);
}
}
else if(sym==lparen)
{
getsymdo;
memcpy(nxtlev, fsys, sizeof(bool)*symnum);
expressiondo(nxtlev, ptx, lev);
getsymdo;
getsymdo;
memcpy(nxtlev, fsys, sizeof(bool)*symnum);
expressiondo(nxtlev, ptx, lev);
gendo(sto, lev-table[i].level, table[i].adr);
}
else if(sym==plusplus)
{
getsymdo;
gendo(lod,lev-table[i].level,table[i].adr);
gendo(lit,0,1);
if(i!=0)
{
gendo(opr,0,2);
gendo(sto,lev-table[i].level,table[i].adr);
}
}
else if(sym==minusminus)
{
getsymdo;
gendo(lod,lev-table[i].level,table[i].adr);
gendo(lit,0,1);
if(i!=0)
{
gendo(opr,0,3);
gendo(sto,lev-table[i].level,table[i].adr);
}
}
else if(sym==plusbk)
{
if(i!=0)
{
gendo(lod,lev-table[i].level,table[i].adr);
}
getsymdo;
memcpy(nxtlev,fsys,sizeof(bool)* symnum);
nxtlev[number]=true;
nxtlev[ident]=true;
expressiondo(nxtlev,ptx,lev);
gendo(opr,0,2);
if(i!=0)
{
gendo(fct1,lev-table[i].level,table[i].adr);
}
}
else if(sym==minusbk)
{
if(i!=0)
{
gendo(lod,lev-table[i].level,table[i].adr);
}
getsymdo;
expressiondo(nxtlev,ptx,lev);
gendo(opr,0,3);
if(i!=0)
{
gendo(fct1,lev-table[i].level,table[i].adr);
}
}
else
{
error(13);
}
}
}
}
else if (sym == forsym)
{
getsymdo;
if(sym != lparen) error(34);
else
{
getsymdo;
statementdo(nxtlev, ptx, lev);
if(sym != semicolon) error(10);
else
{
cx1=cx;
getsymdo;
conditiondo(nxtlev, ptx, lev);
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);
if(sym != rparen) error(22);
else
{
gendo(jmp,0,cx1);
getsymdo;
cx5=cx;
statementdo(nxtlev, ptx, lev);
code[cx3].a=cx5;
gendo(jmp,0,cx4);
code[cx2].a=cx;
}
}
}
}
}
else
{
if (sym == readsym)
{
getsymdo;
if (sym != lparen)
{
error(34);
}
else
{
do {
getsymdo;
if(sym!=percent)
{
error(33);
}
int flag_d=0;
getsymdo;
if(sym==diosym)
{
flag_d=1;
}
getsymdo;
if(sym!=comma)
{
error(33);
}
getsymdo;
if (sym == ident)
{
i = position(id, *ptx);
}
else
{
i=0;
}
if (i == 0)
{
error(35);
}
else
{
if (table[i].kind == constant || table[i].kind == procedur)
{
error(32);
}
else
{
if (table[i].kind == constant || table[i].kind == procedur) {
error(32);
}
else {
getsymdo;
if (sym != lparen) {
gendo(opr, 0, 16);
gen(sto, lev - table[i].level, table[i].adr);
}
else {
getsymdo;
memcpy(nxtlev, fsys, sizeof(bool) * symnum);
nxtlev[rparen] = true;
expressiondo(nxtlev, ptx, lev);
int ltmp = lev - table[i].level;
int adrtmp = table[i].adr;
gendo(arrcheck, table[i].startid, table[i].size);
gendo(jpc, 0, 0);
gendo(opr, 0, 16);
gendo(sta, ltmp, adrtmp);
getsymdo;
}
}
}
}
} while (sym == comma);
}
if(sym != rparen)
{
error(33);
while (!inset(sym, fsys))
{
getsymdo;
}
}
else
{
getsymdo;
}
}
else
{
if (sym == writesym)
{
getsymdo;
if (sym == lparen)
{
do {
getsymdo;
if(sym!=percent)
{
error(33);
}
int flag_d=0;
getsymdo;
if(sym==diosym)
{
flag_d=1;
}
getsymdo;
if(sym!=comma)
{
error(33);
}
getsymdo;
if(sym==ident)
{
i=position(id,*ptx);
if(i==0)
{
error(11);
}
}
memcpy(nxtlev, fsys, sizeof(bool)*symnum);
nxtlev[rparen] = true;
nxtlev[comma] = true;
expressiondo(nxtlev, ptx, lev);
gendo(opr, 0, 14);
} while (sym == comma);
if (sym != rparen)
{
error(33);
}
else
{
getsymdo;
}
}
gendo(opr, 0, 15);
}
else
{
if (sym == callsym)
{
getsymdo;
if (sym != ident)
{
error(14);
}
else
{
i = position(id, *ptx);
if (i == 0)
{
error(11);
}
else
{
if (table[i].kind == procedur)
{
gendo(cal, lev-table[i].level, table[i].adr);
}
else
{
error(15);
}
}
getsymdo;
}
}
else
{
if (sym == ifsym)
{
getsymdo;
memcpy(nxtlev, fsys, sizeof(bool) * symnum);
nxtlev[thensym] = true;
nxtlev[dosym] = true;
conditiondo(nxtlev, ptx, lev);
if (sym == thensym)
{
getsymdo;
}
else
{
error(16);
}
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;
}
else
{
if (sym == beginsym)
{
getsymdo;
memcpy(nxtlev, fsys, sizeof(bool)*symnum);
nxtlev[semicolon] = true;
nxtlev[endsym] = true;
statementdo(nxtlev, ptx, lev);
while (inset(sym, statbegsys) || sym==semicolon)
{
if (sym == semicolon)
{
getsymdo;
}
else
{
error(10);
}
statementdo(nxtlev, ptx, lev);
}
if(sym == endsym)
{
getsymdo;
}
else
{
error(17);
}
}
else
{
if (sym == whilesym)
{
cx1 = cx;
getsymdo;
memcpy(nxtlev, fsys, sizeof(bool)*symnum);
nxtlev[dosym] = true;
conditiondo(nxtlev, ptx, lev);
cx2 = cx;
gendo(jpc, 0, 0);
if (sym == dosym)
{
getsymdo;
}
else
{
error(18);
}
statementdo(fsys, ptx, lev);
gendo(jmp, 0, cx1);
code[cx2].a = cx;
}
else
{
memset(nxtlev, 0, sizeof(bool)*symnum);
testdo(fsys, nxtlev, 19);
}
}
}
}
}
}
}
return 0;
}
(3)Expression()函数用来进行表达式的处理,其中嵌套项处理,因子处理。文法如下: <表达式>::=[+|-]<项>{<加法运算符><项>} <项>::=<因子>{<乘法运算符><因子>} <因子>::=<标识符>|<无符号整数>|‘(’<表达式>‘)’
int expression(bool* fsys, int* ptx, int lev)
{
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);
}
}
return 0;
}
int term(bool* fsys, int* ptx, int lev)
{
enum symbol mulop;
bool nxtlev[symnum];
memcpy(nxtlev, fsys, sizeof(bool)*symnum);
nxtlev[times] = true;
nxtlev[slash] = true;
factordo(nxtlev, ptx, lev);
while(sym==times || sym==slash)
{
mulop = sym;
getsymdo;
factordo(nxtlev, ptx, lev);
if(mulop == times)
{
gendo(opr, 0, 4);
}
else
{
gendo(opr, 0, 5);
}
}
return 0;
}
int factor(bool* fsys, int* ptx, int lev)
{
int i;
bool nxtlev[symnum];
testdo(facbegsys, fsys, 24);
if(inset(sym,facbegsys))
{
if(sym == ident)
{
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:
getsymdo;
if (sym == lparen)
{
int ltmp = lev - table[i].level;
int adrtmp = table[i].adr;
getsymdo;
memcpy(nxtlev, fsys, sizeof(bool) * symnum);
nxtlev[rparen] = true;
expressiondo(nxtlev, ptx, lev);
gendo(lda, ltmp, adrtmp);
}
if (sym == rparen)
{
}
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, lev);
if (sym == rparen)
{
getsymdo;
}
else
{
error(22);
}
}
testdo(fsys, facbegsys, 23);
}
}
}
return 0;
}
二、功能扩展
本综合实验根据已经掌握的编译原理和技术,在读懂读通 “PL/0 编译程序”的基础上,根据要求修改编译源程序,在符合 PL/0 语言基本词法、语法规则的前提下,对 PL/0 语言的功能进行扩充。
同时,完成了基于 EditPlus 的 PL0 语言语言集成开发环境配置,作为 PL0 语言的 IDE 使用,实现了代码折叠、代码缩进、自动补全、语法高亮、显示行号等功能。
其中包含了以下的功能设计:
- I/O 功能扩展:增加了格式化输入、格式化输出。
- 数据结构的扩展:增加了一维数组,包括申明、赋值、读写等基本功能。
- 计算功能的扩展:增加了后置++、后置–、+=、-=运算符号。
- 控制逻辑上的扩展:增加 for 循环、增加 if-then-else 结构。
- 语句注释和代码块的注释。
- 完成基于 EditPlus 的 PL/0 预演集成开发环境配置,实现代码折叠、代码缩进、自动补全、语法高亮、显示行号等功能。
1. 功能扩充后的EBNF表示
这里只展示扩充部分:
<一维数组说明部分>::=VAR<标识符>‘(’<上界>:<下界>‘)’
<上界>::=<常量>|<变量>|<表达式>
<下界>::=<常量>|<变量>|<表达式>
<行注释>::=
<块注释>::=
<复合赋值运算符>::=-=|+=
<后置自增运算符>::=++
<后置自减运算符>::=--
<for 型循环语句>::=for‘(’<赋值语句>;<条件>;<赋值语句>‘)’
<读语句>::=READ‘(’%d{%d}<标识符>{,<标识符>}‘)’
<写语句>::=WRITE‘(’%d{%d}<表达式>{,<表达式>}‘)’
2. 功能扩充后的语法表述图
语法分析过程依赖关系: 分程序语法描述图分别描述了常量申明,变量申明,过程申明和一维数组申明。如图可知,不能将常量定义写在变量定义前面: ) 语句语法描述图中分别对赋值语句,call 语句,begin 语句,if 语句,else 语句,while 语句,read 语句,write 语句,for 循环语句等进行了描述。(关于 for 语句的描述有点不太确定,循环时的箭头应该从括号两边,还是括号内部开始?这里采用括号内部的方案)
3. 新增部分功能设计
符号表更新:
enum symbol {
nul, ident, number, plus, minus,
times, slash, oddsym, eql, neq,
lss, leq, gtr, geq, lparen,
rparen, comma, semicolon, period, becomes,
beginsym, endsym, ifsym, thensym, whilesym,
writesym, readsym, dosym, callsym, constsym,
varsym, procsym, percent, colon, diosym,
elsesym, forsym, plusplus, minusminus,plusbk,
minusbk,
};
Percent(%):用来进行格式化输出输出。 Colon(:):用来实现一维数组的申明,位于上界和下界之间作为分隔符。 Diosym(d):用来进行格式化输出输出。 Elsesym(else):增加 if-then-else 控制逻辑。Forsym(for):增加 for 循环。 Plusplus(++):增加复合运算符后置++。Minusminus(–):增加复合运算符后置–。Plusbk(+=);增加复合运算+=。 Minusbk(-=):增加复合赋值运算-=。 Slash(/):原有除法运算符,用来实现单行注释和块注释。Times(*):原有的乘法运算符,用来实现多行的块注释。
保留字更新:
strcpy(&(word[0][0]), "begin");
strcpy(&(word[1][0]), "call");
strcpy(&(word[2][0]), "const");
strcpy(&(word[3][0]), "d");
strcpy(&(word[4][0]), "do");
strcpy(&(word[5][0]), "else");
strcpy(&(word[6][0]), "end");
strcpy(&(word[7][0]), "for");
strcpy(&(word[8][0]), "if");
strcpy(&(word[9][0]), "odd");
strcpy(&(word[10][0]), "procedure");
strcpy(&(word[11][0]), "read");
strcpy(&(word[12][0]), "then");
strcpy(&(word[13][0]), "var");
strcpy(&(word[14][0]), "while");
strcpy(&(word[15][0]), "write");
wsym[0] = beginsym;
wsym[1] = callsym;
wsym[2] = constsym;
wsym[3] = diosym;
wsym[4] = dosym;
wsym[5] = elsesym;
wsym[6] = endsym;
wsym[7] = forsym;
wsym[8] = ifsym;
wsym[9] = oddsym;
wsym[10] = procsym;
wsym[11] = readsym;
wsym[12] = thensym;
wsym[13] = varsym;
wsym[14] = whilesym;
wsym[15] = writesym;
3.1 增加 I/O 格式化输入输出
设计思路:
采用“%d”对输入和输出进行格式化,在 statement() 函数中,当判断该语句为读(写) 语句时,读入一个字符,判断 sym 是否为 lparen,若非,则报错。否则,读入一个字符,判断 sym 是否为percent,若非,报错。否则,读入一个字符,判断 sym 是否为 diosym,再读入一个字符,判断 sym 是否 为 comma,若非,报错。否则,读入一个字符,判断 sym 是否为 ident,后面的处理方式与原程序相同。
其语法如下:
<读语句>::=READ‘(’%d{%d}<标识符>{,<标识符>}‘)’ <写语句>::=WRITE‘(’%d{%d}<表达式>{,<表达式>}‘)’
3.2 增加一维数组
设计思路:
考虑读入数组,假如我们有定义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 来完成我们需要的功能。代码如下:
case sto:
t--;
s[base(i.l, s, b) + i.a] = s[t]; break;
case sto2:
t--;
s[base(i.l, s, b) + s[t - 1]] = s[t]; break;
很简单,基本没有变化,只不过 sto 的地址是传入的,而 sto2 的地址是从栈顶拿到的,这样我们就解决了找到数组元素并赋值的过程。
其语法如下:
<一维数组说明部分>::=VAR<标识符>‘(’<上界>:<下界>‘)’ <上界>::=<常量>|<变量>|<表达式> <下界>::=<常量>|<变量>|<表达式>
3.3 增加for循环
设计思路:
首先获取 for,然后获取 lparen,读入一个符号,调用语句处理过程算得赋值号右边的值并通过相应的指令将此值放到数据栈顶。接着获取分号,然后用 condition 过程处理条件语句,把当前 condition 生成的判断条件代码地址 cx 保存到 cx1 中。接着再读取一个分号,调用表达式处理过程算得循环变量的值并判断是否退出循环,把本次循环结束的下一个位置保存到 cx2 生成跳转,并在循环结束时用 cx2 更新为目前循环结束跳转地址。
其语法如下:
<for 型循环语句>::=for‘(’<赋值语句>;<条件>;<赋值语句>‘)’
3.4 增加行注释和块注释
设计思路:
判断当前读取字符为‘/’时,继续读取,若为*,则一直读取,不进行处理,直到遇见*,结束循环,再读取一个字符(‘/’),判断为块注释。否则若为‘/’,则判断为行注释。两者都不满足,判断该符号为 slash。
其语法如下:
<行注释>::=//<语句> <块注释>::=/<语句>{<语句>}/
3.5 增加运算符
设计思路:
判断当前符号为‘+’时,继续获取字符,若为‘+’,则 sym=plusplus,若为‘=’,则 sym=plusbk,否则 sym 为 plus。 判断当前符号为‘-’时,继续获取字符,若为‘-’,则 sym=minusminus,若为‘=’,则 sym=minusbk,否则 sym 为 minus。 然后在 statement 函数里根据 sym 值的不同,进行对应的操作,实现运算功能。
其语法如下:
<复合赋值运算符>::=-=|+= <后置自增运算符>::=++ <后置自减运算符>::=–
3.6 增加if-then-else
设计思路:
首先对读到的词调用 getsym 进行词法分析,把当前的代码地址 cx 保存在 cx2 中,调用 gen(jmp,0,0) 生成条件跳转,地址未知。将当前代码的地址加 1 赋给 cx,同时把 cx 的地址保存在 cx2 中。
其语法如下:(其中 else 部分可有可无)
<条件语句>::=IF<条件>THEN<语句>[ELSE<语句>]
三、成果展示
1. IED配置
以下测试均在已经配置好的EditPlus 上测试,同时也可以看到代码高亮显示,至于自动补全应用于写代码的过程中,已通过测试。
2. 斐波那契数列
同时也可用来做格式化输入和输出测试。
3. 鸡兔同笼问题
输入头和脚数,输出兔子和鸡的个数。可用来测试格式化输入和输出。
4. 求取最大公因数
输入 2 个数,求最大公因数。可同时用来测试格式化输入和输出,以及代码注释。
5. 一维数组赋值、输入和输出
通过下标访问数组的某个元素并进行赋值,读入某个数组元素的值,并原貌输出。(键盘输入的是 34)
6. for循环、++、–运算
递增输出 1,2,3,4,5; 递减输出 100,99,98,97,96;
7. if-then-else结构实现
四、实验思考和总结
保留字名字word[][]和保留字符号 wsym[]下标设置要对应同一个符号,不然会出现各种位置错误! 如图为正确设置方式:(按照数序排列,便于进行折半查找,加快查找速率)
对于中文注释的显示问题,EditPlus 保存文件编码为 ANSI。保存为 UTF-8 会显示乱码。
五、整体代码实现
基于C++实现PL0的编译 1700+行代码,包括运行代码所需要的PL0文件,编写不易,多多支持!!!
|