persist 0.1.1.5 → 1.0.0.0
raw patch · 5 files changed
+1090/−600 lines, 5 filesdep ~QuickCheckdep ~basedep ~bytestringnew-uploaderPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: QuickCheck, base, bytestring, containers, test-framework, test-framework-quickcheck2, text
API changes (from Hackage documentation)
- Data.Persist: instance (Data.Persist.SumArity (a GHC.Generics.:+: b) GHC.TypeNats.<= 255, Data.Persist.GPersistGetSum 0 (a GHC.Generics.:+: b)) => Data.Persist.GPersistGet (a GHC.Generics.:+: b)
- Data.Persist: instance (Data.Persist.SumArity (a GHC.Generics.:+: b) GHC.TypeNats.<= 255, Data.Persist.GPersistPutSum 0 (a GHC.Generics.:+: b)) => Data.Persist.GPersistPut (a GHC.Generics.:+: b)
- Data.Persist: instance Data.Persist.Persist (Data.Persist.BigEndian GHC.Int.Int16)
- Data.Persist: instance Data.Persist.Persist (Data.Persist.BigEndian GHC.Int.Int32)
- Data.Persist: instance Data.Persist.Persist (Data.Persist.BigEndian GHC.Int.Int64)
- Data.Persist: instance Data.Persist.Persist (Data.Persist.BigEndian GHC.Types.Double)
- Data.Persist: instance Data.Persist.Persist (Data.Persist.BigEndian GHC.Types.Float)
- Data.Persist: instance Data.Persist.Persist (Data.Persist.BigEndian GHC.Types.Int)
- Data.Persist: instance Data.Persist.Persist (Data.Persist.BigEndian GHC.Types.Word)
- Data.Persist: instance Data.Persist.Persist (Data.Persist.BigEndian GHC.Word.Word16)
- Data.Persist: instance Data.Persist.Persist (Data.Persist.BigEndian GHC.Word.Word32)
- Data.Persist: instance Data.Persist.Persist (Data.Persist.BigEndian GHC.Word.Word64)
- Data.Persist: instance Data.Persist.Persist (Data.Persist.LittleEndian GHC.Int.Int16)
- Data.Persist: instance Data.Persist.Persist (Data.Persist.LittleEndian GHC.Int.Int32)
- Data.Persist: instance Data.Persist.Persist (Data.Persist.LittleEndian GHC.Int.Int64)
- Data.Persist: instance Data.Persist.Persist (Data.Persist.LittleEndian GHC.Types.Double)
- Data.Persist: instance Data.Persist.Persist (Data.Persist.LittleEndian GHC.Types.Float)
- Data.Persist: instance Data.Persist.Persist (Data.Persist.LittleEndian GHC.Types.Int)
- Data.Persist: instance Data.Persist.Persist (Data.Persist.LittleEndian GHC.Types.Word)
- Data.Persist: instance Data.Persist.Persist (Data.Persist.LittleEndian GHC.Word.Word16)
- Data.Persist: instance Data.Persist.Persist (Data.Persist.LittleEndian GHC.Word.Word32)
- Data.Persist: instance Data.Persist.Persist (Data.Persist.LittleEndian GHC.Word.Word64)
- Data.Persist: instance Data.Persist.Persist Data.ByteString.Internal.ByteString
- Data.Persist: instance Data.Persist.Persist GHC.Integer.Type.Integer
- Data.Persist: instance Data.Persist.Persist GHC.Natural.Natural
- Data.Persist.Internal: [chkBegin] :: Chunk -> !Ptr Word8
- Data.Persist.Internal: [chkEnd] :: Chunk -> !Ptr Word8
- Data.Persist.Internal: [geBegin] :: GetEnv -> !Ptr Word8
- Data.Persist.Internal: [geBuf] :: GetEnv -> !ForeignPtr Word8
- Data.Persist.Internal: [geEnd] :: GetEnv -> !Ptr Word8
- Data.Persist.Internal: [geTmp] :: GetEnv -> !Ptr Word8
- Data.Persist.Internal: [peChks] :: PutEnv -> !IORef (NonEmpty Chunk)
- Data.Persist.Internal: [peEnd] :: PutEnv -> !IORef (Ptr Word8)
- Data.Persist.Internal: [peTmp] :: PutEnv -> !Ptr Word8
- Data.Persist.Internal: evalPutIO :: Put a -> IO (a, ByteString)
+ Data.Persist: ($dmget) :: (Persist t, Generic t, GPersistGet (Rep t)) => Get t
+ Data.Persist: ($dmput) :: (Persist t, Generic t, GPersistPut (Rep t)) => t -> Put ()
+ Data.Persist: MkViaReinterpretAs :: a -> ViaReinterpretAs a
+ Data.Persist: MkViaSerializeAs :: a -> ViaSerializeAs a
+ Data.Persist: castGet :: SerializeAs a => SerializeTarget a -> a
+ Data.Persist: castPut :: SerializeAs a => a -> SerializeTarget a
+ Data.Persist: class HasEndianness a
+ Data.Persist: class ReinterpretAs a where {
+ Data.Persist: class SerializeAs a where {
+ Data.Persist: encodeLazy :: Persist a => a -> ByteString
+ Data.Persist: endiannessSize :: HasEndianness a => Int
+ Data.Persist: evalPutLazy :: Put a -> (a, ByteString)
+ Data.Persist: getPrefix :: Int -> Get a -> Get a
+ Data.Persist: instance (Data.Persist.FitsInByte (Data.Persist.SumArity (a GHC.Generics.:+: b)), Data.Persist.GPersistGetSum 0 (a GHC.Generics.:+: b)) => Data.Persist.GPersistGet (a GHC.Generics.:+: b)
+ Data.Persist: instance (Data.Persist.FitsInByte (Data.Persist.SumArity (a GHC.Generics.:+: b)), Data.Persist.GPersistPutSum 0 (a GHC.Generics.:+: b)) => Data.Persist.GPersistPut (a GHC.Generics.:+: b)
+ Data.Persist: instance (Data.Persist.ReinterpretAs a, Foreign.Storable.Storable a, Foreign.Storable.Storable b, Data.Persist.HasEndianness b, b GHC.Types.~ Data.Persist.ReinterpretTarget a) => Data.Persist.HasEndianness (Data.Persist.ViaReinterpretAs a)
+ Data.Persist: instance (Data.Persist.SerializeAs a, Data.Persist.HasEndianness (Data.Persist.SerializeTarget a)) => Data.Persist.HasEndianness (Data.Persist.ViaSerializeAs a)
+ Data.Persist: instance (Data.Persist.SerializeAs a, Data.Persist.Persist (Data.Persist.SerializeTarget a)) => Data.Persist.Persist (Data.Persist.ViaSerializeAs a)
+ Data.Persist: instance Data.Persist.HasEndianness GHC.Int.Int16
+ Data.Persist: instance Data.Persist.HasEndianness GHC.Int.Int32
+ Data.Persist: instance Data.Persist.HasEndianness GHC.Int.Int64
+ Data.Persist: instance Data.Persist.HasEndianness GHC.Int.Int8
+ Data.Persist: instance Data.Persist.HasEndianness GHC.Types.Double
+ Data.Persist: instance Data.Persist.HasEndianness GHC.Types.Float
+ Data.Persist: instance Data.Persist.HasEndianness GHC.Types.Int
+ Data.Persist: instance Data.Persist.HasEndianness GHC.Types.Word
+ Data.Persist: instance Data.Persist.HasEndianness GHC.Word.Word16
+ Data.Persist: instance Data.Persist.HasEndianness GHC.Word.Word32
+ Data.Persist: instance Data.Persist.HasEndianness GHC.Word.Word64
+ Data.Persist: instance Data.Persist.HasEndianness GHC.Word.Word8
+ Data.Persist: instance Data.Persist.HasEndianness a => Data.Persist.Persist (Data.Persist.BigEndian a)
+ Data.Persist: instance Data.Persist.HasEndianness a => Data.Persist.Persist (Data.Persist.LittleEndian a)
+ Data.Persist: instance Data.Persist.Persist Data.ByteString.Internal.Type.ByteString
+ Data.Persist: instance Data.Persist.Persist GHC.Num.Integer.Integer
+ Data.Persist: instance Data.Persist.Persist GHC.Num.Natural.Natural
+ Data.Persist: instance Data.Persist.ReinterpretAs GHC.Types.Double
+ Data.Persist: instance Data.Persist.ReinterpretAs GHC.Types.Float
+ Data.Persist: instance Data.Persist.SerializeAs GHC.Int.Int16
+ Data.Persist: instance Data.Persist.SerializeAs GHC.Int.Int32
+ Data.Persist: instance Data.Persist.SerializeAs GHC.Int.Int64
+ Data.Persist: instance Data.Persist.SerializeAs GHC.Int.Int8
+ Data.Persist: instance Data.Persist.SerializeAs GHC.Types.Int
+ Data.Persist: instance Data.Persist.SerializeAs GHC.Types.Word
+ Data.Persist: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.Persist.ViaReinterpretAs a)
+ Data.Persist: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.Persist.ViaSerializeAs a)
+ Data.Persist: instance GHC.Classes.Ord a => GHC.Classes.Ord (Data.Persist.ViaReinterpretAs a)
+ Data.Persist: instance GHC.Classes.Ord a => GHC.Classes.Ord (Data.Persist.ViaSerializeAs a)
+ Data.Persist: instance GHC.Show.Show a => GHC.Show.Show (Data.Persist.ViaReinterpretAs a)
+ Data.Persist: instance GHC.Show.Show a => GHC.Show.Show (Data.Persist.ViaSerializeAs a)
+ Data.Persist: newtype ViaReinterpretAs a
+ Data.Persist: newtype ViaSerializeAs a
+ Data.Persist: reserveSize :: HasEndianness a => Put (PutSize a)
+ Data.Persist: resolveSizeBE :: (Integral a, HasEndianness a) => PutSize a -> a -> Put ()
+ Data.Persist: resolveSizeExclusiveBE :: (Integral a, HasEndianness a) => PutSize a -> Put ()
+ Data.Persist: resolveSizeExclusiveLE :: (Integral a, HasEndianness a) => PutSize a -> Put ()
+ Data.Persist: resolveSizeInclusiveBE :: (Integral a, HasEndianness a) => PutSize a -> Put ()
+ Data.Persist: resolveSizeInclusiveLE :: (Integral a, HasEndianness a) => PutSize a -> Put ()
+ Data.Persist: resolveSizeLE :: (Integral a, HasEndianness a) => PutSize a -> a -> Put ()
+ Data.Persist: runPutLazy :: Put a -> ByteString
+ Data.Persist: type ReinterpretTarget a;
+ Data.Persist: type SerializeTarget a;
+ Data.Persist: unsafeGet :: Persist t => Get t
+ Data.Persist: unsafeGetBE :: HasEndianness a => Get a
+ Data.Persist: unsafeGetLE :: HasEndianness a => Get a
+ Data.Persist: unsafePut :: Persist t => t -> Put ()
+ Data.Persist: unsafePutBE :: HasEndianness a => a -> Put ()
+ Data.Persist: unsafePutLE :: HasEndianness a => a -> Put ()
+ Data.Persist: }
+ Data.Persist.Internal: PutSize :: !Ptr Word8 -> !Ptr Word8 -> !Ptr Word8 -> PutSize a
+ Data.Persist.Internal: PutSizeMissingStartChunk :: PutException
+ Data.Persist.Internal: [begin] :: Chunk -> {-# UNPACK #-} !Ptr Word8
+ Data.Persist.Internal: [buf] :: GetEnv -> !ForeignPtr Word8
+ Data.Persist.Internal: [chunkStart] :: PutSize a -> !Ptr Word8
+ Data.Persist.Internal: [chunks] :: PutEnv -> !IORef (NonEmpty Chunk)
+ Data.Persist.Internal: [end] :: Chunk -> {-# UNPACK #-} !Ptr Word8
+ Data.Persist.Internal: [sizePtr] :: PutSize a -> !Ptr Word8
+ Data.Persist.Internal: [sizeStart] :: PutSize a -> !Ptr Word8
+ Data.Persist.Internal: [tmp] :: PutEnv -> {-# UNPACK #-} !Ptr Word8
+ Data.Persist.Internal: data PutException
+ Data.Persist.Internal: data PutSize a
+ Data.Persist.Internal: evalPutLazy :: Put a -> (a, ByteString)
+ Data.Persist.Internal: evalPutLazyIO :: Put a -> IO (a, ByteString)
+ Data.Persist.Internal: evalPutStrictIO :: Put a -> IO (a, ByteString)
+ Data.Persist.Internal: instance GHC.Classes.Eq Data.Persist.Internal.PutException
+ Data.Persist.Internal: instance GHC.Exception.Type.Exception Data.Persist.Internal.PutException
+ Data.Persist.Internal: instance GHC.Show.Show Data.Persist.Internal.PutException
+ Data.Persist.Internal: unsafeGetPrefix :: Int -> Get a -> Get a
- Data.Persist: get :: (Persist t, Generic t, GPersistGet (Rep t)) => Get t
+ Data.Persist: get :: Persist t => Get t
- Data.Persist: put :: (Persist t, Generic t, GPersistPut (Rep t)) => t -> Put ()
+ Data.Persist: put :: Persist t => t -> Put ()
- Data.Persist.Internal: Chunk :: !Ptr Word8 -> !Ptr Word8 -> Chunk
+ Data.Persist.Internal: Chunk :: {-# UNPACK #-} !Ptr Word8 -> {-# UNPACK #-} !Ptr Word8 -> Chunk
- Data.Persist.Internal: GetEnv :: !ForeignPtr Word8 -> !Ptr Word8 -> !Ptr Word8 -> !Ptr Word8 -> GetEnv
+ Data.Persist.Internal: GetEnv :: !ForeignPtr Word8 -> {-# UNPACK #-} !Ptr Word8 -> {-# UNPACK #-} !Ptr Word8 -> {-# UNPACK #-} !Ptr Word8 -> GetEnv
- Data.Persist.Internal: PutEnv :: !IORef (NonEmpty Chunk) -> !IORef (Ptr Word8) -> !Ptr Word8 -> PutEnv
+ Data.Persist.Internal: PutEnv :: !IORef (NonEmpty Chunk) -> !IORef (Ptr Word8) -> {-# UNPACK #-} !Ptr Word8 -> PutEnv
Files
- Changelog.md +88/−0
- persist.cabal +68/−56
- src/Data/Persist.hs +584/−374
- src/Data/Persist/Internal.hs +185/−87
- tests/RoundTrip.hs +165/−83
+ Changelog.md view
@@ -0,0 +1,88 @@+## Changes from 0.1.1.5 to 1.0.0.0 ##++ * Use DerivingVia to reduce repetititon for base instances+ + Add three helper type classes: HasEndianness, SerializeAs,+ ReinterpretAs, and two newtypes: ViaSerializeAs and ViaReinterpretAs.+ HasEndianness allows us to create persist instances for `BigEndian a`+ and `LittleEndian a` without repetititon. SerializeAs allows anyone to+ supply a cast function in both directions and then re-use either a+ HasEndianness instance or a Persist instance. See the source file for+ example uses.++ * Add unsafe versions of put/get and helpers+ + Allow for Persist instances to reserve specific sizes ahead of time and+ then elide the checks while serializing. As an implemented example, we+ allocate space for the length of a ByteString and the bytes in the+ ByteString together. For characters as well, we allocate the space all+ at once. This technique would be useful for arrays of fixed size+ elements, like unboxed vectors.++ * Add getPrefix and unsafeGetPrefix+ + It often occurs when deserializing a custom format that we need to+ recursively deserialize a subcomponent from the next `n` bytes. The+ naive way to do this would be to get a `ByteString` for those n bytes,+ and then run the appropriate `Get` on that `ByteString`. To simplify+ this process, introduce `getPrefix` and `unsafeGetPrefix`. These+ manipulate the `Get` internals to expose only the appropriate bytes to+ the sub-deserializer. This avoids repeated copying, and even repeated+ `ByteString` construction.++ * Add length backpatching+ + There are many serialized formats where a serialized sub-value is+ prefixed by its length. The simplest way to find the serialized length+ of a value is to serialize it and then to measure the length. For+ formats that use a fixed number of bytes for this length, we can avoid+ copying and also walking a data structure twice: Once to find the size,+ and again to write the data with the known size. It is straightforward+ to use this. Reserve the size, serialize the sub-value, then resolve the+ size, with the correct endianness and inclusivity.+ + We do this by reserving the bytes ahead of time and returning a+ `PutSize` handle that can be used later to fill in the result.+ Currently, the library handles computing the appropriate size, which can+ be filled in either Big or Little endian, and either inclusive or+ exclusive of the size itself. DHCPv6 packets use this style of+ length-value encoding. As does Cassandra's encoding format, and MongoDB+ as well. MongoDB uses this same backpatching optimization.++ * Switch to NoFieldSelectors and OverloadedRecordDot+ + GHC has supported OverloadedRecordDot and NoFieldSelectors since 9.2+ Switch away from prefixed names and instead just use names and record+ dot syntax. There are a handful of now-ambiguous updates. They were+ resolved using a small lambda and RecordWildCards/NamedFieldPuns. This+ is a breaking change, but there are several and we are planning on a new+ major release.++ * Bump version bounds to match ghc 9.2+ + Bump tested-with to match the versions of ghc 9.2--9.12 that were+ manually tested. Bump versions of any libraries included with ghc to+ match the minimum version included with ghc 9.2++ * Add Kyle Butt as maintainer and author++ * Borrow improved error message handling from Store++ * Improve interaction with bytestring library+ + Avoid a copy for a strict ByteString with only one chunk by calling+ `reallocBytes` and then building the ByteString directly by recording a+ finalizer.+ + Add support for running a put and producing a lazy ByteString. Turn each+ chunk into a strict ByteString with the appropriate finalizer, and then+ use `foldlM` to reverse the chunks and build the lazy ByteString at the+ same time.++ * Add resolveSize helpers that write a computed size+ + This makes list serialization much faster. It makes almost all of the+ serialization benchmarks competitive with store. The remaining gap is+ due to reinterpretCast. Copying a Double to tmp and then again to the+ buffer is slower. But this is a simple win that puts the new reservation+ code to good use.
persist.cabal view
@@ -1,71 +1,83 @@-name: persist-version: 0.1.1.5-license: BSD3-license-file: LICENSE-author: Daniel Mendler <mail@daniel-mendler.de>,- Michael Sloan <sloan@fpcomplete.com>,- FP Complete,- Lennart Kolmodin <kolmodin@dtek.chalmers.se>,- Galois Inc.,- Lemmih <lemmih@gmail.com>,- Bas van Dijk <v.dijk.bas@gmail.com>-maintainer: Daniel Mendler <mail@daniel-mendler.de>-category: Data, Parsing-stability: provisional-build-type: Simple-cabal-version: >= 1.10-synopsis: Minimal serialization library with focus on performance-homepage: https://github.com/minad/persist-tested-with: GHC == 8.4.4, GHC == 8.6.4+cabal-version: 3.6+name: persist+version: 1.0.0.0+license: BSD-3-Clause+license-file: LICENSE+author:+ Daniel Mendler <mail@daniel-mendler.de>,+ Michael Sloan <sloan@fpcomplete.com>,+ FP Complete,+ Lennart Kolmodin <kolmodin@dtek.chalmers.se>,+ Galois Inc.,+ Lemmih <lemmih@gmail.com>,+ Bas van Dijk <v.dijk.bas@gmail.com>,+ Kyle Butt <kyle@iteratee.net> +maintainer: Kyle Butt <kyle@iteratee.net>+category: Data, Parsing+stability: provisional+build-type: Simple+synopsis: Minimal serialization library with focus on performance+homepage: https://github.com/minad/persist+tested-with:+ ghc ==9.2.8+ ghc ==9.4.8+ ghc ==9.6.7+ ghc ==9.8.4+ ghc ==9.10.2+ ghc ==9.12.2+extra-doc-files: Changelog.md+ description:- A binary serialization library with focus on performance similar to store and cereal+ A binary serialization library with focus on performance similar to store and+ cereal. Includes utilities to match externally specified data formats. source-repository head- type: git- location: git://github.com/minad/persist+ type: git+ location: https://github.com/minad/persist flag force-unaligned- manual: True- default: False+ manual: True+ default: False+ flag force-aligned- manual: True- default: False+ manual: True+ default: False library- default-language: Haskell2010-- build-depends: base >= 4.7 && < 5, containers,- bytestring >= 0.10.4 && < 1,- text >= 1.2 && < 1.3-- if !flag(force-aligned) && (flag(force-unaligned) || arch(i386) || arch(x86_64))- cpp-options: -DUNALIGNED_MEMORY-- hs-source-dirs: src-- exposed-modules: Data.Persist,- Data.Persist.Internal-- ghc-options: -Wall -O2 -funbox-strict-fields+ default-language: Haskell2010+ build-depends:+ base >=4.16 && <5,+ bytestring >=0.11.1 && <1,+ containers >=0.6.5.1 && <0.9,+ text >=1.2.5 && <2.2, + if !flag(force-aligned) && (flag(force-unaligned) || arch(i386) || arch(x86_64))+ cpp-options: -DUNALIGNED_MEMORY+ hs-source-dirs: src+ exposed-modules:+ Data.Persist+ Data.Persist.Internal + ghc-options:+ -Wall+ -funbox-strict-fields test-suite test-persist- default-language: Haskell2010-- type: exitcode-stdio-1.0-- build-depends: base == 4.*,- bytestring >= 0.9,- QuickCheck,- test-framework,- test-framework-quickcheck2,- persist,- text >= 1.2 && < 1.3+ default-language: Haskell2010+ type: exitcode-stdio-1.0+ build-depends:+ QuickCheck >=2.16.0 && <2.17,+ base,+ bytestring,+ persist,+ test-framework >=0.8.2 && <0.9,+ test-framework-quickcheck2 >=0.3.0 && <0.4,+ text, - main-is: Main.hs- other-modules: RoundTrip- GetTests+ main-is: Main.hs+ other-modules:+ GetTests+ RoundTrip - hs-source-dirs: tests+ hs-source-dirs: tests
src/Data/Persist.hs view
@@ -1,85 +1,106 @@ {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-}+{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DefaultSignatures #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE DerivingVia #-} {-# LANGUAGE EmptyCase #-}-{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-}--module Data.Persist (+{-# LANGUAGE NoFieldSelectors #-} - -- * The Persist class- Persist(..)+module Data.Persist+ ( -- * The Persist class+ Persist (..) -- * Endianness- , HostEndian- , BigEndian(..)- , LittleEndian(..)+ , HostEndian+ , BigEndian (..)+ , LittleEndian (..) + -- * Helpers for writing Persist instances+ , HasEndianness (..)+ , ReinterpretAs (..)+ , SerializeAs (..)+ , ViaReinterpretAs (..)+ , ViaSerializeAs (..)+ -- * Serialization- , encode- , decode+ , encode+ , encodeLazy+ , decode -- * The Get type- , Get- , runGet- , ensure- , skip- , getBytes- , getByteString- , remaining- , eof- , getHE- , getLE- , getBE+ , Get+ , runGet+ , ensure+ , skip+ , getBytes+ , getByteString+ , remaining+ , eof+ , getPrefix+ , getHE+ , getLE+ , getBE -- * The Put type- , Put- , runPut- , evalPut- , grow- , putByteString- , putHE- , putLE- , putBE-) where+ , Put+ , runPut+ , runPutLazy+ , evalPut+ , evalPutLazy+ , grow+ , putByteString+ , putHE+ , putLE+ , putBE -import Control.Monad-import Data.Bits+ -- * Size Reserve/Resolve+ , reserveSize+ , resolveSizeExclusiveBE+ , resolveSizeExclusiveLE+ , resolveSizeInclusiveBE+ , resolveSizeInclusiveLE+ , resolveSizeLE+ , resolveSizeBE+ ) where++import Control.Exception (throw)+import Control.Monad (forM_, when, (<$!>))+import Data.Bits (Bits (..)) import Data.ByteString (ByteString)-import Data.Int+import Data.IORef (readIORef)+import Data.Int (Int16, Int32, Int64, Int8) import Data.IntMap (IntMap) import Data.IntSet (IntSet)+import Data.Kind (Constraint, Type) import Data.List (unfoldr)-import Data.List.NonEmpty (NonEmpty(..))+import Data.List.NonEmpty (NonEmpty (..)) import Data.Map (Map) import Data.Persist.Internal import Data.Proxy import Data.Sequence (Seq) import Data.Set (Set) import Data.Text (Text)-import Data.Word-import Foreign (Ptr, Storable(..), plusPtr, minusPtr, castPtr, withForeignPtr)-import GHC.Base (unsafeChr, ord)-import GHC.Exts (IsList(..))-import GHC.Generics-import GHC.Real (Ratio(..))-import GHC.TypeLits-import Numeric.Natural+#ifdef UNALIGNED_MEMORY+import Data.Word (Word8, Word16, Word32, Word64, byteSwap16, byteSwap32, byteSwap64)+#else+import Data.Word (Word8, Word16, Word32, Word64)+#endif import qualified Data.ByteString as B import qualified Data.ByteString.Internal as B import qualified Data.ByteString.Lazy as L@@ -88,11 +109,28 @@ import qualified Data.Monoid as M import qualified Data.Text.Encoding as TE import qualified Data.Tree as T+import Foreign (Ptr, Storable (..), castPtr, minusPtr, plusPtr, withForeignPtr)+import Foreign.Marshal.Utils (copyBytes)+import GHC.Base (ord, unsafeChr)+import GHC.Exts (IsList (..))+import GHC.Generics+import GHC.Real (Ratio (..))+import GHC.TypeLits+ ( ErrorMessage (..)+ , KnownNat+ , Nat+ , Natural+ , Symbol+ , TypeError+ , natVal+ , type (+)+ , type (<=?)+ ) #include "MachDeps.h" -putHE :: Persist (HostEndian a) => a -> Put ()-getHE :: Persist (HostEndian a) => Get a+putHE :: (Persist (HostEndian a)) => a -> Put ()+getHE :: (Persist (HostEndian a)) => Get a {-# INLINE putHE #-} {-# INLINE getHE #-} @@ -146,15 +184,15 @@ {-# INLINE peekByte #-} poke16LE p y = do- pokeByte p $ y+ pokeByte p y pokeByte (p `plusPtr` 1) $ y `unsafeShiftR` 8 poke16BE p y = do pokeByte p $ y `unsafeShiftR` 8- pokeByte (p `plusPtr` 1) $ y+ pokeByte (p `plusPtr` 1) y poke32LE p y = do- pokeByte p $ y+ pokeByte p y pokeByte (p `plusPtr` 1) $ y `unsafeShiftR` 8 pokeByte (p `plusPtr` 2) $ y `unsafeShiftR` 16 pokeByte (p `plusPtr` 3) $ y `unsafeShiftR` 24@@ -163,10 +201,10 @@ pokeByte p $ y `unsafeShiftR` 24 pokeByte (p `plusPtr` 1) $ y `unsafeShiftR` 16 pokeByte (p `plusPtr` 2) $ y `unsafeShiftR` 8- pokeByte (p `plusPtr` 3) $ y+ pokeByte (p `plusPtr` 3) y poke64LE p y = do- pokeByte p $ y+ pokeByte p y pokeByte (p `plusPtr` 1) $ y `unsafeShiftR` 8 pokeByte (p `plusPtr` 2) $ y `unsafeShiftR` 16 pokeByte (p `plusPtr` 3) $ y `unsafeShiftR` 24@@ -183,7 +221,7 @@ pokeByte (p `plusPtr` 4) $ y `unsafeShiftR` 24 pokeByte (p `plusPtr` 5) $ y `unsafeShiftR` 16 pokeByte (p `plusPtr` 6) $ y `unsafeShiftR` 8- pokeByte (p `plusPtr` 7) $ y+ pokeByte (p `plusPtr` 7) y peek16LE p = do !x0 <- peekByte @Word16 p@@ -327,18 +365,27 @@ peek64BE p = fromBE64 <$!> peek (castPtr @_ @Word64 p) #endif -newtype BigEndian a = BigEndian { unBE :: a }+newtype BigEndian a = BigEndian {unBE :: a} deriving (Show, Eq, Ord, Functor, Foldable, Traversable, Generic) -newtype LittleEndian a = LittleEndian { unLE :: a }+newtype LittleEndian a = LittleEndian {unLE :: a} deriving (Show, Eq, Ord, Functor, Foldable, Traversable, Generic) class Persist t where -- | Encode a value in the Put monad. put :: t -> Put ()+ -- | Decode a value in the Get monad get :: Get t + -- | Encode a value without checking for sufficient size.+ unsafePut :: t -> Put ()+ unsafePut = put++ -- | Decode a value without checking for sufficient size.+ unsafeGet :: Get t+ unsafeGet = get+ default put :: (Generic t, GPersistPut (Rep t)) => t -> Put () put = gput . from @@ -346,109 +393,115 @@ get = to <$!> gget -- | Encode a value using binary serialization to a strict ByteString.-encode :: Persist a => a -> ByteString+encode :: (Persist a) => a -> ByteString encode = runPut . put --- | Decode a value from a strict ByteString, reconstructing the original--- structure.-decode :: Persist a => ByteString -> Either String a+-- | Encode a value using binary serialization to a lazy ByteString.+encodeLazy :: (Persist a) => a -> L.ByteString+encodeLazy = runPutLazy . put+++{- | Decode a value from a strict ByteString, reconstructing the original+structure.+-}+decode :: (Persist a) => ByteString -> Either String a decode = runGet get -putLE :: Persist (LittleEndian a) => a -> Put ()+putLE :: (Persist (LittleEndian a)) => a -> Put () putLE = put . LittleEndian {-# INLINE putLE #-} -putBE :: Persist (BigEndian a) => a -> Put ()+putBE :: (Persist (BigEndian a)) => a -> Put () putBE = put . BigEndian {-# INLINE putBE #-} -getLE :: Persist (LittleEndian a) => Get a-getLE = unLE <$!> get+getLE :: forall a. (Persist (LittleEndian a)) => Get a+getLE = (.unLE) <$!> get @(LittleEndian a) {-# INLINE getLE #-} -getBE :: Persist (BigEndian a) => Get a-getBE = unBE <$!> get+getBE :: forall a. (Persist (BigEndian a)) => Get a+getBE = (.unBE) <$!> get @(BigEndian a) {-# INLINE getBE #-} -unsafePutByte :: Integral a => a -> Put ()+unsafePutByte :: (Integral a) => a -> Put () unsafePutByte x = Put $ \_ p -> do poke p $ fromIntegral x pure $! p `plusPtr` 1 :!: () {-# INLINE unsafePutByte #-} -unsafePut16LE :: Integral a => a -> Put ()+unsafePut16LE :: (Integral a) => a -> Put () unsafePut16LE x = Put $ \_ p -> do poke16LE p $ fromIntegral x pure $! p `plusPtr` 2 :!: () {-# INLINE unsafePut16LE #-} -unsafePut32LE :: Integral a => a -> Put ()+unsafePut32LE :: (Integral a) => a -> Put () unsafePut32LE x = Put $ \_ p -> do poke32LE p $ fromIntegral x pure $! p `plusPtr` 4 :!: () {-# INLINE unsafePut32LE #-} -unsafePut64LE :: Integral a => a -> Put ()+unsafePut64LE :: (Integral a) => a -> Put () unsafePut64LE x = Put $ \_ p -> do poke64LE p $ fromIntegral x pure $! p `plusPtr` 8 :!: () {-# INLINE unsafePut64LE #-} -unsafePut16BE :: Integral a => a -> Put ()+unsafePut16BE :: (Integral a) => a -> Put () unsafePut16BE x = Put $ \_ p -> do poke16BE p $ fromIntegral x pure $! p `plusPtr` 2 :!: () {-# INLINE unsafePut16BE #-} -unsafePut32BE :: Integral a => a -> Put ()+unsafePut32BE :: (Integral a) => a -> Put () unsafePut32BE x = Put $ \_ p -> do poke32BE p $ fromIntegral x pure $! p `plusPtr` 4 :!: () {-# INLINE unsafePut32BE #-} -unsafePut64BE :: Integral a => a -> Put ()+unsafePut64BE :: (Integral a) => a -> Put () unsafePut64BE x = Put $ \_ p -> do poke64BE p $ fromIntegral x pure $! p `plusPtr` 8 :!: () {-# INLINE unsafePut64BE #-} -unsafeGetByte :: Num a => Get a+unsafeGetByte :: (Num a) => Get a unsafeGetByte = Get $ \_ p -> do x <- peek p pure $! p `plusPtr` 1 :!: fromIntegral x {-# INLINE unsafeGetByte #-} -unsafeGet16LE :: Num a => Get a+unsafeGet16LE :: (Num a) => Get a unsafeGet16LE = Get $ \_ p -> do x <- peek16LE p pure $! p `plusPtr` 2 :!: fromIntegral x {-# INLINE unsafeGet16LE #-} -unsafeGet32LE :: Num a => Get a+unsafeGet32LE :: (Num a) => Get a unsafeGet32LE = Get $ \_ p -> do x <- peek32LE p pure $! p `plusPtr` 4 :!: fromIntegral x {-# INLINE unsafeGet32LE #-} -unsafeGet64LE :: Num a => Get a+unsafeGet64LE :: (Num a) => Get a unsafeGet64LE = Get $ \_ p -> do x <- peek64LE p pure $! p `plusPtr` 8 :!: fromIntegral x {-# INLINE unsafeGet64LE #-} -unsafeGet16BE :: Num a => Get a+unsafeGet16BE :: (Num a) => Get a unsafeGet16BE = Get $ \_ p -> do x <- peek16BE p pure $! p `plusPtr` 2 :!: fromIntegral x {-# INLINE unsafeGet16BE #-} -unsafeGet32BE :: Num a => Get a+unsafeGet32BE :: (Num a) => Get a unsafeGet32BE = Get $ \_ p -> do x <- peek32BE p pure $! p `plusPtr` 4 :!: fromIntegral x {-# INLINE unsafeGet32BE #-} -unsafeGet64BE :: Num a => Get a+unsafeGet64BE :: (Num a) => Get a unsafeGet64BE = Get $ \_ p -> do x <- peek64BE p pure $! p `plusPtr` 8 :!: fromIntegral x@@ -461,11 +514,11 @@ {-# INLINE reinterpretCast #-} reinterpretCastPut :: (Storable a, Storable b) => a -> Put b-reinterpretCastPut x = Put $ \e p -> (p :!:) <$!> reinterpretCast (peTmp e) x+reinterpretCastPut x = Put $ \e p -> (p :!:) <$!> reinterpretCast e.tmp x {-# INLINE reinterpretCastPut #-} reinterpretCastGet :: (Storable a, Storable b) => a -> Get b-reinterpretCastGet x = Get $ \e p -> (p :!:) <$!> reinterpretCast (geTmp e) x+reinterpretCastGet x = Get $ \e p -> (p :!:) <$!> reinterpretCast e.tmp x {-# INLINE reinterpretCastGet #-} -- The () type need never be written to disk: values of singleton type@@ -476,232 +529,202 @@ get = pure () {-# INLINE get #-} -instance Persist Word8 where- put x = do- grow 1- unsafePutByte x- {-# INLINE put #-}-- get = do- ensure 1- unsafeGetByte- {-# INLINE get #-}--instance Persist (LittleEndian Word16) where- put x = do- grow 2- unsafePut16LE $ unLE x- {-# INLINE put #-}+class HasEndianness a where+ unsafeGetLE :: Get a+ unsafePutLE :: a -> Put ()+ unsafeGetBE :: Get a+ unsafePutBE :: a -> Put ()+ endiannessSize :: Int - get = do- ensure 2- LittleEndian <$!> unsafeGet16LE- {-# INLINE get #-}+instance HasEndianness Word8 where+ unsafeGetLE = unsafeGetByte+ unsafeGetBE = unsafeGetByte+ unsafePutLE = unsafePutByte+ unsafePutBE = unsafePutByte+ endiannessSize = 1 -instance Persist (BigEndian Word16) where- put x = do- grow 2- unsafePut16BE $ unBE x- {-# INLINE put #-}+instance HasEndianness Word16 where+ unsafeGetLE = unsafeGet16LE+ {-# INLINE unsafeGetLE #-}+ unsafeGetBE = unsafeGet16BE+ {-# INLINE unsafeGetBE #-}+ unsafePutLE = unsafePut16LE+ {-# INLINE unsafePutLE #-}+ unsafePutBE = unsafePut16BE+ {-# INLINE unsafePutBE #-}+ endiannessSize = 2+ {-# INLINE endiannessSize #-} - get = do- ensure 2- BigEndian <$!> unsafeGet16BE- {-# INLINE get #-}+instance HasEndianness Word32 where+ unsafeGetLE = unsafeGet32LE+ {-# INLINE unsafeGetLE #-}+ unsafeGetBE = unsafeGet32BE+ {-# INLINE unsafeGetBE #-}+ unsafePutLE = unsafePut32LE+ {-# INLINE unsafePutLE #-}+ unsafePutBE = unsafePut32BE+ {-# INLINE unsafePutBE #-}+ endiannessSize = 4+ {-# INLINE endiannessSize #-} -instance Persist Word16 where- put = putLE- {-# INLINE put #-}- get = getLE- {-# INLINE get #-}+instance HasEndianness Word64 where+ unsafeGetLE = unsafeGet64LE+ {-# INLINE unsafeGetLE #-}+ unsafeGetBE = unsafeGet64BE+ {-# INLINE unsafeGetBE #-}+ unsafePutLE = unsafePut64LE+ {-# INLINE unsafePutLE #-}+ unsafePutBE = unsafePut64BE+ {-# INLINE unsafePutBE #-}+ endiannessSize = 8+ {-# INLINE endiannessSize #-} -instance Persist (LittleEndian Word32) where+instance (HasEndianness a) => Persist (LittleEndian a) where put x = do- grow 4- unsafePut32LE $ unLE x+ grow (endiannessSize @a)+ unsafePut x {-# INLINE put #-} - get = do- ensure 4- LittleEndian <$!> unsafeGet32LE- {-# INLINE get #-}--instance Persist (BigEndian Word32) where- put x = do- grow 4- unsafePut32BE $ unBE x- {-# INLINE put #-}+ unsafePut = unsafePutLE . (.unLE)+ {-# INLINE unsafePut #-} get = do- ensure 4- BigEndian <$!> unsafeGet32BE+ ensure (endiannessSize @a)+ unsafeGet {-# INLINE get #-} -instance Persist Word32 where- put = putLE- {-# INLINE put #-}- get = getLE- {-# INLINE get #-}+ unsafeGet = LittleEndian <$!> unsafeGetLE+ {-# INLINE unsafeGet #-} -instance Persist (LittleEndian Word64) where+instance (HasEndianness a) => Persist (BigEndian a) where put x = do- grow 8- unsafePut64LE $ unLE x+ grow (endiannessSize @a)+ unsafePut x {-# INLINE put #-} - get = do- ensure 8- LittleEndian <$!> unsafeGet64LE- {-# INLINE get #-}--instance Persist (BigEndian Word64) where- put x = do- grow 8- unsafePut64BE $ unBE x- {-# INLINE put #-}+ unsafePut = unsafePutBE . (.unBE)+ {-# INLINE unsafePut #-} get = do- ensure 8- BigEndian <$!> unsafeGet64BE- {-# INLINE get #-}--instance Persist Word64 where- put = putLE- {-# INLINE put #-}- get = getLE+ ensure (endiannessSize @a)+ unsafeGet {-# INLINE get #-} -instance Persist Int8 where- put = put @Word8 . fromIntegral- {-# INLINE put #-}- get = fromIntegral <$!> get @Word8- {-# INLINE get #-}+ unsafeGet = BigEndian <$!> unsafeGetBE+ {-# INLINE unsafeGet #-} -instance Persist (LittleEndian Int16) where- put = put . fmap (fromIntegral @_ @Word16)- {-# INLINE put #-}- get = fmap (fromIntegral @Word16) <$!> get- {-# INLINE get #-}+deriving via (LittleEndian Word8) instance Persist Word8+deriving via (LittleEndian Word16) instance Persist Word16+deriving via (LittleEndian Word32) instance Persist Word32+deriving via (LittleEndian Word64) instance Persist Word64 -instance Persist (BigEndian Int16) where- put = put . fmap (fromIntegral @_ @Word16)- {-# INLINE put #-}- get = fmap (fromIntegral @Word16) <$!> get- {-# INLINE get #-}+class SerializeAs a where+ type SerializeTarget a+ castPut :: a -> SerializeTarget a+ castGet :: SerializeTarget a -> a -instance Persist Int16 where- put = putLE- {-# INLINE put #-}- get = getLE- {-# INLINE get #-}+instance SerializeAs Int8 where+ type SerializeTarget Int8 = Word8+ castPut = fromIntegral+ castGet = fromIntegral -instance Persist (LittleEndian Int32) where- put = put . fmap (fromIntegral @_ @Word32)- {-# INLINE put #-}- get = fmap (fromIntegral @Word32) <$!> get- {-# INLINE get #-}+instance SerializeAs Int16 where+ type SerializeTarget Int16 = Word16+ castPut = fromIntegral+ castGet = fromIntegral -instance Persist (BigEndian Int32) where- put = put . fmap (fromIntegral @_ @Word32)- {-# INLINE put #-}- get = fmap (fromIntegral @Word32) <$!> get- {-# INLINE get #-}+instance SerializeAs Int32 where+ type SerializeTarget Int32 = Word32+ castPut = fromIntegral+ castGet = fromIntegral -instance Persist Int32 where- put = putLE- {-# INLINE put #-}- get = getLE- {-# INLINE get #-}+instance SerializeAs Int64 where+ type SerializeTarget Int64 = Word64+ castPut = fromIntegral+ castGet = fromIntegral -instance Persist (LittleEndian Int64) where- put = put . fmap (fromIntegral @_ @Word64)- {-# INLINE put #-}- get = fmap (fromIntegral @Word64) <$!> get- {-# INLINE get #-}+newtype ViaSerializeAs a = MkViaSerializeAs a+ deriving (Eq, Ord, Show) -instance Persist (BigEndian Int64) where- put = put . fmap (fromIntegral @_ @Word64)- {-# INLINE put #-}- get = fmap (fromIntegral @Word64) <$!> get- {-# INLINE get #-}+instance (SerializeAs a, HasEndianness (SerializeTarget a)) => HasEndianness (ViaSerializeAs a) where+ endiannessSize = endiannessSize @(SerializeTarget a)+ {-# INLINE endiannessSize #-}+ unsafeGetBE = MkViaSerializeAs . castGet <$!> unsafeGetBE+ {-# INLINE unsafeGetBE #-}+ unsafeGetLE = MkViaSerializeAs . castGet <$!> unsafeGetLE+ {-# INLINE unsafeGetLE #-}+ unsafePutBE (MkViaSerializeAs x) = unsafePutBE . castPut $! x+ {-# INLINE unsafePutBE #-}+ unsafePutLE (MkViaSerializeAs x) = unsafePutLE . castPut $! x+ {-# INLINE unsafePutLE #-} -instance Persist Int64 where- put = putLE- {-# INLINE put #-}- get = getLE- {-# INLINE get #-}+deriving via (ViaSerializeAs Int8) instance HasEndianness Int8+deriving via (ViaSerializeAs Int16) instance HasEndianness Int16+deriving via (ViaSerializeAs Int32) instance HasEndianness Int32+deriving via (ViaSerializeAs Int64) instance HasEndianness Int64 -instance Persist (LittleEndian Double) where- put x = reinterpretCastPut (unLE x) >>= putLE @Word64- {-# INLINE put #-}- get = getLE @Word64 >>= fmap LittleEndian . reinterpretCastGet- {-# INLINE get #-}+deriving via (LittleEndian Int8) instance Persist Int8+deriving via (LittleEndian Int16) instance Persist Int16+deriving via (LittleEndian Int32) instance Persist Int32+deriving via (LittleEndian Int64) instance Persist Int64 -instance Persist (BigEndian Double) where- put x = reinterpretCastPut (unBE x) >>= putBE @Word64- {-# INLINE put #-}- get = getBE @Word64 >>= fmap BigEndian . reinterpretCastGet- {-# INLINE get #-}+instance SerializeAs Word where+ type SerializeTarget Word = Word64+ castPut = fromIntegral+ castGet = fromIntegral -instance Persist Double where- put = putLE- {-# INLINE put #-}- get = getLE- {-# INLINE get #-}+instance SerializeAs Int where+ type SerializeTarget Int = Int64+ castPut = fromIntegral+ castGet = fromIntegral -instance Persist (LittleEndian Float) where- put x = reinterpretCastPut (unLE x) >>= putLE @Word32+instance (SerializeAs a, Persist (SerializeTarget a)) => Persist (ViaSerializeAs a) where+ put (MkViaSerializeAs x) = put (castPut x) {-# INLINE put #-}- get = getLE @Word32 >>= fmap LittleEndian . reinterpretCastGet+ unsafePut (MkViaSerializeAs x) = unsafePut (castPut x)+ {-# INLINE unsafePut #-}+ get = MkViaSerializeAs . castGet <$> get {-# INLINE get #-}+ unsafeGet = MkViaSerializeAs . castGet <$> unsafeGet+ {-# INLINE unsafeGet #-} -instance Persist (BigEndian Float) where- put x = reinterpretCastPut (unBE x) >>= putBE @Word32- {-# INLINE put #-}- get = getBE @Word32 >>= fmap BigEndian . reinterpretCastGet- {-# INLINE get #-}+deriving via (ViaSerializeAs Word) instance HasEndianness Word+deriving via (ViaSerializeAs Int) instance HasEndianness Int+deriving via (ViaSerializeAs Word) instance Persist Word+deriving via (ViaSerializeAs Int) instance Persist Int -instance Persist Float where- put = putLE- {-# INLINE put #-}- get = getLE- {-# INLINE get #-}+class ReinterpretAs a where+ type ReinterpretTarget a -instance Persist (LittleEndian Word) where- put = put . fmap (fromIntegral @_ @Word64)- {-# INLINE put #-}- get = fmap (fromIntegral @Word64) <$!> get- {-# INLINE get #-}+newtype ViaReinterpretAs a = MkViaReinterpretAs a+ deriving (Eq, Ord, Show) -instance Persist (BigEndian Word) where- put = put . fmap (fromIntegral @_ @Word64)- {-# INLINE put #-}- get = fmap (fromIntegral @Word64) <$!> get- {-# INLINE get #-}+instance+ forall a b.+ (ReinterpretAs a, Storable a, Storable b, HasEndianness b, b ~ ReinterpretTarget a) =>+ HasEndianness (ViaReinterpretAs a)+ where+ unsafePutLE (MkViaReinterpretAs x) = reinterpretCastPut x >>= unsafePutLE @b+ {-# INLINE unsafePutLE #-}+ unsafePutBE (MkViaReinterpretAs x) = reinterpretCastPut x >>= unsafePutBE @b+ {-# INLINE unsafePutBE #-}+ unsafeGetLE = MkViaReinterpretAs <$> (unsafeGetLE @(ReinterpretTarget a) >>= reinterpretCastGet)+ {-# INLINE unsafeGetLE #-}+ unsafeGetBE = MkViaReinterpretAs <$> (unsafeGetBE @(ReinterpretTarget a) >>= reinterpretCastGet)+ {-# INLINE unsafeGetBE #-}+ endiannessSize = endiannessSize @b -instance Persist Word where- put = putLE- {-# INLINE put #-}- get = getLE- {-# INLINE get #-}+instance ReinterpretAs Double where+ type ReinterpretTarget Double = Word64 -instance Persist (LittleEndian Int) where- put = put . fmap (fromIntegral @_ @Int64)- {-# INLINE put #-}- get = fmap (fromIntegral @Int64) <$!> get- {-# INLINE get #-}+instance ReinterpretAs Float where+ type ReinterpretTarget Float = Word32 -instance Persist (BigEndian Int) where- put = put . fmap (fromIntegral @_ @Int64)- {-# INLINE put #-}- get = fmap (fromIntegral @Int64) <$!> get- {-# INLINE get #-}+deriving via (ViaReinterpretAs Double) instance HasEndianness Double+deriving via (ViaReinterpretAs Float) instance HasEndianness Float -instance Persist Int where- put = putLE- {-# INLINE put #-}- get = getLE- {-# INLINE get #-}+deriving via (LittleEndian Double) instance Persist Double+deriving via (LittleEndian Float) instance Persist Float instance Persist Integer where put n = do@@ -715,14 +738,16 @@ unroll :: (Integral a, Bits a) => a -> [Word8] unroll = unfoldr step- where step 0 = Nothing- step i = Just (fromIntegral i, i `unsafeShiftR` 8)+ where+ step 0 = Nothing+ step i = Just (fromIntegral i, i `unsafeShiftR` 8) roll :: (Integral a, Bits a) => [Word8] -> a roll = foldr unstep 0- where unstep b a = a `unsafeShiftL` 8 .|. fromIntegral b+ where+ unstep b a = a `unsafeShiftL` 8 .|. fromIntegral b -instance Persist a => Persist (Ratio a) where+instance (Persist a) => Persist (Ratio a) where put (n :% d) = put n *> put d {-# INLINE put #-} @@ -735,49 +760,76 @@ -- Char is serialized as UTF-8 instance Persist Char where- put a | c <= 0x7f = put (fromIntegral c :: Word8)- | c <= 0x7ff = do put (0xc0 .|. y)- put (0x80 .|. z)- | c <= 0xffff = do put (0xe0 .|. x)- put (0x80 .|. y)- put (0x80 .|. z)- | c <= 0x10ffff = do put (0xf0 .|. w)- put (0x80 .|. x)- put (0x80 .|. y)- put (0x80 .|. z)- | otherwise = error "Not a valid Unicode code point"- where- c = ord a- z, y, x, w :: Word8- z = fromIntegral (c .&. 0x3f)- y = fromIntegral (unsafeShiftR c 6 .&. 0x3f)- x = fromIntegral (unsafeShiftR c 12 .&. 0x3f)- w = fromIntegral (unsafeShiftR c 18 .&. 0x7)+ put a+ | c <= 0x7f = grow 1 >> unsafePut a+ | c <= 0x7ff = grow 2 >> unsafePut a+ | c <= 0xffff = grow 3 >> unsafePut a+ | c <= 0x10ffff = grow 4 >> unsafePut a+ | otherwise = error "Not a valid Unicode code point"+ where+ c = ord a {-# INLINE put #-} + unsafePut a+ | c <= 0x7f = unsafePut (fromIntegral c :: Word8)+ | c <= 0x7ff = do+ unsafePut (0xc0 .|. y)+ unsafePut (0x80 .|. z)+ | c <= 0xffff = do+ unsafePut (0xe0 .|. x)+ unsafePut (0x80 .|. y)+ unsafePut (0x80 .|. z)+ | c <= 0x10ffff = do+ unsafePut (0xf0 .|. w)+ unsafePut (0x80 .|. x)+ unsafePut (0x80 .|. y)+ unsafePut (0x80 .|. z)+ | otherwise = error "Not a valid Unicode code point"+ where+ c = ord a+ z, y, x, w :: Word8+ z = fromIntegral (c .&. 0x3f)+ y = fromIntegral (unsafeShiftR c 6 .&. 0x3f)+ x = fromIntegral (unsafeShiftR c 12 .&. 0x3f)+ w = fromIntegral (unsafeShiftR c 18 .&. 0x7)+ {-# INLINE unsafePut #-}+ get = do let byte = fromIntegral <$!> get @Word8 shiftL6 = flip unsafeShiftL 6 w <- byte- r <- if | w < 0x80 -> pure w- | w < 0xe0 -> do- x <- xor 0x80 <$!> byte- pure $ x .|. shiftL6 (xor 0xc0 w)- | w < 0xf0 -> do- x <- xor 0x80 <$!> byte- y <- xor 0x80 <$!> byte- pure $ y .|. shiftL6 (x .|. shiftL6- (xor 0xe0 w))- | otherwise -> do- x <- xor 0x80 <$!> byte- y <- xor 0x80 <$!> byte- z <- xor 0x80 <$!> byte- pure $ z .|. shiftL6 (y .|. shiftL6- (x .|. shiftL6 (xor 0xf0 w)))- if r <= 0x10FFFF then- pure $ unsafeChr r- else- failGet CharException "Invalid character"+ r <-+ if+ | w < 0x80 -> pure w+ | w < 0xe0 -> do+ x <- xor 0x80 <$!> byte+ pure $ x .|. shiftL6 (xor 0xc0 w)+ | w < 0xf0 -> do+ x <- xor 0x80 <$!> byte+ y <- xor 0x80 <$!> byte+ pure $+ y+ .|. shiftL6+ ( x+ .|. shiftL6+ (xor 0xe0 w)+ )+ | otherwise -> do+ x <- xor 0x80 <$!> byte+ y <- xor 0x80 <$!> byte+ z <- xor 0x80 <$!> byte+ pure $+ z+ .|. shiftL6+ ( y+ .|. shiftL6+ (x .|. shiftL6 (xor 0xf0 w))+ )+ if r <= 0x10FFFF+ then+ pure $ unsafeChr r+ else+ failGet CharException "Invalid character" {-# INLINE get #-} instance Persist Text where@@ -791,60 +843,105 @@ instance Persist Bool instance Persist Ordering instance (Persist a) => Persist (Maybe a)-instance Persist e => Persist (T.Tree e)+instance (Persist e) => Persist (T.Tree e) instance (Persist a, Persist b) => Persist (Either a b)-instance (Persist a, Persist b) => Persist (a,b)-instance (Persist a, Persist b, Persist c) => Persist (a,b,c)-instance (Persist a, Persist b, Persist c, Persist d)- => Persist (a,b,c,d)-instance (Persist a, Persist b, Persist c, Persist d, Persist e)- => Persist (a,b,c,d,e)-instance (Persist a, Persist b, Persist c, Persist d, Persist e- , Persist f)- => Persist (a,b,c,d,e,f)-instance (Persist a, Persist b, Persist c, Persist d, Persist e- , Persist f, Persist g)- => Persist (a,b,c,d,e,f,g)-instance Persist a => Persist (M.Dual a)+instance (Persist a, Persist b) => Persist (a, b)+instance (Persist a, Persist b, Persist c) => Persist (a, b, c)+instance+ (Persist a, Persist b, Persist c, Persist d) =>+ Persist (a, b, c, d)+instance+ (Persist a, Persist b, Persist c, Persist d, Persist e) =>+ Persist (a, b, c, d, e)+instance+ ( Persist a+ , Persist b+ , Persist c+ , Persist d+ , Persist e+ , Persist f+ ) =>+ Persist (a, b, c, d, e, f)+instance+ ( Persist a+ , Persist b+ , Persist c+ , Persist d+ , Persist e+ , Persist f+ , Persist g+ ) =>+ Persist (a, b, c, d, e, f, g)+instance (Persist a) => Persist (M.Dual a) instance Persist M.All instance Persist M.Any-instance Persist a => Persist (M.Sum a)-instance Persist a => Persist (M.Product a)-instance Persist a => Persist (M.First a)-instance Persist a => Persist (M.Last a)+instance (Persist a) => Persist (M.Sum a)+instance (Persist a) => Persist (M.Product a)+instance (Persist a) => Persist (M.First a)+instance (Persist a) => Persist (M.Last a) --- | Persist a list in the following format:--- Word64 (little endian format)--- element 1--- ...--- element n-instance Persist a => Persist [a] where- put l = do- put $ length l- mapM_ put l- {-# INLINE put #-}+{- | Persist a list in the following format:+ Word64 (little endian format)+ element 1+ ...+ element n+-}+instance (Persist a) => Persist [a] where+ put l = do+ sizeHandle <- reserveSize @Word64+ go sizeHandle 0 l+ where+ go sizeHandle !n [] = resolveSizeLE sizeHandle n+ go sizeHandle !n (x : rest) = put x >> go sizeHandle (n + 1) rest+ {-# INLINE put #-} - get = go [] =<< get @Word64- where go as 0 = pure $! reverse as- go as i = do x <- get- x `seq` go (x:as) (i - 1)- {-# INLINE get #-}+ get = go [] =<< get @Word64+ where+ go as 0 = pure $! reverse as+ go as i = do+ x <- get+ x `seq` go (x : as) (i - 1)+ {-# INLINE get #-} instance Persist ByteString where put s = do- put $ B.length s- putByteString s+ let lengthSize = fromIntegral (endiannessSize @Int)+ grow (B.length s + lengthSize)+ unsafePut s+ {-# INLINE put #-}++ unsafePut s = do+ unsafePut $ B.length s+ unsafePutByteString s+ {-# INLINE unsafePut #-}+ get = get >>= getByteString+ {-# INLINE get #-} instance Persist L.ByteString where- put = put . L.toStrict+ put s = do+ let lengthSize = fromIntegral (endiannessSize @Int64)+ grow (fromIntegral $ L.length s + lengthSize)+ unsafePut s+ {-# INLINE put #-}++ unsafePut s = do+ unsafePut $ L.length s+ forM_ (L.toChunks s) unsafePutByteString+ {-# INLINE unsafePut #-}+ get = L.fromStrict <$!> get+ {-# INLINE get #-} instance Persist S.ShortByteString where put s = do+ let lengthSize = fromIntegral (endiannessSize @Int)+ grow (S.length s + lengthSize)+ unsafePut s++ unsafePut s = do let n = S.length s- put n- grow n+ unsafePut n Put $ \_ p -> do S.copyToPtr s 0 p n pure $! p `plusPtr` n :!: ()@@ -867,25 +964,25 @@ put = put . toList get = fromList <$!> get -instance Persist e => Persist (NonEmpty e) where+instance (Persist e) => Persist (NonEmpty e) where put = put . toList {-# INLINE put #-} get = fromList <$!> get {-# INLINE get #-} -instance Persist e => Persist (IntMap e) where+instance (Persist e) => Persist (IntMap e) where put = put . toList {-# INLINE put #-} get = fromList <$!> get {-# INLINE get #-} -instance Persist e => Persist (Seq e) where+instance (Persist e) => Persist (Seq e) where put = put . toList {-# INLINE put #-} get = fromList <$!> get {-# INLINE get #-} -type family SumArity (a :: * -> *) :: Nat where+type family SumArity (a :: Type -> Type) :: Nat where SumArity (C1 c a) = 1 SumArity (x :+: y) = SumArity x + SumArity y @@ -895,19 +992,19 @@ class GPersistGet f where gget :: Get (f a) -instance GPersistPut f => GPersistPut (M1 i c f) where+instance (GPersistPut f) => GPersistPut (M1 i c f) where gput = gput . unM1 {-# INLINE gput #-} -instance GPersistGet f => GPersistGet (M1 i c f) where+instance (GPersistGet f) => GPersistGet (M1 i c f) where gget = fmap M1 gget {-# INLINE gget #-} -instance Persist a => GPersistPut (K1 i a) where+instance (Persist a) => GPersistPut (K1 i a) where gput = put . unK1 {-# INLINE gput #-} -instance Persist a => GPersistGet (K1 i a) where+instance (Persist a) => GPersistGet (K1 i a) where gget = fmap K1 get {-# INLINE gget #-} @@ -935,35 +1032,50 @@ gget = (:*:) <$!> gget <*> gget {-# INLINE gget #-} -instance (SumArity (a :+: b) <= 255, GPersistPutSum 0 (a :+: b)) => GPersistPut (a :+: b) where+instance (FitsInByte (SumArity (a :+: b)), GPersistPutSum 0 (a :+: b)) => GPersistPut (a :+: b) where gput x = gputSum x (Proxy :: Proxy 0) {-# INLINE gput #-} -instance (SumArity (a :+: b) <= 255, GPersistGetSum 0 (a :+: b)) => GPersistGet (a :+: b) where+instance (FitsInByte (SumArity (a :+: b)), GPersistGetSum 0 (a :+: b)) => GPersistGet (a :+: b) where gget = do tag <- get ggetSum tag (Proxy :: Proxy 0) {-# INLINE gget #-} -class KnownNat n => GPersistPutSum (n :: Nat) (f :: * -> *) where+type FitsInByte n = FitsInByteResult (n <=? 255)++type family FitsInByteResult (b :: Bool) :: Constraint where+ FitsInByteResult 'True = ()+ FitsInByteResult 'False =+ TypeErrorMessage+ "Generic deriving of Persist instances can only be used on datatypes with fewer than 256 constructors."++type family TypeErrorMessage (a :: Symbol) :: Constraint where+ TypeErrorMessage a = TypeError ('Text a)++class (KnownNat n) => GPersistPutSum (n :: Nat) (f :: Type -> Type) where gputSum :: f p -> Proxy n -> Put () -class KnownNat n => GPersistGetSum (n :: Nat) (f :: * -> *) where+class (KnownNat n) => GPersistGetSum (n :: Nat) (f :: Type -> Type) where ggetSum :: Word8 -> Proxy n -> Get (f p) -instance (GPersistPutSum n a, GPersistPutSum (n + SumArity a) b, KnownNat n)- => GPersistPutSum n (a :+: b) where+instance+ (GPersistPutSum n a, GPersistPutSum (n + SumArity a) b, KnownNat n) =>+ GPersistPutSum n (a :+: b)+ where gputSum (L1 l) _ = gputSum l (Proxy :: Proxy n) gputSum (R1 r) _ = gputSum r (Proxy :: Proxy (n + SumArity a)) {-# INLINE gputSum #-} -instance (GPersistGetSum n a, GPersistGetSum (n + SumArity a) b, KnownNat n)- => GPersistGetSum n (a :+: b) where+instance+ (GPersistGetSum n a, GPersistGetSum (n + SumArity a) b, KnownNat n) =>+ GPersistGetSum n (a :+: b)+ where ggetSum tag proxyL | tag < sizeL = L1 <$!> ggetSum tag proxyL | otherwise = R1 <$!> ggetSum tag (Proxy :: Proxy (n + SumArity a))- where- sizeL = fromInteger (natVal (Proxy :: Proxy (n + SumArity a)))+ where+ sizeL = fromInteger (natVal (Proxy :: Proxy (n + SumArity a))) {-# INLINE ggetSum #-} instance (GPersistPut a, KnownNat n) => GPersistPutSum n (C1 c a) where@@ -977,8 +1089,8 @@ | tag == cur = gget | tag > cur = fail "Sum tag invalid" | otherwise = fail "Implementation error"- where- cur = fromInteger (natVal (Proxy :: Proxy n))+ where+ cur = fromInteger (natVal (Proxy :: Proxy n)) {-# INLINE ggetSum #-} -- | Ensure that @n@ bytes are available. Fails if fewer than @n@ bytes are available.@@ -997,10 +1109,11 @@ Get $ \_ p -> pure $! p `plusPtr` n :!: () {-# INLINE skip #-} --- | Get the number of remaining unparsed bytes. Useful for checking whether--- all input has been consumed.+{- | Get the number of remaining unparsed bytes. Useful for checking whether+all input has been consumed.+-} remaining :: Get Int-remaining = Get $ \e p -> pure $! p :!: geEnd e `minusPtr` p+remaining = Get $ \e p -> pure $! p :!: e.end `minusPtr` p {-# INLINE remaining #-} -- -- | Succeed if end of input reached.@@ -1014,24 +1127,121 @@ getBytes :: Int -> Get ByteString getBytes n = do ensure n- Get $ \e p -> pure $! p `plusPtr` n :!: B.PS (geBuf e) (p `minusPtr` geBegin e) n+ Get $ \e p -> pure $! p `plusPtr` n :!: B.PS e.buf (p `minusPtr` e.begin) n {-# INLINE getBytes #-} --- | An efficient 'get' method for strict ByteStrings. Fails if fewer--- than @n@ bytes are left in the input. This function creates a fresh--- copy of the underlying bytes.+{- | An efficient 'get' method for strict ByteStrings. Fails if fewer+than @n@ bytes are left in the input. This function creates a fresh+copy of the underlying bytes.+-} getByteString :: Int -> Get ByteString getByteString n = B.copy <$!> getBytes n {-# INLINE getByteString #-} +{- | An efficient 'get' method to apply a recursive 'get' to a substring of+known length.+-}+getPrefix :: Int -> Get a -> Get a+getPrefix prefixLength baseGet = do+ ensure prefixLength+ unsafeGetPrefix prefixLength baseGet+ runPut :: Put a -> ByteString runPut = snd . evalPut {-# INLINE runPut #-} +runPutLazy :: Put a -> L.ByteString+runPutLazy = snd . evalPutLazy+{-# INLINE runPutLazy #-}+ putByteString :: ByteString -> Put ()-putByteString (B.PS b o n) = do- grow n+putByteString bs = do+ grow (B.length bs)+ unsafePutByteString bs++unsafePutByteString :: ByteString -> Put ()+unsafePutByteString (B.PS b o n) = do Put $ \_ p -> do- withForeignPtr b $ \q -> B.memcpy p (q `plusPtr` o) n+ withForeignPtr b $ \q -> copyBytes p (q `plusPtr` o) n pure $! p `plusPtr` n :!: () {-# INLINE putByteString #-}++{- | Reserve a length value that can be filled in later. The length+ value itself must have a fixed size.+-}+reserveSize :: forall a. (HasEndianness a) => Put (PutSize a)+reserveSize = do+ grow sizeSize+ doWrite+ where+ sizeSize = fromIntegral $ endiannessSize @a+ doWrite = Put $ \e p -> do+ let p' = p `plusPtr` sizeSize+ (c :| _) <- readIORef e.chunks+ pure $!+ p'+ :!: PutSize+ { sizePtr = p+ , sizeStart = p'+ , chunkStart = c.begin+ }++{- | Backpatch a computed length value, excluding the bytes for the length+ itself.+-}+resolveSizeExclusive :: forall a. (Integral a, HasEndianness a) => (a -> Put ()) -> PutSize a -> Put ()+resolveSizeExclusive putter PutSize {..} = Put $ \e p -> do+ writeSize <- computeSize e chunkStart sizeStart p+ _ <- (putter writeSize).unPut e sizePtr+ pure $ p :!: ()++resolveSizeInclusive :: forall a. (Integral a, HasEndianness a) => (a -> Put ()) -> PutSize a -> Put ()+resolveSizeInclusive putter PutSize {..} = Put $ \e p -> do+ writeSize <- computeSize e chunkStart sizePtr p+ _ <- (putter writeSize).unPut e sizePtr+ pure $ p :!: ()++resolveSize :: forall a. (Integral a, HasEndianness a) => (a -> Put ()) -> PutSize a -> a -> Put ()+resolveSize putter PutSize {..} manualSize = Put $ \e p -> do+ _ <- (putter manualSize).unPut e sizePtr+ pure $ p :!: ()++computeSize ::+ forall a.+ (Integral a, HasEndianness a) =>+ PutEnv ->+ Ptr Word8 ->+ Ptr Word8 ->+ Ptr Word8 ->+ IO a+computeSize env chunkStartPtr basePtr finalPtr = do+ (chunk :| chunks) <- readIORef env.chunks+ if chunkStartPtr == chunk.begin+ then pure . fromIntegral $! finalPtr `minusPtr` basePtr+ else pure $ loop chunks (finalPtr `minusPtr` chunk.begin)+ where+ loop :: [Chunk] -> Int -> a+ loop [] !_acc = throw PutSizeMissingStartChunk+ loop (chunk : _) !acc+ | chunk.begin == chunkStartPtr =+ fromIntegral $ acc + (chunk.end `minusPtr` basePtr)+ loop (chunk : rest) !acc =+ loop rest (acc + (chunk.end `minusPtr` chunk.begin))++resolveSizeExclusiveBE :: (Integral a, HasEndianness a) => PutSize a -> Put ()+resolveSizeExclusiveBE = resolveSizeExclusive unsafePutBE++resolveSizeExclusiveLE :: (Integral a, HasEndianness a) => PutSize a -> Put ()+resolveSizeExclusiveLE = resolveSizeExclusive unsafePutLE++resolveSizeInclusiveBE :: (Integral a, HasEndianness a) => PutSize a -> Put ()+resolveSizeInclusiveBE = resolveSizeInclusive unsafePutBE++resolveSizeInclusiveLE :: (Integral a, HasEndianness a) => PutSize a -> Put ()+resolveSizeInclusiveLE = resolveSizeInclusive unsafePutLE++resolveSizeLE :: (Integral a, HasEndianness a) => PutSize a -> a -> Put()+resolveSizeLE = resolveSize unsafePutLE++resolveSizeBE :: (Integral a, HasEndianness a) => PutSize a -> a -> Put()+resolveSizeBE = resolveSize unsafePutBE
src/Data/Persist/Internal.hs view
@@ -1,56 +1,79 @@ {-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-}-{-# LANGUAGE DefaultSignatures #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DeriveTraversable #-}-{-# LANGUAGE EmptyCase #-}-{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE KindSignatures #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE NoFieldSelectors #-} -module Data.Persist.Internal (- (:!:)(..)+module Data.Persist.Internal+ ( (:!:) (..)+ -- * The Get type- , Get(..)- , GetEnv(..)- , GetException(..)- , getOffset- , failGet- , runGet- , runGetIO+ , Get (..)+ , GetEnv (..)+ , GetException (..)+ , getOffset+ , failGet+ , runGet+ , runGetIO+ , unsafeGetPrefix -- * The Put type- , Put(..)- , PutEnv(..)- , Chunk(..)- , evalPut- , evalPutIO- , grow-) where+ , Put (..)+ , PutEnv (..)+ , PutException (..)+ , Chunk (..)+ , evalPut+ , evalPutStrictIO+ , evalPutLazy+ , evalPutLazyIO+ , grow + -- * Size reservations+ , PutSize (..)+ ) where+ import Control.Exception import Control.Monad+import qualified Control.Monad.Fail as Fail import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import qualified Data.ByteString.Internal as B+import qualified Data.ByteString.Lazy.Internal as BL+#if MIN_VERSION_base(4,20,0) import Data.Foldable (foldlM)+#else+import Data.Foldable (foldl', foldlM)+#endif import Data.IORef-import Data.List.NonEmpty (NonEmpty(..))+import Data.List.NonEmpty (NonEmpty (..)) import Data.Word-import Foreign (ForeignPtr, Ptr, plusPtr, minusPtr,- withForeignPtr, mallocBytes, free, allocaBytes)+import Foreign+ ( ForeignPtr+ , Ptr+ , allocaBytes+ , finalizerFree+ , free+ , mallocBytes+ , minusPtr+ , newForeignPtr+ , plusPtr+ , reallocBytes+ , withForeignPtr+ )+import Foreign.Marshal.Utils (copyBytes) import System.IO.Unsafe-import qualified Control.Monad.Fail as Fail-import qualified Data.ByteString.Internal as B #include "MachDeps.h" @@ -58,19 +81,19 @@ infixl 2 :!: data GetEnv = GetEnv- { geBuf :: !(ForeignPtr Word8)- , geBegin :: !(Ptr Word8)- , geEnd :: !(Ptr Word8)- , geTmp :: !(Ptr Word8)+ { buf :: !(ForeignPtr Word8)+ , begin :: {-# UNPACK #-} !(Ptr Word8)+ , end :: {-# UNPACK #-} !(Ptr Word8)+ , tmp :: {-# UNPACK #-} !(Ptr Word8) } newtype Get a = Get- { unGet :: GetEnv -> Ptr Word8 -> IO ((Ptr Word8) :!: a)+ { unGet :: GetEnv -> Ptr Word8 -> IO (Ptr Word8 :!: a) } instance Functor Get where fmap f m = Get $ \e p -> do- p' :!: x <- unGet m e p+ p' :!: x <- m.unGet e p pure $! p' :!: f x {-# INLINE fmap #-} @@ -79,8 +102,8 @@ {-# INLINE pure #-} f <*> a = Get $ \e p -> do- p' :!: f' <- unGet f e p- p'' :!: a' <- unGet a e p'+ p' :!: f' <- f.unGet e p+ p'' :!: a' <- a.unGet e p' pure $! p'' :!: f' a' {-# INLINE (<*>) #-} @@ -91,8 +114,8 @@ instance Monad Get where m >>= f = Get $ \e p -> do- p' :!: x <- unGet m e p- unGet (f x) e p'+ p' :!: x <- m.unGet e p+ (f x).unGet e p' {-# INLINE (>>=) #-} #if !MIN_VERSION_base(4,11,0)@@ -109,12 +132,18 @@ instance Exception GetException +data PutException+ = PutSizeMissingStartChunk+ deriving (Eq, Show)++instance Exception PutException+ instance Fail.MonadFail Get where fail msg = failGet GenericGetException ("Failed reading: " <> msg) {-# INLINE fail #-} getOffset :: Get Int-getOffset = Get $ \e p -> pure $! p :!: (p `minusPtr` (geBegin e))+getOffset = Get $ \e p -> pure $! p :!: (p `minusPtr` e.begin) {-# INLINE getOffset #-} failGet :: (Int -> String -> GetException) -> String -> Get a@@ -124,35 +153,45 @@ runGetIO :: Get a -> ByteString -> IO a runGetIO m s = run- where run = withForeignPtr buf $ \p -> allocaBytes 8 $ \t -> do- let env = GetEnv { geBuf = buf, geBegin = p, geEnd = p `plusPtr` (pos + len), geTmp = t }- _ :!: r <- unGet m env (p `plusPtr` pos)- pure r- (B.PS buf pos len) = s+ where+ run = withForeignPtr buf $ \p -> allocaBytes 8 $ \t -> do+ let env = GetEnv {buf, begin = p, end = p `plusPtr` (pos + len), tmp = t}+ _ :!: r <- m.unGet env (p `plusPtr` pos)+ pure r+ (B.PS buf pos len) = s -- | Run the Get monad applies a 'get'-based parser on the input ByteString runGet :: Get a -> ByteString -> Either String a-runGet m s = unsafePerformIO $ catch (Right <$!> (runGetIO m s)) handler- where handler (e :: GetException) = pure $ Left $ displayException e+runGet m s = unsafePerformIO $ catch (Right <$!> runGetIO m s) handler+ where+ handler (e :: GetException) = pure $ Left $ displayException e {-# NOINLINE runGet #-} +unsafeGetPrefix :: Int -> Get a -> Get a+unsafeGetPrefix prefixLength baseGet = Get $ \env p -> do+ let p' = p `plusPtr` prefixLength+ env' = (\GetEnv {..} -> GetEnv {end = p', ..}) env+ _ :!: r <- baseGet.unGet env' p+ pure $ p' :!: r+{-# INLINE unsafeGetPrefix #-}+ data Chunk = Chunk- { chkBegin :: !(Ptr Word8)- , chkEnd :: !(Ptr Word8)+ { begin :: {-# UNPACK #-} !(Ptr Word8)+ , end :: {-# UNPACK #-} !(Ptr Word8) } data PutEnv = PutEnv- { peChks :: !(IORef (NonEmpty Chunk))- , peEnd :: !(IORef (Ptr Word8))- , peTmp :: !(Ptr Word8)+ { chunks :: !(IORef (NonEmpty Chunk))+ , end :: !(IORef (Ptr Word8))+ , tmp :: {-# UNPACK #-} !(Ptr Word8) } newtype Put a = Put- { unPut :: PutEnv -> Ptr Word8 -> IO ((Ptr Word8) :!: a) }+ {unPut :: PutEnv -> Ptr Word8 -> IO (Ptr Word8 :!: a)} instance Functor Put where fmap f m = Put $ \e p -> do- p' :!: x <- unPut m e p+ p' :!: x <- m.unPut e p pure $! p' :!: f x {-# INLINE fmap #-} @@ -161,8 +200,8 @@ {-# INLINE pure #-} f <*> a = Put $ \e p -> do- p' :!: f' <- unPut f e p- p'' :!: a' <- unPut a e p'+ p' :!: f' <- f.unPut e p+ p'' :!: a' <- a.unPut e p' pure $! p'' :!: f' a' {-# INLINE (<*>) #-} @@ -173,10 +212,16 @@ instance Monad Put where m >>= f = Put $ \e p -> do- p' :!: x <- unPut m e p- unPut (f x) e p'+ p' :!: x <- m.unPut e p+ (f x).unPut e p' {-# INLINE (>>=) #-} +data PutSize a = PutSize+ { sizePtr :: !(Ptr Word8)+ , sizeStart :: !(Ptr Word8)+ , chunkStart :: !(Ptr Word8)+ }+ minChunkSize :: Int minChunkSize = 0x10000 {-# INLINE minChunkSize #-}@@ -193,47 +238,100 @@ grow n | n < 0 = error "grow: negative length" | otherwise = Put $ \e p -> do- end <- readIORef (peEnd e)- if end `minusPtr` p >= n then- pure $! p :!: ()- else- doGrow e p n+ end <- readIORef e.end+ if end `minusPtr` p >= n+ then+ pure $! p :!: ()+ else+ doGrow e p n {-# INLINE grow #-} -doGrow :: PutEnv -> Ptr Word8 -> Int -> IO ((Ptr Word8) :!: ())+doGrow :: PutEnv -> Ptr Word8 -> Int -> IO (Ptr Word8 :!: ()) doGrow e p n = do k <- newChunk n- modifyIORef' (peChks e) $ \case- (c:|cs) -> k :| c { chkEnd = p } : cs- writeIORef (peEnd e) (chkEnd k)- pure $! chkBegin k :!: ()+ modifyIORef' e.chunks $ \case+ (c :| cs) ->+ let !c' = (\Chunk {..} -> Chunk {end = p, ..}) c+ in k :| c' : cs+ writeIORef e.end $! k.end+ pure $! k.begin :!: () {-# NOINLINE doGrow #-} chunksLength :: [Chunk] -> Int-chunksLength = foldr (\c s -> s + chkEnd c `minusPtr` chkBegin c) 0+chunksLength = foldl' (\s c -> s + c.end `minusPtr` c.begin) 0 {-# INLINE chunksLength #-} catChunks :: [Chunk] -> IO ByteString catChunks chks = B.create (chunksLength chks) $ \p ->- void $ foldlM (\q c -> do- let n = chkEnd c `minusPtr` chkBegin c- B.memcpy q (chkBegin c) n- free $ chkBegin c- pure (q `plusPtr` n)) p $ reverse chks+ void+ $ foldlM+ ( \q c -> do+ let n = c.end `minusPtr` c.begin+ copyBytes q c.begin n+ free c.begin+ pure (q `plusPtr` n)+ )+ p+ $ reverse chks {-# INLINE catChunks #-} -evalPutIO :: Put a -> IO (a, ByteString)-evalPutIO p = do+evalPutIO :: Put a -> (a -> NonEmpty Chunk -> IO (a, b)) -> IO (a, b)+evalPutIO p chunkConsumer = do k <- newChunk 0- chks <- newIORef (k:|[])- end <- newIORef (chkEnd k)- p' :!: r <- allocaBytes 8 $ \t ->- unPut p PutEnv { peChks = chks, peEnd = end, peTmp = t } (chkBegin k)- cs <- readIORef chks- s <- case cs of- (x:|xs) -> catChunks $ x { chkEnd = p' } : xs- pure (r, s)+ chunks <- newIORef (k :| [])+ curEnd <- newIORef k.end+ p' :!: r <- allocaBytes 8 $ \tmp ->+ p.unPut PutEnv {chunks, end = curEnd, tmp} k.begin+ cs <- readIORef chunks+ case cs of+ (x :| xs) -> do+ let !x' = (\Chunk {..} -> Chunk {end = p', ..}) x+ chunkConsumer r (x' :| xs)+{-# INLINE evalPutIO #-} +evalPutStrictIO :: Put a -> IO (a, ByteString)+evalPutStrictIO p = evalPutIO p chunkHandler+ where+ chunkHandler r cs = do+ s <- case cs of+ (x :| []) -> singleChunk x+ (x :| xs) -> catChunks (x : xs)+ pure (r, s)+ singleChunk Chunk {..} = do+ case end `minusPtr` begin of+ 0 -> do+ free begin+ pure B.empty+ newSize -> do+ newPtr <- reallocBytes begin newSize+ foreignNewPtr <- newForeignPtr finalizerFree newPtr+ pure $ B.BS foreignNewPtr newSize+{-# INLINE evalPutStrictIO #-}++evalPutLazyIO :: Put a -> IO (a, BL.ByteString)+evalPutLazyIO p = evalPutIO p chunkHandler+ where+ chunkHandler r cs = do+ s <- case cs of+ (x :| xs) -> foldlM makeLBSChunk BL.Empty (x : xs)+ pure (r, s)+ makeLBSChunk :: BL.ByteString -> Chunk -> IO BL.ByteString+ makeLBSChunk lbsTail Chunk {..} = do+ case end `minusPtr` begin of+ 0 -> do+ free begin+ pure lbsTail+ newSize -> do+ newPtr <- reallocBytes begin newSize+ foreignNewPtr <- newForeignPtr finalizerFree newPtr+ let strictChunk = B.BS foreignNewPtr newSize+ pure $ BL.Chunk strictChunk lbsTail+{-# INLINE evalPutLazyIO #-}+ evalPut :: Put a -> (a, ByteString)-evalPut p = unsafePerformIO $ evalPutIO p+evalPut p = unsafePerformIO $ evalPutStrictIO p {-# NOINLINE evalPut #-}++evalPutLazy :: Put a -> (a, BL.ByteString)+evalPutLazy p = unsafePerformIO $ evalPutLazyIO p+{-# NOINLINE evalPutLazy #-}
tests/RoundTrip.hs view
@@ -1,102 +1,184 @@+{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} ----------------------------------------------------------------------------------- |--- Module :--- Copyright : (c) Galois, Inc, 2009--- License : BSD3------ Maintainer : Trevor Elliott <trevor@galois.com>--- Stability :--- Portability :---++{- |+Module :+Copyright : (c) Galois, Inc, 2009+License : BSD3++Maintainer : Trevor Elliott <trevor@galois.com>+Stability :+Portability :+-} module RoundTrip where -import Numeric.Natural+import qualified Data.ByteString as BS (length)+import qualified Data.ByteString.Char8 as B8 (unpack)+import qualified Data.ByteString.Lazy as LBS (toStrict)+import Data.Int import Data.Persist import Data.Text (Text) import qualified Data.Text as T import Data.Word-import Data.Int-import System.Exit (ExitCode(..), exitSuccess, exitWith)-import Test.QuickCheck as QC--import Test.Framework (Test(),testGroup)+import Numeric.Natural+import System.Exit (ExitCode (..), exitSuccess, exitWith)+import Test.Framework (Test (), testGroup) import Test.Framework.Providers.QuickCheck2 (testProperty)+import Test.QuickCheck as QC roundTrip :: (Persist a, Eq a) => (a -> Put ()) -> Get a -> a -> Bool-roundTrip p g a = res == Right a- where res = runGet (g <* eof) (runPut (p a))+roundTrip p g a = res == Right a && lazyRes == Right a+ where+ res = runGet (g <* eof) (runPut (p a))+ lazyRes = runGet (g <* eof) (LBS.toStrict (runPutLazy (p a))) +roundTripLengthInclusiveLE ::+ forall a l.+ (Persist a, Eq a, Eq l, HasEndianness l, Integral l) =>+ (a -> Put ()) ->+ Get a ->+ a ->+ Bool+roundTripLengthInclusiveLE putter getter val =+ result == Right (putterLength, val)+ where+ result = flip runGet putResult $ (,) <$> (getLE @l) <*> (getter <* eof)+ putResult = runPut $ do+ sizeSlot <- reserveSize @l+ putter val+ resolveSizeInclusiveLE sizeSlot+ putterLength = fromIntegral @_ @l $ BS.length putResult++roundTripLengthInclusiveBE ::+ forall a l.+ (Persist a, Eq a, Eq l, HasEndianness l, Integral l) =>+ (a -> Put ()) ->+ Get a ->+ a ->+ Bool+roundTripLengthInclusiveBE putter getter val =+ result == Right (putterLength, val)+ where+ result = flip runGet putResult $ (,) <$> (getBE @l) <*> (getter <* eof)+ putResult = runPut $ do+ sizeSlot <- reserveSize @l+ putter val+ resolveSizeInclusiveBE sizeSlot+ putterLength = fromIntegral @_ @l $ BS.length putResult++roundTripLengthExclusiveLE ::+ forall a l.+ (Persist a, Eq a, Eq l, HasEndianness l, Integral l) =>+ (a -> Put ()) ->+ Get a ->+ a ->+ Bool+roundTripLengthExclusiveLE putter getter val =+ result == Right (putterLength, val)+ where+ result = flip runGet putResult $ (,) <$> (getLE @l) <*> (getter <* eof)+ putResult = runPut $ do+ sizeSlot <- reserveSize @l+ putter val+ resolveSizeExclusiveLE sizeSlot+ putterLength = fromIntegral @_ @l $ BS.length putResult - endiannessSize @l++roundTripLengthExclusiveBE ::+ forall a l.+ (Persist a, Eq a, Eq l, HasEndianness l, Integral l) =>+ (a -> Put ()) ->+ Get a ->+ a ->+ Bool+roundTripLengthExclusiveBE putter getter val =+ result == Right (putterLength, val)+ where+ result = flip runGet putResult $ (,) <$> (getBE @l) <*> (getter <* eof)+ putResult = runPut $ do+ sizeSlot <- reserveSize @l+ putter val+ resolveSizeExclusiveBE sizeSlot+ putterLength = fromIntegral @_ @l $ BS.length putResult - endiannessSize @l+ -- | Did a call to 'quickCheckResult' succeed? isSuccess :: QC.Result -> Bool-isSuccess Success{} = True+isSuccess Success {} = True isSuccess _ = False tests :: Test-tests = testGroup "Round Trip"- [ testProperty "Word8 Round Trip" $ roundTrip put (get @Word8)- , testProperty "Word16 Round Trip" $ roundTrip put (get @Word16)- , testProperty "Word16be Round Trip" $ roundTrip putBE (getBE @Word16)- , testProperty "Word16le Round Trip" $ roundTrip putLE (getLE @Word16)- , testProperty "Word16host Round Trip" $ roundTrip putHE (getHE @Word16)- , testProperty "Word32 Round Trip" $ roundTrip put (get @Word32)- , testProperty "Word32be Round Trip" $ roundTrip putBE (getBE @Word32)- , testProperty "Word32le Round Trip" $ roundTrip putLE (getLE @Word32)- , testProperty "Word32host Round Trip" $ roundTrip putHE (getHE @Word32)- , testProperty "Word64 Round Trip" $ roundTrip put (get @Word64)- , testProperty "Word64be Round Trip" $ roundTrip putBE (getBE @Word64)- , testProperty "Word64le Round Trip" $ roundTrip putLE (getLE @Word64)- , testProperty "Word64host Round Trip" $ roundTrip putHE (getHE @Word64)-- , testProperty "Int8 Round Trip" $ roundTrip put (get @Int8)- , testProperty "Int16 Round Trip" $ roundTrip put (get @Int16)- , testProperty "Int16be Round Trip" $ roundTrip putBE (getBE @Int16)- , testProperty "Int16le Round Trip" $ roundTrip putLE (getLE @Int16)- , testProperty "Int16host Round Trip" $ roundTrip putHE (getHE @Int16)- , testProperty "Int32 Round Trip" $ roundTrip put (get @Int32)- , testProperty "Int32be Round Trip" $ roundTrip putBE (getBE @Int32)- , testProperty "Int32le Round Trip" $ roundTrip putLE (getLE @Int32)- , testProperty "Int32host Round Trip" $ roundTrip putHE (getHE @Int32)- , testProperty "Int64 Round Trip" $ roundTrip put (get @Int64)- , testProperty "Int64be Round Trip" $ roundTrip putBE (getBE @Int64)- , testProperty "Int64le Round Trip" $ roundTrip putLE (getLE @Int64)- , testProperty "Int64host Round Trip" $ roundTrip putHE (getHE @Int64)-- , testProperty "Float Round Trip" $ roundTrip put (get @Float)- , testProperty "Floatbe Round Trip" $ roundTrip putBE (getBE @Float)- , testProperty "Floatle Round Trip" $ roundTrip putLE (getLE @Float)- , testProperty "Floathost Round Trip" $ roundTrip putHE (getHE @Float)- , testProperty "Double Round Trip" $ roundTrip put (get @Double)- , testProperty "Doublebe Round Trip" $ roundTrip putBE (getBE @Double)- , testProperty "Doublele Round Trip" $ roundTrip putLE (getLE @Double)- , testProperty "Doublehost Round Trip" $ roundTrip putHE (getHE @Double)-- , testProperty "Char Round Trip"- $ roundTrip put (get :: Get Char)- , testProperty "String Round Trip"- $ roundTrip put (get :: Get String)- , testProperty "Text Round Trip"- $ roundTrip put get . T.pack- , testProperty "Integer Round Trip"- $ roundTrip put (get :: Get Integer)- , testProperty "Natural Round Trip"- $ roundTrip put get . (fromInteger :: Integer -> Natural) . abs- , testProperty "(Word8,Word8) Round Trip"- $ roundTrip put (get :: Get (Word8, Word8))- , testProperty "(Word8,Word16,Word32,Word64) Round Trip"- $ roundTrip put (get :: Get (Word8, Word16, Word32, Word64))- , testProperty "Complex Round Trip"- $ roundTrip put (get :: Get (Either (Word8, Word8) (Word16, Either Int32 [String], Word64)))- , testProperty "[Word8] Round Trip"- $ roundTrip put (get :: Get [Word8])- , testProperty "Bool Round Trip"- $ roundTrip put (get :: Get Bool)- , testProperty "Ordering Round Trip"- $ roundTrip put (get :: Get Ordering)- , testProperty "Maybe Word8 Round Trip"- $ roundTrip put (get :: Get (Maybe Word8))- , testProperty "Either Word8 Word16 Round Trip"- $ roundTrip put (get :: Get (Either Word8 Word16))- ]+tests =+ testGroup+ "Round Trip"+ [ testProperty "Word8 Round Trip" $ roundTrip put (get @Word8)+ , testProperty "Word16 Round Trip" $ roundTrip put (get @Word16)+ , testProperty "Word16be Round Trip" $ roundTrip putBE (getBE @Word16)+ , testProperty "Word16le Round Trip" $ roundTrip putLE (getLE @Word16)+ , testProperty "Word16host Round Trip" $ roundTrip putHE (getHE @Word16)+ , testProperty "Word32 Round Trip" $ roundTrip put (get @Word32)+ , testProperty "Word32be Round Trip" $ roundTrip putBE (getBE @Word32)+ , testProperty "Word32le Round Trip" $ roundTrip putLE (getLE @Word32)+ , testProperty "Word32host Round Trip" $ roundTrip putHE (getHE @Word32)+ , testProperty "Word64 Round Trip" $ roundTrip put (get @Word64)+ , testProperty "Word64be Round Trip" $ roundTrip putBE (getBE @Word64)+ , testProperty "Word64le Round Trip" $ roundTrip putLE (getLE @Word64)+ , testProperty "Word64host Round Trip" $ roundTrip putHE (getHE @Word64)+ , testProperty "Int8 Round Trip" $ roundTrip put (get @Int8)+ , testProperty "Int16 Round Trip" $ roundTrip put (get @Int16)+ , testProperty "Int16be Round Trip" $ roundTrip putBE (getBE @Int16)+ , testProperty "Int16le Round Trip" $ roundTrip putLE (getLE @Int16)+ , testProperty "Int16host Round Trip" $ roundTrip putHE (getHE @Int16)+ , testProperty "Int32 Round Trip" $ roundTrip put (get @Int32)+ , testProperty "Int32be Round Trip" $ roundTrip putBE (getBE @Int32)+ , testProperty "Int32le Round Trip" $ roundTrip putLE (getLE @Int32)+ , testProperty "Int32host Round Trip" $ roundTrip putHE (getHE @Int32)+ , testProperty "Int64 Round Trip" $ roundTrip put (get @Int64)+ , testProperty "Int64be Round Trip" $ roundTrip putBE (getBE @Int64)+ , testProperty "Int64le Round Trip" $ roundTrip putLE (getLE @Int64)+ , testProperty "Int64host Round Trip" $ roundTrip putHE (getHE @Int64)+ , testProperty "Float Round Trip" $ roundTrip put (get @Float)+ , testProperty "Floatbe Round Trip" $ roundTrip putBE (getBE @Float)+ , testProperty "Floatle Round Trip" $ roundTrip putLE (getLE @Float)+ , testProperty "Floathost Round Trip" $ roundTrip putHE (getHE @Float)+ , testProperty "Double Round Trip" $ roundTrip put (get @Double)+ , testProperty "Doublebe Round Trip" $ roundTrip putBE (getBE @Double)+ , testProperty "Doublele Round Trip" $ roundTrip putLE (getLE @Double)+ , testProperty "Doublehost Round Trip" $ roundTrip putHE (getHE @Double)+ , testProperty "Char Round Trip" $+ roundTrip put (get :: Get Char)+ , testProperty "String Round Trip" $+ roundTrip put (get :: Get String)+ , testProperty "Text Round Trip" $+ roundTrip put get . T.pack+ , testProperty "Integer Round Trip" $+ roundTrip put (get :: Get Integer)+ , testProperty "Natural Round Trip" $+ roundTrip put get . (fromInteger :: Integer -> Natural) . abs+ , testProperty "(Word8,Word8) Round Trip" $+ roundTrip put (get :: Get (Word8, Word8))+ , testProperty "(Word8,Word16,Word32,Word64) Round Trip" $+ roundTrip put (get :: Get (Word8, Word16, Word32, Word64))+ , testProperty "Complex Round Trip" $+ roundTrip put (get :: Get (Either (Word8, Word8) (Word16, Either Int32 [String], Word64)))+ , testProperty "[Word8] Round Trip" $+ roundTrip put (get :: Get [Word8])+ , testProperty "Bool Round Trip" $+ roundTrip put (get :: Get Bool)+ , testProperty "Ordering Round Trip" $+ roundTrip put (get :: Get Ordering)+ , testProperty "Maybe Word8 Round Trip" $+ roundTrip put (get :: Get (Maybe Word8))+ , testProperty "Either Word8 Word16 Round Trip" $+ roundTrip put (get :: Get (Either Word8 Word16))+ , testProperty "Sized LE Inclusive roundTrip for (Text,Text,Text) and Word64" $+ roundTripLengthInclusiveLE @_ @Word64 put get . (\(s1, s2, s3) -> (T.pack s1, T.pack s2, T.pack s3))+ , testProperty "Sized BE Inclusive roundTrip for (Text,Text,Text) and Word64" $+ roundTripLengthInclusiveBE @_ @Word64 put get . (\(s1, s2, s3) -> (T.pack s1, T.pack s2, T.pack s3))+ , testProperty "Sized LE Exclusive roundTrip for (Text,Text,Text) and Word64" $+ roundTripLengthExclusiveLE @_ @Word64 put get . (\(s1, s2, s3) -> (T.pack s1, T.pack s2, T.pack s3))+ , testProperty "Sized BE Exclusive roundTrip for (Text,Text,Text) and Word64" $+ roundTripLengthExclusiveBE @_ @Word64 put get . (\(s1, s2, s3) -> (T.pack s1, T.pack s2, T.pack s3))+ ]