在Bison中能否为定义为非终结符的运算符配置优先级规则?
Bison公共二元运算符规则优先级失效解决方案
问题根因
Bison的优先级匹配规则是编译期静态确定的,每个产生式的默认优先级等于该产生式最右侧终结符的优先级。
你当前的写法将所有二元运算符封装到了binop非终结符中,核心产生式expr: expr binop expr最右侧的符号是expr(非终结符),没有关联任何运算符token,Bison无法为该产生式匹配不同运算符的优先级,所有二元运算只能按默认的移进归约顺序处理,自然优先级、结合性规则全部失效。
可行解决方案
不需要拆分为19条单运算符规则,只需要按优先级层级对表达式和运算符分组即可,代码量远低于拆分单运算符的方案,同时保持逻辑规整:
- 先按实际运算符优先级划分层级,同优先级、同结合性的运算符分为一组
- 每个优先级层级对应一级表达式规则,同一层级的运算符公共绑定到该层的二元运算产生式
示例代码
// 优先级声明从低到高排列 %left ASSIGN %left OR %left AND %left GT GE LT LE EQ NE %left PLUS MINUS %left MUL DIV %left UMINUS NOT // 顶层表达式 expr : assign_expr ; // 赋值运算层 assign_expr : cmp_expr | assign_expr assign_op cmp_expr {$$ = new BinOpExpression($1, $3, $2);} ; // 比较运算层 cmp_expr : add_expr | cmp_expr cmp_op add_expr {$$ = new BinOpExpression($1, $3, $2);} ; // 加减运算层 add_expr : mul_expr | add_expr add_op mul_expr {$$ = new BinOpExpression($1, $3, $2);} ; // 乘除运算层 mul_expr : unary_expr | mul_expr mul_op unary_expr {$$ = new BinOpExpression($1, $3, $2);} ; // 一元运算层 unary_expr : primary_expr | NOT unary_expr {$$ = new UnaryBooleanNegationExpression($2);} | MINUS unary_expr %prec UMINUS {$$ = new UnaryNumericNegationExpression($2);} ; // 基础原子层(括号、常量、标识符等) primary_expr : LPAREN expr RPAREN {$$ = $2;} | NUMBER | ID ; // 各层级运算符分组定义 assign_op : ASSIGN {$$ = new Assign();} ; cmp_op : GT {$$ = new GreaterThan();} | GE {$$ = new GreaterOrEqual();} | LT {$$ = new LessThan();} | LE {$$ = new LessOrEqual();} | EQ {$$ = new Equal();} | NE {$$ = new NotEqual();} ; add_op : PLUS {$$ = new Add();} | MINUS {$$ = new Sub();} ; mul_op : MUL {$$ = new Mul();} | DIV {$$ = new Div();} ;
这种写法下,每个层级的二元运算产生式的优先级会自动匹配该层级运算符的优先级,你有多少个优先级层级就写多少条二元运算规则,远少于19条单运算符规则,也不需要重复编写BinOpExpression的构造逻辑。
为什么没有完全不拆分规则的方案
Bison的优先级机制是编译期基于token静态生成的,无法在运行时根据binop返回的运算符对象动态调整产生式优先级,因此必须让产生式在编译期就能关联到对应优先级的运算符token,完全不拆分的公共expr binop expr规则无法满足这个要求。
内容的提问来源于stack exchange,提问作者Eagle E
相关产品推荐
相关产品推荐

