编译原理程序设计实践(六) 语句和子过程的处理代码

/* 语句处理过程statement */
/* 参数说明:fsys: 如果出错可用来恢复语法分析的符号集合 */
void statement(const symset& fsys,const int& lev, int& tx)
/* 表达式处理过程expression */
/* 参数说明:fsys: 如果出错可用来恢复语法分析的符号集合 */
{ 
	int i, cx1, cx2;	
	/* statement */
	if  (sym == ident)/* 所谓"语句"可能是赋值语句,以标识符开头 */
	{
		i = position(id, tx) ; /* 在符号表中查到该标识符所在位置 */
		if  (i == 0)/* 如果没找到 */
			error(11); /* 抛出11号错误 */
		else
		if  (table[i].kind != variable) /* 如果在符号表中找到该标识符,但该标识符不是变量名 */ 
		{
			error(12); /* 抛出12号错误 */
			i = 0; /* i置0作为错误标志 */
		}
		getsym( ); /* 获得下一个token,正常应为赋值号 */
		if  (sym == becomes)/* 如果的确为赋值号 */
			getsym(); /* 获取下一个token,正常应为一个表达式 */
		else
			error(13); /* 如果赋值语句的左部标识符号后所接不是赋值号,抛出13号错误 */
		expression(fsys,lev,tx); /* 处理表达式 */
		if  (i != 0) /* 如果不曾出错,i将不为0,i所指为当前语名左部标识符在符号表中的位置 */
			gen(sto, lev -table[i].level,table[i].adr); /* 产生一行把表达式值写往指定内存的sto目标代码 */
	}
	else
		if  (sym == readsym)/* 如果不是赋值语句,而是遇到了read语句 */
		{
			getsym( ); /* 获得下一token,正常情况下应为左括号 */
			if  (sym != lparen) /* 如果read语句后跟的不是左括号 */
				error(34); /* 抛出34号错误 */
			else
			do { /* 循环得到read语句括号中的参数表,依次产生相应的“从键盘读入”目标代码 */
				getsym( ); /* 获得一个token,正常应是一个变量名 */
				if  (sym == ident)/* 如果确为一个标识符 */ 
					/* 这里略有问题,还应判断一下这个标识符是不是变量名,如果是常量名或过程名应出错 */
					i = position(id,tx); /* 查符号表,找到它所在位置给i,找不到时i会为0 */
				else
					i = 0 ; /* 不是标识符则有问题,i置0作为出错标志 */
				if  (i == 0)/* 如果有错误 */
					error(35); /* 抛出35号错误 */
				else /* 否则生成相应的目标代码 */
				{
					gen(opr, 0, 16); /* 生成16号操作指令:从键盘读入数字 */
					gen(sto, lev - table[i].level,table[i].adr); /* 生成sto指令,把读入的值存入指定变量所在的空间 */
				}
				getsym(); /* 获取下一个token,如果是逗号,则read语还没完,否则应当是右括号 */
			}while (sym == comma); /* 不断生成代码直到read语句的参数表中的变量遍历完为止,这里遇到不是逗号,应为右括号 */
			if  (sym != rparen) /* 如果不是我们预想中的右括号 */
			{
				error(33); /* 抛出33号错误 */
				while(!(in(sym,fsys) )) /* 依靠fsys集,找到下一个合法的token,恢复语法分析 */
					getsym();
			}
			else
				getsym(); /* 如果read语句正常结束,得到下一个token,一般为分号或end */
		}
		else
			if  (sym == writesym)/* 如果遇到了write语句 */
			{
				getsym( ); /* 获取下一token,应为左括号 */
				if  (sym == lparen)/* 如确为左括号 */
				{
					do { /* 依次获取括号中的每一个值,进行输出 */
						getsym( ); /* 获得一个token,这里应是一个标识符 */
						expression(symset{rparen, comma} + fsys, lev,tx); /* 调用expression过程分析表达式,用于出错恢复的集合中加上右括号和逗号 */
						gen(opr, 0, 14); /* 生成14号指令:向屏幕输出 */
					}while (sym == comma); /* 循环直到遇到的不再是逗号,这时应是右括号 */
					if  (sym != rparen) /* 如果不是右括号 */
						error(33); /* 抛出33号错误 */
					else
						getsym( ); /* 正常情况下要获取下一个token,为后面准备好 */
				}
				gen(opr, 0, 15); /* 生成一个15号操作的目标代码,功能是输出一个换行 */
					/* 由此可知PL/0中的write语句与Pascal中的writeln语句类似,是带有输出换行的 */
			}

			else
				if  (sym == callsym)/* 如果是call语句 */
				{
					getsym( ); /* 获取token,应是过程名型标识符 */
					if  (sym != ident) /* 如果call后跟的不是标识符 */
						error(14); /* 抛出14号错误 */
					else
					{
						i = position(id, tx) ; /* 从符号表中找出相应的标识符 */
						if  (i == 0)/* 如果没找到 */
							error(11); /* 抛出11号错误 */
						else 			 /* 如果找到标识符位于符号表第i位置 */
						if  (table[i].kind == procedure)/* 如果这个标识符为一个过程名 */
							gen(cal,lev-table[i].level,table[i].adr); /*生成cal目标代码,呼叫这个过程 */
						else
							error(15); /* 如果call后跟的不是过程名,抛出15号错误 */
						getsym( ); /* 获取下一token,为后面作准备 */
					}
				}
			
				else
					if  (sym == ifsym)/* 如果是if语句 */
					{
						getsym( ); /* 获取一token应是一个逻辑表达式 */
						condition(symset{thensym, dosym}+fsys, lev ,tx); /* 对逻辑表达式进行分析计算,出错恢复集中加入then和do语句 */
						if (sym ==thensym)/* 表达式后应遇到then语句 */
							getsym( ); /* 获取then后的token,应是一语句 */
						else
							error(16); /* 如果if后没有then,抛出16号错误 */
						cx1 = cx ; /* 记下当前代码分配指针位置 */
						gen(jpc, 0, 0); /* 生成条件跳转指令,跳转位置暂时填0,分析完语句后再填写 */
						statement(fsys,lev,tx); /* 分析then后的语句 */
						code[cx1].a=cx; /* 上一行指令(cx1所指的)的跳转位置应为当前cx所指位置 */
					}
						
					else
						if  (sym == beginsym)/* 如果遇到begin */
						{
							getsym( ); /* 获取下一个token */
							statement(symset{semicolon, endsym}+fsys, lev, tx);/* 对begin与end之间的语句进行分析处理 */
							while(in(sym, symset{semicolon}+statbegsys)) /* 如果分析完一句后遇到分号或语句开始符循环分析下一句语句 */
							{
								if  (sym == semicolon)/* 如果语句是分号(可能是空语句) */
									getsym( ); /* 获取下一token继续分析 */
								else
									error(10); /* 如果语句与语句间没有分号,出10号错 */
								statement(symset{semicolon, endsym}+fsys,lev,tx); /* 分析一个语句 */
							}
							if  (sym == endsym)/* 如果语句全分析完了,应该遇到end */
								getsym( ); /* 的确是end,读下一token */
							else
								error(17); /* 如果不是end,抛出17号错 */
						}
						else
							if  (sym == whilesym)/* 如果遇到while语句 */
							{
								cx1 = cx ; /* 记下当前代码分配位置,这是while循环的开始位置 */
								getsym( ); /* 获取下一token,应为一逻辑表达式 */
								condition(symset{dosym}+fsys, lev, tx); /* 对这个逻辑表达式进行分析计算 */
								cx2 = cx ; /* 记下当前代码分配位置,这是while的do中的语句的开始位置 */
								gen(jpc, 0, 0); /* 生成条件跳转指令,跳转位置暂时填0 */
								if  (sym == dosym)/* 逻辑表达式后应为do语句 */
									getsym( ); /* 获取下一token */
								else
									error(18); /* 条件后缺少do,抛出18号错误 */
								statement(fsys,lev,tx); /* 分析do后的语句块 */
								gen(jmp, 0, cx1); /* 循环跳转到cx1位置,即再次进行逻辑判断 */
								code[cx2].a = cx; /* 把刚才填0的跳转位置改成当前位置,完成while语句的处理 */
							}
							test(fsys, symset( ), 19); /* 至此一个语句处理完成,一定会遇到fsys集中的符号,如果没有遇到,就抛19号错 */
} /* statement */;
/* 语法分析过程block */
/* 参数:lev:这一次语法分析所在的层次 */
/*       tx:符号表指针 */
/*       fsys:用于出错恢复的单词集合 */
void block(int lev, int tx, const symset& fsys)
{
	int dx; /* data allocation index */ /* 数据段内存分配指针,指向下一个被分配空间在数据段中的偏移位置 */
	int tx0;  /* initial table index */ /* 记录本层开始时符号表位置 */
	int cx0;  /* initial code index */ /* 记录本层开始时代码段分配位置 */

	/* block */
	dx = 3 ; /* 地址指示器给出每层局部量当前已分配到的相对位置。
			 置初始值为3的原因是:每一层最开始的位置有三个空间用于存放静态链SL、动态链DL和返回地址RA */
	tx0 = tx ; /* 初始符号表指针指向当前层的符号在符号表中的开始位置 */
	table[tx].adr = cx ; /* 符号表当前位置记下当前层代码的开始位置 */
	gen(jmp, 0, 0); /* 产生一行跳转指令,跳转位置暂时未知填0 */
	if  (lev > levmax) /* 如果当前过程嵌套层数大于最大允许的套层数 */
		error(32); /* 发出32号错误 */
	do { /* 开始循环处理源程序中所有的声明部分 */
		if  (sym == constsym)/* 如果当前token是const保留字,开始进行常量声明 */
		{
			getsym( ); /* 获取下一个token,正常应为用作常量名的标识符 */
			do { /* 反复进行常量声明 */
				constdeclaration(lev, tx, dx); /* 声明以当前token为标识符的常量 */
				while(sym == comma) /* 如果遇到了逗号则反复声明下一个常量 */
				{
					getsym( ); /* 获取下一个token,这里正好应该是标识符 */
					constdeclaration(lev, tx, dx); /* 声明以当前token为标识符的常量 */
				}
				if  (sym == semicolon)/* 如果常量声明结束,应遇到分号 */
					getsym( ); /* 获取下一个token,为下一轮循环做好准备 */
				else
					error(5); /* 如果常量声明语句结束后没有遇到分号则发出5号错误 */
			}while(sym == ident); /* 如果遇到非标识符,则常量声明结束 */
		}
		/* 此处的常量声明的语法与课本上的EBNF范式有不同之处:
		它可以接受像下面的声明方法,而根据课本上的EBNF范式不可得出下面的语法:
		const a = 3, b = 3; c = 6; d = 7, e = 8; 
		即它可以接受分号或逗号隔开的常量声明,而根据EBNF范式只可接受用逗号隔开的声明 */
		if  (sym == varsym)/* 如果当前token是var保留字,开始进行变量声明,与常量声明类似 */
		{
			getsym( ); /* 获取下一个token,此处正常应为用作变量名的一个标识符 */
			do { /* 反复进行变量声明 */
				vardeclaration(lev, tx, dx); /* 以当前token为标识符声明一个变量 */
				while(sym == comma ) /* 如果遇到了逗号则反复声明下一个变量 */
				{
					getsym( ); /* 获取下一个token,这里正好应该是标识符 */
					vardeclaration(lev, tx, dx); /* 声明以当前token为标识符的变量 */
				}
				if  (sym == semicolon)/* 如果变量声明结束,应遇到分号 */
					getsym( ); /* 获取下一个token,为下一轮循环做好准备 */
				else
					error(5); /* 如果变量声明语句结束后没有遇到分号则发出5号错误 */
			}while (sym == ident); /* 如果遇到非标识符,则变量声明结束 */
			/* 这里也存在与上面的常量声明一样的毛病:与PL/0的语法规范有冲突。 */
		}
		while(sym == procsym ) /* 循环声明各子过程 */
		{
			getsym( ); /* 获取下一个token,此处正常应为作为过程名的标识符 */
			if  (sym == ident)/* 如果token确为标识符 */
			{
				enter(procedure, lev ,tx ,dx); /* 把这个过程登录到名字表中 */
				getsym( ); /* 获取下一个token,正常情况应为分号 */
			}
			else
				error(4); /* 否则抛出4号错误 */
			if  (sym == semicolon)/* 如果当前token为分号 */
				getsym( ); /* 获取下一个token,准备进行语法分析的递归调用 */
			else
				error(5); /* 否则抛出5号错误 */
			block(lev + 1, tx, symset{semicolon}+fsys); /* 递归调用语法分析过程,当前层次加一,同时传递表头索引、合法单词符 */
			if  (sym == semicolon)/* 递归返回后当前token应为递归调用时的最后一个end后的分号 */
			{
				getsym( ); /* 获取下一个token */
				test(statbegsys + symset{ident, procsym}, fsys, 6); /* 检查当前token是否合法,不合法则用fsys恢复语法分析同时抛6号错 */
			}
			else
				error(5); /* 如果过程声明后的符号不是分号,抛出5号错误 */
		}
		test(statbegsys + symset{ident}, declbegsys, 7); /* 检查当前状态是否合法,不合法则用声明开始符号作出错恢复、抛7号错 */
	}while (in(sym,declbegsys)); /* 直到声明性的源程序分析完毕,继续向下执行,分析主程序 */
	code[table[tx0].adr].a = cx ; /* 把前面生成的跳转语句的跳转位置改成当前位置 */
	/* 在符号表中记录 */
	table[tx0].adr = cx ;  /* 地址为当前代码分配地址 */
	table[tx0].size = dx ; /* 长度为当前数据段分配位置 */
	cx0 = cx ; /* 记下当前代码分配位置 */
	gen(_int, 0, dx); /* 生成分配空间指令,分配dx个空间 */
	statement(symset{semicolon, endsym}+fsys,lev,tx); /* 处理当前遇到的语句或语句块 */
	gen(opr, 0, 0); /* 生成从子程序返回操作指令 */
	test(fsys, symset( ), 8); /* 用fsys检查当前状态是否合法,不合法则抛8号错 */
	listcode(cx0); /* 列出本层的类PCODE代码 */
} /* block */;

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值