Bison的使用

一、Bison对输入的匹配

  bison是基于你所给定的语法来生成一个可以识别这个语法中有效“语句”的语法分析器。例如下面的这个例子:
statement:NAME ‘=’ expression
expression:NUMBER ‘+’ NUMBER
| NUMBER ‘?’ NUMBER
;
其中statement是递归的开始语句,NAME为递归终止符号,expression为递归式,同样的NUMBER也为递归终止符号,所以这句话可以理解为NAME=NUMBER+NUMBER/NUMBER?NUMBER,此处应注意终结符号和非终结符号是不同的.

二、移进/规约分析

1、移进
从一中的例子可以看出,bison在对语句进行分析时,会有一个递归的过程,当它分析到expression这个非终止符时,会递归到下一个语句,左值是expression开始的这个语句。此时bison会把expression先压入内部堆栈,接着切换到一个新的状态,这个状态会对应着刚刚压入内部堆栈的状态,这种过程叫移进.
2、规约
那么同样的,当expression匹配完成后,此时发现已经达到终态,可以完全匹配,所以要将栈中的expression状态移出,这样的过程叫规约.
3、二者的分析
现在举一个具体的例子来对上面的概念进行说明。现在有一个表达式a=13+15,bison先对其进行移进匹配:
首先NAME=expression这个语句没有组成规则,所以要先进行移进,
按照顺序a(此时NAME本是是终结符,所以直接被消除)
a =
a = 13
a = 13 +
a = 13 + 15
13 + 15已经可以匹配expression了,所以消除expression,此时就可以规约statement,将NAME=expression替换成statement.

三、Bison的LALR(1)

bison使用的是LALR(1),即自左向右一次只分析一个记号,看下面的例子
statement: a B C
| d B G
a: A | Z
d: Y|U
此处如果输入A B C,bison是会报错的,因为对于另一个输入Y B G来看,bison必须要通过C,G来区别究竟是a还是b记号,但是可惜的是bison一次只能识别一个记号,所以这2个输入bison识别到B时会出现二义性.

四、对bison和flex实现的一个计算器的代码分析

extern int yylineno;
void yyerror(char* s,...);

struct ast{
   int nodetype;
   struct ast* l;
   struct ast* r;
};

struct numval{
   int nodetype;
   double number;
};

struct ast* newcast(int nodetype,struct ast* l,struct ast* r);
struct ast* newnum(double d);

double eval(struct ast*);

void treefree(struct ast*);

先来看这里的代码,这是对计算器的一个声明,第一行的yylineno是来自于flex中,注意在flex中要使用%option yylineno来选择使用这个变量,yyerror是一个对原有函数的简单扩展,ast是定义即将生成的抽象语法树的节点类型,newcast,newnum是分别用来创建抽象语法树的节点的,
eval是遍历整棵树获得表达式的结果,treefree自然是释放整棵树.

%{
#include <stdio.h>
#include <stdlib.h>
#include "calc.h"
%}
%union{
   struct ast* a;
   double d;
}
%token <d> NUMBER
%token EOL

%type <a> exp factor term

再看这里,是bison文件中的第一部分的声明,union表明即将使用的终结符号的类型有struct ast*和double,也就是一中的NAME和NUMBER的类型。%token指定了使用的终结符的符号,< d >的作用是指定NUMBER的类型为double,在union中可以看到d的类型为double,而EOL没有为其指定类型,故其类型默认为int.%type的作用是用来定义一个非终结字符,也就是一中的expression,同样的< a >是指定这些字符的类型为struct ast*

%%
calclist:
  | calclist exp EOL {printf("=%4.4g\n",eval($2));
                      treefree($2);
                      printf("> ");
  }

  | calclist EOL {printf("> ");}
 ;
exp:factor
  | exp '+' factor{ $$=newcast('+',$1,$3);}
  | exp '-' factor{ $$=newcast('-',$1,$3);}
  ;
