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

Text
E -> E + T | T
T -> T * F | F
F -> ( E ) | id

which 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).

TermDefinition
Right-sentential formA sentential form that occurs in some rightmost derivation from the start symbol
HandleIn a right-sentential form γ\gamma, a substring β\beta together with a production A→βA \to \beta such that replacing that occurrence of β\beta by AA gives the previous right-sentential form of a rightmost derivation
Handle pruningRepeatedly locating the handle and reducing it; it yields the rightmost derivation in reverse
Viable prefixA 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 conflictThe parser cannot decide whether to shift the next token or to reduce
Reduce-reduce conflictThe 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 E→E+TE \to E + T, but reducing it gives E * id, which is not a right-sentential form of this grammar. The handle is id (by F→idF \to \textbf{id}).

How it works

The four actions

ActionEffect
ShiftMove the next input token onto the stack
Reduce A→βA \to \betaPop ∣β∣\lvert\beta\rvert symbols (the handle β\beta) and push AA
AcceptThe stack holds only the start symbol and the input is exhausted
ErrorNo valid action; call recovery

A shift-reduce trace

Parsing id * id + id (stack bottom is $\text{\textdollar}, top at the right):

StepStackInputAction
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 $ T and the next token is *. Reducing E→TE \to T would be wrong: the T must become the left operand of *. The parser shifts because * cannot follow EE.
  • At step 8 the stack is $ T again, but the next token is +. Now reducing E→TE \to T is right, because + can follow EE.

The decision depends on the next token, which is exactly what LR parsing tables encode.

Shift-reduce trace of id \* id + id with the handle at each reduction
Figure 1. The 14-step shift-reduce trace of id * id + id, with the handle underlined in the stack at each of the 8 reductions.

Handle pruning for id + id * id

Reading a rightmost derivation backwards, each right-sentential form has one handle:

Right-sentential formHandleReduce by
id + id * idfirst idF→idF \to \textbf{id}
F + id * idFT→FT \to F
T + id * idTE→TE \to T
E + id * idsecond idF→idF \to \textbf{id}
E + F * idFT→FT \to F
E + T * idthird idF→idF \to \textbf{id}
E + T * FT * FT→T∗FT \to T * F
E + TE + TE→E+TE \to E + T
Eaccept

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 EE although E→TE \to T 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 S on the stack and else next, the parser can reduce the short if or shift the else. Parser generators resolve it by shifting, which attaches the else to the nearest if.
  • 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 ε\varepsilon-productions and no production body with two adjacent nonterminals. For such grammars a shift-reduce parser can decide using three relations between terminals:

RelationMeaning
a⋖ba \lessdot baa yields precedence to bb: bb starts a handle (shift)
a≐ba \doteq baa and bb belong to the same handle, as ( and )
a⋗ba \gtrdot baa takes precedence over bb: the handle ends at aa (reduce)

Relations are read between the topmost terminal on the stack (ignoring nonterminals) and the next input token. From precedence and associativity: if θ1\theta_1 has higher precedence than θ2\theta_2, then θ1⋗θ2\theta_1 \gtrdot \theta_2 and θ2⋖θ1\theta_2 \lessdot \theta_1; for equal precedence, left-associative operators give θ1⋗θ2\theta_1 \gtrdot \theta_2 and θ2⋗θ1\theta_2 \gtrdot \theta_1, right-associative ones give ⋖\lessdot both ways; every operator ⋖\lessdot id and id ⋗\gtrdot every operator; $⋖\text{\textdollar} \lessdot everything and everything ⋗$\gtrdot \text{\textdollar}.

For + and * (both left-associative, * higher), with rows the stack terminal and columns the input token:

id+*$
id⋗\gtrdot⋗\gtrdot⋗\gtrdot
+⋖\lessdot⋗\gtrdot⋖\lessdot⋗\gtrdot
*⋖\lessdot⋗\gtrdot⋗\gtrdot⋗\gtrdot
$⋖\lessdot⋖\lessdot⋖\lessdotaccept

The parse of id + id * id with this table (each reduced handle becomes an unnamed nonterminal N):

StackInputRelationAction
$id + id * id $$⋖id\text{\textdollar} \lessdot \textbf{id}shift
$ id+ id * id $id⋗+\textbf{id} \gtrdot +reduce id
$ N+ id * id $$⋖+\text{\textdollar} \lessdot +shift
$ N +id * id $+⋖id+ \lessdot \textbf{id}shift
$ N + id* id $id⋗∗\textbf{id} \gtrdot *reduce id
$ N + N* id $+⋖∗+ \lessdot *shift
$ N + N *id $∗⋖id* \lessdot \textbf{id}shift
$ N + N * id$id⋗$\textbf{id} \gtrdot \text{\textdollar}reduce id
$ N + N * N$∗⋗$* \gtrdot \text{\textdollar}reduce N * N
$ N + N$+⋗$+ \gtrdot \text{\textdollar}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 T→FT \to F and E→TE \to T, 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 AA 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 ff (stack side) and gg (input side) with a⋖b  ⟹  f(a)<g(b)a \lessdot b \implies f(a) < g(b) and a⋗b  ⟹  f(a)>g(b)a \gtrdot b \implies f(a) > g(b). For the table above one solution is f(id)=4,g(id)=5f(\textbf{id}) = 4, g(\textbf{id}) = 5, f(+)=2,g(+)=1f(+) = 2, g(+) = 1, f(∗)=4,g(∗)=3f(*) = 4, g(*) = 3, f($)=g($)=0f(\text{\textdollar}) = g(\text{\textdollar}) = 0. Each defined equal-precedence relation similarly requires f(a)=g(b)f(a)=g(b); 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:

RulesGroupingValue
Usual: * above + and -, all left-associative((2+(3×4))−5)((2 + (3 \times 4)) - 5)9
All three equal, left-associative(((2+3)×4)−5)(((2 + 3) \times 4) - 5)15
All three equal, right-associative(2+(3×(4−5)))(2 + (3 \times (4 - 5)))−1-1
+ above * above -, all left-associative(((2+3)×4)−5)(((2 + 3) \times 4) - 5)15
- above * above +(2+(3×(4−5)))(2 + (3 \times (4 - 5)))−1-1

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 ε\varepsilon-reduction pushes a nonterminal without popping anything).
  • Left recursion is harmless, even preferable, in bottom-up parsing: E -> E + T reduces 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 * id shows. "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 ε\varepsilon; a helper nonterminal between two nonterminals disqualifies the grammar until it is substituted away.
  • Operator-precedence reduction counts. These skip unit productions: id + id * id takes 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

  1. 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.

Next steps