import Control.Monad (when) import System.Environment import System.Console.GetOpt import System.FilePath import System.IO import Language.SSVM.Types import Language.SSVM.Parser import Language.SSVM.Interpreter import Language.SSVM.Binary -- | Command-line flag data Flag = Mode Mode | Output FilePath deriving (Eq, Show) -- | Working mode data Mode = Interpret | Trace | TraceBytecode | Compile | Run | Decompile | Help deriving (Eq, Show) -- | config data RunMode = RunMode { mode :: Mode, inputFile :: FilePath, outputFile :: Maybe FilePath } deriving (Eq, Show) -- | Default run mode defaultMode :: RunMode defaultMode = RunMode { mode = Run, inputFile = "-", outputFile = Nothing } -- | Command-line options description options :: [OptDescr Flag] options = [ Option "c" ["compile"] (NoArg $ Mode Compile) "compile source code to bytecode", Option "i" ["interpret"] (NoArg $ Mode Interpret) "interpret source code", Option "e" ["run"] (NoArg $ Mode Run) "run compiled bytecode", Option "d" ["decompile"] (NoArg $ Mode Decompile) "decompile bytecode into pseudo source code", Option "t" ["trace"] (NoArg $ Mode Trace) "interpret and trace source code ", Option "" ["trace-bytecode"] (NoArg $ Mode TraceBytecode) "run and trace bytecode ", Option "o" ["output"] (ReqArg Output "FILE") "set output file name", Option "h" ["help"] (NoArg $ Mode Help) "show this help and exit" ] usage :: String usage = usageInfo header options where header = "Usage: ssvm [MODE] [-o OUTPUT] FILE" flags2runmode :: [Flag] -> RunMode flags2runmode flags = foldl go defaultMode flags where go acc (Mode m) = acc {mode = m} go acc (Output path) = acc {outputFile = Just path} parseCmdLine :: [String] -> Either String RunMode parseCmdLine args = case getOpt Permute options args of (flags, [], []) | Mode Help `elem` flags -> Right (flags2runmode flags) | otherwise -> Left "No input file" (flags, [file], []) -> Right $ (flags2runmode flags) {inputFile = file} (_, (_:_:_), []) -> Left "More than one input file" (_, _, errs) -> Left $ unlines errs ++ usage doInterpret :: FilePath -> IO () doInterpret path = do mbCode <- parseSourceFile path case mbCode of Left err -> fail (show err) Right code -> runVM (interpret code) doTrace :: FilePath -> IO () doTrace path = do mbCode <- parseSourceFile path case mbCode of Left err -> fail (show err) Right code -> traceVM (interpret code) doTraceBytecode :: FilePath -> IO () doTraceBytecode path = do code <- loadCode path traceVM (interpret code) doCompile :: FilePath -> Maybe FilePath -> IO () doCompile src mbdst = do let dst = case mbdst of Just x -> x Nothing -> replaceExtension src ".bytecode" mbCode <- parseSourceFile src case mbCode of Left err -> fail (show err) Right code -> dumpCode dst code doRun :: FilePath -> IO () doRun path = do code <- loadCode path runVM (interpret code) doDecompile :: FilePath -> IO () doDecompile path = do code <- loadCode path putStrLn $ showCode code main = do args <- getArgs case parseCmdLine args of Left err -> error err Right m -> do term <- hIsTerminalDevice stdout when term $ hSetBuffering stdout NoBuffering case mode m of Interpret -> doInterpret (inputFile m) Compile -> doCompile (inputFile m) (outputFile m) Run -> doRun (inputFile m) Decompile -> doDecompile (inputFile m) Trace -> doTrace (inputFile m) TraceBytecode -> doTraceBytecode (inputFile m) Help -> putStrLn usage