melf-1.3.1: src/Control/Exception/ChainedException.hs
-- |
-- Module : Control.Exception.ChainedException
-- Description : Exception that keeps the stack of error locations
-- Copyright : (c) Aleksey Makarov, 2021
-- License : BSD 3-Clause License
-- Maintainer : aleksey.makarov@gmail.com
-- Stability : experimental
-- Portability : portable
--
-- Exception that keeps the stack of error locations.
-- Look also at these:
-- https://hackage.haskell.org/package/loch-th
-- https://github.com/MaartenFaddegon/Hoed
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE RankNTypes #-}
module Control.Exception.ChainedException
( ChainedExceptionNext(..)
, ChainedException(..)
, chainedError
, chainedError'
, addContext
, addContext'
, maybeAddContext
, maybeAddContext'
, eitherAddContext'
) where
-- https://stackoverflow.com/questions/13379356/finding-the-line-number-of-a-function-in-haskell
import Control.Exception hiding (try, catch)
import Control.Monad.Catch
import Language.Haskell.TH
-- | Structure to organize the stack of exceptions with locations
data ChainedExceptionNext = Null -- ^ exception was initiated by @`chainedError`@
| Next SomeException -- ^ some context was added to @t`SomeException`@ by @`addContext`@
| NextChained ChainedException -- ^ some context was added to a @t`ChainedException`@ by @`addContext`@
-- | Exception that keeps track of error locations
data ChainedException = ChainedException
{ err :: String -- ^ description of the error
, loc :: Loc -- ^ location
, stack :: ChainedExceptionNext -- ^ stack of locations
}
formatLoc :: Loc -> String
formatLoc loc =
let
file = loc_filename loc
(line, _) = loc_start loc
in concat [file, ":", show line]
instance Show ChainedException where
show ChainedException{..} = showThis ++ case stack of
Null -> ""
NextChained ce -> " // " ++ show ce
Next e -> " // " ++ show e
where
showThis = concat [err, if null err then "" else " ", "(", formatLoc loc, ")" ]
instance Exception ChainedException
withLoc :: Q Exp -> Q Exp
withLoc f = appE f (location >>= liftLoc)
liftLoc :: Loc -> Q Exp
liftLoc Loc {..} = [| Loc loc_filename loc_package loc_module loc_start loc_end |]
--------------------------------------------------------
chainedErrorX :: MonadThrow m => Loc -> String -> m a
chainedErrorX loc s = throwM $ ChainedException s loc Null
-- | @\$chainedError@ results in a function of type
-- \'@chainedError :: MonadThrow m => String -> m a@\'.
-- It throws t`ChainedException` with its argument as error description.
chainedError :: Q Exp
chainedError = withLoc [| chainedErrorX |]
-- | @\$chainedError'@ is the same as @$`chainedError` ""@
chainedError' :: Q Exp
chainedError' = withLoc [| (`chainedErrorX` []) |]
addContextX :: MonadCatch m => Loc -> String -> m a -> m a
addContextX loc s m = m `catch` f
where
f :: MonadThrow m => SomeException -> m a
f e = throwM $ ChainedException s loc $ case fromException e of
Just ce -> NextChained ce
Nothing -> Next e
-- | @\$addContext@ results in a function of type
-- \'@addContext :: MonadCatch m => String -> m a -> m a@\'.
-- It runs the second argument and adds t`ChainedException` with its first argument
-- to the exceptions thrown from that monad.
addContext :: Q Exp
addContext = withLoc [| addContextX |]
-- | @\$addContext'@ is the same as @$addContext ""@
addContext' :: Q Exp
addContext' = withLoc [| (`addContextX` []) |]
maybeAddContextX :: MonadThrow m => Loc -> String -> Maybe a -> m a
maybeAddContextX loc s = maybe (throwM $ ChainedException s loc Null) return
-- | @\$maybeAddContext@ results in a function of type
-- \'@maybeAddContext :: MonadThrow m => String -> Maybe a -> m a@\'.
-- If its second argument is `Nothing`, it throws t`ChainedException` with its first argument,
-- else it returns the value of `Just`.
maybeAddContext :: Q Exp
maybeAddContext = withLoc [| maybeAddContextX |]
-- | @\$maybeAddContext'@ is the same as @$maybeAddContext ""@
maybeAddContext' :: Q Exp
maybeAddContext' = withLoc [| (`maybeAddContextX` []) |]
eitherAddContextX :: MonadThrow m => Loc -> Either String a -> m a
eitherAddContextX loc = either (\ s -> throwM $ ChainedException s loc Null) return
-- | @\$eitherAddContext'@ results in a function of type
-- \'@eitherAddContext' :: MonadThrow m => Either String a -> m a@\'.
-- If its argument is @`Left` e@, it throws t`ChainedException` with @e@ as error description,
-- else it returns the value of `Right`.
eitherAddContext' :: Q Exp
eitherAddContext' = withLoc [| eitherAddContextX |]