JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

Why Grammars? Generating Nested Structure

Finite automata read strings and hit a wall: with finite memory they cannot match nested brackets. Grammars flip the question from reading to building, and recursion lets a finite list of rules generate infinite, nested structure.

The wall a finite automaton runs into

By now you can build and trust a DFA: a turnstile-like machine that remembers only its current state, reads a string left to right, and at the end says accept or reject. You also know the painful limit you proved with the pumping lemma: the language a^n b^n — equal numbers of a's then b's — is not regular. The intuition is blunt. A DFA has a fixed, finite set of states, so it can only remember one of finitely many things; to check a^n b^n it would have to remember how many a's it saw, and n can grow without bound. Finite memory simply cannot count without limit.

This is not a quirk of toy examples. Real programs are full of nesting: parentheses inside parentheses, blocks inside blocks, an `if` whose body contains another `if`. The simplest honest version of nesting is balanced brackets — strings like (()) and ()() are balanced, but ()) and )( are not. To recognise balanced brackets you must, in effect, match every open bracket with a later close bracket, tracking how deep you currently are. That depth is unbounded, so once again no regular language can capture it. We have hit the ceiling of the finite-automaton world.

Flip the question: from reading to building

Every machine you have met so far is a reader: hand it a string, it decides yes or no. A grammar turns the telescope around. Instead of reading a finished string and judging it, a grammar builds strings from scratch. You start with a single placeholder, and you repeatedly replace placeholders with other pieces according to a fixed list of rules, until nothing but real letters remains. The set of all strings you can possibly build this way is the language the grammar describes. The question shifts from recognition to generation.

A grammar has four ingredients. There are variables (also called nonterminals): special placeholder symbols, written as capital letters, that stand for 'a piece still under construction.' There are terminals: the actual alphabet letters, written lowercase, that survive into a finished string and are never rewritten. There is a start symbol, one chosen variable you always begin from. And there is a finite list of production rules, each of the form A → α, read 'A may be rewritten as α', where the left side is a single variable and the right side is any string of variables and terminals. That single-variable-on-the-left restriction is exactly what makes a grammar a context-free grammar.

Why call it context-free? Because the left side of every rule is just one variable, you may rewrite that variable no matter what surrounds it — its 'context' does not matter. This is a restriction on the form of the rules, not a claim that meaning is thrown away. Formally we package a context-free grammar as a 4-tuple (V, Σ, R, S): V the variables, Σ (Sigma, the alphabet) the terminals, R the rules, and S the start symbol, with V and Σ disjoint.

Recursion is the whole trick

Here is the move that breaks the finite-automaton ceiling: a variable is allowed to appear inside its own right-hand side. The rule S → ( S ) says 'an S can be a pair of brackets wrapped around another S.' Apply it once and you have ( S ); apply it again on the inner S and you have ( ( S ) ); and so on, as deep as you like. A finite list of rules generates unboundedly deep nesting, because each use of the rule can spawn a fresh copy of the variable to expand later. This is recursion, and it is exactly the power a DFA lacked.

Let us actually solve the two languages that beat the DFA. For a^n b^n the grammar is one recursive rule plus a base case: S → a S b and S → epsilon, where epsilon is the empty string. Each use of S → a S b adds exactly one a on the left and one b on the right, in lockstep, so the counts can never drift apart; S → epsilon stops the recursion in the middle. For balanced brackets the grammar S → S S | ( S ) | epsilon does the job: ( S ) wraps a nested group, S S puts two balanced strings side by side, and epsilon is the empty (still balanced) string.

Grammar for a^n b^n:                Grammar for balanced brackets:

  S -> a S b                          S -> S S | ( S ) | epsilon
  S -> epsilon

Build aabb step by step:            Build (()) step by step:

  S                                   S
  => a S b      (S -> a S b)          => ( S )       (S -> ( S ))
  => a a S b b  (S -> a S b)          => ( S S )     (S -> S S)
  => a a b b    (S -> epsilon)        => ( ( S ) S ) (S -> ( S ))
                                      => ( ( ) S )   (S -> epsilon)
  yields  aabb                        => ( ( ) )     (S -> epsilon)
                                      yields  (())
Two grammars that finite automata cannot handle. The recursive rule (S inside its own right side) is what generates unbounded, matched nesting.

Reading a build off the rules

The step-by-step story above — S => a S b => a a S b b => a a b b — is called a derivation: a sequence of strings where each one comes from the previous by applying a single rule, starting from the start symbol and ending in a string of pure terminals. We write S =>* aabb to mean 'S derives aabb in zero or more steps.' A string belongs to the language generated by the grammar exactly when there exists some derivation of it from S. (You will study derivations, and the parse trees that picture them, in depth in the next guide; here just notice that 'is it in the language?' has become 'can the grammar build it?')

There is a clean way to see the design pattern: read each rule as a sentence about structure. S → a S b reads 'a thing is an a, then a smaller thing, then a b.' E → E + E reads 'an expression can be an expression plus an expression.' Designing a grammar is largely the craft of inventing the right variables — each naming one coherent kind of sub-structure — and then writing rules that say how each kind is built from terminals and from other (or the same) kinds. Recursion among the variables is what lets a handful of rules describe an infinite family of strings.

When structure is not unique: ambiguity

Now a real subtlety, and the reason these grammars matter for programming. Consider arithmetic: E → E + E | E * E | a. Try to build a + a * a. You can build it two genuinely different ways — one that groups it as a + (a * a), and one that groups it as (a + a) * a. Same string, two different structures. A grammar in which some string can be built with two distinct structures is called an ambiguous grammar, and ambiguity is poison for a compiler: if a * binds before or after a + depending on a coin flip, your program's meaning is undefined.

The cure is to encode precedence and associativity into the rules themselves — to layer the grammar so the structure is forced. Introduce separate variables for the levels: an expression is a sum of terms, a term is a product of factors, a factor is an atom or a parenthesised expression. Then E → E + T | T and T → T * F | F and F → ( E ) | a. Because + lives at a higher layer than *, every * must group before any + reaches it, so a + a * a can only be built as a + (a * a). The grouping is now baked into the shape of the rules.

Where grammars sit, and where this is heading

Step back and place this on the map. Regular languages — the DFA/NFA/regex world of the rungs below — are the bottom rung; context-free languages, the ones grammars generate, sit one rung higher and strictly contain the regular languages (every regular language has a grammar, but a^n b^n has a grammar and no DFA). Later in this rung you will meet the machine that matches grammars in raw power: the pushdown automaton, a finite-state control with one stack of plates where you only ever touch the top plate. That stack is precisely the unbounded memory a plain DFA was missing, and it is exactly enough — no more, no less — to recognise the context-free languages.

The four remaining guides in this rung build on today's picture. Guide 2 makes derivations and parse trees precise, and shows why a parse tree — not the order you happened to apply rules — is the honest notion of a string's structure. Guide 3 turns grammar design into a teachable skill, with a^n b^n, balanced brackets, and arithmetic as worked patterns. Guide 4 attacks ambiguity head on. Guide 5 connects all this to the BNF and EBNF notation in which real programming-language manuals actually publish their syntax. You came in able to read strings; you leave this rung able to generate and structure them.