pipes-attoparsec 0.2.0.0 → 0.3.0
raw patch · 8 files changed
+250/−254 lines, 8 filesdep ~pipesdep ~pipes-parsedep ~textPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: pipes, pipes-parse, text, transformers
API changes (from Hackage documentation)
- 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
+ Pipes.Attoparsec: ParsingError :: [String] -> String -> ParsingError
+ Pipes.Attoparsec: class (Eq a, Monoid a) => ParserInput a
+ Pipes.Attoparsec: data ParsingError
+ Pipes.Attoparsec: isEndOfParserInput :: (ParserInput a, Monad m) => StateT (Producer a m r) m Bool
+ Pipes.Attoparsec: parse :: (Monad m, ParserInput a) => Parser a b -> StateT (Producer a m r) m (Either ParsingError (Int, b))
+ Pipes.Attoparsec: parseMany :: (Monad m, ParserInput a) => Parser a b -> Producer a m r -> Producer' (Int, b) m (Either (ParsingError, Producer a m r) r)
+ Pipes.Attoparsec: peContexts :: ParsingError -> [String]
+ Pipes.Attoparsec: peMessage :: ParsingError -> String
Files
- NEWS +7/−0
- README.md +2/−0
- pipes-attoparsec.cabal +11/−10
- src/Control/Proxy/Attoparsec.hs +0/−117
- src/Control/Proxy/Attoparsec/Internal.hs +0/−108
- src/Pipes/Attoparsec.hs +98/−0
- src/Pipes/Attoparsec/Internal.hs +113/−0
- tests/Test/Attoparsec.hs +19/−19
NEWS view
@@ -1,7 +1,14 @@+# 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
README.md view
@@ -1,5 +1,7 @@ # pipes-attoparsec +[](http://travis-ci.org/k0001/pipes-attoparsec)+ Utilities to run **Attoparsec** parser on **Pipes** input streams. Check the source or rendered Haddocks for documentation.
pipes-attoparsec.cabal view
@@ -1,12 +1,12 @@ name: pipes-attoparsec-version: 0.2.0.0+version: 0.3.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+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@@ -15,7 +15,7 @@ synopsis: Attoparsec and Pipes integration. extra-source-files: README.md PEOPLE NEWS description:- Utilities to run @attoparsec@ parsers on @pipes@ input streams.+ Utilities to run Attoparsec parsers on Pipes input streams. . See the @NEWS@ file in the source distribution to learn about any important changes between version.@@ -26,15 +26,15 @@ library hs-source-dirs: src- exposed-modules: Control.Proxy.Attoparsec- Control.Proxy.Attoparsec.Internal+ exposed-modules: Pipes.Attoparsec+ other-modules: Pipes.Attoparsec.Internal build-depends: base (==4.*)- , pipes (>=3.3 && <3.4)- , pipes-parse (>=1.0 && <1.1) , attoparsec (>=0.10 && <0.11) , bytestring (>=0.9.2.1)- , text+ , pipes (>=4.0 && <4.1)+ , pipes-parse (>=2.0 && <2.1)+ , text (>=0.11.2.3) , transformers (>=0.2 && <=0.4) ghc-options: -Wall -O2 @@ -47,12 +47,13 @@ build-depends: base+ , attoparsec , mmorph , pipes- , attoparsec- , text , pipes-attoparsec , pipes-parse+ , text+ , transformers , QuickCheck (== 2.*) , HUnit (== 1.*) , test-framework (>= 0.6)
− src/Control/Proxy/Attoparsec.hs
@@ -1,117 +0,0 @@--- | This module allows you to run Attoparsec parsers on input flowing--- downstream through Pipes streams, possibly interleaving other stream--- effects.------ This module builds on top of the @pipes-parse@ package and assumes--- you have read "Control.Proxy.Parse.Tutorial".--module Control.Proxy.Attoparsec- ( -- * Parsing- -- $parsing- parse- , parseD- , isEndOfParserInput- -- * Types- , I.ParserInput(I.null)- , I.ParsingError(..)- ) where------------------------------------------------------------------------------------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_)------------------------------------------------------------------------------------- $parsing------ 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'.------ 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.------ * 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.------ * /Do not/ use this proxy if 'isEndOfParserInput' returns 'True',--- otherwise you may get unexpected parsing errors.------ Here is an example parsing loop that allows interleaving stream effects--- together with 'parse':------ > 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 #-}----- | Parses consecutive elements flowing downstream until end of input.------ * 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- loop = do- eof <- P.liftP isEndOfParserInput- unless eof $ do- () <- P.respond =<< parse parser- loop-{-# INLINABLE parseD #-}-------------------------------------------------------------------------------------- | 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/Internal.hs
@@ -1,108 +0,0 @@-{-# 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/Pipes/Attoparsec.hs view
@@ -0,0 +1,98 @@+{-# 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.+--+-- This module builds on top of the @attoparsec@, @pipes@ and @pipes-parse@+-- libraries and assumes you understand how to use those.++module Pipes.Attoparsec+ ( -- * Parsing+ parse+ , parseMany+ , isEndOfParserInput+ -- * Types+ , I.ParserInput+ , I.ParsingError(..)+ ) where++--------------------------------------------------------------------------------++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))++--------------------------------------------------------------------------------++-- | 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.+--+-- Use this function only if 'isEndOfParserInput' returns 'False', otherwise+-- you'll get unexpected parsing errors.+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+{-# 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.+--+-- 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+ 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++--------------------------------------------------------------------------------++-- | 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+{-# INLINABLE isEndOfParserInput #-}+
+ src/Pipes/Attoparsec/Internal.hs view
@@ -0,0 +1,113 @@+{-# 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+ ) 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/Test/Attoparsec.hs view
@@ -4,18 +4,17 @@ module Test.Attoparsec (tests) where 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)+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) -- | Parses a 'Char' repeated four times. four :: AT.Parser Char@@ -28,13 +27,14 @@ 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+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- okErr = either (const $ not ok) (const $ ok) epe okRes = res == output return $ okMlo && okErr && okRes @@ -56,7 +56,7 @@ , (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: Wrong" ,["abcd","efgh"] ,[] ,["bcd","efgh"]) , (False ,"2 chunk: One then wrong" ,["aaaab","bxz"] ,['a'] ,["xz"]) , (True ,"3 chunk: One" ,["a","a","aa"] ,['a'] ,[]) ]