Bison笔记
2016/10/21
1.语法结构
%{
C/C++头文件、全局文件、全局变量、类型定义
词法分析器yylex(采用lex进行词法分析)和错误打印函数
%}
Bison声明区间。定义之后用到的终结符、非终结符、操作符优先级
%%
Bison语法规则定义
%%
C/C++代码 需要定义prologue区域函数,或者其他代码,生成的c/c++文件会完全拷贝这份代码。
2.FAQ
终结符、非终结符定义
Token用于定义终结符 type定义非终结符 操作符也属于终结符
Left表示左关联运算符 right表示右关联运算符
%token NUM
%nonassoc ‘<’ 表示该终结符无结合性 不能出现a<b<c
%left ‘+’ ‘-’ 左结合 后面接操作符 下方的操作符比上方的优先级高
%left ‘*’ ‘/’
%right NEG NEG表示非
%right ‘^’
Bison声明区域
%union{
Expressions* expressions;/*表达式集合*/
Expression* expression;/*表达式*/
char name[32];
double num;
}
%token ASSIGN 258
%token<num> DOUBLE_CONST 259
%token<name> IDENTIFIER 260
%token IF 261 THEN 262 ELSE 263 FI 264
%token WHILE 265 LOOP 266 POOL 267
%type<expression> expr
%type<expressions> exprs
%type<expressions> exprs_no
%%
input:
/* empty */
| exprs
;
exprs:
error { $$ = 0;}
| exprs error
| expr ';'
{
$$ = t_single_exprs($1);
Execute($1);
}
| exprs expr ';'
{
$$ = t_append_exprs($1, $2);
Execute($2);
}
;
expr:
IDENTIFIER { $$ = t_id($1); }
| DOUBLE_CONST { $$ = t_num($1);}
| expr '+' expr { $$ = t_plus($1, $3); }
| expr '-' expr { $$ = t_sub($1, $3); }
| expr '*' expr { $$ = t_mul($1, $3); }
| expr '/' expr { $$ = t_div($1, $3); }
| '(' expr ')' { $$ = $2;}
| '{' exprs_no '}' { $$ = t_block($2);}
| expr '<' expr { $$ = t_less($1, $3); }
| expr '=' expr { $$ = t_eq($1, $3); }
| IDENTIFIER ASSIGN expr { $$ = t_assign($1, $3); }
| IF expr THEN expr ELSE expr FI { $$ = t_if($2, $4, $6); }
| WHILE expr LOOP expr POOL { $$ = t_while($2, $4); }
;
exprs_no:
expr ';'
{
$$ = t_single_exprs($1);
}
| exprs_no expr ';'
{
$$ = t_append_exprs($1, $2);
}
;
%%
终结符和非终结符
终结符的类型通过"%token<类型名>终结符"这样的格式来确定
%type是指定非终结符的类型,用法和%token一样,不过不需要指定编号。我们可以发expr是expression类型。这里expr的意思是一个表达式,exprs和exprs_no是多个表达式集合。
非终结符也可以通过%type<类型名>非终结符这种格式来表示。
两者区别是终结符相当于原子不可分,而非终结符相当于分子可分,可由终结符或非终结符归约而成。
终结符使用词法分析来识别,而非终结符使用的归约方法。
在bison中词法分析需要由自己指定,比如示例中的yylex(),也可以采用flex定义词法分析规则,利用生成的词法分析代码来完成,比如在nessus使用的nasl语言使用的自己实现的词法分析,详见规则文件中的mylex,yara中则使用的flex实现的词法分析。
Union结构
Union{
par_exp_t* exp;
int lt_integer;
}
Bison中默认将所有的语义值都定义为int类型,可以通过定义宏YYSTYPE来改变值的类型。如果有多个值类型,则需要通过在Bison声明中使用%union列举出所有的类型。
Union中的每一个项,都是一个语法规则的每一个非终结符.
其中par_exp_t用来描述被识别出的exp的信息,对应到c/c++代码中的类型。
可以这样定义此非终结符
%type exp
%type lt_integer
如果类型名称与非终结符名称不一致可使用如下方法
Union{
par_exp_t* eee;
int iii;
}
%type<eee> exp
%type<iii> lt_integer
在一条归约规则中,每个终结符或非终结符都对应一个c类型,如果没有指定则为int类型,具体的类型在union中定义,在每一条匹配规则中用$$或$n表示该类型。
声明语法的开始符号
%start tiptop
这是告知bison, 这是语法最终需要规约的非终结符号。
示例
input:
/* empty */
| exprs
;
exprs:
error { $$ = 0;}
| exprs error
| expr ';'
{
$$ = t_single_exprs($1);
Execute($1);
}
| exprs expr ';'
{
$$ = t_append_exprs($1, $2);
Execute($2);
}
;
在一条规则中$$表示表达式的返回值,$1表示第一个终结符,依次类推。
从上面input可以看到输入为一个表达式集合,而exprs是由expr ';'或exprs expr ';'组成。也就是说一个表达式集合,是由一个或多个表达式后跟';'组成。
$$ = t_single_exprs($1);动作的意思是创建只有一个表达式expr的表达式集,赋值给exprs,此时只归约到一个表达式。
$$ = t_append_exprs($1, $2);动作的意思是把表达式expr加入到exprs集合里。Execute($1);的意思是执行这个表达式。这里执行的意思是计算这个表达式的语义值,输出结果。
归约过程
expr:
IDENTIFIER { $$ = t_id($1); }
| DOUBLE_CONST { $$ = t_num($1);}
| expr '+' expr { $$ = t_plus($1, $3); }
| expr '-' expr { $$ = t_sub($1, $3); }
| expr '*' expr { $$ = t_mul($1, $3); }
| expr '/' expr { $$ = t_div($1, $3); }
| '(' expr ')' { $$ = $2;}
| '{' exprs_no '}' { $$ = t_block($2);}
| expr '<' expr { $$ = t_less($1, $3); }
| expr '=' expr { $$ = t_eq($1, $3); }
| IDENTIFIER ASSIGN expr { $$ = t_assign($1, $3); }
| IF expr THEN expr ELSE expr FI { $$ = t_if($2, $4, $6); }
| WHILE expr LOOP expr POOL { $$ = t_while($2, $4); }
;
'{' exprs_no '}' { $$ = t_block($2);}是类似cool语言的一个语法规则:一个表达式可以推出大括号包围的表达式集合。这个集合类似之前的exprs,区别是exprs_no不需要立即执行表达式的值。因为可能条件判断不符合,所以这段代码就不能执行。
3.语法规则分析
expr:
NUM { $$ = $1; }
| expr '+' expr { $$ = $1 + $3; }
| expr '-' expr { $$ = $1 - $3; }
;
例如这样一个语句:1+3-2。根据规则expr->NUM先归约expr+3-2,然后规约'+',继续归约终结符为expr+expr-2。因为左结合,根据expr->expr + expr,得expr-2。继续归约终结符的expr-expr,最后结果为expr,归约结束
移进归约分析
语法分析有自顶向下(LL、递归下降分析)和自底向上(LR、移进归约分析)两种方法。
Bison采用LALR分析方法,适用于上下文无关文法。
参考链接http://www.cppblog.com/woaidongmao/archive/2008/11/23/67635.aspx
操作符优先级
例如1-2*3,考虑到文法二义性可能会产生2种方式进行分析。
假定分析器已经看到了终结符'1','-'和'2';那么应该对它们归约到减法运算规则吗?这取决于下一个终结符。当然,若下一个终结符是')',就必须归约;此时移进是非法的,因为没有任何规则可以对序列'- 2 )'进行归约,也没有以这个序列开始的什么东西。但是如果下一个终结符是'*'或者'<',那么就需要做一个选择:移进或者归约,都可以让分析得以完成,但是却有不同的结果。
为了决定Bison应该怎么做,必须考虑这两个结果。若下一个终结符即操作符op被移进,那么必然是op首先做归约,然后才有机会让前面的减法操作符做归约。其结果就是(有效的)'1–(2 op 3)'。另一方面,若在移进op之前先对减法做归约,那结果就是'(1–2) op 3'。很显然,这里移进或者规约的选择取决于减法操作符'-'与下一个操作符op之间的优先级:若op是乘法操作符'*',那么就选择移进;若是关系运算符'<'则应该选择规约。
左关联与右关联操作符
那么诸如'1 – 2 – 5'这样的输入又如何呢?是应该作为'(1–2)–5'还是应该作为'1–(2–5)'?对于大多数的操作符,我们倾向于前一种形式,称作左关联(left association)。后一种形式称作右关联(right association),对于赋值操作符来说是比较理想的。当堆栈中已经有'1–2'且预读终结符是'-',此时分析器选择移进还是归约与选择左关联还是右关联是一回事:移进将会进行右关联。