Hello! Welcome back to our course on the Theory of Computation.
In our last lesson, we explored how to represent derivations visually using parse trees and how to use them to identify grammatical ambiguity. We focused on analyzing existing grammars. Today, we shift our focus from analysis to synthesis. Our goal is to develop the skills to design Context-Free Grammars (CFGs) for key context-free languages, such as palindromes and balanced parentheses.
Designing a CFG is a creative process that blends logic and pattern recognition. Given your background in software engineering and AI, you can think of it as being similar to designing a recursive algorithm. We need to identify the simplest cases (base cases) and then define rules for building more complex strings from simpler ones (the recursive step).
1. The Core Strategy: Recursive Design
The most effective way to design a CFG is to think about the language's structure recursively. For any given language, ask yourself two questions:
- What are the smallest possible strings in this language? These will form your base cases, which are typically handled by rules that produce terminals or the empty string, . This is the termination condition for your recursion.
- How can I build larger strings in the language from smaller strings already in the language? This will inform your recursive rules, where a non-terminal appears on both the left and right side of a production.
Let's apply this strategy to some fundamental language patterns.
2. Pattern 1: Matching Symmetric Symbols
Many context-free languages involve some form of symmetry or matching of symbols from the outside in. The canonical example is the language .
- Base Case: The smallest string is for , which is .
- Recursive Step: If we have a string that is already in the language, we can create a new, larger string by wrapping it with an
aat the front and abat the end:ab= .
This logic translates directly into the following grammar, where S is the start symbol:
- (Recursive step)
- (Base case)
The following video provides a concise walkthrough of this example and others, which will serve as our guide for several patterns in this lesson.
Context-Free Grammars (CFGs): 15 Examples
This video, "Context-Free Grammars (CFGs): 15 Examples" from Easy Theory, begins by demonstrating the recursive design principle with the language aⁿbⁿ. This is a perfect starting point.
Watch the first example in the video from 00:00 to 02:18. Pay close attention to how the host identifies the base case (empty string) and the recursive case (aSb) to construct the grammar.
Palindromes: A Generalization of Symmetry
A palindrome is a string that reads the same forwards and backwards (e.g., racecar). This is a generalization of the a^n b^n pattern. Let's design a CFG for the language of all palindromes over the alphabet .
- Recursive Step: If a string
wis a palindrome, then0w0and1w1are also palindromes. - Base Cases: What are the smallest palindromes? The empty string () is a palindrome (for even-length cases). The single characters
0and1are also palindromes (for odd-length cases).
This gives us the grammar:
The video below explains this construction very clearly.
Context-Free Grammars (CFGs): 5 Easy Examples
This clip from "Context-Free Grammars (CFGs): 5 Easy Examples" by Easy Theory focuses directly on building a grammar for palindromes. It does an excellent job explaining the need for both even and odd length base cases.
Watch the segment from 05:56 to 09:08. Notice how the rules 0S0 and 1S1 handle the recursion, while 0, 1, and ε handle the different base cases.
To solidify this, here is a visual breakdown of how this grammar generates both even and odd length palindromes.

