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