编译原理上机实习

你离开我真会死。 提交于 2019-12-28 19:47:37

一、实习目的

理解编译程序的构造原理,掌握编译程序的构造方法与技术。通过实习,使学生既加深对编译原理基础理论的理解,又提高动手能力,特别是提高软件设计能力。

二、实习要求

在理解编译原理基本思想的基础上,选择一个自己熟悉的程序设计语言,完成编译程序的设计和实现过程。
编译程序的设计可以采用自顶向下和自底向上两种不同的方法。由于许多高级语言(如PASCAL,C)中的语法成分都是递归定义的,所以本实习要求学生采用递归下降分析技术,这是一种自顶向下的的编译方法,其基本思想是对语言的每个(或若干个)语法成分编制一个处理子程序,从处理<程序>这个语法成分的子程序开始,在分析过程中调用一系列过程或函数,对源程序进行语法和语义分析,直到整个源程序处理完毕为止。
本上机实习是为C语言(子集)设计一个编译程序,完成词法分析、语法分析、语义分析等功能,并生成某种机器上的目标代码(汇编语言)或中间代码(四元式)。

三、实习步骤

1.阅读《上机实习指导书》。
2.根据设计要求写算法,画程序框图
3.根据框图编写编译程序
4.输入编译程序并上机调试
5.撰写上机实习报告

四、实习内容

1、题目:C语言小子集编译程序的实现
2、C语言小子集的文法规则:

1.<程序>::=main(){<分程序>}
2.<分程序>::=<变量说明部分>;<语句部分>
3.<变量说明部分>::=<变量说明><标识符表>
4.<变量说明>::=int
5.<标识符表>::=<标识符表>,<标识符>
6.<标识符表>::=<标识符>
7.<标识符>::=<字母>
8.<标识符>::=<标识符><字母>
9.<标识符>::=<标识符><数字>
10.<语句部分>::=<语句部分>;<语句>|<语句>
11.<语句>::=<赋值语句>|<条件语句>|<循环语句>|
12.<赋值语句>::=<标识符>=<表达式>
13.<条件>::=<表达式><关系运算符><表达式>
14.<表达式>::=<项>|<表达式><加法运算符><项>
15.<项>::=<因子>|<项><乘法运算符><因子>
16.<因子>::=<标识符>|<常量>|(<表达式>)
17.<常量>::=<无符号整数>
18.<无符号整数>::=<数字序列>
19.<数字序列>::=<数字序列><数字>
20.<数字序列>::=<数字>
21.<加法运算符>::=+|-
22.<乘法运算符>::=*|/
23.<关系运算符>::=<|>|!=|>=|<=|==
24.<复合语句>::={<语句部分>}
25.<语句1>::=<语句>|<复合语句>
26.<条件语句>::=if(<条件>)<语句1>else<语句1>
27.<循环语句>::=while(<条件>)do<语句1>
28.<字母>::=a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z
29.<数字>::=0|1|2|3|4|5|6|7|8|9

3、实现功能:
(1)词法分析
扫描源程序,根据词法规则,识别单词,填写相应的表。如果产生词法错误,则显示错误信息、位置,并试图从错误中恢复。简单的恢复方法是忽略该字符(或单词)继续扫描。
(2)语法分析
对源程序作语法分析,确定是否属于C语言小子集,同时揭示出程序的内在结构。
(3)语法错误检查
根据C语言小子集的文法规则设置检测手段,通过查错子程序或一些查错语句,报告源程序出错位置、性质等,直至整个程序结束为止。
(4)语义分析与目标代码生成
在语法分析的基础上,进行语义分析,生成输入源程序的目标代码。输入源程序的目标代码可以建立在一个假想的处理机(虚拟机)上,或者以所学的汇编语言为基础,也可以生成四元式序列。

输入源程序样本:(这只是一个例子,调试时可以任意修改或换其它程序)
main ()
{ int a,b,y,max;
a=10; b= ;
while (a>0)
{ b=a+b*a;
a=a-1
};
x=a+b; y=b+b;
if (x>y) max=x
else max=y
}

