binary-generic 0.1 → 0.2
raw patch · 3 files changed
+325/−98 lines, 3 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Data.Binary.Generic: getAlgebraic :: Data a => Get a
+ Data.Binary.Generic: getGenericByCallback :: Data a => (forall d. Data d => Get d) -> Get a
+ Data.Binary.Generic: putAlgebraic :: Data a => a -> Put
+ Data.Binary.Generic: putGenericByCallback :: Data a => (forall d. Data d => d -> Put) -> a -> Put
+ Data.Binary.Generic.Extensions: extGet :: (Monad m, Typeable a, Typeable b) => m b -> m a -> m a
+ Data.Binary.Generic.Extensions: extPut :: (Typeable a, Typeable b) => (b -> q) -> (a -> q) -> a -> q
+ Data.Binary.Generic.Extensions: getExtByteString :: Typeable a => Get a -> Get a
+ Data.Binary.Generic.Extensions: getExtChar :: Typeable a => Get a -> Get a
+ Data.Binary.Generic.Extensions: getExtDefault :: Typeable a => Get a -> Get a
+ Data.Binary.Generic.Extensions: getExtFloat :: Typeable a => Get a -> Get a
+ Data.Binary.Generic.Extensions: getExtInt :: Typeable a => Get a -> Get a
+ Data.Binary.Generic.Extensions: getExtInteger :: Typeable a => Get a -> Get a
+ Data.Binary.Generic.Extensions: getExtText :: Typeable a => Get a -> Get a
+ Data.Binary.Generic.Extensions: getExtWord :: Typeable a => Get a -> Get a
+ Data.Binary.Generic.Extensions: putExtByteString :: Typeable a => (a -> Put) -> a -> Put
+ Data.Binary.Generic.Extensions: putExtChar :: Typeable a => (a -> Put) -> a -> Put
+ Data.Binary.Generic.Extensions: putExtDefault :: Typeable a => (a -> Put) -> a -> Put
+ Data.Binary.Generic.Extensions: putExtFloat :: Typeable a => (a -> Put) -> a -> Put
+ Data.Binary.Generic.Extensions: putExtInt :: Typeable a => (a -> Put) -> a -> Put
+ Data.Binary.Generic.Extensions: putExtInteger :: Typeable a => (a -> Put) -> a -> Put
+ Data.Binary.Generic.Extensions: putExtText :: Typeable a => (a -> Put) -> a -> Put
+ Data.Binary.Generic.Extensions: putExtWord :: Typeable a => (a -> Put) -> a -> Put
- Data.Binary.Generic: getGeneric :: (Data a) => Get a
+ Data.Binary.Generic: getGeneric :: Data a => Get a
- Data.Binary.Generic: putGeneric :: (Data a) => a -> Put
+ Data.Binary.Generic: putGeneric :: Data a => a -> Put
Files
- binary-generic.cabal +7/−4
- src/Data/Binary/Generic.hs +69/−94
- src/Data/Binary/Generic/Extensions.hs +249/−0
binary-generic.cabal view
@@ -1,5 +1,5 @@ name: binary-generic-version: 0.1+version: 0.2 license: BSD3 license-file: LICENSE author: Lars Petersen <info@lars-petersen.net>@@ -7,8 +7,10 @@ homepage: http://github.com/lpeterse/binary-generic description: Instead of manual or semi-automated generation of instances of 'Data.Binary.Binary' you just derive - 'Data.Data' and the library automatically figures- out how to (de-)serialize the type.+ 'Data.Data.Data' and the library automatically+ figures out how to (de-)serialize the type.+ You may also define your own type-specific + stack of serialisation functions. synopsis: Generic binary serialisation using binary and syb. category: Data, Parsing stability: experimental@@ -25,6 +27,7 @@ syb, text hs-source-dirs: src- exposed-modules: Data.Binary.Generic+ exposed-modules: Data.Binary.Generic,+ Data.Binary.Generic.Extensions ghc-options: -Wall
src/Data/Binary/Generic.hs view
@@ -1,4 +1,4 @@-{-# OPTIONS -XNoMonomorphismRestriction #-}+{-# OPTIONS -XNoMonomorphismRestriction -XRankNTypes #-} ----------------------------------------------------------------------------- -- |@@ -9,115 +9,90 @@ -- Maintainer : Lars Petersen <info@lars-petersen.net> -- Stability : experimental ----- The following primitive datatypes are used as basecases and get serialized--- according to their instances of @Data.Binary@: -- --- - 'Char'--- --- - 'Int'--- --- - 'Word'--- --- - 'Integer'--- --- - 'Int8'--- --- - 'Int16'--- --- - 'Int32'--- --- - 'Int64'--- --- - 'Word8'--- --- - 'Word16'--- --- - 'Word32'--- --- - 'Word64'+-- For any algebraic datatype just make it an instance of class @Data.Data.Data@+-- by simply deriving 'Data' on definition or try stand-alone-deriving. This+-- allows the library to enumerate the value constructors and thereby+-- encoding their index. Notice that serialisation depends on a type's+-- structure. Serialisations might get unreadable if the type is altered. -- --- - 'Data.ByteString.Lazy.ByteString'+-- 'getGeneric' and 'putGeneric' implement a selection of type-specific +-- defaults and are grounded by a canonical serialisation for all algebraic+-- types that instantiate 'Data.Data.Data'.+-- Have a look at @Data.Binary.Generic.Extensions@ for details. -- --- - 'Data.Text.Lazy.Text' encoded as Utf8+-- If you want to ground your own type-specific stack @myStack@ of extensions+-- write the following for the @Get@-part (the @Put@-part follows+-- analogously): -- --- 'Float' and 'Double' are serialized according to 'IEEE754'.--- For any algebraic datatype just make it an instance of class @Data.Data@--- by simply deriving 'Data' on definition or try stand-alone-deriving.+-- > getMyStack :: Data a => Get a+-- > getMyStack = myStack (getGenericByCallback getMyStack) -- ----------------------------------------------------------------------------- module Data.Binary.Generic (- - getGeneric++ getAlgebraic+ , putAlgebraic++ , getGeneric , putGeneric + , getGenericByCallback+ , putGenericByCallback+ ) where +import Data.Data import Data.Binary-import Data.Binary.IEEE754 (putFloat32be, getFloat32be, putFloat64be, getFloat64be)-import Data.ByteString.Lazy (ByteString) +import Data.Binary.Put (putWord16be)+import Data.Binary.Get (getWord16be)+import Data.Binary.Generic.Extensions -import Data.Data-import Data.Generics -import Data.Word ()-import Data.Int -import Data.Text.Lazy (Text) -import Data.Text.Lazy.Encoding (encodeUtf8, decodeUtf8)+getAlgebraic :: Data a => Get a+getAlgebraic = getGenericByCallback getAlgebraic +putAlgebraic :: Data a => a -> Put+putAlgebraic = putGenericByCallback putAlgebraic -getGeneric :: (Data a) => Get a-getGeneric = generalCase - `extR` (get :: Get Char)- `extR` (get :: Get Int)- `extR` (get :: Get Word)- `extR` (get :: Get Integer)- `extR` (get :: Get Int8)- `extR` (get :: Get Int16)- `extR` (get :: Get Int32)- `extR` (get :: Get Int64)- `extR` (get :: Get Word8)- `extR` (get :: Get Word16)- `extR` (get :: Get Word32)- `extR` (get :: Get Word64)- `extR` (get :: Get ByteString)- `extR` (getText :: Get Text)- `extR` (getFloat32be :: Get Float)- `extR` (getFloat64be :: Get Double)- where- getText = get >>= (return . decodeUtf8)- fromIntegralM = return . fromIntegral- myDataType = dataTypeOf ((undefined :: Get b -> b) generalCase)- generalCase = let imax = maxConstrIndex myDataType- index | imax == 1 = return 1 :: Get Int- | imax <= 255 = (get :: Get Word8) >>= fromIntegralM- | otherwise = (get :: Get Word16) >>= fromIntegralM- in index >>= \i-> fromConstrM getGeneric (indexConstr myDataType i) -putGeneric :: (Data a) => a -> Put -putGeneric = generalCase - `extQ` (put :: Char -> Put)- `extQ` (put :: Int -> Put) - `extQ` (put :: Word -> Put) - `extQ` (put :: Integer -> Put)- `extQ` (put :: Int8 -> Put)- `extQ` (put :: Int16 -> Put)- `extQ` (put :: Int32 -> Put)- `extQ` (put :: Int64 -> Put)- `extQ` (put :: Word8 -> Put)- `extQ` (put :: Word16 -> Put)- `extQ` (put :: Word32 -> Put)- `extQ` (put :: Word64 -> Put)- `extQ` (put :: ByteString -> Put)- `extQ` (put . encodeUtf8 :: Text -> Put)- `extQ` (putFloat32be :: Float -> Put)- `extQ` (putFloat64be :: Double -> Put)- where- generalCase t = let i = fromIntegral $ constrIndex (toConstr t)- imax = maxConstrIndex (dataTypeOf t) - putIndex | imax == 1 = return ()- | imax <= 255 = put (i :: Word8) >> return ()- | otherwise = put (i :: Word16) >> return ()- in foldl (>>) putIndex (gmapQ putGeneric t) - +getGeneric :: Data a => Get a+getGeneric = getExtDefault (getGenericByCallback getGeneric)++putGeneric :: Data a => a -> Put+putGeneric = putExtDefault (putGenericByCallback putGeneric)+++--------------------------------------------------------------+-- algebraic basecases with callbacks+--------------------------------------------------------------++getGenericByCallback :: Data a => (forall d. Data d => Get d) -> Get a+getGenericByCallback c = generalCase + where+ myDataType = dataTypeOf ((undefined :: Get b -> b) generalCase)+ typeName = showsTypeRep (typeOf $ (undefined :: Get b -> b) generalCase) ""+ generalCase = let imax = maxConstrIndex myDataType+ index | imax == 0 = error "getGeneric: constructor count is 0."+ | imax == 1 = return 0 :: Get Int+ | imax <= 256 = getWord8 >>= (return . fromIntegral)+ | imax <= 65536 = getWord16be >>= (return . fromIntegral)+ | otherwise = error "getGeneric: constructor count out of range."+ in if isAlgType myDataType+ then index >>= \i-> fromConstrM c (indexConstr myDataType (i+1))+ else error $ "getGeneric: `" ++ typeName ++ "' is not algebraic."++putGenericByCallback :: Data a => (forall d. Data d => d -> Put) -> a -> Put +putGenericByCallback c t = let i = fromIntegral $ constrIndex (toConstr t) - 1 + imax = maxConstrIndex (dataTypeOf t) + typeName = showsTypeRep (typeOf t) ""+ putIndex | imax == 0 = error "putGeneric: constructor count is 0."+ | imax == 1 = return ()+ | imax <= 256 = putWord8 i + | imax <= 65536 = putWord16be i + | otherwise = error "putGeneric: constructor count out of range."+ in if isAlgType (dataTypeOf t)+ then foldl (>>) putIndex (gmapQ c t) + else error $ "putGeneric: `" ++ typeName ++ "' is not algebraic."
+ src/Data/Binary/Generic/Extensions.hs view
@@ -0,0 +1,249 @@+{-# OPTIONS -XNoMonomorphismRestriction #-}++-----------------------------------------------------------------------------+-- |+-- Module : Data.Binary.Generic.Extensions+-- Copyright : Lars Petersen+-- License : BSD3-style (see LICENSE)+-- +-- Maintainer : Lars Petersen <info@lars-petersen.net>+-- Stability : experimental+--+-- You can build your own type-specific stacks. For example the default stack+-- looks like this, whereas the ordering determines, which function matches+-- first for a specific type. This especially allows you to override the+-- default choices:+--+-- > getExtDefault :: Typeable a => Get a -> Get a+-- > getExtDefault = getExtInteger+-- > . getExtChar+-- > . getExtWord+-- > . getExtInt+-- > . getExtFloat+-- > . getExtText+-- > . getExtByteString+-- >+-- > putExtDefault :: Typeable a => (a -> Put) -> a -> Put+-- > putExtDefault = putExtInteger+-- > . putExtChar+-- > . putExtWord+-- > . putExtInt+-- > . putExtFloat+-- > . putExtText+-- > . putExtByteString+-- +-- Notice that these stacks have to be grounded, ideally with something+-- that handles algebraic types.+-- Have a look at @Data.Binary.Generic@ how this is done for the default +-- stack.+-- +-- IMPORTANT: You cannot simply apply an extension to 'getGeneric' or+-- 'putGeneric', since these do a recursive call at the bottom level+-- which points to the top of the stack.+-- +--+-----------------------------------------------------------------------------++module Data.Binary.Generic.Extensions (+ + extGet+ , extPut++ , getExtDefault+ , putExtDefault++ , getExtInteger+ , putExtInteger+ , getExtChar+ , putExtChar+ , getExtWord+ , putExtWord+ , getExtInt+ , putExtInt+ , getExtFloat+ , putExtFloat+ , getExtText+ , putExtText+ , getExtByteString+ , putExtByteString++ ) where++import Data.Binary+import Data.Binary.IEEE754 (putFloat32be, getFloat32be, putFloat64be, getFloat64be)++import Data.Data+import Data.Generics++import Data.Word ()+import Data.Int +import Data.List+import Data.Bits+import Data.ByteString.Lazy (ByteString) ++import qualified Data.Text as T +import qualified Data.Text.Encoding as TE+import qualified Data.Text.Lazy as LT +import qualified Data.Text.Lazy.Encoding as LTE++import Control.Monad+++extGet :: (Monad m, Typeable a, Typeable b) => m b -> m a -> m a+extGet = flip extR++extPut :: (Typeable a, Typeable b) => (b -> q) -> (a -> q) -> a -> q+extPut = flip extQ+++getExtDefault :: Typeable a => Get a -> Get a+getExtDefault = getExtInteger+ . getExtChar+ . getExtWord+ . getExtInt+ . getExtFloat+ . getExtText+ . getExtByteString++putExtDefault :: Typeable a => (a -> Put) -> a -> Put+putExtDefault = putExtInteger+ . putExtChar+ . putExtWord+ . putExtInt+ . putExtFloat+ . putExtText+ . putExtByteString+++getExtInteger :: Typeable a => Get a -> Get a+getExtInteger = extGet (getInteger :: Get Integer) ++putExtInteger :: Typeable a => (a -> Put) -> a -> Put +putExtInteger = extPut (putInteger :: Integer -> Put)+++getExtChar :: Typeable a => Get a -> Get a+getExtChar = extGet (get :: Get Char) ++putExtChar :: Typeable a => (a -> Put) -> a -> Put +putExtChar = extPut (put :: Char -> Put)+++getExtWord :: Typeable a => Get a -> Get a+getExtWord = extGet (get :: Get Word )+ . extGet (get :: Get Word8 ) + . extGet (get :: Get Word16) + . extGet (get :: Get Word32) + . extGet (get :: Get Word64) ++putExtWord :: Typeable a => (a -> Put) -> a -> Put +putExtWord = extPut (put :: Word -> Put) + . extPut (put :: Word8 -> Put) + . extPut (put :: Word16 -> Put) + . extPut (put :: Word32 -> Put) + . extPut (put :: Word64 -> Put) + ++getExtInt :: Typeable a => Get a -> Get a+getExtInt = extGet (get :: Get Int )+ . extGet (get :: Get Int8 ) + . extGet (get :: Get Int16) + . extGet (get :: Get Int32) + . extGet (get :: Get Int64) ++putExtInt :: Typeable a => (a -> Put) -> a -> Put +putExtInt = extPut (put :: Int -> Put) + . extPut (put :: Int8 -> Put) + . extPut (put :: Int16 -> Put) + . extPut (put :: Int32 -> Put) + . extPut (put :: Int64 -> Put) +++getExtFloat :: Typeable a => Get a -> Get a+getExtFloat = extGet (getFloat32be :: Get Float)+ . extGet (getFloat64be :: Get Double)++putExtFloat :: Typeable a => (a -> Put) -> a -> Put +putExtFloat = extPut (putFloat32be :: Float -> Put)+ . extPut (putFloat64be :: Double -> Put)+++getExtText :: Typeable a => Get a -> Get a+getExtText = extGet (getText :: Get T.Text) + . extGet (getTextL :: Get LT.Text)++putExtText :: Typeable a => (a -> Put) -> a -> Put +putExtText = extPut (putText :: T.Text -> Put)+ . extPut (putTextL :: LT.Text -> Put)+++getExtByteString :: Typeable a => Get a -> Get a+getExtByteString = extGet (get :: Get ByteString) ++putExtByteString :: Typeable a => (a -> Put) -> a -> Put +putExtByteString = extPut (put :: ByteString -> Put)++++-------------------------------------------------------------------+-- serialisation for Data.Text+-------------------------------------------------------------------++getText :: Get T.Text+getText = get >>= (return . TE.decodeUtf8)++getTextL :: Get LT.Text+getTextL = get >>= (return . LTE.decodeUtf8)++putText :: T.Text -> Put+putText = put . TE.encodeUtf8++putTextL :: LT.Text -> Put+putTextL = put . LTE.encodeUtf8+++-------------------------------------------------------------------+-- integer serialisation with consistent big-endian byteorder+-------------------------------------------------------------------+++{-# INLINE putInteger #-}+putInteger :: Integer -> Put +putInteger n | n >= lo && n <= hi = do+ putWord8 0+ put (fromIntegral n :: Int32) -- fast path+ where+ lo = fromIntegral (minBound :: Int32) :: Integer+ hi = fromIntegral (maxBound :: Int32) :: Integer+putInteger n = do+ putWord8 1+ put sign+ put $ reverse (unroll (abs n)) -- unroll the bytes+ where+ sign = fromIntegral (signum n) :: Word8++{-# INLINE getInteger #-}+getInteger :: Get Integer+getInteger = do+ tag <- get :: Get Word8+ case tag of+ 0 -> liftM fromIntegral (get :: Get Int32)+ _ -> do sign <- get+ bytes <- get+ let v = roll (reverse bytes)+ return $! if sign == (1 :: Word8) then v else - v++--+-- Fold and unfold an Integer to and from a list of its bytes+--+unroll :: Integer -> [Word8]+unroll = unfoldr step+ where+ step 0 = Nothing+ step i = Just (fromIntegral i, i `shiftR` 8)++roll :: [Word8] -> Integer+roll = foldr unstep 0+ where+ unstep b a = a `shiftL` 8 .|. fromIntegral b+