binary 0.6.4.0 → 0.10.0.0
raw patch · 33 files changed
Files
- .gitignore +0/−14
- .hgignore +0/−5
- README +0/−77
- README.md +93/−0
- TODO +0/−28
- benchmarks/Builder.hs +8/−9
- benchmarks/Cabal24.hs +360/−0
- benchmarks/GenericsBench.hs +52/−0
- benchmarks/GenericsBenchCache.hs +40/−0
- benchmarks/GenericsBenchTypes.hs +35/−0
- benchmarks/Get.hs +246/−108
- benchmarks/Makefile +0/−34
- benchmarks/Put.hs +178/−0
- binary.cabal +206/−42
- changelog.md +183/−0
- index.html +0/−161
- src/Data/Binary.hs +64/−55
- src/Data/Binary/Builder.hs +226/−9
- src/Data/Binary/Builder/Base.hs +0/−514
- src/Data/Binary/Builder/Internal.hs +0/−28
- src/Data/Binary/Class.hs +579/−99
- src/Data/Binary/FloatCast.hs +45/−0
- src/Data/Binary/Generic.hs +47/−24
- src/Data/Binary/Get.hs +316/−90
- src/Data/Binary/Get/Internal.hs +192/−65
- src/Data/Binary/Internal.hs +15/−0
- src/Data/Binary/Put.hs +200/−15
- tests/Action.hs +351/−51
- tests/Arbitrary.hs +9/−40
- tests/File.hs +43/−0
- tests/Makefile +0/−20
- tests/QC.hs +395/−104
- tools/derive/BinaryDerive.hs +1/−1
− .gitignore
@@ -1,14 +0,0 @@-*.hi-*.o-*.p_hi-*.prof-*.tix-.hpc/-/benchmarks/bench-/benchmarks/builder-/dist/*-/tests/qc-GNUmakefile-dist-boot-dist-install-ghc.mk
− .hgignore
@@ -1,5 +0,0 @@-^dist$-syntax: glob-.*.swp-*~-\#*
− README
@@ -1,77 +0,0 @@-- binary: efficient, pure binary serialisation using lazy ByteStrings---------------------------------------------------------------------------The 'binary' package provides Data.Binary, containing the Binary class,-and associated methods, for serialising values to and from lazy-ByteStrings. --A key feature of 'binary' is that the interface is both pure, and efficient.--The 'binary' package is portable to GHC and Hugs.--Building:-- runhaskell Setup.lhs configure- runhaskell Setup.lhs build- runhaskell Setup.lhs install--First:- import Data.Binary--and then write an instance of Binary for the type you wish to serialise.-More information in the haddock documentation.--Deriving:--It is possible to mechanically derive new instances of Binary for your-types, if they support the Data and Typeable classes. A script is-provided in tools/derive. Here's an example of its use.-- $ cd binary - $ cd tools/derive -- $ ghci -fglasgow-exts BinaryDerive.hs-- *BinaryDerive> :l Example.hs -- *Main> deriveM (undefined :: Exp)-- instance Binary Main.Exp where- put (ExpOr a b) = putWord8 0 >> put a >> put b- put (ExpAnd a b) = putWord8 1 >> put a >> put b- put (ExpEq a b) = putWord8 2 >> put a >> put b- put (ExpNEq a b) = putWord8 3 >> put a >> put b- put (ExpAdd a b) = putWord8 4 >> put a >> put b- put (ExpSub a b) = putWord8 5 >> put a >> put b- put (ExpVar a) = putWord8 6 >> put a- put (ExpInt a) = putWord8 7 >> put a- get = do- tag_ <- getWord8- case tag_ of- 0 -> get >>= \a -> get >>= \b -> return (ExpOr a b)- 1 -> get >>= \a -> get >>= \b -> return (ExpAnd a b)- 2 -> get >>= \a -> get >>= \b -> return (ExpEq a b)- 3 -> get >>= \a -> get >>= \b -> return (ExpNEq a b)- 4 -> get >>= \a -> get >>= \b -> return (ExpAdd a b)- 5 -> get >>= \a -> get >>= \b -> return (ExpSub a b)- 6 -> get >>= \a -> return (ExpVar a)- 7 -> get >>= \a -> return (ExpInt a)- _ -> fail "no decoding"--Contributors:-- Lennart Kolmodin- Duncan Coutts- Don Stewart- Spencer Janssen- David Himmelstrup- Björn Bringert- Ross Paterson- Einar Karttunen- John Meacham- Ulf Norell- Tomasz Zielonka- Stefan Karrmann- Bryan O'Sullivan- Florian Weimer
+ README.md view
@@ -0,0 +1,93 @@+# binary package #++[](https://hackage.haskell.org/package/binary) [](https://www.stackage.org/package/binary) [](http://travis-ci.org/kolmodin/binary)++*Efficient, pure binary serialisation using lazy ByteStrings.*++The ``binary`` package provides Data.Binary, containing the Binary class,+and associated methods, for serialising values to and from lazy+ByteStrings. +A key feature of ``binary`` is that the interface is both pure, and efficient.+The ``binary`` package is portable to GHC and Hugs.++## Installing binary from Hackage ##++``binary`` is part of The Glasgow Haskell Compiler (GHC) and therefore if you+have either GHC or [The Haskell Platform](http://www.haskell.org/platform/)+installed, you already have ``binary``.++More recent versions of ``binary`` than you might have installed may be+available. You can use ``cabal-install`` to install a later version from+[Hackage](http://hackage.haskell.org/package/binary).++ $ cabal update+ $ cabal install binary++## Building binary ##++``binary`` comes with both a test suite and a set of benchmarks.+While developing, you probably want to enable both.+Here's how to get the latest version of the repository, configure and build.++ $ git clone git@github.com:kolmodin/binary.git+ $ cd binary+ $ cabal update+ $ cabal configure --enable-tests --enable-benchmarks+ $ cabal build++Run the test suite.++ $ cabal test++## Using binary ##++First:++ import Data.Binary++and then write an instance of Binary for the type you wish to serialise.+An example doing exactly this can be found in the Data.Binary module.+You can also use the Data.Binary.Builder module to efficiently build+lazy bytestrings using the ``Builder`` monoid. Or, alternatively, the+Data.Binary.Get and Data.Binary.Put to serialize/deserialize using+the ``Get`` and ``Put`` monads.++More information in the haddock documentation.++## Deriving binary instances using GHC's Generic ##++Beginning with GHC 7.2, it is possible to use binary serialization without+writing any instance boilerplate code.++```haskell+{-# LANGUAGE DeriveGeneric #-}++import Data.Binary+import GHC.Generics (Generic)++data Foo = Foo deriving (Generic)++-- GHC will automatically fill out the instance+instance Binary Foo+```++## Contributors ##++* Lennart Kolmodin+* Duncan Coutts+* Don Stewart+* Spencer Janssen+* David Himmelstrup+* Björn Bringert+* Ross Paterson+* Einar Karttunen+* John Meacham+* Ulf Norell+* Tomasz Zielonka+* Stefan Karrmann+* Bryan O'Sullivan+* Bas van Dijk+* Florian Weimer++For a full list of contributors, see+[here](https://github.com/kolmodin/binary/graphs/contributors).
− TODO
@@ -1,28 +0,0 @@-layer handling:-- bit packing- state parameters- string pools-- reading structures from the end of a stream, seek/tell behaviour--seek based protocols are too hard. - hGetContents/ interleaving.--user requests:-- get remaining bytestring after a runGet-- some kind of lookahead, or restoring parsing state, or something with- equal functionality. make it another layer on top?-- getLazyByteString takes an Int, which in Haskell98 is only guarantied to- be 29 bits, ie. 512 mb.- maybe we should have a readN64 for allowing reading of larger stuff?- (which could be implemented with readN on 64bit machines)- reference: bringerts tar archive decoder would be limitid to 0.5GB- files, alt. 2GB in GHC--SYB-deriving--investigate the UArray instance, it does not seem to compile in GHC 6.4
benchmarks/Builder.hs view
@@ -6,24 +6,21 @@ module Main (main) where +#if ! MIN_VERSION_base(4,8,0)+import Data.Monoid (Monoid(mappend, mempty))+#endif+ import Control.DeepSeq import Control.Exception (evaluate)-import Control.Monad.Trans (liftIO)-import Criterion.Config-import Criterion.Main hiding (run)+import Criterion.Main import qualified Data.ByteString as S import qualified Data.ByteString.Char8 as C import qualified Data.ByteString.Lazy as L import Data.Char (ord)-import Data.Monoid (Monoid(mappend, mempty)) import Data.Word (Word8) import Data.Binary.Builder -#if __GLASGOW_HASKELL__ < 706-instance NFData S.ByteString-#endif- main :: IO () main = do evaluate $ rnf@@ -83,7 +80,7 @@ -- Write 100 short, length-prefixed ByteStrings. lengthPrefixedBS :: S.ByteString -> Builder-lengthPrefixedBS bs = loop 100+lengthPrefixedBS bs = loop (100 :: Int) where loop n | n `seq` False = undefined loop 0 = mempty loop n =@@ -192,7 +189,9 @@ ------------------------------------------------------------------------ -- Utilities +#if !MIN_VERSION_base(4,11,0) infixr 6 <> (<>) :: Monoid m => m -> m -> m (<>) = mappend+#endif
+ benchmarks/Cabal24.hs view
@@ -0,0 +1,360 @@+{-# LANGUAGE DeriveGeneric #-}++-- | This module contains type definitions copied from Cabal-1.24.2.0+-- to avoid a dependency on Cabal. Their contents for the benchmark are read+-- from a cache file using their 'Read' instance, see "GenericsBenchCache".+--+module Cabal24 where++import Data.Version (Version)+import GHC.Generics (Generic)+import Data.Map (Map)++data Benchmark = Benchmark {+ benchmarkName :: String,+ benchmarkInterface :: BenchmarkInterface,+ benchmarkBuildInfo :: BuildInfo,+ benchmarkEnabled :: Bool+ } deriving (Generic, Eq, Ord, Read, Show)++data BenchmarkInterface =+ BenchmarkExeV10 Version FilePath+ | BenchmarkUnsupported BenchmarkType+ deriving (Generic, Eq, Ord, Read, Show)++data BenchmarkType = BenchmarkTypeExe Version+ | BenchmarkTypeUnknown String Version+ deriving (Generic, Eq, Ord, Read, Show)++data BuildInfo = BuildInfo {+ buildable :: Bool,+ buildTools :: [Dependency],+ cppOptions :: [String],+ ccOptions :: [String],+ ldOptions :: [String],+ pkgconfigDepends :: [Dependency],+ frameworks :: [String],+ extraFrameworkDirs:: [String],+ cSources :: [FilePath],+ jsSources :: [FilePath],+ hsSourceDirs :: [FilePath],+ otherModules :: [ModuleName],+ defaultLanguage :: Maybe Language,+ otherLanguages :: [Language],+ defaultExtensions :: [Extension],+ otherExtensions :: [Extension],+ oldExtensions :: [Extension],+ extraLibs :: [String],+ extraGHCiLibs :: [String],+ extraLibDirs :: [String],+ includeDirs :: [FilePath],+ includes :: [FilePath],+ installIncludes :: [FilePath],+ options :: [(CompilerFlavor,[String])],+ profOptions :: [(CompilerFlavor,[String])],+ sharedOptions :: [(CompilerFlavor,[String])],+ customFieldsBI :: [(String,String)],+ targetBuildDepends :: [Dependency],+ targetBuildRenaming :: Map PackageName ModuleRenaming+ } deriving (Generic, Eq, Ord, Read, Show)++data BuildType+ = Simple+ | Configure+ | Make+ | Custom+ | UnknownBuildType String+ deriving (Generic, Eq, Ord, Read, Show)++data CompilerFlavor = GHC | GHCJS | NHC | YHC | Hugs | HBC | Helium+ | JHC | LHC | UHC+ | HaskellSuite String+ | OtherCompiler String+ deriving (Generic, Eq, Ord, Read, Show)++data Dependency = Dependency PackageName VersionRange+ deriving (Generic, Eq, Ord, Read, Show)++data Executable = Executable {+ exeName :: String,+ modulePath :: FilePath,+ buildInfo :: BuildInfo+ }+ deriving (Generic, Eq, Ord, Read, Show)++data Extension =+ EnableExtension KnownExtension+ | DisableExtension KnownExtension+ | UnknownExtension String+ deriving (Generic, Eq, Ord, Read, Show)++newtype FlagName = FlagName String+ deriving (Generic, Eq, Ord, Read, Show)++data KnownExtension =+ OverlappingInstances+ | UndecidableInstances+ | IncoherentInstances+ | DoRec+ | RecursiveDo+ | ParallelListComp+ | MultiParamTypeClasses+ | MonomorphismRestriction+ | FunctionalDependencies+ | Rank2Types+ | RankNTypes+ | PolymorphicComponents+ | ExistentialQuantification+ | ScopedTypeVariables+ | PatternSignatures+ | ImplicitParams+ | FlexibleContexts+ | FlexibleInstances+ | EmptyDataDecls+ | CPP+ | KindSignatures+ | BangPatterns+ | TypeSynonymInstances+ | TemplateHaskell+ | ForeignFunctionInterface+ | Arrows+ | Generics+ | ImplicitPrelude+ | NamedFieldPuns+ | PatternGuards+ | GeneralizedNewtypeDeriving+ | ExtensibleRecords+ | RestrictedTypeSynonyms+ | HereDocuments+ | MagicHash+ | TypeFamilies+ | StandaloneDeriving+ | UnicodeSyntax+ | UnliftedFFITypes+ | InterruptibleFFI+ | CApiFFI+ | LiberalTypeSynonyms+ | TypeOperators+ | RecordWildCards+ | RecordPuns+ | DisambiguateRecordFields+ | TraditionalRecordSyntax+ | OverloadedStrings+ | GADTs+ | GADTSyntax+ | MonoPatBinds+ | RelaxedPolyRec+ | ExtendedDefaultRules+ | UnboxedTuples+ | DeriveDataTypeable+ | DeriveGeneric+ | DefaultSignatures+ | InstanceSigs+ | ConstrainedClassMethods+ | PackageImports+ | ImpredicativeTypes+ | NewQualifiedOperators+ | PostfixOperators+ | QuasiQuotes+ | TransformListComp+ | MonadComprehensions+ | ViewPatterns+ | XmlSyntax+ | RegularPatterns+ | TupleSections+ | GHCForeignImportPrim+ | NPlusKPatterns+ | DoAndIfThenElse+ | MultiWayIf+ | LambdaCase+ | RebindableSyntax+ | ExplicitForAll+ | DatatypeContexts+ | MonoLocalBinds+ | DeriveFunctor+ | DeriveTraversable+ | DeriveFoldable+ | NondecreasingIndentation+ | SafeImports+ | Safe+ | Trustworthy+ | Unsafe+ | ConstraintKinds+ | PolyKinds+ | DataKinds+ | ParallelArrays+ | RoleAnnotations+ | OverloadedLists+ | EmptyCase+ | AutoDeriveTypeable+ | NegativeLiterals+ | BinaryLiterals+ | NumDecimals+ | NullaryTypeClasses+ | ExplicitNamespaces+ | AllowAmbiguousTypes+ | JavaScriptFFI+ | PatternSynonyms+ | PartialTypeSignatures+ | NamedWildCards+ | DeriveAnyClass+ | DeriveLift+ | StaticPointers+ | StrictData+ | Strict+ | ApplicativeDo+ | DuplicateRecordFields+ | TypeApplications+ | TypeInType+ | UndecidableSuperClasses+ | MonadFailDesugaring+ | TemplateHaskellQuotes+ | OverloadedLabels+ deriving (Generic, Eq, Ord, Read, Show)++data Language =+ Haskell98+ | Haskell2010+ | UnknownLanguage String+ deriving (Generic, Eq, Ord, Read, Show)++data Library = Library {+ exposedModules :: [ModuleName],+ reexportedModules :: [ModuleReexport],+ requiredSignatures:: [ModuleName],+ exposedSignatures:: [ModuleName],+ libExposed :: Bool,+ libBuildInfo :: BuildInfo+ }+ deriving (Generic, Eq, Ord, Read, Show)++data License =+ GPL (Maybe Version)+ | AGPL (Maybe Version)+ | LGPL (Maybe Version)+ | BSD2+ | BSD3+ | BSD4+ | MIT+ | ISC+ | MPL Version+ | Apache (Maybe Version)+ | PublicDomain+ | AllRightsReserved+ | UnspecifiedLicense+ | OtherLicense+ | UnknownLicense String+ deriving (Generic, Eq, Ord, Read, Show)++newtype ModuleName = ModuleName [String]+ deriving (Generic, Eq, Ord, Read, Show)++data ModuleReexport = ModuleReexport {+ moduleReexportOriginalPackage :: Maybe PackageName,+ moduleReexportOriginalName :: ModuleName,+ moduleReexportName :: ModuleName+ } deriving (Generic, Eq, Ord, Read, Show)++data ModuleRenaming = ModuleRenaming Bool [(ModuleName, ModuleName)]+ deriving (Generic, Eq, Ord, Read, Show)++data PackageDescription+ = PackageDescription {+ package :: PackageIdentifier,+ license :: License,+ licenseFiles :: [FilePath],+ copyright :: String,+ maintainer :: String,+ author :: String,+ stability :: String,+ testedWith :: [(CompilerFlavor,VersionRange)],+ homepage :: String,+ pkgUrl :: String,+ bugReports :: String,+ sourceRepos :: [SourceRepo],+ synopsis :: String,+ description :: String,+ category :: String,+ customFieldsPD :: [(String,String)],+ buildDepends :: [Dependency],+ specVersionRaw :: Either Version VersionRange,+ buildType :: Maybe BuildType,+ setupBuildInfo :: Maybe SetupBuildInfo,+ library :: Maybe Library,+ executables :: [Executable],+ testSuites :: [TestSuite],+ benchmarks :: [Benchmark],+ dataFiles :: [FilePath],+ dataDir :: FilePath,+ extraSrcFiles :: [FilePath],+ extraTmpFiles :: [FilePath],+ extraDocFiles :: [FilePath]+ } deriving (Generic, Eq, Ord, Read, Show)++data PackageIdentifier+ = PackageIdentifier {+ pkgName :: PackageName,+ pkgVersion :: Version+ }+ deriving (Generic, Eq, Ord, Read, Show)++newtype PackageName = PackageName { unPackageName :: String }+ deriving (Generic, Eq, Ord, Read, Show)++data RepoKind =+ RepoHead+ | RepoThis+ | RepoKindUnknown String+ deriving (Generic, Eq, Ord, Read, Show)++data RepoType = Darcs | Git | SVN | CVS+ | Mercurial | GnuArch | Bazaar | Monotone+ | OtherRepoType String+ deriving (Generic, Eq, Ord, Read, Show)++data SetupBuildInfo = SetupBuildInfo {+ setupDepends :: [Dependency],+ defaultSetupDepends :: Bool+ }+ deriving (Generic, Eq, Ord, Read, Show)++data SourceRepo = SourceRepo {+ repoKind :: RepoKind,+ repoType :: Maybe RepoType,+ repoLocation :: Maybe String,+ repoModule :: Maybe String,+ repoBranch :: Maybe String,+ repoTag :: Maybe String,+ repoSubdir :: Maybe FilePath+}+ deriving (Generic, Eq, Ord, Read, Show)++data TestSuite = TestSuite {+ testName :: String,+ testInterface :: TestSuiteInterface,+ testBuildInfo :: BuildInfo,+ testEnabled :: Bool+ }+ deriving (Generic, Eq, Ord, Read, Show)++data TestSuiteInterface =+ TestSuiteExeV10 Version FilePath+ | TestSuiteLibV09 Version ModuleName+ | TestSuiteUnsupported TestType+ deriving (Generic, Eq, Ord, Read, Show)++data TestType = TestTypeExe Version+ | TestTypeLib Version+ | TestTypeUnknown String Version+ deriving (Generic, Eq, Ord, Read, Show)++data VersionRange+ = AnyVersion+ | ThisVersion Version+ | LaterVersion Version+ | EarlierVersion Version+ | WildcardVersion Version+ | UnionVersionRanges VersionRange VersionRange+ | IntersectVersionRanges VersionRange VersionRange+ | VersionRangeParens VersionRange+ deriving (Generic, Eq, Ord, Read, Show)
+ benchmarks/GenericsBench.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE DeriveGeneric, StandaloneDeriving, BangPatterns #-}+module Main where++import qualified Data.ByteString.Lazy as L+import Cabal24 (PackageDescription)++import Criterion.Main++import qualified Data.Binary as Binary+import Data.Binary.Get (Get)+import qualified Data.Binary.Get as Binary++import GenericsBenchCache++main :: IO ()+main = benchmark =<< readPackageDescriptionCache 100++benchmark :: [PackageDescription] -> IO ()+benchmark pds = do+ let lbs = encode pds+ !_ = L.length lbs+ str = show pds+ !_ = length str+ defaultMain [+ bench "encode" (nf encode pds)+ , bench "decode" (nf decode lbs)+ , bench "decode null" (nf decodeNull lbs)+ , bgroup "embarrassment" [+ bench "read" (nf readPackageDescription str)+ , bench "show" (nf show pds)+ ]+ ]++encode :: [PackageDescription] -> L.ByteString+encode = Binary.encode++decode :: L.ByteString -> Int+decode = length . (Binary.decode :: L.ByteString -> [PackageDescription])++decodeNull :: L.ByteString -> ()+decodeNull =+ Binary.runGet $ do+ n <- Binary.get :: Get Int+ go n+ where+ go 0 = return ()+ go i = do+ x <- Binary.get :: Get PackageDescription+ x `seq` go (i-1)++readPackageDescription :: String -> Int+readPackageDescription = length . (read :: String -> [PackageDescription])
+ benchmarks/GenericsBenchCache.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE DeriveGeneric, StandaloneDeriving, BangPatterns, CPP #-}+module GenericsBenchCache (readPackageDescriptionCache) where++import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Lazy.Char8 as LC8+import qualified Codec.Compression.GZip as GZip++import Cabal24 (PackageDescription)++import System.Directory+import System.Exit++import GenericsBenchTypes ()++#if ! MIN_VERSION_base(4,8,0)+import Control.Applicative ((<$>))+#endif++readPackageDescriptionCache :: Int -> IO [PackageDescription]+readPackageDescriptionCache amount = do+ cacheExists <- doesFileExist cacheFilePath+ bs <-+ if cacheExists+ then do+ putStrLn "reading the cache file, might take a moment..."+ L.readFile cacheFilePath+ else do+ -- In older versions of this benchmark, there was machinery to+ -- regenerate the cache using the data in @~/.cabal@. Now the cache is+ -- simply stored in the repo to avoid a dependency on Cabal the library.+ putStrLn (cacheFilePath ++ " missing, aborting")+ exitFailure+ let str = LC8.unpack (GZip.decompress bs)+ pds = take amount (read str)+ -- PackageDescription doesn't implement NFData, let's force with the following line+ (length (show pds)) `seq` putStrLn "done reading the cache file"+ return pds++cacheFilePath :: String+cacheFilePath = "generics-bench.cache.gz"
+ benchmarks/GenericsBenchTypes.hs view
@@ -0,0 +1,35 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+module GenericsBenchTypes where++import Cabal24+import Generics.Deriving.Instances ()+import Data.Binary++instance Binary Benchmark+instance Binary BenchmarkInterface+instance Binary BenchmarkType+instance Binary BuildInfo+instance Binary BuildType+instance Binary CompilerFlavor+instance Binary Dependency+instance Binary Executable+instance Binary Extension+instance Binary FlagName+instance Binary KnownExtension+instance Binary Language+instance Binary Library+instance Binary License+instance Binary ModuleName+instance Binary ModuleReexport+instance Binary ModuleRenaming+instance Binary PackageDescription+instance Binary PackageIdentifier+instance Binary PackageName+instance Binary RepoKind+instance Binary RepoType+instance Binary SetupBuildInfo+instance Binary SourceRepo+instance Binary TestSuite+instance Binary TestSuiteInterface+instance Binary TestType+instance Binary VersionRange
benchmarks/Get.hs view
@@ -4,24 +4,27 @@ #include "MachDeps.h" #endif -module Main (main) where+module Main where import Control.DeepSeq import Control.Exception (evaluate)-import Control.Monad.Trans (liftIO)-import Criterion.Config-import Criterion.Main hiding (run)+import Criterion.Main import qualified Data.ByteString as S import qualified Data.ByteString.Char8 as C8 import qualified Data.ByteString.Lazy as L+import Data.Bits import Data.Char (ord)-import Data.Monoid (Monoid(mappend, mempty))-import Data.Word (Word8, Word16, Word32)+import Data.List (foldl') import Control.Applicative+import Data.Binary import Data.Binary.Get-import Data.Binary ( get ) +import qualified Data.Serialize.Get as Cereal++import qualified Data.Attoparsec.ByteString as A+import qualified Data.Attoparsec.ByteString.Lazy as AL+ #if !MIN_VERSION_bytestring(0,10,0) instance NFData S.ByteString instance NFData L.ByteString where@@ -32,41 +35,95 @@ main = do evaluate $ rnf [ rnf brackets,+ rnf bracketsInChunks,+ rnf bracketCount, rnf oneMegabyte,- rnf oneMegabyteLBS+ rnf oneMegabyteLBS,+ rnf manyBytes,+ rnf encodedBigInteger ] defaultMain- [- bench "brackets 100k one chunk input" $- whnf (runTest bracketParser) brackets- , bench "brackets 100k in 1024 100 byte chunks" $- whnf (runTest bracketParser) bracketsInChunks- , bench "getStruct4 1MB struct of 4 word32 strict" $- whnf (runTest (getStruct4Strict mega)) oneMegabyteLBS- , bench "getStruct4 1MB struct of 4 word32" $- whnf (runTest (getStruct4 mega)) oneMegabyteLBS- , bench "getWord8 1MB chunk size 1 byte" $- whnf (runTest (getWord8N1 mega)) oneMegabyteLBS- , bench "getWord8 1MB chunk size 2 bytes" $- whnf (runTest (getWord8N2 mega)) oneMegabyteLBS- , bench "getWord8 1MB chunk size 4 bytes" $- whnf (runTest (getWord8N4 mega)) oneMegabyteLBS- , bench "getWord8 1MB chunk size 8 bytes" $- whnf (runTest (getWord8N8 mega)) oneMegabyteLBS- , bench "getWord8 1MB chunk size 16 bytes" $- whnf (runTest (getWord8N16 mega)) oneMegabyteLBS- , bench "getWord8 1MB chunk size 2 bytes Applicative" $- whnf (runTest (getWord8N2A mega)) oneMegabyteLBS- , bench "getWord8 1MB chunk size 4 bytes Applicative" $- whnf (runTest (getWord8N4A mega)) oneMegabyteLBS- , bench "getWord8 1MB chunk size 8 bytes Applicative" $- whnf (runTest (getWord8N8A mega)) oneMegabyteLBS- , bench "getWord8 1MB chunk size 16 bytes Applicative" $- whnf (runTest (getWord8N16A mega)) oneMegabyteLBS+ [ bgroup "brackets"+ [ bench "Binary 100kb, one chunk" $+ whnf (checkBracket . runTest bracketParser) brackets+ , bench "Binary 100kb, 100 byte chunks" $+ whnf (checkBracket . runTest bracketParser) bracketsInChunks+ , bench "Attoparsec lazy-bs 100kb, one chunk" $+ whnf (checkBracket . runAttoL bracketParser_atto) brackets+ , bench "Attoparsec lazy-bs 100kb, 100 byte chunks" $+ whnf (checkBracket . runAttoL bracketParser_atto) bracketsInChunks+ , bench "Attoparsec strict-bs 100kb" $+ whnf (checkBracket . runAtto bracketParser_atto) $ S.concat (L.toChunks brackets)+ , bench "Cereal strict-bs 100kb" $+ whnf (checkBracket . runCereal bracketParser_cereal) $ S.concat (L.toChunks brackets)+ ]+ , bgroup "comparison getStruct4, 1MB of struct of 4 Word8s"+ [ bench "Attoparsec" $+ whnf (runAtto (getStruct4_atto mega)) oneMegabyte+ , bench "Binary" $+ whnf (runTest (getStruct4 mega)) oneMegabyteLBS+ , bench "Cereal" $+ whnf (runCereal (getStruct4_cereal mega)) oneMegabyte+ ]+ , bgroup "comparison getWord8, 1MB"+ [ bench "Attoparsec" $+ whnf (runAtto (getWord8N1_atto mega)) oneMegabyte+ , bench "Binary" $+ whnf (runTest (getWord8N1 mega)) oneMegabyteLBS+ , bench "Cereal" $+ whnf (runCereal (getWord8N1_cereal mega)) oneMegabyte+ ]+ , bgroup "getWord8 1MB"+ [ bench "chunk size 2 bytes" $+ whnf (runTest (getWord8N2 mega)) oneMegabyteLBS+ , bench "chunk size 4 bytes" $+ whnf (runTest (getWord8N4 mega)) oneMegabyteLBS+ , bench "chunk size 8 bytes" $+ whnf (runTest (getWord8N8 mega)) oneMegabyteLBS+ , bench "chunk size 16 bytes" $+ whnf (runTest (getWord8N16 mega)) oneMegabyteLBS+ ]+ , bgroup "getWord8 1MB Applicative"+ [ bench "chunk size 2 bytes" $+ whnf (runTest (getWord8N2A mega)) oneMegabyteLBS+ , bench "chunk size 4 bytes" $+ whnf (runTest (getWord8N4A mega)) oneMegabyteLBS+ , bench "chunk size 8 bytes" $+ whnf (runTest (getWord8N8A mega)) oneMegabyteLBS+ , bench "chunk size 16 bytes" $+ whnf (runTest (getWord8N16A mega)) oneMegabyteLBS+ ]+ , bgroup "roll"+ [ bench "foldr" $ nf (roll_foldr :: [Word8] -> Integer) manyBytes+ , bench "foldl'" $ nf (roll_foldl' :: [Word8] -> Integer) manyBytes+ ]+ , bgroup "Integer"+ [ bench "decode" $ nf (decode :: L.ByteString -> Integer) encodedBigInteger+ ] ] -runTest parser inp = runGet parser inp+checkBracket :: Int -> Int+checkBracket x | x == bracketCount = x+ | otherwise = error "argh!" +runTest :: Get a -> L.ByteString -> a+runTest decoder inp = runGet decoder inp++runCereal :: Cereal.Get a -> C8.ByteString -> a+runCereal decoder inp = case Cereal.runGet decoder inp of+ Right a -> a+ Left err -> error err++runAtto :: AL.Parser a -> C8.ByteString -> a+runAtto decoder inp = case A.parseOnly decoder inp of+ Right a -> a+ Left err -> error err++runAttoL :: Show a => AL.Parser a -> L.ByteString -> a+runAttoL decoder inp = case AL.parse decoder inp of+ AL.Done _ r -> r+ a -> error (show a)+ -- Defs. oneMegabyte :: S.ByteString@@ -75,80 +132,139 @@ oneMegabyteLBS :: L.ByteString oneMegabyteLBS = L.fromChunks [oneMegabyte] +mega :: Int mega = 1024 * 1024 -- 100k of brackets+bracketTest :: L.ByteString -> Int bracketTest inp = runTest bracketParser inp -brackets = L.fromChunks [C8.concat (replicate 1024 "((()((()()))((()(()()()()()()()(((()()()()(()()(()(()())))))()((())())))()())(((())())(()))))()(()))")]-bracketsInChunks = L.fromChunks (replicate 1024 "((()((()()))((()(()()()()()()()(((()()()()(()()(()(()())))))()((())())))()())(((())())(()))))()(()))")+bracketCount :: Int+bracketCount = fromIntegral $ L.length brackets `div` 2 -bracketParser = cont <|> end+brackets :: L.ByteString+brackets = L.fromChunks [C8.concat (L.toChunks bracketsInChunks)]++bracketsInChunks :: L.ByteString+bracketsInChunks = L.fromChunks (replicate chunksOfBrackets oneChunk) where- end = return 0+ oneChunk = "((()((()()))((()(()()()()()()()(((()()()()(()()(()(()())))))()((())())))()())(((())())(()))))()(()))"+ chunksOfBrackets = 102400 `div` S.length oneChunk++bracketParser :: Get Int+bracketParser = cont <|> return 0+ where cont = do v <- some ( do 40 <- getWord8- n <- bracketParser+ n <- many cont 41 <- getWord8- return $! n + 1)+ return $! sum n + 1) return $! sum v --- Struct strict+bracketParser_cereal :: Cereal.Get Int+bracketParser_cereal = cont <|> return 0+ where+ cont = do v <- some ( do 40 <- Cereal.getWord8+ n <- many cont+ 41 <- Cereal.getWord8+ return $! sum n + 1)+ return $! sum v -data Struct4S = Struct4S !Word32 !Word32 !Word32 !Word32+bracketParser_atto :: A.Parser Int+bracketParser_atto = cont <|> return 0+ where+ cont = do v <- some ( do _ <- A.word8 40+ n <- bracketParser_atto+ _ <- A.word8 41+ return $! n + 1)+ return $! sum v -instance NFData Struct4S where- rnf (Struct4S !a !b !c !d) = ()+-- Strict struct of 4 Word8s+data S2 = S2 {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8+data S4 = S4 {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8+data S8 = S8 {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8+ {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8+data S16 = S16 {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8+ {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8+ {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8+ {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8 -getStruct4Strict = loop []+getStruct4 :: Int -> Get [S4]+getStruct4 = loop [] where loop acc 0 = return acc loop acc n = do- !w0 <- get- !w1 <- get- !w2 <- get- !w3 <- get- loop (Struct4S w0 w1 w2 w3 : acc) (n - 16)---- Struct lazy--data Struct4 = Struct4 Word32 Word32 Word32 Word32--instance NFData Struct4 where- rnf (Struct4 !a !b !c !d) = ()+ !w0 <- getWord8+ !w1 <- getWord8+ !w2 <- getWord8+ !w3 <- getWord8+ let !s = S4 w0 w1 w2 w3+ loop (s : acc) (n - 4) -getStruct4 = loop []+getStruct4_cereal :: Int -> Cereal.Get [S4]+getStruct4_cereal = loop [] where loop acc 0 = return acc loop acc n = do- w0 <- get- w1 <- get- w2 <- get- w3 <- get- loop (Struct4 w0 w1 w2 w3 : acc) (n - 16)+ !w0 <- Cereal.getWord8+ !w1 <- Cereal.getWord8+ !w2 <- Cereal.getWord8+ !w3 <- Cereal.getWord8+ let !s = S4 w0 w1 w2 w3+ loop (s : acc) (n - 4) --- No-allocation loops.+getStruct4_atto :: Int -> A.Parser [S4]+getStruct4_atto = loop []+ where loop acc 0 = return acc+ loop acc n = do+ !w0 <- A.anyWord8+ !w1 <- A.anyWord8+ !w2 <- A.anyWord8+ !w3 <- A.anyWord8+ let !s = S4 w0 w1 w2 w3+ loop (s : acc) (n - 4) -getWord8N1 = loop 0+getWord8N1 :: Int -> Get [Word8]+getWord8N1 = loop [] where loop s n | s `seq` n `seq` False = undefined loop s 0 = return s loop s n = do s0 <- getWord8- loop (s+s0) (n-1)+ loop (s0:s) (n-1) -getWord8N2 = loop 0+getWord8N1_cereal :: Int -> Cereal.Get [Word8]+getWord8N1_cereal = loop [] where loop s n | s `seq` n `seq` False = undefined loop s 0 = return s loop s n = do+ s0 <- Cereal.getWord8+ loop (s0:s) (n-1)++getWord8N1_atto :: Int -> A.Parser [Word8]+getWord8N1_atto = loop []+ where loop s n | s `seq` n `seq` False = undefined+ loop s 0 = return s+ loop s n = do+ s0 <- A.anyWord8+ loop (s0:s) (n-1)++getWord8N2 :: Int -> Get [S2]+getWord8N2 = loop []+ where loop s n | s `seq` n `seq` False = undefined+ loop s 0 = return s+ loop s n = do s0 <- getWord8 s1 <- getWord8- loop (s+s0+s1) (n-2)+ let !v = S2 s0 s1+ loop (v:s) (n-2) -getWord8N2A = loop 0+getWord8N2A :: Int -> Get [S2]+getWord8N2A = loop [] where loop s n | s `seq` n `seq` False = undefined loop s 0 = return s loop s n = do- v <- (+) <$> getWord8 <*> getWord8- loop (s+v) (n-2)+ !v <- S2 <$> getWord8 <*> getWord8+ loop (v:s) (n-2) -getWord8N4 = loop 0+getWord8N4 :: Int -> Get [S4]+getWord8N4 = loop [] where loop s n | s `seq` n `seq` False = undefined loop s 0 = return s loop s n = do@@ -156,17 +272,19 @@ s1 <- getWord8 s2 <- getWord8 s3 <- getWord8- loop (s+s0+s1+s2+s3) (n-4)+ let !v = S4 s0 s1 s2 s3+ loop (v:s) (n-4) -getWord8N4A = loop 0+getWord8N4A :: Int -> Get [S4]+getWord8N4A = loop [] where loop s n | s `seq` n `seq` False = undefined loop s 0 = return s loop s n = do- let p !s0 !s1 !s2 !s3 = s0 + s1 + s2 + s3- v <- p <$> getWord8 <*> getWord8 <*> getWord8 <*> getWord8- loop (s+v) (n-4)+ !v <- S4 <$> getWord8 <*> getWord8 <*> getWord8 <*> getWord8+ loop (v:s) (n-4) -getWord8N8 = loop 0+getWord8N8 :: Int -> Get [S8]+getWord8N8 = loop [] where loop s n | s `seq` n `seq` False = undefined loop s 0 = return s loop s n = do@@ -178,15 +296,15 @@ s5 <- getWord8 s6 <- getWord8 s7 <- getWord8- loop (s+s0+s1+s2+s3+s4+s5+s6+s7) (n-8)+ let !v = S8 s0 s1 s2 s3 s4 s5 s6 s7+ loop (v:s) (n-8) -getWord8N8A = loop 0+getWord8N8A :: Int -> Get [S8]+getWord8N8A = loop [] where loop s n | s `seq` n `seq` False = undefined loop s 0 = return s loop s n = do- let p !s0 !s1 !s2 !s3 !s4 !s5 !s6 !s7 =- s0 + s1 + s2 + s3 + s4 + s5 + s6 + s7- v <- p <$> getWord8+ !v <- S8 <$> getWord8 <*> getWord8 <*> getWord8 <*> getWord8@@ -194,9 +312,10 @@ <*> getWord8 <*> getWord8 <*> getWord8- loop (s+v) (n-8)+ loop (v:s) (n-8) -getWord8N16 = loop 0+getWord8N16 :: Int -> Get [S16]+getWord8N16 = loop [] where loop s n | s `seq` n `seq` False = undefined loop s 0 = return s loop s n = do@@ -216,28 +335,47 @@ s13 <- getWord8 s14 <- getWord8 s15 <- getWord8- loop (s+s0+s1+s2+s3+s4+s5+s6+s7+s8+s9+s10+s11+s12+s13+s14+s15) (n-16)+ let !v = S16 s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15+ loop (v:s) (n-16) -getWord8N16A = loop 0+getWord8N16A :: Int -> Get [S16]+getWord8N16A = loop [] where loop s n | s `seq` n `seq` False = undefined loop s 0 = return s loop s n = do- let p !s0 !s1 !s2 !s3 !s4 !s5 !s6 !s7 !s8 !s9 !s10 !s11 !s12 !s13 !s14 !s15 =- s0 + s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 + s9 + s10 + s11 + s12 + s13 + s14 + s15- !v <- p <$> getWord8- <*> getWord8- <*> getWord8- <*> getWord8- <*> getWord8- <*> getWord8- <*> getWord8- <*> getWord8- <*> getWord8- <*> getWord8- <*> getWord8- <*> getWord8- <*> getWord8- <*> getWord8- <*> getWord8- <*> getWord8- loop (s+v) (n-16)+ !v <- S16 <$> getWord8+ <*> getWord8+ <*> getWord8+ <*> getWord8+ <*> getWord8+ <*> getWord8+ <*> getWord8+ <*> getWord8+ <*> getWord8+ <*> getWord8+ <*> getWord8+ <*> getWord8+ <*> getWord8+ <*> getWord8+ <*> getWord8+ <*> getWord8+ loop (v:s) (n-16)++manyBytes :: [Word8]+manyBytes = concat $ replicate 256 [0..255]++bigInteger :: Integer+bigInteger = roll_foldl' manyBytes++encodedBigInteger :: L.ByteString+encodedBigInteger = encode bigInteger++roll_foldr :: (Integral a, Bits a) => [Word8] -> a+roll_foldr = foldr unstep 0+ where+ unstep b a = a `shiftL` 8 .|. fromIntegral b++roll_foldl' :: (Integral a, Bits a) => [Word8] -> a+roll_foldl' = foldl' unstep 0 . reverse+ where+ unstep a b = a `shiftL` 8 .|. fromIntegral b
− benchmarks/Makefile
@@ -1,34 +0,0 @@-ghc := ghc-ghc-flags :=-programs := builder bench--SYSTEM_BINARY := binary-0.5.1.0--.PHONY: all-all: $(programs)--builder: Builder.hs- $(ghc) $(ghc-flags) --make -O2 Builder.hs -o $@ -fforce-recomp -i../src--get: Get.hs- $(ghc) $(ghc-flags) --make -O2 Get.hs -o $@ -fforce-recomp -i../src--system-get: Get.hs- $(ghc) $(ghc-flags) --make -O2 Get.hs -o $@ -package $(SYSTEM_BINARY)--bench: Benchmark.hs MemBench.hs CBenchmark.o- $(ghc) $(ghc-flags) --make -O2 -fliberate-case-threshold=1000 Benchmark.hs CBenchmark.o -o $@ -fforce-recomp -i../src--system-bench: Benchmark.hs MemBench.hs CBenchmark.o- $(ghc) $(ghc-flags) --make -O2 -fliberate-case-threshold=1000 Benchmark.hs CBenchmark.o -o $@ -no-user-package-conf--.PHONY: run-bench-run-bench: bench- ./bench 100--CBenchmark.o: CBenchmark.c- $(ghc) -c -optc -O3 $< -o $@--.PHONY: clean-clean:- rm -f *.o *.hi $(programs)
+ benchmarks/Put.hs view
@@ -0,0 +1,178 @@+{-# LANGUAGE CPP, ExistentialQuantification #-}+{-# LANGUAGE DeriveGeneric #-}++module Main (main) where++import Control.DeepSeq+import Control.Exception (evaluate)+import Criterion.Main+import qualified Data.ByteString as S+import qualified Data.ByteString.Char8 as C+import qualified Data.ByteString.Lazy as L+import Data.Monoid++import GHC.Generics++import Data.Binary+import Data.Binary.Put+import Data.ByteString.Builder as BB+import Prelude -- Silence Monoid import warning.++main :: IO ()+main = do+ evaluate $ rnf+ [ rnf bigIntegers+ , rnf smallIntegers+ , rnf smallByteStrings+ , rnf smallStrings+ , rnf doubles+ , rnf word8s+ , rnf word16s+ , rnf word32s+ , rnf word64s+ ]+ defaultMain+ [+ bench "small Integers" $ whnf (run . fromIntegers) smallIntegers,+ bench "big Integers" $ whnf (run . fromIntegers) bigIntegers,++ bench "[small Integer]" $ whnf (run . put) smallIntegers,+ bench "[big Integer]" $ whnf (run . put) bigIntegers,++ bench "small ByteStrings" $ whnf (run . fromByteStrings) smallByteStrings,+ bench "[small ByteString]" $ whnf (run . put) smallByteStrings,++ bench "small Strings" $ whnf (run . fromStrings) smallStrings,+ bench "[small String]" $ whnf (run . put) smallStrings,++ bench "Double" $ whnf (run . put) doubles,++ bench "Word8s monoid put" $ whnf (run . fromWord8s) word8s,+ bench "Word8s builder" $ whnf (L.length . toLazyByteString . fromWord8sBuilder) word8s,+ bench "[Word8]" $ whnf (run . put) word8s,+ bench "Word16s monoid put" $ whnf (run . fromWord16s) word16s,+ bench "Word16s builder" $ whnf (L.length . toLazyByteString . fromWord16sBuilder) word16s,+ bench "[Word16]" $ whnf (run . put) word16s,+ bench "Word32s monoid put" $ whnf (run . fromWord32s) word32s,+ bench "Word32s builder" $ whnf (L.length . toLazyByteString . fromWord32sBuilder) word32s,+ bench "[Word32]" $ whnf (run . put) word32s,+ bench "Word64s monoid put" $ whnf (run . fromWord64s) word64s,+ bench "Word64s builder" $ whnf (L.length . toLazyByteString . fromWord64sBuilder) word64s,+ bench "[Word64]" $ whnf (run . put) word64s++ , bgroup "Generics" [+ bench "Struct monoid put" $ whnf (run . fromStructs) structs,+ bench "Struct put as list" $ whnf (run . put) structs,+ bench "StructList monoid put" $ whnf (run . fromStructLists) structLists,+ bench "StructList put as list" $ whnf (run . put) structLists+ ]+ ]+ where+ run = L.length . runPut++data Struct = Struct Word8 Word16 Word32 Word64 deriving Generic+instance Binary Struct++data StructList = StructList [Struct] deriving Generic+instance Binary StructList++structs :: [Struct]+structs = take 10000 $ [ Struct a b 0 0 | a <- [0 .. maxBound], b <- [0 .. maxBound] ]++structLists :: [StructList]+structLists = replicate 1000 (StructList (take 10 structs))++-- Input data++smallIntegers :: [Integer]+smallIntegers = [0..10000]+{-# NOINLINE smallIntegers #-}++bigIntegers :: [Integer]+bigIntegers = [m .. m + 10000]+ where+ m :: Integer+ m = fromIntegral (maxBound :: Word64)+{-# NOINLINE bigIntegers #-}++smallByteStrings :: [S.ByteString]+smallByteStrings = replicate 10000 $ C.pack "abcdefghi"+{-# NOINLINE smallByteStrings #-}++smallStrings :: [String]+smallStrings = replicate 10000 "abcdefghi"+{-# NOINLINE smallStrings #-}++doubles :: [Double]+doubles = take 10000 $ [ sign * 2 ** n | sign <- [-1, 1], n <- [ 0, 0.2 .. 1023 ]]++word8s :: [Word8]+word8s = take 10000 $ cycle [minBound .. maxBound]+{-# NOINLINE word8s #-}++word16s :: [Word16]+word16s = take 10000 $ cycle [minBound .. maxBound]+{-# NOINLINE word16s #-}++word32s :: [Word32]+word32s = take 10000 $ cycle [minBound .. maxBound]+{-# NOINLINE word32s #-}++word64s :: [Word64]+word64s = take 10000 $ cycle [minBound .. maxBound]+{-# NOINLINE word64s #-}++------------------------------------------------------------------------+-- Benchmarks++fromIntegers :: [Integer] -> Put+fromIntegers [] = mempty+fromIntegers (x:xs) = put x `mappend` fromIntegers xs++fromByteStrings :: [S.ByteString] -> Put+fromByteStrings [] = mempty+fromByteStrings (x:xs) = put x `mappend` fromByteStrings xs++fromStrings :: [String] -> Put+fromStrings [] = mempty+fromStrings (x:xs) = put x `mappend` fromStrings xs++fromWord8s :: [Word8] -> Put+fromWord8s [] = mempty+fromWord8s (x:xs) = put x `mappend` fromWord8s xs++fromWord8sBuilder :: [Word8] -> BB.Builder+fromWord8sBuilder [] = mempty+fromWord8sBuilder (x:xs) = BB.word8 x `mappend` fromWord8sBuilder xs++fromWord16s :: [Word16] -> Put+fromWord16s [] = mempty+fromWord16s (x:xs) = put x `mappend` fromWord16s xs++fromWord16sBuilder :: [Word16] -> BB.Builder+fromWord16sBuilder [] = mempty+fromWord16sBuilder (x:xs) = BB.word16BE x `mappend` fromWord16sBuilder xs++fromWord32s :: [Word32] -> Put+fromWord32s [] = mempty+fromWord32s (x:xs) = put x `mappend` fromWord32s xs++fromWord32sBuilder :: [Word32] -> BB.Builder+fromWord32sBuilder [] = mempty+fromWord32sBuilder (x:xs) = BB.word32BE x `mappend` fromWord32sBuilder xs++fromWord64s :: [Word64] -> Put+fromWord64s [] = mempty+fromWord64s (x:xs) = put x `mappend` fromWord64s xs++fromWord64sBuilder :: [Word64] -> BB.Builder+fromWord64sBuilder [] = mempty+fromWord64sBuilder (x:xs) = BB.word64BE x `mappend` fromWord64sBuilder xs++fromStructs :: [Struct] -> Put+fromStructs [] = mempty+fromStructs (x:xs) = put x `mappend` fromStructs xs++fromStructLists :: [StructList] -> Put+fromStructLists [] = mempty+fromStructLists (x:xs) = put x `mappend` fromStructLists xs
binary.cabal view
@@ -1,13 +1,15 @@ name: binary-version: 0.6.4.0+version: 0.10.0.0 license: BSD3 license-file: LICENSE author: Lennart Kolmodin <kolmodin@gmail.com>-maintainer: Lennart Kolmodin, Don Stewart <dons@galois.com>+maintainer: Lennart Kolmodin, Don Stewart <dons00@gmail.com> homepage: https://github.com/kolmodin/binary description: Efficient, pure binary serialisation using lazy ByteStrings. Haskell values may be encoded to and from binary formats, written to disk as binary, or sent over the network.+ The format used can be automatically generated, or+ you can choose to implement a custom format if needed. Serialisation speeds of over 1 G\/sec have been observed, so this library should be suitable for high performance scenarios.@@ -16,89 +18,251 @@ stability: provisional build-type: Simple cabal-version: >= 1.8-tested-with: GHC == 7.0.4, GHC == 7.4.1, GHC == 7.6.1-extra-source-files: README index.html+tested-with: GHC == 7.4.2, GHC == 7.6.3, GHC == 7.8.4, GHC == 7.10.3, GHC == 8.0.2+extra-source-files:+ README.md changelog.md docs/hcar/binary-Lb.tex tools/derive/*.hs +-- from the benchmark 'bench'+extra-source-files:+ benchmarks/CBenchmark.h+ source-repository head type: git location: git://github.com/kolmodin/binary.git -flag development- default: False- library- build-depends: base >= 3.0 && < 5, bytestring >= 0.9, containers, array+ build-depends: base >= 4.5.0.0 && < 5, bytestring >= 0.10.4, containers, array hs-source-dirs: src exposed-modules: Data.Binary, Data.Binary.Put, Data.Binary.Get, Data.Binary.Get.Internal,- Data.Binary.Builder,- Data.Binary.Builder.Internal-- other-modules: Data.Binary.Builder.Base,- Data.Binary.Class+ Data.Binary.Builder - if impl(ghc >= 7.2.1)- cpp-options: -DGENERICS- other-modules: Data.Binary.Generic- if impl(ghc <= 7.6)- -- prior to ghc-7.4 generics lived in ghc-prim- build-depends: ghc-prim+ other-modules: Data.Binary.Class,+ Data.Binary.Internal,+ Data.Binary.Generic,+ Data.Binary.FloatCast+ if impl(ghc <= 7.6)+ -- prior to ghc-7.4 generics lived in ghc-prim+ build-depends: ghc-prim ghc-options: -O2 -Wall -fliberate-case-threshold=1000 + if impl(ghc >= 8.0)+ ghc-options: -Wcompat -Wnoncanonical-monad-instances -Wnoncanonical-monadfail-instances++-- Due to circular dependency, we cannot make any of the test-suites or+-- benchmark depend on the binary library. Instead, for each test-suite and+-- benchmark, we include the source directory of binary and build-depend on all+-- the dependencies binary has.+ test-suite qc type: exitcode-stdio-1.0- hs-source-dirs: tests+ hs-source-dirs: src tests main-is: QC.hs+ other-modules:+ Action+ Arbitrary+ other-modules:+ Data.Binary+ Data.Binary.Builder+ Data.Binary.Class+ Data.Binary.FloatCast+ Data.Binary.Generic+ Data.Binary.Get+ Data.Binary.Get.Internal+ Data.Binary.Internal+ Data.Binary.Put build-depends:- array,- base >= 3.0 && < 5,- bytestring >= 0.9,- containers,+ base >= 4.5.0.0 && < 5,+ bytestring >= 0.10.4, random>=1.0.1.0, test-framework,- test-framework-quickcheck2,- QuickCheck>=2.5+ test-framework-quickcheck2 >= 0.3,+ QuickCheck >= 2.9 + -- build dependencies from using binary source rather than depending on the library+ build-depends: array, containers+ ghc-options: -Wall -O2 -threaded+ if impl(ghc <= 7.6)+ -- prior to ghc-7.4 generics lived in ghc-prim+ build-depends: ghc-prim+++test-suite read-write-file+ type: exitcode-stdio-1.0+ hs-source-dirs: src tests+ main-is: File.hs+ other-modules:+ Data.Binary+ Data.Binary.Builder+ Data.Binary.Class+ Data.Binary.FloatCast+ Data.Binary.Generic+ Data.Binary.Get+ Data.Binary.Get.Internal+ Data.Binary.Internal+ Data.Binary.Put+ build-depends:+ base >= 4.5.0.0 && < 5,+ bytestring >= 0.10.4,+ Cabal,+ directory,+ filepath,+ HUnit++ -- build dependencies from using binary source rather than depending on the library+ build-depends: array, containers ghc-options: -Wall- hs-source-dirs: src+ if impl(ghc <= 7.6)+ -- prior to ghc-7.4 generics lived in ghc-prim+ build-depends: ghc-prim + benchmark bench type: exitcode-stdio-1.0- hs-source-dirs: benchmarks+ hs-source-dirs: src benchmarks main-is: Benchmark.hs+ other-modules:+ MemBench+ Data.Binary+ Data.Binary.Builder+ Data.Binary.Class+ Data.Binary.FloatCast+ Data.Binary.Generic+ Data.Binary.Get+ Data.Binary.Get.Internal+ Data.Binary.Internal+ Data.Binary.Put build-depends:- base >= 3.0 && < 5,- binary,- bytestring+ base >= 4.5.0.0 && < 5,+ bytestring >= 0.10.4+ -- build dependencies from using binary source rather than depending on the library+ build-depends: array, containers c-sources: benchmarks/CBenchmark.c include-dirs: benchmarks ghc-options: -O2+ if impl(ghc <= 7.6)+ -- prior to ghc-7.4 generics lived in ghc-prim+ build-depends: ghc-prim + benchmark get type: exitcode-stdio-1.0- hs-source-dirs: benchmarks+ hs-source-dirs: src benchmarks main-is: Get.hs+ other-modules:+ Data.Binary+ Data.Binary.Builder+ Data.Binary.Class+ Data.Binary.FloatCast+ Data.Binary.Generic+ Data.Binary.Get.Internal+ Data.Binary.Internal+ Data.Binary.Put build-depends:- base >= 3.0 && < 5,- binary,- bytestring,- criterion,+ attoparsec,+ base >= 4.5.0.0 && < 5,+ bytestring >= 0.10.4,+ cereal,+ criterion == 1.*, deepseq, mtl- ghc-options: -O2+ -- build dependencies from using binary source rather than depending on the library+ build-depends: array, containers+ ghc-options: -O2 -Wall+ if impl(ghc <= 7.6)+ -- prior to ghc-7.4 generics lived in ghc-prim+ build-depends: ghc-prim ++benchmark put+ type: exitcode-stdio-1.0+ hs-source-dirs: src benchmarks+ main-is: Put.hs+ other-modules:+ Data.Binary+ Data.Binary.Builder+ Data.Binary.Class+ Data.Binary.FloatCast+ Data.Binary.Generic+ Data.Binary.Get+ Data.Binary.Get.Internal+ Data.Binary.Internal+ build-depends:+ base >= 4.5.0.0 && < 5,+ bytestring >= 0.10.4,+ criterion == 1.*,+ deepseq+ -- build dependencies from using binary source rather than depending on the library+ build-depends: array, containers+ ghc-options: -O2 -Wall+ if impl(ghc <= 7.6)+ -- prior to ghc-7.4 generics lived in ghc-prim+ build-depends: ghc-prim++benchmark generics-bench+ type: exitcode-stdio-1.0+ hs-source-dirs: src benchmarks+ main-is: GenericsBench.hs+ other-modules:+ Data.Binary+ Data.Binary.Builder+ Data.Binary.Class+ Data.Binary.FloatCast+ Data.Binary.Generic+ Data.Binary.Get+ Data.Binary.Get.Internal+ Data.Binary.Internal+ Data.Binary.Put+ build-depends:+ base >= 4.5.0.0 && < 5,+ bytestring >= 0.10.4,+ -- The benchmark already depended on 'generic-deriving' transitively. That's+ -- what caused one of the problems, as both 'generic-deriving' and+ -- 'GenericsBenchTypes' used to define 'instance Generic Version'.+ generic-deriving >= 0.10,+ directory,+ filepath,+ unordered-containers,+ zlib,+ criterion+ + other-modules:+ Cabal24+ GenericsBenchCache+ GenericsBenchTypes+ -- build dependencies from using binary source rather than depending on the library+ build-depends: array, containers+ ghc-options: -O2 -Wall+ if impl(ghc <= 7.6)+ -- prior to ghc-7.4 generics lived in ghc-prim+ build-depends: ghc-prim+ benchmark builder type: exitcode-stdio-1.0- hs-source-dirs: benchmarks+ hs-source-dirs: src benchmarks main-is: Builder.hs+ other-modules:+ Data.Binary+ Data.Binary.Builder+ Data.Binary.Class+ Data.Binary.FloatCast+ Data.Binary.Generic+ Data.Binary.Get+ Data.Binary.Get.Internal+ Data.Binary.Internal+ Data.Binary.Put build-depends:- base >= 3.0 && < 5,- binary,- bytestring,- criterion,+ base >= 4.5.0.0 && < 5,+ bytestring >= 0.10.4,+ criterion == 1.*, deepseq, mtl+ -- build dependencies from using binary source rather than depending on the library+ build-depends: array, containers ghc-options: -O2+ if impl(ghc <= 7.6)+ -- prior to ghc-7.4 generics lived in ghc-prim+ build-depends: ghc-prim
+ changelog.md view
@@ -0,0 +1,183 @@+binary+======++binary-0.10.0.0+---------------++- Add binary instance for Data.Functor.Identity from base, #146.+- Don't use * when we have TypeOperators, #148.++binary-0.9.0.0+--------------++- `0.8.5.0` was first released as version `0.9.0.0`. It didn't have any+ breaking changes though, so it was again released as version `0.8.5.0`+ according to PVP. Next breaking release of `binary` will be version+ `0.10.0.0`.++binary-0.8.5.0+--------------++- Add Binary instances for Typeable TypeReps, #131.++binary-0.8.4.1+--------------++- Fix compilation with bytestring < 0.10.4.++binary-0.8.4.0+--------------++- `binary` supports GHC >= 7.4.2+- Performance improvements for `Alternative` functions.+- put/get functions for IEEE-754 floats and doubles, #119.+- Fix performance bugs, #115.+- Binary instances for datatypes in `Data.Monoid` and `Data.Semigroup`, #114.++binary-0.8.3.0+--------------++- Replace binary's home grown `Builder` with `Data.ByteString.Builder`.+ `Data.Binary.Builder` now exports `Data.ByteString.Builder.Builder`.+- Add `putList :: [a] -> Put` to the `Binary` class. This is used to be able to+ use the list writing primitives of the new Builder. This brought a number of speedups;+ Encoding a String is now 70% faster. [Word8] is 76% faster, which also makes+ Integer 34% faster. Similar numbers for all [IntXX] and [WordXX].+- Fail gracefully within `Get` when decoding `Bool` and `Ordering`. Previously+ when decoding invalid data these instances would fail with `error`.+- Add Binary instance for `Complex a`.+- Add Monoid and Semigroup instance for `Put`.++binary-0.8.2.1+--------------++- Fix compilation error when using older GHC versions and clang. clang barfs on some of its CPP input (#105).++binary-0.8.2.0+--------------++- When using GHC >= 8, `Data.Binary.Get.Get` implements MonadFail and delegates its `fail` to `MonadFail.fail`.++binary-0.8.1.0+--------------++- Add binary instance for `Data.ByteString.Short`.+- Add get/put functions for all Int sizes to `Data.Binary.Builder`, `Data.Binary.Get` and `Data.Binary.Put`.++binary-0.8.0.1+--------------++- Address compiler warnings.++binary-0.8.0.0+--------------++- Added binary instance for `Version` from `Data.Version`.+- Added binary instance for `Void` from GHC 7.10.1.+- Added binary instance for `(Data.Fixed a)` from GHC 7.8.1.+- Added semigroup instance for `Data.Binary.Builder` from GHC 8.0.++binary-0.7.6.1+--------------++- Fix compilation for GHC == 7.2.*.++binary-0.7.6.0+--------------++- Added binary instance for GHC.Fingerprint (from GHC >= 7.4).++binary-0.7.5.0+--------------++- Fix performance bug that was noticable when you get a big strict ByteString+ and the input to the decoder consists of many small chunks.+ - https://github.com/kolmodin/binary/issues/73+ - https://github.com/kolmodin/binary/pull/76+- Fix memory leak when decoding Double and Float.+ - Commit 497a181c083fa9faf7fa3aa64d1d8deb9ac76ecb+- We now require QuickCheck >= 2.8. Remove our version of arbitrarySizedNatural.++binary-0.7.4.0+--------------++- Some invalid UTF-8 strings caused an exception when decoded. Those errors will+ now now fail in the Get monad instead. See #70.+ Patch contributed by @ttuegel.++binary-0.7.3.0+--------------++- Add Binary instance for Natural (only with base > 4.8).++binary-0.7.2.3+--------------++- Remove INLINEs from GBinary/GSum methods. These interact very badly with the+ GHC 7.9.x simplifier. See also;+ - https://github.com/kolmodin/binary/pull/62+ - https://ghc.haskell.org/trac/ghc/ticket/9630+ - https://ghc.haskell.org/trac/ghc/ticket/9583++binary-0.7.2.2+--------------++- Make import of GHC.Base future-proof (https://github.com/kolmodin/binary/pull/59).++binary-0.7.2.1+--------------++- Fix to compile on GHC 6.10.4 and older (https://github.com/kolmodin/binary/issues/55).++binary-0.7.2.0+--------------++- Add `isolate :: Int -> Get a -> Get a`.+- Add `label :: String -> Get a -> Get a`.++binary-0.7.1.0+--------------++- Add `lookAheadE :: Get (Either a b) -> Get (Either a b)`.+- Add MonadPlus instance for Get. +++binary-0.7.0.1+--------------++- Updates to documentation.++binary-0.7.0.0+--------------++- Add `lookAhead :: Get a -> Get a`.+- Add `lookAheadM :: Get (Maybe a) -> Get (Maybe a)`.+- Add Alternative instance for Get (provides `<|>`).+- Add `decodeOrFail :: Binary a => L.ByteString -> Either (L.ByteString, ByteOffset, String) (L.ByteString, ByteOffset, a)`+- Add `decodeFileOrFail :: Binary a => FilePath -> IO (Either (ByteOffset, String) a)`.+- Remove `Ord` class constraint from `Set` and `Map` Binary instances.++binary-0.6.4+------------++- Add `runGetOrFail :: Get a -> L.ByteString -> Either (L.ByteString, ByteOffset, String) (L.ByteString, ByteOffset, a)`.++binary-0.6.3+------------++- Documentation tweeks, internal restructuring, more tests.++binary-0.6.2+------------++- `some` and `many` more efficient.+- Fix bug where `bytesRead` returned the wrong value.+- Documentation improvements.++binary-0.6.1+------------++- Fix bug where a decoder could return with `Partial` after the previous reply was `Nothing`.++binary-0.6.0.0+--------------
− index.html
@@ -1,161 +0,0 @@-<?xml version="1.0" encoding="iso-8859-1"?>-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">--<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">-<head>- <title>Data.Binary - efficient, pure binary serialisation for Haskell</title>- <link rel="stylesheet" href="http://www.cse.unsw.edu.au/~dons/main.css" type="text/css" />-</head>--<body xml:lang="en" lang="en">-- <div id="content">-- <h2>Data.Binary</h2>--<table width="80%" align="center"> <tr><td>-- <h3>About</h3>- <p>- Data.Binary is a library for high performance binary serialisation- of <a href="http://haskell.org">Haskell</a> data. It uses the- <a href="http://www.cse.unsw.edu.au/~dons/fps.html"- >ByteString</a> library to achieve efficient, lazy reading and- writing of structures in binary format.- </p>-- <p>- Chris Eidhof writes on his use of Data.Binary implementing a- full-text search engine:- </p>- <pre>- "The communication with Sphinx is done using a quite low-level binary- protocol, but Data.Binary saved the day: it made it very easy for us- to decode all the binary things. Especially the use of the Get and- Put monads are a big improvement over the manual reading and keeping- track of positions, as is done in the PHP/Python clients."- </pre>-- <h3>Example</h3>- For example, to serialise an interpreter's abstract syntax tree to- binary format:-<pre><span class='keyword'>import</span> <span class='conid'>Data</span><span class='varop'>.</span><span class='conid'>Binary</span>-<span class='keyword'>import</span> <span class='conid'>Control</span><span class='varop'>.</span><span class='conid'>Monad</span>-<span class='keyword'>import</span> <span class='conid'>Codec</span><span class='varop'>.</span><span class='conid'>Compression</span><span class='varop'>.</span><span class='conid'>GZip</span>--<span class='comment'>-- A Haskell AST structure</span>-<span class='keyword'>data</span> <span class='conid'>Exp</span> <span class='keyglyph'>=</span> <span class='conid'>IntE</span> <span class='conid'>Int</span>- <span class='keyglyph'>|</span> <span class='conid'>OpE</span> <span class='conid'>String</span> <span class='conid'>Exp</span> <span class='conid'>Exp</span>- <span class='keyword'>deriving</span> <span class='conid'>Eq</span>--<span class='comment'>-- An instance of Binary to encode and decode an Exp in binary</span>-<span class='keyword'>instance</span> <span class='conid'>Binary</span> <span class='conid'>Exp</span> <span class='keyword'>where</span>- <span class='varid'>put</span> <span class='layout'>(</span><span class='conid'>IntE</span> <span class='varid'>i</span><span class='layout'>)</span> <span class='keyglyph'>=</span> <span class='varid'>put</span> <span class='layout'>(</span><span class='num'>0</span> <span class='keyglyph'>::</span> <span class='conid'>Word8</span><span class='layout'>)</span> <span class='varop'>>></span> <span class='varid'>put</span> <span class='varid'>i</span>- <span class='varid'>put</span> <span class='layout'>(</span><span class='conid'>OpE</span> <span class='varid'>s</span> <span class='varid'>e1</span> <span class='varid'>e2</span><span class='layout'>)</span> <span class='keyglyph'>=</span> <span class='varid'>put</span> <span class='layout'>(</span><span class='num'>1</span> <span class='keyglyph'>::</span> <span class='conid'>Word8</span><span class='layout'>)</span> <span class='varop'>>></span> <span class='varid'>put</span> <span class='varid'>s</span> <span class='varop'>>></span> <span class='varid'>put</span> <span class='varid'>e1</span> <span class='varop'>>></span> <span class='varid'>put</span> <span class='varid'>e2</span>- <span class='varid'>get</span> <span class='keyglyph'>=</span> <span class='keyword'>do</span> <span class='varid'>tag</span> <span class='keyglyph'><-</span> <span class='varid'>getWord8</span>- <span class='keyword'>case</span> <span class='varid'>tag</span> <span class='keyword'>of</span>- <span class='num'>0</span> <span class='keyglyph'>-></span> <span class='varid'>liftM</span> <span class='conid'>IntE</span> <span class='varid'>get</span>- <span class='num'>1</span> <span class='keyglyph'>-></span> <span class='varid'>liftM3</span> <span class='conid'>OpE</span> <span class='varid'>get</span> <span class='varid'>get</span> <span class='varid'>get</span>--<span class='comment'>-- A test expression</span>-<span class='varid'>e</span> <span class='keyglyph'>=</span> <span class='conid'>OpE</span> <span class='str'>"*"</span> <span class='layout'>(</span><span class='conid'>IntE</span> <span class='num'>7</span><span class='layout'>)</span> <span class='layout'>(</span><span class='conid'>OpE</span> <span class='str'>"/"</span> <span class='layout'>(</span><span class='conid'>IntE</span> <span class='num'>4</span><span class='layout'>)</span> <span class='layout'>(</span><span class='conid'>IntE</span> <span class='num'>2</span><span class='layout'>)</span><span class='layout'>)</span>--<span class='comment'>-- Serialise and compress with gzip, then decompress and deserialise</span>-<span class='varid'>main</span> <span class='keyglyph'>=</span> <span class='keyword'>do</span>- <span class='keyword'>let</span> <span class='varid'>t</span> <span class='keyglyph'>=</span> <span class='varid'>compress</span> <span class='layout'>(</span><span class='varid'>encode</span> <span class='varid'>e</span><span class='layout'>)</span>- <span class='varid'>print</span> <span class='varid'>t</span>- <span class='keyword'>let</span> <span class='varid'>e'</span> <span class='keyglyph'>=</span> <span class='varid'>decode</span> <span class='layout'>(</span><span class='varid'>decompress</span> <span class='varid'>t</span><span class='layout'>)</span>- <span class='varid'>print</span> <span class='layout'>(</span><span class='varid'>e</span> <span class='varop'>==</span> <span class='varid'>e'</span><span class='layout'>)</span>-</pre>-- <h3>Download</h3>-- <table width="100%"><tr valign="top">- <td><h4>stable release</h4>- <table>- <tr><td>- <a href="http://hackage.haskell.org/cgi-bin/hackage-scripts/package/binary-0.4.2"- >binary 0.4.2</a> - </td><td>(Apr 2008)</td></tr>-- <tr><td>- <a href="http://hackage.haskell.org/cgi-bin/hackage-scripts/package/binary-0.4.1"- >binary 0.4.1</a> - </td><td>(Oct 2007)</td></tr>-- <tr><td>- <a href="http://hackage.haskell.org/cgi-bin/hackage-scripts/package/binary-0.4"- >binary 0.4</a> - </td><td>(Oct 2007)</td></tr>-- <tr><td>- <a href="http://hackage.haskell.org/cgi-bin/hackage-scripts/package/binary-0.3"- >binary 0.3</a> - </td><td>(Mar 2007)</td></tr>-- <tr><td>- <a href="http://hackage.haskell.org/cgi-bin/hackage-scripts/package/binary-0.3"- >binary 0.2</a> - </td><td>(Jan 2007)</td></tr>-- </table> - </td>- <td><h4>development branch</h4>- <table>- <tr><td>- darcs get <a href="http://code.haskell.org/binary"- >http://code.haskell.org/binary</a>- </td></tr>- </table>- </td> </tr> </table>-- <h3>Download</h3>- <ul>- <li>- <a href="http://hackage.haskell.org/packages/archive/binary/0.4.1/doc/html/Data-Binary.html">Documentation</a>- </li>- </ul>-- <h3>Project Activity</h3>-- <center>- <img src="http://www.cse.unsw.edu.au/~dons/images/commits/community/binary-commits.png"- alt="binary commit statistics" />- </center>-- <h3>Starring...</h3>-- The Binary Strike Force- <ul>- <li>Lennart Kolmodin </li>- <li>Duncan Coutts </li>- <li>Don Stewart </li>- <li>Spencer Janssen </li>- <li>David Himmelstrup </li>- <li>Björn Bringert </li>- <li>Ross Paterson </li>- <li>Einar Karttunen </li>- <li>John Meacham </li>- <li>Ulf Norell </li>- <li>Bryan O'Sullivan </li>- <li>Tomasz Zielonka </li>- <li>Florian Weimer </li>- <li>Judah Jacobson </li>- </ul>--</td></tr> </table>--<img src="http://xmonad.org/images/HPC.badge.jpg" alt="covered by HPC" />-<img src="http://xmonad.org/images/cabal.png" alt="built with Cabal" />-<img src="http://xmonad.org/images/quickcheck.png" alt="tested with QuickCheck" />-- </div>--- <div id="footer">-Mon Jul 14 11:37:21 PDT 2008- </div>--</body>-</html>
src/Data/Binary.hs view
@@ -1,7 +1,5 @@ {-# LANGUAGE CPP #-}-#if __GLASGOW_HASKELL__ >= 701 {-# LANGUAGE Trustworthy #-}-#endif ----------------------------------------------------------------------------- -- | -- Module : Data.Binary@@ -10,7 +8,7 @@ -- -- Maintainer : Lennart Kolmodin <kolmodin@gmail.com> -- Stability : unstable--- Portability : portable to Hugs and GHC. Requires the FFI and some flexible instances+-- Portability : portable to Hugs and GHC. Requires the FFI and some flexible instances. -- -- Binary serialisation of Haskell values to and from lazy 'ByteString's. -- The Binary library provides methods for encoding Haskell values as@@ -18,17 +16,25 @@ -- then be written to disk, sent over the network, or further processed -- (for example, compressed with gzip). ----- The @Binary@ package is notable in that it provides both pure, and+-- The @binary@ package is notable in that it provides both pure, and -- high performance serialisation. ----- Values are always encoded in network order (big endian) form, and--- encoded data should be portable across machine endianness, word size,--- or compiler version. For example, data encoded using the 'Binary' class--- could be written from GHC, and read back in Hugs.+-- Values encoded using the 'Binary' class are always encoded in network order+-- (big endian) form, and encoded data should be portable across+-- machine endianness, word size, or compiler version. For example,+-- data encoded using the 'Binary' class could be written on any machine,+-- and read back on any another. ----- You can either provide a hand written implementation of the 'Binary' class,--- or derive one using the generic support. See 'GBinary'.+-- If the specifics of the data format is not important to you, for example,+-- you are more interested in serializing and deserializing values than+-- in which format will be used, it is possible to derive 'Binary'+-- instances using the generic support. See 'GBinaryGet' and+-- 'GBinaryPut'. --+-- If you have specific requirements about the encoding format, you can use+-- the encoding and decoding primitives directly, see the modules+-- "Data.Binary.Get" and "Data.Binary.Put".+-- ----------------------------------------------------------------------------- module Data.Binary (@@ -38,11 +44,10 @@ -- ** Example -- $example -#ifdef GENERICS -- * Generic support -- $generics- , GBinary(..)-#endif+ , GBinaryGet(..)+ , GBinaryPut(..) -- * The Get and Put monads , Get@@ -55,14 +60,12 @@ -- * Binary serialisation , encode -- :: Binary a => a -> ByteString , decode -- :: Binary a => ByteString -> a+ , decodeOrFail -- * IO functions for serialisation , encodeFile -- :: Binary a => FilePath -> a -> IO () , decodeFile -- :: Binary a => FilePath -> IO a---- Lazy put and get--- , lazyPut--- , lazyGet+ , decodeFileOrFail , module Data.Word -- useful @@ -73,12 +76,13 @@ import Data.Binary.Class import Data.Binary.Put import Data.Binary.Get-#ifdef GENERICS import Data.Binary.Generic ()-#endif +import qualified Data.ByteString as B ( hGet, length ) import Data.ByteString.Lazy (ByteString) import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Lazy.Internal as L ( defaultChunkSize )+import System.IO ( withBinaryFile, IOMode(ReadMode) ) ------------------------------------------------------------------------ @@ -95,12 +99,12 @@ -- structure to serialise: -- -- > instance Binary Exp where--- > put (IntE i) = do put (0 :: Word8)--- > put i--- > put (OpE s e1 e2) = do put (1 :: Word8)--- > put s--- > put e1--- > put e2+-- > put (IntE i) = do put (0 :: Word8)+-- > put i+-- > put (OpE s e1 e2) = do put (1 :: Word8)+-- > put s+-- > put e1+-- > put e2 -- > -- > get = do t <- get :: Get Word8 -- > case t of@@ -165,14 +169,25 @@ {-# INLINE encode #-} -- | Decode a value from a lazy ByteString, reconstructing the original structure.--- decode :: Binary a => ByteString -> a decode = runGet get +-- | Decode a value from a lazy ByteString. Returning 'Left' on failure and+-- 'Right' on success. In both cases the unconsumed input and the number of+-- consumed bytes is returned. In case of failure, a human-readable error+-- message will be returned as well.+--+-- /Since: 0.7.0.0/+decodeOrFail :: Binary a => L.ByteString+ -> Either (L.ByteString, ByteOffset, String)+ (L.ByteString, ByteOffset, a)+decodeOrFail = runGetOrFail get++ ------------------------------------------------------------------------ -- Convenience IO operations --- | Lazily serialise a value to a file+-- | Lazily serialise a value to a file. -- -- This is just a convenience function, it's defined simply as: --@@ -185,40 +200,34 @@ encodeFile :: Binary a => FilePath -> a -> IO () encodeFile f v = L.writeFile f (encode v) --- | Lazily reconstruct a value previously written to a file.------ This is just a convenience function, it's defined simply as:------ > decodeFile f = return . decode =<< B.readFile f------ So for example if you wanted to decompress as well, you could use:------ > return . decode . decompress =<< B.readFile f------ After contructing the data from the input file, 'decodeFile' checks--- if the file is empty, and in doing so will force the associated file--- handle closed, if it is indeed empty. If the file is not empty,--- it is up to the decoding instance to consume the rest of the data,--- or otherwise finalise the resource.+-- | Decode a value from a file. In case of errors, 'error' will+-- be called with the error message. --+-- /Since: 0.7.0.0/ decodeFile :: Binary a => FilePath -> IO a decodeFile f = do- s <- L.readFile f- return $ runGet (do v <- get- m <- isEmpty- m `seq` return v) s+ result <- decodeFileOrFail f+ case result of+ Right x -> return x+ Left (_,str) -> error str --- needs bytestring 0.9.1.x to work+-- | Decode a value from a file. In case of success, the value will be returned+-- in 'Right'. In case of decoder errors, the error message together with+-- the byte offset will be returned.+decodeFileOrFail :: Binary a => FilePath -> IO (Either (ByteOffset, String) a)+decodeFileOrFail f =+ withBinaryFile f ReadMode $ \h -> do+ feed (runGetIncremental get) h+ where -- TODO: put in Data.Binary.Get and name pushFromHandle?+ feed (Done _ _ x) _ = return (Right x)+ feed (Fail _ pos str) _ = return (Left (pos, str))+ feed (Partial k) h = do+ chunk <- B.hGet h L.defaultChunkSize+ case B.length chunk of+ 0 -> feed (k Nothing) h+ _ -> feed (k (Just chunk)) h --------------------------------------------------------------------------- Lazy put and get---- lazyPut :: (Binary a) => a -> Put--- lazyPut a = put (encode a)---- lazyGet :: (Binary a) => Get a--- lazyGet = fmap decode get- -- $generics -- -- Beginning with GHC 7.2, it is possible to use binary serialization
src/Data/Binary/Builder.hs view
@@ -1,23 +1,23 @@-{-# LANGUAGE CPP #-}-#if __GLASGOW_HASKELL__ >= 701+{-# LANGUAGE CPP, MagicHash #-} {-# LANGUAGE Safe #-}-#endif+ ----------------------------------------------------------------------------- -- | -- Module : Data.Binary.Builder -- Copyright : Lennart Kolmodin, Ross Paterson -- License : BSD3-style (see LICENSE)--- +-- -- Maintainer : Lennart Kolmodin <kolmodin@gmail.com> -- Stability : experimental -- Portability : portable to Hugs and GHC ----- Efficient construction of lazy bytestrings.+-- Efficient constructions of lazy bytestrings. --+-- This now re-exports 'Data.ByteString.Lazy.Builder'.+-- ----------------------------------------------------------------------------- module Data.Binary.Builder (- -- * The Builder type Builder , toLazyByteString@@ -28,7 +28,9 @@ , append , fromByteString -- :: S.ByteString -> Builder , fromLazyByteString -- :: L.ByteString -> Builder-+#if MIN_VERSION_bytestring(0,10,4)+ , fromShortByteString -- :: T.ByteString -> Builder+#endif -- * Flushing the buffer state , flush @@ -37,21 +39,236 @@ , putWord16be -- :: Word16 -> Builder , putWord32be -- :: Word32 -> Builder , putWord64be -- :: Word64 -> Builder+ , putInt16be -- :: Int16 -> Builder+ , putInt32be -- :: Int32 -> Builder+ , putInt64be -- :: Int64 -> Builder -- ** Little-endian writes , putWord16le -- :: Word16 -> Builder , putWord32le -- :: Word32 -> Builder , putWord64le -- :: Word64 -> Builder+ , putInt16le -- :: Int16 -> Builder+ , putInt32le -- :: Int32 -> Builder+ , putInt64le -- :: Int64 -> Builder -- ** Host-endian, unaligned writes , putWordhost -- :: Word -> Builder , putWord16host -- :: Word16 -> Builder , putWord32host -- :: Word32 -> Builder , putWord64host -- :: Word64 -> Builder+ , putInthost -- :: Int -> Builder+ , putInt16host -- :: Int16 -> Builder+ , putInt32host -- :: Int32 -> Builder+ , putInt64host -- :: Int64 -> Builder -- ** Unicode , putCharUtf8+ , putStringUtf8+ ) where - ) where+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L -import Data.Binary.Builder.Base+#if MIN_VERSION_bytestring(0,10,4)+import qualified Data.ByteString.Short as T+#endif++import qualified Data.ByteString.Builder as B+import qualified Data.ByteString.Builder.Prim as Prim+import Data.ByteString.Builder ( Builder, toLazyByteString )+import Data.ByteString.Builder.Extra ( flush )+import Data.Monoid+import Data.Word+import Data.Int+import Prelude -- Silence AMP warning.++------------------------------------------------------------------------++-- | /O(1)./ The empty Builder, satisfying+--+-- * @'toLazyByteString' 'empty' = 'L.empty'@+--+empty :: Builder+empty = mempty+{-# INLINE empty #-}++-- | /O(1)./ A Builder taking a single byte, satisfying+--+-- * @'toLazyByteString' ('singleton' b) = 'L.singleton' b@+--+singleton :: Word8 -> Builder+singleton = B.word8+{-# INLINE singleton #-}++------------------------------------------------------------------------++-- | /O(1)./ The concatenation of two Builders, an associative operation+-- with identity 'empty', satisfying+--+-- * @'toLazyByteString' ('append' x y) = 'L.append' ('toLazyByteString' x) ('toLazyByteString' y)@+--+append :: Builder -> Builder -> Builder+append = mappend+{-# INLINE append #-}++-- | /O(1)./ A Builder taking a 'S.ByteString', satisfying+--+-- * @'toLazyByteString' ('fromByteString' bs) = 'L.fromChunks' [bs]@+--+fromByteString :: S.ByteString -> Builder+fromByteString = B.byteString+{-# INLINE fromByteString #-}++-- | /O(1)./ A Builder taking a lazy 'L.ByteString', satisfying+--+-- * @'toLazyByteString' ('fromLazyByteString' bs) = bs@+--+fromLazyByteString :: L.ByteString -> Builder+fromLazyByteString = B.lazyByteString+{-# INLINE fromLazyByteString #-}++#if MIN_VERSION_bytestring(0,10,4)+-- | /O(n)./ A builder taking 'T.ShortByteString' and copy it to a Builder,+-- satisfying+--+-- * @'toLazyByteString' ('fromShortByteString' bs) = 'L.fromChunks' ['T.fromShort' bs]+fromShortByteString :: T.ShortByteString -> Builder+fromShortByteString = B.shortByteString+{-# INLINE fromShortByteString #-}+#endif++------------------------------------------------------------------------++-- | Write a Word16 in big endian format+putWord16be :: Word16 -> Builder+putWord16be = B.word16BE+{-# INLINE putWord16be #-}++-- | Write a Word16 in little endian format+putWord16le :: Word16 -> Builder+putWord16le = B.word16LE+{-# INLINE putWord16le #-}++-- | Write a Word32 in big endian format+putWord32be :: Word32 -> Builder+putWord32be = B.word32BE+{-# INLINE putWord32be #-}++-- | Write a Word32 in little endian format+putWord32le :: Word32 -> Builder+putWord32le = B.word32LE+{-# INLINE putWord32le #-}++-- | Write a Word64 in big endian format+putWord64be :: Word64 -> Builder+putWord64be = B.word64BE+{-# INLINE putWord64be #-}++-- | Write a Word64 in little endian format+putWord64le :: Word64 -> Builder+putWord64le = B.word64LE+{-# INLINE putWord64le #-}++-- | Write a Int16 in big endian format+putInt16be :: Int16 -> Builder+putInt16be = B.int16BE+{-# INLINE putInt16be #-}++-- | Write a Int16 in little endian format+putInt16le :: Int16 -> Builder+putInt16le = B.int16LE+{-# INLINE putInt16le #-}++-- | Write a Int32 in big endian format+putInt32be :: Int32 -> Builder+putInt32be = B.int32BE+{-# INLINE putInt32be #-}++-- | Write a Int32 in little endian format+putInt32le :: Int32 -> Builder+putInt32le = B.int32LE+{-# INLINE putInt32le #-}++-- | Write a Int64 in big endian format+putInt64be :: Int64 -> Builder+putInt64be = B.int64BE++-- | Write a Int64 in little endian format+putInt64le :: Int64 -> Builder+putInt64le = B.int64LE+++------------------------------------------------------------------------+-- Unaligned, word size ops++-- | /O(1)./ A Builder taking a single native machine word. The word is+-- written in host order, host endian form, for the machine you're on.+-- On a 64 bit machine the Word is an 8 byte value, on a 32 bit machine,+-- 4 bytes. Values written this way are not portable to+-- different endian or word sized machines, without conversion.+--+putWordhost :: Word -> Builder+putWordhost = Prim.primFixed Prim.wordHost+{-# INLINE putWordhost #-}++-- | Write a Word16 in native host order and host endianness.+-- 2 bytes will be written, unaligned.+putWord16host :: Word16 -> Builder+putWord16host = Prim.primFixed Prim.word16Host+{-# INLINE putWord16host #-}++-- | Write a Word32 in native host order and host endianness.+-- 4 bytes will be written, unaligned.+putWord32host :: Word32 -> Builder+putWord32host = Prim.primFixed Prim.word32Host+{-# INLINE putWord32host #-}++-- | Write a Word64 in native host order.+-- On a 32 bit machine we write two host order Word32s, in big endian form.+-- 8 bytes will be written, unaligned.+putWord64host :: Word64 -> Builder+putWord64host = Prim.primFixed Prim.word64Host+{-# INLINE putWord64host #-}++-- | /O(1)./ A Builder taking a single native machine word. The word is+-- written in host order, host endian form, for the machine you're on.+-- On a 64 bit machine the Int is an 8 byte value, on a 32 bit machine,+-- 4 bytes. Values written this way are not portable to+-- different endian or word sized machines, without conversion.+--+putInthost :: Int -> Builder+putInthost = Prim.primFixed Prim.intHost+{-# INLINE putInthost #-}++-- | Write a Int16 in native host order and host endianness.+-- 2 bytes will be written, unaligned.+putInt16host :: Int16 -> Builder+putInt16host = Prim.primFixed Prim.int16Host+{-# INLINE putInt16host #-}++-- | Write a Int32 in native host order and host endianness.+-- 4 bytes will be written, unaligned.+putInt32host :: Int32 -> Builder+putInt32host = Prim.primFixed Prim.int32Host+{-# INLINE putInt32host #-}++-- | Write a Int64 in native host order.+-- On a 32 bit machine we write two host order Int32s, in big endian form.+-- 8 bytes will be written, unaligned.+putInt64host :: Int64 -> Builder+putInt64host = Prim.primFixed Prim.int64Host+{-# INLINE putInt64host #-}+++------------------------------------------------------------------------+-- Unicode++-- | Write a character using UTF-8 encoding.+putCharUtf8 :: Char -> Builder+putCharUtf8 = Prim.primBounded Prim.charUtf8+{-# INLINE putCharUtf8 #-}++-- | Write a String using UTF-8 encoding.+putStringUtf8 :: String -> Builder+putStringUtf8 = Prim.primMapListBounded Prim.charUtf8+{-# INLINE putStringUtf8 #-}
− src/Data/Binary/Builder/Base.hs
@@ -1,514 +0,0 @@-{-# LANGUAGE BangPatterns, CPP, MagicHash #-}-#if __GLASGOW_HASKELL__ >= 701-{-# LANGUAGE Trustworthy #-}-#endif---------------------------------------------------------------------------------- |--- Module : Data.Binary.Builder.Base--- Copyright : Lennart Kolmodin, Ross Paterson--- License : BSD3-style (see LICENSE)------ Maintainer : Lennart Kolmodin <kolmodin@gmail.com>--- Stability : experimental--- Portability : portable to Hugs and GHC------ A module exporting types and functions that are shared by--- 'Data.Binary.Builder' and 'Data.Binary.Builder.Internal'.-----------------------------------------------------------------------------------#if defined(__GLASGOW_HASKELL__) && !defined(__HADDOCK__)-#include "MachDeps.h"-#endif--module Data.Binary.Builder.Base (- -- * The Builder type- Builder- , toLazyByteString-- -- * Constructing Builders- , empty- , singleton- , append- , fromByteString -- :: S.ByteString -> Builder- , fromLazyByteString -- :: L.ByteString -> Builder-- -- * Flushing the buffer state- , flush-- -- * Derived Builders- -- ** Big-endian writes- , putWord16be -- :: Word16 -> Builder- , putWord32be -- :: Word32 -> Builder- , putWord64be -- :: Word64 -> Builder-- -- ** Little-endian writes- , putWord16le -- :: Word16 -> Builder- , putWord32le -- :: Word32 -> Builder- , putWord64le -- :: Word64 -> Builder-- -- ** Host-endian, unaligned writes- , putWordhost -- :: Word -> Builder- , putWord16host -- :: Word16 -> Builder- , putWord32host -- :: Word32 -> Builder- , putWord64host -- :: Word64 -> Builder-- -- ** Unicode- , putCharUtf8-- -- * Low-level construction of Builders- , writeN- , writeAtMost- ) where--import qualified Data.ByteString as S-import qualified Data.ByteString.Lazy as L-import Data.Monoid-import Data.Word-import Foreign--import System.IO.Unsafe as IO ( unsafePerformIO )--#ifdef BYTESTRING_IN_BASE-import Data.ByteString.Base (inlinePerformIO)-import qualified Data.ByteString.Base as S-import qualified Data.ByteString.Lazy.Base as L-#else-import Data.ByteString.Internal (inlinePerformIO)-import qualified Data.ByteString.Internal as S-import qualified Data.ByteString.Lazy.Internal as L-#endif--#if defined(__GLASGOW_HASKELL__) && !defined(__HADDOCK__)-import GHC.Base-import GHC.Word (Word32(..),Word16(..),Word64(..))-# if WORD_SIZE_IN_BITS < 64-import GHC.Word (uncheckedShiftRL64#)-# endif-#endif------------------------------------------------------------------------------ | A 'Builder' is an efficient way to build lazy 'L.ByteString's.--- There are several functions for constructing 'Builder's, but only one--- to inspect them: to extract any data, you have to turn them into lazy--- 'L.ByteString's using 'toLazyByteString'.------ Internally, a 'Builder' constructs a lazy 'L.Bytestring' by filling byte--- arrays piece by piece. As each buffer is filled, it is \'popped\'--- off, to become a new chunk of the resulting lazy 'L.ByteString'.--- All this is hidden from the user of the 'Builder'.--newtype Builder = Builder {- runBuilder :: (Buffer -> IO L.ByteString)- -> Buffer- -> IO L.ByteString- }--instance Monoid Builder where- mempty = empty- {-# INLINE mempty #-}- mappend = append- {-# INLINE mappend #-}- mconcat = foldr mappend mempty- {-# INLINE mconcat #-}------------------------------------------------------------------------------ | /O(1)./ The empty Builder, satisfying------ * @'toLazyByteString' 'empty' = 'L.empty'@----empty :: Builder-empty = Builder (\ k b -> k b)-{-# INLINE empty #-}---- | /O(1)./ A Builder taking a single byte, satisfying------ * @'toLazyByteString' ('singleton' b) = 'L.singleton' b@----singleton :: Word8 -> Builder-singleton = writeN 1 . flip poke-{-# INLINE singleton #-}------------------------------------------------------------------------------ | /O(1)./ The concatenation of two Builders, an associative operation--- with identity 'empty', satisfying------ * @'toLazyByteString' ('append' x y) = 'L.append' ('toLazyByteString' x) ('toLazyByteString' y)@----append :: Builder -> Builder -> Builder-append (Builder f) (Builder g) = Builder (f . g)-{-# INLINE [0] append #-}---- | /O(1)./ A Builder taking a 'S.ByteString', satisfying------ * @'toLazyByteString' ('fromByteString' bs) = 'L.fromChunks' [bs]@----fromByteString :: S.ByteString -> Builder-fromByteString bs- | S.null bs = empty- | otherwise = flush `append` mapBuilder (L.Chunk bs)-{-# INLINE fromByteString #-}---- | /O(1)./ A Builder taking a lazy 'L.ByteString', satisfying------ * @'toLazyByteString' ('fromLazyByteString' bs) = bs@----fromLazyByteString :: L.ByteString -> Builder-fromLazyByteString bss = flush `append` mapBuilder (bss `L.append`)-{-# INLINE fromLazyByteString #-}------------------------------------------------------------------------------ Our internal buffer type-data Buffer = Buffer {-# UNPACK #-} !(ForeignPtr Word8)- {-# UNPACK #-} !Int -- offset- {-# UNPACK #-} !Int -- used bytes- {-# UNPACK #-} !Int -- length left------------------------------------------------------------------------------ | /O(n)./ Extract a lazy 'L.ByteString' from a 'Builder'.--- The construction work takes place if and when the relevant part of--- the lazy 'L.ByteString' is demanded.----toLazyByteString :: Builder -> L.ByteString-toLazyByteString m = IO.unsafePerformIO $ do- buf <- newBuffer defaultSize- runBuilder (m `append` flush) (const (return L.Empty)) buf-{-# INLINE toLazyByteString #-}---- | /O(1)./ Pop the 'S.ByteString' we have constructed so far, if any,--- yielding a new chunk in the result lazy 'L.ByteString'.-flush :: Builder-flush = Builder $ \ k buf@(Buffer p o u l) ->- if u == 0 -- Invariant (from Data.ByteString.Lazy)- then k buf- else let !b = Buffer p (o+u) 0 l- !bs = S.PS p o u- in return $! L.Chunk bs (inlinePerformIO (k b))--------------------------------------------------------------------------------- copied from Data.ByteString.Lazy----defaultSize :: Int-defaultSize = 32 * k - overhead- where k = 1024- overhead = 2 * sizeOf (undefined :: Int)------------------------------------------------------------------------------ | Sequence an IO operation on the buffer-withBuffer :: (Buffer -> IO Buffer) -> Builder-withBuffer f = Builder $ \ k buf -> f buf >>= k-{-# INLINE withBuffer #-}---- | Get the size of the buffer-withSize :: (Int -> Builder) -> Builder-withSize f = Builder $ \ k buf@(Buffer _ _ _ l) ->- runBuilder (f l) k buf---- | Map the resulting list of bytestrings.-mapBuilder :: (L.ByteString -> L.ByteString) -> Builder-mapBuilder f = Builder (fmap f .)------------------------------------------------------------------------------ | Ensure that there are at least @n@ many bytes available.-ensureFree :: Int -> Builder-ensureFree n = n `seq` withSize $ \ l ->- if n <= l then empty else- flush `append` withBuffer (const (newBuffer (max n defaultSize)))-{-# INLINE [0] ensureFree #-}---- | Ensure that @n@ bytes are available, and then use @f@ to write at--- most @n@ bytes into memory. @f@ must return the actual number of--- bytes written.-writeAtMost :: Int -> (Ptr Word8 -> IO Int) -> Builder-writeAtMost n f = ensureFree n `append` withBuffer (writeBuffer f)-{-# INLINE [0] writeAtMost #-}---- | Ensure that @n@ bytes are available, and then use @f@ to write--- exactly @n@ bytes into memory.-writeN :: Int -> (Ptr Word8 -> IO ()) -> Builder-writeN n f = writeAtMost n (\ p -> f p >> return n)-{-# INLINE writeN #-}--writeBuffer :: (Ptr Word8 -> IO Int) -> Buffer -> IO Buffer-writeBuffer f (Buffer fp o u l) = do- n <- withForeignPtr fp (\p -> f (p `plusPtr` (o+u)))- return $! Buffer fp o (u+n) (l-n)-{-# INLINE writeBuffer #-}--newBuffer :: Int -> IO Buffer-newBuffer size = do- fp <- S.mallocByteString size- return $! Buffer fp 0 0 size-{-# INLINE newBuffer #-}--------------------------------------------------------------------------------- We rely on the fromIntegral to do the right masking for us.--- The inlining here is critical, and can be worth 4x performance------- | Write a Word16 in big endian format-putWord16be :: Word16 -> Builder-putWord16be w = writeN 2 $ \p -> do- poke p (fromIntegral (shiftr_w16 w 8) :: Word8)- poke (p `plusPtr` 1) (fromIntegral (w) :: Word8)-{-# INLINE putWord16be #-}---- | Write a Word16 in little endian format-putWord16le :: Word16 -> Builder-putWord16le w = writeN 2 $ \p -> do- poke p (fromIntegral (w) :: Word8)- poke (p `plusPtr` 1) (fromIntegral (shiftr_w16 w 8) :: Word8)-{-# INLINE putWord16le #-}---- putWord16le w16 = writeN 2 (\p -> poke (castPtr p) w16)---- | Write a Word32 in big endian format-putWord32be :: Word32 -> Builder-putWord32be w = writeN 4 $ \p -> do- poke p (fromIntegral (shiftr_w32 w 24) :: Word8)- poke (p `plusPtr` 1) (fromIntegral (shiftr_w32 w 16) :: Word8)- poke (p `plusPtr` 2) (fromIntegral (shiftr_w32 w 8) :: Word8)- poke (p `plusPtr` 3) (fromIntegral (w) :: Word8)-{-# INLINE putWord32be #-}------- a data type to tag Put/Check. writes construct these which are then--- inlined and flattened. matching Checks will be more robust with rules.------- | Write a Word32 in little endian format-putWord32le :: Word32 -> Builder-putWord32le w = writeN 4 $ \p -> do- poke p (fromIntegral (w) :: Word8)- poke (p `plusPtr` 1) (fromIntegral (shiftr_w32 w 8) :: Word8)- poke (p `plusPtr` 2) (fromIntegral (shiftr_w32 w 16) :: Word8)- poke (p `plusPtr` 3) (fromIntegral (shiftr_w32 w 24) :: Word8)-{-# INLINE putWord32le #-}---- on a little endian machine:--- putWord32le w32 = writeN 4 (\p -> poke (castPtr p) w32)---- | Write a Word64 in big endian format-putWord64be :: Word64 -> Builder-#if WORD_SIZE_IN_BITS < 64------ To avoid expensive 64 bit shifts on 32 bit machines, we cast to--- Word32, and write that----putWord64be w =- let a = fromIntegral (shiftr_w64 w 32) :: Word32- b = fromIntegral w :: Word32- in writeN 8 $ \p -> do- poke p (fromIntegral (shiftr_w32 a 24) :: Word8)- poke (p `plusPtr` 1) (fromIntegral (shiftr_w32 a 16) :: Word8)- poke (p `plusPtr` 2) (fromIntegral (shiftr_w32 a 8) :: Word8)- poke (p `plusPtr` 3) (fromIntegral (a) :: Word8)- poke (p `plusPtr` 4) (fromIntegral (shiftr_w32 b 24) :: Word8)- poke (p `plusPtr` 5) (fromIntegral (shiftr_w32 b 16) :: Word8)- poke (p `plusPtr` 6) (fromIntegral (shiftr_w32 b 8) :: Word8)- poke (p `plusPtr` 7) (fromIntegral (b) :: Word8)-#else-putWord64be w = writeN 8 $ \p -> do- poke p (fromIntegral (shiftr_w64 w 56) :: Word8)- poke (p `plusPtr` 1) (fromIntegral (shiftr_w64 w 48) :: Word8)- poke (p `plusPtr` 2) (fromIntegral (shiftr_w64 w 40) :: Word8)- poke (p `plusPtr` 3) (fromIntegral (shiftr_w64 w 32) :: Word8)- poke (p `plusPtr` 4) (fromIntegral (shiftr_w64 w 24) :: Word8)- poke (p `plusPtr` 5) (fromIntegral (shiftr_w64 w 16) :: Word8)- poke (p `plusPtr` 6) (fromIntegral (shiftr_w64 w 8) :: Word8)- poke (p `plusPtr` 7) (fromIntegral (w) :: Word8)-#endif-{-# INLINE putWord64be #-}---- | Write a Word64 in little endian format-putWord64le :: Word64 -> Builder--#if WORD_SIZE_IN_BITS < 64-putWord64le w =- let b = fromIntegral (shiftr_w64 w 32) :: Word32- a = fromIntegral w :: Word32- in writeN 8 $ \p -> do- poke (p) (fromIntegral (a) :: Word8)- poke (p `plusPtr` 1) (fromIntegral (shiftr_w32 a 8) :: Word8)- poke (p `plusPtr` 2) (fromIntegral (shiftr_w32 a 16) :: Word8)- poke (p `plusPtr` 3) (fromIntegral (shiftr_w32 a 24) :: Word8)- poke (p `plusPtr` 4) (fromIntegral (b) :: Word8)- poke (p `plusPtr` 5) (fromIntegral (shiftr_w32 b 8) :: Word8)- poke (p `plusPtr` 6) (fromIntegral (shiftr_w32 b 16) :: Word8)- poke (p `plusPtr` 7) (fromIntegral (shiftr_w32 b 24) :: Word8)-#else-putWord64le w = writeN 8 $ \p -> do- poke p (fromIntegral (w) :: Word8)- poke (p `plusPtr` 1) (fromIntegral (shiftr_w64 w 8) :: Word8)- poke (p `plusPtr` 2) (fromIntegral (shiftr_w64 w 16) :: Word8)- poke (p `plusPtr` 3) (fromIntegral (shiftr_w64 w 24) :: Word8)- poke (p `plusPtr` 4) (fromIntegral (shiftr_w64 w 32) :: Word8)- poke (p `plusPtr` 5) (fromIntegral (shiftr_w64 w 40) :: Word8)- poke (p `plusPtr` 6) (fromIntegral (shiftr_w64 w 48) :: Word8)- poke (p `plusPtr` 7) (fromIntegral (shiftr_w64 w 56) :: Word8)-#endif-{-# INLINE putWord64le #-}---- on a little endian machine:--- putWord64le w64 = writeN 8 (\p -> poke (castPtr p) w64)----------------------------------------------------------------------------- Unaligned, word size ops---- | /O(1)./ A Builder taking a single native machine word. The word is--- written in host order, host endian form, for the machine you're on.--- On a 64 bit machine the Word is an 8 byte value, on a 32 bit machine,--- 4 bytes. Values written this way are not portable to--- different endian or word sized machines, without conversion.----putWordhost :: Word -> Builder-putWordhost w =- writeN (sizeOf (undefined :: Word)) (\p -> poke (castPtr p) w)-{-# INLINE putWordhost #-}---- | Write a Word16 in native host order and host endianness.--- 2 bytes will be written, unaligned.-putWord16host :: Word16 -> Builder-putWord16host w16 =- writeN (sizeOf (undefined :: Word16)) (\p -> poke (castPtr p) w16)-{-# INLINE putWord16host #-}---- | Write a Word32 in native host order and host endianness.--- 4 bytes will be written, unaligned.-putWord32host :: Word32 -> Builder-putWord32host w32 =- writeN (sizeOf (undefined :: Word32)) (\p -> poke (castPtr p) w32)-{-# INLINE putWord32host #-}---- | Write a Word64 in native host order.--- On a 32 bit machine we write two host order Word32s, in big endian form.--- 8 bytes will be written, unaligned.-putWord64host :: Word64 -> Builder-putWord64host w =- writeN (sizeOf (undefined :: Word64)) (\p -> poke (castPtr p) w)-{-# INLINE putWord64host #-}----------------------------------------------------------------------------- Unicode---- Code lifted from the text package by Bryan O'Sullivan.---- | Write a character using UTF-8 encoding.-putCharUtf8 :: Char -> Builder-putCharUtf8 x = writeAtMost 4 $ \ p -> case undefined of- _ | n <= 0x7F -> poke p c >> return 1- | n <= 0x07FF -> do- poke p a2- poke (p `plusPtr` 1) b2- return 2- | n <= 0xFFFF -> do- poke p a3- poke (p `plusPtr` 1) b3- poke (p `plusPtr` 2) c3- return 3- | otherwise -> do- poke p a4- poke (p `plusPtr` 1) b4- poke (p `plusPtr` 2) c4- poke (p `plusPtr` 3) d4- return 4- where- n = ord x- c = fromIntegral n- (a2,b2) = ord2 x- (a3,b3,c3) = ord3 x- (a4,b4,c4,d4) = ord4 x--ord2 :: Char -> (Word8,Word8)-ord2 c = (x1,x2)- where- n = ord c- x1 = fromIntegral $ (n `shiftR` 6) + 0xC0- x2 = fromIntegral $ (n .&. 0x3F) + 0x80--ord3 :: Char -> (Word8,Word8,Word8)-ord3 c = (x1,x2,x3)- where- n = ord c- x1 = fromIntegral $ (n `shiftR` 12) + 0xE0- x2 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80- x3 = fromIntegral $ (n .&. 0x3F) + 0x80--ord4 :: Char -> (Word8,Word8,Word8,Word8)-ord4 c = (x1,x2,x3,x4)- where- n = ord c- x1 = fromIntegral $ (n `shiftR` 18) + 0xF0- x2 = fromIntegral $ ((n `shiftR` 12) .&. 0x3F) + 0x80- x3 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80- x4 = fromIntegral $ (n .&. 0x3F) + 0x80----------------------------------------------------------------------------- Unchecked shifts--{-# INLINE shiftr_w16 #-}-shiftr_w16 :: Word16 -> Int -> Word16-{-# INLINE shiftr_w32 #-}-shiftr_w32 :: Word32 -> Int -> Word32-{-# INLINE shiftr_w64 #-}-shiftr_w64 :: Word64 -> Int -> Word64--#if defined(__GLASGOW_HASKELL__) && !defined(__HADDOCK__)-shiftr_w16 (W16# w) (I# i) = W16# (w `uncheckedShiftRL#` i)-shiftr_w32 (W32# w) (I# i) = W32# (w `uncheckedShiftRL#` i)--# if WORD_SIZE_IN_BITS < 64-shiftr_w64 (W64# w) (I# i) = W64# (w `uncheckedShiftRL64#` i)-# else-shiftr_w64 (W64# w) (I# i) = W64# (w `uncheckedShiftRL#` i)-# endif--#else-shiftr_w16 = shiftR-shiftr_w32 = shiftR-shiftr_w64 = shiftR-#endif----------------------------------------------------------------------------- Some nice rules for Builder--#if __GLASGOW_HASKELL__ >= 700--- In versions of GHC prior to 7.0 these rules would make GHC believe--- that 'writeN' and 'ensureFree' are recursive and the rules wouldn't--- fire.-{-# RULES--"append/writeAtMost" forall a b (f::Ptr Word8 -> IO Int)- (g::Ptr Word8 -> IO Int) ws.- append (writeAtMost a f) (append (writeAtMost b g) ws) =- append (writeAtMost (a+b) (\p -> f p >>= \n ->- g (p `plusPtr` n) >>= \m ->- let s = n+m in s `seq` return s)) ws--"writeAtMost/writeAtMost" forall a b (f::Ptr Word8 -> IO Int)- (g::Ptr Word8 -> IO Int).- append (writeAtMost a f) (writeAtMost b g) =- writeAtMost (a+b) (\p -> f p >>= \n ->- g (p `plusPtr` n) >>= \m ->- let s = n+m in s `seq` return s)--"ensureFree/ensureFree" forall a b .- append (ensureFree a) (ensureFree b) = ensureFree (max a b)--"flush/flush"- append flush flush = flush-- #-}-#endif
− src/Data/Binary/Builder/Internal.hs
@@ -1,28 +0,0 @@-{-# LANGUAGE CPP #-}-#if __GLASGOW_HASKELL__ >= 701-{-# LANGUAGE Trustworthy #-}-#endif--------------------------------------------------------------------------------- |--- Module : Data.Binary.Builder.Internal--- Copyright : Lennart Kolmodin, Ross Paterson--- License : BSD3-style (see LICENSE)--- --- Maintainer : Lennart Kolmodin <kolmodin@gmail.com>--- Stability : experimental--- Portability : portable to Hugs and GHC------ A module containing semi-public 'Builder' internals that exposes--- low level construction functions. Modules which extend the--- 'Builder' system will need to use this module while ideally most--- users will be able to make do with the public interface modules.-----------------------------------------------------------------------------------module Data.Binary.Builder.Internal (- -- * Low-level construction of Builders- writeN- , writeAtMost- ) where--import Data.Binary.Builder.Base
src/Data/Binary/Class.hs view
@@ -1,10 +1,23 @@ {-# LANGUAGE CPP, FlexibleContexts #-}-#if __GLASGOW_HASKELL__ >= 701+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE PatternGuards #-} {-# LANGUAGE Trustworthy #-}++#if __GLASGOW_HASKELL__ >= 706+{-# LANGUAGE PolyKinds #-} #endif-#ifdef GENERICS-{-# LANGUAGE DefaultSignatures #-}++#if MIN_VERSION_base(4,8,0)+#define HAS_NATURAL+#define HAS_VOID #endif++#if MIN_VERSION_base(4,7,0)+#define HAS_FIXED_CONSTRUCTOR+#endif+ ----------------------------------------------------------------------------- -- | -- Module : Data.Binary.Class@@ -24,29 +37,55 @@ -- * The Binary class Binary(..) -#ifdef GENERICS -- * Support for generics- , GBinary(..)-#endif+ , GBinaryGet(..)+ , GBinaryPut(..) ) where import Data.Word+import Data.Bits+import Data.Int+import Data.Complex (Complex(..))+#ifdef HAS_VOID+import Data.Void+#endif import Data.Binary.Put import Data.Binary.Get +#if ! MIN_VERSION_base(4,8,0)+import Control.Applicative+import Data.Monoid (mempty)+#endif+import qualified Data.Monoid as Monoid+import Data.Monoid ((<>))+#if MIN_VERSION_base(4,8,0)+import Data.Functor.Identity (Identity (..))+#endif+#if MIN_VERSION_base(4,9,0)+import qualified Data.List.NonEmpty as NE+import qualified Data.Semigroup as Semigroup+#endif import Control.Monad-import Foreign import Data.ByteString.Lazy (ByteString) import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Builder.Prim as Prim -import Data.Char (chr,ord)-import Data.List (unfoldr)+import Data.List (unfoldr, foldl') -- And needed for the instances:+#if MIN_VERSION_base(4,10,0)+import Type.Reflection+import Type.Reflection.Unsafe+import Data.Kind (Type)+import GHC.Exts (RuntimeRep(..), VecCount, VecElem)+#endif import qualified Data.ByteString as B+#if MIN_VERSION_bytestring(0,10,4)+import qualified Data.ByteString.Short as BS+#endif import qualified Data.Map as Map import qualified Data.Set as Set import qualified Data.IntMap as IntMap@@ -57,25 +96,35 @@ import Data.Array.Unboxed -#ifdef GENERICS import GHC.Generics++#ifdef HAS_NATURAL+import Numeric.Natural #endif +import qualified Data.Fixed as Fixed+ -- -- This isn't available in older Hugs or older GHC ---#if __GLASGOW_HASKELL__ >= 606 import qualified Data.Sequence as Seq import qualified Data.Foldable as Fold-#endif +import GHC.Fingerprint++import Data.Version (Version(..))+ ------------------------------------------------------------------------ -#ifdef GENERICS-class GBinary f where+-- Factored into two classes because this makes GHC optimize the+-- instances faster. This doesn't matter for builds of binary,+-- but it matters a lot for end-users who write 'instance Binary T'.+-- See also: https://ghc.haskell.org/trac/ghc/ticket/9630+class GBinaryPut f where gput :: f t -> Put++class GBinaryGet f where gget :: Get (f t)-#endif -- | The 'Binary' class provides 'put' and 'get', methods to encode and -- decode a Haskell value to a lazy 'ByteString'. It mirrors the 'Read' and@@ -100,32 +149,59 @@ -- | Decode a value in the Get monad get :: Get t -#ifdef GENERICS- default put :: (Generic t, GBinary (Rep t)) => t -> Put+ -- | Encode a list of values in the Put monad.+ -- The default implementation may be overridden to be more efficient+ -- but must still have the same encoding format.+ putList :: [t] -> Put+ putList = defaultPutList++ default put :: (Generic t, GBinaryPut (Rep t)) => t -> Put put = gput . from - default get :: (Generic t, GBinary (Rep t)) => Get t+ default get :: (Generic t, GBinaryGet (Rep t)) => Get t get = to `fmap` gget-#endif +{-# INLINE defaultPutList #-}+defaultPutList :: Binary a => [a] -> Put+defaultPutList xs = put (length xs) <> mapM_ put xs+ ------------------------------------------------------------------------ -- Simple instances +#ifdef HAS_VOID+-- Void never gets written nor reconstructed since it's impossible to have a+-- value of that type++-- | /Since: 0.8.0.0/+instance Binary Void where+ put = absurd+ get = mzero+#endif+ -- The () type need never be written to disk: values of singleton type -- can be reconstructed from the type alone instance Binary () where- put () = return ()+ put () = mempty get = return () -- Bools are encoded as a byte in the range 0 .. 1 instance Binary Bool where put = putWord8 . fromIntegral . fromEnum- get = liftM (toEnum . fromIntegral) getWord8+ get = getWord8 >>= toBool+ where+ toBool 0 = return False+ toBool 1 = return True+ toBool c = fail ("Could not map value " ++ show c ++ " to Bool") -- Values of type 'Ordering' are encoded as a byte in the range 0 .. 2 instance Binary Ordering where put = putWord8 . fromIntegral . fromEnum- get = liftM (toEnum . fromIntegral) getWord8+ get = getWord8 >>= toOrd+ where+ toOrd 0 = return LT+ toOrd 1 = return EQ+ toOrd 2 = return GT+ toOrd c = fail ("Could not map value " ++ show c ++ " to Ordering") ------------------------------------------------------------------------ -- Words and Ints@@ -133,54 +209,94 @@ -- Words8s are written as bytes instance Binary Word8 where put = putWord8+ {-# INLINE putList #-}+ putList xs =+ put (length xs)+ <> putBuilder (Prim.primMapListFixed Prim.word8 xs) get = getWord8 -- Words16s are written as 2 bytes in big-endian (network) order instance Binary Word16 where put = putWord16be+ {-# INLINE putList #-}+ putList xs =+ put (length xs)+ <> putBuilder (Prim.primMapListFixed Prim.word16BE xs) get = getWord16be -- Words32s are written as 4 bytes in big-endian (network) order instance Binary Word32 where put = putWord32be+ {-# INLINE putList #-}+ putList xs =+ put (length xs)+ <> putBuilder (Prim.primMapListFixed Prim.word32BE xs) get = getWord32be -- Words64s are written as 8 bytes in big-endian (network) order instance Binary Word64 where put = putWord64be+ {-# INLINE putList #-}+ putList xs =+ put (length xs)+ <> putBuilder (Prim.primMapListFixed Prim.word64BE xs) get = getWord64be -- Int8s are written as a single byte. instance Binary Int8 where- put i = put (fromIntegral i :: Word8)- get = liftM fromIntegral (get :: Get Word8)+ put = putInt8+ {-# INLINE putList #-}+ putList xs =+ put (length xs)+ <> putBuilder (Prim.primMapListFixed Prim.int8 xs)+ get = getInt8 -- Int16s are written as a 2 bytes in big endian format instance Binary Int16 where- put i = put (fromIntegral i :: Word16)- get = liftM fromIntegral (get :: Get Word16)+ put = putInt16be+ {-# INLINE putList #-}+ putList xs =+ put (length xs)+ <> putBuilder (Prim.primMapListFixed Prim.int16BE xs)+ get = getInt16be -- Int32s are written as a 4 bytes in big endian format instance Binary Int32 where- put i = put (fromIntegral i :: Word32)- get = liftM fromIntegral (get :: Get Word32)+ put = putInt32be+ {-# INLINE putList #-}+ putList xs =+ put (length xs)+ <> putBuilder (Prim.primMapListFixed Prim.int32BE xs)+ get = getInt32be --- Int64s are written as a 4 bytes in big endian format+-- Int64s are written as a 8 bytes in big endian format instance Binary Int64 where- put i = put (fromIntegral i :: Word64)- get = liftM fromIntegral (get :: Get Word64)+ put = putInt64be+ {-# INLINE putList #-}+ putList xs =+ put (length xs)+ <> putBuilder (Prim.primMapListFixed Prim.int64BE xs)+ get = getInt64be ------------------------------------------------------------------------ -- Words are are written as Word64s, that is, 8 bytes in big endian format instance Binary Word where- put i = put (fromIntegral i :: Word64)- get = liftM fromIntegral (get :: Get Word64)+ put = putWord64be . fromIntegral+ {-# INLINE putList #-}+ putList xs =+ put (length xs)+ <> putBuilder (Prim.primMapListFixed Prim.word64BE (map fromIntegral xs))+ get = liftM fromIntegral getWord64be -- Ints are are written as Int64s, that is, 8 bytes in big endian format instance Binary Int where- put i = put (fromIntegral i :: Int64)- get = liftM fromIntegral (get :: Get Int64)+ put = putInt64be . fromIntegral+ {-# INLINE putList #-}+ putList xs =+ put (length xs)+ <> putBuilder (Prim.primMapListFixed Prim.int64BE (map fromIntegral xs))+ get = liftM fromIntegral getInt64be ------------------------------------------------------------------------ --@@ -198,17 +314,16 @@ instance Binary Integer where {-# INLINE put #-}- put n | n >= lo && n <= hi = do- putWord8 0- put (fromIntegral n :: SmallInt) -- fast path+ put n | n >= lo && n <= hi =+ putBuilder (Prim.primFixed (Prim.word8 Prim.>*< Prim.int32BE) (0, fromIntegral n)) where lo = fromIntegral (minBound :: SmallInt) :: Integer hi = fromIntegral (maxBound :: SmallInt) :: Integer - put n = do+ put n = putWord8 1- put sign- put (unroll (abs n)) -- unroll the bytes+ <> put sign+ <> put (unroll (abs n)) -- unroll the bytes where sign = fromIntegral (signum n) :: Word8 @@ -222,20 +337,58 @@ let v = roll bytes return $! if sign == (1 :: Word8) then v else - v +-- | /Since: 0.8.0.0/+#ifdef HAS_FIXED_CONSTRUCTOR+instance Binary (Fixed.Fixed a) where+ put (Fixed.MkFixed a) = put a+ get = Fixed.MkFixed `liftM` get+#else+instance forall a. Fixed.HasResolution a => Binary (Fixed.Fixed a) where+ -- Using undefined :: Maybe a as a proxy, as Data.Proxy is introduced only in base-4.7+ put x = put (truncate (x * fromInteger (Fixed.resolution (undefined :: Maybe a))) :: Integer)+ get = (\x -> fromInteger x / fromInteger (Fixed.resolution (undefined :: Maybe a))) `liftM` get+#endif+ -- -- Fold and unfold an Integer to and from a list of its bytes ---unroll :: Integer -> [Word8]+unroll :: (Integral a, Bits a) => a -> [Word8] unroll = unfoldr step where step 0 = Nothing step i = Just (fromIntegral i, i `shiftR` 8) -roll :: [Word8] -> Integer-roll = foldr unstep 0+roll :: (Integral a, Bits a) => [Word8] -> a+roll = foldl' unstep 0 . reverse where- unstep b a = a `shiftL` 8 .|. fromIntegral b+ unstep a b = a `shiftL` 8 .|. fromIntegral b +#ifdef HAS_NATURAL+-- Fixed-size type for a subset of Natural+type NaturalWord = Word64++-- | /Since: 0.7.3.0/+instance Binary Natural where+ {-# INLINE put #-}+ put n | n <= hi =+ putWord8 0+ <> put (fromIntegral n :: NaturalWord) -- fast path+ where+ hi = fromIntegral (maxBound :: NaturalWord) :: Natural++ put n =+ putWord8 1+ <> put (unroll (abs n)) -- unroll the bytes++ {-# INLINE get #-}+ get = do+ tag <- get :: Get Word8+ case tag of+ 0 -> liftM fromIntegral (get :: Get NaturalWord)+ _ -> do bytes <- get+ return $! roll bytes+#endif+ {- --@@ -303,32 +456,21 @@ -} instance (Binary a,Integral a) => Binary (R.Ratio a) where- put r = put (R.numerator r) >> put (R.denominator r)+ put r = put (R.numerator r) <> put (R.denominator r) get = liftM2 (R.%) get get +instance Binary a => Binary (Complex a) where+ {-# INLINE put #-}+ put (r :+ i) = put (r, i)+ {-# INLINE get #-}+ get = (\(r,i) -> r :+ i) <$> get+ ------------------------------------------------------------------------ -- Char is serialised as UTF-8 instance Binary Char where- put a | c <= 0x7f = put (fromIntegral c :: Word8)- | c <= 0x7ff = do put (0xc0 .|. y)- put (0x80 .|. z)- | c <= 0xffff = do put (0xe0 .|. x)- put (0x80 .|. y)- put (0x80 .|. z)- | c <= 0x10ffff = do put (0xf0 .|. w)- put (0x80 .|. x)- put (0x80 .|. y)- put (0x80 .|. z)- | otherwise = error "Not a valid Unicode code point"- where- c = ord a- z, y, x, w :: Word8- z = fromIntegral (c .&. 0x3f)- y = fromIntegral (shiftR c 6 .&. 0x3f)- x = fromIntegral (shiftR c 12 .&. 0x3f)- w = fromIntegral (shiftR c 18 .&. 0x7)-+ put = putCharUtf8+ putList str = put (length str) <> putStringUtf8 str get = do let getByte = liftM (fromIntegral :: Word8 -> Int) get shiftL6 = flip shiftL 6 :: Int -> Int@@ -349,25 +491,37 @@ z <- liftM (xor 0x80) getByte return (z .|. shiftL6 (y .|. shiftL6 (x .|. shiftL6 (xor 0xf0 w))))- return $! chr r+ getChr r+ where+ getChr w+ | w <= 0x10ffff = return $! toEnum $ fromEnum w+ | otherwise = fail "Not a valid Unicode code point!" ------------------------------------------------------------------------ -- Instances for the first few tuples instance (Binary a, Binary b) => Binary (a,b) where- put (a,b) = put a >> put b+ {-# INLINE put #-}+ put (a,b) = put a <> put b+ {-# INLINE get #-} get = liftM2 (,) get get instance (Binary a, Binary b, Binary c) => Binary (a,b,c) where- put (a,b,c) = put a >> put b >> put c+ {-# INLINE put #-}+ put (a,b,c) = put a <> put b <> put c+ {-# INLINE get #-} get = liftM3 (,,) get get get instance (Binary a, Binary b, Binary c, Binary d) => Binary (a,b,c,d) where- put (a,b,c,d) = put a >> put b >> put c >> put d+ {-# INLINE put #-}+ put (a,b,c,d) = put a <> put b <> put c <> put d+ {-# INLINE get #-} get = liftM4 (,,,) get get get get instance (Binary a, Binary b, Binary c, Binary d, Binary e) => Binary (a,b,c,d,e) where- put (a,b,c,d,e) = put a >> put b >> put c >> put d >> put e+ {-# INLINE put #-}+ put (a,b,c,d,e) = put a <> put b <> put c <> put d <> put e+ {-# INLINE get #-} get = liftM5 (,,,,) get get get get get --@@ -376,39 +530,55 @@ instance (Binary a, Binary b, Binary c, Binary d, Binary e, Binary f) => Binary (a,b,c,d,e,f) where+ {-# INLINE put #-} put (a,b,c,d,e,f) = put (a,(b,c,d,e,f))+ {-# INLINE get #-} get = do (a,(b,c,d,e,f)) <- get ; return (a,b,c,d,e,f) instance (Binary a, Binary b, Binary c, Binary d, Binary e, Binary f, Binary g) => Binary (a,b,c,d,e,f,g) where+ {-# INLINE put #-} put (a,b,c,d,e,f,g) = put (a,(b,c,d,e,f,g))+ {-# INLINE get #-} get = do (a,(b,c,d,e,f,g)) <- get ; return (a,b,c,d,e,f,g) instance (Binary a, Binary b, Binary c, Binary d, Binary e, Binary f, Binary g, Binary h) => Binary (a,b,c,d,e,f,g,h) where+ {-# INLINE put #-} put (a,b,c,d,e,f,g,h) = put (a,(b,c,d,e,f,g,h))+ {-# INLINE get #-} get = do (a,(b,c,d,e,f,g,h)) <- get ; return (a,b,c,d,e,f,g,h) instance (Binary a, Binary b, Binary c, Binary d, Binary e, Binary f, Binary g, Binary h, Binary i) => Binary (a,b,c,d,e,f,g,h,i) where+ {-# INLINE put #-} put (a,b,c,d,e,f,g,h,i) = put (a,(b,c,d,e,f,g,h,i))+ {-# INLINE get #-} get = do (a,(b,c,d,e,f,g,h,i)) <- get ; return (a,b,c,d,e,f,g,h,i) instance (Binary a, Binary b, Binary c, Binary d, Binary e, Binary f, Binary g, Binary h, Binary i, Binary j) => Binary (a,b,c,d,e,f,g,h,i,j) where+ {-# INLINE put #-} put (a,b,c,d,e,f,g,h,i,j) = put (a,(b,c,d,e,f,g,h,i,j))+ {-# INLINE get #-} get = do (a,(b,c,d,e,f,g,h,i,j)) <- get ; return (a,b,c,d,e,f,g,h,i,j) ------------------------------------------------------------------------ -- Container types +#if MIN_VERSION_base(4,8,0)+instance Binary a => Binary (Identity a) where+ put (Identity x) = put x+ get = Identity <$> get+#endif+ instance Binary a => Binary [a] where- put l = put (length l) >> mapM_ put l- get = do n <- get :: Get Int- getMany n+ put = putList+ get = do n <- get :: Get Int+ getMany n -- | 'getMany n' get 'n' elements in order, without blowing the stack. getMany :: Binary a => Int -> Get [a]@@ -423,7 +593,7 @@ instance (Binary a) => Binary (Maybe a) where put Nothing = putWord8 0- put (Just x) = putWord8 1 >> put x+ put (Just x) = putWord8 1 <> put x get = do w <- getWord8 case w of@@ -431,8 +601,8 @@ _ -> liftM Just get instance (Binary a, Binary b) => Binary (Either a b) where- put (Left a) = putWord8 0 >> put a- put (Right b) = putWord8 1 >> put b+ put (Left a) = putWord8 0 <> put a+ put (Right b) = putWord8 1 <> put b get = do w <- getWord8 case w of@@ -443,8 +613,8 @@ -- ByteStrings (have specially efficient instances) instance Binary B.ByteString where- put bs = do put (B.length bs)- putByteString bs+ put bs = put (B.length bs)+ <> putByteString bs get = get >>= getByteString --@@ -453,39 +623,46 @@ -- Requires 'flexible instances' -- instance Binary ByteString where- put bs = do put (fromIntegral (L.length bs) :: Int)- putLazyByteString bs+ put bs = put (fromIntegral (L.length bs) :: Int)+ <> putLazyByteString bs get = get >>= getLazyByteString ++#if MIN_VERSION_bytestring(0,10,4)+instance Binary BS.ShortByteString where+ put bs = put (BS.length bs)+ <> putShortByteString bs+ get = get >>= fmap BS.toShort . getByteString+#endif+ ------------------------------------------------------------------------ -- Maps and Sets -instance (Ord a, Binary a) => Binary (Set.Set a) where- put s = put (Set.size s) >> mapM_ put (Set.toAscList s)+instance (Binary a) => Binary (Set.Set a) where+ put s = put (Set.size s) <> mapM_ put (Set.toAscList s) get = liftM Set.fromDistinctAscList get -instance (Ord k, Binary k, Binary e) => Binary (Map.Map k e) where- put m = put (Map.size m) >> mapM_ put (Map.toAscList m)+instance (Binary k, Binary e) => Binary (Map.Map k e) where+ put m = put (Map.size m) <> mapM_ put (Map.toAscList m) get = liftM Map.fromDistinctAscList get instance Binary IntSet.IntSet where- put s = put (IntSet.size s) >> mapM_ put (IntSet.toAscList s)+ put s = put (IntSet.size s) <> mapM_ put (IntSet.toAscList s) get = liftM IntSet.fromDistinctAscList get instance (Binary e) => Binary (IntMap.IntMap e) where- put m = put (IntMap.size m) >> mapM_ put (IntMap.toAscList m)+ put m = put (IntMap.size m) <> mapM_ put (IntMap.toAscList m) get = liftM IntMap.fromDistinctAscList get ------------------------------------------------------------------------ -- Queues and Sequences -#if __GLASGOW_HASKELL__ >= 606 -- -- This is valid Hugs, but you need the most recent Hugs -- instance (Binary e) => Binary (Seq.Seq e) where- put s = put (Seq.length s) >> Fold.mapM_ put s+ put s = put (Seq.length s) <> Fold.mapM_ put s get = do n <- get :: Get Int rep Seq.empty n get where rep xs 0 _ = return $! xs@@ -493,34 +670,38 @@ x <- g rep (xs Seq.|> x) (n-1) g -#endif- ------------------------------------------------------------------------ -- Floating point instance Binary Double where put d = put (decodeFloat d)- get = liftM2 encodeFloat get get+ get = do+ x <- get+ y <- get+ return $! encodeFloat x y instance Binary Float where put f = put (decodeFloat f)- get = liftM2 encodeFloat get get+ get = do+ x <- get+ y <- get+ return $! encodeFloat x y ------------------------------------------------------------------------ -- Trees instance (Binary e) => Binary (T.Tree e) where- put (T.Node r s) = put r >> put s+ put (T.Node r s) = put r <> put s get = liftM2 T.Node get get ------------------------------------------------------------------------ -- Arrays instance (Binary i, Ix i, Binary e) => Binary (Array i e) where- put a = do+ put a = put (bounds a)- put (rangeSize $ bounds a) -- write the length- mapM_ put (elems a) -- now the elems.+ <> put (rangeSize $ bounds a) -- write the length+ <> mapM_ put (elems a) -- now the elems. get = do bs <- get n <- get -- read the length@@ -531,12 +712,311 @@ -- The IArray UArray e constraint is non portable. Requires flexible instances -- instance (Binary i, Ix i, Binary e, IArray UArray e) => Binary (UArray i e) where- put a = do+ put a = put (bounds a)- put (rangeSize $ bounds a) -- now write the length- mapM_ put (elems a)+ <> put (rangeSize $ bounds a) -- now write the length+ <> mapM_ put (elems a) get = do bs <- get n <- get xs <- getMany n return (listArray bs xs)++------------------------------------------------------------------------+-- Fingerprints++-- | /Since: 0.7.6.0/+instance Binary Fingerprint where+ put (Fingerprint x1 x2) = put x1 <> put x2+ get = do+ x1 <- get+ x2 <- get+ return $! Fingerprint x1 x2++------------------------------------------------------------------------+-- Version++-- | /Since: 0.8.0.0/+instance Binary Version where+ put (Version br tags) = put br <> put tags+ get = Version <$> get <*> get++------------------------------------------------------------------------+-- Data.Monoid datatypes++-- | /Since: 0.8.4.0/+instance Binary a => Binary (Monoid.Dual a) where+ get = fmap Monoid.Dual get+ put = put . Monoid.getDual++-- | /Since: 0.8.4.0/+instance Binary Monoid.All where+ get = fmap Monoid.All get+ put = put . Monoid.getAll++-- | /Since: 0.8.4.0/+instance Binary Monoid.Any where+ get = fmap Monoid.Any get+ put = put . Monoid.getAny++-- | /Since: 0.8.4.0/+instance Binary a => Binary (Monoid.Sum a) where+ get = fmap Monoid.Sum get+ put = put . Monoid.getSum++-- | /Since: 0.8.4.0/+instance Binary a => Binary (Monoid.Product a) where+ get = fmap Monoid.Product get+ put = put . Monoid.getProduct++-- | /Since: 0.8.4.0/+instance Binary a => Binary (Monoid.First a) where+ get = fmap Monoid.First get+ put = put . Monoid.getFirst++-- | /Since: 0.8.4.0/+instance Binary a => Binary (Monoid.Last a) where+ get = fmap Monoid.Last get+ put = put . Monoid.getLast++#if MIN_VERSION_base(4,8,0)+-- | /Since: 0.8.4.0/+instance Binary (f a) => Binary (Monoid.Alt f a) where+ get = fmap Monoid.Alt get+ put = put . Monoid.getAlt+#endif++#if MIN_VERSION_base(4,9,0)+------------------------------------------------------------------------+-- Data.Semigroup datatypes++-- | /Since: 0.8.4.0/+instance Binary a => Binary (Semigroup.Min a) where+ get = fmap Semigroup.Min get+ put = put . Semigroup.getMin++-- | /Since: 0.8.4.0/+instance Binary a => Binary (Semigroup.Max a) where+ get = fmap Semigroup.Max get+ put = put . Semigroup.getMax++-- | /Since: 0.8.4.0/+instance Binary a => Binary (Semigroup.First a) where+ get = fmap Semigroup.First get+ put = put . Semigroup.getFirst++-- | /Since: 0.8.4.0/+instance Binary a => Binary (Semigroup.Last a) where+ get = fmap Semigroup.Last get+ put = put . Semigroup.getLast++-- | /Since: 0.8.4.0/+instance Binary a => Binary (Semigroup.Option a) where+ get = fmap Semigroup.Option get+ put = put . Semigroup.getOption++-- | /Since: 0.8.4.0/+instance Binary m => Binary (Semigroup.WrappedMonoid m) where+ get = fmap Semigroup.WrapMonoid get+ put = put . Semigroup.unwrapMonoid++-- | /Since: 0.8.4.0/+instance (Binary a, Binary b) => Binary (Semigroup.Arg a b) where+ get = liftM2 Semigroup.Arg get get+ put (Semigroup.Arg a b) = put a <> put b++------------------------------------------------------------------------+-- Non-empty lists++-- | /Since: 0.8.4.0/+instance Binary a => Binary (NE.NonEmpty a) where+ get = fmap NE.fromList get+ put = put . NE.toList+#endif++------------------------------------------------------------------------+-- Typeable/Reflection++#if MIN_VERSION_base(4,10,0)++-- $typeable-instances+--+-- 'Binary' instances for GHC's "Type.Reflection", "Data.Typeable", and+-- kind-system primitives are only provided with @base-4.10.0@ (shipped with GHC+-- 8.2.1). In prior GHC releases some of these instances were provided by+-- 'GHCi.TH.Binary' in the @ghci@ package.+--+-- These include instances for,+--+-- * 'VecCount'+-- * 'VecElem'+-- * 'RuntimeRep'+-- * 'KindRep'+-- * 'TypeLitSort'+-- * 'TyCon'+-- * 'TypeRep'+-- * 'SomeTypeRep' (also known as 'Data.Typeable.TypeRep')+--++-- | @since 0.8.5.0. See #typeable-instances#+instance Binary VecCount where+ put = putWord8 . fromIntegral . fromEnum+ get = toEnum . fromIntegral <$> getWord8++-- | @since 0.8.5.0. See #typeable-instances#+instance Binary VecElem where+ put = putWord8 . fromIntegral . fromEnum+ get = toEnum . fromIntegral <$> getWord8++-- | @since 0.8.5.0. See #typeable-instances#+instance Binary RuntimeRep where+ put (VecRep a b) = putWord8 0 >> put a >> put b+ put (TupleRep reps) = putWord8 1 >> put reps+ put (SumRep reps) = putWord8 2 >> put reps+ put LiftedRep = putWord8 3+ put UnliftedRep = putWord8 4+ put IntRep = putWord8 5+ put WordRep = putWord8 6+ put Int64Rep = putWord8 7+ put Word64Rep = putWord8 8+ put AddrRep = putWord8 9+ put FloatRep = putWord8 10+ put DoubleRep = putWord8 11++ get = do+ tag <- getWord8+ case tag of+ 0 -> VecRep <$> get <*> get+ 1 -> TupleRep <$> get+ 2 -> SumRep <$> get+ 3 -> pure LiftedRep+ 4 -> pure UnliftedRep+ 5 -> pure IntRep+ 6 -> pure WordRep+ 7 -> pure Int64Rep+ 8 -> pure Word64Rep+ 9 -> pure AddrRep+ 10 -> pure FloatRep+ 11 -> pure DoubleRep+ _ -> fail "GHCi.TH.Binary.putRuntimeRep: invalid tag"++-- | @since 0.8.5.0. See #typeable-instances#+instance Binary TyCon where+ put tc = do+ put (tyConPackage tc)+ put (tyConModule tc)+ put (tyConName tc)+ put (tyConKindArgs tc)+ put (tyConKindRep tc)+ get = mkTyCon <$> get <*> get <*> get <*> get <*> get++-- | @since 0.8.5.0. See #typeable-instances#+instance Binary KindRep where+ put (KindRepTyConApp tc k) = putWord8 0 >> put tc >> put k+ put (KindRepVar bndr) = putWord8 1 >> put bndr+ put (KindRepApp a b) = putWord8 2 >> put a >> put b+ put (KindRepFun a b) = putWord8 3 >> put a >> put b+ put (KindRepTYPE r) = putWord8 4 >> put r+ put (KindRepTypeLit sort r) = putWord8 5 >> put sort >> put r++ get = do+ tag <- getWord8+ case tag of+ 0 -> KindRepTyConApp <$> get <*> get+ 1 -> KindRepVar <$> get+ 2 -> KindRepApp <$> get <*> get+ 3 -> KindRepFun <$> get <*> get+ 4 -> KindRepTYPE <$> get+ 5 -> KindRepTypeLit <$> get <*> get+ _ -> fail "GHCi.TH.Binary.putKindRep: invalid tag"++-- | @since 0.8.5.0. See #typeable-instances#+instance Binary TypeLitSort where+ put TypeLitSymbol = putWord8 0+ put TypeLitNat = putWord8 1+ get = do+ tag <- getWord8+ case tag of+ 0 -> pure TypeLitSymbol+ 1 -> pure TypeLitNat+ _ -> fail "GHCi.TH.Binary.putTypeLitSort: invalid tag"++putTypeRep :: TypeRep a -> Put+-- Special handling for TYPE, (->), and RuntimeRep due to recursive kind+-- relations.+-- See Note [Mutually recursive representations of primitive types]+putTypeRep rep -- Handle Type specially since it's so common+ | Just HRefl <- rep `eqTypeRep` (typeRep :: TypeRep Type)+ = put (0 :: Word8)+putTypeRep (Con' con ks) = do+ put (1 :: Word8)+ put con+ put ks+putTypeRep (App f x) = do+ put (2 :: Word8)+ putTypeRep f+ putTypeRep x+putTypeRep (Fun arg res) = do+ put (3 :: Word8)+ putTypeRep arg+ putTypeRep res+putTypeRep _ = fail "GHCi.TH.Binary.putTypeRep: Impossible"++getSomeTypeRep :: Get SomeTypeRep+getSomeTypeRep = do+ tag <- get :: Get Word8+ case tag of+ 0 -> return $ SomeTypeRep (typeRep :: TypeRep Type)+ 1 -> do con <- get :: Get TyCon+ ks <- get :: Get [SomeTypeRep]+ return $ SomeTypeRep $ mkTrCon con ks+ 2 -> do SomeTypeRep f <- getSomeTypeRep+ SomeTypeRep x <- getSomeTypeRep+ case typeRepKind f of+ Fun arg res ->+ case arg `eqTypeRep` typeRepKind x of+ Just HRefl -> do+ case typeRepKind res `eqTypeRep` (typeRep :: TypeRep Type) of+ Just HRefl -> return $ SomeTypeRep $ mkTrApp f x+ _ -> failure "Kind mismatch" []+ _ -> failure "Kind mismatch"+ [ "Found argument of kind: " ++ show (typeRepKind x)+ , "Where the constructor: " ++ show f+ , "Expects an argument of kind: " ++ show arg+ ]+ _ -> failure "Applied non-arrow type"+ [ "Applied type: " ++ show f+ , "To argument: " ++ show x+ ]+ 3 -> do SomeTypeRep arg <- getSomeTypeRep+ SomeTypeRep res <- getSomeTypeRep+ case typeRepKind arg `eqTypeRep` (typeRep :: TypeRep Type) of+ Just HRefl ->+ case typeRepKind res `eqTypeRep` (typeRep :: TypeRep Type) of+ Just HRefl -> return $ SomeTypeRep $ Fun arg res+ Nothing -> failure "Kind mismatch" []+ Nothing -> failure "Kind mismatch" []+ _ -> failure "Invalid SomeTypeRep" []+ where+ failure description info =+ fail $ unlines $ [ "GHCi.TH.Binary.getSomeTypeRep: "++description ]+ ++ map (" "++) info++instance Typeable a => Binary (TypeRep (a :: k)) where+ put = putTypeRep+ get = do+ SomeTypeRep rep <- getSomeTypeRep+ case rep `eqTypeRep` expected of+ Just HRefl -> pure rep+ Nothing -> fail $ unlines+ [ "GHCi.TH.Binary: Type mismatch"+ , " Deserialized type: " ++ show rep+ , " Expected type: " ++ show expected+ ]+ where expected = typeRep :: TypeRep a++instance Binary SomeTypeRep where+ put (SomeTypeRep rep) = putTypeRep rep+ get = getSomeTypeRep+#endif+
+ src/Data/Binary/FloatCast.hs view
@@ -0,0 +1,45 @@++{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE Trustworthy #-}++-- | This module was written based on+-- <http://hackage.haskell.org/package/reinterpret-cast-0.1.0/docs/src/Data-ReinterpretCast-Internal-ImplArray.html>.+--+-- Implements casting via a 1-elemnt STUArray, as described in+-- <http://stackoverflow.com/a/7002812/263061>.+module Data.Binary.FloatCast+ ( floatToWord+ , wordToFloat+ , doubleToWord+ , wordToDouble+ ) where++import Data.Word (Word32, Word64)+import Data.Array.ST (newArray, readArray, MArray, STUArray)+import Data.Array.Unsafe (castSTUArray)+import GHC.ST (runST, ST)++-- | Reinterpret-casts a `Float` to a `Word32`.+floatToWord :: Float -> Word32+floatToWord x = runST (cast x)+{-# INLINE floatToWord #-}++-- | Reinterpret-casts a `Word32` to a `Float`.+wordToFloat :: Word32 -> Float+wordToFloat x = runST (cast x)+{-# INLINE wordToFloat #-}++-- | Reinterpret-casts a `Double` to a `Word64`.+doubleToWord :: Double -> Word64+doubleToWord x = runST (cast x)+{-# INLINE doubleToWord #-}++-- | Reinterpret-casts a `Word64` to a `Double`.+wordToDouble :: Word64 -> Double+wordToDouble x = runST (cast x)+{-# INLINE wordToDouble #-}++cast :: (MArray (STUArray s) a (ST s),+ MArray (STUArray s) b (ST s)) => a -> ST s b+cast x = newArray (0 :: Int, 0) x >>= castSTUArray >>= flip readArray 0+{-# INLINE cast #-}
src/Data/Binary/Generic.hs view
@@ -1,7 +1,12 @@ {-# LANGUAGE BangPatterns, CPP, FlexibleInstances, KindSignatures,- ScopedTypeVariables, Trustworthy, TypeOperators, TypeSynonymInstances #-}+ ScopedTypeVariables, TypeOperators, TypeSynonymInstances #-}+{-# LANGUAGE Safe #-} {-# OPTIONS_GHC -fno-warn-orphans #-} +#if __GLASGOW_HASKELL__ >= 800+#define HAS_DATA_KIND+#endif+ ----------------------------------------------------------------------------- -- | -- Module : Data.Binary.Generic@@ -25,31 +30,46 @@ import Data.Binary.Put import Data.Bits import Data.Word+import Data.Monoid ((<>))+#ifdef HAS_DATA_KIND+import Data.Kind+#endif import GHC.Generics+import Prelude -- Silence AMP warning. -- Type without constructors-instance GBinary V1 where- gput _ = return ()+instance GBinaryPut V1 where+ gput _ = pure ()++instance GBinaryGet V1 where gget = return undefined -- Constructor without arguments-instance GBinary U1 where- gput U1 = return ()+instance GBinaryPut U1 where+ gput U1 = pure ()++instance GBinaryGet U1 where gget = return U1 -- Product: constructor with parameters-instance (GBinary a, GBinary b) => GBinary (a :*: b) where- gput (x :*: y) = gput x >> gput y+instance (GBinaryPut a, GBinaryPut b) => GBinaryPut (a :*: b) where+ gput (x :*: y) = gput x <> gput y++instance (GBinaryGet a, GBinaryGet b) => GBinaryGet (a :*: b) where gget = (:*:) <$> gget <*> gget -- Metadata (constructor name, etc)-instance GBinary a => GBinary (M1 i c a) where+instance GBinaryPut a => GBinaryPut (M1 i c a) where gput = gput . unM1++instance GBinaryGet a => GBinaryGet (M1 i c a) where gget = M1 <$> gget -- Constants, additional parameters, and rank-1 recursion-instance Binary a => GBinary (K1 i a) where+instance Binary a => GBinaryPut (K1 i a) where gput = put . unK1++instance Binary a => GBinaryGet (K1 i a) where gget = K1 <$> get -- Borrowed from the cereal package.@@ -65,20 +85,19 @@ #define PUTSUM(WORD) GUARD(WORD) = putSum (0 :: WORD) (fromIntegral size) #define GETSUM(WORD) GUARD(WORD) = (get :: Get WORD) >>= checkGetSum (fromIntegral size) -instance ( GSum a, GSum b- , GBinary a, GBinary b- , SumSize a, SumSize b) => GBinary (a :+: b) where+instance ( GSumPut a, GSumPut b+ , SumSize a, SumSize b) => GBinaryPut (a :+: b) where gput | PUTSUM(Word8) | PUTSUM(Word16) | PUTSUM(Word32) | PUTSUM(Word64) | otherwise = sizeError "encode" size where size = unTagged (sumSize :: Tagged (a :+: b) Word64)- {-# INLINE gput #-} +instance ( GSumGet a, GSumGet b+ , SumSize a, SumSize b) => GBinaryGet (a :+: b) where gget | GETSUM(Word8) | GETSUM(Word16) | GETSUM(Word32) | GETSUM(Word64) | otherwise = sizeError "decode" size where size = unTagged (sumSize :: Tagged (a :+: b) Word64)- {-# INLINE gget #-} sizeError :: Show size => String -> size -> error sizeError s size =@@ -86,45 +105,49 @@ ------------------------------------------------------------------------ -checkGetSum :: (Ord word, Num word, Bits word, GSum f)+checkGetSum :: (Ord word, Num word, Bits word, GSumGet f) => word -> word -> Get (f a) checkGetSum size code | code < size = getSum code size | otherwise = fail "Unknown encoding for constructor" {-# INLINE checkGetSum #-} -class GSum f where+class GSumGet f where getSum :: (Ord word, Num word, Bits word) => word -> word -> Get (f a)++class GSumPut f where putSum :: (Num w, Bits w, Binary w) => w -> w -> f a -> Put -instance (GSum a, GSum b, GBinary a, GBinary b) => GSum (a :+: b) where+instance (GSumGet a, GSumGet b) => GSumGet (a :+: b) where getSum !code !size | code < sizeL = L1 <$> getSum code sizeL | otherwise = R1 <$> getSum (code - sizeL) sizeR where sizeL = size `shiftR` 1 sizeR = size - sizeL- {-# INLINE getSum #-} +instance (GSumPut a, GSumPut b) => GSumPut (a :+: b) where putSum !code !size s = case s of L1 x -> putSum code sizeL x R1 x -> putSum (code + sizeL) sizeR x where sizeL = size `shiftR` 1 sizeR = size - sizeL- {-# INLINE putSum #-} -instance GBinary a => GSum (C1 c a) where+instance GBinaryGet a => GSumGet (C1 c a) where getSum _ _ = gget- {-# INLINE getSum #-} - putSum !code _ x = put code *> gput x- {-# INLINE putSum #-}+instance GBinaryPut a => GSumPut (C1 c a) where+ putSum !code _ x = put code <> gput x ------------------------------------------------------------------------ class SumSize f where sumSize :: Tagged f Word64 -newtype Tagged (s :: * -> *) b = Tagged {unTagged :: b}+#ifdef HAS_DATA_KIND+newtype Tagged (s :: Type -> Type) b = Tagged {unTagged :: b}+#else+newtype Tagged (s :: * -> *) b = Tagged {unTagged :: b}+#endif instance (SumSize a, SumSize b) => SumSize (a :+: b) where sumSize = Tagged $ unTagged (sumSize :: Tagged a Word64) +
src/Data/Binary/Get.hs view
@@ -1,7 +1,5 @@ {-# LANGUAGE CPP, RankNTypes, MagicHash, BangPatterns #-}-#if __GLASGOW_HASKELL__ >= 701 {-# LANGUAGE Trustworthy #-}-#endif #if defined(__GLASGOW_HASKELL__) && !defined(__HADDOCK__) #include "MachDeps.h"@@ -12,25 +10,134 @@ -- Module : Data.Binary.Get -- Copyright : Lennart Kolmodin -- License : BSD3-style (see LICENSE)--- +-- -- Maintainer : Lennart Kolmodin <kolmodin@gmail.com> -- Stability : experimental -- Portability : portable to Hugs and GHC. ----- The Get monad. A monad for efficiently building structures from+-- The 'Get' monad. A monad for efficiently building structures from -- encoded lazy ByteStrings. --+-- Primitives are available to decode words of various sizes, both big and+-- little endian.+--+-- Let's decode binary data representing illustrated here.+-- In this example the values are in little endian.+--+-- > +------------------+--------------+-----------------++-- > | 32 bit timestamp | 32 bit price | 16 bit quantity |+-- > +------------------+--------------+-----------------++--+-- A corresponding Haskell value looks like this:+--+-- @+--data Trade = Trade+-- { timestamp :: !'Word32'+-- , price :: !'Word32'+-- , qty :: !'Word16'+-- } deriving ('Show')+-- @+--+-- The fields in @Trade@ are marked as strict (using @!@) since we don't need+-- laziness here. In practise, you would probably consider using the UNPACK+-- pragma as well.+-- <https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#unpack-pragma>+--+-- Now, let's have a look at a decoder for this format.+--+-- @+--getTrade :: 'Get' Trade+--getTrade = do+-- timestamp <- 'getWord32le'+-- price <- 'getWord32le'+-- quantity <- 'getWord16le'+-- return '$!' Trade timestamp price quantity+-- @+--+-- Or even simpler using applicative style:+--+-- @+--getTrade' :: 'Get' Trade+--getTrade' = Trade '<$>' 'getWord32le' '<*>' 'getWord32le' '<*>' 'getWord16le'+-- @+--+-- There are two kinds of ways to execute this decoder, the lazy input+-- method and the incremental input method. Here we will use the lazy+-- input method.+--+-- Let's first define a function that decodes many @Trade@s.+--+-- @+--getTrades :: Get [Trade]+--getTrades = do+-- empty <- 'isEmpty'+-- if empty+-- then return []+-- else do trade <- getTrade+-- trades <- getTrades+-- return (trade:trades)+-- @+--+-- Finally, we run the decoder:+--+-- @+--lazyIOExample :: IO [Trade]+--lazyIOExample = do+-- input <- BL.readFile \"trades.bin\"+-- return ('runGet' getTrades input)+-- @+--+-- This decoder has the downside that it will need to read all the input before+-- it can return. On the other hand, it will not return anything until+-- it knows it could decode without any decoder errors.+--+-- You could also refactor to a left-fold, to decode in a more streaming fashion,+-- and get the following decoder. It will start to return data without knowing+-- that it can decode all input.+--+-- @+--incrementalExample :: BL.ByteString -> [Trade]+--incrementalExample input0 = go decoder input0+-- where+-- decoder = 'runGetIncremental' getTrade+-- go :: 'Decoder' Trade -> BL.ByteString -> [Trade]+-- go ('Done' leftover _consumed trade) input =+-- trade : go decoder (BL.chunk leftover input)+-- go ('Partial' k) input =+-- go (k . takeHeadChunk $ input) (dropHeadChunk input)+-- go ('Fail' _leftover _consumed msg) _input =+-- error msg+--+--takeHeadChunk :: BL.ByteString -> Maybe BS.ByteString+--takeHeadChunk lbs =+-- case lbs of+-- (BL.Chunk bs _) -> Just bs+-- _ -> Nothing+--+--dropHeadChunk :: BL.ByteString -> BL.ByteString+--dropHeadChunk lbs =+-- case lbs of+-- (BL.Chunk _ lbs') -> lbs'+-- _ -> BL.Empty+-- @+--+-- The @lazyIOExample@ uses lazy I/O to read the file from the disk, which is+-- not suitable in all applications, and certainly not if you need to read+-- from a socket which has higher likelihood to fail. To address these needs,+-- use the incremental input method like in @incrementalExample@.+-- For an example of how to read incrementally from a Handle,+-- see the implementation of 'decodeFileOrFail' in "Data.Binary". ----------------------------------------------------------------------------- module Data.Binary.Get ( - -- * The Get type+ -- * The Get monad Get -- * The lazy input interface -- $lazyinterface- , runGet + , runGet , runGetOrFail , ByteOffset @@ -44,11 +151,15 @@ , pushChunks , pushEndOfInput - -- * Parsing+ -- * Decoding , skip , isEmpty , bytesRead- -- , lookAhead+ , isolate+ , lookAhead+ , lookAheadM+ , lookAheadE+ , label -- ** ByteStrings , getByteString@@ -56,7 +167,7 @@ , getLazyByteStringNul , getRemainingLazyByteString - -- ** Decoding words+ -- ** Decoding Words , getWord8 -- *** Big-endian decoding@@ -75,18 +186,47 @@ , getWord32host , getWord64host + -- ** Decoding Ints+ , getInt8++ -- *** Big-endian decoding+ , getInt16be+ , getInt32be+ , getInt64be++ -- *** Little-endian decoding+ , getInt16le+ , getInt32le+ , getInt64le++ -- *** Host-endian, unaligned decoding+ , getInthost+ , getInt16host+ , getInt32host+ , getInt64host++ -- ** Decoding Floats/Doubles+ , getFloatbe+ , getFloatle+ , getFloathost+ , getDoublebe+ , getDoublele+ , getDoublehost+ -- * Deprecated functions , runGetState -- DEPRECATED , remaining -- DEPRECATED , getBytes -- DEPRECATED ) where+#if ! MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif import Foreign import qualified Data.ByteString as B import qualified Data.ByteString.Unsafe as B import qualified Data.ByteString.Lazy as L--import Control.Applicative+import qualified Data.ByteString.Lazy.Internal as L import Data.Binary.Get.Internal hiding ( Decoder(..), runGetIncremental ) import qualified Data.Binary.Get.Internal as I@@ -97,11 +237,14 @@ import GHC.Word #endif +-- needed for casting words to float/double+import Data.Binary.FloatCast (wordToFloat, wordToDouble)+ -- $lazyinterface -- The lazy interface consumes a single lazy 'L.ByteString'. It's the easiest -- interface to get started with, but it doesn't support interleaving I\/O and -- parsing, unless lazy I/O is used.--- +-- -- There is no way to provide more input other than the initial data. To be -- able to incrementally give more data, see the incremental input interface. @@ -156,32 +299,43 @@ go (k $! (acc - unused)) acc -- | DEPRECATED. Provides compatibility with previous versions of this library.--- Run a 'Get' monad and return a tuple with thee values.+-- Run a 'Get' monad and return a tuple with three values. -- The first value is the result of the decoder. The second and third are the -- unused input, and the number of consumed bytes.-{-# DEPRECATED runGetState "Use runGetPartial instead. This function will be removed." #-}+{-# DEPRECATED runGetState "Use runGetIncremental instead. This function will be removed." #-} runGetState :: Get a -> L.ByteString -> ByteOffset -> (a, L.ByteString, ByteOffset)-runGetState g lbs0 pos' = go (runGetIncremental g) (L.toChunks lbs0)+runGetState g lbs0 pos' = go (runGetIncremental g) lbs0 where- go (Done s pos a) lbs = (a, L.fromChunks (s:lbs), pos+pos')- go (Partial k) (x:xs) = go (k $ Just x) xs- go (Partial k) [] = go (k Nothing) []+ go (Done s pos a) lbs = (a, L.chunk s lbs, pos+pos')+ go (Partial k) lbs = go (k (takeHeadChunk lbs)) (dropHeadChunk lbs) go (Fail _ pos msg) _ = error ("Data.Binary.Get.runGetState at position " ++ show pos ++ ": " ++ msg) +takeHeadChunk :: L.ByteString -> Maybe B.ByteString+takeHeadChunk lbs =+ case lbs of+ (L.Chunk bs _) -> Just bs+ _ -> Nothing++dropHeadChunk :: L.ByteString -> L.ByteString+dropHeadChunk lbs =+ case lbs of+ (L.Chunk _ lbs') -> lbs'+ _ -> L.Empty+ -- | Run a 'Get' monad and return 'Left' on failure and 'Right' on -- success. In both cases any unconsumed input and the number of bytes -- consumed is returned. In the case of failure, a human-readable -- error message is included as well.+--+-- /Since: 0.6.4.0/ runGetOrFail :: Get a -> L.ByteString -> Either (L.ByteString, ByteOffset, String) (L.ByteString, ByteOffset, a)-runGetOrFail g bs = feedAll (runGetIncremental g) chunks+runGetOrFail g lbs0 = feedAll (runGetIncremental g) lbs0 where- chunks = L.toChunks bs- feedAll (Done x pos r) xs = Right ((L.fromChunks (x:xs)), pos, r)- feedAll (Partial k) (x:xs) = feedAll (k (Just x)) xs- feedAll (Partial k) [] = feedAll (k Nothing) []- feedAll (Fail x pos msg) xs = Left ((L.fromChunks (x:xs)), pos, msg)+ feedAll (Done bs pos x) lbs = Right (L.chunk bs lbs, pos, x)+ feedAll (Partial k) lbs = feedAll (k (takeHeadChunk lbs)) (dropHeadChunk lbs)+ feedAll (Fail x pos msg) xs = Left (L.chunk x xs, pos, msg) -- | An offset, counted in bytes. type ByteOffset = Int64@@ -189,12 +343,10 @@ -- | The simplest interface to run a 'Get' decoder. If the decoder runs into -- an error, calls 'fail', or runs out of input, it will call 'error'. runGet :: Get a -> L.ByteString -> a-runGet g bs = feedAll (runGetIncremental g) chunks+runGet g lbs0 = feedAll (runGetIncremental g) lbs0 where- chunks = L.toChunks bs- feedAll (Done _ _ r) _ = r- feedAll (Partial k) (x:xs) = feedAll (k (Just x)) xs- feedAll (Partial k) [] = feedAll (k Nothing) []+ feedAll (Done _ _ x) _ = x+ feedAll (Partial k) lbs = feedAll (k (takeHeadChunk lbs)) (dropHeadChunk lbs) feedAll (Fail _ pos msg) _ = error ("Data.Binary.Get.runGet at position " ++ show pos ++ ": " ++ msg) @@ -203,7 +355,7 @@ -- will add the input to 'B.ByteString' of unconsumed input. -- -- @--- 'runGetPartial' myParser \`pushChunk\` myInput1 \`pushChunk\` myInput2+-- 'runGetIncremental' myParser \`pushChunk\` myInput1 \`pushChunk\` myInput2 -- @ pushChunk :: Decoder a -> B.ByteString -> Decoder a pushChunk r inp =@@ -213,16 +365,19 @@ Fail inp0 p s -> Fail (inp0 `B.append` inp) p s --- | Feed a 'Decoder' with more input. If the 'Decoder' is 'Done' or 'Fail' it -- will add the input to 'ByteString' of unconsumed input.+-- | Feed a 'Decoder' with more input. If the 'Decoder' is 'Done' or 'Fail' it+-- will add the input to 'ByteString' of unconsumed input. -- -- @--- 'runGetPartial' myParser \`pushChunks\` myLazyByteString+-- 'runGetIncremental' myParser \`pushChunks\` myLazyByteString -- @ pushChunks :: Decoder a -> L.ByteString -> Decoder a pushChunks r0 = go r0 . L.toChunks where go r [] = r- go r (x:xs) = go (pushChunk r x) xs+ go (Done inp pos a) xs = Done (B.concat (inp:xs)) pos a+ go (Fail inp pos s) xs = Fail (B.concat (inp:xs)) pos s+ go (Partial k) (x:xs) = go (k (Just x)) xs -- | Tell a 'Decoder' that there is no more input. This passes 'Nothing' to a -- 'Partial' decoder, otherwise returns the decoder unchanged.@@ -232,60 +387,44 @@ Done _ _ _ -> r Partial k -> k Nothing Fail _ _ _ -> r- ++-- | Skip ahead @n@ bytes. Fails if fewer than @n@ bytes are available.+skip :: Int -> Get ()+skip n = withInputChunks (fromIntegral n) consumeBytes (const ()) failOnEOF+ -- | An efficient get method for lazy ByteStrings. Fails if fewer than @n@ -- bytes are left in the input. getLazyByteString :: Int64 -> Get L.ByteString-getLazyByteString n0 = L.fromChunks <$> go n0- where- consume n str- | fromIntegral (B.length str) >= n = Right (B.splitAt (fromIntegral n) str)- | otherwise = Left (fromIntegral (B.length str))- go n = do- str <- get- case consume n str of- Left used -> do- put B.empty- demandInput- fmap (str:) (go (n - used))- Right (want,rest) -> do- put rest- return [want]+getLazyByteString n0 = withInputChunks n0 consumeBytes L.fromChunks failOnEOF +consumeBytes :: Consume Int64+consumeBytes n str+ | fromIntegral (B.length str) >= n = Right (B.splitAt (fromIntegral n) str)+ | otherwise = Left (n - fromIntegral (B.length str))++consumeUntilNul :: Consume ()+consumeUntilNul _ str =+ case B.break (==0) str of+ (want, rest) | B.null rest -> Left ()+ | otherwise -> Right (want, B.drop 1 rest)++consumeAll :: Consume ()+consumeAll _ _ = Left ()++resumeOnEOF :: [B.ByteString] -> Get L.ByteString+resumeOnEOF = return . L.fromChunks+ -- | Get a lazy ByteString that is terminated with a NUL byte. -- The returned string does not contain the NUL byte. Fails -- if it reaches the end of input without finding a NUL. getLazyByteStringNul :: Get L.ByteString-getLazyByteStringNul = L.fromChunks <$> go- where- findNull str =- case B.break (==0) str of- (want,rest) | B.null rest -> Nothing- | otherwise -> Just (want, B.drop 1 rest)- go = do- str <- get- case findNull str of- Nothing -> do- put B.empty- demandInput- fmap (str:) go- Just (want,rest) -> do- put rest- return [want]- +getLazyByteStringNul = withInputChunks () consumeUntilNul L.fromChunks failOnEOF+ -- | Get the remaining bytes as a lazy ByteString. -- Note that this can be an expensive function to use as it forces reading -- all input and keeping the string in-memory. getRemainingLazyByteString :: Get L.ByteString-getRemainingLazyByteString = L.fromChunks <$> go- where- go = do- str <- get- put B.empty- done <- isEmpty- if done- then return [str]- else fmap (str:) go+getRemainingLazyByteString = withInputChunks () consumeAll L.fromChunks resumeOnEOF ------------------------------------------------------------------------ -- Primtives@@ -300,8 +439,14 @@ -- | Read a Word8 from the monad state getWord8 :: Get Word8 getWord8 = readN 1 B.unsafeHead-{-# INLINE getWord8 #-}+{-# INLINE[2] getWord8 #-} +-- | Read an Int8 from the monad state+getInt8 :: Get Int8+getInt8 = fromIntegral <$> getWord8+{-# INLINE getInt8 #-}++ -- force GHC to inline getWordXX {-# RULES "getWord8/readN" getWord8 = readN 1 B.unsafeHead@@ -310,8 +455,7 @@ "getWord32be/readN" getWord32be = readN 4 word32be "getWord32le/readN" getWord32le = readN 4 word32le "getWord64be/readN" getWord64be = readN 8 word64be-"getWord64le/readN" getWord64le = readN 8 word64le- #-}+"getWord64le/readN" getWord64le = readN 8 word64le #-} -- | Read a Word16 in big endian format getWord16be :: Get Word16@@ -321,7 +465,7 @@ word16be = \s -> (fromIntegral (s `B.unsafeIndex` 0) `shiftl_w16` 8) .|. (fromIntegral (s `B.unsafeIndex` 1))-{-# INLINE getWord16be #-}+{-# INLINE[2] getWord16be #-} {-# INLINE word16be #-} -- | Read a Word16 in little endian format@@ -332,7 +476,7 @@ word16le = \s -> (fromIntegral (s `B.unsafeIndex` 1) `shiftl_w16` 8) .|. (fromIntegral (s `B.unsafeIndex` 0) )-{-# INLINE getWord16le #-}+{-# INLINE[2] getWord16le #-} {-# INLINE word16le #-} -- | Read a Word32 in big endian format@@ -345,7 +489,7 @@ (fromIntegral (s `B.unsafeIndex` 1) `shiftl_w32` 16) .|. (fromIntegral (s `B.unsafeIndex` 2) `shiftl_w32` 8) .|. (fromIntegral (s `B.unsafeIndex` 3) )-{-# INLINE getWord32be #-}+{-# INLINE[2] getWord32be #-} {-# INLINE word32be #-} -- | Read a Word32 in little endian format@@ -358,7 +502,7 @@ (fromIntegral (s `B.unsafeIndex` 2) `shiftl_w32` 16) .|. (fromIntegral (s `B.unsafeIndex` 1) `shiftl_w32` 8) .|. (fromIntegral (s `B.unsafeIndex` 0) )-{-# INLINE getWord32le #-}+{-# INLINE[2] getWord32le #-} {-# INLINE word32le #-} -- | Read a Word64 in big endian format@@ -375,7 +519,7 @@ (fromIntegral (s `B.unsafeIndex` 5) `shiftl_w64` 16) .|. (fromIntegral (s `B.unsafeIndex` 6) `shiftl_w64` 8) .|. (fromIntegral (s `B.unsafeIndex` 7) )-{-# INLINE getWord64be #-}+{-# INLINE[2] getWord64be #-} {-# INLINE word64be #-} -- | Read a Word64 in little endian format@@ -392,9 +536,42 @@ (fromIntegral (s `B.unsafeIndex` 2) `shiftl_w64` 16) .|. (fromIntegral (s `B.unsafeIndex` 1) `shiftl_w64` 8) .|. (fromIntegral (s `B.unsafeIndex` 0) )-{-# INLINE getWord64le #-}+{-# INLINE[2] getWord64le #-} {-# INLINE word64le #-} ++-- | Read an Int16 in big endian format.+getInt16be :: Get Int16+getInt16be = fromIntegral <$> getWord16be+{-# INLINE getInt16be #-}++-- | Read an Int32 in big endian format.+getInt32be :: Get Int32+getInt32be = fromIntegral <$> getWord32be+{-# INLINE getInt32be #-}++-- | Read an Int64 in big endian format.+getInt64be :: Get Int64+getInt64be = fromIntegral <$> getWord64be+{-# INLINE getInt64be #-}+++-- | Read an Int16 in little endian format.+getInt16le :: Get Int16+getInt16le = fromIntegral <$> getWord16le+{-# INLINE getInt16le #-}++-- | Read an Int32 in little endian format.+getInt32le :: Get Int32+getInt32le = fromIntegral <$> getWord32le+{-# INLINE getInt32le #-}++-- | Read an Int64 in little endian format.+getInt64le :: Get Int64+getInt64le = fromIntegral <$> getWord64le+{-# INLINE getInt64le #-}++ ------------------------------------------------------------------------ -- Host-endian reads @@ -420,7 +597,62 @@ getWord64host = getPtr (sizeOf (undefined :: Word64)) {-# INLINE getWord64host #-} +-- | /O(1)./ Read a single native machine word in native host+-- order. It works in the same way as 'getWordhost'.+getInthost :: Get Int+getInthost = getPtr (sizeOf (undefined :: Int))+{-# INLINE getInthost #-}++-- | /O(1)./ Read a 2 byte Int16 in native host order and host endianness.+getInt16host :: Get Int16+getInt16host = getPtr (sizeOf (undefined :: Int16))+{-# INLINE getInt16host #-}++-- | /O(1)./ Read an Int32 in native host order and host endianness.+getInt32host :: Get Int32+getInt32host = getPtr (sizeOf (undefined :: Int32))+{-# INLINE getInt32host #-}++-- | /O(1)./ Read an Int64 in native host order and host endianess.+getInt64host :: Get Int64+getInt64host = getPtr (sizeOf (undefined :: Int64))+{-# INLINE getInt64host #-}++ ------------------------------------------------------------------------+-- Double/Float reads++-- | Read a 'Float' in big endian IEEE-754 format.+getFloatbe :: Get Float+getFloatbe = wordToFloat <$> getWord32be+{-# INLINE getFloatbe #-}++-- | Read a 'Float' in little endian IEEE-754 format.+getFloatle :: Get Float+getFloatle = wordToFloat <$> getWord32le+{-# INLINE getFloatle #-}++-- | Read a 'Float' in IEEE-754 format and host endian.+getFloathost :: Get Float+getFloathost = wordToFloat <$> getWord32host+{-# INLINE getFloathost #-}++-- | Read a 'Double' in big endian IEEE-754 format.+getDoublebe :: Get Double+getDoublebe = wordToDouble <$> getWord64be+{-# INLINE getDoublebe #-}++-- | Read a 'Double' in little endian IEEE-754 format.+getDoublele :: Get Double+getDoublele = wordToDouble <$> getWord64le+{-# INLINE getDoublele #-}++-- | Read a 'Double' in IEEE-754 format and host endian.+getDoublehost :: Get Double+getDoublehost = wordToDouble <$> getWord64host+{-# INLINE getDoublehost #-}++------------------------------------------------------------------------ -- Unchecked shifts shiftl_w16 :: Word16 -> Int -> Word16@@ -433,12 +665,6 @@ #if WORD_SIZE_IN_BITS < 64 shiftl_w64 (W64# w) (I# i) = W64# (w `uncheckedShiftL64#` i)--#if __GLASGOW_HASKELL__ <= 606--- Exported by GHC.Word in GHC 6.8 and higher-foreign import ccall unsafe "stg_uncheckedShiftL64"- uncheckedShiftL64# :: Word64# -> Int# -> Word64#-#endif #else shiftl_w64 (W64# w) (I# i) = W64# (w `uncheckedShiftL#` i)
src/Data/Binary/Get/Internal.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE CPP, RankNTypes, MagicHash, BangPatterns #-}+{-# LANGUAGE CPP, RankNTypes, MagicHash, BangPatterns, TypeFamilies #-} -- CPP C style pre-precessing, the #if defined lines -- RankNTypes forall r. statement@@ -16,18 +16,26 @@ , readNWith -- * Parsing- , skip , bytesRead- + , isolate++ -- * With input chunks+ , withInputChunks+ , Consume+ , failOnEOF+ , get , put- , demandInput , ensureN -- * Utility , remaining , getBytes , isEmpty+ , lookAhead+ , lookAheadM+ , lookAheadE+ , label -- ** ByteStrings , getByteString@@ -36,17 +44,16 @@ import Foreign import qualified Data.ByteString as B-import qualified Data.ByteString.Internal as B import qualified Data.ByteString.Unsafe as B import Control.Applicative--#if __GLASGOW_HASKELL__ < 704 && !defined(__HADDOCK__)--- needed for (# unboxing #) with magic hash--- Do we still need these? Works without on modern GHCs.-import GHC.Base+import Control.Monad+#if MIN_VERSION_base(4,9,0)+import qualified Control.Monad.Fail as Fail #endif +import Data.Binary.Internal ( accursedUnutterablePerformIO )+ -- Kolmodin 20100427: at zurihac we discussed of having partial take a -- "Maybe ByteString" and implemented it in this way. -- The reasoning was that you could accidently provide an empty bytestring,@@ -56,7 +63,7 @@ -- rare, as the RTS should only wake you up if you actually have some data -- to read from your fd. --- | A decoder procuced by running a 'Get' monad.+-- | A decoder produced by running a 'Get' monad. data Decoder a = Fail !B.ByteString String -- ^ The decoder ran into an error. The decoder either used -- 'fail' or was not provided enough input.@@ -77,18 +84,19 @@ newtype Get a = C { runCont :: forall r. B.ByteString -> Success a r ->- Decoder r }+ Decoder r } type Success a r = B.ByteString -> a -> Decoder r instance Monad Get where- return = returnG+ return = pure (>>=) = bindG- fail = failG+#if MIN_VERSION_base(4,9,0)+ fail = Fail.fail -returnG :: a -> Get a-returnG a = C $ \s ks -> ks s a-{-# INLINE [0] returnG #-}+instance Fail.MonadFail Get where+#endif+ fail = failG bindG :: Get a -> (a -> Get b) -> Get b bindG (C c) f = C $ \i ks -> c i (\i' a -> (runCont (f a)) i' ks)@@ -109,11 +117,16 @@ {-# INLINE fmapG #-} instance Applicative Get where- pure = returnG- {-# INLINE pure #-}+ pure = \x -> C $ \s ks -> ks s x+ {-# INLINE [0] pure #-} (<*>) = apG {-# INLINE (<*>) #-} +-- | /Since: 0.7.1.0/+instance MonadPlus Get where+ mzero = empty+ mplus = (<|>)+ instance Functor Get where fmap = fmapG @@ -158,29 +171,77 @@ Done _ _ -> r prompt :: B.ByteString -> Decoder a -> (B.ByteString -> Decoder a) -> Decoder a-prompt inp kf ks =- let loop =- Partial $ \sm ->- case sm of- Just s | B.null s -> loop- | otherwise -> ks (inp `B.append` s)- Nothing -> kf- in loop+prompt inp kf ks = prompt' kf (\inp' -> ks (inp `B.append` inp')) +prompt' :: Decoder a -> (B.ByteString -> Decoder a) -> Decoder a+prompt' kf ks =+ let loop =+ Partial $ \sm ->+ case sm of+ Just s | B.null s -> loop+ | otherwise -> ks s+ Nothing -> kf+ in loop+ -- | Get the total number of bytes read to this point. bytesRead :: Get Int64 bytesRead = C $ \inp k -> BytesRead (fromIntegral $ B.length inp) (k inp) --- | Demand more input. If none available, fail.-demandInput :: Get ()-demandInput = C $ \inp ks ->- prompt inp (Fail inp "demandInput: not enough bytes") (\inp' -> ks inp' ())+-- | Isolate a decoder to operate with a fixed number of bytes, and fail if+-- fewer bytes were consumed, or more bytes were attempted to be consumed.+-- If the given decoder fails, 'isolate' will also fail.+-- Offset from 'bytesRead' will be relative to the start of 'isolate', not the+-- absolute of the input.+--+-- /Since: 0.7.2.0/+isolate :: Int -- ^ The number of bytes that must be consumed+ -> Get a -- ^ The decoder to isolate+ -> Get a+isolate n0 act+ | n0 < 0 = fail "isolate: negative size"+ | otherwise = go n0 (runCont act B.empty Done)+ where+ go !n (Done left x)+ | n == 0 && B.null left = return x+ | otherwise = do+ pushFront left+ let consumed = n0 - n - B.length left+ fail $ "isolate: the decoder consumed " ++ show consumed ++ " bytes" +++ " which is less than the expected " ++ show n0 ++ " bytes"+ go 0 (Partial resume) = go 0 (resume Nothing)+ go n (Partial resume) = do+ inp <- C $ \inp k -> do+ let takeLimited str =+ let (inp', out) = B.splitAt n str+ in k out (Just inp')+ case not (B.null inp) of+ True -> takeLimited inp+ False -> prompt inp (k B.empty Nothing) takeLimited+ case inp of+ Nothing -> go n (resume Nothing)+ Just str -> go (n - B.length str) (resume (Just str))+ go _ (Fail bs err) = pushFront bs >> fail err+ go n (BytesRead r resume) =+ go n (resume $! fromIntegral n0 - fromIntegral n - r) --- | Skip ahead @n@ bytes. Fails if fewer than @n@ bytes are available.-skip :: Int -> Get ()-skip n = readN n (const ())-{-# INLINE skip #-}+type Consume s = s -> B.ByteString -> Either s (B.ByteString, B.ByteString) +withInputChunks :: s -> Consume s -> ([B.ByteString] -> b) -> ([B.ByteString] -> Get b) -> Get b+withInputChunks initS consume onSucc onFail = go initS []+ where+ go state acc = C $ \inp ks ->+ case consume state inp of+ Left state' -> do+ let acc' = inp : acc+ prompt'+ (runCont (onFail (reverse acc')) B.empty ks)+ (\str' -> runCont (go state' acc') str' ks)+ Right (want,rest) -> do+ ks rest (onSucc (reverse (want:acc)))++failOnEOF :: [B.ByteString] -> Get a+failOnEOF bs = C $ \_ _ -> Fail (B.concat bs) "not enough bytes"+ -- | Test whether all input has been consumed, i.e. there are no remaining -- undecoded bytes. isEmpty :: Get Bool@@ -195,34 +256,101 @@ getBytes = getByteString {-# INLINE getBytes #-} +-- | /Since: 0.7.0.0/ instance Alternative Get where empty = C $ \inp _ks -> Fail inp "Data.Binary.Get(Alternative).empty"- (<|>) f g = C $ \inp ks ->- let r0 = runCont (try f) inp (\inp' a -> Done inp' a)- go r = case r of- Done inp' a -> ks inp' a- Partial k -> Partial (go . k)- Fail inp' _str -> runCont g inp' ks- BytesRead unused k -> BytesRead unused (go . k)- in go r0+ {-# INLINE empty #-}+ (<|>) f g = do+ (decoder, bs) <- runAndKeepTrack f+ case decoder of+ Done inp x -> C $ \_ ks -> ks inp x+ Fail _ _ -> pushBack bs >> g+ _ -> error "Binary: impossible"+ {-# INLINE (<|>) #-} some p = (:) <$> p <*> many p+ {-# INLINE some #-} many p = do v <- (Just <$> p) <|> pure Nothing case v of Nothing -> pure [] Just x -> (:) x <$> many p+ {-# INLINE many #-} --- | Try to execute a Get. If it fails, the consumed input will be restored.-try :: Get a -> Get a-try g = C $ \inp ks ->+-- | Run a decoder and keep track of all the input it consumes.+-- Once it's finished, return the final decoder (always 'Done' or 'Fail'),+-- and unconsume all the the input the decoder required to run.+-- Any additional chunks which was required to run the decoder+-- will also be returned.+runAndKeepTrack :: Get a -> Get (Decoder a, [B.ByteString])+runAndKeepTrack g = C $ \inp ks -> let r0 = runCont g inp (\inp' a -> Done inp' a) go !acc r = case r of- Done inp' a -> ks inp' a+ Done inp' a -> ks inp (Done inp' a, reverse acc) Partial k -> Partial $ \minp -> go (maybe acc (:acc) minp) (k minp)- Fail _ s -> Fail (B.concat (inp : reverse acc)) s+ Fail inp' s -> ks inp (Fail inp' s, reverse acc) BytesRead unused k -> BytesRead unused (go acc . k) in go [] r0+{-# INLINE runAndKeepTrack #-} +pushBack :: [B.ByteString] -> Get ()+pushBack [] = C $ \ inp ks -> ks inp ()+pushBack bs = C $ \ inp ks -> ks (B.concat (inp : bs)) ()+{-# INLINE pushBack #-}++pushFront :: B.ByteString -> Get ()+pushFront bs = C $ \ inp ks -> ks (B.append bs inp) ()+{-# INLINE pushFront #-}++-- | Run the given decoder, but without consuming its input. If the given+-- decoder fails, then so will this function.+--+-- /Since: 0.7.0.0/+lookAhead :: Get a -> Get a+lookAhead g = do+ (decoder, bs) <- runAndKeepTrack g+ case decoder of+ Done _ a -> pushBack bs >> return a+ Fail inp s -> C $ \_ _ -> Fail inp s+ _ -> error "Binary: impossible"++-- | Run the given decoder, and only consume its input if it returns 'Just'.+-- If 'Nothing' is returned, the input will be unconsumed.+-- If the given decoder fails, then so will this function.+--+-- /Since: 0.7.0.0/+lookAheadM :: Get (Maybe a) -> Get (Maybe a)+lookAheadM g = do+ let g' = maybe (Left ()) Right <$> g+ either (const Nothing) Just <$> lookAheadE g'++-- | Run the given decoder, and only consume its input if it returns 'Right'.+-- If 'Left' is returned, the input will be unconsumed.+-- If the given decoder fails, then so will this function.+--+-- /Since: 0.7.1.0/+lookAheadE :: Get (Either a b) -> Get (Either a b)+lookAheadE g = do+ (decoder, bs) <- runAndKeepTrack g+ case decoder of+ Done _ (Left x) -> pushBack bs >> return (Left x)+ Done inp (Right x) -> C $ \_ ks -> ks inp (Right x)+ Fail inp s -> C $ \_ _ -> Fail inp s+ _ -> error "Binary: impossible"++-- | Label a decoder. If the decoder fails, the label will be appended on+-- a new line to the error message string.+--+-- /Since: 0.7.2.0/+label :: String -> Get a -> Get a+label msg decoder = C $ \inp ks ->+ let r0 = runCont decoder inp (\inp' a -> Done inp' a)+ go r = case r of+ Done inp' a -> ks inp' a+ Partial k -> Partial (go . k)+ Fail inp' s -> Fail inp' (s ++ "\n" ++ msg)+ BytesRead u k -> BytesRead u (go . k)+ in go r0+ -- | DEPRECATED. Get the number of bytes of remaining input. -- Note that this is an expensive function to use as in order to calculate how -- much input remains, all input has to be read and kept in-memory.@@ -265,18 +393,8 @@ {-# RULES -"<$> to <*>" forall f g.- (<$>) f g = returnG f <*> g- "readN/readN merge" forall n m f g.- apG (readN n f) (readN m g) = readN (n+m) (\bs -> f bs $ g (B.unsafeDrop n bs))--"returnG/readN swap" [~1] forall f.- returnG f = readN 0 (const f)--"readN 0/returnG swapback" [1] forall f.- readN 0 f = returnG (f B.empty)- #-}+ apG (readN n f) (readN m g) = readN (n+m) (\bs -> f bs $ g (B.unsafeDrop n bs)) #-} -- | Ensure that there are at least @n@ bytes available. If not, the -- computation will escape with 'Partial'.@@ -284,20 +402,29 @@ ensureN !n0 = C $ \inp ks -> do if B.length inp >= n0 then ks inp ()- else runCont (go n0) inp ks+ else runCont (withInputChunks n0 enoughChunks onSucc onFail >>= put) inp ks where -- might look a bit funny, but plays very well with GHC's inliner. -- GHC won't inline recursive functions, so we make ensureN non-recursive- go n = C $ \inp ks -> do- if B.length inp >= n- then ks inp ()- else runCont (demandInput >> go n) inp ks+ enoughChunks n str+ | B.length str >= n = Right (str,B.empty)+ | otherwise = Left (n - B.length str)+ -- Sometimes we will produce leftovers lists of the form [B.empty, nonempty]+ -- where `nonempty` is a non-empty ByteString. In this case we can avoid a copy+ -- by simply dropping the empty prefix. In principle ByteString might want+ -- to gain this optimization as well+ onSucc = B.concat . dropWhile B.null+ onFail bss = C $ \_ _ -> Fail (B.concat bss) "not enough bytes" {-# INLINE ensureN #-} unsafeReadN :: Int -> (B.ByteString -> a) -> Get a unsafeReadN !n f = C $ \inp ks -> do ks (B.unsafeDrop n inp) $! f inp -- strict return +-- | @readNWith n f@ where @f@ must be deterministic and not have side effects. readNWith :: Int -> (Ptr a -> IO a) -> Get a readNWith n f = do- readN n $ \s -> B.inlinePerformIO $ B.unsafeUseAsCString s (f . castPtr)+ -- It should be safe to use accursedUnutterablePerformIO here.+ -- The action must be deterministic and not have any external side effects.+ -- It depends on the value of the ByteString so the value dependencies look OK.+ readN n $ \s -> accursedUnutterablePerformIO $ B.unsafeUseAsCString s (f . castPtr) {-# INLINE readNWith #-}
+ src/Data/Binary/Internal.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE CPP #-}++module Data.Binary.Internal + ( accursedUnutterablePerformIO ) where++#if MIN_VERSION_bytestring(0,10,6)+import Data.ByteString.Internal( accursedUnutterablePerformIO )+#else+import Data.ByteString.Internal( inlinePerformIO )++{-# INLINE accursedUnutterablePerformIO #-}+-- | You must be truly desperate to come to me for help.+accursedUnutterablePerformIO :: IO a -> a+accursedUnutterablePerformIO = inlinePerformIO+#endif
src/Data/Binary/Put.hs view
@@ -1,6 +1,9 @@ {-# LANGUAGE CPP #-}-#if __GLASGOW_HASKELL__ >= 701-{-# LANGUAGE Trustworthy #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE Safe #-}++#if MIN_VERSION_base(4,9,0)+#define HAS_SEMIGROUP #endif -----------------------------------------------------------------------------@@ -8,7 +11,7 @@ -- Module : Data.Binary.Put -- Copyright : Lennart Kolmodin -- License : BSD3-style (see LICENSE)--- +-- -- Maintainer : Lennart Kolmodin <kolmodin@gmail.com> -- Stability : stable -- Portability : Portable to Hugs and GHC. Requires MPTCs@@ -32,41 +35,76 @@ -- * Primitives , putWord8+ , putInt8 , putByteString , putLazyByteString+#if MIN_VERSION_bytestring(0,10,4)+ , putShortByteString+#endif -- * Big-endian primitives , putWord16be , putWord32be , putWord64be+ , putInt16be+ , putInt32be+ , putInt64be+ , putFloatbe+ , putDoublebe -- * Little-endian primitives , putWord16le , putWord32le , putWord64le+ , putInt16le+ , putInt32le+ , putInt64le+ , putFloatle+ , putDoublele -- * Host-endian, unaligned writes , putWordhost -- :: Word -> Put , putWord16host -- :: Word16 -> Put , putWord32host -- :: Word32 -> Put , putWord64host -- :: Word64 -> Put+ , putInthost -- :: Int -> Put+ , putInt16host -- :: Int16 -> Put+ , putInt32host -- :: Int32 -> Put+ , putInt64host -- :: Int64 -> Put+ , putFloathost+ , putDoublehost + -- * Unicode+ , putCharUtf8+ , putStringUtf8+ ) where -import Data.Monoid+import qualified Data.Monoid as Monoid import Data.Binary.Builder (Builder, toLazyByteString) import qualified Data.Binary.Builder as B +import Data.Int import Data.Word import qualified Data.ByteString as S import qualified Data.ByteString.Lazy as L+#if MIN_VERSION_bytestring(0,10,4)+import Data.ByteString.Short+#endif +#ifdef HAS_SEMIGROUP+import Data.Semigroup+#endif+ import Control.Applicative+import Prelude -- Silence AMP warning. +-- needed for casting Floats/Doubles to words.+import Data.Binary.FloatCast (floatToWord, doubleToWord) ------------------------------------------------------------------------ --- XXX Strict in buffer only. +-- XXX Strict in buffer only. data PairS a = PairS a !Builder sndS :: PairS a -> Builder@@ -83,29 +121,58 @@ {-# INLINE fmap #-} instance Applicative PutM where- pure = return+ pure a = Put $ PairS a Monoid.mempty+ {-# INLINE pure #-}+ m <*> k = Put $ let PairS f w = unPut m PairS x w' = unPut k- in PairS (f x) (w `mappend` w')+ in PairS (f x) (w `Monoid.mappend` w') + m *> k = Put $+ let PairS _ w = unPut m+ PairS b w' = unPut k+ in PairS b (w `Monoid.mappend` w')+ {-# INLINE (*>) #-}+ -- Standard Writer monad, with aggressive inlining instance Monad PutM where- return a = Put $ PairS a mempty- {-# INLINE return #-}- m >>= k = Put $ let PairS a w = unPut m PairS b w' = unPut (k a)- in PairS b (w `mappend` w')+ in PairS b (w `Monoid.mappend` w') {-# INLINE (>>=) #-} - m >> k = Put $- let PairS _ w = unPut m- PairS b w' = unPut k- in PairS b (w `mappend` w')+ return = pure+ {-# INLINE return #-}++ (>>) = (*>) {-# INLINE (>>) #-} +instance Monoid.Monoid (PutM ()) where+ mempty = pure ()+ {-# INLINE mempty #-}++#ifdef HAS_SEMIGROUP+ mappend = (<>)+#else+ mappend = mappend'+#endif+ {-# INLINE mappend #-}++mappend' :: Put -> Put -> Put+mappend' m k = Put $+ let PairS _ w = unPut m+ PairS _ w' = unPut k+ in PairS () (w `Monoid.mappend` w')+{-# INLINE mappend' #-}++#ifdef HAS_SEMIGROUP+instance Semigroup (PutM ()) where+ (<>) = mappend'+ {-# INLINE (<>) #-}+#endif+ tell :: Builder -> Put tell b = Put $ PairS () b {-# INLINE tell #-}@@ -142,6 +209,11 @@ putWord8 = tell . B.singleton {-# INLINE putWord8 #-} +-- | Efficiently write a signed byte into the output buffer+putInt8 :: Int8 -> Put+putInt8 = tell . B.singleton . fromIntegral+{-# INLINE putInt8 #-}+ -- | An efficient primitive to write a strict ByteString into the output buffer. -- It flushes the current buffer, and writes the argument into a new chunk. putByteString :: S.ByteString -> Put@@ -154,6 +226,13 @@ putLazyByteString = tell . B.fromLazyByteString {-# INLINE putLazyByteString #-} +#if MIN_VERSION_bytestring(0,10,4)+-- | Write 'ShortByteString' to the buffer+putShortByteString :: ShortByteString -> Put+putShortByteString = tell . B.fromShortByteString+{-# INLINE putShortByteString #-}+#endif+ -- | Write a Word16 in big endian format putWord16be :: Word16 -> Put putWord16be = tell . B.putWord16be@@ -184,6 +263,37 @@ putWord64le = tell . B.putWord64le {-# INLINE putWord64le #-} +-- | Write an Int16 in big endian format+putInt16be :: Int16 -> Put+putInt16be = tell . B.putInt16be+{-# INLINE putInt16be #-}++-- | Write an Int16 in little endian format+putInt16le :: Int16 -> Put+putInt16le = tell . B.putInt16le+{-# INLINE putInt16le #-}++-- | Write an Int32 in big endian format+putInt32be :: Int32 -> Put+putInt32be = tell . B.putInt32be+{-# INLINE putInt32be #-}++-- | Write an Int32 in little endian format+putInt32le :: Int32 -> Put+putInt32le = tell . B.putInt32le+{-# INLINE putInt32le #-}++-- | Write an Int64 in big endian format+putInt64be :: Int64 -> Put+putInt64be = tell . B.putInt64be+{-# INLINE putInt64be #-}++-- | Write an Int64 in little endian format+putInt64le :: Int64 -> Put+putInt64le = tell . B.putInt64le+{-# INLINE putInt64le #-}++ ------------------------------------------------------------------------ -- | /O(1)./ Write a single native machine word. The word is@@ -214,3 +324,78 @@ putWord64host :: Word64 -> Put putWord64host = tell . B.putWord64host {-# INLINE putWord64host #-}++-- | /O(1)./ Write a single native machine word. The word is+-- written in host order, host endian form, for the machine you're on.+-- On a 64 bit machine the Int is an 8 byte value, on a 32 bit machine,+-- 4 bytes. Values written this way are not portable to+-- different endian or word sized machines, without conversion.+--+putInthost :: Int -> Put+putInthost = tell . B.putInthost+{-# INLINE putInthost #-}++-- | /O(1)./ Write an Int16 in native host order and host endianness.+-- For portability issues see @putInthost@.+putInt16host :: Int16 -> Put+putInt16host = tell . B.putInt16host+{-# INLINE putInt16host #-}++-- | /O(1)./ Write an Int32 in native host order and host endianness.+-- For portability issues see @putInthost@.+putInt32host :: Int32 -> Put+putInt32host = tell . B.putInt32host+{-# INLINE putInt32host #-}++-- | /O(1)./ Write an Int64 in native host order+-- On a 32 bit machine we write two host order Int32s, in big endian form.+-- For portability issues see @putInthost@.+putInt64host :: Int64 -> Put+putInt64host = tell . B.putInt64host+{-# INLINE putInt64host #-}++------------------------------------------------------------------------+-- Floats/Doubles++-- | Write a 'Float' in big endian IEEE-754 format.+putFloatbe :: Float -> Put+putFloatbe = putWord32be . floatToWord+{-# INLINE putFloatbe #-}++-- | Write a 'Float' in little endian IEEE-754 format.+putFloatle :: Float -> Put+putFloatle = putWord32le . floatToWord+{-# INLINE putFloatle #-}++-- | Write a 'Float' in native in IEEE-754 format and host endian.+putFloathost :: Float -> Put+putFloathost = putWord32host . floatToWord+{-# INLINE putFloathost #-}++-- | Write a 'Double' in big endian IEEE-754 format.+putDoublebe :: Double -> Put+putDoublebe = putWord64be . doubleToWord+{-# INLINE putDoublebe #-}++-- | Write a 'Double' in little endian IEEE-754 format.+putDoublele :: Double -> Put+putDoublele = putWord64le . doubleToWord+{-# INLINE putDoublele #-}++-- | Write a 'Double' in native in IEEE-754 format and host endian.+putDoublehost :: Double -> Put+putDoublehost = putWord64host . doubleToWord+{-# INLINE putDoublehost #-}++------------------------------------------------------------------------+-- Unicode++-- | Write a character using UTF-8 encoding.+putCharUtf8 :: Char -> Put+putCharUtf8 = tell . B.putCharUtf8+{-# INLINE putCharUtf8 #-}++-- | Write a String using UTF-8 encoding.+putStringUtf8 :: String -> Put+putStringUtf8 = tell . B.putStringUtf8+{-# INLINE putStringUtf8 #-}
tests/Action.hs view
@@ -1,106 +1,406 @@-{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE PatternGuards #-} module Action where -import Control.Applicative-import Test.QuickCheck+import Control.Applicative+import Control.Monad+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import Data.Char+import Data.List (intersperse, nub) -import qualified Data.ByteString.Lazy as L+import Test.Framework+import Test.Framework.Providers.QuickCheck2+import Test.QuickCheck -import qualified Data.Binary.Get as Binary+import Arbitrary ()+import qualified Data.Binary.Get as Binary -import Arbitrary()+tests :: [Test]+tests = [ testProperty "action" prop_action+ , testProperty "label" prop_label+ , testProperty "fail" prop_fail ] data Action- = GetByteString Int+ = Actions [Action]+ | GetByteString Int+ | GetByteStringL Int+ | Skip Int+ | Isolate Int [Action] | Try [Action] [Action]+ | Label String [Action]+ | LookAhead [Action]+ -- | First argument is True if this action returns Just, otherwise False.+ | LookAheadM Bool [Action]+ -- | First argument is True if this action returns Right, otherwise Left.+ | LookAheadE Bool [Action] | BytesRead | Fail deriving (Show, Eq) instance Arbitrary Action where+ arbitrary = fmap Actions (gen_actions False) shrink action = case action of- GetByteString n -> [ GetByteString n' | n' <- shrink n, n > 0 ]+ Actions [a] -> [a]+ Actions as -> [ Actions as' | as' <- shrink as ] BytesRead -> [] Fail -> []+ GetByteString n -> [ GetByteString n' | n' <- shrink n ]+ GetByteStringL n -> [ GetByteStringL n' | n' <- shrink n ]+ Skip n -> [ Skip n' | n' <- shrink n ]+ Isolate n as -> nub $ Actions as :+ [ Isolate n' as' | (n',as') <- shrink (n,as)+ , n' >= 0+ , n' <= max_len as' + 1 ]+ Label str a -> Actions a : [ Label str a' | a' <- shrink a ]+ LookAhead a -> Actions a : [ LookAhead a' | a' <- shrink a ]+ LookAheadM b a -> Actions a : [ LookAheadM b a' | a' <- shrink a ]+ LookAheadE b a -> Actions a : [ LookAheadE b a' | a' <- shrink a ]+ Try [Fail] b -> Actions b : [ Try [Fail] b' | b' <- shrink b ] Try a b ->- [ Try a' b' | a' <- shrink a, b' <- shrink b ]- ++ [ Try a' b | a' <- shrink a ]- ++ [ Try a b' | b' <- shrink b ]+ [Actions a | not (willFail' a)]+ ++ [ Try a' b' | (a',b') <- shrink (a,b) ] -willFail :: [Action] -> Bool-willFail [] = False-willFail (x:xs) =- case x of- GetByteString _ -> willFail xs- Try a b -> (willFail a && willFail b) || willFail xs- BytesRead -> willFail xs- Fail -> True+willFail :: Int -> [Action] -> Bool+willFail inp xxs =+ case eval inp xxs of+ EFail {} -> True+ _ -> False +willFail' :: [Action] -> Bool+willFail' = willFail maxBound++-- | The maximum length of input decoder can request.+-- The decoder may end up using less, but never more.+-- This way, you know how much input to generate for running a decoder test. max_len :: [Action] -> Int max_len [] = 0 max_len (x:xs) = case x of- GetByteString n -> n + max_len xs+ Actions xs' -> max_len (xs' ++ xs) BytesRead -> max_len xs Fail -> 0- Try a b -> max (max_len a) (max_len b) + max_len xs+ GetByteString n -> n + max_len xs+ GetByteStringL n -> n + max_len xs+ Skip n -> n + max_len xs+ Isolate n xs'+ | Just _ <- actual_len' [Isolate n xs'] -> n + max_len xs+ | otherwise -> n+ Label _ xs' -> max_len (xs' ++ xs)+ LookAhead xs'+ | willFail' xs' -> max_len xs'+ | otherwise -> max (max_len xs') (max_len xs)+ LookAheadM consume xs'+ | consume -> max_len (xs' ++ xs)+ | otherwise -> max_len (LookAhead xs' : xs)+ LookAheadE consume xs'+ | consume -> max_len (xs' ++ xs)+ | otherwise -> max_len (LookAhead xs' : xs)+ Try a b+ | willFail' a && willFail' b -> max (max_len a) (max_len b)+ | willFail' a -> max (max_len a) (max_len b) + max_len xs+ | otherwise -> max_len (a ++ xs) -actual_len :: [Action] -> Maybe Int-actual_len = go 0- where- go !s [] = Just s- go !s (x:xs) =- case x of- GetByteString n -> go (s+n) xs- Fail -> Nothing- BytesRead -> go s xs- Try a b | not (willFail a) -> liftA2 (+) (go s a) (actual_len xs)- | not (willFail b) -> liftA2 (+) (go s b) (actual_len xs)- | otherwise -> Nothing+-- | The actual length of input that will be consumed when+-- a decoder is executed, or Nothing if the decoder will fail.+actual_len :: Int -> [Action] -> Maybe Int+actual_len inp xs =+ case eval inp xs of+ ESuccess inp' -> Just (inp - inp')+ _ -> Nothing +actual_len' :: [Action] -> Maybe Int+actual_len' = actual_len maxBound++randomInput :: Int -> Gen L.ByteString+randomInput 0 = return L.empty+randomInput n = do+ m <- choose (1, min n 10)+ s <- vectorOf m $ choose ('a', 'z')+ let b = B.pack $ map (fromIntegral.ord) s+ rest <- randomInput (n-m)+ return (L.append (L.fromChunks [b]) rest)+ -- | Build binary programs and compare running them to running a (hopefully) -- identical model. -- Tests that 'bytesRead' returns correct values when used together with '<|>' -- and 'fail'. prop_action :: Property prop_action =- forAllShrink gen_actions shrink $ \ actions ->- forAll arbitrary $ \ lbs ->- L.length lbs >= fromIntegral (max_len actions) ==>- case Binary.runGet (eval actions) lbs of- () -> True+ forAllShrink (gen_actions False) shrink $ \ actions ->+ let max_len_input = max_len actions in+ forAll (randomInput max_len_input) $ \ lbs ->+ let allInput = B.concat (L.toChunks lbs) in+ case Binary.runGetOrFail (execute allInput actions) lbs of+ Right (_inp, _off, _x) -> True+ Left (_inp, _off, _msg) -> True -eval :: [Action] -> Binary.Get ()-eval = go 0+-- | When a decoder aborts with 'fail', check that all relevant uses of 'label'+-- are respected.+prop_label :: Property+prop_label =+ forAllShrink (gen_actions True) shrink $ \ actions ->+ let max_len_input = max_len actions in+ forAll (randomInput max_len_input) $ \ lbs ->+ let allInput = B.concat (L.toChunks lbs) in+ collect (failReason $ eval max_len_input actions) $+ case Binary.runGetOrFail (execute allInput actions) lbs of+ Left (_inp, _off, msg) ->+ let lbls = case collectLabels max_len_input actions of+ Just lbls' -> lbls'+ Nothing -> error ("expected labels, got: " ++ msg)+ expectedMsg = concat $ intersperse "\n" lbls+ in expectedMsg === msg+ Right (_inp, _off, _value) -> label "test case without 'fail'" $ True++-- | When a decoder aborts with 'fail', check the fail position and+-- remaining input.+prop_fail :: Property+prop_fail =+ forAllShrink (gen_actions True) shrink $ \ actions ->+ let max_len_input = max_len actions in+ forAll (randomInput max_len_input) $ \ lbs ->+ let allInput = B.concat (L.toChunks lbs) in+ collect (failReason $ eval max_len_input actions) $+ case Binary.runGetOrFail (execute allInput actions) lbs of+ Left (inp, off, _msg) ->+ case () of+ _ | Just off /= findFailPosition max_len_input actions ->+ error ("fail position incorrect, expected " +++ show (findFailPosition max_len_input actions) +++ " but got " ++ show off)+ | inp /= L.drop (fromIntegral off) lbs ->+ error $ "remaining output incorrect, was: " ++ show inp +++ ", should hav been: " ++ show (L.drop (fromIntegral off) lbs)+ | otherwise -> property True+ Right (_inp, _off, _value) -> label "test case without 'fail'" $ property True++-- | Collect all the labels up to a 'fail', or Nothing if the+-- decoder will not fail.+collectLabels :: Int -> [Action] -> Maybe [String]+collectLabels inp xxs =+ case eval inp xxs of+ EFail _ lbls _ -> Just lbls+ _ -> Nothing++-- | Finds at which byte offset the decoder will fail,+-- or Nothing if it won't fail.+findFailPosition :: Int -> [Action] -> Maybe Binary.ByteOffset+findFailPosition inp xxs =+ case eval inp xxs of+ EFail _ _ inp' -> return (fromIntegral (inp-inp'))+ _ -> Nothing++failReason :: Eval -> String+failReason (EFail fr _ _) = show fr+failReason _ = "NoFail"++-- | The result of an evaluation.+data Eval = ESuccess Int+ -- ^ The evalutation completed successfully. Contains the number of+ -- remaining bytes of the input.+ | EFail FailReason [String] Int+ -- ^ The evaluation completed with a failure. Contains the labels up+ -- to the failure, and the number of remaining bytes of the input.+ deriving (Show,Eq)++data FailReason+ = FRFail+ | FRIsolateTooMuch+ | FRIsolateTooLittle+ | FRTooMuch+ deriving (Show,Eq)++-- | Given the number of input bytes and a list of actions, evaluate the+-- actions and return whether the actions succeeed or fail.+eval :: Int -> [Action] -> Eval+eval inp0 = go inp0 [] where+ step :: Int -> Int -> [String] -> [Action] -> Eval+ step inp n lbls xs+ | inp - n < 0 =+ let msg = "not enough bytes"+ in EFail FRTooMuch (msg:lbls) inp+ | otherwise = go (inp-n) lbls xs+ go :: Int -> [String] -> [Action] -> Eval+ go inp _lbls [] = ESuccess inp+ go inp lbls (x:xs) =+ case x of+ Actions xs' -> go inp lbls (xs'++xs)+ BytesRead -> go inp lbls xs+ Fail -> EFail FRFail ("fail":lbls) inp+ GetByteString n -> step inp n lbls xs+ GetByteStringL n -> step inp n lbls xs+ Skip n -> step inp n lbls xs+ Isolate n xs'+ | n > inp ->+ case go inp lbls xs' of+ ESuccess inp' ->+ let msg = "isolate: the decoder consumed " ++ show (inp - inp') +++ " bytes which is less than the expected " ++ (show n) +++ " bytes"+ in EFail FRTooMuch (msg:lbls) inp'+ efail -> efail+ | otherwise ->+ case go n lbls xs' of+ EFail fr lbls' inp' -> EFail fr lbls' (inp - n + inp')+ ESuccess 0 -> go (inp-n) lbls xs+ ESuccess inp' ->+ let msg = "isolate: the decoder consumed " ++ show (n - inp') +++ " bytes which is less than the expected " ++ (show n) +++ " bytes"+ in EFail FRIsolateTooLittle (msg:lbls) (inp - n + inp')+ Label str xs' ->+ case go inp (str:lbls) xs' of+ EFail fr lbls' inp' -> EFail fr lbls' inp'+ ESuccess inp' -> go inp' lbls xs+ LookAhead xs'+ | EFail fr lbls' inp' <- go inp lbls xs' -> EFail fr lbls' inp'+ | otherwise -> go inp lbls xs+ LookAheadM consume xs'+ | consume -> go inp lbls (xs'++xs)+ | otherwise -> go inp lbls (LookAhead xs' : xs)+ LookAheadE consume xs'+ | consume -> go inp lbls (xs'++xs)+ | otherwise -> go inp lbls (LookAhead xs' : xs)+ Try a b ->+ case go inp lbls a of+ ESuccess inp' -> go inp' lbls xs+ EFail {} -> go inp lbls (b++xs)+ +-- | Execute (run) the model.+-- First argument is all the input that will be used when executing+-- this decoder. It is used in this function to compare the expected+-- value with the actual value from the decoder functions.+-- The second argument is the model - the actions we will execute.+execute :: B.ByteString -> [Action] -> Binary.Get ()+execute inp acts0 = go 0 acts0 >> return ()+ where+ inp_len = B.length inp go _ [] = return () go pos (x:xs) = case x of- GetByteString n ->- Binary.getByteString n >> go (pos+n) xs+ Actions a -> go pos (a++xs)+ GetByteString n -> do+ -- Run the operation in the Get monad...+ actual <- Binary.getByteString n+ let expected = B.take n . B.drop pos $ inp+ -- ... and compare that we got what we expected.+ when (actual /= expected) $ error $+ "execute(getByteString): actual /= expected at pos " ++ show pos +++ ", got: " ++ show actual ++ ", expected: " ++ show expected+ go (pos+n) xs+ GetByteStringL n -> do+ -- Run the operation in the Get monad...+ actual <- L.toStrict <$> Binary.getLazyByteString (fromIntegral n)+ let expected = B.take n . B.drop pos $ inp+ -- ... and compare that we got what we expected.+ when (actual /= expected) $ error $+ "execute(getLazyByteString): actual /= expected at pos " ++ show pos +++ ", got: " ++ show actual ++ ", expected: " ++ show expected+ go (pos+n) xs+ Skip n -> do+ Binary.skip n+ go (pos+n) xs BytesRead -> do pos' <- Binary.bytesRead- if (pos == fromIntegral pos')+ if pos == fromIntegral pos' then go pos xs- else error $ "expected " ++ show pos ++ " but got " ++ show pos'+ else error $ "execute(bytesRead): expected " +++ show pos ++ " but got " ++ show pos' Fail -> fail "fail"- Try a b -> do- len <- leg pos a <|> leg pos b+ Isolate n as -> do+ let str = B.take n (B.drop pos inp)+ _ <- Binary.isolate n (execute str as)+ when (willFail (inp_len - pos) [Isolate n as]) $+ error "expected isolate to fail"+ go (pos + n) xs+ Label str as -> do+ len <- Binary.label str (leg pos as)+ go (pos+len) xs+ LookAhead a -> do+ _ <- Binary.lookAhead (go pos a)+ go pos xs+ LookAheadM b a -> do+ let f True = Just <$> leg pos a+ f False = go pos a >> return Nothing+ len <- Binary.lookAheadM (f b) case len of- Nothing -> error "got Nothing, but we're still here..."+ Nothing -> go pos xs Just offset -> go (pos+offset) xs- leg pos t = go pos t >> return (actual_len t)+ LookAheadE b a -> do+ let f True = Right <$> leg pos a+ f False = go pos a >> return (Left ())+ len <- Binary.lookAheadE (f b)+ case len of+ Left _ -> go pos xs+ Right offset -> go (pos+offset) xs+ Try a b -> do+ offset <- leg pos a <|> leg pos b+ go (pos+offset) xs+ leg pos t = do+ go pos t+ case actual_len (inp_len - pos) t of+ Nothing -> error "impossible: branch should have failed"+ Just offset -> return offset -gen_actions :: Gen [Action]-gen_actions = sized (go False)+gen_actions :: Bool -> Gen [Action]+gen_actions genFail = do+ acts <- sized (go False)+ return acts where go :: Bool -> Int -> Gen [Action] go _ 0 = return [] go inTry s = oneof $ [ do n <- choose (0,10) (:) (GetByteString n) <$> go inTry (s-1)+ , do n <- choose (0,10)+ (:) (GetByteStringL n) <$> go inTry (s-1)+ , do n <- choose (0,10)+ (:) (Skip n) <$> go inTry (s-1) , do (:) BytesRead <$> go inTry (s-1) , do t1 <- go True (s `div` 2) t2 <- go inTry (s `div` 2) (:) (Try t1 t2) <$> go inTry (s `div` 2)- ] ++ [ return [Fail] | inTry ]+ , do t <- go inTry (s`div`2)+ (:) (LookAhead t) <$> go inTry (s-1)+ , do t <- go inTry (s`div`2)+ b <- arbitrary+ (:) (LookAheadM b t) <$> go inTry (s-1)+ , do t <- go inTry (s`div`2)+ b <- arbitrary+ (:) (LookAheadE b t) <$> go inTry (s-1)+ , do t <- go inTry (s`div`2)+ Positive n <- arbitrary :: Gen (Positive Int)+ (:) (Label ("some label: " ++ show n) t) <$> go inTry (s-1)+ , do t <- resize (s`div`2) (gen_isolate (genFail || inTry))+ (:) t <$> go inTry (s-1)+ ] ++ [frequency [(if inTry || genFail then 1 else 0, return [Fail])+ ,(9 , go inTry s)]]++gen_isolate :: Bool -> Gen Action+gen_isolate genFail = gen_actions genFail >>= go+ where+ go t0 = do+ -- We can isolate the decoder with three different ranges;+ -- * give too few bytes -> isolate will fail+ -- * give exactly right amount of bytes -> isolate+ -- will succeed if the given decoder succeeds+ -- * give too many bytes -> isolate will fail+ -- Here we generate Isolates that belong to the different+ -- buckets.+ let t = t0+ tooFewBytes n = do+ n' <- choose (0, n)+ return (n',t)+ requiredBytes n = return (n,t)+ tooManyBytes n = do+ n' <- choose (n+1, n+10)+ return (n+n',t)+ let trees+ | Just n <- actual_len' t = oneof $+ [ requiredBytes n ] +++ [ tooFewBytes n | genFail ] +++ [ tooManyBytes n | genFail ]+ | otherwise = return (max_len t, t)+ (n,t') <- trees+ return (Isolate n t')
tests/Arbitrary.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Arbitrary where@@ -6,49 +7,17 @@ import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as L+#if MIN_VERSION_bytestring(0,10,4)+import qualified Data.ByteString.Short as S+#endif instance Arbitrary L.ByteString where- arbitrary = arbitrary >>= return . L.fromChunks . filter (not. B.null) -- maintain the invariant.+ arbitrary = fmap L.fromChunks arbitrary instance Arbitrary B.ByteString where arbitrary = B.pack `fmap` arbitrary -instance (Arbitrary a, Arbitrary b, Arbitrary c, Arbitrary d, Arbitrary e,- Arbitrary f) =>- Arbitrary (a,b,c,d,e,f) where- arbitrary = do- (a,b,c,d,e) <- arbitrary- f <- arbitrary- return (a,b,c,d,e,f)--instance (Arbitrary a, Arbitrary b, Arbitrary c, Arbitrary d, Arbitrary e,- Arbitrary f, Arbitrary g) =>- Arbitrary (a,b,c,d,e,f,g) where- arbitrary = do- (a,b,c,d,e) <- arbitrary- (f,g) <- arbitrary- return (a,b,c,d,e,f,g)--instance (Arbitrary a, Arbitrary b, Arbitrary c, Arbitrary d, Arbitrary e,- Arbitrary f, Arbitrary g, Arbitrary h) =>- Arbitrary (a,b,c,d,e,f,g,h) where- arbitrary = do- (a,b,c,d,e) <- arbitrary- (f,g,h) <- arbitrary- return (a,b,c,d,e,f,g,h)--instance (Arbitrary a, Arbitrary b, Arbitrary c, Arbitrary d, Arbitrary e,- Arbitrary f, Arbitrary g, Arbitrary h, Arbitrary i) =>- Arbitrary (a,b,c,d,e,f,g,h,i) where- arbitrary = do- (a,b,c,d,e) <- arbitrary- (f,g,h,i) <- arbitrary- return (a,b,c,d,e,f,g,h,i)--instance (Arbitrary a, Arbitrary b, Arbitrary c, Arbitrary d, Arbitrary e,- Arbitrary f, Arbitrary g, Arbitrary h, Arbitrary i, Arbitrary j) =>- Arbitrary (a,b,c,d,e,f,g,h,i,j) where- arbitrary = do- (a,b,c,d,e) <- arbitrary- (f,g,h,i,j) <- arbitrary- return (a,b,c,d,e,f,g,h,i,j)+#if MIN_VERSION_bytestring(0,10,4)+instance Arbitrary S.ShortByteString where+ arbitrary = S.toShort `fmap` arbitrary+#endif
+ tests/File.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE CPP #-}+module Main where++#if ! MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif++import System.Directory (getTemporaryDirectory)+import System.FilePath ((</>))+import Test.HUnit++import Distribution.Simple.Utils (withTempDirectory)+import Distribution.Verbosity (silent)++import Data.Binary++data Foo = Bar !Word32 !Word32 !Word32 deriving (Eq, Show)++instance Binary Foo where+ get = Bar <$> get <*> get <*> get+ put (Bar a b c) = put (a,b,c)++exampleData :: [Foo]+exampleData = make bytes+ where+ make (a:b:c:xs) = Bar a b c : make xs+ make _ = []+ bytes = take (256*1024) (cycle [minBound..maxBound])++readWriteTest :: Test+readWriteTest = TestCase $ do+ tmpDir <- getTemporaryDirectory+ withTempDirectory silent tmpDir "foo-dir" $ \dir -> do+ let fn = dir </> "foo.bin"+ encodeFile fn exampleData+ content <- decodeFile fn+ -- It'd be nice to use lsof to verify that 'fn' isn't still open.+ exampleData @=? content++main :: IO ()+main = do + _ <- runTestTT readWriteTest+ return ()
− tests/Makefile
@@ -1,20 +0,0 @@-ghc := ghc-ghc-flags := --all: compiled--interpreted:- runhaskell QC.hs 1000--compiled:- $(ghc) --make -fhpc -O QC.hs -o qc -fforce-recomp -threaded -rtsopts -i../src -XCPP -package test-framework -package test-framework-quickcheck2 $(ghc-flags)- rm -f qc.tix- ./qc --maximum-generated-tests=1000 -j2 +RTS -N2--hugs:- runhugs -98 QC.hs --clean:- rm -f *.o *.hi qc *.tix *~--.PHONY: clean
tests/QC.hs view
@@ -1,46 +1,47 @@-{-# LANGUAGE ScopedTypeVariables #-}-module Main where--import Data.Binary-import Data.Binary.Put-import Data.Binary.Get+{-# LANGUAGE CPP, ScopedTypeVariables, DataKinds, TypeSynonymInstances #-}+module Main ( main ) where -import Control.Applicative-import Control.Monad (unless)+#if MIN_VERSION_base(4,8,0)+#define HAS_NATURAL+#endif -import qualified Data.ByteString as B--- import qualified Data.ByteString.Internal as B--- import qualified Data.ByteString.Unsafe as B-import qualified Data.ByteString.Lazy as L-import qualified Data.ByteString.Lazy.Internal as L--- import qualified Data.Map as Map--- import qualified Data.Set as Set--- import qualified Data.IntMap as IntMap--- import qualified Data.IntSet as IntSet+#if MIN_VERSION_base(4,7,0)+#define HAS_FIXED_CONSTRUCTOR+#endif --- import Data.Array (Array)--- import Data.Array.IArray--- import Data.Array.Unboxed (UArray)+import Control.Applicative+import Control.Exception as C (SomeException,+ catch, evaluate)+import Control.Monad (unless, liftM2)+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Lazy.Internal as L+#if MIN_VERSION_bytestring(0,10,4)+import Data.ByteString.Short (ShortByteString)+#endif+import Data.Int+import Data.Ratio+import Data.Typeable+import System.IO.Unsafe --- import Data.Word-import Data.Int-import Data.Ratio+#ifdef HAS_NATURAL+import Numeric.Natural+#endif -import Control.Exception as C (catch,evaluate,SomeException)--- import Control.Monad--- import System.Environment--- import System.IO-import System.IO.Unsafe+import GHC.Fingerprint -import Test.QuickCheck--- import Text.Printf+import qualified Data.Fixed as Fixed -import Test.Framework-import Test.Framework.Providers.QuickCheck2--- import Data.Monoid+import Test.Framework+import Test.Framework.Providers.QuickCheck2+import Test.QuickCheck hiding (total) -import Action (prop_action)-import Arbitrary()+import qualified Action (tests)+import Arbitrary ()+import Data.Binary+import Data.Binary.Get+import Data.Binary.Put+import qualified Data.Binary.Class as Class ------------------------------------------------------------------------ @@ -61,7 +62,12 @@ (\(_e :: SomeException) -> return True) -- low level ones:+--+-- Words +prop_Word8 :: Word8 -> Property+prop_Word8 = roundTripWith putWord8 getWord8+ prop_Word16be :: Word16 -> Property prop_Word16be = roundTripWith putWord16be getWord16be @@ -92,7 +98,93 @@ prop_Wordhost :: Word -> Property prop_Wordhost = roundTripWith putWordhost getWordhost +-- Ints +prop_Int8 :: Int8 -> Property+prop_Int8 = roundTripWith putInt8 getInt8++prop_Int16be :: Int16 -> Property+prop_Int16be = roundTripWith putInt16be getInt16be++prop_Int16le :: Int16 -> Property+prop_Int16le = roundTripWith putInt16le getInt16le++prop_Int16host :: Int16 -> Property+prop_Int16host = roundTripWith putInt16host getInt16host++prop_Int32be :: Int32 -> Property+prop_Int32be = roundTripWith putInt32be getInt32be++prop_Int32le :: Int32 -> Property+prop_Int32le = roundTripWith putInt32le getInt32le++prop_Int32host :: Int32 -> Property+prop_Int32host = roundTripWith putInt32host getInt32host++prop_Int64be :: Int64 -> Property+prop_Int64be = roundTripWith putInt64be getInt64be++prop_Int64le :: Int64 -> Property+prop_Int64le = roundTripWith putInt64le getInt64le++prop_Int64host :: Int64 -> Property+prop_Int64host = roundTripWith putInt64host getInt64host++prop_Inthost :: Int -> Property+prop_Inthost = roundTripWith putInthost getInthost++-- Floats and Doubles++prop_Floatbe :: Float -> Property+prop_Floatbe = roundTripWith putFloatbe getFloatbe++prop_Floatle :: Float -> Property+prop_Floatle = roundTripWith putFloatle getFloatle++prop_Floathost :: Float -> Property+prop_Floathost = roundTripWith putFloathost getFloathost++prop_Doublebe :: Double -> Property+prop_Doublebe = roundTripWith putDoublebe getDoublebe++prop_Doublele :: Double -> Property+prop_Doublele = roundTripWith putDoublele getDoublele++prop_Doublehost :: Double -> Property+prop_Doublehost = roundTripWith putDoublehost getDoublehost++#if MIN_VERSION_base(4,10,0)+testTypeable :: Test+testTypeable = testProperty "TypeRep" prop_TypeRep++prop_TypeRep :: TypeRep -> Property+prop_TypeRep = roundTripWith Class.put Class.get++atomicTypeReps :: [TypeRep]+atomicTypeReps =+ [ typeRep (Proxy :: Proxy ())+ , typeRep (Proxy :: Proxy String)+ , typeRep (Proxy :: Proxy Int)+ , typeRep (Proxy :: Proxy (,))+ , typeRep (Proxy :: Proxy ((,) (Maybe Int)))+ , typeRep (Proxy :: Proxy Maybe)+ , typeRep (Proxy :: Proxy 'Nothing)+ , typeRep (Proxy :: Proxy 'Left)+ , typeRep (Proxy :: Proxy "Hello")+ , typeRep (Proxy :: Proxy 42)+ , typeRep (Proxy :: Proxy '[1,2,3,4])+ , typeRep (Proxy :: Proxy ('Left Int))+ , typeRep (Proxy :: Proxy (Either Int String))+ , typeRep (Proxy :: Proxy (() -> ()))+ ]++instance Arbitrary TypeRep where+ arbitrary = oneof (map pure atomicTypeReps)+#else+testTypeable :: Test+testTypeable = testGroup "Skipping Typeable tests" []+#endif+ -- done, partial and fail -- | Test partial results.@@ -199,7 +291,94 @@ (a,b) = decode (encode x) _types = [a,b] +-- In binary-0.5 the Get monad looked like+--+-- > data S = S {-# UNPACK #-} !B.ByteString+-- > L.ByteString+-- > {-# UNPACK #-} !Int64+-- >+-- > newtype Get a = Get { unGet :: S -> (# a, S #) }+--+-- with a helper function+--+-- > mkState :: L.ByteString -> Int64 -> S+-- > mkState l = case l of+-- > L.Empty -> S B.empty L.empty+-- > L.Chunk x xs -> S x xs+--+-- Note that mkState is strict in its first argument. This goes wrong in this+-- function:+--+-- > getBytes :: Int -> Get B.ByteString+-- > getBytes n = do+-- > S s ss bytes <- traceNumBytes n $ get+-- > if n <= B.length s+-- > then do let (consume,rest) = B.splitAt n s+-- > put $! S rest ss (bytes + fromIntegral n)+-- > return $! consume+-- > else+-- > case L.splitAt (fromIntegral n) (s `join` ss) of+-- > (consuming, rest) ->+-- > do let now = B.concat . L.toChunks $ consuming+-- > put $ mkState rest (bytes + fromIntegral n)+-- > -- forces the next chunk before this one is returned+-- > if (B.length now < n)+-- > then+-- > fail "too few bytes"+-- > else+-- > return now+--+-- Consider the else-branch of this function; suppose we ask for n bytes;+-- the call to L.splitAt gives us a lazy bytestring 'consuming' of precisely @n@+-- bytes (unless we don't have enough data, in which case we fail); but then+-- the strict evaluation of mkState on 'rest' means we look ahead too far.+--+-- Although this is all done completely differently in binary-0.7 it is+-- important that the same bug does not get introduced in some other way. The+-- test is basically the same test that already exists in this test suite,+-- verifying that+--+-- > decode . refragment . encode == id+--+-- However, we use a different 'refragment', one that introduces an exception+-- as the tail of the bytestring after rechunking. If we don't look ahead too+-- far then this should make no difference, but if we do then this will throw+-- an exception (for instance, in binary-0.5, this will throw an exception for+-- certain rechunkings, but not for others).+--+-- To make sure that the property holds no matter what refragmentation we use,+-- we test exhaustively for a single chunk, and all ways to break the string+-- into 2, 3 and 4 chunks.+prop_lookAheadIndepOfChunking :: (Eq a, Binary a) => a -> Property+prop_lookAheadIndepOfChunking testInput =+ forAll (testCuts (L.length (encode testInput))) $+ roundTrip testInput . rechunk+ where+ testCuts :: forall a. (Num a, Enum a) => a -> Gen [a]+ testCuts len = elements $ [ [] ]+ ++ [ [i]+ | i <- [0 .. len] ]+ ++ [ [i, j]+ | i <- [0 .. len]+ , j <- [0 .. len - i] ]+ ++ [ [i, j, k]+ | i <- [0 .. len]+ , j <- [0 .. len - i]+ , k <- [0 .. len - i - j] ] + -- Rechunk a bytestring, leaving the tail as an exception rather than Empty+ rechunk :: forall a. Integral a => [a] -> L.ByteString -> L.ByteString+ rechunk cuts = fromChunks . cut cuts . B.concat . L.toChunks+ where+ cut :: [a] -> B.ByteString -> [B.ByteString]+ cut [] bs = [bs]+ cut (i:is) bs = let (bs0, bs1) = B.splitAt (fromIntegral i) bs+ in bs0 : cut is bs1++ fromChunks :: [B.ByteString] -> L.ByteString+ fromChunks [] = error "Binary should not have to ask for this chunk!"+ fromChunks (bs:bss) = L.Chunk bs (fromChunks bss)+ -- String utilities prop_getLazyByteString :: L.ByteString -> Property@@ -282,6 +461,75 @@ ------------------------------------------------------------------------ +genInteger :: Gen Integer+genInteger = do+ b <- arbitrary+ if b then genIntegerSmall else genIntegerSmall++genIntegerSmall :: Gen Integer+genIntegerSmall = arbitrary++genIntegerBig :: Gen Integer+genIntegerBig = do+ x <- arbitrarySizedIntegral :: Gen Integer+ -- arbitrarySizedIntegral generates numbers smaller than+ -- (maxBound :: Word32), so let's make them bigger to better test+ -- the Binary instance.+ return (x + fromIntegral (maxBound :: Word32))++#ifdef HAS_NATURAL+genNatural :: Gen Natural+genNatural = do+ b <- arbitrary+ if b then genNaturalSmall else genNaturalBig++genNaturalSmall :: Gen Natural+genNaturalSmall = arbitrarySizedNatural++genNaturalBig :: Gen Natural+genNaturalBig = do+ x <- arbitrarySizedNatural :: Gen Natural+ -- arbitrarySizedNatural generates numbers smaller than+ -- (maxBound :: Word64), so let's make them bigger to better test+ -- the Binary instance.+ return (x + fromIntegral (maxBound :: Word64))+#endif++------------------------------------------------------------------------++genFingerprint :: Gen Fingerprint+genFingerprint = liftM2 Fingerprint arbitrary arbitrary+#if !MIN_VERSION_base(4,7,0)+instance Show Fingerprint where+ show (Fingerprint x1 x2) = show (x1,x2)+#endif++------------------------------------------------------------------------++#ifdef HAS_FIXED_CONSTRUCTOR++fixedPut :: forall a. Fixed.HasResolution a => Fixed.Fixed a -> Put+fixedPut x = put (truncate (x * fromInteger (Fixed.resolution (undefined :: Maybe a))) :: Integer)++fixedGet :: forall a. Fixed.HasResolution a => Get (Fixed.Fixed a)+fixedGet = (\x -> fromInteger x / fromInteger (Fixed.resolution (undefined :: Maybe a))) `liftA` get++-- | Serialise using base >=4.7 and <4.7 methods agree+prop_fixed_ser :: Fixed.Fixed Fixed.E3 -> Bool+prop_fixed_ser x = runPut (put x) == runPut (fixedPut x)++-- | Serialised with base >=4.7, unserialised with base <4.7 method roundtrip+prop_fixed_constr_resolution :: Fixed.Fixed Fixed.E3 -> Bool+prop_fixed_constr_resolution x = runGet fixedGet (runPut (put x)) == x++-- | Serialised with base <4.7, unserialised with base >=4.7 method roundtrip+prop_fixed_resolution_constr :: Fixed.Fixed Fixed.E3 -> Bool+prop_fixed_resolution_constr x = runGet get (runPut (fixedPut x)) == x++#endif++------------------------------------------------------------------------+ type T a = a -> Property type B a = a -> Bool @@ -291,6 +539,20 @@ test :: (Eq a, Binary a) => a -> Property test a = forAll positiveList (roundTrip a . refragment) +test' :: (Show a, Arbitrary a) => String -> (a -> Property) -> ([a] -> Property) -> Test+test' desc prop propList =+ testGroup desc [+ testProperty desc prop,+ testProperty ("[" ++ desc ++ "]") propList+ ]++testWithGen :: (Show a, Eq a, Binary a) => String -> Gen a -> Test+testWithGen desc gen =+ testGroup desc [+ testProperty desc (forAll gen test),+ testProperty ("[" ++ desc ++ "]") (forAll (listOf gen) test)+ ]+ positiveList :: Gen [Int] positiveList = fmap (filter (/=0) . map abs) $ arbitrary @@ -304,6 +566,9 @@ , testGroup "Boundaries" [ testProperty "read to much" (p (prop_readTooMuch :: B Word8)) , testProperty "read negative length" (p (prop_getByteString_negative :: T Int))+ , -- Arbitrary test input+ let testInput :: [Int] ; testInput = [0 .. 10]+ in testProperty "look-ahead independent of chunking" (p (prop_lookAheadIndepOfChunking testInput)) ] , testGroup "Partial"@@ -314,11 +579,11 @@ ] , testGroup "Model"- [ testProperty "action" Action.prop_action- ]+ Action.tests , testGroup "Primitives"- [ testProperty "Word16be" (p prop_Word16be)+ [ testProperty "Word8" (p prop_Word8)+ , testProperty "Word16be" (p prop_Word16be) , testProperty "Word16le" (p prop_Word16le) , testProperty "Word16host" (p prop_Word16host) , testProperty "Word32be" (p prop_Word32be)@@ -328,86 +593,103 @@ , testProperty "Word64le" (p prop_Word64le) , testProperty "Word64host" (p prop_Word64host) , testProperty "Wordhost" (p prop_Wordhost)+ -- Int+ , testProperty "Int8" (p prop_Int8)+ , testProperty "Int16be" (p prop_Int16be)+ , testProperty "Int16le" (p prop_Int16le)+ , testProperty "Int16host" (p prop_Int16host)+ , testProperty "Int32be" (p prop_Int32be)+ , testProperty "Int32le" (p prop_Int32le)+ , testProperty "Int32host" (p prop_Int32host)+ , testProperty "Int64be" (p prop_Int64be)+ , testProperty "Int64le" (p prop_Int64le)+ , testProperty "Int64host" (p prop_Int64host)+ , testProperty "Inthost" (p prop_Inthost)+ -- Float/Double+ , testProperty "Floatbe" (p prop_Floatbe)+ , testProperty "Floatle" (p prop_Floatle)+ , testProperty "Floathost" (p prop_Floathost)+ , testProperty "Doublebe" (p prop_Doublebe)+ , testProperty "Doublele" (p prop_Doublele)+ , testProperty "Doublehost" (p prop_Doublehost) ] , testGroup "String utils" [ testProperty "getLazyByteString" prop_getLazyByteString- , testProperty "getLazyByteStringNul" prop_getLazyByteStringNul + , testProperty "getLazyByteStringNul" prop_getLazyByteStringNul , testProperty "getLazyByteStringNul No Null" prop_getLazyByteStringNul_noNul- , testProperty "getRemainingLazyByteString" prop_getRemainingLazyByteString + , testProperty "getRemainingLazyByteString" prop_getRemainingLazyByteString ] - , testGroup "Using Binary class, refragmented ByteString" $ map (uncurry testProperty)- [ ("()", p (test :: T () ))- , ("Bool", p (test :: T Bool ))- , ("Ordering", p (test :: T Ordering ))- , ("Ratio Int", p (test :: T (Ratio Int) ))--- , ("Word8", p (test :: T Word8 ))- , ("Word16", p (test :: T Word16 ))- , ("Word32", p (test :: T Word32 ))- , ("Word64", p (test :: T Word64 ))+ , testGroup "Using Binary class, refragmented ByteString"+ [ test' "()" (test :: T () ) test+ , test' "Bool" (test :: T Bool ) test+ , test' "Char" (test :: T Char ) test+ , test' "Ordering" (test :: T Ordering ) test+ , test' "Ratio Int" (test :: T (Ratio Int)) test - , ("Int8", p (test :: T Int8 ))- , ("Int16", p (test :: T Int16 ))- , ("Int32", p (test :: T Int32 ))- , ("Int64", p (test :: T Int64 ))+ , test' "Word" (test :: T Word ) test+ , test' "Word8" (test :: T Word8 ) test+ , test' "Word16" (test :: T Word16) test+ , test' "Word32" (test :: T Word32) test+ , test' "Word64" (test :: T Word64) test - , ("Word", p (test :: T Word ))- , ("Int", p (test :: T Int ))- , ("Integer", p (test :: T Integer ))+ , test' "Int" (test :: T Int ) test+ , test' "Int8" (test :: T Int8 ) test+ , test' "Int16" (test :: T Int16) test+ , test' "Int32" (test :: T Int32) test+ , test' "Int64" (test :: T Int64) test - , ("Float", p (test :: T Float ))- , ("Double", p (test :: T Double ))+ , testWithGen "Integer mixed" genInteger+ , testWithGen "Integer small" genIntegerSmall+ , testWithGen "Integer big" genIntegerBig - , ("Char", p (test :: T Char ))+ , test' "Fixed" (test :: T (Fixed.Fixed Fixed.E3) ) test+#ifdef HAS_NATURAL+ , testWithGen "Natural mixed" genNatural+ , testWithGen "Natural small" genNaturalSmall+ , testWithGen "Natural big" genNaturalBig+#endif+ , testWithGen "GHC.Fingerprint" genFingerprint - , ("[()]", p (test :: T [()] ))- , ("[Word8]", p (test :: T [Word8] ))- , ("[Word32]", p (test :: T [Word32] ))- , ("[Word64]", p (test :: T [Word64] ))- , ("[Word]", p (test :: T [Word] ))- , ("[Int]", p (test :: T [Int] ))- , ("[Integer]", p (test :: T [Integer] ))- , ("String", p (test :: T String ))- , ("((), ())", p (test :: T ((), ()) ))- , ("(Word8, Word32)", p (test :: T (Word8, Word32) ))- , ("(Int8, Int32)", p (test :: T (Int8, Int32) ))- , ("(Int32, [Int])", p (test :: T (Int32, [Int]) ))+ , test' "Float" (test :: T Float ) test+ , test' "Double" (test :: T Double) test - , ("Maybe Int8", p (test :: T (Maybe Int8) ))- , ("Either Int8 Int16", p (test :: T (Either Int8 Int16) ))+ , test' "((), ())" (test :: T ((), ()) ) test+ , test' "(Word8, Word32)" (test :: T (Word8, Word32) ) test+ , test' "(Int8, Int32)" (test :: T (Int8, Int32) ) test+ , test' "(Int32, [Int])" (test :: T (Int32, [Int]) ) test+ , test' "Maybe Int8" (test :: T (Maybe Int8) ) test+ , test' "Either Int8 Int16" (test :: T (Either Int8 Int16) ) test - , ("(Int, ByteString)",- p (test :: T (Int, B.ByteString) ))- , ("[(Int, ByteString)]",- p (test :: T [(Int, B.ByteString)] ))+ , test' "(Int, ByteString)"+ (test :: T (Int, B.ByteString) ) test+ , test' "[(Int, ByteString)]"+ (test :: T [(Int, B.ByteString)] ) test - , ("(Maybe Int64, Bool, [Int])",- p (test :: T (Maybe Int64, Bool, [Int])))- , ("(Maybe Word8, Bool, [Int], Either Bool Word8)",- p (test :: T (Maybe Word8, Bool, [Int], Either Bool Word8) ))- , ("(Maybe Word16, Bool, [Int], Either Bool Word16, Int)",- p (test :: T (Maybe Word16, Bool, [Int], Either Bool Word16, Int) ))+ , test' "(Maybe Int64, Bool, [Int])"+ (test :: T (Maybe Int64, Bool, [Int])) test+ , test' "(Maybe Word8, Bool, [Int], Either Bool Word8)"+ (test :: T (Maybe Word8, Bool, [Int], Either Bool Word8)) test+ , test' "(Maybe Word16, Bool, [Int], Either Bool Word16, Int)"+ (test :: T (Maybe Word16, Bool, [Int], Either Bool Word16, Int)) test - , ("(Int,Int,Int,Int,Int,Int)",- p (test :: T (Int,Int,Int,Int,Int,Int)))- , ("(Int,Int,Int,Int,Int,Int,Int)",- p (test :: T (Int,Int,Int,Int,Int,Int,Int)))- , ("(Int,Int,Int,Int,Int,Int,Int,Int)",- p (test :: T (Int,Int,Int,Int,Int,Int,Int,Int)))- , ("(Int,Int,Int,Int,Int,Int,Int,Int,Int)",- p (test :: T (Int,Int,Int,Int,Int,Int,Int,Int,Int)))- , ("(Int,Int,Int,Int,Int,Int,Int,Int,Int,Int)",- p (test :: T (Int,Int,Int,Int,Int,Int,Int,Int,Int,Int)))- {-- , ("IntSet", p (test :: T IntSet.IntSet ))- , ("IntMap ByteString", p (test :: T (IntMap.IntMap B.ByteString) ))- -}+ , test' "(Int,Int,Int,Int,Int,Int)"+ (test :: T (Int,Int,Int,Int,Int,Int)) test+ , test' "(Int,Int,Int,Int,Int,Int,Int)"+ (test :: T (Int,Int,Int,Int,Int,Int,Int)) test+ , test' "(Int,Int,Int,Int,Int,Int,Int,Int)"+ (test :: T (Int,Int,Int,Int,Int,Int,Int,Int)) test+ , test' "(Int,Int,Int,Int,Int,Int,Int,Int,Int)"+ (test :: T (Int,Int,Int,Int,Int,Int,Int,Int,Int)) test+ , test' "(Int,Int,Int,Int,Int,Int,Int,Int,Int,Int)"+ (test :: T (Int,Int,Int,Int,Int,Int,Int,Int,Int,Int)) test - , ("B.ByteString", p (test :: T B.ByteString ))- , ("L.ByteString", p (test :: T L.ByteString ))+ , test' "B.ByteString" (test :: T B.ByteString) test+ , test' "L.ByteString" (test :: T L.ByteString) test+#if MIN_VERSION_bytestring(0,10,4)+ , test' "ShortByteString" (test :: T ShortByteString) test+#endif ] , testGroup "Invariants" $ map (uncurry testProperty)@@ -415,8 +697,17 @@ , ("[B.ByteString] invariant", p (prop_invariant :: B [B.ByteString] )) , ("L.ByteString invariant", p (prop_invariant :: B L.ByteString )) , ("[L.ByteString] invariant", p (prop_invariant :: B [L.ByteString] ))+#if MIN_VERSION_bytestring(0,10,4)+ , ("ShortByteString invariant", p (prop_invariant :: B ShortByteString ))+ , ("[ShortByteString] invariant", p (prop_invariant :: B [ShortByteString] ))+#endif ]+#ifdef HAS_FIXED_CONSTRUCTOR+ , testGroup "Fixed"+ [ testProperty "Serialisation same" $ p prop_fixed_ser+ , testProperty "MkFixed -> HasResolution" $ p prop_fixed_constr_resolution+ , testProperty "HasResolution -> MkFixed" $ p prop_fixed_resolution_constr+ ]+#endif+ , testTypeable ]---- GHC only:--- ,("Sequence", p (roundTrip :: Seq.Seq Int64 -> Bool))
tools/derive/BinaryDerive.hs view
@@ -17,7 +17,7 @@ | nTypeChildren > 0 = wrap (join ", " (map ("Binary "++) typeLetters)) ++ " => " | otherwise = ""- inst = wrap $ tyConString typeName ++ concatMap (" "++) typeLetters+ inst = wrap $ tyConName typeName ++ concatMap (" "++) typeLetters wrap x = if nTypeChildren > 0 then "("++x++")" else x join sep lst = concat $ intersperse sep lst nTypeChildren = length typeChildren