符号,直到读入一个能使编译程序恢复正常语法分析工作的单词为止。具体做法是: 当语法分析进入以某些关键字(保留字)或终结符集合为开始符的语法单元时,通常 在它的入口和出口处,调用一个测试程序 TEST(见图2.9)。例如:语句的开始符是be TEST gin,if,while,call,read,write:说明的开始符 是var,const,procedure:因子的开始符是“(", SYM在S1中2 ident,number。当语法分析进入这样的语法单 打印出错编号和 元前,可用测试程序检查当前单词符号是否属 返回○ 于它们开始符号的集合,若不是则出错。另外 S1=S1+s2 由干PL/0编译程序采用自顶向下的分析方 法,一个语法单元分析程序调用别的语法单元 的分析程序时,以参数形式(文本中以FSYS SYM在s1中?> 定义为单词符号集合作为形参)给出被调用的 GETSYM 语法分析程序出口时合法的后继单词符号集 合(如表2.3所给出),在出口处也调用测试程 图2.9TEST测试过程流程图 序。若当前单词符号是属于所给集合,则语法 分析正常进行,否则出错。单词符号集合FSYS参数是可传递的,随着调用语法分析程序 层次的深入,FSYS的集合逐步补充合法单词符。 表2.3PL/0文法非终结符的开始符号与后继符号集合表 非终结符名 开始符号集合 后继符号集合 const var 分程序 procedure ident if call begin while read write end 语句 ident call begin if while read write 条件 odd+ ( then do ident number 表达式 ;)rop ident number end then do 项 ident number . )rop end then do 因子 ident number( rop+-¥/end then do 注:表23中0p'表示关系运算符集合,如=,#,<,<=,>,>=。 TEST测试过程有三个参数,它们的含意是 ①S:当语法分析进入或退出某一语法单元时当前单词符号应属于的集合,它可能 是一个语法单元开始符号的集合或后继符号的集合 ②S:在某一出错状态时,可恢复语法分析继续正常工作的补充单词符号集合,因为 22·
当语法分析出错时,即当前单词符号不在S,集合中,为了继续编译,需跳过后边输入的 些单词符号,直到当前输入的单词符号是属于S,或S,集合。 ③n:整型数,出错信息编号。 为了进一步明确S,S,集合的含意,现以因子(FACTO)的语法分析程序为例,在过 程FACTOR的入口处调用了一次TEST过程,它的实参S,是因子开始符号的集合(文 本中的FACBEGSYS).S,是每个过程的形参FSYS调用时实参的传递值,当编译程序第 一次调用BLOCK时,FSYS实参为[·与说明开始符和语句开始符集台的和。以后随 调用语法分析程序层次的深入逐步增加,如调用语句时增加了[,]和[endsym],在表达式 语法分析中调用项时又增加了[+]和[一],而在项中调用因子时又增加了[*]和/,这 样在进入因子分析程序时即使当前符号不是因子开始符,出错后只要跳过一定的符号,遇 到当时输入的单词符号在FSYS中或在因子开始符号集合中,均可继续正常进行语法分 析。而在因子过程的出口处也调用了测试程序,不过这时S,和S实参怡恰相反,说明当 时的FSYS集合的单词符号都是因子正常出口时允许的单词符号。而因子的开始符号为 可恢复正常语法分析的补充单词符号中。然而PL/0编译程序对测试程序TEST的调用 有一定的灵活性 对语义错误,如标识符未加说明就引用,或虽经说明,但引用与说明的属性不一致,这 时只给出错误信息和出错位置,编译工作可继续进行。而对运行错,如溢出,越界等,只能 在运行时发现,由于PL/0编译程序的功能限制无法指出源程序的错误位置。 这节最后我们给出PL/0语言的出错信息表。 出错编号 出错原因 常数说明中的“=”写成“:=” 常数说明中的“一”后应是数字 常数说明中的标识符后应是“一”。 const,var,procedure后应为标识符 漏掉了‘,’或‘;’。 6 过程说明后的符号不正确(应是语句开始符,或过程定义符) 应是语句开始符 8: 程序体内语句部分的后跟符不正确」 9 程序结尾丢了句号’。 10: 语句之间漏了‘,’ 11: 标识符未说明。 12: 赋值语句中,赋值号左部标识符属性应是变量。 13: 赋值语句左部标识符后应是赋值号·:一’ 1 call后应为标识符。 15 cal后标识符属性应为过程 16 条件语句中丢了then', 17: 丢了end"或'' ·23
18: while型循环语句中丢了do' 19 语句后的符号不正确。 20: 应为关系运算符, 21: 表达式内标识符属性不能是过程 22: 表达式中漏掉右括号)’。 23: 因子后的非法符号。 24: 表达式的开始符不能是此符号。 31: 数越界 32: read语句括号中的标识符不是变量, 2.7PL/0编译程序的目标代码解释执行时的存储分配 当源程序经过语法分析,如果未发现错误时,由编译程序调用解释程序,对存放在 CODE中的目标代码CODE[0]开始进行解释执行。当编译结束后,记录源程序中标识符 的TABL,E表已没有作用。因此存储区只需以数组CODE存放的只读目标程序和运行时 的数据区S。S是由解释程序定义的一维整型数组。由于PL/0语言的目标程序是一种假 想的栈式计算机的汇编语言,仍用PASCAL语言解释执行。解释执行时的数据空间S为 栈式计算机的存储空间。遵循后进先出规则,对每个过程(包括主程序)当被调用时,才分 配数据空间,退出过程时,则所分配的数据空间被释放。 解释程序还定义了4个寄存器。 (1)1:指令寄存器。存放着当前正在解释的一条目标指令。 (2)P:程序地址寄存器。指向下一条要执行的目标程序的地址(相当目标程序CODE 数组的下标). (3)T:栈顶寄存器。由于每个过程当它被运行时,给它分配的数据空间(下边称数据 段)可分成两部分。 静态部分:包括变量存放区和三个联系单元(联系单元的作用见后)。 动态部分:作为临时工作单元和累加器用。需要时随时分配,用完后立即释放。栈顶 寄存器T指出了当前栈中最新分配的单元(T也是数组S的下标)。 (4)B:基址寄存器,指向每个过程被调用时,在数据区S中给它分配的数据段起始地 址,也称基地址。 为了实现对每个过程调用时给它分配数据段,也就是对即将运行的过程所需数据段 进栈:过程运行结束后释放数据段,即该数据段退栈:以及嵌套过程之间对标识符引用的 寻址问题.每个过程被调用时,在栈顶分配三个联系单元,这三个单元存放的内容分别为: (1)SL:静态链:它是指向定义该过程的直接外过程(或主程序)运行时最新数据段的 基地址。 (2)DL:动态链:它是指向调用该过程前正在运行过程的数据段基地址。 (3)RA:返回地址:记录调用该过程时目标程序的断点,即当时的程序地址寄存器P 的值。也就是调用过程指令的下一条指令的地址。 ·24·
P引,/0编译程序给变量分配的地址只是确定变量在数据段内的相对位置。对每个过 程从3开始顺序增加。3以前的三个单元为上面指出的三个联系单元。因此静态链的作用 是当-“个过程引用包围它的过程(或主程序)所定义的标识符时,首先沿静态链跳过个数 为层差的数据段,找到定义该标识符过程的数据段基地址,再加上给此标识符(一般是变 量标识符)分配的相对位置,就得到该标识符在整个数据区栈中的绝对位置。动态链和返 回地址的作用是当一个过程运行结束后,为了恢复调用该过程前的执行状态而设置的。 下面举例说明解释执行时数据区的变化过程,图2.10给出示意图。 在图2.10中我们可以看到当例中程序执行进入到C过程后,在C过程中又调用B 过程时,数据区栈中的状况,这时过程B的静态链是指向过程A的基地址,而不是指向过 程C的基地址,因为过程B是由过程A定义的,它的名字在A层的名字表中,当在C过 程中调用B过程时,层次差为2,所以这时应沿C过程数据的静态链,跳过两个数据段后 的基地址值,才是当前被调用的B过程的静态链之值,这里也可看出不管B过程在何时 被调用,它的数据段静态链总是指向定义它的A过程的最新数据段基地址 具体的过程调用和结束,对上述寄存器及三个联系单元的填写和恢复由下列目标指 令完成。 (1)INT 0 A 每个过程目标程序的入口都有这样一条指令,用以完成开辟数据段的工作。A域的 值指出数据段的大小,即为局部变量个数+3(联系单元个数为3)。由编译程序的代码生 成给出。开辟数据段的结果是政变栈顶寄存器T的值,即T=T十A:。 (2)OPR00 是每个过程出口处的一条目标指令用以完成该过程运行结束后释放数据段的工作, 即退栈工作,恢复调用该过程前正在运行的过程(或主程序)的数据段基地址寄存器的值 和栈顶寄存器T的值,并将返回地址送到指令地址寄存器P中,以使调用前的程序从断 点开始继续执行。 (3)CAL.L A; 为调用过程的目标指令,其中 L:为层次差,它是寻找静态链的依据.在解释程序中由BASE(L)函数来计算,L,为参 数,实参为所给层差 A:为被调用过程的目标程序入口。 CL指令还完成填写静态链、动态链、返回地址,给出被调用过程的基地址值,送入 基址寄存器B中,目标程序的入口地址A的值送指令地址寄存器P中,使指令从A开始 执行。 最后为了使读者弄清PL0编译程序各阶段的任务源程序和目标程序的等价功能: 解释执行目标程序时数据栈的变化情况,建议读者参看第2.5节中的例子,在阅读PL/0 编译程序文本时,可按例子对照学习。 另外由于PL./0编译程序是用PASCAL语言编写的(若文件名为PLO.PAS),所以 要对PL/0语言的源程序进行编译,如在PDP-11机上,首先必须对PL,0.PAS进行编 译,汇编、连接得到PI0.SAV文件。运行PL0.SAV文件才是启动PL/0的编译程序。因 ·25*
此执行命令。 RUN PL0启动PL/0编译程序,输出一些询问信息,需用户回答 输出信息 回答信息 INPUT FILE? PL/0源程序文件名) LIST OBIECT CODE? Y或N) 目标程序输出的次序是,最内层的过程体在最前边,主程序体在最后。源程序清单中 的序号,为该语句在目标程序中对应的起始位置,但需指出例题中序号为0,1指令的内容 为: 0jmp088为主程序入口 1jmp022为过程P的入口 B的局邵变量 例:若有程序结构为: DL procedure A. C的局部变量 procedure B SL B的局部变量 call B 本 RA A的局部岁量 DL 程 call B, 体 主段序变量区 主「 程calA: 图2.10运行时数据栈S的变化状态 2.8练习 1.PL/0语言允许过程嵌套定义和递归调用,试问它的编译程序如何解决运行时的 存储管理。 ?.若PL/0编译程序运行时的存储分配策略采用栈式动态分配,并用动态链和静态 链的方式分别解决递归调用和非局部变量的引用问题,试写出下列程序执行到赋值语句 b1一10时运行栈的布局示意图。 26