Machine-Checked Grammar-Constrained Decoding

A Lean 4 formalization of grammar-constrained decoding (GCD), following the algorithm of Flexible and Efficient Grammar-Constrained Decoding (Park et al.). It builds an executable next-token checker by composing a detokenizing lexer transducer with a pushdown parser, and proves that the checker is sound and complete: after any token prefix it allows exactly the next tokens that can still be extended to a grammatical output.

Highlights

  • An end-to-end machine-checked correctness theorem (GCDChecker_correct) for an executable GCD checker, under a small, explicit bundle of assumptions.
  • A bug found in the published lexing construction. The original lexer has no distinguished start state, so it accepts end-of-stream whenever the lexing automaton happens to return to its start state, i.e. in the middle of an unfinished terminal. The regex (ab)*a triggers it. We give a corrected construction with a separate start state and verify it.
  • The assumptions made precise. The paper leaves several conditions implicit. The one that does real work is the universal separator: a distinguished whitespace terminal that ends any lexeme and is a no-op for the parser. It is what collapses the otherwise unbounded set of lexer continuations to a finite check.
  • Reusable infrastructure with no GCD-specific dependencies: partial finite automata and transducers with executable composition (Automata.lean), nondeterministic pushdown automata with stack lemmas and a stack-forgetting NFA over-approximation (PDA.lean), and a verified finite graph search (Producible.lean).

Building

Requires Lean 4, toolchain leanprover/lean4:v4.29.0-rc6 (see lean-toolchain); mathlib is pinned in lake-manifest.json.

lake exe cache get   # prebuilt mathlib oleans, recommended before the first build
lake build ConstrainedDecodingFormalization ConstrainedDecodingFormalization.GCDTest

The development contains no sorry and declares no axioms.

The pipeline

An LLM emits tokens; a grammar is defined over terminals that a lexer groups from characters. The target semantics is the composition

tokens --detokenize--> characters --lex--> terminals --parse--> accept

A complete token sequence is valid exactly when this pipeline succeeds. The checker decides, incrementally, whether a candidate token keeps that pipeline completable.

  1. Lexing. BuildLexingFST compiles a lexer specification (a character automaton labeled with terminals) into a one-lookahead maximal-munch transducer. Lexing/Correctness.lean proves it equivalent to the relational specification PartialLexRel.
  2. Detokenization. BuildDetokenizingFST flattens tokens to characters; composing it with the lexer gives BuildDetokLexer, driven directly by tokens.
  3. Realizable tails. Modulo whitespace, the terminal sequences the lexer can still produce from a state are exactly those whose first terminal is single-producible there. This is the finiteness result the checker rests on.
  4. Tables. BuildInverseTokenSpannerTable records, per lexer state, the realizable sequence heads each token exposes and inverts that map back to tokens. PreprocessParser sorts heads into always-allowed, always-rejected, and stack-dependent using stack invariance and NFA over-approximation.
  5. Online mask. ComputeValidTokenMask seeds the mask with the always-allowed tokens and tests each stack-dependent head against the live parser configuration. GCDChecker wires this to a prefix evaluation.

Module map

FileRole
Char.leanEOS-extended alphabet ExtChar α (abbrev Ch α)
Language.leanPrefix closure Language.prefixes, bridging to Mathlib.Computability.Language
Automata.leanPartial deterministic FSA and FST, executable composition, mathlib DFA/NFA conversions
PDA.leanPushdown automaton, stack semantics, evalFrom, toNFA over-approximation
Producible.leanDepth-first search for single-producible terminals, with its correctness proof
Vocabulary.leanVocabulary α β typeclass: tokens to character strings, with the singleton-token law
Lexing/Base.leanLexer specs, the partial lexer PartialLex/PartialLexRel, and BuildLexingFST
Lexing/Correctness.leanEquivalence of partial lexing, the relational lexer, and the lexing FST
Lexing/Detokenizing.leanDetokenizing FST and its composition BuildDetokLexer
Lexing/Whitespace.leanWhitespace-exchange lemmas and the realizable-tail characterization
Lexing.leanCompatibility import for the four Lexing/ modules
RealizableSequence.leanRealizable sequence heads and the inverse token-spanner table
Checker.leanThe executable Checker β interface and its language-level semantics
ParserWithEOS.leanEOS-augmented parser used when lexer output carries an end marker
GCDAssumptions.leanThe GCDAssumptions bundle, including the universal-separator condition
GCDAlgorithm.leanPreprocessParser, ComputeValidTokenMask, and the executable GCDChecker
GCDStepProofs.leanStep-level mask correctness: Soundness, Completeness, EOSCompleteness
GCDCheckerLanguage.leanBridge to checkerLanguage = TargetLanguage
GCDProductivity.leanProductivity, path independence, and the final GCDChecker_correct
GrammarConstrainedDecoding.leanCompatibility import for the GCD proof stack
GCDTest.leanA finite JSON-like grammar with all assumptions discharged (jsonChecker_correct)

