-- | The results from running Grempa on a grammar (i.e. a parser) and parsing
--   errors.
module Data.Parser.Grempa.Parser.Result
    ( ParseResult
    , Parser
    , ParseError(..)
    , showError
    , parse
    ) where

import Data.List

import Data.Parser.Grempa.Grammar.Token

-- | The result of running a parser
type ParseResult t a = Either (ParseError t) a

-- | The type of a parser generated by Grempa
type Parser t a = [t] -> ParseResult t a

-- | The different kinds of errors that can occur
data ParseError t
    -- | The parser did not get an accepted string of tokens.
    = ParseError
        { expectedTokens :: [Tok t] -- ^ A list of tokens that would have been
                                    -- acceptable inputs when the error occured.
        , position       :: Integer -- ^ The position (index into the input
                                    -- token list) at which the error occured.
        }
    -- | This should not happen. Please file a bug report if it does.
    | InternalParserError
        { position :: Integer -- ^ The position at which something went
                              -- horribly wrong.
        }
  deriving Show

instance Functor ParseError where
    fmap f (ParseError e p)        = ParseError (map (fmap f) e) p
    fmap _ (InternalParserError p) = InternalParserError p

-- | Make a prettier error string from a 'ParseError'.
--   This shows the position as an index into the input string of tokens, which
--   may not always be preferable, as that position may differ to the position
--   in the input if it is first processed by a lexer.
--   It also shows the expected tokens.
showError :: Show t => ParseError t -> String
showError e = case e of
    ParseError ts pos       -> "Parse error at " ++ show pos
                            ++ ", expecting one of {"
                            ++ intercalate "," (map tokToString ts) ++ "}."
    InternalParserError pos -> "Internal parser error at "
                            ++ show pos ++ "."

-- | Throw away the 'Either' from the 'ParseResult' and throw an exception using
--   'showError' if something went wrong.
parse :: Show t => Parser t a -> [t] -> a
parse p i = case p i of
    Left err  -> error $ showError err
    Right res -> res