本文地址:http://www.cnblogs.com/archimedes/p/pl0-compiler1.html,转载请注明源地址。
PL/0简介
以下内容摘自维基:
PL/0,is similar to but much simpler than the general-purpose programming language Pascal, intended as an educational programming language. It serves as an example of how to construct a compiler. It was originally introduced in the book, Algorithms + Data Structures = Programs, by Niklaus Wirth in 1975. It features quite limited language constructs: there are no real numbers, very few basic arithmetic operations and no control-flow constructs other than "if" and "while" blocks. While these limitations make writing real applications in this language impractical, it helps the compiler remain compact and simple.
翻译如下:
PL/0语言,作为一个教育用的编程语言,和通用编程语言Pascal有些类似但是要简单得多。作为如何构建一个编译器的一个例子。它最初是出自Niklaus Wirth于1975年写的《算法+数据结构=程序》一书中。它具有非常有限的语言构造:没有实数,只有很少量的基本算术运算,除了"if"和"while"语句块以外没有其他的控制流。虽然这些限制使这种语言在实际应用中受到限制,但它却有助于编译器保持紧凑和简单。
PL/0语言是PASCAL语言的子集
同PASCAL
作用域规则(内层可引用包围它的外层定义的标识符),上下文约束,过程可嵌套定义,可递归调用
子集
数据类型,只有整型
数据结构 ,只有简变和常数
数字最多为14位
标识符的有效长度是10
语句种类
过程最多可嵌套三层
从上面的叙述可以看出,熟悉PL/0的相关指令,并能用其他语言(我选择C语言)来写一个简单的解释器,在某种程度上掌握一些编译器的原理,提高自身的编程能力
PL/0程序示例
举几个实例更加的直观:
1、计算最大公约数
var m, n, r, q; { 计算m和n的最大公约数 } procedure gcd; begin while r#0 do begin q := m / n; r := m - q * n; m := n; n := r; end end; begin read(m); read(n); { 为了方便,规定m >= n } if m < n then begin r := m; m := n; n := r; end; begin r:=1; call gcd; write(m); end; end.
2、计算 sum = 1! + 2 ! + ... + n! (n从控制台读入)
var n, m, fact, sum; { 递归计算 fact = m! } procedure factorial; begin if m > 0 then begin fact := fact * m; m := m - 1; call factorial; end; end; begin {读入n } read(n); sum := 0; while n > 0 do begin m := n; fact := 1; call factorial; sum := sum + fact; n := n - 1; end; {输出n! } write(sum); end.
对于上面的代码,熟悉Pascal语言的同学应该很熟悉,没错,pl/0就是简化版本的pascal,为了降低设计编译程序的难度而故意为之专供教学用的。
PL/0编译程序
PL/0编译系统
类pcode代码指令的结构
PL/0编译程序所产生的目标代码是一个假想栈式计算机的汇编语言,可称为类PCODE指令代码,它不依赖具体计算机,其指令集极为简单,指令格式也很单纯,其格式如下:
f | l | a |
其中f代表功能码,l表示层次差,也就是变量或过程被引用的分程序与说明该变量或过程的分程序之间的层次差。a的含意对不同的指令有所区别,对存取指令表示位移量,而对其它的指令则分别有不同的含义,见下面对每条指令的解释说明。
目标指令有8条:
① LIT:将常量值取到运行栈顶。a域为常数值。
② LOD:将变量放到栈顶。a域为变量在所说明层中的相对位置,l为调用层与说明层的层差值。
③ STO:将栈顶的内容送入某变量单元中。a,l域的含意同LOD指令。
④ CAL:调用过程的指令。a为被调用过程的目标程序入口地址,l为层差。
⑤ INT:为被调用的过程(或主程序)在运行栈中开辟数据区。a域为开辟的单元个数。
⑥ JMP:无条件转移指令,a为转向地址。
⑦ JPC:条件转移指令,当栈顶的布尔值为非真时,转向a域的地址,否则顺序执行。
⑧ OPR:关系运算和算术运算指令。将栈顶和次栈顶的内容进行运算,结果存放在次栈顶,此外还可以是读写等特殊功能的指令,具体操作由a域值给出。(详见解释执行程序)。
类pcode代码指令的详细解释(指令功能表)
LIT 0 a | 将常数值取到栈顶,a为常数值 |
LOD l a | 将变量值取到栈顶,a为偏移量,l为层差 |
STO l a | 将栈顶内容送入某变量单元中,a为偏移量,l为层差 |
CAL l a | 调用过程,a为过程地址,l为层差 |
INT 0 a | 在运行栈中为被调用的过程开辟a个单元的数据区 |
JMP 0 a | 无条件跳转至a地址 |
JPC 0 a | 条件跳转,当栈顶布尔值非真则跳转至a地址,否则顺序执行 |
OPR 0 0 | 过程调用结束后,返回调用点并退栈 |
OPR 0 1 | 栈顶元素取反 |
OPR 0 2 | 次栈顶与栈顶相加,退两个栈元素,结果值进栈 |
OPR 0 3 | 次栈顶减去栈顶,退两个栈元素,结果值进栈 |
OPR 0 4 | 次栈顶乘以栈顶,退两个栈元素,结果值进栈 |
OPR 0 5 | 次栈顶除以栈顶,退两个栈元素,结果值进栈 |
OPR 0 6 | 栈顶元素的奇偶判断,结果值在栈顶 |
OPR 0 7 | |
OPR 0 8 | 次栈顶与栈顶是否相等,退两个栈元素,结果值进栈 |
OPR 0 9 | 次栈顶与栈顶是否不等,退两个栈元素,结果值进栈 |
OPR 0 10 | 次栈顶是否小于栈顶,退两个栈元素,结果值进栈 |
OPR 0 11 | 次栈顶是否大于等于栈顶,退两个栈元素,结果值进栈 |
OPR 0 12 | 次栈顶是否大于栈顶,退两个栈元素,结果值进栈 |
OPR 0 13 | 次栈顶是否小于等于栈顶,退两个栈元素,结果值进栈 |
OPR 0 14 | 栈顶值输出至屏幕 |
OPR 0 15 | 屏幕输出换行 |
OPR 0 16 | 从命令行读入一个输入置于栈顶 |
一个PL/0程序与目标代码类pcode指令的映射例子:
PL/0编译程序总体结构
PL/0编译程序的组织:一个以语法、语义分析程序为中心的单遍编译程序
参考资料
《编译原理》--清华大学出版社