msgpack-persist (empty) → 0.0.15
raw patch · 13 files changed
+1117/−0 lines, 13 filesdep +QuickCheckdep +basedep +bytestringsetup-changed
Dependencies added: QuickCheck, base, bytestring, containers, criterion, deepseq, groom, hashable, hspec, monad-validate, msgpack-persist, msgpack-types, persist, text, unordered-containers, vector
Files
- LICENSE +25/−0
- Setup.lhs +3/−0
- bench/Data/MessagePack/IntBench.hs +87/−0
- bench/Data/MessagePackBench.hs +74/−0
- bench/benchmark.hs +14/−0
- msgpack-persist.cabal +100/−0
- src/Data/MessagePack.hs +80/−0
- src/Data/MessagePack/Get.hs +111/−0
- src/Data/MessagePack/Put.hs +192/−0
- test/Data/MessagePackSpec.hs +349/−0
- test/Data/Result.hs +30/−0
- test/testsuite.hs +1/−0
- tools/msgpack-parser.hs +51/−0
+ LICENSE view
@@ -0,0 +1,25 @@+Copyright (c) 2017-2018, The TokTok Team+Copyright (c) 2009-2010, Hideyuki Tanaka+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:+ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.+ * Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.+ * Neither the name of the Hideyuki Tanaka nor the+ names of its contributors may be used to endorse or promote products+ derived from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY Hideyuki Tanaka ''AS IS'' AND ANY+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.lhs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
+ bench/Data/MessagePack/IntBench.hs view
@@ -0,0 +1,87 @@+module Data.MessagePack.IntBench (suite) where++import Criterion.Main (Benchmark, bench, bgroup, nf)+import qualified Data.ByteString.Lazy as LBS+import Data.Int (Int64)++import Data.MessagePack+++packInt :: Int64 -> LBS.ByteString+packInt = pack++unpackInt :: LBS.ByteString -> Maybe Int64+unpackInt = unpack+++suite :: [Benchmark]+suite =+ [ bgroup "pack" -- should be constant time+ [ bench "0x1" $ nf packInt 0x1+ , bench "0x10" $ nf packInt 0x10+ , bench "0x100" $ nf packInt 0x100+ , bench "0x1000" $ nf packInt 0x1000+ , bench "0x10000" $ nf packInt 0x10000+ , bench "0x100000" $ nf packInt 0x100000+ , bench "0x1000000" $ nf packInt 0x1000000+ , bench "0x10000000" $ nf packInt 0x10000000+ , bench "0x100000000" $ nf packInt 0x100000000+ , bench "0x1000000000" $ nf packInt 0x1000000000+ , bench "0x10000000000" $ nf packInt 0x10000000000+ , bench "0x100000000000" $ nf packInt 0x100000000000+ , bench "0x1000000000000" $ nf packInt 0x1000000000000+ , bench "0x10000000000000" $ nf packInt 0x10000000000000+ , bench "0x100000000000000" $ nf packInt 0x100000000000000+ , bench "0x1000000000000000" $ nf packInt 0x1000000000000000+ , bench "-0x1" $ nf packInt (-0x1 )+ , bench "-0x10" $ nf packInt (-0x10 )+ , bench "-0x100" $ nf packInt (-0x100 )+ , bench "-0x1000" $ nf packInt (-0x1000 )+ , bench "-0x10000" $ nf packInt (-0x10000 )+ , bench "-0x100000" $ nf packInt (-0x100000 )+ , bench "-0x1000000" $ nf packInt (-0x1000000 )+ , bench "-0x10000000" $ nf packInt (-0x10000000 )+ , bench "-0x100000000" $ nf packInt (-0x100000000 )+ , bench "-0x1000000000" $ nf packInt (-0x1000000000 )+ , bench "-0x10000000000" $ nf packInt (-0x10000000000 )+ , bench "-0x100000000000" $ nf packInt (-0x100000000000 )+ , bench "-0x1000000000000" $ nf packInt (-0x1000000000000 )+ , bench "-0x10000000000000" $ nf packInt (-0x10000000000000 )+ , bench "-0x100000000000000" $ nf packInt (-0x100000000000000 )+ , bench "-0x1000000000000000" $ nf packInt (-0x1000000000000000)+ ]+ , bgroup "unpack" -- should be constant time+ [ bench "0x1" $ nf unpackInt (packInt 0x1 )+ , bench "0x10" $ nf unpackInt (packInt 0x10 )+ , bench "0x100" $ nf unpackInt (packInt 0x100 )+ , bench "0x1000" $ nf unpackInt (packInt 0x1000 )+ , bench "0x10000" $ nf unpackInt (packInt 0x10000 )+ , bench "0x100000" $ nf unpackInt (packInt 0x100000 )+ , bench "0x1000000" $ nf unpackInt (packInt 0x1000000 )+ , bench "0x10000000" $ nf unpackInt (packInt 0x10000000 )+ , bench "0x100000000" $ nf unpackInt (packInt 0x100000000 )+ , bench "0x1000000000" $ nf unpackInt (packInt 0x1000000000 )+ , bench "0x10000000000" $ nf unpackInt (packInt 0x10000000000 )+ , bench "0x100000000000" $ nf unpackInt (packInt 0x100000000000 )+ , bench "0x1000000000000" $ nf unpackInt (packInt 0x1000000000000 )+ , bench "0x10000000000000" $ nf unpackInt (packInt 0x10000000000000 )+ , bench "0x100000000000000" $ nf unpackInt (packInt 0x100000000000000 )+ , bench "0x1000000000000000" $ nf unpackInt (packInt 0x1000000000000000 )+ , bench "-0x1" $ nf unpackInt (packInt (-0x1 ))+ , bench "-0x10" $ nf unpackInt (packInt (-0x10 ))+ , bench "-0x100" $ nf unpackInt (packInt (-0x100 ))+ , bench "-0x1000" $ nf unpackInt (packInt (-0x1000 ))+ , bench "-0x10000" $ nf unpackInt (packInt (-0x10000 ))+ , bench "-0x100000" $ nf unpackInt (packInt (-0x100000 ))+ , bench "-0x1000000" $ nf unpackInt (packInt (-0x1000000 ))+ , bench "-0x10000000" $ nf unpackInt (packInt (-0x10000000 ))+ , bench "-0x100000000" $ nf unpackInt (packInt (-0x100000000 ))+ , bench "-0x1000000000" $ nf unpackInt (packInt (-0x1000000000 ))+ , bench "-0x10000000000" $ nf unpackInt (packInt (-0x10000000000 ))+ , bench "-0x100000000000" $ nf unpackInt (packInt (-0x100000000000 ))+ , bench "-0x1000000000000" $ nf unpackInt (packInt (-0x1000000000000 ))+ , bench "-0x10000000000000" $ nf unpackInt (packInt (-0x10000000000000 ))+ , bench "-0x100000000000000" $ nf unpackInt (packInt (-0x100000000000000 ))+ , bench "-0x1000000000000000" $ nf unpackInt (packInt (-0x1000000000000000))+ ]+ ]
+ bench/Data/MessagePackBench.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveAnyClass #-}+module Data.MessagePackBench (suite) where++import Control.DeepSeq (NFData)+import Criterion.Main (Benchmark, bench, bgroup, nf)+import qualified Data.ByteString.Lazy as LBS+import Data.Int (Int64)+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)+import Test.QuickCheck.Gen (resize, unGen)+import Test.QuickCheck.Random (mkQCGen)++import GHC.Generics++import Data.MessagePack+++defaultSeed :: Int+defaultSeed = 301++instance MessagePack a => MessagePack (Maybe a)++data Expr+ = I Int+ | Add Expr Expr+ | Prod Expr Expr+ deriving (NFData, Generic)++instance MessagePack Expr++expr :: Int -> Expr+expr i = foldr build (I 10) [0..i] where+ build x e =+ case x `mod` 4 of+ 0 -> Add e e+ 1 -> Prod e e+ 2 -> Add e (I x)+ 3 -> Prod (I x) e++arb :: Arbitrary a => Int -> a+arb size =+ let g = unGen $ resize size arbitrary in+ g (mkQCGen defaultSeed) defaultSeed+++benchRange+ :: NFData b+ => Int -> Int -> Int -> (a -> b) -> (Int -> a) -> [Benchmark]+benchRange from to steps f g =+ map (\step ->+ let sz = from + ((to - from) `div` (steps - 1)) * step in+ bench (show sz) $ nf f (g sz)+ ) [0..steps-1]+++suite :: [Benchmark]+suite =+ [ bgroup "pack"+ [ bgroup "Expr" $ benchRange 10 40 4 pack expr+ , bench "Just Int" $ nf pack (Just (3 :: Int))+ , bench "Nothing" $ nf pack (Nothing :: Maybe Int)+ , bench "()" $ nf pack ()+ , bgroup "[a]" $ benchRange 1000 10000 10 pack (`replicate` ())+ -- ^ should be linear+ ]+ , bgroup "unpack"+ [ bgroup "Expr" $ benchRange 10 100 10 (unpack :: LBS.ByteString -> Maybe Expr) (pack . expr)+ , bench "Just Int" $ nf (unpack :: LBS.ByteString -> Maybe Int) (pack (Just (3 :: Int)))+ , bench "Nothing" $ nf (unpack :: LBS.ByteString -> Maybe Int) (pack (Nothing :: Maybe Int))+ , bench "()" $ nf (unpack :: LBS.ByteString -> Maybe () ) (pack ())+ , bgroup "[a]" $ benchRange 1000 10000 10 pack (`replicate` ())+ -- ^ should be linear+ ]+ ]
+ bench/benchmark.hs view
@@ -0,0 +1,14 @@+module Main (main) where++import Criterion.Main (bgroup, defaultMain)+import Test.QuickCheck.Random (mkQCGen)++import qualified Data.MessagePack.IntBench+import qualified Data.MessagePackBench+++main :: IO ()+main = defaultMain+ [ bgroup "Data.MessagePack" Data.MessagePackBench.suite+ , bgroup "Data.MessagePack.Int" Data.MessagePack.IntBench.suite+ ]
+ msgpack-persist.cabal view
@@ -0,0 +1,100 @@+name: msgpack-persist+version: 0.0.15+synopsis: A Haskell implementation of MessagePack+homepage: http://msgpack.org/+license: BSD3+license-file: LICENSE+author: Hideyuki Tanaka+maintainer: Iphigenia Df <iphydf@gmail.com>+copyright: Copyright (c) 2009-2016, Hideyuki Tanaka+category: Data+stability: Experimental+cabal-version: >= 1.10+build-type: Simple+description:+ A Haskell implementation of MessagePack <http://msgpack.org/>+ .+ This is a fork of msgpack-haskell <https://github.com/msgpack/msgpack-haskell>,+ since the original author is unreachable. This fork incorporates a number of+ bugfixes and is actively being developed.++source-repository head+ type: git+ location: https://github.com/TokTok/hs-msgpack-persist++library+ default-language: Haskell2010+ hs-source-dirs:+ src+ ghc-options:+ -Wall+ -fno-warn-unused-imports+ exposed-modules:+ Data.MessagePack+ other-modules:+ Data.MessagePack.Get+ Data.MessagePack.Put+ build-depends:+ base < 5+ , bytestring+ , monad-validate+ , msgpack-types >= 0.2 && < 0.3+ , persist+ , text++executable msgpack-parser+ default-language: Haskell2010+ hs-source-dirs:+ tools+ ghc-options:+ -Wall+ -fno-warn-unused-imports+ main-is: msgpack-parser.hs+ build-depends:+ base < 5+ , bytestring+ , groom+ , msgpack-persist++test-suite testsuite+ type: exitcode-stdio-1.0+ default-language: Haskell2010+ hs-source-dirs: test+ main-is: testsuite.hs+ other-modules:+ Data.MessagePackSpec+ Data.Result+ ghc-options:+ -Wall+ -fno-warn-unused-imports+ build-depends:+ base < 5+ , QuickCheck+ , bytestring+ , containers+ , hashable+ , hspec+ , msgpack-persist+ , msgpack-types >= 0.1.0+ , text+ , unordered-containers+ , vector++benchmark benchmark+ type: exitcode-stdio-1.0+ default-language: Haskell2010+ hs-source-dirs: bench+ main-is: benchmark.hs+ other-modules:+ Data.MessagePack.IntBench+ Data.MessagePackBench+ ghc-options:+ -Wall+ -fno-warn-unused-imports+ build-depends:+ base < 5+ , QuickCheck+ , bytestring+ , criterion+ , deepseq+ , msgpack-persist
+ src/Data/MessagePack.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE Trustworthy #-}+{-# OPTIONS_GHC -Wno-orphans #-}+--------------------------------------------------------------------+-- |+-- Module : Data.MessagePack+-- Copyright : (c) Hideyuki Tanaka, 2009-2015+-- License : BSD3+--+-- Maintainer: tanaka.hideyuki@gmail.com+-- Stability : experimental+-- Portability: portable+--+-- Simple interface to pack and unpack MessagePack data.+--+--------------------------------------------------------------------++module Data.MessagePack (+ -- * Simple interface to pack and unpack msgpack binary+ pack+ , unpack+ , unpackEither+ , unpackValidate++ -- * Re-export modules+ -- $reexports+ , module X+ ) where++import Control.Applicative (Applicative)+import Control.Monad ((>=>))+import Control.Monad.Validate (MonadValidate, refute, runValidate)+import qualified Data.ByteString.Lazy as L+import qualified Data.Persist as P++import Data.MessagePack.Get as X+import Data.MessagePack.Put as X+import Data.MessagePack.Types as X+++-- | Pack a Haskell value to MessagePack binary.+pack :: MessagePack a => a -> L.ByteString+pack = L.fromStrict . P.runPut . P.put . toObject defaultConfig++-- | Unpack MessagePack binary to a Haskell value.+--+-- On failure, returns a list of error messages.+unpackValidate :: (MonadValidate DecodeError m, MessagePack a)+ => L.ByteString -> m a+unpackValidate bs = (eitherToM . P.runGet P.get . L.toStrict) bs >>= fromObjectWith defaultConfig+ where+ eitherToM (Left msg) = refute $ decodeError msg+ eitherToM (Right res) = return res+++unpackEither :: (MessagePack a)+ => L.ByteString -> Either DecodeError a+unpackEither = runValidate . unpackValidate++-- | Unpack MessagePack binary to a Haskell value. If it fails, it fails in the+-- Monad. In the Maybe monad, failure returns Nothing.+#if (MIN_VERSION_base(4,13,0))+unpack :: (Applicative m, Monad m, MonadFail m, MessagePack a)+#else+unpack :: (Applicative m, Monad m, MessagePack a)+#endif+ => L.ByteString -> m a+unpack = eitherToM . unpackEither+ where+ eitherToM (Left msgs) = fail $ show msgs+ eitherToM (Right res) = return res++instance P.Persist Object where+ get = getObject+ {-# INLINE get #-}++ put = putObject+ {-# INLINE put #-}
+ src/Data/MessagePack/Get.hs view
@@ -0,0 +1,111 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE Trustworthy #-}++--------------------------------------------------------------------+-- |+-- Module : Data.MessagePack.Get+-- Copyright : (c) Hideyuki Tanaka, 2009-2015+-- License : BSD3+--+-- Maintainer: tanaka.hideyuki@gmail.com+-- Stability : experimental+-- Portability: portable+--+-- MessagePack Deserializer using @Data.Persist@+--+--------------------------------------------------------------------++module Data.MessagePack.Get+ ( getObject+ ) where++import Control.Applicative ((<$), (<$>), (<*>), (<|>))+import Control.Monad (guard, replicateM)+import Data.Bits ((.&.))+import qualified Data.ByteString as S+import Data.Int (Int16, Int32, Int64, Int8)+import Data.Persist (Get, get, unBE)+import qualified Data.Persist as P+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import Data.Word (Word64, Word32, Word16, Word8)++import Data.MessagePack.Types (Object (..))++getObject :: Get Object+getObject = getWord8 >>= \case+ 0xC0 -> pure ObjectNil+ 0xC2 -> pure $ ObjectBool False+ 0xC3 -> pure $ ObjectBool True+ c | c .&. 0xE0 == 0xE0 -> pure $ ObjectInt $ fromIntegral (fromIntegral c :: Int8)+ 0xD0 -> ObjectInt . fromIntegral <$> getInt8+ 0xD1 -> ObjectInt . fromIntegral <$> getInt16be+ 0xD2 -> ObjectInt . fromIntegral <$> getInt32be+ 0xD3 -> ObjectInt . fromIntegral <$> getInt64be+ c | c .&. 0x80 == 0x00 -> pure $ ObjectWord $ fromIntegral c+ 0xCC -> ObjectWord . fromIntegral <$> getWord8+ 0xCD -> ObjectWord . fromIntegral <$> getWord16be+ 0xCE -> ObjectWord . fromIntegral <$> getWord32be+ 0xCF -> ObjectWord . fromIntegral <$> getWord64be+ 0xCA -> ObjectFloat <$> getFloat32be+ 0xCB -> ObjectDouble <$> getFloat64be+ t | t .&. 0xE0 == 0xA0 -> let len = fromIntegral $ t .&. 0x1F in ObjectStr <$> (getByteString len >>= decodeStr)+ 0xD9 -> ObjectStr <$> (fromIntegral <$> getWord8 >>= getByteString >>= decodeStr)+ 0xDA -> ObjectStr <$> (fromIntegral <$> getWord16be >>= getByteString >>= decodeStr)+ 0xDB -> ObjectStr <$> (fromIntegral <$> getWord32be >>= getByteString >>= decodeStr)+ 0xC4 -> ObjectBin <$> (fromIntegral <$> getWord8 >>= getByteString)+ 0xC5 -> ObjectBin <$> (fromIntegral <$> getWord16be >>= getByteString)+ 0xC6 -> ObjectBin <$> (fromIntegral <$> getWord32be >>= getByteString)+ t | t .&. 0xF0 == 0x90 -> let len = fromIntegral $ t .&. 0x0F in ObjectArray <$> replicateM len getObject+ 0xDC -> fromIntegral <$> getWord16be >>= \len -> ObjectArray <$> replicateM len getObject+ 0xDD -> fromIntegral <$> getWord32be >>= \len -> ObjectArray <$> replicateM len getObject+ t | t .&. 0xF0 == 0x80 -> let len = fromIntegral $ t .&. 0x0F in ObjectMap <$> replicateM len ((,) <$> getObject <*> getObject)+ 0xDE -> fromIntegral <$> getWord16be >>= \len -> ObjectMap <$> replicateM len ((,) <$> getObject <*> getObject)+ 0xDF -> fromIntegral <$> getWord32be >>= \len -> ObjectMap <$> replicateM len ((,) <$> getObject <*> getObject)+ 0xD4 -> ObjectExt <$> getWord8 <*> getByteString 1+ 0xD5 -> ObjectExt <$> getWord8 <*> getByteString 2+ 0xD6 -> ObjectExt <$> getWord8 <*> getByteString 4+ 0xD7 -> ObjectExt <$> getWord8 <*> getByteString 8+ 0xD8 -> ObjectExt <$> getWord8 <*> getByteString 16+ 0xC7 -> fromIntegral <$> getWord8 >>= \len -> ObjectExt <$> getWord8 <*> getByteString len+ 0xC8 -> fromIntegral <$> getWord16be >>= \len -> ObjectExt <$> getWord8 <*> getByteString len+ 0xC9 -> fromIntegral <$> getWord32be >>= \len -> ObjectExt <$> getWord8 <*> getByteString len+ _ -> fail "Data.MessagePack.Get.getObject: Encountered invalid byte"++ where+ decodeStr bs = case T.decodeUtf8' bs of+ Left _ -> fail "Data.MessagePack.Get.getObject: cannot decode bytestring to text"+ Right v -> pure v++getWord8 :: Get Word8+getWord8 = get++getWord16be :: Get Word16+getWord16be = unBE <$> get++getWord32be :: Get Word32+getWord32be = unBE <$> get++getWord64be :: Get Word64+getWord64be = unBE <$> get++getFloat32be :: Get Float+getFloat32be = unBE <$> get++getFloat64be :: Get Double+getFloat64be = unBE <$> get++getByteString :: Int -> Get S.ByteString+getByteString = P.getByteString++getInt8 :: Get Int8+getInt8 = fromIntegral <$> getWord8++getInt16be :: Get Int16+getInt16be = fromIntegral <$> getWord16be++getInt32be :: Get Int32+getInt32be = fromIntegral <$> getWord32be++getInt64be :: Get Int64+getInt64be = fromIntegral <$> getWord64be
+ src/Data/MessagePack/Put.hs view
@@ -0,0 +1,192 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE Trustworthy #-}+--------------------------------------------------------------------+-- |+-- Module : Data.MessagePack.Put+-- Copyright : (c) Hideyuki Tanaka, 2009-2015+-- License : BSD3+--+-- Maintainer: tanaka.hideyuki@gmail.com+-- Stability : experimental+-- Portability: portable+--+-- MessagePack Serializer using @Data.Persist@+--+--------------------------------------------------------------------++module Data.MessagePack.Put+ ( putObject+ , putNil+ , putBool+ , putInt+ , putWord+ , putFloat+ , putDouble+ , putStr+ , putBin+ , putArray+ , putMap+ , putExt+ ) where++import Data.Bits ((.|.))+import qualified Data.ByteString as S+import Data.Int (Int64)+import Data.Persist (put)+import qualified Data.Persist as P+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import Data.Word (Word64, Word32, Word16, Word8)++import Prelude hiding (putStr)++import Data.MessagePack.Types (Object (..))++type Put = P.Put ()++putWord8 :: Word8 -> Put+putWord8 = put++putWord16be :: Word16 -> Put+putWord16be = put . P.BigEndian++putWord32be :: Word32 -> Put+putWord32be = put . P.BigEndian++putWord64be :: Word64 -> Put+putWord64be = put . P.BigEndian++putFloat32be :: Float -> Put+putFloat32be = put . P.BigEndian++putFloat64be :: Double -> Put+putFloat64be = put . P.BigEndian++putByteString :: S.ByteString -> Put+putByteString = P.putByteString++putObject :: Object -> Put+putObject = \case+ ObjectNil -> putNil+ ObjectBool b -> putBool b+ ObjectInt n -> putInt n+ ObjectWord n -> putWord n+ ObjectFloat f -> putFloat f+ ObjectDouble d -> putDouble d+ ObjectStr t -> putStr t+ ObjectBin b -> putBin b+ ObjectArray a -> putArray putObject a+ ObjectMap m -> putMap putObject putObject m+ ObjectExt b r -> putExt b r++putNil :: Put+putNil = putWord8 0xC0++putBool :: Bool -> Put+putBool False = putWord8 0xC2+putBool True = putWord8 0xC3++putInt :: Int64 -> Put+putInt n+ | -0x20 <= n && n < 0x80 =+ putWord8 (fromIntegral n)+ | 0 <= n && n < 0x100 =+ putWord8 0xCC >> putWord8 (fromIntegral n)+ | 0 <= n && n < 0x10000 =+ putWord8 0xCD >> putWord16be (fromIntegral n)+ | 0 <= n && n < 0x100000000 =+ putWord8 0xCE >> putWord32be (fromIntegral n)+ | 0 <= n =+ putWord8 0xCF >> putWord64be (fromIntegral n)+ | -0x80 <= n =+ putWord8 0xD0 >> putWord8 (fromIntegral n)+ | -0x8000 <= n =+ putWord8 0xD1 >> putWord16be (fromIntegral n)+ | -0x80000000 <= n =+ putWord8 0xD2 >> putWord32be (fromIntegral n)+ | otherwise =+ putWord8 0xD3 >> putWord64be (fromIntegral n)++putWord :: Word64 -> Put+putWord n+ | n < 0x80 =+ putWord8 (fromIntegral n)+ | n < 0x100 =+ putWord8 0xCC >> putWord8 (fromIntegral n)+ | n < 0x10000 =+ putWord8 0xCD >> putWord16be (fromIntegral n)+ | n < 0x100000000 =+ putWord8 0xCE >> putWord32be (fromIntegral n)+ | otherwise =+ putWord8 0xCF >> putWord64be n++putFloat :: Float -> Put+putFloat f = do+ putWord8 0xCA+ putFloat32be f++putDouble :: Double -> Put+putDouble d = do+ putWord8 0xCB+ putFloat64be d++putStr :: T.Text -> Put+putStr t = do+ let bs = T.encodeUtf8 t+ case S.length bs of+ len | len <= 31 ->+ putWord8 $ 0xA0 .|. fromIntegral len+ | len < 0x100 ->+ putWord8 0xD9 >> putWord8 (fromIntegral len)+ | len < 0x10000 ->+ putWord8 0xDA >> putWord16be (fromIntegral len)+ | otherwise ->+ putWord8 0xDB >> putWord32be (fromIntegral len)+ putByteString bs++putBin :: S.ByteString -> Put+putBin bs = do+ case S.length bs of+ len | len < 0x100 ->+ putWord8 0xC4 >> putWord8 (fromIntegral len)+ | len < 0x10000 ->+ putWord8 0xC5 >> putWord16be (fromIntegral len)+ | otherwise ->+ putWord8 0xC6 >> putWord32be (fromIntegral len)+ putByteString bs++putArray :: (a -> Put) -> [a] -> Put+putArray p xs = do+ case length xs of+ len | len <= 15 ->+ putWord8 $ 0x90 .|. fromIntegral len+ | len < 0x10000 ->+ putWord8 0xDC >> putWord16be (fromIntegral len)+ | otherwise ->+ putWord8 0xDD >> putWord32be (fromIntegral len)+ mapM_ p xs++putMap :: (a -> Put) -> (b -> Put) -> [(a, b)] -> Put+putMap p q xs = do+ case length xs of+ len | len <= 15 ->+ putWord8 $ 0x80 .|. fromIntegral len+ | len < 0x10000 ->+ putWord8 0xDE >> putWord16be (fromIntegral len)+ | otherwise ->+ putWord8 0xDF >> putWord32be (fromIntegral len)+ mapM_ (\(a, b) -> p a >> q b) xs++putExt :: Word8 -> S.ByteString -> Put+putExt typ dat = do+ case S.length dat of+ 1 -> putWord8 0xD4+ 2 -> putWord8 0xD5+ 4 -> putWord8 0xD6+ 8 -> putWord8 0xD7+ 16 -> putWord8 0xD8+ len | len < 0x100 -> putWord8 0xC7 >> putWord8 (fromIntegral len)+ | len < 0x10000 -> putWord8 0xC8 >> putWord16be (fromIntegral len)+ | otherwise -> putWord8 0xC9 >> putWord32be (fromIntegral len)+ putWord8 typ+ putByteString dat
+ test/Data/MessagePackSpec.hs view
@@ -0,0 +1,349 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StrictData #-}+{-# LANGUAGE Trustworthy #-}+module Data.MessagePackSpec where++import Test.Hspec+import Test.QuickCheck+import qualified Test.QuickCheck.Gen as Gen++import Control.Applicative ((<$>), (<*>))+import qualified Data.ByteString.Char8 as S+import qualified Data.ByteString.Lazy as L8+import qualified Data.ByteString.Lazy.Char8 as L+import qualified Data.HashMap.Strict as HashMap+import Data.Hashable (Hashable)+import Data.Int (Int16, Int32, Int64, Int8)+import qualified Data.IntMap as IntMap+import qualified Data.Map as Map+import qualified Data.Maybe as Maybe+import qualified Data.Result as R+import qualified Data.Text.Lazy as LT+import qualified Data.Vector as V+import qualified Data.Vector.Storable as VS+import qualified Data.Vector.Unboxed as VU+import Data.Word (Word, Word16, Word32, Word64,+ Word8)+import GHC.Generics (Generic)++import Data.MessagePack+++data Unit = Unit+ deriving (Eq, Show, Generic)++instance MessagePack Unit+++data TyConArgs = TyConArgs Int Int Int+ deriving (Eq, Show, Generic)++instance MessagePack TyConArgs+++data Record = Record+ { recordField1 :: Int+ , recordField2 :: Double+ , recordField3 :: String+ }+ deriving (Eq, Show, Generic)++instance MessagePack Record+++data Foo+ = Foo1+ | Foo2 Int+ | Foo3 Int+ | Foo4 Int+ | Foo5 Int+ | Foo6 { unFoo3 :: Int }+ | Foo7 Int+ | Foo8 Int Int+ | Foo9 Int Int Int+ deriving (Eq, Show, Generic)++instance MessagePack Foo++instance Arbitrary Foo where+ arbitrary = Gen.oneof+ [ return Foo1+ , Foo2 <$> arbitrary+ , Foo3 <$> arbitrary+ , Foo4 <$> arbitrary+ , Foo5 <$> arbitrary+ , Foo6 <$> arbitrary+ , Foo7 <$> arbitrary+ , Foo8 <$> arbitrary <*> arbitrary+ , Foo9 <$> arbitrary <*> arbitrary <*> arbitrary+ ]+++instance (Hashable k, Ord k, Eq k, Arbitrary k, Arbitrary v)+ => Arbitrary (HashMap.HashMap k v) where+ arbitrary = HashMap.fromList . Map.assocs <$> arbitrary++instance Arbitrary a => Arbitrary (V.Vector a) where+ arbitrary = V.fromList <$> arbitrary++instance (Arbitrary a, VS.Storable a) => Arbitrary (VS.Vector a) where+ arbitrary = VS.fromList <$> arbitrary++instance (Arbitrary a, VU.Unbox a) => Arbitrary (VU.Vector a) where+ arbitrary = VU.fromList <$> arbitrary++instance Arbitrary S.ByteString where+ arbitrary = S.pack <$> arbitrary++instance Arbitrary L.ByteString where+ arbitrary = L.pack <$> arbitrary++instance Arbitrary LT.Text where+ arbitrary = LT.pack <$> arbitrary++mid :: MessagePack a => a -> a+mid = Maybe.fromJust . unpack . pack+++intMid :: Int64 -> Int64+intMid = mid+++coerce :: (MessagePack a, MessagePack b) => a -> Maybe b+coerce = unpack . pack+++checkMessage :: Show a => R.Result a -> Expectation+checkMessage (R.Success res) =+ expectationFailure $ "unexpected success: " ++ show res+checkMessage (R.Failure msg) =+ msg `shouldContain` "invalid encoding for "+++spec :: Spec+spec = do+ describe "unpack" $+ it "does not throw exceptions on arbitrary data" $+ property $ \bs ->+ case unpack bs of+ Just "" -> return () :: IO ()+ _ -> return () :: IO ()++ describe "Assoc" $ do+ it "supports read/show" $+ property $ \(a :: Assoc [(Int, Int)]) ->+ read (show a) `shouldBe` a++ it "inherits ordering from its contained type" $+ property $ \(a :: Assoc Int) b ->+ (unAssoc a < unAssoc b) `shouldBe` (a < b)++ describe "failures" $+ it "should contain the same start of the failure message for all types" $ do+ checkMessage (unpack (pack $ ObjectInt (-1)) :: R.Result Foo)+ checkMessage (unpack (pack [ObjectInt (-1), ObjectInt 0]) :: R.Result Foo)+ checkMessage (unpack (pack $ ObjectArray []) :: R.Result TyConArgs)+ checkMessage (unpack (pack $ ObjectArray []) :: R.Result Record)+ checkMessage (unpack (pack [0 :: Int, 1, 2, 3]) :: R.Result Record)+ checkMessage (unpack (pack "") :: R.Result Unit)+ checkMessage (unpack (pack "") :: R.Result TyConArgs)+ checkMessage (unpack (pack "") :: R.Result Record)+ checkMessage (unpack (pack "") :: R.Result ())+ checkMessage (unpack (pack ()) :: R.Result Int)+ checkMessage (unpack (pack ()) :: R.Result Bool)+ checkMessage (unpack (pack ()) :: R.Result Float)+ checkMessage (unpack (pack ()) :: R.Result Double)+ checkMessage (unpack (pack ()) :: R.Result S.ByteString)+ checkMessage (unpack (pack ()) :: R.Result LT.Text)+ checkMessage (unpack (pack "") :: R.Result [String])+ checkMessage (unpack (pack ()) :: R.Result (V.Vector Int))+ checkMessage (unpack (pack ()) :: R.Result (VS.Vector Int))+ checkMessage (unpack (pack ()) :: R.Result (VU.Vector Int))+ checkMessage (unpack (pack "") :: R.Result (Assoc [(Int, Int)]))+ checkMessage (unpack (pack ()) :: R.Result (Int, Int))+ checkMessage (unpack (pack ()) :: R.Result (Int, Int, Int))+ checkMessage (unpack (pack ()) :: R.Result (Int, Int, Int, Int))+ checkMessage (unpack (pack ()) :: R.Result (Int, Int, Int, Int, Int))+ checkMessage (unpack (pack ()) :: R.Result (Int, Int, Int, Int, Int, Int))+ checkMessage (unpack (pack ()) :: R.Result (Int, Int, Int, Int, Int, Int, Int))+ checkMessage (unpack (pack ()) :: R.Result (Int, Int, Int, Int, Int, Int, Int, Int))+ checkMessage (unpack (pack ()) :: R.Result (Int, Int, Int, Int, Int, Int, Int, Int, Int))++ describe "type coercion" $ do+ it "bool<-int" $+ property $ \(a :: Int) -> coerce a `shouldBe` (Nothing :: Maybe Bool)++ it "int<-bool" $+ property $ \(a :: Bool) -> coerce a `shouldBe` (Nothing :: Maybe Int)++ it "float<-int" $+ property $ \(a :: Int) -> coerce a `shouldBe` Just (fromIntegral a :: Float)+ it "float<-double" $+ property $ \(a :: Double) -> coerce a `shouldBe` Just (realToFrac a :: Float)+ it "float<-string" $+ property $ \(a :: String) -> coerce a `shouldBe` (Nothing :: Maybe Float)++ it "double<-int" $+ property $ \(a :: Int) -> coerce a `shouldBe` Just (fromIntegral a :: Double)+ it "double<-float" $+ property $ \(a :: Float) -> coerce a `shouldBe` Just (realToFrac a :: Double)+ it "double<-string" $+ property $ \(a :: String) -> coerce a `shouldBe` (Nothing :: Maybe Double)++ it "bin<-string" $+ property $ \(a :: S.ByteString) -> coerce a `shouldBe` (Nothing :: Maybe String)++ it "string<-bin" $+ property $ \(a :: String) -> coerce a `shouldBe` (Nothing :: Maybe S.ByteString)++ describe "Identity Properties" $ do+ let sizes = [0xf, 0x10, 0x1f, 0x20, 0xff, 0x100, 0xffff, 0x10000]++ it "unit encoding" $+ Unit `shouldBe` mid Unit++ it "map encodings" $ do+ let rt n = let a = IntMap.fromList [(x, -x) | x <- [0..n]] in a `shouldBe` mid a+ mapM_ rt sizes++ it "list encodings" $ do+ let rt n = let a = replicate n "hello" in a `shouldBe` mid a+ mapM_ rt sizes++ it "vector encodings" $ do+ let rt n = let a = V.fromList [0..n] in a `shouldBe` mid a+ mapM_ rt sizes++ it "storable-vector encodings" $ do+ let rt n = let a = VS.fromList [0..n] in a `shouldBe` mid a+ mapM_ rt sizes++ it "unboxed-vector encodings" $ do+ let rt n = let a = VU.fromList [0..n] in a `shouldBe` mid a+ mapM_ rt sizes++ it "string encodings" $ do+ let rt n = let a = replicate n 'a' in a `shouldBe` mid a+ mapM_ rt sizes++ it "bytestring encodings" $ do+ let rt n = let a = S.pack $ replicate n 'a' in a `shouldBe` mid a+ mapM_ rt sizes++ it "ext encodings" $ do+ let rt n = let a = ObjectExt 0 $ S.pack $ replicate n 'a' in a `shouldBe` mid a+ mapM_ rt [0..20]+ mapM_ rt sizes++ it "int encodings" $ do+ (-0x7fffffffffffffff) `shouldBe` intMid (-0x7fffffffffffffff)+ (-0x80000000) `shouldBe` intMid (-0x80000000)+ (-0x7fffffff) `shouldBe` intMid (-0x7fffffff)+ (-0x8000) `shouldBe` intMid (-0x8000)+ (-0x7fff) `shouldBe` intMid (-0x7fff)+ (-1) `shouldBe` intMid (-1)+ 0 `shouldBe` intMid 0+ 1 `shouldBe` intMid 1+ 0x7fff `shouldBe` intMid 0x7fff+ 0x8000 `shouldBe` intMid 0x8000+ 0x7fffffff `shouldBe` intMid 0x7fffffff+ 0x80000000 `shouldBe` intMid 0x80000000+ 0x7fffffffffffffff `shouldBe` intMid 0x7fffffffffffffff++ it "int" $ property $ \(a :: Int ) -> a `shouldBe` mid a+ it "int8" $ property $ \(a :: Int8 ) -> a `shouldBe` mid a+ it "int16" $ property $ \(a :: Int16 ) -> a `shouldBe` mid a+ it "int32" $ property $ \(a :: Int32 ) -> a `shouldBe` mid a+ it "int64" $ property $ \(a :: Int64 ) -> a `shouldBe` mid a+ it "word" $ property $ \(a :: Word ) -> a `shouldBe` mid a+ it "word8" $ property $ \(a :: Word8 ) -> a `shouldBe` mid a+ it "word16" $ property $ \(a :: Word16) -> a `shouldBe` mid a+ it "word32" $ property $ \(a :: Word32) -> a `shouldBe` mid a+ it "word64" $ property $ \(a :: Word64) -> a `shouldBe` mid a++ it "ext" $+ property $ \(n, a) -> ObjectExt n a `shouldBe` mid (ObjectExt n a)+ it "nil" $+ property $ \(a :: ()) -> a `shouldBe` mid a+ it "bool" $+ property $ \(a :: Bool) -> a `shouldBe` mid a+ it "float" $+ property $ \(a :: Float) -> a `shouldBe` mid a+ it "double" $+ property $ \(a :: Double) -> a `shouldBe` mid a+ it "string" $+ property $ \(a :: String) -> a `shouldBe` mid a+ it "bytestring" $+ property $ \(a :: S.ByteString) -> a `shouldBe` mid a+ it "lazy-bytestring" $+ property $ \(a :: L.ByteString) -> a `shouldBe` mid a+ it "lazy-text" $+ property $ \(a :: LT.Text) -> a `shouldBe` mid a+ it "[int]" $+ property $ \(a :: [Int]) -> a `shouldBe` mid a+ it "vector int" $+ property $ \(a :: V.Vector Int) -> a `shouldBe` mid a+ it "storable-vector int" $+ property $ \(a :: VS.Vector Int) -> a `shouldBe` mid a+ it "unboxed-vector int" $+ property $ \(a :: VU.Vector Int) -> a `shouldBe` mid a+ it "[string]" $+ property $ \(a :: [String]) -> a `shouldBe` mid a+ it "(int, int)" $+ property $ \(a :: (Int, Int)) -> a `shouldBe` mid a+ it "(int, int, int)" $+ property $ \(a :: (Int, Int, Int)) -> a `shouldBe` mid a+ it "(int, int, int, int)" $+ property $ \(a :: (Int, Int, Int, Int)) -> a `shouldBe` mid a+ it "(int, int, int, int, int)" $+ property $ \(a :: (Int, Int, Int, Int, Int)) -> a `shouldBe` mid a+ it "(int, int, int, int, int, int)" $+ property $ \(a :: (Int, Int, Int, Int, Int, Int)) -> a `shouldBe` mid a+ it "(int, int, int, int, int, int, int)" $+ property $ \(a :: (Int, Int, Int, Int, Int, Int, Int)) -> a `shouldBe` mid a+ it "(int, int, int, int, int, int, int, int)" $+ property $ \(a :: (Int, Int, Int, Int, Int, Int, Int, Int)) -> a `shouldBe` mid a+ it "(int, int, int, int, int, int, int, int, int)" $+ property $ \(a :: (Int, Int, Int, Int, Int, Int, Int, Int, Int)) -> a `shouldBe` mid a+ it "[(int, double)]" $+ property $ \(a :: [(Int, Double)]) -> a `shouldBe` mid a+ it "[(string, string)]" $+ property $ \(a :: [(String, String)]) -> a `shouldBe` mid a+ it "Assoc [(string, int)]" $+ property $ \(a :: Assoc [(String, Int)]) -> a `shouldBe` mid a+ it "Map String Int" $+ property $ \(a :: Map.Map String Int) -> a `shouldBe` mid a+ it "IntMap Int" $+ property $ \(a :: IntMap.IntMap Int) -> a `shouldBe` mid a+ it "HashMap String Int" $+ property $ \(a :: HashMap.HashMap String Int) -> a `shouldBe` mid a++ it "generics" $+ property $ \(a :: Foo) -> a `shouldBe` mid a+ it "arbitrary message" $+ property $ \(a :: Object) -> a `shouldBe` mid a++ describe "encoding validation" $ do+ it "word64 2^64-1" $+ pack (0xffffffffffffffff :: Word64) `shouldBe` L8.pack [0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]++ it "decodes empty array as ()" $+ unpack (pack ([] :: [Int])) `shouldBe` Just ()++ describe "show" $ do+ it "Foo" $ do+ show (toObject defaultConfig Foo1) `shouldBe` "ObjectWord 0"+ show (toObject defaultConfig $ Foo3 3) `shouldBe` "ObjectArray [ObjectWord 2,ObjectWord 3]"+ show (toObject defaultConfig $ Foo3 (-3)) `shouldBe` "ObjectArray [ObjectWord 2,ObjectInt (-3)]"+ show (toObject defaultConfig $ Foo8 3 5) `shouldBe` "ObjectArray [ObjectWord 7,ObjectArray [ObjectWord 3,ObjectWord 5]]"+ show (toObject defaultConfig $ Foo8 (-3) (-5)) `shouldBe` "ObjectArray [ObjectWord 7,ObjectArray [ObjectInt (-3),ObjectInt (-5)]]"+ show (toObject defaultConfig $ Foo9 3 5 7) `shouldBe` "ObjectArray [ObjectWord 8,ObjectArray [ObjectWord 3,ObjectWord 5,ObjectWord 7]]"+ show (toObject defaultConfig $ Foo9 (-3) (-5) 7) `shouldBe` "ObjectArray [ObjectWord 8,ObjectArray [ObjectInt (-3),ObjectInt (-5),ObjectWord 7]]"++ it "TyConArgs" $+ show (toObject defaultConfig $ TyConArgs 3 5 7) `shouldBe` "ObjectArray [ObjectWord 3,ObjectWord 5,ObjectWord 7]"++ it "Record" $+ show (toObject defaultConfig $ Record 3 5 "7") `shouldBe` "ObjectArray [ObjectWord 3,ObjectDouble 5.0,ObjectStr \"7\"]"
+ test/Data/Result.hs view
@@ -0,0 +1,30 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE Safe #-}+{-# LANGUAGE StrictData #-}+module Data.Result+ ( Result (..)+ ) where++import Control.Applicative (Applicative (..), (<$>), (<*>))+import Control.Monad.Fail (MonadFail (..))++data Result a+ = Success a+ | Failure String+ deriving (Read, Show, Eq, Functor)++instance Applicative Result where+ pure = Success++ Success f <*> x = fmap f x+ Failure msg <*> _ = Failure msg++instance Monad Result where+ return = Success++ Success x >>= f = f x+ Failure msg >>= _ = Failure msg++instance MonadFail Result where+ fail = Failure
+ test/testsuite.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ tools/msgpack-parser.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE Trustworthy #-}+-- | A MessagePack parser.+--+-- Example usage:+-- $ echo -ne "\x94\x01\xa1\x32\xa1\x33\xa4\x50\x6f\x6f\x66" | ./msgpack-parser+-- or+-- $ echo 'ObjectArray [ObjectInt 97, ObjectStr "test", ObjectBool True]' | ./msgpack-parser+--+-- This tool performs two symmetrical functions:+-- 1. It can decode binary data representing a+-- Data.MessagePack.Object into a human-readable string.+-- 2. It can do the reverse: encode a human-readable string into+-- a binary representation of Data.MessagePack.Object.+--+-- No flags are required as it automatically detects which of these+-- two functions it should perform. This is done by first assuming+-- the input is human readable. If it fails to parse it, it then+-- considers it as binary data.+--+-- Therefore, given a valid input, the tool has the following property+-- $ ./msgpack-parser < input.bin | ./msgpack-parser+-- will output back the contents of input.bin.+--+-- In case the input is impossible to parse, nothing is output.+--+-- Known bugs:+-- - If no input is given, the tool exits with+-- "Data.Binary.Get.runGet at position 0: not enough bytes"+-- - The tool does not check that all the input is parsed.+-- Therefore, "abc" is interpreted as just "ObjectInt 97".+--+module Main where++import Control.Applicative ((<$>), (<|>))+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Lazy.Char8 as L8+import Data.Maybe (fromMaybe)+import Data.MessagePack (Object, pack, unpack)+import Text.Groom (groom)+import Text.Read (readMaybe)+++parse :: L.ByteString -> L.ByteString+parse str = fromMaybe L.empty $+ pack <$> (readMaybe $ L8.unpack str :: Maybe Object)+ <|>+ L8.pack . flip (++) "\n" . groom <$> (unpack str :: Maybe Object)+++main :: IO ()+main = parse <$> L.getContents >>= L.putStr