packages feed

hs-bindgen-1.0.0.0: src-internal/HsBindgen/BindingSpec/Gen.hs

-- | Binding specification generation
--
-- Intended for qualified import.
--
-- > import HsBindgen.BindingSpec.Gen qualified as BindingSpec
module HsBindgen.BindingSpec.Gen (
    -- * Public API
    genBindingSpec
  ) where

import Data.ByteString (ByteString)
import Data.List.NonEmpty qualified as NonEmpty
import Data.Map.Strict qualified as Map
import Data.Ord qualified as Ord
import Data.Set qualified as Set

import Clang.HighLevel.Types

import HsBindgen.Backend.Hs.AST qualified as Hs
import HsBindgen.Backend.Hs.Origin qualified as HsOrigin
import HsBindgen.BindingSpec.Private.Common
import HsBindgen.BindingSpec.Private.V1 (CTypeSpec (..), UnresolvedBindingSpec)
import HsBindgen.BindingSpec.Private.V1 qualified as BindingSpec
import HsBindgen.Errors
import HsBindgen.Frontend.Analysis.DeclIndex (DeclIndex)
import HsBindgen.Frontend.Analysis.DeclIndex qualified as DeclIndex
import HsBindgen.Frontend.Analysis.IncludeGraph (IncludeGraph)
import HsBindgen.Frontend.Analysis.IncludeGraph qualified as IncludeGraph
import HsBindgen.Frontend.Pass.Final
import HsBindgen.Frontend.Pass.ResolveBindingSpecs.IsPass qualified as ResolveBindingSpecs
import HsBindgen.Frontend.ProcessIncludes
import HsBindgen.Imports
import HsBindgen.Instances qualified as Inst
import HsBindgen.IR.C qualified as C
import HsBindgen.IR.Translation
import HsBindgen.Language.Haskell qualified as Hs

{-------------------------------------------------------------------------------
  Public API
-------------------------------------------------------------------------------}

-- | Generate binding specification
genBindingSpec ::
     Format
  -> Hs.ModuleName
  -> IncludeGraph
  -> DeclIndex l
  -> GetMainHeaders
  -> [(C.DeclId, RealPath)]
  -> [(C.DeclId, (RealPath, Hs.Name Hs.NsTypeConstr))]
  -> [Hs.Decl l]
  -> ByteString
genBindingSpec
  format
  hsModuleName
  includeGraph
  declIndex
  getMainHeaders
  omitTypes
  squashedTypes =
      BindingSpec.encode compareCDeclId format
    . genBindingSpec' hsModuleName getMainHeaders omitTypes squashedTypes
  where
    compareCDeclId :: C.DeclId -> C.DeclId -> Ordering
    compareCDeclId cDeclIdL cDeclIdR = Ord.comparing aux cDeclIdL cDeclIdR

    aux :: C.DeclId -> (IncludeGraph.IncludeOrderIx, Int, Int, Text)
    aux cDeclId =
      case DeclIndex.lookupLoc cDeclId declIndex of
        Just locs ->
          -- NOTE: At the moment, we attempt to get the “minimum” location and
          -- discard other conflicting locations (e.g., the ones of other
          -- colliding definitions). This is OK, since we only use the location
          -- to sort the binding specifications before generating them.
          let loc = C.declLocsMin locs
              orderIx = IncludeGraph.lookupIncludeOrder order (singleLocPath loc)
          in  ( orderIx
              , loc.singleLocLine
              , loc.singleLocColumn
              , C.renderDeclId cDeclId
              )
        Nothing -> (
            IncludeGraph.NotInIncludeGraph
          , maxBound
          , maxBound
          , C.renderDeclId cDeclId
          )

    order :: IncludeGraph.IncludeOrder
    order = IncludeGraph.toIncludeOrder includeGraph

{-------------------------------------------------------------------------------
  Auxiliary functions
-------------------------------------------------------------------------------}

