Bison 详解:强大的语法分析器生成工具

 

一、Bison 的起源与定位

Bison 是一款经典的语法分析器生成工具,它的发展历程与 Unix 系统的演进紧密相连。其前身可以追溯到 20 世纪 70 年代由贝尔实验室开发的 Yacc(Yet Another Compiler-Compiler)。Yacc 作为一款开创性的工具,为编译器的开发提供了极大的便利,让开发者能够通过定义语法规则来自动生成语法分析器。

然而,随着时间的推移,Yacc 的一些局限性逐渐显现,例如缺乏对某些现代语法特性的支持、可移植性不足等。为了弥补这些缺陷,自由软件基金会(FSF)在 Yacc 的基础上开发了 Bison,首个版本于 1985 年发布。Bison 不仅保持了与 Yacc 的兼容性,使得大量基于 Yacc 编写的代码能够平滑迁移,还增加了许多新特性,如更好的错误处理、更丰富的语法规则定义方式等。

Bison 的定位是作为一款通用的语法分析器生成工具,主要用于编译器、解释器、配置文件解析器等需要对输入进行语法分析的程序开发。它接受以巴科斯 - 诺尔范式(BNF)及其扩展形式定义的语法规则作为输入,生成相应的 C、C++ 或其他语言的语法分析器代码。这些生成的分析器能够按照定义的语法规则,对输入的字符流进行解析,识别出合法的语法结构,并执行相应的语义动作。

如今,Bison 已成为众多开发者在开发语法分析相关程序时的重要选择,广泛应用于各种编程语言的编译器实现、文本处理工具、数据格式解析器等领域。

二、Bison 的核心概念

(一)语法分析器

语法分析器是编译器或解释器中的重要组成部分,其主要功能是根据语法规则,对词法分析器生成的记号(token)流进行分析,判断输入是否符合语法规则,并构建相应的语法树(或抽象语法树)。Bison 生成的正是这样的语法分析器,它能够自动处理语法分析过程中的各种情况,如语法正确时的结构构建、语法错误时的提示与恢复等。

(二)上下文无关文法

Bison 基于上下文无关文法(Context-Free Grammar,CFG)进行工作。上下文无关文法由一组终结符、非终结符、开始符号和产生式规则组成。

  • 终结符:是语法中不可再分的基本符号,通常对应词法分析器生成的记号,如标识符、常量、运算符等。
  • 非终结符:是用于表示语法结构的符号,它们可以通过产生式规则分解为终结符或其他非终结符。
  • 开始符号:是文法中最顶层的非终结符,代表整个语法结构的起始点。
  • 产生式规则:形式为 A → α,其中 A 是非终结符,α 是由终结符和非终结符组成的符号串,表示 A 可以被替换为 α。

例如,一个简单的算术表达式文法可以定义为:


expr → expr + term

expr → expr - term

expr → term

term → term * factor

term → term / factor

term → factor

factor → ( expr )

factor → number

其中,expr、term、factor 是非终结符,+、-、*、/、(、)、number 是终结符,expr 是开始符号。

(三)LR 分析算法

Bison 生成的语法分析器采用 LR 分析算法(Left-to-Right, Rightmost derivation in reverse)。LR 分析算法是一种高效的自底向上语法分析算法,它通过读取输入记号(从左到右),维护一个状态栈和一个符号栈,根据当前状态和输入记号,查询分析表来决定执行移进(shift)、归约(reduce)、接受(accept)或报错(error)操作。

常见的 LR 分析变体包括 SLR(Simple LR)、LALR(Look-Ahead LR)和 LR (1) 等。Bison 默认使用 LALR (1) 算法,它在 LR (1) 算法的基础上进行了简化,减少了状态数量,同时保持了较好的分析能力,适用于大多数常见的语法结构。

(四)语义动作

在 Bison 中,语义动作是嵌入在产生式规则中的代码片段,用于在语法分析过程中执行特定的操作,如计算表达式的值、构建语法树节点、进行类型检查等。当一个产生式规则被归约时,其对应的语义动作将被执行。

语义动作通常使用 $1、$2、…、$n 来引用产生式规则右部各个符号对应的属性值,使用 $$ 来表示归约后得到的非终结符的属性值。例如,对于产生式 expr → expr + term { $$ = $1 + $3; },当该规则被归约时,将 expr(\(1)和 `term`(\)3)的值相加,结果赋给新的 expr($$)。

三、Bison 的语法规则文件结构

一个完整的 Bison 语法规则文件通常由三个部分组成,各部分之间用 %% 分隔,具体结构如下:


%{

声明部分:包含C/C++的头文件引用、全局变量定义、函数声明等。

%}

文法定义部分:定义终结符和非终结符的类型、优先级和结合性,以及具体的产生式规则和语义动作。

%%

产生式规则部分:详细列出各个产生式规则,每个规则后可以跟随相应的语义动作。

%%

用户自定义函数部分:包含语义动作中用到的辅助函数的实现等。

(一)声明部分(%{... %})

声明部分主要用于包含必要的头文件、定义全局变量和函数原型等,这些内容会被直接复制到生成的语法分析器代码中。例如:


%{

#include <stdio.h>

#include <stdlib.h>

int yylex(void);

void yyerror(const char *s);

%}

其中,yylex 是词法分析器函数,由 Flex 等工具生成,Bison 生成的分析器会调用它来获取下一个记号;yyerror 是错误处理函数,用于在发生语法错误时输出错误信息。

(二)文法定义部分

  1. 终结符与非终结符定义

Bison 中,通常用 %token 声明终结符,用 %type 声明非终结符的类型。例如:


%token NUMBER PLUS MINUS MULTIPLY DIVIDE LPAREN RPAREN

%type <value> expr term factor

其中,NUMBER、PLUS 等是终结符;<value> 表示非终结符 expr、term、factor 的属性值类型为 value(通常需要在声明部分定义该类型,如 typedef int value;)。

  1. 优先级和结合性定义

当文法中存在二义性时(即同一个输入序列可以通过不同的归约顺序得到不同的语法树),需要通过定义运算符的优先级和结合性来消除二义性。Bison 中使用 %left(左结合)、%right(右结合)、%nonassoc(不可结合)来定义结合性,优先级由声明的顺序决定,后面声明的优先级更高。例如:


%left PLUS MINUS

%left MULTIPLY DIVIDE

%right UMINUS

上述定义表示 PLUS 和 MINUS 具有相同的优先级,且为左结合;MULTIPLY 和 DIVIDE 优先级高于 PLUS 和 MINUS,也是左结合;UMINUS(一元减)为右结合,优先级最高。

(三)产生式规则部分

产生式规则部分是 Bison 文件的核心,用于定义文法的产生式以及对应的语义动作。其基本格式为:


非终结符:

符号串1 { 语义动作1 }

| 符号串2 { 语义动作2 }

| ...

;

例如,对于算术表达式文法,其产生式规则可以定义为:


expr:

term { $$ = $1; }

| expr PLUS term { $$ = $1 + $3; }

| expr MINUS term { $$ = $1 - $3; }

;

term:

factor { $$ = $1; }

| term MULTIPLY factor { $$ = $1 * $3; }

| term DIVIDE factor { $$ = $1 / $3; }

;

factor:

NUMBER { $$ = $1; }

| LPAREN expr RPAREN { $$ = $2; }

| MINUS factor %prec UMINUS { $$ = -$2; }

;

其中,%prec UMINUS 表示该产生式中的 MINUS 具有 UMINUS 所定义的优先级。

(四)用户自定义函数部分

这部分主要包含语义动作中用到的辅助函数的实现,最常见的是 yyerror 函数的实现。例如:


void yyerror(const char *s) {

fprintf(stderr, "Error: %s\n", s);

}

int main(void) {

yyparse();

return 0;

}

yyparse 是 Bison 生成的语法分析器的主函数,调用它将启动语法分析过程。

四、Bison 与 Flex 的协同使用

在实际的编译器或解析器开发中,Bison 通常与 Flex(一个词法分析器生成工具)协同工作。Flex 负责将输入的字符流转换为一系列记号(终结符),然后 Bison 生成的语法分析器对这些记号进行分析。

(一)Flex 词法分析器的生成

Flex 的输入文件(通常以 .l 为扩展名)定义了词法规则,用于识别不同的终结符。例如,一个简单的算术表达式词法规则文件 calc.l 如下:


%{

#include "calc.tab.h" // 包含Bison生成的头文件,定义了终结符常量

#include <stdlib.h>

%}

%%

[0-9]+ { yylval = atoi(yytext); return NUMBER; }

"+" { return PLUS; }

"-" { return MINUS; }

"*" { return MULTIPLY; }

"/" { return DIVIDE; }

"(" { return LPAREN; }

")" { return RPAREN; }

[ \t\n] { /* 忽略空白字符 */ }

. { yyerror("Unknown character"); }

%%

int yywrap(void) {

return 1;

}

其中,yytext 是 Flex 提供的变量,存储当前匹配的字符串;yylval 是一个全局变量,用于传递记号的属性值(如数字的数值),其类型需要在 Bison 文件中定义(通过 %union 或直接定义)。

运行 flex calc.l 命令,Flex 会生成词法分析器代码 lex.yy.c。

(二)Bison 语法分析器的生成

Bison 的输入文件(通常以 .y 为扩展名)如前面所述的算术表达式文法文件 calc.y。运行 bison -d calc.y 命令,Bison 会生成语法分析器代码 calc.tab.c 和头文件 calc.tab.h(-d 选项用于生成头文件,其中包含了终结符的定义)。

(三)编译与运行

