packages feed

feldspar-compiler-0.4.0.2: Feldspar/Compiler/Frontend/CommandLine/Main.hs

{-# LANGUAGE CPP #-}
module Main where
-- ====================================== Feldspar imports ==================================
import Feldspar.NameExtractor
import Feldspar.Compiler.Compiler
import qualified Feldspar.Compiler.Compiler as CompilerCore
import Feldspar.Compiler.Backend.C.Options
import qualified Feldspar.Compiler.Backend.C.Options as CoreOptions
import qualified Feldspar.Compiler.Frontend.CommandLine.API.Options as StandaloneOptions
import Feldspar.Compiler.Frontend.CommandLine.API.Constants
import Feldspar.Compiler.Frontend.CommandLine.API.Library as StandaloneLib
import Feldspar.Compiler.Backend.C.Library
import Feldspar.Compiler.Frontend.CommandLine.API
import Feldspar.Compiler.Imperative.Representation
import Feldspar.Compiler.Backend.C.CodeGeneration
import Feldspar.Compiler.Error
import Feldspar.Compiler.Backend.C.Plugin.PrettyPrint
-- ====================================== System imports ==================================
import System.IO
import System.Exit
import System.Info
import System.Process
import System.IO.Error
import System.FilePath
import System.Directory
import System.Environment
import System.Console.ANSI
import System.Console.GetOpt
-- ====================================== Control imports ==================================
import Control.Monad
import Control.Exception
import Control.Monad.Error
import Control.Monad.CatchIO
-- ====================================== Other imports ==================================
import Data.List
import Debug.Trace
import Language.Haskell.Interpreter


data CompilationError =
      InterpreterError InterpreterError
    | InternalErrorCall String

compileFunction :: String -> String -> CoreOptions.Options -> Int -> OriginalFunctionSignature
                -> Interpreter ((String, Either (Module ()) CompilationError), Int)
compileFunction inFileName outFileName coreOptions linenum originalFunctionSignature = do
    let functionName = originalFunctionName originalFunctionSignature
    (SomeCompilable prg) <- interpret ("SomeCompilable " ++ functionName) (as::SomeCompilable)
    let compilationUnit = executePluginChain Standalone prg originalFunctionSignature {
        originalFunctionName = fixFunctionName $ originalFunctionName originalFunctionSignature
    } coreOptions
    -- XXX force evaluation in order to be able to catch the exceptions
    -- liftIO $ evaluate $ compToC coreOptions compilationUnit -- XXX somehow not enough(?!) -- counter-example: structexamples
    result <- liftIO $ do 
        tempdir <- System.IO.Error.catch (getTemporaryDirectory) (\_ -> return ".")
        (tempfile, temph) <- openTempFile tempdir "feldspar-temp.txt"
        let (cCode, (resultLinenum, resultCol)) = compToC ((coreOptions, Declaration_pl), linenum) compilationUnit
        Control.Exception.finally (hPutStrLn temph cCode)
                                  (do hClose temph
                                      removeFileIfPossible tempfile)
        return $ ((functionName, Left compilationUnit), resultLinenum)
    return result

compileAllFunctions :: String -> String -> CoreOptions.Options -> Int -> [OriginalFunctionSignature]
                    -> Interpreter [(String, Either (Module ()) CompilationError)]
compileAllFunctions inFileName outFileName options linenum [] = return []
compileAllFunctions inFileName outFileName options linenum (x:xs) = do
    let functionName = originalFunctionName x
    (compilationUnit, resultLinenum) <- (catchError (compileFunction inFileName outFileName options linenum x)
                              (\(e::InterpreterError) -> return $ ((functionName, Right $ InterpreterError e), linenum)))
                          `Control.Monad.CatchIO.catch`
                          (\msg -> return $ ((functionName,
                                             Right $ InternalErrorCall $ errorPrefix ++ show (msg::Control.Exception.ErrorCall)),
                                             linenum))
    result <- compileAllFunctions inFileName outFileName options (resultLinenum + 1) xs
    return $ compilationUnit : result

-- | Interpreter body for single-function compilation
singleFunctionCompilationBody :: String -> String -> CoreOptions.Options -> OriginalFunctionSignature
                              -> Interpreter (IO CompilationResult)
singleFunctionCompilationBody inFileName outFileName coreOptions originalFunctionSignature = do
    liftIO $ fancyWrite $ "Compiling function " ++ (originalFunctionName originalFunctionSignature) ++ "..."
    (SomeCompilable prg) <-
        interpret ("SomeCompilable " ++ originalFunctionName originalFunctionSignature) (as::SomeCompilable)
    liftIO $ standaloneCompile prg inFileName outFileName originalFunctionSignature coreOptions
    return $ return CompilationSuccess

mergeCompilationUnits :: [Module ()] -> Module ()
mergeCompilationUnits [] = handleError "Standalone" InvariantViolation "Called mergeCompilationUnits with an empty list"
mergeCompilationUnits [x] = x
mergeCompilationUnits l@(x:xs) = Module {
    definitions = nub $ definitions x ++ (definitions $ mergeCompilationUnits xs), -- nub is in fact a "global plugin" here
    moduleLabel = ()
}

padFunctionName :: String -> String
padFunctionName n = StandaloneLib.rpadWith 50 '.' $ "Function " ++ n

writeErrors :: (String, Either a CompilationError) -> IO ()
writeErrors (functionName, Left x) = return ()
writeErrors (functionName, Right err) = case err of 
    InterpreterError ie -> do
        withColor Red $ putStrLn $ "Error in function " ++ functionName ++ ":"
        printInterpreterError ie
    InternalErrorCall ec -> do
        withColor Red $ putStrLn $ "Error in function " ++ functionName ++ ":"
        withColor Red $ putStrLn ec

writeSummary :: (String, Either a CompilationError) -> IO ()
writeSummary (functionName, Left x) = do
    withColor Cyan $ putStr $ padFunctionName functionName
    withColor Green $ putStrLn "[OK]"
writeSummary (functionName, Right msg) = do
    withColor Cyan $ putStr $ padFunctionName functionName
    withColor Red $ putStrLn "[FAILED]"

filterLefts :: [(String, Either a b)] -> [a]
filterLefts [] = []
filterLefts [(_,Left x)]  = [x]
filterLefts [(_,Right _)] = []
filterLefts ((_,Left x):xs)  = x : filterLefts xs
filterLefts ((_,Right _):xs) = filterLefts xs

-- | Interpreter body for multi-function compilation
multiFunctionCompilationBody :: String -> String -> CoreOptions.Options -> [OriginalFunctionSignature]
                             -> Interpreter (IO CompilationResult)
multiFunctionCompilationBody inFileName outFileName coreOptions declarationList = do
    let (headers, linenum) = genHeaders coreOptions
    liftIO $ appendFile outFileName $ headers
    compilationUnits <- compileAllFunctions inFileName outFileName coreOptions linenum declarationList
    liftIO $ do
        mapM writeErrors compilationUnits
        withColor Blue $ putStrLn "\n================= [ Summary of compilation results ] =================\n"
        mapM writeSummary compilationUnits
        let mergedCompilationUnits = mergeCompilationUnits $ filterLefts compilationUnits
        (appendFile outFileName $ fst $ compToC ((coreOptions, Declaration_pl), linenum) mergedCompilationUnits)
            `Control.Exception.catch`
                (\msg -> withColor Red $ putStrLn $ errorPrefix ++ show (msg::Control.Exception.ErrorCall))
    return $ return CompilationSuccess

-- | Calculates the output file name.
convertOutputFileName :: String -> Maybe String -> String
convertOutputFileName inputFileName maybeOutputFileName = case maybeOutputFileName of
    Nothing -> takeFileName $ replaceExtension inputFileName ".c" -- remove takeFileName to return the full path
    Just overriddenFileName -> overriddenFileName

removeFileIfPossible :: String -> IO ()
removeFileIfPossible filename = removeFile filename `Prelude.catch` (const $ return())

fancyWrite :: String -> IO ()
fancyWrite s = do
    withColor Blue $ putStr "=== [ "
    withColor Cyan $ putStr $ rpad 70 s
    withColor Blue $ putStrLn " ] ==="

main = do
    args <- getArgs

    when (length args == 0) (do
        putStrLn $ usageInfo helpHeader StandaloneOptions.optionDescriptors
        exitWith ExitSuccess)

    -- Parse options, getting a list of option actions
    let (actions, nonOptions, errors) = getOpt Permute StandaloneOptions.optionDescriptors args

    when (length errors > 0) (do
        putStrLn $ concat errors
        putStrLn $ usageInfo helpHeader StandaloneOptions.optionDescriptors
        exitWith (ExitFailure 1))

    -- Here we thread startOptions through all supplied option actions
    opts <- foldl (>>=) (return StandaloneOptions.startOptions) actions

    when (length nonOptions /= 1) (do
        putStrLn "ERROR: Exactly one input file expected."
        exitWith (ExitFailure 1))

    let StandaloneOptions.Options {
                  StandaloneOptions.optSingleFunction = functionMode
                , StandaloneOptions.optOutputFileName = maybeOutputFileName
                , StandaloneOptions.optCompilerMode   = compilerMode } = opts
    let inputFileName = replace (head nonOptions) "\\" "/" -- change it for multi-file operation
    let outputFileName = convertOutputFileName inputFileName maybeOutputFileName

    -- -- -- Input file preparations -- -- --
    removeFileIfPossible $ replaceExtension inputFileName ".hi"
    removeFileIfPossible $ replaceExtension inputFileName ".o"
    -- -- -- Output file preparations -- -- --
    renameFile outputFileName (outputFileName ++ ".bak") `Prelude.catch` (const $ return())
    -- -- -- </prepare> -- -- --
    fileDescriptor <- openFile inputFileName ReadMode
    fileContents <- hGetContents fileDescriptor

    let declarationList = getExtendedDeclarationList fileContents
    let moduleName = getModuleName fileContents
    fancyWrite $ "Compilation target: module " ++ moduleName
    fancyWrite $ "Output file: " ++ outputFileName

#ifdef RELEASE
    let needGlobal = False -- globalImportList modules are package modules and shouldn't be loaded, only imported
#else 
    let needGlobal = True -- in normal mode, we need to load them before importing them
#endif

    let highLevelInterpreterWithModuleInfo body = 
            highLevelInterpreter moduleName inputFileName globalImportList needGlobal False body

    -- C code generation
    case functionMode of
        StandaloneOptions.MultiFunction 
          | length declarationList == 0 -> putStrLn "No functions to compile."
          | otherwise -> do
                fancyWrite $ "Number of functions to compile: " ++ (show $ length declarationList)
                highLevelInterpreterWithModuleInfo
                    (multiFunctionCompilationBody inputFileName outputFileName compilerMode declarationList)
                return ()
        StandaloneOptions.SingleFunction funName -> do
            let originalFunctionSignatureNeeded = 
                    case filter ((==funName).originalFunctionName) declarationList of
                            [a] -> a
                            []  -> error $ "Function " ++ funName ++ " not found"
                            _   -> error "Unexpected error SC/01" 
            highLevelInterpreterWithModuleInfo
                (singleFunctionCompilationBody inputFileName outputFileName compilerMode originalFunctionSignatureNeeded)
            return ()