这是在上机作业的代码的基础上进一步修改扩充而成的,具体项目如下:
设计要求
对PL/0语言在上机实验的功能前提下再进行下列扩充:
- 扩充加1(即++)和减1(即--)运算;
- 添加循环语句“REPEAT<语句>{;<语句>}DOWHILE<条件>”;
- 扩充赋值运算,包括+=和-=。
设计报告要求给出对PL/0编译程序扩充部分的语法描述图;对程序的修改部分加以注释;测试用例的源程序、目标程序和运行结果。
扩充SYMBOL
在已经添加了ELSESYM、REPEATSYM和UNTILSYM这三个SYM的基础上,为了实现如上要求的扩充,必须再正确地添加INC、DEC、DOWHILESYM、PLUSBK和MINUSBK等五个SYMBOL,具体必须包括以下三个方面的修改或添加:
- 在枚举变量SYMBOL的定义内添加INC、DEC、DOWHILESYM、PLUSBK和MINUSBK;给SYMOUT在相应的位置添加“INC”、“DEC”、“DOWHILESYM”、“PLUSBK”和“MINUSBK”等五个数组元素。则SYMBOL由原来的36个值扩展为目前的41个值,SYMOUT也由原来的36个元素扩展为目前的41个元素,所以,除了Error函数中可能出现的作为参数的“36”不被替换为“41”外,其余的“36”均用“41”来替换;
- 我们所新加入的五个SYMBOL中,只有机内表示DOWHILESYM所对应的“DOWHILE”是保留字,所以全局量NORW必须在原来上机实验值17的基础上加1,即将NORW的修改为18;
- 对程序初始化过程进行修改。在Run按钮的响应函数中,修改KWORD数组和WSYM数组的初始值:将“DOWHILESYM”按字典顺序插入KWORD数组中,将DOWHILESYM按字典顺序插入WSYM数组中。
这样,我们就正确地加入了五个将用来作为扩充语言功能的SYM。
修改词法分析器
词法分析是语法分析的前提,为了能正确的完成语法分析,首先要有无误的词法分析。
- 完成INC(++)、PLUSBK(+=)的词法分析,代码修改如下:
else if(CH=='+')
{ GetCh();
if(CH=='+')
{ SYM=INC; GetCh(); }
else if(CH=='=')
{ SYM=PLUSBK; GetCh(); }
else SYM=PLUS;
}
- 完成DEC(?D?D)、MINUSBK(-=)的词法分析,代码修改如下:
else if(CH=='-')
{ GetCh();
if(CH=='-')
{ SYM=DEC; GetCh(); }
else if(CH=='=')
{ SYM=MINUSBK; GetCh(); }
else SYM=MINUS;
}
同时,由于我们的词法分析器已经能正确分析出加减法运算符,我们可以将按钮响应函数中对SSYM数组初始化的语句“SSYM['+']=PLUS; SSYM['-']=MINUS;”删除。
这样,我们就完成了对词法分析器的修改。
扩充INC和DEC操作
- 完成指令的扩充。在解释程序中的OPR指令类中添加如下代码:
case 20:S[T]++; break;
case 21:S[T]--; break;
以上两条指令分别对应INC和DEC对数据栈的操作。
- 存在INC和DEC操作的语法图有如下两个:
根据以上语法图,我们只要对语句处理程序和因子处理程序进行修改添加,即可实现增加INC和DEC操作,首先对语句处理程序进行如下修改:
case IDENT:
i=POSITION(ID,TX);
if(i==0) Error(11);
else if(TABLE[i].KIND!=VARIABLE) { Error(12); i=0; }
GetSym();
switch(SYM)
{ case BECOMES:
GetSym();
EXPRESSION(FSYS,LEV,TX);
if(i!=0) GEN(STO,LEV-TABLE[i].vp.LEVEL,TABLE[i].vp.ADR);
break;
case INC:
case DEC:
if(i!=0) GEN(LOD,LEV-TABLE[i].vp.LEVEL,TABLE[i].vp.ADR);
if(SYM==INC) GEN(OPR,0,20);
else GEN(OPR,0,21);
if(i!=0) GEN(STO,LEV-TABLE[i].vp.LEVEL,TABLE[i].vp.ADR);
GetSym();
break;
default: Error(13); GetSym();
}
break;
其次,再对因子处理程序修改如下:
case VARIABLE:
GEN(LOD,LEV-TABLE[i].vp.LEVEL,TABLE[i].vp.ADR);
GetSym();
if(SYM==INC||SYM==DEC)
{ if(SYM==INC) GEN(OPR,0,20);
else GEN(OPR,0,21); GEN(STO,LEV-TABLE[i].vp.LEVEL,TABLE[i].vp.ADR);
GEN(LOD,LEV-TABLE[i].vp.LEVEL,TABLE[i].vp.ADR);
GetSym();
}
break;
这样,对INC和DEC操作就扩充完成。
扩充+=和-=操作
这两个操作都是一种对变量进行赋值的形式,其合法的语句形式的语法图(略去已处理部分)如下所示:
根据图3,在已经处理部分的基础上,添加对+=运算和-=运算的扩充,相关代码添加如下:
case PLUSBK:
case MINUSBK:
flag=SYM; //flag是一个局部整型变量
GetSym();
GEN(LOD,LEV-TABLE[i].vp.LEVEL,TABLE[i].vp.ADR);
EXPRESSION(FSYS,LEV,TX);
if(flag==PLUSBK) GEN(OPR,0,2);
else GEN(OPR,0,3);
GEN(STO,LEV-TABLE[i].vp.LEVEL,TABLE[i].vp.ADR);
break;
这样就完成了对+=运算和-=运算的添加。
添加REPEAT-DOWHILE语句
在上机实验的基础上,添加循环语句“
REPEAT<语句>{;<语句>}DOWHILE<条件>”的语法描述图如下:
根据扩充循环语句的语法描述图,我们可以轻易地在语句处理函数中添加代码来处理指定循环格式的语句:
case REPEATSYM:
GetSym(); CX1=CX;
STATEMENT(SymSetUnion(SymSetNew(SEMICOLON,
UNTILSYM,DOWHILESYM),FSYS),LEV,TX);
while(SymIn(SYM,SymSetAdd(SEMICOLON,STATBEGSYS)))
{ if(SYM==SEMICOLON) GetSym();
else Error(10);
STATEMENT(SymSetUnion(SymSetNew(SEMICOLON,
UNTILSYM,DOWHILESYM),FSYS),LEV,TX);
}
flag=SYM; GetSym();
CONDITION(FSYS,LEV,TX);
CX2=CX; GEN(JPC,0,0);
CX3=CX; GEN(JMP,0,0); //CX3是一个整型局部量
if(flag==UNTILSYM)
{ CODE[CX2].A=CX1; CODE[CX3].A=CX; }
else
{ CODE[CX2].A=CX; CODE[CX3].A=CX1; }
break;
这样,我们就完成在上机实验的基础上对REPEAT-DOWHILE语句功能的扩充。
所增加的错误号及意义
错 误 号 |
错 误 意 义 |
13 |
语句中最左边标识符的跟随符号错误; |
40 |
REPEAT后面没有可匹配的“UNTIL”或“DOWHILE”; |
41 |
条件语句嵌套时产生二义性。 |
实验测试
测试用例重点测试添加扩充的那部分功能能否正确实现,其中包括一些语句的嵌套,用例代码如下所示:
PROGRAM TEST;
VAR A;B;C;D;
BEGIN
A:=0;
A++;
WRITE(A);
B:=A++;
WRITE(B);
B:=A++*10;
WRITE(B);
B+=5;
WRITE(B);
A-=5;
WRITE(A);
C:=3;
READ(D);
IF ODD D THEN
REPEAT
A++;
B+=A;
WRITE(A);
WRITE(B);
C--;
DOWHILE C>=0
ELSE
REPEAT
A--;
B-=A;
WRITE(A);
WRITE(B);
C--;
UNTIL C=0
END.
关于编译输出文件,由于长度较长,请参看附带软盘里面的相关内容(我将输出文件改称了*.TXT文件)。
编译通过,运行过程当读入D为奇数时,结果为:
~~~ RUN PL0 ~~~
1
2
30
35
-2
51 //接收的D值为奇数51
-1
34
0
34
1
35
2
37
~~~ END PL0 ~~~
当运行过程读入D为偶数时,结果为:
~~~ RUN PL0 ~~~
1
2
30
35
-2
0 //接收的D值为偶数0
-3
38
-4
42
-5
47
~~~ END PL0 ~~~
运行结果完全合乎设计要求,而且,经过将可执行文件传送给同学,进行多方面的测试,反馈回来的数据显示,对扩充修改后PL/0编译器的能够正确的编译其所能支持的语法形式语句组成的程序。