module HeaderParser(parseHeader) where import Data.List import Data.Maybe import Data.Char import Control.Applicative hiding (many, (<|>), optional) import qualified Data.Map as M import Text.ParserCombinators.Parsec import HeaderData data HeaderState = HeaderState { namespacestack :: [String] , classstack :: [(InheritLevel, String)] } deriving (Eq, Read, Show) pushNamespace n = updateState (\h -> h{namespacestack = n:(namespacestack h)}) popNamespace = updateState (\h -> h{namespacestack = tail (namespacestack h)}) pushClass n = updateState (\h -> h{classstack = n:(classstack h)}) popClass = updateState (\h -> h{classstack = tail (classstack h)}) setLevel l = do n <- classstack <$> getState case n of ((_, c):ms) -> do let cn = ((l,c):ms) updateState (\h -> h{classstack = cn}) _ -> return () header :: CharParser HeaderState Header header = many oneobj oneobj :: CharParser HeaderState Object oneobj = do spaces _ <- many eos w <- gettype case w of "namespace" -> namespace (many1 oneobj) "class" -> classDecl "struct" -> try structDecl <|> varFunDecl "struct" "typedef" -> typedef "enum" -> enum "extern" -> extern "using" -> using _ -> macro w <|> varFunDecl w extern :: CharParser HeaderState Object extern = do spaces en <- quoted spaces objs <- try manyext <|> (do o <- oneobj; return [o]) return $ ExternDecl en objs where manyext = do _ <- char '{' os <- many oneobj spaces _ <- char '}' spaces return os using :: CharParser HeaderState Object using = do _ <- many1 whitespace (n, iden) <- try (do _ <- string "namespace" _ <- many1 whitespace i <- identifier return (True, i)) <|> (do i <- typename return (False, i)) spaces _ <- eos return $ Using n iden -- macro is a hack. macro w = do _ <- try (char '(' >> spaces >> optional (paramDecl Nothing) >> spaces >> char ')') spaces _ <- optional (eos) return $ VarDecl (ParamDecl w "macro" Nothing Nothing) Nothing enum :: CharParser HeaderState Object enum = do _ <- many1 whitespace n <- identifier spaces _ <- char '{' vals <- sepBy1 enumVal (char ',') spaces optional (char ',') spaces _ <- char '}' spaces _ <- eos spaces cls <- classstack <$> getState return $ EnumDef n vals cls enumVal = do spaces ev <- identifier spaces val <- optionMaybe (char '=' >> spaces >> many1 valuechar) spaces return $ EnumVal ev val typedef :: CharParser HeaderState Object typedef = do allchars <- many1 typedefchar spaces _ <- eos let ns = words allchars return $ TypeDef (intercalate " " (init ns), last ns) gettype :: CharParser u String gettype = concat <$> many1 (typename <|> templ) templ = do _ <- char '<' v <- manyTill anyChar (char '>') return ('<' : (v ++ ">")) getvalue :: CharParser u String getvalue = quoted <|> many1 valuechar quoted :: CharParser u String quoted = do _ <- char '"' v <- manyTill anyChar (char '"') return ('"' : (v ++ "\"")) valuechar = oneOf valuechars valuechars = typechars ++ ".-" typename = many1 typechar typechar = oneOf typechars typechars = idChar ++ "*:&" typedefchar = oneOf typedefchars typedefchars = typechars ++ " \t,<>\r\n" structDecl = classDecl' Public classDecl = classDecl' Private classDecl' lev = do _ <- many1 whitespace optional (char '_' >> identifier >> spaces) n <- identifier spaces inherits <- option [] inheritDecls spaces ns <- namespacestack <$> getState cs <- classstack <$> getState (eos >> return (ClassDecl n inherits cs ns [])) <|> clconts cs ns n inherits lev clconts cs ns n inherits lev = do _ <- char '{' spaces pushClass (lev, n) ret <- clcontents popClass spaces _ <- char '}' spaces _ <- eos spaces return $ ClassDecl n inherits cs ns ret clcontents :: CharParser HeaderState [(InheritLevel, Object)] clcontents = do spaces objs <- many $ do spaces mparse <- optionMaybe (many1 (try setinheritlevel <|> try frienddecl)) case mparse of Just _ -> return Nothing -- ignore friend declarations Nothing -> do obj <- try specialClassFunction <|> oneobj vis <- getVisibility <$> getState let vn = case vis of Nothing -> Public Just (v, _) -> v return $ Just (vn, obj) return $ catMaybes objs -- constructor or destructor. specialClassFunction = do spaces optional (string "virtual") spaces cname <- (snd . head . classstack) <$> getState fn <- ((string ('~' : cname)) <|> string cname) spaces funDecl fn "" inheritDecls = char ':' >> spaces >> sepBy inh (char ',') where inh = do spaces l <- inheritl spaces n <- gettype spaces return $ InheritDecl n l inheritance = do _ <- char ':' spaces inheritl capitalize [] = [] capitalize (x:xs) = toUpper x : xs inheritl = do spaces try (string "public" >> return Public) <|> try (string "protected" >> return Protected) <|> (string "private" >> return Private) frienddecl = do _ <- string "friend" spaces _ <- string "class" spaces _ <- identifier spaces _ <- eos spaces return () -- end of statement eos :: CharParser u () eos = char ';' >> spaces >> many eos >> return () setinheritlevel = do str <- try (string "public") <|> try (string "protected") <|> string "private" spaces _ <- char ':' spaces setLevel $ case str of "public" -> Public "protected" -> Protected _ -> Private whitespace = oneOf (" \t\n\r") namespace :: CharParser HeaderState [Object] -> CharParser HeaderState Object namespace nscont = do _ <- many1 whitespace n <- option "" identifier spaces _ <- char '{' spaces pushNamespace n ret <- nscont popNamespace spaces _ <- char '}' spaces return $ Namespace n ret identList = many1 ((concat <$> (many1 (try opr <|> gettype))) >>= \t -> spaces >> return t) "type" opr = do st <- string "operator" spaces vl <- oprchars return $ st ++ vl oprchars :: CharParser u String oprchars = try ((string"()") <|> many1 (oneOf "!+-=/*.-><[]|&")) getassign = do v1 <- getvalue spaces v2 <- try (char '(' >> spaces >> char ')' >> return "()") <|> option "" (do os <- concat <$> many1 oprchars spaces nm <- getassign return $ os ++ nm) return $ v1 ++ v2 varFunDecl :: String -> CharParser HeaderState Object varFunDecl ft = do _ <- many1 whitespace is <- identList spaces let alls = (ft:is) ptrs = length $ takeWhile (== '*') (last alls) nm = drop ptrs $ last alls ns = intercalate " " (init alls) ++ replicate ptrs '*' vis <- getVisibility <$> getState if isOperator nm then funDecl nm ns else do pdecl <- paramDecl (Just alls) (optional (char '=' >> spaces >> getassign) >> spaces >> (eos <|> (char ',' >> untilEOS >> return ())) >> -- just ignore all other declarations spaces >> return (VarDecl pdecl vis)) <|> funDecl nm ns isOperator :: String -> Bool isOperator "operator" = True isOperator t = takeWhile isAlpha (drop 2 (dropWhile (/= ':') t)) == "operator" getVisibility :: HeaderState -> Maybe (InheritLevel, String) getVisibility h = let cs = classstack h in case cs of (c:_) -> Just c _ -> Nothing funDecl :: String -> String -> CharParser HeaderState Object funDecl fn ft = do n <- if isOperator fn then spaces >> option "" oprchars else return "" spaces _ <- char '(' "start of function parameter list: (" spaces pars <- (try (spaces >> string "void" >> spaces >> return [])) <|> sepBy (paramDecl Nothing) (char ',' >> spaces) _ <- char ')' "end of function parameter list: )" spaces constfun <- option False (try (string "const" >> spaces) >> return True) optional (many (identifier >> spaces)) -- constructing member variables optional (char ':' >> many (many1 (digit <|> oneOf ("(.-+)[]" ++ typedefchars)) >> spaces)) abstr <- (char '{' >> ignoreBraces >> skipMany eos >> return False) <|> (eos >> return False) <|> (char '=' >> spaces >> char '0' >> spaces >> eos >> return True) spaces ns <- namespacestack <$> getState vs <- getVisibility <$> getState return $ FunDecl (fn ++ n) ft pars ns vs constfun abstr ignoreBraces :: CharParser u () ignoreBraces = ignoreBraces' (0 :: Int) where ignoreBraces' n = do skipMany $ noneOf "{}" v <- oneOf "{}" case v of '}' -> case n of 0 -> return () _ -> ignoreBraces' (n - 1) _ -> ignoreBraces' (n + 1) paramDecl :: Maybe [String] -> CharParser HeaderState ParamDecl paramDecl mv = do pts <- case mv of Nothing -> many1 (refMark <|> ptrStar <|> (gettype >>= \n -> spaces >> return n)) Just v -> return v spaces val <- optionMaybe optionalParams arr <- optionMaybe (concat <$> many1 (between (char '[') (char ']') (many (noneOf "]")) >>= \v -> spaces >> return v)) if '*' `elem` last pts -- pointer as "variable name" => no variable name given then return $ ParamDecl "" (intercalate " " pts) val arr else return $ ParamDecl (last pts) (intercalate " " (init pts)) val arr optionalParams = do _ <- char '=' spaces v <- getassign spaces r <- option "" (string "(" >> spaces >> string ")" >> return "()") spaces return (v ++ r) refMark :: CharParser u String refMark = do c <- char '&' spaces return [c] ptrStar :: CharParser u String ptrStar = do ns <- many1 $ char '*' spaces return ns idCharInit = ['a'..'z'] ++ ['A'..'Z'] ++ "_" idChar = ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "_" identifier :: CharParser u String identifier = do m <- oneOf idCharInit n <- many $ oneOf idChar return (m:n) untilEOL :: CharParser u String untilEOL = manyTill (anyChar) (eof <|> try (char '\n' >> return ())) untilEOS :: CharParser u String untilEOS = manyTill (anyChar) (eof <|> try (char ';' >> return ())) escapedEOL :: CharParser u Char escapedEOL = char '\\' >> newline preprocess :: CharParser (M.Map String String) String preprocess = do spaces concat <$> many (spaces >> ((char '#' >> preprocessorLine) <|> otherLine)) preprocessorLine = do spaces n <- (string "define" >> macroDef) <|> otherMacro spaces return n otherMacro = untilEOL >> return "" macroDef :: CharParser (M.Map String String) String macroDef = do spaces mname <- identifier mval <- option "" (many1 (oneOf " \t") >> untilEOL) updateState (M.insert mname mval) return "" otherLine :: CharParser (M.Map String String) String otherLine = do n <- concat <$> many1 expandMacro spaces return n expandMacro = do ns <- many $ noneOf $ ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "#" mexp <- try expandWord ns2 <- many $ noneOf $ ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "#" return $ ns ++ mexp ++ ns2 expandWord = do wd <- many1 alphaNum ms <- getState return $ fromMaybe wd (M.lookup wd ms) removeComments :: CharParser () String removeComments = do optional getComment many getCode where getCode = do n <- anyChar optional getComment return n getComment :: CharParser () String getComment = concat <$> many1 (try blockComment <|> lineComment) blockComment :: CharParser u String blockComment = do _ <- try (string "/*") manyTill anyChar (try (string "*/")) eol :: CharParser u () eol = (try (string "\r\n") >> return ()) <|> (oneOf "\r\n" >> return ()) lineComment :: CharParser u String lineComment = do _ <- try (string "//") manyTill anyChar (try eol) parseHeader :: String -> Either (String, ParseError) Header parseHeader input = case parse removeComments "removeComments" input of Left err -> Left (input, err) Right inp -> case runParser preprocess M.empty "preprocessor" inp of Left err2 -> Left (inp, err2) Right prp -> case runParser header (HeaderState [] []) "Header" prp of Left err -> Left (prp, err) Right v -> Right v