factor:term 
   | factor '*' term {$$=newcast('*',$1,$3);}
   | factor '/' term {$$=newcast('/',$1,$3);}
  ;
term: NUMBER {$$=newnum($1);}
   | '|' term{$$=newcast('|',$2,NULL);}
   | '(' exp ')' {$$=$2;}
   | '-' term {$$=newcast('M',$2,NULL);}
  ;
%%

  这是bison的第二部分,是用来定义各种动作的,首先来说第一句,它可以匹配2种结果,一种是规约了exp和EOL之后所得到的表达式的结果,一种是只规约EOL的结果,当然这个地方为了计算器能够连续进行计算,所以将开始符号calclist进行了移进。接下来,来看第二个符号exp,因为表达式可能是嵌套式的,例如4-5+3这种,所以exp同样在右式当中也要移进,这里注意一点,$$代替的是表达式的左式,即exp,$1、$2对应的是右式中的第一个符号和第二个符号.
  特别要注意的是这个地方通过factor,term的移进顺序指定了一个优先级,可以看出,在第二句中,bison一定会先移进factor,接着到factor,会先移进term,之后在term中才会进行规约,这样就实现了绝对值,(,负号的优先级高于乘除,乘除会高于加减

%option noyywrap nodefault yylineno
%{
   #include "calc.tab.h"
   #include "calc.h"
%}
EXP ([Ee][-+]?[0-9]+)
%%
"+" |
"-" |
"*" |
"/" |
"(" |
")" | {return yytext[0];}
[0-9]+"."[0-9]*{EXP}? |
"."?[0-9]+{EXP}? {yylval.d=atof(yytext);return NUMBER;}

\n {return EOL;}
"//".*
[ \t] {}
. {yyerror("Mystery character %c\n",*yytext);}
%%

接下来就是flex中的文件,首先为了保证flex能正常使用bison中定义的符号,当然要包含bison生成的头文件 #include “calc.tab.h”。%option noyywrap是为了保证生成的文件在c中的可移植性.下面的就是对数的各种模式定义以及符号定义。当然有一点需要注意,yylval已经在bison中被定义为union类型了,因此浮点数的值应该赋给yylval.d

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include "calc.h"

struct ast* newcast(int nodetype,struct ast* l,struct ast* r){
   struct ast* a=malloc(sizeof(struct ast));

   if(!a){
     yyerror("out of space");
     exit(0);
   }
   a->nodetype=nodetype;
   a->l=l;
   a->r=r;
   return a;
}

struct ast* newnum(double d){
    struct numval* a=malloc(sizeof(struct numval));

    if(!a){
      yyerror("out of space");
      exit(0);
    }
   a->nodetype='K';
   a->number=d;
   return (struct ast*)a;
}

double eval(struct ast* a){
   double v;
   switch(a->nodetype){
     case 'K':v=((struct numval*)a)->number;break;

     case '+':v=eval(a->l)+eval(a->r);break;
     case '-':v=eval(a->l)-eval(a->r);break;
     case '*':v=eval(a->l)*eval(a->r);break;
     case '/':v=eval(a->l)/eval(a->r);break;
     case '|':v=eval(a->l);if(v<0)v=-v;break;
     case 'M':v=-eval(a->l);break;
     default:printf("internal error:bad node %c\n",a->nodetype);
   }
  return v;
}

void treefree(struct ast* a){
    switch(a->nodetype){
        case '+':
        case '-':
        case '*':
        case '/':
           treefree(a->r);

        case '|':
        case 'M':
             treefree(a->l);

        case 'K':
            free(a);
            break;
         default:printf("internal error:free bad node %c\n",a->nodetype);
    }
}

void yyerror(char* s,...){
   va_list ap;
   va_start(ap,s);

   fprintf(stderr,"%d: error: ",yylineno);
   vprintf(stderr,s,ap);
   fprintf(stderr,"\n");
}

int main(){ 
   printf("> ");
   return yyparse();
}

这是bison中的声明的定义文件

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值