{-# LANGUAGE GeneralizedNewtypeDeriving #-} module HsDev.Symbols.Resolve ( ResolveM(..), ResolvedTree, ResolvedModule(..), scopeModule, exportsModule, resolvedTopScope, resolve, resolveOne, resolveModule, exported, resolveImport, mergeImported ) where import Control.Applicative import Control.Arrow import Control.Monad.Reader import Control.Monad.State import Data.Foldable (Foldable) import Data.Function (on) import Data.List (sortBy, groupBy, find) import Data.Map (Map) import qualified Data.Map as M import Data.Maybe (fromMaybe, maybeToList) import Data.Monoid (mconcat, mappend) import Data.Ord (comparing) import Data.String (fromString) import Data.Text (Text) import Data.Traversable (Traversable, traverse) import HsDev.Database import HsDev.Project import HsDev.Symbols import HsDev.Symbols.Util -- | Resolve monad uses existing @Database@ and @ResolvedTree@ as state. newtype ResolveM a = ResolveM { runResolveM :: ReaderT Database (State ResolvedTree) a } deriving (Functor, Applicative, Monad, MonadState ResolvedTree, MonadReader Database) -- | Tree of resolved modules type ResolvedTree = Map ModuleId ResolvedModule -- | Module with declarations bringed to scope and with exported declarations data ResolvedModule = ResolvedModule { resolvedModule :: Module, resolvedScope :: [Declaration], resolvedExports :: [Declaration] } -- | Make @Module@ with scope declarations scopeModule :: ResolvedModule -> Module scopeModule r = (resolvedModule r) { moduleDeclarations = resolvedScope r } -- | Make @Module@ with exported only declarations exportsModule :: ResolvedModule -> Module exportsModule r = (resolvedModule r) { moduleDeclarations = resolvedExports r } -- | Get top-level scope resolvedTopScope :: ResolvedModule -> [Declaration] resolvedTopScope = filter isTop . resolvedScope where isTop :: Declaration -> Bool isTop = any (not . importIsQualified) . fromMaybe [] . declarationImported -- | Resolve modules, function is not IO, so all file names must be canonicalized resolve :: (Traversable t, Foldable t) => Database -> t Module -> t ResolvedModule resolve db = flip evalState M.empty . flip runReaderT db . runResolveM . traverse resolveModule -- | Resolve one module resolveOne :: Database -> Module -> ResolvedModule resolveOne db = fromMaybe (error "Resolve: impossible happened") . resolve db . Just -- | Resolve module resolveModule :: Module -> ResolveM ResolvedModule resolveModule m = gets (M.lookup $ moduleId m) >>= maybe resolveModule' return where resolveModule' = save $ case moduleLocation m of CabalModule {} -> return ResolvedModule { resolvedModule = m, resolvedScope = moduleDeclarations m, resolvedExports = moduleDeclarations m } _ -> do scope' <- liftM ((thisDecls ++) . mergeImported . concat) . mapM (resolveImport m) . (import_ (fromString "Prelude") :) . moduleImports $ m let exports' = concatMap (exported scope') . fromMaybe [] . moduleExports $ m return $ ResolvedModule m (sortDeclarations scope') (sortDeclarations exports') thisDecls :: [Declaration] thisDecls = map selfImport $ moduleDeclarations m selfImport :: Declaration -> Declaration selfImport d = d { declarationImported = Just [import_ $ moduleName m] } save :: ResolveM ResolvedModule -> ResolveM ResolvedModule save act = do rm <- act modify $ M.insert (moduleId (resolvedModule rm)) rm return rm -- | Select declarations exported with @Export@ exported :: [Declaration] -> Export -> [Declaration] exported ds (ExportName q n) = maybeToList $ find isExported ds where isExported :: Declaration -> Bool isExported decl' = declarationName decl' == n && case q of Nothing -> any (not . importIsQualified) $ fromMaybe [] $ declarationImported decl' Just q' -> any ((== q') . importName) $ fromMaybe [] $ declarationImported decl' exported ds (ExportModule m) = filter (any (unqualBy m) . fromMaybe [] . declarationImported) ds where unqualBy :: Text -> Import -> Bool unqualBy m' i = importName i == m' && not (importIsQualified i) -- | Bring declarations into scope resolveImport :: Module -> Import -> ResolveM [Declaration] resolveImport m i = liftM (map $ setImport i) resolveImport' where resolveImport' :: ResolveM [Declaration] resolveImport' = do ms <- case moduleLocation m of FileModule file proj -> do db <- ask let proj' = proj >>= refineProject db case proj' of Nothing -> selectImport i [ inFile $ importedModulePath (moduleName m) file (importModuleName i), byCabal] Just p -> selectImport i [ inProject p, inDepsOf' file p] CabalModule cabal _ _ -> selectImport i [inCabal cabal] ModuleSource _ -> selectImport i [byCabal] liftM (filterImportList . concatMap resolvedExports) $ mapM resolveModule ms setImport :: Import -> Declaration -> Declaration setImport i' d' = d' { declarationImported = Just [i'] `mappend` declarationImported d' } selectImport :: Import -> [ModuleId -> Bool] -> ResolveM [Module] selectImport i' fs = liftM (selectModules select') ask where select' md = moduleName md == importModuleName i' && any ($ moduleId md) (byImport i' : fs) filterImportList :: [Declaration] -> [Declaration] filterImportList = case importList i of Nothing -> id Just il -> filter (passImportList il . declarationName) byImport :: Import -> ModuleId -> Bool byImport i' m' = importModuleName i' == moduleIdName m' deps f p = maybe [] infoDepends $ fileTarget p f inDepsOf' f p m' = any (`inPackage` m') (deps f p) -- | Merge imported declarations mergeImported :: [Declaration] -> [Declaration] mergeImported = map merge' . groupBy ((==) `on` declId) . sortBy (comparing declId) where declId :: Declaration -> (Text, Maybe ModuleId) declId = declarationName &&& declarationDefined merge' :: [Declaration] -> Declaration merge' [] = error "mergeImported: impossible" merge' ds@(d:_) = d { declarationImported = mconcat $ map declarationImported ds }