IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 基于C++的PL0语言编译器及功能扩充 -> 正文阅读

[C++知识库]基于C++的PL0语言编译器及功能扩充


内容有点长,代码量7000行+,创造不易,点赞关注加三连!!!有错误可指出,谢谢大咖~

一、实验介绍

1. PL0文法说明

图1

PL0 编译程序总体结构如图所示,该程序的主要语法分析过程采用递归下降子程序法实现。为了将上下文有关文法转化为上下文无关文法,定义符号表进行管理,最终转化为虚拟机代码进行解释执行。PL0 语言编译程序采用以语法分析为核心、一遍扫描的编译方法。词法分析和代码生成作为独立的子程序供语法分析程序调用。语法分析的同时,提供了出错报告和出错恢复的功能。在源程序没有错误编译通过的情况下,调用类 P-code 解释程序解释执行生成的类 P-code代码。

PL/0 的文法分析部分是在编译源程序的 statement 部分,这部分的 EBNF 表示如下:

<语句>::=<赋值语句>|<条件语句>|<当型循环语句>|<过程调用语句>|<读语句>|<写语句>|<复合语句>|<>
<赋值语句>::=<标识符>:=<表达式>
<复合语句>::=BRGIN<语句>{<语句>}END
<条件>::=<表达式><关系运算符><表达式>|ODD<表达式>
<表达式>::=[+|-]<>{<加法运算符><>}
<>::=<因子>{<乘法运算符><因子>}
<因子>::=<标识符>|<无符号整数>|‘(’<表达式>‘)’
<条件语句>::=IF<条件>THEN<语句>
<过程调用语句>::=CALL<标识符>
<当型循环语句>::=WHILE<条件>DO<语句>
<读语句>::=READ‘(<标识符>{,<标识符>}‘)’
<写语句>::=WRITE‘(<表达式>{,<表达式>}‘)’

2. PL0主函数

PL0主函数 main()执行流程下:首先打开一个文件,返回一个文件指针。如果可以成功打开的话,开始读取字符,如果不是文件结束符时,进入 block()函数,进行语法分析。遇到程序结束符时,统计程序中的语法错误,最后给出相应的提示,并关闭打开的文件,防止文件数据的丢失。

int main()
{
	bool nxtlev[symnum];

	printf("Input pl/0 file:   ");
	scanf("%s", fname);     /* 输入文件名 */

	fin = fopen(fname, "r");

	if (fin)
	{
        fname[0]='y';
		listswitch = (fname[0]=='y' || fname[0]=='Y');
		fname[0]='y';
		tableswitch = (fname[0]=='y' || fname[0]=='Y');

		fa1 = fopen("fa1.tmp", "w");
		fprintf(fa1,"Input pl/0 file:   ");
		fprintf(fa1,"%s\n",fname);

		init();     /* 初始化 */

		err = 0;
		cc = cx = ll = 0;
		ch = ' ';

		if(-1 != getsym())
		{
			fa = fopen("fa.tmp", "w");
			fas = fopen("fas.tmp", "w");
			addset(nxtlev, declbegsys, statbegsys, symnum);
			nxtlev[period] = true;

			if(-1 == block(0, 0, nxtlev))   /* 调用编译程序 */
			{
				fclose(fa);
				fclose(fa1);
				fclose(fas);
				fclose(fin);
				printf("\n");
				return 0;
			}
			fclose(fa);
			fclose(fa1);
			fclose(fas);

			if (sym != period)
			{
				error(9);
			}

			if (err == 0)
			{
				fa2 = fopen("fa2.tmp", "w");
				interpret();    /* 调用解释执行程序 */
				fclose(fa2);
			}
			else
			{
				printf("Errors in pl/0 program");
			}
		}

		fclose(fin);
	}
	else
	{
		printf("Can't open file!\n");
	}

	printf("\n");
	return 0;
}

3. 词法分析函数

Getsym()函数用于对词法进行分析。调用 getsym 时,它通过 getch 过程从源程序中获得一个字符。如果这个字符是字母,则继续获取字符或数字,最终可以拼成一个单词,查保留字表,如果查到则为保留字,把 sym 变量赋成相应的保留字类型值;如果没有查到,则这个单词应是一个用户自定义的标识符(可能是变量名、常量名或是过程的名字),把 sym 置为 ident,把这个单词存入 id 变量。查保留字表时使用了二分法查找以提高效率。如果 getch 获得的字符是数字,则继续用 getch 获取数字,并把它们拼成一个整数,然后把 sym 置为 number,并把拼成的数值放入 num 变量。如果识别出其它合法的符号(比如:赋值号、大于号、小于等于号等),则把 sym 则成相应的类型。如果遇到不合法的字符,把 sym 置成 nul 。

