packages feed

radix-tree-1.1.0.0: src/Data/RadixNTree/Word8/Key.hs

{-# LANGUAGE BangPatterns
           , RankNTypes #-}

module Data.RadixNTree.Word8.Key
  ( Tsil (..)
  , YtpmeNon (..)
  , Build (..)

  , buildBytes0

  , buildByteString0
  , buildShortByteString0

  , unsafeBuildText0

  , Build1 (..)

  , buildBytes1

  , buildByteString1
  , buildShortByteString1

  , unsafeBuildText1

  , Feed (..)
  , feedBytes0

  , feedByteString0
  , feedShortByteString0
  , feedLazyByteString0

  , feedText0
  , feedLazyText0

  , Feed1 (..)
  , feedBytes1

  , unsafeFeedByteString1
  , unsafeFeedShortByteString1
  , unsafeFeedLazyByteString1

  , unsafeFeedText1
  , unsafeFeedLazyText1
  ) where

import           Data.ByteArray.NonEmpty

import           Control.Monad.ST
import qualified Data.ByteString as BS
import qualified Data.ByteString.Internal as Strict (ByteString (..), unsafeCreate)
import qualified Data.ByteString.Lazy as Lazy (ByteString)
import qualified Data.ByteString.Lazy.Internal as LazyBS (ByteString (..))
import           Data.ByteString.Short.Internal (ShortByteString (..))
import           Data.ByteString.Unsafe
import           Data.List.NonEmpty (NonEmpty (..))
import           Data.Primitive.ByteArray
import qualified Data.Text.Array as Array
import qualified Data.Text.Internal as Strict (Text (..))
import qualified Data.Text.Internal.Lazy as LazyText (Text (..))
import qualified Data.Text.Lazy as Lazy (Text)
import           Data.Word
import           Foreign.Ptr



-- | Snoc-list.
data Tsil a = Lin
            | Snoc (Tsil a) a

-- | Snoc-list with a guaranteed element at the back.
data YtpmeNon a = Tsil a :/ a

-- | Key as stored in the radix tree.
newtype Build = Build
                  -- | List of memory chunks that constitute the key.
                  --
                  --   The first chunk is at the bottom of the list.
                  (Tsil ByteArray)

instance Show Build where
  showsPrec d = showsPrec d . buildBytes0

-- | Non-empty key as stored in the radix tree.
newtype Build1 = Build1
                   -- | List of memory chunks that constitute the key.
                   --
                   --   The first chunk is at the bottom of the list.
                   (YtpmeNon ByteArray)

instance Show Build1 where
  showsPrec d xs = let ~(y :| ys) = buildBytes1 xs
                   in showsPrec d (y:ys)

buildBytes0 :: Build -> [Word8]
buildBytes0 (Build xs) = go [] xs
  where
    go acc as =
      case as of
        Snoc bs a -> go (Data.ByteArray.NonEmpty.toList a <> acc) bs
        Lin       -> acc

buildBytes1 :: Build1 -> NonEmpty Word8
buildBytes1 (Build1 (xs :/ x)) = go (toNonEmpty x) xs
  where
    go acc as =
      case as of
        Snoc bs a -> go (toNonEmpty a <> acc) bs
        Lin       -> acc



sizeofBuild0 :: Build -> Int
sizeofBuild0 (Build xs) = go xs
  where
    go as =
      case as of
        Snoc bs arr -> sizeofByteArray arr + go bs
        Lin         -> 0

sizeofBuild1 :: Build1 -> Int
sizeofBuild1 (Build1 (xs :/ arr)) = sizeofByteArray arr + sizeofBuild0 (Build xs)

writePtr :: Ptr Word8 -> Int -> Build -> IO ()
writePtr ptr off0 (Build xs) = go off0 xs
  where
    go off as =
      case as of
        Snoc bs arr -> do
          let off' = off - sizeofByteArray arr
          copyByteArrayToAddr (plusPtr ptr off') arr 0 (sizeofByteArray arr)
          go off' bs

        Lin         -> pure ()

writePtr1 :: Ptr Word8 -> Int -> Build1 -> IO ()
writePtr1 ptr off (Build1 (xs :/ arr)) = do
  let off' = off - sizeofByteArray arr
  copyByteArrayToAddr (plusPtr ptr off') arr 0 (sizeofByteArray arr)
  writePtr ptr off' (Build xs)



buildByteString0 :: Build -> Strict.ByteString
buildByteString0 xs =
  let len = sizeofBuild0 xs
  in Strict.unsafeCreate len (\ptr -> writePtr ptr len xs)

buildByteString1 :: Build1 -> Strict.ByteString
buildByteString1 xs =
  let len = sizeofBuild1 xs
  in Strict.unsafeCreate len (\ptr -> writePtr1 ptr len xs)



writeArr :: MutableByteArray s -> Int -> Build -> ST s ()
writeArr marr off0 (Build xs) = go off0 xs
  where
    go off as =
      case as of
        Snoc bs arr -> do
          let off' = off - sizeofByteArray arr
          copyByteArray marr off' arr 0 (sizeofByteArray arr)
          go off' bs

        Lin         -> pure ()

writeArr1 :: MutableByteArray s -> Int -> Build1 -> ST s ()
writeArr1 marr off (Build1 (xs :/ arr)) = do
  let off' = off - sizeofByteArray arr
  copyByteArray marr off' arr 0 (sizeofByteArray arr)
  writeArr marr off' (Build xs)



{-# INLINE buildShortByteString0 #-}
buildShortByteString0 :: Build -> ShortByteString
buildShortByteString0 xs =
  runST $ do
    let len = sizeofBuild0 xs
    marr <- newByteArray len
    writeArr marr len xs
    ByteArray arr <- unsafeFreezeByteArray marr
    pure $ SBS arr

{-# INLINE buildShortByteString1 #-}
buildShortByteString1 :: Build1 -> ShortByteString
buildShortByteString1 xs =
  runST $ do
    let len = sizeofBuild1 xs
    marr <- newByteArray len
    writeArr1 marr len xs
    ByteArray arr <- unsafeFreezeByteArray marr
    pure $ SBS arr

{-# INLINE unsafeBuildText0 #-}
unsafeBuildText0 :: Build -> Strict.Text
unsafeBuildText0 xs =
  runST $ do
    let len = sizeofBuild0 xs
    marr <- newByteArray len
    writeArr marr len xs
    ByteArray arr <- unsafeFreezeByteArray marr
    pure $ Strict.Text (Array.ByteArray arr) 0 len

{-# INLINE unsafeBuildText1 #-}
unsafeBuildText1 :: Build1 -> Strict.Text
unsafeBuildText1 xs =
  runST $ do
    let len = sizeofBuild1 xs
    marr <- newByteArray len
    writeArr1 marr len xs
    ByteArray arr <- unsafeFreezeByteArray marr
    pure $ Strict.Text (Array.ByteArray arr) 0 len



-- | Key as a sequence of individual bytes.
newtype Feed = Feed
                 -- | @destroy@ part of the @destroy/unfoldr@ rule.
                 (forall a. (forall x. (x -> Step Word8 x) -> x -> a) -> a)

{-# INLINE vomit #-}
vomit :: (x -> Step a x) -> x -> [a]
vomit step = go
  where
    go s =
      case step s of
        More w ws -> w : go ws
        Done      -> []

instance Show Feed where
  showsPrec d (Feed f) = showsPrec d $ f vomit

noFeed :: Feed
noFeed = Feed $ \f -> f (\_ -> Done) ()

{-# INLINE feedBytes0 #-}
feedBytes0 :: [Word8] -> Feed
feedBytes0 ws0 = Feed $ \f -> f go ws0
  where
    go (w:ws) = More w ws
    go []     = Done



-- | Key as a non-empty sequence of individual bytes.
data Feed1 = Feed1
               -- | First byte of the key.
               {-# UNPACK #-} !Word8

               -- | @destroy@ part of the @destroy/unfoldr@ rule.
               (forall a. (forall x. (x -> Step Word8 x) -> x -> a) -> a)

instance Show Feed1 where
  showsPrec d (Feed1 w0 f) = showsPrec d $ w0 :| f vomit

{-# INLINE feedBytes1 #-}
feedBytes1 :: NonEmpty Word8 -> Feed1
feedBytes1 (w0 :| ws) =
  let Feed f = feedBytes0 ws
  in Feed1 w0 f




stepByteString :: Strict.ByteString -> Int -> Step Word8 Int
stepByteString bs = go
  where
    go n =
      if n >= BS.length bs
        then Done
        else let !n' = n + 1
             in More (unsafeIndex bs n) n'

{-# INLINE feedByteString0 #-}
feedByteString0 :: Strict.ByteString -> Feed
feedByteString0 bs = Feed $ \f -> f (stepByteString bs) 0

{-# INLINE unsafeFeedByteString1 #-}
unsafeFeedByteString1 :: Strict.ByteString -> Feed1
unsafeFeedByteString1 bs = Feed1 (unsafeIndex bs 0) (\f -> f (stepByteString bs) 1)



stepByteArray :: ByteArray -> Int -> Int -> Step Word8 Int
stepByteArray arr len = go
  where
    go n =
      if n >= len
        then Done
        else let !n' = n + 1
             in More (indexByteArray arr n) n'

{-# INLINE feedShortByteString0 #-}
feedShortByteString0 :: ShortByteString -> Feed
feedShortByteString0 (SBS arr) =
  Feed $ \f ->
    f (stepByteArray (ByteArray arr) $ sizeofByteArray (ByteArray arr)) 0

{-# INLINE unsafeFeedShortByteString1 #-}
unsafeFeedShortByteString1 :: ShortByteString -> Feed1
unsafeFeedShortByteString1 (SBS arr) =
  Feed1 (indexByteArray (ByteArray arr) 0) $ \f ->
    f (stepByteArray (ByteArray arr) $ sizeofByteArray (ByteArray arr)) 1



{-# INLINE feedText0 #-}
feedText0 :: Strict.Text -> Feed
feedText0 (Strict.Text (Array.ByteArray arr) n len) =
  Feed $ \f ->
    f (stepByteArray (ByteArray arr) len) n

{-# INLINE unsafeFeedText1 #-}
unsafeFeedText1 :: Strict.Text -> Feed1
unsafeFeedText1 (Strict.Text (Array.ByteArray arr) n len) =
  Feed1 (indexByteArray (ByteArray arr) n) $ \f ->
    let !n' = n + 1
    in f (stepByteArray (ByteArray arr) len) n'



data CarryBS = CarryBS
                 {-# UNPACK #-} !Int
                 !Strict.ByteString
                 !Lazy.ByteString

stepLazyByteString :: CarryBS -> Step Word8 CarryBS
stepLazyByteString (CarryBS n bs lbs) =
  if n >= BS.length bs
    then case lbs of
           LazyBS.Chunk bs' lbs' -> stepLazyByteString (CarryBS 0 bs' lbs')
           LazyBS.Empty          -> Done

    else let !n' = n + 1
         in More (unsafeIndex bs n) (CarryBS n' bs lbs)

{-# INLINE feedLazyByteString0 #-}
feedLazyByteString0 :: Lazy.ByteString -> Feed
feedLazyByteString0 b =
  case b of
    LazyBS.Empty        -> noFeed
    LazyBS.Chunk bs lbs -> Feed $ \f -> f stepLazyByteString (CarryBS 0 bs lbs)

{-# INLINE unsafeFeedLazyByteString1 #-}
unsafeFeedLazyByteString1 :: Strict.ByteString -> Lazy.ByteString -> Feed1
unsafeFeedLazyByteString1 bs lbs =
  Feed1 (unsafeIndex bs 0) $ \f ->
    f stepLazyByteString (CarryBS 1 bs lbs)



data CarryTxt = CarryTxt
                  {-# UNPACK #-} !Int
                  {-# UNPACK #-} !Int
                  !ByteArray
                  !Lazy.Text

stepLazyText :: CarryTxt -> Step Word8 CarryTxt
stepLazyText (CarryTxt n len arr t) =
  if n >= len
    then case t of
           LazyText.Chunk (Strict.Text (Array.ByteArray arr') n' len') t' ->
             stepLazyText (CarryTxt n' len' (ByteArray arr') t')

           LazyText.Empty -> Done

    else let !n' = n + 1
         in More (indexByteArray arr n) (CarryTxt n' len arr t)

{-# INLINE feedLazyText0 #-}
feedLazyText0 :: Lazy.Text -> Feed
feedLazyText0 t =
  case t of
    LazyText.Empty                                                -> noFeed
    LazyText.Chunk (Strict.Text (Array.ByteArray arr) n len) ltxt ->
      Feed $ \f -> f stepLazyText (CarryTxt n len (ByteArray arr) ltxt)

{-# INLINE unsafeFeedLazyText1 #-}
unsafeFeedLazyText1 :: Strict.Text -> Lazy.Text -> Feed1
unsafeFeedLazyText1 (Strict.Text (Array.ByteArray arr) n len) ltxt =
  Feed1 (indexByteArray (ByteArray arr) n) $ \f ->
    let !n' = n + 1
    in f stepLazyText (CarryTxt n' len (ByteArray arr) ltxt)