By the end of this chapter you'll be able to…

  • 1Name the compiler phases and which errors each detects
  • 2Explain why the lexer uses a regular language and the parser a context-free one
  • 3Distinguish token, lexeme and pattern
  • 4Apply the longest-match and pattern-order disambiguation rules
  • 5Explain why left recursion breaks top-down parsing and how to remove it
  • 6Left factor a grammar with a common prefix
  • 7State why grammar transformations change the grammar and not the language
  • 8Compute FIRST for any string of grammar symbols
  • 9Compute FOLLOW including the nullable-propagation rule
  • 10Build an LL(1) parsing table and detect conflicts
  • 11Explain why ambiguous and left-recursive grammars are never LL(1)
  • 12Describe shift-reduce parsing and the notion of a handle
  • 13Construct LR(0) items using closure and goto
  • 14Distinguish shift-reduce from reduce-reduce conflicts
  • 15Order the LR family by power and table size
  • 16Explain why SLR's use of global FOLLOW weakens it
  • 17Explain LALR merging and its effect on each conflict type
  • 18State the relationship between LL(1) and LR(1) grammars
  • 19Distinguish synthesised from inherited attributes
  • 20Identify S-attributed and L-attributed definitions
  • 21Explain why S-attributed definitions suit bottom-up parsing
💡
Why this chapter matters in GATE
The compiler front end turns characters into structure in stages, because each stage produces something the next can consume more cheaply. The lexer uses a regular language and the parser a context-free one, and that split is deliberate: recognising an identifier needs no nesting so a finite automaton suffices at linear cost, while recognising nested expressions needs a stack and is more expensive, so the easy part is handled first. Every parsing method is characterised by two choices — how much lookahead it consults, and whether it builds the tree from the root down or the leaves up. The grammar classes are not properties of languages but exactly the sets of grammars each method handles without conflict, so a language can have both an LL(1) grammar and a non-LL(1) one. The diagnostic question is therefore what the method must decide and whether the grammar supplies enough information at that point; a conflict means it does not.

Before you start — revise these

🔗
Regular Expressions & Finite Automata
The lexer is built by converting regular expressions to a minimised DFA, which is exactly the pipeline from that chapter.
🔗
Context-Free Grammars & Pushdown Automata
Grammars, derivations, parse trees and ambiguity are developed there, and a shift-reduce parser is a deterministic pushdown automaton.
🔗
Arrays, Stacks, Queues & Linked Lists
Shift-reduce parsing is stack manipulation, and the stack discipline is what makes handle recognition possible.

Lexical Analysis, Parsing & Syntax-Directed Translation

The compiler front end turns a stream of characters into a structured representation, and it does so in stages because each stage produces something the next can consume more cheaply.

The lexer uses a regular language and the parser uses a context-free one, and that split is not arbitrary. Recognising an identifier needs no nesting, so a finite automaton suffices and runs in linear time with a tiny constant. Recognising nested expressions needs a stack, which is more expensive, so it is worth handling the easy part first.

Every parsing method is characterised by two choices: how much lookahead it consults, and whether it builds the parse tree from the root down or from the leaves up.

The grammar classes — LL(1), SLR(1), LALR(1), CLR(1) — are not properties of languages. They are exactly the sets of grammars each method can handle without conflict. A language may have an LL(1) grammar and a non-LL(1) grammar simultaneously, and questions exploit that constantly.

So the diagnostic question is: what does this method need to decide, and does the grammar give it enough information at that point? A conflict means the answer is no.

1. The Front End Pipeline

Six phases are named, and the first four are the front end.

PhaseInputOutputErrors detected
Lexical analysisCharactersTokensIllegal characters
Syntax analysisTokensParse treeMalformed structure
Semantic analysisParse treeAnnotated treeType errors, undeclared names
Intermediate code generationAnnotated treeThree-address code
OptimisationIntermediate codeBetter code
Code generationIntermediate codeTarget code

Which phase catches which error is examined directly. A missing semicolon is syntax. A misspelled keyword is usually lexical only if it produces an illegal character; otherwise it becomes an identifier token and fails at syntax or semantics. Adding an integer to a string is semantic, because the grammar permits the shape and only the types forbid it.

The symbol table spans all phases rather than belonging to one. The lexer creates entries, the parser and semantic analyser annotate them with types and scopes, and code generation reads addresses from them.

2. Lexical Analysis

The lexer groups characters into tokens. A token is a category, a lexeme is the actual text, and a pattern is the rule describing the category.

For the input count, the token is identifier, the lexeme is count, and the pattern is the regular expression for identifiers.

Token patterns are regular languages, so the lexer is built by converting regular expressions to an NFA, then to a DFA, then minimising. That pipeline is exactly the automata theory chapter applied.

Two rules resolve ambiguity when several patterns match.

Longest match wins: given elsewhere, the lexer produces one identifier rather than the keyword else followed by where.

Among equal-length matches, the earlier-listed pattern wins, which is why keyword patterns are listed before the identifier pattern. Otherwise every keyword would be lexed as an identifier.

