编译原理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:指明没有结合性
我设定了如下优先级和结合性:
运算符 | 结合性 | 优先级(数字越大优先级越低) |
---|---|---|
[] | 左结合 | 1 |
() | 左结合 | 1 |
/ | 左结合 | 2 |
* | 左结合 | 2 |
+ | 左结合 | 3 |
- | 左结合 | 3 |
> | 左结合 | 4 |
>= | 左结合 | 4 |
< | 左结合 | 4 |
<= | 左结合 | 4 |
== | 左结合 | 5 |
!= | 左结合 | 5 |
= | 右结合 | 6 |
, | 左结合 | 7 |
语法规则
通过查阅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 normal
diff ./tests/lab2/syntree_easy ./tests/lab2/syntree_easy_std
diff ./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
遇到输入结束或者不能恢复的错误就会返回;
- 调用函数
编译原理2-Bison语法分析相关推荐
- 编译原理--实验2 语法分析
文章目录 前言 1.1实验目的 1.2 实验任务 1.3 实验内容 1.3.1 实验要求 1.3.2 输入格式 1.3.3 输出格式 1.3.4 样例 1.4 程序 1.4.1 程序流程图 1.4.2 ...
- 【编译原理】Python语法分析LL(1)、LR(1)
目录 一.实验目的 二.实验任务 三.实验原理 1 LL(1)文法 2 LR文法 四.实验过程 1 LL(1)文法 2 LR文法 五.实验结果 1 LL(1)文法 2 LR(0)文法 3 ...
- 编译原理教程_4 语法分析
文章原稿 https://gitee.com/fakerlove/fundamentals-of-compiling 文章目录 4. 语法分析 4.1 概述 4.1.1功能 4.1.2 基本任务 4. ...
- 编译原理学习之语法分析
1.语法分析的地位 –是编译程序的核心部分. 2.语法分析的任务 –识别由词法分析得出的单词序列是否是给定文法的句子. 3.语法分析的理论基础 –上下文无关文法和下推自动机 4.语法分析的方式 1)自 ...
- 编译原理 —— 什么是语法分析
语法分析 识别句子中的各个短语 从词法分析器输出的token序列中识别出各类短语,并构造语法分析树(parse tree) 语法分析要解决的问题是:如何根据语法规则为输入句子构造语法分析树 转载地址: ...
- 【编译原理】自上而下语法分析(CC++源码+实验报告)
文章目录 1 实验目的和内容 1.1 实验目的 1.2 实验内容 1.3 实验要求 2 设计思想 2.1 根据BNF描述该文法 2.2 根据文法画相应的语法图 2.3 判断是否是LL(1)文法-求Fi ...
- 编译原理——自上而下的语法分析方法(LL分析法)
自上而下的语法分析方法(LL分析法) 概述: 语法分析的地位:编译程序的核心部分 任务:词法分析出来的单词序列是否是给定文法的句子 理论:上下文无关文法和下推自动机 方式:自上而下的语法分析(推导)和 ...
- 编译原理——自上而下的语法分析
语法分析的功能: 语法分析方法的分类: (1)自上而下(自顶而下)分析法:从开始符号出发,进行最左推导,或从根开始,构造推导树.(回溯分析法属于不确定的自上而下的语法分析方法,而递归下降分析法和预测 ...
- 编译原理--语法分析:LR(1)
规范LR(1)项 对LR(0)中本来需要归约的行为,再往前看一步,考察归约后, 下一步是否会报错. 入股归约后下一步不会报错,按LR(0)进行归约. 否则,不进行归约.正式地将, 可行前缀&有 ...
- 编译原理--语法分析:LR
归约 可将自底向上语法分析过程看成将一个串ω"归约"为文法开始符号的过程. 在每个归约步骤中, 一个与某产生式体相匹配的特定子串被替换为该产生式头部的非终结符号. 句柄剪枝 如有S=>^{*}_ ...
最新文章
- mysql jdbc 表结构_JavaEE_day09_MySQL多表JDBC
- 【内核数据结构】 内核链表分析
- 字符串所有排列组合暴力递归
- C语言二分法求最小值解,C语言用二分法求方程的近似解的方法
- c语言程序设计实践教程张卫国,C语言程序设计实践教程
- 数值计算方法(五)——迭代法求方程根
- postgresql 配置redis_自建 Gitlab (邮箱配置、拆分 PostgreSQL、Redis) + 随想
- IOS端与Java端MD5加密方法
- linux学习入门标准教程(2)
- SAP系统如何打NOTE?
- 【深度优先搜索】复原IP地址
- 如何简单快速的写出幸运转盘抽奖
- 26个字母大写及小写分别对应的ASCII码值
- 51单片机两只老虎 c语言,基于51单片机的简易电子琴(两只老虎)
- Neo4j Server shutdown initiated by request.解决方案
- 手机投屏到电脑的实用工具
- Python爬虫 - 统计自己读过小说的字数
- [31] FatMouse and Cheese
- 环形缓冲区RingBuff的代码实现
- 计算机课设微程序最大值,计算机组成原理课程设计(微程序)报告
热门文章
- IPC连接错误号分析
- 什么是白皮书?【理解较局限,仅个人学习记录】
- Python脚本批量读取哨兵2号(Sentinel2)影像并另存为Geotiff格式
- 研究型论文_具有 ExtraTrees 特征选择、极限学习机集成和 softmax 聚合的多层入侵检测系统(英文论文)
- 仿人机器人的跑步研究学习笔记1之机器人的基础知识
- mac装win10检测不到独立显卡
- 总结:shell中的if条件判断
- nested exception is java.lang.ClassCastException: java.lang String cannot be cast to java.util.Map
- 学成在线项目-轮播图banner
- matlab画青藏高原地图,教程合集 | NCL与GrADS地图绘制合集