----------------------------------------------------------------------------- -- | -- Module : Language.C.Wrapper.Preprocess -- Copyright : (c) 2008 Benedikt Huber -- License : BSD-style -- Maintainer : benedikt.huber@gmail.com -- Stability : experimental -- Portability : portable -- -- Invoking external preprocessors. ----------------------------------------------------------------------------- module Language.C.System.Preprocess ( Preprocessor(..), CppOption(..), CppArgs(..),rawCppArgs,addCppOption,addExtraOption, runPreprocessor, isPreprocessed, ) where import Language.C.Data.InputStream import System.Exit import System.Directory import System.FilePath import System.Environment import System.IO import Control.Exception import Control.Monad import Data.List -- | 'Preprocessor' encapsulates the abstract interface for invoking C preprocessors class Preprocessor cpp where -- | parse the given command line arguments, and return a pair of parsed and ignored arguments parseCPPArgs :: cpp -> [String] -> Either String (CppArgs, [String]) -- | run the preprocessor runCPP :: cpp -> CppArgs -> IO ExitCode -- | file extension of a preprocessed file preprocessedExt :: String preprocessedExt = ".i" -- | Generic Options for the preprocessor data CppOption = IncludeDir FilePath | Define String String | Undefine String | IncludeFile FilePath -- | Generic arguments for the preprocessor data CppArgs = CppArgs { cppOptions :: [CppOption], extraOptions :: [String], cppTmpDir :: Maybe FilePath, inputFile :: FilePath, outputFile :: Maybe FilePath } -- | Cpp arguments that only specify the input file name. cppFile :: FilePath -> CppArgs cppFile input_file = CppArgs { cppOptions = [], extraOptions = [], cppTmpDir = Nothing, inputFile = input_file, outputFile = Nothing } -- | use the given preprocessor arguments without analyzing them rawCppArgs :: [String] -> FilePath -> CppArgs rawCppArgs opts input_file = CppArgs { inputFile = input_file, cppOptions = [], extraOptions = opts, outputFile = Nothing, cppTmpDir = Nothing } -- | add a typed option to the given preprocessor arguments addCppOption :: CppArgs -> CppOption -> CppArgs addCppOption cpp_args opt = cpp_args { cppOptions = opt : (cppOptions cpp_args) } -- | add a string option to the given preprocessor arguments addExtraOption :: CppArgs -> String -> CppArgs addExtraOption cpp_args extra = cpp_args { extraOptions = extra : (extraOptions cpp_args) } -- | run the preprocessor and return an 'InputStream' if preprocesssing succeeded runPreprocessor :: (Preprocessor cpp) => cpp -> CppArgs -> IO (Either ExitCode InputStream) runPreprocessor cpp cpp_args = do bracket getActualOutFile -- remove outfile if it was temporary removeTmpOutFile -- invoke preprocessor invokeCpp where getActualOutFile :: IO FilePath getActualOutFile = maybe (mkOutputFile (cppTmpDir cpp_args) (inputFile cpp_args)) return (outputFile cpp_args) invokeCpp actual_out_file = do exit_code <- runCPP cpp (cpp_args { outputFile = Just actual_out_file}) case exit_code of ExitSuccess -> liftM Right (readInputStream actual_out_file) ExitFailure _ -> return $ Left exit_code removeTmpOutFile out_file = maybe (removeFile out_file) (\_ -> return ()) (outputFile cpp_args) -- | create an output file, given @Maybe tmpdir@ and @inputfile@ mkOutputFile :: Maybe FilePath -> FilePath -> IO FilePath mkOutputFile tmp_dir_opt input_file = do tmpDir <- getTempDir tmp_dir_opt mkTmpFile tmpDir (getOutputFileName input_file) where getTempDir (Just tmpdir) = return tmpdir getTempDir Nothing = getTemporaryDirectory -- | compute output file name from input file name getOutputFileName :: FilePath -> FilePath getOutputFileName fp | hasExtension fp = replaceExtension filename preprocessedExt | otherwise = addExtension filename preprocessedExt where filename = takeFileName fp -- | create a temporary file mkTmpFile :: FilePath -> FilePath -> IO FilePath mkTmpFile tmp_dir file_templ = do -- putStrLn $ "TmpDir: "++tmp_dir -- putStrLn $ "FileTempl: "++file_templ (path,file_handle) <- openTempFile tmp_dir file_templ hClose file_handle return path -- | guess whether a file is preprocessed (file end with .i) isPreprocessed :: FilePath -> Bool isPreprocessed = (".i" `isSuffixOf`)