(3) 整数n,表示有关错误的诊断号: void test(symset s1, symset s2, int n) {
symset s;
if (! inset(sym, s1)) {
error(n);
s = uniteset(s1, s2); while(! inset(sym, s)) getsym(); destroyset(s); } }
我们前面提出的方案,具有这样的性质:试图通过略过输入正文中的一个或多个符号来恢复分析的正常步调。在错误仅为漏掉一个符号所引起的情况下,它都是不适宜的策略。经验表明,这类错误基本上限于那种仅有语法作用,而不代表动作的符号(如“;”)。把一些关键字加到后继符号集合中去可使分析程序不再盲目地跳过后面的符号,好象漏掉的已经补上去一样。下面程序段就是PL/0分析程序中复合语句分析的一小段。它的效果等于关键字前插入漏掉的分号。statbegsys集合是“语句”这个结构的首符号集。
if (sym == SYM_BEGIN) { getsym();
set1 = createset(SYM_SEMICOLON, SYM_END, SYM_NULL); set = uniteset(set1, fsys); statement(set);
while (sym == SYM_SEMICOLON || inset(sym, statbegsys)) {
if (sym == SYM_SEMICOLON) {
getsym(); } else {
error(10); }
statement(set); } // while
destroyset(set1); destroyset(set); if (sym == SYM_END) {
getsym();
15
} else {
error(17); // ';' or 'end' expected. } }
相关过程:test(), inset(), createset, uniteset(), error().
2.7 符号表管理
为了组成一条指令,编译程序必须知道其操作码及其参数(数或地址)。这些值是由编译程序本身联系到相应标识符上去的。这种联系是在处理常数、变量和过程说明完成的。为此,标识符表应包含每一标识符所联系的属性;如果标识符被说明为常数,其属性值为常数值;如果标识符被说明成变量,其属性就是由层次和修正量(偏移量)组成的地址;如果标识符被说明为过程,其属性就是过程的入口地址及层次。
常数的值由程序正文提供,编译的任务就是确定存放该值的地址。我们选择顺序分配变量和代码的方法;每遇到一个变量说明,就将数据单元的下标加一(PL/0机中,每个变量占一个存贮单元)。开始编译一个过程时,要对数据单元的下标dx赋初值,表示新开辟一个数据区。dx的初值为3,因为每个数据区包含三个内部变量RA,DL和SL。
相关过程:enter(),该函数用于向符号表添加新的符号,并确定标识符的有关属性。
2.8 其他
本教程所提供的PL/0编译程序包括词法分析、语法分析、错误诊断、代码生成、解释执行等几部分。关于这几个程序,我们做如下说明:
(1) 每一个分程序(过程)被编译结束后,将列出该部分PL/0程序代码。
这个工作由过程listcode()完成。注意,每个分程序(过程)的第一条指令未被列出。该指令是跳转指令。其作用是绕过该分程序的说明部分所产生的代码(含过程说明所产生的代码)。
(2) 解释程序作为PL/0编译程序的一个过程,若被编译的源代码没有错
误,则编译结束时调用这个过程。
(3) PL/0语言没有输出语句。解释程序按执行次序,每遇到对变量的赋值
就输出其值。
16
第二部分 上机实践要求
“编译原理与技术”的上机实验要求你对PL/0语言及其编译器进行扩充和修改。每个扩充或修改方式可得到不同的分数,满分为100分。 完成上机作业后,必须提交下列文档:
(1) 修改后的PL/0语言文本。包含词法分析(正规式),语法分析(BNF)。 (2) 有关修改后的PL/0编译/解释器的说明。详细说明你的编译器是如何
编译新的PL/0语言程序的。指出你的程序中最精彩的部分,以及你为什么这样做,你是如何控制和恢复语义错误的。
(3) 给出你所改动后的编译器源程序清单,并标记出你所修改的部分。比
较你的编译器和原来的编译器之间的差别。
(4) 说明你的编译器中可能存在的错误。
(5) 总结经验与教训,如果重做一遍,你会有哪些新的改进?
第三部分 PL/0语言编译器源程序
1.一个例子
1.1 PL/0语言源程序
下面我们给出一个PL/0语言写的二数相乘、除并求最大公约数的算法:
const m = 7, n = 85; var x, y, z, q, r;
procedure multiply; var a, b; begin
a := x; b := y; z := 0; while b > 0 do begin
if odd b then z := z + a; a := 2 * a; b := b / 2; end end;
17
procedure divide; var w; begin
r := x; q := 0; w := y; while w > y do begin
q := 2 * q; w := w / 2; if w <= r then begin
r := r - w; q := q + 1; end; end end;
procedure gcd; var f, g; begin
f := x; g := y;
while f <> g do begin
if f < g then g := g – f; if g < f then f := f – g; end end;
begin
x := m; y := n; call multiply; x := 25; y := 3; call divide; x := 34; y := 36; call gcd; end.
18
1.2 生成的代码(片段)
前面我们给出了PL/0语言写的一段程序,其中乘法过程经过编译程序产生以下代码:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
INT 0 LOD 1 STO 0 LOD 1 STO 0 LIT 0 STO 1 LOD 0 LIT 0 OPR 0 JPC 0 LOD 0 OPR 0 JPC 0 LOD 1 LOD 0 OPR 0 STO 1 LIT 0 LOD 0 OPR 0 STO 0 LOD 0 LIT 0 OPR 0 STO 0 JMP 0 OPR 0
5 3 3 4 4 0 5 4 0 12 29 4 6 20 5 3 2 5 2 3 4 3 4 2 2 4 9 0
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
allocate storage//申请空间5个 x
a := x
a y
b := y
b 0
z := 0
z b
b > 0 0
>
if b <= 0 then goto 29 b
odd(b)
odd
if not(odd(b)) goto 20 z
if
a
z := z + a
+ z 2 a
a := 2 * a *
a b 2
b := b / 2
/ b
goto 9 return
while
上述代码采用助记符形式,“--”后面是为了便于理解而额外加上的注释,大括号右边为左部代码序列对应的源程序中的语句或表达式。
2. PL/0语言编译器源程序
PL/0语言编译器源程序包括如下C程序文件,PL0.h、PL0.c、set.h和set.c。
19