Skip to main content

antlr4_runtime/atn/
lexer_dfa.rs

1// SPDX-License-Identifier: BSD-3-Clause
2// Copyright (c) 2026 Konstantin Vyatkin
3//! Ahead-of-time lexer DFA compilation.
4//!
5//! ANTLR runtimes normally discover the lexer DFA lazily: the ATN simulator
6//! computes epsilon closures per input character and caches the resulting
7//! config sets. This module runs the same subset construction eagerly over
8//! the entire character alphabet, once per grammar, so token matching becomes
9//! one table lookup per character with no closure computation, hashing, or
10//! config allocation on the hot path.
11//!
12//! Compilation is conservative at the edge level: a transition whose target
13//! closure crosses a semantic predicate (whose outcome exists only at parse
14//! time), grows an unbounded caller-context path (recursive lexer rules such as
15//! nested comments), or exceeds the state budget is compiled as an *escape*
16//! edge. Dynamic closures carry their pre-closure configs so the interpreter
17//! resumes from the narrowed edge instead of re-matching from the token start.
18//! Because the construction reuses the interpreter's own closure, pruning, and
19//! accept-selection code, a compiled walk that does not escape reproduces
20//! interpreter behavior exactly.
21
22use std::collections::HashMap;
23use std::hash::BuildHasherDefault;
24
25use memchr::{memchr, memchr2, memchr3};
26
27#[cfg(feature = "perf-counters")]
28use crate::atn::ascii_range::AsciiRangeClass;
29use crate::atn::ascii_range::{self, AsciiRanges};
30use crate::atn::lexer::{
31    LexerConfig, best_accept, epsilon_closure, lexer_action_belongs_to_accept, prune_after_accepts,
32    set_config_state,
33};
34use crate::atn::{LexerAtn, LexerTransition};
35use crate::int_stream::EOF;
36use crate::lexer::{
37    EMPTY_LEXER_CONTEXT, LexerContextArena, LexerContextId, LexerContextNode, LexerDfaActionKey,
38    LexerDfaConfigKey, LexerDfaKey,
39};
40use crate::prediction::{PredictionFxHasher, PredictionWorkspace};
41
42#[allow(clippy::disallowed_types)]
43type FxHashMap<K, V> = HashMap<K, V, BuildHasherDefault<PredictionFxHasher>>;
44
45const MIN_CHAR_VALUE: i32 = 0;
46const MAX_CHAR_VALUE: i32 = 0x0010_FFFF;
47
48/// Sentinel state id meaning "no transition".
49pub(super) const DEAD_STATE: u16 = u16::MAX;
50
51/// Sentinel state id meaning "hand this edge to the ATN interpreter".
52pub(super) const ESCAPE_STATE: u16 = u16::MAX - 1;
53
54/// Per-mode state budget; targets past it compile as escape edges. The cap
55/// also bounds compile time for pathological grammars.
56const MAX_MODE_STATES: usize = 4096;
57
58/// Caller-context paths deeper than this escape to the interpreter, as a backstop
59/// for grammars with extraordinarily long non-recursive fragment chains.
60const MAX_CONTEXT_DEPTH: usize = 32;
61
62/// Configs whose surviving action trace grows past this escape to the
63/// interpreter: a custom action crossed inside a loop is genuinely
64/// position-dependent and cannot compile to finitely many DFA states.
65const MAX_ACTION_TRACES: usize = 16;
66
67/// Dense per-state edge row width, matching the interpreter's DFA cache rows.
68const ASCII_EDGE_SYMBOLS: usize = 128;
69/// [`ASCII_EDGE_SYMBOLS`] as a code point for segment arithmetic.
70const ASCII_EDGE_LIMIT: i32 = 128;
71
72/// Exact self-loop shape for one compiled state's ASCII row.
73#[derive(Clone, Copy, Debug, Eq, PartialEq)]
74pub(super) enum AsciiRun {
75    None,
76    Any,
77    Until1(u8),
78    Until2(u8, u8),
79    Until3(u8, u8, u8),
80    Ranges(AsciiRanges),
81}
82
83impl AsciiRun {
84    fn classify(row: &[u16; ASCII_EDGE_SYMBOLS], state: u16) -> Self {
85        let mut exits = [0_u8; 3];
86        let mut count = 0;
87        for (symbol, &target) in row.iter().enumerate() {
88            if target == state {
89                continue;
90            }
91            if count < exits.len() {
92                exits[count] = u8::try_from(symbol).expect("symbol overflow");
93            }
94            count += 1;
95        }
96        match count {
97            0 => Self::Any,
98            1 => Self::Until1(exits[0]),
99            2 => Self::Until2(exits[0], exits[1]),
100            3 => Self::Until3(exits[0], exits[1], exits[2]),
101            _ => AsciiRanges::from_self_loops(row, state).map_or(Self::None, Self::Ranges),
102        }
103    }
104
105    fn scan(self, input: &[u8]) -> Option<AsciiRunScan> {
106        let exit = match self {
107            Self::None => return None,
108            Self::Any => None,
109            Self::Until1(a) => memchr(a, input),
110            Self::Until2(a, b) => memchr2(a, b, input),
111            Self::Until3(a, b, c) => memchr3(a, b, c, input),
112            Self::Ranges(ranges) => {
113                let bytes = ascii_range::scan_scalar(ranges, input);
114                return Some(AsciiRunScan {
115                    bytes,
116                    found_exit: bytes != input.len(),
117                    #[cfg(any(feature = "perf-counters", test))]
118                    range: Some(AsciiRangeScan { ranges }),
119                });
120            }
121        };
122        Some(AsciiRunScan {
123            bytes: exit.unwrap_or(input.len()),
124            found_exit: exit.is_some(),
125            #[cfg(any(feature = "perf-counters", test))]
126            range: None,
127        })
128    }
129
130    const fn serialized_words(self) -> usize {
131        if matches!(self, Self::Ranges(_)) {
132            3
133        } else {
134            1
135        }
136    }
137
138    fn write_serialized(self, out: &mut Vec<u32>) {
139        match self {
140            Self::None => out.push(0),
141            Self::Any => out.push(1),
142            Self::Until1(a) => out.push(2 | (u32::from(a) << 8)),
143            Self::Until2(a, b) => {
144                out.push(3 | (u32::from(a) << 8) | (u32::from(b) << 16));
145            }
146            Self::Until3(a, b, c) => {
147                out.push(4 | (u32::from(a) << 8) | (u32::from(b) << 16) | (u32::from(c) << 24));
148            }
149            Self::Ranges(ranges) => {
150                out.push(5 | (u32::from(ranges.count()) << 8));
151                out.extend(ranges.packed_words());
152            }
153        }
154    }
155
156    fn read_serialized(reader: &mut SerializedReader<'_>) -> Option<Self> {
157        let word = reader.next()?;
158        match word.to_le_bytes() {
159            [0, 0, 0, 0] => Some(Self::None),
160            [1, 0, 0, 0] => Some(Self::Any),
161            [2, a, 0, 0] if a.is_ascii() => Some(Self::Until1(a)),
162            [3, a, b, 0] if a.is_ascii() && b.is_ascii() => Some(Self::Until2(a, b)),
163            [4, a, b, c] if a.is_ascii() && b.is_ascii() && c.is_ascii() => {
164                Some(Self::Until3(a, b, c))
165            }
166            [5, count, 0, 0] => Some(Self::Ranges(AsciiRanges::from_packed(
167                count,
168                [reader.next()?, reader.next()?],
169            )?)),
170            _ => None,
171        }
172    }
173
174    #[cfg(feature = "perf-counters")]
175    fn descriptor_kind(self) -> AsciiRunDescriptorKind {
176        match self {
177            Self::None => AsciiRunDescriptorKind::None,
178            Self::Any => AsciiRunDescriptorKind::Any,
179            Self::Until1(_) | Self::Until2(..) | Self::Until3(..) => AsciiRunDescriptorKind::Until,
180            Self::Ranges(ranges) => AsciiRunDescriptorKind::Ranges {
181                count: ranges.count(),
182                class: ranges.class(),
183            },
184        }
185    }
186}
187
188#[derive(Clone, Copy, Debug, Eq, PartialEq)]
189pub(super) struct AsciiRunScan {
190    pub(super) bytes: usize,
191    pub(super) found_exit: bool,
192    #[cfg(any(feature = "perf-counters", test))]
193    pub(super) range: Option<AsciiRangeScan>,
194}
195
196#[derive(Clone, Copy, Debug, Eq, PartialEq)]
197#[cfg(any(feature = "perf-counters", test))]
198pub(super) struct AsciiRangeScan {
199    ranges: AsciiRanges,
200}
201
202#[cfg(feature = "perf-counters")]
203impl AsciiRangeScan {
204    pub(super) fn class(self) -> AsciiRangeClass {
205        self.ranges.class()
206    }
207}
208
209#[derive(Clone, Copy, Debug, Eq, PartialEq)]
210#[cfg(feature = "perf-counters")]
211pub(crate) enum AsciiRunDescriptorKind {
212    None,
213    Any,
214    Until,
215    Ranges { count: u8, class: AsciiRangeClass },
216}
217
218/// A lexer DFA compiled ahead of time from a lexer ATN.
219///
220/// Build one per grammar with [`CompiledLexerDfa::compile`] (generated lexers
221/// cache it in a `OnceLock` beside the deserialized ATN) and match tokens
222/// through [`crate::atn::lexer::next_token_compiled`] or
223/// [`crate::atn::lexer::next_token_compiled_with_hooks`].
224#[derive(Clone, Debug)]
225pub struct CompiledLexerDfa {
226    mode_starts: Vec<Option<u16>>,
227    states: Vec<CompiledLexerState>,
228    ascii_runs: Vec<AsciiRun>,
229    ascii_rows: Vec<[u16; ASCII_EDGE_SYMBOLS]>,
230    wide_rows: Vec<Box<[WideRange]>>,
231    accepts: Vec<CompiledLexerAccept>,
232    escape_rows: Vec<CompiledLexerEscapeRow>,
233    continuations: Vec<CompiledLexerContinuation>,
234}
235
236/// One compiled DFA state; rows are pooled indices because many states share
237/// identical edge rows (identifier continuations, string bodies, …).
238#[derive(Clone, Copy, Debug)]
239struct CompiledLexerState {
240    ascii_row: u32,
241    wide_row: u32,
242    eof_target: u16,
243    /// Index into `accepts`, or `u32::MAX` when the state does not accept.
244    accept: u32,
245}
246
247/// Inclusive code-point range above the ASCII row mapping to one target.
248#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
249struct WideRange {
250    low: u32,
251    high: u32,
252    target: u16,
253}
254
255/// Dynamic outgoing edges for one compiled state.
256#[derive(Clone, Debug)]
257struct CompiledLexerEscapeRow {
258    ranges: Box<[CompiledLexerEscapeRange]>,
259    eof: u32,
260}
261
262/// Inclusive character range that resumes one interpreter continuation.
263#[derive(Clone, Copy, Debug)]
264struct CompiledLexerEscapeRange {
265    low: u32,
266    high: u32,
267    continuation: u32,
268}
269
270/// ATN configs immediately after an escaped consuming transition and before
271/// its dynamic epsilon closure.
272#[derive(Clone, Debug)]
273pub(super) struct CompiledLexerContinuation {
274    pub(super) contexts: Vec<CompiledLexerContext>,
275    pub(super) configs: Vec<CompiledLexerConfig>,
276}
277
278/// One ordered caller-context node in a continuation-local topological table.
279#[derive(Clone, Copy, Debug)]
280pub(super) enum CompiledLexerContext {
281    Singleton { parent: u32, return_state: usize },
282    Union { left: u32, right: u32 },
283}
284
285/// Position-independent ATN config stored in an escape continuation.
286#[derive(Clone, Debug)]
287pub(super) struct CompiledLexerConfig {
288    pub(super) state: usize,
289    pub(super) consumed_eof: bool,
290    pub(super) alt_rule_index: Option<usize>,
291    pub(super) passed_non_greedy: bool,
292    pub(super) context: u32,
293    pub(super) actions: Vec<CompiledLexerActionTrace>,
294}
295
296/// Accept metadata for one DFA state: the winning lexer rule plus the action
297/// transitions collected on the accepted ATN path.
298#[derive(Clone, Debug)]
299pub(super) struct CompiledLexerAccept {
300    pub(super) rule_index: usize,
301    pub(super) consumed_eof: bool,
302    pub(super) actions: Vec<CompiledLexerActionTrace>,
303}
304
305/// One recorded lexer action, positioned relative to the accept boundary so
306/// the same DFA state serves every input offset.
307#[derive(Clone, Copy, Debug)]
308pub(super) struct CompiledLexerActionTrace {
309    pub(super) action_index: usize,
310    pub(super) rule_index: usize,
311    /// Characters consumed between the action transition and the accept.
312    pub(super) behind: usize,
313}
314
315impl CompiledLexerDfa {
316    /// Compiles every lexer mode whose initial closure is static and fits the
317    /// state budget; dynamic interior edges carry interpreter continuations.
318    pub fn compile(atn: &LexerAtn) -> Self {
319        let mut dfa = Self {
320            mode_starts: Vec::new(),
321            states: Vec::new(),
322            ascii_runs: Vec::new(),
323            ascii_rows: Vec::new(),
324            wide_rows: Vec::new(),
325            accepts: Vec::new(),
326            escape_rows: Vec::new(),
327            continuations: Vec::new(),
328        };
329        let mut pools = RowPools::default();
330        for mode in 0..atn.mode_to_start_state().len() {
331            let start = build_mode(atn, mode, &mut dfa, &mut pools);
332            dfa.mode_starts.push(start);
333        }
334        #[cfg(feature = "perf-counters")]
335        dfa.record_ascii_run_descriptors();
336        dfa
337    }
338
339    /// True when at least one lexer mode compiled to static tables.
340    pub fn has_compiled_modes(&self) -> bool {
341        self.mode_starts.iter().any(Option::is_some)
342    }
343
344    /// Number of compiled DFA states across all modes (diagnostics).
345    pub const fn state_count(&self) -> usize {
346        self.states.len()
347    }
348
349    /// Per-mode compilation outcome (diagnostics): `true` = static tables.
350    pub fn compiled_mode_flags(&self) -> Vec<bool> {
351        self.mode_starts.iter().map(Option::is_some).collect()
352    }
353
354    /// Per-mode state counts (diagnostics), derived from start offsets.
355    pub fn mode_state_counts(&self) -> Vec<usize> {
356        let mut starts: Vec<usize> = self
357            .mode_starts
358            .iter()
359            .flatten()
360            .map(|&start| usize::from(start))
361            .collect();
362        starts.push(self.states.len());
363        starts.windows(2).map(|pair| pair[1] - pair[0]).collect()
364    }
365
366    /// Compiled start state for `mode`, or `None` when the mode is
367    /// interpreter-matched.
368    pub(super) fn mode_start(&self, mode: i32) -> Option<u16> {
369        let mode = usize::try_from(mode).ok()?;
370        self.mode_starts.get(mode).copied().flatten()
371    }
372
373    pub(super) fn accept(&self, state: u16) -> Option<&CompiledLexerAccept> {
374        self.accepts
375            .get(self.states[usize::from(state)].accept as usize)
376    }
377
378    /// Target for one byte from a stream known to contain only ASCII.
379    pub(super) fn ascii_target(&self, state: u16, symbol: u8) -> u16 {
380        debug_assert!(symbol.is_ascii());
381        let compiled = &self.states[usize::from(state)];
382        self.ascii_rows[compiled.ascii_row as usize][usize::from(symbol)]
383    }
384
385    pub(super) fn ascii_run(&self, state: u16) -> AsciiRun {
386        self.ascii_runs[usize::from(state)]
387    }
388
389    pub(super) fn scan_ascii_run(&self, state: u16, input: &[u8]) -> Option<AsciiRunScan> {
390        self.ascii_run(state).scan(input)
391    }
392
393    /// `LexerTransition` target for a non-EOF symbol, or [`DEAD_STATE`].
394    pub(super) fn char_target(&self, state: u16, symbol: i32) -> u16 {
395        let compiled = &self.states[usize::from(state)];
396        let code_point = symbol.cast_unsigned();
397        if let Ok(ascii) = usize::try_from(symbol)
398            && ascii < ASCII_EDGE_SYMBOLS
399        {
400            return self.ascii_rows[compiled.ascii_row as usize][ascii];
401        }
402        let row = &self.wide_rows[compiled.wide_row as usize];
403        match row.binary_search_by(|range| range.low.cmp(&code_point)) {
404            Ok(found) => row[found].target,
405            Err(insert) => {
406                if insert > 0 && row[insert - 1].high >= code_point {
407                    row[insert - 1].target
408                } else {
409                    DEAD_STATE
410                }
411            }
412        }
413    }
414
415    /// `LexerTransition` target for the EOF symbol, or [`DEAD_STATE`].
416    pub(super) fn eof_target(&self, state: u16) -> u16 {
417        self.states[usize::from(state)].eof_target
418    }
419
420    /// Interpreter continuation for an escaped character edge.
421    pub(super) fn char_continuation(
422        &self,
423        state: u16,
424        symbol: i32,
425    ) -> Option<&CompiledLexerContinuation> {
426        let code_point = symbol.cast_unsigned();
427        let ranges = &self.escape_rows[usize::from(state)].ranges;
428        let found = match ranges.binary_search_by(|range| range.low.cmp(&code_point)) {
429            Ok(found) => Some(found),
430            Err(insert) if insert > 0 && ranges[insert - 1].high >= code_point => Some(insert - 1),
431            Err(_) => None,
432        }?;
433        let continuation = usize::try_from(ranges[found].continuation).ok()?;
434        self.continuations.get(continuation)
435    }
436
437    /// Interpreter continuation for an escaped EOF edge.
438    pub(super) fn eof_continuation(&self, state: u16) -> Option<&CompiledLexerContinuation> {
439        let continuation = usize::try_from(self.escape_rows[usize::from(state)].eof).ok()?;
440        self.continuations.get(continuation)
441    }
442
443    /// Flattens the compiled DFA into a `u32` stream for embedding in
444    /// generated code.
445    ///
446    /// The format is internal to this runtime version; [`Self::from_serialized`]
447    /// rejects streams from other versions so generated lexers can fall back
448    /// to [`Self::compile`].
449    pub fn serialize(&self) -> Vec<u32> {
450        // Exact word count: the tag, eight section-length words, and each
451        // section's payload (states are 4 words, compact runs are 1 word,
452        // range runs are 3 words, ASCII rows pack 2 targets per word, and wide
453        // ranges/action traces are 3 words each behind their row length).
454        let ascii_run_words: usize = self
455            .ascii_runs
456            .iter()
457            .map(|run| run.serialized_words())
458            .sum();
459        let wide_words: usize = self.wide_rows.iter().map(|row| 1 + row.len() * 3).sum();
460        let accept_words: usize = self
461            .accepts
462            .iter()
463            .map(|accept| 3 + accept.actions.len() * 3)
464            .sum();
465        let escape_row_words: usize = self
466            .escape_rows
467            .iter()
468            .map(|row| 2 + row.ranges.len() * 3)
469            .sum();
470        let continuation_words: usize = self
471            .continuations
472            .iter()
473            .map(|continuation| {
474                2 + continuation.contexts.len() * 3
475                    + continuation
476                        .configs
477                        .iter()
478                        .map(|config| 6 + config.actions.len() * 3)
479                        .sum::<usize>()
480            })
481            .sum();
482        let capacity = 9
483            + self.mode_starts.len()
484            + self.states.len() * 4
485            + ascii_run_words
486            + self.ascii_rows.len() * (ASCII_EDGE_SYMBOLS / 2)
487            + wide_words
488            + accept_words
489            + escape_row_words
490            + continuation_words;
491        let mut out = Vec::with_capacity(capacity);
492        out.push(SERIALIZED_TAG);
493        out.push(self.mode_starts.len() as u32);
494        for start in &self.mode_starts {
495            out.push(start.map_or(u32::MAX, u32::from));
496        }
497        out.push(self.states.len() as u32);
498        for state in &self.states {
499            out.push(state.ascii_row);
500            out.push(state.wide_row);
501            out.push(u32::from(state.eof_target));
502            out.push(state.accept);
503        }
504        out.push(u32::try_from(self.ascii_runs.len()).expect("ascii_runs length overflow"));
505        for &run in &self.ascii_runs {
506            run.write_serialized(&mut out);
507        }
508        out.push(self.ascii_rows.len() as u32);
509        for row in &self.ascii_rows {
510            for pair in row.chunks(2) {
511                out.push(u32::from(pair[0]) | (u32::from(pair[1]) << 16));
512            }
513        }
514        out.push(self.wide_rows.len() as u32);
515        for row in &self.wide_rows {
516            out.push(row.len() as u32);
517            for range in &**row {
518                out.push(range.low);
519                out.push(range.high);
520                out.push(u32::from(range.target));
521            }
522        }
523        out.push(self.accepts.len() as u32);
524        for accept in &self.accepts {
525            out.push(accept.rule_index as u32);
526            out.push(u32::from(accept.consumed_eof));
527            out.push(accept.actions.len() as u32);
528            for action in &accept.actions {
529                out.push(action.action_index as u32);
530                out.push(action.rule_index as u32);
531                out.push(action.behind as u32);
532            }
533        }
534        out.push(self.escape_rows.len() as u32);
535        for row in &self.escape_rows {
536            out.push(row.eof);
537            out.push(row.ranges.len() as u32);
538            for range in &*row.ranges {
539                out.push(range.low);
540                out.push(range.high);
541                out.push(range.continuation);
542            }
543        }
544        out.push(self.continuations.len() as u32);
545        for continuation in &self.continuations {
546            out.push(continuation.contexts.len() as u32);
547            for context in &continuation.contexts {
548                match context {
549                    CompiledLexerContext::Singleton {
550                        parent,
551                        return_state,
552                    } => {
553                        out.push(0);
554                        out.push(*parent);
555                        out.push(
556                            u32::try_from(*return_state)
557                                .expect("lexer context return state must fit in u32"),
558                        );
559                    }
560                    CompiledLexerContext::Union { left, right } => {
561                        out.push(1);
562                        out.push(*left);
563                        out.push(*right);
564                    }
565                }
566            }
567            out.push(continuation.configs.len() as u32);
568            for config in &continuation.configs {
569                out.push(config.state as u32);
570                out.push(config.alt_rule_index.map_or(u32::MAX, |rule| rule as u32));
571                out.push(u32::from(config.consumed_eof));
572                out.push(u32::from(config.passed_non_greedy));
573                out.push(config.context);
574                out.push(config.actions.len() as u32);
575                for action in &config.actions {
576                    out.push(action.action_index as u32);
577                    out.push(action.rule_index as u32);
578                    out.push(action.behind as u32);
579                }
580            }
581        }
582        debug_assert_eq!(
583            out.len(),
584            capacity,
585            "serialized stream fills its capacity exactly"
586        );
587        out
588    }
589
590    /// Rebuilds a compiled DFA from [`Self::serialize`] output; `None` when
591    /// the stream comes from a different runtime version or is malformed.
592    pub fn from_serialized(data: &[u32]) -> Option<Self> {
593        let mut reader = SerializedReader { data, position: 0 };
594        if reader.next()? != SERIALIZED_TAG {
595            return None;
596        }
597        let mode_count = reader.next_len()?;
598        let mut mode_starts = Vec::with_capacity(mode_count);
599        for _ in 0..mode_count {
600            let word = reader.next()?;
601            let start = if word == u32::MAX {
602                None
603            } else {
604                Some(u16::try_from(word).ok()?)
605            };
606            mode_starts.push(start);
607        }
608        let states = reader.read_states()?;
609        let ascii_runs = reader.read_ascii_runs()?;
610        let ascii_rows = reader.read_ascii_rows()?;
611        let wide_rows = reader.read_wide_rows()?;
612        let accepts = reader.read_accepts()?;
613        let escape_rows = reader.read_escape_rows()?;
614        let continuations = reader.read_continuations()?;
615        if reader.position != data.len() {
616            return None;
617        }
618        let dfa = Self {
619            mode_starts,
620            states,
621            ascii_runs,
622            ascii_rows,
623            wide_rows,
624            accepts,
625            escape_rows,
626            continuations,
627        };
628        if !dfa.table_indexes_are_valid() {
629            return None;
630        }
631        #[cfg(feature = "perf-counters")]
632        dfa.record_ascii_run_descriptors();
633        Some(dfa)
634    }
635
636    /// Cheap structural validation so a corrupted embedded stream degrades to
637    /// runtime compilation instead of an out-of-bounds panic mid-parse.
638    fn table_indexes_are_valid(&self) -> bool {
639        let state_ok =
640            |target: u16| usize::from(target) < self.states.len() || target >= ESCAPE_STATE;
641        let continuation_ok = |continuation: u32| {
642            usize::try_from(continuation)
643                .is_ok_and(|continuation| continuation < self.continuations.len())
644        };
645        self.mode_starts
646            .iter()
647            .flatten()
648            .all(|&start| usize::from(start) < self.states.len())
649            && self.states.iter().all(|state| {
650                (state.ascii_row as usize) < self.ascii_rows.len()
651                    && (state.wide_row as usize) < self.wide_rows.len()
652                    && state_ok(state.eof_target)
653                    && (state.accept == u32::MAX || (state.accept as usize) < self.accepts.len())
654            })
655            && self.ascii_runs.len() == self.states.len()
656            && self.states.iter().zip(&self.ascii_runs).enumerate().all(
657                |(state_id, (state, &ascii_run))| {
658                    u16::try_from(state_id).is_ok_and(|state_id| {
659                        ascii_run
660                            == AsciiRun::classify(
661                                &self.ascii_rows[state.ascii_row as usize],
662                                state_id,
663                            )
664                    })
665                },
666            )
667            && self
668                .ascii_rows
669                .iter()
670                .all(|row| row.iter().all(|&target| state_ok(target)))
671            && self.wide_rows.iter().all(|row| {
672                wide_row_is_searchable(row) && row.iter().all(|range| state_ok(range.target))
673            })
674            && self.escape_rows.len() == self.states.len()
675            && self.escape_rows.iter().all(|row| {
676                (row.eof == u32::MAX || continuation_ok(row.eof))
677                    && escape_row_is_searchable(&row.ranges)
678                    && row
679                        .ranges
680                        .iter()
681                        .all(|range| continuation_ok(range.continuation))
682            })
683            && self
684                .continuations
685                .iter()
686                .all(compiled_continuation_contexts_are_valid)
687    }
688
689    #[cfg(feature = "perf-counters")]
690    fn record_ascii_run_descriptors(&self) {
691        for &run in &self.ascii_runs {
692            crate::perf::record_lexer_run_descriptor(run.descriptor_kind());
693        }
694    }
695}
696
697fn compiled_continuation_contexts_are_valid(continuation: &CompiledLexerContinuation) -> bool {
698    let contexts_valid = continuation
699        .contexts
700        .iter()
701        .enumerate()
702        .all(|(index, context)| {
703            let local_id = u32::try_from(index + 1).ok();
704            local_id.is_some_and(|local_id| match context {
705                CompiledLexerContext::Singleton { parent, .. } => *parent < local_id,
706                CompiledLexerContext::Union { left, right } => {
707                    *left < local_id && *right < local_id
708                }
709            })
710        });
711    contexts_valid
712        && u32::try_from(continuation.contexts.len()).is_ok_and(|max_context| {
713            continuation
714                .configs
715                .iter()
716                .all(|config| config.context <= max_context)
717        })
718}
719
720/// Wide rows must hold well-formed, sorted, disjoint ranges for
721/// [`CompiledLexerDfa::char_target`]'s binary search; anything else would
722/// silently misroute transitions instead of degrading to recompilation.
723fn wide_row_is_searchable(row: &[WideRange]) -> bool {
724    row.iter().all(|range| range.low <= range.high)
725        && row.windows(2).all(|pair| pair[0].high < pair[1].low)
726}
727
728fn escape_row_is_searchable(row: &[CompiledLexerEscapeRange]) -> bool {
729    row.iter().all(|range| range.low <= range.high)
730        && row.windows(2).all(|pair| pair[0].high < pair[1].low)
731}
732
733/// Version tag guarding embedded tables against format or construction-semantic
734/// drift.
735const SERIALIZED_TAG: u32 = 0x4C58_4407;
736
737/// Cursor over a serialized DFA stream.
738struct SerializedReader<'a> {
739    data: &'a [u32],
740    position: usize,
741}
742
743impl SerializedReader<'_> {
744    fn next(&mut self) -> Option<u32> {
745        let value = self.data.get(self.position).copied();
746        self.position += 1;
747        value
748    }
749
750    fn next_u16(&mut self) -> Option<u16> {
751        u16::try_from(self.next()?).ok()
752    }
753
754    fn next_len(&mut self) -> Option<usize> {
755        usize::try_from(self.next()?).ok()
756    }
757
758    fn read_states(&mut self) -> Option<Vec<CompiledLexerState>> {
759        let count = self.next_len()?;
760        let mut states = Vec::with_capacity(count.min(self.data.len()));
761        for _ in 0..count {
762            states.push(CompiledLexerState {
763                ascii_row: self.next()?,
764                wide_row: self.next()?,
765                eof_target: self.next_u16()?,
766                accept: self.next()?,
767            });
768        }
769        Some(states)
770    }
771
772    fn read_ascii_runs(&mut self) -> Option<Vec<AsciiRun>> {
773        let count = self.next_len()?;
774        let mut runs = Vec::with_capacity(count.min(self.data.len()));
775        for _ in 0..count {
776            runs.push(AsciiRun::read_serialized(self)?);
777        }
778        Some(runs)
779    }
780
781    fn read_ascii_rows(&mut self) -> Option<Vec<[u16; ASCII_EDGE_SYMBOLS]>> {
782        let count = self.next_len()?;
783        let mut rows = Vec::with_capacity(count.min(self.data.len()));
784        for _ in 0..count {
785            let mut row = [DEAD_STATE; ASCII_EDGE_SYMBOLS];
786            for pair in 0..ASCII_EDGE_SYMBOLS / 2 {
787                let word = self.next()?;
788                row[pair * 2] = (word & 0xFFFF) as u16;
789                row[pair * 2 + 1] = (word >> 16) as u16;
790            }
791            rows.push(row);
792        }
793        Some(rows)
794    }
795
796    fn read_wide_rows(&mut self) -> Option<Vec<Box<[WideRange]>>> {
797        let count = self.next_len()?;
798        let mut rows = Vec::with_capacity(count.min(self.data.len()));
799        for _ in 0..count {
800            let len = self.next_len()?;
801            let mut row = Vec::with_capacity(len.min(self.data.len()));
802            for _ in 0..len {
803                row.push(WideRange {
804                    low: self.next()?,
805                    high: self.next()?,
806                    target: self.next_u16()?,
807                });
808            }
809            rows.push(row.into());
810        }
811        Some(rows)
812    }
813
814    fn read_accepts(&mut self) -> Option<Vec<CompiledLexerAccept>> {
815        let count = self.next_len()?;
816        let mut accepts = Vec::with_capacity(count.min(self.data.len()));
817        for _ in 0..count {
818            let rule_index = self.next_len()?;
819            let consumed_eof = self.next()? != 0;
820            let action_count = self.next_len()?;
821            let mut actions = Vec::with_capacity(action_count.min(self.data.len()));
822            for _ in 0..action_count {
823                actions.push(CompiledLexerActionTrace {
824                    action_index: self.next_len()?,
825                    rule_index: self.next_len()?,
826                    behind: self.next_len()?,
827                });
828            }
829            accepts.push(CompiledLexerAccept {
830                rule_index,
831                consumed_eof,
832                actions,
833            });
834        }
835        Some(accepts)
836    }
837
838    fn read_escape_rows(&mut self) -> Option<Vec<CompiledLexerEscapeRow>> {
839        let count = self.next_len()?;
840        let mut rows = Vec::with_capacity(count.min(self.data.len()));
841        for _ in 0..count {
842            let eof = self.next()?;
843            let len = self.next_len()?;
844            let mut ranges = Vec::with_capacity(len.min(self.data.len()));
845            for _ in 0..len {
846                ranges.push(CompiledLexerEscapeRange {
847                    low: self.next()?,
848                    high: self.next()?,
849                    continuation: self.next()?,
850                });
851            }
852            rows.push(CompiledLexerEscapeRow {
853                ranges: ranges.into(),
854                eof,
855            });
856        }
857        Some(rows)
858    }
859
860    fn read_continuations(&mut self) -> Option<Vec<CompiledLexerContinuation>> {
861        let count = self.next_len()?;
862        let mut continuations = Vec::with_capacity(count.min(self.data.len()));
863        for _ in 0..count {
864            let context_count = self.next_len()?;
865            let mut contexts = Vec::with_capacity(context_count.min(self.data.len()));
866            for _ in 0..context_count {
867                let kind = self.next()?;
868                let first = self.next()?;
869                let second = self.next()?;
870                contexts.push(match kind {
871                    0 => CompiledLexerContext::Singleton {
872                        parent: first,
873                        return_state: usize::try_from(second).ok()?,
874                    },
875                    1 => CompiledLexerContext::Union {
876                        left: first,
877                        right: second,
878                    },
879                    _ => return None,
880                });
881            }
882            let config_count = self.next_len()?;
883            let mut configs = Vec::with_capacity(config_count.min(self.data.len()));
884            for _ in 0..config_count {
885                let state = self.next_len()?;
886                let alt_rule = self.next()?;
887                let alt_rule_index = if alt_rule == u32::MAX {
888                    None
889                } else {
890                    Some(usize::try_from(alt_rule).ok()?)
891                };
892                let consumed_eof = self.next()? != 0;
893                let passed_non_greedy = self.next()? != 0;
894                let context = self.next()?;
895                let action_count = self.next_len()?;
896                let mut actions = Vec::with_capacity(action_count.min(self.data.len()));
897                for _ in 0..action_count {
898                    actions.push(CompiledLexerActionTrace {
899                        action_index: self.next_len()?,
900                        rule_index: self.next_len()?,
901                        behind: self.next_len()?,
902                    });
903                }
904                configs.push(CompiledLexerConfig {
905                    state,
906                    consumed_eof,
907                    alt_rule_index,
908                    passed_non_greedy,
909                    context,
910                    actions,
911                });
912            }
913            continuations.push(CompiledLexerContinuation { contexts, configs });
914        }
915        Some(continuations)
916    }
917}
918
919/// Deduplicating pools for edge rows shared by many DFA states.
920#[derive(Debug, Default)]
921struct RowPools {
922    ascii_ids: FxHashMap<[u16; ASCII_EDGE_SYMBOLS], u32>,
923    wide_ids: FxHashMap<Box<[WideRange]>, u32>,
924}
925
926impl RowPools {
927    fn intern_ascii(
928        &mut self,
929        rows: &mut Vec<[u16; ASCII_EDGE_SYMBOLS]>,
930        row: [u16; ASCII_EDGE_SYMBOLS],
931    ) -> u32 {
932        *self.ascii_ids.entry(row).or_insert_with(|| {
933            rows.push(row);
934            (rows.len() - 1) as u32
935        })
936    }
937
938    fn intern_wide(&mut self, rows: &mut Vec<Box<[WideRange]>>, row: Vec<WideRange>) -> u32 {
939        let row: Box<[WideRange]> = row.into();
940        if let Some(&id) = self.wide_ids.get(&row) {
941            return id;
942        }
943        rows.push(row.clone());
944        let id = (rows.len() - 1) as u32;
945        self.wide_ids.insert(row, id);
946        id
947    }
948}
949
950/// In-progress subset construction for one lexer mode.
951///
952/// States are numbered globally (`base` + discovery order) so edges can be
953/// written directly into the final table, but nothing is committed to the
954/// shared [`CompiledLexerDfa`] until the whole mode succeeds.
955struct ModeBuild {
956    base: usize,
957    continuation_base: usize,
958    contexts: LexerContextArena,
959    workspace: PredictionWorkspace,
960    ids: FxHashMap<LexerDfaKey, u16>,
961    configs: Vec<Vec<LexerConfig>>,
962    steps: Vec<usize>,
963    accepts: Vec<Option<CompiledLexerAccept>>,
964    continuations: Vec<CompiledLexerContinuation>,
965}
966
967/// Edge rows produced by expanding one DFA state.
968struct StateRows {
969    /// Sorted, disjoint code-point segments with live targets.
970    segments: Vec<(i32, i32, u16)>,
971    eof_target: u16,
972    escapes: Vec<(i32, i32, u32)>,
973    eof_escape: u32,
974}
975
976#[derive(Clone, Copy)]
977struct EdgeTarget {
978    state: u16,
979    continuation: u32,
980}
981
982impl EdgeTarget {
983    const DEAD: Self = Self {
984        state: DEAD_STATE,
985        continuation: u32::MAX,
986    };
987}
988
989impl ModeBuild {
990    fn new(base: usize, continuation_base: usize) -> Self {
991        Self {
992            base,
993            continuation_base,
994            contexts: LexerContextArena::new(),
995            workspace: PredictionWorkspace::default(),
996            ids: FxHashMap::default(),
997            configs: Vec::new(),
998            steps: Vec::new(),
999            accepts: Vec::new(),
1000            continuations: Vec::new(),
1001        }
1002    }
1003
1004    const fn len(&self) -> usize {
1005        self.configs.len()
1006    }
1007
1008    /// Returns the state id for a closed, pruned config set, creating the
1009    /// state when the (input-offset-normalized) identity is new.
1010    /// [`ESCAPE_STATE`] means the state budget is exhausted and the edge must
1011    /// hand the token to the interpreter.
1012    fn intern(&mut self, atn: &LexerAtn, configs: Vec<LexerConfig>, step: usize) -> u16 {
1013        let key = LexerDfaKey::new(
1014            configs
1015                .iter()
1016                .map(|config| relative_config_key(config, step))
1017                .collect(),
1018        );
1019        if let Some(&id) = self.ids.get(&key) {
1020            return id;
1021        }
1022        let local = self.configs.len();
1023        let global = self.base + local;
1024        if local >= MAX_MODE_STATES || global >= usize::from(ESCAPE_STATE) {
1025            return ESCAPE_STATE;
1026        }
1027        let Ok(id) = u16::try_from(global) else {
1028            return ESCAPE_STATE;
1029        };
1030        self.ids.insert(key, id);
1031        self.accepts.push(compiled_accept(atn, &configs, step));
1032        self.configs.push(configs);
1033        self.steps.push(step);
1034        id
1035    }
1036
1037    fn add_continuation(&mut self, configs: &[LexerConfig], step: usize) -> u32 {
1038        let id = self.continuation_base + self.continuations.len();
1039        let Ok(id) = u32::try_from(id) else {
1040            return u32::MAX;
1041        };
1042        let mut context_ids = FxHashMap::default();
1043        context_ids.insert(EMPTY_LEXER_CONTEXT, 0);
1044        let mut contexts = Vec::new();
1045        let compiled_configs = configs
1046            .iter()
1047            .map(|config| CompiledLexerConfig {
1048                state: config.state,
1049                consumed_eof: config.consumed_eof,
1050                alt_rule_index: config.alt_rule_index(),
1051                passed_non_greedy: config.passed_non_greedy,
1052                context: compile_context(
1053                    &self.contexts,
1054                    config.context,
1055                    &mut context_ids,
1056                    &mut contexts,
1057                ),
1058                actions: config
1059                    .actions
1060                    .as_slice()
1061                    .iter()
1062                    .map(|action| CompiledLexerActionTrace {
1063                        action_index: action.action_index,
1064                        rule_index: action.rule_index,
1065                        behind: step.saturating_sub(action.position),
1066                    })
1067                    .collect(),
1068            })
1069            .collect();
1070        self.continuations.push(CompiledLexerContinuation {
1071            contexts,
1072            configs: compiled_configs,
1073        });
1074        id
1075    }
1076}
1077
1078/// Normalizes one config for DFA-state identity, measuring action positions
1079/// backwards from the current input offset (`step`).
1080///
1081/// This differs from the interpreter cache's token-start-relative deltas on
1082/// purpose: rule-final lexer commands (`skip`, `pushMode`, …) fire a fixed
1083/// distance before the accept, so anchoring at the read position keeps the
1084/// state space finite regardless of token length.
1085fn relative_config_key(config: &LexerConfig, step: usize) -> LexerDfaConfigKey {
1086    LexerDfaConfigKey::new(
1087        config.state,
1088        config.alt_rule_index(),
1089        config.consumed_eof,
1090        config.passed_non_greedy,
1091        config.context,
1092        config
1093            .actions
1094            .as_slice()
1095            .iter()
1096            .map(|action| LexerDfaActionKey {
1097                action_index: action.action_index,
1098                position_delta: step.saturating_sub(action.position),
1099                rule_index: action.rule_index,
1100            })
1101            .collect(),
1102    )
1103}
1104
1105/// Computes the accept metadata for a DFA state from its config set, using
1106/// the interpreter's own rule-priority selection.
1107fn compiled_accept(
1108    atn: &LexerAtn,
1109    configs: &[LexerConfig],
1110    step: usize,
1111) -> Option<CompiledLexerAccept> {
1112    let accept = best_accept(atn, configs)?;
1113    debug_assert!(
1114        accept.position == step,
1115        "every config in a lexer DFA state shares the state's input offset"
1116    );
1117    Some(CompiledLexerAccept {
1118        rule_index: accept.rule_index,
1119        consumed_eof: accept.consumed_eof,
1120        actions: accept
1121            .actions
1122            .iter()
1123            .map(|trace| CompiledLexerActionTrace {
1124                action_index: trace.action_index,
1125                rule_index: trace.rule_index,
1126                behind: accept.position.saturating_sub(trace.position),
1127            })
1128            .collect(),
1129    })
1130}
1131
1132/// Runs subset construction for one mode; `None` leaves the whole mode to the
1133/// interpreter (only when its very first closure already escapes).
1134fn build_mode(
1135    atn: &LexerAtn,
1136    mode: usize,
1137    dfa: &mut CompiledLexerDfa,
1138    pools: &mut RowPools,
1139) -> Option<u16> {
1140    let start_state = atn.mode_to_start_state().get(mode).copied()?;
1141    let mut build = ModeBuild::new(dfa.states.len(), dfa.continuations.len());
1142    let start_configs = closed_configs(
1143        atn,
1144        &mut build,
1145        vec![LexerConfig::new(start_state, 0, EMPTY_LEXER_CONTEXT)],
1146    )?;
1147    let start_id = build.intern(atn, start_configs, 0);
1148    if start_id == ESCAPE_STATE {
1149        return None;
1150    }
1151
1152    let mut rows = Vec::new();
1153    let mut cursor = 0;
1154    while cursor < build.len() {
1155        rows.push(expand_state(atn, &mut build, cursor));
1156        cursor += 1;
1157    }
1158
1159    commit_mode(dfa, pools, build, rows);
1160    Some(start_id)
1161}
1162
1163/// Closes and prunes a moved config set exactly like the interpreter does.
1164/// `None` means the closure crossed a semantic predicate (which only the
1165/// interpreter can evaluate) or entered a recursive lexer rule (nested
1166/// comments never determinize), so the edge must escape.
1167fn closed_configs(
1168    atn: &LexerAtn,
1169    build: &mut ModeBuild,
1170    moved: Vec<LexerConfig>,
1171) -> Option<Vec<LexerConfig>> {
1172    let closure = epsilon_closure(
1173        atn,
1174        moved,
1175        &mut build.contexts,
1176        &mut build.workspace,
1177        &mut |_| true,
1178    );
1179    if closure.has_semantic_context {
1180        return None;
1181    }
1182    if closure
1183        .configs
1184        .iter()
1185        .any(|config| has_recursive_context(config, &build.contexts))
1186    {
1187        return None;
1188    }
1189    let mut configs = closure.configs;
1190    for config in &mut configs {
1191        prune_dead_action_traces(atn, config);
1192        if config.actions.len() > MAX_ACTION_TRACES {
1193            return None;
1194        }
1195    }
1196    Some(prune_after_accepts(atn, configs))
1197}
1198
1199/// Drops action traces the accept-time dispatcher would suppress anyway.
1200///
1201/// The interpreter keeps traces of every action transition it crosses and
1202/// filters them per accept with `lexer_action_belongs_to_accept`; a token
1203/// rule referenced from another rule leaves traces that can never fire (its
1204/// commands belong to itself, not the enclosing rule). Carrying them into
1205/// DFA-state identity would mint a fresh state per input offset — rules that
1206/// loop over comment/whitespace references would never determinize.
1207fn prune_dead_action_traces(atn: &LexerAtn, config: &mut LexerConfig) {
1208    let Some(accept_rule) = config.alt_rule_index() else {
1209        return;
1210    };
1211    config
1212        .actions
1213        .retain(|trace| lexer_action_belongs_to_accept(atn, accept_rule, trace.rule_index));
1214}
1215
1216/// Copies one arena context into a continuation-local topological table.
1217fn compile_context(
1218    contexts: &LexerContextArena,
1219    context: LexerContextId,
1220    ids: &mut FxHashMap<LexerContextId, u32>,
1221    compiled: &mut Vec<CompiledLexerContext>,
1222) -> u32 {
1223    if let Some(&id) = ids.get(&context) {
1224        return id;
1225    }
1226
1227    enum Frame {
1228        Visit(LexerContextId),
1229        Finish(LexerContextId),
1230    }
1231
1232    let mut pending = vec![Frame::Visit(context)];
1233    while let Some(frame) = pending.pop() {
1234        match frame {
1235            Frame::Visit(current) => {
1236                if ids.contains_key(&current) {
1237                    continue;
1238                }
1239                let node = contexts.node(current);
1240                match node {
1241                    LexerContextNode::Empty => {
1242                        ids.insert(current, 0);
1243                    }
1244                    LexerContextNode::Singleton { parent, .. } => {
1245                        pending.push(Frame::Finish(current));
1246                        pending.push(Frame::Visit(parent));
1247                    }
1248                    LexerContextNode::Union { left, right } => {
1249                        pending.push(Frame::Finish(current));
1250                        pending.push(Frame::Visit(right));
1251                        pending.push(Frame::Visit(left));
1252                    }
1253                }
1254            }
1255            Frame::Finish(current) => {
1256                let node = match contexts.node(current) {
1257                    LexerContextNode::Empty => unreachable!("empty contexts finish immediately"),
1258                    LexerContextNode::Singleton {
1259                        parent,
1260                        return_state,
1261                    } => CompiledLexerContext::Singleton {
1262                        parent: ids[&parent],
1263                        return_state,
1264                    },
1265                    LexerContextNode::Union { left, right } => CompiledLexerContext::Union {
1266                        left: ids[&left],
1267                        right: ids[&right],
1268                    },
1269                };
1270                let id = u32::try_from(compiled.len() + 1)
1271                    .expect("compiled lexer context table overflow");
1272                compiled.push(node);
1273                ids.insert(current, id);
1274            }
1275        }
1276    }
1277
1278    ids[&context]
1279}
1280
1281/// Detects a repeated return state on any caller-context path. Such recursive
1282/// paths grow without bound and therefore cannot be represented by a finite
1283/// compiled DFA.
1284fn has_recursive_context(config: &LexerConfig, contexts: &LexerContextArena) -> bool {
1285    enum Frame {
1286        Visit(LexerContextId),
1287        LeaveRule,
1288    }
1289
1290    let mut path = Vec::with_capacity(MAX_CONTEXT_DEPTH);
1291    let mut pending = vec![Frame::Visit(config.context)];
1292    while let Some(frame) = pending.pop() {
1293        match frame {
1294            Frame::Visit(context) => match contexts.node(context) {
1295                LexerContextNode::Empty => {}
1296                LexerContextNode::Union { left, right } => {
1297                    pending.push(Frame::Visit(right));
1298                    pending.push(Frame::Visit(left));
1299                }
1300                LexerContextNode::Singleton {
1301                    parent,
1302                    return_state,
1303                } => {
1304                    if path.len() >= MAX_CONTEXT_DEPTH || path.contains(&return_state) {
1305                        return true;
1306                    }
1307                    path.push(return_state);
1308                    pending.push(Frame::LeaveRule);
1309                    pending.push(Frame::Visit(parent));
1310                }
1311            },
1312            Frame::LeaveRule => {
1313                path.pop().expect("leave frame must match an entered rule");
1314            }
1315        }
1316    }
1317
1318    false
1319}
1320
1321/// Computes every outgoing edge of one interned DFA state.
1322fn expand_state(atn: &LexerAtn, build: &mut ModeBuild, local: usize) -> StateRows {
1323    let configs = build.configs[local].clone();
1324    let step = build.steps[local];
1325    let entries = consuming_entries(atn, &configs);
1326    let eof_target = eof_move(atn, build, &configs, step, &entries);
1327
1328    let entry_intervals: Vec<Vec<(i32, i32)>> = entries
1329        .iter()
1330        .map(|(_, transition)| transition_char_intervals(transition))
1331        .collect();
1332    let segments = char_segments(&entry_intervals);
1333    let matrix = segment_mask_matrix(&segments, &entry_intervals, entries.len());
1334    let words = entries.len().div_ceil(64);
1335
1336    let mut rows = StateRows {
1337        segments: Vec::new(),
1338        eof_target: eof_target.state,
1339        escapes: Vec::new(),
1340        eof_escape: eof_target.continuation,
1341    };
1342    // Distinct transition sets are few even when segments are many (large
1343    // Unicode classes fragment the alphabet), so closures run once per
1344    // matching-transition mask, not once per segment.
1345    let mut mask_targets: FxHashMap<Vec<u64>, EdgeTarget> = FxHashMap::default();
1346    for (index, &(low, high)) in segments.iter().enumerate() {
1347        let mask = &matrix[index * words..(index + 1) * words];
1348        if mask.iter().all(|&word| word == 0) {
1349            continue;
1350        }
1351        let target = match mask_targets.get(mask) {
1352            Some(&target) => target,
1353            None => {
1354                let target = move_target(atn, build, &configs, step, &entries, mask);
1355                mask_targets.insert(mask.to_vec(), target);
1356                target
1357            }
1358        };
1359        if target.state != DEAD_STATE {
1360            rows.segments.push((low, high, target.state));
1361            if target.continuation != u32::MAX {
1362                rows.escapes.push((low, high, target.continuation));
1363            }
1364        }
1365    }
1366    rows
1367}
1368
1369/// Lists each config's consuming transitions in the interpreter's move order.
1370fn consuming_entries<'a>(
1371    atn: &'a LexerAtn,
1372    configs: &[LexerConfig],
1373) -> Vec<(usize, &'a LexerTransition)> {
1374    let mut entries = Vec::new();
1375    for (config_index, config) in configs.iter().enumerate() {
1376        let Some(state) = atn.state(config.state) else {
1377            continue;
1378        };
1379        for transition in &state.transitions {
1380            if !transition.is_epsilon() {
1381                entries.push((config_index, transition));
1382            }
1383        }
1384    }
1385    entries
1386}
1387
1388/// Splits the code-point alphabet at every interval boundary, so each segment
1389/// is matched uniformly by every transition.
1390fn char_segments(entry_intervals: &[Vec<(i32, i32)>]) -> Vec<(i32, i32)> {
1391    let mut cuts = Vec::new();
1392    for intervals in entry_intervals {
1393        for &(low, high) in intervals {
1394            cuts.push(low);
1395            cuts.push(high + 1);
1396        }
1397    }
1398    cuts.sort_unstable();
1399    cuts.dedup();
1400    cuts.windows(2).map(|pair| (pair[0], pair[1] - 1)).collect()
1401}
1402
1403/// Marks, for every segment, which entries match it — one bit row per
1404/// segment. Sweeping each entry's intervals over the sorted segment starts
1405/// keeps the work proportional to interval count, not `segments × entries`.
1406fn segment_mask_matrix(
1407    segments: &[(i32, i32)],
1408    entry_intervals: &[Vec<(i32, i32)>],
1409    entry_count: usize,
1410) -> Vec<u64> {
1411    let words = entry_count.div_ceil(64);
1412    let mut matrix = vec![0_u64; segments.len() * words];
1413    for (bit, intervals) in entry_intervals.iter().enumerate() {
1414        for &(low, high) in intervals {
1415            // Interval boundaries are cut points, so the covered segments are
1416            // exactly those whose start lies inside the interval.
1417            let from = segments.partition_point(|&(start, _)| start < low);
1418            let to = segments.partition_point(|&(start, _)| start <= high);
1419            for segment in from..to {
1420                matrix[segment * words + bit / 64] |= 1 << (bit % 64);
1421            }
1422        }
1423    }
1424    matrix
1425}
1426
1427/// Materializes the code-point intervals a transition consumes, clamped to
1428/// the valid character range (EOF is handled separately).
1429fn transition_char_intervals(transition: &LexerTransition) -> Vec<(i32, i32)> {
1430    let mut intervals = Vec::new();
1431    let mut push_clamped = |low: i32, high: i32| {
1432        let low = low.max(MIN_CHAR_VALUE);
1433        let high = high.min(MAX_CHAR_VALUE);
1434        if low <= high {
1435            intervals.push((low, high));
1436        }
1437    };
1438    match transition {
1439        LexerTransition::Atom { label, .. } => push_clamped(*label, *label),
1440        LexerTransition::Range { start, stop, .. } => push_clamped(*start, *stop),
1441        LexerTransition::Set { set, .. } => {
1442            for &(low, high) in set.ranges() {
1443                push_clamped(low, high);
1444            }
1445        }
1446        LexerTransition::NotSet { set, .. } => {
1447            // `NotSet` matches the complement within the character range;
1448            // `IntervalSet` ranges are sorted and coalesced.
1449            let mut next = MIN_CHAR_VALUE;
1450            for &(low, high) in set.ranges() {
1451                if low > next {
1452                    push_clamped(next, low - 1);
1453                }
1454                next = next.max(high.saturating_add(1));
1455            }
1456            push_clamped(next, MAX_CHAR_VALUE);
1457        }
1458        LexerTransition::Wildcard { .. } => push_clamped(MIN_CHAR_VALUE, MAX_CHAR_VALUE),
1459        _ => {}
1460    }
1461    intervals
1462}
1463
1464/// Advances the masked entries by one character and interns the result;
1465/// closures that escape compile as [`ESCAPE_STATE`] edges.
1466fn move_target(
1467    atn: &LexerAtn,
1468    build: &mut ModeBuild,
1469    configs: &[LexerConfig],
1470    step: usize,
1471    entries: &[(usize, &LexerTransition)],
1472    mask: &[u64],
1473) -> EdgeTarget {
1474    let mut moved = Vec::new();
1475    for (bit, (config_index, transition)) in entries.iter().enumerate() {
1476        if mask[bit / 64] & (1 << (bit % 64)) == 0 {
1477            continue;
1478        }
1479        let mut advanced = configs[*config_index].clone();
1480        set_config_state(atn, &mut advanced, transition.target());
1481        advanced.position += 1;
1482        moved.push(advanced);
1483    }
1484    let continuation_configs = moved.clone();
1485    let Some(active) = closed_configs(atn, build, moved) else {
1486        return EdgeTarget {
1487            state: ESCAPE_STATE,
1488            continuation: build.add_continuation(&continuation_configs, step + 1),
1489        };
1490    };
1491    if active.is_empty() {
1492        return EdgeTarget::DEAD;
1493    }
1494    EdgeTarget {
1495        state: build.intern(atn, active, step + 1),
1496        continuation: u32::MAX,
1497    }
1498}
1499
1500/// Advances the EOF-matching entries; EOF consumes no character, so the input
1501/// offset stays put and the moved configs record `consumed_eof`.
1502fn eof_move(
1503    atn: &LexerAtn,
1504    build: &mut ModeBuild,
1505    configs: &[LexerConfig],
1506    step: usize,
1507    entries: &[(usize, &LexerTransition)],
1508) -> EdgeTarget {
1509    let mut moved = Vec::new();
1510    for (config_index, transition) in entries {
1511        if !transition.matches(EOF, MIN_CHAR_VALUE, MAX_CHAR_VALUE) {
1512            continue;
1513        }
1514        let mut advanced = configs[*config_index].clone();
1515        set_config_state(atn, &mut advanced, transition.target());
1516        advanced.consumed_eof = true;
1517        moved.push(advanced);
1518    }
1519    if moved.is_empty() {
1520        return EdgeTarget::DEAD;
1521    }
1522    let continuation_configs = moved.clone();
1523    let Some(active) = closed_configs(atn, build, moved) else {
1524        return EdgeTarget {
1525            state: ESCAPE_STATE,
1526            continuation: build.add_continuation(&continuation_configs, step),
1527        };
1528    };
1529    if active.is_empty() {
1530        return EdgeTarget::DEAD;
1531    }
1532    EdgeTarget {
1533        state: build.intern(atn, active, step),
1534        continuation: u32::MAX,
1535    }
1536}
1537
1538/// Converts a finished mode's edge rows into pooled table entries.
1539fn commit_mode(
1540    dfa: &mut CompiledLexerDfa,
1541    pools: &mut RowPools,
1542    build: ModeBuild,
1543    rows: Vec<StateRows>,
1544) {
1545    let ModeBuild {
1546        accepts,
1547        continuations,
1548        ..
1549    } = build;
1550    for (accept, state_rows) in accepts.into_iter().zip(rows) {
1551        let accept_id = accept.map_or(u32::MAX, |accept| {
1552            dfa.accepts.push(accept);
1553            (dfa.accepts.len() - 1) as u32
1554        });
1555        let (ascii_row, wide_row) = split_rows(&state_rows.segments);
1556        let state_id = u16::try_from(dfa.states.len()).expect("state ID overflow");
1557        let ascii_run = AsciiRun::classify(&ascii_row, state_id);
1558        dfa.states.push(CompiledLexerState {
1559            ascii_row: pools.intern_ascii(&mut dfa.ascii_rows, ascii_row),
1560            wide_row: pools.intern_wide(&mut dfa.wide_rows, wide_row),
1561            eof_target: state_rows.eof_target,
1562            accept: accept_id,
1563        });
1564        dfa.ascii_runs.push(ascii_run);
1565        dfa.escape_rows.push(CompiledLexerEscapeRow {
1566            ranges: merge_escape_ranges(&state_rows.escapes).into(),
1567            eof: state_rows.eof_escape,
1568        });
1569    }
1570    dfa.continuations.extend(continuations);
1571}
1572
1573fn merge_escape_ranges(segments: &[(i32, i32, u32)]) -> Vec<CompiledLexerEscapeRange> {
1574    let mut ranges: Vec<CompiledLexerEscapeRange> = Vec::new();
1575    for &(low, high, continuation) in segments {
1576        let low = low.cast_unsigned();
1577        let high = high.cast_unsigned();
1578        if let Some(last) = ranges.last_mut()
1579            && last.continuation == continuation
1580            && last.high.checked_add(1) == Some(low)
1581        {
1582            last.high = high;
1583            continue;
1584        }
1585        ranges.push(CompiledLexerEscapeRange {
1586            low,
1587            high,
1588            continuation,
1589        });
1590    }
1591    ranges
1592}
1593
1594/// Splits sorted segments into the dense ASCII row and merged wide ranges.
1595fn split_rows(segments: &[(i32, i32, u16)]) -> ([u16; ASCII_EDGE_SYMBOLS], Vec<WideRange>) {
1596    let mut ascii = [DEAD_STATE; ASCII_EDGE_SYMBOLS];
1597    let mut wide: Vec<WideRange> = Vec::new();
1598    for &(low, high, target) in segments {
1599        let ascii_high = high.min(ASCII_EDGE_LIMIT - 1);
1600        for code_point in low..=ascii_high {
1601            ascii[code_point.cast_unsigned() as usize] = target;
1602        }
1603        if high >= ASCII_EDGE_LIMIT {
1604            let low = low.max(ASCII_EDGE_LIMIT).cast_unsigned();
1605            let high = high.cast_unsigned();
1606            if let Some(last) = wide.last_mut()
1607                && last.target == target
1608                && last.high + 1 == low
1609            {
1610                last.high = high;
1611                continue;
1612            }
1613            wide.push(WideRange { low, high, target });
1614        }
1615    }
1616    (ascii, wide)
1617}
1618
1619#[cfg(test)]
1620mod tests {
1621    use super::*;
1622    use crate::atn::lexer::{
1623        next_token, next_token_compiled, next_token_compiled_with_hooks, next_token_with_hooks,
1624    };
1625    use crate::atn::serialized::{AtnDeserializer, SerializedAtn};
1626    use crate::char_stream::{CharStream, InputStream, TextInterval};
1627    use crate::int_stream::IntStream;
1628    use crate::lexer::{BaseLexer, Lexer};
1629    use crate::recognizer::RecognizerData;
1630    use crate::token::{TOKEN_EOF, Token, TokenSink, TokenStore};
1631    use crate::vocabulary::Vocabulary;
1632
1633    #[derive(Debug, Eq, PartialEq)]
1634    struct TokenSnapshot {
1635        token_type: i32,
1636        text: String,
1637        channel: i32,
1638        start: usize,
1639        stop: usize,
1640        start_byte: Option<usize>,
1641        stop_byte: Option<usize>,
1642        line: usize,
1643        column: usize,
1644    }
1645
1646    #[derive(Debug, Eq, PartialEq)]
1647    struct StreamSnapshot {
1648        tokens: Vec<TokenSnapshot>,
1649        errors: Vec<String>,
1650        final_mode: i32,
1651        popped_modes: Vec<i32>,
1652    }
1653
1654    fn compiled_token<I>(
1655        lexer: &mut BaseLexer<I>,
1656        atn: &LexerAtn,
1657        dfa: &CompiledLexerDfa,
1658    ) -> TokenSnapshot
1659    where
1660        I: CharStream,
1661    {
1662        let mut store = TokenStore::new(lexer.source_text(), lexer.source_name());
1663        let mut sink = TokenSink::new(&mut store);
1664        let id = next_token_compiled(lexer, &mut sink, atn, dfa).expect("test token should fit");
1665        let token = sink.view(id).expect("emitted token should exist");
1666        TokenSnapshot {
1667            token_type: token.token_type(),
1668            text: token.text_or_empty().to_owned(),
1669            channel: token.channel(),
1670            start: token.start(),
1671            stop: token.stop(),
1672            start_byte: token.start_byte(),
1673            stop_byte: token.stop_byte(),
1674            line: token.line(),
1675            column: token.column(),
1676        }
1677    }
1678
1679    fn interpreted_token(lexer: &mut BaseLexer<InputStream>, atn: &LexerAtn) -> TokenSnapshot {
1680        let mut store = TokenStore::new(lexer.source_text(), lexer.source_name());
1681        let mut sink = TokenSink::new(&mut store);
1682        let id = next_token(lexer, &mut sink, atn).expect("test token should fit");
1683        let token = sink.view(id).expect("emitted token should exist");
1684        TokenSnapshot {
1685            token_type: token.token_type(),
1686            text: token.text_or_empty().to_owned(),
1687            channel: token.channel(),
1688            start: token.start(),
1689            stop: token.stop(),
1690            start_byte: token.start_byte(),
1691            stop_byte: token.stop_byte(),
1692            line: token.line(),
1693            column: token.column(),
1694        }
1695    }
1696
1697    #[derive(Clone, Debug)]
1698    struct FallbackInput(InputStream);
1699
1700    impl IntStream for FallbackInput {
1701        fn consume(&mut self) {
1702            self.0.consume();
1703        }
1704
1705        fn la(&mut self, offset: isize) -> i32 {
1706            self.0.la(offset)
1707        }
1708
1709        fn index(&self) -> usize {
1710            self.0.index()
1711        }
1712
1713        fn seek(&mut self, index: usize) {
1714            self.0.seek(index);
1715        }
1716
1717        fn size(&self) -> usize {
1718            self.0.size()
1719        }
1720
1721        fn source_name(&self) -> &str {
1722            self.0.source_name()
1723        }
1724    }
1725
1726    // Deliberately implements none of the optional fast paths.
1727    impl CharStream for FallbackInput {
1728        fn text(&self, interval: TextInterval) -> String {
1729            self.0.text(interval)
1730        }
1731    }
1732
1733    fn recognizer_data() -> RecognizerData {
1734        RecognizerData::new(
1735            "T",
1736            Vocabulary::new(
1737                [None, Some("'ab'"), Some("' '")],
1738                [None, Some("AB"), Some("WS")],
1739                [None::<&str>, None, None],
1740            ),
1741        )
1742    }
1743
1744    /// Two-rule lexer (`AB: 'ab';` and `WS: ' ' -> skip;`), with rule 0's
1745    /// final epsilon optionally replaced by a semantic predicate transition.
1746    // `#[rustfmt::skip]`: this serialized ATN is a positional `i32` stream whose
1747    // meaning comes from its one-record-per-line grouping. Leaving it to rustfmt
1748    // explodes it to one integer per line (the cast/path elements defeat the
1749    // packed-list tactic) and destroys the mapping to the ANTLR ATN layout.
1750    #[rustfmt::skip]
1751    fn two_rule_atn(with_predicate: bool) -> LexerAtn {
1752        let epsilon_or_predicate = if with_predicate { 4 } else { 1 };
1753        AtnDeserializer::new(&SerializedAtn::from_i32(&[
1754            4, 0, 2, // version, lexer, max token type
1755            9, // states
1756            6, -1, // 0 token start
1757            2, 0, // 1 rule 0 start
1758            1, 0, // 2
1759            1, 0, // 3
1760            7, 0, // 4 rule 0 stop
1761            2, 1, // 5 rule 1 start
1762            1, 1, // 6
1763            1, 1, // 7
1764            7, 1, // 8 rule 1 stop
1765            0, // non-greedy
1766            0, // precedence
1767            2, // rules
1768            1, 1, // rule 0 starts at 1, token type 1
1769            5, 2, // rule 1 starts at 5, token type 2
1770            1, // modes
1771            0, // default mode starts at 0
1772            0, // sets
1773            8, // edges
1774            0, 1, 1, 0, 0, 0, // start -> rule 0
1775            0, 5, 1, 0, 0, 0, // start -> rule 1
1776            1, 2, 5, 'a' as i32, 0, 0, // 'a'
1777            2, 3, 5, 'b' as i32, 0, 0, // 'b'
1778            3, 4, epsilon_or_predicate, 0, 0, 0, // epsilon or predicate to stop
1779            5, 6, 5, ' ' as i32, 0, 0, // ' '
1780            6, 7, 1, 0, 0, 0, //
1781            7, 8, 6, 1, 0, 0, // action 0, then stop
1782            1, // decisions
1783            0, 1, // lexer actions
1784            6, 0, 0, // skip
1785        ]))
1786        .deserialize()
1787        .expect("artificial lexer ATN should deserialize")
1788    }
1789
1790    /// One token rule matching `[\u{100}-\u{200}]+`, exercising wide rows.
1791    // `#[rustfmt::skip]`: keep the one-record-per-line ATN grouping (see
1792    // `two_rule_atn`). Pure literals keep rustfmt's packed layout today, but a
1793    // single cast/path element would explode it, so pin it defensively.
1794    #[rustfmt::skip]
1795    fn wide_range_atn() -> LexerAtn {
1796        AtnDeserializer::new(&SerializedAtn::from_i32(&[
1797            4, 0, 1, // version, lexer, max token type
1798            5, // states
1799            6, -1, // 0 token start
1800            2, 0, // 1 rule 0 start
1801            1, 0, // 2
1802            1, 0, // 3
1803            7, 0, // 4 rule 0 stop
1804            0, // non-greedy
1805            0, // precedence
1806            1, // rules
1807            1, 1, // rule 0 starts at 1, token type 1
1808            1, // modes
1809            0, // default mode starts at 0
1810            0, // sets
1811            5, // edges
1812            0, 1, 1, 0, 0, 0, // start -> rule 0
1813            1, 2, 1, 0, 0, 0, //
1814            2, 3, 2, 0x100, 0x200, 0, // range
1815            3, 2, 1, 0, 0, 0, // greedy loop continues first
1816            3, 4, 1, 0, 0, 0, // then exits to stop
1817            0, // decisions
1818            0, // lexer actions
1819        ]))
1820        .deserialize()
1821        .expect("artificial wide-range lexer ATN should deserialize")
1822    }
1823
1824    /// One token rule matching one or more characters other than newline,
1825    /// double quote, or backslash. Its loop state has exactly three ASCII
1826    /// exits and is therefore eligible for `Until3`.
1827    #[rustfmt::skip]
1828    fn complement_loop_atn() -> LexerAtn {
1829        AtnDeserializer::new(&SerializedAtn::from_i32(&[
1830            4, 0, 1, // version, lexer, max token type
1831            5, // states
1832            6, -1, // 0 token start
1833            2, 0, // 1 rule 0 start
1834            1, 0, // 2
1835            1, 0, // 3
1836            7, 0, // 4 rule 0 stop
1837            0, // non-greedy
1838            0, // precedence
1839            1, // rules
1840            1, 1, // rule 0 starts at 1, token type 1
1841            1, // modes
1842            0, // default mode starts at 0
1843            1, // sets
1844            3, 0, // three intervals, no EOF
1845            '\n' as i32, '\n' as i32,
1846            '"' as i32, '"' as i32,
1847            '\\' as i32, '\\' as i32,
1848            5, // edges
1849            0, 1, 1, 0, 0, 0, // start -> rule 0
1850            1, 2, 1, 0, 0, 0, //
1851            2, 3, 8, 0, 0, 0, // not set 0
1852            3, 2, 1, 0, 0, 0, // greedy loop continues first
1853            3, 4, 1, 0, 0, 0, // then exits to stop
1854            0, // decisions
1855            0, // lexer actions
1856        ]))
1857        .deserialize()
1858        .expect("artificial complement-loop lexer ATN should deserialize")
1859    }
1860
1861    /// Three `+` rules for decimal digits, ASCII identifier continuations, and
1862    /// whitespace. Their loop states exercise one-, three-, and four-range
1863    /// descriptors; trailing built-in actions cover channel changes, mode
1864    /// stack changes, and skipped tokens.
1865    #[rustfmt::skip]
1866    fn range_loop_atn() -> LexerAtn {
1867        AtnDeserializer::new(&SerializedAtn::from_i32(&[
1868            4, 0, 3, // version, lexer, max token type
1869            13, // states
1870            6, -1, // 0 token start
1871            2, 0, // 1 number start
1872            1, 0, // 2
1873            1, 0, // 3
1874            7, 0, // 4 number stop
1875            2, 1, // 5 identifier start
1876            1, 1, // 6
1877            1, 1, // 7
1878            7, 1, // 8 identifier stop
1879            2, 2, // 9 whitespace start
1880            1, 2, // 10
1881            1, 2, // 11
1882            7, 2, // 12 whitespace stop
1883            0, // non-greedy
1884            0, // precedence
1885            3, // rules
1886            1, 1, // number starts at 1, token type 1
1887            5, 2, // identifier starts at 5, token type 2
1888            9, 3, // whitespace starts at 9, token type 3
1889            1, // modes
1890            0, // default mode starts at 0
1891            3, // sets
1892            1, 0, // set 0: decimal digits
1893            '0' as i32, '9' as i32,
1894            4, 0, // set 1: ASCII identifier continuation
1895            '0' as i32, '9' as i32,
1896            'A' as i32, 'Z' as i32,
1897            '_' as i32, '_' as i32,
1898            'a' as i32, 'z' as i32,
1899            3, 0, // set 2: tab/newline, carriage return, space
1900            '\t' as i32, '\n' as i32,
1901            '\r' as i32, '\r' as i32,
1902            ' ' as i32, ' ' as i32,
1903            15, // edges
1904            0, 1, 1, 0, 0, 0, // start -> number
1905            0, 5, 1, 0, 0, 0, // start -> identifier
1906            0, 9, 1, 0, 0, 0, // start -> whitespace
1907            1, 2, 1, 0, 0, 0, //
1908            2, 3, 7, 0, 0, 0, // number set
1909            3, 2, 1, 0, 0, 0, // loop
1910            3, 4, 6, 0, 0, 0, // channel action, then stop
1911            5, 6, 1, 0, 0, 0, //
1912            6, 7, 7, 1, 0, 0, // identifier set
1913            7, 6, 1, 0, 0, 0, // loop
1914            7, 8, 6, 1, 1, 0, // push-mode action, then stop
1915            9, 10, 1, 0, 0, 0, //
1916            10, 11, 7, 2, 0, 0, // whitespace set
1917            11, 10, 1, 0, 0, 0, // loop
1918            11, 12, 6, 2, 2, 0, // skip action, then stop
1919            0, // decisions
1920            3, // lexer actions
1921            0, 7, 0, // channel 7
1922            5, 0, 0, // push mode 0
1923            6, 0, 0, // skip
1924        ]))
1925        .deserialize()
1926        .expect("artificial range-loop lexer ATN should deserialize")
1927    }
1928
1929    fn compiled_stream(input: &str, atn: &LexerAtn, dfa: &CompiledLexerDfa) -> StreamSnapshot {
1930        let mut lexer = BaseLexer::new(InputStream::new(input), recognizer_data());
1931        let mut tokens = Vec::new();
1932        loop {
1933            let token = compiled_token(&mut lexer, atn, dfa);
1934            let at_eof = token.token_type == TOKEN_EOF;
1935            tokens.push(token);
1936            if at_eof {
1937                break;
1938            }
1939        }
1940        let errors = lexer
1941            .drain_errors()
1942            .into_iter()
1943            .map(|error| error.message)
1944            .collect();
1945        let final_mode = lexer.mode();
1946        let mut popped_modes = Vec::new();
1947        while let Some(mode) = lexer.pop_mode() {
1948            popped_modes.push(mode);
1949        }
1950        StreamSnapshot {
1951            tokens,
1952            errors,
1953            final_mode,
1954            popped_modes,
1955        }
1956    }
1957
1958    fn serialized_run_offset(dfa: &CompiledLexerDfa, state: usize) -> usize {
1959        4 + dfa.mode_starts.len()
1960            + dfa.states.len() * 4
1961            + dfa.ascii_runs[..state]
1962                .iter()
1963                .map(|run| run.serialized_words())
1964                .sum::<usize>()
1965    }
1966
1967    fn first_serialized_range_offset(dfa: &CompiledLexerDfa) -> usize {
1968        let state = dfa
1969            .ascii_runs
1970            .iter()
1971            .position(|run| matches!(run, AsciiRun::Ranges(_)))
1972            .expect("test DFA should contain a range descriptor");
1973        serialized_run_offset(dfa, state)
1974    }
1975
1976    #[test]
1977    fn recursive_context_detection_enforces_depth_and_cycle_backstops() {
1978        let mut contexts = LexerContextArena::new();
1979        let mut bounded = EMPTY_LEXER_CONTEXT;
1980        for return_state in 0..MAX_CONTEXT_DEPTH {
1981            bounded = contexts.singleton(bounded, return_state);
1982        }
1983        let config = |context| LexerConfig::new(0, 0, context).with_alt_rule_index(0);
1984
1985        assert!(!has_recursive_context(&config(bounded), &contexts));
1986
1987        let too_deep = contexts.singleton(bounded, MAX_CONTEXT_DEPTH);
1988        assert!(has_recursive_context(&config(too_deep), &contexts));
1989
1990        let first = contexts.singleton(EMPTY_LEXER_CONTEXT, 7);
1991        let recursive = contexts.singleton(first, 7);
1992        assert!(has_recursive_context(&config(recursive), &contexts));
1993    }
1994
1995    #[test]
1996    fn deep_union_context_operations_fit_on_a_small_native_stack() {
1997        let mut contexts = LexerContextArena::new();
1998        let mut workspace = PredictionWorkspace::default();
1999        let mut context = contexts.singleton(EMPTY_LEXER_CONTEXT, 0);
2000        for return_state in 1..2048 {
2001            let branch = contexts.singleton(EMPTY_LEXER_CONTEXT, return_state);
2002            context = contexts.merge(context, branch, &mut workspace);
2003        }
2004        let expected_contexts = contexts.len() - 1;
2005        let config = LexerConfig::new(0, 0, context).with_alt_rule_index(0);
2006
2007        std::thread::Builder::new()
2008            .stack_size(64 * 1024)
2009            .spawn(move || {
2010                assert!(!has_recursive_context(&config, &contexts));
2011
2012                let mut ids = FxHashMap::default();
2013                ids.insert(EMPTY_LEXER_CONTEXT, 0);
2014                let mut compiled = Vec::new();
2015                let compiled_context =
2016                    compile_context(&contexts, config.context, &mut ids, &mut compiled);
2017                assert_eq!(compiled.len(), expected_contexts);
2018                assert_eq!(compiled_context as usize, expected_contexts);
2019            })
2020            .expect("small-stack context thread should start")
2021            .join()
2022            .expect("deep context traversal should not overflow");
2023    }
2024
2025    #[test]
2026    fn ascii_run_classifies_and_scans_only_exact_self_loops() {
2027        let state = 7;
2028        let mut row = [state; ASCII_EDGE_SYMBOLS];
2029        assert_eq!(AsciiRun::classify(&row, state), AsciiRun::Any);
2030        assert_eq!(
2031            AsciiRun::Any.scan(b"body"),
2032            Some(AsciiRunScan {
2033                bytes: 4,
2034                found_exit: false,
2035                range: None,
2036            })
2037        );
2038
2039        row[usize::from(b'\n')] = DEAD_STATE;
2040        row[usize::from(b'"')] = 3;
2041        row[usize::from(b'\\')] = ESCAPE_STATE;
2042        let run = AsciiRun::classify(&row, state);
2043        assert_eq!(run, AsciiRun::Until3(b'\n', b'"', b'\\'));
2044        assert_eq!(
2045            run.scan(b"body\\tail"),
2046            Some(AsciiRunScan {
2047                bytes: 4,
2048                found_exit: true,
2049                range: None,
2050            })
2051        );
2052
2053        row[usize::from(b'\r')] = DEAD_STATE;
2054        assert_eq!(AsciiRun::classify(&row, state), AsciiRun::None);
2055        assert_eq!(AsciiRun::None.scan(b"body"), None);
2056
2057        let mut identifier = [DEAD_STATE; ASCII_EDGE_SYMBOLS];
2058        for byte in b'0'..=b'9' {
2059            identifier[usize::from(byte)] = state;
2060        }
2061        for byte in b'A'..=b'Z' {
2062            identifier[usize::from(byte)] = state;
2063        }
2064        identifier[usize::from(b'_')] = state;
2065        for byte in b'a'..=b'z' {
2066            identifier[usize::from(byte)] = state;
2067        }
2068        let AsciiRun::Ranges(ranges) = AsciiRun::classify(&identifier, state) else {
2069            panic!("identifier row should produce a range descriptor");
2070        };
2071        assert_eq!(ranges.count(), 4);
2072        assert_eq!(
2073            AsciiRun::Ranges(ranges)
2074                .scan(b"abc_123!")
2075                .expect("range descriptor should scan")
2076                .bytes,
2077            7
2078        );
2079
2080        let mut unsupported = [DEAD_STATE; ASCII_EDGE_SYMBOLS];
2081        for byte in *b"acegi" {
2082            unsupported[usize::from(byte)] = state;
2083        }
2084        assert_eq!(AsciiRun::classify(&unsupported, state), AsciiRun::None);
2085    }
2086
2087    #[test]
2088    fn run_scans_match_scalar_compiled_walks_for_random_ascii() {
2089        let atn = complement_loop_atn();
2090        let accelerated = CompiledLexerDfa::compile(&atn);
2091        assert!(
2092            accelerated
2093                .ascii_runs
2094                .iter()
2095                .any(|run| matches!(run, AsciiRun::Until3(..)))
2096        );
2097        let mut scalar = accelerated.clone();
2098        for run in &mut scalar.ascii_runs {
2099            *run = AsciiRun::None;
2100        }
2101
2102        let mut inputs = vec![
2103            "a".repeat(512),
2104            format!(
2105                "{}\"{}\\{}\n{}",
2106                "a".repeat(64),
2107                "b".repeat(65),
2108                "c".repeat(66),
2109                "d".repeat(67)
2110            ),
2111        ];
2112        let mut random = 0xA5A5_79E3_u32;
2113        for _ in 0..128 {
2114            random = random.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
2115            let len = (random as usize) & 0xFF;
2116            let mut input = String::with_capacity(len);
2117            for _ in 0..len {
2118                random = random.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
2119                input.push(char::from((random >> 25) as u8));
2120            }
2121            inputs.push(input);
2122        }
2123
2124        for input in inputs {
2125            assert_eq!(
2126                compiled_stream(&input, &atn, &accelerated),
2127                compiled_stream(&input, &atn, &scalar),
2128                "compiled walks diverged for {input:?}"
2129            );
2130        }
2131    }
2132
2133    #[test]
2134    fn range_scans_match_scalar_compiled_token_streams() {
2135        let atn = range_loop_atn();
2136        let accelerated = CompiledLexerDfa::compile(&atn);
2137        let counts = accelerated
2138            .ascii_runs
2139            .iter()
2140            .filter_map(|run| match run {
2141                AsciiRun::Ranges(ranges) => Some(ranges.count()),
2142                _ => None,
2143            })
2144            .collect::<Vec<_>>();
2145        assert!(counts.contains(&1), "{counts:?}");
2146        assert!(counts.contains(&3), "{counts:?}");
2147        assert!(counts.contains(&4), "{counts:?}");
2148
2149        let mut scalar = accelerated.clone();
2150        for run in &mut scalar.ascii_runs {
2151            if matches!(run, AsciiRun::Ranges(_)) {
2152                *run = AsciiRun::None;
2153            }
2154        }
2155        let input = format!(
2156            "{}!\t{}\r\n{} {}",
2157            "identifier_0123456789".repeat(12),
2158            "1234567890".repeat(20),
2159            "Another_identifier_9876543210".repeat(10),
2160            "short_123"
2161        );
2162
2163        let accelerated = compiled_stream(&input, &atn, &accelerated);
2164        let scalar = compiled_stream(&input, &atn, &scalar);
2165        assert_eq!(accelerated, scalar);
2166        assert_eq!(accelerated.final_mode, 0);
2167        assert_eq!(accelerated.popped_modes, [0, 0, 0]);
2168        assert!(
2169            accelerated.tokens.iter().any(|token| token.channel == 7),
2170            "{accelerated:?}"
2171        );
2172        assert!(
2173            accelerated.tokens.iter().any(|token| token.line > 1),
2174            "{accelerated:?}"
2175        );
2176        assert_eq!(
2177            accelerated
2178                .tokens
2179                .last()
2180                .expect("stream includes EOF")
2181                .token_type,
2182            TOKEN_EOF
2183        );
2184        assert_eq!(
2185            accelerated.errors,
2186            ["token recognition error at: '!'".to_owned()]
2187        );
2188    }
2189
2190    #[test]
2191    fn compiled_dfa_matches_longest_token_and_skips() {
2192        let atn = two_rule_atn(false);
2193        let dfa = CompiledLexerDfa::compile(&atn);
2194        assert!(dfa.has_compiled_modes());
2195        assert!(dfa.mode_start(0).is_some());
2196
2197        let mut lexer = BaseLexer::new(InputStream::new(" ab"), recognizer_data());
2198        let token = compiled_token(&mut lexer, &atn, &dfa);
2199        assert_eq!(token.token_type, 1);
2200        assert_eq!(token.text, "ab");
2201        assert_eq!(compiled_token(&mut lexer, &atn, &dfa).token_type, TOKEN_EOF);
2202    }
2203
2204    #[test]
2205    fn predicate_edge_resumes_the_interpreter_for_true_and_false_outcomes() {
2206        let atn = two_rule_atn(true);
2207        let dfa = CompiledLexerDfa::compile(&atn);
2208        // The predicate sits mid-rule, so the mode still compiles; only the
2209        // edge that would cross it resumes the interpreter.
2210        assert!(dfa.mode_start(0).is_some());
2211        assert!(
2212            !dfa.continuations.is_empty(),
2213            "predicate edge should preserve a narrowed continuation"
2214        );
2215
2216        let mut lexer = BaseLexer::new(InputStream::new(" ab"), recognizer_data());
2217        let mut store = TokenStore::new(lexer.source_text(), lexer.source_name());
2218        let mut sink = TokenSink::new(&mut store);
2219        let mut true_predicate_calls = 0;
2220        let id = next_token_compiled_with_hooks(
2221            &mut lexer,
2222            &mut sink,
2223            &atn,
2224            &dfa,
2225            |_, _| {},
2226            |_, _| {
2227                true_predicate_calls += 1;
2228                true_predicate_calls == 1
2229            },
2230            |_, _, _| {},
2231        )
2232        .expect("test token should fit");
2233        let token = sink.view(id).expect("emitted token should exist");
2234        assert_eq!(token.token_type(), 1);
2235        assert_eq!(token.text(), Some("ab"));
2236        assert_eq!(true_predicate_calls, 1);
2237
2238        let mut compiled = BaseLexer::new(InputStream::new("ab"), recognizer_data());
2239        let mut interpreted = BaseLexer::new(InputStream::new("ab"), recognizer_data());
2240        let mut compiled_store = TokenStore::new(compiled.source_text(), compiled.source_name());
2241        let mut interpreted_store =
2242            TokenStore::new(interpreted.source_text(), interpreted.source_name());
2243        let mut compiled_sink = TokenSink::new(&mut compiled_store);
2244        let mut interpreted_sink = TokenSink::new(&mut interpreted_store);
2245        let mut compiled_predicate_calls = 0;
2246        let compiled_id = next_token_compiled_with_hooks(
2247            &mut compiled,
2248            &mut compiled_sink,
2249            &atn,
2250            &dfa,
2251            |_, _| {},
2252            |_, _| {
2253                compiled_predicate_calls += 1;
2254                false
2255            },
2256            |_, _, _| {},
2257        )
2258        .expect("false predicate should recover to EOF");
2259        let mut interpreted_predicate_calls = 0;
2260        let interpreted_id = next_token_with_hooks(
2261            &mut interpreted,
2262            &mut interpreted_sink,
2263            &atn,
2264            |_, _| {},
2265            |_, _| {
2266                interpreted_predicate_calls += 1;
2267                false
2268            },
2269            |_, _, _| {},
2270        )
2271        .expect("interpreted false predicate should recover to EOF");
2272        assert_eq!(compiled_predicate_calls, interpreted_predicate_calls);
2273        assert_eq!(compiled_predicate_calls, 1);
2274        assert_eq!(
2275            compiled_sink
2276                .view(compiled_id)
2277                .expect("compiled token should exist")
2278                .token_type(),
2279            interpreted_sink
2280                .view(interpreted_id)
2281                .expect("interpreted token should exist")
2282                .token_type()
2283        );
2284        assert_eq!(
2285            compiled
2286                .drain_errors()
2287                .into_iter()
2288                .map(|error| error.message)
2289                .collect::<Vec<_>>(),
2290            interpreted
2291                .drain_errors()
2292                .into_iter()
2293                .map(|error| error.message)
2294                .collect::<Vec<_>>()
2295        );
2296    }
2297
2298    #[test]
2299    fn compiled_dfa_walks_wide_ranges() {
2300        let atn = wide_range_atn();
2301        let dfa = CompiledLexerDfa::compile(&atn);
2302        assert!(dfa.mode_start(0).is_some());
2303
2304        let mut lexer = BaseLexer::new(InputStream::new("ĀĂ"), recognizer_data());
2305        let token = compiled_token(&mut lexer, &atn, &dfa);
2306        assert_eq!(token.token_type, 1);
2307        assert_eq!(token.text, "ĀĂ");
2308        assert_eq!(compiled_token(&mut lexer, &atn, &dfa).token_type, TOKEN_EOF);
2309    }
2310
2311    #[test]
2312    fn compiled_dfa_keeps_custom_streams_on_the_compatible_fallback() {
2313        let atn = two_rule_atn(false);
2314        let dfa = CompiledLexerDfa::compile(&atn);
2315        let mut lexer = BaseLexer::new(FallbackInput(InputStream::new(" ab")), recognizer_data());
2316
2317        let token = compiled_token(&mut lexer, &atn, &dfa);
2318        assert_eq!(token.token_type, 1);
2319        assert_eq!(token.text, "ab");
2320        assert_eq!((token.line, token.column), (1, 1));
2321        assert_eq!(lexer.input().index(), 3);
2322    }
2323
2324    #[cfg(feature = "perf-counters")]
2325    #[test]
2326    fn lexer_counters_distinguish_ascii_unicode_and_replay_paths() {
2327        let ascii_atn = two_rule_atn(false);
2328        let ascii_dfa = CompiledLexerDfa::compile(&ascii_atn);
2329        crate::perf::reset();
2330        let mut ascii = BaseLexer::new(InputStream::new(" ab"), recognizer_data());
2331        let token = compiled_token(&mut ascii, &ascii_atn, &ascii_dfa);
2332        assert_eq!(token.text, "ab");
2333        let [direct, generic, replay, bulk] = crate::perf::lexer_snapshot();
2334        assert!(direct >= 3, "{direct}");
2335        assert_eq!(generic, 0);
2336        assert_eq!(replay, 0);
2337        assert_eq!(bulk, 3);
2338
2339        let unicode_atn = wide_range_atn();
2340        let unicode_dfa = CompiledLexerDfa::compile(&unicode_atn);
2341        crate::perf::reset();
2342        let mut unicode = BaseLexer::new(InputStream::new("ĀĂ"), recognizer_data());
2343        let token = compiled_token(&mut unicode, &unicode_atn, &unicode_dfa);
2344        assert_eq!(token.text, "ĀĂ");
2345        let [direct, generic, replay, bulk] = crate::perf::lexer_snapshot();
2346        assert_eq!(direct, 0);
2347        assert!(generic >= 2, "{generic}");
2348        assert_eq!(replay, 0);
2349        assert_eq!(bulk, 2);
2350
2351        crate::perf::reset();
2352        let mut fallback =
2353            BaseLexer::new(FallbackInput(InputStream::new(" ab")), recognizer_data());
2354        let token = compiled_token(&mut fallback, &ascii_atn, &ascii_dfa);
2355        assert_eq!(token.text, "ab");
2356        let [direct, generic, replay, bulk] = crate::perf::lexer_snapshot();
2357        assert_eq!(direct, 0);
2358        assert!(generic >= 3, "{generic}");
2359        assert_eq!(replay, 3);
2360        assert_eq!(bulk, 0);
2361    }
2362
2363    #[cfg(feature = "perf-counters")]
2364    #[test]
2365    fn lexer_counters_report_compiled_run_scan_coverage() {
2366        let atn = complement_loop_atn();
2367        let dfa = CompiledLexerDfa::compile(&atn);
2368        crate::perf::reset();
2369        let input = format!("{}\"", "a".repeat(512));
2370        let mut lexer = BaseLexer::new(InputStream::new(input), recognizer_data());
2371        let token = compiled_token(&mut lexer, &atn, &dfa);
2372        assert_eq!(token.text.len(), 512);
2373
2374        let [scalar, calls, bytes, exits, ends, rejected] = crate::perf::lexer_run_snapshot();
2375        assert_eq!(scalar, 10);
2376        assert_eq!(calls, 1);
2377        assert_eq!(bytes, 503);
2378        assert_eq!(exits, 1);
2379        assert_eq!(ends, 0);
2380        assert_eq!(rejected, 0);
2381    }
2382
2383    #[cfg(feature = "perf-counters")]
2384    #[test]
2385    fn lexer_counters_report_range_descriptor_and_scan_coverage() {
2386        crate::perf::reset();
2387        let before = crate::perf::lexer_range_descriptor_snapshot();
2388        let atn = range_loop_atn();
2389        let dfa = CompiledLexerDfa::compile(&atn);
2390        let descriptors = crate::perf::lexer_range_descriptor_snapshot();
2391        assert!(descriptors[3] > before[3], "{before:?} -> {descriptors:?}");
2392        assert!(descriptors[5] > before[5], "{before:?} -> {descriptors:?}");
2393        assert!(descriptors[6] > before[6], "{before:?} -> {descriptors:?}");
2394        assert!(descriptors[7] > before[7], "{before:?} -> {descriptors:?}");
2395        assert!(descriptors[8] > before[8], "{before:?} -> {descriptors:?}");
2396        assert!(descriptors[9] > before[9], "{before:?} -> {descriptors:?}");
2397
2398        crate::perf::reset();
2399        let input = format!(
2400            "{} {} {}",
2401            "long_identifier_0123456789".repeat(20),
2402            "1234567890".repeat(40),
2403            " \t\r\n".repeat(80)
2404        );
2405        let _ = compiled_stream(&input, &atn, &dfa);
2406        let scans = crate::perf::lexer_range_scan_snapshot();
2407        assert!(scans[0] > 0, "{scans:?}");
2408        assert!(scans[1] > 0, "{scans:?}");
2409        assert!(scans[2] > 0, "{scans:?}");
2410        assert!(scans[3] > 0, "{scans:?}");
2411        assert!(scans[4] > 0, "{scans:?}");
2412    }
2413
2414    #[test]
2415    fn compiled_dfa_reports_recognition_errors_like_the_interpreter() {
2416        let atn = wide_range_atn();
2417        let dfa = CompiledLexerDfa::compile(&atn);
2418
2419        let mut compiled = BaseLexer::new(InputStream::new("zĀ"), recognizer_data());
2420        let mut interpreted = BaseLexer::new(InputStream::new("zĀ"), recognizer_data());
2421        loop {
2422            let compiled_token = compiled_token(&mut compiled, &atn, &dfa);
2423            let interpreted_token = interpreted_token(&mut interpreted, &atn);
2424            assert_eq!(compiled_token, interpreted_token);
2425            if compiled_token.token_type == TOKEN_EOF {
2426                break;
2427            }
2428        }
2429        let compiled_errors: Vec<String> = compiled
2430            .drain_errors()
2431            .into_iter()
2432            .map(|error| error.message)
2433            .collect();
2434        let interpreted_errors: Vec<String> = interpreted
2435            .drain_errors()
2436            .into_iter()
2437            .map(|error| error.message)
2438            .collect();
2439        assert_eq!(compiled_errors, vec!["token recognition error at: 'z'"]);
2440        assert_eq!(compiled_errors, interpreted_errors);
2441    }
2442
2443    #[test]
2444    fn serialization_round_trips() {
2445        let atn = range_loop_atn();
2446        let dfa = CompiledLexerDfa::compile(&atn);
2447        let stream = dfa.serialize();
2448
2449        let restored =
2450            CompiledLexerDfa::from_serialized(&stream).expect("stream should deserialize");
2451        assert_eq!(restored.serialize(), stream);
2452        assert_eq!(restored.continuations.len(), dfa.continuations.len());
2453        assert_eq!(restored.ascii_runs, dfa.ascii_runs);
2454
2455        let mut lexer = BaseLexer::new(InputStream::new("identifier_123"), recognizer_data());
2456        let token = compiled_token(&mut lexer, &atn, &restored);
2457        assert_eq!(token.token_type, 2);
2458        assert_eq!(token.text, "identifier_123");
2459
2460        // A stream from a different runtime version is rejected, not trusted.
2461        let mut wrong_tag = stream;
2462        wrong_tag[0] ^= 1;
2463        assert!(CompiledLexerDfa::from_serialized(&wrong_tag).is_none());
2464    }
2465
2466    #[test]
2467    fn serialized_run_descriptors_are_validated_against_ascii_rows() {
2468        let dfa = CompiledLexerDfa::compile(&complement_loop_atn());
2469        let state = dfa
2470            .ascii_runs
2471            .iter()
2472            .position(|&run| run != AsciiRun::Any)
2473            .expect("test grammar should contain a state that is not Any");
2474        let mut stream = dfa.serialize();
2475        stream[serialized_run_offset(&dfa, state)] = 1;
2476
2477        assert!(CompiledLexerDfa::from_serialized(&stream).is_none());
2478    }
2479
2480    #[test]
2481    fn malformed_serialized_range_descriptors_are_rejected() {
2482        let dfa = CompiledLexerDfa::compile(&range_loop_atn());
2483        let stream = dfa.serialize();
2484        let range = first_serialized_range_offset(&dfa);
2485        assert_eq!(stream[range].to_le_bytes()[0], 5);
2486
2487        let mut zero_ranges = stream.clone();
2488        zero_ranges[range] = 5;
2489        assert!(CompiledLexerDfa::from_serialized(&zero_ranges).is_none());
2490
2491        let mut too_many_ranges = stream.clone();
2492        too_many_ranges[range] = 5 | (5 << 8);
2493        assert!(CompiledLexerDfa::from_serialized(&too_many_ranges).is_none());
2494
2495        let mut adjacent_ranges = stream.clone();
2496        adjacent_ranges[range] = 5 | (2 << 8);
2497        adjacent_ranges[range + 1] = u32::from(b'a')
2498            | (u32::from(b'm') << 8)
2499            | (u32::from(b'n') << 16)
2500            | (u32::from(b'z') << 24);
2501        adjacent_ranges[range + 2] = 0;
2502        assert!(CompiledLexerDfa::from_serialized(&adjacent_ranges).is_none());
2503
2504        let mut non_ascii_range = stream.clone();
2505        non_ascii_range[range] = 5 | (1 << 8);
2506        non_ascii_range[range + 1] = u32::from(b'a') | (128 << 8);
2507        non_ascii_range[range + 2] = 0;
2508        assert!(CompiledLexerDfa::from_serialized(&non_ascii_range).is_none());
2509
2510        let mut nonzero_padding = stream.clone();
2511        nonzero_padding[range] = 5 | (1 << 8);
2512        nonzero_padding[range + 1] = u32::from(b'0')
2513            | (u32::from(b'9') << 8)
2514            | (u32::from(b'A') << 16)
2515            | (u32::from(b'Z') << 24);
2516        nonzero_padding[range + 2] = 0;
2517        assert!(CompiledLexerDfa::from_serialized(&nonzero_padding).is_none());
2518
2519        let mut truncated = stream;
2520        truncated.truncate(range + 2);
2521        assert!(CompiledLexerDfa::from_serialized(&truncated).is_none());
2522    }
2523
2524    #[test]
2525    fn malformed_wide_rows_are_rejected() {
2526        let atn = wide_range_atn();
2527        let stream = CompiledLexerDfa::compile(&atn).serialize();
2528
2529        // Invert the [0x100, 0x200] range's bounds in place; a broken wide
2530        // row must fail validation, not silently misroute binary searches.
2531        let position = stream
2532            .windows(2)
2533            .position(|pair| pair == [0x100, 0x200])
2534            .expect("wide-range test grammar serializes its range bounds");
2535        let mut inverted = stream;
2536        inverted.swap(position, position + 1);
2537        assert!(CompiledLexerDfa::from_serialized(&inverted).is_none());
2538    }
2539
2540    #[test]
2541    fn malformed_escape_continuations_are_rejected() {
2542        let atn = two_rule_atn(true);
2543        let mut dfa = CompiledLexerDfa::compile(&atn);
2544        let range = dfa
2545            .escape_rows
2546            .iter_mut()
2547            .flat_map(|row| row.ranges.iter_mut())
2548            .next()
2549            .expect("predicate grammar should contain an escape range");
2550        range.continuation = u32::MAX - 1;
2551
2552        assert!(CompiledLexerDfa::from_serialized(&dfa.serialize()).is_none());
2553    }
2554
2555    #[test]
2556    fn escape_range_merging_does_not_wrap_maximum_bound() {
2557        let ranges = merge_escape_ranges(&[(-1, -1, 0), (0, 0, 0)]);
2558
2559        assert_eq!(ranges.len(), 2);
2560        assert_eq!((ranges[0].low, ranges[0].high), (u32::MAX, u32::MAX));
2561        assert_eq!((ranges[1].low, ranges[1].high), (0, 0));
2562    }
2563
2564    #[test]
2565    fn force_interpreted_bypasses_compiled_tables() {
2566        let atn = two_rule_atn(false);
2567        let dfa = CompiledLexerDfa::compile(&atn);
2568
2569        let mut lexer = BaseLexer::new(InputStream::new("ab"), recognizer_data());
2570        lexer.set_force_interpreted(true);
2571        let token = compiled_token(&mut lexer, &atn, &dfa);
2572        assert_eq!(token.token_type, 1);
2573        // The interpreter path records the learned-DFA trace; the compiled
2574        // walk does not.
2575        assert!(!lexer.lexer_dfa_string().is_empty());
2576    }
2577}