hint-0.2: src/Language/Haskell/Interpreter/GHC/Base.hs
module Language.Haskell.Interpreter.GHC.Base
where
import Control.Monad.Trans ( MonadIO(liftIO) )
import Control.Monad.Reader ( ReaderT, ask, runReaderT )
import Control.Monad.Error ( Error(..), MonadError(..), ErrorT, runErrorT )
import Control.Exception ( throwDyn )
import Control.Concurrent.MVar ( MVar, newMVar, withMVar )
import Data.IORef ( IORef, newIORef,
modifyIORef, atomicModifyIORef )
import Data.Typeable ( Typeable )
import qualified GHC
import qualified Outputable as GHC.O
import qualified SrcLoc as GHC.S
import qualified ErrUtils as GHC.E
import qualified Language.Haskell.Interpreter.GHC.Compat as Compat
-- autogenerated by Cabal script
import Language.Haskell.Interpreter.GHC.LibDir ( ghc_libdir )
newtype Interpreter a =
Interpreter{unInterpreter :: ReaderT SessionState
(ErrorT InterpreterError
IO) a}
deriving (Typeable, Functor, Monad, MonadIO)
instance MonadError InterpreterError Interpreter where
throwError = Interpreter . throwError
catchError (Interpreter m) catchE = Interpreter $ m `catchError` (\e ->
unInterpreter $ catchE e)
data InterpreterError = UnknownError String
| WontCompile [GhcError]
| NotAllowed String
deriving (Show, Typeable)
instance Error InterpreterError where
noMsg = UnknownError ""
strMsg = UnknownError
-- I'm assuming operations on a ghcSession are not thread-safe. Besides, we need
-- to be sure that messages captured by the log handler correspond to a single
-- operation. Hence, we put the whole state on an MVar, and synchronize on it
newtype InterpreterSession =
InterpreterSession {sessionState :: MVar SessionState}
data SessionState = SessionState{ghcSession :: GHC.Session,
ghcErrListRef :: IORef [GhcError],
ghcErrLogger :: GhcErrLogger}
-- When intercepting errors reported by GHC, we only get a GHC.E.Message
-- and a GHC.S.SrcSpan. The latter holds the file name and the location
-- of the error. However, SrcSpan is abstract and it doesn't provide
-- functions to retrieve the line and column of the error... we can only
-- generate a string with this information. Maybe I can parse this string
-- later.... (sigh)
data GhcError = GhcError{errMsg :: String} deriving Show
mkGhcError :: GHC.S.SrcSpan -> GHC.O.PprStyle -> GHC.E.Message -> GhcError
mkGhcError src_span style msg = GhcError{errMsg = niceErrMsg}
where niceErrMsg = GHC.O.showSDoc . GHC.O.withPprStyle style $
GHC.E.mkLocMessage src_span msg
type GhcErrLogger = GHC.Severity
-> GHC.S.SrcSpan
-> GHC.O.PprStyle
-> GHC.E.Message
-> IO ()
-- ================= Creating a session =========================
-- | Builds a new session using the (hopefully) correct path to the GHC in use.
-- (the path is determined at build time of the package)
newSession :: IO InterpreterSession
newSession = newSessionUsing ghc_libdir
-- | Builds a new session, given the path to a GHC installation
-- (e.g. \/usr\/local\/lib\/ghc-6.6).
newSessionUsing :: FilePath -> IO InterpreterSession
newSessionUsing ghc_root =
do
ghc_session <- Compat.newSession ghc_root
--
ghc_err_list_ref <- newIORef []
let log_handler = mkLogHandler ghc_err_list_ref
--
let session_state = SessionState{ghcSession = ghc_session,
ghcErrListRef = ghc_err_list_ref,
ghcErrLogger = log_handler}
--
-- set HscTarget to HscInterpreted (default is HsAsm!).
-- setSessionDynFlags loads info on packages availables; this call
-- is mandatory!
-- also set a custom log handler, to intercept error messages :S
dflags <- GHC.getSessionDynFlags ghc_session
let myFlags = dflags{GHC.hscTarget = GHC.HscInterpreted,
GHC.log_action = log_handler}
GHC.setSessionDynFlags ghc_session myFlags
--
return . InterpreterSession =<< newMVar session_state
mkLogHandler :: IORef [GhcError] -> GhcErrLogger
mkLogHandler r _ src style msg = modifyIORef r (errorEntry :)
where errorEntry = mkGhcError src style msg
-- ================= Executing the interpreter ==================
-- | Executes the interpreter using a given session. This is a thread-safe
-- operation, if the InterpreterSession is in-use, the call will block until
-- the other one finishes.
--
-- In case of error, it will throw a dynamic InterpreterError exception.
withSession :: InterpreterSession -> Interpreter a -> IO a
withSession s i = withMVar (sessionState s) $ \ss ->
do err_or_res <- runErrorT . flip runReaderT ss $ unInterpreter i
either throwDyn return err_or_res
-- ================ Handling the interpreter state =================
fromSessionState :: (SessionState -> a) -> Interpreter a
fromSessionState f = Interpreter $ fmap f ask
-- modifies the session state and returns the old value
modifySessionState :: Show a
=> (SessionState -> IORef a)
-> (a -> a)
-> Interpreter a
modifySessionState target f =
do
ref <- fromSessionState target
old_val <- liftIO $ atomicModifyIORef ref (\a -> (f a, a))
return old_val