如果你已经储备bison的相关基础知识,阅读理解下面的代码会轻松得多。没有bison基础的同学请点击查看bison基本的语法规则及相关介绍。
移进/规约冲突一般是由文法二义性造成的,关于二义性可以看看这篇文章,以及这位中科院老师的讲解。对于这种问题bison提供了一个聪明的方法,它可以在语法规则之外单独描述优先级。这不仅消除了二义性,也使得语法分析器代码变得短小而且易于维护。
bison操作符优先级的规则
bison操作符优先级的规则,使用%left
,%right
,%nonassoc
或%precedence
声明记号并指定其优先级和结合性。它的语法与%token
语法非常相似,如下:
%left symbols…
%left <type> symbols…
以表达式“ x op y op z”为例,运算符op的关联性决定了运算符嵌套,是通过先将x与y分组还是先将y与z分组来解析。
- %left指定左相关性(将x与y优先分组)
- %right指定右相关性(将y与z优先分组)
- %nonassoc未指定结合性,这意味着“ x op y op z”被视为语法错误。
- %precedence仅赋予符号优先级,并且根本不定义任何关联性。使用此命令仅定义优先级,并保留由于启用了结合性而引起的任何潜在冲突。
运算符的优先级确定它如何与其他运算符嵌套。在单个优先级声明中声明的所有标记都具有相同的优先级,并根据它们的关联性嵌套在一起。当在不同优先级声明中声明的两个标记相关联时,后一个声明的标记具有更高的优先级,并且首先分组。
结合下面的一个例子进一步学习bison操作符的优先级规则:
%{
enum Node_T{
NT_UNDEF,
NT_ADD,
NT_SUB,
NT_MUL,
NT_DIV,
NT_ABS,
NT_NEG,
};
struct ASTNode *newast(int NodeType, struct ASTNode *Lft, struct ASTNode *Rht);
struct ASTNode *newnum(double d);
%}
/* %union关键字声明了语法规则中所有语法值可能会用到的数据类型的一个集合 */
%union{
struct ASTNode *a;
}
/* 声明的顺序决定了优先级,越后声明的优先级越高。bison遇到移进/规约冲突时,它将查询优先级表,来解决冲突 */
%left '+' '-'
%left '*' '/'
%nonassoc '|' UMINUS // UMINUS为符号操作符的伪记号。使用%nonassoc来声明'|'和UMINUS,表示这两个操作符没有结合性,并且它们具有最高的优先级。
%type <a> exp
%%
...
exp: exp '+' exp { $$ = newast(NT_ADD, $1, $3); }
| exp '-' exp { $$ = newast(NT_SUB, $1, $3); }
| exp '*' exp { $$ = newast(NT_MUL, $1, $3); }
| exp '/' exp { $$ = newast(NT_DIV, $1, $3); }
| '|' exp { $$ = newast(NT_ABS, $2, NULL); }
| '(' exp ')' { $$ = $2; }
| '-' exp %prec UMINUS { $$ = newast(NT_NEG, NULL, $2); } // '-'原本声明时的优先级要小于'*',使用%pre UMINUS,可以让'-'拥有UMINUS的优先级。从而使得表达出"负号"的效果
| NUMBER { $$ = newnum($1); }
%%
什么时候不应该使用优先级规则
使用优先级规则解决移进/规约冲突时,有的时候会写出让人难以明白的语法规则。例如下面情况:表达式语法或解决在if/then/else语言结构的语法中的"dangling else"冲突,如下:
一个经典的例子:
从这个语法片段可以看出,else是可选的。不幸的是,下列代码片段:
这种语法的二义性,应该通过修正语法来解决冲突。引入新的规则
来确定到底由哪个if来控制特定的else子句。应该将上面的语法规则修改为:
通常出现二义性语法都可以通过引入新的规则
来消除二义性,这样做会增加语法树的高度。