pl|0编译程序

PL|0的词法分析程序GETSYM是一个独立的过程,其功能是为语法语义分析提供单词,把输入的字符串形式的源程序分割成一个个单词符号传递给语法语义分析,为此PL|0编译程序设置了3个全程变量如下:

SYM  存放每个单词的类别,用内部编码形式表示;

ID 存放用户所定义的标识符的值,即标识符字符串的机内表示;

NUM 存放用户定义的数。


单词的种类有5种

基本字 也可称为保留字,如begin end if then

运算符   如+ - 等

标识符  用户定义的变量名,常数名  过程名

常数 如10 100等整数

界符  




如果我们把基本字,运算符 界符称为语言固有的单词,而对标识符,常数称为用户定义的单词,词法分析程序对语言固有的单词只给出类别存放在SYM中,而对用户定义的单词(标识符或常数)既给类别又给值,其类别放在SYM中,值放在ID或num中,单词的全体由编译程序定义的纯量类型SYMBOL给出,如下面将提出的IFSYM,THENSYM,IDENT,NUMBER均属于SYMBOL中的元素




词法分析程序GETSYM将完成下列任务

(1)滤空格   空格在词法分析时是一种不可缺少的界符,而在语法分析是则是无用的,所以必须滤掉。

(2)识别保留字 主程序定义了一个以字符为元素的一维数组,称为保留字表,对每个字母开头的字母、数字字符串要查此表,若查着则识别保留字,将对应的类别放在SYM中,若查不着,则认为是用户定义的标识符。

(3) 识别标识符 对用户定义的标识符将IDENT放在SYM中,标识符本身的值放在ID中

(4)拼数 当扫描到数字串时,将字符串形式的十进制数转换成机内表示的二进制数,然后把数的类别NUMBER放在SYM中,数值本身的值存放在NUM中;

(5)拼复合词 对两个字符组成的算符,识别后将类别送SYM中;

(6)输出源程序   为边读入字符边输出



由于一个单词往往是由一个或多个字符组成的,所以在词法分析过程GETSYM中又定义了一个取字符过程GETCH,由词法分析需要取字符时调用。




GETCH所用的单元说明

CH:存放当前读取的字符,初值设为空;

LINE:一维数组,数组元素是字符,界符对为1:80 用于读入一行字符的缓冲区


PL|0编译程序的语法语义分析

语法分析的任务是识别单词符号序列是否符合给定的语法规则,PL|0语言的语法规则已在之前给出,本节将以语法图描述的语法形式为依据,给出语法分析过程的直观思想。


Pl|0编译程序的语法分析采用的是自顶向下的递归子程序法,粗略地说,就是对应每个非终结符语法单元,编一个独立的处理过程(子程序),语法分析从读入第一个单词开始由开始符“程序”出发,沿语法描述图看也就进入了一个语法单元,再沿当前所进入的语法描述图的箭头方向进行分析,当遇到描述符中是终止符时,则判断当前读入的单词是否与图中的终结符相匹配,若匹配,则执行相应的语义程序(就是翻译程序),再读取下一个单词继续分析,遇到分支点时将当前的单词和分支点上的多个终结符逐个比较,若都不匹配时可能是进入下一非终结符语法单元或是出错。

如果一个pl|0语言程序的单词序列在整个语法分析中,都能逐个得到匹配,直到程序结束符“。”,这时就说所输入的程序是正确的。对于正确的语法分析做相应的语义翻译,生成目标代码。

以上所说的语法分析过程非常直观粗浅,实际上应用递归子程序法构造语法分析程序时,对文法有一定的要求和限制,这个问题我们将在第五章进行详细讨论。


此外,从PL|0的语法描述图中可以清楚地看到,对PL|0的语法进行语法分析时,各个非终结符语法单元所对应的分析过程之间存在调用关系,调用关系图也称为PL|0语法的依赖图,在图中箭头所指向的程序单元表示存在调用关系,从图中不难看出这些子程序在语法分析时直接或间接递归调用。


除主程序之外,语法语义分析的处理由分程序BLOCK过程完成,在过程BLOCK内对说明部分及程序体部分的处理说明如下:

(1)说明部分的处理

