理论知识
源程序输入与词法分析输出
输入:源程序由字符组成,词法分析程序接收源代码作为输入。
输出:词法分析程序的输出通常是标记化的标记(token)。这些标记是源代码中的识别单元,如关键字、标识符、运算符、常量等。
正则文法及其状态转换图的基本概念
正则文法是一种描述字符串集的形式文法,使用正则表达式来表示字符串模式。
状态转换图(也称为有限状态自动机)是描述正则文法的图形化表示,由节点(状态)和转移(根据输入字符进行状态间的转换)组成。
正则表达式及有限自动机的基本概念
正则表达式是用于描述字符串模式的表达式,包括字符、操作符和通配符。
有限自动机是有限状态的机器,接受输入并根据输入进行状态转换。它可以是确定的(DFA)或非确定的(NFA),用于匹配正则表达式定义的模式。
正规文法构造相应的状态转换图的基本方法
正规文法是一种描述文法的形式化系统,能够生成正则语言。构建状态转换图的方法包括将正规文法的规则转化为状态及其之间的转移关系,每个状态代表一个模式的识别状态。
正则表达式构造有限自动机的基本方法及不确定有限自动机确定化的基本方法
构造有限自动机:正则表达式可直接转换为非确定有限自动机(NFA),NFA 通过状态和转移描述模式的匹配过程。
不确定有限自动机确定化:从 NFA 到 DFA 的转换过程称为确定化,这包括将非确定性转换为确定性,即对于每个状态和输入字符,有一个明确的下一个状态。
任务要求
实验项目
以下为正则文法所描述的C语言子集单词符号的示例,请补充单词符号: ++,--, >>, <<, += , -= ,*=, /= ,&&(逻辑与),||(逻辑或),!(逻辑非)等等,给出补充后描述C语言子集单词符号的正则文法,设计并实现其词法分析程序。
以下文法为左线性正则文法:
- <标识符>→字母︱ <标识符>字母︱ <标识符>d
- <无符号整数>→数字︱ <无符号整数>数字
- <单字符分界符>→+ ︱- ︱* ︱;︱, ︱(︱) ︱{︱}
- <双字符分界符>→<大于>=︱<小于>=︱<小于>>︱<感叹号>=︱<等于>=︱<斜竖>*
- <小于>→<
- <等于>→=
- <大于>→>
- <斜竖> →/
- <感叹号>→!
该语言的保留字 :void、int、float、double、if、else、for、do、while 等等(也可补充)。
设计说明
- 可将该语言设计成大小写不敏感,也可设计成大小写敏感,用户定义的标识符最长不超过32个字符;
- 字母为a-z A-Z,数字为0-9;
- 可以对上述文法进行扩充和改造;
- “/*……*/”和“//”(一行内)为程序的注释部分。
设计要求
- 给出各单词符号的类别编码;
- 词法分析程序应能发现输入串中的错误;
- 词法分析作为单独一遍编写,词法分析结果为二元式序列组成的中间文件;
- 设计至少4个测试用例(尽可能完备),并给出测试结果。
任务实现
语言与文法说明
- 该语言大小写敏感,用户定义的标识符最长不超过32个字符;
- 字母为a-z A-Z,数字为0-9;
- “/*……*/”和“//”(一行内)为程序的注释部分。
修改后的正则文法:
<标识符>->字母|<标识符>字母|<标识符>数字
<无符号整数>->数字|<无符号整数>数字
<单字符分界符>->;|,|(|)|{|}|!|<斜竖>|<加号>|<减号>|<乘号>|<感叹号>
<双字符分界符>-><大于>=|<大于>>|<小于>=|<小于><|<小于>>|<感叹号>=|<等于>=|<加号>=|<减号>=|<乘号>=|<斜竖>=|<与>&| <或>||<加号>+|<减号>-
<小于>-><
<大于>->>
<等于>->=
<斜竖>->/
<感叹号>->!
<加号>->+
<减号>->-
<乘号>->*
<与>->&
<或>->|
单词符号的类别编码
- 关键字:1
- 标识符:0
- char型变量: 5
- 字符串变量:4
- 数字:6
- 单分隔符:2
- 双分隔符:3
程序功能描述
这个程序是一个词法分析器(Lexical Analyzer),用于对输入的源代码文件进行词法分析,提取其中的标识符、运算符、常数、关键字等内容,并将提取的内容按照类型分类后保存到输出文件中。它的主要功能包括:
预处理函数 (pretreatment):
读取输入的源代码文件,去除其中的注释(单行注释 // 和多行注释 /* ... */),将代码进行处理,去除多余的空格和空行。
string pretreatment(const string& p_in) {
string r = ""; // 用来保存去除注释后的源程序
try {
std::ifstream C_txt(p_in);
std::stringstream buffer;
buffer << C_txt.rdbuf();
std::string line;
bool InComment = false; // 用于跟踪是否在注释块内
while (getline(buffer, line)) {
// 处理多行注释
if (InComment) {
size_t found = line.find("*/");
if (found != string::npos)
{
line = line.substr(found + 2);
InComment = false; // 出注释块了
}
else
{
continue; // 在多行注释中,跳过当前行
}
}
// 处理单行注释
size_t SingleCommentIndex = line.find("//");
if (SingleCommentIndex != string::npos)
{
line = line.substr(0, SingleCommentIndex);
}
// 处理多行注释的开始
size_t MultiCommentStartIndex = line.find("/*");
if (MultiCommentStartIndex != std::string::npos)
{
size_t MultiCommentEndIndex = line.find("*/");
if (MultiCommentEndIndex != std::string::npos)
{
// 删除多行注释
line = line.substr(0, MultiCommentStartIndex) + line.substr(MultiCommentEndIndex + 2);
}
else
{
// 进入多行注释块
line = line.substr(0, MultiCommentStartIndex);
InComment = true; // 注释块内标识为真
}
}
r += " " + line; // 将每行用空格隔开
}
// 删除多余的空格
r.erase(std::unique(r.begin(), r.end(), [](char a, char b) { return a == ' ' && b == ' '; }), r.end());
r.erase(std::remove(r.begin(), r.end(), '\n'), r.end());
// 去除首尾空格
size_t start = r.find_first_not_of(" \t\n\r");
size_t end = r.find_last_not_of(" \t\n\r");
if (start == string::npos) {
r = ""; // Empty or all spaces
}
else
r = r.substr(start, end - start + 1);
cout << r << endl; // 将预处理后的字符串输出
}
catch (const exception& e)
{
cerr << e.what() << endl;
}
return r; // 返回处理好后的字符串
}
词法分析函数 (scanner):
对经过预处理的源代码进行逐个字符的扫描,根据字符的特性,将其识别为标识符、运算符、常数、字符串等。
根据 ASCII 码值将字符进行分类判断,包括空格、字母、数字、双引号、单引号和其他符号。
通过状态判断和字符串拼接,提取出相应的标识符,并根据其类别将其归入对应的表中,例如关键字表、标识符表、运算符表等。
void scanner(const std::string& path_in, const std::string& path_out) {
try {
string result = ""; // 保存token信息
size_t subscript = 0; // 计数
int t, status, pre_subscript; // 分别为:记录单个字符的ASCII码,状态,记录上一个字符的下标
string word; // 形成单词
string r = path_in; // 经预处理后的文本字符流信息
while (subscript < r.length()) {
t = static_cast<int>(r[subscript]); // 将字符转换为ASCII码
if (t == 32)
status = 0; // 空格
else if ((t > 64 && t < 91) || (t > 96 && t < 123)) // 字母
status = 1;
else if (t >= 48 && t <= 57) // 数字
status = 2;
else if (t == 34) // 双引号是字符串
status = 3;
else if (t == 39) // 单引号是单字符
status = 4;
else if ((t >= 33 && t <= 47 && t != 34 && t != 39) || (t >= 58 && t <= 64) || (t >= 91 && t <= 96) || (t >= 123 && t <= 126))
status = 5;
else {
// 非法字符,输出异常信息
cout << "出现非法字符:" << endl;
cout << r[subscript] << ":";
cout << static_cast<int>(r[subscript]) << endl;
break;
}
switch (status) { // 对于每一种状态
case 0: // 空格
subscript++;
break;
case 1: // 字母(单词)
word = "";
pre_subscript = subscript;
while ((t > 64 && t < 91) || (t > 96 && t < 123) || (t > 47 && t < 58)) {
word = word + r[subscript];
subscript++;
if (subscript < r.length())
t = static_cast<int>(r[subscript]);
else
break;
}
// 在这里处理关键字、标识符,添加到 result 和对应的列表中
if (find(begin(k), end(k), word) != end(k)) // 关键字
result = result + "( " + KEY + ", " + word + ")\n";
else //标识符
{
if (std::find(i.begin(), i.end(), word) == i.end())
i.push_back(word);
result = result + "( "+ ID + ", " + word + ")\n";
}
break;
case 2: // 数字
word = "";
pre_subscript = subscript;
while (t > 47 && t < 58) {
word += r[subscript];
subscript++;
if (subscript < r.length())
t = static_cast<int>(r[subscript]);
else
break;
}
if (std::find(c.begin(), c.end(), word) == c.end()) // 判断是否为常数
c.push_back(word);
result = result + "( "+ NUMBER + ", " + word + ")\n";
break;
case 3: { // 字符串
word = "";
pre_subscript = subscript;
subscript++;
t = static_cast<int>(r[subscript]);
while (t != 34) {
word += r[subscript];
subscript++;
if (subscript < r.length())
t = static_cast<int>(r[subscript]);
else
break;
}
subscript++;
if (find(S.begin(), S.end(), word) == S.end()) // 判断是否为字符串
S.push_back(word);
result = result + "( " + STRING + ", " + word + ")\n";
break;
}
case 4: // 字符常量
word = "";
pre_subscript = subscript;
subscript++;
t = static_cast<int>(r[subscript]);
while (t != 39) {
word += r[subscript];
subscript++;
if (subscript < r.length())
t = static_cast<int>(r[subscript]);
else
break;
}
subscript++;
if (find(C.begin(), C.end(), word) == C.end()) // 判断是否为单字符
C.push_back(word);
result = result + "( " + CHAR + ", " + word + ")\n";
break;
case 5: { // 符号 如果是符号,直接将符号取出
word = r.substr(subscript, 1);
if (subscript + 1 < r.length())
t = static_cast<int>(r[subscript + 1]);
if ((t >= 33 && t <= 47 && t != 34 && t != 39) || (t >= 58 && t <= 64) || (t >= 91 && t <= 96) || (t >= 123 && t <= 126)) {
char f_point1 = r[subscript];
char f_point2 = r[subscript + 1];
if ((f_point1 == '&' && f_point2 == '&') || (f_point1 == '+' && f_point2 == '+')
|| (f_point1 == '-' && f_point2 == '-') || (f_point1 == '|' && f_point2 == '|')
|| (f_point1 == '>' && f_point2 == '=') || (f_point1 == '<' && f_point2 == '=')
|| (f_point1 == '=' && f_point2 == '=') || (f_point1 == '!' && f_point2 == '=')
|| (f_point1 == '<' && f_point2 == '<') || (f_point1 == '>' && f_point2 == '>')
|| (f_point1 == '+' && f_point2 == '=') || (f_point1 == '-' && f_point2 == '=')
|| (f_point1 == '+' && f_point2 == '+') || (f_point1 == '*' && f_point2 == '=')
|| (f_point1 == '/' && f_point2 == '=') || (f_point1 == '%' && f_point2 == '=')
|| (f_point1 == '&' && f_point2 == '=') || (f_point1 == '|' && f_point2 == '=')
|| (f_point1 == '^' && f_point2 == '=')) {
word = r.substr(subscript, 2);
subscript++;
}
}
int mm;
// 查找p表
for (mm = 0; mm < p.size(); mm++) {
if (word == p[mm]) {
if_point = 1;
break;
}
}
if (if_point == 1)
{ // 如果匹配成功
if(mm<=24) //单字分界符
result = result + "( " + SD +", "+ word + ")\n";
else //双字分界符
result = result + "( " + SD +", "+ word + ")\n";
}
else { // 匹配失败
cout << "出现未定义符号: " << word << endl;
}
subscript++;
if_point = 0;
break;
}
default:
cout << "\n字符识别异常:无法识别的状态" << endl;
break;
}
}
// 将结果保存在文档中
saveResultsToFile(result, i, C, S, c, p, k, path_out);
}
catch (const std::exception& e)
{
cerr << e.what() << endl;
}
}
输出函数 (saveResultsToFile):
将识别的结果按照类型分别保存到输出文件中。它将分别输出 Token 序列、标识符表、单字符表、字符串表、常数表、运算符表和关键字表。
void saveResultsToFile(const string& result, vector<string> i, vector<string> C, vector<string> S, vector<string> c, vector<string> p, vector<string> k, const string& path_out) {
ofstream outputFile(path_out);
if (outputFile.is_open()) {
outputFile << result;
outputFile.close();
}
}
主要数据结构描述
LexicalAnalyzer 类:
- vector<string> k - 存储关键字表
- vector<string> p - 存储运算符和界符表
- vector<string> i - 存储标识符表
- vector<string> C - 存储单字符表
- vector<string> S - 存储多字符表
- vector<string> c - 存储常数表
- int if_point - 用于标记某个字符是否为操作符或界符
程序结构描述
- startLexicalAnalysis 方法:开始词法分析,进行预处理并调用 scanner 方法进行分析。
- pretreatment 方法:预处理,去除注释和多余的空格。
- scanner 方法:扫描器,用于将经过预处理的字符串转换为 token 序列。
- getListAsString 方法:将存储的字符串向量转换为单个字符串以便保存到输出文件。
- saveResultsToFile 方法:保存结果到输出文件。
完整代码
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <algorithm>
#include <cctype>
using namespace std;
#define ID '0' // 标识符
#define KEY '1' // 关键字
#define SD '2' // 单字分界符
#define DD '3' // 双字分界符
#define STRING '4' // 字符串常量
#define CHAR '5' //字符常量
#define NUMBER '6' //数字
class LexicalAnalyzer {
private:
vector<string> k = { "begin","end","then","int", "for","while", "void", "if", "char", "return", "short", "long", "short", "signed", "unsigned",
"restrict", "struct", "union", "enum", "typedef", "sizeof", "auto", "static", "register", "extern", "const",
"volatile", "break", "goto", "else", "switch", "case", "default", "continue", "do", "double", "float" }; // 关键字表
vector<string> p = { "+", "-", "*", "/", "%", ".", ">", "<", "=", "(", ")", "{", "}", ";", "[", "]", ":", "?", "'",
"\"", ",", "&", "|", "~", "^", "++", "--", "==", ">=", "<=", "!=", "<<", ">>", "!", "&&", "||", "+=", "-=", "*=", "/=",
"%=", "&=", "|=", "^=", ">>=", "<<=", "*", "&" }; // 运算符和界符表
vector<std::string> i; // 标识符表
vector<std::string> C; // 字符常量表
vector<std::string> S; // 字符串常量表
vector<std::string> c; // 常数表
int if_point = 0;
public:
void startLexicalAnalysis(const std::string& path_in, const std::string& path_out) {
string p_in = pretreatment(path_in);
scanner(p_in, path_out);
}
string pretreatment(const string& p_in) {
string r = ""; // 用来保存去除注释后的源程序
try {
std::ifstream C_txt(p_in);
std::stringstream buffer;
buffer << C_txt.rdbuf();
std::string line;
bool InComment = false; // 用于跟踪是否在注释块内
while (getline(buffer, line)) {
// 处理多行注释
if (InComment) {
size_t found = line.find("*/");
if (found != string::npos)
{
line = line.substr(found + 2);
InComment = false; // 出注释块了
}
else
{
continue; // 在多行注释中,跳过当前行
}
}
// 处理单行注释
size_t SingleCommentIndex = line.find("//");
if (SingleCommentIndex != string::npos)
{
line = line.substr(0, SingleCommentIndex);
}
// 处理多行注释的开始
size_t MultiCommentStartIndex = line.find("/*");
if (MultiCommentStartIndex != std::string::npos)
{
size_t MultiCommentEndIndex = line.find("*/");
if (MultiCommentEndIndex != std::string::npos)
{
// 删除多行注释
line = line.substr(0, MultiCommentStartIndex) + line.substr(MultiCommentEndIndex + 2);
}
else
{
// 进入多行注释块
line = line.substr(0, MultiCommentStartIndex);
InComment = true; // 注释块内标识为真
}
}
r += " " + line; // 将每行用空格隔开
}
// 删除多余的空格
r.erase(std::unique(r.begin(), r.end(), [](char a, char b) { return a == ' ' && b == ' '; }), r.end());
r.erase(std::remove(r.begin(), r.end(), '\n'), r.end());
// 去除首尾空格
size_t start = r.find_first_not_of(" \t\n\r");
size_t end = r.find_last_not_of(" \t\n\r");
if (start == string::npos) {
r = ""; // Empty or all spaces
}
else
r = r.substr(start, end - start + 1);
cout << r << endl; // 将预处理后的字符串输出
}
catch (const exception& e)
{
cerr << e.what() << endl;
}
return r; // 返回处理好后的字符串
}
void scanner(const std::string& path_in, const std::string& path_out) {
try {
string result = ""; // 保存token信息
size_t subscript = 0; // 计数
int t, status, pre_subscript; // 分别为:记录单个字符的ASCII码,状态,记录上一个字符的下标
string word; // 形成单词
string r = path_in; // 经预处理后的文本字符流信息
while (subscript < r.length()) {
t = static_cast<int>(r[subscript]); // 将字符转换为ASCII码
if (t == 32)
status = 0; // 空格
else if ((t > 64 && t < 91) || (t > 96 && t < 123)) // 字母
status = 1;
else if (t >= 48 && t <= 57) // 数字
status = 2;
else if (t == 34) // 双引号是字符串
status = 3;
else if (t == 39) // 单引号是单字符
status = 4;
else if ((t >= 33 && t <= 47 && t != 34 && t != 39) || (t >= 58 && t <= 64) || (t >= 91 && t <= 96) || (t >= 123 && t <= 126))
status = 5;
else {
// 非法字符,输出异常信息
cout << "出现非法字符:" << endl;
cout << r[subscript] << ":";
cout << static_cast<int>(r[subscript]) << endl;
break;
}
switch (status) { // 对于每一种状态
case 0: // 空格
subscript++;
break;
case 1: // 字母(单词)
word = "";
pre_subscript = subscript;
while ((t > 64 && t < 91) || (t > 96 && t < 123) || (t > 47 && t < 58)) {
word = word + r[subscript];
subscript++;
if (subscript < r.length())
t = static_cast<int>(r[subscript]);
else
break;
}
// 在这里处理关键字、标识符,添加到 result 和对应的列表中
if (find(begin(k), end(k), word) != end(k)) // 关键字
result = result + "( " + KEY + ", " + word + ")\n";
else //标识符
{
if (std::find(i.begin(), i.end(), word) == i.end())
i.push_back(word);
result = result + "( "+ ID + ", " + word + ")\n";
}
break;
case 2: // 数字
word = "";
pre_subscript = subscript;
while (t > 47 && t < 58) {
word += r[subscript];
subscript++;
if (subscript < r.length())
t = static_cast<int>(r[subscript]);
else
break;
}
if (std::find(c.begin(), c.end(), word) == c.end()) // 判断是否为常数
c.push_back(word);
result = result + "( "+ NUMBER + ", " + word + ")\n";
break;
case 3: { // 字符串
word = "";
pre_subscript = subscript;
subscript++;
t = static_cast<int>(r[subscript]);
while (t != 34) {
word += r[subscript];
subscript++;
if (subscript < r.length())
t = static_cast<int>(r[subscript]);
else
break;
}
subscript++;
if (find(S.begin(), S.end(), word) == S.end()) // 判断是否为字符串
S.push_back(word);
result = result + "( " + STRING + ", " + word + ")\n";
break;
}
case 4: // 字符常量
word = "";
pre_subscript = subscript;
subscript++;
t = static_cast<int>(r[subscript]);
while (t != 39) {
word += r[subscript];
subscript++;
if (subscript < r.length())
t = static_cast<int>(r[subscript]);
else
break;
}
subscript++;
if (find(C.begin(), C.end(), word) == C.end()) // 判断是否为单字符
C.push_back(word);
result = result + "( " + CHAR + ", " + word + ")\n";
break;
case 5: { // 符号 如果是符号,直接将符号取出
word = r.substr(subscript, 1);
if (subscript + 1 < r.length())
t = static_cast<int>(r[subscript + 1]);
if ((t >= 33 && t <= 47 && t != 34 && t != 39) || (t >= 58 && t <= 64) || (t >= 91 && t <= 96) || (t >= 123 && t <= 126)) {
char f_point1 = r[subscript];
char f_point2 = r[subscript + 1];
if ((f_point1 == '&' && f_point2 == '&') || (f_point1 == '+' && f_point2 == '+')
|| (f_point1 == '-' && f_point2 == '-') || (f_point1 == '|' && f_point2 == '|')
|| (f_point1 == '>' && f_point2 == '=') || (f_point1 == '<' && f_point2 == '=')
|| (f_point1 == '=' && f_point2 == '=') || (f_point1 == '!' && f_point2 == '=')
|| (f_point1 == '<' && f_point2 == '<') || (f_point1 == '>' && f_point2 == '>')
|| (f_point1 == '+' && f_point2 == '=') || (f_point1 == '-' && f_point2 == '=')
|| (f_point1 == '+' && f_point2 == '+') || (f_point1 == '*' && f_point2 == '=')
|| (f_point1 == '/' && f_point2 == '=') || (f_point1 == '%' && f_point2 == '=')
|| (f_point1 == '&' && f_point2 == '=') || (f_point1 == '|' && f_point2 == '=')
|| (f_point1 == '^' && f_point2 == '=')) {
word = r.substr(subscript, 2);
subscript++;
}
}
int mm;
// 查找p表
for (mm = 0; mm < p.size(); mm++) {
if (word == p[mm]) {
if_point = 1;
break;
}
}
if (if_point == 1)
{ // 如果匹配成功
if(mm<=24) //单字分界符
result = result + "( " + SD +", "+ word + ")\n";
else //双字分界符
result = result + "( " + SD +", "+ word + ")\n";
}
else { // 匹配失败
cout << "出现未定义符号: " << word << endl;
}
subscript++;
if_point = 0;
break;
}
default:
cout << "\n字符识别异常:无法识别的状态" << endl;
break;
}
}
// 将结果保存在文档中
saveResultsToFile(result, i, C, S, c, p, k, path_out);
}
catch (const std::exception& e)
{
cerr << e.what() << endl;
}
}
// 将vector<String>转换成String用于保存信息写入文档
string getListAsString(const vector<string>& t) {
std::stringstream ss;
for (const auto& item : t) {
ss << item << " ";
}
return ss.str();
}
void saveResultsToFile(const string& result, vector<string> i, vector<string> C, vector<string> S, vector<string> c, vector<string> p, vector<string> k, const string& path_out) {
ofstream outputFile(path_out);
if (outputFile.is_open()) {
outputFile << result;
outputFile.close();
}
}
};
int main() {
std::string path_in = "./lab3_4.txt";
std::string path_out = "./lab3_4_out.txt";
LexicalAnalyzer lexicalAnalyzer;
lexicalAnalyzer.startLexicalAnalysis(path_in, path_out);
return 0;
}
测试
测试用例(部分)如下:
终端运行结果:
词法分析结果(部分):