-- This file is part of Hoppy. -- -- Copyright 2015-2021 Bryan Gardiner -- -- This program is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by -- the Free Software Foundation, either version 3 of the License, or -- (at your option) any later version. -- -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU Affero General Public License for more details. -- -- You should have received a copy of the GNU Affero General Public License -- along with this program. If not, see . -- | Interface for defining bindings to C++ classes. module Foreign.Hoppy.Generator.Spec.Class ( -- * Data type Class, -- * Construction makeClass, -- * Properties -- ** Common classExtName, classIdentifier, classReqs, classAddendum, -- ** Class hierarchy classSuperclasses, classIsMonomorphicSuperclass, classSetMonomorphicSuperclass, classIsSubclassOfMonomorphic, classSetSubclassOfMonomorphic, -- ** Entities classEntities, classAddEntities, classVariables, classCtors, classMethods, classEntityPrefix, classSetEntityPrefix, classDtorIsPublic, classSetDtorPrivate, classConversion, classIsException, classMakeException, -- * Entity types ClassEntity (..), IsClassEntity (..), classEntityExtName, classEntityExtNames, classEntityForeignName, classEntityForeignName', -- ** Class variables ClassVariable, -- *** Construction makeClassVariable, makeClassVariable_, mkClassVariable, mkClassVariable_, mkStaticClassVariable, mkStaticClassVariable_, -- ** Constructors Ctor, -- *** Construction makeCtor, makeCtor_, mkCtor, mkCtor_, -- *** Properties ctorExtName, ctorParams, ctorExceptionHandlers, -- ** Methods (member functions) Method, MethodApplicability (..), Staticness (..), MethodImpl (..), -- *** Construction makeMethod, makeMethod_, makeFnMethod, makeFnMethod_, mkMethod, mkMethod_, mkMethod', mkMethod'_, mkConstMethod, mkConstMethod_, mkConstMethod', mkConstMethod'_, mkStaticMethod, mkStaticMethod_, mkStaticMethod', mkStaticMethod'_, -- *** Properties methodExtName, methodImpl, methodApplicability, methodConst, methodStatic, methodPurity, methodParams, methodReturn, methodExceptionHandlers, -- ** Class properties (getter/setter pairs) Prop, -- ** Construction mkProp, mkProp_, mkStaticProp, mkStaticProp_, mkBoolIsProp, mkBoolIsProp_, mkBoolHasProp, mkBoolHasProp_, -- * Conversions ClassConversion (..), classConversionNone, classModifyConversion, classSetConversion, ClassHaskellConversion (..), classSetHaskellConversion, -- * Haskell generator -- ** Names toHsValueClassName, toHsValueClassName', toHsWithValuePtrName, toHsWithValuePtrName', toHsPtrClassName, toHsPtrClassName', toHsCastMethodName, toHsCastMethodName', toHsDownCastClassName, toHsDownCastClassName', toHsDownCastMethodName, toHsDownCastMethodName', toHsCastPrimitiveName, toHsCastPrimitiveName', toHsConstCastFnName, toHsConstCastFnName', toHsDataTypeName, toHsDataTypeName', toHsDataCtorName, toHsDataCtorName', toHsClassDeleteFnName', toHsClassDeleteFnPtrName', toHsCtorName, toHsCtorName', toHsMethodName, toHsMethodName', toHsClassEntityName, toHsClassEntityName', -- * Internal classFindCopyCtor, sayCppExportVar, sayHsExportVar, ) where import Control.Monad (forM, forM_, unless, when) import Control.Monad.Except (throwError) import Data.Char (toUpper) import Data.Function (on) import Data.Maybe (fromMaybe, isJust, isNothing, mapMaybe) import Data.List (intersperse) import Foreign.Hoppy.Generator.Common (fromMaybeM, lowerFirst) import {-# SOURCE #-} qualified Foreign.Hoppy.Generator.Language.Cpp as LC import {-# SOURCE #-} qualified Foreign.Hoppy.Generator.Language.Haskell as LH import Foreign.Hoppy.Generator.Spec.Base import qualified Foreign.Hoppy.Generator.Spec.Function as Function import Foreign.Hoppy.Generator.Types (boolT, constT, fnT, objT, ptrT, refT, voidT) import GHC.Stack (HasCallStack) import Language.Haskell.Syntax ( HsName (HsIdent), HsQName (UnQual), HsType (HsTyCon, HsTyFun, HsTyVar), ) -- | A C++ class declaration. See 'IsClassEntity' for more information about the -- interaction between a class's names and the names of entities within the -- class. -- -- Use this data type's 'HasReqs' instance to make the class accessible. You do -- not need to add requirements for methods' parameter or return types. data Class = Class { classExtName :: ExtName -- ^ The class's external name. , classIdentifier :: Identifier -- ^ The identifier used to refer to the class. , classSuperclasses :: [Class] -- ^ The class's public superclasses. , classEntities :: [ClassEntity] -- ^ The class's entities. , classDtorIsPublic :: Bool -- ^ The class's methods. , classConversion :: ClassConversion -- ^ Behaviour for converting objects to and from foriegn values. , classReqs :: Reqs -- ^ Requirements for bindings to access this class. , classAddendum :: Addendum -- ^ The class's addendum. , classIsMonomorphicSuperclass :: Bool -- ^ This is true for classes passed through -- 'classSetMonomorphicSuperclass'. , classIsSubclassOfMonomorphic :: Bool -- ^ This is true for classes passed through -- 'classSetSubclassOfMonomorphic'. , classIsException :: Bool -- ^ Whether to support using the class as a C++ exception. , classEntityPrefix :: String -- ^ The prefix applied to the external names of entities (methods, etc.) -- within this class when determining the names of foreign languages' -- corresponding bindings. This defaults to the external name of the class, -- plus an underscore. Changing this allows you to potentially have -- entities with the same foreign name in separate modules. This may be the -- empty string, in which case the foreign name will simply be the external -- name of the entity. -- -- This does __not__ affect the things' external names themselves; external -- names must still be unique in an interface. For instance, a method with -- external name @bar@ in a class with external name @Flab@ and prefix -- @Flob_@ will use the effective external name @Flab_bar@, but the -- generated name in say Haskell would be @Flob_bar@. -- -- See 'IsClassEntity' and 'classSetEntityPrefix'. } instance Eq Class where (==) = (==) `on` classExtName instance Ord Class where compare = compare `on` classExtName instance Show Class where show cls = concat [""] instance Exportable Class where sayExportCpp = sayCppExport sayExportHaskell = sayHsExport getExportExceptionClass cls = if classIsException cls then Just cls else Nothing instance HasExtNames Class where getPrimaryExtName = classExtName getNestedExtNames cls = concatMap (classEntityExtNames cls) $ classEntities cls instance HasReqs Class where getReqs = classReqs setReqs reqs cls = cls { classReqs = reqs } instance HasAddendum Class where getAddendum = classAddendum setAddendum addendum cls = cls { classAddendum = addendum } -- | Creates a binding for a C++ class and its contents. makeClass :: Identifier -> Maybe ExtName -- ^ An optional external name; will be automatically derived from the -- identifier if absent by dropping leading namespaces, and taking the -- last component (sans template arguments). -> [Class] -- ^ Superclasses. -> [ClassEntity] -> Class makeClass identifier maybeExtName supers entities = let extName = extNameOrIdentifier identifier maybeExtName in Class { classIdentifier = identifier , classExtName = extName , classSuperclasses = supers , classEntities = entities , classDtorIsPublic = True , classConversion = classConversionNone , classReqs = mempty , classAddendum = mempty , classIsMonomorphicSuperclass = False , classIsSubclassOfMonomorphic = False , classIsException = False , classEntityPrefix = fromExtName extName ++ "_" } -- | Sets the prefix applied to foreign languages' entities generated from -- methods, etc. within the class. -- -- See 'IsClassEntity' and 'classEntityPrefix'. classSetEntityPrefix :: String -> Class -> Class classSetEntityPrefix prefix cls = cls { classEntityPrefix = prefix } -- | Adds constructors to a class. classAddEntities :: [ClassEntity] -> Class -> Class classAddEntities ents cls = if null ents then cls else cls { classEntities = classEntities cls ++ ents } -- | Returns all of the class's variables. classVariables :: Class -> [ClassVariable] classVariables = mapMaybe pickVar . classEntities where pickVar ent = case ent of CEVar v -> Just v CECtor _ -> Nothing CEMethod _ -> Nothing CEProp _ -> Nothing -- | Returns all of the class's constructors. classCtors :: Class -> [Ctor] classCtors = mapMaybe pickCtor . classEntities where pickCtor ent = case ent of CEVar _ -> Nothing CECtor ctor -> Just ctor CEMethod _ -> Nothing CEProp _ -> Nothing -- | Returns all of the class's methods, including methods generated from -- 'Prop's. classMethods :: Class -> [Method] classMethods = concatMap pickMethods . classEntities where pickMethods ent = case ent of CEVar _ -> [] CECtor _ -> [] CEMethod m -> [m] CEProp (Prop ms) -> ms -- | Marks a class's destructor as private, so that a binding for it won't be -- generated. classSetDtorPrivate :: Class -> Class classSetDtorPrivate cls = cls { classDtorIsPublic = False } -- | Explicitly marks a class as being monomorphic (i.e. not having any -- virtual methods or destructors). By default, Hoppy assumes that a class that -- is derived is also polymorphic, but it can happen that this is not the case. -- Downcasting with @dynamic_cast@ from such classes is not available. See also -- 'classSetSubclassOfMonomorphic'. classSetMonomorphicSuperclass :: Class -> Class classSetMonomorphicSuperclass cls = cls { classIsMonomorphicSuperclass = True } -- | Marks a class as being derived from some monomorphic superclass. This -- prevents any downcasting to this class. Generally it is better to use -- 'classSetMonomorphicSuperclass' on the specific superclasses that are -- monomorphic, but in cases where this is not possible, this function can be -- applied to the subclass instead. classSetSubclassOfMonomorphic :: Class -> Class classSetSubclassOfMonomorphic cls = cls { classIsSubclassOfMonomorphic = True } -- | Marks a class as being used as an exception. This makes the class -- throwable and catchable. classMakeException :: Class -> Class classMakeException cls = case classIsException cls of False -> cls { classIsException = True } True -> cls -- | Separately from passing object handles between C++ and foreign languages, -- objects can also be made to implicitly convert to native values in foreign -- languages. A single such type may be associated with any C++ class for each -- foreign language. The foreign type and the conversion process in each -- direction are specified using this object. Converting a C++ object to a -- foreign value is also called decoding, and vice versa is called encoding. A -- class may be convertible in one direction and not the other. -- -- To use these implicit conversions, instead of specifying an object handle -- type such as -- @'Foreign.Hoppy.Generator.Types.ptrT' . 'Foreign.Hoppy.Generator.Types.objT'@ -- or -- @'Foreign.Hoppy.Generator.Types.refT' . 'Foreign.Hoppy.Generator.Types.objT'@, -- use 'Foreign.Hoppy.Generator.Types.objT' directly. -- -- The subfields in this object specify how to do conversions between C++ and -- foreign languages. data ClassConversion = ClassConversion { classHaskellConversion :: ClassHaskellConversion -- ^ Conversions to and from Haskell. -- NOTE! When adding new languages here, add the language to -- 'classSetConversionToHeap', and 'classSetConversionToGc' as well if the -- language supports garbage collection. } -- | Conversion behaviour for a class that is not convertible. classConversionNone :: ClassConversion classConversionNone = ClassConversion classHaskellConversionNone -- | Modifies a class's 'ClassConversion' structure with a given function. classModifyConversion :: HasCallStack => (ClassConversion -> ClassConversion) -> Class -> Class classModifyConversion f cls = let cls' = cls { classConversion = f $ classConversion cls } conv = classConversion cls' haskellConv = classHaskellConversion conv in case undefined of _ | (isJust (classHaskellConversionToCppFn haskellConv) || isJust (classHaskellConversionFromCppFn haskellConv)) && isNothing (classHaskellConversionType haskellConv) -> error $ "classModifyConversion: " ++ show cls' ++ " was given a Haskell-to-C++ or C++-to-Haskell conversion function" ++ " but no Haskell type. Please provide a classHaskellConversionType." _ -> cls' -- | Replaces a class's 'ClassConversion' structure. classSetConversion :: ClassConversion -> Class -> Class classSetConversion c = classModifyConversion $ const c -- | Controls how conversions between C++ objects and Haskell values happen in -- Haskell bindings. data ClassHaskellConversion = ClassHaskellConversion { classHaskellConversionType :: Maybe (LH.Generator HsType) -- ^ Produces the Haskell type that represents a value of the corresponding -- C++ class. This generator may add imports, but must not output code or -- add exports. , classHaskellConversionToCppFn :: Maybe (LH.Generator ()) -- ^ Produces a Haskell expression that evaluates to a function that takes -- an value of the type that 'classHaskellConversionType' generates, and -- returns a non-const handle for a new C++ object in IO. The generator -- must output code and may add imports, but must not add exports. -- -- If this field is present, then 'classHaskellConversionType' must also be -- present. , classHaskellConversionFromCppFn :: Maybe (LH.Generator ()) -- ^ Produces a Haskell expression that evaluates to a function that takes a -- const handle for a C++ object, and returns a value of the type that -- 'classHaskellConversionType' generates, in IO. It should not delete the -- handle. The generator must output code and may add imports, but must not -- add exports. -- -- If this field is present, then 'classHaskellConversionType' must also be -- present. } -- | Conversion behaviour for a class that is not convertible to or from -- Haskell. classHaskellConversionNone :: ClassHaskellConversion classHaskellConversionNone = ClassHaskellConversion { classHaskellConversionType = Nothing , classHaskellConversionToCppFn = Nothing , classHaskellConversionFromCppFn = Nothing } -- | Replaces a class's 'classHaskellConversion' with a given value. classSetHaskellConversion :: ClassHaskellConversion -> Class -> Class classSetHaskellConversion conv = classModifyConversion $ \c -> c { classHaskellConversion = conv } -- | Things that live inside of a class, and have the class's external name -- prepended to their own in generated code. With an external name of @\"bar\"@ -- and a class with external name @\"foo\"@, the resulting name will be -- @\"foo_bar\"@. -- -- See 'classEntityPrefix' and 'classSetEntityPrefix'. class IsClassEntity a where -- | Extracts the external name of the object, without the class name added. classEntityExtNameSuffix :: a -> ExtName -- | Computes the external name to use in generated code, containing both the -- class's and object's external names. This is the concatenation of the -- class's and entity's external names, separated by an underscore. classEntityExtName :: IsClassEntity a => Class -> a -> ExtName classEntityExtName cls x = toExtName $ fromExtName (classExtName cls) ++ "_" ++ fromExtName (classEntityExtNameSuffix x) -- | Computes the name under which a class entity is to be exposed in foreign -- languages. This is the concatenation of a class's entity prefix, and the -- external name of the entity. classEntityForeignName :: IsClassEntity a => Class -> a -> ExtName classEntityForeignName cls x = classEntityForeignName' cls $ classEntityExtNameSuffix x -- | Computes the name under which a class entity is to be exposed in foreign -- languages, given a class and an entity's external name. The result is the -- concatenation of a class's entity prefix, and the external name of the -- entity. classEntityForeignName' :: Class -> ExtName -> ExtName classEntityForeignName' cls extName = toExtName $ classEntityPrefix cls ++ fromExtName extName -- | A C++ entity that belongs to a class. data ClassEntity = CEVar ClassVariable | CECtor Ctor | CEMethod Method | CEProp Prop -- | Returns all of the names in a 'ClassEntity' within the corresponding -- 'Class'. classEntityExtNames :: Class -> ClassEntity -> [ExtName] classEntityExtNames cls ent = case ent of CEVar v -> [classEntityExtName cls v] CECtor ctor -> [classEntityExtName cls ctor] CEMethod m -> [classEntityExtName cls m] CEProp (Prop methods) -> map (classEntityExtName cls) methods -- | A C++ member variable. data ClassVariable = ClassVariable { classVarExtName :: ExtName -- ^ The variable's external name. , classVarCName :: String -- ^ The variable's C++ name. , classVarType :: Type -- ^ The variable's type. This may be -- 'Foreign.Hoppy.Generator.Types.constT' to indicate that the variable is -- read-only. , classVarStatic :: Staticness -- ^ Whether the variable is static (i.e. whether it exists once in the -- class itself and not in each instance). , classVarGettable :: Bool -- ^ Whether the variable should have an accompanying getter. Note this -- exists only for disabling getters on callback variables - as there is -- currently no functionality to pass callbacks out of c++ } instance Show ClassVariable where show v = concat [""] instance IsClassEntity ClassVariable where classEntityExtNameSuffix = classVarExtName -- | Creates a 'ClassVariable' with full generality and manual name specification. -- -- The result is wrapped in a 'CEVar'. For an unwrapped value, use -- 'makeClassVariable_'. makeClassVariable :: String -> Maybe ExtName -> Type -> Staticness -> Bool -> ClassEntity makeClassVariable cName maybeExtName tp static gettable = CEVar $ makeClassVariable_ cName maybeExtName tp static gettable -- | The unwrapped version of 'makeClassVariable'. makeClassVariable_ :: String -> Maybe ExtName -> Type -> Staticness -> Bool -> ClassVariable makeClassVariable_ cName maybeExtName = ClassVariable (extNameOrString cName maybeExtName) cName -- | Creates a 'ClassVariable' for a nonstatic class variable for -- @class::varName@ whose external name is @class_varName@. -- -- The result is wrapped in a 'CEVar'. For an unwrapped value, use -- 'mkClassVariable_'. mkClassVariable :: String -> Type -> ClassEntity mkClassVariable = (CEVar .) . mkClassVariable_ -- | The unwrapped version of 'mkClassVariable'. mkClassVariable_ :: String -> Type -> ClassVariable mkClassVariable_ cName t = makeClassVariable_ cName Nothing t Nonstatic True -- | Same as 'mkClassVariable', but returns a static variable instead. -- -- The result is wrapped in a 'CEVar'. For an unwrapped value, use -- 'mkStaticClassVariable_'. mkStaticClassVariable :: String -> Type -> ClassEntity mkStaticClassVariable = (CEVar .) . mkStaticClassVariable_ -- | The unwrapped version of 'mkStaticClassVariable'. mkStaticClassVariable_ :: String -> Type -> ClassVariable mkStaticClassVariable_ cName t = makeClassVariable_ cName Nothing t Static True -- | Returns the external name of the getter function for the class variable. classVarGetterExtName :: Class -> ClassVariable -> ExtName classVarGetterExtName cls v = toExtName $ fromExtName (classEntityExtName cls v) ++ "_get" -- | Returns the foreign name of the getter function for the class variable. classVarGetterForeignName :: Class -> ClassVariable -> ExtName classVarGetterForeignName cls v = toExtName $ fromExtName (classEntityForeignName cls v) ++ "_get" -- | Returns the external name of the setter function for the class variable. classVarSetterExtName :: Class -> ClassVariable -> ExtName classVarSetterExtName cls v = toExtName $ fromExtName (classEntityExtName cls v) ++ "_set" -- | Returns the foreign name of the setter function for the class variable. classVarSetterForeignName :: Class -> ClassVariable -> ExtName classVarSetterForeignName cls v = toExtName $ fromExtName (classEntityForeignName cls v) ++ "_set" -- | A C++ class constructor declaration. data Ctor = Ctor { ctorExtName :: ExtName -- ^ The constructor's external name. , ctorParams :: [Parameter] -- ^ The constructor's parameters. , ctorExceptionHandlers :: ExceptionHandlers -- ^ Exceptions that the constructor may throw. } instance Show Ctor where show ctor = concat [""] instance HandlesExceptions Ctor where getExceptionHandlers = ctorExceptionHandlers modifyExceptionHandlers f ctor = ctor { ctorExceptionHandlers = f $ ctorExceptionHandlers ctor } instance IsClassEntity Ctor where classEntityExtNameSuffix = ctorExtName -- | Creates a 'Ctor' with full generality. -- -- The result is wrapped in a 'CECtor'. For an unwrapped value, use -- 'makeCtor_'. makeCtor :: IsParameter p => ExtName -> [p] -> ClassEntity makeCtor = (CECtor .) . makeCtor_ -- | The unwrapped version of 'makeCtor'. makeCtor_ :: IsParameter p => ExtName -> [p] -> Ctor makeCtor_ extName params = Ctor extName (map toParameter params) mempty -- | @mkCtor name@ creates a 'Ctor' whose external name is @className_name@. -- -- The result is wrapped in a 'CECtor'. For an unwrapped value, use -- 'makeCtor_'. mkCtor :: IsParameter p => String -> [p] -> ClassEntity mkCtor = (CECtor .) . mkCtor_ -- | The unwrapped version of 'mkCtor'. mkCtor_ :: IsParameter p => String -> [p] -> Ctor mkCtor_ extName params = makeCtor_ (toExtName extName) (map toParameter params) -- | Searches a class for a copy constructor, returning it if found. classFindCopyCtor :: Class -> Maybe Ctor classFindCopyCtor cls = case mapMaybe check $ classCtors cls of [ctor] -> Just ctor _ -> Nothing where check ctor = let paramTypes = map (stripConst . normalizeType . parameterType) $ ctorParams ctor in if paramTypes == [Internal_TObj cls] || paramTypes == [Internal_TRef $ Internal_TConst $ Internal_TObj cls] then Just ctor else Nothing -- | A C++ class method declaration. -- -- Any operator function that can be written as a method may have its binding be -- written either as part of the associated class or as a separate entity, -- independently of how the function is declared in C++. data Method = Method { methodImpl :: MethodImpl -- ^ The underlying code that the binding calls. , methodExtName :: ExtName -- ^ The method's external name. , methodApplicability :: MethodApplicability -- ^ How the method is associated to its class. , methodPurity :: Purity -- ^ Whether the method is pure. , methodParams :: [Parameter] -- ^ The method's parameters. , methodReturn :: Type -- ^ The method's return type. , methodExceptionHandlers :: ExceptionHandlers -- ^ Exceptions that the method might throw. } instance Show Method where show method = concat [" show name FnMethod name -> show name, " ", show (methodApplicability method), " ", show (methodPurity method), " ", show (methodParams method), " ", show (methodReturn method), ">"] instance HandlesExceptions Method where getExceptionHandlers = methodExceptionHandlers modifyExceptionHandlers f method = method { methodExceptionHandlers = f $ methodExceptionHandlers method } instance IsClassEntity Method where classEntityExtNameSuffix = methodExtName -- | The C++ code to which a 'Method' is bound. data MethodImpl = RealMethod (FnName String) -- ^ The 'Method' is bound to an actual class method. | FnMethod (FnName Identifier) -- ^ The 'Method' is bound to a wrapper function. When wrapping a method -- with another function, this is preferrable to just using a -- 'Foreign.Hoppy.Generator.Spec.Function.Function' binding because a method -- will still appear to be part of the class in foreign bindings. deriving (Eq, Show) -- | How a method is associated to its class. A method may be static, const, or -- neither (a regular method). data MethodApplicability = MNormal | MStatic | MConst deriving (Bounded, Enum, Eq, Show) -- | Whether or not a method is static. data Staticness = Nonstatic | Static deriving (Bounded, Enum, Eq, Show) -- | Returns the constness of a method, based on its 'methodApplicability'. methodConst :: Method -> Constness methodConst method = case methodApplicability method of MConst -> Const _ -> Nonconst -- | Returns the staticness of a method, based on its 'methodApplicability'. methodStatic :: Method -> Staticness methodStatic method = case methodApplicability method of MStatic -> Static _ -> Nonstatic -- | Creates a 'Method' with full generality and manual name specification. -- -- The result is wrapped in a 'CEMethod'. For an unwrapped value, use -- 'makeMethod_'. makeMethod :: (IsFnName String name, IsParameter p) => name -- ^ The C++ name of the method. -> ExtName -- ^ The external name of the method. -> MethodApplicability -> Purity -> [p] -- ^ Parameter types. -> Type -- ^ Return type. -> ClassEntity makeMethod = (((((CEMethod .) .) .) .) .) . makeMethod_ -- | The unwrapped version of 'makeMethod'. makeMethod_ :: (IsFnName String name, IsParameter p) => name -> ExtName -> MethodApplicability -> Purity -> [p] -> Type -> Method makeMethod_ cName extName appl purity paramTypes retType = Method (RealMethod $ toFnName cName) extName appl purity (toParameters paramTypes) retType mempty -- | Creates a 'Method' that is in fact backed by a C++ non-member function (a -- la 'Foreign.Hoppy.Generator.Spec.Function.makeFn'), but appears to be a -- regular method. This is useful for wrapping a method on the C++ side when -- its arguments aren't right for binding directly. -- -- A @this@ pointer parameter is __not__ automatically added to the parameter -- list for non-static methods created with @makeFnMethod@. -- -- The result is wrapped in a 'CEMethod'. For an unwrapped value, use -- 'makeFnMethod_'. makeFnMethod :: (IsFnName Identifier name, IsParameter p) => name -> String -> MethodApplicability -> Purity -> [p] -> Type -> ClassEntity makeFnMethod = (((((CEMethod .) .) .) .) .) . makeFnMethod_ -- | The unwrapped version of 'makeFnMethod'. makeFnMethod_ :: (IsFnName Identifier name, IsParameter p) => name -> String -> MethodApplicability -> Purity -> [p] -> Type -> Method makeFnMethod_ cName foreignName appl purity paramTypes retType = Method (FnMethod $ toFnName cName) (toExtName foreignName) appl purity (toParameters paramTypes) retType mempty -- | This function is internal. -- -- Creates a method similar to 'makeMethod', but with automatic naming. The -- method's external name will be @className ++ \"_\" ++ cppMethodName@. If the -- method name is a 'FnOp' then the 'operatorPreferredExtName' will be appeneded -- to the class name. -- -- For creating multiple bindings to a method, see @makeMethod''@. makeMethod' :: (IsFnName String name, IsParameter p) => name -- ^ The C++ name of the method. -> MethodApplicability -> Purity -> [p] -- ^ Parameter types. -> Type -- ^ Return type. -> Method makeMethod' name = makeMethod''' (toFnName name) Nothing -- | This function is internal. -- -- Creates a method similar to @makeMethod'@, but with an custom string that -- will be appended to the class name to form the method's external name. This -- is useful for making multiple bindings to a method, e.g. for overloading and -- optional arguments. makeMethod'' :: (IsFnName String name, IsParameter p) => name -- ^ The C++ name of the method. -> String -- ^ A foreign name for the method. -> MethodApplicability -> Purity -> [p] -- ^ Parameter types. -> Type -- ^ Return type. -> Method makeMethod'' name foreignName = makeMethod''' (toFnName name) $ Just foreignName -- | The implementation of @makeMethod'@ and @makeMethod''@. makeMethod''' :: (HasCallStack, IsParameter p) => FnName String -- ^ The C++ name of the method. -> Maybe String -- ^ A foreign name for the method. -> MethodApplicability -> Purity -> [p] -- ^ Parameter types. -> Type -- ^ Return type. -> Method makeMethod''' (FnName "") maybeForeignName _ _ paramTypes retType = error $ concat ["makeMethod''': Given an empty method name with foreign name ", show maybeForeignName, ", parameter types ", show paramTypes, ", and return type ", show retType, "."] makeMethod''' name (Just "") _ _ paramTypes retType = error $ concat ["makeMethod''': Given an empty foreign name with method ", show name, ", parameter types ", show paramTypes, ", and return type ", show retType, "."] makeMethod''' name maybeForeignName appl purity paramTypes retType = let extName = flip fromMaybe (toExtName <$> maybeForeignName) $ case name of FnName s -> toExtName s FnOp op -> operatorPreferredExtName op in makeMethod_ name extName appl purity (toParameters paramTypes) retType -- | Creates a nonconst, nonstatic 'Method' for @class::methodName@ and whose -- external name is @class_methodName@. If the name is an operator, then the -- 'operatorPreferredExtName' will be used in the external name. -- -- For creating multiple bindings to a method, see 'mkMethod''. -- -- The result is wrapped in a 'CEMethod'. For an unwrapped value, use -- 'mkMethod_'. mkMethod :: (IsFnName String name, IsParameter p) => name -- ^ The C++ name of the method. -> [p] -- ^ Parameter types. -> Type -- ^ Return type. -> ClassEntity mkMethod = ((CEMethod .) .) . mkMethod_ -- | The unwrapped version of 'mkMethod'. mkMethod_ :: (IsFnName String name, IsParameter p) => name -> [p] -> Type -> Method mkMethod_ name = makeMethod' name MNormal Nonpure -- | Creates a nonconst, nonstatic 'Method' for method @class::methodName@ and -- whose external name is @class_methodName@. This enables multiple 'Method's -- with different foreign names (and hence different external names) to bind to -- the same method, e.g. to make use of optional arguments or overloading. See -- 'mkMethod' for a simpler form. -- -- The result is wrapped in a 'CEMethod'. For an unwrapped value, use -- 'mkMethod'_'. mkMethod' :: (IsFnName String name, IsParameter p) => name -- ^ The C++ name of the method. -> String -- ^ A foreign name for the method. -> [p] -- ^ Parameter types. -> Type -- ^ Return type. -> ClassEntity mkMethod' = (((CEMethod .) .) .) . mkMethod'_ -- | The unwrapped version of 'mkMethod''. mkMethod'_ :: (IsFnName String name, IsParameter p) => name -> String -> [p] -> Type -> Method mkMethod'_ cName foreignName = makeMethod'' cName foreignName MNormal Nonpure -- | Same as 'mkMethod', but returns an 'MConst' method. -- -- The result is wrapped in a 'CEMethod'. For an unwrapped value, use -- 'mkConstMethod_'. mkConstMethod :: (IsFnName String name, IsParameter p) => name -> [p] -> Type -> ClassEntity mkConstMethod = ((CEMethod .) .) . mkConstMethod_ -- | The unwrapped version of 'mkConstMethod'. mkConstMethod_ :: (IsFnName String name, IsParameter p) => name -> [p] -> Type -> Method mkConstMethod_ name = makeMethod' name MConst Nonpure -- | Same as 'mkMethod'', but returns an 'MConst' method. -- -- The result is wrapped in a 'CEMethod'. For an unwrapped value, use -- 'mkConstMethod'_'. mkConstMethod' :: (IsFnName String name, IsParameter p) => name -> String -> [p] -> Type -> ClassEntity mkConstMethod' = (((CEMethod .) .) .) . mkConstMethod'_ -- | The unwrapped version of 'mkConstMethod''. mkConstMethod'_ :: (IsFnName String name, IsParameter p) => name -> String -> [p] -> Type -> Method mkConstMethod'_ cName foreignName = makeMethod'' cName foreignName MConst Nonpure -- | Same as 'mkMethod', but returns an 'MStatic' method. -- -- The result is wrapped in a 'CEMethod'. For an unwrapped value, use -- 'mkStaticMethod_'. mkStaticMethod :: (IsFnName String name, IsParameter p) => name -> [p] -> Type -> ClassEntity mkStaticMethod = ((CEMethod .) .) . mkStaticMethod_ -- | The unwrapped version of 'mkStaticMethod'. mkStaticMethod_ :: (IsFnName String name, IsParameter p) => name -> [p] -> Type -> Method mkStaticMethod_ name = makeMethod' name MStatic Nonpure -- | Same as 'mkMethod'', but returns an 'MStatic' method. -- -- The result is wrapped in a 'CEMethod'. For an unwrapped value, use -- 'mkStaticMethod'_'. mkStaticMethod' :: (IsFnName String name, IsParameter p) => name -> String -> [p] -> Type -> ClassEntity mkStaticMethod' = (((CEMethod .) .) .) . mkStaticMethod'_ -- | The unwrapped version of 'mkStaticMethod''. mkStaticMethod'_ :: (IsFnName String name, IsParameter p) => name -> String -> [p] -> Type -> Method mkStaticMethod'_ cName foreignName = makeMethod'' cName foreignName MStatic Nonpure -- | A \"property\" getter/setter pair. newtype Prop = Prop [Method] -- | Creates a getter/setter binding pair for methods: -- -- > T foo() const -- > void setFoo(T) -- -- The result is wrapped in a 'CEProp'. For an unwrapped value, use -- 'mkProp_'. mkProp :: String -> Type -> ClassEntity mkProp = (CEProp .) . mkProp_ -- | The unwrapped version of 'mkProp'. mkProp_ :: String -> Type -> Prop mkProp_ name t = let c:cs = name setName = 's' : 'e' : 't' : toUpper c : cs in Prop [ mkConstMethod_ name np t , mkMethod_ setName [t] Internal_TVoid ] -- | Creates a getter/setter binding pair for static methods: -- -- > static T foo() const -- > static void setFoo(T) mkStaticProp :: String -> Type -> ClassEntity mkStaticProp = (CEProp .) . mkStaticProp_ -- | The unwrapped version of 'mkStaticProp'. mkStaticProp_ :: String -> Type -> Prop mkStaticProp_ name t = let c:cs = name setName = 's' : 'e' : 't' : toUpper c : cs in Prop [ mkStaticMethod_ name np t , mkStaticMethod_ setName [t] Internal_TVoid ] -- | Creates a getter/setter binding pair for boolean methods, where the getter -- is prefixed with @is@: -- -- > bool isFoo() const -- > void setFoo(bool) -- -- The result is wrapped in a 'CEProp'. For an unwrapped value, use -- 'mkBoolIsProp_'. mkBoolIsProp :: String -> ClassEntity mkBoolIsProp = CEProp . mkBoolIsProp_ -- | The unwrapped version of 'mkBoolIsProp'. mkBoolIsProp_ :: String -> Prop mkBoolIsProp_ name = let c:cs = name name' = toUpper c : cs isName = 'i':'s':name' setName = 's':'e':'t':name' in Prop [ mkConstMethod_ isName np boolT , mkMethod_ setName [boolT] voidT ] -- | Creates a getter/setter binding pair for boolean methods, where the getter -- is prefixed with @has@: -- -- > bool hasFoo() const -- > void setFoo(bool) -- -- The result is wrapped in a 'CEProp'. For an unwrapped value, use -- 'mkBoolHasProp_'. mkBoolHasProp :: String -> ClassEntity mkBoolHasProp = CEProp . mkBoolHasProp_ -- | The unwrapped version of 'mkBoolHasProp'. mkBoolHasProp_ :: String -> Prop mkBoolHasProp_ name = let c:cs = name name' = toUpper c : cs hasName = 'h':'a':'s':name' setName = 's':'e':'t':name' in Prop [ mkConstMethod_ hasName np boolT , mkMethod_ setName [boolT] voidT ] sayCppExport :: LC.SayExportMode -> Class -> LC.Generator () sayCppExport mode cls = case mode of LC.SayHeader -> return () LC.SaySource -> do let clsPtr = ptrT $ objT cls constClsPtr = ptrT $ constT $ objT cls -- TODO Is this redundant for a completely empty class? (No ctors or methods, private dtor.) LC.addReqsM $ classReqs cls -- This is needed at least for the delete function. -- Export each of the class's constructors. forM_ (classCtors cls) $ \ctor -> Function.sayCppExportFn (classEntityExtName cls ctor) (Function.CallFn $ LC.say "new" >> LC.sayIdentifier (classIdentifier cls)) Nothing (ctorParams ctor) clsPtr (ctorExceptionHandlers ctor) True -- Render the body. -- Export a delete function for the class. when (classDtorIsPublic cls) $ LC.sayFunction (cppDeleteFnName cls) ["self"] (fnT [constClsPtr] voidT) $ Just $ LC.say "delete self;\n" -- Export each of the class's variables. forM_ (classVariables cls) $ sayCppExportClassVar cls -- Export each of the class's methods. forM_ (classMethods cls) $ \method -> do let static = case methodStatic method of Static -> True Nonstatic -> False thisType = case methodConst method of Const -> constClsPtr Nonconst -> clsPtr nonMemberCall = static || case methodImpl method of RealMethod {} -> False FnMethod {} -> True Function.sayCppExportFn (classEntityExtName cls method) (case methodImpl method of RealMethod name -> case name of FnName cName -> Function.CallFn $ do when static $ do LC.sayIdentifier (classIdentifier cls) LC.say "::" LC.say cName FnOp op -> Function.CallOp op FnMethod name -> case name of FnName cName -> Function.CallFn $ LC.sayIdentifier cName FnOp op -> Function.CallOp op) (if nonMemberCall then Nothing else Just thisType) (methodParams method) (methodReturn method) (methodExceptionHandlers method) True -- Render the body. -- Export upcast functions for the class to its direct superclasses. forM_ (classSuperclasses cls) $ genUpcastFns cls -- Export downcast functions from the class's direct and indirect -- superclasses to it. unless (classIsSubclassOfMonomorphic cls) $ forM_ (classSuperclasses cls) $ genDowncastFns cls where genUpcastFns :: Class -> Class -> LC.Generator () genUpcastFns cls' ancestorCls = do LC.sayFunction (cppCastFnName cls' ancestorCls) ["self"] (fnT [ptrT $ constT $ objT cls'] $ ptrT $ constT $ objT ancestorCls) (Just $ LC.say "return self;\n") forM_ (classSuperclasses ancestorCls) $ genUpcastFns cls' genDowncastFns :: Class -> Class -> LC.Generator () genDowncastFns cls' ancestorCls = unless (classIsMonomorphicSuperclass ancestorCls) $ do let clsPtr = ptrT $ constT $ objT cls' ancestorPtr = ptrT $ constT $ objT ancestorCls LC.sayFunction (cppCastFnName ancestorCls cls') ["self"] (fnT [ancestorPtr] clsPtr) $ Just $ do LC.say "return dynamic_cast<" LC.sayType Nothing clsPtr LC.say ">(self);\n" forM_ (classSuperclasses ancestorCls) $ genDowncastFns cls' sayCppExportClassVar :: Class -> ClassVariable -> LC.Generator () sayCppExportClassVar cls v = sayCppExportVar (classVarType v) (case classVarStatic v of Nonstatic -> Just (ptrT $ constT $ objT cls, ptrT $ objT cls) Static -> Nothing) (classVarGettable v) (classVarGetterExtName cls v) (classVarSetterExtName cls v) (case classVarStatic v of Nonstatic -> LC.say $ classVarCName v Static -> do LC.sayIdentifier $ classIdentifier cls LC.says ["::", classVarCName v]) makeClassCppName :: String -> Class -> String makeClassCppName prefix cls = LC.makeCppName [prefix, fromExtName $ classExtName cls] -- | \"gendel\" is the prefix used for wrappers for @delete@ calls. cppDeleteFnPrefix :: String cppDeleteFnPrefix = "gendel" -- | Returns the C++ binding function name of the wrapper for the delete method -- for a class. cppDeleteFnName :: Class -> String cppDeleteFnName = makeClassCppName cppDeleteFnPrefix -- | @cppCastFnName fromCls toCls@ returns the name of the generated C++ -- function that casts a pointer from @fromCls@ to @toCls@. cppCastFnName :: Class -> Class -> String cppCastFnName from to = concat [ "gencast__" , fromExtName $ classExtName from , "__" , fromExtName $ classExtName to ] sayHsExport :: LH.SayExportMode -> Class -> LH.Generator () sayHsExport mode cls = LH.withErrorContext ("generating class " ++ show (classExtName cls)) $ do case mode of LH.SayExportForeignImports -> do sayHsExportClassVars mode cls sayHsExportClassCtors mode cls forM_ (classMethods cls) $ \method -> (Function.sayHsExportFn mode <$> classEntityExtName cls <*> classEntityForeignName cls <*> methodPurity <*> pure (getMethodEffectiveParams cls method) <*> methodReturn <*> methodExceptionHandlers) method LH.SayExportDecls -> do sayHsExportClassClass True cls Const sayHsExportClassClass True cls Nonconst sayHsExportClassStaticMethods cls -- Create a newtype for referencing foreign objects with pointers. The -- newtype is not used with encodings of value objects. sayHsExportClassDataType True cls Const sayHsExportClassDataType True cls Nonconst sayHsExportClassExceptionSupport True cls sayHsExportClassVars mode cls sayHsExportClassCtors mode cls LH.SayExportBoot -> do sayHsExportClassClass False cls Const sayHsExportClassClass False cls Nonconst sayHsExportClassDataType False cls Const sayHsExportClassDataType False cls Nonconst sayHsExportClassExceptionSupport False cls sayHsExportClassVars mode cls sayHsExportClassCastPrimitives mode cls sayHsExportClassSpecialFns mode cls sayHsExportClassClass :: Bool -> Class -> Constness -> LH.Generator () sayHsExportClassClass doDecls cls cst = LH.withErrorContext "generating Haskell typeclass" $ do hsTypeName <- toHsDataTypeName cst cls hsValueClassName <- toHsValueClassName cls hsWithValuePtrName <- toHsWithValuePtrName cls hsPtrClassName <- toHsPtrClassName cst cls hsCastMethodName <- toHsCastMethodName cst cls let supers = classSuperclasses cls hsSupers <- (\x -> if null x then do LH.addImports hsImportForRuntime return ["HoppyFHR.CppPtr"] else return x) =<< case cst of Const -> mapM (toHsPtrClassName Const) supers Nonconst -> (:) <$> toHsPtrClassName Const cls <*> mapM (toHsPtrClassName Nonconst) supers -- Print the value class definition. There is only one of these, and it is -- spiritually closer to the const version of the pointers for this class, so -- we emit for the const case only. when (cst == Const) $ do LH.addImports hsImportForPrelude LH.addExport' hsValueClassName LH.ln LH.saysLn ["class ", hsValueClassName, " a where"] LH.indent $ LH.saysLn [hsWithValuePtrName, " :: a -> (", hsTypeName, " -> HoppyP.IO b) -> HoppyP.IO b"] -- Generate instances for all pointer subtypes. LH.ln LH.saysLn ["instance {-# OVERLAPPABLE #-} ", hsPtrClassName, " a => ", hsValueClassName, " a", if doDecls then " where" else ""] when doDecls $ do LH.addImports $ mconcat [hsImports "Prelude" ["($)", "(.)"], hsImportForPrelude] LH.indent $ LH.saysLn [hsWithValuePtrName, " = HoppyP.flip ($) . ", hsCastMethodName] -- When the class is encodable to a native Haskell type, also print an -- instance for it. let conv = LH.getClassHaskellConversion cls case (classHaskellConversionType conv, classHaskellConversionToCppFn conv) of (Just hsTypeGen, Just _) -> do hsType <- hsTypeGen LH.ln LH.saysLn ["instance {-# OVERLAPPING #-} ", hsValueClassName, " (", LH.prettyPrint hsType, ")", if doDecls then " where" else ""] when doDecls $ do LH.addImports hsImportForRuntime LH.indent $ LH.saysLn [hsWithValuePtrName, " = HoppyFHR.withCppObj"] _ -> return () -- Print the pointer class definition. LH.addExport' hsPtrClassName LH.ln LH.saysLn $ "class (" : intersperse ", " (map (++ " this") hsSupers) ++ [") => ", hsPtrClassName, " this where"] LH.indent $ LH.saysLn [hsCastMethodName, " :: this -> ", hsTypeName] -- Print the non-static methods. when doDecls $ do let methods = filter ((cst ==) . methodConst) $ classMethods cls forM_ methods $ \method -> when (methodStatic method == Nonstatic) $ (Function.sayHsExportFn LH.SayExportDecls <$> classEntityExtName cls <*> classEntityForeignName cls <*> methodPurity <*> pure (getMethodEffectiveParams cls method) <*> methodReturn <*> methodExceptionHandlers) method sayHsExportClassStaticMethods :: Class -> LH.Generator () sayHsExportClassStaticMethods cls = forM_ (classMethods cls) $ \method -> when (methodStatic method == Static) $ (Function.sayHsExportFn LH.SayExportDecls <$> classEntityExtName cls <*> classEntityForeignName cls <*> methodPurity <*> methodParams <*> methodReturn <*> methodExceptionHandlers) method sayHsExportClassDataType :: Bool -> Class -> Constness -> LH.Generator () sayHsExportClassDataType doDecls cls cst = LH.withErrorContext "generating Haskell data types" $ do hsTypeName <- toHsDataTypeName cst cls hsCtor <- toHsDataCtorName LH.Unmanaged cst cls hsCtorGc <- toHsDataCtorName LH.Managed cst cls constCastFnName <- toHsConstCastFnName cst cls LH.addImports $ mconcat [hsImportForForeign, hsImportForPrelude, hsImportForRuntime] -- Unfortunately, we must export the data constructor, so that GHC can marshal -- it in foreign calls in other modules. LH.addExport' hsTypeName LH.ln LH.saysLn ["data ", hsTypeName, " ="] LH.indent $ do LH.saysLn [" ", hsCtor, " (HoppyF.Ptr ", hsTypeName, ")"] LH.saysLn ["| ", hsCtorGc, " (HoppyF.ForeignPtr ()) (HoppyF.Ptr ", hsTypeName, ")"] when doDecls $ do LH.addImports $ hsImport1 "Prelude" "(==)" LH.indent $ LH.sayLn "deriving (HoppyP.Show)" LH.ln LH.saysLn ["instance HoppyP.Eq ", hsTypeName, " where"] LH.indent $ LH.saysLn ["x == y = HoppyFHR.toPtr x == HoppyFHR.toPtr y"] LH.ln LH.saysLn ["instance HoppyP.Ord ", hsTypeName, " where"] LH.indent $ LH.saysLn ["compare x y = HoppyP.compare (HoppyFHR.toPtr x) (HoppyFHR.toPtr y)"] -- Generate const_cast functions: -- castFooToConst :: Foo -> FooConst -- castFooToNonconst :: FooConst -> Foo hsTypeNameOppConst <- toHsDataTypeName (constNegate cst) cls LH.ln LH.addExport constCastFnName LH.saysLn [constCastFnName, " :: ", hsTypeNameOppConst, " -> ", hsTypeName] when doDecls $ do LH.addImports $ hsImport1 "Prelude" "($)" hsCtorOppConst <- toHsDataCtorName LH.Unmanaged (constNegate cst) cls hsCtorGcOppConst <- toHsDataCtorName LH.Managed (constNegate cst) cls LH.saysLn [constCastFnName, " (", hsCtorOppConst, " ptr') = ", hsCtor, " $ HoppyF.castPtr ptr'"] LH.saysLn [constCastFnName, " (", hsCtorGcOppConst, " fptr' ptr') = ", hsCtorGc, " fptr' $ HoppyF.castPtr ptr'"] -- Generate an instance of CppPtr. LH.ln if doDecls then do LH.addImports $ hsImport1 "Prelude" "($)" LH.saysLn ["instance HoppyFHR.CppPtr ", hsTypeName, " where"] LH.indent $ do LH.saysLn ["nullptr = ", hsCtor, " HoppyF.nullPtr"] LH.ln LH.saysLn ["withCppPtr (", hsCtor, " ptr') f' = f' ptr'"] LH.saysLn ["withCppPtr (", hsCtorGc, " fptr' ptr') f' = HoppyF.withForeignPtr fptr' $ \\_ -> f' ptr'"] LH.ln LH.saysLn ["toPtr (", hsCtor, " ptr') = ptr'"] LH.saysLn ["toPtr (", hsCtorGc, " _ ptr') = ptr'"] LH.ln LH.saysLn ["touchCppPtr (", hsCtor, " _) = HoppyP.return ()"] LH.saysLn ["touchCppPtr (", hsCtorGc, " fptr' _) = HoppyF.touchForeignPtr fptr'"] when (classDtorIsPublic cls) $ do LH.addImports $ hsImport1 "Prelude" "(==)" LH.ln LH.saysLn ["instance HoppyFHR.Deletable ", hsTypeName, " where"] LH.indent $ do -- Note, similar "delete" and "toGc" functions are generated for exception -- classes' ExceptionClassInfo structures. case cst of Const -> LH.saysLn ["delete (", hsCtor, " ptr') = ", toHsClassDeleteFnName' cls, " ptr'"] Nonconst -> do constTypeName <- toHsDataTypeName Const cls LH.saysLn ["delete (",hsCtor, " ptr') = ", toHsClassDeleteFnName' cls, " $ (HoppyF.castPtr ptr' :: HoppyF.Ptr ", constTypeName, ")"] LH.saysLn ["delete (", hsCtorGc, " _ _) = HoppyP.fail $ HoppyP.concat ", "[\"Deletable.delete: Asked to delete a GC-managed \", ", show hsTypeName, ", \" object.\"]"] LH.ln LH.saysLn ["toGc this'@(", hsCtor, " ptr') = ", -- No sense in creating a ForeignPtr for a null pointer. "if ptr' == HoppyF.nullPtr then HoppyP.return this' else HoppyP.fmap ", "(HoppyP.flip ", hsCtorGc, " ptr') $ ", "HoppyF.newForeignPtr ", -- The foreign delete function takes a const pointer; we cast it to -- take a Ptr () to match up with the ForeignPtr () we're creating, -- assuming that data pointers have the same representation. "(HoppyF.castFunPtr ", toHsClassDeleteFnPtrName' cls, " :: HoppyF.FunPtr (HoppyF.Ptr () -> HoppyP.IO ())) ", "(HoppyF.castPtr ptr' :: HoppyF.Ptr ())"] LH.saysLn ["toGc this'@(", hsCtorGc, " {}) = HoppyP.return this'"] forM_ (classFindCopyCtor cls) $ \copyCtor -> do copyCtorName <- toHsCtorName cls copyCtor LH.ln LH.saysLn ["instance HoppyFHR.Copyable ", hsTypeName, " ", case cst of Nonconst -> hsTypeName Const -> hsTypeNameOppConst, " where copy = ", copyCtorName] else do LH.saysLn ["instance HoppyFHR.CppPtr ", hsTypeName] when (classDtorIsPublic cls) $ LH.saysLn ["instance HoppyFHR.Deletable ", hsTypeName] forM_ (classFindCopyCtor cls) $ \_ -> LH.saysLn ["instance HoppyFHR.Copyable ", hsTypeName, " ", case cst of Nonconst -> hsTypeName Const -> hsTypeNameOppConst] -- Generate instances for all superclasses' typeclasses. genInstances hsTypeName [] cls where genInstances :: String -> [Class] -> Class -> LH.Generator () genInstances hsTypeName path ancestorCls = do -- In this example Bar inherits from Foo. We are generating instances -- either for BarConst or Bar, depending on 'cst'. -- -- BarConst's instances: -- instance FooConstPtr BarConst where -- toFooConst (BarConst ptr') = FooConst $ castBarToFoo ptr' -- toFooConst (BarConstGc fptr' ptr') = FooConstGc fptr' $ castBarToFoo ptr' -- -- instance BarConstPtr BarConst where -- toFooConst = id -- -- Bar's instances: -- instance FooConstPtr Bar -- toFooConst (Bar ptr') = -- FooConst $ castBarToFoo $ castBarToConst ptr' -- toFooConst (BarGc fptr' ptr') = -- FooConstGc fptr' $ castBarToFoo $ castBarToConst ptr' -- -- instance FooPtr Bar -- toFoo (Bar ptr') = -- Foo $ castFooToNonconst $ castBarToFoo $ castBarToConst ptr' -- toFoo (BarGc fptr' ptr') = -- FooGc fptr' $ castFooToNonconst $ castBarToFoo $ castBarToConst ptr' -- -- instance BarConstPtr Bar -- toBarConst (Bar ptr') = Bar $ castBarToConst ptr' -- toBarConst (BarGc fptr' ptr') = BarGc fptr' $ castBarToConst ptr' -- -- instance BarPtr Bar -- toBar = id -- -- In all cases, we unwrap the pointer, maybe add const, maybe do an -- upcast, maybe remove const, then rewrap the pointer. The identity -- cases are where we just unwrap and wrap again. forM_ (case cst of Const -> [Const] Nonconst -> [Const, Nonconst]) $ \ancestorCst -> do LH.ln ancestorPtrClassName <- toHsPtrClassName ancestorCst ancestorCls LH.saysLn ["instance ", ancestorPtrClassName, " ", hsTypeName, if doDecls then " where" else ""] when doDecls $ LH.indent $ do -- Unqualified, for Haskell instance methods. let castMethodName = toHsCastMethodName' ancestorCst ancestorCls if null path && cst == ancestorCst then do LH.addImports hsImportForPrelude LH.saysLn [castMethodName, " = HoppyP.id"] else do let addConst = cst == Nonconst removeConst = ancestorCst == Nonconst when (addConst || removeConst) $ LH.addImports hsImportForForeign forM_ ([minBound..] :: [LH.Managed]) $ \managed -> do ancestorCtor <- case managed of LH.Unmanaged -> (\x -> [x]) <$> toHsDataCtorName LH.Unmanaged ancestorCst ancestorCls LH.Managed -> (\x -> [x, " fptr'"]) <$> toHsDataCtorName LH.Managed ancestorCst ancestorCls ptrPattern <- case managed of LH.Unmanaged -> (\x -> [x, " ptr'"]) <$> toHsDataCtorName LH.Unmanaged cst cls LH.Managed -> (\x -> [x, " fptr' ptr'"]) <$> toHsDataCtorName LH.Managed cst cls LH.saysLn . concat =<< sequence [ return $ [castMethodName, " ("] ++ ptrPattern ++ [") = "] ++ ancestorCtor , if removeConst then do ancestorConstType <- toHsDataTypeName Const ancestorCls ancestorNonconstType <- toHsDataTypeName Nonconst ancestorCls return [" $ (HoppyF.castPtr :: HoppyF.Ptr ", ancestorConstType, " -> HoppyF.Ptr ", ancestorNonconstType, ")"] else return [] , if not $ null path then do LH.addImports $ hsImport1 "Prelude" "($)" castPrimitiveName <- toHsCastPrimitiveName cls cls ancestorCls return [" $ ", castPrimitiveName] else return [] , if addConst then do LH.addImports $ hsImport1 "Prelude" "($)" nonconstTypeName <- toHsDataTypeName Nonconst cls constTypeName <- toHsDataTypeName Const cls return [" $ (HoppyF.castPtr :: HoppyF.Ptr ", nonconstTypeName, " -> HoppyF.Ptr ", constTypeName, ")"] else return [] , return [" ptr'"] ] forM_ (classSuperclasses ancestorCls) $ genInstances hsTypeName $ ancestorCls : path sayHsExportClassVars :: LH.SayExportMode -> Class -> LH.Generator () sayHsExportClassVars mode cls = forM_ (classVariables cls) $ sayHsExportClassVar mode cls sayHsExportClassVar :: LH.SayExportMode -> Class -> ClassVariable -> LH.Generator () sayHsExportClassVar mode cls v = LH.withErrorContext ("generating variable " ++ show (classVarExtName v)) $ sayHsExportVar mode (classVarType v) (case classVarStatic v of Nonstatic -> Just cls Static -> Nothing) (classVarGettable v) (classVarGetterExtName cls v) (classVarGetterForeignName cls v) (classVarSetterExtName cls v) (classVarSetterForeignName cls v) sayHsExportClassCtors :: LH.SayExportMode -> Class -> LH.Generator () sayHsExportClassCtors mode cls = LH.withErrorContext "generating constructors" $ forM_ (classCtors cls) $ \ctor -> (Function.sayHsExportFn mode <$> classEntityExtName cls <*> classEntityForeignName cls <*> pure Nonpure <*> ctorParams <*> pure (ptrT $ objT cls) <*> ctorExceptionHandlers) ctor sayHsExportClassSpecialFns :: LH.SayExportMode -> Class -> LH.Generator () sayHsExportClassSpecialFns mode cls = do typeName <- toHsDataTypeName Nonconst cls typeNameConst <- toHsDataTypeName Const cls -- Say the delete function. LH.withErrorContext "generating delete bindings" $ case mode of LH.SayExportForeignImports -> when (classDtorIsPublic cls) $ do LH.addImports $ mconcat [hsImportForForeign, hsImportForPrelude] LH.saysLn ["foreign import ccall \"", cppDeleteFnName cls, "\" ", toHsClassDeleteFnName' cls, " :: HoppyF.Ptr ", typeNameConst, " -> HoppyP.IO ()"] LH.saysLn ["foreign import ccall \"&", cppDeleteFnName cls, "\" ", toHsClassDeleteFnPtrName' cls, " :: HoppyF.FunPtr (HoppyF.Ptr ", typeNameConst, " -> HoppyP.IO ())"] -- The user interface to this is the generic 'delete' function, rendered -- elsewhere. LH.SayExportDecls -> return () LH.SayExportBoot -> return () LH.withErrorContext "generating pointer Assignable instance" $ case mode of LH.SayExportForeignImports -> return () LH.SayExportDecls -> do LH.addImports $ mconcat [hsImport1 "Prelude" "($)", hsImportForForeign, hsImportForRuntime] LH.ln LH.saysLn ["instance HoppyFHR.Assignable (HoppyF.Ptr (HoppyF.Ptr ", typeName, ")) ", typeName, " where"] LH.indent $ LH.sayLn "assign ptr' value' = HoppyF.poke ptr' $ HoppyFHR.toPtr value'" LH.SayExportBoot -> return () -- If the class has an assignment operator that takes its own type, then -- generate an instance of Assignable. LH.withErrorContext "generating Assignable instance" $ do let assignmentMethods = flip filter (classMethods cls) $ \m -> let paramTypes = map parameterType $ methodParams m in methodApplicability m == MNormal && (paramTypes == [objT cls] || paramTypes == [refT $ constT $ objT cls]) && (case methodImpl m of RealMethod name -> name == FnOp OpAssign FnMethod name -> name == FnOp OpAssign) withAssignmentMethod f = case assignmentMethods of [] -> return () [m] -> f m _ -> throwError $ concat ["Can't determine an Assignable instance to generator for ", show cls, " because it has multiple assignment operators ", show assignmentMethods] when (mode == LH.SayExportDecls) $ withAssignmentMethod $ \m -> do LH.addImports $ mconcat [hsImport1 "Prelude" "(>>)", hsImportForPrelude] valueClassName <- toHsValueClassName cls assignmentMethodName <- toHsMethodName cls m LH.ln LH.saysLn ["instance ", valueClassName, " a => HoppyFHR.Assignable ", typeName, " a where"] LH.indent $ LH.saysLn ["assign x' y' = ", assignmentMethodName, " x' y' >> HoppyP.return ()"] -- A pointer to an object pointer is decodable to an object pointer by peeking -- at the value, so generate a Decodable instance. You are now a two-star -- programmer. There is a generic @Ptr (Ptr a)@ to @Ptr a@ instance which -- handles deeper levels. LH.withErrorContext "generating pointer Decodable instance" $ do case mode of LH.SayExportForeignImports -> return () LH.SayExportDecls -> do LH.addImports $ mconcat [hsImport1 "Prelude" "(.)", hsImportForForeign, hsImportForPrelude, hsImportForRuntime] LH.ln LH.saysLn ["instance HoppyFHR.Decodable (HoppyF.Ptr (HoppyF.Ptr ", typeName, ")) ", typeName, " where"] LH.indent $ do ctorName <- toHsDataCtorName LH.Unmanaged Nonconst cls LH.saysLn ["decode = HoppyP.fmap ", ctorName, " . HoppyF.peek"] LH.SayExportBoot -> do LH.addImports $ mconcat [hsImportForForeign, hsImportForRuntime] LH.ln -- TODO Encodable. LH.saysLn ["instance HoppyFHR.Decodable (HoppyF.Ptr (HoppyF.Ptr ", typeName, ")) ", typeName] -- Say Encodable and Decodable instances, if the class is encodable and -- decodable. LH.withErrorContext "generating Encodable/Decodable instances" $ do let conv = LH.getClassHaskellConversion cls forM_ (classHaskellConversionType conv) $ \hsTypeGen -> do let hsTypeStrGen = hsTypeGen >>= \hsType -> return $ "(" ++ LH.prettyPrint hsType ++ ")" case mode of LH.SayExportForeignImports -> return () LH.SayExportDecls -> do -- Say the Encodable instances. forM_ (classHaskellConversionToCppFn conv) $ \toCppFnGen -> do hsTypeStr <- hsTypeStrGen LH.addImports $ mconcat [hsImportForPrelude, hsImportForRuntime] castMethodName <- toHsCastMethodName Const cls LH.ln LH.saysLn ["instance HoppyFHR.Encodable ", typeName, " ", hsTypeStr, " where"] LH.indent $ do LH.sayLn "encode =" LH.indent toCppFnGen LH.ln LH.saysLn ["instance HoppyFHR.Encodable ", typeNameConst, " ", hsTypeStr, " where"] LH.indent $ LH.saysLn ["encode = HoppyP.fmap (", castMethodName, ") . HoppyFHR.encodeAs (HoppyP.undefined :: ", typeName, ")"] -- Say the Decodable instances. forM_ (classHaskellConversionFromCppFn conv) $ \fromCppFnGen -> do hsTypeStr <- hsTypeStrGen LH.addImports hsImportForRuntime castMethodName <- toHsCastMethodName Const cls LH.ln LH.saysLn ["instance HoppyFHR.Decodable ", typeName, " ", hsTypeStr, " where"] LH.indent $ LH.saysLn ["decode = HoppyFHR.decode . ", castMethodName] LH.ln LH.saysLn ["instance HoppyFHR.Decodable ", typeNameConst, " ", hsTypeStr, " where"] LH.indent $ do LH.sayLn "decode =" LH.indent fromCppFnGen LH.SayExportBoot -> do -- Say the Encodable instances. forM_ (classHaskellConversionToCppFn conv) $ \_ -> do hsTypeStr <- hsTypeStrGen LH.addImports hsImportForRuntime LH.ln LH.saysLn ["instance HoppyFHR.Encodable ", typeName, " (", hsTypeStr, ")"] LH.saysLn ["instance HoppyFHR.Encodable ", typeNameConst, " (", hsTypeStr, ")"] -- Say the Decodable instances. forM_ (classHaskellConversionFromCppFn conv) $ \_ -> do hsTypeStr <- hsTypeStrGen LH.addImports hsImportForRuntime LH.ln LH.saysLn ["instance HoppyFHR.Decodable ", typeName, " (", hsTypeStr, ")"] LH.saysLn ["instance HoppyFHR.Decodable ", typeNameConst, " (", hsTypeStr, ")"] -- | Generates a non-const @CppException@ instance if the class is an exception -- class. sayHsExportClassExceptionSupport :: Bool -> Class -> LH.Generator () sayHsExportClassExceptionSupport doDecls cls = when (classIsException cls) $ LH.withErrorContext "generating exception support" $ do typeName <- toHsDataTypeName Nonconst cls typeNameConst <- toHsDataTypeName Const cls -- Generate a non-const CppException instance. exceptionId <- getHsClassExceptionId cls LH.addImports hsImportForRuntime LH.ln LH.saysLn ["instance HoppyFHR.CppException ", typeName, if doDecls then " where" else ""] when doDecls $ LH.indent $ do ctorName <- toHsDataCtorName LH.Unmanaged Nonconst cls ctorGcName <- toHsDataCtorName LH.Managed Nonconst cls LH.addImports $ mconcat [hsImports "Prelude" ["($)", "(.)", "(=<<)"], hsImportForForeign, hsImportForMap, hsImportForPrelude] LH.sayLn "cppExceptionInfo _ =" LH.indent $ do LH.saysLn ["HoppyFHR.ExceptionClassInfo (HoppyFHR.ExceptionId ", show $ getExceptionId exceptionId, ") ", show typeName, " upcasts' delete' copy' toGc'"] -- Note, similar "delete" and "toGc" functions are generated for the class's -- Deletable instance. LH.saysLn ["where delete' ptr' = ", toHsClassDeleteFnName' cls, " (HoppyF.castPtr ptr' :: HoppyF.Ptr ", typeNameConst, ")"] LH.indentSpaces 6 $ do LH.ln LH.saysLn ["copy' = HoppyP.fmap (HoppyF.castPtr . HoppyFHR.toPtr) . HoppyFHR.copy . ", ctorName, " . HoppyF.castPtr"] LH.ln LH.saysLn ["toGc' ptr' = HoppyF.newForeignPtr ", -- The foreign delete function takes a const pointer; we cast it to -- take a Ptr () to match up with the ForeignPtr () we're creating, -- assuming that data pointers have the same representation. "(HoppyF.castFunPtr ", toHsClassDeleteFnPtrName' cls, " :: HoppyF.FunPtr (HoppyF.Ptr () -> HoppyP.IO ())) ", "ptr'"] LH.sayLn "upcasts' = HoppyDM.fromList" LH.indent $ case classSuperclasses cls of [] -> LH.sayLn "[]" _ -> do let genCast :: Bool -> [Class] -> Class -> LH.Generator () genCast first path ancestorCls = when (classIsException ancestorCls) $ do let path' = ancestorCls : path ancestorId <- getHsClassExceptionId ancestorCls ancestorCastChain <- forM (zip path' $ drop 1 path') $ \(to, from) -> -- We're upcasting, so 'from' is the subclass. toHsCastPrimitiveName from from to LH.saysLn $ concat [ [if first then "[" else ",", " ( HoppyFHR.ExceptionId ", show $ getExceptionId ancestorId, ", \\(e' :: HoppyF.Ptr ()) -> "] , intersperse " $ " $ "HoppyF.castPtr" : ancestorCastChain ++ ["HoppyF.castPtr e' :: HoppyF.Ptr ()"] , [")"] ] forM_ (classSuperclasses ancestorCls) $ genCast False path' forM_ (zip (classSuperclasses cls) (True : repeat False)) $ \(ancestorCls, first) -> genCast first [cls] ancestorCls LH.sayLn "]" LH.ln LH.saysLn ["cppExceptionBuild fptr' ptr' = ", ctorGcName, " fptr' (HoppyF.castPtr ptr' :: HoppyF.Ptr ", typeName, ")"] LH.ln LH.saysLn ["cppExceptionBuildToGc ptr' = HoppyFHR.toGc $ ", ctorName, " (HoppyF.castPtr ptr' :: HoppyF.Ptr ", typeName, ")"] -- Generate a const CppException instance that piggybacks off of the -- non-const implementation. LH.ln LH.saysLn ["instance HoppyFHR.CppException ", typeNameConst, if doDecls then " where" else ""] when doDecls $ LH.indent $ do LH.addImports $ mconcat [hsImport1 "Prelude" "(.)", hsImportForPrelude] constCastFnName <- toHsConstCastFnName Const cls LH.saysLn ["cppExceptionInfo _ = HoppyFHR.cppExceptionInfo (HoppyP.undefined :: ", typeName, ")"] LH.saysLn ["cppExceptionBuild = (", constCastFnName, " .) . HoppyFHR.cppExceptionBuild"] LH.saysLn ["cppExceptionBuildToGc = HoppyP.fmap ", constCastFnName, " . HoppyFHR.cppExceptionBuildToGc"] -- Generate a non-const CppThrowable instance. LH.ln LH.saysLn ["instance HoppyFHR.CppThrowable ", typeName, if doDecls then " where" else ""] when doDecls $ LH.indent $ do ctorName <- toHsDataCtorName LH.Unmanaged Nonconst cls ctorGcName <- toHsDataCtorName LH.Managed Nonconst cls LH.addImports $ mconcat [hsImportForForeign, hsImportForPrelude] LH.saysLn ["toSomeCppException this'@(", ctorName, " ptr') = ", "HoppyFHR.SomeCppException (HoppyFHR.cppExceptionInfo this') HoppyP.Nothing ", "(HoppyF.castPtr ptr')"] LH.saysLn ["toSomeCppException this'@(", ctorGcName, " fptr' ptr') = ", "HoppyFHR.SomeCppException (HoppyFHR.cppExceptionInfo this') (HoppyP.Just fptr') ", "(HoppyF.castPtr ptr')"] sayHsExportClassCastPrimitives :: LH.SayExportMode -> Class -> LH.Generator () sayHsExportClassCastPrimitives mode cls = LH.withErrorContext "generating cast primitives" $ do clsType <- toHsDataTypeName Const cls case mode of LH.SayExportForeignImports -> forAncestors cls $ \super -> do hsCastFnName <- toHsCastPrimitiveName cls cls super hsDownCastFnName <- toHsCastPrimitiveName cls super cls superType <- toHsDataTypeName Const super LH.addImports hsImportForForeign LH.addExport hsCastFnName LH.saysLn [ "foreign import ccall \"", cppCastFnName cls super , "\" ", hsCastFnName, " :: HoppyF.Ptr ", clsType, " -> HoppyF.Ptr ", superType ] unless (classIsSubclassOfMonomorphic cls || classIsMonomorphicSuperclass super) $ do LH.addExport hsDownCastFnName LH.saysLn [ "foreign import ccall \"", cppCastFnName super cls , "\" ", hsDownCastFnName, " :: HoppyF.Ptr ", superType, " -> HoppyF.Ptr " , clsType ] return True LH.SayExportDecls -> -- Generate a downcast typeclass and instances for all ancestor classes -- for the current constness. These don't need to be in the boot file, -- since they're not used by other generated bindings. unless (classIsSubclassOfMonomorphic cls) $ forM_ [minBound..] $ \cst -> do downCastClassName <- toHsDownCastClassName cst cls downCastMethodName <- toHsDownCastMethodName cst cls typeName <- toHsDataTypeName cst cls LH.addExport' downCastClassName LH.ln LH.saysLn ["class ", downCastClassName, " a where"] LH.indent $ LH.saysLn [downCastMethodName, " :: ", LH.prettyPrint $ HsTyFun (HsTyVar $ HsIdent "a") $ HsTyCon $ UnQual $ HsIdent typeName] LH.ln forAncestors cls $ \super -> case classIsMonomorphicSuperclass super of True -> return False False -> do superTypeName <- toHsDataTypeName cst super primitiveCastFn <- toHsCastPrimitiveName cls super cls LH.saysLn ["instance ", downCastClassName, " ", superTypeName, " where"] -- If Foo is a superclass of Bar: -- -- instance BarSuper Foo where -- downToBar castFooToNonconst . downcast' . castFooToConst -- where downcast' (FooConst ptr') = BarConst $ castFooToBar ptr' -- downcast' (FooConstGc fptr' ptr') = BarConstGc fptr' $ castFooToBar ptr' -- -- instance BarSuperConst FooConst where -- downToBarConst = downcast' -- where downcast' (FooConst ptr') = BarConst $ castFooToBar ptr' -- downcast' (FooConstGc fptr' ptr') = BarConstGc fptr' $ castFooToBar ptr' LH.indent $ do case cst of Const -> LH.saysLn [downCastMethodName, " = cast'"] Nonconst -> do LH.addImports $ hsImport1 "Prelude" "(.)" castClsToNonconst <- toHsConstCastFnName Nonconst cls castSuperToConst <- toHsConstCastFnName Const super LH.saysLn [downCastMethodName, " = ", castClsToNonconst, " . cast' . ", castSuperToConst] LH.indent $ do LH.sayLn "where" LH.indent $ do clsCtorName <- toHsDataCtorName LH.Unmanaged Const cls clsCtorGcName <- toHsDataCtorName LH.Managed Const cls superCtorName <- toHsDataCtorName LH.Unmanaged Const super superCtorGcName <- toHsDataCtorName LH.Managed Const super LH.saysLn ["cast' (", superCtorName, " ptr') = ", clsCtorName, " $ ", primitiveCastFn, " ptr'"] LH.saysLn ["cast' (", superCtorGcName, " fptr' ptr') = ", clsCtorGcName , " fptr' $ ", primitiveCastFn, " ptr'"] return True LH.SayExportBoot -> do forAncestors cls $ \super -> do hsCastFnName <- toHsCastPrimitiveName cls cls super superType <- toHsDataTypeName Const super LH.addImports hsImportForForeign LH.addExport hsCastFnName LH.saysLn [hsCastFnName, " :: HoppyF.Ptr ", clsType, " -> HoppyF.Ptr ", superType] return True where forAncestors :: Class -> (Class -> LH.Generator Bool) -> LH.Generator () forAncestors cls' f = forM_ (classSuperclasses cls') $ \super -> do recur <- f super when recur $ forAncestors super f getMethodEffectiveParams :: Class -> Method -> [Parameter] getMethodEffectiveParams cls method = (case methodImpl method of RealMethod {} -> case methodApplicability method of MNormal -> (("this" ~: ptrT $ objT cls) :) MConst -> (("this" ~: ptrT $ constT $ objT cls) :) MStatic -> id FnMethod {} -> id) $ methodParams method getHsClassExceptionId :: Class -> LH.Generator ExceptionId getHsClassExceptionId cls = do iface <- LH.askInterface fromMaybeM (throwError $ concat ["Internal error, exception class ", show cls, " doesn't have an exception ID"]) $ interfaceExceptionClassId iface cls -- | The name for the typeclass of types that can be represented as values of -- the given C++ class. toHsValueClassName :: Class -> LH.Generator String toHsValueClassName cls = LH.inFunction "toHsValueClassName" $ LH.addExtNameModule (classExtName cls) $ toHsValueClassName' cls -- | Pure version of 'toHsValueClassName' that doesn't create a qualified name. toHsValueClassName' :: Class -> String toHsValueClassName' cls = toHsDataTypeName' Nonconst cls ++ "Value" -- | The name of the method within the 'toHsValueClassName' typeclass for -- accessing an object of the type as a pointer. toHsWithValuePtrName :: Class -> LH.Generator String toHsWithValuePtrName cls = LH.inFunction "toHsWithValuePtrName" $ LH.addExtNameModule (classExtName cls) $ toHsWithValuePtrName' cls -- | Pure version of 'toHsWithValuePtrName' that doesn't create a qualified name. toHsWithValuePtrName' :: Class -> String toHsWithValuePtrName' cls = concat ["with", toHsDataTypeName' Nonconst cls, "Ptr"] -- | The name for the typeclass of types that are (possibly const) pointers to -- objects of the given C++ class, or subclasses. toHsPtrClassName :: Constness -> Class -> LH.Generator String toHsPtrClassName cst cls = LH.inFunction "toHsPtrClassName" $ LH.addExtNameModule (classExtName cls) $ toHsPtrClassName' cst cls -- | Pure version of 'toHsPtrClassName' that doesn't create a qualified name. toHsPtrClassName' :: Constness -> Class -> String toHsPtrClassName' cst cls = toHsDataTypeName' cst cls ++ "Ptr" -- | The name of the function that upcasts pointers to the specific class type -- and constness. toHsCastMethodName :: Constness -> Class -> LH.Generator String toHsCastMethodName cst cls = LH.inFunction "toHsCastMethodName" $ LH.addExtNameModule (classExtName cls) $ toHsCastMethodName' cst cls -- | Pure version of 'toHsCastMethodName' that doesn't create a qualified name. toHsCastMethodName' :: Constness -> Class -> String toHsCastMethodName' cst cls = "to" ++ toHsDataTypeName' cst cls -- | The name of the typeclass that provides a method to downcast to a specific -- class type. See 'toHsDownCastMethodName'. toHsDownCastClassName :: Constness -> Class -> LH.Generator String toHsDownCastClassName cst cls = LH.inFunction "toHsDownCastClassName" $ LH.addExtNameModule (classExtName cls) $ toHsDownCastClassName' cst cls -- | Pure version of 'toHsDownCastClassName' that doesn't create a qualified -- name. toHsDownCastClassName' :: Constness -> Class -> String toHsDownCastClassName' cst cls = concat [toHsDataTypeName' Nonconst cls, "Super", case cst of Const -> "Const" Nonconst -> ""] -- | The name of the function that downcasts pointers to the specific class type -- and constness. toHsDownCastMethodName :: Constness -> Class -> LH.Generator String toHsDownCastMethodName cst cls = LH.inFunction "toHsDownCastMethodName" $ LH.addExtNameModule (classExtName cls) $ toHsDownCastMethodName' cst cls -- | Pure version of 'toHsDownCastMethodName' that doesn't create a qualified -- name. toHsDownCastMethodName' :: Constness -> Class -> String toHsDownCastMethodName' cst cls = "downTo" ++ toHsDataTypeName' cst cls -- | The import name for the foreign function that casts between two specific -- pointer types. Used for upcasting and downcasting. -- -- We need to know which module the cast function resides in, and while we could -- look this up, the caller always knows, so we just have them pass it in. toHsCastPrimitiveName :: Class -> Class -> Class -> LH.Generator String toHsCastPrimitiveName descendentClass from to = LH.inFunction "toHsCastPrimitiveName" $ LH.addExtNameModule (classExtName descendentClass) $ toHsCastPrimitiveName' from to -- | Pure version of 'toHsCastPrimitiveName' that doesn't create a qualified -- name. toHsCastPrimitiveName' :: Class -> Class -> String toHsCastPrimitiveName' from to = concat ["cast", toHsDataTypeName' Nonconst from, "To", toHsDataTypeName' Nonconst to] -- | The name of one of the functions that add/remove const to/from a class's -- pointer type. Given 'Const', it will return the function that adds const, -- and given 'Nonconst', it will return the function that removes const. toHsConstCastFnName :: Constness -> Class -> LH.Generator String toHsConstCastFnName cst cls = LH.inFunction "toHsConstCastFnName" $ LH.addExtNameModule (classExtName cls) $ toHsConstCastFnName' cst cls -- | Pure version of 'toHsConstCastFnName' that doesn't create a qualified name. toHsConstCastFnName' :: Constness -> Class -> String toHsConstCastFnName' cst cls = concat ["cast", toHsDataTypeName' Nonconst cls, case cst of Const -> "ToConst" Nonconst -> "ToNonconst"] -- | The name of the data type that represents a pointer to an object of the -- given class and constness. toHsDataTypeName :: Constness -> Class -> LH.Generator String toHsDataTypeName cst cls = LH.inFunction "toHsDataTypeName" $ LH.addExtNameModule (classExtName cls) $ toHsDataTypeName' cst cls -- | Pure version of 'toHsDataTypeName' that doesn't create a qualified name. toHsDataTypeName' :: Constness -> Class -> String toHsDataTypeName' cst cls = LH.toHsTypeName' cst $ classExtName cls -- | The name of a data constructor for one of the object pointer types. toHsDataCtorName :: LH.Managed -> Constness -> Class -> LH.Generator String toHsDataCtorName m cst cls = LH.inFunction "toHsDataCtorName" $ LH.addExtNameModule (classExtName cls) $ toHsDataCtorName' m cst cls -- | Pure version of 'toHsDataCtorName' that doesn't create a qualified name. toHsDataCtorName' :: LH.Managed -> Constness -> Class -> String toHsDataCtorName' m cst cls = case m of LH.Unmanaged -> base LH.Managed -> base ++ "Gc" where base = toHsDataTypeName' cst cls -- | The name of the foreign function import wrapping @delete@ for the given -- class type. This is in internal to the binding; normal users should use -- 'Foreign.Hoppy.Runtime.delete'. -- -- This is internal to a generated Haskell module, so it does not have a public -- (qualified) form. toHsClassDeleteFnName' :: Class -> String toHsClassDeleteFnName' cls = 'd':'e':'l':'e':'t':'e':'\'':toHsDataTypeName' Nonconst cls -- | The name of the foreign import that imports the same function as -- 'toHsClassDeleteFnName'', but as a 'Foreign.Ptr.FunPtr' rather than an actual -- function. -- -- This is internal to a generated Haskell module, so it does not have a public -- (qualified) form. toHsClassDeleteFnPtrName' :: Class -> String toHsClassDeleteFnPtrName' cls = 'd':'e':'l':'e':'t':'e':'P':'t':'r':'\'':toHsDataTypeName' Nonconst cls -- | Returns the name of the Haskell function that invokes the given -- constructor. toHsCtorName :: Class -> Ctor -> LH.Generator String toHsCtorName cls ctor = LH.inFunction "toHsCtorName" $ toHsClassEntityName cls $ fromExtName $ ctorExtName ctor -- | Pure version of 'toHsCtorName' that doesn't create a qualified name. toHsCtorName' :: Class -> Ctor -> String toHsCtorName' cls ctor = toHsClassEntityName' cls $ fromExtName $ ctorExtName ctor -- | Returns the name of the Haskell function that invokes the given method. toHsMethodName :: Class -> Method -> LH.Generator String toHsMethodName cls method = LH.inFunction "toHsMethodName" $ toHsClassEntityName cls $ fromExtName $ methodExtName method -- | Pure version of 'toHsMethodName' that doesn't create a qualified name. toHsMethodName' :: Class -> Method -> String toHsMethodName' cls method = toHsClassEntityName' cls $ fromExtName $ methodExtName method -- | Returns the name of the Haskell function for an entity in a class. toHsClassEntityName :: IsFnName String name => Class -> name -> LH.Generator String toHsClassEntityName cls name = LH.addExtNameModule (classExtName cls) $ toHsClassEntityName' cls name -- | Pure version of 'toHsClassEntityName' that doesn't create a qualified name. toHsClassEntityName' :: IsFnName String name => Class -> name -> String toHsClassEntityName' cls name = lowerFirst $ fromExtName $ classEntityForeignName' cls $ case toFnName name of FnName name' -> toExtName name' FnOp op -> operatorPreferredExtName op -- | Generates C++ gateway functions (via 'Function.sayCppExportFn') for getting -- and setting a variable (possibly a class variable). sayCppExportVar :: Type -- ^ The type that the variable holds. -> Maybe (Type, Type) -- ^ @Nothing@ if the variable is not a class variable. If it is, then the -- first type is the generated getter's argument type for the object, and -- the second is the generated setter's argument type. For a class @cls@, -- this can be: -- -- > Just ('ptrT' $ 'constT' $ 'objT' cls, 'ptrT' $ 'objT' cls) -> Bool -- ^ Whether to generate a getter. Passing false here is useful when a -- variable's type can't be sensibly converted to a foreign language's -- value. -> ExtName -- ^ An external name from which to generate a getter function name. -> ExtName -- ^ An external name from which to generate a setter function name. -> LC.Generator () -- ^ A C++ generator that emits the variable name. -> LC.Generator () sayCppExportVar t maybeThisTypes gettable getterName setterName sayVarName = do let (isConst, deconstType) = case t of Internal_TConst t' -> (True, t') t' -> (False, t') -- Say a getter function. when gettable $ Function.sayCppExportFn getterName (Function.VarRead sayVarName) (fmap fst maybeThisTypes) [] deconstType mempty True -- Say a setter function. unless isConst $ Function.sayCppExportFn setterName (Function.VarWrite sayVarName) (fmap snd maybeThisTypes) [toParameter $ deconstType] voidT mempty True -- | Generates Haskell gateway functions (via 'Function.sayHsExportFn') for -- getting and setting a variable (possibly a class variable). sayHsExportVar :: LH.SayExportMode -- ^ The phase of code generation. -> Type -- ^ The type that the variable holds. -> Maybe Class -- ^ The type of the class holding the variable, if generating code for a -- class variable. -> Bool -- ^ Whether to generate a getter. Passing false here is useful when a -- variable's type can't be sensibly converted to a foreign language's -- value. -> ExtName -- ^ An external name for the getter. -> ExtName -- ^ A foreign external name for the getter. See 'Function.sayHsExportFn'. -> ExtName -- ^ An external name for the setter. -> ExtName -- ^ A foreign external name for the setter. See 'Function.sayHsExportFn'. -> LH.Generator () sayHsExportVar mode t classIfNonstatic gettable getterExtName getterForeignName setterExtName setterForeignName = do let (isConst, deconstType) = case t of Internal_TConst t' -> (True, t') t' -> (False, t') when gettable $ Function.sayHsExportFn mode getterExtName getterForeignName Nonpure (maybe [] (\cls -> [toParameter $ ptrT $ constT $ objT cls]) classIfNonstatic) deconstType mempty unless isConst $ Function.sayHsExportFn mode setterExtName setterForeignName Nonpure (maybe [toParameter deconstType] (\cls -> [toParameter $ ptrT $ objT cls, toParameter deconstType]) classIfNonstatic) voidT mempty