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 +8/−0
- bytesmith.cabal +2/−1
- src/Data/Bytes/Parser.hs +67/−28
- src/Data/Bytes/Parser/Internal.hs +11/−0
- src/Data/Bytes/Parser/Latin.hs +102/−18
- src/Data/Bytes/Parser/Types.hs +41/−0
- src/Data/Bytes/Parser/Unsafe.hs +14/−9
- test/Main.hs +88/−46
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)