snappy-c-0.1.0: src/Codec/Compression/SnappyC/Internal/Buffer.hs
-- | Intended for qualified import:
--
-- > import Codec.Compression.SnappyC.Internal.Buffer (Buffer)
-- > import Codec.Compression.SnappyC.Internal.Buffer qualified as Buffer
module Codec.Compression.SnappyC.Internal.Buffer
( -- * 'Buffer' type
Buffer -- Opaque
-- ** Introduction
, empty
, append
-- ** Elimination
, toStrict
-- ** Length etc.
, length
, null
-- ** Splitting
, splitExactly
) where
import Prelude hiding (length, null)
import Data.ByteString qualified as Strict (ByteString)
import Data.ByteString qualified as BS.Strict
import Data.ByteString.Lazy qualified as Lazy (ByteString)
import Data.ByteString.Lazy qualified as BS.Lazy
-- | Intended for efficiency in the case of a bunch of appends followed by a
-- bunch of splits.
data Buffer =
Forward
Lazy.ByteString
!Int
| Backward
[Strict.ByteString]
!Int
deriving Show
-- | Empty buffer
empty :: Buffer
empty = Backward [] 0
-- | Is the 'Buffer' empty?
--
-- /O(1)/
null :: Buffer -> Bool
null = (== 0) . length
-- | Length
--
-- /O(1)/
length :: Buffer -> Int
length (Forward _ l) = l
length (Backward _ l) = l
-- | Append data to the end of the 'Buffer'.
--
-- /O(n)/ if the given buffer is forwards, /O(1)/ otherwise.
append :: Buffer -> Strict.ByteString -> Buffer
append b bs =
let
!(chunks, l) = backwards b
in
Backward (bs : chunks) (l + BS.Strict.length bs)
-- | Get the buffered chunks in backwards order
--
-- /O(n)/ if the given buffer is forwards, /O(1)/ otherwise.
backwards :: Buffer -> ([Strict.ByteString], Int)
backwards (Forward bs l) = (reverse $ BS.Lazy.toChunks bs, l)
backwards (Backward bs l) = (bs, l)
-- | Get the buffer data as a 'Lazy.ByteString' paired with its length.
--
-- /O(n)/ if the buffer is backward, /O(1)/ otherwise.
toLazy :: Buffer -> (Lazy.ByteString, Int)
toLazy (Forward bs l) = (bs, l)
toLazy (Backward bs l) = (BS.Lazy.fromChunks $ reverse bs, l)
-- | Create a 'Buffer' from a 'Lazy.ByteString' paired with its length.
--
-- /O(1)/
fromLazy :: (Lazy.ByteString, Int) -> Buffer
fromLazy (bs, l) = Forward bs l
-- | Split off a chunk of exactly @n@ bytes.
--
-- If there aren't enough bytes, return how many bytes we need.
--
-- /O(1)/ if the length is insufficient, /O(n)/ otherwise.
splitExactly :: Int -> Buffer -> Either Int (Strict.ByteString, Buffer)
splitExactly n b
| length b < n
= Left (n - length b)
| otherwise
= let
!(bs, bsLength) = toLazy b
!(tookBs, rest) = BS.Lazy.splitAt (fromIntegral n) bs
in
Right
( BS.Lazy.toStrict tookBs
, fromLazy (rest, bsLength - n)
)
-- | Get the buffer data as a 'Strict.ByteString'. __Only__ call this function if
-- you are __sure__ the data in the buffer is small.
--
-- /O(n)/
--
-- TODO: We are losing size information here that we could use to our advantage
-- in the conversion, but it's a minor inefficiency.
toStrict :: Buffer -> Strict.ByteString
toStrict (Forward bs _) = BS.Lazy.toStrict bs
toStrict (Backward bs _) = BS.Strict.concat $ reverse bs