1 {-# LANGUAGE RankNTypes, TypeFamilies, BangPatterns, Trustworthy #-}
11 -- ** @view@ \/ @(^.)@
14 -- ** @over@ \/ @(%~)@
20 -- * Special types: @Producer Text m (Producer Text m r)@ and @FreeT (Producer Text m) m r@
58 -- * Primitive Character Parsers
94 , module Data.ByteString
96 , module Data.Profunctor
101 import Control.Applicative ((<*))
102 import Control.Monad (liftM, join)
103 import Control.Monad.Trans.State.Strict (StateT(..), modify)
104 import qualified Data.Text as T
105 import Data.Text (Text)
106 import qualified Data.Text.Lazy as TL
107 import Data.ByteString (ByteString)
108 import Data.Functor.Constant (Constant(Constant, getConstant))
109 import Data.Functor.Identity (Identity)
110 import Data.Profunctor (Profunctor)
111 import qualified Data.Profunctor
113 import Pipes.Group (concats, intercalates, FreeT(..), FreeF(..))
114 import qualified Pipes.Group as PG
115 import qualified Pipes.Parse as PP
116 import Pipes.Parse (Parser)
117 import Pipes.Text.Encoding (Lens'_, Iso'_)
118 import qualified Pipes.Prelude as P
119 import Data.Char (isSpace)
120 import Data.Word (Word8)
121 import Foreign.Storable (sizeOf)
122 import Data.Bits (shiftL)
123 import Prelude hiding (
153 This package provides @pipes@ utilities for /text streams/ or /character streams/,
154 realized as streams of 'Text' chunks. The individual chunks are uniformly /strict/,
155 and thus you will generally want @Data.Text@ in scope. But the type
156 @Producer Text m r@ ,as we are using it, is a sort of /pipes/ equivalent of the lazy @Text@ type.
158 This particular module provides many functions equivalent in one way or another to
159 the pure functions in
160 <https://hackage.haskell.org/package/text-1.1.0.0/docs/Data-Text-Lazy.html Data.Text.Lazy>.
161 They transform, divide, group and fold text streams. Though @Producer Text m r@
162 is the type of \'effectful Text\', the functions in this module are \'pure\'
163 in the sense that they are uniformly monad-independent.
164 Simple /IO/ operations are defined in @Pipes.Text.IO@ -- as lazy IO @Text@
165 operations are in @Data.Text.Lazy.IO@. Inter-operation with @ByteString@
166 is provided in @Pipes.Text.Encoding@, which parallels @Data.Text.Lazy.Encoding@.
168 The Text type exported by @Data.Text.Lazy@ is basically that of a lazy list of
169 strict Text: the implementation is arranged so that the individual strict 'Text'
170 chunks are kept to a reasonable size; the user is not aware of the divisions
171 between the connected 'Text' chunks.
172 So also here: the functions in this module are designed to operate on streams that
173 are insensitive to text boundaries. This means that they may freely split
174 text into smaller texts and /discard empty texts/. The objective, though, is
175 that they should /never concatenate texts/ in order to provide strict upper
176 bounds on memory usage.
178 For example, to stream only the first three lines of 'stdin' to 'stdout' you
182 > import qualified Pipes.Text as Text
183 > import qualified Pipes.Text.IO as Text
184 > import Pipes.Group (takes')
187 > main = runEffect $ takeLines 3 Text.stdin >-> Text.stdout
189 > takeLines n = Text.unlines . takes' n . view Text.lines
191 The above program will never bring more than one chunk of text (~ 32 KB) into
192 memory, no matter how long the lines are.
196 As this example shows, one superficial difference from @Data.Text.Lazy@
197 is that many of the operations, like 'lines', are \'lensified\'; this has a
198 number of advantages (where it is possible); in particular it facilitates their
199 use with 'Parser's of Text (in the general <http://hackage.haskell.org/package/pipes-parse-3.0.1/docs/Pipes-Parse-Tutorial.html pipes-parse>
200 sense.) The disadvantage, famously, is that the messages you get for type errors can be
201 a little alarming. The remarks that follow in this section are for non-lens adepts.
203 Each lens exported here, e.g. 'lines', 'chunksOf' or 'splitAt', reduces to the
204 intuitively corresponding function when used with @view@ or @(^.)@. Instead of
207 > splitAt 17 producer
209 as we would with the Prelude or Text functions, we write
211 > view (splitAt 17) producer
215 > producer ^. splitAt 17
217 This may seem a little indirect, but note that many equivalents of
218 @Text -> Text@ functions are exported here as 'Pipe's. Here too we recover the intuitively
219 corresponding functions by prefixing them with @(>->)@. Thus something like
221 > stripLines = Text.unlines . Group.maps (>-> Text.stripStart) . view Text.lines
223 would drop the leading white space from each line.
225 The lenses in this library are marked as /improper/; this just means that
226 they don't admit all the operations of an ideal lens, but only /getting/ and /focusing/.
227 Just for this reason, though, the magnificent complexities of the lens libraries
228 are a distraction. The lens combinators to keep in mind, the ones that make sense for
229 our lenses, are @view@ \/ @(^.)@), @over@ \/ @(%~)@ , and @zoom@.
231 One need only keep in mind that if @l@ is a @Lens'_ a b@, then:
235 @view l@ is a function @a -> b@ . Thus @view l a@ (also written @a ^. l@ )
236 is the corresponding @b@; as was said above, this function will be exactly the
237 function you think it is, given its name. Thus to uppercase the first n characters
238 of a Producer, leaving the rest the same, we could write:
241 > upper n p = do p' <- p ^. Text.splitAt n >-> Text.toUpper
245 @over l@ is a function @(b -> b) -> a -> a@. Thus, given a function that modifies
246 @b@s, the lens lets us modify an @a@ by applying @f :: b -> b@ to
247 the @b@ that we can \"see\" through the lens. So @over l f :: a -> a@
248 (it can also be written @l %~ f@).
249 For any particular @a@, then, @over l f a@ or @(l %~ f) a@ is a revised @a@.
250 So above we might have written things like these:
252 > stripLines = Text.lines %~ maps (>-> Text.stripStart)
253 > stripLines = over Text.lines (maps (>-> Text.stripStart))
254 > upper n = Text.splitAt n %~ (>-> Text.toUpper)
258 @zoom l@, finally, is a function from a @Parser b m r@
259 to a @Parser a m r@ (or more generally a @StateT (Producer b m x) m r@).
260 Its use is easiest to see with an decoding lens like 'utf8', which
261 \"sees\" a Text producer hidden inside a ByteString producer:
262 @drawChar@ is a Text parser, returning a @Maybe Char@, @zoom utf8 drawChar@ is
263 a /ByteString/ parser, returning a @Maybe Char@. @drawAll@ is a Parser that returns
264 a list of everything produced from a Producer, leaving only the return value; it would
265 usually be unreasonable to use it. But @zoom (splitAt 17) drawAll@
266 returns a list of Text chunks containing the first seventeen Chars, and returns the rest of
267 the Text Producer for further parsing. Suppose that we want, inexplicably, to
268 modify the casing of a Text Producer according to any instruction it might
269 contain at the start. Then we might write something like this:
271 > obey :: Monad m => Producer Text m b -> Producer Text m b
272 > obey p = do (ts, p') <- lift $ runStateT (zoom (Text.splitAt 7) drawAll) p
273 > let seven = T.concat ts
274 > case T.toUpper seven of
275 > "TOUPPER" -> p' >-> Text.toUpper
276 > "TOLOWER" -> p' >-> Text.toLower
277 > _ -> do yield seven
281 > >>> let doc = each ["toU","pperTh","is document.\n"]
282 > >>> runEffect $ obey doc >-> Text.stdout
285 The purpose of exporting lenses is the mental economy achieved with this three-way
286 applicability. That one expression, e.g. @lines@ or @splitAt 17@ can have these
287 three uses is no more surprising than that a pipe can act as a function modifying
288 the output of a producer, namely by using @>->@ to its left: @producer >-> pipe@
289 -- but can /also/ modify the inputs to a consumer by using @>->@ to its right:
292 The three functions, @view@ \/ @(^.)@, @over@ \/ @(%~)@ and @zoom@ are supplied by
293 both <http://hackage.haskell.org/package/lens lens> and
294 <http://hackage.haskell.org/package/lens-family lens-family> The use of 'zoom' is explained
295 in <http://hackage.haskell.org/package/pipes-parse-3.0.1/docs/Pipes-Parse-Tutorial.html Pipes.Parse.Tutorial>
296 and to some extent in the @Pipes.Text.Encoding@ module here.
300 These simple 'lines' examples reveal a more important difference from @Data.Text.Lazy@ .
301 This is in the types that are most closely associated with our central text type,
302 @Producer Text m r@. In @Data.Text@ and @Data.Text.Lazy@ we find functions like
304 > splitAt :: Int -> Text -> (Text, Text)
305 > lines :: Text -> [Text]
306 > chunksOf :: Int -> Text -> [Text]
308 which relate a Text with a pair of Texts or a list of Texts.
309 The corresponding functions here (taking account of \'lensification\') are
311 > view . splitAt :: (Monad m, Integral n) => n -> Producer Text m r -> Producer Text m (Producer Text m r)
312 > view lines :: Monad m => Producer Text m r -> FreeT (Producer Text m) m r
313 > view . chunksOf :: (Monad m, Integral n) => n -> Producer Text m r -> FreeT (Producer Text m) m r
315 Some of the types may be more readable if you imagine that we have introduced
316 our own type synonyms
318 > type Text m r = Producer T.Text m r
319 > type Texts m r = FreeT (Producer T.Text m) m r
321 Then we would think of the types above as
323 > view . splitAt :: (Monad m, Integral n) => n -> Text m r -> Text m (Text m r)
324 > view lines :: (Monad m) => Text m r -> Texts m r
325 > view . chunksOf :: (Monad m, Integral n) => n -> Text m r -> Texts m r
327 which brings one closer to the types of the similar functions in @Data.Text.Lazy@
329 In the type @Producer Text m (Producer Text m r)@ the second
330 element of the \'pair\' of effectful Texts cannot simply be retrieved
331 with something like 'snd'. This is an \'effectful\' pair, and one must work
332 through the effects of the first element to arrive at the second Text stream, even
333 if you are proposing to throw the Text in the first element away.
334 Note that we use Control.Monad.join to fuse the pair back together, since it specializes to
336 > join :: Monad m => Producer Text m (Producer m r) -> Producer m r
338 The return type of 'lines', 'words', 'chunksOf' and the other /splitter/ functions,
339 @FreeT (Producer m Text) m r@ -- our @Texts m r@ -- is the type of (effectful)
340 lists of (effectful) texts. The type @([Text],r)@ might be seen to gather
341 together things of the forms:
346 > (Text, (Text, (Text, r)))
347 > (Text, (Text, (Text, (Text, r))))
350 (We might also have identified the sum of those types with @Free ((,) Text) r@
351 -- or, more absurdly, @FreeT ((,) Text) Identity r@.)
353 Similarly, our type @Texts m r@, or @FreeT (Text m) m r@ -- in fact called
354 @FreeT (Producer Text m) m r@ here -- encompasses all the members of the sequence:
359 > Text m (Text m (Text m r))
360 > Text m (Text m (Text m (Text m r)))
363 We might have used a more specialized type in place of @FreeT (Producer a m) m r@,
364 or indeed of @FreeT (Producer Text m) m r@, but it is clear that the correct
365 result type of 'lines' will be isomorphic to @FreeT (Producer Text m) m r@ .
369 > lines :: Monad m => Lens'_ (Producer Text m r) (FreeT (Producer Text m) m r)
370 > view . lines :: Monad m => Producer Text m r -> FreeT (Producer Text m) m r
372 should really have the type
374 > lines :: Monad m => Pipe Text Text m r
376 as e.g. 'toUpper' does. But this would spoil the control we are
377 attempting to maintain over the size of chunks. It is in fact just
378 as unreasonable to want such a pipe as to want
380 > Data.Text.Lazy.lines :: Text -> Text
382 to 'rechunk' the strict Text chunks inside the lazy Text to respect
383 line boundaries. In fact we have
385 > Data.Text.Lazy.lines :: Text -> [Text]
386 > Prelude.lines :: String -> [String]
388 where the elements of the list are themselves lazy Texts or Strings; the use
389 of @FreeT (Producer Text m) m r@ is simply the 'effectful' version of this.
391 The @Pipes.Group@ module, which can generally be imported without qualification,
392 provides many functions for working with things of type @FreeT (Producer a m) m r@.
393 In particular it conveniently exports the constructors for @FreeT@ and the associated
394 @FreeF@ type -- a fancy form of @Either@, namely
396 > data FreeF f a b = Pure a | Free (f b)
398 for pattern-matching. Consider the implementation of the 'words' function, or
399 of the part of the lens that takes us to the words; it is compact but exhibits many
400 of the points under discussion, including explicit handling of the @FreeT@ and @FreeF@
401 constuctors. Keep in mind that
403 > newtype FreeT f m a = FreeT (m (FreeF f a (FreeT f m a)))
404 > next :: Monad m => Producer a m r -> m (Either r (a, Producer a m r))
406 Thus the @do@ block after the @FreeT@ constructor is in the base monad, e.g. 'IO' or 'Identity';
407 the later subordinate block, opened by the @Free@ constructor, is in the @Producer@ monad:
409 > words :: Monad m => Producer Text m r -> FreeT (Producer Text m) m r
410 > words p = FreeT $ do -- With 'next' we will inspect p's first chunk, excluding spaces;
411 > x <- next (p >-> dropWhile isSpace) -- note that 'dropWhile isSpace' is a pipe, and is thus *applied* with '>->'.
412 > return $ case x of -- We use 'return' and so need something of type 'FreeF (Text m) r (Texts m r)'
413 > Left r -> Pure r -- 'Left' means we got no Text chunk, but only the return value; so we are done.
414 > Right (txt, p') -> Free $ do -- If we get a chunk and the rest of the producer, p', we enter the 'Producer' monad
415 > p'' <- view (break isSpace) -- When we apply 'break isSpace', we get a Producer that returns a Producer;
416 > (yield txt >> p') -- so here we yield everything up to the next space, and get the rest back.
417 > return (words p'') -- We then carry on with the rest, which is likely to begin with space.
421 -- | Convert a lazy 'TL.Text' into a 'Producer' of strict 'Text's
422 fromLazy :: (Monad m) => TL.Text -> Producer' Text m ()
423 fromLazy = TL.foldrChunks (\e a -> yield e >> a) (return ())
424 {-# INLINE fromLazy #-}
427 (^.) :: a -> ((b -> Constant b b) -> (a -> Constant b a)) -> b
428 a ^. lens = getConstant (lens Constant a)
431 -- | Apply a transformation to each 'Char' in the stream
432 map :: (Monad m) => (Char -> Char) -> Pipe Text Text m r
433 map f = P.map (T.map f)
434 {-# INLINABLE map #-}
436 {-# RULES "p >-> map f" forall p f .
437 p >-> map f = for p (\txt -> yield (T.map f txt))
440 -- | Map a function over the characters of a text stream and concatenate the results
442 :: (Monad m) => (Char -> Text) -> Pipe Text Text m r
443 concatMap f = P.map (T.concatMap f)
444 {-# INLINABLE concatMap #-}
446 {-# RULES "p >-> concatMap f" forall p f .
447 p >-> concatMap f = for p (\txt -> yield (T.concatMap f txt))
451 -- | Transform a Pipe of 'String's into one of 'Text' chunks
452 pack :: Monad m => Pipe String Text m r
454 {-# INLINEABLE pack #-}
456 {-# RULES "p >-> pack" forall p .
457 p >-> pack = for p (\txt -> yield (T.pack txt))
460 -- | Transform a Pipes of 'Text' chunks into one of 'String's
461 unpack :: Monad m => Pipe Text String m r
462 unpack = for cat (\t -> yield (T.unpack t))
463 {-# INLINEABLE unpack #-}
465 {-# RULES "p >-> unpack" forall p .
466 p >-> unpack = for p (\txt -> yield (T.unpack txt))
469 -- | @toCaseFold@, @toLower@, @toUpper@ and @stripStart@ are standard 'Text' utilities,
470 -- here acting as 'Text' pipes, rather as they would on a lazy text
471 toCaseFold :: Monad m => Pipe Text Text m r
472 toCaseFold = P.map T.toCaseFold
473 {-# INLINEABLE toCaseFold #-}
475 {-# RULES "p >-> toCaseFold" forall p .
476 p >-> toCaseFold = for p (\txt -> yield (T.toCaseFold txt))
480 -- | lowercase incoming 'Text'
481 toLower :: Monad m => Pipe Text Text m r
482 toLower = P.map T.toLower
483 {-# INLINEABLE toLower #-}
485 {-# RULES "p >-> toLower" forall p .
486 p >-> toLower = for p (\txt -> yield (T.toLower txt))
489 -- | uppercase incoming 'Text'
490 toUpper :: Monad m => Pipe Text Text m r
491 toUpper = P.map T.toUpper
492 {-# INLINEABLE toUpper #-}
494 {-# RULES "p >-> toUpper" forall p .
495 p >-> toUpper = for p (\txt -> yield (T.toUpper txt))
498 -- | Remove leading white space from an incoming succession of 'Text's
499 stripStart :: Monad m => Pipe Text Text m r
502 let text = T.stripStart chunk
507 {-# INLINEABLE stripStart #-}
509 -- | @(take n)@ only allows @n@ individual characters to pass;
510 -- contrast @Pipes.Prelude.take@ which would let @n@ chunks pass.
511 take :: (Monad m, Integral a) => a -> Pipe Text Text m ()
512 take n0 = go n0 where
517 let len = fromIntegral (T.length txt)
519 then yield (T.take (fromIntegral n) txt)
523 {-# INLINABLE take #-}
525 -- | @(drop n)@ drops the first @n@ characters
526 drop :: (Monad m, Integral a) => a -> Pipe Text Text m r
527 drop n0 = go n0 where
532 let len = fromIntegral (T.length txt)
535 yield (T.drop (fromIntegral n) txt)
538 {-# INLINABLE drop #-}
540 -- | Take characters until they fail the predicate
541 takeWhile :: (Monad m) => (Char -> Bool) -> Pipe Text Text m ()
542 takeWhile predicate = go
546 let (prefix, suffix) = T.span predicate txt
552 {-# INLINABLE takeWhile #-}
554 -- | Drop characters until they fail the predicate
555 dropWhile :: (Monad m) => (Char -> Bool) -> Pipe Text Text m r
556 dropWhile predicate = go where
559 case T.findIndex (not . predicate) txt of
564 {-# INLINABLE dropWhile #-}
566 -- | Only allows 'Char's to pass if they satisfy the predicate
567 filter :: (Monad m) => (Char -> Bool) -> Pipe Text Text m r
568 filter predicate = P.map (T.filter predicate)
569 {-# INLINABLE filter #-}
571 {-# RULES "p >-> filter q" forall p q .
572 p >-> filter q = for p (\txt -> yield (T.filter q txt))
575 -- | Strict left scan over the characters
578 => (Char -> Char -> Char) -> Char -> Pipe Text Text m r
580 yield (T.singleton begin)
585 let txt' = T.scanl step c txt
589 {-# INLINABLE scan #-}
591 {-| Fold a pure 'Producer' of strict 'Text's into a lazy
594 toLazy :: Producer Text Identity () -> TL.Text
595 toLazy = TL.fromChunks . P.toList
596 {-# INLINABLE toLazy #-}
598 {-| Fold an effectful 'Producer' of strict 'Text's into a lazy
601 Note: 'toLazyM' is not an idiomatic use of @pipes@, but I provide it for
602 simple testing purposes. Idiomatic @pipes@ style consumes the chunks
603 immediately as they are generated instead of loading them all into memory.
605 toLazyM :: (Monad m) => Producer Text m () -> m TL.Text
606 toLazyM = liftM TL.fromChunks . P.toListM
607 {-# INLINABLE toLazyM #-}
609 -- | Reduce the text stream using a strict left fold over characters
612 => (x -> Char -> x) -> x -> (x -> r) -> Producer Text m () -> m r
613 foldChars step begin done = P.fold (T.foldl' step) begin done
614 {-# INLINABLE foldChars #-}
616 -- | Retrieve the first 'Char'
617 head :: (Monad m) => Producer Text m () -> m (Maybe Char)
623 Left _ -> return Nothing
624 Right (c, _) -> return (Just c)
625 {-# INLINABLE head #-}
627 -- | Retrieve the last 'Char'
628 last :: (Monad m) => Producer Text m () -> m (Maybe Char)
638 else go (Just $ T.last txt) p'
639 {-# INLINABLE last #-}
641 -- | Determine if the stream is empty
642 null :: (Monad m) => Producer Text m () -> m Bool
644 {-# INLINABLE null #-}
646 -- | Count the number of characters in the stream
647 length :: (Monad m, Num n) => Producer Text m () -> m n
648 length = P.fold (\n txt -> n + fromIntegral (T.length txt)) 0 id
649 {-# INLINABLE length #-}
651 -- | Fold that returns whether 'M.Any' received 'Char's satisfy the predicate
652 any :: (Monad m) => (Char -> Bool) -> Producer Text m () -> m Bool
653 any predicate = P.any (T.any predicate)
654 {-# INLINABLE any #-}
656 -- | Fold that returns whether 'M.All' received 'Char's satisfy the predicate
657 all :: (Monad m) => (Char -> Bool) -> Producer Text m () -> m Bool
658 all predicate = P.all (T.all predicate)
659 {-# INLINABLE all #-}
661 -- | Return the maximum 'Char' within a text stream
662 maximum :: (Monad m) => Producer Text m () -> m (Maybe Char)
663 maximum = P.fold step Nothing id
668 else Just $ case mc of
669 Nothing -> T.maximum txt
670 Just c -> max c (T.maximum txt)
671 {-# INLINABLE maximum #-}
673 -- | Return the minimum 'Char' within a text stream (surely very useful!)
674 minimum :: (Monad m) => Producer Text m () -> m (Maybe Char)
675 minimum = P.fold step Nothing id
681 Nothing -> Just (T.minimum txt)
682 Just c -> Just (min c (T.minimum txt))
683 {-# INLINABLE minimum #-}
685 -- | Find the first element in the stream that matches the predicate
688 => (Char -> Bool) -> Producer Text m () -> m (Maybe Char)
689 find predicate p = head (p >-> filter predicate)
690 {-# INLINABLE find #-}
692 -- | Index into a text stream
694 :: (Monad m, Integral a)
695 => a-> Producer Text m () -> m (Maybe Char)
696 index n p = head (p >-> drop n)
697 {-# INLINABLE index #-}
700 -- | Store a tally of how many segments match the given 'Text'
701 count :: (Monad m, Num n) => Text -> Producer Text m () -> m n
702 count c p = P.fold (+) 0 id (p >-> P.map (fromIntegral . T.count c))
703 {-# INLINABLE count #-}
706 -- | Consume the first character from a stream of 'Text'
708 -- 'next' either fails with a 'Left' if the 'Producer' has no more characters or
709 -- succeeds with a 'Right' providing the next character and the remainder of the
715 -> m (Either r (Char, Producer Text m r))
721 Left r -> return (Left r)
722 Right (txt, p') -> case (T.uncons txt) of
724 Just (c, txt') -> return (Right (c, yield txt' >> p'))
725 {-# INLINABLE nextChar #-}
727 -- | Draw one 'Char' from a stream of 'Text', returning 'Left' if the 'Producer' is empty
729 drawChar :: (Monad m) => Parser Text m (Maybe Char)
733 Nothing -> return Nothing
734 Just txt -> case (T.uncons txt) of
739 {-# INLINABLE drawChar #-}
741 -- | Push back a 'Char' onto the underlying 'Producer'
742 unDrawChar :: (Monad m) => Char -> Parser Text m ()
743 unDrawChar c = modify (yield (T.singleton c) >>)
744 {-# INLINABLE unDrawChar #-}
746 {-| 'peekChar' checks the first 'Char' in the stream, but uses 'unDrawChar' to
752 > Left _ -> return ()
753 > Right c -> unDrawChar c
758 peekChar :: (Monad m) => Parser Text m (Maybe Char)
763 Just c -> unDrawChar c
765 {-# INLINABLE peekChar #-}
767 {-| Check if the underlying 'Producer' has no more characters
769 Note that this will skip over empty 'Text' chunks, unlike
770 'PP.isEndOfInput' from @pipes-parse@, which would consider
771 an empty 'Text' a valid bit of input.
773 > isEndOfChars = liftM isLeft peekChar
775 isEndOfChars :: (Monad m) => Parser Text m Bool
781 {-# INLINABLE isEndOfChars #-}
784 -- | Splits a 'Producer' after the given number of characters
786 :: (Monad m, Integral n)
788 -> Lens'_ (Producer Text m r)
789 (Producer Text m (Producer Text m r))
790 splitAt n0 k p0 = fmap join (k (go n0 p0))
796 Left r -> return (return r)
797 Right (txt, p') -> do
798 let len = fromIntegral (T.length txt)
804 let (prefix, suffix) = T.splitAt (fromIntegral n) txt
806 return (yield suffix >> p')
807 {-# INLINABLE splitAt #-}
810 -- | Split a text stream in two, producing the longest
811 -- consecutive group of characters that satisfies the predicate
812 -- and returning the rest
817 -> Lens'_ (Producer Text m r)
818 (Producer Text m (Producer Text m r))
819 span predicate k p0 = fmap join (k (go p0))
824 Left r -> return (return r)
825 Right (txt, p') -> do
826 let (prefix, suffix) = T.span predicate txt
833 return (yield suffix >> p')
834 {-# INLINABLE span #-}
836 {-| Split a text stream in two, producing the longest
837 consecutive group of characters that don't satisfy the predicate
842 -> Lens'_ (Producer Text m r)
843 (Producer Text m (Producer Text m r))
844 break predicate = span (not . predicate)
845 {-# INLINABLE break #-}
847 {-| Improper lens that splits after the first group of equivalent Chars, as
848 defined by the given equivalence relation
852 => (Char -> Char -> Bool)
853 -> Lens'_ (Producer Text m r)
854 (Producer Text m (Producer Text m r))
855 groupBy equals k p0 = fmap join (k ((go p0))) where
859 Left r -> return (return r)
860 Right (txt, p') -> case T.uncons txt of
862 Just (c, _) -> (yield txt >> p') ^. span (equals c)
863 {-# INLINABLE groupBy #-}
865 -- | Improper lens that splits after the first succession of identical 'Char' s
867 => Lens'_ (Producer Text m r)
868 (Producer Text m (Producer Text m r))
870 {-# INLINABLE group #-}
872 {-| Improper lens that splits a 'Producer' after the first word
874 Unlike 'words', this does not drop leading whitespace
877 => Lens'_ (Producer Text m r)
878 (Producer Text m (Producer Text m r))
879 word k p0 = fmap join (k (to p0))
882 p' <- p^.span isSpace
884 {-# INLINABLE word #-}
888 => Lens'_ (Producer Text m r)
889 (Producer Text m (Producer Text m r))
890 line = break (== '\n')
892 {-# INLINABLE line #-}
895 -- | Intersperse a 'Char' in between the characters of stream of 'Text'
897 :: (Monad m) => Char -> Producer Text m r -> Producer Text m r
904 Right (txt, p') -> do
905 yield (T.intersperse c txt)
911 Right (txt, p') -> do
912 yield (T.singleton c)
913 yield (T.intersperse c txt)
915 {-# INLINABLE intersperse #-}
919 -- | Improper isomorphism between a 'Producer' of 'ByteString's and 'Word8's
920 packChars :: Monad m => Iso'_ (Producer Char m x) (Producer Text m x)
921 packChars = Data.Profunctor.dimap to (fmap from)
923 -- to :: Monad m => Producer Char m x -> Producer Text m x
924 to p = PG.folds step id done (p^.PG.chunksOf defaultChunkSize)
926 step diffAs c = diffAs . (c:)
928 done diffAs = T.pack (diffAs [])
930 -- from :: Monad m => Producer Text m x -> Producer Char m x
931 from p = for p (each . T.unpack)
933 {-# INLINABLE packChars #-}
935 defaultChunkSize :: Int
936 defaultChunkSize = 16384 - (sizeOf (undefined :: Int) `shiftL` 1)
938 -- | Split a text stream into 'FreeT'-delimited text streams of fixed size
940 :: (Monad m, Integral n)
941 => n -> Lens'_ (Producer Text m r)
942 (FreeT (Producer Text m) m r)
943 chunksOf n k p0 = fmap concats (k (FreeT (go p0)))
949 Right (txt, p') -> Free $ do
950 p'' <- (yield txt >> p') ^. splitAt n
951 return $ FreeT (go p'')
952 {-# INLINABLE chunksOf #-}
955 {-| Split a text stream into sub-streams delimited by characters that satisfy the
962 -> FreeT (Producer Text m) m r
963 splitsWith predicate p0 = FreeT (go0 p0)
968 Left r -> return (Pure r)
972 else return $ Free $ do
973 p'' <- (yield txt >> p') ^. span (not . predicate)
974 return $ FreeT (go1 p'')
979 Right (_, p') -> Free $ do
980 p'' <- p' ^. span (not . predicate)
981 return $ FreeT (go1 p'')
982 {-# INLINABLE splitsWith #-}
984 -- | Split a text stream using the given 'Char' as the delimiter
987 -> Lens'_ (Producer Text m r)
988 (FreeT (Producer Text m) m r)
990 fmap (PG.intercalates (yield (T.singleton c))) (k (splitsWith (c ==) p))
991 {-# INLINABLE splits #-}
993 {-| Isomorphism between a stream of 'Text' and groups of equivalent 'Char's , using the
994 given equivalence relation
998 => (Char -> Char -> Bool)
999 -> Lens'_ (Producer Text m x) (FreeT (Producer Text m) m x)
1000 groupsBy equals k p0 = fmap concats (k (FreeT (go p0))) where
1001 go p = do x <- next p
1002 case x of Left r -> return (Pure r)
1003 Right (bs, p') -> case T.uncons bs of
1005 Just (c, _) -> do return $ Free $ do
1006 p'' <- (yield bs >> p')^.span (equals c)
1007 return $ FreeT (go p'')
1008 {-# INLINABLE groupsBy #-}
1011 -- | Like 'groupsBy', where the equality predicate is ('==')
1014 => Lens'_ (Producer Text m x) (FreeT (Producer Text m) m x)
1015 groups = groupsBy (==)
1016 {-# INLINABLE groups #-}
1020 {-| Split a text stream into 'FreeT'-delimited lines
1023 :: (Monad m) => Iso'_ (Producer Text m r) (FreeT (Producer Text m) m r)
1024 lines = Data.Profunctor.dimap _lines (fmap _unlines)
1026 _lines p0 = FreeT (go0 p0)
1031 Left r -> return (Pure r)
1035 else return $ Free $ go1 (yield txt >> p')
1037 p' <- p ^. break ('\n' ==)
1041 Left r -> return $ Pure r
1042 Right (_, p'') -> go0 p''
1045 -- => FreeT (Producer Text m) m x -> Producer Text m x
1046 _unlines = concats . PG.maps (<* yield (T.singleton '\n'))
1049 {-# INLINABLE lines #-}
1052 -- | Split a text stream into 'FreeT'-delimited words
1054 :: (Monad m) => Iso'_ (Producer Text m r) (FreeT (Producer Text m) m r)
1055 words = Data.Profunctor.dimap go (fmap _unwords)
1058 x <- next (p >-> dropWhile isSpace)
1061 Right (bs, p') -> Free $ do
1062 p'' <- (yield bs >> p') ^. break isSpace
1064 _unwords = PG.intercalates (yield $ T.singleton ' ')
1066 {-# INLINABLE words #-}
1069 {-| 'intercalate' concatenates the 'FreeT'-delimited text streams after
1070 interspersing a text stream in between them
1074 => Producer Text m ()
1075 -> FreeT (Producer Text m) m r
1076 -> Producer Text m r
1077 intercalate p0 = go0
1080 x <- lift (runFreeT f)
1087 x <- lift (runFreeT f)
1094 {-# INLINABLE intercalate #-}
1096 {-| Join 'FreeT'-delimited lines into a text stream
1099 :: (Monad m) => FreeT (Producer Text m) m r -> Producer Text m r
1103 x <- lift (runFreeT f)
1108 yield $ T.singleton '\n'
1110 {-# INLINABLE unlines #-}
1112 {-| Join 'FreeT'-delimited words into a text stream
1115 :: (Monad m) => FreeT (Producer Text m) m r -> Producer Text m r
1116 unwords = intercalate (yield $ T.singleton ' ')
1117 {-# INLINABLE unwords #-}
1122 @Data.Text@ re-exports the 'Text' type.
1124 @Pipes.Parse@ re-exports 'input', 'concat', 'FreeT' (the type) and the 'Parse' synonym.