{-
Tock: a compiler for parallel languages
Copyright (C) 2007 University of Kent
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see .
-}
-- | Driver for the compiler.
module Main (main) where
import Control.Monad.Error
import Control.Monad.Identity
import Control.Monad.State
import Control.Monad.Writer
import Data.Either
import Data.Generics
import Data.Maybe
import qualified Data.Set as Set
import List
import System
import System.Console.GetOpt
import System.Directory
import System.Exit
import System.IO
import System.Process
import AnalyseAsm
import qualified AST as A
import CompilerCommands
import CompState
import Errors
import FlowGraph
import GenerateC
import GenerateCPPCSP
import LexOccam
import Metadata
import ParseOccam
import ParseRain
import Pass
import PassList
import PreprocessOccam
import PrettyShow
import ShowCode
import Utils
type OptFunc = CompState -> IO CompState
optionsNoWarnings :: [OptDescr OptFunc]
optionsNoWarnings =
[ Option [] ["backend"] (ReqArg optBackend "BACKEND")
"code-generating backend (options: c, cppcsp, dumpast, src)"
, Option ['h'] ["help"] (NoArg optPrintHelp) "print this help"
, Option [] ["help-warnings"] (NoArg optPrintWarningHelp)
"print help about warning options"
, Option ['k'] ["keep-temporaries"] (NoArg $ optKeepTemporaries) "keep temporary files"
, Option ['f'] ["compiler-flags"] (ReqArg optCompilerFlags "FLAGS") "flags for C/C++ compiler"
, Option [] ["external-link"] (ReqArg optCompilerLinkFlags "FLAGS") "link flags for C/C++ compiler"
, Option [] ["run-indent"] (NoArg $ optRunIndent) "run indent on source before compilation (will full mode)"
, Option [] ["frontend"] (ReqArg optFrontend "FRONTEND") "language frontend (options: occam, rain)"
, Option [] ["mode"] (ReqArg optMode "MODE") "select mode (options: flowgraph, lex, html, parse, compile, post-c, full)"
, Option [] ["no-main"] (NoArg optNoMain) "file has no main process; do not link either"
, Option ['o'] ["output"] (ReqArg optOutput "FILE") "output file (default \"-\")"
, Option [] ["sanity-check"] (ReqArg optSanityCheck "SETTING") "internal sanity check (options: on, off)"
, Option [] ["occam2-mobility"] (ReqArg optClassicOccamMobility "SETTING") "occam2 implicit mobility (EXPERIMENTAL) (options: on, off)"
, Option [] ["usage-checking"] (ReqArg optUsageChecking "SETTING") "usage checking (options: on, off)"
, Option [] ["unknown-stack-size"] (ReqArg optStackSize "BYTES")
"stack amount to allocate for unknown C functions"
, Option ['v'] ["verbose"] (NoArg $ optVerbose) "be more verbose (use multiple times for more detail)"
]
optionsWarnings :: [OptDescr OptFunc]
optionsWarnings = concat
[[Option [] ["w" ++ show w] (NoArg $ optEnableWarning w)
("Enable warning " ++ show w ++ " (" ++ describeWarning w ++ ")")]
++ [Option [] ["wno" ++ show w] (NoArg $ optDisableWarning w)
("Disable warning " ++ show w ++ " (" ++ describeWarning w ++ ")")]
| w <- [minBound .. maxBound]]
optMode :: String -> OptFunc
optMode s ps
= do mode <- case s of
"compile" -> return ModeCompile
"flowgraph" -> return ModeFlowGraph
"full" -> return ModeFull
"parse" -> return ModeParse
"post-c" -> return ModePostC
"lex" -> return ModeLex
"html" -> return ModeHTML
_ -> dieIO (Nothing, "Unknown mode: " ++ s)
return $ ps { csMode = mode }
optBackend :: String -> OptFunc
optBackend s ps
= do backend <- case s of
"c" -> return BackendC
"cppcsp" -> return BackendCPPCSP
"dumpast" -> return BackendDumpAST
"src" -> return BackendSource
_ -> dieIO (Nothing, "Unknown backend: " ++ s)
return $ ps { csBackend = backend }
optFrontend :: String -> OptFunc
optFrontend s ps
= do frontend <- case s of
"occam" -> return FrontendOccam
"rain" -> return FrontendRain
_ -> dieIO (Nothing, "Unknown frontend: " ++ s)
return $ ps { csFrontend = frontend }
optCompilerFlags :: String -> OptFunc
optCompilerFlags flags ps = return $ ps { csCompilerFlags = flags ++ " " ++ csCompilerFlags ps}
optCompilerLinkFlags :: String -> OptFunc
optCompilerLinkFlags flags ps = return $ ps { csCompilerLinkFlags = flags ++ " " ++ csCompilerLinkFlags ps}
optVerbose :: OptFunc
optVerbose ps = return $ ps { csVerboseLevel = csVerboseLevel ps + 1 }
optKeepTemporaries :: OptFunc
optKeepTemporaries ps = return $ ps { csKeepTemporaries = True }
optRunIndent :: OptFunc
optRunIndent ps = return $ ps { csRunIndent = True }
optNoMain :: OptFunc
optNoMain ps = return $ ps { csHasMain = False }
optStackSize :: String -> OptFunc
optStackSize s ps = return $ ps { csUnknownStackSize = read s }
optOutput :: String -> OptFunc
optOutput s ps = return $ ps { csOutputFile = s }
optPrintHelp :: OptFunc
optPrintHelp _ = dieIO (Nothing, usageInfo "Usage: tock [OPTION...] SOURCEFILE" optionsNoWarnings)
optPrintWarningHelp :: OptFunc
optPrintWarningHelp _ = dieIO (Nothing, usageInfo "Usage: tock [OPTION...] SOURCEFILE" optionsWarnings)
optOnOff :: (String, Bool -> CompState -> CompState) -> String -> OptFunc
optOnOff (n, f) s ps
= do mode <- case s of
"on" -> return True
"off" -> return False
_ -> dieIO (Nothing, "Unknown " ++ n ++ " mode: " ++ s)
return $ f mode ps
optUsageChecking :: String -> OptFunc
optUsageChecking = optOnOff ("usage checking", \m ps -> ps { csUsageChecking = m })
optSanityCheck :: String -> OptFunc
optSanityCheck = optOnOff ("sanity checking", \m ps -> ps { csSanityCheck = m })
optClassicOccamMobility :: String -> OptFunc
optClassicOccamMobility = optOnOff ("occam 2 mobility", \m ps -> ps { csClassicOccamMobility = m })
optEnableWarning :: WarningType -> OptFunc
optEnableWarning w ps = return $ ps { csEnabledWarnings = Set.insert w (csEnabledWarnings ps) }
optDisableWarning :: WarningType -> OptFunc
optDisableWarning w ps = return $ ps { csEnabledWarnings = Set.delete w (csEnabledWarnings ps) }
getOpts :: [String] -> IO ([OptFunc], [String])
getOpts argv =
case getOpt RequireOrder (optionsNoWarnings ++ optionsWarnings) argv of
(o,n,[] ) -> return (o,n)
(_,_,errs) -> error (concat errs ++ usageInfo header optionsNoWarnings)
where header = "Usage: tock [OPTION...] SOURCEFILE"
main :: IO ()
main = do
argv <- getArgs
(opts, args) <- getOpts argv
let fn = case args of
[fn] -> fn
_ -> error "Must specify a single input file (use \"tock --help\" to see options)"
-- Try to guess the filename from the extension. Since this function is
-- applied before the options are applied, it will be overriden by the
-- --frontend=x command-line option
let (frontendGuess, fileStem)
= if ".occ" `isSuffixOf` fn
then (\ps -> ps {csFrontend = FrontendOccam},
Just $ take (length fn - length ".occ") fn)
else if ".rain" `isSuffixOf` fn
then (\ps -> ps {csFrontend = FrontendRain},
Just $ take (length fn - length ".rain") fn)
else (id, Nothing)
initState <- foldl (>>=) (return $ frontendGuess emptyState) opts
let operation
= case csMode initState of
ModePostC -> useOutputOptions (postCAnalyse fn)
ModeFull -> evalStateT (compileFull fn fileStem) []
mode -> useOutputOptions (compile mode fn)
-- Run the compiler.
v <- runPassM initState operation
case v of
(Left e, cs) -> showWarnings (csWarnings cs) >> dieIO e
(Right r, cs) -> showWarnings (csWarnings cs)
removeFiles :: [FilePath] -> IO ()
removeFiles = mapM_ (\file -> catch (removeFile file) doNothing)
where
doNothing :: IOError -> IO ()
doNothing _ = return ()
-- When we die inside the StateT [FilePath] monad, we should delete all the
-- temporary files listed in the state, then die in the PassM monad:
-- TODO: Not totally sure this technique works if functions inside the PassM
-- monad die, but there will only be temp files to clean up if postCAnalyse
-- dies
instance Die (StateT [FilePath] PassM) where
dieReport err
= do files <- get
-- If removing the files fails, we don't want to die with that
-- error; we want the user to see the original error, so ignore
-- errors arising from removing the files:
optsPS <- lift $ getCompState
when (not $ csKeepTemporaries optsPS) $
liftIO $ removeFiles files
lift $ dieReport err
compileFull :: String -> Maybe String -> StateT [FilePath] PassM ()
compileFull inputFile moutputFile
= do optsPS <- lift get
outputFile <- case (csOutputFile optsPS, moutputFile) of
-- If the user hasn't given an output file, we guess by
-- using a stem (input file minus known extension).
-- If the extension isn't known, the user must specify
-- the output file
("-", Just file) -> return $ file ++ ".tock"
("-", Nothing) -> dieReport (Nothing, "Must specify an output file when using full-compile mode")
(file, _) -> return file
let extension = case csBackend optsPS of
BackendC -> ".c"
BackendCPPCSP -> ".cpp"
_ -> ""
-- Translate input file to C/C++
let cFile = outputFile ++ extension
hFile = outputFile ++ ".h"
iFile = outputFile ++ ".inc"
lift $ modify $ \cs -> cs { csOutputIncFile = Just iFile }
lift $ withOutputFile cFile $ \hb ->
withOutputFile hFile $ \hh ->
compile ModeCompile inputFile ((hb, hh), hFile)
noteFile cFile
when (csRunIndent optsPS) $
exec $ "indent " ++ cFile
shouldLink <- lift getCompState >>* csHasMain
case csBackend optsPS of
BackendC ->
let sFile = outputFile ++ ".s"
oFile = outputFile ++ ".o"
postCFile = outputFile ++ "_post.c"
postOFile = outputFile ++ "_post.o"
occFile = outputFile ++ "_wrapper.occ"
in
do sequence_ $ map noteFile [sFile, oFile, postCFile, postOFile, occFile]
-- Compile the C into assembly, and assembly into an object file
exec $ cAsmCommand cFile sFile (csCompilerFlags optsPS)
exec $ cCommand sFile oFile (csCompilerFlags optsPS)
-- Analyse the assembly for stack sizes, and output a
-- "post" C file
lift $ withOutputFile postCFile $ \h -> postCAnalyse sFile ((h,intErr),intErr)
-- Compile this new "post" C file into an object file
exec $ cCommand postCFile postOFile (csCompilerFlags optsPS)
cs <- lift getCompState
let otherOFiles = [usedFile ++ ".tock.o"
| usedFile <- Set.toList $ csUsedFiles cs]
-- Link the object files into a binary
when shouldLink $
exec $ cLinkCommand (oFile : postOFile : otherOFiles) outputFile (csCompilerLinkFlags optsPS)
-- For C++, just compile the source file directly into a binary
BackendCPPCSP ->
exec $ cxxCommand cFile outputFile
(csCompilerFlags optsPS ++ " " ++ csCompilerLinkFlags optsPS)
_ -> dieReport (Nothing, "Cannot use specified backend: "
++ show (csBackend optsPS)
++ " with full-compile mode")
-- Finally, remove the temporary files:
tempFiles <- get
when (not $ csKeepTemporaries optsPS) $
liftIO $ removeFiles tempFiles
where
intErr :: a
intErr = error "Internal error involving handles"
noteFile :: Monad m => FilePath -> StateT [FilePath] m ()
noteFile fp = modify (\fps -> (fp:fps))
withOutputFile :: FilePath -> (Handle -> PassM ()) -> PassM ()
withOutputFile path func
= do handle <- liftIO $ openFile path WriteMode
func handle
liftIO $ hClose handle
exec :: String -> StateT [FilePath] PassM ()
exec cmd = do lift $ progress $ "Executing command: " ++ cmd
p <- liftIO $ runCommand cmd
exitCode <- liftIO $ waitForProcess p
case exitCode of
ExitSuccess -> return ()
ExitFailure n -> dieReport (Nothing, "Command \"" ++ cmd ++ "\" failed: exited with code: " ++ show n)
-- | Picks out the handle from the options and passes it to the function:
useOutputOptions :: (((Handle, Handle), String) -> PassM ()) -> PassM ()
useOutputOptions func
= do optsPS <- get
withHandleFor (csOutputFile optsPS) $ \hb ->
withHandleFor (csOutputHeaderFile optsPS) $ \hh ->
func ((hb, hh), csOutputHeaderFile optsPS)
where
withHandleFor "-" func = func stdout
withHandleFor file func =
do progress $ "Writing output file " ++ file
f <- liftIO $ openFile file WriteMode
func f
liftIO $ hClose f
showTokens :: Bool -> [Token] -> String
showTokens html ts = evalState (mapM showToken ts >>* spaceOut) 0
where
spaceOut = foldl join ""
join prev (Right str) = prev ++ " " ++ str
join prev (Left spacing)
| spacing >= 0 = prev ++ concat (replicate spacing space)
| spacing < 0 = foldl (.) id (replicate (length space * negate spacing) init) $ prev
showToken (Token _ tt) = showTokenType tt
showTokenType :: TokenType -> State Int (Either Int String)
showTokenType (TokReserved s) = ret $ h s
showTokenType (TokIdentifier s) = ret s
showTokenType (TokStringCont s) = ret s
showTokenType (TokStringLiteral s) = ret s
showTokenType (TokCharLiteral s) = ret s
showTokenType (TokIntLiteral s) = ret s
showTokenType (TokHexLiteral s) = ret s
showTokenType (TokRealLiteral s) = ret s
showTokenType (TokPreprocessor s) = ret $ h s
showTokenType (IncludeFile s) = ret $ h "#INCLUDE \"" ++ s ++ "\""
showTokenType (Pragma s) = ret $ h "#PRAGMA " ++ s
showTokenType (Indent) = modify (+2) >> return (Left 2)
showTokenType (Outdent) = modify (subtract 2) >> return (Left (-2))
showTokenType (EndOfLine)
= do indentation <- get
ret $ newline ++ concat (replicate indentation space)
ret :: String -> State Int (Either Int String)
ret = return . Right
(space, newline, h) = if html
then (" ", "
\n", \s -> "" ++ s ++ "")
else (" ", "\n", id)
-- | Compile a file.
-- This is written in the PassM monad -- as are most of the things it calls --
-- because then it's very easy to pass the state around.
compile :: CompMode -> String -> ((Handle, Handle), String) -> PassM ()
compile mode fn (outHandles@(outHandle, _), headerName)
= do optsPS <- get
debug "{{{ Parse"
progress "Parse"
(ast1, lexed) <- case csFrontend optsPS of
FrontendOccam ->
do lexed <- preprocessOccamProgram fn
case mode of
-- In lex mode, don't parse, because it will probably fail anyway:
ModeLex -> return (A.Only emptyMeta (), lexed)
ModeHTML -> return (A.Only emptyMeta (), lexed)
_ -> do parsed <- parseOccamProgram lexed
return (parsed, lexed)
FrontendRain -> do parsed <- liftIO (readFile fn) >>= parseRainProgram fn
return (parsed, [])
debugAST ast1
debug "}}}"
case mode of
ModeLex -> liftIO $ hPutStr outHandle $ pshow lexed
ModeHTML -> liftIO $ hPutStr outHandle $ showTokens True lexed
ModeParse -> liftIO $ hPutStr outHandle $ pshow ast1
ModeFlowGraph ->
do procs <- findAllProcesses
let fs :: Data t => t -> PassM String
fs = ((liftM $ (take 20) . (filter ((/=) '\"'))) . pshowCode)
let labelFuncs = mkLabelFuncsGeneric fs
graphs <- mapM
((liftM $ either (const Nothing) Just) . (buildFlowGraphP labelFuncs) )
(map (A.Only emptyMeta) (snd $ unzip $ procs))
-- We need this line to enforce the type of the mAlter monad (Identity)
-- since it is never used. Then we used graphsTyped (rather than graphs)
-- to prevent a compiler warning at graphsTyped being unused;
-- graphs is of course identical to graphsTyped, as you can see here:
let (graphsTyped :: [Maybe (FlowGraph' Identity String A.Process)])
= map (transformMaybe $ \(x,_,_) -> x) graphs
-- TODO: output each process to a separate file, rather than just taking the first:
liftIO $ hPutStr outHandle $ head $ map makeFlowGraphInstr (catMaybes graphsTyped)
ModeCompile ->
do progress "Passes:"
passes <- calculatePassList
ast2 <- runPasses passes ast1
debug "{{{ Generate code"
progress $ "- Backend: " ++ (show $ csBackend optsPS)
let generator :: A.AST -> PassM ()
generator
= case csBackend optsPS of
BackendC -> generateC outHandles headerName
BackendCPPCSP -> generateCPPCSP outHandles headerName
BackendDumpAST -> liftIO . hPutStr outHandle . pshow
BackendSource -> (liftIO . hPutStr outHandle) <.< showCode
generator ast2
debug "}}}"
progress "Done"
-- | Analyse an assembly file.
postCAnalyse :: String -> ((Handle, Handle), String) -> PassM ()
postCAnalyse fn ((outHandle, _), _)
= do asm <- liftIO $ readFile fn
progress "Analysing assembly"
output <- analyseAsm asm
liftIO $ hPutStr outHandle output