Skip to content

Instantly share code, notes, and snippets.

@heitor-lassarote
Last active December 6, 2021 13:56
Show Gist options
  • Save heitor-lassarote/3e7314956e86b8227f6f6040e69aca9d to your computer and use it in GitHub Desktop.
Save heitor-lassarote/3e7314956e86b8227f6f6040e69aca9d to your computer and use it in GitHub Desktop.
Custom parser combinator in Haskell
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