{-# 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) -- liftIO $ warningM "HaRe" $ "initGhcSession:loadModuleGraphGhc done" 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 setCurrentDirectory $ gfromJust "getCabalAllTargets" (cradleCabalDir cradle) (libs,exes,tests,benches) <- liftIO $ cabalAllTargets cabal setCurrentDirectory currentDir let -- libs' = addCurrentDir libs exes' = addCurrentDir exes tests' = addCurrentDir tests benches' = addCurrentDir benches addCurrentDir ts = map (\t -> combine currentDir 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 -- ---------------------------------------------------------------------