{-# LANGUAGE DisambiguateRecordFields, TypeFamilies, StandaloneDeriving, DeriveFunctor, DeriveFoldable, GeneralizedNewtypeDeriving #-} ------------------------------------------------------------------------------------- -- | -- Copyright : (c) Hans Hoglund 2012 -- License : BSD-style -- Maintainer : hans@hanshoglund.se -- Stability : experimental -- Portability : GHC -- -- ------------------------------------------------------------------------------------- module Language.Modulo.Load ( -- ** Paths ModulePath, relativePath, absolutePaths, -- *** Standard paths stdModulePaths, withStdModulePaths, -- ** Loading loadModule, loadDependencies, ) where import Control.Exception import Language.Modulo import Language.Modulo.Parse import Language.Modulo.Util import qualified Data.List.NonEmpty as NonEmpty -- We should mimic CPP behaviour or it is a bug -- We use usr/modules in place of usr/include etc. -- | -- Path where modules are stored. -- type ModulePath = FilePath -- | -- Module paths, in order of preference. stdModulePaths :: [ModulePath] stdModulePaths = ["/usr/modules", "/usr/local/modules"] -- | -- Append the standard paths to the given paths. -- -- That is, the given paths take precedence over the standards. -- withStdModulePaths :: [ModulePath] -> [ModulePath] withStdModulePaths = (++ stdModulePaths) -- | -- Converts a module name to a relative path. -- relativePath :: ModuleName -> FilePath relativePath n = concatSep "/" (getModuleNameList n) ++ ".module" -- | -- Converts a module name to a list of absolute paths, in order of preference. -- absolutePaths :: [ModulePath] -> ModuleName -> [FilePath] absolutePaths ps n = map (++ "/" ++ relativePath n) ps -- TODO detect and fail on recursive dependencies -- TODO generally more safe version of loadModule and loadDepencencies -- | -- Load the dependencies of the given module. -- -- Dependencies of the loaded modules are loaded transitively. -- This function blocks if a recursive dependency is encountered. -- loadDependencies :: [ModulePath] -> Module -> IO [Module] loadDependencies ps m = do let depNames =filterImports $ modImports m deps <- concatMapM (loadModule ps) depNames return $ m : deps -- | -- Load a module of the given name. -- -- Dependencies of the loaded modules are loaded transitively. -- This function blocks if a recursive dependency is encountered. -- loadModule :: [ModulePath] -> ModuleName -> IO [Module] loadModule ps n = do m <- unsafeLoad ps n let depNames = filterImports $ modImports m deps <- concatMapM (loadModule ps) depNames return $ m : deps where unsafeLoad :: [ModulePath] -> ModuleName -> IO Module unsafeLoad ps n = do s <- unsafeReadAny $ absolutePaths ps n let m = unsafeParse s if (modName m /= n) then (error $ "File name does not match module name: \n" ++ " Saw: `" ++ show (modName m) ++ "'\n" ++ " Expected: `" ++ show n ++ "'\n") else (return m) unsafeParse :: String -> Module unsafeParse s = case (parse s) of Left e -> error $ "Parse error: " ++ show e Right m -> m -- | -- Extract imports to load based on the import convention. -- -- This function should probably use some plugin-style system to alter the -- loading mechanism, for now non-standard imports are just ignored. -- filterImports :: [(ModuleName, Maybe String)] -> [ModuleName] filterImports = concatMap $ \imp -> case imp of (name, Nothing) -> [name] (name, Just conv) -> [] -- ignore -- | -- Attempt to read all given files, returning the contents of the first successful -- read, or failing if none could be found. unsafeReadAny :: [FilePath] -> IO String unsafeReadAny [] = error "unsafeReadAny: Empty path list" unsafeReadAny (f:fs) = do r <- try $ readFile f :: IO (Either IOException String) case r of Left e -> if (null fs) then (error $ show e) else unsafeReadAny fs Right s -> return s