Algorithm Search⌘K

Proofs: Shunting-yard algorithm

These are the human proofs of Shunting-yard algorithm, at full length.

The shunting-yard algorithm outputs the postfix form of the precedence parse tree full

This proof shows: correctness, termination.

We use the pseudocode and the relation o⊐to \sqsupset t of the page. The precedence levels are 1,…,m1, \ldots, m, and all operators of one level have the same associativity. A primary is an operand or a group ( e )(\,e\,) where ee is an expression. A flat sequence is σ=x0 o1 x1 o2⋯ok xk\sigma = x_0\, o_1\, x_1\, o_2 \cdots o_k\, x_k with k≥0k \ge 0, primaries xix_i and binary operators oio_i. Write P(⋅)P(\cdot) for the postfix form of a tree, and P(x)P(x) for the postfix form of the tree of a primary xx (the operand itself, or PP of the tree of ee for x=( e )x = (\,e\,)).

The tree of a flat sequence

Definition. The tree τ(σ)\tau(\sigma) is defined by induction on kk. If k=0k = 0, τ(σ)\tau(\sigma) is the tree of x0x_0. If k≥1k \ge 1, let pp be the least precedence among o1,…,oko_1, \ldots, o_k. The root is oro_r, where rr is the largest index with prec(or)=p\text{prec}(o_r) = p if level pp is left associative, and the smallest such index if it is right associative. The left subtree is τ(x0⋯xr−1)\tau(x_0 \cdots x_{r-1}) and the right subtree is τ(xr⋯xk)\tau(x_r \cdots x_k).

This is the parse tree of the precedence grammar, whose level pp is Ep→Ep o Ep+1∣Ep+1E_p \to E_p\, o\, E_{p+1} \mid E_{p+1} when it is left associative and Ep→Ep+1 o Ep∣Ep+1E_p \to E_{p+1}\, o\, E_p \mid E_{p+1} when it is right associative, with Em+1→( E1 )∣idE_{m+1} \to (\,E_1\,) \mid \text{id}. A string derived from EqE_{q} contains, outside parentheses, only operators of level at least qq. So in a derivation from EpE_p of a flat sequence whose least level is pp, the topmost production of level pp splits at the last operator of level pp (left associative: its right part comes from Ep+1E_{p+1} and has no operator of level pp) or at the first one (right associative: its left part comes from Ep+1E_{p+1}). By induction on kk this split is forced at every node, so the parse tree exists, is unique, and equals τ(σ)\tau(\sigma).

Right spine. The right spine of a tree is its root, the root of its right subtree, and so on, down to the last leaf. In τ(σ)\tau(\sigma) the spine operators are os1,…,osho_{s_1}, \ldots, o_{s_h} from the root down, with s1<⋯<shs_1 < \cdots < s_h, and the last primary is xkx_k. Let λi\lambda_i be the left subtree of osio_{s_i}. Applying the definition of postfix form hh times gives

P(τ(σ))=P(λ1) P(λ2)⋯P(λh) P(xk)  osh osh−1⋯os1.(1)P(\tau(\sigma)) = P(\lambda_1)\, P(\lambda_2) \cdots P(\lambda_h)\, P(x_k)\; o_{s_h}\, o_{s_{h-1}} \cdots o_{s_1}. \tag{1}

The same formula holds for the subtree rooted at any spine node osio_{s_i}, with λi,…,λh\lambda_i, \ldots, \lambda_h.

Lemma 1 (spine order). For consecutive spine operators a=osia = o_{s_i} and b=osi+1b = o_{s_{i+1}}, not a⊐ba \sqsupset b.

Proof. Let aa be the root of the subsequence ρ\rho whose tree is rooted at aa, with least level p=prec(a)p = \text{prec}(a), and let bb be the root of the right part of ρ\rho. If level pp is left associative, aa is the last operator of level pp in ρ\rho, so every operator of the right part, bb among them, has prec(b)>p\text{prec}(b) > p; then not a⊐ba \sqsupset b. If level pp is right associative, prec(b)≥p\text{prec}(b) \ge p; if prec(b)>p\text{prec}(b) > p we are done, and if prec(b)=p\text{prec}(b) = p then bb is right associative, so again not a⊐ba \sqsupset b. □\square

