Regular Languages, DFA, and NFA
A finite automaton recognizes a language by reading an input left to right and accepting exactly when the final state is accepting. A DFA has one transition for each state-symbol pair, so an input of length $n$ follows one state sequence in $O(n)$ time. An NFA may have several labeled transitions and $\varepsilon$-transitions that consume no input; simulation maintains the set of all states reachable after each prefix.
The $\varepsilon$-closure of a set contains every state reachable through zero or more $\varepsilon$-transitions. Subset construction converts an NFA to a DFA by treating each reachable, closed NFA-state subset as one DFA state. A subset is accepting if it contains an accepting NFA state. An $m$-state NFA can theoretically induce $2^m$ subsets, though only reachable subsets are built.
DFA minimization removes unreachable states and merges states with indistinguishable future behavior. Hopcroft's partition-refinement algorithm begins with accepting and rejecting blocks and repeatedly splits them using incoming transitions, running in $O(|\Sigma|m\log m)$. The minimal DFA is unique up to state renaming. Regular languages are closed under union, intersection, complement, difference, concatenation, Kleene star, reversal, homomorphism, and inverse homomorphism.
flowchart TD
INPUT["Read next symbol c"]
NFA["NFA active-state set"]
CLOSURE["Move on c and take epsilon closure"]
DFA["DFA subset-state"]
NEXT["Take one transition on c"]
INPUT --> NFA
NFA --> CLOSURE
CLOSURE -->|"next reachable set"| NFA
INPUT --> DFA
DFA --> NEXT
NEXT -->|"next subset-state"| DFA
Regex Construction and Engine Safety
Classical regular expressions use literals, alternation, concatenation, repetition, and grouping. Thompson construction composes these operations into an $O(m)$-state $\varepsilon$-NFA for an expression of size $m$. Simulating its active-state set takes polynomial time, commonly $O(nm)$ for input length $n$, and never needs to enumerate matching paths separately.
Brzozowski's derivative $\partial_c(R)$ denotes the language remaining after consuming $c$. Repeated derivatives recognize a string by testing whether the final residual expression is nullable. Normalizing and memoizing derivatives constructs DFA-like states lazily.
A backtracking engine instead selects alternatives recursively and revisits choices after failure. This supports convenient nonregular extensions but permits exponential behavior when nested or overlapping quantifiers describe many equivalent decompositions. Atomic groups and possessive quantifiers can suppress selected retries; automata-based engines provide predictable execution for purely regular patterns.
Catastrophic Backtracking
Consider (a+)+$ on an input containing $n$ copies of a followed by b. When the end anchor fails at b, a backtracking engine repartitions the preceding a symbols among iterations of the inner and outer + operators.
For four copies, it may try the ordered partitions 4, 3+1, 2+2, 2+1+1, 1+3, 1+2+1, 1+1+2, and 1+1+1+1. In general there are $2^{n-1}$ such partitions, so rejection can require exponentially many paths.
A Thompson NFA keeps every reachable state in one active set. Each a updates that set once, and the final b eliminates every accepting path. Its run is $O(nm)$ for pattern size $m$, effectively linear for this fixed pattern, without enumerating the partitions.
Backreferences, recursive subpatterns, and balancing groups can recognize languages beyond the regular languages, so a finite automaton cannot generally replace them. When these features are unnecessary, prefer an automata-based engine or test a constrained backtracking pattern against adversarial inputs.
Context-Free Grammars and Preparation
A context-free grammar contains terminals, nonterminals, productions, and a start symbol. A derivation replaces nonterminals according to productions; a parse tree records the resulting hierarchy, and its leaves spell the input. A grammar is ambiguous when one input has multiple parse trees, as with an expression grammar lacking precedence and associativity.
Top-down parsing cannot directly handle $E\rightarrow E+T\mid T$, because expanding $E$ recurses without consuming input. Immediate left recursion becomes $E\rightarrow TE'$ and $E'\rightarrow +TE'\mid\varepsilon$. Left factoring changes $A\rightarrow\alpha\beta_1\mid\alpha\beta_2$ into $A\rightarrow\alpha A'$ followed by alternatives for $A'$.
$\operatorname{FIRST}(X)$ contains terminals that can begin strings derived from $X$, including $\varepsilon$ when nullable. $\operatorname{FOLLOW}(A)$ contains terminals that may immediately follow $A$, plus the end marker for the start symbol. An LL(1) table uses these sets to select one production from one lookahead token. A pushdown automaton supplies the unbounded stack needed for nesting: terminals match input, while nonterminals expand into production bodies.
Predictive and Shift-Reduce Parsing
Recursive descent assigns a procedure to each nonterminal and works well for prepared predictive grammars. LL parsers formalize this with a table, reading left to right and constructing a leftmost derivation. Pratt parsing specializes in expressions: null denotations parse atoms and prefixes, left denotations parse postfix and infix uses, and binding powers encode precedence and associativity.
Shift-reduce parsers maintain states and recognized symbols. A shift consumes a token; a reduction replaces a production body with its left-hand nonterminal. LR items track positions within productions. SLR chooses reductions using FOLLOW sets, LALR merges canonical states with the same LR(0) core, and canonical LR preserves item-specific lookahead for more power and larger tables.
| Parser family | Grammar requirements | Lookahead and tables | Relative power | Implementation cost | Typical use |
|---|---|---|---|---|---|
| Recursive descent | No left recursion; alternatives distinguishable by chosen lookahead | Procedures and token tests | Depends on custom lookahead | Low for small grammars | Configuration and hand-written language parsers |
| Pratt | Expressions expressed through binding powers and token behavior | Small precedence dispatch table | Strong for prefix, postfix, and infix expressions | Low to moderate | Expression subgrammars |
| LL(1) | Disjoint FIRST/FIRST and FIRST/FOLLOW predictions | One table entry per nonterminal-token pair | Predictive CFGs | Moderate | Generated top-down parsers |
| SLR(1) | LR(0) cores with conflict-free FOLLOW reductions | Compact ACTION and GOTO tables | Stronger than LL(1), weaker than LALR(1) | Moderate | Small deterministic grammars |
| LALR(1) | Equal LR(0) cores must merge safely | Merged LR tables | Usually close to canonical LR | High manually | Traditional parser generators |
| Canonical LR(1) | Grammar must be LR(1) | Large item-specific lookahead tables | Strongest deterministic family listed | High | Grammars requiring precise contexts |
Generalized and Practical Parsing
Earley parsing uses prediction, scanning, and completion at each input position. It handles arbitrary context-free grammars in $O(n^3)$ worst-case time, improves for unambiguous and deterministic grammars, and can produce a shared parse forest. CYK fills a triangular interval table for a grammar in Chomsky normal form in $O(n^3|G|)$ time.
A lexer normally applies maximal munch: choose the longest token starting at the current position, then use rule priority for ties. Modes change active rules inside strings, comments, interpolation, or embedded languages. Parsers should build ASTs with source spans rather than preserve every grammar node. Panic-mode recovery skips to a synchronizing token; local recovery inserts or deletes a likely token and must always make progress.
Pretty-printers reconstruct text with precedence-aware parentheses and layout rules. Streaming parsers retain only enough state for the next chunk, while incremental parsers reuse immutable subtrees and restartable lexer states after local edits.
Next in the Series
In String Compression, we'll explore Entropy coding, dictionary compression, BWT pipelines, and searchable compressed representations.