编译原理实验二:Bison

本文介绍如何使用Bison和Flex构建语法分析树,包括词法分析器和语法分析器的设计与实现,以及如何生成正确的语法分析树。

编译原理实验二:Bison

实验要求

1.了解Bision基础知识,如何将文法产生式转换为Bison语句

2.阅读/src/common/SyntaxTree.c,对应头文件 /include/SyntaxTree.h,理解分析树生成的过程。

3.了解Bison与Flex的协同工作过程,理解pass_node函数并改写lab1代码。了解yylval工作原理。

4.补全 src/parser/syntax_analyzer.y 文件和lexical_analyzer.l文件

实验难点

1.Bison的使用

Bison是一个语法分析器的生成工具,用于生成语法分析器。Bison可以将LALR文法转换可编译的c代码。Bison文件的扩展名为.y,在Bison文件中给出LALR文法以及一些分析动作,编译就可以产生一个语法分析器。

Bison文件以.y结尾,与Lex文件的编写规则类似,由%%区分的三部分构成:

%{
   
   
/* 这部分代码会被原样拷贝到生成的 .c 文件的开头 */
#include <stdio.h>
int yylex(void);
void yyerror(const char *s);
%}

/*一些指令,如用%start指定起始符号,%token定义token*/
%start reimu
%token REIMU
    
%%
/*解析规则*/
产生式  {
   
   动作代码}

%%
/*辅助函数,会被复制到生成的.c文件的末尾*/

Bison需要一个yylex来获取下一个词法单元,还需要一个yyerror提供报错。定义主函数,调用yyparse(),就可以让语法分析器工作。

在语法分析过程中,语法分析树的叶子节点是一个具体的语义值,该值的类型是YYSTYPE,在Bison中用%union指明。不同的节点对应着不同的终结符,可能为不同的类型,因此union中可以包含不同的数据类型。可以指明一个终结符或是非终结符的类型,以便后续的使用。可以使用%type <>或%token <>指明类型。其中%token是在声明词法单元名的同时指明类型,声明的token会由Bison导出到最终的.h文件中,让词法分析器也可以直接使用。

%token <num> NUMBER		/*声明词法单元名,并在<>中指明类型*/
%type  <typex> expr		/*指明类型*/
...
%union{
   
   
    char op;
    double num;
}

下面说明语法分析时的动作怎么编写。以一个边进行语法分析边按照语义执行的计算器为例,识别到加法语句的动作为:

E→E+E	{
   
   E=E1+E2}

在Bison中的实现:

term : term ADDOP factor
     {
   
   
        switch $2 {
   
   
        case '+': $$ = $1 + $3; break;
        case '-': $$ = $1 - $3; break;
        }
     }

其中$$表示当前节点,$1,$2,$3表示产生式的成分,也是当前节点的子节点。由于采用自底向上分析(LALR)文法,构建语法树是推导的过程,这些子节点是已经解析的,当前节点则是规约产生的。使用节点union的哪个类型操作,是已经用<>在开头的%token和%type中指明的。

实际的编译器中,语法分析相应的动作通常是建立抽象语法树,进行语义分析,或是直接产生中间或目标代码。在本实验中,动作为自底向上构建语法分析树。

2.Bison与Flex的协同工作

Bison需要一个yylex来完成词法分析,这部分的工作是词法分析器完成的。词法分析器不仅要将词素识别为词法单元并返回词法单元值,还要返回词法单元的属性。这是通过yylval完成的。yylval是使用Bison生成的.c文件中声明的一个全局的变量,类型为YYSTYPE,即在Bison文件中%union声明的类型,使用这个类型将属性值传递。进行词法分析时,只要将属性值存入yylval,语法分析器就可以从yylval获取识别到的词法单元的属性值。

在实验中,yylval仅包含一个语法分析树的节点指针。节点中包含一个节点名。对于语法分析树的叶子节点,这个节点名就是词法单元的值,对于非叶子节点,这个节点名为语法成分名。在以下分析语法分析树的生成过程中,分析了节点及分析树是如何构造的。

3.分析树的生成过程

