广州大学学生实验报告
开课学院及实验室:计算机科学与工程实验室电子楼412B室 2023年12月19日
学院 | 计算机科学与网络工程学院 | 年级、专业、班 | **** | 姓名 | *** | 学号 | **** | |
实验课程名称 | 编译原理实验 | 成绩 | *** | |||||
实验项目名称 | 词法分析 | 指导老师 | **** |
一、语法分析——实验目的
通过设计、扩充已有的 C 语言样例中的词法分析内容,为扩展的 C 语言样
例编制并调试一个词法分析程序,从而掌握词法分析程序的构造方法,加深对词
法分析原理的理解。
二、实验环境
(实验使用的软件/硬件环境)win11, dev C++, vs code, vs
三、实验内容及原理
基本实验要求
1) 编码实现将各类编程语言(不仅限指导书中提供的 C 语言样例)源程序
翻译成对应的二元组 TOKEN 序列,并能检查一定的词法错误,要求在
实现的代码上做好详细注释。
2) 编码实现的功能包含对关键字、带下划线的标识符、字符串常量、 注释
的识别,10 或 16 进制的整型常量、带指数的双精度常量的识别,以及
词法错误处理(选做)。
3) 按模板撰写完成详细的实验报告,根据完成的工作量、正确性和代码注
释与实验报告清晰程度综合打分。
探索部分实验要求
(一)依据表示标识符的正规式给出最简 DFA 的状态转换图
输入:表示标识符的正规式(标识符:以字母开头,由字母和数字构成的字符串)。
输出:
n 确定化后的 NFA 状态转换图或转换矩阵和初、终态;
n 最简化后的 DFA 状态转换图或状态转换矩阵及初、终态;
编码实现以下功能:
(1) 明确字母表∑中包含的字符,输入表示标识符的正规式,依据等价规则
构造相应的 NFA;(思考 NFA 的状态转换图以什么样的形式来存储)
(2) 编码实现对 NFA 的确定化,生成对应的 DFA;(难点)
(3) 编码实现对 DFA 的最简化,并输出其状态转换图。
(二)上传源程序代码图片,识别图中有效字符代码,并按照基础实验
要求完成对代码中不同类型单词的识别和输出
输入:包含源代码的图片(图片格式不限,但必须包含基础实验要求中待识别的
单词对象)。
输出:同基础实验要求一样
编码实现以下功能:
(1) 编码识别图中源程序的各项有效代码字符并进行存储,要求不改变代码
原意。
(2) 同基础实验要求一样。
完整代码
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <set>
using namespace std;
// 单词的类型
enum TokenType
{
Keywords,
Identifiers,
Literals, // 常数
Operators, // 运算符
Delimiters, // 界符
};
// 结构体 : 包含单词类型, 种别码, 值
struct Token
{
TokenType tokenType; // 单词类型
int syn; // 种别码
string value; // 值
// 构造函数
Token(TokenType tokenType1, int syn1, string value1)
{
this->tokenType = tokenType1;
this->syn = syn1;
this->value = value1;
}
Token() {}
};
vector<Token> ans; // 二元组结果
vector<string> ans_error;
vector<string> keywords = {
"main", "int", "char", "if", "else", "for", "while", "return", "void"}; // 保留字
// 种别码
map<string, int> getSyn = {
{"main", 1},
{"int", 2},
{"char", 3},
{"if", 4},
{"else", 5},
{"for", 6},
{"while", 7},
{"return", 8},
{"void", 9},
{"string", 50},
{"id", 10},
{"num", 20},
{"=", 21},
{"+", 22},
{"-", 23},
{"*", 24},
{"“", 11},
// 第二列
{"/", 25},
{"(", 26},
{")", 27},
{"[", 28},
{"]", 29},
{"{", 30},
{"}", 31},
{",", 32},
{":", 33},
{";", 34},
{">", 35},
{"<", 36},
{">=", 37},
{"<=", 38},
{"==", 39},
{"!=", 40},
{"”", 12},
};
bool isKeyWord(string s)
{
int n = keywords.size();
for (int i = 0; i < n; i++)
{
if (s == keywords[i])
{
return true;
}
}
return false;
}
void tokenize(string sourceCode)
{
int n = sourceCode.size();
for (int i = 0; i < n; i++)
{
char ch = sourceCode[i];
// 字母 : 保留字或者标识符
// 字母开头或者_下划线开头
if (isalpha(ch) || ch == '_')
{
string currentToken = ""; // 当前单词
while (i < n && (isdigit(sourceCode[i]) || isalpha(sourceCode[i]) || sourceCode[i] == '_'))
{
currentToken += sourceCode[i];
i++;
}
// 遇到了其他字符(空格, “”, 或者其他())
// 查询是否是保留字
if (isKeyWord(currentToken))
{
Token token;
token.tokenType = Keywords;
token.syn = getSyn[currentToken];
token.value = currentToken;
ans.push_back(token);
}
// 标识符
else
{
Token token;
token.tokenType = Identifiers;
token.syn = getSyn["id"]; // id的种别码
token.value = currentToken;
ans.push_back(token);
}
i--;
}
// 数字
// 添加功能 : 小数点
else if (isdigit(ch))
{
string currentToken = "";
int j;
set<char> st; // 记录 小数点, e, E的出现次数;
for (j = i; j < n; j++)
{
// 数字
if (isdigit(sourceCode[j]))
{
currentToken += sourceCode[j];
}
// 小数点
else if (sourceCode[j] == '.')
{
// 第一次出现小数点
if (!st.count('.'))
{
st.insert('.');
currentToken += sourceCode[j];
}
else
{
ans_error.push_back("错误, 浮点数出现了两个小数点.");
}
}
// 字母e和字母E
else if (sourceCode[j] == 'e' || sourceCode[j] == 'E')
{
// 第一次出现e
if (!st.count(sourceCode[j]))
{
st.insert(sourceCode[j]);
currentToken += sourceCode[j];
}
else
{
ans_error.push_back("错误, 科学记数法出现了两个e或者E.");
}
}
// e后面有+和-
else if (sourceCode[j] == '+' || sourceCode[j] == '-')
{
// 第一次出现这个
if (!st.count(sourceCode[j]) && (sourceCode[j - 1] == 'e' || sourceCode[j - 1] == 'E'))
{
st.insert(sourceCode[j]);
currentToken += sourceCode[j];
}
else
{
ans_error.push_back("错误, 科学记数法中 : + 或者 -出现问题, (重复出现或者前面没有e或者E)");
}
}
else
{
break;
}
}
Token token;
token.tokenType = Literals;
token.syn = getSyn["num"];
token.value = currentToken;
ans.push_back(token);
i = j - 1;
}
// 加号 +
else if (ch == '+')
{
Token token;
token.tokenType = Operators;
token.syn = getSyn["+"];
token.value = "+";
ans.push_back(token);
}
// 减号
else if (ch == '-')
{
Token token;
token.tokenType = Operators;
token.syn = getSyn["-"];
token.value = "-";
ans.push_back(token);
}
// 乘号
else if (ch == '*')
{
Token token;
token.tokenType = Operators;
token.syn = getSyn["*"];
token.value = "*";
ans.push_back(token);
}
// 小于 或者 小于等于
if (ch == '<')
{
if (sourceCode[i + 1] == '=')
{
string currentToken = "<=";
Token token;
token.tokenType = Operators;
token.syn = getSyn["<="];
token.value = "<=";
ans.push_back(token);
i++;
}
else
{
string currentToken = "<";
Token token;
token.tokenType = Operators;
token.syn = getSyn["<"];
token.value = "<";
ans.push_back(token);
}
}
// 大于 或者 大于等于
if (ch == '>')
{
if (sourceCode[i + 1] == '=')
{
string currentToken = ">=";
Token token;
token.tokenType = Operators;
token.syn = getSyn[">="];
token.value = ">=";
ans.push_back(token);
i++;
}
else
{
string currentToken = ">";
Token token;
token.tokenType = Operators;
token.syn = getSyn[">"];
token.value = ">";
ans.push_back(token);
}
}
//= 或者 ==
if (ch == '=')
{
if (sourceCode[i + 1] == '=')
{
string currentToken = "==";
Token token;
token.tokenType = Operators;
token.syn = getSyn["=="];
token.value = "==";
ans.push_back(token);
i++;
}
else
{
string currentToken = "=";
Token token;
token.tokenType = Operators;
token.syn = getSyn["="];
token.value = "=";
ans.push_back(token);
}
}
// 分号
if (ch == ';')
{
Token token;
token.tokenType = Delimiters;
token.syn = getSyn[";"];
// token.value = "" + ch; //不能直接拼接
token.value = ";";
ans.push_back(token);
}
// 字符串常量 : 双引号
else if (ch == '"')
{
Token tokenLeft(Delimiters, getSyn["\""], "\"");
ans.push_back(tokenLeft); // 双引号
bool contain = false;
int j = i + 1;
for (j = i + 1; j < n; j++)
{
if (sourceCode[j] == '"')
{
contain = true;
break;
}
}
//""不成对出现
if (contain == false)
{
string s = "编译错误, 双引号\"\"缺失, 没有成对出现";
ans_error.push_back(s);
}
else
{
string currentToken = sourceCode.substr(i + 1, (j - 1) - (i + 1) + 1);
Token token;
token.tokenType = Literals;
token.syn = getSyn["string"];
token.value = currentToken;
ans.push_back(token);
Token tokenRight(Delimiters, getSyn["\""], "\""); // 双引号
ans.push_back(tokenRight); // 双引号
i = j;
}
}
// ()
else if (ch == '(' || ch == ')')
{
if (ch == '(')
{
Token tokenLeft(Delimiters, getSyn["("], "(");
ans.push_back(tokenLeft); // 双引号
bool contain = false;
int j = i + 1;
for (j = i + 1; j < n; j++)
{
if (sourceCode[j] == ')')
{
contain = true;
break;
}
}
//""不成对出现
if (contain == false)
{
string s = "编译错误, 括号)缺失, 没有成对出现";
ans_error.push_back(s);
}
}
// 右括号
else
{
Token tokenLeft(Delimiters, getSyn[")"], ")");
ans.push_back(tokenLeft); // 双引号
}
}
// []
else if (ch == '[' || ch == ']')
{
if (ch == '[')
{
Token tokenLeft(Delimiters, getSyn["["], "[");
ans.push_back(tokenLeft); // 双引号
bool contain = false;
int j = i + 1;
for (j = i + 1; j < n; j++)
{
if (sourceCode[j] == ']')
{
contain = true;
break;
}
}
//""不成对出现
if (contain == false)
{
string s = "编译错误, 中括号 ] 缺失, 没有成对出现";
ans_error.push_back(s);
}
}
// 右括号
else
{
Token tokenLeft(Delimiters, getSyn["]"], "]");
ans.push_back(tokenLeft); // 双引号
}
}
// {}
else if (ch == '{' || ch == '}')
{
if (ch == '{')
{
Token tokenLeft(Delimiters, getSyn["{"], "{");
ans.push_back(tokenLeft); // 双引号
bool contain = false;
int j = i + 1;
for (j = i + 1; j < n; j++)
{
if (sourceCode[j] == '}')
{
contain = true;
break;
}
}
//""不成对出现
if (contain == false)
{
string s = "编译错误, 大括号 } 缺失, 没有成对出现";
ans_error.push_back(s);
}
}
// 右括号
else
{
Token tokenLeft(Delimiters, getSyn["}"], "}");
ans.push_back(tokenLeft); // 双引号
}
}
// 冒号
else if (ch == ':')
{
Token token;
token.tokenType = Delimiters;
token.syn = getSyn[":"];
token.value = ":";
ans.push_back(token);
}
// 叹号
else if (ch == '!')
{
if (sourceCode[i + 1] == '=')
{
Token token;
token.tokenType = Operators;
token.syn = getSyn["!="];
token.value = "!=";
ans.push_back(token);
i++;
}
else
{
Token token;
token.tokenType = Operators;
token.syn = getSyn["!"];
token.value = "!";
ans.push_back(token);
}
}
// 点 .
else if (ch == '.')
{
Token token;
token.tokenType = Operators;
token.syn = getSyn["."];
token.value = ".";
ans.push_back(token);
}
// 除号/ 注释
else if (ch == '/')
{
// 注释1 : //
if (sourceCode[i + 1] == '/')
{
int j = i + 2;
string s = "//";
while (j < n && sourceCode[j] != '\n')
{
s += sourceCode[j];
j++;
}
i = j;
Token token;
token.tokenType = Literals;
token.value = s;
ans.push_back(token);
}
// 注释2 : / * * /
else if (sourceCode[i + 1] == '*')
{
int j = i + 2;
while (j + 1 < n && !(sourceCode[j] == '*' && sourceCode[j + 1] == '/'))
{
j++;
}
// /* 成对出现 : 正常
if (sourceCode[j] == '*' && sourceCode[j + 1] == '/')
{
string s = "/*" + sourceCode.substr(i + 2, j - 1 - (i + 2) + 1) + "*/";
Token token;
token.tokenType = Literals;
token.value = s;
ans.push_back(token);
i = j + 1;
}
else
{
ans_error.push_back("编译错误, /* 注释符号不成对出现");
}
}
// 普通除号
else
{
Token token;
token.tokenType = Operators;
token.syn = getSyn["/"];
token.value = "/";
ans.push_back(token);
}
}
}
}
int main()
{
// 打开文件
cout << "正在打开文件... ..." << endl;
fstream file("source_code.c");
// fstream file("imgToCode.c");
string sourceCode = "";
if (!file.is_open())
{
cout << "打开文件失败" << endl;
}
else
{
cout << "打开文件成功" << endl;
string buf;
while (getline(file, buf))
{
buf = "\n" + buf;
sourceCode += buf;
}
}
// 读取文件的内容
cout << "源程序的代码是 : " << endl;
cout << sourceCode << endl;
// 编译中
tokenize(sourceCode);
if (ans_error.size() == 0)
{
cout << "编译成功" << endl;
for (auto token : ans)
{
cout << "( " << token.syn << " : " << token.value << " )" << endl;
}
}
else {
cout << "编译失败" << endl;
for(auto s : ans_error)
cout << s << endl;
}
}
四、关键数据结构和核心算法
4.1 数据结构
单词的类型 : 枚举类
结构体 : 包含单词类型, 种别码, 值
|
- 2 核心算法
|
python图像识别 :
import pytesseract |
4.3 错误处理
4.4 运行结果
五、总结
实现了一个词法分析器
- 对输入源程序字符串进行词法分析,输出二元组序列,包括对以下字符的分析
- 关键字
- 带下划线的标识符
- 字符串常量
- 注释
- 科学计数法
- 实现OCR图像, 能够对图像形式的c程序代码, 进行识别得到源程序字符串, 并对其进行分析.