Welcome to the next lesson in your compiler design journey. In our previous session, we explored the world of top-down parsing, culminating in the construction and use of LL(1) parsing tables. You learned how to use FIRST and FOLLOW sets to predict which production to apply. While powerful, LL(1) parsers have limitations and cannot handle many common grammatical constructs like left recursion.
Today, we shift our perspective from top-down to bottom-up parsing. This lesson introduces a more powerful class of parsers called LR parsers, which are widely used in practice. Our focus will be on the first variant, Simple LR (SLR(1)). Your goal is to construct an SLR(1) parsing table and use it to parse an input string, while also learning to identify potential shift-reduce or reduce-reduce conflicts. This moves us from trying to derive a string from the start symbol (top-down) to trying to reduce a string back to the start symbol (bottom-up).
The Bottom-Up Approach: Shift and Reduce
Imagine building a structure. The LL(1) top-down approach is like having a blueprint and starting from the roof, trying to build down to the foundation. The bottom-up approach is the more conventional method: you start with the foundation and individual bricks, and build upwards until you have the complete structure.
In parsing, the "bricks" are the terminals of our input string, and the "structure" is the start symbol of the grammar. A bottom-up parser scans the input and "shifts" tokens onto a stack. When the top of the stack matches the right-hand side (RHS) of a grammar rule—a pattern known as a handle—the parser "reduces" it by replacing the handle with the non-terminal from the left-hand side (LHS) of that rule. This process continues until the entire string is reduced to the start symbol.
The two fundamental actions are:
- Shift: Move the next input token onto the top of the stack.
- Reduce: Replace a sequence of symbols on top of the stack with a non-terminal , where is a production.
The core challenge for a bottom-up parser is to know exactly when to shift and when to reduce. This decision-making logic is encoded in an LR parsing table.
Building the Parser's "States": Canonical Collection of LR(0) Items
Before we can build the table, we need to define the "states" of our parser. Each state represents a set of possibilities for what we have seen so far and what we might see next. These possibilities are captured by LR(0) items.
An LR(0) item is simply a production from the grammar with a dot . placed somewhere on the right-hand side. For example, for the production A → (A), the LR(0) items are:
A → . (A)(We expect to see(next)A → ( . A )(We have just seen(and now expectA)A → ( A . )(We have seen(Aand now expect))A → ( A ) .(We have seen the entire RHS; a reduction is possible)
To build the full set of states, we start with an augmented grammar (adding a new start production like S' → S) and apply two operations: closure() and goto(). These operations systematically generate all possible sets of LR(0) items, which form the states of our parser's control automaton.
The process of creating this collection of states is the foundation for all LR parsers.
SLR(1) parsing example | LR(0) items | CD | Compiler Design | Lec-21 | Bhanu Priya
This video from Education 4u provides a clear, step-by-step demonstration of augmenting a grammar and creating the complete set of LR(0) item states.
Watch the segment from this part to see how to build the states for a sample grammar. Pay attention to: Augmenting the grammar: Adding a new start symbol A' that derives the original start symbol A. The initial state (I0): It starts with the closure of the augmented production A' → .A. Goto operation: How moving the dot over a symbol (terminal or non-terminal) transitions from one state to another. Closure operation: When the dot is before a non-terminal, all productions of that non-terminal are added to the state with the dot at the beginning.
The resulting diagram of states (I0, I1, I2, etc.) and transitions is called the Canonical Collection of LR(0) items. This is our map for building the parsing table.
Constructing the SLR(1) Parsing Table
The SLR(1) parsing table dictates the parser's actions. It has rows corresponding to the states (I0, I1, ...) and two main sections of columns:
- ACTION: For all terminal symbols in the grammar. This part tells the parser whether to shift, reduce, or accept.
- GOTO: For all non-terminal symbols. This part tells the parser which state to transition to after a reduction.
The crucial difference between an LR(0) parser and an SLR(1) parser lies in how reduce actions are placed in the table. LR(0) is naive: if a state contains a final item A → α ., it places a reduce action in the entire row for that state. This often leads to conflicts.
SLR(1) is smarter. It uses the FOLLOW set of the non-terminal on the LHS of the production to decide where to place reduce actions. This is the "1" in SLR(1)—it uses one lookahead symbol to make a more informed decision.
SLR(1) parsing example | LR(0) items | CD | Compiler Design | Lec-21 | Bhanu Priya
To understand this key distinction, let's revisit the Education 4u video for a concise explanation.
Watch the segment from this explanation, which explicitly states the core rule of SLR(1): "we place the reduce move only in the follow of the left-hand side, not to the entire row."
With this principle in mind, the table construction algorithm is as follows:
Let the set of LR(0) item states be .
- Shift Action: If state contains an item
[A → α . a β](whereais a terminal) andgoto(I_i, a) = I_j, then setACTION[i, a] = Shift j(orSj). - Reduce Action: If state contains a final item
[A → α .](whereAis not the augmented start symbol), then for every terminaltinFOLLOW(A), setACTION[i, t] = Reduce by A → α(orRk, wherekis the production number). - Accept Action: If state contains the item
[S' → S .], then setACTION[i, $] = Accept. - GOTO Entry: If
goto(I_i, A) = I_j(whereAis a non-terminal), then setGOTO[i, A] = j. - All other entries are errors.
Let's see this algorithm applied to the states we generated earlier.
SLR(1) parsing example | LR(0) items | CD | Compiler Design | Lec-21 | Bhanu Priya
The same video now shows how to fill the ACTION and GOTO parts of the SLR(1) table using the states that were just created.
Watch from this segment. Notice how for the reduce actions (like in state I3), the video first computes FOLLOW(A) and places the R2 action only in the columns for the terminals in that FOLLOW set ($ and )), not across the whole row.
Parsing an Input String with the SLR(1) Table
With the table constructed, we can now parse an input string. The process uses a stack (which holds states and grammar symbols), the input string (terminated by $), and the parsing table.
The algorithm is simple:
At each step, let the state on top of the stack be s and the current input symbol be a.
- Look up
ACTION[s, a]in the table. - If it's
Shift j: Push the input symbolaand then the statejonto the stack. Advance the input pointer. - If it's
Reduce by A → α: Pop2 * |α|symbols from the stack (each symbol and its corresponding state). Let the state now exposed at the top of the stack bes'. Push the non-terminalAonto the stack. Then push the stateGOTO[s', A]onto the stack. Do not advance the input pointer. - If it's
Accept: Parsing is successful. - If it's
Error: The string is not in the language.
Let's watch this process in action.
SLR(1) parsing example | LR(0) items | CD | Compiler Design | Lec-21 | Bhanu Priya
This final segment of the video demonstrates the parsing of an input string (a) using the table we just saw being built.
Follow the trace from this demonstration. Keep track of the stack, the input, and the action being performed at each step. This is a very common format for GATE questions.
To reinforce this, let's watch another detailed parsing example from Prof. Ravindrababu Ravula, which gives a very clear blow-by-blow account of the stack operations.
CD | Parsers | LR(0) parsing example and SLR(1) | Ravindrababu Ravula | Free GATE CS Classes
This video provides a second, highly detailed trace of the parsing process for a different grammar.
Watch the parsing trace from this video. Pay close attention to how reduce moves are handled: pop 2 * |RHS| symbols, push the LHS non-terminal, and then use the GOTO table to find the next state.
Conflicts: When a Grammar is Not SLR(1)
The power of SLR(1) is limited. Sometimes, the table construction algorithm results in a cell with more than one entry. This is a conflict, and it means the grammar is not SLR(1). There are two types of conflicts:
-
Shift-Reduce (S/R) Conflict: The parser doesn't know whether to shift the current token or reduce a handle on the stack. This occurs in a state
I_iif it contains:- An item like
[A → α . a β](suggesting a shift ona). - And a final item
[B → γ .]whereais inFOLLOW(B)(suggesting a reduce ona).
- An item like
-
Reduce-Reduce (R/R) Conflict: The parser doesn't know which of two possible reductions to perform. This occurs in a state
I_iif it contains:- A final item
[A → α .]. - And another final item
[B → β .], whereFOLLOW(A) ∩ FOLLOW(B)is not empty (i.e., they share a common lookahead symbol).
- A final item
Understanding how these conflicts arise is key to diagnosing why a grammar fails to be SLR(1).
CD | Parsers | LR(0) parsing example and SLR(1) | Ravindrababu Ravula | Free GATE CS Classes
Prof. Ravula's video offers an excellent explanation of these conflicts.
First, watch from this part to understand the general conditions for Shift-Reduce and Reduce-Reduce conflicts in any LR parser. Then, watch the crucial follow-up from this segment, which explains precisely how the FOLLOW set plays a role in causing or avoiding these conflicts in an SLR(1) context.
Conclusion
In this lesson, we made the significant leap from top-down to bottom-up parsing. We focused on the SLR(1) technique, which represents a sweet spot between simplicity and power.
Your key takeaways are:
- Bottom-up parsing works by shifting input symbols onto a stack and reducing handles (RHS of productions) to their corresponding non-terminals.
- The process starts by building the canonical collection of LR(0) items, which serve as the states of the parser.
- The SLR(1) parsing table is constructed using these states. Shift actions are determined by
gototransitions on terminals, while Reduce actions are placed in the columns corresponding to theFOLLOWset of the production's LHS non-terminal. - Conflicts (Shift-Reduce or Reduce-Reduce) arise when a state and a lookahead symbol lead to more than one possible action. A grammar with such conflicts is not SLR(1).
You are now equipped to construct an SLR(1) parser from scratch and use it to validate a string, a fundamental skill in compiler theory and a frequent topic in GATE.
In our next lesson, we will continue our exploration of bottom-up parsing by examining more powerful techniques: CLR(1) and LALR(1). These methods refine the concept of lookahead to handle a broader class of grammars, including many that are not SLR(1).