packages feed

pipes-attoparsec 0.0.2 → 0.1.0.1

raw patch · 11 files changed

+785/−68 lines, 11 filesdep +HUnitdep +QuickCheckdep +bytestringdep −pipes-coredep ~attoparsecnew-uploaderPVP ok

version bump matches the API change (PVP)

Dependencies added: HUnit, QuickCheck, bytestring, pipes, pipes-attoparsec, test-framework, test-framework-hunit, test-framework-quickcheck2, text

Dependencies removed: pipes-core

Dependency ranges changed: attoparsec

API changes (from Hackage documentation)

- Control.Pipe.Attoparsec: DivergentParser :: ParseError
- Control.Pipe.Attoparsec: ParseError :: [String] -> String -> ParseError
- Control.Pipe.Attoparsec: data ParseError
- Control.Pipe.Attoparsec: errorContexts :: ParseError -> [String]
- Control.Pipe.Attoparsec: errorMessage :: ParseError -> String
- Control.Pipe.Attoparsec: instance Exception ParseError
- Control.Pipe.Attoparsec: instance Show ParseError
- Control.Pipe.Attoparsec: instance Typeable ParseError
- Control.Pipe.Attoparsec: pipeParser :: (Monoid a, Monad m) => (a -> IResult a r) -> Pipe a x m (a, Either ParseError r)
+ Control.Proxy.Attoparsec: parserD :: (Proxy p, Monad m, AttoparsecInput a) => Parser a b -> () -> p (ParserStatus a) (ParserSupply a) () b m r
+ Control.Proxy.Attoparsec: parserInputD :: (Monad m, Proxy p) => ParserStatus a -> p () a (ParserStatus a) (ParserSupply a) m r
+ Control.Proxy.Attoparsec.Control: limitInputLength :: (Monad m, Proxy p, AttoparsecInput a) => Int -> ParserStatus a -> EitherP BadInput p (ParserStatus a) (ParserSupply a) (ParserStatus a) (ParserSupply a) m r
+ Control.Proxy.Attoparsec.Control: retryLeftovers :: (Monad m, Proxy p, AttoparsecInput a) => ParserStatus a -> p (ParserStatus a) (ParserSupply a) (ParserStatus a) (ParserSupply a) m r
+ Control.Proxy.Attoparsec.Control: skipMalformedChunks :: (Monad m, Proxy p, AttoparsecInput a) => ParserStatus a -> p (ParserStatus a) (ParserSupply a) (ParserStatus a) (ParserSupply a) m r
+ Control.Proxy.Attoparsec.Control: throwParsingErrors :: (Monad m, Proxy p, AttoparsecInput a) => ParserStatus a -> EitherP BadInput p (ParserStatus a) (ParserSupply a) (ParserStatus a) (ParserSupply a) m r
+ Control.Proxy.Attoparsec.Tutorial: Name :: Text -> Name
+ Control.Proxy.Attoparsec.Tutorial: data Name
+ Control.Proxy.Attoparsec.Tutorial: hello :: Parser Name
+ Control.Proxy.Attoparsec.Tutorial: helloPipe1 :: (Proxy p, Monad m) => () -> Pipe p Text Name m r
+ Control.Proxy.Attoparsec.Tutorial: helloPipe2 :: (Proxy p, Monad m) => () -> Pipe p Text Name m r
+ Control.Proxy.Attoparsec.Tutorial: helloPipe3 :: (Proxy p, Monad m) => () -> Pipe (EitherP BadInput p) Text Name m r
+ Control.Proxy.Attoparsec.Tutorial: helloPipe4 :: (Proxy p, Monad m) => () -> Pipe (EitherP BadInput p) Text Name m r
+ Control.Proxy.Attoparsec.Tutorial: helloPipe5 :: (Proxy p, Monad m) => () -> Pipe (EitherP BadInput p) Text Name m r
+ Control.Proxy.Attoparsec.Tutorial: helloPipe6 :: (Proxy p, Monad m) => () -> Pipe p Text Name m r
+ Control.Proxy.Attoparsec.Tutorial: input1 :: [Text]
+ Control.Proxy.Attoparsec.Tutorial: input2 :: [Text]
+ Control.Proxy.Attoparsec.Tutorial: instance Eq Name
+ Control.Proxy.Attoparsec.Tutorial: instance Show Name
+ Control.Proxy.Attoparsec.Tutorial: skipPartialResults :: (Monad m, Proxy p, AttoparsecInput a) => ParserStatus a -> p (ParserStatus a) (ParserSupply a) (ParserStatus a) (ParserSupply a) m r
+ Control.Proxy.Attoparsec.Types: Failed :: a -> ParserError -> ParserStatus a
+ Control.Proxy.Attoparsec.Types: InputTooLong :: Int -> BadInput
+ Control.Proxy.Attoparsec.Types: MalformedInput :: ParserError -> BadInput
+ Control.Proxy.Attoparsec.Types: ParserError :: [String] -> String -> ParserError
+ Control.Proxy.Attoparsec.Types: Parsing :: Int -> ParserStatus a
+ Control.Proxy.Attoparsec.Types: Resume :: SupplyUse
+ Control.Proxy.Attoparsec.Types: Start :: SupplyUse
+ Control.Proxy.Attoparsec.Types: class Eq a => AttoparsecInput a
+ Control.Proxy.Attoparsec.Types: data BadInput
+ Control.Proxy.Attoparsec.Types: data ParserError
+ Control.Proxy.Attoparsec.Types: data ParserStatus a
+ Control.Proxy.Attoparsec.Types: data SupplyUse
+ Control.Proxy.Attoparsec.Types: drop :: AttoparsecInput a => Int -> a -> a
+ Control.Proxy.Attoparsec.Types: errorContexts :: ParserError -> [String]
+ Control.Proxy.Attoparsec.Types: errorMessage :: ParserError -> String
+ Control.Proxy.Attoparsec.Types: instance AttoparsecInput ByteString
+ Control.Proxy.Attoparsec.Types: instance AttoparsecInput Text
+ Control.Proxy.Attoparsec.Types: instance Eq BadInput
+ Control.Proxy.Attoparsec.Types: instance Eq ParserError
+ Control.Proxy.Attoparsec.Types: instance Eq SupplyUse
+ Control.Proxy.Attoparsec.Types: instance Eq a => Eq (ParserStatus a)
+ Control.Proxy.Attoparsec.Types: instance Show BadInput
+ Control.Proxy.Attoparsec.Types: instance Show ParserError
+ Control.Proxy.Attoparsec.Types: instance Show SupplyUse
+ Control.Proxy.Attoparsec.Types: instance Show a => Show (ParserStatus a)
+ Control.Proxy.Attoparsec.Types: itlLength :: BadInput -> Int
+ Control.Proxy.Attoparsec.Types: length :: AttoparsecInput a => a -> Int
+ Control.Proxy.Attoparsec.Types: miParserErrror :: BadInput -> ParserError
+ Control.Proxy.Attoparsec.Types: null :: AttoparsecInput a => a -> Bool
+ Control.Proxy.Attoparsec.Types: parse :: AttoparsecInput a => Parser a b -> a -> IResult a b
+ Control.Proxy.Attoparsec.Types: psError :: ParserStatus a -> ParserError
+ Control.Proxy.Attoparsec.Types: psLeftover :: ParserStatus a -> a
+ Control.Proxy.Attoparsec.Types: psLength :: ParserStatus a -> Int
+ Control.Proxy.Attoparsec.Types: type ParserSupply a = (SupplyUse, a)

