编译原理--实验1 词法分析

文章目录

  • 前言
  • 1.1 实验目的
  • 1.2 实验任务
  • 1.3 实验内容
    • 1.3.1 实验要求
    • 1.3.2 输入格式
    • 1.3.3 输出格式
    • 1.3.4 样例
    • 1.3.5 C--语言文法
  • 1.4 程序代码
    • 1.4.1 程序流程图
    • 1.4.2 程序源码
  • 1.5 总结


前言

编译原理课程实验的实验课内容—构造词法分析程序。通过本次实验,希望对于编译原理中词法分析这个功能有更深和更好的理解。

1.1 实验目的

(1)理解有穷自动机及其应用。

(2)掌握 NFA 到 DFA 的等价变换方法、DFA 最小化的方法。

(3)掌握设计、编码、调试词法分析程序的技术和方法。

1.2 实验任务

编写一个程序对输入的源代码(源代码可以是C++, C, Java等)进行词法分析,并打印分析结果。

1.3 实验内容

1.3.1 实验要求

你的程序要能够查出源代码中可能包含的词法错误: 词法错误(错误类型 A):(以输入 C–源代码为例)即出现 C–词法中未定义的字符 以及任何不符合 C–词法单元定义的字符;

1.3.2 输入格式

你的程序输入是一个包含 C++ 源代码的文本文件,程序需要能够接收一个输入文件名作为参数,以获得相应的输出结果。

1.3.3 输出格式

要求通过标准输出打印程序的运行结果。对于那些包含词法错误的输入文件,只要输出相关的词法有误的信息即可。在这种情况下,注意不要输出任何与语法树有关的内容。

要求输出的信息包括错误类型、出错的行号以及说明文字, 其格式为:

Error type [错误类型] at Line [行号]: [说明文字].

说明文字的内容没有具体要求,但是错误类型和出错的行号一定要正确,因为这是判断 输出的错误提示信息是否正确的唯一标准。请严格遵守实验要求中给定的错误分类(即词法 错误为错误类型 A).注意,输入文件中可能会包含一个或者多个词法错误(但输入文件的同一行中保证不出现多个错误),你的程序需要将这些错误全部报告出来,每一条错误提示信息在输出中单独占一行。 对于那些没有任何词法错误的输入文件,你的程序需要打印每一个词法单元的名称以及 与其对应的词素,无需打印行号。词法单元名与相应词素之间以一个冒号和一个空格隔开。 每一条词法单元的信息单独占一行。

在很多程序设计语言中,下面的类别覆盖了大部分或所有的词法单元:

1)关键字。一个关键字的类型就是该关键字本身。

2)运算符。它可以表示单个运算符,也可以像表 1-1 中的 comparison 那样,表示一类 运算符。

3)标识符。一个表示所有标识符的词法单元。

4)常量。一个或多个表示常量的词法单元,比如数字和字面值字符串。 5)界符。每一个标点符号有一个词法单元,比如左右括号、逗号和分号。

编译原理--实验1 词法分析_第1张图片

1.3.4 样例

样例1:

输入:

int main()
{
    int i=1;
    int j=~i;
}

输出:

Error type A at Line 4: Mysterious character "~".

样例2:

int inc()
{
    int i;
    i=100;
}

输出:

TYPE: int
ID: inc
LP: (
RP: )
LC: {
TYPE: int
ID: i
SEMI: ;
ID: i
RELOP: =
INT: 100
SEMI: ;
RC: }

1.3.5 C–语言文法

以下为C–文法中各个标识符或关键字等所对应的名称

INT→ /* A sequence of digits without spaces */
FLOAT → /* A real number consisting of digits and one decimal point. The decimal point must be surrounded by at least one digit */
ID → /* A character string consisting of 52 upper- or lower-case alphabetic, 10 numeric and one underscore characters. Besides, an identifier must not start with a digit */
SEMI → ;
COMMA → , 
ASSIGNOP→ = 
RELOP → > | < | >= | <= | == | !=
PLUS → +
MINUS → - 
STAR → * 
DIV → /
AND → &&
OR → ||
DOT → . 
NOT → !
TYPE →int | float
LP → (
RP → )
LB → [
RB → ]
LC → {
RC → }
STRUCT → struct
RETURN → return
IF → if
ELSE → else
WHILE → while

1.4 程序代码

1.4.1 程序流程图

编译原理--实验1 词法分析_第2张图片

1.4.2 程序源码

# include
# include
# include
# include//流对象
# include
# include
using namespace std;

