packages feed

curry-base-0.2.9: Curry/Base/MessageMonad.hs

{-# LANGUAGE FlexibleContexts #-}
{- | The monads MsgMonad and MsgMonadIO provide a common way to log warning
     messages and to stop execution when an error occurs. They may be used to
     integrate different compiler passes smoothly.

     (c) 2009, Holger Siegel.
-}

module Curry.Base.MessageMonad where

import Control.Monad.Error
import Control.Monad.Writer
import Control.Monad.Identity

import Curry.Base.Position

{- | Message monad transformer enabling the reporting of 'WarnMsg's as
     warnings and additionally a 'WarnMsg' as an error message.
-}
type MsgMonadT m = ErrorT WarnMsg (WriterT [WarnMsg] m)

-- | Simple message monad
type MsgMonad = MsgMonadT Identity

-- | Message monad with underlying 'IO' monad
type MsgMonadIO = MsgMonadT IO

-- | Data type for warning messages
data WarnMsg = WarnMsg
  { warnPos :: Maybe Position -- ^ optional source code position
  , warnTxt :: String         -- ^ the message itself
  }

instance Error WarnMsg where
  noMsg  = WarnMsg Nothing "Failure!"
  strMsg = WarnMsg Nothing

instance Show WarnMsg where
  show = showWarning

-- | Show a 'WarnMsg' as a warning
showWarning :: WarnMsg -> String
showWarning w = "Warning: " ++ pos ++ warnTxt w
  where pos = case warnPos w of
                Nothing -> ""
                Just p  -> show p ++ ": "

-- | Show a 'WarnMsg' as an error
showError :: WarnMsg -> String
showError w = "Error: " ++ pos ++ warnTxt w
  where pos = case warnPos w of
                Nothing -> ""
                Just p -> show p ++ ": "

-- | Evaluate the value of a 'MsgMonad a'
runMsg :: MsgMonad a -> (Either WarnMsg a, [WarnMsg])
runMsg = runIdentity . runWriterT . runErrorT

{- | Directly evaluate to the success value of a 'MsgMonad a'. Errors are
     converted in a call to the 'error' function.
-}
ok :: MsgMonad a -> a
ok = either (error . showError) id . fst . runMsg

-- | Sequence 'MsgMonad' action inside the 'IO' monad.
runMsgIO :: MsgMonad a -> (a -> IO (MsgMonad b)) -> IO (MsgMonad b)
runMsgIO m f = case runMsg m of
  (Left  e, msgs) -> return (tell msgs >> throwError e)
  (Right x, msgs) -> do
    m' <- f x
    case runMsg m' of
      (Left _  , _    ) -> return m'
      (Right x', msgs') -> return (tell (msgs ++ msgs') >> return x')

-- | Convert a 'MsgMonad' to a 'MsgMonadIO'
dropIO :: MsgMonad a -> MsgMonadIO a
dropIO m = case runMsg m of
  (Left  e, msgs) -> tell msgs >> throwError e
  (Right x, msgs) -> tell msgs >> return x

-- | Abort the computation with an error message
failWith :: (MonadError a m, Error a) => String -> m b
failWith = throwError . strMsg

-- | Abort the computation with an error message at a certain position
failWithAt :: (MonadError WarnMsg m) => Position -> String -> m a
failWithAt p = throwError . WarnMsg (Just p)

-- | Report a warning message
warnMessage :: (MonadWriter [WarnMsg] m) => String -> m ()
warnMessage s = tell [WarnMsg Nothing s]

-- | Report a warning message for a given position
warnMessageAt :: (MonadWriter [WarnMsg] m) => Position -> String -> m ()
warnMessageAt p s  = tell [WarnMsg (Just p) s]