on master help sign in
Merge
8d0f235Bryan O'Sullivan
@@ -39,3 +39,4 @@ b75d3041d275e8e76b26605b0d426a572ddf1249 0.11.1.3
396a3d847a56a69d0514a79cb212cb218271ad0917 0.11.1.13
396a3d847a56a69d0514a79cb212cb218271ad0917 0.11.1.13
401d2c6fa9092c6a4000b2abdd9d01f3efcd477be5 0.11.2.0
401d2c6fa9092c6a4000b2abdd9d01f3efcd477be5 0.11.2.0
4178219784cf3652cc662805bf2971bd62d80210a9 0.11.2.1
4178219784cf3652cc662805bf2971bd62d80210a9 0.11.2.1
42+4297307ebc11ad677cfba6b40319e7e5e2c0cfee 0.11.2.3
Data/Text.hs+7−4
@@ -1,5 +1,8 @@
1{-# LANGUAGE BangPatterns, CPP, MagicHash, Rank2Types, UnboxedTuples #-}
1{-# LANGUAGE BangPatterns, CPP, MagicHash, Rank2Types, UnboxedTuples #-}
2{-# OPTIONS_GHC -fno-warn-orphans #-}
2{-# OPTIONS_GHC -fno-warn-orphans #-}
3+#if __GLASGOW_HASKELL__ >= 702
4+{-# LANGUAGE Trustworthy #-}
5+#endif
3
6
4-- |
7-- |
5-- Module : Data.Text
8-- Module : Data.Text
@@ -370,7 +373,7 @@ compareText ta@(Text _arrA _offA lenA) tb@(Text _arrB _offB lenB)
370-- | /O(n)/ Convert a 'String' into a 'Text'. Subject to
373-- | /O(n)/ Convert a 'String' into a 'Text'. Subject to
371-- fusion. Performs replacement on invalid scalar values.
374-- fusion. Performs replacement on invalid scalar values.
372pack :: String -> Text
375pack :: String -> Text
373−pack = unstream . S.streamList . L.map safe
376+pack = unstream . S.map safe . S.streamList
374{-# INLINE [1] pack #-}
377{-# INLINE [1] pack #-}
375
378
376-- | /O(n)/ Convert a Text into a String. Subject to fusion.
379-- | /O(n)/ Convert a Text into a String. Subject to fusion.
@@ -378,17 +381,17 @@ unpack :: Text -> String
378unpack = S.unstreamList . stream
381unpack = S.unstreamList . stream
379{-# INLINE [1] unpack #-}
382{-# INLINE [1] unpack #-}
380
383
381−-- | /O(n)/ Convert a literal string into a Text.
384+-- | /O(n)/ Convert a literal string into a Text. Subject to fusion.
382unpackCString# :: Addr# -> Text
385unpackCString# :: Addr# -> Text
383unpackCString# addr# = unstream (S.streamCString# addr#)
386unpackCString# addr# = unstream (S.streamCString# addr#)
384{-# NOINLINE unpackCString# #-}
387{-# NOINLINE unpackCString# #-}
385
388
386{-# RULES "TEXT literal" forall a.
389{-# 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)))
388 = unpackCString# a #-}
391 = unpackCString# a #-}
389
392
390{-# RULES "TEXT literal UTF8" forall a.
393{-# 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)))
392 = unpackCString# a #-}
395 = unpackCString# a #-}
393
396
394-- | /O(1)/ Convert a character into a Text. Subject to fusion.
397-- | /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
114-- | Create an uninitialized mutable array.
114-- | Create an uninitialized mutable array.
115new :: forall s. Int -> ST s (MArray s)
115new :: forall s. Int -> ST s (MArray s)
116new n
116new n
117− | n < 0 || n .&. highBit /= 0 = error $ "Data.Text.Array.new: size overflow"
117+ | n < 0 || n .&. highBit /= 0 = array_size_error
118 | otherwise = ST $ \s1# ->
118 | otherwise = ST $ \s1# ->
119 case newByteArray# len# s1# of
119 case newByteArray# len# s1# of
120 (# s2#, marr# #) -> (# s2#, MArray marr#
120 (# s2#, marr# #) -> (# s2#, MArray marr#
@@ -126,6 +126,9 @@ new n
126 highBit = maxBound `xor` (maxBound `shiftR` 1)
126 highBit = maxBound `xor` (maxBound `shiftR` 1)
127{-# INLINE new #-}
127{-# INLINE new #-}
128
128
129+array_size_error :: a
130+array_size_error = error "Data.Text.Array.new: size overflow"
131+
129-- | Freeze a mutable array. Do not mutate the 'MArray' afterwards!
132-- | Freeze a mutable array. Do not mutate the 'MArray' afterwards!
130unsafeFreeze :: MArray s -> ST s Array
133unsafeFreeze :: MArray s -> ST s Array
131unsafeFreeze MArray{..} = ST $ \s# ->
134unsafeFreeze MArray{..} = ST $ \s# ->
@@ -181,6 +184,7 @@ run2 k = runST (do
181 (marr,b) <- k
184 (marr,b) <- k
182 arr <- unsafeFreeze marr
185 arr <- unsafeFreeze marr
183 return (arr,b))
186 return (arr,b))
187+{-# INLINE run2 #-}
184
188
185-- | Copy some elements of a mutable array.
189-- | Copy some elements of a mutable array.
186copyM :: MArray s -- ^ Destination
190copyM :: MArray s -- ^ Destination
Data/Text/Encoding.hs+25−6
@@ -1,5 +1,8 @@
1{-# LANGUAGE BangPatterns, CPP, ForeignFunctionInterface, MagicHash,
1{-# LANGUAGE BangPatterns, CPP, ForeignFunctionInterface, MagicHash,
2 UnliftedFFITypes #-}
2 UnliftedFFITypes #-}
3+#if __GLASGOW_HASKELL__ >= 702
4+{-# LANGUAGE Trustworthy #-}
5+#endif
3-- |
6-- |
4-- Module : Data.Text.Encoding
7-- Module : Data.Text.Encoding
5-- Copyright : (c) 2009, 2010, 2011 Bryan O'Sullivan,
8-- Copyright : (c) 2009, 2010, 2011 Bryan O'Sullivan,
@@ -23,6 +26,7 @@ module Data.Text.Encoding
23 -- * Decoding ByteStrings to Text
26 -- * Decoding ByteStrings to Text
24 -- $strict
27 -- $strict
25 decodeASCII
28 decodeASCII
29+ , decodeLatin1
26 , decodeUtf8
30 , decodeUtf8
27 , decodeUtf16LE
31 , decodeUtf16LE
28 , decodeUtf16BE
32 , decodeUtf16BE
@@ -57,7 +61,7 @@ import Data.Bits ((.&.))
57import Data.ByteString as B
61import Data.ByteString as B
58import Data.ByteString.Internal as B
62import Data.ByteString.Internal as B
59import Data.Text.Encoding.Error (OnDecodeError, UnicodeException, strictDecode)
63import Data.Text.Encoding.Error (OnDecodeError, UnicodeException, strictDecode)
60−import Data.Text.Internal (Text(..))
64+import Data.Text.Internal (Text(..), safe, textP)
61import Data.Text.Private (runText)
65import Data.Text.Private (runText)
62import Data.Text.UnsafeChar (ord, unsafeWrite)
66import Data.Text.UnsafeChar (ord, unsafeWrite)
63import Data.Text.UnsafeShift (shiftL, shiftR)
67import Data.Text.UnsafeShift (shiftL, shiftR)
@@ -68,11 +72,11 @@ import Foreign.Marshal.Utils (with)
68import Foreign.Ptr (Ptr, minusPtr, plusPtr)
72import Foreign.Ptr (Ptr, minusPtr, plusPtr)
69import Foreign.Storable (peek, poke)
73import Foreign.Storable (peek, poke)
70import GHC.Base (MutableByteArray#)
74import GHC.Base (MutableByteArray#)
71−import System.IO.Unsafe (unsafePerformIO)
72import qualified Data.Text.Array as A
75import qualified Data.Text.Array as A
73import qualified Data.Text.Encoding.Fusion as E
76import qualified Data.Text.Encoding.Fusion as E
74import qualified Data.Text.Encoding.Utf16 as U16
77import qualified Data.Text.Encoding.Utf16 as U16
75import qualified Data.Text.Fusion as F
78import qualified Data.Text.Fusion as F
79+import Data.Text.Unsafe (unsafeDupablePerformIO)
76
80
77-- $strict
81-- $strict
78--
82--
@@ -90,11 +94,23 @@ import qualified Data.Text.Fusion as F
90-- | /Deprecated/. Decode a 'ByteString' containing 7-bit ASCII
94-- | /Deprecated/. Decode a 'ByteString' containing 7-bit ASCII
91-- encoded text.
95-- encoded text.
92--
96--
93−-- This function is deprecated. Use 'decodeUtf8' instead.
97+-- This function is deprecated. Use 'decodeLatin1' instead.
94decodeASCII :: ByteString -> Text
98decodeASCII :: ByteString -> Text
95decodeASCII = decodeUtf8
99decodeASCII = decodeUtf8
96{-# DEPRECATED decodeASCII "Use decodeUtf8 instead" #-}
100{-# DEPRECATED decodeASCII "Use decodeUtf8 instead" #-}
97
101
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+
98-- | Decode a 'ByteString' containing UTF-8 encoded text.
114-- | Decode a 'ByteString' containing UTF-8 encoded text.
99decodeUtf8With :: OnDecodeError -> ByteString -> Text
115decodeUtf8With :: OnDecodeError -> ByteString -> Text
100decodeUtf8With onErr (PS fp off len) = runText $ \done -> do
116decodeUtf8With onErr (PS fp off len) = runText $ \done -> do
@@ -114,7 +130,7 @@ decodeUtf8With onErr (PS fp off len) = runText $ \done -> do
114 Just c -> do
130 Just c -> do
115 destOff <- peek destOffPtr
131 destOff <- peek destOffPtr
116 w <- unsafeSTToIO $
132 w <- unsafeSTToIO $
117− unsafeWrite dest (fromIntegral destOff) c
133+ unsafeWrite dest (fromIntegral destOff) (safe c)
118 poke destOffPtr (destOff + fromIntegral w)
134 poke destOffPtr (destOff + fromIntegral w)
119 loop $ curPtr' `plusPtr` 1
135 loop $ curPtr' `plusPtr` 1
120 loop (ptr `plusPtr` off)
136 loop (ptr `plusPtr` off)
@@ -141,12 +157,12 @@ decodeUtf8 = decodeUtf8With strictDecode
141-- If the input contains any invalid UTF-8 data, the relevant
157-- If the input contains any invalid UTF-8 data, the relevant
142-- exception will be returned, otherwise the decoded text.
158-- exception will be returned, otherwise the decoded text.
143decodeUtf8' :: ByteString -> Either UnicodeException Text
159decodeUtf8' :: ByteString -> Either UnicodeException Text
144−decodeUtf8' = unsafePerformIO . try . evaluate . decodeUtf8With strictDecode
160+decodeUtf8' = unsafeDupablePerformIO . try . evaluate . decodeUtf8With strictDecode
145{-# INLINE decodeUtf8' #-}
161{-# INLINE decodeUtf8' #-}
146
162
147-- | Encode text using UTF-8 encoding.
163-- | Encode text using UTF-8 encoding.
148encodeUtf8 :: Text -> ByteString
164encodeUtf8 :: Text -> ByteString
149−encodeUtf8 (Text arr off len) = unsafePerformIO $ do
165+encodeUtf8 (Text arr off len) = unsafeDupablePerformIO $ do
150 let size0 = max len 4
166 let size0 = max len 4
151 mallocByteString size0 >>= start size0 off 0
167 mallocByteString size0 >>= start size0 off 0
152 where
168 where
@@ -279,3 +295,6 @@ encodeUtf32BE txt = E.unstream (E.restreamUtf32BE (F.stream txt))
279foreign import ccall unsafe "_hs_text_decode_utf8" c_decode_utf8
295foreign import ccall unsafe "_hs_text_decode_utf8" c_decode_utf8
280 :: MutableByteArray# s -> Ptr CSize
296 :: MutableByteArray# s -> Ptr CSize
281 -> Ptr Word8 -> Ptr Word8 -> IO (Ptr Word8)
297 -> 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 @@
1{-# LANGUAGE CPP, DeriveDataTypeable #-}
1{-# LANGUAGE CPP, DeriveDataTypeable #-}
2+#if __GLASGOW_HASKELL__ >= 702
3+{-# LANGUAGE Trustworthy #-}
4+#endif
2-- |
5-- |
3-- Module : Data.Text.Encoding.Error
6-- Module : Data.Text.Encoding.Error
4-- Copyright : (c) Bryan O'Sullivan 2009
7-- Copyright : (c) Bryan O'Sullivan 2009
Data/Text/Encoding/Fusion.hs+2−2
@@ -44,12 +44,12 @@ import Data.Text.UnsafeShift (shiftL, shiftR)
44import Data.Word (Word8, Word16, Word32)
44import Data.Word (Word8, Word16, Word32)
45import Foreign.ForeignPtr (withForeignPtr, ForeignPtr)
45import Foreign.ForeignPtr (withForeignPtr, ForeignPtr)
46import Foreign.Storable (pokeByteOff)
46import Foreign.Storable (pokeByteOff)
47−import System.IO.Unsafe (unsafePerformIO)
48import qualified Data.ByteString as B
47import qualified Data.ByteString as B
49import qualified Data.ByteString.Unsafe as B
48import qualified Data.ByteString.Unsafe as B
50import qualified Data.Text.Encoding.Utf8 as U8
49import qualified Data.Text.Encoding.Utf8 as U8
51import qualified Data.Text.Encoding.Utf16 as U16
50import qualified Data.Text.Encoding.Utf16 as U16
52import qualified Data.Text.Encoding.Utf32 as U32
51import qualified Data.Text.Encoding.Utf32 as U32
52+import Data.Text.Unsafe (unsafeDupablePerformIO)
53
53
54streamASCII :: ByteString -> Stream Char
54streamASCII :: ByteString -> Stream Char
55streamASCII bs = Stream next 0 (maxSize l)
55streamASCII bs = Stream next 0 (maxSize l)
@@ -163,7 +163,7 @@ streamUtf32LE onErr bs = Stream next 0 (maxSize (l `shiftR` 2))
163
163
164-- | /O(n)/ Convert a 'Stream' 'Word8' to a 'ByteString'.
164-- | /O(n)/ Convert a 'Stream' 'Word8' to a 'ByteString'.
165unstream :: Stream Word8 -> ByteString
165unstream :: Stream Word8 -> ByteString
166−unstream (Stream next s0 len) = unsafePerformIO $ do
166+unstream (Stream next s0 len) = unsafeDupablePerformIO $ do
167 let mlen = upperBound 4 len
167 let mlen = upperBound 4 len
168 mallocByteString mlen >>= loop mlen 0 s0
168 mallocByteString mlen >>= loop mlen 0 s0
169 where
169 where
Data/Text/Fusion/Common.hs+6−2
@@ -172,7 +172,7 @@ data C s = C0 !s
172
172
173-- | /O(n)/ Adds a character to the front of a Stream Char.
173-- | /O(n)/ Adds a character to the front of a Stream Char.
174cons :: Char -> Stream Char -> Stream Char
174cons :: 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)
176 where
176 where
177 next (C1 s) = Yield w (C0 s)
177 next (C1 s) = Yield w (C0 s)
178 next (C0 s) = case next0 s of
178 next (C0 s) = case next0 s of
@@ -218,9 +218,13 @@ head (Stream next s0 _len) = loop_head s0
218 loop_head !s = case next s of
218 loop_head !s = case next s of
219 Yield x _ -> x
219 Yield x _ -> x
220 Skip s' -> loop_head s'
220 Skip s' -> loop_head s'
221− Done -> streamError "head" "Empty stream"
221+ Done -> head_empty
222{-# INLINE [0] head #-}
222{-# INLINE [0] head #-}
223
223
224+head_empty :: a
225+head_empty = streamError "head" "Empty stream"
226+{-# NOINLINE head_empty #-}
227+
224-- | /O(1)/ Returns the first character and remainder of a 'Stream
228-- | /O(1)/ Returns the first character and remainder of a 'Stream
225-- Char', or 'Nothing' if empty. Subject to array fusion.
229-- Char', or 'Nothing' if empty. Subject to array fusion.
226uncons :: Stream Char -> Maybe (Char, Stream Char)
230uncons :: Stream Char -> Maybe (Char, Stream Char)
Data/Text/IO.hs+30−9
@@ -1,4 +1,7 @@
1{-# LANGUAGE BangPatterns, CPP, RecordWildCards, ScopedTypeVariables #-}
1{-# LANGUAGE BangPatterns, CPP, RecordWildCards, ScopedTypeVariables #-}
2+#if __GLASGOW_HASKELL__ >= 702
3+{-# LANGUAGE Trustworthy #-}
4+#endif
2-- |
5-- |
3-- Module : Data.Text.IO
6-- Module : Data.Text.IO
4-- Copyright : (c) 2009, 2010 Bryan O'Sullivan,
7-- Copyright : (c) 2009, 2010 Bryan O'Sullivan,
@@ -26,6 +29,7 @@ module Data.Text.IO
26 , appendFile
29 , appendFile
27 -- * Operations on handles
30 -- * Operations on handles
28 , hGetContents
31 , hGetContents
32+ , hGetChunk
29 , hGetLine
33 , hGetLine
30 , hPutStr
34 , hPutStr
31 , hPutStrLn
35 , hPutStrLn
@@ -97,6 +101,30 @@ writeFile p = withFile p WriteMode . flip hPutStr
97appendFile :: FilePath -> Text -> IO ()
101appendFile :: FilePath -> Text -> IO ()
98appendFile p = withFile p AppendMode . flip hPutStr
102appendFile p = withFile p AppendMode . flip hPutStr
99
103
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+
100-- | Read the remaining contents of a 'Handle' as a string. The
128-- | Read the remaining contents of a 'Handle' as a string. The
101-- 'Handle' is closed once the contents have been read, or if an
129-- 'Handle' is closed once the contents have been read, or if an
102-- exception is thrown.
130-- exception is thrown.
@@ -117,16 +145,9 @@ hGetContents h = do
117 wantReadableHandle "hGetContents" h readAll
145 wantReadableHandle "hGetContents" h readAll
118 where
146 where
119 readAll hh@Handle__{..} = do
147 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
128 buf <- readIORef haCharBuffer
149 buf <- readIORef haCharBuffer
129− t <- readChunk hh buf `E.catch` catchError
150+ t <- readChunk hh buf `E.catch` catchError "hGetContents" h hh
130 if T.null t
151 if T.null t
131 then return [t]
152 then return [t]
132 else (t:) `fmap` readChunks
153 else (t:) `fmap` readChunks
Data/Text/Internal.hs+25−4
@@ -23,6 +23,7 @@
23module Data.Text.Internal
23module Data.Text.Internal
24 (
24 (
25 -- * Types
25 -- * Types
26+ -- $internals
26 Text(..)
27 Text(..)
27 -- * Construction
28 -- * Construction
28 , text
29 , text
@@ -47,9 +48,9 @@ import Data.Typeable (Typeable)
47
48
48-- | A space efficient, packed, unboxed Unicode text type.
49-- | A space efficient, packed, unboxed Unicode text type.
49data Text = Text
50data Text = Text
50− {-# UNPACK #-} !A.Array -- payload
51+ {-# UNPACK #-} !A.Array -- payload (Word16 elements)
51− {-# UNPACK #-} !Int -- offset
52+ {-# UNPACK #-} !Int -- offset (units of Word16, not Char)
52− {-# UNPACK #-} !Int -- length
53+ {-# UNPACK #-} !Int -- length (units of Word16, not Char)
53 deriving (Typeable)
54 deriving (Typeable)
54
55
55-- | Smart constructor.
56-- | Smart constructor.
@@ -90,7 +91,8 @@ showText (Text arr off len) =
90-- scalar values, but are unfortunately admitted as valid 'Char'
91-- scalar values, but are unfortunately admitted as valid 'Char'
91-- values by Haskell. They cannot be represented in a 'Text'. This
92-- values by Haskell. They cannot be represented in a 'Text'. This
92-- function remaps those code points to the Unicode replacement
93-- 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.
94safe :: Char -> Char
96safe :: Char -> Char
95safe c
97safe c
96 | ord c .&. 0x1ff800 /= 0xd800 = c
98 | ord c .&. 0x1ff800 /= 0xd800 = c
@@ -101,3 +103,22 @@ safe c
101firstf :: (a -> c) -> Maybe (a,b) -> Maybe (c,b)
103firstf :: (a -> c) -> Maybe (a,b) -> Maybe (c,b)
102firstf f (Just (a, b)) = Just (f a, b)
104firstf f (Just (a, b)) = Just (f a, b)
103firstf _ Nothing = Nothing
105firstf _ 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 @@
1{-# OPTIONS_GHC -fno-warn-orphans #-}
1{-# OPTIONS_GHC -fno-warn-orphans #-}
2{-# LANGUAGE BangPatterns, MagicHash, CPP #-}
2{-# LANGUAGE BangPatterns, MagicHash, CPP #-}
3+#if __GLASGOW_HASKELL__ >= 702
4+{-# LANGUAGE Trustworthy #-}
5+#endif
3-- |
6-- |
4-- Module : Data.Text.Lazy
7-- Module : Data.Text.Lazy
5-- Copyright : (c) 2009, 2010, 2012 Bryan O'Sullivan
8-- Copyright : (c) 2009, 2010, 2012 Bryan O'Sullivan
Data/Text/Lazy/Builder.hs+6−269
@@ -3,7 +3,8 @@
3-----------------------------------------------------------------------------
3-----------------------------------------------------------------------------
4-- |
4-- |
5-- Module : Data.Text.Lazy.Builder
5-- Module : Data.Text.Lazy.Builder
6+-- Copyright : (c) 2013 Bryan O'Sullivan
6−-- Copyright : (c) 2010 Johan Tibell
7+-- (c) 2010 Johan Tibell
7-- License : BSD3-style (see LICENSE)
8-- License : BSD3-style (see LICENSE)
8--
9--
9-- Maintainer : Johan Tibell <johan.tibell@gmail.com>
10-- Maintainer : Johan Tibell <johan.tibell@gmail.com>
@@ -15,7 +16,9 @@
15-- @fromLazyText@, which construct new builders, and 'mappend', which
16-- @fromLazyText@, which construct new builders, and 'mappend', which
16-- concatenates two builders.
17-- concatenates two builders.
17--
18--
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
19--
22--
20-- > singleton 'a' `mappend` (singleton 'b' `mappend` singleton 'c')
23-- > singleton 'a' `mappend` (singleton 'b' `mappend` singleton 'c')
21--
24--
@@ -43,270 +46,4 @@ module Data.Text.Lazy.Builder
43 , flush
46 , flush
44 ) where
47 ) where
45
48
46−import Control.Monad.ST (ST, runST)
49+import Data.Text.Lazy.Builder.Internal
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− #-}
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 #-}
2
2
3-- Module: Data.Text.Lazy.Builder.Int
3-- Module: Data.Text.Lazy.Builder.Int
4+-- Copyright: (c) 2013 Bryan O'Sullivan
4−-- Copyright: (c) 2011 MailRank, Inc.
5+-- (c) 2011 MailRank, Inc.
5-- License: BSD3
6-- License: BSD3
6-- Maintainer: Bryan O'Sullivan <bos@serpentine.com>
7-- Maintainer: Bryan O'Sullivan <bos@serpentine.com>
7-- Stability: experimental
8-- Stability: experimental
@@ -17,12 +18,16 @@ module Data.Text.Lazy.Builder.Int
17
18
18import Data.Int (Int8, Int16, Int32, Int64)
19import Data.Int (Int8, Int16, Int32, Int64)
19import Data.Monoid (mempty)
20import Data.Monoid (mempty)
21+import qualified Data.ByteString.Unsafe as B
20import Data.Text.Lazy.Builder.Functions ((<>), i2d)
22import 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
22import Data.Word (Word, Word8, Word16, Word32, Word64)
26import Data.Word (Word, Word8, Word16, Word32, Word64)
23import GHC.Base (quotInt, remInt)
27import GHC.Base (quotInt, remInt)
24import GHC.Num (quotRemInteger)
28import GHC.Num (quotRemInteger)
25import GHC.Types (Int(..))
29import GHC.Types (Int(..))
30+import Control.Monad.ST
26
31
27#ifdef __GLASGOW_HASKELL__
32#ifdef __GLASGOW_HASKELL__
28# if __GLASGOW_HASKELL__ < 611
33# if __GLASGOW_HASKELL__ < 611
@@ -54,12 +59,7 @@ decimal :: Integral a => a -> Builder
54{-# RULES "decimal/Word32" decimal = positive :: Word32 -> Builder #-}
59{-# RULES "decimal/Word32" decimal = positive :: Word32 -> Builder #-}
55{-# RULES "decimal/Word64" decimal = positive :: Word64 -> Builder #-}
60{-# RULES "decimal/Word64" decimal = positive :: Word64 -> Builder #-}
56{-# RULES "decimal/Integer" decimal = integer 10 :: Integer -> Builder #-}
61{-# RULES "decimal/Integer" decimal = integer 10 :: Integer -> Builder #-}
57−decimal i
62+decimal i = decimal' (<= -128) 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
63
63
64boundedDecimal :: (Integral a, Bounded a) => a -> Builder
64boundedDecimal :: (Integral a, Bounded a) => a -> Builder
65{-# SPECIALIZE boundedDecimal :: Int -> Builder #-}
65{-# SPECIALIZE boundedDecimal :: Int -> Builder #-}
@@ -67,11 +67,22 @@ boundedDecimal :: (Integral a, Bounded a) => a -> Builder
67{-# SPECIALIZE boundedDecimal :: Int16 -> Builder #-}
67{-# SPECIALIZE boundedDecimal :: Int16 -> Builder #-}
68{-# SPECIALIZE boundedDecimal :: Int32 -> Builder #-}
68{-# SPECIALIZE boundedDecimal :: Int32 -> Builder #-}
69{-# SPECIALIZE boundedDecimal :: Int64 -> Builder #-}
69{-# SPECIALIZE boundedDecimal :: Int64 -> Builder #-}
70−boundedDecimal i
70+boundedDecimal i = decimal' (== minBound) i
71− | i < 0 = singleton '-' <>
71+
72− if i == minBound
72+decimal' :: (Integral a) => (a -> Bool) -> a -> Builder
73+{-# INLINE decimal' #-}
74+decimal' p i
75+ | i < 0 = if p i
73− then positive (-(i `quot` 10)) <> digit (-(i `rem` 10))
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)))
74− else positive (-i)
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
75 | otherwise = positive i
86 | otherwise = positive i
76
87
77positive :: (Integral a) => a -> Builder
88positive :: (Integral a) => a -> Builder
@@ -85,9 +96,56 @@ positive :: (Integral a) => a -> Builder
85{-# SPECIALIZE positive :: Word16 -> Builder #-}
96{-# SPECIALIZE positive :: Word16 -> Builder #-}
86{-# SPECIALIZE positive :: Word32 -> Builder #-}
97{-# SPECIALIZE positive :: Word32 -> Builder #-}
87{-# SPECIALIZE positive :: Word64 -> Builder #-}
98{-# SPECIALIZE positive :: Word64 -> Builder #-}
88−positive = go
99+positive i
89− where go n | n < 10 = digit n
100+ | i < 10 = writeN 1 $ \marr off -> unsafeWrite marr off (i2w i)
90− | otherwise = go (n `quot` 10) <> digit (n `rem` 10)
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
91
149
92hexadecimal :: Integral a => a -> Builder
150hexadecimal :: Integral a => a -> Builder
93{-# SPECIALIZE hexadecimal :: Int -> Builder #-}
151{-# 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
2-- |
5-- |
3-- Module : Data.Text.Lazy.Encoding
6-- Module : Data.Text.Lazy.Encoding
4-- Copyright : (c) 2009, 2010 Bryan O'Sullivan
7-- Copyright : (c) 2009, 2010 Bryan O'Sullivan
@@ -20,6 +23,7 @@ module Data.Text.Lazy.Encoding
20 -- * Decoding ByteStrings to Text
23 -- * Decoding ByteStrings to Text
21 -- $strict
24 -- $strict
22 decodeASCII
25 decodeASCII
26+ , decodeLatin1
23 , decodeUtf8
27 , decodeUtf8
24 , decodeUtf16LE
28 , decodeUtf16LE
25 , decodeUtf16BE
29 , decodeUtf16BE
@@ -48,7 +52,6 @@ import Control.Exception (evaluate, try)
48import Data.Bits ((.&.))
52import Data.Bits ((.&.))
49import Data.Text.Encoding.Error (OnDecodeError, UnicodeException, strictDecode)
53import Data.Text.Encoding.Error (OnDecodeError, UnicodeException, strictDecode)
50import Data.Text.Lazy.Internal (Text(..), chunk, empty, foldrChunks)
54import Data.Text.Lazy.Internal (Text(..), chunk, empty, foldrChunks)
51−import System.IO.Unsafe (unsafePerformIO)
52import qualified Data.ByteString as S
55import qualified Data.ByteString as S
53import qualified Data.ByteString.Lazy as B
56import qualified Data.ByteString.Lazy as B
54import qualified Data.ByteString.Lazy.Internal as B
57import qualified Data.ByteString.Lazy.Internal as B
@@ -57,6 +60,7 @@ import qualified Data.Text as T
57import qualified Data.Text.Encoding as TE
60import qualified Data.Text.Encoding as TE
58import qualified Data.Text.Lazy.Encoding.Fusion as E
61import qualified Data.Text.Lazy.Encoding.Fusion as E
59import qualified Data.Text.Lazy.Fusion as F
62import qualified Data.Text.Lazy.Fusion as F
63+import Data.Text.Unsafe (unsafeDupablePerformIO)
60
64
61-- $strict
65-- $strict
62--
66--
@@ -74,11 +78,15 @@ import qualified Data.Text.Lazy.Fusion as F
74-- | /Deprecated/. Decode a 'ByteString' containing 7-bit ASCII
78-- | /Deprecated/. Decode a 'ByteString' containing 7-bit ASCII
75-- encoded text.
79-- encoded text.
76--
80--
77−-- This function is deprecated. Use 'decodeUtf8' instead.
81+-- This function is deprecated. Use 'decodeLatin1' instead.
78decodeASCII :: B.ByteString -> Text
82decodeASCII :: B.ByteString -> Text
79decodeASCII = decodeUtf8
83decodeASCII = decodeUtf8
80{-# DEPRECATED decodeASCII "Use decodeUtf8 instead" #-}
84{-# DEPRECATED decodeASCII "Use decodeUtf8 instead" #-}
81
85
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+
82-- | Decode a 'ByteString' containing UTF-8 encoded text.
90-- | Decode a 'ByteString' containing UTF-8 encoded text.
83decodeUtf8With :: OnDecodeError -> B.ByteString -> Text
91decodeUtf8With :: OnDecodeError -> B.ByteString -> Text
84decodeUtf8With onErr bs0 = fast bs0
92decodeUtf8With onErr bs0 = fast bs0
@@ -141,7 +149,7 @@ decodeUtf8 = decodeUtf8With strictDecode
141-- input before it can return a result. If you need lazy (streaming)
149-- input before it can return a result. If you need lazy (streaming)
142-- decoding, use 'decodeUtf8With' in lenient mode.
150-- decoding, use 'decodeUtf8With' in lenient mode.
143decodeUtf8' :: B.ByteString -> Either UnicodeException Text
151decodeUtf8' :: B.ByteString -> Either UnicodeException Text
144−decodeUtf8' bs = unsafePerformIO $ do
152+decodeUtf8' bs = unsafeDupablePerformIO $ do
145 let t = decodeUtf8 bs
153 let t = decodeUtf8 bs
146 try (evaluate (rnf t `seq` t))
154 try (evaluate (rnf t `seq` t))
147 where
155 where
Data/Text/Lazy/Encoding/Fusion.hs+2−2
@@ -42,7 +42,7 @@ import Data.Word (Word8, Word16, Word32)
42import qualified Data.Text.Encoding.Utf8 as U8
42import qualified Data.Text.Encoding.Utf8 as U8
43import qualified Data.Text.Encoding.Utf16 as U16
43import qualified Data.Text.Encoding.Utf16 as U16
44import qualified Data.Text.Encoding.Utf32 as U32
44import qualified Data.Text.Encoding.Utf32 as U32
45−import System.IO.Unsafe (unsafePerformIO)
45+import Data.Text.Unsafe (unsafeDupablePerformIO)
46import Foreign.ForeignPtr (withForeignPtr, ForeignPtr)
46import Foreign.ForeignPtr (withForeignPtr, ForeignPtr)
47import Foreign.Storable (pokeByteOff)
47import Foreign.Storable (pokeByteOff)
48import Data.ByteString.Internal (mallocByteString, memcpy)
48import Data.ByteString.Internal (mallocByteString, memcpy)
@@ -272,7 +272,7 @@ streamUtf32LE onErr bs0 = Stream next (T bs0 S0 0) unknownSize
272-- | /O(n)/ Convert a 'Stream' 'Word8' to a lazy 'ByteString'.
272-- | /O(n)/ Convert a 'Stream' 'Word8' to a lazy 'ByteString'.
273unstreamChunks :: Int -> Stream Word8 -> ByteString
273unstreamChunks :: Int -> Stream Word8 -> ByteString
274unstreamChunks chunkSize (Stream next s0 len0) = chunk s0 (upperBound 4 len0)
274unstreamChunks chunkSize (Stream next s0 len0) = chunk s0 (upperBound 4 len0)
275− where chunk s1 len1 = unsafePerformIO $ do
275+ where chunk s1 len1 = unsafeDupablePerformIO $ do
276 let len = max 4 (min len1 chunkSize)
276 let len = max 4 (min len1 chunkSize)
277 mallocByteString len >>= loop len 0 s1
277 mallocByteString len >>= loop len 0 s1
278 where
278 where
Data/Text/Lazy/Fusion.hs+26−24
@@ -24,6 +24,7 @@ module Data.Text.Lazy.Fusion
24
24
25import Prelude hiding (length)
25import Prelude hiding (length)
26import qualified Data.Text.Fusion.Common as S
26import qualified Data.Text.Fusion.Common as S
27+import Control.Monad.ST (runST)
27import Data.Text.Fusion.Internal
28import Data.Text.Fusion.Internal
28import Data.Text.Fusion.Size (isEmpty, unknownSize)
29import Data.Text.Fusion.Size (isEmpty, unknownSize)
29import Data.Text.Lazy.Internal
30import Data.Text.Lazy.Internal
@@ -47,38 +48,39 @@ stream text = Stream next (text :*: 0) unknownSize
47 where Iter c d = iter t i
48 where Iter c d = iter t i
48{-# INLINE [0] stream #-}
49{-# INLINE [0] stream #-}
49
50
50−data UC s = UC s {-# UNPACK #-} !Int
51−
52-- | /O(n)/ Convert a 'Stream Char' into a 'Text', using the given
51-- | /O(n)/ Convert a 'Stream Char' into a 'Text', using the given
53-- chunk size.
52-- chunk size.
54unstreamChunks :: Int -> Stream Char -> Text
53unstreamChunks :: Int -> Stream Char -> Text
55−unstreamChunks chunkSize (Stream next s0 len0)
54+unstreamChunks !chunkSize (Stream next s0 len0)
56 | isEmpty len0 = Empty
55 | isEmpty len0 = Empty
57 | otherwise = outer s0
56 | otherwise = outer s0
58 where
57 where
59− outer s = {-# SCC "unstreamChunks/outer" #-}
58+ outer so = {-# SCC "unstreamChunks/outer" #-}
60− case next s of
59+ case next so of
61 Done -> Empty
60 Done -> Empty
62 Skip s' -> outer s'
61 Skip s' -> outer s'
63− Yield x s' -> I.Text arr 0 len `chunk` outer s''
62+ Yield x s' -> runST $ do
64− where (arr, UC s'' len) = A.run2 fill
65− fill = do a <- A.new unknownLength
63+ a <- A.new unknownLength
66− unsafeWrite a 0 x >>= inner a unknownLength s'
64+ unsafeWrite a 0 x >>= inner a unknownLength s'
67− unknownLength = 4
65+ where unknownLength = 4
66+ where
68− inner marr len s !i
67+ inner marr !len s !i
69− | i + 1 >= chunkSize = return (marr, UC s i)
68+ | i + 1 >= chunkSize = finish marr i s
70− | i + 1 >= len = {-# SCC "unstreamChunks/resize" #-} do
69+ | i + 1 >= len = {-# SCC "unstreamChunks/resize" #-} do
71− let newLen = min (len `shiftL` 1) chunkSize
70+ let newLen = min (len `shiftL` 1) chunkSize
72− marr' <- A.new newLen
71+ marr' <- A.new newLen
73− A.copyM marr' 0 marr 0 len
72+ A.copyM marr' 0 marr 0 len
74− inner marr' newLen s i
73+ inner marr' newLen s i
75− | otherwise =
74+ | otherwise =
76− {-# SCC "unstreamChunks/inner" #-}
75+ {-# SCC "unstreamChunks/inner" #-}
77− case next s of
76+ case next s of
78− Done -> return (marr, UC s i)
77+ Done -> finish marr i s
79− Skip s' -> inner marr len s' i
78+ Skip s' -> inner marr len s' i
80− Yield x s' -> do d <- unsafeWrite marr i x
79+ Yield x s' -> do d <- unsafeWrite marr i x
81− inner marr len s' (i+d)
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')
82{-# INLINE [0] unstreamChunks #-}
84{-# INLINE [0] unstreamChunks #-}
83
85
84-- | /O(n)/ Convert a 'Stream Char' into a 'Text', using
86-- | /O(n)/ Convert a 'Stream Char' into a 'Text', using
Data/Text/Lazy/IO.hs+3
@@ -1,4 +1,7 @@
1{-# LANGUAGE BangPatterns, CPP, RecordWildCards #-}
1{-# LANGUAGE BangPatterns, CPP, RecordWildCards #-}
2+#if __GLASGOW_HASKELL__ >= 702
3+{-# LANGUAGE Trustworthy #-}
4+#endif
2-- |
5-- |
3-- Module : Data.Text.Lazy.IO
6-- Module : Data.Text.Lazy.IO
4-- Copyright : (c) 2009, 2010 Bryan O'Sullivan,
7-- 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
2
5
3-- |
6-- |
4-- Module : Data.Text.Lazy.Read
7-- 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
2
5
3-- |
6-- |
4-- Module : Data.Text.Read
7-- 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)
80 delta | nextInPattern = nlen + 1
80 delta | nextInPattern = nlen + 1
81 | c == z = skip + 1
81 | c == z = skip + 1
82 | otherwise = 1
82 | otherwise = 1
83− nextInPattern = mask .&. swizzle (hindex' (i+nlen)) == 0
83+ where nextInPattern = mask .&. swizzle (hindex' (i+nlen)) == 0
84 !(mask :* skip) = buildTable 0 0 (nlen-2)
84 !(mask :* skip) = buildTable 0 0 (nlen-2)
85 scanOne c = loop 0
85 scanOne c = loop 0
86 where loop !i | i >= hlen = []
86 where loop !i | i >= hlen = []
Data/Text/Unsafe.hs+6
@@ -14,6 +14,7 @@ module Data.Text.Unsafe
14 (
14 (
15 inlineInterleaveST
15 inlineInterleaveST
16 , inlinePerformIO
16 , inlinePerformIO
17+ , unsafeDupablePerformIO
17 , Iter(..)
18 , Iter(..)
18 , iter
19 , iter
19 , iter_
20 , iter_
@@ -33,6 +34,11 @@ import Data.Text.Internal (Text(..))
33import Data.Text.Unsafe.Base (inlineInterleaveST, inlinePerformIO)
34import Data.Text.Unsafe.Base (inlineInterleaveST, inlinePerformIO)
34import Data.Text.UnsafeChar (unsafeChr)
35import Data.Text.UnsafeChar (unsafeChr)
35import qualified Data.Text.Array as A
36import 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
36
42
37-- | /O(1)/ A variant of 'head' for non-empty 'Text'. 'unsafeHead'
43-- | /O(1)/ A variant of 'head' for non-empty 'Text'. 'unsafeHead'
38-- omits the check for the empty case, so there is an obligation on
44-- 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) {
61 return *state = utf8d[256 + *state + type];
61 return *state = utf8d[256 + *state + type];
62}
62}
63
63
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+
64/*
102/*
65 * A best-effort decoder. Runs until it hits either end of input or
103 * A best-effort decoder. Runs until it hits either end of input or
66 * the start of an invalid byte sequence.
104 * the start of an invalid byte sequence.
tests/Tests/Properties.hs+13
@@ -32,6 +32,7 @@ import Test.Framework (Test, testGroup)
32import Test.Framework.Providers.QuickCheck2 (testProperty)
32import Test.Framework.Providers.QuickCheck2 (testProperty)
33import qualified Data.Bits as Bits (shiftL, shiftR)
33import qualified Data.Bits as Bits (shiftL, shiftR)
34import qualified Data.ByteString as B
34import qualified Data.ByteString as B
35+import qualified Data.ByteString.Lazy as BL
35import qualified Data.List as L
36import qualified Data.List as L
36import qualified Data.Text as T
37import qualified Data.Text as T
37import qualified Data.Text.Encoding as E
38import qualified Data.Text.Encoding as E
@@ -65,10 +66,20 @@ tl_unstreamChunks x = f 11 x == f 1000 x
65tl_chunk_unchunk = (TL.fromChunks . TL.toChunks) `eq` id
66tl_chunk_unchunk = (TL.fromChunks . TL.toChunks) `eq` id
66tl_from_to_strict = (TL.fromStrict . TL.toStrict) `eq` id
67tl_from_to_strict = (TL.fromStrict . TL.toStrict) `eq` id
67
68
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+
68t_ascii t = E.decodeASCII (E.encodeUtf8 a) == a
75t_ascii t = E.decodeASCII (E.encodeUtf8 a) == a
69 where a = T.map (\c -> chr (ord c `mod` 128)) t
76 where a = T.map (\c -> chr (ord c `mod` 128)) t
70tl_ascii t = EL.decodeASCII (EL.encodeUtf8 a) == a
77tl_ascii t = EL.decodeASCII (EL.encodeUtf8 a) == a
71 where a = TL.map (\c -> chr (ord c `mod` 128)) t
78 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
72t_utf8 = forAll genUnicode $ (E.decodeUtf8 . E.encodeUtf8) `eq` id
83t_utf8 = forAll genUnicode $ (E.decodeUtf8 . E.encodeUtf8) `eq` id
73t_utf8' = forAll genUnicode $ (E.decodeUtf8' . E.encodeUtf8) `eq` (id . Right)
84t_utf8' = forAll genUnicode $ (E.decodeUtf8' . E.encodeUtf8) `eq` (id . Right)
74tl_utf8 = forAll genUnicode $ (EL.decodeUtf8 . EL.encodeUtf8) `eq` id
85tl_utf8 = forAll genUnicode $ (EL.decodeUtf8 . EL.encodeUtf8) `eq` id
@@ -754,6 +765,8 @@ tests =
754 testGroup "transcoding" [
765 testGroup "transcoding" [
755 testProperty "t_ascii" t_ascii,
766 testProperty "t_ascii" t_ascii,
756 testProperty "tl_ascii" tl_ascii,
767 testProperty "tl_ascii" tl_ascii,
768+ testProperty "t_latin1" t_latin1,
769+ testProperty "tl_latin1" tl_latin1,
757 testProperty "t_utf8" t_utf8,
770 testProperty "t_utf8" t_utf8,
758 testProperty "t_utf8'" t_utf8',
771 testProperty "t_utf8'" t_utf8',
759 testProperty "tl_utf8" tl_utf8,
772 testProperty "tl_utf8" tl_utf8,
tests/Tests/Regressions.hs+12−1
@@ -8,10 +8,12 @@ module Tests.Regressions
8
8
9import Control.Exception (SomeException, handle)
9import Control.Exception (SomeException, handle)
10import System.IO
10import System.IO
11−import Test.HUnit (assertFailure)
11+import Test.HUnit (assertBool, assertFailure)
12import qualified Data.ByteString as B
12import qualified Data.ByteString as B
13+import Data.ByteString.Char8 ()
13import qualified Data.ByteString.Lazy as LB
14import qualified Data.ByteString.Lazy as LB
14import qualified Data.Text as T
15import qualified Data.Text as T
16+import qualified Data.Text.Encoding as TE
15import qualified Data.Text.IO as T
17import qualified Data.Text.IO as T
16import qualified Data.Text.Lazy as LT
18import qualified Data.Text.Lazy as LT
17import qualified Data.Text.Lazy.Encoding as LE
19import qualified Data.Text.Lazy.Encoding as LE
@@ -49,9 +51,18 @@ replicate_crash = handle (\(_::SomeException) -> return ()) $
49 power | maxBound == (2147483647::Int) = 28
51 power | maxBound == (2147483647::Int) = 28
50 | otherwise = 60 :: Int
52 | otherwise = 60 :: Int
51
53
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+
52tests :: F.Test
62tests :: F.Test
53tests = F.testGroup "Regressions"
63tests = F.testGroup "Regressions"
54 [ F.testCase "hGetContents_crash" hGetContents_crash
64 [ F.testCase "hGetContents_crash" hGetContents_crash
55 , F.testCase "lazy_encode_crash" lazy_encode_crash
65 , F.testCase "lazy_encode_crash" lazy_encode_crash
56 , F.testCase "replicate_crash" replicate_crash
66 , F.testCase "replicate_crash" replicate_crash
67+ , F.testCase "utf8_decode_unsafe" utf8_decode_unsafe
57 ]
68 ]
tests/text-tests.cabal+2
@@ -87,6 +87,8 @@ library
87 Data.Text.Lazy.Builder
87 Data.Text.Lazy.Builder
88 Data.Text.Lazy.Builder.Functions
88 Data.Text.Lazy.Builder.Functions
89 Data.Text.Lazy.Builder.Int
89 Data.Text.Lazy.Builder.Int
90+ Data.Text.Lazy.Builder.Int.Digits
91+ Data.Text.Lazy.Builder.Internal
90 Data.Text.Lazy.Builder.RealFloat
92 Data.Text.Lazy.Builder.RealFloat
91 Data.Text.Lazy.Builder.RealFloat.Functions
93 Data.Text.Lazy.Builder.RealFloat.Functions
92 Data.Text.Lazy.Encoding
94 Data.Text.Lazy.Encoding
text.cabal+20−7
@@ -1,5 +1,5 @@
1name: text
1name: text
2−version: 0.11.2.3
2+version: 0.11.2.4
3homepage: https://github.com/bos/text
3homepage: https://github.com/bos/text
4bug-reports: https://github.com/bos/text/issues
4bug-reports: https://github.com/bos/text/issues
5synopsis: An efficient packed Unicode text type.
5synopsis: An efficient packed Unicode text type.
@@ -109,6 +109,8 @@ library
109 Data.Text.Fusion.Size
109 Data.Text.Fusion.Size
110 Data.Text.IO.Internal
110 Data.Text.IO.Internal
111 Data.Text.Lazy.Builder.Functions
111 Data.Text.Lazy.Builder.Functions
112+ Data.Text.Lazy.Builder.Int.Digits
113+ Data.Text.Lazy.Builder.Internal
112 Data.Text.Lazy.Builder.RealFloat.Functions
114 Data.Text.Lazy.Builder.RealFloat.Functions
113 Data.Text.Lazy.Encoding.Fusion
115 Data.Text.Lazy.Encoding.Fusion
114 Data.Text.Lazy.Fusion
116 Data.Text.Lazy.Fusion
@@ -131,7 +133,7 @@ library
131 cpp-options: -DHAVE_DEEPSEQ
133 cpp-options: -DHAVE_DEEPSEQ
132 else
134 else
133 build-depends: extensible-exceptions
135 build-depends: extensible-exceptions
134− extensions: PatternSignatures
136+ extensions: ScopedTypeVariables
135
137
136 ghc-options: -Wall -funbox-strict-fields -O2
138 ghc-options: -Wall -funbox-strict-fields -O2
137 if impl(ghc >= 6.8)
139 if impl(ghc >= 6.8)
@@ -149,16 +151,15 @@ library
149 cpp-options: -DINTEGER_GMP
151 cpp-options: -DINTEGER_GMP
150 build-depends: integer-gmp >= 0.2
152 build-depends: integer-gmp >= 0.2
151
153
152−
153 if impl(ghc >= 6.9) && impl(ghc < 6.11)
154 if impl(ghc >= 6.9) && impl(ghc < 6.11)
154 cpp-options: -DINTEGER_GMP
155 cpp-options: -DINTEGER_GMP
155 build-depends: integer >= 0.1 && < 0.2
156 build-depends: integer >= 0.1 && < 0.2
156
157
157test-suite tests
158test-suite tests
158 type: exitcode-stdio-1.0
159 type: exitcode-stdio-1.0
159− hs-source-dirs: tests
160+ hs-source-dirs: tests .
160 main-is: Tests.hs
161 main-is: Tests.hs
161− -- c-sources: cbits/cbits.c
162+ c-sources: cbits/cbits.c
162
163
163 ghc-options:
164 ghc-options:
164 -Wall -threaded -O0 -rtsopts
165 -Wall -threaded -O0 -rtsopts
@@ -169,6 +170,7 @@ test-suite tests
169 build-depends:
170 build-depends:
170 HUnit >= 1.2,
171 HUnit >= 1.2,
171 QuickCheck >= 2.4,
172 QuickCheck >= 2.4,
173+ array,
172 base,
174 base,
173 bytestring,
175 bytestring,
174 deepseq,
176 deepseq,
@@ -177,8 +179,19 @@ test-suite tests
177 random,
179 random,
178 test-framework >= 0.4,
180 test-framework >= 0.4,
179 test-framework-hunit >= 0.2,
181 test-framework-hunit >= 0.2,
180− test-framework-quickcheck2 >= 0.2,
182+ test-framework-quickcheck2 >= 0.2
181− text
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
182
195
183source-repository head
196source-repository head
184 type: git
197 type: git