blob: 86e068a33d0485ec629b7680e7a25575d8e2995c [file] [log] [blame] [edit]
-- |
-- Module : Minigent.CLI
-- Copyright : (c) Data61 2018-2019
-- Commonwealth Science and Research Organisation (CSIRO)
-- ABN 41 687 119 230
-- License : BSD3
--
-- The command line interface of the compiler.
--
{-# LANGUAGE FlexibleContexts #-}
module Minigent.CLI
( main
, compiler
, Phase (..)
, Format (..)
, Output (..)
, Directive (..)
-- * Internal
, parsePhase
) where
import Minigent.Syntax
import Minigent.Syntax.Parser
import Minigent.Syntax.Lexer
import Minigent.Syntax.Utils
import Minigent.Syntax.PrettyPrint
import Minigent.Environment
import Minigent.Reorganiser
import Minigent.TC
import Minigent.Termination
import Minigent.CG
import Control.Monad
import Control.Applicative
import Control.Monad.Except
import Control.Monad.IO.Class
import System.Exit
import System.Environment
import System.IO
import System.FilePath
import System.Directory
import qualified Data.Map as M
import qualified Data.Text as T
import Data.List (intercalate)
import Text.Earley
import Data.Text.Prettyprint.Doc.Render.Terminal
import Data.Text.Prettyprint.Doc (unAnnotateS, unAnnotate, defaultLayoutOptions, layoutPretty, vcat, (<+>), pretty)
import Debug.Trace
-- | The phases of the compiler, ordered in the order listed.
data Phase = Lex | Parse | Reorg | TC | Term | CG deriving (Ord, Enum, Eq, Show)
-- | The way a dump should be formatted when printed.
data Format = PrettyColour -- ^ Print with a pretty printer and ANSI colours if printing to stdout
| PrettyPlain -- ^ Print with a pretty printer and no colours
| Raw -- ^ Print the raw abstract syntax tree (with 'Show')
deriving (Eq, Show)
-- | Where a dump should be output
data Output = File FilePath | Stdout
deriving (Eq, Show)
-- | The various command line arguments of the compiler.
data Directive
= Dump Phase Output Format
| NoColour
| GenTermGraph FilePath
deriving (Eq, Show)
singleToken :: MonadError String m => (String -> m a) -> [String] -> m ([String],a)
singleToken parse [] = throwError "argument expected"
singleToken parse (x:xs) = do
p <- parse x
pure (xs, p)
parseFormat :: MonadError String m => String -> m Format
parseFormat "pretty" = pure PrettyColour
parseFormat "pretty-plain" = pure PrettyPlain
parseFormat "raw" = pure Raw
parseFormat _ = throwError "invalid output format"
-- | Exposed only for tests.
parsePhase :: MonadError String m => String -> m Phase
parsePhase "lexer" = pure Lex
parsePhase "parse" = pure Parse
parsePhase "reorg" = pure Reorg
parsePhase "tc" = pure TC
parsePhase "term" = pure Term
parsePhase "cg" = pure CG
parsePhase _ = throwError "invalid phase"
parseExtantFilePath :: (MonadError String m, MonadIO m) => String -> m FilePath
parseExtantFilePath s = do
b <- liftIO $ doesFileExist s
if b then pure s else throwError ("file does not exist: " ++ s)
parseOutputFilePath :: (MonadError String m, MonadIO m) => String -> m FilePath
parseOutputFilePath s = do
b <- liftIO $ doesDirectoryExist (takeDirectory s)
if b then pure s else throwError ("directory does not exist: " ++ takeDirectory s)
parseDirectives :: (Alternative m, MonadError String m, MonadIO m) => [String] -> m ([Directive], [FilePath])
parseDirectives [] = return ([],[])
parseDirectives ("--dump-stdout":rest) = do
(rest', phase) <- singleToken parsePhase rest
(rest'', format) <- singleToken parseFormat rest' <|> pure (rest', PrettyColour)
(dirs, files) <- parseDirectives rest''
pure (Dump phase Stdout format: dirs, files)
parseDirectives ("--no-colour":rest) = do
(dirs, files) <- parseDirectives rest
pure (NoColour:dirs, files)
parseDirectives ("--gen-term-graph":rest) = do
(rest', outFile) <- singleToken parseOutputFilePath rest
(dirs, files) <- parseDirectives rest'
pure ((GenTermGraph outFile):dirs, files)
parseDirectives ("--dump":rest) = do
(rest', phase) <- singleToken parsePhase rest
(rest'', format) <- singleToken parseFormat rest' <|> pure (rest', PrettyPlain)
(rest''',outfile) <- singleToken parseOutputFilePath rest''
(dirs, files) <- parseDirectives rest'''
pure (Dump phase (File outfile) format: dirs, files)
parseDirectives (f:rest) = do
(dirs, files) <- parseDirectives rest
pure (dirs, f:files)
parseArgs :: [String] -> IO (Either String (Phase, [Directive], [FilePath]))
parseArgs [] = pure (Left "No argument given")
parseArgs (x:xs) = runExceptT $ do
phase <- parsePhase x
(dirs, files) <- parseDirectives xs
return (phase, dirs, files)
printHelp :: IO ()
printHelp = putStrLn $ unlines
[ "usage: minigent PHASE [DIRECTIVES ..] [FILE ..]"
, ""
, " Compiles up to a given phase, carrying out any relevant directives for each file."
, ""
, " PHASE - one of: lexer, parse, reorg, tc, cg. "
, ""
, " DIRECTIVES - one of: "
, " --dump PHASE [FORMAT] FILE (writes the output of the given phase to the given file)"
, " --dump-stdout PHASE [FORMAT] (writes the output of the given phase to stdout)"
, " --gen-term-graph FILE (generates a DOT graph for each function and writes them to the given file)"
, ""
, " FORMAT - one of: pretty, pretty-plain, raw"
, " If not provided, --dump will use pretty-plain and --dump-stdout will use pretty"
]
-- | Main function.
main :: IO ()
main = do
args <- getArgs
if args == ["--help"]
then printHelp
else do
args' <- parseArgs args
case args' of
Left err -> die ("minigent: " ++ err)
Right (phase, dirs, files) -> do
compiler phase dirs files
lexerPhase :: String -> IO [Token]
lexerPhase input =
let toks = lexer input
in case filter isUnknown toks of
[] -> pure toks
(Unknown c : _) -> do
die ("Lexer error. Unrecognized character '" ++ c :"'")
where
isUnknown (Unknown _) = True
isUnknown _ = False
parserPhase :: [Token] -> IO [RawTopLevel]
parserPhase input =
let (rs, rp) = fullParses (parser toplevels) input
in case rs of
[] -> die ("Parsing failed.\n" ++ show rp )
(x:_) -> pure x
reorgPhase :: [RawTopLevel] -> IO GlobalEnvironments
reorgPhase x =
let (envs, errs) = reorg x
in case errs of
[] -> pure envs
errs -> do
die ("Reorg failed.\n" ++ unlines errs)
tcPhase :: Bool -> GlobalEnvironments -> IO GlobalEnvironments
tcPhase colour envs = do
rs <- withUnifVars (tc envs (M.toList (defns envs)))
GlobalEnvs <$> (M.fromList . concat <$> mapM go rs) <*> pure (types envs)
where
handleColour = if not colour then unAnnotate else id
go (Left (f, cs)) = do
hPutStrLn stderr ("Typecheck failed in function " ++ f)
hPutDoc stderr (handleColour (vcat (map ((pretty " • " <+>) . prettyConstraint) cs)))
hPutStrLn stderr ""
pure []
go (Right b) = pure [b]
terminationPhase :: Bool -> GlobalEnvironments -> IO [(FunName, [Assertion], String)]
terminationPhase b envs
= let (errs, dumps) = termCheck envs in
case errs of
[] -> return dumps
xs -> do -- Error
mapM_ (hPutStrLn stderr) xs
return dumps
cgPhase :: GlobalEnvironments -> IO String
cgPhase gs = pure (cg gs)
lexerDump :: [Token] -> Directive -> IO ()
lexerDump toks (Dump Lex out fmt) =
write out (format fmt toks)
where
write (File f) = writeFile f
write (Stdout) = putStrLn
format Raw = show
format _ = unlines . map show
lexerDump _ _ = return ()
parserDump :: [RawTopLevel] -> Directive -> IO ()
parserDump tops (Dump Parse out fmt) =
write out (format fmt tops)
where
write (File f) = writeFile f
write (Stdout) = putStrLn
format Raw = show
format PrettyColour = T.unpack
. renderStrict
. layoutPretty defaultLayoutOptions
. vcat
. map prettyToplevel
format PrettyPlain = T.unpack
. renderStrict
. unAnnotateS
. layoutPretty defaultLayoutOptions
. vcat
. map prettyToplevel
parserDump _ _ = return ()
reorgDump :: GlobalEnvironments -> Directive -> IO ()
reorgDump tops (Dump Reorg out fmt) =
write out (format fmt tops)
where
write (File f) = writeFile f
write (Stdout) = putStrLn
format Raw = show
format PrettyColour = T.unpack
. renderStrict
. layoutPretty defaultLayoutOptions
. prettyGlobalEnvs
format PrettyPlain = T.unpack
. renderStrict
. unAnnotateS
. layoutPretty defaultLayoutOptions
. prettyGlobalEnvs
reorgDump _ _ = return ()
tcDump :: GlobalEnvironments -> Directive -> IO ()
tcDump tops (Dump TC out fmt) =
write out (format fmt tops)
where
write (File f) = writeFile f
write (Stdout) = putStrLn
format Raw = show
format PrettyColour = T.unpack
. renderStrict
. layoutPretty defaultLayoutOptions
. prettyGlobalEnvs
format PrettyPlain = T.unpack
. renderStrict
. unAnnotateS
. layoutPretty defaultLayoutOptions
. prettyGlobalEnvs
tcDump _ _ = return ()
termDump :: [(FunName, [Assertion], String)] -> Directive -> IO ()
termDump dumps (Dump Term out fmt) = do
write out $ intercalate "\n" $ map (\(f, as, _) -> "Function " ++ f ++ ":\n" ++ format fmt as) dumps
return ()
where
write (File f) = writeFile f
write (Stdout) = putStrLn
format Raw = ushow -- Necessary for showing unicode
format PrettyColour = T.unpack
. renderStrict
. layoutPretty defaultLayoutOptions
. vcat . map prettyAssertion
format PrettyPlain = T.unpack
. renderStrict
. unAnnotateS
. layoutPretty defaultLayoutOptions
. vcat . map prettyAssertion
termDump dumps (GenTermGraph f) = do
writeFile f $ intercalate "\n" $ map (\(_, _, g) -> g) dumps
return ()
termDump _ _ = return ()
cgDump :: String -> Directive -> IO ()
cgDump s (Dump CG out fmt) = write out s
where
write (File f) = writeFile f
write (Stdout) = putStrLn
cgDump _ _ = mempty
-- | Compile the given files up to the given phase, dumping
-- output according to the given directives.
compiler :: Phase -> [Directive] -> [FilePath] -> IO ()
compiler phase dirs [] = die "no input files given"
compiler phase dirs files = do
input <- unlines <$> mapM readFile files
upTo Lex
toks <- lexerPhase input
mapM_ (lexerDump toks) dirs
upTo Parse
ast <- parserPhase toks
mapM_ (parserDump ast) dirs
upTo Reorg
envs <- reorgPhase ast
mapM_ (reorgDump envs) dirs
upTo TC
binds <- tcPhase (NoColour `notElem` dirs) envs
mapM_ (tcDump binds) dirs
upTo Term
funDumps <- terminationPhase False envs
mapM_ (termDump funDumps) dirs
upTo CG
barf <- cgPhase binds
mapM_ (cgDump barf) dirs
where
upTo p = unless (p <= phase) exitSuccess