{-| Module : Foreign.Marshal.Codensity Description : A Codensity-based wrapper for Haskell-to-C marshalling functions. Copyright : (c) Alexis Williams, 2019 License : MPL-2.0 Maintainer : alexis@typedr.at Stability : provisional Portability : portable This library wraps the standard @base@ bracketed allocation primitives (along with those from "Data.ByteString" and "Data.ByteString.Short") in a 'Codensity'-based interface to ease the chaining of complex marshalling operations. -} module Foreign.Marshal.Codensity ( -- * @alloca@ alloca, allocaWith, allocaBytes, allocaBytesAligned -- * @calloc@ , calloc, callocBytes -- * @allocaArray@ , allocaArray, allocaArrayWith, allocaArrayWithOf , allocaArray0, allocaArrayWith0, allocaArrayWith0Of -- * @callocArray@ , callocArray, callocArray0 -- * @withForeignPtr@ (and alternatives) , withForeignPtr , bracketCodensity -- * @ToCString@ , ToCString(..) -- * Reexports , CString, CStringLen , Ptr, nullPtr , Codensity(..) , lowerCodensity -- * Projected variants -- | These variants work in the same way as their corresponding functions -- without the terminal prime, but with a function argument that projects -- the results of the (possibly implicit) 'Fold' into a 'Storable' value. -- -- As in the @lens@ library, the variants beginning with an @i@ use -- 'IndexedFold's rather than 'Fold's, and no versions without terminal -- primes exist because there is no generic enough use for indexes to -- give a sensible default. , AnIndexedFold , allocaArrayWith', allocaArrayWithOf' , iallocaArrayWith', iallocaArrayWithOf' , allocaArrayWith0', allocaArrayWith0Of' , iallocaArrayWith0', iallocaArrayWith0Of' ) where import Control.Exception ( finally ) import Control.Lens.Fold import Control.Lens.Getter import Control.Lens.Indexed -- For documentation: import Control.Lens.Type ( IndexedTraversal, IndexedLens ) import Control.Monad.Codensity import Control.Monad.IO.Class import qualified Data.ByteString as BS import qualified Data.ByteString.Short as SBS import Data.Foldable ( foldrM ) import Data.Functor ( ($>) ) import qualified Foreign.C.String as C import Foreign.C.String ( CString, CStringLen ) import qualified Foreign.ForeignPtr as C import Foreign.ForeignPtr ( ForeignPtr ) import qualified Foreign.Marshal.Alloc as C import qualified Foreign.Marshal.Array as C import Foreign.Marshal.Utils ( fillBytes ) import Foreign.Ptr import Foreign.Storable -- | 'alloca' @\@a@ is a continuation that provides access to a pointer into a -- temporary block of memory sufficient to hold values of type @a@. alloca :: Storable a => Codensity IO (Ptr a) alloca = Codensity C.alloca {-# INLINE alloca #-} -- | 'allocaWith' @a@ is a continuation that provides access to a pointer into -- a temporary block of memory containing @a@. allocaWith :: Storable a => a -> Codensity IO (Ptr a) allocaWith val = do ptr <- alloca liftIO $ poke ptr val return ptr {-# INLINE allocaWith #-} -- | 'allocaBytes' @n@ is a continuation that provides access to a pointer into -- a temporary block of memory sufficient to hold @n@ bytes, with -- machine-standard alignment. allocaBytes :: Int -> Codensity IO (Ptr a) allocaBytes size = Codensity $ C.allocaBytes size {-# INLINE allocaBytes #-} -- | 'allocaBytesAligned' @n@ @a@ is a continuation that provides access to a -- pointer into a temporary block of memory sufficient to hold @n@ bytes, -- with @a@-byte alignment. allocaBytesAligned :: Int -> Int -> Codensity IO (Ptr a) allocaBytesAligned size align = Codensity $ C.allocaBytesAligned size align {-# INLINE allocaBytesAligned #-} -- | 'bracketCodensity' allows one to add initialization and finalization hooks to -- an existing 'Codensity' that will be executed even in cases where an exception -- occurs. -- -- This provides an alternative to 'ForeignPtr's when the pointer will only -- be used in code that is written with this library in mind. bracketCodensity :: (a -> IO b) -> (a -> IO c) -> Codensity IO a -> Codensity IO a bracketCodensity init' final m = do a <- m liftIO $ init' a wrapCodensity (`finally` final a) return a {-# INLINE bracketCodensity #-} -- -- | 'calloc' @\@a@ is a continuation that provides access to a pointer into a -- temporary block of zeroed memory sufficient to hold values of type @a@. calloc :: forall a. Storable a => Codensity IO (Ptr a) calloc = do ptr <- alloca let size = sizeOf (undefined :: a) liftIO $ fillBytes ptr 0 size return ptr {-# INLINE calloc #-} -- | 'callocBytes' @n@ is a continuation that provides access to a pointer into -- a temporary block of zeroed memory sufficient to hold @n@ bytes, with -- machine-standard alignment. callocBytes :: Int -> Codensity IO (Ptr a) callocBytes size = do ptr <- allocaBytes size liftIO $ fillBytes ptr 0 size return ptr {-# INLINE callocBytes #-} -- -- | 'allocaArray' @\@a@ @n@ is a continuation that provides access to a -- pointer into a temporary block of memory sufficient to hold @n@ values of -- type @a@. allocaArray :: Storable a => Int -> Codensity IO (Ptr a) allocaArray size = Codensity (C.allocaArray size) {-# INLINE allocaArray #-} -- | 'allocaArrayWith' @xs@ is a continuation that provides access to a -- pointer into a temporary block of memory containing the values of @xs@. allocaArrayWith :: (Foldable f, Storable a) => f a -> Codensity IO (Ptr a) allocaArrayWith = allocaArrayWith' return {-# INLINE allocaArrayWith #-} -- Why isn't this defined as `allocaArrayWithOf' folded`? I don't want to -- lose the potential performance benefits of a specialized `length`. allocaArrayWith' :: (Foldable f, Storable b) => (a -> Codensity IO b) -> f a -> Codensity IO (Ptr b) allocaArrayWith' f xs = do ptr <- allocaArray (length xs) _ <- foldrM go ptr xs return ptr where go x ptr = do x' <- f x liftIO $ poke ptr x' return (C.advancePtr ptr 1) {-# INLINE allocaArrayWith' #-} -- | 'allocaArrayWithOf' @f@ works in the same way as 'allocaArrayWith', but -- using the 'Fold' @f@ rather than any 'Foldable' instance. allocaArrayWithOf :: (Storable a) => Fold s a -> s -> Codensity IO (Ptr a) allocaArrayWithOf fold = allocaArrayWithOf' fold return {-# INLINE allocaArrayWithOf #-} allocaArrayWithOf' :: (Storable b) => Fold s a -> (a -> Codensity IO b) -> s -> Codensity IO (Ptr b) allocaArrayWithOf' fold f xs = do ptr <- allocaArray (lengthOf fold xs) _ <- foldrMOf fold go ptr xs return ptr where go x ptr = do x' <- f x liftIO $ poke ptr x' return (C.advancePtr ptr 1) {-# INLINE allocaArrayWithOf' #-} iallocaArrayWith' :: (FoldableWithIndex i f, Storable b) => (i -> a -> Codensity IO b) -> f a -> Codensity IO (Ptr b) iallocaArrayWith' f xs = do ptr <- allocaArray (length xs) _ <- ifoldrMOf ifolded go ptr xs return ptr where go i x ptr = do x' <- f i x liftIO $ poke ptr x' return (C.advancePtr ptr 1) {-# INLINE iallocaArrayWith' #-} -- | A generic 'IndexedFold' or equivalent, taking an 'IndexedGetter', -- 'IndexedFold' (obviously), 'Control.Lens.Type.IndexedTraversal', or -- 'Control.Lens.Type.IndexedLens'. type AnIndexedFold i s a = forall m p. (Indexable i p) => p a (Const m a) -> s -> Const m s iallocaArrayWithOf' :: (Storable b) => AnIndexedFold i s a -> (i -> a -> Codensity IO b) -> s -> Codensity IO (Ptr b) iallocaArrayWithOf' fold f xs = do ptr <- allocaArray (lengthOf fold xs) _ <- ifoldrMOf fold go ptr xs return ptr where go i x ptr = do x' <- f i x liftIO $ poke ptr x' return (C.advancePtr ptr 1) {-# INLINE iallocaArrayWithOf' #-} -- -- | 'allocaArray0' @\@a@ @n@ is a continuation that provides access to a -- pointer into a temporary block of memory sufficient to hold @n@ values of -- type @a@, along with a final terminal element. allocaArray0 :: Storable a => Int -> Codensity IO (Ptr a) allocaArray0 len = Codensity (C.allocaArray0 len) {-# INLINE allocaArray0 #-} -- | 'allocaArrayWith0' @xs@ @end@ is a continuation that provides access to a -- pointer into a temporary block of memory containing the values of @xs@, -- terminated with @end@. allocaArrayWith0 :: (Foldable f, Storable a) => f a -> a -> Codensity IO (Ptr a) allocaArrayWith0 = allocaArrayWith0' return {-# INLINE allocaArrayWith0 #-} allocaArrayWith0' :: (Foldable f, Storable b) => (a -> Codensity IO b) -> f a -> b -> Codensity IO (Ptr b) allocaArrayWith0' f xs end = do ptr <- allocaArray (length xs) endPtr <- foldrMOf folded go ptr xs liftIO $ poke endPtr end return ptr where go x ptr = do x' <- f x liftIO $ poke ptr x' return (C.advancePtr ptr 1) {-# INLINE allocaArrayWith0' #-} -- | 'allocaArrayWith0Of' @t@ works in the same way as 'allocaArrayWith0', but -- using the 'Fold' @t@ rather than any 'Foldable' instance. allocaArrayWith0Of :: (Storable a) => Fold s a -> s -> a -> Codensity IO (Ptr a) allocaArrayWith0Of fold = allocaArrayWith0Of' fold return {-# INLINE allocaArrayWith0Of #-} allocaArrayWith0Of' :: (Storable b) => Fold s a -> (a -> Codensity IO b) -> s -> b -> Codensity IO (Ptr b) allocaArrayWith0Of' fold f xs end = do ptr <- allocaArray (lengthOf fold xs) endPtr <- foldrMOf fold go ptr xs liftIO $ poke endPtr end return ptr where go x ptr = do x' <- f x liftIO $ poke ptr x' return (C.advancePtr ptr 1) {-# INLINE allocaArrayWith0Of' #-} iallocaArrayWith0' :: (FoldableWithIndex i f, Storable b) => (i -> a -> Codensity IO b) -> f a -> b -> Codensity IO (Ptr b) iallocaArrayWith0' f xs end = do ptr <- allocaArray (length xs) endPtr <- ifoldrMOf ifolded go ptr xs liftIO $ poke endPtr end return ptr where go i x ptr = do x' <- f i x liftIO $ poke ptr x' return (C.advancePtr ptr 1) {-# INLINE iallocaArrayWith0' #-} iallocaArrayWith0Of' :: (Storable b) => AnIndexedFold i s a -> (i -> a -> Codensity IO b) -> s -> b -> Codensity IO (Ptr b) iallocaArrayWith0Of' fold f xs end = do ptr <- allocaArray (lengthOf fold xs) endPtr <- ifoldrMOf fold go ptr xs liftIO $ poke endPtr end return ptr where go i x ptr = do x' <- f i x liftIO $ poke ptr x' return (C.advancePtr ptr 1) {-# INLINE iallocaArrayWith0Of' #-} -- -- | 'callocArray' @\@a@ @n@ is a continuation that provides access to a -- pointer into a temporary block of zeroed memory sufficient to hold @n@ -- values of type @a@. callocArray :: forall a . Storable a => Int -> Codensity IO (Ptr a) callocArray len = do ptr <- allocaArray len let size = sizeOf (undefined :: a) liftIO $ fillBytes ptr 0 (len * size) return ptr {-# INLINE callocArray #-} -- | 'callocArray0' @\@a@ @n@ is a continuation that provides access to a -- pointer into a temporary block of zeroed memory sufficient to hold @n@ -- values of type @a@, along with a final terminal element. callocArray0 :: forall a . Storable a => Int -> Codensity IO (Ptr a) callocArray0 len = do ptr <- allocaArray0 len let size = sizeOf (undefined :: a) liftIO $ fillBytes ptr 0 (len * size) return ptr {-# INLINE callocArray0 #-} -- -- | 'withForeignPtr' @ptr@ is a continuation that provides safe access to the -- backing pointer of @ptr@. withForeignPtr :: ForeignPtr a -> Codensity IO (Ptr a) withForeignPtr ptr = Codensity (C.withForeignPtr ptr) {-# INLINE withForeignPtr #-} -- -- | 'ToCString' @a@ is a class for types @a@ that can be encoded into -- 'CString's. class ToCString a where -- | 'withCString' @a@ is a continuation that provides access to @a@ as a -- 'CString'. withCString :: a -> Codensity IO CString -- | 'withCStringLen' @a@ is a continuation that provides access to @a@ as a -- 'CStringLen'. withCStringLen :: a -> Codensity IO CStringLen instance ToCString String where withCString s = Codensity (C.withCString s) {-# INLINE withCString #-} withCStringLen s = Codensity (C.withCStringLen s) {-# INLINE withCStringLen #-} instance ToCString BS.ByteString where withCString s = Codensity (BS.useAsCString s) {-# INLINE withCString #-} withCStringLen s = Codensity (BS.useAsCStringLen s) {-# INLINE withCStringLen #-} instance ToCString SBS.ShortByteString where withCString s = Codensity (SBS.useAsCString s) {-# INLINE withCString #-} withCStringLen s = Codensity (SBS.useAsCStringLen s) {-# INLINE withCStringLen #-}