packages feed

borsh-0.1.0: src/Codec/Borsh/Incremental/Monad.hs

module Codec.Borsh.Incremental.Monad (
    -- * Definition
    Incr(..)
  , runIncr
    -- * Operations supported by the monad
  , liftIncr
  , needChunk
  , decodeFail
    -- * (Partial) results
  , IDecode(..)
  , DeserialiseFailure(..)
  , runIDecode
  ) where

import Control.Monad
import Control.Monad.ST
import Control.Exception

import qualified Data.ByteString      as S
import qualified Data.ByteString.Lazy as L

import Codec.Borsh.Incremental.Located

{-------------------------------------------------------------------------------
  Definition
-------------------------------------------------------------------------------}

-- | Monad for incremental decoding
--
-- Think of 'Incr' as the monad we use for processing the full input, whereas
-- 'Decoder' is the monad used for processing a single chunk of the input.
newtype Incr s a = Incr {
      getIncr :: forall r. (a -> ST s (IDecode s r)) -> ST s (IDecode s r)
    }

runIncr :: Incr s (LocatedChunk, a) -> ST s (IDecode s a)
runIncr (Incr f) = f $ \(chunk, x) -> return $ IDecodeDone chunk x

{-------------------------------------------------------------------------------
  Monad instance
-------------------------------------------------------------------------------}

instance Functor (Incr s) where
  fmap = liftM

instance Applicative (Incr s) where
  pure x = Incr ($ x)
  (<*>)  = ap

instance Monad (Incr s) where
  return  = pure
  m >>= f = Incr $ \k -> getIncr m $ \x -> getIncr (f x) k

{-------------------------------------------------------------------------------
  Operations supported by the monad
-------------------------------------------------------------------------------}

liftIncr :: ST s a -> Incr s a
liftIncr action = Incr (action >>=)

needChunk :: Incr s (Maybe S.ByteString)
needChunk = Incr $ \k -> return $ IDecodePartial $ \mbs -> k mbs

decodeFail :: LocatedChunk -> String -> Incr s a
decodeFail chunk@(L _ off) e = Incr $ \_ ->
    return $ IDecodeFail chunk (DeserialiseFailure off e)

{-------------------------------------------------------------------------------
  (Partial) results
-------------------------------------------------------------------------------}

data IDecode s a =
    IDecodePartial (Maybe S.ByteString -> ST s (IDecode s a))
  | IDecodeDone !LocatedChunk a
  | IDecodeFail !LocatedChunk DeserialiseFailure

-- | Error type for deserialisation.
data DeserialiseFailure =
    DeserialiseFailure
      ByteOffset -- ^ The position of the decoder when the failure occurred
      String     -- ^ Message explaining the failure
  deriving stock (Eq, Show)
  deriving anyclass (Exception)

runIDecode ::
     (forall s. ST s (IDecode s a))
  -> L.ByteString
  -> Either DeserialiseFailure (L.ByteString, ByteOffset, a)
runIDecode d lbs =
    runST (go (L.toChunks lbs) =<< d)
  where
    go :: [S.ByteString]
       -> IDecode s a
       -> ST s (Either DeserialiseFailure (L.ByteString, ByteOffset, a))
    go chunks = \case
        IDecodeFail _ err ->
          return (Left err)
        IDecodeDone (L bs off) x ->
          return (Right (L.fromChunks $ prepend bs chunks, off, x))
        IDecodePartial k ->
          case chunks of
            []         -> k Nothing   >>= go []
            bs:chunks' -> k (Just bs) >>= go chunks'

    prepend :: S.ByteString -> [S.ByteString] -> [S.ByteString]
    prepend bs bss
      | S.null bs = bss
      | otherwise = bs : bss