{-# LANGUAGE BangPatterns, CPP, MagicHash, OverloadedStrings, UnboxedTuples #-} -- Module: Blaze.Text.Int -- Copyright: (c) 2011 MailRank, Inc. -- License: BSD3 -- Maintainer: Bryan O'Sullivan -- Stability: experimental -- Portability: portable -- -- Efficiently serialize an integral value as a lazy 'L.ByteString'. module Blaze.Text.Int ( digit , integral , minus ) where import Blaze.ByteString.Builder import Blaze.ByteString.Builder.Char8 import Data.ByteString.Char8 () import Data.Int (Int8, Int16, Int32, Int64) import Data.Monoid (mappend, mempty) import Data.Word (Word, Word8, Word16, Word32, Word64) import GHC.Base (quotInt, remInt) import GHC.Num (quotRemInteger) import GHC.Types (Int(..)) #ifdef __GLASGOW_HASKELL__ # if __GLASGOW_HASKELL__ < 611 import GHC.Integer.Internals # else import GHC.Integer.GMP.Internals # endif #endif #ifdef INTEGER_GMP # define PAIR(a,b) (# a,b #) #else # define PAIR(a,b) (a,b) #endif integral :: Integral a => a -> Builder {-# RULES "integral/Int" integral = bounded :: Int -> Builder #-} {-# RULES "integral/Int8" integral = bounded :: Int8 -> Builder #-} {-# RULES "integral/Int16" integral = bounded :: Int16 -> Builder #-} {-# RULES "integral/Int32" integral = bounded :: Int32 -> Builder #-} {-# RULES "integral/Int64" integral = bounded :: Int64 -> Builder #-} {-# RULES "integral/Word" integral = nonNegative :: Word -> Builder #-} {-# RULES "integral/Word8" integral = nonNegative :: Word8 -> Builder #-} {-# RULES "integral/Word16" integral = nonNegative :: Word16 -> Builder #-} {-# RULES "integral/Word32" integral = nonNegative :: Word32 -> Builder #-} {-# RULES "integral/Word64" integral = nonNegative :: Word64 -> Builder #-} {-# RULES "integral/Integer" integral = integer :: Integer -> Builder #-} -- This definition of the function is here PURELY to be used by ghci -- and those rare cases where GHC is being invoked without -- optimization, as otherwise the rewrite rules above should fire. The -- test for "-0" catches an overflow if we render minBound. integral i | i >= 0 = nonNegative i | toByteString b == "-0" = fromString (show i) | otherwise = b where b = minus `mappend` nonNegative (-i) bounded :: (Bounded a, Integral a) => a -> Builder {-# SPECIALIZE bounded :: Int -> Builder #-} {-# SPECIALIZE bounded :: Int8 -> Builder #-} {-# SPECIALIZE bounded :: Int16 -> Builder #-} {-# SPECIALIZE bounded :: Int32 -> Builder #-} {-# SPECIALIZE bounded :: Int64 -> Builder #-} bounded i | i >= 0 = nonNegative i | i > minBound = minus `mappend` nonNegative (-i) | otherwise = minus `mappend` nonNegative (negate (k `quot` 10)) `mappend` digit (negate (k `rem` 10)) where k = minBound `asTypeOf` i nonNegative :: Integral a => a -> Builder {-# SPECIALIZE nonNegative :: Int -> Builder #-} {-# SPECIALIZE nonNegative :: Int8 -> Builder #-} {-# SPECIALIZE nonNegative :: Int16 -> Builder #-} {-# SPECIALIZE nonNegative :: Int32 -> Builder #-} {-# SPECIALIZE nonNegative :: Int64 -> Builder #-} {-# SPECIALIZE nonNegative :: Word -> Builder #-} {-# SPECIALIZE nonNegative :: Word8 -> Builder #-} {-# SPECIALIZE nonNegative :: Word16 -> Builder #-} {-# SPECIALIZE nonNegative :: Word32 -> Builder #-} {-# SPECIALIZE nonNegative :: Word64 -> Builder #-} nonNegative = go where go n | n < 10 = digit n | otherwise = go (n `quot` 10) `mappend` digit (n `rem` 10) digit :: Integral a => a -> Builder digit n = fromWord8 $! fromIntegral n + 48 {-# INLINE digit #-} minus :: Builder minus = fromWord8 45 int :: Int -> Builder int = integral {-# INLINE int #-} integer :: Integer -> Builder integer (S# i#) = int (I# i#) integer i | i < 0 = minus `mappend` go (-i) | otherwise = go i where go n | n < maxInt = int (fromInteger n) | otherwise = putH (splitf (maxInt * maxInt) n) splitf p n | p > n = [n] | otherwise = splith p (splitf (p*p) n) splith p (n:ns) = case n `quotRemInteger` p of PAIR(q,r) | q > 0 -> q : r : splitb p ns | otherwise -> r : splitb p ns splith _ _ = error "splith: the impossible happened." splitb p (n:ns) = case n `quotRemInteger` p of PAIR(q,r) -> q : r : splitb p ns splitb _ _ = [] data T = T !Integer !Int fstT :: T -> Integer fstT (T a _) = a maxInt :: Integer maxDigits :: Int T maxInt maxDigits = until ((>mi) . (*10) . fstT) (\(T n d) -> T (n*10) (d+1)) (T 10 1) where mi = fromIntegral (maxBound :: Int) putH :: [Integer] -> Builder putH (n:ns) = case n `quotRemInteger` maxInt of PAIR(x,y) | q > 0 -> int q `mappend` pblock r `mappend` putB ns | otherwise -> int r `mappend` putB ns where q = fromInteger x r = fromInteger y putH _ = error "putH: the impossible happened" putB :: [Integer] -> Builder putB (n:ns) = case n `quotRemInteger` maxInt of PAIR(x,y) -> pblock q `mappend` pblock r `mappend` putB ns where q = fromInteger x r = fromInteger y putB _ = mempty pblock :: Int -> Builder pblock = go maxDigits where go !d !n | d == 1 = digit n | otherwise = go (d-1) q `mappend` digit r where q = n `quotInt` 10 r = n `remInt` 10