/// Extracts prefix or suffix literal sequences from [`Hir`] expressions. /// /// Literal extraction is based on the following observations: /// /// * Many regexes start with one or a small number of literals. /// * Substring search for literals is often much faster (sometimes by an order /// of magnitude) than a regex search. /// /// Thus, in many cases, one can search for literals to find candidate starting /// locations of a match, and then only run the full regex engine at each such /// location instead of over the full haystack. /// /// The main downside of literal extraction is that it can wind up causing a /// search to be slower overall. For example, if there are many matches or if /// there are many candidates that don't ultimately lead to a match, then a /// lot of overhead will be spent in shuffing back-and-forth between substring /// search and the regex engine. This is the fundamental reason why literal /// optimizations for regex patterns is sometimes considered a "black art." /// /// # Look-around assertions /// /// Literal extraction treats all look-around assertions as-if they match every /// empty string. So for example, the regex `\bquux\b` will yield a sequence /// containing a single exact literal `quux`. However, not all occurrences /// of `quux` correspond to a match a of the regex. For example, `\bquux\b` /// does not match `ZquuxZ` anywhere because `quux` does not fall on a word /// boundary. /// /// In effect, if your regex contains look-around assertions, then a match of /// an exact literal does not necessarily mean the regex overall matches. So /// you may still need to run the regex engine in such cases to confirm the /// match. /// /// The precise guarantee you get from a literal sequence is: if every literal /// in the sequence is exact and the original regex contains zero look-around /// assertions, then a preference-order multi-substring search of those /// literals will precisely match a preference-order search of the original /// regex. /// /// # Example /// /// This shows how to extract prefixes: /// /// ``` /// use regex_syntax::{hir::literal::{Extractor, Literal, Seq}, parse}; /// /// let hir = parse(r"(a|b|c)(x|y|z)[A-Z]+foo")?; /// let got = Extractor::new().extract(&hir); /// // All literals returned are "inexact" because none of them reach the /// // match state. /// let expected = Seq::from_iter([ /// Literal::inexact("ax"), /// Literal::inexact("ay"), /// Literal::inexact("az"), /// Literal::inexact("bx"), /// Literal::inexact("by"), /// Literal::inexact("bz"), /// Literal::inexact("cx"), /// Literal::inexact("cy"), /// Literal::inexact("cz"), /// ]); /// assert_eq!(expected, got); /// /// # Ok::<(), Box<dyn std::error::Error>>(()) /// ``` /// /// This shows how to extract suffixes: /// /// ``` /// use regex_syntax::{ /// hir::literal::{Extractor, ExtractKind, Literal, Seq}, /// parse, /// }; /// /// let hir = parse(r"foo|[A-Z]+bar")?; /// let got = Extractor::new().kind(ExtractKind::Suffix).extract(&hir); /// // Since 'foo' gets to a match state, it is considered exact. But 'bar' /// // does not because of the '[A-Z]+', and thus is marked inexact. /// let expected = Seq::from_iter([ /// Literal::exact("foo"), /// Literal::inexact("bar"), /// ]); /// assert_eq!(expected, got); /// /// # Ok::<(), Box<dyn std::error::Error>>(()) /// ``` #[derive(Clone, Debug)] pubstruct Extractor {
kind: ExtractKind,
limit_class: usize,
limit_repeat: usize,
limit_literal_len: usize,
limit_total: usize,
}
impl Extractor { /// Create a new extractor with a default configuration. /// /// The extractor can be optionally configured before calling /// [`Extractor::extract`] to get a literal sequence. pubfn new() -> Extractor {
Extractor {
kind: ExtractKind::Prefix,
limit_class: 10,
limit_repeat: 10,
limit_literal_len: 100,
limit_total: 250,
}
}
/// Execute the extractor and return a sequence of literals. pubfn extract(&self, hir: &Hir) -> Seq { usecrate::hir::HirKind::*;
match *hir.kind() {
Empty | Look(_) => Seq::singleton(self::Literal::exact(vec![])),
Literal(hir::Literal(ref bytes)) => { letmut seq =
Seq::singleton(self::Literal::exact(bytes.to_vec())); self.enforce_literal_len(&mut seq);
seq
}
Class(hir::Class::Unicode(ref cls)) => { self.extract_class_unicode(cls)
}
Class(hir::Class::Bytes(ref cls)) => self.extract_class_bytes(cls),
Repetition(ref rep) => self.extract_repetition(rep),
Capture(hir::Capture { ref sub, .. }) => self.extract(sub),
Concat(ref hirs) => matchself.kind {
ExtractKind::Prefix => self.extract_concat(hirs.iter()),
ExtractKind::Suffix => self.extract_concat(hirs.iter().rev()),
},
Alternation(ref hirs) => { // Unlike concat, we always union starting from the beginning, // since the beginning corresponds to the highest preference, // which doesn't change based on forwards vs reverse. self.extract_alternation(hirs.iter())
}
}
}
/// Set the kind of literal sequence to extract from an [`Hir`] expression. /// /// The default is to extract prefixes, but suffixes can be selected /// instead. The contract for prefixes is that every match of the /// corresponding `Hir` must start with one of the literals in the sequence /// returned. Moreover, the _order_ of the sequence returned corresponds to /// the preference order. /// /// Suffixes satisfy a similar contract in that every match of the /// corresponding `Hir` must end with one of the literals in the sequence /// returned. However, there is no guarantee that the literals are in /// preference order. /// /// Remember that a sequence can be infinite. For example, unless the /// limits are configured to be impractically large, attempting to extract /// prefixes (or suffixes) for the pattern `[A-Z]` will return an infinite /// sequence. Generally speaking, if the sequence returned is infinite, /// then it is presumed to be unwise to do prefix (or suffix) optimizations /// for the pattern. pubfn kind(&mutself, kind: ExtractKind) -> &mut Extractor { self.kind = kind; self
}
/// Configure a limit on the length of the sequence that is permitted for /// a character class. If a character class exceeds this limit, then the /// sequence returned for it is infinite. /// /// This prevents classes like `[A-Z]` or `\pL` from getting turned into /// huge and likely unproductive sequences of literals. /// /// # Example /// /// This example shows how this limit can be lowered to decrease the tolerance /// for character classes being turned into literal sequences. /// /// ``` /// use regex_syntax::{hir::literal::{Extractor, Seq}, parse}; /// /// let hir = parse(r"[0-9]")?; /// /// let got = Extractor::new().extract(&hir); /// let expected = Seq::new([ /// "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", /// ]); /// assert_eq!(expected, got); /// /// // Now let's shrink the limit and see how that changes things. /// let got = Extractor::new().limit_class(4).extract(&hir); /// let expected = Seq::infinite(); /// assert_eq!(expected, got); /// /// # Ok::<(), Box<dyn std::error::Error>>(()) /// ``` pubfn limit_class(&mutself, limit: usize) -> &mut Extractor { self.limit_class = limit; self
}
/// Configure a limit on the total number of repetitions that is permitted /// before literal extraction is stopped. /// /// This is useful for limiting things like `(abcde){50}`, or more /// insidiously, `(?:){1000000000}`. This limit prevents any one single /// repetition from adding too much to a literal sequence. /// /// With this limit set, repetitions that exceed it will be stopped and any /// literals extracted up to that point will be made inexact. /// /// # Example /// /// This shows how to decrease the limit and compares it with the default. /// /// ``` /// use regex_syntax::{hir::literal::{Extractor, Literal, Seq}, parse}; /// /// let hir = parse(r"(abc){8}")?; /// /// let got = Extractor::new().extract(&hir); /// let expected = Seq::new(["abcabcabcabcabcabcabcabc"]); /// assert_eq!(expected, got); /// /// // Now let's shrink the limit and see how that changes things. /// let got = Extractor::new().limit_repeat(4).extract(&hir); /// let expected = Seq::from_iter([ /// Literal::inexact("abcabcabcabc"), /// ]); /// assert_eq!(expected, got); /// /// # Ok::<(), Box<dyn std::error::Error>>(()) /// ``` pubfn limit_repeat(&mutself, limit: usize) -> &mut Extractor { self.limit_repeat = limit; self
}
/// Configure a limit on the maximum length of any literal in a sequence. /// /// This is useful for limiting things like `(abcde){5}{5}{5}{5}`. While /// each repetition or literal in that regex is small, when all the /// repetitions are applied, one ends up with a literal of length `5^4 = /// 625`. /// /// With this limit set, literals that exceed it will be made inexact and /// thus prevented from growing. /// /// # Example /// /// This shows how to decrease the limit and compares it with the default. /// /// ``` /// use regex_syntax::{hir::literal::{Extractor, Literal, Seq}, parse}; /// /// let hir = parse(r"(abc){2}{2}{2}")?; /// /// let got = Extractor::new().extract(&hir); /// let expected = Seq::new(["abcabcabcabcabcabcabcabc"]); /// assert_eq!(expected, got); /// /// // Now let's shrink the limit and see how that changes things. /// let got = Extractor::new().limit_literal_len(14).extract(&hir); /// let expected = Seq::from_iter([ /// Literal::inexact("abcabcabcabcab"), /// ]); /// assert_eq!(expected, got); /// /// # Ok::<(), Box<dyn std::error::Error>>(()) /// ``` pubfn limit_literal_len(&mutself, limit: usize) -> &mut Extractor { self.limit_literal_len = limit; self
}
/// Configure a limit on the total number of literals that will be /// returned. /// /// This is useful as a practical measure for avoiding the creation of /// large sequences of literals. While the extractor will automatically /// handle local creations of large sequences (for example, `[A-Z]` yields /// an infinite sequence by default), large sequences can be created /// through non-local means as well. /// /// For example, `[ab]{3}{3}` would yield a sequence of length `512 = 2^9` /// despite each of the repetitions being small on their own. This limit /// thus represents a "catch all" for avoiding locally small sequences from /// combining into large sequences. /// /// # Example /// /// This example shows how reducing the limit will change the literal /// sequence returned. /// /// ``` /// use regex_syntax::{hir::literal::{Extractor, Literal, Seq}, parse}; /// /// let hir = parse(r"[ab]{2}{2}")?; /// /// let got = Extractor::new().extract(&hir); /// let expected = Seq::new([ /// "aaaa", "aaab", "aaba", "aabb", /// "abaa", "abab", "abba", "abbb", /// "baaa", "baab", "baba", "babb", /// "bbaa", "bbab", "bbba", "bbbb", /// ]); /// assert_eq!(expected, got); /// /// // The default limit is not too big, but big enough to extract all /// // literals from '[ab]{2}{2}'. If we shrink the limit to less than 16, /// // then we'll get a truncated set. Notice that it returns a sequence of /// // length 4 even though our limit was 10. This is because the sequence /// // is difficult to increase without blowing the limit. Notice also /// // that every literal in the sequence is now inexact because they were /// // stripped of some suffix. /// let got = Extractor::new().limit_total(10).extract(&hir); /// let expected = Seq::from_iter([ /// Literal::inexact("aa"), /// Literal::inexact("ab"), /// Literal::inexact("ba"), /// Literal::inexact("bb"), /// ]); /// assert_eq!(expected, got); /// /// # Ok::<(), Box<dyn std::error::Error>>(()) /// ``` pubfn limit_total(&mutself, limit: usize) -> &mut Extractor { self.limit_total = limit; self
}
/// Extract a sequence from the given concatenation. Sequences from each of /// the child HIR expressions are combined via cross product. /// /// This short circuits once the cross product turns into a sequence /// containing only inexact literals. fn extract_concat<'a, I: Iterator<Item = &'a Hir>>(&self, it: I) -> Seq { letmut seq = Seq::singleton(self::Literal::exact(vec![])); for hir in it { // If every element in the sequence is inexact, then a cross // product will always be a no-op. Thus, there is nothing else we // can add to it and can quit early. Note that this also includes // infinite sequences. if seq.is_inexact() { break;
} // Note that 'cross' also dispatches based on whether we're // extracting prefixes or suffixes.
seq = self.cross(seq, &mutself.extract(hir));
}
seq
}
/// Extract a sequence from the given alternation. /// /// This short circuits once the union turns into an infinite sequence. fn extract_alternation<'a, I: Iterator<Item = &'a Hir>>(
&self,
it: I,
) -> Seq { letmut seq = Seq::empty(); for hir in it { // Once our 'seq' is infinite, every subsequent union // operation on it will itself always result in an // infinite sequence. Thus, it can never change and we can // short-circuit. if !seq.is_finite() { break;
}
seq = self.union(seq, &mutself.extract(hir));
}
seq
}
/// Extract a sequence of literals from the given repetition. We do our /// best, Some examples: /// /// 'a*' => [inexact(a), exact("")] /// 'a*?' => [exact(""), inexact(a)] /// 'a+' => [inexact(a)] /// 'a{3}' => [exact(aaa)] /// 'a{3,5} => [inexact(aaa)] /// /// The key here really is making sure we get the 'inexact' vs 'exact' /// attributes correct on each of the literals we add. For example, the /// fact that 'a*' gives us an inexact 'a' and an exact empty string means /// that a regex like 'ab*c' will result in [inexact(ab), exact(ac)] /// literals being extracted, which might actually be a better prefilter /// than just 'a'. fn extract_repetition(&self, rep: &hir::Repetition) -> Seq { letmut subseq = self.extract(&rep.sub); match *rep {
hir::Repetition { min: 0, max, greedy, .. } => { // When 'max=1', we can retain exactness, since 'a?' is // equivalent to 'a|'. Similarly below, 'a??' is equivalent to // '|a'. if max != Some(1) {
subseq.make_inexact();
} letmut empty = Seq::singleton(Literal::exact(vec![])); if !greedy {
mem::swap(&mut subseq, &mut empty);
} self.union(subseq, &mut empty)
}
hir::Repetition { min, max: Some(max), .. } if min == max => {
assert!(min > 0); // handled above let limit =
u32::try_from(self.limit_repeat).unwrap_or(u32::MAX); letmut seq = Seq::singleton(Literal::exact(vec![])); for _ in0..cmp::min(min, limit) { if seq.is_inexact() { break;
}
seq = self.cross(seq, &mut subseq.clone());
} if usize::try_from(min).is_err() || min > limit {
seq.make_inexact();
}
seq
}
hir::Repetition { min, .. } => {
assert!(min > 0); // handled above let limit =
u32::try_from(self.limit_repeat).unwrap_or(u32::MAX); letmut seq = Seq::singleton(Literal::exact(vec![])); for _ in0..cmp::min(min, limit) { if seq.is_inexact() { break;
}
seq = self.cross(seq, &mut subseq.clone());
}
seq.make_inexact();
seq
}
}
}
/// Convert the given Unicode class into a sequence of literals if the /// class is small enough. If the class is too big, return an infinite /// sequence. fn extract_class_unicode(&self, cls: &hir::ClassUnicode) -> Seq { ifself.class_over_limit_unicode(cls) { return Seq::infinite();
} letmut seq = Seq::empty(); for r in cls.iter() { for ch in r.start()..=r.end() {
seq.push(Literal::from(ch));
}
} self.enforce_literal_len(&mut seq);
seq
}
/// Convert the given byte class into a sequence of literals if the class /// is small enough. If the class is too big, return an infinite sequence. fn extract_class_bytes(&self, cls: &hir::ClassBytes) -> Seq { ifself.class_over_limit_bytes(cls) { return Seq::infinite();
} letmut seq = Seq::empty(); for r in cls.iter() { for b in r.start()..=r.end() {
seq.push(Literal::from(b));
}
} self.enforce_literal_len(&mut seq);
seq
}
/// Returns true if the given Unicode class exceeds the configured limits /// on this extractor. fn class_over_limit_unicode(&self, cls: &hir::ClassUnicode) -> bool { letmut count = 0; for r in cls.iter() { if count > self.limit_class { returntrue;
}
count += r.len();
}
count > self.limit_class
}
/// Returns true if the given byte class exceeds the configured limits on /// this extractor. fn class_over_limit_bytes(&self, cls: &hir::ClassBytes) -> bool { letmut count = 0; for r in cls.iter() { if count > self.limit_class { returntrue;
}
count += r.len();
}
count > self.limit_class
}
/// Compute the cross product of the two sequences if the result would be /// within configured limits. Otherwise, make `seq2` infinite and cross the /// infinite sequence with `seq1`. fn cross(&self, mut seq1: Seq, seq2: &mut Seq) -> Seq { if seq1.max_cross_len(seq2).map_or(false, |len| len > self.limit_total)
{
seq2.make_infinite();
} iflet ExtractKind::Suffix = self.kind {
seq1.cross_reverse(seq2);
} else {
seq1.cross_forward(seq2);
}
assert!(seq1.len().map_or(true, |x| x <= self.limit_total)); self.enforce_literal_len(&mut seq1);
seq1
}
/// Union the two sequences if the result would be within configured /// limits. Otherwise, make `seq2` infinite and union the infinite sequence /// with `seq1`. fn union(&self, mut seq1: Seq, seq2: &mut Seq) -> Seq { if seq1.max_union_len(seq2).map_or(false, |len| len > self.limit_total)
{ // We try to trim our literal sequences to see if we can make // room for more literals. The idea is that we'd rather trim down // literals already in our sequence if it means we can add a few // more and retain a finite sequence. Otherwise, we'll union with // an infinite sequence and that infects everything and effectively // stops literal extraction in its tracks. // // We do we keep 4 bytes here? Well, it's a bit of an abstraction // leakage. Downstream, the literals may wind up getting fed to // the Teddy algorithm, which supports searching literals up to // length 4. So that's why we pick that number here. Arguably this // should be a tuneable parameter, but it seems a little tricky to // describe. And I'm still unsure if this is the right way to go // about culling literal sequences. matchself.kind {
ExtractKind::Prefix => {
seq1.keep_first_bytes(4);
seq2.keep_first_bytes(4);
}
ExtractKind::Suffix => {
seq1.keep_last_bytes(4);
seq2.keep_last_bytes(4);
}
}
seq1.dedup();
seq2.dedup(); if seq1
.max_union_len(seq2)
.map_or(false, |len| len > self.limit_total)
{
seq2.make_infinite();
}
}
seq1.union(seq2);
assert!(seq1.len().map_or(true, |x| x <= self.limit_total));
seq1
}
/// Applies the literal length limit to the given sequence. If none of the /// literals in the sequence exceed the limit, then this is a no-op. fn enforce_literal_len(&self, seq: &mutSeq) { let len = self.limit_literal_len; matchself.kind {
ExtractKind::Prefix => seq.keep_first_bytes(len),
ExtractKind::Suffix => seq.keep_last_bytes(len),
}
}
}
/// The kind of literals to extract from an [`Hir`] expression. /// /// The default extraction kind is `Prefix`. #[non_exhaustive] #[derive(Clone, Debug)] pubenum ExtractKind { /// Extracts only prefix literals from a regex.
Prefix, /// Extracts only suffix literals from a regex. /// /// Note that the sequence returned by suffix literals currently may /// not correctly represent leftmost-first or "preference" order match /// semantics.
Suffix,
}
impl ExtractKind { /// Returns true if this kind is the `Prefix` variant. pubfn is_prefix(&self) -> bool {
matches!(*self, ExtractKind::Prefix)
}
/// Returns true if this kind is the `Suffix` variant. pubfn is_suffix(&self) -> bool {
matches!(*self, ExtractKind::Suffix)
}
}
/// A sequence of literals. /// /// A `Seq` is very much like a set in that it represents a union of its /// members. That is, it corresponds to a set of literals where at least one /// must match in order for a particular [`Hir`] expression to match. (Whether /// this corresponds to the entire `Hir` expression, a prefix of it or a suffix /// of it depends on how the `Seq` was extracted from the `Hir`.) /// /// It is also unlike a set in that multiple identical literals may appear, /// and that the order of the literals in the `Seq` matters. For example, if /// the sequence is `[sam, samwise]` and leftmost-first matching is used, then /// `samwise` can never match and the sequence is equivalent to `[sam]`. /// /// # States of a sequence /// /// A `Seq` has a few different logical states to consider: /// /// * The sequence can represent "any" literal. When this happens, the set does /// not have a finite size. The purpose of this state is to inhibit callers /// from making assumptions about what literals are required in order to match /// a particular [`Hir`] expression. Generally speaking, when a set is in this /// state, literal optimizations are inhibited. A good example of a regex that /// will cause this sort of set to appear is `[A-Za-z]`. The character class /// is just too big (and also too narrow) to be usefully expanded into 52 /// different literals. (Note that the decision for when a seq should become /// infinite is determined by the caller. A seq itself has no hard-coded /// limits.) /// * The sequence can be empty, in which case, it is an affirmative statement /// that there are no literals that can match the corresponding `Hir`. /// Consequently, the `Hir` never matches any input. For example, `[a&&b]`. /// * The sequence can be non-empty, in which case, at least one of the /// literals must match in order for the corresponding `Hir` to match. /// /// # Example /// /// This example shows how literal sequences can be simplified by stripping /// suffixes and minimizing while maintaining preference order. /// /// ``` /// use regex_syntax::hir::literal::{Literal, Seq}; /// /// let mut seq = Seq::new(&[ /// "farm", /// "appliance", /// "faraway", /// "apple", /// "fare", /// "gap", /// "applicant", /// "applaud", /// ]); /// seq.keep_first_bytes(3); /// seq.minimize_by_preference(); /// // Notice that 'far' comes before 'app', which matches the order in the /// // original sequence. This guarantees that leftmost-first semantics are /// // not altered by simplifying the set. /// let expected = Seq::from_iter([ /// Literal::inexact("far"), /// Literal::inexact("app"), /// Literal::exact("gap"), /// ]); /// assert_eq!(expected, seq); /// ``` #[derive(Clone, Eq, PartialEq)] pubstruct Seq { /// The members of this seq. /// /// When `None`, the seq represents all possible literals. That is, it /// prevents one from making assumptions about specific literals in the /// seq, and forces one to treat it as if any literal might be in the seq. /// /// Note that `Some(vec![])` is valid and corresponds to the empty seq of /// literals, i.e., a regex that can never match. For example, `[a&&b]`. /// It is distinct from `Some(vec![""])`, which corresponds to the seq /// containing an empty string, which matches at every position.
literals: Option<Vec<Literal>>,
}
impl Seq { /// Returns an empty sequence. /// /// An empty sequence matches zero literals, and thus corresponds to a /// regex that itself can never match. #[inline] pubfn empty() -> Seq {
Seq { literals: Some(vec![]) }
}
/// Returns a sequence of literals without a finite size and may contain /// any literal. /// /// A sequence without finite size does not reveal anything about the /// characteristics of the literals in its set. There are no fixed prefixes /// or suffixes, nor are lower or upper bounds on the length of the literals /// in the set known. /// /// This is useful to represent constructs in a regex that are "too big" /// to useful represent as a sequence of literals. For example, `[A-Za-z]`. /// When sequences get too big, they lose their discriminating nature and /// are more likely to produce false positives, which in turn makes them /// less likely to speed up searches. /// /// More pragmatically, for many regexes, enumerating all possible literals /// is itself not possible or might otherwise use too many resources. So /// constraining the size of sets during extraction is a practical trade /// off to make. #[inline] pubfn infinite() -> Seq {
Seq { literals: None }
}
/// Returns a sequence containing a single literal. #[inline] pubfn singleton(lit: Literal) -> Seq {
Seq { literals: Some(vec![lit]) }
}
/// Returns a sequence of exact literals from the given byte strings. #[inline] pubfn new<I, B>(it: I) -> Seq where
I: IntoIterator<Item = B>,
B: AsRef<[u8]>,
{
it.into_iter().map(|b| Literal::exact(b.as_ref())).collect()
}
/// If this is a finite sequence, return its members as a slice of /// literals. /// /// The slice returned may be empty, in which case, there are no literals /// that can match this sequence. #[inline] pubfn literals(&self) -> Option<&[Literal]> { self.literals.as_deref()
}
/// Push a literal to the end of this sequence. /// /// If this sequence is not finite, then this is a no-op. /// /// Similarly, if the most recently added item of this sequence is /// equivalent to the literal given, then it is not added. This reflects /// a `Seq`'s "set like" behavior, and represents a practical trade off. /// Namely, there is never any need to have two adjacent and equivalent /// literals in the same sequence, _and_ it is easy to detect in some /// cases. #[inline] pubfn push(&mutself, lit: Literal) { let lits = matchself.literals {
None => return,
Some(refmut lits) => lits,
}; if lits.last().map_or(false, |m| m == &lit) { return;
}
lits.push(lit);
}
/// Make all of the literals in this sequence inexact. /// /// This is a no-op if this sequence is not finite. #[inline] pubfn make_inexact(&mutself) { let lits = matchself.literals {
None => return,
Some(refmut lits) => lits,
}; for lit in lits.iter_mut() {
lit.make_inexact();
}
}
/// Converts this sequence to an infinite sequence. /// /// This is a no-op if the sequence is already infinite. #[inline] pubfn make_infinite(&mutself) { self.literals = None;
}
/// Modify this sequence to contain the cross product between it and the /// sequence given. /// /// The cross product only considers literals in this sequence that are /// exact. That is, inexact literals are not extended. /// /// The literals are always drained from `other`, even if none are used. /// This permits callers to reuse the sequence allocation elsewhere. /// /// If this sequence is infinite, then this is a no-op, regardless of what /// `other` contains (and in this case, the literals are still drained from /// `other`). If `other` is infinite and this sequence is finite, then this /// is a no-op, unless this sequence contains a zero-length literal. In /// which case, the infiniteness of `other` infects this sequence, and this /// sequence is itself made infinite. /// /// Like [`Seq::union`], this may attempt to deduplicate literals. See /// [`Seq::dedup`] for how deduplication deals with exact and inexact /// literals. /// /// # Example /// /// This example shows basic usage and how exact and inexact literals /// interact. /// /// ``` /// use regex_syntax::hir::literal::{Literal, Seq}; /// /// let mut seq1 = Seq::from_iter([ /// Literal::exact("foo"), /// Literal::inexact("bar"), /// ]); /// let mut seq2 = Seq::from_iter([ /// Literal::inexact("quux"), /// Literal::exact("baz"), /// ]); /// seq1.cross_forward(&mut seq2); /// /// // The literals are pulled out of seq2. /// assert_eq!(Some(0), seq2.len()); /// /// let expected = Seq::from_iter([ /// Literal::inexact("fooquux"), /// Literal::exact("foobaz"), /// Literal::inexact("bar"), /// ]); /// assert_eq!(expected, seq1); /// ``` /// /// This example shows the behavior of when `other` is an infinite /// sequence. /// /// ``` /// use regex_syntax::hir::literal::{Literal, Seq}; /// /// let mut seq1 = Seq::from_iter([ /// Literal::exact("foo"), /// Literal::inexact("bar"), /// ]); /// let mut seq2 = Seq::infinite(); /// seq1.cross_forward(&mut seq2); /// /// // When seq2 is infinite, cross product doesn't add anything, but /// // ensures all members of seq1 are inexact. /// let expected = Seq::from_iter([ /// Literal::inexact("foo"), /// Literal::inexact("bar"), /// ]); /// assert_eq!(expected, seq1); /// ``` /// /// This example is like the one above, but shows what happens when this /// sequence contains an empty string. In this case, an infinite `other` /// sequence infects this sequence (because the empty string means that /// there are no finite prefixes): /// /// ``` /// use regex_syntax::hir::literal::{Literal, Seq}; /// /// let mut seq1 = Seq::from_iter([ /// Literal::exact("foo"), /// Literal::exact(""), // inexact provokes same behavior /// Literal::inexact("bar"), /// ]); /// let mut seq2 = Seq::infinite(); /// seq1.cross_forward(&mut seq2); /// /// // seq1 is now infinite! /// assert!(!seq1.is_finite()); /// ``` /// /// This example shows the behavior of this sequence is infinite. /// /// ``` /// use regex_syntax::hir::literal::{Literal, Seq}; /// /// let mut seq1 = Seq::infinite(); /// let mut seq2 = Seq::from_iter([ /// Literal::exact("foo"), /// Literal::inexact("bar"), /// ]); /// seq1.cross_forward(&mut seq2); /// /// // seq1 remains unchanged. /// assert!(!seq1.is_finite()); /// // Even though the literals in seq2 weren't used, it was still drained. /// assert_eq!(Some(0), seq2.len()); /// ``` #[inline] pubfn cross_forward(&mutself, other: &mut Seq) { let (lits1, lits2) = matchself.cross_preamble(other) {
None => return,
Some((lits1, lits2)) => (lits1, lits2),
}; let newcap = lits1.len().saturating_mul(lits2.len()); for selflit in mem::replace(lits1, Vec::with_capacity(newcap)) { if !selflit.is_exact() {
lits1.push(selflit); continue;
} for otherlit in lits2.iter() { letmut newlit = Literal::exact(Vec::with_capacity(
selflit.len() + otherlit.len(),
));
newlit.extend(&selflit);
newlit.extend(&otherlit); if !otherlit.is_exact() {
newlit.make_inexact();
}
lits1.push(newlit);
}
}
lits2.drain(..); self.dedup();
}
/// Modify this sequence to contain the cross product between it and /// the sequence given, where the sequences are treated as suffixes /// instead of prefixes. Namely, the sequence `other` is *prepended* /// to `self` (as opposed to `other` being *appended* to `self` in /// [`Seq::cross_forward`]). /// /// The cross product only considers literals in this sequence that are /// exact. That is, inexact literals are not extended. /// /// The literals are always drained from `other`, even if none are used. /// This permits callers to reuse the sequence allocation elsewhere. /// /// If this sequence is infinite, then this is a no-op, regardless of what /// `other` contains (and in this case, the literals are still drained from /// `other`). If `other` is infinite and this sequence is finite, then this /// is a no-op, unless this sequence contains a zero-length literal. In /// which case, the infiniteness of `other` infects this sequence, and this /// sequence is itself made infinite. /// /// Like [`Seq::union`], this may attempt to deduplicate literals. See /// [`Seq::dedup`] for how deduplication deals with exact and inexact /// literals. /// /// # Example /// /// This example shows basic usage and how exact and inexact literals /// interact. /// /// ``` /// use regex_syntax::hir::literal::{Literal, Seq}; /// /// let mut seq1 = Seq::from_iter([ /// Literal::exact("foo"), /// Literal::inexact("bar"), /// ]); /// let mut seq2 = Seq::from_iter([ /// Literal::inexact("quux"), /// Literal::exact("baz"), /// ]); /// seq1.cross_reverse(&mut seq2); /// /// // The literals are pulled out of seq2. /// assert_eq!(Some(0), seq2.len()); /// /// let expected = Seq::from_iter([ /// Literal::inexact("quuxfoo"), /// Literal::inexact("bar"), /// Literal::exact("bazfoo"), /// ]); /// assert_eq!(expected, seq1); /// ``` /// /// This example shows the behavior of when `other` is an infinite /// sequence. /// /// ``` /// use regex_syntax::hir::literal::{Literal, Seq}; /// /// let mut seq1 = Seq::from_iter([ /// Literal::exact("foo"), /// Literal::inexact("bar"), /// ]); /// let mut seq2 = Seq::infinite(); /// seq1.cross_reverse(&mut seq2); /// /// // When seq2 is infinite, cross product doesn't add anything, but /// // ensures all members of seq1 are inexact. /// let expected = Seq::from_iter([ /// Literal::inexact("foo"), /// Literal::inexact("bar"), /// ]); /// assert_eq!(expected, seq1); /// ``` /// /// This example is like the one above, but shows what happens when this /// sequence contains an empty string. In this case, an infinite `other` /// sequence infects this sequence (because the empty string means that /// there are no finite suffixes): /// /// ``` /// use regex_syntax::hir::literal::{Literal, Seq}; /// /// let mut seq1 = Seq::from_iter([ /// Literal::exact("foo"), /// Literal::exact(""), // inexact provokes same behavior /// Literal::inexact("bar"), /// ]); /// let mut seq2 = Seq::infinite(); /// seq1.cross_reverse(&mut seq2); /// /// // seq1 is now infinite! /// assert!(!seq1.is_finite()); /// ``` /// /// This example shows the behavior when this sequence is infinite. /// /// ``` /// use regex_syntax::hir::literal::{Literal, Seq}; /// /// let mut seq1 = Seq::infinite(); /// let mut seq2 = Seq::from_iter([ /// Literal::exact("foo"), /// Literal::inexact("bar"), /// ]); /// seq1.cross_reverse(&mut seq2); /// /// // seq1 remains unchanged. /// assert!(!seq1.is_finite()); /// // Even though the literals in seq2 weren't used, it was still drained. /// assert_eq!(Some(0), seq2.len()); /// ``` #[inline] pubfn cross_reverse(&mutself, other: &mut Seq) { let (lits1, lits2) = matchself.cross_preamble(other) {
None => return,
Some((lits1, lits2)) => (lits1, lits2),
}; // We basically proceed as we do in 'cross_forward' at this point, // except that the outer loop is now 'other' and the inner loop is now // 'self'. That's because 'self' corresponds to suffixes and 'other' // corresponds to the sequence we want to *prepend* to the suffixes. let newcap = lits1.len().saturating_mul(lits2.len()); let selflits = mem::replace(lits1, Vec::with_capacity(newcap)); for (i, otherlit) in lits2.drain(..).enumerate() { for selflit in selflits.iter() { if !selflit.is_exact() { // If the suffix isn't exact, then we can't prepend // anything to it. However, we still want to keep it. But // we only want to keep one of them, to avoid duplication. // (The duplication is okay from a correctness perspective, // but wasteful.) if i == 0 {
lits1.push(selflit.clone());
} continue;
} letmut newlit = Literal::exact(Vec::with_capacity(
otherlit.len() + selflit.len(),
));
newlit.extend(&otherlit);
newlit.extend(&selflit); if !otherlit.is_exact() {
newlit.make_inexact();
}
lits1.push(newlit);
}
} self.dedup();
}
/// A helper function the corresponds to the subtle preamble for both /// `cross_forward` and `cross_reverse`. In effect, it handles the cases /// of infinite sequences for both `self` and `other`, as well as ensuring /// that literals from `other` are drained even if they aren't used. fn cross_preamble<'a>(
&'a mut self,
other: &'a mut Seq,
) -> Option<(&'a mut Vec<Literal>, &'a mut Vec<Literal>)> { let lits2 = match other.literals {
None => { // If our current seq contains the empty string and the seq // we're adding matches any literal, then it follows that the // current seq must now also match any literal. // // Otherwise, we just have to make sure everything in this // sequence is inexact. ifself.min_literal_len() == Some(0) {
*self = Seq::infinite();
} else { self.make_inexact();
} return None;
}
Some(refmut lits) => lits,
}; let lits1 = matchself.literals {
None => { // If we aren't going to make it to the end of this routine // where lits2 is drained, then we need to do it now.
lits2.drain(..); return None;
}
Some(refmut lits) => lits,
};
Some((lits1, lits2))
}
/// Unions the `other` sequence into this one. /// /// The literals are always drained out of the given `other` sequence, /// even if they are being unioned into an infinite sequence. This permits /// the caller to reuse the `other` sequence in another context. /// /// Some literal deduping may be performed. If any deduping happens, /// any leftmost-first or "preference" order match semantics will be /// preserved. /// /// # Example /// /// This example shows basic usage. /// /// ``` /// use regex_syntax::hir::literal::Seq; /// /// let mut seq1 = Seq::new(&["foo", "bar"]); /// let mut seq2 = Seq::new(&["bar", "quux", "foo"]); /// seq1.union(&mut seq2); /// /// // The literals are pulled out of seq2. /// assert_eq!(Some(0), seq2.len()); /// /// // Adjacent literals are deduped, but non-adjacent literals may not be. /// assert_eq!(Seq::new(&["foo", "bar", "quux", "foo"]), seq1); /// ``` /// /// This example shows that literals are drained from `other` even when /// they aren't necessarily used. /// /// ``` /// use regex_syntax::hir::literal::Seq; /// /// let mut seq1 = Seq::infinite(); /// // Infinite sequences have no finite length. /// assert_eq!(None, seq1.len()); /// /// let mut seq2 = Seq::new(&["bar", "quux", "foo"]); /// seq1.union(&mut seq2); /// /// // seq1 is still infinite and seq2 has been drained. /// assert_eq!(None, seq1.len()); /// assert_eq!(Some(0), seq2.len()); /// ``` #[inline] pubfn union(&mutself, other: &mut Seq) { let lits2 = match other.literals {
None => { // Unioning with an infinite sequence always results in an // infinite sequence. self.make_infinite(); return;
}
Some(refmut lits) => lits.drain(..),
}; let lits1 = matchself.literals {
None => return,
Some(refmut lits) => lits,
};
lits1.extend(lits2); self.dedup();
}
/// Unions the `other` sequence into this one by splice the `other` /// sequence at the position of the first zero-length literal. /// /// This is useful for preserving preference order semantics when combining /// two literal sequences. For example, in the regex `(a||f)+foo`, the /// correct preference order prefix sequence is `[a, foo, f]`. /// /// The literals are always drained out of the given `other` sequence, /// even if they are being unioned into an infinite sequence. This permits /// the caller to reuse the `other` sequence in another context. Note that /// the literals are drained even if no union is performed as well, i.e., /// when this sequence does not contain a zero-length literal. /// /// Some literal deduping may be performed. If any deduping happens, /// any leftmost-first or "preference" order match semantics will be /// preserved. /// /// # Example /// /// This example shows basic usage. /// /// ``` /// use regex_syntax::hir::literal::Seq; /// /// let mut seq1 = Seq::new(&["a", "", "f", ""]); /// let mut seq2 = Seq::new(&["foo"]); /// seq1.union_into_empty(&mut seq2); /// /// // The literals are pulled out of seq2. /// assert_eq!(Some(0), seq2.len()); /// // 'foo' gets spliced into seq1 where the first empty string occurs. /// assert_eq!(Seq::new(&["a", "foo", "f"]), seq1); /// ``` /// /// This example shows that literals are drained from `other` even when /// they aren't necessarily used. /// /// ``` /// use regex_syntax::hir::literal::Seq; /// /// let mut seq1 = Seq::new(&["foo", "bar"]); /// let mut seq2 = Seq::new(&["bar", "quux", "foo"]); /// seq1.union_into_empty(&mut seq2); /// /// // seq1 has no zero length literals, so no splicing happens. /// assert_eq!(Seq::new(&["foo", "bar"]), seq1); /// // Even though no splicing happens, seq2 is still drained. /// assert_eq!(Some(0), seq2.len()); /// ``` #[inline] pubfn union_into_empty(&mutself, other: &mut Seq) { let lits2 = other.literals.as_mut().map(|lits| lits.drain(..)); let lits1 = matchself.literals {
None => return,
Some(refmut lits) => lits,
}; let first_empty = match lits1.iter().position(|m| m.is_empty()) {
None => return,
Some(i) => i,
}; let lits2 = match lits2 {
None => { // Note that we are only here if we've found an empty literal, // which implies that an infinite sequence infects this seq and // also turns it into an infinite sequence. self.literals = None; return;
}
Some(lits) => lits,
}; // Clearing out the empties needs to come before the splice because // the splice might add more empties that we don't want to get rid // of. Since we're splicing into the position of the first empty, the // 'first_empty' position computed above is still correct.
lits1.retain(|m| !m.is_empty());
lits1.splice(first_empty..first_empty, lits2); self.dedup();
}
/// Deduplicate adjacent equivalent literals in this sequence. /// /// If adjacent literals are equivalent strings but one is exact and the /// other inexact, the inexact literal is kept and the exact one is /// removed. /// /// Deduping an infinite sequence is a no-op. /// /// # Example /// /// This example shows how literals that are duplicate byte strings but /// are not equivalent with respect to exactness are resolved. /// /// ``` /// use regex_syntax::hir::literal::{Literal, Seq}; /// /// let mut seq = Seq::from_iter([ /// Literal::exact("foo"), /// Literal::inexact("foo"), /// ]); /// seq.dedup(); /// /// assert_eq!(Seq::from_iter([Literal::inexact("foo")]), seq); /// ``` #[inline] pubfn dedup(&mutself) { iflet Some(refmut lits) = self.literals {
lits.dedup_by(|lit1, lit2| { if lit1.as_bytes() != lit2.as_bytes() { returnfalse;
} if lit1.is_exact() != lit2.is_exact() {
lit1.make_inexact();
lit2.make_inexact();
} true
});
}
}
/// Sorts this sequence of literals lexicographically. /// /// Note that if, before sorting, if a literal that is a prefix of another /// literal appears after it, then after sorting, the sequence will not /// represent the same preference order match semantics. For example, /// sorting the sequence `[samwise, sam]` yields the sequence `[sam, /// samwise]`. Under preference order semantics, the latter sequence will /// never match `samwise` where as the first sequence can. /// /// # Example /// /// This example shows basic usage. /// /// ``` /// use regex_syntax::hir::literal::Seq; /// /// let mut seq = Seq::new(&["foo", "quux", "bar"]); /// seq.sort(); /// /// assert_eq!(Seq::new(&["bar", "foo", "quux"]), seq); /// ``` #[inline] pubfn sort(&mutself) { iflet Some(refmut lits) = self.literals {
lits.sort();
}
}
/// Reverses all of the literals in this sequence. /// /// The order of the sequence itself is preserved. /// /// # Example /// /// This example shows basic usage. /// /// ``` /// use regex_syntax::hir::literal::Seq; /// /// let mut seq = Seq::new(&["oof", "rab"]); /// seq.reverse_literals(); /// assert_eq!(Seq::new(&["foo", "bar"]), seq); /// ``` #[inline] pubfn reverse_literals(&mutself) { iflet Some(refmut lits) = self.literals { for lit in lits.iter_mut() {
lit.reverse();
}
}
}
/// Shrinks this seq to its minimal size while respecting the preference /// order of its literals. /// /// While this routine will remove duplicate literals from this seq, it /// will also remove literals that can never match in a leftmost-first or /// "preference order" search. Similar to [`Seq::dedup`], if a literal is /// deduped, then the one that remains is made inexact. /// /// This is a no-op on seqs that are empty or not finite. /// /// # Example /// /// This example shows the difference between `{sam, samwise}` and /// `{samwise, sam}`. /// /// ``` /// use regex_syntax::hir::literal::{Literal, Seq}; /// /// // If 'sam' comes before 'samwise' and a preference order search is /// // executed, then 'samwise' can never match. /// let mut seq = Seq::new(&["sam", "samwise"]); /// seq.minimize_by_preference(); /// assert_eq!(Seq::from_iter([Literal::inexact("sam")]), seq); /// /// // But if they are reversed, then it's possible for 'samwise' to match /// // since it is given higher preference. /// let mut seq = Seq::new(&["samwise", "sam"]); /// seq.minimize_by_preference(); /// assert_eq!(Seq::new(&["samwise", "sam"]), seq); /// ``` /// /// This example shows that if an empty string is in this seq, then /// anything that comes after it can never match. /// /// ``` /// use regex_syntax::hir::literal::{Literal, Seq}; /// /// // An empty string is a prefix of all strings, so it automatically /// // inhibits any subsequent strings from matching. /// let mut seq = Seq::new(&["foo", "bar", "", "quux", "fox"]); /// seq.minimize_by_preference(); /// let expected = Seq::from_iter([ /// Literal::exact("foo"), /// Literal::exact("bar"), /// Literal::inexact(""), /// ]); /// assert_eq!(expected, seq); /// /// // And of course, if it's at the beginning, then it makes it impossible /// // for anything else to match. /// let mut seq = Seq::new(&["", "foo", "quux", "fox"]); /// seq.minimize_by_preference(); /// assert_eq!(Seq::from_iter([Literal::inexact("")]), seq); /// ``` #[inline] pubfn minimize_by_preference(&mutself) { iflet Some(refmut lits) = self.literals {
PreferenceTrie::minimize(lits, false);
}
}
/// Trims all literals in this seq such that only the first `len` bytes /// remain. If a literal has less than or equal to `len` bytes, then it /// remains unchanged. Otherwise, it is trimmed and made inexact. /// /// # Example /// /// ``` /// use regex_syntax::hir::literal::{Literal, Seq}; /// /// let mut seq = Seq::new(&["a", "foo", "quux"]); /// seq.keep_first_bytes(2); /// /// let expected = Seq::from_iter([ /// Literal::exact("a"), /// Literal::inexact("fo"), /// Literal::inexact("qu"), /// ]); /// assert_eq!(expected, seq); /// ``` #[inline] pubfn keep_first_bytes(&mutself, len: usize) { iflet Some(refmut lits) = self.literals { for m in lits.iter_mut() {
m.keep_first_bytes(len);
}
}
}
/// Trims all literals in this seq such that only the last `len` bytes /// remain. If a literal has less than or equal to `len` bytes, then it /// remains unchanged. Otherwise, it is trimmed and made inexact. /// /// # Example /// /// ``` /// use regex_syntax::hir::literal::{Literal, Seq}; /// /// let mut seq = Seq::new(&["a", "foo", "quux"]); /// seq.keep_last_bytes(2); /// /// let expected = Seq::from_iter([ /// Literal::exact("a"), /// Literal::inexact("oo"), /// Literal::inexact("ux"), /// ]); /// assert_eq!(expected, seq); /// ``` #[inline] pubfn keep_last_bytes(&mutself, len: usize) { iflet Some(refmut lits) = self.literals { for m in lits.iter_mut() {
m.keep_last_bytes(len);
}
}
}
/// Returns true if this sequence is finite. /// /// When false, this sequence is infinite and must be treated as if it /// contains every possible literal. #[inline] pubfn is_finite(&self) -> bool { self.literals.is_some()
}
/// Returns true if and only if this sequence is finite and empty. /// /// An empty sequence never matches anything. It can only be produced by /// literal extraction when the corresponding regex itself cannot match. #[inline] pubfn is_empty(&self) -> bool { self.len() == Some(0)
}
/// Returns the number of literals in this sequence if the sequence is /// finite. If the sequence is infinite, then `None` is returned. #[inline] pubfn len(&self) -> Option<usize> { self.literals.as_ref().map(|lits| lits.len())
}
/// Returns true if and only if all literals in this sequence are exact. /// /// This returns false if the sequence is infinite. #[inline] pubfn is_exact(&self) -> bool { self.literals().map_or(false, |lits| lits.iter().all(|x| x.is_exact()))
}
/// Returns true if and only if all literals in this sequence are inexact. /// /// This returns true if the sequence is infinite. #[inline] pubfn is_inexact(&self) -> bool { self.literals().map_or(true, |lits| lits.iter().all(|x| !x.is_exact()))
}
/// Return the maximum length of the sequence that would result from /// unioning `self` with `other`. If either set is infinite, then this /// returns `None`. #[inline] pubfn max_union_len(&self, other: &Seq) -> Option<usize> { let len1 = self.len()?; let len2 = other.len()?;
Some(len1.saturating_add(len2))
}
/// Return the maximum length of the sequence that would result from the /// cross product of `self` with `other`. If either set is infinite, then /// this returns `None`. #[inline] pubfn max_cross_len(&self, other: &Seq) -> Option<usize> { let len1 = self.len()?; let len2 = other.len()?;
Some(len1.saturating_mul(len2))
}
/// Returns the length of the shortest literal in this sequence. /// /// If the sequence is infinite or empty, then this returns `None`. #[inline] pubfn min_literal_len(&self) -> Option<usize> { self.literals.as_ref()?.iter().map(|x| x.len()).min()
}
/// Returns the length of the longest literal in this sequence. /// /// If the sequence is infinite or empty, then this returns `None`. #[inline] pubfn max_literal_len(&self) -> Option<usize> { self.literals.as_ref()?.iter().map(|x| x.len()).max()
}
/// Returns the longest common prefix from this seq. /// /// If the seq matches any literal or other contains no literals, then /// there is no meaningful prefix and this returns `None`. /// /// # Example /// /// This shows some example seqs and their longest common prefix. /// /// ``` /// use regex_syntax::hir::literal::Seq; /// /// let seq = Seq::new(&["foo", "foobar", "fo"]); /// assert_eq!(Some(&b"fo"[..]), seq.longest_common_prefix()); /// let seq = Seq::new(&["foo", "foo"]); /// assert_eq!(Some(&b"foo"[..]), seq.longest_common_prefix()); /// let seq = Seq::new(&["foo", "bar"]); /// assert_eq!(Some(&b""[..]), seq.longest_common_prefix()); /// let seq = Seq::new(&[""]); /// assert_eq!(Some(&b""[..]), seq.longest_common_prefix()); /// /// let seq = Seq::infinite(); /// assert_eq!(None, seq.longest_common_prefix()); /// let seq = Seq::empty(); /// assert_eq!(None, seq.longest_common_prefix()); /// ``` #[inline] pubfn longest_common_prefix(&self) -> Option<&[u8]> { // If we match everything or match nothing, then there's no meaningful // longest common prefix. let lits = matchself.literals {
None => return None,
Some(ref lits) => lits,
}; if lits.len() == 0 { return None;
} let base = lits[0].as_bytes(); letmut len = base.len(); for m in lits.iter().skip(1) {
len = m
.as_bytes()
.iter()
.zip(base[..len].iter())
.take_while(|&(a, b)| a == b)
.count(); if len == 0 { return Some(&[]);
}
}
Some(&base[..len])
}
/// Returns the longest common suffix from this seq. /// /// If the seq matches any literal or other contains no literals, then /// there is no meaningful suffix and this returns `None`. /// /// # Example /// /// This shows some example seqs and their longest common suffix. /// /// ``` /// use regex_syntax::hir::literal::Seq; /// /// let seq = Seq::new(&["oof", "raboof", "of"]); /// assert_eq!(Some(&b"of"[..]), seq.longest_common_suffix()); /// let seq = Seq::new(&["foo", "foo"]); /// assert_eq!(Some(&b"foo"[..]), seq.longest_common_suffix()); /// let seq = Seq::new(&["foo", "bar"]); /// assert_eq!(Some(&b""[..]), seq.longest_common_suffix()); /// let seq = Seq::new(&[""]); /// assert_eq!(Some(&b""[..]), seq.longest_common_suffix()); /// /// let seq = Seq::infinite(); /// assert_eq!(None, seq.longest_common_suffix()); /// let seq = Seq::empty(); /// assert_eq!(None, seq.longest_common_suffix()); /// ``` #[inline] pubfn longest_common_suffix(&self) -> Option<&[u8]> { // If we match everything or match nothing, then there's no meaningful // longest common suffix. let lits = matchself.literals {
None => return None,
Some(ref lits) => lits,
}; if lits.len() == 0 { return None;
} let base = lits[0].as_bytes(); letmut len = base.len(); for m in lits.iter().skip(1) {
len = m
.as_bytes()
.iter()
.rev()
.zip(base[base.len() - len..].iter().rev())
.take_while(|&(a, b)| a == b)
.count(); if len == 0 { return Some(&[]);
}
}
Some(&base[base.len() - len..])
}
/// Optimizes this seq while treating its literals as prefixes and /// respecting the preference order of its literals. /// /// The specific way "optimization" works is meant to be an implementation /// detail, as it essentially represents a set of heuristics. The goal /// that optimization tries to accomplish is to make the literals in this /// set reflect inputs that will result in a more effective prefilter. /// Principally by reducing the false positive rate of candidates found by /// the literals in this sequence. That is, when a match of a literal is /// found, we would like it to be a strong predictor of the overall match /// of the regex. If it isn't, then much time will be spent starting and /// stopping the prefilter search and attempting to confirm the match only /// to have it fail. /// /// Some of those heuristics might be: /// /// * Identifying a common prefix from a larger sequence of literals, and /// shrinking the sequence down to that single common prefix. /// * Rejecting the sequence entirely if it is believed to result in very /// high false positive rate. When this happens, the sequence is made /// infinite. /// * Shrinking the sequence to a smaller number of literals representing /// prefixes, but not shrinking it so much as to make literals too short. /// (A sequence with very short literals, of 1 or 2 bytes, will typically /// result in a higher false positive rate.) /// /// Optimization should only be run once extraction is complete. Namely, /// optimization may make assumptions that do not compose with other /// operations in the middle of extraction. For example, optimization will /// reduce `[E(sam), E(samwise)]` to `[E(sam)]`, but such a transformation /// is only valid if no other extraction will occur. If other extraction /// may occur, then the correct transformation would be to `[I(sam)]`. /// /// The [`Seq::optimize_for_suffix_by_preference`] does the same thing, but /// for suffixes. /// /// # Example /// /// This shows how optimization might transform a sequence. Note that /// the specific behavior is not a documented guarantee. The heuristics /// used are an implementation detail and may change over time in semver /// compatible releases. /// /// ``` /// use regex_syntax::hir::literal::{Seq, Literal}; /// /// let mut seq = Seq::new(&[ /// "samantha", /// "sam", /// "samwise", /// "frodo", /// ]); /// seq.optimize_for_prefix_by_preference(); /// assert_eq!(Seq::from_iter([ /// Literal::exact("samantha"), /// // Kept exact even though 'samwise' got pruned /// // because optimization assumes literal extraction /// // has finished. /// Literal::exact("sam"), /// Literal::exact("frodo"), /// ]), seq); /// ``` /// /// # Example: optimization may make the sequence infinite /// /// If the heuristics deem that the sequence could cause a very high false /// positive rate, then it may make the sequence infinite, effectively /// disabling its use as a prefilter. /// /// ``` /// use regex_syntax::hir::literal::{Seq, Literal}; /// /// let mut seq = Seq::new(&[ /// "samantha", /// // An empty string matches at every position, /// // thus rendering the prefilter completely /// // ineffective. /// "", /// "sam", /// "samwise", /// "frodo", /// ]); /// seq.optimize_for_prefix_by_preference(); /// assert!(!seq.is_finite()); /// ``` /// /// Do note that just because there is a `" "` in the sequence, that /// doesn't mean the sequence will always be made infinite after it is /// optimized. Namely, if the sequence is considered exact (any match /// corresponds to an overall match of the original regex), then any match /// is an overall match, and so the false positive rate is always `0`. /// /// To demonstrate this, we remove `samwise` from our sequence. This /// results in no optimization happening and all literals remain exact. /// Thus the entire sequence is exact, and it is kept as-is, even though /// one is an ASCII space: /// /// ``` /// use regex_syntax::hir::literal::{Seq, Literal}; /// /// let mut seq = Seq::new(&[ /// "samantha", /// " ", /// "sam", /// "frodo", /// ]); /// seq.optimize_for_prefix_by_preference(); /// assert!(seq.is_finite()); /// ``` #[inline] pubfn optimize_for_prefix_by_preference(&mutself) { self.optimize_by_preference(true);
}
/// Optimizes this seq while treating its literals as suffixes and /// respecting the preference order of its literals. /// /// Optimization should only be run once extraction is complete. /// /// The [`Seq::optimize_for_prefix_by_preference`] does the same thing, but /// for prefixes. See its documentation for more explanation. #[inline] pubfn optimize_for_suffix_by_preference(&mutself) { self.optimize_by_preference(false);
}
fn optimize_by_preference(&mutself, prefix: bool) { let origlen = matchself.len() {
None => return,
Some(len) => len,
}; // Just give up now if our sequence contains an empty string. ifself.min_literal_len().map_or(false, |len| len == 0) { // We squash the sequence so that nobody else gets any bright // ideas to try and use it. An empty string implies a match at // every position. A prefilter cannot help you here. self.make_infinite(); return;
} // Make sure we start with the smallest sequence possible. We use a // special version of preference minimization that retains exactness. // This is legal because optimization is only expected to occur once // extraction is complete. if prefix { iflet Some(refmut lits) = self.literals {
PreferenceTrie::minimize(lits, true);
}
}
// Look for a common prefix (or suffix). If we found one of those and // it's long enough, then it's a good bet that it will be our fastest // possible prefilter since single-substring search is so fast. let fix = if prefix { self.longest_common_prefix()
} else { self.longest_common_suffix()
}; iflet Some(fix) = fix { // As a special case, if we have a common prefix and the leading // byte of that prefix is one that we think probably occurs rarely, // then strip everything down to just that single byte. This should // promote the use of memchr. // // ... we only do this though if our sequence has more than one // literal. Otherwise, we'd rather just stick with a single literal // scan. That is, using memchr is probably better than looking // for 2 or more literals, but probably not as good as a straight // memmem search. // // ... and also only do this when the prefix is short and probably // not too discriminatory anyway. If it's longer, then it's // probably quite discriminatory and thus is likely to have a low // false positive rate. if prefix
&& origlen > 1
&& fix.len() >= 1
&& fix.len() <= 3
&& rank(fix[0]) < 200
{ self.keep_first_bytes(1); self.dedup(); return;
} // We only strip down to the common prefix/suffix if we think // the existing set of literals isn't great, or if the common // prefix/suffix is expected to be particularly discriminatory. let isfast = self.is_exact() && self.len().map_or(false, |len| len <= 16); let usefix = fix.len() > 4 || (fix.len() > 1 && !isfast); if usefix { // If we keep exactly the number of bytes equal to the length // of the prefix (or suffix), then by the definition of a // prefix, every literal in the sequence will be equivalent. // Thus, 'dedup' will leave us with one literal. // // We do it this way to avoid an alloc, but also to make sure // the exactness of literals is kept (or not). if prefix { self.keep_first_bytes(fix.len());
} else { self.keep_last_bytes(fix.len());
} self.dedup();
assert_eq!(Some(1), self.len()); // We still fall through here. In particular, we want our // longest common prefix to be subject to the poison check.
}
} // If we have an exact sequence, we *probably* just want to keep it // as-is. But there are some cases where we don't. So we save a copy of // the exact sequence now, and then try to do some more optimizations // below. If those don't work out, we go back to this exact sequence. // // The specific motivation for this is that we sometimes wind up with // an exact sequence with a hefty number of literals. Say, 100. If we // stuck with that, it would be too big for Teddy and would result in // using Aho-Corasick. Which is fine... but the lazy DFA is plenty // suitable in such cases. The real issue is that we will wind up not // using a fast prefilter at all. So in cases like this, even though // we have an exact sequence, it would be better to try and shrink the // sequence (which we do below) and use it as a prefilter that can // produce false positive matches. // // But if the shrinking below results in a sequence that "sucks," then // we don't want to use that because we already have an exact sequence // in hand. let exact: Option<Seq> = ifself.is_exact() { Some(self.clone()) } else { None }; // Now we attempt to shorten the sequence. The idea here is that we // don't want to look for too many literals, but we want to shorten // our sequence enough to improve our odds of using better algorithms // downstream (such as Teddy). // // The pair of numbers in this list corresponds to the maximal prefix // (in bytes) to keep for all literals and the length of the sequence // at which to do it. // // So for example, the pair (3, 500) would mean, "if we have more than // 500 literals in our sequence, then truncate all of our literals // such that they are at most 3 bytes in length and the minimize the // sequence." const ATTEMPTS: [(usize, usize); 5] =
[(5, 10), (4, 10), (3, 64), (2, 64), (1, 10)]; for (keep, limit) in ATTEMPTS { let len = matchself.len() {
None => break,
Some(len) => len,
}; if len <= limit { break;
} if prefix { self.keep_first_bytes(keep);
} else { self.keep_last_bytes(keep);
} if prefix { iflet Some(refmut lits) = self.literals {
PreferenceTrie::minimize(lits, true);
}
}
} // Check for a poison literal. A poison literal is one that is short // and is believed to have a very high match count. These poisons // generally lead to a prefilter with a very high false positive rate, // and thus overall worse performance. // // We do this last because we could have gone from a non-poisonous // sequence to a poisonous one. Perhaps we should add some code to // prevent such transitions in the first place, but then again, we // likely only made the transition in the first place if the sequence // was itself huge. And huge sequences are themselves poisonous. So... iflet Some(lits) = self.literals() { if lits.iter().any(|lit| lit.is_poisonous()) { self.make_infinite();
}
} // OK, if we had an exact sequence before attempting more optimizations // above and our post-optimized sequence sucks for some reason or // another, then we go back to the exact sequence. iflet Some(exact) = exact { // If optimizing resulted in dropping our literals, then certainly // backup and use the exact sequence that we had. if !self.is_finite() {
*self = exact; return;
} // If our optimized sequence contains a short literal, then it's // *probably* not so great. So throw it away and revert to the // exact sequence. ifself.min_literal_len().map_or(true, |len| len <= 2) {
*self = exact; return;
} // Finally, if our optimized sequence is "big" (i.e., can't use // Teddy), then also don't use it and rely on the exact sequence. ifself.len().map_or(true, |len| len > 64) {
*self = exact; return;
}
}
}
}
impl FromIterator<Literal> for Seq { fn from_iter<T: IntoIterator<Item = Literal>>(it: T) -> Seq { letmut seq = Seq::empty(); for literal in it {
seq.push(literal);
}
seq
}
}
/// A single literal extracted from an [`Hir`] expression. /// /// A literal is composed of two things: /// /// * A sequence of bytes. No guarantees with respect to UTF-8 are provided. /// In particular, even if the regex a literal is extracted from is UTF-8, the /// literal extracted may not be valid UTF-8. (For example, if an [`Extractor`] /// limit resulted in trimming a literal in a way that splits a codepoint.) /// * Whether the literal is "exact" or not. An "exact" literal means that it /// has not been trimmed, and may continue to be extended. If a literal is /// "exact" after visiting the entire `Hir` expression, then this implies that /// the literal leads to a match state. (Although it doesn't necessarily imply /// all occurrences of the literal correspond to a match of the regex, since /// literal extraction ignores look-around assertions.) #[derive(Clone, Eq, PartialEq, PartialOrd, Ord)] pubstruct Literal {
bytes: Vec<u8>,
exact: bool,
}
impl Literal { /// Returns a new exact literal containing the bytes given. #[inline] pubfn exact<B: Into<Vec<u8>>>(bytes: B) -> Literal {
Literal { bytes: bytes.into(), exact: true }
}
/// Returns a new inexact literal containing the bytes given. #[inline] pubfn inexact<B: Into<Vec<u8>>>(bytes: B) -> Literal {
Literal { bytes: bytes.into(), exact: false }
}
/// Returns the bytes in this literal. #[inline] pubfn as_bytes(&self) -> &[u8] {
&self.bytes
}
/// Yields ownership of the bytes inside this literal. /// /// Note that this throws away whether the literal is "exact" or not. #[inline] pubfn into_bytes(self) -> Vec<u8> { self.bytes
}
/// Returns the length of this literal in bytes. #[inline] pubfn len(&self) -> usize { self.as_bytes().len()
}
/// Returns true if and only if this literal has zero bytes. #[inline] pubfn is_empty(&self) -> bool { self.len() == 0
}
/// Returns true if and only if this literal is exact. #[inline] pubfn is_exact(&self) -> bool { self.exact
}
/// Marks this literal as inexact. /// /// Inexact literals can never be extended. For example, /// [`Seq::cross_forward`] will not extend inexact literals. #[inline] pubfn make_inexact(&mutself) { self.exact = false;
}
/// Reverse the bytes in this literal. #[inline] pubfn reverse(&mutself) { self.bytes.reverse();
}
/// Extend this literal with the literal given. /// /// If this literal is inexact, then this is a no-op. #[inline] pubfn extend(&mutself, lit: &Literal) { if !self.is_exact() { return;
} self.bytes.extend_from_slice(&lit.bytes);
}
/// Trims this literal such that only the first `len` bytes remain. If /// this literal has fewer than `len` bytes, then it remains unchanged. /// Otherwise, the literal is marked as inexact. #[inline] pubfn keep_first_bytes(&mutself, len: usize) { if len >= self.len() { return;
} self.make_inexact(); self.bytes.truncate(len);
}
/// Trims this literal such that only the last `len` bytes remain. If this /// literal has fewer than `len` bytes, then it remains unchanged. /// Otherwise, the literal is marked as inexact. #[inline] pubfn keep_last_bytes(&mutself, len: usize) { if len >= self.len() { return;
} self.make_inexact(); self.bytes.drain(..self.len() - len);
}
/// Returns true if it is believe that this literal is likely to match very /// frequently, and is thus not a good candidate for a prefilter. fn is_poisonous(&self) -> bool { self.is_empty() || (self.len() == 1 && rank(self.as_bytes()[0]) >= 250)
}
}
impl core::fmt::Debug for Literal { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { let tag = ifself.exact { "E" } else { "I" };
f.debug_tuple(tag)
.field(&crate::debug::Bytes(self.as_bytes()))
.finish()
}
}
/// A "preference" trie that rejects literals that will never match when /// executing a leftmost first or "preference" search. /// /// For example, if 'sam' is inserted, then trying to insert 'samwise' will be /// rejected because 'samwise' can never match since 'sam' will always take /// priority. However, if 'samwise' is inserted first, then inserting 'sam' /// after it is accepted. In this case, either 'samwise' or 'sam' can match in /// a "preference" search. /// /// Note that we only use this trie as a "set." That is, given a sequence of /// literals, we insert each one in order. An `insert` will reject a literal /// if a prefix of that literal already exists in the trie. Thus, to rebuild /// the "minimal" sequence, we simply only keep literals that were successfully /// inserted. (Since we don't need traversal, one wonders whether we can make /// some simplifications here, but I haven't given it a ton of thought and I've /// never seen this show up on a profile. Because of the heuristic limits /// imposed on literal extractions, the size of the inputs here is usually /// very small.) #[derive(Debug)] struct PreferenceTrie { /// The states in this trie. The index of a state in this vector is its ID.
states: Vec<State>, /// This vec indicates which states are match states. It always has /// the same length as `states` and is indexed by the same state ID. /// A state with identifier `sid` is a match state if and only if /// `matches[sid].is_some()`. The option contains the index of the literal /// corresponding to the match. The index is offset by 1 so that it fits in /// a NonZeroUsize.
matches: Vec<Option<NonZeroUsize>>, /// The index to allocate to the next literal added to this trie. Starts at /// 1 and increments by 1 for every literal successfully added to the trie.
next_literal_index: usize,
}
/// A single state in a trie. Uses a sparse representation for its transitions. #[derive(Debug, Default)] struct State { /// Sparse representation of the transitions out of this state. Transitions /// are sorted by byte. There is at most one such transition for any /// particular byte.
trans: Vec<(u8, usize)>,
}
impl PreferenceTrie { /// Minimizes the given sequence of literals while preserving preference /// order semantics. /// /// When `keep_exact` is true, the exactness of every literal retained is /// kept. This is useful when dealing with a fully extracted `Seq` that /// only contains exact literals. In that case, we can keep all retained /// literals as exact because we know we'll never need to match anything /// after them and because any removed literals are guaranteed to never /// match. fn minimize(literals: &mut Vec<Literal>, keep_exact: bool) { use core::cell::RefCell;
// MSRV(1.61): Use retain_mut here to avoid interior mutability. let trie = RefCell::new(PreferenceTrie {
states: vec![],
matches: vec![],
next_literal_index: 1,
}); letmut make_inexact = vec![];
literals.retain(|lit| { match trie.borrow_mut().insert(lit.as_bytes()) {
Ok(_) => true,
Err(i) => { if !keep_exact {
make_inexact.push(i.checked_sub(1).unwrap());
} false
}
}
}); for i in make_inexact {
literals[i].make_inexact();
}
}
/// Returns `Ok` if the given byte string is accepted into this trie and /// `Err` otherwise. The index for the success case corresponds to the /// index of the literal added. The index for the error case corresponds to /// the index of the literal already in the trie that prevented the given /// byte string from being added. (Which implies it is a prefix of the one /// given.) /// /// In short, the byte string given is accepted into the trie if and only /// if it is possible for it to match when executing a preference order /// search. fn insert(&mutself, bytes: &[u8]) -> Result<usize, usize> { letmut prev = self.root(); iflet Some(idx) = self.matches[prev] { return Err(idx.get());
} for &b in bytes.iter() { matchself.states[prev].trans.binary_search_by_key(&b, |t| t.0) {
Ok(i) => {
prev = self.states[prev].trans[i].1; iflet Some(idx) = self.matches[prev] { return Err(idx.get());
}
}
Err(i) => { let next = self.create_state(); self.states[prev].trans.insert(i, (b, next));
prev = next;
}
}
} let idx = self.next_literal_index; self.next_literal_index += 1; self.matches[prev] = NonZeroUsize::new(idx);
Ok(idx)
}
/// Returns the root state ID, and if it doesn't exist, creates it. fn root(&mutself) -> usize { if !self.states.is_empty() { 0
} else { self.create_state()
}
}
/// Creates a new empty state and returns its ID. fn create_state(&mutself) -> usize { let id = self.states.len(); self.states.push(State::default()); self.matches.push(None);
id
}
}
/// Returns the "rank" of the given byte. /// /// The minimum rank value is `0` and the maximum rank value is `255`. /// /// The rank of a byte is derived from a heuristic background distribution of /// relative frequencies of bytes. The heuristic says that lower the rank of a /// byte, the less likely that byte is to appear in any arbitrary haystack. pubfn rank(byte: u8) -> u8 { crate::rank::BYTE_FREQUENCIES[usize::from(byte)]
}
#[cfg(test)] mod tests { usesuper::*;
fn parse(pattern: &str) -> Hir { crate::ParserBuilder::new().utf8(false).build().parse(pattern).unwrap()
}
// FIXME: The suffixes for this don't look quite right to me. I think // the right suffixes would be: [I(ac), I(bc), E(c)]. The main issue I // think is that suffixes are computed by iterating over concatenations // in reverse, and then [bc, ac, c] ordering is indeed correct from // that perspective. We also test a few more equivalent regexes, and // we get the same result, so it is consistent at least I suppose. // // The reason why this isn't an issue is that it only messes up // preference order, and currently, suffixes are never used in a // context where preference order matters. For prefixes it matters // because we sometimes want to use prefilters without confirmation // when all of the literals are exact (and there's no look-around). But // we never do that for suffixes. Any time we use suffixes, we always // include a confirmation step. If that ever changes, then it's likely // this bug will need to be fixed, but last time I looked, it appears // hard to do so.
assert_eq!(
inexact([I("a"), I("b"), E("c")], [I("bc"), I("ac"), E("c")]),
e(r"a*b*c")
);
assert_eq!(
inexact([I("a"), I("b"), E("c")], [I("bc"), I("ac"), E("c")]),
e(r"(a+)?(b+)?c")
);
assert_eq!(
inexact([I("a"), I("b"), E("c")], [I("bc"), I("ac"), E("c")]),
e(r"(a+|)(b+|)c")
); // A few more similarish but not identical regexes. These may have a // similar problem as above.
assert_eq!(
inexact(
[I("a"), I("b"), I("c"), E("")],
[I("c"), I("b"), I("a"), E("")]
),
e(r"a*b*c*")
);
assert_eq!(inexact([I("a"), I("b"), I("c")], [I("c")]), e(r"a*b*c+"));
assert_eq!(inexact([I("a"), I("b")], [I("bc")]), e(r"a*b+c"));
assert_eq!(inexact([I("a"), I("b")], [I("c"), I("b")]), e(r"a*b+c*"));
assert_eq!(inexact([I("ab"), E("a")], [I("b"), E("a")]), e(r"ab*"));
assert_eq!(
inexact([I("ab"), E("ac")], [I("bc"), E("ac")]),
e(r"ab*c")
);
assert_eq!(inexact([I("ab")], [I("b")]), e(r"ab+"));
assert_eq!(inexact([I("ab")], [I("bc")]), e(r"ab+c"));
// This tests patterns that contain something that defeats literal // detection, usually because it would blow some limit on the total number // of literals that can be returned. // // The main idea is that when literal extraction sees something that // it knows will blow a limit, it replaces it with a marker that says // "any literal will match here." While not necessarily true, the // over-estimation is just fine for the purposes of literal extraction, // because the imprecision doesn't matter: too big is too big. // // This is one of the trickier parts of literal extraction, since we need // to make sure all of our literal extraction operations correctly compose // with the markers. #[test] fn anything() {
assert_eq!(infinite(), e(r"."));
assert_eq!(infinite(), e(r"(?s)."));
assert_eq!(infinite(), e(r"[A-Za-z]"));
assert_eq!(infinite(), e(r"[A-Z]"));
assert_eq!(exact([""]), e(r"[A-Z]{0}"));
assert_eq!(infinite(), e(r"[A-Z]?"));
assert_eq!(infinite(), e(r"[A-Z]*"));
assert_eq!(infinite(), e(r"[A-Z]+"));
assert_eq!((seq([I("1")]), Seq::infinite()), e(r"1[A-Z]"));
assert_eq!((seq([I("1")]), seq([I("2")])), e(r"1[A-Z]2"));
assert_eq!((Seq::infinite(), seq([I("123")])), e(r"[A-Z]+123"));
assert_eq!(infinite(), e(r"[A-Z]+123[A-Z]+"));
assert_eq!(infinite(), e(r"1|[A-Z]|3"));
assert_eq!(
(seq([E("1"), I("2"), E("3")]), Seq::infinite()),
e(r"1|2[A-Z]|3"),
);
assert_eq!(
(Seq::infinite(), seq([E("1"), I("2"), E("3")])),
e(r"1|[A-Z]2|3"),
);
assert_eq!(
(seq([E("1"), I("2"), E("4")]), seq([E("1"), I("3"), E("4")])),
e(r"1|2[A-Z]3|4"),
);
assert_eq!((Seq::infinite(), seq([I("2")])), e(r"(?:|1)[A-Z]2"));
assert_eq!(inexact([I("a")], [I("z")]), e(r"a.z"));
}
// Like the 'anything' test, but it uses smaller limits in order to test // the logic for effectively aborting literal extraction when the seqs get // too big. #[test] fn anything_small_limits() { fn prefixes(pattern: &str) -> Seq {
Extractor::new()
.kind(ExtractKind::Prefix)
.limit_total(10)
.extract(&parse(pattern))
}
// This tests a specific regex along with some heuristic steps to reduce // the sequences extracted. This is meant to roughly correspond to the // types of heuristics used to shrink literal sets in practice. (Shrinking // is done because you want to balance "spend too much work looking for // too many literals" and "spend too much work processing false positive // matches from short literals.") #[test] #[cfg(feature = "unicode-case")] fn holmes() { let expected = inexact(
["HOL", "HOl", "HoL", "Hol", "hOL", "hOl", "hoL", "hol"].map(I),
[ "MES", "MEs", "Eſ", "MeS", "Mes", "eſ", "mES", "mEs", "meS", "mes",
]
.map(I),
); let (mut prefixes, mut suffixes) = e(r"(?i)Holmes");
prefixes.keep_first_bytes(3);
suffixes.keep_last_bytes(3);
prefixes.minimize_by_preference();
suffixes.minimize_by_preference();
assert_eq!(expected, (prefixes, suffixes));
}
// This tests that we get some kind of literals extracted for a beefier // alternation with case insensitive mode enabled. At one point during // development, this returned nothing, and motivated some special case // code in Extractor::union to try and trim down the literal sequences // if the union would blow the limits set. #[test] #[cfg(feature = "unicode-case")] fn holmes_alt() { letmut pre =
prefixes(r"(?i)Sherlock|Holmes|Watson|Irene|Adler|John|Baker");
assert!(pre.len().unwrap() > 0);
pre.optimize_for_prefix_by_preference();
assert!(pre.len().unwrap() > 0);
}
// See: https://github.com/rust-lang/regex/security/advisories/GHSA-m5pq-gvj9-9vr8 // See: CVE-2022-24713 // // We test this here to ensure literal extraction completes in reasonable // time and isn't materially impacted by these sorts of pathological // repeats. #[test] fn crazy_repeats() {
assert_eq!(inexact([E("")], [E("")]), e(r"(?:){4294967295}"));
assert_eq!(
inexact([E("")], [E("")]),
e(r"(?:){64}{64}{64}{64}{64}{64}")
);
assert_eq!(inexact([E("")], [E("")]), e(r"x{0}{4294967295}"));
assert_eq!(inexact([E("")], [E("")]), e(r"(?:|){4294967295}"));
#[test] fn optimize() { // This gets a common prefix that isn't too short. let (p, s) =
opt(["foobarfoobar", "foobar", "foobarzfoobar", "foobarfoobar"]);
assert_eq!(seq([I("foobar")]), p);
assert_eq!(seq([I("foobar")]), s);
// This also finds a common prefix, but since it's only one byte, it // prefers the multiple literals. let (p, s) = opt(["abba", "akka", "abccba"]);
assert_eq!(exact(["abba", "akka", "abccba"]), (p, s));
// The empty string is poisonous, so our seq becomes infinite, even // though all literals are exact. let (p, s) = opt(["foobarfoo", "foo", "", "foozfoo", "foofoo"]);
assert!(!p.is_finite());
assert!(!s.is_finite());
// A space is also poisonous, so our seq becomes infinite. But this // only gets triggered when we don't have a completely exact sequence. // When the sequence is exact, spaces are okay, since we presume that // any prefilter will match a space more quickly than the regex engine. // (When the sequence is exact, there's a chance of the prefilter being // used without needing the regex engine at all.) letmut p = seq([E("foobarfoo"), I("foo"), E(" "), E("foofoo")]);
p.optimize_for_prefix_by_preference();
assert!(!p.is_finite());
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.72 Sekunden
(vorverarbeitet am 2026-06-23)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.