on master help sign in
Simplify wordWithQuotes, add wordStripQuotes
commit40dd1665fe3a5e9bd418a9b1a6bda62f076017d7 authorHarendra Kumar <harendra@composewell.com> dateThu 23 Feb 2023, 09:22 (4 years ago) parent32785ad
2 files changed+99−47
core/src/Streamly/Data/Parser.hs+1 core/src/Streamly/Internal/Data/Parser/ParserD.hs+98−47
core/src/Streamly/Data/Parser.hs+1
@@ -127,6 +127,7 @@ module Streamly.Data.Parser
127127 -- ** Framing
128128 -- , wordFramedBy
129129 , wordWithQuotes
130+ , wordStripQuotes
130131
131132 -- * Alternative
132133 , alt
core/src/Streamly/Internal/Data/Parser/ParserD.hs+98−47
@@ -146,6 +146,7 @@ module Streamly.Internal.Data.Parser.ParserD
146146 , takeFramedByGeneric
147147 , wordFramedBy
148148 , wordWithQuotes
149+ , wordStripQuotes
149150 , blockWithQuotes
150151
151152 -- Matching strings
@@ -1589,51 +1590,18 @@ data WordQuotedState s b a =
15891590 | WordQuotedEsc !s !Int !a !a
15901591 | WordQuotedSkipPost !b
15911592
1592−-- XXX If quote start and end chars are different then quotes can be nested
1593−-- without escaping. However, is there a case for the use of nested quotes?
1594−
1595−-- XXX Simplify like blockWithQuotes
1596−
1597−-- | Like 'wordBy' but word separators within quotes are ignored. Quote
1598−-- character can be used within quotes if escaped using the supplied escape
1599−-- char. A different quoting character can be used inside quotes without
1600−-- escaping. Starting quote determines the ending quote.
1601−--
1602−-- Example:
1603−--
1604−-- >>> :{
1605−-- >>> lq x =
1606−-- >>> case x of
1607−-- >>> '"' -> Just x
1608−-- >>> '\'' -> Just x
1609−-- >>> _ -> Nothing
1610−-- >>> :}
1611−--
1612−-- >>> rq = (`elem` ['"', '\''])
1613−-- >>> p keep = Parser.wordWithQuotes keep (== '\\') lq rq isSpace Fold.toList
1614−--
1615−-- >>> Stream.parse (p False) $ Stream.fromList "a\"b'c\";'d\"e'f ghi"
1616−-- Right "ab'c;d\"ef"
1617−--
1618−-- Outer quotes and backslashes from the input string are removed by Haskell,
1619−-- therefore, the actual input string looks like:
1620−-- a"b'c";'d"e'f ghi
1621−-- From this the outer quotes are stripped by the parser, double quotes inside
1622−-- single quotes or vice-versa are kept.
1623−--
1624−-- >>> Stream.parse (p True) $ Stream.fromList "a\"b'c\";'d\"e'f ghi"
1625−-- Right "a\"b'c\";'d\"e'f"
1626−--
1627−{-# INLINE wordWithQuotes #-}
1628−wordWithQuotes :: (Monad m, Eq a) =>
1629− Bool -- ^ keep the quotes in the output
1630− -> (a -> Bool) -- ^ Matches an escape elem?
1631− -> (a -> Maybe a) -- ^ If left quote, return right quote, else nothing.
1632− -> (a -> Bool) -- ^ Matches a right quote?
1633− -> (a -> Bool) -- ^ Matches a word separator?
1593+-- The invalid unquoted char predicate can be used to generate parse error for
1594+-- a bracket end without a bracket begin.
1595+{-# INLINE wordWithQuotesWith #-}
1596+wordWithQuotesWith :: (Monad m, Eq a) =>
1597+ (a -> Bool) -- ^ Matches an invalid unquoted char
1598+ -> Bool -- ^ Retain the quotes and escape chars in the output
1599+ -> (a -> Bool) -- ^ Matches an escape elem?
1600+ -> (a -> Maybe a) -- ^ If left quote, return right quote, else Nothing.
1601+ -> (a -> Bool) -- ^ Matches a word separator?
16341602 -> Fold m a b
16351603 -> Parser a m b
1636−wordWithQuotes keepQuotes isEsc toRight isEnd isSep
1604+wordWithQuotesWith isInvalid keepQuotes isEsc toRight isSep
16371605 (Fold fstep finitial fextract) =
16381606 Parser step initial extract
16391607
@@ -1673,8 +1641,8 @@ wordWithQuotes keepQuotes isEsc toRight isEnd isSep
16731641 then process s a 1 a qr
16741642 else return $ Continue 0 $ WordQuotedWord s 1 a qr
16751643 Nothing
1676− | isEnd a ->
1677− return $ Error "wordWithQuotes: missing frame start"
1644+ | isInvalid a ->
1645+ return $ Error "wordWithQuotes: invalid unquoted char"
16781646 | otherwise -> processUnquoted s a
16791647 step (WordUnquotedWord s) a
16801648 | isEsc a = return $ Continue 0 $ WordUnquotedEsc s
@@ -1688,15 +1656,17 @@ wordWithQuotes keepQuotes isEsc toRight isEnd isSep
16881656 then process s a 1 a qr
16891657 else return $ Continue 0 $ WordQuotedWord s 1 a qr
16901658 Nothing ->
1691− if isEnd a
1692− then return $ Error "wordWithQuotes: missing frame start"
1659+ if isInvalid a
1660+ then return $ Error "wordWithQuotes: invalid unquoted char"
16931661 else processUnquoted s a
16941662 step (WordQuotedWord s n ql qr) a
16951663 | isEsc a = return $ Continue 0 $ WordQuotedEsc s n ql qr
1664+ {-
16961665 -- XXX Will this ever occur? Will n ever be 0?
16971666 | n == 0 && isSep a = do
16981667 b <- fextract s
16991668 return $ Partial 0 $ WordQuotedSkipPost b
1669+ -}
17001670 | otherwise = do
17011671 if a == qr
17021672 then
@@ -1730,6 +1700,87 @@ wordWithQuotes keepQuotes isEsc toRight isEnd isSep
17301700 err "wordWithQuotes: trailing escape"
17311701 extract (WordQuotedSkipPost b) = return (Done 0 b)
17321702
1703+-- | Quote and bracket aware word splitting. Like 'wordBy' but word separators
1704+-- within specified quotes or brackets are ignored. If the end quote is
1705+-- different from the start quote it is called a bracket. The following quoting
1706+-- rules apply:
1707+--
1708+-- * A quoting or bracket character can be used within the same quote or
1709+-- bracket if escaped using the supplied escape char.
1710+-- * A quoting character other than the current quote can be used inside quotes
1711+-- without escaping e.g. "a single quote ' inside a double quote".
1712+-- * A starting quote or bracket char without an ending char generates a parse
1713+-- error.
1714+-- * An ending bracket char without a corresponding begin quote is ignored e.g.
1715+-- this parenthesis ) is ignored.
1716+-- * Brackets can be nested.
1717+--
1718+-- Examples of quotes - "double quotes", 'single quotes', (parens), {braces},
1719+-- ((nested) brackets).
1720+--
1721+-- Example:
1722+--
1723+-- >>> :{
1724+-- >>> q x =
1725+-- >>> case x of
1726+-- >>> '"' -> Just x
1727+-- >>> '\'' -> Just x
1728+-- >>> _ -> Nothing
1729+-- >>> :}
1730+--
1731+-- >>> p = Parser.wordWithQuotes (== '\\') q isSpace Fold.toList
1732+-- >>> Stream.parse p $ Stream.fromList "a\"b'c\";'d\"e'f ghi"
1733+-- Right "a\"b'c\";'d\"e'f"
1734+--
1735+-- Note that outer quotes and backslashes from the input string are consumed by
1736+-- Haskell, therefore, the actual input string passed to the parser is:
1737+-- a"b'c";'d"e'f ghi
1738+--
1739+-- Similarly, when printing, double quotes are escaped by Haskell.
1740+--
1741+{-# INLINE wordWithQuotes #-}
1742+wordWithQuotes :: (Monad m, Eq a) =>
1743+ (a -> Bool) -- ^ Matches an escape elem?
1744+ -> (a -> Maybe a) -- ^ If left quote, return right quote, else Nothing.
1745+ -> (a -> Bool) -- ^ Matches a word separator?
1746+ -> Fold m a b
1747+ -> Parser a m b
1748+wordWithQuotes = wordWithQuotesWith (const False) True
1749+
1750+-- | Like 'wordWithQuotes' but strips the quotes and escape chars.
1751+--
1752+-- Example:
1753+--
1754+-- >>> :{
1755+-- >>> q x =
1756+-- >>> case x of
1757+-- >>> '"' -> Just x
1758+-- >>> '\'' -> Just x
1759+-- >>> _ -> Nothing
1760+-- >>> :}
1761+--
1762+-- >>> p = Parser.wordStripQuotes (== '\\') q isSpace Fold.toList
1763+-- >>> Stream.parse p $ Stream.fromList "a\"b'c\";'d\"e'f ghi"
1764+-- Right "ab'c;d\"ef"
1765+--
1766+-- Note that outer quotes and backslashes from the input string are consumed by
1767+-- Haskell, therefore, the actual input string passed to the parser is:
1768+-- a"b'c";'d"e'f ghi
1769+--
1770+-- From this the outermost quotes are stripped by the parser, double quotes inside
1771+-- single quotes or vice-versa are kept.
1772+--
1773+-- Similarly, when printing, double quotes are escaped by Haskell.
1774+--
1775+{-# INLINE wordStripQuotes #-}
1776+wordStripQuotes :: (Monad m, Eq a) =>
1777+ (a -> Bool) -- ^ Matches an escape elem?
1778+ -> (a -> Maybe a) -- ^ If left quote, return right quote, else Nothing.
1779+ -> (a -> Bool) -- ^ Matches a word separator?
1780+ -> Fold m a b
1781+ -> Parser a m b
1782+wordStripQuotes = wordWithQuotesWith (const False) False
1783+
17331784data BlockParseState s =
17341785 BlockInit !s
17351786 | BlockUnquoted !Int !s