packages feed

twee-lib-2.7.1: Data/Binary/Sharing.hs

-- | A serialisation library with support for sharing. Built on top of binary.

{-# LANGUAGE ConstraintKinds, GADTs, DerivingVia, TypeSynonymInstances, FlexibleInstances, DefaultSignatures, DeriveAnyClass, DeriveFunctor, TupleSections #-}
module Data.Binary.Sharing(Binary(..), Shared(..), Put, PutM, encode, encodeFile, runPut, liftPut, unliftPut, Get, decode, decodeFile, runGet, liftGet, unliftGet, putList, getList, putWord8, getWord8, putInt8, getInt8, putIntegral, getIntegral, ReadShowBinary(..)) where
import qualified Data.Binary as S
import qualified Data.Binary.Get as S
import qualified Data.Binary.Put as S
import Data.Typeable
import qualified Data.HashMap.Strict as HashMap
import Data.HashMap.Strict(HashMap)
import Data.Maybe
import GHC.Generics
import Data.Hashable
import Control.Monad
import Data.Bits
import Data.Word
import Data.Int
import Data.Char
import qualified Data.ByteString.Lazy as BS
import qualified Data.ByteString as BSS
import Control.Monad.Trans.State.Strict hiding (get, put)
import qualified Control.Monad.Trans.State.Strict as State
import qualified Data.Primitive.SmallArray as SmallArray
import qualified Data.Foldable as Foldable
import qualified Data.Map as Map
import qualified Data.IntMap as IntMap
import qualified Data.Set as Set
import qualified Data.IntSet as IntSet
import qualified Data.HashSet as HashSet

----------------------------------------------------------------------
-- The basic serialiser/deserialiser types.
--
-- The idea is that both serialisation and deserialisation maintain a
-- store of shared values we have already seen (each associated with an
-- ID). When we emit a value we have already seen, we instead just emit
-- its ID. This is handled in the "Binary (Shared a)" instance.
----------------------------------------------------------------------

-- | A serialiser.
newtype PutM a = PutM { unPutM :: Store -> S.PutM (a, Store) } deriving Functor
-- Note we can't use StateT because then generalised newtype deriving doesn't work
instance Applicative PutM where
  pure x = PutM $ \store -> pure (x, store)
  mf <*> mx = mf >>= \f -> mx >>= \x -> pure (f x)
instance Monad PutM where
  mx >>= f =
    PutM $ \store -> do
      (x, store) <- unPutM mx store
      unPutM (f x) store
type Put = PutM ()

instance Semigroup Put where
  mx <> my = do { mx; my }

instance Monoid Put where
  mempty = pure mempty

-- Lift a state-monadic computation into Put.
liftStatePut :: State Store a -> PutM a
liftStatePut act = PutM $ \store -> return (runState act store)

-- | Lift a binary serialiser into Put.
liftPut :: S.PutM a -> PutM a
liftPut putter = PutM $ \store -> (, store) <$> putter

-- | Convert a 'Put' back into a binary serialiser.
unliftPut :: PutM a -> S.PutM a
unliftPut (PutM putter) = fst <$> putter emptyStore

-- | Serialise a value.
runPut :: Put -> BS.ByteString
runPut = S.runPut . unliftPut

-- | Serialise a value.
encode :: Binary a => a -> BS.ByteString
encode = runPut . put

-- | Serialise a value to a file.
encodeFile :: Binary a => FilePath -> a -> IO ()
encodeFile path = BS.writeFile path . encode

-- | A deserialiser.
newtype Get a = Get { unGet :: Store -> S.Get (a, Store) } deriving Functor
instance Applicative Get where
  pure x = Get $ \store -> pure (x, store)
  mf <*> mx = mf >>= \f -> mx >>= \x -> pure (f x)
instance Monad Get where
  mx >>= f =
    Get $ \store -> do
      (x, store) <- unGet mx store
      unGet (f x) store

-- Lift a state-monadic computation into Get.
liftStateGet :: State Store a -> Get a
liftStateGet act = Get $ \store -> return (runState act store)

-- | Lift a binary serialiser into Get.
liftGet :: S.Get a -> Get a
liftGet getter = Get $ \store -> (, store) <$> getter

-- | Convert a 'Get' back into a binary serialiser.
unliftGet :: Get a -> S.Get a
unliftGet (Get getter) = fst <$> getter emptyStore

-- | Deserialise a value.
runGet :: Get a -> BS.ByteString -> a
runGet = S.runGet . unliftGet

-- | Deserialise a value.
decode :: Binary a => BS.ByteString -> a
decode = runGet get

-- | Deserialise a value from a file.
decodeFile :: Binary a => FilePath -> IO a
decodeFile path = decode <$> BS.readFile path

----------------------------------------------------------------------
-- The sharing primitive.
----------------------------------------------------------------------

-- | A newtype wrapper which uses sharing on serialisation.
-- If the same 'Shared' value is 'put' multiple times, only the first
-- occurrence will be serialised in full and the later ones will
-- instead serialise as a "pointer" to the first one. The test for
-- whether a value is the same as a previous one uses '(==)'.
newtype Shared a = Shared { getShared :: a } deriving (Eq, Show)

-- The binary format for a "Shared a".
-- The first time a given Shared value appears, we allocate a key (ID) for
-- it and emit it as a 'Let'; if it reoccurs, we emit a 'Ref' to the existing ID.
data SharedEncoding a = Let Key a | Ref Key

instance Binary a => Binary (SharedEncoding a) where
  put (Let key x) = do
    put (key `unsafeShiftL` 1)
    put x
  put (Ref key) = do
    put (key `unsafeShiftL` 1 + 1)

  get = do
    keyTag <- get
    let key = keyTag `unsafeShiftR` 1
    case testBit keyTag 0 of
      False -> do -- Let
        x <- get
        return (Let key x)
      True -> -- Ref
        return (Ref key)

instance (Typeable a, Eq a, Hashable a, Binary a) => Binary (Shared a) where
  put (Shared x) = do
    mkey <- liftStatePut (lookupValue x)
    case mkey of
      Just key ->
        put (Ref key :: SharedEncoding a)
      Nothing -> do
        key <- liftStatePut reserveKey
        put (Let key x)
        liftStatePut (addValue key x)

  get = Shared <$> do
    value <- get
    case value of
      Let key x -> do
        liftStateGet (addValue key x)
        return x
      Ref key ->
        liftStateGet (lookupKey key)

----------------------------------------------------------------------
-- The store of previously-seen values.
----------------------------------------------------------------------

data Store =
  Store {
    -- Maps between values and ID numbers.
    store_keys :: HashMap Key Value,
    store_values :: HashMap Value Key,
    -- The next available ID number.
    store_next_key :: Key }
type Key = Integer

-- Useful for making heterogeneous maps.
data Value where
  Value :: (Eq a, Hashable a, Typeable a) => a -> Value

instance Eq Value where
  Value x == Value y =
    case cast x of
      Just x' -> x' == y
      Nothing -> error "type error"

instance Hashable Value where
  hashWithSalt s (Value x) = hashWithSalt s (typeOf x, x)

emptyStore :: Store
emptyStore = Store HashMap.empty HashMap.empty 0

-- Check if an existing value exists in the store.
lookupValue :: (Eq a, Hashable a, Typeable a) => a -> State Store (Maybe Key)
lookupValue x = do
  let value = Value x
  Store{..} <- State.get
  return (fromIntegral <$> HashMap.lookup value store_values)

-- Allocate a new key.
reserveKey :: Monad m => StateT Store m Key
reserveKey = do
  store@Store{..} <- State.get
  State.put store{store_next_key = store_next_key+1}
  return (fromIntegral store_next_key)

-- Add a new value to the store.
addValue :: (Eq a, Hashable a, Typeable a) => Key -> a -> State Store ()
addValue key x = do
  let value = Value x
  store@Store{..} <- State.get
  when (HashMap.member key store_keys) $ error "key already found"
  when (HashMap.member value store_values) $ error "value already found"
  State.put store{
    store_keys = HashMap.insert key value store_keys,
    store_values = HashMap.insert value key store_values }

-- Find a value by ID number.
lookupKey :: Typeable a => Key -> State Store a
lookupKey key = do
  Store{..} <- State.get
  case HashMap.lookup key store_keys of
    Just (Value x) -> return (fromJust (cast x))
    Nothing -> error ("invalid value " ++ show key ++ " " ++ show store_next_key)

----------------------------------------------------------------------
-- Primitive serialisers.
----------------------------------------------------------------------

putWord8 = liftPut . S.putWord8
getWord8 = liftGet S.getWord8
putInt8 = liftPut . S.putInt8
getInt8 = liftGet S.getInt8

putIntegral :: (Integral a, Bits a) => a -> Put
putIntegral n
  | toInteger n >= -1 && toInteger n < 126 =
    putWord8 (clearBit (fromIntegral n) 7)
  | otherwise = do
    putWord8 (setBit (fromIntegral n) 7)
    putIntegral (n `shiftR` 7)

getIntegral :: (Integral a, Bits a) => Get a
getIntegral = getIntegral' 0 0

getIntegral' :: (Integral a, Bits a) => Int -> a -> Get a
getIntegral' !k !n = do
  x <- getWord8
  let !n' = n + fromIntegral (clearBit x 7) `shiftL` k
  if testBit x 7 then getIntegral' (k+7) n'
    else if x == 127 then return (n' - (1 `shiftL` (k+7)))
    else return n'

-- QuickCheck property:
-- quickCheck (withNumTests 1000000 (withMaxSize 100000 (\x -> runGet getIntegral (runPut (putIntegral (x :: Int))) === Right x)))
-- TODO: add to test suite

putList :: (a -> Put) -> [a] -> Put
putList putter xs = do
  put (length xs)
  mapM_ putter xs

getList :: Get a -> Get [a]
getList getter = do
  len <- get
  replicateM len getter

----------------------------------------------------------------------
-- A class for serialisation/deserialisation.
----------------------------------------------------------------------

class Binary a where
  put :: a -> Put
  get :: Get a

  default put :: (Generic a, GBinary (Rep a)) => a -> Put
  put x = gput (from x)

  default get :: (Generic a, GBinary (Rep a)) => Get a
  get = to <$> gget

instance Binary Int where { put = putIntegral; get = getIntegral }
instance Binary Int8 where { put = putInt8; get = getInt8 }
instance Binary Int16 where { put = putIntegral; get = getIntegral }
instance Binary Int32 where { put = putIntegral; get = getIntegral }
instance Binary Int64 where { put = putIntegral; get = getIntegral }
instance Binary Word where { put = putIntegral; get = getIntegral }
instance Binary Word8 where { put = putWord8; get = getWord8 }
instance Binary Word16 where { put = putIntegral; get = getIntegral }
instance Binary Word32 where { put = putIntegral; get = getIntegral }
instance Binary Word64 where { put = putIntegral; get = getIntegral }
instance Binary Integer where { put = putIntegral; get = getIntegral }
instance Binary Char where { put = putIntegral . ord; get = chr <$> getIntegral }
instance Binary BS.ByteString where { put = liftPut . S.put; get = liftGet S.get }
instance Binary BSS.ByteString where { put = liftPut . S.put; get = liftGet S.get }
instance Binary Float where { put = liftPut. S.put; get = liftGet S.get }
instance Binary Double where { put = liftPut . S.put; get = liftGet S.get }
deriving instance Binary Bool
deriving instance Binary ()
deriving instance (Binary a, Binary b) => Binary (a, b)
deriving instance (Binary a, Binary b, Binary c) => Binary (a, b, c)
deriving instance (Binary a, Binary b, Binary c, Binary d) => Binary (a, b, c, d)
deriving instance (Binary a, Binary b, Binary c, Binary d, Binary e) => Binary (a, b, c, d, e)
deriving instance Binary a => Binary (Maybe a)
deriving instance (Binary a, Binary b) => Binary (Either a b)
instance Binary a => Binary (SmallArray.SmallArray a) where
  put = put . Foldable.toList
  get = SmallArray.smallArrayFromList <$> get

-- | Serialise a value via read/show.
newtype ReadShowBinary a = ReadShowBinary { getReadShowBinary :: a }
instance (Show a, Read a) => Binary (ReadShowBinary a) where
  put = put . show . getReadShowBinary
  get = ReadShowBinary . read <$> get

instance Binary a => Binary [a] where
  put = putList put
  get = getList get

instance (Binary k, Binary v, Ord k) => Binary (Map.Map k v) where
  put = put . Map.toList
  get = Map.fromList <$> get

instance (Binary k, Binary v, Hashable k) => Binary (HashMap k v) where
  put = put . HashMap.toList
  get = HashMap.fromList <$> get

instance Binary v => Binary (IntMap.IntMap v) where
  put = put . IntMap.toList
  get = IntMap.fromList <$> get

instance (Binary k, Ord k) => Binary (Set.Set k) where
  put = put . Set.toList
  get = Set.fromList <$> get

instance (Binary k, Hashable k) => Binary (HashSet.HashSet k) where
  put = put . HashSet.toList
  get = HashSet.fromList <$> get

instance Binary IntSet.IntSet where
  put = put . IntSet.toList
  get = IntSet.fromList <$> get

----------------------------------------------------------------------
-- Generic instances.
----------------------------------------------------------------------

class GBinary f where
  gput :: f a -> Put
  gget :: Get (f a)

  gconstructors :: proxy (f a) -> Int
  gconstructors _ = 1

  gconstructor :: f a -> Int
  gconstructor _ = 0

  gputConstructor :: f a -> Put
  gputConstructor = gput

  ggetConstructor :: Int -> Get (f a)
  ggetConstructor 0 = gget
  ggetConstructor _ = error "ggetConstructor: out of bounds"

instance (GBinary f, GBinary g) => GBinary (f :*: g) where
  gput (x :*: y) = do
    gput x
    gput y
  gget = liftM2 (:*:) gget gget

instance (GBinary f, GBinary g) => GBinary (f :+: g) where
  gput x = do
    put (gconstructor x)
    gputConstructor x

  gget = do
    tag <- get
    ggetConstructor tag

  gconstructors _ =
    gconstructors (Proxy :: Proxy (f a)) + gconstructors (Proxy :: Proxy (g a))

  gconstructor (L1 x) = gconstructor x
  gconstructor (R1 x) = gconstructor x + gconstructors (Proxy :: Proxy (f a))

  gputConstructor (L1 x) = gputConstructor x
  gputConstructor (R1 x) = gputConstructor x

  ggetConstructor n
    | n < m = L1 <$> ggetConstructor n
    | otherwise = R1 <$> ggetConstructor (n-m)
    where
      m = gconstructors (Proxy :: Proxy (f a))

instance GBinary f => GBinary (M1 i c f) where
  gput (M1 x) = gput x
  gget = M1 <$> gget
  gconstructors _ = gconstructors (Proxy :: Proxy (f a))
  gconstructor (M1 x) = gconstructor x
  gputConstructor (M1 x) = gputConstructor x
  ggetConstructor n = M1 <$> ggetConstructor n

instance Binary a => GBinary (K1 i a) where
  gput (K1 x) = put x
  gget = K1 <$> get

instance GBinary U1 where
  gput U1 = return ()
  gget = return U1

instance GBinary V1 where
  gput _ = error "gput: void"
  gget = error "gget: void"
  gconstructors _ = 0
  gconstructor _ = error "gconstructor: void"
  gputConstructor = error "gputConstructor: void"
  ggetConstructor _ = error "ggetConstructor: void"