module Nirum.Package.ModuleSet ( ImportError ( CircularImportError , MissingImportError , MissingModulePathError ) , ModuleSet , fromList , fromMap , keys , keysSet , length , lookup , null , toAscList , toList , toMap ) where import qualified Data.Map.Strict as M import qualified Data.Set as S import Prelude hiding (length, lookup, null) import qualified Nirum.Constructs.DeclarationSet as DS import Nirum.Constructs.Identifier (Identifier) import Nirum.Constructs.Module (Module (Module), imports) import Nirum.Constructs.ModulePath (ModulePath) import Nirum.Constructs.TypeDeclaration ( TypeDeclaration ( Import , ServiceDeclaration , TypeDeclaration ) ) data ImportError = CircularImportError [ModulePath] | MissingModulePathError ModulePath ModulePath | MissingImportError ModulePath ModulePath Identifier deriving (Eq, Ord, Show) -- | The set of 'Module' values. It can be looked up by its 'ModulePath'. newtype ModuleSet = ModuleSet (M.Map ModulePath Module) deriving (Eq, Ord, Show) fromMap :: M.Map ModulePath Module -> Either (S.Set ImportError) ModuleSet fromMap ms | S.null importErrors = Right moduleSet | otherwise = Left importErrors where moduleSet :: ModuleSet moduleSet = ModuleSet ms importErrors :: S.Set ImportError importErrors = detectImportErrors moduleSet fromList :: [(ModulePath, Module)] -> Either (S.Set ImportError) ModuleSet fromList = fromMap . M.fromList toMap :: ModuleSet -> M.Map ModulePath Module toMap (ModuleSet ms) = ms toList :: ModuleSet -> [(ModulePath, Module)] toList = M.toList . toMap toAscList :: ModuleSet -> [(ModulePath, Module)] toAscList = M.toAscList . toMap length :: ModuleSet -> Int length = M.size . toMap null :: ModuleSet -> Bool null = M.null . toMap keys :: ModuleSet -> [ModulePath] keys = M.keys . toMap keysSet :: ModuleSet -> S.Set ModulePath keysSet = M.keysSet . toMap lookup :: ModulePath -> ModuleSet -> Maybe Module lookup path = M.lookup path . toMap detectImportErrors :: ModuleSet -> S.Set ImportError detectImportErrors moduleSet = detectMissingImports moduleSet `S.union` detectCircularImports moduleSet detectMissingImports :: ModuleSet -> S.Set ImportError detectMissingImports moduleSet = S.fromList [e | (path, mod') <- toList moduleSet, e <- detect path mod'] where detect :: ModulePath -> Module -> [ImportError] detect path module' = [ e | (path', idents) <- M.toList (imports module') , e <- case lookup path' moduleSet of Nothing -> [MissingModulePathError path path'] Just (Module decls _) -> [ e | i <- S.toList idents , e <- case DS.lookup i decls of Just TypeDeclaration {} -> [] Just ServiceDeclaration {} -> [] Just Import {} -> [MissingImportError path path' i] Nothing -> [MissingImportError path path' i] ] ] detectCircularImports :: ModuleSet -> S.Set ImportError detectCircularImports (ModuleSet ms) = S.fromList [e | path <- M.keys ms, e <- detect path []] where moduleImports :: M.Map ModulePath (S.Set ModulePath) moduleImports = M.fromList [ (path, M.keysSet $ imports module') | (path, module') <- M.toList ms ] detect :: ModulePath -> [ModulePath] -> [ImportError] detect path reversedCycle | path `elem` reversedCycle = [CircularImportError $ reverse reversedCycle'] | otherwise = case M.lookup path moduleImports of Just paths -> [ e | path' <- S.toList paths , e <- detect path' reversedCycle' ] Nothing -> [] where reversedCycle' :: [ModulePath] reversedCycle' = path : reversedCycle