packages feed

fficxx-0.6: src/FFICXX/Generate/ContentMaker.hs

{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}

module FFICXX.Generate.ContentMaker where

import Control.Lens                           ( (&), (.~), at )
import Control.Monad.Trans.Reader
import Data.Either                            ( rights )
import Data.Functor.Identity                  ( Identity )
import qualified Data.Map as M
import Data.Maybe                             ( mapMaybe  )
import Data.Monoid                            ( (<>) )
import Data.List                              ( intercalate, nub )
import Data.List.Split                        ( splitOn )
import Language.Haskell.Exts.Syntax           ( Module(..)
                                              , Decl(..)
                                              )
import System.FilePath                        ( (<.>), (</>) )
--
import FFICXX.Runtime.CodeGen.Cxx             ( HeaderName(..) )
import qualified FFICXX.Runtime.CodeGen.Cxx as R
--
import FFICXX.Generate.Code.Cpp               ( genAllCppHeaderInclude
                                              , genCppDefMacroAccessor
                                              , genCppDefMacroNonVirtual
                                              , genCppDefMacroTemplateMemberFunction
                                              , genCppDefMacroVirtual
                                              , genCppDefInstAccessor
                                              , genCppDefInstNonVirtual
                                              , genCppDefInstVirtual
                                              , genCppHeaderInstAccessor
                                              , genCppHeaderInstNonVirtual
                                              , genCppHeaderInstVirtual
                                              , genCppHeaderMacroAccessor
                                              , genCppHeaderMacroType
                                              , genCppHeaderMacroVirtual
                                              , genCppHeaderMacroNonVirtual
                                              , genTopLevelCppDefinition
                                              , topLevelDecl
                                              )
import FFICXX.Generate.Code.HsCast            ( genHsFrontInstCastable
                                              , genHsFrontInstCastableSelf
                                              )
import FFICXX.Generate.Code.HsFFI             ( genHsFFI
                                              , genImportInFFI
                                              , genTopLevelFFI
                                              )
import FFICXX.Generate.Code.HsFrontEnd        ( genExport
                                              , genExtraImport
                                              , genHsFrontDecl
                                              , genHsFrontDowncastClass
                                              , genHsFrontInst
                                              , genHsFrontInstNew
                                              , genHsFrontInstNonVirtual
                                              , genHsFrontInstStatic
                                              , genHsFrontInstVariables
                                              , genHsFrontUpcastClass
                                              , genImportInCast
                                              , genImportInImplementation
                                              , genImportInInterface
                                              , genImportInModule
                                              , genImportInTopLevel
                                              , genTopLevelDef
                                              , hsClassRawType
                                              )
import FFICXX.Generate.Code.HsProxy           ( genProxyInstance )
import FFICXX.Generate.Code.HsTemplate        ( genImportInTemplate
                                              , genImportInTH
                                              , genTemplateMemberFunctions
                                              , genTmplInstance
                                              , genTmplInterface
                                              , genTmplImplementation
                                              )
import FFICXX.Generate.Dependency
import FFICXX.Generate.Name                   ( ffiClassName, hsClassName
                                              , hsFrontNameForTopLevel
                                              )
import FFICXX.Generate.Type.Annotate          ( AnnotateMap )
import FFICXX.Generate.Type.Class             ( Class(..)
                                              , ClassGlobal(..)
                                              , DaughterMap
                                              , ProtectedMethod(..)
                                              , isAbstractClass
                                              )
import FFICXX.Generate.Type.Module            ( ClassImportHeader(..)
                                              , ClassModule(..)
                                              , TemplateClassImportHeader(..)
                                              , TemplateClassModule(..)
                                              , TopLevelImportHeader(..)
                                              )
import FFICXX.Generate.Type.PackageInterface  ( ClassName(..)
                                              , PackageInterface
                                              , PackageName(..)
                                              )
import FFICXX.Generate.Util.HaskellSrcExts


srcDir :: FilePath -> FilePath
srcDir installbasedir = installbasedir </> "src"

csrcDir :: FilePath -> FilePath
csrcDir installbasedir = installbasedir </> "csrc"

---- common function for daughter

-- |
mkGlobal :: [Class] -> ClassGlobal
mkGlobal = ClassGlobal <$> mkDaughterSelfMap <*> mkDaughterMap


