PL0编译器分析与语法扩展

转载请注明来自 b0t0w1’blog

一、简介

1.什么是 pl0 语言

  PL 语言是PASCAL语言的一个子集,该语言不太大,但能充分展示高级语言的最基本成分。PL0具有子程序概念,包括过程说明和过程调用语句。在数据类型方面,PL0只包含唯一的整型,可以说明这种类型的常量和变量。运算符有+,-,*,/,=,<>,<,>,<=,>=,(,)。说明部分包括常量说明、变量说明和过程说明。

2.pl0语言语法

  给个简单的例子,program 表明这是 pl0 语言, main 为程序名称,接下是变量声明与过程声明,beginend 中间为 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. 整体思路

本次分析只分析扩展功能时需要使用的代码,其他略过。
  interpret 工程用于翻译中间语言代码,此次不作修改、不作分析直接使用就好。 PL 工程中 pl.cpp 用于生成中间语言代码,其他 .cpp 用于 I/0 操作,亦不作分析。
  此次分析从各函数入手,着重分析扩展语法时需要用到,需要修改的函数。对函数的分析比较简要,想深入了解的欢迎下载上面链接的源代码,里面有更为详尽的分析。

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() 并没有结束,还要初始化一些集合,此处也比较难理解。不懂时略过以后慢慢体会。

  首先思考一个问题,保留字全部声明了,但是保留字的使用也要合乎一定的规则(此处规则不是指类似于 forwhile 循环内部的关键字合法性,而是将 forwhile 看成一个块)。具体来说,比如程序开始,读入 program main ; 确认是 pl0 语言,那么接下来的一定是 varconsttypeprocedure 等声明,而不可能是 forwhileif 等语句,这些语句应该在声明后且也在 begin 后才能出现。也就是说程序中出现就是语法错误,编译器应当报错。

  那如何判断读到的关键字在此处是否合法呢?initial() 集合吧关键字进行了分类,每个类构成一个集合,编译语句时要指明所属的集合,如果超过了说明程序语法错误或者这一块程序编译完需要更换集合。比如前面例子中读到了 begin 说明声明结束,要开始处理 for 等结构了。

详细如下:
decbegsys: 声明开始的符号集
facbegsys: 因子开始的符号集
typebegsys: 设置类型符号集
constbegsys: 常量申明符号集
statbegsys: 语句开始的符号集
  
  其中 statebegsys 因为扩展了 forrepeatcase 等结构,要将该结构的开始关键字放入,但是结构里的关键字不能加入。比如 for 循环里的 to 等,它们的语法是否合法有编译 for 的函数判断。

statbegsys = 
  • 20
    点赞
  • 118
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值