教你读懂Bison语法分析代码,掌握基本符号

关于Bison的使用细节可以查看https://www.gnu.org/software/bison/manual/bison.html,是Bison的英文手册。

如果你有哪个部分不清晰,可以点击上面的Bison链接,通过”Ctrl+F“搜索你不清楚的关键字来查询文档。(不过文档很生涩难读,读不懂的部分可以粘贴给语言大模型翻译。

目录

一、什么是Bison

1.基本介绍

2.基本组成部分

二、Bison的基本符号

1.“:”

2.“|”

3.";"

4.“{}”

扫描二维码关注公众号,回复: 17639924 查看本文章

5.“%%”

6.“%token”"%type""%start"

7."<>"

8." ' '  "

9."$$"和"$n"

10."@$""@n"

11.“%precedence”"%left""right""%nonassoc"


一、什么是Bison

1.基本介绍

Bison 是一个广泛使用的语法分析工具,它是 GNU 项目的一部分,旨在为编程语言的解析和编译提供支持。Bison 允许用户定义语言的语法,并生成一个可用于解析该语言的解析器。

Bison 是 Yacc 的 GNU 实现,功能和语法上相似,但 Bison 提供了更多的特性,比如更好的错误处理和可重入解析器。

(扩展)GNU(GNU's Not Unix)是一个自由软件项目,旨在创建一个完全自由的Unix-like操作系统。

Bison还涉及到两个概念:

  • 上下文无关文法(CFG):Bison 使用上下文无关文法来描述编程语言的语法。用户可以定义语法规则和语义动作。
  • 产生式(Production Rules): 语法规则被定义为产生式,由一个非终结符和它可以替代的终结符或其他非终结符构成。

2.基本组成部分

1)声明部分:

- 包含 `%token`、`%type`、`%start` 等指令,用于声明终结符、非终结符和起始符号。

- 可以包含 C 语言的头文件。

2)语法规则部分:

- 定义语法规则,描述如何从终结符和非终结符生成语言构造。

- 语法规则通常还包括语义动作,即在解析时执行的代码。

3. 辅助程序代码部:

- 包含额外的 C 代码,可用于定义辅助函数或数据结构。

下面bison代码可以很好地展现代码组成部分,每个部分通过“%%”分隔开。

%{
#include <stdio.h>
#include <stdlib.h>
%}

%token NUMBER

%% 

expression:
      NUMBER
    | expression '+' NUMBER { printf("%d\n", $1 + $3); }
    | expression '-' NUMBER { printf("%d\n", $1 - $3); }
    ;

%% 

int main(void) {
    return yyparse();
}

void yyerror(const char *s) {
    fprintf(stderr, "Error: %s\n", s);
}

二、Bison的基本符号

1.“:”

功能:`:` 符号用于分隔规则的左侧(产生式的名称)和右侧(产生式的定义)部分。

示例:

 //产生式:statement->expression
statement : expression
  {
      // 语义动作
  };

2.“|”

功能:`|` 符号用于定义一个语法规则的多个选择(备选项)。

示例:

 //相当于产生式中的“|”符号
//expression->term| expression+term |expression-term
expression : term 
             | expression '+' term
             | expression '-' term;

3.";"

功能:`;` 符号用于结束一个语法规则的定义。每个产生式的定义都必须以 `;` 结束。

示例:

  expression : term '+' term
              | term '-' term;

4.“{}”

功能:`{}` 符号用于包围语义动作的代码块,这些动作在解析完规则后执行,通常用于构造抽象语法树(AST)或其他数据结构。

`{}`内的内容可以是C++代码,比如”$$=new int(3)“.与一般C++程序不同点就在于,增加了"$$""$n"等符号来指代产生式的左部符号、右部的第n个符号。

示例:

 //在解析完expression->term后,会执行语义动作“ printf("1")”,打印出1来
expression : term 
  {
      printf("1"); // 语义动作
  };

5.“%%”

功能:`%%` 符号用于分隔语法规则部分和其他部分(如声明部分或 C 代码部分)。

