Theory of Computation & Computer Graphics β Introduction to Context Free Language, NEC licence examination syllabus (Nepal Engineering Council).
Parse Tree and its Construction
How a parser actually builds the tree β top-down by prediction, or bottom-up by reduction.
π Where this lives: The two construction strategies in this topic are the two families of real parser generators. ANTLR, used for thousands of language tools, builds top-down; yacc and bison, which parsed most of the C and Unix world for decades, build bottom-up. When a compiler reports "syntax error near line 47", the position and quality of that message is determined by which of these strategies it uses β top-down parsers know what they expected, bottom-up parsers only know what did not fit. Search "LL versus LR parsing parser generator ANTLR bison".
The two strategies
PARSING IS THE PROBLEM OF FINDING A DERIVATION TREE FOR A GIVEN
STRING, OR REPORTING THAT NONE EXISTS.
Note the direction of travel. The previous topic assumed the
derivation was known and drew the tree; A PARSER IS GIVEN ONLY
THE STRING AND MUST DISCOVER THE TREE. That is a search
problem, and the two strategies are two ways to organise the
search.
ββ TOP-DOWN PARSING ββββββββββββββββββββββββββββββββββββββββ
START AT THE ROOT (the start symbol) AND WORK TOWARD THE
LEAVES, expanding variables until the input is matched.
It constructs a LEFTMOST DERIVATION, in the order the
derivation is written.
RECURSIVE DESCENT is the direct implementation: write ONE
FUNCTION PER VARIABLE, each of which tries the productions for
that variable. It is the parsing method most easily written by
hand, and hand-written recursive descent parsers are used in
several production compilers precisely because they are easy
to read and to give good error messages.
THE FUNDAMENTAL DIFFICULTY: WHICH PRODUCTION TO CHOOSE. On
reaching a variable A with several productions, the parser
must decide which to try. An LL(1) parser decides by looking
at ONE lookahead token.
LL(k) means: scan LEFT to right, produce a LEFTMOST
derivation, using k tokens of lookahead.
TWO OBSTACLES MUST BE REMOVED FIRST:
1. LEFT RECURSION β a production A β AΞ± causes recursive
descent to call itself with no progress, giving infinite
recursion.
THE STANDARD ELIMINATION:
A β AΞ± | Ξ² becomes A β Ξ² Aβ²
Aβ² β Ξ± Aβ² | Ξ΅
Applied to the expression grammar:
E β E + T | T becomes E β T Eβ²
Eβ² β + T Eβ² | Ξ΅
NOTE THE PRICE: THE GRAMMAR IS NO LONGER LEFT-RECURSIVE,
SO THE TREE NO LONGER DIRECTLY EXPRESSES LEFT
ASSOCIATIVITY. The parser must reassemble associativity
itself, which is a real cost of the top-down approach.
2. COMMON PREFIXES β A β Ξ±Ξ²β | Ξ±Ξ²β leaves the parser unable
to choose after seeing only Ξ±.
LEFT FACTORING fixes it:
A β Ξ±Ξ²β | Ξ±Ξ²β becomes A β Ξ± Aβ²
Aβ² β Ξ²β | Ξ²β
ββ BOTTOM-UP PARSING βββββββββββββββββββββββββββββββββββββββ
START AT THE LEAVES (the input tokens) AND WORK TOWARD THE
ROOT, replacing right-hand sides by their left-hand
variables.
It constructs a RIGHTMOST DERIVATION IN REVERSE, which is the
detail that surprises students and is frequently examined.
SHIFT-REDUCE PARSING is the mechanism, using a STACK:
SHIFT β push the next input token onto the stack
REDUCE β when the top of the stack matches the right-hand
side of a production, pop it and push the
left-hand variable
ACCEPT β the stack holds only S and the input is
exhausted
ERROR β no action applies
THE HANDLE is the substring that should be reduced next β the
right-hand side whose reduction is a step of the rightmost
derivation in reverse. FINDING THE HANDLE IS THE ENTIRE
PROBLEM, and an LR parser uses a table built from the grammar
to decide.
LR(k) means: scan LEFT to right, produce a RIGHTMOST
derivation (reversed), with k lookahead tokens. The family in
increasing power: LR(0) β SLR(1) β LALR(1) β LR(1).
LALR(1) IS WHAT yacc AND bison GENERATE β a deliberate
compromise, nearly as powerful as LR(1) with far smaller
tables.
ββ A WORKED SHIFT-REDUCE PARSE βββββββββββββββββββββββββββββ
Grammar: E β E + T | T, T β T * F | F, F β id
Input: id + id * id
STACK INPUT ACTION
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
$ id + id * id $ shift
$ id + id * id $ reduce F β id
$ F + id * id $ reduce T β F
$ T + id * id $ reduce E β T
$ E + id * id $ shift
$ E + id * id $ shift
$ E + id * id $ reduce F β id
$ E + F * id $ reduce T β F
$ E + T * id $ shift
$ E + T * id $ shift
$ E + T * id $ reduce F β id
$ E + T * F $ reduce T β T * F
$ E + T $ reduce E β E + T
$ E $ ACCEPT
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
NOTICE THE DECISION AT "$ E + T" WITH "* id" REMAINING: the
parser could have reduced E β E + T immediately, but doing so
would have made the multiplication apply to the whole sum.
IT SHIFTS INSTEAD, because the lookahead * binds tighter.
THIS IS A SHIFT-REDUCE CONFLICT RESOLVED BY PRECEDENCE, and
it is exactly how yacc handles operator precedence
declarations.
READ THE REDUCTIONS BOTTOM TO TOP AND THEY ARE THE RIGHTMOST
DERIVATION β which is what "rightmost in reverse" means.
General parsing, and comparison
ββ WHEN THE GRAMMAR IS NOT LL OR LR ββββββββββββββββββββββββ
LL(1) and LR(1) grammars are restricted classes. For an
ARBITRARY context-free grammar, general algorithms exist:
THE CYK ALGORITHM (Cocke-Younger-Kasami)
Requires the grammar in CHOMSKY NORMAL FORM. It fills an
upper-triangular table where entry (i, j) holds every
variable deriving the substring from position i of length j.
FOR j = 1: X β table[i,1] if X β wα΅’ is a production
FOR j > 1: X β table[i,j] if there is a production
X β YZ and a split point k with
Y β table[i,k] and Z β table[i+k, jβk]
ACCEPT if the start symbol appears in table[1, n].
RUNS IN O(nΒ³ Β· |G|) TIME AND O(nΒ²) SPACE β cubic, so it is
not used for programming languages, but it WORKS FOR EVERY
CFG including ambiguous ones, and it is a DYNAMIC
PROGRAMMING algorithm of the same family as the ones in the
algorithms subject.
EARLEY'S ALGORITHM
Handles any CFG in O(nΒ³), improving to O(nΒ²) for
unambiguous grammars and O(n) for LR-parsable ones.
NO NORMAL FORM REQUIRED, which makes it popular in natural
language processing where grammars are large and messy.
ββ THE COMPARISON ββββββββββββββββββββββββββββββββββββββββββ
TOP-DOWN (LL) BOTTOM-UP (LR)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
direction root β leaves leaves β root
derivation LEFTMOST RIGHTMOST, REVERSED
basic action PREDICT which REDUCE when a handle
production applies is complete
needs NO left recursion, handles left recursion
left factoring NATURALLY
grammar class SMALLER LARGER β every LL(1)
grammar is LR(1)
hand-writing EASY (recursive impractical
descent)
error messages BETTER β the parser weaker: it knows only
knows what it that nothing fits
EXPECTED
typical tool ANTLR, hand-written yacc, bison (LALR)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
THE ENGINEERING SUMMARY: LR PARSERS ACCEPT MORE GRAMMARS AND
NEED A GENERATOR; LL PARSERS ACCEPT FEWER GRAMMARS AND CAN BE
WRITTEN AND DEBUGGED BY HAND. Several major compilers β GCC
among them β abandoned generated bottom-up parsers in favour
of hand-written recursive descent, precisely for the error
message and maintainability advantages in the last two rows.
ββ ERROR HANDLING ββββββββββββββββββββββββββββββββββββββββββ
A parser that stops at the first error is nearly useless, so
real parsers RECOVER and continue:
PANIC MODE β discard tokens until a SYNCHRONISING token
(typically ; or }) is found, then resume. Simple and
robust; may skip real errors.
PHRASE-LEVEL RECOVERY β perform a local correction, such as
inserting a missing semicolon.
ERROR PRODUCTIONS β add productions to the grammar for
common mistakes, so the parser recognises the error
explicitly and reports it precisely.
GLOBAL CORRECTION β find the minimal edit making the input
parseable. Theoretically clean, too expensive in practice.
THE PRACTICAL GOAL IS TO REPORT EVERY REAL ERROR ONCE AND
INVENT NONE β and CASCADING ERRORS, where one mistake produces
twenty messages, is the failure mode recovery exists to
prevent.
Bottom-up parsing builds a rightmost derivation in reverse β read the reductions in a shift-reduce trace from the bottom upward and they are exactly the rightmost derivation. That inversion is the detail most often missed, and it is why LR parsers can handle left recursion that defeats LL parsers.
π Go further: GCC and Clang both parse C++ with hand-written recursive descent, having abandoned generated bottom-up parsers. The reason is the last two rows of the comparison table. A top-down parser knows what it was expecting when it fails, so it can say "expected ';' after declaration"; a bottom-up parser only knows that no table entry matched, so it says "syntax error". For a language whose users are humans writing thousands of lines a day, the quality of the error message outweighs the theoretical advantage of accepting a larger grammar class β a rare case where the weaker formalism won on engineering grounds. Search "GCC recursive descent parser replaced bison error messages".
π‘ Exam angle: distinguish top-down (root to leaves, leftmost derivation, predictive) from bottom-up (leaves to root, rightmost derivation reversed, shift-reduce). Know the meaning of LL(k) and LR(k) letter by letter. Be able to perform left recursion elimination (A β AΞ± | Ξ² becomes A β Ξ²Aβ², Aβ² β Ξ±Aβ² | Ξ΅) and left factoring. The likely long question is a shift-reduce parse trace with stack, input and action columns β practise it on the expression grammar and be able to identify the handle. Know that CYK needs CNF and runs in O(nΒ³), and be ready to compare the two strategies.
Syllabus points
Parse tree, root, yield
Constructing parse trees
Create a free account to tick topics off, take notes as you read, watch the video lessons and get a day-by-day study plan built around your exam date.
Related topics in Introduction to Context Free Language