packages feed

cgen-0.0.4: src/CppGen.hs

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)}

refToPointer :: String -> String
refToPointer t = 
  if last 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 (funname fun) (rettype fun) (paramFormat (params fun))
        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
            hPrintf h "%s %s(%s)\n" 
                (stripStatic $ rettype fun)
                (funname fun)
                (paramFormat (params fun))
            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
        excludeFun f = lastDef ' ' (correctType $ rettype f) == '&' || -- TODO: allow returned references
                       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);" clname fnname fnparams
  | otherwise 
      = printf "    return this_ptr->%s(%s);" fnname fnparams

funDeclaration :: String -> String -> String -> String
funDeclaration fnname rttype fnparams =
    printf "%s %s(%s);" (stripStatic rttype) fnname fnparams

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 mnt 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<String, Animation*>::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"
addNamespaceQual :: [String] -> String -> String
addNamespaceQual ns n
  | isStatic n = "static " ++ addNamespaceQual ns (stripStatic 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