Introduction
A bottom-up parser builds the parse tree from the leaves up to the root. It starts from the input string and repeatedly replaces a substring that matches the body of a production by the production's head, a step called a reduction, until only the start symbol remains. Read backwards, the sequence of reductions is a rightmost derivation of the input.
The general mechanism is shift-reduce parsing: a stack holds grammar symbols, the parser either shifts the next input token onto the stack or reduces a group of symbols on top of the stack, and the whole difficulty is knowing when to reduce and by which production. The LR family of parsers (LR(0), SLR, LALR, canonical LR) answers that question with automata; operator-precedence parsing answers it, for a restricted class of grammars, with precedence relations between terminals. This note gives the mechanism, the vocabulary (handles, viable prefixes, conflicts) and operator precedence; the LR tables follow in the next notes.
Prerequisites
- FIRST and FOLLOW Sets: FOLLOW sets decide reductions in SLR parsing and appear in conflict analysis.
- Rightmost derivations and parse trees, from the Theory of Computation notes.
Core concepts
The running grammar is the layered expression grammar
E -> E + T | T
T -> T * F | F
F -> ( E ) | idwhich is left-recursive and therefore unsuitable for LL(1) parsing, but is handled directly by bottom-up parsers. The parser's working storage is a stack, and a shift-reduce parser is a deterministic pushdown automaton whose stack holds grammar symbols (or states).
| Term | Definition |
|---|---|
| Right-sentential form | A sentential form that occurs in some rightmost derivation from the start symbol |
| Handle | In a right-sentential form , a substring together with a production such that replacing that occurrence of by gives the previous right-sentential form of a rightmost derivation |
| Handle pruning | Repeatedly locating the handle and reducing it; it yields the rightmost derivation in reverse |
| Viable prefix | A prefix of a right-sentential form that does not extend past the right end of the handle; exactly the strings that can appear on the stack of a shift-reduce parser |
| Shift-reduce conflict | The parser cannot decide whether to shift the next token or to reduce |
| Reduce-reduce conflict | The parser cannot decide which of two productions to reduce by |
The handle is always on top of the stack. In a shift-reduce parser the handle never appears buried inside the stack: once the right end of a handle has been shifted, the parser reduces before shifting again. That is why a stack suffices.
Unambiguous grammars have unique handles. Every right-sentential form of an unambiguous grammar has exactly one handle. An ambiguous grammar can have two, which is why it always causes conflicts in any deterministic shift-reduce parser.
A handle is not just any matching body. In the right-sentential form E + T * id, the substring E + T matches the body of , but reducing it gives E * id, which is not a right-sentential form of this grammar. The handle is id (by ).
How it works
The four actions
| Action | Effect |
|---|---|
| Shift | Move the next input token onto the stack |
| Reduce | Pop symbols (the handle ) and push |
| Accept | The stack holds only the start symbol and the input is exhausted |
| Error | No valid action; call recovery |
A shift-reduce trace
Parsing id * id + id (stack bottom is , top at the right):
| Step | Stack | Input | Action |
|---|---|---|---|
| 1 | $ | id * id + id $ | shift |
| 2 | $ id | * id + id $ | reduce F -> id |
| 3 | $ F | * id + id $ | reduce T -> F |
| 4 | $ T | * id + id $ | shift |
| 5 | $ T * | id + id $ | shift |
| 6 | $ T * id | + id $ | reduce F -> id |
| 7 | $ T * F | + id $ | reduce T -> T * F |
| 8 | $ T | + id $ | reduce E -> T |
| 9 | $ E | + id $ | shift |
| 10 | $ E + | id $ | shift |
| 11 | $ E + id | $ | reduce F -> id |
| 12 | $ E + F | $ | reduce T -> F |
| 13 | $ E + T | $ | reduce E -> E + T |
| 14 | $ E | $ | accept |
There are 5 shifts (one per token) and 8 reductions (one per interior node of the parse tree, that is, one per step of the rightmost derivation). Two decisions show why a parser needs more than the stack contents:
- At step 4 the stack is
$ Tand the next token is*. Reducing would be wrong: theTmust become the left operand of*. The parser shifts because*cannot follow . - At step 8 the stack is
$ Tagain, but the next token is+. Now reducing is right, because+can follow .
The decision depends on the next token, which is exactly what LR parsing tables encode.

