packages feed

vimus-0.1.0: src/Command/Parser.hs

{-# LANGUAGE DeriveFunctor #-}
module Command.Parser where

import           Prelude hiding (takeWhile)
import           Data.List (intercalate)
import           Control.Monad
import           Control.Applicative

type Name  = String
type Value = String

-- | Errors are ordered from less specific to more specific.  More specific
-- errors take precedence over less specific ones.
data ParseError =
    Empty
  | ParseError String
  | SuperfluousInput Value
  | MissingArgument Name
  | InvalidArgument Name Value
  | SpecificArgumentError String
  deriving (Eq, Ord)

instance Show ParseError where
  show e = case e of
    Empty                      -> "Control.Applicative.Alternative.empty"
    ParseError err             -> "parse error: " ++ err
    SuperfluousInput input     -> case words input of
      []  -> "superfluous input: " ++ show input
      [x] -> "unexpected argument: " ++ show x
      xs  -> "unexpected arguments: " ++ intercalate ", " (map show xs)
    MissingArgument name       -> "missing required argument: " ++ name
    InvalidArgument name value -> "argument " ++ show value ++ " is not a valid " ++ name
    SpecificArgumentError err  -> err

newtype Parser a = Parser {runParser :: String -> Either ParseError (a, String)}
  deriving Functor

instance Applicative Parser where
  pure  = return
  (<*>) = ap

instance Monad Parser where
  fail      = parserFail . ParseError
  return a  = Parser $ \input -> Right (a, input)
  p1 >>= p2 = Parser $ \input -> runParser p1 input >>= uncurry (runParser . p2)

instance Alternative Parser where
  empty = parserFail Empty
  p1 <|> p2 = Parser $ \input -> case runParser p1 input of
    Left err -> either (Left . max err) (Right) (runParser p2 input)
    x        -> x

-- | Recognize a character that satisfies a given predicate.
satisfy :: (Char -> Bool) -> Parser Char
satisfy p = Parser go
  where
    go (x:xs)
      | p x       = Right (x, xs)
      | otherwise = (Left . ParseError) ("satisfy: unexpected " ++ show x)
    go ""         = (Left . ParseError) "satisfy: unexpected end of input"

-- | Recognize a given character.
char :: Char -> Parser Char
char c = satisfy (== c)

-- | Recognize a given string.
string :: String -> Parser String
string = mapM char

parserFail :: ParseError -> Parser a
parserFail = Parser . const . Left

takeWhile :: (Char -> Bool) -> Parser String
takeWhile p = Parser $ Right . span p

takeWhile1 :: (Char -> Bool) -> Parser String
takeWhile1 p = Parser go
  where
    go ""         = (Left . ParseError) "takeWhile1: unexpected end of input"
    go (x:xs)
      | p x       = let (ys, zs) = span p xs in Right (x:ys, zs)
      | otherwise = (Left . ParseError) ("takeWhile1: unexpected " ++ show x)

skipWhile :: (Char -> Bool) -> Parser ()
skipWhile p = takeWhile p *> pure ()

-- | Consume and return all remaining input.
takeInput :: Parser String
takeInput = Parser $ \input -> Right (input, "")

-- | Succeed only if all input has been consumed.
endOfInput :: Parser ()
endOfInput = Parser $ \input -> case input of
  "" -> Right ((), "")
  xs -> (Left . ParseError) ("endOfInput: remaining input " ++ show xs)