编译原理:X语言 2023-12-28

1.X语言编译器(简单c语言):词法分析+语法分析部分
2.全文31810字

标签

        #本人初学者#,#仅供学习交流,其他用途与本人无关#,#侵权必删#,#转载标明出处#,#C++控制台#,#编译原理#,#词法分析#,#语法分析#
声明:

        1.欢迎各位朋友转载,但是请注明文章出处,附上文章链接。

        2.所有文章均为个人学习心得,部分学习视频过程中的截图如有侵犯到作者隐私,可联系我删除。

        3.欢迎各位朋友多交流学习,本人对自己的文章进行不定期的修正与更新,因此请到我的博客首页查看某篇章的最新版本。

程序介绍

        根据文法的规则,对测试代码,进行词法分析和语法分析过程,并输出结果。

效果展示

        ps.只有词法分析器【关键字检测、变量名是否合法】和语法分析器【是否符合语法规则】,并不包含语义分析器【意味着,不能检查变量是否定义了,无法实现运算功能】

1.测试代码符合文法规则73cddf2e50d84b458c2490749af85ae3.png

2.变量名命名错误,提示报错

        ps.这里的报错行提示,有一些bug,可能行数不是很准确,暂时没改a9d8410a15364e3794196a2e6eda72e7.png

3.语法错误

        ps.语法错误是根据文法定义得出的,文法中不存在这条规则,那么就会报错。

3aad7a881e8547199ad66311247b1c4c.png

使用方法

        首先,更改main.cpp的path路径【大概是第8行】,让其可以读取到文件【Vn.txt、Vt.txt、wenfa.txt、test.txt】的位置。

        其次,可以更改test.txt中的代码,这个是我们要测试的代码。

        最后,运行main.cpp,验证test.txt中的代码,是否符合词法和语法的规则。

 

基础知识(核心内容)

