Skip to content

Commit 80746c5

Browse files
hPutBuilder
1 parent 19725eb commit 80746c5

File tree

3 files changed

+78
-19
lines changed

3 files changed

+78
-19
lines changed

src/Data/Text/Internal/Builder.hs

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ module Data.Text.Internal.Builder
3939
( -- * Public API
4040
-- ** The Builder type
4141
Builder
42+
, getTexts
4243
, toLazyText
4344
, toLazyTextWith
45+
, starter
4446

4547
-- ** Constructing Builders
4648
, singleton
@@ -91,11 +93,19 @@ import GHC.Stack (HasCallStack)
9193
newtype Builder = Builder {
9294
-- Invariant (from Data.Text.Lazy):
9395
-- The lists include no null Texts.
94-
runBuilder :: forall s. (Buffer s -> ST s [S.Text])
96+
runBuilder :: forall s
97+
. (Int -> (Int -> ST s (A.MArray s)) -> Buffer s -> ST s [S.Text])
98+
-> Int -- buffer size
99+
-> (Int -> ST s (A.MArray s)) -- new array
95100
-> Buffer s
96101
-> ST s [S.Text]
97102
}
98103

104+
getTexts :: Int -> (Int -> ST s (A.MArray s)) -> Builder -> ST s [S.Text]
105+
getTexts chunkSize new b =
106+
newBuffer new chunkSize >>= runBuilder (b `append` flush) starter chunkSize new
107+
108+
99109
instance Semigroup Builder where
100110
(<>) = append
101111
{-# INLINE (<>) #-}
@@ -133,7 +143,7 @@ instance Ord Builder where
133143
-- * @'toLazyText' 'empty' = 'L.empty'@
134144
--
135145
empty :: Builder
136-
empty = Builder (\ k buf -> k buf)
146+
empty = Builder (\ k new buf -> k new buf)
137147
{-# INLINE empty #-}
138148

139149
-- | /O(1)./ A @Builder@ taking a single character, satisfying
@@ -157,6 +167,7 @@ singleton c = writeAtMost 4 $ \ marr o -> unsafeWrite marr o (safe c)
157167
--
158168
append :: Builder -> Builder -> Builder
159169
append (Builder f) (Builder g) = Builder (f . g)
170+
--append (Builder f) (Builder g) = Builder $ \ k new buf -> f (g k) new buf
160171
{-# INLINE [0] append #-}
161172

162173
-- TODO: Experiment to find the right threshold.
@@ -193,22 +204,20 @@ fromText t@(Text arr off l)
193204
--
194205
-- @since 1.2.0.0
195206
fromString :: String -> Builder
196-
fromString str = Builder $ \k (Buffer p0 o0 u0 l0) ->
197-
let loop !marr !o !u !l [] = k (Buffer marr o u l)
207+
fromString str = Builder $ \k chunkSize new (Buffer p0 o0 u0 l0) ->
208+
let loop !marr !o !u !l [] = k chunkSize new (Buffer marr o u l)
198209
loop marr o u l s@(c:cs)
199210
| l <= 3 = do
200211
A.shrinkM marr (o + u)
201212
arr <- A.unsafeFreeze marr
202213
let !t = Text arr o u
203-
marr' <- A.new chunkSize
214+
marr' <- new chunkSize
204215
ts <- inlineInterleaveST (loop marr' 0 0 chunkSize s)
205216
return $ t : ts
206217
| otherwise = do
207218
n <- unsafeWrite marr (o+u) (safe c)
208219
loop marr o (u+n) (l-n) cs
209220
in loop p0 o0 u0 l0 str
210-
where
211-
chunkSize = smallChunkSize
212221
{-# INLINEABLE fromString #-}
213222

214223
-- | /O(1)./ A @Builder@ taking a lazy @Text@, satisfying
@@ -243,18 +252,21 @@ toLazyText = toLazyTextWith smallChunkSize
243252
-- buffers will be the default buffer size.
244253
toLazyTextWith :: Int -> Builder -> L.Text
245254
toLazyTextWith chunkSize m = L.fromChunks (runST $
246-
newBuffer chunkSize >>= runBuilder (m `append` flush) (const (return [])))
255+
newBuffer A.new chunkSize >>= runBuilder (m `append` flush) starter smallChunkSize A.new)
256+
257+
starter :: Monad m => a -> b -> c -> m [d]
258+
starter _ _ _ = return []
247259

248260
-- | /O(1)./ Pop the strict @Text@ we have constructed so far, if any,
249261
-- yielding a new chunk in the result lazy @Text@.
250262
flush :: Builder
251-
flush = Builder $ \ k buf@(Buffer p o u l) ->
263+
flush = Builder $ \ k cs new buf@(Buffer p o u l) ->
252264
if u == 0
253-
then k buf
265+
then k cs new buf
254266
else do arr <- A.unsafeFreeze p
255267
let !b = Buffer p (o+u) 0 l
256268
!t = Text arr o u
257-
ts <- inlineInterleaveST (k b)
269+
ts <- inlineInterleaveST (k cs new b)
258270
return $! t : ts
259271
{-# INLINE [1] flush #-}
260272
-- defer inlining so that flush/flush rule may fire.
@@ -263,18 +275,18 @@ flush = Builder $ \ k buf@(Buffer p o u l) ->
263275

264276
-- | Sequence an ST operation on the buffer
265277
withBuffer :: (forall s. Buffer s -> ST s (Buffer s)) -> Builder
266-
withBuffer f = Builder $ \k buf -> f buf >>= k
278+
withBuffer f = Builder $ \k cs new buf -> f buf >>= k cs new
267279
{-# INLINE withBuffer #-}
268280

269281
-- | Get the size of the buffer
270282
withSize :: (Int -> Builder) -> Builder
271-
withSize f = Builder $ \ k buf@(Buffer _ _ _ l) ->
272-
runBuilder (f l) k buf
283+
withSize f = Builder $ \ k cs new buf@(Buffer _ _ _ l) ->
284+
runBuilder (f l) k cs new buf
273285
{-# INLINE withSize #-}
274286

275287
-- | Map the resulting list of texts.
276288
mapBuilder :: ([S.Text] -> [S.Text]) -> Builder
277-
mapBuilder f = Builder (fmap f .)
289+
mapBuilder f = Builder $ \ k cs new b -> f <$> k cs new b
278290

279291
------------------------------------------------------------------------
280292

@@ -283,7 +295,7 @@ ensureFree :: Int -> Builder
283295
ensureFree !n = withSize $ \ l ->
284296
if n <= l
285297
then empty
286-
else flush `append'` withBuffer (const (newBuffer (max n smallChunkSize)))
298+
else flush `append'` Builder (\k chunkSize new _ -> k chunkSize new =<< newBuffer new (max n chunkSize))
287299
{-# INLINE [0] ensureFree #-}
288300

289301
writeAtMost :: Int -> (forall s. A.MArray s -> Int -> ST s Int) -> Builder
@@ -302,9 +314,9 @@ writeBuffer f (Buffer p o u l) = do
302314
return $! Buffer p o (u+n) (l-n)
303315
{-# INLINE writeBuffer #-}
304316

305-
newBuffer :: Int -> ST s (Buffer s)
306-
newBuffer size = do
307-
arr <- A.new size
317+
newBuffer :: (Int -> ST s (A.MArray s)) -> Int -> ST s (Buffer s)
318+
newBuffer new size = do
319+
arr <- new size
308320
return $! Buffer arr 0 0 size
309321
{-# INLINE newBuffer #-}
310322

src/Data/Text/Lazy/Builder/IO.hs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{-# LANGUAGE MagicHash #-}
2+
{-# LANGUAGE NamedFieldPuns #-}
3+
{-# LANGUAGE RecordWildCards #-}
4+
module Data.Text.Lazy.Builder.IO
5+
( hPutBuilder
6+
, hPutBuilderUtf8
7+
) where
8+
9+
import System.IO (hPutBuf)
10+
import Data.Text.Internal (Text(Text))
11+
import GHC.IO.Handle.Types (Handle, Handle__(..), BufferMode(BlockBuffering))
12+
import Data.Text.Internal.Builder (Builder, getTexts)
13+
import Data.Text.Array (newPinned)
14+
import Data.Array.Byte (ByteArray(ByteArray))
15+
import GHC.IO.Handle.Internals (flushWriteBuffer, wantWritableHandle, flushWriteBuffer)
16+
import Data.Foldable (for_)
17+
import GHC.Exts (byteArrayContents#)
18+
import Control.Monad.ST (runST)
19+
import GHC.Ptr (Ptr(Ptr))
20+
import GHC.IO.Encoding (textEncodingName)
21+
22+
hPutBuilder :: Handle -> Builder -> IO ()
23+
hPutBuilder h b = do
24+
(mode, nl, isUtf8) <- wantWritableHandle "hPutStr" h $ \(Handle__ {..}) -> do
25+
let isUtf8 = maybe False (("UTF-8" ==) . textEncodingName) haCodec
26+
return (haBufferMode, haOutputNL, isUtf8)
27+
case mode of
28+
--NoBuffering -> hPutChars h b
29+
--LineBuffering -> writeLines h nl buf b
30+
BlockBuffering _
31+
-- | nl == CRLF -> writeBlocksCRLF h buf b
32+
| isUtf8 -> hPutBuilderUtf8 h b
33+
-- | otherwise -> writeBlocksRaw h buf b
34+
35+
36+
hPutBuilderUtf8 :: Handle -> Builder -> IO ()
37+
hPutBuilderUtf8 h b = do
38+
flushBytes
39+
-- ????? Does the text ByteArray have a chance of being garbage collected before the flush finishes?
40+
for_ textBuffers $ \(Text (ByteArray a#) _ bufR) -> hPutBuf h (Ptr (byteArrayContents# a#)) bufR
41+
where
42+
flushBytes = wantWritableHandle "hPutBuilder" h flushWriteBuffer
43+
textBuffers = runST $ getTexts bufferSize newPinned b
44+
45+
bufferSize :: Int
46+
bufferSize = 1024 -- I don't know what this should be

text.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ library
195195
Data.Text.Lazy
196196
Data.Text.Lazy.Builder
197197
Data.Text.Lazy.Builder.Int
198+
Data.Text.Lazy.Builder.IO
198199
Data.Text.Lazy.Builder.RealFloat
199200
Data.Text.Lazy.Encoding
200201
Data.Text.Lazy.IO

0 commit comments

Comments
 (0)