parsec-class-1.0.0.0: src/Text/Parsec/Class.hs
{-# LANGUAGE FlexibleContexts, RankNTypes #-} -- for 'CharParser'
-- | 'HasParser' can be considered a dual to 'Pretty' like 'Read' is to 'Show'.
-- The class provides "Data.Parsec" parsers for its instances that construct
-- the type from its textual representation. Combined with the 'parseM' and
-- 'parse' convenience functions, this class makes parsing simple. Unlike
-- 'Read', Parsec parsers return reasonable error messages in case of failure.
-- Also, there is a rich set of combinators and additional libraries available
-- for re-use.
module Text.Parsec.Class
( CharParser, HasParser(parser), ErrorContext
, parseM, parse
, -- * Re-exports from Text.Parsec
module Text.Parsec
)
where
import Prelude hiding ( fail )
import Text.Parsec.Class.Orphans ( ) -- TODO: This is unnecessary.
import Control.Exception ( throw )
import Control.Monad.Fail
import Data.Functor.Identity
import Numeric.Natural ( Natural )
import Text.Parsec hiding ( parse )
-- | A simplified 'ParsecT' parser that consumes some kind of character stream
-- without requiring any particular state state.
type CharParser st input m a = Stream st m Char => ParsecT st input m a
-- | Types that are instances of this class can be parsed and constructed from
-- some character based text representation.
class HasParser a where
parser :: CharParser st input m a
-- | Parsers functions like 'parse' or 'parseM' use this type to provide a
-- helpful context in case the parser failes. Parsec uses the synonym
-- 'SourceName' for the same purpose, but in fact this type doesn't necessarily
-- have to be a file name. It can be any name or identifier. Oftentimes, it
-- it's useful to pass the name of the type that the parser attempted to parse.
type ErrorContext = String
-- | Convenience wrapper around 'runParserT' that uses the 'HasParser' class to
-- determine the desired parser for the given result type. The function reports
-- syntax errors via 'fail'.
--
-- >>> parseM "Natural" "987654321" :: IO Natural
-- 987654321
-- >>> parseM "Natural" "123456789" :: Maybe Natural
-- Just 123456789
--
-- Please note that parsers run this way do not ignore any white space:
--
-- >>> parseM "Natural" " 1" :: Maybe Natural
-- Nothing
-- >>> parseM "Natural" "1 " :: Maybe Natural
-- Nothing
parseM :: (MonadFail m, Stream input m Char, HasParser a) => ErrorContext -> input -> m a
parseM ctx x = runParserT (parser <* eof) () ctx x >>= either (fail . show) return
-- | Convenience wrapper around 'runParser' that uses the 'HasParser' class to
-- determine the desired parser for the given result type. The function reports
-- syntax errors by 'throw'ing 'ParseError'. This approach is inherently impure
-- and complicates error handling greatly. Use this function only on occasions
-- where parser errors are fatal errors that your code cannot recover from. In
-- almost all cases, 'parseM' is the better choice.
--
-- >>> parse "Natural" "12345" :: Natural
-- 12345
--
-- Like 'parseM', this function does not skip over any white space. Use
-- Parsec's primitive 'runParser' or 'runParserT' functions if you don't like
-- this behavior:
--
-- >>> runParser (spaces >> parser) () "Natural" " 1 " :: Either ParseError Natural
-- Right 1
parse :: (Stream input Identity Char, HasParser a) => ErrorContext -> input -> a
parse ctx = either throw id . runParser (parser <* eof) () ctx
----- Useful HasParser instances ----------------------------------------------
instance HasParser Natural where
parser = read <$> many1 digit