-- TODO <https://github.com/well-typed/hs-bindgen/issues/1436>
-- TODO <https://github.com/well-typed/hs-bindgen/issues/1549>
-- Once squashing is configurable (#1436), we should deal with aliases (#1549).
genBindingSpec' ::
     Hs.ModuleName
  -> GetMainHeaders
  -> [(C.DeclId, RealPath)]
  -> [(C.DeclId, (RealPath, Hs.Name Hs.NsTypeConstr))]
  -> [Hs.Decl l]
  -> UnresolvedBindingSpec
genBindingSpec' hsModuleName getMainHeaders omitTypes squashedTypes =
    foldr aux spec0
  where
    spec0 :: UnresolvedBindingSpec
    spec0 = BindingSpec.BindingSpec {
        moduleName = hsModuleName
      , cTypes     = Map.fromListWith (++) $
          [ (cDeclId, [(getMainHeaders' path, Omit)])
          | (cDeclId, path) <- omitTypes
          ] ++
          [ let headers   = getMainHeaders' sourcePath
                cTypeSpec :: CTypeSpec
                cTypeSpec = CTypeSpec{
                    hsName = Just hsName
                  , enum   = Nothing
                  }
            in  (cDeclId, [(headers, Require cTypeSpec)])
          | (cDeclId, (sourcePath, hsName)) <- squashedTypes
          ]
      , hsTypes = Map.empty
      }

    getMainHeaders' :: RealPath -> Set C.HashIncludeArg
    getMainHeaders' =
        either
          (\s -> panicPure ("Could not get main headers: " ++ s))
          (Set.fromList . NonEmpty.toList)
      . getMainHeaders

    aux ::
         Hs.Decl l
      -> UnresolvedBindingSpec
      -> UnresolvedBindingSpec
    aux = \case
      Hs.DeclTypSyn typSyn          -> insertType $ auxTypSyn    typSyn
      Hs.DeclData struct            -> insertType $ auxStruct    struct
      Hs.DeclEmpty edata            -> insertType $ auxEmptyData edata
      Hs.DeclNewtype ntype          ->
        case ntype.origin.kind of
          HsOrigin.Aux{} -> id
          _otherwise     -> insertType $ auxNewtype ntype
      Hs.DeclPatSyn{}               -> id
      Hs.DeclCompletePragma{}       -> id
      Hs.DeclDefineInstance{}       -> id
      Hs.DeclDeriveInstance{}       -> id
      Hs.DeclForeignImport{}        -> id
      Hs.DeclForeignImportWrapper{} -> id
      Hs.DeclForeignImportDynamic{} -> id
      Hs.DeclFunction{}             -> id
      Hs.DeclMacroValue{}           -> id
      Hs.DeclVar{}                  -> id

    -- TODO <https://github.com/well-typed/hs-bindgen/issues/2284>
    --
    -- Bindings specifications for macros defined on the command line or the
    -- root header.
    insertType ::
         ( (C.DeclInfo Final, BindingSpec.CTypeSpec)
         , (Hs.Name Hs.NsTypeConstr, BindingSpec.HsTypeSpec)
         )
      -> UnresolvedBindingSpec
      -> UnresolvedBindingSpec
    insertType ((declInfo, cTypeSpec), (hsName, hsTypeSpec)) spec =
      case singleLocPath declInfo.loc of
        C.InHeader path ->
          spec
            & #cTypes %~
                Map.insertWith (++)
                  declInfo.id.cName
                  [(getMainHeaders' path, Require cTypeSpec)]
            & #hsTypes %~
                Map.insert hsName hsTypeSpec
        -- Not in any header, so no binding specification can refer to it
        C.InRootHeader  -> spec
        C.OnCommandLine -> spec

    auxTypSyn ::
         Hs.TypSyn
      -> ( (C.DeclInfo Final, BindingSpec.CTypeSpec)
         , (Hs.Name Hs.NsTypeConstr, BindingSpec.HsTypeSpec)
         )
    auxTypSyn typSyn =
      let cTypeSpec = BindingSpec.CTypeSpec {
              hsName = Just typSyn.name
            , enum   = Nothing
            }
          hsTypeSpec = BindingSpec.HsTypeSpec {
              hsRep     = Just BindingSpec.HsTypeRepTypeAlias
            , instances = Map.empty
            }
      in  ( (typSyn.origin.info, cTypeSpec)
          , (typSyn.name, hsTypeSpec)
          )

    auxStruct ::
         Hs.Struct
      -> ( (C.DeclInfo Final, BindingSpec.CTypeSpec)
         , (Hs.Name Hs.NsTypeConstr, BindingSpec.HsTypeSpec)
         )
    auxStruct hsStruct = case hsStruct.origin of
      Nothing -> panicPure "Origin of structure unavailable"
      Just originDecl ->
        let cTypeSpec = BindingSpec.CTypeSpec {
                hsName = Just hsStruct.name
              , enum   = Nothing
              }
            hsRecordRep = BindingSpec.HsRecordRep {
                constructor = Just hsStruct.constr
              , fields      = Just [
                    field.name
                  | field <- hsStruct.fields
                  ]
              }
            hsTypeSpec = BindingSpec.HsTypeSpec {
                hsRep     = Just $ BindingSpec.HsTypeRepRecord hsRecordRep
              , instances =
                  mkInstSpecs
                    (maybe Map.empty (.instances) $ originDecl.spec.hsSpec)
                    hsStruct.instances
              }
        in  ( (originDecl.info, cTypeSpec)
            , (hsStruct.name, hsTypeSpec)
            )

    auxEmptyData ::
         Hs.EmptyData
      -> ( (C.DeclInfo Final, BindingSpec.CTypeSpec)
         , (Hs.Name Hs.NsTypeConstr, BindingSpec.HsTypeSpec)
         )
    auxEmptyData edata =
      let originDecl   = edata.origin
          cTypeSpec = BindingSpec.CTypeSpec {
              hsName = Just edata.name
            , enum   = Nothing
            }
          hsTypeSpec = BindingSpec.HsTypeSpec {
              hsRep     = Just BindingSpec.HsTypeRepEmptyData
            , instances =
                mkInstSpecs
                  (maybe Map.empty (.instances) $ originDecl.spec.hsSpec)
                  edata.instances
            }
      in  ( (originDecl.info, cTypeSpec)
          , (edata.name, hsTypeSpec)
          )

    auxNewtype ::
         Hs.Newtype
      -> ( (C.DeclInfo Final, BindingSpec.CTypeSpec)
         , (Hs.Name Hs.NsTypeConstr, BindingSpec.HsTypeSpec)
         )
    auxNewtype hsNewtype =
      let originDecl   = hsNewtype.origin
          cTypeSpec    = BindingSpec.CTypeSpec {
              hsName = Just hsNewtype.name
            , enum   =
                case originDecl.spec.cSpec of
                  Just spec -> spec.enum
                  Nothing   ->
                    case originDecl.kind of
                      HsOrigin.Enum{} -> Just BindingSpec.CEnumOpen
                      _otherwise      -> Nothing
            }
          hsNewtypeRep = BindingSpec.HsNewtypeRep {
              constructor = Just hsNewtype.constr
            , field       = Just hsNewtype.field.name
            , ffiType     = hsNewtype.ffiType
            }
          hsTypeSpec = BindingSpec.HsTypeSpec {
              hsRep     = Just $ BindingSpec.HsTypeRepNewtype hsNewtypeRep
            , instances =
                mkInstSpecs
                  (maybe Map.empty (.instances) $ originDecl.spec.hsSpec)
                  hsNewtype.instances
            }
      in  ( (originDecl.info, cTypeSpec)
          , (hsNewtype.name, hsTypeSpec)
          )

-- TODO <https://github.com/well-typed/hs-bindgen/issues/1766>
-- We should allow users to specify strategies for instance deriving.
--
-- TODO <https://github.com/well-typed/hs-bindgen/issues/648>
-- It should be possible to generate instances with constraints.
mkInstSpecs ::
     Map Inst.TypeClass (Omittable BindingSpec.InstanceSpec)
  -> Set Inst.TypeClass
  -> Map Inst.TypeClass (Omittable BindingSpec.InstanceSpec)
mkInstSpecs specMap insts = Map.fromList $
    [ (cls, Require def)
    | cls <- Set.toList insts
    ]
    ++
    [ (cls, Omit)
    | (cls, Omit) <- Map.toList specMap
    ]