/*
* 词法分析,获取一个符号
*/
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<al)
			{
				a[k] = ch;
				k++;
			}
			getchdo;
		} while (ch>='a' && ch<='z' || ch>='0' && ch<='9');
		a[k] = 0;
		strcpy(id, a);
		i = 0;
		j = norw-1;
		do {    /* 搜索当前符号是否为保留字 */
			k = (i+j)/2;
			if (strcmp(id,word[k]) <= 0)
			{
				j = k - 1;
			}
			if (strcmp(id,word[k]) >= 0)
			{
				i = k + 1;
			}
		} while (i <= j);
		if (i-1 > j)
		{
			sym = wsym[k];
		}
		else
		{
			sym = ident; /* 搜索失败则,是名字或数字 */
		}
	}
	else
	{
		if (ch>='0' && ch<='9')
		{           /* 检测是否为数字:以0..9开头 */
			k = 0;
			num = 0;
			sym = number;
			do {
				num = 10*num + ch - '0';
				k++;
				getchdo;
			} while (ch>='0' && ch<='9'); /* 获取数字的值 */
			k--;
			if (k > nmax)
			{
				error(30);
			}
		}
		else
		{
			if (ch == ':')      /* 检测赋值符号 */
			{
				getchdo;
				if (ch == '=')
				{
					sym = becomes;
					getchdo;
				}
				else
				{
					//sym = nul;  /* 不能识别的符号 */
					sym=colon;//识别为冒号 
				}
			}
			else
			{
				if (ch == '<')      /* 检测小于或小于等于符号 */
				{
					getchdo;
					if (ch == '=')
					{
						sym = leq;     /* 小于等于号 */
						getchdo;     /* 再读一个符号 */
					}
					else
					{
						sym = lss;     /* 检单独的小于号 */
					}
				}
				else
				{
					if (ch=='>')        /* 检测大于或大于等于符号 */
					{
						getchdo;
						if (ch == '=')
						{
							sym = geq;     /* 大于等于号 */
							getchdo;
						}
						else
						{
							sym = gtr;     /* 单独的大于号 */
						}
					}
					else if(ch=='+')     /* 读到+号 */
					{
					    getchdo;
					    if(ch=='+')
					    {
					    	sym=plusplus;  //++
					    	getchdo;
						}
						else if(ch=='=')
		                {
			                sym=plusbk;  //+=
			                getchdo;
		                }						
						else
						{
							sym=plus;
						}										
					} 
					else if(ch=='-')     /* 读到-号 */
					{
					    getchdo;
					    if(ch=='-')
					    {
					    	sym=minusminus;  //--
					    	getchdo;
						}
						else if(ch=='=')
		                {
			                sym=minusbk;  //-=
			                getchdo;
		                }						
						else
						{
							sym=minus;
						}										
					}
					else if(ch=='/')//添加注释 
					{
						getchdo;
						if(ch=='*')
						{
							getchdo;
							while(1)
							{
								while(ch!='*')
								    getchdo;
								getchdo;
								if(ch=='/')//注释/*...*/ 
								    break;
							}
							getchdo;
							getsym();
						}
						else if(ch=='/') //行注释// 
						{
							cc=ll;
							ch=' ';
							getsym();
						}
						else
						{
							sym=slash;
						}
					}
					else
					{
						sym = ssym[ch];     /* 当符号不满足上述条件时,全部按照单字符符号处理 */
						//getchdo;
						//richard
						if (sym != period)
						{
							getchdo;
						}
						//end richard
					}
				}
			}
		}
	}
	return 0;
}

4. 语法语义分析

(1)Block()函数主要用来对常量、变量和过程申明的处理,并进行填表工作。在判断了嵌套层数没有超过规定的层数后,开始分析源程序。首先判断是否遇到了常量声明,如果遇到则开始常量定义,把常量存入符号表。接下去用同样的方法分析变量声明,变量定义过程中会用 dx 变量记录下局部数据段分配的空间个数。然后如果遇到 procedure 保留字则进行过程声明和定义,声明的方法是把过程的名字和所在的层次记入符号表,过程定义的方法就是通过递归调用 block 过程,因为每个过程都是一个分程序。

