@@ -1,5 +1,8 @@
1−{-# LANGUAGE BangPatterns, CPP, ForeignFunctionInterface, MagicHash,
1+{-# LANGUAGE BangPatterns, CPP, ForeignFunctionInterface, GeneralizedNewtypeDeriving, MagicHash,
2 UnliftedFFITypes #-}
2 UnliftedFFITypes #-}
3+#if __GLASGOW_HASKELL__ >= 702
4+{-# LANGUAGE Trustworthy #-}
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
26 , decodeUtf8
30 , decodeUtf8
27 , decodeUtf16LE
31 , decodeUtf16LE
28 , decodeUtf16BE
32 , decodeUtf16BE
@@ -39,17 +43,23 @@ module Data.Text.Encoding
39 , decodeUtf32LEWith
43 , decodeUtf32LEWith
40 , decodeUtf32BEWith
44 , decodeUtf32BEWith
46+ -- ** Stream oriented decoding
49+ , streamDecodeUtf8With
42 -- * Encoding Text to ByteStrings
52 -- * Encoding Text to ByteStrings
43 , encodeUtf8
53 , encodeUtf8
44 , encodeUtf16LE
54 , encodeUtf16LE
45 , encodeUtf16BE
55 , encodeUtf16BE
46 , encodeUtf32LE
56 , encodeUtf32LE
47 , encodeUtf32BE
57 , encodeUtf32BE
49 -- * Generic encoding of Text
59 -- * Generic encoding of Text
50− , encodeStreamWithB
60+ -- , encodeStreamWithB
51− , encodeTextWithB
61+ -- , encodeTextWithB
52− , encodeUtf8Builder
62+ -- , encodeUtf8Builder
53 , encodeUtf8Escaped
63 , encodeUtf8Escaped
@@ -59,28 +69,35 @@ import Control.Monad.ST.Unsafe (unsafeIOToST, unsafeSTToIO)
60import Control.Monad.ST (unsafeIOToST, unsafeSTToIO)
70import Control.Monad.ST (unsafeIOToST, unsafeSTToIO)
72+import Control.Monad.ST (runST)
62import Data.Bits ((.&.))
73import Data.Bits ((.&.))
63import Data.ByteString as B
74import Data.ByteString as B
64import Data.ByteString.Internal as B
75import Data.ByteString.Internal as B
65−import Data.ByteString.Lazy.Builder.Internal 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
66−import qualified Data.ByteString.Lazy.Builder.BasicEncoding.Internal as B
79+import qualified Data.ByteString.Builder.Prim.Internal as BP
67−import qualified Data.ByteString.Lazy.Builder.BasicEncoding as B
80+import qualified Data.ByteString.Builder.Prim as BP
68import Data.Text.Encoding.Error (OnDecodeError, UnicodeException, strictDecode)
82import 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)
70import Data.Text.UnsafeChar (ord, unsafeWrite)
85import Data.Text.UnsafeChar (ord, unsafeWrite)
71import Data.Text.UnsafeShift (shiftL, shiftR)
86import Data.Text.UnsafeShift (shiftL, shiftR)
72−import Data.Word (Word8)
87+import Data.Word (Word8, Word32)
73import Foreign.C.Types (CSize)
88import Foreign.C.Types (CSize)
74import Foreign.ForeignPtr (withForeignPtr)
89import Foreign.ForeignPtr (withForeignPtr)
75import Foreign.Marshal.Utils (with)
90import Foreign.Marshal.Utils (with)
76−import Foreign.Ptr (Ptr, minusPtr, plusPtr)
91+import Foreign.Ptr (Ptr, minusPtr, nullPtr, plusPtr)
77−import Foreign.Storable (peek, poke)
92+import Foreign.Storable (Storable, peek, poke)
78import GHC.Base (MutableByteArray#)
93import GHC.Base (MutableByteArray#)
79−import System.IO.Unsafe (unsafePerformIO)
80import qualified Data.Text.Array as A
94import qualified Data.Text.Array as A
81import qualified Data.Text.Encoding.Fusion as E
95import qualified Data.Text.Encoding.Fusion as E
82import qualified Data.Text.Encoding.Utf16 as U16
96import qualified Data.Text.Encoding.Utf16 as U16
83import qualified Data.Text.Fusion as F
97import qualified Data.Text.Fusion as F
98+import Data.Text.Unsafe (unsafeDupablePerformIO)
100+#include "text_cbits.h"
85-- $strict
102-- $strict
@@ -98,39 +115,171 @@ import qualified Data.Text.Fusion as F
98-- | /Deprecated/. Decode a 'ByteString' containing 7-bit ASCII
115-- | /Deprecated/. Decode a 'ByteString' containing 7-bit ASCII
99-- encoded text.
116-- encoded text.
101−-- This function is deprecated. Use 'decodeUtf8' instead.
118+-- This function is deprecated. Use 'decodeLatin1' instead.
102decodeASCII :: ByteString -> Text
119decodeASCII :: ByteString -> Text
103decodeASCII = decodeUtf8
120decodeASCII = decodeUtf8
104{-# DEPRECATED decodeASCII "Use decodeUtf8 instead" #-}
121{-# DEPRECATED decodeASCII "Use decodeUtf8 instead" #-}
123+-- | Decode a 'ByteString' containing Latin-1 (aka ISO-8859-1) encoded text.
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
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))
106-- | Decode a 'ByteString' containing UTF-8 encoded text.
135-- | Decode a 'ByteString' containing UTF-8 encoded text.
107decodeUtf8With :: OnDecodeError -> ByteString -> Text
136decodeUtf8With :: 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)
142+ curPtr' <- c_decode_utf8 (A.maBA dest) destOffPtr curPtr end
145+ n <- peek destOffPtr
146+ unsafeSTToIO (done dest (fromIntegral n))
149+ case onErr desc (Just x) of
150+ Nothing -> loop $ curPtr' `plusPtr` 1
152+ destOff <- peek destOffPtr
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
110− a = A.run2 (A.new len >>= unsafeIOToST . go)
111 desc = "Data.Text.Encoding.decodeUtf8: Invalid UTF-8 stream"
160 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)
116− curPtr' <- c_decode_utf8 (A.maBA dest) destOffPtr curPtr end
119− n <- peek destOffPtr
120− return (dest,fromIntegral n)
123− case onErr desc (Just x) of
124− Nothing -> loop $ curPtr' `plusPtr` 1
126− destOff <- peek destOffPtr
128− unsafeWrite dest (fromIntegral destOff) c
129− poke destOffPtr (destOff + fromIntegral w)
130− loop $ curPtr' `plusPtr` 1
131− loop (ptr `plusPtr` off)
132{- INLINE[0] decodeUtf8With #-}
161{- INLINE[0] decodeUtf8With #-}
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.
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.
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.
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.
186+-- let 'Some' t0 f0 = 'streamDecodeUtf8' \"hi \\xe2\"
187+-- t0 == \"hi \" :: 'Text'
190+-- We use the continuation @f0@ to decode our second packet.
193+-- let 'Some' t1 f1 = f0 \"\\x98\"
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.
202+-- let 'Some' t2 f2 = f1 \"\\x83\"
206+-- If given invalid input, an exception will be thrown by the function
207+-- or continuation where it is encountered.
209+-- | A stream oriented decoding result.
210+data Decoding = Some Text ByteString (ByteString -> Decoding)
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 .
217+ where prec = 10; prec' = prec + 1
219+newtype CodePoint = CodePoint Word32 deriving (Eq, Show, Num, Storable)
220+newtype DecoderState = DecoderState Word32 deriving (Eq, Show, Num, Storable)
222+-- | Decode, in a stream oriented way, a 'ByteString' containing UTF-8
223+-- encoded text that is known to be valid.
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
232+-- | Decode, in a stream oriented way, a 'ByteString' containing UTF-8
234+streamDecodeUtf8With :: OnDecodeError -> ByteString -> Decoding
235+streamDecodeUtf8With onErr = decodeChunk 0 0
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)
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)
251+ poke curPtrPtr curPtr
252+ curPtr' <- c_decode_utf8_with_state (A.maBA dest) destOffPtr
253+ curPtrPtr end codepointPtr statePtr
254+ state <- peek statePtr
257+ -- We encountered an encoding error
259+ case onErr desc (Just x) of
260+ Nothing -> loop $ curPtr' `plusPtr` 1
262+ destOff <- peek destOffPtr
264+ unsafeWrite dest (fromIntegral destOff) (safe c)
265+ poke destOffPtr (destOff + fromIntegral w)
267+ loop $ curPtr' `plusPtr` 1
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"
134-- | Decode a 'ByteString' containing UTF-8 encoded text that is known
283-- | Decode a 'ByteString' containing UTF-8 encoded text that is known
135-- to be valid.
284-- to be valid.
@@ -144,17 +293,22 @@ decodeUtf8 = decodeUtf8With strictDecode
144{-# RULES "STREAM stream/decodeUtf8 fusion" [1]
293{-# RULES "STREAM stream/decodeUtf8 fusion" [1]
145 forall bs. F.stream (decodeUtf8 bs) = E.streamUtf8 strictDecode bs #-}
294 forall bs. F.stream (decodeUtf8 bs) = E.streamUtf8 strictDecode bs #-}
147−-- | Decode a 'ByteString' containing UTF-8 encoded text..
296+-- | Decode a 'ByteString' containing UTF-8 encoded text.
149-- If the input contains any invalid UTF-8 data, the relevant
298-- If the input contains any invalid UTF-8 data, the relevant
150-- exception will be returned, otherwise the decoded text.
299-- exception will be returned, otherwise the decoded text.
151decodeUtf8' :: ByteString -> Either UnicodeException Text
300decodeUtf8' :: ByteString -> Either UnicodeException Text
152−decodeUtf8' = unsafePerformIO . try . evaluate . decodeUtf8With strictDecode
301+decodeUtf8' = unsafeDupablePerformIO . try . evaluate . decodeUtf8With strictDecode
153{-# INLINE decodeUtf8' #-}
302{-# INLINE decodeUtf8' #-}
155-- | Encode text using UTF-8 encoding.
304-- | Encode text using UTF-8 encoding.
156encodeUtf8 :: Text -> ByteString
305encodeUtf8 :: Text -> ByteString
157−encodeUtf8 (Text arr off len) = unsafePerformIO $ do
306+encodeUtf8 =
307+ BL.toStrict . B.toLazyByteString
308+ . encodeUtf8Escaped (BP.liftFixedToBounded BP.word8)
311+encodeUtf8 (Text arr off len) = unsafeDupablePerformIO $ do
158 let size0 = max len 4
312 let size0 = max len 4
159 mallocByteString size0 >>= start size0 off 0
313 mallocByteString size0 >>= start size0 off 0
@@ -207,6 +361,55 @@ encodeUtf8 (Text arr off len) = unsafePerformIO $ do
207 poke8 (m+1) $ ((w `shiftR` 6) .&. 0x3F) + 0x80
361 poke8 (m+1) $ ((w `shiftR` 6) .&. 0x3F) + 0x80
208 poke8 (m+2) $ (w .&. 0x3F) + 0x80
362 poke8 (m+2) $ (w .&. 0x3F) + 0x80
209 go (n+1) (m+3)
363 go (n+1) (m+3)
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) =
372+ bound = max 4 $ BP.sizeBound be
377+ outerLoop !i0 !br@(B.BufferRange op0 ope)
379+ | op0 `plusPtr` bound < ope =
380+ goPartial (i0 + min outRemaining inpRemaining)
381+ | otherwise = return $ B.bufferFull bound op0 (outerLoop i0)
383+ outRemaining = (ope `minusPtr` op0) `div` bound
384+ inpRemaining = iend - i0
386+ goPartial !iendTmp = go i0 op0
389+ | i < iendTmp = case A.unsafeIndex arr i of
391+ BP.runB be (fromIntegral w) op >>= go (i + 1)
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)
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)
409+ outerLoop i (B.BufferRange op ope)
411+ poke8 j v = poke (op `plusPtr` j) (fromIntegral v :: Word8)
211-- | Decode text from little endian UTF-16 encoding.
414-- | Decode text from little endian UTF-16 encoding.
212decodeUtf16LEWith :: OnDecodeError -> ByteString -> Text
415decodeUtf16LEWith :: OnDecodeError -> ByteString -> Text
@@ -288,14 +491,24 @@ foreign import ccall unsafe "_hs_text_decode_utf8" c_decode_utf8
288 :: MutableByteArray# s -> Ptr CSize
491 :: MutableByteArray# s -> Ptr CSize
289 -> Ptr Word8 -> Ptr Word8 -> IO (Ptr Word8)
492 -> Ptr Word8 -> Ptr Word8 -> IO (Ptr Word8)
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)
499+foreign import ccall unsafe "_hs_text_decode_latin1" c_decode_latin1
500+ :: MutableByteArray# s -> Ptr Word8 -> Ptr Word8 -> IO ()
291-- | Encode all elements of a 'F.Stream' using a 'B.BoundedEncoding'.
504-- | Encode all elements of a 'F.Stream' using a 'B.BoundedEncoding'.
292{-# INLINE encodeStreamWithB #-}
505{-# INLINE encodeStreamWithB #-}
293encodeStreamWithB :: B.BoundedEncoding a -> F.Stream a -> B.Builder
506encodeStreamWithB :: B.BoundedEncoding a -> F.Stream a -> B.Builder
294−encodeStreamWithB be =
507+encodeStreamWithB be =
295 \(F.Stream next s0 _) -> B.builder $ step next s0
508 \(F.Stream next s0 _) -> B.builder $ step next s0
297 bound = B.sizeBound be
510 bound = B.sizeBound be
298− step next s0 k (B.BufferRange op0 ope0) =
511+ step next s0 k (B.BufferRange op0 ope0) =
299 go s0 op0
512 go s0 op0
301 go s !op = case next s of
514 go s !op = case next s of
@@ -303,11 +516,11 @@ encodeStreamWithB be =
303 F.Skip s' -> go s' op
516 F.Skip s' -> go s' op
304 F.Yield x s'
517 F.Yield x s'
305 | op `plusPtr` bound <= ope0 -> B.runB be x op >>= go s'
518 | op `plusPtr` bound <= ope0 -> B.runB be x op >>= go s'
306− | otherwise ->
519+ | otherwise ->
307 return $ B.bufferFull bound op (step next s k)
520 return $ B.bufferFull bound op (step next s k)
311-- | /Subject to fusion./
524-- | /Subject to fusion./
312-- Encode all 'Char's of a 'T.Text' using a 'B.BoundedEncoding'.
525-- Encode all 'Char's of a 'T.Text' using a 'B.BoundedEncoding'.
313{-# INLINE encodeTextWithB #-}
526{-# INLINE encodeTextWithB #-}
@@ -317,51 +530,6 @@ encodeTextWithB be = encodeStreamWithB be . F.stream
317-- | Encode text using UTF-8 encoding.
530-- | Encode text using UTF-8 encoding.
318encodeUtf8Builder :: Text -> B.Builder
531encodeUtf8Builder :: Text -> B.Builder
319encodeUtf8Builder = encodeUtf8Escaped (B.fromF B.word8)
532encodeUtf8Builder = encodeUtf8Escaped (B.fromF B.word8)
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) =
327− bound = max 4 $ B.sizeBound be
332− outerLoop !i0 !br@(BufferRange op0 ope)
334− | op0 `plusPtr` bound < ope =
335− goPartial (i0 + min outRemaining inpRemaining)
336− | otherwise = return $ bufferFull bound op0 (outerLoop i0)
338− outRemaining = (ope `minusPtr` op0) `div` bound
339− inpRemaining = iend - i0
341− goPartial !iendTmp = go i0 op0
344− | i < iendTmp = case A.unsafeIndex arr i of
346− B.runB be (fromIntegral w) op >>= go (i + 1)
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)
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)
364− outerLoop i (BufferRange op ope)
366− poke8 j v = poke (op `plusPtr` j) (fromIntegral v :: Word8)