3. Pattern 2: Sequential and Nested Structures
Not all languages are built from the outside in. Some are built by concatenating valid strings or nesting them. The classic example is the language of balanced parentheses.
A string of parentheses is balanced if:
- It is the empty string. (Base Case)
- It is of the form
(w), wherewis a balanced string. (Nesting) - It is of the form
xy, wherexandyare both balanced strings. (Concatenation)
This definition leads to the grammar:
As we discussed in the previous lesson, the S -> SS rule makes this grammar ambiguous. For example, the string ()() has two different leftmost derivations. While correct, this ambiguity is often undesirable. An alternative, unambiguous grammar for the same language is:
This version generates a balanced string (S) and then, optionally, another balanced string S immediately after it, avoiding the ambiguity of the SS rule.
The following video segment discusses the balanced parentheses problem and highlights the ambiguity issue.
Context-Free Grammars (CFGs): 15 Examples
Let's return to the "15 Examples" video. This segment covers the balanced parentheses language, including a discussion of the ambiguous SS rule and how to construct an unambiguous version.
Watch the segment from 41:18 to 44:10. Focus on the two ways to define balanced parentheses recursively (nesting vs. concatenation) and how they translate into different CFG rules.
4. Pattern 3: Advanced Composition Techniques
More complex languages often require combining these basic patterns. This is where using multiple non-terminals becomes essential. You can think of non-terminals as "helper functions" or "subroutines" that are responsible for generating a specific part of the language.
Chaining Variables for Sequential Dependencies
Consider the language . Here, every a and every b must be matched by a c. The a's come before the b's.
We can't use a single symmetric rule. The solution is to handle the a's first, then hand off to another variable to handle the b's.
- Generate
a's andc's symmetrically:S -> aSc. - Once all
a's are generated, switch to a new non-terminal, let's call itB, to generateb's andc's symmetrically. - The base case is
ε.
This gives the grammar:
- (Match an
awith ac, or switch to matchingb's) - (Match a
bwith ac, or terminate)
Watch the following clip to see this technique in action.
Context-Free Grammars (CFGs): 15 Examples
The "15 Examples" video provides an excellent explanation of how to chain variables to handle sequential dependencies.
Watch from 06:39 to 09:17. The key idea here is using one variable S to handle the a-c matching and then transitioning to another variable X to handle the b-c matching.
Other Powerful Techniques
The "15 Examples" video contains several other excellent patterns. I encourage you to watch these short clips to add more tools to your CFG design toolkit.
Context-Free Grammars (CFGs): 15 Examples
These final clips from the same video demonstrate how to handle logical 'OR' conditions and nested, non-adjacent dependencies.
Please watch the following three segments: Union ('OR' condition) (26:46 - 29:32): For a language like {aⁱbʲcᵏ | i=j or j=k}. The strategy is to create separate grammars for each condition and combine them with a rule S -> A | B. Nested Dependencies (29:32 - 31:04): For a language like {aⁿbᵐcᵐdⁿ}. This shows how to handle matching pairs that are not adjacent, using a nested recursive structure S -> aSd | B, B -> bBc | ε. Independent Parts (04:48 - 06:39): For a language like {aⁿbᵐaⁿ}. This demonstrates how to handle parts of a string that are independent of each other (the bᵐ part) by delegating to a separate variable that generates a regular language (B -> bB | ε).
Test your understanding!
Let's combine some of these patterns. Design a Context-Free Grammar for the language .
For example, a cdc bb (), a d bb (), aa ccddcc bbbb () are in .
The string acb is not in because the number of b's is not twice the number of a's.
The string a cd bb is not in because cd is not a palindrome.
The string ε is not in because .
Hint: Think about the overall structure first (a...bb). What needs to go in the middle? How can you delegate the generation of w to a helper non-terminal?
Show answer
Let's break down the design process.
-
Outer Structure: The language has a symmetric component, but it's not a 1-to-1 match. For every one
aat the beginning, we need twob's at the end. The recursive step for the main structure is therefore . -
Inner Structure: In the middle of the
a's andb's, we need a palindromewover{c, d}. We can create a separate non-terminal, let's call itW, to generate this palindrome. The grammar forWwould be the standard palindrome grammar: . -
Connecting the Parts: The
S \to aSbbrule creates the outer structure. When do we generate the palindromew? After all thea's andb's have been laid down. This means the base case for theSrecursion should be to generateW. So, we replace the typicalεbase case withW. -
Putting it together:
- (The rule
S -> aWbbis the base case for n=1, stopping the recursion)
Let's refine the
Srule. The condition is , so we can't generateWdirectly fromS. The recursion must happen at least once.
The rule handles the recursion. The base case must generate the structure for , which isa W bb. So the grammar is:Let's trace
aa cddc bbbb(): - (The rule
This grammar correctly generates the language.
Conclusion
In this lesson, we moved from analyzing grammars to designing them. We saw that building a CFG is a recursive process that involves identifying base cases and recursive rules for generating larger strings.
Key Takeaways:
- Recursive Design: The core strategy is to define a language in terms of itself. Find the smallest strings (base cases) and figure out how to build larger strings from smaller ones (recursive steps).
- Symmetric Matching: For languages like
aⁿbⁿand palindromes, use rules like to build from the outside in. - Sequential/Nested Structures: For languages like balanced parentheses, rules like can generate nested and concatenated strings.
- Composition with Helper Variables: For complex languages, use different non-terminals as subroutines to handle different parts of the string, such as chaining them for sequential dependencies () or combining them for logical 'OR' conditions ().
Preview of the Next Lesson:
Our design process can sometimes lead to grammars that are overly complex, containing redundant or useless rules. Before we can use these grammars in practical applications like parsing, we need to clean them up. In the next lesson, we will learn about algorithms to simplify CFGs by removing useless symbols, -productions, and unit productions. This "grammar hygiene" is an essential step in the formal language toolkit.