{-# LANGUAGE CPP #-}
-- | A parse result type for parsers from AST to Haskell types.
module Distribution.Parsec.Types.ParseResult (
    ParseResult,
    runParseResult,
    recoverWith,
    parseWarning,
    parseFailure,
    parseFatalFailure,
    parseFatalFailure',
    parseWarnings',
    ) where

import           Prelude ()
import           Distribution.Compat.Prelude
import           Distribution.Parsec.Types.Common
                 (PError (..), PWarnType (..), PWarning (..), Position (..))

-- | A monad with failure and accumulating errors and warnings.
newtype ParseResult a = PR { runPR :: PRState -> (Maybe a, PRState) }

data PRState = PRState ![PWarning] ![PError]

emptyPRState :: PRState
emptyPRState = PRState [] []

-- | Destruct a 'ParseResult' into the emitted warnings and errors, and
-- possibly the final result if there were no errors.
runParseResult :: ParseResult a -> ([PWarning], [PError], Maybe a)
runParseResult pr = case runPR pr emptyPRState of
    (res, PRState warns errs)
        -- If there are any errors, don't return the result
        | null errs -> (warns, [], res)
        | otherwise -> (warns, errs, Nothing)

instance Functor ParseResult where
    fmap f (PR pr) = PR $ \s -> case pr s of
        (r, s') -> (fmap f r, s')

instance Applicative ParseResult where
    pure x = PR $ \s -> (Just x, s)
    -- | Make it concat perrors
    (<*>) = ap
    PR a *> PR b = PR $ \s0 -> case a s0 of
        (x, s1) -> case b s1 of
            (y, s2) -> (x *> y, s2)

instance Monad ParseResult where
    return = pure
    (>>) = (*>)
    PR m >>= k = PR $ \s -> case m s of
        (Nothing, s') -> (Nothing, s')
        (Just x,  s') -> runPR (k x) s'

-- | "Recover" the parse result, so we can proceed parsing.
-- 'runParseResult' will still result in 'Nothing', if there are recorded errors.
recoverWith :: ParseResult a -> a -> ParseResult a
recoverWith (PR f) x = PR $ \s -> case f s of
    (Nothing, s') -> (Just x, s')
    r             -> r

parseWarning :: Position -> PWarnType -> String -> ParseResult ()
parseWarning pos t msg = PR $ \(PRState warns errs) ->
    (Just (), PRState (PWarning t pos msg : warns) errs)

parseWarnings' :: [PWarning] -> ParseResult ()
parseWarnings' newWarns = PR $ \(PRState warns errs) ->
    (Just (), PRState (warns ++ newWarns) errs)

-- | Add an error, but not fail the parser yet.
--
-- For fatal failure use 'parseFatalFailure'
parseFailure :: Position -> String -> ParseResult ()
parseFailure pos msg = PR $ \(PRState warns errs) ->
    (Just (), PRState warns (PError pos msg : errs))

-- | Add an fatal error.
parseFatalFailure :: Position -> String -> ParseResult a
parseFatalFailure pos msg = PR $ \(PRState warns errs) ->
    (Nothing, PRState warns (PError pos msg : errs))

parseFatalFailure' :: ParseResult a
parseFatalFailure' = PR f
  where
    f s@(PRState warns errs)
        | null errs = (Nothing, PRState warns [PError (Position 0 0) "Unknown failure"])
        | otherwise = (Nothing, s)
