GNU Bison & the yacc Lineage (C)
The classical bottom-up generator family: a fifty-year-old declarative grammar DSL compiled into a table-driven LALR(1) shift-reduce parser, with operator-precedence conflict resolution, an optional GLR mode for ambiguous grammars, and a separate lexer.
| Field | Value |
|---|---|
| Language | C (Bison output: C, C++, D, Java); Bison itself is C + an m4 skeleton layer |
| License | GPL-3.0-or-later (Bison); the generated parser carries a special exception so output is not GPL-encumbered |
| Repository | git.savannah.gnu.org/cgit/bison.git |
| Documentation | GNU Bison manual (bison.html) |
| Key authors | Stephen C. Johnson (yacc, 1975); Robert Corbett (original Bison); Akim Demaille, Paul Eggert, Joel E. Denny (maintainers) |
| Category | Generator (LR) — external grammar DSL, offline table generation |
| Algorithm / class | Deterministic LR with LALR(1) (default), IELR(1), or canonical-LR(1) tables; optional GLR |
| Lexing model | Separate lexer (yylex), conventionally generated by lex/flex; not scannerless |
| Latest release | Bison 3.8.2 (25 September 2021) |
NOTE
This deep-dive treats the whole lineage as one subject: Stephen Johnson's original AT&T yacc (1975), GNU Bison as its dominant modern descendant, and the siblings Berkeley yacc (byacc), BSD yacc, and SQLite's Lemon. Bison is the primary lens because it is the most feature-rich and the best-documented; the others are surveyed against it under Ecosystem & maturity. For the underlying theory see the bottom-up parsing theory note; for the comparison against PEG/combinator/incremental approaches see the capstone comparison.
Overview
What it solves
A program that reads structured input is, implicitly, defining a language. Johnson's 1975 paper opens with exactly this framing (yacc.pdf, abstract):
"Computer program input generally has some structure; in fact, every computer program that does input can be thought of as defining an 'input language' which it accepts. … Yacc provides a general tool for describing the input to a computer program. The Yacc user specifies the structures of his input, together with code to be invoked as each such structure is recognized."
yacc's contribution was to make the LR-parsing machinery of Knuth and DeRemer practical and declarative: you write a context-free grammar annotated with C actions, and yacc emits a C subroutine — yyparse — that recognizes that grammar in linear time using precomputed parse tables. The grammar class is stated precisely in the same abstract:
"Yacc is written in portable C. The class of specifications accepted is a very general one: LALR(1) grammars with disambiguating rules."
GNU Bison is the Free Software Foundation's reimplementation and superset. The manual's one-line self-description (introduction):
"Bison is a general-purpose parser generator that converts an annotated context-free grammar into a deterministic LR or generalized LR (GLR) parser employing LALR(1), IELR(1) or canonical LR(1) parser tables. … Bison is upward compatible with Yacc: all properly-written Yacc grammars ought to work with Bison with no change."
The problem this family solves, then, is: turn a high-level grammar into an efficient, deterministic, table-driven recognizer without the grammar author hand-writing a parser. It is the canonical "parser generator" — the antithesis of a hand-written recursive-descent or parser-combinator approach, where the grammar and the parser are the same hand-maintained code.
Design philosophy
Three commitments define the lineage:
The grammar is the source of truth, and it is bottom-up. Unlike top-down generators (ANTLR/LL) or PEG tools that read like the grammar's recursive-descent expansion, an LR grammar is recognized bottom-up: the parser shifts tokens onto a stack and reduces a run of stack symbols to a nonterminal the instant a complete right-hand side is on top. The Bison manual states this plainly (The Bison Parser Algorithm):
"As Bison reads tokens, it pushes them onto a stack along with their semantic values. The stack is called the parser stack. Pushing a token is traditionally called shifting. … When the last
ntokens and groupings shifted match the components of a grammar rule, they can be combined according to that rule. This is called reduction."This makes LR strictly more powerful than LL for a fixed lookahead: left recursion is natural (indeed preferred), and the parser commits to a rule only once it has seen the entire right-hand side plus one lookahead token.
Conflicts are resolved declaratively, not by reordering rules. When the precomputed tables admit two legal actions for one state/lookahead pair, that is a conflict. yacc/Bison resolve conflicts via precedence and associativity declarations (
%left,%right,%nonassoc,%prec) layered on top of two default rules, rather than forcing the author to refactor the grammar into an unambiguous form. This is the single feature that makes expression grammars tractable.The lexer is separate and the parser calls it. yacc parses tokens, not characters; a user-supplied
yylexreturns the next token. From Johnson (yacc.pdf):"The user must supply a lexical analyzer to read the input stream and communicate tokens (with values, if desired) to the parser. The lexical analyzer is an integer-valued function called
yylex. … If there is a value associated with that token, it should be assigned to the external variableyylval."This is the opposite of a scannerless design (PEG/packrat, simdjson): tokenization is a distinct phase, conventionally handled by
lex/flex. Lemon inverts only the call direction (see Ecosystem), not the two-phase structure.
How it works
The pipeline
Bison is an offline code generator. Given parser.y, it runs the LR table-construction pipeline and emits parser.tab.c (+ optionally parser.tab.h of token codes). The real source tree (src/) lays the pipeline out one phase per file:
| Phase | Source file(s) | Role |
|---|---|---|
| Parse the grammar file | parse-gram.y, scan-gram.l, reader.c | Bison's own grammar parser (itself bootstrapped from a Bison grammar) + lexer |
| Symbol table | symtab.c, gram.c/gram.h | Terminals, nonterminals, types, precedence levels |
| LR(0) automaton | lr0.c, closure.c, derives.c, nullable.c | Build the characteristic state machine (item sets) |
| Lookahead sets | lalr.c | DeRemer–Pennello LALR(1) lookahead computation |
| Full-power tables (optional) | ielr.c, AnnotationList.c, InadequacyList.c | IELR(1) inadequacy-elimination → minimal LR(1) tables |
| Conflict resolution | conflicts.c | Apply precedence/associativity; report unresolved shift/reduce & reduce/reduce |
| Counterexamples | counterexample.c | Generate concrete conflict witnesses (-Wcounterexamples) |
| Emit tables + parser | tables.c, output.c, files.c | Pack the action/goto tables; expand the m4 skeleton into the target language |
The emitted parser is a fixed driver (an m4 skeleton such as yacc.c, glr.c, lalr1.cc) parameterized by the packed tables. The driver is a tiny loop; all grammar-specific knowledge lives in integer tables.
The runtime: a shift-reduce automaton over a value stack
The generated yyparse is a pushdown automaton. It keeps two parallel stacks — a state stack and a semantic-value stack — and loops:
loop:
state := top of state stack
action := action_table[state][lookahead] # one table lookup
case action of
shift s -> push token + its yylval; push state s; read next lookahead
reduce r -> pop |rhs(r)| symbols; run rule r's action; goto_table lookup; push lhs
accept -> return 0
error -> invoke yyerror; enter error recoveryThe two default disambiguating rules baked into table construction are stated verbatim by Johnson (yacc.pdf):
"Yacc invokes two disambiguating rules by default: 1. In a shift/reduce conflict, the default is to do the shift. 2. In a reduce/reduce conflict, the default is to reduce by the earlier grammar rule (in the input sequence)."
Semantic values: $$, $1, and the typed %union
Every grammar symbol carries a semantic value. An action computes the value of the rule's left-hand side ($$) from the values of its components ($1, $2, …). Johnson's description (yacc.pdf):
"To return a value, the action normally sets the pseudo-variable
$$to some value. … To obtain the values returned by previous actions and the lexical analyzer, the action may use the pseudo-variables$1, $2, …, which refer to the values returned by the components of the right side of a rule, reading from left to right."
When different symbols need different C types, %union declares the tagged union that backs the value stack, and %type/%token annotate each symbol with its member. A canonical Bison calculator fragment:
%union {
int num; /* value of NUMBER and of expr */
char *str;
}
%token <num> NUMBER
%type <num> expr
%left '+' '-' /* lowest precedence, left-associative */
%left '*' '/' /* higher precedence, left-associative */
%right UMINUS /* unary minus, highest, right-assoc */
%%
expr:
NUMBER { $$ = $1; }
| expr '+' expr { $$ = $1 + $3; }
| expr '*' expr { $$ = $1 * $3; }
| '-' expr %prec UMINUS { $$ = -$2; } /* %prec overrides the rule's precedence */
;
%%The expression grammar above is grossly ambiguous as written (expr '+' expr etc.), yet Bison compiles it without conflicts because the precedence declarations resolve every shift/reduce ambiguity — the mechanism analyzed next. %prec UMINUS overrides a rule's default precedence (normally that of its last terminal) so unary minus binds tighter than binary minus.
Mid-rule actions
An action may appear inside a rule, not only at its end. Bison treats a mid-rule action as an anonymous nonterminal that reduces to the empty string at that point, so its $$ becomes a numbered component ($1, $2, …) for the rest of the rule. This lets an action installed mid-parse (e.g. opening a new scope before parsing a block body) see the values to its left — at the cost of introducing fresh reductions that can manufacture new conflicts.
Counterexample generation: concrete conflict witnesses
The historically hardest part of using yacc/Bison is reading a shift/reduce conflict report and figuring out what input triggers it. Since 3.8, bison -Wcounterexamples (-Wcex, implemented in counterexample.c) synthesizes a concrete input string that exhibits each conflict — the feature was, per the manual, "initially developed by Chinawat Isradisaikul and Andrew Myers" from their PLDI 2015 paper, whose abstract states the goal:
"This paper proposes a practical algorithm that generates compact, helpful counterexamples for LALR grammars. For each parsing conflict in a grammar, a counterexample demonstrating the conflict is constructed. When the grammar in question is ambiguous, the algorithm usually generates a compact counterexample illustrating the ambiguity."
Bison distinguishes a unifying counterexample (one string with two derivations — proof the grammar is ambiguous) from a nonunifying one (two strings agreeing up to the conflict point — the grammar merely needs more lookahead / a stronger parser). The dangling-else conflict produces this verbatim output (Counterexamples manual):
else.y: warning: shift/reduce conflict on token "else" [-Wcounterexamples]
Example: "if" expr "then" "if" expr "then" stmt • "else" stmt
Shift derivation
if_stmt
↳ 3: "if" expr "then" stmt
↳ 2: if_stmt
↳ 4: "if" expr "then" stmt • "else" stmt
Example: "if" expr "then" "if" expr "then" stmt • "else" stmt
Reduce derivation
if_stmt
↳ 4: "if" expr "then" stmt "else" stmt
↳ 2: if_stmt
↳ 3: "if" expr "then" stmt •This is a step-change in usability: the developer sees the exact ambiguous program, not just a state number. It is the closest the LR lineage comes to the IDE-grade error reporting of tree-sitter.
Algorithm & grammar class
The formalism is deterministic LR over context-free grammars. Bison offers three table-construction algorithms in the LR(1) family, selected by %define lr.type (LR Table Construction manual):
lr.type | What it builds | States | Power vs. canonical LR(1) |
|---|---|---|---|
lalr (default) | DeRemer–Pennello LALR(1) — merged lookaheads | smallest | weaker: merging can introduce mysterious reduce/reduce conflicts |
ielr | Denny–Malloy IELR(1) | ≈ LALR-sized | full canonical-LR(1) language power, LALR-sized tables |
canonical-lr | Knuth's canonical LR(1) | largest (often 10×) | full power, but impractically large |
The manual frames ielr as the sweet spot: "given any grammar (LR or non-LR), parsers using IELR or canonical LR parser tables always accept exactly the same set of sentences. However, like LALR, IELR merges parser states during parser table construction so that the number of parser states is often an order of magnitude less than for canonical LR" (LR Table Construction). The original IELR(1) paper (Denny & Malloy, Science of Computer Programming 75(11), 2010) motivates it: "LALR sometimes generates parser tables that do not accept the full language that the grammar developer expects, but canonical LR is too inefficient to be practical particularly during grammar development."
Ambiguity handling is the discriminating axis:
A grammar is LALR(1) iff the merged tables have no conflicts. When they do, two resolution layers apply: (a) the two defaults (shift over reduce; earlier rule wins reduce/reduce), and (b) precedence/associativity. The manual (How Precedence) describes the rule precisely:
"The resolution of conflicts works by comparing the precedence of the rule being considered with that of the lookahead token. If the token's precedence is higher, the choice is to shift. If the rule's precedence is higher, the choice is to reduce. If they have equal precedence, the choice is made based on the associativity of that precedence level."
Johnson's original wording ties associativity to the action (
yacc.pdf): "left associative implies reduce, right associative implies shift, and nonassociating implies error." Each rule's precedence is, by default, that of its last terminal;%precoverrides it.Beyond LALR(1),
%glr-parserswitches to Generalized LR. The manual (GLR Parsers): when faced with an unresolved conflict, "GLR parsers use the simple expedient of doing both, effectively cloning the parser to follow both possibilities. … The parsers proceed in lockstep; that is, all of them consume (shift) a given input symbol before any of them proceed to the next." A split parser that reaches an impossible state dies; survivors that reach the same state merge. GLR handles "any context-free grammar for which the number of possible parses of any given string is finite" — i.e. genuine ambiguity is allowed, resolved by%dprec(static per-rule dynamic-precedence preference) or%merge(run both actions, then call a user merge function). The cost: "a GLR parser can take quadratic or cubic worst-case time, and the current Bison parser even takes exponential time and space for some grammars" (GLR). On conflict-free input GLR runs identically to the deterministic parser. This is the same general-parsing territory as Earley/GLL, reached from the LR side.
Interface & composition model
The grammar is an external DSL (.y file), compiled offline — the polar opposite of an embedded combinator library where the grammar is host-language values. The composition model has sharp consequences:
- No runtime composition. You cannot build a parser from smaller parser values at runtime; grammars compose only by textually combining rules and re-running Bison. There is no first-class "parser" object the way
nomor Parsec have. - AST/CST construction is manual and eager. There is no automatic tree. Each rule's action builds whatever the author writes — typically an AST node via
$$ = make_node($1, $3). The value stack is the only built-in data structure; the shape of the result is entirely the author's. (Contrast tree-sitter, which produces a uniform CST automatically, or ANTLR, which can auto-generate a visitor-walkable tree.) - Host-language integration via skeletons. The same grammar can target C (
yacc.c), C++ (lalr1.cc, with a realvariant-typed value stack instead of a Cunion), Java, or D, by swapping them4skeleton. The C++ skeleton additionally offers aparserclass rather than a globalyyparse. - Reentrancy is opt-in. Classic yacc uses global variables (
yylval,yychar, …), so a parser is neither reentrant nor thread-safe. Bison's%define api.pure fullthreads all state through parameters;%param/%lex-parampass extra context toyyparse/yylex. - Push vs. pull. By default
yyparsepulls tokens by callingyylex(the inversion-of-control Lemon objects to).%define api.push-pull push(orboth) instead generates ayypstateobject plusyypush_parse, which the caller feeds one token at a time — useful when the lexer (or a network read loop) must own the control flow.
Performance
Deterministic LR parsing is the performance high-water mark for general grammars:
- Time: O(n) in the input length. Each token is shifted exactly once and each reduction pops a bounded number of symbols; the total number of reductions is linear in the input. Every parser step is a small handful of array lookups into the packed action/goto tables — no backtracking, no memoization, no speculation on the deterministic path. This is asymptotically optimal and has a tiny constant factor, which is why Bison-class parsers underpin GCC's historical C parser, Bash, and countless interpreters.
- Space: O(n) stack in the worst case (deeply right-nested input), typically far less. The tables themselves are static and compact:
tables.cpacks the (sparse) action/goto matrices using the classic default-reduction + displacement scheme so most entries cost no storage. - No allocation on the hot path beyond growing the two stacks (a
reallocdoubling, rarely triggered). The C skeleton allocates nothing per token. - Zero-copy / streaming. The parser is pull-based and token-at-a-time, so it streams naturally; it never needs the whole input resident. Semantic values can be slices into a caller-owned buffer. There is, however, no SIMD or data-parallelism — LR parsing is inherently sequential (each action depends on the prior state), the antithesis of simdjson's branch-free vectorized stage. An absence worth stating: the LR model has no data-parallel formulation.
- GLR is the exception: as quoted above, ambiguous regions cost up to cubic (and pathologically exponential) time and space, because multiple parser stacks are kept alive. The deterministic regions stay linear.
Bison ships no canonical benchmark suite; its performance claim is the asymptotic one (linear, table-driven), and the practical evidence is its fifty-year deployment in latency-sensitive compilers. byacc historically advertised faster generated parsers than AT&T yacc (see Ecosystem), but the differences are constant-factor.
Error handling & recovery
Two distinct concerns: conflict diagnosis at generation time and syntax-error recovery at parse time.
- Generation-time diagnosis is the lineage's modern strength. Beyond the conflict counts, counterexamples (above) give a concrete witness string and the two derivations;
bison --report=alldumps the full automaton (parser.output) with item sets and lookaheads.%expect N/%expect-rr Nassert an expected conflict count so an unexpected conflict fails the build. - Parse-time recovery uses the special
errortoken. The grammar author writes rules likestmt: error ';'. On a syntax error, Bison callsyyerror, then pops the stack until it reaches a state that can shifterror, discards input tokens until one is acceptable, and resumes.yyerrokclears the "still recovering" flag;yyclearindiscards the current lookahead. This is coarse, author-driven panic-mode recovery — it works at statement boundaries but cannot, on its own, produce the fine-grained, locally-correct error messages or the partial trees that an IDE wants. - No incremental reparsing. The classic LR parser reparses from scratch; there is no built-in mechanism to reuse a previous parse after an edit. This is the decisive gap versus tree-sitter, which was designed for incremental, error-tolerant editor reparsing. The LR lineage is batch-oriented and not IDE-grade out of the box; its natural home is compilers and one-shot tools, not language servers. (This absence is itself the finding: the model predates the IDE era and optimizes for throughput on complete inputs.)
Ecosystem & maturity
The most mature, most deployed parsing technology in existence — and a whole genus of siblings:
| Tool | Origin / maintainer | Algorithm | Distinguishing trait |
|---|---|---|---|
| AT&T yacc | Stephen C. Johnson, Bell Labs, 1975 | LALR(1) | The original; ships in commercial Unix; AT&T proprietary license |
| GNU Bison | Robert Corbett → FSF (Demaille, Eggert, …) | LALR/IELR/canonical/GLR | The feature-rich superset surveyed here; C/C++/D/Java output; counterexamples |
Berkeley yacc (byacc) | Robert Corbett, ~1990; now Thomas Dickey (invisible-island) | LALR(1) | Public domain; faster parsers than AT&T yacc; optional reentrancy; strict yacc compatibility |
| BSD yacc | the BSD distributions' packaging of byacc | LALR(1) | The byacc lineage as shipped in *BSD; same public-domain core |
| Lemon | D. Richard Hipp (SQLite), 1990s | LALR(1) | Reentrant & thread-safe by construction; tokenizer-calls-parser; non-terminal destructors |
GNU Bison is the de-facto standard generator on free systems. Production users include Bash, the historical GCC C/C++ front ends (now hand-written, but Bison-derived), PostgreSQL's SQL grammar, the Ruby (
parse.y) and PHP grammars, GNUm4, and innumerable DSLs. It is GNU-coreutils-grade stable: the API has been backward-compatible for decades, and the special license exception keeps generated parsers freely licensable.byacc/BSD yacc trade Bison's feature surface for a tiny, strictly-yacc-compatible, public-domain core. Its three historical advantages over AT&T yacc were faster generated parsers, optional reentrant output, and the public-domain license. Thomas Dickey's maintained snapshots keep it ANSI-C-clean and tune its reentrant mode against Bison.Lemon is the most interesting divergence — SQLite's in-tree generator, used to build the SQL parser at the heart of the most-deployed database engine on earth. It keeps the LALR(1) algorithm but redesigns the runtime contract (
lemon.html):"Lemon also uses a parsing engine that is faster than yacc and bison and which is both reentrant and threadsafe. … The Lemon approach is reentrant and threadsafe, whereas Yacc uses global variables and is therefore neither."
Two design inversions drive that: control flow and globals. "In yacc and bison, the parser calls the tokenizer. In Lemon, the tokenizer calls the parser" — i.e. Lemon is push-only by design (cf. Bison's opt-in push mode). And "Lemon uses no global variables. … Lemon allows multiple parsers to be running simultaneously. Yacc and bison do not." The C interface is three functions:
cvoid *ParseAlloc(void *(*malloc)(size_t)); /* create a parser instance */ void Parse(void *p, int tokenID, TOKENTYPE t); /* feed one token (push) */ void ParseFree(void *p, void (*free)(void *)); /* destroy the instance */Lemon also addresses LR's classic memory-leak hazard — values on the stack when a parse is aborted by an error — with
%destructordirectives: "A non-terminal's destructor is called to dispose of the non-terminal's value whenever the non-terminal is popped from the stack." Its error recovery is the sameerror-token panic-mode as yacc, gated so the%syntax_errorcallback "will not be called again until at least three new tokens have been successfully shifted."Tooling. The classic pairing is
flex(lexer) + Bison (parser);bison -yprovides a yacc-compatibility mode. The lineage is taught from the Dragon Book (Aho, Lam, Sethi & Ullman, Compilers: Principles, Techniques, and Tools, 2nd ed., 2006) — whose cover knight literally wields a lance labelled "LALR parser generator" — making it the most pedagogically entrenched parsing technology.Ports/derivatives abound: CUP and Java CUP (Java, the original home of counterexample generation), PLY (Python), GPPG,
goyacc(Go),racc(Ruby), Menhir (OCaml, which generalizes Pager's minimal-LR(1) work that IELR(1) was measured against), and Bison's own C++/D/Java back ends.
Strengths
- Linear-time, table-driven, no backtracking on the deterministic path — asymptotically optimal with a tiny constant; ideal for compilers and one-shot batch tools.
- More powerful than LL for a fixed lookahead: left recursion is natural, and the parser commits only after seeing a full right-hand side.
- Declarative conflict resolution. Precedence/associativity (
%left/%right/%nonassoc/%prec) turn a grossly ambiguous expression grammar into a conflict-free one without rule refactoring. - Best-in-class conflict diagnostics since 3.8:
-Wcounterexamplesproduces a concrete witness input and both derivations — far better than a bare state number. - Full LR(1) power available via
%define lr.type ielrat near-LALR table size, escaping LALR's "mysterious" merge conflicts. - GLR escape hatch (
%glr-parser) for genuinely ambiguous or nondeterministic grammars, with%dprec/%mergeto resolve the ambiguity in semantics. - Multi-language output (C, C++, D, Java) from one grammar via swappable
m4skeletons. - Unmatched maturity & deployment: 50 years of production use, decades of API stability, ubiquitous packaging, and the canonical textbook treatment.
Weaknesses
- Conflicts are hard for beginners, even with counterexamples; LALR merge conflicts and reduce/reduce conflicts demand real understanding of the automaton.
- Separate lexer required. Token/character split adds a
flex(or hand-writtenyylex) dependency and a lexer-grammar boundary that scannerless tools avoid; lexer/parser feedback (the classic "lexer hack") is awkward. - No automatic AST/CST. Tree building is entirely manual via
$$/$nactions; there is no uniform parse tree, no built-in visitor. - Not IDE-grade. No incremental reparsing, only coarse
error-token panic-mode recovery, no partial trees — the wrong tool for a language server (use tree-sitter). - Globals & non-reentrancy by default in the classic C output; reentrancy/thread-safety is opt-in (
api.pure) — Lemon andbyaccfix this differently. - No runtime composition. Grammars are static text compiled offline; you cannot build parsers from smaller parser values like a combinator library.
- Inherently sequential: no SIMD/data-parallel formulation; GLR's ambiguity handling can degrade to cubic or exponential time/space.
- Stagnant pace: the latest stable release is 3.8.2 (2021); the model is essentially complete, which is a strength for stability but means little modern (incremental/error-recovery) innovation flows into it.
Key design decisions and trade-offs
| Decision | Rationale | Trade-off |
|---|---|---|
| Bottom-up LR (vs. top-down LL) | Strictly more grammars per fixed lookahead; left recursion is natural | Conflicts are less intuitive; the automaton is opaque to grammar authors |
| LALR(1) tables by default | Smallest tables; fast generation; adequate for most languages | Merging lookaheads creates "mysterious" reduce/reduce conflicts on some valid LR(1) grammars |
| Precedence/associativity declarations resolve conflicts | Expression grammars stay readable and ambiguous-but-resolved instead of refactored | Misapplied precedence silently hides real grammar bugs; rule precedence = "last terminal" surprises |
Separate lexer (yylex), parser-pulls-tokens | Clean two-phase model; reuse lex/flex; tokens not characters keep the grammar small | Not scannerless; lexer/parser boundary friction; non-reentrant globals by default |
Manual $$/$n semantic actions over a %union value stack | Maximum flexibility — build any AST, evaluate inline, no imposed tree shape | No automatic CST; tree building is boilerplate; type-unsafe C union (C++ skeleton mitigates) |
| Default "shift over reduce" + "earlier rule wins" | Sensible defaults (dangling-else attaches to innermost if) with zero author effort | Silent resolution can mask an unintended ambiguity if the author never reads the warnings |
%glr-parser as an opt-in for ambiguity | One flag unlocks full general (CFG) parsing power while keeping LR speed on clean input | Up to cubic / pathologically exponential time & space; needs %dprec/%merge for semantics |
IELR(1) opt-in (lr.type=ielr) instead of canonical LR default | Full LR(1) power at LALR-sized tables; canonical LR is impractically large | Not the default (LALR is), so authors must know to ask for it; extra generation cost |
| Offline generation to static integer tables | Linear-time, allocation-free hot loop; trivially embeddable C | No runtime grammar composition; every grammar change means re-running Bison |
Sources
- Stephen C. Johnson, Yacc: Yet Another Compiler-Compiler, Bell Labs CSTR 32, 1975 — origin, LALR(1)+disambiguating-rules class,
$$/$n,yylex, the two default disambiguating rules - GNU Bison manual — the authoritative reference; introduction, algorithm, precedence, GLR, counterexamples, LR table types
- The Bison Parser Algorithm (mirror) — bottom-up / shift-reduce / parser-stack description
- Shift/Reduce Conflicts — default shift; dangling-else
- How Precedence works (mirror) — rule-vs-token precedence comparison
- GLR Parsers (mirror) — split/merge,
%dprec/%merge, complexity - LR Table Construction (mirror) —
lr.type= lalr/ielr/canonical-lr - Counterexamples —
-Wcounterexamples, unifying vs nonunifying, the dangling-else output
- GNU Bison source tree (
git.savannah.gnu.org) —lalr.c,ielr.c,lr0.c,conflicts.c,counterexample.c,tables.c,parse-gram.y - Joel E. Denny & Brian A. Malloy, IELR(1), Science of Computer Programming 75(11), 2010 — minimal-LR(1) tables with full LR(1) power
- Chinawat Isradisaikul & Andrew Myers, Finding Counterexamples from Parsing Conflicts, PLDI 2015 — the counterexample algorithm Bison adopted
- SQLite Lemon documentation (
lemon.html) — reentrant/thread-safe, tokenizer-calls-parser,ParseAlloc/Parse/ParseFree,%destructor - Berkeley yacc (
byacc), Thomas Dickey / invisible-island — public-domain LALR(1) yacc, reentrant mode - GNU Bison home — release status (3.8.2, 2021)
- Aho, Lam, Sethi & Ullman, Compilers: Principles, Techniques, and Tools (2nd ed., 2006) — the Dragon Book; canonical LALR/bottom-up treatment
- Related deep-dives: bottom-up parsing theory · general parsing (Earley/GLL/GLR) · ANTLR (top-down) · Menhir (OCaml LR) · tree-sitter (incremental) · comparison