1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
|
-- | Support for enums and flags.
module Data.GI.CodeGen.EnumFlags
( genEnum
, genFlags
) where
import Control.Monad (when, forM_)
#if !MIN_VERSION_base(4,11,0)
import Data.Monoid ((<>))
#endif
import Data.Text (Text)
import qualified Data.Set as S
import Foreign.C (CUInt)
import Foreign.Storable (sizeOf)
import Data.GI.CodeGen.API
import Data.GI.CodeGen.Code
import Data.GI.CodeGen.Haddock (deprecatedPragma, writeDocumentation,
writeHaddock, RelativeDocPosition(..))
import Data.GI.CodeGen.SymbolNaming (upperName)
import Data.GI.CodeGen.Util (tshow)
-- | Given a list of named enum members, filter out those that have
-- the same value as a previous entry in the list.
dropDuplicated :: [(Text, EnumerationMember)] -> [(Text, EnumerationMember)]
dropDuplicated namedMembers = go namedMembers enumMemberValue S.empty
where go :: Ord c => [(a, b)] -> (b->c) -> S.Set c -> [(a, b)]
go [] _ _ = []
go ((n, m) : rest) f seen =
if S.member (f m) seen
-- already seen, discard
then go rest f seen
else (n,m) : go rest f (S.insert (f m) seen)
genEnumOrFlags :: HaddockSection -> Name -> Enumeration -> ExcCodeGen ()
genEnumOrFlags docSection n@(Name ns name) e = do
-- Conversion functions expect enums and flags to map to CUInt,
-- which we assume to be of 32 bits. Fail early, instead of giving
-- strange errors at runtime.
when (sizeOf (0 :: CUInt) /= 4) $
notImplementedError $ "Unsupported CUInt size: " <> tshow (sizeOf (0 :: CUInt))
when (enumStorageBytes e /= 4) $
notImplementedError $ "Storage of size /= 4 not supported : " <> tshow (enumStorageBytes e)
let name' = upperName n
members' = flip map (enumMembers e) $ \member ->
let n = upperName $ Name ns (name <> "_" <> enumMemberName member)
in (n, member)
deprecatedPragma name' (enumDeprecated e)
group $ do
export docSection (name' <> "(..)")
hsBoot . line $ "data " <> name'
writeDocumentation DocBeforeSymbol (enumDocumentation e)
line $ "data " <> name' <> " = "
indent $
case members' of
((fieldName, firstMember):fs) -> do
line $ " " <> fieldName
writeDocumentation DocAfterSymbol (enumMemberDoc firstMember)
forM_ fs $ \(n, member) -> do
line $ "| " <> n
writeDocumentation DocAfterSymbol (enumMemberDoc member)
line $ "| Another" <> name' <> " Int"
writeHaddock DocAfterSymbol "Catch-all for unknown values"
line "deriving (Show, Eq)"
_ -> return ()
group $ do
bline $ "instance P.Enum " <> name' <> " where"
indent $ do
forM_ members' $ \(n, m) ->
line $ "fromEnum " <> n <> " = " <> tshow (enumMemberValue m)
line $ "fromEnum (Another" <> name' <> " k) = k"
blank
indent $ do
forM_ (dropDuplicated members') $ \(n, m) ->
line $ "toEnum " <> tshow (enumMemberValue m) <> " = " <> n
line $ "toEnum k = Another" <> name' <> " k"
group $ do
line $ "instance P.Ord " <> name' <> " where"
indent $ line "compare a b = P.compare (P.fromEnum a) (P.fromEnum b)"
maybe (return ()) (genErrorDomain docSection name') (enumErrorDomain e)
genBoxedEnum :: Name -> Text -> CodeGen e ()
genBoxedEnum n typeInit = do
let name' = upperName n
group $ do
line $ "type instance O.ParentTypes " <> name' <> " = '[]"
bline $ "instance O.HasParentTypes " <> name'
group $ do
line $ "foreign import ccall \"" <> typeInit <> "\" c_" <>
typeInit <> " :: "
indent $ line "IO GType"
group $ do
bline $ "instance B.Types.TypedObject " <> name' <> " where"
indent $ line $ "glibType = c_" <> typeInit
group $ do
bline $ "instance B.Types.BoxedEnum " <> name'
genEnum :: Name -> Enumeration -> CodeGen e ()
genEnum n@(Name _ name) enum = do
line $ "-- Enum " <> name
let docSection = NamedSubsection EnumSection (upperName n)
handleCGExc (\e -> do
line $ "-- XXX Code Generation error"
printCGError e)
(do genEnumOrFlags docSection n enum
case enumTypeInit enum of
Nothing -> return ()
Just ti -> genBoxedEnum n ti)
genBoxedFlags :: Name -> Text -> CodeGen e ()
genBoxedFlags n typeInit = do
let name' = upperName n
group $ do
line $ "type instance O.ParentTypes " <> name' <> " = '[]"
bline $ "instance O.HasParentTypes " <> name'
group $ do
line $ "foreign import ccall \"" <> typeInit <> "\" c_" <>
typeInit <> " :: "
indent $ line "IO GType"
group $ do
bline $ "instance B.Types.TypedObject " <> name' <> " where"
indent $ line $ "glibType = c_" <> typeInit
group $ do
bline $ "instance B.Types.BoxedFlags " <> name'
-- | Very similar to enums, but we also declare ourselves as members of
-- the IsGFlag typeclass.
genFlags :: Name -> Flags -> CodeGen e ()
genFlags n@(Name _ name) (Flags enum) = do
line $ "-- Flags " <> name
let docSection = NamedSubsection FlagSection (upperName n)
handleCGExc (\e -> do
line "-- XXX Code generation error"
printCGError e)
(do
genEnumOrFlags docSection n enum
case enumTypeInit enum of
Nothing -> return ()
Just ti -> genBoxedFlags n ti
let name' = upperName n
group $ bline $ "instance IsGFlag " <> name')
-- | Support for enums encapsulating error codes.
genErrorDomain :: HaddockSection -> Text -> Text -> CodeGen e ()
genErrorDomain docSection name' domain = do
group $ do
line $ "instance GErrorClass " <> name' <> " where"
indent $ line $
"gerrorClassDomain _ = \"" <> domain <> "\""
-- Generate type specific error handling (saves a bit of typing, and
-- it's clearer to read).
group $ do
let catcher = "catch" <> name'
writeHaddock DocBeforeSymbol catcherDoc
line $ catcher <> " ::"
indent $ do
line "IO a ->"
line $ "(" <> name' <> " -> GErrorMessage -> IO a) ->"
line "IO a"
line $ catcher <> " = catchGErrorJustDomain"
group $ do
let handler = "handle" <> name'
writeHaddock DocBeforeSymbol handleDoc
line $ handler <> " ::"
indent $ do
line $ "(" <> name' <> " -> GErrorMessage -> IO a) ->"
line "IO a ->"
line "IO a"
line $ handler <> " = handleGErrorJustDomain"
export docSection ("catch" <> name')
export docSection ("handle" <> name')
where
catcherDoc :: Text
catcherDoc = "Catch exceptions of type `" <> name' <> "`. This is a specialized version of `Data.GI.Base.GError.catchGErrorJustDomain`."
handleDoc :: Text
handleDoc = "Handle exceptions of type `" <> name' <> "`. This is a specialized version of `Data.GI.Base.GError.handleGErrorJustDomain`."
|