packages feed

cparsing-0.1.0.0: MiniC/MiniCPP.hs

-- | Preprocessor for C
module MiniC.MiniCPP where

import Text.Parsec
import Data.Maybe
import Data.List (find)
import Control.Monad.Trans.Class (lift)
import Control.Applicative hiding ((<|>), many, optional)
import System.Directory
import System.FilePath

import Text.Preprocess.Parser
import Text.Parsec.ExtraCombinators
import MiniC.Parser.Lexical

-- TODO: macro concatenation with ##
-- TODO: multiline macros with \
-- TODO: include directories
  
cPreproc = defaultPreprocessor 
  { macroDef = 
     do try $ newline *> symbol "#define"
        -- do not capture whitespace from macro value
        name <- identOrReserved
        params <- option [] (try (whiteSpace *> openParen) *> (identOrReserved `sepBy1` comma) <* char ')') 
        val <- manyTill anyChar (skip (lookAhead newline) <|> eof)
        return $ MacroDef name Just (execMacro name params val)
      <?> "macro definition"
  , macroAppl =
     do name <- try identOrReserved
        params <- option [] (char '(' *> (macroArg `sepBy1` char ',') <* char ')')
        return (MacroAppl name params)
      <?> "macro application"
  , includeDirective = \file -> 
     do try $ newline *> symbol "#include"
        name <- (char '"' *> many (noneOf "\"") <* char '"')
                   <|> (char '<' *> many (noneOf ">") <* char '>' )
        let incFile = combine (takeDirectory file) name
        exist <- lift $ doesFileExist incFile
        src <- lift $ if exist then readFile incFile else return ""
        return (src, incFile)
  , condDirective = \st -> 
     do ifdef <- try $ newline *> (try (symbol "#ifdef") *> return id
                                     <|> symbol "#ifndef" *> return not)
        macro <- identOrReserved
        let res = ifdef $ isJust (find ((==macro) . name) (defs st))
        thenB <- (if res then id else revertState) $ preprocess cPreproc
        elseB <- option [] (try (newline *> string "#else") 
                             *> (if res then revertState else id) (preprocess cPreproc))
        newline *> string "#endif" <?> "#endif"
        return [NoPP (if res then thenB else elseB)]
  , failDirective = try (newline *> char '#') *> fail "unexpected macro"
  , identToken = identOrReserved
  }
    where execMacro name params val (MacroAppl applName actParams)
            = let replaceParser :: Parsec String () [PPRes]
                  pAssoc = zip params actParams
                  replaceChoice = choice $ map (\(from,to) -> (try (string from) *> return (PPAgain to))) pAssoc
                                           ++ map (\(from, to) -> (try (string ("#"++from)) *> return (NoPP ("\""++to++"\"")))) pAssoc
                  replaceParser = many (replaceChoice 
                                          <|> (NoPP <$> identOrReserved) 
                                          <|> ((NoPP . (:[])) <$> anyChar)
                                          )
               in if name == applName 
                    then Just $ if length params /= length actParams 
                           then Left $ name ++ ": Expected " ++ show (length params) 
                                            ++ " arguments, received " ++ show (length actParams)
                           else Right $ either (error . ("Macro replace failed: "++) . show) id 
                                               (runParser replaceParser () "<macroreplace>" val)
                    else Nothing

                    
                    
macroArg :: Monad m => ParsecT String u m String
macroArg = macroArg' True

macroArg' :: Monad m => Bool -> ParsecT String u m String
macroArg' commaForb 
  = concat <$> many (((:[]) <$> noneOf ((if commaForb then (',':) else id) "()'\"")) 
                       <|> ((\a->"("++a++")") <$> (char '(' *> macroArg' False <* char ')'))
                       <|> ((\a->['\'',a,'\'']) <$> simpleCharLiteral)
                       <|> ((\a->'"':a++"\"") <$> simpleStringLiteral)
                       )
                       
    
-- | The source that could possibly be rewritten if there is a corresponding macro definition.    
data MacroAppl
  = MacroAppl { macroApplName :: String
              , macroApplParams :: [String]
              }