{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -Wno-unused-top-binds #-}

module TextBuilderCore
  ( TextBuilder (..),

    -- * Destructors
    isEmpty,
    toText,

    -- * Constructors

    -- ** Text
    string,
    text,
    lazyText,

    -- ** Character
    char,
    unicodeCodepoint,

    -- ** Primitives
    unsafeChars,
    unsafeSeptets,
    unsafeReverseSeptets,
  )
where

import qualified Data.Text as Text
import qualified Data.Text.Array as TextArray
import qualified Data.Text.Internal as TextInternal
import qualified Data.Text.Lazy as TextLazy
import TextBuilderCore.Prelude

#if MIN_VERSION_text(2,0,0)
import qualified TextBuilderCore.Utf8View as Utf8View
#else
import qualified TextBuilderCore.Utf16View as Utf16View
#endif

-- |
-- Composable specification of how to efficiently construct strict 'Text'.
--
-- Provides instances of 'Semigroup' and 'Monoid', which have complexity of /O(1)/.
data TextBuilder
  = TextBuilder
      -- | Estimated maximum size of the byte array to allocate.
      --
      -- If the builder is empty it must be 0.
      -- Otherwise it must be greater than or equal to the amount of bytes to be written.
      --
      -- __Warning:__ Due to \"text\" switching from UTF-16 to UTF-8 since version 2, 'Word16' is used as the byte when the \"text\" version is @<2@ and 'Word8' is used when it's @>=2@.
      Int
      -- | Function that populates a preallocated byte array of the estimated maximum size specified above provided an offset into it and producing the offset after.
      --
      -- __Warning:__ The function must not write outside of the allocated array or bad things will happen to the running app.
      --
      -- __Warning:__ Keep in mind that the array is operating on 'Word8' values starting from @text-2.0@, but prior to it it operates on 'Word16'. This is due to the \"text\" library switching from UTF-16 to UTF-8 after version 2. To deal with this you have the following options:
      --
      -- 1. Restrict the version of the \"text\" library in your package to @>=2@.
      --
      -- 2. Use helpers provided by this library, such as 'unsafeSeptets' and 'unsafeReverseSeptets', which abstract over the differences in the underlying representation.
      --
      -- 3. Use CPP to conditionally compile your code for different versions of \"text\".
      (forall s. TextArray.MArray s -> Int -> ST s Int)

instance IsString TextBuilder where
  fromString :: String -> TextBuilder
fromString = String -> TextBuilder
string

instance Show TextBuilder where
  show :: TextBuilder -> String
show = Text -> String
forall a. Show a => a -> String
show (Text -> String) -> (TextBuilder -> Text) -> TextBuilder -> String
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
. TextBuilder -> Text
toText

instance Eq TextBuilder where
  == :: TextBuilder -> TextBuilder -> Bool
(==) = (Text -> Text -> Bool)
-> (TextBuilder -> Text) -> TextBuilder -> TextBuilder -> Bool
forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
on Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
(==) TextBuilder -> Text
toText

instance Semigroup TextBuilder where
  {-# INLINE (<>) #-}
  <> :: TextBuilder -> TextBuilder -> TextBuilder
(<>) (TextBuilder Int
estimatedArraySizeL forall s. MArray s -> Int -> ST s Int
writeL) (TextBuilder Int
estimatedArraySizeR forall s. MArray s -> Int -> ST s Int
writeR) =
    Int -> (forall s. MArray s -> Int -> ST s Int) -> TextBuilder
TextBuilder
      (Int
estimatedArraySizeL Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
estimatedArraySizeR)
      ( \MArray s
array Int
offset -> do
          offsetAfter1 <- MArray s -> Int -> ST s Int
forall s. MArray s -> Int -> ST s Int
writeL MArray s
array Int
offset
          writeR array offsetAfter1
      )
  stimes :: forall b. Integral b => b -> TextBuilder -> TextBuilder
stimes b
n (TextBuilder Int
maxSize forall s. MArray s -> Int -> ST s Int
write) =
    Int -> (forall s. MArray s -> Int -> ST s Int) -> TextBuilder
TextBuilder
      (Int
maxSize Int -> Int -> Int
forall a. Num a => a -> a -> a
* b -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral b
n)
      ( \MArray s
array ->
          let go :: b -> Int -> ST s Int
go b
n Int
offset =
                if b
n b -> b -> Bool
forall a. Ord a => a -> a -> Bool
> b
0
                  then do
                    offset <- MArray s -> Int -> ST s Int
forall s. MArray s -> Int -> ST s Int
write MArray s
array Int
offset
                    go (pred n) offset
                  else Int -> ST s Int
forall a. a -> ST s a
forall (m :: * -> *) a. Monad m => a -> m a
return Int
offset
           in b -> Int -> ST s Int
go b
n
      )

instance Monoid TextBuilder where
  {-# INLINE mempty #-}
  mempty :: TextBuilder
mempty = Int -> (forall s. MArray s -> Int -> ST s Int) -> TextBuilder
TextBuilder Int
0 ((Int -> ST s Int) -> MArray s -> Int -> ST s Int
forall a b. a -> b -> a
const Int -> ST s Int
forall a. a -> ST s a
forall (m :: * -> *) a. Monad m => a -> m a
return)
  {-# INLINE mconcat #-}
  mconcat :: [TextBuilder] -> TextBuilder
mconcat [TextBuilder]
list =
    Int -> (forall s. MArray s -> Int -> ST s Int) -> TextBuilder
TextBuilder
      ((Int -> TextBuilder -> Int) -> Int -> [TextBuilder] -> Int
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' (\Int
acc (TextBuilder Int
maxSize forall s. MArray s -> Int -> ST s Int
_) -> Int
acc Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
maxSize) Int
0 [TextBuilder]
list)
      ( \MArray s
array ->
          let go :: [TextBuilder] -> Int -> ST s Int
go [] Int
offset = Int -> ST s Int
forall a. a -> ST s a
forall (m :: * -> *) a. Monad m => a -> m a
return Int
offset
              go (TextBuilder Int
_ forall s. MArray s -> Int -> ST s Int
write : [TextBuilder]
xs) Int
offset = do
                offsetAfter <- MArray s -> Int -> ST s Int
forall s. MArray s -> Int -> ST s Int
write MArray s
array Int
offset
                go xs offsetAfter
           in [TextBuilder] -> Int -> ST s Int
go [TextBuilder]
list
      )

instance Arbitrary TextBuilder where
  arbitrary :: Gen TextBuilder
arbitrary = Text -> TextBuilder
text (Text -> TextBuilder) -> (String -> Text) -> String -> 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
. String -> Text
Text.pack (String -> TextBuilder) -> Gen String -> Gen TextBuilder
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Gen String
forall a. Arbitrary a => Gen a
arbitrary
  shrink :: TextBuilder -> [TextBuilder]
shrink TextBuilder
a = Text -> TextBuilder
text (Text -> TextBuilder) -> (String -> Text) -> String -> 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
. String -> Text
Text.pack (String -> TextBuilder) -> [String] -> [TextBuilder]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> [String]
forall a. Arbitrary a => a -> [a]
shrink (Text -> String
Text.unpack (TextBuilder -> Text
toText TextBuilder
a))

-- * Destructors

-- | Execute the builder producing a strict text.
toText :: TextBuilder -> Text
toText :: TextBuilder -> Text
toText (TextBuilder Int
maxSize forall s. MArray s -> Int -> ST s Int
write) =
  (forall s. ST s Text) -> Text
forall a. (forall s. ST s a) -> a
runST ((forall s. ST s Text) -> Text) -> (forall s. ST s Text) -> Text
forall a b. (a -> b) -> a -> b
$ do
    array <- Int -> ST s (MArray s)
forall s. Int -> ST s (MArray s)
TextArray.new Int
maxSize
    offsetAfter <- write array 0
    frozenArray <- TextArray.unsafeFreeze array
    return $ TextInternal.text frozenArray 0 offsetAfter

-- | Check whether the builder is empty.
{-# INLINE isEmpty #-}
isEmpty :: TextBuilder -> Bool
isEmpty :: TextBuilder -> Bool
isEmpty (TextBuilder Int
maxSize forall s. MArray s -> Int -> ST s Int
_) = Int
maxSize Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0

-- * Constructors

-- | Construct from a list of characters.
{-# INLINE string #-}
string :: String -> TextBuilder
string :: String -> TextBuilder
string String
string =
  Int -> String -> TextBuilder
unsafeChars (String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
string) String
string

-- | Strict text.
{-# INLINEABLE text #-}
text :: Text -> TextBuilder
#if MIN_VERSION_text(2,0,0)
text :: Text -> TextBuilder
text (TextInternal.Text Array
array Int
offset Int
length) =
  Int -> (forall s. MArray s -> Int -> ST s Int) -> TextBuilder
TextBuilder Int
length \MArray s
builderArray Int
builderOffset -> do
    Int -> MArray s -> Int -> Array -> Int -> ST s ()
forall s. Int -> MArray s -> Int -> Array -> Int -> ST s ()
TextArray.copyI Int
length MArray s
builderArray Int
builderOffset Array
array Int
offset
    Int -> ST s Int
forall a. a -> ST s a
forall (m :: * -> *) a. Monad m => a -> m a
return (Int -> ST s Int) -> Int -> ST s Int
forall a b. (a -> b) -> a -> b
$ Int
builderOffset Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
length
#else
text (TextInternal.Text array offset length) =
  TextBuilder length \builderArray builderOffset -> do
    let builderOffsetAfter = builderOffset + length
    TextArray.copyI builderArray builderOffset array offset builderOffsetAfter
    return builderOffsetAfter
#endif

-- | Lazy text.
{-# INLINE lazyText #-}
lazyText :: TextLazy.Text -> TextBuilder
lazyText :: Text -> TextBuilder
lazyText =
  (Text -> TextBuilder -> TextBuilder)
-> TextBuilder -> Text -> TextBuilder
forall a. (Text -> a -> a) -> a -> Text -> a
TextLazy.foldrChunks (TextBuilder -> TextBuilder -> TextBuilder
forall a. Monoid a => a -> a -> a
mappend (TextBuilder -> TextBuilder -> TextBuilder)
-> (Text -> TextBuilder) -> Text -> TextBuilder -> 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
. Text -> TextBuilder
text) TextBuilder
forall a. Monoid a => a
mempty

-- ** Codepoint

-- | Unicode character.
{-# INLINE char #-}
char :: Char -> TextBuilder
char :: Char -> TextBuilder
char = Int -> TextBuilder
unicodeCodepoint (Int -> TextBuilder) -> (Char -> Int) -> Char -> 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
. Char -> Int
ord

-- | Safe Unicode codepoint with invalid values replaced by the @�@ char (codepoint @0xfffd@),
-- which is the same as what @Data.Text.'Data.Text.pack'@ does.
{-# INLINE unicodeCodepoint #-}
unicodeCodepoint :: Int -> TextBuilder
unicodeCodepoint :: Int -> TextBuilder
unicodeCodepoint = Int -> TextBuilder
unsafeUnicodeCodepoint (Int -> TextBuilder) -> (Int -> Int) -> Int -> 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
. Int -> Int
forall {a}. (Bits a, Num a) => a -> a
project
  where
    project :: a -> a
project a
x =
      if a
x a -> a -> a
forall a. Bits a => a -> a -> a
.&. a
0x1ff800 a -> a -> Bool
forall a. Eq a => a -> a -> Bool
/= a
0xd800
        then a
x
        else a
0xfffd

-- | Unicode codepoint.
--
-- __Warning:__ It is your responsibility to ensure that the codepoint is in proper range,
-- otherwise the produced text will be broken.
-- It must be in the range of 0x0000 to 0x10FFFF.
{-# INLINE unsafeUnicodeCodepoint #-}
unsafeUnicodeCodepoint :: Int -> TextBuilder
#if MIN_VERSION_text(2,0,0)
unsafeUnicodeCodepoint :: Int -> TextBuilder
unsafeUnicodeCodepoint Int
x =
  Int -> Utf8View
Utf8View.unicodeCodepoint Int
x Word8 -> TextBuilder
unsafeUtf8CodeUnits1 Word8 -> Word8 -> TextBuilder
unsafeUtf8CodeUnits2 Word8 -> Word8 -> Word8 -> TextBuilder
unsafeUtf8CodeUnits3 Word8 -> Word8 -> Word8 -> Word8 -> TextBuilder
unsafeUtf8CodeUnits4
#else
unsafeUnicodeCodepoint x =
  Utf16View.unicodeCodepoint x unsafeUtf16CodeUnits1 unsafeUtf16CodeUnits2
#endif

-- | Single code-unit UTF-8 character.
unsafeUtf8CodeUnits1 :: Word8 -> TextBuilder
#if MIN_VERSION_text(2,0,0)
{-# INLINEABLE unsafeUtf8CodeUnits1 #-}
unsafeUtf8CodeUnits1 :: Word8 -> TextBuilder
unsafeUtf8CodeUnits1 Word8
unit1 =
  Int -> (forall s. MArray s -> Int -> ST s Int) -> TextBuilder
TextBuilder Int
1 \MArray s
array Int
offset ->
    MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array Int
offset Word8
unit1
      ST s () -> Int -> ST s Int
forall (f :: * -> *) a b. Functor f => f a -> b -> f b
$> Int -> Int
forall a. Enum a => a -> a
succ Int
offset
#else
{-# INLINE unsafeUtf8CodeUnits1 #-}
unsafeUtf8CodeUnits1 unit1 =
  Utf16View.utf8CodeUnits1 unit1 unsafeUtf16CodeUnits1 unsafeUtf16CodeUnits2
#endif

-- | Double code-unit UTF-8 character.
unsafeUtf8CodeUnits2 :: Word8 -> Word8 -> TextBuilder
#if MIN_VERSION_text(2,0,0)
{-# INLINEABLE unsafeUtf8CodeUnits2 #-}
unsafeUtf8CodeUnits2 :: Word8 -> Word8 -> TextBuilder
unsafeUtf8CodeUnits2 Word8
unit1 Word8
unit2 =
  Int -> (forall s. MArray s -> Int -> ST s Int) -> TextBuilder
TextBuilder Int
2 \MArray s
array Int
offset -> do
    MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array Int
offset Word8
unit1
    MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array (Int
offset Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1) Word8
unit2
    Int -> ST s Int
forall a. a -> ST s a
forall (m :: * -> *) a. Monad m => a -> m a
return (Int -> ST s Int) -> Int -> ST s Int
forall a b. (a -> b) -> a -> b
$ Int
offset Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
2
#else
{-# INLINE unsafeUtf8CodeUnits2 #-}
unsafeUtf8CodeUnits2 unit1 unit2 =
  Utf16View.utf8CodeUnits2 unit1 unit2 unsafeUtf16CodeUnits1 unsafeUtf16CodeUnits2
#endif

-- | Triple code-unit UTF-8 character.
unsafeUtf8CodeUnits3 :: Word8 -> Word8 -> Word8 -> TextBuilder
#if MIN_VERSION_text(2,0,0)
{-# INLINEABLE unsafeUtf8CodeUnits3 #-}
unsafeUtf8CodeUnits3 :: Word8 -> Word8 -> Word8 -> TextBuilder
unsafeUtf8CodeUnits3 Word8
unit1 Word8
unit2 Word8
unit3 =
  Int -> (forall s. MArray s -> Int -> ST s Int) -> TextBuilder
TextBuilder Int
3 \MArray s
array Int
offset -> do
    MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array Int
offset Word8
unit1
    MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array (Int
offset Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1) Word8
unit2
    MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array (Int
offset Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
2) Word8
unit3
    Int -> ST s Int
forall a. a -> ST s a
forall (m :: * -> *) a. Monad m => a -> m a
return (Int -> ST s Int) -> Int -> ST s Int
forall a b. (a -> b) -> a -> b
$ Int
offset Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
3
#else
{-# INLINE unsafeUtf8CodeUnits3 #-}
unsafeUtf8CodeUnits3 unit1 unit2 unit3 =
  Utf16View.utf8CodeUnits3 unit1 unit2 unit3 unsafeUtf16CodeUnits1 unsafeUtf16CodeUnits2
#endif

-- | UTF-8 character out of 4 code units.
unsafeUtf8CodeUnits4 :: Word8 -> Word8 -> Word8 -> Word8 -> TextBuilder
#if MIN_VERSION_text(2,0,0)
{-# INLINEABLE unsafeUtf8CodeUnits4 #-}
unsafeUtf8CodeUnits4 :: Word8 -> Word8 -> Word8 -> Word8 -> TextBuilder
unsafeUtf8CodeUnits4 Word8
unit1 Word8
unit2 Word8
unit3 Word8
unit4 =
  Int -> (forall s. MArray s -> Int -> ST s Int) -> TextBuilder
TextBuilder Int
4 \MArray s
array Int
offset -> do
    MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array Int
offset Word8
unit1
    MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array (Int
offset Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1) Word8
unit2
    MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array (Int
offset Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
2) Word8
unit3
    MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array (Int
offset Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
3) Word8
unit4
    Int -> ST s Int
forall a. a -> ST s a
forall (m :: * -> *) a. Monad m => a -> m a
return (Int -> ST s Int) -> Int -> ST s Int
forall a b. (a -> b) -> a -> b
$ Int
offset Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
4
#else
{-# INLINE unsafeUtf8CodeUnits4 #-}
unsafeUtf8CodeUnits4 unit1 unit2 unit3 unit4 =
  Utf16View.utf8CodeUnits4 unit1 unit2 unit3 unit4 unsafeUtf16CodeUnits1 unsafeUtf16CodeUnits2
#endif

-- | Single code-unit UTF-16 character.
unsafeUtf16CodeUnits1 :: Word16 -> TextBuilder
#if MIN_VERSION_text(2,0,0)
{-# INLINE unsafeUtf16CodeUnits1 #-}
unsafeUtf16CodeUnits1 :: Word16 -> TextBuilder
unsafeUtf16CodeUnits1 = Int -> TextBuilder
unsafeUnicodeCodepoint (Int -> TextBuilder) -> (Word16 -> Int) -> Word16 -> 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
. Word16 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral
#else
{-# INLINEABLE unsafeUtf16CodeUnits1 #-}
unsafeUtf16CodeUnits1 unit =
  TextBuilder 1 \array offset ->
    TextArray.unsafeWrite array offset unit
      $> succ offset
#endif

-- | Double code-unit UTF-16 character.
unsafeUtf16CodeUnits2 :: Word16 -> Word16 -> TextBuilder
#if MIN_VERSION_text(2,0,0)
{-# INLINE unsafeUtf16CodeUnits2 #-}
unsafeUtf16CodeUnits2 :: Word16 -> Word16 -> TextBuilder
unsafeUtf16CodeUnits2 Word16
unit1 Word16
unit2 = Int -> TextBuilder
unsafeUnicodeCodepoint Int
cp
  where
    cp :: Int
cp = (((Word16 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word16
unit1 Int -> Int -> Int
forall a. Bits a => a -> a -> a
.&. Int
0x3FF) Int -> Int -> Int
forall a. Bits a => a -> Int -> a
`shiftL` Int
10) Int -> Int -> Int
forall a. Bits a => a -> a -> a
.|. (Word16 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word16
unit2 Int -> Int -> Int
forall a. Bits a => a -> a -> a
.&. Int
0x3FF)) Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
0x10000
#else
{-# INLINEABLE unsafeUtf16CodeUnits2 #-}
unsafeUtf16CodeUnits2 unit1 unit2 =
  TextBuilder 2 \array offset -> do
    TextArray.unsafeWrite array offset unit1
    TextArray.unsafeWrite array (succ offset) unit2
    return $ offset + 2
#endif

-- * Basic Unsafe Primitives

-- |
-- Helper for constructing from char producers a bit more efficiently than via @(text . fromString)@.
--
-- >>> unsafeChars 3 "123"
-- "123"
--
-- >>> unsafeChars 4 "123"
-- "123"
{-# INLINE unsafeChars #-}
unsafeChars ::
  -- | Maximum size of the provided list of characters.
  --
  -- __Warning__: Must be greater than or equal to the length of the list.
  Int ->
  [Char] ->
  TextBuilder
#if MIN_VERSION_text(2,0,0)
unsafeChars :: Int -> String -> TextBuilder
unsafeChars Int
maxChars String
chars =
  Int -> (forall s. MArray s -> Int -> ST s Int) -> TextBuilder
TextBuilder
    (Int
maxChars Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
4)
    ( \MArray s
array ->
        (Char -> (Int -> ST s Int) -> Int -> ST s Int)
-> (Int -> ST s Int) -> String -> Int -> ST s Int
forall a b. (a -> b -> b) -> b -> [a] -> b
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr
          ( \Char
char Int -> ST s Int
next Int
offset ->
              Int -> Utf8View
Utf8View.unicodeCodepoint
                (Char -> Int
ord Char
char)
                ( \Word8
byte -> do
                    MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array Int
offset Word8
byte
                    Int -> ST s Int
next (Int -> Int
forall a. Enum a => a -> a
succ Int
offset)
                )
                ( \Word8
byte1 Word8
byte2 -> do
                    MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array Int
offset Word8
byte1
                    MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array (Int -> Int
forall a. Enum a => a -> a
succ Int
offset) Word8
byte2
                    Int -> ST s Int
next (Int
offset Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
2)
                )
                ( \Word8
byte1 Word8
byte2 Word8
byte3 -> do
                    MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array Int
offset Word8
byte1
                    MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array (Int -> Int
forall a. Enum a => a -> a
succ Int
offset) Word8
byte2
                    MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array (Int
offset Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
2) Word8
byte3
                    Int -> ST s Int
next (Int
offset Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
3)
                )
                ( \Word8
byte1 Word8
byte2 Word8
byte3 Word8
byte4 -> do
                    MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array Int
offset Word8
byte1
                    MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array (Int -> Int
forall a. Enum a => a -> a
succ Int
offset) Word8
byte2
                    MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array (Int
offset Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
2) Word8
byte3
                    MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array (Int
offset Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
3) Word8
byte4
                    Int -> ST s Int
next (Int
offset Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
4)
                )
          )
          Int -> ST s Int
forall a. a -> ST s a
forall (m :: * -> *) a. Monad m => a -> m a
return
          String
chars
    )
#else
unsafeChars maxChars chars =
  TextBuilder
    (maxChars * 2)
    ( \array ->
        foldr
          ( \char next offset ->
              Utf16View.unicodeCodepoint
                (ord char)
                ( \byte -> do
                    TextArray.unsafeWrite array offset byte
                    next (succ offset)
                )
                ( \byte1 byte2 -> do
                    TextArray.unsafeWrite array offset byte1
                    TextArray.unsafeWrite array (succ offset) byte2
                    next (offset + 2)
                )
          )
          return
          chars
    )
#endif

-- |
-- Provides a unified way to deal with the byte array regardless of the version of the @text@ library.
--
-- Keep in mind that prior to @text-2.0@, the array was operating on 'Word16' values due to the library abstracting over @UTF-16@.
-- Starting from @text-2.0@, the array operates on 'Word8' values and the library abstracts over @UTF-8@.
--
-- This function is useful for building ASCII values.
--
-- >>> unsafeSeptets 3 (fmap (+48) [1, 2, 3])
-- "123"
--
-- >>> unsafeSeptets 4 (fmap (+48) [1, 2, 3])
-- "123"
{-# INLINE unsafeSeptets #-}
unsafeSeptets ::
  -- | Maximum size of the byte array to allocate.
  --
  -- Must be greater than or equal to the length of the list.
  --
  -- __Warning:__ If it is smaller, bad things will happen.
  -- We'll be writing outside of the allocated array.
  Int ->
  -- | List of bytes to write.
  --
  -- __Warning:__ It is your responsibility to ensure that the bytes are smaller than 128.
  -- Otherwise the produced text will have a broken encoding.
  --
  -- To ensure of optimization kicking in it is advised to construct the list using 'GHC.List.build'.
  [Word8] ->
  TextBuilder
#if MIN_VERSION_text(2,0,0)
unsafeSeptets :: Int -> [Word8] -> TextBuilder
unsafeSeptets Int
maxSize [Word8]
bytes =
  Int -> (forall s. MArray s -> Int -> ST s Int) -> TextBuilder
TextBuilder
    Int
maxSize
    ( \MArray s
array ->
        (Word8 -> (Int -> ST s Int) -> Int -> ST s Int)
-> (Int -> ST s Int) -> [Word8] -> Int -> ST s Int
forall a b. (a -> b -> b) -> b -> [a] -> b
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr
          ( \Word8
byte Int -> ST s Int
next Int
offset -> do
              MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array Int
offset Word8
byte
              Int -> ST s Int
next (Int -> Int
forall a. Enum a => a -> a
succ Int
offset)
          )
          Int -> ST s Int
forall a. a -> ST s a
forall (m :: * -> *) a. Monad m => a -> m a
return
          [Word8]
bytes
    )
#else
unsafeSeptets maxSize bytes =
  TextBuilder
    maxSize
    ( \array ->
        foldr
          ( \byte next offset -> do
              TextArray.unsafeWrite array offset (fromIntegral byte)
              next (succ offset)
          )
          return
          bytes
    )
#endif

-- | Same as 'unsafeSeptets', but writes the bytes in reverse order and requires the size to be precise.
--
-- >>> unsafeReverseSeptets 3 (fmap (+48) [1, 2, 3])
-- "321"
{-# INLINE unsafeReverseSeptets #-}
unsafeReverseSeptets ::
  -- | Precise amount of bytes in the list.
  --
  -- Needs to be precise, because writing happens in reverse order.
  --
  -- __Warning:__ If it is smaller, bad things will happen.
  -- We'll be writing outside of the allocated array.
  Int ->
  -- | List of bytes to write in reverse order.
  --
  -- __Warning:__ It is your responsibility to ensure that the bytes are smaller than 128.
  -- Otherwise the produced text will have a broken encoding.
  --
  -- To ensure of optimization kicking in it is advised to construct the list using 'GHC.List.build'.
  [Word8] ->
  TextBuilder
#if MIN_VERSION_text(2,0,0)
unsafeReverseSeptets :: Int -> [Word8] -> TextBuilder
unsafeReverseSeptets Int
preciseSize [Word8]
bytes =
  Int -> (forall s. MArray s -> Int -> ST s Int) -> TextBuilder
TextBuilder
    Int
preciseSize
    ( \MArray s
array Int
startOffset ->
        let endOffset :: Int
endOffset = Int
startOffset Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
preciseSize
         in (Word8 -> (Int -> ST s Int) -> Int -> ST s Int)
-> (Int -> ST s Int) -> [Word8] -> Int -> ST s Int
forall a b. (a -> b -> b) -> b -> [a] -> b
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr
              ( \Word8
byte Int -> ST s Int
next Int
offset -> do
                  MArray s -> Int -> Word8 -> ST s ()
forall s. MArray s -> Int -> Word8 -> ST s ()
TextArray.unsafeWrite MArray s
array Int
offset Word8
byte
                  Int -> ST s Int
next (Int -> Int
forall a. Enum a => a -> a
pred Int
offset)
              )
              (\Int
_ -> Int -> ST s Int
forall a. a -> ST s a
forall (m :: * -> *) a. Monad m => a -> m a
return Int
endOffset)
              [Word8]
bytes
              (Int -> Int
forall a. Enum a => a -> a
pred Int
endOffset)
    )
#else
unsafeReverseSeptets preciseSize bytes =
  TextBuilder
    preciseSize
    ( \array startOffset ->
        let endOffset = startOffset + preciseSize
         in foldr
              ( \byte next offset -> do
                  TextArray.unsafeWrite array offset (fromIntegral byte)
                  next (pred offset)
              )
              (\_ -> return endOffset)
              bytes
              (pred endOffset)
    )
#endif