hsemail-2: src/Text/Parsec/Rfc2234.hs
{-# LANGUAGE FlexibleContexts #-}
{- |
Module : Text.Parsec.Rfc2234
Copyright : (c) 2013 Peter Simons
License : BSD3
Maintainer : simons@cryp.to
Stability : provisional
Portability : portable
This module provides parsers for the grammar defined in
RFC2234, \"Augmented BNF for Syntax Specifications:
ABNF\", <http://www.faqs.org/rfcs/rfc2234.html>. The
terminal called @char@ in the RFC is called 'character'
here to avoid conflicts with Parsec's 'char' function.
-}
module Text.Parsec.Rfc2234 where
import Control.Monad ( liftM2, replicateM )
import Data.Char ( toUpper, chr, ord )
import Text.Parsec hiding (crlf)
import qualified Text.Parsec.String as PS
-- Customize hlint ...
{-# ANN module "HLint: ignore Use camelCase" #-}
----------------------------------------------------------------------
-- * Parser Combinators
----------------------------------------------------------------------
-- |Case-insensitive variant of Parsec's 'char' function.
caseChar :: Stream s m Char => Char -> ParsecT s u m Char
caseChar c = satisfy (\x -> toUpper x == toUpper c)
-- |Case-insensitive variant of Parsec's 'string' function.
caseString :: Stream s m Char => String -> ParsecT s u m ()
caseString cs = mapM_ caseChar cs <?> cs
-- |Match a parser at least @n@ times.
-- manyN :: Int -> GenParser a b c -> GenParser a b [c]
manyN :: Int -> ParsecT s u m a -> ParsecT s u m [a]
manyN n p
| n <= 0 = return []
| otherwise = liftM2 (++) (replicateM n p) (many p)
-- |Match a parser at least @n@ times, but no more than @m@ times.
manyNtoM :: Int -> Int -> ParsecT s u m a -> ParsecT s u m [a]
manyNtoM n m p
| n < 0 = return []
| n > m = return []
| n == m = replicateM n p
| n == 0 = foldr (<|>) (return []) (map (\x -> try $ replicateM x p) (reverse [1..m]))
| otherwise = liftM2 (++) (replicateM n p) (manyNtoM 0 (m-n) p)
-- |Helper function to generate 'Parser'-based instances for
-- the 'Read' class.
parsec2read :: PS.Parser a -> String -> [(a, String)]
parsec2read f x = either (error . show) id (parse f' "" x)
where
f' = do { a <- f; res <- getInput; return [(a,res)] }
----------------------------------------------------------------------
-- * Primitive Parsers
----------------------------------------------------------------------
-- |Match any character of the alphabet.
alpha :: Stream s m Char => ParsecT s u m Char
alpha = satisfy (\c -> c `elem` (['A'..'Z'] ++ ['a'..'z']))
<?> "alphabetic character"
-- |Match either \"1\" or \"0\".
bit :: Stream s m Char => ParsecT s u m Char
bit = oneOf "01" <?> "bit ('0' or '1')"
-- |Match any 7-bit US-ASCII character except for NUL (ASCII value 0, that is).
character :: Stream s m Char => ParsecT s u m Char
character = satisfy (\c -> (c >= chr 1) && (c <= chr 127))
<?> "7-bit character excluding NUL"
-- |Match the carriage return character @\\r@.
cr :: Stream s m Char => ParsecT s u m Char
cr = char '\r' <?> "carriage return"
-- |Match returns the linefeed character @\\n@.
lf :: Stream s m Char => ParsecT s u m Char
lf = char '\n' <?> "linefeed"
-- |Match the Internet newline @\\r\\n@.
crlf :: Stream s m Char => ParsecT s u m String
crlf = do c <- cr
l <- lf
return [c,l]
<?> "carriage return followed by linefeed"
-- |Match any US-ASCII control character. That is
-- any character with a decimal value in the range of [0..31,127].
ctl :: Stream s m Char => ParsecT s u m Char
ctl = satisfy (\c -> ord c `elem` ([0..31] ++ [127]))
<?> "control character"
-- |Match the double quote character \"@\"@\".
dquote :: Stream s m Char => ParsecT s u m Char
dquote = char (chr 34) <?> "double quote"
-- |Match any character that is valid in a hexadecimal number;
-- [\'0\'..\'9\'] and [\'A\'..\'F\',\'a\'..\'f\'] that is.
hexdig :: Stream s m Char => ParsecT s u m Char
hexdig = hexDigit <?> "hexadecimal digit"
-- |Match the tab (\"@\\t@\") character.
htab :: Stream s m Char => ParsecT s u m Char
htab = char '\t' <?> "horizontal tab"
-- |Match \"linear white-space\". That is any number of consecutive
-- 'wsp', optionally followed by a 'crlf' and (at least) one more
-- 'wsp'.
lwsp :: Stream s m Char => ParsecT s u m String
lwsp = do r <- choice
[ many1 wsp
, try (liftM2 (++) crlf (many1 wsp))
]
rs <- option [] lwsp
return (r ++ rs)
<?> "linear white-space"
-- |Match /any/ character.
octet :: Stream s m Char => ParsecT s u m Char
octet = anyChar <?> "any 8-bit character"
-- |Match the space.
sp :: Stream s m Char => ParsecT s u m Char
sp = char ' ' <?> "space"
-- |Match any printable ASCII character. (The \"v\" stands for
-- \"visible\".) That is any character in the decimal range of
-- [33..126].
vchar :: Stream s m Char => ParsecT s u m Char
vchar = satisfy (\c -> (c >= chr 33) && (c <= chr 126))
<?> "printable character"
-- |Match either 'sp' or 'htab'.
wsp :: Stream s m Char => ParsecT s u m Char
wsp = sp <|> htab <?> "white-space"
-- ** Useful additions
-- |Match a \"quoted pair\". Any characters (excluding CR and
-- LF) may be quoted.
quoted_pair :: Stream s m Char => ParsecT s u m String
quoted_pair = do _ <- char '\\'
r <- noneOf "\r\n"
return ['\\',r]
<?> "quoted pair"
-- |Match a quoted string. The specials \"@\\@\" and
-- \"@\"@\" must be escaped inside a quoted string; CR and
-- LF are not allowed at all.
quoted_string :: Stream s m Char => ParsecT s u m String
quoted_string = do _ <- dquote
r <- many qcont
_ <- dquote
return ("\"" ++ concat r ++ "\"")
<?> "quoted string"
where
qtext = noneOf "\\\"\r\n"
qcont = many1 qtext <|> quoted_pair