编译原理课程实验的实验课内容—构造词法分析程序。通过本次实验,希望对于编译原理中词法分析这个功能有更深和更好的理解。
(1)理解有穷自动机及其应用。
(2)掌握 NFA 到 DFA 的等价变换方法、DFA 最小化的方法。
(3)掌握设计、编码、调试词法分析程序的技术和方法。
编写一个程序对输入的源代码(源代码可以是C++, C, Java等)进行词法分析,并打印分析结果。
你的程序要能够查出源代码中可能包含的词法错误: 词法错误(错误类型 A):(以输入 C–源代码为例)即出现 C–词法中未定义的字符 以及任何不符合 C–词法单元定义的字符;
你的程序输入是一个包含 C++ 源代码的文本文件,程序需要能够接收一个输入文件名作为参数,以获得相应的输出结果。
要求通过标准输出打印程序的运行结果。对于那些包含词法错误的输入文件,只要输出相关的词法有误的信息即可。在这种情况下,注意不要输出任何与语法树有关的内容。
要求输出的信息包括错误类型、出错的行号以及说明文字, 其格式为:
Error type [错误类型] at Line [行号]: [说明文字].
说明文字的内容没有具体要求,但是错误类型和出错的行号一定要正确,因为这是判断 输出的错误提示信息是否正确的唯一标准。请严格遵守实验要求中给定的错误分类(即词法 错误为错误类型 A).注意,输入文件中可能会包含一个或者多个词法错误(但输入文件的同一行中保证不出现多个错误),你的程序需要将这些错误全部报告出来,每一条错误提示信息在输出中单独占一行。 对于那些没有任何词法错误的输入文件,你的程序需要打印每一个词法单元的名称以及 与其对应的词素,无需打印行号。词法单元名与相应词素之间以一个冒号和一个空格隔开。 每一条词法单元的信息单独占一行。
在很多程序设计语言中,下面的类别覆盖了大部分或所有的词法单元:
1)关键字。一个关键字的类型就是该关键字本身。
2)运算符。它可以表示单个运算符,也可以像表 1-1 中的 comparison 那样,表示一类 运算符。
3)标识符。一个表示所有标识符的词法单元。
4)常量。一个或多个表示常量的词法单元,比如数字和字面值字符串。 5)界符。每一个标点符号有一个词法单元,比如左右括号、逗号和分号。
样例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: }
以下为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
# 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 + "'";
}
这次实验让我掌握了一个小型词法分析程序完整的构造过程,从查阅各种资料开始,到慢慢的理解别人的代码和思想,再到自己动手去编写一个词法分析程序。这样的一个过程既锻炼了自己的学习能力,也锻炼了自己的思维和编程能力,帮助我更好的学习编译原理这门课程,令我感到受益匪浅。
参考文献:(38条消息) 编译原理实验一:词法分析_那又怎样的博客-CSDN博客_编译原理实验一 词法分析