Lexical analysis cannot detect most errors, because almost any character sequence forms some valid token. Its errors are confined to characters that begin no legal token at all.

3. Top-Down Parsing

A top-down parser builds the parse tree from the root, expanding a variable at each step and matching terminals as it goes. It produces a leftmost derivation.

Two grammar features make top-down parsing impossible and must be removed first.

Left recursion causes infinite descent: a rule expands into something beginning with , forever. It is removed by rewriting as with , which converts left recursion into right recursion.

Common prefixes prevent a decision with one lookahead symbol. Rules and look identical until has been consumed. Left factoring rewrites them as with , deferring the choice until the distinguishing symbol is visible.

Neither transformation changes the language, only the grammar, which is the practical face of grammar classes being about grammars rather than languages.

4. FIRST, FOLLOW and LL(1)

The parsing table is built from two sets.

FIRST of a string is the set of terminals that can begin a string derived from it, plus if it can derive the empty string.

FOLLOW of a variable is the set of terminals that can appear immediately after it in some sentential form, plus the end marker if it can appear at the end.

Computing them follows fixed rules. For FIRST, take the first symbol; if it is a terminal, that is the answer, and if it is a variable that can vanish, continue to the next symbol.

For FOLLOW, whatever follows a variable in a production body contributes, and if the remainder can vanish, the FOLLOW of the left-hand side contributes too.

The FOLLOW of the start symbol always contains the end marker.

The parsing table places production in the cell for row and each terminal in FIRST(). If can derive , the production also goes in every cell for FOLLOW(), since seeing something that can follow means should vanish.

A grammar is LL(1) exactly when no cell holds two productions. Two productions in one cell means the parser cannot choose with one symbol of lookahead.

Left recursion always causes a conflict, and so does an unfactored common prefix, which is why both must be removed. An ambiguous grammar is never LL(1), since ambiguity guarantees a cell collision.

5. Bottom-Up Parsing

A bottom-up parser builds the tree from the leaves, repeatedly recognising a right-hand side and replacing it with the left-hand side. It produces a rightmost derivation in reverse.

The mechanism is shift-reduce. Shift pushes the next input symbol onto a stack; reduce pops a right-hand side and pushes the corresponding variable. The parser accepts when the stack holds only the start symbol and the input is exhausted.

A handle is the substring that should be reduced next, and the entire difficulty of bottom-up parsing is identifying handles. The LR family solves it by tracking, in the parser state, which productions are partially matched.

An LR(0) item is a production with a dot marking how much has been seen. The item means is on the stack and is still expected.

The canonical collection of item sets is built by closure and goto. Closure adds, for a dot before a variable, every production of that variable with the dot at the start. Goto moves the dot past a symbol and takes the closure again.

Two conflicts can arise.

A shift-reduce conflict occurs when a state contains both a completed item, suggesting reduce, and an item with the dot before a terminal, suggesting shift.

A reduce-reduce conflict occurs when a state contains two different completed items.

6. The LR Family

Four methods differ in how much information they use to decide when to reduce.

MethodReduce decisionTable sizePower
LR(0)Reduce on every inputSmallestWeakest
SLR(1)Reduce only on FOLLOWSmallStronger
LALR(1)Reduce on merged lookaheadsSmallStronger still
CLR(1)Reduce on exact lookaheadsLargestStrongest

The containment is strict at every step: every LR(0) grammar is SLR(1), every SLR(1) grammar is LALR(1), and every LALR(1) grammar is CLR(1).

SLR uses FOLLOW, which is a global approximation. It asks what could follow the variable anywhere in the grammar, which is sometimes more than what could follow it in this particular state. That over-approximation is what makes SLR weaker than it might be.

CLR carries an exact lookahead with each item, computed for the specific context, so it never reduces when it should not. The cost is many more states, since items differing only in lookahead are kept separate.

LALR merges CLR states having the same core — the same items ignoring lookaheads — and unions their lookahead sets. The table shrinks to LR(0) size while retaining most of CLR's power.

Merging can introduce a reduce-reduce conflict but never a shift-reduce conflict, and that asymmetry is examined constantly. A shift decision depends only on the core, which merging preserves; a reduce decision depends on the lookahead sets, which merging enlarges and can therefore make overlap.

Every LL(1) grammar is LR(1), but not conversely. Bottom-up parsing sees the whole right-hand side before deciding, while top-down must commit at the start, so bottom-up is strictly more informed.

7. Syntax-Directed Translation

A syntax-directed definition attaches attributes to grammar symbols and rules for computing them.

A synthesised attribute is computed from the children, flowing up the tree. An inherited attribute is computed from the parent or siblings, flowing down or across.

An S-attributed definition uses only synthesised attributes. It can always be evaluated bottom-up during a shift-reduce parse, computing each value at the moment of reduction, which makes it the natural fit for LR parsing.

An L-attributed definition allows inherited attributes, but each may depend only on the parent and on siblings to its left. That restriction guarantees a single left-to-right depth-first pass suffices.

