on master help sign in
Merge
commit8d0f2353791f122623f4a3306f86a51a5eaa0a62 authorBryan O'Sullivan <bos@serpentine.com> dateTue 19 Mar 2013, 22:30 (14 years ago) parent7dac6e9481cec9
27 files changed+674−355
.hgtags+1 Data/Text.hs+7−4 Data/Text/Array.hs+5−1 Data/Text/Encoding.hs+25−6 Data/Text/Encoding/Error.hs+3 Data/Text/Encoding/Fusion.hs+2−2 Data/Text/Fusion/Common.hs+6−2 Data/Text/IO.hs+30−9 Data/Text/Internal.hs+25−4 Data/Text/Lazy.hs+3 Data/Text/Lazy/Builder.hs+6−269 Data/Text/Lazy/Builder/Int.hs+75−17 Data/Text/Lazy/Builder/Int/Digits.hs+22 Data/Text/Lazy/Builder/Internal.hs+321 Data/Text/Lazy/Encoding.hs+12−4 Data/Text/Lazy/Encoding/Fusion.hs+2−2 Data/Text/Lazy/Fusion.hs+26−24 Data/Text/Lazy/IO.hs+3 Data/Text/Lazy/Read.hs+4−1 Data/Text/Read.hs+4−1 Data/Text/Search.hs+1−1 Data/Text/Unsafe.hs+6 cbits/cbits.c+38 tests/Tests/Properties.hs+13 tests/Tests/Regressions.hs+12−1 tests/text-tests.cabal+2 text.cabal+20−7
.hgtags+1
@@ -39,3 +39,4 @@ b75d3041d275e8e76b26605b0d426a572ddf1249 0.11.1.3
39396a3d847a56a69d0514a79cb212cb218271ad0917 0.11.1.13
40401d2c6fa9092c6a4000b2abdd9d01f3efcd477be5 0.11.2.0
414178219784cf3652cc662805bf2971bd62d80210a9 0.11.2.1
42+4297307ebc11ad677cfba6b40319e7e5e2c0cfee 0.11.2.3
Data/Text.hs+7−4
@@ -1,5 +1,8 @@
11{-# LANGUAGE BangPatterns, CPP, MagicHash, Rank2Types, UnboxedTuples #-}
22{-# OPTIONS_GHC -fno-warn-orphans #-}
3+#if __GLASGOW_HASKELL__ >= 702
4+{-# LANGUAGE Trustworthy #-}
5+#endif
36
47-- |
58-- Module : Data.Text
@@ -370,7 +373,7 @@ compareText ta@(Text _arrA _offA lenA) tb@(Text _arrB _offB lenB)
370373-- | /O(n)/ Convert a 'String' into a 'Text'. Subject to
371374-- fusion. Performs replacement on invalid scalar values.
372375pack :: String -> Text
373−pack = unstream . S.streamList . L.map safe
376+pack = unstream . S.map safe . S.streamList
374377{-# INLINE [1] pack #-}
375378
376379-- | /O(n)/ Convert a Text into a String. Subject to fusion.
@@ -378,17 +381,17 @@ unpack :: Text -> String
378381unpack = S.unstreamList . stream
379382{-# INLINE [1] unpack #-}
380383
381−-- | /O(n)/ Convert a literal string into a Text.
384+-- | /O(n)/ Convert a literal string into a Text. Subject to fusion.
382385unpackCString# :: Addr# -> Text
383386unpackCString# addr# = unstream (S.streamCString# addr#)
384387{-# NOINLINE unpackCString# #-}
385388
386389{-# RULES "TEXT literal" forall a.
387− unstream (S.streamList (L.map safe (GHC.unpackCString# a)))
390+ unstream (S.map safe (S.streamList (GHC.unpackCString# a)))
388391 = unpackCString# a #-}
389392
390393{-# RULES "TEXT literal UTF8" forall a.
391− unstream (S.streamList (L.map safe (GHC.unpackCStringUtf8# a)))
394+ unstream (S.map safe (S.streamList (GHC.unpackCStringUtf8# a)))
392395 = unpackCString# a #-}
393396
394397-- | /O(1)/ Convert a character into a Text. Subject to fusion.
Data/Text/Array.hs+5−1
@@ -114,7 +114,7 @@ instance IArray (MArray s) where
114114-- | Create an uninitialized mutable array.
115115new :: forall s. Int -> ST s (MArray s)
116116new n
117− | n < 0 || n .&. highBit /= 0 = error $ "Data.Text.Array.new: size overflow"
117+ | n < 0 || n .&. highBit /= 0 = array_size_error
118118 | otherwise = ST $ \s1# ->
119119 case newByteArray# len# s1# of
120120 (# s2#, marr# #) -> (# s2#, MArray marr#
@@ -126,6 +126,9 @@ new n
126126 highBit = maxBound `xor` (maxBound `shiftR` 1)
127127{-# INLINE new #-}
128128
129+array_size_error :: a
130+array_size_error = error "Data.Text.Array.new: size overflow"
131+
129132-- | Freeze a mutable array. Do not mutate the 'MArray' afterwards!
130133unsafeFreeze :: MArray s -> ST s Array
131134unsafeFreeze MArray{..} = ST $ \s# ->
@@ -181,6 +184,7 @@ run2 k = runST (do
181184 (marr,b) <- k
182185 arr <- unsafeFreeze marr
183186 return (arr,b))
187+{-# INLINE run2 #-}
184188
185189-- | Copy some elements of a mutable array.
186190copyM :: MArray s -- ^ Destination
Data/Text/Encoding.hs+25−6
@@ -1,5 +1,8 @@
11{-# LANGUAGE BangPatterns, CPP, ForeignFunctionInterface, MagicHash,
22 UnliftedFFITypes #-}
3+#if __GLASGOW_HASKELL__ >= 702
4+{-# LANGUAGE Trustworthy #-}
5+#endif
36-- |
47-- Module : Data.Text.Encoding
58-- Copyright : (c) 2009, 2010, 2011 Bryan O'Sullivan,
@@ -23,6 +26,7 @@ module Data.Text.Encoding
2326 -- * Decoding ByteStrings to Text
2427 -- $strict
2528 decodeASCII
29+ , decodeLatin1
2630 , decodeUtf8
2731 , decodeUtf16LE
2832 , decodeUtf16BE
@@ -57,7 +61,7 @@ import Data.Bits ((.&.))
5761import Data.ByteString as B
5862import Data.ByteString.Internal as B
5963import Data.Text.Encoding.Error (OnDecodeError, UnicodeException, strictDecode)
60−import Data.Text.Internal (Text(..))
64+import Data.Text.Internal (Text(..), safe, textP)
6165import Data.Text.Private (runText)
6266import Data.Text.UnsafeChar (ord, unsafeWrite)
6367import Data.Text.UnsafeShift (shiftL, shiftR)
@@ -68,11 +72,11 @@ import Foreign.Marshal.Utils (with)
6872import Foreign.Ptr (Ptr, minusPtr, plusPtr)
6973import Foreign.Storable (peek, poke)
7074import GHC.Base (MutableByteArray#)
71−import System.IO.Unsafe (unsafePerformIO)
7275import qualified Data.Text.Array as A
7376import qualified Data.Text.Encoding.Fusion as E
7477import qualified Data.Text.Encoding.Utf16 as U16
7578import qualified Data.Text.Fusion as F
79+import Data.Text.Unsafe (unsafeDupablePerformIO)
7680
7781-- $strict
7882--
@@ -90,11 +94,23 @@ import qualified Data.Text.Fusion as F
9094-- | /Deprecated/. Decode a 'ByteString' containing 7-bit ASCII
9195-- encoded text.
9296--
93−-- This function is deprecated. Use 'decodeUtf8' instead.
97+-- This function is deprecated. Use 'decodeLatin1' instead.
9498decodeASCII :: ByteString -> Text
9599decodeASCII = decodeUtf8
96100{-# DEPRECATED decodeASCII "Use decodeUtf8 instead" #-}
97101
102+-- | Decode a 'ByteString' containing Latin-1 (aka ISO-8859-1) encoded text.
103+--
104+-- 'decodeLatin1' is semantically equivalent to
105+-- @Data.Text.pack . Data.ByteString.Char8.unpack@
106+decodeLatin1 :: ByteString -> Text
107+decodeLatin1 (PS fp off len) = textP a 0 len
108+ where
109+ a = A.run (A.new len >>= unsafeIOToST . go)
110+ go dest = withForeignPtr fp $ \ptr -> do
111+ c_decode_latin1 (A.maBA dest) (ptr `plusPtr` off) (ptr `plusPtr` (off+len))
112+ return dest
113+
98114-- | Decode a 'ByteString' containing UTF-8 encoded text.
99115decodeUtf8With :: OnDecodeError -> ByteString -> Text
100116decodeUtf8With onErr (PS fp off len) = runText $ \done -> do
@@ -114,7 +130,7 @@ decodeUtf8With onErr (PS fp off len) = runText $ \done -> do
114130 Just c -> do
115131 destOff <- peek destOffPtr
116132 w <- unsafeSTToIO $
117− unsafeWrite dest (fromIntegral destOff) c
133+ unsafeWrite dest (fromIntegral destOff) (safe c)
118134 poke destOffPtr (destOff + fromIntegral w)
119135 loop $ curPtr' `plusPtr` 1
120136 loop (ptr `plusPtr` off)
@@ -141,12 +157,12 @@ decodeUtf8 = decodeUtf8With strictDecode
141157-- If the input contains any invalid UTF-8 data, the relevant
142158-- exception will be returned, otherwise the decoded text.
143159decodeUtf8' :: ByteString -> Either UnicodeException Text
144−decodeUtf8' = unsafePerformIO . try . evaluate . decodeUtf8With strictDecode
160+decodeUtf8' = unsafeDupablePerformIO . try . evaluate . decodeUtf8With strictDecode
145161{-# INLINE decodeUtf8' #-}
146162
147163-- | Encode text using UTF-8 encoding.
148164encodeUtf8 :: Text -> ByteString
149−encodeUtf8 (Text arr off len) = unsafePerformIO $ do
165+encodeUtf8 (Text arr off len) = unsafeDupablePerformIO $ do
150166 let size0 = max len 4
151167 mallocByteString size0 >>= start size0 off 0
152168 where
@@ -279,3 +295,6 @@ encodeUtf32BE txt = E.unstream (E.restreamUtf32BE (F.stream txt))
279295foreign import ccall unsafe "_hs_text_decode_utf8" c_decode_utf8
280296 :: MutableByteArray# s -> Ptr CSize
281297 -> Ptr Word8 -> Ptr Word8 -> IO (Ptr Word8)
298+
299+foreign import ccall unsafe "_hs_text_decode_latin1" c_decode_latin1
300+ :: MutableByteArray# s -> Ptr Word8 -> Ptr Word8 -> IO ()
Data/Text/Encoding/Error.hs+3
@@ -1,4 +1,7 @@
11{-# LANGUAGE CPP, DeriveDataTypeable #-}
2+#if __GLASGOW_HASKELL__ >= 702
3+{-# LANGUAGE Trustworthy #-}
4+#endif
25-- |
36-- Module : Data.Text.Encoding.Error
47-- Copyright : (c) Bryan O'Sullivan 2009
Data/Text/Encoding/Fusion.hs+2−2
@@ -44,12 +44,12 @@ import Data.Text.UnsafeShift (shiftL, shiftR)
4444import Data.Word (Word8, Word16, Word32)
4545import Foreign.ForeignPtr (withForeignPtr, ForeignPtr)
4646import Foreign.Storable (pokeByteOff)
47−import System.IO.Unsafe (unsafePerformIO)
4847import qualified Data.ByteString as B
4948import qualified Data.ByteString.Unsafe as B
5049import qualified Data.Text.Encoding.Utf8 as U8
5150import qualified Data.Text.Encoding.Utf16 as U16
5251import qualified Data.Text.Encoding.Utf32 as U32
52+import Data.Text.Unsafe (unsafeDupablePerformIO)
5353
5454streamASCII :: ByteString -> Stream Char
5555streamASCII bs = Stream next 0 (maxSize l)
@@ -163,7 +163,7 @@ streamUtf32LE onErr bs = Stream next 0 (maxSize (l `shiftR` 2))
163163
164164-- | /O(n)/ Convert a 'Stream' 'Word8' to a 'ByteString'.
165165unstream :: Stream Word8 -> ByteString
166−unstream (Stream next s0 len) = unsafePerformIO $ do
166+unstream (Stream next s0 len) = unsafeDupablePerformIO $ do
167167 let mlen = upperBound 4 len
168168 mallocByteString mlen >>= loop mlen 0 s0
169169 where
Data/Text/Fusion/Common.hs+6−2
@@ -172,7 +172,7 @@ data C s = C0 !s
172172
173173-- | /O(n)/ Adds a character to the front of a Stream Char.
174174cons :: Char -> Stream Char -> Stream Char
175−cons w (Stream next0 s0 len) = Stream next (C1 s0) (len+1)
175+cons !w (Stream next0 s0 len) = Stream next (C1 s0) (len+1)
176176 where
177177 next (C1 s) = Yield w (C0 s)
178178 next (C0 s) = case next0 s of
@@ -218,9 +218,13 @@ head (Stream next s0 _len) = loop_head s0
218218 loop_head !s = case next s of
219219 Yield x _ -> x
220220 Skip s' -> loop_head s'
221− Done -> streamError "head" "Empty stream"
221+ Done -> head_empty
222222{-# INLINE [0] head #-}
223223
224+head_empty :: a
225+head_empty = streamError "head" "Empty stream"
226+{-# NOINLINE head_empty #-}
227+
224228-- | /O(1)/ Returns the first character and remainder of a 'Stream
225229-- Char', or 'Nothing' if empty. Subject to array fusion.
226230uncons :: Stream Char -> Maybe (Char, Stream Char)
Data/Text/IO.hs+30−9
@@ -1,4 +1,7 @@
11{-# LANGUAGE BangPatterns, CPP, RecordWildCards, ScopedTypeVariables #-}
2+#if __GLASGOW_HASKELL__ >= 702
3+{-# LANGUAGE Trustworthy #-}
4+#endif
25-- |
36-- Module : Data.Text.IO
47-- Copyright : (c) 2009, 2010 Bryan O'Sullivan,
@@ -26,6 +29,7 @@ module Data.Text.IO
2629 , appendFile
2730 -- * Operations on handles
2831 , hGetContents
32+ , hGetChunk
2933 , hGetLine
3034 , hPutStr
3135 , hPutStrLn
@@ -97,6 +101,30 @@ writeFile p = withFile p WriteMode . flip hPutStr
97101appendFile :: FilePath -> Text -> IO ()
98102appendFile p = withFile p AppendMode . flip hPutStr
99103
104+catchError :: String -> Handle -> Handle__ -> IOError -> IO Text
105+catchError caller h Handle__{..} err
106+ | isEOFError err = do
107+ buf <- readIORef haCharBuffer
108+ return $ if isEmptyBuffer buf
109+ then T.empty
110+ else T.singleton '\r'
111+ | otherwise = E.throwIO (augmentIOError err caller h)
112+
113+-- | /Experimental./ Read a single chunk of strict text from a
114+-- 'Handle'. The size of the chunk depends on the amount of input
115+-- currently buffered.
116+--
117+-- This function blocks only if there is no data available, and EOF
118+-- has not yet been reached. Once EOF is reached, this function
119+-- returns an empty string instead of throwing an exception.
120+hGetChunk :: Handle -> IO Text
121+hGetChunk h = wantReadableHandle "hGetChunk" h readSingleChunk
122+ where
123+ readSingleChunk hh@Handle__{..} = do
124+ buf <- readIORef haCharBuffer
125+ t <- readChunk hh buf `E.catch` catchError "hGetChunk" h hh
126+ return (hh, t)
127+
100128-- | Read the remaining contents of a 'Handle' as a string. The
101129-- 'Handle' is closed once the contents have been read, or if an
102130-- exception is thrown.
@@ -117,16 +145,9 @@ hGetContents h = do
117145 wantReadableHandle "hGetContents" h readAll
118146 where
119147 readAll hh@Handle__{..} = do
120− let catchError e
121− | isEOFError e = do
122− buf <- readIORef haCharBuffer
123− return $ if isEmptyBuffer buf
124− then T.empty
125− else T.singleton '\r'
126− | otherwise = E.throwIO (augmentIOError e "hGetContents" h)
127− readChunks = do
148+ let readChunks = do
128149 buf <- readIORef haCharBuffer
129− t <- readChunk hh buf `E.catch` catchError
150+ t <- readChunk hh buf `E.catch` catchError "hGetContents" h hh
130151 if T.null t
131152 then return [t]
132153 else (t:) `fmap` readChunks
Data/Text/Internal.hs+25−4
@@ -23,6 +23,7 @@
2323module Data.Text.Internal
2424 (
2525 -- * Types
26+ -- $internals
2627 Text(..)
2728 -- * Construction
2829 , text
@@ -47,9 +48,9 @@ import Data.Typeable (Typeable)
4748
4849-- | A space efficient, packed, unboxed Unicode text type.
4950data Text = Text
50− {-# UNPACK #-} !A.Array -- payload
51− {-# UNPACK #-} !Int -- offset
52− {-# UNPACK #-} !Int -- length
51+ {-# UNPACK #-} !A.Array -- payload (Word16 elements)
52+ {-# UNPACK #-} !Int -- offset (units of Word16, not Char)
53+ {-# UNPACK #-} !Int -- length (units of Word16, not Char)
5354 deriving (Typeable)
5455
5556-- | Smart constructor.
@@ -90,7 +91,8 @@ showText (Text arr off len) =
9091-- scalar values, but are unfortunately admitted as valid 'Char'
9192-- values by Haskell. They cannot be represented in a 'Text'. This
9293-- function remaps those code points to the Unicode replacement
93−-- character \"&#xfffd;\", and leaves other code points unchanged.
94+-- character (U+FFFD, \'&#xfffd;\'), and leaves other code points
95+-- unchanged.
9496safe :: Char -> Char
9597safe c
9698 | ord c .&. 0x1ff800 /= 0xd800 = c
@@ -101,3 +103,22 @@ safe c
101103firstf :: (a -> c) -> Maybe (a,b) -> Maybe (c,b)
102104firstf f (Just (a, b)) = Just (f a, b)
103105firstf _ Nothing = Nothing
106+
107+-- $internals
108+--
109+-- Internally, the 'Text' type is represented as an array of 'Word16'
110+-- UTF-16 code units. The offset and length fields in the constructor
111+-- are in these units, /not/ units of 'Char'.
112+--
113+-- Invariants that all functions must maintain:
114+--
115+-- * Since the 'Text' type uses UTF-16 internally, it cannot represent
116+-- characters in the reserved surrogate code point range U+D800 to
117+-- U+DFFF. To maintain this invariant, the 'safe' function maps
118+-- 'Char' values in this range to the replacement character (U+FFFD,
119+-- \'&#xfffd;\').
120+--
121+-- * A leading (or \"high\") surrogate code unit (0xD800–0xDBFF) must
122+-- always be followed by a trailing (or \"low\") surrogate code unit
123+-- (0xDC00-0xDFFF). A trailing surrogate code unit must always be
124+-- preceded by a leading surrogate code unit.
Data/Text/Lazy.hs+3
@@ -1,5 +1,8 @@
11{-# OPTIONS_GHC -fno-warn-orphans #-}
22{-# LANGUAGE BangPatterns, MagicHash, CPP #-}
3+#if __GLASGOW_HASKELL__ >= 702
4+{-# LANGUAGE Trustworthy #-}
5+#endif
36-- |
47-- Module : Data.Text.Lazy
58-- Copyright : (c) 2009, 2010, 2012 Bryan O'Sullivan
Data/Text/Lazy/Builder.hs+6−269
@@ -3,7 +3,8 @@
33-----------------------------------------------------------------------------
44-- |
55-- Module : Data.Text.Lazy.Builder
6−-- Copyright : (c) 2010 Johan Tibell
6+-- Copyright : (c) 2013 Bryan O'Sullivan
7+-- (c) 2010 Johan Tibell
78-- License : BSD3-style (see LICENSE)
89--
910-- Maintainer : Johan Tibell <johan.tibell@gmail.com>
@@ -15,7 +16,9 @@
1516-- @fromLazyText@, which construct new builders, and 'mappend', which
1617-- concatenates two builders.
1718--
18−-- To get maximum performance when building lazy @Text@ values using a builder, associate @mappend@ calls to the right. For example, prefer
19+-- To get maximum performance when building lazy @Text@ values using a
20+-- builder, associate @mappend@ calls to the right. For example,
21+-- prefer
1922--
2023-- > singleton 'a' `mappend` (singleton 'b' `mappend` singleton 'c')
2124--
@@ -43,270 +46,4 @@ module Data.Text.Lazy.Builder
4346 , flush
4447 ) where
4548
46−import Control.Monad.ST (ST, runST)
47−import Data.Bits ((.&.))
48−import Data.Monoid (Monoid(..))
49−import Data.Text.Internal (Text(..))
50−import Data.Text.Lazy.Internal (smallChunkSize)
51−import Data.Text.Unsafe (inlineInterleaveST)
52−import Data.Text.UnsafeChar (ord, unsafeWrite)
53−import Data.Text.UnsafeShift (shiftR)
54−import Prelude hiding (map, putChar)
55−
56−import qualified Data.String as String
57−import qualified Data.Text as S
58−import qualified Data.Text.Array as A
59−import qualified Data.Text.Lazy as L
60−
61−------------------------------------------------------------------------
62−
63−-- | A @Builder@ is an efficient way to build lazy @Text@ values.
64−-- There are several functions for constructing builders, but only one
65−-- to inspect them: to extract any data, you have to turn them into
66−-- lazy @Text@ values using @toLazyText@.
67−--
68−-- Internally, a builder constructs a lazy @Text@ by filling arrays
69−-- piece by piece. As each buffer is filled, it is \'popped\' off, to
70−-- become a new chunk of the resulting lazy @Text@. All this is
71−-- hidden from the user of the @Builder@.
72−newtype Builder = Builder {
73− -- Invariant (from Data.Text.Lazy):
74− -- The lists include no null Texts.
75− runBuilder :: forall s. (Buffer s -> ST s [S.Text])
76− -> Buffer s
77− -> ST s [S.Text]
78− }
79−
80−instance Monoid Builder where
81− mempty = empty
82− {-# INLINE mempty #-}
83− mappend = append
84− {-# INLINE mappend #-}
85− mconcat = foldr mappend mempty
86− {-# INLINE mconcat #-}
87−
88−instance String.IsString Builder where
89− fromString = fromString
90− {-# INLINE fromString #-}
91−
92−instance Show Builder where
93− show = show . toLazyText
94−
95−instance Eq Builder where
96− a == b = toLazyText a == toLazyText b
97−
98−instance Ord Builder where
99− a <= b = toLazyText a <= toLazyText b
100−
101−------------------------------------------------------------------------
102−
103−-- | /O(1)./ The empty @Builder@, satisfying
104−--
105−-- * @'toLazyText' 'empty' = 'L.empty'@
106−--
107−empty :: Builder
108−empty = Builder (\ k buf -> k buf)
109−{-# INLINE empty #-}
110−
111−-- | /O(1)./ A @Builder@ taking a single character, satisfying
112−--
113−-- * @'toLazyText' ('singleton' c) = 'L.singleton' c@
114−--
115−singleton :: Char -> Builder
116−singleton c = writeAtMost 2 $ \ marr o ->
117− if n < 0x10000
118− then A.unsafeWrite marr o (fromIntegral n) >> return 1
119− else do
120− A.unsafeWrite marr o lo
121− A.unsafeWrite marr (o+1) hi
122− return 2
123− where n = ord c
124− m = n - 0x10000
125− lo = fromIntegral $ (m `shiftR` 10) + 0xD800
126− hi = fromIntegral $ (m .&. 0x3FF) + 0xDC00
127−{-# INLINE singleton #-}
128−
129−------------------------------------------------------------------------
130−
131−-- | /O(1)./ The concatenation of two builders, an associative
132−-- operation with identity 'empty', satisfying
133−--
134−-- * @'toLazyText' ('append' x y) = 'L.append' ('toLazyText' x) ('toLazyText' y)@
135−--
136−append :: Builder -> Builder -> Builder
137−append (Builder f) (Builder g) = Builder (f . g)
138−{-# INLINE [0] append #-}
139−
140−-- TODO: Experiment to find the right threshold.
141−copyLimit :: Int
142−copyLimit = 128
143−
144−-- This function attempts to merge small @Text@ values instead of
145−-- treating each value as its own chunk. We may not always want this.
146−
147−-- | /O(1)./ A @Builder@ taking a 'S.Text', satisfying
148−--
149−-- * @'toLazyText' ('fromText' t) = 'L.fromChunks' [t]@
150−--
151−fromText :: S.Text -> Builder
152−fromText t@(Text arr off l)
153− | S.null t = empty
154− | l <= copyLimit = writeN l $ \marr o -> A.copyI marr o arr off (l+o)
155− | otherwise = flush `append` mapBuilder (t :)
156−{-# INLINE [1] fromText #-}
157−
158−{-# RULES
159−"fromText/pack" forall s .
160− fromText (S.pack s) = fromString s
161− #-}
162−
163−-- | /O(1)./ A Builder taking a @String@, satisfying
164−--
165−-- * @'toLazyText' ('fromString' s) = 'L.fromChunks' [S.pack s]@
166−--
167−fromString :: String -> Builder
168−fromString str = Builder $ \k (Buffer p0 o0 u0 l0) ->
169− let loop !marr !o !u !l [] = k (Buffer marr o u l)
170− loop marr o u l s@(c:cs)
171− | l <= 1 = do
172− arr <- A.unsafeFreeze marr
173− let !t = Text arr o u
174− marr' <- A.new chunkSize
175− ts <- inlineInterleaveST (loop marr' 0 0 chunkSize s)
176− return $ t : ts
177− | otherwise = do
178− n <- unsafeWrite marr (o+u) c
179− loop marr o (u+n) (l-n) cs
180− in loop p0 o0 u0 l0 str
181− where
182− chunkSize = smallChunkSize
183−{-# INLINE fromString #-}
184−
185−-- | /O(1)./ A @Builder@ taking a lazy @Text@, satisfying
186−--
187−-- * @'toLazyText' ('fromLazyText' t) = t@
188−--
189−fromLazyText :: L.Text -> Builder
190−fromLazyText ts = flush `append` mapBuilder (L.toChunks ts ++)
191−{-# INLINE fromLazyText #-}
192−
193−------------------------------------------------------------------------
194−
195−-- Our internal buffer type
196−data Buffer s = Buffer {-# UNPACK #-} !(A.MArray s)
197− {-# UNPACK #-} !Int -- offset
198− {-# UNPACK #-} !Int -- used units
199− {-# UNPACK #-} !Int -- length left
200−
201−------------------------------------------------------------------------
202−
203−-- | /O(n)./ Extract a lazy @Text@ from a @Builder@ with a default
204−-- buffer size. The construction work takes place if and when the
205−-- relevant part of the lazy @Text@ is demanded.
206−toLazyText :: Builder -> L.Text
207−toLazyText = toLazyTextWith smallChunkSize
208−
209−-- | /O(n)./ Extract a lazy @Text@ from a @Builder@, using the given
210−-- size for the initial buffer. The construction work takes place if
211−-- and when the relevant part of the lazy @Text@ is demanded.
212−--
213−-- If the initial buffer is too small to hold all data, subsequent
214−-- buffers will be the default buffer size.
215−toLazyTextWith :: Int -> Builder -> L.Text
216−toLazyTextWith chunkSize m = L.fromChunks (runST $
217− newBuffer chunkSize >>= runBuilder (m `append` flush) (const (return [])))
218−
219−-- | /O(1)./ Pop the strict @Text@ we have constructed so far, if any,
220−-- yielding a new chunk in the result lazy @Text@.
221−flush :: Builder
222−flush = Builder $ \ k buf@(Buffer p o u l) ->
223− if u == 0
224− then k buf
225− else do arr <- A.unsafeFreeze p
226− let !b = Buffer p (o+u) 0 l
227− !t = Text arr o u
228− ts <- inlineInterleaveST (k b)
229− return $! t : ts
230−
231−------------------------------------------------------------------------
232−
233−-- | Sequence an ST operation on the buffer
234−withBuffer :: (forall s. Buffer s -> ST s (Buffer s)) -> Builder
235−withBuffer f = Builder $ \k buf -> f buf >>= k
236−{-# INLINE withBuffer #-}
237−
238−-- | Get the size of the buffer
239−withSize :: (Int -> Builder) -> Builder
240−withSize f = Builder $ \ k buf@(Buffer _ _ _ l) ->
241− runBuilder (f l) k buf
242−{-# INLINE withSize #-}
243−
244−-- | Map the resulting list of texts.
245−mapBuilder :: ([S.Text] -> [S.Text]) -> Builder
246−mapBuilder f = Builder (fmap f .)
247−
248−------------------------------------------------------------------------
249−
250−-- | Ensure that there are at least @n@ many elements available.
251−ensureFree :: Int -> Builder
252−ensureFree !n = withSize $ \ l ->
253− if n <= l
254− then empty
255− else flush `append'` withBuffer (const (newBuffer (max n smallChunkSize)))
256−{-# INLINE [0] ensureFree #-}
257−
258−writeAtMost :: Int -> (forall s. A.MArray s -> Int -> ST s Int) -> Builder
259−writeAtMost n f = ensureFree n `append'` withBuffer (writeBuffer f)
260−{-# INLINE [0] writeAtMost #-}
261−
262−-- | Ensure that @n@ many elements are available, and then use @f@ to
263−-- write some elements into the memory.
264−writeN :: Int -> (forall s. A.MArray s -> Int -> ST s ()) -> Builder
265−writeN n f = writeAtMost n (\ p o -> f p o >> return n)
266−{-# INLINE writeN #-}
267−
268−writeBuffer :: (A.MArray s -> Int -> ST s Int) -> Buffer s -> ST s (Buffer s)
269−writeBuffer f (Buffer p o u l) = do
270− n <- f p (o+u)
271− return $! Buffer p o (u+n) (l-n)
272−{-# INLINE writeBuffer #-}
273−
274−newBuffer :: Int -> ST s (Buffer s)
275−newBuffer size = do
276− arr <- A.new size
277− return $! Buffer arr 0 0 size
278−{-# INLINE newBuffer #-}
279−
280−------------------------------------------------------------------------
281−-- Some nice rules for Builder
282−
283−-- This function makes GHC understand that 'writeN' and 'ensureFree'
284−-- are *not* recursive in the precense of the rewrite rules below.
285−-- This is not needed with GHC 7+.
286−append' :: Builder -> Builder -> Builder
287−append' (Builder f) (Builder g) = Builder (f . g)
288−{-# INLINE append' #-}
289−
290−{-# RULES
291−
292−"append/writeAtMost" forall a b (f::forall s. A.MArray s -> Int -> ST s Int)
293− (g::forall s. A.MArray s -> Int -> ST s Int) ws.
294− append (writeAtMost a f) (append (writeAtMost b g) ws) =
295− append (writeAtMost (a+b) (\marr o -> f marr o >>= \ n ->
296− g marr (o+n) >>= \ m ->
297− let s = n+m in s `seq` return s)) ws
298−
299−"writeAtMost/writeAtMost" forall a b (f::forall s. A.MArray s -> Int -> ST s Int)
300− (g::forall s. A.MArray s -> Int -> ST s Int).
301− append (writeAtMost a f) (writeAtMost b g) =
302− writeAtMost (a+b) (\marr o -> f marr o >>= \ n ->
303− g marr (o+n) >>= \ m ->
304− let s = n+m in s `seq` return s)
305−
306−"ensureFree/ensureFree" forall a b .
307− append (ensureFree a) (ensureFree b) = ensureFree (max a b)
308−
309−"flush/flush"
310− append flush flush = flush
311−
312− #-}
49+import Data.Text.Lazy.Builder.Internal
Data/Text/Lazy/Builder/Int.hs+75−17
@@ -1,7 +1,8 @@
1−{-# LANGUAGE BangPatterns, CPP, MagicHash, UnboxedTuples #-}
1+{-# LANGUAGE BangPatterns, CPP, MagicHash, RankNTypes, UnboxedTuples #-}
22
33-- Module: Data.Text.Lazy.Builder.Int
4−-- Copyright: (c) 2011 MailRank, Inc.
4+-- Copyright: (c) 2013 Bryan O'Sullivan
5+-- (c) 2011 MailRank, Inc.
56-- License: BSD3
67-- Maintainer: Bryan O'Sullivan <bos@serpentine.com>
78-- Stability: experimental
@@ -17,12 +18,16 @@ module Data.Text.Lazy.Builder.Int
1718
1819import Data.Int (Int8, Int16, Int32, Int64)
1920import Data.Monoid (mempty)
21+import qualified Data.ByteString.Unsafe as B
2022import Data.Text.Lazy.Builder.Functions ((<>), i2d)
21−import Data.Text.Lazy.Builder
23+import Data.Text.Lazy.Builder.Internal
24+import Data.Text.Lazy.Builder.Int.Digits (digits)
25+import Data.Text.Array
2226import Data.Word (Word, Word8, Word16, Word32, Word64)
2327import GHC.Base (quotInt, remInt)
2428import GHC.Num (quotRemInteger)
2529import GHC.Types (Int(..))
30+import Control.Monad.ST
2631
2732#ifdef __GLASGOW_HASKELL__
2833# if __GLASGOW_HASKELL__ < 611
@@ -54,12 +59,7 @@ decimal :: Integral a => a -> Builder
5459{-# RULES "decimal/Word32" decimal = positive :: Word32 -> Builder #-}
5560{-# RULES "decimal/Word64" decimal = positive :: Word64 -> Builder #-}
5661{-# RULES "decimal/Integer" decimal = integer 10 :: Integer -> Builder #-}
57−decimal i
58− | i < 0 = singleton '-' <>
59− if i <= -128
60− then positive (-(i `quot` 10)) <> digit (-(i `rem` 10))
61− else positive (-i)
62− | otherwise = positive i
62+decimal i = decimal' (<= -128) i
6363
6464boundedDecimal :: (Integral a, Bounded a) => a -> Builder
6565{-# SPECIALIZE boundedDecimal :: Int -> Builder #-}
@@ -67,11 +67,22 @@ boundedDecimal :: (Integral a, Bounded a) => a -> Builder
6767{-# SPECIALIZE boundedDecimal :: Int16 -> Builder #-}
6868{-# SPECIALIZE boundedDecimal :: Int32 -> Builder #-}
6969{-# SPECIALIZE boundedDecimal :: Int64 -> Builder #-}
70−boundedDecimal i
71− | i < 0 = singleton '-' <>
72− if i == minBound
73− then positive (-(i `quot` 10)) <> digit (-(i `rem` 10))
74− else positive (-i)
70+boundedDecimal i = decimal' (== minBound) i
71+
72+decimal' :: (Integral a) => (a -> Bool) -> a -> Builder
73+{-# INLINE decimal' #-}
74+decimal' p i
75+ | i < 0 = if p i
76+ then let j = -(i `quot` 10)
77+ !n = countDigits j
78+ in writeN (n + 2) $ \marr off -> do
79+ unsafeWrite marr off minus
80+ posDecimal marr (off+1) n j
81+ unsafeWrite marr (off+n+1) (i2w (-(i `rem` 10)))
82+ else let j = -i
83+ !n = countDigits j
84+ in writeN (n + 1) $ \marr off ->
85+ unsafeWrite marr off minus >> posDecimal marr (off+1) n j
7586 | otherwise = positive i
7687
7788positive :: (Integral a) => a -> Builder
@@ -85,9 +96,56 @@ positive :: (Integral a) => a -> Builder
8596{-# SPECIALIZE positive :: Word16 -> Builder #-}
8697{-# SPECIALIZE positive :: Word32 -> Builder #-}
8798{-# SPECIALIZE positive :: Word64 -> Builder #-}
88−positive = go
89− where go n | n < 10 = digit n
90− | otherwise = go (n `quot` 10) <> digit (n `rem` 10)
99+positive i
100+ | i < 10 = writeN 1 $ \marr off -> unsafeWrite marr off (i2w i)
101+ | otherwise = let !n = countDigits i
102+ in writeN n $ \marr off -> posDecimal marr off n i
103+
104+posDecimal :: (Integral a) =>
105+ forall s. MArray s -> Int -> Int -> a -> ST s ()
106+{-# INLINE posDecimal #-}
107+posDecimal marr off0 ds v0 = go (off0 + ds - 1) v0
108+ where go off v
109+ | v >= 100 = do
110+ write2 off $ let u = v `rem` 100
111+ in u + u
112+ go (off - 2) (v `quot` 100)
113+ | v < 10 = unsafeWrite marr off (i2w v)
114+ | otherwise = write2 off (v + v)
115+ write2 off i = do
116+ unsafeWrite marr off $ get (i + 1)
117+ unsafeWrite marr (off - 1) $ get i
118+ get i = fromIntegral $ B.unsafeIndex digits (fromIntegral i) :: Word16
119+
120+minus, zero :: Word16
121+{-# INLINE minus #-}
122+{-# INLINE zero #-}
123+minus = 45
124+zero = 48
125+
126+i2w :: (Integral a) => a -> Word16
127+{-# INLINE i2w #-}
128+i2w v = zero + fromIntegral v
129+
130+countDigits :: (Integral a) => a -> Int
131+{-# INLINE countDigits #-}
132+countDigits v0 = go 1 (fromIntegral v0 :: Word64)
133+ where go !k v
134+ | v < 10 = k
135+ | v < 100 = k + 1
136+ | v < 1000 = k + 2
137+ | v < 1000000000000 =
138+ k + if v < 100000000
139+ then if v < 1000000
140+ then if v < 10000
141+ then 3
142+ else 4 + fin v 100000
143+ else 6 + fin v 10000000
144+ else if v < 10000000000
145+ then 8 + fin v 1000000000
146+ else 10 + fin v 100000000000
147+ | otherwise = go (k + 12) (v `quot` 1000000000000)
148+ fin v n = if v >= n then 1 else 0
91149
92150hexadecimal :: Integral a => a -> Builder
93151{-# SPECIALIZE hexadecimal :: Int -> Builder #-}
Data/Text/Lazy/Builder/Int/Digits.hsnew file+22
@@ -0,0 +1,22 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+
3+-- Module: Data.Text.Lazy.Builder.Int.Digits
4+-- Copyright: (c) 2013 Bryan O'Sullivan
5+-- License: BSD3
6+-- Maintainer: Bryan O'Sullivan <bos@serpentine.com>
7+-- Stability: experimental
8+-- Portability: portable
9+--
10+-- This module exists because the C preprocessor does things that we
11+-- shall not speak of when confronted with Haskell multiline strings.
12+
13+module Data.Text.Lazy.Builder.Int.Digits (digits) where
14+
15+import Data.ByteString.Char8 (ByteString)
16+
17+digits :: ByteString
18+digits = "0001020304050607080910111213141516171819\
19+ \2021222324252627282930313233343536373839\
20+ \4041424344454647484950515253545556575859\
21+ \6061626364656667686970717273747576777879\
22+ \8081828384858687888990919293949596979899"
Data/Text/Lazy/Builder/Internal.hsnew file+321
@@ -0,0 +1,321 @@
1+{-# LANGUAGE BangPatterns, CPP, Rank2Types #-}
2+
3+-----------------------------------------------------------------------------
4+-- |
5+-- Module : Data.Text.Lazy.Builder.Internal
6+-- Copyright : (c) 2013 Bryan O'Sullivan
7+-- (c) 2010 Johan Tibell
8+-- License : BSD3-style (see LICENSE)
9+--
10+-- Maintainer : Johan Tibell <johan.tibell@gmail.com>
11+-- Stability : experimental
12+-- Portability : portable to Hugs and GHC
13+--
14+-- Efficient construction of lazy @Text@ values. The principal
15+-- operations on a @Builder@ are @singleton@, @fromText@, and
16+-- @fromLazyText@, which construct new builders, and 'mappend', which
17+-- concatenates two builders.
18+--
19+-- To get maximum performance when building lazy @Text@ values using a
20+-- builder, associate @mappend@ calls to the right. For example,
21+-- prefer
22+--
23+-- > singleton 'a' `mappend` (singleton 'b' `mappend` singleton 'c')
24+--
25+-- to
26+--
27+-- > singleton 'a' `mappend` singleton 'b' `mappend` singleton 'c'
28+--
29+-- as the latter associates @mappend@ to the left.
30+--
31+-----------------------------------------------------------------------------
32+
33+module Data.Text.Lazy.Builder.Internal
34+ ( -- * Public API
35+ -- ** The Builder type
36+ Builder
37+ , toLazyText
38+ , toLazyTextWith
39+
40+ -- ** Constructing Builders
41+ , singleton
42+ , fromText
43+ , fromLazyText
44+ , fromString
45+
46+ -- ** Flushing the buffer state
47+ , flush
48+
49+ -- * Internal functions
50+ , append'
51+ , ensureFree
52+ , writeN
53+ ) where
54+
55+import Control.Monad.ST (ST, runST)
56+import Data.Bits ((.&.))
57+import Data.Monoid (Monoid(..))
58+import Data.Text.Internal (Text(..))
59+import Data.Text.Lazy.Internal (smallChunkSize)
60+import Data.Text.Unsafe (inlineInterleaveST)
61+import Data.Text.UnsafeChar (ord, unsafeWrite)
62+import Data.Text.UnsafeShift (shiftR)
63+import Prelude hiding (map, putChar)
64+
65+import qualified Data.String as String
66+import qualified Data.Text as S
67+import qualified Data.Text.Array as A
68+import qualified Data.Text.Lazy as L
69+
70+------------------------------------------------------------------------
71+
72+-- | A @Builder@ is an efficient way to build lazy @Text@ values.
73+-- There are several functions for constructing builders, but only one
74+-- to inspect them: to extract any data, you have to turn them into
75+-- lazy @Text@ values using @toLazyText@.
76+--
77+-- Internally, a builder constructs a lazy @Text@ by filling arrays
78+-- piece by piece. As each buffer is filled, it is \'popped\' off, to
79+-- become a new chunk of the resulting lazy @Text@. All this is
80+-- hidden from the user of the @Builder@.
81+newtype Builder = Builder {
82+ -- Invariant (from Data.Text.Lazy):
83+ -- The lists include no null Texts.
84+ runBuilder :: forall s. (Buffer s -> ST s [S.Text])
85+ -> Buffer s
86+ -> ST s [S.Text]
87+ }
88+
89+instance Monoid Builder where
90+ mempty = empty
91+ {-# INLINE mempty #-}
92+ mappend = append
93+ {-# INLINE mappend #-}
94+ mconcat = foldr mappend mempty
95+ {-# INLINE mconcat #-}
96+
97+instance String.IsString Builder where
98+ fromString = fromString
99+ {-# INLINE fromString #-}
100+
101+instance Show Builder where
102+ show = show . toLazyText
103+
104+instance Eq Builder where
105+ a == b = toLazyText a == toLazyText b
106+
107+instance Ord Builder where
108+ a <= b = toLazyText a <= toLazyText b
109+
110+------------------------------------------------------------------------
111+
112+-- | /O(1)./ The empty @Builder@, satisfying
113+--
114+-- * @'toLazyText' 'empty' = 'L.empty'@
115+--
116+empty :: Builder
117+empty = Builder (\ k buf -> k buf)
118+{-# INLINE empty #-}
119+
120+-- | /O(1)./ A @Builder@ taking a single character, satisfying
121+--
122+-- * @'toLazyText' ('singleton' c) = 'L.singleton' c@
123+--
124+singleton :: Char -> Builder
125+singleton c = writeAtMost 2 $ \ marr o ->
126+ if n < 0x10000
127+ then A.unsafeWrite marr o (fromIntegral n) >> return 1
128+ else do
129+ A.unsafeWrite marr o lo
130+ A.unsafeWrite marr (o+1) hi
131+ return 2
132+ where n = ord c
133+ m = n - 0x10000
134+ lo = fromIntegral $ (m `shiftR` 10) + 0xD800
135+ hi = fromIntegral $ (m .&. 0x3FF) + 0xDC00
136+{-# INLINE singleton #-}
137+
138+------------------------------------------------------------------------
139+
140+-- | /O(1)./ The concatenation of two builders, an associative
141+-- operation with identity 'empty', satisfying
142+--
143+-- * @'toLazyText' ('append' x y) = 'L.append' ('toLazyText' x) ('toLazyText' y)@
144+--
145+append :: Builder -> Builder -> Builder
146+append (Builder f) (Builder g) = Builder (f . g)
147+{-# INLINE [0] append #-}
148+
149+-- TODO: Experiment to find the right threshold.
150+copyLimit :: Int
151+copyLimit = 128
152+
153+-- This function attempts to merge small @Text@ values instead of
154+-- treating each value as its own chunk. We may not always want this.
155+
156+-- | /O(1)./ A @Builder@ taking a 'S.Text', satisfying
157+--
158+-- * @'toLazyText' ('fromText' t) = 'L.fromChunks' [t]@
159+--
160+fromText :: S.Text -> Builder
161+fromText t@(Text arr off l)
162+ | S.null t = empty
163+ | l <= copyLimit = writeN l $ \marr o -> A.copyI marr o arr off (l+o)
164+ | otherwise = flush `append` mapBuilder (t :)
165+{-# INLINE [1] fromText #-}
166+
167+{-# RULES
168+"fromText/pack" forall s .
169+ fromText (S.pack s) = fromString s
170+ #-}
171+
172+-- | /O(1)./ A Builder taking a @String@, satisfying
173+--
174+-- * @'toLazyText' ('fromString' s) = 'L.fromChunks' [S.pack s]@
175+--
176+fromString :: String -> Builder
177+fromString str = Builder $ \k (Buffer p0 o0 u0 l0) ->
178+ let loop !marr !o !u !l [] = k (Buffer marr o u l)
179+ loop marr o u l s@(c:cs)
180+ | l <= 1 = do
181+ arr <- A.unsafeFreeze marr
182+ let !t = Text arr o u
183+ marr' <- A.new chunkSize
184+ ts <- inlineInterleaveST (loop marr' 0 0 chunkSize s)
185+ return $ t : ts
186+ | otherwise = do
187+ n <- unsafeWrite marr (o+u) c
188+ loop marr o (u+n) (l-n) cs
189+ in loop p0 o0 u0 l0 str
190+ where
191+ chunkSize = smallChunkSize
192+{-# INLINE fromString #-}
193+
194+-- | /O(1)./ A @Builder@ taking a lazy @Text@, satisfying
195+--
196+-- * @'toLazyText' ('fromLazyText' t) = t@
197+--
198+fromLazyText :: L.Text -> Builder
199+fromLazyText ts = flush `append` mapBuilder (L.toChunks ts ++)
200+{-# INLINE fromLazyText #-}
201+
202+------------------------------------------------------------------------
203+
204+-- Our internal buffer type
205+data Buffer s = Buffer {-# UNPACK #-} !(A.MArray s)
206+ {-# UNPACK #-} !Int -- offset
207+ {-# UNPACK #-} !Int -- used units
208+ {-# UNPACK #-} !Int -- length left
209+
210+------------------------------------------------------------------------
211+
212+-- | /O(n)./ Extract a lazy @Text@ from a @Builder@ with a default
213+-- buffer size. The construction work takes place if and when the
214+-- relevant part of the lazy @Text@ is demanded.
215+toLazyText :: Builder -> L.Text
216+toLazyText = toLazyTextWith smallChunkSize
217+
218+-- | /O(n)./ Extract a lazy @Text@ from a @Builder@, using the given
219+-- size for the initial buffer. The construction work takes place if
220+-- and when the relevant part of the lazy @Text@ is demanded.
221+--
222+-- If the initial buffer is too small to hold all data, subsequent
223+-- buffers will be the default buffer size.
224+toLazyTextWith :: Int -> Builder -> L.Text
225+toLazyTextWith chunkSize m = L.fromChunks (runST $
226+ newBuffer chunkSize >>= runBuilder (m `append` flush) (const (return [])))
227+
228+-- | /O(1)./ Pop the strict @Text@ we have constructed so far, if any,
229+-- yielding a new chunk in the result lazy @Text@.
230+flush :: Builder
231+flush = Builder $ \ k buf@(Buffer p o u l) ->
232+ if u == 0
233+ then k buf
234+ else do arr <- A.unsafeFreeze p
235+ let !b = Buffer p (o+u) 0 l
236+ !t = Text arr o u
237+ ts <- inlineInterleaveST (k b)
238+ return $! t : ts
239+
240+------------------------------------------------------------------------
241+
242+-- | Sequence an ST operation on the buffer
243+withBuffer :: (forall s. Buffer s -> ST s (Buffer s)) -> Builder
244+withBuffer f = Builder $ \k buf -> f buf >>= k
245+{-# INLINE withBuffer #-}
246+
247+-- | Get the size of the buffer
248+withSize :: (Int -> Builder) -> Builder
249+withSize f = Builder $ \ k buf@(Buffer _ _ _ l) ->
250+ runBuilder (f l) k buf
251+{-# INLINE withSize #-}
252+
253+-- | Map the resulting list of texts.
254+mapBuilder :: ([S.Text] -> [S.Text]) -> Builder
255+mapBuilder f = Builder (fmap f .)
256+
257+------------------------------------------------------------------------
258+
259+-- | Ensure that there are at least @n@ many elements available.
260+ensureFree :: Int -> Builder
261+ensureFree !n = withSize $ \ l ->
262+ if n <= l
263+ then empty
264+ else flush `append'` withBuffer (const (newBuffer (max n smallChunkSize)))
265+{-# INLINE [0] ensureFree #-}
266+
267+writeAtMost :: Int -> (forall s. A.MArray s -> Int -> ST s Int) -> Builder
268+writeAtMost n f = ensureFree n `append'` withBuffer (writeBuffer f)
269+{-# INLINE [0] writeAtMost #-}
270+
271+-- | Ensure that @n@ many elements are available, and then use @f@ to
272+-- write some elements into the memory.
273+writeN :: Int -> (forall s. A.MArray s -> Int -> ST s ()) -> Builder
274+writeN n f = writeAtMost n (\ p o -> f p o >> return n)
275+{-# INLINE writeN #-}
276+
277+writeBuffer :: (A.MArray s -> Int -> ST s Int) -> Buffer s -> ST s (Buffer s)
278+writeBuffer f (Buffer p o u l) = do
279+ n <- f p (o+u)
280+ return $! Buffer p o (u+n) (l-n)
281+{-# INLINE writeBuffer #-}
282+
283+newBuffer :: Int -> ST s (Buffer s)
284+newBuffer size = do
285+ arr <- A.new size
286+ return $! Buffer arr 0 0 size
287+{-# INLINE newBuffer #-}
288+
289+------------------------------------------------------------------------
290+-- Some nice rules for Builder
291+
292+-- This function makes GHC understand that 'writeN' and 'ensureFree'
293+-- are *not* recursive in the precense of the rewrite rules below.
294+-- This is not needed with GHC 7+.
295+append' :: Builder -> Builder -> Builder
296+append' (Builder f) (Builder g) = Builder (f . g)
297+{-# INLINE append' #-}
298+
299+{-# RULES
300+
301+"append/writeAtMost" forall a b (f::forall s. A.MArray s -> Int -> ST s Int)
302+ (g::forall s. A.MArray s -> Int -> ST s Int) ws.
303+ append (writeAtMost a f) (append (writeAtMost b g) ws) =
304+ append (writeAtMost (a+b) (\marr o -> f marr o >>= \ n ->
305+ g marr (o+n) >>= \ m ->
306+ let s = n+m in s `seq` return s)) ws
307+
308+"writeAtMost/writeAtMost" forall a b (f::forall s. A.MArray s -> Int -> ST s Int)
309+ (g::forall s. A.MArray s -> Int -> ST s Int).
310+ append (writeAtMost a f) (writeAtMost b g) =
311+ writeAtMost (a+b) (\marr o -> f marr o >>= \ n ->
312+ g marr (o+n) >>= \ m ->
313+ let s = n+m in s `seq` return s)
314+
315+"ensureFree/ensureFree" forall a b .
316+ append (ensureFree a) (ensureFree b) = ensureFree (max a b)
317+
318+"flush/flush"
319+ append flush flush = flush
320+
321+ #-}
Data/Text/Lazy/Encoding.hs+12−4
@@ -1,4 +1,7 @@
1−{-# LANGUAGE BangPatterns #-}
1+{-# LANGUAGE BangPatterns,CPP #-}
2+#if __GLASGOW_HASKELL__ >= 702
3+{-# LANGUAGE Trustworthy #-}
4+#endif
25-- |
36-- Module : Data.Text.Lazy.Encoding
47-- Copyright : (c) 2009, 2010 Bryan O'Sullivan
@@ -20,6 +23,7 @@ module Data.Text.Lazy.Encoding
2023 -- * Decoding ByteStrings to Text
2124 -- $strict
2225 decodeASCII
26+ , decodeLatin1
2327 , decodeUtf8
2428 , decodeUtf16LE
2529 , decodeUtf16BE
@@ -48,7 +52,6 @@ import Control.Exception (evaluate, try)
4852import Data.Bits ((.&.))
4953import Data.Text.Encoding.Error (OnDecodeError, UnicodeException, strictDecode)
5054import Data.Text.Lazy.Internal (Text(..), chunk, empty, foldrChunks)
51−import System.IO.Unsafe (unsafePerformIO)
5255import qualified Data.ByteString as S
5356import qualified Data.ByteString.Lazy as B
5457import qualified Data.ByteString.Lazy.Internal as B
@@ -57,6 +60,7 @@ import qualified Data.Text as T
5760import qualified Data.Text.Encoding as TE
5861import qualified Data.Text.Lazy.Encoding.Fusion as E
5962import qualified Data.Text.Lazy.Fusion as F
63+import Data.Text.Unsafe (unsafeDupablePerformIO)
6064
6165-- $strict
6266--
@@ -74,11 +78,15 @@ import qualified Data.Text.Lazy.Fusion as F
7478-- | /Deprecated/. Decode a 'ByteString' containing 7-bit ASCII
7579-- encoded text.
7680--
77−-- This function is deprecated. Use 'decodeUtf8' instead.
81+-- This function is deprecated. Use 'decodeLatin1' instead.
7882decodeASCII :: B.ByteString -> Text
7983decodeASCII = decodeUtf8
8084{-# DEPRECATED decodeASCII "Use decodeUtf8 instead" #-}
8185
86+-- | Decode a 'ByteString' containing Latin-1 (aka ISO-8859-1) encoded text.
87+decodeLatin1 :: B.ByteString -> Text
88+decodeLatin1 = foldr (chunk . TE.decodeLatin1) empty . B.toChunks
89+
8290-- | Decode a 'ByteString' containing UTF-8 encoded text.
8391decodeUtf8With :: OnDecodeError -> B.ByteString -> Text
8492decodeUtf8With onErr bs0 = fast bs0
@@ -141,7 +149,7 @@ decodeUtf8 = decodeUtf8With strictDecode
141149-- input before it can return a result. If you need lazy (streaming)
142150-- decoding, use 'decodeUtf8With' in lenient mode.
143151decodeUtf8' :: B.ByteString -> Either UnicodeException Text
144−decodeUtf8' bs = unsafePerformIO $ do
152+decodeUtf8' bs = unsafeDupablePerformIO $ do
145153 let t = decodeUtf8 bs
146154 try (evaluate (rnf t `seq` t))
147155 where
Data/Text/Lazy/Encoding/Fusion.hs+2−2
@@ -42,7 +42,7 @@ import Data.Word (Word8, Word16, Word32)
4242import qualified Data.Text.Encoding.Utf8 as U8
4343import qualified Data.Text.Encoding.Utf16 as U16
4444import qualified Data.Text.Encoding.Utf32 as U32
45−import System.IO.Unsafe (unsafePerformIO)
45+import Data.Text.Unsafe (unsafeDupablePerformIO)
4646import Foreign.ForeignPtr (withForeignPtr, ForeignPtr)
4747import Foreign.Storable (pokeByteOff)
4848import Data.ByteString.Internal (mallocByteString, memcpy)
@@ -272,7 +272,7 @@ streamUtf32LE onErr bs0 = Stream next (T bs0 S0 0) unknownSize
272272-- | /O(n)/ Convert a 'Stream' 'Word8' to a lazy 'ByteString'.
273273unstreamChunks :: Int -> Stream Word8 -> ByteString
274274unstreamChunks chunkSize (Stream next s0 len0) = chunk s0 (upperBound 4 len0)
275− where chunk s1 len1 = unsafePerformIO $ do
275+ where chunk s1 len1 = unsafeDupablePerformIO $ do
276276 let len = max 4 (min len1 chunkSize)
277277 mallocByteString len >>= loop len 0 s1
278278 where
Data/Text/Lazy/Fusion.hs+26−24
@@ -24,6 +24,7 @@ module Data.Text.Lazy.Fusion
2424
2525import Prelude hiding (length)
2626import qualified Data.Text.Fusion.Common as S
27+import Control.Monad.ST (runST)
2728import Data.Text.Fusion.Internal
2829import Data.Text.Fusion.Size (isEmpty, unknownSize)
2930import Data.Text.Lazy.Internal
@@ -47,38 +48,39 @@ stream text = Stream next (text :*: 0) unknownSize
4748 where Iter c d = iter t i
4849{-# INLINE [0] stream #-}
4950
50−data UC s = UC s {-# UNPACK #-} !Int
51−
5251-- | /O(n)/ Convert a 'Stream Char' into a 'Text', using the given
5352-- chunk size.
5453unstreamChunks :: Int -> Stream Char -> Text
55−unstreamChunks chunkSize (Stream next s0 len0)
54+unstreamChunks !chunkSize (Stream next s0 len0)
5655 | isEmpty len0 = Empty
5756 | otherwise = outer s0
5857 where
59− outer s = {-# SCC "unstreamChunks/outer" #-}
60− case next s of
58+ outer so = {-# SCC "unstreamChunks/outer" #-}
59+ case next so of
6160 Done -> Empty
6261 Skip s' -> outer s'
63− Yield x s' -> I.Text arr 0 len `chunk` outer s''
64− where (arr, UC s'' len) = A.run2 fill
65− fill = do a <- A.new unknownLength
66− unsafeWrite a 0 x >>= inner a unknownLength s'
67− unknownLength = 4
68− inner marr len s !i
69− | i + 1 >= chunkSize = return (marr, UC s i)
70− | i + 1 >= len = {-# SCC "unstreamChunks/resize" #-} do
71− let newLen = min (len `shiftL` 1) chunkSize
72− marr' <- A.new newLen
73− A.copyM marr' 0 marr 0 len
74− inner marr' newLen s i
75− | otherwise =
76− {-# SCC "unstreamChunks/inner" #-}
77− case next s of
78− Done -> return (marr, UC s i)
79− Skip s' -> inner marr len s' i
80− Yield x s' -> do d <- unsafeWrite marr i x
81− inner marr len s' (i+d)
62+ Yield x s' -> runST $ do
63+ a <- A.new unknownLength
64+ unsafeWrite a 0 x >>= inner a unknownLength s'
65+ where unknownLength = 4
66+ where
67+ inner marr !len s !i
68+ | i + 1 >= chunkSize = finish marr i s
69+ | i + 1 >= len = {-# SCC "unstreamChunks/resize" #-} do
70+ let newLen = min (len `shiftL` 1) chunkSize
71+ marr' <- A.new newLen
72+ A.copyM marr' 0 marr 0 len
73+ inner marr' newLen s i
74+ | otherwise =
75+ {-# SCC "unstreamChunks/inner" #-}
76+ case next s of
77+ Done -> finish marr i s
78+ Skip s' -> inner marr len s' i
79+ Yield x s' -> do d <- unsafeWrite marr i x
80+ inner marr len s' (i+d)
81+ finish marr len s' = do
82+ arr <- A.unsafeFreeze marr
83+ return (I.Text arr 0 len `Chunk` outer s')
8284{-# INLINE [0] unstreamChunks #-}
8385
8486-- | /O(n)/ Convert a 'Stream Char' into a 'Text', using
Data/Text/Lazy/IO.hs+3
@@ -1,4 +1,7 @@
11{-# LANGUAGE BangPatterns, CPP, RecordWildCards #-}
2+#if __GLASGOW_HASKELL__ >= 702
3+{-# LANGUAGE Trustworthy #-}
4+#endif
25-- |
36-- Module : Data.Text.Lazy.IO
47-- Copyright : (c) 2009, 2010 Bryan O'Sullivan,
Data/Text/Lazy/Read.hs+4−1
@@ -1,4 +1,7 @@
1−{-# LANGUAGE OverloadedStrings #-}
1+{-# LANGUAGE OverloadedStrings, CPP #-}
2+#if __GLASGOW_HASKELL__ >= 702
3+{-# LANGUAGE Trustworthy #-}
4+#endif
25
36-- |
47-- Module : Data.Text.Lazy.Read
Data/Text/Read.hs+4−1
@@ -1,4 +1,7 @@
1−{-# LANGUAGE OverloadedStrings, UnboxedTuples #-}
1+{-# LANGUAGE OverloadedStrings, UnboxedTuples, CPP #-}
2+#if __GLASGOW_HASKELL__ >= 702
3+{-# LANGUAGE Trustworthy #-}
4+#endif
25
36-- |
47-- Module : Data.Text.Read
Data/Text/Search.hs+1−1
@@ -80,7 +80,7 @@ indices _needle@(Text narr noff nlen) _haystack@(Text harr hoff hlen)
8080 delta | nextInPattern = nlen + 1
8181 | c == z = skip + 1
8282 | otherwise = 1
83− nextInPattern = mask .&. swizzle (hindex' (i+nlen)) == 0
83+ where nextInPattern = mask .&. swizzle (hindex' (i+nlen)) == 0
8484 !(mask :* skip) = buildTable 0 0 (nlen-2)
8585 scanOne c = loop 0
8686 where loop !i | i >= hlen = []
Data/Text/Unsafe.hs+6
@@ -14,6 +14,7 @@ module Data.Text.Unsafe
1414 (
1515 inlineInterleaveST
1616 , inlinePerformIO
17+ , unsafeDupablePerformIO
1718 , Iter(..)
1819 , iter
1920 , iter_
@@ -33,6 +34,11 @@ import Data.Text.Internal (Text(..))
3334import Data.Text.Unsafe.Base (inlineInterleaveST, inlinePerformIO)
3435import Data.Text.UnsafeChar (unsafeChr)
3536import qualified Data.Text.Array as A
37+#if __GLASGOW_HASKELL__ >= 611
38+import GHC.IO (unsafeDupablePerformIO)
39+#else
40+import GHC.IOBase (unsafeDupablePerformIO)
41+#endif
3642
3743-- | /O(1)/ A variant of 'head' for non-empty 'Text'. 'unsafeHead'
3844-- omits the check for the empty case, so there is an obligation on
cbits/cbits.c+38
@@ -61,6 +61,44 @@ decode(uint32_t *state, uint32_t* codep, uint32_t byte) {
6161 return *state = utf8d[256 + *state + type];
6262}
6363
64+/*
65+ * The ISO 8859-1 (aka latin-1) code points correspond exactly to the first 256 unicode
66+ * code-points, therefore we can trivially convert from a latin-1 encoded bytestring to
67+ * an UTF16 array
68+ */
69+void
70+_hs_text_decode_latin1(uint16_t *dest, const uint8_t const *src,
71+ const uint8_t const *srcend)
72+{
73+ const uint8_t *p = src;
74+
75+#if defined(__i386__) || defined(__x86_64__)
76+ /* This optimization works on a little-endian systems by using
77+ (aligned) 32-bit loads instead of 8-bit loads
78+ */
79+
80+ /* consume unaligned prefix */
81+ while (p != srcend && (uintptr_t)p & 0x3)
82+ *dest++ = *p++;
83+
84+ /* iterate over 32-bit aligned loads */
85+ while (p < srcend - 3) {
86+ const uint32_t w = *((const uint32_t *)p);
87+
88+ *dest++ = w & 0xff;
89+ *dest++ = (w >> 8) & 0xff;
90+ *dest++ = (w >> 16) & 0xff;
91+ *dest++ = (w >> 24) & 0xff;
92+
93+ p += 4;
94+ }
95+#endif
96+
97+ /* handle unaligned suffix */
98+ while (p != srcend)
99+ *dest++ = *p++;
100+}
101+
64102/*
65103 * A best-effort decoder. Runs until it hits either end of input or
66104 * the start of an invalid byte sequence.
tests/Tests/Properties.hs+13
@@ -32,6 +32,7 @@ import Test.Framework (Test, testGroup)
3232import Test.Framework.Providers.QuickCheck2 (testProperty)
3333import qualified Data.Bits as Bits (shiftL, shiftR)
3434import qualified Data.ByteString as B
35+import qualified Data.ByteString.Lazy as BL
3536import qualified Data.List as L
3637import qualified Data.Text as T
3738import qualified Data.Text.Encoding as E
@@ -65,10 +66,20 @@ tl_unstreamChunks x = f 11 x == f 1000 x
6566tl_chunk_unchunk = (TL.fromChunks . TL.toChunks) `eq` id
6667tl_from_to_strict = (TL.fromStrict . TL.toStrict) `eq` id
6768
69+-- Note: this silently truncates code-points > 255 to 8-bit due to 'B.pack'
70+encodeL1 :: T.Text -> B.ByteString
71+encodeL1 = B.pack . map (fromIntegral . fromEnum) . T.unpack
72+encodeLazyL1 :: TL.Text -> BL.ByteString
73+encodeLazyL1 = BL.fromChunks . map encodeL1 . TL.toChunks
74+
6875t_ascii t = E.decodeASCII (E.encodeUtf8 a) == a
6976 where a = T.map (\c -> chr (ord c `mod` 128)) t
7077tl_ascii t = EL.decodeASCII (EL.encodeUtf8 a) == a
7178 where a = TL.map (\c -> chr (ord c `mod` 128)) t
79+t_latin1 t = E.decodeLatin1 (encodeL1 a) == a
80+ where a = T.map (\c -> chr (ord c `mod` 256)) t
81+tl_latin1 t = EL.decodeLatin1 (encodeLazyL1 a) == a
82+ where a = TL.map (\c -> chr (ord c `mod` 256)) t
7283t_utf8 = forAll genUnicode $ (E.decodeUtf8 . E.encodeUtf8) `eq` id
7384t_utf8' = forAll genUnicode $ (E.decodeUtf8' . E.encodeUtf8) `eq` (id . Right)
7485tl_utf8 = forAll genUnicode $ (EL.decodeUtf8 . EL.encodeUtf8) `eq` id
@@ -754,6 +765,8 @@ tests =
754765 testGroup "transcoding" [
755766 testProperty "t_ascii" t_ascii,
756767 testProperty "tl_ascii" tl_ascii,
768+ testProperty "t_latin1" t_latin1,
769+ testProperty "tl_latin1" tl_latin1,
757770 testProperty "t_utf8" t_utf8,
758771 testProperty "t_utf8'" t_utf8',
759772 testProperty "tl_utf8" tl_utf8,
tests/Tests/Regressions.hs+12−1
@@ -8,10 +8,12 @@ module Tests.Regressions
88
99import Control.Exception (SomeException, handle)
1010import System.IO
11−import Test.HUnit (assertFailure)
11+import Test.HUnit (assertBool, assertFailure)
1212import qualified Data.ByteString as B
13+import Data.ByteString.Char8 ()
1314import qualified Data.ByteString.Lazy as LB
1415import qualified Data.Text as T
16+import qualified Data.Text.Encoding as TE
1517import qualified Data.Text.IO as T
1618import qualified Data.Text.Lazy as LT
1719import qualified Data.Text.Lazy.Encoding as LE
@@ -49,9 +51,18 @@ replicate_crash = handle (\(_::SomeException) -> return ()) $
4951 power | maxBound == (2147483647::Int) = 28
5052 | otherwise = 60 :: Int
5153
54+-- Reported by John Millikin: a UTF-8 decode error handler could
55+-- return a bogus substitution character, which we would write without
56+-- checking.
57+utf8_decode_unsafe :: IO ()
58+utf8_decode_unsafe = do
59+ let t = TE.decodeUtf8With (\_ _ -> Just '\xdc00') "\x80"
60+ assertBool "broken error recovery shouldn't break us" (t == "\xfffd")
61+
5262tests :: F.Test
5363tests = F.testGroup "Regressions"
5464 [ F.testCase "hGetContents_crash" hGetContents_crash
5565 , F.testCase "lazy_encode_crash" lazy_encode_crash
5666 , F.testCase "replicate_crash" replicate_crash
67+ , F.testCase "utf8_decode_unsafe" utf8_decode_unsafe
5768 ]
tests/text-tests.cabal+2
@@ -87,6 +87,8 @@ library
8787 Data.Text.Lazy.Builder
8888 Data.Text.Lazy.Builder.Functions
8989 Data.Text.Lazy.Builder.Int
90+ Data.Text.Lazy.Builder.Int.Digits
91+ Data.Text.Lazy.Builder.Internal
9092 Data.Text.Lazy.Builder.RealFloat
9193 Data.Text.Lazy.Builder.RealFloat.Functions
9294 Data.Text.Lazy.Encoding
text.cabal+20−7
@@ -1,5 +1,5 @@
11name: text
2−version: 0.11.2.3
2+version: 0.11.2.4
33homepage: https://github.com/bos/text
44bug-reports: https://github.com/bos/text/issues
55synopsis: An efficient packed Unicode text type.
@@ -109,6 +109,8 @@ library
109109 Data.Text.Fusion.Size
110110 Data.Text.IO.Internal
111111 Data.Text.Lazy.Builder.Functions
112+ Data.Text.Lazy.Builder.Int.Digits
113+ Data.Text.Lazy.Builder.Internal
112114 Data.Text.Lazy.Builder.RealFloat.Functions
113115 Data.Text.Lazy.Encoding.Fusion
114116 Data.Text.Lazy.Fusion
@@ -131,7 +133,7 @@ library
131133 cpp-options: -DHAVE_DEEPSEQ
132134 else
133135 build-depends: extensible-exceptions
134− extensions: PatternSignatures
136+ extensions: ScopedTypeVariables
135137
136138 ghc-options: -Wall -funbox-strict-fields -O2
137139 if impl(ghc >= 6.8)
@@ -149,16 +151,15 @@ library
149151 cpp-options: -DINTEGER_GMP
150152 build-depends: integer-gmp >= 0.2
151153
152−
153154 if impl(ghc >= 6.9) && impl(ghc < 6.11)
154155 cpp-options: -DINTEGER_GMP
155156 build-depends: integer >= 0.1 && < 0.2
156157
157158test-suite tests
158159 type: exitcode-stdio-1.0
159− hs-source-dirs: tests
160+ hs-source-dirs: tests .
160161 main-is: Tests.hs
161− -- c-sources: cbits/cbits.c
162+ c-sources: cbits/cbits.c
162163
163164 ghc-options:
164165 -Wall -threaded -O0 -rtsopts
@@ -169,6 +170,7 @@ test-suite tests
169170 build-depends:
170171 HUnit >= 1.2,
171172 QuickCheck >= 2.4,
173+ array,
172174 base,
173175 bytestring,
174176 deepseq,
@@ -177,8 +179,19 @@ test-suite tests
177179 random,
178180 test-framework >= 0.4,
179181 test-framework-hunit >= 0.2,
180− test-framework-quickcheck2 >= 0.2,
181− text
182+ test-framework-quickcheck2 >= 0.2
183+
184+ if impl(ghc >= 6.11)
185+ if flag(integer-simple)
186+ cpp-options: -DINTEGER_SIMPLE
187+ build-depends: integer-simple >= 0.1 && < 0.5
188+ else
189+ cpp-options: -DINTEGER_GMP
190+ build-depends: integer-gmp >= 0.2
191+
192+ if impl(ghc >= 6.9) && impl(ghc < 6.11)
193+ cpp-options: -DINTEGER_GMP
194+ build-depends: integer >= 0.1 && < 0.2
182195
183196source-repository head
184197 type: git