将 Flex 和 Bison 生成的代码以及用户自定义的代码一起编译,即可得到完整的解析器程序。例如:


gcc -o calc calc.tab.c lex.yy.c -lm

其中,-lm 选项用于链接数学库(如果程序中用到了数学函数)。运行生成的 calc 程序,就可以输入算术表达式进行解析和计算了。

五、Bison 的错误处理

Bison 提供了一定的错误处理机制,能够在输入存在语法错误时进行提示,并尽可能地恢复分析过程,继续处理后续的输入。

(一)错误产生式

在 Bison 文法中,可以定义错误产生式来处理特定的语法错误。错误产生式通常包含 error 符号,例如:


expr:

...

| error { yyerror("Invalid expression"); yyerrok; }

;

当语法分析器遇到错误时,会尝试找到包含 error 符号的产生式进行归约,执行相应的语义动作(如输出错误信息)。yyerrok 宏用于重置错误状态,使分析器能够继续处理后续输入。

(二)错误恢复

Bison 的默认错误恢复策略是跳过输入字符,直到找到一个能够恢复分析的同步点(通常是一些高优先级的终结符,如分号、右括号等)。开发者也可以通过自定义错误处理函数和错误产生式,实现更灵活的错误恢复机制。

(三)yyerror 函数

yyerror 函数是 Bison 中用于输出错误信息的函数,必须由用户实现。该函数接受一个字符串参数,即错误信息,通常将其输出到标准错误流。例如:


void yyerror(const char *s) {

fprintf(stderr, "Line %d: %s\n", yylineno, s);

}

其中,yylineno 是 Flex 提供的变量,用于记录当前的行号,方便定位错误位置。

六、Bison 的高级特性

(一)% union 定义属性值类型

当非终结符和终结符的属性值有多种类型时,可以使用 %union 来定义一个联合体类型,包含所有可能的类型。例如:


%union {

int ival;

float fval;

char *sval;

}

%token <ival> INT

%token <fval> FLOAT

%token <sval> STRING

%type <ival> expr_i

%type <fval> expr_f

这样,不同的记号和非终结符可以拥有不同类型的属性值,通过 <ival>、<fval> 等指定。

(二)位置信息跟踪

Bison 可以跟踪每个记号和非终结符在输入中的位置信息(如行号、列号),这对于生成有意义的错误信息非常重要。通过 %locations 声明启用位置跟踪,然后在 Flex 中设置 yylloc 变量(表示当前记号的位置)。例如:

在 Bison 文件中:


%locations

在 Flex 文件中:


%{

#include "calc.tab.h"

#include "calc.tab.h"

#define YY_USER_ACTION yylloc.first_line = yylloc.last_line = yylineno;

%}

这样,Bison 生成的分析器就可以获取每个符号的位置信息,并在错误信息中使用。

(三)GLR 分析器

对于一些复杂的、存在二义性且难以用 LALR (1) 算法处理的文法,Bison 支持生成 GLR(Generalized LR)分析器。GLR 分析器能够同时探索多个可能的分析路径,从而处理更广泛的文法。通过 %glr-parser 声明启用 GLR 模式:


%glr-parser

(四)参数化非终结符

Bison 允许非终结符带有参数,这可以用于传递一些上下文信息,简化语义动作的编写。例如:


%type <ival> expr(int flag)

expr(flag):

term(flag) { $$ = $1; }

| expr(flag) PLUS term(flag) { $$ = $1 + $3; }

;

这里,expr 非终结符带有一个 flag 参数,在产生式中可以使用该参数。

七、Bison 的应用场景

(一)编译器开发

Bison 最主要的应用场景是编译器的开发。无论是开发新的编程语言,还是为现有语言实现编译器前端,Bison 都能帮助开发者快速生成语法分析器,处理语言的语法结构,生成中间代码或抽象语法树,为后续的语义分析、代码优化和代码生成奠定基础。

(二)解释器开发

对于解释型语言,Bison 可以用于生成解释器的语法分析部分,解析输入的源代码,直接执行相应的语义动作,实现对语言的解释执行。

(三)配置文件解析

许多应用程序都需要处理配置文件,配置文件通常有其特定的语法结构。使用 Bison 和 Flex 可以快速开发出配置文件解析器,将配置文件中的信息解析为程序内部的数据结构,方便程序读取和使用配置信息。

(四)数据格式处理

对于一些自定义的数据格式或标准的数据格式(如 JSON、XML 的子集等),Bison 可以用于开发相应的解析器,对数据进行验证和提取,实现数据的导入、导出或转换。

(五)领域特定语言(DSL)开发

在特定的领域中,为了提高开发效率或简化问题描述,常常会设计领域特定语言。Bison 可以用于实现 DSL 的语法分析器,使 DSL

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

globaldeepthinker

能为我买一杯咖啡吗谢谢你的帮助

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值