Custom parser combinator in Haskell
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Parser | |
( Parser (..) | |
) where | |
import Control.Applicative (Alternative (..)) | |
import Data.List (nub) | |
type Offset = Int | |
data Error i e = Error | |
{ erOffset :: Offset | |
, erError :: ErrorType i e | |
} deriving (Eq, Show) | |
data ErrorType i e | |
= EndOfInput | |
| Unexpected i | |
| Expected i i | |
| ExpectedEndOfFile i | |
| CustomError e | |
| Empty | |
deriving (Eq, Show) | |
newtype Parser i e a = Parser | |
{ runParser :: [i] -> Offset -> Either [Error i e] (Offset, a, [i]) | |
} | |
token :: (i -> ErrorType i e) -> (i -> Bool) -> Parser i e i | |
token mkErr predicate = Parser $ \input offset -> | |
case input of | |
[] -> Left [Error offset EndOfInput] | |
hd : rest | |
| predicate hd -> Right (offset + 1, hd, rest) | |
| otherwise -> Left [Error offset $ mkErr hd] | |
satisfy :: (i -> Bool) -> Parser i e i | |
satisfy = token Unexpected | |
char :: Eq i => i -> Parser i e i | |
char i = token (Expected i) (== i) | |
eof :: Parser i e () | |
eof = Parser $ \input offset -> | |
case input of | |
[] -> Right (offset, (), []) | |
hd : _ -> Left [Error offset $ ExpectedEndOfFile hd] | |
parse :: Parser i e a -> [i] -> Either [Error i e] a | |
parse (Parser p) input = | |
case p input 0 of | |
Left e -> Left e | |
Right (_offset, output, _rest) -> Right output | |
instance Functor (Parser i e) where | |
fmap f (Parser p) = Parser $ \input offset -> | |
case p input offset of | |
Left err -> Left err | |
Right (offset', output, rest) -> Right (offset', f output, rest) | |
instance Applicative (Parser i e) where | |
pure a = Parser $ \input offset -> Right (offset, a, input) | |
Parser f <*> Parser p = Parser $ \input offset -> | |
case f input offset of | |
Left err -> Left err | |
Right (offset', f', rest) -> | |
case p rest offset' of | |
Left err -> Left err | |
Right (offset'', output, rest') -> Right (offset'', f' output, rest') | |
instance Monad (Parser i e) where | |
return = pure | |
Parser p >>= k = Parser $ \input offset -> | |
case p input offset of | |
Left err -> Left err | |
Right (offset', output, rest) -> | |
let | |
Parser p' = k output | |
in | |
p' rest offset' | |
string :: Eq i => [i] -> Parser i e [i] | |
string = traverse char | |
instance (Eq i, Eq e) => Alternative (Parser i e) where | |
empty = Parser $ \_ offset -> Left [Error offset Empty] | |
Parser l <|> Parser r = Parser $ \input offset -> | |
case l input offset of | |
Left err -> | |
case r input offset of | |
Left err' -> Left $ nub $ err <> err' | |
Right result -> Right result | |
Right result -> Right result |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment