module CppGen(handleHeader) where import System.FilePath import System.IO import Data.List import Data.Maybe import Control.Monad import Text.Printf import qualified Data.Set as S import Text.Regex.Posix import Safe import HeaderData import CppUtils import Utils publicMemberFunction :: Object -> Bool publicMemberFunction (FunDecl _ _ _ _ (Just (Public, _)) _ _) = True publicMemberFunction _ = False showP (ParamDecl pn pt _ _) = pt ++ " " ++ pn paramFormat :: [ParamDecl] -> String paramFormat (p1:p2:ps) = showP p1 ++ ", " ++ paramFormat (p2:ps) paramFormat [p1] = showP p1 paramFormat [] = "" correctParam :: ParamDecl -> ParamDecl correctParam p = p{vartype = correctType (vartype p)} -- TODO: arrays? refToPointerParam :: ParamDecl -> ParamDecl refToPointerParam p = p{vartype = refToPointer (vartype p)} isRef :: String -> Bool isRef t = last t == '&' refToPointer :: String -> String refToPointer t = if isRef t then init t ++ "*" else t handleHeader :: FilePath -> [FilePath] -> [String] -> [String] -> [(String, String)] -> FilePath -> [Object] -> IO () handleHeader outdir incfiles exclclasses excls rens headername objs = do withFile outfile WriteMode $ \h -> do hPrintf h "#ifndef CGEN_%s_H\n" (toCapital (takeBaseName headername)) hPrintf h "#define CGEN_%s_H\n" (toCapital (takeBaseName headername)) hPrintf h "\n" forM_ incfiles $ \inc -> do hPrintf h "#include <%s>\n" inc hPrintf h "\n" hPrintf h "extern \"C\"\n" hPrintf h "{\n" hPrintf h "\n" forM_ namespaces $ \ns -> do hPrintf h "using namespace %s;\n" ns hPrintf h "\n" forM_ typedefs $ \(td1, td2) -> do hPrintf h "typedef %s %s;\n" td1 td2 hPrintf h "\n\n" hPrintf h "#ifdef CGEN_HS\n" forM_ (getEnums objs) $ \enum -> do hPutStrLn h $ enumDeclaration (enumname enum) (enumvalues enum) hPrintf h "#endif\n\n" forM_ funs $ \fun -> do hPutStrLn h $ funDeclaration fun True hPrintf h "\n" hPrintf h "}\n" hPrintf h "\n" hPrintf h "#endif\n" hPrintf h "\n" hPutStrLn stderr $ "Wrote file " ++ outfile withFile cppoutfile WriteMode $ \h -> do hPrintf h "#define CGEN_OUTPUT_INTERN\n" hPrintf h "#include \"%s\"" headername hPrintf h "\n" forM_ (zip funs allfuns) $ \(fun, origfun) -> do hPutStrLn h $ funDeclaration fun False hPrintf h "{\n" -- NOTE: do NOT call refToPointerParam or refParamsToPointers -- for prs, because then the information that the parameter -- is actually a reference and the pointer must be dereferenced -- is lost. let prs = intercalate ", " $ map (correctRef . renameParam rens) $ params $ correctFuncParams origfun switch (funname origfun) [(getClname origfun, hPrintf h " return new %s(%s);\n" (stripStatic $ stripExtra $ rettype fun) prs), ('~':getClname origfun, hPrintf h " delete this_ptr;\n")] (hPutStrLn h $ funDefinition (funname origfun) (rettype fun) (getClname origfun) prs) hPrintf h "}\n" hPrintf h "\n" hPutStrLn stderr $ "Wrote file " ++ cppoutfile where outfile = (outdir headername) cppoutfile = (outdir takeBaseName headername <.> "cpp") allfuns = implicitcdtors ++ filter (\f -> publicMemberFunction f && not (excludeFun f) && not (abstractConstructor classes f)) (getFuns objs) implicitcdtors = concatMap getImplicitCDtor classes namespaces = filter (not . null) $ nub $ map (headDef "") (map fnnamespace funs) -- list of names of all parsed classes classnames = filter (not . null) $ nub $ map getObjName $ getClasses objs classes = concatMap (\nm -> filter (classHasName nm) (getClasses objs)) classnames alltypedefs = catMaybes $ map getTypedef (map snd $ concatMap classobjects classes) -- typedefs used in function parameter and return types usedtypedefs = usedTypedefs usedtypes alltypedefs extratypedefs = extraTypedefs usedtypedefs alltypedefs -- NOTE: can't just use only public typedefs, because they sometimes depend on -- protected typedefs, so include them as well (so-called secondary typedefs). typedefs = nub $ extratypedefs ++ usedtypedefs allenums = map snd $ filter (\(v, o) -> isEnum o && v == Public) $ concatMap classobjects classes funs = mangle $ map expandFun allfuns functionReturnsRef f = lastDef ' ' (correctType $ rettype f) == '&' excludeFun f = or (map (\e -> funname f =~ e) excls) || or (map (\e -> fromMaybe "" (liftM snd (fnvisibility f)) =~ e) exclclasses) || take 8 (rettype f) == "template" || -- TODO: allow return types that start with "template" rettype f == "operator" || -- conversion operator is parsed as operator as return type take 8 (funname f) == "operator" -- TODO: allow normal functions with name starting with operator expandFun f = addConstness . -- add const keyword if the function is const refParamsToPointers . -- ref params to pointers renameTypes rens . -- rename types as specified by user addClassspaces allenums classes . -- add qualification when necessary correctFuncRetType . -- remove keywords from return type correctFuncParams . -- create param name if none, remove keywords finalName . -- expand function name by class and namespace addThisPointer . -- 1st parameter extendFunc $ f -- constructor & destructor handling usedtypes = getAllTypes funs getImplicitCDtor :: Object -> [Object] getImplicitCDtor c@(ClassDecl cname _ _ cns objs) | null $ filter (not . isAbstractFun) $ getFuns . map snd . filter (\(p, _) -> p == Public) $ objs = [] -- forward declaration or abstract | not (publicClass c) = [] | abstractClass c = [] | otherwise = dl ++ cl where dl = if any isDestructor (map snd objs) -- explicit destructor then [] else [FunDecl ('~':cname) "void" [] cns (Just (Public, cname)) False False] cl = if any isConstructor (map snd objs) -- explicit constructor then [] else [FunDecl cname "" [] cns (Just (Public, cname)) False False] getImplicitCDtor _ = [] isConstructor :: Object -> Bool isConstructor (FunDecl fname _ _ _ (Just (_, cname)) _ _) = fname == cname isConstructor _ = False isDestructor :: Object -> Bool isDestructor (FunDecl fname _ _ _ (Just (_, cname)) _ _) = fname == '~':cname isDestructor _ = False funDefinition :: String -> String -> String -> String -> String funDefinition fnname rttype clname fnparams | rttype == "void" = printf " this_ptr->%s(%s);" fnname fnparams | isStatic rttype && stripStatic rttype == "void" = printf " %s::%s(%s);" clname fnname fnparams | isStatic rttype = printf " return %s%s::%s(%s);" getaddr clname fnname fnparams | otherwise = printf " return %sthis_ptr->%s(%s);" getaddr fnname fnparams where getaddr = if isRef rttype then "&" else "" funDeclaration :: Object -> Bool -> String funDeclaration fun semicolon = printf "%s %s(%s)%s" (refToPointer $ stripStatic $ rettype fun) (funname fun) (paramFormat (params fun)) (if semicolon then ";" else "") enumDeclaration :: String -> [EnumVal] -> String enumDeclaration ename evalues = if not (enumReadable evalues) then "" else printf "enum %s {\n %s\n};\n\n" ename vals where vals = intercalate ",\n " (map printEnumval (getEnumValues evalues)) printEnumval :: (String, Int) -> String printEnumval (n, v) = printf "%s = %d" n v refParamsToPointers f@(FunDecl _ _ ps _ _ _ _) = f{params = map refToPointerParam ps} refParamsToPointers n = n renameTypes :: [(String, String)] -> Object -> Object renameTypes rens f@(FunDecl _ rt ps _ _ _ _) = f{rettype = renameType rens rt, params = map (renameParam rens) ps} renameTypes _ n = n renameParam :: [(String, String)] -> ParamDecl -> ParamDecl renameParam rens p@(ParamDecl _ pt _ _) = p{vartype = renameType rens pt} renameType :: [(String, String)] -> String -> String renameType rens t = let mnt = lookup tm rens tm = stripStatic $ stripExtra t mf1 = if isConst t then makeConst else id mf2 = makePtr (isPtr t) in case lookup t rens of Just t' -> t' Nothing -> case lookup tm rens of Nothing -> if '<' `elem` t && '>' `elem` t then handleTemplateTypes rens t else t Just t' -> if isStatic (stripExtra t) then "static " ++ ((mf1 . mf2) t') else (mf1 . mf2) t' handleTemplateTypes :: [(String, String)] -> String -> String handleTemplateTypes rens t = let alltypes = typesInType t newtypes = map (renameType rens) alltypes in foldr (uncurry replace) t (zip alltypes newtypes) makeConst :: String -> String makeConst n = "const " ++ n makePtr :: Int -> String -> String makePtr num t = t ++ replicate num '*' abstractConstructor :: [Object] -> Object -> Bool abstractConstructor classes (FunDecl fn _ _ _ (Just (_, _)) _ _) = case fetchClass classes fn of Nothing -> False Just cl -> any isAbstractFun (map snd $ classobjects cl) abstractConstructor _ _ = False -- typesInType "const int" = ["int"] -- typesInType "map::type" = ["String", "Animation"] typesInType :: String -> [String] typesInType v = case betweenAngBrackets v of "" -> [stripExtra v] n -> map stripExtra $ splitBy ',' n -- all typedefs whose definition depends on another typedef. extraTypedefs :: [(String, String)] -> [(String, String)] -> [(String, String)] extraTypedefs usedts allts = case filter (extractSecType usedts) allts of [] -> [] -- NOTE: the order here is significant for the dependencies -- between the typedefs. newusedts -> extraTypedefs newusedts allts ++ newusedts -- whether any of the types in the snd of the tuple is contained in -- any of the fsts of the list. extractSecType :: [(String, String)] -> (String, String) -> Bool extractSecType ts (_, t2) = let sectypes = typesInType t2 tsstypes = concatMap typesInType (map fst ts) in (any (`elem` tsstypes) sectypes) -- for all types of a function, turn "y" into "x::y" when y is a nested class inside x. addClassspaces :: [Object] -> [Object] -> Object -> Object addClassspaces enums classes f@(FunDecl _ rt ps _ _ _ _) = let rt' = addClassQual enums classes rt ps' = map (addParamClassQual enums classes) ps in f{rettype = rt', params = ps'} addClassspaces _ _ n = n addParamClassQual :: [Object] -> [Object] -> ParamDecl -> ParamDecl addParamClassQual enums classes p@(ParamDecl _ t _ _) = let t' = addClassQual enums classes t in p{vartype = t'} -- add class qualification to rt, if a class named rt is found. -- the qualification added is the class nesting of the found class. addClassQual :: [Object] -> [Object] -> String -> String addClassQual enums classes rt = case fetchClass classes (stripStatic $ stripExtra rt) of Nothing -> case fetchEnum enums (stripStatic $ stripExtra rt) of Nothing -> rt Just e -> addNamespaceQual (map snd $ enumclassnesting e) rt Just c -> addNamespaceQual (map snd $ classnesting c) rt -- addNamespaceQual ["aa", "bb"] "foo" = "bb::aa::foo" -- addNamespaceQual ["aa", "bb"] "static foo" = "static bb::aa::foo" -- TODO: won't work when both defined with both static and const. addNamespaceQual :: [String] -> String -> String addNamespaceQual ns n | isStatic n = "static " ++ addNamespaceQual ns (stripStatic n) | isConst n = "const " ++ addNamespaceQual ns (stripConst n) | otherwise = concatMap (++ "::") ns ++ n -- turn a "char& param" into "*param". correctRef :: ParamDecl -> String correctRef (ParamDecl nm pt _ _) = if '&' `elem` take 2 (reverse pt) then '*':nm else nm -- separate pointer * from other chars for all params. -- if param has no name, create one. -- remove keywords such as virtual, etc. correctFuncParams :: Object -> Object correctFuncParams f@(FunDecl _ _ ps _ _ _ _) = f{params = checkParamNames (map (correctParam) ps)} correctFuncParams n = n -- for each unnamed parameter, -- create a parameter name of (type) ++ running index. checkParamNames :: [ParamDecl] -> [ParamDecl] checkParamNames = go (1 :: Int) where go _ [] = [] go n (p:ps) = let (p', n') = case varname p of "" -> (p{varname = (stripStatic $ stripExtra $ vartype p) ++ (show n)}, n + 1) _ -> (p, n) in p':(go n' ps) -- expand function name by namespace and class name. finalName :: Object -> Object finalName f@(FunDecl fname _ _ funns _ _ _) = let clname = getClname f nsname = headDef "" funns updname = nsname ++ (if not (null nsname) then "_" else "") ++ clname ++ (if not (null clname) then "_" else "") ++ fname in f{funname = updname} finalName n = n constructorName, destructorName :: String constructorName = "new" destructorName = "delete" addThisPointer :: Object -> Object addThisPointer f@(FunDecl fname rttype ps _ (Just (_, clname)) _ _) | fname == constructorName = f | isStatic rttype = f | otherwise = f{params = (t:ps)} where t = ParamDecl this_ptrName (clname ++ "*") Nothing Nothing addThisPointer n = n this_ptrName = "this_ptr" -- correct constructors and destructors. extendFunc :: Object -> Object extendFunc f@(FunDecl fname _ _ _ (Just (_, clname)) _ _) | fname == clname = f{funname = constructorName, rettype = fname ++ " *"} | fname == '~':clname = f{funname = destructorName, rettype = "void"} | otherwise = f extendFunc n = n -- const keyword to return value and this_ptr if needed. addConstness :: Object -> Object addConstness f@(FunDecl _ fr ps _ _ constfunc _) = f{rettype = cident fr, params = map cidentP ps} where cident v = if not (isConst v) && constfunc && '*' `elem` v then "const " ++ v else v cidentP p = let n = if not (isConst (varname p)) && constfunc && varname p == this_ptrName then "const " ++ vartype p else vartype p in p{vartype = n} addConstness n = n -- filtering typedefs doesn't help - t1 may refer to private definitions. usedTypedefs :: S.Set String -> [(String, String)] -> [(String, String)] usedTypedefs s = filter (\(_, t2) -> t2 `S.member` s) -- separate pointer * from other chars in function return type. -- remove keywords such as virtual, etc. correctFuncRetType :: Object -> Object correctFuncRetType f@(FunDecl _ fr _ _ _ _ _) = f{rettype = correctType fr} correctFuncRetType n = n -- o(n^2). -- adds cleaned up type names at the end of the overloaded function name. mangle :: [Object] -> [Object] mangle [] = [] mangle (n:ns) = let m = n{funname = funname n ++ functionMangleSuffix n} in if null $ filter (== funname n) $ map funname ns then n : mangle ns else m : mangle ns functionMangleSuffix :: Object -> String functionMangleSuffix (FunDecl _ _ [] _ _ _ _) = "_void" functionMangleSuffix (FunDecl _ _ ps _ _ _ _) = '_' : concatMap (mangleType . vartype) ps functionMangleSuffix _ = "" mangleType :: String -> String mangleType = filter (`notElem` ": <>") . map (\c -> if c == '*' then 'P' else if c == '&' then 'R' else c) . replace "::type" "" . stripConst