grep-hyperscan 0.0.1

Use hyperscan with the 'grep' crate.
Documentation
use std::mem;
use std::cell::RefMut;
use std::collections::VecDeque;

use grep_matcher::Match;
use hyperscan::{
    ScanFlags, Scannable, MatchEventCallback, MatchEventCallbackMut, Scratch,
    RawScratch, BlockDatabase, BlockScanner
};

/// `MatchData` stores the search parameters for the haystack grep is currently
/// requesting.
#[derive(Debug)]
pub struct MatchData {
    /// `VecDeque` is used to store results in a haystack the first time
    /// `first_at` is called.
    pub results: VecDeque<Match>,
    /// The len of the haystack the thread is currently analysing.
    haystack_len: usize,
    /// We need to know when we answer `Ok`/`None` to hyperscan so that we know
    /// when a new haystack comes.
    started: bool,
}

impl MatchData {
    /// Create a new scratchpad if a new thread appeared.
    pub fn new_match_data() -> MatchData {
        MatchData {
            // We assume we are not going to have hundreds of threads,
            // so allocating a VecDeque of size 100 for the results per thread
            // seems OK.
            results: VecDeque::with_capacity(100),
            started: false,
            haystack_len: 0,
        }
    }

    /// Signal that we finished sending results for the current haystack and the
    /// next call to `find_at` will be on a new haystack.
    pub fn stop(&mut self) {
        self.started = false;
    }

    /// Check if we need to restart an hyperscan run in case of a new haystack.
    /// Sometimes `find_at` get sent a new haystack while we are already started.
    /// It seems to only be the same haystack with EOL added at the end, or the
    /// right-most part of the original haystack. Thus checking the length only
    /// should be ok.
    pub fn should_restart(&self, len : usize) -> bool {
        len != self.haystack_len || !self.started
    }

    /// Reset the object for a new haystack of len len.
    pub fn restart(&mut self, len : usize) {
        self.haystack_len = len;
        self.started = true;
        self.results.clear();
    }
}

/// The actual callback (fn pointer) sent to hyperscan.
pub fn hyperscan_callback(
    _: u32,
    from: u64,
    to: u64,
    _: u32,
    this: &mut RefMut<MatchData>
) -> u32 {
    this.results.push_back(Match::new(from as usize, to as usize));
    0
}


/// The rust-hyperscan library didn't assume the use of a scratchbuffer through
/// multiple threads, so `RawScratch` can not be sent accross thread boundaries.
/// `ThreadScratch` defines a thread safe type of scratchbuffer that can be used
/// accross different threads (as long as the callback doesn't move the
/// scratchbuffer from one thread to the other).
#[derive(Debug)]
pub struct ThreadScratch {
    pub s: RawScratch,
}

/// SAFETY: Since `RawScratch` is just an interface to a scratchpad, the only
/// guaranty we need is to be able to send a scratchpad from one thread to the
/// other. According to
/// https://intel.github.io/hyperscan/dev-reference/runtime.html?highlight=thread#scratch-space
/// scratchpads can be moved from one thread to the other.
/// This is assuming that the hyperscan callback doesn't move the scratchbuffer
/// (unsure how to formally enforce that with rust ?), but this is impossible
/// with our current `MatchData` implementation.
unsafe impl Send for ThreadScratch {}

/// Extension trait for a scanner allowing the hyperscan callback
/// to mut its context and not itself.
///
/// By default, the context is not mutable because of trait generality for
/// vectored/streaming modes. However with the block mode we can mut it
/// as it is done in hyperscan::BlockScanner trait definition.
pub trait ScanMutCtxtExt<T: Scannable, S: Scratch> : BlockScanner<T,S> {
    /// This is the function call in which the actual pattern matching
    /// takes place for databases defining this trait.
    fn scan_mut_ctxt<D>(
        &self,
        data: T,
        flags: ScanFlags,
        scratch: &S,
        callback: Option<MatchEventCallbackMut<D>>,
        context: Option<&mut D>,
    ) -> Result<&Self, hyperscan::Error> {
        self.scan(
            data,
            flags,
            scratch,
            // SAFETY: see hyperscanner::BlockScanner::scan_mut default
            // implementation. `MatchEventCallback` is just a type used for
            // different mode generality.
            callback.map(|f| unsafe {
                mem::transmute::<MatchEventCallbackMut<D>, MatchEventCallback<D>>(f)
            }),
            context.map(|v| &*v),
        )
    }
}

/// Implements the `ScanMutCtxtExt` trait for a `BlockDatabase`
impl<T: Scannable, S: Scratch> ScanMutCtxtExt<T,S> for BlockDatabase {}