packages feed

pipes-attoparsec 0.3.1 → 0.6.0

raw patch · 10 files changed

Files

LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2012-2013, Renzo Carbonara+Copyright (c) 2012-, Renzo Carbonara Copyright (c) 2012, Paolo Capriotti  All rights reserved.
− NEWS
@@ -1,20 +0,0 @@-# Version 0.3.1--* Support attoparsec-0.11.---# Version 0.3.0--* Upgrade to pipes-4.0.0 and pipes-parse-2.0.0, removing proxy-  transformers and changing the API substantially.---# Version 0.2.0.0--* Droped the previous API in favour of a new and incompatible API-  that supports interleaved parsing by relying on pipes-parse.---# Version 0.1.0.1--* First version mentioned in NEWS file.
PEOPLE view
@@ -6,3 +6,5 @@ Renzo Carbonara Gabriel Gonzalez Patrick Wheeler+Danny Navarro+Michael Thompson
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
+ changelog.md view
@@ -0,0 +1,75 @@+# Version 0.6.0++* Remove support for `Control.Monad.Trans.Error`. +++# Version 0.5.1.5++* Remove upper bound limits on dependencies other than `base`.+++# Version 0.5.1.4++* Bump upper bound dependency on `pipes`.+++# Version 0.5.1.3++* Bump upper bound dependency on `transformers`.+++# Version 0.5.1.2++* Bump upper bound dependency on `attoparsec`.+++# Version 0.5.1.1++* Bump upper bound dependency on `text`.+++# Version 0.5.1++* Bump upper bound dependency on `attoparsec`.+++# Version 0.5.0++* Correctly propagate state in `parsedL`.++* `parse` and `parseL` return `Nothing` if used on an exhausted+  `Producer`.++* Performance improvements.+++# Version 0.4.0.1++* Relax lower and upper dependencies on `text`.+++# Version 0.4.0++* API revamped in order to support pipes-parse-3.0.*.+++# Version 0.3.1++* Support attoparsec-0.11.+++# Version 0.3.0++* Upgrade to pipes-4.0.0 and pipes-parse-2.0.0, removing proxy+  transformers and changing the API substantially.+++# Version 0.2.0.0++* Droped the previous API in favour of a new and incompatible API+  that supports interleaved parsing by relying on pipes-parse.+++# Version 0.1.0.1++* First version mentioned in NEWS file.
pipes-attoparsec.cabal view
@@ -1,23 +1,22 @@+cabal-version:      2.4 name:               pipes-attoparsec-version:            0.3.1-license:            BSD3+version:            0.6.0+license:            BSD-3-Clause license-file:       LICENSE-copyright:          Copyright (c) Renzo Carbonara 2012-2013, Paolo Capriotti 2012+copyright:          Copyright (c) Renzo Carbonara 2012-, Paolo Capriotti 2012 author:             Renzo Carbonara maintainer:         renzocarbonaraλgmail.com stability:          Experimental-tested-with:        GHC ==7.6.3 homepage:           https://github.com/k0001/pipes-attoparsec bug-reports:        https://github.com/k0001/pipes-attoparsec/issues category:           Pipes, Parser build-type:         Simple-cabal-version:      >=1.8 synopsis:           Attoparsec and Pipes integration.-extra-source-files: README.md PEOPLE NEWS+extra-source-files: README.md PEOPLE changelog.md description:   Utilities to run Attoparsec parsers on Pipes input streams.   .-  See the @NEWS@ file in the source distribution to learn about any+  See the @changelog.md@ file in the source distribution to learn about any   important changes between version.  source-repository head@@ -25,20 +24,21 @@     location: git://github.com/k0001/pipes-attoparsec.git  library+  default-language: Haskell2010   hs-source-dirs:  src   exposed-modules: Pipes.Attoparsec-                 , Pipes.Attoparsec.Internal   build-depends:-      base         (==4.*)-    , attoparsec   (>=0.10 && <0.12)+      base         (>=4.5      && <5.0)+    , attoparsec   (>=0.10)     , bytestring   (>=0.9.2.1)-    , pipes        (>=4.0 && <4.1)-    , pipes-parse  (>=2.0 && <2.1)-    , text         (>=0.11.2.3)-    , transformers (>=0.2 && <=0.4)+    , pipes        (>=4.1)+    , pipes-parse  (>=3.0.1)+    , text         (>=0.11.2.0)+    , transformers (>=0.2)   ghc-options: -Wall -O2  test-suite tests+  default-language: Haskell2010   type:           exitcode-stdio-1.0   hs-source-dirs: tests   main-is:        Main.hs@@ -54,9 +54,6 @@     , pipes-parse     , text     , transformers-    , QuickCheck                 (== 2.*)-    , HUnit                      (== 1.*)-    , test-framework             (>= 0.6)-    , test-framework-quickcheck2 (>= 0.2)-    , test-framework-hunit       (>= 0.2)-+    , HUnit                       >= 1.2+    , tasty                       >= 0.7+    , tasty-hunit                 >= 0.4
src/Pipes/Attoparsec.hs view
@@ -1,98 +1,217 @@-{-# Language RankNTypes #-}---- | The utilities in this module allow you to run Attoparsec parsers on input--- flowing downstream through pipes, possibly interleaving other stream effects--- while doing so.+-- | @pipes@ utilities for incrementally running @attoparsec@-based parsers. ----- This module builds on top of the @attoparsec@, @pipes@ and @pipes-parse@--- libraries and assumes you understand how to use those.+-- This module assumes familiarity with @pipes-parse@, you can learn about it in+-- "Pipes.Parse.Tutorial". -module Pipes.Attoparsec-  ( -- * Parsing-    parse-  , parseMany-  , isEndOfParserInput-    -- * Types-  , I.ParserInput-  , I.ParsingError(..)-  ) where+{-# LANGUAGE BangPatterns       #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances  #-}+{-# LANGUAGE RankNTypes         #-} ---------------------------------------------------------------------------------+module Pipes.Attoparsec (+    -- * Parsing+      parse+    , parsed +    -- ** Including input length+    --+    -- $lengths+    , parseL+    , parsedL++    -- * Utils+    , isEndOfParserInput++    -- * Types+    , ParserInput+    , ParsingError(..)+    ) where++import           Control.Exception                (Exception)+import qualified Control.Monad.Trans.State.Strict as S+import qualified Data.Attoparsec.ByteString+import qualified Data.Attoparsec.Text+import           Data.Attoparsec.Types            (IResult (..))+import qualified Data.Attoparsec.Types            as Attoparsec+import           Data.ByteString                  (ByteString)+import qualified Data.ByteString+import           Data.Data                        (Data, Typeable)+import           Data.Text                        (Text)+import qualified Data.Text import           Pipes-import qualified Pipes.Parse                       as Pp-import qualified Pipes.Lift                        as P-import qualified Pipes.Attoparsec.Internal         as I-import qualified Control.Monad.Trans.State.Strict  as S-import           Data.Attoparsec.Types             (Parser)-import           Data.Monoid                       (Monoid(mempty))+import qualified Pipes.Parse                      as Pipes (Parser)  -------------------------------------------------------------------------------- --- | Run an Attoparsec 'Parser' on input from the underlying 'Producer',--- returning either a 'I.ParsingError' on failure, or a pair with the parsed--- entity together with the length of input consumed in order to produce it.+-- | Convert an @attoparsec@ 'Attoparsec.Parser' to a @pipes-parse@+-- 'Pipes.Parser'. ----- Use this function only if 'isEndOfParserInput' returns 'False', otherwise--- you'll get unexpected parsing errors.+-- This 'Pipes.Parser' is compatible with the tools from "Pipes.Parse".+--+-- It returns 'Nothing' if the underlying 'Producer' is exhausted, otherwise+-- it attempts to run the given attoparsec 'Attoparsec.Parser' on the underlying+-- 'Producer', possibly failing with 'ParsingError'. parse-  :: (Monad m, I.ParserInput a)-  => Parser a b  -- ^Attoparsec parser.-  -> S.StateT (Producer a m r) m (Either I.ParsingError (Int, b))-parse attoparser = do-    (eb, mlo) <- I.parseWithDraw attoparser-    case mlo of-      Just lo -> Pp.unDraw lo-      Nothing -> return ()-    return eb+  :: (Monad m, ParserInput a)+  => Attoparsec.Parser a b                            -- ^ Attoparsec parser+  -> Pipes.Parser a m (Maybe (Either ParsingError b)) -- ^ Pipes parser+parse parser = S.StateT $ \p0 -> do+    x <- nextSkipEmpty p0+    case x of+      Left r       -> return (Nothing, return r)+      Right (a,p1) -> step (yield a >>) (_parse parser a) p1+  where+    step diffP res p0 = case res of+      Fail _ c m -> return (Just (Left (ParsingError c m)), diffP p0)+      Done a b   -> return (Just (Right b), yield a >> p0)+      Partial k  -> do+        x <- nextSkipEmpty p0+        case x of+          Left e -> step diffP (k mempty) (return e)+          Right (a,p1) -> step (diffP . (yield a >>)) (k a) p1 {-# INLINABLE parse #-} --- | Continuously run an Attoparsec 'Parser' on input from the given 'Producer',--- sending downstream pairs of each successfully parsed entity together with the--- length of input consumed in order to produce it.++-- | Convert a producer of 'ParserInput' to a producer of parsed values. ----- This 'Producer' runs until it either runs out of input or a parsing--- failure occurs, in which case it returns 'Left' with a 'I.ParsingError' and a--- 'Producer' with any leftovers. You can use 'P.errorP' to turn the 'Either'--- return value into an 'Control.Monad.Trans.Error.ErrorT' monad transformer.-parseMany-  :: (Monad m, I.ParserInput a)-  => Parser a b       -- ^Attoparsec parser.-  -> Producer a m r   -- ^Producer from which to draw input.-  -> Producer' (Int, b) m (Either (I.ParsingError, Producer a m r) r)-parseMany attoparser src = do-    (me, src') <- P.runStateP src go-    return $ case me of-      Left  e -> Left  (e, src')-      Right r -> Right r+-- This producer returns 'Right' when end-of-input is reached successfully,+-- otherwise it returns a 'ParsingError' and the leftovers including+-- the malformed input that couldn't be parsed. You can use 'Pipes.Lift.errorP'+-- to promote the 'Either' return value to an 'Control.Monad.Trans.Error.ErrorT'+-- monad transformer.+parsed+  :: (Monad m, ParserInput a)+  => Attoparsec.Parser a b  -- ^ Attoparsec parser+  -> Producer a m r         -- ^ Raw input+  -> Producer b m (Either (ParsingError, Producer a m r) r)+parsed parser = go   where-    go = do-        eof <- lift isEndOfParserInput-        if eof-          then do-            ra <- lift Pp.draw-            case ra of-              Left  r -> return (Right r)-              Right _ -> error "Pipes.Attoparsec.parseMany: impossible!!"-          else do-            eb <- lift (parse attoparser)-            case eb of-              Left  e -> return (Left e)-              Right b -> yield b >> go+    go p0 = do+      x <- lift (nextSkipEmpty p0)+      case x of+        Left r       -> return (Right r)+        Right (a,p1) -> step (yield a >>) (_parse parser a) p1+    step diffP res p0 = case res of+      Fail _ c m -> return (Left (ParsingError c m, diffP p0))+      Done a b   -> yield b >> go (yield a >> p0)+      Partial k  -> do+        x <- lift (nextSkipEmpty p0)+        case x of+          Left e -> step diffP (k mempty) (return e)+          Right (a,p1) -> step (diffP . (yield a >>)) (k a) p1+{-# INLINABLE parsed #-}  --------------------------------------------------------------------------------+-- $lengths+-- Like the functions above, but these also provide information about+-- the length of input consumed in order to fully parse each value.+-------------------------------------------------------------------------------- --- | Like 'P.isEndOfInput', except it also consumes and discards leading--- empty 'I.ParserInput' chunks.-isEndOfParserInput-  :: (I.ParserInput a, Monad m)-  => S.StateT (Producer a m r) m Bool-isEndOfParserInput = do-    ma <- Pp.draw-    case ma of-      Left  _         -> return True-      Right a-        | a == mempty -> isEndOfParserInput-        | otherwise   -> Pp.unDraw a >> return False+-- | Like 'parse', but also returns the length of input consumed to parse the+-- value.+parseL+    :: (Monad m, ParserInput a)+    => Attoparsec.Parser a b                           -- ^ Attoparsec parser+    -> Pipes.Parser a m (Maybe (Either ParsingError (Int, b))) -- ^ Pipes parser+parseL parser = S.StateT $ \p0 -> do+    x <- nextSkipEmpty p0+    case x of+      Left r       -> return (Nothing, return r)+      Right (a,p1) -> step (yield a >>) (_parse parser a) p1 (_length a)+  where+    step diffP res p0 !len = case res of+      Fail _ c m -> return (Just (Left (ParsingError c m)), diffP p0)+      Done a b   -> return (Just (Right (len - _length a, b)), yield a >> p0)+      Partial k  -> do+        x <- nextSkipEmpty p0+        case x of+          Left e -> step diffP (k mempty) (return e) len+          Right (a,p1) -> step (diffP . (yield a >>)) (k a) p1 (len + _length a)+{-# INLINABLE parseL #-}+++-- | Like 'parsed', except this tags each parsed value with the length of input+-- consumed to parse the value.+parsedL+    :: (Monad m, ParserInput a)+    => Attoparsec.Parser a b    -- ^ Attoparsec parser+    -> Producer a m r           -- ^ Raw input+    -> Producer (Int, b) m (Either (ParsingError, Producer a m r) r)+parsedL parser = go+  where+    go p0 = do+      x <- lift (nextSkipEmpty p0)+      case x of+        Left r       -> return (Right r)+        Right (a,p1) -> step (yield a >>) (_parse parser a) p1 (_length a)+    step diffP res p0 !len = case res of+      Fail _ c m -> return (Left (ParsingError c m, diffP p0))+      Done a b   -> yield (len - _length a, b) >> go (yield a >> p0)+      Partial k  -> do+        x <- lift (nextSkipEmpty p0)+        case x of+          Left e -> step diffP (k mempty) (return e) len+          Right (a,p1) -> step (diffP . (yield a >>)) (k a) p1 (len + _length a)+{-# INLINABLE parsedL #-}++--------------------------------------------------------------------------------++-- | Like 'Pipes.Parse.isEndOfInput', except that it also consumes and discards+-- leading empty chunks.+isEndOfParserInput :: (Monad m, ParserInput a) => Pipes.Parser a m Bool+isEndOfParserInput = S.StateT $ \p0 -> do+    x <- nextSkipEmpty p0+    case x of+       Left r        -> return (True,  return r)+       Right (a, p1) -> return (False, yield a >> p1) {-# INLINABLE isEndOfParserInput #-}++--------------------------------------------------------------------------------++-- | A class for valid @attoparsec@ input types+class (Eq a, Monoid a) => ParserInput a where+    _parse  :: Attoparsec.Parser a b -> a -> IResult a b+    _length :: a -> Int++-- | Strict 'ByteString'.+instance ParserInput ByteString where+    _parse  = Data.Attoparsec.ByteString.parse+    {-# INLINE _parse #-}+    _length = Data.ByteString.length+    {-# INLINE _length #-}++-- | Strict 'Text'.+instance ParserInput Text where+    _parse  = Data.Attoparsec.Text.parse+    {-# INLINE _parse #-}+    _length = Data.Text.length+    {-# INLINE _length #-}++--------------------------------------------------------------------------------++-- | A parsing error report, as provided by Attoparsec's 'Fail'.+data ParsingError = ParsingError+    { peContexts :: [String]  -- ^ Contexts where the parsing error occurred.+    , peMessage  ::  String   -- ^ Parsing error description message.+    } deriving (Show, Read, Eq, Data, Typeable)++instance Exception ParsingError++--------------------------------------------------------------------------------+-- Internal stuff++-- | Like 'Pipes.next', except it skips leading 'mempty' chunks.+nextSkipEmpty+  :: (Monad m, Eq a, Monoid a)+  => Producer a m r+  -> m (Either r (a, Producer a m r))+nextSkipEmpty = go where+    go p0 = do+      x <- next p0+      case x of+         Left  _        -> return x+         Right (a,p1)+          | a == mempty -> go p1+          | otherwise   -> return x+{-# INLINABLE nextSkipEmpty #-} 
− src/Pipes/Attoparsec/Internal.hs
@@ -1,114 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE FlexibleInstances #-}---- | This module provides low-level integration with Attoparsec and is likely--- to be modified in backwards-incompatible ways in the future.------ Use the stable API exported by the "Pipes.Attoparsec" module instead.--module Pipes.Attoparsec.Internal-  ( -- * Types-    ParsingError(..)-  , ParserInput-    -- * Parsing-  , parseWithDraw-  , parseWithRaw-  ) where------------------------------------------------------------------------------------import           Control.Exception                 (Exception)-import           Control.Monad.Trans.Error         (Error)-import           Data.Attoparsec.Types             (Parser, IResult(..))-import qualified Data.Attoparsec.ByteString        as AB-import qualified Data.Attoparsec.Text              as AT-import qualified Data.ByteString.Char8             as B-import           Data.Data                         (Data, Typeable)-import           Data.Monoid                       (Monoid(mempty))-import qualified Data.Text                         as T-import           Pipes                             (Producer)-import qualified Pipes.Parse                       as Pp-import           Prelude                           hiding (null, length)-------------------------------------------------------------------------------------- | A parsing error report, as provided by Attoparsec's 'Fail'.-data ParsingError = ParsingError-  { peContexts :: [String]  -- ^ Contexts where the parsing error occurred.-  , peMessage  :: String    -- ^ Parsing error description message.-  } deriving (Show, Read, Eq, Data, Typeable)--instance Exception ParsingError-instance Error     ParsingError------------------------------------------------------------------------------------instance (Monad m, ParserInput a) => Error (ParsingError, Producer a m r)-------------------------------------------------------------------------------------- | A class for valid Attoparsec input types: strict 'T.Text' and--- strict 'B.ByteString'.-class (Eq a, Monoid a) => ParserInput a where-    -- | Run a 'Parser' with input @a@.-    parse :: Parser a b -> a -> IResult a b-    -- | Length of @a@.-    length :: a -> Int--instance ParserInput B.ByteString where-    parse      = AB.parse-    length     = B.length--instance ParserInput T.Text where-    parse      = AT.parse-    length     = T.length-------------------------------------------------------------------------------------- | Run a parser drawing input from the given monadic action as needed.-parseWithRaw-  :: (Monad m, ParserInput a)-  => m a-  -- ^An action that will be executed to provide the parser with more input-  -- as needed. If the action returns 'mempty', then it's assumed no more-  -- input is available.-  -> Parser a r-  -- ^Parser to run on the given input-  -> m (Either ParsingError (Int, r), Maybe a)-  -- ^Either a parser error or a pair of a result and the parsed input length,-  -- as well as an any leftovers.-parseWithRaw refill p = refill >>= \a -> step (length a) (parse p a)-  where-    step !len res = case res of-        Partial k  -> refill >>= \a -> step (len + length a) (k a)-        Done t r   -> return (Right (len - length t, r), mayInput t)-        Fail t c m -> return (Left  (ParsingError c m) , mayInput t)-{-# INLINABLE parseWithRaw #-}---- | Run a parser drawing input from the underlying 'Producer'.-parseWithDraw-  :: (Monad m, ParserInput a)-  => Parser a b-  -- ^Parser to run on the given input-  -> Pp.StateT (Producer a m r) m (Either ParsingError (Int, b), Maybe a)-  -- ^Either a parser error or a pair of a result and the parsed input length,-  -- as well as an any leftovers.-parseWithDraw = parseWithRaw refill-  where-    refill = do-        ra <- Pp.draw-        case ra of-          Left  _         -> return mempty-          Right a-            | a == mempty -> refill-            | otherwise   -> return a-{-# INLINABLE parseWithDraw #-}-------------------------------------------------------------------------------------- | Wrap @a@ in 'Just' if not-null. Otherwise, 'Nothing'.-mayInput :: ParserInput a => a -> Maybe a-mayInput x | x == mempty = Nothing-           | otherwise   = Just x-{-# INLINE mayInput #-}
tests/Main.hs view
@@ -1,19 +1,12 @@ module Main (main) where -import Test.Framework (defaultMain, testGroup)-import Test.Framework.Providers.QuickCheck2 (testProperty)-import Test.Framework.Providers.HUnit (testCase)-import Test.HUnit- import qualified Test.Attoparsec--main = defaultMain tests+import qualified Test.Tasty as Tasty -tests =-    [ testGroup "Sample."   sampleTests-    , testGroup "Attoparsec." Test.Attoparsec.tests-    ]+main :: IO ()+main = Tasty.defaultMain tests -sampleTests = [ testProperty "QuickCheck" $ \x -> const True (x :: Int) == True-              , testCase     "HUnit"      $ True @?= True-              ]+tests :: Tasty.TestTree+tests = Tasty.testGroup "root"+      [ Tasty.testGroup "Attoparsec." Test.Attoparsec.tests+      ]
tests/Test/Attoparsec.hs view
@@ -1,20 +1,18 @@ {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE NoMonomorphismRestriction #-}  module Test.Attoparsec (tests) where -import           Control.Monad-import           Pipes-import qualified Pipes.Lift                         as P-import qualified Pipes.Prelude                      as P-import           Control.Monad.Trans.Error          (runErrorT)-import           Control.Monad.Trans.Writer.Strict  (runWriterT, tell)-import           Pipes.Attoparsec                   (parseMany)-import qualified Data.Attoparsec.Text               as AT-import           Data.Functor.Identity              (runIdentity)-import qualified Data.Text                          as T-import           Test.Framework.Providers.HUnit     (testCase)-import           Test.HUnit                         (Assertion, assert)+import           Control.Monad                     (replicateM_)+import           Control.Monad.Trans.Writer.Strict (runWriterT, tell)+import qualified Data.Attoparsec.Text              as AT+import           Data.Functor.Identity             (runIdentity)+import           Data.Text                         (Text)+import           Pipes                             (each, for, lift, runEffect)+import           Pipes.Attoparsec                  (parsed)+import           Pipes.Prelude                     (toListM)+import           Test.HUnit                        (Assertion, assert)+import           Test.Tasty                        (TestTree)+import           Test.Tasty.HUnit                  (testCase)  -- | Parses a 'Char' repeated four times. four :: AT.Parser Char@@ -23,20 +21,22 @@    replicateM_ 3 $ AT.char c    return c -type ParseTest = (Bool, String, [T.Text], [Char], [T.Text])-+type ParseTest = (Bool, String, [Text], [Char], [Text])  assertFoursTest :: ParseTest -> Assertion-assertFoursTest (ok, _title, input, output, mlo) = assert . runIdentity $ do-  (e,res) <- runWriterT . runErrorT . P.toListM $-                for (P.errorP $ parseMany four $ each input)-                    (\a -> lift . lift $ tell [snd a])-  let (okErr,mlo') = case e of-       Left (_, pmlo') -> (not ok, P.toList . fmap fst $ P.runWriterP pmlo')-       Right _         -> (ok, [])-  let okMlo = mlo' == mlo-      okRes = res == output-  return $ okMlo && okErr && okRes+assertFoursTest (ok, _title, input, output, mlo) =+    assert $ res == output+          && isRight e == ok+          && mlo' == mlo+  where+    (e, res) = runIdentity . runWriterT . runEffect+             $ for (parsed four $ each input)+                   (\c -> lift $ tell [c])+    mlo' = case e of+               Right _ -> []+               Left (_,pmlo') -> fst . runIdentity+                                     . runWriterT+                                     $ toListM pmlo'  foursTests :: [ParseTest] foursTests =@@ -46,9 +46,9 @@   , (True  ,"1 chunk: One twice"        ,["aaaaaaaa"]       ,['a','a'] ,[])   , (True  ,"1 chunk: Two"              ,["aaaabbbb"]       ,['a','b'] ,[])   , (True  ,"1 chunk: Two between null" ,["aaaa","","bbbb"] ,['a','b'] ,[])-  , (False ,"1 chunk: Partial"          ,["aaaab"]          ,['a']     ,[])-  , (False ,"1 chunk: Wrong"            ,["aaxbb"]          ,[]        ,["xbb"])-  , (False ,"1 chunk: One then wrong"   ,["aaaavz"]         ,['a']     ,["z"])+  , (False ,"1 chunk: Partial"          ,["aaaab"]          ,['a']     ,["b"])+  , (False ,"1 chunk: Wrong"            ,["aaxbb"]          ,[]        ,["aaxbb"])+  , (False ,"1 chunk: One then wrong"   ,["aaaavz"]         ,['a']     ,["vz"])   , (True  ,"2 chunk: Empty"            ,["",""]            ,[]        ,[])   , (True  ,"2 chunk: Empty then one"   ,["","aaaa"]        ,['a']     ,[])   , (True  ,"2 chunk: One"              ,["a","aaa"]        ,['a']     ,[])@@ -56,12 +56,20 @@   , (True  ,"2 chunk: One''"            ,["aaa","a"]        ,['a']     ,[])   , (True  ,"2 chunk: One'''"           ,["aaaa",""]        ,['a']     ,[])   , (True  ,"2 chunk: Two"              ,["aaaa","bbbb"]    ,['a','b'] ,[])-  , (False ,"2 chunk: Wrong"            ,["abcd","efgh"]    ,[]        ,["bcd","efgh"])-  , (False ,"2 chunk: One then wrong"   ,["aaaab","bxz"]    ,['a']     ,["xz"])+  , (False ,"2 chunk: Wrong"            ,["abcd","efgh"]    ,[]        ,["abcd","efgh"])+  , (False ,"2 chunk: Wrong'"           ,["a","axbb"]       ,[]        ,["a","axbb"])+  , (False ,"2 chunk: One then wrong"   ,["aaaab","bxz"]    ,['a']     ,["b","bxz"])   , (True  ,"3 chunk: One"              ,["a","a","aa"]     ,['a']     ,[])+  , (False ,"3 chunk: Wrong"            ,["a","a","axbb"]   ,[]        ,["a","a","axbb"])   ] +testCaseFoursTest :: (Bool, [Char], [Text], [Char], [Text]) -> TestTree testCaseFoursTest ft@(_,name,_,_,_) =   testCase ("Fours." ++ name) $ assertFoursTest ft +tests :: [TestTree] tests = map testCaseFoursTest foursTests++isRight :: Either a b -> Bool+isRight (Right _) = True+isRight _         = False