on master help sign in
Add wordFramedBy parser
commit2e32c313e8df27dd47b071967089e08d8c1f14fa authorHarendra Kumar <harendra@composewell.com> dateMon 9 May 2022, 13:36 (4 years ago) parent28d5a2f
2 files changed+127−3
core/src/Streamly/Internal/Data/Parser.hs+36−2 core/src/Streamly/Internal/Data/Parser/ParserD.hs+91−1
core/src/Streamly/Internal/Data/Parser.hs+36−2
@@ -128,8 +128,7 @@ module Streamly.Internal.Data.Parser
128128 , takeFramedBy_
129129 , takeFramedByEsc_
130130 , takeFramedByGeneric
131− -- , wordByQuoted
132− -- , wordByQuotedEsc
131+ , wordFramedBy
133132
134133 -- | Unimplemented
135134 --
@@ -252,6 +251,7 @@ import qualified Streamly.Internal.Data.Parser.ParserK.Type as K
252251-- >>> :set -package streamly
253252-- >>> import Prelude hiding (any, all, dropWhile, take, takeWhile, sequence, concatMap, maybe, either, filter)
254253-- >>> import Control.Applicative ((<|>))
254+-- >>> import Data.Char (isSpace)
255255-- >>> import qualified Streamly.Prelude as Stream
256256-- >>> import qualified Streamly.Internal.Data.Stream.IsStream as Stream (parse, parseMany)
257257-- >>> import qualified Streamly.Internal.Data.Fold as Fold
@@ -661,6 +661,7 @@ dropWhile p = takeWhile p FL.drain
661661-- Separators
662662-------------------------------------------------------------------------------
663663
664+-- XXX We can remove Maybe from esc
664665{-# INLINE takeFramedByGeneric #-}
665666takeFramedByGeneric :: MonadCatch m =>
666667 Maybe (a -> Bool)
@@ -823,6 +824,8 @@ takeFramedBy_ isBegin isEnd f = D.toParserK $ D.takeFramedBy_ isBegin isEnd f
823824-- * Stops - when it finds a word separator after a non-word element
824825-- * Fails - never.
825826--
827+-- >>> wordBy = Parser.wordFramedBy (const False) (const False) (const False)
828+--
826829-- @
827830-- S.wordsBy pred f = S.parseMany (PR.wordBy pred f)
828831-- @
@@ -831,6 +834,37 @@ takeFramedBy_ isBegin isEnd f = D.toParserK $ D.takeFramedBy_ isBegin isEnd f
831834wordBy :: MonadCatch m => (a -> Bool) -> Fold m a b -> Parser m a b
832835wordBy f = D.toParserK . D.wordBy f
833836
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+
834868-- | Given an input stream @[a,b,c,...]@ and a comparison function @cmp@, the
835869-- parser assigns the element @a@ to the first group, then if @a \`cmp` b@ is
836870-- '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
8282
8383 -- Words and grouping
8484 , wordBy
85− -- wordFramedBy
85+ , wordFramedBy
8686 , groupBy
8787 , groupByRolling
8888 , groupByRollingEither
@@ -1033,6 +1033,96 @@ wordBy predicate (Fold fstep finitial fextract) = Parser step initial extract
10331033 extract (WBWord s) = fextract s
10341034 extract (WBRight b) = return b
10351035
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+
10361126{-# ANN type GroupByState Fuse #-}
10371127data GroupByState a s
10381128 = GroupByInit !s