packages feed

text-builder-linear-0.1.4: src/Data/Text/Builder/Linear.hs

{-# LANGUAGE CPP #-}

-- |
-- Copyright:   (c) 2022 Andrew Lelechenko
-- Licence:     BSD3
-- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--
-- Builder for strict t'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,
  fromUnboundedDec,
  fromHex,
  fromDouble,
) where

import Data.Bits (FiniteBits)
import Data.ByteString.Internal (ByteString (..))
import Data.Text.Internal (Text (..))
import GHC.Exts (Addr#, IsString (..))
#if MIN_VERSION_base(4,17,0)
import GHC.Exts (Multiplicity)
#else
import GHC.Base (Multiplicity)
#endif

import Data.Text.Builder.Linear.Buffer

-- | Thin wrapper over t'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 t'Builder's allocates
-- thunks. This is to a certain extent mitigated by aggressive inlining,
-- but it is faster to use t'Buffer' directly.
newtype Builder = Builder {unBuilder ∷ Buffer ⊸ Buffer}

-- | @since 0.1.4
instance Eq Builder where
  b1 == b2 = runBuilder b1 == runBuilder b2

-- | @since 0.1.4
instance Ord Builder where
  compare b1 b2 = compare (runBuilder b1) (runBuilder b2)
  b1 <= b2 = runBuilder b1 <= runBuilder b2
  b1 < b2 = runBuilder b1 < runBuilder b2
  b1 >= b2 = runBuilder b1 >= runBuilder b2
  b1 > b2 = runBuilder b1 > runBuilder b2

-- | Run t'Builder' computation on an empty t'Buffer', returning strict t'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 ∷ Multiplicity). Builder %m → Text
runBuilder (Builder f) = runBuffer f
{-# INLINE runBuilder #-}

-- | Same as 'runBuilder', but returning a UTF-8 encoded strict 'ByteString'.
runBuilderBS ∷ ∀ (m ∷ Multiplicity). Builder %m → ByteString
runBuilderBS (Builder f) = runBufferBS f
{-# INLINE runBuilderBS #-}

instance Show Builder where
  show (Builder f) = show (runBuffer f)

instance Semigroup Builder where
  Builder f <> Builder g = Builder $ \b → g (f b)
  {-# INLINE (<>) #-}

instance Monoid Builder where
  mempty = Builder (\b → b)
  {-# INLINE mempty #-}

-- | Use 'fromString' to create t'Builder' from 'String'.
instance IsString Builder where
  fromString = fromText . fromString
  {-# INLINE fromString #-}

-- | Create t'Builder', containing a given t'Text'.
--
-- >>> :set -XOverloadedStrings
-- >>> fromText "foo" <> fromText "bar"
-- "foobar"
--
-- For literal strings it is faster to use 'fromAddr' instead of 'fromText'.
fromText ∷ Text → Builder
fromText x = Builder $ \b → b |> x
{-# INLINE fromText #-}

-- | Create t'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 x = Builder $ \b → b |>. x
{-# INLINE fromChar #-}

-- | Create t'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 @\\NUL@ and must be a valid UTF-8,
-- these conditions are not checked.
fromAddr ∷ Addr# → Builder
fromAddr x = Builder $ \b → b |># x
{-# INLINE fromAddr #-}

-- | Create t'Builder', containing decimal representation of a given /bounded/ integer.
--
-- >>> fromChar 'x' <> fromDec (123 :: Int)
-- "x123"
fromDec ∷ (Integral a, FiniteBits a) ⇒ a → Builder
fromDec x = Builder $ \b → b |>$ x
{-# INLINE fromDec #-}

-- | Create t'Builder', containing decimal representation of a given /unbounded/ integer.
--
-- >>> fromChar 'x' <> fromUnboundedDec (1e24 :: Integer)
-- "x1000000000000000000000000"
--
-- @since 0.1.3
fromUnboundedDec ∷ Integral a ⇒ a → Builder
fromUnboundedDec x = Builder $ \b → b |>$$ x
{-# INLINE fromUnboundedDec #-}

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

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