1use 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(); 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; 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); let mut edges_flat = Vec::new();
841 for e in &disco_order {
842 let Some(&id) = final_remap.get(e) else {
843 continue; };
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; }
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(); 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; 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}