[Bison] Bison 中的 prec
TL; DR
本文主要研究在 Bison 中 %prec 的含义。
结论:
1 | %left '+' 'a' |
表示 exp : 'a' exp %prec ptm
这条语法的优先级和关联性与 ptm 这个符号一致。
基础知识
Bison 中的 shift/reduce
1 | // calc.l |
上面是一个只有 +-* 的语法,下面用一个实际的例子来解释 Bison 中的 shift/redeuce。
假设要规约 -4+4;
通过语法分析生成下述的 token 数组 [-, number, +, number]
,规约过程如下:
1 | . - number + number |
读入一个下一个符号,shift:
1 | - . number + number |
无法 reduce,shift:
1 | - number . + number |
number 可以 reduce,下一个 token 是 +
,所以选择 reduce:
1 | - term . + number |
term 可以 reduce,下一个 token 是 +,所以选择 reduce:
1 | - exp . + number |
现在遇到一个问题:-
exp 可以规约,但是 exp 后面也可以匹配 +
,也就是此时遇到了 shift/reduce 冲突。Bison 选择能 shift 就 shift,除非有其他的优先级的设置。
This situation, where either a shift or a reduction would be valid, is called a shift/reduce conflict. Bison is designed to resolve these conflicts by choosing to shift, unless otherwise directed by operator precedence declarations. To see the reason for this, let’s contrast it with the other alternative.
这段话来自:https://www.gnu.org/software/bison/manual/html_node/Shift_002fReduce.html
因为此时没有其他的优先级的设置,所以选择 shift:
1 | - exp + . number |
无法 reduce 选择 shift:
1 | - exp + number . |
选择 reduce:
1 | - exp + term . |
选择 reduce:
1 | - exp + exp . |
选择 reduce:
1 | exp + exp . |
选择 reduce:
1 | exp . |
Bison 中的 Precedence 和 Associativity
Bison 文件中:
1 | %right '=' |
这个在 Bison 中表示:
- 优先级:
=
<+
==-
<*
==/
- 关联性:
=
右关联,+-*/
左关联
优先级好理解,1 + 2 * 3 先乘法后加法。
关联性表示,当遇到 1 + 1 + 2 的时候,左关联表示:
1 | (1 + 1) + 2 |
右关联性表示:
1 | 1 + (1 + 2) |
赋值语句是右关联的,因为:
1 | a = b = c |
是先给 b 赋值再给 a 赋值的。
%prec 在 Bison 中的作用
1 | %left '+' 'a' |
首先为什么要引入一个新的符号 a
呢?原因在于 -
一元运算符的优先级虽然比 *
高,但是先计算 *
还是先计算 -
不影响最后的答案。所以本文构造一个新的运算符 a
。它在做二元运算的时候和减法一致。一元运算的时候将后者的值平发。并且做二元运算的时候优先级比 *
低。
结论就是 %prec ptm
把 exp: 'a' exp
的优先级提升到和 ptm 这个符号一致。下面通过实际的例子解释为什么要这样做。
实际操作
本文构造的运算符 a
在做一元运算符的时候优先级要比 *
高。比如当遇到下面的输入的时候:
1 | a4*4; |
答案应该为 (a4)*4 = 64
而不应该是a(4*4)=256
。但是 a
已经定义过优先级了:
1 | %left '+' 'a' |
比 *
的优先级要低。为了实现这一目的可以使用 %prec 这个符号。所有代码如下:
1 | // calc.y |
1 | // calc.l |
1 | calc: lex.yy.c y.tab.c |
当没有 %prec
的时候遇到 a4*4;
:
1 | $ make calc && ./calc |
答案为 256,加上 %prec 以后:
1 | // calc.y 部分代码 |
1 | $ make calc && ./calc |
原因在于提升了 a exp
的优先级。当遇到 a exp . *
的时候选择 reduce 而不是 shift。
参考
- https://www.gnu.org/software/bison/manual/html_node/Shift_002fReduce.html
- https://www.gnu.org/software/bison/manual/html_node/Contextual-Precedence.html
- https://www.ibm.com/docs/en/zos/2.3.0?topic=section-precedence-in-grammar-rules
- https://www.ibm.com/docs/en/zos/2.4.0?topic=section-precedence-associativity