packages feed

libclang-bindings-0.1.0.0: src/Clang/Internal/Results.hs

{-# LANGUAGE AllowAmbiguousTypes #-}

-- | Utilities for checking the results of C functions
module Clang.Internal.Results (
    -- * Failed calls
    CallFailed(..)
  , callFailed
  , callFailedShow
    -- * Specific conditions
  , cToBool
  , ensure
  , ensureOn
  , ensureNotNull
  , checkNotNull
  , ensureNotInRange
    -- * Auxiliary
  , IsNullPtr(..)
  ) where

import Control.Exception
import Control.Monad.IO.Class
import Data.Coerce
import Foreign
import GHC.Stack

import Clang.Backtrace
import Clang.Enum.Simple
import Clang.LowLevel.Core.Instances ()

{-------------------------------------------------------------------------------
  Failed calls
-------------------------------------------------------------------------------}

-- | Call to @libclang@ failed
--
-- In @libclang@, being a C framework, errors are returned as values; in order
-- to ensure that we don't forget to check for these error values, we turn them
-- into 'CallFailed' exceptions.
data CallFailed = CallFailed String Backtrace
  deriving stock (Show)
  deriving Exception via CollectedBacktrace CallFailed

callFailed :: (MonadIO m, HasCallStack) => String -> m a
callFailed hint = do
    stack <- collectBacktrace
    liftIO $ throwIO $ CallFailed hint stack

callFailedShow :: (MonadIO m, Show hint, HasCallStack) => hint -> m a
callFailedShow = callFailed . show

{-------------------------------------------------------------------------------
  Specific conditions
-------------------------------------------------------------------------------}

cToBool :: (Num a, Eq a) => a -> Bool
cToBool 0 = False
cToBool _ = True

-- | Check result for error value
ensure :: (HasCallStack, Show a) => (a -> Bool) -> IO a -> IO a
ensure = ensureOn id

-- | Generalization of 'ensure' with an additional translation step
--
-- This is useful in cases where the value that should be included in the
-- exception should not be the original value but the translated one.
ensureOn :: (HasCallStack, Show b)
  => (a -> b)
  -> (b -> Bool)
  -> IO a -> IO a
ensureOn f p call = do
    x <- call
    if p (f x)
      then return x
      else callFailedShow (f x)

-- | Ensure that a function did not return 'nullPtr' (indicating error)
ensureNotNull ::
     (HasCallStack, IsNullPtr a, Show a)
  => IO a -> IO a
ensureNotNull = ensure (not . isNullPtr)

-- | If the result is 'nullPtr', return 'Nothing'
checkNotNull :: IsNullPtr a => IO a -> IO (Maybe a)
checkNotNull call = do
    ptr <- call
    return $ if isNullPtr ptr
               then Nothing
               else Just ptr

-- | Ensure that the result is not in the range of the specified enum
--
-- This is used for functions which return errors from a specified enum, such as
-- @clang_Type_getSizeOf@, which will return errors from the 'CXTypeLayoutError'
-- enum.
--
-- Intended for use with a type argument:
--
-- > ensureNotInRange @CXTypeLayoutError $
-- >   wrap_Type_getSizeOf typ'
ensureNotInRange :: forall hs a.
     (HasCallStack, Integral a, Show hs, IsSimpleEnum hs)
  => IO a -> IO a
ensureNotInRange = ensureOn conv (not . simpleEnumInRange)
  where
    conv :: a -> SimpleEnum hs
    conv = coerceSimpleEnum . fromIntegral

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

class IsNullPtr a where
  isNullPtr :: a -> Bool

instance IsNullPtr (Ptr a) where
  isNullPtr ptr = ptr' == nullPtr
    where
      ptr' :: Ptr x
      ptr' = coerce ptr