Handle pruning for id + id * id
Reading a rightmost derivation backwards, each right-sentential form has one handle:
| Right-sentential form | Handle | Reduce by |
|---|---|---|
id + id * id | first id | |
F + id * id | F | |
T + id * id | T | |
E + id * id | second id | |
E + F * id | F | |
E + T * id | third id | |
E + T * F | T * F | |
E + T | E + T | |
E | accept |
Measured in input tokens covered, the right end of each handle never moves left: 1, 1, 1, 3, 3, 5, 5, 5. That is why the handle is always on top of the stack. In E + T * id, T is not reduced to although matches: reducing it would give E + E * id, which is not a right-sentential form.
Viable prefixes
Every stack in the trace is a viable prefix: T *, T * F, E + T, and so on. Some strings are not: E + T + is never a stack content, because before + is shifted after E + T the handle E + T must be reduced. The set of viable prefixes of any grammar is a regular language; the LR(0) automaton of the next note is a DFA that recognises it, and that fact is what makes LR parsing possible.
Conflicts
- Shift-reduce. The classic case is the dangling else: with
if E then Son the stack andelsenext, the parser can reduce the shortifor shift theelse. Parser generators resolve it by shifting, which attaches theelseto the nearestif. - Reduce-reduce. Two different productions have their bodies on top of the stack; typical when two nonterminals can derive the same string, for example a name that may be a procedure call or an array reference in
p(i). - An ambiguous grammar always gives at least one conflict. A conflict does not prove ambiguity: it may only mean that the chosen parsing method (LR(0), SLR, LALR) is too weak for the grammar.
Operator-precedence parsing
An operator grammar has no -productions and no production body with two adjacent nonterminals. For such grammars a shift-reduce parser can decide using three relations between terminals:
| Relation | Meaning |
|---|---|
| yields precedence to : starts a handle (shift) | |
and belong to the same handle, as ( and ) | |
| takes precedence over : the handle ends at (reduce) |
Relations are read between the topmost terminal on the stack (ignoring nonterminals) and the next input token. From precedence and associativity: if has higher precedence than , then and ; for equal precedence, left-associative operators give and , right-associative ones give both ways; every operator id and id every operator; everything and everything .
For + and * (both left-associative, * higher), with rows the stack terminal and columns the input token:
id | + | * | $ | |
|---|---|---|---|---|
id | ||||
+ | ||||
* | ||||
$ | accept |
The parse of id + id * id with this table (each reduced handle becomes an unnamed nonterminal N):
| Stack | Input | Relation | Action |
|---|---|---|---|
$ | id + id * id $ | shift | |
$ id | + id * id $ | reduce id | |
$ N | + id * id $ | shift | |
$ N + | id * id $ | shift | |
$ N + id | * id $ | reduce id | |
$ N + N | * id $ | shift | |
$ N + N * | id $ | shift | |
$ N + N * id | $ | reduce id | |
$ N + N * N | $ | reduce N * N | |
$ N + N | $ | reduce N + N | |
$ N | $ | accept |
There are 5 shifts and 5 reductions, against 8 reductions in the LR parse of the layered grammar: an operator-precedence parser never performs the unit reductions and , because it does not track which nonterminal is on the stack.
An operator-grammar trap. E -> E A E | id, A -> + | * is not an operator grammar: the body E A E has two adjacent nonterminals. Substituting for gives E -> E + E | E * E | id, which is an operator grammar (and ambiguous, which operator precedence tolerates, because the relations choose the grouping).
Precedence functions. The table can be compressed into two integer functions (stack side) and (input side) with and . For the table above one solution is , , , . Each defined equal-precedence relation similarly requires ; these implications apply to defined relations, not blank/error entries. The price is that blank (error) entries disappear: every pair now compares, so some errors are detected later.
Operator-precedence parsing handles only a small class of grammars and has trouble with operators such as unary minus that share a symbol with a binary one; it is mainly used for expressions.
Evaluating an expression under given precedence and associativity
The same rules decide how an expression is grouped. Take 2 + 3 * 4 - 5:
| Rules | Grouping | Value |
|---|---|---|
Usual: * above + and -, all left-associative | 9 | |
| All three equal, left-associative | 15 | |
| All three equal, right-associative | ||
+ above * above -, all left-associative | 15 | |
- above * above + |
Important properties and observations
- Bottom-up parsing produces a rightmost derivation in reverse; top-down parsing produces a leftmost derivation in order.
- Shifts equal the number of tokens; reductions equal the number of interior nodes of the parse tree (each -reduction pushes a nonterminal without popping anything).
- Left recursion is harmless, even preferable, in bottom-up parsing:
E -> E + Treduces each operand as soon as it is complete and keeps the stack short. - The stack content is always a viable prefix; a parser that detects an error does so as soon as the prefix read so far cannot be extended to a sentence (the valid-prefix property of LR parsers).
- Common misconceptions. "The handle is the leftmost substring matching a body": not always, as
E + T * idshows. "Every conflict means the grammar is ambiguous": no, only that the method is too weak. "Operator-precedence parsing works for any grammar": only for operator grammars, and not for all of them.
GATE examination perspective
- Reverse of which derivation. Bottom-up parsing traces out a rightmost derivation in reverse. Options with "leftmost" or "rightmost in order" are wrong.
- Find the handle. Consider a given right-sentential form: work out the previous right-sentential form, do not just match the first body you see.
- Count shifts and reductions. Apply this to a string: shifts = tokens, reductions = interior nodes (rightmost-derivation steps).
- Viable prefixes. Decide whether a string can be on the stack; a string containing a complete handle followed by more symbols is not viable.
- Handle-pruning sequences. Questions list the right-sentential forms or the reductions in order; build them from the rightmost derivation, one interior node per reduction.
- Operator grammar or not. Check every body for adjacent nonterminals and for ; a helper nonterminal between two nonterminals disqualifies the grammar until it is substituted away.
- Operator-precedence reduction counts. These skip unit productions:
id + id * idtakes 5 reductions there, 8 in an LR parse. - Conflicts. Identify shift-reduce versus reduce-reduce; the dangling else is the standard shift-reduce case and is resolved by shifting.
- Precedence and associativity puzzles. Questions define unusual precedences or associativities and ask for the value of an expression; apply the grouping rules strictly, as in the table above.
Summary
Sources
- A. V. Aho, M. S. Lam, R. Sethi, J. D. Ullman, Compilers, Principles, Techniques, and Tools, 2nd ed., Pearson Addison-Wesley, 2007: Section 4.5 (bottom-up parsing: reductions, handle pruning, shift-reduce parsing, conflicts during shift-reduce parsing), Section 4.6.5 (viable prefixes), Section 4.8.1 (precedence and associativity to resolve conflicts), Section 4.8.2 (the dangling-else ambiguity). Operator-precedence relations and precedence functions are standard material; the 2nd edition's contents list no separate section for them, so no section is cited.