@@ -146,6 +146,7 @@ module Streamly.Internal.Data.Parser.ParserD
146 , takeFramedByGeneric
146 , takeFramedByGeneric
147 , wordFramedBy
147 , wordFramedBy
148 , wordWithQuotes
148 , wordWithQuotes
149 , blockWithQuotes
150 , blockWithQuotes
151 -- Matching strings
152 -- Matching strings
@@ -1589,51 +1590,18 @@ data WordQuotedState s b a =
1589 | WordQuotedEsc !s !Int !a !a
1590 | WordQuotedEsc !s !Int !a !a
1590 | WordQuotedSkipPost !b
1591 | WordQuotedSkipPost !b
1592−-- XXX If quote start and end chars are different then quotes can be nested
1593+-- The invalid unquoted char predicate can be used to generate parse error for
1593−-- without escaping. However, is there a case for the use of nested quotes?
1594+-- a bracket end without a bracket begin.
1594−
1595+{-# INLINE wordWithQuotesWith #-}
1595−-- XXX Simplify like blockWithQuotes
1596+wordWithQuotesWith :: (Monad m, Eq a) =>
1596−
1597+ (a -> Bool) -- ^ Matches an invalid unquoted char
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.
1607−-- >>> '"' -> Just x
1608−-- >>> '\'' -> Just x
1612−-- >>> rq = (`elem` ['"', '\''])
1613−-- >>> p keep = Parser.wordWithQuotes keep (== '\\') lq rq isSpace Fold.toList
1615−-- >>> Stream.parse (p False) $ Stream.fromList "a\"b'c\";'d\"e'f ghi"
1616−-- Right "ab'c;d\"ef"
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.
1624−-- >>> Stream.parse (p True) $ Stream.fromList "a\"b'c\";'d\"e'f ghi"
1625−-- Right "a\"b'c\";'d\"e'f"
1627−{-# INLINE wordWithQuotes #-}
1628−wordWithQuotes :: (Monad m, Eq a) =>
1629− Bool -- ^ keep the quotes in the output
1598+ -> Bool -- ^ Retain the quotes and escape chars in the output
1630− -> (a -> Bool) -- ^ Matches an escape elem?
1599+ -> (a -> Bool) -- ^ Matches an escape elem?
1631− -> (a -> Maybe a) -- ^ If left quote, return right quote, else nothing.
1600+ -> (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?
1601+ -> (a -> Bool) -- ^ Matches a word separator?
1634 -> Fold m a b
1602 -> Fold m a b
1635 -> Parser a m b
1603 -> Parser a m b
1636−wordWithQuotes keepQuotes isEsc toRight isEnd isSep
1604+wordWithQuotesWith isInvalid keepQuotes isEsc toRight isSep
1637 (Fold fstep finitial fextract) =
1605 (Fold fstep finitial fextract) =
1638 Parser step initial extract
1606 Parser step initial extract
@@ -1673,8 +1641,8 @@ wordWithQuotes keepQuotes isEsc toRight isEnd isSep
1673 then process s a 1 a qr
1641 then process s a 1 a qr
1674 else return $ Continue 0 $ WordQuotedWord s 1 a qr
1642 else return $ Continue 0 $ WordQuotedWord s 1 a qr
1676− | isEnd a ->
1644+ | isInvalid a ->
1677− return $ Error "wordWithQuotes: missing frame start"
1645+ return $ Error "wordWithQuotes: invalid unquoted char"
1678 | otherwise -> processUnquoted s a
1646 | otherwise -> processUnquoted s a
1679 step (WordUnquotedWord s) a
1647 step (WordUnquotedWord s) a
1680 | isEsc a = return $ Continue 0 $ WordUnquotedEsc s
1648 | isEsc a = return $ Continue 0 $ WordUnquotedEsc s
@@ -1688,15 +1656,17 @@ wordWithQuotes keepQuotes isEsc toRight isEnd isSep
1688 then process s a 1 a qr
1656 then process s a 1 a qr
1689 else return $ Continue 0 $ WordQuotedWord s 1 a qr
1657 else return $ Continue 0 $ WordQuotedWord s 1 a qr
1690 Nothing ->
1658 Nothing ->
1691− if isEnd a
1659+ if isInvalid a
1692− then return $ Error "wordWithQuotes: missing frame start"
1660+ then return $ Error "wordWithQuotes: invalid unquoted char"
1693 else processUnquoted s a
1661 else processUnquoted s a
1694 step (WordQuotedWord s n ql qr) a
1662 step (WordQuotedWord s n ql qr) a
1695 | isEsc a = return $ Continue 0 $ WordQuotedEsc s n ql qr
1663 | isEsc a = return $ Continue 0 $ WordQuotedEsc s n ql qr
1696 -- XXX Will this ever occur? Will n ever be 0?
1665 -- XXX Will this ever occur? Will n ever be 0?
1697 | n == 0 && isSep a = do
1666 | n == 0 && isSep a = do
1698 b <- fextract s
1667 b <- fextract s
1699 return $ Partial 0 $ WordQuotedSkipPost b
1668 return $ Partial 0 $ WordQuotedSkipPost b
1700 | otherwise = do
1670 | otherwise = do
1701 if a == qr
1671 if a == qr
@@ -1730,6 +1700,87 @@ wordWithQuotes keepQuotes isEsc toRight isEnd isSep
1730 err "wordWithQuotes: trailing escape"
1700 err "wordWithQuotes: trailing escape"
1731 extract (WordQuotedSkipPost b) = return (Done 0 b)
1701 extract (WordQuotedSkipPost b) = return (Done 0 b)
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
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
1714+-- * An ending bracket char without a corresponding begin quote is ignored e.g.
1715+-- this parenthesis ) is ignored.
1716+-- * Brackets can be nested.
1718+-- Examples of quotes - "double quotes", 'single quotes', (parens), {braces},
1719+-- ((nested) brackets).
1726+-- >>> '"' -> Just x
1727+-- >>> '\'' -> Just x
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"
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
1739+-- Similarly, when printing, double quotes are escaped by Haskell.
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?
1748+wordWithQuotes = wordWithQuotesWith (const False) True
1750+-- | Like 'wordWithQuotes' but strips the quotes and escape chars.
1757+-- >>> '"' -> Just x
1758+-- >>> '\'' -> Just x
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"
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
1770+-- From this the outermost quotes are stripped by the parser, double quotes inside
1771+-- single quotes or vice-versa are kept.
1773+-- Similarly, when printing, double quotes are escaped by Haskell.
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?
1782+wordStripQuotes = wordWithQuotesWith (const False) False
1733data BlockParseState s =
1784data BlockParseState s =
1734 BlockInit !s
1785 BlockInit !s
1735 | BlockUnquoted !Int !s
1786 | BlockUnquoted !Int !s