Main theorems

TheoremStatement
GCDChecker_correctThe checker satisfies the full checkerCorrect interface: EOS is allowed iff the prefix is in the target language, and every allowed token sequence is a prefix of some target-language word.
GCDChecker_checkerLanguage_eq_TargetLanguageThe accepted token language of GCDChecker spec P equals the lexer/parser target language.
GCDChecker_intermediateLanguage_eq_TargetLanguage_prefixesThe prefixes the checker allows equal the prefix closure of the target language.
GCDChecker_productiveEvery incrementally allowed prefix extends to an accepted word.
GCDChecker_pathIndependentThe checker depends only on the flattened character content of the prefix.
Soundness / Completeness / EOSCompletenessStep level: a token's mask bit is true iff a viable continuation exists through the composed FST and parser.
computeSingleProducible_correctThe executable DFS enumerates exactly the single-producible terminals.
mem_ComputeValidTokenMask_preprocess_iffSemantic membership characterization of the online mask.

Assumptions

The final theorems are parameterized by one package, GCDAssumptions spec P tnonwhite twhite qnonwhite qwhite:

structure GCDAssumptions
    (spec : LexerSpec α Γ σa) (P : PDA Γ π σp)
    (tnonwhite twhite : α) (qnonwhite qwhite : σa) : Prop where
  hempty        : [] ∉ spec.automaton.accepts
  lexer_pruned  : spec.automaton.pruned
  parser_pruned : P.pruned
  whitespace    : GCDWhitespaceAssumption spec P tnonwhite twhite qnonwhite qwhite
  • No empty lexeme (hempty). The lexer automaton's start state is not accepting, so no lexeme is empty.
  • Lexer prunedness. Every reachable lexer state can still reach an accepting state. Used by the realizable-tail argument.
  • Parser prunedness. Every reachable parser configuration has an accepted continuation.
  • Universal separator (GCDWhitespaceAssumption). The condition is organized around a distinguished whitespace character twhite (with a witnessing non-whitespace character tnonwhite, and the lexer states qwhite/qnonwhite they lead to). On the lexer side, twhite belongs to no lexeme other than the whitespace terminal, so it always ends the preceding lexeme and returns the lexer to a clean post-separator state. On the parser side, ParserIgnoresTerminal holds: every state reads the whitespace terminal with the identity transition.
  • Singleton tokens. Carried by the Vocabulary α β instance: every single character is itself a token (flatten (embed a) = [a]) and no token flattens to nothing (flatten b ≠ []).

To instantiate the end-to-end theorem for a grammar, supply finite/enumerable alphabets and states, a Vocabulary instance, and a proof of GCDAssumptions. The generic theorems are not reproved. GCDTest.lean does this for a shallow JSON grammar, using newline as the separator; native_decide discharges the finite side conditions there.

Dependency visualizer

An interactive declaration dependency graph is at ucsd-formal.github.io/constrained-decoding-formalization.

./lean-dep-viz serve                     # serve at localhost:3000
./lean-dep-viz build --output-dir site   # generate a static site

Paper-to-formalization reference

Maps definitions, algorithms, and results from Park et al. to their Lean counterparts.

Structures and definitions

