pipes-parse 3.0.2 → 3.0.3
raw patch · 5 files changed
+812/−793 lines, 5 filessetup-changed
Files
- LICENSE +24/−24
- Setup.hs +2/−2
- pipes-parse.cabal +40/−40
- src/Pipes/Parse.hs +344/−325
- src/Pipes/Parse/Tutorial.hs +402/−402
LICENSE view
@@ -1,24 +1,24 @@-Copyright (c) 2013, 2014 Gabriel Gonzalez-All rights reserved.--Redistribution and use in source and binary forms, with or without modification,-are permitted provided that the following conditions are met:- * Redistributions of source code must retain the above copyright notice,- this list of conditions and the following disclaimer.- * Redistributions in binary form must reproduce the above copyright notice,- this list of conditions and the following disclaimer in the documentation- and/or other materials provided with the distribution.- * Neither the name of Gabriel Gonzalez nor the names of other contributors- may be used to endorse or promote products derived from this software- without specific prior written permission.--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR-ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+Copyright (c) 2013, 2014 Gabriel Gonzalez +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of Gabriel Gonzalez nor the names of other contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Setup.hs view
@@ -1,2 +1,2 @@-import Distribution.Simple-main = defaultMain+import Distribution.Simple +main = defaultMain
pipes-parse.cabal view
@@ -1,40 +1,40 @@-Name: pipes-parse-Version: 3.0.2-Cabal-Version: >=1.8.0.2-Build-Type: Simple-License: BSD3-License-File: LICENSE-Copyright: 2013, 2014 Gabriel Gonzalez-Author: Gabriel Gonzalez-Maintainer: Gabriel439@gmail.com-Bug-Reports: https://github.com/Gabriel439/Haskell-Pipes-Parse-Library/issues-Synopsis: Parsing infrastructure for the pipes ecosystem-Description: @pipes-parse@ builds upon the @pipes@ library to provide shared- parsing idioms and utilities:- .- * /Leftovers/: Save unused input for later consumption- .- * /Leftover propagation/: Leftovers are propagated backwards perfectly- .- * /Connect and Resume/: Use @StateT@ to save unused input for later- .- * /Termination Safety/: Detect and recover from end of input- .- @Pipes.Parse@ contains the full documentation for this library.- . - Read @Pipes.Parse.Tutorial@ for an extensive tutorial.-Category: Control, Pipes, Parsing-Source-Repository head- Type: git- Location: https://github.com/Gabriel439/Haskell-Pipes-Parse-Library--Library- HS-Source-Dirs: src- Build-Depends:- base >= 4 && < 5 ,- pipes >= 4.1 && < 4.2,- transformers >= 0.2.0.0 && < 0.5- Exposed-Modules:- Pipes.Parse,- Pipes.Parse.Tutorial- GHC-Options: -O2 -Wall+Name: pipes-parse +Version: 3.0.3 +Cabal-Version: >=1.8.0.2 +Build-Type: Simple +License: BSD3 +License-File: LICENSE +Copyright: 2013, 2014 Gabriel Gonzalez +Author: Gabriel Gonzalez +Maintainer: Gabriel439@gmail.com +Bug-Reports: https://github.com/Gabriel439/Haskell-Pipes-Parse-Library/issues +Synopsis: Parsing infrastructure for the pipes ecosystem +Description: @pipes-parse@ builds upon the @pipes@ library to provide shared + parsing idioms and utilities: + . + * /Leftovers/: Save unused input for later consumption + . + * /Leftover propagation/: Leftovers are propagated backwards perfectly + . + * /Connect and Resume/: Use @StateT@ to save unused input for later + . + * /Termination Safety/: Detect and recover from end of input + . + @Pipes.Parse@ contains the full documentation for this library. + . + Read @Pipes.Parse.Tutorial@ for an extensive tutorial. +Category: Control, Pipes, Parsing +Source-Repository head + Type: git + Location: https://github.com/Gabriel439/Haskell-Pipes-Parse-Library + +Library + HS-Source-Dirs: src + Build-Depends: + base >= 4 && < 5 , + pipes >= 4.1 && < 4.2, + transformers >= 0.2.0.0 && < 0.5 + Exposed-Modules: + Pipes.Parse, + Pipes.Parse.Tutorial + GHC-Options: -O2 -Wall
src/Pipes/Parse.hs view
@@ -1,325 +1,344 @@-{-| Element-agnostic parsing utilities for @pipes@-- See "Pipes.Parse.Tutorial" for an extended tutorial--}--{-# LANGUAGE RankNTypes #-}--module Pipes.Parse (- -- * Parsing- -- $parsing- Parser- , draw- , skip- , drawAll- , skipAll- , unDraw- , peek- , isEndOfInput- , foldAll- , foldAllM-- -- * Parsing Lenses- -- $parsinglenses- , span- , splitAt- , groupBy- , group-- -- * Utilities- , toParser- , toParser_-- -- * Re-exports- -- $reexports- , module Control.Monad.Trans.Class- , module Control.Monad.Trans.State.Strict- , module Pipes- ) where--import Control.Monad (join)-import Control.Monad.Trans.Class (lift)-import qualified Control.Monad.Trans.State.Strict as S-import Control.Monad.Trans.State.Strict (- StateT(StateT, runStateT), evalStateT, execStateT )-import Data.Functor.Constant (Constant(Constant, getConstant))-import Pipes.Internal (unsafeHoist, closed)-import Pipes (Producer, yield, next)-import Pipes as NoReexport--import Prelude hiding (span, splitAt)--{- $parsing- @pipes-parse@ handles end-of-input and pushback by storing a 'Producer' in- a 'StateT' layer.-- Connect 'Parser's to 'Producer's using either 'runStateT', 'evalStateT', or- 'execStateT':--> runStateT :: Parser a m r -> Producer a m x -> m (r, Producer a m x)-> evalStateT :: Parser a m r -> Producer a m x -> m r-> execStateT :: Parser a m r -> Producer a m x -> m (Producer a m x)-> ^^^^^^^^^^^^^^-> Leftovers--}---- | A 'Parser' is an action that reads from and writes to a stored 'Producer'-type Parser a m r = forall x . StateT (Producer a m x) m r--{-| Draw one element from the underlying 'Producer', returning 'Nothing' if the- 'Producer' is empty--}-draw :: Monad m => Parser a m (Maybe a)-draw = do- p <- S.get- x <- lift (next p)- case x of- Left r -> do- S.put (return r)- return Nothing- Right (a, p') -> do- S.put p'- return (Just a)-{-# INLINABLE draw #-}--{-| Skip one element from the underlying 'Producer', returning 'True' if- successful or 'False' if the 'Producer' is empty--> skip = fmap isJust draw--}-skip :: Monad m => Parser a m Bool-skip = do- x <- draw- return $ case x of- Nothing -> False- Just _ -> True-{-# INLINABLE skip #-}--{-| Draw all elements from the underlying 'Producer'-- Note that 'drawAll' is not an idiomatic use of @pipes-parse@, but I provide- it for simple testing purposes. Idiomatic @pipes-parse@ style consumes the- elements immediately as they are generated instead of loading all elements- into memory. For example, you can use 'foldAll' or 'foldAllM' for this- purpose.--}-drawAll :: Monad m => Parser a m [a]-drawAll = go id- where- go diffAs = do- x <- draw- case x of- Nothing -> return (diffAs [])- Just a -> go (diffAs . (a:))-{-# INLINABLE drawAll #-}---- | Drain all elements from the underlying 'Producer'-skipAll :: Monad m => Parser a m ()-skipAll = go- where- go = do- x <- draw- case x of- Nothing -> return ()- Just _ -> go-{-# INLINABLE skipAll #-}---- | Push back an element onto the underlying 'Producer'-unDraw :: Monad m => a -> Parser a m ()-unDraw a = S.modify (yield a >>)-{-# INLINABLE unDraw #-}--{-| 'peek' checks the first element of the stream, but uses 'unDraw' to push the- element back so that it is available for the next 'draw' command.--> peek = do-> x <- draw-> case x of-> Nothing -> return ()-> Just a -> unDraw a-> return x--}-peek :: Monad m => Parser a m (Maybe a)-peek = do- x <- draw- case x of- Nothing -> return ()- Just a -> unDraw a- return x-{-# INLINABLE peek #-}--{-| Check if the underlying 'Producer' is empty--> isEndOfInput = fmap isNothing peek--}-isEndOfInput :: Monad m => Parser a m Bool-isEndOfInput = do- x <- peek- return (case x of- Nothing -> True- Just _ -> False )-{-# INLINABLE isEndOfInput #-}--{-| Fold all input values--> Control.Foldl.purely foldAll :: Monad m => Fold a b -> Parser a m b--}-foldAll - :: Monad m- => (x -> a -> x)- -- ^ Step function- -> x- -- ^ Initial accumulator- -> (x -> b)- -- ^ Extraction function- -> Parser a m b-foldAll step begin done = go begin- where- go x = do- ea <- draw- case ea of- Nothing -> return (done x)- Just a -> go $! step x a-{-# INLINABLE foldAll #-}--{-| Fold all input values monadically--> Control.Foldl.impurely foldAllM :: Monad m => FoldM a m b -> Parser a m b--}-foldAllM- :: Monad m- => (x -> a -> m x)- -- ^ Step function- -> m x- -- ^ Initial accumulator- -> (x -> m b)- -- ^ Extraction function- -> Parser a m b-foldAllM step begin done = do- x0 <- lift begin- go x0- where- go x = do- ea <- draw- case ea of- Nothing -> lift (done x)- Just a -> do- x' <- lift (step x a)- go $! x'-{-# INLINABLE foldAllM #-}--{- $parsinglenses- Connect lenses to 'Producer's using ('Lens.Family.^.') or- 'Lens.Family.view':--> (^.) :: Producer a m x-> -> Lens' (Producer a m x) (Producer b m y)-> -> Producer b m y-- Connect lenses to 'Parser's using 'Lens.Family.State.Strict.zoom':--> zoom :: Lens' (Producer a m x) (Producer b m y)-> -> Parser b m r-> -> Parser a m r-- Connect lenses to each other using ('.') (i.e. function composition):--> (.) :: Lens' (Producer a m x) (Producer b m y)-> -> Lens' (Producer b m y) (Producer c m z)-> -> Lens' (Producer a m y) (Producer c m z)--}--type Lens' a b = forall f . (Functor f) => (b -> f b) -> (a -> f a)--{-| 'span' is an improper lens that splits the 'Producer' into two 'Producer's,- where the outer 'Producer' is the longest consecutive group of elements that- satisfy the predicate--}-span- :: Monad m- => (a -> Bool) -> Lens' (Producer a m x) (Producer a m (Producer a m x))-span predicate k p0 = fmap join (k (to p0))- where--- to :: Monad m => Producer a m x -> Producer a m (Producer a m x)- to p = do- x <- lift (next p)- case x of- Left r -> return (return r)- Right (a, p') ->- if (predicate a)- then do- yield a- to p'- else return (yield a >> p')-{-# INLINABLE span #-}--{-| 'splitAt' is an improper lens that splits a 'Producer' into two 'Producer's- after a fixed number of elements--}-splitAt- :: Monad m- => Int -> Lens' (Producer a m x) (Producer a m (Producer a m x))-splitAt n0 k p0 = fmap join (k (to n0 p0))- where--- to :: Monad m => Int -> Producer a m x -> Producer a m (Producer a m x)- to n p =- if (n <= 0)- then return p- else do- x <- lift (next p)- case x of- Left r -> return (return r)- Right (a, p') -> do- yield a- to (n - 1) p'-{-# INLINABLE splitAt #-}--(^.) :: a -> ((b -> Constant b b) -> (a -> Constant b a)) -> b-a ^. lens = getConstant (lens Constant a)--{-| 'groupBy' splits a 'Producer' into two 'Producer's after the first group of- elements that are equal according to the equality predicate--}-groupBy- :: Monad m- => (a -> a -> Bool)- -> Lens' (Producer a m x) (Producer a m (Producer a m x))-groupBy equals k p0 = fmap join (k (to p0))- where--- to :: Monad m => Producer a m r -> Producer a m (Producer a m x)- to p = do- x <- lift (next p)- case x of- Left r -> return (return r)- Right (a, p') -> (yield a >> p') ^. span (equals a)-{-# INLINABLE groupBy #-}---- | Like 'groupBy', where the equality predicate is ('==')-group- :: (Monad m, Eq a) => Lens' (Producer a m x) (Producer a m (Producer a m x))-group = groupBy (==)-{-# INLINABLE group #-}--{-| Convert a 'Consumer' to a 'Parser'-- 'Nothing' signifies end of input--}-toParser :: Monad m => Consumer (Maybe a) m r -> Parser a m r-toParser consumer = runEffect (lift draw >~ unsafeHoist lift consumer)-{-# INLINABLE toParser #-}---- | Convert a never-ending 'Consumer' to a 'Parser'-toParser_ :: Monad m => Consumer a m X -> Parser a m ()-toParser_ consumer = StateT $ \producer -> do- r <- runEffect (producer >-> fmap closed consumer)- return ((), return r)-{-# INLINABLE toParser_ #-}--{- $reexports- "Control.Monad.Trans.Class" re-exports 'lift'.-- "Control.Monad.Trans.State.Strict" re-exports 'StateT', 'runStateT',- 'evalStateT', and 'execStateT'.-- "Pipes" re-exports 'Producer', 'yield', and 'next'.--}+{-| Element-agnostic parsing utilities for @pipes@ + + See "Pipes.Parse.Tutorial" for an extended tutorial +-} + +{-# LANGUAGE RankNTypes #-} + +module Pipes.Parse ( + -- * Parsing + -- $parsing + Parser + , draw + , skip + , drawAll + , skipAll + , unDraw + , peek + , isEndOfInput + , foldAll + , foldAllM + + -- * Parsing Lenses + -- $parsinglenses + , span + , splitAt + , groupBy + , group + + -- * Utilities + , toParser + , toParser_ + , parseForever + , parseForever_ + + -- * Re-exports + -- $reexports + , module Control.Monad.Trans.Class + , module Control.Monad.Trans.State.Strict + , module Pipes + ) where + +import Control.Monad (join, forever, liftM) +import Control.Monad.Trans.Class (lift) +import qualified Control.Monad.Trans.State.Strict as S +import Control.Monad.Trans.State.Strict ( + StateT(StateT, runStateT), evalStateT, execStateT ) +import Data.Functor.Constant (Constant(Constant, getConstant)) +import Data.Foldable (forM_) +import Pipes.Internal (unsafeHoist, closed) +import Pipes (Producer, yield, next) +import Pipes as NoReexport + +import Prelude hiding (span, splitAt) + +{- $parsing + @pipes-parse@ handles end-of-input and pushback by storing a 'Producer' in + a 'StateT' layer. + + Connect 'Parser's to 'Producer's using either 'runStateT', 'evalStateT', or + 'execStateT': + +> runStateT :: Parser a m r -> Producer a m x -> m (r, Producer a m x) +> evalStateT :: Parser a m r -> Producer a m x -> m r +> execStateT :: Parser a m r -> Producer a m x -> m (Producer a m x) +> ^^^^^^^^^^^^^^ +> Leftovers +-} + +-- | A 'Parser' is an action that reads from and writes to a stored 'Producer' +type Parser a m r = forall x . StateT (Producer a m x) m r + +{-| Draw one element from the underlying 'Producer', returning 'Nothing' if the + 'Producer' is empty +-} +draw :: Monad m => Parser a m (Maybe a) +draw = do + p <- S.get + x <- lift (next p) + case x of + Left r -> do + S.put (return r) + return Nothing + Right (a, p') -> do + S.put p' + return (Just a) +{-# INLINABLE draw #-} + +{-| Skip one element from the underlying 'Producer', returning 'True' if + successful or 'False' if the 'Producer' is empty + +> skip = fmap isJust draw +-} +skip :: Monad m => Parser a m Bool +skip = do + x <- draw + return $ case x of + Nothing -> False + Just _ -> True +{-# INLINABLE skip #-} + +{-| Draw all elements from the underlying 'Producer' + + Note that 'drawAll' is not an idiomatic use of @pipes-parse@, but I provide + it for simple testing purposes. Idiomatic @pipes-parse@ style consumes the + elements immediately as they are generated instead of loading all elements + into memory. For example, you can use 'foldAll' or 'foldAllM' for this + purpose. +-} +drawAll :: Monad m => Parser a m [a] +drawAll = go id + where + go diffAs = do + x <- draw + case x of + Nothing -> return (diffAs []) + Just a -> go (diffAs . (a:)) +{-# INLINABLE drawAll #-} + +-- | Drain all elements from the underlying 'Producer' +skipAll :: Monad m => Parser a m () +skipAll = go + where + go = do + x <- draw + case x of + Nothing -> return () + Just _ -> go +{-# INLINABLE skipAll #-} + +-- | Push back an element onto the underlying 'Producer' +unDraw :: Monad m => a -> Parser a m () +unDraw a = S.modify (yield a >>) +{-# INLINABLE unDraw #-} + +{-| 'peek' checks the first element of the stream, but uses 'unDraw' to push the + element back so that it is available for the next 'draw' command. + +> peek = do +> x <- draw +> case x of +> Nothing -> return () +> Just a -> unDraw a +> return x +-} +peek :: Monad m => Parser a m (Maybe a) +peek = do + x <- draw + forM_ x unDraw + return x +{-# INLINABLE peek #-} + +{-| Check if the underlying 'Producer' is empty + +> isEndOfInput = fmap isNothing peek +-} +isEndOfInput :: Monad m => Parser a m Bool +isEndOfInput = do + x <- peek + return (case x of + Nothing -> True + Just _ -> False ) +{-# INLINABLE isEndOfInput #-} + +{-| Fold all input values + +> Control.Foldl.purely foldAll :: Monad m => Fold a b -> Parser a m b +-} +foldAll + :: Monad m + => (x -> a -> x) + -- ^ Step function + -> x + -- ^ Initial accumulator + -> (x -> b) + -- ^ Extraction function + -> Parser a m b +foldAll step begin done = go begin + where + go x = do + ea <- draw + case ea of + Nothing -> return (done x) + Just a -> go $! step x a +{-# INLINABLE foldAll #-} + +{-| Fold all input values monadically + +> Control.Foldl.impurely foldAllM :: Monad m => FoldM a m b -> Parser a m b +-} +foldAllM + :: Monad m + => (x -> a -> m x) + -- ^ Step function + -> m x + -- ^ Initial accumulator + -> (x -> m b) + -- ^ Extraction function + -> Parser a m b +foldAllM step begin done = do + x0 <- lift begin + go x0 + where + go x = do + ea <- draw + case ea of + Nothing -> lift (done x) + Just a -> do + x' <- lift (step x a) + go $! x' +{-# INLINABLE foldAllM #-} + +{- $parsinglenses + Connect lenses to 'Producer's using ('Lens.Family.^.') or + 'Lens.Family.view': + +> (^.) :: Producer a m x +> -> Lens' (Producer a m x) (Producer b m y) +> -> Producer b m y + + Connect lenses to 'Parser's using 'Lens.Family.State.Strict.zoom': + +> zoom :: Lens' (Producer a m x) (Producer b m y) +> -> Parser b m r +> -> Parser a m r + + Connect lenses to each other using ('.') (i.e. function composition): + +> (.) :: Lens' (Producer a m x) (Producer b m y) +> -> Lens' (Producer b m y) (Producer c m z) +> -> Lens' (Producer a m y) (Producer c m z) +-} + +type Lens' a b = forall f . (Functor f) => (b -> f b) -> a -> f a + +{-| 'span' is an improper lens that splits the 'Producer' into two 'Producer's, + where the outer 'Producer' is the longest consecutive group of elements that + satisfy the predicate +-} +span + :: Monad m + => (a -> Bool) -> Lens' (Producer a m x) (Producer a m (Producer a m x)) +span predicate k p0 = fmap join (k (to p0)) + where +-- to :: Monad m => Producer a m x -> Producer a m (Producer a m x) + to p = do + x <- lift (next p) + case x of + Left r -> return (return r) + Right (a, p') -> + if predicate a + then do + yield a + to p' + else return (yield a >> p') +{-# INLINABLE span #-} + +{-| 'splitAt' is an improper lens that splits a 'Producer' into two 'Producer's + after a fixed number of elements +-} +splitAt + :: Monad m + => Int -> Lens' (Producer a m x) (Producer a m (Producer a m x)) +splitAt n0 k p0 = fmap join (k (to n0 p0)) + where +-- to :: Monad m => Int -> Producer a m x -> Producer a m (Producer a m x) + to n p = + if n <= 0 + then return p + else do + x <- lift (next p) + case x of + Left r -> return (return r) + Right (a, p') -> do + yield a + to (n - 1) p' +{-# INLINABLE splitAt #-} + +(^.) :: a -> ((b -> Constant b b) -> a -> Constant b a) -> b +a ^. lens = getConstant (lens Constant a) + +{-| 'groupBy' splits a 'Producer' into two 'Producer's after the first group of + elements that are equal according to the equality predicate +-} +groupBy + :: Monad m + => (a -> a -> Bool) + -> Lens' (Producer a m x) (Producer a m (Producer a m x)) +groupBy equals k p0 = fmap join (k (to p0)) + where +-- to :: Monad m => Producer a m r -> Producer a m (Producer a m x) + to p = do + x <- lift (next p) + case x of + Left r -> return (return r) + Right (a, p') -> (yield a >> p') ^. span (equals a) +{-# INLINABLE groupBy #-} + +-- | Like 'groupBy', where the equality predicate is ('==') +group + :: (Monad m, Eq a) => Lens' (Producer a m x) (Producer a m (Producer a m x)) +group = groupBy (==) +{-# INLINABLE group #-} + +{-| Convert a 'Consumer' to a 'Parser' + + 'Nothing' signifies end of input +-} +toParser :: Monad m => Consumer (Maybe a) m r -> Parser a m r +toParser consumer = runEffect (lift draw >~ unsafeHoist lift consumer) +{-# INLINABLE toParser #-} + +-- | Convert a never-ending 'Consumer' to a 'Parser' +toParser_ :: Monad m => Consumer a m X -> Parser a m () +toParser_ consumer = StateT $ \producer -> do + r <- runEffect (producer >-> fmap closed consumer) + return ((), return r) +{-# INLINABLE toParser_ #-} + + +-- | Convert a 'Parser' to a 'Pipe' by running it repeatedly on the input +parseForever :: + Monad m => + (forall n. Monad n => Parser a n (Either r b)) -> + Pipe a b m r +parseForever parse = go (forever (lift await >>= yield)) + where go prod = do (b, prod') <- runStateT parse prod + either return ((>> go prod') . yield) b + +-- | Variant of `parseForever` for parsers which return a Maybe +-- instead of an Either +parseForever_ :: + Monad m => + (forall n. Monad n => Parser a n (Maybe b)) -> + Pipe a b m () +parseForever_ parse = parseForever (liftM (maybe (Left ()) Right) parse) + +{- $reexports + "Control.Monad.Trans.Class" re-exports 'lift'. + + "Control.Monad.Trans.State.Strict" re-exports 'StateT', 'runStateT', + 'evalStateT', and 'execStateT'. + + "Pipes" re-exports 'Producer', 'yield', and 'next'. +-}
src/Pipes/Parse/Tutorial.hs view
@@ -1,402 +1,402 @@-{-# OPTIONS_GHC -fno-warn-unused-imports #-}--{-| @pipes-parse@ builds upon @pipes@ to add several missing features necessary- to implement 'Parser's:-- * End-of-input detection, so that 'Parser's can react to an exhausted input- stream-- * Leftovers support, which simplifies several parsing problems-- * Connect-and-resume, to connect a 'Producer' to a 'Parser' and retrieve- unused input--}--module Pipes.Parse.Tutorial (- -- * Overview- -- $overview-- -- * Parsers- -- $parsers-- -- * Lenses- -- $lenses-- -- * Getters- -- $getters-- -- * Building Lenses- -- $buildlenses-- -- * Conclusion- -- $conclusion- ) where--import Pipes-import Pipes.Parse--{- $overview- @pipes-parse@ centers on three abstractions:-- * 'Producer's, unchanged from @pipes@-- * 'Parser's, which play a role analogous to 'Consumer's-- * 'Lens.Family2.Lens''es between 'Producer's, which play a role analogous to- 'Pipe's-- There are four ways to connect these three abstractions:-- * Connect 'Parser's to 'Producer's using 'runStateT' \/ 'evalStateT' \/- 'execStateT':--> runStateT :: Parser a m r -> Producer a m x -> m (r, Producer a m x)-> evalStateT :: Parser a m r -> Producer a m x -> m r-> execStateT :: Parser a m r -> Producer a m x -> m ( Producer a m x)--- * Connect 'Lens.Family2.Lens''es to 'Parser's using- 'Lens.Family.State.Strict.zoom'--> zoom :: Lens' (Producer a m x) (Producer b m y)-> -> Parser b m r-> -> Parser a m r-- * Connect 'Producer's to 'Lens.Family2.Lens''es using ('Lens.Family.^.') or- 'Lens.Family.view':--> (^.) :: Producer a m x-> -> Lens' (Producer a m x) (Producer b m y)-> -> Producer b m y-- * Connect 'Lens.Family2.Lens''es to 'Lens.Family2.Lens''es using ('.') (i.e.- function composition):--> (.) :: Lens' (Producer a m x) (Producer b m y)-> -> Lens' (Producer b m y) (Producer c m z)-> -> Lens' (Producer a m x) (Producer c m z)-- You can obtain the necessary lens utilities from either:- - * The @lens-family-core@ library, importing @Lens.Family@ (for- ('Lens.Family.^.') \/ 'Lens.Family.view' and 'Lens.Family.over') and- @Lens.Family.State.Strict@ (for 'Lens.Family.State.Strict.zoom'), or:-- * The @lens@ library, importing @Control.Lens@ (for ('Control.Lens.^.') \/- 'Control.Lens.view', 'Control.Lens.over' and 'Control.Lens.zoom')-- This tutorial uses @Lens.Family@ since it has fewer dependencies and simpler- types.--}--{- $parsers- 'Parser's handle end-of-input and pushback by storing a 'Producer' in a- 'StateT' layer:--> type Parser a m r = forall x . StateT (Producer a m x) m r-- To draw a single element from the underlying 'Producer', use the 'draw'- command:--> draw :: Monad m => Parser a m (Maybe a)-- 'draw' returns the next element from the 'Producer' wrapped in 'Just' or- returns 'Nothing' if the underlying 'Producer' is empty. Here's an example- 'Parser' written using 'draw' that retrieves the first two elements from a- stream:--> import Pipes.Parse->-> drawTwo :: Monad m => Parser a m (Maybe a, Maybe a)-> drawTwo = do-> mx <- draw-> my <- draw-> return (mx, my)->-> -- or: drawTwo = liftM2 (,) draw draw-- Since a 'Parser' is just a 'StateT' action, you run a 'Parser' using the- same run functions as 'StateT':--> -- Feed a 'Producer' to a 'Parser', returning the result and leftovers-> runStateT :: Parser a m r -> Producer a m x -> m (r, Producer a m x)->-> -- Feed a 'Producer' to a 'Parser', returning only the result-> evalStateT :: Parser a m r -> Producer a m x -> m r->-> -- Feed a 'Producer' to a 'Parser', returning only the leftovers-> execStateT :: Parser a m r -> Producer a m x -> m ( Producer a m x)-- All three of these functions require a 'Producer' which we feed to the- 'Parser'. For example, we can feed standard input:-->>> evalStateT drawTwo Pipes.Prelude.stdinLn-Pink<Enter>-Elephants<Enter>-(Just "Pink",Just "Elephants")-- The result is wrapped in a 'Maybe' because 'draw' can fail if the 'Producer'- is empty:-->>> evalStateT drawTwo (yield 0)-(Just 0,Nothing)-- Parsing might not necessarily consume the entire stream. We can use- 'runStateT' or 'execStateT' to retrieve unused elements that our parser does- not consume:-->>> import Pipes->>> (result, unused) <- runStateT drawTwo (each [1..4])->>> -- View the parsed result->>> result-(Just 1,Just 2)->>> -- Now print the leftovers->>> runEffect $ for unused (lift . print)-3-4---}--{- $lenses- @pipes-parse@ also provides a convenience function for testing purposes that- draws all remaining elements and returns them as a list:--> drawAll :: Monad m => Parser a m [a]-- For example:-->>> import Pipes->>> import Pipes.Parse->>> evalStateT drawAll (each [1..10])-[1,2,3,4,5,6,7,8,9,10]-- However, this function is not recommended in general because it loads the- entire input into memory, which defeats the purpose of streaming parsing.-- You can instead use 'foldAll' if you wish to fold all input elements into a- single result:-->>> evalStateT (foldAll (+) 0 id) (each [1..10])-55-- You can also use the @foldl@ package to simplify writing more complex folds:-->>> import Control.Applicative->>> import Control.Foldl as L->>> evalStateT (purely foldAll (liftA2 (,) L.sum L.maximum)) (each [1..10])-(55,Just 10)-- But what if you wanted to draw or fold just the first three elements from- an infinite stream instead of the entire input? This is what lenses are- for:--> import Lens.Family-> import Lens.Family.State.Strict-> import Pipes-> import Pipes.Parse->-> import Prelude hiding (splitAt, span)->-> drawThree :: Monad m => Parser a m [a]-> drawThree = zoom (splitAt 3) drawAll-- 'Lens.Family.State.Strict.zoom' lets you delimit a 'Parser' using a- 'Lens.Family2.Lens''. The above code says to limit 'drawAll' to a subset of- the input, in this case the first three elements:-->>> evalStateT drawThree (each [1..])-[1,2,3]-- 'splitAt' is a 'Lens.Family2.Lens'' with the following type:--> splitAt-> :: Monad m-> => Int -> Lens' (Producer a m x) (Producer a m (Producer a m x))-- The easiest way to understand 'splitAt' is to study what happens when you- use it as a getter:--> view (splitAt 3) :: Producer a m x -> Producer a m (Producer a m x) -- In this context, @(splitAt 3)@ behaves like 'splitAt' from the Prelude,- except instead of splitting a list it splits a 'Producer'. Here's an- example of how you can use 'splitAt':--> outer :: Monad m => Producer Int m (Producer Int m ())-> outer = each [1..6] ^. splitAt 3-- The above definition of @outer@ is exactly equivalent to:--> outer = do-> each [1..3]-> return (each [4..6])-- We can prove this by successively running the outer and inner 'Producer'- layers:-->>> -- Print all the elements in the outer layer and return the inner layer->>> inner <- runEffect $ for outer (lift . print)-1-2-3->>> -- Now print the elements in the inner layer->>> runEffect $ for inner (lift . print)-4-5-6-- We can also uses lenses to modify 'Parser's, using- 'Lens.Family.State.Strict.zoom'. When we combine- 'Lens.Family.State.Strict.zoom' with @(splitAt 3)@ we limit a parser to the- the first three elements of the stream. When the parser is done- 'Lens.Family.State.Strict.zoom' also returns unused elements back to the- original stream. We can demonstrate this using the following example- parser:--> splitExample :: Monad m => Parser a m ([a], Maybe a, [a])-> splitExample = do-> x <- zoom (splitAt 3) drawAll-> y <- zoom (splitAt 3) draw-> z <- zoom (splitAt 3) drawAll-> return (x, y, z)-- The second parser begins where the first parser left off:-->>> evalStateT splitExample (each [1..])-([1,2,3],Just 4,[5,6,7])-- 'span' behaves the same way, except that it uses a predicate and takes as- many consecutive elements as possible that satisfy the predicate:--> spanExample :: Monad m => Parser Int m (Maybe Int, [Int], Maybe Int)-> spanExample = do-> x <- zoom (span (>= 4)) draw-> y <- zoom (span (< 4)) drawAll-> z <- zoom (span (>= 4)) draw-> return (x, y, z)-- Note that even if the first parser fails, subsequent parsers can still- succeed because they operate under a different lens:-->>> evalStateT spanExample (each [1..])-(Nothing,[1,2,3],Just 4)-- You can even nest 'Lens.Family.State.Strict.zoom's, too:--> nestExample :: Monad m => Parser Int m (Maybe Int, [Int], Maybe Int)-> nestExample = zoom (splitAt 2) spanExample-- All the parsers from @spanExample@ now only see a subset of the input,- namely the first two elements:-->>> evalStateT nestExample (each [1..])-(Nothing,[1,2],Nothing)---}--{- $getters- Not all transformations are reversible. For example, consider the following- contrived function:--> import Pipes-> import qualified Pipes.Prelude as P->-> map' :: Monad m => (a -> b) -> Producer a m r -> Producer b m r-> map' f p = p >-> P.map f-- Given a function of type @(a -> b)@, we can transform a stream of @a@'s into- a stream of @b@'s, but not the other way around. Transformations which are- not reversible and cannot be modeled as 'Pipe's can only be modeled as- functions between 'Producer's. However, 'Pipe's are preferable to functions- between 'Producer's when possible because 'Pipe's can transform both- 'Producer's and 'Consumer's.-- If you prefer, you can use lens-like syntax for functions between- 'Producer's by promoting them to @Getter@s using 'Lens.Family.to':--> import Lens.Family->-> example :: Monad m => Producer Int m ()-> example = each [1..3] ^. to (map' (*2))-- However, a function of 'Producer's (or the equivalent @Getter@) cannot be- used transform 'Parser's (using 'Lens.Family.State.Strict.zoom' or- otherwise) . This reflects the fact that such a transformation cannot be- applied in reversed.--}--{- $buildlenses- Lenses are very easy to write if you are willing to depend on either the- @lens-family@ or @lens@ library. Both of these libraries provide an- 'Lens.Family2.Unchecked.iso' function that you can use to assemble your own- lenses. You only need two functions which reversibly transform back and- forth between a stream of @a@s and a stream of @b@s:--> -- "Forward"-> fw :: Producer a m x -> Producer b m y->-> -- "Backward"-> bw :: Producer b m y -> Producer a m x-- ... such that:--> fw . bw = id->-> bw . fw = id-- You can then convert them to a 'Lens.Family2.Lens'' using- 'Lens.Family2.Unchecked.iso':--> import Lens.Family2 (Lens')-> import Lens.Family2.Unchecked (iso)->-> lens :: Lens' (Producer a m x) (Producer b m y)-> lens = iso fw bw-- You can even do this without incurring any dependencies if you rewrite the- above code like this:--> -- This type synonym requires the 'RankNTypes' extension-> type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a)->-> lens :: Lens' (Producer a m x) (Producer b m y)-> lens k p = fmap bw (k (fw p))-- This is what @pipes-parse@ does internally, and you will find several- examples of this pattern in the source code of the "Pipes.Parse" module.-- Lenses defined using either approach will work with both the @lens@ and- @lens-family@ libraries.--}--{- $conclusion- @pipes-parse@ introduces core idioms for @pipes@-based parsing. These- idioms reuse 'Producer's, but introduce two new abstractions:- 'Lens.Family2.Lens''es and 'Parser's.-- This library is very minimal and only contains datatype-agnostic parsing- utilities, so this tutorial does not explore the full range of parsing- tricks using lenses. For example, you can also use lenses to change the- element type.-- Several downstream libraries provide more specific functionality, including:-- * @pipes-binary@: Lenses and parsers for @binary@ values-- * @pipes-attoparsec@: Converts @attoparsec@ parsers to @pipes@ parsers-- * @pipes-aeson@: Lenses and parsers for JSON values-- * @pipes-bytestring@: Lenses and parsers for byte streams-- * @pipes-text@: Lenses and parsers for text encodings-- To learn more about @pipes-parse@, ask questions, or follow development, you- can subscribe to the @haskell-pipes@ mailing list at:-- <https://groups.google.com/forum/#!forum/haskell-pipes>-- ... or you can mail the list directly at:-- <mailto:haskell-pipes@googlegroups.com>--}+{-# OPTIONS_GHC -fno-warn-unused-imports #-} + +{-| @pipes-parse@ builds upon @pipes@ to add several missing features necessary + to implement 'Parser's: + + * End-of-input detection, so that 'Parser's can react to an exhausted input + stream + + * Leftovers support, which simplifies several parsing problems + + * Connect-and-resume, to connect a 'Producer' to a 'Parser' and retrieve + unused input +-} + +module Pipes.Parse.Tutorial ( + -- * Overview + -- $overview + + -- * Parsers + -- $parsers + + -- * Lenses + -- $lenses + + -- * Getters + -- $getters + + -- * Building Lenses + -- $buildlenses + + -- * Conclusion + -- $conclusion + ) where + +import Pipes +import Pipes.Parse + +{- $overview + @pipes-parse@ centers on three abstractions: + + * 'Producer's, unchanged from @pipes@ + + * 'Parser's, which play a role analogous to 'Consumer's + + * 'Lens.Family2.Lens''es between 'Producer's, which play a role analogous to + 'Pipe's + + There are four ways to connect these three abstractions: + + * Connect 'Parser's to 'Producer's using 'runStateT' \/ 'evalStateT' \/ + 'execStateT': + +> runStateT :: Parser a m r -> Producer a m x -> m (r, Producer a m x) +> evalStateT :: Parser a m r -> Producer a m x -> m r +> execStateT :: Parser a m r -> Producer a m x -> m ( Producer a m x) + + + * Connect 'Lens.Family2.Lens''es to 'Parser's using + 'Lens.Family.State.Strict.zoom' + +> zoom :: Lens' (Producer a m x) (Producer b m y) +> -> Parser b m r +> -> Parser a m r + + * Connect 'Producer's to 'Lens.Family2.Lens''es using ('Lens.Family.^.') or + 'Lens.Family.view': + +> (^.) :: Producer a m x +> -> Lens' (Producer a m x) (Producer b m y) +> -> Producer b m y + + * Connect 'Lens.Family2.Lens''es to 'Lens.Family2.Lens''es using ('.') (i.e. + function composition): + +> (.) :: Lens' (Producer a m x) (Producer b m y) +> -> Lens' (Producer b m y) (Producer c m z) +> -> Lens' (Producer a m x) (Producer c m z) + + You can obtain the necessary lens utilities from either: + + * The @lens-family-core@ library, importing @Lens.Family@ (for + ('Lens.Family.^.') \/ 'Lens.Family.view' and 'Lens.Family.over') and + @Lens.Family.State.Strict@ (for 'Lens.Family.State.Strict.zoom'), or: + + * The @lens@ library, importing @Control.Lens@ (for ('Control.Lens.^.') \/ + 'Control.Lens.view', 'Control.Lens.over' and 'Control.Lens.zoom') + + This tutorial uses @Lens.Family@ since it has fewer dependencies and simpler + types. +-} + +{- $parsers + 'Parser's handle end-of-input and pushback by storing a 'Producer' in a + 'StateT' layer: + +> type Parser a m r = forall x . StateT (Producer a m x) m r + + To draw a single element from the underlying 'Producer', use the 'draw' + command: + +> draw :: Monad m => Parser a m (Maybe a) + + 'draw' returns the next element from the 'Producer' wrapped in 'Just' or + returns 'Nothing' if the underlying 'Producer' is empty. Here's an example + 'Parser' written using 'draw' that retrieves the first two elements from a + stream: + +> import Pipes.Parse +> +> drawTwo :: Monad m => Parser a m (Maybe a, Maybe a) +> drawTwo = do +> mx <- draw +> my <- draw +> return (mx, my) +> +> -- or: drawTwo = liftM2 (,) draw draw + + Since a 'Parser' is just a 'StateT' action, you run a 'Parser' using the + same run functions as 'StateT': + +> -- Feed a 'Producer' to a 'Parser', returning the result and leftovers +> runStateT :: Parser a m r -> Producer a m x -> m (r, Producer a m x) +> +> -- Feed a 'Producer' to a 'Parser', returning only the result +> evalStateT :: Parser a m r -> Producer a m x -> m r +> +> -- Feed a 'Producer' to a 'Parser', returning only the leftovers +> execStateT :: Parser a m r -> Producer a m x -> m ( Producer a m x) + + All three of these functions require a 'Producer' which we feed to the + 'Parser'. For example, we can feed standard input: + +>>> evalStateT drawTwo Pipes.Prelude.stdinLn +Pink<Enter> +Elephants<Enter> +(Just "Pink",Just "Elephants") + + The result is wrapped in a 'Maybe' because 'draw' can fail if the 'Producer' + is empty: + +>>> evalStateT drawTwo (yield 0) +(Just 0,Nothing) + + Parsing might not necessarily consume the entire stream. We can use + 'runStateT' or 'execStateT' to retrieve unused elements that our parser does + not consume: + +>>> import Pipes +>>> (result, unused) <- runStateT drawTwo (each [1..4]) +>>> -- View the parsed result +>>> result +(Just 1,Just 2) +>>> -- Now print the leftovers +>>> runEffect $ for unused (lift . print) +3 +4 + +-} + +{- $lenses + @pipes-parse@ also provides a convenience function for testing purposes that + draws all remaining elements and returns them as a list: + +> drawAll :: Monad m => Parser a m [a] + + For example: + +>>> import Pipes +>>> import Pipes.Parse +>>> evalStateT drawAll (each [1..10]) +[1,2,3,4,5,6,7,8,9,10] + + However, this function is not recommended in general because it loads the + entire input into memory, which defeats the purpose of streaming parsing. + + You can instead use 'foldAll' if you wish to fold all input elements into a + single result: + +>>> evalStateT (foldAll (+) 0 id) (each [1..10]) +55 + + You can also use the @foldl@ package to simplify writing more complex folds: + +>>> import Control.Applicative +>>> import Control.Foldl as L +>>> evalStateT (purely foldAll (liftA2 (,) L.sum L.maximum)) (each [1..10]) +(55,Just 10) + + But what if you wanted to draw or fold just the first three elements from + an infinite stream instead of the entire input? This is what lenses are + for: + +> import Lens.Family +> import Lens.Family.State.Strict +> import Pipes +> import Pipes.Parse +> +> import Prelude hiding (splitAt, span) +> +> drawThree :: Monad m => Parser a m [a] +> drawThree = zoom (splitAt 3) drawAll + + 'Lens.Family.State.Strict.zoom' lets you delimit a 'Parser' using a + 'Lens.Family2.Lens''. The above code says to limit 'drawAll' to a subset of + the input, in this case the first three elements: + +>>> evalStateT drawThree (each [1..]) +[1,2,3] + + 'splitAt' is a 'Lens.Family2.Lens'' with the following type: + +> splitAt +> :: Monad m +> => Int -> Lens' (Producer a m x) (Producer a m (Producer a m x)) + + The easiest way to understand 'splitAt' is to study what happens when you + use it as a getter: + +> view (splitAt 3) :: Producer a m x -> Producer a m (Producer a m x) + + In this context, @(splitAt 3)@ behaves like 'splitAt' from the Prelude, + except instead of splitting a list it splits a 'Producer'. Here's an + example of how you can use 'splitAt': + +> outer :: Monad m => Producer Int m (Producer Int m ()) +> outer = each [1..6] ^. splitAt 3 + + The above definition of @outer@ is exactly equivalent to: + +> outer = do +> each [1..3] +> return (each [4..6]) + + We can prove this by successively running the outer and inner 'Producer' + layers: + +>>> -- Print all the elements in the outer layer and return the inner layer +>>> inner <- runEffect $ for outer (lift . print) +1 +2 +3 +>>> -- Now print the elements in the inner layer +>>> runEffect $ for inner (lift . print) +4 +5 +6 + + We can also uses lenses to modify 'Parser's, using + 'Lens.Family.State.Strict.zoom'. When we combine + 'Lens.Family.State.Strict.zoom' with @(splitAt 3)@ we limit a parser to the + the first three elements of the stream. When the parser is done + 'Lens.Family.State.Strict.zoom' also returns unused elements back to the + original stream. We can demonstrate this using the following example + parser: + +> splitExample :: Monad m => Parser a m ([a], Maybe a, [a]) +> splitExample = do +> x <- zoom (splitAt 3) drawAll +> y <- zoom (splitAt 3) draw +> z <- zoom (splitAt 3) drawAll +> return (x, y, z) + + The second parser begins where the first parser left off: + +>>> evalStateT splitExample (each [1..]) +([1,2,3],Just 4,[5,6,7]) + + 'span' behaves the same way, except that it uses a predicate and takes as + many consecutive elements as possible that satisfy the predicate: + +> spanExample :: Monad m => Parser Int m (Maybe Int, [Int], Maybe Int) +> spanExample = do +> x <- zoom (span (>= 4)) draw +> y <- zoom (span (< 4)) drawAll +> z <- zoom (span (>= 4)) draw +> return (x, y, z) + + Note that even if the first parser fails, subsequent parsers can still + succeed because they operate under a different lens: + +>>> evalStateT spanExample (each [1..]) +(Nothing,[1,2,3],Just 4) + + You can even nest 'Lens.Family.State.Strict.zoom's, too: + +> nestExample :: Monad m => Parser Int m (Maybe Int, [Int], Maybe Int) +> nestExample = zoom (splitAt 2) spanExample + + All the parsers from @spanExample@ now only see a subset of the input, + namely the first two elements: + +>>> evalStateT nestExample (each [1..]) +(Nothing,[1,2],Nothing) + +-} + +{- $getters + Not all transformations are reversible. For example, consider the following + contrived function: + +> import Pipes +> import qualified Pipes.Prelude as P +> +> map' :: Monad m => (a -> b) -> Producer a m r -> Producer b m r +> map' f p = p >-> P.map f + + Given a function of type @(a -> b)@, we can transform a stream of @a@'s into + a stream of @b@'s, but not the other way around. Transformations which are + not reversible and cannot be modeled as 'Pipe's can only be modeled as + functions between 'Producer's. However, 'Pipe's are preferable to functions + between 'Producer's when possible because 'Pipe's can transform both + 'Producer's and 'Consumer's. + + If you prefer, you can use lens-like syntax for functions between + 'Producer's by promoting them to @Getter@s using 'Lens.Family.to': + +> import Lens.Family +> +> example :: Monad m => Producer Int m () +> example = each [1..3] ^. to (map' (*2)) + + However, a function of 'Producer's (or the equivalent @Getter@) cannot be + used transform 'Parser's (using 'Lens.Family.State.Strict.zoom' or + otherwise) . This reflects the fact that such a transformation cannot be + applied in reversed. +-} + +{- $buildlenses + Lenses are very easy to write if you are willing to depend on either the + @lens-family@ or @lens@ library. Both of these libraries provide an + 'Lens.Family2.Unchecked.iso' function that you can use to assemble your own + lenses. You only need two functions which reversibly transform back and + forth between a stream of @a@s and a stream of @b@s: + +> -- "Forward" +> fw :: Producer a m x -> Producer b m y +> +> -- "Backward" +> bw :: Producer b m y -> Producer a m x + + ... such that: + +> fw . bw = id +> +> bw . fw = id + + You can then convert them to a 'Lens.Family2.Lens'' using + 'Lens.Family2.Unchecked.iso': + +> import Lens.Family2 (Lens') +> import Lens.Family2.Unchecked (iso) +> +> lens :: Lens' (Producer a m x) (Producer b m y) +> lens = iso fw bw + + You can even do this without incurring any dependencies if you rewrite the + above code like this: + +> -- This type synonym requires the 'RankNTypes' extension +> type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a) +> +> lens :: Lens' (Producer a m x) (Producer b m y) +> lens k p = fmap bw (k (fw p)) + + This is what @pipes-parse@ does internally, and you will find several + examples of this pattern in the source code of the "Pipes.Parse" module. + + Lenses defined using either approach will work with both the @lens@ and + @lens-family@ libraries. +-} + +{- $conclusion + @pipes-parse@ introduces core idioms for @pipes@-based parsing. These + idioms reuse 'Producer's, but introduce two new abstractions: + 'Lens.Family2.Lens''es and 'Parser's. + + This library is very minimal and only contains datatype-agnostic parsing + utilities, so this tutorial does not explore the full range of parsing + tricks using lenses. For example, you can also use lenses to change the + element type. + + Several downstream libraries provide more specific functionality, including: + + * @pipes-binary@: Lenses and parsers for @binary@ values + + * @pipes-attoparsec@: Converts @attoparsec@ parsers to @pipes@ parsers + + * @pipes-aeson@: Lenses and parsers for JSON values + + * @pipes-bytestring@: Lenses and parsers for byte streams + + * @pipes-text@: Lenses and parsers for text encodings + + To learn more about @pipes-parse@, ask questions, or follow development, you + can subscribe to the @haskell-pipes@ mailing list at: + + <https://groups.google.com/forum/#!forum/haskell-pipes> + + ... or you can mail the list directly at: + + <mailto:haskell-pipes@googlegroups.com> +-}