文章目录
零、相关介绍
词法分析器(Scanner)是编译程序中的第一个阶段,它的主要任务是将源代码中的字符序列转换成一系列的单词符号(Token)。以下是关于词法分析器构造的详细解释:
1、词法分析器概述
定义与作用
- 词法分析器是编译器的前端组件,负责读取源代码,识别出其中的单词符号。
- 这些符号包括关键字、标识符、常量、运算符等,它们是语法分析的基础。
工作原理
- 词法分析器通常以一个接一个的方式处理输入字符,将它们组合成单词符号。
- 它通过构建一个词汇表来识别哪些字符串可以构成合法的单词符号。
2、词法单元的识别
正则表达式
- 使用正则表达式定义单词符号的格式,如标识符可能由字母、数字和下划线组成。
- 正则表达式为词法分析器提供了一种高效识别单词符号的方法。
有穷自动机(NFA/DFA)
- 将正则表达式转换为有穷自动机,特别是确定性有穷自动机(DFA),以实现高效的词法分析。
- DFA可以确保每个状态迁移都是唯一的,从而快速识别出单词符号。
词法单元的模式匹配
- 词法分析器通过模式匹配来识别输入字符流中的单词符号。
- 它逐个读取字符,直到识别出一个单词符号或遇到非法字符。
3、词法分析器的输出
Token序列
- 词法分析器输出的Token序列包含了源代码中的所有单词符号。
- 每个Token通常包含一个类型(如变量名、关键字等)和相关的属性值(如变量名的名称)。
错误处理
- 词法分析器在遇到无法识别的字符序列时会生成错误信息。
- 它需要能够从错误中恢复,以便继续分析源代码的后续部分。
与语法分析器的交互
- 词法分析器通常与语法分析器紧密协作,以便于两者可以共享数据结构和提高性能。
- 它们之间的交互通常是通过生成的Token序列来实现的。
4、词法分析器的实现技术
扫描算法
- 词法分析器可以使用不同的扫描算法,如回溯扫描算法和预测扫描算法。
- 这些算法决定了词法分析器如何处理输入字符和识别单词符号。
实现工具
- 词法分析器可以使用专门的工具如Flex(快速词法分析器生成器)来生成。
- 这些工具可以根据定义的词汇表和规则自动生成词法分析器的代码。
优化技术
- 为了提高效率,词法分析器可以实现不同的优化技术,如预处理和缓存。
- 这些技术可以减少不必要的计算和提高词法分析的速度。
5、实际应用与挑战
编程语言支持
- 不同的编程语言可能有不同的词法规则,词法分析器需要能够适应这些差异。
- 它必须能够处理不同编程语言的语法和语义特性。
性能要求
- 词法分析器需要在保证准确性的同时提供高性能的分析速度。
- 它的性能直接影响到整个编译器的效率。
错误恢复
- 词法分析器需要具备强大的错误恢复能力,以便在遇到错误时能够提供有用的反馈。
- 它应该能够帮助程序员快速定位和修复源代码中的错误。
总之,构造一个高效且准确的词法分析器是编译原理中的一个基本而重要的任务。通过理解其基本原理和实现技术,可以为编程语言设计和实现高效、可靠的编译器。
一、 实验目的和要求
设计、编制、调试一个词法分析程序,对单词进行识别和编码,加深对词法分析原理的理解。
二、实验环境(实验设备)
硬件:微型计算机
软件:Windows 操作系统、Visual Studio 2019
三、实验原理及内容
(一)设计概要
1、C++语言子集
(1) 关键字:
void、int、main、return、if、else
(2) 运算符和界限符:
+、-、*、/、%、=
;、(、)、[、]、{、}
(3) 整型常数(INT)和标识符(ID)通过正规文法定义
:= |
:= letter||(||)
2、单词及编码
基本符号 | 类型 | 类型说明 |
---|---|---|
void | 1 | 关键字 |
int | 1 | 关键字 |
main | 1 | 关键字 |
return | 1 | 关键字 |
if | 1 | 关键字 |
else | 1 | 关键字 |
; | 2 | 界限符 |
( | 2 | 界限符 |
) | 2 | 界限符 |
[ | 2 | 界限符 |
] | 2 | 界限符 |
{ | 2 | 界限符 |
} | 2 | 界限符 |
+ | 3 | 运算符 |
- | 3 | 运算符 |
* | 3 | 运算符 |
/ | 3 | 运算符 |
% | 3 | 运算符 |
= | 3 | 运算符 |
标识符 | 4 | 标识符 |
整常数 | 5 | 常量 |
3、状态转换图
(二)实现分析
1、代码
#include <iostream>
#include<fstream>
#define KEYWORD 1
#define DELIMITER 2
#define OPERATOR 3
#define IDENTIFIER 4
#define CONSTINTEGRAL 5
#define ELSE 0
// 关键词列表,界限符列表、运算符列表
std::string keyWord[] = { "void", "int", "char", "main", "return", "if", "else"};
std::string delimiter[] = { "(", ")", "[", "]", "{", "}", ";", "," };
std::string operator0[] = { "+", "-", "*", "/", "%", "=" };
// 检测 char 中字符是否为字母
bool Letter(char c)
{
if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')
{
return true;
}
return false;
}
// 检测单词是否为关键字
bool KeyWord(std::string str)
{
for (std::string p : keyWord)
{
if (p == str)
return true;
}
return false;
}
// 判断单词是否是界限符
bool Delimiter(std::string str)
{
for (std::string p : delimiter)
{
if (p == str)
return true;
}
return false;
}
// 判断单词是否是运算符
bool Operator(std::string str)
{
for (std::string p : operator0)
{
if (p == str)
return true;
}
return false;
}
// 判断字符是否为数字
bool Digit(char c)
{
if (c >= '0' && c <= '9')
{
return true;
}
return false;
}
// 由 token 查保留字表,若 token 中字符串为保留字符则返回其类别编码,否则返回值为0
int Reserve(std::string str)
{
int type = ELSE;
if (KeyWord(str))
type = KEYWORD;
else if (Delimiter(str))
type = DELIMITER;
else if (Operator(str))
type = OPERATOR;
else if (Digit(str[0]))
type = CONSTINTEGRAL;
// 标识符
else if (str[0] == '_' || Letter(str[0]))
{
type = IDENTIFIER;
{
for (int i = 1; i < str.length(); i++)
{
// 若不符合标识符规则,则type=0
if (!(str[i] == '_' || Letter(str[i]) || Digit(str[i])))
{
type = ELSE;
break;
}
}
}
}
else
type = ELSE;
return type;
}
// 读取源代码文本文件
std::string ReadFile(std::string url)
{
std::string token = "";
char ch;
std::ifstream infile;
infile.open(url, std::ios::in);
if (!infile.is_open())
{
std::cout << "文件无法打开!";
return "";
}
infile >> ch;
while (!infile.eof())
{
token = token + ch;
infile >> ch;
}
infile.close();
return token;
}
int main()
{
// 读取源代码文本文件,将空格和回车删除
std::string token = ReadFile("code.txt");
// 打印处理后的源代码
std::cout << "token = " << token << std::endl;
// str 存入一个单词
std::string str = "";
int i = 0;
// 依次遍历源代码中的每一个字符
while(i < token.length())
{
str = "";
// 数字开头
if (Digit(token[i]))
{
while (Digit(token[i]))
str = str + token[i++];
// 打印分类结果
std::cout << "(" << Reserve(str) << ", " << "\"" << str << "\"" << ")" << std::endl;
}
// 下划线或者字母开头的单词,即标识符或者关键字
else if (token[i] == '_' || Letter(token[i]))
{
while (token[i] == '_' || Letter(token[i]) || Digit(token[i]))
{
str = str + token[i++];
// 如果是关键字,立即结束
if (Reserve(str) == KEYWORD)
break;
}
std::cout << "(" << Reserve(str) << ", " << "\"" << str << "\"" << ")" << std::endl;
}
// 运算符
else {
str = str + token[i++];
std::cout << "(" << Reserve(str) << ", " << "\"" << str << "\"" << ")" << std::endl;
}
}
}
1、时间复杂度分析
时间复杂度为O(n)
(三)结果分析
1、 实验使用的源代码文本文件内容如下
2、实验结果
3、结果分析
各种单词均识别分类正确,实验正确
实 验 报 告
四、实验小结(包括问题和解决方法、心得体会、意见与建议等)
(一)实验中遇到的主要问题及解决方法
1、 在编译时,只有第一个标识符识别正确,从第二个标识符开始被识别成非法单词,后来经过调试,发现是在循环过程中,应该首先假设下划线或者字母开头的任意单词是标识符,然后再判断单词中是否出现其他非法字符
2、 在读取源代码文本文件时,无法读取到最后一个字符,后来通过调试,发现读取每个字符时,不能使用string类型变量保存读取到的字符,改用char类型变量读取后,能够正常读取文件内所有的字符。
(二)实验心得
1、熟悉了词法分析的过程
2、加深了对编译器分析源代码词法的理解