写在前面
纯粹,为了记录.
以后某一天,回过头来看看.给大学留些痕迹hh.
PS:很多思路也都是学习前辈们的,如果能顺便帮到你,不客气!
内容
编译原理 实验2
通过编制调试一个具体的词法分析程序,加深对词法分析原理的理解。并掌握在对程序设计语言源程序进行扫描过程中将其分解为各类单词的词法分析方法。
本实验以用户指定的想编译的以C语言编写的文件作为词法分析程序的输入数据。
在进行词法分析中,先自文件头开始以行为单位扫描程序,将该行的字符读入预先设定的一个数组缓冲区中,然后对该数组的字符逐词分割,进行词法分析,将每个词分割成关键字、标识符、常量和运算符四种词种,最终产生四个相对应的表,即关键字表、标识符表、常量表和运算符表,它们以文件的形式进行存储。除此之外,还产生一个编译后的文件,它指定了每个词在四个表中的位置。
编程实现源程序的输入和扫描程序。
源代码
#include <iostream>
#include <string>
#include <fstream> //ifstream读文件,ofstream写文件,fstream读写文件
#include <windows.h>
using namespace std;
/************************************全局常量************************************/
//C语言关键字,32个
const string keyWord[32] = { "auto", "double", "int", "struct", "break", "else", "long", "switch",
"case", "enum", "register", "typedef", "char", "extern", "return", "union",
"const", "float" "short", "unsigned", "continue", "for", "signed", "void",
"default", "goto", "sizeof", "volatile", "do", "if", "while", "static"
};
const string IN_FILE_PATH = "1.txt";
const string OUT_FILE_PATH = "after_com.txt";
const string KEY_FILE_PATH = "key.txt";
const string OPERATION_FILE_PATH = "operation.txt";
const string ID_FILE_PATH = "id.txt";
const string CONST_FILE_PATH = "const.txt";
/************************************全局变量************************************/
int keyPos = 0; //记录关键字输出表的行位置
int operationPos = 0; //记录运算符输出表的行位置
int idPos = 0; //记录标识符输出表的行位置
int constPos = 0; //记录常量输出表的行位置
/************************************文件输入输出流************************************/
//ios::app——所有输出附加在文件末尾
ifstream inStream(IN_FILE_PATH, ios::in); //源代码
ofstream outStream(OUT_FILE_PATH, ios::out); //编译后的文件
ofstream keyStream(KEY_FILE_PATH, ios::out); //关键字文件
ofstream idStream(ID_FILE_PATH, ios::out); //标识符文件
ofstream operationStream(OPERATION_FILE_PATH, ios::out);//运算符文件
ofstream constStream(CONST_FILE_PATH, ios::out); //常量文件
/************************************函数声明************************************/
string UTF8ToGB(const char *str); /*转换编码函数*/
bool isLetter(char ch); /*判断字符是否为字母*/
bool isDigit(char ch); /*判断字符是否为数字*/
void lexicalAnalyzer(string line, int row); /*词法分析器*/
bool isKeyWord(string word); /*判断是否是关键字*/
bool openAllFile(); /*打开所需的所有文件:1个输入文件即源代码、5个输出文件*/
void closeAllFile(); /*关闭所有文件*/
int main() {
if (!openAllFile()) {
cout << "存在文件未成功打开!" << endl;
return -1;
}
cout << "##############################################################" << endl;
cout << "### 词法分析结果如下,数据已输出至 当前目录下: ##" << endl;
cout << "### 关键字表、标识符表、常量表、运算符表、编译后的文件. ##" << endl;
cout << "##############################################################" << endl;
cout << endl;
//逐行读取源代码文件
int row_index = 0;
while (!inStream.eof()) {
string lineBuf; //存放读取到的一行字符串
getline(inStream, lineBuf, '\n'); //注意,'\n'截止字符是从流中取出来丢弃
lineBuf = UTF8ToGB(lineBuf.c_str()).c_str(); //转换编码,解决中文乱码的问题
lexicalAnalyzer(lineBuf, row_index);
row_index ++ ;
//cout << lineBuf << endl; //打印到屏幕
//outStream << lineBuf << endl; //输出到文件2.txt
}
closeAllFile();
return 0;
}
/********************************************
* 功能:对输入的一行源代码进行词法分析,并将结果输出到对应文件
* 输入参数:string line,int row(源代码行号)
* 返回类型:无
* 说明:将每个词划分为关键字、标识符、常量和运算符四种词种,其余词归为其他词种
* 注:
1、数字未加入浮点类型的状态转换,只分割为整型数;
2、标识符可以字母或者下划线开头;
3、运算符只判断简单的几个运算符;
*********************************************/
void lexicalAnalyzer(string line, int row) {
int index = 0; //当前指针的位置,即列号
int len = line.size();
//去掉头部空白
//即从开始态进入0态(依据状态图)
while (index <= len - 1 && line[index] == ' ') {
index ++ ;
}
while (index <= len - 1) {
char ch = line[index]; //读取当前位置的字符
string word = ""; //存放中间态、终态的单词
/*
当前为开始态,即0态,word为空
*/
//字符为字母或者下划线
if (isLetter(ch) || ch == '_') {
word += ch; //由0态变为1态
ch = line[ ++ index];
//字符为字母或者下划线或者数字
while (isLetter(ch) || ch == '_' || isDigit(ch)) {
word += ch; //由1态变为1态,循环
ch = line[ ++ index];
}
//从循环出来,即 非字母、下划线、数字,则由1态进入2态
//cout << "关键字或者标识符:" << word << endl;
//注意index此时不后移,因为未存入word
if (isKeyWord(word)) {
cout << "关键字:" << word << " 位置:(" << row << "," << index - word.size() << ")" << endl;
//写入关键字表:二元组(关键字,在源代码中的起始位置)
keyStream << word << " 在源代码中的起始位置:(" << row << "," << index - word.size() << ")" << endl;
//写入编译后的表:三元组(关键字,表名,在表中的起始位置)
outStream << word << " 表名:key.txt" << " 在表中的起始位置:(" << keyPos << ",0)" << endl;
keyPos ++ ;
} else {
cout << "标识符:" << word << " 位置:(" << row << "," << index - word.size() << ")" << endl;
//写入标识符表:二元组(标识符,在源代码中的起始位置)
idStream << word << " 在源代码中的起始位置:(" << row << "," << index - word.size() << ")" << endl;
//写入编译后的表:三元组(标识符,表名,在表中的起始位置)
outStream << word << " 表名:id.txt" << " 在表中的起始位置:(" << idPos << ",0)" << endl;
idPos ++ ;
}
}
//字符为数字
else if (isDigit(ch)) {
//字符为数字,循环从3态变为3态
while (isDigit(ch)) {
word += ch;
ch = line[ ++ index];
}
//字符为非数字,则由3态变为4态
cout << "标识符:" << word << " 位置:(" << row << "," << index - word.size() << ")" << endl;
//写入常量表:二元组(常量,在源代码中的起始位置)
constStream << word << " 在源代码中的起始位置:(" << row << "," << index - word.size() << ")" << endl;
//写入编译后的表:三元组(常量,表名,在表中的起始位置)
outStream << word << " 表名:const.txt" << " 在表中的起始位置:(" << constPos << ",0)" << endl;
constPos ++ ;
//index也不移动,因为该index位置字符未使用,同上
}
//字符为算术运算符
else if (ch == '+' || ch == '-' || ch == '*' || ch == '/') {
//则由0态变为5、6、7、8、9态其中一个
word += ch;
index ++ ; //指针后移
cout << "算术运算符:" << word << " 位置:(" << row << "," << index - word.size() << ")" << endl;
//写入算术运算符表:二元组(运算符,在源代码中的起始位置)
operationStream << word << " 在源代码中的起始位置:(" << row << "," << index - word.size() << ")" << endl;
//写入编译后的表:三元组(运算符,表名,在表中的起始位置)
outStream << word << " 表名:operation.txt" << " 在表中的起始位置:(" << operationPos << ",0)" << endl;
operationPos ++ ;
}
//字符为比较运算符
else if (ch == '<' || ch == '>' || ch == '=') {
//由0态变为10态
word += ch;
ch = line[ ++ index];
if (ch == '=') {
word += ch; //由10态变为11态
index ++ ; //该位置字符已用,指针后移
}
cout << "比较运算符:" << word << " 位置:(" << row << "," << index - word.size() << ")" << endl;
//写入运算符表:二元组(运算符,在源代码中的起始位置)
operationStream << word << " 在源代码中的起始位置:(" << row << "," << index - word.size() << ")" << endl;
//写入编译后的表:三元组(运算符,表名,在表中的起始位置)
outStream << word << " 表名:operation.txt" << " 在表中的起始位置:(" << operationPos << ",0)" << endl;
operationPos ++ ;
}
//为其他字符
else {
index ++ ; //指针后移
}
}
index ++ ; //指针后移,继续读取字符
}
/********************************************
* 功能:判断字符是否为字母
* 输入参数:字符ch
* 返回类型:bool
* 说明:是则返回true,不是返回false
*********************************************/
bool isLetter(char ch) {
if ( (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
return true; //字母
}
return false; //非字母
}
/********************************************
* 功能:判断字符是否为数字
* 输入参数:字符ch
* 返回类型:bool
* 说明:是则返回true,不是返回false
*********************************************/
bool isDigit(char ch) {
if (ch >= '0' && ch <= '9') {
return true; //数字
}
return false; //非数字
}
/********************************************
* 功能:判断单词是否为关键字
* 输入参数:string word
* 返回类型:bool
* 说明:在关键字常量数组中遍历,寻找与word匹配的关键字,找到则返回true,否则返回false
*********************************************/
bool isKeyWord(string word) {
for (int i = 0; i < 32; i ++ ) {
if (word == keyWord[i]) {
return true; //是关键字
}
}
return false; //非关键字
}
/********************************************
* 功能:判断5个文件是否全部正常打开
* 输入参数:无
* 返回类型:无
*********************************************/
void closeAllFile() {
inStream.close();
outStream.close();
keyStream.close();
idStream.close();
operationStream.close();
constStream.close();
}
/********************************************
* 功能:判断5个文件是否全部正常打开
* 输入参数:无
* 返回类型:bool
* 说明:成功则返回true,否则false
*********************************************/
bool openAllFile() {
if (!inStream) {
cout << "open in fail!" << endl;
return false;
}
if (!outStream) {
cout << "open out fail!" << endl;
return false;
}
if (!keyStream) {
cout << "open key fail!" << endl;
return false;
}
if (!idStream) {
cout << "open id fail!" << endl;
return false;
}
if (!operationStream) {
cout << "open operation fail!" << endl;
return -1;
}
if (!constStream) {
cout << "open const fail!" << endl;
return false;
}
return true; //全部文件打开成功
}
/*转换编码函数,解决中文乱码的问题*/
/*该函数引用自:作者——https://blog.csdn.net/qd1308504206*/
std::string UTF8ToGB(const char *str) {
std::string result;
WCHAR *strSrc;
LPSTR szRes;
//获得临时变量的大小
int i = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
strSrc = new WCHAR[i + 1];
MultiByteToWideChar(CP_UTF8, 0, str, -1, strSrc, i);
//获得临时变量的大小
i = WideCharToMultiByte(CP_ACP, 0, strSrc, -1, NULL, 0, NULL, NULL);
szRes = new CHAR[i + 1];
WideCharToMultiByte(CP_ACP, 0, strSrc, -1, szRes, i, NULL, NULL);
result = szRes;
delete[]strSrc;
delete[]szRes;
return result;
}