module TextBuilder.Domains.Digits where

import qualified Data.Text.Array as TextArray
import qualified TextBuilder.Domains.Digits.Codepoints as Codepoints
import TextBuilder.Prelude
import TextBuilderCore

-- | Decimal digit.
{-# INLINE decimalDigit #-}
decimalDigit :: (Integral a) => a -> TextBuilder
decimalDigit :: forall a. Integral a => a -> TextBuilder
decimalDigit (a -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral -> Int
n) =
  Int -> TextBuilder
unicodeCodepoint (Int
n Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
48)

-- | Hexadecimal digit.
{-# INLINE hexadecimalDigit #-}
hexadecimalDigit :: (Integral a) => a -> TextBuilder
hexadecimalDigit :: forall a. Integral a => a -> TextBuilder
hexadecimalDigit (a -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral -> Int
n) =
  if Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
9
    then Int -> TextBuilder
unicodeCodepoint (Int
n Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
48)
    else Int -> TextBuilder
unicodeCodepoint (Int
n Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
87)

{-# INLINE customFixedNumeralSystem #-}
customFixedNumeralSystem ::
  (FiniteBits a, Integral a) =>
  -- | Number of bits per digit.
  Int ->
  -- | Projection to codepoint with handling of overflow.
  (a -> a) ->
  -- | Value.
  a ->
  TextBuilder
customFixedNumeralSystem :: forall a.
(FiniteBits a, Integral a) =>
Int -> (a -> a) -> a -> TextBuilder
customFixedNumeralSystem Int
bitsPerDigit a -> a
digitCodepoint a
val =
  let size :: Int
size = Int -> Int -> Int
forall a. Integral a => a -> a -> a
div (a -> Int
forall b. FiniteBits b => b -> Int
finiteBitSize a
val Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
bitsPerDigit Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1) Int
bitsPerDigit
   in Int -> (forall s. MArray s -> Int -> ST s Int) -> TextBuilder
TextBuilder Int
size \MArray s
array Int
arrayStartIndex ->
        let go :: a -> Int -> ST s Int
go a
val Int
arrayIndex =
              if Int
arrayIndex Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
arrayStartIndex
                then do
                  MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array Int
arrayIndex (a -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (a -> a
digitCodepoint a
val))
                  a -> Int -> ST s Int
go (a -> Int -> a
forall a. Bits a => a -> Int -> a
unsafeShiftR a
val Int
bitsPerDigit) (Int -> Int
forall a. Enum a => a -> a
pred Int
arrayIndex)
                else Int -> ST s Int
forall a. a -> ST s a
forall (m :: * -> *) a. Monad m => a -> m a
return Int
indexAfter
            indexAfter :: Int
indexAfter =
              Int
arrayStartIndex Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
size
         in a -> Int -> ST s Int
go a
val (Int -> Int
forall a. Enum a => a -> a
pred Int
indexAfter)

-- |
-- [Two's complement](https://en.wikipedia.org/wiki/Two%27s_complement) binary representation of a value.
--
-- Bits of a statically sized value padded from the left according to the size.
-- If it's a negatable integer, the sign is reflected in the bits.
--
-- >>> binary @Int8 0
-- "00000000"
--
-- >>> binary @Int8 4
-- "00000100"
--
-- >>> binary @Int8 (-1)
-- "11111111"
--
-- >>> binary @Word8 255
-- "11111111"
--
-- >>> binary @Int16 4
-- "0000000000000100"
--
-- >>> binary @Int16 (-4)
-- "1111111111111100"
{-# INLINE binary #-}
binary :: (FiniteBits a) => a -> TextBuilder
binary :: forall a. FiniteBits a => a -> TextBuilder
binary a
val =
  let size :: Int
size = a -> Int
forall b. FiniteBits b => b -> Int
finiteBitSize a
val
   in Int -> (forall s. MArray s -> Int -> ST s Int) -> TextBuilder
TextBuilder Int
size \MArray s
array Int
arrayStartIndex ->
        let go :: a -> Int -> ST s Int
go a
val Int
arrayIndex =
              if Int
arrayIndex Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
arrayStartIndex
                then do
                  MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array Int
arrayIndex if a -> Int -> Bool
forall a. Bits a => a -> Int -> Bool
testBit a
val Int
0 then Word8
49 else Word8
48
                  a -> Int -> ST s Int
go (a -> Int -> a
forall a. Bits a => a -> Int -> a
unsafeShiftR a
val Int
1) (Int -> Int
forall a. Enum a => a -> a
pred Int
arrayIndex)
                else Int -> ST s Int
forall a. a -> ST s a
forall (m :: * -> *) a. Monad m => a -> m a
return Int
indexAfter
            indexAfter :: Int
indexAfter =
              Int
arrayStartIndex Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
size
         in a -> Int -> ST s Int
go a
val (Int -> Int
forall a. Enum a => a -> a
pred Int
indexAfter)

-- |
-- Same as 'binary', but with the \"0b\" prefix.
--
-- >>> prefixedBinary @Int8 0
-- "0b00000000"
{-# INLINE prefixedBinary #-}
prefixedBinary :: (FiniteBits a) => a -> TextBuilder
prefixedBinary :: forall a. FiniteBits a => a -> TextBuilder
prefixedBinary = TextBuilder -> TextBuilder -> TextBuilder
forall a. Monoid a => a -> a -> a
mappend TextBuilder
"0b" (TextBuilder -> TextBuilder)
-> (a -> TextBuilder) -> a -> TextBuilder
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. a -> TextBuilder
forall a. FiniteBits a => a -> TextBuilder
binary

-- | Octal representation of an integer.
--
-- >>> octal @Int32 123456
-- "00000361100"
--
-- >>> octal @Int32 (-123456)
-- "77777416700"
{-# INLINE octal #-}
octal :: (FiniteBits a, Integral a) => a -> TextBuilder
octal :: forall a. (FiniteBits a, Integral a) => a -> TextBuilder
octal = Int -> (a -> a) -> a -> TextBuilder
forall a.
(FiniteBits a, Integral a) =>
Int -> (a -> a) -> a -> TextBuilder
customFixedNumeralSystem Int
3 (a -> a
forall a. (Bits a, Num a) => a -> a
Codepoints.octalDigit (a -> a) -> (a -> a) -> a -> a
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. a -> a
forall a b. (Integral a, Num b) => a -> b
fromIntegral)

-- |
-- Same as 'octal', but with the \"0o\" prefix.
--
-- >>> prefixedOctal @Int8 0
-- "0o000"
{-# INLINE prefixedOctal #-}
prefixedOctal :: (FiniteBits a, Integral a) => a -> TextBuilder
prefixedOctal :: forall a. (FiniteBits a, Integral a) => a -> TextBuilder
prefixedOctal = TextBuilder -> TextBuilder -> TextBuilder
forall a. Monoid a => a -> a -> a
mappend TextBuilder
"0o" (TextBuilder -> TextBuilder)
-> (a -> TextBuilder) -> a -> TextBuilder
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. a -> TextBuilder
forall a. (FiniteBits a, Integral a) => a -> TextBuilder
octal

-- | Integer in hexadecimal notation with a fixed number of digits determined by the size of the type.
--
-- >>> hexadecimal @Int8 0
-- "00"
--
-- >>> hexadecimal @Int8 4
-- "04"
--
-- >>> hexadecimal @Int8 (-128)
-- "80"
--
-- >>> hexadecimal @Int8 (-1)
-- "ff"
--
-- >>> hexadecimal @Word8 255
-- "ff"
--
-- >>> hexadecimal @Int32 123456
-- "0001e240"
--
-- >>> hexadecimal @Int32 (-123456)
-- "fffe1dc0"
{-# INLINE hexadecimal #-}
hexadecimal :: (FiniteBits a, Integral a) => a -> TextBuilder
hexadecimal :: forall a. (FiniteBits a, Integral a) => a -> TextBuilder
hexadecimal = Int -> (a -> a) -> a -> TextBuilder
forall a.
(FiniteBits a, Integral a) =>
Int -> (a -> a) -> a -> TextBuilder
customFixedNumeralSystem Int
4 (a -> a
forall a. (Bits a, Num a, Ord a) => a -> a
Codepoints.hexDigit (a -> a) -> (a -> a) -> a -> a
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. a -> a
forall a b. (Integral a, Num b) => a -> b
fromIntegral)

-- |
-- Same as 'hexadecimal', but with the \"0x\" prefix.
--
-- >>> prefixedHexadecimal @Int8 0
-- "0x00"
{-# INLINE prefixedHexadecimal #-}
prefixedHexadecimal :: (FiniteBits a, Integral a) => a -> TextBuilder
prefixedHexadecimal :: forall a. (FiniteBits a, Integral a) => a -> TextBuilder
prefixedHexadecimal = TextBuilder -> TextBuilder -> TextBuilder
forall a. Monoid a => a -> a -> a
mappend TextBuilder
"0x" (TextBuilder -> TextBuilder)
-> (a -> TextBuilder) -> a -> TextBuilder
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. a -> TextBuilder
forall a. (FiniteBits a, Integral a) => a -> TextBuilder
hexadecimal

-- * Signed Numbers

{-# INLINE signed #-}
signed :: (Ord a, Num a) => (a -> TextBuilder) -> a -> TextBuilder
signed :: forall a. (Ord a, Num a) => (a -> TextBuilder) -> a -> TextBuilder
signed a -> TextBuilder
onUnsigned a
i =
  if a
i a -> a -> Bool
forall a. Ord a => a -> a -> Bool
>= a
0
    then a -> TextBuilder
onUnsigned a
i
    else Int -> TextBuilder
unicodeCodepoint Int
45 TextBuilder -> TextBuilder -> TextBuilder
forall a. Semigroup a => a -> a -> a
<> a -> TextBuilder
onUnsigned (a -> a
forall a. Num a => a -> a
negate a
i)

-- | Signed decimal representation of an integer.
--
-- >>> decimal 123456
-- "123456"
--
-- >>> decimal (-123456)
-- "-123456"
--
-- >>> decimal 0
-- "0"
{-# INLINEABLE decimal #-}
decimal :: (Integral a) => a -> TextBuilder
decimal :: forall a. Integral a => a -> TextBuilder
decimal = (a -> TextBuilder) -> a -> TextBuilder
forall a. (Ord a, Num a) => (a -> TextBuilder) -> a -> TextBuilder
signed a -> TextBuilder
forall a. Integral a => a -> TextBuilder
unsignedDecimal

-- * Unsigned Numbers

-- | Render a number in the given radix.
{-# INLINE digitsByRadix #-}
digitsByRadix :: (Integral a) => a -> (a -> a) -> a -> TextBuilder
digitsByRadix :: forall a. Integral a => a -> (a -> a) -> a -> TextBuilder
digitsByRadix a
radix a -> a
digitCodepoint =
  Int -> [a] -> a -> TextBuilder
go Int
0 []
  where
    go :: Int -> [a] -> a -> TextBuilder
go !Int
offset ![a]
digits a
x = case a -> a -> (a, a)
forall a. Integral a => a -> a -> (a, a)
divMod a
x a
radix of
      (a
next, a
digit) ->
        if a
next a -> a -> Bool
forall a. Ord a => a -> a -> Bool
<= a
0
          then Int -> [a] -> TextBuilder
finish (Int -> Int
forall a. Enum a => a -> a
succ Int
offset) (a
digit a -> [a] -> [a]
forall a. a -> [a] -> [a]
: [a]
digits)
          else Int -> [a] -> a -> TextBuilder
go (Int -> Int
forall a. Enum a => a -> a
succ Int
offset) (a
digit a -> [a] -> [a]
forall a. a -> [a] -> [a]
: [a]
digits) a
next

    finish :: Int -> [a] -> TextBuilder
finish Int
size [a]
digits =
      Int -> (forall s. MArray s -> Int -> ST s Int) -> TextBuilder
TextBuilder Int
size MArray s -> Int -> ST s Int
forall s. MArray s -> Int -> ST s Int
action
      where
        action :: TextArray.MArray s -> Int -> ST s Int
        action :: forall s. MArray s -> Int -> ST s Int
action MArray s
array =
          [a] -> Int -> ST s Int
go [a]
digits
          where
            go :: [a] -> Int -> ST s Int
go [a]
digits Int
offset = case [a]
digits of
              [] -> Int -> ST s Int
forall a. a -> ST s a
forall (m :: * -> *) a. Monad m => a -> m a
return Int
offset
              (a
digit : [a]
digits) -> do
                MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array Int
offset (a -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (a -> a
digitCodepoint a
digit))
                [a] -> Int -> ST s Int
go [a]
digits (Int -> Int
forall a. Enum a => a -> a
succ Int
offset)

-- | Unsigned octal representation of a non-negative integral value.
--
-- __Warning:__ It is your responsibility to ensure that the value is non-negative.
--
--
-- >>> unsignedOctal 7
-- "7"
--
-- >>> unsignedOctal 9
-- "11"
--
-- >>> unsignedOctal 16
-- "20"
{-# INLINE unsignedOctal #-}
unsignedOctal :: (Integral a) => a -> TextBuilder
unsignedOctal :: forall a. Integral a => a -> TextBuilder
unsignedOctal =
  a -> (a -> a) -> a -> TextBuilder
forall a. Integral a => a -> (a -> a) -> a -> TextBuilder
digitsByRadix a
8 (a -> a -> a
forall a. Num a => a -> a -> a
+ a
48)

-- | Unsigned decimal representation of a non-negative integral value.
--
-- __Warning:__ It is your responsibility to ensure that the value is non-negative.
--
-- >>> unsignedDecimal 123456
-- "123456"
--
-- >>> unsignedDecimal 0
-- "0"
{-# INLINE unsignedDecimal #-}
unsignedDecimal :: (Integral a) => a -> TextBuilder
unsignedDecimal :: forall a. Integral a => a -> TextBuilder
unsignedDecimal =
  a -> (a -> a) -> a -> TextBuilder
forall a. Integral a => a -> (a -> a) -> a -> TextBuilder
digitsByRadix a
10 (a -> a -> a
forall a. Num a => a -> a -> a
+ a
48)

-- | Unsigned hexadecimal representation of a non-negative integral value.
--
-- __Warning:__ It is your responsibility to ensure that the value is non-negative.
--
-- >>> unsignedHexadecimal 123456
-- "1e240"
--
-- >>> unsignedHexadecimal 0
-- "0"
{-# INLINE unsignedHexadecimal #-}
unsignedHexadecimal :: (Integral a) => a -> TextBuilder
unsignedHexadecimal :: forall a. Integral a => a -> TextBuilder
unsignedHexadecimal =
  a -> (a -> a) -> a -> TextBuilder
forall a. Integral a => a -> (a -> a) -> a -> TextBuilder
digitsByRadix a
16 (\a
digit -> if a
digit a -> a -> Bool
forall a. Ord a => a -> a -> Bool
<= a
9 then a
digit a -> a -> a
forall a. Num a => a -> a -> a
+ a
48 else a
digit a -> a -> a
forall a. Num a => a -> a -> a
+ a
87)

-- * Other

-- | Fixed-length decimal without sign.
-- Padded with zeros or trimmed depending on whether it's shorter or longer
-- than specified.
--
-- >>> fixedLengthDecimal 5 123
-- "00123"
--
-- >>> fixedLengthDecimal 5 123456
-- "23456"
--
-- >>> fixedLengthDecimal 5 (-123456)
-- "23456"
--
-- >>> fixedLengthDecimal 7 (-123456)
-- "0123456"
--
-- >>> fixedLengthDecimal 0 123
-- ""
--
-- >>> fixedLengthDecimal (-2) 123
-- ""
{-# INLINEABLE fixedLengthDecimal #-}
fixedLengthDecimal :: (Integral a) => Int -> a -> TextBuilder
fixedLengthDecimal :: forall a. Integral a => Int -> a -> TextBuilder
fixedLengthDecimal (Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
0 -> Int
size) (a -> a
forall a. Num a => a -> a
abs -> a
val) =
  Int -> (forall s. MArray s -> Int -> ST s Int) -> TextBuilder
TextBuilder Int
size ((forall s. MArray s -> Int -> ST s Int) -> TextBuilder)
-> (forall s. MArray s -> Int -> ST s Int) -> TextBuilder
forall a b. (a -> b) -> a -> b
$ \MArray s
array Int
startOffset ->
    let offsetAfter :: Int
offsetAfter = Int
startOffset Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
size
        writeValue :: a -> Int -> ST s Int
writeValue a
val Int
offset =
          if Int
offset Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
startOffset
            then
              if a
val a -> a -> Bool
forall a. Eq a => a -> a -> Bool
/= a
0
                then case a -> a -> (a, a)
forall a. Integral a => a -> a -> (a, a)
divMod a
val a
10 of
                  (a
val, a
digit) -> do
                    MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array Int
offset (Word8 -> ST s ()) -> Word8 -> ST s ()
forall a b. (a -> b) -> a -> b
$ Word8
48 Word8 -> Word8 -> Word8
forall a. Num a => a -> a -> a
+ a -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral a
digit
                    a -> Int -> ST s Int
writeValue a
val (Int -> Int
forall a. Enum a => a -> a
pred Int
offset)
                else Int -> ST s Int
writePadding Int
offset
            else Int -> ST s Int
forall a. a -> ST s a
forall (m :: * -> *) a. Monad m => a -> m a
return Int
offsetAfter
        writePadding :: Int -> ST s Int
writePadding Int
offset =
          if Int
offset Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
startOffset
            then do
              MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array Int
offset Word8
48
              Int -> ST s Int
writePadding (Int -> Int
forall a. Enum a => a -> a
pred Int
offset)
            else Int -> ST s Int
forall a. a -> ST s a
forall (m :: * -> *) a. Monad m => a -> m a
return Int
offsetAfter
     in a -> Int -> ST s Int
writeValue a
val (Int -> Int
forall a. Enum a => a -> a
pred Int
offsetAfter)

-- | Decimal representation of an integral value with thousands separated by the specified character.
--
-- >>> thousandSeparatedDecimal ',' 1234567890
-- "1,234,567,890"
--
-- >>> thousandSeparatedDecimal ' ' (-1234567890)
-- "-1 234 567 890"
{-# INLINEABLE thousandSeparatedDecimal #-}
thousandSeparatedDecimal :: (Integral a) => Char -> a -> TextBuilder
thousandSeparatedDecimal :: forall a. Integral a => Char -> a -> TextBuilder
thousandSeparatedDecimal Char
separatorChar =
  (a -> TextBuilder) -> a -> TextBuilder
forall a. (Ord a, Num a) => (a -> TextBuilder) -> a -> TextBuilder
signed (Char -> a -> TextBuilder
forall a. Integral a => Char -> a -> TextBuilder
unsignedThousandSeparatedDecimal Char
separatorChar)

-- | Decimal representation of an unsigned integral value with thousands separated by the specified character.
--
-- __Warning:__ It is your responsibility to ensure that the value is non-negative.
--
-- >>> unsignedThousandSeparatedDecimal ',' 1234567890
-- "1,234,567,890"
--
-- >>> unsignedThousandSeparatedDecimal ' ' 1234567890
-- "1 234 567 890"
--
-- >>> unsignedThousandSeparatedDecimal ',' 0
-- "0"
{-# INLINEABLE unsignedThousandSeparatedDecimal #-}
unsignedThousandSeparatedDecimal :: (Integral a) => Char -> a -> TextBuilder
unsignedThousandSeparatedDecimal :: forall a. Integral a => Char -> a -> TextBuilder
unsignedThousandSeparatedDecimal Char
separatorChar =
  a -> TextBuilder
processRightmostDigit
  where
    processRightmostDigit :: a -> TextBuilder
processRightmostDigit a
value =
      case a -> a -> (a, a)
forall a. Integral a => a -> a -> (a, a)
divMod a
value a
10 of
        (a
value, a
digit) ->
          [TextBuilder] -> Int -> a -> TextBuilder
processAnotherDigit [a -> TextBuilder
forall a. Integral a => a -> TextBuilder
decimalDigit a
digit] (Int
1 :: Int) a
value
    processAnotherDigit :: [TextBuilder] -> Int -> a -> TextBuilder
processAnotherDigit [TextBuilder]
builders Int
index a
value =
      if a
value a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
0
        then [TextBuilder] -> TextBuilder
forall a. Monoid a => [a] -> a
mconcat [TextBuilder]
builders
        else case a -> a -> (a, a)
forall a. Integral a => a -> a -> (a, a)
divMod a
value a
10 of
          (a
value, a
digit) ->
            if Int -> Int -> Int
forall a. Integral a => a -> a -> a
mod Int
index Int
3 Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0
              then
                [TextBuilder] -> Int -> a -> TextBuilder
processAnotherDigit
                  (a -> TextBuilder
forall a. Integral a => a -> TextBuilder
decimalDigit a
digit TextBuilder -> [TextBuilder] -> [TextBuilder]
forall a. a -> [a] -> [a]
: Char -> TextBuilder
char Char
separatorChar TextBuilder -> [TextBuilder] -> [TextBuilder]
forall a. a -> [a] -> [a]
: [TextBuilder]
builders)
                  (Int -> Int
forall a. Enum a => a -> a
succ Int
index)
                  a
value
              else
                [TextBuilder] -> Int -> a -> TextBuilder
processAnotherDigit
                  (a -> TextBuilder
forall a. Integral a => a -> TextBuilder
decimalDigit a
digit TextBuilder -> [TextBuilder] -> [TextBuilder]
forall a. a -> [a] -> [a]
: [TextBuilder]
builders)
                  (Int -> Int
forall a. Enum a => a -> a
succ Int
index)
                  a
value