grep-hyperscan 0.0.1

Use hyperscan with the 'grep' crate.
Documentation
use std::cell::RefCell;
use std::sync::Arc;
use thread_local::CachedThreadLocal;

use grep_matcher::{NoCaptures, Match, Matcher};
use hyperscan::{
    CompileFlags, pattern, Patterns, ScratchAllocator,
    DatabaseBuilder, BlockDatabase
};
use crate::hyperscan_bridge::{
    ThreadScratch, MatchData, ScanMutCtxtExt,
    hyperscan_callback
};

/// A builder for configuring the compilation of a hyperscan db.
#[derive(Clone, Debug)]
pub struct RegexMatcherBuilder {
    //TODO
}

impl Default for RegexMatcherBuilder {
    fn default() -> Self {
        RegexMatcherBuilder {
            //TODO
        }
    }
}

impl RegexMatcherBuilder {
    /// Create a new matcher builder with a default configuration.
    pub fn new() -> RegexMatcherBuilder {
        Default::default()
    }

    /// Compile the given pattern into an hyperscan db using the current
    /// configuration.
    ///
    /// If there was a problem compiling the pattern, then an error is
    /// returned.
    pub fn build(
        &self,
        patterns: &[String]
    ) -> Result<RegexMatcher, hyperscan::Error> {
        let compile_flags = CompileFlags(hyperscan::HS_FLAG_SOM_LEFTMOST);
        let parsed: Patterns = patterns.iter()
                                       .enumerate()
                                       .map(|(i,p)| pattern!(p, flags => compile_flags, id => i))
                                       .collect();
        Ok(RegexMatcher {
            db : Arc::new(parsed.build()?),
            match_data: CachedThreadLocal::new(),
            scratchpad: CachedThreadLocal::new(),
        })
    }
}


/// An implementation of the `Matcher` trait using hyperscan.
#[derive(Debug)]
pub struct RegexMatcher {
    /// The compiled list of signature inside a thread safe pointer to a thread
    /// safe database.
    db : Arc<BlockDatabase>,

    /// Keep data on the current haystack matching state.
    /// Since different threads can match different haystack, we need to keep
    /// match data separate for each thread.
    match_data: CachedThreadLocal<RefCell<MatchData>>,

    /// Scratchpad used by hyperscan. Per hyperscan code, a scratchbuffer can be
    /// reused between different run, but each thread mush have its own
    /// scratchbuffer.
    scratchpad: CachedThreadLocal<ThreadScratch>,
}

/// A thread safe implementation of Clone for our hyperscan `RegexMatcher`
/// (1 scratchpad / thread)
impl Clone for RegexMatcher {
    fn clone(&self) -> Self {
        RegexMatcher {
            db : self.db.clone(),
            match_data: CachedThreadLocal::new(),
            scratchpad: CachedThreadLocal::new(),
        }
    }
}

impl RegexMatcher {
    /// Create a new matcher from the given pattern using the default
    /// configuration.
    pub fn new(patterns: &[String]) -> Result<RegexMatcher, hyperscan::Error> {
        RegexMatcherBuilder::new().build(patterns)
    }
}

impl Matcher for RegexMatcher {
    type Captures = NoCaptures;
    type Error = hyperscan::Error;

    /// Main implementation. Just try to get the current MatchData for the
    /// thread before calling the actual `find_at` function.
    /// Note that we don't get the thread data for the scratchpad yet, as we
    /// can still read data from hyperscan result or from the buffered results.
    fn find_at(
        &self,
        haystack: &[u8],
        at: usize,
    ) -> Result<Option<Match>, Self::Error> {
        self.find_at_with_match_data(self.match_data(), haystack, at)
    }

    /// Hyperscan can not match captures, hence the `NoCaptures`
    fn new_captures(&self) -> Result<NoCaptures, Self::Error> {
        Ok(NoCaptures::new())
    }
}

/// Thread safety methods
impl RegexMatcher {

    /// Acquire or create a new local ref for `match_data`
    fn match_data(&self) -> &RefCell<MatchData> {
        let create = || RefCell::new(MatchData::new_match_data());
        self.match_data.get_or(create)
    }

    /// Acquire or create a new local ref for a scratchpad
    /// This is where we allocate the scratchpad if needed.
    fn scratchpad(&self) -> Result<&ThreadScratch, hyperscan::Error> {
        let create = || Ok(ThreadScratch { s : self.db.alloc()? });
        self.scratchpad.get_or_try(create)
    }

    /// Like `find_at`, but accepts match data instead of acquiring one itself.
    /// Start the matching if it's a new block, then iterate over the matches
    #[inline(always)]
    fn find_at_with_match_data<'s>(
        &self,
        match_data: &RefCell<MatchData>,
        subject: &'s [u8],
        start: usize,
    ) -> Result<Option<Match>, hyperscan::Error> {
        let mut match_data = match_data.borrow_mut();
        if match_data.should_restart(subject.len()) {
            match_data.restart(subject.len());
            let scratchpad = self.scratchpad()?;
            self.db.scan_mut_ctxt(subject, 0,
                                    &scratchpad.s,
                                     Some(hyperscan_callback),
                                     Some(&mut match_data))?;
        }

        //consumes results until we have a match after start
        while let Some(m) = match_data.results.pop_front() {
            if m.start() >= start {
                return Ok(Some(m));
            }
        }

        match_data.stop();
        Ok(None)
    }
}