文章目录
PL-0
代码参考
介绍
PL/0编译程序的简单实现
PL0编译程序的结构图与解释执行结构
函数功能说明表
过程和函数嵌套定义结构
总体流程图
GetCH()
GetSym()
词法分析程序GETSYM将完成下列任务:
-
空格;
-
识别保留字;
-
识别标识符;
-
拼数;
-
拼复合词,如:>=、:=、<=等单词;
-
输出源程序
在GETSYM中要调用GETCH,其功能是取字符。
BLOCK
语法描述
PL/0文法的EBNF表示
EBNF表示的符号说明
-
‘< >’ 用左右尖括号括起来的中文字表示语法构造成分,或称语法单位,为非终结符。
-
‘::=’ 该符号的左部由右部定义,可读作“定义为”。
-
‘|’ 表示“或”,为左部可由多个右部定义。
-
‘{ }’ 表示花括号内的语法成分可以重复。在不加上下界时可重复0到任意次数,有上下界时为可重复次数的限制。
-
‘[ ]’ 表示方括号内的成分为任选项。
-
‘( )’ 表示圆括号内的成分优先。
上述符号称“元符号”,定义文法用到上述符号作为文法符号时需要引号 ‘’括起。
PL/0语言文法的EBNF表示:
- <程序> ::= <分程序>.
- <分程序> ::= [<常量说明部分>][<变量说明部分>][<过程说明部分>]<语句>
- <常量说明部分> ::= CONST<常量定义>{,<常量定义>};
- <常量定义> ::= <标识符>=<无符号整数>
- <无符号整数> ::= <数字>{<数字>}
- <变量说明部分> ::= VAR<标识符>{,<标识符>};
- <标识符> ::= <字母>{<字母>|<数字>}
- <过程说明部分> ::= <过程首部><分程序>{;<过程说明部分>};
- <过程首部> ::= PROCEDURE<标识符>;
- <语句> ::= <赋值语句>|<条件语句>|<当型循环语句>|<过程调用语句>|<读语句>|<写语句>|<复合语句>|<空>
- <赋值语句> ::= <标识符>:=<表达式>
- <复合语句> ::= BEGIN<语句>{;<语句>}END
- <条件> ::= <表达式><关系运算符><表达式>|ODD<表达式>
- <表达式> ::= [+|-]<项>{<加法运算符><项>}
- <项> ::= <因子>{<乘法运算符><因子>}
- <因子> ::= <标识符>|<无符号整数>| ‘(’<表达式> ‘)’
- <加法运算符> ::= +|-
- <乘法运算符> ::= * | /
- <关系运算法>::== | # | < | <= | > | >=
- <条件语句> ::= IF<条件>THEN<语句>
- <过程调用语句> ::= CALL<标识符>
- <当型循环语句> ::= WHILE<条件>DO<语句>
- <读语句> ::= READ‘(’<标识符>{,<标识符>} ‘)’
- <写语句> ::= WRITE‘(’<表达式>{,<表达式>} ‘)’
- <字母> ::= a|b|……|X|Y|Z
- <数字> ::= 0|1|2|……|8|9
PL/0语言出错编号表
出错编号 | 出错原因 |
---|---|
1 | 常数说明中的= 写成:= |
2 | 常数说明中的= 后应是数字 |
3 | 常数说明中的表示符(标识符?)后应是= |
4 | const ,var ,procedure 后应是标识符 |
5 | 漏掉了. 或; |
6 | 过程说明后的符号不正确(应是语句开始符,或过程定义符) |
7 | 应是语句开始符 |
8 | 程序题内语句部分的后跟符不正确 |
9 | 程序结尾丢了句号. |
10 | 语句之间漏了; |
11 | 标识符未说明 |
12 | 赋值语句中,赋值号左部标识符属性应是变量 |
13 | 赋值语句左部标识符后应是赋值号:= |
14 | call 后应为标识符 |
15 | call 后标识符属性应为过程 |
16 | 条件语句中丢了then |
17 | 丢了end 或; |
18 | while 型循环语句中丢了do |
19 | 语句后的符号不正确 |
20 | 应为关系运算符 |
21 | 表达式内标识符属性不能是过程 |
22 | 表达式中漏掉右括号) |
23 | 因子后的非法符号 |
24 | 表达式的开始符不能是此符号 |
31 | 数越界 |
32 | 过程嵌套层数大于最大允许的套层数 |
33 | read、write语句的后跟符应为) |
34 | read、write保留字后应接( |
35 | read语句中标识符应为变量 |
36 | read、write语句括号中应接标识符 |
PL/0编译程序的目标代码解释执行时的存储分配
当编译程序经过语法分析,如果未发现错误时,由编译程序调用解释程序,对放在CODE中的目标代码从CODE[0]开始进行解释执行。当解释结束后,记录源程序中标示符的TABLE表已经没有作用了,因此存储区只需要以数组CODE存放的只读目标代码程序和运行时的数据区S,S是由解释程序定义的一维整型数组。由于PL/0语言的目标代码是一种假想的栈式计算机的汇编语言,用PASCAL语言解释执行,解释执行时的数据空间S为假栈式计算机存储空间。遵循后进先出规则,对每个过程当被调用时,才分配数据空间,退出程序时,则所有分配的数据被释放。
解释程序定义了4个寄存器。
I:指令寄存器。存放着当前正在执行的一条目标指令;
P:程序地址寄存器。指向下一条要执行的目标指令的地址(相当于CODE数组的下标);
T:栈顶寄存器。栈顶寄存器T指出了当前栈中最新分配单元。
B:基址寄存器。指向每个过程被调用时,在数据区S中给它分配的数据段起始地址。
由于每个过程当它运行时,给他分配的数据空间(数据段)可分为两部分:
静态部分 包括变量存放区和三个联系单元(SL、DL、RA);
动态部分 作为临时工作单元和累加器使用。需要随时分配,用完后立即释放。
为了实现对每个过程调用时给它分配数据段,也就是对即将运行的过程所需数据段进栈;过程运行结束后释放数据段,即数据段退栈;以及嵌套过程之间对标识符引用的寻址问题。
每个过程被调用时,在栈顶分配三个联系单元:
SL静态链:它指向定义该过程的直接外接过程的数据段基地址;
DL 动态链:它指向调用该过程前正在运行过程的数据段基地址;
RA 返回地址:记录调用该过程是目标程序的断点,即当时程序的地址寄存器P的值,也就是调用过程指令的下一条指令的地址。
PL/0编译程序给变量分配的地址只是确定变量在数据段内的相对位置。对每个过程从DX := 3开始顺序增加。3以前的三个单元为上面指出的三个联系单元(SL、DL、RA)。因此静态连接的作用是当一个过程引用包围它的过程所定义的标识符时,首先沿静态链跳过个数为层差的数据段,找到定义该标识符过程的数据段基地址,再加上所给标识符分配的相对位置,就得到该标识符在整个数据栈中的绝对位置。动态链和返回地址的作用是当一个过程结束后,为恢复调用该过程前的执行状态而设置的。
例如,从后面的图中可以看出,当程序执行进入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) OPR 0 0
是每个过程出口处的一条目标指令。用已完成该过程运行结束后释放数据段的工作。恢复调用该过程前正在运行的过程的数据段基地址寄存器的值,和栈顶寄存器的值,并将返回地址送到指令地址寄存器P中,以使调用前的程序从断点开始继续执行。
(3)CAL L A