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 -- * Downgrade to Fold
24 -- First order parsers
41 -- Parsers chained in series, if one parser terminates the composition
42 -- terminates. Currently we are using folds to collect the output of the
43 -- parsers but we can use Parsers instead of folds to make the composition
44 -- more powerful. For example, we can do:
46 -- sliceSepByMax cond n p = sliceBy cond (take n p)
47 -- sliceSepByBetween cond m n p = sliceBy cond (takeBetween m n p)
48 -- takeWhileBetween cond m n p = takeWhile cond (takeBetween m n p)
50 -- Grab a sequence of input elements without inspecting them
52 -- , take -- take -- takeBetween 0 n
53 -- , takeLE1 -- take1 -- takeBetween 1 n
54 , takeEQ -- takeBetween n n
55 , takeGE -- takeBetween n maxBound
57 -- Grab a sequence of input elements by inspecting them
63 -- , sliceSepByBetween
67 -- , frameSepBy -- parse frames escaped by an escape char/sequence
74 -- , prefixOf -- match any prefix of a given string
75 -- , suffixOf -- match any suffix of a given string
76 -- , infixOf -- match any substring of a given string
83 -- Second order parsers (parsers using parsers)
84 -- * Binary Combinators
86 -- ** Sequential Applicative
90 -- ** Parallel Applicatives
94 -- , teeTill -- like manyTill but parallel
96 -- ** Sequential Interleaving
97 -- Use two folds, run a primary parser, its rejected values go to the
101 -- ** Sequential Alternative
104 -- ** Parallel Alternatives
109 -- * N-ary Combinators
110 -- ** Sequential Collection
114 -- ** Sequential Repetition
117 -- , countBetweenTill
123 -- -- ** Special cases
124 -- XXX traditional implmentations of these may be of limited use. For
125 -- example, consider parsing lines separated by "\r\n". The main parser
126 -- will have to detect and exclude the sequence "\r\n" anyway so that we
127 -- can apply the "sep" parser.
129 -- We can instead implement these as special cases of deintercalate.
140 -- A simple and stupid impl would be to just convert the stream to an array
141 -- and give the array reference to all consumers. The array can be grown on
142 -- demand by any consumer and truncated when nonbody needs it.
144 -- -- ** Distribute to collection
145 -- -- ** Distribute to repetition
147 -- -- ** Interleaved collection
150 -- -- ** Interleaved repetition
151 -- repeat one parser and when it fails run an error recovery parser
152 -- e.g. to find a key frame in the stream after an error
154 -- ** Collection of Alternatives
157 -- , fastestN -- first N successful in time
158 -- , choiceN -- first N successful in position
159 , choice -- first successful in position
161 -- -- ** Repeated Alternatives
162 -- , retryMax -- try N times
163 -- , retryUntil -- try until successful
164 -- , retryUntilN -- try until successful n times
168import Control.Exception (assert, Exception)
169import Control.Monad.Catch (MonadCatch, MonadThrow(..))
170import Fusion.Plugin.Types (Fuse(..))
171import Streamly.Internal.Data.Fold.Type (Fold(..))
172import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
174import qualified Streamly.Internal.Data.Fold.Type as FL
177 (any, all, take, takeWhile, sequence, concatMap, maybe, either, span)
178import Streamly.Internal.Data.Parser.ParserD.Tee
179import Streamly.Internal.Data.Parser.ParserD.Type
184-- >>> import Prelude hiding ()
185-- >>> import qualified Streamly.Prelude as Stream
186-- >>> import qualified Streamly.Internal.Data.Stream.IsStream as Stream
187-- >>> import qualified Streamly.Data.Fold as Fold
188-- >>> import qualified Streamly.Internal.Data.Parser as Parser
190-------------------------------------------------------------------------------
191-- Downgrade a parser to a Fold
192-------------------------------------------------------------------------------
194data ParserToFoldError =
202instance Exception ParserToFoldError
204-- | See 'Streamly.Internal.Data.Parser.toFold'.
209toFold :: MonadThrow m => Parser m a b -> Fold m a b
210toFold (Parser pstep pinitial pextract) = Fold step initial pextract
217 IPartial s -> return $ FL.Partial s
218 IDone b -> return $ FL.Done b
219 IError err -> throwM $ InitialError err
224 Partial 0 s -> return $ FL.Partial s
225 Continue 0 s -> return $ FL.Partial s
226 Done 0 b -> return $ FL.Done b
227 Partial n _ -> throwM $ PartialError n
228 Continue n _ -> throwM $ ContinueError n
229 Done n _ -> throwM $ DoneError n
230 Error err -> throwM $ ErrorError err
232-------------------------------------------------------------------------------
233-- Upgrade folds to parses
234-------------------------------------------------------------------------------
236-- | See 'Streamly.Internal.Data.Parser.fromFold'.
240{-# INLINE fromFold #-}
241fromFold :: Monad m => Fold m a b -> Parser m a b
242fromFold (Fold fstep finitial fextract) = Parser step initial fextract
250 FL.Partial s1 -> IPartial s1
257 FL.Partial s1 -> Partial 0 s1
258 FL.Done b -> Done 0 b
260-------------------------------------------------------------------------------
262-------------------------------------------------------------------------------
264-- | See 'Streamly.Internal.Data.Parser.peek'.
268{-# INLINABLE peek #-}
269peek :: MonadThrow m => Parser m a a
270peek = Parser step initial extract
274 initial = return $ IPartial ()
276 step () a = return $ Done 1 a
278 extract () = throwM $ ParseError "peek: end of input"
280-- | See 'Streamly.Internal.Data.Parser.eof'.
285eof :: Monad m => Parser m a ()
286eof = Parser step initial return
290 initial = return $ IPartial ()
292 step () _ = return $ Error "eof: not at end of input"
294-- | See 'Streamly.Internal.Data.Parser.satisfy'.
298{-# INLINE satisfy #-}
299satisfy :: MonadThrow m => (a -> Bool) -> Parser m a a
300satisfy predicate = Parser step initial extract
304 initial = return $ IPartial ()
309 else Error "satisfy: predicate failed"
311 extract _ = throwM $ ParseError "satisfy: end of input"
313-- | See 'Streamly.Internal.Data.Parser.maybe'.
318maybe :: MonadThrow m => (a -> Maybe b) -> Parser m a b
319maybe parser = Parser step initial extract
323 initial = return $ IPartial ()
328 Nothing -> Error "maybe: predicate failed"
330 extract _ = throwM $ ParseError "maybe: end of input"
332-- | See 'Streamly.Internal.Data.Parser.either'.
337either :: MonadThrow m => (a -> Either String b) -> Parser m a b
338either parser = Parser step initial extract
342 initial = return $ IPartial ()
347 Left err -> Error $ "either: " ++ err
349 extract _ = throwM $ ParseError "either: end of input"
351-------------------------------------------------------------------------------
353-------------------------------------------------------------------------------
355-- | See 'Streamly.Internal.Data.Parser.takeBetween'.
359{-# INLINE takeBetween #-}
360takeBetween :: MonadCatch m => Int -> Int -> Fold m a b -> Parser m a b
361takeBetween low high (Fold fstep finitial fextract) =
363 Parser step initial extract
370 FL.Partial s -> IPartial $ Tuple' 0 s
375 $ "takeBetween: the collecting fold terminated without"
376 ++ " consuming any elements"
377 ++ " minimum" ++ show low ++ " elements needed"
383 $ "takeBetween: lower bound - " ++ show low
384 ++ " is greater than higher bound - " ++ show high
385 | high <= 0 = Done 1 <$> fextract s
390 FL.Partial s1 -> Continue 0 $ Tuple' i1 s1
393 $ "takeBetween: the collecting fold terminated after"
394 ++ " consuming" ++ show i1 ++ " elements"
395 ++ " minimum" ++ show low ++ " elements needed"
401 then Done 0 <$> fextract s1
402 else return $ Partial 0 $ Tuple' i1 s1
403 FL.Done b -> return $ Done 0 b
410 | i >= low && i <= high = fextract s
411 | otherwise = throwM $ ParseError err
416 "takeBetween: Expecting alteast " ++ show low
417 ++ " elements, got " ++ show i
419-- | See 'Streamly.Internal.Data.Parser.takeEQ'.
424takeEQ :: MonadThrow m => Int -> Fold m a b -> Parser m a b
425takeEQ n (Fold fstep finitial fextract) = Parser step initial extract
434 FL.Partial s -> IPartial $ Tuple' 0 s
439 $ "takeEQ: Expecting exactly " ++ show cnt
440 ++ " elements, fold terminated without"
441 ++ " consuming any elements"
448 FL.Partial s -> Continue 0 $ Tuple' i1 s
451 $ "takeEQ: Expecting exactly " ++ show cnt
452 ++ " elements, fold terminated on " ++ show i1
457 FL.Partial s -> fextract s
458 FL.Done b -> return b
459 -- XXX we should not reach here when initial returns Step type
460 -- reachable only when n == 0
461 | otherwise = Done 1 <$> fextract r
468 | i == 0 && cnt == 0 = fextract r
472 $ "takeEQ: Expecting exactly " ++ show cnt
473 ++ " elements, input terminated on " ++ show i
475-- | See 'Streamly.Internal.Data.Parser.takeGE'.
480takeGE :: MonadThrow m => Int -> Fold m a b -> Parser m a b
481takeGE n (Fold fstep finitial fextract) = Parser step initial extract
489 FL.Partial s -> IPartial $ Tuple' 0 s
494 $ "takeGE: Expecting at least " ++ show cnt
495 ++ " elements, fold terminated without"
496 ++ " consuming any elements"
503 FL.Partial s -> Continue 0 $ Tuple' i1 s
506 $ "takeGE: Expecting at least " ++ show cnt
507 ++ " elements, fold terminated on " ++ show i1
512 FL.Partial s -> Partial 0 $ Tuple' i1 s
513 FL.Done b -> Done 0 b
520 | i >= cnt = fextract r
524 $ "takeGE: Expecting at least " ++ show cnt
525 ++ " elements, input terminated on " ++ show i
527-- | See 'Streamly.Internal.Data.Parser.takeWhile'.
531{-# INLINE takeWhile #-}
532takeWhile :: Monad m => (a -> Bool) -> Fold m a b -> Parser m a b
533takeWhile predicate (Fold fstep finitial fextract) =
534 Parser step initial fextract
541 FL.Partial s -> IPartial s
550 FL.Partial s1 -> Partial 0 s1
551 FL.Done b -> Done 0 b
552 else Done 1 <$> fextract s
554-- | See 'Streamly.Internal.Data.Parser.takeWhile1'.
558{-# INLINE takeWhile1 #-}
559takeWhile1 :: MonadThrow m => (a -> Bool) -> Fold m a b -> Parser m a b
560takeWhile1 predicate (Fold fstep finitial fextract) =
561 Parser step initial extract
568 FL.Partial s -> IPartial (Left s)
571 $ "takeWhile1: fold terminated without consuming:"
574 {-# INLINE process #-}
579 FL.Partial s1 -> Partial 0 (Right s1)
580 FL.Done b -> Done 0 b
585 else return $ Error "takeWhile1: predicate failed on first element"
593 extract (Left _) = throwM $ ParseError "takeWhile1: end of input"
594 extract (Right s) = fextract s
596-- | See 'Streamly.Internal.Data.Parser.sliceSepByP'.
600sliceSepByP :: MonadCatch m =>
601 (a -> Bool) -> Parser m a b -> Parser m a b
602sliceSepByP cond (Parser pstep pinitial pextract) =
604 Parser step initial pextract
617-- | See 'Streamly.Internal.Data.Parser.sliceBeginWith'.
621data SliceBeginWithState s = Left' s | Right' s
623{-# INLINE sliceBeginWith #-}
624sliceBeginWith :: Monad m => (a -> Bool) -> Fold m a b -> Parser m a b
625sliceBeginWith cond (Fold fstep finitial fextract) =
627 Parser step initial extract
635 FL.Partial s -> IPartial (Left' s)
636 FL.Done _ -> IError "sliceBeginWith : bad finitial"
638 {-# INLINE process #-}
643 FL.Partial s1 -> Partial 0 (Right' s1)
644 FL.Done b -> Done 0 b
649 else error $ "sliceBeginWith : slice begins with an element which "
650 ++ "fails the predicate"
654 else Done 1 <$> fextract s
656 extract (Left' s) = fextract s
657 extract (Right' s) = fextract s
659data WordByState s b = WBLeft !s | WBWord !s | WBRight !b
661-- | See 'Streamly.Internal.Data.Parser.wordBy'.
665wordBy :: Monad m => (a -> Bool) -> Fold m a b -> Parser m a b
666wordBy predicate (Fold fstep finitial fextract) = Parser step initial extract
670 {-# INLINE worder #-}
675 FL.Partial s1 -> Partial 0 $ WBWord s1
676 FL.Done b -> Done 0 b
682 FL.Partial s -> IPartial $ WBLeft s
688 else return $ Partial 0 $ WBLeft s
694 return $ Partial 0 $ WBRight b
697 $ if not (predicate a)
699 else Partial 0 $ WBRight b
701 extract (WBLeft s) = fextract s
702 extract (WBWord s) = fextract s
703 extract (WBRight b) = return b
705{-# ANN type GroupByState Fuse #-}
708 | GroupByGrouping !a !s
710-- | See 'Streamly.Internal.Data.Parser.groupBy'.
712{-# INLINE groupBy #-}
713groupBy :: Monad m => (a -> a -> Bool) -> Fold m a b -> Parser m a b
714groupBy eq (Fold fstep finitial fextract) = Parser step initial extract
718 {-# INLINE grouper #-}
723 FL.Done b -> Done 0 b
724 FL.Partial s1 -> Partial 0 (GroupByGrouping a0 s1)
730 FL.Partial s -> IPartial $ GroupByInit s
733 step (GroupByInit s) a = grouper s a a
734 step (GroupByGrouping a0 s) a =
737 else Done 1 <$> fextract s
739 extract (GroupByInit s) = fextract s
740 extract (GroupByGrouping _ s) = fextract s
742-- | See 'Streamly.Internal.Data.Parser.groupByRolling'.
744{-# INLINE groupByRolling #-}
745groupByRolling :: Monad m => (a -> a -> Bool) -> Fold m a b -> Parser m a b
746groupByRolling eq (Fold fstep finitial fextract) = Parser step initial extract
750 {-# INLINE grouper #-}
755 FL.Done b -> Done 0 b
756 FL.Partial s1 -> Partial 0 (GroupByGrouping a s1)
762 FL.Partial s -> IPartial $ GroupByInit s
765 step (GroupByInit s) a = grouper s a
766 step (GroupByGrouping a0 s) a =
769 else Done 1 <$> fextract s
771 extract (GroupByInit s) = fextract s
772 extract (GroupByGrouping _ s) = fextract s
774-- XXX use an Unfold instead of a list?
775-- XXX custom combinators for matching list, array and stream?
777-- | See 'Streamly.Internal.Data.Parser.eqBy'.
782eqBy :: MonadThrow m => (a -> a -> Bool) -> [a] -> Parser m a ()
783eqBy cmp str = Parser step initial extract
787 initial = return $ IPartial str
789 step [] _ = return $ Done 0 ()
794 else Error "eqBy: failed, yet to match the last element"
800 $ "eqBy: failed, yet to match "
801 ++ show (length xs + 1) ++ " elements"
806 $ "eqBy: end of input, yet to match "
807 ++ show (length xs) ++ " elements"
809--------------------------------------------------------------------------------
811--------------------------------------------------------------------------------
813-- | @span p f1 f2@ composes folds @f1@ and @f2@ such that @f1@ consumes the
814-- input as long as the predicate @p@ is 'True'. @f2@ consumes the rest of the
818-- > let span_ p xs = Stream.parse (Parser.span p Fold.toList Fold.toList) $ Stream.fromList xs
820-- > span_ (< 1) [1,2,3]
823-- > span_ (< 2) [1,2,3]
826-- > span_ (< 4) [1,2,3]
833span :: Monad m => (a -> Bool) -> Fold m a b -> Fold m a c -> Parser m a (b, c)
834span p f1 f2 = noErrorUnsafeSplitWith (,) (takeWhile p f1) (fromFold f2)
836-- | Break the input stream into two groups, the first group takes the input as
837-- long as the predicate applied to the first element of the stream and next
838-- input element holds 'True', the second group takes the rest of the input.
845 => (a -> a -> Bool) -> Fold m a b -> Fold m a c -> Parser m a (b, c)
846spanBy eq f1 f2 = noErrorUnsafeSplitWith (,) (groupBy eq f1) (fromFold f2)
848-- | Like 'spanBy' but applies the predicate in a rolling fashion i.e.
849-- predicate is applied to the previous and the next input elements.
852{-# INLINE spanByRolling #-}
855 => (a -> a -> Bool) -> Fold m a b -> Fold m a c -> Parser m a (b, c)
856spanByRolling eq f1 f2 =
857 noErrorUnsafeSplitWith (,) (groupByRolling eq f1) (fromFold f2)
859-------------------------------------------------------------------------------
861-------------------------------------------------------------------------------
863-- | See 'Streamly.Internal.Data.Parser.takeP'.
867takeP :: Monad m => Int -> Parser m a b -> Parser m a b
868takeP lim (Parser pstep pinitial pextract) = Parser step initial extract
877 then return $ IPartial $ Tuple' 0 s
878 else IDone <$> pextract s
879 IDone b -> return $ IDone b
880 IError e -> return $ IError e
882 step (Tuple' cnt r) a = do
883 assert (cnt < lim) (return ())
888 assert (cnt1 >= 0) (return ())
890 then return $ Partial 0 $ Tuple' cnt1 s
891 else Done 0 <$> pextract s
893 assert (cnt1 >= 0) (return ())
895 then return $ Continue 0 $ Tuple' cnt1 s
896 -- XXX This should error out?
897 -- If designed properly, this will probably error out.
898 -- "pextract" should error out
902 -- This is a tricky case, we have the following options:
903 -- 1. Done 0 with extract as you have written
904 -- 2. Done n, will require buffering elements
905 -- 3. Use a backtracking fold and not a parser, once we have
906 -- backtracking in folds
907 else Done 0 <$> pextract s
910 assert (taken >= 0) (return ())
911 return $ Partial n $ Tuple' taken s
914 assert (taken >= 0) (return ())
915 return $ Continue n $ Tuple' taken s
916 Done n b -> return $ Done n b
917 Error str -> return $ Error str
919 extract (Tuple' _ r) = pextract r
921-- | See 'Streamly.Internal.Data.Parser.lookahead'.
925{-# INLINE lookAhead #-}
926lookAhead :: MonadThrow m => Parser m a b -> Parser m a b
927lookAhead (Parser step1 initial1 _) = Parser step initial extract
934 IPartial s -> IPartial (Tuple' 0 s)
938 step (Tuple' cnt st) a = do
943 Partial n s -> Continue n (Tuple' (cnt1 - n) s)
944 Continue n s -> Continue n (Tuple' (cnt1 - n) s)
945 Done _ b -> Done cnt1 b
946 Error err -> Error err
948 -- XXX returning an error let's us backtrack. To implement it in a way so
949 -- that it terminates on eof without an error then we need a way to
950 -- backtrack on eof, that will require extract to return 'Step' type.
951 extract (Tuple' n _) =
954 $ "lookAhead: end of input after consuming "
955 ++ show n ++ " elements"
957-------------------------------------------------------------------------------
959-------------------------------------------------------------------------------
961-- | See 'Streamly.Internal.Data.Parser.deintercalate'.
965{-# INLINE deintercalate #-}
968 Fold m a y -> Parser m x a
969 -> Fold m b z -> Parser m x b
971deintercalate = undefined
973-------------------------------------------------------------------------------
974-- Sequential Collection
975-------------------------------------------------------------------------------
977-- | See 'Streamly.Internal.Data.Parser.sequence'.
981{-# INLINE sequence #-}
984 Fold m b c -> t (Parser m a b) -> Parser m a c
985sequence _f _p = undefined
987-------------------------------------------------------------------------------
988-- Alternative Collection
989-------------------------------------------------------------------------------
991-- | See 'Streamly.Internal.Data.Parser.choice'.
996choice :: (MonadCatch m, Foldable t) => t (Parser m a b) -> Parser m a b
997choice = foldl1 shortest
999-------------------------------------------------------------------------------
1000-- Sequential Repetition
1001-------------------------------------------------------------------------------
1003-- | See 'Streamly.Internal.Data.Parser.many'.
1008many :: MonadCatch m => Parser m a b -> Fold m b c -> Parser m a c
1010-- many = countBetween 0 maxBound
1012-- | See 'Streamly.Internal.Data.Parser.some'.
1017some :: MonadCatch m => Parser m a b -> Fold m b c -> Parser m a c
1019-- some f p = many (takeGE 1 f) p
1020-- many = countBetween 1 maxBound
1022-- | See 'Streamly.Internal.Data.Parser.countBetween'.
1026{-# INLINE countBetween #-}
1029 Int -> Int -> Parser m a b -> Fold m b c -> Parser m a c
1030countBetween _m _n _p = undefined
1031-- countBetween m n p f = many (takeBetween m n f) p
1033-- | See 'Streamly.Internal.Data.Parser.count'.
1040 Int -> Parser m a b -> Fold m b c -> Parser m a c
1041count n = countBetween n n
1042-- count n f p = many (takeEQ n f) p
1044data ManyTillState fs sr sl
1045 = ManyTillR Int fs sr
1046 | ManyTillL Int fs sl
1048-- | See 'Streamly.Internal.Data.Parser.manyTill'.
1052{-# INLINE manyTill #-}
1053manyTill :: MonadCatch m
1054 => Fold m b c -> Parser m a b -> Parser m a x -> Parser m a c
1055manyTill (Fold fstep finitial fextract)
1056 (Parser stepL initialL extractL)
1057 (Parser stepR initialR _) =
1058 Parser step initial extract
1062 -- Caution: Mutual recursion
1064 -- Don't inline this
1065 scrutL fs p c d e = do
1068 IPartial sl -> return $ c (ManyTillL 0 fs sl)
1072 FL.Partial fs1 -> scrutR fs1 p c d e
1073 FL.Done fb -> return $ d fb
1074 IError err -> return $ e err
1076 {-# INLINE scrutR #-}
1077 scrutR fs p c d e = do
1080 IPartial sr -> return $ p (ManyTillR 0 fs sr)
1081 IDone _ -> d <$> fextract fs
1082 IError _ -> scrutL fs p c d e
1087 FL.Partial fs -> scrutR fs IPartial IPartial IDone IError
1088 FL.Done b -> return $ IDone b
1090 step (ManyTillR cnt fs st) a = do
1093 Partial n s -> return $ Partial n (ManyTillR 0 fs s)
1095 assert (cnt + 1 - n >= 0) (return ())
1096 return $ Continue n (ManyTillR (cnt + 1 - n) fs s)
1104 return $ Continue (cnt + 1) (ManyTillL 0 fs sl)
1112 FL.Partial fs1 -> scrutR fs1 p c d Error
1113 FL.Done fb -> return $ Done cnt1 fb
1114 IError err -> return $ Error err
1115 -- XXX the cnt is being used only by the assert
1116 step (ManyTillL cnt fs st) a = do
1119 Partial n s -> return $ Partial n (ManyTillL 0 fs s)
1121 assert (cnt + 1 - n >= 0) (return ())
1122 return $ Continue n (ManyTillL (cnt + 1 - n) fs s)
1127 scrutR s (Partial n) (Continue n) (Done n) Error
1128 FL.Done b1 -> return $ Done n b1
1129 Error err -> return $ Error err
1131 extract (ManyTillL _ fs sR) = do
1132 res <- extractL sR >>= fstep fs
1134 FL.Partial s -> fextract s
1135 FL.Done b -> return b
1136 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