示例:

 %{
  // C 代码
  %}
  
  %%
  
  // 语法规则定义
  ```

6.“%token”"%type""%start"

功能:“%token”"%type""%start"分别表示终结符、非终结符、开始符号

示例:

%token NUM        // 声明一个 token,表示数字
%token PLUS       // 声明一个 token,表示加号
%token TIMES      // 声明一个 token,表示乘号
%token LPAREN     // 声明一个 token,表示左括号
%token RPAREN     // 声明一个 token,表示右括号

%type <int> expr  // 声明一个非终结符expr,类型为 int
%type <int> term
%type <int> factor

%start expr       // 声明起始符号为 expr

7."<>"

功能:`<>`符号括起来的部分通常用于表示类型或模板参数。可以是基本数据类型(比如int,float),也可以是自定义的数据结构(比如<program><block>)

示例:

%union{
    Symbol symbol_token;
    Program program;  
}
%token <symbol_token> STR_CONST IDENT

%type <program> Program
%type <int> expr term

8." ' '  "

功能:单引号用于表示终结符(token),即解析器在输入字符串中需要匹配的字符或标记。例如,`'{'` 和 `'}'` 表示匹配大括号。

示例:

expression : term 
             | expression '+' term
             | expression '-' term;

9."$$"和"$n"

功能:"$$"符号表示当前规则的值。通常用于在规则的动作部分生成计算结果或其他值。

"$n"符号表示当前规则中第 `n` 个符号的值。第一个符号用 `$1` 表示,第二个用 `$2` 表示,以此类推。

示例:

%{
#include <stdio.h>
#include <stdlib.h>
%}

%token NUM
%token PLUS MINUS

%type <int> expr term

%%

// 语法规则
expr:
    expr PLUS term    { $$ = $1 + $3; }  // 表达式加法,expr的结果为expr+term
    | expr MINUS term { $$ = $1 - $3; }  // 表达式减法
    | term             { $$ = $1; }       // 基础表达式
    ;

term:
    NUM               { $$ = $1; }         // 数字,term的结果为NUM
    ;

%%

10."@$""@n"

功能:`@$` 和 `@n` 用于访问在 Bison 中对应的符号或规则组件的位置信息。而”$$“"$n"都是用来处理语义值的,而不是位置信息。

示例:

expr
:term
{
    @$=@1;//将 `term` 的位置信息作为 `expr` 的位置信息。这种处理主要用于在需要位置跟踪的上下文中,例如错误信息的输出或是警告信息的提示。
}

11.“%precedence”"%left""right""%nonassoc"

功能:`"%left"表示左结合,"%right"表示右结合,"%nonassoc"表示非结合。“%precedence”用于定义优先级但没有结合性的运算符(在 Bison 中,有些运算符不需要结合性,比如一元运算符(如负号 `-`),“%precedence”只是简单地告诉解析器运算符的优先级。)

什么是左结合、右结合、非结合?

那算式"a+b+c"而言,左结合是"(a+b)+c",右结合是"a+(b+c)",非结合是不允许"a+b+c"这样加号连续出现的式子。

也就是对于"a+b+c"中的"b",左边有个”+“,右边也有个"+".左结合就是b先与左边的符号结合,右结合就是b先与右边的符号结合。而非结合时,b左边右边都存在非结合的符号就是非法的。

示例1(ps:同样结合性的,后面行声明的优先级更高):

//a+b-c*d/e=(a+b)-(c*d/e)
//对于c,碰见‘-’‘*’时,‘*’的优先级高,优先与*结合;b碰见”+“和"-",优先级相同,是左结合,先与左边的"+"结合
%left '+' '-'       // 左结合,优先级低于乘法和除法
%left '*' '/'       // 左结合,优先级高于加法和减法

示例2:

//a^b^c=a^(b^c)
//-a^c=(-a)^c
%right '^'        // 右结合,用于幂运算
%left '+' '-'
%left '*' '/'
%precedence UMINUS // 单目负号优先级高于乘除

示例3:

//a>b合法,a>b>c不合法(不能连续使用)
%nonassoc '<' '>'  // 非结合,用于比较运算符