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]