/*
* 编译程序
*
* lev:    当前分程序所在层
* tx:     名字表当前尾指针
* fsys:   当前模块后跟符号集?
*/
int block(int lev, int tx, bool* fsys)
{
	int i;

	int dx;                 /* 名字分配到的相对地址 */
	int tx0;                /* 保留初始tx */
	int cx0;                /* 保留初始cx */
	bool nxtlev[symnum];    /* 在下级函数的参数中,符号集合均为值参,但由于使用数组实现,
							传递进来的是指针,为防止下级函数改变上级函数的集合,开辟新的空?
							传递给下级函数*/

	dx = 3;
	tx0 = tx;               /* 记录本层名字的初始位置 */
	table[tx].adr = cx;

	gendo(jmp, 0, 0);

	if (lev > levmax)
	{
		error(32);
	}

	do {

		if (sym == constsym)    /* 收到常量声明符号,开始处理常量声明 */
		{
			getsymdo;
			do { 
			constdeclarationdo(&tx, lev, &dx);  /* dx的值会被constdeclaration改变,使用指针 */
			while (sym == comma)
			{
				getsymdo;
				constdeclarationdo(&tx, lev, &dx);
			}
			if (sym == semicolon)
			{
				getsymdo;
			}
			else
			{
				error(5);   /*漏掉了逗号或者分号*/
			}
			} while (sym == ident); 
		}

		if (sym == varsym)      /* 收到变量声明符号,开始处理变量声明 */
		{
			getsymdo;           /*调用getsym()的宏*/
			do {
			vardeclarationdo(&tx, lev, &dx);  /*添加符号表信息*/
			while (sym == comma)//, 
			{
				getsymdo;
				vardeclarationdo(&tx, lev, &dx);
			}
			if (sym == semicolon)//;
			{
				getsymdo;
			}
			else
			{
				error(5);
			}
			}while (sym == ident);
		}

		while (sym == procsym) /* 收到过程声明符号,开始处理过程声明 */
		{
			getsymdo;

			if (sym == ident)
			{
				enter(procedur, &tx, lev, &dx); /* 记录过程名字 */
				getsymdo;
			}
			else
			{
				error(4);   /* procedure后应为标识符 */
			}

			if (sym == semicolon)
			{
				getsymdo;
			}
			else
			{
				error(5);   /* 漏掉了分号 */
			}

			memcpy(nxtlev, fsys, sizeof(bool)*symnum);
			nxtlev[semicolon] = true;
			if (-1 == block(lev+1, tx, nxtlev))
			{
				return -1;  /* 递归调用 */
			}

			if(sym == semicolon)
			{
				getsymdo;
				memcpy(nxtlev, statbegsys, sizeof(bool)*symnum);
				nxtlev[ident] = true;
				nxtlev[procsym] = true;
				testdo(nxtlev, fsys, 6);
			}
			else
			{
				error(5);   /* 漏掉了分号 */
			}
		}
		memcpy(nxtlev, statbegsys, sizeof(bool)*symnum);
		nxtlev[ident] = true;
		nxtlev[period]=true;
		testdo(nxtlev, declbegsys, 7);
	} while (inset(sym, declbegsys));   /* 直到没有声明符号 */

	code[table[tx0].adr].a = cx;    /* 开始生成当前过程代码 */
	table[tx0].adr = cx;            /* 当前过程代码地址 */
	table[tx0].size = dx;           /* 声明部分中每增加一条声明都会给dx增加1,声明部分已经结束,dx就是当前过程数据的size */
	cx0 = cx;
	gendo(inte, 0, dx);             /* 生成分配内存代码 */

	if (tableswitch)        /* 输出名字表 */
	{
		printf("TABLE:\n");
		if (tx0+1 > tx)
		{
			printf("    NULL\n");
		}
		for (i=tx0+1; i<=tx; i++)
		{
			switch (table[i].kind)
			{//名字表的输出 
			case constant:
				printf("    %d const %s ", i, table[i].name);
				printf("val=%d\n", table[i].val);
				fprintf(fas, "    %d const %s ", i, table[i].name);
				fprintf(fas, "val=%d\n", table[i].val);
				break;
			case variable:
				printf("    %d var   %s ", i, table[i].name);
				printf("lev=%d addr=%d\n", table[i].level, table[i].adr);
				fprintf(fas, "    %d var   %s ", i, table[i].name);
				fprintf(fas, "lev=%d addr=%d\n", table[i].level, table[i].adr);
				break;
			case procedur:
				printf("    %d proc  %s ", i, table[i].name);
				printf("lev=%d addr=%d size=%d\n", table[i].level, table[i].adr, table[i].size);
				fprintf(fas,"    %d proc  %s ", i, table[i].name);
				fprintf(fas,"lev=%d addr=%d size=%d\n", table[i].level, table[i].adr, table[i].size);
				break;
			case array://数组符号表输出 
				printf("%d arr %s ", i, table[i].name);
				printf("lev=%d addr=%d size=%d startid=%d\n", table[i].level, table[i].adr, table[i].size,table[i].startid);
				fprintf(fas, "%d array %s ", i, table[i].name);
				fprintf(fas, "lev=%d addr=%d size=%d startid=%d\n", table[i].level, table[i].adr, table[i].size,table[i].startid);
			    break;
			}
		}
		printf("\n");
	}

	/* 语句后跟符号为分号或end */
	memcpy(nxtlev, fsys, sizeof(bool)*symnum);  /* 每个后跟符号集和都包含上层后跟符号集和,以便补救 */
	nxtlev[semicolon] = true;
	nxtlev[endsym] = true;
	statementdo(nxtlev, &tx, lev);
	gendo(opr, 0, 0);                       /* 每个过程出口都要使用的释放数据段指令 */
	memset(nxtlev, 0, sizeof(bool)*symnum); /*分程序没有补救集合 */
	testdo(fsys, nxtlev, 8);                /* 检测后跟符号正确性 */
	listcode(cx0);                          /* 输出代码 */
	return 0;
}

(2)Statement()函数主要用来进行基本语句的判断。判断文法为:
<语句>::=<赋值语句>|<条件语句>|<当型循环语句>|<过程调用语句>|<读语句>|<写语句>|<复合语句>|

