packages feed

borsh-0.2.0: src/Codec/Borsh/Incremental/Decoder.hs

{-# OPTIONS_GHC -Wno-incomplete-patterns #-}

module Codec.Borsh.Incremental.Decoder (
    -- * Definition
    Decoder(..)
    -- * Operations supported by any decoder
  , liftDecoder
  , decodeLittleEndian
  , decodeLargeToken
  , decodeIncremental
  , decodeIncremental_
    -- * Running
  , DecodeResult(..)
  , deserialiseByteString
  ) where

import Control.Applicative
import Control.Monad
import Control.Monad.Fail
import Control.Monad.ST
import Data.Word

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

import Codec.Borsh.Incremental.Located
import Codec.Borsh.Incremental.Monad
import Codec.Borsh.Internal.Util.ByteString
import Codec.Borsh.Internal.Util.ByteSwap

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

-- | Decoder
--
-- A decoder describes how to match against a single chunk of the input.
--
-- For decoders for primitive types, use 'Codec.Borsh.FromBorsh' instances.
newtype Decoder s a = Decoder {
      matchChunk :: LocatedChunk -> ST s (LocatedChunk, DecodeResult s a)
    }

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

-- | Lift an 'ST' operation into the 'Decoder' monad.
liftDecoder :: ST s a -> Decoder s a
liftDecoder sa = Decoder $ \chunk -> (chunk, ) . DecodeDone <$> sa

-- | Decode a value encoded in little endian byte order (e.g. as mandated by the
-- [Borsh](https://borsh.io) spec).
decodeLittleEndian :: forall s a. ByteSwap a => Decoder s a
decodeLittleEndian = Decoder aux
  where
    aux :: LocatedChunk -> ST s (LocatedChunk, DecodeResult s a)
    aux chunk@(L bs off) =
        case peekByteString bs of
          Just (x, sizeX, bs') ->
            return (L bs' (off + sizeX), DecodeDone x)
          Nothing ->
            return (chunk, DecodeNeedsData decodeLittleEndian)

-- | Large token of known length that spans multiple chunks
--
-- This is NOT incremental: all chunks will be read into memory before the
-- result is returned. Primarily useful for large types that are not
-- easily split into (valid) chunks, such as UTF8-encoded text (if were
-- wanted to split that, we'd have to split it at UTF8 boundaries).
decodeLargeToken ::
     Word32 -- ^ Number of bytes to decode
  -> Decoder s L.ByteString
decodeLargeToken n = Decoder $ \chunk ->
    return (chunk, DecodeLargeToken n return)

-- | Incremental interface
--
-- When decoding large objects such as lists, we do not want to bring all
-- required chunks into memory before decoding the list. Instead, we want to
-- decode the list elements as we go. In this case, 'DecodeIncremental' can
-- be used to repeatedly decode a value using decoder for the elements.
--
-- NOTE: This interface is incremental in the sense that the /input chunks/
-- are read one at a time. It is /NOT/ incremental in the generated /output/.
decodeIncremental ::
     Word32      -- ^ Number of elements in the sequence to decode
  -> Decoder s a -- ^ 'Decoder' to run for the individual elements
  -> Decoder s [a]
decodeIncremental n d = Decoder $ \chunk ->
    return (chunk, DecodeIncremental n d return)

-- | Variation on 'decoreIncremental', where we do not accumulate results
--
-- This is useful for example for datatypes that we can update imperatively
-- (using the 'ST' monad), such as mutable arrays. It could also be used to skip
-- over unused parts of the input.
decodeIncremental_ ::
     Word32       -- ^ Number of elements in the sequence to decode
  -> Decoder s () -- ^ 'Decoder' to run for the individual elements
  -> Decoder s ()
decodeIncremental_ n d = Decoder $ \chunk ->
    return (chunk, DecodeIncremental_ n d $ return ())

{-------------------------------------------------------------------------------
  Results
-------------------------------------------------------------------------------}

data DecodeResult s a where
  -- | The decoder terminated successfully: we can stop decoding
  DecodeDone :: a -> DecodeResult s a

  -- | The decoder failed: we should abort
  DecodeFail :: String -> DecodeResult s a

  -- | The decoder needs more data before it can continue
  --
  -- NOTE: The decoder that is waiting for more data may not be (and typically
  -- will not be) the decoder we started with in 'matchChunk': in the typical
  -- case, a bunch of values will have been decoded successfully before we get
  -- to a (continuation) decoder that requires data beyond the current chunk.
  DecodeNeedsData :: Decoder s a -> DecodeResult s a

  -- | Large token of known length that spans multiple chunks
  --
  -- See 'decodeLargeToken' for discussion.
  --
  -- The continuation will be called with a lazy bytestring of precisely the
  -- requested length (provided enough input is available, of course), along
  -- with the remaining input token to be provided to the continuation decoder.
  DecodeLargeToken ::
       Word32  -- ^ Required number of bytes
    -> (L.ByteString -> Decoder s a)
    -> DecodeResult s a

  -- | Incremental interface
  --
  -- See 'decodeIncremental' for discussion.
  DecodeIncremental ::
       Word32               -- ^ How often to repeat the smaller decoder
    -> Decoder s a          -- ^ Decoder to repeat
    -> ([a] -> Decoder s b) -- ^ Process all elements
    -> DecodeResult s b

  -- | Variation on 'DecodeIncremental', where we do not accumulate results
  --
  -- See 'decodeIncremental_' for discussion.
  DecodeIncremental_ ::
       Word32        -- ^ How often to repeat the smaller decoder
    -> Decoder s ()  -- ^ Decoder to repeat (imperatively handling each element)
    -> Decoder s a   -- ^ Continuation
    -> DecodeResult s a

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

instance Functor (Decoder s) where
  fmap = liftA

instance Applicative (Decoder s) where
  pure x = Decoder $ \chunk -> return (chunk, DecodeDone x)
  (<*>)  = ap

instance Monad (Decoder s) where
  return  = pure
  x >>= f = Decoder $ \chunk -> do
      (chunk', result) <- matchChunk x chunk
      case result of
        DecodeDone a ->
          matchChunk (f a) chunk'
        DecodeFail e ->
          return (chunk', DecodeFail e)
        DecodeNeedsData d ->
          return (chunk', DecodeNeedsData (d >>= f))
        DecodeLargeToken reqLen k ->
          return (chunk', DecodeLargeToken reqLen (k >=> f))
        DecodeIncremental count d k ->
          return (chunk', DecodeIncremental count d (k >=> f))
        DecodeIncremental_ count d k ->
          return (chunk', DecodeIncremental_ count d (k >>= f))

instance MonadFail (Decoder s) where
  fail e = Decoder $ \chunk -> return (chunk, DecodeFail e)

{-------------------------------------------------------------------------------
  Running decoders
-------------------------------------------------------------------------------}

-- | Top-level entry point
--
-- We start without any input at all (and depending on the specific decoder,
-- we may never need any).
runDecoder :: Decoder s a -> Incr s (LocatedChunk, a)
runDecoder = runWith $ L S.empty 0

-- | Run decoder against specified chunk
runWith :: LocatedChunk -> Decoder s a -> Incr s (LocatedChunk, a)
runWith chunk d = uncurry processResult =<< liftIncr (matchChunk d chunk)

{-------------------------------------------------------------------------------
  Processing the result of a decoder
-------------------------------------------------------------------------------}

-- | Process decoder result
processResult :: LocatedChunk -> DecodeResult s a -> Incr s (LocatedChunk, a)
processResult chunk = \case
    DecodeDone x -> return    (chunk, x)
    DecodeFail e -> decodeFail chunk  e

    DecodeNeedsData      d   -> processNeedsData      d   chunk
    DecodeLargeToken   n   k -> processLargeToken   n   k chunk
    DecodeIncremental  n d k -> processIncremental  n d k chunk
    DecodeIncremental_ n d k -> processIncremental_ n d k chunk

processNeedsData ::
     Decoder s a
  -> Located S.ByteString
  -> Incr s (LocatedChunk, a)
processNeedsData d chunk@(L bs off) = needChunk >>= \case
    Nothing   -> decodeFail chunk "end of input"
    Just next -> runWith (L (bs <> next) off) d

-- | Auxiliary to 'processResult': process token that spans multple chunks
--
-- Precondition: if the accumulated length exceeds the required length, we must
-- be able to split the mostly added chunk to make up for the difference.
processLargeToken :: forall s a.
     Word32                         -- ^ Required total size
  -> (L.ByteString -> Decoder s a)  -- ^ Continuation
  -> LocatedChunk                   -- ^ Current chunk
  -> Incr s (LocatedChunk, a)
processLargeToken reqLen k = go . toLocatedChunks
  where
    go :: LocatedChunks -> Incr s (LocatedChunk, a)
    go acc =
        case splitChunks reqLen acc of
          Nothing -> needChunk >>= \case
            Nothing   -> decodeFail (fromLocatedChunks acc) "end of input"
            Just next -> go (addChunk next acc)
          Just (large, left) ->
            uncurry processResult =<< liftIncr (matchChunk (k large) left)

-- | Auxiliary to 'processResult': incremental decoding
processIncremental :: forall s a b.
     Word32                -- ^ Number of elements required
  -> Decoder s a           -- ^ Decoder to repeat
  -> ([a] -> Decoder s b)  -- ^ Continuation once we processed all elements
  -> LocatedChunk          -- ^ Current chunk
  -> Incr s (LocatedChunk, b)
processIncremental count d k = go [] count
  where
    go :: [a] -> Word32 -> LocatedChunk -> Incr s (LocatedChunk, b)
    go acc 0 chunk = do result <- liftIncr (matchChunk (k (reverse acc)) chunk)
                        uncurry processResult result
    go acc n chunk = do (chunk', a) <- runWith chunk d
                        go (a:acc) (n - 1) chunk'

-- | Imperative version of 'processIncremental'
--
-- See 'DecodeIncremental_' for discussion.
processIncremental_ :: forall s a.
     Word32        -- ^ Number of elements required
  -> Decoder s ()  -- ^ Decoder to repeat
  -> Decoder s a   -- ^ Continuation once we processed all elements
  -> LocatedChunk  -- ^ Current chunk
  -> Incr s (LocatedChunk, a)
processIncremental_ count d k = go count
  where
    go :: Word32 -> LocatedChunk -> Incr s (LocatedChunk, a)
    go 0 chunk = do result <- liftIncr (matchChunk k chunk)
                    uncurry processResult result
    go n chunk = do (chunk', ()) <- runWith chunk d
                    go (n - 1) chunk'

{-------------------------------------------------------------------------------
  Top-level API
-------------------------------------------------------------------------------}

-- | Run decoder
deserialiseByteString ::
     (forall s. Decoder s a)
  -- ^ Decoder
  -> L.ByteString
  -- ^ Input
  -> Either DeserialiseFailure (L.ByteString, ByteOffset, a)
  -- ^ Left-over input, offset of the left-over input relative to the start,
  -- and the result.
deserialiseByteString d = runIDecode (runIncr (runDecoder d))