{-# LANGUAGE CPP #-}
module Interfaces (loadInterfaces) where
#if __GLASGOW_HASKELL__ >= 804
import Prelude hiding ((<>))
#endif
import Control.Monad (unless)
import qualified Control.Monad.State as S (StateT, execStateT, gets, modify)
import qualified Data.Map as M (insert, member)
import Curry.Base.Ident
import Curry.Base.Monad
import Curry.Base.Position
import Curry.Base.SpanInfo ()
import Curry.Base.Pretty
import Curry.Files.PathUtils
import Curry.Syntax
import Base.Messages
import Env.Interface
import Checks.InterfaceSyntaxCheck (intfSyntaxCheck)
type IntfLoader a = S.StateT LoaderState IO a
data LoaderState = LoaderState
{ iEnv :: InterfaceEnv
, spaths :: [FilePath]
, errs :: [Message]
}
report :: [Message] -> IntfLoader ()
report msg = S.modify $ \ s -> s { errs = msg ++ errs s }
loaded :: ModuleIdent -> IntfLoader Bool
loaded m = S.gets $ \ s -> m `M.member` iEnv s
searchPaths :: IntfLoader [FilePath]
searchPaths = S.gets spaths
addInterface :: ModuleIdent -> Interface -> IntfLoader ()
addInterface m intf = S.modify $ \ s -> s { iEnv = M.insert m intf $ iEnv s }
loadInterfaces :: [FilePath]
-> Module a
-> CYIO InterfaceEnv
loadInterfaces paths (Module _ _ m _ is _) = do
res <- liftIO $ S.execStateT load (LoaderState initInterfaceEnv paths [])
if null (errs res) then ok (iEnv res) else failMessages (reverse $ errs res)
where load = mapM_ (loadInterface [m]) [(p, m') | ImportDecl p m' _ _ _ <- is]
loadInterface :: HasPosition a => [ModuleIdent] -> (a, ModuleIdent)
-> IntfLoader ()
loadInterface ctxt imp@(pp, m)
| m `elem` ctxt = report [errCyclicImport p (m : takeWhile (/= m) ctxt)]
| otherwise = do
isLoaded <- loaded m
unless isLoaded $ do
paths <- searchPaths
mbIntf <- liftIO $ lookupCurryInterface paths m
case mbIntf of
Nothing -> report [errInterfaceNotFound p m]
Just fn -> compileInterface ctxt imp fn
where p = getPosition pp
compileInterface :: HasPosition p => [ModuleIdent] -> (p, ModuleIdent) -> FilePath
-> IntfLoader ()
compileInterface ctxt (p, m) fn = do
mbSrc <- liftIO $ readModule fn
case mbSrc of
Nothing -> report [errInterfaceNotFound p m]
Just src -> case runCYMIgnWarn (parseInterface fn src) of
Left err -> report err
Right intf@(Interface n is _) ->
if m /= n
then report [errWrongInterface (first fn) m n]
else do
let (intf', intfErrs) = intfSyntaxCheck intf
mapM_ report [intfErrs]
mapM_ (loadInterface (m : ctxt)) [ (q, i) | IImportDecl q i <- is ]
addInterface m intf'
errInterfaceNotFound :: HasPosition p => p -> ModuleIdent -> Message
errInterfaceNotFound p m = posMessage p $
text "Interface for module" <+> text (moduleName m) <+> text "not found"
errWrongInterface :: HasPosition p => p -> ModuleIdent -> ModuleIdent -> Message
errWrongInterface p m n = posMessage p $
text "Expected interface for" <+> text (moduleName m)
<> comma <+> text "but found" <+> text (moduleName n)
errCyclicImport :: HasPosition p => p -> [ModuleIdent] -> Message
errCyclicImport _ [] = internalError "Interfaces.errCyclicImport: empty list"
errCyclicImport p [m] = posMessage p $
text "Recursive import for module" <+> text (moduleName m)
errCyclicImport p ms = posMessage p $
text "Cyclic import dependency between modules"
<+> hsep (punctuate comma (map text inits)) <+> text "and" <+> text lastm
where
(inits, lastm) = splitLast $ map moduleName ms
splitLast [] = internalError "Interfaces.splitLast: empty list"
splitLast (x : []) = ([] , x)
splitLast (x : y : ys) = (x : xs, z) where (xs, z) = splitLast (y : ys)