module Agda.Interaction.Imports where
import Prelude hiding (catch)
import Control.Monad.Error
import Control.Monad.State
import qualified Data.Map as Map
import qualified Data.List as List
import qualified Data.Set as Set
import qualified Data.ByteString.Lazy as BS
import Data.Generics
import Data.List
import Data.Map (Map)
import System.Directory
import System.Time
import Control.OldException
import qualified System.IO.UTF8 as UTF8
import System.FilePath (isAbsolute)
import Agda.Syntax.Position
import qualified Agda.Syntax.Concrete as C
import Agda.Syntax.Abstract.Name
import Agda.Syntax.Parser
import Agda.Syntax.Scope.Base
import Agda.Syntax.Scope.Monad
import Agda.Syntax.Translation.ConcreteToAbstract
import Agda.Syntax.Internal
import Agda.Termination.TermCheck
import Agda.TypeChecking.Reduce
import Agda.TypeChecking.Monad
import Agda.TypeChecking.Monad.Builtin
import Agda.TypeChecking.Serialise
import Agda.TypeChecking.Primitive
import Agda.TypeChecker
import Agda.Interaction.Options
import Agda.Interaction.Highlighting.Precise (HighlightingInfo)
import Agda.Interaction.Highlighting.Generate
import Agda.Interaction.Highlighting.Emacs
import Agda.Interaction.Highlighting.Vim
import qualified Agda.Interaction.Highlighting.Range as R
import Agda.Utils.FileName
import Agda.Utils.Monad
import Agda.Utils.IO
import Agda.Utils.Impossible
#include "../undefined.h"
toIFile :: FilePath -> FilePath
toIFile = setExtension ".agdai"
mergeInterface :: Interface -> TCM ()
mergeInterface i = do
let sig = iSignature i
builtin = Map.toList $ iBuiltin i
prim = [ x | (_,Prim x) <- builtin ]
bi = Map.fromList [ (x,Builtin t) | (x,Builtin t) <- builtin ]
bs <- getBuiltinThings
reportSLn "import.iface.merge" 10 $ "Merging interface"
reportSLn "import.iface.merge" 20 $
" Current builtins " ++ show (Map.keys bs) ++ "\n" ++
" New builtins " ++ show (Map.keys bi)
case Map.toList $ Map.intersection bs bi of
[] -> return ()
(b, Builtin x):_ -> typeError $ DuplicateBuiltinBinding b x x
(_, Prim{}):_ -> __IMPOSSIBLE__
modify $ \st -> st { stImports = unionSignatures [stImports st, sig]
, stImportedBuiltins = stImportedBuiltins st `Map.union` bi
}
reportSLn "import.iface.merge" 20 $
" Rebinding primitives " ++ show prim
prim <- Map.fromList <$> mapM rebind prim
modify $ \st -> st { stImportedBuiltins = stImportedBuiltins st `Map.union` prim
}
where
rebind x = do
PrimImpl _ pf <- lookupPrimitiveFunction x
return (x, Prim pf)
addImportedThings :: Signature -> BuiltinThings PrimFun -> TCM ()
addImportedThings isig ibuiltin =
modify $ \st -> st
{ stImports = unionSignatures [stImports st, isig]
, stImportedBuiltins = Map.union (stImportedBuiltins st) ibuiltin
}
data FileType = SourceFile | InterfaceFile
findFile :: FileType -> ModuleName -> TCM FilePath
findFile ft m = do
let x = mnameToConcrete m
dirs <- getIncludeDirs
let files = [ dir ++ [slash] ++ file
| dir <- dirs
, file <- map (C.moduleNameToFileName x) exts
]
files' <- liftIO $ filterM doesFileExist files
files' <- liftIO $ nubFiles files'
case files' of
[] -> typeError $ FileNotFound m files
file:_ -> return file
where
exts = case ft of
SourceFile -> [".agda", ".lagda"]
InterfaceFile -> [".agdai"]
scopeCheckImport :: ModuleName -> TCM (ModuleName, Map ModuleName Scope)
scopeCheckImport x = do
reportSLn "import.scope" 5 $ "Scope checking " ++ show x
visited <- Map.keys <$> getVisitedModules
reportSLn "import.scope" 10 $ " visited: " ++ show visited
(i,t) <- getInterface x
addImport x
return (iModuleName i `withRangesOfQ` mnameToConcrete x, iScope i)
alreadyVisited :: ModuleName -> TCM (Interface, ClockTime) -> TCM (Interface, ClockTime)
alreadyVisited x getIface = do
mm <- getVisitedModule x
case mm of
Just it -> do
reportSLn "import.visit" 10 $ " Already visited " ++ show x
return it
Nothing -> do
reportSLn "import.visit" 5 $ " Getting interface for " ++ show x
(i, t) <- getIface
reportSLn "import.visit" 5 $ " Now we've looked at " ++ show x
visitModule x i t
return (i, t)
getInterface :: ModuleName -> TCM (Interface, ClockTime)
getInterface x = alreadyVisited x $ addImportCycleCheck x $ do
file <- findFile SourceFile x
reportSLn "import.iface" 10 $ " Check for cycle"
checkForImportCycle
uptodate <- ifM ignoreInterfaces
(return False)
(liftIO $ toIFile file `isNewerThan` file)
reportSLn "import.iface" 5 $ " " ++ show x ++ " is " ++ (if uptodate then "" else "not ") ++ "up-to-date."
(i,t) <- if uptodate
then skip x file
else typeCheck file
visited <- isVisited x
reportSLn "import.iface" 5 $ if visited then " We've been here. Don't merge."
else " New module. Let's check it out."
unless visited $ mergeInterface i
storeDecodedModule x i t
return (i,t)
where
skip x file = do
let ifile = toIFile file
t <- liftIO $ getModificationTime ifile
mm <- getDecodedModule x
mi <- case mm of
Just (im, tm) ->
if tm < t
then do dropDecodedModule x
reportSLn "import.iface" 5 $ " file is newer, re-reading " ++ ifile
liftIO $ readInterface ifile
else do reportSLn "import.iface" 5 $ " using stored version of " ++ ifile
return (Just im)
Nothing ->
do reportSLn "import.iface" 5 $ " no stored version, reading " ++ ifile
liftIO $ readInterface ifile
case mi of
Nothing -> do
reportSLn "import.iface" 5 $ " bad interface, re-type checking"
typeCheck file
Just i -> do
reportSLn "import.iface" 5 $ " imports: " ++ show (iImportedModules i)
ts <- map snd <$> mapM getInterface (iImportedModules i)
if any (> t) ts
then do
typeCheck file
else do
reportSLn "" 1 $ "Skipping " ++ show x ++ " (" ++ ifile ++ ")."
return (i, t)
typeCheck file = do
ms <- getImportPath
vs <- getVisitedModules
ds <- getDecodedModules
opts <- commandLineOptions
trace <- getTrace
isig <- getImportedSignature
ibuiltin <- gets stImportedBuiltins
r <- liftIO $ runTCM $
createInterface opts trace ms vs ds
isig ibuiltin (Just x) file False
case r of
Left err -> throwError err
Right (_, Warnings termErrs@(_:_) []) -> do
typeError $ TerminationCheckFailed termErrs
Right (_, Warnings _ _) -> __IMPOSSIBLE__
Right (_, Success vs ds i isig ibuiltin) -> do
let ifile = toIFile file
t <- liftIO $ ifM (doesFileExist ifile)
(getModificationTime ifile)
getClockTime
setVisitedModules vs
setDecodedModules ds
addImportedThings isig ibuiltin
return (i, t)
readInterface :: FilePath -> IO (Maybe Interface)
readInterface file = do
(s, close) <- readBinaryFile' file
do i <- decode s
let n = BS.length s
() <- when (n == n) $ return ()
close
i `seq` return $ Just i
`catch` \e -> close >> handler e
`catch` handler
where
handler e = case e of
ErrorCall _ -> return Nothing
IOException e -> do
UTF8.putStrLn $ "IO exception: " ++ show e
return Nothing
_ -> throwIO e
writeInterface :: FilePath -> Interface -> IO ()
writeInterface file i = do
encodeFile file i
`catch` \e -> do
UTF8.putStrLn $ "failed to write interface " ++ file ++ " : " ++ show e
removeFile file
return ()
data CreateInterfaceResult
= Success { cirVisited :: VisitedModules
, cirDecoded :: DecodedModules
, cirInterface :: Interface
, cirSignature :: Signature
, cirBuiltin :: BuiltinThings PrimFun
}
| Warnings { terminationProblems :: [([QName], [R.Range])]
, unsolvedMetaVariables :: [Range]
}
createInterface
:: CommandLineOptions
-> CallTrace
-> [ModuleName]
-> VisitedModules
-> DecodedModules
-> Signature
-> BuiltinThings PrimFun
-> Maybe ModuleName
-> FilePath
-> Bool
-> TCM (TopLevelInfo, CreateInterfaceResult)
createInterface opts trace path visited decoded
isig ibuiltin mname file changeDir
| not (isAbsolute file) = __IMPOSSIBLE__
| otherwise = withImportPath path $ do
setDecodedModules decoded
setTrace trace
setCommandLineOptions opts
setVisitedModules visited
reportSLn "" 1 $ "Checking " ++ (case mname of
Nothing -> file
Just m -> show m ++ " (" ++ file ++ ")") ++ "."
reportSLn "import.iface.create" 5 $ "Creating interface for " ++ show mname
reportSLn "import.iface.create" 10 $ " visited: " ++ show (Map.keys visited)
addImportedThings isig ibuiltin
(pragmas, top) <- liftIO $ parseFile' moduleParser file
when changeDir $
liftIO $ setWorkingDirectory file top
pragmas <- concat <$> concreteToAbstract_ pragmas
setOptionsFromPragmas pragmas
topLevel <- concreteToAbstract_ (TopLevel top)
termErrs <- catchError (do
checkModuleName mname file topLevel
checkDecls (topLevelDecls topLevel)
termErrs <- ifM (optTerminationCheck <$> commandLineOptions)
(termDecls $ topLevelDecls topLevel)
(return [])
mapM_ (\e -> reportSLn "term.warn.no" 2
(show (fst e) ++
" does NOT pass the termination checker."))
termErrs
return termErrs
) (\e -> do
whenM (optGenerateEmacsFile <$> commandLineOptions) $ do
writeEmacsFile =<<
generateSyntaxInfo file TypeCheckingNotDone topLevel []
throwError e)
syntaxInfo <- generateSyntaxInfo file TypeCheckingDone
topLevel termErrs
whenM (optGenerateEmacsFile <$> commandLineOptions) $
writeEmacsFile syntaxInfo
whenM (optGenerateVimFile <$> commandLineOptions) $
withScope_ (insideScope topLevel) $ generateVimFile file
unsolvedMetas <- List.nub <$> (mapM getMetaRange =<< getOpenMetas)
case unsolvedMetas of
[] -> return ()
_ -> do
unsolvedOK <- optAllowUnsolved <$> commandLineOptions
unless (unsolvedOK && path == []) $ do
typeError $ UnsolvedMetas unsolvedMetas
setScope $ outsideScope topLevel
reportSLn "scope.top" 50 $ "SCOPE " ++ show (insideScope topLevel)
let ok = null termErrs && null unsolvedMetas
(,) topLevel <$> if ok then do
i <- buildInterface (topLevelModuleName topLevel) syntaxInfo
isig <- getImportedSignature
vs <- getVisitedModules
ds <- getDecodedModules
ibuiltin <- gets stImportedBuiltins
liftIO $ writeInterface (toIFile file) i
modify (\s -> s { stCurrentModule =
Just (topLevelModuleName topLevel, i) })
return (Success vs ds i isig ibuiltin)
else
return (Warnings termErrs unsolvedMetas)
buildInterface :: ModuleName
-> HighlightingInfo
-> TCM Interface
buildInterface m syntaxInfo = do
reportSLn "import.iface" 5 "Building interface..."
scope' <- getScope
let scope = scope' { scopeCurrent = m }
sig <- getSignature
builtin <- gets stLocalBuiltins
ms <- getImports
hsImps <- getHaskellImports
let builtin' = Map.mapWithKey (\x b -> fmap (const x) b) builtin
reportSLn "import.iface" 7 " instantiating all meta variables"
i <- instantiateFull $ Interface
{ iImportedModules = Set.toList ms
, iModuleName = m
, iScope = publicModules scope
, iSignature = sig
, iBuiltin = builtin'
, iHaskellImports = hsImps
, iHighlighting = syntaxInfo
}
reportSLn "import.iface" 7 " interface complete"
return i
setWorkingDirectory :: FilePath -> [C.Declaration] -> IO ()
setWorkingDirectory _ [] = __IMPOSSIBLE__
setWorkingDirectory file xs = case last xs of
C.Module _ n _ _ -> do
absolute <- canonicalizePath file
let (path, _, _) = splitFilePath absolute
setCurrentDirectory (dropDirectory (countDots n) path)
_ -> __IMPOSSIBLE__
where
countDots (C.QName _) = 0
countDots (C.Qual _ n) = 1 + countDots n
matchFileName :: ModuleName -> FilePath -> Bool
matchFileName mname file = expected `isSuffixOf` given || literate `isSuffixOf` given
where
given = splitPath file
expected = splitPath $ C.moduleNameToFileName (mnameToConcrete mname) ".agda"
literate = splitPath $ C.moduleNameToFileName (mnameToConcrete mname) ".lagda"
checkModuleName
:: Maybe ModuleName
-> FilePath
-> TopLevelInfo
-> TCM ()
checkModuleName mname file topLevel = case mname of
Nothing -> unless (matchFileName actualName file) err
Just expectedName -> unless (actualName == expectedName) err
where
actualName = topLevelModuleName topLevel
err = typeError $ ModuleNameDoesntMatchFileName actualName
isNewerThan :: FilePath -> FilePath -> IO Bool
isNewerThan new old = do
newExist <- doesFileExist new
oldExist <- doesFileExist old
if not (newExist && oldExist)
then return newExist
else do
newT <- getModificationTime new
oldT <- getModificationTime old
return $ newT >= oldT