packages feed

packstream-0.1.0.1: src/Data/PackStream/Serializer.hs

{-# LANGUAGE TypeApplications #-}
module Data.PackStream.Serializer
(
  null, bool, integer, float,
  bytes, string, list, dict, structure,
  value
) where


import Data.PackStream.Internal.Type (Structure (..), Value (..))
import Data.PackStream.Internal.Code
import Data.PackStream.Internal.Binary (Serialize(..), inDepth)

import Prelude hiding (null)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS (singleton, cons, append, length, concat, empty)
import Data.Text (Text)
import Data.Text.Encoding (encodeUtf8)
import Data.Map.Strict (Map, toList)
import Data.Word (Word8)


-- |Represent '()' as 'PackStream' 'ByteString'
null :: ByteString
null = BS.singleton nullCode

-- |Represent 'Bool' as 'PackStream' 'ByteString'
bool :: Bool -> ByteString
bool False = BS.singleton falseCode
bool True  = BS.singleton trueCode

-- |Represent 'Int' as 'PackStream' 'ByteString'
integer :: Int -> ByteString
integer n | n >= -16 && n < 128 = serialize n
          | inDepth 8  n        = int8Code  `BS.cons` serialize n
          | inDepth 16 n        = int16Code `BS.cons` serialize n
          | inDepth 32 n        = int32Code `BS.cons` serialize n
          | inDepth 64 n        = int64Code `BS.cons` serialize n
          | otherwise           = BS.empty

-- |Represent 'Double' as 'PackStream' 'ByteString'
float :: Double -> ByteString
float x = floatCode `BS.cons` serialize x

-- |Represent 'ByteString' as 'PackStream' 'ByteString'
bytes :: ByteString -> ByteString
bytes bs = constructCollection mstart bs
  where
    mstart :: Maybe ByteString
    mstart = (`BS.cons` serializeLen (BS.length bs)) <$> firstByte
  
    firstByte :: Maybe Word8
    firstByte | BS.length bs <= 0xFF       = pure bytes8Code
              | BS.length bs <= 0xFFFF     = pure bytes16Code
              | BS.length bs <= 0xFFFFFFFF = pure bytes32Code
              | otherwise                  = Nothing

-- |Represent 'Text' as 'PackStream' 'ByteString'
string :: Text -> ByteString
string s = constructCollection mstart (serialize s)
  where
    mstart :: Maybe ByteString
    mstart = firstBytes stringTinyCode string8Code string16Code string32Code (BS.length $ encodeUtf8 s)

-- |Represent '[Value]' as 'PackStream' 'ByteString'
list :: [Value] -> ByteString
list l = constructCollection mstart (BS.concat (value <$> l))
  where
    mstart :: Maybe ByteString
    mstart = firstBytes listTinyCode list8Code list16Code list32Code (length l)

-- |Represent 'Map Text Value' as 'PackStream' 'ByteString'
dict :: Map Text Value -> ByteString
dict d = constructCollection mstart (BS.concat (kvs <$> toList d))
  where
    mstart :: Maybe ByteString
    mstart = firstBytes dictTinyCode dict8Code dict16Code dict32Code (length d)

    kvs :: (Text, Value) -> ByteString
    kvs (k, v) = string k `BS.append` value v

-- |Represent 'Structure' as 'PackStream' 'ByteString'
structure :: Structure -> ByteString
structure s | len < 16  = structureCode + len `BS.cons` signature s `BS.cons` BS.concat (value <$> fields s)
            | otherwise = BS.empty
  where
    len = fromIntegral $ length $ fields s

-- |Represent 'Value' as 'PackStream' 'ByteString'
value :: Value -> ByteString
value N     = null
value (B b) = bool b
value (I i) = integer i
value (F f) = float f
value (U u) = bytes u
value (T t) = string t
value (L l) = list l
value (D d) = dict d
value (S s) = structure s

-- Helper functions

constructCollection :: Maybe ByteString -> ByteString -> ByteString
constructCollection mstart end = case mstart of
                                   Just start -> start `BS.append` end
                                   Nothing    -> BS.empty

firstBytes :: Word8 -> Word8 -> Word8 -> Word8 -> Int -> Maybe ByteString
firstBytes bt b8 b16 b32 len | len <= 0xF        = pure $ BS.singleton $ bt  + fromIntegral len
                             | len <= 0xFF       = pure $ b8  `BS.cons` serializeLen len
                             | len <= 0xFFFF     = pure $ b16 `BS.cons` serializeLen len
                             | len <= 0xFFFFFFFF = pure $ b32 `BS.cons` serializeLen len
                             | otherwise         = Nothing

serializeLen :: Int -> ByteString
serializeLen = serialize @Word . fromIntegral