消除左递归后的文法:
1.<程序>::=main(){<分程序>}
2.<分程序>::=<变量说明部分>;<语句部分>
3.<变量说明部分>::=<变量说明><标识符表>
4.<变量说明>::=int
5.<标识符表>::=<标识符><标识符表1>
6. <标识符表1>::=,<标识符><标识符表1>|空串
7.<标识符>::=<字母><标识符1>
8.<标识符>::=<数字><标识符1>
9.<标识符1>::=<字母><标识符1>|空串
10.<标识符1>::=<数字><标识符1>|空串
11.<语句部分>::=<语句><语句部分1>
12.<语句部分1>::=;<语句><语句部分1>|空串
13.<语句>::=<赋值语句>|<条件语句>|<循环语句>
14.<赋值语句>::=<标识符>=<表达式>
15.<条件>::=<表达式><关系运算符><表达式>
16.<表达式>::=<项><表达式1>
17.<表达式1>::=<加法运算符><项><表达式1> | 空串
18.<项>::=<因子><项1>
19.<项1>::=<乘法运算符><因子><项1>|空串
20.<因子>::=<标识符>|<常量>|(<表达式>)
21.<常量>::=<无符号整数>
22.<无符号整数>::=<数字序列>
23.<数字序列>::=<数字><数字序列1>
24.<数字序列1>::=<数字><数字序列1>
25.<加法运算符>::=+|-
26.<乘法运算符>::=*|/
27. <关系运算符>::=<|>|!=|>=|<=|==
28. <复合语句>::={<语句部分>}
29.<语句1>::=<语句>|<复合语句>
30. <条件语句>::=if(<条件>)<语句1>else<语句1>
31. <循环语句>::=while(<条件>)do<语句1>
32. <字母>::=a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z
33. <数字>::=0|1|2|3|4|5|6|7|8|9

开始

1.词法分析

种别码对应表

单词/符号 种别码 单词/符号 种别码
main 1 != 16
int 2 >= 17
if 3 <= 18
else 4 == 19
while 5 * 20
do 6 / 21
, 7 + 22
; 8 - 23
( 9 整数 24
) 10 标识符 25
{ 11
} 12
= 13
< 14
> 15

我的思路是先判断是否要进行取词操作(flag_cw),紧接着判断是否有空格、换行符(有则行数line自增)、制表符,然后利用getc()取词。(这其中涉及到文件操作,具体的请朋友们自行度娘,资源应该很多)。紧接着就是对读入的字符利用typeOf()分类,并将字符分类成各个单词/符号。

代码

void getWord()                    //词法分析
{
	int i = 0;
	int j = 0;
	int type;
	if(flag_cw == 0)标识符 
	{
		letter = getc(cfin);
	}

	while(letter == " " || letter == "\n" || letter == "\t")
	{
		if(letter == "\n")
			line++;

		letter = getc(cfin);
		flag_cw = 1;
	}

	type = typeOf(letter);
	switch(type)
	{
	case 1:     //识别数字
		{
			for(i = 0;i < 20;i++)
				word[i] = NULL;

			i = 0;
			do
			{
				strcpy(&word[i],letter.c_str());   //将从文件中获取的单个字符存入word数组中
				i++;
				letter = getc(cfin);
				flag_cw = 1;
			}while(typeOf(letter) == 1);   //数字类型为1

			strcpy(word1.content,word);    //将word数组中的单词存入单词表,下面类似代码以此类推
			word1.type = 24;               //整数类型为24
			word1.numOfLine = line;        //单词所在行数
			break;
		}

	case 2:                                //识别以字母开头的标识符
		{
			for(i = 0;i < 20;i++)
				word[i] = NULL;

			i = 0;
			do
			{
				strcpy(&word[i],letter.c_str());
				i++;
				letter = getc(cfin);
				flag_cw = 1;
			}while(typeOf(letter) == 1 || typeOf(letter) == 2);   //除开头外有数字或字母的标识符

			strcpy(word1.content,word);
			word1.numOfLine = line;

			if(isKeyWord(word) != 0)       //识别保留字
			{
				word1.type = isKeyWord(word);    //将保留字对应的种别码写进单词表
				break;
			}

			else
			{
				word1.type = 25;    //标识符类型为25
				break;
			}
		}

	case 3:                                //识别符号
		{
			for(i = 0;i < 20;i++)
				word[i] = NULL;

			i = 0;
			strcpy(&word[i],letter.c_str());
			
			if(letter == "=" || letter == "<" || letter == ">" || letter == "!")    
			{
				i++;
				letter = getc(cfin);
				if(letter == "=")          //判断"!=","==",">=","<="
				{
					flag_cw = 1;
					strcpy(&word[i],letter.c_str());
					strcpy(word1.content,word);
					word1.type = isToken(word1.content);
			        word1.numOfLine = line;
					letter = getc(cfin);
				}

				else
				{
			        flag_cw = 1;
			        strcpy(word1.content,word);
			        word1.type = isToken(word);
			        word1.numOfLine = line;
			        //printError("错误的符号");
				}
			}

			else
			{
			    i++;
			    letter = getc(cfin);
			    flag_cw = 1;
			    strcpy(word1.content,word);
		        word1.type = isToken(word);
		        word1.numOfLine = line;
			}
			break;
		}

	default:
		{
			word1.type = 0;              //错误的单词
			strcat(word1.content," ");
			break;
		}
	}
}