Lemma 2 (suffix). Let tt be an operator. If osi⊐to_{s_i} \sqsupset t, then osi′⊐to_{s_{i'}} \sqsupset t for every i′>ii' > i.

Proof. It suffices to take i′=i+1i' = i + 1 and then repeat. Write a=osia = o_{s_i} and b=osi+1b = o_{s_{i+1}}. By Lemma 1, prec(b)>prec(a)\text{prec}(b) > \text{prec}(a), or prec(b)=prec(a)\text{prec}(b) = \text{prec}(a) and the level of aa is right associative. If prec(a)>prec(t)\text{prec}(a) > \text{prec}(t), then prec(b)>prec(t)\text{prec}(b) > \text{prec}(t) and b⊐tb \sqsupset t. Otherwise prec(a)=prec(t)\text{prec}(a) = \text{prec}(t) and tt is left associative, so the level of aa, which is the level of tt, is left associative; then the second case of Lemma 1 is impossible, so prec(b)>prec(t)\text{prec}(b) > \text{prec}(t) and b⊐tb \sqsupset t. □\square

So the spine operators that pop tt are exactly osg,…,osho_{s_g}, \ldots, o_{s_h} for some gg with 1≤g≤h+11 \le g \le h + 1 (the set is empty when g=h+1g = h + 1). Define the graft of tt and a primary yy onto τ(σ)\tau(\sigma): take the spine node osgo_{s_g}, or the last leaf xkx_k when g=h+1g = h + 1, and replace the subtree θ\theta rooted there by the node t(θ,y)t(\theta, y).

Lemma 3 (graft). $\tau(\sigma, t, y) = $ the graft of tt and yy onto τ(σ)\tau(\sigma).

Proof. Induction on kk. If k=0k = 0, then τ(x0 t y)=t(x0,y)\tau(x_0\, t\, y) = t(x_0, y), the spine has no operator, g=1=h+1g = 1 = h + 1, and the graft at the leaf x0x_0 gives the same tree. Let k≥1k \ge 1, let pp be the least level in σ\sigma, oro_r the root of τ(σ)\tau(\sigma), σ=L or R\sigma = L\, o_r\, R. Four cases.

  1. prec(t)<p\text{prec}(t) < p. In σ t y\sigma\, t\, y the operator tt is the only one of least level, so the root is tt with the subtrees τ(σ)\tau(\sigma) and yy. Every spine operator has level at least p>prec(t)p > \text{prec}(t), so it pops tt; then g=1g = 1, and the graft at the root gives the same tree.
  2. prec(t)=p\text{prec}(t) = p and level pp is left associative. The root of σ t y\sigma\, t\, y is the last operator of level pp, which is tt, with left part σ\sigma. The root oro_r pops tt (equal precedence, tt left associative), so g=1g = 1 by Lemma 2, and the graft gives the same tree.
  3. prec(t)=p\text{prec}(t) = p and level pp is right associative. The root of σ t y\sigma\, t\, y is the first operator of level pp, which is oro_r, with left part LL and right part R t yR\, t\, y. The root oro_r does not pop tt. So the graft point is on the spine of τ(R)\tau(R), and it is the same node as the graft point of tt in τ(R)\tau(R), because the spine of τ(σ)\tau(\sigma) is oro_r followed by the spine of τ(R)\tau(R). By the induction hypothesis for RR, which has fewer operators, τ(R t y)\tau(R\, t\, y) is the graft onto τ(R)\tau(R), and so τ(σ t y)\tau(\sigma\, t\, y) is the graft onto τ(σ)\tau(\sigma).
  4. prec(t)>p\text{prec}(t) > p. The least level of σ t y\sigma\, t\, y is still pp, and tt is not of level pp, so the root is still oro_r (the last or the first operator of level pp does not change), with left part LL and right part R t yR\, t\, y. The root does not pop tt, since prec(or)<prec(t)\text{prec}(o_r) < \text{prec}(t). The rest is as in case 3. □\square

The invariant

Fix a flat sequence σ=x0 o1⋯ok xk\sigma = x_0\, o_1 \cdots o_k\, x_k that the algorithm reads with a stack whose top part, before x0x_0, is a base: either the bottom of the stack or a (. We assume the claim of the theorem below for every group ( e )(\,e\,) among the xix_i (induction on the nesting depth, see the end), so reading a primary xx appends P(x)P(x) to the output and leaves the stack as it was.

Invariant (S). After the algorithm reads x0 o1⋯oj xjx_0\, o_1 \cdots o_j\, x_j, the stack above the base holds exactly the spine operators os1,…,osho_{s_1}, \ldots, o_{s_h} of τ(x0⋯xj)\tau(x_0 \cdots x_j), with os1o_{s_1} at the bottom and osho_{s_h} on top, and the output written since the base is P(λ1)⋯P(λh) P(xj)P(\lambda_1) \cdots P(\lambda_h)\, P(x_j).

Initialization. After x0x_0, the stack above the base is empty, the tree is the tree of x0x_0 with no spine operator, and the output is P(x0)P(x_0).

Maintenance. Let (S) hold after xjx_j, and let t=oj+1t = o_{j+1} and y=xj+1y = x_{j+1}. The loop of lines L9 and L10 pops operators from the top while the top is an operator that pops tt; it stops at the base, since a ( is not an operator and the bottom ends the loop. By Lemma 2 the operators popped are exactly osh,osh−1,…,osgo_{s_h}, o_{s_{h-1}}, \ldots, o_{s_g}, in this order. The output becomes

P(λ1)⋯P(λg−1)  [P(λg)⋯P(λh) P(xj) osh⋯osg],P(\lambda_1) \cdots P(\lambda_{g-1})\; \bigl[P(\lambda_g) \cdots P(\lambda_h)\, P(x_j)\, o_{s_h} \cdots o_{s_g}\bigr],

and by (1) applied to the subtree θ\theta at osgo_{s_g}, the bracket is P(θ)P(\theta); if g=h+1g = h + 1 the bracket is P(xj)P(x_j) and θ\theta is the leaf xjx_j. Line L11 pushes tt, and reading yy appends P(y)P(y). By Lemma 3, the new tree is the graft: its spine is os1,…,osg−1,to_{s_1}, \ldots, o_{s_{g-1}}, t, the left subtree of tt is θ\theta, and the last primary is yy. So the stack is the new spine and the output is P(λ1)⋯P(λg−1) P(θ) P(y)P(\lambda_1) \cdots P(\lambda_{g-1})\, P(\theta)\, P(y), which is (S) for j+1j + 1.

End of the sequence. A flat sequence ends at a ) (line L6) or at the end of the input (line L12). Both pop all operators above the base, from the top: line L6 stops at the (, and line L14 empties the stack. They append osh⋯os1o_{s_h} \cdots o_{s_1}, so by (1) the output written since the base is P(τ(σ))P(\tau(\sigma)). Line L7 then removes the (, so the stack is as it was before the group.

The theorem

Theorem. If the input is an expression ee of the precedence grammar, SHUNTING-YARD returns PP of the parse tree of ee.

Proof. By induction on the nesting depth of parentheses. An expression is a flat sequence of primaries, and the groups among them have smaller depth, so by the induction hypothesis reading a group ( e′ )(\,e'\,) pushes (, appends PP of the tree of e′e' (by the argument above with the ( as base), and pops the (, which is the assumption used for (S). The whole input is a flat sequence with the bottom of the stack as base, so the output is P(τ(e))P(\tau(e)), which is PP of the parse tree by the definition section. Line L13 never fires, because each ( was removed by its ). □\square

Rejection. The implementation keeps a flag that is true exactly when the next token must start a primary. It accepts a token only if the token sequence so far is a prefix of the form x0 o1 x1⋯x_0\, o_1\, x_1 \cdots with matched parentheses at each ), and at the end it requires the flag to be false and no ( on the stack. Every expression of the grammar passes these checks, and every token sequence that passes them is a flat sequence of primaries with matched parentheses, which is an expression of the grammar. So the implementation raises an error exactly on the inputs that are not expressions.

Termination. Each token is read once. Each loop of lines L6, L9 and L12 pops one token at each run, and each token is pushed at most once, so the loops run at most nn times in total. ■\blacksquare

Back to Shunting-yard algorithm