bool isIdentifier(string s);//标识符
bool isKeywords(string s);//关键字
bool isDigit(string s);//常数
bool isOperator(string s);//运算符
bool isOperator(char c);//运算符
string result(string s);//根据传入的参数s产生对应的输出
string miss = "";
int line_num = 1;
int flag = 0;  //全局变量,判断输入的源代码是否有词法错误
//每次按行读,那么出错误就可以按行来看
int main()
{
	//=====================测试函数是否正确=============================
	/*if(isIdentifier("_s2ias"))
		cout << "是标识符" << endl;			//这样调试非常规范
	if (isKeywords("for"))
		cout << "是关键字" << endl;
	if (isDigit("12a42"))
		cout << "是数字" << endl;
	if (isOperator(">"))
		cout << "是运算符" << endl;
	result("?");*/
	//===================================================================
	string file = ("自定义测试样例3.txt");
	ifstream input(file);
	//输入文件流,注意编码,文本文件编码格式需和项目一直,否则乱码
	string copy;

	getline(input, copy, '\0');		//将文本的内容输出到屏幕上
	cout << copy << endl;//测试是否正确

	//此时input已经指到了文件尾,为了后面的读取,需要关闭再打开
	input.close();
	input.open(file);

	/*测试结果要求以原数据与结果对照的形式输出并保存在Result.txt中,
	同时要把结果输出到屏幕。
	*/
	cout << "处理后结果:\n";
	string str;
	string words;
	while (getline(input, str)) //读取文件每一次读取一行,遇到EOF结束
	{
		//从输入流中获取单词,需要用到输入流对象,即istringstream
		istringstream strCin(str);
		string s;
		while (strCin >> words)  //这里的words可能是一大长串句子 , 按行处理
		{
			/*注意处理逗号,比如int a,b;这里有一个单词"a,b;”,所以要处理一个字符串里面
			的各种运算符,但是这样会很麻烦,发现没有,用ide写代码写完一句输入分号时,ide
			会自动加入空格,这样就方便处理多了*/


			//1.首先可以确定的是关键字肯定是单独作为一个单词的
			if (isKeywords(words))
			{
				s = result(words);
				continue;
			}

			//2,对单词进行扫描,肯定是标识符,运算符,逗号分号,数字等等混合在一起的单词
			vector<int> index = { 0 };
			vector<string> mulWords;//将words分解为多个单词
			if (words.length() == 1)
			{
				string rel = result(words);
				continue;
			}
			for (int i = 0; i < words.length(); i++)
			{

				//运算符有两位的,比如"<=",">=","==","!="
				if ((i < words.length() - 1) && isOperator(words[i]) && isOperator(words[i + 1]))
				{
					//但是要注意只有以上四种两位运算符,比如+-,))就不是,但是))还是要输出),)
					if (string(words.begin() + i, words.begin() + i + 2) == "<=" ||
						string(words.begin() + i, words.begin() + i + 2) == ">=" ||
						string(words.begin() + i, words.begin() + i + 2) == "==" ||
						string(words.begin() + i, words.begin() + i + 2) == "!=")
					{
						index.push_back(i);
						index.push_back(i + 2);
						i++;
					}
					else if (isOperator(words[i]))
					{
						if (find(index.begin(), index.end(), i) == index.end())   //查找失败,index中没有i这个数,插入
							index.push_back(i);
						if (find(index.begin(), index.end(), i + 1) == index.end())   //查找失败,index中没有i+1这个数,插入
							index.push_back(i + 1);

					}
				}
				//逗号,运算符作为分隔 ,也包括最后一个字符是不是运算符
				else if (isOperator(words[i]))
				{
					if (find(index.begin(), index.end(), i) == index.end())
						//比如遇到"a,b"这里下标0和1将a分开,1到2将逗号分开,2到3将b分开
						index.push_back(i);
					if (find(index.begin(), index.end(), i + 1) == index.end())
						index.push_back(i + 1);

					//如果是a<=b这样的呢?一样,先0和1将a分开,1和2将<分开,2和3将=分开
					//3和4将b分开,然后后面分隔单词时,注意如果相邻都是运算符,则忽略,比如
					//后面判断到1和2,2和3都是运算符,则忽略2

				}

			}
			for (int i = 0; i < index.size() - 1; i++)
			{
				string rel;
				//比如遇到"<=",需要提取”<=“
				/*if (isOperator(words[index[i]]) && isOperator(words[index[i + 1]]))
				{
					rel = result(string(words.begin() + index[i], words.begin() + index[i + 2]));
					i++;
				}
				else*/
				rel = result(string(words.begin() + index[i], words.begin() + index[i + 1])); //每次传入前后的两个参数指令
			}
		}
		miss = "";  //换行之后要清空miss
		line_num++;
	}

	if (flag == 0)
	{
		input.close();
		input.open(file);
		str = "";
		words = "";
		while (getline(input, str)) //读取文件每一次读取一行,遇到EOF结束
		{
			//从输入流中获取单词,需要用到输入流对象,即istringstream
			istringstream strCin(str);
			string s;
			while (strCin >> words)  //这里的words可能是一大长串句子 , 按行处理
			{
				/*注意处理逗号,比如int a,b;这里有一个单词"a,b;”,所以要处理一个字符串里面
				的各种运算符,但是这样会很麻烦,发现没有,用ide写代码写完一句输入分号时,ide
				会自动加入空格,这样就方便处理多了*/


				//1.首先可以确定的是关键字肯定是单独作为一个单词的
				if (isKeywords(words))
				{
					s = result(words);
					cout << s << endl;
					continue;
				}

				//2,对单词进行扫描,肯定是标识符,运算符,逗号分号,数字等等混合在一起的单词
				vector<int> index = { 0 };
				vector<string> mulWords;//将words分解为多个单词
				if (words.length() == 1)
				{
					string rel = result(words);
					cout << rel << endl;
					continue;
				}
				for (int i = 0; i < words.length(); i++)
				{

					//运算符有两位的,比如"<=",">=","==","!="
					if ((i < words.length() - 1) && isOperator(words[i]) && isOperator(words[i + 1]))
					{
						//但是要注意只有以上四种两位运算符,比如+-,))就不是,但是))还是要输出),)
						if (string(words.begin() + i, words.begin() + i + 2) == "<=" ||
							string(words.begin() + i, words.begin() + i + 2) == ">=" ||
							string(words.begin() + i, words.begin() + i + 2) == "==" ||
							string(words.begin() + i, words.begin() + i + 2) == "!=")
						{
							index.push_back(i);
							index.push_back(i + 2);
							i++;
						}
						else if (isOperator(words[i]))
						{
							if (find(index.begin(), index.end(), i) == index.end())   //查找失败,index中没有i这个数,插入
								index.push_back(i);
							if (find(index.begin(), index.end(), i + 1) == index.end())   //查找失败,index中没有i+1这个数,插入
								index.push_back(i + 1);

						}
					}
					//逗号,运算符作为分隔 ,也包括最后一个字符是不是运算符
					else if (isOperator(words[i]))
					{
						if (find(index.begin(), index.end(), i) == index.end())
							//比如遇到"a,b"这里下标0和1将a分开,1到2将逗号分开,2到3将b分开
							index.push_back(i);
						if (find(index.begin(), index.end(), i + 1) == index.end())
							index.push_back(i + 1);

						//如果是a<=b这样的呢?一样,先0和1将a分开,1和2将<分开,2和3将=分开
						//3和4将b分开,然后后面分隔单词时,注意如果相邻都是运算符,则忽略,比如
						//后面判断到1和2,2和3都是运算符,则忽略2

					}

				}
				for (int i = 0; i < index.size() - 1; i++)
				{
					string rel;
					//比如遇到"<=",需要提取”<=“
					/*if (isOperator(words[index[i]]) && isOperator(words[index[i + 1]]))
					{
						rel = result(string(words.begin() + index[i], words.begin() + index[i + 2]));
						i++;
					}
					else*/
					rel = result(string(words.begin() + index[i], words.begin() + index[i + 1])); //每次传入前后的两个参数指令
					cout << rel << endl;
				}
				if (index.size() <= 1)
				{
					string rel = result(words);
					cout << rel << endl;
				}
			}
			miss = "";  //换行之后要清空miss
			line_num++;
		}
	}
	input.close();
	system("pause");
	return 0;
}