Every S-attributed definition is L-attributed, since having no inherited attributes satisfies the restriction vacuously.

The classic use of an inherited attribute is propagating a declared type down a list of identifiers, where the type appears once at the left and must reach every name to its right.

A definition that is not L-attributed may need multiple passes or an explicit dependency graph, and if the graph has a cycle no evaluation order exists at all.

8. Worked Examples

Example 1. Compute FIRST and FOLLOW for the grammar , , .

Start with FIRST.

FIRST() is , directly from its two productions.

FIRST() is , likewise.

FIRST() begins with FIRST() minus , giving . Since can vanish, continue to , adding . Since can also vanish, continue to , adding . As the whole body cannot vanish — is a terminal — is not included.

FIRST() = .

Now FOLLOW.

FOLLOW() = \{\}$, the end marker, as always for the start symbol.

For FOLLOW(): in , what follows is . FIRST() is plus, since can vanish, .

FOLLOW() = .

For FOLLOW(): in , what follows is .

FOLLOW() = .

Note that FOLLOW() picked up only because is nullable. Had been unable to vanish, would not appear there, and this nullable-propagation step is the one most often skipped.

Example 2. Remove left recursion and left factor: and .

For the left recursion in , identify the recursive alternative and the non-recursive one .

The standard transformation gives and .

The new grammar generates the same strings, but the recursion is now on the right, so a top-down parser descends into only after consuming a , and terminates.

Note that the transformation loses left associativity in the grammar's structure, which is why a translation scheme must reintroduce it through attributes rather than relying on the tree shape.

For the common prefix in , the alternatives and share the prefix .

Factor it out: with .

Now a parser seeing commits to the first alternative and defers the -versus- decision until the next symbol, which is exactly one lookahead away.

Both transformations change the grammar and not the language, which is why a language can be LL(1) even when a particular grammar for it is not.

Example 3. Is the grammar , LL(1)?

This is the dangling-else grammar, with for "if", for "then", for "else".

Two productions for begin with , so there is an unfactored common prefix. That alone means the grammar is not LL(1), since the parser cannot choose between them on seeing .

Left factor: with .

Now check the table cell for and the terminal .

goes there, since FIRST() contains .

goes into every cell for FOLLOW(), and FOLLOW() contains — because in , the before can itself be an if-then-else whose follows.

Both productions land in the cell for , so the grammar is still not LL(1) even after factoring.

The conflict is genuine ambiguity: an else can attach to either of two open ifs. Real parsers resolve it by a rule rather than by the grammar, preferring to shift, which attaches the else to the nearest if.

Example 4. Explain why LALR merging can create a reduce-reduce conflict but never a shift-reduce conflict.

LALR builds the CLR item sets and then merges any two states whose cores are identical, where the core is the set of items ignoring their lookahead components. The merged state's lookahead set for each item is the union of the originals.

Consider a shift-reduce conflict. A shift action arises from an item with the dot before a terminal, which is part of the core. A reduce action arises from a completed item with a lookahead.

If the merged state has a shift on terminal , then the core contained an item with the dot before , so both original states had that same shift.

For a conflict, some completed item must have in its merged lookahead set, meaning was in one of the originals. But that original state had both the shift on and the reduce on — so it already had the conflict before merging.

Merging therefore cannot create a shift-reduce conflict; it can only expose one that CLR already had.

Now consider reduce-reduce. Suppose state 1 has completed items with lookahead and with lookahead , and state 2 has the same items with lookaheads and respectively.

Neither state conflicts, since in each the two lookahead sets are disjoint.

After merging, the first item has lookahead and so does the second. Now both reduce on and both reduce on — a genuine new conflict created by the merge.

That is why a grammar can be CLR(1) and not LALR(1), and why the containment is strict.

Example 5. Classify these attributes as synthesised or inherited, and state whether the definition is S-attributed or L-attributed: computing the value of an arithmetic expression; propagating a declared type to a list of identifiers.

Expression evaluation is synthesised. The value of is computed as the sum of the values of and , both children. Information flows strictly upward from leaves to root.

The definition is S-attributed, using only synthesised attributes, and can therefore be evaluated during a bottom-up parse by computing each value at the moment its production is reduced. No separate tree traversal is needed.

Type propagation is inherited. In a declaration such as int a, b, c, the type appears once, at the far left, and must reach every identifier to its right.

With a grammar and , the attribute L.type is set from T.type by the parent, then passed down to L1.type. Information flows downward and leftward-to-rightward, which is the definition of inherited.

The definition is L-attributed, because L.type depends only on the parent's attribute and on siblings to its left — namely , which precedes in the body.

It is not S-attributed, so it cannot be evaluated by a pure bottom-up pass without extra machinery. It can be evaluated in one left-to-right depth-first traversal, which is what L-attributed guarantees.

Example 6. A grammar has 3 shift-reduce conflicts under SLR(1) but none under LALR(1). What does this tell you?

