Freeze independently reviewed relevance calibration corpus
This commit is contained in:
1 parent
b6dd973a88
commit
4e60038c2f
36 files changed
+6565
No files matched your search
@@ -0,0 +1,691 @@
|
||||
use alloc::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
packed::{pattern::Patterns, rabinkarp::RabinKarp, teddy},
|
||||
util::search::{Match, Span},
|
||||
};
|
||||
|
||||
/// This is a limit placed on the total number of patterns we're willing to try
|
||||
/// and match at once. As more sophisticated algorithms are added, this number
|
||||
/// may be increased.
|
||||
const PATTERN_LIMIT: usize = 128;
|
||||
|
||||
/// A knob for controlling the match semantics of a packed multiple string
|
||||
/// searcher.
|
||||
///
|
||||
/// This differs from the [`MatchKind`](crate::MatchKind) type in the top-level
|
||||
/// crate module in that it doesn't support "standard" match semantics,
|
||||
/// and instead only supports leftmost-first or leftmost-longest. Namely,
|
||||
/// "standard" semantics cannot be easily supported by packed searchers.
|
||||
///
|
||||
/// For more information on the distinction between leftmost-first and
|
||||
/// leftmost-longest, see the docs on the top-level `MatchKind` type.
|
||||
///
|
||||
/// Unlike the top-level `MatchKind` type, the default match semantics for this
|
||||
/// type are leftmost-first.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum MatchKind {
|
||||
/// Use leftmost-first match semantics, which reports leftmost matches.
|
||||
/// When there are multiple possible leftmost matches, the match
|
||||
/// corresponding to the pattern that appeared earlier when constructing
|
||||
/// the automaton is reported.
|
||||
///
|
||||
/// This is the default.
|
||||
LeftmostFirst,
|
||||
/// Use leftmost-longest match semantics, which reports leftmost matches.
|
||||
/// When there are multiple possible leftmost matches, the longest match
|
||||
/// is chosen.
|
||||
LeftmostLongest,
|
||||
}
|
||||
|
||||
impl Default for MatchKind {
|
||||
fn default() -> MatchKind {
|
||||
MatchKind::LeftmostFirst
|
||||
}
|
||||
}
|
||||
|
||||
/// The configuration for a packed multiple pattern searcher.
|
||||
///
|
||||
/// The configuration is currently limited only to being able to select the
|
||||
/// match semantics (leftmost-first or leftmost-longest) of a searcher. In the
|
||||
/// future, more knobs may be made available.
|
||||
///
|
||||
/// A configuration produces a [`packed::Builder`](Builder), which in turn can
|
||||
/// be used to construct a [`packed::Searcher`](Searcher) for searching.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// This example shows how to use leftmost-longest semantics instead of the
|
||||
/// default (leftmost-first).
|
||||
///
|
||||
/// ```
|
||||
/// use aho_corasick::{packed::{Config, MatchKind}, PatternID};
|
||||
///
|
||||
/// # fn example() -> Option<()> {
|
||||
/// let searcher = Config::new()
|
||||
/// .match_kind(MatchKind::LeftmostLongest)
|
||||
/// .builder()
|
||||
/// .add("foo")
|
||||
/// .add("foobar")
|
||||
/// .build()?;
|
||||
/// let matches: Vec<PatternID> = searcher
|
||||
/// .find_iter("foobar")
|
||||
/// .map(|mat| mat.pattern())
|
||||
/// .collect();
|
||||
/// assert_eq!(vec![PatternID::must(1)], matches);
|
||||
/// # Some(()) }
|
||||
/// # if cfg!(all(feature = "std", any(
|
||||
/// # target_arch = "x86_64", target_arch = "aarch64",
|
||||
/// # ))) {
|
||||
/// # example().unwrap()
|
||||
/// # } else {
|
||||
/// # assert!(example().is_none());
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Config {
|
||||
kind: MatchKind,
|
||||
force: Option<ForceAlgorithm>,
|
||||
only_teddy_fat: Option<bool>,
|
||||
only_teddy_256bit: Option<bool>,
|
||||
heuristic_pattern_limits: bool,
|
||||
}
|
||||
|
||||
/// An internal option for forcing the use of a particular packed algorithm.
|
||||
///
|
||||
/// When an algorithm is forced, if a searcher could not be constructed for it,
|
||||
/// then no searcher will be returned even if an alternative algorithm would
|
||||
/// work.
|
||||
#[derive(Clone, Debug)]
|
||||
enum ForceAlgorithm {
|
||||
Teddy,
|
||||
RabinKarp,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Config {
|
||||
Config::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Create a new default configuration. A default configuration uses
|
||||
/// leftmost-first match semantics.
|
||||
pub fn new() -> Config {
|
||||
Config {
|
||||
kind: MatchKind::LeftmostFirst,
|
||||
force: None,
|
||||
only_teddy_fat: None,
|
||||
only_teddy_256bit: None,
|
||||
heuristic_pattern_limits: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a packed builder from this configuration. The builder can be
|
||||
/// used to accumulate patterns and create a [`Searcher`] from them.
|
||||
pub fn builder(&self) -> Builder {
|
||||
Builder::from_config(self.clone())
|
||||
}
|
||||
|
||||
/// Set the match semantics for this configuration.
|
||||
pub fn match_kind(&mut self, kind: MatchKind) -> &mut Config {
|
||||
self.kind = kind;
|
||||
self
|
||||
}
|
||||
|
||||
/// An undocumented method for forcing the use of the Teddy algorithm.
|
||||
///
|
||||
/// This is only exposed for more precise testing and benchmarks. Callers
|
||||
/// should not use it as it is not part of the API stability guarantees of
|
||||
/// this crate.
|
||||
#[doc(hidden)]
|
||||
pub fn only_teddy(&mut self, yes: bool) -> &mut Config {
|
||||
if yes {
|
||||
self.force = Some(ForceAlgorithm::Teddy);
|
||||
} else {
|
||||
self.force = None;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// An undocumented method for forcing the use of the Fat Teddy algorithm.
|
||||
///
|
||||
/// This is only exposed for more precise testing and benchmarks. Callers
|
||||
/// should not use it as it is not part of the API stability guarantees of
|
||||
/// this crate.
|
||||
#[doc(hidden)]
|
||||
pub fn only_teddy_fat(&mut self, yes: Option<bool>) -> &mut Config {
|
||||
self.only_teddy_fat = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// An undocumented method for forcing the use of SSE (`Some(false)`) or
|
||||
/// AVX (`Some(true)`) algorithms.
|
||||
///
|
||||
/// This is only exposed for more precise testing and benchmarks. Callers
|
||||
/// should not use it as it is not part of the API stability guarantees of
|
||||
/// this crate.
|
||||
#[doc(hidden)]
|
||||
pub fn only_teddy_256bit(&mut self, yes: Option<bool>) -> &mut Config {
|
||||
self.only_teddy_256bit = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// An undocumented method for forcing the use of the Rabin-Karp algorithm.
|
||||
///
|
||||
/// This is only exposed for more precise testing and benchmarks. Callers
|
||||
/// should not use it as it is not part of the API stability guarantees of
|
||||
/// this crate.
|
||||
#[doc(hidden)]
|
||||
pub fn only_rabin_karp(&mut self, yes: bool) -> &mut Config {
|
||||
if yes {
|
||||
self.force = Some(ForceAlgorithm::RabinKarp);
|
||||
} else {
|
||||
self.force = None;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Request that heuristic limitations on the number of patterns be
|
||||
/// employed. This useful to disable for benchmarking where one wants to
|
||||
/// explore how Teddy performs on large number of patterns even if the
|
||||
/// heuristics would otherwise refuse construction.
|
||||
///
|
||||
/// This is enabled by default.
|
||||
pub fn heuristic_pattern_limits(&mut self, yes: bool) -> &mut Config {
|
||||
self.heuristic_pattern_limits = yes;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder for constructing a packed searcher from a collection of patterns.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// This example shows how to use a builder to construct a searcher. By
|
||||
/// default, leftmost-first match semantics are used.
|
||||
///
|
||||
/// ```
|
||||
/// use aho_corasick::{packed::{Builder, MatchKind}, PatternID};
|
||||
///
|
||||
/// # fn example() -> Option<()> {
|
||||
/// let searcher = Builder::new()
|
||||
/// .add("foobar")
|
||||
/// .add("foo")
|
||||
/// .build()?;
|
||||
/// let matches: Vec<PatternID> = searcher
|
||||
/// .find_iter("foobar")
|
||||
/// .map(|mat| mat.pattern())
|
||||
/// .collect();
|
||||
/// assert_eq!(vec![PatternID::ZERO], matches);
|
||||
/// # Some(()) }
|
||||
/// # if cfg!(all(feature = "std", any(
|
||||
/// # target_arch = "x86_64", target_arch = "aarch64",
|
||||
/// # ))) {
|
||||
/// # example().unwrap()
|
||||
/// # } else {
|
||||
/// # assert!(example().is_none());
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Builder {
|
||||
/// The configuration of this builder and subsequent matcher.
|
||||
config: Config,
|
||||
/// Set to true if the builder detects that a matcher cannot be built.
|
||||
inert: bool,
|
||||
/// The patterns provided by the caller.
|
||||
patterns: Patterns,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
/// Create a new builder for constructing a multi-pattern searcher. This
|
||||
/// constructor uses the default configuration.
|
||||
pub fn new() -> Builder {
|
||||
Builder::from_config(Config::new())
|
||||
}
|
||||
|
||||
fn from_config(config: Config) -> Builder {
|
||||
Builder { config, inert: false, patterns: Patterns::new() }
|
||||
}
|
||||
|
||||
/// Build a searcher from the patterns added to this builder so far.
|
||||
pub fn build(&self) -> Option<Searcher> {
|
||||
if self.inert || self.patterns.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut patterns = self.patterns.clone();
|
||||
patterns.set_match_kind(self.config.kind);
|
||||
let patterns = Arc::new(patterns);
|
||||
let rabinkarp = RabinKarp::new(&patterns);
|
||||
// Effectively, we only want to return a searcher if we can use Teddy,
|
||||
// since Teddy is our only fast packed searcher at the moment.
|
||||
// Rabin-Karp is only used when searching haystacks smaller than what
|
||||
// Teddy can support. Thus, the only way to get a Rabin-Karp searcher
|
||||
// is to force it using undocumented APIs (for tests/benchmarks).
|
||||
let (search_kind, minimum_len) = match self.config.force {
|
||||
None | Some(ForceAlgorithm::Teddy) => {
|
||||
debug!("trying to build Teddy packed matcher");
|
||||
let teddy = match self.build_teddy(Arc::clone(&patterns)) {
|
||||
None => return None,
|
||||
Some(teddy) => teddy,
|
||||
};
|
||||
let minimum_len = teddy.minimum_len();
|
||||
(SearchKind::Teddy(teddy), minimum_len)
|
||||
}
|
||||
Some(ForceAlgorithm::RabinKarp) => {
|
||||
debug!("using Rabin-Karp packed matcher");
|
||||
(SearchKind::RabinKarp, 0)
|
||||
}
|
||||
};
|
||||
Some(Searcher { patterns, rabinkarp, search_kind, minimum_len })
|
||||
}
|
||||
|
||||
fn build_teddy(&self, patterns: Arc<Patterns>) -> Option<teddy::Searcher> {
|
||||
teddy::Builder::new()
|
||||
.only_256bit(self.config.only_teddy_256bit)
|
||||
.only_fat(self.config.only_teddy_fat)
|
||||
.heuristic_pattern_limits(self.config.heuristic_pattern_limits)
|
||||
.build(patterns)
|
||||
}
|
||||
|
||||
/// Add the given pattern to this set to match.
|
||||
///
|
||||
/// The order in which patterns are added is significant. Namely, when
|
||||
/// using leftmost-first match semantics, then when multiple patterns can
|
||||
/// match at a particular location, the pattern that was added first is
|
||||
/// used as the match.
|
||||
///
|
||||
/// If the number of patterns added exceeds the amount supported by packed
|
||||
/// searchers, then the builder will stop accumulating patterns and render
|
||||
/// itself inert. At this point, constructing a searcher will always return
|
||||
/// `None`.
|
||||
pub fn add<P: AsRef<[u8]>>(&mut self, pattern: P) -> &mut Builder {
|
||||
if self.inert {
|
||||
return self;
|
||||
} else if self.patterns.len() >= PATTERN_LIMIT {
|
||||
self.inert = true;
|
||||
self.patterns.reset();
|
||||
return self;
|
||||
}
|
||||
// Just in case PATTERN_LIMIT increases beyond u16::MAX.
|
||||
assert!(self.patterns.len() <= u16::MAX as usize);
|
||||
|
||||
let pattern = pattern.as_ref();
|
||||
if pattern.is_empty() {
|
||||
self.inert = true;
|
||||
self.patterns.reset();
|
||||
return self;
|
||||
}
|
||||
self.patterns.add(pattern);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add the given iterator of patterns to this set to match.
|
||||
///
|
||||
/// The iterator must yield elements that can be converted into a `&[u8]`.
|
||||
///
|
||||
/// The order in which patterns are added is significant. Namely, when
|
||||
/// using leftmost-first match semantics, then when multiple patterns can
|
||||
/// match at a particular location, the pattern that was added first is
|
||||
/// used as the match.
|
||||
///
|
||||
/// If the number of patterns added exceeds the amount supported by packed
|
||||
/// searchers, then the builder will stop accumulating patterns and render
|
||||
/// itself inert. At this point, constructing a searcher will always return
|
||||
/// `None`.
|
||||
pub fn extend<I, P>(&mut self, patterns: I) -> &mut Builder
|
||||
where
|
||||
I: IntoIterator<Item = P>,
|
||||
P: AsRef<[u8]>,
|
||||
{
|
||||
for p in patterns {
|
||||
self.add(p);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the number of patterns added to this builder.
|
||||
pub fn len(&self) -> usize {
|
||||
self.patterns.len()
|
||||
}
|
||||
|
||||
/// Returns the length, in bytes, of the shortest pattern added.
|
||||
pub fn minimum_len(&self) -> usize {
|
||||
self.patterns.minimum_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Builder {
|
||||
fn default() -> Builder {
|
||||
Builder::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// A packed searcher for quickly finding occurrences of multiple patterns.
|
||||
///
|
||||
/// If callers need more flexible construction, or if one wants to change the
|
||||
/// match semantics (either leftmost-first or leftmost-longest), then one can
|
||||
/// use the [`Config`] and/or [`Builder`] types for more fine grained control.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// This example shows how to create a searcher from an iterator of patterns.
|
||||
/// By default, leftmost-first match semantics are used.
|
||||
///
|
||||
/// ```
|
||||
/// use aho_corasick::{packed::{MatchKind, Searcher}, PatternID};
|
||||
///
|
||||
/// # fn example() -> Option<()> {
|
||||
/// let searcher = Searcher::new(["foobar", "foo"].iter().cloned())?;
|
||||
/// let matches: Vec<PatternID> = searcher
|
||||
/// .find_iter("foobar")
|
||||
/// .map(|mat| mat.pattern())
|
||||
/// .collect();
|
||||
/// assert_eq!(vec![PatternID::ZERO], matches);
|
||||
/// # Some(()) }
|
||||
/// # if cfg!(all(feature = "std", any(
|
||||
/// # target_arch = "x86_64", target_arch = "aarch64",
|
||||
/// # ))) {
|
||||
/// # example().unwrap()
|
||||
/// # } else {
|
||||
/// # assert!(example().is_none());
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Searcher {
|
||||
patterns: Arc<Patterns>,
|
||||
rabinkarp: RabinKarp,
|
||||
search_kind: SearchKind,
|
||||
minimum_len: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum SearchKind {
|
||||
Teddy(teddy::Searcher),
|
||||
RabinKarp,
|
||||
}
|
||||
|
||||
impl Searcher {
|
||||
/// A convenience function for constructing a searcher from an iterator
|
||||
/// of things that can be converted to a `&[u8]`.
|
||||
///
|
||||
/// If a searcher could not be constructed (either because of an
|
||||
/// unsupported CPU or because there are too many patterns), then `None`
|
||||
/// is returned.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// use aho_corasick::{packed::{MatchKind, Searcher}, PatternID};
|
||||
///
|
||||
/// # fn example() -> Option<()> {
|
||||
/// let searcher = Searcher::new(["foobar", "foo"].iter().cloned())?;
|
||||
/// let matches: Vec<PatternID> = searcher
|
||||
/// .find_iter("foobar")
|
||||
/// .map(|mat| mat.pattern())
|
||||
/// .collect();
|
||||
/// assert_eq!(vec![PatternID::ZERO], matches);
|
||||
/// # Some(()) }
|
||||
/// # if cfg!(all(feature = "std", any(
|
||||
/// # target_arch = "x86_64", target_arch = "aarch64",
|
||||
/// # ))) {
|
||||
/// # example().unwrap()
|
||||
/// # } else {
|
||||
/// # assert!(example().is_none());
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn new<I, P>(patterns: I) -> Option<Searcher>
|
||||
where
|
||||
I: IntoIterator<Item = P>,
|
||||
P: AsRef<[u8]>,
|
||||
{
|
||||
Builder::new().extend(patterns).build()
|
||||
}
|
||||
|
||||
/// A convenience function for calling `Config::new()`.
|
||||
///
|
||||
/// This is useful for avoiding an additional import.
|
||||
pub fn config() -> Config {
|
||||
Config::new()
|
||||
}
|
||||
|
||||
/// A convenience function for calling `Builder::new()`.
|
||||
///
|
||||
/// This is useful for avoiding an additional import.
|
||||
pub fn builder() -> Builder {
|
||||
Builder::new()
|
||||
}
|
||||
|
||||
/// Return the first occurrence of any of the patterns in this searcher,
|
||||
/// according to its match semantics, in the given haystack. The `Match`
|
||||
/// returned will include the identifier of the pattern that matched, which
|
||||
/// corresponds to the index of the pattern (starting from `0`) in which it
|
||||
/// was added.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// use aho_corasick::{packed::{MatchKind, Searcher}, PatternID};
|
||||
///
|
||||
/// # fn example() -> Option<()> {
|
||||
/// let searcher = Searcher::new(["foobar", "foo"].iter().cloned())?;
|
||||
/// let mat = searcher.find("foobar")?;
|
||||
/// assert_eq!(PatternID::ZERO, mat.pattern());
|
||||
/// assert_eq!(0, mat.start());
|
||||
/// assert_eq!(6, mat.end());
|
||||
/// # Some(()) }
|
||||
/// # if cfg!(all(feature = "std", any(
|
||||
/// # target_arch = "x86_64", target_arch = "aarch64",
|
||||
/// # ))) {
|
||||
/// # example().unwrap()
|
||||
/// # } else {
|
||||
/// # assert!(example().is_none());
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn find<B: AsRef<[u8]>>(&self, haystack: B) -> Option<Match> {
|
||||
let haystack = haystack.as_ref();
|
||||
self.find_in(haystack, Span::from(0..haystack.len()))
|
||||
}
|
||||
|
||||
/// Return the first occurrence of any of the patterns in this searcher,
|
||||
/// according to its match semantics, in the given haystack starting from
|
||||
/// the given position.
|
||||
///
|
||||
/// The `Match` returned will include the identifier of the pattern that
|
||||
/// matched, which corresponds to the index of the pattern (starting from
|
||||
/// `0`) in which it was added. The offsets in the `Match` will be relative
|
||||
/// to the start of `haystack` (and not `at`).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// When `span` does not correspond to a valid range in `haystack`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// use aho_corasick::{packed::{MatchKind, Searcher}, PatternID, Span};
|
||||
///
|
||||
/// # fn example() -> Option<()> {
|
||||
/// let haystack = "foofoobar";
|
||||
/// let searcher = Searcher::new(["foobar", "foo"].iter().cloned())?;
|
||||
/// let mat = searcher.find_in(haystack, Span::from(3..haystack.len()))?;
|
||||
/// assert_eq!(PatternID::ZERO, mat.pattern());
|
||||
/// assert_eq!(3, mat.start());
|
||||
/// assert_eq!(9, mat.end());
|
||||
/// # Some(()) }
|
||||
/// # if cfg!(all(feature = "std", any(
|
||||
/// # target_arch = "x86_64", target_arch = "aarch64",
|
||||
/// # ))) {
|
||||
/// # example().unwrap()
|
||||
/// # } else {
|
||||
/// # assert!(example().is_none());
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn find_in<B: AsRef<[u8]>>(
|
||||
&self,
|
||||
haystack: B,
|
||||
span: Span,
|
||||
) -> Option<Match> {
|
||||
let haystack = haystack.as_ref();
|
||||
match self.search_kind {
|
||||
SearchKind::Teddy(ref teddy) => {
|
||||
if haystack[span].len() < teddy.minimum_len() {
|
||||
return self.find_in_slow(haystack, span);
|
||||
}
|
||||
teddy.find(&haystack[..span.end], span.start)
|
||||
}
|
||||
SearchKind::RabinKarp => {
|
||||
self.rabinkarp.find_at(&haystack[..span.end], span.start)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an iterator of non-overlapping occurrences of the patterns in
|
||||
/// this searcher, according to its match semantics, in the given haystack.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// use aho_corasick::{packed::{MatchKind, Searcher}, PatternID};
|
||||
///
|
||||
/// # fn example() -> Option<()> {
|
||||
/// let searcher = Searcher::new(["foobar", "foo"].iter().cloned())?;
|
||||
/// let matches: Vec<PatternID> = searcher
|
||||
/// .find_iter("foobar fooba foofoo")
|
||||
/// .map(|mat| mat.pattern())
|
||||
/// .collect();
|
||||
/// assert_eq!(vec![
|
||||
/// PatternID::must(0),
|
||||
/// PatternID::must(1),
|
||||
/// PatternID::must(1),
|
||||
/// PatternID::must(1),
|
||||
/// ], matches);
|
||||
/// # Some(()) }
|
||||
/// # if cfg!(all(feature = "std", any(
|
||||
/// # target_arch = "x86_64", target_arch = "aarch64",
|
||||
/// # ))) {
|
||||
/// # example().unwrap()
|
||||
/// # } else {
|
||||
/// # assert!(example().is_none());
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn find_iter<'a, 'b, B: ?Sized + AsRef<[u8]>>(
|
||||
&'a self,
|
||||
haystack: &'b B,
|
||||
) -> FindIter<'a, 'b> {
|
||||
let haystack = haystack.as_ref();
|
||||
let span = Span::from(0..haystack.len());
|
||||
FindIter { searcher: self, haystack, span }
|
||||
}
|
||||
|
||||
/// Returns the match kind used by this packed searcher.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// use aho_corasick::packed::{MatchKind, Searcher};
|
||||
///
|
||||
/// # fn example() -> Option<()> {
|
||||
/// let searcher = Searcher::new(["foobar", "foo"].iter().cloned())?;
|
||||
/// // leftmost-first is the default.
|
||||
/// assert_eq!(&MatchKind::LeftmostFirst, searcher.match_kind());
|
||||
/// # Some(()) }
|
||||
/// # if cfg!(all(feature = "std", any(
|
||||
/// # target_arch = "x86_64", target_arch = "aarch64",
|
||||
/// # ))) {
|
||||
/// # example().unwrap()
|
||||
/// # } else {
|
||||
/// # assert!(example().is_none());
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn match_kind(&self) -> &MatchKind {
|
||||
self.patterns.match_kind()
|
||||
}
|
||||
|
||||
/// Returns the minimum length of a haystack that is required in order for
|
||||
/// packed searching to be effective.
|
||||
///
|
||||
/// In some cases, the underlying packed searcher may not be able to search
|
||||
/// very short haystacks. When that occurs, the implementation will defer
|
||||
/// to a slower non-packed searcher (which is still generally faster than
|
||||
/// Aho-Corasick for a small number of patterns). However, callers may
|
||||
/// want to avoid ever using the slower variant, which one can do by
|
||||
/// never passing a haystack shorter than the minimum length returned by
|
||||
/// this method.
|
||||
#[inline]
|
||||
pub fn minimum_len(&self) -> usize {
|
||||
self.minimum_len
|
||||
}
|
||||
|
||||
/// Returns the approximate total amount of heap used by this searcher, in
|
||||
/// units of bytes.
|
||||
#[inline]
|
||||
pub fn memory_usage(&self) -> usize {
|
||||
self.patterns.memory_usage()
|
||||
+ self.rabinkarp.memory_usage()
|
||||
+ self.search_kind.memory_usage()
|
||||
}
|
||||
|
||||
/// Use a slow (non-packed) searcher.
|
||||
///
|
||||
/// This is useful when a packed searcher could be constructed, but could
|
||||
/// not be used to search a specific haystack. For example, if Teddy was
|
||||
/// built but the haystack is smaller than ~34 bytes, then Teddy might not
|
||||
/// be able to run.
|
||||
fn find_in_slow(&self, haystack: &[u8], span: Span) -> Option<Match> {
|
||||
self.rabinkarp.find_at(&haystack[..span.end], span.start)
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchKind {
|
||||
fn memory_usage(&self) -> usize {
|
||||
match *self {
|
||||
SearchKind::Teddy(ref ted) => ted.memory_usage(),
|
||||
SearchKind::RabinKarp => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over non-overlapping matches from a packed searcher.
|
||||
///
|
||||
/// The lifetime `'s` refers to the lifetime of the underlying [`Searcher`],
|
||||
/// while the lifetime `'h` refers to the lifetime of the haystack being
|
||||
/// searched.
|
||||
#[derive(Debug)]
|
||||
pub struct FindIter<'s, 'h> {
|
||||
searcher: &'s Searcher,
|
||||
haystack: &'h [u8],
|
||||
span: Span,
|
||||
}
|
||||
|
||||
impl<'s, 'h> Iterator for FindIter<'s, 'h> {
|
||||
type Item = Match;
|
||||
|
||||
fn next(&mut self) -> Option<Match> {
|
||||
if self.span.start > self.span.end {
|
||||
return None;
|
||||
}
|
||||
match self.searcher.find_in(self.haystack, self.span) {
|
||||
None => None,
|
||||
Some(m) => {
|
||||
self.span.start = m.end();
|
||||
Some(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Batch label release
|
||||
|
||||
Juniper Bakery prints an almond warning on its oat biscuits because they contain almond flour. A second staff member verifies the allergen label before a batch leaves the kitchen. Record the batch number and both initials on the release sheet.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Delivery packing checklist
|
||||
|
||||
Juniper Bakery delivery drivers pack sealed biscuit cartons in reusable crates. Check the destination address and carton count before loading. Keep crates dry. The dispatch checklist records delivery counts rather than ingredient approval.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Frame claim instructions
|
||||
|
||||
Aster Cycles frame warranty covers cracked welds for five years after purchase. Send a photograph of the cracked weld and the frame serial number to the workshop. Retain the original receipt. Tyres and brake pads are wear items and are excluded.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Wear-part service prices
|
||||
|
||||
Aster Cycles routine maintenance prices: brake pad replacement costs 24 euros; tyre fitting costs 18 euros. The workshop checks tyre pressure during a service. These paid wear-part jobs do not require a warranty claim.
|
||||
@@ -0,0 +1,404 @@
|
||||
use crate::error::{ErrorKind, Position};
|
||||
use crate::identifier::Identifier;
|
||||
use crate::{BuildMetadata, Comparator, Op, Prerelease, Version, VersionReq};
|
||||
use alloc::vec::Vec;
|
||||
use core::str::FromStr;
|
||||
|
||||
/// Error parsing a SemVer version or version requirement.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use semver::Version;
|
||||
///
|
||||
/// fn main() {
|
||||
/// let err = Version::parse("1.q.r").unwrap_err();
|
||||
///
|
||||
/// // "unexpected character 'q' while parsing minor version number"
|
||||
/// eprintln!("{}", err);
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Error {
|
||||
pub(crate) kind: ErrorKind,
|
||||
}
|
||||
|
||||
impl FromStr for Version {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(text: &str) -> Result<Self, Self::Err> {
|
||||
if text.is_empty() {
|
||||
return Err(Error::new(ErrorKind::Empty));
|
||||
}
|
||||
|
||||
let mut pos = Position::Major;
|
||||
let (major, text) = numeric_identifier(text, pos)?;
|
||||
let text = dot(text, pos)?;
|
||||
|
||||
pos = Position::Minor;
|
||||
let (minor, text) = numeric_identifier(text, pos)?;
|
||||
let text = dot(text, pos)?;
|
||||
|
||||
pos = Position::Patch;
|
||||
let (patch, text) = numeric_identifier(text, pos)?;
|
||||
|
||||
if text.is_empty() {
|
||||
return Ok(Version::new(major, minor, patch));
|
||||
}
|
||||
|
||||
let (pre, text) = if let Some(text) = text.strip_prefix('-') {
|
||||
pos = Position::Pre;
|
||||
let (pre, text) = prerelease_identifier(text)?;
|
||||
if pre.is_empty() {
|
||||
return Err(Error::new(ErrorKind::EmptySegment(pos)));
|
||||
}
|
||||
(pre, text)
|
||||
} else {
|
||||
(Prerelease::EMPTY, text)
|
||||
};
|
||||
|
||||
let (build, text) = if let Some(text) = text.strip_prefix('+') {
|
||||
pos = Position::Build;
|
||||
let (build, text) = build_identifier(text)?;
|
||||
if build.is_empty() {
|
||||
return Err(Error::new(ErrorKind::EmptySegment(pos)));
|
||||
}
|
||||
(build, text)
|
||||
} else {
|
||||
(BuildMetadata::EMPTY, text)
|
||||
};
|
||||
|
||||
if let Some(unexpected) = text.chars().next() {
|
||||
return Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected)));
|
||||
}
|
||||
|
||||
Ok(Version {
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
pre,
|
||||
build,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for VersionReq {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(text: &str) -> Result<Self, Self::Err> {
|
||||
let text = text.trim_start_matches(' ');
|
||||
if let Some((ch, text)) = wildcard(text) {
|
||||
let rest = text.trim_start_matches(' ');
|
||||
if rest.is_empty() {
|
||||
return Ok(VersionReq::STAR);
|
||||
} else if rest.starts_with(',') {
|
||||
return Err(Error::new(ErrorKind::WildcardNotTheOnlyComparator(ch)));
|
||||
} else {
|
||||
return Err(Error::new(ErrorKind::UnexpectedAfterWildcard));
|
||||
}
|
||||
}
|
||||
|
||||
let depth = 0;
|
||||
let mut comparators = Vec::new();
|
||||
let len = version_req(text, &mut comparators, depth)?;
|
||||
unsafe { comparators.set_len(len) }
|
||||
Ok(VersionReq { comparators })
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Comparator {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(text: &str) -> Result<Self, Self::Err> {
|
||||
let text = text.trim_start_matches(' ');
|
||||
let (comparator, pos, rest) = comparator(text)?;
|
||||
if !rest.is_empty() {
|
||||
let unexpected = rest.chars().next().unwrap();
|
||||
return Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected)));
|
||||
}
|
||||
Ok(comparator)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Prerelease {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(text: &str) -> Result<Self, Self::Err> {
|
||||
let (pre, rest) = prerelease_identifier(text)?;
|
||||
if !rest.is_empty() {
|
||||
return Err(Error::new(ErrorKind::IllegalCharacter(Position::Pre)));
|
||||
}
|
||||
Ok(pre)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for BuildMetadata {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(text: &str) -> Result<Self, Self::Err> {
|
||||
let (build, rest) = build_identifier(text)?;
|
||||
if !rest.is_empty() {
|
||||
return Err(Error::new(ErrorKind::IllegalCharacter(Position::Build)));
|
||||
}
|
||||
Ok(build)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
fn new(kind: ErrorKind) -> Self {
|
||||
Error { kind }
|
||||
}
|
||||
}
|
||||
|
||||
impl Op {
|
||||
const DEFAULT: Self = Op::Caret;
|
||||
}
|
||||
|
||||
fn numeric_identifier(input: &str, pos: Position) -> Result<(u64, &str), Error> {
|
||||
let mut len = 0;
|
||||
let mut value = 0u64;
|
||||
|
||||
while let Some(&digit) = input.as_bytes().get(len) {
|
||||
if digit < b'0' || digit > b'9' {
|
||||
break;
|
||||
}
|
||||
if value == 0 && len > 0 {
|
||||
return Err(Error::new(ErrorKind::LeadingZero(pos)));
|
||||
}
|
||||
match value
|
||||
.checked_mul(10)
|
||||
.and_then(|value| value.checked_add((digit - b'0') as u64))
|
||||
{
|
||||
Some(sum) => value = sum,
|
||||
None => return Err(Error::new(ErrorKind::Overflow(pos))),
|
||||
}
|
||||
len += 1;
|
||||
}
|
||||
|
||||
if len > 0 {
|
||||
Ok((value, &input[len..]))
|
||||
} else if let Some(unexpected) = input[len..].chars().next() {
|
||||
Err(Error::new(ErrorKind::UnexpectedChar(pos, unexpected)))
|
||||
} else {
|
||||
Err(Error::new(ErrorKind::UnexpectedEnd(pos)))
|
||||
}
|
||||
}
|
||||
|
||||
fn wildcard(input: &str) -> Option<(char, &str)> {
|
||||
if let Some(rest) = input.strip_prefix('*') {
|
||||
Some(('*', rest))
|
||||
} else if let Some(rest) = input.strip_prefix('x') {
|
||||
Some(('x', rest))
|
||||
} else if let Some(rest) = input.strip_prefix('X') {
|
||||
Some(('X', rest))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn dot(input: &str, pos: Position) -> Result<&str, Error> {
|
||||
if let Some(rest) = input.strip_prefix('.') {
|
||||
Ok(rest)
|
||||
} else if let Some(unexpected) = input.chars().next() {
|
||||
Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected)))
|
||||
} else {
|
||||
Err(Error::new(ErrorKind::UnexpectedEnd(pos)))
|
||||
}
|
||||
}
|
||||
|
||||
fn prerelease_identifier(input: &str) -> Result<(Prerelease, &str), Error> {
|
||||
let (string, rest) = identifier(input, Position::Pre)?;
|
||||
let identifier = unsafe { Identifier::new_unchecked(string) };
|
||||
Ok((Prerelease { identifier }, rest))
|
||||
}
|
||||
|
||||
fn build_identifier(input: &str) -> Result<(BuildMetadata, &str), Error> {
|
||||
let (string, rest) = identifier(input, Position::Build)?;
|
||||
let identifier = unsafe { Identifier::new_unchecked(string) };
|
||||
Ok((BuildMetadata { identifier }, rest))
|
||||
}
|
||||
|
||||
fn identifier(input: &str, pos: Position) -> Result<(&str, &str), Error> {
|
||||
let mut accumulated_len = 0;
|
||||
let mut segment_len = 0;
|
||||
let mut segment_has_nondigit = false;
|
||||
|
||||
loop {
|
||||
match input.as_bytes().get(accumulated_len + segment_len) {
|
||||
Some(b'A'..=b'Z') | Some(b'a'..=b'z') | Some(b'-') => {
|
||||
segment_len += 1;
|
||||
segment_has_nondigit = true;
|
||||
}
|
||||
Some(b'0'..=b'9') => {
|
||||
segment_len += 1;
|
||||
}
|
||||
boundary => {
|
||||
if segment_len == 0 {
|
||||
if accumulated_len == 0 && boundary != Some(&b'.') {
|
||||
return Ok(("", input));
|
||||
} else {
|
||||
return Err(Error::new(ErrorKind::EmptySegment(pos)));
|
||||
}
|
||||
}
|
||||
if pos == Position::Pre
|
||||
&& segment_len > 1
|
||||
&& !segment_has_nondigit
|
||||
&& input[accumulated_len..].starts_with('0')
|
||||
{
|
||||
return Err(Error::new(ErrorKind::LeadingZero(pos)));
|
||||
}
|
||||
accumulated_len += segment_len;
|
||||
if boundary == Some(&b'.') {
|
||||
accumulated_len += 1;
|
||||
segment_len = 0;
|
||||
segment_has_nondigit = false;
|
||||
} else {
|
||||
return Ok(input.split_at(accumulated_len));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn op(input: &str) -> (Op, &str) {
|
||||
let bytes = input.as_bytes();
|
||||
if bytes.first() == Some(&b'=') {
|
||||
(Op::Exact, &input[1..])
|
||||
} else if bytes.first() == Some(&b'>') {
|
||||
if bytes.get(1) == Some(&b'=') {
|
||||
(Op::GreaterEq, &input[2..])
|
||||
} else {
|
||||
(Op::Greater, &input[1..])
|
||||
}
|
||||
} else if bytes.first() == Some(&b'<') {
|
||||
if bytes.get(1) == Some(&b'=') {
|
||||
(Op::LessEq, &input[2..])
|
||||
} else {
|
||||
(Op::Less, &input[1..])
|
||||
}
|
||||
} else if bytes.first() == Some(&b'~') {
|
||||
(Op::Tilde, &input[1..])
|
||||
} else if bytes.first() == Some(&b'^') {
|
||||
(Op::Caret, &input[1..])
|
||||
} else {
|
||||
(Op::DEFAULT, input)
|
||||
}
|
||||
}
|
||||
|
||||
fn comparator(input: &str) -> Result<(Comparator, Position, &str), Error> {
|
||||
let (mut op, text) = op(input);
|
||||
let default_op = input.len() == text.len();
|
||||
let text = text.trim_start_matches(' ');
|
||||
|
||||
let mut pos = Position::Major;
|
||||
let (major, text) = numeric_identifier(text, pos)?;
|
||||
let mut has_wildcard = false;
|
||||
|
||||
let (minor, text) = if let Some(text) = text.strip_prefix('.') {
|
||||
pos = Position::Minor;
|
||||
if let Some((_, text)) = wildcard(text) {
|
||||
has_wildcard = true;
|
||||
if default_op {
|
||||
op = Op::Wildcard;
|
||||
}
|
||||
(None, text)
|
||||
} else {
|
||||
let (minor, text) = numeric_identifier(text, pos)?;
|
||||
(Some(minor), text)
|
||||
}
|
||||
} else {
|
||||
(None, text)
|
||||
};
|
||||
|
||||
let (patch, text) = if let Some(text) = text.strip_prefix('.') {
|
||||
pos = Position::Patch;
|
||||
if let Some((_, text)) = wildcard(text) {
|
||||
if default_op {
|
||||
op = Op::Wildcard;
|
||||
}
|
||||
(None, text)
|
||||
} else if has_wildcard {
|
||||
return Err(Error::new(ErrorKind::UnexpectedAfterWildcard));
|
||||
} else {
|
||||
let (patch, text) = numeric_identifier(text, pos)?;
|
||||
(Some(patch), text)
|
||||
}
|
||||
} else {
|
||||
(None, text)
|
||||
};
|
||||
|
||||
let (pre, text) = if patch.is_some() && text.starts_with('-') {
|
||||
pos = Position::Pre;
|
||||
let text = &text[1..];
|
||||
let (pre, text) = prerelease_identifier(text)?;
|
||||
if pre.is_empty() {
|
||||
return Err(Error::new(ErrorKind::EmptySegment(pos)));
|
||||
}
|
||||
(pre, text)
|
||||
} else {
|
||||
(Prerelease::EMPTY, text)
|
||||
};
|
||||
|
||||
let text = if patch.is_some() && text.starts_with('+') {
|
||||
pos = Position::Build;
|
||||
let text = &text[1..];
|
||||
let (build, text) = build_identifier(text)?;
|
||||
if build.is_empty() {
|
||||
return Err(Error::new(ErrorKind::EmptySegment(pos)));
|
||||
}
|
||||
text
|
||||
} else {
|
||||
text
|
||||
};
|
||||
|
||||
let text = text.trim_start_matches(' ');
|
||||
|
||||
let comparator = Comparator {
|
||||
op,
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
pre,
|
||||
};
|
||||
|
||||
Ok((comparator, pos, text))
|
||||
}
|
||||
|
||||
fn version_req(input: &str, out: &mut Vec<Comparator>, depth: usize) -> Result<usize, Error> {
|
||||
let (comparator, pos, text) = match comparator(input) {
|
||||
Ok(success) => success,
|
||||
Err(mut error) => {
|
||||
if let Some((ch, mut rest)) = wildcard(input) {
|
||||
rest = rest.trim_start_matches(' ');
|
||||
if rest.is_empty() || rest.starts_with(',') {
|
||||
error.kind = ErrorKind::WildcardNotTheOnlyComparator(ch);
|
||||
}
|
||||
}
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
|
||||
if text.is_empty() {
|
||||
out.reserve_exact(depth + 1);
|
||||
unsafe { out.as_mut_ptr().add(depth).write(comparator) }
|
||||
return Ok(depth + 1);
|
||||
}
|
||||
|
||||
let text = if let Some(text) = text.strip_prefix(',') {
|
||||
text.trim_start_matches(' ')
|
||||
} else {
|
||||
let unexpected = text.chars().next().unwrap();
|
||||
return Err(Error::new(ErrorKind::ExpectedCommaFound(pos, unexpected)));
|
||||
};
|
||||
|
||||
const MAX_COMPARATORS: usize = 32;
|
||||
if depth + 1 == MAX_COMPARATORS {
|
||||
return Err(Error::new(ErrorKind::ExcessiveComparators));
|
||||
}
|
||||
|
||||
// Recurse to collect parsed Comparator objects on the stack. We perform a
|
||||
// single allocation to allocate exactly the right sized Vec only once the
|
||||
// total number of comparators is known.
|
||||
let len = version_req(text, out, depth + 1)?;
|
||||
unsafe { out.as_mut_ptr().add(depth).write(comparator) }
|
||||
Ok(len)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Volunteer crossing rota
|
||||
|
||||
The Alder Ferry volunteer rota assigns two qualified crew members to each Saturday crossing. The skipper confirms attendance by Thursday evening. If either crew member is unavailable, cancel the crossing and notify booked passengers.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Passenger bicycle fares
|
||||
|
||||
Alder Ferry charges passengers two euros and bicycles one euro per crossing. Children under six travel free with an adult. The fare sheet covers ticket prices and does not assign crew or confirm sailing availability.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Tank controller dry-run protection
|
||||
|
||||
The Linden Garden rainwater controller stops the irrigation pump when the tank level falls below twelve centimetres. Refill above twenty centimetres before resetting the controller. This prevents the pump running dry. Record each low-water shutdown in the garden log.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Sprinkler winter storage
|
||||
|
||||
Linden Garden volunteers drain sprinkler hoses before the first frost. Disconnect the hose couplings and store the dry hoses in the tool shed. This winter storage checklist does not configure the tank sensor or controller.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Moving-day meter record
|
||||
|
||||
Residents of Rowan Court photograph the electricity meter on moving day. The photograph must show the reading and the meter identifier. Send it with the handover date to the property office within three days. The office uses the reading for the departing resident’s final bill.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Annual radiator inspection
|
||||
|
||||
Rowan Court schedules radiator inspections every November. A technician checks valves for leaks and trapped air. Residents arrange access between 09:00 and 15:00. The maintenance log records repairs, not electricity billing.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Loan transit cover
|
||||
|
||||
The Elm Museum loan agreement requires insurance during both outward and return transport of the ceramic bowl. The borrower supplies an insurance certificate naming the museum before collection. Coverage is for the agreed replacement value of 12000 euros.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Gallery humidity record
|
||||
|
||||
Elm Museum conservators record gallery relative humidity twice each day. Ceramic display shelves are checked for dust every Friday. The room log records measurements and cleaning; it is not the loan or transport insurance agreement.
|
||||
Reference in new issue
Block a user