packages feed

dahdit-0.2.0: src/Dahdit/Run.hs

module Dahdit.Run
  ( GetError (..)
  , prettyGetError
  , runGetInternal
  , runCount
  , runPutInternal
  )
where

import Control.Applicative (Alternative (..))
import Control.Exception (Exception (..), onException)
import Control.Monad (replicateM_, unless)
import Control.Monad.Except (ExceptT, MonadError, runExceptT, throwError)
import Control.Monad.Free.Church (F (..))
import Control.Monad.Reader (MonadReader (..), ReaderT (..), asks)
import Control.Monad.ST.Strict (ST, runST)
import Control.Monad.ST.Unsafe (unsafeIOToST, unsafeSTToIO)
import Control.Monad.State.Strict (MonadState, State, runState)
import qualified Control.Monad.State.Strict as State
import Control.Monad.Trans (lift)
import Control.Monad.Trans.Free (FreeT (..), iterT, wrap)
import Control.Monad.Trans.Maybe (MaybeT (..))
import Dahdit.Free
  ( Get (..)
  , GetF (..)
  , GetLookAheadF (..)
  , GetScopeF (..)
  , GetStaticArrayF (..)
  , GetStaticSeqF (..)
  , Put
  , PutF (..)
  , PutM (..)
  , PutStaticArrayF (..)
  , PutStaticHintF (..)
  , PutStaticSeqF (..)
  , ScopeMode (..)
  )
import Dahdit.LiftedPrimArray (LiftedPrimArray (..), sizeofLiftedPrimArray)
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 (..), staticByteSize)
import Data.Coerce (coerce)
import Data.Foldable (for_, toList)
import Data.Int (Int8)
import Data.Maybe (fromJust)
import Data.STRef.Strict (STRef, newSTRef, readSTRef, writeSTRef)
import qualified Data.Sequence as Seq
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 _) = staticByteSize prox * coerce n

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

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

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

-- Get:

data GetError
  = GetErrorParseNeed !String !ByteCount !ByteCount
  | GetErrorScopedMismatch !ByteCount !ByteCount
  | GetErrorFail !String
  deriving stock (Eq, Show)

instance Exception GetError where
  displayException = prettyGetError

prettyGetError :: GetError -> String
prettyGetError = \case
  GetErrorParseNeed nm ac bc -> "End of input parsing " ++ nm ++ " (have " ++ show (unByteCount ac) ++ " bytes, need " ++ show (unByteCount bc) ++ ")"
  GetErrorScopedMismatch ac bc -> "Did not parse enough scoped input (read " ++ show (unByteCount ac) ++ " bytes, expected " ++ show (unByteCount bc) ++ ")"
  GetErrorFail msg -> "User error: " ++ msg

data GetEnv s r = GetEnv
  { geOff :: !(STRef s ByteCount)
  -- ^ Offset from buffer start (in bytes)
  , geCap :: !ByteCount
  -- ^ Capacity of buffer segment
  , geArray :: !r
  -- ^ Source buffer
  }

newGetEnv :: ByteCount -> ByteCount -> r -> ST s (GetEnv s r)
newGetEnv off cap mem = do
  offRef <- newSTRef off
  pure (GetEnv offRef cap mem)

newtype GetEff s r a = GetEff {unGetEff :: ReaderT (GetEnv s r) (ExceptT GetError (ST s)) a}
  deriving newtype (Functor, Applicative, Monad, MonadReader (GetEnv s r), MonadError GetError)

runGetEff :: GetEff s r a -> GetEnv s r -> ST s (Either GetError a)
runGetEff act env = runExceptT (runReaderT (unGetEff act) env)

instance MonadFail (GetEff s r) where
  fail = GetEff . throwError . GetErrorFail

stGetEff :: ST s a -> GetEff s r a
stGetEff = GetEff . lift . lift

newtype GetRun s r a = GetRun {unGetRun :: FreeT GetF (GetEff s r) a}
  deriving newtype (Functor, Applicative, Monad)

guardReadBytes :: String -> ByteCount -> GetEff s r ByteCount
guardReadBytes nm bc = do
  GetEnv offRef cap _ <- ask
  off <- stGetEff (readSTRef offRef)
  let ac = cap - off
  if bc > ac
    then throwError (GetErrorParseNeed nm ac bc)
    else pure off

readBytes :: String -> ByteCount -> (r -> ByteCount -> a) -> GetEff s r a
readBytes nm bc f = do
  off <- guardReadBytes nm bc
  GetEnv offRef _ mem <- ask
  stGetEff $ do
    let a = f mem off
        newOff = off + bc
    writeSTRef offRef newOff
    pure a

readScope :: ReadMem r => GetScopeF (GetEff s r a) -> GetEff s r a
readScope (GetScopeF sm bc g k) = do
  GetEnv offRef oldCap _ <- ask
  oldOff <- stGetEff (readSTRef offRef)
  let oldAvail = oldCap - oldOff
  if bc > oldAvail
    then throwError (GetErrorParseNeed "scope" oldAvail bc)
    else do
      let newCap = oldOff + bc
      a <- local (\ge -> ge {geCap = newCap}) (mkGetEff g)
      case sm of
        ScopeModeWithin -> k a
        ScopeModeExact -> do
          newOff <- stGetEff (readSTRef offRef)
          let actualBc = newOff - oldOff
          if actualBc == bc
            then k a
            else throwError (GetErrorScopedMismatch actualBc bc)

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

