@@ -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'