packages feed

hs-bindgen-1.0.0.0: src-internal/HsBindgen/Frontend/Pass/Parse/Context.hs

module HsBindgen.Frontend.Pass.Parse.Context (
    -- * Declaration and parse contexts
    RequiredForScoping(..)
  , DeclCtx(..)
  , ParseCtx -- opaque
  , mkCtx
  , pushCtx

    -- * Errors
  , ExceptionInCtx(..)
  , addCtxHandler
  ) where

import Control.Exception (Exception (..), SomeException)
import Data.List.NonEmpty ((<|))
import Data.List.NonEmpty qualified as NonEmpty
import Data.Typeable (Typeable)
import GHC.Records (HasField (..))

import HsBindgen.Imports
import HsBindgen.IR.C qualified as C

-- | We always need to parse declarations required for scoping as they may
--   affect other declarations
data RequiredForScoping =
    RequiredForScoping
  | NotRequiredForScoping
  deriving stock (Show, Eq, Ord)

data DeclCtx = DeclCtx {
     kind    :: C.NameKind
   , scoping :: RequiredForScoping
   }
  deriving stock (Show, Eq, Ord)

-- | The parse context stores information about the inner-most declaration
--   (i.e., the one we are parsing right now), and the outer-most declaration
--   (i.e., the top-level declaration).
data ParseCtx = ParseCtx { _ctxs  :: NonEmpty DeclCtx }
  deriving stock (Show, Eq, Ord)

mkCtx :: DeclCtx -> ParseCtx
mkCtx ctx = ParseCtx{
      _ctxs  = NonEmpty.singleton ctx
    }

pushCtx :: DeclCtx -> ParseCtx -> ParseCtx
pushCtx ctx e = ParseCtx{
      _ctxs = ctx <| e._ctxs
    }

instance HasField "inner" ParseCtx DeclCtx where
  getField = NonEmpty.head . (._ctxs)

instance HasField "outer" ParseCtx DeclCtx where
  getField  = NonEmpty.last . (._ctxs)

{-------------------------------------------------------------------------------
  Errors

  Dealing with errors while parsing is tricky:

  * The Clang API does not make it very easy to associate an error with a source
    location, so it's hard to produce a useful error message.

  * Parsing always happens whilst processing some enclosing declaration. When we
    encounter something unsupported (or unexpected), we'll want to register a
    parse failure and avoid generating bindings for that declaration, but we do
    not have sufficient context to do so.

  For both of these reasons we simply throw an exception, and then /catch/
  that exception in 'foldDec'. This allows us to address both of these issues:
  a declaration has a clear source location, so we can generate a helpful error
  message, and we can skip the declaration we're currently processing.
-------------------------------------------------------------------------------}

data ExceptionInCtx e = ExceptionInCtx {
      exception :: e
    , ctx       :: ParseCtx
    }
  deriving stock (Show, Eq, Ord, Generic)

instance (Show e, Typeable e) => Exception (ExceptionInCtx e)

addCtxHandler ::
     forall e a. (Exception e)
  => Proxy e -> ParseCtx -> SomeException -> IO a
addCtxHandler _p c e
  | Just e' <- (fromException @e e) =
      throwIO (ExceptionInCtx e' c)
  | otherwise = throwIO e