# 源语言和目标语言
本次大作业设计的编译器是将 PL/0 语言编译成类 P-code 语言的程序。
# PL/0 语言
PL/0 语言是一种功能及其有限的编程语言,其数据类型仅存在整数,控制流仅有 if...then...
和 while...do...
两种。此外,其不存在函数传参和面向对象等 “现代” 的设计,而是仅存在 call
指令作为调用的方式。这一方面使得其编写思路与汇编语言相当类似,另一方面也使得其编译器十分简单。
# 类 P-Code 语言
类 P-code 语言是 PL/0 编译器的目标语言,是运行于纯栈式机器上的汇编语言。
其基本指令形式为 F L A
, 三部分的各自含义如下:
F
: 指令的操作码。L
: 若起作用,则表示引用层与声明层之间的层次差;若不起作用,则置为0
。A
: 不同的指令含义不同。
具体的命令及解释如下:
F | L | A | 功能 |
---|---|---|---|
INT | 0 | A | 在栈顶开辟 A 个存储单元 |
CAL | L | A | 调用地址为 A 的过程,调用过程与被调用过程的层差为 L |
LIT | 0 | A | 立即数 A 存入 所指单元, 加 1 |
LOD | L | A | 将层差为 L 、偏移量为 A 的存储单元的值取到栈顶, 加 1 |
STO | L | A | 将栈顶的值存入层差为 L 、偏移址为 A 的单元, 减 1 |
OPR | 0 | 0 | 结束被调用过程,返回调用点并退栈 |
OPR | 0 | 1 | 求栈顶元素的相反数,结果值留在栈顶 |
OPR | 0 | 6 | 栈顶内容若为奇数则变为 1 ,若为偶数则变为 0 |
OPR | 0 | 2 | 次栈顶与栈顶的值相加,结果存入次栈顶, 减 1 |
OPR | 0 | 3 | 次栈顶的值减去栈顶的值,结果存放次栈顶, 减 l |
OPR | 0 | 4 | 次栈顶的值乘以栈顶的值,结果存放次栈顶, 减 1 |
OPR | 0 | 5 | 次栈顶的值乘以栈顶的值,结果存放次栈顶, 减 l |
OPR | 0 | 8 | 次栈顶与栈顶内容若相等,则将 0 存于次栈顶, 减 1 |
OPR | 0 | 9 | 次栈顶与栈顶内容若不相等,则将 0 存千次栈顶, 减 1 |
OPR | 0 | 10 | 次栈顶内容若小于栈顶,则将 0 存于次栈顶, 减 1 |
OPR | 0 | 11 | 次栈顶内容若小于等于栈顶,则将 0 存于次栈顶, 减 1 |
OPR | 0 | 12 | 次栈顶内容若大于栈顶,则将 0 存于次栈顶, 减 1 |
OPR | 0 | 13 | 次栈顶内容若大于等于栈顶,则将 0 存于次栈顶, 减 1 |
OPR | 0 | 14 | 栈顶的值输出至控制台屏幕, 减 1 |
OPR | 0 | 15 | 控制台屏幕输出一个换行 |
OPR | 0 | 16 | 从控制台读入一行输人,置入栈顶, 加 1 |
JMP | 0 | A | 无条件转移至地址 A |
JPC | 0 | A | 若栈顶为 0,则转移至地址 A, t 减 1 |
# 词法分析的实现
词法分析采用 DFA 实现。从文件流中每读取一个字符, DFA 进行一次循环和状态选择。
DFA 的基本状态如下:
// enum class for Lexer's DFA | |
enum Lex_state { | |
START_STATE, // 起始状态 | |
NUM_STATE, // 数字 | |
ID_STATE, // 标识符 & 关键字 | |
COMMENT_STATE, // 注释 | |
GTR_STATE, // > | |
GEQ_STATE, // >= | |
LES_STATE, // < | |
LEQ_STATE, // <= | |
ASS_STATE, // := | |
}; |
# 语法分析和代码生成
在我的程序中,中间和目标代码生成在语法分析器的结构中实现。注意到, PL/0 语言是栈式计算机语言