packages feed

binary-ext (empty) → 0.8.4.1

raw patch · 12 files changed

+2521/−0 lines, 12 filesdep +Cabaldep +HUnitdep +QuickChecksetup-changed

Dependencies added: Cabal, HUnit, QuickCheck, array, base, binary, bytestring, containers, directory, filepath, ghc-prim, random, test-framework, test-framework-quickcheck2

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) Lennart Kolmodin++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++1. Redistributions of source code must retain the above copyright+   notice, this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright+   notice, this list of conditions and the following disclaimer in the+   documentation and/or other materials provided with the distribution.++3. Neither the name of the author nor the names of his contributors+   may be used to endorse or promote products derived from this software+   without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,26 @@+# binary-ext package #++An alternate with typed errors for ``Data.Binary.Get`` monad from ``binary`` library.++## Building binary-ext ##++Here's how to get the latest version of the repository and build.++    $ git clone https://github.com/A1-Triard/binary-ext.git+    $ cd binary-ext+    $ stack build++Run the test suite.++    $ stack test++## Using binary-ext ##++First:++    import Data.Binary.Put+    import Data.Binary.Get.Ext++and then use the ``Get`` and ``Put`` monads to serialize/deserialize.++More information in the haddock documentation.
+ Setup.lhs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
+ binary-ext.cabal view
@@ -0,0 +1,94 @@+name:            binary-ext+version:         0.8.4.1+license:         BSD3+license-file:    LICENSE+author:          Lennart Kolmodin <kolmodin@gmail.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.+synopsis:        Binary serialisation for Haskell values using lazy ByteStrings+category:        Data, Parsing+stability:       provisional+build-type:      Simple+cabal-version:   >= 1.8+tested-with:     GHC == 7.4.2, GHC == 7.6.3, GHC == 7.8.4, GHC == 7.10.3+extra-source-files:+  README.md++source-repository head+  type: git+  location: git://github.com/A1-Triard/binary-ext.git++library+  build-depends:   base >= 4.5.0.0 && < 5, bytestring >= 0.10.2, containers, array, binary+  hs-source-dirs:  src+  exposed-modules: Data.Binary.Get.Ext+                 , Data.Binary.Get.Ext.Internal++  other-modules:   Data.Binary.Internal,+                   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+-- depend on the binary library. Instead, for each test-suite,+-- 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: src tests+  main-is: QC.hs+  other-modules: Action+               , Arbitrary+               , Data.Binary.Get.Ext+               , Data.Binary.Get.Ext.Internal+               , Data.Binary.Internal+               , Data.Binary.FloatCast+  build-depends: base >= 4.5.0.0 && < 5+               , binary+               , bytestring >= 0.10.2+               , random>=1.0.1.0+               , test-framework+               , test-framework-quickcheck2 >= 0.3+               , QuickCheck++  -- 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+  build-depends: base >= 4.5.0.0 && < 5+               , bytestring >= 0.10.2+               , Cabal+               , binary+               , directory+               , filepath+               , HUnit++  -- build dependencies from using binary source rather than depending on the library+  build-depends: array, containers+  ghc-options: -Wall+  if impl(ghc <= 7.6)+    -- prior to ghc-7.4 generics lived in ghc-prim+    build-depends: ghc-prim
+ 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/Get/Ext.hs view
@@ -0,0 +1,650 @@+{-# LANGUAGE CPP, RankNTypes, MagicHash, BangPatterns #-}+{-# LANGUAGE Trustworthy #-}++#if defined(__GLASGOW_HASKELL__) && !defined(__HADDOCK__)+#include "MachDeps.h"+#endif++-----------------------------------------------------------------------------+-- |+-- Module      : Data.Binary.Get.Ext+-- 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+-- 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'+-- @+--+-- 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.Ext (++    -- * The Get monad+      Get++    -- * The lazy input interface+    -- $lazyinterface+    , runGetOrFail+    , ByteOffset++    -- * The incremental input interface+    -- $incrementalinterface+    , Decoder(..)+    , runGetIncremental++    -- ** Providing input+    , pushChunk+    , pushChunks+    , pushEndOfInput++    -- * Decoding+    , skip+    , isEmpty+    , bytesRead+    , totalBytesRead+    , isolate+    , lookAhead+    , lookAheadM+    , lookAheadE+    , label+    , onError+    , withError+    , failG++    -- ** ByteStrings+    , getByteString+    , getLazyByteString+    , getLazyByteStringNul+    , getRemainingLazyByteString++    -- ** Decoding Words+    , getWord8++    -- *** Big-endian decoding+    , getWord16be+    , getWord32be+    , getWord64be++    -- *** Little-endian decoding+    , getWord16le+    , getWord32le+    , getWord64le++    -- *** Host-endian, unaligned decoding+    , getWordhost+    , getWord16host+    , 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++    ) 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 qualified Data.ByteString.Lazy.Internal as L++import Data.Binary.Get.Ext.Internal hiding ( Decoder(..), runGetIncremental )+import qualified Data.Binary.Get.Ext.Internal as I++#if defined(__GLASGOW_HASKELL__) && !defined(__HADDOCK__)+-- needed for (# unboxing #) with magic hash+import GHC.Base+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.++-- $incrementalinterface+-- The incremental interface gives you more control over how input is+-- provided during parsing. This lets you e.g. interleave parsing and+-- I\/O.+--+-- The incremental interface consumes a strict 'B.ByteString' at a time, each+-- being part of the total amount of input. If your decoder needs more input to+-- finish it will return a 'Partial' with a continuation.+-- If there is no more input, provide it 'Nothing'.+--+-- 'Fail' will be returned if it runs into an error, together with a message,+-- the position and the remaining input.+-- If it succeeds it will return 'Done' with the resulting value,+-- the position and the remaining input.++-- | A decoder procuced by running a 'Get' monad.+data Decoder e a = Fail !B.ByteString {-# UNPACK #-} !ByteOffset (Either String e)+              -- ^ The decoder ran into an error. The decoder either used+              -- 'fail' or was not provided enough input. Contains any+              -- unconsumed input and the number of bytes consumed.+              | Partial (Maybe B.ByteString -> Decoder e a)+              -- ^ The decoder has consumed the available input and needs+              -- more to continue. Provide 'Just' if more input is available+              -- and 'Nothing' otherwise, and you will get a new 'Decoder'.+              | Done !B.ByteString {-# UNPACK #-} !ByteOffset a+              -- ^ The decoder has successfully finished. Except for the+              -- output value you also get any unused input as well as the+              -- number of bytes consumed.++-- | Run a 'Get' monad. See 'Decoder' for what to do next, like providing+-- input, handling decoder errors and to get the output value.+-- Hint: Use the helper functions 'pushChunk', 'pushChunks' and+-- 'pushEndOfInput'.+runGetIncremental :: ByteOffset -> Get e a -> Decoder e a+runGetIncremental base_offset = calculateOffset base_offset . I.runGetIncremental base_offset++calculateOffset :: ByteOffset -> I.Decoder e a -> Decoder e a+calculateOffset base_offset r0 = go r0 0+  where+  go r !acc = case r of+                I.Done inp a -> Done inp (base_offset + (acc - fromIntegral (B.length inp))) a+                I.Fail inp s -> Fail inp (base_offset + (acc - fromIntegral (B.length inp))) s+                I.Partial k ->+                    Partial $ \ms ->+                      case ms of+                        Nothing -> go (k Nothing) acc+                        Just i -> go (k ms) (acc + fromIntegral (B.length i))+                I.BytesRead unused k ->+                    go (k $! (acc - unused)) acc++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 :: ByteOffset -> Get e a -> L.ByteString+             -> Either (L.ByteString, ByteOffset, Either String e) (L.ByteString, ByteOffset, a)+runGetOrFail ge g lbs0 = feedAll (runGetIncremental ge g) lbs0+  where+  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++-- | Feed a 'Decoder' with more input. If the 'Decoder' is 'Done' or 'Fail' it+-- will add the input to 'B.ByteString' of unconsumed input.+--+-- @+--    'runGetIncremental' myParser \`pushChunk\` myInput1 \`pushChunk\` myInput2+-- @+pushChunk :: Decoder e a -> B.ByteString -> Decoder e a+pushChunk r inp =+  case r of+    Done inp0 p a -> Done (inp0 `B.append` inp) p a+    Partial k -> k (Just inp)+    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.+--+-- @+--    'runGetIncremental' myParser \`pushChunks\` myLazyByteString+-- @+pushChunks :: Decoder e a -> L.ByteString -> Decoder e a+pushChunks r0 = go r0 . L.toChunks+  where+  go r [] = r+  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.+pushEndOfInput :: Decoder e a -> Decoder e a+pushEndOfInput r =+  case r of+    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 = 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 e 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 = 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 e L.ByteString+getRemainingLazyByteString = withInputChunks () consumeAll L.fromChunks resumeOnEOF++------------------------------------------------------------------------+-- Primtives++-- helper, get a raw Ptr onto a strict ByteString copied out of the+-- underlying lazy byteString.++getPtr :: Storable a => Int -> Get () a+getPtr n = readNWith n peek+{-# INLINE getPtr #-}++-- | Read a Word8 from the monad state+getWord8 :: Get () Word8+getWord8 = readN 1 B.unsafeHead+{-# 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+"getWord16be/readN" getWord16be = readN 2 word16be+"getWord16le/readN" getWord16le = readN 2 word16le+"getWord32be/readN" getWord32be = readN 4 word32be+"getWord32le/readN" getWord32le = readN 4 word32le+"getWord64be/readN" getWord64be = readN 8 word64be+"getWord64le/readN" getWord64le = readN 8 word64le #-}++-- | Read a Word16 in big endian format+getWord16be :: Get () Word16+getWord16be = readN 2 word16be++word16be :: B.ByteString -> Word16+word16be = \s ->+        (fromIntegral (s `B.unsafeIndex` 0) `shiftl_w16` 8) .|.+        (fromIntegral (s `B.unsafeIndex` 1))+{-# INLINE[2] getWord16be #-}+{-# INLINE word16be #-}++-- | Read a Word16 in little endian format+getWord16le :: Get () Word16+getWord16le = readN 2 word16le++word16le :: B.ByteString -> Word16+word16le = \s ->+              (fromIntegral (s `B.unsafeIndex` 1) `shiftl_w16` 8) .|.+              (fromIntegral (s `B.unsafeIndex` 0) )+{-# INLINE[2] getWord16le #-}+{-# INLINE word16le #-}++-- | Read a Word32 in big endian format+getWord32be :: Get () Word32+getWord32be = readN 4 word32be++word32be :: B.ByteString -> Word32+word32be = \s ->+              (fromIntegral (s `B.unsafeIndex` 0) `shiftl_w32` 24) .|.+              (fromIntegral (s `B.unsafeIndex` 1) `shiftl_w32` 16) .|.+              (fromIntegral (s `B.unsafeIndex` 2) `shiftl_w32`  8) .|.+              (fromIntegral (s `B.unsafeIndex` 3) )+{-# INLINE[2] getWord32be #-}+{-# INLINE word32be #-}++-- | Read a Word32 in little endian format+getWord32le :: Get () Word32+getWord32le = readN 4 word32le++word32le :: B.ByteString -> Word32+word32le = \s ->+              (fromIntegral (s `B.unsafeIndex` 3) `shiftl_w32` 24) .|.+              (fromIntegral (s `B.unsafeIndex` 2) `shiftl_w32` 16) .|.+              (fromIntegral (s `B.unsafeIndex` 1) `shiftl_w32`  8) .|.+              (fromIntegral (s `B.unsafeIndex` 0) )+{-# INLINE[2] getWord32le #-}+{-# INLINE word32le #-}++-- | Read a Word64 in big endian format+getWord64be :: Get () Word64+getWord64be = readN 8 word64be++word64be :: B.ByteString -> Word64+word64be = \s ->+              (fromIntegral (s `B.unsafeIndex` 0) `shiftl_w64` 56) .|.+              (fromIntegral (s `B.unsafeIndex` 1) `shiftl_w64` 48) .|.+              (fromIntegral (s `B.unsafeIndex` 2) `shiftl_w64` 40) .|.+              (fromIntegral (s `B.unsafeIndex` 3) `shiftl_w64` 32) .|.+              (fromIntegral (s `B.unsafeIndex` 4) `shiftl_w64` 24) .|.+              (fromIntegral (s `B.unsafeIndex` 5) `shiftl_w64` 16) .|.+              (fromIntegral (s `B.unsafeIndex` 6) `shiftl_w64`  8) .|.+              (fromIntegral (s `B.unsafeIndex` 7) )+{-# INLINE[2] getWord64be #-}+{-# INLINE word64be #-}++-- | Read a Word64 in little endian format+getWord64le :: Get () Word64+getWord64le = readN 8 word64le++word64le :: B.ByteString -> Word64+word64le = \s ->+              (fromIntegral (s `B.unsafeIndex` 7) `shiftl_w64` 56) .|.+              (fromIntegral (s `B.unsafeIndex` 6) `shiftl_w64` 48) .|.+              (fromIntegral (s `B.unsafeIndex` 5) `shiftl_w64` 40) .|.+              (fromIntegral (s `B.unsafeIndex` 4) `shiftl_w64` 32) .|.+              (fromIntegral (s `B.unsafeIndex` 3) `shiftl_w64` 24) .|.+              (fromIntegral (s `B.unsafeIndex` 2) `shiftl_w64` 16) .|.+              (fromIntegral (s `B.unsafeIndex` 1) `shiftl_w64`  8) .|.+              (fromIntegral (s `B.unsafeIndex` 0) )+{-# 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++-- | /O(1)./ Read a single native machine word. The word is read 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.+getWordhost :: Get () Word+getWordhost = getPtr (sizeOf (undefined :: Word))+{-# INLINE getWordhost #-}++-- | /O(1)./ Read a 2 byte Word16 in native host order and host endianness.+getWord16host :: Get () Word16+getWord16host = getPtr (sizeOf (undefined :: Word16))+{-# INLINE getWord16host #-}++-- | /O(1)./ Read a Word32 in native host order and host endianness.+getWord32host :: Get () Word32+getWord32host = getPtr  (sizeOf (undefined :: Word32))+{-# INLINE getWord32host #-}++-- | /O(1)./ Read a Word64 in native host order and host endianess.+getWord64host   :: Get () Word64+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+shiftl_w32 :: Word32 -> Int -> Word32+shiftl_w64 :: Word64 -> Int -> Word64++#if defined(__GLASGOW_HASKELL__) && !defined(__HADDOCK__)+shiftl_w16 (W16# w) (I# i) = W16# (w `uncheckedShiftL#`   i)+shiftl_w32 (W32# w) (I# i) = W32# (w `uncheckedShiftL#`   i)++#if WORD_SIZE_IN_BITS < 64+shiftl_w64 (W64# w) (I# i) = W64# (w `uncheckedShiftL64#` i)++#else+shiftl_w64 (W64# w) (I# i) = W64# (w `uncheckedShiftL#` i)+#endif++#else+shiftl_w16 = shiftL+shiftl_w32 = shiftL+shiftl_w64 = shiftL+#endif
+ src/Data/Binary/Get/Ext/Internal.hs view
@@ -0,0 +1,428 @@+{-# LANGUAGE CPP, RankNTypes, MagicHash, BangPatterns, TypeFamilies #-}++-- CPP C style pre-precessing, the #if defined lines+-- RankNTypes forall r. statement+-- MagicHash the (# unboxing #), also needs GHC.primitives++module Data.Binary.Get.Ext.Internal (++    -- * The Get e type+      Get+    , runCont+    , Decoder(..)+    , runGetIncremental++    , readN+    , readNWith++    -- * Parsing+    , bytesRead+    , totalBytesRead+    , isolate++    -- * With input chunks+    , withInputChunks+    , Consume+    , failOnEOF++    , get+    , put+    , ensureN++    -- * Utility+    , isEmpty+    , failG+    , lookAhead+    , lookAheadM+    , lookAheadE+    , label+    , onError+    , withError++    -- ** ByteStrings+    , getByteString++    ) where++import Foreign+import qualified Data.ByteString as B+import qualified Data.ByteString.Unsafe as B++import Control.Applicative+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,+-- and it should not terminate the decoding (empty would mean eof).+-- However, I'd say that it's also a risk that you get stuck in a loop,+-- where you keep providing an empty string. Anyway, no new input should be+-- rare, as the RTS should only wake you up if you actually have some data+-- to read from your fd.++-- | A decoder produced by running a 'Get' monad.+data Decoder e a = Fail !B.ByteString (Either String e)+              -- ^ The decoder ran into an error. The decoder either used+              -- 'fail' or was not provided enough input.+              | Partial (Maybe B.ByteString -> Decoder e a)+              -- ^ The decoder has consumed the available input and needs+              -- more to continue. Provide 'Just' if more input is available+              -- and 'Nothing' otherwise, and you will get a new 'Decoder'.+              | Done !B.ByteString a+              -- ^ The decoder has successfully finished. Except for the+              -- output value you also get the unused input.+              | BytesRead {-# UNPACK #-} !Int64 (Int64 -> Decoder e a)+              -- ^ The decoder needs to know the current position in the input.+              -- Given the number of bytes remaning in the decoder, the outer+              -- decoder runner needs to calculate the position and+              -- resume the decoding.++-- unrolled codensity/state monad+newtype Get e a = C { runCont :: forall r. Int64 -> B.ByteString -> Success e a r -> Decoder e r }++type Success e a r = B.ByteString -> a -> Decoder e r++instance Monad (Get e) where+  return = pure+  (>>=) = bindG+#if MIN_VERSION_base(4,9,0)+  fail = Fail.fail++instance Fail.MonadFail (Get e) where+#endif+  fail = failG_++bindG :: Get e a -> (a -> Get e b) -> Get e b+bindG (C c) f = C $ \ge i ks -> c ge i (\i' a -> (runCont (f a)) ge i' ks)+{-# INLINE bindG #-}++failG_ :: String -> Get e a+failG_ str = C $ \_ i _ks -> Fail i $ Left str++failG :: e -> Get e a+failG err = C $ \_ i _ks -> Fail i $ Right err++apG :: Get e (a -> b) -> Get e a -> Get e b+apG d e = do+  b <- d+  a <- e+  return (b a)+{-# INLINE [0] apG #-}++fmapG :: (a -> b) -> Get e a -> Get e b+fmapG f m = C $ \ge i ks -> runCont m ge i (\i' a -> ks i' (f a))+{-# INLINE fmapG #-}++instance Applicative (Get e) where+  pure = \x -> C $ \_ s ks -> ks s x+  {-# INLINE [0] pure #-}+  (<*>) = apG+  {-# INLINE (<*>) #-}++instance MonadPlus (Get e) where+  mzero = empty+  mplus = (<|>)++instance Functor (Get e) where+  fmap = fmapG++instance Functor (Decoder e) where+  fmap f (Done s a) = Done s (f a)+  fmap f (Partial k) = Partial (fmap f . k)+  fmap _ (Fail s err) = Fail s err+  fmap f (BytesRead b k) = BytesRead b (fmap f . k)++instance (Show e, Show a) => Show (Decoder e a) where+  show (Fail _ err) = "Fail: " ++ show err+  show (Partial _) = "Partial _"+  show (Done _ a) = "Done: " ++ show a+  show (BytesRead _ _) = "BytesRead"++-- | Run a 'Get' monad. See 'Decoder' for what to do next, like providing+-- input, handling decoding errors and to get the output value.+runGetIncremental :: Int64 -> Get e a -> Decoder e a+runGetIncremental ge g = noMeansNo $+  runCont g ge B.empty (\i a -> Done i a)++-- | Make sure we don't have to pass Nothing to a Partial twice.+-- This way we don't need to pass around an EOF value in the Get e monad, it+-- can safely ask several times if it needs to.+noMeansNo :: Decoder e a -> Decoder e a+noMeansNo r0 = go r0+  where+  go r =+    case r of+      Partial k -> Partial $ \ms ->+                    case ms of+                      Just _ -> go (k ms)+                      Nothing -> neverAgain (k ms)+      BytesRead n k -> BytesRead n (go . k)+      Done _ _ -> r+      Fail _ _ -> r+  neverAgain r =+    case r of+      Partial k -> neverAgain (k Nothing)+      BytesRead n k -> BytesRead n (neverAgain . k)+      Fail _ _ -> r+      Done _ _ -> r++prompt :: B.ByteString -> Decoder e a -> (B.ByteString -> Decoder e a) -> Decoder e a+prompt inp kf ks = prompt' kf (\inp' -> ks (inp `B.append` inp'))++prompt' :: Decoder e a -> (B.ByteString -> Decoder e a) -> Decoder e a+prompt' kf ks =+  let loop =+        Partial $ \sm ->+          case sm of+            Just s | B.null s -> loop+                   | otherwise -> ks s+            Nothing -> kf+  in loop+  +getBaseOffset :: Get e Int64+getBaseOffset = C $ \ge s ks -> ks s ge++-- | Get e the total number of bytes read to this point.+totalBytesRead :: Get e Int64+totalBytesRead = do+  base_offset <- getBaseOffset+  offset <- bytesRead+  return $ base_offset + offset++-- | Get e the total number of bytes read to this point.+bytesRead :: Get e Int64+bytesRead = C $ \_ inp k -> BytesRead (fromIntegral $ B.length inp) (k 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.+isolate :: Int   -- ^ The number of bytes that must be consumed+        -> Get e a -- ^ The decoder to isolate+        -> (Int -> e) -- ^ The error if fewer bytes were consumed+        -> Get e a+isolate n0 act err+  | n0 < 0 = fail "isolate: negative size"+  | otherwise = do+    ge <- getBaseOffset+    offset <- bytesRead+    go n0 (runCont act (ge + offset) 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+        failG $ err consumed+  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 (Right ferr)) = pushFront bs >> failG ferr+  go _ (Fail bs (Left ferr)) = pushFront bs >> failG_ ferr+  go n (BytesRead r resume) =+    go n (resume $! fromIntegral n0 - fromIntegral n - r)++type Consume s = s -> B.ByteString -> Either s (B.ByteString, B.ByteString)++withInputChunks :: s -> Consume s -> ([B.ByteString] -> b) -> ([B.ByteString] -> Get e b) -> Get e b+withInputChunks initS consume onSucc onFail = go initS []+  where+  go state acc = C $ \ge inp ks ->+    case consume state inp of+      Left state' -> do+        let acc' = inp : acc+        prompt'+          (runCont (onFail (reverse acc')) ge B.empty ks)+          (\str' -> runCont (go state' acc') ge str' ks)+      Right (want,rest) -> do+        ks rest (onSucc (reverse (want:acc)))++failOnEOF :: [B.ByteString] -> Get () a+failOnEOF bs = C $ \_ _ _ -> Fail (B.concat bs) $ Right ()++-- | Test whether all input has been consumed, i.e. there are no remaining+-- undecoded bytes.+isEmpty :: Get e Bool+isEmpty = C $ \_ inp ks ->+    if B.null inp+      then prompt inp (ks inp True) (\inp' -> ks inp' False)+      else ks inp False++instance Alternative (Get e) where+  empty = C $ \_ inp _ks -> Fail inp $ Left "Data.Binary.Get(Alternative).empty"+  {-# 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 #-}++-- | 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 e a -> Get e (Decoder e a, [B.ByteString])+runAndKeepTrack g = C $ \ge inp ks ->+  let r0 = runCont g ge inp (\inp' a -> Done inp' a)+      go !acc r = case r of+                    Done inp' a -> ks inp (Done inp' a, reverse acc)+                    Partial k -> Partial $ \minp -> go (maybe acc (:acc) minp) (k minp)+                    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 e ()+pushBack [] = C $ \_  inp ks -> ks inp ()+pushBack bs = C $ \_  inp ks -> ks (B.concat (inp : bs)) ()+{-# INLINE pushBack #-}++pushFront :: B.ByteString -> Get e ()+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 e a -> Get e 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 e (Maybe a) -> Get e (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.+lookAheadE :: Get e (Either a b) -> Get e (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.+label :: String -> Get String a -> Get String a+label msg = onError (\x -> x ++ "\n" ++ msg)++-- | Convert decoder error. If the decoder fails, the given function will be applied+-- to the error message.+onError :: (e -> e') -> Get e a -> Get e' a+onError msg decoder = C $ \ge inp ks ->+  let r0 = runCont decoder ge inp (\inp' a -> Done inp' a)+      go r = case r of+                 Done inp' a -> ks inp' a+                 Partial k -> Partial (go . k)+                 Fail inp' (Left s) -> Fail inp' $ Left s+                 Fail inp' (Right s) -> Fail inp' $ Right $ msg s+                 BytesRead u k -> BytesRead u (go . k)+  in go r0+  +-- | Set decoder error. If the decoder fails, the given error will be used+-- as the error message.+withError :: Get () a -> e -> Get e a+withError decoder msg = onError (const msg) decoder++------------------------------------------------------------------------+-- ByteStrings+--++-- | An efficient get method for strict ByteStrings. Fails if fewer than @n@+-- bytes are left in the input. If @n <= 0@ then the empty string is returned.+getByteString :: Int -> Get () B.ByteString+getByteString n | n > 0 = readN n (B.unsafeTake n)+                | otherwise = return B.empty+{-# INLINE getByteString #-}++-- | Get e the current chunk.+get :: Get e B.ByteString+get = C $ \_ inp ks -> ks inp inp++-- | Replace the current chunk.+put :: B.ByteString -> Get e ()+put s = C $ \_ _inp ks -> ks s ()++-- | Return at least @n@ bytes, maybe more. If not enough data is available+-- the computation will escape with 'Partial'.+readN :: Int -> (B.ByteString -> a) -> Get () a+readN !n f = ensureN n >> unsafeReadN n f+{-# INLINE [0] readN #-}++{-# RULES++"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)) #-}++-- | Ensure that there are at least @n@ bytes available. If not, the+-- computation will escape with 'Partial'.+ensureN :: Int -> Get () ()+ensureN !n0 = C $ \ge inp ks -> do+  if B.length inp >= n0+    then ks inp ()+    else runCont (withInputChunks n0 enoughChunks onSucc onFail >>= put) ge 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+    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) $ Right ()+{-# 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+    -- 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
+ tests/Action.hs view
@@ -0,0 +1,425 @@+{-# LANGUAGE PatternGuards #-}+module Action where++import Control.Applicative+import Control.Monad+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import Data.Char+import Data.Int+import Data.List (intersperse, nub)++import Test.Framework+import Test.Framework.Providers.QuickCheck2+import Test.QuickCheck++import Arbitrary ()+import qualified Data.Binary.Get.Ext as Binary++tests :: [Test]+tests = [ testProperty "action" prop_action+        , testProperty "label" prop_label+        , testProperty "fail" prop_fail ]++data Action+  = 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+      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 ->+        [Actions a | not (willFail' a)]+        ++ [ Try a' b' | (a',b') <- shrink (a,b) ]++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+    Actions xs' -> max_len (xs' ++ xs)+    BytesRead -> max_len xs+    Fail -> 0+    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)++-- | 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 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 0 (execute allInput actions) lbs of+        Right (_inp, _off, _x) -> True+        Left (_inp, _off, _msg) -> True++-- | 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 0 (execute allInput actions) lbs of+        Left (_, _, Left e) -> error $ "Internal error " ++ e+        Left (_inp, _off, Right 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 0 (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)++getLazyByteString :: Int64 -> Binary.Get String L.ByteString+getLazyByteString = (`Binary.withError` "not enough bytes") . Binary.getLazyByteString++skip :: Int -> Binary.Get String ()+skip = (`Binary.withError` "not enough bytes") . Binary.skip++isolate :: Int -> Binary.Get String a -> Binary.Get String a+isolate n decoder =+  Binary.isolate n decoder (msg n)+  where+    msg n0 consumed+      =  "isolate: the decoder consumed " ++ show consumed ++ " bytes"+      ++ " which is less than the expected " ++ show n0 ++ " bytes"++getByteString :: Int -> Binary.Get String B.ByteString+getByteString = (`Binary.withError` "not enough bytes") . Binary.getByteString++-- | 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 String ()+execute inp acts0 = go 0 acts0 >> return ()+  where+  inp_len = B.length inp+  go _ [] = return ()+  go pos (x:xs) =+    case x of+      Actions a -> go pos (a++xs)+      GetByteString n -> do+        -- Run the operation in the Get monad...+        actual <- 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 <$> 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+        skip n+        go (pos+n) xs+      BytesRead -> do+        pos' <- Binary.bytesRead+        if pos == fromIntegral pos'+          then go pos xs+          else error $ "execute(bytesRead): expected " +++            show pos ++ " but got " ++ show pos'+      Fail -> Binary.failG "fail"+      Isolate n as -> do+        let str = B.take n (B.drop pos inp)+        _ <- 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 -> go pos xs+          Just offset -> go (pos+offset) xs+      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 :: 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)+                       , 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
@@ -0,0 +1,23 @@+{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Arbitrary where++import Test.QuickCheck++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 = fmap L.fromChunks arbitrary++instance Arbitrary B.ByteString where+  arbitrary = B.pack `fmap` arbitrary++#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/QC.hs view
@@ -0,0 +1,739 @@+{-# LANGUAGE CPP, ScopedTypeVariables #-}+module Main ( main ) where++#if MIN_VERSION_base(4,8,0)+#define HAS_NATURAL+#endif++#if MIN_VERSION_base(4,7,0)+#define HAS_FIXED_CONSTRUCTOR+#endif++import Control.Applicative+import Control.Exception as C (SomeException, catch, evaluate)+import Control.Monad (unless)+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Lazy.Internal as L+import Data.Int+import Data.Ratio+import System.IO.Unsafe++#ifdef HAS_NATURAL+import Numeric.Natural+#endif++import Test.Framework+import Test.Framework.Providers.QuickCheck2+import Test.QuickCheck++import qualified Action (tests)+import Arbitrary ()+import Data.Binary hiding (Get, get, getWord8)+import Data.Binary.Get.Ext hiding+  ( getLazyByteString, getLazyByteStringNul, getWord8+  , getWord16le, getWord16be, getWord16host+  , getWord32le, getWord32be, getWord32host+  , getWord64le, getWord64be, getWord64host+  , getWordhost, getByteString+  , getInt8+  , getInt16be, getInt16le, getInt16host+  , getInt32be, getInt32le, getInt32host+  , getInt64be, getInt64le, getInt64host+  , getInthost+  )+import qualified Data.Binary.Get.Ext as Binary+  ( getLazyByteString, getLazyByteStringNul, getWord8+  , getWord16le, getWord16be, getWord16host+  , getWord32le, getWord32be, getWord32host+  , getWord64le, getWord64be, getWord64host+  , getWordhost, getByteString+  , getInt8+  , getInt16be, getInt16le, getInt16host+  , getInt32be, getInt32le, getInt32host+  , getInt64be, getInt64le, getInt64host+  , getInthost+  )+import Data.Binary.Put++------------------------------------------------------------------------++roundTrip :: (Eq a, Binary a) => a -> (L.ByteString -> L.ByteString) -> Bool+roundTrip a f = a ==+    {-# SCC "decode.refragment.encode" #-} decode (f (encode a))++runGet :: Get String a -> L.ByteString -> a+runGet decoder inp =+  case runGetOrFail 0 decoder inp of+    Right (_, _, a) -> a+    Left (_, pos, msg) -> error $ "Data.Binary.Get.runGet at position " ++ show pos ++ ": " ++ show msg++withStringError :: Get () a -> Get String a+withStringError = (`withError` "not enough bytes")++getByteString :: Int -> Get String B.ByteString+getByteString = withStringError . Binary.getByteString++getWord8 :: Get String Word8+getWord8 = withStringError Binary.getWord8++getWord16be :: Get String Word16+getWord16be = withStringError Binary.getWord16be++getWord16le :: Get String Word16+getWord16le = withStringError Binary.getWord16le++getWord16host :: Get String Word16+getWord16host = withStringError Binary.getWord16host++getWord32be :: Get String Word32+getWord32be = withStringError Binary.getWord32be++getWord32le :: Get String Word32+getWord32le = withStringError Binary.getWord32le++getWord32host :: Get String Word32+getWord32host = withStringError Binary.getWord32host++getWord64be :: Get String Word64+getWord64be = withStringError Binary.getWord64be++getWord64le :: Get String Word64+getWord64le = withStringError Binary.getWord64le++getWord64host :: Get String Word64+getWord64host = withStringError Binary.getWord64host++getWordhost :: Get String Word+getWordhost = withStringError Binary.getWordhost++getInt8 :: Get String Int8+getInt8 = withStringError Binary.getInt8++getInt16be :: Get String Int16+getInt16be = withStringError Binary.getInt16be++getInt16le :: Get String Int16+getInt16le = withStringError Binary.getInt16le++getInt16host :: Get String Int16+getInt16host = withStringError Binary.getInt16host++getInt32be :: Get String Int32+getInt32be = withStringError Binary.getInt32be++getInt32le :: Get String Int32+getInt32le = withStringError Binary.getInt32le++getInt32host :: Get String Int32+getInt32host = withStringError Binary.getInt32host++getInt64be :: Get String Int64+getInt64be = withStringError Binary.getInt64be++getInt64le :: Get String Int64+getInt64le = withStringError Binary.getInt64le++getInt64host :: Get String Int64+getInt64host = withStringError Binary.getInt64host++getInthost :: Get String Int+getInthost = withStringError Binary.getInthost++roundTripWith ::  Eq a => (a -> Put) -> Get String a -> a -> Property+roundTripWith putter getter x =+    forAll positiveList $ \xs ->+    x == runGet getter (refragment xs (runPut (putter x)))++-- make sure that a test fails+mustThrowError :: B a+mustThrowError a = unsafePerformIO $+    C.catch (do _ <- C.evaluate a+                return False)+            (\(_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++prop_Word16le :: Word16 -> Property+prop_Word16le = roundTripWith putWord16le getWord16le++prop_Word16host :: Word16 -> Property+prop_Word16host = roundTripWith putWord16host getWord16host++prop_Word32be :: Word32 -> Property+prop_Word32be = roundTripWith putWord32be getWord32be++prop_Word32le :: Word32 -> Property+prop_Word32le = roundTripWith putWord32le getWord32le++prop_Word32host :: Word32 -> Property+prop_Word32host = roundTripWith putWord32host getWord32host++prop_Word64be :: Word64 -> Property+prop_Word64be = roundTripWith putWord64be getWord64be++prop_Word64le :: Word64 -> Property+prop_Word64le = roundTripWith putWord64le getWord64le++prop_Word64host :: Word64 -> Property+prop_Word64host = roundTripWith putWord64host getWord64host++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+-}++-- done, partial and fail++-- | Test partial results.+-- May or may not use the whole input, check conditions for the different+-- outcomes.+prop_partial :: L.ByteString -> Property+prop_partial lbs = forAll (choose (0, L.length lbs * 2)) $ \skipN ->+  let result = pushChunks (runGetIncremental 0 decoder) lbs+      decoder = do+        s <- getByteString (fromIntegral skipN)+        return (L.fromChunks [s])+  in case result of+       Partial _ -> L.length lbs < skipN+       Done unused _pos value ->+         and [ L.length value == skipN+             , L.append value (L.fromChunks [unused]) == lbs+             ]+       Fail _ _ _ -> False++-- | Fail a decoder and make sure the result is sane.+prop_fail :: L.ByteString -> String -> Property+prop_fail lbs msg = forAll (choose (0, L.length lbs)) $ \pos ->+  let result = pushChunks (runGetIncremental 0 decoder) lbs+      decoder = do+        -- use part of the input...+        _ <- getByteString (fromIntegral pos)+        -- ... then fail+        failG msg+  in case result of+     Fail unused pos' msg' ->+       and [ pos == pos'+           , Right msg == msg'+           , L.length lbs - pos == fromIntegral (B.length unused)+           , L.fromChunks [unused] `L.isSuffixOf` lbs+           ]+     _ -> False -- wuut?++-- read negative length+prop_getByteString_negative :: Int -> Property+prop_getByteString_negative n =+  n < 1 ==>+    runGet (getByteString n) L.empty == B.empty+++prop_bytesRead :: L.ByteString -> Property+prop_bytesRead lbs =+  forAll (makeChunks 0 totalLength) $ \chunkSizes ->+  let result = pushChunks (runGetIncremental 0 decoder) lbs+      decoder = do+        -- Read some data and invoke bytesRead several times.+        -- Each time, check that the values are what we expect.+        flip mapM_ chunkSizes $ \(total, step) -> do+          _ <- getByteString (fromIntegral step)+          n <- bytesRead+          unless (n == total) $ fail "unexpected position"+        bytesRead+  in case result of+       Done unused pos value ->+         and [ value == totalLength+             , pos == value+             , B.null unused+             ]+       Partial _ -> False+       Fail _ _ _ -> False+  where+    totalLength = L.length lbs+    makeChunks total i+      | i == 0 = return []+      | otherwise = do+          n <- choose (0,i)+          let total' = total + n+          rest <- makeChunks total' (i - n)+          return ((total',n):rest)+++-- | We're trying to guarantee that the Decoder will not ask for more input+-- with Partial if it has been given Nothing once.+-- In this test we're making the decoder return 'Partial' to get more+-- input, and to get knownledge of the current position using 'BytesRead'.+-- Both of these operations, when used with the <|> operator, result internally+-- in that the decoder return with Partial and BytesRead multiple times,+-- in which case we need to keep track of if the user has passed Nothing to a+-- Partial in the past.+prop_partialOnlyOnce :: Property+prop_partialOnlyOnce = property $+  let result = runGetIncremental 0 (decoder <|> decoder)+      decoder = do+        0 <- bytesRead+        _ <- getWord8 -- this will make the decoder return with Partial+        return "shouldn't get here"+  in case result of+       -- we expect Partial followed by Fail+       Partial k -> case k Nothing of -- push down a Nothing+                      Fail _ _ _ -> True+                      Partial _ -> error $ "partial twice! oh noes!"+                      Done _ _ _ -> error $ "we're not supposed to be done."+       _ -> error $ "not partial, error!"++-- read too much+prop_readTooMuch :: (Eq a, Binary a) => a -> Bool+prop_readTooMuch x = mustThrowError $ x == a && x /= b+  where+    -- encode 'a', but try to read 'b' too+    (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++getLazyByteString :: Int64 -> Get String L.ByteString+getLazyByteString = (`withError` "not enough bytes") . Binary.getLazyByteString++getLazyByteStringNul :: Get String L.ByteString+getLazyByteStringNul = Binary.getLazyByteStringNul `withError` "not enough bytes"++prop_getLazyByteString :: L.ByteString -> Property+prop_getLazyByteString lbs = forAll (choose (0, 2 * L.length lbs)) $ \len ->+  let result = pushChunks (runGetIncremental 0 decoder) lbs+      decoder = getLazyByteString len+  in case result of+       Done unused _pos value ->+         and [ value == L.take len lbs+             , L.fromChunks [unused] == L.drop len lbs+             ]+       Partial _ -> len > L.length lbs+       _ -> False++prop_getLazyByteStringNul :: Word16 -> [Int] -> Property+prop_getLazyByteStringNul count0 fragments = count >= 0 ==>+  forAll (choose (0, count)) $ \pos ->+  let lbs = case L.splitAt pos (L.replicate count 65) of+              (start,end) -> refragment fragments $ L.concat [start, L.singleton 0, end]+      result = pushEndOfInput $ pushChunks (runGetIncremental 0 getLazyByteStringNul) lbs+  in case result of+       Done unused pos' value ->+         and [ value == L.take pos lbs+             , pos + 1 == pos' -- 1 for the NUL+             , L.fromChunks [unused] == L.drop (pos + 1) lbs+             ]+       _ -> False+  where+  count = fromIntegral count0 -- to make the generated numbers a bit smaller++-- | Same as prop_getLazyByteStringNul, but without any NULL in the string.+prop_getLazyByteStringNul_noNul :: Word16 -> [Int] -> Property+prop_getLazyByteStringNul_noNul count0 fragments = count >= 0 ==>+  let lbs = refragment fragments $ L.replicate count 65+      result = pushEndOfInput $ pushChunks (runGetIncremental 0 getLazyByteStringNul) lbs+  in case result of+       Fail _ _ _ -> True+       _ -> False+  where+  count = fromIntegral count0 -- to make the generated numbers a bit smaller++prop_getRemainingLazyByteString :: L.ByteString -> Property+prop_getRemainingLazyByteString lbs = property $+  let result = pushEndOfInput $ pushChunks (runGetIncremental 0 getRemainingLazyByteString) lbs+  in case result of+    Done unused pos value ->+      and [ value == lbs+          , B.null unused+          , fromIntegral pos == L.length lbs+          ]+    _ -> False++-- sanity:++invariant_lbs :: L.ByteString -> Bool+invariant_lbs (L.Empty)      = True+invariant_lbs (L.Chunk x xs) = not (B.null x) && invariant_lbs xs++prop_invariant :: (Binary a) => a -> Bool+prop_invariant = invariant_lbs . encode++-- refragment a lazy bytestring's chunks+refragment :: [Int] -> L.ByteString -> L.ByteString+refragment [] lbs = lbs+refragment (x:xs) lbs =+    let x' = fromIntegral . (+1) . abs $ x+        rest = refragment xs (L.drop x' lbs) in+    L.append (L.fromChunks [B.concat . L.toChunks . L.take x' $ lbs]) rest++-- check identity of refragmentation+prop_refragment :: L.ByteString -> [Int] -> Bool+prop_refragment lbs xs = lbs == refragment xs lbs++-- check that refragmention still hold invariant+prop_refragment_inv :: L.ByteString -> [Int] -> Bool+prop_refragment_inv lbs xs = invariant_lbs $ refragment xs lbs++main :: IO ()+main = defaultMain tests++------------------------------------------------------------------------++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++------------------------------------------------------------------------++#if !MIN_VERSION_base(4,7,0)+instance Show Fingerprint where+  show (Fingerprint x1 x2) = show (x1,x2)+#endif++type T a = a -> Property+type B a = a -> Bool++p :: (Testable p) => p -> Property+p = property++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++tests :: [Test]+tests =+        [ testGroup "Utils"+            [ testProperty "refragment id" (p prop_refragment)+            , testProperty "refragment invariant" (p prop_refragment_inv)+            ]++        , 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"+            [ testProperty "partial" (p prop_partial)+            , testProperty "fail"    (p prop_fail)+            , testProperty "bytesRead" (p prop_bytesRead)+            , testProperty "partial only once" (p prop_partialOnlyOnce)+            ]++        , testGroup "Model"+            Action.tests++        , testGroup "Primitives"+            [ 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)+            , testProperty "Word32le"   (p prop_Word32le)+            , testProperty "Word32host" (p prop_Word32host)+            , testProperty "Word64be"   (p prop_Word64be)+            , 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 No Null" prop_getLazyByteStringNul_noNul+            , testProperty "getRemainingLazyByteString" prop_getRemainingLazyByteString+            ]++        , 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++            , 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++            , 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++            , testWithGen "Integer mixed" genInteger+            , testWithGen "Integer small" genIntegerSmall+            , testWithGen "Integer big"   genIntegerBig++#ifdef HAS_NATURAL+            , testWithGen "Natural mixed" genNatural+            , testWithGen "Natural small" genNaturalSmall+            , testWithGen "Natural big"   genNaturalBig+#endif++            , test' "Float"       (test :: T Float ) test+            , test' "Double"      (test :: T Double) test++            , 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++            , test' "(Int, ByteString)"+                    (test     :: T (Int, B.ByteString)   ) test+            , test' "[(Int, ByteString)]"+                    (test     :: T [(Int, B.ByteString)] ) test++            , 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++{-+            , 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+-}++            , test' "B.ByteString" (test :: T B.ByteString) test+            , test' "L.ByteString" (test :: T L.ByteString) test+            ]++        , testGroup "Invariants" $ map (uncurry testProperty)+            [ ("B.ByteString invariant",   p (prop_invariant :: B B.ByteString                 ))+            , ("[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]               ))+            ]+        ]