module Main where
import Control.Monad.Extra
( findM, unlessM, whenM )
import Control.Monad.IO.Class
import Data.List ( intercalate )
import qualified Data.Map.Strict as Map
import System.Directory
( createDirectoryIfMissing, doesFileExist )
import System.Exit ( exitSuccess )
import System.FilePath
import FreeC.Application.Debug
import FreeC.Application.Option.Help
import FreeC.Application.Option.Version
import FreeC.Application.Options
import FreeC.Application.Options.Parser
import FreeC.Backend
import FreeC.Environment
import FreeC.Environment.LookupOrFail
import FreeC.Environment.ModuleInterface.Decoder
import FreeC.Environment.ModuleInterface.Encoder
import FreeC.Frontend
import qualified FreeC.IR.Base.Prelude as IR.Prelude
import FreeC.IR.DependencyGraph
import FreeC.IR.SrcSpan
import qualified FreeC.IR.Syntax as IR
import FreeC.Monad.Application
import FreeC.Monad.Converter
import FreeC.Monad.Reporter
import FreeC.Pipeline
import FreeC.Pretty
( putPrettyLn, showPretty, writePrettyFile )
main :: IO ()
main = runApp compiler
compiler :: Application ()
compiler = do
getOpts >>= liftReporterIO . getAndParseArgs >>= putOpts
whenM (inOpts optShowHelp) $ liftIO $ do
putUsageInfo
exitSuccess
whenM (inOpts optShowVersion) $ liftIO $ do
putVersionInfo
exitSuccess
whenM (inOpts (null . optInputFiles)) $ liftIO $ do
putDebug "No input file.\n"
putUsageInfo
exitSuccess
frontend <- selectFrontend
backend <- selectBackend
loadPrelude
backendSpecialAction backend
case frontend of
Frontend
{ frontendParseFile = parser, frontendSimplifyModule = simplifier } -> do
let Backend { backendConvertModule = converter } = backend
inputFiles <- inOpts optInputFiles
inputModules <- mapM (parseInputFileWith parser) inputFiles
sortedModules <- sortInputModules inputModules
outputModules <- mapM (convertInputModuleWith simplifier converter)
sortedModules
mapM_ (uncurry (writeOutputModule backend)) outputModules
selectFrontend :: Application Frontend
selectFrontend = do
name <- inOpts optFrontend
case Map.lookup name frontends of
Nothing -> do
reportFatal
$ Message NoSrcSpan Error
$ "Unrecognized frontend. Currently supported frontends are: "
++ showFrontends
++ "."
Just f -> return f
selectBackend :: Application Backend
selectBackend = do
name <- inOpts optBackend
case Map.lookup name backends of
Nothing -> do
reportFatal
$ Message NoSrcSpan Error
$ "Unrecognized backend. Currently supported backends are: "
++ showBackends
++ "."
Just b -> return b
parseInputFileWith
:: FrontendParser decls -> FilePath -> Application (IR.ModuleOf decls)
parseInputFileWith parser inputFile = reportApp $ do
putDebug $ "Loading " ++ inputFile
contents <- liftIO $ readFile inputFile
parser (mkSrcFile inputFile contents)
sortInputModules :: [IR.ModuleOf decls] -> Application [IR.ModuleOf decls]
sortInputModules = mapM checkForCycle . groupModules
where
checkForCycle :: DependencyComponent (IR.ModuleOf decls)
-> Application (IR.ModuleOf decls)
checkForCycle (NonRecursive m) = return m
checkForCycle (Recursive ms) = reportFatal
$ Message NoSrcSpan Error
$ "Module imports form a cycle: "
++ intercalate ", " (map (showPretty . IR.modName) ms)
convertInputModuleWith :: FrontendSimplifier decls
-> BackendConverter
-> IR.ModuleOf decls
-> Application (IR.ModName, String)
convertInputModuleWith simplifier converter inputModule = do
let modName = IR.modName inputModule
srcSpan = IR.modSrcSpan inputModule
if hasSrcSpanFile srcSpan
then putDebug
$ "Compiling "
++ showPretty modName
++ " ("
++ srcFileName (srcSpanFile srcSpan)
++ ")"
else putDebug $ "Compiling " ++ showPretty modName
reportApp $ do
loadRequiredModules inputModule
moduleEnv $ do
inputModule' <- simplifier inputModule
outputModule <- liftConverter $ runPipeline inputModule'
outputModule' <- converter outputModule
return (modName, outputModule')
writeOutputModule :: Backend -> IR.ModName -> String -> Application ()
writeOutputModule backend modName outputStr = do
maybeOutputDir <- inOpts optOutputDir
case maybeOutputDir of
Nothing -> liftIO $ putPrettyLn outputStr
Just outputDir -> do
let outputPath = map (\c -> if c == '.' then '/' else c) modName
outputFile
= outputDir </> outputPath <.> backendFileExtension backend
ifaceFile = outputDir </> outputPath <.> "json"
iface <- liftConverter $ lookupAvailableModuleOrFail NoSrcSpan modName
liftIO $ createDirectoryIfMissing True (takeDirectory outputFile)
writeModuleInterface ifaceFile iface
liftIO $ writePrettyFile outputFile outputStr
loadRequiredModules :: IR.ModuleOf decls -> Application ()
loadRequiredModules = mapM_ loadImport . IR.modImports
loadImport :: IR.ImportDecl -> Application ()
loadImport decl = do
let srcSpan = IR.importSrcSpan decl
modName = IR.importName decl
unlessM (liftConverter (inEnv (isModuleAvailable modName)))
$ loadModule srcSpan modName
loadModule :: SrcSpan -> IR.ModName -> Application ()
loadModule srcSpan modName = do
baseLibDir <- inOpts optBaseLibDir
importDirs <- inOpts optImportDirs
ifaceFile <- findIfaceFile (baseLibDir : importDirs)
iface <- loadModuleInterface ifaceFile
modifyEnv $ makeModuleAvailable iface
where
filename :: FilePath
filename = map (\c -> if c == '.' then '/' else c) modName
extensions :: [String]
extensions = ["json", "toml"]
findIfaceFile :: [FilePath] -> Application FilePath
findIfaceFile directories = do
let ifaceFiles = [directory </> filename <.> extension
| directory <- directories
, extension <- extensions
]
ifraceFile <- findM (liftIO . doesFileExist) ifaceFiles
maybe reportMissingModule return ifraceFile
reportMissingModule :: Application a
reportMissingModule = reportFatal
$ Message srcSpan Error
$ "Could not find imported module " ++ showPretty modName ++ "."
loadPrelude :: Application ()
loadPrelude = loadModule NoSrcSpan IR.Prelude.modName