readStaticArray :: ReadMem r => GetStaticArrayF (GetEff s r a) -> GetEff s r a
readStaticArray gsa@(GetStaticArrayF _ _ k) = do
  let bc = getStaticArraySize gsa
  sa <- readBytes "static vector" bc (\mem off -> cloneArrayMemInBytes mem off bc)
  k (LiftedPrimArray sa)

readLookAhead :: ReadMem r => GetLookAheadF (GetEff s r a) -> GetEff s r a
readLookAhead (GetLookAheadF g k) = do
  offRef <- asks geOff
  startOff <- stGetEff (readSTRef offRef)
  a <- mkGetEff g
  stGetEff (writeSTRef offRef startOff)
  k a

execGetRun :: ReadMem r => GetF (GetEff s r a) -> GetEff s r a
execGetRun = \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 (\_ _ -> ()) *> k
  GetFLookAhead gla -> readLookAhead gla
  GetFRemainingSize k -> do
    GetEnv offRef cap _ <- ask
    off <- stGetEff (readSTRef offRef)
    k (cap - off)
  GetFFail msg -> fail msg

runGetRun :: ReadMem r => GetRun s r a -> GetEnv s r -> ST s (Either GetError a)
runGetRun = runGetEff . iterGetRun

iterGetRun :: ReadMem r => GetRun s r a -> GetEff s r a
iterGetRun act = iterT execGetRun (unGetRun act)

mkGetRun :: Get a -> GetRun s r a
mkGetRun (Get (F w)) = GetRun (w pure wrap)

mkGetEff :: ReadMem r => Get a -> GetEff s r a
mkGetEff = iterGetRun . mkGetRun

runGetInternal :: ReadMem r => ByteCount -> Get a -> ByteCount -> r -> (Either GetError a, ByteCount)
runGetInternal off act cap mem = runST $ do
  let eff = mkGetEff act
  env <- newGetEnv off cap mem
  ea <- runGetEff eff env
  bc <- readSTRef (geOff env)
  pure (ea, bc)

-- Put unsafe:

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

newPutEnv :: ByteCount -> q s -> ST s (PutEnv s q)
newPutEnv cap mem = do
  offRef <- newSTRef 0
  pure (PutEnv offRef cap mem)

newtype PutEff s q a = PutEff {unPutEff :: ReaderT (PutEnv s q) (ST s) a}
  deriving newtype (Functor, Applicative, Monad, MonadReader (PutEnv s q))

runPutEff :: PutEff s q a -> PutEnv s q -> ST s a
runPutEff act = runReaderT (unPutEff act)

stPutEff :: ST s a -> PutEff s q a
stPutEff = PutEff . lift

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

writeBytes :: ByteCount -> (q s -> ByteCount -> ST s ()) -> PutEff s q ()
writeBytes bc f = do
  PutEnv offRef _ mem <- ask
  stPutEff $ do
    off <- readSTRef offRef
    f mem off
    let newOff = off + bc
    writeSTRef offRef newOff

writeStaticSeq :: WriteMem q => PutStaticSeqF (PutEff s q a) -> PutEff s q 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 => PutStaticArrayF (PutEff s q a) -> PutEff s q a
writeStaticArray psa@(PutStaticArrayF needElems mz a@(LiftedPrimArray ba) k) = do
  let elemSize = putStaticArrayElemSize psa
      haveElems = sizeofLiftedPrimArray a
      useElems = min haveElems (coerce needElems)
      useBc = elemSize * coerce useElems
  writeBytes useBc (copyArrayMemInBytes 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 => PutF (PutEff s q a) -> PutEff s q 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 => PutRun s q a -> PutEnv s q -> ST s a
runPutRun = runPutEff . iterPutRun

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

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

mkPutEff :: WriteMem q => PutM a -> PutEff s q a
mkPutEff = iterPutRun . mkPutRun

runPutUnsafe :: WriteMem q => Put -> ByteCount -> q s -> ST s ByteCount
runPutUnsafe act len mem = do
  let eff = mkPutRun act
  st@(PutEnv offRef _ _) <- newPutEnv len mem
  runPutRun eff st
  readSTRef 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 :: FreeT 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

-- Put safe:

runPutInternal :: WriteMem q => Put -> ByteCount -> (forall s. ByteCount -> ST s (q s)) -> (forall s. q s -> ByteCount -> ByteCount -> ST s z) -> z
runPutInternal act cap mkMem useMem = runST $ do
  mem <- mkMem cap
  case releaseMem mem of
    Nothing -> runPutUnsafe act cap mem >>= useMem mem cap
    Just rel -> unsafeIOToST (onException (unsafeSTToIO (runPutUnsafe act cap mem >>= useMem mem cap)) rel)