{-# LANGUAGE CPP #-}
module CurryDeps
( Source (..), flatDeps, deps, flattenDeps, sourceDeps, moduleDeps ) where
#if __GLASGOW_HASKELL__ >= 804
import Prelude hiding ((<>))
#endif
import Control.Monad (foldM)
import Data.List (isSuffixOf, nub)
import qualified Data.Map as Map (Map, empty, insert, lookup, toList)
import Curry.Base.Ident
import Curry.Base.Monad
import Curry.Base.Pretty
import Curry.Files.Filenames
import Curry.Files.PathUtils
import Curry.Syntax
( Module (..), ModulePragma (..), ImportDecl (..), parseHeader, parsePragmas
, patchModuleId, hasLanguageExtension)
import Base.Messages
import Base.SCC (scc)
import CompilerOpts (Options (..), CppOpts (..), KnownExtension (..))
import CondCompile (condCompile)
data Source
= Source FilePath [ModulePragma] [ModuleIdent]
| Interface FilePath
| Unknown
deriving (Eq, Show)
type SourceEnv = Map.Map ModuleIdent Source
flatDeps :: Options -> FilePath -> CYIO [(ModuleIdent, Source)]
flatDeps opts fn = do
sEnv <- deps opts Map.empty fn
case flattenDeps sEnv of
(env, [] ) -> ok env
(_ , errs) -> failMessages errs
deps :: Options -> SourceEnv -> FilePath -> CYIO SourceEnv
deps opts sEnv fn
| ext == icurryExt = return sEnv
| ext `elem` sourceExts = sourceDeps opts sEnv fn
| otherwise = targetDeps opts sEnv fn
where ext = takeExtension fn
targetDeps :: Options -> SourceEnv -> FilePath -> CYIO SourceEnv
targetDeps opts sEnv fn = do
mFile <- liftIO $ lookupFile [""] sourceExts fn
case mFile of
Nothing -> return $ Map.insert (mkMIdent [fn]) Unknown sEnv
Just file -> sourceDeps opts sEnv file
sourceDeps :: Options -> SourceEnv -> FilePath -> CYIO SourceEnv
sourceDeps opts sEnv fn = readHeader opts fn >>= moduleDeps opts sEnv fn
moduleDeps :: Options -> SourceEnv -> FilePath -> Module a -> CYIO SourceEnv
moduleDeps opts sEnv fn mdl@(Module _ ps m _ _ _) = case Map.lookup m sEnv of
Just _ -> return sEnv
Nothing -> do
let imps = imports opts mdl
sEnv' = Map.insert m (Source fn ps imps) sEnv
foldM (moduleIdentDeps opts) sEnv' imps
imports :: Options -> Module a -> [ModuleIdent]
imports opts mdl@(Module _ _ m _ is _) = nub $
[preludeMIdent | m /= preludeMIdent && not noImplicitPrelude]
++ [m' | ImportDecl _ m' _ _ _ <- is]
where noImplicitPrelude = NoImplicitPrelude `elem` optExtensions opts
|| mdl `hasLanguageExtension` NoImplicitPrelude
moduleIdentDeps :: Options -> SourceEnv -> ModuleIdent -> CYIO SourceEnv
moduleIdentDeps opts sEnv m = case Map.lookup m sEnv of
Just _ -> return sEnv
Nothing -> do
mFile <- liftIO $ lookupCurryModule ("." : optImportPaths opts)
(optLibraryPaths opts) m
case mFile of
Nothing -> return $ Map.insert m Unknown sEnv
Just fn
| icurryExt `isSuffixOf` fn ->
return $ Map.insert m (Interface fn) sEnv
| otherwise -> do
hdr@(Module _ _ m' _ _ _) <- readHeader opts fn
if m == m' then moduleDeps opts sEnv fn hdr
else failMessages [errWrongModule m m']
readHeader :: Options -> FilePath -> CYIO (Module ())
readHeader opts fn = do
mbFile <- liftIO $ readModule fn
case mbFile of
Nothing -> failMessages [errMissingFile fn]
Just src -> do
prgs <- liftCYM $ parsePragmas fn src
let cppOpts = optCppOpts opts
cppOpts' =
cppOpts { cppRun = cppRun cppOpts || hasLanguageExtension prgs CPP }
condC <- condCompile cppOpts' fn src
hdr <- liftCYM $ parseHeader fn condC
return $ patchModuleId fn hdr
flattenDeps :: SourceEnv -> ([(ModuleIdent, Source)], [Message])
flattenDeps = fdeps . sortDeps
where
sortDeps :: SourceEnv -> [[(ModuleIdent, Source)]]
sortDeps = scc idents imported . Map.toList
idents (m, _) = [m]
imported (_, Source _ _ ms) = ms
imported (_, _) = []
fdeps :: [[(ModuleIdent, Source)]] -> ([(ModuleIdent, Source)], [Message])
fdeps = foldr checkdep ([], [])
checkdep [] (srcs, errs) = (srcs , errs )
checkdep [src] (srcs, errs) = (src : srcs, errs )
checkdep dep (srcs, errs) = (srcs , err : errs)
where err = errCyclicImport $ map fst dep
errMissingFile :: FilePath -> Message
errMissingFile fn = message $ sep $ map text [ "Missing file:", fn ]
errWrongModule :: ModuleIdent -> ModuleIdent -> Message
errWrongModule m m' = message $ sep $
[ text "Expected module for", text (moduleName m) <> comma
, text "but found", text (moduleName m') ]
errCyclicImport :: [ModuleIdent] -> Message
errCyclicImport [] = internalError "CurryDeps.errCyclicImport: empty list"
errCyclicImport [m] = message $ sep $ map text
[ "Recursive import for module", moduleName m ]
errCyclicImport ms = message $ sep $
text "Cyclic import dependency between modules" : punctuate comma inits
++ [text "and", lastm]
where
(inits, lastm) = splitLast $ map (text . moduleName) ms
splitLast [] = internalError "CurryDeps.splitLast: empty list"
splitLast (x : []) = ([] , x)
splitLast (x : xs) = (x : ys, y) where (ys, y) = splitLast xs