2026-09-05
The asker is trying to eliminate the classic dangling-else shift/reduce conflict in a small Bison grammar by using %nonassoc and %prec — not because they need a novel solution, but to understand precisely how Bison's precedence machinery works when it's applied to something that isn't an operator.
Why it's interesting. Precedence in Bison is almost always taught with binary operators: give + and * priorities, done. But precedence is really a lower-level mechanism — it's a tie-breaker attached to tokens and productions, consulted whenever the LALR(1) table has a shift/reduce conflict on a particular lookahead. Understanding what happens when you try to bend that mechanism to non-operator tokens (like ELSE) forces you to think about the conflict as a comparison between two things: the precedence of the rule about to be reduced and the precedence of the lookahead token.
The mechanics. When Bison sees IF PO PC statement · ELSE ..., it has a choice: reduce (turn the inner IF ... statement into a statement, associating ELSE with the outer if) or shift (attach ELSE to the inner if). Bison resolves this by comparing:
%prec.ELSE.Higher precedence wins; equal precedence uses associativity (%nonassoc → error, %left → reduce, %right → shift).
Sketch of a fix. The idiomatic recipe is:
%nonassoc THEN /* pseudo-token, never lexed */
%nonassoc ELSE
if-stmt : IF PO PC statement %prec THEN
| IF PO PC statement ELSE statement
;
Because ELSE has higher precedence than the fake THEN, and the short rule is tagged with THEN's precedence, Bison prefers to shift ELSE — attaching it to the innermost if, which matches C/Java semantics.
Gotchas. A few things trip people up:
%prec, the short rule's precedence defaults to PC (its rightmost terminal), which is probably undeclared and therefore has no precedence — so Bison silently falls back to its default "prefer shift" and emits a warning rather than resolving cleanly.%nonassoc makes equal-precedence conflicts a syntax error at parse time, not a grammar error at generation time — surprising if you expected it to just pick a side.bison -Wcounterexamples (or -v and read the .output file) — the state table shows exactly which precedences were compared.%prec looks like a knob you turn on operators, but it's really a rule-level annotation whose behavior only makes sense once you understand the shift-vs-reduce comparison it drives.
