具体代码已放至Github(仅供参考):
qxpBlog/Compiler_UESTC: 电子科技大学编译原理实验 (github.com)
具体实验过程如下:
一、实验内容及步骤:
1. 实验内容:
(1) 学习所提供的“表达式文法”的LR分析处理
理解 calc1.l, calc1.y, calc2.l, calc2.y的内容
在VSCode中建立工程,对calc3.l, calc3.y调试运行
(2) 学习lrgram.txt所提供的文法
与递归下降分析所提供的文法作比较
(3) 编写SysY语言所提供文法的LR语法分析程序
1) 编写生成“语法树”的相关程序,包括
bison源程序 lrparser.y
flex源程序 lrlex.l
语法树相关程序 ast.h 和 ast.c
2. 实验步骤:
(1)在VSCode中建立工程,对calc3.l, calc3.y调试运行,在终端输入表达式“1+3*5-6-9+5”,程序运行结果如图1-1所示。
图 1-1 calc3.l、 calc3.y调试运行结果
(2)编写生成“语法树”的相关程序
1) 编写Bison源程序Irparser.y(报告中只展示核心代码部分)
Bison源程序的定义部分中,包含的头文件、函数声明、全局变量声明以及联合类型全局变量yylval的成员变量声明如图2-1(a)所示。
图 2-1(a) Bison源程序的定义部分
定义部分中,使用%token定义的终结符以及使用%type定义的非终结符,如图2-1(b)所示。
图 2-1(b) 终结符、非终结符定义
Bison源程序的翻译规则(文法及对应的处理)部分中,sysy文法开始部分及其对应的处理如图2-2(a)所示。处理部分中$$ 指的该产生式归约后数值栈栈顶元素,$n 指产生式右侧从左到右第n个符号在数值栈所对应的值。
图 2-2(a) sysy文法开始部分翻译规则
翻译规则部分中,变量声明对应的sysy文法及相应的处理如图2-2(b)所示,其中主要建立的ast结点为VAR_DECL类型的结点。
图 2-2(b) sysy文法变量声明部分翻译规则
翻译规则部分中,函数定义对应的sysy文法及相应的处理如图2-2(c)所示,其中主要建立的ast结点为PARM_DECL、FUNCTION_DECL类型的结点。
图 2-2(c) sysy文法函数定义部分翻译规则
翻译规则部分中,语句对应的sysy文法及相应的处理如图2-2(d)所示,其中主要建立的ast结点为COMPOUND_STMT类型以及其他各类语句(例如if,while等等)的结点。
图 2-2(d) sysy文法语句部分翻译规则
翻译规则部分中,表达式对应的sysy文法及相应的处理如图2-2(e)所示,其中主要建立的ast结点为BINARY_OPERATOR类型的结点。
图 2-2(e) sysy文法表达式部分翻译规则
最后,Bison源程序中的函数定义部分为空。
Flex源程序中声明及定义部分如图3-1所示。
图 3-1 Flex源程序中声明及定义部分
Flex源程序中规则部分(识别符号的正则表达式及其对应的相关的动作)如图3-2(a)、3-2(b)所示。
图 3-2(a) Flex源程序中规则部分
图 3-2(b) Flex源程序中规则部分
Flex源程序中辅助函数(yywrap函数)部分如图3-3所示。
图 3-3 Flex源程序中辅助函数部分
ast.h文件中,全局变量定义及抽象语法树结点定义如图4-1所示。
图 4-1 全局变量定义及抽象语法树结点定义
建立ast结点函数的声明如图4-2所示。
图 4-2 建立ast结点函数的声明
ast.c文件中,showAst函数如图4-3(a)、图4-3(b)所示。
图 4-3(a) showAst函数
图 4-3(b) showAst函数
在showAst函数中,对与不同的结点类型,我们将打印不同的信息。
对于函数形参结点,我们调用showParaDecl函数对其所包含的结点进行打印,如图4-4所示。由于构建函数形参结点的时候我们是逆序构建,因此需采用非递归中序遍历的算法对其结点进行打印,以便保证函数形参顺序的正确。
图 4-4 showParaDecl函数定义
对于复合语句,我们调用showCompoundStmt函数对其所包含的结点进行打印,如图4-5所示。由于构建函数形参结点的时候我们是顺序序构建,并且假定只有left结点才是真正的语句结点,因此我们只需逐一访问每一层复合语句结点的左子结点并打印其所包含的信息即可。
图 4-5 showCompoundStmt函数定义
对于函数调用结点,我们调用showCallExp函数对其所包含的结点进行打印,如图4-6所示。由于构建函数形参结点的时候我们是顺序序构建,并且假定只有left结点才是真正的语句结点,因此我们只需逐一访问每一层复合语句结点的左子结点并打印其所包含的信息即可。
图 4-6 showCallExp函数定义
对于编译单元,我们调用showTranstion函数对其所包含的结点进行打印,如图4-7所示。由于构建函数形参结点的时候我们是顺序序构建,并且只有left结点才是变量结点或常量结点或者函数定义节点,因此我们只需逐一访问每一层编译单元结点的左子结点并打印其所包含的信息即可。
图 4-7 showTrasntion函数定义
在语法分析过程中,用于获取变量和常量数据类型的字符串形式的函数如图4-8(a)、图4-8(b)所示。
图 4-8(a) 获取变量数据类型字符串型的函数
图 4-8(b) 获取常量数据类型字符串型的函数
建立编译单元结点的函数newCompUnit如图4-9所示。其中left结点为变量声明或者常量声明或者函数定义,right结点为下一个编译单元结点。
图 4-9 建立编译单元结点的函数newCompUnit的定义
建立函数声明结点的函数newFuncDecl如图4-10所示。其中left结点为函数的形参列表结点,right结点函数的函数体结点。
图 4-10 建立函数声明结点的函数newFuncDecl的定义
建立变量声明结点的函数newVarDecl如图4-11所示。其中left结点固定为NULL,right结点为变量的初始赋值结点。
图 4-11 建立变量声明结点的函数newVarDecl的定义
建立表达式结点的函数newBianryOper如图4-12所示。其中oper为该表达式的操作符,left结点表达式的左操作数结点,right结点为表达式的右操作数结点。
图 4-12 建立表达式结点的函数newBianryOper的定义
图 5-1 main函数及yyerror函数的定义
6) 编译上述几个源程序得到的rdparser.exe可运行文件, 最终从命令行读取要分析的字符串,分析后调用showAst打印该程序的抽象语法树结构。
在命令行输入字符串“int main(){int l = 5; while(l > 0){ l = l - 1;}} int m = 9;”,程序运行结果如图6-1所示。
图 6-1 运行结果
二、实验运行结果及测试:
(1)测试用例1:在终端输入字符串“int a1; const int a = 3; int main2(){int b1; int b2; int b3; }” ,程序运行结果如图7-1所示。
图 7-1 测试用例1运行结果
(2)测试用例2:在终端输入字符串“int n;int main(){int a0;int a1;int b;while(b == 5){b = b + 1;}}” ,程序运行结果如图7-2所示。
图 7-2 测试用例2运行结果
(3)测试用例3:在终端输入字符串“int main(int m, int n){l = 1; if(6 < 2){return 1;}else {return 5;} }” ,程序运行结果如图7-3所示。
图 7-3 测试用例3运行结果
(4)测试用例4:在终端输入字符串“int f1(){} float f2(){} int f3(){}”,程序运行结果如图7-4所示。
图 7-4 测试用例4运行结果
三、实验结论与总结:
本次实验所实现的LR语法分析程序,基本上能够完成对sysy文法中函数定义、变量声明、常量声明、语句、表达式等部分的语法分析,并能够生成并打印相应的抽象语法树(AST)。较好的完成了本次实验的基本要求。
通过本次实验,了解并掌握了LR语法分析的基本原理,对Bison(Yacc的GNU版本)这一生成语法分析器的工具有了一定的认识与了解,能够利用Bison自动生成识别相应文法的语法分析器。同时也对编译过程中语法分析这一环节有了一个更加深刻的认知。在实验的过程中也发现了LR语法分析的不足之处:对于文法中含有移进-规约冲突与规约-规约冲突的分析效果不佳。