module Language.Haskell.Modules.ModuVerse
( ModuleInfo(..)
, moduleName
, ModuVerseState
, moduVerseInit
, ModuVerse(..)
, getNames
, getInfo
, putModule
, putModuleAnew
, findModule
, delName
, getExtensions
, modifyExtensions
, parseModule
, parseModuleMaybe
, loadModule
, unloadModule
) where
import Control.Applicative ((<$>))
import "MonadCatchIO-mtl" Control.Monad.CatchIO as IO (catch, MonadCatchIO, throw)
import Control.Monad.Trans (liftIO, MonadIO)
import Data.Map as Map (delete, empty, insert, keys, lookup, Map)
import Data.Maybe (fromMaybe)
import Data.Set as Set (fromList, Set)
import qualified Language.Haskell.Exts.Annotated as A (Module(..), ModuleHead(..), ModuleName(..), parseFileWithComments)
import Language.Haskell.Exts.Comments (Comment(..))
import Language.Haskell.Exts.Extension (Extension)
import qualified Language.Haskell.Exts.Parser as Exts (defaultParseMode, fromParseResult, ParseMode(extensions, parseFilename), ParseResult)
import Language.Haskell.Exts.SrcLoc (SrcSpanInfo)
import Language.Haskell.Exts.Syntax as S (ModuleName(..))
import Language.Haskell.Modules.SourceDirs (modulePathBase, pathKey, PathKey(..), pathKeyMaybe, SourceDirs(..))
import Language.Haskell.Modules.Util.QIO (MonadVerbosity, qLnPutStr, quietly)
import System.IO.Error (isDoesNotExistError, isUserError)
deriving instance Ord Comment
data ModuleInfo
= ModuleInfo
{ module_ :: A.Module SrcSpanInfo
, text_ :: String
, comments_ :: [Comment]
, key_ :: PathKey }
deriving (Eq, Ord, Show)
moduleName :: ModuleInfo -> S.ModuleName
moduleName (ModuleInfo (A.Module _ mh _ _ _) _ _ _) =
S.ModuleName $ maybe "Main" (\ (A.ModuleHead _ (A.ModuleName _ s) _ _) -> s) mh
moduleName (ModuleInfo m _ _ _) = error $ "Unsupported Module: " ++ show m
data ModuVerseState =
ModuVerseState { moduleNames_ :: Maybe (Map S.ModuleName ModuleInfo)
, moduleInfo_ :: Map PathKey ModuleInfo
, extensions_ :: [Extension]
, sourceDirs_ :: [FilePath]
} deriving (Eq, Ord, Show)
moduVerseInit :: ModuVerseState
moduVerseInit =
ModuVerseState { moduleNames_ = Nothing
, moduleInfo_ = Map.empty
, extensions_ = Exts.extensions Exts.defaultParseMode
, sourceDirs_ = ["."] }
getNames :: ModuVerse m => m (Set S.ModuleName)
getNames = getModuVerse >>= return . Set.fromList . keys . fromMaybe (error "No modules in ModuVerse, use putModule") . moduleNames_
getInfo :: ModuVerse m => S.ModuleName -> m (Maybe ModuleInfo)
getInfo name = getModuVerse >>= return . Map.lookup name . fromMaybe (error "No modules in ModuVerse, use putModule") . moduleNames_
putName :: ModuVerse m => S.ModuleName -> ModuleInfo -> m ()
putName name info = modifyModuVerse (\ s -> s {moduleNames_ = Just (Map.insert name info (fromMaybe Map.empty (moduleNames_ s)))})
putModule :: (ModuVerse m, MonadVerbosity m) => String -> m ()
putModule name = pathKey (modulePathBase "hs" (S.ModuleName name)) >>= parseModule >>= putName (S.ModuleName name)
putModuleAnew :: (ModuVerse m, MonadVerbosity m) => String -> m ()
putModuleAnew name = pathKey (modulePathBase "hs" (S.ModuleName name)) >>= loadModule >>= putName (S.ModuleName name)
findModule :: (ModuVerse m, MonadVerbosity m) => String -> m (Maybe ModuleInfo)
findModule name = pathKeyMaybe (modulePathBase "hs" (S.ModuleName name)) >>= parseModuleMaybe
delName :: ModuVerse m => S.ModuleName -> m ()
delName name = modifyModuVerse (\ s -> s { moduleNames_ = Just (Map.delete name (fromMaybe Map.empty (moduleNames_ s)))
, moduleInfo_ = Map.empty })
class (MonadIO m, MonadCatchIO m, Functor m) => ModuVerse m where
getModuVerse :: m ModuVerseState
modifyModuVerse :: (ModuVerseState -> ModuVerseState) -> m ()
getExtensions :: ModuVerse m => m [Extension]
getExtensions = getModuVerse >>= return . extensions_
modifyExtensions :: ModuVerse m => ([Extension] -> [Extension]) -> m ()
modifyExtensions f = modifyModuVerse (\ s -> s {extensions_ = f (extensions_ s)})
instance ModuVerse m => SourceDirs m where
putDirs xs = modifyModuVerse (\ s -> s {sourceDirs_ = xs})
getDirs = getModuVerse >>= return . sourceDirs_
parseModule :: (ModuVerse m, MonadVerbosity m) => PathKey -> m ModuleInfo
parseModule key = parseModuleMaybe (Just key) >>= maybe (error $ "parseModule - not found: " ++ show key) return
parseModuleMaybe :: (ModuVerse m, MonadVerbosity m) => Maybe PathKey -> m (Maybe ModuleInfo)
parseModuleMaybe Nothing = return Nothing
parseModuleMaybe (Just key) =
(look >>= load) `IO.catch` (\ (e :: IOError) -> if isDoesNotExistError e || isUserError e then return Nothing else throw e)
where
look =
do verse <- getModuVerse
return $ Map.lookup key (moduleInfo_ verse)
load (Just x) = return (Just x)
load Nothing = Just <$> loadModule key
loadModule :: (ModuVerse m, MonadVerbosity m) => PathKey -> m ModuleInfo
loadModule key =
do text <- liftIO $ readFile (unPathKey key)
quietly $ qLnPutStr ("parsing " ++ unPathKey key)
(parsed, comments) <- parseFileWithComments (unPathKey key) >>= return . Exts.fromParseResult
modifyModuVerse (\ x -> x {moduleInfo_ = Map.insert key (ModuleInfo parsed text comments key) (moduleInfo_ x)})
return (ModuleInfo parsed text comments key)
unloadModule :: (ModuVerse m, MonadVerbosity m) => PathKey -> m ()
unloadModule key =
modifyModuVerse (\ x -> x {moduleInfo_ = Map.delete key (moduleInfo_ x)})
parseFileWithComments :: ModuVerse m => FilePath -> m (Exts.ParseResult (A.Module SrcSpanInfo, [Comment]))
parseFileWithComments path =
do exts <- getExtensions
liftIO (A.parseFileWithComments (Exts.defaultParseMode {Exts.extensions = exts, Exts.parseFilename = path}) path)