Strategy for Pre {
self.search(cache, input).map(|m| HalfMatch::new(m.pattern(), m.end()))
}
+ fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
+ self.search(cache, input).is_some()
+ }
+
fn search_slots(
&self,
cache: &mut Cache,
@@ -452,7 +458,7 @@ impl Core {
.utf8(info.config().get_utf8_empty())
.nfa_size_limit(info.config().get_nfa_size_limit())
.shrink(false)
- .captures(true)
+ .which_captures(info.config().get_which_captures())
.look_matcher(lookm);
let nfa = thompson::Compiler::new()
.configure(thompson_config.clone())
@@ -499,7 +505,10 @@ impl Core {
// useful with capturing groups in reverse. And of course,
// the lazy DFA ignores capturing groups in all cases.
.configure(
- thompson_config.clone().captures(false).reverse(true),
+ thompson_config
+ .clone()
+ .which_captures(WhichCaptures::None)
+ .reverse(true),
)
.build_many_from_hir(hirs)
.map_err(BuildError::nfa)?;
@@ -620,6 +629,29 @@ impl Core {
}
}
+ fn is_match_nofail(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
+ if let Some(ref e) = self.onepass.get(input) {
+ trace!(
+ "using OnePass for is-match search at {:?}",
+ input.get_span()
+ );
+ e.search_slots(&mut cache.onepass, input, &mut []).is_some()
+ } else if let Some(ref e) = self.backtrack.get(input) {
+ trace!(
+ "using BoundedBacktracker for is-match search at {:?}",
+ input.get_span()
+ );
+ e.is_match(&mut cache.backtrack, input)
+ } else {
+ trace!(
+ "using PikeVM for is-match search at {:?}",
+ input.get_span()
+ );
+ let e = self.pikevm.get();
+ e.is_match(&mut cache.pikevm, input)
+ }
+ }
+
fn is_capture_search_needed(&self, slots_len: usize) -> bool {
slots_len > self.nfa.group_info().implicit_slot_len()
}
@@ -700,7 +732,7 @@ impl Strategy for Core {
// The main difference with 'search' is that if we're using a DFA, we
// can use a single forward scan without needing to run the reverse
// DFA.
- return if let Some(e) = self.dfa.get(input) {
+ if let Some(e) = self.dfa.get(input) {
trace!("using full DFA for half search at {:?}", input.get_span());
match e.try_search_half_fwd(input) {
Ok(x) => x,
@@ -720,7 +752,38 @@ impl Strategy for Core {
}
} else {
self.search_half_nofail(cache, input)
- };
+ }
+ }
+
+ #[cfg_attr(feature = "perf-inline", inline(always))]
+ fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
+ if let Some(e) = self.dfa.get(input) {
+ trace!(
+ "using full DFA for is-match search at {:?}",
+ input.get_span()
+ );
+ match e.try_search_half_fwd(input) {
+ Ok(x) => x.is_some(),
+ Err(_err) => {
+ trace!("full DFA half search failed: {}", _err);
+ self.is_match_nofail(cache, input)
+ }
+ }
+ } else if let Some(e) = self.hybrid.get(input) {
+ trace!(
+ "using lazy DFA for is-match search at {:?}",
+ input.get_span()
+ );
+ match e.try_search_half_fwd(&mut cache.hybrid, input) {
+ Ok(x) => x.is_some(),
+ Err(_err) => {
+ trace!("lazy DFA half search failed: {}", _err);
+ self.is_match_nofail(cache, input)
+ }
+ }
+ } else {
+ self.is_match_nofail(cache, input)
+ }
}
#[cfg_attr(feature = "perf-inline", inline(always))]
@@ -980,6 +1043,21 @@ impl Strategy for ReverseAnchored {
}
}
+ #[cfg_attr(feature = "perf-inline", inline(always))]
+ fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
+ if input.get_anchored().is_anchored() {
+ return self.core.is_match(cache, input);
+ }
+ match self.try_search_half_anchored_rev(cache, input) {
+ Err(_err) => {
+ trace!("fast reverse anchored search failed: {}", _err);
+ self.core.is_match_nofail(cache, input)
+ }
+ Ok(None) => false,
+ Ok(Some(_)) => true,
+ }
+ }
+
#[cfg_attr(feature = "perf-inline", inline(always))]
fn search_slots(
&self,
@@ -1332,6 +1410,28 @@ impl Strategy for ReverseSuffix {
}
}
+ #[cfg_attr(feature = "perf-inline", inline(always))]
+ fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
+ if input.get_anchored().is_anchored() {
+ return self.core.is_match(cache, input);
+ }
+ match self.try_search_half_start(cache, input) {
+ Err(RetryError::Quadratic(_err)) => {
+ trace!("reverse suffix half optimization failed: {}", _err);
+ self.core.is_match_nofail(cache, input)
+ }
+ Err(RetryError::Fail(_err)) => {
+ trace!(
+ "reverse suffix reverse fast half search failed: {}",
+ _err
+ );
+ self.core.is_match_nofail(cache, input)
+ }
+ Ok(None) => false,
+ Ok(Some(_)) => true,
+ }
+ }
+
#[cfg_attr(feature = "perf-inline", inline(always))]
fn search_slots(
&self,
@@ -1480,7 +1580,7 @@ impl ReverseInner {
.utf8(core.info.config().get_utf8_empty())
.nfa_size_limit(core.info.config().get_nfa_size_limit())
.shrink(false)
- .captures(false)
+ .which_captures(WhichCaptures::None)
.look_matcher(lookm);
let result = thompson::Compiler::new()
.configure(thompson_config)
@@ -1714,6 +1814,25 @@ impl Strategy for ReverseInner {
}
}
+ #[cfg_attr(feature = "perf-inline", inline(always))]
+ fn is_match(&self, cache: &mut Cache, input: &Input<'_>) -> bool {
+ if input.get_anchored().is_anchored() {
+ return self.core.is_match(cache, input);
+ }
+ match self.try_search_full(cache, input) {
+ Err(RetryError::Quadratic(_err)) => {
+ trace!("reverse inner half optimization failed: {}", _err);
+ self.core.is_match_nofail(cache, input)
+ }
+ Err(RetryError::Fail(_err)) => {
+ trace!("reverse inner fast half search failed: {}", _err);
+ self.core.is_match_nofail(cache, input)
+ }
+ Ok(None) => false,
+ Ok(Some(_)) => true,
+ }
+ }
+
#[cfg_attr(feature = "perf-inline", inline(always))]
fn search_slots(
&self,
diff --git a/regex-automata/src/meta/wrappers.rs b/regex-automata/src/meta/wrappers.rs
index 8f58363a1..08110d9bb 100644
--- a/regex-automata/src/meta/wrappers.rs
+++ b/regex-automata/src/meta/wrappers.rs
@@ -87,6 +87,15 @@ impl PikeVMEngine {
Ok(PikeVMEngine(engine))
}
+ #[cfg_attr(feature = "perf-inline", inline(always))]
+ pub(crate) fn is_match(
+ &self,
+ cache: &mut PikeVMCache,
+ input: &Input<'_>,
+ ) -> bool {
+ self.0.is_match(cache.0.as_mut().unwrap(), input.clone())
+ }
+
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn search_slots(
&self,
@@ -212,6 +221,29 @@ impl BoundedBacktrackerEngine {
}
}
+ #[cfg_attr(feature = "perf-inline", inline(always))]
+ pub(crate) fn is_match(
+ &self,
+ cache: &mut BoundedBacktrackerCache,
+ input: &Input<'_>,
+ ) -> bool {
+ #[cfg(feature = "nfa-backtrack")]
+ {
+ // OK because we only permit access to this engine when we know
+ // the haystack is short enough for the backtracker to run without
+ // reporting an error.
+ self.0
+ .try_is_match(cache.0.as_mut().unwrap(), input.clone())
+ .unwrap()
+ }
+ #[cfg(not(feature = "nfa-backtrack"))]
+ {
+ // Impossible to reach because this engine is never constructed
+ // if the requisite features aren't enabled.
+ unreachable!()
+ }
+ }
+
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn search_slots(
&self,
diff --git a/regex-automata/src/nfa/thompson/backtrack.rs b/regex-automata/src/nfa/thompson/backtrack.rs
index 75b6c096b..eba037c1d 100644
--- a/regex-automata/src/nfa/thompson/backtrack.rs
+++ b/regex-automata/src/nfa/thompson/backtrack.rs
@@ -19,7 +19,7 @@ use crate::{
empty, iter,
prefilter::Prefilter,
primitives::{NonMaxUsize, PatternID, SmallIndex, StateID},
- search::{Anchored, Input, Match, MatchError, Span},
+ search::{Anchored, HalfMatch, Input, Match, MatchError, Span},
},
};
@@ -300,15 +300,6 @@ impl Builder {
&self,
nfa: NFA,
) -> Result {
- // If the NFA has no captures, then the backtracker doesn't work since
- // it relies on them in order to report match locations. However, in
- // the special case of an NFA with no patterns, it is allowed, since
- // no matches can ever be produced. And importantly, an NFA with no
- // patterns has no capturing groups anyway, so this is necessary to
- // permit the backtracker to work with regexes with zero patterns.
- if !nfa.has_capture() && nfa.pattern_len() > 0 {
- return Err(BuildError::missing_captures());
- }
nfa.look_set_any().available().map_err(BuildError::word)?;
Ok(BoundedBacktracker { config: self.config.clone(), nfa })
}
@@ -954,8 +945,14 @@ impl BoundedBacktracker {
None => return Ok(None),
Some(pid) => pid,
};
- let start = slots[0].unwrap().get();
- let end = slots[1].unwrap().get();
+ let start = match slots[0] {
+ None => return Ok(None),
+ Some(s) => s.get(),
+ };
+ let end = match slots[1] {
+ None => return Ok(None),
+ Some(s) => s.get(),
+ };
return Ok(Some(Match::new(pid, Span { start, end })));
}
let ginfo = self.get_nfa().group_info();
@@ -965,8 +962,14 @@ impl BoundedBacktracker {
None => return Ok(None),
Some(pid) => pid,
};
- let start = slots[pid.as_usize() * 2].unwrap().get();
- let end = slots[pid.as_usize() * 2 + 1].unwrap().get();
+ let start = match slots[pid.as_usize() * 2] {
+ None => return Ok(None),
+ Some(s) => s.get(),
+ };
+ let end = match slots[pid.as_usize() * 2 + 1] {
+ None => return Ok(None),
+ Some(s) => s.get(),
+ };
Ok(Some(Match::new(pid, Span { start, end })))
}
@@ -1292,12 +1295,14 @@ impl BoundedBacktracker {
) -> Result