编译原理课设-设计一个词法分析器

2023-10-27

设计课设时时间紧凑,难免有些错误,文末还有完整的word可以直接下载使用,也可以直接私信我发你

摘要

词法分析(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. 对于源程序中的词法错误,能够做出简单的错误处理,给出简单的错误提示,保证顺利完成整个源程序的词法分析。

四、设计流程图

(一)整体流程图
在这里插入图片描述

(二)关键字、标识符模块流程图
在这里插入图片描述

(三)预处理模块流程图
在这里插入图片描述

(四)运算符分隔符模块流程图
在这里插入图片描述

(五)状态转换图
在这里插入图片描述

五、程序设计与实现

(一)程序分析

  1. 定义目标语言的可用符号表
    (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;
}

链接:https://download.csdn.net/download/qq_45254369/72083545

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

编译原理课设-设计一个词法分析器 的相关文章

  • 我如何才能等待多个事情

    我正在使用 C 11 和 stl 线程编写一个线程安全队列 WaitAndPop 方法当前如下所示 我希望能够将一些内容传递给 WaitAndPop 来指示调用线程是否已被要求停止 如果 WaitAndPop 等待并返回队列的元素 则应返回
  • 以文化中立的方式将字符串拆分为单词

    我提出了下面的方法 旨在将可变长度的文本拆分为单词数组 以进行进一步的全文索引处理 删除停止词 然后进行词干分析 结果似乎不错 但我想听听关于这种实现对于不同语言的文本的可靠性的意见 您会建议使用正则表达式来代替吗 请注意 我选择不使用 S
  • 为什么 C# Array.BinarySearch 这么快?

    我已经实施了一个很简单用于在整数数组中查找整数的 C 中的 binarySearch 实现 二分查找 static int binarySearch int arr int i int low 0 high arr Length 1 mid
  • WCF RIA 服务 - 加载多个实体

    我正在寻找一种模式来解决以下问题 我认为这很常见 我正在使用 WCF RIA 服务在初始加载时将多个实体返回给客户端 我希望两个实体异步加载 以免锁定 UI 并且我想利用 RIA 服务来执行此操作 我的解决方案如下 似乎有效 这种方法会遇到
  • 在结构中使用 typedef 枚举并避免类型混合警告

    我正在使用 C99 我的编译器是 IAR Embedded workbench 但我认为这个问题对于其他一些编译器也有效 我有一个 typedef 枚举 其中包含一些项目 并且我向该新类型的结构添加了一个元素 typedef enum fo
  • ASP.NET MVC:这个业务逻辑应该放在哪里?

    我正在开发我的第一个真正的 MVC 应用程序 并尝试遵循一般的 OOP 最佳实践 我正在将控制器中的一些简单业务逻辑重构到我的域模型中 我最近一直在阅读一些内容 很明显我应该将逻辑放在域模型实体类中的某个位置 以避免出现 贫血域模型 反模式
  • 查找c中结构元素的偏移量

    struct a struct b int i float j x struct c int k float l y z 谁能解释一下如何找到偏移量int k这样我们就可以找到地址int i Use offsetof 找到从开始处的偏移量z
  • 从Web API同步调用外部api

    我需要从我的 Web API 2 控制器调用外部 api 类似于此处的要求 使用 HttpClient 从 Web API 操作调用外部 HTTP 服务 https stackoverflow com questions 13222998
  • HTTPWebResponse 响应字符串被截断

    应用程序正在与 REST 服务通信 Fiddler 显示作为 Apps 响应传入的完整良好 XML 响应 该应用程序位于法属波利尼西亚 在新西兰也有一个相同的副本 因此主要嫌疑人似乎在编码 但我们已经检查过 但空手而归 查看流读取器的输出字
  • Clang 3.1 + libc++ 编译错误

    我已经构建并安装了 在前缀下 alt LLVM Clang trunk 2012 年 4 月 23 日 在 Ubuntu 12 04 上成功使用 GCC 4 6 然后使用此 Clang 构建的 libc 当我想使用它时我必须同时提供 lc
  • 如何从 appsettings.json 文件中的对象数组读取值

    我的 appsettings json 文件 StudentBirthdays Anne 01 11 2000 Peter 29 07 2001 Jane 15 10 2001 John Not Mentioned 我有一个单独的配置类 p
  • 使用 WebClient 时出现 System.Net.WebException:无法创建 SSL/TLS 安全通道

    当我执行以下代码时 System Net ServicePointManager ServerCertificateValidationCallback sender certificate chain errors gt return t
  • C#中如何移动PictureBox?

    我已经使用此代码来移动图片框pictureBox MouseMove event pictureBox Location new System Drawing Point e Location 但是当我尝试执行时 图片框闪烁并且无法识别确切
  • while 循环中的 scanf

    在这段代码中 scanf只工作一次 我究竟做错了什么 include
  • SolrNet连接说明

    为什么 SolrNet 连接的容器保持静态 这是一个非常大的错误 因为当我们在应用程序中向应用程序发送异步请求时 SolrNet 会表现异常 在 SolrNet 中如何避免这个问题 class P static void M string
  • Windows 窗体:如果文本太长,请添加新行到标签

    我正在使用 C 有时 从网络服务返回的文本 我在标签中显示 太长 并且会在表单边缘被截断 如果标签不适合表单 是否有一种简单的方法可以在标签中添加换行符 Thanks 如果您将标签设置为autosize 它会随着您输入的任何文本自动增长 为
  • 覆盖子类中的字段或属性

    我有一个抽象基类 我想声明一个字段或属性 该字段或属性在从该父类继承的每个类中具有不同的值 我想在基类中定义它 以便我可以在基类方法中引用它 例如覆盖 ToString 来表示 此对象的类型为 property field 我有三种方法可以
  • 基于 OpenCV 边缘的物体检测 C++

    我有一个应用程序 我必须检测场景中某些项目的存在 这些项目可以旋转并稍微缩放 更大或更小 我尝试过使用关键点检测器 但它们不够快且不够准确 因此 我决定首先使用 Canny 或更快的边缘检测算法 检测模板和搜索区域中的边缘 然后匹配边缘以查
  • Windows 和 Linux 上的线程

    我在互联网上看到过在 Windows 上使用 C 制作多线程应用程序的教程 以及在 Linux 上执行相同操作的其他教程 但不能同时用于两者 是否存在即使在 Linux 或 Windows 上编译也能工作的函数 您需要使用一个包含两者的实现
  • 对来自流读取器的过滤数据执行小计

    编辑问题未得到解答 我有一个基于 1 个标准的过滤输出 前 3 个数字是 110 210 或 310 给出 3 个不同的组 从流阅读器控制台 问题已编辑 因为第一个答案是我给出的具体示例的字面解决方案 我使用的实际字符串长度为 450 个

随机推荐

  • jenkins-自动化打包部署

    环境 centos 7 2或者更新 rmp包 官方下载地址 Redhat Jenkins Packages java 1 8 0 安装 root jenkins yum install wget java 1 8 0 y root jenk
  • 滤波算法(一)

    滤波算法 算法一 一阶滤波算法 低通滤波器 首先要讲的是一阶滤波算法 也就是低通滤波算法 这个滤波算法对于低频的噪声具有非常好的效果 对于0到一定频率的信号是能够无失真接收的 这个算法通过硬件的电路推导 因其十分的简单 一阶滤波算法为 滤波
  • 认识正则表达式

    正则表达式re 正则表达式 re 是一套字符串数据筛选规范 在各种语言中 c c java python 都是通用的 对数据进行清洗 在不同的语言中使用流程不同 python中的用法 import re 导入re模块 自带库 result
  • Andriod 应用两种设计风格

    Andriod 应用两种设计风格 仿 iOS 风格 Andriod Design
  • db2插入 timestamp 类型 慎用

    表结构 create table tableName id varchar 8 not null date time timestamp 正确的方式 insert into tableName id date time values 000
  • Activity过度动画

    Activity过度动画 java代码 res文件 enter anim xml exit anim xml java代码 button setOnClickListener new View OnClickListener Overrid
  • c语言数字转字符串不用函数,不使用c的任何库函数 实现字符串到整数的转换 整数到字符串的转换...

    转载请标明出处 http www cnblogs com NongSi Net p 6805844 html 今天主要总结下 完成编程 1 除printf函数之外 不用任何c语言库函数 实现将字符串转化为整数的函数myatoi 可以支持负整
  • 大数据时代的Tcaplus游戏存储

    大数据时代的Tcaplus游戏存储 shiweizhang 2015 10 27 1 7k浏览 游戏开发数据分析场景 想免费获取内部独家PPT资料库 观看行业大牛直播 点击加入腾讯游戏学院游戏开发行业精英群711501594 摘要 大数据具
  • 怎么使用maven?

    文章目录 Maven环境搭建 创建maven项目 添加依赖 插件 添加依赖 javax servlet api 和 javax servlet jsp api 添加maven插件 运行项目 使用命令行方式运行项目 使用本地tomcat运行m
  • 【神经网络深度学习】--语义分割 Unet

    Unet 发表于 2015 年 属于 FCN 的一种变体 Unet 的初衷是为了解决生物医学图像的问题 由于效果确实很好后来也被广泛的应用在语义分割的各个方向 如卫星图像分割 工业瑕疵检测等 Unet 跟 FCN 都是 Encoder De
  • 两个无序的数组合并为一个有序的数组

    这道题很有意思 考察对于排序思想的理解 我相信大部分的人都写过两个有序链表的合并 不过前提是有序 这里是无序的 其次是链表合并指向变化 不许要开辟额外的空间就可以去做 这里不可以 因此是数组 你可以思考一下充满了挑战性 那么这道题该如何处理
  • STL之deque源码

    stl deque h 如果vector能满足你的需求 那么就使用vector 如果不得不使用deque 那么在进行一算法 尤其是sort 操作时 应该先把deque中的元素复制到vector中 执行完算法再复制回去 这样的效率往往要高于直
  • LA@0线性方程组的解摘要@记号说明

    文章目录 摘要 方程编号说明 摘要 n元线性方程组是包含n个未知量的线性方程组 它的解是一个n维向量 称为线性方程组的解向量 简称解 当线性方程组的解不唯一时 同一个 线性方程组的解向量之间具有一定关系 下面我们主要以线性方程组的向量方程形
  • 【数据分析】Pandas处理excel--导入+保存xlsx

    首先安装好pandas pip命令安装 本节案例数据表lesson4 xlsx Section 1 导入数据 用的pandas的read x 方法 x表示待导入文件的格式 导入 xlsx文件 使用read excel 文件路径 filePa
  • [渗透&攻防] 二.SQL MAP工具从零解读数据库及基础用法

    这是最近学习渗透和网站攻防的文章 希望能深入地学习这部分知识 自己作为一个初学者 前一篇文章从数据库原理解读了防止SQL注入 这篇文章通过SQLMAP工具简单介绍SQL注入及用法 自己仍在慢慢探索网络攻防和渗透 希望文章对你有所帮助 尤其是
  • 九十二.字符串算法问题(一)

    题一 判断字符串中有无重复字符 实现一个算法 确定一个字符串的所有字符是否全都不同 import java util Scanner public class LianXi public static boolean checkdiffer
  • virt-manager创建虚机需要指定的设置

    如果使用默认设置 鼠标键盘都不能用 也不能通过宿主机访问外网 所以在创建的时候 需要 好了 等到安装完毕 鼠标键盘在vnc中都能正常使用 也可以上网了
  • MATLAB读写.wav和.raw音频文件

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 wav文件格式 二 matlab代码 1 fread读 wav文件 2 fread读 raw音频文件 3 wav转raw 3 raw转wav 5 更改音频
  • String,StringBuffer,StringBuilder三者之间的联系和区别

    一 String 和 StringBuffer StringBuilder 相同点 String StringBuffer StringBuilder都是可以用来存储字符串的 不同点 1 String存储的字符串是不可变的 StringBu
  • 编译原理课设-设计一个词法分析器

    设计课设时时间紧凑 难免有些错误 文末还有完整的word可以直接下载使用 也可以直接私信我发你 文章目录 摘要 二 设计内容 一 目的 二 整体框架 三 设计类 四 项目技术 1 守卫锁lock guard C 11 2 正则表达式 C 1