Welcome back. In our previous lesson, we took a high-level tour of the compiler, breaking it down into distinct phases. We focused on the first phase, lexical analysis, where you saw how the raw source code is transformed into a stream of tokens using regular expressions and finite automata. This token stream, while structured, still lacks grammatical validation. It's like having a collection of English words; they are valid words, but we don't yet know if they form a coherent sentence.
That's the job of the syntax analyzer, or parser. Today, we delve into the core mechanics of a specific type of parser: the top-down or predictive parser. Our goal is to compute FIRST and FOLLOW sets for a given grammar to determine if it is LL(1). These sets are fundamental tools that allow a parser to "predict" which grammar rule to apply by looking at just one upcoming token, a critical feature for efficient parsing. This lesson will equip you with the algorithmic skills to analyze a grammar's suitability for this parsing technique, a common topic in GATE.
The Parser's Dilemma: Which Rule to Choose?
Imagine a parser is trying to analyze a piece of code. It has a non-terminal symbol, let's say A, that it needs to expand. The grammar gives it several options:A → α | β | γ
How does the parser decide whether to replace A with α, β, or γ? A naive approach would be to try one, and if it leads to a dead end, backtrack and try another. This is inefficient. A predictive parser, specifically an LL(1) parser, avoids this by making an informed choice based on the single next input token, called the lookahead symbol.
To make this choice, it needs answers to two questions:
- What are the possible first terminals that can be derived from
α,β, andγ? - If a rule can derive an empty string (ε), what are the terminals that are allowed to follow
Ain a valid sentence?
The FIRST and FOLLOW sets provide the precise, algorithmically-computed answers to these questions.
Computing the FIRST Set
The FIRST set of a non-terminal X, denoted , is the set of terminals that can appear as the first symbol in any string derived from X. If X can derive the empty string, ε, then ε is also in .
Here are the rules to compute the FIRST sets for all non-terminals in a grammar:
- For a terminal
a: . - For a non-terminal
X:- If there is a production
X → ε, add ε to . - If there is a production
X → aα(whereais a terminal), addato . - If there is a production
X → Y₁Y₂...Yk(whereY₁is a non-terminal), add everything in (except ε) to . - If contains ε, then also add everything in (except ε) to . Continue this process. If ε is in for all , then add ε to .
- If there is a production
Computing the FOLLOW Set
The FOLLOW set of a non-terminal A, denoted , is the set of terminals that can appear immediately to the right of A in some sentential form. We also use a special symbol, $, to denote the end of the input string.
Here are the rules to compute the FOLLOW sets:
- Place
$in , whereSis the start symbol. - If there is a production
A → αBβ:- Add everything in , except ε, to .
- If contains ε (or if β is empty), then everything in must be in .
- If there is a production
A → αB, then everything in is in .
These rules are applied iteratively until no new terminals can be added to any FOLLOW set.
Let's see these rules applied to GATE-style problems. The following video systematically calculates FIRST and FOLLOW sets for several grammars.
FIRST() and FOLLOW() Functions – Solved Problems (Set 1)
This video from Neso Academy, "FIRST() and FOLLOW() Functions – Solved Problems (Set 1)", is an excellent practical guide. It demonstrates the application of the rules we've just defined.
Please watch the following segments carefully. Focus on how the rules are applied step-by-step, especially in cases involving ε-productions. First example: This problem shows how computing the FOLLOW set of one non-terminal might require the FIRST set of another, and how ε-productions chain this dependency. Second example: This is a comprehensive problem that involves computing FIRST and FOLLOW for all non-terminals in a more complex grammar. Pay close attention to how FOLLOW sets are built by looking at symbols to the right and by inheriting from the non-terminal on the left-hand side of a production. Third example: This example reinforces the concepts with a different grammar structure.
The LL(1) Condition
Now that we can compute these sets, we can define an LL(1) grammar. The name stands for Left-to-right scan of the input, Leftmost derivation, and 1 symbol of lookahead. A grammar is LL(1) if and only if for every non-terminal A with two or more distinct productions, say A → α and A → β, the following conditions hold:
-
Disjoint FIRST sets: The FIRST sets of the right-hand sides must be disjoint. Formally, . This ensures that if the lookahead symbol is
t, it can belong to the FIRST set of at most one of the possible expansions. -
Handling Epsilon: At most one production can derive ε. If one production can derive ε (say,
α →* ε), then its FIRST set will contain ε. In this case, the FIRST set of any other productionβmust not contain any symbol that is in the FOLLOW set ofA. Formally, if ε is in , then . This prevents confusion: if the lookahead symbol is in , the parser knows to apply theA → α(which will eventually become ε) rule. If it's in , it appliesA → β. There cannot be an overlap.
If either of these conditions is violated for any non-terminal, the grammar is not LL(1). The parser would face a conflict, meaning it wouldn't know which production to choose based on the lookahead, and would have to guess or backtrack.
Let's apply this test to some grammars.
LL(1) Parsing – Solved Problems (Set 1)
The video "LL(1) Parsing – Solved Problems (Set 1)" from Neso Academy is the perfect follow-up. It takes the FIRST and FOLLOW sets and uses them to check the LL(1) property by attempting to build a parsing table and identifying conflicts.
Watch the following examples to see how the LL(1) conditions are checked in practice. Non-LL(1) example: This is a crucial segment. It computes FIRST and FOLLOW, and then shows a conflict. When filling the parsing table for non-terminal S and lookahead a, there are two possible productions. This violates the LL(1) condition. The same happens for lookahead b. LL(1) example: Here, after computing the sets, the resulting parsing table has no conflicts. Each cell has at most one entry, so the grammar is LL(1). Complex LL(1) example: This example with multiple non-terminals reinforces the procedure. Notice how the productions that derive ε are placed in the table cells corresponding to the non-terminal's FOLLOW set. Shortcut for non-LL(1): This final part shows a quick way to spot a non-LL(1) grammar. If two productions for the same non-terminal S, say S→A and S→a, have overlapping FIRST sets (e.g., if FIRST(A) also contains a), you can immediately conclude the grammar is not LL(1) without building the full table.
The conflicts highlighted in the video—multiple entries in a single cell of the parsing table—are the direct result of violating one of the two LL(1) conditions we discussed. For GATE, being able to quickly compute these sets and check for these conflict conditions is a key problem-solving skill.
Conclusion
In this lesson, we moved from the what of syntax (tokens) to the how of parsing (prediction). You've learned the algorithms for computing FIRST and FOLLOW sets, which are the cornerstones of predictive parsing.
Here are the key takeaways:
- FIRST(X) is the set of terminals (plus potentially ε) that can begin strings derived from
X. - FOLLOW(A) is the set of terminals (plus potentially
$) that can appear immediately afterAin a valid derivation. - A grammar is LL(1) if, for any non-terminal, the parser can uniquely determine which production to apply by looking at only the next input symbol.
- This uniqueness is guaranteed if the LL(1) conditions are met: disjoint FIRST sets for alternative productions, and if one production yields ε, its FOLLOW set must be disjoint from the other productions' FIRST sets.
We've now established the theoretical basis for building a predictive parser. You can determine if a grammar is "good enough" for this simple, efficient parsing method. In our next lesson, we will put this theory into practice. We'll learn how to construct the complete LL(1) parsing table and use it with a stack-based algorithm to parse an input string, tracing the entire process from start to finish.