1use std::sync::Arc;
11
12use crate::engine::fuzzy_bridge::FuzzyBridge;
13use crate::types::FuzzyLimits;
14
15use super::hir::HirClass;
16use crate::parser::Anchor;
17
18#[derive(Debug, Clone, Default)]
21pub struct CostConstraint {
22 pub insertion_cost: u8,
24 pub deletion_cost: u8,
26 pub substitution_cost: u8,
28 pub transposition_cost: u8,
30 pub max_cost: u8,
32}
33
34impl CostConstraint {
35 #[must_use]
37 pub fn is_satisfied(
38 &self,
39 insertions: u8,
40 deletions: u8,
41 substitutions: u8,
42 transpositions: u8,
43 ) -> bool {
44 let total_cost = (u16::from(insertions) * u16::from(self.insertion_cost))
45 + (u16::from(deletions) * u16::from(self.deletion_cost))
46 + (u16::from(substitutions) * u16::from(self.substitution_cost))
47 + (u16::from(transpositions) * u16::from(self.transposition_cost));
48 total_cost < u16::from(self.max_cost)
49 }
50}
51
52pub type StateId = usize;
54
55pub type PatternIndex = usize;
57
58#[derive(Debug, Clone)]
60pub struct Nfa {
61 pub states: Vec<State>,
63 pub start: StateId,
65 pub group_states: Vec<(StateId, StateId)>,
68 pub named_group_states: std::collections::HashMap<String, (StateId, StateId)>,
71}
72
73impl Nfa {
74 #[must_use]
76 pub fn new() -> Self {
77 Nfa {
78 states: vec![State::Accept],
79 start: 0,
80 group_states: Vec::new(),
81 named_group_states: std::collections::HashMap::new(),
82 }
83 }
84
85 #[must_use]
92 pub fn is_simple_fuzzy_only(&self) -> bool {
93 let mut visited = vec![false; self.states.len()];
94 self.check_simple_fuzzy_only(self.start, &mut visited, false)
95 }
96
97 #[must_use]
105 pub fn is_simple_alternation(&self) -> bool {
106 !self.get_alternation_pattern_indices().is_empty()
107 }
108
109 #[must_use]
114 pub fn get_alternation_pattern_indices(&self) -> Vec<PatternIndex> {
115 let mut state_id = self.start;
117 loop {
118 match &self.states[state_id] {
119 State::Epsilon { targets } if targets.len() == 1 => {
120 state_id = targets[0];
121 }
122 _ => break,
123 }
124 }
125
126 let branches = match &self.states[state_id] {
128 State::Split { branches, .. } if branches.len() >= 2 => branches,
129 _ => return Vec::new(),
130 };
131
132 let mut pattern_indices = Vec::with_capacity(branches.len());
133
134 for &branch_id in branches {
135 if let Some(idx) = self.check_simple_alternation_branch(branch_id) {
138 pattern_indices.push(idx);
139 } else {
140 return Vec::new(); }
142 }
143
144 pattern_indices
145 }
146
147 fn check_simple_alternation_branch(&self, mut state_id: StateId) -> Option<PatternIndex> {
150 loop {
152 match &self.states[state_id] {
153 State::Epsilon { targets } if targets.len() == 1 => {
154 state_id = targets[0];
155 }
156 _ => break,
157 }
158 }
159
160 let (pattern_index, next) = match &self.states[state_id] {
162 State::FuzzyLiteral {
163 pattern_index,
164 next,
165 min_edits: None,
166 cost_constraint: None,
167 ..
168 } => (*pattern_index, *next),
169 _ => return None,
170 };
171
172 let mut state_id = next;
174 loop {
175 match &self.states[state_id] {
176 State::Accept => return Some(pattern_index),
177 State::Epsilon { targets } if targets.len() == 1 => {
178 state_id = targets[0];
179 }
180 _ => return None,
181 }
182 }
183 }
184
185 fn check_simple_fuzzy_only(
188 &self,
189 state_id: StateId,
190 visited: &mut [bool],
191 seen_fuzzy: bool,
192 ) -> bool {
193 if visited[state_id] {
194 return false; }
196 visited[state_id] = true;
197
198 match &self.states[state_id] {
199 State::Accept => seen_fuzzy, State::Epsilon { targets } => {
202 if targets.len() != 1 {
204 return false; }
206 self.check_simple_fuzzy_only(targets[0], visited, seen_fuzzy)
207 }
208
209 State::FuzzyLiteral {
210 next,
211 min_edits,
212 cost_constraint,
213 ..
214 } => {
215 if seen_fuzzy || min_edits.is_some() || cost_constraint.is_some() {
217 return false;
218 }
219 self.check_simple_fuzzy_only(*next, visited, true)
220 }
221
222 State::Char { .. }
224 | State::FuzzyChar { .. }
225 | State::CaptureStart { .. }
226 | State::CaptureEnd { .. }
227 | State::Anchor { .. }
228 | State::Lookahead { .. }
229 | State::Lookbehind { .. }
230 | State::Backreference { .. }
231 | State::Split { .. }
232 | State::ResetMatchStart { .. }
233 | State::AtomicGroup { .. }
234 | State::RecursivePattern { .. }
235 | State::RecursiveGroup { .. }
236 | State::RecursiveNamedGroup { .. } => false,
237 }
238 }
239
240 pub fn add_state(&mut self, state: State) -> StateId {
242 let id = self.states.len();
243 self.states.push(state);
244 id
245 }
246
247 #[must_use]
249 pub fn state(&self, id: StateId) -> &State {
250 &self.states[id]
251 }
252
253 pub fn state_mut(&mut self, id: StateId) -> &mut State {
255 &mut self.states[id]
256 }
257
258 #[must_use]
260 pub fn is_accepting(&self, id: StateId) -> bool {
261 matches!(self.states[id], State::Accept)
262 }
263
264 #[must_use]
266 pub fn state_count(&self) -> usize {
267 self.states.len()
268 }
269
270 #[must_use]
275 pub fn has_lazy_quantifiers(&self) -> bool {
276 self.states
277 .iter()
278 .any(|state| matches!(state, State::Split { greedy: false, .. }))
279 }
280
281 #[must_use]
284 pub fn has_reset_match_start(&self) -> bool {
285 self.states
286 .iter()
287 .any(|state| matches!(state, State::ResetMatchStart { .. }))
288 }
289
290 #[must_use]
292 pub fn has_lookahead(&self) -> bool {
293 self.states
294 .iter()
295 .any(|state| matches!(state, State::Lookahead { .. }))
296 }
297
298 #[must_use]
300 pub fn has_word_boundary(&self) -> bool {
301 self.states.iter().any(|state| {
302 if let State::Anchor { kind, .. } = state {
303 matches!(kind, Anchor::WordBoundary | Anchor::NotWordBoundary)
304 } else {
305 false
306 }
307 })
308 }
309
310 #[must_use]
313 pub fn has_recursion(&self) -> bool {
314 self.states.iter().any(|state| {
315 matches!(
316 state,
317 State::RecursivePattern { .. }
318 | State::RecursiveGroup { .. }
319 | State::RecursiveNamedGroup { .. }
320 )
321 })
322 }
323
324 #[must_use]
334 pub fn is_word_bounded_literal(&self) -> bool {
335 let mut visited = vec![false; self.states.len()];
336 self.check_word_bounded_literal(self.start, &mut visited, false, false, false)
337 }
338
339 fn check_word_bounded_literal(
349 &self,
350 state_id: StateId,
351 visited: &mut [bool],
352 seen_start_boundary: bool,
353 seen_end_boundary: bool,
354 seen_literal: bool,
355 ) -> bool {
356 if visited[state_id] {
357 return false;
358 }
359 visited[state_id] = true;
360
361 match &self.states[state_id] {
362 State::Accept => {
363 seen_start_boundary && seen_end_boundary && seen_literal
365 }
366 State::Epsilon { targets } if targets.len() == 1 => self.check_word_bounded_literal(
367 targets[0],
368 visited,
369 seen_start_boundary,
370 seen_end_boundary,
371 seen_literal,
372 ),
373 State::Anchor {
374 kind: crate::parser::Anchor::WordBoundary,
375 next,
376 } => {
377 if seen_literal && !seen_end_boundary {
378 if seen_start_boundary {
381 self.check_word_bounded_literal(
382 *next,
383 visited,
384 seen_start_boundary,
385 true, seen_literal,
387 )
388 } else {
389 false
390 }
391 } else if !seen_start_boundary && !seen_literal && !seen_end_boundary {
392 self.check_word_bounded_literal(
394 *next, visited, true, false, false,
396 )
397 } else {
398 false }
400 }
401 State::FuzzyLiteral { next, .. } => {
402 if seen_start_boundary && !seen_literal && !seen_end_boundary {
403 self.check_word_bounded_literal(
405 *next,
406 visited,
407 seen_start_boundary,
408 false,
409 true, )
411 } else {
412 false
413 }
414 }
415 _ => false,
416 }
417 }
418
419 #[must_use]
429 pub fn first_char_class(&self) -> Option<HirClass> {
430 let mut visited = vec![false; self.states.len()];
431 self.first_char_class_from(self.start, &mut visited)
432 }
433
434 fn first_char_class_from(&self, state_id: StateId, visited: &mut [bool]) -> Option<HirClass> {
435 if visited[state_id] {
436 return None; }
438 visited[state_id] = true;
439
440 match &self.states[state_id] {
441 State::Epsilon { targets } => {
442 if targets.len() == 1 {
443 self.first_char_class_from(targets[0], visited)
444 } else {
445 None }
447 }
448 State::Char { class, .. } => Some(class.clone()),
449 State::FuzzyChar { class, limits, .. } => {
450 let max_deletions = limits
453 .as_ref()
454 .and_then(FuzzyLimits::get_deletions)
455 .unwrap_or(0);
456 if max_deletions == 0 {
457 Some(class.clone())
458 } else {
459 None
460 }
461 }
462 State::CaptureStart { next, .. } | State::CaptureEnd { next, .. } => {
463 self.first_char_class_from(*next, visited)
464 }
465 State::Split { branches, .. } => {
466 if branches.is_empty() {
468 return None;
469 }
470 let first = self.first_char_class_from(branches[0], visited)?;
471 for &branch in &branches[1..] {
472 let branch_class = self.first_char_class_from(branch, visited)?;
473 if branch_class.chars != first.chars
476 || branch_class.ranges != first.ranges
477 || branch_class.negated != first.negated
478 {
479 return None;
480 }
481 }
482 Some(first)
483 }
484 _ => None,
485 }
486 }
487
488 #[must_use]
490 pub fn into_sub_nfa(self) -> Box<Nfa> {
491 Box::new(self)
492 }
493
494 #[must_use]
501 pub fn ends_with_end_anchor(&self) -> bool {
502 let mut visited = vec![false; self.states.len()];
503 self.check_ends_with_end_anchor(self.start, &mut visited)
504 }
505
506 fn check_ends_with_end_anchor(&self, state_id: StateId, visited: &mut [bool]) -> bool {
508 if visited[state_id] {
509 return false; }
511 visited[state_id] = true;
512
513 #[allow(clippy::match_same_arms)]
514 match &self.states[state_id] {
515 State::Accept => false,
517
518 State::Epsilon { targets } => {
520 !targets.is_empty()
522 && targets
523 .iter()
524 .all(|&t| self.check_ends_with_end_anchor(t, visited))
525 }
526
527 State::Split { branches, .. } => {
528 !branches.is_empty()
530 && branches
531 .iter()
532 .all(|&b| self.check_ends_with_end_anchor(b, visited))
533 }
534
535 State::Anchor {
537 kind: Anchor::End,
538 next,
539 } => {
540 self.path_reaches_accept(*next, &mut vec![false; self.states.len()])
542 }
543
544 State::Anchor { next, .. } => {
545 self.check_ends_with_end_anchor(*next, visited)
547 }
548
549 State::Char { next, .. }
551 | State::FuzzyChar { next, .. }
552 | State::FuzzyLiteral { next, .. }
553 | State::CaptureStart { next, .. }
554 | State::CaptureEnd { next, .. }
555 | State::Backreference { next, .. }
556 | State::ResetMatchStart { next }
557 | State::Lookahead { next, .. }
558 | State::Lookbehind { next, .. }
559 | State::AtomicGroup { next, .. }
560 | State::RecursivePattern { next, .. }
561 | State::RecursiveGroup { next, .. }
562 | State::RecursiveNamedGroup { next, .. } => {
563 self.check_ends_with_end_anchor(*next, visited)
564 }
565 }
566 }
567
568 fn path_reaches_accept(&self, state_id: StateId, visited: &mut [bool]) -> bool {
570 if visited[state_id] {
571 return false;
572 }
573 visited[state_id] = true;
574
575 match &self.states[state_id] {
576 State::Accept => true,
577 State::Epsilon { targets } => targets
578 .iter()
579 .any(|&t| self.path_reaches_accept(t, visited)),
580 State::CaptureEnd { next, .. } => self.path_reaches_accept(*next, visited),
581 _ => false, }
583 }
584
585 #[must_use]
592 pub fn max_simple_length(&self) -> Option<usize> {
593 let mut visited = vec![false; self.states.len()];
594 self.max_simple_length_from(self.start, &mut visited)
595 }
596
597 fn max_simple_length_from(&self, state_id: StateId, visited: &mut [bool]) -> Option<usize> {
598 if visited[state_id] {
599 return None; }
601 visited[state_id] = true;
602
603 #[allow(clippy::match_same_arms)]
604 let result = match &self.states[state_id] {
605 State::Accept => Some(0),
606
607 State::Epsilon { targets } => {
608 let mut max = Some(0);
610 for &target in targets {
611 let t_max = self.max_simple_length_from(target, visited)?;
612 max = max.map(|m| m.max(t_max));
613 }
614 max
615 }
616
617 State::Char { next, .. } => self.max_simple_length_from(*next, visited).map(|m| m + 1),
618
619 State::FuzzyChar { next, limits, .. } => {
620 self.max_simple_length_from(*next, visited).map(|m| {
623 m + 1
624 + limits
625 .as_ref()
626 .and_then(FuzzyLimits::get_insertions)
627 .unwrap_or(0) as usize
628 })
629 }
630
631 State::FuzzyLiteral { .. } => None, State::CaptureStart { next, .. }
634 | State::CaptureEnd { next, .. }
635 | State::Anchor { next, .. } => self.max_simple_length_from(*next, visited),
636
637 State::Split { branches, .. } => {
638 let mut max = Some(0);
640 for &branch in branches {
641 let b_max = self.max_simple_length_from(branch, visited)?;
642 max = max.map(|m| m.max(b_max));
643 }
644 max
645 }
646
647 State::Lookahead { next, .. } | State::Lookbehind { next, .. } => {
648 self.max_simple_length_from(*next, visited)
650 }
651
652 State::Backreference { .. } => None, State::AtomicGroup { .. } => None, State::ResetMatchStart { .. } => None, State::RecursivePattern { .. } => None, State::RecursiveGroup { .. } => None, State::RecursiveNamedGroup { .. } => None, };
662
663 visited[state_id] = false; result
665 }
666
667 pub fn length_range<F>(&self, pattern_lengths: F) -> (usize, Option<usize>)
674 where
675 F: Fn(usize) -> Option<(usize, u8)>,
676 {
677 let mut visited = vec![false; self.states.len()];
678 let mut memo: Vec<Option<(usize, Option<usize>)>> = vec![None; self.states.len()];
679 self.length_range_state(self.start, &pattern_lengths, &mut visited, &mut memo)
680 }
681
682 fn length_range_state<F>(
684 &self,
685 state_id: StateId,
686 pattern_lengths: &F,
687 visited: &mut [bool],
688 memo: &mut [Option<(usize, Option<usize>)>],
689 ) -> (usize, Option<usize>)
690 where
691 F: Fn(usize) -> Option<(usize, u8)>,
692 {
693 if let Some(result) = memo[state_id] {
695 return result;
696 }
697
698 if visited[state_id] {
700 return (0, None);
701 }
702 visited[state_id] = true;
703
704 let result = match &self.states[state_id] {
705 State::Accept | State::ResetMatchStart { .. } => (0, Some(0)),
706
707 State::Epsilon { targets } => {
708 let mut min = usize::MAX;
710 let mut max: Option<usize> = Some(0);
711 for &target in targets {
712 let (t_min, t_max) =
713 self.length_range_state(target, pattern_lengths, visited, memo);
714 min = min.min(t_min);
715 max = match (max, t_max) {
716 (Some(a), Some(b)) => Some(a.max(b)),
717 _ => None,
718 };
719 }
720 if min == usize::MAX {
721 min = 0;
722 }
723 (min, max)
724 }
725
726 State::Char { next, .. } => {
727 let (next_min, next_max) =
728 self.length_range_state(*next, pattern_lengths, visited, memo);
729 (next_min + 1, next_max.map(|m| m + 1))
730 }
731
732 State::FuzzyChar { next, limits, .. } => {
733 let (next_min, next_max) =
738 self.length_range_state(*next, pattern_lengths, visited, memo);
739 let max_edits = limits
740 .as_ref()
741 .and_then(FuzzyLimits::get_edits)
742 .unwrap_or(0) as usize;
743 let char_min = usize::from(max_edits == 0);
745 (next_min + char_min, next_max.map(|m| m + 1))
746 }
747
748 State::FuzzyLiteral {
749 pattern_index,
750 next,
751 ..
752 } => {
753 let (next_min, next_max) =
754 self.length_range_state(*next, pattern_lengths, visited, memo);
755 if let Some((pat_len, max_edits)) = pattern_lengths(*pattern_index) {
756 let edits = max_edits as usize;
760 let fuzzy_min = pat_len.saturating_sub(edits);
761 let fuzzy_max = pat_len + edits;
762 (next_min + fuzzy_min, next_max.map(|m| m + fuzzy_max))
763 } else {
764 (next_min, None)
766 }
767 }
768
769 State::CaptureStart { next, .. }
770 | State::CaptureEnd { next, .. }
771 | State::Anchor { next, .. }
772 | State::Lookahead { next, .. }
773 | State::Lookbehind { next, .. }
774 | State::AtomicGroup { next, .. } => {
775 self.length_range_state(*next, pattern_lengths, visited, memo)
776 }
777
778 State::Backreference { next, .. } => {
779 let _ = self.length_range_state(*next, pattern_lengths, visited, memo);
781 (0, None)
782 }
783
784 State::Split { branches, .. } => {
785 let mut min = usize::MAX;
787 let mut max: Option<usize> = Some(0);
788 for &branch in branches {
789 let (b_min, b_max) =
790 self.length_range_state(branch, pattern_lengths, visited, memo);
791 min = min.min(b_min);
792 max = match (max, b_max) {
793 (Some(a), Some(b)) => Some(a.max(b)),
794 _ => None,
795 };
796 }
797 if min == usize::MAX {
798 min = 0;
799 }
800 (min, max)
801 }
802
803 State::RecursivePattern { .. }
804 | State::RecursiveGroup { .. }
805 | State::RecursiveNamedGroup { .. } => (0, None), };
807
808 visited[state_id] = false;
809 memo[state_id] = Some(result);
810 result
811 }
812}
813
814impl Default for Nfa {
815 fn default() -> Self {
816 Self::new()
817 }
818}
819
820#[derive(Debug, Clone)]
822pub enum State {
823 Accept,
825
826 Epsilon {
828 targets: Vec<StateId>,
830 },
831
832 Char {
834 class: HirClass,
836 next: StateId,
838 },
839
840 FuzzyChar {
843 class: HirClass,
845 limits: Option<FuzzyLimits>,
847 min_edits: Option<u8>,
849 cost_constraint: Option<CostConstraint>,
851 next: StateId,
853 },
854
855 FuzzyLiteral {
858 pattern_index: PatternIndex,
860 limits: Option<FuzzyLimits>,
862 min_edits: Option<u8>,
864 cost_constraint: Option<CostConstraint>,
866 next: StateId,
868 },
869
870 CaptureStart {
872 index: usize,
874 next: StateId,
876 },
877
878 CaptureEnd {
880 index: usize,
882 next: StateId,
884 },
885
886 Anchor {
888 kind: Anchor,
890 next: StateId,
892 },
893
894 Lookahead {
896 positive: bool,
898 nfa: Box<Nfa>,
900 literals: Vec<LiteralPattern>,
902 next: StateId,
904 },
905
906 Lookbehind {
908 positive: bool,
910 nfa: Box<Nfa>,
912 literals: Vec<LiteralPattern>,
914 bridge: Option<Arc<FuzzyBridge>>,
916 next: StateId,
918 },
919
920 Backreference {
922 group: usize,
924 limits: Option<FuzzyLimits>,
926 next: StateId,
928 },
929
930 Split {
933 branches: Vec<StateId>,
935 greedy: bool,
938 },
939
940 ResetMatchStart {
944 next: StateId,
946 },
947
948 AtomicGroup {
951 nfa: Box<Nfa>,
953 next: StateId,
955 },
956
957 RecursivePattern {
960 next: StateId,
962 },
963
964 RecursiveGroup {
966 group: usize,
968 next: StateId,
970 },
971
972 RecursiveNamedGroup {
974 name: String,
976 next: StateId,
978 },
979}
980
981impl State {
982 #[must_use]
984 pub fn epsilon(target: StateId) -> Self {
985 State::Epsilon {
986 targets: vec![target],
987 }
988 }
989
990 #[must_use]
992 pub fn epsilon_multi(targets: Vec<StateId>) -> Self {
993 State::Epsilon { targets }
994 }
995
996 #[must_use]
998 pub fn char_match(class: HirClass, next: StateId) -> Self {
999 State::Char { class, next }
1000 }
1001
1002 #[must_use]
1004 pub fn fuzzy_literal(
1005 pattern_index: PatternIndex,
1006 limits: Option<FuzzyLimits>,
1007 min_edits: Option<u8>,
1008 cost_constraint: Option<CostConstraint>,
1009 next: StateId,
1010 ) -> Self {
1011 State::FuzzyLiteral {
1012 pattern_index,
1013 limits,
1014 min_edits,
1015 cost_constraint,
1016 next,
1017 }
1018 }
1019
1020 #[must_use]
1022 pub fn capture_start(index: usize, next: StateId) -> Self {
1023 State::CaptureStart { index, next }
1024 }
1025
1026 #[must_use]
1028 pub fn capture_end(index: usize, next: StateId) -> Self {
1029 State::CaptureEnd { index, next }
1030 }
1031
1032 #[must_use]
1034 pub fn anchor(kind: Anchor, next: StateId) -> Self {
1035 State::Anchor { kind, next }
1036 }
1037
1038 #[must_use]
1040 pub fn split(branches: Vec<StateId>) -> Self {
1041 State::Split {
1042 branches,
1043 greedy: true,
1044 }
1045 }
1046
1047 #[must_use]
1049 pub fn lookahead(
1050 positive: bool,
1051 nfa: Box<Nfa>,
1052 literals: Vec<LiteralPattern>,
1053 next: StateId,
1054 ) -> Self {
1055 State::Lookahead {
1056 positive,
1057 nfa,
1058 literals,
1059 next,
1060 }
1061 }
1062
1063 pub fn lookbehind(
1065 positive: bool,
1066 nfa: Box<Nfa>,
1067 literals: Vec<LiteralPattern>,
1068 next: StateId,
1069 ) -> Self {
1070 let bridge = if literals.is_empty() {
1072 None
1073 } else {
1074 FuzzyBridge::new(&literals, None, None, false).map(Arc::new)
1075 };
1076 State::Lookbehind {
1077 positive,
1078 nfa,
1079 literals,
1080 bridge,
1081 next,
1082 }
1083 }
1084
1085 #[must_use]
1087 pub fn backreference(group: usize, limits: Option<FuzzyLimits>, next: StateId) -> Self {
1088 State::Backreference {
1089 group,
1090 limits,
1091 next,
1092 }
1093 }
1094
1095 #[must_use]
1097 pub fn next_states(&self) -> Vec<StateId> {
1098 #[allow(clippy::match_same_arms)]
1099 match self {
1100 State::Accept => vec![],
1101 State::Epsilon { targets } => targets.clone(),
1102 State::Char { next, .. }
1103 | State::FuzzyChar { next, .. }
1104 | State::FuzzyLiteral { next, .. }
1105 | State::CaptureStart { next, .. }
1106 | State::CaptureEnd { next, .. }
1107 | State::Anchor { next, .. }
1108 | State::Lookahead { next, .. }
1109 | State::Lookbehind { next, .. }
1110 | State::Backreference { next, .. }
1111 | State::AtomicGroup { next, .. }
1112 | State::RecursivePattern { next, .. }
1113 | State::RecursiveGroup { next, .. }
1114 | State::RecursiveNamedGroup { next, .. } => vec![*next],
1115 State::Split { branches, .. } => branches.clone(),
1116 State::ResetMatchStart { .. } => vec![],
1117 }
1118 }
1119}
1120
1121#[derive(Debug, Clone)]
1123pub struct NfaFragment {
1124 pub start: StateId,
1126 pub ends: Vec<StateId>,
1128}
1129
1130impl NfaFragment {
1131 #[must_use]
1133 pub fn new(start: StateId, ends: Vec<StateId>) -> Self {
1134 NfaFragment { start, ends }
1135 }
1136
1137 #[must_use]
1139 pub fn single(start: StateId, end: StateId) -> Self {
1140 NfaFragment {
1141 start,
1142 ends: vec![end],
1143 }
1144 }
1145}
1146
1147#[derive(Debug, Clone)]
1150pub struct EditCharRestriction {
1151 pub chars: Vec<char>,
1153 pub ranges: Vec<(char, char)>,
1155}
1156
1157impl EditCharRestriction {
1158 #[must_use]
1160 pub fn new(chars: Vec<char>, ranges: Vec<(char, char)>) -> Self {
1161 EditCharRestriction { chars, ranges }
1162 }
1163
1164 #[must_use]
1166 pub fn allows(&self, ch: char) -> bool {
1167 self.chars.contains(&ch)
1168 || self
1169 .ranges
1170 .iter()
1171 .any(|&(start, end)| ch >= start && ch <= end)
1172 }
1173}
1174
1175#[derive(Debug, Clone)]
1177pub struct LiteralPattern {
1178 pub text: String,
1180 pub limits: Option<FuzzyLimits>,
1182 pub min_edits: Option<u8>,
1184 pub edit_chars: Option<EditCharRestriction>,
1187}
1188
1189impl LiteralPattern {
1190 #[must_use]
1192 pub fn new(text: String, limits: Option<FuzzyLimits>, min_edits: Option<u8>) -> Self {
1193 LiteralPattern {
1194 text,
1195 limits,
1196 min_edits,
1197 edit_chars: None,
1198 }
1199 }
1200
1201 #[must_use]
1203 pub fn with_edit_chars(
1204 text: String,
1205 limits: Option<FuzzyLimits>,
1206 min_edits: Option<u8>,
1207 edit_chars: Option<EditCharRestriction>,
1208 ) -> Self {
1209 LiteralPattern {
1210 text,
1211 limits,
1212 min_edits,
1213 edit_chars,
1214 }
1215 }
1216}