module Camfort.Input
(
Default(..)
, FileProgram
, callAndSummarise
, doAnalysisReportWithModFiles
, doAnalysisSummary
, doRefactor
, doRefactorAndCreate
, doRefactorWithModFiles
, doCreateBinary
, readParseSrcDir
, getModFilesWithNames
) where
import Control.Monad (forM)
import Data.Binary (decodeFileOrFail)
import qualified Data.ByteString.Char8 as B
import Data.Char (toUpper)
import Data.List (foldl', (\\), intercalate)
import Data.Maybe
import Data.Text.Encoding (encodeUtf8, decodeUtf8With)
import Data.Text.Encoding.Error (replace)
import System.Directory
import System.FilePath ((</>), takeExtension)
import qualified Language.Fortran.AST as F
import qualified Language.Fortran.Parser.Any as FP
import Language.Fortran.Util.ModFile
import Camfort.Analysis.Annotations
import Camfort.Helpers
import Camfort.Output
class Default t where
defaultValue :: t
printExcludes :: Filename -> [Filename] -> IO ()
printExcludes _ [] = pure ()
printExcludes _ [""] = pure ()
printExcludes inSrc excludes =
putStrLn $ concat ["Excluding ", intercalate "," excludes, " from ", inSrc, "/"]
doAnalysisSummary :: (Monoid s, Show' s)
=> (FileProgram -> (s, FileProgram))
-> FileOrDir -> [Filename] -> IO ()
doAnalysisSummary aFun inSrc excludes = do
printExcludes inSrc excludes
ps <- readParseSrcDir inSrc excludes
let (out, _) = callAndSummarise aFun ps
putStrLn . show' $ out
callAndSummarise :: (Monoid s)
=> (FileProgram -> (s, a))
-> [(FileProgram, SourceText)]
-> (s, [a])
callAndSummarise aFun =
foldl' (\(n, pss) (ps, _) ->
let (n', ps') = aFun ps
in (n `mappend` n', ps' : pss)) (mempty, [])
doAnalysisReportWithModFiles
:: ([FileProgram] -> r)
-> (r -> IO out)
-> FileOrDir
-> Maybe FileOrDir
-> [Filename]
-> IO out
doAnalysisReportWithModFiles rFun sFun inSrc incDir excludes = do
printExcludes inSrc excludes
ps <- readParseSrcDirWithModFiles inSrc incDir excludes
let report = rFun . fmap fst $ ps
sFun report
doRefactor :: ([FileProgram]
-> (String, [FileProgram]))
-> FileOrDir -> [Filename] -> FileOrDir
-> IO String
doRefactor rFun inSrc excludes outSrc =
doRefactorWithModFiles rFun inSrc Nothing excludes outSrc
doRefactorWithModFiles
:: ([FileProgram] -> (String, [FileProgram]))
-> FileOrDir
-> Maybe FileOrDir
-> [Filename]
-> FileOrDir
-> IO String
doRefactorWithModFiles rFun inSrc incDir excludes outSrc = do
printExcludes inSrc excludes
ps <- readParseSrcDirWithModFiles inSrc incDir excludes
let (report, ps') = rFun . fmap fst $ ps
let outputs = reassociateSourceText (fmap snd ps) ps'
outputFiles inSrc outSrc outputs
pure report
doRefactorAndCreate
:: ([FileProgram] -> (String, [FileProgram], [FileProgram]))
-> FileOrDir -> [Filename] -> FileOrDir -> IO String
doRefactorAndCreate rFun inSrc excludes outSrc = do
printExcludes inSrc excludes
ps <- readParseSrcDir inSrc excludes
let (report, ps', ps'') = rFun . fmap fst $ ps
let outputs = reassociateSourceText (fmap snd ps) ps'
let outputs' = map (\pf -> (pf, B.empty)) ps''
outputFiles inSrc outSrc outputs
outputFiles inSrc outSrc outputs'
pure report
type FileProgram = F.ProgramFile A
doCreateBinary
:: ([FileProgram] -> (String, [(Filename, B.ByteString)]))
-> FileOrDir
-> Maybe FileOrDir
-> [Filename]
-> FileOrDir
-> IO String
doCreateBinary rFun inSrc incDir excludes outSrc = do
printExcludes inSrc excludes
ps <- readParseSrcDirWithModFiles inSrc incDir excludes
let (report, bins) = rFun . fmap fst $ ps
outputFiles inSrc outSrc bins
pure report
reassociateSourceText :: [SourceText]
-> [F.ProgramFile Annotation]
-> [(F.ProgramFile Annotation, SourceText)]
reassociateSourceText ps ps' = zip ps' ps
readParseSrcDir :: FileOrDir
-> [Filename]
-> IO [(FileProgram, SourceText)]
readParseSrcDir inp excludes =
readParseSrcDirWithModFiles inp Nothing excludes
readParseSrcDirWithModFiles :: FileOrDir
-> Maybe FileOrDir
-> [Filename]
-> IO [(FileProgram, SourceText)]
readParseSrcDirWithModFiles inp incDir excludes = do
isdir <- isDirectory inp
files <-
if isdir
then do
files <- getFortranFiles inp
let excludes' = excludes ++ map (\x -> inp ++ "/" ++ x) excludes
pure $ map (\y -> inp ++ "/" ++ y) files \\ excludes'
else pure [inp]
mapMaybeM (readParseSrcFileWithModFiles incDir) files
where
mapMaybeM :: Monad m => (a -> m (Maybe b)) -> [a] -> m [b]
mapMaybeM f = fmap catMaybes . mapM f
readParseSrcFileWithModFiles :: Maybe FileOrDir
-> Filename
-> IO (Maybe (FileProgram, SourceText))
readParseSrcFileWithModFiles incDir f = do
inp <- flexReadFile f
mods <- maybe (pure emptyModFiles) getModFiles incDir
let result = FP.fortranParserWithModFiles mods inp f
case result of
Right ast -> pure $ Just (fmap (const unitAnnotation) ast, inp)
Left err -> print err >> pure Nothing
where
flexReadFile :: String -> IO B.ByteString
flexReadFile = fmap (encodeUtf8 . decodeUtf8With (replace ' ')) . B.readFile
getFortranFiles :: FileOrDir -> IO [String]
getFortranFiles =
fmap (filter isFortran) . rGetDirContents
where
isFortran :: Filename -> Bool
isFortran x = takeExtension x `elem` (exts ++ extsUpper)
where exts = [".f", ".f90", ".f77", ".cmn", ".inc"]
extsUpper = map (map toUpper) exts
rGetDirContents :: FileOrDir -> IO [Filename]
rGetDirContents d = do
ds <- listDirectory d
fmap concat . mapM rGetDirContents' $ ds
where
rGetDirContents' path = do
let dPath = d </> path
isDir <- doesDirectoryExist dPath
if isDir then do
fmap (fmap (path </>)) (rGetDirContents dPath)
else pure [path]
getModFilesWithNames :: FileOrDir -> IO [(Filename, ModFile)]
getModFilesWithNames dir = do
modFileNames <- filter isModFile <$> rGetDirContents dir
forM modFileNames $ \ modFileName -> do
eResult <- decodeFileOrFail (dir ++ "/" ++ modFileName)
case eResult of
Left (offset, msg) -> do
putStrLn $ modFileName ++ ": Error at offset " ++ show offset ++ ": " ++ msg
pure (modFileName, emptyModFile)
Right modFile -> do
putStrLn $ modFileName ++ ": successfully parsed precompiled file."
pure (modFileName, modFile)
where
isModFile :: Filename -> Bool
isModFile = (== modFileSuffix) . takeExtension
getModFiles :: FileOrDir -> IO ModFiles
getModFiles = fmap (fmap snd) . getModFilesWithNames