补充: PL/0 语言及其编译程序的实现
1 概述
PL/0 语言的编译程序把 PL/0 语言程序翻译成为一种称
为 类 pcode 的假想的栈式计算机汇编语言程序。这种
汇编语言与机器无关,若要在某一机器上实现 PL/0 语
言程序,只需用机器上配置的任何语言对类 pcode 语言
程序进行解释执行。
世界著名计算机科学家 N.Wirth 编写了 "PL/0 语言的
编译程序"。
后面附了 PL/0 编译程序源代码。
2. PL/0 语言描述
何为 PL/0 语言 - PL/0 语言是 PASCAL 语言的子集. 具备了一般高级语言的必备部分.
(如: read,write,if-then,do,while,call,begin-end,赋值语句)
PL/0 语言中的数据类型只有整型,没有浮点数,所以圆周率只能近似为 3。数字最多为 14 位。标识符的有效长度是 10
PL/0 语言允许过程嵌套定义和递归调用的。过程最多可嵌套三层。
过程可以引用自己定义的局部标识符,也可以引用包围在它的外过程(包括主程序)定义的标识符。
1
以下将用 扩充的巴科斯-瑙尔范式(BACKUS-NAUR FORM)和 语法图 (EBNF)两种形式给出 PL/0 语言的语法描述。
-------------------------------------------------------------------------------------------------------------------------------------------------------
(a) PL/0 语言文法的 EBNF 表示
EBNF 表示的符号说明
1) 〈 〉:用左右尖括号括起来的中文字表示语法构造成分,或称语法单位,为非终结符。
2) ∷= :该符号的左部由右部定义,可读作'定义为'。
3)
4)
| :表示'或',为左部可由多个右部定义。 <标识符 | 常量> 是不允许的. 因为非终结符作为一个独立的单位,不可分割
{ } :花括号表示其内的语法成分可以重复。在不加上下界时可重复 0 到任意次数,有上下界时为可重复次数的限制。
如:{*}表示*重复任意次,{*}38 表示*重复 3-8 次。例如<标识符>::= l{l|d}09 表示标识符的长度小于等于 10.
5)
6)
[ ] :方括号表示其内的成分为任选项。
( ) :表示圆括号内的成分优先。
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
2
(b) PL/0 语言的语法描述图
用椭圆和圆圈中的英文字表示终结符,用长方形内的中文字表示非终结符。
内的文字表示非终结符
或
内的文字或符号表示终结符
画语法图时要注意箭头方向和弧度,语法图中应该无直角,如图 2.1 给出的 PL/0 语法描述图。沿语法图分析时不能走
尖狐线。
图 2.1(a) 程序语法描述图 p9
按照上图,不能将常量定义写在变量定义前面.
图 2.1(b) 分程序语法描述图 p10
3
可以改动上图, 增加 else 语句
if
条件
then
语句
else
语句
图 2.1(c) 语句语法描述图
可以改动上图, 使 do 语句可以处理多条语句.
do
语句
,
图 2.1(d) 条件语法描述图
4
图 2.1(e) 表达式语法描述图
图 2.1(f) 项语法描述图
图 2.1(g) 因子语法描述图
5
附录: 源代码
pl0c.h
/* 关键字个数 */
#define norw 13
/* 名字表容量 */
#define txmax 100
/* 所有的add1用于定义数组 */
#define txmaxadd1 101
/* number的最大位数 */
#define nmax 14
/* 符号的最大长度 */
#define al 10
/* 地址上界 */
#define amax 2047
/* 最大允许过程嵌套声明层数 */
#define levmax 3
/* 最多的虚拟机代码数 */
#define cxmax 200
#define cxmaxadd1 201
/* 当函数中会发生fatal error时,返回-1告知调用它的函数,最终退出程序 */
#define getsymdo if(-1==getsym())return -1
#define getchdo if(-1==getch())return -1
#define testdo(a,b,c) if(-1==test(a,b,c))return -1
#define gendo(a,b,c) if(-1==gen(a,b,c))return -1
#define expressiondo(a,b,c) if(-1==expression(a,b,c))return -1
#define factordo(a,b,c) if(-1==factor(a,b,c))return -1
#define termdo(a,b,c) if(-1==term(a,b,c))return -1
#define conditiondo(a,b,c) if(-1==condition(a,b,c))return -1
#define statementdo(a,b,c) if(-1==statement(a,b,c))return -1
#define constdeclarationdo(a,b,c) if(-1==constdeclaration(a,b,c))return -1
#define vardeclarationdo(a,b,c) if(-1==vardeclaration(a,b,c))return -1
typedef enum {false,true} bool;
/* 符号 */
enum symbol
{nul,ident,number,plus,minus,times,slash,oddsym,eql,neq,lss,leq,gtr,geq,lparen,rparen,comma,semicolon,perio
d,becomes,beginsym,endsym,ifsym,thensym,whilesym,writesym,readsym,dosym,callsym,constsym,varsym,procsym};
#define symnum 32
/* 名字表中的类型 */
enum object {constant,variable,procedur};
/* 虚拟机代码 */
enum fct {lit,opr,lod,sto,cal,inte,jmp,jpc};
#define fctnum 8
6
/* 虚拟机代码结构 */
struct instruction
{
};
enum fct f; /* 虚拟机代码指令 */
int l; /* 引用层与声明层的层次差 */
int a; /* 根据f的不同而不同 */
FILE* fas;
/* 输出名字表 */
FILE* fa; /* 输出虚拟机代码 */
FILE* fa1; /* 输出源文件及其各行对应的首地址 */
FILE* fa2; /* 输出结果 */
bool listswitch; /* 显示虚拟机代码与否 */
bool tableswitch; /* 显示名字表与否 */
char ch; /* 获取字符的缓冲区,getch 使用 */
enum symbol sym; /* 当前的符号 */
char id[al]; /* 当前ident */
int num; /* 当前number */
int cc,ll,kk; /* getch使用的计数器,cc表示当前字符(ch)的位置 */
int cx; /* 虚拟机代码指针 */
char line[81]; /* 读取行缓冲区 */
char a[al]; /* 临时符号 */
struct instruction code[cxmaxadd1]; /* 存放虚拟机代码的数组 */
char word[norw][al]; /* 保留字 */
enum symbol wsym[norw]; /* 保留字对应的符号值 */
enum symbol ssym[256]; /* 单字符的符号值 */
char mnemonic[fctnum][5]; /* 虚拟机代码指令名称 */
bool declbegsys[symnum]; /* 表示声明开始的符号集合 */
bool statbegsys[symnum]; /* 表示语句开始的符号集合 */
bool facbegsys[symnum]; /* 表示因子开始的符号集合 */
/* 名字表结构 */
struct tablestruct
{
};
char name[al];
/* 名字 */
enum object kind; /* 类型:const,var or procedure */
int val; /* 数值,仅const使用 */
int level; /* 所处层,仅const不使用 */
int adr; /* 地址,仅const不使用 */
int size; /* 需要分配的数据区空间,仅procedure使用 */
struct tablestruct table[txmaxadd1]; /* 名字表 */
FILE* fin;
FILE* fout;
char fname[al];
int err; /* 错误计数器 */
7
void error(int n);
int getsym();
int getch();
void init();
int gen(enum fct x,int y,int z);
int test(bool* s1,bool* s2,int n);
int inset(int e,bool* s);
int addset(bool* sr,bool* s1,bool* s2,int n);
int subset(bool* sr,bool* s1,bool* s2,int n);
int mulset(bool* sr,bool* s1,bool* s2,int n);
int block(int lev,int tx,bool* fsys);
void interpret();
int factor(bool* fsys,int* ptx,int lev);
int term(bool* fsys,int* ptx,int lev);
int condition(bool* fsys,int* ptx,int lev);
int expression(bool* fsys,int* ptx,int lev);
int statement(bool* fsys,int* ptx,int lev);
void listcode(int cx0);
int vardeclaration(int* ptx,int lev,int* pdx);
int constdeclaration(int* ptx,int lev,int* pdx);
int postion(char* idt,int tx);
void enter(enum object k,int* ptx,int lev,int* pdx);
int base(int l,int* s,int b);
pl0c.c
/*
Windows 下c语言PL/0编译程序
在Visual C++ 6.0和Visual C.NET上运行通过
使用方法:
运行后输入PL/0源程序文件名
回答是否输出虚拟机代码
回答是否输出名字表
fa.tmp输出虚拟机代码
fa1.tmp输出源文件及其各行对应的首地址
fa2.tmp输出结果
fas.tmp输出名字表
*/
#include
#include "pl0c.h"
#include "string.h"
/* 解释执行时使用的栈 */
#define stacksize 500
8