@@ -0,0 +1,115 @@
1+{-# LANGUAGE BangPatterns, ScopedTypeVariables #-}
4+-- Module : Network.Wai.Search
5+-- Copyright : (c) Bryan O'Sullivan 2009
8+-- Maintainer : bos@serpentine.com, rtomharper@googlemail.com,
10+-- Stability : experimental
13+-- Port of Data.Text.Search to ByteStrings, applied to the Wai framework
17+-- * R. S. Boyer, J. S. Moore: A Fast String Searching Algorithm.
18+-- Communications of the ACM, 20, 10, 762-772 (1977)
20+-- * R. N. Horspool: Practical Fast Searching in Strings. Software -
21+-- Practice and Experience 10, 501-506 (1980)
23+-- * D. M. Sunday: A Very Fast Substring Search Algorithm.
24+-- Communications of the ACM, 33, 8, 132-142 (1990)
26+-- * F. Lundh: The Fast Search Algorithm.
27+-- <http://effbot.org/zone/stringlib.htm> (2006)
29+module Network.Wai.Search
36+import qualified Data.ByteString.Unsafe as SU
37+import qualified Data.ByteString as S
38+import Data.ByteString ( ByteString )
39+import Data.Word (Word64)
40+import Data.Bits ((.|.), (.&.), shiftL)
42+data PairS a b = !a :*: !b
44+-- | /O(n+m)/ Find the offset of @needle@ within @haystack@. The offset
45+-- returned represents the location within the haystack ByteString
47+-- In (unlikely) bad cases, this algorithm's complexity degrades
49+index :: ByteString -- ^ Substring to search for (@needle@)
50+ -> ByteString -- ^ Text to search in (@haystack@)
53+ | nlen == 1 = scanOne (nindex 0)
54+ | nlen <= 0 || ldiff < 0 = Nothing
57+ nlen = S.length needle
58+ hlen = S.length haystack
62+ nindex k = SU.unsafeIndex needle k
63+ hindex k = SU.unsafeIndex haystack k
64+ hindex' k | k == hlen = 0
65+ | otherwise = SU.unsafeIndex haystack k
66+ (mask :: Word64) :*: skip = buildTable 0 0 (nlen-2)
67+ buildTable !i !msk !skp
68+ | i >= nlast = (msk .|. swizzle z) :*: skp
69+ | otherwise = buildTable (i+1) (msk .|. swizzle c) skp'
71+ skp' | c == z = nlen - i - 2
73+ swizzle k = 1 `shiftL` (fromIntegral k .&. 0x3f)
75+ | i > ldiff = Nothing
76+ | c == z && candidateMatch 0 = Just i
77+ | otherwise = scan (i + delta)
78+ where c = hindex (i + nlast)
81+ | hindex (i+j) /= nindex j = False
82+ | otherwise = candidateMatch (j+1)
83+ delta | nextInPattern = nlen + 1
86+ nextInPattern = mask .&. swizzle (hindex' (i+nlen)) == 0
88+ where loop !i | i >= hlen = Nothing
89+ | hindex i == c = Just i
90+ | otherwise = loop (i+1)
93+-- It's not likely that one arbitrary string will ever be a substring of some
94+-- other string, so this is pretty much a negative test
96+checkNeg :: ByteString -> ByteString -> Bool
99+ | otherwise = (S.findSubstring n h) == (index n h)
102+-- Make sure that index works the same as ByteString.findSubstring when we
105+checkFound :: ByteString -> Int -> Int -> Bool
106+checkFound bs offset len
107+ | S.null n = True -- cop out
108+ | otherwise = case index n bs of
112+ n = S.take len $ S.drop offset bs