packages feed

mono-hash-0.1.0.0: src/Data/Hash/Mono/Internal/ByteFoldable.hs

{-# LANGUAGE InstanceSigs #-}

-- | Byte encoding for types.
module Data.Hash.Mono.Internal.ByteFoldable (
    ByteFoldable (..),
)
where

import Data.Bits (Bits (shiftR, (.&.)))
import Data.Char qualified as Char
import Data.Int (Int16, Int32, Int64, Int8)
import Data.WideWord.Word128 (Word128 (..))
import Data.WideWord.Word256 (Word256 (..))
import Data.Word (Word16, Word32, Word64, Word8)

{- | Types with a fixed byte encoding.

An instance must define a fixed order for the bytes to be folded.
-}
class ByteFoldable a where
    -- | Fold over the bytes of a value.
    foldlBytes :: (b -> Word8 -> b) -> b -> a -> b

{- | 'Char' is encoded as
<https://wtf-8.codeberg.page WTF-8>, the UTF-8 encoding extended to
lone surrogates. For all other code points the bytes are ordinary UTF-8.

'Char' can represent lone surrogates, which strict UTF-8 cannot encode.
Replacing them, as @Text@ does with @U+FFFD@, would be lossy and would let
distinct strings produce the same bytes. WTF-8 instead uses the ordinary
three-byte shape for them, so every 'Char' has an encoding, the encoding
stays injective, and valid text remains plain UTF-8. A 'String' and a
@Text@ containing a lone surrogate therefore encode differently.
-}
instance ByteFoldable Char where
    foldlBytes f z c
        | n < 0x80 = f z (fromIntegral n)
        | n < 0x800 =
            f
                (f z (fromIntegral (0xc0 + (n `shiftR` 6))))
                (fromIntegral (0x80 + (n .&. 0x3f)))
        | n < 0x10000 =
            f
                ( f
                    (f z (fromIntegral (0xe0 + (n `shiftR` 12))))
                    (fromIntegral (0x80 + ((n `shiftR` 6) .&. 0x3f)))
                )
                (fromIntegral (0x80 + (n .&. 0x3f)))
        | otherwise =
            f
                ( f
                    ( f
                        (f z (fromIntegral (0xf0 + (n `shiftR` 18))))
                        (fromIntegral (0x80 + ((n `shiftR` 12) .&. 0x3f)))
                    )
                    (fromIntegral (0x80 + ((n `shiftR` 6) .&. 0x3f)))
                )
                (fromIntegral (0x80 + (n .&. 0x3f)))
      where
        n :: Int
        n = Char.ord c
    {-# INLINE foldlBytes #-}

instance ByteFoldable Word8 where
    foldlBytes f = f
    {-# INLINE foldlBytes #-}

{- | Encoded as its two's-complement bit pattern, the same bytes as the
corresponding 'Word8'.
-}
instance ByteFoldable Int8 where
    foldlBytes f z i = foldlBytes f z (fromIntegral i :: Word8)
    {-# INLINE foldlBytes #-}

-- | Encoded as two bytes, most significant first.
instance ByteFoldable Word16 where
    foldlBytes f z w =
        f (f z (fromIntegral (w `shiftR` 8))) (fromIntegral w)
    {-# INLINE foldlBytes #-}

{- | Encoded as its two's-complement bit pattern, the same bytes as the
corresponding 'Word16'.
-}
instance ByteFoldable Int16 where
    foldlBytes f z i = foldlBytes f z (fromIntegral i :: Word16)
    {-# INLINE foldlBytes #-}

-- | Encoded as four bytes, most significant first.
instance ByteFoldable Word32 where
    foldlBytes f z w =
        f
            ( f
                ( f
                    (f z (fromIntegral (w `shiftR` 24)))
                    (fromIntegral (w `shiftR` 16))
                )
                (fromIntegral (w `shiftR` 8))
            )
            (fromIntegral w)
    {-# INLINE foldlBytes #-}

{- | Encoded as its two's-complement bit pattern, the same bytes as the
corresponding 'Word32'.
-}
instance ByteFoldable Int32 where
    foldlBytes f z i = foldlBytes f z (fromIntegral i :: Word32)
    {-# INLINE foldlBytes #-}

-- | Encoded as eight bytes, most significant first.
instance ByteFoldable Word64 where
    foldlBytes f z w =
        f
            ( f
                ( f
                    ( f
                        ( f
                            ( f
                                ( f
                                    (f z (fromIntegral (w `shiftR` 56)))
                                    (fromIntegral (w `shiftR` 48))
                                )
                                (fromIntegral (w `shiftR` 40))
                            )
                            (fromIntegral (w `shiftR` 32))
                        )
                        (fromIntegral (w `shiftR` 24))
                    )
                    (fromIntegral (w `shiftR` 16))
                )
                (fromIntegral (w `shiftR` 8))
            )
            (fromIntegral w)
    {-# INLINE foldlBytes #-}

{- | Encoded as its two's-complement bit pattern, the same bytes as the
corresponding 'Word64'.
-}
instance ByteFoldable Int64 where
    foldlBytes f z i = foldlBytes f z (fromIntegral i :: Word64)
    {-# INLINE foldlBytes #-}

-- | Encoded as sixteen bytes, most significant first.
instance ByteFoldable Word128 where
    foldlBytes f z w =
        foldlBytes f (foldlBytes f z (word128Hi64 w)) (word128Lo64 w)
    {-# INLINE foldlBytes #-}

-- | Encoded as thirty-two bytes, most significant first.
instance ByteFoldable Word256 where
    foldlBytes f z w =
        foldlBytes
            f
            ( foldlBytes
                f
                ( foldlBytes
                    f
                    (foldlBytes f z (word256hi w))
                    (word256m1 w)
                )
                (word256m0 w)
            )
            (word256lo w)
    {-# INLINE foldlBytes #-}