posix-api-0.4.0.1: src/Foreign/C/String/Managed.hs
{-# language BangPatterns #-}
{-# language DerivingStrategies #-}
{-# language GeneralizedNewtypeDeriving #-}
{-# language MagicHash #-}
{-# language MultiWayIf #-}
{-# language TypeApplications #-}
{-# language ScopedTypeVariables #-}
{-# language UnboxedTuples #-}
module Foreign.C.String.Managed
( ManagedCString(..)
, terminated
, terminatedU
, unterminated
, fromBytes
, fromLatinString
, fromShortText
, pinnedFromBytes
, pin
, touch
, contents
) where
import Control.Monad.ST (ST)
import Control.Monad.ST.Run (runByteArrayST)
import Data.Bytes.Types (Bytes(Bytes))
import Data.Char (ord)
import Data.Primitive (ByteArray(..),MutableByteArray)
import Data.Text.Short (ShortText)
import Data.Word (Word8)
import Foreign.C.String (CString)
import Foreign.Ptr (castPtr)
import GHC.Exts (Int(I#),Char(C#),ByteArray#,chr#,touch#)
import GHC.IO (IO(IO))
import qualified Data.Bytes as Bytes
import qualified Data.Bytes.Text.Utf8 as Utf8
import qualified Data.Primitive as PM
import qualified Data.Text.Short as TS
import qualified GHC.Exts as Exts
-- | An unsliced byte sequence with @NUL@ as the final byte.
newtype ManagedCString = ManagedCString ByteArray
deriving newtype Eq
instance Semigroup ManagedCString where
ManagedCString a <> ManagedCString b = ManagedCString $ runByteArrayST $ do
let lenA = PM.sizeofByteArray a
let lenB = PM.sizeofByteArray b
dst <- PM.newByteArray (lenA + lenB - 1)
PM.copyByteArray dst 0 a 0 (lenA - 1)
PM.copyByteArray dst (lenA - 1) b 0 lenB
PM.unsafeFreezeByteArray dst
instance Monoid ManagedCString where
mempty = ManagedCString $ runByteArrayST $ do
dst <- PM.newByteArray 1
PM.writeByteArray dst 0 (0 :: Word8)
PM.unsafeFreezeByteArray dst
instance Exts.IsString ManagedCString where
fromString = fromLatinString
instance Show ManagedCString where
showsPrec _ (ManagedCString arr) s0 = PM.foldrByteArray
( \(w :: Word8) s ->
if | w == 0 -> s
| w < 32 -> '?' : s
| w > 126 -> '?' : s
| otherwise -> case fromIntegral @Word8 @Int w of
I# i -> C# (chr# i) : s
) s0 arr
terminatedU :: ManagedCString -> ByteArray
terminatedU (ManagedCString x) = x
terminated :: ManagedCString -> Bytes
terminated (ManagedCString x) = Bytes.fromByteArray x
unterminated :: ManagedCString -> Bytes
unterminated (ManagedCString x) = Bytes x 0 (PM.sizeofByteArray x - 1)
fromShortText :: ShortText -> ManagedCString
fromShortText !ts = fromBytes (Utf8.fromShortText ts)
-- | Copies the slice, appending a @NUL@ byte to the end.
fromBytes :: Bytes -> ManagedCString
fromBytes (Bytes arr off len) = ManagedCString $ runByteArrayST $ do
dst <- PM.newByteArray (len + 1)
PM.copyByteArray dst 0 arr off len
PM.writeByteArray dst len (0 :: Word8)
PM.unsafeFreezeByteArray dst
-- | Copies the slice into pinned memory, appending a @NUL@ byte to the end.
pinnedFromBytes :: Bytes -> ManagedCString
pinnedFromBytes (Bytes arr off len) = ManagedCString $ runByteArrayST $ do
dst <- PM.newPinnedByteArray (len + 1)
PM.copyByteArray dst 0 arr off len
PM.writeByteArray dst len (0 :: Word8)
PM.unsafeFreezeByteArray dst
pin :: ManagedCString -> ManagedCString
pin (ManagedCString x) = if PM.isByteArrayPinned x
then ManagedCString x
else ManagedCString $ runByteArrayST $ do
let len = PM.sizeofByteArray x
dst <- PM.newPinnedByteArray len
PM.copyByteArray dst 0 x 0 len
PM.unsafeFreezeByteArray dst
touch :: ManagedCString -> IO ()
touch (ManagedCString (ByteArray x)) = touchByteArray# x
touchByteArray# :: ByteArray# -> IO ()
touchByteArray# x = IO $ \s -> case touch# x s of s' -> (# s', () #)
-- | Convert a 'String' consisting of only characters representable
-- by ISO-8859-1. These are encoded with ISO-8859-1. Any character
-- with a codepoint above @U+00FF@ is replaced by an unspecified byte.
fromLatinString :: String -> ManagedCString
{-# noinline fromLatinString #-}
fromLatinString str = ManagedCString $ runByteArrayST $ do
let lenPred0 = 63
dst0 <- PM.newByteArray (lenPred0 + 1)
go str dst0 0 lenPred0
where
go :: forall s. String -> MutableByteArray s -> Int -> Int -> ST s ByteArray
go [] !dst !ix !_ = do
PM.writeByteArray dst ix (0 :: Word8)
PM.resizeMutableByteArray dst (ix + 1) >>= PM.unsafeFreezeByteArray
go (c:cs) !dst !ix !lenPred = if ix < lenPred
then do
PM.writeByteArray dst ix (fromIntegral @Int @Word8 (ord c))
go cs dst (ix + 1) lenPred
else do
let nextLenPred = lenPred * 2
dst' <- PM.newByteArray (nextLenPred + 1)
PM.copyMutableByteArray dst' 0 dst 0 ix
PM.writeByteArray dst' ix (fromIntegral @Int @Word8 (ord c))
go cs dst' (ix + 1) nextLenPred
-- | Get a pointer to the payload of the managed C string. The behavior is
-- undefined if the argument is not pinned.
contents :: ManagedCString -> CString
contents (ManagedCString x) = castPtr (PM.byteArrayContents x)