hs-bindgen-1.0.0.0: src-internal/HsBindgen/BindingSpec.hs
-- | Binding specification
--
-- Intended for qualified import.
--
-- > import HsBindgen.BindingSpec qualified as BindingSpec
module HsBindgen.BindingSpec (
-- * Public API
-- ** Types
BindingSpec -- opaque
, ExternalBindingSpec
, PrescriptiveBindingSpec
, ResolvedExtBinding(..)
-- ** Configuration
, EnableStdlibBindingSpec(..)
, BindingSpecConfig(..)
, Version.BindingSpecCompatibility(..)
-- ** Loading
, getStdlibBindingSpec
, loadExtBindingSpecs
, loadPrescriptiveBindingSpec
, loadBindingSpecs
-- ** Encoding
, Common.Format(..)
, Common.getFormat
, encode
-- ** Trace messages
, Common.BindingSpecReadMsg(..)
, Common.BindingSpecResolveMsg(..)
, Common.BindingSpecMergeMsg(..)
, Common.BindingSpecMsg(..)
-- * Internal API
, BindingSpec.currentBindingSpecVersion
, empty
, moduleName
-- ** Types
, Common.Omittable(..)
, BindingSpec.CTypeSpec(..)
, BindingSpec.CEnumSpec(..)
, BindingSpec.HsTypeSpec(..)
, BindingSpec.HsTypeRep(..)
, BindingSpec.HsRecordRep(..)
, BindingSpec.HsNewtypeRep(..)
, BindingSpec.HsFFIType(..)
, BindingSpec.hsSpecFFIType
, BindingSpec.InstanceSpec(..)
-- ** Query
, getCTypes
, lookupCTypeSpec
, lookupHsTypeSpec
-- ** Merging
, BindingSpec.MergedBindingSpecs
, BindingSpec.lookupMergedBindingSpecs
) where
import Data.ByteString (ByteString)
import Data.Ord qualified as Ord
import Clang.Args (ClangArgs)
import Clang.Paths (RealPath)
import HsBindgen.BindingSpec.Private.Common qualified as Common
import HsBindgen.BindingSpec.Private.Stdlib qualified as Stdlib
import HsBindgen.BindingSpec.Private.V1 qualified as BindingSpec
import HsBindgen.BindingSpec.Private.Version qualified as Version
import HsBindgen.Imports
import HsBindgen.IR.C qualified as C
import HsBindgen.Language.Haskell qualified as Hs
import HsBindgen.Util.Monad
import HsBindgen.Util.Tracer
{-------------------------------------------------------------------------------
Public API: Types
-------------------------------------------------------------------------------}
-- | Binding specification
--
-- A binding specification serves two purposes:
--
-- * A /prescriptive binding specification/ is used to configure how bindings
-- are generated.
-- * An /external binding specification/ is used to specify existing bindings
-- that should be used, /external/ from the module being generated.
--
-- Note that a /generated binding specification/ may be used for either/both of
-- these two purposes.
data BindingSpec = BindingSpec {
unresolved :: BindingSpec.UnresolvedBindingSpec
, resolved :: BindingSpec.ResolvedBindingSpec
}
deriving stock (Show, Eq)
-- | External binding specification
--
-- This type alias is just used as documentation.
type ExternalBindingSpec = BindingSpec
-- | Prescriptive binding specification
--
-- This type alias is just used as documentation.
type PrescriptiveBindingSpec = BindingSpec
-- | Resolved external binding
data ResolvedExtBinding = ResolvedExtBinding{
-- | C declaration for which we are using this binding
cName :: C.DeclId
-- | Source location(s) of C declaration for which we are using this binding
, locs :: C.DeclLocs
-- | The Haskell type which will be used
, hsName :: Hs.ExtRef
-- | Additional information about the C type
, cSpec :: BindingSpec.CTypeSpec
-- | Additional information about the Haskell type
, hsSpec :: BindingSpec.HsTypeSpec
}
deriving stock (Eq, Ord, Generic, Show)
{-------------------------------------------------------------------------------
Public API: Configuration
-------------------------------------------------------------------------------}
-- | Configure if the @stdlib@ binding specification should be used
data EnableStdlibBindingSpec =
-- | Automatically include @stdlib@
EnableStdlibBindingSpec
-- | Do not include @stdlib@
| DisableStdlibBindingSpec
deriving stock (Show, Eq)
instance Default EnableStdlibBindingSpec where
def = EnableStdlibBindingSpec
data BindingSpecConfig = BindingSpecConfig {
stdlibSpec :: EnableStdlibBindingSpec
, compatibility :: Version.BindingSpecCompatibility
, extBindingSpecs :: [FilePath]
, prescriptiveBindingSpec :: Maybe FilePath
}
deriving stock (Show, Eq, Generic)
instance Default BindingSpecConfig where
def = BindingSpecConfig {
stdlibSpec = EnableStdlibBindingSpec
, compatibility = def
, extBindingSpecs = []
, prescriptiveBindingSpec = Nothing
}
{-------------------------------------------------------------------------------
Public API: Loading
-------------------------------------------------------------------------------}
-- | Get the standard library external binding specification
getStdlibBindingSpec ::
Tracer Common.BindingSpecMsg
-> ClangArgs
-> IO ExternalBindingSpec
getStdlibBindingSpec tracer args = BindingSpec Stdlib.bindingSpec <$>
BindingSpec.resolve
(contramap Common.BindingSpecResolveMsg tracer)
Common.BindingSpecResolveExternalHeader
args
Stdlib.bindingSpec
-- | Load external binding specifications
--
-- The format is determined by filename extension. The following formats are
-- supported:
--
-- * YAML (@.yaml@ extension)
-- * JSON (@.json@ extension)
loadExtBindingSpecs ::
Tracer Common.BindingSpecMsg
-> ClangArgs
-> EnableStdlibBindingSpec
-> Version.BindingSpecCompatibility
-> [FilePath]
-> IO BindingSpec.MergedBindingSpecs
loadExtBindingSpecs tracer args enableStdlib cmpt paths = do
uspecs <- withStdlib <$> mapMaybeM read' paths
rspecs <- mapM resolve' uspecs
let (msgs, mspec) = BindingSpec.merge rspecs
mapM_ (traceWith tracerMerge . withCallStack) msgs
return mspec
where
withStdlib ::
[BindingSpec.UnresolvedBindingSpec]
-> [BindingSpec.UnresolvedBindingSpec]
withStdlib = case enableStdlib of
DisableStdlibBindingSpec -> id
EnableStdlibBindingSpec -> (Stdlib.bindingSpec :)
read' :: FilePath -> IO (Maybe BindingSpec.UnresolvedBindingSpec)
read' = BindingSpec.readFile tracerRead cmpt Nothing
resolve' ::
BindingSpec.UnresolvedBindingSpec
-> IO BindingSpec.ResolvedBindingSpec
resolve' =
BindingSpec.resolve
tracerResolve
Common.BindingSpecResolveExternalHeader
args
tracerRead :: Tracer Common.BindingSpecReadMsg
tracerRead = contramap Common.BindingSpecReadMsg tracer
tracerResolve :: Tracer Common.BindingSpecResolveMsg
tracerResolve = contramap Common.BindingSpecResolveMsg tracer
tracerMerge :: Tracer Common.BindingSpecMergeMsg
tracerMerge = contramap Common.BindingSpecMergeMsg tracer
-- | Load prescriptive binding specification
--
-- The format is determined by filename extension. The following formats are
-- supported:
--
-- * YAML (@.yaml@ extension)
-- * JSON (@.json@ extension)
loadPrescriptiveBindingSpec ::
Tracer Common.BindingSpecMsg
-> ClangArgs
-> Hs.ModuleName
-> Version.BindingSpecCompatibility
-> Maybe FilePath
-> IO PrescriptiveBindingSpec
loadPrescriptiveBindingSpec tracer args hsModuleName cmpt =
fmap (fromMaybe $ empty hsModuleName) . \case
Nothing -> return Nothing
Just path ->
BindingSpec.readFile tracerRead cmpt (Just hsModuleName) path >>= \case
Nothing -> return Nothing
Just uspec ->
Just . BindingSpec uspec <$>
BindingSpec.resolve
tracerResolve
Common.BindingSpecResolvePrescriptiveHeader
args
uspec
where
tracerRead :: Tracer Common.BindingSpecReadMsg
tracerRead = contramap Common.BindingSpecReadMsg tracer
tracerResolve :: Tracer Common.BindingSpecResolveMsg
tracerResolve = contramap Common.BindingSpecResolveMsg tracer
-- | A combination of 'loadExtBindingSpecs' and 'loadPrescriptiveBindingSpec'
loadBindingSpecs ::
Tracer Common.BindingSpecMsg
-> ClangArgs
-> Hs.ModuleName
-> BindingSpecConfig
-> IO (BindingSpec.MergedBindingSpecs, PrescriptiveBindingSpec)
loadBindingSpecs tracer args hsModuleName config =
(,)
<$> loadExtBindingSpecs
tracer
args
config.stdlibSpec
config.compatibility
config.extBindingSpecs
<*> loadPrescriptiveBindingSpec
tracer
args
hsModuleName
config.compatibility
config.prescriptiveBindingSpec
{-------------------------------------------------------------------------------
Public API: Encoding
-------------------------------------------------------------------------------}
-- | Encode a binding specification
encode :: Common.Format -> BindingSpec -> ByteString
encode format spec = BindingSpec.encode defCompareCDeclId format spec.unresolved
where
defCompareCDeclId :: C.DeclId -> C.DeclId -> Ordering
defCompareCDeclId = Ord.comparing C.renderDeclId
{-------------------------------------------------------------------------------
Internal API
-------------------------------------------------------------------------------}
-- | Construct an empty binding specification for the given module
empty :: Hs.ModuleName -> BindingSpec
empty hsModuleName = BindingSpec{
unresolved = BindingSpec.empty hsModuleName
, resolved = BindingSpec.empty hsModuleName
}
-- | Get the module name for a binding specification
moduleName :: BindingSpec -> Hs.ModuleName
moduleName spec = spec.unresolved.moduleName
{-------------------------------------------------------------------------------
Internal API: Query
-------------------------------------------------------------------------------}
-- | Get the C types in a binding specification
getCTypes :: BindingSpec -> Map C.DeclId [Set RealPath]
getCTypes spec = BindingSpec.getCTypes spec.resolved
-- | Lookup the @'Common.Omittable' 'BindingSpec.CTypeSpec'@ associated with a C
-- type
lookupCTypeSpec ::
C.DeclId
-> Set RealPath
-> BindingSpec
-> Maybe (Hs.ModuleName, Common.Omittable BindingSpec.CTypeSpec)
lookupCTypeSpec cDeclId headers spec =
BindingSpec.lookupCTypeSpec cDeclId headers spec.resolved
-- | Lookup the 'BindingSpec.HsTypeSpec' associated with a Haskell type
lookupHsTypeSpec ::
Hs.Name Hs.NsTypeConstr
-> BindingSpec
-> Maybe BindingSpec.HsTypeSpec
lookupHsTypeSpec hsIdentifier spec =
BindingSpec.lookupHsTypeSpec hsIdentifier spec.resolved