bool isIdentifier(string s)//标识符,试验要求:ID=letter(letter|digit)*
{
	if (!isKeywords(s))//标识符不能是关键字
	{
		if ((s[0] >= 'a'&&s[0] <= 'z') || (s[0] >= 'A'&&s[0] <= 'Z'))//是字母
		{
			for (int i = 1; i < s.length(); i++)
			{
				if ((s[i] >= 'a'&&s[i] <= 'z') || (s[i] >= 'A'&&s[i] <= 'Z')
					|| (s[i] >= '0'&&s[i] <= '9'))
					continue;
				else
				{
					miss = s[i];
					return false;
				}
			}
			miss = "";
			return true;
		}
		else
		{
			miss = s[0];
			return false;
		}
	}
	return false;
}
bool isKeywords(string s)//判断是不是关键字
{
	static vector<string> keyVec = { "main", "int", "float", "double", "char",
		"if", "then","else", "switch", "case", "break", "continue", "while",
		"do", "for","struct","." };
	vector<string>::iterator result = find(keyVec.begin(), keyVec.end(), s);
	if (result != keyVec.end())
		return true;
	else return false;
}
bool isDigit(string s)//整型数字,NUM=digit digit*
{
	if (s[0] >= '0'&&s[0] <= '9')
	{
		for (int i = 1; i < s.length(); i++)
		{
			if (s[i] >= '0'&&s[i] <= '9')
				continue;
			else
			{
				miss = s[i];
				return false;
			}
		}
		miss = "";
		return true;
	}
	return false;
}

