{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE NamedFieldPuns #-} -- | a monad that collects warnings, outputs, etc, module LintWriter where import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.IO as T import Data.Maybe (isJust, mapMaybe) import Control.Monad.Writer import Control.Monad.Trans.Maybe -- | Levels of errors and warnings, collectively called -- "Hints" until I can think of some better name data Level = Warning | Suggestion | Info | Forbidden | Error deriving Show -- | a hint comes with an explanation (and a level) data Hint = Hint { hintLevel :: Level , hintMsg :: Text } deriving Show -- shorter constructor hint level msg = Hint { hintLevel = level, hintMsg = msg } -- | a monad to collect hints. If it yields Left, then the -- map is flawed in some fundamental way which prevented us -- from getting any hints at all except whatever broke it type LintWriter a = WriterT [Hint] (Either Hint) a type LintResult a = Either Hint (a, [Hint]) -- | write a hint into the LintWriter monad lint :: Level -> Text -> LintWriter () lint level = tell . (: []) . hint level warn = lint Warning info = lint Info forbid = lint Forbidden suggest = lint Suggestion complain = lint Error -- TODO: all these functions should probably also just operate on LintWriter -- | converts a Maybe to an Either, with a default value for Left unwrap :: b -> Maybe a -> Either b a unwrap hint maybe = case maybe of Just a -> Right a Nothing -> Left hint -- | unwrap and produce a warning if the value was Nothing unwrapWarn :: Text -> Maybe a -> Either Hint a unwrapWarn msg = unwrap $ hint Warning msg -- | same as unwrapWarn, but for booleans assertWarn :: Text -> Bool -> LintWriter () assertWarn msg cond = lift $ if cond then Right () else Left $ hint Warning msg