SLR decides to reduce a production whenever the lookahead is in FOLLOW(), where FOLLOW is computed globally over the entire grammar.

That is an over-approximation. FOLLOW() collects every terminal that can follow anywhere in any derivation, but in a particular parser state, only some of those are actually possible.

If a terminal is in FOLLOW() globally but cannot follow in the context this state represents, SLR still schedules a reduce on . If the state also shifts on , a spurious conflict appears.

LALR carries lookaheads computed per item set rather than per variable, so its lookahead for that reduce action is a subset of FOLLOW() — precisely the terminals that can follow in this context.

If is excluded, the conflict vanishes, and the grammar parses cleanly.

So the observation tells us the grammar is LALR(1) but not SLR(1), and that all three conflicts were artefacts of FOLLOW's global approximation rather than genuine ambiguities in the grammar.

This is the usual reason SLR fails, and it is why practical parser generators use LALR: it achieves nearly CLR's power at LR(0)'s table size.

Summary

The front end splits into a regular part and a context-free part because recognising tokens needs no nesting and recognising structure does.

A token is a category, a lexeme is the text, and a pattern is the rule. Longest match wins, and among ties the earlier-listed pattern wins, which is why keywords precede identifiers.

Top-down parsing produces a leftmost derivation and requires left recursion removed and common prefixes factored. Neither transformation changes the language.

FIRST collects the terminals that can begin a string; FOLLOW collects those that can follow a variable, and the start symbol's FOLLOW always contains the end marker. A nullable symbol propagates the next symbol's FIRST into the earlier one's FOLLOW.

A grammar is LL(1) exactly when no parsing-table cell holds two productions. Ambiguous grammars and left-recursive grammars never are.

Bottom-up parsing produces a rightmost derivation in reverse by shifting and reducing, and the difficulty is identifying handles. LR items with a dot track partial matches.

LR(0) is contained in SLR(1), contained in LALR(1), contained in CLR(1), all strictly. SLR uses global FOLLOW and is weakened by that approximation; CLR uses exact per-state lookaheads and pays in table size; LALR merges same-core states to get most of CLR's power at LR(0)'s size.

Merging can create a reduce-reduce conflict but never a shift-reduce conflict, because shift actions depend only on the core.

Every LL(1) grammar is LR(1) and not conversely, since bottom-up sees the whole right-hand side before committing.

S-attributed definitions use only synthesised attributes and evaluate during a bottom-up parse. L-attributed definitions allow inherited attributes depending only on the parent and left siblings, and evaluate in one left-to-right pass.

Key formulas & results

Everything to memorise for the exam hall, in one card. Screenshot this for revision.