/*
* 语句处理
*/
int statement(bool* fsys, int* ptx, int lev)//修改最多的函数 
{
	int i, cx1, cx2,cx3,cx4,cx5;
	bool nxtlev[symnum];

	if (sym == ident)   /* 准备按照赋值语句处理 */
	{
		i = position(id, *ptx);
		if (i == 0)
		{
			error(11);  /* 变量未找到 */
		}
		else
		{
			if(table[i].kind != variable&&(table[i].kind != array))
			{
				error(12);  /* 赋值语句格式错误 */
				i = 0;
			}
			else
			{
				enum fct fct1 = sto;
				getsymdo;
				if(sym == becomes)
				{
					getsymdo;
					memcpy(nxtlev, fsys, sizeof(bool)*symnum);
				    expressiondo(nxtlev, ptx, lev); /* 处理赋值符号右侧表达式 */
				    if(i != 0)
				    {
				    	/* expression将执行一系列指令,但最终结果将会保存在栈顶,执行sto命令完成赋值 */
				    	gendo(sto, lev-table[i].level, table[i].adr);
			    	}
				}
				else if(sym==lparen)//数组 
				{
					getsymdo;
					memcpy(nxtlev, fsys, sizeof(bool)*symnum);
				    expressiondo(nxtlev, ptx, lev); /* 处理赋值符号右侧表达式 */
				    getsymdo;//一个;=
					getsymdo;//后面和var赋值相同,除了最后生成的语句 
					memcpy(nxtlev, fsys, sizeof(bool)*symnum);
				    expressiondo(nxtlev, ptx, lev); /* 处理赋值符号右侧表达式 */
				    /* expression将执行一系列指令,但最终结果将会保存在栈顶,执行sto命令完成赋值 */
				    gendo(sto, lev-table[i].level, table[i].adr);
				}
				else if(sym==plusplus) // 后++运算 
                {
                    getsymdo;
                    gendo(lod,lev-table[i].level,table[i].adr);//找到变量地址,将其值入栈
                    gendo(lit,0,1);//将常数1取到栈顶
                    if(i!=0)
                    {
                        gendo(opr,0,2);     //执行加操作
                        gendo(sto,lev-table[i].level,table[i].adr);
                    }
                }
                else if(sym==minusminus) // 后--运算 
                {
                    getsymdo;
                    gendo(lod,lev-table[i].level,table[i].adr);//找到变量地址,将其值入栈
                    gendo(lit,0,1);//将常数1取到栈顶
                    if(i!=0)
                    {
                        gendo(opr,0,3);     //执行减操作
                        gendo(sto,lev-table[i].level,table[i].adr);
                    }
                }
                else if(sym==plusbk)		//增加运算符+=
						{
							if(i!=0)
							{
								gendo(lod,lev-table[i].level,table[i].adr);
							}
							getsymdo;
							memcpy(nxtlev,fsys,sizeof(bool)* symnum);
							nxtlev[number]=true;
							nxtlev[ident]=true;
							expressiondo(nxtlev,ptx,lev);
							gendo(opr,0,2);
							if(i!=0)
							{
								gendo(fct1,lev-table[i].level,table[i].adr);
							}
						}
						else if(sym==minusbk)		//增加运算符-=
						{
							if(i!=0)
							{
								gendo(lod,lev-table[i].level,table[i].adr);
							}
							getsymdo;
							expressiondo(nxtlev,ptx,lev);
							gendo(opr,0,3);
							if(i!=0)
							{
								gendo(fct1,lev-table[i].level,table[i].adr);
							}
						}
				else
				{
					error(13);  /* 没有检测到赋值符号 */
				}				
			}
		}//if (i == 0)
	}
	else if (sym == forsym)      //检测到for语句
	{
		getsymdo;
		if(sym != lparen)  error(34);//没有左括号出错
		else 
		{
			getsymdo;
			statementdo(nxtlev, ptx, lev);  //S1代码
			if(sym != semicolon)  error(10); //语句缺少分号出错
			else
			{
			    cx1=cx;
				getsymdo;
				conditiondo(nxtlev, ptx, lev);   //E代码
				if(sym != semicolon)  error(10);  //语句缺少分号出错
				else 
				{	cx2=cx;
					gendo(jpc,0,0);
					cx3=cx;
					gendo(jmp,0,0);
					getsymdo;
					cx4=cx;
					statementdo(nxtlev, ptx, lev);	//S2代码
					if(sym != rparen)  error(22);  //缺少右括号出错
					else 
					{
						gendo(jmp,0,cx1);
						getsymdo;
						cx5=cx;
						statementdo(nxtlev, ptx, lev);  //S3代码
						code[cx3].a=cx5;
						gendo(jmp,0,cx4);
						code[cx2].a=cx;
					}
				}
			}
		}							
	}
	else
	{
		if (sym == readsym) /* 准备按照read语句处理 */
		{
			getsymdo;
			if (sym != lparen)
			{
				error(34);  /* 格式错误,应是左括号 */
			}
			else
			{
				do {
					getsymdo;
					if(sym!=percent)
					{
						error(33);//缺少% 
					}
					int flag_d=0;
					getsymdo;
					if(sym==diosym)
					{
						flag_d=1;
					} 
					getsymdo;
					if(sym!=comma)
					{
						error(33);//缺少, 
					}
					getsymdo;					
					if (sym == ident)
					{
						i = position(id, *ptx); /* 查找要读的变量 */
					}
					else
					{
						i=0;
					}

					if (i == 0)
					{
						error(35);  /* read()中应是声明过的变量名 */
					}
					else
					{
						if (table[i].kind == constant || table[i].kind == procedur)
					    {
						    error(32);	/* read()参数表的标识符不是变量, thanks to amd */
					    }
					    else
				        {
					        if (table[i].kind == constant || table[i].kind == procedur) {
						        error(32);		// read()中的标识符不是变量
					        }
					        else {
						        getsymdo;
					        	if (sym != lparen) {			//非数组,即变量
						        	gendo(opr, 0, 16);
						        	gen(sto, lev - table[i].level, table[i].adr);
					        	}
						        else {		//处理数组
							        getsymdo;
						        	memcpy(nxtlev, fsys, sizeof(bool) * symnum);
						        	nxtlev[rparen] = true;
						        	expressiondo(nxtlev, ptx, lev);
						        	int ltmp = lev - table[i].level;
						        	int adrtmp = table[i].adr;
						        	gendo(arrcheck, table[i].startid, table[i].size);
						        	gendo(jpc, 0, 0);
						        	gendo(opr, 0, 16);
						        	gendo(sta, ltmp, adrtmp);
						        	getsymdo;
						        }
					        }
				       }						
				   } 					
//					getsymdo;又是一个坑,上面已经读取过了 
				} while (sym == comma); /* 一条read语句可读多个变量 */
			}
			if(sym != rparen)
			{
				error(33);  /* 格式错误,应是右括号 */
				while (!inset(sym, fsys))   /* 出错补救,直到收到上层函数的后跟符号 */
				{
					getsymdo;
				}
			}
			else
			{
				getsymdo;
			}
		}
		else
		{
			if (sym == writesym)    /* 准备按照write语句处理,与read类似 */
			{
				getsymdo;
				if (sym == lparen)
				{
					do {
						getsymdo;
						if(sym!=percent)
						{
							error(33);//缺少% 
						}
						int flag_d=0; 
						getsymdo;
						if(sym==diosym)
						{
							flag_d=1;
						}
						getsymdo;
						if(sym!=comma)
						{
							error(33);//缺少, 
						}
						getsymdo;
						if(sym==ident)
						{
							i=position(id,*ptx);
							if(i==0)
							{
								error(11);//未找到 
							}							
						}
						
						memcpy(nxtlev, fsys, sizeof(bool)*symnum);
						nxtlev[rparen] = true;
						nxtlev[comma] = true;       /* write的后跟符号为) or , */
						expressiondo(nxtlev, ptx, lev); /* 调用表达式处理,此处与read不同,read为给变量赋值 */
						gendo(opr, 0, 14);  /* 生成输出指令,输出栈顶的值 */
					} while (sym == comma);
					if (sym != rparen)
					{
						error(33);  /* write()中应为完整表达式 */
					}
					else
					{
						getsymdo;
					}
				}
				gendo(opr, 0, 15);  /* 输出换行 */
			}
			else
			{
				if (sym == callsym) /* 准备按照call语句处理 */
				{
					getsymdo;
					if (sym != ident)
					{
						error(14);  /* call后应为标识符 */
					}
					else
					{
						i = position(id, *ptx);
						if (i == 0)
						{
							error(11);  /* 过程未找到 */
						}
						else
						{
							if (table[i].kind == procedur)
							{
								gendo(cal, lev-table[i].level, table[i].adr);   /* 生成call指令 */
							}
							else
							{
								error(15);  /* call后标识符应为过程 */
							}
						}
						getsymdo;
					}
				}
				else
				{
					if (sym == ifsym)   /* 准备按照if语句处理 */
					{
						getsymdo;
						memcpy(nxtlev, fsys, sizeof(bool) * symnum);
						nxtlev[thensym] = true;
						nxtlev[dosym] = true;
						conditiondo(nxtlev, ptx, lev);
						if (sym == thensym)
						{
							getsymdo;
						}
						else
						{
							error(16);
						}
						cx1 = cx;
						gendo(jpc, 0, 0);
						statementdo(fsys, ptx, lev);
						if(sym == elsesym)//else语句增加 
						{
							getsymdo;
							cx2 = cx;
							gendo(jmp, 0, 0);
							code[cx1].a = cx;
							statementdo(fsys, ptx, lev);
							code[cx2].a = cx;
						}
						else
						 code[cx1].a = cx;
						
					}
					else
					{
						if (sym == beginsym)    /* 准备按照复合语句处理 */
						{
							getsymdo;
							memcpy(nxtlev, fsys, sizeof(bool)*symnum);
							nxtlev[semicolon] = true;
							nxtlev[endsym] = true;  /* 后跟符号为分号或end */
							/* 循环调用语句处理函数,直到下一个符号不是语句开始符号或收到end */
							statementdo(nxtlev, ptx, lev);

							while (inset(sym, statbegsys) || sym==semicolon)
							{
								if (sym == semicolon)
								{
									getsymdo;
								}
								else
								{
									error(10);  /* 缺少分号 */
								}
								statementdo(nxtlev, ptx, lev);
							}
							if(sym == endsym)
							{
								getsymdo;
							}
							else
							{
								error(17);  /* 缺少end或分号 */
							}
						}
						else
						{
							if (sym == whilesym)    /* 准备按照while语句处理 */
							{
								cx1 = cx;   /* 保存判断条件操作的位置 */
								getsymdo;
								memcpy(nxtlev, fsys, sizeof(bool)*symnum);
								nxtlev[dosym] = true;   /* 后跟符号为do */
								conditiondo(nxtlev, ptx, lev);  /* 调用条件处理 */
								cx2 = cx;   /* 保存循环体的结束的下一个位置 */
								gendo(jpc, 0, 0);   /* 生成条件跳转,但跳出循环的地址未知 */
								if (sym == dosym)
								{
									getsymdo;
								}
								else
								{
									error(18);  /* 缺少do */
								}
								statementdo(fsys, ptx, lev);    /* 循环体 */
								gendo(jmp, 0, cx1); /* 回头重新判断条件 */
								code[cx2].a = cx;   /* 反填跳出循环的地址,与if类似 */
							}
							else
							{
								memset(nxtlev, 0, sizeof(bool)*symnum); /* 语句结束无补救集合 */
								testdo(fsys, nxtlev, 19);   /* 检测语句结束的正确性 */
							}
						}
					}
				}
			}
		}
	}
	return 0;
}

