Add conversions between ShortByteString and CString
authorMoritz Kiefer <moritz.kiefer@purelyfunctional.org>
Tue, 2 May 2017 18:31:09 +0000 (20:31 +0200)
committerMoritz Kiefer <moritz.kiefer@purelyfunctional.org>
Mon, 31 Jul 2017 16:29:44 +0000 (18:29 +0200)
Data/ByteString/Short.hs
Data/ByteString/Short/Internal.hs

index 4248417..70ae738 100644 (file)
@@ -78,6 +78,15 @@ module Data.ByteString.Short (
 
     -- * Other operations
     empty, null, length, index,
+
+    -- * Low level conversions
+    -- ** Packing 'CString's and pointers
+    packCString,
+    packCStringLen,
+
+    -- ** Using ByteStrings as 'CString's
+    useAsCString,
+    useAsCStringLen
   ) where
 
 import Data.ByteString.Short.Internal
index a52c52c..dbde958 100644 (file)
@@ -33,10 +33,19 @@ module Data.ByteString.Short.Internal (
     empty, null, length, index, unsafeIndex,
 
     -- * Low level operations
-    createFromPtr, copyToPtr
+    createFromPtr, copyToPtr,
+
+    -- * Low level conversions
+    -- ** Packing 'CString's and pointers
+    packCString,
+    packCStringLen,
+
+    -- ** Using ByteStrings as 'CString's
+    useAsCString,
+    useAsCStringLen
   ) where
 
-import Data.ByteString.Internal (ByteString(..), accursedUnutterablePerformIO)
+import Data.ByteString.Internal (ByteString(..), accursedUnutterablePerformIO, c_strlen)
 
 import Data.Typeable    (Typeable)
 import Data.Data        (Data(..), mkNoRepType)
@@ -47,6 +56,7 @@ import Data.Monoid      (Monoid(..))
 import Data.String      (IsString(..))
 import Control.DeepSeq  (NFData(..))
 import qualified Data.List as List (length)
+import Foreign.C.String (CString, CStringLen)
 #if MIN_VERSION_base(4,7,0)
 import Foreign.C.Types  (CSize(..), CInt(..))
 #elif MIN_VERSION_base(4,4,0)
@@ -54,6 +64,7 @@ import Foreign.C.Types  (CSize(..), CInt(..), CLong(..))
 #else
 import Foreign.C.Types  (CSize, CInt, CLong)
 #endif
+import Foreign.Marshal.Alloc (allocaBytes)
 import Foreign.Ptr
 import Foreign.ForeignPtr (touchForeignPtr)
 #if MIN_VERSION_base(4,5,0)
@@ -61,6 +72,7 @@ import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
 #else
 import Foreign.ForeignPtr (unsafeForeignPtrToPtr)
 #endif
+import Foreign.Storable (pokeByteOff)
 
 #if MIN_VERSION_base(4,5,0)
 import qualified GHC.Exts
@@ -90,7 +102,8 @@ import GHC.ST         (ST(ST), runST)
 import GHC.Word
 
 import Prelude ( Eq(..), Ord(..), Ordering(..), Read(..), Show(..)
-               , ($), error, (++)
+               , ($), error, (++), (.)
+               , String, userError
                , Bool(..), (&&), otherwise
                , (+), (-), fromIntegral
                , return )
@@ -595,3 +608,60 @@ foreign import ccall unsafe "fpstring.h fps_memcpy_offsets"
                    -> ByteArray# -> CLong -> CSize -> IO ()
 #endif
 
+-- | /O(n)./ Construct a new @ShortByteString@ from a @CString@. The
+-- resulting @ShortByteString@ is an immutable copy of the original
+-- @CString@, and is managed on the Haskell heap. The original
+-- @CString@ must be null terminated.
+--
+-- @since 0.10.10.0
+packCString :: CString -> IO ShortByteString
+packCString cstr = do
+  len <- c_strlen cstr
+  packCStringLen (cstr, fromIntegral len)
+
+-- | /O(n)./ Construct a new @ShortByteString@ from a @CStringLen@. The
+-- resulting @ShortByteString@ is an immutable copy of the original @CStringLen@.
+-- The @ShortByteString@ is a normal Haskell value and will be managed on the
+-- Haskell heap.
+--
+-- @since 0.10.10.0
+packCStringLen :: CStringLen -> IO ShortByteString
+packCStringLen (cstr, len) | len >= 0 = createFromPtr cstr len
+packCStringLen (_, len) =
+  moduleErrorIO "packCStringLen" ("negative length: " ++ show len)
+
+-- | /O(n) construction./ Use a @ShortByteString@ with a function requiring a
+-- null-terminated @CString@.  The @CString@ is a copy and will be freed
+-- automatically; it must not be stored or used after the
+-- subcomputation finishes.
+--
+-- @since 0.10.10.0
+useAsCString :: ShortByteString -> (CString -> IO a) -> IO a
+useAsCString bs action =
+  allocaBytes (l+1) $ \buf -> do
+      copyToPtr bs 0 buf (fromIntegral l)
+      pokeByteOff buf l (0::Word8)
+      action buf
+  where l = length bs
+
+-- | /O(n) construction./ Use a @ShortByteString@ with a function requiring a @CStringLen@.
+-- As for @useAsCString@ this function makes a copy of the original @ShortByteString@.
+-- It must not be stored or used after the subcomputation finishes.
+--
+-- @since 0.10.10.0
+useAsCStringLen :: ShortByteString -> (CStringLen -> IO a) -> IO a
+useAsCStringLen bs action =
+  allocaBytes l $ \buf -> do
+      copyToPtr bs 0 buf (fromIntegral l)
+      action (buf, l)
+  where l = length bs
+
+-- ---------------------------------------------------------------------
+-- Internal utilities
+
+moduleErrorIO :: String -> String -> IO a
+moduleErrorIO fun msg = throwIO . userError $ moduleErrorMsg fun msg
+{-# NOINLINE moduleErrorIO #-}
+
+moduleErrorMsg :: String -> String -> String
+moduleErrorMsg fun msg = "Data.ByteString.Short." ++ fun ++ ':':' ':msg