@@ -1,5 +1,8 @@
1−{-# LANGUAGE BangPatterns, CPP, ForeignFunctionInterface, MagicHash,
1+{-# LANGUAGE BangPatterns, CPP, ForeignFunctionInterface, GeneralizedNewtypeDeriving, 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
@@ -39,17 +43,23 @@ module Data.Text.Encoding
3943 , decodeUtf32LEWith
4044 , decodeUtf32BEWith
4145
46+ -- ** Stream oriented decoding
47+ -- $stream
48+ , streamDecodeUtf8
49+ , streamDecodeUtf8With
50+ , Decoding(..)
51+
4252 -- * Encoding Text to ByteStrings
4353 , encodeUtf8
4454 , encodeUtf16LE
4555 , encodeUtf16BE
4656 , encodeUtf32LE
4757 , encodeUtf32BE
48−
58+
4959 -- * Generic encoding of Text
50− , encodeStreamWithB
51− , encodeTextWithB
52− , encodeUtf8Builder
60+ -- , encodeStreamWithB
61+ -- , encodeTextWithB
62+ -- , encodeUtf8Builder
5363 , encodeUtf8Escaped
5464 ) where
5565
@@ -59,28 +69,35 @@ import Control.Monad.ST.Unsafe (unsafeIOToST, unsafeSTToIO)
5969#else
6070import Control.Monad.ST (unsafeIOToST, unsafeSTToIO)
6171#endif
72+import Control.Monad.ST (runST)
6273import Data.Bits ((.&.))
6374import Data.ByteString as B
6475import Data.ByteString.Internal as B
65−import Data.ByteString.Lazy.Builder.Internal as B
66−import qualified Data.ByteString.Lazy.Builder.BasicEncoding.Internal as B
67−import qualified Data.ByteString.Lazy.Builder.BasicEncoding as B
76+import qualified Data.ByteString.Lazy as BL
77+import qualified Data.ByteString.Builder as B
78+import qualified Data.ByteString.Builder.Internal as B
79+import qualified Data.ByteString.Builder.Prim.Internal as BP
80+import qualified Data.ByteString.Builder.Prim as BP
81+import Data.Text ()
6882import Data.Text.Encoding.Error (OnDecodeError, UnicodeException, strictDecode)
69−import Data.Text.Internal (Text(..), textP)
83+import Data.Text.Internal (Text(..), safe, textP)
84+import Data.Text.Private (runText)
7085import Data.Text.UnsafeChar (ord, unsafeWrite)
7186import Data.Text.UnsafeShift (shiftL, shiftR)
72−import Data.Word (Word8)
87+import Data.Word (Word8, Word32)
7388import Foreign.C.Types (CSize)
7489import Foreign.ForeignPtr (withForeignPtr)
7590import Foreign.Marshal.Utils (with)
76−import Foreign.Ptr (Ptr, minusPtr, plusPtr)
77−import Foreign.Storable (peek, poke)
91+import Foreign.Ptr (Ptr, minusPtr, nullPtr, plusPtr)
92+import Foreign.Storable (Storable, peek, poke)
7893import GHC.Base (MutableByteArray#)
79−import System.IO.Unsafe (unsafePerformIO)
8094import qualified Data.Text.Array as A
8195import qualified Data.Text.Encoding.Fusion as E
8296import qualified Data.Text.Encoding.Utf16 as U16
8397import qualified Data.Text.Fusion as F
98+import Data.Text.Unsafe (unsafeDupablePerformIO)
99+
100+#include "text_cbits.h"
84101
85102-- $strict
86103--
@@ -98,39 +115,171 @@ import qualified Data.Text.Fusion as F
98115-- | /Deprecated/. Decode a 'ByteString' containing 7-bit ASCII
99116-- encoded text.
100117--
101−-- This function is deprecated. Use 'decodeUtf8' instead.
118+-- This function is deprecated. Use 'decodeLatin1' instead.
102119decodeASCII :: ByteString -> Text
103120decodeASCII = decodeUtf8
104121{-# DEPRECATED decodeASCII "Use decodeUtf8 instead" #-}
105122
123+-- | Decode a 'ByteString' containing Latin-1 (aka ISO-8859-1) encoded text.
124+--
125+-- 'decodeLatin1' is semantically equivalent to
126+-- @Data.Text.pack . Data.ByteString.Char8.unpack@
127+decodeLatin1 :: ByteString -> Text
128+decodeLatin1 (PS fp off len) = textP a 0 len
129+ where
130+ a = A.run (A.new len >>= unsafeIOToST . go)
131+ go dest = withForeignPtr fp $ \ptr -> do
132+ c_decode_latin1 (A.maBA dest) (ptr `plusPtr` off) (ptr `plusPtr` (off+len))
133+ return dest
134+
106135-- | Decode a 'ByteString' containing UTF-8 encoded text.
107136decodeUtf8With :: OnDecodeError -> ByteString -> Text
108−decodeUtf8With onErr (PS fp off len) = textP (fst a) 0 (snd a)
137+decodeUtf8With onErr (PS fp off len) = runText $ \done -> do
138+ let go dest = withForeignPtr fp $ \ptr ->
139+ with (0::CSize) $ \destOffPtr -> do
140+ let end = ptr `plusPtr` (off + len)
141+ loop curPtr = do
142+ curPtr' <- c_decode_utf8 (A.maBA dest) destOffPtr curPtr end
143+ if curPtr' == end
144+ then do
145+ n <- peek destOffPtr
146+ unsafeSTToIO (done dest (fromIntegral n))
147+ else do
148+ x <- peek curPtr'
149+ case onErr desc (Just x) of
150+ Nothing -> loop $ curPtr' `plusPtr` 1
151+ Just c -> do
152+ destOff <- peek destOffPtr
153+ w <- unsafeSTToIO $
154+ unsafeWrite dest (fromIntegral destOff) (safe c)
155+ poke destOffPtr (destOff + fromIntegral w)
156+ loop $ curPtr' `plusPtr` 1
157+ loop (ptr `plusPtr` off)
158+ (unsafeIOToST . go) =<< A.new len
109159 where
110− a = A.run2 (A.new len >>= unsafeIOToST . go)
111160 desc = "Data.Text.Encoding.decodeUtf8: Invalid UTF-8 stream"
112− go dest = withForeignPtr fp $ \ptr ->
113− with (0::CSize) $ \destOffPtr -> do
114− let end = ptr `plusPtr` (off + len)
115− loop curPtr = do
116− curPtr' <- c_decode_utf8 (A.maBA dest) destOffPtr curPtr end
117− if curPtr' == end
118− then do
119− n <- peek destOffPtr
120− return (dest,fromIntegral n)
121− else do
122− x <- peek curPtr'
123− case onErr desc (Just x) of
124− Nothing -> loop $ curPtr' `plusPtr` 1
125− Just c -> do
126− destOff <- peek destOffPtr
127− w <- unsafeSTToIO $
128− unsafeWrite dest (fromIntegral destOff) c
129− poke destOffPtr (destOff + fromIntegral w)
130− loop $ curPtr' `plusPtr` 1
131− loop (ptr `plusPtr` off)
132161{- INLINE[0] decodeUtf8With #-}
133162
163+-- $stream
164+--
165+-- The 'streamDecodeUtf8' and 'streamDecodeUtf8With' functions accept
166+-- a 'ByteString' that represents a possibly incomplete input (e.g. a
167+-- packet from a network stream) that may not end on a UTF-8 boundary.
168+--
169+-- The first element of the result is the maximal chunk of 'Text' that
170+-- can be decoded from the given input. The second is a function which
171+-- accepts another 'ByteString'. That string will be assumed to
172+-- directly follow the string that was passed as input to the original
173+-- function, and it will in turn be decoded.
174+--
175+-- To help understand the use of these functions, consider the Unicode
176+-- string @\"hi ☃\"@. If encoded as UTF-8, this becomes @\"hi
177+-- \\xe2\\x98\\x83\"@; the final @\'☃\'@ is encoded as 3 bytes.
178+--
179+-- Now suppose that we receive this encoded string as 3 packets that
180+-- are split up on untidy boundaries: @[\"hi \\xe2\", \"\\x98\",
181+-- \"\\x83\"]@. We cannot decode the entire Unicode string until we
182+-- have received all three packets, but we would like to make progress
183+-- as we receive each one.
184+--
185+-- @
186+-- let 'Some' t0 f0 = 'streamDecodeUtf8' \"hi \\xe2\"
187+-- t0 == \"hi \" :: 'Text'
188+-- @
189+--
190+-- We use the continuation @f0@ to decode our second packet.
191+--
192+-- @
193+-- let 'Some' t1 f1 = f0 \"\\x98\"
194+-- t1 == \"\"
195+-- @
196+--
197+-- We could not give @f0@ enough input to decode anything, so it
198+-- returned an empty string. Once we feed our second continuation @f1@
199+-- the last byte of input, it will make progress.
200+--
201+-- @
202+-- let 'Some' t2 f2 = f1 \"\\x83\"
203+-- t2 == \"☃\"
204+-- @
205+--
206+-- If given invalid input, an exception will be thrown by the function
207+-- or continuation where it is encountered.
208+
209+-- | A stream oriented decoding result.
210+data Decoding = Some Text ByteString (ByteString -> Decoding)
211+
212+instance Show Decoding where
213+ showsPrec d (Some t bs _) = showParen (d > prec) $
214+ showString "Some " . showsPrec prec' t .
215+ showChar ' ' . showsPrec prec' bs .
216+ showString " _"
217+ where prec = 10; prec' = prec + 1
218+
219+newtype CodePoint = CodePoint Word32 deriving (Eq, Show, Num, Storable)
220+newtype DecoderState = DecoderState Word32 deriving (Eq, Show, Num, Storable)
221+
222+-- | Decode, in a stream oriented way, a 'ByteString' containing UTF-8
223+-- encoded text that is known to be valid.
224+--
225+-- If the input contains any invalid UTF-8 data, an exception will be
226+-- thrown (either by this function or a continuation) that cannot be
227+-- caught in pure code. For more control over the handling of invalid
228+-- data, use 'streamDecodeUtf8With'.
229+streamDecodeUtf8 :: ByteString -> Decoding
230+streamDecodeUtf8 = streamDecodeUtf8With strictDecode
231+
232+-- | Decode, in a stream oriented way, a 'ByteString' containing UTF-8
233+-- encoded text.
234+streamDecodeUtf8With :: OnDecodeError -> ByteString -> Decoding
235+streamDecodeUtf8With onErr = decodeChunk 0 0
236+ where
237+ -- We create a slightly larger than necessary buffer to accommodate a
238+ -- potential surrogate pair started in the last buffer
239+ decodeChunk :: CodePoint -> DecoderState -> ByteString -> Decoding
240+ decodeChunk codepoint0 state0 bs@(PS fp off len) =
241+ runST $ (unsafeIOToST . decodeChunkToBuffer) =<< A.new (len+1)
242+ where
243+ decodeChunkToBuffer :: A.MArray s -> IO Decoding
244+ decodeChunkToBuffer dest = withForeignPtr fp $ \ptr ->
245+ with (0::CSize) $ \destOffPtr ->
246+ with codepoint0 $ \codepointPtr ->
247+ with state0 $ \statePtr ->
248+ with nullPtr $ \curPtrPtr ->
249+ let end = ptr `plusPtr` (off + len)
250+ loop curPtr = do
251+ poke curPtrPtr curPtr
252+ curPtr' <- c_decode_utf8_with_state (A.maBA dest) destOffPtr
253+ curPtrPtr end codepointPtr statePtr
254+ state <- peek statePtr
255+ case state of
256+ UTF8_REJECT -> do
257+ -- We encountered an encoding error
258+ x <- peek curPtr'
259+ case onErr desc (Just x) of
260+ Nothing -> loop $ curPtr' `plusPtr` 1
261+ Just c -> do
262+ destOff <- peek destOffPtr
263+ w <- unsafeSTToIO $
264+ unsafeWrite dest (fromIntegral destOff) (safe c)
265+ poke destOffPtr (destOff + fromIntegral w)
266+ poke statePtr 0
267+ loop $ curPtr' `plusPtr` 1
268+
269+ _ -> do
270+ -- We encountered the end of the buffer while decoding
271+ n <- peek destOffPtr
272+ codepoint <- peek codepointPtr
273+ chunkText <- unsafeSTToIO $ do
274+ arr <- A.unsafeFreeze dest
275+ return $! textP arr 0 (fromIntegral n)
276+ lastPtr <- peek curPtrPtr
277+ let left = lastPtr `minusPtr` curPtr
278+ return $ Some chunkText (B.drop left bs)
279+ (decodeChunk codepoint state)
280+ in loop (ptr `plusPtr` off)
281+ desc = "Data.Text.Encoding.streamDecodeUtf8With: Invalid UTF-8 stream"
282+
134283-- | Decode a 'ByteString' containing UTF-8 encoded text that is known
135284-- to be valid.
136285--
@@ -144,17 +293,22 @@ decodeUtf8 = decodeUtf8With strictDecode
144293{-# RULES "STREAM stream/decodeUtf8 fusion" [1]
145294 forall bs. F.stream (decodeUtf8 bs) = E.streamUtf8 strictDecode bs #-}
146295
147−-- | Decode a 'ByteString' containing UTF-8 encoded text..
296+-- | Decode a 'ByteString' containing UTF-8 encoded text.
148297--
149298-- If the input contains any invalid UTF-8 data, the relevant
150299-- exception will be returned, otherwise the decoded text.
151300decodeUtf8' :: ByteString -> Either UnicodeException Text
152−decodeUtf8' = unsafePerformIO . try . evaluate . decodeUtf8With strictDecode
301+decodeUtf8' = unsafeDupablePerformIO . try . evaluate . decodeUtf8With strictDecode
153302{-# INLINE decodeUtf8' #-}
154303
155304-- | Encode text using UTF-8 encoding.
156305encodeUtf8 :: Text -> ByteString
157−encodeUtf8 (Text arr off len) = unsafePerformIO $ do
306+encodeUtf8 =
307+ BL.toStrict . B.toLazyByteString
308+ . encodeUtf8Escaped (BP.liftFixedToBounded BP.word8)
309+
310+{-
311+encodeUtf8 (Text arr off len) = unsafeDupablePerformIO $ do
158312 let size0 = max len 4
159313 mallocByteString size0 >>= start size0 off 0
160314 where
@@ -207,6 +361,55 @@ encodeUtf8 (Text arr off len) = unsafePerformIO $ do
207361 poke8 (m+1) $ ((w `shiftR` 6) .&. 0x3F) + 0x80
208362 poke8 (m+2) $ (w .&. 0x3F) + 0x80
209363 go (n+1) (m+3)
364+-}
365+
366+-- | Encode text using UTF-8 encoding and escape the ASCII characters using
367+-- a 'BP.PrimBounded'.
368+encodeUtf8Escaped :: BP.BoundedPrim Word8 -> Text -> B.Builder
369+encodeUtf8Escaped be (Text arr off len) =
370+ B.builder step
371+ where
372+ bound = max 4 $ BP.sizeBound be
373+ iend = off + len
374+ step !k =
375+ outerLoop off
376+ where
377+ outerLoop !i0 !br@(B.BufferRange op0 ope)
378+ | i0 >= iend = k br
379+ | op0 `plusPtr` bound < ope =
380+ goPartial (i0 + min outRemaining inpRemaining)
381+ | otherwise = return $ B.bufferFull bound op0 (outerLoop i0)
382+ where
383+ outRemaining = (ope `minusPtr` op0) `div` bound
384+ inpRemaining = iend - i0
385+
386+ goPartial !iendTmp = go i0 op0
387+ where
388+ go !i !op
389+ | i < iendTmp = case A.unsafeIndex arr i of
390+ w | w <= 0x7F -> do
391+ BP.runB be (fromIntegral w) op >>= go (i + 1)
392+ | w <= 0x7FF -> do
393+ poke8 0 $ (w `shiftR` 6) + 0xC0
394+ poke8 1 $ (w .&. 0x3f) + 0x80
395+ go (i + 1) (op `plusPtr` 2)
396+ | 0xD800 <= w && w <= 0xDBFF -> do
397+ let c = ord $ U16.chr2 w (A.unsafeIndex arr (i+1))
398+ poke8 0 $ (c `shiftR` 18) + 0xF0
399+ poke8 1 $ ((c `shiftR` 12) .&. 0x3F) + 0x80
400+ poke8 2 $ ((c `shiftR` 6) .&. 0x3F) + 0x80
401+ poke8 3 $ (c .&. 0x3F) + 0x80
402+ go (i + 2) (op `plusPtr` 4)
403+ | otherwise -> do
404+ poke8 0 $ (w `shiftR` 12) + 0xE0
405+ poke8 1 $ ((w `shiftR` 6) .&. 0x3F) + 0x80
406+ poke8 2 $ (w .&. 0x3F) + 0x80
407+ go (i + 1) (op `plusPtr` 3)
408+ | otherwise =
409+ outerLoop i (B.BufferRange op ope)
410+ where
411+ poke8 j v = poke (op `plusPtr` j) (fromIntegral v :: Word8)
412+
210413
211414-- | Decode text from little endian UTF-16 encoding.
212415decodeUtf16LEWith :: OnDecodeError -> ByteString -> Text
@@ -288,14 +491,24 @@ foreign import ccall unsafe "_hs_text_decode_utf8" c_decode_utf8
288491 :: MutableByteArray# s -> Ptr CSize
289492 -> Ptr Word8 -> Ptr Word8 -> IO (Ptr Word8)
290493
494+foreign import ccall unsafe "_hs_text_decode_utf8_state" c_decode_utf8_with_state
495+ :: MutableByteArray# s -> Ptr CSize
496+ -> Ptr (Ptr Word8) -> Ptr Word8
497+ -> Ptr CodePoint -> Ptr DecoderState -> IO (Ptr Word8)
498+
499+foreign import ccall unsafe "_hs_text_decode_latin1" c_decode_latin1
500+ :: MutableByteArray# s -> Ptr Word8 -> Ptr Word8 -> IO ()
501+
502+
503+{-
291504-- | Encode all elements of a 'F.Stream' using a 'B.BoundedEncoding'.
292505{-# INLINE encodeStreamWithB #-}
293506encodeStreamWithB :: B.BoundedEncoding a -> F.Stream a -> B.Builder
294−encodeStreamWithB be =
507+encodeStreamWithB be =
295508 \(F.Stream next s0 _) -> B.builder $ step next s0
296509 where
297510 bound = B.sizeBound be
298− step next s0 k (B.BufferRange op0 ope0) =
511+ step next s0 k (B.BufferRange op0 ope0) =
299512 go s0 op0
300513 where
301514 go s !op = case next s of
@@ -303,11 +516,11 @@ encodeStreamWithB be =
303516 F.Skip s' -> go s' op
304517 F.Yield x s'
305518 | op `plusPtr` bound <= ope0 -> B.runB be x op >>= go s'
306− | otherwise ->
519+ | otherwise ->
307520 return $ B.bufferFull bound op (step next s k)
308521
309522
310−-- |
523+-- |
311524-- | /Subject to fusion./
312525-- Encode all 'Char's of a 'T.Text' using a 'B.BoundedEncoding'.
313526{-# INLINE encodeTextWithB #-}
@@ -317,51 +530,6 @@ encodeTextWithB be = encodeStreamWithB be . F.stream
317530-- | Encode text using UTF-8 encoding.
318531encodeUtf8Builder :: Text -> B.Builder
319532encodeUtf8Builder = encodeUtf8Escaped (B.fromF B.word8)
533+-}
320534
321−-- | Encode text using UTF-8 encoding and escape the ASCII characters using
322−-- a 'BoundedEncoding'.
323−encodeUtf8Escaped :: B.BoundedEncoding Word8 -> Text -> B.Builder
324−encodeUtf8Escaped be (Text arr off len) =
325− B.builder step
326− where
327− bound = max 4 $ B.sizeBound be
328− iend = off + len
329− step !k =
330− outerLoop off
331− where
332− outerLoop !i0 !br@(BufferRange op0 ope)
333− | i0 >= iend = k br
334− | op0 `plusPtr` bound < ope =
335− goPartial (i0 + min outRemaining inpRemaining)
336− | otherwise = return $ bufferFull bound op0 (outerLoop i0)
337− where
338− outRemaining = (ope `minusPtr` op0) `div` bound
339− inpRemaining = iend - i0
340−
341− goPartial !iendTmp = go i0 op0
342− where
343− go !i !op
344− | i < iendTmp = case A.unsafeIndex arr i of
345− w | w <= 0x7F -> do
346− B.runB be (fromIntegral w) op >>= go (i + 1)
347− | w <= 0x7FF -> do
348− poke8 0 $ (w `shiftR` 6) + 0xC0
349− poke8 1 $ (w .&. 0x3f) + 0x80
350− go (i + 1) (op `plusPtr` 2)
351− | 0xD800 <= w && w <= 0xDBFF -> do
352− let c = ord $ U16.chr2 w (A.unsafeIndex arr (i+1))
353− poke8 0 $ (c `shiftR` 18) + 0xF0
354− poke8 1 $ ((c `shiftR` 12) .&. 0x3F) + 0x80
355− poke8 2 $ ((c `shiftR` 6) .&. 0x3F) + 0x80
356− poke8 3 $ (c .&. 0x3F) + 0x80
357− go (i + 2) (op `plusPtr` 4)
358− | otherwise -> do
359− poke8 0 $ (w `shiftR` 12) + 0xE0
360− poke8 1 $ ((w `shiftR` 6) .&. 0x3F) + 0x80
361− poke8 2 $ (w .&. 0x3F) + 0x80
362− go (i + 1) (op `plusPtr` 3)
363− | otherwise =
364− outerLoop i (BufferRange op ope)
365− where
366− poke8 j v = poke (op `plusPtr` j) (fromIntegral v :: Word8)
367535