Skip to main content

antlr4_runtime/atn/
lexer_dfa.rs

1//! Ahead-of-time lexer DFA compilation.
2//!
3//! ANTLR runtimes normally discover the lexer DFA lazily: the ATN simulator
4//! computes epsilon closures per input character and caches the resulting
5//! config sets. This module runs the same subset construction eagerly over
6//! the entire character alphabet, once per grammar, so token matching becomes
7//! one table lookup per character with no closure computation, hashing, or
8//! config allocation on the hot path.
9//!
10//! Compilation is conservative at the edge level: a transition whose target
11//! closure crosses a semantic predicate (whose outcome exists only at parse
12//! time), grows an unbounded rule-call stack (recursive lexer rules such as
13//! nested comments), or exceeds the state budget is compiled as an *escape*
14//! edge. A token walk that reaches an escape edge is re-matched from the
15//! token start by the ATN interpreter, so rare dynamic constructs never
16//! poison the rest of the mode. Because the construction reuses the
17//! interpreter's own closure, pruning, and accept-selection code, a compiled
18//! walk that does not escape reproduces interpreter behavior exactly.
19
20use std::collections::HashMap;
21use std::hash::BuildHasherDefault;
22
23use crate::atn::lexer::{
24    LexerConfig, best_accept, epsilon_closure, lexer_action_belongs_to_accept,
25    prune_after_accepts, set_config_state,
26};
27use crate::atn::{Atn, Transition};
28use crate::int_stream::EOF;
29use crate::lexer::{LexerDfaActionKey, LexerDfaConfigKey, LexerDfaKey};
30use crate::prediction::PredictionFxHasher;
31
32#[allow(clippy::disallowed_types)]
33type FxHashMap<K, V> = HashMap<K, V, BuildHasherDefault<PredictionFxHasher>>;
34
35const MIN_CHAR_VALUE: i32 = 0;
36const MAX_CHAR_VALUE: i32 = 0x0010_FFFF;
37
38/// Sentinel state id meaning "no transition".
39pub(super) const DEAD_STATE: u16 = u16::MAX;
40
41/// Sentinel state id meaning "re-match this token with the ATN interpreter".
42pub(super) const ESCAPE_STATE: u16 = u16::MAX - 1;
43
44/// Per-mode state budget; targets past it compile as escape edges. The cap
45/// also bounds compile time for pathological grammars.
46const MAX_MODE_STATES: usize = 4096;
47
48/// Rule-call stacks deeper than this escape to the interpreter, as a backstop
49/// for grammars with extraordinarily long non-recursive fragment chains.
50const MAX_STACK_DEPTH: usize = 32;
51
52/// Configs whose surviving action trace grows past this escape to the
53/// interpreter: a custom action crossed inside a loop is genuinely
54/// position-dependent and cannot compile to finitely many DFA states.
55const MAX_ACTION_TRACES: usize = 16;
56
57/// Dense per-state edge row width, matching the interpreter's DFA cache rows.
58const ASCII_EDGE_SYMBOLS: usize = 128;
59/// [`ASCII_EDGE_SYMBOLS`] as a code point for segment arithmetic.
60const ASCII_EDGE_LIMIT: i32 = 128;
61
62/// A lexer DFA compiled ahead of time from a lexer ATN.
63///
64/// Build one per grammar with [`CompiledLexerDfa::compile`] (generated lexers
65/// cache it in a `OnceLock` beside the deserialized ATN) and match tokens
66/// through [`crate::atn::lexer::next_token_compiled`] or
67/// [`crate::atn::lexer::next_token_compiled_with_hooks`].
68#[derive(Clone, Debug)]
69pub struct CompiledLexerDfa {
70    mode_starts: Vec<Option<u16>>,
71    states: Vec<CompiledLexerState>,
72    ascii_rows: Vec<[u16; ASCII_EDGE_SYMBOLS]>,
73    wide_rows: Vec<Box<[WideRange]>>,
74    accepts: Vec<CompiledLexerAccept>,
75}
76
77/// One compiled DFA state; rows are pooled indices because many states share
78/// identical edge rows (identifier continuations, string bodies, …).
79#[derive(Clone, Copy, Debug)]
80struct CompiledLexerState {
81    ascii_row: u32,
82    wide_row: u32,
83    eof_target: u16,
84    /// Index into `accepts`, or `u32::MAX` when the state does not accept.
85    accept: u32,
86}
87
88/// Inclusive code-point range above the ASCII row mapping to one target.
89#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
90struct WideRange {
91    low: u32,
92    high: u32,
93    target: u16,
94}
95
96/// Accept metadata for one DFA state: the winning lexer rule plus the action
97/// transitions collected on the accepted ATN path.
98#[derive(Clone, Debug)]
99pub(super) struct CompiledLexerAccept {
100    pub(super) rule_index: usize,
101    pub(super) consumed_eof: bool,
102    pub(super) actions: Vec<CompiledLexerActionTrace>,
103}
104
105/// One recorded lexer action, positioned relative to the accept boundary so
106/// the same DFA state serves every input offset.
107#[derive(Clone, Copy, Debug)]
108pub(super) struct CompiledLexerActionTrace {
109    pub(super) action_index: usize,
110    pub(super) rule_index: usize,
111    /// Characters consumed between the action transition and the accept.
112    pub(super) behind: usize,
113}
114
115impl CompiledLexerDfa {
116    /// Compiles every lexer mode of `atn` that has no semantic predicates and
117    /// fits the state budget; the rest stay interpreter-matched.
118    pub fn compile(atn: &Atn) -> Self {
119        let mut dfa = Self {
120            mode_starts: Vec::new(),
121            states: Vec::new(),
122            ascii_rows: Vec::new(),
123            wide_rows: Vec::new(),
124            accepts: Vec::new(),
125        };
126        let mut pools = RowPools::default();
127        for mode in 0..atn.mode_to_start_state().len() {
128            let start = build_mode(atn, mode, &mut dfa, &mut pools);
129            dfa.mode_starts.push(start);
130        }
131        dfa
132    }
133
134    /// True when at least one lexer mode compiled to static tables.
135    pub fn has_compiled_modes(&self) -> bool {
136        self.mode_starts.iter().any(Option::is_some)
137    }
138
139    /// Number of compiled DFA states across all modes (diagnostics).
140    pub const fn state_count(&self) -> usize {
141        self.states.len()
142    }
143
144    /// Per-mode compilation outcome (diagnostics): `true` = static tables.
145    pub fn compiled_mode_flags(&self) -> Vec<bool> {
146        self.mode_starts.iter().map(Option::is_some).collect()
147    }
148
149    /// Per-mode state counts (diagnostics), derived from start offsets.
150    pub fn mode_state_counts(&self) -> Vec<usize> {
151        let mut starts: Vec<usize> = self
152            .mode_starts
153            .iter()
154            .flatten()
155            .map(|&start| usize::from(start))
156            .collect();
157        starts.push(self.states.len());
158        starts.windows(2).map(|pair| pair[1] - pair[0]).collect()
159    }
160
161    /// Compiled start state for `mode`, or `None` when the mode is
162    /// interpreter-matched.
163    pub(super) fn mode_start(&self, mode: i32) -> Option<u16> {
164        let mode = usize::try_from(mode).ok()?;
165        self.mode_starts.get(mode).copied().flatten()
166    }
167
168    pub(super) fn accept(&self, state: u16) -> Option<&CompiledLexerAccept> {
169        self.accepts
170            .get(self.states[usize::from(state)].accept as usize)
171    }
172
173    /// Transition target for a non-EOF symbol, or [`DEAD_STATE`].
174    pub(super) fn char_target(&self, state: u16, symbol: i32) -> u16 {
175        let compiled = &self.states[usize::from(state)];
176        let code_point = symbol.cast_unsigned();
177        if let Ok(ascii) = usize::try_from(symbol)
178            && ascii < ASCII_EDGE_SYMBOLS
179        {
180            return self.ascii_rows[compiled.ascii_row as usize][ascii];
181        }
182        let row = &self.wide_rows[compiled.wide_row as usize];
183        match row.binary_search_by(|range| range.low.cmp(&code_point)) {
184            Ok(found) => row[found].target,
185            Err(insert) => {
186                if insert > 0 && row[insert - 1].high >= code_point {
187                    row[insert - 1].target
188                } else {
189                    DEAD_STATE
190                }
191            }
192        }
193    }
194
195    /// Transition target for the EOF symbol, or [`DEAD_STATE`].
196    pub(super) fn eof_target(&self, state: u16) -> u16 {
197        self.states[usize::from(state)].eof_target
198    }
199
200    /// Flattens the compiled DFA into a `u32` stream for embedding in
201    /// generated code.
202    ///
203    /// The format is internal to this runtime version; [`Self::from_serialized`]
204    /// rejects streams from other versions so generated lexers can fall back
205    /// to [`Self::compile`].
206    pub fn serialize(&self) -> Vec<u32> {
207        // Exact word count: the tag, five section-length words, and each
208        // section's payload (states are 4 words; ASCII rows pack 2 targets
209        // per word; wide ranges and action traces are 3 words each behind
210        // their per-row/per-accept length words).
211        let wide_words: usize = self.wide_rows.iter().map(|row| 1 + row.len() * 3).sum();
212        let accept_words: usize = self
213            .accepts
214            .iter()
215            .map(|accept| 3 + accept.actions.len() * 3)
216            .sum();
217        let capacity = 6
218            + self.mode_starts.len()
219            + self.states.len() * 4
220            + self.ascii_rows.len() * (ASCII_EDGE_SYMBOLS / 2)
221            + wide_words
222            + accept_words;
223        let mut out = Vec::with_capacity(capacity);
224        out.push(SERIALIZED_TAG);
225        out.push(self.mode_starts.len() as u32);
226        for start in &self.mode_starts {
227            out.push(start.map_or(u32::MAX, u32::from));
228        }
229        out.push(self.states.len() as u32);
230        for state in &self.states {
231            out.push(state.ascii_row);
232            out.push(state.wide_row);
233            out.push(u32::from(state.eof_target));
234            out.push(state.accept);
235        }
236        out.push(self.ascii_rows.len() as u32);
237        for row in &self.ascii_rows {
238            for pair in row.chunks(2) {
239                out.push(u32::from(pair[0]) | (u32::from(pair[1]) << 16));
240            }
241        }
242        out.push(self.wide_rows.len() as u32);
243        for row in &self.wide_rows {
244            out.push(row.len() as u32);
245            for range in &**row {
246                out.push(range.low);
247                out.push(range.high);
248                out.push(u32::from(range.target));
249            }
250        }
251        out.push(self.accepts.len() as u32);
252        for accept in &self.accepts {
253            out.push(accept.rule_index as u32);
254            out.push(u32::from(accept.consumed_eof));
255            out.push(accept.actions.len() as u32);
256            for action in &accept.actions {
257                out.push(action.action_index as u32);
258                out.push(action.rule_index as u32);
259                out.push(action.behind as u32);
260            }
261        }
262        debug_assert_eq!(out.len(), capacity, "serialized stream fills its capacity exactly");
263        out
264    }
265
266    /// Rebuilds a compiled DFA from [`Self::serialize`] output; `None` when
267    /// the stream comes from a different runtime version or is malformed.
268    pub fn from_serialized(data: &[u32]) -> Option<Self> {
269        let mut reader = SerializedReader { data, position: 0 };
270        if reader.next()? != SERIALIZED_TAG {
271            return None;
272        }
273        let mode_count = reader.next_len()?;
274        let mut mode_starts = Vec::with_capacity(mode_count);
275        for _ in 0..mode_count {
276            let word = reader.next()?;
277            let start = if word == u32::MAX {
278                None
279            } else {
280                Some(u16::try_from(word).ok()?)
281            };
282            mode_starts.push(start);
283        }
284        let states = reader.read_states()?;
285        let ascii_rows = reader.read_ascii_rows()?;
286        let wide_rows = reader.read_wide_rows()?;
287        let accepts = reader.read_accepts()?;
288        if reader.position != data.len() {
289            return None;
290        }
291        let dfa = Self {
292            mode_starts,
293            states,
294            ascii_rows,
295            wide_rows,
296            accepts,
297        };
298        dfa.table_indexes_are_valid().then_some(dfa)
299    }
300
301    /// Cheap structural validation so a corrupted embedded stream degrades to
302    /// runtime compilation instead of an out-of-bounds panic mid-parse.
303    fn table_indexes_are_valid(&self) -> bool {
304        let state_ok = |target: u16| {
305            usize::from(target) < self.states.len() || target >= ESCAPE_STATE
306        };
307        self.mode_starts
308            .iter()
309            .flatten()
310            .all(|&start| usize::from(start) < self.states.len())
311            && self.states.iter().all(|state| {
312                (state.ascii_row as usize) < self.ascii_rows.len()
313                    && (state.wide_row as usize) < self.wide_rows.len()
314                    && state_ok(state.eof_target)
315                    && (state.accept == u32::MAX || (state.accept as usize) < self.accepts.len())
316            })
317            && self.ascii_rows.iter().all(|row| row.iter().all(|&target| state_ok(target)))
318            && self.wide_rows.iter().all(|row| {
319                wide_row_is_searchable(row) && row.iter().all(|range| state_ok(range.target))
320            })
321    }
322}
323
324/// Wide rows must hold well-formed, sorted, disjoint ranges for
325/// [`CompiledLexerDfa::char_target`]'s binary search; anything else would
326/// silently misroute transitions instead of degrading to recompilation.
327fn wide_row_is_searchable(row: &[WideRange]) -> bool {
328    row.iter().all(|range| range.low <= range.high)
329        && row.windows(2).all(|pair| pair[0].high < pair[1].low)
330}
331
332/// Version tag guarding embedded tables against serialization format drift.
333const SERIALIZED_TAG: u32 = 0x4C58_4401;
334
335/// Cursor over a serialized DFA stream.
336struct SerializedReader<'a> {
337    data: &'a [u32],
338    position: usize,
339}
340
341impl SerializedReader<'_> {
342    fn next(&mut self) -> Option<u32> {
343        let value = self.data.get(self.position).copied();
344        self.position += 1;
345        value
346    }
347
348    fn next_u16(&mut self) -> Option<u16> {
349        u16::try_from(self.next()?).ok()
350    }
351
352    fn next_len(&mut self) -> Option<usize> {
353        usize::try_from(self.next()?).ok()
354    }
355
356    fn read_states(&mut self) -> Option<Vec<CompiledLexerState>> {
357        let count = self.next_len()?;
358        let mut states = Vec::with_capacity(count.min(self.data.len()));
359        for _ in 0..count {
360            states.push(CompiledLexerState {
361                ascii_row: self.next()?,
362                wide_row: self.next()?,
363                eof_target: self.next_u16()?,
364                accept: self.next()?,
365            });
366        }
367        Some(states)
368    }
369
370    fn read_ascii_rows(&mut self) -> Option<Vec<[u16; ASCII_EDGE_SYMBOLS]>> {
371        let count = self.next_len()?;
372        let mut rows = Vec::with_capacity(count.min(self.data.len()));
373        for _ in 0..count {
374            let mut row = [DEAD_STATE; ASCII_EDGE_SYMBOLS];
375            for pair in 0..ASCII_EDGE_SYMBOLS / 2 {
376                let word = self.next()?;
377                row[pair * 2] = (word & 0xFFFF) as u16;
378                row[pair * 2 + 1] = (word >> 16) as u16;
379            }
380            rows.push(row);
381        }
382        Some(rows)
383    }
384
385    fn read_wide_rows(&mut self) -> Option<Vec<Box<[WideRange]>>> {
386        let count = self.next_len()?;
387        let mut rows = Vec::with_capacity(count.min(self.data.len()));
388        for _ in 0..count {
389            let len = self.next_len()?;
390            let mut row = Vec::with_capacity(len.min(self.data.len()));
391            for _ in 0..len {
392                row.push(WideRange {
393                    low: self.next()?,
394                    high: self.next()?,
395                    target: self.next_u16()?,
396                });
397            }
398            rows.push(row.into());
399        }
400        Some(rows)
401    }
402
403    fn read_accepts(&mut self) -> Option<Vec<CompiledLexerAccept>> {
404        let count = self.next_len()?;
405        let mut accepts = Vec::with_capacity(count.min(self.data.len()));
406        for _ in 0..count {
407            let rule_index = self.next_len()?;
408            let consumed_eof = self.next()? != 0;
409            let action_count = self.next_len()?;
410            let mut actions = Vec::with_capacity(action_count.min(self.data.len()));
411            for _ in 0..action_count {
412                actions.push(CompiledLexerActionTrace {
413                    action_index: self.next_len()?,
414                    rule_index: self.next_len()?,
415                    behind: self.next_len()?,
416                });
417            }
418            accepts.push(CompiledLexerAccept {
419                rule_index,
420                consumed_eof,
421                actions,
422            });
423        }
424        Some(accepts)
425    }
426}
427
428/// Deduplicating pools for edge rows shared by many DFA states.
429#[derive(Debug, Default)]
430struct RowPools {
431    ascii_ids: FxHashMap<[u16; ASCII_EDGE_SYMBOLS], u32>,
432    wide_ids: FxHashMap<Box<[WideRange]>, u32>,
433}
434
435impl RowPools {
436    fn intern_ascii(&mut self, rows: &mut Vec<[u16; ASCII_EDGE_SYMBOLS]>, row: [u16; ASCII_EDGE_SYMBOLS]) -> u32 {
437        *self.ascii_ids.entry(row).or_insert_with(|| {
438            rows.push(row);
439            (rows.len() - 1) as u32
440        })
441    }
442
443    fn intern_wide(&mut self, rows: &mut Vec<Box<[WideRange]>>, row: Vec<WideRange>) -> u32 {
444        let row: Box<[WideRange]> = row.into();
445        if let Some(&id) = self.wide_ids.get(&row) {
446            return id;
447        }
448        rows.push(row.clone());
449        let id = (rows.len() - 1) as u32;
450        self.wide_ids.insert(row, id);
451        id
452    }
453}
454
455/// In-progress subset construction for one lexer mode.
456///
457/// States are numbered globally (`base` + discovery order) so edges can be
458/// written directly into the final table, but nothing is committed to the
459/// shared [`CompiledLexerDfa`] until the whole mode succeeds.
460struct ModeBuild {
461    base: usize,
462    ids: FxHashMap<LexerDfaKey, u16>,
463    configs: Vec<Vec<LexerConfig>>,
464    steps: Vec<usize>,
465    accepts: Vec<Option<CompiledLexerAccept>>,
466}
467
468/// Edge rows produced by expanding one DFA state.
469struct StateRows {
470    /// Sorted, disjoint code-point segments with live targets.
471    segments: Vec<(i32, i32, u16)>,
472    eof_target: u16,
473}
474
475impl ModeBuild {
476    fn new(base: usize) -> Self {
477        Self {
478            base,
479            ids: FxHashMap::default(),
480            configs: Vec::new(),
481            steps: Vec::new(),
482            accepts: Vec::new(),
483        }
484    }
485
486    const fn len(&self) -> usize {
487        self.configs.len()
488    }
489
490    /// Returns the state id for a closed, pruned config set, creating the
491    /// state when the (input-offset-normalized) identity is new.
492    /// [`ESCAPE_STATE`] means the state budget is exhausted and the edge must
493    /// hand the token to the interpreter.
494    fn intern(&mut self, atn: &Atn, configs: Vec<LexerConfig>, step: usize) -> u16 {
495        let key = LexerDfaKey::new(
496            configs
497                .iter()
498                .map(|config| relative_config_key(config, step))
499                .collect(),
500        );
501        if let Some(&id) = self.ids.get(&key) {
502            return id;
503        }
504        let local = self.configs.len();
505        let global = self.base + local;
506        if local >= MAX_MODE_STATES || global >= usize::from(ESCAPE_STATE) {
507            return ESCAPE_STATE;
508        }
509        let Ok(id) = u16::try_from(global) else {
510            return ESCAPE_STATE;
511        };
512        self.ids.insert(key, id);
513        self.accepts.push(compiled_accept(atn, &configs, step));
514        self.configs.push(configs);
515        self.steps.push(step);
516        id
517    }
518}
519
520/// Normalizes one config for DFA-state identity, measuring action positions
521/// backwards from the current input offset (`step`).
522///
523/// This differs from the interpreter cache's token-start-relative deltas on
524/// purpose: rule-final lexer commands (`skip`, `pushMode`, …) fire a fixed
525/// distance before the accept, so anchoring at the read position keeps the
526/// state space finite regardless of token length.
527fn relative_config_key(config: &LexerConfig, step: usize) -> LexerDfaConfigKey {
528    LexerDfaConfigKey::new(
529        config.state,
530        config.alt_rule_index,
531        config.consumed_eof,
532        config.passed_non_greedy,
533        config.stack.clone(),
534        config
535            .actions
536            .iter()
537            .map(|action| LexerDfaActionKey {
538                action_index: action.action_index,
539                position_delta: step.saturating_sub(action.position),
540                rule_index: action.rule_index,
541            })
542            .collect(),
543    )
544}
545
546/// Computes the accept metadata for a DFA state from its config set, using
547/// the interpreter's own rule-priority selection.
548fn compiled_accept(atn: &Atn, configs: &[LexerConfig], step: usize) -> Option<CompiledLexerAccept> {
549    let accept = best_accept(atn, configs)?;
550    debug_assert!(
551        accept.position == step,
552        "every config in a lexer DFA state shares the state's input offset"
553    );
554    Some(CompiledLexerAccept {
555        rule_index: accept.rule_index,
556        consumed_eof: accept.consumed_eof,
557        actions: accept
558            .actions
559            .iter()
560            .map(|trace| CompiledLexerActionTrace {
561                action_index: trace.action_index,
562                rule_index: trace.rule_index,
563                behind: accept.position.saturating_sub(trace.position),
564            })
565            .collect(),
566    })
567}
568
569/// Runs subset construction for one mode; `None` leaves the whole mode to the
570/// interpreter (only when its very first closure already escapes).
571fn build_mode(
572    atn: &Atn,
573    mode: usize,
574    dfa: &mut CompiledLexerDfa,
575    pools: &mut RowPools,
576) -> Option<u16> {
577    let start_state = atn.mode_to_start_state().get(mode).copied()?;
578    let mut build = ModeBuild::new(dfa.states.len());
579    let start_configs = closed_configs(
580        atn,
581        vec![LexerConfig {
582            state: start_state,
583            position: 0,
584            consumed_eof: false,
585            alt_rule_index: None,
586            passed_non_greedy: false,
587            stack: Vec::new(),
588            actions: Vec::new(),
589        }],
590    )?;
591    let start_id = build.intern(atn, start_configs, 0);
592    if start_id == ESCAPE_STATE {
593        return None;
594    }
595
596    let mut rows = Vec::new();
597    let mut cursor = 0;
598    while cursor < build.len() {
599        rows.push(expand_state(atn, &mut build, cursor));
600        cursor += 1;
601    }
602
603    commit_mode(dfa, pools, build, rows);
604    Some(start_id)
605}
606
607/// Closes and prunes a moved config set exactly like the interpreter does.
608/// `None` means the closure crossed a semantic predicate (which only the
609/// interpreter can evaluate) or entered a recursive lexer rule (nested
610/// comments never determinize), so the edge must escape.
611fn closed_configs(atn: &Atn, moved: Vec<LexerConfig>) -> Option<Vec<LexerConfig>> {
612    let closure = epsilon_closure(atn, moved, &mut |_| true);
613    if closure.has_semantic_context {
614        return None;
615    }
616    if closure.configs.iter().any(has_recursive_stack) {
617        return None;
618    }
619    let mut configs = closure.configs;
620    for config in &mut configs {
621        prune_dead_action_traces(atn, config);
622        if config.actions.len() > MAX_ACTION_TRACES {
623            return None;
624        }
625    }
626    Some(prune_after_accepts(atn, configs))
627}
628
629/// Drops action traces the accept-time dispatcher would suppress anyway.
630///
631/// The interpreter keeps traces of every action transition it crosses and
632/// filters them per accept with `lexer_action_belongs_to_accept`; a token
633/// rule referenced from another rule leaves traces that can never fire (its
634/// commands belong to itself, not the enclosing rule). Carrying them into
635/// DFA-state identity would mint a fresh state per input offset — rules that
636/// loop over comment/whitespace references would never determinize.
637fn prune_dead_action_traces(atn: &Atn, config: &mut LexerConfig) {
638    let Some(accept_rule) = config.alt_rule_index else {
639        return;
640    };
641    config
642        .actions
643        .retain(|trace| lexer_action_belongs_to_accept(atn, accept_rule, trace.rule_index));
644}
645
646/// Detects lexer-rule recursion: re-entering a rule from the same call site
647/// pushes the same follow state again, so a duplicated stack entry (or an
648/// implausibly deep stack) marks a config a finite DFA cannot represent.
649fn has_recursive_stack(config: &LexerConfig) -> bool {
650    let stack = &config.stack;
651    if stack.len() > MAX_STACK_DEPTH {
652        return true;
653    }
654    stack
655        .iter()
656        .enumerate()
657        .any(|(index, follow)| stack[..index].contains(follow))
658}
659
660/// Computes every outgoing edge of one interned DFA state.
661fn expand_state(atn: &Atn, build: &mut ModeBuild, local: usize) -> StateRows {
662    let configs = build.configs[local].clone();
663    let step = build.steps[local];
664    let entries = consuming_entries(atn, &configs);
665    let eof_target = eof_move(atn, build, &configs, step, &entries);
666
667    let entry_intervals: Vec<Vec<(i32, i32)>> = entries
668        .iter()
669        .map(|(_, transition)| transition_char_intervals(transition))
670        .collect();
671    let segments = char_segments(&entry_intervals);
672    let matrix = segment_mask_matrix(&segments, &entry_intervals, entries.len());
673    let words = entries.len().div_ceil(64);
674
675    let mut rows = StateRows {
676        segments: Vec::new(),
677        eof_target,
678    };
679    // Distinct transition sets are few even when segments are many (large
680    // Unicode classes fragment the alphabet), so closures run once per
681    // matching-transition mask, not once per segment.
682    let mut mask_targets: FxHashMap<Vec<u64>, u16> = FxHashMap::default();
683    for (index, &(low, high)) in segments.iter().enumerate() {
684        let mask = &matrix[index * words..(index + 1) * words];
685        if mask.iter().all(|&word| word == 0) {
686            continue;
687        }
688        let target = match mask_targets.get(mask) {
689            Some(&target) => target,
690            None => {
691                let target = move_target(atn, build, &configs, step, &entries, mask);
692                mask_targets.insert(mask.to_vec(), target);
693                target
694            }
695        };
696        if target != DEAD_STATE {
697            rows.segments.push((low, high, target));
698        }
699    }
700    rows
701}
702
703/// Lists each config's consuming transitions in the interpreter's move order.
704fn consuming_entries<'a>(atn: &'a Atn, configs: &[LexerConfig]) -> Vec<(usize, &'a Transition)> {
705    let mut entries = Vec::new();
706    for (config_index, config) in configs.iter().enumerate() {
707        let Some(state) = atn.state(config.state) else {
708            continue;
709        };
710        for transition in &state.transitions {
711            if !transition.is_epsilon() {
712                entries.push((config_index, transition));
713            }
714        }
715    }
716    entries
717}
718
719/// Splits the code-point alphabet at every interval boundary, so each segment
720/// is matched uniformly by every transition.
721fn char_segments(entry_intervals: &[Vec<(i32, i32)>]) -> Vec<(i32, i32)> {
722    let mut cuts = Vec::new();
723    for intervals in entry_intervals {
724        for &(low, high) in intervals {
725            cuts.push(low);
726            cuts.push(high + 1);
727        }
728    }
729    cuts.sort_unstable();
730    cuts.dedup();
731    cuts.windows(2).map(|pair| (pair[0], pair[1] - 1)).collect()
732}
733
734/// Marks, for every segment, which entries match it — one bit row per
735/// segment. Sweeping each entry's intervals over the sorted segment starts
736/// keeps the work proportional to interval count, not `segments × entries`.
737fn segment_mask_matrix(
738    segments: &[(i32, i32)],
739    entry_intervals: &[Vec<(i32, i32)>],
740    entry_count: usize,
741) -> Vec<u64> {
742    let words = entry_count.div_ceil(64);
743    let mut matrix = vec![0_u64; segments.len() * words];
744    for (bit, intervals) in entry_intervals.iter().enumerate() {
745        for &(low, high) in intervals {
746            // Interval boundaries are cut points, so the covered segments are
747            // exactly those whose start lies inside the interval.
748            let from = segments.partition_point(|&(start, _)| start < low);
749            let to = segments.partition_point(|&(start, _)| start <= high);
750            for segment in from..to {
751                matrix[segment * words + bit / 64] |= 1 << (bit % 64);
752            }
753        }
754    }
755    matrix
756}
757
758/// Materializes the code-point intervals a transition consumes, clamped to
759/// the valid character range (EOF is handled separately).
760fn transition_char_intervals(transition: &Transition) -> Vec<(i32, i32)> {
761    let mut intervals = Vec::new();
762    let mut push_clamped = |low: i32, high: i32| {
763        let low = low.max(MIN_CHAR_VALUE);
764        let high = high.min(MAX_CHAR_VALUE);
765        if low <= high {
766            intervals.push((low, high));
767        }
768    };
769    match transition {
770        Transition::Atom { label, .. } => push_clamped(*label, *label),
771        Transition::Range { start, stop, .. } => push_clamped(*start, *stop),
772        Transition::Set { set, .. } => {
773            for &(low, high) in set.ranges() {
774                push_clamped(low, high);
775            }
776        }
777        Transition::NotSet { set, .. } => {
778            // `NotSet` matches the complement within the character range;
779            // `IntervalSet` ranges are sorted and coalesced.
780            let mut next = MIN_CHAR_VALUE;
781            for &(low, high) in set.ranges() {
782                if low > next {
783                    push_clamped(next, low - 1);
784                }
785                next = next.max(high.saturating_add(1));
786            }
787            push_clamped(next, MAX_CHAR_VALUE);
788        }
789        Transition::Wildcard { .. } => push_clamped(MIN_CHAR_VALUE, MAX_CHAR_VALUE),
790        _ => {}
791    }
792    intervals
793}
794
795/// Advances the masked entries by one character and interns the result;
796/// closures that escape compile as [`ESCAPE_STATE`] edges.
797fn move_target(
798    atn: &Atn,
799    build: &mut ModeBuild,
800    configs: &[LexerConfig],
801    step: usize,
802    entries: &[(usize, &Transition)],
803    mask: &[u64],
804) -> u16 {
805    let mut moved = Vec::new();
806    for (bit, (config_index, transition)) in entries.iter().enumerate() {
807        if mask[bit / 64] & (1 << (bit % 64)) == 0 {
808            continue;
809        }
810        let mut advanced = configs[*config_index].clone();
811        set_config_state(atn, &mut advanced, transition.target());
812        advanced.position += 1;
813        moved.push(advanced);
814    }
815    let Some(active) = closed_configs(atn, moved) else {
816        return ESCAPE_STATE;
817    };
818    if active.is_empty() {
819        return DEAD_STATE;
820    }
821    build.intern(atn, active, step + 1)
822}
823
824/// Advances the EOF-matching entries; EOF consumes no character, so the input
825/// offset stays put and the moved configs record `consumed_eof`.
826fn eof_move(
827    atn: &Atn,
828    build: &mut ModeBuild,
829    configs: &[LexerConfig],
830    step: usize,
831    entries: &[(usize, &Transition)],
832) -> u16 {
833    let mut moved = Vec::new();
834    for (config_index, transition) in entries {
835        if !transition.matches(EOF, MIN_CHAR_VALUE, MAX_CHAR_VALUE) {
836            continue;
837        }
838        let mut advanced = configs[*config_index].clone();
839        set_config_state(atn, &mut advanced, transition.target());
840        advanced.consumed_eof = true;
841        moved.push(advanced);
842    }
843    if moved.is_empty() {
844        return DEAD_STATE;
845    }
846    let Some(active) = closed_configs(atn, moved) else {
847        return ESCAPE_STATE;
848    };
849    if active.is_empty() {
850        return DEAD_STATE;
851    }
852    build.intern(atn, active, step)
853}
854
855/// Converts a finished mode's edge rows into pooled table entries.
856fn commit_mode(dfa: &mut CompiledLexerDfa, pools: &mut RowPools, build: ModeBuild, rows: Vec<StateRows>) {
857    for (accept, state_rows) in build.accepts.into_iter().zip(rows) {
858        let accept_id = accept.map_or(u32::MAX, |accept| {
859            dfa.accepts.push(accept);
860            (dfa.accepts.len() - 1) as u32
861        });
862        let (ascii_row, wide_row) = split_rows(&state_rows.segments);
863        dfa.states.push(CompiledLexerState {
864            ascii_row: pools.intern_ascii(&mut dfa.ascii_rows, ascii_row),
865            wide_row: pools.intern_wide(&mut dfa.wide_rows, wide_row),
866            eof_target: state_rows.eof_target,
867            accept: accept_id,
868        });
869    }
870}
871
872/// Splits sorted segments into the dense ASCII row and merged wide ranges.
873fn split_rows(segments: &[(i32, i32, u16)]) -> ([u16; ASCII_EDGE_SYMBOLS], Vec<WideRange>) {
874    let mut ascii = [DEAD_STATE; ASCII_EDGE_SYMBOLS];
875    let mut wide: Vec<WideRange> = Vec::new();
876    for &(low, high, target) in segments {
877        let ascii_high = high.min(ASCII_EDGE_LIMIT - 1);
878        for code_point in low..=ascii_high {
879            ascii[code_point.cast_unsigned() as usize] = target;
880        }
881        if high >= ASCII_EDGE_LIMIT {
882            let low = low.max(ASCII_EDGE_LIMIT).cast_unsigned();
883            let high = high.cast_unsigned();
884            if let Some(last) = wide.last_mut()
885                && last.target == target
886                && last.high + 1 == low
887            {
888                last.high = high;
889                continue;
890            }
891            wide.push(WideRange { low, high, target });
892        }
893    }
894    (ascii, wide)
895}
896
897#[cfg(test)]
898mod tests {
899    use super::*;
900    use crate::atn::lexer::{next_token, next_token_compiled, next_token_compiled_with_hooks};
901    use crate::atn::serialized::{AtnDeserializer, SerializedAtn};
902    use crate::char_stream::InputStream;
903    use crate::lexer::BaseLexer;
904    use crate::recognizer::RecognizerData;
905    use crate::token::{TOKEN_EOF, Token};
906    use crate::vocabulary::Vocabulary;
907
908    fn recognizer_data() -> RecognizerData {
909        RecognizerData::new(
910            "T",
911            Vocabulary::new(
912                [None, Some("'ab'"), Some("' '")],
913                [None, Some("AB"), Some("WS")],
914                [None::<&str>, None, None],
915            ),
916        )
917    }
918
919    /// Two-rule lexer (`AB: 'ab';` and `WS: ' ' -> skip;`), with rule 0's
920    /// final epsilon optionally replaced by a semantic predicate transition.
921    fn two_rule_atn(with_predicate: bool) -> Atn {
922        let epsilon_or_predicate = if with_predicate { 4 } else { 1 };
923        AtnDeserializer::new(&SerializedAtn::from_i32(&[
924            4, 0, 2, // version, lexer, max token type
925            9, // states
926            6, -1, // 0 token start
927            2, 0, // 1 rule 0 start
928            1, 0, // 2
929            1, 0, // 3
930            7, 0, // 4 rule 0 stop
931            2, 1, // 5 rule 1 start
932            1, 1, // 6
933            1, 1, // 7
934            7, 1, // 8 rule 1 stop
935            0, // non-greedy
936            0, // precedence
937            2, // rules
938            1, 1, // rule 0 starts at 1, token type 1
939            5, 2, // rule 1 starts at 5, token type 2
940            1, // modes
941            0, // default mode starts at 0
942            0, // sets
943            8, // edges
944            0, 1, 1, 0, 0, 0, // start -> rule 0
945            0, 5, 1, 0, 0, 0, // start -> rule 1
946            1, 2, 5, 'a' as i32, 0, 0, // 'a'
947            2, 3, 5, 'b' as i32, 0, 0, // 'b'
948            3, 4, epsilon_or_predicate, 0, 0, 0, // epsilon or predicate to stop
949            5, 6, 5, ' ' as i32, 0, 0, // ' '
950            6, 7, 1, 0, 0, 0, //
951            7, 8, 6, 1, 0, 0, // action 0, then stop
952            1, // decisions
953            0, 1, // lexer actions
954            6, 0, 0, // skip
955        ]))
956        .deserialize()
957        .expect("artificial lexer ATN should deserialize")
958    }
959
960    /// One token rule matching `[\u{100}-\u{200}]+`, exercising wide rows.
961    fn wide_range_atn() -> Atn {
962        AtnDeserializer::new(&SerializedAtn::from_i32(&[
963            4, 0, 1, // version, lexer, max token type
964            5, // states
965            6, -1, // 0 token start
966            2, 0, // 1 rule 0 start
967            1, 0, // 2
968            1, 0, // 3
969            7, 0, // 4 rule 0 stop
970            0, // non-greedy
971            0, // precedence
972            1, // rules
973            1, 1, // rule 0 starts at 1, token type 1
974            1, // modes
975            0, // default mode starts at 0
976            0, // sets
977            5, // edges
978            0, 1, 1, 0, 0, 0, // start -> rule 0
979            1, 2, 1, 0, 0, 0, //
980            2, 3, 2, 0x100, 0x200, 0, // range
981            3, 2, 1, 0, 0, 0, // greedy loop continues first
982            3, 4, 1, 0, 0, 0, // then exits to stop
983            0, // decisions
984            0, // lexer actions
985        ]))
986        .deserialize()
987        .expect("artificial wide-range lexer ATN should deserialize")
988    }
989
990    #[test]
991    fn compiled_dfa_matches_longest_token_and_skips() {
992        let atn = two_rule_atn(false);
993        let dfa = CompiledLexerDfa::compile(&atn);
994        assert!(dfa.has_compiled_modes());
995        assert!(dfa.mode_start(0).is_some());
996
997        let mut lexer = BaseLexer::new(InputStream::new(" ab"), recognizer_data());
998        let token = next_token_compiled(&mut lexer, &atn, &dfa);
999        assert_eq!(token.token_type(), 1);
1000        assert_eq!(token.text(), "ab");
1001        assert_eq!(
1002            next_token_compiled(&mut lexer, &atn, &dfa).token_type(),
1003            TOKEN_EOF
1004        );
1005    }
1006
1007    #[test]
1008    fn predicate_edge_escapes_to_the_interpreter() {
1009        let atn = two_rule_atn(true);
1010        let dfa = CompiledLexerDfa::compile(&atn);
1011        // The predicate sits mid-rule, so the mode still compiles; only the
1012        // edge that would cross it escapes to the interpreter.
1013        assert!(dfa.mode_start(0).is_some());
1014
1015        let mut lexer = BaseLexer::new(InputStream::new(" ab"), recognizer_data());
1016        let token = next_token_compiled_with_hooks(
1017            &mut lexer,
1018            &atn,
1019            &dfa,
1020            |_, _| {},
1021            |_, _| true,
1022            |_, _, _| {},
1023        );
1024        assert_eq!(token.token_type(), 1);
1025        assert_eq!(token.text(), "ab");
1026    }
1027
1028    #[test]
1029    fn compiled_dfa_walks_wide_ranges() {
1030        let atn = wide_range_atn();
1031        let dfa = CompiledLexerDfa::compile(&atn);
1032        assert!(dfa.mode_start(0).is_some());
1033
1034        let mut lexer = BaseLexer::new(InputStream::new("ĀĂ"), recognizer_data());
1035        let token = next_token_compiled(&mut lexer, &atn, &dfa);
1036        assert_eq!(token.token_type(), 1);
1037        assert_eq!(token.text(), "ĀĂ");
1038        assert_eq!(
1039            next_token_compiled(&mut lexer, &atn, &dfa).token_type(),
1040            TOKEN_EOF
1041        );
1042    }
1043
1044    #[test]
1045    fn compiled_dfa_reports_recognition_errors_like_the_interpreter() {
1046        let atn = wide_range_atn();
1047        let dfa = CompiledLexerDfa::compile(&atn);
1048
1049        let mut compiled = BaseLexer::new(InputStream::new("zĀ"), recognizer_data());
1050        let mut interpreted = BaseLexer::new(InputStream::new("zĀ"), recognizer_data());
1051        loop {
1052            let compiled_token = next_token_compiled(&mut compiled, &atn, &dfa);
1053            let interpreted_token = next_token(&mut interpreted, &atn);
1054            assert_eq!(compiled_token.token_type(), interpreted_token.token_type());
1055            assert_eq!(compiled_token.text(), interpreted_token.text());
1056            if compiled_token.token_type() == TOKEN_EOF {
1057                break;
1058            }
1059        }
1060        let compiled_errors: Vec<String> = compiled
1061            .drain_errors()
1062            .into_iter()
1063            .map(|error| error.message)
1064            .collect();
1065        let interpreted_errors: Vec<String> = interpreted
1066            .drain_errors()
1067            .into_iter()
1068            .map(|error| error.message)
1069            .collect();
1070        assert_eq!(compiled_errors, vec!["token recognition error at: 'z'"]);
1071        assert_eq!(compiled_errors, interpreted_errors);
1072    }
1073
1074    #[test]
1075    fn serialization_round_trips() {
1076        let atn = two_rule_atn(false);
1077        let dfa = CompiledLexerDfa::compile(&atn);
1078        let stream = dfa.serialize();
1079
1080        let restored =
1081            CompiledLexerDfa::from_serialized(&stream).expect("stream should deserialize");
1082        assert_eq!(restored.serialize(), stream);
1083
1084        let mut lexer = BaseLexer::new(InputStream::new(" ab"), recognizer_data());
1085        let token = next_token_compiled(&mut lexer, &atn, &restored);
1086        assert_eq!(token.token_type(), 1);
1087        assert_eq!(token.text(), "ab");
1088
1089        // A stream from a different runtime version is rejected, not trusted.
1090        let mut wrong_tag = stream;
1091        wrong_tag[0] ^= 1;
1092        assert!(CompiledLexerDfa::from_serialized(&wrong_tag).is_none());
1093    }
1094
1095    #[test]
1096    fn malformed_wide_rows_are_rejected() {
1097        let atn = wide_range_atn();
1098        let stream = CompiledLexerDfa::compile(&atn).serialize();
1099
1100        // Invert the [0x100, 0x200] range's bounds in place; a broken wide
1101        // row must fail validation, not silently misroute binary searches.
1102        let position = stream
1103            .windows(2)
1104            .position(|pair| pair == [0x100, 0x200])
1105            .expect("wide-range test grammar serializes its range bounds");
1106        let mut inverted = stream;
1107        inverted.swap(position, position + 1);
1108        assert!(CompiledLexerDfa::from_serialized(&inverted).is_none());
1109    }
1110
1111    #[test]
1112    fn force_interpreted_bypasses_compiled_tables() {
1113        let atn = two_rule_atn(false);
1114        let dfa = CompiledLexerDfa::compile(&atn);
1115
1116        let mut lexer = BaseLexer::new(InputStream::new("ab"), recognizer_data());
1117        lexer.set_force_interpreted(true);
1118        let token = next_token_compiled(&mut lexer, &atn, &dfa);
1119        assert_eq!(token.token_type(), 1);
1120        // The interpreter path records the learned-DFA trace; the compiled
1121        // walk does not.
1122        assert!(!lexer.lexer_dfa_string().is_empty());
1123    }
1124}