(3)Expression()函数用来进行表达式的处理,其中嵌套项处理,因子处理。文法如下:
<表达式>::=[+|-]<项>{<加法运算符><项>}
<项>::=<因子>{<乘法运算符><因子>}
<因子>::=<标识符>|<无符号整数>|‘(’<表达式>‘)’

/*
* 表达式处理
*/
int expression(bool* fsys, int* ptx, int lev)
{
	enum symbol addop;  /* 用于保存正负号 */
	bool nxtlev[symnum];

	if(sym==plus || sym==minus) /* 开头的正负号,此时当前表达式被看作一个正的或负的项 */
	{
		addop = sym;    /* 保存开头的正负号 */
		getsymdo;
		memcpy(nxtlev, fsys, sizeof(bool)*symnum);
		nxtlev[plus] = true;
		nxtlev[minus] = true;
		termdo(nxtlev, ptx, lev);   /* 处理项 */
		if (addop == minus)
		{
			gendo(opr,0,1); /* 如果开头为负号生成取负指令 */
		}
	}
	else    /* 此时表达式被看作项的加减 */
	{
		memcpy(nxtlev, fsys, sizeof(bool)*symnum);
		nxtlev[plus] = true;
		nxtlev[minus] = true;
		termdo(nxtlev, ptx, lev);   /* 处理项 */
	}
	while (sym==plus || sym==minus)
	{
		addop = sym;
		getsymdo;
		memcpy(nxtlev, fsys, sizeof(bool)*symnum);
		nxtlev[plus] = true;
		nxtlev[minus] = true;
		termdo(nxtlev, ptx, lev);   /* 处理项 */
		if (addop == plus)
		{
			gendo(opr, 0, 2);   /* 生成加法指令 */
		}
		else
		{
			gendo(opr, 0, 3);   /* 生成减法指令 */
		}
	}
	return 0;
}
/*
* 项处理
*/
int term(bool* fsys, int* ptx, int lev)
{
	enum symbol mulop;  /* 用于保存乘除法符号 */
	bool nxtlev[symnum];

	memcpy(nxtlev, fsys, sizeof(bool)*symnum);
	nxtlev[times] = true;
	nxtlev[slash] = true;
	factordo(nxtlev, ptx, lev); /* 处理因子 */
	while(sym==times || sym==slash)
	{
		mulop = sym;
		getsymdo;
		factordo(nxtlev, ptx, lev);
		if(mulop == times)
		{
			gendo(opr, 0, 4);   /* 生成乘法指令 */
		}
		else
		{
			gendo(opr, 0, 5);   /* 生成除法指令 */
		}
	}
	return 0;
}
/*
* 因子处理
*/
int factor(bool* fsys, int* ptx, int lev)
{
	int i;
	bool nxtlev[symnum];
	testdo(facbegsys, fsys, 24);    /* 检测因子的开始符号 */
	/* while(inset(sym, facbegsys)) */  /* 循环直到不是因子开始符号 */
	if(inset(sym,facbegsys))    /* BUG: 原来的方法var1(var2+var3)会被错误识别为因子 */
	{
		if(sym == ident)    /* 因子为常量或变量 */
		{
			i = position(id, *ptx); /* 查找名字 */
			if (i == 0)
			{
				error(11);  /* 标识符未声明 */
			}
			else
			{
				switch (table[i].kind)
				{
				case constant:  /* 名字为常量 */
					gendo(lit, 0, table[i].val);    /* 直接把常量的值入栈 */
					break;
				case variable:  /* 名字为变量 */
					gendo(lod, lev-table[i].level, table[i].adr);   /* 找到变量地址并将其值入栈 */
					break;
				case procedur:  /* 名字为过程 */
					error(21);  /* 不能为过程 */
					break;
				case array:
					getsymdo;
					if (sym == lparen)
					{					//是数组
						int ltmp = lev - table[i].level;
						int adrtmp = table[i].adr;
						getsymdo;
						memcpy(nxtlev, fsys, sizeof(bool) * symnum);
						nxtlev[rparen] = true;
						expressiondo(nxtlev, ptx, lev);
						gendo(lda, ltmp, adrtmp);
						//getsymdo;							
					}
					if (sym == rparen)
					{
						//getsymdo;
					}
					break;
				}
			}
			getsymdo;
		}
		else
		{
			if(sym == number)   /* 因子为数 */
			{
				if (num > amax)
				{
					error(31);
					num = 0;
				}
				gendo(lit, 0, num);
				getsymdo;
			}
			else
			{
				if (sym == lparen)  /* 因子为表达式 */
				{
					getsymdo;
					memcpy(nxtlev, fsys, sizeof(bool)*symnum);
					nxtlev[rparen] = true;
					expressiondo(nxtlev, ptx, lev);
					if (sym == rparen)
					{
						getsymdo;
					}
					else
					{
						error(22);  /* 缺少右括号 */
					}
				}
				testdo(fsys, facbegsys, 23);    /* 因子后有非法符号 */
			}
		}
	}
	return 0;
}

