camfort-1.0: src/Camfort/Input.hs
{- |
Module : Camfort.Input
Description : Handles input of code base and passing the files on to core functionality.
Copyright : Copyright 2017, Dominic Orchard, Andrew Rice, Mistral Contrastin, Matthew Danish
License : Apache-2.0
Maintainer : dom.orchard@gmail.com
-}
module Camfort.Input
(
-- * Classes
Default(..)
-- * Datatypes and Aliases
, ProgramFile
, AnalysisProgram
, AnalysisRunner
, AnalysisRunnerP
, AnalysisRunnerConsumer
-- * Builders for analysers and refactorings
, runPerFileAnalysisP
, runMultiFileAnalysis
, describePerFileAnalysisP
, doRefactor
, doRefactorAndCreate
, perFileRefactoring
-- * Source directory and file handling
, readParseSrcDir
, loadModAndProgramFiles
-- * Combinators
, runThen
) where
import Control.Monad.IO.Class
import qualified Data.ByteString.Char8 as B
import Data.Either (partitionEithers)
import Data.List (intercalate)
import Control.Lens
import Control.DeepSeq
import qualified Language.Fortran.AST as F
import Language.Fortran.Util.ModFile (ModFiles, emptyModFiles)
import Language.Fortran.ParserMonad (FortranVersion(..))
import Camfort.Analysis
import Camfort.Analysis.Annotations
import Camfort.Analysis.Logger
import Camfort.Analysis.ModFile (MFCompiler, genModFiles, readParseSrcDir)
import Camfort.Helpers
import Camfort.Output
import Pipes
import qualified Pipes.Prelude as P
-- | An analysis program which accepts inputs of type @a@ and produces results
-- of type @b@.
--
-- Has error messages of type @e@ and warnings of type @w@. Runs in the base
-- monad @m@.
type AnalysisProgram e w m a b = a -> AnalysisT e w m b
-- | An 'AnalysisRunner' is a function to run an 'AnalysisProgram' in a
-- particular way. Produces a final result of type @r@.
type AnalysisRunner e w m a b r =
AnalysisProgram e w m a b -> LogOutput m -> LogLevel -> Bool -> ModFiles -> [(ProgramFile, SourceText)] -> m r
type AnalysisRunnerP e w m a b r =
AnalysisProgram e w m a b -> LogOutput m -> LogLevel -> Bool -> ModFiles -> Pipe (ProgramFile, SourceText) r m ()
type AnalysisRunnerConsumer e w m a b r =
AnalysisProgram e w m a b -> LogOutput m -> LogLevel -> Bool -> ModFiles -> Consumer (ProgramFile, SourceText) m ()
--------------------------------------------------------------------------------
-- Simple runners
--------------------------------------------------------------------------------
-- | Given an analysis program for a single file, run it over every input file
-- and collect the reports. Doesn't produce any output.
runPerFileAnalysisP
:: (MonadIO m, Describe e, Describe w, NFData e, NFData w, NFData b)
=> AnalysisRunnerP e w m ProgramFile b (AnalysisReport e w b)
runPerFileAnalysisP program logOutput logLevel _ modFiles =
P.mapM $ \ (pf, _) -> do
-- liftIO . putStrLn $ "Running analysis on " ++ (F.pfGetFilename pf)
runAnalysisT (F.pfGetFilename pf)
logOutput
logLevel
modFiles
(program pf)
-- | Run an analysis program over every input file and get the report. Doesn't
-- produce any output.
runMultiFileAnalysis
:: (Monad m, Describe e, Describe w)
=> AnalysisRunner e w m [ProgramFile] b (AnalysisReport e w b)
runMultiFileAnalysis program logOutput logLevel _ modFiles
= runAnalysisT "<unknown>" logOutput logLevel modFiles . program . map fst
--------------------------------------------------------------------------------
-- Complex Runners
--------------------------------------------------------------------------------
-- doCreateBinary
-- :: (MonadIO m, Describe r, Describe w, Describe e)
-- => Text -> AnalysisRunner e w m ProgramFile r ()
-- doCreateBinary analysisName = runPerFileAnalysis `runThen` writeCompiledFiles
-- where
-- writeCompiledFiles :: (r, [(Filename, B.ByteString)]) -> IO r
-- writeCompiledFiles (report, bins) = do
-- outputFiles inSrc outSrc bins
-- pure report
-- FIXME
{-
compilePerFile :: (Describe e, Describe e', Describe w, Describe r) =>
Text
-> FileOrDir
-> FilePath
-> AnalysisRunner e w IO [ProgramFile] (r, [Either e' ProgramFile]) ()
compilePerFile analysisName inSrc outSrc =
runPerFileAnalysis `runThen` writeCompiledFiles
where
writeCompiledFiles :: (r, [(Filename, B.ByteString)]) -> IO r
writeCompiledFiles (report, bins) = do
outputFiles inSrc outSrc bins
pure report
-}
-- | Given an analysis program for a single file, run it over every input file
-- and collect the reports, then print those reports to standard output.
describePerFileAnalysisP
:: (MonadIO m, Describe r, ExitCodeOfReport r, Describe w, Describe e, NFData e, NFData w, NFData r)
=> Text -> AnalysisRunnerP e w m ProgramFile r (AnalysisReport e w r)
describePerFileAnalysisP analysisName program logOutput logLevel snippets modFiles = do
runPerFileAnalysisP program logOutput logLevel snippets modFiles >->
(P.mapM $ \ r -> liftIO (putDescribeReport analysisName (Just logLevel) snippets r) >> pure r)
-- | Accepts an analysis program for multiple input files which produces a
-- result value along with refactored files. Performs the refactoring, and
-- prints the result value with the report.
doRefactor
:: (Describe e, Describe e', Describe w, Describe r, ExitCodeOfReport r)
=> Text
-> FileOrDir -> FilePath
-> AnalysisRunner e w IO [ProgramFile] (r, [Either e' ProgramFile]) Int
doRefactor analysisName inSrc outSrc program logOutput logLevel snippets modFiles pfsTexts = do
report <- runMultiFileAnalysis program logOutput logLevel snippets modFiles pfsTexts
let
-- Get the user-facing output from the report
report' = fmap fst report
-- Get the refactoring result form the report
resultFiles = report ^? arResult . _ARSuccess . _2
putDescribeReport analysisName (Just logLevel) snippets report'
-- If the refactoring succeeded, change the files
case resultFiles of
Just fs -> finishRefactor inSrc outSrc (map snd pfsTexts) fs >>
return (exitCodeOf report')
Nothing -> return (exitCodeOf report')
-- | Accepts an analysis program for multiple input files which produces
-- refactored files and creates new files. Performs the refactoring.
doRefactorAndCreate
:: (Describe e, Describe w)
=> Text
-> FileOrDir -> FilePath
-> AnalysisRunner e w IO [ProgramFile] ([ProgramFile], [ProgramFile]) Int
doRefactorAndCreate analysisName inSrc outSrc program logOutput logLevel snippets modFiles pfsTexts = do
report <- runMultiFileAnalysis program logOutput logLevel snippets modFiles pfsTexts
let
-- Get the user-facing output from the report
report' = fmap (const ()) report
-- Get the refactoring result form the report
resultFiles = report ^? arResult . _ARSuccess
putDescribeReport analysisName (Just logLevel) snippets report'
case resultFiles of
-- If the refactoring succeeded, change the files
Just fs -> finishRefactorAndCreate inSrc outSrc (map snd pfsTexts) fs >>
return (exitCodeOf report')
Nothing -> return (exitCodeOf report')
-- | Accepts an analysis program to refactor a single file and returns an
-- analysis program to refactor each input file with that refactoring.
perFileRefactoring
:: (Monad m)
=> AnalysisProgram e w m ProgramFile ProgramFile
-> AnalysisProgram e w m [ProgramFile] ((), [Either e ProgramFile])
perFileRefactoring program pfs = do
pfs' <- mapM program pfs
return ((), fmap pure pfs')
--------------------------------------------------------------------------------
-- Refactoring Combinators
--------------------------------------------------------------------------------
finishRefactor
:: FileOrDir -> FilePath
-> [SourceText]
-- ^ Original source from the input files
-> [Either e ProgramFile]
-- ^ Changed input files (or errors)
-> IO ()
finishRefactor inSrc outSrc inputText analysisOutput = do
let (_, ps') = partitionEithers analysisOutput
outputs = reassociateSourceText inputText ps'
outputFiles inSrc outSrc outputs
finishRefactorAndCreate
:: FileOrDir -> FilePath
-> [SourceText]
-- ^ Original source from the input files
-> ([ProgramFile], [ProgramFile])
-- ^ Changed input files, newly created files
-> IO ()
finishRefactorAndCreate inSrc outSrc inputText analysisOutput = do
let changedFiles = reassociateSourceText inputText (fst analysisOutput)
newFiles = map (\pf -> (pf, B.empty)) (snd analysisOutput)
outputFiles inSrc outSrc changedFiles
outputFiles inSrc outSrc newFiles
--------------------------------------------------------------------------------
-- Combinators
--------------------------------------------------------------------------------
-- | Monadic bind for analysis runners.
runThen
:: (Monad m)
=> AnalysisRunner e w m a b r -> (r -> m r')
-> AnalysisRunner e w m a b r'
runThen runner withResult program output level snippets modFiles programFiles =
runner program output level snippets modFiles programFiles >>= withResult
--------------------------------------------------------------------------------
-- Misc
--------------------------------------------------------------------------------
-- | Class for default values of some type 't'
class Default t where
defaultValue :: t
-- | Print a string to the user informing them of files excluded
-- from the operation.
printExcludes :: Filename -> [Filename] -> IO ()
printExcludes _ [] = pure ()
printExcludes _ [""] = pure ()
printExcludes inSrc excludes =
putStrLn $ concat ["Excluding ", intercalate "," excludes, " from ", inSrc, "/"]
-- | For refactorings which create additional files.
type ProgramFile = F.ProgramFile A
reassociateSourceText
:: [SourceText]
-> [F.ProgramFile Annotation]
-> [(F.ProgramFile Annotation, SourceText)]
reassociateSourceText ps ps' = zip ps' ps
loadModAndProgramFiles
:: (MonadIO m)
=> Maybe FortranVersion
-> MFCompiler r m -> r
-> FileOrDir -- ^ Input source file or directory
-> FileOrDir -- ^ Include path
-> [Filename] -- ^ Excluded files
-> m (ModFiles, [(ProgramFile, SourceText)])
loadModAndProgramFiles mv mfc env inSrc incDir excludes = do
liftIO $ printExcludes inSrc excludes
modFiles <- genModFiles mv emptyModFiles mfc env incDir excludes
ps <- liftIO $ readParseSrcDir mv modFiles inSrc excludes
pure (modFiles, ps)