ip-0.9: src/Data/Text/Builder/Variable.hs
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MultiWayIf #-}
{-| This is a builder optimized for concatenating short
variable-length strings whose length has a known upper
bound. In these cases, this can be up to ten times faster
than the builder provided by the @text@ library. However,
data whose textual encoding has no known upper bound cannot
be encoded by the builder provided here. For example, it
is possible to provide decimal builders for types like 'Int8' and
'Word16', whose lengths are respectively bounded by
4 and 5. However, this is not possible for 'Integer', since
its decimal representation could be arbitrarily long.
-}
module Data.Text.Builder.Variable
( Builder
, run
, contramap
, charBmp
, staticCharBmp
, word8
) where
import Data.Monoid
import Data.Word
import Data.Text (Text)
import Text.Printf (printf)
import Control.Monad.ST
import Data.Char (ord)
import Data.Vector (Vector)
import Data.Function (on)
import Data.Maybe (fromMaybe)
import qualified Data.Vector as Vector
import qualified Data.Text as Text
import qualified Data.Text.Array as A
import qualified Data.Text.Builder.Common.Internal as I
import qualified Data.Text.Internal as TI
data Builder a
= Builder
{-# UNPACK #-} !Int -- the maximum length, not a character count
!(forall s. Int -> A.MArray s -> a -> ST s Int)
instance Monoid (Builder a) where
{-# INLINE mempty #-}
mempty = Builder 0 (\i _ _ -> return i)
{-# INLINE mappend #-}
mappend (Builder len1 f) (Builder len2 g) =
Builder (len1 + len2) $ \ix1 marr a -> do
ix2 <- f ix1 marr a
g ix2 marr a
run :: Builder a -> a -> Text
run (Builder maxLen f) = \a ->
let (outArr,len) = A.run2 $ do
marr <- A.new maxLen
finalIx <- f 0 marr a
return (marr,finalIx)
in TI.text outArr 0 len
{-# INLINE run #-}
contramap :: (b -> a) -> Builder a -> Builder b
contramap f (Builder len g) = Builder len $ \i marr b ->
g i marr (f b)
{-# INLINE contramap #-}
-- finish writing this. it's important for completeness
-- char :: Builder a
-- char = Builder 2 $ \
-- | A character in the basic multilingual plane.
charBmp :: Builder Char
charBmp = Builder 1 $ \i marr c -> do
A.unsafeWrite marr i (fromIntegral (ord c))
return (i + 1)
{-# INLINE charBmp #-}
staticCharBmp :: Char -> Builder a
staticCharBmp c = Builder 1 $ \i marr _ -> do
A.unsafeWrite marr i (fromIntegral (ord c))
return (i + 1)
{-# INLINE staticCharBmp #-}
word8 :: Builder Word8
word8 = Builder 3 $ \pos marr w -> if
| w < 10 -> do
A.unsafeWrite marr pos (i2w w)
return (pos + 1)
| w < 100 -> do
let wInt = fromIntegral w
ix = wInt + wInt
A.unsafeWrite marr pos (A.unsafeIndex I.twoDecimalDigits ix)
A.unsafeWrite marr (pos + 1) (A.unsafeIndex I.twoDecimalDigits (ix + 1))
return (pos + 2)
| otherwise -> do
let wInt = fromIntegral w
ix = wInt + wInt + wInt
A.unsafeWrite marr pos (A.unsafeIndex I.threeDecimalDigits ix)
A.unsafeWrite marr (pos + 1) (A.unsafeIndex I.threeDecimalDigits (ix + 1))
A.unsafeWrite marr (pos + 2) (A.unsafeIndex I.threeDecimalDigits (ix + 2))
return (pos + 3)
{-# INLINE word8 #-}
-- This has not yet been tested.
vector ::
Text -- ^ Default, used when index is out of range
-> Vector Text -- ^ Texts to index into
-> Builder Int
vector tDef v =
let xs = Vector.map I.portableUntext v
xDef = I.portableUntext tDef
in Builder
(Vector.maximum $ Vector.map I.portableTextLength $ Vector.cons tDef v)
$ \pos marr i -> do
let (arr,len) = fromMaybe xDef (xs Vector.!? i)
finalIx = i + len
A.copyI marr i arr 0 finalIx
return finalIx
{-# INLINE vector #-}
i2w :: Integral a => a -> Word16
i2w v = asciiZero + fromIntegral v
{-# INLINE i2w #-}
asciiZero :: Word16
asciiZero = 48
{-# INLINE asciiZero #-}