{- Mackerel: a strawman device definition DSL for Barrelfish Copyright (c) 2007-2011, ETH Zurich. All rights reserved. This file is distributed under the terms in the attached LICENSE file. If you do not find this file, copies can be found by writing to: ETH Zurich D-INFK, Universitaetstrasse 6, CH-8092 Zurich. Attn: Systems Group. -} module Main where import System.IO import System.IO.Error import System.Console.GetOpt import System.FilePath import System.Exit import System.Environment import Data.Maybe import Data.List import Text.ParserCombinators.Parsec as Parsec import Text.Printf import qualified MackerelParser import qualified BitFieldDriver import qualified ShiftDriver import Checks import Dev import Control.Exception -- -- Command line options and parsing code -- -- Datatypes for carrying command options around data Target = BitFieldDriver | ShiftDriver | NullDriver deriving (Eq, Show) data Options = Options { opt_infilename :: Maybe String, opt_outfilename :: Maybe String, opt_includedirs :: [String], opt_target :: Target, opt_usage_error :: Bool, opt_verbosity :: Integer } deriving (Show,Eq) defaultOptions :: Options defaultOptions = Options { opt_infilename = Nothing, opt_outfilename = Nothing, opt_includedirs = [], opt_target = ShiftDriver, opt_usage_error = False, opt_verbosity = 1 } -- For driving System.GetOpt options :: [OptDescr (Options -> Options)] options = [ Option ['c'] ["input-file"] (ReqArg (\f opts -> opts { opt_infilename = Just f } ) "file") "input file" , Option ['I'] ["include-dir"] (ReqArg (\ d opts -> opts { opt_includedirs = opt_includedirs opts ++ [d] }) "dir") "include directory (can be given multiple times)" , Option ['v'] ["verbose"] (NoArg (\ opts -> opts { opt_verbosity = opt_verbosity opts + 1 } )) "increase verbosity level" , Option ['o'] ["output"] (ReqArg (\ f opts -> opts { opt_outfilename = Just f }) "file") "output file name" , Option ['S'] ["shift-driver"] (NoArg (\ opts -> opts { opt_target = ShiftDriver } )) "use shift driver (default; preferred)" , Option ['n'] ["null-driver"] (NoArg (\ opts -> opts { opt_target = NullDriver } )) "use null output driver (don't generate any C)" , Option ['B'] ["bitfield-driver"] (NoArg (\ opts -> opts { opt_target = BitFieldDriver } )) "use bitfield driver (deprecrated: do not use)" ] -- -- Set the correct default input and output files -- defaultOutput :: Options -> Options defaultOutput opts = case opt_outfilename opts of (Just _) -> opts Nothing -> opts { opt_outfilename = Just (case opt_infilename opts of (Just i) -> ((dropExtension $ takeFileName i) ++ "_dev.h") Nothing -> "mackerel_output.h" ) } defaultInput :: Options -> [String] -> IO (Options) defaultInput opts [f] = if isNothing $ opt_infilename opts then return (defaultOutput (opts { opt_infilename = Just f })) else usageError [] defaultInput opts [] = if (isJust $ opt_infilename opts) then return (defaultOutput opts) else usageError [] defaultInput _ _ = usageError [] compilerOpts :: [String] -> IO (Options) compilerOpts argv = case getOpt Permute options argv of (o,n,[]) -> defaultInput (foldl (flip id) defaultOptions o) n (_,_,errs) -> usageError errs usageError :: [String] -> IO (Options) usageError errs = ioError (userError (concat errs ++ usageInfo usage options)) where usage = "Usage: mackerel " -- -- Processing source files --- -- Null compilation nullCompiler :: String -> String -> Dev.Rec -> String nullCompiler _ _ _ = "" -- Perform run-time checks run_checks :: String -> Dev.Rec -> IO String run_checks input_fn dev = case (Checks.check_all input_fn dev) of Just errors -> do { (hPutStrLn stderr (unlines [ e ++ "\n" | e <-errors])) ; System.Exit.exitWith (ExitFailure 1) } Nothing -> do { return "" } -- Parsing the input file into an AST parseFile :: String -> IO MackerelParser.DeviceFile parseFile fname = do src <- readFile fname case (runParser MackerelParser.devfile () fname src) of Left err -> ioError $ userError ("Parse error at: " ++ (show err)) Right x -> return x -- Traverse the include path to find an import file findImport :: [String] -> String -> IO MackerelParser.DeviceFile findImport [] f = ioError (userError $ printf "Can't find import '%s'" f) findImport (d:t) f = do catch (parseFile (d f)) (\e -> (if isDoesNotExistError e then findImport t f else ioError e)) -- Perform the transitive closure of all the imports resolveImports :: [MackerelParser.DeviceFile] -> [String] -> IO [MackerelParser.DeviceFile] resolveImports dfl path = let allimports = nub $ concat [ il | (MackerelParser.DeviceFile _ il) <- dfl ] gotimports = [ n | (MackerelParser.DeviceFile (MackerelParser.Device n _ _ _ _) _) <- dfl ] required = allimports \\ gotimports in case required of [] -> return dfl (t:_) -> do { i <- (findImport path (t ++ ".dev")) ; resolveImports (dfl ++ [i]) path } testentry :: IO () testentry = let input_fn = "../../devices/xapic.dev" output_fn = "x2apic.dev.h" includedirs = ["../../devices"] in do { hPutStrLn stdout ("IN: " ++ input_fn) ; hPutStrLn stdout ("OUT: " ++ output_fn) ; df <- parseFile input_fn ; dfl <- resolveImports [df] includedirs ; let dev = make_dev df (tail dfl) in do { _ <- run_checks input_fn dev ; outFileD <- openFile output_fn WriteMode ; hPutStr outFileD (ShiftDriver.compile input_fn output_fn dev) ; hClose outFileD } } -- Main entry point of Mackernel main :: IO () main = do { cli <- System.Environment.getArgs ; opts <- compilerOpts cli ; let input_fn = fromJust $ opt_infilename opts output_fn = fromJust $ opt_outfilename opts in do { df <- parseFile input_fn ; dfl <- resolveImports [df] (opt_includedirs opts) ; let dev = make_dev df (tail dfl) in do { _ <- run_checks input_fn dev ; outFileD <- openFile output_fn WriteMode ; hPutStr outFileD ((case (opt_target opts) of NullDriver -> nullCompiler ShiftDriver -> ShiftDriver.compile BitFieldDriver -> BitFieldDriver.compile) input_fn output_fn dev) ; hClose outFileD } } }