4-- Module : Streamly.Internal.Data.Parser.ParserD
5-- Copyright : (c) 2020 Composewell Technologies
6-- License : BSD-3-Clause
7-- Maintainer : streamly@composewell.com
8-- Stability : experimental
11-- Direct style parser implementation with stream fusion.
13module Streamly.Internal.Data.Parser.ParserD
21 -- First order parsers
38 -- Parsers chained in series, if one parser terminates the composition
39 -- terminates. Currently we are using folds to collect the output of the
40 -- parsers but we can use Parsers instead of folds to make the composition
41 -- more powerful. For example, we can do:
43 -- sliceSepByMax cond n p = sliceBy cond (take n p)
44 -- sliceSepByBetween cond m n p = sliceBy cond (takeBetween m n p)
45 -- takeWhileBetween cond m n p = takeWhile cond (takeBetween m n p)
47 -- Grab a sequence of input elements without inspecting them
49 -- , take -- take -- takeBetween 0 n
50 -- , takeLE1 -- take1 -- takeBetween 1 n
51 , takeEQ -- takeBetween n n
52 , takeGE -- takeBetween n maxBound
54 -- Grab a sequence of input elements by inspecting them
60 -- , sliceSepByBetween
64 -- , frameSepBy -- parse frames escaped by an escape char/sequence
71 -- , prefixOf -- match any prefix of a given string
72 -- , suffixOf -- match any suffix of a given string
73 -- , infixOf -- match any substring of a given string
80 -- Second order parsers (parsers using parsers)
81 -- * Binary Combinators
83 -- ** Sequential Applicative
87 -- ** Parallel Applicatives
91 -- , teeTill -- like manyTill but parallel
93 -- ** Sequential Interleaving
94 -- Use two folds, run a primary parser, its rejected values go to the
98 -- ** Sequential Alternative
101 -- ** Parallel Alternatives
106 -- * N-ary Combinators
107 -- ** Sequential Collection
111 -- ** Sequential Repetition
114 -- , countBetweenTill
120 -- -- ** Special cases
121 -- XXX traditional implmentations of these may be of limited use. For
122 -- example, consider parsing lines separated by "\r\n". The main parser
123 -- will have to detect and exclude the sequence "\r\n" anyway so that we
124 -- can apply the "sep" parser.
126 -- We can instead implement these as special cases of deintercalate.
137 -- A simple and stupid impl would be to just convert the stream to an array
138 -- and give the array reference to all consumers. The array can be grown on
139 -- demand by any consumer and truncated when nonbody needs it.
141 -- -- ** Distribute to collection
142 -- -- ** Distribute to repetition
144 -- -- ** Interleaved collection
147 -- -- ** Interleaved repetition
148 -- repeat one parser and when it fails run an error recovery parser
149 -- e.g. to find a key frame in the stream after an error
151 -- ** Collection of Alternatives
154 -- , fastestN -- first N successful in time
155 -- , choiceN -- first N successful in position
156 , choice -- first successful in position
158 -- -- ** Repeated Alternatives
159 -- , retryMax -- try N times
160 -- , retryUntil -- try until successful
161 -- , retryUntilN -- try until successful n times
165import Control.Exception (assert)
166import Control.Monad.Catch (MonadCatch, MonadThrow(..))
167import Fusion.Plugin.Types (Fuse(..))
168import Streamly.Internal.Data.Fold.Type (Fold(..))
169import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
171import qualified Streamly.Internal.Data.Fold.Type as FL
174 (any, all, take, takeWhile, sequence, concatMap, maybe, either, span)
175import Streamly.Internal.Data.Parser.ParserD.Tee
176import Streamly.Internal.Data.Parser.ParserD.Type
181-- >>> import Prelude hiding ()
182-- >>> import qualified Streamly.Prelude as Stream
183-- >>> import qualified Streamly.Internal.Data.Stream.IsStream as Stream
184-- >>> import qualified Streamly.Data.Fold as Fold
185-- >>> import qualified Streamly.Internal.Data.Parser as Parser
187-------------------------------------------------------------------------------
188-- Upgrade folds to parses
189-------------------------------------------------------------------------------
191-- | See 'Streamly.Internal.Data.Parser.fromFold'.
195{-# INLINE fromFold #-}
196fromFold :: Monad m => Fold m a b -> Parser m a b
197fromFold (Fold fstep finitial fextract) = Parser step initial fextract
205 FL.Partial s1 -> IPartial s1
212 FL.Partial s1 -> Partial 0 s1
213 FL.Done b -> Done 0 b
215-------------------------------------------------------------------------------
217-------------------------------------------------------------------------------
219-- | See 'Streamly.Internal.Data.Parser.peek'.
223{-# INLINABLE peek #-}
224peek :: MonadThrow m => Parser m a a
225peek = Parser step initial extract
229 initial = return $ IPartial ()
231 step () a = return $ Done 1 a
233 extract () = throwM $ ParseError "peek: end of input"
235-- | See 'Streamly.Internal.Data.Parser.eof'.
240eof :: Monad m => Parser m a ()
241eof = Parser step initial return
245 initial = return $ IPartial ()
247 step () _ = return $ Error "eof: not at end of input"
249-- | See 'Streamly.Internal.Data.Parser.satisfy'.
253{-# INLINE satisfy #-}
254satisfy :: MonadThrow m => (a -> Bool) -> Parser m a a
255satisfy predicate = Parser step initial extract
259 initial = return $ IPartial ()
264 else Error "satisfy: predicate failed"
266 extract _ = throwM $ ParseError "satisfy: end of input"
268-- | See 'Streamly.Internal.Data.Parser.maybe'.
273maybe :: MonadThrow m => (a -> Maybe b) -> Parser m a b
274maybe parser = Parser step initial extract
278 initial = return $ IPartial ()
283 Nothing -> Error "maybe: predicate failed"
285 extract _ = throwM $ ParseError "maybe: end of input"
287-- | See 'Streamly.Internal.Data.Parser.either'.
292either :: MonadThrow m => (a -> Either String b) -> Parser m a b
293either parser = Parser step initial extract
297 initial = return $ IPartial ()
302 Left err -> Error $ "either: " ++ err
304 extract _ = throwM $ ParseError "either: end of input"
306-------------------------------------------------------------------------------
308-------------------------------------------------------------------------------
310-- | See 'Streamly.Internal.Data.Parser.takeBetween'.
314{-# INLINE takeBetween #-}
315takeBetween :: MonadCatch m => Int -> Int -> Fold m a b -> Parser m a b
316takeBetween low high (Fold fstep finitial fextract) =
318 Parser step initial extract
325 FL.Partial s -> IPartial $ Tuple' 0 s
330 $ "takeBetween: the collecting fold terminated without"
331 ++ " consuming any elements"
332 ++ " minimum" ++ show low ++ " elements needed"
338 $ "takeBetween: lower bound - " ++ show low
339 ++ " is greater than higher bound - " ++ show high
340 | high <= 0 = Done 1 <$> fextract s
345 FL.Partial s1 -> Continue 0 $ Tuple' i1 s1
348 $ "takeBetween: the collecting fold terminated after"
349 ++ " consuming" ++ show i1 ++ " elements"
350 ++ " minimum" ++ show low ++ " elements needed"
356 then Done 0 <$> fextract s1
357 else return $ Partial 0 $ Tuple' i1 s1
358 FL.Done b -> return $ Done 0 b
365 | i >= low && i <= high = fextract s
366 | otherwise = throwM $ ParseError err
371 "takeBetween: Expecting alteast " ++ show low
372 ++ " elements, got " ++ show i
374-- | See 'Streamly.Internal.Data.Parser.takeEQ'.
379takeEQ :: MonadThrow m => Int -> Fold m a b -> Parser m a b
380takeEQ n (Fold fstep finitial fextract) = Parser step initial extract
389 FL.Partial s -> IPartial $ Tuple' 0 s
394 $ "takeEQ: Expecting exactly " ++ show cnt
395 ++ " elements, fold terminated without"
396 ++ " consuming any elements"
403 FL.Partial s -> Continue 0 $ Tuple' i1 s
406 $ "takeEQ: Expecting exactly " ++ show cnt
407 ++ " elements, fold terminated on " ++ show i1
412 FL.Partial s -> fextract s
413 FL.Done b -> return b
414 -- XXX we should not reach here when initial returns Step type
415 -- reachable only when n == 0
416 | otherwise = Done 1 <$> fextract r
423 | i == 0 && cnt == 0 = fextract r
427 $ "takeEQ: Expecting exactly " ++ show cnt
428 ++ " elements, input terminated on " ++ show i
430-- | See 'Streamly.Internal.Data.Parser.takeGE'.
435takeGE :: MonadThrow m => Int -> Fold m a b -> Parser m a b
436takeGE n (Fold fstep finitial fextract) = Parser step initial extract
444 FL.Partial s -> IPartial $ Tuple' 0 s
449 $ "takeGE: Expecting at least " ++ show cnt
450 ++ " elements, fold terminated without"
451 ++ " consuming any elements"
458 FL.Partial s -> Continue 0 $ Tuple' i1 s
461 $ "takeGE: Expecting at least " ++ show cnt
462 ++ " elements, fold terminated on " ++ show i1
467 FL.Partial s -> Partial 0 $ Tuple' i1 s
468 FL.Done b -> Done 0 b
475 | i >= cnt = fextract r
479 $ "takeGE: Expecting at least " ++ show cnt
480 ++ " elements, input terminated on " ++ show i
482-- | See 'Streamly.Internal.Data.Parser.takeWhile'.
486{-# INLINE takeWhile #-}
487takeWhile :: Monad m => (a -> Bool) -> Fold m a b -> Parser m a b
488takeWhile predicate (Fold fstep finitial fextract) =
489 Parser step initial fextract
496 FL.Partial s -> IPartial s
505 FL.Partial s1 -> Partial 0 s1
506 FL.Done b -> Done 0 b
507 else Done 1 <$> fextract s
509-- | See 'Streamly.Internal.Data.Parser.takeWhile1'.
513{-# INLINE takeWhile1 #-}
514takeWhile1 :: MonadThrow m => (a -> Bool) -> Fold m a b -> Parser m a b
515takeWhile1 predicate (Fold fstep finitial fextract) =
516 Parser step initial extract
523 FL.Partial s -> IPartial (Left s)
526 $ "takeWhile1: fold terminated without consuming:"
529 {-# INLINE process #-}
534 FL.Partial s1 -> Partial 0 (Right s1)
535 FL.Done b -> Done 0 b
540 else return $ Error "takeWhile1: predicate failed on first element"
548 extract (Left _) = throwM $ ParseError "takeWhile1: end of input"
549 extract (Right s) = fextract s
551-- | See 'Streamly.Internal.Data.Parser.sliceSepByP'.
555sliceSepByP :: MonadCatch m =>
556 (a -> Bool) -> Parser m a b -> Parser m a b
557sliceSepByP cond (Parser pstep pinitial pextract) =
559 Parser step initial pextract
572-- | See 'Streamly.Internal.Data.Parser.sliceBeginWith'.
576data SliceBeginWithState s = Left' s | Right' s
578{-# INLINE sliceBeginWith #-}
579sliceBeginWith :: Monad m => (a -> Bool) -> Fold m a b -> Parser m a b
580sliceBeginWith cond (Fold fstep finitial fextract) =
582 Parser step initial extract
590 FL.Partial s -> IPartial (Left' s)
591 FL.Done _ -> IError "sliceBeginWith : bad finitial"
593 {-# INLINE process #-}
598 FL.Partial s1 -> Partial 0 (Right' s1)
599 FL.Done b -> Done 0 b
604 else error $ "sliceBeginWith : slice begins with an element which "
605 ++ "fails the predicate"
609 else Done 1 <$> fextract s
611 extract (Left' s) = fextract s
612 extract (Right' s) = fextract s
614data WordByState s b = WBLeft !s | WBWord !s | WBRight !b
616-- | See 'Streamly.Internal.Data.Parser.wordBy'.
620wordBy :: Monad m => (a -> Bool) -> Fold m a b -> Parser m a b
621wordBy predicate (Fold fstep finitial fextract) = Parser step initial extract
625 {-# INLINE worder #-}
630 FL.Partial s1 -> Partial 0 $ WBWord s1
631 FL.Done b -> Done 0 b
637 FL.Partial s -> IPartial $ WBLeft s
643 else return $ Partial 0 $ WBLeft s
649 return $ Partial 0 $ WBRight b
652 $ if not (predicate a)
654 else Partial 0 $ WBRight b
656 extract (WBLeft s) = fextract s
657 extract (WBWord s) = fextract s
658 extract (WBRight b) = return b
660{-# ANN type GroupByState Fuse #-}
663 | GroupByGrouping !a !s
665-- | See 'Streamly.Internal.Data.Parser.groupBy'.
667{-# INLINE groupBy #-}
668groupBy :: Monad m => (a -> a -> Bool) -> Fold m a b -> Parser m a b
669groupBy eq (Fold fstep finitial fextract) = Parser step initial extract
673 {-# INLINE grouper #-}
678 FL.Done b -> Done 0 b
679 FL.Partial s1 -> Partial 0 (GroupByGrouping a0 s1)
685 FL.Partial s -> IPartial $ GroupByInit s
688 step (GroupByInit s) a = grouper s a a
689 step (GroupByGrouping a0 s) a =
692 else Done 1 <$> fextract s
694 extract (GroupByInit s) = fextract s
695 extract (GroupByGrouping _ s) = fextract s
697-- | See 'Streamly.Internal.Data.Parser.groupByRolling'.
699{-# INLINE groupByRolling #-}
700groupByRolling :: Monad m => (a -> a -> Bool) -> Fold m a b -> Parser m a b
701groupByRolling eq (Fold fstep finitial fextract) = Parser step initial extract
705 {-# INLINE grouper #-}
710 FL.Done b -> Done 0 b
711 FL.Partial s1 -> Partial 0 (GroupByGrouping a s1)
717 FL.Partial s -> IPartial $ GroupByInit s
720 step (GroupByInit s) a = grouper s a
721 step (GroupByGrouping a0 s) a =
724 else Done 1 <$> fextract s
726 extract (GroupByInit s) = fextract s
727 extract (GroupByGrouping _ s) = fextract s
729-- XXX use an Unfold instead of a list?
730-- XXX custom combinators for matching list, array and stream?
732-- | See 'Streamly.Internal.Data.Parser.eqBy'.
737eqBy :: MonadThrow m => (a -> a -> Bool) -> [a] -> Parser m a ()
738eqBy cmp str = Parser step initial extract
742 initial = return $ IPartial str
744 step [] _ = return $ Done 0 ()
749 else Error "eqBy: failed, yet to match the last element"
755 $ "eqBy: failed, yet to match "
756 ++ show (length xs + 1) ++ " elements"
761 $ "eqBy: end of input, yet to match "
762 ++ show (length xs) ++ " elements"
764--------------------------------------------------------------------------------
766--------------------------------------------------------------------------------
768-- | @span p f1 f2@ composes folds @f1@ and @f2@ such that @f1@ consumes the
769-- input as long as the predicate @p@ is 'True'. @f2@ consumes the rest of the
773-- > let span_ p xs = Stream.parse (Parser.span p Fold.toList Fold.toList) $ Stream.fromList xs
775-- > span_ (< 1) [1,2,3]
778-- > span_ (< 2) [1,2,3]
781-- > span_ (< 4) [1,2,3]
788span :: Monad m => (a -> Bool) -> Fold m a b -> Fold m a c -> Parser m a (b, c)
789span p f1 f2 = noErrorUnsafeSplitWith (,) (takeWhile p f1) (fromFold f2)
791-- | Break the input stream into two groups, the first group takes the input as
792-- long as the predicate applied to the first element of the stream and next
793-- input element holds 'True', the second group takes the rest of the input.
800 => (a -> a -> Bool) -> Fold m a b -> Fold m a c -> Parser m a (b, c)
801spanBy eq f1 f2 = noErrorUnsafeSplitWith (,) (groupBy eq f1) (fromFold f2)
803-- | Like 'spanBy' but applies the predicate in a rolling fashion i.e.
804-- predicate is applied to the previous and the next input elements.
807{-# INLINE spanByRolling #-}
810 => (a -> a -> Bool) -> Fold m a b -> Fold m a c -> Parser m a (b, c)
811spanByRolling eq f1 f2 =
812 noErrorUnsafeSplitWith (,) (groupByRolling eq f1) (fromFold f2)
814-------------------------------------------------------------------------------
816-------------------------------------------------------------------------------
818-- | See 'Streamly.Internal.Data.Parser.takeP'.
822takeP :: Monad m => Int -> Parser m a b -> Parser m a b
823takeP lim (Parser pstep pinitial pextract) = Parser step initial extract
832 then return $ IPartial $ Tuple' 0 s
833 else IDone <$> pextract s
834 IDone b -> return $ IDone b
835 IError e -> return $ IError e
837 step (Tuple' cnt r) a = do
838 assert (cnt < lim) (return ())
843 assert (cnt1 >= 0) (return ())
845 then return $ Partial 0 $ Tuple' cnt1 s
846 else Done 0 <$> pextract s
848 assert (cnt1 >= 0) (return ())
850 then return $ Continue 0 $ Tuple' cnt1 s
851 -- XXX This should error out?
852 -- If designed properly, this will probably error out.
853 -- "pextract" should error out
857 -- This is a tricky case, we have the following options:
858 -- 1. Done 0 with extract as you have written
859 -- 2. Done n, will require buffering elements
860 -- 3. Use a backtracking fold and not a parser, once we have
861 -- backtracking in folds
862 else Done 0 <$> pextract s
865 assert (taken >= 0) (return ())
866 return $ Partial n $ Tuple' taken s
869 assert (taken >= 0) (return ())
870 return $ Continue n $ Tuple' taken s
871 Done n b -> return $ Done n b
872 Error str -> return $ Error str
874 extract (Tuple' _ r) = pextract r
876-- | See 'Streamly.Internal.Data.Parser.lookahead'.
880{-# INLINE lookAhead #-}
881lookAhead :: MonadThrow m => Parser m a b -> Parser m a b
882lookAhead (Parser step1 initial1 _) = Parser step initial extract
889 IPartial s -> IPartial (Tuple' 0 s)
893 step (Tuple' cnt st) a = do
898 Partial n s -> Continue n (Tuple' (cnt1 - n) s)
899 Continue n s -> Continue n (Tuple' (cnt1 - n) s)
900 Done _ b -> Done cnt1 b
901 Error err -> Error err
903 -- XXX returning an error let's us backtrack. To implement it in a way so
904 -- that it terminates on eof without an error then we need a way to
905 -- backtrack on eof, that will require extract to return 'Step' type.
906 extract (Tuple' n _) =
909 $ "lookAhead: end of input after consuming "
910 ++ show n ++ " elements"
912-------------------------------------------------------------------------------
914-------------------------------------------------------------------------------
916-- | See 'Streamly.Internal.Data.Parser.deintercalate'.
920{-# INLINE deintercalate #-}
923 Fold m a y -> Parser m x a
924 -> Fold m b z -> Parser m x b
926deintercalate = undefined
928-------------------------------------------------------------------------------
929-- Sequential Collection
930-------------------------------------------------------------------------------
932-- | See 'Streamly.Internal.Data.Parser.sequence'.
936{-# INLINE sequence #-}
939 Fold m b c -> t (Parser m a b) -> Parser m a c
940sequence _f _p = undefined
942-------------------------------------------------------------------------------
943-- Alternative Collection
944-------------------------------------------------------------------------------
946-- | See 'Streamly.Internal.Data.Parser.choice'.
951choice :: (MonadCatch m, Foldable t) => t (Parser m a b) -> Parser m a b
952choice = foldl1 shortest
954-------------------------------------------------------------------------------
955-- Sequential Repetition
956-------------------------------------------------------------------------------
958-- | See 'Streamly.Internal.Data.Parser.many'.
963many :: MonadCatch m => Parser m a b -> Fold m b c -> Parser m a c
965-- many = countBetween 0 maxBound
967-- | See 'Streamly.Internal.Data.Parser.some'.
972some :: MonadCatch m => Parser m a b -> Fold m b c -> Parser m a c
974-- some f p = many (takeGE 1 f) p
975-- many = countBetween 1 maxBound
977-- | See 'Streamly.Internal.Data.Parser.countBetween'.
981{-# INLINE countBetween #-}
984 Int -> Int -> Parser m a b -> Fold m b c -> Parser m a c
985countBetween _m _n _p = undefined
986-- countBetween m n p f = many (takeBetween m n f) p
988-- | See 'Streamly.Internal.Data.Parser.count'.
995 Int -> Parser m a b -> Fold m b c -> Parser m a c
996count n = countBetween n n
997-- count n f p = many (takeEQ n f) p
999data ManyTillState fs sr sl
1000 = ManyTillR Int fs sr
1001 | ManyTillL Int fs sl
1003-- | See 'Streamly.Internal.Data.Parser.manyTill'.
1007{-# INLINE manyTill #-}
1008manyTill :: MonadCatch m
1009 => Fold m b c -> Parser m a b -> Parser m a x -> Parser m a c
1010manyTill (Fold fstep finitial fextract)
1011 (Parser stepL initialL extractL)
1012 (Parser stepR initialR _) =
1013 Parser step initial extract
1017 -- Caution: Mutual recursion
1019 -- Don't inline this
1020 scrutL fs p c d e = do
1023 IPartial sl -> return $ c (ManyTillL 0 fs sl)
1027 FL.Partial fs1 -> scrutR fs1 p c d e
1028 FL.Done fb -> return $ d fb
1029 IError err -> return $ e err
1031 {-# INLINE scrutR #-}
1032 scrutR fs p c d e = do
1035 IPartial sr -> return $ p (ManyTillR 0 fs sr)
1036 IDone _ -> d <$> fextract fs
1037 IError _ -> scrutL fs p c d e
1042 FL.Partial fs -> scrutR fs IPartial IPartial IDone IError
1043 FL.Done b -> return $ IDone b
1045 step (ManyTillR cnt fs st) a = do
1048 Partial n s -> return $ Partial n (ManyTillR 0 fs s)
1050 assert (cnt + 1 - n >= 0) (return ())
1051 return $ Continue n (ManyTillR (cnt + 1 - n) fs s)
1059 return $ Continue (cnt + 1) (ManyTillL 0 fs sl)
1067 FL.Partial fs1 -> scrutR fs1 p c d Error
1068 FL.Done fb -> return $ Done cnt1 fb
1069 IError err -> return $ Error err
1070 -- XXX the cnt is being used only by the assert
1071 step (ManyTillL cnt fs st) a = do
1074 Partial n s -> return $ Partial n (ManyTillL 0 fs s)
1076 assert (cnt + 1 - n >= 0) (return ())
1077 return $ Continue n (ManyTillL (cnt + 1 - n) fs s)
1082 scrutR s (Partial n) (Continue n) (Done n) Error
1083 FL.Done b1 -> return $ Done n b1
1084 Error err -> return $ Error err
1086 extract (ManyTillL _ fs sR) = do
1087 res <- extractL sR >>= fstep fs
1089 FL.Partial s -> fextract s
1090 FL.Done b -> return b
1091 extract (ManyTillR _ fs _) = fextract fs
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list