packstream-0.1.0.1: src/Data/PackStream/Internal/Binary.hs
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
module Data.PackStream.Internal.Binary where
import Data.PackStream.Internal.Type (PackStreamError(..))
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS (length)
import Data.ByteString.Lazy (fromStrict, toStrict)
import Data.Binary (Binary, encode, decode)
import Data.Binary.IEEE754 (wordToDouble, doubleToWord)
import Data.Text (Text)
import Data.Int (Int8, Int16, Int32, Int64)
import Control.Applicative (liftA2)
import Data.Word (Word8, Word16, Word32, Word64)
import Control.Monad.Except (throwError, MonadError)
import Data.Text.Encoding (decodeUtf8, encodeUtf8)
-- |The data types that can be intepreted from 'ByteString'
class Interpret a where
-- |Interpret a 'ByteString' as a specific type of raise 'PackStreamError'
interpret :: MonadError PackStreamError m => ByteString -> m a
default interpret :: (MonadError PackStreamError m, Binary a) => ByteString -> m a
interpret = pure . decode . fromStrict
instance Interpret Int8
instance Interpret Int16
instance Interpret Int32
instance Interpret Int64
instance Interpret Word8
instance Interpret Word16
instance Interpret Word32
instance Interpret Word64
instance Interpret Word where
interpret bs | BS.length bs == 1 = fromIntegral @Word8 <$> interpret bs
| BS.length bs == 2 = fromIntegral @Word16 <$> interpret bs
| BS.length bs == 4 = fromIntegral @Word32 <$> interpret bs
| BS.length bs == 8 = fromIntegral @Word64 <$> interpret bs
| otherwise = throwError NotWord
instance Interpret Int where
interpret bs | BS.length bs == 1 = fromIntegral @Int8 <$> interpret bs
| BS.length bs == 2 = fromIntegral @Int16 <$> interpret bs
| BS.length bs == 4 = fromIntegral @Int32 <$> interpret bs
| BS.length bs == 8 = fromIntegral @Int64 <$> interpret bs
| otherwise = throwError NotInt
instance Interpret Integer where
interpret = fmap fromIntegral . interpret @Int
instance Interpret Double where
interpret = fmap wordToDouble . interpret
instance Interpret ByteString where
interpret = pure
instance Interpret Text where
interpret = pure . decodeUtf8
-- |The data types that can be serialized into 'ByteString'
class Serialize a where
-- |Serialize a specific data type into a 'ByteString'
serialize :: a -> ByteString
default serialize :: Binary a => a -> ByteString
serialize = toStrict . encode
instance Serialize Int8
instance Serialize Int16
instance Serialize Int32
instance Serialize Int64
instance Serialize Word8
instance Serialize Word16
instance Serialize Word32
instance Serialize Word64
instance Serialize Word where
serialize i | i <= 0xFF = serialize @Word8 $ fromIntegral i
| i <= 0xFFFF = serialize @Word16 $ fromIntegral i
| i <= 0xFFFFFFFF = serialize @Word32 $ fromIntegral i
| i <= 0xFFFFFFFFFFFFFFFF = serialize @Word64 $ fromIntegral i
| otherwise = error "Not a 64-bit unsigned integer"
instance Serialize Int where
serialize i | inDepth 8 i = serialize @Int8 $ fromIntegral i
| inDepth 16 i = serialize @Int16 $ fromIntegral i
| inDepth 32 i = serialize @Int32 $ fromIntegral i
| inDepth 64 i = serialize @Int64 $ fromIntegral i
| otherwise = error "Not a 64-bit integer"
instance Serialize Integer where
serialize i | inDepth 64 i = serialize @Int $ fromIntegral i
| otherwise = error "Not a 64-bit integer"
instance Serialize Double where
serialize = serialize . doubleToWord
instance Serialize ByteString where
serialize = id
instance Serialize Text where
serialize = encodeUtf8
-- |Check that the 'Integral' value is in the n-bit bounds
inDepth :: Integral a => Int -> a -> Bool
inDepth bitDepth = let bound = 2 ^ (bitDepth - 1) :: Integer
in liftA2 (&&) (>= -bound) (< bound) . fromIntegral