pipes-attoparsec 0.1.0.1 → 0.2.0.0
raw patch · 13 files changed
+336/−723 lines, 13 filesdep +mmorphdep +pipes-parsedep +transformersdep ~attoparsecdep ~basedep ~bytestringPVP ok
version bump matches the API change (PVP)
Dependencies added: mmorph, pipes-parse, transformers
Dependency ranges changed: attoparsec, base, bytestring, pipes
API changes (from Hackage documentation)
- 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)
+ Control.Proxy.Attoparsec: ParsingError :: [String] -> String -> ParsingError
+ Control.Proxy.Attoparsec: class Monoid a => ParserInput a
+ Control.Proxy.Attoparsec: data ParsingError
+ Control.Proxy.Attoparsec: isEndOfParserInput :: (ParserInput a, Monad m, Proxy p) => StateP [a] p () (Maybe a) y' y m Bool
+ Control.Proxy.Attoparsec: null :: ParserInput a => a -> Bool
+ Control.Proxy.Attoparsec: parse :: (ParserInput a, Monad m, Proxy p) => Parser a r -> EitherP ParsingError (StateP [a] p) () (Maybe a) y' y m r
+ Control.Proxy.Attoparsec: parseD :: (ParserInput a, Monad m, Proxy p) => Parser a b -> () -> Pipe (EitherP ParsingError (StateP [a] p)) (Maybe a) b m ()
+ Control.Proxy.Attoparsec: peContexts :: ParsingError -> [String]
+ Control.Proxy.Attoparsec: peMessage :: ParsingError -> String
Files
- LICENSE +2/−2
- NEWS +8/−0
- PEOPLE +6/−0
- README.md +9/−2
- pipes-attoparsec.cabal +42/−48
- src/Control/Proxy/Attoparsec.hs +96/−73
- src/Control/Proxy/Attoparsec/Control.hs +0/−80
- src/Control/Proxy/Attoparsec/Internal.hs +108/−0
- src/Control/Proxy/Attoparsec/Tutorial.lhs +0/−344
- src/Control/Proxy/Attoparsec/Types.hs +0/−101
- tests/Main.hs +3/−5
- tests/Test/Attoparsec.hs +62/−2
- tests/Test/Tutorial.hs +0/−66
LICENSE view
@@ -1,5 +1,5 @@-Copyright (c) 2012-2012, Paolo Capriotti-Copyright (c) 2012-, Renzo Carbonara+Copyright (c) 2012-2013, Renzo Carbonara+Copyright (c) 2012, Paolo Capriotti All rights reserved.
+ NEWS view
@@ -0,0 +1,8 @@+# 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
@@ -0,0 +1,6 @@+The following people have participated in creating this library, either+by directly contributing code or by providing thoughtful input in+discussions about the library design.++Renzo Carbonara+Gabriel Gonzalez
README.md view
@@ -1,5 +1,12 @@ # pipes-attoparsec -Utilities to convert an Attoparsec Parser into a Pipe.+Utilities to run **Attoparsec** parser on **Pipes** input streams. -Version 0.1.\* is not backwards compatible with previous versions.+Check the source or rendered Haddocks for documentation.++This code is licensed under the terms of the so called **3-clause BSD+license**. Read the file named ``LICENSE`` found in this same directory+for details.++See the ``PEOPLE`` file to learn about the people involved in this+effort.
pipes-attoparsec.cabal view
@@ -1,29 +1,24 @@-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.+name: pipes-attoparsec+version: 0.2.0.0+license: BSD3+license-file: LICENSE+copyright: Copyright (c) Renzo Carbonara 2012-2013, Paolo Capriotti 2012+author: Renzo Carbonara+maintainer: renzocarbonaraλgmail.com+stability: Experimental+tested-with: GHC ==7.4.1+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 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.+ Utilities to run @attoparsec@ parsers on @pipes@ input streams. .- Version 0.1.* is not backwards compatible with previous versions.-extra-source-files:- README.md+ See the @NEWS@ file in the source distribution to learn about any+ important changes between version. source-repository head type: git@@ -32,36 +27,35 @@ library hs-source-dirs: src exposed-modules: Control.Proxy.Attoparsec- Control.Proxy.Attoparsec.Control- Control.Proxy.Attoparsec.Tutorial- Control.Proxy.Attoparsec.Types+ Control.Proxy.Attoparsec.Internal build-depends:- base (==4.*),- pipes (>=3.0),- attoparsec (>=0.10),- bytestring,- text-- ghc-options:- -Wall -fno-warn-missing-signatures+ base (==4.*)+ , pipes (>=3.3 && <3.4)+ , pipes-parse (>=1.0 && <1.1)+ , attoparsec (>=0.10 && <0.11)+ , bytestring (>=0.9.2.1)+ , text+ , transformers (>=0.2 && <=0.4)+ ghc-options: -Wall -O2 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+ ghc-options: -Wall -rtsopts -fno-warn-missing-signatures build-depends:- base (== 4.*),- 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)+ base+ , mmorph+ , pipes+ , attoparsec+ , text+ , pipes-attoparsec+ , pipes-parse+ , QuickCheck (== 2.*)+ , HUnit (== 1.*)+ , test-framework (>= 0.6)+ , test-framework-quickcheck2 (>= 0.2)+ , test-framework-hunit (>= 0.2)+
src/Control/Proxy/Attoparsec.hs view
@@ -1,94 +1,117 @@--- | 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.+-- | This module allows you to run Attoparsec parsers on input flowing+-- downstream through Pipes streams, possibly interleaving other stream+-- effects. ----- See "Control.Proxy.Attoparsec.Tutorial" for an extensive introduction--- with examples.+-- This module builds on top of the @pipes-parse@ package and assumes+-- you have read "Control.Proxy.Parse.Tutorial". module Control.Proxy.Attoparsec- ( -- * Parsing Pipe- -- $pieces- parserInputD- , parserD- -- * Modules- , module Control.Proxy.Attoparsec.Types- , module Control.Proxy.Attoparsec.Control+ ( -- * Parsing+ -- $parsing+ parse+ , parseD+ , isEndOfParserInput+ -- * Types+ , I.ParserInput(I.null)+ , I.ParsingError(..) ) 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)+-------------------------------------------------------------------------------- +import Control.Monad (unless)+import qualified Control.Proxy as P+import qualified Control.Proxy.Parse as Pa+import qualified Control.Proxy.Attoparsec.Internal as I+import qualified Control.Proxy.Trans.Either as P+import qualified Control.Proxy.Trans.State as P+import Data.Attoparsec.Types (Parser)+import Data.Foldable (mapM_)+import Data.Function (fix)+import Prelude hiding (mapM_) --- $pieces+--------------------------------------------------------------------------------+-- $parsing ----- A 'Pipe' that parses @a@ input flowing downstream into @b@ values is--- made of at least two smaller cooperating 'Proxy's:+-- There are two basic parsing facilities exported by this module, and choosing+-- between them is easy: If you need to interleave Attoparsec parsing with other+-- stream effects you must use 'parse', otherwise you may use the simpler+-- 'parseD'. ----- 1. 'parserInputD': Prepares @a@ input received from upstream to be--- consumed by a downstream parsing 'Proxy'.+-- These proxies use the 'P.EitherP' proxy transformer to report parsing errors,+-- you might use any of the facilities exported by "Control.Proxy.Trans.Either"+-- to recover from them.+++-- | Parses one element flowing downstream. ----- 2. 'parserD': Repeatedly runs a given @'Parser' a b@ on input 'a'--- from upstream, and sends 'b' values downstream.+-- * In case of parsing errors, a 'I.ParsingError' exception is thrown in+-- the 'Pe.EitherP' proxy transformer. ----- Given a @'Parser' a b@ named @myParser@, the simplest way to use--- these 'Proxy's together is:+-- * Requests more input from upstream using 'Pa.draw' when needed. ----- > myParsingPipe :: (Proxy p, Monad m, AttoparsecInput a) => Pipe a b m r--- > myParsingPipe = parserInputD >-> parserD myParser+-- * /Do not/ use this proxy if 'isEndOfParserInput' returns 'True',+-- otherwise you may get unexpected parsing errors. ----- 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'.+-- Here is an example parsing loop that allows interleaving stream effects+-- together with 'parse': ----- 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+-- > loop = do+-- > eof <- liftP isEndOfParserInput+-- > unless eof $ do+-- > -- 1. Possibly perform some stream effects here.+-- > -- 2. Parse one element from the stream.+-- > exampleElement <- parse myExampleParser+-- > -- 3. Do something with exampleElement and possibly perform+-- > -- some more stream effects.+-- > -- 4. Start all over again.+-- > loop+parse+ :: (I.ParserInput a, Monad m, P.Proxy p)+ => Parser a r -- ^Attoparsec parser to run on the input stream.+ -> P.EitherP I.ParsingError (P.StateP [a] p) () (Maybe a) y' y m r+ -- ^Proxy compatible with the facilities provided by "Control.Proxy.Parse".+parse parser = do+ (er, mlo) <- P.liftP (I.parseWithMay Pa.draw parser)+ P.liftP (mapM_ Pa.unDraw mlo)+ either P.throw return er+{-# INLINABLE parse #-} --- | 'Proxy' using the given @'Parser' a b@ to repeatedly parse pieces--- of @'ParserSupply' a@ values flowing downstream into @b@ values.+-- | Parses consecutive elements flowing downstream until end of input. ----- 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)+-- * In case of parsing errors, a 'I.ParsingError' exception is thrown in the+-- 'Pe.EitherP' proxy transformer.+--+-- * Requests more input from upstream using 'Pa.draw' when needed.+--+-- * Empty input chunks flowing downstream will be discarded.+parseD+ :: (I.ParserInput a, Monad m, P.Proxy p)+ => Parser a b -- ^Attoparsec parser to run on the input stream.+ -> () -> P.Pipe (P.EitherP I.ParsingError (P.StateP [a] p)) (Maybe a) b m ()+ -- ^Proxy compatible with the facilities provided by "Control.Proxy.Parse".+parseD parser = \() -> loop 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)+ loop = do+ eof <- P.liftP isEndOfParserInput+ unless eof $ do+ () <- P.respond =<< parse parser+ loop+{-# INLINABLE parseD #-} - 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+-------------------------------------------------------------------------------- +-- | Like 'Pa.isEndOfInput', except it also consumes and discards leading+-- empty 'I.ParserInput' chunks.+isEndOfParserInput+ :: (I.ParserInput a, Monad m, P.Proxy p)+ => P.StateP [a] p () (Maybe a) y' y m Bool+isEndOfParserInput = fix $ \loop -> do+ ma <- Pa.draw+ case ma of+ Just a+ | I.null a -> loop+ | otherwise -> Pa.unDraw a >> return False+ Nothing -> return True+{-# INLINABLE isEndOfParserInput #-}
− src/Control/Proxy/Attoparsec/Control.hs
@@ -1,80 +0,0 @@--- | 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/Internal.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# OPTIONS_HADDOCK hide prune #-}++-- | 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 "Control.Proxy.Attoparsec" module instead.++module Control.Proxy.Attoparsec.Internal+ ( -- * Types+ ParsingError(..)+ , ParserInput(..)+ -- * Parsing+ , parseWith+ , parseWithMay+ -- * Utils+ , mayInput+ ) where++--------------------------------------------------------------------------------++import Control.Exception (Exception)+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 Prelude hiding (null)++--------------------------------------------------------------------------------++-- | 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, Eq, Data, Typeable)++instance Exception ParsingError where++--------------------------------------------------------------------------------++-- | A class for valid Attoparsec input types: strict 'T.Text' and+-- strict 'B.ByteString'.+class (Monoid a) => ParserInput a where+ -- | Run a 'Parser' with input @a@.+ parse :: Parser a b -> a -> IResult a b+ -- | Tests whether @a@ is empty.+ null :: a -> Bool++instance ParserInput B.ByteString where+ parse = AB.parse+ null = B.null++instance ParserInput T.Text where+ parse = AT.parse+ null = T.null++--------------------------------------------------------------------------------++-- | Run a parser drawing input from the given monadic action as needed.+parseWith+ :: (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 r, Maybe a)+ -- ^Either a parser error or a parsed result, together with any leftover.+parseWith refill p = step . parse p =<< refill where+ step (Partial k) = step . k =<< refill+ step (Done t r) = return (Right r, mayInput t)+ step (Fail t c m) = return (Left (ParsingError c m), mayInput t)+{-# INLINABLE parseWith #-}+++-- | Run a parser drawing input from the given monadic action as needed.+parseWithMay+ :: (Monad m, ParserInput a)+ => m (Maybe a)+ -- ^An action that will be executed to provide the parser with more input+ -- as needed. If the action returns 'Nothing', then it's assumed no more+ -- input is available. @'Just' 'mempty'@ input is discarded.+ -> Parser a r+ -- ^Parser to run on the given input+ -> m (Either ParsingError r, Maybe a)+ -- ^Either a parser error or a parsed result, together with any leftover.+parseWithMay refill p = parseWith loop p+ where+ loop = do+ ma <- refill+ case ma of+ Just a+ | null a -> loop -- retry on null input+ | otherwise -> return a+ Nothing -> return mempty+{-# INLINABLE parseWithMay #-}+++-- | Wrap @a@ in 'Just' if not-null. Otherwise, 'Nothing'.+mayInput :: ParserInput a => a -> Maybe a+mayInput = \x -> if null x then Nothing else Just x+{-# INLINABLE mayInput #-}++
− src/Control/Proxy/Attoparsec/Tutorial.lhs
@@ -1,344 +0,0 @@-> {-# 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
@@ -1,101 +0,0 @@-{-# 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
@@ -1,19 +1,17 @@-module Main where+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.Tutorial import qualified Test.Attoparsec main = defaultMain tests tests =- [ testGroup "Sample" sampleTests- , testGroup "Tutorial" Test.Tutorial.tests- , testGroup "Attoparsec" Test.Attoparsec.tests+ [ testGroup "Sample." sampleTests+ , testGroup "Attoparsec." Test.Attoparsec.tests ] sampleTests = [ testProperty "QuickCheck" $ \x -> const True (x :: Int) == True
tests/Test/Attoparsec.hs view
@@ -1,7 +1,67 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE NoMonomorphismRestriction #-} module Test.Attoparsec (tests) where --- TODO+import Control.Monad+import Control.Proxy ((>->))+import qualified Control.Proxy as P+import qualified Control.Proxy.Trans.State as P+import qualified Control.Proxy.Trans.Either as P+import qualified Control.Proxy.Trans.Writer as P+import qualified Control.Proxy.Parse as Pa+import Control.Proxy.Attoparsec (parseD)+import qualified Data.Attoparsec.Text as AT+import qualified Data.Text as T+import Data.Maybe+import Test.Framework.Providers.HUnit (testCase)+import Test.HUnit (Assertion, assert) -tests = []+-- | Parses a 'Char' repeated four times.+four :: AT.Parser Char+four = do+ c <- AT.anyChar+ replicateM_ 3 $ AT.char c+ return c++type ParseTest = (Bool, String, [T.Text], [Char], [T.Text])+++assertFoursTest :: ParseTest -> Assertion+assertFoursTest (ok, _title, input, output, mlo) = assert . fromJust $ do+ let srcS = (Pa.wrap.) $ P.fromListS input+ run = P.runProxy . P.runWriterK . P.runStateK Pa.mempty . P.runEitherK+ ((epe,mlo'), res) <- run $ srcS >-> parseD four+ >-> P.liftP . P.liftP . P.toListD+ let okMlo = mlo' == mlo+ okErr = either (const $ not ok) (const $ ok) epe+ okRes = res == output+ return $ okMlo && okErr && okRes++foursTests :: [ParseTest]+foursTests =+ [ (True ,"0 chunk" ,[] ,[] ,[])+ , (True ,"1 chunk: Empty" ,[] ,[] ,[])+ , (True ,"1 chunk: One" ,["aaaa"] ,['a'] ,[])+ , (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"])+ , (True ,"2 chunk: Empty" ,["",""] ,[] ,[])+ , (True ,"2 chunk: Empty then one" ,["","aaaa"] ,['a'] ,[])+ , (True ,"2 chunk: One" ,["a","aaa"] ,['a'] ,[])+ , (True ,"2 chunk: One'" ,["aa","aa"] ,['a'] ,[])+ , (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"])+ , (False ,"2 chunk: One then wrong" ,["aaaab","bxz"] ,['a'] ,["xz"])+ , (True ,"3 chunk: One" ,["a","a","aa"] ,['a'] ,[])+ ]++testCaseFoursTest ft@(_,name,_,_,_) =+ testCase ("Fours." ++ name) $ assertFoursTest ft++tests = map testCaseFoursTest foursTests
− tests/Test/Tutorial.hs
@@ -1,66 +0,0 @@-{-# 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- ]-