由于PL|0语言允许过程调用语句,且允许过程嵌套定义,因此每个过程应有过程首部以定义局部于它自己过程的常量,变量,和过程标识符,也称为局部量。每个过程所定义的局部量只能提供它自己和它自己定义的内过程引用,对于同一层并列过程的调用关系是先定义的可以被后定义的引用,反之不可以。


说明部分的处理任务是对每个过程(包括主程序,也可看成是一个主程序)的说明对象,造名字表,填写所在的层次(主程序为第0层,主程序所定义的过程为第一层,随着嵌套的深度增加而层次次数加大)pl|0再多允许3层,标识符的属性和分配的相对位置等。标识符的属性不同时,所需要填写的信息也不同,登录信息是调用ENTER过程完成的。

名字表是全程量一维数组TABLE,tx是索引表的指针,表中的每个元素为记录型数据,表中的LEV表示层次,DX表示给本层局部变量分配的相对存储位置,每说明完一个变量后DX加1.

例如 一个PL|0语言过程说明部分的片段为:


CONST A=35,B=49;

VAR C,D ,E;

PROCEDURE P;

VAR G

对常量,变量,和过程说明分析后,得到TABLE表中的信息如表所示,其中过程名的ADR域,是在过程体的目标代码生成时反填过程体的入口地址,P过程的所在层次是LEV,则P过程定义的变量名G的层次是LEV+1,SIZE是记录该过程所需的数据空间,至于在造表和查表的过程中,如何保证每个过程的局部量不被它的外层引用,要自己总结。

TABLE表的表头索引TX和层次LEV,都是BLOCK的值参,在主程序调用BLOCK时的实参值都为0,每个过程中变量的相对起始位置都是BLOCK内置初值DX:=3.


(2)过程体的分析

程序的主体是由语句构成的,处理完过程的说明后就处理由语句组成的过程体,从语法上对语句进行逐句分析,当语法正确时就生成相应的语句功能的目标代码,当遇到标识符的引用时就调用POSITION函数查TABLE表,看是否有过正确的定义。若已有,则用表中取相应的有关信息,供代码的生成用,若无定义则调用出错处理程序。


pl|0编译程序的目标代码结构和代码生成


PL|0编译程序所产生的目标代码是一个假想栈式计算机的汇编语言,可称为类PCODE指令代码,它不依赖任何实际的计算机,其指令集较为简单,指令格式为:

f     l   a

其中的f代表功能码,l表示层次差,也就是引用变量或过程的分程序与说明该变量或过程的分程序之间的层次差,a的含义对不同的指令有所区别,对每条指令的解释说明如下:

目标指令有8条

LIT  将常量取到运行栈顶,a域为常数值。

LOD 将变量放到栈顶,a域是说明(定义)变量的过程,给变量分配的存储空间,相对该过程的运行栈基地址的偏移量,l为调用层与说明层的差值。

STO 将栈顶的内容存入到某变量单元,a,l域的含义同lod指令

CAL 调用过程的指令,a为被调用过程的目标程序,入口地址,l为层差。

INT 为被调用的过程(或主程序)在运行栈中开辟的数据区,a域为开辟的单元个数。

JMP 为无条件转移指令,a为转向地址

JPC 条件转移指令,当栈顶的布尔值为非真时,转向a域的地址,否则顺序执行。

OPR opr的l域为0,a域的内容决定操作含义,具体含义为:

a=0 过程调用结束后返回调用点,并释放被调用过程在运行栈的数据空间;

a=1 栈顶值取反结果在栈顶

a=2 2~5 将栈顶和次栈顶内容做算术运算,结果存放在次栈顶。

a=6 将栈顶值做奇偶判断,奇数为真,偶数为假,结果在栈顶,

a=8~13 将栈顶和次栈顶的内容做关系运算,结果放在次栈顶,

a=14 栈顶值输出到屏幕,

a=15 屏幕输出换行

a=16 从命令行读入一个输入到栈顶


a为其他值均为非法指令。


编译程序的目标代码是在分析程序体时生成的,在处理说明部分一般不生成目标代码,而当分析程序体中的每个语句时,当语法正确则调用目标代码生成过程以生成与PL|0语句等价功能的目标代码,直到编译正常结束。

pl|0语言的代码生成是由过程gen完成的,gen过程有三个参数,分别表示目标代码的功能码,层差,和位移量,(对不同指令的含义不同,)生成的代码顺序存放在数组code中,code为一维数组,数组元素为记录型数据,每一条记录就是一条目标指令,cx为code下标的指针,由0开始顺序增加,,实际上目标代码的顺序是内层过程的排在前面,主程序的目标代码在最后。


