packages feed

bytesmith 0.2.0.1 → 0.3.0.0

raw patch · 8 files changed

+333/−102 lines, 8 filesdep ~byteslicePVP ok

version bump matches the API change (PVP)

Dependency ranges changed: byteslice

API changes (from Hackage documentation)

- Data.Bytes.Parser: [Parser] :: forall (r :: RuntimeRep) (e :: Type) (s :: Type) (a :: TYPE r). {runParser :: (# ByteArray#, Int#, Int# #) -> ST# s (Result# e a)} -> Parser e s a
- Data.Bytes.Parser: instance (GHC.Classes.Eq e, GHC.Classes.Eq a) => GHC.Classes.Eq (Data.Bytes.Parser.Result e a)
- Data.Bytes.Parser: instance (GHC.Show.Show e, GHC.Show.Show a) => GHC.Show.Show (Data.Bytes.Parser.Result e a)
- Data.Bytes.Parser: newtype Parser :: forall (r :: RuntimeRep). Type -> Type -> TYPE r -> Type
- Data.Bytes.Parser: parseBytesST :: Parser e s a -> Bytes -> ST s (Result e a)
- Data.Bytes.Parser.Latin: hexWord16 :: e -> Parser e s Word16
+ Data.Bytes.Parser: Slice :: {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> a -> Slice a
+ Data.Bytes.Parser: data Parser :: forall (r :: RuntimeRep). Type -> Type -> TYPE r -> Type
+ Data.Bytes.Parser: data Slice a
+ Data.Bytes.Parser: parseBytesEffectfully :: Parser e s a -> Bytes -> ST s (Result e a)
+ Data.Bytes.Parser: skipTrailedBy :: e -> Word8 -> Parser e s ()
+ Data.Bytes.Parser: takeTrailedBy :: e -> Word8 -> Parser e s Bytes
+ Data.Bytes.Parser.Latin: decWord64 :: e -> Parser e s Word64
+ Data.Bytes.Parser.Latin: hexFixedWord16 :: e -> Parser e s Word16
+ Data.Bytes.Parser.Latin: hexNibble :: e -> Parser e s Word
+ Data.Bytes.Parser.Latin: hexNibbleLower :: e -> Parser e s Word
+ Data.Bytes.Parser.Latin: tryHexNibble :: Parser e s (Maybe Word)
+ Data.Bytes.Parser.Latin: tryHexNibbleLower :: Parser e s (Maybe Word)
+ Data.Bytes.Parser.Unsafe: [Parser] :: forall (r :: RuntimeRep) (e :: Type) (s :: Type) (a :: TYPE r). {runParser :: (# ByteArray#, Int#, Int# #) -> ST# s (Result# e a)} -> Parser e s a
+ Data.Bytes.Parser.Unsafe: newtype Parser :: forall (r :: RuntimeRep). Type -> Type -> TYPE r -> Type
- Data.Bytes.Parser: Success :: !a -> !Int -> Result e a
+ Data.Bytes.Parser: Success :: {-# UNPACK #-} !Slice a -> Result e a

Files

CHANGELOG.md view
@@ -1,5 +1,13 @@ # Revision history for bytesmith +## 0.3.0.0 -- 2019-??-??++* Include the offset into the byte sequence in `Result`. Breaking change.+* Rename `hexWord16` to `hexFixedWord16`. Breaking change.+* Rename `parseBytesST` to `parseBytesEffectfully`. Breaking change.+* Add `hexNibbleLower` and `tryHexNibbleLower`.+* Add `hexNibble` and `tryHexNibble`.+ ## 0.2.0.1 -- 2019-09-24  * Correct an overflow-detection mistake in the implementation
bytesmith.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: bytesmith-version: 0.2.0.1+version: 0.3.0.0 synopsis: Nonresumable byte parser description:   Parse bytes as fast as possible. This is a nonresumable parser@@ -26,6 +26,7 @@     Data.Bytes.Parser.Utf8   other-modules:     Data.Bytes.Parser.Internal+    Data.Bytes.Parser.Types   build-depends:     , base >=4.12 && <5     , bytestring >=0.10.8 && <0.11
src/Data/Bytes/Parser.hs view
@@ -22,19 +22,22 @@ -- modules. module Data.Bytes.Parser   ( -- * Types-    Parser(..)+    Parser   , Result(..)+  , Slice(..)     -- * Run Parsers   , parseByteArray   , parseBytes-  , parseBytesST+  , parseBytesEffectfully     -- * One Byte   , any     -- * Many Bytes   , take   , takeWhile+  , takeTrailedBy     -- * Skip   , skipWhile+  , skipTrailedBy     -- * Match   , byteArray   , bytes@@ -80,12 +83,15 @@  import Prelude hiding (length,any,fail,takeWhile,take,replicate) -import Data.Bytes.Parser.Internal (InternalResult(..),Parser(..),unboxBytes,boxBytes,Result#,uneffectful,fail)-import Data.Bytes.Parser.Unsafe (unconsume)+import Data.Bytes.Parser.Internal (InternalResult(..),Parser(..),ST#,unboxBytes)+import Data.Bytes.Parser.Internal (boxBytes,Result#,uneffectful,fail)+import Data.Bytes.Parser.Internal (uneffectful#)+import Data.Bytes.Parser.Types (Result(Failure,Success),Slice(Slice))+import Data.Bytes.Parser.Unsafe (unconsume,expose,cursor) import Data.Bytes.Types (Bytes(..)) import Data.Primitive (ByteArray(..))-import GHC.Exts (Int(I#),Word#,Int#,Char#,(+#),(-#),(>=#))-import GHC.ST (ST(..),runST)+import GHC.Exts (Int(I#),Word#,Int#,Char#,runRW#,(+#),(-#),(>=#))+import GHC.ST (ST(..)) import GHC.Word (Word32(W32#),Word8) import Data.Primitive.Contiguous (Contiguous,Element) @@ -93,27 +99,25 @@ import qualified Data.Primitive as PM import qualified Data.Primitive.Contiguous as C --- | The result of running a parser.-data Result e a-  = Failure e-    -- ^ An error message indicating what went wrong.-  | Success !a !Int-    -- ^ The parsed value and the number of bytes-    -- remaining in parsed slice.-  deriving (Eq,Show)- -- | Parse a slice of a byte array. This can succeed even if the -- entire slice was not consumed by the parser. parseBytes :: forall e a. (forall s. Parser e s a) -> Bytes -> Result e a-parseBytes p !b = runST action+parseBytes p !b = runResultST action   where-  action :: forall s. ST s (Result e a)-  action = case p @s of-    Parser f -> ST-      (\s0 -> case f (unboxBytes b) s0 of-        (# s1, r #) -> (# s1, boxPublicResult r #)-      )+  action :: forall s. ST# s (Result# e a)+  action s0 = case p @s of+    Parser f -> f (unboxBytes b) s0 +-- This is used internally to help reduce boxing when a parser+-- gets run. Due to the late inlining of runRW#, this variant+-- of runST still cause the result value to be boxed. However,+-- it avoids the additional boxing that the Success data+-- constructor would normally cause.+runResultST :: (forall s. ST# s (Result# e x)) -> Result e x+runResultST f = case (runRW# (\s0 -> case f s0 of { (# _, r #) -> r })) of+  (# e | #) -> Failure e+  (# | (# x, off, len #) #) -> Success (Slice (I# off) (I# len) x)+ -- | Variant of 'parseBytes' that accepts an unsliced 'ByteArray'. parseByteArray :: (forall s. Parser e s a) -> ByteArray -> Result e a parseByteArray p b =@@ -121,8 +125,8 @@  -- | Variant of 'parseBytes' that allows the parser to be run -- as part of an existing effectful context.-parseBytesST :: Parser e s a -> Bytes -> ST s (Result e a)-parseBytesST (Parser f) !b = ST+parseBytesEffectfully :: Parser e s a -> Bytes -> ST s (Result e a)+parseBytesEffectfully (Parser f) !b = ST   (\s0 -> case f (unboxBytes b) s0 of     (# s1, r #) -> (# s1, boxPublicResult r #)   )@@ -183,6 +187,38 @@ takeWhile f = uneffectful $ \chunk -> case B.takeWhile f chunk of   bs -> InternalSuccess bs (offset chunk + length bs) (length chunk - length bs) +-- | Take bytes until the specified byte is encountered. Consumes+-- the matched byte as well. Fails if the byte is not present.+-- Visually, the cursor advancement and resulting @Bytes@ for+-- @takeTrailedBy 0x19@ look like this:+--+-- >  0x10 0x13 0x08 0x15 0x19 0x23 0x17 | input+-- > |---->---->---->---->----|          | cursor+-- > {----*----*----*----}               | result bytes+takeTrailedBy :: e -> Word8 -> Parser e s Bytes+takeTrailedBy e !w = do+  !start <- cursor+  skipTrailedBy e w+  !end <- cursor+  !arr <- expose+  pure (Bytes arr start (end - start))++-- | Skip all characters until the character from the is encountered+-- and then consume the matching byte as well.+skipTrailedBy :: e -> Word8 -> Parser e s ()+skipTrailedBy e !w = uneffectful# (\c -> skipUntilConsumeByteLoop e w c)++skipUntilConsumeByteLoop ::+     e -- Error message+  -> Word8 -- byte to match+  -> Bytes -- Chunk+  -> Result# e ()+skipUntilConsumeByteLoop e !w !c = if length c > 0+  then if PM.indexByteArray (array c) (offset c) /= (w :: Word8)+    then skipUntilConsumeByteLoop e w (B.unsafeDrop 1 c)+    else (# | (# (), unI (offset c + 1), unI (length c - 1) #) #)+  else (# e | #)+ -- | Take the given number of bytes. Fails if there is not enough --   remaining input. take :: e -> Int -> Parser e s Bytes@@ -226,7 +262,7 @@   InternalSuccess (length chunk == 0) (offset chunk) (length chunk)  boxPublicResult :: Result# e a -> Result e a-boxPublicResult (# | (# a, _, c #) #) = Success a (I# c)+boxPublicResult (# | (# a, b, c #) #) = Success (Slice (I# b) (I# c) a) boxPublicResult (# e | #) = Failure e  -- | Convert a 'Word32' parser to a 'Word#' parser.@@ -386,9 +422,9 @@ -- | Run a parser in a delimited context, failing if the requested number -- of bytes are not available or if the delimited parser does not -- consume all input. This combinator can be understood as a composition--- of 'take', 'effect', 'parseBytesST', and 'endOfInput'. It is provided as--- a single combinator because for convenience and because it is easy--- make mistakes when manually assembling the aforementioned parsers.+-- of 'take', 'effect', 'parseBytesEffectfully', and 'endOfInput'. It is+-- provided as a single combinator because for convenience and because it is+-- easy to make mistakes when manually assembling the aforementioned parsers. -- The pattern of prefixing an encoding with its length is common. -- This is discussed more in -- <https://github.com/bos/attoparsec/issues/129 attoparsec issue #129>.@@ -430,3 +466,6 @@           go (ix + 1)         else effect (C.unsafeFreeze marr)   go 0++unI :: Int -> Int#+unI (I# w) = w
src/Data/Bytes/Parser/Internal.hs view
@@ -19,9 +19,11 @@ module Data.Bytes.Parser.Internal   ( Parser(..)   , InternalResult(..)+  , InternalStep(..)   , Bytes#   , ST#   , Result#+  , unfailing   , uneffectful   , uneffectful#   , boxBytes@@ -57,10 +59,19 @@     -- The parsed value, the offset after the last consumed byte, and the     -- number of bytes remaining in parsed slice. +data InternalStep a = InternalStep !a !Int !Int+ uneffectful :: (Bytes -> InternalResult e a) -> Parser e s a {-# inline uneffectful #-} uneffectful f = Parser   ( \b s0 -> (# s0, unboxResult (f (boxBytes b)) #) )++-- This is like uneffectful but for parsers that always succeed.+-- These combinators typically have names that begin with @try@.+unfailing :: (Bytes -> InternalStep a) -> Parser e s a+{-# inline unfailing #-}+unfailing f = Parser+  ( \b s0 -> (# s0, case f (boxBytes b) of { InternalStep a (I# off) (I# len) -> (# | (# a, off, len #) #) } #) )  boxBytes :: Bytes# -> Bytes {-# inline boxBytes #-}
src/Data/Bytes/Parser/Latin.hs view
@@ -46,6 +46,7 @@   , decWord8   , decWord16   , decWord32+  , decWord64     -- *** Signed   , decUnsignedInt   , decUnsignedInt#@@ -57,13 +58,18 @@   , decUnsignedInteger   , decTrailingInteger     -- ** Hexadecimal-  , hexWord16+  , hexFixedWord16+  , hexNibbleLower+  , tryHexNibbleLower+  , hexNibble+  , tryHexNibble   ) where  import Prelude hiding (length,any,fail,takeWhile)  import Data.Bits ((.|.)) import Data.Bytes.Types (Bytes(..))+import Data.Bytes.Parser.Internal (InternalStep(..),unfailing) import Data.Bytes.Parser.Internal (Parser(..),ST#,uneffectful,Result#,uneffectful#) import Data.Bytes.Parser.Internal (InternalResult(..),indexLatinCharArray,upcastUnitSuccess) import Data.Bytes.Parser.Internal (boxBytes)@@ -74,7 +80,7 @@ import GHC.Exts (Int(I#),Char(C#),Word#,Int#,Char#,(+#),(-#),indexCharArray#) import GHC.Exts (TYPE,RuntimeRep,int2Word#,or#) import GHC.Exts (ltWord#,gtWord#,notI#)-import GHC.Word (Word(W#),Word8(W8#),Word16(W16#),Word32(W32#))+import GHC.Word (Word(W#),Word8(W8#),Word16(W16#),Word32(W32#),Word64(W64#))  import qualified GHC.Exts as Exts import qualified Data.Bytes as Bytes@@ -171,7 +177,7 @@      in InternalSuccess c (offset chunk + 1) (length chunk - 1)   else InternalFailure e --- | Consume a character from the input or return Nothing if+-- | Consume a character from the input or return @Nothing@ if -- end of the stream has been reached. Since ISO 8859-1 maps every -- bytes to a character, this parser never fails. opt :: Parser e s (Maybe Char)@@ -295,6 +301,16 @@     (# s1, r #) -> (# s1, upcastWordResult r #)   ) +-- | Parse a decimal-encoded unsigned number. If the number is+-- too large to be represented by a 64-bit word, this fails with+-- the provided error message. This accepts any number of leading+-- zeroes.+decWord64 :: e -> Parser e s Word64+decWord64 e = Parser+  (\chunk0 s0 -> case decWordStart e (boxBytes chunk0) s0 of+    (# s1, r #) -> (# s1, upcastWord64Result r #)+  )+ decSmallWordStart ::      e -- Error message   -> Word -- Upper Bound@@ -316,21 +332,30 @@   -> Word -- Accumulator   -> Bytes -- Chunk   -> Result# e Word#-decWordMore e !acc !chunk0 = if length chunk0 > 0-  then+decWordMore e !acc !chunk0 = case len of+  0 -> (# | (# unW (fromIntegral acc), unI (offset chunk0), 0# #) #)+  _ ->     let !w = fromIntegral @Word8 @Word           (PM.indexByteArray (array chunk0) (offset chunk0)) - 48-        !acc' = acc * 10 + w-     in if w < 10 && acc' >= acc-          then decWordMore e acc' (Bytes.unsafeDrop 1 chunk0)-          else (# | (# unW acc, unI (offset chunk0), unI (length chunk0)  #) #)-  else (# | (# unW acc, unI (offset chunk0), 0# #) #)-+     in if w < 10+          then+            let (overflow,acc') = unsignedPushBase10 acc w+             in if overflow+               then (# e | #)+               else decWordMore e acc' (Bytes.unsafeDrop 1 chunk0)+          else (# | (# unW (fromIntegral acc), unI (offset chunk0), len# #) #)+  where+  !len@(I# len# ) = length chunk0  upcastWordResult :: Result# e Word# -> Result# e Word upcastWordResult (# e | #) = (# e | #) upcastWordResult (# | (# a, b, c #) #) = (# | (# W# a, b, c #) #) +-- This only works on 64-bit platforms.+upcastWord64Result :: Result# e Word# -> Result# e Word64+upcastWord64Result (# e | #) = (# e | #)+upcastWord64Result (# | (# a, b, c #) #) = (# | (# W64# a, b, c #) #)+ decSmallWordMore ::      e -- Error message   -> Word -- Accumulator@@ -674,18 +699,18 @@ -- This is insensitive to case. This is particularly useful when -- parsing escape sequences in C or JSON, which allow encoding -- characters in the Basic Multilingual Plane as @\\uhhhh@.-hexWord16 :: e -> Parser e s Word16-{-# inline hexWord16 #-}-hexWord16 e = Parser-  (\x s0 -> case runParser (hexWord16# e) x s0 of+hexFixedWord16 :: e -> Parser e s Word16+{-# inline hexFixedWord16 #-}+hexFixedWord16 e = Parser+  (\x s0 -> case runParser (hexFixedWord16# e) x s0 of     (# s1, r #) -> case r of       (# err | #) -> (# s1, (# err | #) #)       (# | (# a, b, c #) #) -> (# s1, (# | (# W16# a, b, c #) #) #)   ) -hexWord16# :: e -> Parser e s Word#-{-# noinline hexWord16# #-}-hexWord16# e = uneffectfulWord# $ \chunk -> if length chunk >= 4+hexFixedWord16# :: e -> Parser e s Word#+{-# noinline hexFixedWord16# #-}+hexFixedWord16# e = uneffectfulWord# $ \chunk -> if length chunk >= 4   then     let !w0@(W# n0) = oneHex $ PM.indexByteArray (array chunk) (offset chunk)         !w1@(W# n1) = oneHex $ PM.indexByteArray (array chunk) (offset chunk + 1)@@ -702,7 +727,58 @@            | otherwise -> (# e | #)   else (# e | #) +-- | Consume a single character that is the lowercase hexadecimal+-- encoding of a 4-bit word. Fails if the character is not in the class+-- @[a-f0-9]@.+hexNibbleLower :: e -> Parser e s Word+hexNibbleLower e = uneffectful $ \chunk -> case length chunk of+  0 -> InternalFailure e+  _ ->+    let w = PM.indexByteArray (array chunk) (offset chunk) :: Word8 in+    if | w >= 48 && w < 58 -> InternalSuccess (fromIntegral w - 48) (offset chunk + 1) (length chunk - 1)+       | w >= 97 && w < 103 -> InternalSuccess (fromIntegral w - 87) (offset chunk + 1) (length chunk - 1)+       | otherwise -> InternalFailure e +-- | Consume a single character that is the case-insensitive hexadecimal+-- encoding of a 4-bit word. Fails if the character is not in the class+-- @[a-fA-F0-9]@.+hexNibble :: e -> Parser e s Word+hexNibble e = uneffectful $ \chunk -> case length chunk of+  0 -> InternalFailure e+  _ ->+    let w = PM.indexByteArray (array chunk) (offset chunk) :: Word8 in+    if | w >= 48 && w < 58 -> InternalSuccess (fromIntegral w - 48) (offset chunk + 1) (length chunk - 1)+       | w >= 65 && w < 71 -> InternalSuccess (fromIntegral w - 55) (offset chunk + 1) (length chunk - 1)+       | w >= 97 && w < 103 -> InternalSuccess (fromIntegral w - 87) (offset chunk + 1) (length chunk - 1)+       | otherwise -> InternalFailure e++-- | Consume a single character that is the lowercase hexadecimal+-- encoding of a 4-bit word. Returns @Nothing@ without consuming+-- the character if it is not in the class @[a-f0-9]@. The parser+-- never fails.+tryHexNibbleLower :: Parser e s (Maybe Word)+tryHexNibbleLower = unfailing $ \chunk -> case length chunk of+  0 -> InternalStep Nothing (offset chunk) (length chunk)+  _ ->+    let w = PM.indexByteArray (array chunk) (offset chunk) :: Word8 in+    if | w >= 48 && w < 58 -> InternalStep (Just (fromIntegral w - 48)) (offset chunk + 1) (length chunk - 1)+       | w >= 97 && w < 103 -> InternalStep (Just (fromIntegral w - 87)) (offset chunk + 1) (length chunk - 1)+       | otherwise -> InternalStep Nothing (offset chunk) (length chunk)++-- | Consume a single character that is the case-insensitive hexadecimal+-- encoding of a 4-bit word. Returns @Nothing@ without consuming+-- the character if it is not in the class @[a-fA-F0-9]@. This parser+-- never fails.+tryHexNibble :: Parser e s (Maybe Word)+tryHexNibble = unfailing $ \chunk -> case length chunk of+  0 -> InternalStep Nothing (offset chunk) (length chunk)+  _ ->+    let w = PM.indexByteArray (array chunk) (offset chunk) :: Word8 in+    if | w >= 48 && w < 58 -> InternalStep (Just (fromIntegral w - 48)) (offset chunk + 1) (length chunk - 1)+       | w >= 65 && w < 71 -> InternalStep (Just (fromIntegral w - 55)) (offset chunk + 1) (length chunk - 1)+       | w >= 97 && w < 103 -> InternalStep (Just (fromIntegral w - 87)) (offset chunk + 1) (length chunk - 1)+       | otherwise -> InternalStep Nothing (offset chunk) (length chunk)+ -- Returns the maximum machine word if the argument is not -- the ASCII encoding of a hexadecimal digit. oneHex :: Word8 -> Word@@ -727,4 +803,12 @@       !cb = int2Word# (gtWord# r1 upper)       !cc = int2Word# (ltWord# r1 0##)       !c = ca `or#` cb `or#` cc+   in (case c of { 0## -> False; _ -> True }, W# r1)++unsignedPushBase10 :: Word -> Word -> (Bool,Word)+unsignedPushBase10 (W# a) (W# b) = +  let !(# ca, r0 #) = Exts.timesWord2# a 10##+      !r1 = Exts.plusWord# r0 b+      !cb = int2Word# (ltWord# r1 r0)+      !c = ca `or#` cb    in (case c of { 0## -> False; _ -> True }, W# r1)
+ src/Data/Bytes/Parser/Types.hs view
@@ -0,0 +1,41 @@+{-# language DeriveFunctor #-}+{-# language DeriveFoldable #-}+{-# language DerivingStrategies #-}++module Data.Bytes.Parser.Types+  ( Parser+  , Result(..)+  , Slice(..)+  ) where++import Data.Bytes.Parser.Internal (Parser(..))++-- | The result of running a parser.+data Result e a+  = Failure e+    -- ^ An error message indicating what went wrong.+  | Success {-# UNPACK #-} !(Slice a)+    -- ^ The parsed value and the number of bytes+    -- remaining in parsed slice.+  deriving stock (Eq,Show,Foldable,Functor)++-- | Slicing metadata (an offset and a length) accompanied+-- by a value. This does not represent a slice into the+-- value. This type is intended to be used as the result+-- of an executed parser. In this context the slicing metadata+-- describe a slice into to the array (or byte array) that+-- from which the value was parsed.+--+-- It is often useful to check the @length@ when a parser+-- succeeds since a non-zero length indicates that there+-- was additional unconsumed input. The @offset@ is only+-- ever needed to construct a new slice (via @Bytes@ or+-- @SmallVector@) from the remaining input.+data Slice a = Slice+  { offset :: {-# UNPACK #-} !Int+    -- ^ Offset into the array.+  , length :: {-# UNPACK #-} !Int+    -- ^ Length of the slice.+  , value :: a+    -- ^ The structured data that was successfully parsed.+  } deriving stock (Eq,Show,Foldable,Functor)
src/Data/Bytes/Parser/Unsafe.hs view
@@ -3,11 +3,13 @@ {-# language DataKinds #-} {-# language DeriveFunctor #-} {-# language DerivingStrategies #-}+{-# language DuplicateRecordFields #-} {-# language GADTSyntax #-} {-# language KindSignatures #-} {-# language LambdaCase #-} {-# language MagicHash #-} {-# language MultiWayIf #-}+{-# language NamedFieldPuns #-} {-# language PolyKinds #-} {-# language RankNTypes #-} {-# language ScopedTypeVariables #-}@@ -19,7 +21,10 @@ -- | Everything in this module is unsafe and can lead to -- nondeterministic output or segfaults if used incorrectly. module Data.Bytes.Parser.Unsafe-  ( cursor+  ( -- * Types+    Parser(..)+    -- * Functions+  , cursor   , expose   , unconsume   , jump@@ -36,27 +41,27 @@ -- it possible to observe the internal difference between 'Bytes' -- that refer to equivalent slices. Be careful. cursor :: Parser e s Int-cursor = uneffectful $ \chunk ->-  InternalSuccess (offset chunk) (offset chunk) (length chunk)+cursor = uneffectful $ \Bytes{offset,length} ->+  InternalSuccess offset offset length  -- | Return the byte array being parsed. This includes bytes -- that preceed the current offset and may include bytes that -- go beyond the length. This is somewhat dangerous, so only -- use this is you know what you're doing. expose :: Parser e s ByteArray-expose = uneffectful $ \chunk ->-  InternalSuccess (array chunk) (offset chunk) (length chunk)+expose = uneffectful $ \Bytes{length,offset,array} ->+  InternalSuccess array offset length  -- | Move the cursor back by @n@ bytes. Precondition: you -- must have previously consumed at least @n@ bytes. unconsume :: Int -> Parser e s ()-unconsume n = uneffectful $ \chunk ->-  InternalSuccess () (offset chunk - n) (length chunk + n)+unconsume n = uneffectful $ \Bytes{length,offset} ->+  InternalSuccess () (offset - n) (length + n)  -- | Set the position to the given index. Precondition: the index -- must be valid. It should be the result of an earlier call to -- 'cursor'. jump :: Int -> Parser e s ()-jump ix = uneffectful $ \chunk ->-  InternalSuccess () ix (length chunk + (offset chunk - ix))+jump ix = uneffectful $ \(Bytes{length,offset}) ->+  InternalSuccess () ix (length + (offset - ix)) 
test/Main.hs view
@@ -11,6 +11,7 @@ import Data.Word (Word8,Word64) import Data.Char (ord) import Data.Bytes.Types (Bytes(Bytes))+import Data.Bytes.Parser (Slice(Slice)) import Test.Tasty (defaultMain,testGroup,TestTree) import Test.Tasty.HUnit ((@=?),testCase) import Test.Tasty.QuickCheck ((===),testProperty)@@ -30,10 +31,13 @@ tests :: TestTree tests = testGroup "Parser"   [ testProperty "decStandardInt" $ \i ->-      P.parseBytes (Latin.decStandardInt ()) (bytes (show i)) === P.Success i 0+      withSz (show i) $ \str len ->+        P.parseBytes (Latin.decStandardInt ()) str+        ===+        P.Success (Slice len 0 i)   , testProperty "big-endian-word64" bigEndianWord64   , testCase "delimit" $-      P.Success (167,14625) 0+      P.Success (Slice 13 0 (167,14625))       @=?       P.parseBytes         (do len <- Latin.decUnsignedInt ()@@ -52,7 +56,7 @@         P.parseBytes (Latin.decUnsignedInt ())           (bytes "742493495120739103935542")     , testCase "B" $-        P.Success 4654667 3+        P.Success (Slice 8 3 4654667)         @=?         P.parseBytes (Latin.decUnsignedInt ())           (bytes "4654667,55")@@ -71,45 +75,73 @@         @=?         P.parseBytes (Latin.decUnsignedInt ())           (bytes (show (fromIntegral @Int @Word maxBound + 1)))-    , testCase "F" $-        P.Success maxBound 0+    , testCase "F" $ withSz (show (maxBound :: Int)) $ \str len ->+        P.Success (Slice len 0 maxBound)         @=?-        P.parseBytes (Latin.decUnsignedInt ())-          (bytes (show (maxBound :: Int)))+        P.parseBytes (Latin.decUnsignedInt ()) str     , testProperty "property" $ \(QC.NonNegative i) ->-        P.parseBytes (Latin.decUnsignedInt ()) (bytes (show i))-        ===-        P.Success i 0+        withSz (show i) $ \str len ->+          P.parseBytes (Latin.decUnsignedInt ()) str+          ===+          P.Success (Slice len 0 i)     ]+  , testGroup "hexNibbleLower"+    [ testCase "A" $+        P.parseBytes (Latin.hexNibbleLower ()) (bytes "Ab") @=? P.Failure ()+    , testCase "B" $+        P.parseBytes (Latin.hexNibbleLower ()) (bytes "bA") @=? P.Success (Slice 2 1 0xb)+    , testCase "C" $+        P.parseBytes (Latin.hexNibbleLower ()) (bytes "") @=? P.Failure ()+    ]+  , testGroup "tryHexNibbleLower"+    [ testCase "A" $+        P.Success @() (Slice 1 2 Nothing)+        @=?+        P.parseBytes Latin.tryHexNibbleLower (bytes "Ab")+    , testCase "B" $+        P.Success @() (Slice 2 1 (Just 0xb))+        @=?+        P.parseBytes Latin.tryHexNibbleLower (bytes "bA")+    , testCase "C" $+        P.Success @() (Slice 1 0 Nothing)+        @=?+        P.parseBytes Latin.tryHexNibbleLower (bytes "")+    ]   , testGroup "decPositiveInteger"     [ testCase "A" $         P.parseBytes (Latin.decUnsignedInteger ())           (bytes "5469999463123462573426452736423546373235260")         @=?-        P.Success 5469999463123462573426452736423546373235260 0+        P.Success+          (Slice 44 0 5469999463123462573426452736423546373235260)     , testProperty "property" $ \(LargeInteger i) ->-        i >= 0-        QC.==>-        P.parseBytes (Latin.decUnsignedInteger ()) (bytes (show i))-        ===-        P.Success i 0+        withSz (show i) $ \str len ->+          i >= 0+          QC.==>+          P.parseBytes (Latin.decUnsignedInteger ()) str+          ===+          P.Success (Slice len 0 i)     ]   , testGroup "decTrailingInteger"     [ testProperty "property" $ \(LargeInteger i) ->-        i >= 0-        QC.==>-        P.parseBytes (Latin.decTrailingInteger 2) (bytes (show i))-        ===-        (P.Success (read ('2' : show i) :: Integer) 0 :: P.Result () Integer)+        withSz (show i) $ \str sz ->+          i >= 0+          QC.==>+          P.parseBytes (Latin.decTrailingInteger 2) str+          ===+          (P.Success (Slice sz 0 (read ('2' : show i) :: Integer)) :: P.Result () Integer)     ]   , testGroup "decSignedInteger"     [ testCase "A" $         P.parseBytes (Latin.decSignedInteger ())           (bytes "-54699994631234625734264527364235463732352601")         @=?-        P.Success (-54699994631234625734264527364235463732352601) 0-    , testCase "B" $-        P.Success (3,(-206173954435705292503)) 0+        P.Success+          ( Slice 46 0+            (-54699994631234625734264527364235463732352601)+          )+    , testCase "B" $ +        P.Success (Slice 25 0 (3,(-206173954435705292503)))         @=?         P.parseBytes           ( pure (,)@@ -118,21 +150,22 @@             <*> Latin.decSignedInteger ()           ) (bytes "3e-206173954435705292503")     , testProperty "property" $ \(LargeInteger i) ->-        P.parseBytes (Latin.decSignedInteger ()) (bytes (show i))-        ===-        P.Success i 0+        withSz (show i) $ \str len ->+          P.parseBytes (Latin.decSignedInteger ()) str+          ===+          P.Success (Slice len 0 i)     ]   , testGroup "decSignedInt"-    [ testProperty "A" $ \i ->-        P.parseBytes (Latin.decSignedInt ()) (bytes (show i))+    [ testProperty "A" $ \i -> withSz (show i) $ \str len ->+        P.parseBytes (Latin.decSignedInt ()) str         ===-        P.Success i 0+        P.Success (Slice len 0 i)     , testProperty "B" $ \i ->-        P.parseBytes-          (Latin.decSignedInt ())-          (bytes ((if i >= 0 then "+" else "") ++ show i))-        ===-        P.Success i 0+        let s = (if i >= 0 then "+" else "") ++ show i in+        withSz s $ \str len ->+          P.parseBytes (Latin.decSignedInt ()) str+          ===+          P.Success (Slice len 0 i)     , testCase "C" $         P.Failure ()         @=?@@ -153,16 +186,14 @@         @=?         P.parseBytes (Latin.decSignedInt ())           (bytes "-4305030950553840988981")-    , testCase "G" $-        P.Success minBound 0+    , testCase "G" $ withSz (show (minBound :: Int)) $ \str len ->+        P.Success (Slice len 0 minBound)         @=?-        P.parseBytes (Latin.decSignedInt ())-          (bytes (show (minBound :: Int)))-    , testCase "H" $-        P.Success maxBound 0+        P.parseBytes (Latin.decSignedInt ()) str+    , testCase "H" $ withSz (show (maxBound :: Int)) $ \str len ->+        P.Success (Slice len 0 maxBound)         @=?-        P.parseBytes (Latin.decSignedInt ())-          (bytes (show (maxBound :: Int)))+        P.parseBytes (Latin.decSignedInt ()) str     , testCase "I" $         P.Failure ()         @=?@@ -177,8 +208,15 @@         P.parseBytes (Latin.decSignedInt ())           (bytes "-9223372036854775809")     ]+  , testGroup "decWord64"+    [ testCase "A" $+        P.Failure ()+        @=?+        P.parseBytes (Latin.decWord64 ())+          (bytes "2481030337885070917891")+    ]   , testCase "decWord-composition" $-      P.Success (42,8) 0+      P.Success (Slice 6 0 (42,8))       @=?       P.parseBytes         ( pure (,)@@ -188,7 +226,7 @@         <*  Ascii.char () '.'         ) (bytes "42.8.")   , testCase "decWord-replicate" $-      P.Success (Exts.fromList [42,93] :: PrimArray Word) 0+      P.Success (Slice 7 0 (Exts.fromList [42,93] :: PrimArray Word))       @=?       P.parseBytes         (P.replicate 2 (Ascii.decWord () <* Ascii.char () '.'))@@ -231,7 +269,7 @@         + fromIntegral h * 256 ^ (0 :: Integer)    in P.parseBytes (BigEndian.word64 ()) (Bytes arr 2 9)       ===-      P.Success expected 1+      P.Success (Slice 10 1 expected)  -- The Arbitrary instance for Integer that comes with -- QuickCheck only generates small numbers.@@ -249,3 +287,7 @@       f :: Word8 -> Integer -> Integer       f w acc = (acc `Bits.shiftL` 8) + fromIntegral w +-- We add an extra 1 since bytes gives us a slice that+-- starts at that offset.+withSz :: String -> (Bytes -> Int -> a) -> a+withSz str f = f (bytes str) (length str + 1)