1、COMPILERPL0语言的示例语法描述补充: 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,ifthen,do,while,call
2、,beginend,赋值语句) PL/0语言中的数据类型只有整型,没有浮点数,所以圆周率只能近似为3。数字最多为14位。标识符的有效长度是10 PL/0语言允许过程嵌套定义和递归调用的。过程最多可嵌套三层。 过程可以引用自己定义的局部标识符,也可以引用包围在它的外过程(包括主程序)定义的标识符。以下将用 扩充的巴科斯-瑙尔范式(BACKUS-NAUR FORM)和 语法图 (EBNF)两种形式给出PL/0语言的语法描述。-(a) PL/0语言文法的EBNF表示 EBNF表示的符号说明1) :用左右尖括号括起来的中文字表示语法构造成分,或称语法单位,为非终结符。2) = :该符号的左部由右部定义
3、,可读作定义为。3) | :表示或,为左部可由多个右部定义。 是不允许的. 因为非终结符作为一个独立的单位,不可分割4) :花括号表示其内的语法成分可以重复。在不加上下界时可重复0到任意次数,有上下界时为可重复次数的限制。 如:*表示*重复任意次,*38表示*重复3-8次。例如:= ll|d09 表示标识符的长度小于等于10.5) :方括号表示其内的成分为任选项。6) ( ) :表示圆括号内的成分优先。 PL/0语言文法的EBNF表示为:程序=分程序分程序=常量说明部分变量说明部分过程说明部分语句常量说明部分=CONST常量定义 ,常量定义;常量定义=标识符=无符号整数无符号整数=数字数字变量
4、说明部分=VAR标识符,标识符;标识符=字母字母|数字过程说明部分=过程首部分程序;过程说明部分;过程首部=PROCEDURE标识符;语句=赋值语句|条件语句|当型循环语句| 过程调用语句|读语句|写语句|复合语句|空赋值语句=标识符=表达式复合语句=BEGIN语句;语句END条件=表达式关系运算符表达式|ODD表达式表达式=+|-项加法运算符项 项=因子乘法运算符因子因子=标识符|无符号整数|(表达式)加法运算符=+|-乘法运算符=*|/关系运算符=#|=|=|=条件语句=IF条件THEN语句过程调用语句=CALL标识符当型循环语句=WHILE条件DO语句读语句=READ(标识符,标识符)写
5、语句=WRITE(表达式,表达式)字母=a|b|X|Y|Z数字=0|1|2|8|9(b) PL/0语言的语法描述图 用椭圆和圆圈中的英文字表示终结符,用长方形内的中文字表示非终结符。 画语法图时要注意箭头方向和弧度,语法图中应该无直角,如图 2.1给出的PL/0语法描述图。沿语法图分析时不能走尖狐线。图 2.1(a) 程序语法描述图 p9图 2.1(b) 分程序语法描述图 p10按照上图,不能将常量定义写在变量定义前面.图2.1(c) 语句语法描述图可以改动上图, 增加else语句可以改动上图, 使do语句可以处理多条语句.图 2.1(d) 条件语法描述图图 2.1(e) 表达式语法描述图图
6、2.1(f) 项语法描述图图 2.1(g) 因子语法描述图附录: 源代码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 cxmax
7、add1 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 factord
8、o(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,
9、b,c) if(-1=vardeclaration(a,b,c)return -1typedef enum false,true bool;/* 符号 */enum symbol nul,ident,number,plus,minus,times,slash,oddsym,eql,neq,lss,leq,gtr,geq,lparen,rparen,comma,semicolon,period,becomes,beginsym,endsym,ifsym,thensym,whilesym,writesym,readsym,dosym,callsym,constsym,varsym,procsym;
10、#define symnum 32/* 名字表中的类型 */enum object constant,variable,procedur;/* 虚拟机代码 */enum fct lit,opr,lod,sto,cal,inte,jmp,jpc;#define fctnum 8/* 虚拟机代码结构 */struct instruction enum fct f; /* 虚拟机代码指令 */ int l; /* 引用层与声明层的层次差 */ int a; /* 根据f的不同而不同 */;FILE* fas; /* 输出名字表 */FILE* fa; /* 输出虚拟机代码 */FILE* fa1;
11、/* 输出源文件及其各行对应的首地址 */FILE* fa2; /* 输出结果 */bool listswitch; /* 显示虚拟机代码与否 */bool tableswitch; /* 显示名字表与否 */char ch; /* 获取字符的缓冲区,getch 使用 */enum symbol sym; /* 当前的符号 */char idal; /* 当前ident */int num; /* 当前number */int cc,ll,kk; /* getch使用的计数器,cc表示当前字符(ch)的位置 */int cx; /* 虚拟机代码指针 */char line81; /* 读取行缓
12、冲区 */char aal; /* 临时符号 */struct instruction codecxmaxadd1; /* 存放虚拟机代码的数组 */char wordnorwal; /* 保留字 */enum symbol wsymnorw; /* 保留字对应的符号值 */enum symbol ssym256; /* 单字符的符号值 */char mnemonicfctnum5; /* 虚拟机代码指令名称 */bool declbegsyssymnum; /* 表示声明开始的符号集合 */bool statbegsyssymnum; /* 表示语句开始的符号集合 */bool facbeg
13、syssymnum; /* 表示因子开始的符号集合 */* 名字表结构 */struct tablestruct char nameal; /* 名字 */ enum object kind; /* 类型:const,var or procedure */ int val; /* 数值,仅const使用 */ int level; /* 所处层,仅const不使用 */ int adr; /* 地址,仅const不使用 */ int size; /* 需要分配的数据区空间,仅procedure使用 */;struct tablestruct tabletxmaxadd1; /* 名字表 */F
14、ILE* fin;FILE* fout;char fnameal;int err; /* 错误计数器 */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
15、(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);voi
16、d 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
17、源程序文件名回答是否输出虚拟机代码回答是否输出名字表fa.tmp输出虚拟机代码fa1.tmp输出源文件及其各行对应的首地址fa2.tmp输出结果fas.tmp输出名字表*/#include #include pl0c.h#include string.h/* 解释执行时使用的栈 */#define stacksize 500 int main() bool nxtlevsymnum; init(); /* 初始化 */ fas=fopen(fas.tmp,w); fa1=fopen(fa1.tmp,w); printf(Input file? ); fprintf(fa1,Input file
18、? ); scanf(%s,fname); /* 输入文件名 */ fin=fopen(fname,r); if(fin) fprintf(fa1,%sn,fname); printf(List object code?(Y/N); /* 是否输出虚拟机代码 */ scanf(%s,fname); listswitch=(fname0=y|fname0=Y); printf(List symbol table?(Y/N); /* 是否输出名字表 */ scanf(%s,fname); tableswitch=(fname0=y|fname0=Y); err=0; cc=cx=ll=0; ch=
19、 ; kk=al-1; if(-1!=getsym() fa=fopen(fa.tmp,w); fa2=fopen(fa2.tmp,w); addset(nxtlev,declbegsys,statbegsys,symnum); nxtlevperiod=true; if(-1=block(0,0,nxtlev) /* 调用编译程序 */ fclose(fa); fclose(fa1); fclose(fin); printf(n); return 0; fclose(fa); fclose(fa1); if(sym!=period)error(9); if(err=0)interpret()
20、; /* 调用解释执行程序 */ else printf(Errors in pl/0 program); fclose(fin); else printf(Cant open file!n); fprintf(fa1,Cant open file!n); fclose(fa1); fclose(fas); printf(n); return 0;/* 在适当的位置显示错误 */void error(int n) char space81; memset(space,32,81); spacecc-1=0; /* 出错时当前符号已经读完,所以cc-1 */ printf(*%s!%dn,spa
21、ce,n); fprintf(fa1,*%s!%dn,space,n); err+;/* 词法分析,获取一个符号 */int getsym() int i,j,k; while(ch= |ch=10|ch=9) /* 忽略空格、换行和TAB */ getchdo; if(ch=a&ch=z) /* 名字或保留字以a.z开头 */ k=0; do if(k=a&ch=0&ch=9); ak=0; strcpy(id,a); i=0; j=norw-1; do /* 搜索当前符号是否为保留字 */ k=(i+j)/2; if(strcmp(id,wordk)=0)i=k+1; while(ij)s
22、ym=wsymk; else sym=ident; /* 搜索失败则,是名字或数字 */ else if(ch=0&ch=0&chnmax)error(30); else if(ch=:) /* 检测赋值符号 */ getchdo; if(ch=) sym=becomes; getchdo; else sym=nul; /* 不能识别的符号 */ else if(ch=) /* 检测大于或大于等于符号 */ getchdo; if(ch=) sym=geq; getchdo; else sym=gtr; else sym=ssymch; /* 当符号不满足上述条件时,全部按照单字符符号处理 *
23、/ getchdo; return 0;/* 生成虚拟机代码 */int gen(enum fct x, /* f */ int y, /* l */ int z /* a */ ) if(cxcxmax) printf(Program too long); /* 程序过长 */ return -1; codecx.f=x; codecx.l=y; codecx.a=z; cx+; return 0;/* 在某一部分(如一条语句,一个表达式)将要结束时时我们希望下一个符号属于某集合(该部分的后跟符号),test负责这项监测,并且负责当监测不通过时的补救措施,程序在需要检测时指定当前需要的符号集
24、合和补救用的集合(如之前未完成部分的后跟符号),以及检测不通过时的错误号 */int test(bool* s1, /* 我们需要的符号 */ bool* s2, /* 如果不是我们需要的,则需要一个补救用的集合 */ int n) /* 错误号 */ if(!inset(sym,s1) error(n); /* 当检测不通过时,不停获取符号,直到它属于需要的集合或补救的集合 */ while(!inset(sym,s1)&(!inset(sym,s2) getsymdo; return 0;/* 编译程序主体 */int block(int lev, /* 当前分程序所在层 */ int t
25、x, /* 名字表当前尾指针 */ bool* fsys /* 当前模块后跟符号集合 */ ) int i; int dx; /* 名字分配到的相对地址 */ int tx0; /* 保留初始tx */ int cx0; /* 保留初始cx */ bool nxtlevsymnum; /* 在下级函数的参数中,符号集合均为值参,但由于使用数租实现, 传递进来的是指针,为防止下级函数改变上级函数的集合,开辟新的空间 传递给下级函数,之后所有的nxtlev都是这样 */ dx=3; tx0=tx; /* 记录本层名字的初始位置 */ tabletx.adr=cx; gendo(jmp,0,0); if(levlevmax)error(32);
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1