Skip to main content

maskforge_core/automaton/
byte_dfa.rs

1//! Anchored byte-DFA built via `regex-automata`, materialized into MaskForge's canonical-DEAD reference graph.
2
3use std::collections::VecDeque;
4
5use regex_automata::dfa::{dense, Automaton, StartKind};
6use regex_automata::nfa::thompson;
7use regex_automata::util::primitives::StateID as RaStateId;
8use regex_automata::util::{start, syntax};
9use regex_automata::{Anchored, MatchKind};
10use rustc_hash::{FxHashMap, FxHashSet};
11
12use crate::error::{CompileError, ErrorCode, Stage};
13use crate::mem_gate::acquire_automaton_build_bytes;
14use crate::primitives::{ByteClassId, StateId};
15
16use super::graph::{graph_limit, AutomatonGraph};
17
18use crate::mask::Bitmask;
19use crate::primitives::TokenId;
20use crate::vocab::Vocabulary;
21
22const DFA_SIZE_LIMIT: usize = 64 << 20;
23const DETERMINIZE_SIZE_LIMIT: usize = 64 << 20;
24const NFA_SIZE_LIMIT: usize = 16 << 20;
25const NEST_LIMIT: u32 = 4096;
26
27const MAX_MATERIALIZE_STATES: usize = 1 << 16;
28const MAX_TRANSITION_CELLS: usize = 1 << 22;
29
30const DISCOVERY_SCRATCH_BYTES_PER_STATE: usize = 256
31    * (std::mem::size_of::<u16>() + std::mem::size_of::<u32>())
32    + 6 * (std::mem::size_of::<u32>() + 16);
33
34fn materialize_worst_case_bytes() -> usize {
35    MAX_MATERIALIZE_STATES * std::mem::size_of::<u32>()
36        + MAX_TRANSITION_CELLS * std::mem::size_of::<(ByteClassId, StateId)>()
37        + MAX_MATERIALIZE_STATES
38            * (std::mem::size_of::<bool>()
39                + std::mem::size_of::<ClassMask>()
40                + std::mem::size_of::<u64>())
41        + MAX_TRANSITION_CELLS * std::mem::size_of::<u32>()
42        + MAX_MATERIALIZE_STATES
43            * (2 * std::mem::size_of::<u32>()
44                + 4 * std::mem::size_of::<usize>()
45                + std::mem::size_of::<u64>())
46}
47
48fn automaton_build_worst_case_bytes(
49    dfa_size_limit: usize,
50    determinize_size_limit: usize,
51    nfa_size_limit: usize,
52) -> usize {
53    dfa_size_limit
54        .saturating_add(determinize_size_limit)
55        .saturating_add(nfa_size_limit)
56        .saturating_add(MAX_MATERIALIZE_STATES.saturating_mul(DISCOVERY_SCRATCH_BYTES_PER_STATE))
57        .saturating_add(materialize_worst_case_bytes())
58}
59
60pub(crate) const DEAD: StateId = StateId(0);
61
62#[derive(Debug)]
63pub(crate) struct ClassTable {
64    pub(crate) members: Vec<Vec<u8>>,
65    pub(crate) of_byte: [ByteClassId; 256],
66}
67
68#[derive(Copy, Clone, Debug)]
69pub(crate) struct ClassMask {
70    words: [u64; 4],
71}
72
73impl ClassMask {
74    fn empty() -> Self {
75        Self { words: [0; 4] }
76    }
77
78    fn set(&mut self, c: usize) {
79        self.words[c >> 6] |= 1u64 << (c & 63);
80    }
81
82    pub(crate) fn contains(&self, c: usize) -> bool {
83        self.words[c >> 6] & (1u64 << (c & 63)) != 0
84    }
85
86    fn is_empty(&self) -> bool {
87        self.words == [0; 4]
88    }
89}
90
91#[derive(Debug)]
92struct Transitions {
93    row_offsets: Box<[u32]>,
94    edges: Box<[(ByteClassId, StateId)]>,
95}
96
97#[derive(Debug)]
98pub struct RefEngine {
99    transitions: Transitions,
100    accepting: Box<[bool]>,
101    residual_cardinality: Box<[u64]>,
102    live_classes: Box<[ClassMask]>,
103    pub(crate) class_table: ClassTable,
104    class_count: usize,
105    state_count: usize,
106    live_transition_count: usize,
107    start: StateId,
108    dead: StateId,
109}
110
111impl RefEngine {
112    pub(crate) fn from_graph(graph: &AutomatonGraph) -> Result<Self, CompileError> {
113        graph_to_engine(graph)
114    }
115
116    #[must_use]
117    pub fn state_count(&self) -> usize {
118        self.state_count
119    }
120
121    #[must_use]
122    pub fn transition_cells(&self) -> usize {
123        self.state_count.saturating_mul(self.class_count)
124    }
125
126    #[must_use]
127    pub fn byte_class_count(&self) -> usize {
128        self.class_count
129    }
130
131    #[must_use]
132    pub fn live_transition_count(&self) -> usize {
133        self.live_transition_count
134    }
135
136    pub(crate) fn raw_byte_transition_count(&self) -> Option<usize> {
137        self.transitions
138            .edges
139            .iter()
140            .try_fold(0usize, |total, (class, _)| {
141                self.class_table
142                    .members
143                    .get(usize::from(class.get()))
144                    .and_then(|bytes| total.checked_add(bytes.len()))
145            })
146    }
147
148    #[must_use]
149    pub fn transition_density(&self) -> f64 {
150        let cells = self.transition_cells();
151        if cells == 0 {
152            return 0.0;
153        }
154        self.live_transition_count as f64 / cells as f64
155    }
156
157    #[must_use]
158    #[deprecated(note = "use live_transition_count() (or transition_cells() for the cell count)")]
159    pub fn transition_count(&self) -> usize {
160        self.live_transition_count
161    }
162
163    #[inline]
164    pub(crate) fn step(&self, state: StateId, class: ByteClassId) -> StateId {
165        let Ok(s) = usize::try_from(state.get()) else {
166            return self.dead;
167        };
168        let c = usize::from(class.get());
169        if s >= self.state_count || c >= self.class_count {
170            return self.dead;
171        }
172        let (Some(&lo), Some(&hi)) = (
173            self.transitions.row_offsets.get(s),
174            self.transitions.row_offsets.get(s + 1),
175        ) else {
176            return self.dead;
177        };
178        for (cc, t) in &self.transitions.edges[lo as usize..hi as usize] {
179            let cc = usize::from(cc.get());
180            if cc == c {
181                return *t;
182            }
183            if cc > c {
184                break;
185            }
186        }
187        self.dead
188    }
189
190    pub(crate) fn live_classes(&self, state: StateId) -> ClassMask {
191        self.live_classes
192            .get(state.get() as usize)
193            .copied()
194            .unwrap_or_else(ClassMask::empty)
195    }
196
197    #[must_use]
198    pub fn start(&self) -> StateId {
199        self.start
200    }
201
202    #[must_use]
203    pub fn dead(&self) -> StateId {
204        self.dead
205    }
206
207    #[must_use]
208    pub fn is_accepting(&self, s: StateId) -> bool {
209        self.accepting
210            .get(s.get() as usize)
211            .copied()
212            .unwrap_or(false)
213    }
214
215    #[must_use]
216    pub fn is_dead(&self, s: StateId) -> bool {
217        s == self.dead || s.get() as usize >= self.state_count
218    }
219
220    #[must_use]
221    pub fn can_continue(&self, s: StateId) -> bool {
222        !self.live_classes(s).is_empty()
223    }
224
225    #[must_use]
226    pub(crate) fn residual_cardinality(&self, s: StateId) -> u64 {
227        self.residual_cardinality
228            .get(s.get() as usize)
229            .copied()
230            .unwrap_or(0)
231    }
232
233    #[must_use]
234    pub fn heap_bytes(&self) -> usize {
235        let transitions = self
236            .transitions
237            .row_offsets
238            .len()
239            .saturating_mul(std::mem::size_of::<u32>())
240            .saturating_add(
241                self.transitions
242                    .edges
243                    .len()
244                    .saturating_mul(std::mem::size_of::<(ByteClassId, StateId)>()),
245            );
246        let accepting = self
247            .accepting
248            .len()
249            .saturating_mul(std::mem::size_of::<bool>());
250        let residual_cardinality = self
251            .residual_cardinality
252            .len()
253            .saturating_mul(std::mem::size_of::<u64>());
254        let live_classes = self
255            .live_classes
256            .len()
257            .saturating_mul(std::mem::size_of::<ClassMask>());
258        let class_bytes: usize = self
259            .class_table
260            .members
261            .iter()
262            .map(Vec::capacity)
263            .sum::<usize>()
264            .saturating_add(
265                self.class_table
266                    .members
267                    .capacity()
268                    .saturating_mul(std::mem::size_of::<Vec<u8>>()),
269            );
270        transitions
271            .saturating_add(accepting)
272            .saturating_add(residual_cardinality)
273            .saturating_add(live_classes)
274            .saturating_add(class_bytes)
275    }
276}
277
278fn residual_cardinalities(
279    transitions: &Transitions,
280    accepting: &[bool],
281    class_table: &ClassTable,
282) -> Result<Box<[u64]>, CompileError> {
283    let state_count = accepting.len();
284    let edge_count = transitions.edges.len();
285    let mut predecessor_counts = Vec::new();
286    predecessor_counts
287        .try_reserve_exact(state_count)
288        .map_err(|_| limit_err())?;
289    predecessor_counts.resize(state_count, 0u32);
290    let mut remaining_outdegree = Vec::new();
291    remaining_outdegree
292        .try_reserve_exact(state_count)
293        .map_err(|_| limit_err())?;
294    remaining_outdegree.resize(state_count, 0u32);
295    for (state, outdegree) in remaining_outdegree.iter_mut().enumerate() {
296        let lo = transitions.row_offsets[state] as usize;
297        let hi = transitions.row_offsets[state + 1] as usize;
298        *outdegree = u32::try_from(hi - lo).map_err(|_| limit_err())?;
299        for &(_, target) in &transitions.edges[lo..hi] {
300            let target = target.get() as usize;
301            predecessor_counts[target] = predecessor_counts[target]
302                .checked_add(1)
303                .ok_or_else(limit_err)?;
304        }
305    }
306
307    let mut predecessor_offsets = Vec::new();
308    predecessor_offsets
309        .try_reserve_exact(state_count + 1)
310        .map_err(|_| limit_err())?;
311    predecessor_offsets.push(0usize);
312    for count in predecessor_counts {
313        let next = predecessor_offsets
314            .last()
315            .copied()
316            .and_then(|offset| offset.checked_add(count as usize))
317            .ok_or_else(limit_err)?;
318        predecessor_offsets.push(next);
319    }
320    let mut predecessors = Vec::new();
321    predecessors
322        .try_reserve_exact(edge_count)
323        .map_err(|_| limit_err())?;
324    predecessors.resize(edge_count, 0u32);
325    let mut cursors = Vec::new();
326    cursors
327        .try_reserve_exact(state_count)
328        .map_err(|_| limit_err())?;
329    cursors.extend_from_slice(&predecessor_offsets[..state_count]);
330    for source in 0..state_count {
331        let lo = transitions.row_offsets[source] as usize;
332        let hi = transitions.row_offsets[source + 1] as usize;
333        for &(_, target) in &transitions.edges[lo..hi] {
334            let target = target.get() as usize;
335            predecessors[cursors[target]] = u32::try_from(source).map_err(|_| limit_err())?;
336            cursors[target] += 1;
337        }
338    }
339
340    let mut queue = VecDeque::new();
341    queue.try_reserve(state_count).map_err(|_| limit_err())?;
342    for (state, &outdegree) in remaining_outdegree.iter().enumerate().skip(1) {
343        if outdegree == 0 {
344            queue.push_back(state);
345        }
346    }
347    let mut finite_order = Vec::new();
348    finite_order
349        .try_reserve_exact(state_count)
350        .map_err(|_| limit_err())?;
351    while let Some(target) = queue.pop_front() {
352        finite_order.push(target);
353        for &source in &predecessors[predecessor_offsets[target]..predecessor_offsets[target + 1]] {
354            let source = source as usize;
355            remaining_outdegree[source] = remaining_outdegree[source]
356                .checked_sub(1)
357                .ok_or_else(limit_err)?;
358            if source != DEAD.get() as usize && remaining_outdegree[source] == 0 {
359                queue.push_back(source);
360            }
361        }
362    }
363
364    let mut cardinalities = Vec::new();
365    cardinalities
366        .try_reserve_exact(state_count)
367        .map_err(|_| limit_err())?;
368    cardinalities.resize(state_count, u64::MAX);
369    cardinalities[DEAD.get() as usize] = 0;
370    for &state in &finite_order {
371        let mut count: u64 = if accepting[state] { 1 } else { 0 };
372        let lo = transitions.row_offsets[state] as usize;
373        let hi = transitions.row_offsets[state + 1] as usize;
374        for &(class, target) in &transitions.edges[lo..hi] {
375            let multiplicity = class_table.members[class.get() as usize].len() as u64;
376            count = count
377                .saturating_add(cardinalities[target.get() as usize].saturating_mul(multiplicity));
378        }
379        cardinalities[state] = count;
380    }
381    Ok(cardinalities.into_boxed_slice())
382}
383
384fn graph_to_engine(graph: &AutomatonGraph) -> Result<RefEngine, CompileError> {
385    let state_count = graph.states.len();
386    let cells = state_count.checked_mul(256).ok_or_else(limit_err)?;
387    if state_count > MAX_MATERIALIZE_STATES || cells > (1 << 24) {
388        return Err(graph_limit("graph byte-matrix cap"));
389    }
390    let estimate = cells
391        .checked_mul(std::mem::size_of::<StateId>())
392        .and_then(|bytes| bytes.checked_add(materialize_worst_case_bytes()))
393        .ok_or_else(limit_err)?;
394    let _permit = acquire_automaton_build_bytes(estimate)?;
395    let live = graph_live_states(graph)?;
396
397    let mut remap = Vec::new();
398    remap
399        .try_reserve_exact(state_count)
400        .map_err(|_| graph_limit("graph remap allocation"))?;
401    remap.resize(state_count, DEAD);
402    let mut next = 1u32;
403    for (index, is_live) in live.iter().copied().enumerate() {
404        if is_live {
405            remap[index] = StateId(next);
406            next = next
407                .checked_add(1)
408                .ok_or_else(|| graph_limit("graph state id"))?;
409        }
410    }
411    let final_state_count = usize::try_from(next).map_err(|_| limit_err())?;
412    let final_cells = final_state_count.checked_mul(256).ok_or_else(limit_err)?;
413
414    let mut matrix = Vec::new();
415    matrix
416        .try_reserve_exact(final_cells)
417        .map_err(|_| graph_limit("graph transition matrix allocation"))?;
418    matrix.resize(final_cells, DEAD);
419    for (source, mapped) in remap.iter().copied().enumerate() {
420        if mapped == DEAD {
421            continue;
422        }
423        let row = usize::try_from(mapped.get()).map_err(|_| limit_err())?;
424        for byte in 0u8..=255 {
425            let target = graph
426                .transition(source, byte)
427                .and_then(|target| remap.get(target).copied())
428                .unwrap_or(DEAD);
429            matrix[row * 256 + usize::from(byte)] = target;
430        }
431    }
432    let (class_table, representatives) = graph_class_table(&matrix, final_state_count)?;
433    let transition_cells = final_state_count
434        .checked_mul(class_table.members.len())
435        .ok_or_else(limit_err)?;
436    if transition_cells > MAX_TRANSITION_CELLS {
437        return Err(graph_limit("live graph transition-cell cap"));
438    }
439    materialize_graph_rows(graph, &remap, matrix, class_table, &representatives)
440}
441
442fn graph_live_states(graph: &AutomatonGraph) -> Result<Vec<bool>, CompileError> {
443    let state_count = graph.states.len();
444    let mut predecessor_count = Vec::new();
445    predecessor_count
446        .try_reserve_exact(state_count)
447        .map_err(|_| graph_limit("graph predecessor allocation"))?;
448    predecessor_count.resize(state_count, 0usize);
449    for edge in &graph.edges {
450        let target = usize::try_from(edge.target.0).map_err(|_| limit_err())?;
451        let count = predecessor_count
452            .get_mut(target)
453            .ok_or_else(|| graph_limit("graph target"))?;
454        *count = count
455            .checked_add(1)
456            .ok_or_else(|| graph_limit("graph predecessor count"))?;
457    }
458    let mut offsets = Vec::new();
459    offsets
460        .try_reserve_exact(state_count + 1)
461        .map_err(|_| graph_limit("graph reverse offsets allocation"))?;
462    offsets.push(0usize);
463    for count in predecessor_count {
464        let next = offsets
465            .last()
466            .copied()
467            .and_then(|offset| offset.checked_add(count))
468            .ok_or_else(|| graph_limit("graph reverse offset"))?;
469        offsets.push(next);
470    }
471    let mut predecessors = Vec::new();
472    predecessors
473        .try_reserve_exact(graph.edges.len())
474        .map_err(|_| graph_limit("graph reverse edges allocation"))?;
475    predecessors.resize(graph.edges.len(), 0usize);
476    let mut cursor = Vec::new();
477    cursor
478        .try_reserve_exact(state_count)
479        .map_err(|_| graph_limit("graph reverse cursor allocation"))?;
480    cursor.extend_from_slice(&offsets[..state_count]);
481    for (source, state) in graph.states.iter().enumerate() {
482        let lo = usize::try_from(state.edge_start).map_err(|_| limit_err())?;
483        let len = usize::try_from(state.edge_len).map_err(|_| limit_err())?;
484        let hi = lo.checked_add(len).ok_or_else(limit_err)?;
485        for edge in graph.edges.get(lo..hi).ok_or_else(limit_err)? {
486            let target = usize::try_from(edge.target.0).map_err(|_| limit_err())?;
487            let at = cursor.get_mut(target).ok_or_else(limit_err)?;
488            predecessors[*at] = source;
489            *at = at.checked_add(1).ok_or_else(limit_err)?;
490        }
491    }
492    let mut live = Vec::new();
493    live.try_reserve_exact(state_count)
494        .map_err(|_| graph_limit("graph live-set allocation"))?;
495    live.resize(state_count, false);
496    let mut queue = VecDeque::new();
497    queue
498        .try_reserve(state_count)
499        .map_err(|_| graph_limit("graph live queue allocation"))?;
500    for (index, state) in graph.states.iter().enumerate() {
501        if state.accepting {
502            live[index] = true;
503            queue.push_back(index);
504        }
505    }
506    while let Some(target) = queue.pop_front() {
507        for &source in &predecessors[offsets[target]..offsets[target + 1]] {
508            if !live[source] {
509                live[source] = true;
510                queue.push_back(source);
511            }
512        }
513    }
514    Ok(live)
515}
516
517fn graph_class_table(
518    matrix: &[StateId],
519    state_count: usize,
520) -> Result<(ClassTable, Vec<u8>), CompileError> {
521    let mut signatures: FxHashMap<Box<[StateId]>, u16> = FxHashMap::default();
522    signatures
523        .try_reserve(256)
524        .map_err(|_| graph_limit("graph class map allocation"))?;
525    let mut members: Vec<Vec<u8>> = Vec::new();
526    members
527        .try_reserve_exact(256)
528        .map_err(|_| graph_limit("graph class allocation"))?;
529    let mut representatives = Vec::new();
530    representatives
531        .try_reserve_exact(256)
532        .map_err(|_| graph_limit("graph class representative allocation"))?;
533    let mut of_byte = [ByteClassId(0); 256];
534    for byte in 0u8..=255 {
535        let mut signature = Vec::new();
536        signature
537            .try_reserve_exact(state_count)
538            .map_err(|_| graph_limit("graph class signature allocation"))?;
539        for state in 0..state_count {
540            signature.push(matrix[state * 256 + usize::from(byte)]);
541        }
542        let class = if let Some(class) = signatures.get(signature.as_slice()) {
543            *class
544        } else {
545            let class = u16::try_from(members.len()).map_err(|_| limit_err())?;
546            signatures.insert(signature.into_boxed_slice(), class);
547            members.push(Vec::new());
548            representatives.push(byte);
549            class
550        };
551        members[usize::from(class)]
552            .try_reserve(1)
553            .map_err(|_| graph_limit("graph class member allocation"))?;
554        members[usize::from(class)].push(byte);
555        of_byte[usize::from(byte)] = ByteClassId(class);
556    }
557    Ok((ClassTable { members, of_byte }, representatives))
558}
559
560fn materialize_graph_rows(
561    graph: &AutomatonGraph,
562    remap: &[StateId],
563    matrix: Vec<StateId>,
564    class_table: ClassTable,
565    representatives: &[u8],
566) -> Result<RefEngine, CompileError> {
567    let state_count = matrix.len() / 256;
568    let mut accepting = Vec::new();
569    accepting
570        .try_reserve_exact(state_count)
571        .map_err(|_| graph_limit("graph accepting allocation"))?;
572    accepting.resize(state_count, false);
573    for (source, mapped) in remap.iter().copied().enumerate() {
574        if mapped != DEAD {
575            accepting[usize::try_from(mapped.get()).map_err(|_| limit_err())?] =
576                graph.states[source].accepting;
577        }
578    }
579    let mut live_classes = Vec::new();
580    live_classes
581        .try_reserve_exact(state_count)
582        .map_err(|_| graph_limit("graph live-class allocation"))?;
583    live_classes.resize(state_count, ClassMask::empty());
584    let mut row_offsets = Vec::new();
585    row_offsets
586        .try_reserve_exact(state_count + 1)
587        .map_err(|_| graph_limit("graph row allocation"))?;
588    let mut edges = Vec::new();
589    edges
590        .try_reserve(matrix.iter().filter(|&&target| target != DEAD).count())
591        .map_err(|_| graph_limit("graph transition allocation"))?;
592    for state in 0..state_count {
593        row_offsets.push(u32::try_from(edges.len()).map_err(|_| limit_err())?);
594        for (class, byte) in representatives.iter().copied().enumerate() {
595            let target = matrix[state * 256 + usize::from(byte)];
596            if target != DEAD {
597                let class_id = ByteClassId(u16::try_from(class).map_err(|_| limit_err())?);
598                edges.push((class_id, target));
599                live_classes[state].set(class);
600            }
601        }
602    }
603    row_offsets.push(u32::try_from(edges.len()).map_err(|_| limit_err())?);
604    let start_index = usize::try_from(graph.start.0).map_err(|_| limit_err())?;
605    let start = remap.get(start_index).copied().unwrap_or(DEAD);
606    let live_transition_count = edges.len();
607    let transitions = Transitions {
608        row_offsets: row_offsets.into_boxed_slice(),
609        edges: edges.into_boxed_slice(),
610    };
611    let accepting = accepting.into_boxed_slice();
612    let residual_cardinality = residual_cardinalities(&transitions, &accepting, &class_table)?;
613    Ok(RefEngine {
614        transitions,
615        accepting,
616        residual_cardinality,
617        live_classes: live_classes.into_boxed_slice(),
618        class_count: class_table.members.len(),
619        class_table,
620        state_count,
621        live_transition_count,
622        start,
623        dead: DEAD,
624    })
625}
626
627pub(crate) fn build_from_regex(pattern: &str) -> Result<RefEngine, CompileError> {
628    build_from_regex_with_limits(
629        pattern,
630        DFA_SIZE_LIMIT,
631        DETERMINIZE_SIZE_LIMIT,
632        NFA_SIZE_LIMIT,
633        NEST_LIMIT,
634    )
635}
636
637pub(crate) fn build_from_unicode_regex(pattern: &str) -> Result<RefEngine, CompileError> {
638    let _permit = acquire_automaton_build_bytes(automaton_build_worst_case_bytes(
639        DFA_SIZE_LIMIT,
640        DETERMINIZE_SIZE_LIMIT,
641        NFA_SIZE_LIMIT,
642    ))?;
643    let dfa = dense::Builder::new()
644        .configure(
645            dense::Config::new()
646                .minimize(false)
647                .start_kind(StartKind::Anchored)
648                .match_kind(MatchKind::All)
649                .dfa_size_limit(Some(DFA_SIZE_LIMIT))
650                .determinize_size_limit(Some(DETERMINIZE_SIZE_LIMIT)),
651        )
652        .syntax(
653            syntax::Config::new()
654                .unicode(true)
655                .utf8(true)
656                .nest_limit(NEST_LIMIT),
657        )
658        .thompson(thompson::Config::new().nfa_size_limit(Some(NFA_SIZE_LIMIT)))
659        .build(pattern)
660        .map_err(map_build_error)?;
661    bfs_materialize(&dfa)
662}
663
664fn build_from_regex_with_limits(
665    pattern: &str,
666    dfa_size_limit: usize,
667    determinize_size_limit: usize,
668    nfa_size_limit: usize,
669    nest_limit: u32,
670) -> Result<RefEngine, CompileError> {
671    let _permit = acquire_automaton_build_bytes(automaton_build_worst_case_bytes(
672        dfa_size_limit,
673        determinize_size_limit,
674        nfa_size_limit,
675    ))?;
676    let dfa = dense::Builder::new()
677        .configure(
678            dense::Config::new()
679                .minimize(false)
680                .start_kind(StartKind::Anchored)
681                .match_kind(MatchKind::All)
682                .dfa_size_limit(Some(dfa_size_limit))
683                .determinize_size_limit(Some(determinize_size_limit)),
684        )
685        .syntax(
686            syntax::Config::new()
687                .unicode(false)
688                .utf8(false)
689                .nest_limit(nest_limit),
690        )
691        .thompson(thompson::Config::new().nfa_size_limit(Some(nfa_size_limit)))
692        .build(pattern)
693        .map_err(map_build_error)?;
694    bfs_materialize(&dfa)
695}
696
697fn map_build_error(e: dense::BuildError) -> CompileError {
698    let nfa_size_limit_hit = std::error::Error::source(&e)
699        .and_then(|src| src.downcast_ref::<thompson::BuildError>())
700        .is_some_and(|nfa_err| nfa_err.size_limit().is_some());
701    let code = if e.is_size_limit_exceeded() || nfa_size_limit_hit {
702        ErrorCode::InternalLimitExceeded
703    } else {
704        ErrorCode::Malformed
705    };
706    let mut observed = e.to_string();
707    let mut source = std::error::Error::source(&e);
708    for _ in 0..4 {
709        let Some(error) = source else {
710            break;
711        };
712        observed.push_str(": ");
713        observed.push_str(&error.to_string());
714        source = error.source();
715    }
716    CompileError::new(
717        code,
718        Stage::L3,
719        "regex-automata failed to build the byte DFA",
720    )
721    .with_observed(observed)
722}
723
724fn build_class_table(dfa: &dense::DFA<Vec<u32>>) -> ClassTable {
725    let classes = dfa.byte_classes();
726    let mut raw: FxHashMap<u8, Vec<u8>> = FxHashMap::default();
727    for b in 0u8..=255 {
728        raw.entry(classes.get(b)).or_default().push(b);
729    }
730    let mut ordered: Vec<(u8, Vec<u8>)> = raw.into_iter().collect();
731    ordered.sort_by_key(|(_, bytes)| bytes[0]);
732    let members: Vec<Vec<u8>> = ordered.into_iter().map(|(_, bytes)| bytes).collect();
733
734    let mut of_byte = [ByteClassId(0); 256];
735    for (cid, bytes) in members.iter().enumerate() {
736        let class = ByteClassId(u16::try_from(cid).expect("byte class count <= 256"));
737        for &b in bytes {
738            of_byte[b as usize] = class;
739        }
740    }
741    ClassTable { members, of_byte }
742}
743
744struct RawState {
745    edges: Vec<(ByteClassId, RaStateId)>,
746    accepting: bool,
747}
748
749fn bfs_materialize(dfa: &dense::DFA<Vec<u32>>) -> Result<RefEngine, CompileError> {
750    let ct = build_class_table(dfa);
751    let num_classes = ct.members.len();
752
753    let ext_start = dfa
754        .start_state(&start::Config::new().anchored(Anchored::Yes))
755        .map_err(|e| {
756            CompileError::new(
757                ErrorCode::Malformed,
758                Stage::L3,
759                "anchored start state does not exist",
760            )
761            .with_observed(e.to_string())
762        })?;
763    if dfa.is_quit_state(ext_start) {
764        return Err(quit_err());
765    }
766
767    let mut disco_order: Vec<RaStateId> = Vec::new();
768    let mut seen: FxHashSet<RaStateId> = FxHashSet::default();
769    let mut raw: FxHashMap<RaStateId, RawState> = FxHashMap::default();
770    let mut queue: VecDeque<RaStateId> = VecDeque::new();
771    queue.push_back(ext_start);
772    seen.insert(ext_start);
773    while let Some(e) = queue.pop_front() {
774        if disco_order.len() >= MAX_MATERIALIZE_STATES {
775            return Err(limit_err());
776        }
777        disco_order.push(e);
778        let mut edges = Vec::new(); // reserving num_classes wastes 90%+ on typical schemas (measured)
779        for (cid, bytes) in ct.members.iter().enumerate() {
780            let t = dfa.next_state(e, bytes[0]);
781            if dfa.is_quit_state(t) {
782                return Err(quit_err());
783            }
784            if !dfa.is_dead_state(t) {
785                let class = ByteClassId(u16::try_from(cid).expect("byte class count <= 256"));
786                edges.push((class, t));
787                if seen.insert(t) {
788                    queue.push_back(t);
789                }
790            }
791        }
792        let accepting = dfa.is_match_state(dfa.next_eoi_state(e));
793        raw.insert(e, RawState { edges, accepting });
794    }
795
796    let mut rev: FxHashMap<RaStateId, Vec<RaStateId>> = FxHashMap::default();
797    for e in &disco_order {
798        for (_c, t) in &raw[e].edges {
799            rev.entry(*t).or_default().push(*e);
800        }
801    }
802    let mut live: FxHashSet<RaStateId> = FxHashSet::default();
803    let mut lq: VecDeque<RaStateId> = VecDeque::new();
804    for e in &disco_order {
805        if raw[e].accepting && live.insert(*e) {
806            lq.push_back(*e);
807        }
808    }
809    while let Some(t) = lq.pop_front() {
810        if let Some(preds) = rev.get(&t) {
811            for p in preds {
812                if live.insert(*p) {
813                    lq.push_back(*p);
814                }
815            }
816        }
817    }
818
819    let mut final_remap: FxHashMap<RaStateId, StateId> = FxHashMap::default();
820    for e in &disco_order {
821        if live.contains(e) && !final_remap.contains_key(e) {
822            let id = StateId(u32::try_from(final_remap.len() + 1).map_err(|_| limit_err())?);
823            final_remap.insert(*e, id);
824        }
825    }
826    let state_count = final_remap.len() + 1; // +1 for DEAD
827
828    let cells = state_count.checked_mul(num_classes).ok_or_else(limit_err)?;
829    if cells > MAX_TRANSITION_CELLS {
830        return Err(limit_err());
831    }
832
833    let mut accepting = vec![false; state_count];
834    let mut live_classes = vec![ClassMask::empty(); state_count];
835    let mut row_offsets = Vec::new();
836    row_offsets
837        .try_reserve_exact(state_count + 1)
838        .map_err(|_| limit_err())?;
839    row_offsets.push(0u32); // DEAD (row 0) is never in disco_order/final_remap and always empty.
840    let mut edges_flat = Vec::new();
841    for e in &disco_order {
842        let Some(&id) = final_remap.get(e) else {
843            continue; // non-live folds into DEAD, no row.
844        };
845        let raw_state = &raw[e];
846        let row = id.get() as usize;
847        accepting[row] = raw_state.accepting;
848        row_offsets.push(u32::try_from(edges_flat.len()).map_err(|_| limit_err())?);
849        let mut lc = ClassMask::empty();
850        for (class, t) in &raw_state.edges {
851            let target = final_remap.get(t).copied().unwrap_or(DEAD);
852            if target == DEAD {
853                continue; // a co-accessibility-pruned edge is exactly "no edge": drop it.
854            }
855            if edges_flat.len() >= MAX_TRANSITION_CELLS {
856                return Err(limit_err());
857            }
858            edges_flat.push((*class, target));
859            lc.set(usize::from(class.get()));
860        }
861        live_classes[row] = lc;
862    }
863    row_offsets.push(u32::try_from(edges_flat.len()).map_err(|_| limit_err())?);
864    let live_transition_count = edges_flat.len();
865
866    let start = final_remap.get(&ext_start).copied().unwrap_or(DEAD);
867    let transitions = Transitions {
868        row_offsets: row_offsets.into_boxed_slice(),
869        edges: edges_flat.into_boxed_slice(),
870    };
871    let accepting = accepting.into_boxed_slice();
872    let residual_cardinality = residual_cardinalities(&transitions, &accepting, &ct)?;
873    Ok(RefEngine {
874        transitions,
875        accepting,
876        residual_cardinality,
877        live_classes: live_classes.into_boxed_slice(),
878        class_table: ct,
879        class_count: num_classes,
880        state_count,
881        live_transition_count,
882        start,
883        dead: DEAD,
884    })
885}
886
887fn quit_err() -> CompileError {
888    CompileError::new(
889        ErrorCode::Unsupported,
890        Stage::L3,
891        "byte DFA reached a quit state",
892    )
893}
894
895fn limit_err() -> CompileError {
896    let mut error = CompileError::new(
897        ErrorCode::InternalLimitExceeded,
898        Stage::L3,
899        "reference DFA state count exceeds the materialization limit",
900    );
901    error.limit = Some((
902        crate::error::LimitKind::StateCount,
903        MAX_MATERIALIZE_STATES.saturating_add(1),
904        MAX_MATERIALIZE_STATES,
905    ));
906    error
907}
908
909impl RefEngine {
910    #[must_use]
911    pub(crate) fn class_of_byte(&self, b: u8) -> ByteClassId {
912        self.class_table.of_byte[b as usize]
913    }
914
915    #[must_use]
916    pub fn consume_token(&self, s: StateId, token_bytes: &[u8]) -> Option<StateId> {
917        let mut cur = s;
918        for &b in token_bytes {
919            if cur == self.dead() {
920                return None;
921            }
922            cur = self.step(cur, self.class_table.of_byte[b as usize]);
923            if cur == self.dead() {
924                return None;
925            }
926        }
927        Some(cur)
928    }
929
930    fn can_consume_scalar_interval(&self, s: StateId, start: u32, end: u32) -> Option<bool> {
931        if start > end || end > 0x10_ffff || self.is_dead(s) {
932            return Some(false);
933        }
934
935        fn encoded(value: u32) -> Option<([u8; 4], usize)> {
936            let scalar = char::from_u32(value)?;
937            let mut storage = [0u8; 4];
938            let len = scalar.encode_utf8(&mut storage).len();
939            Some((storage, len))
940        }
941
942        let (lower, width) = encoded(start)?;
943        let (upper, upper_width) = encoded(end)?;
944        if width != upper_width {
945            return None;
946        }
947        let mut frontier = Vec::new();
948        frontier.try_reserve_exact(1).ok()?;
949        frontier.push((s, true, true));
950
951        for position in 0..width {
952            let mut next = Vec::new();
953            next.try_reserve(self.state_count.min(256).saturating_mul(4))
954                .ok()?;
955            for &(state, lower_tight, upper_tight) in &frontier {
956                let lo = if lower_tight {
957                    lower[position]
958                } else if position == 0 {
959                    lower[0]
960                } else {
961                    0x80
962                };
963                let hi = if upper_tight {
964                    upper[position]
965                } else if position == 0 {
966                    upper[0]
967                } else {
968                    0xbf
969                };
970                for byte in lo..=hi {
971                    let target = self.step(state, self.class_of_byte(byte));
972                    if self.is_dead(target) {
973                        continue;
974                    }
975                    let candidate = (
976                        target,
977                        lower_tight && byte == lower[position],
978                        upper_tight && byte == upper[position],
979                    );
980                    if !next.contains(&candidate) {
981                        next.try_reserve(1).ok()?;
982                        next.push(candidate);
983                    }
984                }
985            }
986            if next.is_empty() {
987                return Some(false);
988            }
989            frontier = next;
990        }
991        Some(true)
992    }
993
994    pub(crate) fn can_consume_supplementary_range(
995        &self,
996        s: StateId,
997        start: u32,
998        end: u32,
999    ) -> Option<bool> {
1000        if start < 0x1_0000 || end > 0x10_ffff {
1001            return Some(false);
1002        }
1003        self.can_consume_scalar_interval(s, start, end)
1004    }
1005
1006    pub(crate) fn can_consume_any_scalar(&self, s: StateId) -> Option<bool> {
1007        let intervals = [
1008            (0, 0x7f),
1009            (0x80, 0x7ff),
1010            (0x800, 0xd7ff),
1011            (0xe000, 0xffff),
1012            (0x1_0000, 0x10_ffff),
1013        ];
1014        let mut unknown = false;
1015        for (start, end) in intervals {
1016            match self.can_consume_scalar_interval(s, start, end) {
1017                Some(true) => return Some(true),
1018                Some(false) => {}
1019                None => unknown = true,
1020            }
1021        }
1022        (!unknown).then_some(false)
1023    }
1024
1025    pub fn allowed_tokens(&self, s: StateId, vocab: &Vocabulary) -> Result<Bitmask, CompileError> {
1026        self.allowed_tokens_from_records(
1027            s,
1028            mask_width(vocab)?,
1029            vocab
1030                .tokens()
1031                .iter()
1032                .map(|(b, i)| (b.as_slice(), i.as_slice())),
1033        )
1034    }
1035
1036    pub(crate) fn allowed_tokens_from_records<'a>(
1037        &self,
1038        s: StateId,
1039        mask_vocab_size: usize,
1040        records: impl Iterator<Item = (&'a [u8], &'a [u32])>,
1041    ) -> Result<Bitmask, CompileError> {
1042        let mut out = Bitmask::zeros(mask_vocab_size);
1043        for (bytes, ids) in records {
1044            if self.consume_token(s, bytes).is_some() {
1045                for &id in ids {
1046                    out.set(TokenId(id))?;
1047                }
1048            }
1049        }
1050        Ok(out)
1051    }
1052
1053    #[must_use]
1054    pub fn eos_legal(&self, s: StateId) -> bool {
1055        self.is_accepting(s)
1056    }
1057
1058    #[must_use]
1059    pub fn accepts(&self, instance: &[u8]) -> bool {
1060        let mut cur = self.start();
1061        for &b in instance {
1062            if cur == self.dead() {
1063                return false;
1064            }
1065            cur = self.step(cur, self.class_table.of_byte[b as usize]);
1066        }
1067        self.is_accepting(cur)
1068    }
1069}
1070
1071pub(crate) fn mask_width(vocab: &Vocabulary) -> Result<usize, CompileError> {
1072    let max_token = vocab.tokens().values().flatten().copied().max();
1073    let hi = max_token.map_or(vocab.eos_token_id(), |m| m.max(vocab.eos_token_id()));
1074    usize::try_from(hi)
1075        .ok()
1076        .and_then(|v| v.checked_add(1))
1077        .ok_or_else(|| {
1078            CompileError::new(
1079                ErrorCode::InternalLimitExceeded,
1080                Stage::L3,
1081                "max token id overflows the mask width",
1082            )
1083        })
1084}
1085
1086#[cfg(test)]
1087type StatePair = (StateId, StateId);
1088#[cfg(test)]
1089type PairPredecessors = FxHashMap<StatePair, Option<(StatePair, u8)>>;
1090
1091#[cfg(test)]
1092pub(crate) fn assert_exact_equivalence(old: &RefEngine, new: &RefEngine) {
1093    let start = (old.start(), new.start());
1094    let mut queue = VecDeque::from([start]);
1095    let mut predecessor = PairPredecessors::default();
1096    predecessor.insert(start, None);
1097    while let Some(pair) = queue.pop_front() {
1098        let classification_matches = old.is_dead(pair.0) == new.is_dead(pair.1)
1099            && old.can_continue(pair.0) == new.can_continue(pair.1)
1100            && old.is_accepting(pair.0) == new.is_accepting(pair.1);
1101        if !classification_matches {
1102            panic!(
1103                "automata differ after bytes {:?}: old(dead={}, continue={}, accept={}) new(dead={}, continue={}, accept={})",
1104                equivalence_counterexample(pair, &predecessor),
1105                old.is_dead(pair.0),
1106                old.can_continue(pair.0),
1107                old.is_accepting(pair.0),
1108                new.is_dead(pair.1),
1109                new.can_continue(pair.1),
1110                new.is_accepting(pair.1),
1111            );
1112        }
1113        for byte in 0u8..=255 {
1114            let next = (
1115                old.step(pair.0, old.class_table.of_byte[usize::from(byte)]),
1116                new.step(pair.1, new.class_table.of_byte[usize::from(byte)]),
1117            );
1118            if let std::collections::hash_map::Entry::Vacant(entry) = predecessor.entry(next) {
1119                entry.insert(Some((pair, byte)));
1120                queue.push_back(next);
1121            }
1122        }
1123    }
1124}
1125
1126#[cfg(test)]
1127fn equivalence_counterexample(mut pair: StatePair, predecessor: &PairPredecessors) -> Vec<u8> {
1128    let mut bytes = Vec::new();
1129    while let Some(Some((previous, byte))) = predecessor.get(&pair) {
1130        bytes.push(*byte);
1131        pair = *previous;
1132    }
1133    bytes.reverse();
1134    bytes
1135}
1136
1137#[cfg(test)]
1138mod tests {
1139    use std::collections::VecDeque;
1140
1141    use super::*;
1142    use crate::mem_gate::BuildGate;
1143
1144    #[test]
1145    #[ignore]
1146    fn engine_build_split_on_production_corpus_patterns() {
1147        use std::time::Instant;
1148        let patterns = [
1149            ("bool", r#"(?:true|false)"#),
1150            (
1151                "flat_object",
1152                r#"\{(?:"age":-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?,"name":"(?:[^"\\\x00-\x1f]|\\["\\/bfnrt]|\\u[0-9a-fA-F]{4})*"|"name":"(?:[^"\\\x00-\x1f]|\\["\\/bfnrt]|\\u[0-9a-fA-F]{4})*","age":-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?)\}"#,
1153            ),
1154            (
1155                "bounded_array_of_objects",
1156                r#"\[(?:\{"x":-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\}(?:,\{"x":-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\}){0,4})\]"#,
1157            ),
1158        ];
1159        for (name, pattern) in patterns {
1160            let n = 200;
1161            let mut dense_ns = 0u128;
1162            let mut materialize_ns = 0u128;
1163            for _ in 0..n {
1164                let t0 = Instant::now();
1165                let dfa = dense::Builder::new()
1166                    .configure(
1167                        dense::Config::new()
1168                            .minimize(false)
1169                            .start_kind(StartKind::Anchored)
1170                            .match_kind(MatchKind::All),
1171                    )
1172                    .syntax(syntax::Config::new().unicode(false).utf8(false))
1173                    .build(pattern)
1174                    .unwrap();
1175                dense_ns += t0.elapsed().as_nanos();
1176
1177                let t0 = Instant::now();
1178                bfs_materialize(&dfa).unwrap();
1179                materialize_ns += t0.elapsed().as_nanos();
1180            }
1181            println!(
1182                "{name:28} dense_dfa_build={:>8.1}us  bfs_materialize={:>8.1}us",
1183                dense_ns as f64 / n as f64 / 1000.0,
1184                materialize_ns as f64 / n as f64 / 1000.0,
1185            );
1186        }
1187    }
1188
1189    #[test]
1190    fn class_table_covers_all_256_bytes_disjointly() {
1191        let e = build_from_regex("(cat|car|carbon)").unwrap();
1192        let ct = &e.class_table;
1193        let mut seen = [false; 256];
1194        for (cid, bytes) in ct.members.iter().enumerate() {
1195            for &b in bytes {
1196                assert!(!seen[b as usize], "byte {b} appears in two classes");
1197                seen[b as usize] = true;
1198                assert_eq!(ct.of_byte[b as usize].0 as usize, cid);
1199            }
1200        }
1201        assert!(seen.iter().all(|&x| x), "every byte 0..=255 is covered");
1202    }
1203
1204    #[test]
1205    fn quit_byte_on_any_reachable_transition_is_a_structured_error() {
1206        let dfa = dense::Builder::new()
1207            .configure(
1208                dense::Config::new()
1209                    .minimize(false)
1210                    .start_kind(StartKind::Anchored)
1211                    .quit(b'x', true),
1212            )
1213            .syntax(syntax::Config::new().unicode(false).utf8(false))
1214            .build("a")
1215            .expect("dfa builds with a quit byte configured");
1216        let err = bfs_materialize(&dfa).expect_err("a reachable quit byte must be rejected");
1217        assert_eq!(err.code, ErrorCode::Unsupported);
1218    }
1219
1220    #[test]
1221    fn dfa_size_limit_exceeded_is_a_typed_internal_limit_not_malformed() {
1222        let err = build_from_regex_with_limits("[a-z]{50}", 1, 1 << 20, 1 << 20, NEST_LIMIT)
1223            .expect_err("1 byte cannot hold any real DFA");
1224        assert_eq!(err.code, ErrorCode::InternalLimitExceeded);
1225        assert_eq!(
1226            err.stage,
1227            Stage::L3,
1228            "automaton admission must report the compile stage"
1229        );
1230    }
1231
1232    #[test]
1233    fn determinize_size_limit_exceeded_is_a_typed_internal_limit_not_malformed() {
1234        let err = build_from_regex_with_limits("[a-z]{50}", 1 << 30, 1, 1 << 20, NEST_LIMIT)
1235            .expect_err("1 byte cannot hold any determinization scratch");
1236        assert_eq!(err.code, ErrorCode::InternalLimitExceeded);
1237        assert_eq!(err.stage, Stage::L3);
1238    }
1239
1240    #[test]
1241    fn nfa_size_limit_exceeded_is_a_typed_internal_limit_not_malformed() {
1242        let err = build_from_regex_with_limits("[a-z]{50}", 1 << 30, 1 << 30, 1, NEST_LIMIT)
1243            .expect_err("1 byte cannot hold any Thompson NFA");
1244        assert_eq!(err.code, ErrorCode::InternalLimitExceeded);
1245        assert_eq!(err.stage, Stage::L3);
1246    }
1247
1248    #[test]
1249    fn wrapped_nfa_build_error_under_a_realistic_size_limit_is_still_typed_internal_limit() {
1250        let pattern = format!(".{{1,{}}}", 1 << 20);
1251        let err = build_from_regex_with_limits(&pattern, 1 << 30, 1 << 30, 1 << 10, NEST_LIMIT)
1252            .expect_err("a million-repetition class must exceed a 1KiB NFA heap");
1253        assert_eq!(err.code, ErrorCode::InternalLimitExceeded);
1254    }
1255
1256    #[test]
1257    #[ignore = "builds a ~65k-state DFA; run manually with --ignored --nocapture"]
1258    fn discovery_state_count_cap_rejects_a_pattern_with_too_many_states() {
1259        let pattern = format!("a{{1,{}}}", MAX_MATERIALIZE_STATES + 10);
1260        let err = build_from_regex(&pattern).expect_err("must exceed the discovery state cap");
1261        assert_eq!(err.code, ErrorCode::InternalLimitExceeded);
1262        assert_eq!(err.stage, Stage::L3);
1263    }
1264
1265    #[test]
1266    fn automaton_gate_is_charged_and_the_permit_is_released_on_success() {
1267        let gate = BuildGate::new(4, 1 << 20, Stage::L3);
1268        let before_peak = gate.peak_bytes();
1269        {
1270            let _permit = gate.acquire(1024).unwrap();
1271            assert_eq!(gate.active_bytes(), 1024);
1272        }
1273        assert_eq!(gate.active_bytes(), 0, "the permit must release on drop");
1274        assert!(gate.peak_bytes() >= before_peak.max(1024));
1275    }
1276
1277    #[test]
1278    fn nest_limit_exceeded_is_a_structured_error() {
1279        let deep = "(?:".repeat(10) + "a" + &")?".repeat(10);
1280        let err = build_from_regex_with_limits(&deep, 1 << 30, 1 << 30, 1 << 30, 5)
1281            .expect_err("10 nested groups must exceed a nest limit of 5");
1282        assert!(matches!(
1283            err.code,
1284            ErrorCode::Malformed | ErrorCode::InternalLimitExceeded
1285        ));
1286    }
1287
1288    #[test]
1289    fn dead_self_loops_on_all_256_bytes_and_never_accepts() {
1290        let e = build_from_regex("true").unwrap();
1291        let dead = e.dead();
1292        assert!(!e.is_accepting(dead));
1293        for b in 0u8..=255 {
1294            let class = e.class_table.of_byte[b as usize];
1295            assert_eq!(
1296                e.step(dead, class),
1297                dead,
1298                "byte {b} from DEAD must stay DEAD"
1299            );
1300        }
1301    }
1302
1303    #[test]
1304    fn dead_row_has_zero_stored_edges() {
1305        let e = build_from_regex("true").unwrap();
1306        assert_eq!(e.transitions.row_offsets[0], e.transitions.row_offsets[1]);
1307    }
1308
1309    #[test]
1310    fn residual_cardinality_is_exact_for_finite_languages_and_marks_infinite_ones() {
1311        let finite = build_from_regex("[a-c]{1,2}").unwrap();
1312        assert_eq!(finite.residual_cardinality(finite.start()), 12);
1313        let after_a = finite.consume_token(finite.start(), b"a").unwrap();
1314        assert_eq!(finite.residual_cardinality(after_a), 4);
1315        assert_eq!(finite.residual_cardinality(finite.dead()), 0);
1316
1317        let infinite = build_from_regex("[a-z]+").unwrap();
1318        assert_eq!(infinite.residual_cardinality(infinite.start()), u64::MAX);
1319        let after_a = infinite.consume_token(infinite.start(), b"a").unwrap();
1320        assert_eq!(infinite.residual_cardinality(after_a), u64::MAX);
1321    }
1322
1323    #[test]
1324    fn wide_out_degree_row_accepts_every_member_at_first_middle_last_and_rejects_gaps() {
1325        let alphabet: Vec<u8> = (1u8..=120).collect();
1326        let pattern = format!(
1327            "(?:{})",
1328            alphabet
1329                .iter()
1330                .map(|&b| format!("\\x{b:02x}"))
1331                .collect::<Vec<_>>()
1332                .join("|")
1333        );
1334        let e = build_from_regex(&pattern).unwrap();
1335        for &b in &alphabet {
1336            assert!(e.accepts(&[b]), "byte {b:#04x} must be accepted");
1337        }
1338        for b in [0u8, 121, 200, 255] {
1339            assert!(
1340                !e.accepts(&[b]),
1341                "byte {b:#04x} was never a branch and must reject"
1342            );
1343        }
1344    }
1345
1346    #[test]
1347    fn independent_walk_against_the_raw_regex_automata_dfa_agrees_on_random_inputs() {
1348        let patterns = [
1349            "(cat|car|carbon)",
1350            "\\{\"a\":(?:true|false)\\}",
1351            "[a-z]{1,5}",
1352            "(?:0|[1-9][0-9]{0,4})",
1353            "(?:foo|foobar|foobaz|bar)",
1354        ];
1355        for pattern in patterns {
1356            let raw_dfa = dense::Builder::new()
1357                .configure(
1358                    dense::Config::new()
1359                        .minimize(false)
1360                        .start_kind(StartKind::Anchored)
1361                        .match_kind(MatchKind::All),
1362                )
1363                .syntax(syntax::Config::new().unicode(false).utf8(false))
1364                .build(pattern)
1365                .unwrap();
1366            let engine = build_from_regex(pattern).unwrap();
1367            let start = raw_dfa
1368                .start_state(&start::Config::new().anchored(Anchored::Yes))
1369                .unwrap();
1370            let mut rng = 0x2545F4914F6CDD1Du64;
1371            let mut next = || {
1372                rng ^= rng << 13;
1373                rng ^= rng >> 7;
1374                rng ^= rng << 17;
1375                rng
1376            };
1377            for _ in 0..200 {
1378                let len = (next() % 6) as usize;
1379                let bytes: Vec<u8> = (0..len).map(|_| (next() % 256) as u8).collect();
1380                let mut raw_state = start;
1381                let mut raw_dead = false;
1382                for &b in &bytes {
1383                    raw_state = raw_dfa.next_state(raw_state, b);
1384                    if raw_dfa.is_dead_state(raw_state) {
1385                        raw_dead = true;
1386                        break;
1387                    }
1388                }
1389                let raw_accepts =
1390                    !raw_dead && raw_dfa.is_match_state(raw_dfa.next_eoi_state(raw_state));
1391                assert_eq!(
1392                    engine.accepts(&bytes),
1393                    raw_accepts,
1394                    "pattern {pattern:?} bytes {bytes:?}"
1395                );
1396            }
1397        }
1398    }
1399
1400    #[test]
1401    fn deterministic_build_is_byte_identical() {
1402        let a = build_from_regex("(cat|car|carbon)").unwrap();
1403        let b = build_from_regex("(cat|car|carbon)").unwrap();
1404        assert_eq!(a.state_count(), b.state_count());
1405        assert_eq!(a.start(), b.start());
1406        assert_eq!(a.class_table.members, b.class_table.members);
1407        let num_classes = a.class_table.members.len();
1408        for raw in 0..a.state_count() {
1409            let s = StateId(raw as u32);
1410            assert_eq!(a.is_accepting(s), b.is_accepting(s));
1411            for c in 0..num_classes {
1412                let class = ByteClassId(u16::try_from(c).unwrap());
1413                assert_eq!(a.step(s, class), b.step(s, class));
1414            }
1415        }
1416    }
1417
1418    #[test]
1419    fn every_live_state_reaches_acceptance() {
1420        let e = build_from_regex("(cat|car|carbon)").unwrap();
1421        let num_classes = e.class_table.members.len();
1422        for start in 1..e.state_count() {
1423            let mut seen = vec![false; e.state_count()];
1424            let mut q = VecDeque::from([start]);
1425            seen[start] = true;
1426            let mut reaches_accept = false;
1427            while let Some(s) = q.pop_front() {
1428                if e.is_accepting(StateId(s as u32)) {
1429                    reaches_accept = true;
1430                    break;
1431                }
1432                for c in 0..num_classes {
1433                    let class = ByteClassId(u16::try_from(c).unwrap());
1434                    let tgt = e.step(StateId(s as u32), class).get() as usize;
1435                    if tgt != 0 && !seen[tgt] {
1436                        seen[tgt] = true;
1437                        q.push_back(tgt);
1438                    }
1439                }
1440            }
1441            assert!(
1442                reaches_accept,
1443                "state {start} cannot reach an accepting state"
1444            );
1445        }
1446    }
1447
1448    #[test]
1449    fn empty_language_collapses_to_dead_start() {
1450        let e = build_from_regex("[a&&b]").unwrap();
1451        assert_eq!(e.start(), e.dead());
1452    }
1453
1454    #[test]
1455    fn empty_pattern_matches_only_the_empty_string() {
1456        let e = build_from_regex("").unwrap();
1457        assert!(e.is_accepting(e.start()));
1458        assert!(!build_from_regex("").unwrap().accepts(b"a"));
1459    }
1460
1461    #[test]
1462    fn escaped_special_bytes_match_the_literal_byte() {
1463        let e = build_from_regex(r"a\.b").unwrap();
1464        assert!(e.accepts(b"a.b"));
1465        assert!(!e.accepts(b"axb"));
1466    }
1467
1468    #[test]
1469    fn anchored_matching_rejects_a_value_embedded_in_trailing_bytes() {
1470        let e = build_from_regex("cat").unwrap(); // anchored + EOI-only: not a substring search
1471        assert!(e.accepts(b"cat"));
1472        assert!(!e.accepts(b"cats"));
1473        assert!(!e.accepts(b"xcat"));
1474        assert!(!e.accepts(b"xcaty"));
1475    }
1476
1477    mod crossref_vs_outlines_core_index_tests {
1478        use super::*;
1479
1480        fn accepts(pattern: &str, s: &str) -> bool {
1481            build_from_regex(pattern).unwrap().accepts(s.as_bytes())
1482        }
1483
1484        #[test]
1485        fn matches_index_from_regex_language() {
1486            let p = "0|[1-9][0-9]*";
1487            assert!(accepts(p, "0"));
1488            assert!(accepts(p, "2"));
1489            assert!(accepts(p, "20"));
1490            assert!(!accepts(p, "02"));
1491            assert!(!accepts(p, "1a"));
1492            assert!(!accepts(p, ""));
1493        }
1494
1495        #[test]
1496        fn matches_index_from_regex_completeness_language() {
1497            let p = "(ac|[^a])+";
1498            assert!(accepts(p, "acac"));
1499            assert!(accepts(p, "b"));
1500            assert!(!accepts(p, "a"));
1501            assert!(!accepts(p, ""));
1502        }
1503
1504        #[test]
1505        fn multibyte_char_class_range_is_a_structured_unicode_mode_error() {
1506            let p = "😇| [😈-😍][😇-😎]*";
1507            let code = build_from_regex(p).unwrap_err().code;
1508            assert!(matches!(
1509                code,
1510                crate::error::ErrorCode::Malformed | crate::error::ErrorCode::Unsupported
1511            ));
1512        }
1513
1514        #[test]
1515        fn multibyte_literal_alternation_still_works_byte_for_byte() {
1516            let p = "😇|😈";
1517            assert!(accepts(p, "😇"));
1518            assert!(accepts(p, "😈"));
1519            assert!(!accepts(p, "😍"));
1520            assert!(!accepts(p, ""));
1521        }
1522
1523        #[test]
1524        fn multibyte_bracket_set_of_discrete_chars_also_fails() {
1525            assert!(build_from_regex("[😈😇]").is_err());
1526        }
1527
1528        #[test]
1529        fn ascii_bracket_range_is_unaffected() {
1530            assert!(accepts("[a-c]", "b"));
1531            assert!(!accepts("[a-c]", "d"));
1532        }
1533
1534        #[test]
1535        fn matches_index_from_regex_initial_in_allowed_language() {
1536            let p = "`\n(\\.\n)?`\n";
1537            assert!(accepts(p, "`\n`\n"));
1538            assert!(accepts(p, "`\n.\n`\n"));
1539            assert!(!accepts(p, "`\n"));
1540            assert!(!accepts(p, ""));
1541        }
1542
1543        #[test]
1544        fn matches_index_incompatible_vocabulary_error_regex() {
1545            let p = "0 1";
1546            assert!(accepts(p, "0 1"));
1547            assert!(!accepts(p, "01"));
1548            assert!(!accepts(p, "0  1"));
1549        }
1550    }
1551
1552    #[test]
1553    fn discovery_capacity_utilization_for_representative_patterns() {
1554        for (name, pattern) in [
1555            ("dense_alternation", "(cat|car|carbon|dog|doghouse|do)"),
1556            ("dense_repetition", "[a-z0-9]+"),
1557            (
1558                "sparse_literal_chain",
1559                "hello world this is a long fixed literal string",
1560            ),
1561            (
1562                "sparse_json_object",
1563                r#"\{"type":"(cat|dog)","age":[0-9]{1,3}\}"#,
1564            ),
1565        ] {
1566            let e = build_from_regex(pattern).unwrap();
1567            let classes = e.class_table.members.len();
1568            let live_states = e.state_count() - 1; // exclude DEAD
1569            let live_transitions = e.live_transition_count();
1570            let avg_out_degree = f64::from(u32::try_from(live_transitions).unwrap())
1571                / f64::from(u32::try_from(live_states.max(1)).unwrap());
1572            let utilization = avg_out_degree / f64::from(u32::try_from(classes).unwrap());
1573            eprintln!(
1574                "{name}: classes={classes} states={live_states} avg_out_degree={avg_out_degree:.1} \
1575                 utilization={utilization:.2}"
1576            );
1577        }
1578    }
1579}