二、功能扩展

本综合实验根据已经掌握的编译原理和技术,在读懂读通 “PL/0 编译程序”的基础上,根据要求修改编译源程序,在符合 PL/0 语言基本词法、语法规则的前提下,对 PL/0 语言的功能进行扩充。

同时,完成了基于 EditPlus 的 PL0 语言语言集成开发环境配置,作为 PL0 语言的 IDE 使用,实现了代码折叠、代码缩进、自动补全、语法高亮、显示行号等功能。

其中包含了以下的功能设计:

  1. I/O 功能扩展:增加了格式化输入、格式化输出。
  2. 数据结构的扩展:增加了一维数组,包括申明、赋值、读写等基本功能。
  3. 计算功能的扩展:增加了后置++、后置–、+=、-=运算符号。
  4. 控制逻辑上的扩展:增加 for 循环、增加 if-then-else 结构。
  5. 语句注释和代码块的注释。
  6. 完成基于 EditPlus 的 PL/0 预演集成开发环境配置,实现代码折叠、代码缩进、自动补全、语法高亮、显示行号等功能。

1. 功能扩充后的EBNF表示

这里只展示扩充部分:

<一维数组说明部分>::=VAR<标识符>‘(’<上界>:<下界>‘)’
<上界>::=<常量>|<变量>|<表达式>
<下界>::=<常量>|<变量>|<表达式>
<行注释>::=//<语句>
<块注释>::=/*<语句>{<语句>}*/
<复合赋值运算符>::=-=|+=
<后置自增运算符>::=++
<后置自减运算符>::=--
<for 型循环语句>::=for‘(’<赋值语句>;<条件>;<赋值语句>‘)’
<读语句>::=READ‘(%d{%d}<标识符>{,<标识符>}‘)’
<写语句>::=WRITE‘(%d{%d}<表达式>{,<表达式>}‘)’

