Computation Theory · Formal Languages

Closure Gaps in Context-Free Languages

Context-free languages are closed under union and star but not under intersection or complement, and the two failures are the same failure seen from two sides.

The regular languages support a full Boolean algebra: union, intersection, complement, and difference all preserve regularity, and all four constructions are effective. The context-free languages support only half of it. Union and concatenation are closed, intersection and complement are not, and the failure is not a technicality about grammars — it is a statement about what a single stack can hold.

Union is easy, intersection is not

Given context-free grammars G₁ = (V₁, Σ, R₁, S₁) and G₂ = (V₂, Σ, R₂, S₂) with disjoint nonterminal sets, a grammar for L(G₁) ∪ L(G₂) is obtained by adding a fresh start symbol S and the rules S → S₁ and S → S₂. The construction is constant-size and needs no analysis of the operands. Concatenation is similar: S → S₁S₂. Star is S → S₁S | ε. All three are closure properties with proofs that are one line long.

Intersection has no such construction, and the reason is structural. A derivation of a string in L(G₁) ∩ L(G₂) must satisfy both grammars simultaneously, and running two independent pushdown automata in parallel would require two stacks. The product construction that works for finite automata fails here because the two machines must agree on how to split the input, and the split decisions interact: the first grammar’s choice of where one nonterminal expands determines which substring the second grammar’s rules must cover.

Intersection with a regular language does work, and the construction is instructive. Take the product of the pushdown automaton for L and the finite automaton for R. The state becomes a pair (pushdown state, finite state), the stack is unchanged, and transitions advance both components on the same input symbol. The result is a pushdown automaton, so L ∩ R is context-free. The asymmetry is exactly the stack: the regular language contributes no storage, so the product needs only the one stack that was already there.

The two-counter witness

The standard non-closure proof uses three languages:

L₁ = { aⁱbʲcᵏ : i = j }        context-free
L₂ = { aⁱbʲcᵏ : j = k }        context-free
L₁ ∩ L₂ = { aⁿbⁿcⁿ : n ≥ 1 }   not context-free

Each of L₁ and L₂ is context-free: L₁ has a grammar that generates aⁱbⁱ and then appends any number of c’s, and L₂ is the same construction shifted. Their intersection forces three counts to agree, and a pushdown automaton can compare at most one pair. This is the same argument as the hierarchy separation in The Chomsky Hierarchy as a Ladder of Memory, applied to closure rather than to expressiveness.

The underlying obstruction is the stack itself: a single stack cannot enforce two independent agreements at once, and the product construction that would enforce them simultaneously is exactly what the class lacks. The same bound appears from the recognition side: a table over substrings, as in Chomsky Normal Form and the CYK Table, records which nonterminals derive each piece of one string, and there is no cell that records simultaneous derivation by two grammars.

The complement failure follows from the intersection failure by a Boolean identity. If context-free languages were closed under complement, then since they are closed under union, they would also be closed under intersection, because L₁ ∩ L₂ = (L₁ᶜ ∪ L₂ᶜ)ᶜ. The intersection failure therefore implies the complement failure, and no separate witness is needed. This is the cleanest form of the argument: closure under complement plus closure under union yields closure under intersection, so a missing intersection kills complement too.

Complement can also be shown directly. The complement of the intersection above is the union of {aⁱbʲcᵏ : i ≠ j or j ≠ k}, which is context-free, with the strings that are not of the form aⁱbʲcᵏ at all, which form a regular set; a union of two context-free languages is context-free. The complement of that union, {aⁱbʲcᵏ : i = j and j = k}, is not. So this particular language and its complement are on opposite sides of the class, which settles non-closure under complement by example.

Why the deterministic subclass behaves differently

Deterministic context-free languages — those recognised by a deterministic pushdown automaton — are closed under complement. The construction is to swap accepting and non-accepting states and make the machine total, which requires a deterministic machine that reads all its input and halts in a state labelled accept or reject. Determinism guarantees that a run exists and is unique, so complementing the acceptance condition complements the language.

The catch is totality. A deterministic pushdown automaton may have a transition undefined for some (state, input, stack top) combination, and an undefined transition is a rejection. To complement, those dead configurations must be given explicit rejecting transitions, which can require adding states and stack behaviour. The standard construction handles this by tracking the end of input and adding a sink state, and it stays within the deterministic class.

