{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Data.GI.CodeGen.Code
( Code
, ModuleInfo(moduleCode, sectionDocs)
, ModuleFlag(..)
, BaseCodeGen
, CodeGen
, ExcCodeGen
, CGError(..)
, genCode
, evalCodeGen
, writeModuleTree
, listModuleTree
, codeToText
, transitiveModuleDeps
, minBaseVersion
, BaseVersion(..)
, showBaseVersion
, registerNSDependency
, qualified
, getDeps
, recurseWithAPIs
, handleCGExc
, describeCGError
, notImplementedError
, badIntroError
, missingInfoError
, indent
, increaseIndent
, bline
, line
, blank
, group
, cppIf
, CPPGuard(..)
, hsBoot
, submodule
, setLanguagePragmas
, setGHCOptions
, setModuleFlags
, setModuleMinBase
, getFreshTypeVariable
, resetTypeVariableScope
, exportModule
, exportDecl
, export
, HaddockSection(..)
, NamedSection(..)
, addSectionFormattedDocs
, findAPI
, getAPI
, findAPIByName
, getAPIs
, getC2HMap
, config
, currentModule
) where
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative ((<$>))
import Data.Monoid (Monoid(..))
#endif
import Control.Monad.Reader
import Control.Monad.State.Strict
import Control.Monad.Except
import qualified Data.Foldable as F
import Data.Maybe (fromMaybe, catMaybes)
import Data.Monoid ((<>), mempty)
import qualified Data.Map.Strict as M
import Data.Sequence (ViewL ((:<)), viewl, (|>))
import qualified Data.Sequence as Seq
import qualified Data.Semigroup as Sem
import qualified Data.Set as Set
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Lazy.Builder as B
import qualified Data.Text.Lazy as LT
import System.Directory (createDirectoryIfMissing)
import System.FilePath (joinPath, takeDirectory)
import Data.GI.CodeGen.API (API, Name(..))
import Data.GI.CodeGen.Config (Config(..))
import {-# SOURCE #-} Data.GI.CodeGen.CtoHaskellMap (cToHaskellMap,
Hyperlink)
import Data.GI.CodeGen.GtkDoc (CRef)
import Data.GI.CodeGen.ModulePath (ModulePath(..), dotModulePath, (/.))
import Data.GI.CodeGen.Type (Type(..))
import Data.GI.CodeGen.Util (tshow, terror, padTo, utf8WriteFile)
import Data.GI.CodeGen.ProjectInfo (authors, license, maintainers)
data CPPConditional = CPPIf Text
deriving (Eq, Show, Ord)
newtype Code = Code (Seq.Seq CodeToken)
deriving (Sem.Semigroup, Monoid, Eq, Show, Ord)
emptyCode :: Code
emptyCode = Code Seq.empty
isCodeEmpty :: Code -> Bool
isCodeEmpty (Code seq) = Seq.null seq
codeSingleton :: CodeToken -> Code
codeSingleton t = Code (Seq.singleton t)
data CodeToken
= Line Text
| Indent Code
| Group Code
| IncreaseIndent
| CPPBlock CPPConditional Code
deriving (Eq, Ord, Show)
type Deps = Set.Set Text
data HaddockSection = ToplevelSection
| NamedSubsection NamedSection Text
deriving (Show, Eq, Ord)
data NamedSection = MethodSection
| PropertySection
| SignalSection
| EnumSection
| FlagSection
deriving (Show, Eq, Ord)
type SymbolName = Text
data Export = Export {
exportType :: ExportType
, exportSymbol :: SymbolName
, exportGuards :: [CPPConditional]
} deriving (Show, Eq, Ord)
data ExportType = ExportSymbol HaddockSection
| ExportTypeDecl
| ExportModule
deriving (Show, Eq, Ord)
data ModuleInfo = ModuleInfo {
modulePath :: ModulePath
, moduleCode :: Code
, bootCode :: Code
, submodules :: M.Map Text ModuleInfo
, moduleDeps :: Deps
, moduleExports :: Seq.Seq Export
, qualifiedImports :: Set.Set ModulePath
, modulePragmas :: Set.Set Text
, moduleGHCOpts :: Set.Set Text
, moduleFlags :: Set.Set ModuleFlag
, sectionDocs :: M.Map HaddockSection Text
, moduleMinBase :: BaseVersion
}
data ModuleFlag = ImplicitPrelude
deriving (Show, Eq, Ord)
data BaseVersion = Base47
| Base48
deriving (Show, Eq, Ord)
showBaseVersion :: BaseVersion -> Text
showBaseVersion Base47 = "4.7"
showBaseVersion Base48 = "4.8"
emptyModule :: ModulePath -> ModuleInfo
emptyModule m = ModuleInfo { modulePath = m
, moduleCode = emptyCode
, bootCode = emptyCode
, submodules = M.empty
, moduleDeps = Set.empty
, moduleExports = Seq.empty
, qualifiedImports = Set.empty
, modulePragmas = Set.empty
, moduleGHCOpts = Set.empty
, moduleFlags = Set.empty
, sectionDocs = M.empty
, moduleMinBase = Base47
}
data CodeGenConfig = CodeGenConfig {
hConfig :: Config
, loadedAPIs :: M.Map Name API
, c2hMap :: M.Map CRef Hyperlink
}
data CGError = CGErrorNotImplemented Text
| CGErrorBadIntrospectionInfo Text
| CGErrorMissingInfo Text
deriving (Show)
data CGState = CGState {
cgsCPPConditionals :: [CPPConditional]
, cgsNextAvailableTyvar :: NamedTyvar
}
data NamedTyvar = SingleCharTyvar Char
| IndexedTyvar Text Integer
emptyCGState :: CGState
emptyCGState = CGState { cgsCPPConditionals = []
, cgsNextAvailableTyvar = SingleCharTyvar 'a'
}
type BaseCodeGen excType a =
ReaderT CodeGenConfig (StateT (CGState, ModuleInfo) (Except excType)) a
type CodeGen a = forall e. BaseCodeGen e a
type ExcCodeGen a = BaseCodeGen CGError a
runCodeGen :: BaseCodeGen e a -> CodeGenConfig -> (CGState, ModuleInfo) ->
(Either e (a, ModuleInfo))
runCodeGen cg cfg state =
dropCGState <$> runExcept (runStateT (runReaderT cg cfg) state)
where dropCGState :: (a, (CGState, ModuleInfo)) -> (a, ModuleInfo)
dropCGState (x, (_, m)) = (x, m)
cleanInfo :: ModuleInfo -> ModuleInfo
cleanInfo info = info { moduleCode = emptyCode, submodules = M.empty,
bootCode = emptyCode, moduleExports = Seq.empty,
qualifiedImports = Set.empty,
sectionDocs = M.empty, moduleMinBase = Base47 }
recurseCG :: BaseCodeGen e a -> BaseCodeGen e (a, Code)
recurseCG = recurseWithState id
recurseWithState :: (CGState -> CGState) -> BaseCodeGen e a
-> BaseCodeGen e (a, Code)
recurseWithState cgsSet cg = do
cfg <- ask
(cgs, oldInfo) <- get
let info = cleanInfo oldInfo
case runCodeGen cg cfg (cgsSet cgs, info) of
Left e -> throwError e
Right (r, new) -> put (cgs, mergeInfoState oldInfo new) >>
return (r, moduleCode new)
recurseWithAPIs :: M.Map Name API -> CodeGen () -> CodeGen ()
recurseWithAPIs apis cg = do
cfg <- ask
(cgs, oldInfo) <- get
let info = cleanInfo oldInfo
cfg' = cfg {loadedAPIs = apis,
c2hMap = cToHaskellMap (M.toList apis)}
case runCodeGen cg cfg' (cgs, info) of
Left e -> throwError e
Right (_, new) -> put (cgs, mergeInfo oldInfo new)
mergeInfoState :: ModuleInfo -> ModuleInfo -> ModuleInfo
mergeInfoState oldState newState =
let newDeps = Set.union (moduleDeps oldState) (moduleDeps newState)
newSubmodules = M.unionWith mergeInfo (submodules oldState) (submodules newState)
newExports = moduleExports oldState <> moduleExports newState
newImports = qualifiedImports oldState <> qualifiedImports newState
newPragmas = Set.union (modulePragmas oldState) (modulePragmas newState)
newGHCOpts = Set.union (moduleGHCOpts oldState) (moduleGHCOpts newState)
newFlags = Set.union (moduleFlags oldState) (moduleFlags newState)
newBoot = bootCode oldState <> bootCode newState
newDocs = sectionDocs oldState <> sectionDocs newState
newMinBase = max (moduleMinBase oldState) (moduleMinBase newState)
in oldState {moduleDeps = newDeps, submodules = newSubmodules,
moduleExports = newExports, qualifiedImports = newImports,
modulePragmas = newPragmas,
moduleGHCOpts = newGHCOpts, moduleFlags = newFlags,
bootCode = newBoot, sectionDocs = newDocs,
moduleMinBase = newMinBase }
mergeInfo :: ModuleInfo -> ModuleInfo -> ModuleInfo
mergeInfo oldInfo newInfo =
let info = mergeInfoState oldInfo newInfo
in info { moduleCode = moduleCode oldInfo <> moduleCode newInfo }
addSubmodule :: Text -> ModuleInfo -> (CGState, ModuleInfo)
-> (CGState, ModuleInfo)
addSubmodule modName submodule (cgs, current) =
(cgs, current { submodules = M.insertWith mergeInfo modName submodule (submodules current)})
submodule' :: Text -> BaseCodeGen e () -> BaseCodeGen e ()
submodule' modName cg = do
cfg <- ask
(_, oldInfo) <- get
let info = emptyModule (modulePath oldInfo /. modName)
case runCodeGen cg cfg (emptyCGState, info) of
Left e -> throwError e
Right (_, smInfo) -> if isCodeEmpty (moduleCode smInfo) &&
M.null (submodules smInfo)
then return ()
else modify' (addSubmodule modName smInfo)
submodule :: ModulePath -> BaseCodeGen e () -> BaseCodeGen e ()
submodule (ModulePath []) cg = cg
submodule (ModulePath (m:ms)) cg = submodule' m (submodule (ModulePath ms) cg)
handleCGExc :: (CGError -> CodeGen a) -> ExcCodeGen a -> CodeGen a
handleCGExc fallback
action = do
cfg <- ask
(cgs, oldInfo) <- get
let info = cleanInfo oldInfo
case runCodeGen action cfg (cgs, info) of
Left e -> fallback e
Right (r, newInfo) -> do
put (cgs, mergeInfo oldInfo newInfo)
return r
getDeps :: CodeGen Deps
getDeps = moduleDeps . snd <$> get
config :: CodeGen Config
config = hConfig <$> ask
currentModule :: CodeGen Text
currentModule = do
(_, s) <- get
return (dotWithPrefix (modulePath s))
getAPIs :: CodeGen (M.Map Name API)
getAPIs = loadedAPIs <$> ask
getC2HMap :: CodeGen (M.Map CRef Hyperlink)
getC2HMap = c2hMap <$> ask
unwrapCodeGen :: CodeGen a -> CodeGenConfig -> (CGState, ModuleInfo)
-> (a, ModuleInfo)
unwrapCodeGen cg cfg info =
case runCodeGen cg cfg info of
Left _ -> error "unwrapCodeGen:: The impossible happened!"
Right (r, newInfo) -> (r, newInfo)
evalCodeGen :: Config -> M.Map Name API ->
ModulePath -> CodeGen a -> (a, ModuleInfo)
evalCodeGen cfg apis mPath cg =
let initialInfo = emptyModule mPath
cfg' = CodeGenConfig {hConfig = cfg, loadedAPIs = apis,
c2hMap = cToHaskellMap (M.toList apis)}
in unwrapCodeGen cg cfg' (emptyCGState, initialInfo)
genCode :: Config -> M.Map Name API ->
ModulePath -> CodeGen () -> ModuleInfo
genCode cfg apis mPath cg = snd $ evalCodeGen cfg apis mPath cg
registerNSDependency :: Text -> CodeGen ()
registerNSDependency name = do
deps <- getDeps
unless (Set.member name deps) $ do
let newDeps = Set.insert name deps
modify' $ \(cgs, s) -> (cgs, s {moduleDeps = newDeps})
transitiveModuleDeps :: ModuleInfo -> Deps
transitiveModuleDeps minfo =
Set.unions (moduleDeps minfo
: map transitiveModuleDeps (M.elems $ submodules minfo))
qualified :: ModulePath -> Name -> CodeGen Text
qualified mp (Name ns s) = do
cfg <- config
when (modName cfg /= ns) $
registerNSDependency ns
(_, minfo) <- get
if mp == modulePath minfo
then return s
else do
qm <- qualifiedImport mp
return (qm <> "." <> s)
qualifiedImport :: ModulePath -> CodeGen Text
qualifiedImport mp = do
modify' $ \(cgs, s) -> (cgs, s {qualifiedImports = Set.insert mp (qualifiedImports s)})
return (qualifiedModuleName mp)
qualifiedModuleName :: ModulePath -> Text
qualifiedModuleName (ModulePath [ns, "Objects", o]) = ns <> "." <> o
qualifiedModuleName (ModulePath [ns, "Interfaces", i]) = ns <> "." <> i
qualifiedModuleName (ModulePath [ns, "Structs", s]) = ns <> "." <> s
qualifiedModuleName (ModulePath [ns, "Unions", u]) = ns <> "." <> u
qualifiedModuleName mp = dotModulePath mp
minBaseVersion :: ModuleInfo -> BaseVersion
minBaseVersion minfo =
maximum (moduleMinBase minfo
: map minBaseVersion (M.elems $ submodules minfo))
describeCGError :: CGError -> Text
describeCGError (CGErrorNotImplemented e) = "Not implemented: " <> tshow e
describeCGError (CGErrorBadIntrospectionInfo e) = "Bad introspection data: " <> tshow e
describeCGError (CGErrorMissingInfo e) = "Missing info: " <> tshow e
notImplementedError :: Text -> ExcCodeGen a
notImplementedError s = throwError $ CGErrorNotImplemented s
badIntroError :: Text -> ExcCodeGen a
badIntroError s = throwError $ CGErrorBadIntrospectionInfo s
missingInfoError :: Text -> ExcCodeGen a
missingInfoError s = throwError $ CGErrorMissingInfo s
getFreshTypeVariable :: CodeGen Text
getFreshTypeVariable = do
(cgs@(CGState{cgsNextAvailableTyvar = available}), s) <- get
let (tyvar, next) =
case available of
SingleCharTyvar char -> case char of
'z' -> ("z", IndexedTyvar "a" 0)
'm' -> ("n", SingleCharTyvar 'o')
c -> (T.singleton c, SingleCharTyvar (toEnum $ fromEnum c + 1))
IndexedTyvar root index -> (root <> tshow index,
IndexedTyvar root (index+1))
put (cgs {cgsNextAvailableTyvar = next}, s)
return tyvar
resetTypeVariableScope :: CodeGen ()
resetTypeVariableScope =
modify' (\(cgs, s) -> (cgs {cgsNextAvailableTyvar = SingleCharTyvar 'a'}, s))
findAPI :: Type -> CodeGen (Maybe API)
findAPI TError = Just <$> findAPIByName (Name "GLib" "Error")
findAPI (TInterface n) = Just <$> findAPIByName n
findAPI _ = return Nothing
getAPI :: Type -> CodeGen API
getAPI t = findAPI t >>= \case
Just a -> return a
Nothing -> terror ("Could not resolve type \"" <> tshow t <> "\".")
findAPIByName :: Name -> CodeGen API
findAPIByName n@(Name ns _) = do
apis <- getAPIs
case M.lookup n apis of
Just api -> return api
Nothing ->
terror $ "couldn't find API description for " <> ns <> "." <> name n
tellCode :: CodeToken -> CodeGen ()
tellCode c = modify' (\(cgs, s) -> (cgs, s {moduleCode = moduleCode s <>
codeSingleton c}))
line :: Text -> CodeGen ()
line = tellCode . Line
bline :: Text -> CodeGen ()
bline l = hsBoot (line l) >> line l
blank :: CodeGen ()
blank = line ""
indent :: BaseCodeGen e a -> BaseCodeGen e a
indent cg = do
(x, code) <- recurseCG cg
tellCode (Indent code)
return x
increaseIndent :: CodeGen ()
increaseIndent = tellCode IncreaseIndent
group :: BaseCodeGen e a -> BaseCodeGen e a
group cg = do
(x, code) <- recurseCG cg
tellCode (Group code)
blank
return x
cppIfBlock :: Text -> BaseCodeGen e a -> BaseCodeGen e a
cppIfBlock cond cg = do
(x, code) <- recurseWithState addConditional cg
tellCode (CPPBlock (CPPIf cond) code)
blank
return x
where addConditional :: CGState -> CGState
addConditional cgs = cgs {cgsCPPConditionals = CPPIf cond :
cgsCPPConditionals cgs}
data CPPGuard = CPPOverloading
cppIf :: CPPGuard -> BaseCodeGen e a -> BaseCodeGen e a
cppIf CPPOverloading = cppIfBlock "ENABLE_OVERLOADING"
hsBoot :: BaseCodeGen e a -> BaseCodeGen e a
hsBoot cg = do
(x, code) <- recurseCG cg
modify' (\(cgs, s) -> (cgs, s{bootCode = bootCode s <>
addGuards (cgsCPPConditionals cgs) code}))
return x
where addGuards :: [CPPConditional] -> Code -> Code
addGuards [] c = c
addGuards (cond : conds) c = codeSingleton $ CPPBlock cond (addGuards conds c)
exportPartial :: ([CPPConditional] -> Export) -> CodeGen ()
exportPartial partial =
modify' $ \(cgs, s) -> (cgs,
let e = partial $ cgsCPPConditionals cgs
in s{moduleExports = moduleExports s |> e})
exportModule :: SymbolName -> CodeGen ()
exportModule m = exportPartial (Export ExportModule m)
exportDecl :: SymbolName -> CodeGen ()
exportDecl d = exportPartial (Export ExportTypeDecl d)
export :: HaddockSection -> SymbolName -> CodeGen ()
export s n = exportPartial (Export (ExportSymbol s) n)
setLanguagePragmas :: [Text] -> CodeGen ()
setLanguagePragmas ps =
modify' $ \(cgs, s) -> (cgs, s{modulePragmas = Set.fromList ps})
setGHCOptions :: [Text] -> CodeGen ()
setGHCOptions opts =
modify' $ \(cgs, s) -> (cgs, s{moduleGHCOpts = Set.fromList opts})
setModuleFlags :: [ModuleFlag] -> CodeGen ()
setModuleFlags flags =
modify' $ \(cgs, s) -> (cgs, s{moduleFlags = Set.fromList flags})
setModuleMinBase :: BaseVersion -> CodeGen ()
setModuleMinBase v =
modify' $ \(cgs, s) -> (cgs, s{moduleMinBase = max v (moduleMinBase s)})
addSectionFormattedDocs :: HaddockSection -> Text -> CodeGen ()
addSectionFormattedDocs section docs =
modify' $ \(cgs, s) -> (cgs, s{sectionDocs = M.insertWith (<>) section
docs (sectionDocs s)})
cppCondFormat :: CPPConditional -> (Text, Text)
cppCondFormat (CPPIf c) = ("#if " <> c <> "\n", "#endif\n")
codeToText :: Code -> Text
codeToText (Code seq) = LT.toStrict . B.toLazyText $ genCode 0 (viewl seq)
where genCode :: Int -> ViewL CodeToken -> B.Builder
genCode _ Seq.EmptyL = mempty
genCode n (Line s :< rest) = B.fromText (paddedLine n s) <>
genCode n (viewl rest)
genCode n (Indent (Code seq) :< rest) = genCode (n+1) (viewl seq) <>
genCode n (viewl rest)
genCode n (Group (Code seq) :< rest) = genCode n (viewl seq) <>
genCode n (viewl rest)
genCode n (CPPBlock cond (Code seq) :< rest) =
let (condBegin, condEnd) = cppCondFormat cond
in B.fromText condBegin <> genCode n (viewl seq) <>
B.fromText condEnd <> genCode n (viewl rest)
genCode n (IncreaseIndent :< rest) = genCode (n+1) (viewl rest)
paddedLine :: Int -> Text -> Text
paddedLine n s = T.replicate (n * 4) " " <> s <> "\n"
comma :: Text -> Text
comma s = padTo 40 s <> ","
formatExport :: (Export -> Text) -> Export -> Text
formatExport formatName export = go (exportGuards export)
where go :: [CPPConditional] -> Text
go [] = (paddedLine 1 . comma . formatName) export
go (c:cs) = let (begin, end) = cppCondFormat c
in begin <> go cs <> end
formatExportedModules :: [Export] -> Maybe Text
formatExportedModules [] = Nothing
formatExportedModules exports =
Just . T.concat . map (formatExport (("module " <>) . exportSymbol))
. filter ((== ExportModule) . exportType) $ exports
formatToplevel :: [Export] -> Maybe Text
formatToplevel [] = Nothing
formatToplevel exports =
Just . T.concat . map (formatExport exportSymbol)
. filter ((== ExportSymbol ToplevelSection) . exportType) $ exports
formatTypeDecls :: [Export] -> Maybe Text
formatTypeDecls exports =
let exportedTypes = filter ((== ExportTypeDecl) . exportType) exports
in if exportedTypes == []
then Nothing
else Just . T.unlines $ [ "-- * Exported types"
, T.concat . map ( formatExport exportSymbol )
$ exportedTypes ]
data Subsection = Subsection { subsectionTitle :: Text
, subsectionAnchor :: Maybe Text
, subsectionDoc :: Maybe Text
} deriving (Eq, Show, Ord)
subsecWithPrefix :: NamedSection -> Text -> Maybe Text -> Subsection
subsecWithPrefix mainSection title doc =
Subsection { subsectionTitle = title
, subsectionAnchor = Just (prefix <> ":" <> title)
, subsectionDoc = doc }
where prefix = case mainSection of
MethodSection -> "method"
PropertySection -> "attr"
SignalSection -> "signal"
EnumSection -> "enum"
FlagSection -> "flag"
mainSectionName :: NamedSection -> Text
mainSectionName MethodSection = "Methods"
mainSectionName PropertySection = "Properties"
mainSectionName SignalSection = "Signals"
mainSectionName EnumSection = "Enumerations"
mainSectionName FlagSection = "Flags"
formatSection :: NamedSection -> [(Subsection, Export)] -> Maybe Text
formatSection section exports =
if null exports
then Nothing
else Just . T.unlines $ [" -- * " <> mainSectionName section
, ( T.unlines
. map formatSubsection
. M.toList ) exportedSubsections]
where
exportedSubsections :: M.Map Subsection (Set.Set Export)
exportedSubsections = foldr extract M.empty exports
extract :: (Subsection, Export) -> M.Map Subsection (Set.Set Export)
-> M.Map Subsection (Set.Set Export)
extract (subsec, m) secs =
M.insertWith Set.union subsec (Set.singleton m) secs
formatSubsection :: (Subsection, Set.Set Export) -> Text
formatSubsection (subsec, symbols) =
T.unlines [ "-- ** " <> case subsectionAnchor subsec of
Just anchor -> subsectionTitle subsec <>
" #" <> anchor <> "#"
Nothing -> subsectionTitle subsec
, case subsectionDoc subsec of
Just text -> "{- | " <> text <> "\n-}"
Nothing -> ""
, ( T.concat
. map (formatExport exportSymbol)
. Set.toList ) symbols]
formatSubsectionExports :: M.Map HaddockSection Text -> [Export] -> [Maybe Text]
formatSubsectionExports docs exports = map (uncurry formatSection)
(M.toAscList collectedExports)
where collectedExports :: M.Map NamedSection [(Subsection, Export)]
collectedExports = foldl classifyExport M.empty exports
classifyExport :: M.Map NamedSection [(Subsection, Export)] ->
Export -> M.Map NamedSection [(Subsection, Export)]
classifyExport m export =
case exportType export of
ExportSymbol hs@(NamedSubsection ms n) ->
let subsec = subsecWithPrefix ms n (M.lookup hs docs)
in M.insertWith (++) ms [(subsec, export)] m
_ -> m
formatExportList :: M.Map HaddockSection Text -> [Export] -> Text
formatExportList docs exports =
T.unlines . catMaybes $ formatExportedModules exports
: formatToplevel exports
: formatTypeDecls exports
: formatSubsectionExports docs exports
languagePragmas :: [Text] -> Text
languagePragmas [] = ""
languagePragmas ps = "{-# LANGUAGE " <> T.intercalate ", " ps <> " #-}\n"
ghcOptions :: [Text] -> Text
ghcOptions [] = ""
ghcOptions opts = "{-# OPTIONS_GHC " <> T.intercalate ", " opts <> " #-}\n"
cppMacros :: Text
cppMacros = T.unlines ["#define ENABLE_OVERLOADING (MIN_VERSION_haskell_gi_overloading(1,0,0) \\"
, " && !defined(__HADDOCK_VERSION__))"
]
standardFields :: Text
standardFields = T.unlines [ "Copyright : " <> authors
, "License : " <> license
, "Maintainer : " <> maintainers ]
moduleHaddock :: Maybe Text -> Text
moduleHaddock Nothing = T.unlines ["{- |", standardFields <> "-}"]
moduleHaddock (Just description) = T.unlines ["{- |", standardFields,
description, "-}"]
modulePrelude :: M.Map HaddockSection Text -> Text -> [Export] -> [Text] -> Text
modulePrelude _ name [] [] = "module " <> name <> " () where\n"
modulePrelude docs name exports [] =
"module " <> name <> "\n ( "
<> formatExportList docs exports
<> " ) where\n"
modulePrelude docs name [] reexportedModules =
"module " <> name <> "\n ( "
<> formatExportList docs (map (\m -> Export ExportModule m []) reexportedModules)
<> " ) where\n\n"
<> T.unlines (map ("import " <>) reexportedModules)
modulePrelude docs name exports reexportedModules =
"module " <> name <> "\n ( "
<> formatExportList docs (map (\m -> Export ExportModule m []) reexportedModules)
<> "\n"
<> formatExportList docs exports
<> " ) where\n\n"
<> T.unlines (map ("import " <>) reexportedModules)
importDeps :: ModulePath -> [ModulePath] -> Text
importDeps _ [] = ""
importDeps (ModulePath prefix) deps = T.unlines . map toImport $ deps
where toImport :: ModulePath -> Text
toImport dep = let impSt = if importSource dep
then "import {-# SOURCE #-} qualified "
else "import qualified "
in impSt <> dotWithPrefix dep <>
" as " <> qualifiedModuleName dep
importSource :: ModulePath -> Bool
importSource (ModulePath [_, "Callbacks"]) = False
importSource (ModulePath mp) = take (length prefix) mp == prefix
moduleImports :: Text
moduleImports = T.unlines [
"import Data.GI.Base.ShortPrelude"
, "import qualified Data.GI.Base.ShortPrelude as SP"
, "import qualified Data.GI.Base.Overloading as O"
, "import qualified Prelude as P"
, ""
, "import qualified Data.GI.Base.Attributes as GI.Attributes"
, "import qualified Data.GI.Base.ManagedPtr as B.ManagedPtr"
, "import qualified Data.GI.Base.GClosure as B.GClosure"
, "import qualified Data.GI.Base.GError as B.GError"
, "import qualified Data.GI.Base.GVariant as B.GVariant"
, "import qualified Data.GI.Base.GValue as B.GValue"
, "import qualified Data.GI.Base.GParamSpec as B.GParamSpec"
, "import qualified Data.GI.Base.CallStack as B.CallStack"
, "import qualified Data.GI.Base.Properties as B.Properties"
, "import qualified Data.Text as T"
, "import qualified Data.ByteString.Char8 as B"
, "import qualified Data.Map as Map"
, "import qualified Foreign.Ptr as FP"
, "import qualified GHC.OverloadedLabels as OL" ]
dotWithPrefix :: ModulePath -> Text
dotWithPrefix mp = dotModulePath ("GI" <> mp)
writeModuleInfo :: Bool -> Maybe FilePath -> ModuleInfo -> IO ()
writeModuleInfo verbose dirPrefix minfo = do
let submodulePaths = map (modulePath) (M.elems (submodules minfo))
submoduleExports = map dotWithPrefix submodulePaths
fname = modulePathToFilePath dirPrefix (modulePath minfo) ".hs"
dirname = takeDirectory fname
code = codeToText (moduleCode minfo)
pragmas = languagePragmas (Set.toList $ modulePragmas minfo)
optionsGHC = ghcOptions (Set.toList $ moduleGHCOpts minfo)
prelude = modulePrelude (sectionDocs minfo)
(dotWithPrefix $ modulePath minfo)
(F.toList (moduleExports minfo))
submoduleExports
imports = if ImplicitPrelude `Set.member` moduleFlags minfo
then ""
else moduleImports
pkgRoot = ModulePath (take 1 (modulePathToList $ modulePath minfo))
deps = importDeps pkgRoot (Set.toList $ qualifiedImports minfo)
haddock = moduleHaddock (M.lookup ToplevelSection (sectionDocs minfo))
when verbose $ putStrLn ((T.unpack . dotWithPrefix . modulePath) minfo
++ " -> " ++ fname)
createDirectoryIfMissing True dirname
utf8WriteFile fname (T.unlines [pragmas, optionsGHC, haddock, cppMacros,
prelude, imports, deps, code])
when (not . isCodeEmpty $ bootCode minfo) $ do
let bootFName = modulePathToFilePath dirPrefix (modulePath minfo) ".hs-boot"
utf8WriteFile bootFName (genHsBoot minfo)
genHsBoot :: ModuleInfo -> Text
genHsBoot minfo =
cppMacros <>
"module " <> (dotWithPrefix . modulePath) minfo <> " where\n\n" <>
moduleImports <> "\n" <>
codeToText (bootCode minfo)
modulePathToFilePath :: Maybe FilePath -> ModulePath -> FilePath -> FilePath
modulePathToFilePath dirPrefix (ModulePath mp) ext =
joinPath (fromMaybe "" dirPrefix : "GI" : map T.unpack mp) ++ ext
writeModuleTree :: Bool -> Maybe FilePath -> ModuleInfo -> IO [Text]
writeModuleTree verbose dirPrefix minfo = do
submodulePaths <- concat <$> forM (M.elems (submodules minfo))
(writeModuleTree verbose dirPrefix)
writeModuleInfo verbose dirPrefix minfo
return $ (dotWithPrefix (modulePath minfo) : submodulePaths)
listModuleTree :: ModuleInfo -> [Text]
listModuleTree minfo =
let submodulePaths = concatMap listModuleTree (M.elems (submodules minfo))
in dotWithPrefix (modulePath minfo) : submodulePaths