hscdio-0.1.0.0: src/Foreign/Libcdio/Util.hs
{-# LANGUAGE ForeignFunctionInterface #-}
{-|
Description: Miscellaneous utility functions, of limited utility.
Copyright: (c) 2018-2021 Sam May
License: GPL-3.0-or-later
Maintainer: ag@eitilt.life
Stability: stable
Portability: non-portable (requires libcdio)
Most functions defined by the C header either encapsulate math to perform on
an object or the system state, or edit `String's in some (barring allocations)
pure manner, including a few with general utility rather than being specific to
libcdio. However, most are also restricted to internal use, and not provided
as symbols exported by the library itself; those internal functions are still
present in the Haskell source as well, for anyone interested. As such, this
module and the functions it provides should rarely need to be used.
= @util.h@
== Defines
* @CDIO_FREE_IF_NOT_NULL@ (removed; Haskell handles its own memory management)
* @CLAMP@ (removed; preprocessor logic isn't required in Haskell)
* @IN@ (removed; preprocessor logic isn't required in Haskell)
* @MAX@ (removed; preprocessor logic isn't required in Haskell)
* @MAX@ (removed; preprocessor logic isn't required in Haskell)
== Symbols
* @_cdio_strfreev@ (removed; list is automatically freed)
* @_cdio_strsplit@ -> 'Foreign.Libcdio.Util.strsplit'
* @cdio_from_bcd8@ -> 'Foreign.Libcdio.Util.fromBcd8'
* @cdio_realpath@ -> 'Foreign.Libcdio.Util.realpath'
* @cdio_to_bcd8@ -> 'Foreign.Libcdio.Util.toBcd8'
-}
module Foreign.Libcdio.Util
( Bcd
, toBcd8, fromBcd8
, strsplit
, realpath
) where
{- Used only by not exported symbols
import qualified Foreign.ForeignPtr as C
import qualified Foreign.Marshal.Alloc as M
import qualified Foreign.Marshal.Array as M
import qualified Foreign.Marshal.Utils as M
import qualified System.IO.Unsafe as IO.Unsafe
import Foreign.Libcdio.Types.Internal
-}
import qualified Data.Word as W
import qualified Data.Ix as I
import qualified Foreign.C.String as C
import qualified Foreign.C.Types as C
import qualified Foreign.Ptr as C
import qualified Foreign.Marshal.Utils as M
import qualified Foreign.Storable as S
import qualified Numeric as NM
import qualified Text.Printf as P
import Foreign.Libcdio.Marshal
{- Not exported
-- | Calculate how many blocks of a given size are required to contain the
-- given number of bytes.
len2blocks
:: W.Word32
-- ^ The number of bytes to fit.
-> W.Word16
-- ^ The size of each block.
-> Lsn
len2blocks b s = fromIntegral $ len2blocks' (fromIntegral b) (fromIntegral s)
foreign import ccall safe "cdio/util.h _cdio_len2blocks"
len2blocks' :: C.CUInt -> C.CUShort -> C.CInt
-- | Round up to the next block boundery after a given offset.
ceil2block
:: Int
-- ^ The base address to round.
-> W.Word16
-- ^ The size of each block.
-> Int
ceil2block p s = fromIntegral $ ceil2block' (fromIntegral p) (fromIntegral s)
foreign import ccall safe "cdio/util.h _cdio_ceil2block"
ceil2block' :: C.CInt -> C.CUShort -> C.CInt
-- | Add a given length after a given address, potentially putting it at the
-- start of a new block if there is not enough space remaining in the one in
-- which the address is located.
ofsAdd
:: Int
-- ^ The base addres.
-> Int
-- ^ The number of bytes to add.
-> W.Word16
-- ^ The size of each block.
-> Int
ofsAdd p a s = fromIntegral $ ofsAdd' (fromIntegral p) (fromIntegral a) (fromIntegral s)
foreign import ccall safe "cdio/util.h _cdio_ofs_add"
ofsAdd' :: C.CInt -> C.CInt -> C.CUShort -> C.CInt
-- | Print a 'True'/'False' value as "yes"/"no".
--
-- A similar, but not identical, result may be obtained with 'show'.
boolStr :: Bool -> String
boolStr = IO.Unsafe.unsafePerformIO . C.peekCString . boolStr' . M.fromBool
foreign import ccall safe "cdio/util.h _cdio_bool_str"
boolStr' :: C.CInt -> C.CString
-- | Copy the contents of one memory address to another, newly allocated one.
memdup :: C.Ptr a -> Word
-> IO (Maybe (C.ForeignPtr a))
-- ^ 'Nothing' if the source is 'nullPtr',
-- or a pointer to the new memory.
memdup p s = do
p' <- memdup' p (fromIntegral s)
if p == C.nullPtr
then return Nothing
else Just <$> C.newForeignPtr cdioFreePtr p'
foreign import ccall safe "cdio/util.h _cdio_memdup"
memdup' :: C.Ptr a -> C.CSize -> IO (C.Ptr a)
-- | Create a copy of a string with all characters transformed to upper case.
--
-- This should be functionally identical to @map toUpper@.
strdupUpper :: String -> IO String
strdupUpper s = C.withCString s strdupUpper' >>= peekFString
foreign import ccall safe "cdio/util.h _cdio_strdup_upper"
strdupUpper' :: C.CString -> IO C.CString
-- | Duplicate a path and make it platform compliant. Typically needed for
-- MinGW\/MSYS where a "\/c\/..." path must be translated to "c:\/...".
strdupFixpath :: String -> IO (Maybe String)
strdupFixpath s = C.withCString s strdupFixpath' >>= peekNullFString
foreign import ccall safe "cdio/util.h _cdio_strdup_fixpath"
strdupFixpath' :: C.CString -> IO C.CString
-- | Count the number of C-style string pointers in a NULL-terminated array.
--
-- As we are already know the list is fully populated, this offers no benefit
-- over 'length'; Haskell's type system will not insert anything acting like a
-- string which is actually @NULL@.
strlenv :: [String] -> IO Word
strlenv ss = M.withMany C.withCString ss $ \ss' ->
fromIntegral <$> M.withArray0 C.nullPtr ss' strlenv'
foreign import ccall safe "cdio/util.h _cdio_strlenv"
strlenv' :: C.Ptr C.CString -> IO C.CSize
-}
-- | Return the substrings between a given delimiter, dropping any empty ones.
--
-- The Haskell repositories provide this via
-- [split](https://hackage.haskell.org/package/split).
--
-- > strsplit str d == Data.List.Split.wordsBy (== d) str
strsplit :: String -> Char -> IO [String]
strsplit s c = C.withCString s (flip strsplit' . fromIntegral $ fromEnum c) >>= peekFStringArray
foreign import ccall safe "cdio/util.h _cdio_strsplit"
strsplit' :: C.CString -> C.CChar -> IO (C.Ptr C.CString)
-- | A bitwise encoding where the lower four bits encode a number modulo 10,
-- and the upper encode the same divided by 10.
newtype Bcd = Bcd W.Word8
instance Eq Bcd where
a == b = fromBcd8 a == fromBcd8 b
instance Ord Bcd where
compare a b = compare (fromBcd8 a) (fromBcd8 b)
instance Show Bcd where
showsPrec p i@(Bcd w)
| mod w 0x10 >= 0xA = showParen (p > application) $
showString ("Bcd 0x" ++ pad) . NM.showHex w
| otherwise = showsPrec p $ fromBcd8 i
where application = 10
pad | i >= 0x10 = ""
| otherwise = "0"
instance Read Bcd where
readsPrec p s =
map toBcd8Fst (readsPrec p s)
++ readParen (p > application)
(\r -> [ (Bcd i, v ++ ' ' : u)
| ("Bcd", t) <- lex r
, ('0':'x':l, u) <- lex t
, (i, v) <- NM.readHex l
]) s
where toBcd8Fst (w, b) = (toBcd8 w, b)
application = 10
-- | >>> map fromBcd8 [minBound, maxBound]
-- [0, 159]
instance Bounded Bcd where
minBound = Bcd 0
maxBound = Bcd 0xF9
instance Enum Bcd where
toEnum i = toBcd8 $ toEnum i
fromEnum b = fromEnum $ fromBcd8 b
succ i
| i >= maxBound = error "Enum.succ(Bcd): tried to take `succ' of maxBound"
| otherwise = toBcd8 . succ $ fromBcd8 i
pred i
| i <= minBound = error "Enum.pred(Bcd): tried to take `pred' of minBound"
| otherwise = toBcd8 . pred $ fromBcd8 i
instance I.Ix Bcd where
range (a, b) = map toBcd8 $ I.range (fromBcd8 a, fromBcd8 b)
inRange (a, b) i = I.inRange (fromBcd8 a, fromBcd8 b) $ fromBcd8 i
index (a, b) i = I.index (fromBcd8 a, fromBcd8 b) $ fromBcd8 i
instance Num Bcd where
a + b = toBcd8 $ fromBcd8 a + fromBcd8 b
a - b = toBcd8 $ fromBcd8 a - fromBcd8 b
a * b = toBcd8 $ fromBcd8 a * fromBcd8 b
abs i = i
signum b = toBcd8 . signum $ fromBcd8 b
fromInteger i = toBcd8 $ fromInteger i
instance Real Bcd where
toRational b = toRational $ fromBcd8 b
instance Integral Bcd where
quotRem dividend divisor = (toBcd8 q, toBcd8 r)
where (q, r) = quotRem (fromBcd8 dividend) (fromBcd8 divisor)
toInteger b = toInteger $ fromBcd8 b
instance P.PrintfArg Bcd where
formatArg = P.formatArg . fromBcd8
instance S.Storable Bcd where
sizeOf (Bcd i) = S.sizeOf i
alignment (Bcd i) = S.alignment i
peek p = Bcd <$> S.peek (C.castPtr p)
poke p (Bcd i) = S.poke (C.castPtr p) i
-- | Encode a number according to the libcdio BCD encoding. If the value is
-- greater than @'maxBound' :: 'Bcd'@, it wraps back around to @0@.
toBcd8 :: Word -> Bcd
toBcd8 = Bcd . fromIntegral . toBcd8' . fromIntegral
foreign import ccall safe "cdio/util.h cdio_to_bcd8"
toBcd8' :: C.CUInt -> C.CUInt
-- | Decode a number in the libcdio BCD encoding.
fromBcd8 :: Bcd -> Word
fromBcd8 (Bcd i) = fromIntegral . fromBcd8' $ fromIntegral i
foreign import ccall safe "cdio/util.h cdio_from_bcd8"
fromBcd8' :: C.CUInt -> C.CUInt
-- | Same as POSIX.1-2001 realpath, if the system provides it. If not,
-- libcdio's "poor-man's simulation" of its behavior.
--
-- The Haskell repositories provide a similar alternative in
-- @System.Directory.canonicalizePath@ from
-- [directory](https://hackage.haskell.org/package/directory).
realpath :: String -> IO (Maybe String)
realpath s = C.withCString s (`realpath'` C.nullPtr) >>= M.maybePeek peekFString
foreign import ccall safe "cdio/util.h cdio_realpath"
realpath' :: C.CString -> C.CString -> IO C.CString
{- Pointer logic is not exposed.
-- | Some function which matches the free() prototype.
type DataFree = C.FunPtr (C.Ptr () -> IO ())
-}