This is why deterministic context-free languages are the practical target of parser generators. LR(1) grammars generate exactly the deterministic context-free languages, and the class has decidable emptiness, equivalence, and complement. The price is that many natural grammars are context-free but not deterministic: the union of two deterministic languages need not be deterministic, so closure under union fails for the deterministic subclass even though it holds for the full class. Neither class has the full Boolean algebra, and they fail at different operations.

What the gap means for specification languages

The closure table is a design document. If a specification language is context-free and is used to describe a set of valid programs, then:

  • Conjoining two independent constraints is not guaranteed to stay in the language. A grammar that enforces “every declared identifier is used” and a grammar that enforces “every used identifier is declared” cannot simply be intersected. Either the conjunction must be expressed in one grammar with shared nonterminals, or the specification must move to a stronger formalism.
  • Negation is not expressible. A rule of the form “reject any input matching this pattern” cannot be added to a context-free grammar as a subtraction, because the class is not closed under difference. The pattern must be excluded by construction, which usually means restructuring the grammar rather than adding a rule.
  • Union and star remain safe. Alternatives and repetition are always available and always cheap.

The practical workaround is to keep the grammar context-free and move the conjunction into a later pass, typically a semantic check over the parse tree. That pass has full computational power, so it can enforce any decidable property, at the cost of separating syntax from semantics and losing the ability to report a syntax error for a violation.

Recognising the intersection of two context-free languages

The intersection is not context-free in general, but it is always context-sensitive, and it is decidable. A recogniser can be built from the CYK tables of both grammars: compute the set of nonterminals of G₁ that derive each substring, compute the same for G₂, and accept when both tables contain their start symbols over the whole input. That is two cubic passes, so the intersection of two context-free languages is decidable in O(n³) time even though the result is not context-free.

def derives(grammar, start, word):
    """True if `start` derives the whole of `word`, for a grammar in Chomsky
    normal form: rules are dict entries mapping a nonterminal to a set of
    right sides, each a tuple of one terminal or two nonterminals."""
    n = len(word)
    table = [[set() for _ in range(n)] for _ in range(n)]
    for i, ch in enumerate(word):
        for lhs, rules in grammar.items():
            if (ch,) in rules:
                table[i][i].add(lhs)
    for length in range(2, n + 1):
        for i in range(n - length + 1):
            j = i + length - 1
            for k in range(i, j):
                for lhs, rules in grammar.items():
                    if any(len(r) == 2 and r[0] in table[i][k] and r[1] in table[k + 1][j]
                           for r in rules):
                        table[i][j].add(lhs)
    if n == 0:                             # only an epsilon rule derives the empty word
        return () in grammar.get(start, set())
    return start in table[0][n - 1]

def in_intersection(g1, s1, g2, s2, word):
    # Two independent passes: closure fails, but the decision problem does not.
    return derives(g1, s1, word) and derives(g2, s2, word)

This is the standard resolution of the apparent paradox: closure fails, decidability does not. The class of context-free languages is not closed under intersection, but the problem “is w in L₁ ∩ L₂” is decidable whenever L₁ and L₂ are given by grammars. Closure is a statement about the class; decidability is a statement about a problem, and the two can point in opposite directions.

The same distinction resolves the complement question. The complement of a context-free language need not be context-free, but membership in the complement is decidable, because membership in the original language is decidable and decidable problems are closed under complement. A language can be decidable without being context-free, and that is precisely the situation.

Where the boundary sits inside the hierarchy

The context-free languages sit at type 2. Their closure failures are inherited from the stack’s single-access discipline, and they disappear at type 1: context-sensitive languages are closed under union, intersection, complement, concatenation, and star. The cost of that closure is the loss of efficient recognition, since type 1 membership has no known polynomial algorithm and the best general procedures are exponential.

That trade is the central one in the theory of formal languages. Each level of the hierarchy buys expressive power and closure properties at the cost of decision procedures. Type 3 has all the closure properties and linear-time recognition. Type 2 has most of them, cubic recognition, and no complement. Type 1 has all of them and no practical recognition. Type 0 has none of the decision procedures and all of the power. The gap described here — intersection and complement — is the exact place where type 2 pays for its stack, and it is why the deterministic subclass, which recovers complement at the cost of union, is the class that real parsers implement.

References

  1. Introduction to Automata Theory, Languages, and Computation — Hopcroft, Motwani, and Ullman; Addison-Wesley
  2. A New Normal-Form Theorem for Context-Free Phrase Structure Grammars — Sheila Greibach, Journal of the ACM, 1965
  3. Introduction to the Theory of Computation — Michael Sipser; Cengage Learning