alfred-margaret 2.1.0.2 → 2.1.1.0
raw patch · 12 files changed
+202/−180 lines, 12 filesdep ~containers
Dependency ranges changed: containers
Files
- CHANGELOG.md +76/−0
- alfred-margaret.cabal +7/−4
- src/Data/Primitive/Extended.hs +21/−0
- src/Data/Text/AhoCorasick/Automaton.hs +17/−15
- src/Data/Text/AhoCorasick/Replacer.hs +1/−1
- src/Data/Text/AhoCorasick/Splitter.hs +5/−0
- src/Data/Text/BoyerMoore/Automaton.hs +20/−13
- src/Data/Text/BoyerMoore/Replacer.hs +4/−0
- src/Data/Text/BoyerMooreCI/Automaton.hs +46/−35
- src/Data/Text/BoyerMooreCI/Replacer.hs +4/−0
- src/Data/Text/Utf8.hs +1/−0
- src/Data/TypedByteArray.hs +0/−112
+ CHANGELOG.md view
@@ -0,0 +1,76 @@+# Changelog++## v2.1.1.0 - "Generically Primitive" (2026-04-24)++Tested on GHC 9.14.1.++- Derive `Generic` for the exposed data types in `Data.Text.AhoCorasick.*`,+ `Data.Text.BoyerMoore.*`, `Data.Text.BoyerMooreCI.*` and+ `Data.Text.CaseSensitivity` ([#65](https://github.com/channable/alfred-margaret/pull/65)).+- Replace the internal `Data.TypedByteArray` wrapper with `PrimArray` from+ `Data.Primitive`; introduces the new internal module `Data.Primitive.Extended`.+- Widen `containers` upper bound to include `0.8`.+- Toolchain: switch development environment and CI to GHC 9.14 and LLVM 19;+ add `direnv` support; bump pinned `nixpkgs` and Nix to 2.24.12.+- Update `tested-with` to GHC 9.14.1.++## v2.1.0.2 - "All the bounds!" (2024-09-06)++Publishing requires `cabal` to know the version bounds as well.++## v2.1.0.1 - "Higher bounds!" (2024-09-05)++Tested on GHC 9.6.6.++- Revise dependency bounds ([#62](https://github.com/channable/alfred-margaret/issues/62) thanks @Bodigrim)+- Allow using primitive < 0.9 and vector < 0.14 ([#59](https://github.com/channable/alfred-margaret/pull/59) thanks @rampion)++## v2.1.0.0 - "All The Cases!" (2022-08-31)++- Added a case-insensitive variant of the Boyer-Moore algorithm in the `Data.Text.BoyerMooreCI.*` modules. ([#47](https://github.com/channable/alfred-margaret/pull/47))+- Fixed a bug in the case-insensitive Aho-Corasick replacer where it would+ replace the wrong section of the haystack when the needle had a different+ byte-length than the matching part of the haystack. ([#47](https://github.com/channable/alfred-margaret/pull/47))+- Allow mapping the payloads of Aho-Corasick automatons. ([#46](https://github.com/channable/alfred-margaret/pull/46))++## v2.0.0.0 - "So Long Surrogates" (2022-05-02)++Switched to text-2.0 which uses UTF-8 encoding internally.++- Removed `Data.Text.Utf8.*` modules+- Replaced `Data.Text.AhoCorasick.*` and `Data.Text.BoyerMoore.*` (previously using UTF-16) with the UTF-8 implementation++## v1.1.2.0 - "ByteArray Boogaloo" (2022-04-21)++Added UTF-8 implementations on a mock `Text` type (in `Data.Text.Utf8`).++- Added `Data.Text.Utf8*` modules+- Moved `CaseSensitivity` to its own `Data.Text.CaseSensitivity` module.+- Added the private module `Data.TypedByteArray` which contains thin wrappers over `ByteArray` and `MutableByteArray`.+- Replaced uses of `Data.Vector.Unboxed.Vector` by `TypedByteArray`.++## v1.1.0.0 - "Moore Features" (2020-10-13)++The most notable addition in this release is the implementation of the Boyer-Moore string search algorithm.++**Compatibility:**++- Extracted the UTF-16 manipulation functions from `Data.Text.AhoCorasick.Automaton` into `Data.Text.Utf16`+- Changed `Data.Text.AhoCorasick.Searcher.Searcher` to remember the case sensitivity used for constructing the searcher+- Removed `Data.Text.AhoCorasick.Searcher.containsAnyIgnoreCase`, the correct implementation is now chosen by `containsAny` based on the case sensitivity of the searcher++Other changes:++- Added `Data.Text.AhoCorasick.Splitter` for splitting a lot of text using the same needle+- Added `Data.Text.BoyerMoore.Automaton`, a UTF-16 implementation of Boyer-Moore+- Added `Data.Text.BoyerMoore.Searcher` for searching for multiple needles at once using Boyer-Moore+- Added `Data.Text.BoyerMoore.Replacer` for replacing text based on the Boyer-Moore search+- Added optional `FromJSON`/`ToJSON` instances for most types (can be toggled via `aeson` cabal flag)++## v1.0.0.0 - "Initial Release" (2019-03-19)++This is the initial open-source release.++- Added `Data.Text.AhoCorasick.Automaton`, a UTF-16 implementation of the Aho-Corasick search algorithm+- Added `Data.Text.AhoCorasick.Searcher`, a bulk search abstraction based on Aho-Corasick+- Added `Data.Text.AhoCorasick.Replacer`, a bulk replace abstraction based on Aho-Corasick
alfred-margaret.cabal view
@@ -1,5 +1,5 @@ name: alfred-margaret-version: 2.1.0.2+version: 2.1.1.0 synopsis: Fast Aho-Corasick string searching description: An efficient implementation of the Aho-Corasick string searching algorithm.@@ -12,6 +12,7 @@ category: Data, Text build-type: Simple extra-source-files: README.md+ , CHANGELOG.md , performance.png cabal-version: >=1.10 tested-with:@@ -23,6 +24,8 @@ , GHC == 9.0.2 -- Nixpkgs unstable (Updated 2024-09-05) , GHC == 9.6.6+ -- Nixpkgs unstable (Updated 2026-04-22)+ , GHC == 9.14.1 source-repository head type: git@@ -57,10 +60,10 @@ , Data.Text.BoyerMooreCI.Automaton , Data.Text.BoyerMooreCI.Replacer , Data.Text.BoyerMooreCI.Searcher- , Data.TypedByteArray+ other-modules: Data.Primitive.Extended build-depends: base >= 4.7 && < 5- , containers >= 0.6 && < 0.8+ , containers >= 0.6 && < 0.9 , deepseq >= 1.4 && < 1.6 , hashable >= 1.4.0.2 && < 1.6 , primitive >= 0.6.4 && < 0.10@@ -76,7 +79,7 @@ cpp-options: -DHAS_AESON } if flag(llvm) {- ghc-options: -fllvm -optlo=-O3 -optlo=-tailcallelim+ ghc-options: -fllvm -optlo=-passes=default<O3> } test-suite test-suite
+ src/Data/Primitive/Extended.hs view
@@ -0,0 +1,21 @@+-- | "Data.Primitive" extended with extra definitions.+--+-- Based on the ".Extended Modules" pattern:+-- https://jaspervdj.be/posts/2015-01-20-haskell-design-patterns-extended-modules.html+module Data.Primitive.Extended+ ( module Data.Primitive+ , replicateMutablePrimArray+ )+ where++import Data.Primitive+import Control.Monad.Primitive (PrimMonad(..))++-- | Like 'replicatePrimArray', but does not freeze the array afterwards and+-- stays within a monadic context, so it can easily be mutated further.+{-# INLINE replicateMutablePrimArray #-}+replicateMutablePrimArray :: (Prim a, PrimMonad m) => Int -> a -> m (MutablePrimArray (PrimState m) a)+replicateMutablePrimArray len value = do+ arr <- newPrimArray len+ setPrimArray arr 0 len value+ pure arr
src/Data/Text/AhoCorasick/Automaton.hs view
@@ -46,8 +46,14 @@ import Control.DeepSeq (NFData) import Data.Bits (Bits (shiftL, shiftR, (.&.), (.|.))) import Data.Char (chr)-import Data.Foldable (foldl') import Data.IntMap.Strict (IntMap)+import Data.Primitive.Extended+ ( Prim+ , PrimArray+ , generatePrimArray+ , indexPrimArray+ , primArrayFromList+ ) import Data.Word (Word32, Word64) import GHC.Generics (Generic) @@ -58,11 +64,9 @@ import Data.Text.CaseSensitivity (CaseSensitivity (..)) import Data.Text.Utf8 (CodePoint, CodeUnitIndex (CodeUnitIndex), Text (..))-import Data.TypedByteArray (Prim, TypedByteArray) import qualified Data.Text as Text import qualified Data.Text.Utf8 as Utf8-import qualified Data.TypedByteArray as TBA -- TYPES -- | A numbered state in the Aho-Corasick automaton.@@ -105,14 +109,14 @@ { machineValues :: !(Vector.Vector [v]) -- ^ For every state, the values associated with its needles. If the state is -- not a match state, the list is empty.- , machineTransitions :: !(TypedByteArray Transition)+ , machineTransitions :: !(PrimArray Transition) -- ^ A packed vector of transitions. For every state, there is a slice of this -- vector that starts at the offset given by `machineOffsets`, and ends at the -- first wildcard transition.- , machineOffsets :: !(TypedByteArray Offset)+ , machineOffsets :: !(PrimArray Offset) -- ^ For every state, the index into `machineTransitions` where the transition -- list for that state starts.- , machineRootAsciiTransitions :: !(TypedByteArray Transition)+ , machineRootAsciiTransitions :: !(PrimArray Transition) -- ^ A lookup table for transitions from the root state, an optimization to -- avoid having to walk all transitions, at the cost of using a bit of -- additional memory.@@ -159,11 +163,11 @@ -- the transitions for a specific state, we also produce a vector of start -- indices. All transition lists are terminated by a wildcard transition, so -- there is no need to record the length.-packTransitions :: [[Transition]] -> (TypedByteArray Transition, TypedByteArray Offset)+packTransitions :: [[Transition]] -> (PrimArray Transition, PrimArray Offset) packTransitions transitions = let- packed = TBA.fromList $ concat transitions- offsets = TBA.fromList $ map fromIntegral $ scanl (+) 0 $ fmap List.length transitions+ packed = primArrayFromList $ concat transitions+ offsets = primArrayFromList $ map fromIntegral $ scanl (+) 0 $ fmap List.length transitions in (packed, offsets) @@ -295,8 +299,8 @@ -- O(1) lookup of a transition, rather than doing a linear scan over all -- transitions. The fallback goes back to the initial state, state 0. {-# NOINLINE buildAsciiTransitionLookupTable #-}-buildAsciiTransitionLookupTable :: IntMap State -> TypedByteArray Transition-buildAsciiTransitionLookupTable transitions = TBA.generate asciiCount $ \i ->+buildAsciiTransitionLookupTable :: IntMap State -> PrimArray Transition+buildAsciiTransitionLookupTable transitions = generatePrimArray asciiCount $ \i -> case IntMap.lookup i transitions of Just state -> newTransition (Char.chr i) state Nothing -> newWildcardTransition 0@@ -384,8 +388,8 @@ at = Vector.unsafeIndex {-# INLINE uAt #-}-uAt :: Prim a => TypedByteArray a -> Int -> a-uAt = TBA.unsafeIndex+uAt :: Prim a => PrimArray a -> Int -> a+uAt = indexPrimArray -- RUNNING THE MACHINE @@ -560,5 +564,3 @@ where loop "" = [""] loop (c:cs) = (:) <$> Utf8.unlowerCodePoint c <*> loop cs--
src/Data/Text/AhoCorasick/Replacer.hs view
@@ -156,7 +156,7 @@ -- A match collected while running replacements. It is isomorphic to the Match -- reported by the automaton, but the data is arranged in a more useful way: -- as the start index and length of the match, and the replacement.-data Match = Match !CodeUnitIndex !CodeUnitIndex !Text deriving (Eq, Ord, Show)+data Match = Match !CodeUnitIndex !CodeUnitIndex !Text deriving (Eq, Ord, Show, Generic) -- | Apply replacements of all matches. Assumes that the matches are ordered by -- match position, and that no matches overlap.
src/Data/Text/AhoCorasick/Splitter.hs view
@@ -4,6 +4,8 @@ -- Licensed under the 3-clause BSD license, see the LICENSE file in the -- repository root. +{-# LANGUAGE GHC2021 #-}+{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} @@ -19,6 +21,7 @@ , splitReverseIgnoreCase ) where +import GHC.Generics (Generic) import Control.DeepSeq (NFData (..)) import Data.Function (on) import Data.Hashable (Hashable (..))@@ -46,6 +49,7 @@ { splitterAutomaton :: AcMachine () -- INVARIANT: Exactly one needle. , splitterSeparator :: Text -- INVARIANT: Equivalent to needle. }+ deriving Generic #if defined(HAS_AESON) instance AE.ToJSON Splitter where@@ -129,6 +133,7 @@ , accumFragmentStart :: !Aho.CodeUnitIndex -- ^ First byte of current fragment (that is the non-separator part) }+ deriving Generic -- | Finalizing the accumulator does more than just 'accumResult', hence this -- is a separate function.
src/Data/Text/BoyerMoore/Automaton.hs view
@@ -37,6 +37,15 @@ import Control.Monad (when) import Control.Monad.ST (runST) import Data.Hashable (Hashable (..))+import Data.Primitive.Extended+ ( Prim+ , PrimArray+ , indexPrimArray+ , newPrimArray+ , replicateMutablePrimArray+ , unsafeFreezePrimArray+ , writePrimArray+ ) import GHC.Generics (Generic) #if defined(HAS_AESON)@@ -45,10 +54,8 @@ import Data.Text.CaseSensitivity (CaseSensitivity (..)) import Data.Text.Utf8 (CodeUnit, CodeUnitIndex (..), Text)-import Data.TypedByteArray (Prim, TypedByteArray) import qualified Data.Text.Utf8 as Utf8-import qualified Data.TypedByteArray as TBA data Next a = Done !a@@ -167,7 +174,7 @@ -- | The suffix table tells us for each character of the pattern how many characters we can -- jump ahead if the match fails at that point.-newtype SuffixTable = SuffixTable (TypedByteArray Int)+newtype SuffixTable = SuffixTable (PrimArray Int) deriving stock (Generic, Show) deriving anyclass (NFData) @@ -180,7 +187,7 @@ buildSuffixTable pattern = runST $ do let patLen = Utf8.lengthUtf8 pattern - table <- TBA.newTypedByteArray $ codeUnitIndex patLen+ table <- newPrimArray $ codeUnitIndex patLen let -- Case 1: For each position of the pattern we record the shift that would align the pattern so@@ -206,7 +213,7 @@ prefixIndex | isPrefix pattern (p + 1) = p + 1 | otherwise = lastPrefixIndex- TBA.writeTypedByteArray table (codeUnitIndex p) (codeUnitIndex $ prefixIndex + patLen - 1 - p)+ writePrimArray table (codeUnitIndex p) (codeUnitIndex $ prefixIndex + patLen - 1 - p) init1 prefixIndex (p - 1) | otherwise = pure () @@ -219,21 +226,21 @@ let suffixLen = suffixLength pattern p when (Utf8.unsafeIndexCodeUnit pattern (p - suffixLen) /= Utf8.unsafeIndexCodeUnit pattern (patLen - 1 - suffixLen)) $- TBA.writeTypedByteArray table (codeUnitIndex $ patLen - 1 - suffixLen) (codeUnitIndex $ patLen - 1 - p + suffixLen)+ writePrimArray table (codeUnitIndex $ patLen - 1 - suffixLen) (codeUnitIndex $ patLen - 1 - p + suffixLen) init2 (p + 1) | otherwise = pure () init1 (patLen - 1) (patLen - 1) init2 0 - SuffixTable <$> TBA.unsafeFreezeTypedByteArray table+ SuffixTable <$> unsafeFreezePrimArray table -- | The bad char table tells us how far we may skip ahead when encountering a certain character -- in the input string. For example, if there's a character that is not contained in the pattern at -- all, we can skip ahead until after that character. data BadCharTable = BadCharTable- { badCharTableEntries :: {-# UNPACK #-} !(TypedByteArray Int)+ { badCharTableEntries :: {-# UNPACK #-} !(PrimArray Int) -- ^ The element type should be CodeUnitIndex, but there's no unboxed vector for that type, and -- defining it would be a lot of boilerplate. , badCharTablePatternLen :: CodeUnitIndex@@ -284,7 +291,7 @@ -- Initialize table with the maximum skip distance, which is the length of the pattern. -- This applies to all characters that are not part of the pattern.- asciiTable <- TBA.replicate badcharTableSize $ codeUnitIndex patLen+ asciiTable <- replicateMutablePrimArray badcharTableSize $ codeUnitIndex patLen let -- Fill the bad character table based on the rightmost occurrence of a character in the pattern.@@ -319,13 +326,13 @@ -- for(i = 0; i < patLen - 1; i++) { | i < patLen - 1 = do let patChar = Utf8.unsafeIndexCodeUnit pattern i- TBA.writeTypedByteArray asciiTable (fromIntegral patChar) (codeUnitIndex $ patLen - 1 - i)+ writePrimArray asciiTable (fromIntegral patChar) (codeUnitIndex $ patLen - 1 - i) fillTable (i + 1) | otherwise = pure () fillTable 0 - asciiTableFrozen <- TBA.unsafeFreezeTypedByteArray asciiTable+ asciiTableFrozen <- unsafeFreezePrimArray asciiTable pure BadCharTable { badCharTableEntries = asciiTableFrozen@@ -336,6 +343,6 @@ -- Helper functions for easily toggling the safety of this module -- | Read from a lookup table at the specified index.-indexTable :: Prim a => TypedByteArray a -> Int -> a+indexTable :: Prim a => PrimArray a -> Int -> a {-# INLINE indexTable #-}-indexTable = TBA.unsafeIndex+indexTable = indexPrimArray
src/Data/Text/BoyerMoore/Replacer.hs view
@@ -4,6 +4,8 @@ -- Licensed under the 3-clause BSD license, see the LICENSE file in the -- repository root. +{-# LANGUAGE GHC2021 #-}+{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleInstances #-} @@ -12,6 +14,7 @@ replaceSingleLimited ) where +import GHC.Generics (Generic) import Data.Text.Utf8 (Text) import Data.Text.BoyerMoore.Automaton (Automaton, CodeUnitIndex) @@ -89,3 +92,4 @@ , rsLength :: !CodeUnitIndex -- ^ Length of the newly build string so far, measured in CodeUnits }+ deriving Generic
src/Data/Text/BoyerMooreCI/Automaton.hs view
@@ -37,6 +37,19 @@ import Control.DeepSeq (NFData) import Control.Monad.ST (runST) import Data.Hashable (Hashable (..))+import Data.Primitive.Extended+ ( Prim+ , PrimArray+ , foldrPrimArray+ , indexPrimArray+ , newPrimArray+ , primArrayFromList+ , primArrayToList+ , replicateMutablePrimArray+ , sizeofPrimArray+ , unsafeFreezePrimArray+ , writePrimArray+ ) import Data.Text.Internal (Text (..)) import GHC.Generics (Generic) @@ -46,13 +59,11 @@ import Data.Text.CaseSensitivity (CaseSensitivity (..)) import Data.Text.Utf8 (BackwardsIter (..), CodePoint, CodeUnitIndex (..))-import Data.TypedByteArray (Prim, TypedByteArray) import qualified Data.Char as Char import qualified Data.HashMap.Strict as HashMap import qualified Data.Text as Text import qualified Data.Text.Utf8 as Utf8-import qualified Data.TypedByteArray as TBA data Next a = Done !a@@ -70,7 +81,7 @@ -- finding @aaaa@ in @aaaaa....aaaaaa@ as for each match it would scan back the whole /m/ characters -- of the pattern. data Automaton = Automaton- { automatonPattern :: !(TypedByteArray CodePoint)+ { automatonPattern :: !(PrimArray CodePoint) , automatonPatternHash :: !Int , automatonSuffixTable :: !SuffixTable , automatonBadCharLookup :: !BadCharLookup@@ -103,7 +114,7 @@ , automatonMinPatternSkip = minimumSkipForVector patternVec } where- patternVec = TBA.fromList (Text.unpack pattern_)+ patternVec = primArrayFromList (Text.unpack pattern_) -- | Finds all matches in the text, calling the match callback with the first and last byte index of -- each match of the pattern.@@ -115,7 +126,7 @@ -> a {-# INLINE runText #-} runText seed f automaton !text- | TBA.null pattern_ = seed+ | sizeofPrimArray pattern_ == 0 = seed | otherwise = alignPattern seed initialHaystackMin (initialHaystackMin + minPatternSkip - 1) where Automaton pattern_ _ suffixTable badCharTable minPatternSkip = automaton@@ -142,7 +153,7 @@ | otherwise = let !iter = Utf8.unsafeIndexAnywhereInCodePoint' (case text of Text d _ _ -> d) alignmentEnd- !patternIndex = TBA.length pattern_ - 1+ !patternIndex = sizeofPrimArray pattern_ - 1 -- End of char may be somewhere different than where we started looking !alignmentEnd' = backwardsIterEndOfChar iter in@@ -160,7 +171,7 @@ let !haystackCodePointLower = Utf8.lowerCodePoint (backwardsIterChar iter) in- case haystackCodePointLower == TBA.unsafeIndex pattern_ patternIndex of+ case haystackCodePointLower == indexPrimArray pattern_ patternIndex of True | patternIndex == 0 -> -- We found a complete match (all pattern characters matched)@@ -214,7 +225,7 @@ -- | Return the pattern that was used to construct the automaton, O(n). patternText :: Automaton -> Text-patternText = Text.pack . TBA.toList . automatonPattern+patternText = Text.pack . primArrayToList . automatonPattern -- | Number of bytes that we can skip in the haystack if we want to skip no more@@ -248,31 +259,31 @@ -- minimumSkipForVector (TBA.fromList "ab..cd") == 6 -- minimumSkipForVector (TBA.fromList "aⱥ💩") == 7 ---minimumSkipForVector :: TypedByteArray CodePoint -> CodeUnitIndex-minimumSkipForVector = TBA.foldr (\cp s -> s + minimumSkipForCodePoint cp) 0+minimumSkipForVector :: PrimArray CodePoint -> CodeUnitIndex+minimumSkipForVector = foldrPrimArray (\cp s -> s + minimumSkipForCodePoint cp) 0 -- | The suffix table tells us for each codepoint (not byte!) of the pattern how many bytes (not -- codepoints!) we can jump ahead if the match fails at that point.-newtype SuffixTable = SuffixTable (TypedByteArray CodeUnitIndex)+newtype SuffixTable = SuffixTable (PrimArray CodeUnitIndex) deriving stock (Generic) deriving anyclass (NFData) instance Show SuffixTable where- show (SuffixTable table) = "SuffixTable (TBA.toList " <> show (TBA.toList table) <> ")"+ show (SuffixTable table) = "SuffixTable (TBA.toList " <> show (primArrayToList table) <> ")" -- | Lookup an entry in the suffix table. suffixLookup :: SuffixTable -> Int -> CodeUnitIndex {-# INLINE suffixLookup #-} suffixLookup (SuffixTable table) = indexTable table -buildSuffixTable :: TypedByteArray CodePoint -> SuffixTable+buildSuffixTable :: PrimArray CodePoint -> SuffixTable buildSuffixTable pattern_ = runST $ do let- patLen = TBA.length pattern_+ patLen = sizeofPrimArray pattern_ wholePatternSkip = minimumSkipForVector pattern_ - table <- TBA.newTypedByteArray patLen+ table <- newPrimArray patLen let -- Case 1: For each position of the pattern we record the shift that would align the pattern so@@ -303,7 +314,7 @@ Nothing -> lastSkipBytes -- Skip the whole pattern _except_ the bytes for the suffix(==prefix) Just nonSkippableBytes -> wholePatternSkip - nonSkippableBytes- TBA.writeTypedByteArray table p skipBytes+ writePrimArray table p skipBytes init1 skipBytes (p - 1) | otherwise = pure () @@ -314,30 +325,30 @@ init2 p skipBytes | p < patLen - 1 = do -- If we find a suffix that ends at p, we can skip everything _after_ p.- let skipBytes' = skipBytes - minimumSkipForCodePoint (TBA.unsafeIndex pattern_ p)+ let skipBytes' = skipBytes - minimumSkipForCodePoint (indexPrimArray pattern_ p) case substringIsSuffix pattern_ p of Nothing -> pure () Just suffixLen -> do- TBA.writeTypedByteArray table (patLen - 1 - suffixLen) skipBytes'+ writePrimArray table (patLen - 1 - suffixLen) skipBytes' init2 (p + 1) skipBytes' | otherwise = pure () init1 (wholePatternSkip-1) (patLen - 1) init2 0 wholePatternSkip- TBA.writeTypedByteArray table (patLen - 1) (CodeUnitIndex 1)+ writePrimArray table (patLen - 1) (CodeUnitIndex 1) - SuffixTable <$> TBA.unsafeFreezeTypedByteArray table+ SuffixTable <$> unsafeFreezePrimArray table -- | True if the suffix of the @pattern@ starting from @pos@ is a prefix of the pattern -- For example, @suffixIsPrefix \"aabbaa\" 4 == Just 2@.-suffixIsPrefix :: TypedByteArray CodePoint -> Int -> Maybe CodeUnitIndex+suffixIsPrefix :: PrimArray CodePoint -> Int -> Maybe CodeUnitIndex suffixIsPrefix pattern_ pos = go 0 (CodeUnitIndex 0) where- suffixLen = TBA.length pattern_ - pos+ suffixLen = sizeofPrimArray pattern_ - pos go !i !skipBytes | i < suffixLen =- let prefixChar = TBA.unsafeIndex pattern_ i in- if prefixChar == TBA.unsafeIndex pattern_ (pos + i)+ let prefixChar = indexPrimArray pattern_ i in+ if prefixChar == indexPrimArray pattern_ (pos + i) then go (i + 1) (skipBytes + minimumSkipForCodePoint prefixChar) else Nothing | otherwise = Just skipBytes@@ -362,12 +373,12 @@ -- substringIsSuffix (Vector.fromList "abaacaabcbaac") 4 == Just 4 -- baac == baac -- substringIsSuffix (Vector.fromList "abaacaabcbaac") 8 == Just 1 -- c == c ---substringIsSuffix :: TypedByteArray CodePoint -> Int -> Maybe Int+substringIsSuffix :: PrimArray CodePoint -> Int -> Maybe Int substringIsSuffix pattern_ pos = go 0 where- patLen = TBA.length pattern_+ patLen = sizeofPrimArray pattern_ go i | i > pos = Nothing -- prefix==suffix, so already covered by suffixIsPrefix- | TBA.unsafeIndex pattern_ (pos - i) == TBA.unsafeIndex pattern_ (patLen - 1 - i) =+ | indexPrimArray pattern_ (pos - i) == indexPrimArray pattern_ (patLen - 1 - i) = go (i + 1) | i == 0 = Nothing -- Nothing matched | otherwise = Just i@@ -377,7 +388,7 @@ -- character in the input string. For example, if there's a character that is not contained in the -- pattern at all, we can skip ahead until after that character. data BadCharLookup = BadCharLookup- { badCharLookupTable :: {-# UNPACK #-} !(TypedByteArray CodeUnitIndex)+ { badCharLookupTable :: {-# UNPACK #-} !(PrimArray CodeUnitIndex) , badCharLookupMap :: !(HashMap.HashMap CodePoint CodeUnitIndex) , badCharLookupDefault :: !CodeUnitIndex }@@ -400,7 +411,7 @@ -buildBadCharLookup :: TypedByteArray CodePoint -> BadCharLookup+buildBadCharLookup :: PrimArray CodePoint -> BadCharLookup buildBadCharLookup pattern_ = runST $ do let@@ -408,7 +419,7 @@ -- Initialize table with the maximum skip distance, which is the length of the pattern. -- This applies to all characters that are not part of the pattern.- table <- (TBA.replicate badCharTableSize defaultSkip)+ table <- replicateMutablePrimArray badCharTableSize defaultSkip let -- Fill the bad character table based on the rightmost occurrence of a character in the pattern.@@ -449,15 +460,15 @@ let skipBytes' = skipBytes - minimumSkipForCodePoint patChar in if fromEnum patChar < badCharTableSize then do- TBA.writeTypedByteArray table (fromEnum patChar) skipBytes'+ writePrimArray table (fromEnum patChar) skipBytes' fillTable badCharMap skipBytes' patChars else let badCharMap' = HashMap.insert patChar skipBytes' badCharMap in fillTable badCharMap' skipBytes' patChars - badCharMap <- fillTable HashMap.empty defaultSkip (TBA.toList pattern_)+ badCharMap <- fillTable HashMap.empty defaultSkip (primArrayToList pattern_) - tableFrozen <- TBA.unsafeFreezeTypedByteArray table+ tableFrozen <- unsafeFreezePrimArray table pure BadCharLookup { badCharLookupTable = tableFrozen@@ -469,6 +480,6 @@ -- Helper functions for easily toggling the safety of this module -- | Read from a lookup table at the specified index.-indexTable :: Prim a => TypedByteArray a -> Int -> a+indexTable :: Prim a => PrimArray a -> Int -> a {-# INLINE indexTable #-}-indexTable = TBA.unsafeIndex+indexTable = indexPrimArray
src/Data/Text/BoyerMooreCI/Replacer.hs view
@@ -4,6 +4,8 @@ -- Licensed under the 3-clause BSD license, see the LICENSE file in the -- repository root. +{-# LANGUAGE GHC2021 #-}+{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleInstances #-} @@ -12,6 +14,7 @@ replaceSingleLimited ) where +import GHC.Generics (Generic) import Data.Text.Utf8 (Text) import Data.Text.BoyerMooreCI.Automaton (Automaton, CodeUnitIndex) @@ -87,3 +90,4 @@ , rsLength :: !CodeUnitIndex -- ^ Length of the newly build string so far, measured in CodeUnits }+ deriving Generic
src/Data/Text/Utf8.hs view
@@ -361,6 +361,7 @@ , backwardsIterEndOfChar :: {-# UNPACK #-} !CodeUnitIndex -- ^ Points to the last byte of the codepoint that we're focused on }+ deriving Generic -- | Similar to unsafeIndexCodePoint', but assumes that the given index is the -- end of a utf8 codepoint. It returns the decoded code point and the index
− src/Data/TypedByteArray.hs
@@ -1,112 +0,0 @@--- Alfred-Margaret: Fast Aho-Corasick string searching--- Copyright 2022 Channable------ Licensed under the 3-clause BSD license, see the LICENSE file in the--- repository root.-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE ScopedTypeVariables #-}--module Data.TypedByteArray- ( Data.TypedByteArray.replicate- , MutableTypedByteArray- , Prim- , TypedByteArray- , fromList- , toList- , generate- , newTypedByteArray- , unsafeFreezeTypedByteArray- , unsafeIndex- , writeTypedByteArray- , null- , length- , foldr- ) where--import Prelude hiding (foldr, length, null)--import Control.DeepSeq (NFData (rnf))-import Control.Monad.Primitive (PrimMonad (PrimState))-import Control.Monad.ST (runST)-import Data.Primitive (ByteArray (ByteArray), MutableByteArray, Prim, byteArrayFromList,- indexByteArray, newByteArray, sizeOf, unsafeFreezeByteArray, writeByteArray)--import qualified Data.Primitive as Primitive----- | Thin wrapper around 'ByteArray' that makes signatures and indexing nicer to read.-newtype TypedByteArray a = TypedByteArray ByteArray- deriving (Show, Eq)---- | Thin wrapper around 'MutableByteArray s' that makes signatures and indexing nicer to read.-newtype MutableTypedByteArray a s = MutableTypedByteArray (MutableByteArray s)--instance NFData (TypedByteArray a) where- rnf (TypedByteArray (ByteArray !_)) = ()--{-# INLINE newTypedByteArray #-}-newTypedByteArray :: forall a m. (Prim a, PrimMonad m) => Int -> m (MutableTypedByteArray a (PrimState m))-newTypedByteArray = fmap MutableTypedByteArray . newByteArray . (* sizeOf (undefined :: a))--{-# INLINE fromList #-}-fromList :: Prim a => [a] -> TypedByteArray a-fromList = TypedByteArray . byteArrayFromList--{-# INLINE toList #-}-toList :: Prim a => TypedByteArray a -> [a]-toList = foldr (:) []---- | Element index without bounds checking.-{-# INLINE unsafeIndex #-}-unsafeIndex :: Prim a => TypedByteArray a -> Int -> a-unsafeIndex (TypedByteArray arr) = indexByteArray arr--{-# INLINE generate #-}--- | Construct a 'TypedByteArray' of the given length by applying the function to each index in @[0..n-1]@.-generate :: Prim a => Int -> (Int -> a) -> TypedByteArray a-generate !n f = runST $ do- -- Allocate enough space for n elements of type a- arr <- newTypedByteArray n- intLoop 0 n $ \i -> i `seq` writeTypedByteArray arr i $ f i-- unsafeFreezeTypedByteArray arr--replicate :: (Prim a, PrimMonad m) => Int -> a -> m (MutableTypedByteArray a (PrimState m))-replicate n value = do- arr <- newTypedByteArray n- intLoop 0 n $ \i -> i `seq` writeTypedByteArray arr i value- pure arr--{-# INLINE writeTypedByteArray #-}-writeTypedByteArray :: (Prim a, PrimMonad m) => MutableTypedByteArray a (PrimState m) -> Int -> a -> m ()-writeTypedByteArray (MutableTypedByteArray array) = writeByteArray array--{-# INLINE unsafeFreezeTypedByteArray #-}-unsafeFreezeTypedByteArray :: PrimMonad m => MutableTypedByteArray a (PrimState m) -> m (TypedByteArray a)-unsafeFreezeTypedByteArray (MutableTypedByteArray array) = TypedByteArray <$> unsafeFreezeByteArray array--{-# INLINE intLoop #-}-intLoop :: Monad m => Int -> Int -> (Int -> m ()) -> m ()-intLoop !iStart !n p = go iStart- where- go !i- | i >= n = pure ()- | otherwise = do- p i- go (i + 1)--{-# INLINE null #-}-null :: TypedByteArray a -> Bool-null (TypedByteArray arr) =- Primitive.sizeofByteArray arr == 0 -- under the assumption that elements are not size 0--{-# INLINE length #-}-length :: forall a. Prim a => TypedByteArray a -> Int-length (TypedByteArray arr) =- -- This is how foldrByteArray calculates it, so must be good- Primitive.sizeofByteArray arr `quot` sizeOf (undefined :: a)--{-# INLINE foldr #-}-foldr :: Prim a => (a -> b -> b) -> b -> TypedByteArray a -> b-foldr f a (TypedByteArray arr) = Primitive.foldrByteArray f a arr-