文章目录
1. 实验目的
通过实验,掌握并实现算符优先分析算法,完成算术表达式的算符优先文法的算符优先分析过程。
2. 实验要求
(1)构造该算符优先文法的优先关系矩阵或优先函数;
(2)输入串应是词法分析的输出二元式序列,即某算术表达式“专题1”的输出结果。输出为输入串是否为该文法定义的算术表达式的判断结果。
(3)算符优先分析过程应能发现输入串出错。
(4)设计两个测试用例(尽可能完备,正确和出错),并给出测试结果;
(5)考虑根据算符优先文法构造算符优先关系矩阵,包括FIRSTVT 和LASTVT 集合,并添加到你的算符优先分析程序中。
3. 程序实现
3.1. 相关环境介绍
操作系统:window 10 21H2
开发环境:Clion-2022.2.1-Windows
编译器:mwing-10.0
3.2. 主要数据结构
主要是单词的信息保存,建立了一个struct
01:struct Keyword{
02: string notation;
03: int class_num;
04: int line;
05: Keyword(string str, int num, int line_){
06: notation = str;
07: class_num = num;
08: line = line_;
09: }
10: Keyword(char* str, int num, int line_){
11: notation = string(str);
12: class_num = num;
13: line = line_;
14: }
15: Keyword(char str, int num, int line_){
16: notation = str;
17: class_num = num;
18: line = line_;
19: }
20:};
其中notation为单词的值,class_num 为单词所属的类别,line是单词在源程序中的行号。
利用map容器,制作了以string为index的二维数组。
下边是以stl为基础建立的相关数据结构
21:map<string, map<string, int>> analyzer_table;
22:map<string, set<string> > firstVt;
23:map<string, set<string> > lastVt;
24:map<string, string> Vn;
25:map<string, string> Vt;
26:
27:map<string, vector<string>[ORMAXFORGRAMMAR] > grammar;
28:map<pair<string,vector<string>>, set<string>> first_alpha;
29:
30:vector<string> leftest_increase;
31:stack <string> analysis_stack;
annalzer_table是分析表,包括所有Vt符号的优先关系,同时,本人通过对文法的拓广,由原来的算法,直接获得了算术文法对#的优先关系。
firstVt和lastVt是保存着所有Vn符号的相应集合。Annlysis_stack为分析栈结构,在分析表驱动函数中用处颇多,leftest_increase为一个分析栈pop元素的记录结构,主要用来判断通过分析表和分析栈程序得到的短语是否为最左素短语,这将会和grammar的语法结构体Vt、Vn集之间相关联。
使用map结构获得由字符串结构作为类似index的表结构替代是实验3中的成果。这里使用map结构完成了ppt中讲授的以Vt或者Vn符号作为二维数组index的机器化。
3.3. 程序结构描述
3.3.1. 设计方法
考虑到程序的通用性,程序的输入流全部都是txt文件。首先读入grammar文件,其中包括文法和Vn以及Vt集的定义。之后,在程序中创建Vn集合Vt集,然后求的firstVt集合lastVt集,在这基础上构建出所有Vt的优先关系分析表。
接下来是和词法分析程序的联动,本程序可以直接调用词法分析程序,因此测试样例直接准备源代码待分析即可。当然,语法分析部分的输出当然还是词法分析的结果,也就是二元式,同时本程序为了报错的方便,在二元式的基础上加入了行号line,变成了三元式。其中的相应config配置依赖文件参考实验1,包括各种文件依赖。
3.3.2. 函数定义
create_Vn_Vt_grammar(grammar_path); 创建Vn Vt集和文法表。
create_firstVt(); 创建firstVt集。
create_lastVt(); 创建lastVt集
create_analyzer_table(); 创建分析表
analyzer_stack(need_to_analysis); 分析栈入口,即分析驱动程序。
int correct_prom() 语句符合文法提示。
int error_prom() 语句不合文法提示。
还有一众辅助函数,不表。
主要函数的编写参照课程讲述。
和课堂教授主要的区别在于分析栈函数。我们结合课堂讲授的程序流程图和基于stl的数据结构删去了goto语句,加入了标志符判断转移的来向,相对课堂上的ppt程序流程图,有所不同。
规约的产生式右部寻找与报错相对为原创。原理为暴力遍历。
4. 程序测试
Alu_test1.txt
这里一个文件中包含了6个测试样例。
32:
33:(a+c * b #
34:a + b #
35:(a+c b ) #
36:23434+34324+(a* b / c) +(a*b/ (2-2)) #
37:( a+ b c ) * 32 / a #
38:a- b * #
由于是从词法分析开始,下边给出中间的结果的输出:
图表 4 1 test1中词法分析结果
图表 4 2 符号类号文件(词法分析程序的输出结果的一部分)
图表 4 3config文件
其中包括了一众依赖文件的路径还有默认待分析文件路径,最下边是书写格式。
图表 4 4 grammar文件
Grammar文件主要是Vn Vt集还有文法的输入,下方是特殊说明。
词法分析程序输出仍然采用了三元组,多了一个行号,方便错误提示,更多的我们直接用了Keyword结构,这对结果没有影响。
图表 4 5 本程序结果,信息提示
通过信息提示可以看出,程序对4个测试样例完成的很好,结果正确。
等式中,实验指导书原文法标识符位置,可以出现数字常量。这是对文法的拓展。
5. 实验汇总
5.1. 技术难点及解决方案
实验本身的重要程序框图,已经在课堂上教授过了,主要是代码编写过程中的问题有些多,所幸最后都解决了。
实验代码编写完成之后,本人突然发现,实验指导的文法与正常编程语言的语法逻辑还是有一定区别的。其中i被定义为标识符,也就是变量,这在算法表达式中i出现的位置可以是标识符也可以是数字常量,甚至可以是一个表达式。这里我们在原有文法的基础上做了一点拓展,新增了一个n的Vt符号,作为数字常量的代指。这样我们的文法就变成了:
幸运的是,在修改文法后本人的实验3程序居然没有一点问题,这真是值得欣慰的事。
E' -> # E #
E -> E + T | E - T | T
T -> T * F | T / F | F
F -> ( E ) | i | n
当然其实我们也可以把i再拓展为表达式,这样其实是一种递归调用。
5.2. 实验感想和经验总结
这次实验主要的编码困难在于数据结构的建立。C++中的二维数组index都是整数,课堂上教授的分析表等表格都是字符作为index,这其中的转换是有难度的。最后,想到了使用STL中的map容器作为数据结构,二维数组就是map套map,还用了vector来存储文法产生式,这样就会出现“数据结构”一节中看似很复杂的容器嵌套结构。数据结构敲定之后,再定义出基本的操作方法,算法沿用课程中教授的,实验就不难完成了。
算法流程应该要和数据结构配合,课上讲解的算法流程图直接编写甚至goto语句,要原流程图分析得到算法原理,再结合新的数据结构给出新的算法流程图,再进行代码编写。
Clion debug变量监视,如果在变量创建之前开始监视,会出现问题。