1.X语言编译器(简单c语言):词法分析+语法分析部分
2.全文31810字
标签
#本人初学者#,#仅供学习交流,其他用途与本人无关#,#侵权必删#,#转载标明出处#,#C++控制台#,#编译原理#,#词法分析#,#语法分析#
声明:
1.欢迎各位朋友转载,但是请注明文章出处,附上文章链接。
2.所有文章均为个人学习心得,部分学习视频过程中的截图如有侵犯到作者隐私,可联系我删除。
3.欢迎各位朋友多交流学习,本人对自己的文章进行不定期的修正与更新,因此请到我的博客首页查看某篇章的最新版本。
程序介绍
根据文法的规则,对测试代码,进行词法分析和语法分析过程,并输出结果。
效果展示
ps.只有词法分析器【关键字检测、变量名是否合法】和语法分析器【是否符合语法规则】,并不包含语义分析器【意味着,不能检查变量是否定义了,无法实现运算功能】
1.测试代码符合文法规则
2.变量名命名错误,提示报错
ps.这里的报错行提示,有一些bug,可能行数不是很准确,暂时没改
3.语法错误
ps.语法错误是根据文法定义得出的,文法中不存在这条规则,那么就会报错。
使用方法
首先,更改main.cpp的path路径【大概是第8行】,让其可以读取到文件【Vn.txt、Vt.txt、wenfa.txt、test.txt】的位置。
其次,可以更改test.txt中的代码,这个是我们要测试的代码。
最后,运行main.cpp,验证test.txt中的代码,是否符合词法和语法的规则。
基础知识(核心内容)
ps.都是个人理解,官方/书中的解释,可以去网络上搜索。
- 终结符Vt:
- 概述:终结符就是你最后输入的,测试代码的抽象集合。也就是说,你输入的那些代码,都可以转化为终结符,用终结符来表示。
- 终结符列表【个人的列表,根据你们的需求自行更改】:定义终结符的序号小于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 # - 举例:
举例代码: int a = 1.1;
int 是 终结符 003
a 是 标识符 021
= 是 终结符 035
1.1 是 实型常量 023
; 是 终结符 015
ps. 在我的文法中,我规定这条语句是错误的,因为我规定,必须先定义再赋值,不能定义变量的时候赋值。
- 非终结符Vn:
- 概述:非终结符,个人认为,是用来表示某一[部分/功能]的描述,通常我们想要实现这个[部分/功能],可以根据文法,可以知道这个非终结符,能推导出其他符号,这些符号是终结符或非终结符的集合。
- 非终结符列表【个人的列表,根据你们的需求自行更改】:定义终结符的序号大于等于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 常量 -
举例
举例文法:程序—>main(){语句部分}
程序 是 非终结符 100这个非终结符可以推导出其他符号
main 是 终结符 001
( 是 终结符 011
) 是 终结符 012
{ 是 终结符 013语句部分 是 非终结符 101
} 是 终结符 014ps.也就是说,我们想要是先出“程序”这个部分,就要按照文法,推导出它的写法规则。
- 文法:
- 概述:一般来说,你的每一行测试代码,都是由文法转化而来,判断代码是否正确的标准,就是判断代码是否符合你的文法。文法就是你的语法规则,每条文法由非终结符Vn和终结符Vt组成。
- 文法列表【个人的列表,根据你们的需求自行更改】:定义每条文法都有固定的格式,并且每条文法占一行。
ps.文法格式: 左部非终结符 —> 右部符号(非终结符与终结符组成)程序—>main(){语句部分}
语句部分—>声明部分 语句序列
声明部分—>变量定义;声明部分
声明部分—>ε
变量定义—>基本类型 标识符列表
标识符列表—>标识符 标识符列表1
标识符列表1—>,标识符列表
标识符列表1—>ε
基本类型—>int
基本类型—>float
语句序列—>语句行 语句序列
语句序列—>ε
语句行—>语句 ;
语句—>赋值语句
赋值语句—>标识符 =表达式
表达式—>项 表达式1
表达式1—>低级运算符 项 表达式1
表达式1—>ε
项—>因子 项1
项1—>高级运算符 项
项1—>ε
因子—>标识符
因子—>常量
因子—>(表达式)
低级运算符—>+
低级运算符—>-
高级运算符—>*
高级运算符—>/
常量—>整型常量
常量—>实型常量
ps. 常量—>整型常量|实型常量 是一个意思,只是给它分开写了。 -
举例:判断代码是否符合语法规则
比如判断代码: 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
这种多个变量的定义,也是符合文法规则的定义的。这个过程,就是我理解的,你的编译器的规则---文法。
-
first集
ps.代码并不唯一,你只要理解这个是怎么得出来的,可以随意编写。-
概述:对于每一个“左部”非终结符,都有一个终结符(包含空)的集合,对于你想进行这个部分(非终结符,左部),你首先要写哪个符号。
-
算法思路:对于相同的符号,不再重复加入
-
遍历所有的文法,对于每一个左部,都有一个first集;对于多个相同的左部,他们的合并才是它本身的first集
-
对于当前文法,我们要看文法的右部,对于右部的第一个符号,进行分析,直到分析出了一个终结符或ε(空),就结束了,把这个符号加入到这个左部的first集合。
-
重复上面操作,直到所有符号的终结符全部分析完毕。
-
-
举例:
对于以下文法,进行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’|ε
-
对于我的上面的文法,得出的first集是
程序|main
语句部分|int float ε 标识符
声明部分|int float ε
变量定义|int float
标识符列表|标识符
标识符列表1|, ε
基本类型|int float
语句序列|标识符 ε
语句行|标识符
语句|标识符
赋值语句|标识符
表达式|标识符 整型常量 实型常量 (
表达式1|+ - ε
项|标识符 整型常量 实型常量 (
项1|* / ε
因子|标识符 整型常量 实型常量 (
低级运算符|+ -
高级运算符|* /
常量|整型常量 实型常量
-
-
follow集
ps.代码并不唯一,你只要理解这个是怎么得出来的,可以随意编写-
概述:对于每一行文法,它的右部的每一个非终结符,预测这个非终结符的下一个符号是什么,就是我们的follow集,它是终结符的集合(不含空)。
-
算法思路:对于相同的符号,不再重复加入
-
首先要在开始文法"程序(100)"中,加入 #(099) ,也就是 follow(程序) = {#}
-
遍历每一行文法,对于当前行文法来说,遍历这条文法的右部
-
对于遍历出的非终结符,我们看它的下一个符号,这样就有很多可能
-
下一个符号是 终结符:直接加入终结符到 这个非终结符的follow集中
-
下一个符号是 非终结符1 :把first(非终结符1)中的所有元素,加入到这个非终结符的follow集中,但是不加入ε,如果存在空,就还要进行下面的操作(后面没有符号)。↓
-
后面没有符号:那么就找到这条文法的左部,并且加入follow(左部),到非终结符的follow集中。注意,如果这个非终结符和左部相同的时候,那么可以不进行这步操作。
-
-
重复进行上述 2和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集。
-
对于我的上面的文法,得出的follow集:
程序|#
语句部分|}
声明部分|标识符 }
变量定义|;
标识符列表|;
标识符列表1|;
基本类型|标识符
语句序列|}
语句行|标识符 }
语句|;
赋值语句|;
表达式|) ;
表达式1|) ;
项|+ - ) ;
项1|+ - ) ;
因子|* / + - ) ;
低级运算符|标识符 整型常量 实型常量 (
高级运算符|标识符 整型常量 实型常量 (
常量|* / + - ) ;
-
-
select集:对于相同的符号,不再重复加入
-
概述:select集合,就是对于当前行,求解右部整体的first集合,select集是终结符的集合(不包含空)
-
算法思路:
-
遍历所有文法,对于每一行文法,求解select(行号) = first(右部)
-
求解右部的frist集合,就要遍历每一个符号
-
如果当前符号是终结符,那么直接加入到select集合,并且结束
-
如果当前符号是非终结符,那么直接加入first(非终结符),如果first集合中含有空,就要求解下一个符号的first集合,直到遇到终结符结束
-
如果最后的右部,first集合中还是含有空,select(行号)中,就要加入follow(左部)
-
-
举例
(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集,所以只需要遍历一次即可。 -
对于我的上面的文法,得出的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|实型常量
-
-
-
预测分析表
-
根据selct集合,构建出来的表,表头是 非终结符 * 终结符 ,然后根据selct集合填写序号。(每一行的左部)* (selct集合) == (行号)
-
思路:
-
初始化预测分析表,所有值置成-1
-
遍历每一行文法,对于当前行文法,拿到文法的左部(非终结符),根据select集合,拿到当前行可以推导出的所有终结符的集合,在这个表格中,对于每一个交汇点,填写对应的文法行号。
-
-
对于我的上面的文法,得出的分析预测表:对于小于100的值,当做终结符,不小于100的值,当做非终结符
-
-
符号栈
-
符号栈概述:把测试代码,进行了词法分析以后,所有单词转化为终结符,并且把结果压入栈中,之后对比预测栈中的文法。
-
预测栈概述:预测栈中,首先压入两个单词,{100,99},100是程序的入口,99是程序的结束标志。预测栈中的非终结符,总是会根据预测分析表,转化为非终结符,与符号栈中的非终结符对比,如果全部匹配成功,则没有语法错误。
-
对比过程:
流程简述:
测试代码:
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个文件。
- 其中3个是进行声明hpp头文件
Lexical 词法分析
LoadFile 文件处理
Parser 语法分析 - 3个是功能实现cpp文件 + 1个程序入口cpp文件
main : 程序的入口,注释的文本可以进行输出测试,运行程序需要改变第8行的文件路径。 - 还有3个文法txt文本文件 + 1个测试代码txt文本文件
test : 你的测试代码
Vn:非终结符定义
Vt:终结符定义
wenfa:文法定义
代码实现
- 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); };
- 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; } }
- 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(); };
- 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; }
- 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(); };
- 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; }
- 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; }