kempe-0.2.0.9: run/Main.hs
module Main (main) where
import Control.Exception (Exception, throwIO)
import Control.Monad ((<=<))
import qualified Data.ByteString.Lazy as BSL
import Data.Semigroup ((<>))
import qualified Data.Text.Lazy.IO as TLIO
import qualified Data.Version as V
import Kempe.AST
import Kempe.File
import Kempe.Lexer
import Kempe.Parser
import Language.C.AST
import Options.Applicative
import qualified Paths_kempe as P
import Prettyprinter (LayoutOptions (LayoutOptions), PageWidth (AvailablePerLine), hardline, layoutSmart)
import Prettyprinter.Render.Text (putDoc, renderIO, renderLazy)
import System.Exit (ExitCode (ExitFailure), exitWith)
import System.IO (stdout)
import System.Info (arch)
data Arch = Aarch64
| X64
data Command = TypeCheck !FilePath
| Compile !FilePath !(Maybe FilePath) !Arch !Bool !Bool !Bool
| Format !FilePath
| Lint !FilePath
| CDecl !FilePath !(Maybe FilePath)
cdecl :: FilePath -> IO ()
cdecl = putDoc . (<> hardline) . prettyHeaders <=< cDeclFile
writeCDecl :: FilePath -> FilePath -> IO ()
writeCDecl fp o = do
ds <- cDeclFile fp
TLIO.writeFile o (renderLazy $ layoutSmart cSettings $ prettyHeaders ds)
fmt :: FilePath -> IO ()
fmt = renderIO stdout <=< fmap (render . (<> hardline) . prettyModule) . parsedFp
where render = layoutSmart settings
settings = LayoutOptions $ AvailablePerLine 80 0.5
parsedFp :: FilePath -> IO (Module AlexPosn AlexPosn AlexPosn)
parsedFp fp = do
contents <- BSL.readFile fp
yeetIO $ parse contents
yeetIO :: Exception e => Either e a -> IO a
yeetIO = either throwIO pure
run :: Command -> IO ()
run (TypeCheck fp) = either throwIO pure =<< tcFile fp
run (Lint fp) = maybe (pure ()) throwIO =<< warnFile fp
run (Compile _ Nothing _ _ False False) = putStrLn "No output file specified!"
run (Compile fp (Just o) X64 dbg False False) = compile fp o dbg
run (Compile fp (Just o) Aarch64 dbg False False) = armCompile fp o dbg
run (Compile fp Nothing _ False True False) = irFile fp
run (Compile fp Nothing X64 False False True) = x86File fp
run (Compile fp Nothing Aarch64 False False True) = armFile fp
run (Format fp) = fmt fp
run (CDecl fp Nothing) = cdecl fp
run (CDecl fp (Just o)) = writeCDecl fp o
run _ = putStrLn "Invalid combination of CLI options. Try kc --help" *> exitWith (ExitFailure 1)
kmpFile :: Parser FilePath
kmpFile = argument str
(metavar "FILE"
<> help "Source file"
<> kmpCompletions)
fmtP :: Parser Command
fmtP = Format <$> kmpFile
lintP :: Parser Command
lintP = Lint <$> kmpFile
cdeclP :: Parser Command
cdeclP = CDecl <$> kmpFile <*> outFile
debugSwitch :: Parser Bool
debugSwitch = switch
(long "debug"
<> short 'g'
<> help "Include debug symbols")
archFlag :: Parser Arch
archFlag = fmap parseArch $ optional $ strOption
(long "arch"
<> metavar "ARCH"
<> help "Target architecture (x64 or aarch64)"
<> completer (listCompleter ["x64", "aarch64"]))
where parseArch :: Maybe String -> Arch
parseArch str' = case (str', arch) of
(Nothing, "aarch64") -> Aarch64
(Nothing, "x86_64") -> X64
(Just "aarch64", _) -> Aarch64
(Just "arm64", _) -> Aarch64
(Just "x64", _) -> X64
(Just "x86_64", _) -> X64
(Just "x86-64", _) -> X64
(Just "amd64", _) -> X64
_ -> error "Failed to parse architecture! Try one of x64, aarch64"
irSwitch :: Parser Bool
irSwitch = switch
(long "dump-ir"
<> help "Write intermediate representation to stdout")
asmSwitch :: Parser Bool
asmSwitch = switch
(long "dump-asm"
<> help "Write assembly (intel syntax) to stdout")
outFile :: Parser (Maybe FilePath)
outFile = optional $ argument str
(metavar "OUTPUT"
<> help "File output")
kmpCompletions :: HasCompleter f => Mod f a
kmpCompletions = completer . bashCompleter $ "file -X '!*.kmp' -o plusdirs"
commandP :: Parser Command
commandP = hsubparser
(command "typecheck" (info tcP (progDesc "Type-check module contents"))
<> command "lint" (info lintP (progDesc "Lint a file")))
<|> hsubparser (command "fmt" (info fmtP (progDesc "Pretty-print a Kempe file")) <> internal)
<|> hsubparser (command "cdecl" (info cdeclP (progDesc "Generate C headers for exported Kempe code")))
<|> compileP
where
tcP = TypeCheck <$> kmpFile
compileP = Compile <$> kmpFile <*> outFile <*> archFlag <*> debugSwitch <*> irSwitch <*> asmSwitch
wrapper :: ParserInfo Command
wrapper = info (helper <*> versionMod <*> commandP)
(fullDesc
<> progDesc "Kempe language compiler for X86_64 and Aarch64"
<> header "Kempe - a stack-based language")
versionMod :: Parser (a -> a)
versionMod = infoOption (V.showVersion P.version) (short 'V' <> long "version" <> help "Show version")
main :: IO ()
main = run =<< execParser wrapper