PaperNotationLeanFile
EOS-extended alphabetΣ ∪ {EOS}ExtChar α (abbrev Ch α)Char.lean
Finite-state automaton(Σ, Q, q₀, δ, F)FSA α σAutomata.lean
Finite-state transducer(Σ, Γ, Q, q₀, δ, F)FST α Γ σAutomata.lean
Pushdown automaton(Σ, Π, Q, q₀, Z₀, δ, F)PDA Γ π σPDA.lean
Lexer specificationautomaton + terminal label per classLexerSpec α Γ σLexing/Base.lean
Token vocabularyV ⊆ Σ⁺Vocabulary α βVocabulary.lean
Grammar languageL(G)PDA.acceptsPDA.lean
Prefix languageprefixes of L(G)Language.prefixesLanguage.lean
Single-producible terminals (Def. C.1)Prod(q)FST.singleProducible qProducible.lean
Realizable sequence heads (Def. 3.2)ReRealizableSequenceHeads fst_compRealizableSequence.lean
Realizable terminal sequencesFST.realizableSequences qAutomata.lean
Inverse token-spanner table (Def. 3.3)T_inv(q, a)InverseTokenSpannerTable fst_compRealizableSequence.lean
Always-allowed tokensA(q_lex, q_parse)PPTable first componentGCDAlgorithm.lean
Stack-dependent headsD(q_lex, q_parse)PPTable second componentGCDAlgorithm.lean
CheckerCChecker βChecker.lean
GCD target languageLex-language of GTargetLanguage spec PGCDCheckerLanguage.lean

Algorithms

PaperLeanFile
Alg. 1: ConstrainedDecodingGCDChecker spec PGCDAlgorithm.lean
Alg. 2: BuildLexingFSTBuildLexingFST specLexing/Base.lean
Alg. 3: BuildDetokenizingFSTBuildDetokenizingFSTLexing/Detokenizing.lean
FST composition (detok ∘ lex)Detokenizing.BuildDetokLexer specLexing/Detokenizing.lean
Alg. 4: BuildInverseTokenSpannerTableBuildInverseTokenSpannerTable fst_compRealizableSequence.lean
Alg. 5: PreprocessParserPreprocessParser fst_comp PGCDAlgorithm.lean
Alg. 6: ComputeValidTokenMaskComputeValidTokenMask P itst table qa qp stGCDAlgorithm.lean
Partial lexer (Lex)PartialLex specLexing/Base.lean
PDA → NFA over-approximationPDA.toNFAPDA.lean
DFS for single-producible terminalsFST.computeSingleProducible qProducible.lean

Propositions and theorems

Paper resultLeanFile
Stack invariance (Prop. 3.1)PDA.stackInvariancePDA.lean
Over-approximation via FSA (Prop. 3.2)PDA.overApproximationPDA.lean
Lexer-FST equivalence (Thm. C.1)PartialLex_to_LexingFST, LexingFST_to_PartialLexRelLexing/Correctness.lean
Single-producibility (Lemma C.3)computeSingleProducible_correctProducible.lean
Valid-mask characterizationmem_ComputeValidTokenMask_preprocess_iffGCDStepProofs.lean
Soundness (Thm. C.4)SoundnessGCDStepProofs.lean
Completeness (Thm. C.5)Completeness, EOSCompletenessGCDStepProofs.lean
Mask ⇒ viable continuationaccept_if_ComputedValidTokenMaskGCDStepProofs.lean
checkerLanguage = target languageGCDChecker_checkerLanguage_eq_TargetLanguageGCDCheckerLanguage.lean
Checker productivityGCDChecker_productiveGCDProductivity.lean
Checker path independenceGCDChecker_pathIndependentGCDProductivity.lean
Full checker interfaceGCDChecker_correctGCDProductivity.lean

Type parameters

VariableRolePaper
αCharacter / input alphabetΣ
βToken alphabetV
ΓTerminal / output alphabetΓ
πStack alphabetΠ
σ, σa, σpAutomaton / parser state typesQ

Most carry FinEnum, DecidableEq, or BEq/LawfulBEq instances.

License

Apache License 2.0. See LICENSE.