{-# LANGUAGE CPP, UnicodeSyntax, GeneralizedNewtypeDeriving, ScopedTypeVariables, FlexibleInstances, MultiParamTypeClasses, UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module HsDev.Tools.Ghc.MGhc (
Session(..), sessionKey, sessionData,
SessionState(..), sessionActive, sessionMap,
MGhcT(..), runMGhcT, liftGhc,
currentSession, getSessionData, setSessionData, hasSession, findSession, findSessionBy, saveSession,
initSession, newSession,
switchSession, switchSession_,
deleteSession, restoreSession, usingSession, tempSession
) where
import Control.Lens
import Control.Monad.Fail as Fail
import Control.Monad.Morph
import Control.Monad.Catch
import Control.Monad.Reader
import Control.Monad.State
import Data.Default as Def
import Data.IORef
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
import Data.Maybe (fromMaybe, isJust)
import System.Log.Simple.Monad (MonadLog(..))
import HsDev.Tools.Ghc.Compat (cleanTemps)
import DynFlags
import Exception hiding (catch, mask, uninterruptibleMask, bracket, finally)
import GHC
import GHCi
import GhcMonad hiding (Session(..))
import qualified GhcMonad (Session(..))
import HscTypes
import Outputable
data Session s d = Session {
_sessionKey :: s,
_sessionData :: d }
deriving (Eq, Ord, Read, Show)
sessionKey :: Lens' (Session s d) s
sessionKey = lens g s where
g = _sessionKey
s sess k = sess { _sessionKey = k }
sessionData :: Lens' (Session s d) d
sessionData = lens g s where
g = _sessionData
s sess dat = sess { _sessionData = dat }
data SessionState s d = SessionState {
_sessionActive :: Maybe (Session s d),
_sessionMap :: Map s (HscEnv, d) }
instance Default (SessionState s d) where
def = SessionState Nothing M.empty
sessionActive :: Lens' (SessionState s d) (Maybe (Session s d))
sessionActive = lens g s where
g = _sessionActive
s st nm = st { _sessionActive = nm }
sessionMap :: Lens' (SessionState s d) (Map s (HscEnv, d))
sessionMap = lens g s where
g = _sessionMap
s st m = st { _sessionMap = m }
instance ExceptionMonad m => ExceptionMonad (StateT s m) where
gcatch act onErr = StateT $ \st -> gcatch (runStateT act st) (\e -> runStateT (onErr e) st)
gmask f = StateT $ gmask . f' where
f' st' act' = runStateT (f act) st' where
act st = StateT $ act' . runStateT st
instance ExceptionMonad m => ExceptionMonad (ReaderT r m) where
gcatch act onErr = ReaderT $ \v -> gcatch (runReaderT act v) (\e -> runReaderT (onErr e) v)
gmask f = ReaderT $ gmask . f' where
f' v' act' = runReaderT (f act) v' where
act v = ReaderT $ act' . runReaderT v
newtype MGhcT s d m a = MGhcT { unMGhcT :: GhcT (ReaderT (Maybe FilePath) (StateT (SessionState s d) m)) a }
deriving (Functor, Applicative, Monad, MonadFail, MonadIO, ExceptionMonad, HasDynFlags, GhcMonad, MonadState (SessionState s d), MonadReader (Maybe FilePath), MonadThrow, MonadCatch, MonadMask, MonadLog)
instance MonadTrans GhcT where
lift = liftGhcT
instance MFunctor GhcT where
hoist fn = GhcT . (fn .) . unGhcT
instance MonadFail m => MonadFail (GhcT m) where
fail = lift . Fail.fail
instance MonadState st m => MonadState st (GhcT m) where
get = lift get
put = lift . put
state = lift . state
instance MonadReader r m => MonadReader r (GhcT m) where
ask = lift ask
local f act = GhcT $ local f . unGhcT act
instance MonadThrow m => MonadThrow (GhcT m) where
throwM = lift . throwM
instance MonadCatch m => MonadCatch (GhcT m) where
catch act onError = GhcT $ \sess -> catch (unGhcT act sess) (flip unGhcT sess . onError)
instance MonadMask m => MonadMask (GhcT m) where
mask f = GhcT $ \s -> mask $ \g -> unGhcT (f $ q g) s where
q g' act = GhcT $ g' . unGhcT act
uninterruptibleMask f = GhcT $ \s -> uninterruptibleMask $ \g -> unGhcT (f $ q g) s where
q g' act = GhcT $ g' . unGhcT act
#if MIN_VERSION_exceptions(0,10,0)
generalBracket acq rel act = GhcT $ \r -> generalBracket
(unGhcT acq r)
(\res exitCase -> case exitCase of
ExitCaseSuccess v -> unGhcT (rel res (ExitCaseSuccess v)) r
ExitCaseException e -> unGhcT (rel res (ExitCaseException e)) r
ExitCaseAbort -> unGhcT (rel res ExitCaseAbort) r)
(\res -> unGhcT (act res) r)
#elif MIN_VERSION_exceptions(0,9,0)
generalBracket acq rel clean act = GhcT $ \r -> generalBracket
(unGhcT acq r)
(\res -> unGhcT (rel res) r)
(\res e -> unGhcT (clean res e) r)
(\res -> unGhcT (act res) r)
#endif
runMGhcT :: (MonadIO m, ExceptionMonad m, Ord s, Monoid d) => Maybe FilePath -> MGhcT s d m a -> m a
runMGhcT lib act = do
ref <- liftIO $ newIORef (panic "empty session")
let
session = GhcMonad.Session ref
flip evalStateT Def.def $ flip runReaderT lib $ flip unGhcT session $ unMGhcT $ act `gfinally` cleanup
where
cleanup :: (MonadIO m, ExceptionMonad m, Ord s, Monoid d) => MGhcT s d m ()
cleanup = do
void saveSession
sessions <- gets (M.elems . view sessionMap)
liftIO $ mapM_ (cleanupSession . view _1) sessions
modify (set sessionMap M.empty)
liftGhc :: MonadIO m => Ghc a -> MGhcT s d m a
liftGhc (Ghc act) = MGhcT $ GhcT $ liftIO . act
currentSession :: MonadIO m => MGhcT s d m (Maybe (Session s d))
currentSession = gets (view sessionActive)
getSessionData :: MonadIO m => MGhcT s d m (Maybe d)
getSessionData = gets (preview (sessionActive . _Just . sessionData))
setSessionData :: MonadIO m => d -> MGhcT s d m ()
setSessionData sdata = modify (set (sessionActive . _Just . sessionData) sdata)
hasSession :: (MonadIO m, Ord s) => s -> MGhcT s d m Bool
hasSession key = do
msess <- findSession key
return $ isJust msess
findSession :: (MonadIO m, Ord s) => s -> MGhcT s d m (Maybe (Session s d))
findSession key = do
sdata <- gets (preview (sessionMap . ix key . _2))
return $ fmap (Session key) sdata
findSessionBy :: MonadIO m => (s -> Bool) -> MGhcT s d m [Session s d]
findSessionBy p = do
sessions <- gets (M.toList . view sessionMap)
return [Session key sdata | (key, (_, sdata)) <- sessions, p key]
saveSession :: (MonadIO m, ExceptionMonad m, Ord s) => MGhcT s d m (Maybe (Session s d))
saveSession = do
msess <- currentSession
case msess of
Just (Session key' dat') -> do
sess <- getSession
modify (set (sessionMap . at key') (Just (sess, dat')))
Nothing -> return ()
return msess
initSession :: (MonadIO m, ExceptionMonad m, Ord s) => MGhcT s d m ()
initSession = do
lib <- ask
initGhcMonad lib
void saveSession
activateSession :: (MonadIO m, ExceptionMonad m, Ord s, Monoid d) => s -> MGhcT s d m (Maybe HscEnv)
activateSession key = do
void saveSession
sdata <- gets (view (sessionMap . ix key . _2))
modify (set sessionActive $ Just (Session key sdata))
gets (preview (sessionMap . ix key . _1))
newSession :: (MonadIO m, ExceptionMonad m, Ord s, Monoid d) => s -> MGhcT s d m ()
newSession key = do
msess <- activateSession key
maybe (return ()) (liftIO . cleanupSession) msess
initSession
switchSession :: (MonadIO m, ExceptionMonad m, Ord s, Monoid d) => s -> MGhcT s d m Bool
switchSession key = do
msess <- activateSession key
case msess of
Nothing -> initSession >> return True
Just sess -> setSession sess >> return False
switchSession_ :: (MonadIO m, ExceptionMonad m, Ord s, Monoid d) => s -> Maybe (MGhcT s d m ()) -> MGhcT s d m ()
switchSession_ key f = do
new <- switchSession key
when new $ fromMaybe (return ()) f
deleteSession :: (MonadIO m, ExceptionMonad m, Ord s, Monoid d) => s -> MGhcT s d m ()
deleteSession key = do
cur <- saveSession
when (preview (_Just . sessionKey) cur == Just key) $
modify (set sessionActive Nothing)
msess <- gets (preview (sessionMap . ix key . _1))
modify (set (sessionMap . at key) Nothing)
case msess of
Nothing -> return ()
Just sess -> liftIO $ cleanupSession sess
restoreSession :: (MonadIO m, MonadMask m, ExceptionMonad m, Ord s, Monoid d) => MGhcT s d m a -> MGhcT s d m a
restoreSession act = bracket saveSession (maybe (return ()) (void . switchSession . view sessionKey)) $ const act
usingSession :: (MonadIO m, MonadMask m, ExceptionMonad m, Ord s, Monoid d) => s -> MGhcT s d m a -> MGhcT s d m a
usingSession key act = restoreSession $ do
void $ switchSession key
act
tempSession :: (MonadIO m, MonadMask m, ExceptionMonad m, Ord s, Monoid d) => s -> MGhcT s d m a -> MGhcT s d m a
tempSession key act = do
exist' <- hasSession key
usingSession key act `finally` unless exist' (deleteSession key)
cleanupSession :: HscEnv -> IO ()
cleanupSession env = do
cleanTemps df
stopIServ env
where
df = hsc_dflags env