PL/0编译程序的语法分析采用了自顶向下的递归子程序法。粗略地说:就是对应每 个非终结符语法单元,编一个独立的处理过程(或子程序).语法分析从读入第一个单词开 始由非终结符‘程序’即开始符出发,沿语法描述图箭头所指出的方向进行分析。当遇到非 终结符时,则调用相应的处理过程,从语法描述图看也就进入了一个语法单元,再沿当前 所进入的语法描述图的箭头方向进行分析,当遇到描述图中是终结符时,则判断当前读入 的单词是否与图中的终结符相匹配,若匹配,则执行相应的语义程序(就是翻译程序)。再 读取下一个单词继续分析。遇到分支点时将当前的单词与分支点上的多个终结符逐个相 比较,若都不匹配时可能是进入下一非终结符语法单位或是 程序 出错。 如果一个PL/0语言的单词序列在整个语法分析中,都 能逐个得到匹配,直到程序结束符“,’,这时就说所输入的程 分程序 序是正确的。对于正确的语法分析做相应的语义翻译,最终得 出目标程序。 以上所说语法分析过程非常直观粗浅,实际上应用递归 条件☐ 子程序法构造语法分析程序时,对文法有一定的要求和限制, 表达式 这个问题我们将在第5章详细讨论。 此外,从PL/0的语法描述图中可以清楚地看到,当对 项 PL0语言进行语法分析时,各个非终结符语法单元所对应的 因子 分析过程之间必须存在相互调用的关系。这种调用关系可用 图2.7表示。也可称为PL0语法的依赖图,在图中箭头所指 图2.7PL/0语法调 向的程序单元表示存在调用关系,从图中不难看出这些子程 用关系图 序在语法分析时被直接或间接递归调用。 语法分析程序除总控外主要有两大部分的功能,即说明部分的处理和程序体部分的 处理,也就是在语法单元中的分程序部分,通常我们把它称为过程。它的处理由过程 BI,OCK完成。其流程图如图2.8所示,在过程BLOCK内对说明部分及程序体部分的分 析说明如下: (1)说明部分的分析 由于PL/0语言允许过程调用语句,且允许过程嵌套定义,因此每个过程应有过程首 部以定义局部于它自己过程的常量、变量和过程标识符,也称局部量。每个过程所定义的 局部量只能供它自己和它自己定义的内过程引用。对于同一层并列过程的调用关系是先 定义的可以被后定义的引用,反之则不行。 说明部分的处理任务就是对每个过程(包括主程序,也可看成是一个主过程)的说明 对象造名字表,填写所在层次(主程序为第0层,主程序定义的过程为第1层,随着嵌套的 深度增加而层次数加大。PL/0最多允许3层),标识符的属性和分配的相对位置等。标识 符的属性不同时,所需要填的信息也不同。登录信息是调用ENTER过程完成的, 所造表放在全程量一维数组TABLE表中,TX为素引表的指针,表中的每个元素为 记录型数据。LEV给出层次,DX给出每层局部量当前已分配到的相对位置,可称地址指 示器,每说明完一个变量后DX指示器加1。 17
BLOCK 常量说明处理 SYM-VARSYM?N I Y 厂变量说明处理 SYM-PROCSYM? 在TAE表中 登记过程名 多数co床 始符? 盟 在TAB以LE表中返填过程体入可 取单词GET5YM】 杏> 调用语句处理过程 生政退出粒据段的指今 [调用列目标程序过程 返回○ 图2.8程序BLOCK过程的流程图 例如:一个过程的说明部分为 C0NSTA-35,B=49: VAR C.D.E: PROCEDURE P: VAR G 对它的常量,变量和过程说明分析后,在TABLE表中的信息如表2.2所示 在说明处理后TABLE表中的信息对于过程名的ADR域,是在过程体的目标代码生 成后反填过程体的入口地址。例中在处理P过程的说明时对LEV就增加1。在P过程中 的变量名的层次为LEV+1后的值。对过程还有一项数据S1ZE,是记录该过程所需的数 ·18·
表2.2 TABLE表中的信息 TXNAME,A KIND.CONSTANT VAL.35 NAME.B KIND.CONSTANT VAL:4 NAME: IND VARIAB LEVEL LEV ADR:DX ME: RIABL LEVEL:LE AD R:DX+ NAME: KIND:VARIABL LEVEL:LE ADR:DX+2 NAME:P KIND:PROCEDUR LEVEL:LEV ADR: SIZE:4 TX→ NAME:G KIND.VARIABLE LEVEL:LEV+1 ADR:DX 据空间。至于在造表和查表的过程中,如何保证每个过程的局部量不被它的外层引用,请 读者阅读完PL/0编译程序后自己总结。 TABLE表的表头素引TX和层次单元LEV都以BLOCK的参数形式出现。在主程 序调用BL,OCK时实参值都为0。每个过程中变量的相对起始位置在BLOCK内置初值 DX:=3. (2)过程体的分析 程序的主体是由语句构成的。处理完过程的说明后就处理由语句组成的过程体,从语 法上要对语句逐句分析。当语法正确时就生成相应语句功能的目标代码。当遇到标识符 的引用时就调用POSITION函数查TABLE表,看是否有过正确的定义,若已有,则从表 中取相应的有关信息,供代码的生成用。若无定义则出错。 2.5PL/0编译程序的目标代码结构和代码生成 PL0编译程序所产生的目标代码是一个假想栈式计算机的汇编语言,通常称 PCODE指令代码,它不依赖任何实际计算机,其指令集极为简单,指令格式也很单纯,其 格式如下: 其中í代表功能码,!表示层次差,也就是变量或过程被引用的分程序与说明该变量 或过程的分程序之间的层次差。a的含意对不同的指令有所区别,对存取指令表示位移 量,而对其它的指令则分别有不同的含义,见下面对每条指令的解释说明。 目标指令有8条 ①LIT:将常量值取到运行栈顶。a域为常数值。 ②LOD:将变量放到栈顶。a域为变量在所说明层中的相对位置,l为调用层与说明 层的层差值。 ③STO:将栈顶的内容送入某变量单元中。a,l域的含意同LOD指令。 ④CAL:调用过程的指令。a为被调用过程的目标程序入口地址,】为层差 ⑤NT:为被调用的过程(或主程序)在运行栈中开辟数据区。a域为开辟的单元 ·19
个数, ®JMP:无条件转移指令,a为转向地址。 ⑦JPC:条件转移指令,当栈顶的布尔值为非真时,转向a域的地址,否则顺序执行。 ⑧OP:关系运算和算术运算指令,将栈顶和次栈顶的内容进行运算,结果存放在次 战顶,此外还可以是毒写等特殊功能的指今,具体操作由a域值给出,详见解释执行 程序。 编译程序的目标代码是在分析程序体时生成的,在处理说明部分时并不生成目标代 码,而当分析程序体中的每个语句时,当语法正确则调用目标代码生成过程以生成与 PL/0语句等价功能的目标代码,直到编译正常结束。 PL/0语言的代码生成是由过程GEN完成的。GEN过程有三个参数,分别代表目标 代码的功能码、层差和位移量(对不同的指令含意不同,详见上述说明)。生成的代码顺序 放在数组CODE中.CODE为一维数组,数组元素为记录型数据每一个记录就是一条目 标指令。CX为指令的指针,由0开始顺序增加。实际上目标代码的顺序是内层过程的排 在前边,主程序的目标代码在最后。下面我们给出一个PL0源程序和对应的目标程序的 清单。 Run p10 Input file?TEST List objeet code?Y const a=10 var b.c procedure p: begin ci=bta end: 2 int 0 37 31od13 4010 The object code 5 opr 0 2 of procedure p 6 sto 14 7 opr begin read(b) while b#0 do call p:write(2*c)read(b) end end. ·20
8 int o 5 9 opr 0 16 10st003 111od03 1213 1it00 opr 14pc024 15 cal 0 2 The obiect code : lod o 4 of main progrem 18 opr 0 4 19opr014 200pr015 sto 23jmp011 24opr00」 start p10 ?2 ?4 ?0 End p10 2.6PL/0编译程序的语法错误处理 编写一个程序,往往难于一次成功,常常会出现各种类型的错误。一般有语法错、语义 错及运行错。出错的原因是多方面的,这就给错误处理带来不少困难。就语法错来说,任 何一个编译程序在进行语法分析遇到错误时,总不会就此停止工作,而是希望能准确地指 出出错位置和错误性质并尽可能进行校正,以便使编译程序能继续工作,但对所有的错误 都做到这样的要求是很困难的,主要困难在校正上,因为编译程序不能完全确定程序人员 的意图。例如在一个表达式中,圆括号不配对时,就不能确定应补在何处。有时由于校正 得不对反而会影响到后边,导致出错。因此编译程序只能采取一些措施,对源程序中的错 误尽量查出,加以修改,以便减少上机次数。 PL/0编译程序对语法错误的处理采用两种办法 (1)对于一些易于校正的错误,如丢了逗号、分号等,则指出出错位置,并加以校正, 校正的方式就是补上逗号或分号 (2)对某些错误编译程序难于确定校正的措施,为了使当前的错误不致影响整个程 序的崩遗,把错误尽量局限在一个局部的语法单位中,这样就需跳过一些后面箱入的单词 21·