on master help sign in
Add wordFramedBy parser
2e32c31Harendra Kumar
core/src/Streamly/Internal/Data/Parser.hs+36−2
@@ -128,8 +128,7 @@ module Streamly.Internal.Data.Parser
128 , takeFramedBy_
128 , takeFramedBy_
129 , takeFramedByEsc_
129 , takeFramedByEsc_
130 , takeFramedByGeneric
130 , takeFramedByGeneric
131− -- , wordByQuoted
131+ , wordFramedBy
132− -- , wordByQuotedEsc
133
132
134 -- | Unimplemented
133 -- | Unimplemented
135 --
134 --
@@ -252,6 +251,7 @@ import qualified Streamly.Internal.Data.Parser.ParserK.Type as K
252-- >>> :set -package streamly
251-- >>> :set -package streamly
253-- >>> import Prelude hiding (any, all, dropWhile, take, takeWhile, sequence, concatMap, maybe, either, filter)
252-- >>> import Prelude hiding (any, all, dropWhile, take, takeWhile, sequence, concatMap, maybe, either, filter)
254-- >>> import Control.Applicative ((<|>))
253-- >>> import Control.Applicative ((<|>))
254+-- >>> import Data.Char (isSpace)
255-- >>> import qualified Streamly.Prelude as Stream
255-- >>> import qualified Streamly.Prelude as Stream
256-- >>> import qualified Streamly.Internal.Data.Stream.IsStream as Stream (parse, parseMany)
256-- >>> import qualified Streamly.Internal.Data.Stream.IsStream as Stream (parse, parseMany)
257-- >>> import qualified Streamly.Internal.Data.Fold as Fold
257-- >>> import qualified Streamly.Internal.Data.Fold as Fold
@@ -661,6 +661,7 @@ dropWhile p = takeWhile p FL.drain
661-- Separators
661-- Separators
662-------------------------------------------------------------------------------
662-------------------------------------------------------------------------------
663
663
664+-- XXX We can remove Maybe from esc
664{-# INLINE takeFramedByGeneric #-}
665{-# INLINE takeFramedByGeneric #-}
665takeFramedByGeneric :: MonadCatch m =>
666takeFramedByGeneric :: MonadCatch m =>
666 Maybe (a -> Bool)
667 Maybe (a -> Bool)
@@ -823,6 +824,8 @@ takeFramedBy_ isBegin isEnd f = D.toParserK $ D.takeFramedBy_ isBegin isEnd f
823-- * Stops - when it finds a word separator after a non-word element
824-- * Stops - when it finds a word separator after a non-word element
824-- * Fails - never.
825-- * Fails - never.
825--
826--
827+-- >>> wordBy = Parser.wordFramedBy (const False) (const False) (const False)
828+--
826-- @
829-- @
827-- S.wordsBy pred f = S.parseMany (PR.wordBy pred f)
830-- S.wordsBy pred f = S.parseMany (PR.wordBy pred f)
828-- @
831-- @
@@ -831,6 +834,37 @@ takeFramedBy_ isBegin isEnd f = D.toParserK $ D.takeFramedBy_ isBegin isEnd f
831wordBy :: MonadCatch m => (a -> Bool) -> Fold m a b -> Parser m a b
834wordBy :: MonadCatch m => (a -> Bool) -> Fold m a b -> Parser m a b
832wordBy f = D.toParserK . D.wordBy f
835wordBy f = D.toParserK . D.wordBy f
833
836
837+-- | Like 'wordBy' but treats anything inside a pair of quotes as a single
838+-- word, the quotes can be escaped by an escape character. Recursive quotes
839+-- are possible if quote begin and end characters are different, quotes must be
840+-- balanced. Outermost quotes are stripped.
841+--
842+-- >>> braces = Parser.wordFramedBy (== '\\') (== '{') (== '}') isSpace Fold.toList
843+-- >>> Stream.parse braces $ Stream.fromList "{ab} cd"
844+-- "ab"
845+-- >>> Stream.parse braces $ Stream.fromList "{ab}{cd}"
846+-- "abcd"
847+-- >>> Stream.parse braces $ Stream.fromList "a{b} cd"
848+-- "ab"
849+-- >>> Stream.parse braces $ Stream.fromList "a{{b}} cd"
850+-- "a{b}"
851+--
852+-- >>> quotes = Parser.wordFramedBy (== '\\') (== '"') (== '"') isSpace Fold.toList
853+-- >>> Stream.parse quotes $ Stream.fromList "\"a\"\"b\""
854+-- "ab"
855+--
856+-- /Pre-release/
857+{-# INLINE wordFramedBy #-}
858+wordFramedBy :: MonadCatch m =>
859+ (a -> Bool) -- ^ Escape
860+ -> (a -> Bool) -- ^ left quote
861+ -> (a -> Bool) -- ^ right quote
862+ -> (a -> Bool) -- ^ word seperator
863+ -> Fold m a b
864+ -> Parser m a b
865+wordFramedBy isEsc isBegin isEnd isSpc =
866+ D.toParserK . D.wordFramedBy isEsc isBegin isEnd isSpc
867+
834-- | Given an input stream @[a,b,c,...]@ and a comparison function @cmp@, the
868-- | Given an input stream @[a,b,c,...]@ and a comparison function @cmp@, the
835-- parser assigns the element @a@ to the first group, then if @a \`cmp` b@ is
869-- parser assigns the element @a@ to the first group, then if @a \`cmp` b@ is
836-- 'True' @b@ is also assigned to the same group. If @a \`cmp` c@ is 'True'
870-- 'True' @b@ is also assigned to the same group. If @a \`cmp` c@ is 'True'
core/src/Streamly/Internal/Data/Parser/ParserD.hs+91−1
@@ -82,7 +82,7 @@ module Streamly.Internal.Data.Parser.ParserD
82
82
83 -- Words and grouping
83 -- Words and grouping
84 , wordBy
84 , wordBy
85− -- wordFramedBy
85+ , wordFramedBy
86 , groupBy
86 , groupBy
87 , groupByRolling
87 , groupByRolling
88 , groupByRollingEither
88 , groupByRollingEither
@@ -1033,6 +1033,96 @@ wordBy predicate (Fold fstep finitial fextract) = Parser step initial extract
1033 extract (WBWord s) = fextract s
1033 extract (WBWord s) = fextract s
1034 extract (WBRight b) = return b
1034 extract (WBRight b) = return b
1035
1035
1036+data WordFramedState s b =
1037+ WordFramedSkipPre !s
1038+ | WordFramedWord !s !Int
1039+ | WordFramedEsc !s !Int
1040+ | WordFramedSkipPost !b
1041+
1042+-- | See 'Streamly.Internal.Data.Parser.wordFramedBy'
1043+--
1044+{-# INLINE wordFramedBy #-}
1045+wordFramedBy :: MonadCatch m =>
1046+ (a -> Bool) -- ^ Escape
1047+ -> (a -> Bool) -- ^ left quote
1048+ -> (a -> Bool) -- ^ right quote
1049+ -> (a -> Bool) -- ^ word seperator
1050+ -> Fold m a b
1051+ -> Parser m a b
1052+wordFramedBy isEsc isBegin isEnd isSep
1053+ (Fold fstep finitial fextract) =
1054+ Parser step initial extract
1055+
1056+ where
1057+
1058+ initial = do
1059+ res <- finitial
1060+ return $
1061+ case res of
1062+ FL.Partial s -> IPartial (WordFramedSkipPre s)
1063+ FL.Done _ ->
1064+ error "wordFramedBy: fold done without input"
1065+
1066+ {-# INLINE process #-}
1067+ process s a n = do
1068+ res <- fstep s a
1069+ return
1070+ $ case res of
1071+ FL.Partial s1 -> Continue 0 (WordFramedWord s1 n)
1072+ FL.Done b -> Done 0 b
1073+
1074+ step (WordFramedSkipPre s) a
1075+ | isEsc a = return $ Continue 0 $ WordFramedEsc s 0
1076+ | isSep a = return $ Partial 0 $ WordFramedSkipPre s
1077+ | isBegin a = return $ Continue 0 $ WordFramedWord s 1
1078+ | isEnd a =
1079+ return $ Error "wordFramedBy: missing frame start"
1080+ | otherwise = process s a 0
1081+ step (WordFramedWord s n) a
1082+ | isEsc a = return $ Continue 0 $ WordFramedEsc s n
1083+ | n == 0 && isSep a = do
1084+ b <- fextract s
1085+ return $ Partial 0 $ WordFramedSkipPost b
1086+ | otherwise = do
1087+ -- We need to use different order for checking begin and end for
1088+ -- the n == 0 and n == 1 case so that when the begin and end
1089+ -- character is the same we treat the one after begin as end.
1090+ if n == 0
1091+ then
1092+ -- Need to check isBegin first
1093+ if isBegin a
1094+ then return $ Continue 0 $ WordFramedWord s 1
1095+ else if isEnd a
1096+ then return $ Error "wordFramedBy: missing frame start"
1097+ else process s a n
1098+ else
1099+ -- Need to check isEnd first
1100+ if isEnd a
1101+ then
1102+ if n == 1
1103+ then return $ Continue 0 $ WordFramedWord s 0
1104+ else process s a (n - 1)
1105+ else if isBegin a
1106+ then process s a (n + 1)
1107+ else process s a n
1108+ step (WordFramedEsc s n) a = process s a n
1109+ step (WordFramedSkipPost b) a =
1110+ return
1111+ $ if not (isSep a)
1112+ then Done 1 b
1113+ else Partial 0 $ WordFramedSkipPost b
1114+
1115+ err = throwM . ParseError
1116+
1117+ extract (WordFramedSkipPre s) = fextract s
1118+ extract (WordFramedWord s n) =
1119+ if n == 0
1120+ then fextract s
1121+ else err "wordFramedBy: missing frame end"
1122+ extract (WordFramedEsc _ _) =
1123+ err "wordFramedBy: trailing escape"
1124+ extract (WordFramedSkipPost b) = return b
1125+
1036{-# ANN type GroupByState Fuse #-}
1126{-# ANN type GroupByState Fuse #-}
1037data GroupByState a s
1127data GroupByState a s
1038 = GroupByInit !s
1128 = GroupByInit !s