PL|0编译程序的语法错误处理

编写一个程序,往往难于一次成功,常常会出现各类型的错误,一般会有语法错,语义错,及运行错,出错的原因是多方面的,这就给错误处理带来不少困难,就语法错误来说,任何一个编译程序在进行语法分析遇到错误时,总不会就此停止工作,而是希望能准确地指出出错位置和错误性质并尽可能进行校正,以便被编译程序能继续工作,但对所有的错误都做到这样的要求是很困难的,主要困难在校正上,因为编译程序不能完全确定程序员的意图,例如在一个表达式,圆括号不配对,就不能确定应不在何处,有时由于校正的不对反而会导致后面正确的部分,却认为是错误的,因此编译程序只能采取一些措施,对源程序中的错误尽量查出,以便修改,


pl|0编译程序对语法错误的处理采用两种办法:

对于一些易于校正的错误,如丢逗号,分号等,则指出处错误位置,并加以校正,校正的方式就是补上逗号或分号;

对某些错误,编译程序难于确定校正的措施,为了使当前的错误不导致整个程序的崩溃,把错误尽量局限在一个局部的语法单元中,这样就需要跳过一些后面输入的单词符号,直到读入一个能使编译程序恢复正常语法分析工作的单词为止,具体做法是:当语法分析进入或退出一个语法单元的处理程序时,调用一个测试程序TEST,它的功能是检查当前单词是否属于该语法单元的开始符号集合或后跟符号集合,

PL|0每个非终结符在语法单元的开始符号集合和后跟符号集合


TEST测试程序过程有三个参数,它们的含义是:

S1     当语法分析进入或退出某一语法单元时当前单词符号应属于的集合,它可能是一个语法单元开始符号的集合或后继符号的集合;

S2 在某一出错状态,可恢复语法分析继续正常工作的补充单词符号集合,因为语法分析出错,即当前单词符号不在S1集合,为了继续编译程序,需跳过后面输入的一些单词符号,直到当前输入的单词符号是属于S1或S2;

n 整型数  出错信息编号



为了进一步明确S1,S2集合的含义,现以因子(FACTOR)的语法分析程序为例,在过程FACTOR的入口处调用了一次TEST过程,它的实参S1是因子开始符号的集合,(文本中的FACBEGSYS).S2是每个过程的形参FSYs调用时实参的传递值,当编译程序第一次调用BLOCK时,FSYS实参为[.]与说明开始符和语句开始符集合的和。以后随着调用语法分析程序层次的深入逐步增加。如调用语句时增加【,】和【endsys】,在表达式语法分析中调用项时又增加【+】、【-】,而在项中调用因子时又增加了【*】和【|】这样在进入因子分析程序时即使当前符号不是因子开始符,出错后只要跳过一定的符号,遇到当前输入的单词符号在FSYS中或在因子开始符号集中均可继续进行语法分析,在因子过程的出口处,也调用了测试程序,不过这是S1和S2实参恰恰相反,说明当时的FSYS集合的单词符号都是因子正常出口时允许的单词符号,而因子的开始符号为可恢复正常语法分析的补充单词符号,然而PL|0编译程序对测试程序TEST的调用有一定的灵活性。

对语义错误,如标识符未加说明就引用,或虽经说明,但引用与说明的属性不一致,这时只给出错误信息和出错位置,编译工作可以继续进行,而对运行错,如溢出,越界,等,只有在运行时发现,由于PL|0编译程序的功能限制无法指出运行时所发生的错误在源程序中的对应位置。

同时可以给出出错信息表如下


PL|0编译程序的目标代码解释执行时的存储分配

当源程序经过语法分析,如果未发现错误时,由编译程序调用解释程序,对存放在CODE中的目标代码从CODE[0]开始进行解释执行当编译结束后,记录源程序中标识符的TABLE表已没有作用,因此存储区只需以数组code存放的只读目标程序和运行时的数据区s。S是由解释程序定义的一维整型数组,由于PL|0语言的目标程序是一种假象的栈式计算机的存储空间,遵循后进先出规则,对每个过程(包括主程序)当被调用时,才分配数据空间,退出过程,所分配的数据空间被释放。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值