binary 0.6.4.0 → 0.7.0.0
raw patch · 14 files changed
+487/−198 lines, 14 filesdep +Cabaldep +HUnitdep +attoparsecdep ~basedep ~bytestringPVP ok
version bump matches the API change (PVP)
Dependencies added: Cabal, HUnit, attoparsec, cereal, directory, filepath
Dependency ranges changed: base, bytestring
API changes (from Hackage documentation)
+ Data.Binary: decodeFileOrFail :: Binary a => FilePath -> IO (Either (ByteOffset, String) a)
+ Data.Binary: decodeOrFail :: Binary a => ByteString -> Either (ByteString, ByteOffset, String) (ByteString, ByteOffset, a)
+ Data.Binary.Get: lookAhead :: Get a -> Get a
+ Data.Binary.Get: lookAheadM :: Get (Maybe a) -> Get (Maybe a)
+ Data.Binary.Get.Internal: lookAhead :: Get a -> Get a
+ Data.Binary.Get.Internal: lookAheadM :: Get (Maybe a) -> Get (Maybe a)
Files
- .gitignore +0/−14
- .hgignore +0/−5
- README +1/−0
- TODO +0/−28
- benchmarks/Get.hs +105/−41
- benchmarks/Makefile +1/−1
- binary.cabal +33/−4
- src/Data/Binary.hs +58/−49
- src/Data/Binary/Class.hs +2/−2
- src/Data/Binary/Get.hs +129/−21
- src/Data/Binary/Get/Internal.hs +46/−14
- tests/Action.hs +72/−18
- tests/Arbitrary.hs +1/−1
- tests/File.hs +39/−0
− .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 view
@@ -74,4 +74,5 @@ Tomasz Zielonka Stefan Karrmann Bryan O'Sullivan+ Bas van Dijk Florian Weimer
− 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/Get.hs view
@@ -4,7 +4,7 @@ #include "MachDeps.h" #endif -module Main (main) where+module Main where import Control.DeepSeq import Control.Exception (evaluate)@@ -22,6 +22,12 @@ import Data.Binary.Get import Data.Binary ( get ) +import qualified Data.Serialize.Get as Cereal+import qualified Data.Serialize 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,21 +38,35 @@ main = do evaluate $ rnf [ rnf brackets,+ rnf bracketsInChunks,+ rnf bracketCount, rnf oneMegabyte, rnf oneMegabyteLBS ] 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" $+ bench "brackets 100kb one chunk input" $+ whnf (checkBracket . runTest bracketParser) brackets+ , bench "brackets 100kb in 100 byte chunks" $+ whnf (checkBracket . runTest bracketParser) bracketsInChunks+ , bench "Attoparsec lazy-bs brackets 100kb one chunk" $+ whnf (checkBracket . runAttoL bracketParser_atto) brackets+ , bench "Attoparsec lazy-bs brackets 100kb in 100 byte chunks" $+ whnf (checkBracket . runAttoL bracketParser_atto) bracketsInChunks+ , bench "Attoparsec strict-bs brackets 10M0kb one chunk" $+ whnf (checkBracket . runAtto bracketParser_atto) $ S.concat (L.toChunks brackets)+ , bench "Binary getStruct4 1MB struct of 4 word8" $ whnf (runTest (getStruct4 mega)) oneMegabyteLBS- , bench "getWord8 1MB chunk size 1 byte" $+ , bench "Cereal getStruct4 1MB struct of 4 word8" $+ whnf (runCereal (getStruct4_cereal mega)) oneMegabyte+ , bench "Attoparsec getStruct4 1MB struct of 4 word8" $+ whnf (runAtto (getStruct4_atto mega)) oneMegabyte+ , bench "Binary getWord8 1MB chunk size 1 byte" $ whnf (runTest (getWord8N1 mega)) oneMegabyteLBS+ , bench "Cereal getWord8 1MB chunk size 1 byte" $+ whnf (runCereal (getWord8N1_cereal mega)) oneMegabyte+ , bench "Attoparsec getWord8 1MB chunk size 1 byte" $+ whnf (runAtto (getWord8N1_atto mega)) oneMegabyte , bench "getWord8 1MB chunk size 2 bytes" $ whnf (runTest (getWord8N2 mega)) oneMegabyteLBS , bench "getWord8 1MB chunk size 4 bytes" $@@ -65,8 +85,18 @@ whnf (runTest (getWord8N16A mega)) oneMegabyteLBS ] -runTest parser inp = runGet parser inp+checkBracket x | x == bracketCount = x+ | otherwise = error "argh!" +runTest decoder inp = runGet decoder inp+runCereal decoder inp = Cereal.runGet decoder inp+runAtto decoder inp = case A.parseOnly decoder inp of+ Right a -> a+ Left err -> error err+runAttoL decoder inp = case AL.parse decoder inp of+ AL.Done _ r -> r+ a -> error (show a)+ -- Defs. oneMegabyte :: S.ByteString@@ -80,49 +110,69 @@ -- 100k of brackets 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.fromChunks [C8.concat (L.toChunks bracketsInChunks)]+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--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 Struct4 = Struct4 {-# UNPACK #-} !Word8+ {-# UNPACK #-} !Word8+ {-# UNPACK #-} !Word8+ {-# UNPACK #-} !Word8+ deriving Show -getStruct4Strict = loop []+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+ !w0 <- getWord8+ !w1 <- getWord8+ !w2 <- getWord8+ !w3 <- getWord8+ let !s = Struct4 w0 w1 w2 w3+ loop (s : acc) (n - 4) -instance NFData Struct4 where- rnf (Struct4 !a !b !c !d) = ()+getStruct4_cereal = loop []+ where loop acc 0 = return acc+ loop acc n = do+ !w0 <- Cereal.getWord8+ !w1 <- Cereal.getWord8+ !w2 <- Cereal.getWord8+ !w3 <- Cereal.getWord8+ let !s = Struct4 w0 w1 w2 w3+ loop (s : acc) (n - 4) -getStruct4 = loop []+getStruct4_atto = 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 <- A.anyWord8+ !w1 <- A.anyWord8+ !w2 <- A.anyWord8+ !w3 <- A.anyWord8+ let !s = Struct4 w0 w1 w2 w3+ loop (s : acc) (n - 4) -- No-allocation loops. @@ -131,15 +181,29 @@ loop s 0 = return s loop s n = do s0 <- getWord8- loop (s+s0) (n-1)+ loop (s0+s) (n-1) +getWord8N1_cereal = loop 0+ 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 = loop 0+ 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 = loop 0 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)+ loop (s0+s1+s) (n-2) getWord8N2A = loop 0 where loop s n | s `seq` n `seq` False = undefined
benchmarks/Makefile view
@@ -2,7 +2,7 @@ ghc-flags := programs := builder bench -SYSTEM_BINARY := binary-0.5.1.0+SYSTEM_BINARY := binary-0.5.1.1 .PHONY: all all: $(programs)
binary.cabal view
@@ -1,13 +1,15 @@ name: binary-version: 0.6.4.0+version: 0.7.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.@@ -17,8 +19,14 @@ 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+extra-source-files:+ README index.html docs/hcar/binary-Lb.tex+ tools/derive/*.hs tests/Makefile benchmarks/Makefile +-- from the benchmark 'bench'+extra-source-files:+ benchmarks/CBenchmark.h+ source-repository head type: git location: git://github.com/kolmodin/binary.git@@ -52,6 +60,9 @@ type: exitcode-stdio-1.0 hs-source-dirs: tests main-is: QC.hs+ other-modules:+ Action+ Arbitrary build-depends: array, base >= 3.0 && < 5,@@ -62,13 +73,29 @@ test-framework-quickcheck2, QuickCheck>=2.5 - ghc-options: -Wall+ ghc-options: -Wall -O2 hs-source-dirs: src +test-suite read-write-file+ type: exitcode-stdio-1.0+ hs-source-dirs: tests+ main-is: File.hs+ build-depends:+ base >= 3.0 && < 5,+ bytestring >= 0.9,+ binary,+ Cabal,+ directory,+ filepath,+ HUnit++ ghc-options: -Wall+ benchmark bench type: exitcode-stdio-1.0 hs-source-dirs: benchmarks main-is: Benchmark.hs+ other-modules: MemBench build-depends: base >= 3.0 && < 5, binary,@@ -82,9 +109,11 @@ hs-source-dirs: benchmarks main-is: Get.hs build-depends:+ attoparsec, base >= 3.0 && < 5, binary, bytestring,+ cereal, criterion, deepseq, mtl
src/Data/Binary.hs view
@@ -10,7 +10,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,16 +18,23 @@ -- 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 'GBinary'.+-- +-- 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". -- ----------------------------------------------------------------------------- @@ -55,14 +62,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 @@ -77,8 +82,11 @@ 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 +103,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 +173,23 @@ {-# 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.+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 +202,32 @@ 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. 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/Class.hs view
@@ -460,11 +460,11 @@ ------------------------------------------------------------------------ -- Maps and Sets -instance (Ord a, Binary a) => Binary (Set.Set a) where+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+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
src/Data/Binary/Get.hs view
@@ -17,15 +17,111 @@ -- 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.+-- <http://www.haskell.org/ghc/docs/latest/html/users_guide/pragmas.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'+-- @+--+-- The applicative style can sometimes result in faster code, as @binary@+-- will try to optimize the code by grouping the reads together.+--+-- 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:+--+-- @+-- example :: IO ()+-- example = do+-- input <- BL.readFile \"trades.bin\"+-- let trades = runGet getTrades input +-- print trades+-- @+--+-- 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 knowning+-- that it can decode all input.+--+-- @+-- example2 :: BL.ByteString -> [Trade]+-- example2 input+-- | BL.null input = []+-- | otherwise =+-- let (trade, rest, _) = 'runGetState' getTrade input 0+-- in trade : example2 rest+-- @+--+-- Both these examples use 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.+-- For an example of this, see the implementation of 'decodeFileOrFail' in+-- "Data.Binary". ----------------------------------------------------------------------------- module Data.Binary.Get ( - -- * The Get type+ -- * The Get monad Get -- * The lazy input interface@@ -44,11 +140,12 @@ , pushChunks , pushEndOfInput - -- * Parsing+ -- * Decoding , skip , isEmpty , bytesRead- -- , lookAhead+ , lookAhead+ , lookAheadM -- ** ByteStrings , getByteString@@ -85,6 +182,7 @@ import qualified Data.ByteString 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 Control.Applicative @@ -161,27 +259,36 @@ -- unused input, and the number of consumed bytes. {-# DEPRECATED runGetState "Use runGetPartial 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. 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 +296,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) @@ -213,7 +318,8 @@ 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@@ -222,7 +328,9 @@ 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.
src/Data/Binary/Get/Internal.hs view
@@ -28,6 +28,8 @@ , remaining , getBytes , isEmpty+ , lookAhead+ , lookAheadM -- ** ByteStrings , getByteString@@ -77,7 +79,7 @@ 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 @@ -197,14 +199,12 @@ 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+ (<|>) 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" some p = (:) <$> p <*> many p many p = do v <- (Just <$> p) <|> pure Nothing@@ -212,16 +212,48 @@ Nothing -> pure [] Just x -> (:) x <$> many p --- | 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 #-}++-- | Run the given decoder, but without consuming its input. If the given+-- decoder fails, then so will this function.+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.+lookAheadM :: Get (Maybe a) -> Get (Maybe a)+lookAheadM g = do+ (decoder, bs) <- runAndKeepTrack g+ case decoder of+ Done _ Nothing -> pushBack bs >> return Nothing+ Done inp (Just x) -> C $ \_ ks -> ks inp (Just x)+ Fail inp s -> C $ \_ _ -> Fail inp s+ _ -> error "Binary: impossible" -- | DEPRECATED. Get the number of bytes of remaining input. -- Note that this is an expensive function to use as in order to calculate how
tests/Action.hs view
@@ -2,8 +2,10 @@ module Action where import Control.Applicative+import Control.Monad import Test.QuickCheck +import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as L import qualified Data.Binary.Get as Binary@@ -11,8 +13,12 @@ import Arbitrary() data Action- = GetByteString Int+ = Actions [Action]+ | GetByteString Int | Try [Action] [Action]+ | LookAhead [Action]+ -- | First argument is True if this action returns Just, otherwise False.+ | LookAheadM Bool [Action] | BytesRead | Fail deriving (Show, Eq)@@ -20,11 +26,17 @@ instance Arbitrary Action where shrink action = case action of- GetByteString n -> [ GetByteString n' | n' <- shrink n, n > 0 ]+ Actions [a] -> [a]+ Actions as -> [ Actions as' | as' <- shrink as ]+ GetByteString n -> [ GetByteString n' | n' <- shrink n, n >= 0 ] BytesRead -> [] Fail -> []+ LookAhead a -> Actions a : [ LookAhead a' | a' <- shrink a ]+ LookAheadM b a -> Actions a : [ LookAheadM 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 ]+ (if not (willFail a) then [Actions a] else [])+ ++ [ Try a' b' | a' <- shrink a, b' <- shrink b ] ++ [ Try a' b | a' <- shrink a ] ++ [ Try a b' | b' <- shrink b ] @@ -32,8 +44,11 @@ willFail [] = False willFail (x:xs) = case x of+ Actions x' -> willFail x' || willFail xs GetByteString _ -> willFail xs Try a b -> (willFail a && willFail b) || willFail xs+ LookAhead a -> willFail a || willFail xs+ LookAheadM _ a -> willFail a || willFail xs BytesRead -> willFail xs Fail -> True @@ -41,23 +56,35 @@ max_len [] = 0 max_len (x:xs) = case x of+ Actions x' -> max_len x' + max_len xs GetByteString n -> n + max_len xs BytesRead -> max_len xs Fail -> 0 Try a b -> max (max_len a) (max_len b) + max_len xs+ LookAhead a -> max (max_len a) (max_len xs)+ LookAheadM b a | willFail a -> max_len a+ | b -> max_len a + max_len xs+ | otherwise -> max (max_len a) (max_len xs) actual_len :: [Action] -> Maybe Int-actual_len = go 0+actual_len [] = Just 0+actual_len (x:xs) =+ case x of+ Actions x' -> (+) <$> actual_len x' <*> rest+ GetByteString n -> (n+) <$> rest+ Fail -> Nothing+ BytesRead -> rest+ LookAhead a | willFail a -> Nothing+ | otherwise -> rest+ LookAheadM b a | willFail a -> Nothing+ | b -> (+) <$> actual_len a <*> rest+ | otherwise -> rest+ Try a b | not (willFail a) -> (+) <$> actual_len a <*> rest+ | not (willFail b) -> (+) <$> actual_len b <*> rest+ | otherwise -> Nothing+ 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+ rest = actual_len xs -- | Build binary programs and compare running them to running a (hopefully) -- identical model.@@ -68,23 +95,45 @@ forAllShrink gen_actions shrink $ \ actions -> forAll arbitrary $ \ lbs -> L.length lbs >= fromIntegral (max_len actions) ==>- case Binary.runGet (eval actions) lbs of+ let allInput = B.concat (L.toChunks lbs) in+ case Binary.runGet (eval allInput actions) lbs of () -> True -eval :: [Action] -> Binary.Get ()-eval = go 0+-- | Evaluate (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 evaluate.+eval :: B.ByteString -> [Action] -> Binary.Get ()+eval str = go 0 where 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 $ str+ -- ... and compare that we got what we expected.+ when (actual /= expected) $ error "actual /= expected"+ go (pos+n) xs BytesRead -> do pos' <- Binary.bytesRead if (pos == fromIntegral pos') then go pos xs else error $ "expected " ++ show pos ++ " but got " ++ show pos' Fail -> fail "fail"+ LookAhead a -> do+ _ <- Binary.lookAhead (go pos a)+ go pos xs+ LookAheadM b a -> do+ let f True = leg pos a+ f False = go pos a >> return Nothing+ len <- Binary.lookAheadM (f b)+ case len of+ Nothing -> go pos xs+ Just offset -> go (pos+offset) xs Try a b -> do len <- leg pos a <|> leg pos b case len of@@ -103,4 +152,9 @@ , do t1 <- go True (s `div` 2) t2 <- go inTry (s `div` 2) (:) (Try t1 t2) <$> go inTry (s `div` 2)+ , 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) ] ++ [ return [Fail] | inTry ]
tests/Arbitrary.hs view
@@ -8,7 +8,7 @@ import qualified Data.ByteString.Lazy as L 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
+ tests/File.hs view
@@ -0,0 +1,39 @@+module Main where++import Control.Applicative+import Test.HUnit+import System.Directory ( getTemporaryDirectory )+import System.FilePath ( (</>) )++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 ()