{-# LANGUAGE LambdaCase #-}
module Data.GI.CodeGen.Callable
( genCCallableWrapper
, genDynamicCallableWrapper
, ForeignSymbol(..)
, ExposeClosures(..)
, hOutType
, skipRetVal
, arrayLengths
, arrayLengthsMap
, callableSignature
, Signature(..)
, 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 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.Haddock (deprecatedPragma, writeHaddock,
writeDocumentation, RelativeDocPosition(..),
writeArgDocumentation, writeReturnDocumentation)
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)
data ExposeClosures = WithClosures
| WithoutClosures
hOutType :: Callable -> [Arg] -> ExcCodeGen TypeRep
hOutType callable outArgs = do
hReturnType <- case returnType callable of
Nothing -> return $ con0 "()"
Just r -> if skipRetVal callable
then return $ con0 "()"
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 (skipRetVal callable)
&& nullableReturnType
then maybeT hReturnType
else hReturnType
return $ case (outArgs, typeShow maybeHReturnType) of
([], _) -> maybeHReturnType
(_, "()") -> "(,)" `con` hOutArgTypes
_ -> "(,)" `con` (maybeHReturnType : hOutArgTypes)
mkForeignImport :: Text -> Callable -> CodeGen Text
mkForeignImport cSymbol callable = do
line first
indent $ do
mapM_ (\a -> line =<< fArgStr a) (args callable)
when (callableThrows callable) $
line $ padTo 40 "Ptr (Ptr GError) -> " <> "-- error"
line =<< last
return hSymbol
where
hSymbol = if T.any (== '_') cSymbol
then lcFirst cSymbol
else "_" <> 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 = typeShow ft' <> " -> "
return $ padTo 40 start <> "-- " <> (argCName arg)
<> " : " <> tshow (argType arg)
last = typeShow <$> io <$> case returnType callable of
Nothing -> return $ con0 "()"
Just r -> foreignType r
mkDynamicImport :: Text -> CodeGen Text
mkDynamicImport typeSynonym = do
line $ "foreign import ccall \"dynamic\" " <> dynamic <> " :: FunPtr "
<> typeSynonym <> " -> " <> typeSynonym
return dynamic
where dynamic = "__dynamic_" <> typeSynonym
wrapMaybe :: Arg -> CodeGen Bool
wrapMaybe arg = if mayBeNull arg
then typeIsNullable (argType arg)
else return False
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)
arrayLengthsMap :: Callable -> [(Arg, Arg)]
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
arrayLengths :: Callable -> [Arg]
arrayLengths callable = map snd (arrayLengthsMap callable) <>
case returnType callable of
Just (TCArray False (-1) length _) ->
if length > -1
then [(args callable)!!length]
else []
_ -> []
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
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
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)
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 <> "'.\""
skipRetVal :: Callable -> Bool
skipRetVal callable = (skipReturn callable) ||
(callableThrows callable &&
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 $
case argType arg of
TCArray False (-1) (-1) _ ->
parenthesize ("length " <> escapedArgName arg)
TCArray False (-1) length _ ->
escapedArgName $ (args callable)!!length
_ -> undefined
Nothing -> badIntroError $ "freeInArgs: do not understand " <> tshow arg
freeInArgs :: Callable -> Map.Map Text Text -> ExcCodeGen [Text]
freeInArgs = freeInArgs' freeInArg
freeInArgsOnError :: Callable -> Map.Map Text Text -> ExcCodeGen [Text]
freeInArgsOnError = freeInArgs' freeInArgOnError
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 -> if callableThrows (cbCallable c)
then return (escapedArgName arg)
else 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)
prepareInCallback :: Arg -> Callback -> CodeGen Text
prepareInCallback arg (Callback {cbCallable = 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)
wrapMaybe arg >>= bool
(do
let name' = prime name
dropped =
case drop of
Just dropper -> parenthesize (dropper <> " " <> name)
Nothing -> name
p <- if (scope == ScopeTypeAsync)
then do ft <- typeShow <$> foreignType (argType arg)
line $ ptrName <> " <- callocMem :: IO (Ptr (" <> ft <> "))"
return $ parenthesize $ "Just " <> ptrName
else return "Nothing"
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 dropped = case drop of
Just dropper ->
parenthesize (dropper <> " " <> jName)
Nothing -> jName
p <- if (scope == ScopeTypeAsync)
then do ft <- typeShow <$> foreignType (argType arg)
line $ ptrName <> " <- callocMem :: IO (Ptr (" <> ft <> "))"
return $ parenthesize $ "Just " <> ptrName
else return "Nothing"
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 <>
" :: " <> typeShow (io ft)
line $ "memcpy " <> name'' <> " " <> name' <> " " <> tshow n
return name'')
(notImplementedError "Nullable inout structs not supported")
Nothing -> do
if argCallerAllocates arg
then return name'
else do
name'' <- genConversion (prime name') $
literal $ M $ "allocMem :: " <> typeShow (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 <>
" :: " <> typeShow (io ft)
Nothing ->
notImplementedError $ ("Don't know how to allocate \""
<> argCName arg <> "\" of type "
<> tshow (argType arg))
else genConversion name $ literal $ M $ "allocMem :: " <> typeShow (io $ ptr ft)
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
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
freeContainerType transfer t aname lname''
return unpacked
convertOutCArray _ t _ _ _ _ =
terror $ "convertOutCArray : unexpected " <> tshow t
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"
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")
closureToCallbackMap :: Callable -> ExcCodeGen (Map.Map Int Arg)
closureToCallbackMap callable =
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
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 -> ExcCodeGen ()
formatHSignature callable symbol = do
sig <- callableSignature callable symbol
indent $ do
let constraints = "B.CallStack.HasCallStack" : signatureConstraints sig
line $ "(" <> T.intercalate ", " constraints <> ") =>"
forM_ (zip ("" : repeat "-> ") (signatureArgTypes sig)) $
\(prefix, (maybeArg, t)) -> do
line $ prefix <> t
case maybeArg of
Nothing -> return ()
Just arg -> writeArgDocumentation arg
let resultPrefix = if null (signatureArgTypes sig)
then ""
else "-> "
line $ resultPrefix <> signatureReturnType sig
writeReturnDocumentation (signatureCallable sig) (skipRetVal callable)
funPtr :: Text
funPtr = "__funPtr"
data Signature = Signature { signatureCallable :: Callable
, signatureConstraints :: [Text]
, signatureArgTypes :: [(Maybe Arg, Text)]
, signatureReturnType :: Text
}
callableSignature :: Callable -> ForeignSymbol -> ExcCodeGen Signature
callableSignature callable symbol = do
let (hInArgs, _) = callableHInArgs callable
(case symbol of
KnownForeignSymbol _ -> WithoutClosures
DynamicForeignSymbol _ -> WithClosures)
(argConstraints, types) <- inArgInterfaces hInArgs
let constraints = ("MonadIO m" : argConstraints)
outType <- hOutType callable (callableHOutArgs callable)
return $ Signature {
signatureCallable = callable,
signatureConstraints = constraints,
signatureReturnType = typeShow ("m" `con` [outType]),
signatureArgTypes = case symbol of
KnownForeignSymbol _ -> zip (map Just hInArgs) types
DynamicForeignSymbol w -> zip (Nothing : map Just hInArgs)
("FunPtr " <> dynamicType w : types)
}
callableHInArgs :: Callable -> ExposeClosures -> ([Arg], [Arg])
callableHInArgs callable expose =
let inArgs = filter ((/= DirectionOut) . direction) $ args callable
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)
callableHOutArgs :: Callable -> [Arg]
callableHOutArgs callable =
let outArgs = filter ((/= DirectionIn) . direction) $ args callable
in filter (`notElem` (arrayLengths callable)) outArgs
convertResult :: Name -> Callable -> Map.Map Text Text ->
ExcCodeGen Text
convertResult n callable nameMap =
if skipRetVal callable || 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
Just (t@(TCArray False (-1) (-1) _)) ->
badIntroError ("`" <> tshow t <>
"' is an array type, but contains no length information,\n"
<> "so it cannot be unpacked.")
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!")
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
t@(TCArray False (-1) (-1) _) ->
if argCallerAllocates arg
then return inName
else badIntroError ("`" <> tshow t <>
"' is an array type, but contains no length information,\n"
<> "so it cannot be unpacked.")
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"
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)
freeContainerType transfer' t peeked undefined
return result
convertOutArgs :: Callable -> Map.Map Text Text -> [Arg] -> ExcCodeGen [Text]
convertOutArgs callable nameMap hOutArgs =
forM hOutArgs (convertOutArg callable nameMap)
invokeCFunction :: Callable -> ForeignSymbol -> [Text] -> CodeGen ()
invokeCFunction callable symbol argNames = do
let returnBind = case returnType callable of
Nothing -> ""
_ -> if skipRetVal callable
then "_ <- "
else "result <- "
maybeCatchGErrors = if callableThrows callable
then "propagateGError $ "
else ""
call = case symbol of
KnownForeignSymbol s -> s
DynamicForeignSymbol w -> parenthesize (dynamicWrapper w
<> " " <> funPtr)
line $ returnBind <> maybeCatchGErrors
<> call <> (T.concat . map (" " <>)) argNames
returnResult :: Callable -> Text -> [Text] -> CodeGen ()
returnResult callable result pps =
if skipRetVal callable || 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) <> ")"
genHaskellWrapper :: Name -> ForeignSymbol -> Callable ->
ExposeClosures -> ExcCodeGen Text
genHaskellWrapper n symbol callable expose = group $ do
let name = case symbol of
KnownForeignSymbol _ -> lowerName n
DynamicForeignSymbol _ -> callbackDynamicWrapper (upperName n)
(hInArgs, omitted) = callableHInArgs callable expose
hOutArgs = callableHOutArgs callable
line $ name <> " ::"
formatHSignature callable symbol
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 hInArgs hOutArgs omitted)
return name
genWrapperBody :: Name -> ForeignSymbol -> Callable ->
[Arg] -> [Arg] -> [Arg] ->
ExcCodeGen ()
genWrapperBody n symbol callable hInArgs hOutArgs omitted = do
readInArrayLengths n callable hInArgs
inArgNames <- forM (args callable) $ \arg ->
prepareArgForCall omitted arg
let nameMap = Map.fromList $ flip zip inArgNames
$ map escapedArgName $ args callable
prepareClosures callable nameMap
if callableThrows callable
then do
line "onException (do"
indent $ do
invokeCFunction callable symbol inArgNames
readOutArrayLengths callable nameMap
result <- convertResult n callable nameMap
pps <- convertOutArgs callable nameMap hOutArgs
freeCallCallbacks callable nameMap
forM_ (args callable) touchInArg
mapM_ line =<< freeInArgs callable nameMap
returnResult callable 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 inArgNames
readOutArrayLengths callable nameMap
result <- convertResult n callable nameMap
pps <- convertOutArgs callable nameMap hOutArgs
freeCallCallbacks callable nameMap
forM_ (args callable) touchInArg
mapM_ line =<< freeInArgs callable nameMap
returnResult callable result pps
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))
fixupLength :: Arg -> Arg
fixupLength a = case Map.lookup a lengthsMap of
Nothing -> a
Just array ->
if argCallerAllocates array
then a {direction = DirectionIn}
else a
data ForeignSymbol = KnownForeignSymbol Text
| DynamicForeignSymbol DynamicWrapper
data DynamicWrapper = DynamicWrapper {
dynamicWrapper :: Text
, dynamicType :: Text
}
genCallableDebugInfo :: Callable -> CodeGen ()
genCallableDebugInfo callable =
group $ do
line $ "-- Args : " <> (tshow $ args callable)
line $ "-- Lengths : " <> (tshow $ arrayLengths callable)
line $ "-- returnType : " <> (tshow $ returnType callable)
line $ "-- throws : " <> (tshow $ callableThrows callable)
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?"
genCCallableWrapper :: Name -> Text -> Callable -> ExcCodeGen ()
genCCallableWrapper n cSymbol callable = do
genCallableDebugInfo callable
let callable' = fixupCallerAllocates callable
hSymbol <- mkForeignImport cSymbol callable'
blank
deprecatedPragma (lowerName n) (callableDeprecated callable)
writeDocumentation DocBeforeSymbol (callableDocumentation callable)
void (genHaskellWrapper n (KnownForeignSymbol hSymbol) callable'
WithoutClosures)
forgetClosures :: Callable -> Callable
forgetClosures c = c {args = map forgetClosure (args c)}
where forgetClosure :: Arg -> Arg
forgetClosure arg = arg {argClosure = -1}
genDynamicCallableWrapper :: Name -> Text -> Callable ->
ExcCodeGen Text
genDynamicCallableWrapper n typeSynonym callable = do
genCallableDebugInfo callable
let callable' = forgetClosures (fixupCallerAllocates callable)
wrapper <- mkDynamicImport typeSynonym
blank
writeHaddock DocBeforeSymbol dynamicDoc
let dyn = DynamicWrapper { dynamicWrapper = wrapper
, dynamicType = typeSynonym }
genHaskellWrapper n (DynamicForeignSymbol dyn) callable' WithClosures
where
dynamicDoc :: Text
dynamicDoc = "Given a pointer to a foreign C function, wrap it into a function callable from Haskell."