packages feed

dahdit-0.8.0: src/Dahdit/Run.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE UndecidableInstances #-}

module Dahdit.Run
  ( GetError (..)
  , prettyGetError
  , runGetInternal
  , GetIncEnv (..)
  , newGetIncEnv
  , GetIncChunk (..)
  , GetIncRequest (..)
  , GetIncSuspend (..)
  , GetIncCb
  , runGetIncInternal
  , runCount
  , runPutInternal
  )
where

import Control.Applicative (Alternative (..))
import Control.Exception (Exception (..))
import Control.Monad (replicateM_, unless)
import Control.Monad.Except (ExceptT, MonadError, runExceptT, throwError)
import Control.Monad.Free.Church (F (..), iterM)
import Control.Monad.Free.Class (MonadFree (..))
import Control.Monad.Primitive (MonadPrim, PrimMonad (..))
import Control.Monad.Reader (MonadReader (..), ReaderT (..), asks)
import Control.Monad.State.Strict (MonadState, State, runState)
import qualified Control.Monad.State.Strict as State
import Control.Monad.Trans (MonadTrans (..))
import Control.Monad.Trans.Free.Church (FT (..), iterT)
import Control.Monad.Trans.Maybe (MaybeT (..))
import Dahdit.Free
  ( Get (..)
  , GetF (..)
  , GetLookAheadF (..)
  , GetScopeF (..)
  , GetStaticArrayF (..)
  , GetStaticSeqF (..)
  , Put
  , PutF (..)
  , PutM (..)
  , PutStaticArrayF (..)
  , PutStaticHintF (..)
  , PutStaticSeqF (..)
  , ScopeMode (..)
  )
import Dahdit.Mem (ReadMem (..), WriteMem (..), readSBSMem, writeSBSMem)
import Dahdit.Nums
  ( DoubleBE
  , DoubleLE
  , FloatBE
  , FloatLE
  , Int16BE
  , Int16LE (..)
  , Int24BE
  , Int24LE
  , Int32BE
  , Int32LE
  , Int64BE
  , Int64LE
  , Word16BE
  , Word16LE (..)
  , Word24BE
  , Word24LE
  , Word32BE
  , Word32LE
  , Word64BE
  , Word64LE
  )
