haskell-gi-0.20: lib/Data/GI/CodeGen/Callable.hs
{-# LANGUAGE LambdaCase #-}
module Data.GI.CodeGen.Callable
( genCCallableWrapper
, genDynamicCallableWrapper
, ForeignSymbol(..)
, ExposeClosures(..)
, hOutType
, arrayLengths
, arrayLengthsMap
, callableSignature
, fixupCallerAllocates
, callableHInArgs
, callableHOutArgs
, wrapMaybe
, inArgInterfaces
) where
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative ((<$>))
#endif
import Control.Monad (forM, forM_, when, void)
import Data.Bool (bool)
import Data.List (nub, (\\))
import Data.Maybe (isJust)
import Data.Monoid ((<>))
import Data.Tuple (swap)
import Data.Typeable (TypeRep, typeOf)
import qualified Data.Map as Map
import qualified Data.Text as T
import Data.Text (Text)
import Data.GI.CodeGen.API
import Data.GI.CodeGen.Code
import Data.GI.CodeGen.Conversions
import Data.GI.CodeGen.SymbolNaming
import Data.GI.CodeGen.Transfer
import Data.GI.CodeGen.Type
import Data.GI.CodeGen.Util
import Text.Show.Pretty (ppShow)
-- | Whether to expose closures and the associated destroy notify
-- handlers in the Haskell wrapper.
data ExposeClosures = WithClosures
| WithoutClosures
hOutType :: Callable -> [Arg] -> Bool -> ExcCodeGen TypeRep
hOutType callable outArgs ignoreReturn = do
hReturnType <- case returnType callable of
Nothing -> return $ typeOf ()
Just r -> if ignoreReturn
then return $ typeOf ()
else haskellType r
hOutArgTypes <- forM outArgs $ \outarg ->
wrapMaybe outarg >>= bool
(haskellType (argType outarg))
(maybeT <$> haskellType (argType outarg))
nullableReturnType <- maybe (return False) typeIsNullable (returnType callable)
let maybeHReturnType = if returnMayBeNull callable && not ignoreReturn
&& nullableReturnType
then maybeT hReturnType
else hReturnType
return $ case (outArgs, tshow maybeHReturnType) of
([], _) -> maybeHReturnType
(_, "()") -> "(,)" `con` hOutArgTypes
_ -> "(,)" `con` (maybeHReturnType : hOutArgTypes)
-- | Generate a foreign import for the given C symbol. Return the name
-- of the corresponding Haskell identifier.
mkForeignImport :: Text -> Callable -> Bool -> CodeGen Text
mkForeignImport cSymbol callable throwsGError = do
line first
indent $ do
mapM_ (\a -> line =<< fArgStr a) (args callable)
when throwsGError $
line $ padTo 40 "Ptr (Ptr GError) -> " <> "-- error"
line =<< last
return hSymbol
where
hSymbol = cSymbol
first = "foreign import ccall \"" <> cSymbol <> "\" " <> hSymbol <> " :: "
fArgStr arg = do
ft <- foreignType $ argType arg
let ft' = if direction arg == DirectionIn || argCallerAllocates arg
then ft
else ptr ft
let start = tshow ft' <> " -> "
return $ padTo 40 start <> "-- " <> (argCName arg)
<> " : " <> tshow (argType arg)
last = tshow <$> io <$> case returnType callable of
Nothing -> return $ typeOf ()
Just r -> foreignType r
-- | Make a wrapper for foreign `FunPtr`s of the given type. Return
-- the name of the resulting dynamic Haskell wrapper.
mkDynamicImport :: Text -> CodeGen Text
mkDynamicImport typeSynonym = do
line $ "foreign import ccall \"dynamic\" " <> dynamic <> " :: FunPtr "
<> typeSynonym <> " -> " <> typeSynonym
return dynamic
where dynamic = "__dynamic_" <> typeSynonym
-- | Given an argument to a function, return whether it should be
-- wrapped in a maybe type (useful for nullable types). We do some
-- sanity checking to make sure that the argument is actually nullable
-- (a relatively common annotation mistake is to mix up (optional)
-- with (nullable)).
wrapMaybe :: Arg -> CodeGen Bool
wrapMaybe arg = if mayBeNull arg
then typeIsNullable (argType arg)
else return False
-- Given the list of arguments returns the list of constraints and the
-- list of types in the signature.
inArgInterfaces :: [Arg] -> ExcCodeGen ([Text], [Text])
inArgInterfaces inArgs = consAndTypes (['a'..'z'] \\ ['m']) inArgs
where
consAndTypes :: [Char] -> [Arg] -> ExcCodeGen ([Text], [Text])
consAndTypes _ [] = return ([], [])
consAndTypes letters (arg:args) = do
(ls, t, cons) <- argumentType letters $ argType arg
t' <- wrapMaybe arg >>= bool (return t)
(return $ "Maybe (" <> t <> ")")
(restCons, restTypes) <- consAndTypes ls args
return (cons <> restCons, t' : restTypes)
-- Given a callable, return a list of (array, length) pairs, where in
-- each pair "length" is the argument holding the length of the
-- (non-zero-terminated, non-fixed size) C array.
arrayLengthsMap :: Callable -> [(Arg, Arg)] -- List of (array, length)
arrayLengthsMap callable = go (args callable) []
where
go :: [Arg] -> [(Arg, Arg)] -> [(Arg, Arg)]
go [] acc = acc
go (a:as) acc = case argType a of
TCArray False fixedSize length _ ->
if fixedSize > -1 || length == -1
then go as acc
else go as $ (a, (args callable)!!length) : acc
_ -> go as acc
-- Return the list of arguments of the callable that contain length
-- arguments, including a possible length for the result of calling
-- the function.
arrayLengths :: Callable -> [Arg]
arrayLengths callable = map snd (arrayLengthsMap callable) <>
-- Often one of the arguments is just the length of
-- the result.
case returnType callable of
Just (TCArray False (-1) length _) ->
if length > -1
then [(args callable)!!length]
else []
_ -> []
-- This goes through a list of [(a,b)], and tags every entry where the
-- "b" field has occurred before with the value of "a" for which it
-- occurred. (The first appearance is not tagged.)
classifyDuplicates :: Ord b => [(a, b)] -> [(a, b, Maybe a)]
classifyDuplicates args = doClassify Map.empty args
where doClassify :: Ord b => Map.Map b a -> [(a, b)] -> [(a, b, Maybe a)]
doClassify _ [] = []
doClassify found ((value, key):args) =
(value, key, Map.lookup key found) :
doClassify (Map.insert key value found) args
-- Read the length of in array arguments from the corresponding
-- Haskell objects. A subtlety is that sometimes a single length
-- argument is expected from the C side to encode the length of
-- various lists. Ideally we would encode this in the types, but the
-- resulting API would be rather cumbersome. We insted perform runtime
-- checks to make sure that the given lists have the same length.
readInArrayLengths :: Name -> Callable -> [Arg] -> ExcCodeGen ()
readInArrayLengths name callable hInArgs = do
let lengthMaps = classifyDuplicates $ arrayLengthsMap callable
forM_ lengthMaps $ \(array, length, duplicate) ->
when (array `elem` hInArgs) $
case duplicate of
Nothing -> readInArrayLength array length
Just previous -> checkInArrayLength name array length previous
-- Read the length of an array into the corresponding variable.
readInArrayLength :: Arg -> Arg -> ExcCodeGen ()
readInArrayLength array length = do
let lvar = escapedArgName length
avar = escapedArgName array
wrapMaybe array >>= bool
(do
al <- computeArrayLength avar (argType array)
line $ "let " <> lvar <> " = " <> al)
(do
line $ "let " <> lvar <> " = case " <> avar <> " of"
indent $ indent $ do
line $ "Nothing -> 0"
let jarray = "j" <> ucFirst avar
al <- computeArrayLength jarray (argType array)
line $ "Just " <> jarray <> " -> " <> al)
-- Check that the given array has a length equal to the given length
-- variable.
checkInArrayLength :: Name -> Arg -> Arg -> Arg -> ExcCodeGen ()
checkInArrayLength n array length previous = do
let name = lowerName n
funcName = namespace n <> "." <> name
lvar = escapedArgName length
avar = escapedArgName array
expectedLength = avar <> "_expected_length_"
pvar = escapedArgName previous
wrapMaybe array >>= bool
(do
al <- computeArrayLength avar (argType array)
line $ "let " <> expectedLength <> " = " <> al)
(do
line $ "let " <> expectedLength <> " = case " <> avar <> " of"
indent $ indent $ do
line $ "Nothing -> 0"
let jarray = "j" <> ucFirst avar
al <- computeArrayLength jarray (argType array)
line $ "Just " <> jarray <> " -> " <> al)
line $ "when (" <> expectedLength <> " /= " <> lvar <> ") $"
indent $ line $ "error \"" <> funcName <> " : length of '" <> avar <>
"' does not agree with that of '" <> pvar <> "'.\""
-- Whether to skip the return value in the generated bindings. The
-- C convention is that functions throwing an error and returning
-- a gboolean set the boolean to TRUE iff there is no error, so
-- the information is always implicit in whether we emit an
-- exception or not, so the return value can be omitted from the
-- generated bindings without loss of information (and omitting it
-- gives rise to a nicer API). See
-- https://bugzilla.gnome.org/show_bug.cgi?id=649657
skipRetVal :: Callable -> Bool -> Bool
skipRetVal callable throwsGError =
(skipReturn callable) ||
(throwsGError && returnType callable == Just (TBasicType TBoolean))
freeInArgs' :: (Arg -> Text -> Text -> ExcCodeGen [Text]) ->
Callable -> Map.Map Text Text -> ExcCodeGen [Text]
freeInArgs' freeFn callable nameMap = concat <$> actions
where
actions :: ExcCodeGen [[Text]]
actions = forM (args callable) $ \arg ->
case Map.lookup (escapedArgName arg) nameMap of
Just name -> freeFn arg name $
-- Pass in the length argument in case it's needed.
case argType arg of
TCArray False (-1) (-1) _ -> undefined
TCArray False (-1) length _ ->
escapedArgName $ (args callable)!!length
_ -> undefined
Nothing -> badIntroError $ "freeInArgs: do not understand " <> tshow arg
-- Return the list of actions freeing the memory associated with the
-- callable variables. This is run if the call to the C function
-- succeeds, if there is an error freeInArgsOnError below is called
-- instead.
freeInArgs = freeInArgs' freeInArg
-- Return the list of actions freeing the memory associated with the
-- callable variables. This is run in case there is an error during
-- the call.
freeInArgsOnError = freeInArgs' freeInArgOnError
-- Marshall the haskell arguments into their corresponding C
-- equivalents. omitted gives a list of DirectionIn arguments that
-- should be ignored, as they will be dealt with separately.
prepareArgForCall :: [Arg] -> Arg -> ExcCodeGen Text
prepareArgForCall omitted arg = do
callback <- findAPI (argType arg) >>=
\case Just (APICallback c) -> return (Just c)
_ -> return Nothing
when (isJust callback && direction arg /= DirectionIn) $
notImplementedError "Only callbacks with DirectionIn are supported"
case direction arg of
DirectionIn -> if arg `elem` omitted
then return . escapedArgName $ arg
else case callback of
Just c -> prepareInCallback arg c
Nothing -> prepareInArg arg
DirectionInout -> prepareInoutArg arg
DirectionOut -> prepareOutArg arg
prepareInArg :: Arg -> ExcCodeGen Text
prepareInArg arg = do
let name = escapedArgName arg
wrapMaybe arg >>= bool
(convert name $ hToF (argType arg) (transfer arg))
(do
let maybeName = "maybe" <> ucFirst name
line $ maybeName <> " <- case " <> name <> " of"
indent $ do
line $ "Nothing -> return nullPtr"
let jName = "j" <> ucFirst name
line $ "Just " <> jName <> " -> do"
indent $ do
converted <- convert jName $ hToF (argType arg)
(transfer arg)
line $ "return " <> converted
return maybeName)
-- Callbacks are a fairly special case, we treat them separately.
prepareInCallback :: Arg -> Callback -> CodeGen Text
prepareInCallback arg (Callback cb) = do
let name = escapedArgName arg
ptrName = "ptr" <> name
scope = argScope arg
(maker, wrapper, drop) <-
case argType arg of
TInterface tn@(Name _ n) ->
do
drop <- if callableHasClosures cb
then Just <$> qualifiedSymbol (callbackDropClosures n) tn
else return Nothing
wrapper <- qualifiedSymbol (callbackHaskellToForeign n) tn
maker <- qualifiedSymbol (callbackWrapperAllocator n) tn
return (maker, wrapper, drop)
_ -> terror $ "prepareInCallback : Not an interface! " <> T.pack (ppShow arg)
when (scope == ScopeTypeAsync) $ do
ft <- tshow <$> foreignType (argType arg)
line $ ptrName <> " <- callocMem :: IO (Ptr (" <> ft <> "))"
wrapMaybe arg >>= bool
(do
let name' = prime name
p = if (scope == ScopeTypeAsync)
then parenthesize $ "Just " <> ptrName
else "Nothing"
dropped =
case drop of
Just dropper -> parenthesize (dropper <> " " <> name)
Nothing -> name
line $ name' <> " <- " <> maker <> " "
<> parenthesize (wrapper <> " " <> p <> " " <> dropped)
when (scope == ScopeTypeAsync) $
line $ "poke " <> ptrName <> " " <> name'
return name')
(do
let maybeName = "maybe" <> ucFirst name
line $ maybeName <> " <- case " <> name <> " of"
indent $ do
line $ "Nothing -> return (castPtrToFunPtr nullPtr)"
let jName = "j" <> ucFirst name
jName' = prime jName
line $ "Just " <> jName <> " -> do"
indent $ do
let p = if (scope == ScopeTypeAsync)
then parenthesize $ "Just " <> ptrName
else "Nothing"
dropped =
case drop of
Just dropper ->
parenthesize (dropper <> " " <> jName)
Nothing -> jName
line $ jName' <> " <- " <> maker <> " "
<> parenthesize (wrapper <> " "
<> p <> " " <> dropped)
when (scope == ScopeTypeAsync) $
line $ "poke " <> ptrName <> " " <> jName'
line $ "return " <> jName'
return maybeName)
prepareInoutArg :: Arg -> ExcCodeGen Text
prepareInoutArg arg = do
name' <- prepareInArg arg
ft <- foreignType $ argType arg
allocInfo <- typeAllocInfo (argType arg)
case allocInfo of
Just (TypeAllocInfo isBoxed n) -> do
let allocator = if isBoxed
then "callocBoxedBytes"
else "callocBytes"
wrapMaybe arg >>= bool
(do
name'' <- genConversion (prime name') $
literal $ M $ allocator <> " " <> tshow n <>
" :: " <> tshow (io ft)
line $ "memcpy " <> name'' <> " " <> name' <> " " <> tshow n
return name'')
-- The semantics of this case are somewhat undefined.
(notImplementedError "Nullable inout structs not supported")
Nothing -> do
if argCallerAllocates arg
then return name'
else do
name'' <- genConversion (prime name') $
literal $ M $ "allocMem :: " <> tshow (io $ ptr ft)
line $ "poke " <> name'' <> " " <> name'
return name''
prepareOutArg :: Arg -> ExcCodeGen Text
prepareOutArg arg = do
let name = escapedArgName arg
ft <- foreignType $ argType arg
if argCallerAllocates arg
then do
allocInfo <- typeAllocInfo (argType arg)
case allocInfo of
Just (TypeAllocInfo isBoxed n) -> do
let allocator = if isBoxed
then "callocBoxedBytes"
else "callocBytes"
genConversion name $ literal $ M $ allocator <> " " <> tshow n <>
" :: " <> tshow (io ft)
Nothing ->
notImplementedError $ ("Don't know how to allocate \""
<> argCName arg <> "\" of type "
<> tshow (argType arg))
else genConversion name $
literal $ M $ "allocMem :: " <> tshow (io $ ptr ft)
-- Convert a non-zero terminated out array, stored in a variable
-- named "aname", into the corresponding Haskell object.
convertOutCArray :: Callable -> Type -> Text -> Map.Map Text Text ->
Transfer -> (Text -> Text) -> ExcCodeGen Text
convertOutCArray callable t@(TCArray False fixed length _) aname
nameMap transfer primeLength = do
if fixed > -1
then do
unpacked <- convert aname $ unpackCArray (tshow fixed) t transfer
-- Free the memory associated with the array
freeContainerType transfer t aname undefined
return unpacked
else do
when (length == -1) $
badIntroError $ "Unknown length for \"" <> aname <> "\""
let lname = escapedArgName $ (args callable)!!length
lname' <- case Map.lookup lname nameMap of
Just n -> return n
Nothing ->
badIntroError $ "Couldn't find out array length " <>
lname
let lname'' = primeLength lname'
unpacked <- convert aname $ unpackCArray lname'' t transfer
-- Free the memory associated with the array
freeContainerType transfer t aname lname''
return unpacked
-- Remove the warning, this should never be reached.
convertOutCArray _ t _ _ _ _ =
terror $ "convertOutCArray : unexpected " <> tshow t
-- Read the array lengths for out arguments.
readOutArrayLengths :: Callable -> Map.Map Text Text -> ExcCodeGen ()
readOutArrayLengths callable nameMap = do
let lNames = nub $ map escapedArgName $
filter ((/= DirectionIn) . direction) $
arrayLengths callable
forM_ lNames $ \lname -> do
lname' <- case Map.lookup lname nameMap of
Just n -> return n
Nothing ->
badIntroError $ "Couldn't find out array length " <>
lname
genConversion lname' $ apply $ M "peek"
-- Touch DirectionIn arguments so we are sure that they exist when the
-- C function was called.
touchInArg :: Arg -> ExcCodeGen ()
touchInArg arg = when (direction arg /= DirectionOut) $ do
let name = escapedArgName arg
case elementType (argType arg) of
Just a -> do
managed <- isManaged a
when managed $ wrapMaybe arg >>= bool
(line $ "mapM_ touchManagedPtr " <> name)
(line $ "whenJust " <> name <> " (mapM_ touchManagedPtr)")
Nothing -> do
managed <- isManaged (argType arg)
when managed $ wrapMaybe arg >>= bool
(line $ "touchManagedPtr " <> name)
(line $ "whenJust " <> name <> " touchManagedPtr")
-- Find the association between closure arguments and their
-- corresponding callback.
closureToCallbackMap :: Callable -> ExcCodeGen (Map.Map Int Arg)
closureToCallbackMap callable =
-- The introspection info does not specify the closure for destroy
-- notify's associated with a callback, since it is implicitly the
-- same one as the ScopeTypeNotify callback associated with the
-- DestroyNotify.
go (filter (not . (`elem` destroyers)) $ args callable) Map.empty
where destroyers = map (args callable!!) . filter (/= -1) . map argDestroy
$ args callable
go :: [Arg] -> Map.Map Int Arg -> ExcCodeGen (Map.Map Int Arg)
go [] m = return m
go (arg:as) m =
if argScope arg == ScopeTypeInvalid
then go as m
else case argClosure arg of
(-1) -> go as m
c -> case Map.lookup c m of
Just _ -> notImplementedError $
"Closure for multiple callbacks unsupported"
<> T.pack (ppShow arg) <> "\n"
<> T.pack (ppShow callable)
Nothing -> go as $ Map.insert c arg m
-- user_data style arguments.
prepareClosures :: Callable -> Map.Map Text Text -> ExcCodeGen ()
prepareClosures callable nameMap = do
m <- closureToCallbackMap callable
let closures = filter (/= -1) . map argClosure $ args callable
forM_ closures $ \closure ->
case Map.lookup closure m of
Nothing -> badIntroError $ "Closure not found! "
<> T.pack (ppShow callable)
<> "\n" <> T.pack (ppShow m)
<> "\n" <> tshow closure
Just cb -> do
let closureName = escapedArgName $ (args callable)!!closure
n = escapedArgName cb
n' <- case Map.lookup n nameMap of
Just n -> return n
Nothing -> badIntroError $ "Cannot find closure name!! "
<> T.pack (ppShow callable) <> "\n"
<> T.pack (ppShow nameMap)
case argScope cb of
ScopeTypeInvalid -> badIntroError $ "Invalid scope! "
<> T.pack (ppShow callable)
ScopeTypeNotified -> do
line $ "let " <> closureName <> " = castFunPtrToPtr " <> n'
case argDestroy cb of
(-1) -> badIntroError $
"ScopeTypeNotified without destructor! "
<> T.pack (ppShow callable)
k -> let destroyName =
escapedArgName $ (args callable)!!k in
line $ "let " <> destroyName <> " = safeFreeFunPtrPtr"
ScopeTypeAsync ->
line $ "let " <> closureName <> " = nullPtr"
ScopeTypeCall -> line $ "let " <> closureName <> " = nullPtr"
freeCallCallbacks :: Callable -> Map.Map Text Text -> ExcCodeGen ()
freeCallCallbacks callable nameMap =
forM_ (args callable) $ \arg -> do
let name = escapedArgName arg
name' <- case Map.lookup name nameMap of
Just n -> return n
Nothing -> badIntroError $ "Could not find " <> name
<> " in " <> T.pack (ppShow callable) <> "\n"
<> T.pack (ppShow nameMap)
when (argScope arg == ScopeTypeCall) $
line $ "safeFreeFunPtr $ castFunPtrToPtr " <> name'
formatHSignature :: Callable -> ForeignSymbol -> Bool -> ExcCodeGen ()
formatHSignature callable symbol throwsGError = do
(constraints, vars) <- callableSignature callable symbol throwsGError
indent $ do
line $ "(" <> T.intercalate ", " constraints <> ") =>"
forM_ (zip ("" : repeat "-> ") vars) $ \(prefix, (t, name)) ->
line $ withComment (prefix <> t) name
-- | Name for the first argument in dynamic wrappers (the `FunPtr`).
funPtr :: Text
funPtr = "__funPtr"
-- | The Haskell signature for the given callable. It returns a tuple
-- ([constraints], [(type, argname)]).
callableSignature :: Callable -> ForeignSymbol -> Bool ->
ExcCodeGen ([Text], [(Text, Text)])
callableSignature callable symbol throwsGError = do
let (hInArgs, _) = callableHInArgs callable
(case symbol of
KnownForeignSymbol _ -> WithoutClosures
DynamicForeignSymbol _ -> WithClosures)
(argConstraints, types) <- inArgInterfaces hInArgs
let constraints = ("MonadIO m" : argConstraints)
ignoreReturn = skipRetVal callable throwsGError
outType <- hOutType callable (callableHOutArgs callable) ignoreReturn
case symbol of
KnownForeignSymbol _ -> do
let allNames = map escapedArgName hInArgs ++ ["result"]
allTypes = types ++ [tshow ("m" `con` [outType])]
return (constraints, zip allTypes allNames)
DynamicForeignSymbol w -> do
let allNames = funPtr : map escapedArgName hInArgs ++ ["result"]
allTypes = ("FunPtr " <> dynamicType w) :
types ++ [tshow ("m" `con` [outType])]
return (constraints, zip allTypes allNames)
-- | "In" arguments for the given callable on the Haskell side,
-- together with the omitted arguments.
callableHInArgs :: Callable -> ExposeClosures -> ([Arg], [Arg])
callableHInArgs callable expose =
let inArgs = filter ((/= DirectionOut) . direction) $ args callable
-- We do not expose user_data arguments,
-- destroynotify arguments, and C array length
-- arguments to Haskell code.
closures = map (args callable!!) . filter (/= -1) . map argClosure $ inArgs
destroyers = map (args callable!!) . filter (/= -1) . map argDestroy $ inArgs
omitted = case expose of
WithoutClosures -> arrayLengths callable <> closures <> destroyers
WithClosures -> arrayLengths callable
in (filter (`notElem` omitted) inArgs, omitted)
-- | "Out" arguments for the given callable on the Haskell side.
callableHOutArgs :: Callable -> [Arg]
callableHOutArgs callable =
let outArgs = filter ((/= DirectionIn) . direction) $ args callable
in filter (`notElem` (arrayLengths callable)) outArgs
-- | Convert the result of the foreign call to Haskell.
convertResult :: Name -> Callable -> Bool -> Map.Map Text Text ->
ExcCodeGen Text
convertResult n callable ignoreReturn nameMap =
if ignoreReturn || returnType callable == Nothing
then return (error "convertResult: unreachable code reached, bug!")
else do
nullableReturnType <- maybe (return False) typeIsNullable (returnType callable)
if returnMayBeNull callable && nullableReturnType
then do
line $ "maybeResult <- convertIfNonNull result $ \\result' -> do"
indent $ do
converted <- unwrappedConvertResult "result'"
line $ "return " <> converted
return "maybeResult"
else do
when nullableReturnType $
line $ "checkUnexpectedReturnNULL \"" <> lowerName n
<> "\" result"
unwrappedConvertResult "result"
where
unwrappedConvertResult rname =
case returnType callable of
-- Arrays without length information are just passed
-- along.
Just (TCArray False (-1) (-1) _) -> return rname
-- Not zero-terminated C arrays require knowledge of the
-- length, so we deal with them directly.
Just (t@(TCArray False _ _ _)) ->
convertOutCArray callable t rname nameMap
(returnTransfer callable) prime
Just t -> do
result <- convert rname $ fToH t (returnTransfer callable)
freeContainerType (returnTransfer callable) t rname undefined
return result
Nothing -> return (error "unwrappedConvertResult: bug!")
-- | Marshal a foreign out argument to Haskell, returning the name of
-- the variable containing the converted Haskell value.
convertOutArg :: Callable -> Map.Map Text Text -> Arg -> ExcCodeGen Text
convertOutArg callable nameMap arg = do
let name = escapedArgName arg
inName <- case Map.lookup name nameMap of
Just name' -> return name'
Nothing -> badIntroError $ "Parameter " <> name <> " not found!"
case argType arg of
-- Passed along as a raw pointer
TCArray False (-1) (-1) _ ->
if argCallerAllocates arg
then return inName
else genConversion inName $ apply $ M "peek"
t@(TCArray False _ _ _) -> do
aname' <- if argCallerAllocates arg
then return inName
else genConversion inName $ apply $ M "peek"
let arrayLength = if argCallerAllocates arg
then id
else prime
wrapArray a = convertOutCArray callable t a
nameMap (transfer arg) arrayLength
wrapMaybe arg >>= bool
(wrapArray aname')
(do line $ "maybe" <> ucFirst aname'
<> " <- convertIfNonNull " <> aname'
<> " $ \\" <> prime aname' <> " -> do"
indent $ do
wrapped <- wrapArray (prime aname')
line $ "return " <> wrapped
return $ "maybe" <> ucFirst aname')
t -> do
peeked <- if argCallerAllocates arg
then return inName
else genConversion inName $ apply $ M "peek"
-- If we alloc we always take control of the resulting
-- memory, otherwise we may leak.
let transfer' = if argCallerAllocates arg
then TransferEverything
else transfer arg
result <- do
let wrap ptr = convert ptr $ fToH (argType arg) transfer'
wrapMaybe arg >>= bool
(wrap peeked)
(do line $ "maybe" <> ucFirst peeked
<> " <- convertIfNonNull " <> peeked
<> " $ \\" <> prime peeked <> " -> do"
indent $ do
wrapped <- wrap (prime peeked)
line $ "return " <> wrapped
return $ "maybe" <> ucFirst peeked)
-- Free the memory associated with the out argument
freeContainerType transfer' t peeked undefined
return result
-- | Convert the list of out arguments to Haskell, returning the
-- names of the corresponding variables containing the marshaled values.
convertOutArgs :: Callable -> Map.Map Text Text -> [Arg] -> ExcCodeGen [Text]
convertOutArgs callable nameMap hOutArgs =
forM hOutArgs (convertOutArg callable nameMap)
-- | Invoke the given C function, taking care of errors.
invokeCFunction :: Callable -> ForeignSymbol -> Bool -> Bool -> [Text] ->
CodeGen ()
invokeCFunction callable symbol throwsGError ignoreReturn argNames = do
let returnBind = case returnType callable of
Nothing -> ""
_ -> if ignoreReturn
then "_ <- "
else "result <- "
maybeCatchGErrors = if throwsGError
then "propagateGError $ "
else ""
call = case symbol of
KnownForeignSymbol s -> s
DynamicForeignSymbol w -> parenthesize (dynamicWrapper w
<> " " <> funPtr)
line $ returnBind <> maybeCatchGErrors
<> call <> (T.concat . map (" " <>)) argNames
-- | Return the result of the call, possibly including out arguments.
returnResult :: Callable -> Bool -> Text -> [Text] -> CodeGen ()
returnResult callable ignoreReturn result pps =
if ignoreReturn || returnType callable == Nothing
then case pps of
[] -> line "return ()"
(pp:[]) -> line $ "return " <> pp
_ -> line $ "return (" <> T.intercalate ", " pps <> ")"
else case pps of
[] -> line $ "return " <> result
_ -> line $ "return (" <> T.intercalate ", " (result : pps) <> ")"
-- | Generate a Haskell wrapper for the given foreign function.
genHaskellWrapper :: Name -> ForeignSymbol -> Callable -> Bool ->
ExposeClosures -> ExcCodeGen Text
genHaskellWrapper n symbol callable throwsGError expose = group $ do
let name = case symbol of
KnownForeignSymbol _ -> lowerName n
DynamicForeignSymbol _ -> callbackDynamicWrapper (upperName n)
(hInArgs, omitted) = callableHInArgs callable expose
hOutArgs = callableHOutArgs callable
ignoreReturn = skipRetVal callable throwsGError
line $ name <> " ::"
formatHSignature callable symbol ignoreReturn
let argNames = case symbol of
KnownForeignSymbol _ -> map escapedArgName hInArgs
DynamicForeignSymbol _ ->
funPtr : map escapedArgName hInArgs
line $ name <> " " <> T.intercalate " " argNames <> " = liftIO $ do"
indent (genWrapperBody n symbol callable throwsGError
ignoreReturn hInArgs hOutArgs omitted)
return name
-- | Generate the body of the Haskell wrapper for the given foreign symbol.
genWrapperBody :: Name -> ForeignSymbol -> Callable -> Bool ->
Bool -> [Arg] -> [Arg] -> [Arg] ->
ExcCodeGen ()
genWrapperBody n symbol callable throwsGError
ignoreReturn hInArgs hOutArgs omitted = do
readInArrayLengths n callable hInArgs
inArgNames <- forM (args callable) $ \arg ->
prepareArgForCall omitted arg
-- Map from argument names to names passed to the C function
let nameMap = Map.fromList $ flip zip inArgNames
$ map escapedArgName $ args callable
prepareClosures callable nameMap
if throwsGError
then do
line "onException (do"
indent $ do
invokeCFunction callable symbol throwsGError
ignoreReturn inArgNames
readOutArrayLengths callable nameMap
result <- convertResult n callable ignoreReturn nameMap
pps <- convertOutArgs callable nameMap hOutArgs
freeCallCallbacks callable nameMap
forM_ (args callable) touchInArg
mapM_ line =<< freeInArgs callable nameMap
returnResult callable ignoreReturn result pps
line " ) (do"
indent $ do
freeCallCallbacks callable nameMap
actions <- freeInArgsOnError callable nameMap
case actions of
[] -> line $ "return ()"
_ -> mapM_ line actions
line " )"
else do
invokeCFunction callable symbol throwsGError
ignoreReturn inArgNames
readOutArrayLengths callable nameMap
result <- convertResult n callable ignoreReturn nameMap
pps <- convertOutArgs callable nameMap hOutArgs
freeCallCallbacks callable nameMap
forM_ (args callable) touchInArg
mapM_ line =<< freeInArgs callable nameMap
returnResult callable ignoreReturn result pps
-- | caller-allocates arguments are arguments that the caller
-- allocates, and the called function modifies. They are marked as
-- 'out' argumens in the introspection data, we sometimes treat them
-- as 'inout' arguments instead. The semantics are somewhat tricky:
-- for memory management purposes they should be treated as "in"
-- arguments, but from the point of view of the exposed API they
-- should be treated as "out" or "inout". Unfortunately we cannot
-- always just assume that they are purely "out", so in many cases the
-- generated API is somewhat suboptimal (since the initial values are
-- not important): for example for g_io_channel_read_chars the size of
-- the buffer to read is determined by the caller-allocates
-- argument. As a compromise, we assume that we can allocate anything
-- that is not a TCArray of length determined by an argument.
fixupCallerAllocates :: Callable -> Callable
fixupCallerAllocates c =
c{args = map (fixupLength . fixupDir) (args c)}
where fixupDir :: Arg -> Arg
fixupDir a = case argType a of
TCArray _ _ l _ ->
if argCallerAllocates a && l > -1
then a {direction = DirectionInout}
else a
_ -> a
lengthsMap :: Map.Map Arg Arg
lengthsMap = Map.fromList (map swap (arrayLengthsMap c))
-- Length arguments of caller-allocates arguments should be
-- treated as "in".
fixupLength :: Arg -> Arg
fixupLength a = case Map.lookup a lengthsMap of
Nothing -> a
Just array ->
if argCallerAllocates array
then a {direction = DirectionIn}
else a
-- | The foreign symbol to wrap. It is either a foreign symbol wrapped
-- in a foreign import, in which case we are given the name of the
-- Haskell wrapper, or alternatively the information about a "dynamic"
-- wrapper in scope.
data ForeignSymbol = KnownForeignSymbol Text -- ^ Haskell symbol in scope.
| DynamicForeignSymbol DynamicWrapper
-- ^ Info about the dynamic wrapper.
-- | Information about a dynamic wrapper.
data DynamicWrapper = DynamicWrapper {
dynamicWrapper :: Text -- ^ Haskell dynamic wrapper
, dynamicType :: Text -- ^ Name of the type synonym for the
-- type of the function to be wrapped.
}
-- | Some debug info for the callable.
genCallableDebugInfo :: Callable -> Bool -> CodeGen ()
genCallableDebugInfo callable throwsGError =
group $ do
line $ "-- Args : " <> (tshow $ args callable)
line $ "-- Lengths : " <> (tshow $ arrayLengths callable)
line $ "-- returnType : " <> (tshow $ returnType callable)
line $ "-- throws : " <> (tshow throwsGError)
line $ "-- Skip return : " <> (tshow $ skipReturn callable)
when (skipReturn callable && returnType callable /= Just (TBasicType TBoolean)) $
do line "-- XXX return value ignored, but it is not a boolean."
line "-- This may be a memory leak?"
-- | Generate a wrapper for a known C symbol.
genCCallableWrapper :: Name -> Text -> Callable -> Bool -> ExcCodeGen ()
genCCallableWrapper n cSymbol callable throwsGError = do
genCallableDebugInfo callable throwsGError
let callable' = fixupCallerAllocates callable
hSymbol <- mkForeignImport cSymbol callable' throwsGError
blank
line $ deprecatedPragma (lowerName n) (callableDeprecated callable)
void (genHaskellWrapper n (KnownForeignSymbol hSymbol) callable'
throwsGError WithoutClosures)
-- | For callbacks we do not need to keep track of which arguments are
-- closures.
forgetClosures :: Callable -> Callable
forgetClosures c = c {args = map forgetClosure (args c)}
where forgetClosure :: Arg -> Arg
forgetClosure arg = arg {argClosure = -1}
-- | Generate a wrapper for a dynamic C symbol (i.e. a Haskell
-- function that will invoke its first argument, which should be a
-- `FunPtr` of the appropriate type). The caller should have created a
-- type synonym with the right type for the foreign symbol.
genDynamicCallableWrapper :: Name -> Text -> Callable -> Bool ->
ExcCodeGen Text
genDynamicCallableWrapper n typeSynonym callable throwsGError = do
genCallableDebugInfo callable throwsGError
let callable' = forgetClosures (fixupCallerAllocates callable)
wrapper <- mkDynamicImport typeSynonym
blank
let dyn = DynamicWrapper { dynamicWrapper = wrapper
, dynamicType = typeSynonym }
genHaskellWrapper n (DynamicForeignSymbol dyn) callable'
throwsGError WithClosures