bookhound-0.1.6.0: src/Parser.hs
module Parser (Parser, ParseResult, ParseError(..), runParser, errorParser,
andThen, exactly, isMatch, check, except, anyOf, allOf, char,
withTransform) where
import Control.Applicative (liftA2)
import Control.Monad (join)
import Data.Maybe (isJust)
import Data.Either (fromRight)
import Data.List (find)
type Input = String
data Parser a = P { parse :: Input -> ParseResult a
, transform :: forall b. Maybe (Parser b -> Parser b)
}
data ParseResult a = Result Input a | Error ParseError
deriving Eq
data ParseError = UnexpectedEof | ExpectedEof Input |
UnexpectedChar Char | UnexpectedString String |
NoMatch String
deriving (Eq, Show)
instance Show a => Show (ParseResult a) where
show (Result i a) = "Pending: " ++ " >" ++ i ++ "< " ++
"\n\nResult: \n" ++ show a
show (Error UnexpectedEof) = "Unexpected end of stream"
show (Error (ExpectedEof i)) = "Expected end of stream, but got >" ++ i ++ "<"
show (Error (UnexpectedChar c)) = "Unexpected char: " ++ "[" ++ show c ++ "]"
show (Error (UnexpectedString s)) = "Unexpected string: " ++ "[" ++ show s ++ "]"
show (Error (NoMatch s)) = "Did not match condition: " ++ s
instance Functor ParseResult where
fmap f (Result i a) = Result i (f a)
fmap _ (Error pe) = Error pe
instance Functor Parser where
fmap f (P p t) = applyTransform t $ mkParser (fmap f . p)
instance Applicative Parser where
pure a = mkParser (`Result` a)
(liftA2) f (P p t) mb@(P _ t') =
applyTransform (findJust t t') combinedParser
where
combinedParser = mkParser (
\x -> case p x of
Result i a -> parse ((f a) <$> mb) i
Error pe -> Error pe)
instance Monad Parser where
(>>=) (P p t) f = applyTransform t combinedParser
where
combinedParser = mkParser (
\x -> case p x of
Result i a -> parse (f a) i
Error pe -> Error pe)
runParser :: Parser a -> Input -> Either ParseError a
runParser p i = toEither $ parse (exactly p) i where
toEither = \case
Error pe -> Left pe
Result _ a -> Right a
errorParser :: ParseError -> Parser a
errorParser = mkParser . const . Error
char :: Parser Char
char = mkParser parseIt where
parseIt [] = Error UnexpectedEof
parseIt (ch : rest) = Result rest ch
andThen :: Parser Input -> Parser a -> Parser a
andThen p1 p2@(P _ t) = applyTransform t $ P (\i -> parse p2 $ fromRight i $ runParser p1 i) t
exactly :: Parser a -> Parser a
exactly (P p t) = applyTransform t $ mkParser (
\x -> case p x of
result@(Result "" _) -> result
Result i _ -> Error $ ExpectedEof i
err@(Error _) -> err)
anyOf :: [Parser a] -> Parser a
anyOf ps = anyOfHelper ps Nothing
allOf :: [Parser a] -> Parser a
allOf ps = allOfHelper ps Nothing
anyOfHelper :: [Parser a] -> (forall b. Maybe (Parser b -> Parser b)) -> Parser a
anyOfHelper [] _ = errorParser $ NoMatch "anyOf"
anyOfHelper [p] _ = p
anyOfHelper ((P p t) : rest) t' = applyTransform (findJust t t') $ mkParser (
\x -> case p x of
result@(Result _ _) -> result
Error _ -> parse (anyOfHelper rest t) x)
allOfHelper :: [Parser a] -> (forall b. Maybe (Parser b -> Parser b)) -> Parser a
allOfHelper [] _ = errorParser $ NoMatch "allOf"
allOfHelper [p] _ = p
allOfHelper ((P p t) : rest) t' = applyTransform (findJust t t') $ mkParser (
\x -> case p x of
Result i _ -> parse (allOfHelper rest t) i
err@(Error _) -> err)
isMatch :: (Char -> Char -> Bool) -> Parser Char -> Char -> Parser Char
isMatch cond parser c1 =
do c2 <- parser
if cond c1 c2
then pure c2
else errorParser $ UnexpectedChar c2
check :: String -> (a -> Bool) -> Parser a -> Parser a
check condName cond parser =
do c2 <- parser
if cond c2
then pure c2
else errorParser $ NoMatch condName
except :: Show a => Parser a -> Parser a -> Parser a
except (P p t) (P p' _) = applyTransform t $ mkParser (
\x -> case p' x of
Result _ a -> Error $ UnexpectedString (show a)
Error _ -> p x)
withTransform :: (forall b. Parser b -> Parser b) -> Parser a -> Parser a
withTransform f = applyTransform $ Just f
applyTransform :: (forall a. Maybe (Parser a -> Parser a)) -> Parser b -> Parser b
applyTransform f p = maybe p (\f' -> (f' p){transform = f} ) f
mkParser :: (Input -> ParseResult a) -> Parser a
mkParser p = P {parse = p, transform = Nothing}
findJust :: forall a. Maybe a -> Maybe a -> Maybe a
findJust ma mb = join $ find isJust [ma, mb]