1#![deny(missing_docs)]
39
40#[cfg(not(any(
41 target_arch = "x86_64",
42 all(target_arch = "aarch64", target_feature = "neon"),
43 all(target_arch = "wasm32", target_feature = "simd128")
44)))]
45compile_error!(
46 "resharp requires a SIMD-capable target: x86_64, aarch64 with target_feature=neon, or wasm32 with target_feature=simd128"
47);
48
49pub(crate) mod accel;
50pub(crate) mod bdfa;
51#[cfg_attr(not(feature = "experimental_capture_groups"), allow(dead_code))]
52pub(crate) mod captures;
53pub(crate) mod ldfa;
54#[cfg_attr(not(feature = "experimental_capture_groups"), allow(dead_code))]
55pub(crate) mod pparse;
56pub(crate) mod fas;
57pub(crate) mod minterms;
58pub(crate) mod fwd;
59pub(crate) mod ismatch;
60pub(crate) mod prefix;
61pub(crate) mod scan;
62
63#[cfg(feature = "stream")]
64pub(crate) mod stream;
65#[cfg(feature = "stream")]
66pub use stream::StreamState;
67
68#[cfg(feature = "serialize")]
69pub mod dump;
70#[cfg(feature = "serialize")]
71#[allow(missing_docs)]
72pub use dump::RegexDump;
73#[cfg(feature = "serialize")]
74#[allow(missing_docs)]
75pub use bdfa::BDFA;
76#[cfg(feature = "serialize")]
77#[allow(missing_docs)]
78pub use ldfa::LDFA;
79#[cfg(feature = "serialize")]
80#[allow(missing_docs)]
81pub use prefix::{NegLb, NegLbTerm, PrefixKind};
82
83pub(crate) mod simd;
84
85#[cfg(feature = "diag")]
86pub use prefix::calc_potential_start;
87#[cfg(feature = "diag")]
88pub use prefix::calc_potential_start_prune;
89#[cfg(feature = "diag")]
90pub use prefix::calc_prefix_sets;
91#[cfg(feature = "diag")]
92pub use prefix::PrefixSets;
93#[cfg(feature = "diag")]
94pub use simd::{force_scalar_scope, ForceScalarGuard};
95pub(crate) use resharp_algebra::nulls::{Nullability, StartPositions};
96pub(crate) use resharp_algebra::solver::TSetId;
97use resharp_algebra::Kind;
98#[doc(hidden)]
99pub use resharp_algebra::NodeId;
100#[doc(hidden)]
101pub use resharp_algebra::RegexBuilder;
102
103pub use resharp_parser::escape;
110pub use resharp_parser::escape_into;
112
113use std::sync::Mutex;
114
115#[derive(Debug)]
117#[non_exhaustive]
118pub enum Error {
119 Parse(Box<resharp_parser::ParseError>),
121 Algebra(resharp_algebra::ResharpError),
123 CapacityExceeded,
125 PatternTooLarge,
127 Serialize(String),
129 InternalError(&'static str),
131}
132
133impl std::fmt::Display for Error {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 match self {
136 Error::Parse(e) => write!(f, "parse error: {}", e),
137 Error::Algebra(e) => write!(f, "{}", e),
138 Error::CapacityExceeded => write!(f, "DFA state capacity exceeded"),
139 Error::PatternTooLarge => write!(f, "pattern too large"),
140 Error::Serialize(ref s) => write!(f, "serialization error: {}", s),
141 Error::InternalError(msg) => write!(f, "internal error: {}", msg),
142 }
143 }
144}
145
146impl std::error::Error for Error {
147 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
148 match self {
149 Error::Parse(e) => Some(e),
150 Error::Algebra(e) => Some(e),
151 Error::CapacityExceeded => None,
152 Error::PatternTooLarge => None,
153 Error::Serialize(_) => None,
154 Error::InternalError(_) => None,
155 }
156 }
157}
158
159impl From<resharp_parser::ParseError> for Error {
160 fn from(e: resharp_parser::ParseError) -> Self {
161 Error::Parse(Box::new(e))
162 }
163}
164
165impl From<resharp_algebra::ResharpError> for Error {
166 fn from(e: resharp_algebra::ResharpError) -> Self {
167 Error::Algebra(e)
168 }
169}
170
171#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
173pub enum UnicodeMode {
174 Ascii,
176 #[default]
179 Default,
180 Full,
183 Javascript,
186}
187
188pub struct RegexOptions {
202 pub max_dfa_capacity: usize,
204 pub lookahead_context_max: u32,
206 pub unicode: UnicodeMode,
209 pub case_insensitive: bool,
211 pub dot_matches_new_line: bool,
213 pub multiline: bool,
216 pub ignore_whitespace: bool,
218 #[cfg(feature = "experimental_capture_groups")]
222 pub implicit_captures: bool,
223 pub hardened: bool,
226 pub unbounded_size: bool,
228 #[doc(hidden)]
229 pub force_convergence: bool,
230 #[doc(hidden)]
233 pub disable_prefixes: bool,
234}
235
236impl Default for RegexOptions {
237 fn default() -> Self {
238 Self {
239 max_dfa_capacity: u16::MAX as usize,
240 lookahead_context_max: 800,
241 unicode: UnicodeMode::Default,
242 case_insensitive: false,
243 dot_matches_new_line: false,
244 multiline: true,
245 ignore_whitespace: false,
246 #[cfg(feature = "experimental_capture_groups")]
247 implicit_captures: false,
248 hardened: false,
249 unbounded_size: false,
250 force_convergence: false,
251 disable_prefixes: false,
252 }
253 }
254}
255
256impl RegexOptions {
257 pub fn unicode(mut self, mode: UnicodeMode) -> Self {
259 self.unicode = mode;
260 self
261 }
262 pub fn case_insensitive(mut self, yes: bool) -> Self {
264 self.case_insensitive = yes;
265 self
266 }
267 pub fn dot_matches_new_line(mut self, yes: bool) -> Self {
269 self.dot_matches_new_line = yes;
270 self
271 }
272 pub fn multiline(mut self, yes: bool) -> Self {
274 self.multiline = yes;
275 self
276 }
277 pub fn ignore_whitespace(mut self, yes: bool) -> Self {
279 self.ignore_whitespace = yes;
280 self
281 }
282 #[cfg(feature = "experimental_capture_groups")]
284 pub fn implicit_captures(mut self, yes: bool) -> Self {
285 self.implicit_captures = yes;
286 self
287 }
288 pub fn hardened(mut self, yes: bool) -> Self {
290 self.hardened = yes;
291 self
292 }
293 #[doc(hidden)]
294 pub fn force_convergence(mut self, yes: bool) -> Self {
295 self.force_convergence = yes;
296 self
297 }
298 pub fn unbounded_size(mut self, yes: bool) -> Self {
302 self.unbounded_size = yes;
303 self
304 }
305}
306
307#[derive(Debug, Clone, Copy, PartialEq, Eq)]
309#[repr(C)]
310pub struct Match {
311 pub start: usize,
313 pub end: usize,
315}
316
317#[cfg(feature = "experimental_capture_groups")]
321#[derive(Debug, Clone, PartialEq, Eq)]
322pub struct Captures<'r> {
323 names: &'r [Option<String>],
324 spans: Vec<Option<(usize, usize)>>,
325}
326
327#[cfg(feature = "experimental_capture_groups")]
328impl Captures<'_> {
329 pub fn get(&self, i: usize) -> Option<Match> {
331 self.spans
332 .get(i)
333 .copied()
334 .flatten()
335 .map(|(start, end)| Match { start, end })
336 }
337
338 pub fn name(&self, name: &str) -> Option<Match> {
340 let i = self.names.iter().position(|n| n.as_deref() == Some(name))?;
341 self.get(i)
342 }
343
344 pub fn spans(&self) -> &[Option<(usize, usize)>] {
346 &self.spans
347 }
348}
349
350pub(crate) struct RegexInner {
351 pub(crate) b: RegexBuilder,
352 pub(crate) fwd: ldfa::LDFA,
353 pub(crate) fwd_ts: ldfa::LDFA,
354 #[cfg_attr(not(feature = "stream"), allow(dead_code))]
355 pub(crate) rev: Option<ldfa::LDFA>,
356 pub(crate) rev_ts: ldfa::LDFA,
357 #[cfg(feature = "convergence_prefix")]
358 pub(crate) conv_b: Option<ldfa::LDFA>,
359 #[cfg(feature = "stream")]
360 pub(crate) stream: stream::StreamInit,
361 pub(crate) nulls: StartPositions,
362 pub(crate) matches: Vec<Match>,
363 pub(crate) bounded: Option<bdfa::BDFA>,
364 pub(crate) fas: Option<fas::FwdDFA>,
365 pub(crate) lb_verify: Option<ldfa::LDFA>,
366 #[cfg_attr(not(feature = "experimental_capture_groups"), allow(dead_code))]
367 pub(crate) capture_root: NodeId,
368 #[cfg_attr(not(feature = "experimental_capture_groups"), allow(dead_code))]
369 pub(crate) capture_dfa: pparse::PosixParser,
370 #[cfg_attr(not(feature = "experimental_capture_groups"), allow(dead_code))]
371 pub(crate) skeleton: Option<resharp_parser::Skeleton>,
372}
373
374#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
375#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
376pub(crate) struct InitialNodeFlags(u8);
377
378impl InitialNodeFlags {
379 const HAS_ANCHORS: u8 = 1 << 0;
380 const HAS_LB: u8 = 1 << 1;
381 const HAS_LA: u8 = 1 << 2;
382
383 pub(crate) fn new(has_anchors: bool, has_lb: bool, has_la: bool) -> Self {
384 let mut bits = 0;
385 if has_anchors {
386 bits |= Self::HAS_ANCHORS;
387 }
388 if has_lb {
389 bits |= Self::HAS_LB;
390 }
391 if has_la {
392 bits |= Self::HAS_LA;
393 }
394 InitialNodeFlags(bits)
395 }
396
397 #[inline]
398 pub(crate) fn has_anchors(self) -> bool {
399 self.0 & Self::HAS_ANCHORS != 0
400 }
401
402 #[inline]
403 #[allow(dead_code)]
404 pub(crate) fn has_lb(self) -> bool {
405 self.0 & Self::HAS_LB != 0
406 }
407 #[inline]
408 pub(crate) fn has_la(self) -> bool {
409 self.0 & Self::HAS_LA != 0
410 }
411}
412
413pub struct Regex {
416 pub(crate) inner: Mutex<RegexInner>,
417 pub(crate) prefix: Option<prefix::PrefixKind>,
418 pub(crate) fixed_length: Option<u32>,
419 pub(crate) empty_nullable: bool,
420 pub(crate) always_nullable: bool,
421 pub(crate) star_loop: bool,
422 pub(crate) is_empty_lang: bool,
425 #[allow(dead_code)]
426 pub(crate) fwd_begin_anchored: bool,
427 pub(crate) fwd_lb_stripped: bool,
428 #[allow(dead_code)]
429 pub(crate) rev_end_anchored: bool,
430 pub(crate) initial_nullability: Nullability,
432 #[allow(dead_code)]
433 pub(crate) fwd_end_nullable: bool,
434 pub(crate) rev_end_nullable: bool,
435 pub(crate) hardened: bool,
436 #[allow(dead_code)]
437 pub(crate) has_bounded: bool,
438 pub(crate) bounded_safe_find_all: bool,
439 pub(crate) lb_check_bytes: u8,
440 pub(crate) fwd_lb_begin_nullable: bool,
441 pub(crate) fwd_lb_begin_len: u8,
442 pub(crate) fwd_lb_begin_classes: Vec<crate::accel::TSet>,
443 pub(crate) fwd_lb_body_nullable: bool,
444 pub(crate) init_flags: InitialNodeFlags,
445 #[cfg(feature = "convergence_prefix")]
446 pub(crate) conv_prefix: bool,
447 pub(crate) neg_lb: Option<prefix::NegLb>,
450 #[cfg_attr(not(feature = "experimental_capture_groups"), allow(dead_code))]
451 pub(crate) group_names: Vec<Option<String>>,
452 #[cfg_attr(not(any(feature = "experimental_capture_groups", feature = "diag")), allow(dead_code))]
453 pub(crate) captures_dispatch: captures::CaptureDispatch,
454 pub(crate) find_all: FindAll,
455 pub(crate) class_plus: Option<[u64; 4]>,
456 #[cfg(feature = "stream")]
457 pub(crate) stream_cache: stream::StreamCache,
458}
459
460#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
462#[derive(Clone, Copy, PartialEq, Eq, Debug)]
463pub enum FindAll {
464 EmptyLang,
466 Anchored,
468 EndAnchored,
470 Hardened,
472 FwdPrefix,
474 FwdLbPrefix,
476 Bounded,
478 Dfa,
480 ClassPlus,
482}
483
484#[derive(Clone, Copy, Default)]
486struct Hardening {
487 full: bool,
488 no_fwd_prefix: bool,
489}
490
491fn auto_harden(b: &mut RegexBuilder, start: NodeId, has_anchors: bool) -> Hardening {
493 const NODE_BUDGET: usize = 128;
494 const LARGE_COVER: u32 = 128;
495 let opener = opener_class(b, start);
496 if opener == TSetId::EMPTY {
497 return Hardening::default();
498 }
499 let opener_full = b.solver().is_full_id(opener);
500 let Some(graph) = build_partial_graph(b, start, NODE_BUDGET) else {
501 return Hardening::default();
502 };
503 if graph
504 .nodes
505 .iter()
506 .any(|&n| n.is_compl(b))
507 {
508 return Hardening::default();
509 }
510 let mut pure_star: Vec<bool> = vec![false; graph.nodes.len()];
511 for (i, &n) in graph.nodes.iter().enumerate() {
512 if i == 0 {
513 continue;
514 }
515 if n.nullability(b) != resharp_algebra::nulls::Nullability::ALWAYS {
516 continue;
517 }
518 if graph.edges[i].len() == 1 {
519 let e = &graph.edges[i][0];
520 if e.dst == i && b.solver().is_full_id(e.set) {
521 pure_star[i] = true;
522 }
523 }
524 }
525 if !has_anchors
526 && graph.edges[0].len() == 1
527 && graph.edges[0][0].dst == 0
528 && b.solver().is_full_id(graph.edges[0][0].set)
529 {
530 return Hardening::default();
531 }
532
533 let reach = transitive_closure(&graph);
534 let sccs = sccs_from_reach(&reach);
535 let mut node_scc: Vec<usize> = vec![0; graph.nodes.len()];
536 for (sid, scc) in sccs.iter().enumerate() {
537 for &n in scc {
538 node_scc[n] = sid;
539 }
540 }
541 let start_in_cycle = sccs[node_scc[0]].len() > 1 || graph.edges[0].iter().any(|e| e.dst == 0);
542 let total_wide_self_loops = graph
543 .nodes
544 .iter()
545 .enumerate()
546 .filter(|(i, _)| !pure_star[*i])
547 .filter(|(i, _)| {
548 let self_cov = graph.edges[*i]
549 .iter()
550 .filter(|e| e.dst == *i)
551 .fold(TSetId::EMPTY, |acc, e| b.solver().or_id(acc, e.set));
552 b.solver().byte_count(self_cov) >= 2
553 })
554 .count();
555 let (min_len, _) = b.get_min_max_length(start);
556 const SHORT_PREFIX: u32 = 3;
557 const ENTRY_BYTES: u32 = 2;
558 for (i, &n) in graph.nodes.iter().enumerate() {
559 if n.nullability(b) == resharp_algebra::nulls::Nullability::NEVER {
560 continue;
561 }
562 let scc = &sccs[node_scc[i]];
563 let scc_non_trivial = scc.len() > 1 || graph.edges[i].iter().any(|e| e.dst == i);
564 if !scc_non_trivial {
565 continue;
566 }
567 let scc_set: std::collections::HashSet<usize> = scc.iter().copied().collect();
568 let in_scc_cov = graph.edges[i]
569 .iter()
570 .filter(|e| scc_set.contains(&e.dst))
571 .fold(TSetId::EMPTY, |acc, e| b.solver().or_id(acc, e.set));
572 if b.solver().byte_count(in_scc_cov) < LARGE_COVER {
573 continue;
574 }
575 if i == 0 {
576 return Hardening {
577 full: true,
578 no_fwd_prefix: true,
579 };
580 }
581 let start_to_i = graph.edges[0]
582 .iter()
583 .filter(|e| e.dst == i)
584 .fold(TSetId::EMPTY, |acc, e| b.solver().or_id(acc, e.set));
585 let entry_wide = b.solver().byte_count(start_to_i) >= ENTRY_BYTES;
586 let start_is_union = start.is_union(b);
587 if !has_anchors && min_len <= SHORT_PREFIX && entry_wide && start_is_union {
588 return Hardening {
589 full: true,
590 no_fwd_prefix: true,
591 };
592 }
593 }
594 let mut no_fwd_prefix = false;
595 let opener_wide = opener_full || b.solver().byte_count(opener) >= LARGE_COVER;
596 for scc in sccs {
597 let non_trivial = scc.len() > 1 || graph.edges[scc[0]].iter().any(|e| e.dst == scc[0]);
598 if !non_trivial {
599 continue;
600 }
601 if scc.iter().all(|&n| pure_star[n]) {
602 continue;
603 }
604 let scc_set: std::collections::HashSet<usize> = scc.iter().copied().collect();
605 if scc_set.contains(&0) {
606 continue; }
608 let sticky = scc.iter().all(|&n| {
609 let cover = graph.edges[n]
610 .iter()
611 .fold(TSetId::EMPTY, |acc, e| b.solver().or_id(acc, e.set));
612 b.solver().is_full_id(cover)
613 });
614 const SPIN_FREQ_THRESHOLD: u64 = crate::prefix::TOTAL_BYTE_FREQ / 2;
615 let scc_set_local: std::collections::HashSet<usize> = scc.iter().copied().collect();
616 let has_wide_spin = scc.iter().any(|&n| {
617 let in_scc_cover = graph.edges[n]
618 .iter()
619 .filter(|e| scc_set_local.contains(&e.dst))
620 .fold(TSetId::EMPTY, |acc, e| b.solver().or_id(acc, e.set));
621 let freq: u64 = b
622 .solver()
623 .collect_bytes(in_scc_cover)
624 .iter()
625 .map(|&byte| crate::simd::BYTE_FREQ[byte as usize] as u64)
626 .sum();
627 freq >= SPIN_FREQ_THRESHOLD
628 });
629 if !has_wide_spin {
630 continue;
631 }
632 let restartable = scc.iter().any(|&n| {
633 graph.edges[n]
634 .iter()
635 .any(|e| scc_set.contains(&e.dst) && b.solver().is_sat_id(e.set, opener))
636 });
637 if !restartable {
638 continue;
639 }
640 if !has_anchors {
641 no_fwd_prefix = true;
642 }
643 let start_branches = graph.edges[0].len() >= 2;
644 let scc_branches = scc.iter().any(|&n| graph.edges[n].len() >= 3);
645 if !start_branches && total_wide_self_loops <= 1 {
646 continue;
647 }
648 let start_escapes_scc = if has_anchors {
649 let start_into_scc = graph.edges[0]
650 .iter()
651 .filter(|e| scc_set.contains(&e.dst))
652 .count();
653 graph.edges[0].len() > start_into_scc
654 } else {
655 let cover = graph.edges[0]
656 .iter()
657 .filter(|e| scc_set.contains(&e.dst) || scc.iter().any(|&s| reach[e.dst][s]))
658 .fold(TSetId::EMPTY, |acc, e| b.solver().or_id(acc, e.set));
659 !b.solver().is_full_id(cover)
660 };
661 if start_escapes_scc && !start_in_cycle {
662 continue;
663 }
664 if min_len <= SHORT_PREFIX && sticky && opener_wide && (start_branches || scc_branches) {
665 return Hardening {
666 full: true,
667 no_fwd_prefix: true,
668 };
669 }
670 }
671 if no_fwd_prefix {
672 return Hardening {
673 full: false,
674 no_fwd_prefix: true,
675 };
676 }
677 Hardening::default()
678}
679
680struct Edge {
681 dst: usize,
682 set: TSetId,
683}
684
685struct Graph {
686 edges: Vec<Vec<Edge>>,
687 nodes: Vec<NodeId>,
688}
689
690const BUILD_PARTIAL_GRAPH_CREATION_BUDGET: u32 = 100_000;
691
692fn build_partial_graph(b: &mut RegexBuilder, start: NodeId, budget: usize) -> Option<Graph> {
693 use std::collections::HashMap;
694 let mut idx: HashMap<NodeId, usize> = HashMap::from([(start, 0)]);
695 let mut edges: Vec<Vec<Edge>> = vec![Vec::new()];
696 let mut nodes: Vec<NodeId> = vec![start];
697 let mut queue: Vec<(usize, NodeId)> = vec![(0, start)];
698 let mut overflow = false;
699 let node_budget_start = b.num_nodes();
700 while let Some((u, node)) = queue.pop() {
701 if b.num_nodes().wrapping_sub(node_budget_start) > BUILD_PARTIAL_GRAPH_CREATION_BUDGET {
702 return None;
703 }
704 let sder = b.der(node, Nullability::CENTER).ok()?;
705 let mut stack = vec![(sder, TSetId::FULL)];
706 b.iter_sat(&mut stack, &mut |_, next, set| {
707 let dst = *idx.entry(next).or_insert_with(|| {
708 if edges.len() >= budget {
709 overflow = true;
710 return usize::MAX;
711 }
712 let i = edges.len();
713 edges.push(Vec::new());
714 nodes.push(next);
715 queue.push((i, next));
716 i
717 });
718 if dst != usize::MAX {
719 edges[u].push(Edge { dst, set });
720 }
721 });
722 if overflow {
723 return None;
724 }
725 }
726 Some(Graph { edges, nodes })
727}
728
729fn transitive_closure(graph: &Graph) -> Vec<Vec<bool>> {
730 let n = graph.edges.len();
731 let mut r = vec![vec![false; n]; n];
732 for i in 0..n {
733 for e in &graph.edges[i] {
734 r[i][e.dst] = true;
735 }
736 }
737 for k in 0..n {
738 for i in 0..n {
739 if !r[i][k] {
740 continue;
741 }
742 for j in 0..n {
743 if r[k][j] {
744 r[i][j] = true;
745 }
746 }
747 }
748 }
749 r
750}
751
752fn sccs_from_reach(reach: &[Vec<bool>]) -> Vec<Vec<usize>> {
754 let n = reach.len();
755 let mut visited = vec![false; n];
756 let mut sccs: Vec<Vec<usize>> = Vec::new();
757 for i in 0..n {
758 if visited[i] {
759 continue;
760 }
761 visited[i] = true;
762 let mut scc = vec![i];
763 for j in (i + 1)..n {
764 if !visited[j] && reach[i][j] && reach[j][i] {
765 visited[j] = true;
766 scc.push(j);
767 }
768 }
769 sccs.push(scc);
770 }
771 sccs
772}
773
774fn opener_class(b: &mut RegexBuilder, start: NodeId) -> TSetId {
775 let sder = match b.der(start, Nullability::CENTER) {
776 Ok(d) => d,
777 Err(_) => return TSetId::EMPTY,
778 };
779 let mut stack = vec![(sder, TSetId::FULL)];
780 let mut acc = TSetId::EMPTY;
781 b.iter_sat(
782 &mut stack,
783 &mut (|bb, next, set| {
784 if next.0 > NodeId::BOT.0 {
785 acc = bb.solver().or_id(acc, set);
786 }
787 }),
788 );
789 acc
790}
791
792fn collect_union_branches(b: &RegexBuilder, node: NodeId, out: &mut Vec<NodeId>) {
793 if node.is_union(b) {
794 collect_union_branches(b, node.left(b), out);
795 collect_union_branches(b, node.right(b), out);
796 } else {
797 out.push(node);
798 }
799}
800
801fn first_lb_in_branch(b: &RegexBuilder, node: NodeId) -> Option<NodeId> {
802 if node.is_lookbehind(b) {
803 return Some(node);
804 }
805 if node.is_concat(b) {
806 return first_lb_in_branch(b, node.left(b));
807 }
808 None
809}
810
811fn neg_lookbehind_body(b: &RegexBuilder, inner: NodeId) -> Option<NodeId> {
813 if !inner.is_concat(b) || inner.left(b) != NodeId::BEGIN {
814 return None;
815 }
816 let compl = inner.right(b);
817 if !compl.is_compl(b) {
818 return None;
819 }
820 let body_ts = compl.left(b);
821 if !body_ts.is_concat(b) || body_ts.left(b) != NodeId::TS {
822 return None;
823 }
824 Some(body_ts.right(b))
825}
826
827fn lb_is_unbounded(b: &RegexBuilder, lb_node: NodeId) -> bool {
828 let inner = b.get_lookbehind_inner(lb_node);
829 if let Some(body) = neg_lookbehind_body(b, inner) {
830 return b.get_min_max_length(body).1 == u32::MAX;
831 }
832 let rest = if inner.is_concat(b) && inner.left(b).is_star(b) {
833 inner.right(b)
834 } else {
835 inner
836 };
837 b.get_min_max_length(rest).1 == u32::MAX
838}
839
840fn any_unbounded_lookback(b: &RegexBuilder, node: NodeId) -> bool {
841 if !node.contains_lookbehind(b) {
842 return false;
843 }
844 if node.is_lookbehind(b) && lb_is_unbounded(b, node) {
845 return true;
846 }
847 [node.left(b), node.right(b)]
848 .into_iter()
849 .any(|c| c != NodeId::MISSING && any_unbounded_lookback(b, c))
850}
851
852fn union_branches_distinguishable(b: &mut RegexBuilder, union_node: NodeId) -> bool {
855 let mut branches = Vec::new();
856 collect_union_branches(b, union_node, &mut branches);
857 union_branches_distinguishable_list(b, &branches)
858}
859
860fn union_branches_distinguishable_list(b: &mut RegexBuilder, branches: &[NodeId]) -> bool {
861 let branches: Vec<NodeId> = branches.iter().copied().filter(|&n| n != NodeId::BOT).collect();
863 if branches.len() <= 1 {
864 return true;
865 }
866 let branches = branches.as_slice();
867 let any_lb = branches.iter().any(|n| n.contains_lookbehind(b));
868 if !any_lb {
869 return true;
870 }
871 let union_node_len = branches
872 .iter()
873 .fold((0u32, 0u32), |(mn, mx), &n| {
874 let (bn, bx) = b.get_min_max_length(n);
875 (mn.min(bn), mx.max(bx))
876 });
877 if union_node_len.1 > 0 && branches.iter().any(|&br| any_unbounded_lookback(b, br)) {
879 return false;
880 }
881 let any_anchors = branches.iter().any(|&br| b.contains_anchors(br));
882 if any_anchors
883 && branches
884 .iter()
885 .any(|&br| br.contains_lookbehind(b) && first_lb_in_branch(b, br).is_none())
886 {
887 return false;
888 }
889 let fixed_lens: Option<Vec<u32>> = branches.iter().map(|&br| b.get_fixed_length(br)).collect();
891 if let Some(lens) = fixed_lens {
892 if lens.iter().all(|&l| l == lens[0]) {
893 return true;
894 }
895 }
896 let mut firsts: Vec<(bool, TSetId, Option<NodeId>, Option<u32>)> =
897 Vec::with_capacity(branches.len());
898 for &br in branches {
899 let has_lb = br.contains_lookbehind(b);
900 let lb_node = if has_lb {
901 first_lb_in_branch(b, br)
902 } else {
903 None
904 };
905 let stripped = match b.strip_lb(br) {
906 Ok(s) => s,
907 Err(_) => return false,
908 };
909 let sets = match prefix::calc_potential_start_prune(b, stripped, 1, 64, false) {
910 Ok(s) => s,
911 Err(_) => return false,
912 };
913 let (bmin, bmax) = b.get_min_max_length(br);
914 let first = match sets.first() {
915 Some(&s) => s,
916 None => {
917 if bmin == 0 {
918 return false;
919 }
920 continue;
921 }
922 };
923 let fixed_len = if bmin == bmax { Some(bmin) } else { None };
924 firsts.push((has_lb, first, lb_node, fixed_len));
925 }
926 for i in 0..firsts.len() {
927 if !firsts[i].0 {
928 continue;
929 }
930 for j in 0..firsts.len() {
931 if i == j {
932 continue;
933 }
934 let inter = b.solver().and_id(firsts[i].1, firsts[j].1);
935 if inter == TSetId::EMPTY {
936 continue;
937 }
938 let lb_same = match (firsts[i].2, firsts[j].2) {
939 (Some(ni), Some(nj)) => {
940 b.get_lookbehind_inner(ni) == b.get_lookbehind_inner(nj)
941 }
942 (None, None) => true,
943 _ => false,
944 };
945 if lb_same {
946 continue;
947 }
948 let same_fixed = matches!(
949 (firsts[i].3, firsts[j].3),
950 (Some(a), Some(c)) if a == c
951 );
952 if !same_fixed {
953 return false;
954 }
955 }
956 }
957 true
958}
959
960#[derive(Clone, Copy, Debug, PartialEq, Eq)]
961enum Compatibility {
962 LookaroundUnion,
963}
964
965fn combine_compatibility(
966 left: Option<Compatibility>,
967 right: Option<Compatibility>,
968) -> Option<Compatibility> {
969 left.or(right)
970}
971
972fn ensure_supported_rec(
973 b: &mut RegexBuilder,
974 node: NodeId,
975 at_start: bool,
976 strict_lb_start: bool,
977 memo: &mut std::collections::HashSet<(NodeId, bool, bool)>,
978) -> Result<Option<Compatibility>, resharp_algebra::ResharpError> {
979 if !node.contains_lookaround(b) {
980 return Ok(None);
981 }
982 if !memo.insert((node, at_start, strict_lb_start)) {
983 return Ok(None);
984 }
985 match b.get_kind(node) {
986 Kind::Union => {
987 let (l, r) = (node.left(b), node.right(b));
988 let has_lb = l.contains_lookbehind(b) || r.contains_lookbehind(b);
989 if has_lb && !union_branches_distinguishable(b, node) {
990 return Err(resharp_algebra::ResharpError::UnsupportedPattern);
991 }
992 let left = ensure_supported_rec(b, l, at_start, strict_lb_start, memo)?;
993 let right = ensure_supported_rec(b, r, at_start, strict_lb_start, memo)?;
994 let union = if has_lb {
995 Some(Compatibility::LookaroundUnion)
996 } else {
997 None
998 };
999 Ok(combine_compatibility(
1000 union,
1001 combine_compatibility(left, right),
1002 ))
1003 }
1004 Kind::Inter => {
1005 let (l, r) = (node.left(b), node.right(b));
1006 for (u, other) in [(l, r), (r, l)] {
1009 if u.is_union(b) && u.contains_lookbehind(b) {
1010 if strict_lb_start && !at_start {
1011 return Err(resharp_algebra::ResharpError::UnsupportedPattern);
1012 }
1013 let mut branches = Vec::new();
1014 collect_union_branches(b, u, &mut branches);
1015 let distributed_branches: Vec<NodeId> =
1016 branches.iter().map(|&br| b.mk_inter(br, other)).collect();
1017 if !union_branches_distinguishable_list(b, &distributed_branches) {
1018 return Err(resharp_algebra::ResharpError::UnsupportedPattern);
1019 }
1020 let other_compatibility =
1021 ensure_supported_rec(b, other, at_start, strict_lb_start, memo)?;
1022 return Ok(combine_compatibility(
1023 Some(Compatibility::LookaroundUnion),
1024 other_compatibility,
1025 ));
1026 }
1027 }
1028 let left = ensure_supported_rec(b, l, at_start, strict_lb_start, memo)?;
1029 let right = ensure_supported_rec(b, r, at_start, strict_lb_start, memo)?;
1030 Ok(combine_compatibility(left, right))
1031 }
1032 Kind::Concat => {
1033 let left = node.left(b);
1034 let right = node.right(b);
1035 let (_, left_max) = b.get_min_max_length(left);
1036 if left_max > 0 && right.is_union(b) && right.contains_lookbehind(b) {
1037 return Err(resharp_algebra::ResharpError::UnsupportedPattern);
1038 }
1039 if left.is_union(b) && left.contains_lookbehind(b) {
1040 if strict_lb_start && !at_start {
1041 return Err(resharp_algebra::ResharpError::UnsupportedPattern);
1042 }
1043 let mut branches = Vec::new();
1044 collect_union_branches(b, left, &mut branches);
1045 let distributed_branches: Vec<NodeId> =
1046 branches.iter().map(|&br| b.mk_concat(br, right)).collect();
1047 if union_branches_distinguishable_list(b, &distributed_branches) {
1048 let right_compatibility =
1049 ensure_supported_rec(b, right, at_start, strict_lb_start, memo)?;
1050 return Ok(combine_compatibility(
1051 Some(Compatibility::LookaroundUnion),
1052 right_compatibility,
1053 ));
1054 } else {
1055 return Err(resharp_algebra::ResharpError::UnsupportedPattern);
1056 }
1057 }
1058 let left_compatibility = ensure_supported_rec(b, left, at_start, strict_lb_start, memo)?;
1059 let (_, left_max) = b.get_min_max_length(left);
1060 let right_compatibility =
1061 ensure_supported_rec(b, right, at_start && left_max == 0, strict_lb_start, memo)?;
1062 Ok(combine_compatibility(
1063 left_compatibility,
1064 right_compatibility,
1065 ))
1066 }
1067 Kind::Star => {
1068 if node.left(b).contains_lookaround(b) {
1069 return Err(resharp_algebra::ResharpError::UnsupportedPattern);
1070 }
1071 ensure_supported_rec(b, node.left(b), at_start, strict_lb_start, memo)
1072 }
1073 Kind::Ordered => ensure_supported_rec(b, node.left(b), at_start, strict_lb_start, memo),
1074 Kind::Compl => ensure_supported_rec(b, node.left(b), at_start, strict_lb_start, memo),
1075 Kind::Lookbehind => {
1076 let prev = node.right(b);
1077 let (_, prev_max) = if prev == NodeId::MISSING {
1078 (0, 0)
1079 } else {
1080 b.get_min_max_length(prev)
1081 };
1082 if !at_start || prev_max > 0 {
1083 return Err(resharp_algebra::ResharpError::UnsupportedPattern);
1084 }
1085 let left = ensure_supported_rec(b, node.left(b), at_start, strict_lb_start, memo)?;
1086 let right = ensure_supported_rec(b, prev, at_start, strict_lb_start, memo)?;
1087 Ok(combine_compatibility(left, right))
1088 }
1089 Kind::Lookahead => {
1090 let left = ensure_supported_rec(b, node.left(b), at_start, strict_lb_start, memo)?;
1091 let right = ensure_supported_rec(b, node.right(b), at_start, strict_lb_start, memo)?;
1092 Ok(combine_compatibility(left, right))
1093 }
1094 Kind::Pred => Ok(None),
1095 Kind::Begin => Ok(None),
1096 Kind::End => Ok(None),
1097 Kind::Tag => Ok(None),
1098 }
1099}
1100
1101fn peel_bare_begin_chain(b: &RegexBuilder, node: NodeId) -> NodeId {
1102 let mut cur = node;
1103 while cur.is_concat(b) && b.is_begin_only_shape(cur.left(b)) {
1104 cur = cur.right(b);
1105 }
1106 cur
1107}
1108
1109fn peel_nullable_lookbehind_prefix_chain(b: &mut RegexBuilder, node: NodeId) -> NodeId {
1110 if node.is_concat(b) {
1111 let left = node.left(b);
1112 if b.contains_lookbehind(left) && b.nullability(left).has(Nullability::BEGIN) {
1113 if left.is_lookbehind(b) {
1114 let prev = b.get_lookbehind_prev(left);
1115 let cont = if prev == NodeId::MISSING { NodeId::EPS } else { prev };
1116 let peeled_prev = peel_nullable_lookbehind_prefix_chain(b, cont);
1117 let right = peel_nullable_lookbehind_prefix_chain(b, node.right(b));
1118 return b.mk_concat(peeled_prev, right);
1119 }
1120 return peel_nullable_lookbehind_prefix_chain(b, node.right(b));
1121 }
1122 return node;
1123 }
1124 if node.is_lookbehind(b) && b.nullability(node).has(Nullability::BEGIN) {
1125 let prev = b.get_lookbehind_prev(node);
1126 let cont = if prev == NodeId::MISSING { NodeId::EPS } else { prev };
1127 return peel_nullable_lookbehind_prefix_chain(b, cont);
1128 }
1129 node
1130}
1131
1132fn neg_lookbehind_marker_prev_end_nullable(b: &mut RegexBuilder, node: NodeId) -> bool {
1133 if !node.is_lookbehind(b) {
1134 return false;
1135 }
1136 let prev = b.get_lookbehind_prev(node);
1137 if prev == NodeId::MISSING {
1138 return false;
1139 }
1140 let inner = b.get_lookbehind_inner(node);
1141 let is_neg_marker =
1142 b.get_kind(inner) == Kind::Begin || (inner.is_concat(b) && inner.left(b) == NodeId::BEGIN);
1143 if !is_neg_marker {
1144 return false;
1145 }
1146 b.nullability(prev).has(Nullability::END)
1147}
1148
1149fn body_after_begin_of(b: &mut RegexBuilder, node: NodeId) -> NodeId {
1150 let after_bare_begin = peel_bare_begin_chain(b, node);
1151 peel_nullable_lookbehind_prefix_chain(b, after_bare_begin)
1152}
1153
1154fn ensure_begin_leading(
1155 b: &RegexBuilder,
1156 node: NodeId,
1157 at_start: bool,
1158 memo: &mut std::collections::HashSet<(NodeId, bool)>,
1159) -> Result<(), resharp_algebra::ResharpError> {
1160 if !b.contains_anchors(node) {
1161 return Ok(());
1162 }
1163 if !memo.insert((node, at_start)) {
1164 return Ok(());
1165 }
1166 match b.get_kind(node) {
1167 Kind::Begin => {
1168 if at_start {
1169 Ok(())
1170 } else {
1171 Err(resharp_algebra::ResharpError::UnsupportedPattern)
1172 }
1173 }
1174 Kind::End | Kind::Pred | Kind::Tag => Ok(()),
1175 Kind::Concat => {
1176 let l = node.left(b);
1177 ensure_begin_leading(b, l, at_start, memo)?;
1178 let (lmin, _) = b.get_min_max_length(l);
1179 ensure_begin_leading(b, node.right(b), at_start && lmin == 0, memo)
1180 }
1181 Kind::Union | Kind::Inter => {
1182 ensure_begin_leading(b, node.left(b), at_start, memo)?;
1183 ensure_begin_leading(b, node.right(b), at_start, memo)
1184 }
1185 Kind::Star | Kind::Ordered => ensure_begin_leading(b, node.left(b), false, memo),
1186 Kind::Compl => Ok(()),
1187 Kind::Lookbehind | Kind::Lookahead => Ok(()),
1188 }
1189}
1190
1191fn ensure_supported(
1192 b: &mut RegexBuilder,
1193 node: NodeId,
1194 group_names: &[Option<String>],
1195) -> Result<Option<Compatibility>, resharp_algebra::ResharpError> {
1196 ensure_begin_leading(b, node, true, &mut std::collections::HashSet::new())?;
1197 captures::ensure_captures_supported(b, node, group_names)?;
1198 ensure_supported_rec(b, node, true, true, &mut std::collections::HashSet::new())
1199}
1200
1201impl Regex {
1202 pub fn new(pattern: &str) -> Result<Regex, Error> {
1208 Self::with_options(pattern, RegexOptions::default())
1209 }
1210
1211 pub fn with_options(pattern: &str, opts: RegexOptions) -> Result<Regex, Error> {
1223 let mut b = RegexBuilder::new();
1224 b.lookahead_context_max = opts.lookahead_context_max;
1225 let pflags = resharp_parser::PatternFlags {
1226 unicode: opts.unicode != UnicodeMode::Ascii,
1227 full_unicode: opts.unicode == UnicodeMode::Full,
1228 ascii_perl_classes: opts.unicode == UnicodeMode::Javascript,
1229 case_insensitive: opts.case_insensitive,
1230 dot_matches_new_line: opts.dot_matches_new_line,
1231 multiline: opts.multiline,
1232 ignore_whitespace: opts.ignore_whitespace,
1233 #[cfg(feature = "experimental_capture_groups")]
1234 implicit_captures: opts.implicit_captures,
1235 #[cfg(not(feature = "experimental_capture_groups"))]
1236 implicit_captures: false,
1237 expanded_ast_limit: if opts.unbounded_size {
1238 u64::MAX
1239 } else {
1240 resharp_parser::DEFAULT_EXPANDED_AST_LIMIT
1241 },
1242 max_list_len: if opts.unbounded_size {
1243 usize::MAX
1244 } else {
1245 resharp_parser::DEFAULT_MAX_LIST_LEN
1246 },
1247 max_repeat: if opts.unbounded_size {
1248 u32::MAX
1249 } else {
1250 resharp_parser::DEFAULT_MAX_REPEAT
1251 },
1252 max_depth: if opts.unbounded_size {
1253 usize::MAX
1254 } else {
1255 resharp_parser::DEFAULT_MAX_DEPTH
1256 },
1257 };
1258 let (node, group_names, skeleton) =
1259 resharp_parser::parse_ast_with_names_and_skeleton(&mut b, pattern, &pflags)?;
1260 Self::from_node_inner(b, node, opts, pattern.len(), group_names, skeleton)
1261 }
1262
1263 #[doc(hidden)]
1265 pub fn from_node(b: RegexBuilder, node: NodeId, opts: RegexOptions) -> Result<Regex, Error> {
1266 Self::from_node_inner(b, node, opts, 0, Vec::new(), None)
1267 }
1268
1269 fn from_node_inner(
1270 mut b: RegexBuilder,
1271 node: NodeId,
1272 opts: RegexOptions,
1273 pattern_len: usize,
1274 group_names: Vec<Option<String>>,
1275 skeleton: Option<resharp_parser::Skeleton>,
1276 ) -> Result<Regex, Error> {
1277 let node_limit = if opts.unbounded_size {
1279 usize::MAX
1280 } else {
1281 200_000
1282 };
1283 if b.tree_size(node, node_limit) >= node_limit {
1284 return Err(Error::PatternTooLarge);
1285 }
1286 let _compatibility = ensure_supported(&mut b, node, &group_names)?;
1287
1288 let empty_nullable = b
1289 .nullability_emptystring(node)
1290 .has(Nullability::EMPTYSTRING);
1291 let initial_nullability = b.nullability(node);
1292
1293 let node_fwd_simpl = b.simplify_fwd_initial(node);
1294 let fwd_start = b.strip_lb(node_fwd_simpl)?;
1295 let fwd_end_nullable = b.nullability(fwd_start).has(Nullability::END);
1296 let rev_basis = b
1297 .strip_trailing_redundant_lookahead(node_fwd_simpl)
1298 .unwrap_or(node_fwd_simpl);
1299 let ts_rev_start = b.ts_rev_start(rev_basis)?;
1300 #[cfg(feature = "debug")]
1302 {
1303 eprintln!("[fwd]: {:.70}", b.pp(node));
1304 eprintln!("[ts_rev]: {:.70}", b.pp(ts_rev_start));
1305 }
1306
1307 let is_empty_lang = node_fwd_simpl == NodeId::BOT;
1308 let body_after_begin = body_after_begin_of(&mut b, node_fwd_simpl);
1310 let lb_stripped = fwd_start != body_after_begin;
1311 let fwd_begin_anchored = b.is_begin_anchored(node_fwd_simpl) && !lb_stripped;
1312 let has_look = b.contains_look(node_fwd_simpl);
1313 let rev_node = b.reverse(node_fwd_simpl)?;
1314 let rev_end_nullable = initial_nullability.has(Nullability::END)
1315 || b.nullability(ts_rev_start).has(Nullability::BEGIN)
1316 || neg_lookbehind_marker_prev_end_nullable(&mut b, node_fwd_simpl);
1317 let rev_end_anchored = b.is_begin_anchored(rev_node) && !fwd_end_nullable;
1318 let fixed_length = b.get_fixed_length(node_fwd_simpl);
1319 let (min_len, max_len) = b.get_min_max_length(node_fwd_simpl);
1320 let max_length = if max_len != u32::MAX {
1321 Some(max_len)
1322 } else {
1323 None
1324 };
1325 let max_cap = opts.max_dfa_capacity.min(u16::MAX as usize);
1326 let mut opts = opts;
1327 let has_anchors_pre = b.contains_anchors(node_fwd_simpl);
1328 let ah = auto_harden(&mut b, fwd_start, has_anchors_pre);
1329 if ah.full {
1330 opts.hardened = true;
1331 }
1332 let (selected, rev_skip, _fwd_prefix_wins) = if opts.disable_prefixes {
1333 (None, None, false)
1334 } else {
1335 prefix::select_prefix(
1336 &mut b,
1337 node_fwd_simpl,
1338 ts_rev_start,
1339 has_look,
1340 min_len,
1341 max_cap,
1342 ah.no_fwd_prefix,
1343 opts.hardened,
1344 opts.force_convergence,
1345 )?
1346 };
1347 #[cfg(feature = "debug")]
1348 {
1349 let kind = match (&selected, &rev_skip) {
1350 (Some(prefix::PrefixKind::AnchoredFwd(_)), _) => "AnchoredFwd",
1351 (Some(prefix::PrefixKind::AnchoredFwdLb(_)), _) => "AnchoredFwdLb",
1352 (Some(prefix::PrefixKind::AnchoredRev), _) => "AnchoredRev",
1353 (Some(prefix::PrefixKind::PotentialStart), _) => "PotentialStart",
1354 #[cfg(feature = "convergence_prefix")]
1355 (Some(prefix::PrefixKind::Convergence), _) => "Convergence",
1356 (None, Some(_)) => "<none> (rev prefix_skip)",
1357 (None, None) => "<none>",
1358 };
1359 eprintln!("[prefix] selected={kind} rev_skip={}", rev_skip.is_some());
1360 }
1361 let has_fwd_prefix = matches!(
1362 selected,
1363 Some(prefix::PrefixKind::AnchoredFwd(_) | prefix::PrefixKind::AnchoredFwdLb(_))
1364 );
1365 let fwd = ldfa::LDFA::new_fwd(&mut b, fwd_start, max_cap)?;
1366
1367 let ts_fwd_start = {
1368 let with_ts = b.mk_concat(NodeId::TS, node);
1369 with_ts
1371 };
1372 #[allow(unused_mut)]
1373 let mut ts_fwd = ldfa::LDFA::new_fwd(&mut b, ts_fwd_start, max_cap)?;
1374
1375 let mut rev_ts = ldfa::LDFA::new_rev(&mut b, ts_rev_start, max_cap)?;
1376 #[cfg(feature = "convergence_prefix")]
1377 let mut conv_b: Option<ldfa::LDFA> = None;
1378 #[cfg(feature = "convergence_prefix")]
1379 let mut conv_prefix = false;
1380 if let Some((search, resume_node, b_node)) = rev_skip {
1381 #[cfg(not(feature = "convergence_prefix"))]
1382 let _ = b_node;
1383 #[cfg(feature = "debug")]
1384 eprintln!("[conv split] resume_node={:?} b_node={:?}", resume_node.map(|n| b.pp(n)), b_node.map(|n| b.pp(n)));
1385 let resume = match resume_node {
1386 Some(node) => {
1387 let pruned_node = rev_ts.state_nodes[rev_ts.pruned as usize];
1388 let union = b.mk_union(node, pruned_node);
1389 #[cfg(feature = "debug")]
1390 eprintln!("[conv resume build] node={} pruned_node={} union={}", b.pp(node), b.pp(pruned_node), b.pp(union));
1391 rev_ts.get_or_register(&mut b, union)
1392 }
1393 None => 0,
1394 };
1395 #[allow(unused_mut)]
1396 let mut window = 0u32;
1397 #[cfg(feature = "convergence_prefix")]
1398 if resume != 0 {
1399 let b_node = b_node.expect(
1400 "convergence prefix (resume != 0) must carry its right-side `b` node",
1401 );
1402 let b_max = b.get_min_max_length(b_node).1;
1403 let fwd_window = if b_max == u32::MAX { 0 } else { b_max };
1404 let rev_b_node = b.reverse(b_node)?;
1405 let rev_b_node = b.normalize_rev(rev_b_node, 0)?;
1406 let rev_window = b.get_min_max_length(rev_b_node).0;
1407 window = fwd_window.max(rev_window);
1408 conv_b = Some(ldfa::LDFA::new_fwd(&mut b, b_node, max_cap)?);
1409 conv_prefix = true;
1410 }
1411 rev_ts.install_prefix(&mut b, search, resume as u32, window)?;
1412 }
1413 if !b.starts_with_ts(ts_rev_start) {
1414 rev_ts.ensure_dead_skip();
1415 }
1416
1417 let rev_anchored = if rev_end_anchored {
1418 let rev_no_ts = b.normalize_rev(rev_node, 0)?;
1419 Some(ldfa::LDFA::new_rev(&mut b, rev_no_ts, max_cap)?)
1420 } else {
1421 None
1422 };
1423
1424 #[cfg(feature = "stream")]
1425 let stream_init = {
1426 let fwd_pruned = b.prune_begin_eps(ts_fwd_start);
1427 let rev_pruned = b.prune_begin_eps(ts_rev_start);
1428 stream::StreamInit {
1429 start_node: node_fwd_simpl,
1430 seek_fwd: ts_fwd.get_or_register(&mut b, fwd_pruned).into(),
1431 seek_rev: rev_ts.get_or_register(&mut b, rev_pruned).into(),
1432 }
1433 };
1434
1435 let mut lb_verify: Option<ldfa::LDFA> = None;
1436 let (
1437 fwd_lb_begin_nullable,
1438 fwd_lb_begin_len,
1439 fwd_lb_begin_classes,
1440 fwd_lb_body_nullable,
1441 lb_check_bytes,
1442 ) =
1443 if matches!(selected, Some(prefix::PrefixKind::AnchoredFwdLb(_))) {
1444 let lb_node = node_fwd_simpl.left(&b);
1445 let lb_inner = b.get_lookbehind_inner(lb_node);
1446 let (lb_stripped_node, lb_fixed) = prefix::fwd_lb_class(&mut b, lb_node)
1447 .ok_or(Error::InternalError("AnchoredFwdLb requires fixed-length lb"))?;
1448 lb_verify = Some(ldfa::LDFA::new_fwd(&mut b, lb_stripped_node, max_cap)?);
1449 let (begin_nullable, begin_len, begin_classes) =
1450 match prefix::fwd_lb_begin_info(&mut b, lb_inner) {
1451 prefix::BeginInfo::None => (false, 0, Vec::new()),
1452 prefix::BeginInfo::Node(_, 0) => (true, 0, Vec::new()),
1453 prefix::BeginInfo::Node(began, len) => {
1454 let classes = prefix::fwd_lb_begin_classes(&mut b, began, len).ok_or(
1455 Error::InternalError("AnchoredFwdLb begin path has no byte classes"),
1456 )?;
1457 (
1458 true,
1459 u8::try_from(len).map_err(|_| {
1460 Error::InternalError("AnchoredFwdLb begin_len exceeds u8")
1461 })?,
1462 classes,
1463 )
1464 }
1465 prefix::BeginInfo::Unrepresentable => {
1466 return Err(Error::InternalError(
1467 "AnchoredFwdLb selected with unrepresentable begin path",
1468 ));
1469 }
1470 };
1471 let body_nullable = b.nullability(fwd_start) != Nullability::NEVER;
1472 (
1473 begin_nullable,
1474 begin_len,
1475 begin_classes,
1476 body_nullable,
1477 u8::try_from(lb_fixed)
1478 .map_err(|_| Error::InternalError("AnchoredFwdLb lb_fixed exceeds u8"))?,
1479 )
1480 } else {
1481 (false, 0, Vec::new(), false, 0)
1482 };
1483
1484 let always_nullable = initial_nullability == Nullability::ALWAYS;
1486 let max_len_limit = if always_nullable { 512 } else { 100 };
1487 let use_bounded = !opts.disable_prefixes
1488 && !has_fwd_prefix
1489 && max_length.is_some()
1490 && max_len <= max_len_limit
1491 && !b.contains_lookbehind(node_fwd_simpl)
1492 && !node_fwd_simpl.contains_lookahead(&b)
1493 && !b.contains_anchors(node_fwd_simpl)
1494 && pattern_len <= 150 && (!empty_nullable || always_nullable);
1496
1497 let bounded = if use_bounded {
1498 Some(bdfa::BDFA::new(&mut b, fwd_start)?)
1499 } else {
1500 None
1501 };
1502
1503 let has_bounded = bounded.is_some();
1504 let bounded_safe_find_all = if has_bounded {
1505 if always_nullable {
1506 true
1507 } else if max_len < min_len.saturating_add(2) {
1508 true
1509 } else {
1510 let inner_match = b.mk_concat(node_fwd_simpl, resharp_algebra::NodeId::TOPPLUS);
1511 let interior = b.mk_concat(resharp_algebra::NodeId::TOPPLUS, inner_match);
1512 let overlap = b.mk_inter(node_fwd_simpl, interior);
1513 b.is_empty_lang(overlap) == Some(true)
1514 }
1515 } else {
1516 false
1517 };
1518 let has_anchors = b.contains_anchors(node_fwd_simpl);
1519 let has_lb = b.contains_lookbehind(node_fwd_simpl);
1520 let has_la = node_fwd_simpl.contains_lookahead(&b);
1521
1522 const CLASS_PLUS_FREQ: u64 = 65_535;
1523 let class_plus = if !has_lb && !has_la && !has_anchors && !has_look && fixed_length.is_none() {
1524 detect_class_plus(&mut b, fwd_start)
1525 .filter(|&c| {
1526 let class = class_freq_sum(&mut b, c);
1527 let compl = total_byte_freq().saturating_sub(class);
1528 class >= CLASS_PLUS_FREQ && compl >= CLASS_PLUS_FREQ
1529 })
1530 .map(|c| class_membership(&mut b, c))
1531 } else {
1532 None
1533 };
1534
1535 let neg_lb = if has_lb && matches!(selected, Some(prefix::PrefixKind::AnchoredFwd(_))) {
1536 prefix::neg_lb_classes(&mut b, node_fwd_simpl)
1537 } else {
1538 None
1539 };
1540
1541 let need_nn_cycle = (opts.hardened && !has_bounded && fixed_length.is_none() && max_cap >= 64)
1542 || initial_nullability == Nullability::ALWAYS;
1543 let nn_cycle = need_nn_cycle && fwd.has_nonnullable_cycle(&mut b, 256);
1544 let hardened = opts.hardened && !has_bounded && fixed_length.is_none() && max_cap >= 64 && nn_cycle;
1545 let star_loop =
1546 initial_nullability == Nullability::ALWAYS && !has_lb && !nn_cycle;
1547
1548 let fas = if hardened || initial_nullability == Nullability::ALWAYS {
1549 let ksm = if hardened {
1550 fwd_start.contains_lookahead(&b) || initial_nullability != Nullability::ALWAYS
1551 } else {
1552 true
1553 };
1554 let ksm = ksm || initial_nullability == Nullability::ALWAYS;
1555 Some(fas::FwdDFA::new(&fwd, ksm))
1556 } else {
1557 None
1558 };
1559
1560 if b.take_unsupported_lb_fusion() {
1561 return Err(Error::Algebra(resharp_algebra::ResharpError::UnsupportedPattern));
1562 }
1563
1564 let captures_dispatch =
1565 captures::compute_capture_dispatch(&b, node_fwd_simpl, group_names.len())?;
1566
1567 let group_names = {
1568 let mut named = Vec::with_capacity(group_names.len() + 1);
1569 named.push(None);
1570 named.extend(group_names);
1571 named
1572 };
1573
1574 Ok(Regex {
1575 inner: Mutex::new(RegexInner {
1576 b,
1577 fwd,
1578 fwd_ts: ts_fwd,
1579 rev: rev_anchored,
1580 rev_ts,
1581 #[cfg(feature = "stream")]
1582 stream: stream_init,
1583 nulls: StartPositions::new(),
1584 matches: Vec::new(),
1585 bounded,
1586 fas,
1587 lb_verify,
1588 #[cfg(feature = "convergence_prefix")]
1589 conv_b,
1590 capture_root: node_fwd_simpl,
1591 capture_dfa: pparse::PosixParser::new(max_cap),
1592 skeleton,
1593 }),
1594 find_all: compute_find_all(
1595 is_empty_lang,
1596 fwd_begin_anchored,
1597 rev_end_anchored,
1598 hardened,
1599 has_bounded,
1600 class_plus.is_some(),
1601 &selected,
1602 ),
1603 fwd_lb_stripped: lb_stripped,
1604 class_plus,
1605 prefix: selected,
1606 fixed_length,
1607 empty_nullable,
1608 always_nullable: initial_nullability == Nullability::ALWAYS,
1609 star_loop,
1610 is_empty_lang,
1611 fwd_begin_anchored,
1612 rev_end_anchored,
1613 initial_nullability,
1614 fwd_end_nullable,
1615 rev_end_nullable,
1616 hardened,
1617 has_bounded,
1618 bounded_safe_find_all,
1619 lb_check_bytes,
1620 fwd_lb_begin_nullable,
1621 fwd_lb_begin_len,
1622 fwd_lb_begin_classes,
1623 fwd_lb_body_nullable,
1624 init_flags: InitialNodeFlags::new(has_anchors, has_lb, has_la),
1625 #[cfg(feature = "convergence_prefix")]
1626 conv_prefix,
1627 neg_lb,
1628 group_names,
1629 captures_dispatch,
1630 #[cfg(feature = "stream")]
1631 stream_cache: Default::default(),
1632 })
1633 }
1634
1635 #[cfg(feature = "diag")]
1636 #[allow(missing_docs)]
1637 pub fn node_count(&self) -> u32 {
1638 self.inner.lock().unwrap_or_else(|e| e.into_inner()).b.num_nodes()
1639 }
1640
1641 #[cfg(feature = "diag")]
1642 #[allow(missing_docs)]
1643 pub fn dfa_stats(&self) -> (usize, usize) {
1644 let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
1645 (inner.fwd.state_nodes.len(), inner.rev_ts.state_nodes.len())
1646 }
1647
1648 #[cfg(feature = "diag")]
1649 #[allow(missing_docs)]
1650 pub fn is_hardened(&self) -> bool {
1651 self.hardened
1652 }
1653
1654 #[cfg(feature = "convergence_prefix")]
1655 #[allow(missing_docs)]
1656 pub fn uses_convergence_prefix(&self) -> bool {
1657 self.conv_prefix
1658 }
1659
1660 #[cfg(feature = "diag")]
1661 #[allow(missing_docs)]
1662 pub fn has_fwd_prefix(&self) -> bool {
1663 matches!(
1664 self.prefix,
1665 Some(prefix::PrefixKind::AnchoredFwd(_) | prefix::PrefixKind::AnchoredFwdLb(_))
1666 )
1667 }
1668
1669 #[cfg(feature = "diag")]
1670 #[allow(missing_docs)]
1671 pub fn has_prefix(&self) -> bool {
1672 self.prefix.is_some()
1673 }
1674
1675 #[cfg(feature = "diag")]
1676 #[allow(missing_docs)]
1677 pub fn is_fwd_begin_anchored(&self) -> bool {
1678 self.fwd_begin_anchored
1679 }
1680
1681 #[cfg(feature = "diag")]
1682 #[allow(missing_docs)]
1683 pub fn bdfa_stats(&self) -> Option<(usize, usize, usize)> {
1684 let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
1685 inner
1686 .bounded
1687 .as_ref()
1688 .map(|b| (b.states.len(), 1usize << b.mt_log, b.prefix_len))
1689 }
1690
1691 #[cfg(feature = "diag")]
1692 #[allow(missing_docs)]
1693 pub fn find_all_kind_name(&self) -> &'static str {
1694 match self.find_all {
1695 FindAll::EmptyLang => "EmptyLang",
1696 FindAll::Anchored => "Anchored",
1697 FindAll::EndAnchored => "EndAnchored",
1698 FindAll::Hardened => "Hardened",
1699 FindAll::Dfa => "Dfa",
1700 FindAll::ClassPlus => "ClassPlus",
1701 FindAll::Bounded => "Bounded",
1702 FindAll::FwdPrefix => "FwdPrefix",
1703 FindAll::FwdLbPrefix => "FwdLbPrefix",
1704 }
1705 }
1706
1707 #[cfg(feature = "diag")]
1708 #[allow(missing_docs)]
1709 pub fn captures_kind_name(&self) -> &'static str {
1710 match &self.captures_dispatch {
1711 captures::CaptureDispatch::Empty => "Empty",
1712 captures::CaptureDispatch::FixedOffsets(_) => "FixedOffsets",
1713 captures::CaptureDispatch::Dfa => "Dfa",
1714 }
1715 }
1716
1717 #[cfg(feature = "diag")]
1718 #[allow(missing_docs)]
1719 pub fn prefix_kind_name(&self) -> Option<&'static str> {
1720 match &self.prefix {
1721 None => None,
1722 Some(prefix::PrefixKind::AnchoredFwd(_)) => Some("AnchoredFwd"),
1723 Some(prefix::PrefixKind::AnchoredFwdLb(_)) => Some("AnchoredFwdLb"),
1724 Some(prefix::PrefixKind::AnchoredRev) => Some("AnchoredRev"),
1725 Some(prefix::PrefixKind::PotentialStart) => Some("PotentialStart"),
1726 #[cfg(feature = "convergence_prefix")]
1727 Some(prefix::PrefixKind::Convergence) => Some("Convergence"),
1728 }
1729 }
1730
1731 #[cfg(feature = "diag")]
1732 #[allow(missing_docs)]
1733 pub fn fwd_prefix_kind(&self) -> Option<(&'static str, usize)> {
1734 match &self.prefix {
1735 Some(prefix::PrefixKind::AnchoredFwd(fp))
1736 | Some(prefix::PrefixKind::AnchoredFwdLb(fp)) => Some((fp.variant_name(), fp.len())),
1737 _ => None,
1738 }
1739 }
1740
1741 #[cfg(feature = "diag")]
1742 #[allow(missing_docs)]
1743 pub fn has_accel(&self) -> (bool, bool) {
1744 let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
1745 let fwd = self.prefix.as_ref().is_some_and(|p| p.is_fwd());
1746 let rev = self.prefix.as_ref().is_some_and(|p| p.is_rev())
1747 || inner.rev_ts.can_skip();
1748 (fwd, rev)
1749 }
1750
1751 pub(crate) fn empty_input_match(&self) -> Option<Match> {
1754 (self.empty_nullable && !self.is_empty_lang).then_some(Match { start: 0, end: 0 })
1755 }
1756
1757 pub fn find_all(&self, input: &[u8]) -> Result<Vec<Match>, Error> {
1766 if input.is_empty() {
1767 return Ok(self.empty_input_match().into_iter().collect());
1768 }
1769
1770 #[cfg(all(feature = "debug", debug_assertions))]
1771 eprintln!("[algorithm] {:?} input={:?}", self.find_all, input);
1772
1773 match self.find_all {
1774 FindAll::EmptyLang => Ok(vec![]),
1775 FindAll::ClassPlus => Ok(self.find_all_class_plus(input)),
1776 FindAll::Anchored => Ok(self.find_anchored(input)?.into_iter().collect()),
1777 FindAll::EndAnchored => Ok(self.find_end_anchored(input)?.into_iter().collect()),
1778 FindAll::Hardened | FindAll::Dfa => self.find_all_dfa(input),
1779 FindAll::Bounded => {
1780 if self.bounded_safe_find_all {
1781 self.find_all_fwd_bounded(input)
1782 } else {
1783 self.find_all_dfa(input)
1784 }
1785 }
1786 FindAll::FwdPrefix => match &self.prefix {
1787 Some(prefix::PrefixKind::AnchoredFwd(fp)) => {
1788 self.find_all_fwd_prefix(fp, self.neg_lb.as_ref(), input)
1789 }
1790 _ => Err(Error::InternalError("FwdPrefix without AnchoredFwd prefix")),
1791 },
1792 FindAll::FwdLbPrefix => match &self.prefix {
1793 Some(prefix::PrefixKind::AnchoredFwdLb(fp)) => {
1794 self.find_all_fwd_lb_prefix(fp, input)
1795 }
1796 _ => Err(Error::InternalError("FwdLbPrefix without AnchoredFwdLb prefix")),
1797 },
1798 }
1799 }
1800
1801 #[cfg(feature = "experimental_capture_groups")]
1810 pub fn captures_all(&self, input: &[u8]) -> Result<Vec<Captures<'_>>, Error> {
1811 let matches = self.find_all(input)?;
1812 {
1813 let inner = &mut *self.inner.lock().unwrap_or_else(|e| e.into_inner());
1814 inner.capture_dfa.begin_input(input);
1815 }
1816 matches.into_iter().map(|m| self.captures_of(input, m)).collect()
1817 }
1818
1819 #[cfg(feature = "experimental_capture_groups")]
1820 fn captures_of(&self, input: &[u8], m: Match) -> Result<Captures<'_>, Error> {
1821 let mut spans = Vec::with_capacity(self.group_names.len());
1822 spans.push(Some((m.start, m.end)));
1823 spans.extend(self.captures_at(input, m.start, m.end)?);
1824 if spans.len() != self.group_names.len() {
1825 return Err(Error::InternalError("capture slot count does not match group count"));
1826 }
1827 Ok(Captures { names: &self.group_names, spans })
1828 }
1829
1830 #[cfg(feature = "experimental_capture_groups")]
1831 fn captures_at(
1832 &self,
1833 input: &[u8],
1834 begin: usize,
1835 end: usize,
1836 ) -> Result<Vec<Option<(usize, usize)>>, Error> {
1837 match &self.captures_dispatch {
1838 captures::CaptureDispatch::Empty => return Ok(Vec::new()),
1839 captures::CaptureDispatch::FixedOffsets(offsets) => {
1840 return Ok(offsets
1841 .iter()
1842 .map(|g| match *g {
1843 captures::GroupOffset::FromBegin { open, close } => {
1844 Some((begin + open as usize, begin + close as usize))
1845 }
1846 captures::GroupOffset::FromEnd { open, close } => {
1847 Some((end - open as usize, end - close as usize))
1848 }
1849 })
1850 .collect());
1851 }
1852 captures::CaptureDispatch::Dfa => {}
1853 }
1854 let inner = &mut *self.inner.lock().unwrap_or_else(|e| e.into_inner());
1855 let RegexInner {
1856 ref mut b,
1857 ref mut capture_dfa,
1858 ref skeleton,
1859 capture_root,
1860 ..
1861 } = *inner;
1862 pparse::extract_captures(b, capture_dfa, capture_root, skeleton.as_ref(), input, begin, end)
1863 }
1864
1865 #[cfg(feature = "experimental_capture_groups")]
1876 pub fn capture_names(&self) -> &[Option<String>] {
1877 &self.group_names
1878 }
1879
1880 #[cfg(feature = "experimental_capture_groups")]
1883 pub fn capture_index_for_name(&self, name: &str) -> Option<usize> {
1884 self.group_names
1885 .iter()
1886 .position(|n| n.as_deref() == Some(name))
1887 }
1888}
1889
1890fn push_end_zero_width(matches: &mut Vec<Match>, len: usize) {
1891 if matches.last().map(|m| m.start) != Some(len) {
1892 matches.push(Match { start: len, end: len });
1893 }
1894}
1895
1896fn single_sat_target(b: &mut RegexBuilder, node: NodeId) -> Option<(NodeId, TSetId)> {
1897 let der = b.der(node, Nullability::CENTER).ok()?;
1898 let mut targets: Vec<(NodeId, TSetId)> = Vec::new();
1899 b.collect_der_targets(der, TSetId::FULL, &mut targets);
1900 let mut live = targets.into_iter().filter(|(t, _)| *t != NodeId::BOT);
1901 let first = live.next()?;
1902 if live.next().is_some() {
1903 return None;
1904 }
1905 Some(first)
1906}
1907
1908fn detect_class_plus(b: &mut RegexBuilder, node: NodeId) -> Option<TSetId> {
1909 if b.nullability(node).has(Nullability::CENTER) {
1910 return None;
1911 }
1912 let (t1, c1) = single_sat_target(b, node)?;
1913 if !b.nullability(t1).has(Nullability::CENTER) {
1914 return None;
1915 }
1916 let (t2, c2) = single_sat_target(b, t1)?;
1917 if t2 != t1 || c2 != c1 {
1918 return None;
1919 }
1920 Some(c1)
1921}
1922
1923fn class_freq_sum(b: &mut RegexBuilder, set: TSetId) -> u64 {
1924 b.solver()
1925 .collect_bytes(set)
1926 .iter()
1927 .map(|&c| crate::simd::BYTE_FREQ[c as usize] as u64)
1928 .sum()
1929}
1930
1931fn total_byte_freq() -> u64 {
1932 crate::simd::BYTE_FREQ.iter().map(|&f| f as u64).sum()
1933}
1934
1935fn class_membership(b: &mut RegexBuilder, set: TSetId) -> [u64; 4] {
1936 let mut table = [0u64; 4];
1937 for &c in b.solver().collect_bytes(set).iter() {
1938 table[(c >> 6) as usize] |= 1u64 << (c & 63);
1939 }
1940 table
1941}
1942
1943fn compute_find_all(
1944 is_empty_lang: bool,
1945 fwd_begin_anchored: bool,
1946 rev_end_anchored: bool,
1947 hardened: bool,
1948 has_bounded: bool,
1949 class_plus: bool,
1950 prefix: &Option<prefix::PrefixKind>,
1951) -> FindAll {
1952 if is_empty_lang {
1953 return FindAll::EmptyLang;
1954 }
1955 if fwd_begin_anchored {
1956 return FindAll::Anchored;
1957 }
1958 if hardened {
1959 return FindAll::Hardened;
1960 }
1961 if class_plus {
1962 return FindAll::ClassPlus;
1963 }
1964 if rev_end_anchored {
1965 return FindAll::EndAnchored;
1966 }
1967 match prefix {
1968 Some(prefix::PrefixKind::AnchoredFwd(_)) => FindAll::FwdPrefix,
1969 Some(prefix::PrefixKind::AnchoredFwdLb(_)) => FindAll::FwdLbPrefix,
1970 Some(prefix::PrefixKind::AnchoredRev | prefix::PrefixKind::PotentialStart) => {
1971 FindAll::Dfa
1972 }
1973 _ => {
1974 if has_bounded {
1975 FindAll::Bounded
1976 } else {
1977 FindAll::Dfa
1978 }
1979 }
1980 }
1981}
1982
1983#[cfg(feature = "convergence_prefix")]
1984pub(crate) fn find_inner_literal(
1985 b: &mut resharp_algebra::RegexBuilder,
1986 search_start: resharp_algebra::NodeId,
1987) -> Option<(
1988 resharp_algebra::NodeId,
1989 Vec<resharp_algebra::solver::TSetId>,
1990 resharp_algebra::solver::TSetId,
1991)> {
1992 use resharp_algebra::NodeId;
1993 use resharp_algebra::solver::TSetId;
1994
1995 if search_start == NodeId::BOT || b.get_min_max_length(search_start).0 == 0 {
1996 return None;
1997 }
1998
1999 const MAX_SINK_FREQ: u64 = 25_000;
2000 fn set_freq(b: &mut resharp_algebra::RegexBuilder, set: TSetId) -> u64 {
2001 b.solver()
2002 .collect_bytes(set)
2003 .iter()
2004 .map(|&c| crate::simd::BYTE_FREQ[c as usize] as u64)
2005 .sum()
2006 }
2007
2008 let mut spine: Vec<(NodeId, NodeId)> = Vec::new();
2009 let mut curr = search_start;
2010 loop {
2011 let is_concat = curr.is_concat(b);
2012 let head = if is_concat { curr.left(b) } else { curr };
2013 spine.push((curr, head));
2014 if is_concat {
2015 curr = curr.right(b);
2016 } else {
2017 break;
2018 }
2019 }
2020
2021 let mut best: Option<(usize, TSetId)> = None;
2022 let mut best_score = u64::MAX;
2023 let mut needle_union = TSetId::EMPTY;
2024 let mut quad = false;
2025 for (i, &(_, head)) in spine.iter().enumerate() {
2026 if !quad && head.is_pred(b) {
2027 let l = head.pred_tset(b);
2028 let freq = set_freq(b, l);
2029 if freq < MAX_SINK_FREQ && freq < best_score {
2030 best = Some((i, l));
2031 best_score = freq;
2032 }
2033 }
2034 if b.get_min_max_length(head).1 == u32::MAX {
2035 let lead = match b.der(head, Nullability::CENTER) {
2036 Ok(d) => {
2037 let mut stack = vec![(d, TSetId::FULL)];
2038 let mut acc = TSetId::EMPTY;
2039 b.iter_sat(&mut stack, &mut |bb, _n, set| {
2040 acc = bb.solver().or_id(acc, set);
2041 });
2042 acc
2043 }
2044 Err(_) => b.solver().not_id(TSetId::EMPTY),
2045 };
2046 if b.solver().is_sat_id(lead, needle_union) {
2047 quad = true;
2048 }
2049 } else if head.is_pred(b) {
2050 let s = head.pred_tset(b);
2051 if set_freq(b, s) < MAX_SINK_FREQ {
2052 needle_union = b.solver().or_id(needle_union, s);
2053 }
2054 }
2055 }
2056 let (i, _l) = best?;
2057
2058 let is_byte_lit = |b: &mut RegexBuilder, head: NodeId| {
2059 if !head.is_pred(b) {
2060 return false;
2061 }
2062 let ts = head.pred_tset(b);
2063 b.solver().collect_bytes(ts).len() == 1
2064 };
2065 let mut i_lo = i;
2066 let mut i_hi = i;
2067 if is_byte_lit(b, spine[i].1) {
2068 while i_lo > 0 && is_byte_lit(b, spine[i_lo - 1].1) {
2069 i_lo -= 1;
2070 }
2071 while i_hi + 1 < spine.len() && is_byte_lit(b, spine[i_hi + 1].1) {
2072 i_hi += 1;
2073 }
2074 }
2075 let run: Vec<TSetId> = (i_lo..=i_hi).map(|k| spine[k].1.pred_tset(b)).collect();
2076 let anchor_set = run[0];
2077 let l_rep = run
2078 .iter()
2079 .copied()
2080 .min_by_key(|&s| set_freq(b, s))
2081 .unwrap();
2082
2083 let mut j = i_lo;
2084 while j > 0 {
2085 let prev_head = spine[j - 1].1;
2086 if !prev_head.is_star(b) {
2087 break;
2088 }
2089 let body = prev_head.left(b);
2090 if !body.is_pred(b) {
2091 break;
2092 }
2093 let body_set = body.pred_tset(b);
2094 if !b.solver().is_sat_id(body_set, anchor_set) {
2095 break;
2096 }
2097 j -= 1;
2098 }
2099 let prefix_result = spine[j].0;
2100 Some((prefix_result, run, l_rep))
2101}
2102
2103#[cfg(feature = "convergence_prefix")]
2104#[doc(hidden)]
2105pub fn detect_inner_literal_bytes(pattern: &str) -> Option<Vec<u8>> {
2106 let mut b = resharp_algebra::RegexBuilder::new();
2107 let pflags = resharp_parser::PatternFlags::default();
2108 let node = resharp_parser::parse_ast_with(&mut b, pattern, &pflags).ok()?;
2109 let node_fwd_simpl = b.simplify_fwd_initial(node);
2110 let rev_basis = b
2111 .strip_trailing_redundant_lookahead(node_fwd_simpl)
2112 .unwrap_or(node_fwd_simpl);
2113 let ts_rev_start = b.ts_rev_start(rev_basis).ok()?;
2114 let rev_stripped = crate::prefix::PrefixSets::compute(&mut b, node_fwd_simpl, ts_rev_start)
2115 .ok()?
2116 .rev_stripped;
2117 let (_c, run, _l) = find_inner_literal(&mut b, rev_stripped)?;
2118 let mut bytes: Vec<u8> = Vec::new();
2119 for &s in run.iter().rev() {
2120 bytes.extend(b.solver().collect_bytes(s));
2121 }
2122 Some(bytes)
2123}
2124
2125impl Regex {
2126 #[cfg(feature = "diag")]
2127 #[allow(missing_docs)]
2128 pub fn rev_state_dump(&self) -> String {
2129 let inner = &mut *self.inner.lock().unwrap_or_else(|e| e.into_inner());
2130 let rev = &inner.rev_ts;
2131 let mut out = String::new();
2132 for (i, &node) in rev.state_nodes.iter().enumerate() {
2133 let eid = rev.effects_id.get(i).copied().unwrap_or(0);
2134 let alg_nid = inner.b.get_nulls_id(node);
2135 let pretty = inner.b.pp(node);
2136 let pretty = if pretty.len() > 200 {
2137 format!("{}...", &pretty[..200])
2138 } else {
2139 pretty
2140 };
2141 out += &format!(
2142 " s[{}] eid={} alg_nid={:?} pp={}\n",
2143 i, eid, alg_nid, pretty
2144 );
2145 }
2146 out
2147 }
2148
2149 #[cfg(feature = "diag")]
2150 #[allow(missing_docs)]
2151 pub fn diag_flags(&self) -> String {
2152 format!("fixed_length={:?} always_nullable={} hardened={} find_all={:?} initial_nullability={:?}", self.fixed_length, self.always_nullable, self.hardened, self.find_all, self.initial_nullability)
2153 }
2154
2155 #[cfg(feature = "diag")]
2156 #[allow(missing_docs)]
2157 pub fn fwd_effects_debug(&self) -> String {
2158 let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
2159 let RegexInner { ref mut b, ref fwd, .. } = *inner;
2160 let mut out = String::new();
2161 for (i, &eid) in fwd.effects_id.iter().enumerate() {
2162 if eid != 0 {
2163 let nulls: Vec<String> = fwd.effects[eid as usize]
2164 .iter()
2165 .map(|n| format!("(mask={},rel={})", n.mask.0, n.rel))
2166 .collect();
2167 let node = fwd.state_nodes.get(i).copied();
2168 let pp = node.map(|n| b.pp(n)).unwrap_or_default();
2169 out += &format!(" state[{}] node={:?}({}) eid={} nulls=[{}]\n", i, node, pp, eid, nulls.join(", "));
2170 }
2171 }
2172 out
2173 }
2174
2175 #[cfg(feature = "diag")]
2176 #[allow(missing_docs)]
2177 pub fn effects_debug(&self) -> String {
2178 let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
2179 let rev = &inner.rev_ts;
2180 let mut out = String::new();
2181 for (i, &eid) in rev.effects_id.iter().enumerate() {
2182 if eid != 0 {
2183 let nulls: Vec<String> = rev.effects[eid as usize]
2184 .iter()
2185 .map(|n| format!("(mask={},rel={})", n.mask.0, n.rel))
2186 .collect();
2187 out += &format!(" state[{}] eid={} nulls=[{}]\n", i, eid, nulls.join(", "));
2188 }
2189 }
2190 out
2191 }
2192
2193 #[cfg(feature = "diag")]
2194 #[allow(missing_docs)]
2195 pub fn collect_rev_nulls_debug(&self, input: &[u8]) -> Vec<usize> {
2196 let inner = &mut *self.inner.lock().unwrap_or_else(|e| e.into_inner());
2197 inner.nulls.clear();
2198 #[cfg(feature = "convergence_prefix")]
2199 {
2200 let RegexInner { rev_ts, b, nulls, conv_b, .. } = &mut *inner;
2201 rev_ts
2202 .collect_rev(b, input.len() - 1, input, nulls, conv_b.as_mut())
2203 .unwrap();
2204 }
2205 #[cfg(not(feature = "convergence_prefix"))]
2206 inner
2207 .rev_ts
2208 .collect_rev(&mut inner.b, input.len() - 1, input, &mut inner.nulls, None)
2209 .unwrap();
2210 inner.nulls.positions_desc().collect()
2211 }
2212
2213 #[cfg(feature = "diag")]
2214 #[allow(missing_docs)]
2215 pub fn scan_fwd_debug(&self, input: &[u8], pos: usize) -> Option<usize> {
2216 let inner = &mut *self.inner.lock().unwrap_or_else(|e| e.into_inner());
2217 inner.fwd.scan_fwd_optional(&mut inner.b, pos, input).unwrap()
2218 }
2219
2220 #[cfg(feature = "diag")]
2221 #[allow(missing_docs)]
2222 pub fn scan_fwd_all_nulls_debug(&self, input: &[u8], pos: usize) -> Vec<usize> {
2223 let inner = &mut *self.inner.lock().unwrap_or_else(|e| e.into_inner());
2224 let mut nulls = Vec::new();
2225 inner
2226 .fwd
2227 .scan_fwd_all_nulls_to(&mut inner.b, pos, input.len(), input, &mut nulls)
2228 .unwrap();
2229 nulls.sort_unstable();
2230 nulls.dedup();
2231 nulls
2232 }
2233
2234 #[cfg(feature = "diag")]
2237 #[allow(missing_docs)]
2238 pub fn rev_walk_trace(&self, input: &[u8]) -> String {
2239 use std::fmt::Write;
2240 let inner = &mut *self.inner.lock().unwrap_or_else(|e| e.into_inner());
2241 let rev = &mut inner.rev_ts;
2242 let b = &mut inner.b;
2243 let mut out = String::new();
2244 if input.is_empty() {
2245 return out;
2246 }
2247 let last = input.len() - 1;
2248 let mt = rev.mt_lookup[input[last] as usize] as u32;
2249 let mut sid = rev.begin_table[mt as usize];
2250 writeln!(
2251 out,
2252 "pos={} byte={:?} (BEGIN ctx) -> s[{}]",
2253 last, input[last] as char, sid
2254 )
2255 .unwrap();
2256 Self::dump_state(&mut out, b, rev, sid);
2257 for i in (0..last).rev() {
2258 let mt = rev.mt_lookup[input[i] as usize] as u32;
2259 sid = rev.lazy_transition(b, sid, mt).unwrap();
2260 writeln!(
2261 out,
2262 "pos={} byte={:?} (CENTER ctx) -> s[{}]",
2263 i, input[i] as char, sid
2264 )
2265 .unwrap();
2266 Self::dump_state(&mut out, b, rev, sid);
2267 if sid as u32 <= ldfa::DFA_DEAD as u32 {
2268 break;
2269 }
2270 }
2271 out
2272 }
2273
2274 #[cfg(feature = "diag")]
2275 #[allow(missing_docs)]
2276 pub fn fwd_state_dump(&self) -> String {
2277 let inner = &mut *self.inner.lock().unwrap_or_else(|e| e.into_inner());
2278 let fwd = &inner.fwd;
2279 let mut out = String::new();
2280 for (i, &node) in fwd.state_nodes.iter().enumerate() {
2281 let eid = fwd.effects_id.get(i).copied().unwrap_or(0);
2282 let ceid = fwd.center_effect_id.get(i).copied().unwrap_or(0);
2283 let pretty = inner.b.pp(node);
2284 let pretty = if pretty.len() > 400 {
2285 format!("{}...", &pretty[..400])
2286 } else {
2287 pretty
2288 };
2289 out += &format!(" s[{}] eid={} ceid={} pp={}\n", i, eid, ceid, pretty);
2290 }
2291 out
2292 }
2293
2294 #[cfg(feature = "diag")]
2295 #[allow(missing_docs)]
2296 pub fn fwd_walk_trace(&self, input: &[u8]) -> String {
2297 use std::fmt::Write;
2298 let inner = &mut *self.inner.lock().unwrap_or_else(|e| e.into_inner());
2299 let fwd = &mut inner.fwd;
2300 let b = &mut inner.b;
2301 let mut out = String::new();
2302 if input.is_empty() {
2303 return out;
2304 }
2305 let mt = fwd.mt_lookup[input[0] as usize] as u32;
2306 let mut sid = fwd.begin_table[mt as usize];
2307 writeln!(
2308 out,
2309 "pos=0 byte={:?} (BEGIN) -> s[{}]",
2310 input[0] as char, sid
2311 )
2312 .unwrap();
2313 Self::dump_fwd_state(&mut out, b, fwd, sid);
2314 for i in 1..input.len() {
2315 let mt = fwd.mt_lookup[input[i] as usize] as u32;
2316 sid = fwd.lazy_transition(b, sid, mt).unwrap();
2317 writeln!(out, "pos={} byte={:?} -> s[{}]", i, input[i] as char, sid).unwrap();
2318 Self::dump_fwd_state(&mut out, b, fwd, sid);
2319 if sid as u32 <= ldfa::DFA_DEAD as u32 {
2320 break;
2321 }
2322 }
2323 out
2324 }
2325
2326 #[cfg(feature = "diag")]
2327 fn dump_fwd_state(
2328 out: &mut String,
2329 b: &mut resharp_algebra::RegexBuilder,
2330 fwd: &ldfa::LDFA,
2331 sid: u16,
2332 ) {
2333 use std::fmt::Write;
2334 if (sid as usize) >= fwd.state_nodes.len() {
2335 writeln!(out, " (uninitialized state)").unwrap();
2336 return;
2337 }
2338 let node = fwd.state_nodes[sid as usize];
2339 let eid = fwd.effects_id.get(sid as usize).copied().unwrap_or(0);
2340 let ceid = fwd.center_effect_id.get(sid as usize).copied().unwrap_or(0);
2341 let pp = b.pp(node);
2342 let pp = if pp.len() > 240 {
2343 format!("{}...", &pp[..240])
2344 } else {
2345 pp
2346 };
2347 writeln!(out, " pp = {}", pp).unwrap();
2348 writeln!(out, " eid={} (end), center_eid={}", eid, ceid).unwrap();
2349 for (label, e) in [("end", eid), ("center", ceid)] {
2350 if e != 0 && (e as usize) < fwd.effects.len() {
2351 let entries: Vec<String> = fwd.effects[e as usize]
2352 .iter()
2353 .map(|n| format!("(mask={:#b},rel={})", n.mask.0, n.rel))
2354 .collect();
2355 writeln!(
2356 out,
2357 " effects[{}][{}] = [{}]",
2358 label,
2359 e,
2360 entries.join(", ")
2361 )
2362 .unwrap();
2363 }
2364 }
2365 }
2366
2367 #[cfg(feature = "diag")]
2368 fn dump_state(
2369 out: &mut String,
2370 b: &mut resharp_algebra::RegexBuilder,
2371 rev: &ldfa::LDFA,
2372 sid: u16,
2373 ) {
2374 use std::fmt::Write;
2375 if (sid as usize) >= rev.state_nodes.len() {
2376 writeln!(out, " (uninitialized state)").unwrap();
2377 return;
2378 }
2379 let node = rev.state_nodes[sid as usize];
2380 let eid = rev.effects_id.get(sid as usize).copied().unwrap_or(0);
2381 let alg_nid = b.get_nulls_id(node);
2382 let pp = b.pp(node);
2383 let pp = if pp.len() > 240 {
2384 format!("{}...", &pp[..240])
2385 } else {
2386 pp
2387 };
2388 writeln!(out, " pp = {}", pp).unwrap();
2389 writeln!(out, " alg_nulls = {:?}", alg_nid).unwrap();
2390 if eid != 0 {
2391 let entries: Vec<String> = rev.effects[eid as usize]
2392 .iter()
2393 .map(|n| format!("(mask={:#b},rel={})", n.mask.0, n.rel))
2394 .collect();
2395 writeln!(
2396 out,
2397 " dfa_effects[eid={}] = [{}] -> EMIT NULL",
2398 eid,
2399 entries.join(", ")
2400 )
2401 .unwrap();
2402 } else {
2403 writeln!(out, " dfa_effects = (none, eid=0)").unwrap();
2404 }
2405 }
2406
2407 fn find_all_class_plus(&self, input: &[u8]) -> Vec<Match> {
2409 let table = self
2410 .class_plus
2411 .expect("FindAll::ClassPlus requires a materialized class membership table");
2412 let member = |byte: u8| table[(byte >> 6) as usize] & (1u64 << (byte & 63)) != 0;
2413 let mut matches = Vec::new();
2414 let n = input.len();
2415 let mut i = 0usize;
2416 while i < n {
2417 if member(input[i]) {
2418 let start = i;
2419 i += 1;
2420 while i < n && member(input[i]) {
2421 i += 1;
2422 }
2423 matches.push(Match { start, end: i });
2424 } else {
2425 i += 1;
2426 }
2427 }
2428 matches
2429 }
2430
2431 #[allow(dead_code)]
2433 fn find_all_trailing_star(&self, input: &[u8]) -> Result<Vec<Match>, Error> {
2434 let inner = &mut *self.inner.lock().unwrap_or_else(|e| e.into_inner());
2435 let mut pos = 0;
2436 while pos < input.len() {
2437 if let Some(max_end) = inner.fwd.scan_fwd_optional(&mut inner.b, pos, input)? {
2438 if max_end > pos {
2439 return Ok(vec![Match {
2440 start: pos,
2441 end: max_end,
2442 }]);
2443 }
2444 }
2445 pos += 1;
2446 }
2447 Ok(vec![])
2448 }
2449
2450 fn find_all_dfa(&self, input: &[u8]) -> Result<Vec<Match>, Error> {
2451 self.find_all_dfa_inner(input)
2452 }
2453
2454 fn find_all_dfa_inner(&self, input: &[u8]) -> Result<Vec<Match>, Error> {
2455 debug_assert!(!input.is_empty());
2456 let inner = &mut *self.inner.lock().unwrap_or_else(|e| e.into_inner());
2457 inner.nulls.clear();
2458 inner.matches.clear();
2459
2460 if self.always_nullable && !self.hardened {
2461 if self.star_loop {
2462 let RegexInner {
2463 ref mut b,
2464 ref mut fwd,
2465 ref mut matches,
2466 ..
2467 } = *inner;
2468 let len = input.len();
2469 let mut start = 0usize;
2470 while start < len {
2471 let end = fwd.scan_fwd_optional(b, start, input)?.expect(
2472 "always-nullable pattern matches (at least empty) at every position",
2473 );
2474 if end > start {
2475 matches.push(Match { start, end });
2476 start = end;
2477 } else {
2478 matches.push(Match { start, end: start });
2479 start += 1;
2480 }
2481 }
2482 push_end_zero_width(matches, len);
2483 return Ok(matches.clone());
2484 }
2485 let RegexInner {
2486 ref mut b,
2487 ref mut fwd,
2488 ref mut matches,
2489 ref mut fas,
2490 ..
2491 } = *inner;
2492 let fas = fas.as_mut().expect("fas initialized for always_nullable");
2493 fwd.scan_fwd_active_set::<true>(b, fas, input, &StartPositions::new(), matches)?;
2494 push_end_zero_width(matches, input.len());
2495 return Ok(matches.clone());
2496 }
2497
2498 if self.rev_end_nullable {
2499 inner.nulls.add(input.len());
2500 }
2501 {
2502 #[cfg(feature = "convergence_prefix")]
2503 {
2504 let RegexInner { rev_ts, b, nulls, conv_b, .. } = &mut *inner;
2505 rev_ts.collect_rev(b, input.len() - 1, input, nulls, conv_b.as_mut())?;
2506 }
2507 #[cfg(not(feature = "convergence_prefix"))]
2508 {
2509 let RegexInner { rev_ts, b, nulls, .. } = &mut *inner;
2510 rev_ts.collect_rev(b, input.len() - 1, input, nulls, None)?;
2511 }
2512 }
2513
2514 if self.initial_nullability.has(Nullability::BEGIN) {
2515 inner.nulls.add(0);
2516 }
2517
2518 #[cfg(all(feature = "debug", debug_assertions))]
2519 eprintln!("[nulls] {:?}", inner.nulls);
2520
2521 if self.hardened {
2522 let RegexInner {
2523 ref mut b,
2524 ref mut fwd,
2525 ref mut matches,
2526 ref mut fas,
2527 ref nulls,
2528 ..
2529 } = *inner;
2530 let fas = fas.as_mut().unwrap();
2531 if self.always_nullable {
2532 fwd.scan_fwd_active_set::<true>(b, fas, input, nulls, matches)?;
2533 push_end_zero_width(matches, input.len());
2534 } else {
2535 fwd.scan_fwd_active_set::<false>(b, fas, input, nulls, matches)?;
2536 }
2537 return Ok(matches.clone());
2538 }
2539
2540 if let Some(fl) = self.fixed_length {
2541 let fl = fl as usize;
2542 let mut last_end = 0;
2543 for start in inner.nulls.positions_asc() {
2544 if start >= last_end {
2545 inner.matches.push(Match {
2546 start,
2547 end: start + fl,
2548 });
2549 last_end = start + fl;
2550 }
2551 }
2552 } else {
2553 inner
2554 .fwd
2555 .scan_fwd_all(&mut inner.b, &inner.nulls, input, &mut inner.matches)?;
2556 }
2557
2558 if self.always_nullable {
2559 inner.matches.push(Match {
2560 start: input.len(),
2561 end: input.len(),
2562 });
2563 }
2564
2565 Ok(inner.matches.clone())
2566 }
2567
2568 pub fn find_anchored(&self, input: &[u8]) -> Result<Option<Match>, Error> {
2572 if input.is_empty() {
2573 return Ok(self.empty_input_match());
2574 }
2575 let inner = &mut *self.inner.lock().unwrap_or_else(|e| e.into_inner());
2576 if self.fwd_lb_stripped {
2577 return Err(Error::Algebra(resharp_algebra::ResharpError::UnsupportedPattern))
2579 }
2580 Ok(inner.fwd.scan_fwd_optional(&mut inner.b, 0, input)?.map(|end| Match { start: 0, end }))
2581 }
2582
2583 pub fn is_full_match(&self, input: &[u8]) -> Result<bool, Error> {
2588 Ok(self
2589 .find_anchored(input)?
2590 .is_some_and(|m| m.end == input.len()))
2591 }
2592
2593 pub(crate) fn find_end_anchored(&self, input: &[u8]) -> Result<Option<Match>, Error> {
2597 debug_assert!(!input.is_empty());
2598 let len = input.len();
2599 let inner = &mut *self.inner.lock().unwrap_or_else(|e| e.into_inner());
2600 let RegexInner { b, rev, .. } = &mut *inner;
2601 let rev_dfa = rev.as_mut().expect(
2602 "find_end_anchored requires the _*-free reverse DFA, built whenever rev_end_anchored holds (the same condition that selects FindAll::EndAnchored)",
2603 );
2604 Ok(rev_dfa
2605 .scan_rev_from(b, len, 0, input)?
2606 .map(|start| Match { start, end: len }))
2607 }
2608
2609}
2610