packages feed

flatparse 0.3.3.0 → 0.3.4.0

raw patch · 5 files changed

+501/−89 lines, 5 filesdep +quickcheck-instancesPVP ok

version bump matches the API change (PVP)

Dependencies added: quickcheck-instances

API changes (from Hackage documentation)

+ FlatParse.Basic: isolate :: Int -> Parser e a -> Parser e a
+ FlatParse.Basic: takeBs :: Int -> Parser e ByteString
+ FlatParse.Basic: takeRestBs :: Parser e ByteString
+ FlatParse.Stateful: isolate :: Int -> Parser e a -> Parser e a
+ FlatParse.Stateful: takeBs :: Int -> Parser e ByteString
+ FlatParse.Stateful: takeRestBs :: Parser e ByteString

Files

README.md view
@@ -3,9 +3,7 @@ [![Hackage](https://img.shields.io/hackage/v/flatparse.svg)](https://hackage.haskell.org/package/flatparse) ![CI](https://github.com/AndrasKovacs/flatparse/actions/workflows/haskell.yml/badge.svg) -`flatparse` is a high-performance parsing library, focusing on __programming languages__ and __human-readable data formats__. The "flat" in the name-refers to the `ByteString` parsing input, which has pinned contiguous data, and also to the library internals, which avoids indirections and heap allocations-whenever possible.+`flatparse` is a high-performance parsing library, supporting parsing for __programming languages__, __human-readable data__ and __machine-readable data__. The "flat" in the name refers to the `ByteString` parsing input, which has pinned contiguous data, and also to the library internals, which avoids indirections and heap allocations whenever possible. `flatparse` is generally __lower-level__ than `parsec`-style libraries, but it is possible to build higher-level features (such as source spans, hints, indentation parsing) on top of it, without making any compromises in performance.  ### LLVM @@ -21,7 +19,7 @@  * __Excellent performance__. On microbenchmarks, `flatparse` is around 10 times faster than `attoparsec` or `megaparsec`. On larger examples with heavier use of source positions and spans and/or indentation parsing, the performance difference grows to 20-30 times. Compile times and exectuable sizes are also significantly better with `flatparse` than with `megaparsec` or `attoparsec`. `flatparse` internals make liberal use of unboxed tuples and GHC primops. As a result, pure validators (parsers returning `()`) in `flatparse` are not difficult to implement with zero heap allocation. * __No incremental parsing__, and __only strict `ByteString`__ is supported as input. However, it can be still useful to convert from `Text`, `String` or other types to `ByteString`, and then use `flatparse` for parsing, since `flatparse` performance usually more than makes up for the conversion costs.-* __Only little-endian 64 bit systems are currently supported__. This may change in the future. Getting good performance requires architecture-specific optimizations; I've only considered the most common setting at this point.+* __Only little-endian 64 bit systems are currently supported as the host machine__. This may change in the future. Getting good performance requires architecture-specific optimizations; I've only considered the most common setting at this point. However, `flatparse` does include specific big-endian parsers for primitive integer types. * __Support for fast source location handling, indentation parsing and informative error messages__. `flatparse` provides a low-level interface to these. Batteries are _not included_, but it should be possible for users to build custom solutions, which are more sophisticated, but still as fast as possible. In my experience, the included batteries in other libraries often come with major unavoidable overheads, and often we still have to extend existing machinery in order to scale to production features. * The __backtracking model__ of `flatparse` is different to parsec libraries, and is more close to the [nom](https://github.com/Geal/nom) library in Rust. The idea is that _parser failure_ is distinguished from _parsing error_. The former is used for control flow, and we can backtrack from it. The latter is used for unrecoverable errors, and by default it's propagated to the top. `flatparse` does not track whether parsers have consumed inputs. In my experience, what we really care about is the failure/error distinction, and in `parsec` or `megaparsec` the consumed/non-consumed separation is often muddled and discarded in larger parser implementations. By default, basic `flatparse` parsers can fail but can not throw errors, with the exception of the specifically error-throwing operations. Hence, `flatparse` users have to be mindful about grammar, and explicitly insert errors where it is known that the input can't be valid. 
flatparse.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           flatparse-version:        0.3.3.0+version:        0.3.4.0 synopsis:       High-performance parsing from strict bytestrings description:    @Flatparse@ is a high-performance parsing library, focusing on programming languages and                 human-readable data formats. See the README for more information:@@ -105,6 +105,7 @@     , bytestring     , flatparse     , hspec+    , quickcheck-instances   if flag(dump)     ghc-options: -ddump-simpl -ddump-stg -ddump-cmm -dsuppress-all -dno-suppress-type-signatures -ddump-to-file   if flag(llvm)
src/FlatParse/Basic.hs view
@@ -33,6 +33,8 @@    -- * Basic lexing and parsing   , eof+  , takeBs+  , takeRestBs   , char   , byte   , bytes@@ -95,6 +97,7 @@   , some   , some_   , notFollowedBy+  , isolate    -- * Positions and spans   , Pos(..)@@ -116,7 +119,7 @@   , mkPos   , FlatParse.Basic.lines -  -- * Getting the rest of the input+  -- * Getting the rest of the input as a 'String'   , takeLine   , traceLine   , takeRest@@ -360,6 +363,25 @@   _  -> Fail# {-# inline eof #-} +-- | Read the given number of bytes as a 'ByteString'.+--+-- Throws a runtime error if given a negative integer.+takeBs :: Int -> Parser e B.ByteString+takeBs (I# n#) = Parser \fp eob s -> case n# <=# minusAddr# eob s of+  1# -> -- have to runtime check for negative values, because they cause a hang+    case n# >=# 0# of+      1# -> OK# (B.PS (ForeignPtr s fp) 0 (I# n#)) (plusAddr# s n#)+      _  -> error "FlatParse.Basic.take: negative integer"+  _  -> Fail#+{-# inline takeBs #-}++-- | Consume the rest of the input. May return the empty bytestring.+takeRestBs :: Parser e B.ByteString+takeRestBs = Parser \fp eob s ->+  let n# = minusAddr# eob s+  in  OK# (B.PS (ForeignPtr s fp) 0 (I# n#)) eob+{-# inline takeRestBs #-}+ -- | Parse a UTF-8 character literal. This is a template function, you can use it as --   @$(char \'x\')@, for example, and the splice in this case has type @Parser e ()@. char :: Char -> Q Exp@@ -421,9 +443,10 @@  {-| Switch expression with an optional first argument for performing a post-processing action after-every successful branch matching. For example, if we have @ws :: Parser e ()@ for a-whitespace parser, we might want to consume whitespace after matching on any of the switch-cases. For that case, we can define a "lexeme" version of `switch` as follows.+every successful branch matching, not including the default branch. For example, if we have+@ws :: Parser e ()@ for a whitespace parser, we might want to consume whitespace after matching+on any of the switch cases. For that case, we can define a "lexeme" version of `switch` as+follows.  @   switch' :: Q Exp -> Q Exp@@ -611,15 +634,16 @@ anyCharASCII_ = () <$ anyCharASCII {-# inline anyCharASCII_ #-} --- | Read an `Int` from the input, as a non-empty digit sequence. The `Int` may---   overflow in the result.+-- | Read a non-negative `Int` from the input, as a non-empty digit sequence.+-- The `Int` may overflow in the result. readInt :: Parser e Int readInt = Parser \fp eob s -> case FlatParse.Internal.readInt eob s of   (# (##) | #)        -> Fail#   (# | (# n, s' #) #) -> OK# (I# n) s' {-# inline readInt #-} --- | Read an `Integer` from the input, as a non-empty digit sequence.+-- | Read a non-negative `Integer` from the input, as a non-empty digit+-- sequence. readInteger :: Parser e Integer readInteger = Parser \fp eob s -> case FlatParse.Internal.readInteger fp eob s of   (# (##) | #)        -> Fail#@@ -707,6 +731,25 @@ notFollowedBy :: Parser e a -> Parser e b -> Parser e a notFollowedBy p1 p2 = p1 <* fails p2 {-# inline notFollowedBy #-}++-- | @isolate n p@ runs the parser @p@ isolated to the next @n@ bytes. All+--   isolated bytes must be consumed.+--+-- Throws a runtime error if given a negative integer.+isolate :: Int -> Parser e a -> Parser e a+isolate (I# n#) p = Parser \fp eob s ->+  let s' = plusAddr# s n#+  in  case n# <=# minusAddr# eob s of+        1# -> case n# >=# 0# of+          1# -> case runParser# p fp s' s of+            OK# a s'' -> case eqAddr# s' s'' of+              1# -> OK# a s''+              _  -> Fail# -- isolated segment wasn't fully consumed+            Fail#     -> Fail#+            Err# e    -> Err# e+          _  -> error "FlatParse.Basic.isolate: negative integer"+        _  -> Fail# -- you tried to isolate more than we have left+{-# inline isolate #-}   --------------------------------------------------------------------------------
src/FlatParse/Stateful.hs view
@@ -39,6 +39,8 @@    -- * Basic lexing and parsing   , eof+  , takeBs+  , takeRestBs   , char   , byte   , bytes@@ -101,6 +103,7 @@   , some   , some_   , notFollowedBy+  , isolate    -- * Positions and spans   , Pos(..)@@ -122,7 +125,7 @@   , Basic.mkPos   , Basic.lines -  -- * Getting the rest of the input+  -- * Getting the rest of the input as a 'String'   , takeLine   , traceLine   , takeRest@@ -394,6 +397,25 @@   _  -> Fail# {-# inline eof #-} +-- | Read the given number of bytes as a 'ByteString'.+--+-- Throws a runtime error if given a negative integer.+takeBs :: Int -> Parser e B.ByteString+takeBs (I# n#) = Parser \fp !r eob s n -> case n# <=# minusAddr# eob s of+  1# -> -- have to runtime check for negative values, because they cause a hang+    case n# >=# 0# of+      1# -> OK# (B.PS (ForeignPtr s fp) 0 (I# n#)) (plusAddr# s n#) n+      _  -> error "FlatParse.Basic.take: negative integer"+  _  -> Fail#+{-# inline takeBs #-}++-- | Consume the rest of the input. May return the empty bytestring.+takeRestBs :: Parser e B.ByteString+takeRestBs = Parser \fp !r eob s n ->+  let n# = minusAddr# eob s+  in  OK# (B.PS (ForeignPtr s fp) 0 (I# n#)) eob n+{-# inline takeRestBs #-}+ -- | Parse a UTF-8 character literal. This is a template function, you can use it as --   @$(char \'x\')@, for example, and the splice in this case has type @Parser e ()@. char :: Char -> Q Exp@@ -740,6 +762,24 @@ notFollowedBy p1 p2 = p1 <* lookahead (fails p2) {-# inline notFollowedBy #-} +-- | @isolate n p@ runs the parser @p@ isolated to the next @n@ bytes. All+--   isolated bytes must be consumed.+--+-- Throws a runtime error if given a negative integer.+isolate :: Int -> Parser e a -> Parser e a+isolate (I# n#) p = Parser \fp !r eob s n ->+  let s' = plusAddr# s n#+  in  case n# <=# minusAddr# eob s of+        1# -> case n# >=# 0# of+          1# -> case runParser# p fp r s' s n of+            OK# a s'' n' -> case eqAddr# s' s'' of+              1# -> OK# a s'' n'+              _  -> Fail# -- isolated segment wasn't fully consumed+            Fail#     -> Fail#+            Err# e    -> Err# e+          _  -> error "FlatParse.Basic.isolate: negative integer"+        _  -> Fail# -- you tried to isolate more than we have left+{-# inline isolate #-}  -------------------------------------------------------------------------------- 
test/Test.hs view
@@ -4,62 +4,21 @@  import Data.ByteString (ByteString) import qualified Data.ByteString as B+import qualified Data.Char import FlatParse.Basic import Test.HUnit import Test.Hspec import Test.Hspec.QuickCheck+import Test.QuickCheck hiding ( (.&.) ) import Data.Word import Data.Int import Data.Bits+import Test.QuickCheck.Instances.ByteString()  main :: IO () main = hspec $ do   basicSpec ------------------------------------------------------------------------------------ Some combinators that make it easier to assert the results of a parser.---- | The parser should parse this string, consuming it entirely, and succeed.-shouldParse :: Show e => Parser e a -> ByteString -> Expectation-p `shouldParse` s = case runParser p s of-  OK _ "" -> pure ()-  OK _ lo -> assertFailure $ "Unexpected leftover: " ++ show lo-  Fail -> assertFailure "Parse failed unexpectedly"-  Err e -> assertFailure $ "Parse threw unexpected error: " ++ show e---- | The parser should parse this string, consuming it entirely, and succeed--- yielding the matching value.-shouldParseWith ::-  (Show a, Eq a, Show e) => Parser e a -> (ByteString, a) -> Expectation-p `shouldParseWith` (s, r) = case runParser p s of-  OK r' "" -> r' `shouldBe` r-  OK _ lo -> assertFailure $ "Unexpected leftover: " ++ show lo-  Fail -> assertFailure "Parse failed unexpectedly"-  Err e -> assertFailure $ "Parse threw unexpected error: " ++ show e---- | The parser should fail when given this string.-shouldParseFail :: Show e => Parser e a -> ByteString -> Expectation-p `shouldParseFail` s = case runParser p s of-  Fail -> pure ()-  OK _ _ -> assertFailure "Parse succeeded unexpectedly"-  Err e -> assertFailure $ "Parse threw unexpected error: " ++ show e---- | The parser should throw an error when given this string.-shouldParseErr :: Parser e a -> ByteString -> Expectation-p `shouldParseErr` s = case runParser p s of-  Err e -> pure ()-  Fail -> assertFailure "Parse failed unexpectedly"-  OK _ _ -> assertFailure "Parse succeeded unexpectedly"---- | The parser should throw an error when given this string, and the error--- should be the one given.-shouldParseErrWith ::-  (Show e, Eq e) => Parser e a -> (ByteString, e) -> Expectation-p `shouldParseErrWith` (s, e) = case runParser p s of-  Err e' -> e' `shouldBe` e-  Fail -> assertFailure "Parse failed unexpectedly"-  OK _ _ -> assertFailure "Parse succeeded unexpectedly"- -- | The spec for FlatParse.Basic. basicSpec :: SpecWith () basicSpec = describe "FlatParse.Basic" $ do@@ -149,65 +108,294 @@       it "fails when out of space" $ $(string "foo") `shouldParseFail` "fo"      describe "switch" $ do-      pure ()+      it "parses simple words" $+        $( switch+             [|+               case _ of+                 "foo" -> pure 1+                 "bar" -> pure 2+               |]+         )+          `shouldParseWith` ("foo", 1) +      it "matches the default" $+        $( switch+             [|+               case _ of+                 "foo" -> pure 1+                 "bar" -> pure 2+                 _ -> pure 0+               |]+         )+          `shouldParsePartialWith` ("fez", 0)++      it "fails with no default" $+        $( switch+             [|+               case _ of+                 "foo" -> pure 1+                 "bar" -> pure 2+               |]+         )+          `shouldParseFail` "fez"++      it "prefers longest match" $+        $( switch+             [|+               case _ of+                 "foo" -> pure 1+                 "foobar" -> pure 2+               |]+         )+          `shouldParseWith` ("foobar", 2)++      it "doesn't reproduce bug #12" $+        $( switch+             [|+               case _ of+                 "Eac" -> pure ()+                 "EAc" -> pure ()+                 "E" -> pure ()+               |]+         )+          `shouldParse` "E"+     describe "switchWithPost" $ do-      pure ()+      it "applies post after match" $+        $( switchWithPost+             (Just [|$(string "bar")|])+             [|+               case _ of+                 "foo" -> pure ()+               |]+         )+          `shouldParse` "foobar" +      it "doesn't apply post after default" $+        $( switchWithPost+             (Just [|$(string "bar")|])+             [|+               case _ of+                 "foo" -> pure ()+                 _ -> pure ()+               |]+         )+          `shouldParse` ""++      it "requires the post must match" $+        $( switchWithPost+             (Just [|$(string "bar")|])+             [|+               case _ of+                 "foo" -> pure ()+               |]+         )+          `shouldParseFail` "foo"+     describe "rawSwitchWithPost" $ do-      pure ()+      it "parses simple words" $+        $( rawSwitchWithPost+             Nothing+             [ ("foo", [|pure 1|]),+               ("bar", [|pure 2|])+             ]+             Nothing+         )+          `shouldParseWith` ("foo", 1) +      it "matches the default" $+        $( rawSwitchWithPost+             Nothing+             [ ("foo", [|pure 1|]),+               ("bar", [|pure 2|])+             ]+             (Just [|pure 0|])+         )+          `shouldParsePartialWith` ("fez", 0)++      it "fails with no default" $+        $( rawSwitchWithPost+             Nothing+             [ ("foo", [|pure 1|]),+               ("bar", [|pure 2|])+             ]+             Nothing+         )+          `shouldParseFail` "fez"++      it "prefers longest match" $+        $( rawSwitchWithPost+             Nothing+             [ ("foo", [|pure 1|]),+               ("foobar", [|pure 2|])+             ]+             Nothing+         )+          `shouldParseWith` ("foobar", 2)++      it "applies post after match" $+        $( rawSwitchWithPost+             (Just [|$(string "bar")|])+             [("foo", [|pure ()|])]+             Nothing+         )+          `shouldParse` "foobar"++      it "doesn't apply post after default" $+        $( rawSwitchWithPost+             (Just [|$(string "bar")|])+             [("foo", [|pure ()|])]+             (Just [|pure ()|])+         )+          `shouldParse` ""++      it "requires the post must match" $+        $( rawSwitchWithPost+             (Just [|$(string "bar")|])+             [("foo", [|pure ()|])]+             Nothing+         )+          `shouldParseFail` "foo"+     describe "satisfy" $ do-      pure ()+      it "succeeds on the right char" $+        satisfy (== 'a') `shouldParseWith` ("a", 'a') +      it "succeeds on multi-byte chars" $ do+        let chars = "$¢€𐍈" :: [Char]+        sequence_+          [ if a == b+              then satisfy (== a) `shouldParseWith` (packUTF8 (pure b), b)+              else satisfy (== a) `shouldParseFail` packUTF8 (pure b)+            | a <- chars,+              b <- chars+          ]++      it "fails on the wrong char" $+        satisfy (== 'a') `shouldParseFail` "b"+      it "fails at end of file" $+        satisfy (== 'a') `shouldParseFail` ""+     describe "satisfyASCII" $ do-      pure ()+      it "succeeds on the right char" $+        satisfyASCII (== 'a') `shouldParseWith` ("a", 'a')+      it "fails on the wrong char" $+        satisfyASCII (== 'a') `shouldParseFail` "b" +      it "fails on the wrong multi-byte char" $+        -- The specification for satisfyASCII requires that the predicate+        -- return False for non-ASCII characters, but multi-byte chars are+        -- still allowed in the input.+        satisfyASCII (== 'a') `shouldParseFail` packUTF8 "ȩ"++      it "fails at end of file" $+        satisfyASCII (== 'a') `shouldParseFail` ""+     describe "satisfyASCII_" $ do-      pure ()+      it "succeeds on the right char" $+        satisfyASCII_ (== 'a') `shouldParseWith` ("a", ())+      it "fails on the wrong char" $+        satisfyASCII_ (== 'a') `shouldParseFail` "b"+      it "fails on the wrong multi-byte char" $+        satisfyASCII_ (== 'a') `shouldParseFail` packUTF8 "ȩ"+      it "fails at end of file" $+        satisfyASCII_ (== 'a') `shouldParseFail` ""      describe "fusedSatisfy" $ do-      pure ()+      it "correctly routes chars based on length" $ do+        fusedSatisfy (== '$') (const False) (const False) (const False)+          `shouldParse` packUTF8 "$"+        fusedSatisfy (const False) (== '¢') (const False) (const False)+          `shouldParse` packUTF8 "¢"+        fusedSatisfy (const False) (const False) (== '€') (const False)+          `shouldParse` packUTF8 "€"+        fusedSatisfy (const False) (const False) (const False) (== '𐍈')+          `shouldParse` packUTF8 "𐍈" +      it "fails on empty input" $+        fusedSatisfy (const True) (const True) (const True) (const True)+          `shouldParseFail` ""+     describe "anyWord8" $ do-      pure ()+      it "reads a byte" $ anyWord8 `shouldParseWith` ("\xef", 0xef)+      it "fails on empty input" $ anyWord8 `shouldParseFail` ""      describe "anyWord16" $ do-      pure ()+      -- Byte order is unspecified, so just assert that it succeeds.+      it "succeeds" $ anyWord16 `shouldParse` "\xef\xbe" +      it "fails on empty input" $ anyWord16 `shouldParseFail` ""+      it "fails on insufficient input" $ anyWord16 `shouldParseFail` "\xff"+     describe "anyWord32" $ do-      pure ()+      -- Byte order is unspecified, so just assert that it succeeds.+      it "succeeds" $ anyWord32 `shouldParse` "\xef\xbe\xae\x7e" +      it "fails on empty input" $ anyWord32 `shouldParseFail` ""+      it "fails on insufficient input" $+        anyWord32 `shouldParseFail` "\xff\xff\xff"+     describe "anyWord" $ do-      pure ()+      -- This combinator is inherently non-portable, but we know a Word is at+      -- least some bytes.+      it "fails on empty input" $ anyWord `shouldParseFail` ""      describe "anyChar" $ do-      pure ()+      it "reads 1-byte char" $ anyChar `shouldParseWith` (packUTF8 "$", '$')+      it "reads 2-byte char" $ anyChar `shouldParseWith` (packUTF8 "¢", '¢')+      it "reads 3-byte char" $ anyChar `shouldParseWith` (packUTF8 "€", '€')+      it "reads 4-byte char" $ anyChar `shouldParseWith` (packUTF8 "𐍈", '𐍈')+      it "fails on empty input" $ anyChar `shouldParseFail` ""      describe "anyChar_" $ do-      pure ()+      it "reads 1-byte char" $ anyChar_ `shouldParseWith` (packUTF8 "$", ())+      it "reads 2-byte char" $ anyChar_ `shouldParseWith` (packUTF8 "¢", ())+      it "reads 3-byte char" $ anyChar_ `shouldParseWith` (packUTF8 "€", ())+      it "reads 4-byte char" $ anyChar_ `shouldParseWith` (packUTF8 "𐍈", ())+      it "fails on empty input" $ anyChar_ `shouldParseFail` ""      describe "anyCharASCII" $ do-      pure ()+      it "reads ASCII char" $ anyCharASCII `shouldParseWith` (packUTF8 "$", '$')+      it "fails on non-ASCII char" $ anyCharASCII `shouldParseFail` packUTF8 "¢"+      it "fails on empty input" $ anyCharASCII `shouldParseFail` ""      describe "anyCharASCII_" $ do-      pure ()+      it "reads ASCII char" $ anyCharASCII_ `shouldParseWith` (packUTF8 "$", ())+      it "fails on non-ASCII char" $+        anyCharASCII_ `shouldParseFail` packUTF8 "¢"+      it "fails on empty input" $ anyCharASCII_ `shouldParseFail` ""      describe "isDigit" $ do-      pure ()--    describe "isGreekLetter" $ do-      pure ()+      it "agrees with Data.Char" $+        property $+          \c -> isDigit c === Data.Char.isDigit c      describe "isLatinLetter" $ do-      pure ()+      it "agrees with Data.Char" $+        property $+          \c ->+            isLatinLetter c+              === (Data.Char.isAsciiUpper c || Data.Char.isAsciiLower c)      describe "readInt" $ do-      pure ()+      it "round-trips on non-negative Ints" $+        property $+          \(NonNegative i) -> readInt `shouldParseWith` (packUTF8 (show i), i) +      it "fails on non-integers" $ readInt `shouldParseFail` "foo"+      it "fails on negative integers" $ readInt `shouldParseFail` "-5"+      it "fails on empty input" $ readInt `shouldParseFail` ""+     describe "readInteger" $ do-      pure ()+      it "round-trips on non-negative Integers" $+        property $+          \(NonNegative i) ->+            readInteger `shouldParseWith` (packUTF8 (show i), i) +      it "fails on non-integers" $ readInteger `shouldParseFail` "foo"+      it "fails on negative integers" $ readInteger `shouldParseFail` "-5"+      it "fails on empty input" $ readInteger `shouldParseFail` ""+     describe "Explicit-endianness machine integers" $ do       describe "Unsigned" $ do         prop "parses Word8s" $ do@@ -242,33 +430,115 @@           \(i :: Int64)  -> anyInt64be  `shouldParseWith` (B.reverse (w64leAsByteString i), i)    describe "Combinators" $ do+    describe "Functor instance" $ do+      it "fmaps over the result" $+        ((+ 2) <$> readInt) `shouldParseWith` ("2", 4)++    describe "Applicative instance" $ do+      it "combines using <*>" $+        ((+) <$> readInt <* $(string "+") <*> readInt)+          `shouldParseWith` ("2+3", 5)++    describe "Monad instance" $ do+      it "combines with a do block" $ do+        let parser = do+              i <- readInt+              $(string "+")+              j <- readInt+              pure (i + j)+        parser `shouldParseWith` ("2+3", 5)+     describe "(<|>)" $ do-      pure ()+      it "chooses first option on success" $+        (("A" <$ $(string "foo")) <|> ("B" <$ $(string "foo")))+          `shouldParseWith` ("foo", "A") +      it "chooses second option when first fails" $+        (("A" <$ $(string "bar")) <|> ("B" <$ $(string "foo")))+          `shouldParseWith` ("foo", "B")+     describe "branch" $ do-      pure ()+      it "chooses the first branch on success" $+        branch (pure ()) (pure "A") (pure "B") `shouldParseWith` ("", "A")+      it "does not backtrack from first branch" $+        branch (pure ()) empty (pure "B") `shouldParseFail` ""+      it "chooses the second branch on failure" $+        branch empty (pure "A") (pure "B") `shouldParseWith` ("", "B")      describe "chainl" $ do-      pure ()+      it "parses a chain of numbers" $+        chainl (+) readInt ($(char '+') *> readInt)+          `shouldParseWith` ("1+2+3", 6) +      it "allows the right chain to be empty" $+        chainl (+) readInt ($(char '+') *> readInt)+          `shouldParseWith` ("1", 1)++      it "requires at least the leftmost parser to match" $+        chainl (+) readInt ($(char '+') *> readInt)+          `shouldParseFail` ""+     describe "chainr" $ do-      pure ()+      it "parses a chain of numbers" $+        chainr (+) (readInt <* $(char '+')) readInt+          `shouldParseWith` ("1+2+3", 6) +      it "allows the left chain to be empty" $+        chainr (+) (readInt <* $(char '+')) readInt+          `shouldParseWith` ("1", 1)++      it "requires at least the rightmost parser to match" $+        chainr (+) (readInt <* $(char '+')) readInt+          `shouldParseFail` ""+     describe "many" $ do-      pure ()+      it "parses many chars" $+        many (satisfy isLatinLetter) `shouldParseWith` ("abc", "abc")+      it "accepts empty input" $+        many (satisfy isLatinLetter) `shouldParseWith` ("", "")+      it "is greedy" $+        (many (satisfy isDigit) *> satisfy isDigit) `shouldParseFail` "123"      describe "many_" $ do-      pure ()+      it "parses many chars" $+        many_ (satisfy isLatinLetter) `shouldParseWith` ("abc", ())+      it "accepts empty input" $+        many_ (satisfy isLatinLetter) `shouldParseWith` ("", ())+      it "is greedy" $+        (many_ (satisfy isDigit) *> satisfy isDigit) `shouldParseFail` "123"      describe "some" $ do-      pure ()+      it "parses some chars" $+        some (satisfy isLatinLetter) `shouldParseWith` ("abc", "abc")+      it "rejects empty input" $+        some (satisfy isLatinLetter) `shouldParseFail` ""+      it "is greedy" $+        (some (satisfy isDigit) *> satisfy isDigit) `shouldParseFail` "123"      describe "some_" $ do-      pure ()+      it "parses some chars" $+        some_ (satisfy isLatinLetter) `shouldParseWith` ("abc", ())+      it "rejects empty input" $+        some_ (satisfy isLatinLetter) `shouldParseFail` ""+      it "is greedy" $+        (some_ (satisfy isDigit) *> satisfy isDigit) `shouldParseFail` "123"      describe "notFollowedBy" $ do-      pure ()+      it "succeeds when it should" $+        readInt `notFollowedBy` $(char '.') `shouldParsePartial` "123+5"+      it "fails when first parser doesn't match" $+        readInt `notFollowedBy` $(char '.') `shouldParseFail` "a"+      it "fails when followed by the wrong thing" $+        readInt `notFollowedBy` $(char '.') `shouldParseFail` "123.0" +    describe "isolate" $ do+      prop "isolate takeRestBs is identity" $ do+        \(bs :: ByteString) ->+          isolate (B.length bs) takeRestBs `shouldParseWith` (bs, bs)+      prop "isolate takeBs length is identity" $ do+        \(bs :: ByteString) ->+          isolate (B.length bs) (takeBs (B.length bs)) `shouldParseWith` (bs, bs)+   describe "Positions and spans" $ do     describe "Pos Ord instance" $ do       pure ()@@ -316,7 +586,7 @@     describe "lines" $ do       pure () -  describe "Getting the rest of the input" $ do+  describe "Getting the rest of the input as a String" $ do     describe "takeLine" $ do       pure () @@ -368,3 +638,63 @@     b6 = fromIntegral $ (w .&. 0x0000FF0000000000) `shiftR` 40     b7 = fromIntegral $ (w .&. 0x00FF000000000000) `shiftR` 48     b8 = fromIntegral $ (w .&. 0xFF00000000000000) `shiftR` 56++--------------------------------------------------------------------------------+-- Some combinators that make it easier to assert the results of a parser.++-- | The parser should parse this string, consuming it entirely, and succeed.+shouldParse :: Show e => Parser e a -> ByteString -> Expectation+p `shouldParse` s = case runParser p s of+  OK _ "" -> pure ()+  OK _ lo -> assertFailure $ "Unexpected leftover: " ++ show lo+  Fail -> assertFailure "Parse failed unexpectedly"+  Err e -> assertFailure $ "Parse threw unexpected error: " ++ show e++-- | The parser should parse this string, possibly with leftovers, and succeed.+shouldParsePartial :: Show e => Parser e a -> ByteString -> Expectation+p `shouldParsePartial` s = case runParser p s of+  OK _ lo -> pure ()+  Fail -> assertFailure "Parse failed unexpectedly"+  Err e -> assertFailure $ "Parse threw unexpected error: " ++ show e++-- | The parser should parse this string, consuming it entirely, and succeed+-- yielding the matching value.+shouldParseWith ::+  (Show a, Eq a, Show e) => Parser e a -> (ByteString, a) -> Expectation+p `shouldParseWith` (s, r) = case runParser p s of+  OK r' "" -> r' `shouldBe` r+  OK _ lo -> assertFailure $ "Unexpected leftover: " ++ show lo+  Fail -> assertFailure "Parse failed unexpectedly"+  Err e -> assertFailure $ "Parse threw unexpected error: " ++ show e++-- | The parser should parse this string, possibly with leftovers, and succeed+-- yielding the matching value.+shouldParsePartialWith ::+  (Show a, Eq a, Show e) => Parser e a -> (ByteString, a) -> Expectation+p `shouldParsePartialWith` (s, r) = case runParser p s of+  OK r' lo -> r' `shouldBe` r+  Fail -> assertFailure "Parse failed unexpectedly"+  Err e -> assertFailure $ "Parse threw unexpected error: " ++ show e++-- | The parser should fail when given this string.+shouldParseFail :: Show e => Parser e a -> ByteString -> Expectation+p `shouldParseFail` s = case runParser p s of+  Fail -> pure ()+  OK _ _ -> assertFailure "Parse succeeded unexpectedly"+  Err e -> assertFailure $ "Parse threw unexpected error: " ++ show e++-- | The parser should throw an error when given this string.+shouldParseErr :: Parser e a -> ByteString -> Expectation+p `shouldParseErr` s = case runParser p s of+  Err e -> pure ()+  Fail -> assertFailure "Parse failed unexpectedly"+  OK _ _ -> assertFailure "Parse succeeded unexpectedly"++-- | The parser should throw an error when given this string, and the error+-- should be the one given.+shouldParseErrWith ::+  (Show e, Eq e) => Parser e a -> (ByteString, e) -> Expectation+p `shouldParseErrWith` (s, e) = case runParser p s of+  Err e' -> e' `shouldBe` e+  Fail -> assertFailure "Parse failed unexpectedly"+  OK _ _ -> assertFailure "Parse succeeded unexpectedly"