metrale_grammar/matcher/
matcher.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//
3// GrammarMatcher — per-request grammar-matching state.
4//
5// Port of `class GrammarMatcher` / `GrammarMatcher::Impl` from
6// `cpp/grammar_matcher.cc` + `include/xgrammar/matcher.h`. This file
7// holds the struct, construction, token/string acceptance, rollback,
8// termination and reset. The bitmask fill and jump-forward search
9// (the long methods) live in the sibling `fill.rs`.
10//
11// In the C++ the `Impl` *inherits* `EarleyParser`; the Rust port
12// composes one instead (`parser` field) — the parser already exposes
13// `advance`, `pop_last_states`, `push_one_state_to_check`,
14// `is_completed`, `latest_scanable_states` and `reset`.
15//
16// Termination: the C++ tracks `stop_token_is_accepted_` on the parser
17// base. The Rust `EarleyParser` clears its own such flag on every
18// `pop_last_states`, which is *not* what the matcher wants (a rollback
19// past a stop token un-terminates, but a rollback that does not reach
20// the stop step must keep it). The matcher therefore owns its own
21// `stop_token_accepted` flag, undone precisely by `rollback`.
22
23use std::collections::VecDeque;
24use std::sync::Arc;
25
26use crate::compiler::{AdaptiveTokenMask, CompiledGrammar};
27use crate::earley::{EarleyParser, NO_PREV_INPUT_POS, ParserState};
28use crate::tokenizer::TokenizerInfo;
29
30/// Reusable scratch buffers for [`GrammarMatcher::fill_next_token_bitmask`].
31///
32/// `fill_next_token_bitmask` / `compute_partitions` run once per decoded
33/// token. Allocating their working sets fresh each call costs ~2.3 MB of
34/// heap traffic per token (a `vocab_size` bool vec, a clone of the
35/// ~1.5 MB sorted decoded vocab, etc). The C++ matcher instead keeps
36/// member buffers and `.clear()`s them. This struct mirrors that: it is
37/// held on the matcher, `std::mem::take`-n during the fill, `.clear()`ed
38/// and reused, then restored.
39#[derive(Debug, Default)]
40pub(super) struct FillScratch {
41    /// `accepted[token_id]` — union of every state's accepted set.
42    pub(super) accepted: Vec<bool>,
43    /// Running rejected intersection; `{-1}` is the universal set.
44    pub(super) rejected: Vec<i32>,
45    /// Snapshot of the parser's latest scanable states (`Copy`), taken
46    /// once so no parser borrow is held across the trial-loop mutation.
47    pub(super) live_states: Vec<ParserState>,
48    /// Per-live-state `(state, mask)` pairs resolved for this fill.
49    pub(super) states: Vec<(ParserState, Arc<AdaptiveTokenMask>)>,
50    /// Per-state delta of newly rejected uncertain-token indices.
51    pub(super) rejected_delta: Vec<i32>,
52    /// Reusable packed-bitmask buffer for the Coalescence fast-path
53    /// (`forced_token` / `next_forced_tokens`). Sized to
54    /// `bitmask_size(vocab_size)` on first use and reused thereafter, so
55    /// the forced-token check costs no per-call heap allocation.
56    pub(super) coalesce_bitmask: Vec<i32>,
57}
58
59/// A stateful matcher that matches sampled tokens against a compiled
60/// grammar — the core of grammar-guided generation.
61///
62/// One `GrammarMatcher` tracks one request's decoding state. Each
63/// decode step Metrale Engine calls [`Self::fill_next_token_bitmask`] to learn
64/// which tokens are legal, samples one, then [`Self::accept_token`].
65/// [`Self::rollback`] undoes accepted tokens (e.g. for speculative
66/// decoding); [`Self::reset`] returns to the initial state.
67#[derive(Debug)]
68pub struct GrammarMatcher {
69    /// The compiled grammar (shared, cheap to clone). `pub(super)` so
70    /// the `fill` hot path can borrow it as a field disjoint from
71    /// `parser` and `scratch` (the borrow checker needs the concrete
72    /// field, not the whole-`self` accessor).
73    pub(super) compiled_grammar: CompiledGrammar,
74    /// The Earley parser driving byte-level matching.
75    pub(super) parser: EarleyParser,
76    /// Token ids that terminate generation. Either the override set or
77    /// the tokenizer's detected stop tokens.
78    stop_token_ids: Vec<i32>,
79    /// When true the matcher completes (terminates) as soon as the
80    /// root rule is matched, without requiring a stop token.
81    terminate_without_stop_token: bool,
82    /// True once a stop token has been accepted — the matcher is then
83    /// terminated. Undone by [`Self::rollback`] past the stop step.
84    stop_token_accepted: bool,
85    /// Byte length consumed by each accepted token / string, in order.
86    /// One entry per `accept_token`/`accept_string` call; the rollback
87    /// unit. A stop token contributes a `0`-length entry.
88    token_length_history: VecDeque<usize>,
89    /// Reusable working buffers for the per-token bitmask fill. Taken
90    /// (`std::mem::take`) for the duration of the fill and restored
91    /// before return — see [`FillScratch`].
92    pub(super) scratch: FillScratch,
93}
94
95impl GrammarMatcher {
96    /// Construct a matcher from a [`CompiledGrammar`].
97    ///
98    /// * `override_stop_tokens` — when `Some`, replaces the tokenizer's
99    ///   detected stop tokens; must be non-empty (panics otherwise,
100    ///   matching the C++ `XGRAMMAR_CHECK`).
101    /// * `terminate_without_stop_token` — terminate on root-rule
102    ///   completion without needing a stop token.
103    /// * `max_rollback_tokens` — accepted for API faithfulness; the C++
104    ///   deprecated and ignores it (rollback is unbounded).
105    ///
106    /// Port of the `GrammarMatcher` constructor.
107    #[must_use]
108    pub fn new(
109        compiled_grammar: CompiledGrammar,
110        override_stop_tokens: Option<Vec<i32>>,
111        terminate_without_stop_token: bool,
112        max_rollback_tokens: i32,
113    ) -> Self {
114        let _ = max_rollback_tokens; // deprecated & unused, kept for ABI parity.
115        if let Some(ref ovr) = override_stop_tokens {
116            assert!(!ovr.is_empty(), "override_stop_tokens must not be empty");
117        }
118        let stop_token_ids = override_stop_tokens
119            .unwrap_or_else(|| compiled_grammar.tokenizer_info().stop_token_ids().to_vec());
120        let parser = EarleyParser::from_grammar(compiled_grammar.grammar_arc());
121        Self {
122            compiled_grammar,
123            parser,
124            stop_token_ids,
125            terminate_without_stop_token,
126            stop_token_accepted: false,
127            token_length_history: VecDeque::new(),
128            scratch: FillScratch::default(),
129        }
130    }
131
132    /// Convenience constructor with default options (no stop-token
133    /// override, requires a stop token, unbounded rollback).
134    #[must_use]
135    pub fn from_compiled_grammar(compiled_grammar: CompiledGrammar) -> Self {
136        Self::new(compiled_grammar, None, false, -1)
137    }
138
139    /// The compiled grammar this matcher runs.
140    #[must_use]
141    pub fn compiled_grammar(&self) -> &CompiledGrammar {
142        &self.compiled_grammar
143    }
144
145    /// The tokenizer info the grammar was compiled against.
146    #[must_use]
147    pub fn tokenizer_info(&self) -> &TokenizerInfo {
148        self.compiled_grammar.tokenizer_info()
149    }
150
151    /// Canonicalize a *live* parser state into the cache key the
152    /// compiler's mask cache is keyed by.
153    ///
154    /// The parser's live scanable states carry real positions
155    /// (`rule_start_pos`, `sub_element_id`, `repeat_count`,
156    /// `partial_codepoint`), but the adaptive-token-mask cache is
157    /// position-agnostic — the C++ `adaptive_token_mask_cache` hashes
158    /// by `StateHashForCache`. This reproduces the exact tuple the
159    /// eager compiler used as the cache key:
160    /// `(rule_id, body_expr_id, element_id, NO_PREV_INPUT_POS, 0)`.
161    /// The result is fed to [`CompiledGrammar::get_or_compute_mask`].
162    pub(super) fn canonical_mask_state(&self, live: &ParserState) -> ParserState {
163        let body_expr_id = self
164            .compiled_grammar
165            .grammar()
166            .rule(live.rule_id)
167            .body_expr_id;
168        ParserState::new(
169            live.rule_id,
170            body_expr_id,
171            live.element_id,
172            NO_PREV_INPUT_POS,
173            0,
174        )
175    }
176
177    /// The stop token ids that terminate this matcher.
178    #[must_use]
179    pub fn stop_token_ids(&self) -> &[i32] {
180        &self.stop_token_ids
181    }
182
183    /// The maximum number of rollback tokens — always `-1` (unbounded),
184    /// matching the deprecated C++ behavior.
185    #[must_use]
186    pub fn max_rollback_tokens(&self) -> i32 {
187        -1
188    }
189
190    /// True if the matcher has terminated. When
191    /// `terminate_without_stop_token` is set this is "root rule
192    /// completed"; otherwise it is "a stop token was accepted".
193    ///
194    /// Port of `IsTerminated`.
195    #[must_use]
196    pub fn is_terminated(&self) -> bool {
197        if self.terminate_without_stop_token {
198            self.parser.is_completed()
199        } else {
200            self.stop_token_accepted
201        }
202    }
203
204    /// True once an actual stop token has been accepted — distinct
205    /// from [`Self::is_terminated`], which also fires on root-rule
206    /// completion under `terminate_without_stop_token`. The bitmask
207    /// fill and jump-forward search guard on this flag specifically.
208    #[must_use]
209    pub(super) fn stop_token_accepted(&self) -> bool {
210        self.stop_token_accepted
211    }
212
213    /// True if the root rule has matched at the current position — a
214    /// stop token may be emitted now. Exposed for callers that need the
215    /// raw completion state independently of termination policy.
216    #[must_use]
217    pub fn is_grammar_completed(&self) -> bool {
218        self.parser.is_completed()
219    }
220
221    /// Try to accept the stop token, terminating the matcher.
222    /// Returns whether the stop token was acceptable. Port of
223    /// `AcceptStopToken`.
224    fn accept_stop_token(&mut self) -> bool {
225        if self.terminate_without_stop_token {
226            return false;
227        }
228        if !self.parser.is_completed() {
229            return false;
230        }
231        debug_assert!(!self.stop_token_accepted);
232        self.token_length_history.push_back(0);
233        self.stop_token_accepted = true;
234        true
235    }
236
237    /// Accept one sampled token and advance the matcher.
238    ///
239    /// Returns `false` (without mutating state) if the token is not
240    /// grammar-legal, is out of range, is a special token, or the
241    /// matcher has already terminated. A stop token terminates the
242    /// matcher. Port of `AcceptToken`.
243    pub fn accept_token(&mut self, token_id: i32, debug: bool) -> bool {
244        let _ = debug;
245        if self.stop_token_accepted {
246            return false;
247        }
248        let vocab_size = self.tokenizer_info().vocab_size() as i32;
249        if token_id < 0 || token_id >= vocab_size {
250            return false;
251        }
252        if self.stop_token_ids.contains(&token_id) {
253            return self.accept_stop_token();
254        }
255        if self
256            .tokenizer_info()
257            .special_token_ids()
258            .contains(&token_id)
259        {
260            return false;
261        }
262        // Decode the token to bytes and feed them one by one.
263        let token = self.tokenizer_info().decoded_vocab()[token_id as usize].clone();
264        self.advance_bytes_as_step(&token)
265    }
266
267    /// Accept a literal string, advancing the matcher byte by byte.
268    /// The whole string counts as one rollback step. Used to seed a
269    /// matcher or in tests. Port of `AcceptString`.
270    pub fn accept_string(&mut self, s: &str, debug: bool) -> bool {
271        self.accept_bytes(s.as_bytes(), debug)
272    }
273
274    /// Accept a literal byte string — the byte-level form of
275    /// [`Self::accept_string`] (token bytes are not always UTF-8).
276    pub fn accept_bytes(&mut self, bytes: &[u8], debug: bool) -> bool {
277        let _ = debug;
278        if self.stop_token_accepted {
279            return false;
280        }
281        self.advance_bytes_as_step(bytes)
282    }
283
284    /// Advance the parser over `bytes` as a single rollback step.
285    ///
286    /// On a rejected byte the bytes already consumed are rolled back so
287    /// the matcher state is unchanged — exactly as the C++
288    /// `AcceptToken` / `AcceptString` do. Returns whether all bytes
289    /// were accepted; on success one history entry is recorded.
290    fn advance_bytes_as_step(&mut self, bytes: &[u8]) -> bool {
291        for (consumed, &byte) in bytes.iter().enumerate() {
292            if !self.parser.advance(byte) {
293                if consumed > 0 {
294                    self.parser.pop_last_states(consumed);
295                }
296                return false;
297            }
298        }
299        self.token_length_history.push_back(bytes.len());
300        true
301    }
302
303    /// Roll back the matcher by `n` accepted tokens / strings.
304    ///
305    /// Each prior `accept_token`/`accept_string` is one unit; a stop
306    /// token is a zero-byte unit whose rollback un-terminates the
307    /// matcher. Panics if `n` exceeds the recorded history. Port of
308    /// `Rollback`.
309    pub fn rollback(&mut self, n: i32) {
310        let n = n.max(0) as usize;
311        assert!(
312            n <= self.token_length_history.len(),
313            "cannot roll back {n} tokens: only {} steps recorded",
314            self.token_length_history.len()
315        );
316        for _ in 0..n {
317            let steps = self
318                .token_length_history
319                .pop_back()
320                .expect("history checked non-empty");
321            if steps == 0 {
322                // The stop-token step: un-terminate, no bytes consumed.
323                self.stop_token_accepted = false;
324            } else {
325                self.parser.pop_last_states(steps);
326            }
327        }
328    }
329
330    /// Reset the matcher to its initial state.
331    pub fn reset(&mut self) {
332        self.parser.reset();
333        self.stop_token_accepted = false;
334        self.token_length_history.clear();
335    }
336
337    /// Number of accepted tokens / strings currently in the rollback
338    /// history.
339    #[must_use]
340    pub fn num_history_steps(&self) -> usize {
341        self.token_length_history.len()
342    }
343}