Files

− Control/Pipe/Attoparsec.hs
@@ -1,45 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}-module Control.Pipe.Attoparsec (-  ParseError(..),-  pipeParser,-  ) where--import qualified Control.Exception as E-import Control.Pipe-import Control.Pipe.Combinators-import Control.Pipe.Exception-import Data.Attoparsec.Types-import Data.Maybe-import Data.Monoid-import Data.Typeable---- | A parse error as returned by Attoparsec.-data ParseError-  = ParseError {-      errorContexts :: [String],  -- ^ Contexts where the error occurred.-      errorMessage :: String      -- ^ Error message.-    }-  | DivergentParser               -- ^ Returned if a parser does not terminate-                                  -- when its input is exhausted.-    deriving (Show, Typeable)--instance E.Exception ParseError---- | Convert a parser continuation into a Pipe.------ To get a parser continuation from a 'Parser', use the @parse@ function of the--- appropriate Attoparsec module.-pipeParser :: (Monoid a, Monad m) => (a -> IResult a r) -> Pipe a x m (a, Either ParseError r)-pipeParser p = go p-  where-    go p = do-      chunk <- tryAwait-      case p (maybe mempty id chunk) of-        Fail leftover contexts msg ->-          return (leftover, Left $ ParseError contexts msg)-        Partial p' ->-          if isNothing chunk-            then return (mempty, Left DivergentParser)-            else go p'-        Done leftover result ->-          return (leftover, Right result)
LICENSE view
@@ -1,4 +1,5 @@-Copyright (c)2012, Paolo Capriotti+Copyright (c) 2012-2012, Paolo Capriotti+Copyright (c) 2012-, Renzo Carbonara  All rights reserved. @@ -13,7 +14,7 @@       disclaimer in the documentation and/or other materials provided       with the distribution. -    * Neither the name of Paolo Capriotti nor the names of other+    * Neither the name of Renzo Carbonara nor the names of other       contributors may be used to endorse or promote products derived       from this software without specific prior written permission. 
+ README.md view
@@ -0,0 +1,5 @@+# pipes-attoparsec++Utilities to convert an Attoparsec Parser into a Pipe.++Version 0.1.\* is not backwards compatible with previous versions.
pipes-attoparsec.cabal view
@@ -1,24 +1,67 @@-Name: pipes-attoparsec-Version: 0.0.2-License: BSD3-License-file: LICENSE-Author: Paolo Capriotti-Maintainer: p.capriotti@gmail.com-Stability: Experimental-Homepage: https://github.com/pcapriotti/pipes-extra-Category: Text-Build-type: Simple-Synopsis: Utilities to convert a parser into a pipe.-Description: Utilities to convert a parser into a pipe.-Cabal-version: >=1.8+name: pipes-attoparsec+version: 0.1.0.1+license: BSD3+license-file: LICENSE+copyright: Copyright (c) Paolo Capriotti 2012-2012,+                         Renzo Carbonara 2012-+author: Paolo Capriotti, Renzo Carbonara+maintainer: renzocarbonaraλgmail.com+stability: Experimental+tested-with: GHC ==7.4.1, GHC ==7.6.1+homepage: https://github.com/k0001/pipes-attoparsec+bug-reports: https://github.com/k0001/pipes-attoparsec+category: Pipes, Parser+build-type: Simple+cabal-version: >=1.8+synopsis: Utilities to convert an Attoparsec parser into a pipe Pipe.+description:+  Utilities to convert an Attoparsec 'Data.Attoparsec.Types.Parser' into+  a 'Control.Proxy.Synonym.Pipe'.+  .+  See "Control.Proxy.Attoparsec.Tutorial" for an extensive introduction with+  examples.+  .+  Version 0.1.* is not backwards compatible with previous versions.+extra-source-files:+  README.md -Source-Repository head-    Type: git-    Location: https://github.com/pcapriotti/pipes-extra+source-repository head+    type: git+    location: git://github.com/k0001/pipes-attoparsec.git -Library-  Exposed-modules: Control.Pipe.Attoparsec-  Build-depends:+library+  hs-source-dirs:  src+  exposed-modules: Control.Proxy.Attoparsec+                   Control.Proxy.Attoparsec.Control+                   Control.Proxy.Attoparsec.Tutorial+                   Control.Proxy.Attoparsec.Types+  build-depends:+    base (==4.*),+    pipes (>=3.0),+    attoparsec (>=0.10),+    bytestring,+    text++  ghc-options:+    -Wall -fno-warn-missing-signatures++test-suite tests+  type:           exitcode-stdio-1.0+  hs-source-dirs: tests+  main-is:        Main.hs+  other-modules:  Test.Attoparsec+                  Test.Tutorial+  ghc-options:+    -Wall -rtsopts -fno-warn-missing-signatures++  build-depends:     base (== 4.*),-    pipes-core (== 0.1.*),-    attoparsec (== 0.10.*)+    pipes (>= 3.0),+    attoparsec (>= 0.10),+    text,+    pipes-attoparsec,+    QuickCheck (== 2.*),+    HUnit (== 1.*),+    test-framework (>= 0.6),+    test-framework-quickcheck2 (>= 0.2),+    test-framework-hunit (>= 0.2)
+ src/Control/Proxy/Attoparsec.hs view
@@ -0,0 +1,94 @@+-- | This module provides utilities to turn your Attoparsec @'Parser' a+-- b@ into a 'Pipe' that parses @a@ values into @b@ values as they flow+-- downstream.+--+-- See "Control.Proxy.Attoparsec.Tutorial" for an extensive introduction+-- with examples.++module Control.Proxy.Attoparsec+  ( -- * Parsing Pipe+    -- $pieces+    parserInputD+  , parserD+    -- * Modules+  , module Control.Proxy.Attoparsec.Types+  , module Control.Proxy.Attoparsec.Control+  ) where++import           Control.Proxy+import           Control.Proxy.Attoparsec.Control+import           Control.Proxy.Attoparsec.Types+import           Data.Attoparsec.Types            (IResult (..), Parser)+import           Prelude                          hiding (length, null)+++-- $pieces+--+-- A 'Pipe' that parses @a@ input flowing downstream into @b@ values is+-- made of at least two smaller cooperating 'Proxy's:+--+--  1. 'parserInputD': Prepares @a@ input received from upstream to be+--     consumed by a downstream parsing 'Proxy'.+--+--  2. 'parserD': Repeatedly runs a given @'Parser' a b@ on input 'a'+--     from upstream, and sends 'b' values downstream.+--+-- Given a @'Parser' a b@ named @myParser@, the simplest way to use+-- these 'Proxy's together is:+--+-- > myParsingPipe :: (Proxy p, Monad m, AttoparsecInput a) => Pipe a b m r+-- > myParsingPipe = parserInputD >-> parserD myParser+--+-- In between these two 'Proxy's, you can place other 'Proxy's to handle+-- extraordinary situations like parsing failures or limiting input+-- length. "Control.Proxy.Attoparsec.Control" exports some useful+-- 'Proxy's that fit this place. If you skip using any of those, the+-- default behavior is that of+-- 'Control.Proxy.Attoparsec.Control.skipMalformedChunks': to simply+-- ignore all parsing errors and malformed input, and start parsing new+-- input as soon as it's available.++++-- | 'Proxy' turning 'a' values flowing downstream into+-- @'ParserSupply' a@ values to be consumed by a downstream parsing+-- 'Proxy'.+--+-- This 'Proxy' responds with @'Resume' a@ to any 'ParserStatus' value+-- received from downstream.+parserInputD :: (Monad m, Proxy p)+             => ParserStatus a -> p () a (ParserStatus a) (ParserSupply a) m r+parserInputD _ = runIdentityP . forever $ request () >>= respond . (,) Resume+++-- | 'Proxy' using the given @'Parser' a b@ to repeatedly parse pieces+-- of @'ParserSupply' a@ values flowing downstream into @b@ values.+--+-- When more input is needed, a @'ParserStatus' a@ value reporting the+-- current parsing status is sent upstream, and in exchange a+-- @'ParserSupply' a@ value is expected, containing more input to be+-- parsed and directives on how to use it (see 'ParserSupply'+-- documentation).+parserD :: (Proxy p, Monad m, AttoparsecInput a)+        => Parser a b+        -> () -> p (ParserStatus a) (ParserSupply a) () b m r+parserD parser () = runIdentityP . forever $ ask k0 0 (Parsing 0)+  where+    k0 = parse parser++    requestNonEmpty status = go where+      go = do+        ps@(_, chunk) <- request status+        if null chunk then go else return ps++    ask k len status = do+      (su, chunk) <- requestNonEmpty status+      case su of+        Start  -> use (k0 chunk) 0+        Resume -> use (k  chunk) (len + length chunk)++    use (Partial k)         len = ask k  len $ Parsing len+    use (Fail rest ctx msg) _   = ask k0 0   $ Failed rest (ParserError ctx msg)+    use (Done rest result)  _   = respond result >> use (k0 rest) 0++
+ src/Control/Proxy/Attoparsec/Control.hs view
@@ -0,0 +1,80 @@+-- | This module exports some useful 'Proxy's that act upon+-- 'ParserStatus' values received from downstream.++module Control.Proxy.Attoparsec.Control+  ( skipMalformedChunks+  , retryLeftovers+  , throwParsingErrors+  , limitInputLength+  ) where++import           Control.Proxy+import           Control.Proxy.Attoparsec.Types+import qualified Control.Proxy.Trans.Either as PE+import           Prelude                    hiding (drop, null)++-- | If a downstream parsing 'Proxy' reports a parser failure, skip the+-- whole input /chunk/ being processed, including any left-overs, and+-- start processing new input as soon as it's available.+--+-- Useful when the input found in a single @'ParserSupply' a@ is+-- supposed to be well-formed, so it can be safely skipped if it is not.+skipMalformedChunks+  :: (Monad m, Proxy p, AttoparsecInput a)+  => ParserStatus a+  -> p (ParserStatus a) (ParserSupply a) (ParserStatus a) (ParserSupply a) m r+skipMalformedChunks = runIdentityK . foreverK $ go+  where go x@(Failed _ _) = request x >>= \(_,a) -> respond (Start, a)+        go x              = request x >>= respond+++-- | If a downstream parsing 'Proxy' reports a parsing failure, then+-- keep retrying with any left-over input, skipping individual bits each+-- time. If there are no left-overs, then more input is requestsd form+-- upstream.+retryLeftovers+  :: (Monad m, Proxy p, AttoparsecInput a)+  => ParserStatus a+  -> p (ParserStatus a) (ParserSupply a) (ParserStatus a) (ParserSupply a) m r+retryLeftovers = runIdentityK . foreverK $ go+  where+    go s@(Failed _ _) = retry s >>= moreSupply >>= respond+    go s              = request s >>= respond++    retry s@(Failed rest _) = do+      s' <- respond (Start, rest)+      if s' == s then return s+                 else retry s'+    retry s = return s++    moreSupply s@(Failed rest _) = do+      let rest' = drop 1 rest+      if null rest'+        then request s+        else return (Start, rest')+    moreSupply s = request s+++-- | If a downstream parsing 'Proxy' reports a parser failure, then+-- throw a 'MalformedInput' error in the 'EitherP' proxy transformer.+throwParsingErrors+  :: (Monad m, Proxy p, AttoparsecInput a)+  => ParserStatus a+  -> PE.EitherP BadInput p (ParserStatus a) (ParserSupply a) (ParserStatus a) (ParserSupply a) m r+throwParsingErrors = foreverK go+  where go (Failed _ e) = PE.throw $ MalformedInput e+        go x            = request x >>= respond+++-- | If a downstream parsing 'Proxy' doesn't produce a value after+-- having consumed input of at least the given length, then throw an+-- 'InputTooLong' error in the 'EitherP' proxy transformer.+limitInputLength+  :: (Monad m, Proxy p, AttoparsecInput a)+  => Int+  -> ParserStatus a+  -> PE.EitherP BadInput p (ParserStatus a) (ParserSupply a) (ParserStatus a) (ParserSupply a) m r+limitInputLength n = foreverK go+  where go (Parsing m) | m >= n = PE.throw $ InputTooLong m+        go x                    = request x >>= respond+
+ src/Control/Proxy/Attoparsec/Tutorial.lhs view
@@ -0,0 +1,344 @@+> {-# LANGUAGE OverloadedStrings #-}++| In this tutorial you will learn how to use this library. The /Simple example/+section should be enough to get you going, but you can keep reading if you want+to better understand how to deal with complex parsing scenarios.++You may import this module and try the subsequent examples as you go.+++> module Control.Proxy.Attoparsec.Tutorial+>   (-- * Simple example+>    -- $example-simple+>+>    -- * Parsing control proxies+>    -- ** Handling parser errors+>    -- $example-errors+>+>    -- ** Composing+>    -- $example-compose-control+>+>    -- ** Custom behavior+>    -- $example-control-custom+>+>    -- * Try for yourself+>    -- $example-try+>     Name(..)+>   , hello+>   , input1+>   , input2+>   , helloPipe1+>   , helloPipe2+>   , helloPipe3+>   , helloPipe4+>   , helloPipe5+>   , helloPipe6+>   , skipPartialResults+>   ) where+>+> import Control.Proxy+> import Control.Proxy.Attoparsec+> import Control.Proxy.Trans.Either+> import Data.Attoparsec.Text+> import Data.Text+>+> data Name = Name Text+>           deriving (Show, Eq)+>+> hello :: Parser Name+> hello = fmap Name $ "Hello " .*> takeWhile1 (/='.') <*. "."+>+> input1 :: [Text]+> input1 =+>   [ "Hello Kate."+>   , "Hello Mary.Hello Jef"+>   , "f."+>   , "Hel"+>   , "lo Tom."+>   ]+>+> input2 :: [Text]+> input2 =+>   [ "Hello Amy."+>   , "Hello, Hello Tim."+>   , "Hello Bob."+>   , "Hello James"+>   , "Hello"+>   , "Hello World."+>   , "HexHello Jon."+>   , "H"+>   , "ello Ann"+>   , "."+>   , "Hello Jean-Luc."+>   ]+>+> helloPipe1 :: (Proxy p, Monad m) => () -> Pipe p Text Name m r+> helloPipe1 = parserInputD >-> parserD hello+>+> helloPipe2 :: (Proxy p, Monad m) => () -> Pipe p Text Name m r+> helloPipe2 = parserInputD >-> retryLeftovers >-> parserD hello+>+> helloPipe3 :: (Proxy p, Monad m) => () -> Pipe (EitherP BadInput p) Text Name m r+> helloPipe3 = parserInputD >-> throwParsingErrors >-> parserD hello+>+> helloPipe4 :: (Proxy p, Monad m) => () -> Pipe (EitherP BadInput p) Text Name m r+> helloPipe4 = parserInputD >-> limitInputLength 10 >-> parserD hello+>+> helloPipe5 :: (Proxy p, Monad m) => () -> Pipe (EitherP BadInput p) Text Name m r+> helloPipe5 = parserInputD >-> limitInputLength 10 >-> retryLeftovers >-> parserD hello+>+> helloPipe6 :: (Proxy p, Monad m) => () -> Pipe p Text Name m r+> helloPipe6 = parserInputD >-> skipPartialResults >-> parserD hello+>+> skipPartialResults+>  :: (Monad m, Proxy p, AttoparsecInput a)+>  => ParserStatus a+>  -> p (ParserStatus a) (ParserSupply a) (ParserStatus a) (ParserSupply a) m r+> skipPartialResults = runIdentityK . foreverK $ go+>   where go x@(Parsing _) = request x >>= \(_, a) -> respond (Start, a)+>         go x             = request x >>= respond+++$example-simple++We'll write a simple 'Parser' that turns 'Text' like /“Hello John Doe.”/+into @'Name' \"John Doe\"@, and then make a 'Pipe' that turns+those 'Text' values flowing downstream into 'Name' values flowing+downstream using that 'Parser'.++In this example we are using 'Text', but we may as well use+'Data.ByteString.ByteString'. Also, the 'OverloadedStrings' language+extension lets us write our parser easily.++  > {-# LANGUAGE OverloadedStrings #-}+  >+  > import Control.Proxy+  > import Control.Proxy.Attoparsec+  > import Control.Proxy.Trans.Either+  > import Data.Attoparsec.Text+  > import Data.Text+  >+  > data Name = Name Text+  >           deriving (Show)+  >+  > hello :: Parser Name+  > hello = fmap Name $ "Hello " .*> takeWhile1 (/='.') <*. "."++We are done with our parser, now lets make a simple parsing 'Pipe' with it.++  > helloPipe1 :: (Proxy p, Monad m) => () -> Pipe p Text Name m r+  > helloPipe1 = parserInputD >-> parserD hello++As the type indicates, this 'Pipe' receives 'Text' values from upstream and+sends 'Name' values downstream. This 'Pipe' is made of two smaller+two smaller cooperating 'Proxy's:++  1. 'parserInputD': Prepares @a@ input received from upstream to be+     consumed by a downstream parsing 'Proxy'.++  2. 'parserD': Repeatedly runs a given @'Parser' a b@ on input 'a' from+     upstream, and sends 'b' values downstream.++We need some sample input to test our simple 'helloPipe1'.++  > input1 :: [Text]+  > input1 =+  >   [ "Hello Kate."+  >   , "Hello Mary.Hello Jef"+  >   , "f."+  >   , "Hel"+  >   , "lo Tom."+  >   ]++We'll use @'fromListS' input1@ as our input source, which sends+downstream one element from the list at a time. We'll call each of these+elements a /chunk/. So, @'fromListS' input1@ sends 5 /chunks/ of+'Text' downstream.++Notice how some of our /chunks/ are not, by themselves, complete inputs+for our 'hello' 'Parser'. This is fine; we want to be able to feed the+'Parser' with either partial or complete input as soon as it's+received from upstream. More input will be requested when needed.+Attoparsec's 'Parser' handles partial parsing just fine.++  >>> runProxy $  fromListS input1 >-> helloPipe1 >-> printD+  Name "Kate"+  Name "Mary"+  Name "Jeff"+  Name "Tom"++We have accomplished our simple goal: We've made a 'Pipe' that parses+downstream flowing input using our 'Parser' 'hello'.+++$example-errors++Let's try with some more complex input.++  > input2 :: [Text]+  > input2 =+  >   [ "Hello Amy."+  >   , "Hello, Hello Tim."+  >   , "Hello Bob."+  >   , "Hello James"+  >   , "Hello"+  >   , "Hello World."+  >   , "HexHello Jon."+  >   , "H"+  >   , "ello Ann"+  >   , "."+  >   , "Hello Jean-Luc."+  >   ]++  >>> runProxy $ fromListS input2 >-> helloPipe1 >-> printD+  Name "Amy"+  Name "Bob"+  Name "JamesHelloHello World"+  Name "Ann"+  Name "Jean-Luc"++The simple @helloPipe1@ we built skips /chunks/ of input that fail to be+parsed, and then continues parsing new input. That approach might be+enough if you are certain your input is always well-formed, but+sometimes you may prefer to act differently on these extraordinary+situations.++Instead of just using 'parserInputD' and 'parserD' to build our+'helloPipe1', we could have used an additional 'Proxy' in between them to+handle these situations. The module "Control.Proxy.Attoparsec.Control"+exports some useful 'Proxy's that serve this purpose. The default+behavior just mentioned resembles the one provided by+'skipMalformedChunks'.++Here are some other examples:++['retryLeftovers']+   On parsing failures, keep retrying with any left-over input, skipping+   individual bits each time. If there are no left-overs, then more+   input is requests form upstream.++  > helloPipe2 :: (Proxy p, Monad m) => () -> Pipe p Text Name m r+  > helloPipe2 = parserInputD >-> retryLeftovers >-> parserD hello++  >>> runProxy $ fromListS input2 >-> helloPipe2 >-> printD+  Name "Amy"+  Name "Tim"+  Name "Bob"+  Name "JamesHelloHello World"+  Name "Jon"+  Name "Ann"+  Name "Jean-Luc"++['throwParsingErrors']+  When a parsing error arises, aborts execution by throwing+  'MalformedInput' in the 'EitherP' proxy transformer.++  > helloPipe3 :: (Proxy p, Monad m) => () -> Pipe (EitherP BadInput p) Text Name m r+  > helloPipe3 = parserInputD >-> throwParsingErrors >-> parserD hello++  >>> runProxy . runEitherK $ fromListS input2 >-> helloPipe3 >-> printD+  Name "Amy"+  Left (MalformedInput {miParserErrror = ParserError {errorContexts = [], errorMessage = "Failed reading: takeWith"}})++[@'limitInputLength' n@]+  If a @'Parser' a b@ has consumed input @a@ of length longer than+  @n@ without producing a @b@ value, and it's still requesting more+  input, then throw 'InputTooLong' in the 'EitherP' proxy transformer.++  > helloPipe4 :: (Proxy p, Monad m) => () -> Pipe (EitherP BadInput p) Text Name m r+  > helloPipe4 = parserInputD >-> limitInputLength 10 >-> parserD hello++  >>> runProxy . runEitherK $ fromListS input2 >-> helloPipe4 >-> printD+  Name "Amy"+  Name "Bob"+  Left (InputTooLong {itlLenght = 11})++  Notice that by default, as mentioned earlier, parsing errors are+  ignored by skipping the malformed /chunk/. That's why we didn't get any+  complaint about the malformed input between /“Amy”/ and /“Bob”/.+++$example-compose-control++These 'Proxy's that control the parsing behavior can be easily plugged+together with @('>->')@ to achieve a combined functionality. Keep in+mind that the order in which these 'Proxy's are used is important.++Suppose you don't want to parse inputs of length longer than 10, and on+parsing failures, you want to retry feeding the parser with any+left-overs. You could achieve that behaviour like this:++  > helloPipe5 :: (Proxy p, Monad m) => () -> Pipe (EitherP BadInput p) Text Name m r+  > helloPipe5 = parserInputD >-> limitInputLength 10 >-> retryLeftovers >-> parserD hello++  >>> runProxy . runEitherK $ fromListS input2 >-> helloPipe5 >-> printD+  Name "Amy"+  Name "Tim"+  Name "Bob"+  Left (InputTooLong {itlLenght = 11})+++$example-control-custom++In case the 'Proxy's provided by "Control.Proxy.Attoparsec.Control" are not+enough for your needs, you can create your custom parsing control 'Proxy'.++Through a parsing control 'Proxy',  @'ParserStatus' a@ values flow+upstream and @'ParserSupply' a@ values flow downstream.  A parsing+control 'Proxy' may use, replace, or alter the flow of these values to+achieve its purpose. A @'ParserStatus' a@ value received from downstream+reports the status of a 'parserD' parsing 'Proxy', and in exchange,+downstream expects a @'ParserSupply' a@ value.++@'ParserSupply' a@ values carry raw input to be parsed and directives on how it+should be used. @'ParserSupply' a@ is just a type synonym for+@('SupplyUse', a)@. The @a@ value is the input chunk, and the 'SupplyUse' value+could be either 'Resume' if the parser currently waiting for input should be+fed, or 'Start', if a new parser should be started and fed the input,+effectively aborting any parsing activity currently waiting for more input.++See the documentation about 'ParserStatus' and 'ParserSupply' for more details.++Suppose you want to write a parsing control 'Proxy' that never provides+additional input to partial parsing results. Let's first take a look at the+type of this 'Proxy':++  > skipPartialResults+  >  :: (Monad m, Proxy p, AttoparsecInput a)+  >  => ParserStatus a+  >  -> p (ParserStatus a) (ParserSupply a) (ParserStatus a) (ParserSupply a) m r++Just like we said, @'ParserStatus' a@ values flow upstream and @'ParserSupply'+a@ values flow downstream.++Now to a simple implementation: If we receive @'Parsing' n@ from downstream,+then we know there is a partial parsing result waiting for more input. If we+were to respond to this request with @('Resume', a)@, then the partial parser+would continue consuming input, but if we change our response to @('Start', a)@,+then the partial parser would be aborted and a new parser would start+consuming the given input. A simple implementation is quite straightforward:++  > skipPartialResults = runIdentityK . foreverK $ go+  >   where go x@(Parsing _) = request x >>= \(_, a) -> respond (Start, a)+  >         go x             = request x >>= respond++We forward upstream the requests we get from downstream. However, in the case+of a @'Parsing' n@ status, we replace with 'Start' the first value in the+@'ParserSupply' a@ pair we receive from upstream before responding.++Now we can use this parsing control 'Proxy' with some simple input and see it+working.++  > helloPipe6 :: (Proxy p, Monad m) => () -> Pipe p Text Name m r+  > helloPipe6 = parserInputD >-> skipPartialResults >-> parserD hello++  >>> runProxy $ fromListS input1 >-> helloPipe6 >-> printD+  Name "Kate"+  Name "Mary"+++$example-try++This module exports the following previous examples so that you can try+them.+
+ src/Control/Proxy/Attoparsec/Types.hs view
@@ -0,0 +1,101 @@+{-# LANGUAGE DeriveFunctor  #-}+{-# LANGUAGE KindSignatures #-}++-- | This module exports common types used throughout+-- @"Control.Proxy.Attoparsec".*@++module Control.Proxy.Attoparsec.Types+  ( -- * Proxy control+    ParserStatus(..)+  , SupplyUse(..)+  , ParserSupply+    -- * Error types+  , BadInput(..)+    -- * Attoparsec integration+  , AttoparsecInput(..)+  , ParserError(..)+  ) where++import qualified Data.Attoparsec.ByteString as ABS+import qualified Data.Attoparsec.Text       as AT+import           Data.Attoparsec.Types+import qualified Data.ByteString            as BS+import qualified Data.Text                  as T++++-- | Status of a parsing 'Proxy'.+data ParserStatus a+  -- | There is a 'Parser' running and waiting for more @a@ input.+  --+  -- Receiving @('Resume', a)@ from upstream would feed input @a@ to the 'Parser'+  -- waiting for more input.+  = Parsing+    { psLength :: Int -- ^Length of input consumed so far.+    }+  -- | A 'Parser' has failed parsing.+  --+  -- Receiving @('Resume', a)@ from upstream would feed input @a@ to a newly+  -- started 'Parser'.+  | Failed+    { psLeftover :: a -- ^Input not yet consumed when the failure occurred.+    , psError    :: ParserError -- ^Error found while parsing.+    }+  deriving (Show, Eq)+++data ParserError = ParserError+    { errorContexts :: [String]  -- ^ Contexts where the error occurred.+    , errorMessage  :: String    -- ^ Error message.+    } deriving (Show, Eq)+++-- | Indicates how should a parsing 'Proxy' use new input.+data SupplyUse+  -- | Start a new parser and fed any new input to it.+  = Start+  -- | Resume feeding any new input to the current parser waiting for input.+  | Resume+  deriving (Eq, Show)+++-- | Input supplied to a parsing 'Proxy'.+--+-- The 'SupplyUse' value indicates how the @a@ input chunk should be used.+type ParserSupply a = (SupplyUse, a)+++-- | Reasons for an input value to be unnaceptable.+data BadInput+  = InputTooLong+    { itlLength :: Int -- ^ Length of the input.+    }+  | MalformedInput+    { miParserErrror :: ParserError -- ^ Error found while parsing.+    }+  deriving (Show, Eq)+++-- | A class for valid Attoparsec input types.+class Eq a => AttoparsecInput a where+    -- | Run a 'Parser' with input @a@.+    parse :: Parser a b -> a -> IResult a b+    -- | Tests whether @a@ is empty.+    null :: a -> Bool+    -- | Skips the first @n@ elements from @a@ and returns the rest.+    drop :: Int -> a -> a+    -- | Number of elements in @a@.+    length :: a -> Int++instance AttoparsecInput BS.ByteString where+    parse = ABS.parse+    null = BS.null+    drop = BS.drop+    length = BS.length++instance AttoparsecInput T.Text where+    parse = AT.parse+    null = T.null+    drop = T.drop+    length = T.length+
+ tests/Main.hs view
@@ -0,0 +1,21 @@+module 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.Tutorial+import qualified Test.Attoparsec++main = defaultMain tests++tests =+    [ testGroup "Sample"   sampleTests+    , testGroup "Tutorial" Test.Tutorial.tests+    , testGroup "Attoparsec" Test.Attoparsec.tests+    ]++sampleTests = [ testProperty "QuickCheck" $ \x -> const True (x :: Int) == True+              , testCase     "HUnit"      $ True @?= True+              ]
+ tests/Test/Attoparsec.hs view
@@ -0,0 +1,7 @@+{-# LANGUAGE OverloadedStrings #-}++module Test.Attoparsec (tests) where++-- TODO++tests = []
+ tests/Test/Tutorial.hs view
@@ -0,0 +1,66 @@+{-# LANGUAGE OverloadedStrings #-}++module Test.Tutorial where++import           Control.Proxy+import           Control.Proxy.Attoparsec+import           Control.Proxy.Attoparsec.Tutorial+import           Control.Proxy.Trans.Either+import           Data.Maybe+import           Test.Framework.Providers.HUnit    (testCase)+import           Test.HUnit                        hiding (test)+++testPipe pipe input test = fromJust $ do+  let proxy = fromListS input >-> pipe >-> toListD+  output <- runWriterT . runProxy . runEitherK $ proxy+  return . assert $ test output+++test_helloPipe1_input1 = testPipe helloPipe1 input1 test+  where test (Right _, x) = x == fmap Name ["Kate" , "Mary", "Jeff", "Tom"]+        test _            = False++test_helloPipe1_input2 = testPipe helloPipe1 input2 test+  where test (Right _, x) = x == fmap Name [ "Amy", "Bob"+                                           , "JamesHelloHello World" , "Ann"+                                           , "Jean-Luc" ]+        test _            = False+++test_helloPipe2_input2 = testPipe helloPipe2 input2 test+   where test (Right _, x) = x == fmap Name [ "Amy" ,"Tim", "Bob"+                                            , "JamesHelloHello World", "Jon"+                                            , "Ann" , "Jean-Luc" ]+         test _            = False++test_helloPipe3_input2 = testPipe helloPipe3 input2 test+   where test (Left (MalformedInput _), [Name "Amy"]) = True+         test _                                       = False+++test_helloPipe4_input2 = testPipe helloPipe4 input2 test+   where test (Left (InputTooLong 11), [Name "Amy", Name "Bob"]) = True+         test _                                                  = False+++test_helloPipe5_input2 = testPipe helloPipe5 input2 test+   where test (Left (InputTooLong 11),+               [Name "Amy", Name "Tim", Name "Bob"]) = True+         test _                                      = False++test_helloPipe6_input1 = testPipe helloPipe6 input1 test+   where test (Right _, [Name "Kate", Name "Mary"]) = True+         test _                                     = False+++tests =+    [ testCase "helloPipe1 + input1" test_helloPipe1_input1+    , testCase "helloPipe1 + input2" test_helloPipe1_input2+    , testCase "helloPipe2 + input2" test_helloPipe2_input2+    , testCase "helloPipe3 + input2" test_helloPipe3_input2+    , testCase "helloPipe4 + input2" test_helloPipe4_input2+    , testCase "helloPipe5 + input2" test_helloPipe5_input2+    , testCase "helloPipe6 + input1" test_helloPipe6_input1+    ]+