{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE EmptyCase #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module Data.Persist.Internal (
(:!:)(..)
, Get(..)
, GetEnv(..)
, GetException(..)
, getOffset
, failGet
, runGet
, runGetIO
, Put(..)
, PutEnv(..)
, Chunk(..)
, evalPut
, evalPutIO
, grow
) where
import Control.Exception
import Control.Monad
import Data.ByteString (ByteString)
import Data.Foldable (foldlM)
import Data.IORef
import Data.List.NonEmpty (NonEmpty(..))
import Data.Word
import Foreign (ForeignPtr, Ptr, plusPtr, minusPtr,
withForeignPtr, mallocBytes, free, allocaBytes)
import System.IO.Unsafe
import qualified Control.Monad.Fail as Fail
import qualified Data.ByteString.Internal as B
#include "MachDeps.h"
data a :!: b = !a :!: !b
infixl 2 :!:
data GetEnv = GetEnv
{ geBuf :: !(ForeignPtr Word8)
, geBegin :: !(Ptr Word8)
, geEnd :: !(Ptr Word8)
, geTmp :: !(Ptr Word8)
}
newtype Get a = Get
{ unGet :: GetEnv -> Ptr Word8 -> IO ((Ptr Word8) :!: a)
}
instance Functor Get where
fmap f m = Get $ \e p -> do
p' :!: x <- unGet m e p
pure $! p' :!: f x
{-# INLINE fmap #-}
instance Applicative Get where
pure a = Get $ \_ p -> pure $! p :!: a
{-# INLINE pure #-}
f <*> a = Get $ \e p -> do
p' :!: f' <- unGet f e p
p'' :!: a' <- unGet a e p'
pure $! p'' :!: f' a'
{-# INLINE (<*>) #-}
m1 *> m2 = do
void m1
m2
{-# INLINE (*>) #-}
instance Monad Get where
m >>= f = Get $ \e p -> do
p' :!: x <- unGet m e p
unGet (f x) e p'
{-# INLINE (>>=) #-}
#if !MIN_VERSION_base(4,11,0)
fail = Fail.fail
{-# INLINE fail #-}
#endif
data GetException
= LengthException Int String
| CharException Int String
| EOFException Int String
| GenericGetException Int String
deriving (Eq, Show)
instance Exception GetException
instance Fail.MonadFail Get where
fail msg = failGet GenericGetException ("Failed reading: " <> msg)
{-# INLINE fail #-}
getOffset :: Get Int
getOffset = Get $ \e p -> pure $! p :!: (p `minusPtr` (geBegin e))
{-# INLINE getOffset #-}
failGet :: (Int -> String -> GetException) -> String -> Get a
failGet ctor msg = do
offset <- getOffset
Get $ \_ _ -> throwIO (ctor offset msg)
runGetIO :: Get a -> ByteString -> IO a
runGetIO m s = run
where run = withForeignPtr buf $ \p -> allocaBytes 8 $ \t -> do
let env = GetEnv { geBuf = buf, geBegin = p, geEnd = p `plusPtr` (pos + len), geTmp = t }
_ :!: r <- unGet m env (p `plusPtr` pos)
pure r
(B.PS buf pos len) = s
runGet :: Get a -> ByteString -> Either String a
runGet m s = unsafePerformIO $ catch (Right <$!> (runGetIO m s)) handler
where handler (e :: GetException) = pure $ Left $ displayException e
{-# NOINLINE runGet #-}
data Chunk = Chunk
{ chkBegin :: !(Ptr Word8)
, chkEnd :: !(Ptr Word8)
}
data PutEnv = PutEnv
{ peChks :: !(IORef (NonEmpty Chunk))
, peEnd :: !(IORef (Ptr Word8))
, peTmp :: !(Ptr Word8)
}
newtype Put a = Put
{ unPut :: PutEnv -> Ptr Word8 -> IO ((Ptr Word8) :!: a) }
instance Functor Put where
fmap f m = Put $ \e p -> do
p' :!: x <- unPut m e p
pure $! p' :!: f x
{-# INLINE fmap #-}
instance Applicative Put where
pure a = Put $ \_ p -> pure $! p :!: a
{-# INLINE pure #-}
f <*> a = Put $ \e p -> do
p' :!: f' <- unPut f e p
p'' :!: a' <- unPut a e p'
pure $! p'' :!: f' a'
{-# INLINE (<*>) #-}
m1 *> m2 = do
void m1
m2
{-# INLINE (*>) #-}
instance Monad Put where
m >>= f = Put $ \e p -> do
p' :!: x <- unPut m e p
unPut (f x) e p'
{-# INLINE (>>=) #-}
minChunkSize :: Int
minChunkSize = 0x10000
{-# INLINE minChunkSize #-}
newChunk :: Int -> IO Chunk
newChunk size = do
let n = max size minChunkSize
p <- mallocBytes n
pure $! Chunk p $ p `plusPtr` n
{-# INLINE newChunk #-}
grow :: Int -> Put ()
grow n
| n < 0 = error "grow: negative length"
| otherwise = Put $ \e p -> do
end <- readIORef (peEnd e)
if end `minusPtr` p >= n then
pure $! p :!: ()
else
doGrow e p n
{-# INLINE grow #-}
doGrow :: PutEnv -> Ptr Word8 -> Int -> IO ((Ptr Word8) :!: ())
doGrow e p n = do
k <- newChunk n
modifyIORef' (peChks e) $ \case
(c:|cs) -> k :| c { chkEnd = p } : cs
writeIORef (peEnd e) (chkEnd k)
pure $! chkBegin k :!: ()
{-# NOINLINE doGrow #-}
chunksLength :: [Chunk] -> Int
chunksLength = foldr (\c s -> s + chkEnd c `minusPtr` chkBegin c) 0
{-# INLINE chunksLength #-}
catChunks :: [Chunk] -> IO ByteString
catChunks chks = B.create (chunksLength chks) $ \p ->
void $ foldlM (\q c -> do
let n = chkEnd c `minusPtr` chkBegin c
B.memcpy q (chkBegin c) n
free $ chkBegin c
pure (q `plusPtr` n)) p $ reverse chks
{-# INLINE catChunks #-}
evalPutIO :: Put a -> IO (a, ByteString)
evalPutIO p = do
k <- newChunk 0
chks <- newIORef (k:|[])
end <- newIORef (chkEnd k)
p' :!: r <- allocaBytes 8 $ \t ->
unPut p PutEnv { peChks = chks, peEnd = end, peTmp = t } (chkBegin k)
cs <- readIORef chks
s <- case cs of
(x:|xs) -> catChunks $ x { chkEnd = p' } : xs
pure (r, s)
evalPut :: Put a -> (a, ByteString)
evalPut p = unsafePerformIO $ evalPutIO p
{-# NOINLINE evalPut #-}