6.4 Shift/Reduce and Reduce/Reduce Conflicts
A parser table conflict is a proof that the current grammar and parser construction cannot choose one action in one table cell. It is not boilerplate to suppress. Conflict reports locate ambiguity, insufficient lookahead, lossy state merging, or a deliberate language rule that must be stated explicitly.
The two conflict kinds differ in character; the table below contrasts them first:
| kind | competitors | common root cause | good default? | typical fix |
|---|---|---|---|---|
| shift/reduce | shift lookahead vs reduce a completed rule | undeclared precedence/associativity, dangling else | often (after fixing language meaning) | layered grammar, precedence declaration |
| reduce/reduce | two completed productions | genuine same-position ambiguity, grammar overlap | rarely | restructure syntax, add keyword/delimiter, defer to semantics |
Remember: a shift/reduce conflict usually means precedence has not been declared yet, while a reduce/reduce conflict usually means the grammar itself is unclear and needs a structural change.
Shift/Reduce: Continue or Finish?
A shift/reduce conflict asks whether the parser should shift the lookahead to grow the current phrase or reduce a completed production now. The ambiguous expression grammar is the classic example:
Expr -> Expr + Expr
| Expr * Expr
| NUMBERAfter parsing NUMBER + NUMBER with lookahead *, reducing Expr + Expr immediately produces (NUMBER + NUMBER) * ...; shifting * produces NUMBER + (NUMBER * ...). Language design must decide that * binds tighter. It can be encoded by rewriting the grammar into levels, declaring precedence/associativity to a generator, or writing a specialized expression parser.
The dangling-else grammar creates another familiar shift/reduce conflict. Most languages resolve it by shifting else, pairing it with the nearest unmatched if. That convention should be explicit in grammar, precedence declarations, or documentation; relying on a generator's default shift preference hides policy in a tool default.
Reduce/Reduce: Two Completed Stories
A reduce/reduce conflict means two or more completed productions both claim the same state and lookahead. Unlike precedence, this rarely has a pleasant default. For example:
Decl -> Type IDENT
Type -> IDENT
Name -> IDENTIf a grammar permits IDENT to be either Type or Name in the same syntactic position, the parser has no basis to choose. The likely repair is to restructure the syntax, add a disambiguating keyword or delimiter, defer a contextual distinction to semantic analysis, or select a parsing strategy designed for genuine ambiguity.
Choosing the first rule in source order produces a deterministic table but not a justified language. It also makes grammar maintenance dangerous: reordering rules can silently change accepted programs.
Diagnose Before You Disambiguate
For every conflict, capture:
- The state or item set, the lookahead, and competing actions.
- A shortest witness token sequence that reaches the cell.
- Whether the conflict is intended ambiguity, insufficient parser power, or an accidental grammar overlap.
- The chosen resolution and its user-visible syntax consequence.
Precedence declarations are excellent for ordinary operator syntax. They are poor medicine for unrelated structural ambiguity. Keep resolved-conflict counts in CI: an unexpected new count is a regression signal, while an intentional count should have an explanatory test.
Resolve the Meaning, Then Encode It
For a - b - c, a left-associative language wants (a - b) - c; a right-associative language wants a - (b - c). A shift/reduce preference is only correct after that source-level choice is made. A layered grammar can encode it directly: Sum -> Sum - Product | Product makes subtraction left-associative while Product protects higher-precedence multiplication. A generator precedence declaration can encode the same policy more compactly, but tests must assert the resulting AST, not merely that no conflict warning appears.
Reduce/reduce conflicts need an even stricter response. If both Type -> IDENT and Name -> IDENT can reduce in the same syntactic location, precedence does not explain which meaning a programmer intended. A keyword, delimiter, scope rule in a later phase, or a more expressive parsing strategy may be required. “First rule wins” is deterministic only by accident.
Conflict Triage
1. Reproduce the shortest token sequence reaching the conflict.
2. Draw the two possible trees or parser actions.
3. State which source program meaning the language promises.
4. Encode that promise in grammar/declaration/code, then add a regression test for the witness.