2. 功能扩充后的语法表述图

语法分析过程依赖关系:
语法分析过程依赖关系
分程序语法描述图分别描述了常量申明,变量申明,过程申明和一维数组申明。如图可知,不能将常量定义写在变量定义前面:
在这里插入图片描述)
语句语法描述图中分别对赋值语句,call 语句,begin 语句,if 语句,else 语句,while 语句,read 语句,write 语句,for 循环语句等进行了描述。(关于 for 语句的描述有点不太确定,循环时的箭头应该从括号两边,还是括号内部开始?这里采用括号内部的方案)
在这里插入图片描述

3. 新增部分功能设计

符号表更新:

/* 符号 */
enum symbol {
    nul,         ident,     number,     plus,      minus,//0-4
    times,       slash,     oddsym,     eql,       neq,  //5-9
    lss,         leq,       gtr,        geq,       lparen,//10-14
    rparen,      comma,     semicolon,  period,    becomes,//15-19
    beginsym,    endsym,    ifsym,      thensym,   whilesym,//20-24
    writesym,    readsym,   dosym,      callsym,   constsym,//25-29
    varsym,      procsym,   percent,    colon,     diosym,  //30-34
    elsesym,     forsym,    plusplus,   minusminus,plusbk, //35-39
    minusbk,                                               //40 
};

Percent(%):用来进行格式化输出输出。
Colon(:):用来实现一维数组的申明,位于上界和下界之间作为分隔符。 Diosym(d):用来进行格式化输出输出。
Elsesym(else):增加 if-then-else 控制逻辑。Forsym(for):增加 for 循环。
Plusplus(++):增加复合运算符后置++。Minusminus(–):增加复合运算符后置–。Plusbk(+=);增加复合运算+=。
Minusbk(-=):增加复合赋值运算-=。
Slash(/):原有除法运算符,用来实现单行注释和块注释。Times(*):原有的乘法运算符,用来实现多行的块注释。

保留字更新:

/* 设置保留字名字,按照字母顺序,便于折半查找 */
	strcpy(&(word[0][0]), "begin");
	strcpy(&(word[1][0]), "call");
	strcpy(&(word[2][0]), "const");
	strcpy(&(word[3][0]), "d");//add
	strcpy(&(word[4][0]), "do");//add
	strcpy(&(word[5][0]), "else");//add
	strcpy(&(word[6][0]), "end");
	strcpy(&(word[7][0]), "for");//add
	strcpy(&(word[8][0]), "if");
	strcpy(&(word[9][0]), "odd");
	strcpy(&(word[10][0]), "procedure");
	strcpy(&(word[11][0]), "read");
	strcpy(&(word[12][0]), "then");
	strcpy(&(word[13][0]), "var");
	strcpy(&(word[14][0]), "while");
	strcpy(&(word[15][0]), "write");

	/* 设置保留字符号 */
	wsym[0] = beginsym;
	wsym[1] = callsym;
	wsym[2] = constsym;
	wsym[3] = diosym;//add
	wsym[4] = dosym;//add	
	wsym[5] = elsesym;//add
	wsym[6] = endsym;
	wsym[7] = forsym;//add
	wsym[8] = ifsym;
	wsym[9] = oddsym;
	wsym[10] = procsym;
	wsym[11] = readsym;
	wsym[12] = thensym;
	wsym[13] = varsym;
	wsym[14] = whilesym;
	wsym[15] = writesym;

3.1 增加 I/O 格式化输入输出

设计思路

采用“%d”对输入和输出进行格式化,在 statement() 函数中,当判断该语句为读(写) 语句时,读入一个字符,判断 sym 是否为 lparen,若非,则报错。否则,读入一个字符,判断 sym 是否为percent,若非,报错。否则,读入一个字符,判断 sym 是否为 diosym,再读入一个字符,判断 sym 是否 为 comma,若非,报错。否则,读入一个字符,判断 sym 是否为 ident,后面的处理方式与原程序相同。

其语法如下:

<读语句>::=READ‘(’%d{%d}<标识符>{,<标识符>}‘)’
<写语句>::=WRITE‘(’%d{%d}<表达式>{,<表达式>}‘)’

3.2 增加一维数组

设计思路:

考虑读入数组,假如我们有定义a(0:3),将来要 read(a(1))。我们在名字表中能查找到的只有 a,也就是 a(0)的地址,那我们怎么知道 a(1)在哪里存储呢?其实就是基地址+偏移量。基地址就是数组首地址, 即 a 的地址,偏移量就是 1,a(1)的地址就是 a.addres+1。考虑到可能有 read(a(0+1))这样的情况,我们使用原有的表达式处理函数。

expressiondo 这个函数会把表达式的值放到栈顶,这并不符合我们的要求, 我们希望他给我们返回一个值,然后使用 sto 指令,把地址传进去。那我们怎么解决这个问题?答案就是我们也把处理延后。 既然偏移量在栈顶,我们把基地址也放到栈顶,然后相加,那么栈顶的就是真实地址, 然后我们就会发现 sto 指令并不能满足我们,因为它使用的是名字表的地址,而我们的地址在栈顶,所以我们自己再写一个指令 sto2 来完成我们需要的功能。代码如下:

case sto:
    /*栈顶的值存到相对当前过程的数据基地址为 a 的内存 */
    t--;
    s[base(i.l, s, b) + i.a] = s[t]; break;
case sto2:
    t--;
    s[base(i.l, s, b) + s[t - 1]] = s[t]; break;

很简单,基本没有变化,只不过 sto 的地址是传入的,而 sto2 的地址是从栈顶拿到的,这样我们就解决了找到数组元素并赋值的过程。

其语法如下:

<一维数组说明部分>::=VAR<标识符>‘(’<上界>:<下界>‘)’
<上界>::=<常量>|<变量>|<表达式>
<下界>::=<常量>|<变量>|<表达式>

3.3 增加for循环

设计思路:

首先获取 for,然后获取 lparen,读入一个符号,调用语句处理过程算得赋值号右边的值并通过相应的指令将此值放到数据栈顶。接着获取分号,然后用 condition 过程处理条件语句,把当前 condition 生成的判断条件代码地址 cx 保存到 cx1 中。接着再读取一个分号,调用表达式处理过程算得循环变量的值并判断是否退出循环,把本次循环结束的下一个位置保存到 cx2 生成跳转,并在循环结束时用 cx2 更新为目前循环结束跳转地址。

其语法如下:

<for 型循环语句>::=for‘(’<赋值语句>;<条件>;<赋值语句>‘)’

3.4 增加行注释和块注释

设计思路:

判断当前读取字符为‘/’时,继续读取,若为*,则一直读取,不进行处理,直到遇见*,结束循环,再读取一个字符(‘/’),判断为块注释。否则若为‘/’,则判断为行注释。两者都不满足,判断该符号为 slash。

其语法如下:

<行注释>::=//<语句>
<块注释>::=/<语句>{<语句>}/

3.5 增加运算符

设计思路:

判断当前符号为‘+’时,继续获取字符,若为‘+’,则 sym=plusplus,若为‘=’,则 sym=plusbk,否则 sym 为 plus。
判断当前符号为‘-’时,继续获取字符,若为‘-’,则 sym=minusminus,若为‘=’,则 sym=minusbk,否则 sym 为 minus。
然后在 statement 函数里根据 sym 值的不同,进行对应的操作,实现运算功能。

其语法如下:

<复合赋值运算符>::=-=|+=
<后置自增运算符>::=++
<后置自减运算符>::=–

3.6 增加if-then-else

设计思路:

首先对读到的词调用 getsym 进行词法分析,把当前的代码地址 cx 保存在 cx2 中,调用 gen(jmp,0,0) 生成条件跳转,地址未知。将当前代码的地址加 1 赋给 cx,同时把 cx 的地址保存在 cx2 中。

其语法如下:(其中 else 部分可有可无)

<条件语句>::=IF<条件>THEN<语句>[ELSE<语句>]

三、成果展示

1. IED配置

以下测试均在已经配置好的EditPlus 上测试,同时也可以看到代码高亮显示,至于自动补全应用于写代码的过程中,已通过测试。

2. 斐波那契数列

同时也可用来做格式化输入和输出测试。
斐波那契数列

3. 鸡兔同笼问题

输入头和脚数,输出兔子和鸡的个数。可用来测试格式化输入和输出
在这里插入图片描述

4. 求取最大公因数

输入 2 个数,求最大公因数。可同时用来测试格式化输入和输出,以及代码注释
在这里插入图片描述

5. 一维数组赋值、输入和输出

通过下标访问数组的某个元素并进行赋值,读入某个数组元素的值,并原貌输出。(键盘输入的是 34)
在这里插入图片描述

6. for循环、++、–运算

递增输出 1,2,3,4,5;
递减输出 100,99,98,97,96;
在这里插入图片描述

7. if-then-else结构实现

在这里插入图片描述

四、实验思考和总结

保留字名字word[][]和保留字符号 wsym[]下标设置要对应同一个符号,不然会出现各种位置错误! 如图为正确设置方式:(按照数序排列,便于进行折半查找,加快查找速率)
在这里插入图片描述

对于中文注释的显示问题,EditPlus 保存文件编码为 ANSI。保存为 UTF-8 会显示乱码。

在这里插入图片描述

五、整体代码实现

基于C++实现PL0的编译
1700+行代码,包括运行代码所需要的PL0文件,编写不易,多多支持!!!

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-06-29 18:48:02  更:2022-06-29 18:48:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/24 0:19:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码