arbtt-0.11: src/Data/Binary/StringRef.hs
{-# LANGUAGE FlexibleInstances, UndecidableInstances, TypeSynonymInstances #-}
{-# LANGUAGE LambdaCase #-}
module Data.Binary.StringRef
( ListOfStringable(..)
, StringReferencingBinary(..)
, IntLen(..)
, ls_encode
, ls_decode
) where
import Data.Binary
import Data.Binary.Put
import Data.Binary.Get
import Control.Monad
import Control.Applicative ((<$>))
import Data.List
import Data.ByteString.Lazy (ByteString)
import qualified Data.MyText as T
import Data.MyText (Text, decodeUtf8, encodeUtf8)
import Debug.Trace
class StringReferencingBinary a => ListOfStringable a where
listOfStrings :: a -> [Text]
-- | An extended version of Binary that passes the list of strings of the
-- previous sample
class StringReferencingBinary a where
ls_put :: [Text] -> a -> Put
ls_get :: [Text] -> Get a
------------------------------------------------------------------------
-- Instances for the first few tuples
instance (StringReferencingBinary a, StringReferencingBinary b) => StringReferencingBinary (a,b) where
ls_put strs (a,b) = ls_put strs a >> ls_put strs b
ls_get strs = liftM2 (,) (ls_get strs) (ls_get strs)
instance (StringReferencingBinary a, StringReferencingBinary b, StringReferencingBinary c) => StringReferencingBinary (a,b,c) where
ls_put strs (a,b,c) = ls_put strs a >> ls_put strs b >> ls_put strs c
ls_get strs = liftM3 (,,) (ls_get strs) (ls_get strs) (ls_get strs)
instance (StringReferencingBinary a, StringReferencingBinary b, StringReferencingBinary c, StringReferencingBinary d) => StringReferencingBinary (a,b,c,d) where
ls_put strs (a,b,c,d) = ls_put strs a >> ls_put strs b >> ls_put strs c >> ls_put strs d
ls_get strs = liftM4 (,,,) (ls_get strs) (ls_get strs) (ls_get strs) (ls_get strs)
instance (StringReferencingBinary a, StringReferencingBinary b, StringReferencingBinary c, StringReferencingBinary d, StringReferencingBinary e) => StringReferencingBinary (a,b,c,d,e) where
ls_put strs (a,b,c,d,e) = ls_put strs a >> ls_put strs b >> ls_put strs c >> ls_put strs d >> ls_put strs e
ls_get strs = liftM5 (,,,,) (ls_get strs) (ls_get strs) (ls_get strs) (ls_get strs) (ls_get strs)
newtype CompactNum a = CompactNum { fromCompactNum :: a }
instance (Integral a, Num a, Binary a) => StringReferencingBinary (CompactNum a) where
ls_put _ (CompactNum i)
| 0 <= i && i < 255 = putWord8 (fromIntegral i)
| otherwise = putWord8 255 >> put i
ls_get _ = fmap CompactNum $ getWord8 >>= \case
i | 0 <= i && i < 255 -> return (fromIntegral i)
| otherwise -> get
instance StringReferencingBinary a => StringReferencingBinary [a] where
ls_put strs l = ls_put strs (CompactNum (length l)) >> mapM_ (ls_put strs) l
ls_get strs = ls_getMany strs . fromCompactNum =<< ls_get strs
instance StringReferencingBinary Text where
ls_put strs s = case elemIndex s strs of
Just i | 0 <= i && i < 255 ->
putWord8 (fromIntegral (succ i))
_ -> putWord8 0 >> ls_put strs (T.unpack s)
ls_get strs = getWord8 >>= \case
0 -> T.pack <$> ls_get strs
i -> return $! strs !! fromIntegral (pred i)
-- | 'ls_get strsMany n' ls_get strs 'n' elements in order, without blowing the stack.
ls_getMany :: StringReferencingBinary a => [Text] -> Int -> Get [a]
ls_getMany strs n = go [] n
where
go xs 0 = return $! reverse xs
go xs i = do x <- ls_get strs
-- we must seq x to avoid stack overflows due to laziness in
-- (>>=)
x `seq` go (x:xs) (i-1)
{-# INLINE ls_getMany #-}
-- compat newtype for deserialization of v2-v4 CaptureData
newtype IntLen a = IntLen { fromIntLen :: a }
-- compat instance for deserialization of v1 CaptureData
instance Binary a => Binary (IntLen a) where
put = put . fromIntLen
get = IntLen <$> get
-- compat instance for deserialization of v2-v4 CaptureData
instance StringReferencingBinary a => StringReferencingBinary (IntLen [a]) where
ls_put strs (IntLen l) = ls_put strs (length l) >> mapM_ (ls_put strs) l
ls_get strs = fmap IntLen $ ls_getMany strs =<< ls_get strs
-- compat instance for deserialization of v2-v4 CaptureData
instance StringReferencingBinary (IntLen Text) where
ls_put strs (IntLen s) = case elemIndex s strs of
Just i | 0 <= i && i < 255 ->
putWord8 (fromIntegral (succ i))
_ -> putWord8 0 >> ls_put strs (IntLen (T.unpack s))
ls_get strs = fmap IntLen $ getWord8 >>= \case
0 -> T.pack . fromIntLen <$> ls_get strs
i -> return $! strs !! fromIntegral (pred i)
{-
instance Binary a => StringReferencingBinary a where
ls_put _ = put
ls_get _ = get
-}
instance StringReferencingBinary Char where { ls_put _ = put; ls_get _ = get }
instance StringReferencingBinary Int where { ls_put _ = put; ls_get _ = get }
instance StringReferencingBinary Integer where { ls_put _ = put; ls_get _ = get }
instance StringReferencingBinary Bool where { ls_put _ = put; ls_get _ = get }
ls_encode :: StringReferencingBinary a => [Text] -> a -> ByteString
ls_encode strs = runPut . ls_put strs
{-# INLINE ls_encode #-}
-- | Decode a value from a lazy ByteString, reconstructing the original structure.
--
ls_decode :: StringReferencingBinary a => [Text] -> ByteString -> a
ls_decode strs = runGet (ls_get strs)