{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
module Data.Text.BoyerMoore.Searcher
( Searcher
, build
, buildWithValues
, needles
, numNeedles
, automata
, caseSensitivity
, containsAny
, setSearcherCaseSensitivity
)
where
import Control.DeepSeq (NFData)
import Data.Bifunctor (first)
import Data.Hashable (Hashable (hashWithSalt), Hashed, hashed, unhashed)
import Data.Text (Text)
import GHC.Generics (Generic)
import Data.Text.BoyerMoore.Automaton (Automaton, CaseSensitivity (..))
import qualified Data.Text.BoyerMoore.Automaton as BoyerMoore
data Searcher v = Searcher
{ searcherCaseSensitive :: CaseSensitivity
, searcherNeedles :: Hashed [(Text, v)]
, searcherNumNeedles :: Int
, searcherAutomata :: [(Automaton, v)]
} deriving (Generic)
instance Show (Searcher v) where
show _ = "Searcher _ _ _"
instance Hashable v => Hashable (Searcher v) where
hashWithSalt salt searcher = hashWithSalt salt $ searcherNeedles searcher
{-# INLINE hashWithSalt #-}
instance Eq v => Eq (Searcher v) where
Searcher cx xs nx _ == Searcher cy ys ny _ = (cx, nx, xs) == (cy, ny, ys)
{-# INLINE (==) #-}
instance NFData v => NFData (Searcher v)
build :: CaseSensitivity -> [Text] -> Searcher ()
{-# INLINABLE build #-}
build case_ = buildWithValues case_ . flip zip (repeat ())
buildWithValues :: Hashable v => CaseSensitivity -> [(Text, v)] -> Searcher v
{-# INLINABLE buildWithValues #-}
buildWithValues case_ ns =
Searcher case_ (hashed ns) (length ns) $ map (first BoyerMoore.buildAutomaton) ns
needles :: Searcher v -> [(Text, v)]
needles = unhashed . searcherNeedles
automata :: Searcher v -> [(Automaton, v)]
automata = searcherAutomata
numNeedles :: Searcher v -> Int
numNeedles = searcherNumNeedles
caseSensitivity :: Searcher v -> CaseSensitivity
caseSensitivity = searcherCaseSensitive
setSearcherCaseSensitivity :: CaseSensitivity -> Searcher v -> Searcher v
setSearcherCaseSensitivity case_ searcher = searcher{
searcherCaseSensitive = case_
}
{-# NOINLINE containsAny #-}
containsAny :: Searcher () -> Text -> Bool
containsAny !searcher !text =
let
f _acc _match = BoyerMoore.Done True
in
case caseSensitivity searcher of
CaseSensitive ->
any (\(automaton, ()) -> BoyerMoore.runText False f automaton text) (automata searcher)
IgnoreCase ->
any (\(automaton, ()) -> BoyerMoore.runLower False f automaton text) (automata searcher)