packages feed

pure-zlib-0.8.0: src/Codec/Compression/Zlib/Monad.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE Rank2Types #-}

module Codec.Compression.Zlib.Monad (
  DeflateM,
  runDeflateM,
  ZlibDecoder (..),
  raise,
  DecompressionError (..),

  -- * Getting data from the input stream.
  nextBits,
  nextByte,
  nextWord16,
  nextWord32,
  nextBlock,
  nextCode,

  -- * Aligning
  advanceToByte,

  -- * Emitting data into the output window
  emitByte,
  emitBlock,
  emitPastChunk,

  -- * Getting and publishing output
  finalAdler,
  moveWindow,
  finalize,
) where

import Codec.Compression.Zlib.Adler32 (
  AdlerState,
  advanceAdler,
  advanceAdlerBlock,
  finalizeAdler,
  initialAdlerState,
 )
import Codec.Compression.Zlib.HuffmanTree (
  AdvanceResult (..),
  HuffmanTree,
  advanceTree,
 )
import Codec.Compression.Zlib.OutputWindow (
  OutputWindow,
  addByte,
  addChunk,
  addOldChunk,
  emitExcess,
  emptyWindow,
  finalizeWindow,
 )
import Control.Exception (Exception)
import Data.Bits (Bits (..))
import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L
import Data.Int (Int64)
import Data.Typeable (Typeable)
import Data.Word (Word16, Word32, Word8)
import GHC.ST (ST)
import Prelude.Compat
import Prelude ()

data DecompressionState s = DecompressionState
  { dcsNextBitNo :: !Int
  , dcsCurByte :: !Word8
  , dcsAdler32 :: !AdlerState
  , dcsInput :: !S.ByteString
  , dcsOutput :: !(OutputWindow s)
  }

instance Show (DecompressionState s) where
  show dcs =
    "DecompressionState<nextBit=" ++ show (dcsNextBitNo dcs) ++ ","
      ++ "curByte="
      ++ show (dcsCurByte dcs)
      ++ ",inputLen="
      ++ show (S.length (dcsInput dcs))
      ++ ">"

-- -----------------------------------------------------------------------------

data DecompressionError
  = HuffmanTreeError String
  | FormatError String
  | DecompressionError String
  | HeaderError String
  | ChecksumError String
  deriving (Typeable, Eq)

instance Show DecompressionError where
  show x =
    case x of
      HuffmanTreeError s -> "Huffman tree manipulation error: " ++ s
      FormatError s -> "Block format error: " ++ s
      DecompressionError s -> "Decompression error: " ++ s
      HeaderError s -> "Header error: " ++ s
      ChecksumError s -> "Checksum error: " ++ s

instance Exception DecompressionError

-- -----------------------------------------------------------------------------

newtype DeflateM s a = DeflateM
  { unDeflateM ::
      DecompressionState s ->
      (DecompressionState s -> a -> ST s (ZlibDecoder s)) ->
      ST s (ZlibDecoder s)
  }

