-- (c) The FFI task force, 2000
--
-- C-specific marshalling support: string marshaling

module CString (

  -- representation of strings in C
  --
  CString,           -- = Ptr CChar
  CStringLen,        -- = (CString, Int)

  -- conversion of C strings into Haskell strings
  --
  peekCString,       -- :: CString    -> IO String
  peekCStringLen,    -- :: CStringLen -> IO String

  -- conversion of Haskell strings into C strings
  --
  newCString,        -- :: String -> IO CString
  newCStringLen,     -- :: String -> IO CStringLen

  -- conversion of Haskell strings into C strings using temporary storage
  --
  withCString,       -- :: String -> (CString    -> IO a) -> IO a
  withCStringLen,    -- :: String -> (CStringLen -> IO a) -> IO a

  -- conversion between Haskell and C characters *ignoring* the encoding
  --
  castCharToCChar,   -- :: Char -> CChar
  castCCharToChar,   -- :: CChar -> Char

  -- UnsafeCString: these might be more efficient than CStrings when
  -- passing the string to an "unsafe" foreign import.  NOTE: this
  -- feature might be removed in favour of a more general approach in
  -- the future.
  --
  UnsafeCString,     -- data UnsafeCString
  withUnsafeCString  -- :: String -> (UnsafeCString -> IO a) -> IO a
) where

import Monad
import Char

import MarshalArray
import MarshalAlloc
import Ptr
import NewStorable
import CTypes
import Int


-- representation of strings in C
-- ------------------------------

type CString    = Ptr CChar             -- conventional NUL terminates strings
type CStringLen = (CString, Int)        -- strings with explicit length


-- exported functions
-- ------------------
--
--  * the following routines apply the default conversion when converting the
--   C-land character encoding into the Haskell-land character encoding
--
--   ** NOTE: The current implementation doesn't handle conversions yet! **
--
--  * the routines using an explicit length tolerate NUL characters in the
--   middle of a string
--

-- marshal a NUL terminated C string into a Haskell string 
--
peekCString    :: CString -> IO String
peekCString cp  = liftM cCharsToChars $ peekArray0 nUL cp

-- marshal a C string with explicit length into a Haskell string 
--
peekCStringLen           :: CStringLen -> IO String
peekCStringLen (cp, len)  = liftM cCharsToChars $ peekArray len cp

-- marshal a Haskell string into a NUL terminated C strings
--
--  * the Haskell string may *not* contain any NUL characters
--
--  * new storage is allocated for the C string and must be explicitly freed
--
newCString :: String -> IO CString
newCString  = newArray0 nUL . charsToCChars

-- marshal a Haskell string into a C string (ie, character array) with
-- explicit length information
--
--  * new storage is allocated for the C string and must be explicitly freed
--
newCStringLen     :: String -> IO CStringLen
newCStringLen str  = liftM (pairLength str) $ newArray (charsToCChars str)

-- marshal a Haskell string into a NUL terminated C strings using temporary
-- storage
--
--  * the Haskell string may *not* contain any NUL characters
--
--  * see the lifetime constraints of `MarshalAlloc.alloca'
--
withCString :: String -> (CString -> IO a) -> IO a
withCString  = withArray0 nUL . charsToCChars

-- marshal a Haskell string into a NUL terminated C strings using temporary
-- storage
--
--  * the Haskell string may *not* contain any NUL characters
--
--  * see the lifetime constraints of `MarshalAlloc.alloca'
--
withCStringLen         :: String -> (CStringLen -> IO a) -> IO a
withCStringLen str act  = withArray (charsToCChars str) $ act . pairLength str

-- auxiliary definitions
-- ----------------------

-- C's end of string character
--
nUL :: CChar
nUL  = 0

-- pair a C string with the length of the given Haskell string
--
pairLength :: String -> CString -> CStringLen
pairLength  = flip (,) . length

-- cast [CChar] to [Char]
--
cCharsToChars :: [CChar] -> [Char]
cCharsToChars  = map castCCharToChar

-- cast [Char] to [CChar]
--
charsToCChars :: [Char] -> [CChar]
charsToCChars  = map castCharToCChar

castCCharToChar :: CChar -> Char
castCCharToChar  = chr . fromIntegral

castCharToCChar :: Char -> CChar
castCharToCChar  = fromIntegral . ord


-- unsafe CStrings
-- ---------------

newtype UnsafeCString = UnsafeCString (Ptr CChar)

withUnsafeCString s f = withCString s (\p -> f (UnsafeCString p))
