300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > 编译原理2-Bison语法分析

编译原理2-Bison语法分析

时间:2022-09-10 05:36:07

相关推荐

编译原理2-Bison语法分析

ps:补上了图

实验要求

了解 Bison 基础知识和理解 Cminus-f 语法(重在了解如何将⽂法产⽣式转换为 Bison 语句)

阅读 /src/common/SyntaxTree.c ,对应头⽂件 /include/SyntaxTree.h (重在理解分析树如何⽣成)

了解 Bison 与 Flex 之间是如何协同⼯作,看懂 pass_node 函数并改写 Lab1 代码(提示:了

解 yylval 是如何⼯作,在代码层⾯上如何将值传给 $1 、 $2 等)

补全 src/parser/syntax_analyzer.y ⽂件和 lexical_analyzer.l ⽂件 ,完成语法分析器,要求最终能够输出解析树。

实验设计

该实验需要完成lexical_analyzer.l和syntax_analyzer.y这两个文件的设计,首先设计.y文件:每个 Bison 文件由序言,Bison 声明,语法规则,结尾这几部分组成,每个部分由%%分隔。

序言(用%{ %}括起来)

我在序言里进行了头文件的声明其中syntax_tree.h定义了结点的结构体_syntax_tree_node和树的结构体_syntax_tree。还声明了建树的相关函数,如new_syntax_tree_node为在树中新建节点,syntax_tree_add_child为添加子节点,new_syntax_tree为建树,del_syntax_tree为删除树,print_syntax_tree为打印树;

序言中还有外部函数和变量的声明,其中yylex()是FLEX工具按照定义好的规则自动生成的C函数,这个C函数把文本串作为输入,按照定义好的规则分析文本串中的字符,找到符合规则的一些字符序列后,就执行在规则中定义好的动作(Action)。找到符合规则的一些字符序列后用yytext指向字符序列的首字符。yyin表示指定的文件,yylex()函数从该文件读取字符:

我在序言中还定义了需要使用的变量和函数声明,如gt变量表示生成的解析树的根节点,yyerror函数处理yacc语法分析程序探测到的语法错误,node函数生成一个结点和它的子节点,并将该结点加入到树中:

Bison 声明

该部分是声明终结符和非终结符,和优先级的定义:

声明终结符:

用%token进行声明,其方法为:

%token<语义值类型> name

Bison会在分析器中将这个声明转换成#define指令以便yylex用name代表这个记号类型码。当栈类型是一个联合体的时候, 就需要指明记号的语义值类型。在实验中我将语义值类型定义为node,该类型通过%union进行声明:

这是我声明的非终结符,这些名称和lab1中的token名称对应:

声明非终结符:

用% type为非终结符指定语义值类型,其类型在%union中给定,方法如下:

%type<语义值类型> nonterminal

这是我声明的终结符,这些名称参考lab2中Cminus-f 的语法规则:

声明开始符号:

使用%start进行声明:%start program

优先级和结合性的定义:

当记号的优先级相同时, 如何嵌套使用它们取决于它们的结合性.当一个记号的优先级更高时,它将先被组合.为了解决LALR中的冲突问题,需要确定好各符号的优先级和结合性。通过查阅bison使用手册,知道怎么设定优先级和结合性:

先声明的优先级低,后声明的优先级高%left:指明有左结合性%right:指明有右结合性%nonassoc:指明没有结合性

我设定了如下优先级和结合性:

语法规则

通过查阅Bison手册可知,一个语法规则的形式如下:

result: components1{ }| components2{ };

result为这个规则所描述的非终结符components 为被这个规则组合在一起的多种终结符和非终结符;用“|”将多条规则进行连接 ; “;”表示规则描述结束;花括号{}里为这个规则的实例被识别出来后进行的动作,一般动作用于计算规则左端的语义值**$∗∗,动作中可以使用∗∗**,动作中可以使用**∗∗,动作中可以使用∗∗n来引用**规则中各部件的语义值,此时$n有对应非终结符或终结符的数据类型

根据上述,我编写的规则如下(由于规则太多这里只显示部分):

规则1描述了非终结符program,该动作里面有两条语句:

语句1

使用了**$1引用部件declaration-list的语义值**,由于在之前声明该非终结符的语义值类型为node,所以$1为node型它调用函数node创建结点,依次传入参数:结点名称,结点的子节点个数,子节点的语义值(即$1),创建好后返回syntax_tree_node *类型,它指向当前创建的结点,将返回值赋给$$,得到规则左边的语义值;对于其它规则的动作,里面的语句基本上和语句1差不多;

语句2:

由于program为开始符号,当识别到该规则的实例,表示这棵树已经建完,此时需要获得该树,而语句2就是实现该操作:将规则左边的语义值,即树的根节点赋给gt->root,打印树时使用变量gt就可以了。

结尾

结尾部分实现了三个函数parse,node和yyerror:

parse函数:

首先它判断input_path是否为NULL,如果是则从 stdin 读取,如果不是就从文件中读取;然后对变量lines,pos_start,pos_end,gt作初始化;然后使用yyrestart(yyin);,因为每次调用yypars,它会忘记上次分析可能拥有的任何状态而重新开始分析,而每次调用yylex它都从上次离开的地方继续分析。所以要在每次parse之前调用yyrestart()来确保lex从头开始分析而不是从上次的地方继续分析。然后调用函数yyparse开始进行分析. 这个函数读入记号,执行动作, 并且最后如果它遇到输入结束或者不能恢复的错误就会返回.最后返回树

node函数为构造树节点,当该节点的孩子个数为0时,给该节点添加一个空字符子节点,否则按顺序添加传入的子节点:

