hoppy-generator 0.5.2 → 0.6.0
raw patch · 32 files changed
+8451/−5684 lines, 32 filesdep +bytestringdep +processdep +temporarydep ~containersdep ~mtlsetup-changedPVP ok
version bump matches the API change (PVP)
Dependencies added: bytestring, process, temporary, text
Dependency ranges changed: containers, mtl
API changes (from Hackage documentation)
- Foreign.Hoppy.Generator.Language.Cpp: callbackClassName :: Callback -> String
- Foreign.Hoppy.Generator.Language.Cpp: callbackFnName :: Callback -> String
- Foreign.Hoppy.Generator.Language.Cpp: callbackImplClassName :: Callback -> String
- Foreign.Hoppy.Generator.Language.Cpp: classCastFnCppName :: Class -> Class -> String
- Foreign.Hoppy.Generator.Language.Cpp: classDeleteFnCppName :: Class -> String
- Foreign.Hoppy.Generator.Language.Cpp: newtype Chunk
- Foreign.Hoppy.Generator.Language.Haskell: callbackToTFn :: HsTypeSide -> Callback -> Generator Type
- Foreign.Hoppy.Generator.Language.Haskell: toHsBitspaceClassName :: Bitspace -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsBitspaceClassName' :: Bitspace -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsBitspaceFromValueName :: Bitspace -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsBitspaceFromValueName' :: Bitspace -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsBitspaceToNumName :: Bitspace -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsBitspaceToNumName' :: Bitspace -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsBitspaceTypeName :: Bitspace -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsBitspaceTypeName' :: Bitspace -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsBitspaceValueName :: Bitspace -> [String] -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsBitspaceValueName' :: Bitspace -> [String] -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsCallbackCtorName :: Callback -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsCallbackCtorName' :: Callback -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsCallbackNewFunPtrFnName :: Callback -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsCallbackNewFunPtrFnName' :: Callback -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsCastMethodName :: Constness -> Class -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsCastMethodName' :: Constness -> Class -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsCastPrimitiveName :: Class -> Class -> Class -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsCastPrimitiveName' :: Class -> Class -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsClassDeleteFnName' :: Class -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsClassDeleteFnPtrName' :: Class -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsClassEntityName :: IsFnName String name => Class -> name -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsClassEntityName' :: IsFnName String name => Class -> name -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsConstCastFnName :: Constness -> Class -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsConstCastFnName' :: Constness -> Class -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsCtorName :: Class -> Ctor -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsCtorName' :: Class -> Ctor -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsDataCtorName :: Managed -> Constness -> Class -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsDataCtorName' :: Managed -> Constness -> Class -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsDataTypeName :: Constness -> Class -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsDataTypeName' :: Constness -> Class -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsDownCastClassName :: Constness -> Class -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsDownCastClassName' :: Constness -> Class -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsDownCastMethodName :: Constness -> Class -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsDownCastMethodName' :: Constness -> Class -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsEnumCtorName :: CppEnum -> [String] -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsEnumCtorName' :: CppEnum -> [String] -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsEnumTypeName :: CppEnum -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsEnumTypeName' :: CppEnum -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsMethodName :: Class -> Method -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsMethodName' :: Class -> Method -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsPtrClassName :: Constness -> Class -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsPtrClassName' :: Constness -> Class -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsValueClassName :: Class -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsValueClassName' :: Class -> String
- Foreign.Hoppy.Generator.Language.Haskell: toHsWithValuePtrName :: Class -> Generator String
- Foreign.Hoppy.Generator.Language.Haskell: toHsWithValuePtrName' :: Class -> String
- Foreign.Hoppy.Generator.Spec: CECtor :: Ctor -> ClassEntity
- Foreign.Hoppy.Generator.Spec: CEMethod :: Method -> ClassEntity
- Foreign.Hoppy.Generator.Spec: CEProp :: Prop -> ClassEntity
- Foreign.Hoppy.Generator.Spec: CEVar :: ClassVariable -> ClassEntity
- Foreign.Hoppy.Generator.Spec: ClassConversion :: ClassHaskellConversion -> ClassConversion
- Foreign.Hoppy.Generator.Spec: ClassHaskellConversion :: Maybe (Generator HsType) -> Maybe (Generator ()) -> Maybe (Generator ()) -> ClassHaskellConversion
- Foreign.Hoppy.Generator.Spec: ExportBitspace :: Bitspace -> Export
- Foreign.Hoppy.Generator.Spec: ExportCallback :: Callback -> Export
- Foreign.Hoppy.Generator.Spec: ExportClass :: Class -> Export
- Foreign.Hoppy.Generator.Spec: ExportEnum :: CppEnum -> Export
- Foreign.Hoppy.Generator.Spec: ExportFn :: Function -> Export
- Foreign.Hoppy.Generator.Spec: ExportVariable :: Variable -> Export
- Foreign.Hoppy.Generator.Spec: FnMethod :: (FnName Identifier) -> MethodImpl
- Foreign.Hoppy.Generator.Spec: Internal_TBitspace :: Bitspace -> Type
- Foreign.Hoppy.Generator.Spec: Internal_TBool :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TCallback :: Callback -> Type
- Foreign.Hoppy.Generator.Spec: Internal_TChar :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TDouble :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TEnum :: CppEnum -> Type
- Foreign.Hoppy.Generator.Spec: Internal_TFloat :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TInt :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TInt16 :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TInt32 :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TInt64 :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TInt8 :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TLLong :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TLong :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TPtrdiff :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TSSize :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TShort :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TSize :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TUChar :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TUInt :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TULLong :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TULong :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TUShort :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TWord16 :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TWord32 :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TWord64 :: Type
- Foreign.Hoppy.Generator.Spec: Internal_TWord8 :: Type
- Foreign.Hoppy.Generator.Spec: MConst :: MethodApplicability
- Foreign.Hoppy.Generator.Spec: MNormal :: MethodApplicability
- Foreign.Hoppy.Generator.Spec: MStatic :: MethodApplicability
- Foreign.Hoppy.Generator.Spec: Nonstatic :: Staticness
- Foreign.Hoppy.Generator.Spec: RealMethod :: (FnName String) -> MethodImpl
- Foreign.Hoppy.Generator.Spec: Static :: Staticness
- Foreign.Hoppy.Generator.Spec: [classHaskellConversionFromCppFn] :: ClassHaskellConversion -> Maybe (Generator ())
- Foreign.Hoppy.Generator.Spec: [classHaskellConversionToCppFn] :: ClassHaskellConversion -> Maybe (Generator ())
- Foreign.Hoppy.Generator.Spec: [classHaskellConversionType] :: ClassHaskellConversion -> Maybe (Generator HsType)
- Foreign.Hoppy.Generator.Spec: [classHaskellConversion] :: ClassConversion -> ClassHaskellConversion
- Foreign.Hoppy.Generator.Spec: bitspaceAddCppType :: Identifier -> Maybe String -> Maybe String -> Bitspace -> Bitspace
- Foreign.Hoppy.Generator.Spec: bitspaceAddEnum :: CppEnum -> Bitspace -> Bitspace
- Foreign.Hoppy.Generator.Spec: bitspaceCppTypeIdentifier :: Bitspace -> Maybe Identifier
- Foreign.Hoppy.Generator.Spec: bitspaceEnum :: Bitspace -> Maybe CppEnum
- Foreign.Hoppy.Generator.Spec: bitspaceExtName :: Bitspace -> ExtName
- Foreign.Hoppy.Generator.Spec: bitspaceFromCppValueFn :: Bitspace -> Maybe String
- Foreign.Hoppy.Generator.Spec: bitspaceReqs :: Bitspace -> Reqs
- Foreign.Hoppy.Generator.Spec: bitspaceSetValuePrefix :: String -> Bitspace -> Bitspace
- Foreign.Hoppy.Generator.Spec: bitspaceToCppValueFn :: Bitspace -> Maybe String
- Foreign.Hoppy.Generator.Spec: bitspaceType :: Bitspace -> Type
- Foreign.Hoppy.Generator.Spec: bitspaceValueNames :: Bitspace -> [(Int, [String])]
- Foreign.Hoppy.Generator.Spec: bitspaceValuePrefix :: Bitspace -> String
- Foreign.Hoppy.Generator.Spec: callbackExtName :: Callback -> ExtName
- Foreign.Hoppy.Generator.Spec: callbackParams :: Callback -> [Type]
- Foreign.Hoppy.Generator.Spec: callbackReqs :: Callback -> Reqs
- Foreign.Hoppy.Generator.Spec: callbackReturn :: Callback -> Type
- Foreign.Hoppy.Generator.Spec: callbackSetThrows :: Bool -> Callback -> Callback
- Foreign.Hoppy.Generator.Spec: callbackThrows :: Callback -> Maybe Bool
- Foreign.Hoppy.Generator.Spec: class IsClassEntity a
- Foreign.Hoppy.Generator.Spec: classAddEntities :: [ClassEntity] -> Class -> Class
- Foreign.Hoppy.Generator.Spec: classConversion :: Class -> ClassConversion
- Foreign.Hoppy.Generator.Spec: classConversionNone :: ClassConversion
- Foreign.Hoppy.Generator.Spec: classCtors :: Class -> [Ctor]
- Foreign.Hoppy.Generator.Spec: classDtorIsPublic :: Class -> Bool
- Foreign.Hoppy.Generator.Spec: classEntities :: Class -> [ClassEntity]
- Foreign.Hoppy.Generator.Spec: classEntityExtName :: IsClassEntity a => Class -> a -> ExtName
- Foreign.Hoppy.Generator.Spec: classEntityExtNameSuffix :: IsClassEntity a => a -> ExtName
- Foreign.Hoppy.Generator.Spec: classEntityForeignName :: IsClassEntity a => Class -> a -> ExtName
- Foreign.Hoppy.Generator.Spec: classEntityForeignName' :: Class -> ExtName -> ExtName
- Foreign.Hoppy.Generator.Spec: classEntityPrefix :: Class -> String
- Foreign.Hoppy.Generator.Spec: classExtName :: Class -> ExtName
- Foreign.Hoppy.Generator.Spec: classFindCopyCtor :: Class -> Maybe Ctor
- Foreign.Hoppy.Generator.Spec: classHaskellConversionNone :: ClassHaskellConversion
- Foreign.Hoppy.Generator.Spec: classIdentifier :: Class -> Identifier
- Foreign.Hoppy.Generator.Spec: classIsException :: Class -> Bool
- Foreign.Hoppy.Generator.Spec: classIsMonomorphicSuperclass :: Class -> Bool
- Foreign.Hoppy.Generator.Spec: classIsSubclassOfMonomorphic :: Class -> Bool
- Foreign.Hoppy.Generator.Spec: classMakeException :: Class -> Class
- Foreign.Hoppy.Generator.Spec: classMethods :: Class -> [Method]
- Foreign.Hoppy.Generator.Spec: classModifyConversion :: (ClassConversion -> ClassConversion) -> Class -> Class
- Foreign.Hoppy.Generator.Spec: classReqs :: Class -> Reqs
- Foreign.Hoppy.Generator.Spec: classSetConversion :: ClassConversion -> Class -> Class
- Foreign.Hoppy.Generator.Spec: classSetDtorPrivate :: Class -> Class
- Foreign.Hoppy.Generator.Spec: classSetEntityPrefix :: String -> Class -> Class
- Foreign.Hoppy.Generator.Spec: classSetHaskellConversion :: ClassHaskellConversion -> Class -> Class
- Foreign.Hoppy.Generator.Spec: classSetMonomorphicSuperclass :: Class -> Class
- Foreign.Hoppy.Generator.Spec: classSetSubclassOfMonomorphic :: Class -> Class
- Foreign.Hoppy.Generator.Spec: classSuperclasses :: Class -> [Class]
- Foreign.Hoppy.Generator.Spec: classVarCName :: ClassVariable -> String
- Foreign.Hoppy.Generator.Spec: classVarExtName :: ClassVariable -> ExtName
- Foreign.Hoppy.Generator.Spec: classVarGettable :: ClassVariable -> Bool
- Foreign.Hoppy.Generator.Spec: classVarGetterExtName :: Class -> ClassVariable -> ExtName
- Foreign.Hoppy.Generator.Spec: classVarGetterForeignName :: Class -> ClassVariable -> ExtName
- Foreign.Hoppy.Generator.Spec: classVarSetterExtName :: Class -> ClassVariable -> ExtName
- Foreign.Hoppy.Generator.Spec: classVarSetterForeignName :: Class -> ClassVariable -> ExtName
- Foreign.Hoppy.Generator.Spec: classVarStatic :: ClassVariable -> Staticness
- Foreign.Hoppy.Generator.Spec: classVarType :: ClassVariable -> Type
- Foreign.Hoppy.Generator.Spec: classVariables :: Class -> [ClassVariable]
- Foreign.Hoppy.Generator.Spec: ctorExceptionHandlers :: Ctor -> ExceptionHandlers
- Foreign.Hoppy.Generator.Spec: ctorExtName :: Ctor -> ExtName
- Foreign.Hoppy.Generator.Spec: ctorParams :: Ctor -> [Type]
- Foreign.Hoppy.Generator.Spec: data Addendum
- Foreign.Hoppy.Generator.Spec: data Bitspace
- Foreign.Hoppy.Generator.Spec: data Callback
- Foreign.Hoppy.Generator.Spec: data Class
- Foreign.Hoppy.Generator.Spec: data ClassConversion
- Foreign.Hoppy.Generator.Spec: data ClassEntity
- Foreign.Hoppy.Generator.Spec: data ClassHaskellConversion
- Foreign.Hoppy.Generator.Spec: data ClassVariable
- Foreign.Hoppy.Generator.Spec: data CppEnum
- Foreign.Hoppy.Generator.Spec: data Ctor
- Foreign.Hoppy.Generator.Spec: data ExceptionHandlers
- Foreign.Hoppy.Generator.Spec: data Function
- Foreign.Hoppy.Generator.Spec: data InterfaceOptions
- Foreign.Hoppy.Generator.Spec: data Method
- Foreign.Hoppy.Generator.Spec: data MethodApplicability
- Foreign.Hoppy.Generator.Spec: data MethodImpl
- Foreign.Hoppy.Generator.Spec: data Prop
- Foreign.Hoppy.Generator.Spec: data Staticness
- Foreign.Hoppy.Generator.Spec: data Variable
- Foreign.Hoppy.Generator.Spec: enumExtName :: CppEnum -> ExtName
- Foreign.Hoppy.Generator.Spec: enumIdentifier :: CppEnum -> Identifier
- Foreign.Hoppy.Generator.Spec: enumReqs :: CppEnum -> Reqs
- Foreign.Hoppy.Generator.Spec: enumSetValuePrefix :: String -> CppEnum -> CppEnum
- Foreign.Hoppy.Generator.Spec: enumValueNames :: CppEnum -> [(Int, [String])]
- Foreign.Hoppy.Generator.Spec: enumValuePrefix :: CppEnum -> String
- Foreign.Hoppy.Generator.Spec: exportAddendum :: Export -> Addendum
- Foreign.Hoppy.Generator.Spec: fnCName :: Function -> FnName Identifier
- Foreign.Hoppy.Generator.Spec: fnExceptionHandlers :: Function -> ExceptionHandlers
- Foreign.Hoppy.Generator.Spec: fnExtName :: Function -> ExtName
- Foreign.Hoppy.Generator.Spec: fnParams :: Function -> [Type]
- Foreign.Hoppy.Generator.Spec: fnPurity :: Function -> Purity
- Foreign.Hoppy.Generator.Spec: fnReqs :: Function -> Reqs
- Foreign.Hoppy.Generator.Spec: fnReturn :: Function -> Type
- Foreign.Hoppy.Generator.Spec: hsImportForTypeable :: HsImportSet
- Foreign.Hoppy.Generator.Spec: makeBitspace :: ExtName -> Type -> [(Int, [String])] -> Bitspace
- Foreign.Hoppy.Generator.Spec: makeCallback :: ExtName -> [Type] -> Type -> Callback
- Foreign.Hoppy.Generator.Spec: makeClass :: Identifier -> Maybe ExtName -> [Class] -> [ClassEntity] -> Class
- Foreign.Hoppy.Generator.Spec: makeClassVariable :: String -> Maybe ExtName -> Type -> Staticness -> Bool -> ClassEntity
- Foreign.Hoppy.Generator.Spec: makeClassVariable_ :: String -> Maybe ExtName -> Type -> Staticness -> Bool -> ClassVariable
- Foreign.Hoppy.Generator.Spec: makeCtor :: ExtName -> [Type] -> ClassEntity
- Foreign.Hoppy.Generator.Spec: makeCtor_ :: ExtName -> [Type] -> Ctor
- Foreign.Hoppy.Generator.Spec: makeEnum :: Identifier -> Maybe ExtName -> [(Int, [String])] -> CppEnum
- Foreign.Hoppy.Generator.Spec: makeFn :: IsFnName Identifier name => name -> Maybe ExtName -> Purity -> [Type] -> Type -> Function
- Foreign.Hoppy.Generator.Spec: makeFnMethod :: IsFnName Identifier name => name -> String -> MethodApplicability -> Purity -> [Type] -> Type -> ClassEntity
- Foreign.Hoppy.Generator.Spec: makeFnMethod_ :: IsFnName Identifier name => name -> String -> MethodApplicability -> Purity -> [Type] -> Type -> Method
- Foreign.Hoppy.Generator.Spec: makeMethod :: IsFnName String name => name -> ExtName -> MethodApplicability -> Purity -> [Type] -> Type -> ClassEntity
- Foreign.Hoppy.Generator.Spec: makeMethod_ :: IsFnName String name => name -> ExtName -> MethodApplicability -> Purity -> [Type] -> Type -> Method
- Foreign.Hoppy.Generator.Spec: makeVariable :: Identifier -> Maybe ExtName -> Type -> Variable
- Foreign.Hoppy.Generator.Spec: methodApplicability :: Method -> MethodApplicability
- Foreign.Hoppy.Generator.Spec: methodConst :: Method -> Constness
- Foreign.Hoppy.Generator.Spec: methodExceptionHandlers :: Method -> ExceptionHandlers
- Foreign.Hoppy.Generator.Spec: methodExtName :: Method -> ExtName
- Foreign.Hoppy.Generator.Spec: methodImpl :: Method -> MethodImpl
- Foreign.Hoppy.Generator.Spec: methodParams :: Method -> [Type]
- Foreign.Hoppy.Generator.Spec: methodPurity :: Method -> Purity
- Foreign.Hoppy.Generator.Spec: methodReturn :: Method -> Type
- Foreign.Hoppy.Generator.Spec: methodStatic :: Method -> Staticness
- Foreign.Hoppy.Generator.Spec: mkBoolHasProp :: String -> ClassEntity
- Foreign.Hoppy.Generator.Spec: mkBoolHasProp_ :: String -> Prop
- Foreign.Hoppy.Generator.Spec: mkBoolIsProp :: String -> ClassEntity
- Foreign.Hoppy.Generator.Spec: mkBoolIsProp_ :: String -> Prop
- Foreign.Hoppy.Generator.Spec: mkClassVariable :: String -> Type -> ClassEntity
- Foreign.Hoppy.Generator.Spec: mkClassVariable_ :: String -> Type -> ClassVariable
- Foreign.Hoppy.Generator.Spec: mkConstMethod :: IsFnName String name => name -> [Type] -> Type -> ClassEntity
- Foreign.Hoppy.Generator.Spec: mkConstMethod' :: IsFnName String name => name -> String -> [Type] -> Type -> ClassEntity
- Foreign.Hoppy.Generator.Spec: mkConstMethod'_ :: IsFnName String name => name -> String -> [Type] -> Type -> Method
- Foreign.Hoppy.Generator.Spec: mkConstMethod_ :: IsFnName String name => name -> [Type] -> Type -> Method
- Foreign.Hoppy.Generator.Spec: mkCtor :: String -> [Type] -> ClassEntity
- Foreign.Hoppy.Generator.Spec: mkCtor_ :: String -> [Type] -> Ctor
- Foreign.Hoppy.Generator.Spec: mkMethod :: IsFnName String name => name -> [Type] -> Type -> ClassEntity
- Foreign.Hoppy.Generator.Spec: mkMethod' :: IsFnName String name => name -> String -> [Type] -> Type -> ClassEntity
- Foreign.Hoppy.Generator.Spec: mkMethod'_ :: IsFnName String name => name -> String -> [Type] -> Type -> Method
- Foreign.Hoppy.Generator.Spec: mkMethod_ :: IsFnName String name => name -> [Type] -> Type -> Method
- Foreign.Hoppy.Generator.Spec: mkProp :: String -> Type -> ClassEntity
- Foreign.Hoppy.Generator.Spec: mkProp_ :: String -> Type -> Prop
- Foreign.Hoppy.Generator.Spec: mkStaticClassVariable :: String -> Type -> ClassEntity
- Foreign.Hoppy.Generator.Spec: mkStaticClassVariable_ :: String -> Type -> ClassVariable
- Foreign.Hoppy.Generator.Spec: mkStaticMethod :: IsFnName String name => name -> [Type] -> Type -> ClassEntity
- Foreign.Hoppy.Generator.Spec: mkStaticMethod' :: IsFnName String name => name -> String -> [Type] -> Type -> ClassEntity
- Foreign.Hoppy.Generator.Spec: mkStaticMethod'_ :: IsFnName String name => name -> String -> [Type] -> Type -> Method
- Foreign.Hoppy.Generator.Spec: mkStaticMethod_ :: IsFnName String name => name -> [Type] -> Type -> Method
- Foreign.Hoppy.Generator.Spec: mkStaticProp :: String -> Type -> ClassEntity
- Foreign.Hoppy.Generator.Spec: mkStaticProp_ :: String -> Type -> Prop
- Foreign.Hoppy.Generator.Spec: varExtName :: Variable -> ExtName
- Foreign.Hoppy.Generator.Spec: varGetterExtName :: Variable -> ExtName
- Foreign.Hoppy.Generator.Spec: varIdentifier :: Variable -> Identifier
- Foreign.Hoppy.Generator.Spec: varIsConst :: Variable -> Bool
- Foreign.Hoppy.Generator.Spec: varReqs :: Variable -> Reqs
- Foreign.Hoppy.Generator.Spec: varSetterExtName :: Variable -> ExtName
- Foreign.Hoppy.Generator.Spec: varType :: Variable -> Type
- Foreign.Hoppy.Generator.Types: bitspaceT :: Bitspace -> Type
+ Foreign.Hoppy.Generator.Compiler: CustomCompiler :: String -> (FilePath -> FilePath -> IO Bool) -> CustomCompiler
+ Foreign.Hoppy.Generator.Compiler: SimpleCompiler :: FilePath -> [String] -> SimpleCompiler
+ Foreign.Hoppy.Generator.Compiler: SomeCompiler :: a -> SomeCompiler
+ Foreign.Hoppy.Generator.Compiler: [ccCompile] :: CustomCompiler -> FilePath -> FilePath -> IO Bool
+ Foreign.Hoppy.Generator.Compiler: [ccLabel] :: CustomCompiler -> String
+ Foreign.Hoppy.Generator.Compiler: [scArguments] :: SimpleCompiler -> [String]
+ Foreign.Hoppy.Generator.Compiler: [scProgram] :: SimpleCompiler -> FilePath
+ Foreign.Hoppy.Generator.Compiler: appendArguments :: [String] -> SimpleCompiler -> SimpleCompiler
+ Foreign.Hoppy.Generator.Compiler: class Show a => Compiler a
+ Foreign.Hoppy.Generator.Compiler: compileProgram :: Compiler a => a -> FilePath -> FilePath -> IO Bool
+ Foreign.Hoppy.Generator.Compiler: data CustomCompiler
+ Foreign.Hoppy.Generator.Compiler: data SimpleCompiler
+ Foreign.Hoppy.Generator.Compiler: data SomeCompiler
+ Foreign.Hoppy.Generator.Compiler: defaultCompiler :: SimpleCompiler
+ Foreign.Hoppy.Generator.Compiler: gppCompiler :: SimpleCompiler
+ Foreign.Hoppy.Generator.Compiler: instance Foreign.Hoppy.Generator.Compiler.Compiler Foreign.Hoppy.Generator.Compiler.CustomCompiler
+ Foreign.Hoppy.Generator.Compiler: instance Foreign.Hoppy.Generator.Compiler.Compiler Foreign.Hoppy.Generator.Compiler.SimpleCompiler
+ Foreign.Hoppy.Generator.Compiler: instance Foreign.Hoppy.Generator.Compiler.Compiler Foreign.Hoppy.Generator.Compiler.SomeCompiler
+ Foreign.Hoppy.Generator.Compiler: instance GHC.Show.Show Foreign.Hoppy.Generator.Compiler.CustomCompiler
+ Foreign.Hoppy.Generator.Compiler: instance GHC.Show.Show Foreign.Hoppy.Generator.Compiler.SimpleCompiler
+ Foreign.Hoppy.Generator.Compiler: instance GHC.Show.Show Foreign.Hoppy.Generator.Compiler.SomeCompiler
+ Foreign.Hoppy.Generator.Compiler: overrideCompilerFromEnvironment :: SimpleCompiler -> IO SimpleCompiler
+ Foreign.Hoppy.Generator.Compiler: prependArguments :: [String] -> SimpleCompiler -> SimpleCompiler
+ Foreign.Hoppy.Generator.Hook: EnumEvaluatorArgs :: Interface -> Reqs -> [Identifier] -> [Identifier] -> Bool -> EnumEvaluatorArgs
+ Foreign.Hoppy.Generator.Hook: EnumEvaluatorResult :: ![Int] -> ![Integer] -> EnumEvaluatorResult
+ Foreign.Hoppy.Generator.Hook: Hooks :: EnumEvaluator -> Hooks
+ Foreign.Hoppy.Generator.Hook: [enumEvaluatorArgsEntryIdentifiers] :: EnumEvaluatorArgs -> [Identifier]
+ Foreign.Hoppy.Generator.Hook: [enumEvaluatorArgsInterface] :: EnumEvaluatorArgs -> Interface
+ Foreign.Hoppy.Generator.Hook: [enumEvaluatorArgsKeepOutputsOnFailure] :: EnumEvaluatorArgs -> Bool
+ Foreign.Hoppy.Generator.Hook: [enumEvaluatorArgsReqs] :: EnumEvaluatorArgs -> Reqs
+ Foreign.Hoppy.Generator.Hook: [enumEvaluatorArgsSizeofIdentifiers] :: EnumEvaluatorArgs -> [Identifier]
+ Foreign.Hoppy.Generator.Hook: [enumEvaluatorResultSizes] :: EnumEvaluatorResult -> ![Int]
+ Foreign.Hoppy.Generator.Hook: [enumEvaluatorResultValues] :: EnumEvaluatorResult -> ![Integer]
+ Foreign.Hoppy.Generator.Hook: [hookEvaluateEnums] :: Hooks -> EnumEvaluator
+ Foreign.Hoppy.Generator.Hook: data EnumEvaluatorArgs
+ Foreign.Hoppy.Generator.Hook: data EnumEvaluatorResult
+ Foreign.Hoppy.Generator.Hook: data Hooks
+ Foreign.Hoppy.Generator.Hook: defaultHooks :: Hooks
+ Foreign.Hoppy.Generator.Hook: evaluateEnumsWithCompiler :: Compiler a => a -> EnumEvaluator
+ Foreign.Hoppy.Generator.Hook: evaluateEnumsWithDefaultCompiler :: EnumEvaluator
+ Foreign.Hoppy.Generator.Hook: instance GHC.Classes.Eq Foreign.Hoppy.Generator.Hook.OrdIdentifier
+ Foreign.Hoppy.Generator.Hook: instance GHC.Classes.Ord Foreign.Hoppy.Generator.Hook.OrdIdentifier
+ Foreign.Hoppy.Generator.Hook: instance GHC.Show.Show Foreign.Hoppy.Generator.Hook.EnumEvaluatorResult
+ Foreign.Hoppy.Generator.Hook: instance GHC.Show.Show Foreign.Hoppy.Generator.Hook.OrdIdentifier
+ Foreign.Hoppy.Generator.Hook: internalEvaluateEnumsForInterface :: Interface -> Bool -> IO Interface
+ Foreign.Hoppy.Generator.Hook: interpretOutputToEvaluateEnums :: EnumEvaluatorArgs -> String -> Either String EnumEvaluatorResult
+ Foreign.Hoppy.Generator.Hook: makeCppSourceToEvaluateEnums :: EnumEvaluatorArgs -> ByteString
+ Foreign.Hoppy.Generator.Hook: type EnumEvaluator = EnumEvaluatorArgs -> IO (Maybe EnumEvaluatorResult)
+ Foreign.Hoppy.Generator.Language.Cpp: SayHeader :: SayExportMode
+ Foreign.Hoppy.Generator.Language.Cpp: SaySource :: SayExportMode
+ Foreign.Hoppy.Generator.Language.Cpp: [chunkIncludes] :: Chunk -> !Set Include
+ Foreign.Hoppy.Generator.Language.Cpp: abort :: ErrorMsg -> Generator a
+ Foreign.Hoppy.Generator.Language.Cpp: addInclude :: MonadWriter [Chunk] m => Include -> m ()
+ Foreign.Hoppy.Generator.Language.Cpp: addIncludes :: MonadWriter [Chunk] m => [Include] -> m ()
+ Foreign.Hoppy.Generator.Language.Cpp: addReqsM :: MonadWriter [Chunk] m => Reqs -> m ()
+ Foreign.Hoppy.Generator.Language.Cpp: askInterface :: MonadReader Env m => m Interface
+ Foreign.Hoppy.Generator.Language.Cpp: askModule :: MonadReader Env m => m Module
+ Foreign.Hoppy.Generator.Language.Cpp: codeChunk :: String -> Chunk
+ Foreign.Hoppy.Generator.Language.Cpp: data Chunk
+ Foreign.Hoppy.Generator.Language.Cpp: data Env
+ Foreign.Hoppy.Generator.Language.Cpp: data SayExportMode
+ Foreign.Hoppy.Generator.Language.Cpp: execGenerator :: Interface -> Module -> Maybe String -> Generator a -> Either ErrorMsg String
+ Foreign.Hoppy.Generator.Language.Cpp: findExportModule :: ExtName -> Generator Module
+ Foreign.Hoppy.Generator.Language.Cpp: getEffectiveExceptionHandlers :: ExceptionHandlers -> Generator ExceptionHandlers
+ Foreign.Hoppy.Generator.Language.Cpp: includesChunk :: Set Include -> Chunk
+ Foreign.Hoppy.Generator.Language.Cpp: makeCppName :: [String] -> String
+ Foreign.Hoppy.Generator.Language.Cpp: renderIdentifier :: Identifier -> String
+ Foreign.Hoppy.Generator.Language.Cpp: sayFunction :: String -> [String] -> Type -> Maybe (Generator ()) -> Generator ()
+ Foreign.Hoppy.Generator.Language.Cpp: type Generator = ReaderT Env (WriterT [Chunk] (Either ErrorMsg))
+ Foreign.Hoppy.Generator.Language.Cpp: typeReqs :: Type -> Generator Reqs
+ Foreign.Hoppy.Generator.Language.Cpp: typeToCType :: Type -> Generator (Maybe Type)
+ Foreign.Hoppy.Generator.Language.Haskell: Env :: Interface -> Module -> String -> Env
+ Foreign.Hoppy.Generator.Language.Haskell: SayExportBoot :: SayExportMode
+ Foreign.Hoppy.Generator.Language.Haskell: SayExportDecls :: SayExportMode
+ Foreign.Hoppy.Generator.Language.Haskell: SayExportForeignImports :: SayExportMode
+ Foreign.Hoppy.Generator.Language.Haskell: [envInterface] :: Env -> Interface
+ Foreign.Hoppy.Generator.Language.Haskell: [envModuleName] :: Env -> String
+ Foreign.Hoppy.Generator.Language.Haskell: [envModule] :: Env -> Module
+ Foreign.Hoppy.Generator.Language.Haskell: addExtNameModule :: ExtName -> String -> Generator String
+ Foreign.Hoppy.Generator.Language.Haskell: data Env
+ Foreign.Hoppy.Generator.Language.Haskell: data SayExportMode
+ Foreign.Hoppy.Generator.Language.Haskell: getEffectiveExceptionHandlers :: ExceptionHandlers -> Generator ExceptionHandlers
+ Foreign.Hoppy.Generator.Language.Haskell: getExtNameModule :: ExtName -> Generator Module
+ Foreign.Hoppy.Generator.Language.Haskell: instance GHC.Classes.Eq Foreign.Hoppy.Generator.Language.Haskell.SayExportMode
+ Foreign.Hoppy.Generator.Language.Haskell: instance GHC.Show.Show Foreign.Hoppy.Generator.Language.Haskell.SayExportMode
+ Foreign.Hoppy.Generator.Language.Haskell: toHsTypeName :: Constness -> ExtName -> Generator String
+ Foreign.Hoppy.Generator.Language.Haskell: toHsTypeName' :: Constness -> ExtName -> String
+ Foreign.Hoppy.Generator.Main: DumpEnums :: Action
+ Foreign.Hoppy.Generator.Main: DumpExtNames :: Action
+ Foreign.Hoppy.Generator.Main: KeepTempOutputsOnFailure :: Action
+ Foreign.Hoppy.Generator.Override: addOverrideMap :: (Ord p, Ord k, Show p, Show k) => p -> Map k v -> MapWithOverrides p k v -> MapWithOverrides p k v
+ Foreign.Hoppy.Generator.Override: addOverrideMaps :: (Ord p, Ord k, Show p, Show k) => Map p (Map k v) -> MapWithOverrides p k v -> MapWithOverrides p k v
+ Foreign.Hoppy.Generator.Override: applyOverrideMaps :: (Ord p, Ord k, Show p, Show k) => Map p (Map k v) -> Map k v -> MapWithOverrides p k v
+ Foreign.Hoppy.Generator.Override: data MapWithOverrides p k v
+ Foreign.Hoppy.Generator.Override: data WithOverrides p v
+ Foreign.Hoppy.Generator.Override: insertMapOverride :: (Ord p, Ord k, Show p, Show k) => p -> k -> v -> MapWithOverrides p k v -> MapWithOverrides p k v
+ Foreign.Hoppy.Generator.Override: mapWithOverrides :: Map k (WithOverrides p v) -> MapWithOverrides p k v
+ Foreign.Hoppy.Generator.Override: overridden :: v -> Map p v -> WithOverrides p v
+ Foreign.Hoppy.Generator.Override: overriddenMapLookup :: (Ord p, Ord k) => p -> k -> MapWithOverrides p k v -> Maybe v
+ Foreign.Hoppy.Generator.Override: overriddenValues :: WithOverrides p v -> Map p v
+ Foreign.Hoppy.Generator.Override: plain :: v -> WithOverrides p v
+ Foreign.Hoppy.Generator.Override: plainMap :: Map k v -> MapWithOverrides p k v
+ Foreign.Hoppy.Generator.Override: unoverriddenValue :: WithOverrides p v -> v
+ Foreign.Hoppy.Generator.Spec: (~:) :: IsParameter a => String -> a -> Parameter
+ Foreign.Hoppy.Generator.Spec: BinaryCompatible :: ConversionMethod c
+ Foreign.Hoppy.Generator.Spec: ConversionSpecCpp :: String -> Generator Reqs -> Generator (Maybe Type) -> Maybe (Generator () -> Maybe (Generator ()) -> Generator ()) -> Maybe (Generator () -> Maybe (Generator ()) -> Generator ()) -> ConversionSpecCpp
+ Foreign.Hoppy.Generator.Spec: ConversionSpecHaskell :: Generator HsType -> Maybe (HsName -> Generator HsQualType) -> Maybe (Generator HsType) -> ConversionMethod (Generator ()) -> ConversionMethod (Generator ()) -> ConversionSpecHaskell
+ Foreign.Hoppy.Generator.Spec: ConversionUnsupported :: ConversionMethod c
+ Foreign.Hoppy.Generator.Spec: CustomConversion :: c -> ConversionMethod c
+ Foreign.Hoppy.Generator.Spec: EnumInfo :: ExtName -> Identifier -> Maybe Type -> Reqs -> EnumValueMap -> EnumInfo
+ Foreign.Hoppy.Generator.Spec: EnumValueAuto :: Identifier -> EnumValue
+ Foreign.Hoppy.Generator.Spec: EnumValueManual :: Integer -> EnumValue
+ Foreign.Hoppy.Generator.Spec: EnumValueMap :: [EnumEntryWords] -> MapWithForeignLanguageOverrides EnumEntryWords EnumEntryWords -> Map EnumEntryWords EnumValue -> EnumValueMap
+ Foreign.Hoppy.Generator.Spec: EvaluatedEnumData :: Type -> EvaluatedEnumValueMap -> EvaluatedEnumData
+ Foreign.Hoppy.Generator.Spec: Export :: a -> Export
+ Foreign.Hoppy.Generator.Spec: Haskell :: ForeignLanguage
+ Foreign.Hoppy.Generator.Spec: Internal_TManual :: ConversionSpec -> Type
+ Foreign.Hoppy.Generator.Spec: [conversionSpecCppConversionFromCppExpr] :: ConversionSpecCpp -> Maybe (Generator () -> Maybe (Generator ()) -> Generator ())
+ Foreign.Hoppy.Generator.Spec: [conversionSpecCppConversionToCppExpr] :: ConversionSpecCpp -> Maybe (Generator () -> Maybe (Generator ()) -> Generator ())
+ Foreign.Hoppy.Generator.Spec: [conversionSpecCppConversionType] :: ConversionSpecCpp -> Generator (Maybe Type)
+ Foreign.Hoppy.Generator.Spec: [conversionSpecCppName] :: ConversionSpecCpp -> String
+ Foreign.Hoppy.Generator.Spec: [conversionSpecCppReqs] :: ConversionSpecCpp -> Generator Reqs
+ Foreign.Hoppy.Generator.Spec: [conversionSpecHaskellCType] :: ConversionSpecHaskell -> Maybe (Generator HsType)
+ Foreign.Hoppy.Generator.Spec: [conversionSpecHaskellFromCppFn] :: ConversionSpecHaskell -> ConversionMethod (Generator ())
+ Foreign.Hoppy.Generator.Spec: [conversionSpecHaskellHsArgType] :: ConversionSpecHaskell -> Maybe (HsName -> Generator HsQualType)
+ Foreign.Hoppy.Generator.Spec: [conversionSpecHaskellHsType] :: ConversionSpecHaskell -> Generator HsType
+ Foreign.Hoppy.Generator.Spec: [conversionSpecHaskellToCppFn] :: ConversionSpecHaskell -> ConversionMethod (Generator ())
+ Foreign.Hoppy.Generator.Spec: [enumInfoExtName] :: EnumInfo -> ExtName
+ Foreign.Hoppy.Generator.Spec: [enumInfoIdentifier] :: EnumInfo -> Identifier
+ Foreign.Hoppy.Generator.Spec: [enumInfoNumericType] :: EnumInfo -> Maybe Type
+ Foreign.Hoppy.Generator.Spec: [enumInfoReqs] :: EnumInfo -> Reqs
+ Foreign.Hoppy.Generator.Spec: [enumInfoValues] :: EnumInfo -> EnumValueMap
+ Foreign.Hoppy.Generator.Spec: [enumValueMapForeignNames] :: EnumValueMap -> MapWithForeignLanguageOverrides EnumEntryWords EnumEntryWords
+ Foreign.Hoppy.Generator.Spec: [enumValueMapNames] :: EnumValueMap -> [EnumEntryWords]
+ Foreign.Hoppy.Generator.Spec: [enumValueMapValues] :: EnumValueMap -> Map EnumEntryWords EnumValue
+ Foreign.Hoppy.Generator.Spec: [evaluatedEnumType] :: EvaluatedEnumData -> Type
+ Foreign.Hoppy.Generator.Spec: [evaluatedEnumValueMap] :: EvaluatedEnumData -> EvaluatedEnumValueMap
+ Foreign.Hoppy.Generator.Spec: castExport :: (Exportable a, Typeable a, Exportable b, Typeable b) => a -> Maybe b
+ Foreign.Hoppy.Generator.Spec: class (HasAddendum a, HasExtNames a, HasReqs a, Typeable a, Show a) => Exportable a
+ Foreign.Hoppy.Generator.Spec: class Show a => IsParameter a
+ Foreign.Hoppy.Generator.Spec: data ConversionMethod c
+ Foreign.Hoppy.Generator.Spec: data ConversionSpec
+ Foreign.Hoppy.Generator.Spec: data ConversionSpecCpp
+ Foreign.Hoppy.Generator.Spec: data ConversionSpecHaskell
+ Foreign.Hoppy.Generator.Spec: data EnumInfo
+ Foreign.Hoppy.Generator.Spec: data EnumValue
+ Foreign.Hoppy.Generator.Spec: data EnumValueMap
+ Foreign.Hoppy.Generator.Spec: data EvaluatedEnumData
+ Foreign.Hoppy.Generator.Spec: data ForeignLanguage
+ Foreign.Hoppy.Generator.Spec: data Parameter
+ Foreign.Hoppy.Generator.Spec: extNameOrFnIdentifier :: HasCallStack => FnName Identifier -> Maybe ExtName -> ExtName
+ Foreign.Hoppy.Generator.Spec: extNameOrIdentifier :: HasCallStack => Identifier -> Maybe ExtName -> ExtName
+ Foreign.Hoppy.Generator.Spec: extNameOrString :: String -> Maybe ExtName -> ExtName
+ Foreign.Hoppy.Generator.Spec: getExportEnumInfo :: Exportable a => a -> Maybe EnumInfo
+ Foreign.Hoppy.Generator.Spec: getExportExceptionClass :: Exportable a => a -> Maybe Class
+ Foreign.Hoppy.Generator.Spec: infixr 0 ~:
+ Foreign.Hoppy.Generator.Spec: interfaceCompiler :: Interface -> Maybe SomeCompiler
+ Foreign.Hoppy.Generator.Spec: interfaceEvaluatedEnumData :: Interface -> Maybe (Map ExtName EvaluatedEnumData)
+ Foreign.Hoppy.Generator.Spec: interfaceGetEvaluatedEnumData :: HasCallStack => Interface -> ExtName -> EvaluatedEnumData
+ Foreign.Hoppy.Generator.Spec: interfaceHooks :: Interface -> Hooks
+ Foreign.Hoppy.Generator.Spec: interfaceModifyHooks :: (Hooks -> Hooks) -> Interface -> Interface
+ Foreign.Hoppy.Generator.Spec: interfaceSetCompiler :: Compiler a => a -> Interface -> Interface
+ Foreign.Hoppy.Generator.Spec: interfaceSetCompiler' :: Maybe SomeCompiler -> Interface -> Interface
+ Foreign.Hoppy.Generator.Spec: interfaceSetNoCompiler :: Interface -> Interface
+ Foreign.Hoppy.Generator.Spec: interfaceSetValidateEnumTypes :: Bool -> Interface -> Interface
+ Foreign.Hoppy.Generator.Spec: interfaceValidateEnumTypes :: Interface -> Bool
+ Foreign.Hoppy.Generator.Spec: makeConversionSpec :: String -> ConversionSpecCpp -> ConversionSpec
+ Foreign.Hoppy.Generator.Spec: makeConversionSpecCpp :: String -> Generator Reqs -> ConversionSpecCpp
+ Foreign.Hoppy.Generator.Spec: makeConversionSpecHaskell :: Generator HsType -> Maybe (Generator HsType) -> ConversionMethod (Generator ()) -> ConversionMethod (Generator ()) -> ConversionSpecHaskell
+ Foreign.Hoppy.Generator.Spec: makeIdPart :: String -> Maybe [Type] -> IdPart
+ Foreign.Hoppy.Generator.Spec: makeIdentifier :: [IdPart] -> Identifier
+ Foreign.Hoppy.Generator.Spec: modifyExceptionHandlers :: HandlesExceptions a => (ExceptionHandlers -> ExceptionHandlers) -> a -> a
+ Foreign.Hoppy.Generator.Spec: newtype Addendum
+ Foreign.Hoppy.Generator.Spec: newtype ExceptionHandlers
+ Foreign.Hoppy.Generator.Spec: newtype InterfaceOptions
+ Foreign.Hoppy.Generator.Spec: np :: [Parameter]
+ Foreign.Hoppy.Generator.Spec: parameterName :: Parameter -> Maybe String
+ Foreign.Hoppy.Generator.Spec: parameterType :: Parameter -> Type
+ Foreign.Hoppy.Generator.Spec: sayExportCpp :: Exportable a => SayExportMode -> a -> Generator ()
+ Foreign.Hoppy.Generator.Spec: sayExportHaskell :: Exportable a => SayExportMode -> a -> Generator ()
+ Foreign.Hoppy.Generator.Spec: toExport :: Exportable a => a -> Export
+ Foreign.Hoppy.Generator.Spec: toParameter :: IsParameter a => a -> Parameter
+ Foreign.Hoppy.Generator.Spec: toParameters :: IsParameter a => [a] -> [Parameter]
+ Foreign.Hoppy.Generator.Spec: type EnumEntryWords = [String]
+ Foreign.Hoppy.Generator.Spec: type EvaluatedEnumValueMap = Map [String] Integer
+ Foreign.Hoppy.Generator.Spec: type MapWithForeignLanguageOverrides = MapWithOverrides ForeignLanguage
+ Foreign.Hoppy.Generator.Spec: type WithForeignLanguageOverrides = WithOverrides ForeignLanguage
+ Foreign.Hoppy.Generator.Spec.Callback: callbackAddendum :: Callback -> Addendum
+ Foreign.Hoppy.Generator.Spec.Callback: callbackClassName :: Callback -> String
+ Foreign.Hoppy.Generator.Spec.Callback: callbackExtName :: Callback -> ExtName
+ Foreign.Hoppy.Generator.Spec.Callback: callbackFnName :: Callback -> String
+ Foreign.Hoppy.Generator.Spec.Callback: callbackImplClassName :: Callback -> String
+ Foreign.Hoppy.Generator.Spec.Callback: callbackParams :: Callback -> [Parameter]
+ Foreign.Hoppy.Generator.Spec.Callback: callbackReqs :: Callback -> Reqs
+ Foreign.Hoppy.Generator.Spec.Callback: callbackReturn :: Callback -> Type
+ Foreign.Hoppy.Generator.Spec.Callback: callbackSetThrows :: Bool -> Callback -> Callback
+ Foreign.Hoppy.Generator.Spec.Callback: callbackT :: Callback -> Type
+ Foreign.Hoppy.Generator.Spec.Callback: callbackThrows :: Callback -> Maybe Bool
+ Foreign.Hoppy.Generator.Spec.Callback: cppCallbackToTFn :: Callback -> Generator Type
+ Foreign.Hoppy.Generator.Spec.Callback: data Callback
+ Foreign.Hoppy.Generator.Spec.Callback: hsCallbackToTFn :: HsTypeSide -> Callback -> Generator Type
+ Foreign.Hoppy.Generator.Spec.Callback: instance Foreign.Hoppy.Generator.Spec.Base.Exportable Foreign.Hoppy.Generator.Spec.Callback.Callback
+ Foreign.Hoppy.Generator.Spec.Callback: instance Foreign.Hoppy.Generator.Spec.Base.HasAddendum Foreign.Hoppy.Generator.Spec.Callback.Callback
+ Foreign.Hoppy.Generator.Spec.Callback: instance Foreign.Hoppy.Generator.Spec.Base.HasExtNames Foreign.Hoppy.Generator.Spec.Callback.Callback
+ Foreign.Hoppy.Generator.Spec.Callback: instance Foreign.Hoppy.Generator.Spec.Base.HasReqs Foreign.Hoppy.Generator.Spec.Callback.Callback
+ Foreign.Hoppy.Generator.Spec.Callback: instance GHC.Classes.Eq Foreign.Hoppy.Generator.Spec.Callback.Callback
+ Foreign.Hoppy.Generator.Spec.Callback: instance GHC.Show.Show Foreign.Hoppy.Generator.Spec.Callback.Callback
+ Foreign.Hoppy.Generator.Spec.Callback: makeCallback :: IsParameter p => ExtName -> [p] -> Type -> Callback
+ Foreign.Hoppy.Generator.Spec.Callback: toHsCallbackCtorName :: Callback -> Generator String
+ Foreign.Hoppy.Generator.Spec.Callback: toHsCallbackCtorName' :: Callback -> String
+ Foreign.Hoppy.Generator.Spec.Callback: toHsCallbackNewFunPtrFnName :: Callback -> Generator String
+ Foreign.Hoppy.Generator.Spec.Callback: toHsCallbackNewFunPtrFnName' :: Callback -> String
+ Foreign.Hoppy.Generator.Spec.Class: CECtor :: Ctor -> ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: CEMethod :: Method -> ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: CEProp :: Prop -> ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: CEVar :: ClassVariable -> ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: ClassConversion :: ClassHaskellConversion -> ClassConversion
+ Foreign.Hoppy.Generator.Spec.Class: ClassHaskellConversion :: Maybe (Generator HsType) -> Maybe (Generator ()) -> Maybe (Generator ()) -> ClassHaskellConversion
+ Foreign.Hoppy.Generator.Spec.Class: FnMethod :: FnName Identifier -> MethodImpl
+ Foreign.Hoppy.Generator.Spec.Class: MConst :: MethodApplicability
+ Foreign.Hoppy.Generator.Spec.Class: MNormal :: MethodApplicability
+ Foreign.Hoppy.Generator.Spec.Class: MStatic :: MethodApplicability
+ Foreign.Hoppy.Generator.Spec.Class: Nonstatic :: Staticness
+ Foreign.Hoppy.Generator.Spec.Class: RealMethod :: FnName String -> MethodImpl
+ Foreign.Hoppy.Generator.Spec.Class: Static :: Staticness
+ Foreign.Hoppy.Generator.Spec.Class: [classHaskellConversionFromCppFn] :: ClassHaskellConversion -> Maybe (Generator ())
+ Foreign.Hoppy.Generator.Spec.Class: [classHaskellConversionToCppFn] :: ClassHaskellConversion -> Maybe (Generator ())
+ Foreign.Hoppy.Generator.Spec.Class: [classHaskellConversionType] :: ClassHaskellConversion -> Maybe (Generator HsType)
+ Foreign.Hoppy.Generator.Spec.Class: [classHaskellConversion] :: ClassConversion -> ClassHaskellConversion
+ Foreign.Hoppy.Generator.Spec.Class: class IsClassEntity a
+ Foreign.Hoppy.Generator.Spec.Class: classAddEntities :: [ClassEntity] -> Class -> Class
+ Foreign.Hoppy.Generator.Spec.Class: classAddendum :: Class -> Addendum
+ Foreign.Hoppy.Generator.Spec.Class: classConversion :: Class -> ClassConversion
+ Foreign.Hoppy.Generator.Spec.Class: classConversionNone :: ClassConversion
+ Foreign.Hoppy.Generator.Spec.Class: classCtors :: Class -> [Ctor]
+ Foreign.Hoppy.Generator.Spec.Class: classDtorIsPublic :: Class -> Bool
+ Foreign.Hoppy.Generator.Spec.Class: classEntities :: Class -> [ClassEntity]
+ Foreign.Hoppy.Generator.Spec.Class: classEntityExtName :: IsClassEntity a => Class -> a -> ExtName
+ Foreign.Hoppy.Generator.Spec.Class: classEntityExtNameSuffix :: IsClassEntity a => a -> ExtName
+ Foreign.Hoppy.Generator.Spec.Class: classEntityExtNames :: Class -> ClassEntity -> [ExtName]
+ Foreign.Hoppy.Generator.Spec.Class: classEntityForeignName :: IsClassEntity a => Class -> a -> ExtName
+ Foreign.Hoppy.Generator.Spec.Class: classEntityForeignName' :: Class -> ExtName -> ExtName
+ Foreign.Hoppy.Generator.Spec.Class: classEntityPrefix :: Class -> String
+ Foreign.Hoppy.Generator.Spec.Class: classExtName :: Class -> ExtName
+ Foreign.Hoppy.Generator.Spec.Class: classFindCopyCtor :: Class -> Maybe Ctor
+ Foreign.Hoppy.Generator.Spec.Class: classIdentifier :: Class -> Identifier
+ Foreign.Hoppy.Generator.Spec.Class: classIsException :: Class -> Bool
+ Foreign.Hoppy.Generator.Spec.Class: classIsMonomorphicSuperclass :: Class -> Bool
+ Foreign.Hoppy.Generator.Spec.Class: classIsSubclassOfMonomorphic :: Class -> Bool
+ Foreign.Hoppy.Generator.Spec.Class: classMakeException :: Class -> Class
+ Foreign.Hoppy.Generator.Spec.Class: classMethods :: Class -> [Method]
+ Foreign.Hoppy.Generator.Spec.Class: classModifyConversion :: HasCallStack => (ClassConversion -> ClassConversion) -> Class -> Class
+ Foreign.Hoppy.Generator.Spec.Class: classReqs :: Class -> Reqs
+ Foreign.Hoppy.Generator.Spec.Class: classSetConversion :: ClassConversion -> Class -> Class
+ Foreign.Hoppy.Generator.Spec.Class: classSetDtorPrivate :: Class -> Class
+ Foreign.Hoppy.Generator.Spec.Class: classSetEntityPrefix :: String -> Class -> Class
+ Foreign.Hoppy.Generator.Spec.Class: classSetHaskellConversion :: ClassHaskellConversion -> Class -> Class
+ Foreign.Hoppy.Generator.Spec.Class: classSetMonomorphicSuperclass :: Class -> Class
+ Foreign.Hoppy.Generator.Spec.Class: classSetSubclassOfMonomorphic :: Class -> Class
+ Foreign.Hoppy.Generator.Spec.Class: classSuperclasses :: Class -> [Class]
+ Foreign.Hoppy.Generator.Spec.Class: classVariables :: Class -> [ClassVariable]
+ Foreign.Hoppy.Generator.Spec.Class: ctorExceptionHandlers :: Ctor -> ExceptionHandlers
+ Foreign.Hoppy.Generator.Spec.Class: ctorExtName :: Ctor -> ExtName
+ Foreign.Hoppy.Generator.Spec.Class: ctorParams :: Ctor -> [Parameter]
+ Foreign.Hoppy.Generator.Spec.Class: data Class
+ Foreign.Hoppy.Generator.Spec.Class: data ClassConversion
+ Foreign.Hoppy.Generator.Spec.Class: data ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: data ClassHaskellConversion
+ Foreign.Hoppy.Generator.Spec.Class: data ClassVariable
+ Foreign.Hoppy.Generator.Spec.Class: data Ctor
+ Foreign.Hoppy.Generator.Spec.Class: data Method
+ Foreign.Hoppy.Generator.Spec.Class: data MethodApplicability
+ Foreign.Hoppy.Generator.Spec.Class: data MethodImpl
+ Foreign.Hoppy.Generator.Spec.Class: data Prop
+ Foreign.Hoppy.Generator.Spec.Class: data Staticness
+ Foreign.Hoppy.Generator.Spec.Class: instance Foreign.Hoppy.Generator.Spec.Base.Exportable Foreign.Hoppy.Generator.Spec.Class.Class
+ Foreign.Hoppy.Generator.Spec.Class: instance Foreign.Hoppy.Generator.Spec.Base.HandlesExceptions Foreign.Hoppy.Generator.Spec.Class.Ctor
+ Foreign.Hoppy.Generator.Spec.Class: instance Foreign.Hoppy.Generator.Spec.Base.HandlesExceptions Foreign.Hoppy.Generator.Spec.Class.Method
+ Foreign.Hoppy.Generator.Spec.Class: instance Foreign.Hoppy.Generator.Spec.Base.HasAddendum Foreign.Hoppy.Generator.Spec.Class.Class
+ Foreign.Hoppy.Generator.Spec.Class: instance Foreign.Hoppy.Generator.Spec.Base.HasExtNames Foreign.Hoppy.Generator.Spec.Class.Class
+ Foreign.Hoppy.Generator.Spec.Class: instance Foreign.Hoppy.Generator.Spec.Base.HasReqs Foreign.Hoppy.Generator.Spec.Class.Class
+ Foreign.Hoppy.Generator.Spec.Class: instance Foreign.Hoppy.Generator.Spec.Class.IsClassEntity Foreign.Hoppy.Generator.Spec.Class.ClassVariable
+ Foreign.Hoppy.Generator.Spec.Class: instance Foreign.Hoppy.Generator.Spec.Class.IsClassEntity Foreign.Hoppy.Generator.Spec.Class.Ctor
+ Foreign.Hoppy.Generator.Spec.Class: instance Foreign.Hoppy.Generator.Spec.Class.IsClassEntity Foreign.Hoppy.Generator.Spec.Class.Method
+ Foreign.Hoppy.Generator.Spec.Class: instance GHC.Classes.Eq Foreign.Hoppy.Generator.Spec.Class.Class
+ Foreign.Hoppy.Generator.Spec.Class: instance GHC.Classes.Eq Foreign.Hoppy.Generator.Spec.Class.MethodApplicability
+ Foreign.Hoppy.Generator.Spec.Class: instance GHC.Classes.Eq Foreign.Hoppy.Generator.Spec.Class.MethodImpl
+ Foreign.Hoppy.Generator.Spec.Class: instance GHC.Classes.Eq Foreign.Hoppy.Generator.Spec.Class.Staticness
+ Foreign.Hoppy.Generator.Spec.Class: instance GHC.Classes.Ord Foreign.Hoppy.Generator.Spec.Class.Class
+ Foreign.Hoppy.Generator.Spec.Class: instance GHC.Enum.Bounded Foreign.Hoppy.Generator.Spec.Class.MethodApplicability
+ Foreign.Hoppy.Generator.Spec.Class: instance GHC.Enum.Bounded Foreign.Hoppy.Generator.Spec.Class.Staticness
+ Foreign.Hoppy.Generator.Spec.Class: instance GHC.Enum.Enum Foreign.Hoppy.Generator.Spec.Class.MethodApplicability
+ Foreign.Hoppy.Generator.Spec.Class: instance GHC.Enum.Enum Foreign.Hoppy.Generator.Spec.Class.Staticness
+ Foreign.Hoppy.Generator.Spec.Class: instance GHC.Show.Show Foreign.Hoppy.Generator.Spec.Class.Class
+ Foreign.Hoppy.Generator.Spec.Class: instance GHC.Show.Show Foreign.Hoppy.Generator.Spec.Class.ClassVariable
+ Foreign.Hoppy.Generator.Spec.Class: instance GHC.Show.Show Foreign.Hoppy.Generator.Spec.Class.Ctor
+ Foreign.Hoppy.Generator.Spec.Class: instance GHC.Show.Show Foreign.Hoppy.Generator.Spec.Class.Method
+ Foreign.Hoppy.Generator.Spec.Class: instance GHC.Show.Show Foreign.Hoppy.Generator.Spec.Class.MethodApplicability
+ Foreign.Hoppy.Generator.Spec.Class: instance GHC.Show.Show Foreign.Hoppy.Generator.Spec.Class.MethodImpl
+ Foreign.Hoppy.Generator.Spec.Class: instance GHC.Show.Show Foreign.Hoppy.Generator.Spec.Class.Staticness
+ Foreign.Hoppy.Generator.Spec.Class: makeClass :: Identifier -> Maybe ExtName -> [Class] -> [ClassEntity] -> Class
+ Foreign.Hoppy.Generator.Spec.Class: makeClassVariable :: String -> Maybe ExtName -> Type -> Staticness -> Bool -> ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: makeClassVariable_ :: String -> Maybe ExtName -> Type -> Staticness -> Bool -> ClassVariable
+ Foreign.Hoppy.Generator.Spec.Class: makeCtor :: IsParameter p => ExtName -> [p] -> ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: makeCtor_ :: IsParameter p => ExtName -> [p] -> Ctor
+ Foreign.Hoppy.Generator.Spec.Class: makeFnMethod :: (IsFnName Identifier name, IsParameter p) => name -> String -> MethodApplicability -> Purity -> [p] -> Type -> ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: makeFnMethod_ :: (IsFnName Identifier name, IsParameter p) => name -> String -> MethodApplicability -> Purity -> [p] -> Type -> Method
+ Foreign.Hoppy.Generator.Spec.Class: makeMethod :: (IsFnName String name, IsParameter p) => name -> ExtName -> MethodApplicability -> Purity -> [p] -> Type -> ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: makeMethod_ :: (IsFnName String name, IsParameter p) => name -> ExtName -> MethodApplicability -> Purity -> [p] -> Type -> Method
+ Foreign.Hoppy.Generator.Spec.Class: methodApplicability :: Method -> MethodApplicability
+ Foreign.Hoppy.Generator.Spec.Class: methodConst :: Method -> Constness
+ Foreign.Hoppy.Generator.Spec.Class: methodExceptionHandlers :: Method -> ExceptionHandlers
+ Foreign.Hoppy.Generator.Spec.Class: methodExtName :: Method -> ExtName
+ Foreign.Hoppy.Generator.Spec.Class: methodImpl :: Method -> MethodImpl
+ Foreign.Hoppy.Generator.Spec.Class: methodParams :: Method -> [Parameter]
+ Foreign.Hoppy.Generator.Spec.Class: methodPurity :: Method -> Purity
+ Foreign.Hoppy.Generator.Spec.Class: methodReturn :: Method -> Type
+ Foreign.Hoppy.Generator.Spec.Class: methodStatic :: Method -> Staticness
+ Foreign.Hoppy.Generator.Spec.Class: mkBoolHasProp :: String -> ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: mkBoolHasProp_ :: String -> Prop
+ Foreign.Hoppy.Generator.Spec.Class: mkBoolIsProp :: String -> ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: mkBoolIsProp_ :: String -> Prop
+ Foreign.Hoppy.Generator.Spec.Class: mkClassVariable :: String -> Type -> ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: mkClassVariable_ :: String -> Type -> ClassVariable
+ Foreign.Hoppy.Generator.Spec.Class: mkConstMethod :: (IsFnName String name, IsParameter p) => name -> [p] -> Type -> ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: mkConstMethod' :: (IsFnName String name, IsParameter p) => name -> String -> [p] -> Type -> ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: mkConstMethod'_ :: (IsFnName String name, IsParameter p) => name -> String -> [p] -> Type -> Method
+ Foreign.Hoppy.Generator.Spec.Class: mkConstMethod_ :: (IsFnName String name, IsParameter p) => name -> [p] -> Type -> Method
+ Foreign.Hoppy.Generator.Spec.Class: mkCtor :: IsParameter p => String -> [p] -> ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: mkCtor_ :: IsParameter p => String -> [p] -> Ctor
+ Foreign.Hoppy.Generator.Spec.Class: mkMethod :: (IsFnName String name, IsParameter p) => name -> [p] -> Type -> ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: mkMethod' :: (IsFnName String name, IsParameter p) => name -> String -> [p] -> Type -> ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: mkMethod'_ :: (IsFnName String name, IsParameter p) => name -> String -> [p] -> Type -> Method
+ Foreign.Hoppy.Generator.Spec.Class: mkMethod_ :: (IsFnName String name, IsParameter p) => name -> [p] -> Type -> Method
+ Foreign.Hoppy.Generator.Spec.Class: mkProp :: String -> Type -> ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: mkProp_ :: String -> Type -> Prop
+ Foreign.Hoppy.Generator.Spec.Class: mkStaticClassVariable :: String -> Type -> ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: mkStaticClassVariable_ :: String -> Type -> ClassVariable
+ Foreign.Hoppy.Generator.Spec.Class: mkStaticMethod :: (IsFnName String name, IsParameter p) => name -> [p] -> Type -> ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: mkStaticMethod' :: (IsFnName String name, IsParameter p) => name -> String -> [p] -> Type -> ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: mkStaticMethod'_ :: (IsFnName String name, IsParameter p) => name -> String -> [p] -> Type -> Method
+ Foreign.Hoppy.Generator.Spec.Class: mkStaticMethod_ :: (IsFnName String name, IsParameter p) => name -> [p] -> Type -> Method
+ Foreign.Hoppy.Generator.Spec.Class: mkStaticProp :: String -> Type -> ClassEntity
+ Foreign.Hoppy.Generator.Spec.Class: mkStaticProp_ :: String -> Type -> Prop
+ Foreign.Hoppy.Generator.Spec.Class: sayCppExportVar :: Type -> Maybe (Type, Type) -> Bool -> ExtName -> ExtName -> Generator () -> Generator ()
+ Foreign.Hoppy.Generator.Spec.Class: sayHsExportVar :: SayExportMode -> Type -> Maybe Class -> Bool -> ExtName -> ExtName -> ExtName -> ExtName -> Generator ()
+ Foreign.Hoppy.Generator.Spec.Class: toHsCastMethodName :: Constness -> Class -> Generator String
+ Foreign.Hoppy.Generator.Spec.Class: toHsCastMethodName' :: Constness -> Class -> String
+ Foreign.Hoppy.Generator.Spec.Class: toHsCastPrimitiveName :: Class -> Class -> Class -> Generator String
+ Foreign.Hoppy.Generator.Spec.Class: toHsCastPrimitiveName' :: Class -> Class -> String
+ Foreign.Hoppy.Generator.Spec.Class: toHsClassDeleteFnName' :: Class -> String
+ Foreign.Hoppy.Generator.Spec.Class: toHsClassDeleteFnPtrName' :: Class -> String
+ Foreign.Hoppy.Generator.Spec.Class: toHsClassEntityName :: IsFnName String name => Class -> name -> Generator String
+ Foreign.Hoppy.Generator.Spec.Class: toHsClassEntityName' :: IsFnName String name => Class -> name -> String
+ Foreign.Hoppy.Generator.Spec.Class: toHsConstCastFnName :: Constness -> Class -> Generator String
+ Foreign.Hoppy.Generator.Spec.Class: toHsConstCastFnName' :: Constness -> Class -> String
+ Foreign.Hoppy.Generator.Spec.Class: toHsCtorName :: Class -> Ctor -> Generator String
+ Foreign.Hoppy.Generator.Spec.Class: toHsCtorName' :: Class -> Ctor -> String
+ Foreign.Hoppy.Generator.Spec.Class: toHsDataCtorName :: Managed -> Constness -> Class -> Generator String
+ Foreign.Hoppy.Generator.Spec.Class: toHsDataCtorName' :: Managed -> Constness -> Class -> String
+ Foreign.Hoppy.Generator.Spec.Class: toHsDataTypeName :: Constness -> Class -> Generator String
+ Foreign.Hoppy.Generator.Spec.Class: toHsDataTypeName' :: Constness -> Class -> String
+ Foreign.Hoppy.Generator.Spec.Class: toHsDownCastClassName :: Constness -> Class -> Generator String
+ Foreign.Hoppy.Generator.Spec.Class: toHsDownCastClassName' :: Constness -> Class -> String
+ Foreign.Hoppy.Generator.Spec.Class: toHsDownCastMethodName :: Constness -> Class -> Generator String
+ Foreign.Hoppy.Generator.Spec.Class: toHsDownCastMethodName' :: Constness -> Class -> String
+ Foreign.Hoppy.Generator.Spec.Class: toHsMethodName :: Class -> Method -> Generator String
+ Foreign.Hoppy.Generator.Spec.Class: toHsMethodName' :: Class -> Method -> String
+ Foreign.Hoppy.Generator.Spec.Class: toHsPtrClassName :: Constness -> Class -> Generator String
+ Foreign.Hoppy.Generator.Spec.Class: toHsPtrClassName' :: Constness -> Class -> String
+ Foreign.Hoppy.Generator.Spec.Class: toHsValueClassName :: Class -> Generator String
+ Foreign.Hoppy.Generator.Spec.Class: toHsValueClassName' :: Class -> String
+ Foreign.Hoppy.Generator.Spec.Class: toHsWithValuePtrName :: Class -> Generator String
+ Foreign.Hoppy.Generator.Spec.Class: toHsWithValuePtrName' :: Class -> String
+ Foreign.Hoppy.Generator.Spec.Enum: class IsAutoEnumValue a
+ Foreign.Hoppy.Generator.Spec.Enum: class IsEnumUnknownValueEntry a
+ Foreign.Hoppy.Generator.Spec.Enum: cppGetEvaluatedEnumData :: HasCallStack => ExtName -> Generator EvaluatedEnumData
+ Foreign.Hoppy.Generator.Spec.Enum: data CppEnum
+ Foreign.Hoppy.Generator.Spec.Enum: enumAddEntryNameOverrides :: IsAutoEnumValue v => ForeignLanguage -> [(v, v)] -> CppEnum -> CppEnum
+ Foreign.Hoppy.Generator.Spec.Enum: enumAddendum :: CppEnum -> Addendum
+ Foreign.Hoppy.Generator.Spec.Enum: enumExtName :: CppEnum -> ExtName
+ Foreign.Hoppy.Generator.Spec.Enum: enumGetOverriddenEntryName :: ForeignLanguage -> CppEnum -> EnumEntryWords -> EnumEntryWords
+ Foreign.Hoppy.Generator.Spec.Enum: enumHasBitOperations :: CppEnum -> Bool
+ Foreign.Hoppy.Generator.Spec.Enum: enumIdentifier :: CppEnum -> Identifier
+ Foreign.Hoppy.Generator.Spec.Enum: enumNumericType :: CppEnum -> Maybe Type
+ Foreign.Hoppy.Generator.Spec.Enum: enumReqs :: CppEnum -> Reqs
+ Foreign.Hoppy.Generator.Spec.Enum: enumSetHasBitOperations :: Bool -> CppEnum -> CppEnum
+ Foreign.Hoppy.Generator.Spec.Enum: enumSetNoUnknownValueEntry :: CppEnum -> CppEnum
+ Foreign.Hoppy.Generator.Spec.Enum: enumSetNumericType :: Maybe Type -> CppEnum -> CppEnum
+ Foreign.Hoppy.Generator.Spec.Enum: enumSetUnknownValueEntry :: IsEnumUnknownValueEntry a => a -> CppEnum -> CppEnum
+ Foreign.Hoppy.Generator.Spec.Enum: enumSetValuePrefix :: String -> CppEnum -> CppEnum
+ Foreign.Hoppy.Generator.Spec.Enum: enumT :: CppEnum -> Type
+ Foreign.Hoppy.Generator.Spec.Enum: enumUnknownValueEntry :: CppEnum -> Maybe EnumEntryWords
+ Foreign.Hoppy.Generator.Spec.Enum: enumUnknownValueEntryDefault :: EnumEntryWords
+ Foreign.Hoppy.Generator.Spec.Enum: enumValuePrefix :: CppEnum -> String
+ Foreign.Hoppy.Generator.Spec.Enum: enumValues :: CppEnum -> EnumValueMap
+ Foreign.Hoppy.Generator.Spec.Enum: hsGetEvaluatedEnumData :: HasCallStack => ExtName -> Generator EvaluatedEnumData
+ Foreign.Hoppy.Generator.Spec.Enum: instance Foreign.Hoppy.Generator.Spec.Base.Exportable Foreign.Hoppy.Generator.Spec.Enum.CppEnum
+ Foreign.Hoppy.Generator.Spec.Enum: instance Foreign.Hoppy.Generator.Spec.Base.HasAddendum Foreign.Hoppy.Generator.Spec.Enum.CppEnum
+ Foreign.Hoppy.Generator.Spec.Enum: instance Foreign.Hoppy.Generator.Spec.Base.HasExtNames Foreign.Hoppy.Generator.Spec.Enum.CppEnum
+ Foreign.Hoppy.Generator.Spec.Enum: instance Foreign.Hoppy.Generator.Spec.Base.HasReqs Foreign.Hoppy.Generator.Spec.Enum.CppEnum
+ Foreign.Hoppy.Generator.Spec.Enum: instance Foreign.Hoppy.Generator.Spec.Enum.IsAutoEnumValue (Foreign.Hoppy.Generator.Spec.Base.EnumEntryWords, GHC.Base.String)
+ Foreign.Hoppy.Generator.Spec.Enum: instance Foreign.Hoppy.Generator.Spec.Enum.IsAutoEnumValue GHC.Base.String
+ Foreign.Hoppy.Generator.Spec.Enum: instance Foreign.Hoppy.Generator.Spec.Enum.IsEnumUnknownValueEntry Foreign.Hoppy.Generator.Spec.Base.EnumEntryWords
+ Foreign.Hoppy.Generator.Spec.Enum: instance Foreign.Hoppy.Generator.Spec.Enum.IsEnumUnknownValueEntry GHC.Base.String
+ Foreign.Hoppy.Generator.Spec.Enum: instance GHC.Classes.Eq Foreign.Hoppy.Generator.Spec.Enum.CppEnum
+ Foreign.Hoppy.Generator.Spec.Enum: instance GHC.Show.Show Foreign.Hoppy.Generator.Spec.Enum.CppEnum
+ Foreign.Hoppy.Generator.Spec.Enum: makeAutoEnum :: IsAutoEnumValue v => Identifier -> Maybe ExtName -> Bool -> [v] -> CppEnum
+ Foreign.Hoppy.Generator.Spec.Enum: makeEnum :: Identifier -> Maybe ExtName -> [(Integer, EnumEntryWords)] -> CppEnum
+ Foreign.Hoppy.Generator.Spec.Enum: toAutoEnumValue :: IsAutoEnumValue a => a -> (EnumEntryWords, String)
+ Foreign.Hoppy.Generator.Spec.Enum: toEnumUnknownValueEntry :: IsEnumUnknownValueEntry a => a -> EnumEntryWords
+ Foreign.Hoppy.Generator.Spec.Enum: toHsEnumCtorName :: CppEnum -> EnumEntryWords -> Generator String
+ Foreign.Hoppy.Generator.Spec.Enum: toHsEnumCtorName' :: CppEnum -> EnumEntryWords -> String
+ Foreign.Hoppy.Generator.Spec.Enum: toHsEnumTypeName :: CppEnum -> Generator String
+ Foreign.Hoppy.Generator.Spec.Enum: toHsEnumTypeName' :: CppEnum -> String
+ Foreign.Hoppy.Generator.Spec.Function: CallFn :: Generator () -> CppCallType
+ Foreign.Hoppy.Generator.Spec.Function: CallOp :: Operator -> CppCallType
+ Foreign.Hoppy.Generator.Spec.Function: FromCpp :: CallDirection
+ Foreign.Hoppy.Generator.Spec.Function: ToCpp :: CallDirection
+ Foreign.Hoppy.Generator.Spec.Function: VarRead :: Generator () -> CppCallType
+ Foreign.Hoppy.Generator.Spec.Function: VarWrite :: Generator () -> CppCallType
+ Foreign.Hoppy.Generator.Spec.Function: data CallDirection
+ Foreign.Hoppy.Generator.Spec.Function: data CppCallType
+ Foreign.Hoppy.Generator.Spec.Function: data Function
+ Foreign.Hoppy.Generator.Spec.Function: fnAddendum :: Function -> Addendum
+ Foreign.Hoppy.Generator.Spec.Function: fnCName :: Function -> FnName Identifier
+ Foreign.Hoppy.Generator.Spec.Function: fnExceptionHandlers :: Function -> ExceptionHandlers
+ Foreign.Hoppy.Generator.Spec.Function: fnExtName :: Function -> ExtName
+ Foreign.Hoppy.Generator.Spec.Function: fnParams :: Function -> [Parameter]
+ Foreign.Hoppy.Generator.Spec.Function: fnPurity :: Function -> Purity
+ Foreign.Hoppy.Generator.Spec.Function: fnReqs :: Function -> Reqs
+ Foreign.Hoppy.Generator.Spec.Function: fnReturn :: Function -> Type
+ Foreign.Hoppy.Generator.Spec.Function: fnT :: [Type] -> Type -> Type
+ Foreign.Hoppy.Generator.Spec.Function: fnT' :: [Parameter] -> Type -> Type
+ Foreign.Hoppy.Generator.Spec.Function: instance Foreign.Hoppy.Generator.Spec.Base.Exportable Foreign.Hoppy.Generator.Spec.Function.Function
+ Foreign.Hoppy.Generator.Spec.Function: instance Foreign.Hoppy.Generator.Spec.Base.HandlesExceptions Foreign.Hoppy.Generator.Spec.Function.Function
+ Foreign.Hoppy.Generator.Spec.Function: instance Foreign.Hoppy.Generator.Spec.Base.HasAddendum Foreign.Hoppy.Generator.Spec.Function.Function
+ Foreign.Hoppy.Generator.Spec.Function: instance Foreign.Hoppy.Generator.Spec.Base.HasExtNames Foreign.Hoppy.Generator.Spec.Function.Function
+ Foreign.Hoppy.Generator.Spec.Function: instance Foreign.Hoppy.Generator.Spec.Base.HasReqs Foreign.Hoppy.Generator.Spec.Function.Function
+ Foreign.Hoppy.Generator.Spec.Function: instance GHC.Classes.Eq Foreign.Hoppy.Generator.Spec.Function.Function
+ Foreign.Hoppy.Generator.Spec.Function: instance GHC.Show.Show Foreign.Hoppy.Generator.Spec.Function.CallDirection
+ Foreign.Hoppy.Generator.Spec.Function: instance GHC.Show.Show Foreign.Hoppy.Generator.Spec.Function.Function
+ Foreign.Hoppy.Generator.Spec.Function: makeFn :: (IsFnName Identifier name, IsParameter p) => name -> Maybe ExtName -> Purity -> [p] -> Type -> Function
+ Foreign.Hoppy.Generator.Spec.Function: sayCppArgNames :: Int -> Generator ()
+ Foreign.Hoppy.Generator.Spec.Function: sayCppArgRead :: CallDirection -> (Int, Type, Maybe Type) -> Generator ()
+ Foreign.Hoppy.Generator.Spec.Function: sayCppExportFn :: ExtName -> CppCallType -> Maybe Type -> [Parameter] -> Type -> ExceptionHandlers -> Bool -> Generator ()
+ Foreign.Hoppy.Generator.Spec.Function: sayHsArgProcessing :: CallDirection -> Type -> String -> String -> Generator ()
+ Foreign.Hoppy.Generator.Spec.Function: sayHsCallAndProcessReturn :: CallDirection -> Type -> [String] -> Generator ()
+ Foreign.Hoppy.Generator.Spec.Function: sayHsExportFn :: SayExportMode -> ExtName -> ExtName -> Purity -> [Parameter] -> Type -> ExceptionHandlers -> Generator ()
+ Foreign.Hoppy.Generator.Spec.Variable: data Variable
+ Foreign.Hoppy.Generator.Spec.Variable: instance Foreign.Hoppy.Generator.Spec.Base.Exportable Foreign.Hoppy.Generator.Spec.Variable.Variable
+ Foreign.Hoppy.Generator.Spec.Variable: instance Foreign.Hoppy.Generator.Spec.Base.HasAddendum Foreign.Hoppy.Generator.Spec.Variable.Variable
+ Foreign.Hoppy.Generator.Spec.Variable: instance Foreign.Hoppy.Generator.Spec.Base.HasExtNames Foreign.Hoppy.Generator.Spec.Variable.Variable
+ Foreign.Hoppy.Generator.Spec.Variable: instance Foreign.Hoppy.Generator.Spec.Base.HasReqs Foreign.Hoppy.Generator.Spec.Variable.Variable
+ Foreign.Hoppy.Generator.Spec.Variable: instance GHC.Classes.Eq Foreign.Hoppy.Generator.Spec.Variable.Variable
+ Foreign.Hoppy.Generator.Spec.Variable: instance GHC.Show.Show Foreign.Hoppy.Generator.Spec.Variable.Variable
+ Foreign.Hoppy.Generator.Spec.Variable: makeVariable :: Identifier -> Maybe ExtName -> Type -> Variable
+ Foreign.Hoppy.Generator.Spec.Variable: varAddendum :: Variable -> Addendum
+ Foreign.Hoppy.Generator.Spec.Variable: varExtName :: Variable -> ExtName
+ Foreign.Hoppy.Generator.Spec.Variable: varGetterExtName :: Variable -> ExtName
+ Foreign.Hoppy.Generator.Spec.Variable: varIdentifier :: Variable -> Identifier
+ Foreign.Hoppy.Generator.Spec.Variable: varIsConst :: Variable -> Bool
+ Foreign.Hoppy.Generator.Spec.Variable: varReqs :: Variable -> Reqs
+ Foreign.Hoppy.Generator.Spec.Variable: varSetterExtName :: Variable -> ExtName
+ Foreign.Hoppy.Generator.Spec.Variable: varType :: Variable -> Type
+ Foreign.Hoppy.Generator.Types: boolT' :: Type
+ Foreign.Hoppy.Generator.Types: convertByCoercingFloating :: ConversionMethod (Generator ())
+ Foreign.Hoppy.Generator.Types: convertByCoercingIntegral :: ConversionMethod (Generator ())
+ Foreign.Hoppy.Generator.Types: doubleT' :: Type
+ Foreign.Hoppy.Generator.Types: floatT' :: Type
+ Foreign.Hoppy.Generator.Types: fnT' :: [Parameter] -> Type -> Type
+ Foreign.Hoppy.Generator.Types: intT' :: Type
+ Foreign.Hoppy.Generator.Types: makeNumericType :: String -> Reqs -> Generator HsType -> Maybe (Generator HsType) -> ConversionMethod (Generator ()) -> ConversionMethod (Generator ()) -> Type
+ Foreign.Hoppy.Generator.Types: manualT :: ConversionSpec -> Type
+ Foreign.Hoppy.Generator.Types: wcharT :: Type
+ Foreign.Hoppy.Generator.Util: splitIntoWords :: String -> [String]
+ Foreign.Hoppy.Generator.Util: withTempDirectory :: String -> Bool -> (FilePath -> IO (Bool, a)) -> IO a
+ Foreign.Hoppy.Generator.Util: withTempFile :: String -> Bool -> (FilePath -> Handle -> IO (Bool, a)) -> IO a
- Foreign.Hoppy.Generator.Language.Cpp: Chunk :: String -> Chunk
+ Foreign.Hoppy.Generator.Language.Cpp: Chunk :: !String -> !Set Include -> Chunk
- Foreign.Hoppy.Generator.Language.Cpp: [chunkContents] :: Chunk -> String
+ Foreign.Hoppy.Generator.Language.Cpp: [chunkContents] :: Chunk -> !String
- Foreign.Hoppy.Generator.Language.Cpp: execChunkWriter :: Writer [Chunk] a -> String
+ Foreign.Hoppy.Generator.Language.Cpp: execChunkWriter :: Writer [Chunk] a -> Chunk
- Foreign.Hoppy.Generator.Language.Cpp: execChunkWriterT :: Monad m => WriterT [Chunk] m a -> m String
+ Foreign.Hoppy.Generator.Language.Cpp: execChunkWriterT :: Monad m => WriterT [Chunk] m a -> m Chunk
- Foreign.Hoppy.Generator.Language.Cpp: runChunkWriter :: Writer [Chunk] a -> (a, String)
+ Foreign.Hoppy.Generator.Language.Cpp: runChunkWriter :: Writer [Chunk] a -> (a, Chunk)
- Foreign.Hoppy.Generator.Language.Cpp: runChunkWriterT :: Monad m => WriterT [Chunk] m a -> m (a, String)
+ Foreign.Hoppy.Generator.Language.Cpp: runChunkWriterT :: Monad m => WriterT [Chunk] m a -> m (a, Chunk)
- Foreign.Hoppy.Generator.Spec: Internal_TFn :: [Type] -> Type -> Type
+ Foreign.Hoppy.Generator.Spec: Internal_TFn :: [Parameter] -> Type -> Type
- Foreign.Hoppy.Generator.Spec: interfaceSetExceptionSupportModule :: Module -> Interface -> Interface
+ Foreign.Hoppy.Generator.Spec: interfaceSetExceptionSupportModule :: HasCallStack => Module -> Interface -> Interface
- Foreign.Hoppy.Generator.Spec: moduleModify' :: Module -> StateT Module (Either String) () -> Module
+ Foreign.Hoppy.Generator.Spec: moduleModify' :: HasCallStack => Module -> StateT Module (Either String) () -> Module
- Foreign.Hoppy.Generator.Spec: operatorPreferredExtName :: Operator -> ExtName
+ Foreign.Hoppy.Generator.Spec: operatorPreferredExtName :: HasCallStack => Operator -> ExtName
- Foreign.Hoppy.Generator.Spec: operatorType :: Operator -> OperatorType
+ Foreign.Hoppy.Generator.Spec: operatorType :: HasCallStack => Operator -> OperatorType
- Foreign.Hoppy.Generator.Spec: toExtName :: String -> ExtName
+ Foreign.Hoppy.Generator.Spec: toExtName :: HasCallStack => String -> ExtName
Files
- Setup.hs +1/−1
- hoppy-generator.cabal +22/−6
- src/Foreign/Hoppy/Generator/Common.hs +91/−18
- src/Foreign/Hoppy/Generator/Common/Consume.hs +30/−7
- src/Foreign/Hoppy/Generator/Compiler.hs +180/−0
- src/Foreign/Hoppy/Generator/Hook.hs +485/−0
- src/Foreign/Hoppy/Generator/Hook.hs-boot +25/−0
- src/Foreign/Hoppy/Generator/Language/Cpp.hs +240/−106
- src/Foreign/Hoppy/Generator/Language/Cpp.hs-boot +33/−0
- src/Foreign/Hoppy/Generator/Language/Cpp/Internal.hs +12/−789
- src/Foreign/Hoppy/Generator/Language/Haskell.hs +96/−457
- src/Foreign/Hoppy/Generator/Language/Haskell.hs-boot +17/−10
- src/Foreign/Hoppy/Generator/Language/Haskell/Internal.hs +202/−1692
- src/Foreign/Hoppy/Generator/Main.hs +114/−30
- src/Foreign/Hoppy/Generator/Override.hs +187/−0
- src/Foreign/Hoppy/Generator/Spec.hs +11/−1
- src/Foreign/Hoppy/Generator/Spec/Base.hs +1831/−2513
- src/Foreign/Hoppy/Generator/Spec/Base.hs-boot +41/−0
- src/Foreign/Hoppy/Generator/Spec/Callback.hs +576/−0
- src/Foreign/Hoppy/Generator/Spec/Callback.hs-boot +27/−0
- src/Foreign/Hoppy/Generator/Spec/Class.hs +2116/−0
- src/Foreign/Hoppy/Generator/Spec/Class.hs-boot +72/−0
- src/Foreign/Hoppy/Generator/Spec/ClassFeature.hs +2/−1
- src/Foreign/Hoppy/Generator/Spec/Conversion.hs +17/−4
- src/Foreign/Hoppy/Generator/Spec/Enum.hs +524/−0
- src/Foreign/Hoppy/Generator/Spec/Enum.hs-boot +27/−0
- src/Foreign/Hoppy/Generator/Spec/Function.hs +905/−0
- src/Foreign/Hoppy/Generator/Spec/Function.hs-boot +29/−0
- src/Foreign/Hoppy/Generator/Spec/Variable.hs +123/−0
- src/Foreign/Hoppy/Generator/Types.hs +299/−48
- src/Foreign/Hoppy/Generator/Util.hs +115/−0
- src/Foreign/Hoppy/Generator/Version.hs +1/−1
Setup.hs view
@@ -1,6 +1,6 @@ -- This file is part of Hoppy. ----- Copyright 2015-2018 Bryan Gardiner <bog@khumba.net>+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net> -- -- 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
hoppy-generator.cabal view
@@ -1,12 +1,12 @@ name: hoppy-generator-version: 0.5.2+version: 0.6.0 synopsis: C++ FFI generator - Code generator homepage: http://khumba.net/projects/hoppy license: AGPL-3 license-file: LICENSE author: Bryan Gardiner <bog@khumba.net> maintainer: Bryan Gardiner <bog@khumba.net>-copyright: Copyright 2015-2018 Bryan Gardiner+copyright: Copyright 2015-2019 Bryan Gardiner category: Foreign build-type: Simple cabal-version: >=1.10@@ -17,12 +17,21 @@ library exposed-modules:- Foreign.Hoppy.Generator.Language.Cpp+ Foreign.Hoppy.Generator.Compiler+ , Foreign.Hoppy.Generator.Hook+ , Foreign.Hoppy.Generator.Language.Cpp , Foreign.Hoppy.Generator.Language.Haskell , Foreign.Hoppy.Generator.Main+ , Foreign.Hoppy.Generator.Override , Foreign.Hoppy.Generator.Spec+ , Foreign.Hoppy.Generator.Spec.Callback , Foreign.Hoppy.Generator.Spec.ClassFeature+ , Foreign.Hoppy.Generator.Spec.Class+ , Foreign.Hoppy.Generator.Spec.Enum+ , Foreign.Hoppy.Generator.Spec.Function+ , Foreign.Hoppy.Generator.Spec.Variable , Foreign.Hoppy.Generator.Types+ , Foreign.Hoppy.Generator.Util , Foreign.Hoppy.Generator.Version other-modules: Foreign.Hoppy.Generator.Common@@ -32,20 +41,27 @@ , Foreign.Hoppy.Generator.Spec.Base , Foreign.Hoppy.Generator.Spec.Conversion default-extensions:- FlexibleContexts+ ExistentialQuantification+ , FlexibleContexts , FlexibleInstances , FunctionalDependencies , LambdaCase , MultiParamTypeClasses+ , ScopedTypeVariables other-extensions: GeneralizedNewtypeDeriving+ , UndecidableInstances build-depends: base >=4.7 && <5- , containers >=0.5 && <0.6+ , bytestring >=0.10 && <0.11+ , containers >=0.5 && <0.7 , directory >=1.2 && <1.4 , filepath >=1.3 && <1.5 , haskell-src >=1.0 && <1.1- , mtl >=2.1 && <2.3+ , mtl >=2.2.1 && <2.3+ , process >=1.2 && <1.7+ , temporary >=1.2 && <1.4+ , text >=1.1 && <1.3 hs-source-dirs: src ghc-options: -W -fwarn-incomplete-patterns -fwarn-unused-do-bind default-language: Haskell2010
src/Foreign/Hoppy/Generator/Common.hs view
@@ -1,6 +1,6 @@ -- This file is part of Hoppy. ----- Copyright 2015-2018 Bryan Gardiner <bog@khumba.net>+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net> -- -- 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@@ -18,18 +18,28 @@ {-# LANGUAGE CPP #-} -- | General routines.+--+-- Unlike "Foreign.Hoppy.Generator.Util", these are private to the package. module Foreign.Hoppy.Generator.Common (+ filterMaybe, fromMaybeM, fromEitherM, maybeFail,+ whileJust_, for,+ butLast, listSubst,+ listSubst',+ doubleQuote,+ strInterpolate, zipWithM,- writeFileIfDifferent, -- * String utilities capitalize, lowerFirst, upperFirst,+ pluralize,+ -- * File utilities+ writeFileIfDifferent, ) where #if !MIN_VERSION_base(4,8,0)@@ -38,9 +48,18 @@ import Control.Exception (evaluate) import Control.Monad (when) import Data.Char (toLower, toUpper)+import qualified Data.List as L+import qualified Data.Map as M+import Data.Map (Map) import System.Directory (doesFileExist) import System.IO (IOMode (ReadMode), hGetContents, withFile) +-- | @filterMaybe bad@ converts a @Just bad@ into a @Nothing@, returning all+-- other @Maybe@ values as is.+filterMaybe :: Eq a => a -> Maybe a -> Maybe a+filterMaybe bad (Just value) | value == bad = Nothing+filterMaybe _ mayb = mayb+ -- | @fromMaybeM m x = maybe m return x@ fromMaybeM :: Monad m => m a -> Maybe a -> m a fromMaybeM = flip maybe return@@ -53,34 +72,64 @@ maybeFail :: Monad m => String -> Maybe a -> m a maybeFail = fromMaybeM . fail +-- | @whileJust_ gen act@ runs @act@ on values generated from @gen@ until @gen@+-- returns a @Nothing@.+whileJust_ :: Monad m => m (Maybe a) -> (a -> m b) -> m ()+whileJust_ gen act = gen >>= \case+ Just x -> act x >> whileJust_ gen act+ Nothing -> return ()+ -- | @for = flip map@ for :: [a] -> (a -> b) -> [b] for = flip map +-- | Drops the last item from the list, if non-empty.+butLast :: [a] -> [a]+butLast [] = []+butLast xs = take (length xs - 1) xs+ -- | @listSubst a b xs@ replaces all @x@ in @xs@ such that @x == a@ with @b@. listSubst :: Eq a => a -> a -> [a] -> [a] listSubst x x' = map $ \y -> if y == x then x' else y +-- | @listSubst' a bs xs@ replaces all @x@ in @xs@ such that @x == a@ with the+-- sequence of items @bs@.+listSubst' :: Eq a => a -> [a] -> [a] -> [a]+listSubst' x xs' = concatMap $ \y -> if y == x then xs' else [y]++-- | Renders a double-quoted string, enclosing the given characters in double+-- quotes and escaping double quotes and backslashes with backslashes.+doubleQuote :: String -> String+doubleQuote str = '"' : escape str ++ "\""+ where escape s =+ if any (\c -> c == '"' || c == '\\') s+ then listSubst' '"' "\\\"" $+ listSubst' '\\' "\\\\" s+ else s++-- | Takes a map of strings and a target string, and replaces references to keys+-- enclosed in braces in the target string with their values. Returns a @Right@+-- with the replaced string on success, and when an unknown key is encountered+-- then a @Left@ with the unknown key.+strInterpolate :: Map String String -> String -> Either String String+strInterpolate values str = case L.findIndex ('{' ==) str of+ Nothing -> Right str+ Just openBraceIndex ->+ let (prefix, termAndSuffix) = splitAt openBraceIndex str+ in case L.findIndex ('}' ==) termAndSuffix of+ Nothing -> Right str+ Just closeBraceIndex ->+ let termLength = closeBraceIndex - 1+ term = take termLength $ tail termAndSuffix+ suffix = drop (closeBraceIndex + 1) termAndSuffix+ in case M.lookup term values of+ Nothing -> Left term+ Just value -> strInterpolate values $ prefix ++ value ++ suffix+ -- | Zips two lists using a monadic function. zipWithM :: Monad m => (a -> b -> m c) -> [a] -> [b] -> m [c] zipWithM f xs ys = sequence $ zipWith f xs ys --- | If the file specified does not exist or its contents does not match the--- given string, then this writes the string to the file.-writeFileIfDifferent :: FilePath -> String -> IO ()-writeFileIfDifferent path newContents = do- exists <- doesFileExist path- -- We need to read the file strictly, otherwise lazy IO might try to write the- -- file while it's still open and locked for reading.- doWrite <- if exists- then (newContents /=) <$> readStrictly- else return True- when doWrite $ writeFile path newContents- where readStrictly = withFile path ReadMode $ \handle -> do- contents <- hGetContents handle- _ <- evaluate $ length contents- return contents- -- | Upper cases the first character of a string, and lower cases the rest of -- it. Does nothing to an empty string. capitalize :: String -> String@@ -96,3 +145,27 @@ upperFirst :: String -> String upperFirst "" = "" upperFirst (c:cs) = toUpper c : cs++-- | Adds a noun onto a number in either singular or plural form, depending on+-- the number.+pluralize :: Int -> String -> String -> String+pluralize num singular plural =+ if num == 1+ then "1 " ++ singular+ else show num ++ " " ++ plural++-- | If the file specified does not exist or its contents does not match the+-- given string, then this writes the string to the file.+writeFileIfDifferent :: FilePath -> String -> IO ()+writeFileIfDifferent path newContents = do+ exists <- doesFileExist path+ -- We need to read the file strictly, otherwise lazy IO might try to write the+ -- file while it's still open and locked for reading.+ doWrite <- if exists+ then (newContents /=) <$> readStrictly+ else return True+ when doWrite $ writeFile path newContents+ where readStrictly = withFile path ReadMode $ \handle -> do+ contents <- hGetContents handle+ _ <- evaluate $ length contents+ return contents
src/Foreign/Hoppy/Generator/Common/Consume.hs view
@@ -1,6 +1,6 @@ -- This file is part of Hoppy. ----- Copyright 2015-2018 Bryan Gardiner <bog@khumba.net>+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net> -- -- 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@@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>. -{-# LANGUAGE CPP #-}+{-# LANGUAGE CPP, UndecidableInstances #-} -- | A monad for consuming streams. module Foreign.Hoppy.Generator.Common.Consume (@@ -24,18 +24,24 @@ runConsumeT, evalConsumeT, execConsumeT,+ Consume,+ runConsume,+ evalConsume,+ execConsume, ) where #if !MIN_VERSION_base(4,8,0) import Control.Applicative ((<*>), Applicative, pure) #endif import Control.Monad (ap, liftM)-import Control.Monad.Trans (MonadTrans, lift)+import Control.Monad.Except (ExceptT)+import Control.Monad.Identity (Identity, runIdentity) import Control.Monad.State (StateT, get, put, runStateT)+import Control.Monad.Trans (MonadTrans, lift) import Data.Tuple (swap) -- | A typeclass for monads that can consume items from a stream.-class MonadConsume s m | m -> s where+class Monad m => MonadConsume s m | m -> s where -- | Attempts to consume an item from the stream. Returns an item if the -- stream is not empty. next :: m (Maybe s)@@ -64,18 +70,35 @@ [] -> return Nothing x:xs -> put' xs >> return (Just x) +instance MonadConsume s m => MonadConsume s (ExceptT e m) where+ next = lift next++instance MonadConsume s m => MonadConsume s (StateT d m) where+ next = lift next+ -- | Runs the consume action, returning the remainder of the stream, and the -- action's result. runConsumeT :: Monad m => [s] -> ConsumeT s m a -> m ([s], a)-runConsumeT stream (ConsumeT m) = liftM swap $ runStateT m stream+runConsumeT stream (ConsumeT m) = swap <$> runStateT m stream -- | Runs the consume action, returning the action's result. evalConsumeT :: Monad m => [s] -> ConsumeT s m a -> m a-evalConsumeT stream = liftM snd . runConsumeT stream+evalConsumeT stream = fmap snd . runConsumeT stream -- | Runs the consume action, returning the remainder of the stream. execConsumeT :: Monad m => [s] -> ConsumeT s m a -> m [s]-execConsumeT stream = liftM fst . runConsumeT stream+execConsumeT stream = fmap fst . runConsumeT stream++type Consume s = ConsumeT s Identity++runConsume :: [s] -> Consume s a -> ([s], a)+runConsume stream m = runIdentity $ runConsumeT stream m++evalConsume :: [s] -> Consume s a -> a+evalConsume stream = snd . runConsume stream++execConsume :: [s] -> Consume s a -> [s]+execConsume stream = fst . runConsume stream get' :: Monad m => ConsumeT s m [s] get' = ConsumeT get
+ src/Foreign/Hoppy/Generator/Compiler.hs view
@@ -0,0 +1,180 @@+-- This file is part of Hoppy.+--+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net>+--+-- 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 <http://www.gnu.org/licenses/>.++-- | Data types for compilers and functions for invoking them.+module Foreign.Hoppy.Generator.Compiler (+ -- * Typeclass+ Compiler (..),+ SomeCompiler (..),+ -- * Data types+ SimpleCompiler (..),+ prependArguments,+ appendArguments,+ overrideCompilerFromEnvironment,+ CustomCompiler (..),+ -- * Standard compilers+ defaultCompiler,+ gppCompiler,+ ) where++import Control.Exception (IOException, try)+import Data.Either (partitionEithers)+import Data.Maybe (fromMaybe)+import qualified Data.Map as M+import Data.Text (pack, splitOn, unpack)+import Foreign.Hoppy.Generator.Common (filterMaybe, strInterpolate)+import System.Environment (lookupEnv)+import System.Exit (ExitCode (ExitFailure, ExitSuccess))+import System.IO (hPutStrLn, stderr)+import System.IO.Unsafe (unsafePerformIO)+import System.Process (createProcess_, proc, showCommandForUser, waitForProcess)++-- | A compiler that exists on the system for compiling C++ code.+class Show a => Compiler a where+ -- | @compileProgram compiler infile outfile@ invokes the given compiler in+ -- the input file, to produce the output file. If the compiler fails or can't+ -- be called for whatever reason, then an error message is printed to standard+ -- error, and false is returned.+ compileProgram :: a -> FilePath -> FilePath -> IO Bool++-- | An existential data type for 'Compiler's.+data SomeCompiler = forall a. Compiler a => SomeCompiler a++instance Show SomeCompiler where+ show (SomeCompiler c) = "<SomeCompiler " ++ show c ++ ">"++instance Compiler SomeCompiler where+ compileProgram (SomeCompiler c) = compileProgram c++-- | A compiler that can compile a source file into a binary with a single+-- program invocation.+--+-- Within the strings in this data type, including the program path, all+-- occurences of @{in}@ and @{out}@ are expanded to the input and desired output+-- files, respectively.+data SimpleCompiler = SimpleCompiler+ { scProgram :: FilePath+ -- ^ The name of the compiler program to call. Lookup is subject to the+ -- regular search path rules of your operating system.+ , scArguments :: [String]+ -- ^ Arguments to pass to the compiler. Each string is passed as a separate+ -- argument. No further word splitting is done.+ }++instance Show SimpleCompiler where+ show compiler =+ "<SimpleCompiler " ++ show (scProgram compiler) ++ " " +++ show (scArguments compiler) ++ ">"++instance Compiler SimpleCompiler where+ compileProgram compiler inPath outPath =+ runProgram compiler+ (scProgram compiler)+ (scArguments compiler)+ (M.fromList [("in", inPath), ("out", outPath)])++-- | Adds arguments to the start of a compiler's argument list.+prependArguments :: [String] -> SimpleCompiler -> SimpleCompiler+prependArguments args compiler =+ compiler { scArguments = args ++ scArguments compiler }++-- | Adds arguments to the end of a compiler's argument list.+appendArguments :: [String] -> SimpleCompiler -> SimpleCompiler+appendArguments args compiler =+ compiler { scArguments = scArguments compiler ++ args }++-- | Modifies a 'SimpleCompiler' based on environment variables.+--+-- If @CXX@ is set and non-empty, it will override the compiler's 'scProgram'.+--+-- If @CXXFLAGS@ is set and non-empty, it will be split into words and each word+-- will be prepended as an argument to 'scArguments'. Quoting is not supported.+overrideCompilerFromEnvironment :: SimpleCompiler -> IO SimpleCompiler+overrideCompilerFromEnvironment compiler = do+ envProgram <- filterMaybe "" <$> lookupEnv "CXX"+ envArguments <- filter (/= "") . splitOnSpace . fromMaybe "" <$> lookupEnv "CXXFLAGS"+ return compiler+ { scProgram = fromMaybe (scProgram compiler) envProgram+ , scArguments = envArguments ++ scArguments compiler+ }+ where splitOnSpace = map unpack . splitOn (pack " ") . pack++-- | A 'Compiler' that allows plugging arbitary logic into the compilation+-- process.+data CustomCompiler = CustomCompiler+ { ccLabel :: String+ -- ^ A label to display when the compiler is 'show'n. The string is+ -- @\"\<CustomCompiler \" ++ label ++ \">\"@.+ , ccCompile :: FilePath -> FilePath -> IO Bool+ -- ^ Given a source file path and an output path, compiles the source file,+ -- producing a binary at the output path. Returns true on success. Logs to+ -- standard error and returns false on failure.+ }++instance Show CustomCompiler where+ show c = "<CustomCompiler " ++ ccLabel c ++ ">"++instance Compiler CustomCompiler where+ compileProgram = ccCompile++-- | The default compiler, used by an 'Foreign.Hoppy.Generator.Spec.Interface'+-- that doesn't specify its own. This is:+--+-- @'unsafePerformIO' $ 'overrideCompilerFromEnvironment' 'gppCompiler'@+defaultCompiler :: SimpleCompiler+{-# NOINLINE defaultCompiler #-}+defaultCompiler = unsafePerformIO $ overrideCompilerFromEnvironment gppCompiler++-- | The GNU C++ compiler, invoked as @g++ -o {out} {in}@.+gppCompiler :: SimpleCompiler+gppCompiler =+ SimpleCompiler+ { scProgram = "g++"+ , scArguments = ["-o", "{out}", "{in}"]+ }++-- | Invokes a program as part of running a compiler. Performs argument+-- interpolation on the program and argument strings. Returns true if the+-- program executes successfully, and false otherwise (logging to stderr).+runProgram :: Show a => a -> FilePath -> [String] -> M.Map String String -> IO Bool+runProgram compiler rawProgram rawArgs values = do+ let interpolationResults =+ partitionEithers $+ map (strInterpolate values) (rawProgram:rawArgs)+ case interpolationResults of+ (unknownKey:_, _) -> do+ hPutStrLn stderr $+ "Error: Hit unknown binding {" ++ unknownKey ++ "} when executing C++ compiler '" +++ show compiler ++ ". program = " ++ show rawProgram ++ ", arguments = " +++ show rawArgs ++ "."+ return False+ ([], program:args) -> do+ let cmdLine = showCommandForUser program args+ forkResult <- try $ createProcess_ program $ proc program args+ case forkResult of+ Left (e :: IOException) -> do+ hPutStrLn stderr $+ "Error: Hoppy failed to invoke program (" ++ cmdLine ++ "): " ++ show e+ return False+ Right (_, _, _, procHandle) -> do+ exitCode <- waitForProcess procHandle+ case exitCode of+ ExitSuccess -> return True+ ExitFailure _ -> do+ hPutStrLn stderr $ "Error: Hoppy call to program failed (" ++ cmdLine ++ ")."+ return False+ ([], []) -> error "runProgram: Can't get here."
+ src/Foreign/Hoppy/Generator/Hook.hs view
@@ -0,0 +1,485 @@+-- This file is part of Hoppy.+--+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net>+--+-- 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 <http://www.gnu.org/licenses/>.++-- | Hooks for controlling various parts of generators.+module Foreign.Hoppy.Generator.Hook (+ Hooks (..),+ defaultHooks,+ -- * Enum evaluation+ EnumEvaluator,+ EnumEvaluatorArgs (..),+ EnumEvaluatorResult (..),+ evaluateEnumsWithCompiler,+ evaluateEnumsWithDefaultCompiler,+ makeCppSourceToEvaluateEnums,+ interpretOutputToEvaluateEnums,+ -- * Internal+ internalEvaluateEnumsForInterface,+ ) where++import Control.Arrow ((&&&))+import Control.Monad (forM, forM_, unless, when)+import Control.Monad.Except (ExceptT (ExceptT), MonadError, runExceptT, throwError)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.State (MonadState, execStateT, modify')+import Control.Monad.Writer (execWriter, tell)+import Data.ByteString.Lazy (ByteString, hPut)+import Data.ByteString.Builder (stringUtf8, toLazyByteString)+import qualified Data.Map as M+import Data.Maybe (isJust, listToMaybe, mapMaybe)+import qualified Data.Set as S+import Foreign.C (CInt, CLong, CLLong, CUInt, CULong, CULLong)+import Foreign.Hoppy.Generator.Common (doubleQuote, for, fromMaybeM, pluralize)+import Foreign.Hoppy.Generator.Common.Consume (MonadConsume, evalConsume, next)+import Foreign.Hoppy.Generator.Compiler (Compiler, SomeCompiler (SomeCompiler), compileProgram)+import Foreign.Hoppy.Generator.Language.Cpp (renderIdentifier)+import Foreign.Hoppy.Generator.Spec.Base+import Foreign.Hoppy.Generator.Types (intT, llongT, longT, uintT, ullongT, ulongT)+import Foreign.Hoppy.Generator.Util (withTempFile)+import Foreign.Storable (Storable, sizeOf)+import System.Exit (ExitCode (ExitFailure, ExitSuccess), exitFailure)+import System.IO (hClose, hPutStrLn, stderr)+import System.Process (readProcessWithExitCode)++-- | These hooks can be used to customize the behaviour of a Hoppy generator.+data Hooks = Hooks+ { hookEvaluateEnums :: EnumEvaluator+ -- ^ This hook is invoked once for an interface when the generator needs+ -- information about some enums beyond what's been baked into the interface+ -- (for example, to compute the enum's numeric type or entry values, see+ -- 'EvaluatedEnumData'). This will be called at most once per interface per+ -- invocation of the generator.+ }++-- | The default set of hooks associated with an interface. This sets+-- 'hookEvaluateEnums' to 'evaluateEnumsWithDefaultCompiler'.+defaultHooks :: Hooks+defaultHooks =+ Hooks+ { hookEvaluateEnums = evaluateEnumsWithDefaultCompiler+ }++-- | A function that answers with representation information about an enum (e.g.+-- entries' numeric values) for a given request. On success, it returns the+-- requested data. On failure, it prints a message to standard error and+-- returns @Nothing@.+type EnumEvaluator = EnumEvaluatorArgs -> IO (Maybe EnumEvaluatorResult)++-- | Inputs to the process of automatically evaluting enums.+data EnumEvaluatorArgs = EnumEvaluatorArgs+ { enumEvaluatorArgsInterface :: Interface+ -- ^ The interface that enum values are being calculated for.+ , enumEvaluatorArgsReqs :: Reqs+ -- ^ Requirements (includes, etc.) needed to reference the enum identifiers+ -- being evaluated.+ , enumEvaluatorArgsSizeofIdentifiers :: [Identifier]+ -- ^ The list of identifiers that we need to compute sizeof() for.+ , enumEvaluatorArgsEntryIdentifiers :: [Identifier]+ -- ^ The list of identifiers to calculate values for.+ , enumEvaluatorArgsKeepOutputsOnFailure :: Bool+ -- ^ Whether to leave temporary build inputs and outputs on disk in case the+ -- calculation fails. If failure does occur and this is true, then the+ -- calculation should print to standard error the location of these files+ -- (this is taken care of by the @calculateEnumValues*@ functions here.)+ }++-- | Raw outputs parsed from the output of an enum evaluator.+data EnumEvaluatorResult = EnumEvaluatorResult+ { enumEvaluatorResultSizes :: ![Int]+ -- ^ The sizeof() for each identifier in 'enumEvaluatorArgsSizeofIdentifiers'.+ -- The lengths of these two lists must match.+ , enumEvaluatorResultValues :: ![Integer]+ -- ^ The numeric value for each identifier in 'enumEvaluatorArgsEntryIdentifiers'.+ -- The lengths of these two lists must match.+ } deriving (Show)++-- | An 'EnumEvaluatorResult' without any data in it.+emptyEnumEvaluatorResult :: EnumEvaluatorResult+emptyEnumEvaluatorResult = EnumEvaluatorResult+ { enumEvaluatorResultSizes = []+ , enumEvaluatorResultValues = []+ }++-- | Calculates enum values using an interface's compiler.+evaluateEnumsWithDefaultCompiler :: EnumEvaluator+evaluateEnumsWithDefaultCompiler args = do+ let iface = enumEvaluatorArgsInterface args+ case interfaceCompiler iface of+ Just (SomeCompiler compiler) -> evaluateEnumsWithCompiler compiler args+ Nothing -> do+ hPutStrLn stderr $+ "evaluateEnumsWithDefaultCompiler: Don't have a compiler to evaluate enums with in " +++ show iface ++ "."+ return Nothing++-- | Evaluate enums using a specified compiler.+evaluateEnumsWithCompiler :: Compiler a => a -> EnumEvaluator+evaluateEnumsWithCompiler compiler args =+ withTempFile "hoppy-enum.cpp" removeBuildFailures $ \cppPath cppHandle ->+ withTempFile "hoppy-enum" removeBuildFailures $ \binPath binHandle -> do+ hPut cppHandle program+ hClose cppHandle+ hClose binHandle+ success <- compileProgram compiler cppPath binPath+ result <- case success of+ False -> do+ hPutStrLn stderr $+ "evaluateEnumsWithCompiler: Failed to build program " ++ show cppPath +++ " to evaluate enums with " ++ show compiler ++ "." ++ removeBuildFailuresNote+ return Nothing+ True -> runAndGetOutput binPath+ let remove = isJust result || removeBuildFailures+ return (remove, (remove, result))++ where removeBuildFailures = not $ enumEvaluatorArgsKeepOutputsOnFailure args++ removeBuildFailuresNote =+ if removeBuildFailures+ then " Pass --keep-temp-outputs-on-failure to keep build outputs around for debugging."+ else " --keep-temp-outputs-on-failure was given, leaving files on disk."++ program = makeCppSourceToEvaluateEnums args++ runAndGetOutput :: FilePath -> IO (Maybe EnumEvaluatorResult)+ runAndGetOutput binPath = do+ result <- runExceptT $ do+ (exitCode, out, err) <- liftIO $ readProcessWithExitCode binPath [] ""+ case exitCode of+ ExitFailure code ->+ throwError $+ "evaluateEnumsWithCompiler: Failed to run binary " ++ show binPath +++ ", code = " ++ show code ++ ", stdout = <<<" ++ out ++ ">>>, stderr = <<<" +++ err ++ ">>>." ++ removeBuildFailuresNote+ ExitSuccess ->+ ExceptT $ return $ interpretOutputToEvaluateEnums args out++ case result of+ Right value -> return $ Just value+ Left err -> do+ hPutStrLn stderr err+ return Nothing++-- | Constructs the C++ source program to evaluate enums.+makeCppSourceToEvaluateEnums :: EnumEvaluatorArgs -> ByteString+makeCppSourceToEvaluateEnums args =+ toLazyByteString $ stringUtf8 $ unlines $+ [ "#include <iostream>"+ , ""+ ] ++ [concatMap includeToString $+ S.elems $ reqsIncludes $ enumEvaluatorArgsReqs args] +++ [ ""+ , "int main() {"+ , " std::cout << \"#sizes\\n\";"+ ] ++ for (enumEvaluatorArgsSizeofIdentifiers args)+ (\identifier ->+ let rendered = renderIdentifier identifier+ in " std::cout << sizeof(" ++ rendered ++ ") << ' ' << " +++ doubleQuote rendered ++ " << '\\n';") +++ [ " std::cout << \"#values\\n\";"+ ] ++ for (enumEvaluatorArgsEntryIdentifiers args)+ (\identifier ->+ let rendered = renderIdentifier identifier+ in " std::cout << (" ++ rendered ++ ") << ' ' << " +++ doubleQuote rendered ++ " << '\\n';") +++ [ ""+ , " return 0;"+ , "}"+ ]++-- | Interprets the output of a program generated by+-- 'makeCppSourceToEvaluateEnums', returning parsed values if successful, and an+-- error string otherwise.+interpretOutputToEvaluateEnums ::+ EnumEvaluatorArgs+ -> String+ -> Either String EnumEvaluatorResult+interpretOutputToEvaluateEnums args out =+ evalConsume (lines out) $ runExceptT $ flip execStateT emptyEnumEvaluatorResult $ do+ expectLine "#sizes"+ readSizes $ enumEvaluatorArgsSizeofIdentifiers args+ expectLine "#values"+ readValues $ enumEvaluatorArgsEntryIdentifiers args+ expectEof+ modify' $ \EnumEvaluatorResult+ { enumEvaluatorResultSizes = sizes+ , enumEvaluatorResultValues = values+ } ->+ EnumEvaluatorResult+ { enumEvaluatorResultSizes = reverse sizes+ , enumEvaluatorResultValues = reverse values+ }+ where expectEof :: (MonadConsume String m, MonadError String m) => m ()+ expectEof = next >>= \case+ Nothing -> return ()+ Just line -> throwError $ "Expected EOF, got " ++ show line ++ "."++ expectLine :: (MonadConsume String m, MonadError String m) => String -> m ()+ expectLine expected = do+ line <- next+ when (line /= Just expected) $+ throwError $ "Expected " ++ show expected ++ ", got " ++ show line ++ "."++ expectIdentifier :: (MonadError String m, Read a) => Identifier -> String -> m a+ expectIdentifier identifier line = case reads line of+ [(value, ' ':identStr)] -> do+ let expectedStr = renderIdentifier identifier+ unless (identStr == expectedStr) $+ throwError $ "Expected identifier " ++ show expectedStr ++ ", but saw identifier " +++ show identStr ++ "."+ return value+ _ ->+ throwError $ "Expected a line for " ++ show identifier ++ ", but got line " +++ show line ++ "."++ readSizes :: (MonadConsume String m, MonadError String m, MonadState EnumEvaluatorResult m)+ => [Identifier]+ -> m ()+ readSizes expectedIdentifiers = case expectedIdentifiers of+ [] -> return ()+ expectedIdentifier:restIdentifiers -> next >>= \case+ Just line -> do+ size <- expectIdentifier expectedIdentifier line+ modify' $ \r@EnumEvaluatorResult { enumEvaluatorResultSizes = sizes } ->+ r { enumEvaluatorResultSizes = size:sizes }+ readSizes restIdentifiers+ Nothing -> throwError "Unexpected end of input while reading enum sizes."++ readValues :: (MonadConsume String m, MonadError String m, MonadState EnumEvaluatorResult m)+ => [Identifier]+ -> m ()+ readValues expectedIdentifiers = case expectedIdentifiers of+ [] -> return ()+ expectedIdentifier:restIdentifiers -> next >>= \case+ Just line -> do+ value <- expectIdentifier expectedIdentifier line+ modify' $ \r@EnumEvaluatorResult { enumEvaluatorResultValues = values } ->+ r { enumEvaluatorResultValues = value:values }+ readValues restIdentifiers+ Nothing -> throwError "Unexpected end of input while reading enum sizes."++-- | Collects all of the enum values that need calculating in an interface, runs+-- the hook to evaluate them, and stores the result in the interface. This+-- won't recalculate enum data if it's already been calculated.+internalEvaluateEnumsForInterface :: Interface -> Bool -> IO Interface+internalEvaluateEnumsForInterface iface keepBuildFailures =+ case interfaceEvaluatedEnumData iface of+ Just _ -> return iface+ Nothing -> internalEvaluateEnumsForInterface' iface keepBuildFailures++internalEvaluateEnumsForInterface' :: Interface -> Bool -> IO Interface+internalEvaluateEnumsForInterface' iface keepBuildFailures = do+ let validateEnumTypes = interfaceValidateEnumTypes iface++ -- Collect all exports in the interface.+ allExports :: M.Map ExtName Export+ allExports = M.unions $ map moduleExports $ M.elems $ interfaceModules iface++ -- Collect pertinent information about all enum exports that we need to+ -- evaluate.+ --+ -- ExtName: The name of the enum.+ -- Maybe Type: The enum's numeric type, if explicitly set.+ -- Maybe Identifier: The enum's identifier, if we need to evaluate it.+ -- Reqs: Requirements to reference the enum.+ -- EnumValueMap: Entries, so that we can evaluate the auto ones.+ enumExports :: [(ExtName, Maybe Type, Maybe Identifier, Reqs, EnumValueMap)]+ enumExports = flip mapMaybe (M.elems allExports) $ \export ->+ flip fmap (getExportEnumInfo export) $ \(info :: EnumInfo) ->+ (enumInfoExtName info,+ enumInfoNumericType info,+ case (enumInfoNumericType info, validateEnumTypes) of+ (Just _, False) -> Nothing -- Don't need to evaluate sizeof().+ _ -> Just $ enumInfoIdentifier info, -- Need to evaluate sizeof().+ enumInfoReqs info,+ enumInfoValues info)++ -- Determine a list of all values to evaluate, and the Reqs required to do+ -- so.+ sumReqs :: Reqs+ sizeofIdentifiersToEvaluate :: [OrdIdentifier]+ entryIdentifiersToEvaluate :: [OrdIdentifier]+ (sumReqs, sizeofIdentifiersToEvaluate, entryIdentifiersToEvaluate) =+ (\(a, b, c) -> (a, b, S.toList $ S.fromList $ map OrdIdentifier c)) $+ execWriter $ forM_ enumExports $ \(_, _, maybeIdent, reqs, entries) -> do+ tell (reqs,+ maybe [] (\i -> [OrdIdentifier i]) maybeIdent,+ [])+ forM_ (M.toList $ enumValueMapValues entries) $ \(_, value) -> case value of+ EnumValueManual _ -> return ()+ EnumValueAuto identifier -> tell (mempty, [], [identifier])++ -- Evaluate the identifiers we are curious about, using the hook provided by+ -- the interface.+ evaluatorResult :: EnumEvaluatorResult <-+ case (sizeofIdentifiersToEvaluate, entryIdentifiersToEvaluate) of+ ([], []) -> return emptyEnumEvaluatorResult+ _ -> do+ let hooks = interfaceHooks iface+ args = EnumEvaluatorArgs+ { enumEvaluatorArgsInterface = iface+ , enumEvaluatorArgsReqs = sumReqs+ , enumEvaluatorArgsSizeofIdentifiers =+ map ordIdentifier sizeofIdentifiersToEvaluate+ , enumEvaluatorArgsEntryIdentifiers =+ map ordIdentifier entryIdentifiersToEvaluate+ , enumEvaluatorArgsKeepOutputsOnFailure = keepBuildFailures+ }+ hookEvaluateEnums hooks args >>=+ fromMaybeM+ (do hPutStrLn stderr $+ "internalEvaluateEnumsForInterface': Failed to build and run program. Aborting."+ exitFailure)++ let evaluatedIdentifierSizes :: M.Map OrdIdentifier Int+ evaluatedIdentifierSizes =+ M.fromList $ zip sizeofIdentifiersToEvaluate $ enumEvaluatorResultSizes evaluatorResult++ evaluatedIdentifierValues :: M.Map OrdIdentifier Integer+ evaluatedIdentifierValues =+ M.fromList $ zip entryIdentifiersToEvaluate $ enumEvaluatorResultValues evaluatorResult++ getIdentifierSize :: Identifier -> IO Int+ getIdentifierSize identifier =+ fromMaybeM+ (do hPutStrLn stderr $+ "internalEvaluateEnumsForInterface': Internal error, " +++ "failed to find evaluated size for " ++ show identifier ++ "."+ exitFailure) $+ M.lookup (OrdIdentifier identifier) evaluatedIdentifierSizes++ getIdentifierValue :: Identifier -> IO Integer+ getIdentifierValue identifier =+ fromMaybeM+ (do hPutStrLn stderr $+ "internalEvaluateEnumsForInterface': Internal error, " +++ "failed to find evaluated value for " ++ show identifier ++ "."+ exitFailure) $+ M.lookup (OrdIdentifier identifier) evaluatedIdentifierValues++ getNumericTypeInfo :: ExtName -> Type -> IO NumericTypeInfo+ getNumericTypeInfo extName t =+ fromMaybeM+ (do hPutStrLn stderr $+ "internalEvaluateEnumsForInterface': Explicit type " ++ show t +++ " for enum " ++ show extName ++ " is not a usable numeric type."+ exitFailure) $+ findNumericTypeInfo t++ -- Build a map containing the evaluated type and numeric values for all of+ -- the enums in the interface.+ evaluatedDataMap :: M.Map ExtName EvaluatedEnumData <-+ fmap M.fromList $ forM enumExports $ \(extName, maybeNumericType, maybeIdent, _, values) -> do+ -- Build a map containing all of the numeric values in the enum.+ numMap :: M.Map [String] Integer <-+ fmap M.fromList $ forM (M.toList $ enumValueMapValues values) $ \(label, value) -> do+ num <- case value of+ EnumValueManual n -> return n+ EnumValueAuto entryIdent -> getIdentifierValue entryIdent+ return (label, num)++ -- Determine the bounds for those values, and use those to select a+ -- numeric type that we should use outside of C++ to represent the enum's+ -- values. C++ doesn't give us a way to ask for the numeric type it uses+ -- directly, so we manually pick a numeric type of the right size that can+ -- handle everything.+ bytes <- case (maybeNumericType, maybeIdent) of+ (Just numericType, Just identifier) -> do+ providedBytes <- numBytes <$> getNumericTypeInfo extName numericType+ evaluatedBytes <- getIdentifierSize identifier++ -- Verify that the explicit numeric type set on the enum is correct to+ -- use.+ when (providedBytes /= evaluatedBytes) $ do+ hPutStrLn stderr $+ "internalEvaluateEnumsForInterface': The explicit type " ++ show numericType +++ " for enum " ++ show extName ++ " takes " ++ pluralize providedBytes "byte" "bytes" +++ ", but sizeof(" ++ renderIdentifier identifier ++ ") evaluates to " +++ pluralize evaluatedBytes "byte" "bytes" ++ "."+ exitFailure++ return providedBytes++ (Just numericType, Nothing) -> numBytes <$> getNumericTypeInfo extName numericType++ (Nothing, Just identifier) -> getIdentifierSize identifier++ (Nothing, Nothing) ->+ error $ "internalEvaluateEnumsForInterface': Internal error, don't have a size for " +++ "enum " ++ show extName ++ ", shouldn't happen."++ let (low, high) = minimum &&& maximum $ M.elems numMap+ numericType <-+ fromMaybeM+ (do hPutStrLn stderr $+ "internalEvaluateEnumsForInterface': Couldn't find a numeric type " +++ "to use to represent the C++ enumeration " ++ show extName ++ "."+ exitFailure) $+ pickNumericType bytes low high++ let result = EvaluatedEnumData+ { evaluatedEnumType = numericType+ , evaluatedEnumValueMap = numMap+ }+ return (extName, result)++ return iface { interfaceEvaluatedEnumData = Just evaluatedDataMap }++newtype OrdIdentifier = OrdIdentifier { ordIdentifier :: Identifier }+ deriving (Eq, Show)++instance Ord OrdIdentifier where+ compare (OrdIdentifier i1) (OrdIdentifier i2) =+ compare (renderIdentifier i1) (renderIdentifier i2)++-- | Bound information about numeric types.+data NumericTypeInfo = NumericTypeInfo+ { numType :: Type+ , numBytes :: Int+ , numMinBound :: Integer+ , numMaxBound :: Integer+ }++-- | Numeric types usable to hold enum values. These are ordered by decreasing+-- precedence (increasing word size).+numericTypeInfo :: [NumericTypeInfo]+numericTypeInfo =+ [ mk intT (undefined :: CInt)+ , mk uintT (undefined :: CUInt)+ , mk longT (undefined :: CLong)+ , mk ulongT (undefined :: CULong)+ , mk llongT (undefined :: CLLong)+ , mk ullongT (undefined :: CULLong)+ ]+ where mk :: forall a. (Bounded a, Integral a, Storable a) => Type -> a -> NumericTypeInfo+ mk t _ = NumericTypeInfo+ { numType = t+ , numBytes = sizeOf (undefined :: a)+ , numMinBound = toInteger (minBound :: a)+ , numMaxBound = toInteger (maxBound :: a)+ }++findNumericTypeInfo :: Type -> Maybe NumericTypeInfo+findNumericTypeInfo t = listToMaybe $ filter (\i -> numType i == t) numericTypeInfo++-- | Selects the preferred numeric type for holding numeric values in the given+-- range.+pickNumericType :: Int -> Integer -> Integer -> Maybe Type+pickNumericType bytes low high =+ fmap numType $ listToMaybe $ flip filter numericTypeInfo $ \info ->+ numBytes info == bytes &&+ numMinBound info <= low &&+ numMaxBound info >= high
+ src/Foreign/Hoppy/Generator/Hook.hs-boot view
@@ -0,0 +1,25 @@+-- This file is part of Hoppy.+--+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net>+--+-- 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 <http://www.gnu.org/licenses/>.++module Foreign.Hoppy.Generator.Hook (+ Hooks,+ defaultHooks,+ ) where++data Hooks++defaultHooks :: Hooks
src/Foreign/Hoppy/Generator/Language/Cpp.hs view
@@ -1,6 +1,6 @@ -- This file is part of Hoppy. ----- Copyright 2015-2018 Bryan Gardiner <bog@khumba.net>+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net> -- -- 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@@ -19,45 +19,125 @@ -- | Shared portion of the C++ code generator. Usable by binding definitions. module Foreign.Hoppy.Generator.Language.Cpp (+ -- * Code generation monad+ Generator,+ Env,+ execGenerator,+ addIncludes, addInclude, addReqsM,+ askInterface, askModule, abort,+ -- * Names+ makeCppName, externalNameToCpp,- classDeleteFnCppName,- classCastFnCppName,- callbackClassName,- callbackImplClassName,- callbackFnName, toArgName, toArgNameAlt, exceptionIdArgName, exceptionPtrArgName, exceptionVarName, exceptionRethrowFnName,+ -- * Token rendering Chunk (..),+ codeChunk,+ includesChunk, runChunkWriter, evalChunkWriter, execChunkWriter, runChunkWriterT, evalChunkWriterT, execChunkWriterT,+ -- * High-level code generation+ SayExportMode (..), say, says, sayIdentifier,+ renderIdentifier, sayVar, sayType,+ sayFunction,+ -- * Auxiliary functions+ typeToCType,+ typeReqs,+ findExportModule,+ getEffectiveExceptionHandlers, ) where -import Control.Monad (liftM)+import Control.Monad (unless)+import Control.Monad.Reader (MonadReader, ReaderT, ask, runReaderT) import Control.Monad.Writer (MonadWriter, Writer, WriterT, runWriter, runWriterT, tell)+import Control.Monad.Trans (lift) import Data.Foldable (forM_) import Data.List (intercalate, intersperse)+import qualified Data.Map as M+import qualified Data.Set as S import Foreign.Hoppy.Generator.Common-import Foreign.Hoppy.Generator.Spec+import Foreign.Hoppy.Generator.Spec.Base+import {-# SOURCE #-} Foreign.Hoppy.Generator.Spec.Class (classIdentifier, classReqs) import Foreign.Hoppy.Generator.Types -cppNameSeparator :: String-cppNameSeparator = "__"+-- | A generator monad for C++ code.+--+-- TODO This should not simply be a type synonym.+type Generator = ReaderT Env (WriterT [Chunk] (Either ErrorMsg)) +-- | Context information for generating C++ code.+data Env = Env+ { envInterface :: Interface+ , envModule :: Module+ }++-- | Runs a generator action and returns its output, or an error message if+-- unsuccessful.+execGenerator :: Interface -> Module -> Maybe String -> Generator a -> Either ErrorMsg String+execGenerator iface m maybeHeaderGuardName action = do+ chunk <- execChunkWriterT $ runReaderT action $ Env iface m+ let contents = chunkContents chunk+ includes = chunkIncludes chunk+ return $ chunkContents $ execChunkWriter $ do+ say "////////// GENERATED FILE, EDITS WILL BE LOST //////////\n"+ forM_ maybeHeaderGuardName $ \x -> do+ says ["\n#ifndef ", x, "\n"]+ says ["#define ", x, "\n"]+ unless (S.null includes) $ do+ say "\n"+ forM_ includes $ say . includeToString+ say "\nextern \"C\" {\n"+ say contents+ say "\n} // extern \"C\"\n"+ forM_ maybeHeaderGuardName $ \x ->+ says ["\n#endif // ifndef ", x, "\n"]++-- | Adds @#include@ statements to the includes block generated at the top of+-- the currently generating file.+addIncludes :: MonadWriter [Chunk] m => [Include] -> m ()+addIncludes = tell . (:[]) . includesChunk . S.fromList++-- | Adds an @#include@ statement to the includes block generated at the top of+-- the currently generating file.+addInclude :: MonadWriter [Chunk] m => Include -> m ()+addInclude = addIncludes . (:[])++-- | Adds requirements ('Reqs' i.e. C++ includes) to the includes block+-- generated at the top of the currently generating file.+--+-- Have to call this @addReqsM@, 'addReqs' is taken by 'HasReqs'.+addReqsM :: MonadWriter [Chunk] m => Reqs -> m ()+addReqsM = tell . (:[]) . includesChunk . reqsIncludes++-- | Returns the currently generating interface.+askInterface :: MonadReader Env m => m Interface+askInterface = fmap envInterface ask++-- | Returns the currently generating module.+askModule :: MonadReader Env m => m Module+askModule = fmap envModule ask++-- | Halts generation and returns the given error message.+abort :: ErrorMsg -> Generator a+abort = lift . lift . Left++-- | Constructs a C++ identifier by combining a list of strings with @__@. makeCppName :: [String] -> String makeCppName = intercalate cppNameSeparator+ where cppNameSeparator = "__" -- | \"genpop\" is the prefix used for individually exported functions. externalNamePrefix :: String@@ -68,42 +148,6 @@ externalNameToCpp extName = makeCppName [externalNamePrefix, fromExtName extName] -makeClassCppName :: String -> Class -> String-makeClassCppName prefix cls = makeCppName [prefix, fromExtName $ classExtName cls]---- | \"gendel\" is the prefix used for wrappers for @delete@ calls.-classDeleteFnPrefix :: String-classDeleteFnPrefix = "gendel"---- | Returns the C++ binding function name of the wrapper for the delete method--- for a class.-classDeleteFnCppName :: Class -> String-classDeleteFnCppName = makeClassCppName classDeleteFnPrefix---- | @classCastFnCppName fromCls toCls@ returns the name of the generated C++--- function that casts a pointer from @fromCls@ to @toCls@.-classCastFnCppName :: Class -> Class -> String-classCastFnCppName from to =- concat [ "gencast__"- , fromExtName $ classExtName from- , "__"- , fromExtName $ classExtName to- ]---- | Returns the name of the outer, copyable class for a callback.-callbackClassName :: Callback -> String-callbackClassName = fromExtName . callbackExtName---- | Returns the name of the internal, non-copyable implementation class for a--- callback.-callbackImplClassName :: Callback -> String-callbackImplClassName = (++ "_impl") . fromExtName . callbackExtName---- | Returns the name of the C++ binding function that creates a C++ callback--- wrapper object from a function pointer to foreign code.-callbackFnName :: Callback -> String-callbackFnName = externalNameToCpp . callbackExtName- -- | Returns a distinct argument variable name for each nonnegative number. toArgName :: Int -> String toArgName = ("arg" ++) . show@@ -141,15 +185,35 @@ identifierChars :: String identifierChars = ['A'..'Z'] ++ ['a'..'z'] ++ ['0'..'9'] ++ "_" --- | A chunk is a string that contains an arbitrary portion of C++ code. The--- only requirement is that chunk boundaries are also C++ token boundaries,--- because the generator monad automates the process of inserting whitespace--- between chunk boundaries where necessary.-newtype Chunk = Chunk { chunkContents :: String }+-- | A chunk is a string that contains an arbitrary portion of C++ code,+-- together with a set of includes. The only requirement is that chunk's code+-- boundaries are also C++ token boundaries, because the generator monad+-- automates the process of inserting whitespace between chunk boundaries where+-- necessary.+data Chunk = Chunk+ { chunkContents :: !String+ , chunkIncludes :: !(S.Set Include)+ } +-- | Builds a 'Chunk' that contains the given code string.+codeChunk :: String -> Chunk+codeChunk code =+ Chunk+ { chunkContents = code+ , chunkIncludes = S.empty+ }++-- | Builds a 'Chunk' that contains the given includes.+includesChunk :: S.Set Include -> Chunk+includesChunk includes =+ Chunk+ { chunkContents = ""+ , chunkIncludes = includes+ }+ -- | Runs a 'Chunk' writer, combining them with 'combineChunks' to form a single -- string.-runChunkWriter :: Writer [Chunk] a -> (a, String)+runChunkWriter :: Writer [Chunk] a -> (a, Chunk) runChunkWriter = fmap combineChunks . runWriter -- | Runs a 'Chunk' writer and returns the monad's value.@@ -157,46 +221,64 @@ evalChunkWriter = fst . runChunkWriter -- | Runs a 'Chunk' writer and returns the written log.-execChunkWriter :: Writer [Chunk] a -> String+execChunkWriter :: Writer [Chunk] a -> Chunk execChunkWriter = snd . runChunkWriter -- | Runs a 'Chunk' writer transformer, combining them with 'combineChunks' to -- form a single string.-runChunkWriterT :: Monad m => WriterT [Chunk] m a -> m (a, String)-runChunkWriterT = liftM (fmap combineChunks) . runWriterT+runChunkWriterT :: Monad m => WriterT [Chunk] m a -> m (a, Chunk)+runChunkWriterT = fmap (fmap combineChunks) . runWriterT -- | Runs a 'Chunk' writer transformer and returns the monad's value. evalChunkWriterT :: Monad m => WriterT [Chunk] m a -> m a-evalChunkWriterT = liftM fst . runChunkWriterT+evalChunkWriterT = fmap fst . runChunkWriterT -- | Runs a 'Chunk' writer transformer and returns the written log.-execChunkWriterT :: Monad m => WriterT [Chunk] m a -> m String-execChunkWriterT = liftM snd . runChunkWriterT+execChunkWriterT :: Monad m => WriterT [Chunk] m a -> m Chunk+execChunkWriterT = fmap snd . runChunkWriterT --- | Flattens a list of chunks down into a single string. Inserts spaces+-- | Flattens a list of chunks down into a single chunk. Inserts spaces -- between chunks where the ends of adjacent chunks would otherwise merge into a--- single C++ token.-combineChunks :: [Chunk] -> String+-- single C++ token. Combines include sets into a single include set.+combineChunks :: [Chunk] -> Chunk combineChunks chunks = let strs = map chunkContents chunks- in concat $ for (zip ("":strs) strs) $ \(prev, cur) ->- let needsSpace =- not (null prev) && not (null cur) &&- (let a = last prev- b = head cur- in -- "intconstx" should become "int const x"- isIdentifierChar a && isIdentifierChar b ||- -- Adjacent template parameter '>'s need spacing in old C++.- a == '>' && b == '>')- in if needsSpace then ' ':cur else cur+ in Chunk+ { chunkContents =+ concat $ for (zip ("":strs) strs) $ \(prev, cur) ->+ let needsSpace =+ not (null prev) && not (null cur) &&+ (let a = last prev+ b = head cur+ in -- "intconstx" should become "int const x"+ isIdentifierChar a && isIdentifierChar b ||+ -- Adjacent template parameter '>'s need spacing in old C++.+ a == '>' && b == '>')+ in if needsSpace then ' ':cur else cur + , chunkIncludes = S.unions $ map chunkIncludes chunks+ }++-- | The section of code that Hoppy is generating, for an export.+data SayExportMode =+ SaySource+ -- ^ Hoppy is generating the C++ source file for a module. The generator+ -- should emit C++ definitions that will be imported over foreign language's+ -- FFIs. This is the main place for code generation in C++ bindings.+ | SayHeader+ -- ^ Hoppy is generating the C++ header file for a module. The generator+ -- should emit C++ declarations that can be @#include@d during the source+ -- file generation of other exportable entities, in order to refer to the+ -- current entity. If it is not possible for other entities to refer to+ -- this one, then nothing needs to be generated.+ -- | Emits a single 'Chunk'. say :: MonadWriter [Chunk] m => String -> m ()-say = tell . (:[]) . Chunk+say = tell . (:[]) . codeChunk -- | Emits a 'Chunk' for each string in a list. says :: MonadWriter [Chunk] m => [String] -> m ()-says = tell . map Chunk+says = tell . map codeChunk -- | Emits an 'Identifier'. sayIdentifier :: MonadWriter [Chunk] m => Identifier -> m ()@@ -211,6 +293,10 @@ sequence_ $ intersperse (say ", ") $ map (sayType Nothing) args say ">" +-- | Renders an 'Identifier' to a string.+renderIdentifier :: Identifier -> String+renderIdentifier = chunkContents . execChunkWriter . sayIdentifier+ -- | @sayVar name maybeParamNames t@ speaks a variable declaration of the form -- @\<type\> \<name\>@, where @\<name\>@ is the given name, and @\<type\>@ is -- rendered by giving @maybeParamNames@ and @t@ to 'sayType'.@@ -236,49 +322,21 @@ else say "(" >> unwrappedOuter >> say ")" in case t of Internal_TVoid -> say "void" >> outer- Internal_TBool -> say "bool" >> outer- Internal_TChar -> say "char" >> outer- Internal_TUChar -> say "unsigned char" >> outer- Internal_TShort -> say "short" >> outer- Internal_TUShort -> say "unsigned short" >> outer- Internal_TInt -> say "int" >> outer- Internal_TUInt -> say "unsigned int" >> outer- Internal_TLong -> say "long" >> outer- Internal_TULong -> say "unsigned long" >> outer- Internal_TLLong -> say "long long" >> outer- Internal_TULLong -> say "unsigned long long" >> outer- Internal_TFloat -> say "float" >> outer- Internal_TDouble -> say "double" >> outer- Internal_TInt8 -> say "int8_t" >> outer- Internal_TInt16 -> say "int16_t" >> outer- Internal_TInt32 -> say "int32_t" >> outer- Internal_TInt64 -> say "int64_t" >> outer- Internal_TWord8 -> say "uint8_t" >> outer- Internal_TWord16 -> say "uint16_t" >> outer- Internal_TWord32 -> say "uint32_t" >> outer- Internal_TWord64 -> say "uint64_t" >> outer- Internal_TPtrdiff -> say "ptrdiff_t" >> outer- Internal_TSize -> say "size_t" >> outer- Internal_TSSize -> say "ssize_t" >> outer- Internal_TEnum e -> sayIdentifier (enumIdentifier e) >> outer- Internal_TBitspace b -> case bitspaceCppTypeIdentifier b of- Just identifier -> sayIdentifier identifier >> outer- Nothing -> sayType' (bitspaceType b) maybeParamNames outerPrec unwrappedOuter Internal_TPtr t' -> sayType' t' Nothing prec $ say "*" >> outer Internal_TRef t' -> sayType' t' Nothing prec $ say "&" >> outer- Internal_TFn paramTypes retType -> sayType' retType Nothing prec $ do+ Internal_TFn params retType -> sayType' retType Nothing prec $ do outer say "(" sequence_ $ intersperse (say ", ") $- for (zip paramTypes $ maybe (repeat Nothing) (map Just) maybeParamNames) $- \(ptype, pname) ->- sayType' ptype Nothing topPrecedence $ forM_ pname say+ for (zip params $ maybe (repeat Nothing) (map Just) $ maybeParamNames) $+ \(param, pname) ->+ sayType' (parameterType param) Nothing topPrecedence $ forM_ pname say say ")"- Internal_TCallback cb -> says [callbackImplClassName cb, "*"] >> outer Internal_TObj cls -> sayIdentifier (classIdentifier cls) >> outer Internal_TObjToHeap cls -> sayType' (refT $ constT $ objT cls) maybeParamNames outerPrec unwrappedOuter Internal_TToGc t' -> sayType' t' maybeParamNames outerPrec unwrappedOuter+ Internal_TManual s -> say (conversionSpecCppName $ conversionSpecCpp s) >> outer Internal_TConst t' -> sayType' t' maybeParamNames outerPrec $ say "const" >> unwrappedOuter -- TODO ^ Is using the outer stuff correctly here? @@ -291,3 +349,79 @@ Internal_TPtr {} -> 9 Internal_TRef {} -> 9 _ -> 8++-- | Renders a C++ function.+sayFunction ::+ String -- ^ Function name.+ -> [String] -- ^ Parameter names.+ -> Type -- ^ Function type. This should use 'fnT' or 'fnT''.+ -> Maybe (Generator ())+ -- ^ If present, then the function is defined and the action here is used+ -- to render its body. If absent, then the function is only declared (no+ -- function body).+ -> Generator ()+sayFunction name paramNames t maybeBody = do+ case t of+ Internal_TFn {} -> return ()+ _ -> abort $ concat ["sayFunction: A function type is required, given ", show t, "."]+ say "\n" -- New top-level structure, leave a blank line.+ sayVar name (Just paramNames) t+ case maybeBody of+ Nothing -> say ";\n"+ Just body -> do+ say " {\n"+ body -- TODO Indent.+ say "}\n"++-- | Returns a 'Type' iff there is a C type distinct from the given C++ type+-- that should be used for conversion.+--+-- This returns @Nothing@ for 'Internal_TManual'. TManual needs special+-- handling.+typeToCType :: Type -> Generator (Maybe Type)+typeToCType t = case t of+ Internal_TRef t' -> return $ Just $ ptrT t'+ Internal_TObj _ -> return $ Just $ ptrT $ constT t+ Internal_TObjToHeap cls -> return $ Just $ ptrT $ objT cls+ Internal_TToGc t'@(Internal_TObj _) -> return $ Just $ ptrT t'+ Internal_TToGc t' -> typeToCType t'+ Internal_TConst t' -> typeToCType t'+ Internal_TManual s -> conversionSpecCppConversionType $ conversionSpecCpp s+ _ -> return Nothing++-- | Returns the requirements to refer to a type from C++ code. This is a+-- monadic function so that it has access to the environment, but it does not+-- emit any code.+typeReqs :: Type -> Generator Reqs+typeReqs t = case t of+ Internal_TVoid -> return mempty+ Internal_TPtr t' -> typeReqs t'+ Internal_TRef t' -> typeReqs t'+ Internal_TFn params retType ->+ -- TODO Is the right 'ReqsType' being used recursively here?+ mconcat <$> mapM typeReqs (retType : map parameterType params)+ Internal_TObj cls -> return $ classReqs cls+ Internal_TObjToHeap cls -> return $ classReqs cls+ Internal_TToGc t' -> typeReqs t'+ Internal_TConst t' -> typeReqs t'+ Internal_TManual s -> conversionSpecCppReqs $ conversionSpecCpp s++-- | Looks up the module exporting the given external name in the current+-- interface. 'abort' is called if the external name is not found.+findExportModule :: ExtName -> Generator Module+findExportModule extName =+ fromMaybeM (abort $ concat+ ["findExportModule: Can't find module exporting ", fromExtName extName, "."]) =<<+ fmap (M.lookup extName . interfaceNamesToModules) askInterface++-- | Combines the given exception handlers (from a particular exported entity)+-- with the handlers from the current module and interface. The given handlers+-- have highest precedence, followed by module handlers, followed by interface+-- handlers.+getEffectiveExceptionHandlers :: ExceptionHandlers -> Generator ExceptionHandlers+getEffectiveExceptionHandlers handlers = do+ ifaceHandlers <- interfaceExceptionHandlers <$> askInterface+ moduleHandlers <- getExceptionHandlers <$> askModule+ -- Exception handlers declared lower in the hierarchy take precedence over+ -- those higher in the hierarchy; ExceptionHandlers is a left-biased monoid.+ return $ mconcat [handlers, moduleHandlers, ifaceHandlers]
+ src/Foreign/Hoppy/Generator/Language/Cpp.hs-boot view
@@ -0,0 +1,33 @@+-- This file is part of Hoppy.+--+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net>+--+-- 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 <http://www.gnu.org/licenses/>.++module Foreign.Hoppy.Generator.Language.Cpp (+ Generator,+ SayExportMode,+ ) where++import Control.Monad.Reader (ReaderT)+import Control.Monad.Writer (WriterT)+import {-# SOURCE #-} Foreign.Hoppy.Generator.Spec.Base (ErrorMsg)++type Generator = ReaderT Env (WriterT [Chunk] (Either ErrorMsg))++data Env++newtype Chunk = Chunk { chunkContents :: String }++data SayExportMode
src/Foreign/Hoppy/Generator/Language/Cpp/Internal.hs view
@@ -1,6 +1,6 @@ -- This file is part of Hoppy. ----- Copyright 2015-2018 Bryan Gardiner <bog@khumba.net>+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net> -- -- 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@@ -15,7 +15,7 @@ -- You should have received a copy of the GNU Affero General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>. -{-# LANGUAGE CPP, ViewPatterns #-}+{-# LANGUAGE CPP #-} -- | Internal portion of the C++ code generator. module Foreign.Hoppy.Generator.Language.Cpp.Internal (@@ -27,95 +27,24 @@ #if !MIN_VERSION_base(4,8,0) import Control.Applicative ((<$>)) #endif-import Control.Monad (liftM, unless, when)-import Control.Monad.Reader (MonadReader, ReaderT, ask, runReaderT)-import Control.Monad.Writer (WriterT, execWriterT, runWriterT, tell)+import Control.Monad (when)+import Control.Monad.Writer (execWriterT, tell) import Control.Monad.Trans (lift) import Data.Foldable (forM_) #if !MIN_VERSION_base(4,8,0) import Data.Functor ((<$)) #endif-import Data.List (intersperse) import qualified Data.Map as M-import Data.Maybe (catMaybes, fromMaybe, isJust) #if !MIN_VERSION_base(4,8,0) import Data.Monoid (mappend, mconcat, mempty) #endif-import qualified Data.Set as S import Foreign.Hoppy.Generator.Common import Foreign.Hoppy.Generator.Language.Cpp import Foreign.Hoppy.Generator.Spec import Foreign.Hoppy.Generator.Types -data CoderDirection = DoDecode | DoEncode- deriving (Eq, Show)--type Generator = ReaderT Env (WriterT [Chunk] (WriterT (S.Set Include) (Either ErrorMsg)))--data Env = Env- { envInterface :: Interface- , envModule :: Module- }--addIncludes :: [Include] -> Generator ()-addIncludes = lift . lift . tell . S.fromList--addInclude :: Include -> Generator ()-addInclude = addIncludes . (:[])---- Have to call this addReqsM, addReqs is taken by HasReqs.-addReqsM :: Reqs -> Generator ()-addReqsM = lift . lift . tell . reqsIncludes--askInterface :: MonadReader Env m => m Interface-askInterface = liftM envInterface ask--askModule :: MonadReader Env m => m Module-askModule = liftM envModule ask---- | Halts generation and returns the given error message.-abort :: ErrorMsg -> Generator a-abort = lift . lift . lift . Left--execGenerator :: Interface -> Module -> Maybe String -> Generator a -> Either ErrorMsg String-execGenerator interface m maybeHeaderGuardName action = do- (contents, includes) <-- (runWriterT $- -- WriterT (S.Set Include) (Either String) String:- execChunkWriterT $- -- WriterT [Chunk] (WriterT (S.Set Include) (Either String)) a:- runReaderT action $ Env interface m)- :: Either String (String, S.Set Include)- return $ execChunkWriter $ do- say "////////// GENERATED FILE, EDITS WILL BE LOST //////////\n"- forM_ maybeHeaderGuardName $ \x -> do- says ["\n#ifndef ", x, "\n"]- says ["#define ", x, "\n"]- unless (S.null includes) $ do- say "\n"- forM_ includes $ say . includeToString- say "\nextern \"C\" {\n"- say contents- say "\n} // extern \"C\"\n"- forM_ maybeHeaderGuardName $ \x ->- says ["\n#endif // ifndef ", x, "\n"]--sayFunction :: String -> [String] -> Type -> Maybe (Generator ()) -> Generator ()-sayFunction name paramNames t maybeBody = do- case t of- Internal_TFn {} -> return ()- _ -> abort $ concat ["sayFunction: A function type is required, given ", show t, "."]- say "\n" -- New top-level structure, leave a blank line.- sayVar name (Just paramNames) t- case maybeBody of- Nothing -> say ";\n"- Just body -> do- say " {\n"- body -- TODO Indent.- say "}\n"- -- | The in-memory result of generating C++ code for an interface.-data Generation = Generation+newtype Generation = Generation { generatedFiles :: M.Map FilePath String -- ^ A map from paths of generated files to the contents of those files. -- The file paths are relative paths below the C++ generation root.@@ -123,21 +52,21 @@ -- | Runs the C++ code generator against an interface. generate :: Interface -> Either ErrorMsg Generation-generate interface =+generate iface = fmap (Generation . M.fromList) $ execWriterT $- forM_ (M.elems $ interfaceModules interface) $ \m -> do- let headerGuard = concat ["HOPPY_MODULE_", interfaceName interface, "_", moduleName m]- header <- lift $ execGenerator interface m (Just headerGuard) sayModuleHeader+ forM_ (M.elems $ interfaceModules iface) $ \m -> do+ let headerGuard = concat ["HOPPY_MODULE_", interfaceName iface, "_", moduleName m]+ header <- lift $ execGenerator iface m (Just headerGuard) sayModuleHeader tell [(moduleHppPath m, header)]- source <- lift $ execGenerator interface m Nothing sayModuleSource+ source <- lift $ execGenerator iface m Nothing sayModuleSource tell [(moduleCppPath m, source)] sayModuleHeader :: Generator () sayModuleHeader = do m <- askModule addReqsM $ moduleReqs m- mapM_ (sayExport False) $ M.elems $ moduleExports m+ mapM_ (sayExportCpp SayHeader) $ M.elems $ moduleExports m iface <- askInterface when (interfaceExceptionSupportModule iface == Just m) $@@ -147,608 +76,12 @@ sayModuleSource = do m <- askModule addInclude $ includeLocal $ moduleHppPath m- mapM_ (sayExport True) $ M.elems $ moduleExports m+ mapM_ (sayExportCpp SaySource) $ M.elems $ moduleExports m iface <- askInterface when (interfaceExceptionSupportModule iface == Just m) $ sayExceptionSupport True -sayExport :: Bool -> Export -> Generator ()-sayExport sayBody export = case export of- ExportVariable v -> when sayBody $ sayExportVariable v-- -- Nothing to do C++ side for an enum or bitspace.- ExportEnum _ -> return ()- ExportBitspace _ -> return ()-- ExportFn fn ->- -- Export a single function.- when sayBody $ do- addReqsM $ fnReqs fn- sayExportFn (fnExtName fn)- (case fnCName fn of- FnName identifier -> CallFn $ sayIdentifier identifier- FnOp op -> CallOp op)- Nothing- (fnParams fn)- (fnReturn fn)- (fnExceptionHandlers fn)- sayBody-- ExportClass cls -> when sayBody $ 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.)- addReqsM $ classReqs cls -- This is needed at least for the delete function.-- -- Export each of the class's constructors.- forM_ (classCtors cls) $ \ctor ->- sayExportFn (classEntityExtName cls ctor)- (CallFn $ say "new" >> sayIdentifier (classIdentifier cls))- Nothing- (ctorParams ctor)- clsPtr- (ctorExceptionHandlers ctor)- sayBody-- -- Export a delete function for the class.- when (classDtorIsPublic cls) $- sayFunction (classDeleteFnCppName cls)- ["self"]- (fnT [constClsPtr] voidT) $- Just $ say "delete self;\n"-- -- Export each of the class's variables.- forM_ (classVariables cls) $ sayExportClassVariable 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- sayExportFn (classEntityExtName cls method)- (case methodImpl method of- RealMethod name -> case name of- FnName cName -> CallFn $ do- when static $ do- sayIdentifier (classIdentifier cls)- say "::"- say cName- FnOp op -> CallOp op- FnMethod name -> case name of- FnName cName -> CallFn $ sayIdentifier cName- FnOp op -> CallOp op)- (if nonMemberCall then Nothing else Just thisType)- (methodParams method)- (methodReturn method)- (methodExceptionHandlers method)- sayBody-- -- 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-- ExportCallback cb -> sayExportCallback sayBody cb-- where genUpcastFns :: Class -> Class -> Generator ()- genUpcastFns cls ancestorCls = do- sayFunction (classCastFnCppName cls ancestorCls)- ["self"]- (fnT [ptrT $ constT $ objT cls] $ ptrT $ constT $ objT ancestorCls)- (Just $ say "return self;\n")- forM_ (classSuperclasses ancestorCls) $ genUpcastFns cls-- genDowncastFns :: Class -> Class -> Generator ()- genDowncastFns cls ancestorCls = unless (classIsMonomorphicSuperclass ancestorCls) $ do- let clsPtr = ptrT $ constT $ objT cls- ancestorPtr = ptrT $ constT $ objT ancestorCls- sayFunction (classCastFnCppName ancestorCls cls)- ["self"]- (fnT [ancestorPtr] clsPtr) $ Just $ do- say "return dynamic_cast<"- sayType Nothing clsPtr- say ">(self);\n"- forM_ (classSuperclasses ancestorCls) $ genDowncastFns cls--sayExportVariable :: Variable -> Generator ()-sayExportVariable v =- sayExportVariable' (varType v)- Nothing- True- (varGetterExtName v)- (varSetterExtName v)- (sayIdentifier $ varIdentifier v)--sayExportClassVariable :: Class -> ClassVariable -> Generator ()-sayExportClassVariable cls v =- sayExportVariable' (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 -> say $ classVarCName v- Static -> do sayIdentifier $ classIdentifier cls- says ["::", classVarCName v])--sayExportVariable' :: Type- -> Maybe (Type, Type)- -> Bool- -> ExtName- -> ExtName- -> Generator ()- -> Generator ()-sayExportVariable' 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 $- sayExportFn getterName- (VarRead sayVarName)- (fmap fst maybeThisTypes)- []- deconstType- mempty- True-- -- Say a setter function.- unless isConst $- sayExportFn setterName- (VarWrite sayVarName)- (fmap snd maybeThisTypes)- [deconstType]- voidT- mempty- True--data CallType =- CallOp Operator- | CallFn (Generator ())- | VarRead (Generator ())- | VarWrite (Generator ())--sayExportFn :: ExtName- -> CallType- -> Maybe Type- -> [Type]- -> Type- -> ExceptionHandlers- -> Bool- -> Generator ()-sayExportFn extName callType maybeThisType paramTypes retType exceptionHandlers sayBody = do- handlerList <- exceptionHandlersList <$> getEffectiveExceptionHandlers exceptionHandlers- let catches = not $ null handlerList- addExceptionParamNames =- if catches then (++ [exceptionIdArgName, exceptionPtrArgName]) else id- addExceptionParamTypes = if catches then (++ [ptrT intT, ptrT $ ptrT voidT]) else id-- paramCount = length paramTypes- paramCTypeMaybes = map typeToCType paramTypes- paramCTypes = zipWith fromMaybe paramTypes paramCTypeMaybes- retCTypeMaybe = typeToCType retType- retCType = fromMaybe retType retCTypeMaybe-- addReqsM . mconcat =<< mapM typeReqs (retType:paramTypes)-- sayFunction (externalNameToCpp extName)- (maybe id (const ("self":)) maybeThisType $- addExceptionParamNames $- zipWith3 (\t ctm -> case t of- Internal_TCallback {} -> toArgNameAlt- _ -> if isJust ctm then toArgNameAlt else toArgName)- paramTypes paramCTypeMaybes [1..paramCount])- (fnT (addExceptionParamTypes $ maybe id (:) maybeThisType paramCTypes)- retCType) $- if not sayBody- then Nothing- else Just $ do- when catches $ do- say "try {\n"- says ["*", exceptionIdArgName, " = 0;\n"]-- -- Convert arguments that aren't passed in directly.- mapM_ (sayArgRead DoDecode) $ zip3 [1..] paramTypes paramCTypeMaybes-- let -- Determines how to call the exported function or method.- sayCall = case callType of- CallOp op -> do- say "("- let effectiveParamCount = paramCount + if isJust maybeThisType then 1 else 0- paramNames@(p1:p2:_) = (if isJust maybeThisType then ("(*self)":) else id) $- map toArgName [1..]- assertParamCount n =- when (effectiveParamCount /= n) $ abort $ concat- ["sayExportFn: Operator ", show op, " for export ", show extName,- " requires ", show n, " parameter(s), but has ", show effectiveParamCount,- "."]- case operatorType op of- UnaryPrefixOperator symbol -> assertParamCount 1 >> says [symbol, p1]- UnaryPostfixOperator symbol -> assertParamCount 1 >> says [p1, symbol]- BinaryOperator symbol -> assertParamCount 2 >> says [p1, symbol, p2]- CallOperator ->- says $ p1 : "(" : take (effectiveParamCount - 1) (drop 1 paramNames) ++ [")"]- ArrayOperator -> assertParamCount 2 >> says [p1, "[", p2, "]"]- say ")"- CallFn sayCppName -> do- when (isJust maybeThisType) $ say "self->"- sayCppName- say "("- sayArgNames paramCount- say ")"- VarRead sayVarName -> do- when (isJust maybeThisType) $ say "self->"- sayVarName- VarWrite sayVarName -> do- when (isJust maybeThisType) $ say "self->"- sayVarName- says [" = ", toArgName 1]-- -- Writes the call, transforming the return value if necessary.- -- These translations should be kept in sync with typeToCType.- sayCallAndReturn retType' retCTypeMaybe' = case (retType', retCTypeMaybe') of- (Internal_TVoid, Nothing) -> sayCall >> say ";\n"- (_, Nothing) -> say "return " >> sayCall >> say ";\n"- (Internal_TBitspace b, Just _) -> do- addReqsM $ bitspaceReqs b- let convFn = bitspaceFromCppValueFn b- say "return "- forM_ convFn $ \f -> says [f, "("]- sayCall- when (isJust convFn) $ say ")"- say ";\n";- (Internal_TRef cls, Just (Internal_TPtr cls')) | cls == cls' ->- say "return &(" >> sayCall >> say ");\n"- (Internal_TObj cls,- Just (Internal_TPtr (Internal_TConst (Internal_TObj cls')))) | cls == cls' ->- sayReturnNew cls sayCall- (Internal_TObjToHeap cls, Just (Internal_TPtr (Internal_TObj cls'))) | cls == cls' ->- sayReturnNew cls sayCall- (Internal_TToGc (Internal_TObj cls),- Just (Internal_TPtr (Internal_TObj cls'))) | cls == cls' ->- sayReturnNew cls sayCall- (Internal_TToGc retType'', _) -> sayCallAndReturn retType'' retCTypeMaybe'- ts -> abort $ concat ["sayExportFn: Unexpected return types ", show ts,- "while generating binding for ", show extName, "."]-- sayCallAndReturn retType retCTypeMaybe-- when catches $ do- iface <- askInterface-- forM_ handlerList $ \handler -> do- say "} catch ("- case handler of- CatchClass cls -> sayVar exceptionVarName Nothing $ refT $ constT $ objT cls- CatchAll -> say "..."- say ") {\n"-- exceptionId <- case handler of- CatchClass cls -> case interfaceExceptionClassId iface cls of- Just exceptionId -> return exceptionId- Nothing -> abort $ concat- ["sayExportFn: Trying to catch non-exception class ", show cls,- " while generating binding for ", show extName, "."]- CatchAll -> return exceptionCatchAllId- says ["*", exceptionIdArgName, " = ", show $ getExceptionId exceptionId, ";\n"]-- case handler of- CatchAll -> says ["*", exceptionPtrArgName, " = 0;\n"]- CatchClass cls -> do- -- Object pointers don't convert automatically to void*.- says ["*", exceptionPtrArgName, " = reinterpret_cast<void*>(new "]- sayType Nothing $ objT cls- says ["(", exceptionVarName, "));\n"]-- -- For all of the types our gateway functions actually return, "return- -- 0" is a valid statement.- when (retType /= Internal_TVoid) $ say "return 0;\n"-- say "}\n"-- where sayReturnNew cls sayCall =- say "return new" >> sayIdentifier (classIdentifier cls) >> say "(" >>- sayCall >> say ");\n"---- | If @dir@ is 'DoDecode', then we are a C++ function reading an argument from--- foreign code. If @dir@ is 'DoEncode', then we are invoking a foreign--- callback.-sayArgRead :: CoderDirection -> (Int, Type, Maybe Type) -> Generator ()-sayArgRead dir (n, stripConst . normalizeType -> cppType, maybeCType) = case cppType of- Internal_TBitspace b -> case maybeCType of- Just cType -> do- let cppTypeId = fromMaybe (error $ concat- ["sayArgRead: Expected ", show b,- " to have a C++ type, but it doesn't."]) $- bitspaceCppTypeIdentifier b- addReqsM $ bitspaceReqs b- case dir of- -- Convert from cType to cppType.- DoDecode -> do- sayIdentifier cppTypeId- says [" ", toArgName n, " = ", fromMaybe "" $ bitspaceToCppValueFn b,- "(", toArgNameAlt n, ");\n"]- -- Convert from cppType to cType.- DoEncode -> do- sayVar (toArgName n) Nothing cType- says [" = ", fromMaybe "" $ bitspaceFromCppValueFn b,- "(", toArgNameAlt n, ");\n"]- Nothing ->- return ()-- Internal_TCallback cb -> do- case dir of- DoDecode -> return ()- DoEncode -> abort $ concat- ["sayArgRead: Encoding of callbacks is not supported. Given ",- show cb, "."]- says [callbackClassName cb, " ", toArgName n, "(", toArgNameAlt n, ");\n"]-- t@(Internal_TPtr (Internal_TFn paramTypes retType)) -> do- -- Assert that all types referred to in a function pointer type are all- -- representable as C types.- let check label t' = (label ++ " " ++ show t') <$ typeToCType t'- mismatches = catMaybes $- check "return type" retType :- map (\paramType -> check "parameter" paramType)- paramTypes- unless (null mismatches) $- abort $ concat $- "sayArgRead: Some types within a function pointer type use non-C types, " :- "but only C types may be used. The unsupported types are: " :- intersperse "; " mismatches ++ [". The whole function type is ", show t, "."]-- convertDefault-- Internal_TRef t -> convertObj t-- Internal_TObj _ -> convertObj $ constT cppType-- Internal_TObjToHeap cls -> case dir of- DoDecode -> error $ objToHeapTWrongDirectionErrorMsg (Just "sayArgRead") cls- DoEncode -> do- sayIdentifier $ classIdentifier cls- says ["* ", toArgName n, " = new "]- sayIdentifier $ classIdentifier cls- says ["(", toArgNameAlt n, ");\n"]-- Internal_TToGc t' -> case dir of- DoDecode -> error $ toGcTWrongDirectionErrorMsg (Just "sayArgRead") t'- DoEncode -> do- let newCppType = case t' of- -- In the case of (TToGc (TObj _)), we copy the temporary object to- -- the heap and let the foreign language manage that value.- Internal_TObj cls -> objToHeapT cls- _ -> t'- sayArgRead dir (n, newCppType, typeToCType newCppType)-- _ -> convertDefault-- where -- Primitive types don't need to be encoded/decoded. But if maybeCType is a- -- Just, then we're expected to do some encoding/decoding, so something is- -- wrong.- --- -- TODO Do we need to handle TConst?- convertDefault = forM_ maybeCType $ \cType ->- abort $ concat- ["sayArgRead: Don't know how to ", show dir, " between C-type ", show cType,- " and C++-type ", show cppType, "."]-- convertObj cppType' = case dir of- DoDecode -> do- sayVar (toArgName n) Nothing $ refT cppType'- says [" = *", toArgNameAlt n, ";\n"]- DoEncode -> do- sayVar (toArgName n) Nothing $ ptrT cppType'- says [" = &", toArgNameAlt n, ";\n"]--sayArgNames :: Int -> Generator ()-sayArgNames count =- says $ intersperse ", " $ map toArgName [1..count]--sayExportCallback :: Bool -> Callback -> Generator ()-sayExportCallback sayBody cb = do- throws <- getEffectiveCallbackThrows cb-- let className = callbackClassName cb- implClassName = callbackImplClassName cb- fnName = callbackFnName cb- paramTypes = callbackParams cb- paramCount = length paramTypes- retType = callbackReturn cb- cbType = callbackT cb- fnType = fnT paramTypes retType-- -- The function pointer we receive from foreign code will work with C-types,- -- so determine what that function looks like.- let paramCTypes = zipWith fromMaybe paramTypes $ map typeToCType paramTypes- retCType = fromMaybe retType $ typeToCType retType-- -- Add requirements specified manually by the callback, and for its parameter- -- and return types.- addReqsM . mconcat . (callbackReqs cb:) =<< mapM typeReqs (retType:paramTypes)-- let fnCType = fnT ((if throws then (++ [ptrT intT, ptrT $ ptrT voidT]) else id)- paramCTypes)- retCType- fnPtrCType = ptrT fnCType-- if not sayBody- then do- -- Render the class declarations into the header file.- (sharedPtrReqs, sharedPtrStr) <- interfaceSharedPtr <$> askInterface- addReqsM sharedPtrReqs-- says ["\nclass ", implClassName, " {\n"]- say "public:\n"- says [" explicit ", implClassName, "("] >> sayType Nothing fnPtrCType >>- say ", void(*)(void(*)()), bool);\n"- says [" ~", implClassName, "();\n"]- say " " >> sayVar "operator()" Nothing fnType >> say ";\n"- say "private:\n"- says [" ", implClassName, "(const ", implClassName, "&);\n"]- says [" ", implClassName, "& operator=(const ", implClassName, "&);\n"]- say "\n"- say " " >> sayVar "f_" Nothing (constT fnPtrCType) >> say ";\n"- say " void (*const release_)(void(*)());\n"- say " const bool releaseRelease_;\n"- say "};\n"-- says ["\nclass ", className, " {\n"]- say "public:\n"- says [" ", className, "() {}\n"]- says [" explicit ", className, "(", implClassName, "* impl) : impl_(impl) {}\n"]- say " " >> sayVar "operator()" Nothing fnType >> say ";\n"- say " operator bool() const;\n"- say "private:\n"- says [" ", sharedPtrStr, "<", implClassName, "> impl_;\n"]- say "};\n"-- else do- -- Render the classes' methods into the source file. First render the- -- impl class's constructor.- says ["\n", implClassName, "::", implClassName, "("] >> sayVar "f" Nothing fnPtrCType >>- say ", void (*release)(void(*)()), bool releaseRelease) :\n"- say " f_(f), release_(release), releaseRelease_(releaseRelease) {}\n"-- -- Then render the destructor.- says ["\n", implClassName, "::~", implClassName, "() {\n"]- say " if (release_) {\n"- say " release_(reinterpret_cast<void(*)()>(f_));\n"- say " if (releaseRelease_) {\n"- say " release_(reinterpret_cast<void(*)()>(release_));\n"- say " }\n"- say " }\n"- say "}\n"-- -- Render the impl operator() method, which does argument decoding and- -- return value encoding and passes C++ values to underlying function- -- poiner.- --- -- TODO Abstract the duplicated code here and in sayExportFn.- let paramCTypeMaybes = map typeToCType paramTypes- retCTypeMaybe = typeToCType retType-- sayFunction (implClassName ++ "::operator()")- (zipWith (\ctm -> if isJust ctm then toArgNameAlt else toArgName)- paramCTypeMaybes [1..paramCount])- fnType $ Just $ do- -- Convert arguments that aren't passed in directly.- mapM_ (sayArgRead DoEncode) $ zip3 [1..] paramTypes paramCTypeMaybes-- when throws $ do- says ["int ", exceptionIdArgName, " = 0;\n"]- says ["void *", exceptionPtrArgName, " = 0;\n"]-- -- Add an include for the exception support module to be able to call the- -- C++ rethrow function.- iface <- askInterface- currentModule <- askModule- case interfaceExceptionSupportModule iface of- Just exceptionSupportModule ->- when (exceptionSupportModule /= currentModule) $- -- TODO Should this be includeStd?- addReqsM $ reqInclude $ includeLocal $ moduleHppPath exceptionSupportModule- Nothing -> abort $ "sayExportCallback: " ++ show iface ++- " uses exceptions, so it needs an exception support " ++- "module. Please use interfaceSetExceptionSupportModule."-- -- Invoke the function pointer into foreign code.- let -- | Generates the call to the foreign language function pointer.- sayCall :: Generator ()- sayCall = do- say "f_("- sayArgNames paramCount- when throws $ do- when (paramCount /= 0) $ say ", "- says ["&", exceptionIdArgName, ", &", exceptionPtrArgName]- say ")"-- -- | Generates code to check whether an exception was thrown by the- -- callback, and rethrows it in C++ if so.- sayExceptionCheck :: Generator ()- sayExceptionCheck = when throws $ do- says ["if (", exceptionIdArgName, " != 0) { ",- exceptionRethrowFnName, "(", exceptionIdArgName, ", ",- exceptionPtrArgName, "); }\n"]-- case (retType, retCTypeMaybe) of- (Internal_TVoid, Nothing) -> do- sayCall >> say ";\n"- sayExceptionCheck- (_, Nothing) -> do- sayVar "result" Nothing retType >> say " = " >> sayCall >> say ";\n"- sayExceptionCheck- say "return result;\n"- (Internal_TBitspace b, Just _) -> do- addReqsM $ bitspaceReqs b- let convFn = bitspaceToCppValueFn b- sayVar "result" Nothing retType- say " = "- forM_ convFn $ \f -> says [f, "("]- sayCall- when (isJust convFn) $ say ")"- say ";\n";- sayExceptionCheck- say "return result;\n"- (Internal_TObj cls1, Just retCType@(Internal_TPtr (Internal_TConst (Internal_TObj cls2))))- | cls1 == cls2 -> do- sayVar "resultPtr" Nothing retCType >> say " = " >> sayCall >> say ";\n"- sayExceptionCheck- sayVar "result" Nothing retType >> say " = *resultPtr;\n"- say "delete resultPtr;\n"- say "return result;\n"- (Internal_TRef (Internal_TConst (Internal_TObj cls1)),- Just (Internal_TPtr (Internal_TConst (Internal_TObj cls2)))) | cls1 == cls2 -> do- sayVar "resultPtr" Nothing retCType >> say " = " >> sayCall >> say ";\n"- sayExceptionCheck- say "return *resultPtr;\n"- (Internal_TRef (Internal_TObj cls1),- Just (Internal_TPtr (Internal_TObj cls2))) | cls1 == cls2 -> do- sayVar "resultPtr" Nothing retCType >> say " = " >> sayCall >> say ";\n"- sayExceptionCheck- say "return *resultPtr;\n"- ts -> abort $ concat- ["sayExportCallback: Unexpected return types ", show ts, "."]-- -- Render the non-impl operator() method, which simply passes C++ values- -- along to the impl object.- sayFunction (className ++ "::operator()")- (map toArgName [1..paramCount])- fnType $ Just $ do- case retType of- Internal_TVoid -> say "(*impl_)("- _ -> say "return (*impl_)("- sayArgNames paramCount- say ");\n"-- -- Render "operator bool", which detects whether the callback was not- -- default-constructed with no actual impl object.- says [className, "::operator bool() const {\n"]- say "return static_cast<bool>(impl_);\n"- say "}\n"-- -- Render the function that creates a new callback object.- let newCallbackFnType = fnT [ fnPtrCType- , ptrT (fnT [ptrT $ fnT [] voidT] voidT)- , boolT- ]- cbType- sayFunction fnName ["f", "release", "releaseRelease"] newCallbackFnType $ Just $- says ["return new ", implClassName, "(f, release, releaseRelease);\n"]- -- | Outputs interface-wide code needed to support exceptions. Currently, this -- comprises the function for rethrowing in C++ an exception transferred from -- a foreign language.@@ -781,113 +114,3 @@ say "}\n" says ["throw \"Internal Hoppy error, ", exceptionRethrowFnName, " got an unknown exception ID.\";\n"]---- | Returns a 'Type' iff there is a C type distinct from the given C++ type--- that should be used for conversion.-typeToCType :: Type -> Maybe Type-typeToCType t = case t of- -- Because we don't know (although we could...) the direction in which we're- -- converting the bitspace value, when the bitspace has a C++ type we have to- -- assume that it needs to be converted. The caller will sort out whether a- -- conversion is actually requested.- Internal_TBitspace b -> case bitspaceCppTypeIdentifier b of- Just _ -> Just $ bitspaceType b- Nothing -> Nothing- Internal_TRef t' -> Just $ ptrT t'- Internal_TObj _ -> Just $ ptrT $ constT t- Internal_TObjToHeap cls -> Just $ ptrT $ objT cls- Internal_TToGc t'@(Internal_TObj _) -> Just $ ptrT t'- Internal_TToGc t' -> typeToCType t'- Internal_TConst t' -> typeToCType t'- _ -> Nothing--typeReqs :: Type -> Generator Reqs-typeReqs t = case t of- Internal_TVoid -> return mempty- Internal_TBool -> return mempty- Internal_TChar -> return mempty- Internal_TUChar -> return mempty- Internal_TShort -> return mempty- Internal_TUShort -> return mempty- Internal_TInt -> return mempty- Internal_TUInt -> return mempty- Internal_TLong -> return mempty- Internal_TULong -> return mempty- Internal_TLLong -> return mempty- Internal_TULLong -> return mempty- Internal_TFloat -> return mempty- Internal_TDouble -> return mempty- Internal_TInt8 -> return cstdintReqs- Internal_TInt16 -> return cstdintReqs- Internal_TInt32 -> return cstdintReqs- Internal_TInt64 -> return cstdintReqs- Internal_TWord8 -> return cstdintReqs- Internal_TWord16 -> return cstdintReqs- Internal_TWord32 -> return cstdintReqs- Internal_TWord64 -> return cstdintReqs- Internal_TPtrdiff -> return cstddefReqs- Internal_TSize -> return cstddefReqs- Internal_TSSize -> return cstddefReqs- Internal_TEnum e -> return $ enumReqs e- Internal_TBitspace b -> typeReqs $ bitspaceType b- Internal_TPtr t' -> typeReqs t'- Internal_TRef t' -> typeReqs t'- Internal_TFn paramTypes retType ->- -- TODO Is the right 'ReqsType' being used recursively here?- mconcat <$> mapM typeReqs (retType:paramTypes)- Internal_TCallback cb -> do- -- TODO Should this be includeStd?- cbClassReqs <- reqInclude . includeLocal . moduleHppPath <$>- findExportModule (callbackExtName cb)- -- TODO Is the right 'ReqsType' being used recursively here?- fnTypeReqs <- typeReqs =<< callbackToTFn cb- return $ cbClassReqs `mappend` fnTypeReqs- Internal_TObj cls -> return $ classReqs cls- Internal_TObjToHeap cls -> return $ classReqs cls- Internal_TToGc t' -> typeReqs t'- Internal_TConst t' -> typeReqs t'--cstddefReqs :: Reqs-cstddefReqs = reqInclude $ includeStd "cstddef"--cstdintReqs :: Reqs-cstdintReqs = reqInclude $ includeStd "cstdint"--findExportModule :: ExtName -> Generator Module-findExportModule extName =- fromMaybeM (abort $ concat- ["findExportModule: Can't find module exporting ", fromExtName extName, "."]) =<<- fmap (M.lookup extName . interfaceNamesToModules) askInterface--getEffectiveExceptionHandlers :: ExceptionHandlers -> Generator ExceptionHandlers-getEffectiveExceptionHandlers handlers = do- ifaceHandlers <- interfaceExceptionHandlers <$> askInterface- moduleHandlers <- getExceptionHandlers <$> askModule- -- Exception handlers declared lower in the hierarchy take precedence over- -- those higher in the hierarchy; ExceptionHandlers is a left-biased monoid.- return $ mconcat [handlers, moduleHandlers, ifaceHandlers]--getEffectiveCallbackThrows :: Callback -> Generator Bool-getEffectiveCallbackThrows cb = case callbackThrows cb of- Just b -> return b- Nothing -> moduleCallbacksThrow <$> askModule >>= \case- Just b -> return b- Nothing -> interfaceCallbacksThrow <$> askInterface---- | Constructs the function type for a callback. A callback that throws has--- additional parameters.------ Keep this in sync with the Haskell generator's version.-callbackToTFn :: Callback -> Generator Type-callbackToTFn cb = do- throws <- mayThrow- return $ Internal_TFn ((if throws then addExcParams else id) $ callbackParams cb)- (callbackReturn cb)-- where mayThrow = case callbackThrows cb of- Just t -> return t- Nothing -> moduleCallbacksThrow <$> askModule >>= \mt -> case mt of- Just t -> return t- Nothing -> interfaceCallbacksThrow <$> askInterface-- addExcParams = (++ [ptrT intT, ptrT $ ptrT voidT])
src/Foreign/Hoppy/Generator/Language/Haskell.hs view
@@ -1,6 +1,6 @@ -- This file is part of Hoppy. ----- Copyright 2015-2018 Bryan Gardiner <bog@khumba.net>+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net> -- -- 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@@ -31,6 +31,7 @@ evalGenerator, execGenerator, renderPartial,+ Env (..), askInterface, askModule, askModuleName,@@ -47,65 +48,24 @@ -- * Language extensions addExtension, -- * Code generation+ SayExportMode (..), sayLn, saysLn, ln, indent, indentSpaces, sayLet,- toHsEnumTypeName,- toHsEnumTypeName',- toHsEnumCtorName,- toHsEnumCtorName',- toHsBitspaceTypeName,- toHsBitspaceTypeName',- toHsBitspaceValueName,- toHsBitspaceValueName',- toHsBitspaceToNumName,- toHsBitspaceToNumName',- toHsBitspaceClassName,- toHsBitspaceClassName',- toHsBitspaceFromValueName,- toHsBitspaceFromValueName',- 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',- toHsCallbackCtorName,- toHsCallbackCtorName',- toHsCallbackNewFunPtrFnName,- toHsCallbackNewFunPtrFnName',+ getExtNameModule,+ addExtNameModule,+ toHsTypeName,+ toHsTypeName', toHsFnName, toHsFnName', toArgName, HsTypeSide (..), cppTypeToHsTypeAndUse, getClassHaskellConversion,- callbackToTFn,+ getEffectiveExceptionHandlers, prettyPrint, ) where @@ -113,17 +73,12 @@ import Control.Applicative ((<$>)) #endif import Control.Arrow (first)-#if MIN_VERSION_mtl(2,2,1) import Control.Monad.Except (Except, catchError, runExcept, throwError)-#else-import Control.Monad.Error (catchError, throwError)-#endif-import Control.Monad.Reader (ReaderT, ask, runReaderT)+import Control.Monad.Reader (ReaderT, asks, runReaderT) import Control.Monad.Writer (WriterT, censor, runWriterT, tell) import Data.Char (toUpper) import Data.Foldable (forM_) import Data.Function (on)-import Data.Functor (($>)) import Data.List (intercalate, intersperse) import qualified Data.Map as M import Data.Maybe (fromMaybe, isJust)@@ -135,7 +90,15 @@ import Data.Tuple (swap) import Foreign.Hoppy.Generator.Common import Foreign.Hoppy.Generator.Spec.Base-import Foreign.Hoppy.Generator.Types+import {-# SOURCE #-} Foreign.Hoppy.Generator.Spec.Class (+ Class,+ ClassHaskellConversion,+ classConversion,+ classExtName,+ classHaskellConversion,+ classHaskellConversionType,+ )+import Foreign.Hoppy.Generator.Types (constT, objT, ptrT) import qualified Language.Haskell.Pretty as P import Language.Haskell.Syntax ( HsName (HsIdent),@@ -157,9 +120,9 @@ -- taking into account the 'interfaceHaskellModuleBase' and the -- 'moduleHaskellName'. getModuleName :: Interface -> Module -> String-getModuleName interface m =+getModuleName iface m = intercalate "." $- interfaceHaskellModuleBase interface +++ interfaceHaskellModuleBase iface ++ fromMaybe [toModuleName $ moduleName m] (moduleHaskellName m) -- | Performs case conversions on the given string to ensure that it is a valid@@ -174,7 +137,7 @@ where -- | Renders an import as a string that contains one or more lines. renderModuleImport :: (HsImportKey, HsImportSpecs) -> String renderModuleImport (key, specs) =- let moduleName = hsImportModule key+ let modName = hsImportModule key maybeQualifiedName = hsImportQualifiedName key isQual = isJust maybeQualifiedName importPrefix = if hsImportSource specs@@ -186,9 +149,9 @@ else "import qualified " in case getHsImportSpecs specs of Nothing -> case maybeQualifiedName of- Nothing -> importPrefix ++ moduleName+ Nothing -> importPrefix ++ modName Just qualifiedName ->- concat [importQualifiedPrefix, moduleName, " as ", qualifiedName]+ concat [importQualifiedPrefix, modName, " as ", qualifiedName] Just specMap -> let specWords :: [String] specWords = concatWithCommas $ map renderSpecAsWords $ M.assocs specMap@@ -196,14 +159,14 @@ singleLineImport = concat $ (if isQual then importQualifiedPrefix else importPrefix) :- moduleName : " (" : intersperse " " specWords +++ modName : " (" : intersperse " " specWords ++ case maybeQualifiedName of Nothing -> [")"] Just qualifiedName -> [") as ", qualifiedName] in if null $ drop maxLineLength singleLineImport then singleLineImport else intercalate "\n" $- (importPrefix ++ moduleName ++ " (") :+ (importPrefix ++ modName ++ " (") : groupWordsIntoLines specWords ++ case maybeQualifiedName of Nothing -> [" )"]@@ -221,9 +184,9 @@ HsImportValSome parts -> case parts of [] -> [name ++ " ()"] [part] -> [concat [name, " (", part, ")"]]- part0:parts -> let (parts', [partN]) = splitAt (length parts - 1) parts+ part0:parts' -> let (parts'', [partN]) = splitAt (length parts' - 1) parts' in concat [name, " (", part0, ","] :- map (++ ",") parts' +++ map (++ ",") parts'' ++ [partN ++ ")"] HsImportValAll -> [name ++ " (..)"] @@ -246,17 +209,17 @@ -- flowed. groupWordsIntoLines :: [String] -> [String] groupWordsIntoLines [] = []- groupWordsIntoLines words =+ groupWordsIntoLines wordList = let (wordCount, line, _) = last $- takeWhile (\(wordCount, _, len) -> wordCount <= 1 || len <= maxLineLength) $- scanl (\(wordCount, acc, len) word ->- (wordCount + 1,+ takeWhile (\(wordCount', _, len) -> wordCount' <= 1 || len <= maxLineLength) $+ scanl (\(wordCount', acc, len) word ->+ (wordCount' + 1, concat [acc, " ", word], len + 1 + length word)) (0, "", 0)- words- in line : groupWordsIntoLines (drop wordCount words)+ wordList+ in line : groupWordsIntoLines (drop wordCount wordList) maxLineLength :: Int maxLineLength = 100@@ -274,11 +237,7 @@ -- not end with punctuation. If there is a suggestion, include it in -- parentheses at the end of the message. 'withErrorContext' and 'inFunction' -- add context information, and should be given clauses, without punctuation.-#if MIN_VERSION_mtl(2,2,1) type Generator = ReaderT Env (WriterT Output (Except ErrorMsg))-#else-type Generator = ReaderT Env (WriterT Output (Either ErrorMsg))-#endif -- | Context information for generating Haskell code. data Env = Env@@ -289,15 +248,15 @@ -- | Returns the currently generating interface. askInterface :: Generator Interface-askInterface = envInterface <$> ask+askInterface = asks envInterface -- | Returns the currently generating module. askModule :: Generator Module-askModule = envModule <$> ask+askModule = asks envModule -- | Returns the currently generating module's Haskell module name. askModuleName :: Generator String-askModuleName = envModuleName <$> ask+askModuleName = asks envModuleName -- | Looks up the 'Module' containing a given external name, throwing an error -- if it can't be found.@@ -305,7 +264,7 @@ getModuleForExtName extName = inFunction "getModuleForExtName" $ do iface <- askInterface case M.lookup extName $ interfaceNamesToModules iface of- Just mod -> return mod+ Just m -> return m Nothing -> throwError $ "Can't find module for " ++ show extName -- | A partially-rendered 'Module'. Contains all of the module's bindings, but@@ -359,22 +318,20 @@ -- Returns an error message if an error occurred, otherwise the action's output -- together with its value. runGenerator :: Interface -> Module -> Generator a -> Either ErrorMsg (Partial, a)-runGenerator iface mod generator =- let modName = getModuleName iface mod+runGenerator iface m generator =+ let modName = getModuleName iface m in fmap (first (Partial modName) . swap) $-#if MIN_VERSION_mtl(2,2,1) runExcept $-#endif flip catchError (\msg -> throwError $ msg ++ ".") $- runWriterT $ runReaderT generator $ Env iface mod modName+ runWriterT $ runReaderT generator $ Env iface m modName -- | Runs a generator action and returns the its value. evalGenerator :: Interface -> Module -> Generator a -> Either ErrorMsg a-evalGenerator iface mod = fmap snd . runGenerator iface mod+evalGenerator iface m = fmap snd . runGenerator iface m -- | Runs a generator action and returns its output. execGenerator :: Interface -> Module -> Generator a -> Either ErrorMsg Partial-execGenerator iface mod = fmap fst . runGenerator iface mod+execGenerator iface m = fmap fst . runGenerator iface m -- | Converts a 'Partial' into a complete Haskell module. renderPartial :: Partial -> String@@ -441,6 +398,33 @@ addExtension extensionName = tell $ mempty { outputExtensions = S.singleton extensionName } +-- | The section of code that Hoppy is generating, for an export.+data SayExportMode =+ SayExportForeignImports+ -- ^ Hoppy is generating @foreign import@ statements for an export. This is+ -- separate from the main 'SayExportDecls' phase because foreign import+ -- statements are emitted directly by a 'Generator', and these need to+ -- appear earlier in the code.+ | SayExportDecls+ -- ^ Hoppy is generating Haskell code to bind to the export. This is the+ -- main step of Haskell code generation for an export.+ --+ -- Here, imports of Haskell modules should be added with 'LH.addImports'+ -- rather than emitting an @import@ statement yourself in the foreign import+ -- step. 'LH.addExtNameModule' may be used to import and reference the+ -- Haskell module of another export.+ | SayExportBoot+ -- ^ If Hoppy needs to generate @hs-boot@ files to break circular+ -- dependences between generated modules, then for each export in each+ -- module involved in a cycle, it will call the generator in this mode to+ -- produce @hs-boot@ code. This code should provide a minimal declaration+ -- of Haskell entities generated by 'SayExportDecls', without providing any+ -- implementation.+ --+ -- For information on the special format of @hs-boot@ files, see the+ -- <https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/separate_compilation.html#how-to-compile-mutually-recursive-modules GHC User's Guide>.+ deriving (Eq, Show)+ -- | Outputs a line of Haskell code. A newline will be added on the end of the -- input. Newline characters must not be given to this function. sayLn :: String -> Generator ()@@ -493,6 +477,8 @@ sayLn "in" indent body +-- | Looks up the module that exports an external name. Throws an error if the+-- external name is not exported. getExtNameModule :: ExtName -> Generator Module getExtNameModule extName = inFunction "getExtNameModule" $ do iface <- askInterface@@ -504,11 +490,11 @@ -- | Returns a module's unique short name that should be used for a qualified -- import of the module. getModuleImportName :: Module -> Generator String-getModuleImportName mod = do+getModuleImportName m = do iface <- askInterface- fromMaybeM (throwError $ "Couldn't find a Haskell import name for " ++ show mod +++ fromMaybeM (throwError $ "Couldn't find a Haskell import name for " ++ show m ++ " (is it included in the interface's module list?)") $- M.lookup mod $+ M.lookup m $ interfaceHaskellModuleImportNames iface -- | Adds a qualified import of the given external name's module into the current@@ -538,9 +524,9 @@ Nothing -> hsEntity -- Same module. Just importName -> concat [importName, ".", hsEntity] -- Different module. --- | Internal helper function for constructing Haskell names from external--- names. Returns a name that is a suitable Haskell type name for the external--- name, and if given 'Const', then with @\"Const\"@ appended.+-- | Constructs Haskell names from external names. Returns a name that is a+-- suitable Haskell type name for the external name, and if given 'Const', then+-- with @\"Const\"@ appended. toHsTypeName :: Constness -> ExtName -> Generator String toHsTypeName cst extName = inFunction "toHsTypeName" $@@ -556,299 +542,6 @@ x:xs -> toUpper x:xs [] -> [] --- | Returns the Haskell name for an enum.-toHsEnumTypeName :: CppEnum -> Generator String-toHsEnumTypeName enum =- inFunction "toHsEnumTypeName" $- addExtNameModule (enumExtName enum) $ toHsEnumTypeName' enum---- | Pure version of 'toHsEnumTypeName' that doesn't create a qualified name.-toHsEnumTypeName' :: CppEnum -> String-toHsEnumTypeName' = toHsTypeName' Nonconst . enumExtName---- | Constructs the data constructor name for a value in an enum. Like C++ and--- unlike say Java, Haskell enum values aren't in a separate enum-specific--- namespace, so we prepend the enum name to the value name to get the data--- constructor name. The value name is a list of words; see 'enumValueNames'.-toHsEnumCtorName :: CppEnum -> [String] -> Generator String-toHsEnumCtorName enum words =- inFunction "toHsEnumCtorName" $- addExtNameModule (enumExtName enum) $ toHsEnumCtorName' enum words---- | Pure version of 'toHsEnumCtorName' that doesn't create a qualified name.-toHsEnumCtorName' :: CppEnum -> [String] -> String-toHsEnumCtorName' enum words =- concat $ enumValuePrefix enum : map capitalize words---- | Returns the Haskell name for a bitspace. See 'toHsEnumTypeName'.-toHsBitspaceTypeName :: Bitspace -> Generator String-toHsBitspaceTypeName bitspace =- inFunction "toHsBitspaceTypeName" $- addExtNameModule (bitspaceExtName bitspace) $ toHsBitspaceTypeName' bitspace---- | Pure version of 'toHsBitspaceTypeName' that doesn't create a qualified name.-toHsBitspaceTypeName' :: Bitspace -> String-toHsBitspaceTypeName' = toHsTypeName' Nonconst . bitspaceExtName---- | Constructs the data constructor name for a value in a bitspace. See--- 'toHsEnumCtorName'.-toHsBitspaceValueName :: Bitspace -> [String] -> Generator String-toHsBitspaceValueName bitspace words =- inFunction "toHsBitspaceValueName" $- addExtNameModule (bitspaceExtName bitspace) $- toHsBitspaceValueName' bitspace words---- | Pure version of 'toHsBitspaceValueName' that doesn't create a qualified name.-toHsBitspaceValueName' :: Bitspace -> [String] -> String-toHsBitspaceValueName' bitspace words =- lowerFirst $ concat $ bitspaceValuePrefix bitspace : map capitalize words---- | Returns the name of the function that will convert a bitspace value into a--- raw numeric value.-toHsBitspaceToNumName :: Bitspace -> Generator String-toHsBitspaceToNumName bitspace =- inFunction "toHsBitspaceToNumName" $- addExtNameModule (bitspaceExtName bitspace) $ toHsBitspaceToNumName' bitspace---- | Pure version of 'toHsBitspaceToNumName' that doesn't create a qualified name.-toHsBitspaceToNumName' :: Bitspace -> String-toHsBitspaceToNumName' = ("from" ++) . toHsBitspaceTypeName'---- | The name of the Haskell typeclass that contains a method for converting to--- a bitspace value.-toHsBitspaceClassName :: Bitspace -> Generator String-toHsBitspaceClassName bitspace =- inFunction "toHsBitspaceClassName" $- addExtNameModule (bitspaceExtName bitspace) $ toHsBitspaceClassName' bitspace---- | Pure version of 'toHsBitspaceClassName' that doesn't create a qualified name.-toHsBitspaceClassName' :: Bitspace -> String-toHsBitspaceClassName' bitspace = 'I':'s':toHsBitspaceTypeName' bitspace---- | The name of the method in the 'toHsBitspaceClassName' typeclass that--- constructs bitspace values.-toHsBitspaceFromValueName :: Bitspace -> Generator String-toHsBitspaceFromValueName bitspace =- inFunction "toHsBitspaceFromValueName" $- addExtNameModule (bitspaceExtName bitspace) $ toHsBitspaceFromValueName' bitspace---- | Pure version of 'toHsBitspaceFromValueName' that doesn't create a qualified name.-toHsBitspaceFromValueName' :: Bitspace -> String-toHsBitspaceFromValueName' = ("to" ++) . toHsBitspaceTypeName'---- | The name for the typeclass of types that can be represented as values of--- the given C++ class.-toHsValueClassName :: Class -> Generator String-toHsValueClassName cls =- inFunction "toHsValueClassName" $- 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 -> Generator String-toHsWithValuePtrName cls =- inFunction "toHsWithValuePtrName" $- 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 -> Generator String-toHsPtrClassName cst cls =- inFunction "toHsPtrClassName" $- 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 -> Generator String-toHsCastMethodName cst cls =- inFunction "toHsCastMethodName" $- 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 -> Generator String-toHsDownCastClassName cst cls =- inFunction "toHsDownCastClassName" $- 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 -> Generator String-toHsDownCastMethodName cst cls =- inFunction "toHsDownCastMethodName" $- 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 -> Generator String-toHsCastPrimitiveName descendentClass from to =- inFunction "toHsCastPrimitiveName" $- 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 -> Generator String-toHsConstCastFnName cst cls =- inFunction "toHsConstCastFnName" $- 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 -> Generator String-toHsDataTypeName cst cls =- inFunction "toHsDataTypeName" $- addExtNameModule (classExtName cls) $ toHsDataTypeName' cst cls---- | Pure version of 'toHsDataTypeName' that doesn't create a qualified name.-toHsDataTypeName' :: Constness -> Class -> String-toHsDataTypeName' cst cls = toHsTypeName' cst $ classExtName cls---- | The name of a data constructor for one of the object pointer types.-toHsDataCtorName :: Managed -> Constness -> Class -> Generator String-toHsDataCtorName m cst cls =- inFunction "toHsDataCtorName" $- addExtNameModule (classExtName cls) $ toHsDataCtorName' m cst cls---- | Pure version of 'toHsDataCtorName' that doesn't create a qualified name.-toHsDataCtorName' :: Managed -> Constness -> Class -> String-toHsDataCtorName' m cst cls = case m of- Unmanaged -> base- 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 -> Generator String-toHsCtorName cls ctor =- 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 -> Generator String-toHsMethodName cls method =- 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 -> Generator String-toHsClassEntityName cls name =- 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---- | The name of the function that takes a Haskell function and wraps it in a--- callback object. This is internal to the binding; normal users can pass--- Haskell functions to be used as callbacks inplicitly.-toHsCallbackCtorName :: Callback -> Generator String-toHsCallbackCtorName callback =- inFunction "toHsCallbackCtorName" $- addExtNameModule (callbackExtName callback) $ toHsCallbackCtorName' callback---- | Pure version of 'toHsCallbackCtorName' that doesn't create a qualified--- name.-toHsCallbackCtorName' :: Callback -> String-toHsCallbackCtorName' callback =- toHsFnName' $ toExtName $ fromExtName (callbackExtName callback) ++ "_new"---- | The name of the function that takes a Haskell function with Haskell-side--- types and wraps it in a 'Foreign.Ptr.FunPtr' that does appropriate--- conversions to and from C-side types.-toHsCallbackNewFunPtrFnName :: Callback -> Generator String-toHsCallbackNewFunPtrFnName callback =- inFunction "toHsCallbackNewFunPtrFnName" $- addExtNameModule (callbackExtName callback) $ toHsCallbackNewFunPtrFnName' callback---- | Pure version of 'toHsCallbackNewFunPtrFnName' that doesn't create a qualified--- name.-toHsCallbackNewFunPtrFnName' :: Callback -> String-toHsCallbackNewFunPtrFnName' callback =- toHsFnName' $ toExtName $ fromExtName (callbackExtName callback) ++ "_newFunPtr"- -- | Converts an external name into a name suitable for a Haskell function or -- variable. toHsFnName :: ExtName -> Generator String@@ -879,51 +572,6 @@ withErrorContext (concat ["converting ", show t, " to ", show side, " type"]) $ case t of Internal_TVoid -> return $ HsTyCon $ Special HsUnitCon- -- C++ has sizeof(bool) == 1, whereas Haskell can > 1, so we have to convert.- Internal_TBool -> case side of- HsCSide -> addImports hsImportForRuntime $> HsTyCon (UnQual $ HsIdent "HoppyFHR.CBool")- HsHsSide -> addImports hsImportForPrelude $> HsTyCon (UnQual $ HsIdent "HoppyP.Bool")- Internal_TChar -> addImports hsImportForForeignC $> HsTyCon (UnQual $ HsIdent "HoppyFC.CChar")- Internal_TUChar -> addImports hsImportForForeignC $> HsTyCon (UnQual $ HsIdent "HoppyFC.CUChar")- Internal_TShort -> addImports hsImportForForeignC $> HsTyCon (UnQual $ HsIdent "HoppyFC.CShort")- Internal_TUShort ->- addImports hsImportForForeignC $> HsTyCon (UnQual $ HsIdent "HoppyFC.CUShort")- Internal_TInt -> case side of- HsCSide -> addImports hsImportForForeignC $> HsTyCon (UnQual $ HsIdent "HoppyFC.CInt")- HsHsSide -> addImports hsImportForPrelude $> HsTyCon (UnQual $ HsIdent "HoppyP.Int")- Internal_TUInt -> addImports hsImportForForeignC $> HsTyCon (UnQual $ HsIdent "HoppyFC.CUInt")- Internal_TLong -> addImports hsImportForForeignC $> HsTyCon (UnQual $ HsIdent "HoppyFC.CLong")- Internal_TULong -> addImports hsImportForForeignC $> HsTyCon (UnQual $ HsIdent "HoppyFC.CULong")- Internal_TLLong -> addImports hsImportForForeignC $> HsTyCon (UnQual $ HsIdent "HoppyFC.CLLong")- Internal_TULLong ->- addImports hsImportForForeignC $> HsTyCon (UnQual $ HsIdent "HoppyFC.CULLong")- Internal_TFloat -> case side of- HsCSide -> addImports hsImportForForeignC $> HsTyCon (UnQual $ HsIdent "HoppyFC.CFloat")- HsHsSide -> addImports hsImportForPrelude $> HsTyCon (UnQual $ HsIdent "HoppyP.Float")- Internal_TDouble -> case side of- HsCSide -> addImports hsImportForForeignC $> HsTyCon (UnQual $ HsIdent "HoppyFC.CDouble")- HsHsSide -> addImports hsImportForPrelude $> HsTyCon (UnQual $ HsIdent "HoppyP.Double")- Internal_TInt8 -> addImports hsImportForInt $> HsTyCon (UnQual $ HsIdent "HoppyDI.Int8")- Internal_TInt16 -> addImports hsImportForInt $> HsTyCon (UnQual $ HsIdent "HoppyDI.Int16")- Internal_TInt32 -> addImports hsImportForInt $> HsTyCon (UnQual $ HsIdent "HoppyDI.Int32")- Internal_TInt64 -> addImports hsImportForInt $> HsTyCon (UnQual $ HsIdent "HoppyDI.Int64")- Internal_TWord8 -> addImports hsImportForWord $> HsTyCon (UnQual $ HsIdent "HoppyDW.Word8")- Internal_TWord16 -> addImports hsImportForWord $> HsTyCon (UnQual $ HsIdent "HoppyDW.Word16")- Internal_TWord32 -> addImports hsImportForWord $> HsTyCon (UnQual $ HsIdent "HoppyDW.Word32")- Internal_TWord64 -> addImports hsImportForWord $> HsTyCon (UnQual $ HsIdent "HoppyDW.Word64")- Internal_TPtrdiff ->- addImports hsImportForForeignC $> HsTyCon (UnQual $ HsIdent "HoppyFC.CPtrdiff")- Internal_TSize -> addImports hsImportForForeignC $> HsTyCon (UnQual $ HsIdent "HoppyFC.CSize")- Internal_TSSize ->- addImports hsImportForSystemPosixTypes $> HsTyCon (UnQual $ HsIdent "HoppySPT.CSsize")- Internal_TEnum e -> HsTyCon . UnQual . HsIdent <$> case side of- HsCSide -> addImports hsImportForForeignC $> "HoppyFC.CInt"- HsHsSide -> toHsEnumTypeName e- Internal_TBitspace b -> case side of- HsCSide -> cppTypeToHsTypeAndUse side $ bitspaceType b- HsHsSide -> do- typeName <- toHsBitspaceTypeName b- return $ HsTyCon $ UnQual $ HsIdent typeName Internal_TPtr (Internal_TObj cls) -> do -- Same as TPtr (TConst (TObj cls)), but nonconst. typeName <- toHsTypeName Nonconst $ classExtName cls@@ -951,19 +599,12 @@ -- to use the C-side type of the pointer target here. HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyF.Ptr") <$> cppTypeToHsTypeAndUse HsCSide t' Internal_TRef t' -> cppTypeToHsTypeAndUse side $ ptrT t'- Internal_TFn paramTypes retType -> do- paramHsTypes <- mapM (cppTypeToHsTypeAndUse side) paramTypes+ Internal_TFn params retType -> do+ paramHsTypes <- mapM (cppTypeToHsTypeAndUse side . parameterType) params retHsType <- cppTypeToHsTypeAndUse side retType addImports hsImportForPrelude return $ foldr HsTyFun (HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyP.IO") retHsType) paramHsTypes- Internal_TCallback cb -> do- hsType <- cppTypeToHsTypeAndUse side =<< callbackToTFn side cb- case side of- HsHsSide -> return hsType- HsCSide -> do- addImports hsImportForRuntime- return $ HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyFHR.CCallback") hsType Internal_TObj cls -> case side of HsCSide -> cppTypeToHsTypeAndUse side $ ptrT $ constT t HsHsSide -> case classHaskellConversionType $ getClassHaskellConversion cls of@@ -977,31 +618,29 @@ Internal_TPtr _ -> cppTypeToHsTypeAndUse side t' Internal_TObj cls -> cppTypeToHsTypeAndUse side $ ptrT $ objT cls _ -> throwError $ tToGcInvalidFormErrorMessage Nothing t'+ Internal_TManual s -> case conversionSpecHaskell s of+ Just h -> case side of+ HsHsSide -> conversionSpecHaskellHsType h+ HsCSide -> fromMaybe (conversionSpecHaskellHsType h) $+ conversionSpecHaskellCType h+ Nothing -> throwError $ show s ++ " defines no Haskell conversion" Internal_TConst t' -> cppTypeToHsTypeAndUse side t' -- | Returns the 'ClassHaskellConversion' of a class. getClassHaskellConversion :: Class -> ClassHaskellConversion getClassHaskellConversion = classHaskellConversion . classConversion --- | Constructs the function type for a callback. For Haskell, the type depends--- on the side; the C++ side has additional parameters.------ Keep this in sync with the C++ generator's version.-callbackToTFn :: HsTypeSide -> Callback -> Generator Type-callbackToTFn side cb = do- needsExcParams <- case side of- HsCSide -> mayThrow- HsHsSide -> return False- return $ Internal_TFn ((if needsExcParams then addExcParams else id) $ callbackParams cb)- (callbackReturn cb)-- where mayThrow = case callbackThrows cb of- Just t -> return t- Nothing -> moduleCallbacksThrow <$> askModule >>= \mt -> case mt of- Just t -> return t- Nothing -> interfaceCallbacksThrow <$> askInterface-- addExcParams = (++ [ptrT intT, ptrT $ ptrT voidT])+-- | Combines the given exception handlers (from a particular exported entity)+-- with the handlers from the current module and interface. The given handlers+-- have highest precedence, followed by module handlers, followed by interface+-- handlers.+getEffectiveExceptionHandlers :: ExceptionHandlers -> Generator ExceptionHandlers+getEffectiveExceptionHandlers handlers = do+ ifaceHandlers <- interfaceExceptionHandlers <$> askInterface+ moduleHandlers <- getExceptionHandlers <$> askModule+ -- Exception handlers declared lower in the hierarchy take precedence over+ -- those higher in the hierarchy; ExceptionHandlers is a left-biased monoid.+ return $ mconcat [handlers, moduleHandlers, ifaceHandlers] -- | Prints a value like 'P.prettyPrint', but removes newlines so that they -- don't cause problems with this module's textual generation. Should be mainly
src/Foreign/Hoppy/Generator/Language/Haskell.hs-boot view
@@ -1,6 +1,6 @@ -- This file is part of Hoppy. ----- Copyright 2015-2018 Bryan Gardiner <bog@khumba.net>+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net> -- -- 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@@ -18,30 +18,37 @@ {-# LANGUAGE CPP #-} module Foreign.Hoppy.Generator.Language.Haskell (+ Managed, Generator, Output,+ SayExportMode,+ withErrorContext,+ addImports,+ sayLn, prettyPrint, ) where -#if MIN_VERSION_mtl(2,2,1) import Control.Monad.Except (Except)-#endif import Control.Monad.Reader (ReaderT) import Control.Monad.Writer (WriterT)-#if !MIN_VERSION_base(4,8,0)-import Data.Monoid (Monoid)-#endif+import {-# SOURCE #-} Foreign.Hoppy.Generator.Spec.Base (HsImportSet) import qualified Language.Haskell.Pretty as P -#if MIN_VERSION_mtl(2,2,1)+data Managed = Unmanaged | Managed+ type Generator = ReaderT Env (WriterT Output (Except String))-#else-type Generator = ReaderT Env (WriterT Output (Either String))-#endif data Env data Output instance Monoid Output++data SayExportMode++withErrorContext :: String -> Generator a -> Generator a++addImports :: HsImportSet -> Generator ()++sayLn :: String -> Generator () prettyPrint :: P.Pretty a => a -> String
src/Foreign/Hoppy/Generator/Language/Haskell/Internal.hs view
@@ -1,1694 +1,204 @@ -- This file is part of Hoppy. ----- Copyright 2015-2018 Bryan Gardiner <bog@khumba.net>------ 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 <http://www.gnu.org/licenses/>.--{-# LANGUAGE CPP #-}---- | Internal portion of the Haskell code generator.-module Foreign.Hoppy.Generator.Language.Haskell.Internal (- Generation,- generate,- generatedFiles,- ) where--#if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>), (<*>), pure)-#endif-import Control.Arrow ((&&&))-import Control.Monad (forM, unless, when)-#if MIN_VERSION_mtl(2,2,1)-import Control.Monad.Except (throwError)-#else-import Control.Monad.Error (throwError)-#endif-import Control.Monad.Trans (lift)-import Control.Monad.Writer (execWriterT, tell)-import Data.Foldable (forM_)-import Data.Graph (SCC (AcyclicSCC, CyclicSCC), stronglyConnComp)-import Data.List (intersperse)-import qualified Data.Map as M-import Data.Maybe (mapMaybe)-#if !MIN_VERSION_base(4,8,0)-import Data.Monoid (mconcat, mempty)-#endif-import qualified Data.Set as S-import Foreign.Hoppy.Generator.Common-import Foreign.Hoppy.Generator.Spec-import Foreign.Hoppy.Generator.Types-import Foreign.Hoppy.Generator.Language.Cpp (- classCastFnCppName,- classDeleteFnCppName,- externalNameToCpp,- )-import Foreign.Hoppy.Generator.Language.Haskell-import Language.Haskell.Syntax (- HsAsst,- HsContext,- HsName (HsIdent),- HsQName (Special, UnQual),- HsQualType (HsQualType),- HsSpecialCon (HsUnitCon),- HsType (HsTyApp, HsTyCon, HsTyFun, HsTyVar),- )-import System.FilePath ((<.>), pathSeparator)---- | The in-memory result of generating Haskell code for an interface.-data Generation = Generation- { generatedFiles :: M.Map FilePath String- -- ^ A map from paths of generated files to the contents of those files.- -- The file paths are relative paths below the Haskell generation root.- }---- | Runs the C++ code generator against an interface.-generate :: Interface -> Either ErrorMsg Generation-generate iface = do- -- Build the partial generation of each module.- modPartials <- forM (M.elems $ interfaceModules iface) $ \m ->- (,) m <$> execGenerator iface m (generateSource m)-- -- Compute the strongly connected components. If there is a nontrivial SCC,- -- then there is a module import cycle that we'll have to break with hs-boot- -- files.- let partialsByHsName :: M.Map HsModuleName Partial- partialsByHsName = M.fromList $ map ((partialModuleHsName &&& id) . snd) modPartials-- sccInput :: [((Module, Partial), Partial, [Partial])]- sccInput = for modPartials $ \x@(_, p) ->- (x, p,- mapMaybe (flip M.lookup partialsByHsName . hsImportModule) $- M.keys $ getHsImportSet $ outputImports $ partialOutput p)-- sccs :: [SCC (Module, Partial)]- sccs = stronglyConnComp sccInput-- fileContents <- execWriterT $ forM_ sccs $ \scc -> case scc of- AcyclicSCC (_, p) -> tell [finishPartial p "hs"]- CyclicSCC mps -> do- let cycleModNames = S.fromList $ map (partialModuleHsName . snd) mps- forM_ mps $ \(m, p) -> do- -- Create a boot partial.- pBoot <- lift $ execGenerator iface m (generateBootSource m)-- -- Change the source and boot partials so that all imports of modules in- -- this cycle are {-# SOURCE #-} imports.- let p' = setSourceImports cycleModNames p- pBoot' = setSourceImports cycleModNames pBoot-- -- Emit the completed partials.- tell [finishPartial p' "hs", finishPartial pBoot' "hs-boot"]-- return $ Generation $ M.fromList fileContents-- where finishPartial :: Partial -> String -> (FilePath, String)- finishPartial p fileExt =- (listSubst '.' pathSeparator (partialModuleHsName p) <.> fileExt,- prependExtensions $ renderPartial p)-- setSourceImports :: S.Set HsModuleName -> Partial -> Partial- setSourceImports modulesToSourceImport p =- let output = partialOutput p- imports = outputImports output- imports' = makeHsImportSet $- M.mapWithKey (setSourceImportIfIn modulesToSourceImport) $- getHsImportSet imports- output' = output { outputImports = imports' }- in p { partialOutput = output' }-- setSourceImportIfIn :: S.Set HsModuleName -> HsImportKey -> HsImportSpecs -> HsImportSpecs- setSourceImportIfIn modulesToSourceImport key specs =- if hsImportModule key `S.member` modulesToSourceImport- then specs { hsImportSource = True }- else specs--prependExtensions :: String -> String-prependExtensions = (prependExtensionsPrefix ++)--prependExtensionsPrefix :: String-prependExtensionsPrefix =- -- MultiParamTypeClasses is necessary for instances of Decodable and- -- Encodable. FlexibleContexts is needed for the type signature of the- -- function that wraps the actual callback function in callback creation- -- functions.- --- -- FlexibleInstances and TypeSynonymInstances are enabled to allow conversions- -- to and from String, which is really [Char].- --- -- UndecidableInstances is needed for instances of the form "SomeClassConstPtr- -- a => SomeClassValue a" (overlapping instances are used here too).- --- -- GeneralizedNewtypeDeriving is to enable automatic deriving of- -- Data.Bits.Bits instances for bitspace newtypes.- concat $ "{-# LANGUAGE " : intersperse ", " extensions ++ [" #-}\n"]- where extensions =- [ "FlexibleContexts"- , "FlexibleInstances"- , "ForeignFunctionInterface"- , "GeneralizedNewtypeDeriving"- , "MonoLocalBinds"- , "MultiParamTypeClasses"- , "ScopedTypeVariables"- , "TypeSynonymInstances"- , "UndecidableInstances"- ]--generateSource :: Module -> Generator ()-generateSource m = do- forM_ (moduleExports m) $ sayExport SayExportForeignImports- forM_ (moduleExports m) $ sayExport SayExportDecls-- iface <- askInterface- when (interfaceExceptionSupportModule iface == Just m) $- sayExceptionSupport True-- addendumHaskell $ getAddendum m--generateBootSource :: Module -> Generator ()-generateBootSource m = do- forM_ (moduleExports m) $ sayExport SayExportBoot-- iface <- askInterface- when (interfaceExceptionSupportModule iface == Just m) $- sayExceptionSupport False--data SayExportMode = SayExportForeignImports | SayExportDecls | SayExportBoot- deriving (Eq, Show)--sayExport :: SayExportMode -> Export -> Generator ()-sayExport mode export = do- case export of- ExportVariable v -> sayExportVar mode v- ExportEnum enum -> sayExportEnum mode enum- ExportBitspace bitspace -> sayExportBitspace mode bitspace- ExportFn fn ->- (sayExportFn mode <$> fnExtName <*> fnExtName <*> fnPurity <*>- fnParams <*> fnReturn <*> fnExceptionHandlers) fn- ExportClass cls -> sayExportClass mode cls- ExportCallback cb -> sayExportCallback mode cb-- when (mode == SayExportDecls) $- addendumHaskell $ exportAddendum export--sayExportVar :: SayExportMode -> Variable -> Generator ()-sayExportVar mode v = withErrorContext ("generating variable " ++ show (varExtName v)) $ do- let getterName = varGetterExtName v- setterName = varSetterExtName v- sayExportVar' mode (varType v) Nothing True getterName getterName setterName setterName--sayExportClassVar :: SayExportMode -> Class -> ClassVariable -> Generator ()-sayExportClassVar mode cls v =- withErrorContext ("generating variable " ++ show (classVarExtName v)) $- sayExportVar' 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)--sayExportVar' :: SayExportMode- -> Type- -> Maybe Class- -> Bool- -> ExtName- -> ExtName- -> ExtName- -> ExtName- -> Generator ()-sayExportVar' 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 $- sayExportFn mode- getterExtName- getterForeignName- Nonpure- (maybe [] (\cls -> [ptrT $ constT $ objT cls]) classIfNonstatic)- deconstType- mempty-- unless isConst $- sayExportFn mode- setterExtName- setterForeignName- Nonpure- (maybe [deconstType] (\cls -> [ptrT $ objT cls, deconstType])- classIfNonstatic)- voidT- mempty--sayExportEnum :: SayExportMode -> CppEnum -> Generator ()-sayExportEnum mode enum =- withErrorContext ("generating enum " ++ show (enumExtName enum)) $- case mode of- -- Nothing to import from the C++ side of an enum.- SayExportForeignImports -> return ()-- SayExportDecls -> do- hsTypeName <- toHsEnumTypeName enum- values <- forM (enumValueNames enum) $ \(value, name) -> do- ctorName <- toHsEnumCtorName enum name- return (value, ctorName)- addImports $ mconcat [hsImports "Prelude" ["($)", "(++)"], hsImportForPrelude]-- -- Print out the data declaration.- ln- addExport' hsTypeName- saysLn ["data ", hsTypeName, " ="]- indent $ do- forM_ (zip (False:repeat True) values) $ \(cont, (_, hsCtorName)) ->- saysLn [if cont then "| " else "", hsCtorName]- sayLn "deriving (HoppyP.Bounded, HoppyP.Eq, HoppyP.Ord, HoppyP.Show)"-- -- Print out the Enum instance.- ln- saysLn ["instance HoppyP.Enum ", hsTypeName, " where"]- indent $ do- forM_ values $ \(num, hsCtorName) ->- saysLn ["fromEnum ", hsCtorName, " = ", show num]- ln- forM_ values $ \(num, hsCtorName) ->- saysLn ["toEnum (", show num, ") = ", hsCtorName]- saysLn ["toEnum n' = HoppyP.error $ ",- show (concat ["Unknown ", hsTypeName, " numeric value: "]),- " ++ HoppyP.show n'"]-- SayExportBoot -> do- hsTypeName <- toHsEnumTypeName enum- addImports hsImportForPrelude- addExport hsTypeName- ln- saysLn ["data ", hsTypeName]- saysLn ["instance HoppyP.Bounded ", hsTypeName]- saysLn ["instance HoppyP.Enum ", hsTypeName]- saysLn ["instance HoppyP.Eq ", hsTypeName]- saysLn ["instance HoppyP.Ord ", hsTypeName]- saysLn ["instance HoppyP.Show ", hsTypeName]--sayExportBitspace :: SayExportMode -> Bitspace -> Generator ()-sayExportBitspace mode bitspace =- withErrorContext ("generating bitspace " ++ show (bitspaceExtName bitspace)) $ do- hsTypeName <- toHsBitspaceTypeName bitspace- fromFnName <- toHsBitspaceToNumName bitspace- className <- toHsBitspaceClassName bitspace- toFnName <- toHsBitspaceFromValueName bitspace- let hsType = HsTyCon $ UnQual $ HsIdent hsTypeName- case mode of- -- Nothing to import from the C++ side of a bitspace.- SayExportForeignImports -> return ()-- SayExportDecls -> do- values <- forM (bitspaceValueNames bitspace) $ \(value, name) -> do- bindingName <- toHsBitspaceValueName bitspace name- return (value, bindingName)-- hsCNumType <- cppTypeToHsTypeAndUse HsCSide $ bitspaceType bitspace- hsHsNumType <- cppTypeToHsTypeAndUse HsHsSide $ bitspaceType bitspace-- -- Print out the data declaration and conversion functions.- addImports $ mconcat [hsImportForBits, hsImportForPrelude, hsImportForRuntime]- addExport' hsTypeName- addExport' className- ln- saysLn ["newtype ", hsTypeName, " = ", hsTypeName, " { ",- fromFnName, " :: ", prettyPrint hsCNumType, " }"]- indent $ sayLn "deriving (HoppyDB.Bits, HoppyP.Bounded, HoppyP.Eq, HoppyP.Ord, HoppyP.Show)"- ln- saysLn ["class ", className, " a where"]- indent $ do- let tyVar = HsTyVar $ HsIdent "a"- saysLn [toFnName, " :: ", prettyPrint $ HsTyFun tyVar hsType]- ln- saysLn ["instance ", className, " (", prettyPrint hsCNumType, ") where"]- indent $ saysLn [toFnName, " = ", hsTypeName]- when (hsHsNumType /= hsCNumType) $ do- saysLn ["instance ", className, " (", prettyPrint hsHsNumType, ") where"]- indent $ saysLn [toFnName, " = ", hsTypeName, " . HoppyFHR.coerceIntegral"]- saysLn ["instance ", className, " ", hsTypeName, " where"]- indent $ saysLn [toFnName, " = HoppyP.id"]-- -- If the bitspace has an associated enum, then print out a conversion- -- instance for it as well.- forM_ (bitspaceEnum bitspace) $ \enum -> do- enumTypeName <- toHsEnumTypeName enum- addImports $ mconcat [hsImport1 "Prelude" "(.)", hsImportForPrelude, hsImportForRuntime]- ln- saysLn ["instance ", className, " ", enumTypeName, " where"]- indent $- saysLn [toFnName, " = ", hsTypeName, " . HoppyFHR.coerceIntegral . HoppyP.fromEnum"]-- -- Print out the constants.- ln- forM_ values $ \(num, valueName) -> do- addExport valueName- saysLn [valueName, " = ", hsTypeName, " (", show num, ")"]-- SayExportBoot -> do- hsCNumType <- cppTypeToHsTypeAndUse HsCSide $ bitspaceType bitspace- hsHsNumType <- cppTypeToHsTypeAndUse HsHsSide $ bitspaceType bitspace-- addImports $ mconcat [hsImportForBits, hsImportForPrelude]- addExport' hsTypeName- addExport' className- ln- saysLn ["newtype ", hsTypeName, " = ", hsTypeName, " { ",- fromFnName, " :: ", prettyPrint hsCNumType, " }"]- ln- saysLn ["instance HoppyDB.Bits ", hsTypeName]- saysLn ["instance HoppyP.Bounded ", hsTypeName]- saysLn ["instance HoppyP.Eq ", hsTypeName]- saysLn ["instance HoppyP.Ord ", hsTypeName]- saysLn ["instance HoppyP.Show ", hsTypeName]- ln- saysLn ["class ", className, " a where"]- indent $ do- let tyVar = HsTyVar $ HsIdent "a"- saysLn [toFnName, " :: ", prettyPrint $ HsTyFun tyVar hsType]- ln- saysLn ["instance ", className, " (", prettyPrint hsCNumType, ")"]- when (hsHsNumType /= hsCNumType) $- saysLn ["instance ", className, " (", prettyPrint hsHsNumType, ")"]- saysLn ["instance ", className, " ", hsTypeName]- forM_ (bitspaceEnum bitspace) $ \enum -> do- enumTypeName <- toHsEnumTypeName enum- saysLn ["instance ", className, " ", enumTypeName]--sayExportFn :: SayExportMode- -> ExtName- -> ExtName- -> Purity- -> [Type]- -> Type- -> ExceptionHandlers- -> Generator ()-sayExportFn mode extName foreignName purity paramTypes retType exceptionHandlers = do- effectiveHandlers <- getEffectiveExceptionHandlers exceptionHandlers- let handlerList = exceptionHandlersList effectiveHandlers- catches = not $ null handlerList-- -- We use the pure version of toHsFnName here; because foreignName isn't an- -- ExtName present in the interface's lookup table, toHsFnName would bail on- -- it. Since functions don't reference each other (e.g. we don't put anything- -- in .hs-boot files for them in circular modules cases), this isn't a problem.- let hsFnName = toHsFnName' foreignName- hsFnImportedName = hsFnName ++ "'"-- case mode of- SayExportForeignImports ->- withErrorContext ("generating imports for function " ++ show extName) $ do- -- Print a "foreign import" statement.- hsCType <- fnToHsTypeAndUse HsCSide purity paramTypes retType effectiveHandlers- saysLn ["foreign import ccall \"", externalNameToCpp extName, "\" ", hsFnImportedName,- " :: ", prettyPrint hsCType]-- SayExportDecls -> withErrorContext ("generating function " ++ show extName) $ do- -- Print the type signature.- ln- addExport hsFnName- hsHsType <- fnToHsTypeAndUse HsHsSide purity paramTypes retType effectiveHandlers- saysLn [hsFnName, " :: ", prettyPrint hsHsType]-- case purity of- Nonpure -> return ()- Pure -> saysLn ["{-# NOINLINE ", hsFnName, " #-}"]-- -- Print the function body.- let argNames = map toArgName [1..length paramTypes]- convertedArgNames = map (++ "'") argNames- -- Operators on this line must bind more weakly than operators used below,- -- namely ($) and (>>=). (So finish the line with ($).)- lineEnd <- case purity of- Nonpure -> return [" ="]- Pure -> do addImports $ mconcat [hsImport1 "Prelude" "($)", hsImportForUnsafeIO]- return [" = HoppySIU.unsafePerformIO $"]- saysLn $ hsFnName : map (' ':) argNames ++ lineEnd- indent $ do- forM_ (zip3 paramTypes argNames convertedArgNames) $ \(t, argName, argName') ->- sayArgProcessing ToCpp t argName argName'-- exceptionHandling <-- if catches- then do iface <- askInterface- currentModule <- askModule- let exceptionSupportModule = interfaceExceptionSupportModule iface- when (exceptionSupportModule /= Just currentModule) $- addImports . hsWholeModuleImport . getModuleName iface =<<- fromMaybeM (throwError- "Internal error, an exception support module is not available")- exceptionSupportModule- addImports $ mconcat [hsImport1 "Prelude" "($)", hsImportForRuntime]- return "HoppyFHR.internalHandleExceptions exceptionDb' $"- else return ""-- let callWords = exceptionHandling : hsFnImportedName : map (' ':) convertedArgNames- sayCallAndProcessReturn ToCpp retType callWords-- SayExportBoot ->- -- Functions (methods included) cannot be referenced from other exports,- -- so we don't need to emit anything.- --- -- If this changes, revisit the comment on hsFnName above.- return ()---- | Prints \"foreign import\" statements and an internal callback construction--- function for a given 'Callback' specification. For example, for a callback--- of 'HsHsSide' type @Int -> String -> IO Int@, we will generate the following--- bindings:------ > foreign import ccall "wrapper" name'newFunPtr--- > :: (CInt -> Ptr CChar -> IO CInt)--- > -> IO (FunPtr (CInt -> Ptr CChar -> IO CInt))--- >--- > -- (This is an ad-hoc generated binding for C++ callback impl class constructor.)--- > foreign import ccall "genpop__name_impl" name'newCallback--- > :: FunPtr (CInt -> Ptr CChar -> IO CInt)--- > -> FunPtr (FunPtr (IO ()) -> IO ())--- > -> Bool--- > -> IO (CCallback (CInt -> Ptr CChar -> IO CInt))--- >--- > name_newFunPtr :: (Int -> String -> IO Int) -> IO (FunPtr (CInt -> Ptr CChar -> IO CInt))--- > name_newFunPtr f'hs = name'newFunPtr $ \excIdPtr excPtrPtr arg1 arg2 ->--- > internalHandleCallbackExceptions excIdPtr excPtrPtr $--- > coerceIntegral arg1 >>= \arg1' ->--- > (...decode the C string) >>= \arg2' ->--- > fmap coerceIntegral--- > (f'hs arg1' arg2')--- >--- > name_new :: (Int -> String -> IO Int) -> IO (CCallback (CInt -> Ptr CChar -> IO CInt))--- > name_new f = do--- > f'p <- name_newFunPtr f--- > name'newCallback f'p freeHaskellFunPtrFunPtr False-sayExportCallback :: SayExportMode -> Callback -> Generator ()-sayExportCallback mode cb =- withErrorContext ("generating callback " ++ show (callbackExtName cb)) $ do- let name = callbackExtName cb- paramTypes = callbackParams cb- retType = callbackReturn cb- hsNewFunPtrFnName <- toHsCallbackNewFunPtrFnName cb- hsCtorName <- toHsCallbackCtorName cb- let hsCtorName'newCallback = hsCtorName ++ "'newCallback"- hsCtorName'newFunPtr = hsCtorName ++ "'newFunPtr"-- hsFnCType <- cppTypeToHsTypeAndUse HsCSide =<< callbackToTFn HsCSide cb- hsFnHsType <- cppTypeToHsTypeAndUse HsHsSide =<< callbackToTFn HsHsSide cb-- let getWholeNewFunPtrFnType = do- addImports $ mconcat [hsImportForForeign, hsImportForPrelude]- return $- HsTyFun hsFnHsType $- HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyP.IO") $- HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyF.FunPtr") hsFnCType- getWholeCtorType = do- addImports $ mconcat [hsImportForPrelude, hsImportForRuntime]- return $- HsTyFun hsFnHsType $- HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyP.IO") $- HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyFHR.CCallback") hsFnCType-- case mode of- SayExportForeignImports -> do- addImports $ mconcat [hsImportForForeign, hsImportForPrelude, hsImportForRuntime]- let hsFunPtrType = HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyF.FunPtr") hsFnCType- hsFunPtrImportType =- HsTyFun hsFnCType $- HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyP.IO") hsFunPtrType- hsCallbackCtorImportType =- HsTyFun hsFunPtrType $- HsTyFun (HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyF.FunPtr") $- HsTyFun (HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyF.FunPtr") $- HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyP.IO") $- HsTyCon $ Special HsUnitCon) $- HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyP.IO") $- HsTyCon $ Special HsUnitCon) $- HsTyFun (HsTyCon $ UnQual $ HsIdent "HoppyP.Bool") $- HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyP.IO") $- HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyFHR.CCallback") hsFnCType-- saysLn ["foreign import ccall \"wrapper\" ", hsCtorName'newFunPtr, " :: ",- prettyPrint hsFunPtrImportType]- saysLn ["foreign import ccall \"", externalNameToCpp name, "\" ",- hsCtorName'newCallback, " :: ", prettyPrint hsCallbackCtorImportType]-- SayExportDecls -> do- addExports [hsNewFunPtrFnName, hsCtorName]-- -- Generate the *_newFunPtr function.- wholeNewFunPtrFnType <- getWholeNewFunPtrFnType- let paramCount = length paramTypes- argNames = map toArgName [1..paramCount]- argNames' = map (++ "'") argNames- throws <- getEffectiveCallbackThrows cb- addImports $ mconcat [hsImport1 "Prelude" "($)",- hsImportForRuntime]- ln- saysLn [hsNewFunPtrFnName, " :: ", prettyPrint wholeNewFunPtrFnType]- saysLn $ hsNewFunPtrFnName : " f'hs = " : hsCtorName'newFunPtr : " $" :- case (if throws then (++ ["excIdPtr", "excPtrPtr"]) else id) argNames of- [] -> []- argNames' -> [" \\", unwords argNames', " ->"]- indent $ do- when throws $ sayLn "HoppyFHR.internalHandleCallbackExceptions excIdPtr excPtrPtr $"- forM_ (zip3 paramTypes argNames argNames') $ \(t, argName, argName') ->- sayArgProcessing FromCpp t argName argName'- sayCallAndProcessReturn FromCpp retType $- "f'hs" : map (' ':) argNames'-- -- Generate the *_new function.- wholeCtorType <- getWholeCtorType- ln- saysLn [hsCtorName, " :: ", prettyPrint wholeCtorType]- saysLn [hsCtorName, " f'hs = do"]- indent $ do- saysLn ["f'p <- ", hsNewFunPtrFnName, " f'hs"]- saysLn [hsCtorName'newCallback, " f'p HoppyFHR.freeHaskellFunPtrFunPtr HoppyP.False"]-- SayExportBoot -> do- addExports [hsNewFunPtrFnName, hsCtorName]- wholeNewFunPtrFnType <- getWholeNewFunPtrFnType- wholeCtorType <- getWholeCtorType- ln- saysLn [hsNewFunPtrFnName, " :: ", prettyPrint wholeNewFunPtrFnType]- ln- saysLn [hsCtorName, " :: ", prettyPrint wholeCtorType]--data CallDirection =- ToCpp -- ^ Haskell code is calling out to C++.- | FromCpp -- ^ C++ is invoking a callback.--sayArgProcessing :: CallDirection -> Type -> String -> String -> Generator ()-sayArgProcessing dir t fromVar toVar =- withErrorContext ("processing argument of type " ++ show t) $- case t of- Internal_TVoid -> throwError $ "TVoid is not a valid argument type"- Internal_TBool -> case dir of- ToCpp -> saysLn ["let ", toVar, " = if ", fromVar, " then 1 else 0 in"]- FromCpp -> do addImports $ hsImport1 "Prelude" "(/=)"- saysLn ["let ", toVar, " = ", fromVar, " /= 0 in"]- Internal_TChar -> noConversion- Internal_TUChar -> noConversion- Internal_TShort -> noConversion- Internal_TUShort -> noConversion- Internal_TInt -> sayCoerceIntegral- Internal_TUInt -> noConversion- Internal_TLong -> noConversion- Internal_TULong -> noConversion- Internal_TLLong -> noConversion- Internal_TULLong -> noConversion- Internal_TFloat -> sayCoerceFloating- Internal_TDouble -> sayCoerceFloating- Internal_TInt8 -> noConversion- Internal_TInt16 -> noConversion- Internal_TInt32 -> noConversion- Internal_TInt64 -> noConversion- Internal_TWord8 -> noConversion- Internal_TWord16 -> noConversion- Internal_TWord32 -> noConversion- Internal_TWord64 -> noConversion- Internal_TPtrdiff -> noConversion- Internal_TSize -> noConversion- Internal_TSSize -> noConversion- Internal_TEnum _ -> do- addImports $ mconcat [hsImport1 "Prelude" "($)", hsImportForPrelude, hsImportForRuntime]- saysLn ["let ", toVar,- -- TODO The coersion here is unnecssary if we replace the C numeric- -- types with their Haskell ones across the board (e.g. CInt ->- -- Int).- case dir of- ToCpp -> " = HoppyFHR.coerceIntegral $ HoppyP.fromEnum "- FromCpp -> " = HoppyP.toEnum $ HoppyFHR.coerceIntegral ",- fromVar, " in"]- Internal_TBitspace b -> case dir of- ToCpp -> do- toNumName <- toHsBitspaceToNumName b- fromValueName <- toHsBitspaceFromValueName b- saysLn ["let ", toVar, " = ", toNumName, " $ ", fromValueName, " ", fromVar, " in"]- FromCpp -> do- typeName <- toHsBitspaceTypeName b- saysLn ["let ", toVar, " = " , typeName, " ", fromVar, " in"]- -- References and pointers are handled equivalently.- Internal_TPtr (Internal_TObj cls) -> case dir of- ToCpp -> do- addImports $ mconcat [hsImport1 "Prelude" "($)",- hsImportForRuntime]- castMethodName <- toHsCastMethodName Nonconst cls- saysLn ["HoppyFHR.withCppPtr (", castMethodName, " ", fromVar,- ") $ \\", toVar, " ->"]- FromCpp -> do- ctorName <- toHsDataCtorName Unmanaged Nonconst cls- saysLn ["let ", toVar, " = ", ctorName, " ", fromVar, " in"]- Internal_TPtr (Internal_TConst (Internal_TObj cls)) -> case dir of- ToCpp -> do- -- Same as the (TObj _), ToCpp case.- addImports $ mconcat [hsImport1 "Prelude" "($)",- hsImportForPrelude,- hsImportForRuntime]- withValuePtrName <- toHsWithValuePtrName cls- saysLn [withValuePtrName, " ", fromVar,- " $ HoppyP.flip HoppyFHR.withCppPtr $ \\", toVar, " ->"]- FromCpp -> do- ctorName <- toHsDataCtorName Unmanaged Const cls- saysLn ["let ", toVar, " = ", ctorName, " ", fromVar, " in"]- Internal_TPtr _ -> noConversion- Internal_TRef t' -> sayArgProcessing dir (ptrT t') fromVar toVar- Internal_TFn {} -> throwError "TFn unimplemented"- Internal_TCallback cb -> case dir of- ToCpp -> do- addImports $ hsImport1 "Prelude" "(>>=)"- callbackCtorName <- toHsCallbackCtorName cb- saysLn [callbackCtorName, " ", fromVar, " >>= \\", toVar, " ->"]- FromCpp -> throwError "Can't receive a callback from C++"- Internal_TObj cls -> case dir of- ToCpp -> do- -- Same as the (TPtr (TConst (TObj _))), ToPtr case.- addImports $ mconcat [hsImport1 "Prelude" "($)",- hsImportForPrelude,- hsImportForRuntime]- withValuePtrName <- toHsWithValuePtrName cls- saysLn [withValuePtrName, " ", fromVar,- " $ HoppyP.flip HoppyFHR.withCppPtr $ \\", toVar, " ->"]- FromCpp -> case classHaskellConversionFromCppFn $ getClassHaskellConversion cls of- Just _ -> do- addImports $ mconcat [hsImport1 "Prelude" "(>>=)",- hsImportForRuntime]- ctorName <- toHsDataCtorName Unmanaged Const cls- saysLn ["HoppyFHR.decode (", ctorName, " ", fromVar, ") >>= \\", toVar, " ->"]- Nothing ->- throwError $ concat- ["Can't pass a TObj of ", show cls,- " from C++ to Haskell because no class decode conversion is defined"]- Internal_TObjToHeap cls -> case dir of- ToCpp -> throwError $ objToHeapTWrongDirectionErrorMsg Nothing cls- FromCpp -> sayArgProcessing dir (ptrT $ objT cls) fromVar toVar- Internal_TToGc t' -> case dir of- ToCpp -> throwError $ toGcTWrongDirectionErrorMsg Nothing t'- FromCpp -> do- addImports $ mconcat [hsImport1 "Prelude" "(>>=)",- hsImportForRuntime]- ctorName <-- maybe (throwError $ tToGcInvalidFormErrorMessage Nothing t')- (toHsDataCtorName Unmanaged Nonconst) $- case stripConst t' of- Internal_TObj cls -> Just cls- Internal_TRef (Internal_TConst (Internal_TObj cls)) -> Just cls- Internal_TRef (Internal_TObj cls) -> Just cls- Internal_TPtr (Internal_TConst (Internal_TObj cls)) -> Just cls- Internal_TPtr (Internal_TObj cls) -> Just cls- _ -> Nothing- saysLn ["HoppyFHR.toGc (", ctorName, " ", fromVar, ") >>= \\", toVar, " ->"]- Internal_TConst t' -> sayArgProcessing dir t' fromVar toVar- where noConversion = saysLn ["let ", toVar, " = ", fromVar, " in"]- sayCoerceIntegral = do- addImports hsImportForRuntime- saysLn ["let ", toVar, " = HoppyFHR.coerceIntegral ", fromVar, " in"]- sayCoerceFloating = do- addImports hsImportForPrelude- saysLn ["let ", toVar, " = HoppyP.realToFrac ", fromVar, " in"]---- | Note that the 'CallDirection' is the direction of the call, not the--- direction of the return. 'ToCpp' means we're returning to the foreign--- language, 'FromCpp' means we're returning from it.-sayCallAndProcessReturn :: CallDirection -> Type -> [String] -> Generator ()-sayCallAndProcessReturn dir t callWords =- withErrorContext ("processing return value of type " ++ show t) $- case t of- Internal_TVoid -> sayCall- Internal_TBool -> do- case dir of- ToCpp -> do addImports $ mconcat [hsImport1 "Prelude" "(/=)", hsImportForPrelude]- sayLn "HoppyP.fmap (/= 0)"- FromCpp -> sayLn "HoppyP.fmap (\\x -> if x then 1 else 0)"- sayCall- Internal_TChar -> sayCall- Internal_TUChar -> sayCall- Internal_TShort -> sayCall- Internal_TUShort -> sayCall- Internal_TInt -> sayCoerceIntegral >> sayCall- Internal_TUInt -> sayCall- Internal_TLong -> sayCall- Internal_TULong -> sayCall- Internal_TLLong -> sayCall- Internal_TULLong -> sayCall- Internal_TFloat -> sayCoerceFloating >> sayCall- Internal_TDouble -> sayCoerceFloating >> sayCall- Internal_TInt8 -> sayCall- Internal_TInt16 -> sayCall- Internal_TInt32 -> sayCall- Internal_TInt64 -> sayCall- Internal_TWord8 -> sayCall- Internal_TWord16 -> sayCall- Internal_TWord32 -> sayCall- Internal_TWord64 -> sayCall- Internal_TPtrdiff -> sayCall- Internal_TSize -> sayCall- Internal_TSSize -> sayCall- Internal_TEnum _ -> do- addImports $ mconcat [hsImport1 "Prelude" "(.)", hsImportForPrelude, hsImportForRuntime]- case dir of- -- TODO The coersion here is unnecssary if we replace the C numeric types- -- with their Haskell ones across the board (e.g. CInt -> Int).- ToCpp -> saysLn ["HoppyP.fmap (HoppyP.toEnum . HoppyFHR.coerceIntegral)"]- FromCpp -> saysLn ["HoppyP.fmap (HoppyFHR.coerceIntegral . HoppyP.fromEnum)"]- sayCall- Internal_TBitspace b -> do- addImports hsImportForPrelude- convFn <- bitspaceConvFn dir b- saysLn ["HoppyP.fmap ", convFn]- sayCall- -- The same as TPtr (TConst (TObj _)), but nonconst.- Internal_TPtr (Internal_TObj cls) -> do- case dir of- ToCpp -> do- addImports hsImportForPrelude- ctorName <- toHsDataCtorName Unmanaged Nonconst cls- saysLn ["HoppyP.fmap ", ctorName]- sayCall- FromCpp -> do- addImports $ mconcat [hsImportForPrelude, hsImportForRuntime]- sayLn "HoppyP.fmap HoppyFHR.toPtr"- sayCall- -- The same as TPtr (TConst (TObj _)), but nonconst.- Internal_TPtr (Internal_TConst (Internal_TObj cls)) -> case dir of- ToCpp -> do- addImports hsImportForPrelude- ctorName <- toHsDataCtorName Unmanaged Const cls- saysLn ["HoppyP.fmap ", ctorName]- sayCall- FromCpp -> do- addImports $ mconcat [hsImportForPrelude, hsImportForRuntime]- sayLn "HoppyP.fmap HoppyFHR.toPtr"- sayCall- Internal_TPtr _ -> sayCall- Internal_TRef t' -> sayCallAndProcessReturn dir (ptrT t') callWords- Internal_TFn {} -> throwError "TFn unimplemented"- Internal_TCallback cb -> case dir of- ToCpp -> throwError "Can't receive a callback from C++"- FromCpp -> do- addImports $ hsImport1 "Prelude" "(=<<)"- ctorName <- toHsCallbackCtorName cb- saysLn [ctorName, "=<<"]- sayCall- Internal_TObj cls -> case dir of- ToCpp -> case classHaskellConversionFromCppFn $ getClassHaskellConversion cls of- Just _ -> do- addImports $ mconcat [hsImports "Prelude" ["(.)", "(=<<)"],- hsImportForRuntime]- ctorName <- toHsDataCtorName Unmanaged Const cls- saysLn ["(HoppyFHR.decodeAndDelete . ", ctorName, ") =<<"]- sayCall- Nothing ->- throwError $ concat- ["Can't return a TObj of ", show cls,- " from C++ to Haskell because no class decode conversion is defined"]- FromCpp -> do- addImports $ mconcat [hsImports "Prelude" ["(.)", "(=<<)"],- hsImportForPrelude,- hsImportForRuntime]- sayLn "(HoppyP.fmap (HoppyFHR.toPtr) . HoppyFHR.encode) =<<"- sayCall- Internal_TObjToHeap cls -> case dir of- ToCpp -> sayCallAndProcessReturn dir (ptrT $ objT cls) callWords- FromCpp -> throwError $ objToHeapTWrongDirectionErrorMsg Nothing cls- Internal_TToGc t' -> case dir of- ToCpp -> do- addImports $ mconcat [hsImport1 "Prelude" "(=<<)",- hsImportForRuntime]- sayLn "HoppyFHR.toGc =<<"- -- TToGc (TObj _) should create a pointer rather than decoding, so we- -- change the TObj _ into a TPtr (TObj _).- case t' of- Internal_TObj _ -> sayCallAndProcessReturn dir (ptrT t') callWords- _ -> sayCallAndProcessReturn dir t' callWords- FromCpp -> throwError $ toGcTWrongDirectionErrorMsg Nothing t'- Internal_TConst t' -> sayCallAndProcessReturn dir t' callWords- where sayCall = saysLn $ "(" : callWords ++ [")"]- sayCoerceIntegral = do addImports $ mconcat [hsImportForPrelude, hsImportForRuntime]- sayLn "HoppyP.fmap HoppyFHR.coerceIntegral"- sayCoerceFloating = do addImports hsImportForPrelude- sayLn "HoppyP.fmap HoppyP.realToFrac"- bitspaceConvFn dir = case dir of- ToCpp -> toHsBitspaceTypeName- FromCpp -> toHsBitspaceToNumName--sayExportClass :: SayExportMode -> Class -> Generator ()-sayExportClass mode cls = withErrorContext ("generating class " ++ show (classExtName cls)) $ do- case mode of- SayExportForeignImports -> do- sayExportClassHsVars mode cls- sayExportClassHsCtors mode cls-- forM_ (classMethods cls) $ \method ->- (sayExportFn mode <$> classEntityExtName cls <*> classEntityForeignName cls <*>- methodPurity <*> pure (getMethodEffectiveParams cls method) <*>- methodReturn <*> methodExceptionHandlers)- method-- SayExportDecls -> do- sayExportClassHsClass True cls Const- sayExportClassHsClass True cls Nonconst-- sayExportClassHsStaticMethods cls-- -- Create a newtype for referencing foreign objects with pointers. The- -- newtype is not used with encodings of value objects.- sayExportClassHsType True cls Const- sayExportClassHsType True cls Nonconst-- sayExportClassExceptionSupport True cls-- sayExportClassHsVars mode cls- sayExportClassHsCtors mode cls-- SayExportBoot -> do- sayExportClassHsClass False cls Const- sayExportClassHsClass False cls Nonconst-- sayExportClassHsType False cls Const- sayExportClassHsType False cls Nonconst-- sayExportClassExceptionSupport False cls-- sayExportClassHsVars mode cls-- sayExportClassCastPrimitives mode cls- sayExportClassHsSpecialFns mode cls--sayExportClassHsClass :: Bool -> Class -> Constness -> Generator ()-sayExportClassHsClass doDecls cls cst = 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 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- addImports hsImportForPrelude- addExport' hsValueClassName- ln- saysLn ["class ", hsValueClassName, " a where"]- indent $- saysLn [hsWithValuePtrName, " :: a -> (", hsTypeName, " -> HoppyP.IO b) -> HoppyP.IO b"]-- -- Generate instances for all pointer subtypes.- ln- saysLn ["instance {-# OVERLAPPABLE #-} ", hsPtrClassName, " a => ", hsValueClassName, " a",- if doDecls then " where" else ""]- when doDecls $ do- addImports $ mconcat [hsImports "Prelude" ["($)", "(.)"],- hsImportForPrelude]- indent $ saysLn [hsWithValuePtrName, " = HoppyP.flip ($) . ", hsCastMethodName]-- -- When the class is encodable to a native Haskell type, also print an- -- instance for it.- let conv = getClassHaskellConversion cls- case (classHaskellConversionType conv,- classHaskellConversionToCppFn conv) of- (Just hsTypeGen, Just _) -> do- hsType <- hsTypeGen- ln- saysLn ["instance {-# OVERLAPPING #-} ", hsValueClassName, " (", prettyPrint hsType, ")",- if doDecls then " where" else ""]- when doDecls $ do- addImports hsImportForRuntime- indent $ saysLn [hsWithValuePtrName, " = HoppyFHR.withCppObj"]- _ -> return ()-- -- Print the pointer class definition.- addExport' hsPtrClassName- ln- saysLn $- "class (" :- intersperse ", " (map (++ " this") hsSupers) ++- [") => ", hsPtrClassName, " this where"]- indent $ 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) $- (sayExportFn SayExportDecls <$> classEntityExtName cls <*> classEntityForeignName cls <*>- methodPurity <*> pure (getMethodEffectiveParams cls method) <*>- methodReturn <*> methodExceptionHandlers) method--sayExportClassHsStaticMethods :: Class -> Generator ()-sayExportClassHsStaticMethods cls =- forM_ (classMethods cls) $ \method ->- when (methodStatic method == Static) $- (sayExportFn SayExportDecls <$> classEntityExtName cls <*> classEntityForeignName cls <*>- methodPurity <*> methodParams <*> methodReturn <*> methodExceptionHandlers) method--sayExportClassHsType :: Bool -> Class -> Constness -> Generator ()-sayExportClassHsType doDecls cls cst = withErrorContext "generating Haskell data types" $ do- hsTypeName <- toHsDataTypeName cst cls- hsCtor <- toHsDataCtorName Unmanaged cst cls- hsCtorGc <- toHsDataCtorName Managed cst cls- constCastFnName <- toHsConstCastFnName cst cls-- addImports $ mconcat [hsImportForForeign, hsImportForPrelude, hsImportForRuntime]- -- Unfortunately, we must export the data constructor, so that GHC can marshal- -- it in foreign calls in other modules.- addExport' hsTypeName- ln- saysLn ["data ", hsTypeName, " ="]- indent $ do- saysLn [" ", hsCtor, " (HoppyF.Ptr ", hsTypeName, ")"]- saysLn ["| ", hsCtorGc, " (HoppyF.ForeignPtr ()) (HoppyF.Ptr ", hsTypeName, ")"]- when doDecls $ do- addImports $ hsImport1 "Prelude" "(==)"- indent $ sayLn "deriving (HoppyP.Show)"- ln- saysLn ["instance HoppyP.Eq ", hsTypeName, " where"]- indent $ saysLn ["x == y = HoppyFHR.toPtr x == HoppyFHR.toPtr y"]- ln- saysLn ["instance HoppyP.Ord ", hsTypeName, " where"]- indent $ 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- ln- addExport constCastFnName- saysLn [constCastFnName, " :: ", hsTypeNameOppConst, " -> ", hsTypeName]- when doDecls $ do- addImports $ hsImport1 "Prelude" "($)"- hsCtorOppConst <- toHsDataCtorName Unmanaged (constNegate cst) cls- hsCtorGcOppConst <- toHsDataCtorName Managed (constNegate cst) cls- saysLn [constCastFnName, " (", hsCtorOppConst,- " ptr') = ", hsCtor, " $ HoppyF.castPtr ptr'"]- saysLn [constCastFnName, " (", hsCtorGcOppConst,- " fptr' ptr') = ", hsCtorGc, " fptr' $ HoppyF.castPtr ptr'"]-- -- Generate an instance of CppPtr.- ln- if doDecls- then do addImports $ hsImport1 "Prelude" "($)"- saysLn ["instance HoppyFHR.CppPtr ", hsTypeName, " where"]- indent $ do- saysLn ["nullptr = ", hsCtor, " HoppyF.nullPtr"]- ln- saysLn ["withCppPtr (", hsCtor, " ptr') f' = f' ptr'"]- saysLn ["withCppPtr (", hsCtorGc,- " fptr' ptr') f' = HoppyF.withForeignPtr fptr' $ \\_ -> f' ptr'"]- ln- saysLn ["toPtr (", hsCtor, " ptr') = ptr'"]- saysLn ["toPtr (", hsCtorGc, " _ ptr') = ptr'"]- ln- saysLn ["touchCppPtr (", hsCtor, " _) = HoppyP.return ()"]- saysLn ["touchCppPtr (", hsCtorGc, " fptr' _) = HoppyF.touchForeignPtr fptr'"]-- when (classDtorIsPublic cls) $ do- addImports $ hsImport1 "Prelude" "(==)"- ln- saysLn ["instance HoppyFHR.Deletable ", hsTypeName, " where"]- indent $ do- -- Note, similar "delete" and "toGc" functions are generated for exception- -- classes' ExceptionClassInfo structures.- case cst of- Const ->- saysLn ["delete (", hsCtor, " ptr') = ", toHsClassDeleteFnName' cls, " ptr'"]- Nonconst -> do- constTypeName <- toHsDataTypeName Const cls- saysLn ["delete (",hsCtor, " ptr') = ", toHsClassDeleteFnName' cls,- " $ (HoppyF.castPtr ptr' :: HoppyF.Ptr ", constTypeName, ")"]- saysLn ["delete (", hsCtorGc,- " _ _) = HoppyP.fail $ HoppyP.concat ",- "[\"Deletable.delete: Asked to delete a GC-managed \", ",- show hsTypeName, ", \" object.\"]"]- ln- 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 ())"]- saysLn ["toGc this'@(", hsCtorGc, " {}) = HoppyP.return this'"]-- forM_ (classFindCopyCtor cls) $ \copyCtor -> do- copyCtorName <- toHsCtorName cls copyCtor- ln- saysLn ["instance HoppyFHR.Copyable ", hsTypeName, " ",- case cst of- Nonconst -> hsTypeName- Const -> hsTypeNameOppConst,- " where copy = ", copyCtorName]-- else do saysLn ["instance HoppyFHR.CppPtr ", hsTypeName]-- when (classDtorIsPublic cls) $- saysLn ["instance HoppyFHR.Deletable ", hsTypeName]-- forM_ (classFindCopyCtor cls) $ \_ ->- 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 -> 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- ln- ancestorPtrClassName <- toHsPtrClassName ancestorCst ancestorCls- saysLn ["instance ", ancestorPtrClassName, " ", hsTypeName,- if doDecls then " where" else ""]- when doDecls $ indent $ do- -- Unqualified, for Haskell instance methods.- let castMethodName = toHsCastMethodName' ancestorCst ancestorCls- if null path && cst == ancestorCst- then do addImports hsImportForPrelude- saysLn [castMethodName, " = HoppyP.id"]- else do let addConst = cst == Nonconst- removeConst = ancestorCst == Nonconst- when (addConst || removeConst) $- addImports hsImportForForeign- forM_ ([minBound..] :: [Managed]) $ \managed -> do- ancestorCtor <- case managed of- Unmanaged -> (\x -> [x]) <$>- toHsDataCtorName Unmanaged ancestorCst ancestorCls- Managed -> (\x -> [x, " fptr'"]) <$>- toHsDataCtorName Managed ancestorCst ancestorCls- ptrPattern <- case managed of- Unmanaged -> (\x -> [x, " ptr'"]) <$>- toHsDataCtorName Unmanaged cst cls- Managed -> (\x -> [x, " fptr' ptr'"]) <$>- toHsDataCtorName Managed cst cls- 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 addImports $ hsImport1 "Prelude" "($)"- castPrimitiveName <- toHsCastPrimitiveName cls cls ancestorCls- return [" $ ", castPrimitiveName]- else return []- , if addConst- then do 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--sayExportClassHsVars :: SayExportMode -> Class -> Generator ()-sayExportClassHsVars mode cls =- forM_ (classVariables cls) $ sayExportClassVar mode cls--sayExportClassHsCtors :: SayExportMode -> Class -> Generator ()-sayExportClassHsCtors mode cls =- withErrorContext "generating constructors" $- forM_ (classCtors cls) $ \ctor ->- (sayExportFn mode <$> classEntityExtName cls <*> classEntityForeignName cls <*>- pure Nonpure <*> ctorParams <*> pure (ptrT $ objT cls) <*>- ctorExceptionHandlers) ctor--sayExportClassHsSpecialFns :: SayExportMode -> Class -> Generator ()-sayExportClassHsSpecialFns mode cls = do- typeName <- toHsDataTypeName Nonconst cls- typeNameConst <- toHsDataTypeName Const cls-- -- Say the delete function.- withErrorContext "generating delete bindings" $- case mode of- SayExportForeignImports -> when (classDtorIsPublic cls) $ do- addImports $ mconcat [hsImportForForeign, hsImportForPrelude]- saysLn ["foreign import ccall \"", classDeleteFnCppName cls, "\" ",- toHsClassDeleteFnName' cls, " :: HoppyF.Ptr ",- typeNameConst, " -> HoppyP.IO ()"]- saysLn ["foreign import ccall \"&", classDeleteFnCppName cls, "\" ",- toHsClassDeleteFnPtrName' cls, " :: HoppyF.FunPtr (HoppyF.Ptr ",- typeNameConst, " -> HoppyP.IO ())"]- -- The user interface to this is the generic 'delete' function, rendered- -- elsewhere.- SayExportDecls -> return ()- SayExportBoot -> return ()-- withErrorContext "generating pointer Assignable instance" $- case mode of- SayExportForeignImports -> return ()- SayExportDecls -> do- addImports $ mconcat [hsImport1 "Prelude" "($)",- hsImportForForeign,- hsImportForRuntime]- ln- saysLn ["instance HoppyFHR.Assignable (HoppyF.Ptr (HoppyF.Ptr ", typeName, ")) ",- typeName, " where"]- indent $ sayLn "assign ptr' value' = HoppyF.poke ptr' $ HoppyFHR.toPtr value'"- SayExportBoot -> return ()-- -- If the class has an assignment operator that takes its own type, then- -- generate an instance of Assignable.- withErrorContext "generating Assignable instance" $ do- let assignmentMethods = flip filter (classMethods cls) $ \m ->- methodApplicability m == MNormal &&- (methodParams m == [objT cls] || methodParams m == [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 == SayExportDecls) $ withAssignmentMethod $ \m -> do- addImports $ mconcat [hsImport1 "Prelude" "(>>)", hsImportForPrelude]- valueClassName <- toHsValueClassName cls- assignmentMethodName <- toHsMethodName cls m- ln- saysLn ["instance ", valueClassName, " a => HoppyFHR.Assignable ", typeName, " a where"]- indent $- 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.- withErrorContext "generating pointer Decodable instance" $ do- case mode of- SayExportForeignImports -> return ()-- SayExportDecls -> do- addImports $ mconcat [hsImport1 "Prelude" "(.)",- hsImportForForeign,- hsImportForPrelude,- hsImportForRuntime]- ln- saysLn ["instance HoppyFHR.Decodable (HoppyF.Ptr (HoppyF.Ptr ",- typeName, ")) ", typeName, " where"]- indent $ do- ctorName <- toHsDataCtorName Unmanaged Nonconst cls- saysLn ["decode = HoppyP.fmap ", ctorName, " . HoppyF.peek"]-- SayExportBoot -> do- addImports $ mconcat [hsImportForForeign, hsImportForRuntime]- ln- -- TODO Encodable.- saysLn ["instance HoppyFHR.Decodable (HoppyF.Ptr (HoppyF.Ptr ", typeName, ")) ", typeName]-- -- Say Encodable and Decodable instances, if the class is encodable and- -- decodable.- withErrorContext "generating Encodable/Decodable instances" $ do- let conv = getClassHaskellConversion cls- forM_ (classHaskellConversionType conv) $ \hsTypeGen -> do- let hsTypeStrGen = hsTypeGen >>= \hsType -> return $ "(" ++ prettyPrint hsType ++ ")"-- case mode of- SayExportForeignImports -> return ()-- SayExportDecls -> do- -- Say the Encodable instances.- forM_ (classHaskellConversionToCppFn conv) $ \toCppFnGen -> do- hsTypeStr <- hsTypeStrGen- addImports $ mconcat [hsImportForPrelude, hsImportForRuntime]- castMethodName <- toHsCastMethodName Const cls-- ln- saysLn ["instance HoppyFHR.Encodable ", typeName, " ", hsTypeStr, " where"]- indent $ do- sayLn "encode ="- indent toCppFnGen- ln- saysLn ["instance HoppyFHR.Encodable ", typeNameConst, " ", hsTypeStr, " where"]- indent $- saysLn ["encode = HoppyP.fmap (", castMethodName,- ") . HoppyFHR.encodeAs (HoppyP.undefined :: ", typeName, ")"]-- -- Say the Decodable instances.- forM_ (classHaskellConversionFromCppFn conv) $ \fromCppFnGen -> do- hsTypeStr <- hsTypeStrGen- addImports hsImportForRuntime- castMethodName <- toHsCastMethodName Const cls-- ln- saysLn ["instance HoppyFHR.Decodable ", typeName, " ", hsTypeStr, " where"]- indent $- saysLn ["decode = HoppyFHR.decode . ", castMethodName]- ln- saysLn ["instance HoppyFHR.Decodable ", typeNameConst, " ", hsTypeStr, " where"]- indent $ do- sayLn "decode ="- indent fromCppFnGen-- SayExportBoot -> do- -- Say the Encodable instances.- forM_ (classHaskellConversionToCppFn conv) $ \_ -> do- hsTypeStr <- hsTypeStrGen- addImports hsImportForRuntime- ln- saysLn ["instance HoppyFHR.Encodable ", typeName, " (", hsTypeStr, ")"]- saysLn ["instance HoppyFHR.Encodable ", typeNameConst, " (", hsTypeStr, ")"]-- -- Say the Decodable instances.- forM_ (classHaskellConversionFromCppFn conv) $ \_ -> do- hsTypeStr <- hsTypeStrGen- addImports hsImportForRuntime- ln- saysLn ["instance HoppyFHR.Decodable ", typeName, " (", hsTypeStr, ")"]- saysLn ["instance HoppyFHR.Decodable ", typeNameConst, " (", hsTypeStr, ")"]---- | Generates a non-const @CppException@ instance if the class is an exception--- class.-sayExportClassExceptionSupport :: Bool -> Class -> Generator ()-sayExportClassExceptionSupport doDecls cls =- when (classIsException cls) $- withErrorContext "generating exception support" $ do- typeName <- toHsDataTypeName Nonconst cls- typeNameConst <- toHsDataTypeName Const cls-- -- Generate a non-const CppException instance.- exceptionId <- getClassExceptionId cls- addImports hsImportForRuntime- ln- saysLn ["instance HoppyFHR.CppException ", typeName,- if doDecls then " where" else ""]- when doDecls $ indent $ do- ctorName <- toHsDataCtorName Unmanaged Nonconst cls- ctorGcName <- toHsDataCtorName Managed Nonconst cls- addImports $ mconcat [hsImports "Prelude" ["($)", "(.)", "(=<<)"],- hsImportForForeign,- hsImportForMap,- hsImportForPrelude]- sayLn "cppExceptionInfo _ ="- indent $ do- 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.- saysLn ["where delete' ptr' = ", toHsClassDeleteFnName' cls,- " (HoppyF.castPtr ptr' :: HoppyF.Ptr ", typeNameConst, ")"]-- indentSpaces 6 $ do- ctorName <- toHsDataCtorName Unmanaged Nonconst cls- ln- saysLn ["copy' = HoppyP.fmap (HoppyF.castPtr . HoppyFHR.toPtr) . HoppyFHR.copy . ",- ctorName, " . HoppyF.castPtr"]-- ln- 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'"]-- sayLn "upcasts' = HoppyDM.fromList"- indent $ case classSuperclasses cls of- [] -> sayLn "[]"- _ -> do- let genCast :: Bool -> [Class] -> Class -> Generator ()- genCast first path ancestorCls =- when (classIsException ancestorCls) $ do- let path' = ancestorCls : path- ancestorId <- getClassExceptionId ancestorCls- ancestorCastChain <- forM (zip path' $ drop 1 path') $ \(to, from) ->- -- We're upcasting, so 'from' is the subclass.- toHsCastPrimitiveName from from to- 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- sayLn "]"-- ln- saysLn ["cppExceptionBuild fptr' ptr' = ", ctorGcName,- " fptr' (HoppyF.castPtr ptr' :: HoppyF.Ptr ", typeName, ")"]- ln- 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.- ln- saysLn ["instance HoppyFHR.CppException ", typeNameConst,- if doDecls then " where" else ""]- when doDecls $ indent $ do- addImports $ mconcat [hsImport1 "Prelude" "(.)",- hsImportForPrelude]- constCastFnName <- toHsConstCastFnName Const cls- saysLn ["cppExceptionInfo _ = HoppyFHR.cppExceptionInfo (HoppyP.undefined :: ",- typeName, ")"]- saysLn ["cppExceptionBuild = (", constCastFnName,- " .) . HoppyFHR.cppExceptionBuild"]- saysLn ["cppExceptionBuildToGc = HoppyP.fmap ", constCastFnName,- " . HoppyFHR.cppExceptionBuildToGc"]-- -- Generate a non-const CppThrowable instance.- ln- saysLn ["instance HoppyFHR.CppThrowable ", typeName,- if doDecls then " where" else ""]- when doDecls $ indent $ do- ctorName <- toHsDataCtorName Unmanaged Nonconst cls- ctorGcName <- toHsDataCtorName Managed Nonconst cls- addImports $ mconcat [hsImportForForeign,- hsImportForPrelude]- saysLn ["toSomeCppException this'@(", ctorName, " ptr') = HoppyFHR.SomeCppException ",- "(HoppyFHR.cppExceptionInfo this') HoppyP.Nothing (HoppyF.castPtr ptr')"]- saysLn ["toSomeCppException this'@(", ctorGcName, " fptr' ptr') = HoppyFHR.SomeCppException ",- "(HoppyFHR.cppExceptionInfo this') (HoppyP.Just fptr') (HoppyF.castPtr ptr')"]--sayExportClassCastPrimitives :: SayExportMode -> Class -> Generator ()-sayExportClassCastPrimitives mode cls = withErrorContext "generating cast primitives" $ do- clsType <- toHsDataTypeName Const cls- case mode of- SayExportForeignImports ->- forAncestors cls $ \super -> do- hsCastFnName <- toHsCastPrimitiveName cls cls super- hsDownCastFnName <- toHsCastPrimitiveName cls super cls- superType <- toHsDataTypeName Const super- addImports hsImportForForeign- addExport hsCastFnName- saysLn [ "foreign import ccall \"", classCastFnCppName cls super- , "\" ", hsCastFnName, " :: HoppyF.Ptr ", clsType, " -> HoppyF.Ptr ", superType- ]- unless (classIsSubclassOfMonomorphic cls || classIsMonomorphicSuperclass super) $ do- addExport hsDownCastFnName- saysLn [ "foreign import ccall \"", classCastFnCppName super cls- , "\" ", hsDownCastFnName, " :: HoppyF.Ptr ", superType, " -> HoppyF.Ptr ", clsType- ]- return True-- 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- addExport' downCastClassName- ln- saysLn ["class ", downCastClassName, " a where"]- indent $ saysLn [downCastMethodName, " :: ",- prettyPrint $ HsTyFun (HsTyVar $ HsIdent "a") $- HsTyCon $ UnQual $ HsIdent typeName]- ln- forAncestors cls $ \super -> case classIsMonomorphicSuperclass super of- True -> return False- False -> do- superTypeName <- toHsDataTypeName cst super- primitiveCastFn <- toHsCastPrimitiveName cls super cls- 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'-- indent $ do- case cst of- Const -> saysLn [downCastMethodName, " = cast'"]- Nonconst -> do- addImports $ hsImport1 "Prelude" "(.)"- castClsToNonconst <- toHsConstCastFnName Nonconst cls- castSuperToConst <- toHsConstCastFnName Const super- saysLn [downCastMethodName, " = ", castClsToNonconst, " . cast' . ",- castSuperToConst]- indent $ do- sayLn "where"- indent $ do- clsCtorName <- toHsDataCtorName Unmanaged Const cls- clsCtorGcName <- toHsDataCtorName Managed Const cls- superCtorName <- toHsDataCtorName Unmanaged Const super- superCtorGcName <- toHsDataCtorName Managed Const super- saysLn ["cast' (", superCtorName, " ptr') = ",- clsCtorName, " $ ", primitiveCastFn, " ptr'"]- saysLn ["cast' (", superCtorGcName, " fptr' ptr') = ",- clsCtorGcName , " fptr' $ ", primitiveCastFn, " ptr'"]- return True-- SayExportBoot -> do- forAncestors cls $ \super -> do- hsCastFnName <- toHsCastPrimitiveName cls cls super- superType <- toHsDataTypeName Const super- addImports $ hsImportForForeign- addExport hsCastFnName- saysLn [hsCastFnName, " :: HoppyF.Ptr ", clsType, " -> HoppyF.Ptr ", superType]- return True-- where forAncestors :: Class -> (Class -> Generator Bool) -> Generator ()- forAncestors cls' f = forM_ (classSuperclasses cls') $ \super -> do- recur <- f super- when recur $ forAncestors super f---- | Outputs the @ExceptionDb@ needed by all Haskell gateway functions that deal--- with exceptions.-sayExceptionSupport :: Bool -> Generator ()-sayExceptionSupport doDecls = do- iface <- askInterface- addExport "exceptionDb'"- addImports hsImportForRuntime- ln- sayLn "exceptionDb' :: HoppyFHR.ExceptionDb"- when doDecls $ do- addImports $ mconcat [hsImport1 "Prelude" "($)",- hsImportForMap]- sayLn "exceptionDb' = HoppyFHR.ExceptionDb $ HoppyDM.fromList"- indent $ do- let classes = interfaceAllExceptionClasses iface- case classes of- [] -> sayLn "[]"- _ -> do- addImports hsImportForPrelude- forM_ (zip classes (True : repeat False)) $ \(cls, first) -> do- exceptionId <-- fromMaybeM (throwError $ "sayExceptionSupport: Internal error, " ++ show cls ++- " has no exception ID.") $- interfaceExceptionClassId iface cls- typeName <- toHsDataTypeName Nonconst cls- saysLn [if first then "[ (" else ", (",- "HoppyFHR.ExceptionId ", show $ getExceptionId exceptionId,- ", HoppyFHR.cppExceptionInfo (HoppyP.undefined :: ",- typeName, "))"]- sayLn "]"---- | Implements special logic on top of 'cppTypeToHsTypeAndUse', that computes--- the Haskell __qualified__ type for a function, including typeclass--- constraints.-fnToHsTypeAndUse :: HsTypeSide- -> Purity- -> [Type]- -> Type- -> ExceptionHandlers- -> Generator HsQualType-fnToHsTypeAndUse side purity paramTypes returnType exceptionHandlers = do- let catches = not $ null $ exceptionHandlersList exceptionHandlers-- params <- mapM contextForParam $- (if catches && side == HsCSide- then (++ [("excId", ptrT intT), ("excPtr", ptrT $ ptrT voidT)])- else id) $- zip (map toArgName [1..]) paramTypes- let context = mapMaybe fst params :: HsContext- hsParams = map snd params-- -- Determine the 'HsHsSide' return type for the function. Do the conversion- -- to a Haskell type, and wrap the result in 'IO' if the function is impure.- -- (HsCSide types always get wrapped in IO.)- hsReturnInitial <- cppTypeToHsTypeAndUse side returnType- hsReturnForPurity <- case (purity, side) of- (Pure, HsHsSide) -> return hsReturnInitial- _ -> do- addImports hsImportForPrelude- return $ HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyP.IO") hsReturnInitial-- return $ HsQualType context $ foldr HsTyFun hsReturnForPurity hsParams-- where contextForParam :: (String, Type) -> Generator (Maybe HsAsst, HsType)- contextForParam (s, t) = case t of- Internal_TBitspace b -> receiveBitspace s t b- Internal_TPtr (Internal_TObj cls) -> receivePtr s cls Nonconst- Internal_TPtr (Internal_TConst (Internal_TObj cls)) -> receiveValue s t cls- Internal_TRef (Internal_TObj cls) -> receivePtr s cls Nonconst- Internal_TRef (Internal_TConst (Internal_TObj cls)) -> receiveValue s t cls- Internal_TObj cls -> receiveValue s t cls- Internal_TConst t' -> contextForParam (s, t')- _ -> handoff side t-- -- Use whatever type 'cppTypeToHsTypeAndUse' suggests, with no typeclass- -- constraints.- handoff :: HsTypeSide -> Type -> Generator (Maybe HsAsst, HsType)- handoff side t = (,) Nothing <$> cppTypeToHsTypeAndUse side t-- -- Receives a @IsFooBitspace a => a@.- receiveBitspace s t b = case side of- HsCSide -> handoff side t- HsHsSide -> do- bitspaceClassName <- toHsBitspaceClassName b- let t' = HsTyVar $ HsIdent s- return (Just (UnQual $ HsIdent bitspaceClassName, [t']),- t')-- -- Receives a @FooPtr this => this@.- receivePtr :: String -> Class -> Constness -> Generator (Maybe HsAsst, HsType)- receivePtr s cls cst = case side of- HsHsSide -> do- ptrClassName <- toHsPtrClassName cst cls- let t' = HsTyVar $ HsIdent s- return (Just (UnQual $ HsIdent ptrClassName, [t']),- t')- HsCSide -> do- addImports $ hsImportForForeign- typeName <- toHsDataTypeName cst cls- return (Nothing, HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyF.Ptr") $- HsTyVar $ HsIdent typeName)-- -- Receives a @FooValue a => a@.- receiveValue :: String -> Type -> Class -> Generator (Maybe HsAsst, HsType)- receiveValue s t cls = case side of- HsCSide -> handoff side t- HsHsSide -> do- addImports hsImportForRuntime- valueClassName <- toHsValueClassName cls- let t' = HsTyVar $ HsIdent s- return (Just (UnQual $ HsIdent valueClassName, [t']),- t')--getMethodEffectiveParams :: Class -> Method -> [Type]-getMethodEffectiveParams cls method =- (case methodImpl method of- RealMethod {} -> case methodApplicability method of- MNormal -> (ptrT (objT cls):)- MConst -> (ptrT (constT $ objT cls):)- MStatic -> id- FnMethod {} -> id) $- methodParams method--getEffectiveExceptionHandlers :: ExceptionHandlers -> Generator ExceptionHandlers-getEffectiveExceptionHandlers handlers = do- ifaceHandlers <- interfaceExceptionHandlers <$> askInterface- moduleHandlers <- getExceptionHandlers <$> askModule- -- Exception handlers declared lower in the hierarchy take precedence over- -- those higher in the hierarchy; ExceptionHandlers is a left-biased monoid.- return $ mconcat [handlers, moduleHandlers, ifaceHandlers]--getEffectiveCallbackThrows :: Callback -> Generator Bool-getEffectiveCallbackThrows cb = case callbackThrows cb of- Just b -> return b- Nothing -> moduleCallbacksThrow <$> askModule >>= \case- Just b -> return b- Nothing -> interfaceCallbacksThrow <$> askInterface--getClassExceptionId :: Class -> Generator ExceptionId-getClassExceptionId cls = do- iface <- askInterface- fromMaybeM (throwError $ concat- ["Internal error, exception class ", show cls, " doesn't have an exception ID"]) $- interfaceExceptionClassId iface cls+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net>+--+-- 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 <http://www.gnu.org/licenses/>.++{-# LANGUAGE CPP #-}++-- | Internal portion of the Haskell code generator.+module Foreign.Hoppy.Generator.Language.Haskell.Internal (+ Generation,+ generate,+ generatedFiles,+ ) where++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative ((<$>), (<*>), pure)+#endif+import Control.Arrow ((&&&))+import Control.Monad (forM, when)+#if MIN_VERSION_mtl(2,2,1)+import Control.Monad.Except (throwError)+#else+import Control.Monad.Error (throwError)+#endif+import Control.Monad.Trans (lift)+import Control.Monad.Writer (execWriterT, tell)+import Data.Foldable (forM_)+import Data.Graph (SCC (AcyclicSCC, CyclicSCC), stronglyConnComp)+import Data.List (intersperse)+import qualified Data.Map as M+import Data.Maybe (mapMaybe)+#if !MIN_VERSION_base(4,8,0)+import Data.Monoid (mconcat, mempty)+#endif+import qualified Data.Set as S+import Foreign.Hoppy.Generator.Common+import Foreign.Hoppy.Generator.Spec+import Foreign.Hoppy.Generator.Spec.Class (toHsDataTypeName)+import Foreign.Hoppy.Generator.Language.Haskell+import System.FilePath ((<.>), pathSeparator)++-- | The in-memory result of generating Haskell code for an interface.+newtype Generation = Generation+ { generatedFiles :: M.Map FilePath String+ -- ^ A map from paths of generated files to the contents of those files.+ -- The file paths are relative paths below the Haskell generation root.+ }++-- | Runs the C++ code generator against an interface.+generate :: Interface -> Either ErrorMsg Generation+generate iface = do+ -- Build the partial generation of each module.+ modPartials <- forM (M.elems $ interfaceModules iface) $ \m ->+ (,) m <$> execGenerator iface m (generateSource m)++ -- Compute the strongly connected components. If there is a nontrivial SCC,+ -- then there is a module import cycle that we'll have to break with hs-boot+ -- files.+ let partialsByHsName :: M.Map HsModuleName Partial+ partialsByHsName = M.fromList $ map ((partialModuleHsName &&& id) . snd) modPartials++ sccInput :: [((Module, Partial), Partial, [Partial])]+ sccInput = for modPartials $ \x@(_, p) ->+ (x, p,+ mapMaybe (flip M.lookup partialsByHsName . hsImportModule) $+ M.keys $ getHsImportSet $ outputImports $ partialOutput p)++ sccs :: [SCC (Module, Partial)]+ sccs = stronglyConnComp sccInput++ fileContents <- execWriterT $ forM_ sccs $ \case+ AcyclicSCC (_, p) -> tell [finishPartial p "hs"]+ CyclicSCC mps -> do+ let cycleModNames = S.fromList $ map (partialModuleHsName . snd) mps+ forM_ mps $ \(m, p) -> do+ -- Create a boot partial.+ pBoot <- lift $ execGenerator iface m (generateBootSource m)++ -- Change the source and boot partials so that all imports of modules in+ -- this cycle are {-# SOURCE #-} imports.+ let p' = setSourceImports cycleModNames p+ pBoot' = setSourceImports cycleModNames pBoot++ -- Emit the completed partials.+ tell [finishPartial p' "hs", finishPartial pBoot' "hs-boot"]++ return $ Generation $ M.fromList fileContents++ where finishPartial :: Partial -> String -> (FilePath, String)+ finishPartial p fileExt =+ (listSubst '.' pathSeparator (partialModuleHsName p) <.> fileExt,+ prependExtensions $ renderPartial p)++ setSourceImports :: S.Set HsModuleName -> Partial -> Partial+ setSourceImports modulesToSourceImport p =+ let output = partialOutput p+ imports = outputImports output+ imports' = makeHsImportSet $+ M.mapWithKey (setSourceImportIfIn modulesToSourceImport) $+ getHsImportSet imports+ output' = output { outputImports = imports' }+ in p { partialOutput = output' }++ setSourceImportIfIn :: S.Set HsModuleName -> HsImportKey -> HsImportSpecs -> HsImportSpecs+ setSourceImportIfIn modulesToSourceImport key specs =+ if hsImportModule key `S.member` modulesToSourceImport+ then specs { hsImportSource = True }+ else specs++prependExtensions :: String -> String+prependExtensions = (prependExtensionsPrefix ++)++prependExtensionsPrefix :: String+prependExtensionsPrefix =+ -- MultiParamTypeClasses is necessary for instances of Decodable and+ -- Encodable. FlexibleContexts is needed for the type signature of the+ -- function that wraps the actual callback function in callback creation+ -- functions.+ --+ -- FlexibleInstances and TypeSynonymInstances are enabled to allow conversions+ -- to and from String, which is really [Char].+ --+ -- UndecidableInstances is needed for instances of the form "SomeClassConstPtr+ -- a => SomeClassValue a" (overlapping instances are used here too).+ concat $ "{-# LANGUAGE " : intersperse ", " extensions ++ [" #-}\n"]+ where extensions =+ [ "FlexibleContexts"+ , "FlexibleInstances"+ , "ForeignFunctionInterface"+ , "MonoLocalBinds"+ , "MultiParamTypeClasses"+ , "ScopedTypeVariables"+ , "TypeSynonymInstances"+ , "UndecidableInstances"+ ]++generateSource :: Module -> Generator ()+generateSource m = do+ forM_ (moduleExports m) $ sayExport SayExportForeignImports+ forM_ (moduleExports m) $ sayExport SayExportDecls++ iface <- askInterface+ when (interfaceExceptionSupportModule iface == Just m) $+ sayExceptionSupport True++ addendumHaskell $ getAddendum m++generateBootSource :: Module -> Generator ()+generateBootSource m = do+ forM_ (moduleExports m) $ sayExport SayExportBoot++ iface <- askInterface+ when (interfaceExceptionSupportModule iface == Just m) $+ sayExceptionSupport False++sayExport :: SayExportMode -> Export -> Generator ()+sayExport mode export = do+ sayExportHaskell mode export++ when (mode == SayExportDecls) $+ addendumHaskell $ getAddendum export++-- | Outputs the @ExceptionDb@ needed by all Haskell gateway functions that deal+-- with exceptions.+sayExceptionSupport :: Bool -> Generator ()+sayExceptionSupport doDecls = do+ iface <- askInterface+ addExport "exceptionDb'"+ addImports hsImportForRuntime+ ln+ sayLn "exceptionDb' :: HoppyFHR.ExceptionDb"+ when doDecls $ do+ addImports $ mconcat [hsImport1 "Prelude" "($)",+ hsImportForMap]+ sayLn "exceptionDb' = HoppyFHR.ExceptionDb $ HoppyDM.fromList"+ indent $ do+ let classes = interfaceAllExceptionClasses iface+ case classes of+ [] -> sayLn "[]"+ _ -> do+ addImports hsImportForPrelude+ forM_ (zip classes (True : repeat False)) $ \(cls, first) -> do+ exceptionId <-+ fromMaybeM (throwError $ "sayExceptionSupport: Internal error, " ++ show cls +++ " has no exception ID.") $+ interfaceExceptionClassId iface cls+ typeName <- toHsDataTypeName Nonconst cls+ saysLn [if first then "[ (" else ", (",+ "HoppyFHR.ExceptionId ", show $ getExceptionId exceptionId,+ ", HoppyFHR.cppExceptionInfo (HoppyP.undefined :: ",+ typeName, "))"]+ sayLn "]"
src/Foreign/Hoppy/Generator/Main.hs view
@@ -1,6 +1,6 @@ -- This file is part of Hoppy. ----- Copyright 2015-2018 Bryan Gardiner <bog@khumba.net>+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net> -- -- 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@@ -49,7 +49,9 @@ import qualified Data.Map as M import Data.Map (Map) import Data.Maybe (fromMaybe)-import Foreign.Hoppy.Generator.Common (writeFileIfDifferent)+import Foreign.Hoppy.Generator.Common (fromMaybeM, writeFileIfDifferent)+import Foreign.Hoppy.Generator.Hook (internalEvaluateEnumsForInterface)+import qualified Foreign.Hoppy.Generator.Language.Cpp as Cpp import qualified Foreign.Hoppy.Generator.Language.Cpp.Internal as Cpp import qualified Foreign.Hoppy.Generator.Language.Haskell.Internal as Haskell import Foreign.Hoppy.Generator.Spec@@ -73,45 +75,81 @@ -- ^ Generates C++ wrappers for an interface in the given location. | GenHaskell FilePath -- ^ Generates Haskell bindings for an interface in the given location.+ | KeepTempOutputsOnFailure+ -- ^ Instructs the generator to keep on disk any temporary programs or files+ -- created, in case of failure.+ | DumpExtNames+ -- ^ Dumps to stdout information about all external names in the current+ -- interface.+ | DumpEnums+ -- ^ Dumps to stdout information about all enums in the current interface. data AppState = AppState { appInterfaces :: Map String Interface- , appCurrentInterface :: Interface+ , appCurrentInterfaceName :: String , appCaches :: Caches+ , appKeepTempOutputsOnFailure :: Bool } +appCurrentInterface :: AppState -> Interface+appCurrentInterface state =+ let name = appCurrentInterfaceName state+ in case M.lookup name $ appInterfaces state of+ Just iface -> iface+ Nothing ->+ error $+ "Main.appCurrentInterface: Internal error, couldn't find current interface " +++ show name ++ "."+ initialAppState :: [Interface] -> AppState initialAppState ifaces = AppState { appInterfaces = M.fromList $ map (interfaceName &&& id) ifaces- , appCurrentInterface = head ifaces+ , appCurrentInterfaceName = interfaceName $ head ifaces , appCaches = M.empty+ , appKeepTempOutputsOnFailure = False } type Caches = Map String InterfaceCache data InterfaceCache = InterfaceCache- { cacheInterface :: Interface- , generatedCpp :: Maybe Cpp.Generation+ { generatedCpp :: Maybe Cpp.Generation , generatedHaskell :: Maybe Haskell.Generation } -emptyCache :: Interface -> InterfaceCache-emptyCache iface = InterfaceCache iface Nothing Nothing+emptyCache :: InterfaceCache+emptyCache = InterfaceCache Nothing Nothing -getGeneratedCpp :: InterfaceCache -> IO (InterfaceCache, Either String Cpp.Generation)-getGeneratedCpp cache = case generatedCpp cache of- Just gen -> return (cache, Right gen)- _ -> case Cpp.generate $ cacheInterface cache of- l@(Left _) -> return (cache, l)- r@(Right gen) -> return (cache { generatedCpp = Just gen }, r)+getGeneratedCpp ::+ AppState+ -> Interface+ -> InterfaceCache+ -> IO (Interface, InterfaceCache, Either String Cpp.Generation)+getGeneratedCpp state iface cache = case generatedCpp cache of+ Just gen -> return (iface, cache, Right gen)+ _ -> do+ iface' <- evaluateEnums state iface+ case Cpp.generate iface' of+ l@(Left _) -> return (iface', cache, l)+ r@(Right gen) -> return (iface', cache { generatedCpp = Just gen }, r) -getGeneratedHaskell :: InterfaceCache -> IO (InterfaceCache, Either String Haskell.Generation)-getGeneratedHaskell cache = case generatedHaskell cache of- Just gen -> return (cache, Right gen)- _ -> case Haskell.generate $ cacheInterface cache of- l@(Left _) -> return (cache, l)- r@(Right gen) -> return (cache { generatedHaskell = Just gen }, r)+getGeneratedHaskell ::+ AppState+ -> Interface+ -> InterfaceCache+ -> IO (Interface, InterfaceCache, Either String Haskell.Generation)+getGeneratedHaskell state iface cache = case generatedHaskell cache of+ Just gen -> return (iface, cache, Right gen)+ _ -> do+ iface' <- evaluateEnums state iface+ case Haskell.generate iface' of+ l@(Left _) -> return (iface', cache, l)+ r@(Right gen) -> return (iface', cache { generatedHaskell = Just gen }, r) +evaluateEnums :: AppState -> Interface -> IO Interface+evaluateEnums state iface =+ internalEvaluateEnumsForInterface iface $+ appKeepTempOutputsOnFailure state+ -- | This provides a simple @main@ function for a generator. Define your @main@ -- as: --@@ -127,7 +165,7 @@ -- | This is a version of 'defaultMain' that accepts multiple interfaces. defaultMain' :: [Either String Interface] -> IO () defaultMain' interfaceResults = do- interfaces <- forM interfaceResults $ \interfaceResult -> case interfaceResult of+ interfaces <- forM interfaceResults $ \case Left errorMsg -> do hPutStrLn stderr $ "Error initializing interface: " ++ errorMsg exitFailure@@ -183,6 +221,13 @@ , " --gen-cpp <outdir> Generate C++ bindings in a directory." , " --gen-hs <outdir> Generate Haskell bindings under the given" , " top-level source directory."+ , " --keep-temp-outputs-on-failure"+ , " Keeps on disk any temporary programs that fail"+ , " to build. Pass this before --gen-* commands."+ , " --dump-ext-names Lists the current interface's external names."+ , " --dump-enums Lists the current interface's enum data."+ , ""+ , "Arguments are processed in the order seen." ] processArgs :: MVar AppState -> [String] -> IO [Action]@@ -198,7 +243,7 @@ "--interface: Interface '" ++ name ++ "' doesn't exist in this generator." _ <- exitFailure return state- Just iface -> return state { appCurrentInterface = iface }+ Just _ -> return state { appCurrentInterfaceName = name } (SelectInterface name:) <$> processArgs stateVar rest "--list-interfaces":rest -> do@@ -259,6 +304,40 @@ uncurry $ writeGeneratedFile baseDir (GenHaskell baseDir:) <$> processArgs stateVar rest + "--dump-ext-names":rest -> do+ withCurrentCache stateVar $ \_ iface cache -> do+ forM_ (interfaceModules iface) $ \m ->+ forM_ (moduleExports m) $ \export ->+ forM_ (getAllExtNames export) $ \extName ->+ putStrLn $ "extname module=" ++ moduleName m ++ " name=" ++ fromExtName extName+ return (iface, cache, ())+ (DumpExtNames:) <$> processArgs stateVar rest++ "--dump-enums":rest -> do+ withCurrentCache stateVar $ \state iface cache -> do+ iface' <- evaluateEnums state iface+ allEvaluatedData <- flip fromMaybeM (interfaceEvaluatedEnumData iface') $ do+ hPutStrLn stderr $ "--dump-enums expected to have evaluated enum data, but doesn't."+ exitFailure+ forM_ (M.toList allEvaluatedData) $ \(extName, evaluatedData) -> do+ m <- flip fromMaybeM (M.lookup extName $ interfaceNamesToModules iface) $ do+ hPutStrLn stderr $+ "--dump-enums couldn't find module for enum " ++ show extName ++ "."+ exitFailure+ let typeStr =+ Cpp.chunkContents $ Cpp.execChunkWriter $+ Cpp.sayType Nothing $ evaluatedEnumType evaluatedData+ putStrLn $ "enum name=" ++ fromExtName extName ++ " module=" ++ moduleName m +++ " type=" ++ typeStr+ forM_ (M.toList $ evaluatedEnumValueMap evaluatedData) $ \(words', number) ->+ putStrLn $ "entry value=" ++ show number ++ " name=" ++ show words'+ return (iface', cache, ())+ (DumpEnums:) <$> processArgs stateVar rest++ "--keep-temp-outputs-on-failure":rest -> do+ modifyMVar_ stateVar $ \state -> return $ state { appKeepTempOutputsOnFailure = True }+ (KeepTempOutputsOnFailure:) <$> processArgs stateVar rest+ arg:_ -> do hPutStrLn stderr $ "Invalid option or missing argument for '" ++ arg ++ "'." exitFailure@@ -269,15 +348,20 @@ createDirectoryIfMissing True $ takeDirectory path writeFileIfDifferent path contents -withCurrentCache :: MVar AppState -> (InterfaceCache -> IO (InterfaceCache, a)) -> IO a+withCurrentCache ::+ MVar AppState+ -> (AppState -> Interface -> InterfaceCache -> IO (Interface, InterfaceCache, a))+ -> IO a withCurrentCache stateVar fn = modifyMVar stateVar $ \state -> do- let currentInterface = appCurrentInterface state- name = interfaceName currentInterface- (cache, result) <- fn $- fromMaybe (emptyCache currentInterface) $- M.lookup name $- appCaches state- return (state { appCaches = M.insert name cache $ appCaches state }, result)+ let iface = appCurrentInterface state+ name = interfaceName iface+ let cache = fromMaybe emptyCache $ M.lookup name $ appCaches state+ (iface', cache', result) <- fn state iface cache+ return ( state { appInterfaces = M.insert name iface' $ appInterfaces state+ , appCaches = M.insert name cache' $ appCaches state+ }+ , result+ ) listInterfaces :: MVar AppState -> IO () listInterfaces = mapM_ (putStrLn . interfaceName) <=< getInterfaces
+ src/Foreign/Hoppy/Generator/Override.hs view
@@ -0,0 +1,187 @@+-- This file is part of Hoppy.+--+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net>+--+-- 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 <http://www.gnu.org/licenses/>.++-- | Support for specifying overrides of values based on parameters.+--+-- For example, an entity may have a name that you want to override on a+-- per-language basis. A single value like this may be represented as a+-- @'WithOverrides' Language Name@ value. Such a value will have a default+-- name, as well as zero or more overrides, keyed by @Language@.+--+-- A 'MapWithOverrides' type is also provided for ease of overriding values+-- inside of a map.+module Foreign.Hoppy.Generator.Override (+ WithOverrides,+ plain,+ overridden,+ unoverriddenValue,+ overriddenValues,+ MapWithOverrides,+ plainMap,+ mapWithOverrides,+ addOverrideMap,+ addOverrideMaps,+ applyOverrideMaps,+ insertMapOverride,+ overriddenMapLookup,+ ) where++import qualified Data.Map as M+import Data.Maybe (fromMaybe)++-- | Represents a default value of type @v@ with optional overrides keyed by+-- parameter type @p@. The type @p@ must have an 'Ord' instance.+data WithOverrides p v = WithOverrides+ { unoverriddenValue :: v+ -- ^ The default, unoverriden value for the 'WithOverrides'. Lookups on the+ -- override will return this value a given parameter doesn't have an+ -- override.++ , overriddenValues :: M.Map p v+ -- ^ Any overridden values that have been added to the 'WithOverrides'.+ }++-- | Creates a 'WithOverrides' with the given default value @v@, and no+-- overridden values.+plain :: v -> WithOverrides p v+plain x = WithOverrides x M.empty++-- | Creates a 'WithOverrides' with the given default value @v@, and overridden+-- values in the map.+overridden :: v -> M.Map p v -> WithOverrides p v+overridden = WithOverrides++-- | Extracts a value, possibly overridden based on a parameter.+getOverride :: Ord p => p -> WithOverrides p v -> v+getOverride p o =+ fromMaybe (unoverriddenValue o) $ M.lookup p $ overriddenValues o++addOverride :: Ord p => p -> v -> WithOverrides p v -> WithOverrides p v+addOverride p v o = o { overriddenValues = M.insert p v $ overriddenValues o }++-- | Represents a map from @k@ values to @v@ values, where each entry can be+-- overridden based on parameter @p@. A key is either present with a default+-- value and possibly some overridden values, or it is completely absent -- it+-- is not possible for a key to have overridden values but no default value.+newtype MapWithOverrides p k v =+ MapWithOverrides { fromMapWithOverrides :: M.Map k (WithOverrides p v) }++-- | Converts a plain map to a 'MapWithOverrides' without any overrides.+plainMap :: M.Map k v -> MapWithOverrides p k v+plainMap = MapWithOverrides . M.map plain++-- | Direct constructor for 'MapWithOverrides'.+mapWithOverrides :: M.Map k (WithOverrides p v) -> MapWithOverrides p k v+mapWithOverrides = MapWithOverrides++-- | Adds an override @v@ for key @k@ under parameter @p@ to a+-- 'MapWithOverrides'.+--+-- It is an error for a parameter to override a key that is not present in the+-- defaults map.+insertMapOverride ::+ (Ord p, Ord k, Show p, Show k)+ => p+ -> k+ -> v+ -> MapWithOverrides p k v+ -> MapWithOverrides p k v+insertMapOverride p k v (MapWithOverrides m) =+ -- We could do this whole operation as an 'alter' rather than a+ -- 'lookup'/'adjust', but this way, if an insertion is invalid then we return+ -- an error directly rather than hiding it inside the resulting structure.+ case M.lookup k m of+ Just _ -> MapWithOverrides $ M.adjust (addOverride p v) k m+ Nothing ->+ error $ "insertMapOverride: Can't add override for parameter " ++ show p +++ " under key " ++ show k ++ " that has no default value."++-- | Adds a collection of overrides @v@ for multiple keys @k@, all under a single+-- parameter @p@, to a 'MapWithOverrides'.+--+-- It is an error for a parameter to override a key that is not present in the+-- defaults map.+addOverrideMap ::+ (Ord p, Ord k, Show p, Show k)+ => p+ -> M.Map k v+ -> MapWithOverrides p k v+ -> MapWithOverrides p k v+addOverrideMap p pOverrides (MapWithOverrides m) =+ MapWithOverrides $+ M.foldrWithKey (\k vOverride acc ->+ M.alter (\kOverrides -> case kOverrides of+ Just overrides ->+ Just $ addOverride p vOverride overrides+ Nothing ->+ error $ "addOverrideMap: Parameter " ++ show p +++ " supplies override for key " ++ show k +++ " that is not in the map of unoverridden values.")+ k+ acc)+ m+ pOverrides++-- | Adds overrides @v@ for multiple keys @k@ under multiple parameters @p@ to a+-- 'MapWithOverrides'.+--+-- It is an error for a parameter to override a key that is not present in the+-- defaults map.+addOverrideMaps ::+ (Ord p, Ord k, Show p, Show k)+ => M.Map p (M.Map k v)+ -> MapWithOverrides p k v+ -> MapWithOverrides p k v+addOverrideMaps overrideMaps (MapWithOverrides m) =+ MapWithOverrides $+ M.foldrWithKey (\p pOverrides ->+ fromMapWithOverrides . addOverrideMap p pOverrides . MapWithOverrides)+ m+ overrideMaps++-- | Constructs a 'MapWithOverrides' from a map of default values and a bunch of+-- parameter-specific maps overlaid on top of it.+--+-- It is an error for a parameter to override a key that is not present in the+-- defaults map.+applyOverrideMaps ::+ (Ord p, Ord k, Show p, Show k)+ => M.Map p (M.Map k v)+ -> M.Map k v+ -> MapWithOverrides p k v+applyOverrideMaps overrideMaps baseMap =+ MapWithOverrides $+ M.foldrWithKey (\p pOverrides acc ->+ M.foldrWithKey (\k vOverride acc' ->+ M.alter (\kOverrides -> case kOverrides of+ Just overrides ->+ Just $ addOverride p vOverride overrides+ Nothing ->+ error $ "applyOverrideMaps: Parameter " ++ show p +++ " supplies override for key " ++ show k +++ " that is not in the map of unoverridden values.")+ k+ acc')+ acc+ pOverrides)+ (M.map plain baseMap)+ overrideMaps++-- | Looks up a value for @k@ in the given 'MapWithOverrides', with the+-- possibility that the value is overridden by the parameter @p@.+overriddenMapLookup :: (Ord p, Ord k) => p -> k -> MapWithOverrides p k v -> Maybe v+overriddenMapLookup p k (MapWithOverrides x) = getOverride p <$> M.lookup k x
src/Foreign/Hoppy/Generator/Spec.hs view
@@ -1,6 +1,6 @@ -- This file is part of Hoppy. ----- Copyright 2015-2018 Bryan Gardiner <bog@khumba.net>+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net> -- -- 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@@ -24,9 +24,19 @@ module Foreign.Hoppy.Generator.Spec ( module Foreign.Hoppy.Generator.Spec.Base, module Foreign.Hoppy.Generator.Spec.Conversion,+ module Foreign.Hoppy.Generator.Spec.Variable,+ module Foreign.Hoppy.Generator.Spec.Enum,+ module Foreign.Hoppy.Generator.Spec.Function,+ module Foreign.Hoppy.Generator.Spec.Callback,+ module Foreign.Hoppy.Generator.Spec.Class, module Foreign.Hoppy.Generator.Spec.ClassFeature, ) where import Foreign.Hoppy.Generator.Spec.Base+import Foreign.Hoppy.Generator.Spec.Callback+import Foreign.Hoppy.Generator.Spec.Class import Foreign.Hoppy.Generator.Spec.ClassFeature import Foreign.Hoppy.Generator.Spec.Conversion+import Foreign.Hoppy.Generator.Spec.Enum+import Foreign.Hoppy.Generator.Spec.Function+import Foreign.Hoppy.Generator.Spec.Variable
src/Foreign/Hoppy/Generator/Spec/Base.hs view
@@ -1,2518 +1,1836 @@ -- This file is part of Hoppy. ----- Copyright 2015-2018 Bryan Gardiner <bog@khumba.net>------ 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 <http://www.gnu.org/licenses/>.--{-# LANGUAGE CPP, GeneralizedNewtypeDeriving #-}--module Foreign.Hoppy.Generator.Spec.Base (- -- * Interfaces- Interface,- ErrorMsg,- InterfaceOptions (..),- defaultInterfaceOptions,- interface,- interface',- interfaceName,- interfaceModules,- interfaceNamesToModules,- interfaceHaskellModuleBase,- interfaceDefaultHaskellModuleBase,- interfaceAddHaskellModuleBase,- interfaceHaskellModuleImportNames,- interfaceExceptionHandlers,- interfaceCallbacksThrow,- interfaceSetCallbacksThrow,- interfaceExceptionClassId,- interfaceExceptionSupportModule,- interfaceSetExceptionSupportModule,- interfaceSetSharedPtr,- -- * C++ includes- Include,- includeStd,- includeLocal,- includeToString,- -- * Modules- Module,- moduleName,- moduleHppPath,- moduleCppPath,- moduleExports,- moduleReqs,- moduleExceptionHandlers,- moduleCallbacksThrow,- moduleSetCallbacksThrow,- moduleAddendum,- moduleHaskellName,- makeModule,- moduleModify,- moduleModify',- moduleSetHppPath,- moduleSetCppPath,- moduleAddExports,- moduleAddHaskellName,- -- * Requirements- Reqs,- reqsIncludes,- reqInclude,- HasReqs (..),- addReqs,- addReqIncludes,- -- * Names and exports- ExtName,- toExtName,- isValidExtName,- fromExtName,- HasExtNames (..),- getAllExtNames,- FnName (..),- IsFnName (..),- Operator (..),- OperatorType (..),- operatorPreferredExtName,- operatorPreferredExtName',- operatorType,- Export (..),- exportAddendum,- Identifier,- identifierParts,- IdPart,- idPartBase,- idPartArgs,- ident, ident', ident1, ident2, ident3, ident4, ident5,- identT, identT', ident1T, ident2T, ident3T, ident4T, ident5T,- -- * Basic types- Type (..),- normalizeType,- stripConst,- -- ** Variables- Variable, makeVariable, varIdentifier, varExtName, varType, varReqs,- varIsConst, varGetterExtName, varSetterExtName,- -- ** Enums- CppEnum, makeEnum, enumIdentifier, enumExtName, enumValueNames, enumReqs,- enumValuePrefix, enumSetValuePrefix,- -- ** Bitspaces- Bitspace, makeBitspace, bitspaceExtName, bitspaceType, bitspaceValueNames, bitspaceEnum,- bitspaceAddEnum, bitspaceCppTypeIdentifier, bitspaceFromCppValueFn, bitspaceToCppValueFn,- bitspaceAddCppType, bitspaceReqs,- bitspaceValuePrefix, bitspaceSetValuePrefix,- -- ** Functions- Purity (..),- Function, makeFn, fnCName, fnExtName, fnPurity, fnParams, fnReturn, fnReqs, fnExceptionHandlers,- -- ** Classes- Class, makeClass, classIdentifier, classExtName, classSuperclasses,- classEntities, classAddEntities, classVariables, classCtors, classMethods,- classDtorIsPublic, classSetDtorPrivate,- classConversion, classReqs, classEntityPrefix, classSetEntityPrefix,- classIsMonomorphicSuperclass, classSetMonomorphicSuperclass,- classIsSubclassOfMonomorphic, classSetSubclassOfMonomorphic,- classIsException, classMakeException,- ClassEntity (..),- IsClassEntity (..), classEntityExtName, classEntityForeignName, classEntityForeignName',- ClassVariable,- makeClassVariable, makeClassVariable_,- mkClassVariable, mkClassVariable_,- mkStaticClassVariable, mkStaticClassVariable_,- classVarCName, classVarExtName, classVarType, classVarStatic, classVarGettable,- classVarGetterExtName, classVarGetterForeignName,- classVarSetterExtName, classVarSetterForeignName,- Ctor, makeCtor, makeCtor_, mkCtor, mkCtor_, ctorExtName, ctorParams, ctorExceptionHandlers,- Method,- MethodImpl (..),- MethodApplicability (..),- Constness (..),- constNegate,- Staticness (..),- makeMethod, makeMethod_,- makeFnMethod, makeFnMethod_,- mkMethod, mkMethod_, mkMethod', mkMethod'_,- mkConstMethod, mkConstMethod_, mkConstMethod', mkConstMethod'_,- mkStaticMethod, mkStaticMethod_, mkStaticMethod', mkStaticMethod'_,- Prop, -- The data constructor is private.- mkProp, mkProp_,- mkStaticProp, mkStaticProp_,- mkBoolIsProp, mkBoolIsProp_,- mkBoolHasProp, mkBoolHasProp_,- methodImpl, methodExtName, methodApplicability, methodPurity, methodParams,- methodReturn, methodExceptionHandlers, methodConst, methodStatic,- -- *** Conversion to and from foreign values- ClassConversion (..),- classConversionNone,- classModifyConversion,- classSetConversion,- ClassHaskellConversion (..),- classHaskellConversionNone,- classSetHaskellConversion,- -- ** Callbacks- Callback, makeCallback,- callbackExtName, callbackParams, callbackReturn, callbackThrows, callbackReqs,- callbackSetThrows,- -- * Exceptions- ExceptionId (..),- exceptionCatchAllId,- ExceptionHandler (..),- ExceptionHandlers (..),- HandlesExceptions (getExceptionHandlers),- handleExceptions,- -- * Addenda- Addendum (..),- HasAddendum (..),- addAddendumHaskell,- -- * Haskell imports- HsModuleName, HsImportSet, HsImportKey (..), HsImportSpecs (..), HsImportName, HsImportVal (..),- hsWholeModuleImport, hsQualifiedImport, hsImport1, hsImport1', hsImports, hsImports',- hsImportSetMakeSource,- -- * Internal to Hoppy- interfaceAllExceptionClasses,- interfaceSharedPtr,- classFindCopyCtor,- -- ** Haskell imports- makeHsImportSet,- getHsImportSet,- hsImportForBits,- hsImportForException,- hsImportForInt,- hsImportForWord,- hsImportForForeign,- hsImportForForeignC,- hsImportForMap,- hsImportForPrelude,- hsImportForRuntime,- hsImportForSystemPosixTypes,- hsImportForTypeable,- hsImportForUnsafeIO,- -- ** Error messages- objToHeapTWrongDirectionErrorMsg,- tToGcInvalidFormErrorMessage,- toGcTWrongDirectionErrorMsg,- ) where--#if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>), (<*>))-#endif-import Control.Arrow ((&&&))-import Control.Monad (liftM2, unless)-#if MIN_VERSION_mtl(2,2,1)-import Control.Monad.Except (MonadError, throwError)-#else-import Control.Monad.Error (MonadError, throwError)-#endif-import Control.Monad.State (MonadState, StateT, execStateT, get, modify)-import Data.Char (isAlpha, isAlphaNum, toUpper)-import Data.Function (on)-import Data.List (intercalate, intersperse)-import qualified Data.Map as M-import Data.Maybe (catMaybes, fromMaybe, isJust, isNothing, mapMaybe)-#if !MIN_VERSION_base(4,8,0)-import Data.Monoid (Monoid, mappend, mconcat, mempty)-#endif-import Data.Semigroup as Sem-import qualified Data.Set as S-import Foreign.Hoppy.Generator.Common-import {-# SOURCE #-} qualified Foreign.Hoppy.Generator.Language.Haskell as Haskell-import Language.Haskell.Syntax (HsType)---- | Indicates strings that are error messages.-type ErrorMsg = String---- | A complete specification of a C++ API. Generators for different languages,--- including the binding generator for C++, use these to produce their output.------ 'Interface' does not have a 'HandlesExceptions' instance because--- 'modifyExceptionHandlers' does not work for it (handled exceptions cannot be--- modified after an 'Interface' is constructed).-data Interface = Interface- { interfaceName :: String- -- ^ The textual name of the interface.- , interfaceModules :: M.Map String Module- -- ^ All of the individual modules, by 'moduleName'.- , interfaceNamesToModules :: M.Map ExtName Module- -- ^ Maps each 'ExtName' exported by some module to the module that exports- -- the name.- , interfaceHaskellModuleBase' :: Maybe [String]- -- ^ See 'interfaceHaskellModuleBase'.- , interfaceHaskellModuleImportNames :: M.Map Module String- -- ^ Short qualified module import names that generated modules use to refer- -- to each other tersely.- , interfaceExceptionHandlers :: ExceptionHandlers- -- ^ Exceptions that all functions in the interface may throw.- , interfaceCallbacksThrow :: Bool- -- ^ Whether callbacks within the interface support throwing C++ exceptions- -- from Haskell into C++ during their execution. This may be overridden by- -- 'moduleCallbacksThrow' and 'callbackThrows'.- , interfaceExceptionNamesToIds :: M.Map ExtName ExceptionId- -- ^ Maps from external names of exception classes to their exception IDs.- , interfaceExceptionSupportModule :: Maybe Module- -- ^ When an interface uses C++ exceptions, then one module needs to- -- manually be selected to contain some interface-specific runtime support.- -- This is the selected module.- , interfaceSharedPtr :: (Reqs, String)- -- ^ The name of the @shared_ptr@ class to use, and the requirements to use- -- it. This defaults to using @std::shared_ptr@ from @<memory>@, but can be- -- changed if necessary via 'interfaceSetSharedPtr'.- }--instance Show Interface where- show iface = concat ["<Interface ", show (interfaceName iface), ">"]---- | Optional parameters when constructing an 'Interface' with 'interface'.-data InterfaceOptions = InterfaceOptions- { interfaceOptionsExceptionHandlers :: ExceptionHandlers- }---- | Options used by 'interface'. This contains no exception handlers.-defaultInterfaceOptions :: InterfaceOptions-defaultInterfaceOptions = InterfaceOptions mempty---- | Constructs an 'Interface' from the required parts. Some validation is--- performed; if the resulting interface would be invalid, an error message is--- returned instead.------ This function passes 'defaultInterfaceOptions' to 'interface''.-interface :: String -- ^ 'interfaceName'- -> [Module] -- ^ 'interfaceModules'- -> Either ErrorMsg Interface-interface ifName modules = interface' ifName modules defaultInterfaceOptions---- | Same as 'interface', but accepts some optional arguments.-interface' :: String -- ^ 'interfaceName'- -> [Module] -- ^ 'interfaceModules'- -> InterfaceOptions- -> Either ErrorMsg Interface-interface' ifName modules options = do- -- TODO Check for duplicate module names.- -- TODO Check for duplicate module file paths.-- -- Check for multiple modules exporting an ExtName.- let extNamesToModules :: M.Map ExtName [Module]- extNamesToModules =- M.unionsWith (++) $- for modules $ \mod ->- let extNames = concatMap getAllExtNames $ M.elems $ moduleExports mod- in M.fromList $ zip extNames $ repeat [mod]-- extNamesInMultipleModules :: [(ExtName, [Module])]- extNamesInMultipleModules =- M.toList $- M.filter (\modules -> case modules of- _:_:_ -> True- _ -> False)- extNamesToModules-- unless (null extNamesInMultipleModules) $- Left $ unlines $- "Some external name(s) are exported by multiple modules:" :- map (\(extName, modules) ->- concat $ "- " : show extName : ": " : intersperse ", " (map show modules))- extNamesInMultipleModules-- let haskellModuleImportNames =- M.fromList $- (\a b f -> zipWith f a b) modules [1..] $- \mod index -> (mod, 'M' : show index)-- -- Generate a unique exception ID integer for each exception class. IDs 0 and- -- 1 are reserved.- let exceptionNamesToIds =- M.fromList $- zip (map classExtName $ interfaceAllExceptionClasses' modules)- (map ExceptionId [exceptionFirstFreeId..])-- return Interface- { interfaceName = ifName- , interfaceModules = M.fromList $ map (moduleName &&& id) modules- , interfaceNamesToModules = M.map (\[x] -> x) extNamesToModules- , interfaceHaskellModuleBase' = Nothing- , interfaceHaskellModuleImportNames = haskellModuleImportNames- , interfaceExceptionHandlers = interfaceOptionsExceptionHandlers options- , interfaceCallbacksThrow = False- , interfaceExceptionNamesToIds = exceptionNamesToIds- , interfaceExceptionSupportModule = Nothing- , interfaceSharedPtr = (reqInclude $ includeStd "memory", "std::shared_ptr")- }---- | The name of the parent Haskell module under which a Haskell module will be--- generated for a Hoppy 'Module'. This is a list of Haskell module path--- components, in other words, @'Data.List.intercalate' "."@ on the list--- produces a Haskell module name. Defaults to--- 'interfaceDefaultHaskellModuleBase', and may be overridden with--- 'interfaceAddHaskellModuleBase'.-interfaceHaskellModuleBase :: Interface -> [String]-interfaceHaskellModuleBase =- fromMaybe interfaceDefaultHaskellModuleBase . interfaceHaskellModuleBase'---- | The default Haskell module under which Hoppy modules will be generated.--- This is @Foreign.Hoppy.Generated@, that is:------ > ["Foreign", "Hoppy", "Generated"]-interfaceDefaultHaskellModuleBase :: [String]-interfaceDefaultHaskellModuleBase = ["Foreign", "Hoppy", "Generated"]---- | Sets an interface to generate all of its modules under the given Haskell--- module prefix. See 'interfaceHaskellModuleBase'.-interfaceAddHaskellModuleBase :: [String] -> Interface -> Either String Interface-interfaceAddHaskellModuleBase modulePath iface = case interfaceHaskellModuleBase' iface of- Nothing -> Right iface { interfaceHaskellModuleBase' = Just modulePath }- Just existingPath ->- Left $ concat- [ "addInterfaceHaskellModuleBase: Trying to add Haskell module base "- , intercalate "." modulePath, " to ", show iface- , " which already has a module base ", intercalate "." existingPath- ]---- | Returns the the exception ID for a class in an interface, if it has one--- (i.e. if it's been marked as an exception class with 'classMakeException').-interfaceExceptionClassId :: Interface -> Class -> Maybe ExceptionId-interfaceExceptionClassId iface cls =- M.lookup (classExtName cls) $ interfaceExceptionNamesToIds iface---- | Returns all of the exception classes in an interface.-interfaceAllExceptionClasses :: Interface -> [Class]-interfaceAllExceptionClasses = interfaceAllExceptionClasses' . M.elems . interfaceModules--interfaceAllExceptionClasses' :: [Module] -> [Class]-interfaceAllExceptionClasses' modules =- flip concatMap modules $ \mod ->- catMaybes $- for (M.elems $ moduleExports mod) $ \export -> case export of- ExportClass cls | classIsException cls -> Just cls- _ -> Nothing---- | Changes 'callbackThrows' for all callbacks in an interface that don't have it--- set explicitly at the module or callback level.-interfaceSetCallbacksThrow :: Bool -> Interface -> Interface-interfaceSetCallbacksThrow b iface = iface { interfaceCallbacksThrow = b }---- | Sets an interface's exception support module, for interfaces that use--- exceptions.-interfaceSetExceptionSupportModule :: Module -> Interface -> Interface-interfaceSetExceptionSupportModule mod iface = case interfaceExceptionSupportModule iface of- Nothing -> iface { interfaceExceptionSupportModule = Just mod }- Just existingMod ->- if mod == existingMod- then iface- else error $ "interfaceSetExceptionSupportModule: " ++ show iface ++- " already has exception support module " ++ show existingMod ++- ", trying to set " ++ show mod ++ "."---- | Installs a custom @std::shared_ptr@ implementation for use by an interface.--- Hoppy uses shared pointers for generated callback code. This function is--- useful for building code with compilers that don't provide a conforming--- @std::shared_ptr@ implementation.------ @interfaceSetSharedPtr ident reqs iface@ modifies @iface@ to use as a--- @shared_ptr@ class the C++ identifier @ident@, which needs @reqs@ in order to--- be accessed. @ident@ should be the name of a template to which an arbitrary--- @\<T\>@ can be appended, for example @"std::shared_ptr"@.------ A @shared_ptr\<T\>@ implementation @foo@ must at least provide the following--- interface:------ > foo(); // Initialization with a null pointer.--- > foo(T*); // Initialization with a given pointer.--- > foo(const foo&); // Copy-construction.--- > T& operator*() const; // Dereferencing (when non-null).--- > T* operator->() const; // Dereferencing and invocation (when non-null).--- > explicit operator bool() const; // Is the target object null?-interfaceSetSharedPtr :: String -> Reqs -> Interface -> Interface-interfaceSetSharedPtr identifier reqs iface =- iface { interfaceSharedPtr = (reqs, identifier) }---- | An @#include@ directive in a C++ file.-data Include = Include- { includeToString :: String- -- ^ Returns the complete @#include ...@ line for an include, including- -- trailing newline.- } deriving (Eq, Ord, Show)---- | Creates an @#include \<...\>@ directive.-includeStd :: String -> Include-includeStd path = Include $ "#include <" ++ path ++ ">\n"---- | Creates an @#include "..."@ directive.-includeLocal :: String -> Include-includeLocal path = Include $ "#include \"" ++ path ++ "\"\n"---- | A portion of functionality in a C++ API. An 'Interface' is composed of--- multiple modules. A module will generate a single compilation unit--- containing bindings for all of the module's exports. The C++ code for a--- generated module will @#include@ everything necessary for what is written to--- the header and source files separately. You can declare include dependencies--- with e.g. 'addReqIncludes', either for individual exports or at the module--- level (via the @'HasReqs' 'Module'@ instance). Dependencies between modules--- are handled automatically, and circularity is supported to a certain extent.--- See the documentation for the individual language modules for further--- details.-data Module = Module- { moduleName :: String- -- ^ The module's name. A module name must identify a unique module within- -- an 'Interface'.- , moduleHppPath :: String- -- ^ A relative path under a C++ sources root to which the generator will- -- write a header file for the module's C++ bindings.- , moduleCppPath :: String- -- ^ A relative path under a C++ sources root to which the generator will- -- write a source file for the module's C++ bindings.- , moduleExports :: M.Map ExtName Export- -- ^ All of the exports in a module.- , moduleReqs :: Reqs- -- ^ Module-level requirements.- , moduleHaskellName :: Maybe [String]- -- ^ The generated Haskell module name, underneath the- -- 'interfaceHaskellModuleBase'. If absent (by default), the 'moduleName'- -- is used. May be modified with 'moduleAddHaskellName'.- , moduleExceptionHandlers :: ExceptionHandlers- -- ^ Exceptions that all functions in the module may throw.- , moduleCallbacksThrow :: Maybe Bool- -- ^ Whether callbacks exported from the module support exceptions being- -- thrown during their execution. When present, this overrides- -- 'interfaceCallbacksThrow'. This maybe overridden by 'callbackThrows'.- , moduleAddendum :: Addendum- -- ^ The module's addendum.- }--instance Eq Module where- (==) = (==) `on` moduleName--instance Ord Module where- compare = compare `on` moduleName--instance Show Module where- show m = concat ["<Module ", moduleName m, ">"]--instance HasReqs Module where- getReqs = moduleReqs- setReqs reqs m = m { moduleReqs = reqs }--instance HasAddendum Module where- getAddendum = moduleAddendum- setAddendum addendum m = m { moduleAddendum = addendum }--instance HandlesExceptions Module where- getExceptionHandlers = moduleExceptionHandlers- modifyExceptionHandlers f m = m { moduleExceptionHandlers = f $ moduleExceptionHandlers m }---- | Creates an empty module, ready to be configured with 'moduleModify'.-makeModule :: String -- ^ 'moduleName'- -> String -- ^ 'moduleHppPath'- -> String -- ^ 'moduleCppPath'- -> Module-makeModule name hppPath cppPath = Module- { moduleName = name- , moduleHppPath = hppPath- , moduleCppPath = cppPath- , moduleExports = M.empty- , moduleReqs = mempty- , moduleHaskellName = Nothing- , moduleExceptionHandlers = mempty- , moduleCallbacksThrow = Nothing- , moduleAddendum = mempty- }---- | Extends a module. To be used with the module state-monad actions in this--- package.-moduleModify :: Module -> StateT Module (Either String) () -> Either ErrorMsg Module-moduleModify = flip execStateT---- | Same as 'moduleModify', but calls 'error' in the case of failure, which is--- okay in for a generator which would abort in this case anyway.-moduleModify' :: Module -> StateT Module (Either String) () -> Module-moduleModify' m action = case moduleModify m action of- Left errorMsg ->- error $ concat- ["moduleModify' failed to modify ", show m, ": ", errorMsg]- Right m' -> m'---- | Replaces a module's 'moduleHppPath'.-moduleSetHppPath :: MonadState Module m => String -> m ()-moduleSetHppPath path = modify $ \m -> m { moduleHppPath = path }---- | Replaces a module's 'moduleCppPath'.-moduleSetCppPath :: MonadState Module m => String -> m ()-moduleSetCppPath path = modify $ \m -> m { moduleCppPath = path }---- | Adds exports to a module. An export must only be added to any module at--- most once, and must not be added to multiple modules.-moduleAddExports :: (MonadError String m, MonadState Module m) => [Export] -> m ()-moduleAddExports exports = do- m <- get- let existingExports = moduleExports m- newExports = M.fromList $ map (getPrimaryExtName &&& id) exports- duplicateNames = (S.intersection `on` M.keysSet) existingExports newExports- if S.null duplicateNames- then modify $ \m -> m { moduleExports = existingExports `mappend` newExports }- else throwError $ concat- ["moduleAddExports: ", show m, " defines external names multiple times: ",- show duplicateNames]---- | Changes a module's 'moduleHaskellName' from the default. This can only be--- called once on a module.-moduleAddHaskellName :: (MonadError String m, MonadState Module m) => [String] -> m ()-moduleAddHaskellName name = do- m <- get- case moduleHaskellName m of- Nothing -> modify $ \m -> m { moduleHaskellName = Just name }- Just name' ->- throwError $ concat- ["moduleAddHaskellName: ", show m, " already has Haskell name ",- show name', "; trying to add name ", show name, "."]---- | Changes 'callbackThrows' for all callbacks in a module that don't have it--- set explicitly.-moduleSetCallbacksThrow :: MonadState Module m => Maybe Bool -> m ()-moduleSetCallbacksThrow b = modify $ \m -> m { moduleCallbacksThrow = b }---- | A set of requirements of needed to use an identifier in C++ (function,--- type, etc.), via a set of 'Include's. The monoid instance has 'mempty' as an--- empty set of includes, and 'mappend' unions two include sets.-data Reqs = Reqs- { reqsIncludes :: S.Set Include- -- ^ The includes specified by a 'Reqs'.- } deriving (Show)--instance Sem.Semigroup Reqs where- (<>) (Reqs incl) (Reqs incl') = Reqs $ mappend incl incl'--instance Monoid Reqs where- mempty = Reqs mempty-- mappend = (<>)-- mconcat reqs = Reqs $ mconcat $ map reqsIncludes reqs---- | Creates a 'Reqs' that contains the given include.-reqInclude :: Include -> Reqs-reqInclude include = mempty { reqsIncludes = S.singleton include }---- | Contains the data types for bindings to C++ entities: 'Function', 'Class',--- etc. Use 'addReqs' or 'addReqIncludes' to specify requirements for these--- entities, e.g. header files that must be included in order to access the--- underlying entities that are being bound.---- | C++ types that have requirements in order to use them in generated--- bindings.-class HasReqs a where- {-# MINIMAL getReqs, (setReqs | modifyReqs) #-}-- -- | Returns an object's requirements.- getReqs :: a -> Reqs-- -- | Replaces an object's requirements with new ones.- setReqs :: Reqs -> a -> a- setReqs = modifyReqs . const-- -- | Modifies an object's requirements.- modifyReqs :: (Reqs -> Reqs) -> a -> a- modifyReqs f x = setReqs (f $ getReqs x) x---- | Adds to a object's requirements.-addReqs :: HasReqs a => Reqs -> a -> a-addReqs reqs = modifyReqs $ mappend reqs---- | Adds a list of includes to the requirements of an object.-addReqIncludes :: HasReqs a => [Include] -> a -> a-addReqIncludes includes =- modifyReqs $ mappend mempty { reqsIncludes = S.fromList includes }---- | An external name is a string that generated bindings use to uniquely--- identify an object at runtime. An external name must start with an--- alphabetic character, and may only contain alphanumeric characters and @'_'@.--- You are free to use whatever naming style you like; case conversions will be--- performed automatically when required. Hoppy does make use of some--- conventions though, for example with 'Operator's and in the provided bindings--- for the C++ standard library.------ External names must be unique within an interface. They may not be reused--- between modules. This assumption is used for symbol naming in compiled--- shared objects and to freely import modules in Haskell bindings.-newtype ExtName = ExtName- { fromExtName :: String- -- ^ Returns the string an an 'ExtName' contains.- } deriving (Eq, Sem.Semigroup, Monoid, Ord)--instance Show ExtName where- show extName = concat ["$\"", fromExtName extName, "\"$"]---- | Creates an 'ExtName' that contains the given string, erroring if the string--- is an invalid 'ExtName'.-toExtName :: String -> ExtName-toExtName str = case str of- -- Keep this logic in sync with isValidExtName.- [] -> error "An ExtName cannot be empty."- _ -> if isValidExtName str- then ExtName str- else error $- "An ExtName must start with a letter and only contain letters, numbers, and '_': " ++- show str---- | Returns true if the given string is represents a valid 'ExtName'.-isValidExtName :: String -> Bool-isValidExtName str = case str of- -- Keep this logic in sync with toExtName.- [] -> False- c:cs -> isAlpha c && all ((||) <$> isAlphaNum <*> (== '_')) cs---- | Generates an 'ExtName' from an 'Identifier', if the given name is absent.-extNameOrIdentifier :: Identifier -> Maybe ExtName -> ExtName-extNameOrIdentifier ident = fromMaybe $ case identifierParts ident of- [] -> error "extNameOrIdentifier: Invalid empty identifier."- parts -> toExtName $ idPartBase $ last parts---- | Generates an 'ExtName' from an @'FnName' 'Identifier'@, if the given name--- is absent.-extNameOrFnIdentifier :: FnName Identifier -> Maybe ExtName -> ExtName-extNameOrFnIdentifier name =- fromMaybe $ case name of- FnName identifier -> case identifierParts identifier of- [] -> error "extNameOrFnIdentifier: Empty idenfitier."- parts -> toExtName $ idPartBase $ last parts- FnOp op -> operatorPreferredExtName op---- | Generates an 'ExtName' from a string, if the given name is absent.-extNameOrString :: String -> Maybe ExtName -> ExtName-extNameOrString str = fromMaybe $ toExtName str---- | Types that have an external name, and also optionally have nested entities--- with external names as well. See 'getAllExtNames'.-class HasExtNames a where- -- | Returns the external name by which a given entity is referenced.- getPrimaryExtName :: a -> ExtName-- -- | Returns external names nested within the given entity. Does not include- -- the primary external name.- getNestedExtNames :: a -> [ExtName]- getNestedExtNames _ = []---- | Returns a list of all of the external names an entity contains. This--- combines both 'getPrimaryExtName' and 'getNestedExtNames'.-getAllExtNames :: HasExtNames a => a -> [ExtName]-getAllExtNames x = getPrimaryExtName x : getNestedExtNames x---- | The C++ name of a function or method.-data FnName name =- FnName name- -- ^ A regular, \"alphanumeric\" name. The exact type depends on what kind of- -- object is being named.- | FnOp Operator- -- ^ An operator name.- deriving (Eq, Ord)--instance Show name => Show (FnName name) where- show (FnName name) = concat ["<FnName ", show name, ">"]- show (FnOp op) = concat ["<FnOp ", show op, ">"]---- | Enables implementing automatic conversions to a @'FnName' t@.-class IsFnName t a where- toFnName :: a -> FnName t--instance IsFnName t (FnName t) where- toFnName = id--instance IsFnName t t where- toFnName = FnName--instance IsFnName t Operator where- toFnName = FnOp---- | Overloadable C++ operators.-data Operator =- OpCall -- ^ @x(...)@- | OpComma -- ^ @x, y@- | OpAssign -- ^ @x = y@- | OpArray -- ^ @x[y]@- | OpDeref -- ^ @*x@- | OpAddress -- ^ @&x@- | OpAdd -- ^ @x + y@- | OpAddAssign -- ^ @x += y@- | OpSubtract -- ^ @x - y@- | OpSubtractAssign -- ^ @x -= y@- | OpMultiply -- ^ @x * y@- | OpMultiplyAssign -- ^ @x *= y@- | OpDivide -- ^ @x / y@- | OpDivideAssign -- ^ @x /= y@- | OpModulo -- ^ @x % y@- | OpModuloAssign -- ^ @x %= y@- | OpPlus -- ^ @+x@- | OpMinus -- ^ @-x@- | OpIncPre -- ^ @++x@- | OpIncPost -- ^ @x++@- | OpDecPre -- ^ @--x@- | OpDecPost -- ^ @x--@- | OpEq -- ^ @x == y@- | OpNe -- ^ @x != y@- | OpLt -- ^ @x < y@- | OpLe -- ^ @x <= y@- | OpGt -- ^ @x > y@- | OpGe -- ^ @x >= y@- | OpNot -- ^ @!x@- | OpAnd -- ^ @x && y@- | OpOr -- ^ @x || y@- | OpBitNot -- ^ @~x@- | OpBitAnd -- ^ @x & y@- | OpBitAndAssign -- ^ @x &= y@- | OpBitOr -- ^ @x | y@- | OpBitOrAssign -- ^ @x |= y@- | OpBitXor -- ^ @x ^ y@- | OpBitXorAssign -- ^ @x ^= y@- | OpShl -- ^ @x << y@- | OpShlAssign -- ^ @x <<= y@- | OpShr -- ^ @x >> y@- | OpShrAssign -- ^ @x >>= y@- deriving (Bounded, Enum, Eq, Ord, Show)---- | The arity and syntax of an operator.-data OperatorType =- UnaryPrefixOperator String -- ^ Prefix unary operators. Examples: @!x@, @*x@, @++x@.- | UnaryPostfixOperator String -- ^ Postfix unary operators. Examples: @x--, x++@.- | BinaryOperator String -- ^ Infix binary operators. Examples: @x * y@, @x >>= y@.- | CallOperator -- ^ @x(...)@ with arbitrary arity.- | ArrayOperator -- ^ @x[y]@, a binary operator with non-infix syntax.--data OperatorInfo = OperatorInfo- { operatorPreferredExtName'' :: ExtName- , operatorType' :: OperatorType- }--makeOperatorInfo :: String -> OperatorType -> OperatorInfo-makeOperatorInfo = OperatorInfo . toExtName---- | Returns a conventional string to use for the 'ExtName' of an operator.-operatorPreferredExtName :: Operator -> ExtName-operatorPreferredExtName op = case M.lookup op operatorInfo of- Just info -> operatorPreferredExtName'' info- Nothing ->- error $ concat- ["operatorPreferredExtName: Internal error, missing info for operator ", show op, "."]---- | Returns a conventional name for an operator, as with--- 'operatorPreferredExtName', but as a string.-operatorPreferredExtName' :: Operator -> String-operatorPreferredExtName' = fromExtName . operatorPreferredExtName---- | Returns the type of an operator.-operatorType :: Operator -> OperatorType-operatorType op = case M.lookup op operatorInfo of- Just info -> operatorType' info- Nothing ->- error $ concat- ["operatorType: Internal error, missing info for operator ", show op, "."]---- | Metadata for operators.------ TODO Test out this missing data.-operatorInfo :: M.Map Operator OperatorInfo-operatorInfo =- let input =- [ (OpCall, makeOperatorInfo "CALL" CallOperator)- , (OpComma, makeOperatorInfo "COMMA" $ BinaryOperator ",")- , (OpAssign, makeOperatorInfo "ASSIGN" $ BinaryOperator "=")- , (OpArray, makeOperatorInfo "ARRAY" ArrayOperator)- , (OpDeref, makeOperatorInfo "DEREF" $ UnaryPrefixOperator "*")- , (OpAddress, makeOperatorInfo "ADDRESS" $ UnaryPrefixOperator "&")- , (OpAdd, makeOperatorInfo "ADD" $ BinaryOperator "+")- , (OpAddAssign, makeOperatorInfo "ADDA" $ BinaryOperator "+=")- , (OpSubtract, makeOperatorInfo "SUB" $ BinaryOperator "-")- , (OpSubtractAssign, makeOperatorInfo "SUBA" $ BinaryOperator "-=")- , (OpMultiply, makeOperatorInfo "MUL" $ BinaryOperator "*")- , (OpMultiplyAssign, makeOperatorInfo "MULA" $ BinaryOperator "*=")- , (OpDivide, makeOperatorInfo "DIV" $ BinaryOperator "/")- , (OpDivideAssign, makeOperatorInfo "DIVA" $ BinaryOperator "/=")- , (OpModulo, makeOperatorInfo "MOD" $ BinaryOperator "%")- , (OpModuloAssign, makeOperatorInfo "MODA" $ BinaryOperator "%=")- , (OpPlus, makeOperatorInfo "PLUS" $ UnaryPrefixOperator "+")- , (OpMinus, makeOperatorInfo "NEG" $ UnaryPrefixOperator "-")- , (OpIncPre, makeOperatorInfo "INC" $ UnaryPrefixOperator "++")- , (OpIncPost, makeOperatorInfo "INCPOST" $ UnaryPostfixOperator "++")- , (OpDecPre, makeOperatorInfo "DEC" $ UnaryPrefixOperator "--")- , (OpDecPost, makeOperatorInfo "DECPOST" $ UnaryPostfixOperator "--")- , (OpEq, makeOperatorInfo "EQ" $ BinaryOperator "==")- , (OpNe, makeOperatorInfo "NE" $ BinaryOperator "!=")- , (OpLt, makeOperatorInfo "LT" $ BinaryOperator "<")- , (OpLe, makeOperatorInfo "LE" $ BinaryOperator "<=")- , (OpGt, makeOperatorInfo "GT" $ BinaryOperator ">")- , (OpGe, makeOperatorInfo "GE" $ BinaryOperator ">=")- , (OpNot, makeOperatorInfo "NOT" $ UnaryPrefixOperator "!")- , (OpAnd, makeOperatorInfo "AND" $ BinaryOperator "&&")- , (OpOr, makeOperatorInfo "OR" $ BinaryOperator "||")- , (OpBitNot, makeOperatorInfo "BNOT" $ UnaryPrefixOperator "~")- , (OpBitAnd, makeOperatorInfo "BAND" $ BinaryOperator "&")- , (OpBitAndAssign, makeOperatorInfo "BANDA" $ BinaryOperator "&=")- , (OpBitOr, makeOperatorInfo "BOR" $ BinaryOperator "|")- , (OpBitOrAssign, makeOperatorInfo "BORA" $ BinaryOperator "|=")- , (OpBitXor, makeOperatorInfo "BXOR" $ BinaryOperator "^")- , (OpBitXorAssign, makeOperatorInfo "BXORA" $ BinaryOperator "^=")- , (OpShl, makeOperatorInfo "SHL" $ BinaryOperator "<<")- , (OpShlAssign, makeOperatorInfo "SHLA" $ BinaryOperator "<<=")- , (OpShr, makeOperatorInfo "SHR" $ BinaryOperator ">>")- , (OpShrAssign, makeOperatorInfo "SHR" $ BinaryOperator ">>=")- ]- in if map fst input == [minBound..]- then M.fromList input- else error "operatorInfo: Operator info list is out of sync with Operator data type."---- | Specifies some C++ object (function or class) to give access to.-data Export =- ExportVariable Variable -- ^ Exports a variable.- | ExportEnum CppEnum -- ^ Exports an enum.- | ExportBitspace Bitspace -- ^ Exports a bitspace.- | ExportFn Function -- ^ Exports a function.- | ExportClass Class -- ^ Exports a class with all of its contents.- | ExportCallback Callback -- ^ Exports a callback.- deriving (Show)--instance HasExtNames Export where- getPrimaryExtName x = case x of- ExportVariable v -> getPrimaryExtName v- ExportEnum e -> getPrimaryExtName e- ExportBitspace b -> getPrimaryExtName b- ExportFn f -> getPrimaryExtName f- ExportClass cls -> getPrimaryExtName cls- ExportCallback cb -> getPrimaryExtName cb-- getNestedExtNames x = case x of- ExportVariable v -> getNestedExtNames v- ExportEnum e -> getNestedExtNames e- ExportBitspace b -> getNestedExtNames b- ExportFn f -> getNestedExtNames f- ExportClass cls -> getNestedExtNames cls- ExportCallback cb -> getNestedExtNames cb---- | Returns the export's addendum. 'Export' doesn't have a 'HasAddendum'--- instance because you normally wouldn't want to modify the addendum of one.-exportAddendum export = case export of- ExportVariable v -> getAddendum v- ExportEnum e -> getAddendum e- ExportBitspace bs -> getAddendum bs- ExportFn f -> getAddendum f- ExportClass cls -> getAddendum cls- ExportCallback cb -> getAddendum cb---- | A path to some C++ object, including namespaces. An identifier consists of--- multiple parts separated by @\"::\"@. Each part has a name string followed--- by an optional template argument list, where each argument gets rendered from--- a 'Type' (non-type arguments for template metaprogramming are not supported).-newtype Identifier = Identifier- { identifierParts :: [IdPart]- -- ^ The separate parts of the identifier, between @::@s.- } deriving (Eq)--instance Show Identifier where- show ident =- (\words -> concat $ "<Identifier " : words ++ [">"]) $- intersperse "::" $- map (\part -> case idPartArgs part of- Nothing -> idPartBase part- Just args ->- concat $- idPartBase part : "<" :- intersperse ", " (map show args) ++ [">"]) $- identifierParts ident---- | A single component of an 'Identifier', between @::@s.-data IdPart = IdPart- { idPartBase :: String- -- ^ The name within the enclosing scope.- , idPartArgs :: Maybe [Type]- -- ^ Template arguments, if present.- } deriving (Eq, Show)---- | Creates an identifier of the form @a@.-ident :: String -> Identifier-ident a = Identifier [IdPart a Nothing]---- | Creates an identifier of the form @a1::a2::...::aN@.-ident' :: [String] -> Identifier-ident' = Identifier . map (\x -> IdPart x Nothing)---- | Creates an identifier of the form @a::b@.-ident1 :: String -> String -> Identifier-ident1 a b = ident' [a, b]---- | Creates an identifier of the form @a::b::c@.-ident2 :: String -> String -> String -> Identifier-ident2 a b c = ident' [a, b, c]---- | Creates an identifier of the form @a::b::c::d@.-ident3 :: String -> String -> String -> String -> Identifier-ident3 a b c d = ident' [a, b, c, d]---- | Creates an identifier of the form @a::b::c::d::e@.-ident4 :: String -> String -> String -> String -> String -> Identifier-ident4 a b c d e = ident' [a, b, c, d, e]---- | Creates an identifier of the form @a::b::c::d::e::f@.-ident5 :: String -> String -> String -> String -> String -> String -> Identifier-ident5 a b c d e f = ident' [a, b, c, d, e, f]---- | Creates an identifier of the form @a\<...\>@.-identT :: String -> [Type] -> Identifier-identT a ts = Identifier [IdPart a $ Just ts]---- | Creates an identifier with arbitrary many templated and non-templated--- parts.-identT' :: [(String, Maybe [Type])] -> Identifier-identT' = Identifier . map (uncurry IdPart)---- | Creates an identifier of the form @a::b\<...\>@.-ident1T :: String -> String -> [Type] -> Identifier-ident1T a b ts = Identifier [IdPart a Nothing, IdPart b $ Just ts]---- | Creates an identifier of the form @a::b::c\<...\>@.-ident2T :: String -> String -> String -> [Type] -> Identifier-ident2T a b c ts = Identifier [IdPart a Nothing, IdPart b Nothing, IdPart c $ Just ts]---- | Creates an identifier of the form @a::b::c::d\<...\>@.-ident3T :: String -> String -> String -> String -> [Type] -> Identifier-ident3T a b c d ts =- Identifier [IdPart a Nothing, IdPart b Nothing, IdPart c Nothing,- IdPart d $ Just ts]---- | Creates an identifier of the form @a::b::c::d::e\<...\>@.-ident4T :: String -> String -> String -> String -> String -> [Type] -> Identifier-ident4T a b c d e ts =- Identifier [IdPart a Nothing, IdPart b Nothing, IdPart c Nothing,- IdPart d Nothing, IdPart e $ Just ts]---- | Creates an identifier of the form @a::b::c::d::e::f\<...\>@.-ident5T :: String -> String -> String -> String -> String -> String -> [Type] -> Identifier-ident5T a b c d e f ts =- Identifier [IdPart a Nothing, IdPart b Nothing, IdPart c Nothing,- IdPart d Nothing, IdPart e Nothing, IdPart f $ Just ts]---- | A concrete C++ type. Use the bindings in "Foreign.Hoppy.Generator.Types"--- for values of this type; these data constructors are subject to change--- without notice.-data Type =- Internal_TVoid- | Internal_TBool- | Internal_TChar- | Internal_TUChar- | Internal_TShort- | Internal_TUShort- | Internal_TInt- | Internal_TUInt- | Internal_TLong- | Internal_TULong- | Internal_TLLong- | Internal_TULLong- | Internal_TFloat- | Internal_TDouble- | Internal_TInt8- | Internal_TInt16- | Internal_TInt32- | Internal_TInt64- | Internal_TWord8- | Internal_TWord16- | Internal_TWord32- | Internal_TWord64- | Internal_TPtrdiff- | Internal_TSize- | Internal_TSSize- | Internal_TEnum CppEnum- | Internal_TBitspace Bitspace- | Internal_TPtr Type- | Internal_TRef Type- | Internal_TFn [Type] Type- | Internal_TCallback Callback- | Internal_TObj Class- | Internal_TObjToHeap Class- | Internal_TToGc Type- | Internal_TConst Type- deriving (Eq, Show)---- | Canonicalizes a 'Type' without changing its meaning. Multiple nested--- 'Internal_TConst's are collapsed into a single one.-normalizeType :: Type -> Type-normalizeType t = case t of- Internal_TVoid -> t- Internal_TBool -> t- Internal_TChar -> t- Internal_TUChar -> t- Internal_TShort -> t- Internal_TUShort -> t- Internal_TInt -> t- Internal_TUInt -> t- Internal_TLong -> t- Internal_TULong -> t- Internal_TLLong -> t- Internal_TULLong -> t- Internal_TFloat -> t- Internal_TDouble -> t- Internal_TInt8 -> t- Internal_TInt16 -> t- Internal_TInt32 -> t- Internal_TInt64 -> t- Internal_TWord8 -> t- Internal_TWord16 -> t- Internal_TWord32 -> t- Internal_TWord64 -> t- Internal_TPtrdiff -> t- Internal_TSize -> t- Internal_TSSize -> t- Internal_TEnum _ -> t- Internal_TBitspace _ -> t- Internal_TPtr t' -> Internal_TPtr $ normalizeType t'- Internal_TRef t' -> Internal_TRef $ normalizeType t'- Internal_TFn paramTypes retType ->- Internal_TFn (map normalizeType paramTypes) $ normalizeType retType- Internal_TCallback _ -> t- Internal_TObj _ -> t- Internal_TObjToHeap _ -> t- Internal_TToGc _ -> t- Internal_TConst (Internal_TConst t') -> normalizeType $ Internal_TConst t'- Internal_TConst _ -> t---- | Strips leading 'Internal_TConst's off of a type.-stripConst :: Type -> Type-stripConst t = case t of- Internal_TConst t' -> stripConst t'- _ -> t---- | A C++ variable.------ Use this data type's 'HasReqs' instance to make the variable accessible.-data Variable = Variable- { varIdentifier :: Identifier- -- ^ The identifier used to refer to the variable.- , varExtName :: ExtName- -- ^ The variable's external name.- , varType :: Type- -- ^ The variable's type. This may be- -- 'Foreign.Hoppy.Generator.Types.constT' to indicate that the variable is- -- read-only.- , varReqs :: Reqs- -- ^ Requirements for bindings to access this variable.- , varAddendum :: Addendum- -- ^ The variable's addendum.- }--instance Eq Variable where- (==) = (==) `on` varExtName--instance Show Variable where- show v = concat ["<Variable ", show (varExtName v), " ", show (varType v), ">"]--instance HasExtNames Variable where- getPrimaryExtName = varExtName- getNestedExtNames v = [varGetterExtName v, varSetterExtName v]--instance HasReqs Variable where- getReqs = varReqs- setReqs reqs v = v { varReqs = reqs }--instance HasAddendum Variable where- getAddendum = varAddendum- setAddendum addendum v = v { varAddendum = addendum }---- | Creates a binding for a C++ variable.-makeVariable :: Identifier -> Maybe ExtName -> Type -> Variable-makeVariable identifier maybeExtName t =- Variable identifier (extNameOrIdentifier identifier maybeExtName) t mempty mempty---- | Returns whether the variable is constant, i.e. whether its type is--- @'Foreign.Hoppy.Generator.Types.constT' ...@.-varIsConst :: Variable -> Bool-varIsConst v = case varType v of- Internal_TConst _ -> True- _ -> False---- | Returns the external name of the getter function for the variable.-varGetterExtName :: Variable -> ExtName-varGetterExtName = toExtName . (++ "_get") . fromExtName . varExtName---- | Returns the external name of the setter function for the variable.-varSetterExtName :: Variable -> ExtName-varSetterExtName = toExtName . (++ "_set") . fromExtName . varExtName---- | A C++ enum declaration. An enum should actually be enumerable (in the--- sense of Haskell's 'Enum'); if it's not, consider using a 'Bitspace' instead.-data CppEnum = CppEnum- { enumIdentifier :: Identifier- -- ^ The identifier used to refer to the enum.- , enumExtName :: ExtName- -- ^ The enum's external name.- , enumValueNames :: [(Int, [String])]- -- ^ The numeric values and names of the enum values. A single value's name- -- is broken up into words. How the words and ext name get combined to make- -- a name in a particular foreign language depends on the language.- , enumReqs :: Reqs- -- ^ Requirements for bindings to access this enum. Currently unused, but- -- will be in the future.- , enumAddendum :: Addendum- -- ^ The enum's addendum.- , enumValuePrefix :: String- -- ^ The prefix applied to value names ('enumValueNames') when determining- -- the names of values in foreign languages. This defaults to the external- -- name of the enum, plus an underscore.- --- -- See 'enumSetValuePrefix'.- }--instance Eq CppEnum where- (==) = (==) `on` enumExtName--instance Show CppEnum where- show e = concat ["<Enum ", show (enumExtName e), " ", show (enumIdentifier e), ">"]--instance HasExtNames CppEnum where- getPrimaryExtName = enumExtName--instance HasReqs CppEnum where- getReqs = enumReqs- setReqs reqs e = e { enumReqs = reqs }--instance HasAddendum CppEnum where- getAddendum = enumAddendum- setAddendum addendum e = e { enumAddendum = addendum }---- | Creates a binding for a C++ enum.-makeEnum :: Identifier -- ^ 'enumIdentifier'- -> Maybe ExtName- -- ^ An optional external name; will be automatically derived from- -- the identifier if absent.- -> [(Int, [String])] -- ^ 'enumValueNames'- -> CppEnum-makeEnum identifier maybeExtName valueNames =- let extName = extNameOrIdentifier identifier maybeExtName- in CppEnum- identifier- extName- valueNames- mempty- mempty- (fromExtName extName ++ "_")---- | Sets the prefix applied to the names of enum values' identifiers in foreign--- languages.------ See 'enumValuePrefix'.-enumSetValuePrefix :: String -> CppEnum -> CppEnum-enumSetValuePrefix prefix enum = enum { enumValuePrefix = prefix }---- | A C++ numeric space with bitwise operations. This is similar to a--- 'CppEnum', but in addition to the extra operations, this differs in that--- these values aren't enumerable.------ Additionally, as a kludge for Qtah, a bitspace may have a C++ type--- ('bitspaceCppTypeIdentifier') separate from its numeric type--- ('bitspaceType'). Qt bitspaces aren't raw numbers but are instead type-safe--- @QFlags@ objects that don't implicitly convert from integers, so we need a--- means to do so manually. Barring general ad-hoc argument and return value--- conversion support, we allow this as follows: when given a C++ type, then a--- bitspace may also have a conversion function between the numeric and C++--- type, in each direction. If a conversion function is present, it will be--- used for conversions in its respective direction. The C++ type is not a full--- 'Type', but only an 'Identifier', since additional information is not needed.--- See 'bitspaceAddCppType'.-data Bitspace = Bitspace- { bitspaceExtName :: ExtName- -- ^ The bitspace's external name.- , bitspaceType :: Type- -- ^ The C++ type used for bits values. This should be a primitive numeric- -- type, usually 'Foreign.Hoppy.Generator.Types.intT'.- , bitspaceValueNames :: [(Int, [String])]- -- ^ The numeric values and names of the bitspace values. See- -- 'enumValueNames'.- , bitspaceEnum :: Maybe CppEnum- -- ^ An associated enum, whose values may be converted to values in the- -- bitspace.- , bitspaceCppTypeIdentifier :: Maybe Identifier- -- ^ The optional C++ type for a bitspace.- , bitspaceToCppValueFn :: Maybe String- -- ^ The name of a C++ function to convert from 'bitspaceType' to the- -- bitspace's C++ type.- , bitspaceFromCppValueFn :: Maybe String- -- ^ The name of a C++ function to convert from the bitspace's C++ type to- -- 'bitspaceType'.- , bitspaceReqs :: Reqs- -- ^ Requirements for emitting the bindings for a bitspace, i.e. what's- -- necessary to reference 'bitspaceCppTypeIdentifier',- -- 'bitspaceFromCppValueFn', and 'bitspaceToCppValueFn'. 'bitspaceType' can- -- take some numeric types that require includes as well, but you don't need- -- to list these here.- , bitspaceAddendum :: Addendum- -- ^ The bitspace's addendum.- , bitspaceValuePrefix :: String- -- ^ The prefix applied to value names ('bitspaceValueNames') when- -- determining the names of values in foreign languages. This defaults to- -- the external name of the bitspace, plus an underscore.- --- -- See 'bitspaceSetValuePrefix'.- }--instance Eq Bitspace where- (==) = (==) `on` bitspaceExtName--instance Show Bitspace where- show e = concat ["<Bitspace ", show (bitspaceExtName e), " ", show (bitspaceType e), ">"]--instance HasExtNames Bitspace where- getPrimaryExtName = bitspaceExtName--instance HasReqs Bitspace where- getReqs = bitspaceReqs- setReqs reqs b = b { bitspaceReqs = reqs }--instance HasAddendum Bitspace where- getAddendum = bitspaceAddendum- setAddendum addendum bs = bs { bitspaceAddendum = addendum }---- | Creates a binding for a C++ bitspace.-makeBitspace :: ExtName -- ^ 'bitspaceExtName'- -> Type -- ^ 'bitspaceType'- -> [(Int, [String])] -- ^ 'bitspaceValueNames'- -> Bitspace-makeBitspace extName t valueNames =- Bitspace extName t valueNames Nothing Nothing Nothing Nothing mempty mempty- (fromExtName extName ++ "_")---- | Sets the prefix applied to the names of enum values' identifiers in foreign--- languages.------ See 'enumValuePrefix'.-bitspaceSetValuePrefix :: String -> Bitspace -> Bitspace-bitspaceSetValuePrefix prefix bitspace = bitspace { bitspaceValuePrefix = prefix }---- | Associates an enum with the bitspace. See 'bitspaceEnum'.-bitspaceAddEnum :: CppEnum -> Bitspace -> Bitspace-bitspaceAddEnum enum bitspace = case bitspaceEnum bitspace of- Just enum' ->- error $ concat- ["bitspaceAddEnum: Adding ", show enum, " to ", show bitspace,- ", but it already has ", show enum', "."]- Nothing ->- if bitspaceValueNames bitspace /= enumValueNames enum- then error $ concat- ["bitspaceAddEnum: Trying to add ", show enum, " to ", show bitspace,- ", but the values aren't equal.\nBitspace values: ", show $ bitspaceValueNames bitspace,- "\n Enum values: ", show $ enumValueNames enum]- else bitspace { bitspaceEnum = Just enum }---- | @bitspaceAddCppType cppTypeIdentifier toCppValueFn fromCppValueFn@--- associates a C++ type (plus optional conversion functions) with a bitspace.--- At least one conversion should be specified, otherwise adding the C++ type--- will mean nothing. You should also add use requirements to the bitspace for--- all of these arguments; see 'HasReqs'.-bitspaceAddCppType :: Identifier -> Maybe String -> Maybe String -> Bitspace -> Bitspace-bitspaceAddCppType cppTypeId toCppValueFnMaybe fromCppValueFnMaybe b =- case bitspaceCppTypeIdentifier b of- Just cppTypeId' ->- error $ concat- ["bitspaceAddCppType: Adding C++ type ", show cppTypeId,- " to ", show b, ", but it already has ", show cppTypeId', "."]- Nothing ->- b { bitspaceCppTypeIdentifier = Just cppTypeId- , bitspaceToCppValueFn = toCppValueFnMaybe- , bitspaceFromCppValueFn = fromCppValueFnMaybe- }---- | Whether or not a function may cause side-effects.------ Haskell bindings for pure functions will not be in 'IO', and calls to pure--- functions will be executed non-strictly. Calls to impure functions will--- execute in the IO monad.------ Member functions for mutable classes should not be made pure, because it is--- difficult in general to control when the call will be made.-data Purity = Nonpure -- ^ Side-affects are possible.- | Pure -- ^ Side-affects will not happen.- deriving (Eq, Show)---- | A C++ function declaration.------ Use this data type's 'HasReqs' instance to make the function accessible. You--- do not need to add requirements for parameter or return types.-data Function = Function- { fnCName :: FnName Identifier- -- ^ The identifier used to call the function.- , fnExtName :: ExtName- -- ^ The function's external name.- , fnPurity :: Purity- -- ^ Whether the function is pure.- , fnParams :: [Type]- -- ^ The function's parameter types.- , fnReturn :: Type- -- ^ The function's return type.- , fnReqs :: Reqs- -- ^ Requirements for bindings to access this function.- , fnExceptionHandlers :: ExceptionHandlers- -- ^ Exceptions that the function might throw.- , fnAddendum :: Addendum- -- ^ The function's addendum.- }--instance Show Function where- show fn =- concat ["<Function ", show (fnExtName fn), " ", show (fnCName fn),- show (fnParams fn), " ", show (fnReturn fn), ">"]--instance HasExtNames Function where- getPrimaryExtName = fnExtName--instance HasReqs Function where- getReqs = fnReqs- setReqs reqs fn = fn { fnReqs = reqs }--instance HandlesExceptions Function where- getExceptionHandlers = fnExceptionHandlers- modifyExceptionHandlers f fn = fn { fnExceptionHandlers = f $ fnExceptionHandlers fn }--instance HasAddendum Function where- getAddendum = fnAddendum- setAddendum addendum fn = fn { fnAddendum = addendum }---- | Creates a binding for a C++ function.-makeFn :: IsFnName Identifier name- => name- -> Maybe ExtName- -- ^ An optional external name; will be automatically derived from- -- the identifier if absent.- -> Purity- -> [Type] -- ^ Parameter types.- -> Type -- ^ Return type.- -> Function-makeFn cName maybeExtName purity paramTypes retType =- let fnName = toFnName cName- in Function fnName- (extNameOrFnIdentifier fnName maybeExtName)- purity paramTypes retType mempty mempty mempty---- | 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- { classIdentifier :: Identifier- -- ^ The identifier used to refer to the class.- , classExtName :: ExtName- -- ^ The class's external name.- , 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 ["<Class ", show (classExtName cls), " ", show (classIdentifier cls), ">"]--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 :: (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 (Haskell.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 (Haskell.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 (Haskell.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- { classVarCName :: String- -- ^ The variable's C++ name.- , classVarExtName :: ExtName- -- ^ The variable's external 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 ["<ClassVariable ",- show $ classVarCName v, " ",- show $ classVarExtName v, " ",- show $ classVarStatic v, " ",- show $ classVarType v, ">"]--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 cName $ extNameOrString cName maybeExtName---- | 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 :: [Type]- -- ^ The constructor's parameter types.- , ctorExceptionHandlers :: ExceptionHandlers- -- ^ Exceptions that the constructor may throw.- }--instance Show Ctor where- show ctor = concat ["<Ctor ", show (ctorExtName ctor), " ", show (ctorParams ctor), ">"]--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 :: ExtName- -> [Type] -- ^ Parameter types.- -> ClassEntity-makeCtor = (CECtor .) . makeCtor_---- | The unwrapped version of 'makeCtor'.-makeCtor_ :: ExtName -> [Type] -> Ctor-makeCtor_ extName paramTypes = Ctor extName paramTypes 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 :: String- -> [Type] -- ^ Parameter types.- -> ClassEntity-mkCtor = (CECtor .) . mkCtor_---- | The unwrapped version of 'mkCtor'.-mkCtor_ :: String -> [Type] -> Ctor-mkCtor_ = makeCtor_ . toExtName---- | Searches a class for a copy constructor, returning it if found.-classFindCopyCtor :: Class -> Maybe Ctor-classFindCopyCtor cls = case mapMaybe check $ classEntities cls of- [ctor] -> Just ctor- _ -> Nothing- where check entity = case entity of- CECtor ctor ->- let params = map (stripConst . normalizeType) (ctorParams ctor)- in if params == [Internal_TObj cls] ||- params == [Internal_TRef $ Internal_TConst $ Internal_TObj cls]- then Just ctor- else Nothing- _ -> 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 :: [Type]- -- ^ The method's parameter types.- , methodReturn :: Type- -- ^ The method's return type.- , methodExceptionHandlers :: ExceptionHandlers- -- ^ Exceptions that the method might throw.- }--instance Show Method where- show method =- concat ["<Method ", show (methodExtName method), " ",- case methodImpl method of- RealMethod name -> 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 '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 const.-data Constness = Nonconst | Const- deriving (Bounded, Enum, Eq, Show)---- | Returns the opposite constness value.-constNegate :: Constness -> Constness-constNegate Nonconst = Const-constNegate Const = Nonconst---- | 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- => name -- ^ The C++ name of the method.- -> ExtName -- ^ The external name of the method.- -> MethodApplicability- -> Purity- -> [Type] -- ^ Parameter types.- -> Type -- ^ Return type.- -> ClassEntity-makeMethod = (((((CEMethod .) .) .) .) .) . makeMethod_---- | The unwrapped version of 'makeMethod'.-makeMethod_ :: IsFnName String name- => name- -> ExtName- -> MethodApplicability- -> Purity- -> [Type]- -> Type- -> Method-makeMethod_ cName extName appl purity paramTypes retType =- Method (RealMethod $ toFnName cName) extName appl purity paramTypes retType mempty---- | Creates a 'Method' that is in fact backed by a C++ non-member function (a--- la '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- => name- -> String- -> MethodApplicability- -> Purity- -> [Type]- -> Type- -> ClassEntity-makeFnMethod = (((((CEMethod .) .) .) .) .) . makeFnMethod_---- | The unwrapped version of 'makeFnMethod'.-makeFnMethod_ :: IsFnName Identifier name- => name- -> String- -> MethodApplicability- -> Purity- -> [Type]- -> Type- -> Method-makeFnMethod_ cName foreignName appl purity paramTypes retType =- Method (FnMethod $ toFnName cName) (toExtName foreignName)- appl purity 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- => name -- ^ The C++ name of the method.- -> MethodApplicability- -> Purity- -> [Type] -- ^ 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- => name -- ^ The C++ name of the method.- -> String -- ^ A foreign name for the method.- -> MethodApplicability- -> Purity- -> [Type] -- ^ Parameter types.- -> Type -- ^ Return type.- -> Method-makeMethod'' name foreignName = makeMethod''' (toFnName name) $ Just foreignName---- | The implementation of 'makeMethod'' and 'makeMethod'''.-makeMethod''' :: FnName String -- ^ The C++ name of the method.- -> Maybe String -- ^ A foreign name for the method.- -> MethodApplicability- -> Purity- -> [Type] -- ^ 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 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- => name -- ^ The C++ name of the method.- -> [Type] -- ^ Parameter types.- -> Type -- ^ Return type.- -> ClassEntity-mkMethod = ((CEMethod .) .) . mkMethod_---- | The unwrapped version of 'mkMethod'.-mkMethod_ :: IsFnName String name- => name- -> [Type]- -> 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- => name -- ^ The C++ name of the method.- -> String -- ^ A foreign name for the method.- -> [Type] -- ^ Parameter types.- -> Type -- ^ Return type.- -> ClassEntity-mkMethod' = (((CEMethod .) .) .) . mkMethod'_---- | The unwrapped version of 'mkMethod''.-mkMethod'_ :: IsFnName String name- => name- -> String- -> [Type]- -> 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 => name -> [Type] -> Type -> ClassEntity-mkConstMethod = ((CEMethod .) .) . mkConstMethod_---- | The unwrapped version of 'mkConstMethod'.-mkConstMethod_ :: IsFnName String name => name -> [Type] -> 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 => name -> String -> [Type] -> Type -> ClassEntity-mkConstMethod' = (((CEMethod .) .) .) . mkConstMethod'_---- | The unwrapped version of 'mkConstMethod''.-mkConstMethod'_ :: IsFnName String name => name -> String -> [Type] -> 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 => name -> [Type] -> Type -> ClassEntity-mkStaticMethod = ((CEMethod .) .) . mkStaticMethod_---- | The unwrapped version of 'mkStaticMethod'.-mkStaticMethod_ :: IsFnName String name => name -> [Type] -> 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 => name -> String -> [Type] -> Type -> ClassEntity-mkStaticMethod' = (((CEMethod .) .) .) . mkStaticMethod'_---- | The unwrapped version of 'mkStaticMethod''.-mkStaticMethod'_ :: IsFnName String name => name -> String -> [Type] -> 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 [] 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 [] 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 [] Internal_TBool- , mkMethod_ setName [Internal_TBool] Internal_TVoid- ]---- | 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 [] Internal_TBool- , mkMethod_ setName [Internal_TBool] Internal_TVoid- ]---- | A non-C++ function that can be invoked via a C++ functor or function--- pointer.------ Use this data type's 'HasReqs' instance to add extra requirements, however--- manually adding requirements for parameter and return types is not necessary.-data Callback = Callback- { callbackExtName :: ExtName- -- ^ The callback's external name.- , callbackParams :: [Type]- -- ^ The callback's parameter types.- , callbackReturn :: Type- -- ^ The callback's return type.- , callbackThrows :: Maybe Bool- -- ^ Whether the callback supports throwing C++ exceptions from Haskell into- -- C++ during its execution. When absent, the value is inherited from- -- 'moduleCallbacksThrow' and 'interfaceCallbacksThrow'.- , callbackReqs :: Reqs- -- ^ Extra requirements for the callback.- , callbackAddendum :: Addendum- -- ^ The callback's addendum.- }--instance Eq Callback where- (==) = (==) `on` callbackExtName--instance Show Callback where- show cb =- concat ["<Callback ", show (callbackExtName cb), " ", show (callbackParams cb), " ",- show (callbackReturn cb)]--instance HasExtNames Callback where- getPrimaryExtName = callbackExtName--instance HasReqs Callback where- getReqs = callbackReqs- setReqs reqs cb = cb { callbackReqs = reqs }--instance HasAddendum Callback where- getAddendum = callbackAddendum- setAddendum addendum cb = cb { callbackAddendum = addendum }---- | Creates a binding for constructing callbacks into foreign code.-makeCallback :: ExtName- -> [Type] -- ^ Parameter types.- -> Type -- ^ Return type.- -> Callback-makeCallback extName paramTypes retType =- Callback extName paramTypes retType Nothing mempty mempty---- | Sets whether a callback supports handling thrown C++ exceptions and passing--- them into C++.-callbackSetThrows :: Bool -> Callback -> Callback-callbackSetThrows value cb = cb { callbackThrows = Just value }---- | Each exception class has a unique exception ID.-newtype ExceptionId = ExceptionId- { getExceptionId :: Int -- ^ Internal.- } deriving (Eq, Show)---- | The exception ID that represents the catch-all type.-exceptionCatchAllId :: ExceptionId-exceptionCatchAllId = ExceptionId 1---- | The lowest exception ID to be used for classes.-exceptionFirstFreeId :: Int-exceptionFirstFreeId = getExceptionId exceptionCatchAllId + 1---- | Indicates the ability to handle a certain type of C++ exception.-data ExceptionHandler =- CatchClass Class- -- ^ Indicates that instances of the given class are handled (including- -- derived types).- | CatchAll- -- ^ Indicates that all C++ exceptions are handled, i.e. @catch (...)@.- deriving (Eq, Ord)---- | Represents a list of exception handlers to be used for a body of code.--- Order is important; a 'CatchAll' will prevent all subsequent handlers from--- being invoked.-data ExceptionHandlers = ExceptionHandlers- { exceptionHandlersList :: [ExceptionHandler]- -- ^ Extracts the list of exception handlers.- }--instance Sem.Semigroup ExceptionHandlers where- (<>) e1 e2 =- ExceptionHandlers $ exceptionHandlersList e1 ++ exceptionHandlersList e2--instance Monoid ExceptionHandlers where- mempty = ExceptionHandlers []-- mappend = (<>)---- | Types that can handle exceptions.-class HandlesExceptions a where- -- | Extracts the exception handlers for an object.- getExceptionHandlers :: a -> ExceptionHandlers-- -- | Modifies an object's exception handlers with a given function.- modifyExceptionHandlers :: (ExceptionHandlers -> ExceptionHandlers) -> a -> a---- | Appends additional exception handlers to an object.-handleExceptions :: HandlesExceptions a => [ExceptionHandler] -> a -> a-handleExceptions classes =- modifyExceptionHandlers $ mappend mempty { exceptionHandlersList = classes }---- | A literal piece of code that will be inserted into a generated source file--- after the regular binding glue. The 'Monoid' instance concatenates code--- (actions).-data Addendum = Addendum- { addendumHaskell :: Haskell.Generator ()- -- ^ Code to be output into the Haskell binding. May also add imports and- -- exports.- }--instance Sem.Semigroup Addendum where- (<>) (Addendum a) (Addendum b) = Addendum $ a >> b--instance Monoid Addendum where- mempty = Addendum $ return ()- mappend = (<>)---- | A typeclass for types that have an addendum.-class HasAddendum a where- {-# MINIMAL getAddendum, (setAddendum | modifyAddendum) #-}-- -- | Returns an object's addendum.- getAddendum :: a -> Addendum-- -- | Replaces and object's addendum with another.- setAddendum :: Addendum -> a -> a- setAddendum addendum = modifyAddendum $ const addendum-- -- | Modified an object's addendum.- modifyAddendum :: (Addendum -> Addendum) -> a -> a- modifyAddendum f x = setAddendum (f $ getAddendum x) x---- | Adds a Haskell addendum to an object.-addAddendumHaskell :: HasAddendum a => Haskell.Generator () -> a -> a-addAddendumHaskell gen = modifyAddendum $ \addendum ->- addendum `mappend` mempty { addendumHaskell = gen }---- | A collection of imports for a Haskell module. This is a monoid: import--- Statements are merged to give the union of imported bindings.------ This structure supports two specific types of imports:--- - @import Foo (...)@--- - @import qualified Foo as Bar@--- Imports with @as@ but without @qualified@, and @qualified@ imports with a--- spec list, are not supported. This satisfies the needs of the code--- generator, and keeps the merging logic simple.-newtype HsImportSet = HsImportSet- { getHsImportSet :: M.Map HsImportKey HsImportSpecs- -- ^ Returns the import set's internal map from module names to imported- -- bindings.- } deriving (Show)--instance Sem.Semigroup HsImportSet where- (<>) (HsImportSet m) (HsImportSet m') =- HsImportSet $ M.unionWith mergeImportSpecs m m'--instance Monoid HsImportSet where- mempty = HsImportSet M.empty-- mappend = (<>)-- mconcat sets =- HsImportSet $ M.unionsWith mergeImportSpecs $ map getHsImportSet sets---- | Constructor for an import set.-makeHsImportSet :: M.Map HsImportKey HsImportSpecs -> HsImportSet-makeHsImportSet = HsImportSet---- | Sets all of the import specifications in an import set to be--- @{-#SOURCE#-}@ imports.-hsImportSetMakeSource :: HsImportSet -> HsImportSet-hsImportSetMakeSource (HsImportSet m) =- HsImportSet $ M.map (\specs -> specs { hsImportSource = True }) m---- | A Haskell module name.-type HsModuleName = String---- | References an occurrence of an import statement, under which bindings can--- be imported. Only imported specs under equal 'HsImportKey's may be merged.-data HsImportKey = HsImportKey- { hsImportModule :: HsModuleName- , hsImportQualifiedName :: Maybe HsModuleName- } deriving (Eq, Ord, Show)---- | A specification of bindings to import from a module. If 'Nothing', then--- the entire module is imported. If @'Just' 'M.empty'@, then only instances--- are imported.-data HsImportSpecs = HsImportSpecs- { getHsImportSpecs :: Maybe (M.Map HsImportName HsImportVal)- , hsImportSource :: Bool- } deriving (Show)---- | Combines two 'HsImportSpecs's into one that imports everything that the two--- did separately.-mergeImportSpecs :: HsImportSpecs -> HsImportSpecs -> HsImportSpecs-mergeImportSpecs (HsImportSpecs mm s) (HsImportSpecs mm' s') =- HsImportSpecs (liftM2 mergeMaps mm mm') (s || s')- where mergeMaps = M.unionWith mergeValues- mergeValues v v' = case (v, v') of- (HsImportValAll, _) -> HsImportValAll- (_, HsImportValAll) -> HsImportValAll- (HsImportValSome s, HsImportValSome s') -> HsImportValSome $ s ++ s'- (x@(HsImportValSome _), _) -> x- (_, x@(HsImportValSome _)) -> x- (HsImportVal, HsImportVal) -> HsImportVal---- | An identifier that can be imported from a module. Symbols may be used here--- when surrounded by parentheses. Examples are @\"fmap\"@ and @\"(++)\"@.-type HsImportName = String---- | Specifies how a name is imported.-data HsImportVal =- HsImportVal- -- ^ The name is imported, and nothing underneath it is.- | HsImportValSome [HsImportName]- -- ^ The name is imported, as are specific names underneath it. This is a- -- @X (a, b, c)@ import.- | HsImportValAll- -- ^ The name is imported, along with all names underneath it. This is a @X- -- (..)@ import.- deriving (Show)---- | An import for the entire contents of a Haskell module.-hsWholeModuleImport :: HsModuleName -> HsImportSet-hsWholeModuleImport moduleName =- HsImportSet $ M.singleton (HsImportKey moduleName Nothing) $- HsImportSpecs Nothing False---- | A qualified import of a Haskell module.-hsQualifiedImport :: HsModuleName -> HsModuleName -> HsImportSet-hsQualifiedImport moduleName qualifiedName =- HsImportSet $ M.singleton (HsImportKey moduleName $ Just qualifiedName) $- HsImportSpecs Nothing False---- | An import of a single name from a Haskell module.-hsImport1 :: HsModuleName -> HsImportName -> HsImportSet-hsImport1 moduleName valueName = hsImport1' moduleName valueName HsImportVal---- | A detailed import of a single name from a Haskell module.-hsImport1' :: HsModuleName -> HsImportName -> HsImportVal -> HsImportSet-hsImport1' moduleName valueName valueType =- HsImportSet $ M.singleton (HsImportKey moduleName Nothing) $- HsImportSpecs (Just $ M.singleton valueName valueType) False---- | An import of multiple names from a Haskell module.-hsImports :: HsModuleName -> [HsImportName] -> HsImportSet-hsImports moduleName names =- hsImports' moduleName $ map (\name -> (name, HsImportVal)) names---- | A detailed import of multiple names from a Haskell module.-hsImports' :: HsModuleName -> [(HsImportName, HsImportVal)] -> HsImportSet-hsImports' moduleName values =- HsImportSet $ M.singleton (HsImportKey moduleName Nothing) $- HsImportSpecs (Just $ M.fromList values) False---- | Imports "Data.Bits" qualified as @HoppyDB@.-hsImportForBits :: HsImportSet-hsImportForBits = hsQualifiedImport "Data.Bits" "HoppyDB"---- | Imports "Control.Exception" qualified as @HoppyCE@.-hsImportForException :: HsImportSet-hsImportForException = hsQualifiedImport "Control.Exception" "HoppyCE"---- | Imports "Data.Int" qualified as @HoppyDI@.-hsImportForInt :: HsImportSet-hsImportForInt = hsQualifiedImport "Data.Int" "HoppyDI"---- | Imports "Data.Word" qualified as @HoppyDW@.-hsImportForWord :: HsImportSet-hsImportForWord = hsQualifiedImport "Data.Word" "HoppyDW"---- | Imports "Foreign" qualified as @HoppyF@.-hsImportForForeign :: HsImportSet-hsImportForForeign = hsQualifiedImport "Foreign" "HoppyF"---- | Imports "Foreign.C" qualified as @HoppyFC@.-hsImportForForeignC :: HsImportSet-hsImportForForeignC = hsQualifiedImport "Foreign.C" "HoppyFC"---- | Imports "Data.Map" qualified as @HoppyDM@.-hsImportForMap :: HsImportSet-hsImportForMap = hsQualifiedImport "Data.Map" "HoppyDM"---- | Imports "Prelude" qualified as @HoppyP@.-hsImportForPrelude :: HsImportSet-hsImportForPrelude = hsQualifiedImport "Prelude" "HoppyP"---- | Imports "Foreign.Hoppy.Runtime" qualified as @HoppyFHR@.-hsImportForRuntime :: HsImportSet-hsImportForRuntime = hsQualifiedImport "Foreign.Hoppy.Runtime" "HoppyFHR"---- | Imports "System.Posix.Types" qualified as @HoppySPT@.-hsImportForSystemPosixTypes :: HsImportSet-hsImportForSystemPosixTypes = hsQualifiedImport "System.Posix.Types" "HoppySPT"---- | Imports "Data.Typeable" qualified as @HoppyDT@.-hsImportForTypeable :: HsImportSet-hsImportForTypeable = hsQualifiedImport "Data.Typeable" "HoppyDT"+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net>+--+-- 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 <http://www.gnu.org/licenses/>.++{-# LANGUAGE CPP, GeneralizedNewtypeDeriving #-}++module Foreign.Hoppy.Generator.Spec.Base (+ ErrorMsg,+ -- * Interfaces+ Interface,+ InterfaceOptions (..),+ defaultInterfaceOptions,+ interface,+ interface',+ interfaceName,+ interfaceModules,+ interfaceNamesToModules,+ interfaceHaskellModuleBase,+ interfaceDefaultHaskellModuleBase,+ interfaceAddHaskellModuleBase,+ interfaceHaskellModuleImportNames,+ interfaceExceptionHandlers,+ interfaceCallbacksThrow,+ interfaceSetCallbacksThrow,+ interfaceExceptionClassId,+ interfaceExceptionSupportModule,+ interfaceSetExceptionSupportModule,+ interfaceSetSharedPtr,+ interfaceCompiler,+ interfaceSetCompiler,+ interfaceSetCompiler',+ interfaceSetNoCompiler,+ interfaceValidateEnumTypes,+ interfaceSetValidateEnumTypes,+ interfaceHooks,+ interfaceModifyHooks,+ -- * C++ includes+ Include,+ includeStd,+ includeLocal,+ includeToString,+ -- * Modules+ Module,+ moduleName,+ moduleHppPath,+ moduleCppPath,+ moduleExports,+ moduleReqs,+ moduleExceptionHandlers,+ moduleCallbacksThrow,+ moduleSetCallbacksThrow,+ moduleAddendum,+ moduleHaskellName,+ makeModule,+ moduleModify,+ moduleModify',+ moduleSetHppPath,+ moduleSetCppPath,+ moduleAddExports,+ moduleAddHaskellName,+ -- * Requirements+ Reqs,+ reqsIncludes,+ reqInclude,+ HasReqs (..),+ addReqs,+ addReqIncludes,+ -- * Names+ ExtName,+ toExtName,+ extNameOrIdentifier,+ extNameOrFnIdentifier,+ extNameOrString,+ isValidExtName,+ fromExtName,+ HasExtNames (..),+ getAllExtNames,+ FnName (..),+ IsFnName (..),+ Operator (..),+ OperatorType (..),+ operatorPreferredExtName,+ operatorPreferredExtName',+ operatorType,+ Identifier,+ makeIdentifier,+ identifierParts,+ IdPart,+ makeIdPart,+ idPartBase,+ idPartArgs,+ ident, ident', ident1, ident2, ident3, ident4, ident5,+ identT, identT', ident1T, ident2T, ident3T, ident4T, ident5T,+ -- * Exports+ Exportable (..),+ Export (..),+ -- * Basic types+ Type (..),+ normalizeType,+ stripConst,+ -- * Functions and parameters+ Constness (..), constNegate,+ Purity (..),+ Parameter, parameterType, parameterName,+ IsParameter (..), toParameters,+ np, (~:),+ -- * Conversions+ ConversionMethod (..),+ ConversionSpec (conversionSpecName, conversionSpecCpp, conversionSpecHaskell),+ makeConversionSpec,+ ConversionSpecCpp (+ ConversionSpecCpp,+ conversionSpecCppName,+ conversionSpecCppReqs,+ conversionSpecCppConversionType,+ conversionSpecCppConversionToCppExpr,+ conversionSpecCppConversionFromCppExpr+ ),+ makeConversionSpecCpp,+ ConversionSpecHaskell (+ ConversionSpecHaskell,+ conversionSpecHaskellHsType,+ conversionSpecHaskellHsArgType,+ conversionSpecHaskellCType,+ conversionSpecHaskellToCppFn,+ conversionSpecHaskellFromCppFn+ ),+ makeConversionSpecHaskell,+ -- * Exceptions+ ExceptionId (..),+ exceptionCatchAllId,+ ExceptionHandler (..),+ ExceptionHandlers (..),+ HandlesExceptions (..),+ handleExceptions,+ -- * Addenda+ Addendum (..),+ HasAddendum (..),+ addAddendumHaskell,+ -- * Enum support+ EnumInfo (..),+ EnumEntryWords,+ EnumValueMap (..),+ EnumValue (..),+ -- * Languages+ ForeignLanguage (..),+ WithForeignLanguageOverrides,+ MapWithForeignLanguageOverrides,+ -- * Haskell imports+ HsModuleName, HsImportSet, HsImportKey (..), HsImportSpecs (..), HsImportName, HsImportVal (..),+ hsWholeModuleImport, hsQualifiedImport, hsImport1, hsImport1', hsImports, hsImports',+ hsImportSetMakeSource,+ -- * Internal to Hoppy+ EvaluatedEnumData (..),+ EvaluatedEnumValueMap,+ interfaceAllExceptionClasses,+ interfaceSharedPtr,+ interfaceEvaluatedEnumData,+ interfaceGetEvaluatedEnumData,+ -- ** Haskell imports+ makeHsImportSet,+ getHsImportSet,+ hsImportForBits,+ hsImportForException,+ hsImportForInt,+ hsImportForWord,+ hsImportForForeign,+ hsImportForForeignC,+ hsImportForMap,+ hsImportForPrelude,+ hsImportForRuntime,+ hsImportForSystemPosixTypes,+ hsImportForUnsafeIO,+ -- ** Error messages+ objToHeapTWrongDirectionErrorMsg,+ tToGcInvalidFormErrorMessage,+ toGcTWrongDirectionErrorMsg,+ ) where++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative ((<$>), (<*>))+#endif+import Control.Arrow ((&&&))+import Control.Monad (liftM2, unless)+#if MIN_VERSION_mtl(2,2,1)+import Control.Monad.Except (MonadError, throwError)+#else+import Control.Monad.Error (MonadError, throwError)+#endif+import Control.Monad.State (MonadState, StateT, execStateT, get, modify, put)+import Data.Char (isAlpha, isAlphaNum)+import Data.Function (on)+import Data.List (intercalate, intersperse)+import qualified Data.Map as M+import Data.Maybe (catMaybes, fromMaybe)+#if !MIN_VERSION_base(4,8,0)+import Data.Monoid (Monoid, mappend, mconcat, mempty)+#endif+import Data.Semigroup as Sem+import qualified Data.Set as S+import Data.Typeable (Typeable, cast)+import Foreign.Hoppy.Generator.Common+import Foreign.Hoppy.Generator.Compiler (Compiler, SomeCompiler (SomeCompiler), defaultCompiler)+import {-# SOURCE #-} Foreign.Hoppy.Generator.Hook (Hooks, defaultHooks)+import {-# SOURCE #-} qualified Foreign.Hoppy.Generator.Language.Cpp as LC+import {-# SOURCE #-} qualified Foreign.Hoppy.Generator.Language.Haskell as LH+import Foreign.Hoppy.Generator.Override (MapWithOverrides, WithOverrides)+import {-# SOURCE #-} Foreign.Hoppy.Generator.Spec.Class (Class, classExtName)+import GHC.Stack (HasCallStack)+import Language.Haskell.Syntax (HsName, HsQualType, HsType)++-- | Indicates strings that are error messages.+type ErrorMsg = String++-- | A complete specification of a C++ API. Generators for different languages,+-- including the binding generator for C++, use these to produce their output.+--+-- 'Interface' does not have a 'HandlesExceptions' instance because+-- 'modifyExceptionHandlers' does not work for it (handled exceptions cannot be+-- modified after an 'Interface' is constructed).+data Interface = Interface+ { interfaceName :: String+ -- ^ The textual name of the interface.+ , interfaceModules :: M.Map String Module+ -- ^ All of the individual modules, by 'moduleName'.+ , interfaceNamesToModules :: M.Map ExtName Module+ -- ^ Maps each 'ExtName' exported by some module to the module that exports+ -- the name.+ , interfaceHaskellModuleBase' :: Maybe [String]+ -- ^ See 'interfaceHaskellModuleBase'.+ , interfaceHaskellModuleImportNames :: M.Map Module String+ -- ^ Short qualified module import names that generated modules use to refer+ -- to each other tersely.+ , interfaceExceptionHandlers :: ExceptionHandlers+ -- ^ Exceptions that all functions in the interface may throw.+ , interfaceCallbacksThrow :: Bool+ -- ^ Whether callbacks within the interface support throwing C++ exceptions+ -- from Haskell into C++ during their execution. This may be overridden by+ -- 'moduleCallbacksThrow' and+ -- 'Foreign.Hoppy.Generator.Spec.Callback.callbackThrows'.+ , interfaceExceptionNamesToIds :: M.Map ExtName ExceptionId+ -- ^ Maps from external names of exception classes to their exception IDs.+ , interfaceExceptionSupportModule :: Maybe Module+ -- ^ When an interface uses C++ exceptions, then one module needs to+ -- manually be selected to contain some interface-specific runtime support.+ -- This is the selected module.+ , interfaceSharedPtr :: (Reqs, String)+ -- ^ The name of the @shared_ptr@ class to use, and the requirements to use+ -- it. This defaults to using @std::shared_ptr@ from @\<memory\>@, but can+ -- be changed if necessary via 'interfaceSetSharedPtr'.+ , interfaceCompiler :: Maybe SomeCompiler+ -- ^ The compiler to use when building code for the interface. This can be+ -- overridden or disabled. This defaults to 'defaultCompiler'.+ , interfaceHooks :: Hooks+ -- ^ Hooks allowing the interface to execute code at various points during+ -- the code generator's execution. This defaults to 'defaultHooks'.+ , interfaceEvaluatedEnumData :: Maybe (M.Map ExtName EvaluatedEnumData)+ -- ^ Evaluated numeric types and values for all enums in the interface.+ , interfaceValidateEnumTypes :: Bool+ -- ^ Whether to validate manually-provided enum numeric types+ -- ('Foreign.Hoppy.Generator.Spec.Enum.enumNumericType') using a compiled+ -- C++ @sizeof()@, as is done for enums that don't have an @enumNumericType@+ -- set.+ --+ -- This defaults to true, but can be set to false to discourage requiring a+ -- compiler. See 'interfaceSetNoCompiler'.+ }++instance Show Interface where+ show iface = concat ["<Interface ", show (interfaceName iface), ">"]++instance HasExports Interface where+ lookupExport name iface =+ lookupExport name =<< M.lookup name (interfaceNamesToModules iface)++-- | Optional parameters when constructing an 'Interface' with 'interface'.+newtype InterfaceOptions = InterfaceOptions+ { interfaceOptionsExceptionHandlers :: ExceptionHandlers+ }++-- | Options used by 'interface'. This contains no exception handlers.+defaultInterfaceOptions :: InterfaceOptions+defaultInterfaceOptions = InterfaceOptions mempty++-- | Constructs an 'Interface' from the required parts. Some validation is+-- performed; if the resulting interface would be invalid, an error message is+-- returned instead.+--+-- This function passes 'defaultInterfaceOptions' to 'interface''.+interface :: String -- ^ 'interfaceName'+ -> [Module] -- ^ 'interfaceModules'+ -> Either ErrorMsg Interface+interface ifName modules = interface' ifName modules defaultInterfaceOptions++-- | Same as 'interface', but accepts some optional arguments.+interface' :: String -- ^ 'interfaceName'+ -> [Module] -- ^ 'interfaceModules'+ -> InterfaceOptions+ -> Either ErrorMsg Interface+interface' ifName modules options = do+ -- TODO Check for duplicate module names.+ -- TODO Check for duplicate module file paths.++ -- Check for multiple modules exporting an ExtName.+ let extNamesToModules :: M.Map ExtName [Module]+ extNamesToModules =+ M.unionsWith (++) $+ for modules $ \m ->+ let extNames = concatMap getAllExtNames $ M.elems $ moduleExports m+ in M.fromList $ zip extNames $ repeat [m]++ extNamesInMultipleModules :: [(ExtName, [Module])]+ extNamesInMultipleModules =+ M.toList $+ M.filter (\case+ _:_:_ -> True+ _ -> False)+ extNamesToModules++ unless (null extNamesInMultipleModules) $+ Left $ unlines $+ "Some external name(s) are exported by multiple modules:" :+ map (\(extName, modules') ->+ concat $ "- " : show extName : ": " : intersperse ", " (map show modules'))+ extNamesInMultipleModules++ let haskellModuleImportNames =+ M.fromList $+ (\a b f -> zipWith f a b) modules [(1::Int)..] $+ \m index -> (m, 'M' : show index)++ -- Generate a unique exception ID integer for each exception class. IDs 0 and+ -- 1 are reserved.+ let exceptionNamesToIds =+ M.fromList $+ zip (map classExtName $ interfaceAllExceptionClasses' modules)+ (map ExceptionId [exceptionFirstFreeId..])++ return Interface+ { interfaceName = ifName+ , interfaceModules = M.fromList $ map (moduleName &&& id) modules+ , interfaceNamesToModules = M.map (\[x] -> x) extNamesToModules+ , interfaceHaskellModuleBase' = Nothing+ , interfaceHaskellModuleImportNames = haskellModuleImportNames+ , interfaceExceptionHandlers = interfaceOptionsExceptionHandlers options+ , interfaceCallbacksThrow = False+ , interfaceExceptionNamesToIds = exceptionNamesToIds+ , interfaceExceptionSupportModule = Nothing+ , interfaceSharedPtr = (reqInclude $ includeStd "memory", "std::shared_ptr")+ , interfaceCompiler = Just $ SomeCompiler defaultCompiler+ , interfaceHooks = defaultHooks+ , interfaceEvaluatedEnumData = Nothing+ , interfaceValidateEnumTypes = True+ }++-- | The name of the parent Haskell module under which a Haskell module will be+-- generated for a Hoppy 'Module'. This is a list of Haskell module path+-- components, in other words, @'Data.List.intercalate' "."@ on the list+-- produces a Haskell module name. Defaults to+-- 'interfaceDefaultHaskellModuleBase', and may be overridden with+-- 'interfaceAddHaskellModuleBase'.+interfaceHaskellModuleBase :: Interface -> [String]+interfaceHaskellModuleBase =+ fromMaybe interfaceDefaultHaskellModuleBase . interfaceHaskellModuleBase'++-- | The default Haskell module under which Hoppy modules will be generated.+-- This is @Foreign.Hoppy.Generated@, that is:+--+-- > ["Foreign", "Hoppy", "Generated"]+interfaceDefaultHaskellModuleBase :: [String]+interfaceDefaultHaskellModuleBase = ["Foreign", "Hoppy", "Generated"]++-- | Sets an interface to generate all of its modules under the given Haskell+-- module prefix. See 'interfaceHaskellModuleBase'.+interfaceAddHaskellModuleBase :: [String] -> Interface -> Either String Interface+interfaceAddHaskellModuleBase modulePath iface = case interfaceHaskellModuleBase' iface of+ Nothing -> Right iface { interfaceHaskellModuleBase' = Just modulePath }+ Just existingPath ->+ Left $ concat+ [ "addInterfaceHaskellModuleBase: Trying to add Haskell module base "+ , intercalate "." modulePath, " to ", show iface+ , " which already has a module base ", intercalate "." existingPath+ ]++-- | Returns the the exception ID for a class in an interface, if it has one+-- (i.e. if it's been marked as an exception class with+-- 'Foreign.Hoppy.Generator.Spec.Class.classMakeException').+interfaceExceptionClassId :: Interface -> Class -> Maybe ExceptionId+interfaceExceptionClassId iface cls =+ M.lookup (classExtName cls) $ interfaceExceptionNamesToIds iface++-- | Returns all of the exception classes in an interface.+interfaceAllExceptionClasses :: Interface -> [Class]+interfaceAllExceptionClasses = interfaceAllExceptionClasses' . M.elems . interfaceModules++interfaceAllExceptionClasses' :: [Module] -> [Class]+interfaceAllExceptionClasses' modules =+ flip concatMap modules $ \m ->+ catMaybes $+ map getExportExceptionClass $+ M.elems $ moduleExports m++-- | Changes 'Foreign.Hoppy.Generator.Spec.Callback.callbackThrows' for all+-- callbacks in an interface that don't have it set explicitly at the module or+-- callback level.+interfaceSetCallbacksThrow :: Bool -> Interface -> Interface+interfaceSetCallbacksThrow b iface = iface { interfaceCallbacksThrow = b }++-- | Sets an interface's exception support module, for interfaces that use+-- exceptions.+interfaceSetExceptionSupportModule :: HasCallStack => Module -> Interface -> Interface+interfaceSetExceptionSupportModule m iface = case interfaceExceptionSupportModule iface of+ Nothing -> iface { interfaceExceptionSupportModule = Just m }+ Just existingMod ->+ if m == existingMod+ then iface+ else error $ "interfaceSetExceptionSupportModule: " ++ show iface +++ " already has exception support module " ++ show existingMod +++ ", trying to set " ++ show m ++ "."++-- | Installs a custom @std::shared_ptr@ implementation for use by an interface.+-- Hoppy uses shared pointers for generated callback code. This function is+-- useful for building code with compilers that don't provide a conforming+-- @std::shared_ptr@ implementation.+--+-- @interfaceSetSharedPtr ident reqs iface@ modifies @iface@ to use as a+-- @shared_ptr@ class the C++ identifier @ident@, which needs @reqs@ in order to+-- be accessed. @ident@ should be the name of a template to which an arbitrary+-- @\<T\>@ can be appended, for example @"std::shared_ptr"@.+--+-- A @shared_ptr\<T\>@ implementation @foo@ must at least provide the following+-- interface:+--+-- > foo(); // Initialization with a null pointer.+-- > foo(T*); // Initialization with a given pointer.+-- > foo(const foo&); // Copy-construction.+-- > T& operator*() const; // Dereferencing (when non-null).+-- > T* operator->() const; // Dereferencing and invocation (when non-null).+-- > explicit operator bool() const; // Is the target object null?+interfaceSetSharedPtr :: String -> Reqs -> Interface -> Interface+interfaceSetSharedPtr identifier reqs iface =+ iface { interfaceSharedPtr = (reqs, identifier) }++-- | Replaces the default compiler used by the interface.+--+-- @interfaceSetCompiler c = 'interfaceSetCompiler'' ('SomeCompiler' c)@+interfaceSetCompiler :: Compiler a => a -> Interface -> Interface+interfaceSetCompiler = interfaceSetCompiler' . Just . SomeCompiler++-- | Replaces the default compiler used by the interface. When given @Nothing@,+-- the interface will not be allowed to compile any code when it generates+-- bindings.+interfaceSetCompiler' :: Maybe SomeCompiler -> Interface -> Interface+interfaceSetCompiler' compiler iface = iface { interfaceCompiler = compiler }++-- | Sets an interface to never compile C++ code during binding generation.+--+-- This sets the interface to have no compiler, and also asks the interface not+-- to do things that require a compiler, which would otherwise cause a runtime+-- failure: currently just validation of provided enum numeric types+-- (@'interfaceSetValidateEnumTypes' False@).+interfaceSetNoCompiler :: Interface -> Interface+interfaceSetNoCompiler =+ interfaceSetValidateEnumTypes False .+ interfaceSetCompiler' Nothing++-- | Controls whether the interface will validate manually specified enum types+-- ('Foreign.Hoppy.Generator.Spec.Enum.enumNumericType') by compiling a C+++-- program.+--+-- See 'interfaceValidateEnumTypes'.+interfaceSetValidateEnumTypes :: Bool -> Interface -> Interface+interfaceSetValidateEnumTypes validate iface =+ iface { interfaceValidateEnumTypes = validate }++-- | Modifies the hooks associated with an interface.+interfaceModifyHooks :: (Hooks -> Hooks) -> Interface -> Interface+interfaceModifyHooks f iface =+ iface { interfaceHooks = f $ interfaceHooks iface }++-- | Returns the map containing the calculated values for all entries in the+-- enum with the given 'ExtName'. This requires hooks to have been run.+interfaceGetEvaluatedEnumData :: HasCallStack => Interface -> ExtName -> EvaluatedEnumData+interfaceGetEvaluatedEnumData iface extName =+ case interfaceEvaluatedEnumData iface of+ Nothing -> error $ "interfaceGetEvaluatedEnumData: Data have not been " +++ "evaluated for " ++ show iface ++ "."+ Just enumMap -> case M.lookup extName enumMap of+ Nothing -> error $ "interfaceGetEvaluatedEnumData: No data found for " +++ show extName ++ " in " ++ show iface ++ "."+ Just info -> info++-- | An @#include@ directive in a C++ file.+newtype Include = Include+ { includeToString :: String+ -- ^ Returns the complete @#include ...@ line for an include, including+ -- trailing newline.+ } deriving (Eq, Ord, Show)++-- | Creates an @#include \<...\>@ directive.+includeStd :: String -> Include+includeStd path = Include $ "#include <" ++ path ++ ">\n"++-- | Creates an @#include "..."@ directive.+includeLocal :: String -> Include+includeLocal path = Include $ "#include \"" ++ path ++ "\"\n"++-- | A portion of functionality in a C++ API. An 'Interface' is composed of+-- multiple modules. A module will generate a single compilation unit+-- containing bindings for all of the module's exports. The C++ code for a+-- generated module will @#include@ everything necessary for what is written to+-- the header and source files separately. You can declare include dependencies+-- with e.g. 'addReqIncludes', either for individual exports or at the module+-- level (via the @'HasReqs' 'Module'@ instance). Dependencies between modules+-- are handled automatically, and circularity is supported to a certain extent.+-- See the documentation for the individual language modules for further+-- details.+data Module = Module+ { moduleName :: String+ -- ^ The module's name. A module name must identify a unique module within+ -- an 'Interface'.+ , moduleHppPath :: String+ -- ^ A relative path under a C++ sources root to which the generator will+ -- write a header file for the module's C++ bindings.+ , moduleCppPath :: String+ -- ^ A relative path under a C++ sources root to which the generator will+ -- write a source file for the module's C++ bindings.+ , moduleExports :: M.Map ExtName Export+ -- ^ All of the exports in a module.+ , moduleReqs :: Reqs+ -- ^ Module-level requirements.+ , moduleHaskellName :: Maybe [String]+ -- ^ The generated Haskell module name, underneath the+ -- 'interfaceHaskellModuleBase'. If absent (by default), the 'moduleName'+ -- is used. May be modified with 'moduleAddHaskellName'.+ , moduleExceptionHandlers :: ExceptionHandlers+ -- ^ Exceptions that all functions in the module may throw.+ , moduleCallbacksThrow :: Maybe Bool+ -- ^ Whether callbacks exported from the module support exceptions being+ -- thrown during their execution. When present, this overrides+ -- 'interfaceCallbacksThrow'. This maybe overridden by+ -- 'Foreign.Hoppy.Generator.Spec.Callback.callbackThrows'.+ , moduleAddendum :: Addendum+ -- ^ The module's addendum.+ }++instance Eq Module where+ (==) = (==) `on` moduleName++instance Ord Module where+ compare = compare `on` moduleName++instance Show Module where+ show m = concat ["<Module ", moduleName m, ">"]++instance HasExports Module where+ lookupExport name m = M.lookup name $ moduleExports m++instance HasReqs Module where+ getReqs = moduleReqs+ setReqs reqs m = m { moduleReqs = reqs }++instance HasAddendum Module where+ getAddendum = moduleAddendum+ setAddendum addendum m = m { moduleAddendum = addendum }++instance HandlesExceptions Module where+ getExceptionHandlers = moduleExceptionHandlers+ modifyExceptionHandlers f m = m { moduleExceptionHandlers = f $ moduleExceptionHandlers m }++-- | Creates an empty module, ready to be configured with 'moduleModify'.+makeModule :: String -- ^ 'moduleName'+ -> String -- ^ 'moduleHppPath'+ -> String -- ^ 'moduleCppPath'+ -> Module+makeModule name hppPath cppPath = Module+ { moduleName = name+ , moduleHppPath = hppPath+ , moduleCppPath = cppPath+ , moduleExports = M.empty+ , moduleReqs = mempty+ , moduleHaskellName = Nothing+ , moduleExceptionHandlers = mempty+ , moduleCallbacksThrow = Nothing+ , moduleAddendum = mempty+ }++-- | Extends a module. To be used with the module state-monad actions in this+-- package.+moduleModify :: Module -> StateT Module (Either String) () -> Either ErrorMsg Module+moduleModify = flip execStateT++-- | Same as 'moduleModify', but calls 'error' in the case of failure, which is+-- okay in for a generator which would abort in this case anyway.+moduleModify' :: HasCallStack => Module -> StateT Module (Either String) () -> Module+moduleModify' m action = case moduleModify m action of+ Left errorMsg ->+ error $ concat+ ["moduleModify' failed to modify ", show m, ": ", errorMsg]+ Right m' -> m'++-- | Replaces a module's 'moduleHppPath'.+moduleSetHppPath :: MonadState Module m => String -> m ()+moduleSetHppPath path = modify $ \m -> m { moduleHppPath = path }++-- | Replaces a module's 'moduleCppPath'.+moduleSetCppPath :: MonadState Module m => String -> m ()+moduleSetCppPath path = modify $ \m -> m { moduleCppPath = path }++-- | Adds exports to a module. An export must only be added to any module at+-- most once, and must not be added to multiple modules.+moduleAddExports :: (MonadError String m, MonadState Module m) => [Export] -> m ()+moduleAddExports exports = do+ m <- get+ let existingExports = moduleExports m+ newExports = M.fromList $ map (getPrimaryExtName &&& id) exports+ duplicateNames = (S.intersection `on` M.keysSet) existingExports newExports+ if S.null duplicateNames+ then put m { moduleExports = existingExports `mappend` newExports }+ else throwError $ concat+ ["moduleAddExports: ", show m, " defines external names multiple times: ",+ show duplicateNames]++-- | Changes a module's 'moduleHaskellName' from the default. This can only be+-- called once on a module.+moduleAddHaskellName :: (MonadError String m, MonadState Module m) => [String] -> m ()+moduleAddHaskellName name = do+ m <- get+ case moduleHaskellName m of+ Nothing -> put m { moduleHaskellName = Just name }+ Just name' ->+ throwError $ concat+ ["moduleAddHaskellName: ", show m, " already has Haskell name ",+ show name', "; trying to add name ", show name, "."]++-- | Changes 'Foreign.Hoppy.Generator.Spec.Callback.callbackThrows' for all+-- callbacks in a module that don't have it set explicitly.+moduleSetCallbacksThrow :: MonadState Module m => Maybe Bool -> m ()+moduleSetCallbacksThrow b = modify $ \m -> m { moduleCallbacksThrow = b }++-- | A set of requirements of needed to use an identifier in C++ (function,+-- type, etc.), via a set of 'Include's. The monoid instance has 'mempty' as an+-- empty set of includes, and 'mappend' unions two include sets.+newtype Reqs = Reqs+ { reqsIncludes :: S.Set Include+ -- ^ The includes specified by a 'Reqs'.+ } deriving (Show)++instance Sem.Semigroup Reqs where+ (<>) (Reqs incl) (Reqs incl') = Reqs $ mappend incl incl'++instance Monoid Reqs where+ mempty = Reqs mempty++ mappend = (<>)++ mconcat reqs = Reqs $ mconcat $ map reqsIncludes reqs++-- | Creates a 'Reqs' that contains the given include.+reqInclude :: Include -> Reqs+reqInclude include = mempty { reqsIncludes = S.singleton include }++-- | Contains the data types for bindings to C++ entities:+-- 'Foreign.Hoppy.Generator.Spec.Function.Function',+-- 'Foreign.Hoppy.Generator.Spec.Class.Class', etc. Use 'addReqs' or+-- 'addReqIncludes' to specify requirements for these entities, e.g. header+-- files that must be included in order to access the underlying entities that+-- are being bound.++-- | C++ types that have requirements in order to use them in generated+-- bindings.+class HasReqs a where+ {-# MINIMAL getReqs, (setReqs | modifyReqs) #-}++ -- | Returns an object's requirements.+ getReqs :: a -> Reqs++ -- | Replaces an object's requirements with new ones.+ setReqs :: Reqs -> a -> a+ setReqs = modifyReqs . const++ -- | Modifies an object's requirements.+ modifyReqs :: (Reqs -> Reqs) -> a -> a+ modifyReqs f x = setReqs (f $ getReqs x) x++-- | Adds to a object's requirements.+addReqs :: HasReqs a => Reqs -> a -> a+addReqs reqs = modifyReqs $ mappend reqs++-- | Adds a list of includes to the requirements of an object.+addReqIncludes :: HasReqs a => [Include] -> a -> a+addReqIncludes includes =+ modifyReqs $ mappend mempty { reqsIncludes = S.fromList includes }++-- | An external name is a string that generated bindings use to uniquely+-- identify an object at runtime. An external name must start with an+-- alphabetic character, and may only contain alphanumeric characters and @'_'@.+-- You are free to use whatever naming style you like; case conversions will be+-- performed automatically when required. Hoppy does make use of some+-- conventions though, for example with 'Operator's and in the provided bindings+-- for the C++ standard library.+--+-- External names must be unique within an interface. They may not be reused+-- between modules. This assumption is used for symbol naming in compiled+-- shared objects and to freely import modules in Haskell bindings.+newtype ExtName = ExtName+ { fromExtName :: String+ -- ^ Returns the string an an 'ExtName' contains.+ } deriving (Eq, Sem.Semigroup, Monoid, Ord)++instance Show ExtName where+ show extName = concat ["$\"", fromExtName extName, "\"$"]++-- | Creates an 'ExtName' that contains the given string, erroring if the string+-- is an invalid 'ExtName'.+toExtName :: HasCallStack => String -> ExtName+toExtName str = case str of+ -- Keep this logic in sync with isValidExtName.+ [] -> error "An ExtName cannot be empty."+ _ -> if isValidExtName str+ then ExtName str+ else error $+ "An ExtName must start with a letter and only contain letters, numbers, and '_': " +++ show str++-- | Returns true if the given string is represents a valid 'ExtName'.+isValidExtName :: String -> Bool+isValidExtName str = case str of+ -- Keep this logic in sync with toExtName.+ [] -> False+ c:cs -> isAlpha c && all ((||) <$> isAlphaNum <*> (== '_')) cs++-- | Generates an 'ExtName' from an 'Identifier', if the given name is absent.+extNameOrIdentifier :: HasCallStack => Identifier -> Maybe ExtName -> ExtName+extNameOrIdentifier identifier = fromMaybe $ case identifierParts identifier of+ [] -> error "extNameOrIdentifier: Invalid empty identifier."+ parts -> toExtName $ idPartBase $ last parts++-- | Generates an 'ExtName' from an @'FnName' 'Identifier'@, if the given name+-- is absent.+extNameOrFnIdentifier :: HasCallStack => FnName Identifier -> Maybe ExtName -> ExtName+extNameOrFnIdentifier name =+ fromMaybe $ case name of+ FnName identifier -> case identifierParts identifier of+ [] -> error "extNameOrFnIdentifier: Empty idenfitier."+ parts -> toExtName $ idPartBase $ last parts+ FnOp op -> operatorPreferredExtName op++-- | Generates an 'ExtName' from a string, if the given name is absent.+extNameOrString :: String -> Maybe ExtName -> ExtName+extNameOrString str = fromMaybe $ toExtName str++-- | Types that have an external name, and also optionally have nested entities+-- with external names as well. See 'getAllExtNames'.+class HasExtNames a where+ -- | Returns the external name by which a given entity is referenced.+ getPrimaryExtName :: a -> ExtName++ -- | Returns external names nested within the given entity. Does not include+ -- the primary external name.+ getNestedExtNames :: a -> [ExtName]+ getNestedExtNames _ = []++-- | Returns a list of all of the external names an entity contains. This+-- combines both 'getPrimaryExtName' and 'getNestedExtNames'.+getAllExtNames :: HasExtNames a => a -> [ExtName]+getAllExtNames x = getPrimaryExtName x : getNestedExtNames x++-- | The C++ name of a function or method.+data FnName name =+ FnName name+ -- ^ A regular, \"alphanumeric\" name. The exact type depends on what kind of+ -- object is being named.+ | FnOp Operator+ -- ^ An operator name.+ deriving (Eq, Ord)++instance Show name => Show (FnName name) where+ show (FnName name) = concat ["<FnName ", show name, ">"]+ show (FnOp op) = concat ["<FnOp ", show op, ">"]++-- | Enables implementing automatic conversions to a @'FnName' t@.+class IsFnName t a where+ toFnName :: a -> FnName t++instance IsFnName t (FnName t) where+ toFnName = id++instance IsFnName t t where+ toFnName = FnName++instance IsFnName t Operator where+ toFnName = FnOp++-- | Overloadable C++ operators.+data Operator =+ OpCall -- ^ @x(...)@+ | OpComma -- ^ @x, y@+ | OpAssign -- ^ @x = y@+ | OpArray -- ^ @x[y]@+ | OpDeref -- ^ @*x@+ | OpAddress -- ^ @&x@+ | OpAdd -- ^ @x + y@+ | OpAddAssign -- ^ @x += y@+ | OpSubtract -- ^ @x - y@+ | OpSubtractAssign -- ^ @x -= y@+ | OpMultiply -- ^ @x * y@+ | OpMultiplyAssign -- ^ @x *= y@+ | OpDivide -- ^ @x / y@+ | OpDivideAssign -- ^ @x /= y@+ | OpModulo -- ^ @x % y@+ | OpModuloAssign -- ^ @x %= y@+ | OpPlus -- ^ @+x@+ | OpMinus -- ^ @-x@+ | OpIncPre -- ^ @++x@+ | OpIncPost -- ^ @x++@+ | OpDecPre -- ^ @--x@+ | OpDecPost -- ^ @x--@+ | OpEq -- ^ @x == y@+ | OpNe -- ^ @x != y@+ | OpLt -- ^ @x < y@+ | OpLe -- ^ @x <= y@+ | OpGt -- ^ @x > y@+ | OpGe -- ^ @x >= y@+ | OpNot -- ^ @!x@+ | OpAnd -- ^ @x && y@+ | OpOr -- ^ @x || y@+ | OpBitNot -- ^ @~x@+ | OpBitAnd -- ^ @x & y@+ | OpBitAndAssign -- ^ @x &= y@+ | OpBitOr -- ^ @x | y@+ | OpBitOrAssign -- ^ @x |= y@+ | OpBitXor -- ^ @x ^ y@+ | OpBitXorAssign -- ^ @x ^= y@+ | OpShl -- ^ @x << y@+ | OpShlAssign -- ^ @x <<= y@+ | OpShr -- ^ @x >> y@+ | OpShrAssign -- ^ @x >>= y@+ deriving (Bounded, Enum, Eq, Ord, Show)++-- | The arity and syntax of an operator.+data OperatorType =+ UnaryPrefixOperator String -- ^ Prefix unary operators. Examples: @!x@, @*x@, @++x@.+ | UnaryPostfixOperator String -- ^ Postfix unary operators. Examples: @x--, x++@.+ | BinaryOperator String -- ^ Infix binary operators. Examples: @x * y@, @x >>= y@.+ | CallOperator -- ^ @x(...)@ with arbitrary arity.+ | ArrayOperator -- ^ @x[y]@, a binary operator with non-infix syntax.++data OperatorInfo = OperatorInfo+ { operatorPreferredExtName'' :: ExtName+ , operatorType' :: OperatorType+ }++makeOperatorInfo :: String -> OperatorType -> OperatorInfo+makeOperatorInfo = OperatorInfo . toExtName++-- | Returns a conventional string to use for the 'ExtName' of an operator.+operatorPreferredExtName :: HasCallStack => Operator -> ExtName+operatorPreferredExtName op = case M.lookup op operatorInfo of+ Just info -> operatorPreferredExtName'' info+ Nothing ->+ error $ concat+ ["operatorPreferredExtName: Internal error, missing info for operator ", show op, "."]++-- | Returns a conventional name for an operator, as with+-- 'operatorPreferredExtName', but as a string.+operatorPreferredExtName' :: Operator -> String+operatorPreferredExtName' = fromExtName . operatorPreferredExtName++-- | Returns the type of an operator.+operatorType :: HasCallStack => Operator -> OperatorType+operatorType op = case M.lookup op operatorInfo of+ Just info -> operatorType' info+ Nothing ->+ error $ concat+ ["operatorType: Internal error, missing info for operator ", show op, "."]++-- | Metadata for operators.+--+-- TODO Test out this missing data.+operatorInfo :: M.Map Operator OperatorInfo+operatorInfo =+ let input =+ [ (OpCall, makeOperatorInfo "CALL" CallOperator)+ , (OpComma, makeOperatorInfo "COMMA" $ BinaryOperator ",")+ , (OpAssign, makeOperatorInfo "ASSIGN" $ BinaryOperator "=")+ , (OpArray, makeOperatorInfo "ARRAY" ArrayOperator)+ , (OpDeref, makeOperatorInfo "DEREF" $ UnaryPrefixOperator "*")+ , (OpAddress, makeOperatorInfo "ADDRESS" $ UnaryPrefixOperator "&")+ , (OpAdd, makeOperatorInfo "ADD" $ BinaryOperator "+")+ , (OpAddAssign, makeOperatorInfo "ADDA" $ BinaryOperator "+=")+ , (OpSubtract, makeOperatorInfo "SUB" $ BinaryOperator "-")+ , (OpSubtractAssign, makeOperatorInfo "SUBA" $ BinaryOperator "-=")+ , (OpMultiply, makeOperatorInfo "MUL" $ BinaryOperator "*")+ , (OpMultiplyAssign, makeOperatorInfo "MULA" $ BinaryOperator "*=")+ , (OpDivide, makeOperatorInfo "DIV" $ BinaryOperator "/")+ , (OpDivideAssign, makeOperatorInfo "DIVA" $ BinaryOperator "/=")+ , (OpModulo, makeOperatorInfo "MOD" $ BinaryOperator "%")+ , (OpModuloAssign, makeOperatorInfo "MODA" $ BinaryOperator "%=")+ , (OpPlus, makeOperatorInfo "PLUS" $ UnaryPrefixOperator "+")+ , (OpMinus, makeOperatorInfo "NEG" $ UnaryPrefixOperator "-")+ , (OpIncPre, makeOperatorInfo "INC" $ UnaryPrefixOperator "++")+ , (OpIncPost, makeOperatorInfo "INCPOST" $ UnaryPostfixOperator "++")+ , (OpDecPre, makeOperatorInfo "DEC" $ UnaryPrefixOperator "--")+ , (OpDecPost, makeOperatorInfo "DECPOST" $ UnaryPostfixOperator "--")+ , (OpEq, makeOperatorInfo "EQ" $ BinaryOperator "==")+ , (OpNe, makeOperatorInfo "NE" $ BinaryOperator "!=")+ , (OpLt, makeOperatorInfo "LT" $ BinaryOperator "<")+ , (OpLe, makeOperatorInfo "LE" $ BinaryOperator "<=")+ , (OpGt, makeOperatorInfo "GT" $ BinaryOperator ">")+ , (OpGe, makeOperatorInfo "GE" $ BinaryOperator ">=")+ , (OpNot, makeOperatorInfo "NOT" $ UnaryPrefixOperator "!")+ , (OpAnd, makeOperatorInfo "AND" $ BinaryOperator "&&")+ , (OpOr, makeOperatorInfo "OR" $ BinaryOperator "||")+ , (OpBitNot, makeOperatorInfo "BNOT" $ UnaryPrefixOperator "~")+ , (OpBitAnd, makeOperatorInfo "BAND" $ BinaryOperator "&")+ , (OpBitAndAssign, makeOperatorInfo "BANDA" $ BinaryOperator "&=")+ , (OpBitOr, makeOperatorInfo "BOR" $ BinaryOperator "|")+ , (OpBitOrAssign, makeOperatorInfo "BORA" $ BinaryOperator "|=")+ , (OpBitXor, makeOperatorInfo "BXOR" $ BinaryOperator "^")+ , (OpBitXorAssign, makeOperatorInfo "BXORA" $ BinaryOperator "^=")+ , (OpShl, makeOperatorInfo "SHL" $ BinaryOperator "<<")+ , (OpShlAssign, makeOperatorInfo "SHLA" $ BinaryOperator "<<=")+ , (OpShr, makeOperatorInfo "SHR" $ BinaryOperator ">>")+ , (OpShrAssign, makeOperatorInfo "SHR" $ BinaryOperator ">>=")+ ]+ in if map fst input == [minBound..]+ then M.fromList input+ else error "operatorInfo: Operator info list is out of sync with Operator data type."++-- | Types that contain 'Export's that can be looked up by their 'ExtName's.+class HasExports a where+ -- | Looks up the 'Export' for an 'ExtName' in the given object.+ lookupExport :: ExtName -> a -> Maybe Export++-- | A path to some C++ object, including namespaces. An identifier consists of+-- multiple parts separated by @\"::\"@. Each part has a name string followed+-- by an optional template argument list, where each argument gets rendered from+-- a 'Type' (non-type arguments for template metaprogramming are not supported).+--+-- The 'Monoid' instance inserts a @::@ between joined identifiers. Usually an+-- identifier needs to contain at least one part, so 'mempty' is an invalid+-- argument to many functions in Hoppy, but it is useful as a base case for+-- appending.+newtype Identifier = Identifier+ { identifierParts :: [IdPart]+ -- ^ The separate parts of the identifier, between @::@s.+ } deriving (Eq, Monoid, Sem.Semigroup)++instance Show Identifier where+ show identifier =+ (\wordList -> concat $ "<Identifier " : wordList ++ [">"]) $+ intersperse "::" $+ map (\part -> case idPartArgs part of+ Nothing -> idPartBase part+ Just args ->+ concat $+ idPartBase part : "<" :+ intersperse ", " (map show args) ++ [">"]) $+ identifierParts identifier++-- | A single component of an 'Identifier', between @::@s.+data IdPart = IdPart+ { idPartBase :: String+ -- ^ The name within the enclosing scope.+ , idPartArgs :: Maybe [Type]+ -- ^ Template arguments, if present.+ } deriving (Eq, Show)++-- | Creates an identifier from a collection of 'IdPart's, with @::@s between.+makeIdentifier :: [IdPart] -> Identifier+makeIdentifier = Identifier++-- | Creates an object representing one component of an identifier.+makeIdPart :: String -> Maybe [Type] -> IdPart+makeIdPart = IdPart++-- | Creates a identifier of the form @a@, without any namespace operators+-- (@::@).+ident :: String -> Identifier+ident a = Identifier [IdPart a Nothing]++-- | Creates an identifier of the form @a1::a2::...::aN@.+ident' :: [String] -> Identifier+ident' = Identifier . map (\x -> IdPart { idPartBase = x, idPartArgs = Nothing })++-- | Creates an identifier of the form @a::b@.+ident1 :: String -> String -> Identifier+ident1 a b = ident' [a, b]++-- | Creates an identifier of the form @a::b::c@.+ident2 :: String -> String -> String -> Identifier+ident2 a b c = ident' [a, b, c]++-- | Creates an identifier of the form @a::b::c::d@.+ident3 :: String -> String -> String -> String -> Identifier+ident3 a b c d = ident' [a, b, c, d]++-- | Creates an identifier of the form @a::b::c::d::e@.+ident4 :: String -> String -> String -> String -> String -> Identifier+ident4 a b c d e = ident' [a, b, c, d, e]++-- | Creates an identifier of the form @a::b::c::d::e::f@.+ident5 :: String -> String -> String -> String -> String -> String -> Identifier+ident5 a b c d e f = ident' [a, b, c, d, e, f]++-- | Creates an identifier of the form @a\<...\>@.+identT :: String -> [Type] -> Identifier+identT a ts = Identifier [IdPart a $ Just ts]++-- | Creates an identifier with arbitrary many templated and non-templated+-- parts.+identT' :: [(String, Maybe [Type])] -> Identifier+identT' = Identifier . map (uncurry IdPart)++-- | Creates an identifier of the form @a::b\<...\>@.+ident1T :: String -> String -> [Type] -> Identifier+ident1T a b ts = Identifier [IdPart a Nothing, IdPart b $ Just ts]++-- | Creates an identifier of the form @a::b::c\<...\>@.+ident2T :: String -> String -> String -> [Type] -> Identifier+ident2T a b c ts = Identifier [IdPart a Nothing, IdPart b Nothing, IdPart c $ Just ts]++-- | Creates an identifier of the form @a::b::c::d\<...\>@.+ident3T :: String -> String -> String -> String -> [Type] -> Identifier+ident3T a b c d ts =+ Identifier [IdPart a Nothing, IdPart b Nothing, IdPart c Nothing,+ IdPart d $ Just ts]++-- | Creates an identifier of the form @a::b::c::d::e\<...\>@.+ident4T :: String -> String -> String -> String -> String -> [Type] -> Identifier+ident4T a b c d e ts =+ Identifier [IdPart a Nothing, IdPart b Nothing, IdPart c Nothing,+ IdPart d Nothing, IdPart e $ Just ts]++-- | Creates an identifier of the form @a::b::c::d::e::f\<...\>@.+ident5T :: String -> String -> String -> String -> String -> String -> [Type] -> Identifier+ident5T a b c d e f ts =+ Identifier [IdPart a Nothing, IdPart b Nothing, IdPart c Nothing,+ IdPart d Nothing, IdPart e Nothing, IdPart f $ Just ts]++-- | Instances of this typeclass are C++ entities that Hoppy can expose to+-- foreign languages: functions, classes, global variables, etc. 'Interface's+-- are largely composed of exports (grouped into modules). Hoppy uses this+-- interface to perform code generation for each entity.+class (HasAddendum a, HasExtNames a, HasReqs a, Typeable a, Show a) => Exportable a where+ -- | Wraps an exportable object in an existential data type.+ --+ -- The default instance is just @toExport = 'Export'@, which does not need to+ -- be overridden in general.+ toExport :: a -> Export+ toExport = Export++ -- | Attempts to cast an exportable object to a specific type, pulling off+ -- 'Export' wrappers as necessary.+ --+ -- The default @castExport = 'cast'@ is fine.+ castExport :: (Typeable a, Exportable b, Typeable b) => a -> Maybe b+ castExport = cast++ -- | Generates the C++ side of the binding for an entity.+ --+ -- For an entity, Hoppy invokes this function once with 'LC.SayHeader' when+ -- generating the header file for a module, and once with 'LC.SaySource' when+ -- generating the corresponding source file.+ sayExportCpp :: LC.SayExportMode -> a -> LC.Generator ()++ -- | Generates the Haskell side of the binding for an entity.+ --+ -- For an entity, Hoppy invokes this function once with+ -- 'LH.SayExportForeignImports' when it is time to emit foreign imports, and+ -- once with 'LH.SayExportDecls' when it is time to generate Haskell binding+ -- code later in the module. Hoppy may also call the function with+ -- 'LH.SayExportBoot', if necessary.+ --+ -- See 'LH.SayExportMode'.+ sayExportHaskell :: LH.SayExportMode -> a -> LH.Generator ()++ -- | If the export is backed by an C++ enum, then this returns known+ -- structural information about the enum. This provides information to the+ -- \"evaluate enums\" hook so that Hoppy can determine enum values on its own.+ --+ -- By default, this returns @Nothing@.+ --+ -- See 'Hooks'.+ getExportEnumInfo :: a -> Maybe EnumInfo+ getExportEnumInfo _ = Nothing++ -- | If the export is backed by a C++ class that is marked as supporting+ -- exceptions, then this returns the class definition.+ --+ -- By default, this returns @Nothing@.+ getExportExceptionClass :: a -> Maybe Class+ getExportExceptionClass _ = Nothing++-- | Specifies some C++ object (function or class) to give access to.+data Export = forall a. Exportable a => Export a++instance HasAddendum Export where+ getAddendum (Export e) = getAddendum e+ setAddendum a (Export e) = Export $ setAddendum a e+ modifyAddendum f (Export e) = Export $ modifyAddendum f e++instance HasExtNames Export where+ getPrimaryExtName (Export e) = getPrimaryExtName e+ getNestedExtNames (Export e) = getNestedExtNames e++instance HasReqs Export where+ getReqs (Export e) = getReqs e+ setReqs reqs (Export e) = Export $ setReqs reqs e+ modifyReqs f (Export e) = Export $ modifyReqs f e++instance Exportable Export where+ toExport = id++ castExport (Export e) = castExport e++ sayExportCpp sayBody (Export e) = sayExportCpp sayBody e++ sayExportHaskell mode (Export e) = sayExportHaskell mode e++ getExportEnumInfo (Export e) = getExportEnumInfo e++ getExportExceptionClass (Export e) = getExportExceptionClass e++instance Show Export where+ show (Export e) = "<Export " ++ show e ++ ">"++-- | A concrete C++ type. Use the bindings in "Foreign.Hoppy.Generator.Types"+-- for values of this type; these data constructors are subject to change+-- without notice.+data Type =+ Internal_TVoid+ | Internal_TPtr Type+ | Internal_TRef Type+ | Internal_TFn [Parameter] Type+ | Internal_TObj Class+ | Internal_TObjToHeap Class+ | Internal_TToGc Type+ | Internal_TManual ConversionSpec+ | Internal_TConst Type+ -- When changing the declarations here, be sure to update the Eq instance.+ deriving (Show)++instance Eq Type where+ Internal_TVoid == Internal_TVoid = True+ (Internal_TPtr t) == (Internal_TPtr t') = t == t'+ (Internal_TRef t) == (Internal_TRef t') = t == t'+ (Internal_TFn ps r) == (Internal_TFn ps' r') =+ (and $ zipWith ((==) `on` parameterType) ps ps') && r == r'+ (Internal_TObj cls) == (Internal_TObj cls') = cls == cls'+ (Internal_TObjToHeap cls) == (Internal_TObjToHeap cls') = cls == cls'+ (Internal_TToGc t) == (Internal_TToGc t') = t == t'+ (Internal_TManual s) == (Internal_TManual s') = s == s'+ (Internal_TConst t) == (Internal_TConst t') = t == t'+ _ == _ = False++-- | Canonicalizes a 'Type' without changing its meaning. Multiple nested+-- 'Internal_TConst's are collapsed into a single one.+normalizeType :: Type -> Type+normalizeType t = case t of+ Internal_TVoid -> t+ Internal_TPtr t' -> Internal_TPtr $ normalizeType t'+ Internal_TRef t' -> Internal_TRef $ normalizeType t'+ Internal_TFn params retType ->+ Internal_TFn (map (onParameterType normalizeType) params) $ normalizeType retType+ Internal_TObj _ -> t+ Internal_TObjToHeap _ -> t+ Internal_TToGc _ -> t+ Internal_TManual _ -> t+ Internal_TConst (Internal_TConst t') -> normalizeType $ Internal_TConst t'+ Internal_TConst _ -> t++-- | Strips leading 'Internal_TConst's off of a type.+stripConst :: Type -> Type+stripConst t = case t of+ Internal_TConst t' -> stripConst t'+ _ -> t++-- | Whether or not @const@ is applied to an entity.+data Constness = Nonconst | Const+ deriving (Bounded, Enum, Eq, Show)++-- | Returns the opposite constness value.+constNegate :: Constness -> Constness+constNegate Nonconst = Const+constNegate Const = Nonconst++-- | Whether or not a function may cause side-effects.+--+-- Haskell bindings for pure functions will not be in 'IO', and calls to pure+-- functions will be executed non-strictly. Calls to impure functions will+-- execute in the IO monad.+--+-- Member functions for mutable classes should not be made pure, because it is+-- difficult in general to control when the call will be made.+data Purity = Nonpure -- ^ Side-affects are possible.+ | Pure -- ^ Side-affects will not happen.+ deriving (Eq, Show)++-- | A parameter to a function, including a type and an optional name. A name+-- can be conveniently associated with a type with the @('~:')@ operator.+--+-- Two @Parameter@s are equal if their types are equal.+data Parameter = Parameter+ { parameterType :: Type+ -- ^ The parameter's data type.+ , parameterName :: Maybe String+ -- ^ An optional variable name to describe the parameter. This name should+ -- follow the same rules as 'ExtName' for its contents.+ } deriving (Show)++-- | Objects that can be coerced to function parameter definitions.+class Show a => IsParameter a where+ toParameter :: a -> Parameter++instance IsParameter Parameter where+ toParameter = id++instance IsParameter Type where+ toParameter t =+ Parameter+ { parameterType = t+ , parameterName = Nothing+ }++onParameterType :: (Type -> Type) -> (Parameter -> Parameter)+onParameterType f p = p { parameterType = f $ parameterType p }++-- | An empty parameter list. This should be used instead of a literal @[]@+-- when declaring an empty parameter list, because in the context of+-- @'IsParameter' a => [a]@, the empty list is ambiguously typed, even though it+-- doesn't matter which instance is selected.+np :: [Parameter]+np = []++-- | Converts a list of parameter-like objects to parameters.+toParameters :: IsParameter a => [a] -> [Parameter]+toParameters = map toParameter++-- | Associates a name string with a type to create a 'Parameter' that+-- can be given as a function or method parameter, instead of a raw 'Type'. The+-- name given here will be included as documentation in the generated code.+--+-- An empty string given for the name means not to associate a name with the+-- parameter. This is useful to leave some parameters unnamed in a parameter+-- list while naming other parameters, since the list must either contain all+-- 'Type's or all 'Parameter's.+(~:) :: IsParameter a => String -> a -> Parameter+(~:) name param =+ (toParameter param) { parameterName = if null name then Nothing else Just name }+infixr 0 ~:++-- | Defines the process for converting a value in one direction between C++ and+-- a foreign language. The type parameter varies depending on the actual+-- conversion being defined.+data ConversionMethod c =+ ConversionUnsupported+ -- ^ The conversion is unsupported. If part of an interface depends on+ -- performing this conversion, code generation will fail.+ | BinaryCompatible+ -- ^ The input value and its corresponding output have the same binary+ -- representation in memory, and require no explicit conversion. Numeric+ -- types may use this conversion method.+ | CustomConversion c+ -- ^ Conversion requires a custom process as specified by the argument.+ --+ -- TODO Split into pure (let) vs nonpure (<-)?+ deriving (Show)++-- | The root data type for specifying how conversions happen between C++ and foreign+-- values.+--+-- The @Cpp@ component of this data structure specifies a C++ type, and+-- conversions between it and something that can be marshalled over a C FFI+-- layer, if such a conversion is possible in each direction.+--+-- Each foreign language has its own component that must be specified in order+-- for types using this specification to be usable in that language.+data ConversionSpec = ConversionSpec+ { conversionSpecName :: String+ -- ^ An identifying name, used for rendering in e.g. error messages.+ , conversionSpecCpp :: ConversionSpecCpp+ -- ^ Fundamental information about the C++ type.+ , conversionSpecHaskell :: Maybe ConversionSpecHaskell+ -- ^ A specification for how values can be used in Haskell.+ }++instance Eq ConversionSpec where+ (==) = (==) `on` conversionSpecName++instance Show ConversionSpec where+ show x = "<ConversionSpec " ++ show (conversionSpecName x) ++ ">"++-- | Creates a 'ConversionSpec' from an identifying name and a specification of+-- the C++ conversion behaviour. By default, no foreign language conversion+-- behaviour is configured. For Haskell, this should be done by using+-- 'makeConversionSpecHaskell' to specify behaviour, then writing that to the+-- 'conversionSpecHaskell' field of the 'ConversionSpec' returned here.+makeConversionSpec ::+ String -- ^ 'conversionSpecName'+ -> ConversionSpecCpp -- ^ 'conversionSpecCpp'+ -> ConversionSpec+makeConversionSpec name cppSpec =+ ConversionSpec+ { conversionSpecName = name+ , conversionSpecCpp = cppSpec+ , conversionSpecHaskell = Nothing+ }++-- | For a 'ConversionSpec', defines the C++ type and conversions to and from a+-- C FFI layer.+--+-- Prefer 'makeConversionSpecCpp' to using this data constructor directly.+--+-- 'conversionSpecCppName' specifies the C++ type of the conversion. This will+-- be the type that is passed over the C FFI as well, unless+-- 'conversionSpecCppConversionType' overrides it.+-- 'conversionSpecCppConversionToCppExpr' and+-- 'conversionSpecCppConversionFromCppExpr' may define custom code generation+-- for passing values over the FFI.+data ConversionSpecCpp = ConversionSpecCpp+ { conversionSpecCppName :: String+ -- ^ The name of the C++ type. May identify a primitive C++ type such as+ -- @\"unsigned int\"@, or a more complex type like+ -- @std::list\<std::string\>@.++ , conversionSpecCppReqs :: LC.Generator Reqs+ -- ^ Computes requirements to refer to the C++ type. Being in the generator+ -- monad, this may use its environment, but should not emit code or 'Reqs'+ -- to the generator directly.++ , conversionSpecCppConversionType :: LC.Generator (Maybe Type)+ -- ^ Specifies the type that will be passed over the C FFI.+ --+ -- If absent (default), then the type named by 'conversionSpecCppName' is+ -- also used for marshalling to foreign languages.+ --+ -- If present, this represents a type distinct from 'conversionSpecCppName'+ -- that will be exchanged across the FFI boundary. In this case, you may+ -- also want to define one or both of 'conversionSpecCppConversionToCppExpr'+ -- and 'conversionSpecCppConversionFromCppExpr'.+ --+ -- This is a monadic value so that it has access to the generator's+ -- environment. The action should not add imports or emit code.++ , conversionSpecCppConversionToCppExpr ::+ Maybe (LC.Generator () -> Maybe (LC.Generator ()) -> LC.Generator ())+ -- ^ This controls behaviour for receiving a value passed into C++ over the+ -- FFI. Specifically, this powers the @ConversionSpec@ being used as+ -- 'Foreign.Hoppy.Generator.Spec.Function.Function' arguments and+ -- 'Foreign.Hoppy.Generator.Spec.Callback.Callback' return values.+ --+ -- When absent (default), generated code assumes that it can implicitly+ -- convert a value passed over the FFI from the C FFI type (see+ -- 'conversionSpecCppConversionType') to the C++ type+ -- (i.e. 'conversionSpecCppName'). When the former is absent, this is+ -- always fine.+ --+ -- When present, this provides custom conversion behaviour for receiving a+ -- value passed into C++ over the FFI. The function should generate C+++ -- code to convert a value from the type passed over the C FFI into the+ -- actual C++ type.+ --+ -- This is a function of the form:+ --+ -- > \emitFromExpr maybeEmitToVar -> ...+ --+ -- If the function's second argument is present, then the function should+ -- emit a variable declaration for that name, created from the expression+ -- emitted by the first argument.+ --+ -- If the function's second argument is absent, then the function should+ -- emit an expression that converts the expression emitted by the first+ -- argument into the appropriate type.+ --+ -- In both cases, the first generator argument should only be evaluated once+ -- by the resulting C++ expression; it is not guaranteed to be pure.++ , conversionSpecCppConversionFromCppExpr ::+ Maybe (LC.Generator () -> Maybe (LC.Generator ()) -> LC.Generator ())+ -- ^ This is the opposite of 'conversionSpecCppConversionToCppExpr'. This+ -- being present enables custom conversion behaviour for passing a value+ -- /out of/ C++ through the FFI. This powers the @ConversionSpec@ being+ -- used as 'Foreign.Hoppy.Generator.Spec.Function.Function' return values+ -- and 'Foreign.Hoppy.Generator.Spec.Callback.Callback' arguments.+ }++-- | Builds a 'ConversionSpecCpp' with a C++ type, with no conversions defined.+makeConversionSpecCpp :: String -> LC.Generator Reqs -> ConversionSpecCpp+makeConversionSpecCpp cppName cppReqs =+ ConversionSpecCpp+ { conversionSpecCppName = cppName+ , conversionSpecCppReqs = cppReqs+ , conversionSpecCppConversionType = return Nothing+ , conversionSpecCppConversionToCppExpr = Nothing+ , conversionSpecCppConversionFromCppExpr = Nothing+ }++-- | Controls how conversions between C++ values and Haskell values happen in+-- Haskell bindings.+--+-- Prefer 'makeConversionSpecHaskell' to using this data constructor directly.+data ConversionSpecHaskell = ConversionSpecHaskell+ { conversionSpecHaskellHsType :: LH.Generator HsType+ -- ^ The type exposed to users of the Haskell side of a binding. Functions+ -- that take one of these values as an argument will expect this type, and+ -- functions returning one of these values will return this type.+ --+ -- This type is wrapped in a generator in order to be able to specify any+ -- necessary imports. This generator should not generate code or add+ -- exports.++ , conversionSpecHaskellHsArgType :: Maybe (HsName -> LH.Generator HsQualType)+ -- ^ If present, then for bindings for C++ functions that expect one of+ -- these values as an argument, rather than taking a fixed concrete type+ -- ('conversionSpecHaskellHsType'), this qualified type will be used+ -- instead. The 'HsName' parameter receives a unique name from the+ -- generator that can be used with 'Language.Haskell.Syntax.HsTyVar' like+ -- so:+ --+ -- > \name -> return $ HsQualType [...constraints...] (HsTyVar name)+ --+ -- 'conversionSpecHaskellHsType' should satisfy this constraint, when+ -- present.+ --+ -- This type is wrapped in a generator in order to be able to specify any+ -- necessary imports. This generator should not generate code or add+ -- exports.++ , conversionSpecHaskellCType :: Maybe (LH.Generator HsType)+ -- ^ If present, then rather than passing a value of native Haskell type+ -- ('conversionSpecHaskellHsType') directly over the FFI, this is an+ -- intermediate type that will be passed instead. This is needed any time+ -- that the former type isn't a simple type that the FFI supports.+ --+ -- 'conversionSpecHaskellToCppFn' and 'conversionSpecHaskellFromCppFn'+ -- marshal values into and out of this type, respsectively.+ --+ -- This type is wrapped in a generator in order to be able to specify any+ -- necessary imports. This generator should not generate code or add+ -- exports.++ , conversionSpecHaskellToCppFn :: ConversionMethod (LH.Generator ())+ -- ^ This defines how a Haskell value is passed to C++. If this is+ -- 'CustomConversion', then 'conversionSpecHaskellCType' must be present,+ -- and the generator should output a function that takes a value of type+ -- 'conversionSpecHaskellHsType' and return a value of+ -- 'conversionSpecHaskellCType'.+ --+ -- If 'conversionSpecHaskellHsArgType' is present, then the function should+ -- be able to accept that more general type instead. This is used for+ -- bindings that call into C++ functions. This function is still+ -- specialized to 'conversionSpecHaskellHsType' when generating code for+ -- callback return values.+ --+ -- The generator should output code and may add imports, but should not add+ -- exports.++ , conversionSpecHaskellFromCppFn :: ConversionMethod (LH.Generator ())+ -- ^ This defines how a Haskell value is passed from C++. If this is+ -- 'CustomConversion', then 'conversionSpecHaskellCType' must be present,+ -- and the generator should output a function that takes a value of type+ -- 'conversionSpecHaskellCType' and return a value of+ -- 'conversionSpecHaskellHsType'.+ --+ -- The generator should output code and may add imports, but should not add+ -- exports.+ }++-- | Builds a 'ConversionSpecHaskell' with the mandatory parameters given.+makeConversionSpecHaskell ::+ LH.Generator HsType -- ^ 'conversionSpecHaskellHsType'+ -> Maybe (LH.Generator HsType) -- ^ 'conversionSpecHaskellCType'+ -> ConversionMethod (LH.Generator ()) -- ^ 'conversionSpecHaskellToCppFn'+ -> ConversionMethod (LH.Generator ()) -- ^ 'conversionSpecHaskellFromCppFn'+ -> ConversionSpecHaskell+makeConversionSpecHaskell hsType cType toCppFn fromCppFn =+ ConversionSpecHaskell+ { conversionSpecHaskellHsType = hsType+ , conversionSpecHaskellHsArgType = Nothing+ , conversionSpecHaskellCType = cType+ , conversionSpecHaskellToCppFn = toCppFn+ , conversionSpecHaskellFromCppFn = fromCppFn+ }++-- | Information about the enum that has been completed beyond what the+-- interface definition provides, possibly by building actual C++ code.+data EvaluatedEnumData = EvaluatedEnumData+ { evaluatedEnumType :: Type+ -- ^ The numeric type that C++ uses to hold the enum's values, or an+ -- equivalently-sized type.+ , evaluatedEnumValueMap :: EvaluatedEnumValueMap+ -- ^ Calculated values for all of the enum's entries.+ }++-- | Contains the numeric values for each of the entries in a C++ enum.+type EvaluatedEnumValueMap = M.Map [String] Integer++-- | Each exception class has a unique exception ID.+newtype ExceptionId = ExceptionId+ { getExceptionId :: Int -- ^ Internal.+ } deriving (Eq, Show)++-- | The exception ID that represents the catch-all type.+exceptionCatchAllId :: ExceptionId+exceptionCatchAllId = ExceptionId 1++-- | The lowest exception ID to be used for classes.+exceptionFirstFreeId :: Int+exceptionFirstFreeId = getExceptionId exceptionCatchAllId + 1++-- | Indicates the ability to handle a certain type of C++ exception.+data ExceptionHandler =+ CatchClass Class+ -- ^ Indicates that instances of the given class are handled (including+ -- derived types).+ | CatchAll+ -- ^ Indicates that all C++ exceptions are handled, i.e. @catch (...)@.+ deriving (Eq, Ord)++-- | Represents a list of exception handlers to be used for a body of code.+-- Order is important; a 'CatchAll' will prevent all subsequent handlers from+-- being invoked.+newtype ExceptionHandlers = ExceptionHandlers+ { exceptionHandlersList :: [ExceptionHandler]+ -- ^ Extracts the list of exception handlers.+ }++instance Sem.Semigroup ExceptionHandlers where+ (<>) e1 e2 =+ ExceptionHandlers $ exceptionHandlersList e1 ++ exceptionHandlersList e2++instance Monoid ExceptionHandlers where+ mempty = ExceptionHandlers []++ mappend = (<>)++-- | Types that can handle exceptions.+class HandlesExceptions a where+ -- | Extracts the exception handlers for an object.+ getExceptionHandlers :: a -> ExceptionHandlers++ -- | Modifies an object's exception handlers with a given function.+ modifyExceptionHandlers :: (ExceptionHandlers -> ExceptionHandlers) -> a -> a++-- | Appends additional exception handlers to an object.+handleExceptions :: HandlesExceptions a => [ExceptionHandler] -> a -> a+handleExceptions classes =+ modifyExceptionHandlers $ mappend mempty { exceptionHandlersList = classes }++-- | A literal piece of code that will be inserted into a generated source file+-- after the regular binding glue. The 'Monoid' instance concatenates code+-- (actions).+newtype Addendum = Addendum+ { addendumHaskell :: LH.Generator ()+ -- ^ Code to be output into the Haskell binding. May also add imports and+ -- exports.+ }++instance Sem.Semigroup Addendum where+ (<>) (Addendum a) (Addendum b) = Addendum $ a >> b++instance Monoid Addendum where+ mempty = Addendum $ return ()+ mappend = (<>)++-- | A typeclass for types that have an addendum.+class HasAddendum a where+ {-# MINIMAL getAddendum, (setAddendum | modifyAddendum) #-}++ -- | Returns an object's addendum.+ getAddendum :: a -> Addendum++ -- | Replaces and object's addendum with another.+ setAddendum :: Addendum -> a -> a+ setAddendum addendum = modifyAddendum $ const addendum++ -- | Modified an object's addendum.+ modifyAddendum :: (Addendum -> Addendum) -> a -> a+ modifyAddendum f x = setAddendum (f $ getAddendum x) x++-- | Adds a Haskell addendum to an object.+addAddendumHaskell :: HasAddendum a => LH.Generator () -> a -> a+addAddendumHaskell gen = modifyAddendum $ \addendum ->+ addendum `mappend` mempty { addendumHaskell = gen }++-- | Structural information about a C++ enum. This is used when Hoppy is+-- evaluating enum data, see 'getExportEnumInfo'.+--+-- See 'Foreign.Hoppy.Generator.Spec.Enum.CppEnum'.+data EnumInfo = EnumInfo+ { enumInfoExtName :: ExtName+ -- ^ The external name of the enum.+ , enumInfoIdentifier :: Identifier+ -- ^ The enum's identifier.+ , enumInfoNumericType :: Maybe Type+ -- ^ The enum's numeric type, if explicitly known to the bindings. This+ -- does not need to be provided. If absent, then Hoppy will calculate the+ -- enum's numeric type on its own, using a C++ compiler. If this is present+ -- however, Hoppy will use it, and additionally validate it against what the+ -- C++ compiler thinks, if validation is enabled (see+ -- 'interfaceValidateEnumTypes').+ , enumInfoReqs :: Reqs+ -- ^ Requirements for accessing the enum.+ , enumInfoValues :: EnumValueMap+ -- ^ The entries in the enum.+ }++-- | A list of words that comprise the name of an enum entry. Each string in+-- this list is treated as a distinct word for the purpose of performing case+-- conversion to create identifiers in foreign languages. These values are most+-- easily created from a C++ identifier using+-- 'Foreign.Hoppy.Generator.Util.splitIntoWords'.+type EnumEntryWords = [String]++-- | Describes the entries in a C++ enum.+--+-- Equality is defined as having the same 'enumValueMapValues'.+data EnumValueMap = EnumValueMap+ { enumValueMapNames :: [EnumEntryWords]+ -- ^ The names of all entries in the enum being generated, in the order+ -- specified by the enum definition. These are the strings used to name+ -- generated bindings. Each name is broken up into words. How the words+ -- and get combined to make a name in a particular foreign language depends+ -- on the language.+ , enumValueMapForeignNames :: MapWithForeignLanguageOverrides EnumEntryWords EnumEntryWords+ -- ^ Per-language renames of enum value entries.+ , enumValueMapValues :: M.Map EnumEntryWords EnumValue+ -- ^ A map specifying for each entry in 'enumValueMapNames', how to+ -- determine the entry's numeric value.+ }++instance Eq EnumValueMap where+ (==) = (==) `on` enumValueMapValues++instance Show EnumValueMap where+ show x = "<EnumValueMap values=" ++ show (enumValueMapValues x) ++ ">"++-- | Describes the value of an entry in a C++ enum. A numeric value may either+-- be provided manually, or if omitted, Hoppy can determine it automatically.+data EnumValue =+ EnumValueManual Integer+ -- ^ A manually specified numeric enum value.+ | EnumValueAuto Identifier+ -- ^ A numeric enum value that will be determined when the generator is run,+ -- by means of compiling a C++ program.+ deriving (Eq, Show)++-- | Languages that Hoppy supports binding to. Currently this is only Haskell.+data ForeignLanguage =+ Haskell -- ^ The Haskell language.+ deriving (Eq, Ord, Show)++-- | A value that may be overridden based on a 'ForeignLanguage'.+type WithForeignLanguageOverrides = WithOverrides ForeignLanguage++-- | A map whose values may be overridden based on a 'ForeignLanguage'.+type MapWithForeignLanguageOverrides = MapWithOverrides ForeignLanguage++-- | A collection of imports for a Haskell module. This is a monoid: import+-- Statements are merged to give the union of imported bindings.+--+-- This structure supports two specific types of imports:+-- - @import Foo (...)@+-- - @import qualified Foo as Bar@+-- Imports with @as@ but without @qualified@, and @qualified@ imports with a+-- spec list, are not supported. This satisfies the needs of the code+-- generator, and keeps the merging logic simple.+data HsImportSet = HsImportSet+ { getHsImportSet :: M.Map HsImportKey HsImportSpecs+ -- ^ Returns the import set's internal map from module names to imported+ -- bindings.+ } deriving (Show)++-- TODO Make HsImportSet back into a newtype when it doesn't involve listing out+-- its contents recursively in Base.hs-boot.++instance Sem.Semigroup HsImportSet where+ (<>) (HsImportSet m) (HsImportSet m') =+ HsImportSet $ M.unionWith mergeImportSpecs m m'++instance Monoid HsImportSet where+ mempty = HsImportSet M.empty++ mappend = (<>)++ mconcat sets =+ HsImportSet $ M.unionsWith mergeImportSpecs $ map getHsImportSet sets++-- | Constructor for an import set.+makeHsImportSet :: M.Map HsImportKey HsImportSpecs -> HsImportSet+makeHsImportSet = HsImportSet++-- | Sets all of the import specifications in an import set to be+-- @{-#SOURCE#-}@ imports.+hsImportSetMakeSource :: HsImportSet -> HsImportSet+hsImportSetMakeSource (HsImportSet m) =+ HsImportSet $ M.map (\specs -> specs { hsImportSource = True }) m++-- | A Haskell module name.+type HsModuleName = String++-- | References an occurrence of an import statement, under which bindings can+-- be imported. Only imported specs under equal 'HsImportKey's may be merged.+data HsImportKey = HsImportKey+ { hsImportModule :: HsModuleName+ , hsImportQualifiedName :: Maybe HsModuleName+ } deriving (Eq, Ord, Show)++-- | A specification of bindings to import from a module. If 'Nothing', then+-- the entire module is imported. If @'Just' 'M.empty'@, then only instances+-- are imported.+data HsImportSpecs = HsImportSpecs+ { getHsImportSpecs :: Maybe (M.Map HsImportName HsImportVal)+ , hsImportSource :: Bool+ } deriving (Show)++-- | Combines two 'HsImportSpecs's into one that imports everything that the two+-- did separately.+mergeImportSpecs :: HsImportSpecs -> HsImportSpecs -> HsImportSpecs+mergeImportSpecs (HsImportSpecs mm s) (HsImportSpecs mm' s') =+ HsImportSpecs (liftM2 mergeMaps mm mm') (s || s')+ where mergeMaps = M.unionWith mergeValues+ mergeValues v v' = case (v, v') of+ (HsImportValAll, _) -> HsImportValAll+ (_, HsImportValAll) -> HsImportValAll+ (HsImportValSome x, HsImportValSome x') -> HsImportValSome $ x ++ x'+ (x@(HsImportValSome _), _) -> x+ (_, x@(HsImportValSome _)) -> x+ (HsImportVal, HsImportVal) -> HsImportVal++-- | An identifier that can be imported from a module. Symbols may be used here+-- when surrounded by parentheses. Examples are @\"fmap\"@ and @\"(++)\"@.+type HsImportName = String++-- | Specifies how a name is imported.+data HsImportVal =+ HsImportVal+ -- ^ The name is imported, and nothing underneath it is.+ | HsImportValSome [HsImportName]+ -- ^ The name is imported, as are specific names underneath it. This is a+ -- @X (a, b, c)@ import.+ | HsImportValAll+ -- ^ The name is imported, along with all names underneath it. This is a @X+ -- (..)@ import.+ deriving (Show)++-- | An import for the entire contents of a Haskell module.+hsWholeModuleImport :: HsModuleName -> HsImportSet+hsWholeModuleImport modName =+ HsImportSet $ M.singleton (HsImportKey modName Nothing) $+ HsImportSpecs Nothing False++-- | A qualified import of a Haskell module.+hsQualifiedImport :: HsModuleName -> HsModuleName -> HsImportSet+hsQualifiedImport modName qualifiedName =+ HsImportSet $ M.singleton (HsImportKey modName $ Just qualifiedName) $+ HsImportSpecs Nothing False++-- | An import of a single name from a Haskell module.+hsImport1 :: HsModuleName -> HsImportName -> HsImportSet+hsImport1 modName valueName = hsImport1' modName valueName HsImportVal++-- | A detailed import of a single name from a Haskell module.+hsImport1' :: HsModuleName -> HsImportName -> HsImportVal -> HsImportSet+hsImport1' modName valueName valueType =+ HsImportSet $ M.singleton (HsImportKey modName Nothing) $+ HsImportSpecs (Just $ M.singleton valueName valueType) False++-- | An import of multiple names from a Haskell module.+hsImports :: HsModuleName -> [HsImportName] -> HsImportSet+hsImports modName names =+ hsImports' modName $ map (\name -> (name, HsImportVal)) names++-- | A detailed import of multiple names from a Haskell module.+hsImports' :: HsModuleName -> [(HsImportName, HsImportVal)] -> HsImportSet+hsImports' modName values =+ HsImportSet $ M.singleton (HsImportKey modName Nothing) $+ HsImportSpecs (Just $ M.fromList values) False++-- | Imports "Data.Bits" qualified as @HoppyDB@.+hsImportForBits :: HsImportSet+hsImportForBits = hsQualifiedImport "Data.Bits" "HoppyDB"++-- | Imports "Control.Exception" qualified as @HoppyCE@.+hsImportForException :: HsImportSet+hsImportForException = hsQualifiedImport "Control.Exception" "HoppyCE"++-- | Imports "Data.Int" qualified as @HoppyDI@.+hsImportForInt :: HsImportSet+hsImportForInt = hsQualifiedImport "Data.Int" "HoppyDI"++-- | Imports "Data.Word" qualified as @HoppyDW@.+hsImportForWord :: HsImportSet+hsImportForWord = hsQualifiedImport "Data.Word" "HoppyDW"++-- | Imports "Foreign" qualified as @HoppyF@.+hsImportForForeign :: HsImportSet+hsImportForForeign = hsQualifiedImport "Foreign" "HoppyF"++-- | Imports "Foreign.C" qualified as @HoppyFC@.+hsImportForForeignC :: HsImportSet+hsImportForForeignC = hsQualifiedImport "Foreign.C" "HoppyFC"++-- | Imports "Data.Map" qualified as @HoppyDM@.+hsImportForMap :: HsImportSet+hsImportForMap = hsQualifiedImport "Data.Map" "HoppyDM"++-- | Imports "Prelude" qualified as @HoppyP@.+hsImportForPrelude :: HsImportSet+hsImportForPrelude = hsQualifiedImport "Prelude" "HoppyP"++-- | Imports "Foreign.Hoppy.Runtime" qualified as @HoppyFHR@.+hsImportForRuntime :: HsImportSet+hsImportForRuntime = hsQualifiedImport "Foreign.Hoppy.Runtime" "HoppyFHR"++-- | Imports "System.Posix.Types" qualified as @HoppySPT@.+hsImportForSystemPosixTypes :: HsImportSet+hsImportForSystemPosixTypes = hsQualifiedImport "System.Posix.Types" "HoppySPT" -- | Imports "System.IO.Unsafe" qualified as @HoppySIU@. hsImportForUnsafeIO :: HsImportSet
+ src/Foreign/Hoppy/Generator/Spec/Base.hs-boot view
@@ -0,0 +1,41 @@+-- This file is part of Hoppy.+--+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net>+--+-- 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 <http://www.gnu.org/licenses/>.++module Foreign.Hoppy.Generator.Spec.Base (+ ErrorMsg,+ Reqs,+ ExtName,+ Identifier,+ Constness,+ HsImportSet,+ ) where++type ErrorMsg = String++data Reqs++newtype ExtName = ExtName { fromExtName :: String }++newtype Identifier = Identifier+ { identifierParts :: [IdPart]+ }++data IdPart++data Constness++data HsImportSet
+ src/Foreign/Hoppy/Generator/Spec/Callback.hs view
@@ -0,0 +1,576 @@+-- This file is part of Hoppy.+--+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net>+--+-- 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 <http://www.gnu.org/licenses/>.++-- | Interface for defining foreign language callbacks.+module Foreign.Hoppy.Generator.Spec.Callback (+ -- * Data type+ Callback, callbackT,+ -- * Construction+ makeCallback,+ -- * Properties+ callbackExtName,+ callbackParams,+ callbackReturn,+ callbackReqs,+ callbackAddendum,+ -- ** Exceptions+ callbackThrows,+ callbackSetThrows,+ -- * C++ generator+ cppCallbackToTFn,+ -- ** Names+ callbackClassName,+ callbackImplClassName,+ callbackFnName,+ -- * Haskell generator+ hsCallbackToTFn,+ -- ** Names+ toHsCallbackCtorName, toHsCallbackCtorName',+ toHsCallbackNewFunPtrFnName, toHsCallbackNewFunPtrFnName',+ ) where++import Control.Monad (forM_, when)+import Data.Function (on)+import Data.Maybe (fromMaybe, isJust)+import qualified Foreign.Hoppy.Generator.Language.Cpp as LC+import 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, fnT', intT, manualT, ptrT, voidT)+import Language.Haskell.Syntax (+ HsName (HsIdent),+ HsQName (Special, UnQual),+ HsSpecialCon (HsUnitCon),+ HsType (HsTyApp, HsTyCon, HsTyFun),+ )++-- | A non-C++ function that can be invoked via a C++ functor or function+-- pointer.+--+-- Use this data type's 'HasReqs' instance to add extra requirements, however+-- manually adding requirements for parameter and return types is not necessary.+data Callback = Callback+ { callbackExtName :: ExtName+ -- ^ The callback's external name.+ , callbackParams :: [Parameter]+ -- ^ The callback's parameters.+ , callbackReturn :: Type+ -- ^ The callback's return type.+ , callbackThrows :: Maybe Bool+ -- ^ Whether the callback supports throwing C++ exceptions from Haskell into+ -- C++ during its execution. When absent, the value is inherited from+ -- 'moduleCallbacksThrow' and 'interfaceCallbacksThrow'.+ , callbackReqs :: Reqs+ -- ^ Extra requirements for the callback.+ , callbackAddendum :: Addendum+ -- ^ The callback's addendum.+ }++instance Eq Callback where+ (==) = (==) `on` callbackExtName++instance Show Callback where+ show cb =+ concat ["<Callback ", show (callbackExtName cb), " ", show (callbackParams cb), " ",+ show (callbackReturn cb)]++instance Exportable Callback where+ sayExportCpp = sayCppExport+ sayExportHaskell = sayHsExport++instance HasExtNames Callback where+ getPrimaryExtName = callbackExtName++instance HasReqs Callback where+ getReqs = callbackReqs+ setReqs reqs cb = cb { callbackReqs = reqs }++instance HasAddendum Callback where+ getAddendum = callbackAddendum+ setAddendum addendum cb = cb { callbackAddendum = addendum }++-- | Creates a binding for constructing callbacks into foreign code.+makeCallback :: IsParameter p+ => ExtName+ -> [p] -- ^ Parameter types.+ -> Type -- ^ Return type.+ -> Callback+makeCallback extName paramTypes retType =+ Callback extName (toParameters paramTypes) retType Nothing mempty mempty++-- | Sets whether a callback supports handling thrown C++ exceptions and passing+-- them into C++.+callbackSetThrows :: Bool -> Callback -> Callback+callbackSetThrows value cb = cb { callbackThrows = Just value }++makeConversion :: Callback -> ConversionSpec+makeConversion cb =+ (makeConversionSpec (show cb) cpp)+ { conversionSpecHaskell = hs }+ where reqsGen = do+ -- TODO Should this be includeStd?+ cbClassReqs <- reqInclude . includeLocal . moduleHppPath <$>+ LC.findExportModule (callbackExtName cb)+ -- TODO Is the right 'ReqsType' being used recursively here?+ fnTypeReqs <- LC.typeReqs =<< cppCallbackToTFn cb+ return $ cbClassReqs `mappend` fnTypeReqs++ cpp =+ (makeConversionSpecCpp (callbackClassName cb) reqsGen)+ { conversionSpecCppConversionType = return $ Just $ ptrT callbackImplClassType+ , conversionSpecCppConversionToCppExpr = Just $ \fromVar maybeToVar -> case maybeToVar of+ Just toVar ->+ LC.says [callbackClassName cb, " "] >> toVar >> LC.say "(" >>+ fromVar >> LC.say ");\n"+ Nothing -> LC.says [callbackClassName cb, "("] >> fromVar >> LC.say ")"+ -- No from-C++ conversion; we don't support passing callbacks back out again.+ }++ hs =+ Just $ makeConversionSpecHaskell+ (LH.cppTypeToHsTypeAndUse LH.HsHsSide =<< hsCallbackToTFn LH.HsHsSide cb)+ (Just $ do+ LH.addImports hsImportForRuntime+ HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyFHR.CCallback") <$>+ (LH.cppTypeToHsTypeAndUse LH.HsCSide =<< hsCallbackToTFn LH.HsCSide cb))+ (CustomConversion $ LH.sayLn =<< toHsCallbackCtorName cb)+ ConversionUnsupported -- Can't receive a callback from C++.++ callbackImplClassType =+ manualT $+ makeConversionSpec implClass $+ makeConversionSpecCpp implClass reqsGen++ implClass = callbackImplClassName cb++-- | Constructs a type value for a callback.+callbackT :: Callback -> Type+-- (Keep docs in sync with hs-boot.)+callbackT = manualT . makeConversion++-- | Returns the name of the outer, copyable C++ class for a callback.+callbackClassName :: Callback -> String+callbackClassName = fromExtName . callbackExtName++-- | Returns the name of the internal, non-copyable implementation C++ class for+-- a callback.+callbackImplClassName :: Callback -> String+callbackImplClassName = (++ "_impl") . fromExtName . callbackExtName++-- | Returns the name of the C++ binding function that creates a C++ callback+-- wrapper object from a function pointer to foreign code.+callbackFnName :: Callback -> String+callbackFnName = LC.externalNameToCpp . callbackExtName++sayCppExport :: LC.SayExportMode -> Callback -> LC.Generator ()+sayCppExport mode cb = do+ throws <- cppGetEffectiveCallbackThrows cb++ let className = callbackClassName cb+ implClassName = callbackImplClassName cb+ fnName = callbackFnName cb+ params = callbackParams cb+ paramTypes = map parameterType params+ paramCount = length params+ retType = callbackReturn cb+ fnType = fnT' params retType++ -- The function pointer we receive from foreign code will work with C-types,+ -- so determine what that function looks like.+ paramCTypes <- zipWith fromMaybe paramTypes <$> mapM LC.typeToCType paramTypes+ retCType <- fromMaybe retType <$> LC.typeToCType retType++ -- Add requirements specified manually by the callback, and for its parameter+ -- and return types.+ LC.addReqsM . mconcat . (callbackReqs cb:) =<< mapM LC.typeReqs (retType:paramTypes)++ let fnCType = fnT ((if throws then (++ [ptrT intT, ptrT $ ptrT voidT]) else id)+ paramCTypes)+ retCType+ fnPtrCType = ptrT fnCType++ case mode of+ LC.SayHeader -> do+ -- Render the class declarations into the header file.+ (sharedPtrReqs, sharedPtrStr) <- interfaceSharedPtr <$> LC.askInterface+ LC.addReqsM sharedPtrReqs++ LC.says ["\nclass ", implClassName, " {\n"]+ LC.say "public:\n"+ LC.says [" explicit ", implClassName, "("] >> LC.sayType Nothing fnPtrCType >>+ LC.say ", void(*)(void(*)()), bool);\n"+ LC.says [" ~", implClassName, "();\n"]+ LC.say " " >> LC.sayVar "operator()" Nothing fnType >> LC.say ";\n"+ LC.say "private:\n"+ LC.says [" ", implClassName, "(const ", implClassName, "&);\n"]+ LC.says [" ", implClassName, "& operator=(const ", implClassName, "&);\n"]+ LC.say "\n"+ LC.say " " >> LC.sayVar "f_" Nothing (constT fnPtrCType) >> LC.say ";\n"+ LC.say " void (*const release_)(void(*)());\n"+ LC.say " const bool releaseRelease_;\n"+ LC.say "};\n"++ LC.says ["\nclass ", className, " {\n"]+ LC.say "public:\n"+ LC.says [" ", className, "() {}\n"]+ LC.says [" explicit ", className, "(", implClassName, "* impl) : impl_(impl) {}\n"]+ LC.say " " >> LC.sayVar "operator()" Nothing fnType >> LC.say ";\n"+ LC.say " operator bool() const;\n"+ LC.say "private:\n"+ LC.says [" ", sharedPtrStr, "<", implClassName, "> impl_;\n"]+ LC.say "};\n"++ LC.SaySource -> do+ -- Render the classes' methods into the source file. First render the+ -- impl class's constructor.+ LC.says ["\n", implClassName, "::", implClassName, "("] >> LC.sayVar "f" Nothing fnPtrCType >>+ LC.say ", void (*release)(void(*)()), bool releaseRelease) :\n"+ LC.say " f_(f), release_(release), releaseRelease_(releaseRelease) {}\n"++ -- Then render the destructor.+ LC.says ["\n", implClassName, "::~", implClassName, "() {\n"]+ LC.say " if (release_) {\n"+ LC.say " release_(reinterpret_cast<void(*)()>(f_));\n"+ LC.say " if (releaseRelease_) {\n"+ LC.say " release_(reinterpret_cast<void(*)()>(release_));\n"+ LC.say " }\n"+ LC.say " }\n"+ LC.say "}\n"++ -- Render the impl operator() method, which does argument decoding and+ -- return value encoding and passes C++ values to underlying function+ -- poiner.+ --+ -- TODO Abstract the duplicated code here and in sayExportFn.+ paramCTypeMaybes <- mapM LC.typeToCType paramTypes+ retCTypeMaybe <- LC.typeToCType retType++ LC.sayFunction (implClassName ++ "::operator()")+ (zipWith3 (\pt ctm ->+ -- TManual needs special handling to determine whether a+ -- conversion is necessary. 'typeToCType' doesn't suffice+ -- because for TManual this check relies on the direction of+ -- the call. See the special case in 'sayCppArgRead' as+ -- well.+ let hasConversion = case pt of+ Internal_TManual s ->+ isJust $ conversionSpecCppConversionToCppExpr $+ conversionSpecCpp s+ _ -> isJust ctm+ in if hasConversion then LC.toArgNameAlt else LC.toArgName)+ paramTypes+ paramCTypeMaybes+ [1..paramCount])+ fnType $ Just $ do+ -- Convert arguments that aren't passed in directly.+ mapM_ (Function.sayCppArgRead Function.FromCpp) $+ zip3 [1..] paramTypes paramCTypeMaybes++ when throws $ do+ LC.says ["int ", LC.exceptionIdArgName, " = 0;\n"]+ LC.says ["void *", LC.exceptionPtrArgName, " = 0;\n"]++ -- Add an include for the exception support module to be able to call the+ -- C++ rethrow function.+ iface <- LC.askInterface+ currentModule <- LC.askModule+ case interfaceExceptionSupportModule iface of+ Just exceptionSupportModule ->+ when (exceptionSupportModule /= currentModule) $+ -- TODO Should this be includeStd?+ LC.addReqsM $ reqInclude $ includeLocal $ moduleHppPath exceptionSupportModule+ Nothing -> LC.abort $ "sayExportCallback: " ++ show iface +++ " uses exceptions, so it needs an exception support " +++ "module. Please use interfaceSetExceptionSupportModule."++ -- Invoke the function pointer into foreign code.+ let -- | Generates the call to the foreign language function pointer.+ sayCall :: LC.Generator ()+ sayCall = do+ LC.say "f_("+ Function.sayCppArgNames paramCount+ when throws $ do+ when (paramCount /= 0) $ LC.say ", "+ LC.says ["&", LC.exceptionIdArgName, ", &", LC.exceptionPtrArgName]+ LC.say ")"++ -- | Generates code to check whether an exception was thrown by the+ -- callback, and rethrows it in C++ if so.+ sayExceptionCheck :: LC.Generator ()+ sayExceptionCheck = when throws $ do+ LC.says ["if (", LC.exceptionIdArgName, " != 0) { ",+ LC.exceptionRethrowFnName, "(", LC.exceptionIdArgName, ", ",+ LC.exceptionPtrArgName, "); }\n"]++ case (retType, retCTypeMaybe) of+ (Internal_TVoid, Nothing) -> do+ sayCall >> LC.say ";\n"+ sayExceptionCheck+ (_, Nothing) -> do+ LC.sayVar "result" Nothing retType >> LC.say " = " >> sayCall >> LC.say ";\n"+ sayExceptionCheck+ LC.say "return result;\n"+ (Internal_TObj cls1,+ Just retCType'@(Internal_TPtr (Internal_TConst (Internal_TObj cls2))))+ | cls1 == cls2 -> do+ LC.sayVar "resultPtr" Nothing retCType' >> LC.say " = " >> sayCall >> LC.say ";\n"+ sayExceptionCheck+ LC.sayVar "result" Nothing retType >> LC.say " = *resultPtr;\n"+ LC.say "delete resultPtr;\n"+ LC.say "return result;\n"+ (Internal_TRef (Internal_TConst (Internal_TObj cls1)),+ Just (Internal_TPtr (Internal_TConst (Internal_TObj cls2)))) | cls1 == cls2 -> do+ LC.sayVar "resultPtr" Nothing retCType >> LC.say " = " >> sayCall >> LC.say ";\n"+ sayExceptionCheck+ LC.say "return *resultPtr;\n"+ (Internal_TRef (Internal_TObj cls1),+ Just (Internal_TPtr (Internal_TObj cls2))) | cls1 == cls2 -> do+ LC.sayVar "resultPtr" Nothing retCType >> LC.say " = " >> sayCall >> LC.say ";\n"+ sayExceptionCheck+ LC.say "return *resultPtr;\n"+ ts -> LC.abort $ concat+ ["sayExportCallback: Unexpected return types ", show ts, "."]++ -- Render the non-impl operator() method, which simply passes C++ values+ -- along to the impl object.+ LC.sayFunction (className ++ "::operator()")+ (map LC.toArgName [1..paramCount])+ fnType $ Just $ do+ case retType of+ Internal_TVoid -> LC.say "(*impl_)("+ _ -> LC.say "return (*impl_)("+ Function.sayCppArgNames paramCount+ LC.say ");\n"++ -- Render "operator bool", which detects whether the callback was not+ -- default-constructed with no actual impl object.+ LC.says [className, "::operator bool() const {\n"]+ LC.say "return static_cast<bool>(impl_);\n"+ LC.say "}\n"++ -- Render the function that creates a new callback object.+ let newCallbackFnType = fnT [ fnPtrCType+ , ptrT (fnT [ptrT $ fnT [] voidT] voidT)+ , boolT+ ] $+ Internal_TManual $+ makeConversionSpec ("<Internal " ++ implClassName ++ " pointer>") $+ makeConversionSpecCpp (implClassName ++ "*") (return mempty)+ LC.sayFunction fnName ["f", "release", "releaseRelease"] newCallbackFnType $ Just $+ LC.says ["return new ", implClassName, "(f, release, releaseRelease);\n"]++-- | Prints \"foreign import\" statements and an internal callback construction+-- function for a given 'Callback' specification. For example, for a callback+-- of 'LH.HsHsSide' type @Int -> String -> IO Int@, we will generate the+-- following bindings:+--+-- > foreign import ccall "wrapper" name'newFunPtr+-- > :: (CInt -> Ptr CChar -> IO CInt)+-- > -> IO (FunPtr (CInt -> Ptr CChar -> IO CInt))+-- >+-- > -- (This is an ad-hoc generated binding for C++ callback impl class constructor.)+-- > foreign import ccall "genpop__name_impl" name'newCallback+-- > :: FunPtr (CInt -> Ptr CChar -> IO CInt)+-- > -> FunPtr (FunPtr (IO ()) -> IO ())+-- > -> Bool+-- > -> IO (CCallback (CInt -> Ptr CChar -> IO CInt))+-- >+-- > name_newFunPtr :: (Int -> String -> IO Int) -> IO (FunPtr (CInt -> Ptr CChar -> IO CInt))+-- > name_newFunPtr f'hs = name'newFunPtr $ \excIdPtr excPtrPtr arg1 arg2 ->+-- > internalHandleCallbackExceptions excIdPtr excPtrPtr $+-- > coerceIntegral arg1 >>= \arg1' ->+-- > (...decode the C string) >>= \arg2' ->+-- > fmap coerceIntegral+-- > (f'hs arg1' arg2')+-- >+-- > name_new :: (Int -> String -> IO Int) -> IO (CCallback (CInt -> Ptr CChar -> IO CInt))+-- > name_new f = do+-- > f'p <- name_newFunPtr f+-- > name'newCallback f'p freeHaskellFunPtrFunPtr False+sayHsExport :: LH.SayExportMode -> Callback -> LH.Generator ()+sayHsExport mode cb =+ LH.withErrorContext ("generating callback " ++ show (callbackExtName cb)) $ do+ let name = callbackExtName cb+ params = callbackParams cb+ retType = callbackReturn cb+ hsNewFunPtrFnName <- toHsCallbackNewFunPtrFnName cb+ hsCtorName <- toHsCallbackCtorName cb+ let hsCtorName'newCallback = hsCtorName ++ "'newCallback"+ hsCtorName'newFunPtr = hsCtorName ++ "'newFunPtr"++ hsFnCType <- LH.cppTypeToHsTypeAndUse LH.HsCSide =<< hsCallbackToTFn LH.HsCSide cb+ hsFnHsType <- LH.cppTypeToHsTypeAndUse LH.HsHsSide =<< hsCallbackToTFn LH.HsHsSide cb++ let getWholeNewFunPtrFnType = do+ LH.addImports $ mconcat [hsImportForForeign, hsImportForPrelude]+ return $+ HsTyFun hsFnHsType $+ HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyP.IO") $+ HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyF.FunPtr") hsFnCType++ getWholeCtorType = do+ LH.addImports $ mconcat [hsImportForPrelude, hsImportForRuntime]+ return $+ HsTyFun hsFnHsType $+ HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyP.IO") $+ HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyFHR.CCallback") hsFnCType++ case mode of+ LH.SayExportForeignImports -> do+ LH.addImports $ mconcat [hsImportForForeign, hsImportForPrelude, hsImportForRuntime]+ let hsFunPtrType = HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyF.FunPtr") hsFnCType+ hsFunPtrImportType =+ HsTyFun hsFnCType $+ HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyP.IO") hsFunPtrType+ hsCallbackCtorImportType =+ HsTyFun hsFunPtrType $+ HsTyFun (HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyF.FunPtr") $+ HsTyFun (HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyF.FunPtr") $+ HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyP.IO") $+ HsTyCon $ Special HsUnitCon) $+ HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyP.IO") $+ HsTyCon $ Special HsUnitCon) $+ HsTyFun (HsTyCon $ UnQual $ HsIdent "HoppyP.Bool") $+ HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyP.IO") $+ HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyFHR.CCallback") hsFnCType++ LH.saysLn ["foreign import ccall \"wrapper\" ", hsCtorName'newFunPtr, " :: ",+ LH.prettyPrint hsFunPtrImportType]+ LH.saysLn ["foreign import ccall \"", LC.externalNameToCpp name, "\" ",+ hsCtorName'newCallback, " :: ", LH.prettyPrint hsCallbackCtorImportType]++ LH.SayExportDecls -> do+ LH.addExports [hsNewFunPtrFnName, hsCtorName]++ -- Generate the *_newFunPtr function.+ wholeNewFunPtrFnType <- getWholeNewFunPtrFnType+ let paramCount = length params+ argNames = map LH.toArgName [1..paramCount]+ argNames' = map (++ "'") argNames+ throws <- hsGetEffectiveCallbackThrows cb+ LH.addImports $ mconcat [hsImport1 "Prelude" "($)",+ hsImportForRuntime]+ LH.ln+ LH.saysLn [hsNewFunPtrFnName, " :: ", LH.prettyPrint wholeNewFunPtrFnType]+ LH.saysLn $ hsNewFunPtrFnName : " f'hs = " : hsCtorName'newFunPtr : " $" :+ case (if throws then (++ ["excIdPtr", "excPtrPtr"]) else id) argNames of+ [] -> []+ argNames'' -> [" \\", unwords argNames'', " ->"]+ LH.indent $ do+ when throws $ LH.sayLn "HoppyFHR.internalHandleCallbackExceptions excIdPtr excPtrPtr $"+ forM_ (zip3 params argNames argNames') $ \(p, argName, argName') ->+ Function.sayHsArgProcessing Function.FromCpp (parameterType p) argName argName'+ Function.sayHsCallAndProcessReturn Function.FromCpp retType $+ "f'hs" : map (' ':) argNames'++ -- Generate the *_new function.+ wholeCtorType <- getWholeCtorType+ LH.ln+ LH.saysLn [hsCtorName, " :: ", LH.prettyPrint wholeCtorType]+ LH.saysLn [hsCtorName, " f'hs = do"]+ LH.indent $ do+ LH.saysLn ["f'p <- ", hsNewFunPtrFnName, " f'hs"]+ LH.saysLn [hsCtorName'newCallback, " f'p HoppyFHR.freeHaskellFunPtrFunPtr HoppyP.False"]++ LH.SayExportBoot -> do+ LH.addExports [hsNewFunPtrFnName, hsCtorName]+ wholeNewFunPtrFnType <- getWholeNewFunPtrFnType+ wholeCtorType <- getWholeCtorType+ LH.ln+ LH.saysLn [hsNewFunPtrFnName, " :: ", LH.prettyPrint wholeNewFunPtrFnType]+ LH.ln+ LH.saysLn [hsCtorName, " :: ", LH.prettyPrint wholeCtorType]++-- | The name of the function that takes a Haskell function and wraps it in a+-- callback object. This is internal to the binding; normal users can pass+-- Haskell functions to be used as callbacks inplicitly.+toHsCallbackCtorName :: Callback -> LH.Generator String+toHsCallbackCtorName callback =+ LH.inFunction "toHsCallbackCtorName" $+ LH.addExtNameModule (callbackExtName callback) $ toHsCallbackCtorName' callback++-- | Pure version of 'toHsCallbackCtorName' that doesn't create a qualified+-- name.+toHsCallbackCtorName' :: Callback -> String+toHsCallbackCtorName' callback =+ LH.toHsFnName' $ toExtName $ fromExtName (callbackExtName callback) ++ "_new"++-- | The name of the function that takes a Haskell function with Haskell-side+-- types and wraps it in a 'Foreign.Ptr.FunPtr' that does appropriate+-- conversions to and from C-side types.+toHsCallbackNewFunPtrFnName :: Callback -> LH.Generator String+toHsCallbackNewFunPtrFnName callback =+ LH.inFunction "toHsCallbackNewFunPtrFnName" $+ LH.addExtNameModule (callbackExtName callback) $ toHsCallbackNewFunPtrFnName' callback++-- | Pure version of 'toHsCallbackNewFunPtrFnName' that doesn't create a qualified+-- name.+toHsCallbackNewFunPtrFnName' :: Callback -> String+toHsCallbackNewFunPtrFnName' callback =+ LH.toHsFnName' $ toExtName $ fromExtName (callbackExtName callback) ++ "_newFunPtr"++cppGetEffectiveCallbackThrows :: Callback -> LC.Generator Bool+cppGetEffectiveCallbackThrows cb = case callbackThrows cb of+ Just b -> return b+ Nothing -> moduleCallbacksThrow <$> LC.askModule >>= \case+ Just b -> return b+ Nothing -> interfaceCallbacksThrow <$> LC.askInterface++hsGetEffectiveCallbackThrows :: Callback -> LH.Generator Bool+hsGetEffectiveCallbackThrows cb = case callbackThrows cb of+ Just b -> return b+ Nothing -> moduleCallbacksThrow <$> LH.askModule >>= \case+ Just b -> return b+ Nothing -> interfaceCallbacksThrow <$> LH.askInterface++-- | Constructs the function type for a callback. A callback that throws has+-- additional parameters.+--+-- Keep this in sync with 'hsCallbackToTFn'.+cppCallbackToTFn :: Callback -> LC.Generator Type+cppCallbackToTFn cb = do+ throws <- mayThrow+ return $ Internal_TFn ((if throws then addExcParams else id) $ callbackParams cb)+ (callbackReturn cb)++ where mayThrow = case callbackThrows cb of+ Just t -> return t+ Nothing -> moduleCallbacksThrow <$> LC.askModule >>= \mt -> case mt of+ Just t -> return t+ Nothing -> interfaceCallbacksThrow <$> LC.askInterface++ addExcParams = (++ [toParameter $ ptrT intT, toParameter $ ptrT $ ptrT voidT])++-- | Constructs the function type for a callback. For Haskell, the type depends+-- on the side; the C++ side has additional parameters.+--+-- Keep this in sync with 'cppCallbackToTFn'.+hsCallbackToTFn :: LH.HsTypeSide -> Callback -> LH.Generator Type+hsCallbackToTFn side cb = do+ needsExcParams <- case side of+ LH.HsCSide -> mayThrow+ LH.HsHsSide -> return False+ return $ Internal_TFn ((if needsExcParams then addExcParams else id) $ callbackParams cb)+ (callbackReturn cb)++ where mayThrow = case callbackThrows cb of+ Just t -> return t+ Nothing -> moduleCallbacksThrow <$> LH.askModule >>= \mt -> case mt of+ Just t -> return t+ Nothing -> interfaceCallbacksThrow <$> LH.askInterface++ addExcParams = (++ [toParameter $ ptrT intT, toParameter $ ptrT $ ptrT voidT])
+ src/Foreign/Hoppy/Generator/Spec/Callback.hs-boot view
@@ -0,0 +1,27 @@+-- This file is part of Hoppy.+--+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net>+--+-- 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 <http://www.gnu.org/licenses/>.++module Foreign.Hoppy.Generator.Spec.Callback (+ callbackT,+ ) where++import Foreign.Hoppy.Generator.Spec.Base (Type)++data Callback++-- | Constructs a type value for a callback.+callbackT :: Callback -> Type
+ src/Foreign/Hoppy/Generator/Spec/Class.hs view
@@ -0,0 +1,2116 @@+-- This file is part of Hoppy.+--+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net>+--+-- 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 <http://www.gnu.org/licenses/>.++-- | 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 ["<Class ", show (classExtName cls), " ", show (classIdentifier cls), ">"]++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 ["<ClassVariable ",+ show $ classVarExtName v, " ",+ show $ classVarCName v, " ",+ show $ classVarStatic v, " ",+ show $ classVarType v, ">"]++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 ["<Ctor ", show (ctorExtName ctor), " ", show (ctorParams ctor), ">"]++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 ["<Method ", show (methodExtName method), " ",+ case methodImpl method of+ RealMethod name -> 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
+ src/Foreign/Hoppy/Generator/Spec/Class.hs-boot view
@@ -0,0 +1,72 @@+-- This file is part of Hoppy.+--+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net>+--+-- 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 <http://www.gnu.org/licenses/>.++module Foreign.Hoppy.Generator.Spec.Class (+ Class,+ classExtName,+ classIdentifier,+ classReqs,+ classConversion,+ ClassConversion (..),+ ClassHaskellConversion (..),+ toHsValueClassName,+ toHsWithValuePtrName,+ toHsPtrClassName,+ toHsCastMethodName,+ toHsDataTypeName,+ toHsDataCtorName,+ ) where++import {-# SOURCE #-} Foreign.Hoppy.Generator.Spec.Base (Constness, ExtName, Identifier, Reqs)+import {-# SOURCE #-} qualified Foreign.Hoppy.Generator.Language.Haskell as LH+import Language.Haskell.Syntax (HsType)++data Class++instance Eq Class+instance Ord Class+instance Show Class++classExtName :: Class -> ExtName++classIdentifier :: Class -> Identifier++classReqs :: Class -> Reqs++classConversion :: Class -> ClassConversion++data ClassConversion = ClassConversion+ { classHaskellConversion :: ClassHaskellConversion+ }++data ClassHaskellConversion = ClassHaskellConversion+ { classHaskellConversionType :: Maybe (LH.Generator HsType)+ , classHaskellConversionToCppFn :: Maybe (LH.Generator ())+ , classHaskellConversionFromCppFn :: Maybe (LH.Generator ())+ }++toHsValueClassName :: Class -> LH.Generator String++toHsWithValuePtrName :: Class -> LH.Generator String++toHsPtrClassName :: Constness -> Class -> LH.Generator String++toHsCastMethodName :: Constness -> Class -> LH.Generator String++toHsDataTypeName :: Constness -> Class -> LH.Generator String++toHsDataCtorName :: LH.Managed -> Constness -> Class -> LH.Generator String
src/Foreign/Hoppy/Generator/Spec/ClassFeature.hs view
@@ -1,6 +1,6 @@ -- This file is part of Hoppy. ----- Copyright 2015-2018 Bryan Gardiner <bog@khumba.net>+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net> -- -- 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@@ -28,6 +28,7 @@ import Data.Monoid (mempty) #endif import Foreign.Hoppy.Generator.Spec.Base+import Foreign.Hoppy.Generator.Spec.Class import Foreign.Hoppy.Generator.Types -- | Sets of functionality that can be stamped onto a class with
src/Foreign/Hoppy/Generator/Spec/Conversion.hs view
@@ -1,6 +1,6 @@ -- This file is part of Hoppy. ----- Copyright 2015-2018 Bryan Gardiner <bog@khumba.net>+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net> -- -- 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@@ -17,8 +17,11 @@ {-# LANGUAGE CPP #-} --- | The primary data types for specifying C++ interfaces.+-- | Conversions for C++ classes. --+-- TODO Refactor this, 'cause the TManual conversion stuff is in Base. (Not a+-- high priority, this /is/ a private module.)+-- -- 'Show' instances in this module produce strings of the form @\"\<TypeOfObject -- nameOfObject otherInfo...\>\"@. They can be used in error messages without -- specifying a noun separately, i.e. write @show cls@ instead of @\"the class@@ -34,10 +37,15 @@ #endif import Foreign.Hoppy.Generator.Language.Haskell import Foreign.Hoppy.Generator.Spec.Base+import Foreign.Hoppy.Generator.Spec.Class import Foreign.Hoppy.Generator.Types -- | Modifies a class's 'ClassConversion' structure by setting all languages--- to use 'ClassConversionToHeap'.+-- to copy objects to the heap when being passed out of C++. Lifetimes of the+-- resulting objects must be managed by code in the foreign language.+--+-- Calling this on a class makes 'objT' behave like 'objToHeapT' for values+-- being passed out of C++. classSetConversionToHeap :: Class -> Class classSetConversionToHeap cls = case classFindCopyCtor cls of Just _ ->@@ -47,7 +55,12 @@ Nothing -> error $ "classSetConversionToHeap: " ++ show cls ++ " must be copyable." -- | Modifies a class's 'ClassConversion' structure by setting all languages--- that support garbage collection to use 'ClassConversionToGc'.+-- that support garbage collection to copy objects to the heap when being passed+-- out of C++, and put those objects under the care of the foreign language's+-- garbage collector.+--+-- Calling this on a class makes 'objT' behave like 'toGcT' for values being+-- passed out of C++. classSetConversionToGc :: Class -> Class classSetConversionToGc cls = case classFindCopyCtor cls of Just _ ->
+ src/Foreign/Hoppy/Generator/Spec/Enum.hs view
@@ -0,0 +1,524 @@+-- This file is part of Hoppy.+--+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net>+--+-- 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 <http://www.gnu.org/licenses/>.++-- | Interface for defining bindings to C++ enumerations.+--+-- In generated Haskell code:+--+-- An enum gets a single algebraic data type with data constructors for each of+-- the values defined in the interface. If the enum has an unknown value name+-- defined, then an additional data constructor is generated that holds a+-- numeric value, and this constructor is used whenever numeric values for which+-- no name is explicitly defined are encountered (otherwise, 'error' is called).+--+-- From the runtime module, a @CppEnum@ instance is generated for the type, and+-- if the enum is declared to permit bit operations, then a 'Data.Bits.Bits'+-- instance is also generated. 'Eq' and 'Ord' instances are generated that+-- compare numeric values.+module Foreign.Hoppy.Generator.Spec.Enum (+ -- * Data type+ CppEnum, enumT,+ -- * Construction+ makeEnum, makeAutoEnum, IsAutoEnumValue (..),+ -- * Properties+ enumExtName,+ enumIdentifier,+ enumNumericType, enumSetNumericType,+ enumValues,+ enumReqs,+ enumAddendum,+ enumValuePrefix, enumSetValuePrefix,+ enumAddEntryNameOverrides,+ enumGetOverriddenEntryName,+ IsEnumUnknownValueEntry (..),+ enumUnknownValueEntry, enumSetUnknownValueEntry, enumSetNoUnknownValueEntry,+ enumUnknownValueEntryDefault,+ enumHasBitOperations, enumSetHasBitOperations,+ -- * C++ generator+ cppGetEvaluatedEnumData,+ -- * Haskell generator+ hsGetEvaluatedEnumData,+ -- ** Names+ toHsEnumTypeName, toHsEnumTypeName',+ toHsEnumCtorName, toHsEnumCtorName',+ ) where++import Control.Arrow ((&&&), (***))+import Control.Monad (forM, forM_, when)+import Control.Monad.Except (throwError)+import Data.Function (on)+import qualified Data.Map as M+import Foreign.Hoppy.Generator.Common (butLast, capitalize, for)+import Foreign.Hoppy.Generator.Spec.Base+import qualified Foreign.Hoppy.Generator.Language.Cpp as LC+import qualified Foreign.Hoppy.Generator.Language.Haskell as LH+import Foreign.Hoppy.Generator.Override (addOverrideMap, overriddenMapLookup, plainMap)+import Foreign.Hoppy.Generator.Types (manualT)+import Foreign.Hoppy.Generator.Util (splitIntoWords)+import GHC.Stack (HasCallStack)+import Language.Haskell.Syntax (+ HsName (HsIdent),+ HsQName (UnQual),+ HsType (HsTyCon),+ )++-- | A C++ enum declaration.+--+-- See 'Foreign.Hoppy.Generator.Spec.EnumInfo'.+data CppEnum = CppEnum+ { enumExtName :: ExtName+ -- ^ The enum's external name.+ , enumIdentifier :: Identifier+ -- ^ The identifier used to refer to the enum.+ , enumNumericType :: Maybe Type+ -- ^ An optional, explicit numeric type provided for the enum's values, that+ -- matches what the C++ compiler uses. Hoppy will use+ -- 'Foreign.Hoppy.Generator.Hook.Hooks' to compute this automatically, if+ -- not given manually. This does not need to be provided. If absent+ -- (default), then Hoppy will calculate the enum's numeric type on its own,+ -- using a C++ compiler. If this is present however, Hoppy will use it, and+ -- additionally validate it against what the C++ compiler thinks, if+ -- validation is enabled (see 'interfaceValidateEnumTypes').+ , enumValues :: EnumValueMap+ -- ^ The numeric values and names of the enum entires.+ , enumReqs :: Reqs+ -- ^ Requirements for bindings to access this enum. Currently unused, but+ -- will be in the future.+ , enumAddendum :: Addendum+ -- ^ The enum's addendum.+ , enumValuePrefix :: String+ -- ^ The prefix applied to value names ('enumValues') when determining the+ -- names of values in foreign languages. This defaults to the external name+ -- of the enum, plus an underscore.+ --+ -- See 'enumSetValuePrefix'.+ , enumUnknownValueEntry :: Maybe EnumEntryWords+ -- ^ A name (a list of words, a la the fields in 'EnumValueMap') for an+ -- optional fallback enum "entry" in generated bindings for holding unknown+ -- values. See 'enumUnknownValueEntryDefault'.+ --+ -- When this is a @Just@, then the generated foreign binding gets an extra+ -- entry that takes an argument holding an arbitrary numeric value (an extra+ -- data constructor in Haskell), and this value is used whenever an unknown+ -- value is seen.+ --+ -- When this is @Nothing@, the enum will not support unknown values.+ -- @toCppEnum@ in the @Foreign.Hoppy.Runtime.CppEnum@ typeclass, as well as+ -- calls or returns from C++ that pass a value not defined in the interface,+ -- will raise an 'error'.+ --+ -- Enums that have this set to @Nothing@ should also have+ -- 'enumHasBitOperations' set to false, to avoid potential errors at+ -- runtime; see that function's documentation.+ --+ -- The 'enumValuePrefix' applies to this name, just as it does to other enum+ -- entries.+ , enumHasBitOperations :: Bool+ -- ^ Whether generated bindings should support bitwise operations on the+ -- enum. This defaults to true.+ --+ -- It is not recommended to disable the unknown value entry+ -- ('enumUnknownValueEntry') while having this be true, because any+ -- computation involving enum values not explicitly defined will cause a+ -- runtime error. This includes undefined combinations of defined values.+ }++instance Eq CppEnum where+ (==) = (==) `on` enumExtName++instance Show CppEnum where+ show e = concat ["<Enum ", show (enumExtName e), " ", show (enumIdentifier e), ">"]++instance Exportable CppEnum where+ sayExportCpp _ _ = return () -- Nothing to do for the C++ side of an enum.++ sayExportHaskell = sayHsExport++ getExportEnumInfo e =+ Just EnumInfo+ { enumInfoExtName = enumExtName e+ , enumInfoIdentifier = enumIdentifier e+ , enumInfoNumericType = enumNumericType e+ , enumInfoReqs = enumReqs e+ , enumInfoValues = enumValues e+ }++instance HasExtNames CppEnum where+ getPrimaryExtName = enumExtName++instance HasReqs CppEnum where+ getReqs = enumReqs+ setReqs reqs e = e { enumReqs = reqs }++instance HasAddendum CppEnum where+ getAddendum = enumAddendum+ setAddendum addendum e = e { enumAddendum = addendum }++-- | Sets an explicit numeric type for the enum. See 'enumNumericType'.+enumSetNumericType :: Maybe Type -> CppEnum -> CppEnum+enumSetNumericType maybeType enum = enum { enumNumericType = maybeType }++-- | The default value for 'enumUnknownValueEntry'. This is @[\"Unknown\"]@.+enumUnknownValueEntryDefault :: EnumEntryWords+enumUnknownValueEntryDefault = ["Unknown"]++-- | Creates a binding for a C++ enum.+--+-- The numeric values of each of the enum's entries must be specified manually+-- using this function. To have these determined automatically, instead use+-- 'makeAutoEnum'.+makeEnum ::+ Identifier -- ^ 'enumIdentifier'+ -> Maybe ExtName+ -- ^ An optional external name; will be automatically derived from+ -- the identifier if absent.+ -> [(Integer, EnumEntryWords)]+ -- ^ A list of (numeric value, symbolic name) pairs describing enum entries to+ -- generate bindings for. Each symbolic name is a list of words, which will+ -- be combined into a single identifier of appropriate naming style for the+ -- target language (title case, for Haskell) with 'enumValuePrefix' prepended.+ -> CppEnum+makeEnum identifier maybeExtName entries =+ let extName = extNameOrIdentifier identifier maybeExtName+ in CppEnum+ extName+ identifier+ Nothing+ (let entries' = for entries $ \(num, words') -> (words', EnumValueManual num)+ entryNames = map fst entries'+ in EnumValueMap+ { enumValueMapNames = entryNames+ , enumValueMapForeignNames = plainMap $ M.fromList $ map (id &&& id) entryNames+ , enumValueMapValues = M.fromList entries'+ })+ mempty+ mempty+ (fromExtName extName ++ "_")+ (Just enumUnknownValueEntryDefault)+ True++-- | Creates a binding for a C++ enum.+--+-- An enum created using this function will determine its entries' numeric+-- values automatically when the generator is run, by compiling a C++ program.+makeAutoEnum ::+ IsAutoEnumValue v+ => Identifier -- ^ 'enumIdentifier'+ -> Maybe ExtName+ -- ^ An optional external name; will be automatically derived from the+ -- identifier if absent.+ -> Bool+ -- ^ Is the enum scoped (@enum class@ or @enum struct@)? That is, are its+ -- entries scoped underneath its name, rather than being at the same level as+ -- its name (as with just @enum@).+ -> [v]+ -- ^ A list of enum entries to calculate and generate bindings for. See+ -- 'IsAutoEnumValue'.+ -> CppEnum+makeAutoEnum identifier maybeExtName scoped entries =+ let extName = extNameOrIdentifier identifier maybeExtName+ in CppEnum+ extName+ identifier+ Nothing+ (let namespaceForValues =+ if scoped+ then identifier+ else makeIdentifier $ butLast $ identifierParts identifier+ entries' =+ map (fmap (\name -> namespaceForValues `mappend` ident name) .+ toAutoEnumValue)+ entries+ entryNames = map fst entries'+ in EnumValueMap+ { enumValueMapNames = entryNames+ , enumValueMapForeignNames = plainMap $ M.fromList $ map (id &&& id) entryNames+ , enumValueMapValues = M.map EnumValueAuto $ M.fromList entries'+ })+ mempty+ mempty+ (fromExtName extName ++ "_")+ (Just enumUnknownValueEntryDefault)+ True++-- | Represents a mapping to an automatically evaluated C++ enum entry.+--+-- The @('EnumEntryWords', String)@ instance is the canonical one, with+-- 'toAutoEnumValue' defined as @id@. The string on the right is the C++ name+-- of the entry, and the list of strings on the left are the words from which to+-- generate foreign binding's entries.+--+-- The @String@ instance takes the C++ name of the entry, and splits it into+-- words via 'splitIntoWords'.+class IsAutoEnumValue a where+ toAutoEnumValue :: a -> (EnumEntryWords, String)++instance IsAutoEnumValue (EnumEntryWords, String) where+ toAutoEnumValue = id++instance IsAutoEnumValue String where+ toAutoEnumValue = splitIntoWords &&& id++-- | Adds overrides for some of an enum's entry names, in a specific language.+enumAddEntryNameOverrides :: IsAutoEnumValue v => ForeignLanguage -> [(v, v)] -> CppEnum -> CppEnum+enumAddEntryNameOverrides lang nameOverrides enum = enum { enumValues = enumValues' }+ where enumValues' =+ (enumValues enum)+ { enumValueMapForeignNames =+ addOverrideMap lang overrideMap $ enumValueMapForeignNames $ enumValues enum }+ overrideMap = M.fromList $ map (toEntryName *** toEntryName) nameOverrides+ toEntryName = fst . toAutoEnumValue++-- | Retrieves the name for an enum entry in a specific foreign language.+enumGetOverriddenEntryName :: ForeignLanguage -> CppEnum -> EnumEntryWords -> EnumEntryWords+enumGetOverriddenEntryName lang enum words' =+ case overriddenMapLookup lang words' $ enumValueMapForeignNames $ enumValues enum of+ Just words'' -> words''+ Nothing ->+ error $ "enumGetOverriddenEntryName: Entry with name " ++ show words' +++ " not found in " ++ show enum ++ "."++-- | Sets the prefix applied to the names of enum values' identifiers in foreign+-- languages.+--+-- See 'enumValuePrefix'.+enumSetValuePrefix :: String -> CppEnum -> CppEnum+enumSetValuePrefix prefix enum = enum { enumValuePrefix = prefix }++-- | Sets the entry name (a list of words, a la the fields in 'EnumValueMap')+-- for the fallback enum entry that holds unknown values.+--+-- Set 'enumUnknownValueEntry', 'enumSetNoUnknownValueEntry'.+enumSetUnknownValueEntry :: IsEnumUnknownValueEntry a => a -> CppEnum -> CppEnum+enumSetUnknownValueEntry name enum =+ enum { enumUnknownValueEntry = Just $ toEnumUnknownValueEntry name }++-- | Sets an enum to have no unknown value entry.+--+-- Set 'enumUnknownValueEntry', 'enumSetUnknownValueEntry'.+enumSetNoUnknownValueEntry :: CppEnum -> CppEnum+enumSetNoUnknownValueEntry enum =+ enum { enumUnknownValueEntry = Nothing }++-- | Values that can be used as a name for an enum's unknown value entry. See+-- 'enumUnknownValueEntry'.+class IsEnumUnknownValueEntry a where+ -- | Converts a value to a list of words to use for an enum's unknown entry+ -- name.+ toEnumUnknownValueEntry :: a -> EnumEntryWords++instance IsEnumUnknownValueEntry EnumEntryWords where+ toEnumUnknownValueEntry = id++instance IsEnumUnknownValueEntry String where+ toEnumUnknownValueEntry = splitIntoWords++-- | Sets whether generated bindings will support bitwise operations on the+-- enum.+--+-- See 'enumHasBitOperations'.+enumSetHasBitOperations :: Bool -> CppEnum -> CppEnum+enumSetHasBitOperations b enum = enum { enumHasBitOperations = b }++makeConversion :: CppEnum -> ConversionSpec+makeConversion e =+ (makeConversionSpec (show e) cpp)+ { conversionSpecHaskell = Just hs }+ where cpp =+ makeConversionSpecCpp (LC.renderIdentifier $ enumIdentifier e)+ (return $ enumReqs e)++ hs =+ makeConversionSpecHaskell+ (HsTyCon . UnQual . HsIdent <$> toHsEnumTypeName e)+ (Just $ do evaluatedData <- hsGetEvaluatedEnumData $ enumExtName e+ LH.cppTypeToHsTypeAndUse LH.HsCSide $ evaluatedEnumType evaluatedData)+ (CustomConversion $ do+ LH.addImports $ mconcat [hsImport1 "Prelude" "(.)",+ hsImportForPrelude,+ hsImportForRuntime]+ LH.sayLn "HoppyP.return . HoppyFHR.fromCppEnum")+ (CustomConversion $ do+ LH.addImports $ mconcat [hsImport1 "Prelude" "(.)",+ hsImportForPrelude,+ hsImportForRuntime]+ LH.sayLn "HoppyP.return . HoppyFHR.toCppEnum")++-- | Constructs a type value for an enum.+enumT :: CppEnum -> Type+-- (Keep docs in sync with hs-boot.)+enumT = manualT . makeConversion++sayHsExport :: LH.SayExportMode -> CppEnum -> LH.Generator ()+sayHsExport mode enum =+ LH.withErrorContext ("generating enum " ++ show (enumExtName enum)) $+ case mode of+ -- Nothing to import from the C++ side of an enum.+ LH.SayExportForeignImports -> return ()++ LH.SayExportDecls -> do+ hsTypeName <- toHsEnumTypeName enum+ evaluatedData <- hsGetEvaluatedEnumData $ enumExtName enum+ numericType <- LH.cppTypeToHsTypeAndUse LH.HsCSide $ evaluatedEnumType evaluatedData+ let evaluatedValueMap = evaluatedEnumValueMap evaluatedData+ evaluatedValues <- forM (enumValueMapNames $ enumValues enum) $ \name ->+ case M.lookup name evaluatedValueMap of+ Just value -> return (name, value)+ Nothing -> throwError $ "Couldn't find evaluated value for " ++ show name+ values :: [(Integer, String)] <- forM evaluatedValues $ \(entryName, value) -> do+ let entryName' = enumGetOverriddenEntryName Haskell enum entryName+ ctorName <- toHsEnumCtorName enum entryName'+ return (value, ctorName)+ maybeUnknownValueCtorName <- forM (enumUnknownValueEntry enum) $ toHsEnumCtorName enum+ LH.addImports $ mconcat [hsImport1 "Prelude" "(==)",+ hsImportForPrelude,+ hsImportForRuntime]++ -- Print out the data declaration.+ LH.ln+ LH.addExport' hsTypeName+ LH.saysLn ["data ", hsTypeName, " ="]+ LH.indent $ do+ forM_ (zip (False:repeat True) values) $ \(cont, (_, hsCtorName)) ->+ LH.saysLn [if cont then "| " else "", hsCtorName]+ -- Only print an unknown value ctor if one has been requested.+ forM_ maybeUnknownValueCtorName $ \unknownValueCtorName ->+ LH.saysLn ["| ", unknownValueCtorName, " (", LH.prettyPrint numericType, ")"]+ LH.sayLn "deriving (HoppyP.Show)"++ -- Print out the (runtime) CppEnum instance.+ LH.ln+ LH.saysLn ["instance HoppyFHR.CppEnum (", LH.prettyPrint numericType, ") ", hsTypeName,+ " where"]+ LH.indent $ do+ forM_ values $ \(num, hsCtorName) ->+ LH.saysLn ["fromCppEnum ", hsCtorName, " = ", show num]+ forM_ maybeUnknownValueCtorName $ \unknownValueCtorName ->+ LH.saysLn ["fromCppEnum (", unknownValueCtorName, " n) = n"]+ LH.ln+ -- We pass the values list through a map here to only keep the first+ -- constructor mapped to each numeric value, otherwise we'd write+ -- duplicate cases.+ forM_ (M.toList $ M.fromListWith const values) $ \(num, hsCtorName) ->+ LH.saysLn ["toCppEnum (", show num, ") = ", hsCtorName]+ case maybeUnknownValueCtorName of+ Just unknownValueCtorName -> LH.saysLn ["toCppEnum n = ", unknownValueCtorName, " n"]+ Nothing -> do+ LH.addImports $ hsImports "Prelude" ["($)", "(++)"]+ LH.saysLn ["toCppEnum n' = HoppyP.error $ ",+ show (concat ["Unknown ", hsTypeName, " numeric value: "]),+ " ++ HoppyP.show n'"]++ -- Print out Eq and Ord instances.+ LH.ln+ LH.saysLn ["instance HoppyP.Eq ", hsTypeName, " where"]+ LH.indent $+ LH.sayLn "x == y = HoppyFHR.fromCppEnum x == HoppyFHR.fromCppEnum y"+ LH.ln+ LH.saysLn ["instance HoppyP.Ord ", hsTypeName, " where"]+ LH.indent $+ LH.sayLn "compare x y = HoppyP.compare (HoppyFHR.fromCppEnum x) (HoppyFHR.fromCppEnum y)"++ when (enumHasBitOperations enum) $ do+ LH.addImports $ mconcat [hsImports "Prelude" ["($)", "(.)"],+ hsImports "Data.Bits" ["(.&.)", "(.|.)"],+ hsImportForBits]+ LH.saysLn ["instance HoppyDB.Bits ", hsTypeName, " where"]+ LH.indent $ do+ let fun1 f =+ LH.saysLn [f, " x = HoppyFHR.toCppEnum $ HoppyDB.",+ f, " $ HoppyFHR.fromCppEnum x"]+ fun1Int f =+ LH.saysLn [f, " x i = HoppyFHR.toCppEnum $ HoppyDB.",+ f, " (HoppyFHR.fromCppEnum x) i"]+ fun2 f =+ LH.saysLn [f, " x y = HoppyFHR.toCppEnum $ HoppyDB.",+ f, " (HoppyFHR.fromCppEnum x) (HoppyFHR.fromCppEnum y)"]+ op2 op =+ LH.saysLn ["x ", op, " y = HoppyFHR.toCppEnum ",+ "(HoppyFHR.fromCppEnum x ", op, " HoppyFHR.fromCppEnum y)"]+ op2 ".&."+ op2 ".|."+ fun2 "xor"+ fun1 "complement"+ fun1Int "shift"+ fun1Int "rotate"+ LH.sayLn "bitSize x = case HoppyDB.bitSizeMaybe x of"+ LH.indent $ do+ LH.sayLn " HoppyP.Just n -> n"+ -- Same error message as the prelude here:+ LH.sayLn " HoppyP.Nothing -> HoppyP.error \"bitSize is undefined\""+ LH.sayLn "bitSizeMaybe = HoppyDB.bitSizeMaybe . HoppyFHR.fromCppEnum"+ LH.sayLn "isSigned = HoppyDB.isSigned . HoppyFHR.fromCppEnum"+ LH.sayLn "testBit x i = HoppyDB.testBit (HoppyFHR.fromCppEnum x) i"+ LH.sayLn "bit = HoppyFHR.toCppEnum . HoppyDB.bit"+ LH.sayLn "popCount = HoppyDB.popCount . HoppyFHR.fromCppEnum"++ LH.SayExportBoot -> do+ hsTypeName <- toHsEnumTypeName enum+ evaluatedData <- hsGetEvaluatedEnumData $ enumExtName enum+ numericType <- LH.cppTypeToHsTypeAndUse LH.HsCSide $ evaluatedEnumType evaluatedData+ LH.addImports $ mconcat [hsImportForPrelude, hsImportForRuntime]+ LH.addExport hsTypeName+ LH.ln+ LH.saysLn ["data ", hsTypeName]+ LH.saysLn ["instance HoppyFHR.CppEnum (", LH.prettyPrint numericType, ") ", hsTypeName]+ LH.saysLn ["instance HoppyP.Eq ", hsTypeName]+ LH.saysLn ["instance HoppyP.Ord ", hsTypeName]+ LH.saysLn ["instance HoppyP.Show ", hsTypeName]+ when (enumHasBitOperations enum) $ do+ LH.addImports hsImportForBits+ LH.saysLn ["instance HoppyDB.Bits ", hsTypeName]++-- | Reads evaluated data for the named enum from the C++ generator environment.+cppGetEvaluatedEnumData :: HasCallStack => ExtName -> LC.Generator EvaluatedEnumData+cppGetEvaluatedEnumData extName = do+ iface <- LC.askInterface+ return $ interfaceGetEvaluatedEnumData iface extName++-- | Reads evaluated data for the named enum from the Haskell generator+-- environment.+hsGetEvaluatedEnumData :: HasCallStack => ExtName -> LH.Generator EvaluatedEnumData+hsGetEvaluatedEnumData extName = do+ iface <- LH.askInterface+ return $ interfaceGetEvaluatedEnumData iface extName++-- | Returns the Haskell name for an enum.+--+-- TODO Clarify, and split into type and data ctor names.+toHsEnumTypeName :: CppEnum -> LH.Generator String+toHsEnumTypeName enum =+ LH.inFunction "toHsEnumTypeName" $+ LH.addExtNameModule (enumExtName enum) $ toHsEnumTypeName' enum++-- | Pure version of 'toHsEnumTypeName' that doesn't create a qualified name.+toHsEnumTypeName' :: CppEnum -> String+toHsEnumTypeName' = LH.toHsTypeName' Nonconst . enumExtName++-- | Constructs the data constructor name for a value in an enum. Like C++ and+-- unlike say Java, Haskell enum values aren't in a separate enum-specific+-- namespace, so we prepend the enum name to the value name to get the data+-- constructor name. The value name is a list of words.+toHsEnumCtorName :: CppEnum -> EnumEntryWords -> LH.Generator String+toHsEnumCtorName enum words' =+ LH.inFunction "toHsEnumCtorName" $+ LH.addExtNameModule (enumExtName enum) $ toHsEnumCtorName' enum words'++-- | Pure version of 'toHsEnumCtorName' that doesn't create a qualified name.+toHsEnumCtorName' :: CppEnum -> EnumEntryWords -> String+toHsEnumCtorName' enum words' =+ concat $ enumValuePrefix enum : map capitalize words'
+ src/Foreign/Hoppy/Generator/Spec/Enum.hs-boot view
@@ -0,0 +1,27 @@+-- This file is part of Hoppy.+--+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net>+--+-- 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 <http://www.gnu.org/licenses/>.++module Foreign.Hoppy.Generator.Spec.Enum (+ enumT,+ ) where++import Foreign.Hoppy.Generator.Spec.Base (Type)++data CppEnum++-- | Constructs a type value for an enum.+enumT :: CppEnum -> Type
+ src/Foreign/Hoppy/Generator/Spec/Function.hs view
@@ -0,0 +1,905 @@+-- This file is part of Hoppy.+--+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net>+--+-- 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 <http://www.gnu.org/licenses/>.++{-# LANGUAGE ViewPatterns #-}++-- | Interface for defining bindings to C++ functions.+module Foreign.Hoppy.Generator.Spec.Function (+ -- * Data type+ Function, fnT, fnT',+ -- * Construction+ makeFn,+ -- * Properties+ fnExtName,+ fnCName,+ fnPurity,+ fnParams,+ fnReturn,+ fnReqs,+ fnAddendum,+ fnExceptionHandlers,+ -- * Code generators+ CallDirection (..),+ -- ** C++ generator+ CppCallType (..),+ sayCppArgRead,+ sayCppArgNames,+ -- * Internal+ -- ** C++ generator+ sayCppExportFn,+ -- ** Haskell generator+ sayHsExportFn,+ sayHsArgProcessing,+ sayHsCallAndProcessReturn,+ ) where++import Control.Monad (forM_, unless, when)+import Control.Monad.Except (throwError)+import Data.Function (on)+import Data.List (intersperse)+import Data.Maybe (catMaybes, fromMaybe, isJust)+import Foreign.Hoppy.Generator.Common (fromMaybeM)+import qualified Foreign.Hoppy.Generator.Language.Cpp as LC+import qualified Foreign.Hoppy.Generator.Language.Haskell as LH+import {-# SOURCE #-} qualified Foreign.Hoppy.Generator.Spec.Class as Class+import Foreign.Hoppy.Generator.Spec.Base+import Foreign.Hoppy.Generator.Types (constT, intT, objT, objToHeapT, ptrT, refT, voidT)+import Language.Haskell.Syntax (+ HsContext,+ HsName (HsIdent),+ HsQName (UnQual),+ HsQualType (HsQualType),+ HsType (HsTyApp, HsTyCon, HsTyFun, HsTyVar),+ )++-- | A C++ function declaration.+--+-- Use this data type's 'HasReqs' instance to make the function accessible. You+-- do not need to add requirements for parameter or return types.+data Function = Function+ { fnCName :: FnName Identifier+ -- ^ The identifier used to call the function.+ , fnExtName :: ExtName+ -- ^ The function's external name.+ , fnPurity :: Purity+ -- ^ Whether the function is pure.+ , fnParams :: [Parameter]+ -- ^ The function's parameters.+ , fnReturn :: Type+ -- ^ The function's return type.+ , fnReqs :: Reqs+ -- ^ Requirements for bindings to access this function.+ , fnExceptionHandlers :: ExceptionHandlers+ -- ^ Exceptions that the function might throw.+ , fnAddendum :: Addendum+ -- ^ The function's addendum.+ }++instance Eq Function where+ (==) = (==) `on` fnExtName++instance Show Function where+ show fn =+ concat ["<Function ", show (fnExtName fn), " ", show (fnCName fn),+ show (fnParams fn), " ", show (fnReturn fn), ">"]++instance Exportable Function where+ sayExportCpp = sayCppExport+ sayExportHaskell = sayHsExport++instance HasExtNames Function where+ getPrimaryExtName = fnExtName++instance HasReqs Function where+ getReqs = fnReqs+ setReqs reqs fn = fn { fnReqs = reqs }++instance HasAddendum Function where+ getAddendum = fnAddendum+ setAddendum addendum fn = fn { fnAddendum = addendum }++instance HandlesExceptions Function where+ getExceptionHandlers = fnExceptionHandlers+ modifyExceptionHandlers f fn = fn { fnExceptionHandlers = f $ fnExceptionHandlers fn }++-- | Creates a binding for a C++ function.+makeFn :: (IsFnName Identifier name, IsParameter p)+ => name+ -> Maybe ExtName+ -- ^ An optional external name; will be automatically derived from+ -- the identifier if absent.+ -> Purity+ -> [p] -- ^ Parameter types.+ -> Type -- ^ Return type.+ -> Function+makeFn cName maybeExtName purity paramTypes retType =+ let fnName = toFnName cName+ in Function fnName+ (extNameOrFnIdentifier fnName maybeExtName)+ purity (toParameters paramTypes) retType mempty mempty mempty++-- | A function taking parameters and returning a value (or 'voidT'). Function+-- pointers must wrap a 'fnT' in a 'ptrT'.+--+-- See also 'fnT'' which accepts parameter information.+fnT :: [Type] -> Type -> Type+-- (Keep docs in sync with hs-boot.)+fnT = Internal_TFn . map toParameter++-- | A version of 'fnT' that accepts additional information about parameters.+fnT' :: [Parameter] -> Type -> Type+-- (Keep docs in sync with hs-boot.)+fnT' = Internal_TFn++sayCppExport :: LC.SayExportMode -> Function -> LC.Generator ()+sayCppExport mode fn = case mode of+ LC.SayHeader -> return ()+ LC.SaySource -> do+ LC.addReqsM $ fnReqs fn+ sayCppExportFn (fnExtName fn)+ (case fnCName fn of+ FnName identifier -> CallFn $ LC.sayIdentifier identifier+ FnOp op -> CallOp op)+ Nothing+ (fnParams fn)+ (fnReturn fn)+ (fnExceptionHandlers fn)+ True -- Render the body.++-- | The direction between languages in which a value is being passed.+data CallDirection =+ ToCpp -- ^ Haskell code is calling out to C++.+ | FromCpp -- ^ C++ is invoking a callback.+ deriving (Show)++-- | The name of a function to call.+data CppCallType =+ CallOp Operator+ -- ^ A call to the given operator, for example @x++@, @x * y@, @a[i]@.+ | CallFn (LC.Generator ())+ -- ^ A call to the function whose name is emitted by the given action.+ | VarRead (LC.Generator ())+ -- ^ Not a function call, but a read from a variable whose name is emitted+ -- by the given action.+ | VarWrite (LC.Generator ())+ -- ^ Not a function call, but a write to a variable whose name is emitted by+ -- the given action.++-- | Generates a C++ wrapper function for calling a C++ function (or method, or+-- reading from or writing to a variable). The generated function handles+-- C++-side marshalling of values and propagating exceptions as requested.+--+-- See also 'sayHsExportFn'.+sayCppExportFn ::+ ExtName -- ^ The external name of the function.+ -> CppCallType -- ^ The C++ name at which the function can be invoked.+ -> Maybe Type+ -- ^ If present, then we are wrapping a method within some class, and the+ -- type is that of the class.+ -> [Parameter] -- ^ Info about the function's parameters.+ -> Type -- ^ The function's return type.+ -> ExceptionHandlers+ -- ^ Exception handlers configured on the function itself. No need to call+ -- 'LC.getEffectiveExceptionHandlers' to combine the function's handlers+ -- with those from the module and interface; this function does that already.+ -> Bool+ -- ^ Whether to generate the function definition. If false, only the+ -- declaration is generated (no function body).+ -> LC.Generator ()+sayCppExportFn extName callType maybeThisType params retType exceptionHandlers sayBody = do+ handlerList <- exceptionHandlersList <$> LC.getEffectiveExceptionHandlers exceptionHandlers+ let paramTypes = map parameterType params+ catches = not $ null handlerList+ addExceptionParamNames =+ if catches then (++ [LC.exceptionIdArgName, LC.exceptionPtrArgName]) else id+ addExceptionParamTypes = if catches then (++ [ptrT intT, ptrT $ ptrT voidT]) else id++ paramCount = length paramTypes+ paramCTypeMaybes <- mapM LC.typeToCType paramTypes+ let paramCTypes = zipWith fromMaybe paramTypes paramCTypeMaybes+ retCTypeMaybe <- LC.typeToCType retType+ let retCType = fromMaybe retType retCTypeMaybe++ LC.addReqsM . mconcat =<< mapM LC.typeReqs (retType:paramTypes)++ LC.sayFunction (LC.externalNameToCpp extName)+ (maybe id (const ("self":)) maybeThisType $+ addExceptionParamNames $+ zipWith3 (\pt ctm ->+ -- TManual needs special handling to determine whether a+ -- conversion is necessary. 'typeToCType' doesn't suffice+ -- because for TManual this check relies on the direction of+ -- the call. See the special case in 'sayCppArgRead' as+ -- well.+ let hasConversion = case pt of+ Internal_TManual s ->+ isJust $ conversionSpecCppConversionToCppExpr $+ conversionSpecCpp s+ _ -> isJust ctm+ in if hasConversion then LC.toArgNameAlt else LC.toArgName)+ paramTypes+ paramCTypeMaybes+ [1..paramCount])+ (fnT (addExceptionParamTypes $ maybe id (:) maybeThisType paramCTypes)+ retCType) $+ if not sayBody+ then Nothing+ else Just $ do+ when catches $ do+ LC.say "try {\n"+ LC.says ["*", LC.exceptionIdArgName, " = 0;\n"]++ -- Convert arguments that aren't passed in directly.+ mapM_ (sayCppArgRead ToCpp) $ zip3 [1..] paramTypes paramCTypeMaybes++ let -- Determines how to call the exported function or method.+ sayCall = case callType of+ CallOp op -> do+ LC.say "("+ let effectiveParamCount = paramCount + if isJust maybeThisType then 1 else 0+ paramNames@(p1:p2:_) = (if isJust maybeThisType then ("(*self)":) else id) $+ map LC.toArgName [1..]+ assertParamCount n =+ when (effectiveParamCount /= n) $ LC.abort $ concat+ ["sayCppExportFn: Operator ", show op, " for export ", show extName,+ " requires ", show n, " parameter(s), but has ", show effectiveParamCount,+ "."]+ case operatorType op of+ UnaryPrefixOperator symbol -> assertParamCount 1 >> LC.says [symbol, p1]+ UnaryPostfixOperator symbol -> assertParamCount 1 >> LC.says [p1, symbol]+ BinaryOperator symbol -> assertParamCount 2 >> LC.says [p1, symbol, p2]+ CallOperator ->+ LC.says $ p1 : "(" : take (effectiveParamCount - 1) (drop 1 paramNames) ++ [")"]+ ArrayOperator -> assertParamCount 2 >> LC.says [p1, "[", p2, "]"]+ LC.say ")"+ CallFn sayCppName -> do+ when (isJust maybeThisType) $ LC.say "self->"+ sayCppName+ LC.say "("+ sayCppArgNames paramCount+ LC.say ")"+ VarRead sayVarName -> do+ when (isJust maybeThisType) $ LC.say "self->"+ sayVarName+ VarWrite sayVarName -> do+ when (isJust maybeThisType) $ LC.say "self->"+ sayVarName+ LC.says [" = ", LC.toArgName 1]++ -- Writes the call, transforming the return value if necessary.+ -- These translations should be kept in sync with typeToCType.+ sayCallAndReturn retType' retCTypeMaybe' = case (retType', retCTypeMaybe') of+ -- Void needs special handling because we don't want a return statement.+ (Internal_TVoid, Nothing) -> sayCall >> LC.say ";\n"++ -- Custom conversions.+ (Internal_TManual s, _) -> do+ -- The ConversionSpec s may or may not specify an intermediate+ -- type to pass over the FFI boundary: the second value in the+ -- pair (we check this before the (_, Nothing) case below). We+ -- don't actually care what it is though, because s already+ -- specifies how to convert.+ case conversionSpecCppConversionToCppExpr $ conversionSpecCpp s of+ -- If there is a custom conversion expression defined, use it.+ Just convFn -> LC.say "return " >> convFn sayCall Nothing >> LC.say ";\n"+ -- Otherwise, assume we can just return the value directly.+ Nothing -> sayCallAndReturnDirect++ -- The case of a value for which no conversion is necessary.+ (_, Nothing) -> sayCallAndReturnDirect++ -- Object cases.+ (Internal_TRef cls, Just (Internal_TPtr cls')) | cls == cls' ->+ LC.say "return &(" >> sayCall >> LC.say ");\n"+ (Internal_TObj cls,+ Just (Internal_TPtr (Internal_TConst (Internal_TObj cls')))) | cls == cls' ->+ sayReturnNew cls sayCall+ (Internal_TObjToHeap cls, Just (Internal_TPtr (Internal_TObj cls'))) | cls == cls' ->+ sayReturnNew cls sayCall+ (Internal_TToGc (Internal_TObj cls),+ Just (Internal_TPtr (Internal_TObj cls'))) | cls == cls' ->+ sayReturnNew cls sayCall+ (Internal_TToGc retType'', _) -> sayCallAndReturn retType'' retCTypeMaybe'++ ts -> LC.abort $ concat ["sayCppExportFn: Unexpected return types ", show ts,+ " while generating binding for ", show extName, "."]++ sayCallAndReturnDirect = LC.say "return " >> sayCall >> LC.say ";\n"++ sayCallAndReturn retType retCTypeMaybe++ when catches $ do+ iface <- LC.askInterface++ forM_ handlerList $ \handler -> do+ LC.say "} catch ("+ case handler of+ CatchClass cls -> LC.sayVar LC.exceptionVarName Nothing $ refT $ constT $ objT cls+ CatchAll -> LC.say "..."+ LC.say ") {\n"++ exceptionId <- case handler of+ CatchClass cls -> case interfaceExceptionClassId iface cls of+ Just exceptionId -> return exceptionId+ Nothing -> LC.abort $ concat+ ["sayCppExportFn: Trying to catch non-exception class ", show cls,+ " while generating binding for ", show extName, "."]+ CatchAll -> return exceptionCatchAllId+ LC.says ["*", LC.exceptionIdArgName, " = ", show $ getExceptionId exceptionId, ";\n"]++ case handler of+ CatchAll -> LC.says ["*", LC.exceptionPtrArgName, " = 0;\n"]+ CatchClass cls -> do+ -- Object pointers don't convert automatically to void*.+ LC.says ["*", LC.exceptionPtrArgName, " = reinterpret_cast<void*>(new "]+ LC.sayType Nothing $ objT cls+ LC.says ["(", LC.exceptionVarName, "));\n"]++ -- For all of the types our gateway functions actually return, "return+ -- 0" is a valid statement.+ when (retType /= Internal_TVoid) $ LC.say "return 0;\n"++ LC.say "}\n"++ where sayReturnNew cls sayCall =+ LC.say "return new" >> LC.sayIdentifier (Class.classIdentifier cls) >> LC.say "(" >>+ sayCall >> LC.say ");\n"++-- | Generates code to marshal a value between a C++ type and the intermediate+-- type to be used over the FFI. If @dir@ is 'ToCpp', then we are a C+++-- function reading an argument from foreign code. If @dir@ is 'FromCpp', then+-- we are invoking a foreign callback.+sayCppArgRead :: CallDirection -> (Int, Type, Maybe Type) -> LC.Generator ()+sayCppArgRead dir (n, stripConst . normalizeType -> cppType, maybeCType) = case cppType of+ t@(Internal_TPtr (Internal_TFn params retType)) -> do+ -- Assert that all types referred to in a function pointer type are all+ -- representable as C types.+ let paramTypes = map parameterType params+ check label t' = ((label ++ " " ++ show t') <$) <$> LC.typeToCType t'+ mismatches <-+ fmap catMaybes $+ (:) <$> check "return type" retType+ <*> mapM (\paramType -> check "parameter" paramType) paramTypes+ unless (null mismatches) $+ LC.abort $ concat $+ "sayCppArgRead: Some types within a function pointer type use non-C types, " :+ "but only C types may be used. The unsupported types are: " :+ intersperse "; " mismatches ++ [". The whole function type is ", show t, "."]++ convertDefault++ Internal_TRef t -> convertObj t++ Internal_TObj _ -> convertObj $ constT cppType++ Internal_TObjToHeap cls -> case dir of+ ToCpp -> error $ objToHeapTWrongDirectionErrorMsg (Just "sayCppArgRead") cls+ FromCpp -> do+ LC.sayIdentifier $ Class.classIdentifier cls+ LC.says ["* ", LC.toArgName n, " = new "]+ LC.sayIdentifier $ Class.classIdentifier cls+ LC.says ["(", LC.toArgNameAlt n, ");\n"]++ Internal_TToGc t' -> case dir of+ ToCpp -> error $ toGcTWrongDirectionErrorMsg (Just "sayCppArgRead") t'+ FromCpp -> do+ let newCppType = case t' of+ -- In the case of (TToGc (TObj _)), we copy the temporary object to+ -- the heap and let the foreign language manage that value.+ Internal_TObj cls -> objToHeapT cls+ _ -> t'+ cType <- LC.typeToCType newCppType+ sayCppArgRead dir (n, newCppType, cType)++ -- In case of a manual type, apply the custom conversion, if there is one.+ Internal_TManual s -> do+ let maybeConvExpr =+ (case dir of+ ToCpp -> conversionSpecCppConversionToCppExpr+ FromCpp -> conversionSpecCppConversionFromCppExpr) $+ conversionSpecCpp s+ forM_ maybeConvExpr $ \gen ->+ gen (LC.say $ LC.toArgNameAlt n) (Just $ LC.say $ LC.toArgName n)++ _ -> convertDefault++ where -- Primitive types don't need to be encoded/decoded. But if maybeCType is a+ -- Just, then we're expected to do some encoding/decoding, so something is+ -- wrong.+ --+ -- TODO Do we need to handle TConst?+ convertDefault = forM_ maybeCType $ \cType ->+ LC.abort $ concat+ ["sayCppArgRead: Don't know how to convert ", show dir, " between C-type ", show cType,+ " and C++-type ", show cppType, "."]++ convertObj cppType' = case dir of+ ToCpp -> do+ LC.sayVar (LC.toArgName n) Nothing $ refT cppType'+ LC.says [" = *", LC.toArgNameAlt n, ";\n"]+ FromCpp -> do+ LC.sayVar (LC.toArgName n) Nothing $ ptrT cppType'+ LC.says [" = &", LC.toArgNameAlt n, ";\n"]++-- | Prints a comma-separated list of the argument names used for C++ gateway+-- functions. The number specifies how many names to print.+sayCppArgNames :: Int -> LC.Generator ()+sayCppArgNames count =+ LC.says $ intersperse ", " $ map LC.toArgName [1..count]++sayHsExport :: LH.SayExportMode -> Function -> LH.Generator ()+sayHsExport mode fn =+ (sayHsExportFn mode <$> fnExtName <*> fnExtName <*> fnPurity <*>+ fnParams <*> fnReturn <*> fnExceptionHandlers) fn++-- | Generates a Haskell wrapper function for calling a C++ function (or method,+-- or reading from or writing to a variable, as with 'sayCppExportFn'). The+-- generated function handles Haskell-side marshalling of values and propagating+-- exceptions as requested.+sayHsExportFn ::+ LH.SayExportMode -- ^ The phase of code generation.+ -> ExtName+ -- ^ The external name for the entity we're generating. For class+ -- entities, this will include the class's external name as a prefix.+ -> ExtName+ -- ^ An alternate external name to use to generate Haskell function names.+ -- For non-class entities, this can be just the regular external name. For+ -- class entities, in order to strip off the class name that was added so+ -- that the entity's external name is unique, this can just be the name of+ -- the function, variable, etc.+ -> Purity -- ^ Whether or not the function is pure (free of side effects).+ -> [Parameter] -- ^ Parameter info.+ -> Type -- ^ The return type.+ -> ExceptionHandlers+ -- ^ Any exception handlers to apply to the binding, in addition to what+ -- its module and interface provide.+ -> LH.Generator ()+sayHsExportFn mode extName foreignName purity params retType exceptionHandlers = do+ effectiveHandlers <- LH.getEffectiveExceptionHandlers exceptionHandlers+ let handlerList = exceptionHandlersList effectiveHandlers+ catches = not $ null handlerList++ paramTypes = map parameterType params++ -- We use the pure version of toHsFnName here; because foreignName isn't an+ -- ExtName present in the interface's lookup table, toHsFnName would bail on+ -- it. Since functions don't reference each other (e.g. we don't put anything+ -- in .hs-boot files for them in circular modules cases), this isn't a problem.+ let hsFnName = LH.toHsFnName' foreignName+ hsFnImportedName = hsFnName ++ "'"++ case mode of+ LH.SayExportForeignImports ->+ LH.withErrorContext ("generating imports for function " ++ show extName) $ do+ -- Print a "foreign import" statement.+ hsCType <- fnToHsTypeAndUse LH.HsCSide purity params retType effectiveHandlers+ LH.saysLn ["foreign import ccall \"", LC.externalNameToCpp extName, "\" ", hsFnImportedName,+ " :: ", renderFnHsType hsCType]++ LH.SayExportDecls -> LH.withErrorContext ("generating function " ++ show extName) $ do+ -- Print the type signature.+ LH.ln+ LH.addExport hsFnName+ hsHsType <- fnToHsTypeAndUse LH.HsHsSide purity params retType effectiveHandlers+ LH.saysLn [hsFnName, " :: ", renderFnHsTypeWithNames hsHsType]++ case purity of+ Nonpure -> return ()+ Pure -> LH.saysLn ["{-# NOINLINE ", hsFnName, " #-}"]++ -- Print the function body.+ let argNames = map LH.toArgName [1..length paramTypes]+ convertedArgNames = map (++ "'") argNames+ -- Operators on this line must bind more weakly than operators used below,+ -- namely ($) and (>>=). (So finish the line with ($).)+ lineEnd <- case purity of+ Nonpure -> return [" ="]+ Pure -> do LH.addImports $ mconcat [hsImport1 "Prelude" "($)", hsImportForUnsafeIO]+ return [" = HoppySIU.unsafePerformIO $"]+ LH.saysLn $ hsFnName : map (' ':) argNames ++ lineEnd+ LH.indent $ do+ forM_ (zip3 paramTypes argNames convertedArgNames) $ \(t, argName, argName') ->+ sayHsArgProcessing ToCpp t argName argName'++ exceptionHandling <-+ if catches+ then do iface <- LH.askInterface+ currentModule <- LH.askModule+ let exceptionSupportModule = interfaceExceptionSupportModule iface+ when (exceptionSupportModule /= Just currentModule) $+ LH.addImports . hsWholeModuleImport . LH.getModuleName iface =<<+ fromMaybeM (throwError+ "Internal error, an exception support module is not available")+ exceptionSupportModule+ LH.addImports $ mconcat [hsImport1 "Prelude" "($)", hsImportForRuntime]+ return "HoppyFHR.internalHandleExceptions exceptionDb' $"+ else return ""++ let callWords = exceptionHandling : hsFnImportedName : map (' ':) convertedArgNames+ sayHsCallAndProcessReturn ToCpp retType callWords++ LH.SayExportBoot ->+ -- Functions (methods included) cannot be referenced from other exports,+ -- so we don't need to emit anything.+ --+ -- If this changes, revisit the comment on hsFnName above.+ return ()++-- | Generates Haskell code to perform marshalling of a function's argument in a+-- specified direction.+--+-- This function either generates a line or lines such that subsequent lines can+-- refer to the output binding. The final line is either terminated with+--+-- > ... $ \value ->+--+-- or+--+-- > let ... in+--+-- so that precedence is not an issue.+sayHsArgProcessing ::+ CallDirection -- ^ The direction of the FFI call.+ -> Type -- ^ The type of the value to be marshalled.+ -> String -- ^ The name of the binding holding the input value.+ -> String -- ^ The name of the binding to create for the output value.+ -> LH.Generator ()+sayHsArgProcessing dir t fromVar toVar =+ LH.withErrorContext ("processing argument of type " ++ show t) $+ case t of+ Internal_TVoid -> throwError $ "TVoid is not a valid argument type"+ -- References and pointers are handled equivalently.+ Internal_TPtr (Internal_TObj cls) -> case dir of+ ToCpp -> do+ LH.addImports $ mconcat [hsImport1 "Prelude" "($)",+ hsImportForRuntime]+ castMethodName <- Class.toHsCastMethodName Nonconst cls+ LH.saysLn ["HoppyFHR.withCppPtr (", castMethodName, " ", fromVar,+ ") $ \\", toVar, " ->"]+ FromCpp -> do+ ctorName <- Class.toHsDataCtorName LH.Unmanaged Nonconst cls+ LH.saysLn ["let ", toVar, " = ", ctorName, " ", fromVar, " in"]+ Internal_TPtr (Internal_TConst (Internal_TObj cls)) -> case dir of+ ToCpp -> do+ -- Same as the (TObj _), ToCpp case.+ LH.addImports $ mconcat [hsImport1 "Prelude" "($)",+ hsImportForPrelude,+ hsImportForRuntime]+ withValuePtrName <- Class.toHsWithValuePtrName cls+ LH.saysLn [withValuePtrName, " ", fromVar,+ " $ HoppyP.flip HoppyFHR.withCppPtr $ \\", toVar, " ->"]+ FromCpp -> do+ ctorName <- Class.toHsDataCtorName LH.Unmanaged Const cls+ LH.saysLn ["let ", toVar, " = ", ctorName, " ", fromVar, " in"]+ Internal_TPtr _ -> noConversion+ Internal_TRef t' -> sayHsArgProcessing dir (ptrT t') fromVar toVar+ Internal_TFn {} -> throwError "TFn unimplemented"+ Internal_TObj cls -> case dir of+ ToCpp -> do+ -- Same as the (TPtr (TConst (TObj _))), ToPtr case.+ LH.addImports $ mconcat [hsImport1 "Prelude" "($)",+ hsImportForPrelude,+ hsImportForRuntime]+ withValuePtrName <- Class.toHsWithValuePtrName cls+ LH.saysLn [withValuePtrName, " ", fromVar,+ " $ HoppyP.flip HoppyFHR.withCppPtr $ \\", toVar, " ->"]+ FromCpp -> case Class.classHaskellConversionFromCppFn $ LH.getClassHaskellConversion cls of+ Just _ -> do+ LH.addImports $ mconcat [hsImport1 "Prelude" "(>>=)",+ hsImportForRuntime]+ ctorName <- Class.toHsDataCtorName LH.Unmanaged Const cls+ LH.saysLn ["HoppyFHR.decode (", ctorName, " ", fromVar, ") >>= \\", toVar, " ->"]+ Nothing ->+ throwError $ concat+ ["Can't pass a TObj of ", show cls,+ " from C++ to Haskell because no class decode conversion is defined"]+ Internal_TObjToHeap cls -> case dir of+ ToCpp -> throwError $ objToHeapTWrongDirectionErrorMsg Nothing cls+ FromCpp -> sayHsArgProcessing dir (ptrT $ objT cls) fromVar toVar+ Internal_TToGc t' -> case dir of+ ToCpp -> throwError $ toGcTWrongDirectionErrorMsg Nothing t'+ FromCpp -> do+ LH.addImports $ mconcat [hsImport1 "Prelude" "(>>=)",+ hsImportForRuntime]+ ctorName <-+ maybe (throwError $ tToGcInvalidFormErrorMessage Nothing t')+ (Class.toHsDataCtorName LH.Unmanaged Nonconst) $+ case stripConst t' of+ Internal_TObj cls -> Just cls+ Internal_TRef (Internal_TConst (Internal_TObj cls)) -> Just cls+ Internal_TRef (Internal_TObj cls) -> Just cls+ Internal_TPtr (Internal_TConst (Internal_TObj cls)) -> Just cls+ Internal_TPtr (Internal_TObj cls) -> Just cls+ _ -> Nothing+ LH.saysLn ["HoppyFHR.toGc (", ctorName, " ", fromVar, ") >>= \\", toVar, " ->"]+ Internal_TConst t' -> sayHsArgProcessing dir t' fromVar toVar++ Internal_TManual s -> do+ let maybeGen =+ fmap (case dir of+ ToCpp -> conversionSpecHaskellToCppFn+ FromCpp -> conversionSpecHaskellFromCppFn) $+ conversionSpecHaskell s+ throwForNoConversion =+ throwError $ concat+ ["No conversion defined for ", show s,+ case dir of+ ToCpp -> " to C++ from Haskell"+ FromCpp -> " from C++ to Haskell"]+ case maybeGen of+ Just (CustomConversion gen) -> do+ LH.addImports $ hsImport1 "Prelude" "(>>=)"+ LH.sayLn "("+ LH.indent gen+ LH.saysLn [") ", fromVar, " >>= \\", toVar, " ->"]+ Just BinaryCompatible -> LH.saysLn ["let ", toVar, " = ", fromVar, " in"]+ Just ConversionUnsupported -> throwForNoConversion+ Nothing -> throwForNoConversion++ where noConversion = LH.saysLn ["let ", toVar, " = ", fromVar, " in"]++-- | Note that the 'CallDirection' is the direction of the call, not the+-- direction of the return. 'ToCpp' means we're returning to the foreign+-- language, 'FromCpp' means we're returning from it.+sayHsCallAndProcessReturn :: CallDirection -> Type -> [String] -> LH.Generator ()+sayHsCallAndProcessReturn dir t callWords =+ LH.withErrorContext ("processing return value of type " ++ show t) $+ case t of+ Internal_TVoid -> sayCall+ -- The same as TPtr (TConst (TObj _)), but nonconst.+ Internal_TPtr (Internal_TObj cls) -> do+ case dir of+ ToCpp -> do+ LH.addImports hsImportForPrelude+ ctorName <- Class.toHsDataCtorName LH.Unmanaged Nonconst cls+ LH.saysLn ["HoppyP.fmap ", ctorName]+ sayCall+ FromCpp -> do+ LH.addImports $ mconcat [hsImportForPrelude, hsImportForRuntime]+ LH.sayLn "HoppyP.fmap HoppyFHR.toPtr"+ sayCall+ -- The same as TPtr (TConst (TObj _)), but nonconst.+ Internal_TPtr (Internal_TConst (Internal_TObj cls)) -> case dir of+ ToCpp -> do+ LH.addImports hsImportForPrelude+ ctorName <- Class.toHsDataCtorName LH.Unmanaged Const cls+ LH.saysLn ["HoppyP.fmap ", ctorName]+ sayCall+ FromCpp -> do+ LH.addImports $ mconcat [hsImportForPrelude, hsImportForRuntime]+ LH.sayLn "HoppyP.fmap HoppyFHR.toPtr"+ sayCall+ Internal_TPtr _ -> sayCall+ Internal_TRef t' -> sayHsCallAndProcessReturn dir (ptrT t') callWords+ Internal_TFn {} -> throwError "TFn unimplemented"+ Internal_TObj cls -> case dir of+ ToCpp -> case Class.classHaskellConversionFromCppFn $ LH.getClassHaskellConversion cls of+ Just _ -> do+ LH.addImports $ mconcat [hsImports "Prelude" ["(.)", "(=<<)"],+ hsImportForRuntime]+ ctorName <- Class.toHsDataCtorName LH.Unmanaged Const cls+ LH.saysLn ["(HoppyFHR.decodeAndDelete . ", ctorName, ") =<<"]+ sayCall+ Nothing ->+ throwError $ concat+ ["Can't return a TObj of ", show cls,+ " from C++ to Haskell because no class decode conversion is defined"]+ FromCpp -> do+ LH.addImports $ mconcat [hsImports "Prelude" ["(.)", "(=<<)"],+ hsImportForPrelude,+ hsImportForRuntime]+ LH.sayLn "(HoppyP.fmap (HoppyFHR.toPtr) . HoppyFHR.encode) =<<"+ sayCall+ Internal_TObjToHeap cls -> case dir of+ ToCpp -> sayHsCallAndProcessReturn dir (ptrT $ objT cls) callWords+ FromCpp -> throwError $ objToHeapTWrongDirectionErrorMsg Nothing cls+ Internal_TToGc t' -> case dir of+ ToCpp -> do+ LH.addImports $ mconcat [hsImport1 "Prelude" "(=<<)",+ hsImportForRuntime]+ LH.sayLn "HoppyFHR.toGc =<<"+ -- TToGc (TObj _) should create a pointer rather than decoding, so we+ -- change the TObj _ into a TPtr (TObj _).+ case t' of+ Internal_TObj _ -> sayHsCallAndProcessReturn dir (ptrT t') callWords+ _ -> sayHsCallAndProcessReturn dir t' callWords+ FromCpp -> throwError $ toGcTWrongDirectionErrorMsg Nothing t'+ Internal_TConst t' -> sayHsCallAndProcessReturn dir t' callWords++ Internal_TManual s -> do+ -- Remember 'dir' is backward here, because we're dealing with a return+ -- value, so these functions look backward.+ let maybeGen =+ fmap (case dir of+ ToCpp -> conversionSpecHaskellFromCppFn+ FromCpp -> conversionSpecHaskellToCppFn) $+ conversionSpecHaskell s+ throwForNoConversion =+ throwError $ concat+ ["No conversion defined for ", show s,+ case dir of+ ToCpp -> " from C++ to Haskell"+ FromCpp -> " to C++ from Haskell"]+ case maybeGen of+ Just (CustomConversion gen) -> do+ LH.addImports $ hsImport1 "Prelude" "(=<<)"+ LH.sayLn "("+ LH.indent gen+ LH.sayLn ") =<<"+ Just BinaryCompatible -> return ()+ Just ConversionUnsupported -> throwForNoConversion+ Nothing -> throwForNoConversion+ sayCall++ where sayCall = LH.saysLn $ "(" : callWords ++ [")"]++-- | The Haskell type of a 'Function', as computed by 'fnToHsTypeAndUse'. This+-- combines a 'HsQualType' with a list of parameter names.+data FnHsType = FnHsType+ { fnHsQualType :: HsQualType+ , fnHsParamNameMaybes :: [Maybe String]+ }++-- | Implements special logic on top of 'LH.cppTypeToHsTypeAndUse', that+-- computes the Haskell __qualified__ type for a function, including typeclass+-- constraints, and bundles it with parameter names.+fnToHsTypeAndUse ::+ LH.HsTypeSide+ -> Purity+ -> [Parameter]+ -> Type+ -> ExceptionHandlers+ -- ^ These should be the effective exception handlers for the function, as+ -- returned by+ -- @'LH.getEffectiveExceptionHandlers' . 'fnExceptionHandlers'@,+ -- not just the function's exception handlers directly from+ -- @fnExceptionHandlers@.+ -> LH.Generator FnHsType+fnToHsTypeAndUse side purity params returnType exceptionHandlers = do+ let catches = not $ null $ exceptionHandlersList exceptionHandlers+ getsExcParams = catches && side == LH.HsCSide++ paramTypes =+ (if getsExcParams then (++ [ptrT intT, ptrT $ ptrT voidT]) else id) $+ map parameterType params++ paramNameMaybes =+ (if getsExcParams then (++ [Just "excId", Just "excPtr"]) else id) $+ map parameterName params++ defaultParamNames = map LH.toArgName [1..]++ defaultedParamNames = zipWith fromMaybe defaultParamNames paramNameMaybes++ paramQualTypes <- mapM contextForParam $ zip defaultedParamNames paramTypes+ let context = concatMap (\(HsQualType ctx _) -> ctx) paramQualTypes :: HsContext+ hsParams = map (\(HsQualType _ t) -> t) paramQualTypes++ -- Determine the 'HsHsSide' return type for the function. Do the conversion+ -- to a Haskell type, and wrap the result in 'IO' if the function is impure.+ -- (HsCSide types always get wrapped in IO.)+ hsReturnInitial <- LH.cppTypeToHsTypeAndUse side returnType+ hsReturnForPurity <- case (purity, side) of+ (Pure, LH.HsHsSide) -> return hsReturnInitial+ _ -> do+ LH.addImports hsImportForPrelude+ return $ HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyP.IO") hsReturnInitial++ return FnHsType+ { fnHsQualType = HsQualType context $ foldr HsTyFun hsReturnForPurity hsParams+ , fnHsParamNameMaybes = paramNameMaybes+ }++ where contextForParam :: (String, Type) -> LH.Generator HsQualType+ contextForParam (s, t) = case t of+ Internal_TPtr (Internal_TObj cls) -> receivePtr s cls Nonconst+ Internal_TPtr (Internal_TConst (Internal_TObj cls)) -> receiveValue s t cls+ Internal_TRef (Internal_TObj cls) -> receivePtr s cls Nonconst+ Internal_TRef (Internal_TConst (Internal_TObj cls)) -> receiveValue s t cls+ Internal_TObj cls -> receiveValue s t cls+ Internal_TManual spec ->+ -- We add a typeclass constraint iff we're generating an exposed+ -- Haskell function (HsHsSide) and there is a constraint declared.+ -- If we're generating the underlying C FFI function, or if there is+ -- no constraint declared, then don't add one.+ case (side, conversionSpecHaskell spec >>= conversionSpecHaskellHsArgType) of+ (LH.HsHsSide, Just f) -> f $ HsIdent s+ _ -> handoff side t+ Internal_TConst t' -> contextForParam (s, t')+ _ -> handoff side t++ -- Use whatever type 'cppTypeToHsTypeAndUse' suggests, with no typeclass+ -- constraints.+ handoff :: LH.HsTypeSide -> Type -> LH.Generator HsQualType+ handoff side' t = HsQualType [] <$> LH.cppTypeToHsTypeAndUse side' t++ -- Receives a @FooPtr this => this@.+ receivePtr :: String -> Class.Class -> Constness -> LH.Generator HsQualType+ receivePtr s cls cst = case side of+ LH.HsHsSide -> do+ ptrClassName <- Class.toHsPtrClassName cst cls+ let t' = HsTyVar $ HsIdent s+ return $ HsQualType [(UnQual $ HsIdent ptrClassName, [t'])] t'+ LH.HsCSide -> do+ LH.addImports hsImportForForeign+ typeName <- Class.toHsDataTypeName cst cls+ return $+ HsQualType [] $+ HsTyApp (HsTyCon $ UnQual $ HsIdent "HoppyF.Ptr")+ (HsTyVar $ HsIdent typeName)++ -- Receives a @FooValue a => a@.+ receiveValue :: String -> Type -> Class.Class -> LH.Generator HsQualType+ receiveValue s t cls = case side of+ LH.HsCSide -> handoff side t+ LH.HsHsSide -> do+ LH.addImports hsImportForRuntime+ valueClassName <- Class.toHsValueClassName cls+ let t' = HsTyVar $ HsIdent s+ return $ HsQualType [(UnQual $ HsIdent valueClassName, [t'])] t'++-- | Renders a 'FnHsType' as a Haskell type, ignoring parameter names. This+-- implementation uses haskell-src.+renderFnHsType :: FnHsType -> String+renderFnHsType = LH.prettyPrint . fnHsQualType++-- | Renders a 'FnHsType' as a Haskell type, including Haddock for parameter+-- names.+--+-- Unfortunately, we have to implement this ourselves, because haskell-src+-- doesn't support comments, and haskell-src-exts's comments implementation+-- relies on using specific source spans, and we don't want all that complexity+-- here. So instead we render it ourselves, inserting "{- ^ ... -}" tags where+-- appropriate.+renderFnHsTypeWithNames :: FnHsType -> String+renderFnHsTypeWithNames fnHsType =+ concat $ renderedContextStrs ++ renderedParamStrs++ where HsQualType assts unqualType = fnHsQualType fnHsType+ paramNameMaybes = fnHsParamNameMaybes fnHsType++ renderedContextStrs :: [String]+ renderedContextStrs =+ if null assts+ then []+ else "(" : intersperse ", " (map renderAsst assts) ++ [") => "]++ renderAsst :: (HsQName, [HsType]) -> String+ renderAsst asst = case asst of+ (UnQual (HsIdent typeclass), [HsTyVar (HsIdent typeVar)]) ->+ concat [typeclass, " ", typeVar]+ _ -> error $ "renderAsst: Unexpected argument: " ++ show asst++ renderedParamStrs :: [String]+ renderedParamStrs = renderParams unqualType paramNameMaybes++ renderParams :: HsType -> [Maybe String] -> [String]+ renderParams fnType' paramNameMaybes' = case (fnType', paramNameMaybes') of+ -- If there's a parameter name, then generate a Haddock comment+ -- showing the name.+ (HsTyFun a b, (Just name):restNames) ->+ "(" : LH.prettyPrint a : ") {- ^ " : name : " -} -> " : renderParams b restNames++ -- If there's no parameter name, then don't generate any documentation+ -- for it, but continue to recur in case there are other parameters+ -- with names.+ (HsTyFun a b, Nothing:restNames) ->+ "(" : LH.prettyPrint a : ") -> " : renderParams b restNames++ -- If we've reached the end of the TyFun chain, then we don't need to+ -- recur further. We can use 'prettyPrint' to render the rest.+ _ -> "(" : LH.prettyPrint fnType' : [")"]
+ src/Foreign/Hoppy/Generator/Spec/Function.hs-boot view
@@ -0,0 +1,29 @@+-- This file is part of Hoppy.+--+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net>+--+-- 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 <http://www.gnu.org/licenses/>.++module Foreign.Hoppy.Generator.Spec.Function (fnT, fnT') where++import Foreign.Hoppy.Generator.Spec.Base (Type, Parameter)++-- | A function taking parameters and returning a value (or 'voidT'). Function+-- pointers must wrap a 'fnT' in a 'ptrT'.+--+-- See also 'fnT'' which accepts parameter information.+fnT :: [Type] -> Type -> Type++-- | A version of 'fnT' that accepts additional information about parameters.+fnT' :: [Parameter] -> Type -> Type
+ src/Foreign/Hoppy/Generator/Spec/Variable.hs view
@@ -0,0 +1,123 @@+-- This file is part of Hoppy.+--+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net>+--+-- 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 <http://www.gnu.org/licenses/>.++-- | Interface for defining bindings to C++ variables.+module Foreign.Hoppy.Generator.Spec.Variable (+ -- * Data type+ Variable,+ -- * Construction+ makeVariable,+ -- * Properties+ varExtName,+ varIdentifier,+ varType,+ varReqs,+ varAddendum,+ varIsConst,+ varGetterExtName,+ varSetterExtName,+ ) where++import Data.Function (on)+import Foreign.Hoppy.Generator.Spec.Base+import qualified Foreign.Hoppy.Generator.Spec.Class as Class+import qualified Foreign.Hoppy.Generator.Language.Cpp as LC+import qualified Foreign.Hoppy.Generator.Language.Haskell as LH++-- | A C++ variable.+--+-- Use this data type's 'HasReqs' instance to make the variable accessible.+data Variable = Variable+ { varIdentifier :: Identifier+ -- ^ The identifier used to refer to the variable.+ , varExtName :: ExtName+ -- ^ The variable's external name.+ , varType :: Type+ -- ^ The variable's type. This may be+ -- 'Foreign.Hoppy.Generator.Types.constT' to indicate that the variable is+ -- read-only.+ , varReqs :: Reqs+ -- ^ Requirements for bindings to access this variable.+ , varAddendum :: Addendum+ -- ^ The variable's addendum.+ }++instance Eq Variable where+ (==) = (==) `on` varExtName++instance Show Variable where+ show v = concat ["<Variable ", show (varExtName v), " ", show (varType v), ">"]++instance Exportable Variable where+ sayExportCpp = sayCppExport+ sayExportHaskell = sayHsExport++instance HasExtNames Variable where+ getPrimaryExtName = varExtName+ getNestedExtNames v = [varGetterExtName v, varSetterExtName v]++instance HasReqs Variable where+ getReqs = varReqs+ setReqs reqs v = v { varReqs = reqs }++instance HasAddendum Variable where+ getAddendum = varAddendum+ setAddendum addendum v = v { varAddendum = addendum }++-- | Creates a binding for a C++ variable.+makeVariable :: Identifier -> Maybe ExtName -> Type -> Variable+makeVariable identifier maybeExtName t =+ Variable identifier (extNameOrIdentifier identifier maybeExtName) t mempty mempty++-- | Returns whether the variable is constant, i.e. whether its type is+-- @'Foreign.Hoppy.Generator.Types.constT' ...@.+varIsConst :: Variable -> Bool+varIsConst v = case varType v of+ Internal_TConst _ -> True+ _ -> False++-- | Returns the external name of the getter function for the variable.+varGetterExtName :: Variable -> ExtName+varGetterExtName = toExtName . (++ "_get") . fromExtName . varExtName++-- | Returns the external name of the setter function for the variable.+varSetterExtName :: Variable -> ExtName+varSetterExtName = toExtName . (++ "_set") . fromExtName . varExtName++sayCppExport :: LC.SayExportMode -> Variable -> LC.Generator ()+sayCppExport mode v = case mode of+ LC.SayHeader -> return ()+ LC.SaySource ->+ Class.sayCppExportVar (varType v)+ Nothing+ True+ (varGetterExtName v)+ (varSetterExtName v)+ (LC.sayIdentifier $ varIdentifier v)++sayHsExport :: LH.SayExportMode -> Variable -> LH.Generator ()+sayHsExport mode v = LH.withErrorContext ("generating variable " ++ show (varExtName v)) $ do+ let getterName = varGetterExtName v+ setterName = varSetterExtName v+ Class.sayHsExportVar mode+ (varType v)+ Nothing+ True+ getterName+ getterName+ setterName+ setterName
src/Foreign/Hoppy/Generator/Types.hs view
@@ -1,6 +1,6 @@ -- This file is part of Hoppy. ----- Copyright 2015-2018 Bryan Gardiner <bog@khumba.net>+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net> -- -- 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@@ -19,21 +19,33 @@ -- these functions, but we try to catch these and fail cleanly as much as -- possible. module Foreign.Hoppy.Generator.Types (- -- * Primitive types+ -- * Qualifiers+ constT,+ -- * Primtive types voidT,+ ptrT,+ refT,+ fnT,+ fnT',+ -- * Numeric types boolT,+ boolT', charT, ucharT,+ wcharT, shortT, ushortT, intT,+ intT', uintT, longT, ulongT, llongT, ullongT, floatT,+ floatT', doubleT,+ doubleT', int8T, int16T, int32T,@@ -45,125 +57,358 @@ ptrdiffT, sizeT, ssizeT,+ -- ** Custom numeric types+ makeNumericType,+ convertByCoercingIntegral,+ convertByCoercingFloating, -- * Complex types- enumT,- bitspaceT,- ptrT,- refT,- fnT,+ manualT, callbackT,+ enumT, objT, objToHeapT, toGcT,- constT, ) where +import {-# SOURCE #-} qualified Foreign.Hoppy.Generator.Language.Haskell as LH+import {-# SOURCE #-} Foreign.Hoppy.Generator.Spec.Callback (callbackT)+import {-# SOURCE #-} Foreign.Hoppy.Generator.Spec.Class (Class)+import {-# SOURCE #-} Foreign.Hoppy.Generator.Spec.Enum (enumT)+import {-# SOURCE #-} Foreign.Hoppy.Generator.Spec.Function (fnT, fnT') import Foreign.Hoppy.Generator.Spec.Base+import Language.Haskell.Syntax (+ HsName (HsIdent),+ HsQName (UnQual),+ HsType (HsTyCon),+ ) -- | C++ @void@, Haskell @()@.+voidT :: Type voidT = Internal_TVoid -- | C++ @bool@, Haskell 'Bool'.-boolT = Internal_TBool+--+-- C++ has sizeof(bool) == 1, whereas Haskell can > 1, so we have to convert.+boolT :: Type+boolT =+ makeNumericType "bool" mempty+ (do LH.addImports hsImportForPrelude+ return $ HsTyCon $ UnQual $ HsIdent "HoppyP.Bool")+ (Just $ do LH.addImports hsImportForForeignC+ return $ HsTyCon $ UnQual $ HsIdent "HoppyFC.CBool")+ (CustomConversion $ do+ LH.addImports $ mconcat [hsImport1 "Prelude" "(.)",+ hsImportForPrelude]+ LH.sayLn "\\x -> HoppyP.return $ if x then 1 else 0")+ (CustomConversion $ do+ LH.addImports $ mconcat [hsImports "Prelude" ["(.)", "(/=)"],+ hsImportForPrelude]+ LH.sayLn "(HoppyP.return . (/= 0))") +-- | C++ @bool@, Haskell 'Foreign.C.CBool'.+boolT' :: Type+boolT' =+ makeNumericType "bool" mempty+ (do LH.addImports hsImportForForeignC+ return $ HsTyCon $ UnQual $ HsIdent "HoppyFC.CBool")+ Nothing BinaryCompatible BinaryCompatible+ -- | C++ @char@, Haskell 'Foreign.C.CChar'.-charT = Internal_TChar+charT :: Type+charT =+ makeNumericType "char" mempty+ (do LH.addImports hsImportForForeignC+ return $ HsTyCon $ UnQual $ HsIdent "HoppyFC.CChar")+ Nothing BinaryCompatible BinaryCompatible -- | C++ @unsigned char@, Haskell 'Foreign.C.CUChar'.-ucharT = Internal_TUChar+ucharT :: Type+ucharT =+ makeNumericType "unsigned char" mempty+ (do LH.addImports hsImportForForeignC+ return $ HsTyCon $ UnQual $ HsIdent "HoppyFC.CUChar")+ Nothing BinaryCompatible BinaryCompatible +-- | C++ @wchar_t@, Haskell 'Foreign.C.CWchar'.+wcharT :: Type+wcharT =+ makeNumericType "wchar_t" mempty+ (do LH.addImports hsImportForForeignC+ return $ HsTyCon $ UnQual $ HsIdent "HoppyFC.CWchar")+ Nothing BinaryCompatible BinaryCompatible+ -- | C++ @short int@, Haskell 'Foreign.C.CShort'.-shortT = Internal_TShort+shortT :: Type+shortT =+ makeNumericType "short" mempty+ (do LH.addImports hsImportForForeignC+ return $ HsTyCon $ UnQual $ HsIdent "HoppyFC.CShort")+ Nothing BinaryCompatible BinaryCompatible -- | C++ @unsigned short int@, Haskell 'Foreign.C.CUShort'.-ushortT = Internal_TUShort+ushortT :: Type+ushortT =+ makeNumericType "unsigned short" mempty+ (do LH.addImports hsImportForForeignC+ return $ HsTyCon $ UnQual $ HsIdent "HoppyFC.CUShort")+ Nothing BinaryCompatible BinaryCompatible --- | C++ @int@, Haskell 'Foreign.C.CInt'.-intT = Internal_TInt+-- | C++ @int@, Haskell 'Int'. See also 'intT''.+intT :: Type+intT =+ makeNumericType "int" mempty+ (do LH.addImports hsImportForPrelude+ return $ HsTyCon $ UnQual $ HsIdent "HoppyP.Int")+ (Just $ do LH.addImports hsImportForForeignC+ return $ HsTyCon $ UnQual $ HsIdent "HoppyFC.CInt")+ convertByCoercingIntegral convertByCoercingIntegral +-- | C++ @int@, Haskell 'Foreign.C.CInt'. See also 'intT'.+intT' :: Type+intT' =+ makeNumericType "int" mempty+ (do LH.addImports hsImportForForeignC+ return $ HsTyCon $ UnQual $ HsIdent "HoppyFC.CInt")+ Nothing BinaryCompatible BinaryCompatible+ -- | C++ @unsigned int@, Haskell 'Foreign.C.CUInt'.-uintT = Internal_TUInt+uintT :: Type+uintT =+ makeNumericType "unsigned int" mempty+ (do LH.addImports hsImportForForeignC+ return $ HsTyCon $ UnQual $ HsIdent "HoppyFC.CUInt")+ Nothing BinaryCompatible BinaryCompatible -- | C++ @long int@, Haskell 'Foreign.C.CLong'.-longT = Internal_TLong+longT :: Type+longT =+ makeNumericType "long" mempty+ (do LH.addImports hsImportForForeignC+ return $ HsTyCon $ UnQual $ HsIdent "HoppyFC.CLong")+ Nothing BinaryCompatible BinaryCompatible -- | C++ @unsigned long int@, Haskell 'Foreign.C.CULong'.-ulongT = Internal_TULong+ulongT :: Type+ulongT =+ makeNumericType "unsigned long" mempty+ (do LH.addImports hsImportForForeignC+ return $ HsTyCon $ UnQual $ HsIdent "HoppyFC.CULong")+ Nothing BinaryCompatible BinaryCompatible -- | C++ @long long int@, Haskell 'Foreign.C.CLLong'.-llongT = Internal_TLLong+llongT :: Type+llongT =+ makeNumericType "long long" mempty+ (do LH.addImports hsImportForForeignC+ return $ HsTyCon $ UnQual $ HsIdent "HoppyFC.CLLong")+ Nothing BinaryCompatible BinaryCompatible -- | C++ @unsigned long long int@, Haskell 'Foreign.C.CULLong'.-ullongT = Internal_TULLong+ullongT :: Type+ullongT =+ makeNumericType "unsigned long long" mempty+ (do LH.addImports hsImportForForeignC+ return $ HsTyCon $ UnQual $ HsIdent "HoppyFC.CULLong")+ Nothing BinaryCompatible BinaryCompatible --- | C++ @float@, Haskell 'Foreign.C.CFloat'.-floatT = Internal_TFloat+-- | C++ @float@, Haskell 'Prelude.Float'. See also 'floatT''.+floatT :: Type+floatT =+ makeNumericType "float" mempty+ (do LH.addImports hsImportForPrelude+ return $ HsTyCon $ UnQual $ HsIdent "HoppyP.Float")+ (Just $ do LH.addImports hsImportForForeignC+ return $ HsTyCon $ UnQual $ HsIdent "HoppyFC.CFloat")+ convertByCoercingFloating convertByCoercingFloating --- | C++ @double@, Haskell 'Foreign.C.CDouble'.-doubleT = Internal_TDouble+-- | C++ @float@, Haskell 'Foreign.C.CFloat'. See also 'floatT'.+floatT' :: Type+floatT' =+ makeNumericType "float" mempty+ (do LH.addImports hsImportForForeignC+ return $ HsTyCon $ UnQual $ HsIdent "Foreign.C.CFloat")+ Nothing BinaryCompatible BinaryCompatible +-- | C++ @double@, Haskell 'Prelude.Double'. See also 'doubleT''.+doubleT :: Type+doubleT =+ makeNumericType "double" mempty+ (do LH.addImports hsImportForPrelude+ return $ HsTyCon $ UnQual $ HsIdent "HoppyP.Double")+ (Just $ do LH.addImports hsImportForForeignC+ return $ HsTyCon $ UnQual $ HsIdent "HoppyFC.CDouble")+ convertByCoercingFloating convertByCoercingFloating++-- | C++ @double@, Haskell 'Foreign.C.CDouble'. See also 'doubleT'.+doubleT' :: Type+doubleT' =+ makeNumericType "double" mempty+ (do LH.addImports hsImportForForeignC+ return $ HsTyCon $ UnQual $ HsIdent "Foreign.C.CDouble")+ Nothing BinaryCompatible BinaryCompatible+ -- | C++ @int8_t@, Haskell 'Data.Int.Int8'.-int8T = Internal_TInt8+int8T :: Type+int8T =+ makeNumericType "int8_t" (reqInclude $ includeStd "cstdint")+ (do LH.addImports hsImportForInt+ return $ HsTyCon $ UnQual $ HsIdent "HoppyDI.Int8")+ Nothing BinaryCompatible BinaryCompatible -- | C++ @int16_t@, Haskell 'Data.Int.Int16'.-int16T = Internal_TInt16+int16T :: Type+int16T =+ makeNumericType "int16_t" (reqInclude $ includeStd "cstdint")+ (do LH.addImports hsImportForInt+ return $ HsTyCon $ UnQual $ HsIdent "HoppyDI.Int16")+ Nothing BinaryCompatible BinaryCompatible -- | C++ @int32_t@, Haskell 'Data.Int.Int32'.-int32T = Internal_TInt32+int32T :: Type+int32T =+ makeNumericType "int32_t" (reqInclude $ includeStd "cstdint")+ (do LH.addImports hsImportForInt+ return $ HsTyCon $ UnQual $ HsIdent "HoppyDI.Int32")+ Nothing BinaryCompatible BinaryCompatible -- | C++ @int64_t@, Haskell 'Data.Int.Int64'.-int64T = Internal_TInt64+int64T :: Type+int64T =+ makeNumericType "int64_t" (reqInclude $ includeStd "cstdint")+ (do LH.addImports hsImportForInt+ return $ HsTyCon $ UnQual $ HsIdent "HoppyDI.Int64")+ Nothing BinaryCompatible BinaryCompatible -- | C++ @uint8_t@, Haskell 'Data.Word.Word8'.-word8T = Internal_TWord8+word8T :: Type+word8T =+ makeNumericType "uint8_t" (reqInclude $ includeStd "cstdint")+ (do LH.addImports hsImportForWord+ return $ HsTyCon $ UnQual $ HsIdent "HoppyDW.Word8")+ Nothing BinaryCompatible BinaryCompatible -- | C++ @uint16_t@, Haskell 'Data.Word.Word16'.-word16T = Internal_TWord16+word16T :: Type+word16T =+ makeNumericType "uint16_t" (reqInclude $ includeStd "cstdint")+ (do LH.addImports hsImportForWord+ return $ HsTyCon $ UnQual $ HsIdent "HoppyDW.Word16")+ Nothing BinaryCompatible BinaryCompatible -- | C++ @uint32_t@, Haskell 'Data.Word.Word32'.-word32T = Internal_TWord32+word32T :: Type+word32T =+ makeNumericType "uint32_t" (reqInclude $ includeStd "cstdint")+ (do LH.addImports hsImportForWord+ return $ HsTyCon $ UnQual $ HsIdent "HoppyDW.Word32")+ Nothing BinaryCompatible BinaryCompatible -- | C++ @uint64_t@, Haskell 'Data.Word.Word64'.-word64T = Internal_TWord64+word64T :: Type+word64T =+ makeNumericType "uint64_t" (reqInclude $ includeStd "cstdint")+ (do LH.addImports hsImportForWord+ return $ HsTyCon $ UnQual $ HsIdent "HoppyDW.Word64")+ Nothing BinaryCompatible BinaryCompatible -- | C++ @ptrdiff_t@, Haskell 'Foreign.C.CPtrdiff'.-ptrdiffT = Internal_TPtrdiff+ptrdiffT :: Type+ptrdiffT =+ makeNumericType "ptrdiff_t" (reqInclude $ includeStd "cstddef")+ (do LH.addImports hsImportForForeignC+ return $ HsTyCon $ UnQual $ HsIdent "HoppyFC.CPtrdiff")+ Nothing BinaryCompatible BinaryCompatible -- | C++ @size_t@, Haskell 'Foreign.C.CSize'.-sizeT = Internal_TSize+sizeT :: Type+sizeT =+ makeNumericType "size_t" (reqInclude $ includeStd "cstddef")+ (do LH.addImports hsImportForForeignC+ return $ HsTyCon $ UnQual $ HsIdent "HoppyFC.CSize")+ Nothing BinaryCompatible BinaryCompatible -- | C++ @ssize_t@, Haskell 'System.Posix.Types.CSsize'.-ssizeT = Internal_TSSize+ssizeT :: Type+ssizeT =+ makeNumericType "ssize_t" (reqInclude $ includeStd "cstddef")+ (do LH.addImports hsImportForSystemPosixTypes+ return $ HsTyCon $ UnQual $ HsIdent "HoppySPT.CSsize")+ Nothing BinaryCompatible BinaryCompatible --- | A C++ @enum@ value.-enumT = Internal_TEnum+-- | Builds a new numeric type definition.+--+-- For convenience, 'convertByCoercingIntegral' and 'convertByCoercingFloating'+-- may be used as conversion methods, for both 'ConversionMethod' arguments this+-- function takes.+makeNumericType ::+ String+ -- ^ The name of the C++ type.+ -> Reqs+ -- ^ Includes necessary to use the C++ type.+ -> LH.Generator HsType+ -- ^ Generator for rendering the Haskell type to be used, along with any+ -- required imports. See 'conversionSpecHaskellHsType'.+ -> Maybe (LH.Generator HsType)+ -- ^ If there is a Haskell type distinct from the previous argument to be+ -- used for passing over the FFI boundary, then provide it here. See+ -- 'conversionSpecHaskellCType'.+ -> ConversionMethod (LH.Generator ())+ -- ^ Method to use to convert a Haskell value to a value to be passed over+ -- the FFI. See 'conversionSpecHaskellToCppFn'.+ -> ConversionMethod (LH.Generator ())+ -- ^ Method to use to convert a value received over the FFI into a Haskell+ -- value. See 'conversionSpecHaskellFromCppFn'.+ -> Type+makeNumericType cppName cppReqs hsTypeGen hsCTypeGen convertToCpp convertFromCpp =+ Internal_TManual spec+ where spec =+ (makeConversionSpec cppName $ makeConversionSpecCpp cppName $ return cppReqs)+ { conversionSpecHaskell =+ Just $ makeConversionSpecHaskell+ hsTypeGen+ hsCTypeGen+ convertToCpp+ convertFromCpp+ } --- | A C++ bitspace value.-bitspaceT = Internal_TBitspace+-- | Conversion method for passing a numeric values to and from Haskell by using+-- @Foreign.Hoppy.Runtime.coerceIntegral@.+convertByCoercingIntegral :: ConversionMethod (LH.Generator ())+convertByCoercingIntegral = CustomConversion $ do+ LH.addImports $ mconcat [hsImport1 "Prelude" "(.)",+ hsImportForPrelude,+ hsImportForRuntime]+ LH.sayLn "HoppyP.return . HoppyFHR.coerceIntegral" +-- | Conversion method for passing a numeric values to and from Haskell by using+-- 'realToFrac'.+convertByCoercingFloating :: ConversionMethod (LH.Generator ())+convertByCoercingFloating = CustomConversion $ do+ LH.addImports $ mconcat [hsImport1 "Prelude" "(.)",+ hsImportForPrelude]+ LH.sayLn "HoppyP.return . HoppyP.realToFrac"+ -- | A pointer to another type.+ptrT :: Type -> Type ptrT = Internal_TPtr -- | A reference to another type.+refT :: Type -> Type refT = Internal_TRef --- | A function taking parameters and returning a value (or 'voidT'). Function--- pointers must wrap a 'fnT' in a 'ptrT'.-fnT = Internal_TFn---- | A handle for calling foreign code from C++.-callbackT = Internal_TCallback- -- | An instance of a class. When used in a parameter or return type and not -- wrapped in a 'ptrT' or 'refT', this is a by-value object.+objT :: Class -> Type objT = Internal_TObj -- | A special case of 'objT' that is only allowed when passing objects from -- C++ to a foreign language. Rather than looking at the object's--- 'ClassConversion', the object will be copied to the heap, and a pointer to--- the heap object will be passed. The object must be copy-constructable.+-- 'Foreign.Hoppy.Generator.Spec.Class.ClassConversion', the object will be+-- copied to the heap, and a pointer to the heap object will be passed. The+-- object must be copy-constructable. -- -- __The foreign language owns the pointer, even for callback arguments.__+objToHeapT :: Class -> Type objToHeapT = Internal_TObjToHeap -- | This type transfers ownership of the object to the foreign language's@@ -179,7 +424,13 @@ -- - @'toGcT' ('refT' ('objT' cls))@ -- - @'toGcT' ('ptrT' ('constT' ('objT' cls)))@ -- - @'toGcT' ('ptrT' ('objT' cls))@+toGcT :: Type -> Type toGcT = Internal_TToGc +-- | Constructs a type from a specification of how to convert values.+manualT :: ConversionSpec -> Type+manualT = Internal_TManual+ -- | A @const@ version of another type.+constT :: Type -> Type constT = Internal_TConst
+ src/Foreign/Hoppy/Generator/Util.hs view
@@ -0,0 +1,115 @@+-- This file is part of Hoppy.+--+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net>+--+-- 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 <http://www.gnu.org/licenses/>.++-- | Miscellaneous utilities that may be useful in Hoppy generators.+module Foreign.Hoppy.Generator.Util (+ -- * String utilities+ splitIntoWords,+ -- * File utilities+ withTempFile,+ withTempDirectory,+ ) where++import Control.Exception (IOException, catch, throwIO)+import Control.Monad (when)+import Data.Char (isDigit, isLetter, isLower, isUpper)+import System.Directory (+ doesDirectoryExist,+ doesFileExist,+ getTemporaryDirectory,+ removeFile,+ removeDirectoryRecursive,+ )+import System.IO (Handle, openTempFile)+import System.IO.Temp (createTempDirectory)++-- | Splits a C++ identifier string into multiple words, doing smart inspection+-- of the case convention of the string. This supports @snake_case@ and+-- @CONSTANT_CASE@, and recognition of @camelCase@, including when acronyms are+-- uppercased (@\"HTMLElement\"@ gives @[\"HTML\", \"Element\"]@). Numbers are+-- treated as their own words, and non-alphanumeric characters are treated as+-- word separators and dropped.+splitIntoWords :: String -> [String]+splitIntoWords cs = case cs of+ "" -> []++ -- The case of multiple upper-case letters, e.g. "HTML", or "HTMLElement".+ c1:c2:_ | isUpper c1 && isUpper c2 ->+ let (upperWord, rest) = span isUpper cs+ (upperWord', rest') = case rest of+ -- This handles the "HTMLElement" case, where we need to shift from+ -- "HTMLE"/"lement" to "HTML"/"Element".+ c3:_ | isLower c3 ->+ let (word, lastChar) = splitAt (length upperWord - 1) upperWord+ in (word, lastChar ++ rest)+ -- But if the part after the upper case part doesn't start with a+ -- lower case letter, then there's no rearranging to do.+ _ -> (upperWord, rest)+ in upperWord' : splitIntoWords rest'++ -- The case of letters, but not multiple upper case letters. Here we want to+ -- take grab "foo" from "fooBar", "a" from "aWidget", and "Too" from "TooNie".+ c1:cs' | isLetter c1 ->+ let (wordTail, rest) = span isLower cs'+ in (c1:wordTail) : splitIntoWords rest++ -- Numbers get treated as their own words.+ c1:_ | isDigit c1 ->+ let (word, rest) = span isDigit cs+ in word : splitIntoWords rest++ -- Non-alphanumeric characters may act as word barriers, but otherwise get+ -- dropped.+ _:cs' -> splitIntoWords cs'++-- | Creates a temporary file whose name is based on the given template string,+-- and runs the given function with the path to the file. The file is deleted+-- when the function completes, if the boolean that the function returns (or, in+-- case of an exception, the boolean that was passed directly to 'withTempFile')+-- is true.+withTempFile :: String -> Bool -> (FilePath -> Handle -> IO (Bool, a)) -> IO a+withTempFile template deleteOnException f = do+ tempDir <- getTemporaryDirectory+ (path, handle) <- openTempFile tempDir template+ catch (do (delete, result) <- f path handle+ when delete $ removeFileIfExists path+ return result)+ (\(e :: IOException) -> do+ when deleteOnException $ removeFileIfExists path+ throwIO e)+ where removeFileIfExists path = do+ exists <- doesFileExist path+ when exists $ removeFile path++-- | Creates a temporary directory whose name is based on the given template+-- string, and runs the given function with the directory's path. The directory+-- is deleted when the function completes, if the boolean that the function+-- returns (or, in case of an exception, the boolean that was passed directly to+-- 'withTempDirectory') is true.+withTempDirectory :: String -> Bool -> (FilePath -> IO (Bool, a)) -> IO a+withTempDirectory template deleteOnException f = do+ outerDir <- getTemporaryDirectory+ dir <- createTempDirectory outerDir template+ catch (do (delete, result) <- f dir+ when delete $ removeIfExists dir+ return result)+ (\(e :: IOException) -> do+ when deleteOnException $ removeIfExists dir+ throwIO e)+ where removeIfExists dir = do+ exists <- doesDirectoryExist dir+ when exists $ removeDirectoryRecursive dir
src/Foreign/Hoppy/Generator/Version.hs view
@@ -1,6 +1,6 @@ -- This file is part of Hoppy. ----- Copyright 2015-2018 Bryan Gardiner <bog@khumba.net>+-- Copyright 2015-2019 Bryan Gardiner <bog@khumba.net> -- -- 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