The organising tool
EVERY PARSING METHOD IS DEFINED BY HOW MUCH LOOKAHEAD IT NEEDS AND WHICH DIRECTION IT BUILDS THE TREE.
GRAMMAR CLASSES ARE PROPERTIES OF GRAMMARS, NOT LANGUAGES. A LANGUAGE CAN HAVE BOTH AN LL(1) GRAMMAR AND A NON-LL(1) ONE.
The phase split
LEXICAL ANALYSIS USES A REGULAR LANGUAGE; SYNTAX ANALYSIS USES A CONTEXT-FREE ONE.
RECOGNISING TOKENS NEEDS NO NESTING SO A FINITE AUTOMATON SUFFICES CHEAPLY; RECOGNISING STRUCTURE NEEDS A STACK, SO THE EASY PART IS HANDLED FIRST.
Which phase catches which error
ILLEGAL CHARACTERS ARE LEXICAL. MALFORMED STRUCTURE IS SYNTACTIC. TYPE MISMATCHES AND UNDECLARED NAMES ARE SEMANTIC.
ADDING AN INTEGER TO A STRING IS SEMANTIC BECAUSE THE GRAMMAR PERMITS THE SHAPE AND ONLY THE TYPES FORBID IT.
Token, lexeme, pattern
A TOKEN IS THE CATEGORY, A LEXEME IS THE ACTUAL TEXT, AND A PATTERN IS THE RULE DESCRIBING THE CATEGORY.
FOR THE INPUT count, THE TOKEN IS IDENTIFIER, THE LEXEME IS count, AND THE PATTERN IS THE REGULAR EXPRESSION FOR IDENTIFIERS.
Lexer disambiguation
LONGEST MATCH WINS. AMONG EQUAL-LENGTH MATCHES, THE EARLIER-LISTED PATTERN WINS.
THIS IS WHY KEYWORD PATTERNS ARE LISTED BEFORE THE IDENTIFIER PATTERN, AND WHY elsewhere LEXES AS ONE IDENTIFIER RATHER THAN else FOLLOWED BY where.
Removing left recursion
A GOES TO A ALPHA OR BETA BECOMES A GOES TO BETA A-PRIME, WITH A-PRIME GOES TO ALPHA A-PRIME OR EPSILON.
LEFT RECURSION CAUSES INFINITE DESCENT IN A TOP-DOWN PARSER. THE TRANSFORMATION CONVERTS IT TO RIGHT RECURSION AND LOSES THE LEFT-ASSOCIATIVE TREE SHAPE.
Left factoring
A GOES TO ALPHA BETA OR ALPHA GAMMA BECOMES A GOES TO ALPHA A-PRIME, WITH A-PRIME GOES TO BETA OR GAMMA.
IT DEFERS THE CHOICE UNTIL THE DISTINGUISHING SYMBOL IS VISIBLE. NEITHER TRANSFORMATION CHANGES THE LANGUAGE, ONLY THE GRAMMAR.
FIRST
FIRST OF A STRING IS THE SET OF TERMINALS THAT CAN BEGIN A STRING DERIVED FROM IT, PLUS EPSILON IF IT CAN DERIVE THE EMPTY STRING.
TAKE THE FIRST SYMBOL; IF IT IS A VARIABLE THAT CAN VANISH, CONTINUE TO THE NEXT SYMBOL AND ADD ITS FIRST TOO.
FOLLOW
FOLLOW OF A VARIABLE IS THE SET OF TERMINALS THAT CAN APPEAR IMMEDIATELY AFTER IT IN SOME SENTENTIAL FORM.
WHATEVER FOLLOWS THE VARIABLE IN A PRODUCTION BODY CONTRIBUTES, AND IF THAT REMAINDER CAN VANISH, THE FOLLOW OF THE LEFT-HAND SIDE CONTRIBUTES TOO.
The end marker rule
THE FOLLOW OF THE START SYMBOL ALWAYS CONTAINS THE END MARKER.
THE NULLABLE-PROPAGATION STEP IN FOLLOW COMPUTATION IS THE ONE MOST OFTEN SKIPPED AND IS WHERE MOST TABLE ERRORS ORIGINATE.
Building the LL(1) table
PLACE A GOES TO ALPHA IN THE CELL FOR ROW A AND EACH TERMINAL IN FIRST(ALPHA). IF ALPHA CAN DERIVE EPSILON, ALSO PLACE IT IN EVERY CELL FOR FOLLOW(A).
A GRAMMAR IS LL(1) EXACTLY WHEN NO CELL HOLDS TWO PRODUCTIONS. AMBIGUOUS AND LEFT-RECURSIVE GRAMMARS NEVER ARE.
Shift-reduce parsing
SHIFT PUSHES THE NEXT INPUT SYMBOL; REDUCE POPS A RIGHT-HAND SIDE AND PUSHES THE VARIABLE. IT PRODUCES A RIGHTMOST DERIVATION IN REVERSE.
A HANDLE IS THE SUBSTRING THAT SHOULD BE REDUCED NEXT, AND IDENTIFYING HANDLES IS THE ENTIRE DIFFICULTY OF BOTTOM-UP PARSING.
LR items
AN LR(0) ITEM IS A PRODUCTION WITH A DOT MARKING HOW MUCH HAS BEEN SEEN. CLOSURE ADDS ALL PRODUCTIONS OF A VARIABLE FOLLOWING THE DOT; GOTO MOVES THE DOT PAST A SYMBOL.
A COMPLETED ITEM SUGGESTS REDUCE; AN ITEM WITH THE DOT BEFORE A TERMINAL SUGGESTS SHIFT.
The two conflicts
A SHIFT-REDUCE CONFLICT HAS BOTH A COMPLETED ITEM AND AN ITEM WITH THE DOT BEFORE A TERMINAL. A REDUCE-REDUCE CONFLICT HAS TWO DIFFERENT COMPLETED ITEMS.
REAL PARSERS OFTEN RESOLVE SHIFT-REDUCE CONFLICTS BY PREFERRING TO SHIFT, WHICH IS HOW THE DANGLING-ELSE AMBIGUITY IS SETTLED.
The LR hierarchy
EVERY LR(0) GRAMMAR IS SLR(1), EVERY SLR(1) GRAMMAR IS LALR(1), AND EVERY LALR(1) GRAMMAR IS CLR(1). ALL CONTAINMENTS ARE STRICT.
LR(0) REDUCES ON EVERY INPUT, SLR ON FOLLOW, LALR ON MERGED LOOKAHEADS, AND CLR ON EXACT PER-STATE LOOKAHEADS.
Why SLR is weaker
SLR USES FOLLOW, WHICH COLLECTS EVERY TERMINAL THAT CAN FOLLOW A VARIABLE ANYWHERE, NOT ONLY IN THIS STATE'S CONTEXT.
THAT OVER-APPROXIMATION SCHEDULES REDUCTIONS THAT ARE IMPOSSIBLE IN CONTEXT, PRODUCING SPURIOUS CONFLICTS THAT LALR AVOIDS.
LALR merging
MERGE CLR STATES WITH IDENTICAL CORES, MEANING THE SAME ITEMS IGNORING LOOKAHEADS, AND UNION THEIR LOOKAHEAD SETS.
THE TABLE SHRINKS TO LR(0) SIZE WHILE RETAINING MOST OF CLR'S POWER, WHICH IS WHY PRACTICAL PARSER GENERATORS USE IT.
What merging can and cannot create
MERGING CAN INTRODUCE A REDUCE-REDUCE CONFLICT BUT NEVER A SHIFT-REDUCE CONFLICT.
A SHIFT ACTION DEPENDS ONLY ON THE CORE, WHICH MERGING PRESERVES. A REDUCE ACTION DEPENDS ON LOOKAHEAD SETS, WHICH MERGING ENLARGES AND CAN MAKE OVERLAP.
LL versus LR
EVERY LL(1) GRAMMAR IS LR(1), BUT NOT CONVERSELY.
BOTTOM-UP PARSING SEES THE WHOLE RIGHT-HAND SIDE BEFORE DECIDING, WHILE TOP-DOWN MUST COMMIT AT THE START, SO BOTTOM-UP IS STRICTLY MORE INFORMED.
Attribute kinds
A SYNTHESISED ATTRIBUTE IS COMPUTED FROM THE CHILDREN AND FLOWS UP. AN INHERITED ATTRIBUTE IS COMPUTED FROM THE PARENT OR SIBLINGS AND FLOWS DOWN OR ACROSS.
EXPRESSION EVALUATION IS SYNTHESISED; PROPAGATING A DECLARED TYPE ALONG A LIST OF IDENTIFIERS IS INHERITED.
S-attributed and L-attributed
S-ATTRIBUTED USES ONLY SYNTHESISED ATTRIBUTES. L-ATTRIBUTED ALLOWS INHERITED ONES DEPENDING ONLY ON THE PARENT AND ON SIBLINGS TO THE LEFT.
EVERY S-ATTRIBUTED DEFINITION IS L-ATTRIBUTED. S-ATTRIBUTED EVALUATES DURING A BOTTOM-UP PARSE; L-ATTRIBUTED NEEDS ONE LEFT-TO-RIGHT DEPTH-FIRST PASS.
⚠️

