摘要
词法分析(lexical analysis)是计算机科学中将字符序列转换为单词(Token)序列的过程。进行词法分析的程序或者函数叫作词法分析器(Lexical analyzer,简称Lexer),也叫扫描器(Scanner)。词法分析器一般以函数的形式存在,供语法分析器调用。
计算机系统与人信息交换界面多数是应用高级语言来实现。一个高级语言程序的实现,必须依赖于相应的编译系统。所谓编译程序就是指能够把某一种语言程序转换成另一种与之等价的语言程序。它通常包括五个阶段:词法分析,语法分析,语义分析与中间代码的产生、优化,目标代码的生成。完成计算机翻译过程的关键阶段,它为后面的语法分析、语义分析做好准备,打好基础,以便快速地、高质量地生成目标语言程序。因此词法分析是编译的基础。
词法分析器所处理的对象即词法分析程序的输入数据,实际上是源程序经过编译预处理,去掉多余的符号后而形成的代码,这样给词法分析带来方便。词法分析的过程是线性的从头至尾扫描一遍,复杂度较低,易实现。
词法分析的第一阶段即扫描器,通常基于有限状态自动机。扫描器能够识别其所能处理的标记中可能包含的所有字符序列(单个这样的字符序列即前面所说的“语素”)。例如“整数”标记可以包含所有数字字符序列。很多情况下,根据第一个非空白字符便可以推导出该标记的类型,于是便可逐个处理之后的字符,直到出现不属于该类型标记字符集中的字符(即最长一致原则)。
词法分析器的工作是低级别的分析:将字符或者字符序列转化成记号。在谈论词法分析时,使用术语“词法记号”(简称记号)、“模式”和“词法单元”表示特定的含义。
一、概述
编译程序要对高级语言编写的源程序进行分析和合成,生成目标程序。词法分析是对源程序进行的首次分析,实现词法分析的程序成为词法分析程序(或词法分析器),也称扫描器。 词法分析的功能是,从左到右逐个地扫描源程序的字符串,按照词法规则,识别出单词符号作为输出,对识别过程中发现的词法错误,输出有关的错误信息。由词法分析识别出的单词流是语法分析的输入,语法分析据此判断它们是否构成了合法的句子。由词法分析识别出的常数和由用户定义的名字,分别在常数表和符号表中予以登记,在编译的各个阶段都要频繁的使用符号表。词法分析可以作为单独的一遍,这时词法分析器的输出形成一个输出文件,作为语法分析器的输入文件,词法分析也可以作为语法分析的一个子程序,每当语法分析需要下一个新单词时,就调用词法分析子程序,从输入字符串中识别一个单词后返回。
二、设计内容
(一)目的
利用c++语言设计一款词法分析器,能识别出关键字,标识符,常数,运算符和分界符等单词符号,过滤符等进行过滤,该词法分析器首先扫描源文件,识别出一-系列具有独立意义的基本语法单位一单词, 包括关键字、保留字、标识符、各种常数、各种运算符及界符等。由于我们规定的c++语言程序语句中涉及单词较少,所以在词法分析阶段忽略了单词输入错误的检查,并在扫描后输出单词符号。规定输出的单词符号格式为如下的三元式: (序号,单词,单词类型)。c++语言中定义了属于这五种类型的大量的单词,但是由于预编译器只识别我们自定义的注释,因此预编译器处理的单词集只是c++语言中定义的单词集的一个真子集。
(二)整体框架
设计的类采用单例模式,这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。我们运用了其中的饿汉模式和懒汉模式,饿汉模式是为了加载的时候就立即初始化,并且创建单例对象,它绝对的线程安全,在线程还没有出现以前就实例化了,不可能存才访问安全的问题。懒汉模式是为了被外部调用的时候内部类才会被加载。
(三)设计类
私有:构造函数、析构函数、拷贝构造、对象赋值、静态类指针
共有:静态函数:产生对象并返回对象指针释放对象函数
(四)项目技术
1. 守卫锁lock_guard(C++11)
守卫锁lock_guard是C++11新特性。单例中使用了守卫锁,是为了防止多线程下同时创建多个对象。在创建时调用构造函数自动加锁,出了作用域就调用析构函数解锁。防止在多线程环境下造成死锁现象。在本项目中采用单线程模式,不存在上述问题。
2. 正则表达式(C++11)
正则匹配是一种规则,它用来匹配(进而捕获、替换)字符串。这种规则需要“模式”、“字符串”这两样东西,“模式”根据正则规则,来处理“字符串”。这种规则被许多语言支持,C++11以后才支持正则。C++11支持的正则和其他语言支持的正则有区别。在本项目中使用到了regex_match是全文匹配。
3. 范围遍历auto(C++11)
C++ 11提供了一个特殊版本的for循环,在很多情况下,它都可以简化数组的处理,这就是基于范围的for循环(Rang-Based for Loop)。在使用基于范围的for循环处理数组时,该循环可以自动为数组中的每个元素迭代一次。例如,如果对一个8元素的数组使用基于范围的for循环,则该循环将迭代8次。因为基于范围的for循环可以自动知道数组中的个数,所以不必使用计数器变量控制其迭代,也不必担心数组下标越界的问题。
4. 互斥锁mutex
互斥锁是线程同步最常用的一种方式,通过互斥锁可以锁定一个代码块,被锁定的这个代码块,所有线程只能顺序执行(不能并行处理),这样多线程访问共享资源数据混乱的问题就可以被解决了,需要付出的代价就是执行效率的降低,因为默认临界区多个线程可以并行处理的,现在只能串性处理。在本例中是为了配合守卫锁使用。
5. 迭代器iterator
在C++中有STL标准库,该库中有很多容器。迭代器iterator提供一种方式,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方法。每个容器都有自己的迭代器。我们在判断处使用了迭代器遍历,遍历集合通过返回的true或false来判断是否有元素可以迭代。
6. 文件流对象
文件流是以外存文件为输入输出对象的数据流。输出文件流是从内存流向外存文件的数据,输入文件流是从外存文件流向内存的数据。每一个文件流都有一个内存缓冲区与之对应。请区分文件流与文件的概念,不用误以为文件流是由若干个文件组成的流。文件流本身不是文件,而只是以文件为输入输出对象的流。若要对磁盘文件输入输出,就必须通过文件流来实现。在本项目读取需要解析的源文件和输出的csv文件使用到了。
7. 哈希表map
map是STL的一个关联容器,它提供一对一的hash。
第一个可以称为关键字(key),每个关键字只能在map中出现一次;
第二个可能称为该关键字的值(value);自动建立<key,value>的对应。key和value可以是任意你需要的类型,包括自定义类型。它提供了快速的插入操作和查找操作,无论哈希表总中有多少条数据,插入和查找的时间复杂度都是为O(1),因为哈希表的查找速度非常快,我们通过哈希表可以快速查找key值。底层实现用红黑树。
8. 枚举enum
我们在未定义,常量标识符,预处理处皆用了枚举法,通过确定枚举对象、枚举范围和判定条件,来确定枚举可能的解,验证是否是问题的解。
优点:
1、增加代码的可读性和可维护性
2、和#define定义的标识符比较枚举有类型检查,更加严谨
3、防止了命名污染(封装)
4、便与调试(define会在预编译就处理好了)
5、方便使用,一次可以定义多个常量
9. 常成员函数const
类的成员函数后面加const,表明这个函数不会对这个类对象的数据成员(非静态数据成员)作任何改变。
常函数:
成员函数后加const后称为常函数;常函数不可以修改成员属性;成员属性声明时加关键字mutable后,依然可以修改。
常对象:
声明对象前加const;常对象只能调用常函数。
三、设计要求
- 对单词的构词规则有明确的定义;
- 编写的分析程序能够正确识别源程序中的单词符号;
- 识别出的单词以<种别码,值>的形式保存在符号表中,正确设计和维护符号表;
- 对于源程序中的词法错误,能够做出简单的错误处理,给出简单的错误提示,保证顺利完成整个源程序的词法分析。
四、设计流程图
(一)整体流程图
(二)关键字、标识符模块流程图
(三)预处理模块流程图
(四)运算符分隔符模块流程图
(五)状态转换图
五、程序设计与实现
(一)程序分析
- 定义目标语言的可用符号表
(1) 关键字
2) 分隔符
’;’ ‘,’ ‘{‘ ‘}’ ‘[‘ ‘]’ ‘(‘ ‘)’
(3) 运算符
’+’ ’-’ ’*’ ’/’ ’>’ ’<’ ’=’ ’!’
(4) 过滤器
’ ’ ’\t’ ’\r’ ’\n’
(5) 自定义函数
void initKeyword(void); //初始化关键字 void initSeparator(void); //初始化分隔符 void initOperator(void); //初始化运算符 void initFilter(void); //初始化过滤符 bool openFile(void); //以文本读方式打开文件 bool isFilter(char ch) const; // 判断是否是过滤字符 bool isKeyword(const string& keyword) const;// 判断是否是关键字 bool isSeparater(char ch) const; // 判断是否是分隔符 bool isOperator(char ch) const; // 判断是否是运算符 getIndex(const string& str) const // 读入 pretreatment(char& ch) // 预处理 static LexicalAnalysis* instance();// 对象实例 void DelObject(); // 删除对象 void run(const string& filename); // 运行函数 size_t getIndex(const string& str) const;//获得该字符或者字符串对应的单词符号值 void pretreatment(char& ch);// 判断以#号开头的预处理指令 void keywordOrIdentifier(char& ch);// 判断是关键字还是标识符 void numberConst(char& ch); // 判断是否是数字 void otherSymbol(char& ch); // 判断是运算符还是过滤符还是其他符号
(三)程序结果
1.文件详细图
2.测试用例
3.运行结果图
4.csv结果图
六、结论
加深对词法分析器的工作过程的理解;加强对词法分析方法的掌握;能够采用一种编程语言实现简单的词法分析程序;能够使用自己编写的分析程序对简单的程序段进行词法分析。从左至右逐个字符地对源程序进行扫描,产生一个个单词符号,把字 符串形式的源程序改造成为单词符号串形式的中间程序。
七、心得体会
经过一个星期的编译原理课程设计,在我们团队的配合下,顺利完成该课程设计。通过该课程设计,收获颇多。
一、对实验原理有更深的理解通过该课程设计,掌握了什么是编译程序,编译程序工作的基本过程及其各阶段的基本任务,熟悉了编译程序总流程框图,了解了编译程序的生成过程、构造工具及其相关的技术对课本上的知识有了更深的理解,课本上的知识师机械的,表面的。通过把该算法的内容,算法的执行顺序在计算机上实现,把原来以为很深奥的书本知识变的更为简单,对实验原理有更深的理解。
二、对该理论在实践中的应用有深刻的理解 通过把该算法的内容,算法的执行顺序在计算机上实现,知道和理解了该理论在计算机中是怎样执行的,对该理论在实践中的应用有深刻的理解。
三、激发了学习的积极性 通过该课程设计,全面系统的理解了编译原理程序构造的一般原理和基本实现方法。把死板的课本知识变得生动有趣,激发了学习的积极性。把学过的计算机编译原理的知识强化,能够把课堂上学的知识通过自己设计的程序表示出来,加深了对理论知识的理解。
四、以前对与计算机操 作系统的认识是模糊的,概念上的,现在通过自己动手做实验,从实践上认识了操作系统是如何处理命令的,如何协调计算机内部各个部件运行,对计算机编译原理的认识更加深刻。课程设计中程序比较复杂,在调试时应该仔细,在程序调试时,注意指针,将不必要的命令去除。
五、在这次课程设计中,我就是按照实验指导的思想来完成。加深了理解文件系统的内部功能及内部实现,培养实践动手能力和程序开发能力的目的。完成了这次词法分析的程序设计,我们收获很多,也体会很多,要学好一门学科,没有刻苦钻研的精神是不行的,只有在不断的尝试中,不断经历失败,然后又不断的尝试才能获得成功。经过无数次的修改从而使程序更全面,更透彻。在调试过程中学习的知识得到了完善和补充,对词法分析器的理解更进一步。这次的程序训练培养了我实际分析问题、编程和动手能力,培养我们的独立思考、求异思维,创新能力,使我们获得更多的知识和更强的能力。
附录
LexicalAnalysis.h /*** * @file : LexicalAnalysis.cpp * @brief : 词法分析主函数 * @autor : 张梦 * @date : 2021-12-10 */ #define _CRT_SECURE_NO_WARNINGS 1 // Microsoft会推荐使用一些他们自己的函数,不想使用就需要加一个宏 #pragma once // 使该头文件只被包含一次 #include <iostream> // C++标准I/O流头文件 #include <string> // 字符串头文件 #include <map> // map/multimap属于关联式容器,底层结构是二叉树实现。红黑树,默认升序 #include <fstream> // 文件输入流 #include <cctype> // 字符判断头文件 #include <regex> // 正则表达式 C++11 #include <thread> // 多线程函数 #include <mutex> // 互斥体 using namespace std; // 命名空间 #define OUTPUTFILENAME "output.csv" enum OtherSymbol { IDENTIFIER = 601, // 标识符值 CONSTANT = 602, // 常数值 PRETREATMENT = 603, // 预处理 }; /** * @brief 对代码进行简单的词法分析 * 采用单例模式,对类进行管理 */ class LexicalAnalysis { public: /** * @brief 单例模式的对象接口 * @param [in] filename 需要解析的文件名 * @return 该对象(this) */ static LexicalAnalysis* instance(); /** * @brief 析构函数 * 用于释放程序员手动开辟的空间 如new malloc等 * @return */ void DelObject(); /** * @brief 核心运行函数 解析字符 * @param [in] filename 需要解析的文件名 * @return */ void run(const string& filename); private: /** * @brief 构造函数 * 用于初始化成员变量 * @return */ LexicalAnalysis(); /** * @brief 析构函数 * 用于释放程序员手动开辟的空间 如new malloc等 * @return */ ~LexicalAnalysis(); /** * @brief 初始化关键字哈希表 * @return */ void initKeyword(void); /** * @brief 初始化分隔符哈希表 * @return */ void initSeparator(void); /** * @brief 初始化运算符哈希表 * @return */ void initOperator(void); /** * @brief 初始化过滤器哈希表 * @return */ void initFilter(void); /** * @brief 根据所给的文件路径打开文件 * @return 返回说明 * <em>false</em> 打开失败 * <em>true</em> 打开成功 */ bool openFile(void); /** * @brief 初始化过滤器哈希表 * @return */ bool isFilter(char ch) const; /** * @brief 判断字符串是否为关键字 * @param [in] keyword 需要判断的字符串 * @return 返回说明 * <em>false</em> 否 * <em>true</em> 是 */ bool isKeyword(const string& keyword) const; /** * @brief 判断字符是否为分隔符 * @param [in] ch 需要判断的字符 * @return 返回说明 * <em>false</em> 否 * <em>true</em> 是 */ bool isSeparater(char ch) const; /** * @brief 判断字符是否为运算符 * @param [in] ch 需要判断的字符 * @return 返回说明 * <em>false</em> 否 * <em>true</em> 是 */ bool isOperator(char ch) const; /** * @brief 获得该字符或者字符串对应的单词符号值 * @param [in] str 需要判断的字符或者字符串 * @return 返回单词符号值 */ size_t getIndex(const string& str) const; /** * @brief 判断以#号开头的预处理指令 * @param [in] ch 当前从文件中读取的字符 * @return */ void pretreatment(char& ch); /** * @brief 判断是关键字还是标识符 * @param [in] ch 当前从文件中读取的字符 * @return */ void keywordOrIdentifier(char& ch); /** * @brief 判断是否是数字 * 目前只能识别整型常量 * @param [in] ch 当前从文件中读取的字符 * @return */ void numberConst(char& ch); /** * @brief 判断是运算符还是过滤符还是其他符号 * @param [in] ch 当前从文件中读取的字符 * @return */ void otherSymbol(char& ch); private: map<int, string> m_keyword; // 关键字集合 map<int, char> m_separator; // 分隔符集合 map<int, char> m_operator; // 运算符集合 map<int, char> m_filter; // 过滤符集合 static LexicalAnalysis* sm_obj; // (single member)类对象 string m_filename; // 保存要解析的文件名 ifstream ifs; // 文件流对象 ofstream ofs; // 用于输出流对象 }; LexicalAnalysis.cpp文件: #include "LexicalAnalysis.h" //LexicalAnalysis* LexicalAnalysis::sm_obj = nullptr; //懒汉模式,以时间换空间 LexicalAnalysis* LexicalAnalysis::sm_obj = new LexicalAnalysis; //饿汉模式,以空间换时间 LexicalAnalysis::LexicalAnalysis() { initKeyword(); // 初始化关键字哈希表 initSeparator();// 初始化分隔符哈希表 initOperator(); // 初始化运算符哈希表 initFilter(); // 初始化过滤器哈希表 } void LexicalAnalysis::initKeyword(void) { m_keyword.insert(make_pair(1, "bool")); // 布尔类型 m_keyword.insert(make_pair(2, "true")); // 布尔类型中的一种 真也是非0 m_keyword.insert(make_pair(3, "false")); // 布尔类型中的一种 假也为0 m_keyword.insert(make_pair(4, "break")); // break(中断、跳出),用在switch语句或者循环语句中 m_keyword.insert(make_pair(5, "continue")); // continue(继续)关键字用于循环结构 m_keyword.insert(make_pair(6, "switch")); // switch(转换)类似于 if-else-if 语句,是一种多分枝语句 m_keyword.insert(make_pair(7, "case")); // 用于 switch 语句中,用于判断不同的条件类型。 m_keyword.insert(make_pair(8, "default")); // default(默认、缺省)用于 switch 语句。当 m_keyword.insert(make_pair(9, "try")); // try(尝试)用于实现 C++ 的异常处理机制。 m_keyword.insert(make_pair(10, "catch")); // catch 和 try 语句一起用于异常处理。 m_keyword.insert(make_pair(11, "throw")); // throw(抛出)用于实现 C++ 的异常处理机制 m_keyword.insert(make_pair(12, "char")); // char(字符,character)类型,C++ 中的基本数据结构 m_keyword.insert(make_pair(13, "short")); // short(短整型,short integer) m_keyword.insert(make_pair(14, "int")); // int(整型,integer),C++ 中的基本数据结构 m_keyword.insert(make_pair(15, "long")); // long(长整型,long integer),C++ 中的基本数据结构 m_keyword.insert(make_pair(16, "float")); // float(浮点数),C++ 中的基本数据结构,精度小于 double。 m_keyword.insert(make_pair(17, "double")); // double(双精度)类型,C++ 中的基本数据结构 m_keyword.insert(make_pair(18, "unsigned")); // unsigned(无符号),表明该类型是无符号数 m_keyword.insert(make_pair(19, "signed")); // signed(有符号),表明该类型是有符号数,和 unsigned 相反。 m_keyword.insert(make_pair(20, "do")); // do-while是一类循环结构 m_keyword.insert(make_pair(21, "if")); // if(如果),C++ 中的条件语句之一,可以根据后面的 bool 类型的值选择进入一个分支执行。 m_keyword.insert(make_pair(22, "else")); // else 紧跟在 if 后面,用于对 if 不成立的情况的选择。 m_keyword.insert(make_pair(23, "return")); // return(返回)用于在函数中返回值 m_keyword.insert(make_pair(24, "for")); // for 是 C++ 中的循环结构之一。 m_keyword.insert(make_pair(25, "while")); // C++中的循环结构之一,表示一种入口条件循环。 m_keyword.insert(make_pair(26, "void")); // void(空的),可以作为函数返回值,表明不返回任何数据 m_keyword.insert(make_pair(27, "enum")); // enum(枚举)类型,给出一系列固定的值,只能在这里面进行选择一个。 m_keyword.insert(make_pair(28, "goto")); // goto(转到),用于无条件跳转到某一标号处开始执行。 m_keyword.insert(make_pair(29, "register")); // register(寄存器)声明的变量称着寄存器变量 m_keyword.insert(make_pair(30, "sizeof")); // sizeof 运算符获得该数据类型占用的字节数。 m_keyword.insert(make_pair(31, "static")); // static(静态的) m_keyword.insert(make_pair(32, "struct")); // struct(结构)类型,类似于 class 关键字 m_keyword.insert(make_pair(33, "union")); // union(联合),类似于 enum m_keyword.insert(make_pair(34, "volatile")); // volatile(不稳定的)限定一个对象可被外部进程(操作系统、硬件或并发线程等)改变 m_keyword.insert(make_pair(35, "auto")); // auto自动类型推导(C++11) m_keyword.insert(make_pair(36, "this")); // this 返回调用者本身的指针。 m_keyword.insert(make_pair(37, "const")); // const(常量的,constant)所修饰的对象或变量不能被改变 m_keyword.insert(make_pair(38, "namespace")); // namespace(命名空间)用于在逻辑上组织类,是一种比类大的结构。 m_keyword.insert(make_pair(39, "new")); // new(新建)用于新建一个对象 m_keyword.insert(make_pair(40, "using")); // 表明使用 namespace。 m_keyword.insert(make_pair(41, "nullptr")); // C++的空类型 m_keyword.insert(make_pair(42, "private")); // private(私有的),C++ 中的访问控制符 m_keyword.insert(make_pair(43, "protected")); // protected(受保护的),C++ 中的访问控制符 m_keyword.insert(make_pair(44, "public")); // public(公有的),C++ 中的访问控制符 m_keyword.insert(make_pair(45, "class")); // class(类)是 C++ 面向对象设计的基础。使用 class 关键字声明一个类。 m_keyword.insert(make_pair(46, "delete")); // delete(删除)释放程序动态申请的内存空间 } void LexicalAnalysis::initSeparator(void) { m_separator.insert(make_pair(101, ';')); // ;分号 m_separator.insert(make_pair(102, ',')); // ,逗号 m_separator.insert(make_pair(103, '{')); // {左大括号 m_separator.insert(make_pair(104, '}')); // }右大括号 m_separator.insert(make_pair(105, '[')); // [左中括号 m_separator.insert(make_pair(106, ']')); // ]右中括号 m_separator.insert(make_pair(107, '(')); // 左圆括号 m_separator.insert(make_pair(108, ')')); // )右圆括号 m_separator.insert(make_pair(109, '\"')); // 双引号 m_separator.insert(make_pair(110, '\'')); // 单引号 m_separator.insert(make_pair(111, ':')); // 冒号 } void LexicalAnalysis::initOperator(void) { m_operator.insert(make_pair(201, '+')); // +加号 m_operator.insert(make_pair(202, '-')); // -减号 m_operator.insert(make_pair(203, '*')); // *乘号 m_operator.insert(make_pair(204, '/')); // /除号 m_operator.insert(make_pair(205, '>')); // >大于号 m_operator.insert(make_pair(206, '<')); // <小于号 m_operator.insert(make_pair(207, '=')); // =赋值运算符 m_operator.insert(make_pair(208, '!')); // 不等于号 } void LexicalAnalysis::initFilter(void) { m_filter.insert(make_pair(201, ' ')); // 空格 m_filter.insert(make_pair(202, '\t')); // 制表符 m_filter.insert(make_pair(203, '\r')); // 退格符 m_filter.insert(make_pair(204, '\n')); // 换行符 } bool LexicalAnalysis::openFile(void) { ifs.open(m_filename, ios::in); //以文本读方式打开文件 ofs.open(OUTPUTFILENAME, ios::trunc); //以文件写的方式打开文件 return true == ifs.is_open() && true == ofs.is_open(); // 判断文件打开是否成功 true成功 false失败 } bool LexicalAnalysis::isFilter(char ch) const { for (const auto& elem : m_filter) // C++11新特性 范围遍历 { if (elem.second == ch) // 当映射表中的value与传入的ch一致是返回true { return true; // 表示查找到了 } } return false; // 表示未查找到 } bool LexicalAnalysis::isKeyword(const string& keyword) const { // 迭代器遍历 for (map<int, string>::const_iterator iter = m_keyword.cbegin(); iter != m_keyword.cend(); ++iter) { if (iter->second == keyword) // 当映射表中的value与传入的keyword一致是返回true { return true; // 表示查找到了 } } return false; // 表示未查找到 } bool LexicalAnalysis::isSeparater(char ch) const { for (const auto& elem : m_separator) // C++11新特性 范围遍历 { if (elem.second == ch) // 当映射表中的value与传入的ch一致是返回true { return true; // 表示查找到了 } } return false; // 表示未查找到 } bool LexicalAnalysis::isOperator(char ch) const { for (const auto& elem : m_operator)// C++11新特性 范围遍历 { if (elem.second == ch)// 当映射表中的value与传入的ch一致是返回true { return true;// 表示查找到了 } } return false;// 表示未查找到 } size_t LexicalAnalysis::getIndex(const string& str) const { for (const auto& elem : m_keyword)// C++11新特性 范围遍历 查找关键字 { if (elem.second == str)// 比较映射表中的value与传入的str一致 { return elem.first; // 查找到就返回当前key } } for (const auto& elem : m_separator)// C++11新特性 范围遍历 查找分隔符 { if (elem.second == str.at(0))// 比较映射表中的value与传入的str一致 { return elem.first;// 查找到就返回当前key } } for (const auto& elem : m_operator)// C++11新特性 范围遍历 查找运算符 { if (elem.second == str.at(0))// 比较映射表中的value与传入的str一致 { return elem.first;// 查找到就返回当前key } } for (const auto& elem : m_filter)// C++11新特性 范围遍历 查找过滤符 { if (elem.second == str.at(0))// 比较映射表中的value与传入的str一致 { return elem.first;// 查找到就返回当前key } } return -1;//所有都没找到时,返回-1 表示库中匹配不上 } void LexicalAnalysis::pretreatment(char& ch) { /* 预处理指令较多 #include #define #pragma once #undef #if #endif 等等 目前只支持:#include #pragma one */ // 处理#pragma once 避免同一个头文件被包含多次 但是#pragma string str; // 用作拼接字符的容器 string buffer; // 用作读取字符串的容器 ifs >> buffer; //读的数据以空格换行符tab分隔 if ("pragma" == buffer)// 判断读出的字符串是否为pragma { str += ch + buffer + " "; // 因为#pramga once 中间本身有空格 我们用流去读就会把空格丢弃掉所以需要手动增加 ifs >> buffer; // 从文件流对象中读取一个字符串 str += buffer; // 拼接 if ("once" == buffer) // 判断读出的字符串是否为once { cout << OtherSymbol::PRETREATMENT << "\t->\t" << str << "\t\t预处理指令" << endl; ofs << OtherSymbol::PRETREATMENT <<","<< str << ",预处理指令" << endl; } else { cout << "error" << "\t->\t" << str << "\t\t无法识别" << endl; ofs << "error" << "," << str << ",无法识别" << endl; } ch = '\0'; // 将ch置为\0 让继续从文件中循环取值 return; } // C++11 强制类型转换 用于数值类型转换 主要用于数据丢失 ifs.seekg(-static_cast<long int>(buffer.size()), ios::cur); // 处理#include 分俩种情况 <>和" " int sign = 0; // 由于俩双引号相同 所以这里记录是否为第二个引号 // '>' != ch 必须在读取前,否则>读取失败 do { if ('"' == ch) // 如果当前字符是双引号那么sign就增一 { ++sign; } str += ch; // 拼接 // 当前字符不为>且不是第二个双引号且不是换行就继续读取 } while ('>' != ch && (ch = ifs.get()) != EOF && 2 != sign && '\n' != ch); /** * @brief 关于正则表达式 * regex_match 全文匹配,即要求整个字符串符合匹配规则,返回true或false * \s表示匹配空格 *表示匹配任意个数 [a-zA-z]表示 一个集合 +表示最少匹配一次 * \s*#include\s*<\s*[a-zA-Z.]+\s*> 匹配<>格式 * \s*#include\s*\"\s*[a-zA-Z.]+\s*\" 匹配""格式 */ if (true == regex_match(str, regex("\\s*#include\\s*<\\s*[a-zA-Z.]+\\s*>")) || true == regex_match(str, regex("\\s*#include\\s*\"\\s*[a-zA-Z.]+\\s*\""))) { cout << OtherSymbol::PRETREATMENT << "\t->\t" << str << "\t\t预处理指令" << endl; ofs << OtherSymbol::PRETREATMENT << "," << str << ",预处理指令" << endl; if (0 == sign) // 当是<>格式时 需要新获取字符 { ch = '\0'; // 将ch置为\0 让继续从文件中循环取值 } } else { cout << "error" << "\t->\t" << str << "\t\t\t无法识别" << endl; ofs << "error" << "," << str << ",无法识别" << endl; ch = '\0'; // 将ch置为\0 让继续从文件中循环取值 } } void LexicalAnalysis::keywordOrIdentifier(char& ch) { string str; // 用作拼接字符的容器 do { str += ch; // 第一个字符 必须是以下划线或者字母开头 之后就可以有数字了 这个字符串只要不是关键字 那么就是标识符 } while ((ch = ifs.get()) != EOF && (0 != isalpha(ch) || '_' == ch || 0 != isdigit(ch))); if (true == isKeyword(str)) // 关键字 { cout << getIndex(str) << "\t->\t" << str << "\t\t关键字" << endl; ofs << getIndex(str) << "," << str << ",关键字" << endl; } else // 标识符 { cout << OtherSymbol::IDENTIFIER << "\t->\t" << str << "\t\t标识符" << endl; ofs << OtherSymbol::IDENTIFIER << "," << str << ",标识符" << endl; } } void LexicalAnalysis::numberConst(char& ch) { string str; // 用作拼接字符的容器 do { str += ch; // 整型数字判断比较简单 只需要判断是否为数字即可 } while ((ch = ifs.get()) != EOF && 0 != isdigit(ch)); //ifs.seekg(-1L, ios::cur); cout << OtherSymbol::CONSTANT << "\t->\t" << str << "\t\t整型常量" << endl; ofs << OtherSymbol::CONSTANT << "," << str << ",整型常量" << endl; } void LexicalAnalysis::otherSymbol(char& ch) { string str;// 用作拼接字符的容器 if (true == isOperator(ch)) // 是运算符 { str += ch; cout << getIndex(str) << "\t->\t" << str << "\t\t运算符" << endl; ofs << getIndex(str) << "," << str << ",运算符" << endl; } else if (true == isSeparater(ch)) // 是分隔符 { str += ch; cout << getIndex(str) << "\t->\t" << str << "\t\t分隔符" << endl; ofs << getIndex(str) << "," << str << ",分隔符" << endl; } else // 识别不了 { str += ch; cout << "error" << "\t->\t" << str << "\t\t无法识别" << endl; ofs << "error" << "," << str << ",无法识别" << endl; } ch = '\0'; // 将ch置为\0 让继续从文件中循环取值 } LexicalAnalysis* LexicalAnalysis::instance() { mutex g_m; // 创建一个锁 // 双重校验锁 if (nullptr == sm_obj)//增加效率 { /* lock_guard 自动加解锁 守卫锁,也叫自解锁。防止异常死锁 作用: 加锁防止多线程同时创建造成内存泄漏 介绍: 在创建时调用构造函数自动加锁出了作用域就调用析构函数解锁 也可以用 下面方法 g_mutex.lock(); ...代码 g_mutex.unlock(); */ lock_guard<mutex> lg(g_m); if (nullptr == sm_obj) { sm_obj = new LexicalAnalysis(); // 创建当前对象 } } return sm_obj; // 返回当前对象 } LexicalAnalysis::~LexicalAnalysis() { // 析构函数 本类因为使用了单例模式 // 所以系统默认的析构函数将不再使用 将其所有功能移植到DelObject函数中 } void LexicalAnalysis::DelObject() { if (LexicalAnalysis::sm_obj != nullptr) // 当当前对象还存在时 { delete sm_obj; // 释放该对象 sm_obj = nullptr; // 并将对象指针置为空 安全起见 } // 关闭文件 ifs.close(); ofs.close(); } void LexicalAnalysis::run(const string& filename) { this->m_filename = filename; // 保存从参数传入的文件路径 if (false == openFile()) // 判断文件是否被打开 预防性编程 提高程序的鲁棒性 { cout << "error:打开文件失败!" << endl; return; } cout << "****************************************************" << endl; cout << "***************** 词法分析如下 *******************" << endl; ofs << "符号码,单词,类别" << endl; string str; // 用于拼接字符 char ch = 0; // 每次读取字符的保存点 while (false == ifs.eof()) // 判断当前是否读完 { str = ""; if (0 == ch) // 当前ch以被解析 需要新获取值 { ch = ifs.get(); // 从文件流对象中读取一个字符 if (true == ifs.eof()) // 判断流对象是否达到末端 { break; } } if (true == isFilter(ch)) // 过滤符号 { ch = '\0'; // 将ch置为\0 让继续从文件中循环取值 } else if ('#' == ch) // 预处理 #include #define { pretreatment(ch); // 调用处理预处理的函数 } else if (0 != isalpha(ch) || '_' == ch) //判断是否为关键字 或者标识符 { keywordOrIdentifier(ch); // 调用处理关键字 或者标识符的函数 } else if (0 != isdigit(ch)) // 是否为数字 目前只能识别整数 { numberConst(ch); // 调用处理数字的函数 } else //其他符号 { otherSymbol(ch); // 处理其他符号 } } cout << "***************** 词法分析完毕 *******************" << endl; cout << "****************************************************" << endl; cout << "csv格式数据导出成功!" << endl; } Main.cpp文件: #include "LexicalAnalysis.h" int main() { string filename; cout << "请输入源文件名(包括路径和后缀):"; cin >> filename; LexicalAnalysis::instance()->run(filename); LexicalAnalysis::instance()->DelObject(); return 0; }