@@ -2,6 +2,8 @@
2{-# LANGUAGE RankNTypes #-}
2{-# LANGUAGE RankNTypes #-}
3{-# LANGUAGE ScopedTypeVariables #-}
3{-# LANGUAGE ScopedTypeVariables #-}
6-- Module : Streamly.Internal.Data.Parser.ParserD
8-- Module : Streamly.Internal.Data.Parser.ParserD
7-- Copyright : (c) 2020 Composewell Technologies
9-- Copyright : (c) 2020 Composewell Technologies
@@ -10,40 +12,19 @@
10-- Stability : experimental
12-- Stability : experimental
11-- Portability : GHC
13-- Portability : GHC
13−-- Fast streaming parsers.
15+-- Direct style parser implementation with stream fusion.
15−-- 'Applicative' and 'Alternative' type class based combinators from the
16−-- <http://hackage.haskell.org/package/parser-combinators parser-combinators>
17−-- package can also be used with the 'Parser' type. However, there are two
18−-- important differences between @parser-combinators@ and the equivalent ones
19−-- provided in this module in terms of performance:
21−-- 1) @parser-combinators@ use plain Haskell lists to collect the results, in a
22−-- strict Monad like IO, the results are necessarily buffered before they can
23−-- be consumed. This may not perform optimally in streaming applications
24−-- processing large amounts of data. Equivalent combinators in this module can
25−-- consume the results of parsing using a 'Fold', thus providing a scalability
26−-- and a generic consumer.
28−-- 2) Several combinators in this module can be many times faster because of
29−-- stream fusion. For example, 'Streamly.Internal.Data.Parser.many' combinator
30−-- in this module is much faster than the 'Control.Applicative.many' combinator
31−-- of 'Alternative' type class.
33−-- Failing parsers in this module throw the 'ParseError' exception.
35−-- XXX As far as possible, try that the combinators in this module and in
36−-- "Text.ParserCombinators.ReadP/parser-combinators/parsec/megaparsec/attoparsec"
37−-- have consistent names. takeP/takeWhileP/munch?
39module Streamly.Internal.Data.Parser.ParserD
17module Streamly.Internal.Data.Parser.ParserD
41 Parser (..)
19 Parser (..)
43 -- First order parsers
23 -- First order parsers
44 -- * Accumulators
24 -- * Accumulators
45 , fromFold
25 , fromFold
46 , toParserK
26 , toParserK
@@ -101,6 +82,7 @@ module Streamly.Internal.Data.Parser.ParserD
102 -- ** Sequential Applicative
83 -- ** Sequential Applicative
103 , splitWith
84 , splitWith
105 -- ** Parallel Applicatives
87 -- ** Parallel Applicatives
106 , teeWith
88 , teeWith
@@ -113,6 +95,9 @@ module Streamly.Internal.Data.Parser.ParserD
113 -- secondary parser.
95 -- secondary parser.
114 , deintercalate
96 , deintercalate
98+ -- ** Sequential Alternative
116 -- ** Parallel Alternatives
101 -- ** Parallel Alternatives
117 , shortest
102 , shortest
118 , longest
103 , longest
@@ -121,6 +106,7 @@ module Streamly.Internal.Data.Parser.ParserD
121 -- * N-ary Combinators
106 -- * N-ary Combinators
122 -- ** Sequential Collection
107 -- ** Sequential Collection
123 , sequence
108 , sequence
125 -- ** Sequential Repetition
111 -- ** Sequential Repetition
@@ -179,7 +165,7 @@ where
179import Control.Exception (assert)
165import Control.Exception (assert)
180import Control.Monad.Catch (MonadCatch, MonadThrow(..))
166import Control.Monad.Catch (MonadCatch, MonadThrow(..))
181import Prelude
167import Prelude
182− hiding (any, all, take, takeWhile, sequence)
168+ hiding (any, all, take, takeWhile, sequence, concatMap)
184import Streamly.Internal.Data.Fold.Types (Fold(..))
170import Streamly.Internal.Data.Fold.Types (Fold(..))
@@ -194,7 +180,9 @@ import Streamly.Internal.Data.Strict
194-- Upgrade folds to parses
180-- Upgrade folds to parses
195-------------------------------------------------------------------------------
181-------------------------------------------------------------------------------
197−-- | The resulting parse never terminates and never errors out.
183+-- | See 'Streamly.Internal.Data.Parser.fromFold'.
199{-# INLINE fromFold #-}
187{-# INLINE fromFold #-}
200fromFold :: Monad m => Fold m a b -> Parser m a b
188fromFold :: Monad m => Fold m a b -> Parser m a b
@@ -205,24 +193,45 @@ fromFold (Fold fstep finitial fextract) = Parser step finitial fextract
205 step s a = Yield 0 <$> fstep s a
193 step s a = Yield 0 <$> fstep s a
207-------------------------------------------------------------------------------
195-------------------------------------------------------------------------------
208−-- Convert to CPS style parser representation
196+-- Convert to and from CPS style parser representation
209-------------------------------------------------------------------------------
197-------------------------------------------------------------------------------
199+-- | Convert a direct style 'Parser' to a CPS style 'K.Parser'.
211−{-# INLINE toParserK #-}
203+{-# INLINE_LATE toParserK #-}
212toParserK :: MonadCatch m => Parser m a b -> K.Parser m a b
204toParserK :: MonadCatch m => Parser m a b -> K.Parser m a b
213toParserK (Parser step initial extract) =
205toParserK (Parser step initial extract) =
214 K.MkParser $ \inp yieldk ->
206 K.MkParser $ \inp yieldk ->
215 Z.parse step initial extract inp >>= yieldk
207 Z.parse step initial extract inp >>= yieldk
209+-- | Convert a CPS style 'K.Parser' to a direct style 'Parser'.
213+{-# NOINLINE fromParserK #-}
214+fromParserK :: Monad m => K.Parser m a b -> Parser m a b
215+fromParserK _ = Parser step initial extract
220+ step () _ = error "fromParserK: unimplemented"
221+ extract () = error "fromParserK: unimplemented"
223+#ifndef DISABLE_FUSION
224+{-# RULES "fromParserK/toParserK fusion" [2]
225+ forall s. toParserK (fromParserK s) = s #-}
226+{-# RULES "toParserK/fromParserK fusion" [2]
227+ forall s. fromParserK (toParserK s) = s #-}
217-------------------------------------------------------------------------------
230-------------------------------------------------------------------------------
218-- Terminating but not failing folds
231-- Terminating but not failing folds
219-------------------------------------------------------------------------------
232-------------------------------------------------------------------------------
221−-- |
234+{-# INLINE any #-}
222−-- >>> S.parse (PR.any (== 0)) $ S.fromList [1,0,1]
225−{-# INLINABLE any #-}
226any :: Monad m => (a -> Bool) -> Parser m a Bool
235any :: Monad m => (a -> Bool) -> Parser m a Bool
227any predicate = Parser step initial return
236any predicate = Parser step initial return
@@ -238,10 +247,6 @@ any predicate = Parser step initial return
238 then Stop 0 True
247 then Stop 0 True
239 else Yield 0 False
248 else Yield 0 False
242−-- >>> S.parse (PR.all (== 0)) $ S.fromList [1,0,1]
245{-# INLINABLE all #-}
250{-# INLINABLE all #-}
246all :: Monad m => (a -> Bool) -> Parser m a Bool
251all :: Monad m => (a -> Bool) -> Parser m a Bool
247all predicate = Parser step initial return
252all predicate = Parser step initial return
@@ -262,15 +267,7 @@ all predicate = Parser step initial return
262-- Failing Parsers
267-- Failing Parsers
263-------------------------------------------------------------------------------
268-------------------------------------------------------------------------------
265−-- | Peek the head element of a stream, without consuming it. Fails if it
270+-- | See 'Streamly.Internal.Data.Parser.peek'.
266−-- encounters end of input.
268−-- >>> S.parse ((,) <$> PR.peek <*> PR.satisfy (> 0)) $ S.fromList [1]
272−-- peek = lookAhead (satisfy True)
275-- /Internal/
272-- /Internal/
@@ -286,10 +283,7 @@ peek = Parser step initial extract
287 extract () = throwM $ ParseError "peek: end of input"
284 extract () = throwM $ ParseError "peek: end of input"
289−-- | Succeeds if we are at the end of input, fails otherwise.
286+-- | See 'Streamly.Internal.Data.Parser.eof'.
291−-- >>> S.parse ((,) <$> PR.satisfy (> 0) <*> PR.eof) $ S.fromList [1]
294-- /Internal/
288-- /Internal/
@@ -303,10 +297,7 @@ eof = Parser step initial return
304 step () _ = return $ Error "eof: not at end of input"
298 step () _ = return $ Error "eof: not at end of input"
306−-- | Returns the next element if it passes the predicate, fails otherwise.
300+-- | See 'Streamly.Internal.Data.Parser.satisfy'.
308−-- >>> S.parse (PR.satisfy (== 1)) $ S.fromList [1,0,1]
311-- /Internal/
302-- /Internal/
@@ -329,21 +320,7 @@ satisfy predicate = Parser step initial extract
329-- Taking elements
320-- Taking elements
330-------------------------------------------------------------------------------
321-------------------------------------------------------------------------------
332−-- XXX Once we have terminating folds, this Parse should get replaced by Fold.
323+-- | See 'Streamly.Internal.Data.Parser.take'.
333−-- Alternatively, we can name it "chunkOf" and the corresponding time domain
334−-- combinator as "intervalOf" or even "chunk" and "interval".
336−-- | Take at most @n@ input elements and fold them using the supplied fold.
338−-- Stops after @n@ elements.
341−-- >>> S.parse (PR.take 1 FL.toList) $ S.fromList [1]
345−-- S.chunksOf n f = S.splitParse (FL.take n f)
348-- /Internal/
325-- /Internal/
@@ -365,16 +342,7 @@ take n (Fold fstep finitial fextract) = Parser step initial extract
366 extract (Tuple' _ r) = fextract r
343 extract (Tuple' _ r) = fextract r
368−--
345+-- | See 'Streamly.Internal.Data.Parser.takeEQ'.
369−-- XXX can we use a "cmp" operation in a common implementation?
371−-- | Stops after taking exactly @n@ input elements.
373−-- * Stops - after @n@ elements.
374−-- * Fails - if the stream ends before it can collect @n@ elements.
376−-- >>> S.parse (PR.takeEQ 4 FL.toList) $ S.fromList [1,0,1]
377−-- > "takeEQ: Expecting exactly 4 elements, got 3"
379-- /Internal/
347-- /Internal/
@@ -403,16 +371,7 @@ takeEQ n (Fold fstep finitial fextract) = Parser step initial extract
403 "takeEQ: Expecting exactly " ++ show n
371 "takeEQ: Expecting exactly " ++ show n
404 ++ " elements, got " ++ show i
372 ++ " elements, got " ++ show i
406−-- | Take at least @n@ input elements, but can collect more.
374+-- | See 'Streamly.Internal.Data.Parser.takeGE'.
409−-- * Fails - if the stream ends before producing @n@ elements.
411−-- >>> S.parse (PR.takeGE 4 FL.toList) $ S.fromList [1,0,1]
412−-- > "takeGE: Expecting at least 4 elements, got only 3"
414−-- >>> S.parse (PR.takeGE 4 FL.toList) $ S.fromList [1,0,1,0,1]
417-- /Internal/
376-- /Internal/
@@ -446,20 +405,7 @@ takeGE n (Fold fstep finitial fextract) = Parser step initial extract
446 then return x
405 then return x
447 else throwM $ ParseError err
406 else throwM $ ParseError err
449−-- | Collect stream elements until an element fails the predicate. The element
408+-- | See 'Streamly.Internal.Data.Parser.takeWhile'.
450−-- on which the predicate fails is returned back to the input stream.
452−-- * Stops - when the predicate fails.
455−-- >>> S.parse (PR.takeWhile (== 0) FL.toList) $ S.fromList [0,0,1,0,1]
458−-- We can implement a @breakOn@ using 'takeWhile':
461−-- breakOn p = takeWhile (not p)
464-- /Internal/
410-- /Internal/
@@ -477,7 +423,7 @@ takeWhile predicate (Fold fstep finitial fextract) =
477 then Yield 0 <$> fstep s a
423 then Yield 0 <$> fstep s a
478 else Stop 1 <$> fextract s
424 else Stop 1 <$> fextract s
480−-- | Like 'takeWhile' but takes at least one element otherwise fails.
426+-- | See 'Streamly.Internal.Data.Parser.takeWhile1'.
482-- /Internal/
428-- /Internal/
@@ -509,20 +455,7 @@ takeWhile1 predicate (Fold fstep finitial fextract) =
509 extract Nothing = throwM $ ParseError "takeWhile1: end of input"
455 extract Nothing = throwM $ ParseError "takeWhile1: end of input"
510 extract (Just s) = fextract s
456 extract (Just s) = fextract s
512−-- | Collect stream elements until an element succeeds the predicate. Drop the
458+-- | See 'Streamly.Internal.Data.Parser.sliceSepBy'.
513−-- element on which the predicate succeeded. The succeeding element is treated
514−-- as an infix separator which is dropped from the output.
516−-- * Stops - when the predicate succeeds.
519−-- >>> S.parse (PR.sliceSepBy (== 1) FL.toList) $ S.fromList [0,0,1,0,1]
522−-- S.splitOn pred f = S.splitParse (PR.sliceSepBy pred f)
524−-- >>> S.toList $ S.splitParse (PR.sliceSepBy (== 1) FL.toList) $ S.fromList [0,0,1,0,1]
527-- /Internal/
460-- /Internal/
@@ -539,14 +472,7 @@ sliceSepBy predicate (Fold fstep finitial fextract) =
539 then Yield 0 <$> fstep s a
472 then Yield 0 <$> fstep s a
540 else Stop 0 <$> fextract s
473 else Stop 0 <$> fextract s
542−-- | Collect stream elements until an element succeeds the predicate. Also take
475+-- | See 'Streamly.Internal.Data.Parser.sliceEndWith'.
543−-- the element on which the predicate succeeded. The succeeding element is
544−-- treated as a suffix separator which is kept in the output segement.
546−-- * Stops - when the predicate succeeds.
549−-- S.splitWithSuffix pred f = S.splitParse (PR.sliceEndWith pred f)
551-- /Unimplemented/
477-- /Unimplemented/
@@ -556,16 +482,7 @@ sliceEndWith ::
556 (a -> Bool) -> Fold m a b -> Parser m a b
482 (a -> Bool) -> Fold m a b -> Parser m a b
557sliceEndWith = undefined
483sliceEndWith = undefined
559−-- | Collect stream elements until an elements passes the predicate, return the
485+-- | See 'Streamly.Internal.Data.Parser.sliceBeginWith'.
560−-- last element on which the predicate succeeded back to the input stream. If
561−-- the predicate succeeds on the first element itself then it is kept in the
562−-- stream and we continue collecting. The succeeding element is treated as a
563−-- prefix separator which is kept in the output segement.
565−-- * Stops - when the predicate succeeds in non-leading position.
568−-- S.splitWithPrefix pred f = S.splitParse (PR.sliceBeginWith pred f)
570-- /Unimplemented/
487-- /Unimplemented/
@@ -575,9 +492,7 @@ sliceBeginWith ::
575 (a -> Bool) -> Fold m a b -> Parser m a b
492 (a -> Bool) -> Fold m a b -> Parser m a b
576sliceBeginWith = undefined
493sliceBeginWith = undefined
578−-- | Split using a condition or a count whichever occurs first. This is a
495+-- | See 'Streamly.Internal.Data.Parser.sliceSepByMax'.
579−-- hybrid of 'splitOn' and 'take'. The element on which the condition succeeds
582-- /Internal/
497-- /Internal/
@@ -601,17 +516,7 @@ sliceSepByMax predicate cnt (Fold fstep finitial fextract) =
601 return $ Stop 0 b
516 return $ Stop 0 b
602 extract (Tuple' _ r) = fextract r
517 extract (Tuple' _ r) = fextract r
604−-- | Like 'splitOn' but strips leading, trailing, and repeated separators.
519+-- | See 'Streamly.Internal.Data.Parser.wordBy'.
605−-- Therefore, @".a..b."@ having '.' as the separator would be parsed as
606−-- @["a","b"]@. In other words, its like parsing words from whitespace
609−-- * Stops - when it finds a word separator after a non-word element
613−-- S.wordsBy pred f = S.splitParse (PR.wordBy pred f)
616-- /Unimplemented/
521-- /Unimplemented/
@@ -621,19 +526,7 @@ wordBy ::
621 (a -> Bool) -> Fold m a b -> Parser m a b
526 (a -> Bool) -> Fold m a b -> Parser m a b
622wordBy = undefined
527wordBy = undefined
624−-- | @groupBy cmp f $ S.fromList [a,b,c,...]@ assigns the element @a@ to the
529+-- | See 'Streamly.Internal.Data.Parser.groupBy'.
625−-- first group, then if @a \`cmp` b@ is 'True' @b@ is also assigned to the same
626−-- group. If @a \`cmp` c@ is 'True' then @c@ is also assigned to the same
627−-- group and so on. When the comparison fails a new group is started. Each
628−-- group is folded using the 'Fold' @f@ and the result of the fold is emitted
629−-- in the output stream.
631−-- * Stops - when the comparison fails.
635−-- S.groupsBy cmp f = S.splitParse (PR.groupBy cmp f)
638-- /Unimplemented/
531-- /Unimplemented/
@@ -646,7 +539,7 @@ groupBy = undefined
646-- XXX use an Unfold instead of a list?
539-- XXX use an Unfold instead of a list?
647-- XXX custom combinators for matching list, array and stream?
540-- XXX custom combinators for matching list, array and stream?
649−-- | Match the given sequence of elements using the given comparison function.
542+-- | See 'Streamly.Internal.Data.Parser.eqBy'.
651-- /Internal/
544-- /Internal/
@@ -662,8 +555,7 @@ eqBy cmp str = Parser step initial extract
662 step [x] a = return $
555 step [x] a = return $
663 if x `cmp` a
556 if x `cmp` a
664 then Stop 0 ()
557 then Stop 0 ()
666− "eqBy: failed, at the last element"
558+ else Error "eqBy: failed, at the last element"
667 step (x:xs) a = return $
559 step (x:xs) a = return $
668 if x `cmp` a
560 if x `cmp` a
669 then Skip 0 xs
561 then Skip 0 xs
@@ -677,6 +569,10 @@ eqBy cmp str = Parser step initial extract
677-- nested parsers
569-- nested parsers
678-------------------------------------------------------------------------------
570-------------------------------------------------------------------------------
572+-- | See 'Streamly.Internal.Data.Parser.lookahead'.
680{-# INLINE lookAhead #-}
576{-# INLINE lookAhead #-}
681lookAhead :: MonadThrow m => Parser m a b -> Parser m a b
577lookAhead :: MonadThrow m => Parser m a b -> Parser m a b
682lookAhead (Parser step1 initial1 _) =
578lookAhead (Parser step1 initial1 _) =
@@ -705,27 +601,7 @@ lookAhead (Parser step1 initial1 _) =
705-- Interleaving
601-- Interleaving
706-------------------------------------------------------------------------------
602-------------------------------------------------------------------------------
708−-- To deinterleave we can chain two parsers one behind the other. The input is
604+-- | See 'Streamly.Internal.Data.Parser.deintercalate'.
709−-- given to the first parser and the input definitively rejected by the first
710−-- parser is given to the second parser.
712−-- We can either have the parsers themselves buffer the input or use the shared
713−-- global buffer to hold it until none of the parsers need it. When the first
714−-- parser returns Skip (i.e. rewind) we let the second parser consume the
715−-- rejected input and when it is done we move the cursor forward to the first
716−-- parser again. This will require a "move forward" command as well.
718−-- To implement grep we can use three parsers, one to find the pattern, one
719−-- to store the context behind the pattern and one to store the context in
720−-- front of the pattern. When a match occurs we need to emit the accumulator of
721−-- all the three parsers. One parser can count the line numbers to provide the
724−-- | Apply two parsers alternately to an input stream. The input stream is
725−-- considered an interleaving of two patterns. The two parsers represent the
728−-- This undoes a "gintercalate" of two streams.
730-- /Unimplemented/
606-- /Unimplemented/
@@ -741,8 +617,7 @@ deintercalate = undefined
741-- Sequential Collection
617-- Sequential Collection
742-------------------------------------------------------------------------------
618-------------------------------------------------------------------------------
744−-- | @sequence f t@ collects sequential parses of parsers in the container @t@
620+-- | See 'Streamly.Internal.Data.Parser.sequence'.
745−-- using the fold @f@. Fails if the input ends or any of the parsers fail.
747-- /Unimplemented/
622-- /Unimplemented/
@@ -756,8 +631,9 @@ sequence _f _p = undefined
756-- Alternative Collection
631-- Alternative Collection
757-------------------------------------------------------------------------------
632-------------------------------------------------------------------------------
759−-- | @choice parsers@ applies the @parsers@ in order and returns the first
634+-- | See 'Streamly.Internal.Data.Parser.choice'.
760−-- successful parse.
635+--
762{-# INLINE choice #-}
638{-# INLINE choice #-}
@@ -769,16 +645,7 @@ choice _ps = undefined
769-- Sequential Repetition
645-- Sequential Repetition
770-------------------------------------------------------------------------------
646-------------------------------------------------------------------------------
772−-- XXX "many" is essentially a Fold because it cannot fail. So it can be
648+-- | See 'Streamly.Internal.Data.Parser.many'.
773−-- downgraded to a Fold. Or we can make the return type a Fold instead and
774−-- upgrade that to a parser when needed.
776−-- | Collect zero or more parses. Apply the parser repeatedly on the input
777−-- stream, stop when the parser fails, accumulate zero or more parse results
778−-- using the supplied 'Fold'. This parser never fails, in case the first
779−-- application of parser fails it returns an empty result.
781−-- Compare with 'Control.Applicative.many'.
783-- /Internal/
650-- /Internal/
@@ -787,12 +654,7 @@ many :: MonadCatch m => Fold m b c -> Parser m a b -> Parser m a c
787many = splitMany
654many = splitMany
788-- many = countBetween 0 maxBound
655-- many = countBetween 0 maxBound
790−-- | Collect one or more parses. Apply the supplied parser repeatedly on the
657+-- | See 'Streamly.Internal.Data.Parser.some'.
791−-- input stream and accumulate the parse results as long as the parser
792−-- succeeds, stop when it fails. This parser fails if not even one result is
795−-- Compare with 'Control.Applicative.some'.
797-- /Internal/
659-- /Internal/
@@ -802,9 +664,7 @@ some = splitSome
802-- some f p = many (takeGE 1 f) p
664-- some f p = many (takeGE 1 f) p
803-- many = countBetween 1 maxBound
665-- many = countBetween 1 maxBound
805−-- | @countBetween m n f p@ collects between @m@ and @n@ sequential parses of
667+-- | See 'Streamly.Internal.Data.Parser.countBetween'.
806−-- parser @p@ using the fold @f@. Stop after collecting @n@ results. Fails if
807−-- the input ends or the parser fails before @m@ results are collected.
809-- /Unimplemented/
669-- /Unimplemented/
@@ -815,9 +675,7 @@ countBetween ::
815countBetween _m _n _f = undefined
675countBetween _m _n _f = undefined
816-- countBetween m n f p = many (takeBetween m n f) p
676-- countBetween m n f p = many (takeBetween m n f) p
818−-- | @count n f p@ collects exactly @n@ sequential parses of parser @p@ using
678+-- | See 'Streamly.Internal.Data.Parser.count'.
819−-- the fold @f@. Fails if the input ends or the parser fails before @n@
820−-- results are collected.
822-- /Unimplemented/
680-- /Unimplemented/
@@ -830,11 +688,7 @@ count n = countBetween n n
831data ManyTillState fs sr sl = ManyTillR Int fs sr | ManyTillL fs sl
689data ManyTillState fs sr sl = ManyTillR Int fs sr | ManyTillL fs sl
833−-- | @manyTill f collect test@ tries the parser @test@ on the input, if @test@
691+-- | See 'Streamly.Internal.Data.Parser.manyTill'.
834−-- fails it backtracks and tries @collect@, after @collect@ succeeds @test@ is
835−-- tried again and so on. The parser stops when @test@ succeeds. The output of
836−-- @test@ is discarded and the output of @collect@ is accumulated by the
837−-- supplied fold. The parser fails if @collect@ fails.
839-- /Internal/
693-- /Internal/