Traps GATE sets — and how to dodge them

These are the exact option-traps and misreads that cost marks under negative marking.

WATCH OUT
Treating LL(1) or LALR(1) as a property of a language
They are properties of grammars. The same language usually has grammars in several classes, which is exactly why left-factoring and left-recursion removal are worth doing.
WATCH OUT
Omitting the nullable-propagation step when computing FOLLOW
If everything after a variable in a production body can vanish, the FOLLOW of the left-hand side must be added. Skipping this produces a FOLLOW set that is too small and a parsing table that looks conflict-free when it is not.
WATCH OUT
Forgetting the end marker in the start symbol's FOLLOW
FOLLOW of the start symbol always contains the end marker, since the whole input can be followed by nothing. Omitting it removes legitimate table entries.
WATCH OUT
Placing an epsilon production only in FIRST cells
A production whose body derives the empty string goes in every cell for FOLLOW of the left-hand side, not in FIRST cells. That is precisely where LL(1) conflicts usually appear.
WATCH OUT
Assuming left-recursion removal preserves associativity
It converts left recursion into right recursion, so the tree shape becomes right-leaning. Left associativity must then be reintroduced through attribute rules rather than relying on the parse tree.
WATCH OUT
Believing LALR merging can create a shift-reduce conflict
It cannot. Shift actions depend only on the core, which merging preserves, so any shift-reduce conflict in the merged table already existed in one of the original CLR states.
WATCH OUT
Assuming LALR and CLR always accept the same grammars
The containment is strict. Merging can union two disjoint lookahead sets into an overlapping one, creating a reduce-reduce conflict that CLR did not have.
WATCH OUT
Claiming LR(1) grammars are a subset of LL(1) grammars
The containment runs the other way. Every LL(1) grammar is LR(1), because bottom-up parsing sees the entire right-hand side before deciding while top-down must commit at the start.
WATCH OUT
Classifying a type error as a syntax error
Type checking is semantic, because the grammar permits the shape and only the types forbid it. Syntax errors are structural, such as a missing bracket or an operator with no operand.
WATCH OUT
Expecting the lexer to detect misspelled keywords
A misspelled keyword usually matches the identifier pattern and is emitted as a valid token. The error surfaces later, at syntax or semantic analysis, when the identifier appears where a keyword was required.
WATCH OUT
Listing the identifier pattern before keyword patterns
With equal-length matches the earlier pattern wins, so every keyword would be lexed as an identifier. Keyword patterns must come first, or the language loses its reserved words.
WATCH OUT
Assuming an L-attributed definition can be evaluated bottom-up
Only S-attributed definitions can, because inherited attributes need values from the parent, which a bottom-up parse has not yet built. L-attributed definitions need a separate left-to-right depth-first traversal.

Exam-pattern practice

PYQ-style questions with full solutions. Work through them as a readiness check — mark yourself honestly and get your gap report at the end.

Readiness check

Are you exam-ready for Lexical Analysis, Parsing & Syntax-Directed Translation?

9 problems from this chapter. Try each one, reveal the worked solution, mark yourself honestly — get your gap report at the end.