分析树的相关数据结构和方法定义在/include/SyntaxTree.h文件中,分析树的节点记录了父节点,子节点的指针,以及子节点数和节点名信息,相关的方法包括生成新的节点,添加子节点,创建语法树等。

//语法分析树的节点
struct _syntax_tree_node {
   
   
	struct _syntax_tree_node * parent;
	struct _syntax_tree_node * children[10];
	int children_num;

	char name[SYNTAX_TREE_NODE_NAME_MAX];
};
typedef struct _syntax_tree_node syntax_tree_node;
//相关函数
syntax_tree_node * new_anon_syntax_tree_node();									//创建新节点
syntax_tree_node * new_syntax_tree_node(const char * name);						
int syntax_tree_add_child(syntax_tree_node * parent, syntax_tree_node * child);	//添加子节点
void del_syntax_tree_node(syntax_tree_node * node, int recursive);				//删除节点
syntax_tree* new_syntax_tree();													//创建语法分析树
void del_syntax_tree(syntax_tree * tree);										//删除分析树
void print_syntax_tree(FILE * fout, syntax_tree * tree);						//输出分析树

每个终结符都对应着一个叶子节点,这个叶子节点在词法分析时就可以产生。在自底向上的分析过程中,首先产生的是叶子节点,在用产生式进行规约时向上构建语法分析树。叶子节点的产生在词法分析器中的pass_node()函数中实现,创建一个新的节点,并将其指针赋值给yylval,节点名为其成分(非终结符名或终结符名),这样语法分析器就可以使用该节点构造语法分析树。

//生成节点并存入yylval传递给语法分析器
void pass_node(char *text){
   
   
     yylval.node = new_syntax_tree_node(text);
}
//识别词法单元时调用pass_node
\+  {
   
    pos_start = pos_end; pos_end += 1; pass_node(yytext); return ADD; }

词法分析完成了叶子节点的产生,剩下的工作就由语法分析来完成了。构建的过程就是在每使用一个产生式进行规约时,建立一个新的节点表示当前产生式的非终结符,然后将产生式中的成分,也就是子节点的指针存入这个新节点中。当最后使用起始产生式规约时,产生的新节点就是语法分析树的根节点,就完成了向上构建语法分析树的工作。实验在Bison的.y文件中,已经给出了创建新节点并建立节点关系的函数,为node()函数,参数为产生式的非终结符名,产生式成分个数(也即子节点个数),子节点的指针。

//产生一个语法分析树新节点的函数
syntax_tree_node *node(const char *node_name, int children_num, ...);
//应用该函数构造语法分析树,根节点的构造
program : declaration-list {
   
    $$ = node("program", 1, $1); gt->root = $$; }

实验设计

1.词法分析部分

完善词法分析部分,即./src/parser/lexical_analyzer.l文件。只需要在识别动作中添加pass_node(yytext)产生词法单元叶子节点,通过yylval传递给语法分析器。对于注释,换行符和空格,不需要添加到语法分析树当中,因此创建节点和返回值,如果读到了就更新lines与pos,保证出错时可以定位,然后进行下一个词法单元的识别就可以了。

%%

\+  {
   
    pos_start = pos_end; pos_end += 1; pass_node(yytext); return ADD; }
\-   {
   
   pos_start = pos_end; pos_end++; pass_node(yytext); return SUB;}
\*   {
   
   pos_start = pos_end; pos_end++; pass_node(yytext); return MUL;}
\/   {
   
   pos_start = pos_end; pos_end++; pass_node(yytext); return DIV;}
\<   {
   
   pos_start = pos_end; pos_end++; pass_node(yytext); return LT;}
"<=" {
   
   pos_start = pos_end; pos_end+=2; pass_node(yytext); return LTE;}
\>   {
   
   pos_start = pos_end; pos_end++; pass_node(yytext); return GT;}
">=" {
   
   pos_start = pos_end; pos_end+=2; pass_node(yytext); return GTE;}
"==" {
   
   pos_start = pos_end; pos_end+=2; pass_node(yytext); return EQ;}	
"!=" {
   
   pos_start = pos_end; pos_end+=2; pass_node(yytext
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值