pl0语法分析词法分析语义分析Word文件下载.docx
《pl0语法分析词法分析语义分析Word文件下载.docx》由会员分享,可在线阅读,更多相关《pl0语法分析词法分析语义分析Word文件下载.docx(48页珍藏版)》请在冰豆网上搜索。
语法分析开始后,首先调用分程序处理过程(block)处理分程序。
过程入口参数置为:
0层、符号表位置0、出错恢复单词集合为句号、声明符或语句开始符。
进入block过程后,首先把局部数据段分配指针设为3,准备分配3个单元供运行期存放静态链SL、动态链DL和返回地址RA。
然后用tx0记录下当前符号表位置并产生一条jmp指令,准备跳转到主程序的开始位置,由于当前还没有知到主程序究竟在何处开始,所以jmp的目标暂时填为0,稍后再改。
同时在符号表的当前位置记录下这个jmp指令在代码段中的位置。
在判断了嵌套层数没有超过规定的层数后,开始分析源程序。
首先判断是否遇到了常量声明,如果遇到则开始常量定义,把常量存入符号表。
接下去用同样的方法分析变量声明,变量定义过程中会用dx变量记录下局部数据段分配的空间个数。
然后如果遇到procedure保留字则进行过程声明和定义,声明的方法是把过程的名字和所在的层次记入符号表,过程定义的方法就是通过递归调用block过程,因为每个过程都是一个分程序。
由于这是分程序中的分程序,因此调用block时需把当前的层次号lev加一传递给block过程。
分程序声明部分完成后,即将进入语句的处理,这时的代码分配指针cx的值正好指向语句的开始位置,这个位置正是前面的jmp指令需要跳转到的位置。
于是通过前面记录下来的地址值,把这个jmp指令的跳转位置改成当前cx的位置。
并在符号表中记录下当前的代码段分配地址和局部数据段要分配的大小(dx的值)。
生成一条int指令,分配dx个空间,作为这个分程序段的第一条指令。
下面就调用语句处理过程statement分析语句。
分析完成后,生成操作数为0的opr指令,用于从分程序返回(对于0层的主程序来说,就是程序运行完成,退出)。
常量定义过程:
通过循环,反复获得标识符和对应的值,存入符号表。
符号表中记录下标识符的名字和它对应的值。
变量定义过程:
与常量定义类似,通过循环,反复获得标识符,存入符号表。
符号表中记录下标识符的名字、它所在的层及它在所在层中的偏移地址。
语句处理过程:
语句处理过程是一个嵌套子程序,通过调用表达式处理、项处理、因子处理等过程及递归调用自己来实现对语句的分析。
语句处理过程可以识别的语句包括赋值语句、read语句、write语句、call语句、if语句、while语句。
当遇到begin/end语句时,就递归调用自己来分析。
分析的同时生成相应的类PCODE指令。
赋值语句的处理:
首先获取赋值号左边的标识符,从符号表中找到它的信息,并确认这个标识符确为变量名。
然后通过调用表达式处理过程算得赋值号右部的表达式的值并生成相应的指令保证这个值放在运行期的数据栈顶。
最后通过前面查到的左部变量的位置信息,生成相应的sto指令,把栈顶值存入指定的变量的空间,实现了赋值操作。
read语句的处理:
确定read语句语法合理的前提下(否则报错),生成相应的指令:
第一条是16号操作
的opr指令,实现从标准输入设备上读一个整数值,放在数据栈顶。
第二条是sto指令,把栈顶的值存入read语句括号中的变量所在的单元。
write语句的处理:
与read语句相似。
在语法正确的前提下,生成指令:
通过循环调用表达式处理过程分析write语句括号中的每一个表达式,生成相应指令保证把表达式的值算出并放到数据栈顶并生成14号操作的opr指令,输出表达式的值。
最后生成15号操作的opr指令输出一个换行。
call语句的处理:
从符号表中找到call语句右部的标识符,获得其所在层次和偏移地址。
然后生成相应的cal指令。
至于调用子过程所需的保护现场等工作是由类PCODE解释程序在解释执行cal指令时自动完成的。
if语句的处理:
按if语句的语法,首先调用逻辑表达式处理过程处理if语句的条件,把相应的真假值放到数据栈顶。
接下去记录下代码段分配位置(即下面生成的jpc指令的位置),然后生成条件转移jpc指令(遇0或遇假转移),转移地址未知暂时填0。
然后调用语句处理过程处理then语句后面的语句或语句块。
then后的语句处理完后,当前代码段分配指针的位置就应该是上面的jpc指令的转移位置。
通过前面记录下的jpc指令的位置,把它的跳转位置改成当前的代码段指针位置。
begin/end语句的处理:
通过循环遍历begin/end语句块中的每一个语句,通过递归调用语句分析过程分析并生成相应代码。
while语句的处理:
首先用cx1变量记下当前代码段分配位置,作为循环的开始位置。
然后处理while语句中的条件表达式生成相应代码把结果放在数据栈顶,再用cx2变量记下当前位置,生成条件转移指令,转移位置未知,填0。
通过递归调用语句分析过程分析do语句后的语句或语句块并生成相应代码。
最后生成一条无条件跳转指令jmp,跳转到cx1所指位置,并把cx2所指的条件跳转指令的跳转位置改成当前代码段分配位置。
表达式、项、因子处理:
根据PL/0语法可知,表达式应该是由正负号或无符号开头、由若干个项以加减号连接而成。
而项是由若干个因子以乘除号连接而成,因子则可能是一个标识符或一个数字,或是一个以括号括起来的子表达式。
根据这样的结构,构造出相应的过程,递归调用就完成了表达式的处理。
把项和因子独立开处理解决了加减号与乘除号的优先级问题。
在这几个过程的反复调用中,始终传递fsys变量的值,保证可以在出错的情况下跳过出错的符号,使分析过程得以进行下去。
逻辑表达式的处理:
首先判断是否为一元逻辑表达式:
判奇偶。
如果是,则通过调用表达式处理过程分析计算表达式的值,然后生成判奇指令。
如果不是,则肯定是二元逻辑运算符,通过调用表达式
处理过程依次分析运算符左右两部分的值,放在栈顶的两个空间中,然后依不同的逻辑运算符,生成相应的逻辑判断指令,放入代码段。
判断单词合法性与出错恢复过程分析:
本过程有三个参数,s1、s2为两个符号集合,n为出错代码。
本过程的功能是:
测试当前符号(即sym变量中的值)是否在s1集合中,如果不在,就通过调用出错报告过程输出出错代码n,并放弃当前符号,通过词法分析过程获取一下单词,直到这个单词出现在s1或s2集合中为止。
这个过程在实际使用中很灵活,主要有两个用法:
在进入某个语法单位时,调用本过程,检查当前符号是否属于该语法单位的开始符号集合。
若不属于,则滤去开始符号和后继符号集合外的所有符号。
在语法单位分析结束时,调用本过程,检查当前符号是否属于调用该语法单位时应有的后继符号集合。
若不属于,则滤去后继符号和开始符号集合外的所有符号。
通过这样的机制,可以在源程序出现错误时,及时跳过出错的部分,保证语法分析可以继续下去。
语法分析过程中调用的其它子过程相对比较简单,请参考源程序的注释。
类PCODE代码解释执行过程分析
这个过程模拟了一台可以运行类PCODE指令的栈式计算机。
它拥有一个栈式数据段用于存放运行期数据、拥有一个代码段用于存放类PCODE程序代码。
同时还拥用数据段分配指针、指令指针、指令寄存器、局部段基址指针等寄存器。
解释执行类PCODE代码时,数据段存储分配方式如下:
对于源程序的每一个过程(包括主程序),在被调用时,首先在数据段中开辟三个空间,存放静态链SL、动态链DL和返回地址RA。
静态链记录了定义该过程的直接外过程(或主程序)运行时最新数据段的基地址。
动态链记录调用该过程前正在运行的过程的数据段基址。
返回地址记录了调用该过程时程序运行的断点位置。
对于主程序来说,SL、DL和RA的值均置为0。
静态链的功能是在一个子过程要引用它的直接或间接父过程(这里的父过程是按定义过程时的嵌套情况来定的,而不是按执行时的调用顺序定的)的变量时,可以通过静态链,跳过个数为层差的数据段,找到包含要引用的变量所在的数据段基址,然后通过偏移地址访问它。
在过程返回时,解释程序通过返回地址恢复指令指针的值到调用前的地址,通过当前段基址恢复数据段分配指针,通过动态链恢复局部段基址指针。
实现子过程的返回。
对于主程序来说,解释程序会遇到返回地址为0的情况,这时就认为程序运行结束。
解释程序过程中的base函数的功能,就是用于沿着静态链,向前查找相差指定层数的局部数据段基址。
这在使用sto、lod等访问局部变量的指令中会经常用到。
类PCODE代码解释执行的部分通过循环和简单的case判断不同的指令,做出相应的动作。
当遇到主程序中的返回指令时,指令指针会指到0位置,把这样一个条件作为终至循环的条件,保证程序运行可以正常的结束。
************************************************************************************
programpl0(input,output));
(*PL/0编译程序与代码生成解释运行程序*)
(*PL/0compilerwithcodegeneration*)
label99;
(*声明出错跳转标记*)
const(*常量定义*)
norw=11;
(*ofreservedwords*)(*保留字的个数*)
txmax=100;
(*lengthofidentifiertable*)(*标识符表的长度(容量)*)
nmax=14;
(*maxnumberofdigitsinnumbers*)(*数字允许的最长位数*)
al=10;
(*lengthofidentifiers*)(*标识符最长长度*)
amax=2047;
(*maximumaddress*)(*寻址空间*)
levmax=3;
(*maxdepthofblocknesting*)(*最大允许的块嵌套层数*)
cxmax=200;
(*sizeofcodearray*)(*类PCODE目标代码数组长度(可容纳代码行数)*)
type(*类型定义*)
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);
(*symobl类型标识了不同类型的词汇*)
alfa=packedarray[1..al]ofchar;
(*alfa类型用于标识符*)
object1=(constant,variable,procedur);
(*object1为三种标识符的类型*)
symset=setofsymbol;
(*symset是symbol类型的一个集合类型,可用于存放一组symbol*)
fct=(lit,opr,lod,sto,cal,int,jmp,jpc);
(*fct类型分别标识类PCODE的各条指令*)instruction=packedrecord
f:
fct;
(*functioncode*)
l:
0..levmax;
(*level*)
a:
0..amax;
(*displacementaddr*)
end;
(*类PCODE指令类型,包含三个字段:
指令f、层差l和另一个操作数a*)
(*
lit0,aloadconstanta
opr0,aexecuteopra
lodl,aloadvariablel,a
stol,astorevariablel,a
call,acallprocedureaatlevell
int0,aincrementt-registerbya
jmp0,ajumptoa
jpc0,ajumpconditionaltoa
*)
var(*全局变量定义*)
ch:
char;
(*lastcharread*)(*主要用于词法分析器,存放最近一次从文件中读出的字符*)
sym:
symbol;
(*lastsymbolread*)(*词法分析器输出结果之用,存放最近一次识别出来的
token的类型*)
id:
alfa;
(*lastidentifierread*)(*词法分析器输出结果之用,存放最近一次识别出来的标识符的名字*)
num:
integer;
(*lastnumberread*)(*词法分析器输出结果之用,存放最近一次识别出来的数字的值*)
cc:
(*charactercount*)(*行缓冲区指针*)
ll:
(*linelength*)(*行缓冲区长度*)
kk:
(*引入此变量是出于程序性能考虑,见getsym过程注释*)
cx:
(*codeallocationindex*)(*代码分配指针,代码生成模块总在cx所指位置生成新的代码*)
line:
array[1..81]ofchar;
(*行缓冲区,用于从文件读出一行,供词法分析获取单词时之用*)
(*词法分析器中用于临时存放正在分析的词*)
code:
array[0..cxmax]ofinstruction;
(*生成的类PCODE代码表,存放编译得到的类PCODE代码*)
word:
array[1..norw]ofalfa;
(*保留字表*)
wsym:
array[1..norw]ofsymbol;
(*保留字表中每一个保留字对应的symbol类型*)ssym:
array['
'
..'
^'
]ofsymbol;
(*一些符号对应的symbol类型表*)
(*wirthuses"
array[char]"
here*)
mnemonic:
array[fct]ofpackedarray[1..5]ofchar;
(*类PCODE指令助记符表*)declbegsys,statbegsys,facbegsys:
symset;
(*声明开始、表达式开始和项开始符号集合*)table:
array[0..txmax]ofrecord(*符号表*)
name:
(*符号的名字*)
casekind:
object1of(*符号的类型*)
constant:
(*如果是常量名*)
(val:
integer);
(*val中放常量的值*)
variable,procedur:
(*如果是变量名或过程名*)
(level,adr,size:
integer)(*存放层差、偏移地址和大小*)
(*"
size"
lackinginorginal.Ithinkitbelonshere*)
fin,fout:
text;
(*fin文本文件用于指向输入的源程序文件,fout程序中没有用到*)sfile:
string;
(*存放PL/0源程序文件的文件名*)
(*出错处理过程error*)
(*参数:
n:
出错代码*)
procedureerror(n:
begin
writeln('
****'
'
:
cc-1,'
!
'
n:
2);
(*在屏幕cc-1位置显示!
与出错代码提示,由于cc
是行缓冲区指针,所以!
所指位置即为出错位置*)
writeln(fa1,'
(*在文件cc-1位置输出!
与出错代码提示*)
err:
=err+1(*出错总次数加一*)
end(*error*);
(*词法分析过程getsym*)
proceduregetsym;
var
i,j,k:
(*读取原程序中下一个字符过程getch*)
proceduregetch;
ifcc=llthen(*如果行缓冲区指针指向行缓冲区最后一个字符就从文件读一行到行缓冲区*)
ifeof(fin)then(*如果到达文件末尾*)
write('
Programincomplete'
);
(*出错,退出程序*)
close(fin);
exit;
ll:
=0;
(*行缓冲区长度置0*)
cc:
(*行缓冲区指针置行首*)
write(cx:
4,'
(*输出cx值,宽度为4*)
write(fa1,cx:
(*输出cx值,宽度为4到文件*)
whilenoteoln(fin)do(*当未到行末时*)
=ll+1;
(*行缓冲区长度加一*)
read(fin,ch);
(*从文件读入一个字符到ch*)
write(ch);
(*在屏幕输出ch*)
write(fa1,ch);
(*把ch输出到文件*)
line[ll]:
=ch;
(*把读到的字符存入行缓冲区相应的位置*)
(*可见,PL/0源程序要求每行的长度都小于81个字符*)
writeln;
(*行缓冲区长度加一,用于容纳即将读入的回车符CR*)
read(fin,line[ll]);
(*把#13(CR)读入行缓冲区尾部*)
(*我添加的代码。
由于PC上文本文件换行是以#13#10(CR+LF)表示的,
所以要把多余的LF从文件读出,这里放在ch变量中是由于ch变量的
值在下面即将被改变,把这个多余值放在ch中没有问题*)writeln(fa1);
=cc+1;
(*行缓冲区指针加一,指向即将读到的字符*)
ch:
=line[cc](*读出字符,放入全局变量ch*)
end(*getch*);
begin(*getsym*)
while(ch='
)dogetch;
ifchin['
a'
z'
]then(*如果读出的字符是一个字母,说明是保留字或标识符*)
k:
(*标识符缓冲区指针置0*)
repeat(*这个循环用于依次读出源文件中的字符构成标识符*)
ifk<
althen(*如果标识符长度没有超过最大标识符长度(如果超过,就取前面一部分,把多余的抛弃)*)
=k+1;
a[k]:
getch(*读下一个字符*)
untilnot(chin['
'
0'
9'
]);
(*直到读出的不是字母或数字,由此可知PL/0的标识符构成规则是:
以字母开头,后面跟若干个字母或数字*)
ifk>
=kkthen(*如果当前获得的标识符长度大于等于kk*)
kk:
=k(*令kk为当前标识符长度*)
else
repeat(*这个循环用于把标识符缓冲后部没有填入相应字母或空格的空间用空格补足*)
a[kk]:
='
;
=kk-1
unt