instance Applicative (DeflateM s) where
  pure x = DeflateM (\s k -> k s x)

  f <*> x = DeflateM $ \s1 k ->
    unDeflateM f s1 $ \s2 g ->
      unDeflateM x s2 $ \s3 y -> k s3 (g y)

  m *> n = DeflateM $ \s1 k ->
    unDeflateM m s1 $ \s2 _ -> unDeflateM n s2 k

  {-# INLINE pure #-}
  {-# INLINE (<*>) #-}
  {-# INLINE (*>) #-}

instance Functor (DeflateM s) where
  fmap f m = DeflateM (\s k -> unDeflateM m s (\s' a -> k s' (f a)))
  {-# INLINE fmap #-}

instance Monad (DeflateM s) where
  {-# INLINE return #-}
  return = pure

  {-# INLINE (>>=) #-}
  m >>= f = DeflateM $ \s1 k ->
    unDeflateM m s1 $ \s2 a -> unDeflateM (f a) s2 k

  (>>) = (*>)
  {-# INLINE (>>) #-}

get :: DeflateM s (DecompressionState s)
get = DeflateM (\s k -> k s s)
{-# INLINE get #-}

set :: DecompressionState s -> DeflateM s ()
set !s = DeflateM (\_ k -> k s ())
{-# INLINE set #-}

raise :: DecompressionError -> DeflateM s a
raise e = DeflateM (\_ _ -> return (DecompError e))
{-# INLINE raise #-}

liftST :: ST s a -> DeflateM s a
liftST action = DeflateM $ \s k -> do
  res <- action
  k s res

-- -----------------------------------------------------------------------------

data ZlibDecoder s
  = NeedMore (S.ByteString -> ST s (ZlibDecoder s))
  | Chunk S.ByteString (ST s (ZlibDecoder s))
  | Done
  | DecompError DecompressionError

runDeflateM :: DeflateM s () -> ST s (ZlibDecoder s)
runDeflateM m = do
  window <- emptyWindow
  let initialState =
        DecompressionState
          { dcsNextBitNo = 8
          , dcsCurByte = 0
          , dcsAdler32 = initialAdlerState
          , dcsInput = S.empty
          , dcsOutput = window
          }
  unDeflateM m initialState (\_ _ -> return Done)
{-# INLINE runDeflateM #-}

-- -----------------------------------------------------------------------------

getNextChunk :: DeflateM s ()
getNextChunk = DeflateM $ \st k -> return (NeedMore (loadChunk st k))
 where
  loadChunk ::
    DecompressionState s ->
    (DecompressionState s -> () -> ST s (ZlibDecoder s)) ->
    S.ByteString ->
    ST s (ZlibDecoder s)
  loadChunk st k bstr =
    case S.uncons bstr of
      Nothing -> return (NeedMore (loadChunk st k))
      Just (nextb, rest) ->
        k st{dcsNextBitNo = 0, dcsCurByte = nextb, dcsInput = rest} ()

{-# SPECIALIZE nextBits :: Int -> DeflateM s Word8 #-}
{-# SPECIALIZE nextBits :: Int -> DeflateM s Int #-}
{-# SPECIALIZE nextBits :: Int -> DeflateM s Int64 #-}
{-# INLINE nextBits #-}
nextBits :: (Num a, Bits a) => Int -> DeflateM s a
nextBits x = nextBits' x 0 0

{-# SPECIALIZE nextBits' :: Int -> Int -> Word8 -> DeflateM s Word8 #-}
{-# SPECIALIZE nextBits' :: Int -> Int -> Int -> DeflateM s Int #-}
{-# SPECIALIZE nextBits' :: Int -> Int -> Int64 -> DeflateM s Int64 #-}
{-# INLINE nextBits' #-}
nextBits' :: (Num a, Bits a) => Int -> Int -> a -> DeflateM s a
nextBits' !x' !shiftNum !acc
  | x' == 0 = return acc
  | otherwise = do
    dcs <- get
    case dcsNextBitNo dcs of
      8 -> case S.uncons (dcsInput dcs) of
        Nothing -> do
          getNextChunk
          nextBits' x' shiftNum acc
        Just (nextb, rest) -> do
          set dcs{dcsNextBitNo = 0, dcsCurByte = nextb, dcsInput = rest}
          nextBits' x' shiftNum acc
      nextBitNo -> do
        let !myBits = min x' (8 - nextBitNo)
            !base = dcsCurByte dcs `shiftR` nextBitNo
            !mask = complement (0xFF `shiftL` myBits)
            !res = fromIntegral (base .&. mask)
            !acc' = acc .|. (res `shiftL` shiftNum)
        set dcs{dcsNextBitNo = nextBitNo + myBits}
        nextBits' (x' - myBits) (shiftNum + myBits) acc'

nextByte :: DeflateM s Word8
nextByte = do
  dcs <- get
  if
      | dcsNextBitNo dcs == 0 -> do
        set dcs{dcsNextBitNo = 8}
        return (dcsCurByte dcs)
      | dcsNextBitNo dcs /= 8 -> nextBits 8 -- we're not aligned. sigh.
      | otherwise -> case S.uncons (dcsInput dcs) of
        Nothing -> getNextChunk >> nextByte
        Just (nextb, rest) -> do
          set
            dcs
              { dcsNextBitNo = 8
              , dcsCurByte = nextb
              , dcsInput = rest
              }
          return nextb

nextWord16 :: DeflateM s Word16
nextWord16 = do
  low <- fromIntegral `fmap` nextByte
  high <- fromIntegral `fmap` nextByte
  return ((high `shiftL` 8) .|. low)

nextWord32 :: DeflateM s Word32
nextWord32 = do
  a <- fromIntegral `fmap` nextByte
  b <- fromIntegral `fmap` nextByte
  c <- fromIntegral `fmap` nextByte
  d <- fromIntegral `fmap` nextByte
  return ((a `shiftL` 24) .|. (b `shiftL` 16) .|. (c `shiftL` 8) .|. d)

nextBlock :: Integral a => a -> DeflateM s L.ByteString
nextBlock amt = do
  dcs <- get
  if
      | dcsNextBitNo dcs == 0 -> do
        let startByte = dcsCurByte dcs
        set dcs{dcsNextBitNo = 8}
        rest <- nextBlock (amt - 1)
        return (L.cons startByte rest)
      | dcsNextBitNo dcs == 8 ->
        getBlock (fromIntegral amt) (dcsInput dcs)
      | otherwise ->
        raise (FormatError "Can't get a block on a non-byte boundary.")
 where
  getBlock len bstr
    | len < S.length bstr = do
      let (mine, rest) = S.splitAt len bstr
      dcs <- get
      set dcs{dcsNextBitNo = 8, dcsInput = rest}
      return (L.fromStrict mine)
    | S.null bstr = do
      getNextChunk
      dcs <- get
      let byte1 = dcsCurByte dcs
      rest <- getBlock (len - 1) (dcsInput dcs)
      return (L.cons byte1 rest)
    | otherwise = do
      rest <- getBlock (len - S.length bstr) S.empty
      return (L.fromStrict bstr `L.append` rest)

nextCode :: Show a => HuffmanTree a -> DeflateM s a
nextCode tree = do
  b <- nextBits 1
  case advanceTree b tree of
    AdvanceError str -> raise (HuffmanTreeError str)
    NewTree tree' -> nextCode tree'
    Result x -> return x
{-# INLINE nextCode #-}

advanceToByte :: DeflateM s ()
advanceToByte = do
  dcs <- get
  set dcs{dcsNextBitNo = 8}

emitByte :: Word8 -> DeflateM s ()
emitByte b = do
  dcs <- get
  output' <- liftST (addByte (dcsOutput dcs) b)
  let adler' = advanceAdler (dcsAdler32 dcs) b
  set dcs{dcsOutput = output', dcsAdler32 = adler'}
{-# INLINE emitByte #-}

emitBlock :: L.ByteString -> DeflateM s ()
emitBlock b = do
  dcs <- get
  output' <- liftST (addChunk (dcsOutput dcs) b)
  let adler' = L.foldlChunks advanceAdlerBlock (dcsAdler32 dcs) b
  set dcs{dcsOutput = output', dcsAdler32 = adler'}

emitPastChunk :: Int -> Int -> DeflateM s ()
emitPastChunk dist len = do
  dcs <- get
  (output', newChunk) <- liftST (addOldChunk (dcsOutput dcs) dist len)
  set
    dcs
      { dcsOutput = output'
      , dcsAdler32 = advanceAdlerBlock (dcsAdler32 dcs) newChunk
      }
{-# INLINE emitPastChunk #-}

finalAdler :: DeflateM s Word32
finalAdler = (finalizeAdler . dcsAdler32) <$> get

moveWindow :: DeflateM s ()
moveWindow = do
  dcs <- get
  possibleExcess <- liftST (emitExcess (dcsOutput dcs))
  case possibleExcess of
    Nothing ->
      return ()
    Just (builtChunk, output') -> do
      set dcs{dcsOutput = output'}
      publish builtChunk

finalize :: DeflateM s ()
finalize = do
  dcs <- get
  lastChunk <- liftST (finalizeWindow (dcsOutput dcs))
  publish lastChunk

{-# INLINE publish #-}
publish :: S.ByteString -> DeflateM s ()
publish bstr = DeflateM $ \st k ->
  return (Chunk bstr (k st ()))