HaRe-0.7.1.3: src/Language/Haskell/Refact/Utils/Monad.hs
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Language.Haskell.Refact.Utils.Monad
( ParseResult
, VerboseLevel(..)
, RefactSettings(..)
, RefactState(..)
, RefactModule(..)
, TargetModule
, RefactStashId(..)
, RefactFlags(..)
, StateStorage(..)
-- GHC monad stuff
, RefactGhc
, runRefactGhc
, getRefacSettings
, defaultSettings
, logSettings
, initGhcSession
, loadModuleGraphGhc
, ensureTargetLoaded
, canonicalizeGraph
, logm
) where
import qualified GHC as GHC
import qualified GHC.Paths as GHC
import qualified GhcMonad as GHC
import qualified MonadUtils as GHC
import Control.Monad.State
import Data.List
import Data.Time.Clock
-- import Data.Tree
import Exception
import Language.Haskell.GhcMod
import Language.Haskell.GhcMod.Internal
-- import Language.Haskell.Refact.Utils.GhcVersionSpecific
import Language.Haskell.Refact.Utils.TokenUtilsTypes
import Language.Haskell.Refact.Utils.TypeSyn
import System.Directory
import System.FilePath.Posix
import System.Log.Logger
import qualified Control.Monad.IO.Class as MU
-- ---------------------------------------------------------------------
data VerboseLevel = Debug | Normal | Off
deriving (Eq,Show)
data RefactSettings = RefSet
{ rsetGhcOpts :: ![String]
, rsetImportPaths :: ![FilePath]
, rsetExpandSplice :: Bool
, rsetLineSeparator :: LineSeparator
, rsetMainFile :: Maybe [FilePath]
, rsetCheckTokenUtilsInvariant :: !Bool
, rsetVerboseLevel :: !VerboseLevel
, rsetEnabledTargets :: (Bool,Bool,Bool,Bool)
} deriving (Show)
deriving instance Show LineSeparator
defaultSettings :: RefactSettings
defaultSettings = RefSet
{ rsetGhcOpts = []
, rsetImportPaths = []
, rsetExpandSplice = False
, rsetLineSeparator = LineSeparator "\0"
, rsetMainFile = Nothing
, rsetCheckTokenUtilsInvariant = False
, rsetVerboseLevel = Normal
-- , rsetEnabledTargets = (True,False,True,False)
, rsetEnabledTargets = (True,True,True,True)
}
logSettings :: RefactSettings
logSettings = defaultSettings { rsetVerboseLevel = Debug }
-- ---------------------------------------------------------------------
data RefactStashId = Stash !String deriving (Show,Eq,Ord)
data RefactModule = RefMod
{ rsTypecheckedMod :: !GHC.TypecheckedModule
, rsOrigTokenStream :: ![PosToken] -- ^Original Token stream for the current module
, rsTokenCache :: !TokenCache -- ^Token stream for the current module, maybe modified, in SrcSpan tree form
, rsStreamModified :: !Bool -- ^current module has updated the token stream
}
data RefactFlags = RefFlags
{ rsDone :: !Bool -- ^Current traversal has already made a change
}
-- | State for refactoring a single file. Holds/hides the token
-- stream, which gets updated transparently at key points.
data RefactState = RefSt
{ rsSettings :: !RefactSettings -- ^Session level settings
, rsUniqState :: !Int -- ^ Current Unique creator value, incremented every time it is used
, rsFlags :: !RefactFlags -- ^ Flags for controlling generic traversals
, rsStorage :: !StateStorage -- ^Temporary storage of values
-- while refactoring takes place
, rsGraph :: [TargetGraph]
, rsModuleGraph :: [([FilePath],GHC.ModuleGraph)]
, rsCurrentTarget :: Maybe [FilePath]
, rsModule :: !(Maybe RefactModule) -- ^The current module being refactored
}
type TargetModule = ([FilePath], GHC.ModSummary)
type TargetGraph = ([FilePath],[(Maybe FilePath, GHC.ModSummary)])
-- |Result of parsing a Haskell source file. It is simply the
-- TypeCheckedModule produced by GHC.
type ParseResult = GHC.TypecheckedModule
-- |Provide some temporary storage while the refactoring is taking
-- place
data StateStorage = StorageNone
| StorageBind (GHC.LHsBind GHC.Name)
| StorageSig (GHC.LSig GHC.Name)
instance Show StateStorage where
show StorageNone = "StorageNone"
show (StorageBind _bind) = "(StorageBind " {- ++ (showGhc bind) -} ++ ")"
show (StorageSig _sig) = "(StorageSig " {- ++ (showGhc sig) -} ++ ")"
-- ---------------------------------------------------------------------
-- StateT and GhcT stack
type RefactGhc a = GHC.GhcT (StateT RefactState IO) a
instance (MU.MonadIO (GHC.GhcT (StateT RefactState IO))) where
liftIO = GHC.liftIO
instance GHC.MonadIO (StateT RefactState IO) where
liftIO f = MU.liftIO f
instance ExceptionMonad m => ExceptionMonad (StateT s m) where
gcatch f h = StateT $ \s -> gcatch (runStateT f s) (\e -> runStateT (h e) s)
gblock = mapStateT gblock
gunblock = mapStateT gunblock
instance (MonadState RefactState (GHC.GhcT (StateT RefactState IO))) where
get = lift get
put = lift . put
-- state = lift . state
instance (MonadTrans GHC.GhcT) where
lift = GHC.liftGhcT
instance (MonadPlus m,Functor m,GHC.MonadIO m,ExceptionMonad m) => MonadPlus (GHC.GhcT m) where
mzero = GHC.GhcT $ \_s -> mzero
x `mplus` y = GHC.GhcT $ \_s -> (GHC.runGhcT (Just GHC.libdir) x) `mplus` (GHC.runGhcT (Just GHC.libdir) y)
-- ---------------------------------------------------------------------
-- | Initialise the GHC session, when starting a refactoring.
-- This should never be called directly.
initGhcSession :: Cradle -> [FilePath] -> RefactGhc ()
initGhcSession cradle importDirs = do
settings <- getRefacSettings
let ghcOptsDirs =
case importDirs of
[] -> (rsetGhcOpts settings)
_ -> ("-i" ++ (intercalate ":" importDirs)):(rsetGhcOpts settings)
let opt = Options {
outputStyle = PlainStyle
, hlintOpts = []
, ghcOpts = ghcOptsDirs
, operators = False
, detailed = False
, qualified = False
, expandSplice = False
, lineSeparator = rsetLineSeparator settings
, packageId = Nothing -- TODO: work this through if needed
}
(_readLog,mcabal) <- initializeFlagsWithCradle opt cradle (options settings) True
case mcabal of
Just cabal -> do
-- targets <- liftIO $ cabalAllTargets cabal
targets <- liftIO $ getCabalAllTargets cradle cabal
-- liftIO $ warningM "HaRe" $ "initGhcSession:targets=" ++ show targets
logm $ "initGhcSession:targets=" ++ show targets
-- TODO: Cannot load multiple main modules, must try to load
-- each main module and retrieve its module graph, and then
-- set the targets to this superset.
let targets' = getEnabledTargets settings targets
case targets' of
([],[]) -> return ()
(libTgts,exeTgts) -> do
-- liftIO $ warningM "HaRe" $ "initGhcSession:tgts=" ++ (show (libTgts,exeTgts))
logm $ "initGhcSession:(libTgts,exeTgts)=" ++ (show (libTgts,exeTgts))
-- setTargetFiles tgts
-- void $ GHC.load GHC.LoadAllTargets
mapM_ loadModuleGraphGhc $ map (\t -> Just [t]) exeTgts
-- Load the library last, most likely in context
case libTgts of
[] -> return ()
_ -> loadModuleGraphGhc (Just libTgts)
-- moduleGraph <- gets rsModuleGraph
-- logm $ "initGhcSession:rsModuleGraph=" ++ (show moduleGraph)
Nothing -> do
let maybeMainFile = rsetMainFile settings
loadModuleGraphGhc maybeMainFile
return()
-- graph <- gets rsGraph
-- liftIO $ warningM "HaRe" $ "initGhcSession:graph=" ++ show graph
return ()
where
options opt
| rsetExpandSplice opt = "-w:" : rsetGhcOpts opt
| otherwise = "-Wall" : rsetGhcOpts opt
-- ---------------------------------------------------------------------
-- getCabalAllTargets :: Cradle -> PackageDescription -> IO ([FilePath],[FilePath],[FilePath],[FilePath])
getCabalAllTargets cradle cabal = do
currentDir <- getCurrentDirectory
let cabalDir = gfromJust "getCabalAllTargets" (cradleCabalDir cradle)
setCurrentDirectory cabalDir
(libs,exes,tests,benches) <- liftIO $ cabalAllTargets cabal
setCurrentDirectory currentDir
let -- libs' = addCurrentDir libs
exes' = addCabalDir exes
tests' = addCabalDir tests
benches' = addCabalDir benches
addCabalDir ts = map (\t -> combine cabalDir t) ts
return (libs,exes',tests',benches')
-- ---------------------------------------------------------------------
-- | Load a module graph into the GHC session, starting from main
loadModuleGraphGhc ::
Maybe [FilePath] -> RefactGhc ()
loadModuleGraphGhc maybeTargetFiles = do
-- currentDir <- liftIO getCurrentDirectory
-- liftIO $ warningM "HaRe" $ "loadModuleGraphGhc:maybeTargetFiles=" ++ show (maybeTargetFiles,currentDir)
case maybeTargetFiles of
Just targetFiles -> do
loadTarget targetFiles
-- setTargetFiles [targetFile]
-- void $ GHC.load GHC.LoadAllTargets
graph <- GHC.getModuleGraph
cgraph <- liftIO $ canonicalizeGraph graph
settings <- get
put $ settings { rsGraph = (rsGraph settings) ++ [(targetFiles,cgraph)]
, rsModuleGraph = (rsModuleGraph settings) ++ [(targetFiles,graph)]
, rsCurrentTarget = maybeTargetFiles
}
-- logm $ "loadModuleGraphGhc:cgraph=" ++ show (map fst cgraph)
-- logm $ "loadModuleGraphGhc:cgraph=" ++ showGhc graph
return ()
Nothing -> return ()
return ()
-- ---------------------------------------------------------------------
loadTarget :: [FilePath] -> RefactGhc ()
loadTarget targetFiles = do
setTargetFiles targetFiles
void $ GHC.load GHC.LoadAllTargets
-- ---------------------------------------------------------------------
-- | Make sure the given file is the currently loaded target, and load
-- it if not. Assumes that all the module graphs had been generated
-- before, so these are not updated.
ensureTargetLoaded :: TargetModule -> RefactGhc GHC.ModSummary
ensureTargetLoaded (target,modSum) = do
settings <- get
let currentTarget = rsCurrentTarget settings
if currentTarget == Just target
then return modSum
else do
loadTarget target
put $ settings { rsCurrentTarget = Just target}
graph <- GHC.getModuleGraph
let newModSum = filter (\ms -> GHC.ms_mod modSum == GHC.ms_mod ms) graph
return $ ghead "ensureTargetLoaded" newModSum
-- ---------------------------------------------------------------------
canonicalizeGraph ::
[GHC.ModSummary] -> IO [(Maybe (FilePath), GHC.ModSummary)]
canonicalizeGraph graph = do
let mm = map (\m -> (GHC.ml_hs_file $ GHC.ms_location m, m)) graph
canon ((Just fp),m) = do
fp' <- canonicalizePath fp
return $ (Just fp',m)
canon (Nothing,m) = return (Nothing,m)
mm' <- mapM canon mm
return mm'
-- ---------------------------------------------------------------------
runRefactGhc ::
RefactGhc a -> RefactState -> IO (a, RefactState)
runRefactGhc comp initState = do
runStateT (GHC.runGhcT (Just GHC.libdir) comp) initState
getRefacSettings :: RefactGhc RefactSettings
getRefacSettings = do
s <- get
return (rsSettings s)
-- ---------------------------------------------------------------------
getEnabledTargets :: RefactSettings -> ([FilePath],[FilePath],[FilePath],[FilePath]) -> ([FilePath],[FilePath])
getEnabledTargets settings (libt,exet,testt,bencht) = (targetsLib,targetsExe)
where
(libEnabled, exeEnabled, testEnabled, benchEnabled) = rsetEnabledTargets settings
targetsLib = on libEnabled libt
targetsExe = on exeEnabled exet
++ on testEnabled testt
++ on benchEnabled bencht
on flag xs = if flag then xs else []
-- ---------------------------------------------------------------------
logm :: String -> RefactGhc ()
logm string = do
settings <- getRefacSettings
let loggingOn = (rsetVerboseLevel settings == Debug)
-- || (rsetVerboseLevel settings == Normal)
when loggingOn $ do
-- ts <- liftIO timeStamp
-- liftIO $ warningM "HaRe" (ts ++ ":" ++ string)
liftIO $ warningM "HaRe" (string)
return ()
timeStamp :: IO String
timeStamp = do
k <- getCurrentTime
return (show k)
-- ---------------------------------------------------------------------
instance Show GHC.ModSummary where
show m = show $ GHC.ms_mod m
instance Show GHC.Module where
show m = GHC.moduleNameString $ GHC.moduleName m
-- ---------------------------------------------------------------------