9 questions~6 min

5-minute revision

The whole chapter, distilled. Read this the night before the exam.

  • Lexing is regular; parsing is context-free.
  • Token is the category, lexeme the text, pattern the rule.
  • Longest match wins; ties go to the earlier pattern.
  • Keywords must be listed before identifiers.
  • Type errors are semantic, not syntactic.
  • Left recursion breaks top-down parsing.
  • Left factoring defers a choice one symbol.
  • Transformations change the grammar, not the language.
  • FIRST collects possible starting terminals.
  • FOLLOW collects terminals that can come next.
  • A nullable tail propagates FOLLOW upward.
  • The start symbol's FOLLOW has the end marker.
  • Epsilon productions go into FOLLOW cells.
  • LL(1) means no cell holds two productions.
  • Ambiguous grammars are never LL(1).
  • Bottom-up gives a reverse rightmost derivation.
  • A handle is what should be reduced next.
  • An item is a production with a dot.
  • Closure adds productions after the dot.
  • Goto moves the dot past a symbol.
  • LR(0) is inside SLR inside LALR inside CLR.
  • All the containments are strict.
  • SLR reduces on global FOLLOW.
  • CLR carries exact per-state lookaheads.
  • LALR merges same-core states.
  • Merging can create reduce-reduce conflicts.
  • Merging never creates shift-reduce conflicts.
  • Every LL(1) grammar is LR(1).
  • Synthesised attributes flow up.
  • Inherited attributes flow down or across.
  • S-attributed suits bottom-up parsing.
  • L-attributed needs one left-to-right pass.
  • Every S-attributed definition is L-attributed.

GATE question blueprint

How this topic is asked, tier by tier — so you can prep to the pattern.

Typical weightage: Compiler Design contributes roughly 6-8 of the 72 core-CS marks; parsing supplies 3-4 of those across 2-3 questions

Question styleMarks eachTypical countWhat it tests
Compiler phases1~1Which phase detects which class of error
FIRST and FOLLOW2~1Computing both sets including nullable propagation and building the table
Grammar transformation2~1Removing left recursion and left factoring, and checking the result
Grammar classes1~1The containment hierarchy and the LL versus LR relationship
LR conflicts2~1Why SLR produces spurious conflicts that LALR avoids
LALR merging2~1Which conflict type merging can introduce and why
Ambiguity in parsing2~1Why factoring cannot remove genuine ambiguity and how parsers resolve it
Attributes2~1Classifying attributes and choosing an evaluation strategy

Exam-hall strategy

Battle-tested tips from mentors and toppers for this topic under the sectional clock.

  1. Check for left recursion and common prefixes before attempting an LL(1) table.
  2. Always add the end marker to the start symbol's FOLLOW first.
  3. Apply the nullable-propagation rule explicitly rather than by eye.
  4. Place epsilon productions in FOLLOW cells, not FIRST cells.
  5. For LR questions, decide whether the conflict is core-based or lookahead-based.
  6. Remember that LALR merging affects reduce actions only.
  7. State counts and conflict counts are commonly set as NAT, which carries no negative marking, so never leave one blank.
  8. For 1-mark and 2-mark MCQs, negative marking is -1/3 and -2/3, so guess only after eliminating an option.
  9. GATE gives a single freely-navigable 180-minute window, so flag a long item-set construction and return to it.

Beyond the exam

Where this skill shows up in the job you're competing for — and in life.

Reading a parser generator's conflict report

A shift-reduce conflict message names a state and two actions, and knowing that shift is preferred by default explains why the dangling else attaches where it does.

Choosing keyword ordering in a lexer specification

The rule that ties go to the earlier pattern is why every lexer specification lists reserved words before the identifier rule.

Diagnosing why a grammar will not compile

Recognising left recursion or an unfactored prefix immediately explains an LL(1) tool's rejection and tells you which transformation to apply.

Deciding where a compiler check belongs

Knowing that type rules are semantic rather than syntactic is what determines whether a new language check goes in the grammar or the analyser.

Where else this topic is tested

Prepare once, score in every exam that asks it.

GATE DALow overlap — compiler design is not a component of that paper
UGC NET Computer ScienceHigh overlap — compiler phases, parser classes and attribute types are examined as direct recall
ISRO / BARC / DRDO computer science papersVery high overlap — FIRST and FOLLOW computation, LR conflict identification and grammar transformation are recurring MCQ topics

Questions aspirants ask

Pulled from the Q&A community and mentor sessions.