-- |
buildDaughterDef ::
     ((String,[Class]) -> String)
  -> DaughterMap
  -> String
buildDaughterDef f m =
    let lst = M.toList m
        f' (x,xs) =  f (x,filter (not.isAbstractClass) xs)
    in (concatMap f' lst)

-- |
buildParentDef :: ((Class,Class) -> [R.CStatement Identity]) -> Class -> [R.CStatement Identity]
buildParentDef f cls = concatMap (\p -> f (p,cls)) . class_allparents $ cls

-- |
mkProtectedFunctionList :: Class -> [R.CMacro Identity]
mkProtectedFunctionList c =
    map (\x-> R.Define (R.sname ("IS_" <> class_name c <> "_" <> x <> "_PROTECTED")) [] [R.CVerbatim "()"])
  . unProtected
  . class_protected
  $ c

-- |
buildTypeDeclHeader ::
     [Class]
  -> String
buildTypeDeclHeader classes =
  let typeDeclBodyStmts =
        intercalate [R.EmptyLine] $
          map (map R.CRegular . genCppHeaderMacroType) classes
  in R.renderBlock $
       R.ExternC $
         [ R.Pragma R.Once, R.EmptyLine ] <> typeDeclBodyStmts


-- |
buildDeclHeader ::
     String     -- ^ C prefix
  -> ClassImportHeader
  -> String
buildDeclHeader cprefix header =
  let classes = [cihClass header]
      aclass = cihClass header
      declHeaderStmts =
           [ R.Include (HdrName (cprefix ++ "Type.h")) ]
        <> map R.Include (cihIncludedHPkgHeadersInH header)
      vdecl  = map genCppHeaderMacroVirtual    classes
      nvdecl = map genCppHeaderMacroNonVirtual classes
      acdecl = map genCppHeaderMacroAccessor   classes
      vdef   = map genCppDefMacroVirtual       classes
      nvdef  = map genCppDefMacroNonVirtual    classes
      acdef  = map genCppDefMacroAccessor      classes
      tmpldef= map (\c -> map (genCppDefMacroTemplateMemberFunction c) (class_tmpl_funcs c)) classes
      declDefStmts =
        intercalate [R.EmptyLine] $ [vdecl,nvdecl,acdecl,vdef,nvdef,acdef]++tmpldef
      classDeclStmts =
        -- NOTE: Deletable is treated specially.
        -- TODO: We had better make it as a separate constructor in Class.
        if (fst.hsClassName) aclass /= "Deletable"
        then    buildParentDef (\(p,c) -> [genCppHeaderInstVirtual (p,c), R.CEmptyLine]) aclass
             <> [genCppHeaderInstVirtual (aclass, aclass), R.CEmptyLine]
             <> concatMap (\c -> [genCppHeaderInstNonVirtual c, R.CEmptyLine]) classes
             <> concatMap (\c -> [genCppHeaderInstAccessor c, R.CEmptyLine]) classes
        else []
  in R.renderBlock $
       R.ExternC $
            [ R.Pragma R.Once, R.EmptyLine ]
         <> declHeaderStmts
         <> [ R.EmptyLine ]
         <> declDefStmts
         <> [ R.EmptyLine ]
         <> map R.CRegular classDeclStmts

-- |
buildDefMain ::
     ClassImportHeader
  -> String
buildDefMain cih =
  let classes = [cihClass cih]
      headerStmts =
           [ R.Include "MacroPatternMatch.h" ]
        <> genAllCppHeaderInclude cih
        <> [ R.Include (cihSelfHeader cih) ]
      namespaceStmts =
        (map R.UsingNamespace . cihNamespace) cih
      aclass = cihClass cih
      aliasStr = intercalate "\n" $
                   mapMaybe typedefstmt $
                     aclass : rights (cihImportedClasses cih)
        where typedefstmt c = let n1 = class_name c
                                  n2 = ffiClassName c
                              in if n1 == n2
                                 then Nothing
                                 else Just ("typedef " <> n1 <> " " <> n2 <> ";")

      cppBodyStmts =
           mkProtectedFunctionList (cihClass cih)
        <> map
             R.CRegular
             (   buildParentDef (\(p,c) -> [genCppDefInstVirtual (p,c), R.CEmptyLine])  (cihClass cih)
              <> (if isAbstractClass aclass
                  then []
                  else [genCppDefInstVirtual (aclass, aclass), R.CEmptyLine]
                 )
              <> concatMap (\c -> [genCppDefInstNonVirtual c, R.CEmptyLine]) classes
              <> concatMap (\c -> [genCppDefInstAccessor c, R.CEmptyLine]) classes
             )
  in concatMap R.renderCMacro
       (   headerStmts
        <> [ R.EmptyLine ]
        <> map R.CRegular namespaceStmts
        <> [ R.EmptyLine
           , R.Verbatim aliasStr
           , R.EmptyLine
           , R.Verbatim "#define CHECKPROTECT(x,y) FXIS_PAREN(IS_ ## x ## _ ## y ## _PROTECTED)\n"
           , R.EmptyLine
           , R.Verbatim "#define TYPECASTMETHOD(cname,mname,oname) \\\n\
                        \  FXIIF( CHECKPROTECT(cname,mname) ) ( \\\n\
                        \  (from_nonconst_to_nonconst<oname,cname ## _t>), \\\n\
                        \  (from_nonconst_to_nonconst<cname,cname ## _t>) )\n"
           , R.EmptyLine
           ]
        <> cppBodyStmts
       )

-- |
buildTopLevelHeader ::
     String     -- ^ C prefix
  -> TopLevelImportHeader
  -> String
buildTopLevelHeader cprefix tih =
  let declHeaderStmts =
           [ R.Include (HdrName (cprefix ++ "Type.h")) ]
        <> map R.Include (map cihSelfHeader (tihClassDep tih) ++ tihExtraHeadersInH tih)
      declBodyStmts = map (R.CDeclaration . topLevelDecl) $ tihFuncs tih
  in R.renderBlock $
       R.ExternC $
            [ R.Pragma R.Once, R.EmptyLine ]
         <> declHeaderStmts
         <> [ R.EmptyLine ]
         <> map R.CRegular declBodyStmts

-- |
buildTopLevelCppDef :: TopLevelImportHeader -> String
buildTopLevelCppDef tih =
  let cihs = tihClassDep tih
      extclasses = tihExtraClassDep tih
      declHeaderStmts =
           [ R.Include "MacroPatternMatch.h"
           , R.Include (HdrName (tihHeaderFileName tih <.> "h"))
           ]
        <> concatMap genAllCppHeaderInclude cihs
        <> otherHeaderStmts
      otherHeaderStmts =
        map R.Include (map cihSelfHeader cihs ++ tihExtraHeadersInCPP tih)

      allns = nub ((tihClassDep tih >>= cihNamespace) ++ tihNamespaces tih)

      namespaceStmts = map R.UsingNamespace allns
      aliasStr = intercalate "\n" $
                   mapMaybe typedefstmt $
                     rights (concatMap cihImportedClasses cihs ++ extclasses)
        where typedefstmt c = let n1 = class_name c
                                  n2 = ffiClassName c
                              in if n1 == n2
                                 then Nothing
                                 else Just ("typedef " <> n1 <> " " <> n2 <> ";")
      declBodyStr =
        intercalate "\n" $
          map (R.renderCStmt . genTopLevelCppDefinition) (tihFuncs tih)

  in concatMap R.renderCMacro
       (   declHeaderStmts
        <> [R.EmptyLine]
        <> map R.CRegular namespaceStmts
        <> [ R.EmptyLine
           , R.Verbatim aliasStr
           , R.EmptyLine
           , R.Verbatim "#define CHECKPROTECT(x,y) FXIS_PAREN(IS_ ## x ## _ ## y ## _PROTECTED)\n"
           , R.EmptyLine
           , R.Verbatim "#define TYPECASTMETHOD(cname,mname,oname) \\\n\
                        \  FXIIF( CHECKPROTECT(cname,mname) ) ( \\\n\
                        \  (to_nonconst<oname,cname ## _t>), \\\n\
                        \  (to_nonconst<cname,cname ## _t>) )\n"
           , R.EmptyLine
           , R.Verbatim declBodyStr
           ]
       )

-- |
buildFFIHsc :: ClassModule -> Module ()
buildFFIHsc m = mkModule (mname <.> "FFI") [lang ["ForeignFunctionInterface"]] ffiImports hscBody
  where mname = cmModule m
        ffiImports = [ mkImport "Data.Word"
                     , mkImport "Data.Int"
                     , mkImport "Foreign.C"
                     , mkImport "Foreign.Ptr"
                     , mkImport (mname <.> "RawType") ]
                     <> genImportInFFI m
                     <> genExtraImport m
        hscBody = genHsFFI (cmCIH m)


-- |
buildRawTypeHs :: ClassModule -> Module ()
buildRawTypeHs m =
    mkModule
      (cmModule m <.> "RawType")
      [ lang
          [ "ForeignFunctionInterface"
          , "TypeFamilies"
          , "MultiParamTypeClasses"
          , "FlexibleInstances"
          , "TypeSynonymInstances"
          , "EmptyDataDecls"
          , "ExistentialQuantification"
          , "ScopedTypeVariables"
          ]
      ]
      rawtypeImports
      rawtypeBody
  where
    rawtypeImports = [ mkImport "Foreign.Ptr"
                     , mkImport "FFICXX.Runtime.Cast"
                     ]
    rawtypeBody = let c = cihClass (cmCIH m)
                  in if isAbstractClass c then [] else hsClassRawType c

-- |
buildInterfaceHs :: AnnotateMap -> ClassModule -> Module ()
buildInterfaceHs amap m =
  mkModule
    (cmModule m <.> "Interface")
    [ lang
      [ "EmptyDataDecls"
      , "ExistentialQuantification"
      , "FlexibleContexts"
      , "FlexibleInstances"
      , "ForeignFunctionInterface"
      , "MultiParamTypeClasses"
      , "ScopedTypeVariables"
      , "TypeFamilies"
      , "TypeSynonymInstances"
      ]
    ]
    ifaceImports
    ifaceBody
  where
    classes = [cihClass (cmCIH m)]
    ifaceImports = [ mkImport "Data.Word"
                   , mkImport "Data.Int"
                   , mkImport "Foreign.C"
                   , mkImport "Foreign.Ptr"
                   , mkImport "FFICXX.Runtime.Cast"
                   ]
                   <> genImportInInterface m
                   <> genExtraImport m
    ifaceBody =
         runReader (mapM genHsFrontDecl classes) amap
      <> (concatMap genHsFrontUpcastClass . filter (not.isAbstractClass)) classes
      <> (concatMap genHsFrontDowncastClass . filter (not.isAbstractClass)) classes

-- |
buildCastHs :: ClassModule -> Module ()
buildCastHs m = mkModule (cmModule m <.> "Cast")
               [ lang [ "FlexibleInstances", "FlexibleContexts", "TypeFamilies"
                      , "MultiParamTypeClasses", "OverlappingInstances", "IncoherentInstances" ] ]
               castImports body
  where classes = [ cihClass (cmCIH m) ]
        castImports =    [ mkImport "Foreign.Ptr"
                         , mkImport "FFICXX.Runtime.Cast"
                         , mkImport "System.IO.Unsafe" ]
                      <> genImportInCast m
        body =    mapMaybe genHsFrontInstCastable classes
               <> mapMaybe genHsFrontInstCastableSelf classes

-- |
buildImplementationHs :: AnnotateMap -> ClassModule -> Module ()
buildImplementationHs amap m =
    mkModule (cmModule m <.> "Implementation")
      [ lang [ "EmptyDataDecls"
             , "FlexibleContexts"
             , "FlexibleInstances"
             , "ForeignFunctionInterface"
             , "IncoherentInstances"
             , "MultiParamTypeClasses"
             , "OverlappingInstances"
             , "TemplateHaskell"
             , "TypeFamilies"
             , "TypeSynonymInstances"
             ]
      ]
      implImports implBody
  where
    classes = [ cihClass (cmCIH m) ]
    implImports = [ mkImport "Data.Monoid"                -- for template member
                  , mkImport "Data.Word"
                  , mkImport "Data.Int"
                  , mkImport "Foreign.C"
                  , mkImport "Foreign.Ptr"
                  , mkImport "Language.Haskell.TH"        -- for template member
                  , mkImport "Language.Haskell.TH.Syntax" -- for template member
                  , mkImport "System.IO.Unsafe"
                  , mkImport "FFICXX.Runtime.Cast"
                  , mkImport "FFICXX.Runtime.CodeGen.Cxx" -- for template member
                  , mkImport "FFICXX.Runtime.TH"          -- for template member
                  ]
                  <> genImportInImplementation m
                  <> genExtraImport m
    f :: Class -> [Decl ()]
    f y = concatMap (flip genHsFrontInst y) (y:class_allparents y)

    implBody =    concatMap f classes
               <> runReader (concat <$> mapM genHsFrontInstNew classes) amap
               <> concatMap genHsFrontInstNonVirtual classes
               <> concatMap genHsFrontInstStatic classes
               <> concatMap genHsFrontInstVariables classes
               <> genTemplateMemberFunctions (cmCIH m)

buildProxyHs :: ClassModule -> Module ()
buildProxyHs m =
    mkModule (cmModule m <.> "Proxy")
      [ lang [ "FlexibleInstances"
             , "OverloadedStrings"
             , "TemplateHaskell"
             ]
      ]
      [ mkImport "Foreign.Ptr"
      , mkImport "FFICXX.Runtime.Cast"
      , mkImport "Language.Haskell.TH"
      , mkImport "Language.Haskell.TH.Syntax"
      , mkImport "FFICXX.Runtime.CodeGen.Cxx"
      ]
      body
  where
    body = genProxyInstance



buildTemplateHs :: TemplateClassModule -> Module ()
buildTemplateHs m =
    mkModule (tcmModule m <.> "Template")
      [ lang
          [ "EmptyDataDecls"
          , "FlexibleInstances"
          , "MultiParamTypeClasses"
          , "TypeFamilies"
          ]
      ]
      imports
      body
  where
    t = tcihTClass $ tcmTCIH m
    imports =    [ mkImport "Foreign.C.Types"
                 , mkImport "Foreign.Ptr"
                 , mkImport "FFICXX.Runtime.Cast"
                 ]
              <> genImportInTemplate t
    body = genTmplInterface t

buildTHHs :: TemplateClassModule -> Module ()
buildTHHs m =
  mkModule (tcmModule m <.> "TH")
    [ lang  ["TemplateHaskell"] ]
    (   [ mkImport "Data.Char"
        , mkImport "Data.List"
        , mkImport "Data.Monoid"
        , mkImport "Foreign.C.Types"
        , mkImport "Foreign.Ptr"
        , mkImport "Language.Haskell.TH"
        , mkImport "Language.Haskell.TH.Syntax"
        , mkImport "FFICXX.Runtime.CodeGen.Cxx"
        , mkImport "FFICXX.Runtime.TH"
        ]
     <> imports
    )
    body
  where
    t = tcihTClass $ tcmTCIH m
    imports =    [ mkImport (tcmModule m <.> "Template") ]
              <> genImportInTH t
    body = tmplImpls <> tmplInsts
    tmplImpls = genTmplImplementation t
    tmplInsts = genTmplInstance (tcmTCIH m)

-- |
buildInterfaceHSBOOT :: String -> Module ()
buildInterfaceHSBOOT mname = mkModule (mname <.> "Interface") [] [] hsbootBody
  where cname = last (splitOn "." mname)
        hsbootBody = [ mkClass cxEmpty ('I':cname) [mkTBind "a"] [] ]

-- |
buildModuleHs :: ClassModule -> Module ()
buildModuleHs m = mkModuleE (cmModule m) [] (genExport c) (genImportInModule c) []
  where c = cihClass (cmCIH m)

-- |
buildTopLevelHs :: String -> ([ClassModule],[TemplateClassModule]) -> TopLevelImportHeader -> Module ()
buildTopLevelHs modname (mods,tmods) tih =
    mkModuleE modname pkgExtensions pkgExports pkgImports pkgBody
  where
    tfns = tihFuncs tih
    pkgExtensions = [ lang [ "FlexibleContexts", "FlexibleInstances" ] ]
    pkgExports =     map (emodule . cmModule) mods
                 ++  map (evar . unqual . hsFrontNameForTopLevel) tfns

    pkgImports = genImportInTopLevel modname (mods,tmods) tih

    pkgBody    =    map (genTopLevelFFI tih) tfns
                 ++ concatMap genTopLevelDef tfns

-- |
buildPackageInterface :: PackageInterface
                      -> PackageName
                      -> [ClassImportHeader]
                      -> PackageInterface
buildPackageInterface pinfc pkgname = foldr f pinfc
  where f cih repo =
          let name = (class_name . cihClass) cih
              header = cihSelfHeader cih
          in repo & at (pkgname,ClsName name) .~ (Just header)