{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveDataTypeable #-}
module Language.Fortran.Util.ModFile
( modFileSuffix, ModFile, ModFiles, emptyModFile, emptyModFiles
, lookupModFileData, getLabelsModFileData, alterModFileData
, genModFile, regenModFile, encodeModFile, decodeModFile
, StringMap, DeclMap, ParamVarMap, DeclContext(..), extractModuleMap, extractDeclMap
, moduleFilename, combinedStringMap, combinedDeclMap, combinedModuleMap, combinedTypeEnv, combinedParamVarMap
, genUniqNameToFilenameMap
, TimestampStatus(..), checkTimestamps )
where
import Control.Monad.State
import Data.Binary (Binary, encode, decodeOrFail)
import qualified Data.ByteString.Lazy.Char8 as LB
import Data.Data
import Data.Generics.Uniplate.Operations
import qualified Data.Map.Strict as M
import Data.Maybe
import GHC.Generics (Generic)
import qualified Language.Fortran.AST as F
import qualified Language.Fortran.Analysis as FA
import qualified Language.Fortran.Analysis.BBlocks as FAB
import qualified Language.Fortran.Analysis.DataFlow as FAD
import qualified Language.Fortran.Analysis.Renaming as FAR
import qualified Language.Fortran.Analysis.Types as FAT
import qualified Language.Fortran.Util.Position as P
import System.Directory
import System.FilePath
modFileSuffix :: String
modFileSuffix = ".fsmod"
data DeclContext = DCMain | DCBlockData | DCModule F.ProgramUnitName
| DCFunction (F.ProgramUnitName, F.ProgramUnitName)
| DCSubroutine (F.ProgramUnitName, F.ProgramUnitName)
deriving (Ord, Eq, Show, Data, Typeable, Generic)
instance Binary DeclContext
type DeclMap = M.Map F.Name (DeclContext, P.SrcSpan)
type StringMap = M.Map String String
type ParamVarMap = FAD.ParameterVarMap
data ModFile = ModFile { mfFilename :: String
, mfStringMap :: StringMap
, mfModuleMap :: FAR.ModuleMap
, mfDeclMap :: DeclMap
, mfTypeEnv :: FAT.TypeEnv
, mfParamVarMap :: ParamVarMap
, mfOtherData :: M.Map String LB.ByteString }
deriving (Eq, Ord, Show, Data, Typeable, Generic)
instance Binary ModFile
type ModFiles = [ModFile]
emptyModFiles :: ModFiles
emptyModFiles = []
emptyModFile :: ModFile
emptyModFile = ModFile "" M.empty M.empty M.empty M.empty M.empty M.empty
regenModFile :: forall a. Data a => F.ProgramFile (FA.Analysis a) -> ModFile -> ModFile
regenModFile pf mf = mf { mfModuleMap = extractModuleMap pf
, mfDeclMap = extractDeclMap pf
, mfTypeEnv = FAT.extractTypeEnv pf
, mfParamVarMap = extractParamVarMap pf
, mfFilename = F.pfGetFilename pf }
genModFile :: forall a. Data a => F.ProgramFile (FA.Analysis a) -> ModFile
genModFile = flip regenModFile emptyModFile
lookupModFileData :: String -> ModFile -> Maybe LB.ByteString
lookupModFileData k = M.lookup k . mfOtherData
getLabelsModFileData :: ModFile -> [String]
getLabelsModFileData = M.keys . mfOtherData
alterModFileData :: (Maybe LB.ByteString -> Maybe LB.ByteString) -> String -> ModFile -> ModFile
alterModFileData f k mf = mf { mfOtherData = M.alter f k . mfOtherData $ mf }
encodeModFile :: [ModFile] -> LB.ByteString
encodeModFile = encode . map each
where
each mf = mf' { mfStringMap = sm }
where
(mf', sm) = extractStringMap (mf { mfStringMap = M.empty })
decodeModFile :: LB.ByteString -> Either String [ModFile]
decodeModFile bs = case decodeOrFail bs of
Left (_, _, s) -> Left s
Right (_, _, mfs) -> Right (map each mfs)
where
each mf = (revertStringMap sm mf { mfStringMap = M.empty }) { mfStringMap = sm }
where sm = mfStringMap mf
combinedModuleMap :: ModFiles -> FAR.ModuleMap
combinedModuleMap = M.unions . map mfModuleMap
combinedTypeEnv :: ModFiles -> FAT.TypeEnv
combinedTypeEnv = M.unions . map mfTypeEnv
combinedDeclMap :: ModFiles -> DeclMap
combinedDeclMap = M.unions . map mfDeclMap
combinedStringMap :: ModFiles -> StringMap
combinedStringMap = M.unions . map mfStringMap
combinedParamVarMap :: ModFiles -> ParamVarMap
combinedParamVarMap = M.unions . map mfParamVarMap
moduleFilename :: ModFile -> String
moduleFilename = mfFilename
genUniqNameToFilenameMap :: ModFiles -> M.Map F.Name String
genUniqNameToFilenameMap = M.unions . map perMF
where
perMF mf = M.fromList [ (n, fname) | modEnv <- M.elems (mfModuleMap mf)
, (n, _) <- M.elems modEnv ]
where
fname = mfFilename mf
extractModuleMap :: forall a. Data a => F.ProgramFile (FA.Analysis a) -> FAR.ModuleMap
extractModuleMap pf
| null mmap = M.singleton F.NamelessMain $ M.unions combinedEnv
| otherwise = M.fromList mmap
where
mmap = [ (n, env) | pu@F.PUModule{} <- childrenBi pf :: [F.ProgramUnit (FA.Analysis a)]
, let a = F.getAnnotation pu
, let n = F.getName pu
, env <- maybeToList (FA.moduleEnv a) ]
combinedEnv = [ env | pu <- childrenBi pf :: [F.ProgramUnit (FA.Analysis a)]
, let a = F.getAnnotation pu
, env <- maybeToList (FA.moduleEnv a) ]
extractDeclMap :: forall a. Data a => F.ProgramFile (FA.Analysis a) -> DeclMap
extractDeclMap pf = M.fromList . concatMap (blockDecls . nameAndBlocks) $ universeBi pf
where
blockDecls :: (DeclContext, Maybe (F.Name, P.SrcSpan), [F.Block (FA.Analysis a)]) -> [(F.Name, (DeclContext, P.SrcSpan))]
blockDecls (dc, mret, bs)
| Nothing <- mret = map decls (universeBi bs)
| Just (ret, ss) <- mret = (ret, (dc, ss)):map decls (universeBi bs)
where
decls d = let (v, ss) = declVarName d in (v, (dc, ss))
declVarName :: F.Declarator (FA.Analysis a) -> (F.Name, P.SrcSpan)
declVarName (F.DeclVariable _ _ e _ _) = (FA.varName e, P.getSpan e)
declVarName (F.DeclArray _ _ e _ _ _) = (FA.varName e, P.getSpan e)
nameAndBlocks :: F.ProgramUnit (FA.Analysis a) -> (DeclContext, Maybe (F.Name, P.SrcSpan), [F.Block (FA.Analysis a)])
nameAndBlocks pu = case pu of
F.PUMain _ _ _ b _ -> (DCMain, Nothing, b)
F.PUModule _ _ _ b _ -> (DCModule $ FA.puName pu, Nothing, b)
F.PUSubroutine _ _ _ _ _ b _ -> (DCSubroutine (FA.puName pu, FA.puSrcName pu), Nothing, b)
F.PUFunction _ _ _ _ _ _ mret b _
| Nothing <- mret
, F.Named n <- FA.puName pu -> (DCFunction (FA.puName pu, FA.puSrcName pu), Just (n, P.getSpan pu), b)
| Just ret <- mret -> (DCFunction (FA.puName pu, FA.puSrcName pu), Just (FA.varName ret, P.getSpan ret), b)
| otherwise -> error $ "nameAndBlocks: un-named function with no return value! " ++ show (FA.puName pu) ++ " at source-span " ++ show (P.getSpan pu)
F.PUBlockData _ _ _ b -> (DCBlockData, Nothing, b)
F.PUComment {} -> (DCBlockData, Nothing, [])
extractStringMap :: Data a => a -> (a, StringMap)
extractStringMap x = fmap (inv . fst) . flip runState (M.empty, 0) $ descendBiM f x
where
inv = M.fromList . map (\ (a,b) -> (b,a)) . M.toList
f :: String -> State (StringMap, Int) String
f s = do
(m, n) <- get
case M.lookup s m of
Just s' -> return s'
Nothing -> do
let s' = '@':show n
put (M.insert s s' m, n + 1)
return s'
revertStringMap :: Data a => StringMap -> a -> a
revertStringMap sm = descendBi (\ s -> s `fromMaybe` M.lookup s sm)
extractParamVarMap :: forall a. Data a => F.ProgramFile (FA.Analysis a) -> ParamVarMap
extractParamVarMap pf = M.fromList cvm
where
pf' = FAD.analyseConstExps $ FAB.analyseBBlocks pf
cvm = [ (FA.varName v, con)
| F.PUModule _ _ _ bs _ <- universeBi pf' :: [F.ProgramUnit (FA.Analysis a)]
, st@(F.StDeclaration _ _ (F.TypeSpec _ _ _ _) _ _) <- universeBi bs :: [F.Statement (FA.Analysis a)]
, F.AttrParameter _ _ <- universeBi st :: [F.Attribute (FA.Analysis a)]
, (F.DeclVariable _ _ v _ _) <- universeBi st :: [F.Declarator (FA.Analysis a)]
, Just con <- [FA.constExp (F.getAnnotation v)] ] ++
[ (FA.varName v, con)
| F.PUModule _ _ _ bs _ <- universeBi pf' :: [F.ProgramUnit (FA.Analysis a)]
, st@F.StParameter {} <- universeBi bs :: [F.Statement (FA.Analysis a)]
, (F.DeclVariable _ _ v _ _) <- universeBi st :: [F.Declarator (FA.Analysis a)]
, Just con <- [FA.constExp (F.getAnnotation v)] ]
data TimestampStatus = NoSuchFile | CompileFile | ModFileExists FilePath
checkTimestamps :: FilePath -> IO TimestampStatus
checkTimestamps path = do
pathExists <- doesFileExist path
modExists <- doesFileExist $ path -<.> modFileSuffix
case (pathExists, modExists) of
(False, _) -> pure NoSuchFile
(True, False) -> pure CompileFile
(True, True) -> do
let modPath = path -<.> modFileSuffix
pathModTime <- getModificationTime path
modModTime <- getModificationTime modPath
if pathModTime < modModTime
then pure $ ModFileExists modPath
else pure CompileFile