{-# LANGUAGE GeneralizedNewtypeDeriving, DefaultSignatures, FlexibleContexts, CPP, MultiParamTypeClasses #-} {-# OPTIONS_GHC -fno-warn-warnings-deprecations #-} ----------------------------------------------------------------------------- -- | -- Module : Language.Glambda.Monad -- Copyright : (C) 2015 Richard Eisenberg -- License : BSD-style (see LICENSE) -- Maintainer : Richard Eisenberg (rae@cs.brynmawr.edu) -- Stability : experimental -- -- The Glam monad, allowing for pretty-printed output to the user, failing -- with an error message, and tracking global variables. -- ---------------------------------------------------------------------------- module Language.Glambda.Monad ( -- * The 'Glam' monad Glam, runGlam, prompt, quit, -- * The 'GlamE' monad GlamE, runGlamE, issueError, eitherToGlamE, -- * General functions over both glamorous monads GlamM(..), ) where import Language.Glambda.Globals import Language.Glambda.Util import System.Console.Haskeline import Text.PrettyPrint.ANSI.Leijen import Control.Monad.Trans.Maybe import Control.Monad.Except import Control.Monad.Reader import Control.Monad.State import System.IO #if __GLASGOW_HASKELL__ < 709 import Control.Applicative #endif -- | A monad giving Haskeline-like interaction, access to 'Globals', -- and the ability to abort with 'mzero'. newtype Glam a = Glam { unGlam :: MaybeT (StateT Globals (InputT IO)) a } deriving (Monad, Functor, Applicative, MonadState Globals, MonadIO) -- | Like the 'Glam' monad, but also supporting error messages via 'Doc's newtype GlamE a = GlamE { unGlamE :: ExceptT Doc Glam a } deriving (Monad, Functor, Applicative, MonadError Doc) instance MonadReader Globals GlamE where ask = GlamE get local f thing_inside = GlamE $ do old_globals <- get put (f old_globals) result <- unGlamE thing_inside put old_globals return result -- | Class for the two glamorous monads class GlamM m where -- | Print a 'Doc' without a newline at the end printDoc :: Doc -> m () -- | Print a 'Doc' with a newline printLine :: Doc -> m () instance GlamM Glam where printDoc = Glam . liftIO . displayIO stdout . toSimpleDoc printLine = Glam . liftIO . displayIO stdout . toSimpleDoc . (<> hardline) instance GlamM GlamE where printDoc = GlamE . lift . printDoc printLine = GlamE . lift . printLine -- | Prompt the user for input, returning a string if one is entered. -- Like 'getInputLine'. prompt :: String -> Glam (Maybe String) prompt = Glam . lift . lift . getInputLine -- | Abort the 'Glam' monad quit :: Glam a quit = do printLine (text "Good-bye.") Glam mzero -- | Abort the computation with an error issueError :: Doc -> GlamE a issueError = GlamE . throwError -- | Hoist an 'Either' into 'GlamE' eitherToGlamE :: Either String a -> GlamE a eitherToGlamE (Left err) = issueError (text err) eitherToGlamE (Right x) = return x -- | Run a 'Glam' computation runGlam :: Glam () -> InputT IO () runGlam thing_inside = ignore $ flip evalStateT emptyGlobals $ runMaybeT $ unGlam thing_inside -- | Run a 'GlamE' computation runGlamE :: GlamE a -> Glam (Either Doc a) runGlamE thing_inside = runExceptT $ unGlamE thing_inside