bool isFloat(string s)
{
	int flag3 = 0;
	if (s[0] >= '0'&&s[0] <= '9')
	{
		for (int i = 1; i < s.length(); i++)
		{
			if (s[i] >= '0'&&s[i] <= '9')
				continue;
			else if (s[i] == '.')
			{
				flag3 = 1;
				continue;
			}
			else if (s[i] == 'e')
			{
				continue;
			}
			else
			{
				miss = s[i];
				return false;
			}
		}
		if (flag3 == 0)
		{
			return false;
		}
		miss = "";
		return true;
	}
	return false;
}

bool isOperator(string s) //运算符 
{
	static vector<string> opeVec = { "=","+","-","*","/","<","<=","==","!=",
		">",">=",";","(",")",":",",","{","}","&&","||","!" };
	vector<string>::iterator result = find(opeVec.begin(), opeVec.end(), s);
	if (result != opeVec.end())
	{
		miss = "";
		return true;
	}
	else
	{
		miss = s;
		return false;
	}
}

bool isOperator(char c)//运算符
{
	static vector<char> opeVec = { '=','+','-','*','/','<',
		//"<=","==","!=",
		'>',
		//">=",
		';','(',')','?',':',',','{','}','!' };
	vector<char>::iterator result = find(opeVec.begin(), opeVec.end(), c);
	if (result != opeVec.end())
		return true;
	else
	{
		miss = c;
		return false;
	}
}

string result(string s)//根据传入的参数s产生对应的输出
{
	//种别码
	//1.标识符
	if (isIdentifier(s))
		return "ID: " + s;

	//2.关键字
	static map<string, string> keyMap;
	keyMap["int"] = "TYPE: ";
	keyMap["main"] = "MAIN: ";
	keyMap["float"] = "TYPE: ";
	keyMap["if"] = "IF: ";
	keyMap["struct"] = "STRUCT: ";
	keyMap["return"] = "RETURN: ";
	keyMap["else"] = "ELSE: ";
	keyMap["while"] = "WHILE: ";
	keyMap["."] = "DOT: ";
	//这里后续把关键字补全
	if (isKeywords(s))
		return keyMap[s] + s;
	//3.浮点型常量
	if (isFloat(s))
		return "FLOAT: " + s;
	//3.整型常量
	else if (isDigit(s))
		return "INT: " + s;

	//4.运算符
	static map<string, string> opeMap;
	opeMap["="] = "RELOP: =";
	opeMap["<"] = "RELOP: <";
	opeMap["<="] = "RELOP: <=";
	opeMap["=="] = "RELOP: ==";
	opeMap["!="] = "RELOP: !=";
	opeMap[">"] = "RELOP: >";
	opeMap[">="] = "RELOP: >=";
	opeMap[";"] = "SEMI: ;";
	opeMap["+"] = "PLUS: +";
	opeMap["("] = "LP: (";
	opeMap["-"] = "MINUS: -";
	opeMap[")"] = "RP: )";
	opeMap["*"] = "STAR: *";
	opeMap["/"] = "DIV: /";
	opeMap[","] = "COMMA: ,";
	opeMap["{"] = "LC: {";
	opeMap["}"] = "RC: }";
	opeMap["["] = "LB: [";
	opeMap["]"] = "RB: ]";
	opeMap["!"] = "NOT: !";
	opeMap["&&"] = "AND: &&";
	opeMap["||"] = "OR: ||";
	if (isOperator(s))
		return opeMap[s];
	flag = 1;
	string ans = "Error type A at Line " + to_string(line_num) + " Mysterious character '" + miss + "'";
	cout << ans << endl;
	return "Error type A at Line " + to_string(line_num) + " Mysterious character '" + miss + "'";
}

1.5 总结

这次实验让我掌握了一个小型词法分析程序完整的构造过程,从查阅各种资料开始,到慢慢的理解别人的代码和思想,再到自己动手去编写一个词法分析程序。这样的一个过程既锻炼了自己的学习能力,也锻炼了自己的思维和编程能力,帮助我更好的学习编译原理这门课程,令我感到受益匪浅。

参考文献:(38条消息) 编译原理实验一:词法分析_那又怎样的博客-CSDN博客_编译原理实验一 词法分析

你可能感兴趣的:(c++)