free-theorems-0.2: src/Language/Haskell/FreeTheorems/Frontend/Error.hs
-- | Provides error handling functions for checking parser output.
-- The functions and data types of this module are mostly tiny, little
-- helpers used by all parser modules.
module Language.Haskell.FreeTheorems.Frontend.Error where
import Control.Monad (foldM)
import Control.Monad.Error (Error(..), throwError)
import Control.Monad.Writer (Writer, runWriter, tell)
import Data.List (intersperse)
import Text.PrettyPrint (Doc, empty, text, fsep, ($$), nest)
import Language.Haskell.FreeTheorems.Syntax
(Declaration, getDeclarationName, unpackIdent)
-- | A wrapper type for a @Writer@ which stores pretty-printable documents along
-- with checked values.
type Checked a = Writer [Doc] a
-- | A wrapper type for @Writer@ which stores pretty-printable documents along
-- with parsed values.
-- This type is provided as standard return type for parsers.
type Parsed a = Writer [Doc] a
-- | The error type is just a synonym for @Either@ where errors are represented
-- by a pretty-printable @Doc@.
type ErrorOr a = Either Doc a
-- needed to make 'ErrorOr' a monad
instance Error Doc where
noMsg = empty
strMsg s = text s
-- | A wrapper function for @runWriter@.
runChecks :: Checked a -> (a, [Doc])
runChecks = runWriter
-- | Applies a checking function (the first argument) element-wise to a list of
-- values (the second argument). The result list contains only those elements
-- for which the checking function does not yield an error.
foldChecks :: (a -> ErrorOr b) -> [a] -> Checked [a]
foldChecks check = foldM doCheck []
where
doCheck xs x =
case getError (check x) of
Nothing -> return (xs ++ [x])
Just e -> tell [e] >> return xs
-- | Checks if the argument contains an error.
isError :: ErrorOr a -> Bool
isError = either (const True) (const False)
-- | Returns the error message stored in the argument or @Nothing@ if there is
-- no error message in the argument.
getError :: ErrorOr a -> Maybe Doc
getError = either Just (const Nothing)
-- | If the first argument is True, then the second argument is taken as an
-- error message. Otherwise () is returned as a non-error message.
errorIf :: Bool -> Doc -> ErrorOr ()
errorIf False = return . const ()
errorIf True = throwError
-- | Transforms a string into a pretty-printed document by splitting the string
-- into words and forming a pretty paragraph of all words.
pp :: String -> Doc
pp = fsep . map text . words
-- | Checks a declaration for errors.
-- If the second argument is an error, this function extends the error
-- message to make clear it belongs to a declaration.
inDecl :: Declaration -> ErrorOr a -> ErrorOr a
inDecl d e = case getError e of
Nothing -> e
Just doc -> throwError $
pp ("In the declaration of "
++ unpackIdent (getDeclarationName d) ++ ":")
$$ nest 2 doc
-- | Used to extend error messages by a list of items violating a certain rule.
violating :: String -> [String] -> String
violating name xs =
let text = if length xs == 1
then " The following " ++ name ++ " violates this rule: "
else " The following " ++ name ++ "s violate this rule: "
in text ++ (concat . intersperse ", " $ xs)