hermit-0.4.0.0: src/HERMIT/Plugin.hs
{-# LANGUAGE KindSignatures, GADTs, FlexibleContexts, GeneralizedNewtypeDeriving, LambdaCase #-}
module HERMIT.Plugin
( -- * The HERMIT Plugin
hermitPlugin
-- ** Running translations
, query
, run
-- ** Using the shell
, interactive
, display
, setPretty
, setPrettyOptions
-- ** Active modifiers
, at
, phase
, after
, before
, until
, allPhases
, firstPhase
, lastPhase
-- ** Knobs and Dials
, getPhaseInfo
, modifyCLS
-- ** Types
, defPS
, HPM
, hpmToIO
) where
import Control.Applicative
import Control.Arrow
import Control.Concurrent.STM
import Control.Monad.Error hiding (guard)
import Control.Monad.Operational
import Control.Monad.State hiding (guard)
import Data.Default
import Data.Monoid
import qualified Data.Map as M
import HERMIT.Dictionary
import HERMIT.External hiding (Query, Shell)
import HERMIT.Kernel.Scoped
import HERMIT.Context
import HERMIT.Kure
import HERMIT.GHC hiding (singleton, liftIO, display, (<>))
import qualified HERMIT.GHC as GHC
import HERMIT.Monad
import HERMIT.Plugin.Builder
import qualified HERMIT.Plugin.Display as Display
import HERMIT.Plugin.Renderer
import HERMIT.Plugin.Types
import HERMIT.PrettyPrinter.Common
import qualified HERMIT.PrettyPrinter.Clean as Clean
import HERMIT.Shell.Command
import HERMIT.Shell.Types (clm)
import Prelude hiding (until)
import System.Console.Haskeline (defaultBehavior)
hermitPlugin :: ([CommandLineOption] -> HPM ()) -> Plugin
hermitPlugin f = buildPlugin $ \ phaseInfo -> runHPM phaseInfo . f
defPS :: SAST -> ScopedKernel -> PhaseInfo -> IO PluginState
defPS initSAST kernel phaseInfo = do
emptyTick <- liftIO $ atomically $ newTVar M.empty
return $ PluginState
{ ps_cursor = initSAST
, ps_pretty = Clean.ppCoreTC
, ps_pretty_opts = def { po_width = 80 }
, ps_render = unicodeConsole
, ps_running_script = False
, ps_tick = emptyTick
, ps_corelint = False
, ps_diffonly = False
, ps_failhard = False
, ps_kernel = kernel
, ps_phase = phaseInfo
}
data HPMInst :: * -> * where
Shell :: [External] -> [CommandLineOption] -> HPMInst ()
Guard :: (PhaseInfo -> Bool) -> HPM () -> HPMInst ()
Focus :: (Injection ModGuts g, Walker HermitC g) => TranslateH g LocalPathH -> HPM a -> HPMInst a
RR :: (Injection ModGuts g, Walker HermitC g) => RewriteH g -> HPMInst ()
Query :: (Injection ModGuts g, Walker HermitC g) => TranslateH g a -> HPMInst a
newtype HPM a = HPM { unHPM :: ProgramT HPMInst PluginM a }
deriving (Functor, Applicative, Monad, MonadIO)
runHPM :: PhaseInfo -> HPM () -> ModGuts -> CoreM ModGuts
runHPM phaseInfo pass = scopedKernel $ \ kernel initSAST -> do
ps <- defPS initSAST kernel phaseInfo
(r,st) <- hpmToIO ps pass
either (\case PAbort -> abortS kernel
PResume sast -> resumeS kernel sast
PError err -> putStrLn err >> abortS kernel)
(\ _ -> resumeS kernel $ ps_cursor st) r
hpmToIO :: PluginState -> HPM a -> IO (Either PException a, PluginState)
hpmToIO initState = runPluginT initState . eval . unHPM
eval :: ProgramT HPMInst PluginM a -> PluginM a
eval comp = do
(kernel, env) <- gets $ ps_kernel &&& mkKernelEnv
v <- viewT comp
case v of
Return x -> return x
RR rr :>>= k -> runS (applyS kernel rr env) >>= eval . k
Query tr :>>= k -> runK (queryS kernel tr env) >>= eval . k
Shell es os :>>= k -> do
-- We want to discard the current focus, open the shell at
-- the top level, then restore the current focus.
paths <- resetScoping env
clm (commandLine os defaultBehavior es)
_ <- resetScoping env
restoreScoping env paths
eval $ k ()
Guard p (HPM m) :>>= k -> gets (p . ps_phase) >>= \ b -> when b (eval m) >>= eval . k
Focus tp (HPM m) :>>= k -> do
p <- runK (queryS kernel tp env) -- run the pathfinding translation
runS $ beginScopeS kernel -- remember the current path
runS $ modPathS kernel (<> p) env -- modify the current path
r <- eval m -- run the focused computation
runS $ endScopeS kernel -- endscope on it, so we go back to where we started
eval $ k r
------------------------- Shell-related helpers --------------------------------------
resetScoping :: HermitMEnv -> PluginM [PathH]
resetScoping env = do
kernel <- gets ps_kernel
paths <- runK $ pathS kernel
replicateM_ (length paths - 1) $ runS $ endScopeS kernel
-- modPathS commonly fails here because the path is unchanged, so throw away failures
catchM (runS $ modPathS kernel (const mempty) env) (const (return ()))
return paths
restoreScoping :: HermitMEnv -> [PathH] -> PluginM ()
restoreScoping _ [] = return ()
restoreScoping env (h:t) = do
kernel <- gets ps_kernel
let go p [] = restore p
go p (p':ps) = restore p >> runS (beginScopeS kernel) >> go p' ps
-- modPathS commonly fails here because the path is unchanged, so throw away failures
restore p = catchM (runS $ modPathS kernel (<> pathToSnocPath p) env)
(const (return ()))
go h t
-- | Run a kernel function on the current SAST
runK :: (SAST -> PluginM a) -> PluginM a
runK f = gets ps_cursor >>= f
-- | Run a kernel function on the current SAST and update ps_cursor
runS :: (SAST -> PluginM SAST) -> PluginM ()
runS f = do
sast <- runK f
modify $ \st -> st { ps_cursor = sast }
interactive :: [External] -> [CommandLineOption] -> HPM ()
interactive es os = HPM . singleton $ Shell (externals ++ es) os
run :: RewriteH CoreTC -> HPM ()
run = HPM . singleton . RR
query :: (Injection GHC.ModGuts g, Walker HermitC g) => TranslateH g a -> HPM a
query = HPM . singleton . Query
----------------------------- guards ------------------------------
guard :: (PhaseInfo -> Bool) -> HPM () -> HPM ()
guard p = HPM . singleton . Guard p
at :: TranslateH CoreTC LocalPathH -> HPM a -> HPM a
at tp = HPM . singleton . Focus tp
phase :: Int -> HPM () -> HPM ()
phase n = guard ((n ==) . phaseNum)
after :: CorePass -> HPM () -> HPM ()
after cp = guard (\phaseInfo -> case phasesDone phaseInfo of
[] -> False
xs -> last xs == cp)
before :: CorePass -> HPM () -> HPM ()
before cp = guard (\phaseInfo -> case phasesLeft phaseInfo of
(x:_) | cp == x -> True
_ -> False)
until :: CorePass -> HPM () -> HPM ()
until cp = guard ((cp `elem`) . phasesLeft)
allPhases :: HPM () -> HPM ()
allPhases = guard (const True)
firstPhase :: HPM () -> HPM ()
firstPhase = guard (null . phasesDone)
lastPhase :: HPM () -> HPM ()
lastPhase = guard (null . phasesLeft)
----------------------------- other ------------------------------
getPhaseInfo :: HPM PhaseInfo
getPhaseInfo = HPM $ lift $ gets ps_phase
display :: HPM ()
display = HPM $ lift $ Display.display Nothing
modifyCLS :: (PluginState -> PluginState) -> HPM ()
modifyCLS = HPM . modify
setPretty :: PrettyH CoreTC -> HPM ()
setPretty pp = modifyCLS $ \s -> s { ps_pretty = pp }
setPrettyOptions :: PrettyOptions -> HPM ()
setPrettyOptions po = modifyCLS $ \s -> s { ps_pretty_opts = po }