yyerror函数:每当yyparse发现一个语法错误的时候, yyparse就会调用该函数进行报错,这里打印报错的哪行哪列信息

完成lexical_analyzer.l文件

将实验1的 .l文件复制到这个文件当中,并做了以下改动:

去掉辅助函数部分;识别到规则时加上将值放入Bison栈中和返回记号的类型的操作;

如下为修改后的操作, 通过pass_node函数将记号的值放入栈中,然后再用return将记号的类型返回:

pass_node函数已经给出:

它传入的是捕获到的字符串text,将text又作为参数传入创建结点函数中,然后将创建好的名为text的节点(这是记号的语义值)存放在全局变量yylval,当存储类型为node的语义值时, 需要使用恰当的联合体成员,所以是yylval.node来存的。这一操作实际上就是将void这个非终结符的记号值存放到node类型的栈中;

不过识别到EOL, BLANK, COMMENT,ERROR时不需要进行压栈(它们不做非终结符)和返回记号类型,它们只需要更改记录位置的变量:

实验结果验证

可以通过如下命令进行编译:

bison –d lexical_analyzer.y //生成syntax_analyzer.c,syntax_analyzer.h(包含.y中定义的所有终结符的记号类型,记号值从258开始;栈的类型定义;变量yylval的外部引用定义 flex lexical_analyzer.l //编译.l得到lex.yy.c,lex.yy.c中包含了函数yylex()gcc syntax_analyzer.c lex.yy.c

也可以使用make parser,通过写好的Makefile文件进行编译。

编译好后输入命令进行验证:

./tests/lab2/test_syntax.sh easy./tests/lab2/test_syntax.sh normaldiff ./tests/lab2/syntree_easy ./tests/lab2/syntree_easy_stddiff ./tests/lab2/syntree_normal ./tests/lab2/syntree_normal_std

可以看到diff后没有其他输出(ifs为我自己写的测试样例),表示通过给出的 easy 测试集和通过 normal 测试集。

测试我自己写的ifs测试样例:

我写这个测试样例主要是为了观察在有if嵌套的情况下,生成的树是什么样的。因为在Bison使用手册中,我看到"悬挂else"歧义,本次实验的语法中也有这种现状存在:

ELSE被读入成为超前扫描记号, 栈中的内容刚好可以由第一个规则进行归约. 但是移进ELSE也是合法的。此时会产生一个移进/归约冲突(这是可以预见的合法的移进/归约冲突, 可以使用%expect n消除警告)。Bison被设计成选择移进来解决这些冲突.因为已经建立的惯例是通过将else与最里面的if匹配来解决歧义;

测试结果分析:

对于第一个if模块,它的结构是 if { if – else},在树中对应为:

对于第二个if模块,它的结构是 if { if} – else,在树中对应为:

可以看到,由于stm中有{},所以这个语法分析器在分析IF stm IF stm ELSE stm结构时,会根据 { } 的范围决定遇到ELSE时是要进行归约(与外面的if匹配)还是移进(与最里面的if匹配)。

思考题

在1.3样例代码中存在左递归文法,为什么bison可以处理?(提示:不用研究bison内部运作机制,在下面知识介绍中有提到bison的一种属性,请结合课内知识思考)

因为bison是**使用LALR(1)**将文法转为解析器的 ,LALR使用了前看符号(在归约时通过FOLLOW(N)选择性归约),所以通过前看符号可以解决左递归文法出现的冲突;

请在代码层面上简述下yylval是怎么完成协同工作的。(提示:无需研究原理,只分析维护了什么数据结构,该数据结构是怎么和$1$2等联系起来?)

flex通过正则表达式读到匹配的字符串后,将字符串转为对应非终结符的语义值,然后将这个语义值放在全局变量yylval中,yylval相当于一个栈,栈的类型可以由%union定义。Bison维护一个栈(这个栈中的每一个元素的值,都是由yylval所指定)来保存文法符号的语义值,当最后n个被移进的记号和语义值匹配某个语法规则时, 就将它们依次弹出栈,再将规则的左部压栈**(归约)**。

bison定义$和和和n来引用栈中的元素:****$$表示规则左部,即归约之后被重新压入栈中的元素;$n表示规则左边第n个部件的语义值,即归约之前栈中距离栈顶编号为i的元素;

请尝试使用1.3样例代码运行除法运算除数为0的例子(测试case中有)看下是否可以通过,为什么我们在case中把该例子认为是合法的?(请从语法与语义上简单思考)

可以通过;语法分析器认为除数为0是合法的,因为**“2/0”可以由上面规定的文法推导出来**,所以从语法上来说它是合法的,由于语法分析使用的是上下文无关文法,所以它不能判断语义是否合法;

能否尝试修改下1.3计算器文法,使得它支持除数0规避功能。

词法分析器在读到非终结符NUMBER时,先判断yytext获取到的值是否为0,不为0才将它的语义值压入到yylval.num中,否则不将其传到语法分析器中:

修改之后,若除数为0,则直接报错,支持除数0规避功能:

实验反馈

通过实现一个cminus-f语法分析器,我大致了解了bison的分析过程: 调用函数yyparse开始进行分析;用词法分析器读取记号:yylex从输入流中识别记号并将记号类型的正值数字码(数字码用来确定需要解析的token类型)返回给语法分析器(数字码在bison编译.y文件时生成的.h文件里),并将这些记号和它们的语义值压入栈中(移进);当最后n个被移进的记号和组匹配某个语法规则时, 可以由那个规则将它们结合起来(归约),这些记号被规则的左部取代。动作是处理归约的一部分, 因为动作会计算这个组的语意值;当yyparse遇到输入结束或者不能恢复的错误就会返回;

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。