Because each class is defined by whether a particular parsing method can operate on a particular grammar without conflict, and the same language admits many grammars. The clearest illustration is left recursion. The natural grammar for arithmetic expressions is left recursive, which makes it not LL(1), because a top-down parser would descend into the same nonterminal forever. Removing the left recursion produces a different grammar for exactly the same language, and that one is LL(1). Nothing about the set of strings changed; only the derivation structure did. The same holds for common prefixes: two alternatives sharing a leading symbol defeat one-symbol lookahead, and left factoring fixes the grammar without touching the language. Two consequences matter for exam questions. First, a question asking whether a language is LL(1) is malformed unless it means whether some LL(1) grammar exists for it — and the answer to that is different from whether the grammar shown is LL(1). Second, ambiguity is the one obstacle that grammar rewriting cannot always remove. An ambiguous grammar is never LL(1), and if the language is inherently ambiguous then no grammar for it is, so no parsing method in the LL or LR families will handle it cleanly. The dangling-else case sits between these: the grammar is ambiguous, no transformation removes it, and real parsers resolve it by a shift-preferring convention rather than by grammar surgery.

Because the two kinds of action depend on different parts of the state, and merging affects only one of them. LALR builds the full CLR collection and then merges any two states whose cores coincide, where the core is the set of items with their lookahead components stripped away. The merged state keeps that shared core and takes the union of the lookahead sets attached to each item. A shift action arises from an item whose dot sits immediately before a terminal. That is a property of the core alone, so both original states had exactly the same shift actions, and so does the merged state. For a shift-reduce conflict to appear after merging, some completed item must carry the shifted terminal in its merged lookahead set. But the merged set is a union, so that terminal came from one of the originals — and in that original state, the same shift and the same reduce were both present. The conflict existed before merging and was merely carried across. A reduce action, by contrast, depends entirely on the lookahead set. Suppose one state reduces production A on lookahead x and production B on lookahead y, while the other reduces A on y and B on x. Neither state conflicts, because in each the two lookahead sets are disjoint. After merging, both items carry the set containing x and y, so both reduce on x and both reduce on y. That conflict is genuinely new, and it is exactly why some grammars are CLR(1) but not LALR(1).

Work production by production and apply three rules in a fixed order, iterating until nothing changes. Start by placing the end marker in FOLLOW of the start symbol; that entry is always present and is the one most often forgotten. Then, for every production, scan the body left to right. Whenever a variable A appears with something after it, add FIRST of everything after A, excluding epsilon, to FOLLOW of A. That is the direct contribution. The third rule is where errors concentrate. If everything after A can derive the empty string — or if A is the last symbol in the body — then whatever can follow the left-hand side can also follow A, so add FOLLOW of the left-hand side to FOLLOW of A. Missing this nullable-propagation step produces FOLLOW sets that are too small, which in turn produces a parsing table that appears conflict-free when it is not, so the error is silent rather than obvious. The computation must be iterated, because adding to one FOLLOW set can enable a later addition elsewhere. Repeat the whole pass until a complete sweep changes nothing. Two checks help. Every FOLLOW set of a variable that can end a sentential form should contain the end marker. And a variable appearing only at the end of every production body should have FOLLOW equal to the FOLLOW of every left-hand side it appears under, with nothing else.

When error messages matter more than grammar convenience, which in practice means almost any compiler whose users are human. A hand-written recursive-descent parser has one function per nonterminal, so at the moment of failure the compiler knows exactly which construct it was attempting to parse and how far it had got. It can emit a message naming the construct, point at the offending token, and recover by skipping to a synchronising token chosen specifically for that context. A generated bottom-up parser fails in a numbered state, and translating that state into a description a programmer would recognise requires substantial additional machinery. The cost is grammar restrictions. Recursive descent needs the grammar free of left recursion and common prefixes, and removing left recursion converts naturally left-associative rules into right-recursive ones, so associativity must be reimposed through explicit attribute handling rather than falling out of the tree shape. A generated LALR parser accepts the natural grammar directly. There is also a maintenance difference. Regenerating a parser from an edited grammar automatically reports any new conflict, whereas a hand-written parser can drift out of agreement with the intended grammar with nothing to catch it. Most production compilers nevertheless choose recursive descent, judging that diagnostics visible to every user outweigh a one-time grammar transformation and ongoing manual care. Generated parsers remain standard for tools where the input is machine-produced and error quality matters less.

Whether the values can be computed during the parse itself or need a separate traversal. An S-attributed definition uses only synthesised attributes, meaning every value is computed from the values of the children. In a bottom-up parse, the children of a production are reduced before the production itself, so at the moment of each reduction all the needed values are already available. The attribute computation can therefore be attached directly to the reduce action, and no parse tree need be built at all. Expression evaluation is the canonical case: the value of a sum is the sum of the values of its operands, computed as the addition is reduced. An L-attributed definition additionally allows inherited attributes, subject to the restriction that each may depend only on the parent's attributes and on siblings to its left. That restriction is exactly what guarantees a single left-to-right depth-first traversal can compute everything, because by the time a node is visited, its parent and all its left siblings have already been processed. Type propagation along a declaration list is the standard example: the type appears at the left and must reach every name to its right, which is a left-sibling dependency. The practical consequence is a pairing. S-attributed definitions match bottom-up parsing and need no tree. L-attributed definitions match top-down parsing, since recursive descent naturally performs a left-to-right depth-first walk, and a bottom-up implementation must either build the tree first or introduce marker nonterminals to force the evaluation order.
Header Logo