ps.都是个人理解,官方/书中的解释,可以去网络上搜索。

  1.  终结符Vt:
    1. 概述:终结符就是你最后输入的,测试代码的抽象集合。也就是说,你输入的那些代码,都可以转化为终结符,用终结符来表示。
    2. 终结符列表【个人的列表,根据你们的需求自行更改】:定义终结符的序号小于100,我们在代码中都用序号进行表示与操作,便于我们判断。
      ps.其中,000序号是空,既不是终结符,也不是非终结符;099序号是结束标志,当符号栈中的099序号匹配之后,代表匹配结束。
       
      000    ε
      001    main
      002    float
      003    int
      011    (
      012    )
      013    {
      014    }
      015    ;
      016    ,
      021    标识符
      022    整型常量
      023    实型常量
      025    +
      026    -
      027    *
      028    /
      035    =
      099    #
    3. 举例:
       

      举例代码: int a = 1.1;
      int 是 终结符 003
      a   是 标识符 021
      =   是 终结符 035
      1.1 是 实型常量 023
      ;    是 终结符 015

      ps. 在我的文法中,我规定这条语句是错误的,因为我规定,必须先定义再赋值,不能定义变量的时候赋值。

       

       

  2. 非终结符Vn:
    1. 概述:非终结符,个人认为,是用来表示某一[部分/功能]的描述,通常我们想要实现这个[部分/功能],可以根据文法,可以知道这个非终结符,能推导出其他符号,这些符号是终结符或非终结符的集合。
    2. 非终结符列表【个人的列表,根据你们的需求自行更改】:定义终结符的序号大于等于100,我们在代码中都用序号进行表示与操作,便于我们判断。
       ps. 序号100代表程序的入口。 

      100    程序
      101    语句部分
      102    声明部分
      103    变量定义
      104    标识符列表
      105    标识符列表1
      106    基本类型
      107    语句序列
      108    语句行
      109    语句
      110    赋值语句
      111    表达式
      112    表达式1
      113    项
      114    项1
      115    因子
      116    低级运算符
      117    高级运算符
      118    常量

    3. 举例

      举例文法:程序—>main(){语句部分} 


      程序  是 非终结符 100

      这个非终结符可以推导出其他符号
      main 是 终结符 001
      (       是  终结符 011
      )       是  终结符 012
      {       是  终结符 013

      语句部分 是 非终结符 101
      }       是  终结符 014

      ps.也就是说,我们想要是先出“程序”这个部分,就要按照文法,推导出它的写法规则。

  3. 文法:
    1. 概述:一般来说,你的每一行测试代码,都是由文法转化而来,判断代码是否正确的标准,就是判断代码是否符合你的文法。文法就是你的语法规则,每条文法由非终结符Vn和终结符Vt组成。
    2. 文法列表【个人的列表,根据你们的需求自行更改】:定义每条文法都有固定的格式,并且每条文法占一行。
      ps.文法格式:   左部非终结符  —>  右部符号(非终结符与终结符组成)

      程序—>main(){语句部分} 
      语句部分—>声明部分 语句序列
      声明部分—>变量定义;声明部分
      声明部分—>ε
      变量定义—>基本类型 标识符列表 
      标识符列表—>标识符 标识符列表1
      标识符列表1—>,标识符列表
      标识符列表1—>ε
      基本类型—>int
      基本类型—>float
      语句序列—>语句行 语句序列
      语句序列—>ε
      语句行—>语句 ;
      语句—>赋值语句
      赋值语句—>标识符 =表达式 
      表达式—>项 表达式1
      表达式1—>低级运算符 项 表达式1
      表达式1—>ε
      项—>因子 项1
      项1—>高级运算符 项
      项1—>ε
      因子—>标识符
      因子—>常量
      因子—>(表达式)
      低级运算符—>+
      低级运算符—>-
      高级运算符—>*
      高级运算符—>/
      常量—>整型常量
      常量—>实型常量

      ps.  常量—>整型常量|实型常量    是一个意思,只是给它分开写了。

    3. 举例:判断代码是否符合语法规则

      比如判断代码: int a

      对应的文法:(部分)

      (1)变量定义—>基本类型 标识符列表 

      (2)基本类型—>int
      (3)基本类型—>float

      (4)标识符列表—>标识符 标识符列表1
      (5)标识符列表1—>,标识符列表
      (6)标识符列表1—>ε

      大概意思就是:

      [1] 你想要"定义变量",那么你后面 你就要根据(1)文法,先写"基本类型",然后再写"标识符列表"。

      [2] 所以先写"基本类型"的话,看看这个"基本类型"是非终结符还是终结符,根据我们的定义,"基本类型"是非终结符,也就是可以继续推导。

      [3]根据文法(2)和(3),"基本类型"可以推导出 int 或 float ,根据我们的定义,都是终结符,无法继续推导,所以,我们定义变量的时候,必须先写 int 或 float,所以我们推导出了int。

      [4]然后看“标识符列表”,是非终结符,还是继续推导,还是根据上面的分析过程,直到出现终结符才会结束。

      [5]根据文法(4),推导出了"标识符"这个终结符,也就是我们的变量名a。

      [6]我们看到,后面还有"标识符列表1"这个非终结符,虽然推导出我们的代码了(int a),但是文法还没有结束,我们必须继续推导。
      [7]根据文法(5)和(6),"标识符列表1"可以推导出 ",标识符列表“ 或 ”ε",根据我们的测试代码,很明显是推导到了"ε",而导致的结束,也就是我们的"int aε"(其中ε代表“空”,序号000,可以省略)
      [8]那么对于另一条语句",标识符列表“,我们分析得出
          int a,b,c
         这种多个变量的定义,也是符合文法规则的定义的。

       

      这个过程,就是我理解的,你的编译器的规则---文法。
       

       

  4. first集
    ps.代码并不唯一,你只要理解这个是怎么得出来的,可以随意编写。

    1. 概述:对于每一个“左部”非终结符,都有一个终结符(包含空)的集合,对于你想进行这个部分(非终结符,左部),你首先要写哪个符号。

    2. 算法思路:对于相同的符号,不再重复加入

      1. 遍历所有的文法,对于每一个左部,都有一个first集;对于多个相同的左部,他们的合并才是它本身的first集

      2. 对于当前文法,我们要看文法的右部,对于右部的第一个符号,进行分析,直到分析出了一个终结符或ε(空),就结束了,把这个符号加入到这个左部的first集合。

      3. 重复上面操作,直到所有符号的终结符全部分析完毕。

    3. 举例:

      对于以下文法,进行first集求解。
      (1)变量定义—>基本类型 标识符列表 

      (2)基本类型—>int
      (3)基本类型—>float

      (4)标识符列表—>标识符 标识符列表1
      (5)标识符列表1—>,标识符列表
      (6)标识符列表1—>ε

      过程:

      1.想要求first(变量定义),首先分析当前行的代码右部第一个符号"基本类型"。
      2."基本类型"是非终结符,没有获取到终结符,那么继续推导,也就是求解first(基本类型)后,加入到first(变量定义)

      3.分析first(基本类型),注意要遍历当前左部的所有文法,得出第一个符号是int 和 float ,都是终结符,那么first(基本类型) = {int,float}
      4. 也就推导出了first(变量定义) = {int,float}
      5.继续求解其他文法,对于(4),求解first(标识符列表) = {标识符},因为右部的第一个符号就是终结符,所以添加到first集后就结束了。

      6.同理,first(标识符列表1) = {, 和 ε}

      ps.可能出现左递归【first(左部) = first(左部),自己得到自己,没办法得到其他终结符】的情况,那就是文法设计的问题,可以进行消除左递归的操作,直接套公式即可。(一个变成两个)
          形式:A → Aa|b
          改写为:A → bA’ ,A’→ aA’|ε
       

    4. 对于我的上面的文法,得出的first集是

      程序|main
      语句部分|int float ε 标识符
      声明部分|int float ε
      变量定义|int float
      标识符列表|标识符
      标识符列表1|, ε
      基本类型|int float
      语句序列|标识符 ε
      语句行|标识符
      语句|标识符
      赋值语句|标识符
      表达式|标识符 整型常量 实型常量 (
      表达式1|+ - ε
      项|标识符 整型常量 实型常量 (
      项1|* / ε
      因子|标识符 整型常量 实型常量 (
      低级运算符|+ -
      高级运算符|* /
      常量|整型常量 实型常量

       

  5. follow集
    ps.代码并不唯一,你只要理解这个是怎么得出来的,可以随意编写

    1. 概述:对于每一行文法,它的右部的每一个非终结符,预测这个非终结符的下一个符号是什么,就是我们的follow集,它是终结符的集合(不含空)。

    2. 算法思路:对于相同的符号,不再重复加入

      1. 首先要在开始文法"程序(100)"中,加入 #(099) ,也就是 follow(程序) = {#}

      2. 遍历每一行文法,对于当前行文法来说,遍历这条文法的右部

      3. 对于遍历出的非终结符,我们看它的下一个符号,这样就有很多可能

        1. 下一个符号是 终结符:直接加入终结符到 这个非终结符的follow集中

        2. 下一个符号是 非终结符1 :把first(非终结符1)中的所有元素,加入到这个非终结符的follow集中,但是不加入ε,如果存在空,就还要进行下面的操作(后面没有符号)。↓

        3. 后面没有符号:那么就找到这条文法的左部,并且加入follow(左部),到非终结符的follow集中。注意,如果这个非终结符和左部相同的时候,那么可以不进行这步操作。

      4. 重复进行上述 2和3的操作,直到不会再变化为止。

    3. 举例

      对于以下文法,进行follow集求解。
      (1)变量定义—>基本类型 标识符列表 

      (2)基本类型—>int
      (3)基本类型—>float

      (4)标识符列表—>标识符 标识符列表1
      (5)标识符列表1—>,标识符列表
      (6)标识符列表1—>ε


       

      过程:

      ps.程序入口的follow集要加入#,但是这是部分文法,没有入口,所以省略。
      1.对于每条文法,要求解follow集,就要看右部,遍历每一个符号,找到非终结符的话,就要求解这个非终结符的follow集。

      2.对于(1)文法,首先遇到了"基本类型",所以我们要先求follow(基本类型),那么就要看这个"基本类型"的下一个符号,是"标识符列表",我们把first(标识符列表)加入到follow(基本类型)中,因为不存在空,所以不需要进行其他操作,即 follow(基本类型) = {标识符}

      3.然后继续往后面求解,遇到非终结符“标识符列表”,那么求解follow(标识符列表),就要看下一个符号,后面啥也没有,就把follow(变量定义)加入到follow(标识符列表) 。。也许follow(变量定义)没有求解完成,我们要进行标记,再下一次循环中,不断完善这些follow集合。

      4.之后遍历下一条文法,对于(2)和(3)文法,全是终结符组成,所以不需要进行什么操作

      5.所以对(4)文法进行遍历,遇到了“标识符列表1”,求解follow(标识符列表1),就要吧follow(标识符列表加入到集合),暂时没有,所以标记起来。

      6. 然后对于(5)文法遍历,需要把follow(标识符列表) 加入到 follow(标识符列表)中。但是我们发现,这个第5步和第6步形成了一个循环,所以在下一次循环中,我们对于这样的一个循环,可以进行消除:前面说过,如果这个非终结符和左部相同的时候,那么可以不进行这步操作。

      7.然后遍历(6)文法,是空,不是非终结符,可以跳过。

      8.之后从头开始,重新分析遍历,前面的分析中,我们知道:一些follow集合和某些follow集合会产生联系,所以,我们每一次都会完善一些follow集合,这样多次循环之后,就会不断完善,从而形成完整的follow集。

    4. 对于我的上面的文法,得出的follow集:

      程序|#
      语句部分|}
      声明部分|标识符 }
      变量定义|;
      标识符列表|;
      标识符列表1|;
      基本类型|标识符
      语句序列|}
      语句行|标识符 }
      语句|;
      赋值语句|;
      表达式|) ;
      表达式1|) ;
      项|+ - ) ;
      项1|+ - ) ;
      因子|* / + - ) ;
      低级运算符|标识符 整型常量 实型常量 (
      高级运算符|标识符 整型常量 实型常量 (
      常量|* / + - ) ;

       

  6. select集:对于相同的符号,不再重复加入

    1. 概述:select集合,就是对于当前行,求解右部整体的first集合,select集是终结符的集合(不包含空)

    2. 算法思路:

      1. 遍历所有文法,对于每一行文法,求解select(行号) = first(右部)

      2. 求解右部的frist集合,就要遍历每一个符号

        1. 如果当前符号是终结符,那么直接加入到select集合,并且结束

        2. 如果当前符号是非终结符,那么直接加入first(非终结符),如果first集合中含有空,就要求解下一个符号的first集合,直到遇到终结符结束

        3. 如果最后的右部,first集合中还是含有空,select(行号)中,就要加入follow(左部)

      3. 举例

        (1)变量定义—>基本类型 标识符列表 

        (2)基本类型—>int
        (3)基本类型—>float

        (4)标识符列表—>标识符 标识符列表1
        (5)标识符列表1—>,标识符列表
        (6)标识符列表1—>ε

        过程:

        0.遍历每一行文法,对于每一行,都有select集合。

        1.根据(1)文法,select(1) = {first(文法(1)右部)},所以求解每一个符号,直到出现终结符

        2.我们已知first(基本类型) = {int,float}不包含空,所以,select(1)={int,float}
        3.继续遍历下一行,根据(2)文法,知道select(2) = {int}

        4.继续遍历下一行,根据(3)文法,知道select(3) = {float}

        5.继续遍历下一行,根据(4)文法,知道select(4) = {标识符}

        6.继续遍历下一行,根据(5)文法,知道selct(5) = {,}

        7.继续遍历下一行,根据(6)文法,知道select(标识符列表) = {ε},但是不能存在空,而且右部ε后面没有其他符号,所以把空替换为,follow(标识符列表1),follow集我们上面已经求完了。

        即:select(6) = {;}


        ps.因为之前已经求解好first集和follow集,所以只需要遍历一次即可。

      4. 对于我的上面的文法,得出的select集

        1|main
        2|int float 标识符 }
        3|int float
        4|标识符 }
        5|int float
        6|标识符
        7|,
        8|;
        9|int
        10|float
        11|标识符
        12|}
        13|标识符
        14|标识符
        15|标识符
        16|标识符 整型常量 实型常量 (
        17|+ -
        18|) ;
        19|标识符 整型常量 实型常量 (
        20|* /
        21|+ - ) ;
        22|标识符
        23|整型常量 实型常量
        24|(
        25|+
        26|-
        27|*
        28|/
        29|整型常量
        30|实型常量

  7. 预测分析表

    1. 根据selct集合,构建出来的表,表头是 非终结符 * 终结符 ,然后根据selct集合填写序号。(每一行的左部)* (selct集合) == (行号)

    2. 思路:

      1. 初始化预测分析表,所有值置成-1

      2. 遍历每一行文法,对于当前行文法,拿到文法的左部(非终结符),根据select集合,拿到当前行可以推导出的所有终结符的集合,在这个表格中,对于每一个交汇点,填写对应的文法行号。

    3. 对于我的上面的文法,得出的分析预测表:对于小于100的值,当做终结符,不小于100的值,当做非终结符
      9c762e8edd514eef96297ab28ee7aa39.png

  8. 符号栈

    1. 符号栈概述:把测试代码,进行了词法分析以后,所有单词转化为终结符,并且把结果压入栈中,之后对比预测栈中的文法。

    2. 预测栈概述:预测栈中,首先压入两个单词,{100,99},100是程序的入口,99是程序的结束标志。预测栈中的非终结符,总是会根据预测分析表,转化为非终结符,与符号栈中的非终结符对比,如果全部匹配成功,则没有语法错误。

    3. 对比过程:

      7739abc2d9e944cbb82b09fa78871608.png
      流程简述:

      测试代码:
      main(){
          int a,b;
          a = 0;
      }

      预测符号栈:  {程序#}

      1.首先把测试代码,根据词法分析,转化为终结符,压入栈中
      "

      main (  )  { 
         int   标识符   ,   标识符   ;
         标识符   =   整形常量  ;
      }

      "

      2.然后进行匹配,根据分析预测表,分析预测符号栈的转化,将其中的非终结符转化为对应的表达式。
      (1)符号栈的main 匹配到 预测符号栈的  “程序”
      (2)预测符号栈的"程序",开始向“main”转化,根据分析预测表(非终结符*终结符==文法行号), “程序(100)” * "main(1)" ==  1  ,也就是说,我们要把非终结符"程序",替换为 第1行文法(程序—>main(){语句部分}) ,才可以把"main"这个符号匹配出去,所以,预测符号栈变化为:
           {程序#}    ---->   {main(){语句部分}#}

      (3)然后,再次拿预测符号栈,与符号栈的单词进行匹配。
              符号栈的main 匹配到 预测符号栈的  “main”
      (4)两个符号栈同时出栈,进行下一个符号的匹配。

      3.当匹配到   -1  位置/两个终结符不能匹配  的时候,说明出现语法错误。
         当匹配到   #  的时候,说明匹配成功。

 

 

程序

ps.如果你理解了以上内容,就可以自己完成语法分析部分,接下来我会围绕我的代码进行说明

 

我的项目分为11个文件。

  1. 其中3个是进行声明hpp头文件
    92c13801b7ca43b2af5569ff45794172.png
    Lexical   词法分析 
    LoadFile   文件处理
    Parser    语法分析
  2. 3个是功能实现cpp文件   +  1个程序入口cpp文件
    6178ac1ce0774c7d8c2492c9726a7e02.png
    main : 程序的入口,注释的文本可以进行输出测试,运行程序需要改变第8行的文件路径。
  3. 还有3个文法txt文本文件   +  1个测试代码txt文本文件
    97322d485e7e489c8bdf6205a03de038.png
    test : 你的测试代码
    Vn:非终结符定义
    Vt:终结符定义
    wenfa:文法定义                 

代码实现

  1. LoadFile.hpp
    #pragma once
    #ifndef __LOADFILE_H__
    #define __LOADFILE_H__
    #endif
    #include <iostream>
    #include <fstream>
    #include <map>
    #include <vector>
    #include <string>
    #include <stack>
    //文件读入类:用来读入文件,处理读入的字符串,存储到对应的变量里
    class loadFile {
    
    public:
    	std::map<int, std::string>vs; // 用序号,转化为对应的字符串
    	std::map<std::string, int>vi; // 用字符串,转化为对应的数字
    	/// <summary>
    	/// wenfa_line:文法左部,对应的哪些行,左部行的列表,总结出左部对应的右部 
    	/// 格式为:
    	/// 文法左部:【出现在行1,出现在行2 ……】
    	/// 
    	/// wenfa_int:行,对应的右部列表,
    	/// 格式为:
    	/// 行:【对应的右部列表】
    	/// 
    	/// </summary>
    	std::map<int, std::vector<int>> wenfa_line, wenfa_int; 
    
    	std::map<int, std::string>code;//当前行的字符串,把读入的文本按行存入,行:当前行的内容
    	std::map<int, int>line_count; //line : count ,用来标记报错的位置,分析技术count在哪一行,
    	std::stack<int> wordStream;  //单词流,把test代码转化为int类型,然后逆序入栈,当前为空,在词法分析器里面赋值
    	bool isRun; //程序运行标志
    private:
    	std::string file_path; // 传入的路径,读取文件的文件夹
    	
    public:
    	//重载构造函数,创建对象时传入路径
    	loadFile(std::string file_path);
    	//启动入口,传入读取的文件名:非终结符文件、终结符文件、文法文件、测试代码文件
    	void run(std::string Vn_file_name, std::string Vt_file_name, std::string Wenfa_file_name, std::string Code_file_name);
    	//输出非终结符
    	void VnPrint();
    	//输出终结符
    	void VtPrint();
    	//输出文法
    	void WenfaPrint();
    	//输出测试代码
    	void CodePrint();
    private:
    	//获取并解析非终结符文件
    	void GetVnFile(std::string file_name);
    	//获取并解析终结符文件
    	void GetVtFile(std::string file_name);
    	//获取并解析文法文件
    	void GetWenFaFile(std::string file_name); 
    	//获取并解析测试代码文件
    	void GetCodeFile(std::string file_name);
    };

     

  2. LoadFile.cpp
    #include "LoadFile.hpp"
    //重载的构造函数,传入路径
    loadFile::loadFile(std::string file_path) {
    	//传入的路径
    	loadFile::file_path = file_path;
    	//程序运行标志
    	isRun = true;
    }
    //程序运行入口
    void loadFile::run(std::string Vn_file_name, std::string Vt_file_name, std::string Wenfa_file_name, std::string Code_file_name) {
    	//检测运行标志
    	if (isRun) {
    		//读取并处理非终结符
    		loadFile::GetVnFile(Vn_file_name);
    		//失败报错,结束运行
    		if(!isRun){
    			std::cout << "loadFile : GetVnFile_err" << std::endl;
    			return;
    		}
    	}
    	//检测运行标志
    	if (isRun) {
    		//读取并处理终结符
    		loadFile::GetVtFile(Vt_file_name);
    		//失败报错,结束运行
    		if (!isRun) {
    			std::cout << "loadFile : GetVtFile" << std::endl;
    			return;
    		}
    	}
    	//检测运行标志
    	if (isRun) {
    		//读取并处理文法
    		loadFile::GetWenFaFile(Wenfa_file_name);
    		//失败报错,结束运行
    		if (!isRun) {
    			std::cout << "loadFile : GetWenFaFile_err" << std::endl;
    			return;
    		}
    	}
    	//检测运行标志
    	if (isRun) {
    		//读取并处理测试代码
    		loadFile::GetCodeFile(Code_file_name);
    		//失败报错,结束运行
    		if (!isRun) {
    			std::cout << "loadFile : GetCodeFile_err" << std::endl;
    			return;
    		}
    	}
    }
    //读取并处理非终结符,传入文件名
    void loadFile::GetVnFile(std::string file_name) {
    	std::ifstream infile_Vn;
    	infile_Vn.open(loadFile::file_path + file_name, std::ios::in);//打开文件:拼接的路径,读入
    	//文件打开失败
    	if (!infile_Vn.is_open())
    	{
    		//运行标志 = false
    		std::cout << "读取文件失败" << std::endl;
    		isRun = false;
    		return;
    	}
    	//存放一行字符串
    	char buf[1024];
    	//按行读取文件
    	while (infile_Vn.getline(buf, sizeof(buf)))
    	{
    		//字符串存到s中
    		std::string s = buf;
    		//非终结符字符串为: 序号	字符串
    		//按照ch字符切割字符串
    		std::string ch = "	";
    		//i是ch在s中的位置
    		int i = s.find(ch);
    		//获取左部序号,stoi是把字符串转为数字
    		int value = std::stoi(s.substr(0, i));
    		//获取右部字符串,s中ch字符的右面
    		std::string key = s.substr(ch.length() + i);
    		//vs赋值,符号string:序号int   
    		vs[value] = key;
    		//vi赋值,序号int:符号string
    		vi[key] = value;
    	}
    	//结束关闭文件
    	infile_Vn.close();
    	
    }
    //读取并处理终结符,传入文件名,与GetVnFile相同
    void loadFile::GetVtFile(std::string file_name) {
    	
    	//Vt
    	std::ifstream infile_Vt;
    	infile_Vt.open(loadFile::file_path + file_name, std::ios::in);//打开文件:拼接的路径,读入
    	//文件打开失败
    	if (!infile_Vt.is_open())
    	{
    		//运行标志 = false
    		std::cout << "读取文件失败" << std::endl;
    		isRun = false;
    		return;
    	}
    	//存放一行字符串
    	char buf[1024];
    	//按行读取文件
    	while (infile_Vt.getline(buf, sizeof(buf)))
    	{
    		std::string s = buf;
    		std::string ch = "	";
    		int i = s.find(ch);
    
    		int value = std::stoi(s.substr(0, i));
    		std::string key = s.substr(ch.length() + i);
    
    		vs[value] = key;
    		vi[key] = value;
    		//std::cout << key << " -> " << value << std::endl;
    	}
    	infile_Vt.close();
    }
    
    //读取并处理文法,传入文件名
    void loadFile::GetWenFaFile(std::string file_name) {
    	std::ifstream infile;
    	infile.open(loadFile::file_path + file_name, std::ios::in);//打开文件:拼接的路径,读入
    	//文件打开失败
    	if (!infile.is_open())
    	{
    		//运行标志 = false
    		std::cout << "读取文件失败:"<<file_name << std::endl;
    		isRun = false;
    		return;
    	}
    	//存放一行字符串
    	char buf[1024];
    	//标记当前行
    	int id = 0;
    	//按行读取文件
    	while (infile.getline(buf, sizeof(buf)))
    	{
    		//获取当前行字符串
    		std::string s = buf;
    		//切割符号ch
    		std::string ch = "—>";
    		//寻找切割点,按照ch字符切割字符串
    		int pos = s.find(ch);
    		//划分左部和右部
    		std::string s1 = s.substr(0, pos); // s1 是 左部非终结符
    		std::string s2 = s.substr(pos + ch.size()); //s2 是 右部的符号串合集
    		//当前行标记
    		id++;
    		//中文匹配变量,tmp存放字符串
    		std::string tmp = "";
    		//遍历当前行的右部符号集合,拼接字符串
    		for (int i = 0; i < s2.size(); i++)
    		{
    			//当前是空格
    			if (s2[i] == ' ') {
    				//如果是没有意义的空格,就继续走:因为tmp是空的,所以当前空格没有意义
    				if (tmp == "") {
    					continue;
    				}
    				//当前结果tmp,没有在vi中被匹配,说明不存在符号,报错
    				if (!(vi.find(tmp) != vi.end())) {
    					std::cout << "空格错误!#" << tmp << "#" << std::endl;
    					//结束运行标志 = false
    					isRun = false;
    					break;
    				}
    				else {
    					//把匹配后的tmp 无意义空格 清空
    					
    					tmp = "";
    				}
    				//空格处理完成
    				continue;
    			}
    			//继续拼接字符串,遇到特殊符号才终止,这里拼接的是中文、符号串、中文+数字类型的串
    			tmp += s2[i];
    			
    			//下一个是中文 或 数字 或 ε
    			/*
    				i + 1 < s2.size():确保存在下一个符号
    				(unsigned)s2[i + 1] > 0x80:确保下一个符号是中文符号
    				(s2[i + 1] >= '0' && s2[i + 1] <= '9')):确保下一个符号是‘0’-‘9’的字符
    				当前if判断逻辑:判断存在下一个符号,且下一个符号是中文或0-9的字符
    
    				这里的功能,确保中文字符串全部读取完毕
    			*/
    			if (((i + 1 < s2.size() && ((unsigned)s2[i + 1] > 0x80 || (s2[i + 1] >= '0' && s2[i + 1] <= '9'))))) {
    				//确定tmp中已经有其他符号了
    				//并且确定tmp的最后一个符号是中文
    
    				/*
    					这里处理了两种情况
    					1.中文后面是中文的时候,加入后面的中文,确保中文字符串全部读入,避免   “语句部分” 读取一半,而匹配到 “语句”
    					2.中文后面是数字,解决了“项1”这种情况,直接把后面的0-9字符作为当前字符串的一部分
    				*/
    
    				if (tmp.size() > 1 && (unsigned)tmp[tmp.size() - 1] > 0x80) {
    					//直接把下一个符号加入tmp,同时不需要下一个循环,因为下一个符号已经加入了,所以让i++
    					i++;
    					
    					tmp += s2[i];
    					//cout << tmp << endl;
    				}
    			}
    			//存在符号tmp,就转为int,加入wenfa_line中
    			if ((vi.find(tmp) != vi.end())) {
    				wenfa_line[id].push_back(vi[tmp]);
    				//std::cout << id << ":" << tmp << std::endl;
    				//加入后清空tmp
    				tmp = "";
    			}
    		}
    		//wenfa_int【左部序号】.加入(当前行),格式是:左部序号:<行号列表>
    		wenfa_int[vi[s1]].push_back(id); // 文法的int类型 非终结符->行号
    	}
    	//关闭文件
    	infile.close();
    }
    
    //读取并处理测试代码,传入文件名
    void loadFile::GetCodeFile(std::string file_name) {
    	std::ifstream infile;
    	infile.open(loadFile::file_path + file_name, std::ios::in);//拼接路径:读入类型
    	//文件打开失败
    	if (!infile.is_open())
    	{
    		std::cout << "读取文件失败:code" << file_name << std::endl;
    		isRun = false;
    		return;
    	}
    	//读取当前行的字符串
    	char buf[1024];
    	//行号标志
    	int id = 0;
    	//按行读取字符串
    	while (infile.getline(buf, sizeof(buf)))
    	{
    		//读入的字符串转为string类型
    		std::string s = buf;
    		//直接放到code中:格式是行int:串string
    		//到词法分析器中去处理这个代码
    		code[id++] = s;
    	}
    	//关闭文件
    	infile.close();
    }
    
    //输出测试
    void loadFile::VnPrint() {
    	std::cout << "*********Vn***********" << std::endl;
    	for (auto vn : vs) {
    		if (vn.first >= 100) {
    			std::cout <<"("<<vn.first<<")|" << vn.second << std::endl;
    		}
    	}
    }
    void loadFile::VtPrint() {
    	std::cout << "*********Vt***********" << std::endl;
    	for (auto vt : vs) {
    		if (vt.first < 100) {
    			std::cout << "(" << vt.first << ")|" << vt.second  << std::endl;
    		}
    	}
    }
    void loadFile::WenfaPrint() {
    	std::cout << "*********WenFa***********" << std::endl;
    	for (auto wf : wenfa_line) {
    		std::cout << wf.first  << "|";
    		for (auto v : wf.second) {
    			std::cout << vs[v] << " ";
    		}
    		std::cout << std::endl;
    	}
    	
    }
    void loadFile::CodePrint() {
    	std::cout << "*********Code***********" << std::endl;
    	for (auto c : code) {
    		std::cout << c.first + 1 << "|";
    		for (auto v : c.second) {
    			std::cout << v << " ";
    		}
    		std::cout << std::endl;
    	}
    }
    

     

  3. Lexical.hpp
    #pragma once
    #ifndef __LEXICAL_H__
    #define __LEXICAL_H__
    #endif
    #include <iostream>
    #include <fstream>
    #include <map>
    #include <vector>
    #include <regex>
    #include <stack>
    #include <string>
    #include "LoadFile.hpp"
    class lexical {
    
    private:
    	//枚举语法分析中,所有可能错误
    	enum ErrorType
    	{
    		//默认错误
    		DEFAULT_ERROR = 1,
    		//整数错误
    		INTEGER_ERROR,
    		//实数错误
    		REAL_ERROR,
    		//点错误
    		DOT_ERROR,
    		//标识符错误
    		IDENTIFIER_ERROR,
    		//单词错误
    		WORD_ERROR,
    	};
    	/// <summary>
    	/// int : string
    	/// 0   : main(){
    	/// 1   : int b,c;
    	/// 
    	/// 
    	/// 0   :1 
    	/// </summary>
    	std::map<int, std::string> code_s;//传进来的代码test,   行:当前行字符串
    
    	std::map<int, std::vector<int>> code;//把code_s 的sting 转化为 int
    	std::stack<int> wordStream;  // 栈:把整个代码,逆序压栈 int
    	std::map<std::string, int>vi; // 字符串转序号   vi["main"] = 1;  vi["("] = ?
    	std::map<int, std::string>vs; //序号转字符串 ,, 输出时候用
    	bool isRun;  // 判断有没有正常运行,单词错误,就结束
    	std::map<int, int>line_size; // 用来报错,每行的符号个数【行,行长度】  【0,4】 
    
    private:
    	//在字符串中,剪切字符串,得到单词符号
    	void GetWord();
    	//检测单词符号的类型:终结符、整型常量、实型常量、“main”、“(”  等等
    	int CheckWord(std::string word);
    	//单词报错:当前行号,当前单词,报错类型
    	bool CheckError(int x, std::string s,ErrorType err);
    	//检测当前单词:调用int CheckWord(std::string word);函数,传入单词和行号
    	void CheckWord(std::string* s, int line);
    public:
    	//重载构造函数,传递loadfile实例,获取其中的数据
    	lexical(loadFile file);
    	//输出代码测试:转为序号int
    	void CodePrint();
    	//返回逆序入栈的单词流栈,更新外面loadfile f 的 WordStream
    	std::stack<int> GetWordStream();
    	//输出单词流栈测试:逆序入栈,出栈输出
    	void WordStreamPrint();
    	//获取运行状态,更新外面loadfile f 的 isRun
    	bool GetRun();
    	//获取代码行,更新外面loadfile f 的 line_count
    	std::map<int, int>GetCodeLine();
    };
    

     

  4. Lexical.cpp
    #include "Lexical.hpp"
    //重载的构造函数,传递loadfile,需要读取处理的数据
    lexical::lexical(loadFile f) {
    	//读取运行状态
    	isRun = f.isRun;
    	//正常运行
    	if (isRun) {
    		//获取代码   行int:字符串string
    		code_s = f.code;
    		//获取vi 符号的string转int
    		vi = f.vi;
    		//获取vs 符号的int转string
    		vs = f.vs;
    		//判断是否读取成功
    		if (!(!code_s.empty() && !vi.empty() && !vs.empty())) {
    			//信息更新失败,运行状态 = false
    			isRun = false;
    		}
    		//报错
    		if (!isRun) {
    			std::cout << "lexical : loadFile_err" << std::endl;
    			return;
    		}
    		
    	}
    	if (isRun) {
    		//词法分析:切割单词
    		GetWord();
    
    		//报错
    		if (!isRun) {
    			//词法分析错误,运行状态 = false
    			std::cout << "lexical : GetWord_err" << std::endl;
    			return;
    		}
    	}
    
    }
    //在当前行字符串中,剪切出一个个单词
    void lexical::GetWord() {
    	
    	//遍历每一行   行号:当前行字符串
    	for (auto s : code_s) {
    		//标记当前行
    		int line = s.first;
    
    		//拼接当前字符串tmp
    		std::string tmp = "";
    
    		//遍历当前行字符串
    		for (auto v : s.second) {
    			//每个字符 v
    
    			//当前字符v不是特殊符号
    			if ((v >= 'a' && v <= 'z') || (v >= 'A' && v <= 'Z') || (v >= '0' && v <= '9')) {
    				//正常加入tmp
    				tmp += v;
    				continue;
    			}
    			else {
    				//v是特殊符号
    				
    				//如果是 . 符号
    				if (v == '.') {
    					//点号先正常加入,里面会有判断,存在点号的字符串会特判
    					
    					tmp += v;
    					continue;
    				}
    				//如果是 _ 符号,在一般语言里面,算是正常输入,如: int _a;
    				else if (v == '_') {
    					//这个算正常输入,直接加入,地位和上面的 “当前字符v不是特殊符号” 相同
    					tmp += v;
    					continue;
    				}
    				else {
    					//v是其他特殊符号: ( + - 等等
    					/*
    						代表tmp 是一个单独的单词, v 也是一个单独的单词
    						例如 : main()
    						v遍历到 ( 时候,tmp中字符串是"main"
    
    						接下来单独判断tmp是什么类型的符号
    						v如果是非空,暂时加入tmp,
    					*/
    
    					//判断tmp是什么类型的符号,传入tmp和当前行
    					CheckWord(&tmp, line);
    					
    					//如果不是空格,就说明后面的符号有意义,进行下一次检测 #1
    					if (v != ' ' && v != '\t') {
    						tmp += v;
    					}
    				}
    			}
    			//当前v遍历结束,如果tmp中还存在符号,就进行判断
    			//这时,tmp中的符号,一般来自于 #1 末尾的特殊符号
    			CheckWord(&tmp, line);
    			
    
    		}
    		
    		//CheckWord(&tmp, line);
    	}
    
    	//std::cout << "检测完毕!" << std::endl;
    }
    
    
    int lexical::CheckWord(std::string s) {
    	//检测当前符号s
    	
    	//存在vn或vt中的符号,直接返回序号
    	if (vi.find(s) != vi.end()) {
    		return vi[s];
    	}
    	else {
    		//其他类型的符号
    		/*
    			是否是 标识符,规则:字母、数字、下划线组成,不能是数字开头
    			是否是 数字, 规则:数字开头
    				1.整数:只有数字字符组成
    				2.实数:数字字符和点号组成,有且只有一个点号,且点号的位置不在开头和末尾
    		
    		*/
    
    		//数字开头,是数字
    		if (s[0] >= '0' && s[0] <= '9') {
    			//检测数字是否合法
    			//判断是否存在点号
    			if (s.find('.') < s.size()) {
    				//是实数
    				
    				//尾部是数字
    				if (!(s[s.size() - 1] >= '0' && s[s.size() - 1] <= '9')) {
    					//实数错误
    					int e = REAL_ERROR * (-1);
    					return e;
    				}
    				//首部是数字
    				if (!(s[0] >= '0' && s[0] <= '9')) {
    					//实数错误
    					int e = REAL_ERROR * (-1);
    					return e;
    				}
    				//中间只有一个‘.’号,剩下都是数字
    				//标记点号是否出现
    				bool isDot = false;
    				//遍历当前字符串单词
    				for (int i = 1; i < s.size(); i++) {
    					//只能是数字或点号
    					if ((s[i] == '.') || (s[i] >= '0' && s[i] <= '9')) {
    						//只能出现一次小数点
    						if (s[i] == '.') {
    							if (isDot) {
    								//点号再次出现就报错
    								int e = DOT_ERROR * (-1);
    								return e;
    							}
    							else {
    								//标记点号已经出现过一次
    								isDot = true;
    							}
    						}
    						continue;
    					}
    					else {
    						//出现其他符号,报错
    						int e = REAL_ERROR * (-1);
    						return e;
    					}
    				}
    				//没有任何错误,就转int,返回 实型常量(int) 类型
    				return vi["实型常量"];
    			}
    			else {
    
    				//是整数,检测是否合法
    				for (int i = 0; i < s.size(); i++) {
    					if ((s[i] >= '0' && s[i] <= '9')) {
    						//只能出现数字
    						continue;
    					}
    					else {
    						//出现其他符号,报错
    						int e = INTEGER_ERROR * (-1);
    						return e;
    					}
    				}
    				//没有任何错误,就转int,返回 整型常量(int) 类型
    				return vi["整型常量"];
    			}
    		}
    		else {
    			//不是数字开头,是标识符,检测是否合法
    			if (!((s[0] >= 'a' && s[0] <= 'z') || (s[0] >= 'A' && s[0] <= 'Z') || (s[0] == '_'))) {
    				//首字母不是正常符号:字母、下划线开头
    				//报错:标识符错误
    				int e = IDENTIFIER_ERROR * (-1);
    				return e;
    			}
    			//遍历当前字符串单词
    			for (int i = 1; i < s.size(); i++) {
    				//正常符号:数字、字母、下划线
    				if ((s[i] >= 'a' && s[i] <= 'z') || (s[i] >= 'A' && s[i] <= 'Z') || (s[i] == '_') || (s[i] >= '0' && s[i] <= '9')) {
    					continue;
    				}
    				//不是正常符号,标识符错误
    				int e = IDENTIFIER_ERROR * (-1);
    				return e;
    			}
    			//没有任何错误,就转int,返回 标识符(int) 类型
    			return vi["标识符"];
    		}
    
    	}
    	//返回未知的默认错误
    	int e = DEFAULT_ERROR * (-1);
    	return e;
    }
    
    //检测当前单词
    void lexical::CheckWord(std::string* s,int line) {
    	//传入需要检测的 tmp 字符串,和行号
    	std::string tmp = *s;
    	
    	//tmp是非空,空字符串没有意义
    	if (!tmp.empty()) {
    		//检测tmp,返回检测结果
    		/*
    			检测结果判断
    			1.如果是x>=0 证明这个单词在vn或vt中存在,也就是非终结符(>=100)或终结符(<100)
    			2.如果是x< 0 证明这个单词是错误的单词,取到x的绝对值后,转为ErrorType枚举,就是报错的类型
    		*/
    		int x = CheckWord(tmp);
    		if (x < 0) {
    			//报错
    			ErrorType e = (ErrorType)(x * (-1));
    			x = CheckError(line, (tmp), e);
    		}
    		//不报错就存到code中, 行int:符号int
    		code[line].push_back(x);
    		//清空tmp
    		(*s).clear();
    	}
    }
    
    //词法分析报错函数
    bool lexical::CheckError(int x, std::string s, ErrorType err) {
    	//x 是行号,s 是当前报错符号 ,err 是报错类型
    	std::cout << "符号:" << s << "(行:" << x << ") 发生 ";
    	switch (err)
    	{
    	case lexical::DEFAULT_ERROR:
    		std::cout << "默认错误" << std::endl;
    		break;
    	case lexical::INTEGER_ERROR:
    		std::cout << "整型错误" << std::endl;
    		break;
    	case lexical::REAL_ERROR:
    		std::cout << "实型错误" << std::endl;
    		break;
    	case lexical::DOT_ERROR:
    		std::cout << " . 符号错误" << std::endl;
    		break;
    	case lexical::IDENTIFIER_ERROR:
    		std::cout << "标识符错误" << std::endl;
    		break;
    	case lexical::WORD_ERROR:
    		std::cout << "单词错误" << std::endl;
    		break;
    	default:
    		std::cout << "未知错误" << std::endl;
    		break;
    	}
    	//运行状态false,
    	isRun = false;
    	return false;
    }
    
    //返回逆序入栈的单词流
    std::stack<int> lexical::GetWordStream() {
    	//栈底先加入#号,是代码结束标志
    	wordStream.push(vi["#"]);
    	//逆序遍历code:  line(int):word列表(int)
    	for (auto it1 = code.rbegin(); it1 != code.rend(); it1++) {
    		//逆序遍历单词 :<word列表>
    		for (auto it2 = (*it1).second.rbegin(); it2 != (*it1).second.rend(); it2++) {
    			//单词流逆序压栈,这样出栈的时候才是顺序的
    			wordStream.push(*it2);
    		}
    	}
    	//返回结果,外面更新loadfile f的wordstream
    	return wordStream;
    }
    //输出代码,以数字的方式【转string】
    void lexical::CodePrint() {
    	std::cout << "*******Code********" << std::endl;
    	for (auto s : code) {
    		std::cout << s.first << "|";
    		for (auto v : s.second) {
    			std::cout << vs[v] << " ";
    		}
    		std::cout << std::endl;
    	}
    }
    //输出单词流栈:出栈输出
    void lexical::WordStreamPrint() {
    	std::stack<int> p = wordStream;
    	std::cout << "*******WordStream********" << std::endl;
    	while (!p.empty()) {
    		std::cout << vs[p.top()] << " ";
    		p.pop();
    	}
    	std::cout << std::endl;
    }
    //获取运行状态,外面更新loadfile f的isRun
    bool lexical::GetRun() {
    	return isRun;
    }
    //获取代码行,外面更新loadfile f的line_count
    std::map<int, int> lexical::GetCodeLine() {
    	//count :: size   代表当前第line行,共有 size 个单词 ==>用来报错显示的
    	std::map<int, int>cl;
    	//初始化cl,当前行的单词 = 0
    	for (auto l : code_s) {
    		cl[l.first] = 0;
    	}
    	//赋值每一行的单词数量
    	for (auto c : code) {
    		cl[c.first] = c.second.size();
    		//std::cout << c.first << "," << cl[c.first] << std::endl;
    	}
    	//获取代码行,外面更新loadfile f的line_count
    	return cl;
    }
    

     

  5. Parser.hpp
    #pragma once
    #ifndef __PARSER_H__
    #define __PARSER_H__
    #endif // !__PARSER_H__
    #include <iostream>
    #include <fstream>
    #include <map>
    #include <vector>
    #include <regex>
    #include <stack>
    #include <string>
    #include "LoadFile.hpp"
    class parser {
    public :
    	bool isShowStack = false;
    private:
    	//枚举语法错误类型
    	enum ERROR
    	{
    		//默认错误
    		DEFAULT_ERR = 1,
    		//语法错误
    		SYNTAX_ERR,
    		//缺少符号
    		MISSYMBOL_ERR, 
    	};
    	/*
    		wf_int  左部int:<行号int>
    		wf_line 行号int:<符号集合int>
    		wf_follow 
    	*/
    	std::map<int, std::vector<int>> wf_int, wf_line; //序列:line   vn:line【vn】
    	//非终结符、终结符 符号string 转 int
    	std::map<std::string, int>Vi;
    	//非终结符、终结符 符号int 转 string
    	std::map<int, std::string>Vs;
    	/*
    		first:存放first集合,  左部非终结符int:first集合的终结符int
    		follow:存放follow集合,右部的非终结符int:follow集合的终结符int
    		select:存放select集合,行号int:select集合的终结符int
    		
    	*/
    	std::map<int, std::vector<int>>first, follow, select;
    	//标记follow集合是否被改变  、 运行标志
    	bool follow_isChange,isRun;
    	//存放预测分析表 (非终结符int,终结符int):行号int
    	std::map<std::pair<int, int>, int> analy;
    	//符号预测栈,单词流
    	std::stack<int>preStack, wordStream;
    	//line :size
    	std::map<int, int>line_count;
    	
    private:
    	//检测当前符号s是否是非终结符
    	bool CheckIsVn(std::string s);
    	//获取文件内容,读loadfile f的数据
    	void GetFile(loadFile file);
    	//判断非终结符vn是否可以推导出 空
    	bool GetIsNull(int vn);
    	//获取first集合
    	void ReFirst(int key);
    	//将序号为id的符号,加入ve的列表中,同时去重
    	void AddWithoutRep(int id, std::vector<int>* ve);
    	//判断是否已经求解完毕
    	bool GetfollowVn(int vn);
    	//获取左部非终结符:行号
    	int GetVnLeft(int line);
    	//获取first集合
    	void GetFirst();
    	//获取Follow集合
    	void GetFollow();
    	//获取Select集合
    	void GetSelect();
    	//获取分析预测表
    	void GetAnaly();
    	//匹配单词流
    	void GetRes();
    	//打印匹配结果
    	void Print();
    	//报错处理:
    	bool CheckError(int x, int y, int count);
    public:
    	//构造函数,获取loadfile f的数据
    	parser(loadFile file);
    	//运行入口
    	void run();
    	//打印测试
    	void wordStreamPrint();
    	void firstPrint();
    	void followPrint();
    	void selectPrint();
    	void analyListPrint();
    	//返回运行状态
    	bool GetRun();
    
    
    };

     

  6. Parser.cpp
    #include "Parser.hpp"
    parser::parser(loadFile file) {
    	isRun = file.isRun;
    	if (isRun) {
    		GetFile(file);
    		if (!isRun) {
    			std::cout << "parser : getFile_err" << std::endl;
    			return;
    		}
    	}
    	
    	if (isRun) {
    		GetFirst();
    		if (!isRun) {
    			std::cout << "parser : getFirst_err" << std::endl;
    			return;
    		}
    	}
    	
    	if (isRun) {
    		GetFollow();
    		if (!isRun) {
    			std::cout << "parser : getFollow_err" << std::endl;
    			return;
    		}
    	}
    	
    	if (isRun) {
    		GetSelect();
    		if (!isRun) {
    			std::cout << "parser : getSelect_err" << std::endl;
    			return;
    		}
    	}
    	
    	if (isRun) {
    		GetAnaly();
    		if(!isRun) {
    			std::cout << "parser : getAnaly_err" << std::endl;
    			return;
    		}
    	}
    	
    	
    	
    }
    bool parser::GetRun() {
    	return isRun;
    }
    //获取文件数据
    void parser::GetFile(loadFile f) {
    	//获取文法   Vn左部:<line>
    	parser::wf_line = f.wenfa_line;
    	//获取文法   line:<当前行右部符号列表>
    	parser::wf_int = f.wenfa_int;
    	//获取文法   拿到vs,用来把符号的int类型的序号,转化为字符串 => Vs[1] = "main"
    	parser::Vs = f.vs;
    	//获取文法   拿到逆序入栈的单词流
    	parser::wordStream = f.wordStream;
    	//获取文法   拿到 行 与 符号 的关系: count:line   第count个符号在第line行,显示报错行   
    	parser::line_count = f.line_count;
    	//判断上面的属性,是否赋值成功
    	if (!(!wf_line.empty() && !wf_int.empty() && !Vs.empty() && !line_count.empty())) {
    		//有错误就结束
    		isRun = false;
    	} 
    }
    
    //语法分析器运行入口
    void parser::run() {
    	//判断标志isRun,程序是否正在运行
    	if (isRun) {
    		//调用函数:匹配单词流
    		GetRes();
    		//输出报错位置,结束程序
    		if (!isRun) {
    			std::cout << "parser : getRes_err" << std::endl;
    			return;
    		}
    	}
    	
    	if (isRun) {
    		//打印:匹配成功!
    		Print();
    		//输出报错位置,结束程序
    		if (!isRun) {
    			std::cout << "parser : Print_err" << std::endl;
    			return;
    		}
    	}
    	
    	
    }
    //输出单词流
    void parser::wordStreamPrint() {
    	//输出测试样例
    	std::cout << "****wordStream******" << std::endl;
    	std::stack<int> tmp = wordStream;
    	while (!tmp.empty()) {
    		std::cout << Vs[tmp.top()]<<" ";
    		tmp.pop();
    	}
    	std::cout << std::endl;
    	
    }
    //输出first集
    void parser::firstPrint() {
    	//输出测试样例
    	std::cout<<"****fitst******" << std::endl;
    	for (auto k : first) {
    		std::cout << Vs[k.first] << "|";
    		for (auto v : k.second) {
    			std::cout << Vs[v] << " ";
    		}
    		std::cout << std::endl;
    	}
    }
    //输出follow集
    void parser::followPrint() {
    	//输出测试样例
    	std::cout << "****follow******" << std::endl;
    	for (auto k : follow) {
    		std::cout << Vs[k.first] << "|";
    		for (auto v : k.second) {
    			std::cout << Vs[v] << " ";
    		}
    		std::cout << std::endl;
    	}
    }
    //输出select集
    void parser::selectPrint() {
    	//输出测试样例
    	std::cout << "****select******" << std::endl;
    	for (auto k : select) {
    		std::cout << k.first << "|";
    		for (auto v : k.second) {
    			std::cout << Vs[v] << " ";
    		}
    		std::cout << std::endl;
    	}
    }
    //输出预测分析表
    void parser::analyListPrint() {
    	
    	
    	//获取终结符与非终结符
    	std::vector<int> vn, vt;
    	for (auto vs : Vs) {
    		if (vs.first == 0) continue;
    		if (vs.first < 100) {
    			vt.push_back(vs.first);
    		}
    		else {
    			vn.push_back(vs.first);
    		}
    	}
    	//输出测试样例
    	std::cout << "****analy******" << std::endl;
    	//输出
    	std::cout << "表" << "\t";
    	for (int j = 0; j < vt.size(); j++) {
    		std::cout << vt[j] << "\t";
    	}
    	std::cout << std::endl;
    
    	for (int i = 0; i < vn.size(); i++) {
    		std::cout << "(" << vn[i] << ")\t";
    		for (int j = 0; j < vt.size(); j++) {
    			//cout<<Vs[vt[j]] << " ";
    			std::pair<int, int> p = std::make_pair(vn[i], vt[j]);
    			std::cout << analy[p] << "\t";
    		}
    		std::cout << std::endl;
    	}
    	
    }
    //检测是否是非终结符
    bool parser::CheckIsVn(std::string s) {
    	//终结符<100    非终结符>=100
    	//vi 把字符串 转为 序号int
    	return !(Vi[s] < 100);
    	
    }
    //求解first
    void parser::GetFirst() {
    	//遍历每一行,first 是左部(int),second 是当前行符号列表(int)
    	for (auto vn : wf_int) {
    		//key 当前行
    		int key = vn.first;
    		//对于当前行,求解first集合
    		ReFirst(key);
    	}
    }
    
    //判断当前非终结符(int),是否存在空
    bool parser::GetIsNull(int vn) {
    	//遍历当前非终结符的,所有右部列表
    	for (auto line : wf_int[vn]) {
    		//line 是 当前非终结符的,某一个右部,正常文法中用“|”分开,遍历右部的所有可能
    		//判断当前右部,line行的所有符号
    		for (auto value : wf_line[line]) {
    			//存在空就返回true
    			if (value == 0) {
    				return true;
    			}
    		}
    	}
    	//不存在空就返回false
    	return false;
    
    }
    
    
    //求解first,对于当前行
    void parser::ReFirst(int key) {
    	//遍历当前非终结符的所有文法
    	for (auto line : wf_int[key]) {
    		//遍历某一行文法
    		for (auto value : wf_line[line]) {
    			//终结符
    			if (value < 100) {
    				//加入到first集合
    				AddWithoutRep(value, &first[key]);
    				break;
    			}
    			//非终结符
    			else if (value >= 100) {
    				//获取非终结符的first
    				ReFirst(value);
    				//添加非终结符的first
    				for (auto f : first[value]) {
    					AddWithoutRep(f, &first[key]);
    				}
    				//判断是否有空
    				if (GetIsNull(value)) {
    					//遍历下一个符号
    					continue;
    				}
    				else {
    					//遍历下一条文法
    					break;
    				}
    			}
    		}
    	}
    }
    //id序号加入ve容器,同时去重
    void parser::AddWithoutRep(int id, std::vector<int>*ve) {
    	for (auto v : *ve) {
    		if (v == id) {
    			return;
    		}
    	}
    
    	(*ve).push_back(id);
    
    	//标记follow中是否变化,follow循环停止标志
    	follow_isChange = true;
    }
    //求解follow
    
    //判断是否已经求解完毕
    bool parser::GetfollowVn(int vn) {
    	for (auto f : follow[vn]) {
    		if (f < 100)continue;
    		//cout << "----" << Vs[f]<<"-----" << endl;
    		return false;
    	}
    	return true;
    }
    
    //求follow集
    void parser::GetFollow() {
    	//初始化,在入口符号中,加入#
    	follow[100].push_back(99);
    	//初始化过程:将每个非终结符的follow集合,不加符号区分(终结符、非终结符)的加入follow集合中
    	// 最后不断迭代,求出follow集合
    	// 
    	
    	//遍历每一行符号
    	for (auto l : wf_line) {
    		//拿到符号列表
    		std::vector<int> ve = l.second;
    		//获取符号数量
    		int s = ve.size();
    		//遍历每个符号
    		for (auto v : ve) {
    			//终结符不需要管
    			if (v < 100)continue;
    			//处理非终结符
    			
    			//v是当前列表中最后一个符号
    			if (v == ve[s - 1]) {
    				//获取左部(参数:行号)
    				int t = GetVnLeft(l.first);
    				//如果左部和最后一个符号相同,就不管
    				if (t == v) continue;
    
    				//不相同就把左部加入follow集
    				AddWithoutRep(t, &follow[v]);
    				
    			}
    			//v不是当前列表中最后一个符号
    			else {
    				//找到v在列表中的位置pos
    				int pos = std::find(ve.begin(), ve.end(), v) - ve.begin();
    				//如果v下一个符号是终结符
    				if (ve[pos + 1] < 100) {
    					//遇到终结符,就加入follow【v】,不需要再继续求下面了
    					AddWithoutRep(ve[pos + 1], &follow[v]);
    					
    				}
    				//如果v下一个符号是非终结符
    				else {
    					//加入下一个符号的first集合
    					for (auto f : first[ve[pos + 1]]) {
    						
    						if (f == 0) {
    							//遇到空的时候,v的下一个元素的follow集合 加入到 当前follow集合,
    
    							//如果是相同的,就不需要加入
    							if (ve[pos + 1] == v) continue;
    							AddWithoutRep(ve[pos + 1], &follow[v]);
    							//cout << "follow(" << Vs[ve[pos + 1]] << ") ";
    						}
    						else {
    							//first集合的非空元素,加入follow[v]
    							AddWithoutRep(f, &follow[v]);
    							//cout << Vs[f] << " ";
    						}
    					}
    				}
    			}
    			//cout << endl;
    		}
    	}
    	//初始化结束,开始迭代follow集合,直到集合中,不存在非终结符为止。标志位是follow_isChange
    
    	while (true) {
    		
    		//循环退出标志
    		bool isF1 = false;
    
    		//遍历所有follow集合
    		for (auto f : follow) {
    
    			//遍历follow集合,直到不出现非终结符
    			for (auto v : f.second) {
    				//当前follow集合中,是否还有需要处理的非终结符:标志
    				bool isF2 = false;
    
    				//终结符不需要处理
    				if (v < 100) {
    					continue;
    				}
    				//处理非终结符
    				else {
    					//含有非终结符,就说明要替换,没有完成,标志改为true,当前循环不退出
    					isF1 = true;
    					//检查当前非终结符v中,follow[v]中是否存在非终结符。
    					bool isVnInFollow = GetfollowVn(v);
    					
    					//保证向上替换原则在下面
    					/*
    						运行逻辑: 
    							1. 当前遍历的follow集合是follow[f.first]
    							2. v是当前follow[f.first]的一个非终结符
    							3. 判断 v 的follow[v]中,是否含有非终结符。
    							4. 如果follow[v]中,有非终结符,就判断是否要把非终结符替换,按照向上替换原则
    						向上替换原则:(大写是非终结符)
    							1.follow[f.first] = {a,b,c,V}
    							2.follow[V] = {a,b,c}
    							3.判断非终结符V的follow集,是否需要替换follow[f.first]中的V
    								(1)如果follow[V]中,存在非终结符,那么就进行替换,在follow[f.first]中消除V
    								(2)如果follow[V]中,不存在非终结符,采取向上替换:如果f.first 的排序在 V 的下面【f.first >= v】, 那么就把follow[f.first]中的V 替换为 follow[V]
    
    							
    					*/
    					if (isVnInFollow || f.first >= v) {
    
    						//存放follow[f.first]替换v后的follow集
    						std::vector<int>tmp;
    						//遍历follow[f.first],先去除 符号v
    						for (auto t : follow[f.first]) {
    							if (t == v)continue;
    							tmp.push_back(t);
    						}
    						//添加成功的标志
    						follow_isChange = false;
    						
    						//遍历follow[v],添加到tmp集合
    						for (auto t : follow[v]) {
    							//如果和左部相同,就下一个
    							if (t == f.first) continue;
    							//添加当前符号,添加成功,follow_isChange = true;
    							AddWithoutRep(t, &tmp);
    							//判断是否添加成功
    							if (follow_isChange) {
    								//isF2代表添加成功,然后复原follow_isChange值
    								follow_isChange = false;
    								isF2 = true;
    							}
    						}
    						//更新新的follow[f.first]集
    						follow[f.first] = tmp;
    					}
    				}
    			}
    		}
    		//当前循环解决了一次非终结符问题,所以标志 = true , 不退出
    		//当所有follow集合中,都不存在非终结符的时候,退出follow集求解
    		if (!isF1) {
    			break;
    		}
    	}
    
    }
    //获取左部:参数:行int
    int parser::GetVnLeft(int line) {
    	//遍历 wf_int   左部int:<行号int>
    	for (auto vn : wf_int) {
    		//遍历左部为vn.first 的 所有行
    		for (auto l : vn.second) {
    			//判断当前行是否和传入的参数相同
    			if (l == line) {
    				//是这一行,就返回左部
    				return vn.first;
    			}
    		}
    	}
    	//返回错误 -1 
    	return -1;
    }
    
    //求select集
    void parser::GetSelect() {
    	//wf_line  行号int:<符号集合int>
    	//遍历每一行
    	for (auto l : wf_line) {
    		//key 是 行号
    		int key = l.first;
    		//是否含有空:标志
    		bool isNull = true;
    
    		//当前行,遍历每一个符号
    		for (auto v : l.second) {
    			/*
    				求整体行的first(整体)集合
    				1.整体可以推出空,就加入follow(左部),元素加入去空,求解成功
    				2.不能推出空,遇到终结符,求解成功
    			*/
    
    			//遇到终结符
    			if (v < 100) {
    				//是空就下一个符号,标记遇到空了
    				if (v == 0) {
    					isNull = true;
    					continue;
    				}
    				//终结符直接加入,并且退出,当前行已经结束
    				isNull = false;
    				//符号v加入select[line] ,去重
    				AddWithoutRep(v, &select[l.first]);
    				break;
    			}
    			else {
    				//是非终结符
    
    				//标记是否存在空
    				int flag = false;
    				//先加入first集合
    				for (auto f : first[v]) {
    					//判断是否存在空,进行标记
    					if (f == 0) {
    						flag = true;
    						continue;
    					}
    					//加入非空元素
    					AddWithoutRep(f, &select[l.first]);
    				}
    				//标记存在空,也就是可以遍历下一个符号
    				isNull = flag;
    				//不存在空,就结束
    				if (!isNull) {
    					break;
    				}
    			}
    		}
    		//存在空,就加入follow[左部]
    		if (isNull) {
    			for (auto f : follow[GetVnLeft(l.first)]) {
    				AddWithoutRep(f, &select[l.first]);
    
    			}
    		}
    
    	}
    
    }
    
    //分析预测表
    void parser::GetAnaly() {
    
    	//获取终结符与非终结符,vn非终结符int  vt终结符int
    	std::vector<int> vn, vt;
    
    	//vs所有符号 :  序号int:符号string
    	for (auto vs : Vs) {
    		//空 不处理
    		if (vs.first == 0) continue;
    		//终结符加入vt
    		if (vs.first < 100) {
    			vt.push_back(vs.first);
    		}
    		//非终结符加入vn
    		else {
    			vn.push_back(vs.first);
    		}
    	}
    	//初始化预测分析表-1
    	/*
    		analy[<非终结符,终结符>] = -1
    	*/
    	for (auto n : vn) {
    		for (auto t : vt) {
    			//map<map<int,int>,int>
    			std::pair<int, int> p = std::make_pair(n, t);
    			analy[p] = -1;
    		}
    	}
    
    	//构建表
    	/*
    		遍历select集 : 行int:<可推导的符号集合int>
    		原理:
    			select集代表,当前行可以推导出的所有终结符集合
    			也就是说,拿到当前行的 左部非终结符 ,和右部可以推导出的 终结符 ,对应当前 行号
    			analy[<非终结符,终结符>] = 行号
    	*/
    
    	//遍历select集合   行int:<可推导的符号集合int>
    	for (auto s : select) {
    		//获取当前行左部 key
    		int key = GetVnLeft(s.first);
    		//遍历当前行,select右部可以推导出的终结符
    		for (auto v : s.second) {
    			//analy[<key,v>] = f.first(行号)
    			std::pair<int, int> p = std::make_pair(key, v);
    			analy[p] = s.first;
    		}
    	}
    	
    }
    
    //逆序符号栈
    void parser::GetRes() {
    	//初始化预测栈
    	/*
    		preStack = {程序#}
    	*/
    	preStack.push(99);
    	preStack.push(100);
    
    	//记录遍历单词的个数,用来计算报错位置
    	int count = 0;
    	//开始匹配
    	while (true) {
    		//拿到预测符号栈 和 单词流 的最顶部 符号
    		int x = preStack.top();
    		int y = wordStream.top();
    
    		//输出测试,外部可以控制显示:符号栈对比过程
    		if (isShowStack) {
    			std::cout << Vs[x]<<"("<<x<<")" << " : " << Vs[y] << std::endl;
    		}
    		//如果预测符号栈推出 空 , 那么去掉空符号,继续匹配下一个
    		if (x == 0) {
    			preStack.pop();
    			continue;
    		}
    		//如果预测符号栈和单词流可以匹配:注意匹配成功的必须是终结符(<100)
    		if (x == y && x < 100) {
    			//判断是否是退出标志 #(99)
    			if (x == 99) {
    				break;
    			}
    			//不是退出标志,正常匹配,去除符号栈和单词流顶部符号
    			preStack.pop();
    			wordStream.pop();
    			//匹配符号计数
    			count++;
    			continue;
    		}
    		else if (x != y && x < 100) {
    			//如果 预测符号栈 和 单词流 都是终结符,但是不能匹配成功,就说明出错了
    			CheckError(x,y,count);
    			break;
    		}
    		//如果预测符号栈当前符号是非终结符,继续推导出终结符
    		if (x >= 100) {
    			//获取预测分析表数据
    			//x是非终结符,y是终结符  查表是否可以推导
    			std::pair<int, int> p = std::make_pair(x, y);
    			//如果是-1,就是无法推导,报错
    			if (analy[p] == -1) {
    				//非终结符  终结符  当前单词的计数序号
    				CheckError(x,y,count);
    				break;
    			}
    			else {
    				//非终结符,可以推导,就把这个非终结符替换 analy[p] = line ,把line行的符号集逆序入栈
    				//非终结符出栈
    				preStack.pop();
    				//符号集逆序入栈
    				for (auto it = wf_line[analy[p]].rbegin(); it != wf_line[analy[p]].rend(); it++) {
    					preStack.push(*it);
    				}
    				continue;
    			}
    		}
    	}
    	
    }
    
    //报错  非终结符,终结符,位置
    bool parser::CheckError(int x,int y,int count) {
    	//运行标志:false
    	isRun = false;
    	
    	//遍历  行号:size
    	int line = -1;
    	for (auto l : line_count) {
    		//line 行号
    		line = l.first;
    		//判断位置是否超过当前行的size
    		if (count > l.second) {
    			//超过了说明不是当前行,减去当前行的size,继续遍历下一行
    			count -= l.second;
    			continue;
    		}
    		//没超过size 说明在这一行报错
    		break;
    	}
    	//报错类型key
    	ERROR key = DEFAULT_ERR;
    	
    	//如果x,y都是终结符
    	if (x < 100 || y<100) {
    		//是语法错误
    		key = SYNTAX_ERR;
    	}
    	else {
    		//否则是缺少符号错误
    		key = MISSYMBOL_ERR;
    	}
    	
    	//输出报错
    	std::cout << "语法错误: ";
    	
    	std::vector<int> tmp;
    	
    	//检测错误类型
    	switch (key)
    	{
    		//默认错误
    	case parser::DEFAULT_ERR:
    		std::cout << "默认错误: "<<Vs[x]<<" : "<<Vs[y] << " (符号错误:行";
    		break;
    	case parser::SYNTAX_ERR:
    		//遍历分析预测表
    		for (auto a : analy) {
    			//存在当前可能。不是-1
    			if (a.second != -1) {
    				//拿到当前pair,检测非终结符
    				std::pair<int, int> p = a.first;
    				//检测非终结符是否相等
    				if (p.first == x) {
    					//终结符加入
    					tmp.push_back(p.second);
    				}
    			}
    		}
    		//存在终结符列表
    		if (!tmp.empty()) {
    			std::cout << "缺少符号,可能是:";
    			//输出可能出现的终结符符号
    			for (auto t : tmp) {
    				std::cout << Vs[t] << " ";
    			}
    			std::cout << " (缺少符号:行";
    		}
    		else {
    			std::cout <<"未知语法:"<<Vs[y] << " (无效语法:行";
    		}
    		
    		break;
    	case parser::MISSYMBOL_ERR:
    		std::cout <<Vs[y] << " (符号错误:行";
    		break;
    	default:
    		std::cout  << " (未知错误:行";
    		break;
    	}
    	std::cout << line << ");" << std::endl;
    	return false;
    }
    
    
    //输出测试
    void parser::Print() {
    	std::cout << "匹配成功!" << std::endl;
    }
    

     

  7. main.cpp
    #include <iostream>
    #include "LoadFile.hpp"
    #include "Parser.hpp"
    #include "Lexical.hpp"
    using namespace std;
    //文件存储路径【相对路径】
    
    std::string path = "../src/";
    
    /// <summary>
    /// 主函数,程序的入口
    /// </summary>
    /// <returns></returns>
    int main() {
    
    	
    	//获取文件路径
    	loadFile f(path);
    	//读取文件:按照文件名
    	f.run("Vn.txt", "Vt.txt", "wenfa.txt", "test.txt");
    	
    
    	//f.VnPrint();
    	//f.VtPrint();
    	//f.WenfaPrint();
    	//f.CodePrint();
    
    	//词法分析器:分析读入的数据
    	lexical le(f);
    	//le.CodePrint();
    	//更新输入的符号栈
    	f.wordStream = le.GetWordStream();
    	f.line_count = le.GetCodeLine();
    	f.isRun = le.GetRun();
    	
    	//le.WordStreamPrint();
    
    	
    	
    	//语法分析器:初始化集合、分析预测表
    	parser p(f);
    	//p.wordStreamPrint();
    	//p.firstPrint();
    	//p.followPrint();
    	//p.selectPrint();
    	//p.analyListPrint();
    	//p.isShowStack = true;
    	
    	//开始匹配字符
    	p.run();
    	f.isRun = p.GetRun();
    
    	if (!f.isRun) {
    		//cout << "中途报错" << endl;
    	}
    
    	return 0;
    }
    
    

     

 

 

  • 27
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值