2.语法分析

根据消除左递归后的文法,开始编写语法分析部分,由于文法数众多,这里选择两个作为范例说明。
以<程序>::=main(){<分程序>} 和 <分程序>::=<变量说明部分>;<语句部分> 为例

void chengXu()       //<程序>::=main(){<分程序>}
{
	getWord();
	if(strcmp(word1.content,"main"))
	{
		printError("缺少关键字'main'");
	}

	getWord();
	if(strcmp(word1.content,"(") != 0)
	{
		printError("缺少左括号'('");
	}

	getWord();
	if(strcmp(word1.content,")") != 0)
	{
		printError("缺少右括号')'");
	}

	getWord();
	if(strcmp(word1.content,"{") != 0)
	{
		printError("缺少左大括号'{'");
	}

	fenChengXu();
	
	if(strcmp(word1.content,"}") != 0)
	{
		printError("缺少右大括号'}'");
	}
	

	op = "go";
	str1 = "";
	str2 = "";
	result1 = "";
	getQuadruple(op,str1,str2,result1);

	cout << "语法分析完成,发现" << err << "个错误" << endl;
}

void fenChengXu()   //<分程序>::=<变量说明部分>;<语句部分> 
{
	bianLiangShuoMingBF();//变量说明部分
	
	if(strcmp(word1.content,";") != 0)
	{
		printError("缺少分界符';'");
	}

	getWord();
	yuJuBuFen();
}

这里要解释下getQuadruple()。这个用于生成中间代码(四元式)。具体的定义如下:

struct Quadruple                    //四元式
{
public:
	string optr;                    //运算符 
	string num1;                    //操作数1
	string num2;                    //操作数2
	string result;                  //结果
	int tag;                        //标记
};

void getQuadruple(string optr,string n1,string n2,string r)   //生成四元式
{
	quadruple1[f_num].optr = optr;
	quadruple1[f_num].num1 = n1;
	quadruple1[f_num].num2 = n2;
	quadruple1[f_num].result = r;
	f_num++;
}

3.语义分析与中间代码生成

这部分也是本人依然不太熟悉的部分,借(chao)鉴(xi)了不少大佬的成果,大家将就看看。。。

template<typename A> string toString(const A& t)      //把数字转换成字符串
{
	ostringstream oss;
	oss << t;
	return oss.str();
}

string newtemp()                                      //设置中间变量
{
	char temp[15] = "A";
	temp[1] = p_temp / 10 + '0';
	temp[2] = p_temp++ % 10 + '0';
	return temp;
}

void addJumpLine(int num,int jumpline,int jumpn)     //插入跳转标号 第1个参数用来填go
{
	if(quadruple1[num].optr == "go")
	{
		quadruple1[num].num1 = toString(jumpline);
	}

	else
	{
		for(int i = f_num - 1;i >= 0;i--)
		{
			if(quadruple1[i].tag == jumpn)
			{
				quadruple1[i].result = toString(jumpline);
				quadruple1[i].tag = 0;
				break;
			}
		}
	}
}

4.运行结果

示例代码:
main ()
{
int a,c;
a=10;
b=12;
while(b != -b)
do
{
c =9+8- (a + b) * b / a + b);
};

if(a > b)
{
a = a + b;
}
else
{
b = a + b;
}

}

结果如下:
在这里插入图片描述
可生成四元式版本:
main ()
{
int a,b,c;
a=10;
b=12;
while(b != 0)
do
{
c =9+8- (a + b) * b / a + b;
};

if(a > b)
{
a = a + b;
}
else
{
b = a + b;
}
}
在这里插入图片描述

5.总结

这是小菜鸟第一次写博客,算作自己的学习笔记。此外,这个程序还有诸多不足之处,还请各位大佬多多指教。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!