大家好,欢迎来到IT知识分享网。
转载请注明来自 b0t0w1’blog
一、简介
1.什么是 pl0 语言
PL 语言是PASCAL语言的一个子集,该语言不太大,但能充分展示高级语言的最基本成分。PL0具有子程序概念,包括过程说明和过程调用语句。在数据类型方面,PL0只包含唯一的整型,可以说明这种类型的常量和变量。运算符有+,-,*,/,=,<>,<,>,<=,>=,(,)。说明部分包括常量说明、变量说明和过程说明。
2.pl0语言语法
给个简单的例子,program 表明这是 pl0 语言, main 为程序名称,接下是变量声明与过程声明,begin 与 end 中间为 main 的执行程序,注意的是声明必须在一开始就全部声明完毕。
program main; var a: integer; procedure P1 (a:integer); begin var b: integer; b := a; // 输出 b call write(b) end; begin // 输入 a call read(a); call P1(a) end.
需要注意的是每个程序块内如果只有一条语句,可以不加begin..end 结构,但是多条语句必须使用 begin…end结构。同时,除了最后一条语句外,每句都已 ‘;’ 结尾。 上面的 call write(b) 和 call P1(a) 为最后一句,不加分号。
3.pl0编译器
编译器由两个项目工程组成, pl 工程将 pl0 语言翻译成中间语言, interpret 工程根据中间语言代码生成可执行文件。
这次所使用的编译器已实现 while 循环和 if 判断语句,变量类型方面实现了整型和字符型以及数组,运算实现了加减乘除、赋值以及 bool 运算。但不支持 for 循环等语法。
4.功能扩展
本次在 pl0 编译器原有的基础上添加了 for 循环 、 repeat 循环, case 条件选择、 +=赋值、 -=赋值、{}注释、–、++ 等。这些功能都是在 PL 工程下实现,本次实验不修改 interpret 工程。
二、 原编译器分析
1. 整体思路
2. 函数分析
main() 函数中,首先输入 pl0 源程序,然后处理源程序输出中间语言代码。main() 函数虽短,但是调用了复杂的函数,函数之间嵌套频繁,个人觉得众多函数才是这个程序的精髓。由于篇幅,只分析需要用到的函数。
(1)initial()
initial() 用于初始化,首先是保留字。保留字作为全局变量,应当在文件一开始就声明其个数与具体有哪些。原有为 20 个,此处为我添加后的,每一个对应一个关键字。
#include "ptoc.h" /* 保留字个数 */ const int norw = 27; enum symbol {nul, ident, intcon, charcon, plus, minus, times, divsym, eql, neq, lss, leq, gtr, geq, ofsym, arraysym, programsym, modsym, andsym, orsym, notsym, lbrack, rbrack, lparen, rparen, comma, semicolon, period, becomes, colon, beginsym, endsym, ifsym, thensym, elsesym, whilesym, repeatsym, dosym, callsym, constsym, typesym, varsym, procsym, forsym, downtosym, tosym, untilsym, casesym, otherwisesym, addsym, subsym, last_symbol};
同时声明类型、指令等,注意此处不为关键字,只是为了方便本程序处理
enum oobject {konstant, typel, variable, prosedure, last_oobject}; enum types {notyp, ints, chars, bool_, arrays, last_types}; enum opcod {lit, lod, ilod, loda, lodt, sto, lodb, cpyb, jmp, jpc, red, wrt, cal, retp, endp, udis, opac, entp, ands, ors, nots, imod, mus, add, sub, mult, idiv, eq, ne, ls, le, gt, ge, last_opcod};
然后我们看看 initial() 是怎么处理的。只列出扩展的代码,其余详见上面链接下载的工程源码。
每个 word1[] 元素存放一个关键字,大小只有 10 个字节。这里我只列出扩展的关键字。有没有发现规律?没错,存放时按照大小顺序排列的,因为在查找中使用的是二分查找。
后面的 wsym[] 用于把 word1[] 和前面声明的 symbol{} 对应起来, 注意必须是一一对应,下标顺序必须保持一致。
然后是 ssym[] 和 mnemonic[],记录着所有的运算符和中间语言指令。同样这些值必须在前面的 enum 处声明。
扩展功能时,首先扩充前面枚举的值,然后扩充 initial() 的值,注意要分清楚扩充的类型,是关键字还是运算符或者是指令,在相应的地方修改。
void initial() { .... word1[ 5] = "case "; word1[ 8] = "downto "; word1[11] = "for "; word1[17] = "otherwise "; word1[20] = "repeat "; word1[22] = "to "; word1[24] = "until "; wsym[ 5] = casesym; wsym[ 8] = downtosym; wsym[11] = forsym; wsym[17] = otherwisesym; wsym[20] = repeatsym; wsym[22] = tosym; wsym[24] = untilsym; .... }
initial() 并没有结束,还要初始化一些集合,此处也比较难理解。不懂时略过以后慢慢体会。
首先思考一个问题,保留字全部声明了,但是保留字的使用也要合乎一定的规则(此处规则不是指类似于 for 、while 循环内部的关键字合法性,而是将 for 、while 看成一个块)。具体来说,比如程序开始,读入 program main ; 确认是 pl0 语言,那么接下来的一定是 var、const、type、procedure 等声明,而不可能是 for、while、if 等语句,这些语句应该在声明后且也在 begin 后才能出现。也就是说程序中出现就是语法错误,编译器应当报错。
那如何判断读到的关键字在此处是否合法呢?initial() 集合吧关键字进行了分类,每个类构成一个集合,编译语句时要指明所属的集合,如果超过了说明程序语法错误或者这一块程序编译完需要更换集合。比如前面例子中读到了 begin 说明声明结束,要开始处理 for 等结构了。
statbegsys =
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/135811.html