-- |
-- Copyright:   (c) 2022 Andrew Lelechenko
-- Licence:     BSD3
-- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--
-- Builder for strict 'Text' and 'ByteString', based on linear types. It consistently
-- outperforms "Data.Text.Lazy.Builder"
-- from @text@ as well as a strict builder from @text-builder@,
-- and scales better.
module Data.Text.Builder.Linear (
  Builder (..),
  runBuilder,
  runBuilderBS,
  fromText,
  fromChar,
  fromAddr,
  fromDec,
  fromHex,
  fromDouble,
) where

import Data.Bits (FiniteBits)
import Data.ByteString.Internal (ByteString (..))
import Data.Text.Internal (Text (..))
import GHC.Exts (Addr#, IsString (..))

import Data.Text.Builder.Linear.Buffer

-- | Thin wrapper over 'Buffer' with a handy 'Semigroup' instance.
--
-- >>> :set -XOverloadedStrings -XMagicHash
-- >>> fromText "foo" <> fromChar '_' <> fromAddr "bar"#
-- "foo_bar"
--
-- Remember: this is a strict builder, so on contrary to "Data.Text.Lazy.Builder"
-- for optimal performance you should use strict left folds instead of lazy right ones.
--
-- Note that (similar to other builders) concatenation of 'Builder's allocates
-- thunks. This is to a certain extent mitigated by aggressive inlining,
-- but it is faster to use 'Buffer' directly.
newtype Builder = Builder {Builder -> Buffer %1 -> Buffer
unBuilder  Buffer  Buffer}

-- | Run 'Builder' computation on an empty 'Buffer', returning strict 'Text'.
--
-- >>> :set -XOverloadedStrings -XMagicHash
-- >>> runBuilder (fromText "foo" <> fromChar '_' <> fromAddr "bar"#)
-- "foo_bar"
--
-- This function has a polymorphic arrow and thus can be used both in
-- usual and linear contexts.
runBuilder   m. Builder %m  Text
runBuilder :: forall (m :: Multiplicity). Builder %m -> Text
runBuilder (Builder Buffer %1 -> Buffer
f) = (Buffer %1 -> Buffer) %1 -> Text
runBuffer Buffer %1 -> Buffer
f
{-# INLINE runBuilder #-}

-- | Same as 'runBuilder', but returning a UTF-8 encoded strict 'ByteString'.
runBuilderBS   m. Builder %m  ByteString
runBuilderBS :: forall (m :: Multiplicity). Builder %m -> ByteString
runBuilderBS (Builder Buffer %1 -> Buffer
f) = (Buffer %1 -> Buffer) %1 -> ByteString
runBufferBS Buffer %1 -> Buffer
f
{-# INLINE runBuilderBS #-}

instance Show Builder where
  show :: Builder -> String
show (Builder Buffer %1 -> Buffer
f) = forall a. Show a => a -> String
show ((Buffer %1 -> Buffer) %1 -> Text
runBuffer Buffer %1 -> Buffer
f)

instance Semigroup Builder where
  Builder Buffer %1 -> Buffer
f <> :: Builder -> Builder -> Builder
<> Builder Buffer %1 -> Buffer
g = (Buffer %1 -> Buffer) -> Builder
Builder forall a b. (a -> b) -> a -> b
$ \Buffer
b  Buffer %1 -> Buffer
g (Buffer %1 -> Buffer
f Buffer
b)
  {-# INLINE (<>) #-}

instance Monoid Builder where
  mempty :: Builder
mempty = (Buffer %1 -> Buffer) -> Builder
Builder (\Buffer
b  Buffer
b)
  {-# INLINE mempty #-}

instance IsString Builder where
  fromString :: String -> Builder
fromString = Text -> Builder
fromText forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. IsString a => String -> a
fromString
  {-# INLINE fromString #-}

-- | Create 'Builder', containing a given 'Text'.
--
-- >>> :set -XOverloadedStrings
-- >>> fromText "foo" <> fromText "bar"
-- "foobar"
fromText  Text  Builder
fromText :: Text -> Builder
fromText Text
x = (Buffer %1 -> Buffer) -> Builder
Builder forall a b. (a -> b) -> a -> b
$ \Buffer
b  Buffer
b Buffer %1 -> Text -> Buffer
|> Text
x
{-# INLINE fromText #-}

-- | Create 'Builder', containing a given 'Char'.
--
-- >>> fromChar 'x' <> fromChar 'y'
-- "xy"
--
-- In contrast to 'Data.Text.Lazy.Builder.singleton', it's a responsibility
-- of the caller to sanitize surrogate code points with 'Data.Text.Internal.safe'.
fromChar  Char  Builder
fromChar :: Char -> Builder
fromChar Char
x = (Buffer %1 -> Buffer) -> Builder
Builder forall a b. (a -> b) -> a -> b
$ \Buffer
b  Buffer
b Buffer %1 -> Char -> Buffer
|>. Char
x
{-# INLINE fromChar #-}

-- | Create 'Builder', containing a null-terminated UTF-8 string, specified by 'Addr#'.
--
-- >>> :set -XMagicHash
-- >>> fromAddr "foo"# <> fromAddr "bar"#
-- "foobar"
--
-- The literal string must not contain zero bytes @\\0@ and must be a valid UTF-8,
-- these conditions are not checked.
fromAddr  Addr#  Builder
fromAddr :: Addr# -> Builder
fromAddr Addr#
x = (Buffer %1 -> Buffer) -> Builder
Builder forall a b. (a -> b) -> a -> b
$ \Buffer
b  Buffer
b Buffer %1 -> Addr# -> Buffer
|># Addr#
x
{-# INLINE fromAddr #-}

-- | Create 'Builder', containing decimal representation of a given integer.
--
-- >>> fromChar 'x' <> fromDec (123 :: Int)
-- "x123"
fromDec  (Integral a, FiniteBits a)  a  Builder
fromDec :: forall a. (Integral a, FiniteBits a) => a -> Builder
fromDec a
x = (Buffer %1 -> Buffer) -> Builder
Builder forall a b. (a -> b) -> a -> b
$ \Buffer
b  Buffer
b forall a. (Integral a, FiniteBits a) => Buffer %1 -> a -> Buffer
|>$ a
x
{-# INLINE fromDec #-}

-- | Create 'Builder', containing hexadecimal representation of a given integer.
--
-- >>> :set -XMagicHash
-- >>> fromAddr "0x"# <> fromHex (0x123def :: Int)
-- "0x123def"
fromHex  (Integral a, FiniteBits a)  a  Builder
fromHex :: forall a. (Integral a, FiniteBits a) => a -> Builder
fromHex a
x = (Buffer %1 -> Buffer) -> Builder
Builder forall a b. (a -> b) -> a -> b
$ \Buffer
b  Buffer
b forall a. (Integral a, FiniteBits a) => Buffer %1 -> a -> Buffer
|>& a
x
{-# INLINE fromHex #-}

-- | Create 'Builder', containing decimal representation of a given 'Double'.
--
-- >>> :set -XMagicHash
-- >>> fromAddr "pi="# <> fromDouble pi
-- "pi=3.141592653589793"
fromDouble  Double  Builder
fromDouble :: Double -> Builder
fromDouble Double
x = (Buffer %1 -> Buffer) -> Builder
Builder forall a b. (a -> b) -> a -> b
$ \Buffer
b  Buffer
b Buffer %1 -> Double -> Buffer
|>% Double
x
{-# INLINE fromDouble #-}