beamable-0.1.0.0: src/Data/Beamable/Internal.hs
{-# OPTIONS -Wall #-}
{-# LANGUAGE DeriveGeneric, TypeOperators, FlexibleContexts, DefaultSignatures #-}
{-# LANGUAGE FlexibleInstances, ScopedTypeVariables, BangPatterns #-}
{-# LANGUAGE DoAndIfThenElse #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Data.Beamable.Internal
( Beamable
, beam
, unbeam
, typeSignR
, typeSign
, TypeSign (..)
) where
import Data.Beamable.Int
import Data.Beamable.Integer
import Data.Beamable.Util
import Blaze.ByteString.Builder
import Data.Digest.Murmur64
import Control.Arrow (first)
import Data.Bits ((.|.), (.&.), shift, testBit)
import Data.ByteString (ByteString)
import Data.Char (ord, chr)
import Data.Int (Int8, Int16, Int32, Int64)
import Data.Monoid (mempty, mappend, mconcat)
import Data.Word (Word, Word8, Word16, Word32, Word64)
import Foreign.Storable
import GHC.Generics
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
class Beamable a where
-- | Serialize value into 'Builder'
beam :: a -> Builder
-- | Deserialize next value from 'ByteString', also returns leftovers
unbeam :: ByteString -> (a, ByteString)
-- | Get value's type signature, should work fine on 'undefined' values
-- takes list of strings with datatypes which already been traversed
-- workaround to support recursive datatypes. In most cases you should be
-- passing empty list in there.
typeSignR :: [String] -> a -> Word64
-- by default let's use generic version
default beam :: (Generic a, GBeamable (Rep a)) => a -> Builder
beam v = gbeam (from v) (0,0)
default unbeam :: (Generic a, GBeamable (Rep a)) => ByteString -> (a, ByteString)
unbeam v = first to $ gunbeam v (0,0)
default typeSignR :: (Generic a, GBeamable (Rep a)) => [String] -> a -> Word64
typeSignR prev v = gtypeSign prev (from v)
-- | Get type signature of arbitrary Beamable datatatype encoded as Word64 hash
-- with all constructors and datatypes in it. It's preferable to use 'typeSign'
-- against typeSignR, because implementation of later might change.
typeSign :: Beamable a => a -> Word64
typeSign = typeSignR []
signMur :: Hashable64 a => a -> Word64
signMur !a = asWord64 $ hash64 a
-- | It's possible to beam arbitrary Storable instances (not very size efficient)
beamStorable :: Storable a => a -> Builder-- {{{
beamStorable = fromStorable
unbeamStorable :: Storable a => ByteString -> (a, ByteString)
unbeamStorable bs = let v = peekBS bs in (v, B.drop (sizeOf v) bs)-- }}}
-- | It's possible to beam arbitrary Enum instances
beamEnum :: Enum a => a -> Builder-- {{{
beamEnum = beamInt . fromIntegral . fromEnum
unbeamEnum :: Enum a => ByteString -> (a, ByteString)
unbeamEnum bs = let (i, bs') = unbeamInt bs in (toEnum (fromIntegral i), bs')-- }}}
class GBeamable f where
gbeam :: f a -> (Int, Word) -> Builder
gunbeam :: B.ByteString -> (Int, Word) -> (f a, B.ByteString)
gtypeSign :: [String] -> f a -> Word64
-- this instance used for datatypes with single constructor only
instance (GBeamable a, Datatype d, Constructor c) => GBeamable (M1 D d (M1 C c a)) where
gbeam (M1 (M1 x)) = gbeam x
gunbeam x = first M1 . gunbeam x
gtypeSign prev x | elem (datatypeName x) prev = signMur (datatypeName x, '_')
gtypeSign prev x = signMur (datatypeName x, ':', gtypeSign (datatypeName x : prev) (unM1 x))
-- this instance used for datatypes with multiple constructors and
-- values are prefixed by uniq number for each constructor
instance (GBeamable a, Constructor c) => GBeamable (M1 C c a) where
gbeam (M1 x) t@(_, dirs) = mappend (beamWord $ fromIntegral dirs) (gbeam x t)
gunbeam bs = first M1 . gunbeam bs
gtypeSign prev x = signMur (conName x, '<', gtypeSign prev (unM1 x))
-- this instance is needed to avoid overlapping instances with (M1 D d (M1 C c a))
instance (Datatype d, GBeamable a, GBeamable b) => GBeamable (M1 D d (a :+: b) ) where
gbeam (M1 x) = gbeam x
gunbeam bs (lev, _) = let (dirs, bs') = unbeamWord bs
in first M1 $ gunbeam bs' (lev, fromIntegral dirs)
gtypeSign prev x | elem (datatypeName x) prev = signMur (datatypeName x, '_')
gtypeSign prev x = signMur ( gtypeSign (datatypeName x : prev) (unL . unM1 $ x), '|'
, gtypeSign (datatypeName x : prev) (unR . unM1 $ x))
-- choose correct constructor based on the first word uncoded from the BS (dirs variable)
instance (GBeamable a, GBeamable b) => GBeamable (a :+: b) where
gbeam (L1 x) (lev, dirs) = gbeam x (lev + 1, dirs)
gbeam (R1 x) (lev, dirs) = gbeam x (lev + 1, dirs + 2^lev)
gunbeam bs (lev, dirs) = if testBit dirs lev
then first R1 $ gunbeam bs (lev + 1, dirs)
else first L1 $ gunbeam bs (lev + 1, dirs)
gtypeSign prev x = signMur (gtypeSign prev (unL x), '|', gtypeSign prev (unR x))
instance GBeamable a => GBeamable (M1 S c a) where
gbeam (M1 x) = gbeam x
gunbeam bs = first M1 . gunbeam bs
gtypeSign prev ~(M1 x) = signMur ('[', gtypeSign prev x)
instance GBeamable U1 where
gbeam _ _ = mempty
gunbeam bs _ = (U1, bs)
gtypeSign _ _x = signMur 'U'
instance (GBeamable a, GBeamable b) => GBeamable (a :*: b) where
gbeam (x :*: y) t = gbeam x t `mappend` gbeam y t
gunbeam bs t = let (ra, bs') = gunbeam bs t
(rb, bs'') = gunbeam bs' t
in (ra :*: rb, bs'')
gtypeSign prev ~(x :*: y) = signMur (gtypeSign prev x, '*', gtypeSign prev y)
instance Beamable a => GBeamable (K1 i a) where
gbeam (K1 x) _ = beam x
gunbeam bs _ = first K1 (unbeam bs)
gtypeSign prev x = signMur ('K', typeSignR prev (unK1 x))
-- | [un]beamWord functions are a bit more efficient than [un]beamInt
-- it assumes that values are non-negative which allows more compact representation
beamWord :: Word64 -> Builder-- {{{
beamWord 0 = fromWord8 0
beamWord i = fromByteString . B.reverse . fst $ B.unfoldrN 10 octets (i, True)
where
octets :: (Word64, Bool) -> Maybe (Word8, (Word64, Bool))
octets (x, isFirst)
| x > 0 = let r = (fromIntegral (x .&. 0x7F)) .|. (if isFirst then 0 else 0x80)
in Just (r, (x `shift` (negate 7), False))
| otherwise = Nothing
unbeamWord :: B.ByteString -> (Word64, B.ByteString)
unbeamWord bs = (B.foldl f 0 this, rest)
where
f :: Word64 -> Word8 -> Word64
f i w = (i `shift` 7) .|. fromIntegral (w .&. 0x7F)
Just lastWord = B.findIndex (not . flip testBit 7) bs
(this, rest) = B.splitAt (lastWord + 1) bs
{-# SPECIALIZE beamWordX :: Word8 -> Builder #-}
{-# SPECIALIZE beamWordX :: Word16 -> Builder #-}
{-# SPECIALIZE beamWordX :: Word32 -> Builder #-}
{-# SPECIALIZE beamWordX :: Word64 -> Builder #-}
beamWordX :: Integral w => w -> Builder
beamWordX = beamWord . fromIntegral
{-# SPECIALIZE unbeamWordX :: B.ByteString -> (Word8, B.ByteString) #-}
{-# SPECIALIZE unbeamWordX :: B.ByteString -> (Word16, B.ByteString) #-}
{-# SPECIALIZE unbeamWordX :: B.ByteString -> (Word32, B.ByteString) #-}
{-# SPECIALIZE unbeamWordX :: B.ByteString -> (Word64, B.ByteString) #-}
unbeamWordX :: Integral w => B.ByteString -> (w, B.ByteString)
unbeamWordX bs = let (i, bs') = unbeamWord bs in (fromIntegral i, bs')-- }}}
newtype TypeSign = TypeSign { unTypeSign :: Word64 } deriving (Num, Show, Eq, Storable)
-- (de)serialization for numbers -- {{{
instance Beamable Int where { beam = beamEnum ; unbeam = unbeamEnum ; typeSignR _ _ = signMur "Int" }
instance Beamable Int8 where { beam = beamEnum ; unbeam = unbeamEnum ; typeSignR _ _ = signMur "Int8" }
instance Beamable Int16 where { beam = beamEnum ; unbeam = unbeamEnum ; typeSignR _ _ = signMur "Int16" }
instance Beamable Int32 where { beam = beamEnum ; unbeam = unbeamEnum ; typeSignR _ _ = signMur "Int32" }
instance Beamable Int64 where { beam = beamInt ; unbeam = unbeamInt ; typeSignR _ _ = signMur "Int64" }
instance Beamable Word where { beam = beamWordX ; unbeam = unbeamWordX ; typeSignR _ _ = signMur "Word" }
instance Beamable Word8 where { beam = beamWordX ; unbeam = unbeamWordX ; typeSignR _ _ = signMur "Word8" }
instance Beamable Word16 where { beam = beamWordX ; unbeam = unbeamWordX ; typeSignR _ _ = signMur "Word16" }
instance Beamable Word32 where { beam = beamWordX ; unbeam = unbeamWordX ; typeSignR _ _ = signMur "Word32" }
instance Beamable Word64 where { beam = beamWord ; unbeam = unbeamWord ; typeSignR _ _ = signMur "Word64" }
instance Beamable Float where { beam = beamStorable ; unbeam = unbeamStorable ; typeSignR _ _ = signMur "Float" }
instance Beamable Double where { beam = beamStorable ; unbeam = unbeamStorable ; typeSignR _ _ = signMur "Double" }
instance Beamable TypeSign where { beam = beamStorable ; unbeam = unbeamStorable ; typeSignR _ _ = signMur "TypeSign" }
instance Beamable Integer where { beam = beamInteger ; unbeam = unbeamInteger ; typeSignR _ _ = signMur "Integer" }
-- }}}
instance Beamable Char where
beam = beamWord . fromIntegral . ord
unbeam = first (chr . fromIntegral) . unbeamWord
typeSignR _ _ = signMur "Char"
-- Unit
instance Beamable () where
beam = const mempty
unbeam bs = ((), bs)
typeSignR _ _ = signMur "()"
-- Tuples
instance (Beamable a, Beamable b) => Beamable (a, b)
instance (Beamable a, Beamable b, Beamable c) => Beamable (a, b, c)
instance (Beamable a, Beamable b, Beamable c, Beamable d) => Beamable (a, b, c, d)
instance (Beamable a, Beamable b, Beamable c, Beamable d
,Beamable e) => Beamable (a, b, c, d, e)
instance (Beamable a, Beamable b, Beamable c, Beamable d
,Beamable e, Beamable f) => Beamable (a, b, c, d, e, f)
instance (Beamable a, Beamable b, Beamable c, Beamable d
,Beamable e, Beamable f, Beamable g) => Beamable (a, b, c, d, e, f, g)
instance (Beamable a, Beamable b) => Beamable (Either a b)
instance Beamable a => Beamable (Maybe a)
instance Beamable Bool
instance Beamable a => Beamable [a] where
beam xs = beamWord (fromIntegral $ length xs) `mappend` mconcat (map beam xs)
unbeam bs = let (cnt, bs') = unbeamWord bs
in unfoldCnt (fromIntegral cnt) unbeam bs'
typeSignR prev _ = signMur ('L', typeSignR prev (undefined :: a))
unfoldCnt :: Int -> (b -> (a, b)) -> b -> ([a], b)
unfoldCnt cnt_i f = unfoldCnt' [] cnt_i
where
unfoldCnt' xs 0 b = (reverse xs, b)
unfoldCnt' xs cnt b = let (x, b') = f b
in unfoldCnt' (x:xs) (cnt - 1) b'
instance Beamable ByteString where
beam bs = beamWord (fromIntegral $ B.length bs) `mappend` fromByteString bs
unbeam = uncurry (B.splitAt . fromIntegral) . unbeamWord
typeSignR _ _ = signMur "ByteString.Strict"
instance Beamable BL.ByteString where
beam = beam . BL.toChunks
unbeam bs = let (chunks, bs') = unbeam bs
in (BL.fromChunks chunks, bs')
typeSignR _ _ = signMur "ByteString.Lazy"