Commit 45b90256 authored by Michael Hanus 's avatar Michael Hanus
Browse files

currypp implementation is now downloaded from CPM

parent c74a76e1
......@@ -10,6 +10,11 @@ browser/.cpm/packages/cass-analysis-0.0.4
browser/.cpm/packages/importusage-0.0.1
browser/.cpm/packages/showflatcurry-0.0.1
currycheck/.cpm/packages/rewriting-0.0.1
currypp/.cpm/packages/cass-0.0.1
currypp/.cpm/packages/cass-analysis-0.0.4
currypp/.cpm/packages/currycheck-1.0.1
currypp/.cpm/packages/rewriting-0.0.1
currypp/.cpm/packages/verify-0.0.2
optimize/.cpm/packages/cass-0.0.1
optimize/.cpm/packages/cass-analysis-0.0.4
......
......@@ -34,9 +34,6 @@ make_analysis:
make_CASS: | make_analysis
@$(MAKE) now_$@
make_currypp: | make_analysis make_CASS make_currycheck
@$(MAKE) now_$@
make_%:
@$(MAKE) now_make_$*
......
# cass-analysis - Base libraries and implementation of program analyses for CASS
This directory contains the implementation of various
program analyses which can be used with CASS
(the Curry Analysis Server System), available in the package `cass`.
{
"name": "cass-analysis",
"version": "0.0.4",
"author": "Michael Hanus <mh@informatik.uni-kiel.de>",
"synopsis": "Libraries with various compile-time analyses for Curry",
"category": [ "Analysis" ],
"dependencies": {
},
"source": {
"git": "https://git.ps.informatik.uni-kiel.de/curry-packages/cass-analysis.git",
"tag": "$version"
}
}
------------------------------------------------------------------------------
--- Demandedness analysis:
--- checks whether functions demands a particular argument, i.e.,
--- delivers only bottom if some argument is bottom.
---
--- @author Michael Hanus
--- @version May 2013
------------------------------------------------------------------------------
module Analysis.Demandedness
where
import Analysis.Types
import FlatCurry.Types
import FlatCurry.Goodies
import List((\\),intercalate)
------------------------------------------------------------------------------
--- Data type to represent information about demanded arguments.
--- Demanded arguments are represented as a list of indices
--- for the arguments, where arguments are numbered from 1.
type DemandedArgs = [Int]
-- Show determinism information as a string.
showDemand :: AOutFormat -> DemandedArgs -> String
showDemand AText [] = "no demanded arguments"
showDemand ANote [] = ""
showDemand fmt (x:xs) =
(if fmt==AText then "demanded arguments: " else "") ++
intercalate "," (map show (x:xs))
-- Abstract demand domain.
data DemandDomain = Bot | Top
-- Least upper bound on abstract demand domain.
lub :: DemandDomain -> DemandDomain -> DemandDomain
lub Bot x = x
lub Top _ = Top
--- Demandedness analysis.
demandAnalysis :: Analysis DemandedArgs
demandAnalysis = dependencyFuncAnalysis "Demand" [1..] daFunc
-- We define the demanded arguments of some primitive prelude operations.
-- Otherwise, we analyse the right-hand sides of the rule.
daFunc :: FuncDecl -> [(QName,DemandedArgs)] -> DemandedArgs
daFunc (Func (m,f) _ _ _ rule) calledFuncs
| f `elem` prelude2s && m==prelude = [1,2]
| f `elem` prelude1s && m==prelude = [1]
| otherwise = daFuncRule calledFuncs rule
where
prelude2s = ["==","=:=","compare","<=","$#","$##","$!","$!!",
"+","-","*","div","mod","divMod","quot","rem","quotRem"]
prelude1s = ["seq","ensureNotFree","apply","cond","=:<=","negateFloat"]
-- TODO: >>= catch catchFail
daFuncRule :: [(QName,DemandedArgs)] -> Rule -> DemandedArgs
daFuncRule _ (External _) = [] -- nothing known about other externals
daFuncRule calledFuncs (Rule args rhs) =
map fst
(filter ((==Bot) . snd)
(map (\botarg -> (botarg,absEvalExpr rhs [botarg])) args))
where
-- abstract evaluation of an expression w.r.t. variables assumed to be Bot
absEvalExpr (Var i) bvs = if i `elem` bvs then Bot else Top
absEvalExpr (Lit _) _ = Top
absEvalExpr (Comb ct g es) bvs =
if ct == FuncCall
then maybe (error $ "Abstract value of " ++ show g ++ " not found!")
(\gdas -> let curargs = map (\ (i,e) -> (i,absEvalExpr e bvs))
(zip [1..] es)
cdas = gdas \\
(map fst (filter ((/=Bot) . snd) curargs))
in if null cdas then Top else Bot)
(lookup g calledFuncs)
else Top
absEvalExpr (Free _ e) bvs = absEvalExpr e bvs
absEvalExpr (Let bs e) bvs = absEvalExpr e (absEvalBindings bs bvs)
absEvalExpr (Or e1 e2) bvs = lub (absEvalExpr e1 bvs) (absEvalExpr e2 bvs)
absEvalExpr (Case _ e bs) bvs =
if absEvalExpr e bvs == Bot
then Bot
else foldr lub Bot (map absEvalBranch bs)
where absEvalBranch (Branch _ be) = absEvalExpr be bvs
absEvalExpr (Typed e _) bvs = absEvalExpr e bvs
-- could be improved with local fixpoint computation
absEvalBindings [] bvs = bvs
absEvalBindings ((i,exp) : bs) bvs =
let ival = absEvalExpr exp bvs
in if ival==Bot
then absEvalBindings bs (i:bvs)
else absEvalBindings bs bvs
prelude :: String
prelude = "Prelude"
------------------------------------------------------------------------------
------------------------------------------------------------------------------
--- Determinism analysis:
--- checks whether functions are deterministic or nondeterministic, i.e.,
--- whether its evaluation on ground argument terms might cause
--- different computation paths.
---
--- @author Michael Hanus
--- @version August 2016
------------------------------------------------------------------------------
module Analysis.Deterministic
( overlapAnalysis, showOverlap, showDet
, functionalAnalysis, showFunctional
, Deterministic(..),nondetAnalysis
, showNonDetDeps, nondetDepAnalysis, nondetDepAllAnalysis
) where
import Analysis.Types
import Char(isDigit)
import FlatCurry.Types
import FlatCurry.Goodies
import List
import Sort(sort)
------------------------------------------------------------------------------
-- The overlapping analysis can be applied to individual functions.
-- It assigns to a FlatCurry function definition a flag which is True
-- if this function is defined with overlapping left-hand sides.
overlapAnalysis :: Analysis Bool
overlapAnalysis = simpleFuncAnalysis "Overlapping" isOverlappingFunction
isOverlappingFunction :: FuncDecl -> Bool
isOverlappingFunction (Func _ _ _ _ (Rule _ e)) = orInExpr e
isOverlappingFunction (Func f _ _ _ (External _)) = f==("Prelude","?")
-- Check an expression for occurrences of OR:
orInExpr :: Expr -> Bool
orInExpr (Var _) = False
orInExpr (Lit _) = False
orInExpr (Comb _ f es) = f==(pre "?") || any orInExpr es
orInExpr (Free _ e) = orInExpr e
orInExpr (Let bs e) = any orInExpr (map snd bs) || orInExpr e
orInExpr (Or _ _) = True
orInExpr (Case _ e bs) = orInExpr e || any orInBranch bs
where orInBranch (Branch _ be) = orInExpr be
orInExpr (Typed e _) = orInExpr e
-- Show overlapping information as a string.
showOverlap :: AOutFormat -> Bool -> String
showOverlap _ True = "overlapping"
showOverlap AText False = "non-overlapping"
showOverlap ANote False = ""
------------------------------------------------------------------------------
-- The functional analysis is a global function dependency analysis.
-- It assigns to a FlatCurry function definition a flag which is True
-- if this function is purely functional defined, i.e., its definition
-- does not depend on operation containing overlapping rules or free variables.
functionalAnalysis :: Analysis Bool
functionalAnalysis = dependencyFuncAnalysis "Functional" True isFuncDefined
-- Show functionally defined information as a string.
showFunctional :: AOutFormat -> Bool -> String
showFunctional _ True = "functional"
showFunctional AText False = "defined with logic features"
showFunctional ANote False = ""
-- An operation is functionally defined if its definition is not
-- non-deterministic (no overlapping rules, no extra variables) and
-- it depends only on functionally defined operations.
isFuncDefined :: FuncDecl -> [(QName,Bool)] -> Bool
isFuncDefined func calledFuncs =
not (isNondetDefined func) && and (map snd calledFuncs)
-- Is a function f defined to be potentially non-deterministic, i.e.,
-- is the rule non-deterministic or does it contain extra variables?
isNondetDefined :: FuncDecl -> Bool
isNondetDefined (Func f _ _ _ rule) =
f `notElem` (map pre ["failed","$!!","$##","normalForm","groundNormalForm"])
-- these operations are internally defined in PAKCS with extra variables
&& isNondetRule rule
where
isNondetRule (Rule _ e) = orInExpr e || extraVarInExpr e
isNondetRule (External _) = f==("Prelude","?")
-- check an expression for occurrences of extra variables:
extraVarInExpr :: Expr -> Bool
extraVarInExpr (Var _) = False
extraVarInExpr (Lit _) = False
extraVarInExpr (Comb _ _ es) = or (map extraVarInExpr es)
extraVarInExpr (Free vars e) = (not (null vars)) || extraVarInExpr e
extraVarInExpr (Let bs e) = any extraVarInExpr (map snd bs) || extraVarInExpr e
extraVarInExpr (Or e1 e2) = extraVarInExpr e1 || extraVarInExpr e2
extraVarInExpr (Case _ e bs) = extraVarInExpr e || any extraVarInBranch bs
where extraVarInBranch (Branch _ be) = extraVarInExpr be
extraVarInExpr (Typed e _) = extraVarInExpr e
------------------------------------------------------------------------------
-- The determinism analysis is a global function dependency analysis.
-- It assigns to a function a flag which indicates whether is function
-- might be non-deterministic, i.e., might reduce in different ways
-- for given ground arguments.
-- If the non-determinism is encapsulated (set functions, AllSolutions),
-- it is classified as deterministic.
--- Data type to represent determinism information.
data Deterministic = NDet | Det
-- Show determinism information as a string.
showDet :: AOutFormat -> Deterministic -> String
showDet _ NDet = "non-deterministic"
showDet AText Det = "deterministic"
showDet ANote Det = ""
nondetAnalysis :: Analysis Deterministic
nondetAnalysis = dependencyFuncAnalysis "Deterministic" Det nondetFunc
-- An operation is non-deterministic if its definition is potentially
-- non-deterministic or it calls a non-deterministic operation
-- where the non-deterministic call is not encapsulated.
nondetFunc :: FuncDecl -> [(QName,Deterministic)] -> Deterministic
nondetFunc func@(Func _ _ _ _ rule) calledFuncs =
if isNondetDefined func || callsNDOpInRule rule
then NDet
else Det
where
callsNDOpInRule (Rule _ e) = callsNDOp e
callsNDOpInRule (External _) = False
callsNDOp (Var _) = False
callsNDOp (Lit _) = False
callsNDOp (Free _ e) = callsNDOp e
callsNDOp (Let bs e) = any callsNDOp (map snd bs) || callsNDOp e
callsNDOp (Or _ _) = True
callsNDOp (Case _ e bs) =
callsNDOp e || any (\ (Branch _ be) -> callsNDOp be) bs
callsNDOp (Typed e _) = callsNDOp e
callsNDOp (Comb _ qf@(mn,fn) es)
| mn == "SetFunctions" && take 3 fn == "set" && all isDigit (drop 3 fn)
= -- non-determinism of function (first argument) is encapsulated so that
-- its called ND functions are not relevant:
if null es then False -- this case should not occur
else any callsNDOp (tail es)
| mn == "AllSolutions" -- && fn `elem`== "getAllValues"
= -- non-determinism of argument is encapsulated so that
-- its called ND functions are not relevant:
False
| otherwise
= maybe False (==NDet) (lookup qf calledFuncs) || any callsNDOp es
------------------------------------------------------------------------------
--- Data type to represent information about non-deterministic dependencies.
--- Basically, it is the set (represented as a sorted list) of
--- all function names that are defined by overlapping rules or rules
--- containing free variables which might be called.
--- In addition, the second component is (possibly) the list of
--- functions from which this non-deterministic function is called.
--- The length of this list is limited by 'maxDepsLength' in the
--- `NonDetAllDeps` analysis or to 1 (i.e., only the direct caller is
--- stored) in the `NonDetDeps` analysis.
type NonDetDeps = [(QName,[QName])]
--- The maximal length of a call chain associated with a non-deterministic
--- operation dependency. We limit the length in order to avoid large
--- analysis times for the `NonDetAllDeps` analysis.
maxDepsLength :: Int
maxDepsLength = 10
-- Show determinism dependency information as a string.
showNonDetDeps :: AOutFormat -> NonDetDeps -> String
showNonDetDeps AText [] = "deterministic"
showNonDetDeps ANote [] = ""
showNonDetDeps ANote xs@(_:_) = intercalate " " (nub (map (snd . fst) xs))
showNonDetDeps AText xs@(_:_) =
"depends on non-det. operations: " ++
intercalate ", " (map showNDOpInfo xs)
where
showNDOpInfo (ndop,cfs) = showQName ndop ++
(if null cfs
then ""
else " (called from " ++ intercalate " -> " (map showQName cfs) ++ ")")
showQName (mn,fn) = mn++"."++fn
--- Non-deterministic dependency analysis.
--- The analysis computes for each operation the set of operations
--- with a non-deterministic definition which might be called by this
--- operation. Non-deterministic operations that are called by other
--- non-deterministic operations are ignored so that only the first
--- (w.r.t. the call sequence) non-deterministic operations are returned.
nondetDepAnalysis :: Analysis NonDetDeps
nondetDepAnalysis =
dependencyFuncAnalysis "NonDetDeps" [] (nondetDeps False)
--- Non-deterministic dependency analysis.
--- The analysis computes for each operation the set of *all* operations
--- with a non-deterministic definition which might be called by this
--- operation.
nondetDepAllAnalysis :: Analysis NonDetDeps
nondetDepAllAnalysis =
dependencyFuncAnalysis "NonDetAllDeps" [] (nondetDeps True)
-- An operation is non-deterministic if its definition is potentially
-- non-deterministic (i.e., the dependency is the operation itself)
-- or it depends on some called non-deterministic function.
nondetDeps :: Bool -> FuncDecl -> [(QName,NonDetDeps)] -> NonDetDeps
nondetDeps alldeps func@(Func f _ _ _ rule) calledFuncs =
let calledndfuncs = sort (nub (map addCaller (calledNDFuncsInRule rule)))
addCaller (ndf,cfs)
| null cfs = (ndf,[f])
| alldeps && f `notElem` cfs
&& length cfs < maxDepsLength = (ndf,f:cfs)
| otherwise = (ndf,cfs)
in if isNondetDefined func
then (f,[]) : (if alldeps then calledndfuncs else [])
else calledndfuncs
where
calledNDFuncsInRule (Rule _ e) = calledNDFuncs e
calledNDFuncsInRule (External _) = []
calledNDFuncs (Var _) = []
calledNDFuncs (Lit _) = []
calledNDFuncs (Free _ e) = calledNDFuncs e
calledNDFuncs (Let bs e) =
concatMap calledNDFuncs (map snd bs) ++ calledNDFuncs e
calledNDFuncs (Or e1 e2) = calledNDFuncs e1 ++ calledNDFuncs e2
calledNDFuncs (Case _ e bs) =
calledNDFuncs e ++ concatMap (\ (Branch _ be) -> calledNDFuncs be) bs
calledNDFuncs (Typed e _) = calledNDFuncs e
calledNDFuncs (Comb _ qf@(mn,fn) es)
| mn == "SetFunctions" && take 3 fn == "set" && all isDigit (drop 3 fn)
= -- non-determinism of function (first argument) is encapsulated so that
-- its called ND functions are not relevant:
if null es then [] -- this case should not occur
else concatMap calledNDFuncs (tail es)
| mn == "AllSolutions" -- && fn `elem`== "getAllValues"
= -- non-determinism of argument is encapsulated so that
-- its called ND functions are not relevant:
[]
| otherwise
= maybe [] id (lookup qf calledFuncs) ++ concatMap calledNDFuncs es
------------------------------------------------------------------------------
pre :: String -> QName
pre n = ("Prelude",n)
------------------------------------------------------------------------------
\ No newline at end of file
--------------------------------------------------------------------------
--- This module contains operations to load and store analysis information
--- persistently in files.
---
--- @author Heiko Hoffmann, Michael Hanus
--- @version March 2017
--------------------------------------------------------------------------
module Analysis.Files where
import Directory
import Distribution ( installDir, lookupModuleSourceInLoadPath
, stripCurrySuffix )
import FlatCurry.Files
import FlatCurry.Goodies (progImports)
import FlatCurry.Types (Prog, QName)
import FilePath
import List (isPrefixOf, isSuffixOf)
import ReadShowTerm (readQTerm, showQTerm)
import Time (ClockTime)
import Analysis.Logging (debugMessage)
import Analysis.ProgInfo
--- Get the file name in which analysis results are stored
--- (without suffix ".pub" or ".priv")
getAnalysisBaseFile :: String -> String -> IO String
getAnalysisBaseFile moduleName anaName = do
analysisDirectory <- getAnalysisDirectory
currentDir <- getCurrentDirectory >>= return . dropDrive
let modAnaName = moduleName <.> anaName
(fileDir,_) <- findModuleSourceInLoadPath moduleName
if isAbsolute fileDir
then return (analysisDirectory </> dropDrive fileDir </> modAnaName)
else return (analysisDirectory </> currentDir </> fileDir </> modAnaName)
--- Get the file name in which public analysis results are stored.
getAnalysisPublicFile :: String -> String -> IO String
getAnalysisPublicFile modname ananame = do
getAnalysisBaseFile modname ananame >>= return . (<.> "pub")
-- Cache directory where analysis info files are stored.
-- If $HOME exists, it is ~/.curryanalysis_cache
getAnalysisDirectory :: IO String
getAnalysisDirectory = do
homedir <- getHomeDirectory
hashomedir <- doesDirectoryExist homedir
let cassStoreDir = if hashomedir then homedir else installDir
return $ cassStoreDir </> ".curryanalysis_cache"
-- loads analysis results for a list of modules
getInterfaceInfos :: String -> [String] -> IO (ProgInfo a)
getInterfaceInfos _ [] = return emptyProgInfo
getInterfaceInfos anaName (mod:mods) =
do modInfo <- loadPublicAnalysis anaName mod
modsInfo <- getInterfaceInfos anaName mods
return (combineProgInfo modInfo modsInfo)
--- Gets the file name in which default analysis values different from
--- standard start values are stored. Typically, such a file contains
--- specific analysis information for external operations.
--- The file must contain a term of the type `[(String,a)]` where
--- the first component of each pair is the name of the operation
--- (it is assumed that this denotes an operation of the current module)
--- and the second component is an analysis value.
loadDefaultAnalysisValues :: String -> String -> IO [(QName,a)]
loadDefaultAnalysisValues anaName moduleName = do
(_,fileName) <- findModuleSourceInLoadPath moduleName
let defaultFileName = stripCurrySuffix fileName ++ ".defaults."++anaName
fileExists <- doesFileExist defaultFileName
if fileExists
then do debugMessage 3 ("Load default values from " ++ defaultFileName)
defaultValues <- readFile defaultFileName >>= return . readQTerm
return (map (\ (f,a) -> ((moduleName,f),a)) defaultValues)
else return []
--- Loads the currently stored analysis information for a module.
loadCompleteAnalysis :: String -> String -> IO (ProgInfo _)
loadCompleteAnalysis ananame mainModule =
getAnalysisBaseFile mainModule ananame >>= readAnalysisFiles
--- Reads analysis result from file for the public entities of a given module.
loadPublicAnalysis:: String -> String -> IO (ProgInfo a)
loadPublicAnalysis anaName moduleName = do
getAnalysisPublicFile moduleName anaName >>= readAnalysisPublicFile
--- Store current import dependencies.
storeImportModuleList :: String -> [String] -> IO ()
storeImportModuleList modname modlist = do
importListFile <- getAnalysisBaseFile modname "IMPORTLIST"
createDirectoryR (dropFileName importListFile)
writeFile importListFile (showQTerm modlist)
--- Gets the file containing import dependencies for a main module
--- (if it exists).
getImportModuleListFile :: String -> IO (Maybe String)
getImportModuleListFile modname = do
importListFile <- getAnalysisBaseFile modname "IMPORTLIST"
iflExists <- doesFileExist importListFile
return $ if iflExists then Just importListFile else Nothing
--- Store an analysis results in a file and create directories if neccesssary.
--- The first argument is the analysis name.
storeAnalysisResult :: String -> String -> ProgInfo a -> IO ()
storeAnalysisResult ananame moduleName result = do
baseFileName <- getAnalysisBaseFile moduleName ananame
createDirectoryR (dropFileName baseFileName)
debugMessage 4 ("Analysis result: " ++ showProgInfo result)
writeAnalysisFiles baseFileName result
-- creates directory (and all needed root-directories) recursively
createDirectoryR :: String -> IO ()
createDirectoryR maindir =
let (drv,dir) = splitDrive maindir
in createDirectories drv (splitDirectories dir)
where
createDirectories _ [] = done
createDirectories dirname (dir:dirs) = do
let createdDir = dirname </> dir
dirExists <- doesDirectoryExist createdDir
unless dirExists $ do
debugMessage 3 ("Creating directory '"++createdDir++"'...")
createDirectory createdDir
createDirectories createdDir dirs
--- Deletes all analysis files for a given analysis name.
deleteAllAnalysisFiles :: String -> IO ()
deleteAllAnalysisFiles ananame = do
analysisDir <- getAnalysisDirectory
deleteAllInDir analysisDir
where
deleteAllInDir dir = do
dircont <- getDirectoryContents dir
mapIO_ processDirElem (filter (not . isPrefixOf ".") dircont)
where
processDirElem f = do
let fullname = dir </> f
when (isAnaFile f) $ do
putStrLn ("DELETE: " ++ fullname)
removeFile fullname
isdir <- doesDirectoryExist fullname
when isdir $ deleteAllInDir fullname
isAnaFile f =
(".pub" `isSuffixOf` f && ('.':ananame) `isSuffixOf` dropExtension f) ||
(".priv" `isSuffixOf` f && ('.':ananame) `isSuffixOf` dropExtension f)
--------------------------------------------------------------------------
-- Auxiliaries for dealing with Curry files.
--- Returns a directory name and the actual source file name for a module
--- by looking up the module source in the current load path.
--- If the module is hierarchical, the directory is the top directory
--- of the hierarchy.
--- An error is raised if there is no corresponding source file.
findModuleSourceInLoadPath :: String -> IO (String,String)
findModuleSourceInLoadPath modname =
lookupModuleSourceInLoadPath modname >>=
maybe (error $ "Source file for module '"++modname++"' not found!")
return
--- Get the imports of a module.
getImports :: String -> IO [String]
getImports moduleName = do
debugMessage 3 ("Reading interface of module "++moduleName)
readNewestFlatCurryInt moduleName >>= return . progImports
-- Get timestamp of a Curry source module file (together with the module name)
getSourceFileTime :: String -> IO (String,ClockTime)
getSourceFileTime moduleName = do
(_,fileName) <- findModuleSourceInLoadPath moduleName
time <- getModificationTime fileName
return (moduleName,time)
-- Get timestamp of FlatCurry file (together with the module name)
getFlatCurryFileTime :: String -> IO (String,Maybe ClockTime)
getFlatCurryFileTime modname =
lookupFlatCurryFileInLoadPath modname >>=
maybe (return (modname, Nothing))
(\fcyFileName -> do
ftime <- getModificationTime fcyFileName
return (modname, Just ftime))
--- Returns name of the FlatCurry file of a module if this file exists
--- and is newer than the source file.
flatCurryFileNewer :: String -> IO (Maybe String)
flatCurryFileNewer modname = do
(_,sourceFileName) <- findModuleSourceInLoadPath modname
stime <- getModificationTime sourceFileName
lookupFlatCurryFileInLoadPath modname >>=
maybe (return Nothing)
(\fcyFileName -> do
itime <- getModificationTime fcyFileName
return (if itime >= stime then Just fcyFileName else Nothing))
--- Returns the newest FlatCurry program for a module.
--- The source program is parsed if the interface older than the source,
--- otherwise the FlatCurry program is read without parsing
--- (note that this returns only the correct version if the
--- imported modules are already parsed or are not relevant here).
readNewestFlatCurry :: String -> IO Prog
readNewestFlatCurry modname =
flatCurryFileNewer modname >>=
maybe (readFlatCurry modname) readFlatCurryFile
--- Returns the newest FlatCurry interface for a module.
--- The source program is parsed if the interface older than the source,
--- otherwise the FlatCurry interface file is read without parsing
--- (note that this returns only the correct version if the
--- imported modules are already parsed or are not relevant here).
readNewestFlatCurryInt :: String -> IO Prog
readNewestFlatCurryInt modname =
flatCurryFileNewer modname >>=
maybe (readFlatCurryInt modname) (readFlatCurryFile . flat2intName)
--- Translates FlatCurry file name to corresponding FlatCurry interface