benc-0.1.1.0: src/Data/Bencode/Util.hs
{-# LANGUAGE BangPatterns #-}
module Data.Bencode.Util
( arrayFromRevListN
, readKnownNaturalAsInt
, readKnownNaturalAsInt64
, readKnownNaturalAsWord
, readKnownNaturalAsWord64
) where
import Data.Bits
import Data.Int
import Data.Word
import qualified Data.ByteString as B
import qualified Data.ByteString.Unsafe as B
import qualified Data.Primitive.Array as A
-- | Create an array from a list in reverse order. The list length must be n.
arrayFromRevListN :: Int -> [a] -> A.Array a
arrayFromRevListN n xs = A.createArray n errorElement $ \a ->
let f x k = \i ->
if i == -1
then pure ()
else A.writeArray a i x *> k (i-1)
in foldr f (\ !_ -> pure ()) xs (n-1)
{-# INLINE arrayFromRevListN #-}
errorElement :: a
errorElement = error "errorElement"
-- | The input string must be an unsigned decimal integer with no extraneous
-- leading zeros. Returns Nothing if the value is outside the bounds of an
-- @Int@.
readKnownNaturalAsInt :: Bool -> B.ByteString -> Maybe Int
readKnownNaturalAsInt = readInt maxIntLen
where
maxIntLen = case finiteBitSize (0 :: Int) of
32 -> 10
64 -> 19
_ -> error "unsupported word size"
{-# INLINE readKnownNaturalAsInt #-}
-- | Similar to 'readKnownNaturalAsInt', for 'Int64'.
readKnownNaturalAsInt64 :: Bool -> B.ByteString -> Maybe Int64
readKnownNaturalAsInt64 = readInt 19
{-# INLINE readKnownNaturalAsInt64 #-}
readInt :: (Bounded a, Integral a) => Int -> Bool -> B.ByteString -> Maybe a
readInt maxLen neg s = if neg then fmap negate n else n
where
-- last digit of maxBound = 7, minBound = 8
n = readWord maxLen (maxBound `div` 10) (7 + fromIntegral (fromEnum neg)) s
{-# INLINE readInt #-}
-- | The input string must be an unsigned decimal integer with no extraneous
-- leading zeros. Returns Nothing if the value is outside the bounds of a
-- @Word@.
readKnownNaturalAsWord :: B.ByteString -> Maybe Word
readKnownNaturalAsWord = readWord maxWordLen (maxBound `div` 10) 5
where
-- last digit of maxBound = 5
maxWordLen = case finiteBitSize (0 :: Word) of
32 -> 10
64 -> 20
_ -> error "unsupported word size"
{-# INLINE readKnownNaturalAsWord #-}
-- | Similar to 'readKnownNaturalAsWord', for 'Word64'.
readKnownNaturalAsWord64 :: B.ByteString -> Maybe Word64
readKnownNaturalAsWord64 = readWord 20 (maxBound `div` 10) 5
{-# INLINE readKnownNaturalAsWord64 #-}
-- maxLen must be > 0!
readWord :: (Bounded a, Integral a) => Int -> a -> a -> B.ByteString -> Maybe a
readWord maxLen maxValueDiv10 maxValueMod10 s =
case compare (B.length s) maxLen of
LT -> Just $! readFull' s
EQ ->
let n = readFull (B.unsafeInit s)
d = digitToI (B.unsafeLast s)
in if n < maxValueDiv10 || n == maxValueDiv10 && d <= maxValueMod10
then Just $! n*10 + d
else Nothing
GT -> Nothing
{-# INLINE readWord #-}
readFull :: Integral a => B.ByteString -> a
readFull = B.foldl' (\acc c -> acc * 10 + digitToI c) 0
{-# INLINE readFull #-}
-- Same as readFull but avoids
-- https://gitlab.haskell.org/ghc/ghc/-/issues/24203
readFull' :: Integral a => B.ByteString -> a
readFull' s = case B.unsnoc s of
Nothing -> 0
Just (s',c) -> readFull s' * 10 + digitToI c
{-# INLINE readFull' #-}
digitToI :: Integral a => Word8 -> a
digitToI c = fromIntegral c - 48
{-# INLINE digitToI #-}