import Dahdit.Proxy (proxyForF)
import Dahdit.Sizes (ByteCount (..), ElemCount (..), primByteSizeOf, staticByteSize)
import Data.Coerce (coerce)
import Data.Foldable (for_, toList)
import Data.Int (Int8)
import Data.Maybe (fromJust)
import Data.Primitive.ByteArray (ByteArray (..))
import Data.Primitive.MutVar (MutVar, modifyMutVar', newMutVar, readMutVar, writeMutVar)
import Data.Primitive.PrimArray (PrimArray (..), sizeofPrimArray)
import Data.Sequence (Seq (..))
import qualified Data.Sequence as Seq
import Data.Text (Text)
import qualified Data.Text as T
import Data.Word (Word8)

-- Sizes:

getStaticSeqSize :: GetStaticSeqF a -> ByteCount
getStaticSeqSize (GetStaticSeqF ec g _) = staticByteSize (proxyForF g) * coerce ec

getStaticArraySize :: GetStaticArrayF a -> ByteCount
getStaticArraySize (GetStaticArrayF n prox _) = primByteSizeOf prox * coerce n

putStaticSeqSize :: PutStaticSeqF a -> ByteCount
putStaticSeqSize (PutStaticSeqF n _ _ s _) = staticByteSize (proxyForF s) * coerce n

putStaticArrayElemSize :: PutStaticArrayF a -> ByteCount
putStaticArrayElemSize (PutStaticArrayF _ _ zs _) = primByteSizeOf (proxyForF zs)

putStaticArraySize :: PutStaticArrayF a -> ByteCount
putStaticArraySize (PutStaticArrayF n _ zs _) = primByteSizeOf (proxyForF zs) * coerce n

-- Get:

data GetError
  = GetErrorLocalCap !Text !ByteCount !ByteCount
  | GetErrorScopedMismatch !ScopeMode !ByteCount !ByteCount
  | GetErrorFail !Text
  | GetErrorGlobalCap !Text !ByteCount !ByteCount
  | GetErrorRemaining !ByteCount
  deriving stock (Eq, Show)

instance Exception GetError where
  displayException = T.unpack . prettyGetError

prettyGetError :: GetError -> Text
prettyGetError = \case
  GetErrorLocalCap nm ac bc ->
    "End of chunk parsing "
      <> nm
      <> " (have "
      <> T.pack (show (unByteCount ac))
      <> " bytes, need "
      <> T.pack (show (unByteCount bc))
      <> ")"
  GetErrorScopedMismatch sm ac bc ->
    let ty = case sm of ScopeModeExact -> "exact"; ScopeModeWithin -> "within"
    in  "Did not parse "
          <> ty
          <> " scoped input (read "
          <> T.pack (show (unByteCount ac))
          <> " bytes, expected "
          <> T.pack (show (unByteCount bc))
          <> ")"
  GetErrorFail msg -> "User error: " <> msg
  GetErrorGlobalCap nm ac bc ->
    "Hit limit parsing "
      <> nm
      <> " (allowed "
      <> T.pack (show (unByteCount ac))
      <> " bytes, need "
      <> T.pack (show (unByteCount bc))
      <> ")"
  GetErrorRemaining ac -> "Cannot read remaining length in stream context (read " <> T.pack (show (unByteCount ac)) <> ")"

-- | Get from a single buffer
runGetInternal :: (ReadMem r m) => ByteCount -> Get a -> ByteCount -> r -> m (Either GetError a, ByteCount)
runGetInternal off act cap mem = do
  let chunk = GetIncChunk off cap mem
  env <- newGetIncEnv (Just (cap - off)) chunk
  (ea, _, off') <- runGetIncInternal act env (const (pure Nothing))
  pure (ea, off')

-- Get inc

data GetIncChunk r = GetIncChunk
  { gicLocalOff :: !ByteCount
  -- ^ Offset from start of local buffer to data
  , gicLocalCap :: !ByteCount
  -- ^ Capacity of local buffer
  , gicArray :: !r
  -- ^ Source buffer
  }

data GetIncEnv s r = GetIncEnv
  { gieGlobalAbs :: !(MutVar s ByteCount)
  -- ^ Offset from start of parsing (0) to current position
  , gieGlobalRel :: !(MutVar s ByteCount)
  -- ^ Offset from start of parsing (0) to start of buffer
  -- It will always be the case that gloRel <= gloAbs
  , gieGlobalCap :: !(MutVar s (Seq ByteCount))
  -- ^ Stack of maximum capcacity (across all chunks)
  -- Top of stack is end of sequence.
  -- This will be narrowed to check scoped length.
  , gieCurChunk :: !(MutVar s (GetIncChunk r))
  -- ^ Current chunk
  , gieLookAhead :: !(MutVar s (Seq ByteCount))
  -- ^ Stack of look ahead points (in absolute position)
  -- Top of stack is end of sequence.
  }

newGetIncEnv :: (MonadPrim s m) => Maybe ByteCount -> GetIncChunk r -> m (GetIncEnv s r)
newGetIncEnv mayCap chunk = do
  gloAbsVar <- newMutVar 0
  gloRelVar <- newMutVar 0
  gloCapVar <- newMutVar (maybe Empty Seq.singleton mayCap)
  curChunkVar <- newMutVar chunk
  lookAheadVar <- newMutVar Empty
  pure (GetIncEnv gloAbsVar gloRelVar gloCapVar curChunkVar lookAheadVar)

-- | A request for more data. Includes absolute position, offset in the current buffer, and required length.
data GetIncRequest = GetIncRequest
  { girAbsPos :: !ByteCount
  , girBaseOff :: !ByteCount
  , girNeedLength :: !ByteCount
  }
  deriving stock (Eq, Ord, Show)

-- | See 'GetIncCb' - this is the functor-ized version so we can suspend execution.
data GetIncSuspend z x = GetIncSuspend !GetIncRequest !(Maybe z -> x)
  deriving stock (Functor)

type GetIncSuspendChunk r = GetIncSuspend (GetIncChunk r)

-- Should not implement 'MonadReader' so we can prevent scoped operations like 'local'
newtype GetIncM s r m a = GetIncM {unGetIncM :: ReaderT (GetIncEnv s r) (ExceptT GetError (FT (GetIncSuspendChunk r) m)) a}
  deriving newtype
    ( Functor
    , Applicative
    , Monad
    , MonadError GetError
    , MonadFree (GetIncSuspendChunk r)
    )

instance MonadTrans (GetIncM s r) where
  lift = GetIncM . lift . lift . lift

-- | Return new chunk containing enough data (or nothing).
type GetIncCb z m = GetIncRequest -> m (Maybe z)

type GetIncCbChunk r m = GetIncCb (GetIncChunk r) m

pushMutVar :: (MonadPrim s m) => MutVar s (Seq a) -> a -> m ()
pushMutVar v a = modifyMutVar' v (:|> a)

popMutVar :: (MonadPrim s m) => MutVar s (Seq a) -> m ()
popMutVar v = modifyMutVar' v (\case Empty -> Empty; as :|> _ -> as)

peekMutVar :: (MonadPrim s m) => MutVar s (Seq a) -> m (Maybe a)
peekMutVar = fmap (\case Empty -> Nothing; _ :|> a -> Just a) . readMutVar

runGetIncM :: (MonadPrim s m) => GetIncM s r m a -> GetIncEnv s r -> GetIncCbChunk r m -> m (Either GetError a)
runGetIncM m env cb =
  runFT
    (runExceptT (runReaderT (unGetIncM m) env))
    pure
    (\k2 (GetIncSuspend req k1) -> cb req >>= k2 . k1)

guardReadBytes :: (MonadPrim s m) => Text -> ByteCount -> GetIncM s r m (ByteCount, GetIncChunk r, ByteCount)
guardReadBytes nm bc = do
  GetIncEnv gloAbsRef gloRelRef capStackRef chunkRef lookStackRef <- GetIncM ask
  -- First check if we're in cap
  gloAbsStart <- lift (readMutVar gloAbsRef)
  let gloAbsEnd = gloAbsStart + bc
  mayCap <- lift (peekMutVar capStackRef)
  case mayCap of
    Just cap | gloAbsEnd > cap -> throwError (GetErrorGlobalCap nm cap gloAbsEnd)
    _ -> pure ()
  -- Now check that we have enough in the local buf
  gloRel <- lift (readMutVar gloRelRef)
  oldChunk@(GetIncChunk oldOff oldCap _) <- lift (readMutVar chunkRef)
  let oldLocOffStart = gloAbsStart - gloRel + oldOff
      oldLocOffEnd = oldLocOffStart + bc
  (newChunk, newLocOffStart) <-
    if oldLocOffEnd <= oldCap
      then pure (oldChunk, oldLocOffStart)
      else do
        lookStack <- lift (readMutVar lookStackRef)
        let gloBaseStart = case lookStack of Empty -> gloAbsStart; x :<| _ -> x
            baseOffStart = gloBaseStart - gloRel + oldOff
            baseOffEnd = gloAbsStart - gloRel + oldOff + bc
        let needLength = baseOffEnd - baseOffStart
            req = GetIncRequest gloAbsStart baseOffStart needLength
        wrap $ GetIncSuspend req $ \case
          Nothing -> throwError (GetErrorLocalCap nm oldCap baseOffEnd)
          Just newChunk@(GetIncChunk newOff newCap _) -> do
            let newEnd = newOff + needLength
            unless (newEnd <= newCap) (throwError (GetErrorLocalCap nm newCap newOff))
            lift (writeMutVar chunkRef newChunk)
            lift (writeMutVar gloRelRef gloBaseStart)
            pure (newChunk, gicLocalOff newChunk)
  pure (gloAbsEnd, newChunk, newLocOffStart)

-- Memory read function takes local start offset
readBytes :: (MonadPrim s m) => Text -> ByteCount -> (r -> ByteCount -> m a) -> GetIncM s r m a
readBytes nm bc f = do
  (gloAbsEnd, newChunk, newLocOffStart) <- guardReadBytes nm bc
  let mem = gicArray newChunk
  a <- lift (f mem newLocOffStart)
  gloAbsRef <- GetIncM (asks gieGlobalAbs)
  lift (writeMutVar gloAbsRef gloAbsEnd)
  pure a

readScope :: (MonadPrim s m, ReadMem r m) => GetScopeF (GetIncM s r m a) -> GetIncM s r m a
readScope (GetScopeF sm bc g k) = do
  GetIncEnv gloAbsRef _ capStackRef _ _ <- GetIncM ask
  gloAbsStart <- lift (readMutVar gloAbsRef)
  let gloAbsMax = gloAbsStart + bc
  mayCap <- lift (peekMutVar capStackRef)
  case mayCap of
    Just cap
      | sm == ScopeModeExact && gloAbsMax > cap ->
          throwError (GetErrorGlobalCap "scope" cap gloAbsMax)
    _ -> pure ()
  lift (pushMutVar capStackRef gloAbsMax)
  a <- interpGetInc g
  lift (popMutVar capStackRef)
  gloAbsEnd <- lift (readMutVar gloAbsRef)
  let actualBc = gloAbsEnd - gloAbsStart
  if (sm == ScopeModeExact && actualBc == bc) || (sm == ScopeModeWithin && actualBc <= bc)
    then k a
    else throwError (GetErrorScopedMismatch sm actualBc bc)

readStaticSeq :: (MonadPrim s m, ReadMem r m) => GetStaticSeqF (GetIncM s r m a) -> GetIncM s r m a
readStaticSeq gss@(GetStaticSeqF ec g k) = do
  let bc = getStaticSeqSize gss
  _ <- guardReadBytes "static sequence" bc
  ss <- Seq.replicateA (coerce ec) (interpGetInc g)
  k ss

readStaticArray :: (MonadPrim s m, ReadMem r m) => GetStaticArrayF (GetIncM s r m a) -> GetIncM s r m a
readStaticArray gsa@(GetStaticArrayF _ _ k) = do
  let bc = getStaticArraySize gsa
  ByteArray ba <- readBytes "static vector" bc (\mem off -> cloneArrayMemInBytes mem off bc)
  k (PrimArray ba)

readLookAhead :: (MonadPrim s m, ReadMem r m) => GetLookAheadF (GetIncM s r m a) -> GetIncM s r m a
readLookAhead (GetLookAheadF g k) = do
  GetIncEnv gloAbsRef _ _ _ lookStackRef <- GetIncM ask
  gloAbs <- lift (readMutVar gloAbsRef)
  lift (pushMutVar lookStackRef gloAbs)
  a <- interpGetInc g
  lift (popMutVar lookStackRef)
  lift (writeMutVar gloAbsRef gloAbs)
  k a

interpGetInc :: (MonadPrim s m, ReadMem r m) => Get a -> GetIncM s r m a
interpGetInc (Get g) = flip iterM g $ \case
  GetFWord8 k -> readBytes "Word8" 1 (indexMemInBytes @_ @_ @Word8) >>= k
  GetFInt8 k -> readBytes "Int8" 1 (indexMemInBytes @_ @_ @Int8) >>= k
  GetFWord16LE k -> readBytes "Word16LE" 2 (indexMemInBytes @_ @_ @Word16LE) >>= k
  GetFInt16LE k -> readBytes "Int16LE" 2 (indexMemInBytes @_ @_ @Int16LE) >>= k
  GetFWord24LE k -> readBytes "Word24LE" 3 (indexMemInBytes @_ @_ @Word24LE) >>= k
  GetFInt24LE k -> readBytes "Int24LE" 3 (indexMemInBytes @_ @_ @Int24LE) >>= k
  GetFWord32LE k -> readBytes "Word32LE" 4 (indexMemInBytes @_ @_ @Word32LE) >>= k
  GetFInt32LE k -> readBytes "Int32LE" 4 (indexMemInBytes @_ @_ @Int32LE) >>= k
  GetFWord64LE k -> readBytes "Word64LE" 8 (indexMemInBytes @_ @_ @Word64LE) >>= k
  GetFInt64LE k -> readBytes "Int64LE" 8 (indexMemInBytes @_ @_ @Int64LE) >>= k
  GetFFloatLE k -> readBytes "FloatLE" 4 (indexMemInBytes @_ @_ @FloatLE) >>= k
  GetFDoubleLE k -> readBytes "DoubleLE" 8 (indexMemInBytes @_ @_ @DoubleLE) >>= k
  GetFWord16BE k -> readBytes "Word16BE" 2 (indexMemInBytes @_ @_ @Word16BE) >>= k
  GetFInt16BE k -> readBytes "Int16BE" 2 (indexMemInBytes @_ @_ @Int16BE) >>= k
  GetFWord24BE k -> readBytes "Word24BE" 3 (indexMemInBytes @_ @_ @Word24BE) >>= k
  GetFInt24BE k -> readBytes "Int24BE" 3 (indexMemInBytes @_ @_ @Int24BE) >>= k
  GetFWord32BE k -> readBytes "Word32BE" 4 (indexMemInBytes @_ @_ @Word32BE) >>= k
  GetFInt32BE k -> readBytes "Int32BE" 4 (indexMemInBytes @_ @_ @Int32BE) >>= k
  GetFWord64BE k -> readBytes "Word64BE" 8 (indexMemInBytes @_ @_ @Word64BE) >>= k
  GetFInt64BE k -> readBytes "Int64BE" 8 (indexMemInBytes @_ @_ @Int64BE) >>= k
  GetFFloatBE k -> readBytes "FloatBE" 4 (indexMemInBytes @_ @_ @FloatBE) >>= k
  GetFDoubleBE k -> readBytes "DoubleBE" 8 (indexMemInBytes @_ @_ @DoubleBE) >>= k
  GetFShortByteString bc k ->
    readBytes "ShortByteString" bc (\mem off -> readSBSMem mem off bc) >>= k
  GetFStaticSeq gss -> readStaticSeq gss
  GetFStaticArray gsa -> readStaticArray gsa
  GetFByteArray bc k ->
    readBytes "ByteArray" bc (\mem off -> cloneArrayMemInBytes mem off bc) >>= k
  GetFScope gs -> readScope gs
  GetFSkip bc k -> readBytes "skip" bc (\_ _ -> pure ()) >> k
  GetFLookAhead gla -> readLookAhead gla
  GetFRemainingSize k -> do
    GetIncEnv gloAbsRef _ capStackRef _ _ <- GetIncM ask
    gloAbs <- lift (readMutVar gloAbsRef)
    capStack <- lift (readMutVar capStackRef)
    case capStack of
      Empty -> throwError (GetErrorRemaining gloAbs)
      _ :|> cap -> k (cap - gloAbs)
  GetFFail msg -> throwError (GetErrorFail msg)

runGetIncInternal
  :: (MonadPrim s m, ReadMem r m)
  => Get a
  -> GetIncEnv s r
  -> GetIncCbChunk r m
  -> m (Either GetError a, ByteCount, ByteCount)
runGetIncInternal getter env cb = do
  let m = interpGetInc getter
  res <- runGetIncM m env cb
  gloAbs <- readMutVar (gieGlobalAbs env)
  gloRel <- readMutVar (gieGlobalRel env)
  curChunk <- readMutVar (gieCurChunk env)
  let baseOff = gloAbs - gloRel + gicLocalOff curChunk
  pure (res, gloAbs, baseOff)

-- Put:

data PutEnv s q = PutEnv
  { peOff :: !(MutVar s ByteCount)
  -- ^ Offset in bytes from start of buffer
  , peCap :: !ByteCount
  -- ^ Capacity of buffer segment
  , peArray :: !(q s)
  -- ^ Destination buffer
  }

newPutEnv :: (PrimMonad m) => ByteCount -> ByteCount -> q (PrimState m) -> m (PutEnv (PrimState m) q)
newPutEnv off cap mem = do
  offRef <- newMutVar off
  pure (PutEnv offRef cap mem)

newtype PutEff q m a = PutEff {unPutEff :: ReaderT (PutEnv (PrimState m) q) m a}
  deriving newtype (Functor, Applicative, Monad)

deriving newtype instance (Monad m, s ~ PrimState m) => MonadReader (PutEnv s q) (PutEff q m)

runPutEff :: PutEff q m a -> PutEnv (PrimState m) q -> m a
runPutEff act = runReaderT (unPutEff act)

stPutEff :: (Monad m) => m a -> PutEff q m a
stPutEff = PutEff . lift

newtype PutRun q m a = PutRun {unPutRun :: FT PutF (PutEff q m) a}
  deriving newtype (Functor, Applicative, Monad)

writeBytes :: (PrimMonad m) => ByteCount -> (q (PrimState m) -> ByteCount -> m ()) -> PutEff q m ()
writeBytes bc f = do
  PutEnv offRef _ mem <- ask
  stPutEff $ do
    off <- readMutVar offRef
    f mem off
    let newOff = off + bc
    writeMutVar offRef newOff

writeStaticSeq :: (WriteMem q m) => PutStaticSeqF (PutEff q m a) -> PutEff q m a
writeStaticSeq (PutStaticSeqF n mz p s k) = do
  for_ (take (coerce n) (toList s)) $ \a -> do
    mkPutEff (p a)
  let e = Seq.length s
  unless (coerce n <= e) $ do
    let q = mkPutEff (p (fromJust mz))
    replicateM_ (coerce n - e) q
  k

writeStaticArray :: (WriteMem q m) => PutStaticArrayF (PutEff q m a) -> PutEff q m a
writeStaticArray psa@(PutStaticArrayF needElems mz pa@(PrimArray ba) k) = do
  let elemSize = putStaticArrayElemSize psa
      haveElems = ElemCount (sizeofPrimArray pa)
      useElems = min haveElems needElems
      useBc = elemSize * coerce useElems
  writeBytes useBc (copyArrayMemInBytes (ByteArray ba) 0 useBc)
  let needBc = putStaticArraySize psa
  unless (useBc == needBc) $ do
    let extraBc = needBc - useBc
    case mz of
      Nothing -> error "no default element for undersized static array"
      Just z -> writeBytes extraBc (setMemInBytes extraBc z)
  k

execPutRun :: (WriteMem q m) => PutF (PutEff q m a) -> PutEff q m a
execPutRun = \case
  PutFWord8 x k -> writeBytes 1 (writeMemInBytes x) >> k
  PutFInt8 x k -> writeBytes 1 (writeMemInBytes x) >> k
  PutFWord16LE x k -> writeBytes 2 (writeMemInBytes x) >> k
  PutFInt16LE x k -> writeBytes 2 (writeMemInBytes x) >> k
  PutFWord24LE x k -> writeBytes 3 (writeMemInBytes x) >> k
  PutFInt24LE x k -> writeBytes 3 (writeMemInBytes x) >> k
  PutFWord32LE x k -> writeBytes 4 (writeMemInBytes x) >> k
  PutFInt32LE x k -> writeBytes 4 (writeMemInBytes x) >> k
  PutFWord64LE x k -> writeBytes 8 (writeMemInBytes x) >> k
  PutFInt64LE x k -> writeBytes 8 (writeMemInBytes x) >> k
  PutFFloatLE x k -> writeBytes 4 (writeMemInBytes x) >> k
  PutFDoubleLE x k -> writeBytes 8 (writeMemInBytes x) >> k
  PutFWord16BE x k -> writeBytes 2 (writeMemInBytes x) >> k
  PutFInt16BE x k -> writeBytes 2 (writeMemInBytes x) >> k
  PutFWord24BE x k -> writeBytes 3 (writeMemInBytes x) >> k
  PutFInt24BE x k -> writeBytes 3 (writeMemInBytes x) >> k
  PutFWord32BE x k -> writeBytes 4 (writeMemInBytes x) >> k
  PutFInt32BE x k -> writeBytes 4 (writeMemInBytes x) >> k
  PutFWord64BE x k -> writeBytes 8 (writeMemInBytes x) >> k
  PutFInt64BE x k -> writeBytes 8 (writeMemInBytes x) >> k
  PutFFloatBE x k -> writeBytes 4 (writeMemInBytes x) >> k
  PutFDoubleBE x k -> writeBytes 8 (writeMemInBytes x) >> k
  PutFShortByteString bc sbs k ->
    writeBytes bc (writeSBSMem sbs bc) >> k
  PutFStaticSeq pss -> writeStaticSeq pss
  PutFStaticArray psa -> writeStaticArray psa
  PutFByteArray bc barr k ->
    writeBytes bc (copyArrayMemInBytes barr 0 bc) >> k
  PutFStaticHint (PutStaticHintF _ p k) -> mkPutEff p >> k

runPutRun :: (WriteMem q m) => PutRun q m a -> PutEnv (PrimState m) q -> m a
runPutRun = runPutEff . iterPutRun

iterPutRun :: (WriteMem q m) => PutRun q m a -> PutEff q m a
iterPutRun act = iterT execPutRun (unPutRun act)

mkPutRun :: PutM a -> PutRun q m a
mkPutRun (PutM (F w)) = PutRun (w pure wrap)

mkPutEff :: (WriteMem q m) => PutM a -> PutEff q m a
mkPutEff = iterPutRun . mkPutRun

runPutInternal :: (WriteMem q m) => ByteCount -> Put -> ByteCount -> q (PrimState m) -> m ByteCount
runPutInternal off act len mem = do
  let eff = mkPutRun act
      cap = off + len
  st@(PutEnv offRef _ _) <- newPutEnv off cap mem
  runPutRun eff st
  readMutVar offRef

-- Count:

newtype CountEff a = CountEff {unCountEff :: MaybeT (State ByteCount) a}
  deriving newtype (Functor, Applicative, Monad, Alternative, MonadState ByteCount)

runCountEff :: CountEff a -> ByteCount -> (Maybe a, ByteCount)
runCountEff act = runState (runMaybeT (unCountEff act))

newtype CountRun a = CountRun {unCountRun :: FT PutF CountEff a}
  deriving newtype (Functor, Applicative, Monad)

execCountRun :: PutF (CountEff a) -> CountEff a
execCountRun = \case
  PutFWord8 _ k -> State.modify' (1 +) >> k
  PutFInt8 _ k -> State.modify' (1 +) >> k
  PutFWord16LE _ k -> State.modify' (2 +) >> k
  PutFInt16LE _ k -> State.modify' (2 +) >> k
  PutFWord24LE _ k -> State.modify' (3 +) >> k
  PutFInt24LE _ k -> State.modify' (3 +) >> k
  PutFWord32LE _ k -> State.modify' (4 +) >> k
  PutFInt32LE _ k -> State.modify' (4 +) >> k
  PutFWord64LE _ k -> State.modify' (8 +) >> k
  PutFInt64LE _ k -> State.modify' (8 +) >> k
  PutFFloatLE _ k -> State.modify' (4 +) >> k
  PutFDoubleLE _ k -> State.modify' (8 +) >> k
  PutFWord16BE _ k -> State.modify' (2 +) >> k
  PutFInt16BE _ k -> State.modify' (2 +) >> k
  PutFWord24BE _ k -> State.modify' (3 +) >> k
  PutFInt24BE _ k -> State.modify' (3 +) >> k
  PutFWord32BE _ k -> State.modify' (4 +) >> k
  PutFInt32BE _ k -> State.modify' (4 +) >> k
  PutFWord64BE _ k -> State.modify' (8 +) >> k
  PutFInt64BE _ k -> State.modify' (8 +) >> k
  PutFFloatBE _ k -> State.modify' (4 +) >> k
  PutFDoubleBE _ k -> State.modify' (8 +) >> k
  PutFShortByteString bc _ k -> State.modify' (bc +) >> k
  PutFStaticSeq pss@(PutStaticSeqF _ _ _ _ k) ->
    let bc = putStaticSeqSize pss
    in  State.modify' (bc +) >> k
  PutFStaticArray psv@(PutStaticArrayF _ _ _ k) ->
    let bc = putStaticArraySize psv
    in  State.modify' (bc +) >> k
  PutFByteArray bc _ k -> State.modify' (bc +) >> k
  PutFStaticHint (PutStaticHintF bc _ k) -> State.modify' (bc +) >> k

runCountRun :: CountRun a -> ByteCount -> (Maybe a, ByteCount)
runCountRun = runCountEff . iterCountRun

iterCountRun :: CountRun a -> CountEff a
iterCountRun act = iterT execCountRun (unCountRun act)

mkCountRun :: PutM a -> CountRun a
mkCountRun (PutM (F w)) = CountRun (w pure wrap)

mkCountEff :: PutM a -> CountEff a
mkCountEff = iterCountRun . mkCountRun

runCount :: Put -> ByteCount
runCount act =
  let eff = mkCountRun act
      (_, bc) = runCountRun eff 0
  in  bc