packages feed

primitive-serial-0.1: src/Data/PrimitiveSerial.hs

module Data.PrimitiveSerial
    ( BSRead(..)
    , runWholeBSRead
    , bsRead
    , bsReadN
    , bsReadEverything
    , bsReadCount
    , decodeNative
    , encodeNative
    , FixedNumeric(..)
    , decodeLittleEndian
    , encodeLittleEndian
    , decodeBigEndian
    , encodeBigEndian
    ) where

import Control.Applicative
import Control.Monad
import Data.ByteString
import Data.ByteString.Internal
import Data.Int
import Data.Word
import Foreign
import Prelude hiding (drop, length, null, take)
import System.Endian
import System.IO.Unsafe

newtype BSRead a = MkBSRead
    { runBSRead :: StrictByteString -> Maybe (StrictByteString, a)
    }

instance Functor BSRead where
    fmap ab (MkBSRead f) = MkBSRead $ \bs -> fmap (fmap ab) $ f bs

instance Applicative BSRead where
    pure a = MkBSRead $ \bs -> Just (bs, a)
    MkBSRead fab <*> MkBSRead fa =
        MkBSRead $ \bs -> do
            (bs', ab) <- fab bs
            (bs'', a) <- fa bs'
            return (bs'', ab a)

instance Alternative BSRead where
    MkBSRead fa <|> MkBSRead fb = MkBSRead $ \bs -> fa bs <|> fb bs
    empty = MkBSRead $ \_ -> Nothing

instance Monad BSRead where
    MkBSRead f >>= q =
        MkBSRead $ \bs -> do
            (bs', a) <- f bs
            runBSRead (q a) bs'

instance MonadPlus BSRead

runWholeBSRead :: BSRead a -> StrictByteString -> Maybe a
runWholeBSRead ra bs = do
    (bs', a) <- runBSRead ra bs
    if null bs'
        then return a
        else Nothing

bsRead :: BSRead Word8
bsRead =
    MkBSRead $ \bs -> do
        w <- indexMaybe bs 0
        return (drop 1 bs, w)

bsReadN :: Int -> BSRead StrictByteString
bsReadN n =
    MkBSRead $ \bs -> do
        if n > length bs
            then Nothing
            else return (drop n bs, take n bs)

bsReadEverything :: BSRead StrictByteString
bsReadEverything = MkBSRead $ \bs -> Just (mempty, bs)

bsReadCount :: BSRead Int
bsReadCount = MkBSRead $ \bs -> Just (bs, length bs)

decodeNative ::
       forall a. Storable a
    => BSRead a
decodeNative = let
    typeSize :: Int
    typeSize = sizeOf (undefined :: a)
    in MkBSRead $ \bs@(BS fptr len) ->
           if typeSize <= len
               then Just $ (drop typeSize bs, unsafePerformIO $ withForeignPtr fptr $ \ptr -> peek $ castPtr ptr)
               else Nothing

encodeNative ::
       forall a. Storable a
    => a
    -> StrictByteString
encodeNative a = let
    typeSize :: Int
    typeSize = sizeOf (undefined :: a)
    in unsafePerformIO $ create typeSize $ \ptr -> poke (castPtr ptr) a

unsafeStorableCoerce ::
       forall a b. (Storable a, Storable b)
    => a
    -> b
unsafeStorableCoerce a = unsafePerformIO $ with a $ \ptr -> peek $ castPtr ptr

class Storable a => FixedNumeric a where
    nativeToLittleEndian :: a -> a
    nativeToBigEndian :: a -> a

instance FixedNumeric Word8 where
    nativeToLittleEndian = id
    nativeToBigEndian = id

instance FixedNumeric Word16 where
    nativeToLittleEndian = fromLE16
    nativeToBigEndian = fromBE16

instance FixedNumeric Word32 where
    nativeToLittleEndian = fromLE32
    nativeToBigEndian = fromBE32

instance FixedNumeric Word64 where
    nativeToLittleEndian = fromLE64
    nativeToBigEndian = fromBE64

instance FixedNumeric Int8 where
    nativeToLittleEndian = id
    nativeToBigEndian = id

instance FixedNumeric Int16 where
    nativeToLittleEndian = unsafeStorableCoerce . nativeToLittleEndian @Word16 . unsafeStorableCoerce
    nativeToBigEndian = unsafeStorableCoerce . nativeToBigEndian @Word16 . unsafeStorableCoerce

instance FixedNumeric Int32 where
    nativeToLittleEndian = unsafeStorableCoerce . nativeToLittleEndian @Word32 . unsafeStorableCoerce
    nativeToBigEndian = unsafeStorableCoerce . nativeToBigEndian @Word32 . unsafeStorableCoerce

instance FixedNumeric Int64 where
    nativeToLittleEndian = unsafeStorableCoerce . nativeToLittleEndian @Word64 . unsafeStorableCoerce
    nativeToBigEndian = unsafeStorableCoerce . nativeToBigEndian @Word64 . unsafeStorableCoerce

instance FixedNumeric Float where
    nativeToLittleEndian = unsafeStorableCoerce . nativeToLittleEndian @Word32 . unsafeStorableCoerce
    nativeToBigEndian = unsafeStorableCoerce . nativeToBigEndian @Word32 . unsafeStorableCoerce

instance FixedNumeric Double where
    nativeToLittleEndian = unsafeStorableCoerce . nativeToLittleEndian @Word64 . unsafeStorableCoerce
    nativeToBigEndian = unsafeStorableCoerce . nativeToBigEndian @Word64 . unsafeStorableCoerce

decodeLittleEndian ::
       forall a. FixedNumeric a
    => BSRead a
decodeLittleEndian = fmap nativeToLittleEndian $ decodeNative

encodeLittleEndian ::
       forall a. FixedNumeric a
    => a
    -> StrictByteString
encodeLittleEndian = encodeNative . nativeToLittleEndian

decodeBigEndian ::
       forall a. FixedNumeric a
    => BSRead a
decodeBigEndian = fmap nativeToBigEndian $ decodeNative

encodeBigEndian ::
       forall a. FixedNumeric a
    => a
    -> StrictByteString
encodeBigEndian = encodeNative . nativeToBigEndian