packages feed

radixtree 0.4.0.0 → 0.6.0.0

raw patch · 6 files changed

+284/−168 lines, 6 filesdep +ghc-compactdep −storesetup-changedPVP ok

version bump matches the API change (PVP)

Dependencies added: ghc-compact

Dependencies removed: store

API changes (from Hackage documentation)

- Data.RadixTree: instance Control.DeepSeq.NFData Data.RadixTree.CompressedRadixNode
- Data.RadixTree: instance Control.DeepSeq.NFData Data.RadixTree.CompressedRadixTree
- Data.RadixTree: instance Control.DeepSeq.NFData Data.RadixTree.CompressedRadixTree1
- Data.RadixTree: instance Control.DeepSeq.NFData Data.RadixTree.RadixNode
- Data.RadixTree: instance Control.DeepSeq.NFData Data.RadixTree.RadixTree
- Data.RadixTree: instance Data.Data.Data Data.RadixTree.RadixNode
- Data.RadixTree: instance Data.Data.Data Data.RadixTree.RadixTree
- Data.RadixTree: instance Data.Store.Impl.Store Data.RadixTree.RadixNode
- Data.RadixTree: instance Data.Store.Impl.Store Data.RadixTree.RadixTree
- Data.RadixTree: instance GHC.Classes.Eq Data.RadixTree.CompressedTrie
- Data.RadixTree: instance GHC.Classes.Eq Data.RadixTree.RadixNode
- Data.RadixTree: instance GHC.Classes.Eq Data.RadixTree.RadixTree
- Data.RadixTree: instance GHC.Classes.Eq Data.RadixTree.Trie
- Data.RadixTree: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.RadixTree.PrefixNode a)
- Data.RadixTree: instance GHC.Show.Show Data.RadixTree.CompressedTrie
- Data.RadixTree: instance GHC.Show.Show Data.RadixTree.RadixNode
- Data.RadixTree: instance GHC.Show.Show Data.RadixTree.RadixTree
- Data.RadixTree: instance GHC.Show.Show Data.RadixTree.Trie
- Data.RadixTree: instance GHC.Show.Show a => GHC.Show.Show (Data.RadixTree.PrefixNode a)
+ Data.RadixTree: fromFoldable_ :: Foldable f => f Text -> RadixTree ()
+ Data.RadixTree: instance (GHC.Classes.Eq a, GHC.Classes.Eq tree) => GHC.Classes.Eq (Data.RadixTree.PrefixNode a tree)
+ Data.RadixTree: instance (GHC.Show.Show a, GHC.Show.Show tree) => GHC.Show.Show (Data.RadixTree.PrefixNode a tree)
+ Data.RadixTree: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (Data.RadixTree.CompressedRadixNode a)
+ Data.RadixTree: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (Data.RadixTree.CompressedRadixTree a)
+ Data.RadixTree: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (Data.RadixTree.CompressedRadixTree1 a)
+ Data.RadixTree: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (Data.RadixTree.RadixNode a)
+ Data.RadixTree: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (Data.RadixTree.RadixTree a)
+ Data.RadixTree: instance Data.Data.Data a => Data.Data.Data (Data.RadixTree.RadixNode a)
+ Data.RadixTree: instance Data.Data.Data a => Data.Data.Data (Data.RadixTree.RadixTree a)
+ Data.RadixTree: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.RadixTree.CompressedTrie a)
+ Data.RadixTree: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.RadixTree.RadixNode a)
+ Data.RadixTree: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.RadixTree.RadixTree a)
+ Data.RadixTree: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.RadixTree.Trie a)
+ Data.RadixTree: instance GHC.Show.Show a => GHC.Show.Show (Data.RadixTree.CompressedTrie a)
+ Data.RadixTree: instance GHC.Show.Show a => GHC.Show.Show (Data.RadixTree.RadixNode a)
+ Data.RadixTree: instance GHC.Show.Show a => GHC.Show.Show (Data.RadixTree.RadixTree a)
+ Data.RadixTree: instance GHC.Show.Show a => GHC.Show.Show (Data.RadixTree.Trie a)
+ Data.RadixTree: keys :: RadixParsing radixtree => radixtree a -> [(Text, a)]
+ Data.RadixTree: lookup :: RadixParsing radixtree => radixtree a -> Text -> Maybe (Text, a)
+ Data.RadixTree: lookup_ :: RadixParsing r => r a -> Text -> Maybe Text
+ Data.RadixTree: parse_ :: (RadixParsing r, CharParsing m) => r a -> m Text
- Data.RadixTree: RadixAccept :: {-# UNPACK #-} !Text -> {-# UNPACK #-} !(Vector RadixNode) -> RadixTree
+ Data.RadixTree: RadixAccept :: {-# UNPACK #-} !Text -> {-# UNPACK #-} !Vector (RadixNode a) -> a -> RadixTree a
- Data.RadixTree: RadixNode :: {-# UNPACK #-} !Text -> !RadixTree -> RadixNode
+ Data.RadixTree: RadixNode :: {-# UNPACK #-} !Text -> !RadixTree a -> RadixNode a
- Data.RadixTree: RadixSkip :: {-# UNPACK #-} !(Vector RadixNode) -> RadixTree
+ Data.RadixTree: RadixSkip :: {-# UNPACK #-} !Vector (RadixNode a) -> RadixTree a
- Data.RadixTree: compressBy :: Text -> RadixTree -> Maybe CompressedRadixTree
+ Data.RadixTree: compressBy :: Text -> RadixTree a -> Maybe (CompressedRadixTree a)
- Data.RadixTree: data CompressedRadixTree
+ Data.RadixTree: data CompressedRadixTree a
- Data.RadixTree: data RadixNode
+ Data.RadixTree: data RadixNode a
- Data.RadixTree: data RadixTree
+ Data.RadixTree: data RadixTree a
- Data.RadixTree: fromFoldable :: Foldable f => f Text -> RadixTree
+ Data.RadixTree: fromFoldable :: Foldable f => f (Text, a) -> RadixTree a
- Data.RadixTree: parse :: (RadixParsing radixtree, CharParsing m) => radixtree -> m Text
+ Data.RadixTree: parse :: (RadixParsing radixtree, CharParsing m) => (Text -> a -> r) -> radixtree a -> m r
- Data.RadixTree: search :: (Monad m, CharParsing m, RadixParsing radixtree) => radixtree -> m [Text]
+ Data.RadixTree: search :: (Monad m, CharParsing m, RadixParsing radixtree) => radixtree a -> m [Text]

Files

− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
bench/Parsing.hs view
@@ -8,6 +8,7 @@ import           Data.Text            (Text) import qualified Data.Text            as T import qualified Data.Vector          as V+import           GHC.Compact import           Text.QuasiText  parseNaiive :: [Text] -> A.Parser Text@@ -19,9 +20,15 @@       ~(src, radix, radix2, tests) ->       bgroup "attoparsec"       [ bench "radix" $-        V.foldl' (\() t -> rnf $ A.parseOnly (R.parse radix) t) () `nf` tests+        V.foldl' (\() t -> rnf $ A.parseOnly (R.parse const radix) t) () `nf` tests+       , bench "radix compressed" $-        V.foldl' (\() t -> rnf $ A.parseOnly (R.parse radix2) t) () `nf` tests+        V.foldl' (\() t -> rnf $ A.parseOnly (R.parse const radix2) t) () `nf` tests++      , env (getCompact <$> compact radix2) $ \compactR ->+        bench "radix compressed compact" $+        V.foldl' (\() t -> rnf $ A.parseOnly (R.parse const radix2) t) () `nf` tests+       , bench "naiive" $         V.foldl' (\() t -> rnf $ A.parseOnly (parseNaiive src) t) () `nf` tests       ]@@ -37,14 +44,14 @@   \long thing that doesn't parse\n\   \z" -sourceRadixCompressed :: R.CompressedRadixTree+sourceRadixCompressed :: R.CompressedRadixTree () sourceRadixCompressed =   case R.compressBy (T.concat source) sourceRadix of     Just ct -> ct     Nothing -> error "could not compress source" -sourceRadix :: R.RadixTree-sourceRadix = R.fromFoldable source+sourceRadix :: R.RadixTree ()+sourceRadix = R.fromFoldable_ source  source :: [Text] source = T.lines [embed|@@ -929,4 +936,3 @@ year years yellow|]-
bench/Search.hs view
@@ -40,14 +40,14 @@   \long thing that doesn't parse\n\   \z" -sourceRadixCompressed :: R.CompressedRadixTree+sourceRadixCompressed :: R.CompressedRadixTree () sourceRadixCompressed =   case R.compressBy (T.concat source) sourceRadix of     Just ct -> ct     Nothing -> error "could not compress source" -sourceRadix :: R.RadixTree-sourceRadix = R.fromFoldable source+sourceRadix :: R.RadixTree ()+sourceRadix = R.fromFoldable_ source  source :: [Text] source = T.lines [embed|@@ -932,4 +932,3 @@ year years yellow|]-
radixtree.cabal view
@@ -1,75 +1,91 @@-name:                radixtree-version:             0.4.0.0-description:         A prefix-tree parsing library-homepage:            https://gitlab.com/transportengineering/radixtree-license:             BSD3-license-file:        LICENSE-author:              Mike Ledger-maintainer:          mike@quasimal.com-copyright:           2018 Mike Ledger-category:            Web-build-type:          Simple-cabal-version:       >=1.10--library-  hs-source-dirs:      src-  exposed-modules:     Data.RadixTree-  build-depends:       base >= 4.7 && < 5-                     , containers >= 0.5-                     , vector-                     , text-                     , deepseq-                     , microlens-                     , parsers >= 0.12-                     , store >= 0.4-                     , mtl >= 2.0-  default-language:    Haskell2010+-- This file has been generated from package.yaml by hpack version 0.28.2.+--+-- see: https://github.com/sol/hpack+--+-- hash: 8255bc497cd8d2d14fe11ccffde96ff9cab05b3bd0f6d264cf26f1789a662c1b -benchmark radixtree-parsing-  type:                exitcode-stdio-1.0-  hs-source-dirs:      bench-  main-is:             Parsing.hs-  ghc-options:         -O2-  build-depends:       base-                     , radixtree-                     , text-                     , QuasiText-                     , criterion-                     , attoparsec-                     , deepseq-                     , vector-  default-language:    Haskell2010+name:           radixtree+version:        0.6.0.0+description:    A prefix-tree parsing library+category:       Web+homepage:       https://gitlab.com/transportengineering/rnd/radixtree+author:         Mike Ledger+maintainer:     mike@quasimal.com+copyright:      2017-2019 Mike Ledger+license:        BSD3+license-file:   LICENSE+build-type:     Simple+cabal-version:  >= 1.10 +source-repository head+  type: git+  location: https://gitlab.com/tranpsortengineering/rnd/radixtree -benchmark radixtree-search-  type:                exitcode-stdio-1.0-  hs-source-dirs:      bench-  main-is:             Search.hs-  ghc-options:         -O2-  build-depends:       base-                     , radixtree-                     , text-                     , QuasiText-                     , criterion-                     , attoparsec-                     , deepseq-                     , vector-  default-language:    Haskell2010+library+  exposed-modules:+      Data.RadixTree+  other-modules:+      Paths_radixtree+  hs-source-dirs:+      src+  build-depends:+      base >=4.7 && <5+    , containers >=0.5+    , deepseq+    , microlens+    , mtl >=2.0+    , parsers >=0.12+    , text+    , vector+  default-language: Haskell2010  test-suite radixtree-test-  type:                exitcode-stdio-1.0-  hs-source-dirs:      test-  main-is:             Spec.hs-  build-depends:       base-                     , radixtree-                     , attoparsec-                     , smallcheck-                     , tasty-                     , tasty-smallcheck-                     , text-  ghc-options:         -threaded -rtsopts -with-rtsopts=-N-  default-language:    Haskell2010+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Paths_radixtree+  hs-source-dirs:+      test+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      attoparsec+    , base+    , radixtree+    , smallcheck+    , tasty+    , tasty-smallcheck+    , text+  default-language: Haskell2010 -source-repository head-  type:     git-  location: https://gitlab.com/tranpsortengineering/radixtree+benchmark radixtree-parsing+  type: exitcode-stdio-1.0+  main-is: Parsing.hs+  hs-source-dirs: bench+  ghc-options: -O2+  build-depends:+      attoparsec+    , base+    , criterion+    , deepseq+    , radixtree+    , text+    , vector+    , QuasiText+    , ghc-compact+  default-language: Haskell2010++benchmark radixtree-search+  type: exitcode-stdio-1.0+  main-is: Search.hs+  hs-source-dirs: bench+  ghc-options: -O2+  build-depends:+      attoparsec+    , base+    , criterion+    , deepseq+    , radixtree+    , text+    , vector+    , QuasiText+  default-language: Haskell2010
src/Data/RadixTree.hs view
@@ -14,10 +14,13 @@   , RadixNode (..)   , CompressedRadixTree     -- * Construction+  , fromFoldable_   , fromFoldable   , compressBy     -- * Parsing with radix trees   , RadixParsing (..)+  , parse_+  , lookup_   , search   ) where import           Control.Applicative@@ -26,11 +29,8 @@ import           Data.Foldable           (asum, foldr', toList) import           Data.Map.Strict         (Map) import qualified Data.Map.Strict         as M-import           Data.Monoid import           Data.Sequence           (Seq) import qualified Data.Sequence           as Seq-import           Data.Store              ()-import           Data.Store.TH           (makeStore) import           Data.Text               (Text) import qualified Data.Text               as T import qualified Data.Text.Array         as TI (Array)@@ -53,50 +53,50 @@ -- - generate RadixTree directly, instead of going through 'Trie'/'CompressedTrie' -- - use compact regions? -data PrefixNode a = Accept !Text !a | Skip !a+data PrefixNode a tree = Accept !Text a !tree | Skip !tree   deriving (Show, Eq) -newtype Trie = Trie (PrefixNode (Map Char Trie))+newtype Trie a = Trie (PrefixNode a (Map Char (Trie a)))   deriving (Show, Eq) -newtype CompressedTrie = CompressedTrie (PrefixNode (Map (Seq Char) CompressedTrie))+newtype CompressedTrie a = CompressedTrie (PrefixNode a (Map (Seq Char) (CompressedTrie a)))   deriving (Show, Eq)  {-# INLINE node #-}-node :: Lens (PrefixNode a) (PrefixNode b) a b+node :: Lens (PrefixNode p a) (PrefixNode p b) a b node = lens-  (\x -> case x of { Accept _ t -> t; Skip t -> t })-  (\x a -> case x of { Accept l _ -> Accept l a; Skip _ -> Skip a })+  (\x -> case x of { Accept _ _ t -> t; Skip t -> t })+  (\x a -> case x of { Accept l p _ -> Accept l p a; Skip _ -> Skip a }) -leaf :: Text -> Text -> Trie-leaf ft t = go (T.unpack t)+leaf :: Text -> Text -> a -> Trie a+leaf ft t v = go (T.unpack t)   where     go (x:xs) = Trie (Skip (M.singleton x (go xs)))-    go []     = Trie (Accept ft M.empty)+    go []     = Trie (Accept ft v M.empty) -insert :: Text -> Text -> Trie -> Trie-insert ft text' (Trie n) = case T.uncons text' of+insert :: Text -> Text -> a -> Trie a -> Trie a+insert ft text' a (Trie n) = case T.uncons text' of   Just (c, cs) -> Trie ((node %~     M.insertWith-    (\_ orig -> Data.RadixTree.insert ft cs orig)+    (\_ orig -> Data.RadixTree.insert ft cs a orig)     c-    (leaf ft cs)) n)+    (leaf ft cs a)) n)   Nothing ->-    Trie (n^.node.to (Accept ft))+    Trie (n^.node.to (Accept ft a)) -makeCompressable :: Trie -> CompressedTrie+makeCompressable :: Trie a -> CompressedTrie a makeCompressable (Trie n) = CompressedTrie (   over node (M.map makeCompressable . M.mapKeysMonotonic Seq.singleton) n) -compress :: Trie -> CompressedTrie+compress :: Trie a -> CompressedTrie a compress = go . makeCompressable   where-    go :: CompressedTrie -> CompressedTrie+    go :: CompressedTrie a -> CompressedTrie a     go (CompressedTrie n) = case n of-      Accept l m -> CompressedTrie (Accept l (M.map go m))-      Skip m     -> CompressedTrie (Skip (M.foldMapWithKey compress1 m))+      Accept l p m -> CompressedTrie (Accept l p (M.map go m))+      Skip m       -> CompressedTrie (Skip (M.foldMapWithKey compress1 m)) -    compress1 :: Seq Char -> CompressedTrie -> Map (Seq Char) CompressedTrie+    compress1 :: Seq Char -> CompressedTrie a -> Map (Seq Char) (CompressedTrie a)     compress1 k c@(CompressedTrie n) =       case M.size sm of         0 -> M.singleton k c@@ -109,36 +109,37 @@  -- | A node in a radixtree. To advance from here a parser must parse the 'Text' -- (i.e., the prefix) value at this node.-data RadixNode = RadixNode {-# UNPACK #-} !Text !RadixTree+data RadixNode a = RadixNode {-# UNPACK #-} !Text !(RadixTree a)   deriving (Eq, Show, Typeable, Data) --- | A radixtree. Construct with 'fromFoldable', and use with 'parse'.-data RadixTree+-- | A radixtree. Construct with 'fromFoldable_, and use with 'parse'.+data RadixTree a   = -- | Can terminate a parser successfully, returning the 'Text' value given.     RadixAccept-    {-# UNPACK #-} !Text -- the final value to return-    {-# UNPACK #-} !(Vector RadixNode) -- possible subtrees beyond this point+    {-# UNPACK #-} !Text -- ^ text to return at this point+    {-# UNPACK #-} !(Vector (RadixNode a)) -- ^ possible subtrees beyond this point+    a -- ^ value to return at this point   | RadixSkip-    {-# UNPACK #-} !(Vector RadixNode) -- possible subtrees beyond this point+    {-# UNPACK #-} !(Vector (RadixNode a)) -- ^ possible subtrees beyond this point   deriving (Eq, Show, Typeable, Data) -instance NFData RadixNode where+instance NFData a => NFData (RadixNode a) where   {-# INLINE rnf #-}   rnf (RadixNode l t) = rnf l `seq` rnf t -instance NFData RadixTree where+instance NFData a => NFData (RadixTree a) where   {-# INLINE rnf #-}-  rnf (RadixAccept t v) = t `seq` rnf v-  rnf (RadixSkip v)     = rnf v+  rnf (RadixAccept t v p) = t `seq` rnf p `seq` rnf v+  rnf (RadixSkip v)       = rnf v  -- | Compress a totally-unoptimised 'Trie' into a nice and easily-parsable -- 'RadixTree'-fromTrie :: Trie -> RadixTree+fromTrie :: Trie a -> RadixTree a fromTrie = go . compress   where     !z = V.empty -    radixNode :: Seq Char -> CompressedTrie -> RadixNode+    radixNode :: Seq Char -> CompressedTrie a -> RadixNode a     radixNode l t = RadixNode (T.pack (toList l)) (go t)      mapToVector :: Map k a -> Vector (k, a)@@ -146,10 +147,10 @@       0  -> z       sz -> V.fromListN sz (M.toList m) -    go :: CompressedTrie -> RadixTree+    go :: CompressedTrie a -> RadixTree a     go (CompressedTrie n) = case n of-      Accept l m -> RadixAccept l . V.map (uncurry radixNode) . mapToVector $! m-      Skip m -> RadixSkip . V.map (uncurry radixNode) . mapToVector $! m+      Accept l p m -> RadixAccept l (V.map (uncurry radixNode) (mapToVector m)) p+      Skip m       -> RadixSkip . V.map (uncurry radixNode) . mapToVector $! m  data TextSlice = TextSlice   { tsOffset16 :: {-# UNPACK #-} !Int -- ^ offset (in units of Word16)@@ -174,108 +175,185 @@ -- parsing benchmarks in this package, the 'CompressedRadixTree' version is -- 254032 bytes, whereas the ordinary 'RadixTree' is a rotund 709904 bytes) at -- no runtime cost.-data CompressedRadixTree-  = CompressedRadixTree {-# UNPACK #-} !TI.Array !CompressedRadixTree1+data CompressedRadixTree a+  = CompressedRadixTree {-# UNPACK #-} !TI.Array !(CompressedRadixTree1 a) -data CompressedRadixTree1+data CompressedRadixTree1 a   = CompressedRadixAccept     {-# UNPACK #-} !TextSlice-    {-# UNPACK #-} !(Vector CompressedRadixNode)-  | CompressedRadixSkip {-# UNPACK #-} !(Vector CompressedRadixNode)+    {-# UNPACK #-} !(Vector (CompressedRadixNode a))+    a+  | CompressedRadixSkip {-# UNPACK #-} !(Vector (CompressedRadixNode a)) -data CompressedRadixNode-  = CompressedRadixNode {-# UNPACK #-} !TextSlice !CompressedRadixTree1+data CompressedRadixNode a+  = CompressedRadixNode {-# UNPACK #-} !TextSlice !(CompressedRadixTree1 a) -instance NFData CompressedRadixNode where+instance NFData a => NFData (CompressedRadixNode a) where   {-# INLINE rnf #-}   rnf (CompressedRadixNode ts t) = ts `seq` rnf t -instance NFData CompressedRadixTree where+instance NFData a => NFData (CompressedRadixTree a) where   {-# INLINE rnf #-}   rnf (CompressedRadixTree arr v) = arr `seq` rnf v -instance NFData CompressedRadixTree1 where+instance NFData a => NFData (CompressedRadixTree1 a) where   {-# INLINE rnf #-}-  rnf (CompressedRadixAccept ts v) = ts `seq` rnf v-  rnf (CompressedRadixSkip v)      = rnf v+  rnf (CompressedRadixAccept ts v a) = ts `seq` rnf a `seq` rnf v+  rnf (CompressedRadixSkip v)        = rnf v --- | Compress a 'RadixTree' given a corpus. All values in the tree be findable--- within the corpus, though the corpus does not have to necessarily be the--- direct source of the tree-compressBy :: Text -> RadixTree -> Maybe CompressedRadixTree+-- | Compress a 'RadixTree' given a corpus. All values in the tree must be+-- findable within the corpus, though the corpus does not have to necessarily be+-- the direct source of the tree+compressBy :: Text -> RadixTree a -> Maybe (CompressedRadixTree a) compressBy full@(TI.Text arr _ _) rt =   CompressedRadixTree arr <$> recompressT rt    where     magic = magicallySaveSpaceSometimes full -    recompressN :: RadixNode -> Maybe CompressedRadixNode+    recompressN :: RadixNode a -> Maybe (CompressedRadixNode a)     recompressN (RadixNode t tree) = CompressedRadixNode <$> magic t <*> recompressT tree -    recompressT :: RadixTree -> Maybe CompressedRadixTree1-    recompressT (RadixSkip v) = CompressedRadixSkip <$> V.mapM recompressN v-    recompressT (RadixAccept t v) = CompressedRadixAccept <$> magic t <*> V.mapM recompressN v+    recompressT :: RadixTree a -> Maybe (CompressedRadixTree1 a)+    recompressT (RadixSkip v)       = CompressedRadixSkip <$> V.mapM recompressN v+    recompressT (RadixAccept t v a) = CompressedRadixAccept <$> magic t <*> V.mapM recompressN v <*> pure a +-- | *Slow*. Same as 'fromFoldable', but you do not need to supply pairs of text+-- and values; they will default to '()'.+fromFoldable_ :: Foldable f => f Text -> RadixTree ()+fromFoldable_ =+  fromTrie . foldr' (\t -> insert t t ()) (Trie (Skip M.empty))+ -- | *Slow*-fromFoldable :: Foldable f => f Text -> RadixTree+fromFoldable :: Foldable f => f (Text, a) -> RadixTree a fromFoldable =-  fromTrie . foldr' (\t -> insert t t) (Trie (Skip M.empty))--makeStore ''RadixNode-makeStore ''RadixTree+  fromTrie . foldr' (\(t, a) -> insert t t a) (Trie (Skip M.empty))  -------------------------------------------------------------------------------- -- Parsers from 'RadixTree's  class RadixParsing radixtree where-  parse :: CharParsing m => radixtree -> m Text+  keys :: radixtree a -> [(Text, a)]+  parse :: CharParsing m => (Text -> a -> r) -> radixtree a -> m r+  lookup :: radixtree a -> Text -> Maybe (Text, a)  {-# INLINE search #-} -- | Find all occurences of the terms in a 'RadixTree' from this point on. This -- will consume the entire remaining input. Can lazily produce results (but this -- depends on your parser).-search :: (Monad m, CharParsing m, RadixParsing radixtree)-       => radixtree -> m [Text]+search+  :: (Monad m, CharParsing m, RadixParsing radixtree)+  => radixtree a+  -> m [Text] search r = go   where     go =-      (parse r >>= \x -> (x:) <$> go) <|>+      (parse const r >>= \x -> (x:) <$> go) <|>       (anyChar >> go) <|>       return [] +{-# INLINE parse_ #-}+parse_ :: (RadixParsing r, CharParsing m) => r a -> m Text+parse_ = Data.RadixTree.parse const++{-# INLINE lookup_ #-}+lookup_ :: RadixParsing r => r a -> Text -> Maybe Text+lookup_ r t = fst <$> Data.RadixTree.lookup r t+ instance RadixParsing RadixTree where+  keys = go []+    where+      go nil (RadixAccept l xs a) = (l,a) : V.foldr (\(RadixNode _ x) xs' -> go xs' x) nil xs+      go nil (RadixSkip xs) = V.foldr (\(RadixNode _ x) xs' -> go xs' x) nil xs+   {-# INLINE parse #-}   -- | Parse from a 'RadixTree'-  parse :: CharParsing m => RadixTree -> m Text-  parse = go+  parse :: CharParsing m => (Text -> a -> r) -> RadixTree a -> m r+  parse constr = go     where       go r = case r of-        RadixAccept l nodes+        RadixAccept l nodes a           | T.null l -> empty-          | otherwise -> asum (V.map parseRadixNode nodes) <|> pure l-        RadixSkip nodes -> asum (V.map parseRadixNode nodes)+          | otherwise -> asum (V.map parseRadixNode nodes) <|> pure (constr l a)+        RadixSkip nodes ->+          asum (V.map parseRadixNode nodes)        {-# INLINE parseRadixNode #-}       parseRadixNode (RadixNode prefix tree)         | T.null prefix = go tree         | otherwise     = try (text prefix *> go tree) +  lookup :: RadixTree a -> Text -> Maybe (Text, a)+  lookup rt0 t0+    | T.null t0 = case rt0 of+        RadixAccept v _ a -> Just (v, a)+        RadixSkip _       -> Nothing+    | otherwise = case rt0 of+        RadixAccept _ ns _ -> lookupRadixNodes t0 ns+        RadixSkip     ns   -> lookupRadixNodes t0 ns+    where+      lookupRadixNodes t v = go 0+        where+          !vlen = V.length v+          go !i+            | i < vlen  = case V.unsafeIndex v i of+                RadixNode pfix rt -> case T.commonPrefixes pfix t of+                  Just (_, remPfx, remSfx)+                    | T.null remPfx -> Data.RadixTree.lookup rt remSfx+                    | otherwise     -> Nothing+                  Nothing -> go (i + 1)+            | otherwise = Nothing+ instance RadixParsing CompressedRadixTree where++  keys (CompressedRadixTree arr crt) = go [] crt+    where+      fromSlice (TextSlice offs len) = TI.text arr offs len++      go nil (CompressedRadixAccept l xs a) =+        (fromSlice l, a) : V.foldr (\(CompressedRadixNode _ x) xs' -> go xs' x) nil xs+      go nil (CompressedRadixSkip xs) =+        V.foldr (\(CompressedRadixNode _ x) xs' -> go xs' x) nil xs+   {-# INLINE parse #-}   -- | Parse from a 'RadixTree'-  parse :: CharParsing m => CompressedRadixTree -> m Text-  parse (CompressedRadixTree arr crt) = go crt+  parse :: CharParsing m => (Text -> a -> r) -> CompressedRadixTree a -> m r+  parse constr (CompressedRadixTree arr crt) = go crt     where       fromSlice (TextSlice offs len) = TI.text arr offs len-       go r = case r of-        CompressedRadixAccept ts nodes -> case fromSlice ts of+        CompressedRadixAccept ts nodes a -> case fromSlice ts of           l | T.null l -> empty-            | otherwise -> asum (V.map parseRadixNode nodes) <|> pure l+            | otherwise -> asum (V.map parseRadixNode nodes) <|> pure (constr l a)         CompressedRadixSkip nodes -> asum (V.map parseRadixNode nodes)-       {-# INLINE parseRadixNode #-}       parseRadixNode (CompressedRadixNode ts tree) = case fromSlice ts of         prefix | T.null prefix -> go tree                | otherwise     -> try (text prefix *> go tree)++  lookup :: CompressedRadixTree a -> Text -> Maybe (Text, a)+  lookup (CompressedRadixTree arr0 rt0) = lookup1 rt0+    where+      fromSlice (TextSlice offs16 len16) = TI.text arr0 offs16 len16++      lookup1 rt !t+        | T.null t = case rt of+            CompressedRadixAccept v _ a -> Just (fromSlice v, a)+            CompressedRadixSkip _       -> Nothing+        | otherwise = case rt of+            CompressedRadixAccept _ ns _ -> lookupCompressedRadixNodes t ns+            CompressedRadixSkip     ns   -> lookupCompressedRadixNodes t ns++      lookupCompressedRadixNodes !t v = go 0+        where+          !vlen = V.length v+          go !i+            | i < vlen  = case V.unsafeIndex v i of+                CompressedRadixNode pfix rt -> case T.commonPrefixes (fromSlice pfix) t of+                  Just (_, remPfx, remSfx)+                    | T.null remPfx -> lookup1 rt remSfx+                    | otherwise     -> Nothing+                  Nothing -> go (i + 1)+            | otherwise = Nothing+ 
test/Spec.hs view
@@ -1,5 +1,9 @@-{-# LANGUAGE OverloadedStrings   #-}-{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE BangPatterns          #-}+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings     #-}+{-# LANGUAGE ScopedTypeVariables   #-}+{-# LANGUAGE ViewPatterns          #-} import           Data.Attoparsec.Text import           Data.Coerce import           Data.List              (sortOn)@@ -13,25 +17,40 @@ sortOnLengthDesc :: [T.Text] -> [T.Text] sortOnLengthDesc = sortOn (negate . T.length) +mkAlternatives :: NonEmpty (NonEmpty Char) -> [T.Text]+mkAlternatives = sortOnLengthDesc . map T.pack . coerce+ naiiveSameAsRadix :: Monad m => Bool -> Property m naiiveSameAsRadix doCompress =-  forAll $ \(alternativesS :: NonEmpty (NonEmpty Char)) ->+  forAll $ \(mkAlternatives -> alternatives) ->     let-      alternatives = sortOnLengthDesc (map T.pack (coerce alternativesS))       rparse-        | doCompress = R.parse $! R.fromFoldable alternatives-        | otherwise = case R.compressBy (T.concat alternatives) (R.fromFoldable alternatives) of-            Just crt -> R.parse $! crt-            Nothing -> error "could not compress radixtree!"-+        | doCompress = R.parse_ $! R.fromFoldable_ alternatives+        | otherwise = case R.compressBy (T.concat alternatives) (R.fromFoldable_ alternatives) of+            Just crt -> R.parse_ $! crt+            Nothing  -> error "could not compress radixtree!"     in forAll $ \(textS :: NonEmpty Char) ->       let text = T.pack (coerce textS)       in parseOnly (choice (map string alternatives)) text ==          parseOnly rparse text +lookupSameishAsParse :: Property IO+lookupSameishAsParse =+  forAll $ \alternatives ->+  let+    !rt = R.fromFoldable_ (mkAlternatives alternatives)+    !p = R.parse_ rt <* endOfInput+  in forAll $ \(textS :: NonEmpty Char) ->+    let text = T.pack (coerce textS)+    in case (parseOnly p text, R.lookup_ rt text) of+      (Right x1, Just x2) -> x1 == x2+      (Left  _ , Nothing) -> True+      _                   -> False+ main :: IO () main = defaultMain $ testGroup "radixtree"   [ testProperty "naiive parsing has the same result as radix tree parsing" (naiiveSameAsRadix False)   , testProperty "naiive parsing has the same result as radix tree parsing when compressed"     (naiiveSameAsRadix True)+  , testProperty "lookup ~ parse" lookupSameishAsParse   ]