{-# LANGUAGE LambdaCase #-}
module Parser
  ( Parser()
  , module X
  , Parser.any
  , satisfy
  , string
  , digit
  , number
  , spaces
  , reserved
  , lexeme
  , (<?>)
  , runParser
  , between ) where

import Control.Applicative as X
import Control.Monad as X

import Data.Char

newtype Parser a
  = Parser { parse :: String -> Either String (a, String) }

runParser :: Parser a -> String -> Either String a
runParser (Parser p) s = fst <$> p s

(<?>) :: Parser a -> String -> Parser a
p <?> err = p <|> fail err
infixl 2 <?>

instance Functor Parser where
  fn `fmap` (Parser p) = Parser go where
    go st = case p st of
      Left e            -> Left e
      Right (res, str') -> Right (fn res, str')

instance Applicative Parser where
  pure x = Parser $ \str -> Right (x, str)
  (Parser p) <*> (Parser p') = Parser go where
    go st = case p st of
      Left e -> Left e
      Right (fn, st') -> case p' st' of
        Left e' -> Left e'
        Right (v, st'') -> Right (fn v, st'')

instance Alternative Parser where
  empty = fail "nothing"
  (Parser p) <|> (Parser p') = Parser go where
    go st = case p st of
      Left _  -> p' st
      Right x -> Right x

instance Monad Parser where
  return = pure
  (Parser p) >>= f = Parser go where
    go s = case p s of
      Left e -> Left e
      Right (x, s') -> parse (f x) s'
  fail m = Parser go where
    go = Left . go'
    go' []     = "expected " ++ m ++ ", got to the end of stream"
    go' (x:xs) = "expected " ++ m ++ ", got '" ++ x:"'"
    

any :: Parser Char
any = Parser go where
  go []     = Left "any: end of file"
  go (x:xs) = Right (x,xs)

satisfy :: (Char -> Bool) -> Parser Char
satisfy f = do
  x <- Parser.any
  if f x
    then return x
    else fail "a solution to the function"


char :: Char -> Parser Char
char c = satisfy (c ==) <?> "literal " ++ [c]

oneOf :: String -> Parser Char
oneOf s = satisfy (`elem` s) <?> "one of '" ++ s ++ "'"

string :: String -> Parser String
string [] = return []
string (x:xs) = do
  char x
  string xs
  return $ x:xs

natural :: Parser Integer
natural = read <$> some (satisfy isDigit)

lexeme :: Parser a -> Parser a
lexeme = (<* spaces)

reserved :: String -> Parser String
reserved = lexeme . string

spaces :: Parser String
spaces = many $ oneOf " \n\r"

digit :: Parser Char
digit = satisfy isDigit

number :: Parser Int
number = do
  s <- string "-" <|> empty
  cs <- some digit
  return $ read (s ++ cs)

between :: Parser b -> Parser c -> Parser a -> Parser a
between o c x = o *> x <* c

contents :: Parser a -> Parser a
contents x = spaces *> x <* spaces

sep :: Parser b -> Parser a -> Parser [a]
sep s c = sep1 s c <|> return []

sep1 :: Parser b -> Parser a -> Parser [a]
sep1 s c = do
  x <- c
  xs <- many $ s >> c
  return $ x:xs

option :: a -> Parser a -> Parser a
option x p = p <|> return x

optionMaybe :: Parser a -> Parser (Maybe a)
optionMaybe p = option Nothing $ Just <$> p

optional :: Parser a -> Parser ()
optional p = void p <|> return ()

eof :: Parser ()
eof = Parser go where
  go (x:_) = Left $ "expected eof, got '" ++ x:"'"
  go []    = Right ((), [])
