@@ -43,6 +43,10 @@ module Data.Text.Encoding
4343 , decodeUtf32LEWith
4444 , decodeUtf32BEWith
4545
46+ -- ** Streaming decoding with controllable error handling
47+ , decodeUtf8With'
48+ , Decoder(..)
49+
4650 -- * Encoding Text to ByteStrings
4751 , encodeUtf8
4852 , encodeUtf16LE
@@ -57,6 +61,7 @@ import Control.Monad.ST.Unsafe (unsafeIOToST, unsafeSTToIO)
5761#else
5862import Control.Monad.ST (unsafeIOToST, unsafeSTToIO)
5963#endif
64+import Control.Monad.ST (runST)
6065import Data.Bits ((.&.))
6166import Data.ByteString as B
6267import Data.ByteString.Internal as B
@@ -65,7 +70,7 @@ import Data.Text.Internal (Text(..), safe, textP)
6570import Data.Text.Private (runText)
6671import Data.Text.UnsafeChar (ord, unsafeWrite)
6772import Data.Text.UnsafeShift (shiftL, shiftR)
68−import Data.Word (Word8)
73+import Data.Word (Word8, Word32)
6974import Foreign.C.Types (CSize)
7075import Foreign.ForeignPtr (withForeignPtr)
7176import Foreign.Marshal.Utils (with)
@@ -139,6 +144,56 @@ decodeUtf8With onErr (PS fp off len) = runText $ \done -> do
139144 desc = "Data.Text.Encoding.decodeUtf8: Invalid UTF-8 stream"
140145{- INLINE[0] decodeUtf8With #-}
141146
147+data Decoder = Some !Text (ByteString -> Decoder)
148+
149+-- | (codepoint, state)
150+type DecoderState = (Word32, Word32)
151+
152+decodeUtf8With' :: OnDecodeError -> ByteString -> Decoder
153+decodeUtf8With' onErr = decodeChunk (0,0)
154+ where
155+ -- We create a slightly larger than necessary buffer to accomodate a
156+ -- potential surrogate pair started in the last buffer
157+ decodeChunk :: DecoderState -> ByteString -> Decoder
158+ decodeChunk (codepoint0,state0) (PS fp off len) =
159+ runST $ (unsafeIOToST . decodeChunkToBuffer) =<< A.new (len+1)
160+ where
161+ decodeChunkToBuffer :: A.MArray s -> IO Decoder
162+ decodeChunkToBuffer dest = withForeignPtr fp $ \ptr ->
163+ with (0::CSize) $ \destOffPtr ->
164+ with codepoint0 $ \codepointPtr ->
165+ with state0 $ \statePtr ->
166+ let end = ptr `plusPtr` (off + len)
167+ loop curPtr = do
168+ curPtr' <- c_decode_utf8_with_state (A.maBA dest) destOffPtr curPtr end
169+ codepointPtr statePtr
170+ state <- peek statePtr
171+ case state of
172+ 12 -> do
173+ -- We encountered an encoding error
174+ x <- peek curPtr'
175+ case onErr desc (Just x) of
176+ Nothing -> loop $ curPtr' `plusPtr` 1
177+ Just c -> do
178+ destOff <- peek destOffPtr
179+ w <- unsafeSTToIO $
180+ unsafeWrite dest (fromIntegral destOff) (safe c)
181+ poke destOffPtr (destOff + fromIntegral w)
182+ poke statePtr 0
183+ loop $ curPtr' `plusPtr` 1
184+
185+ _ -> do
186+ -- We encountered the end of the buffer while decoding
187+ n <- peek destOffPtr
188+ codepoint <- peek codepointPtr
189+ chunkText <- unsafeSTToIO $ do
190+ arr <- A.unsafeFreeze dest
191+ return $! textP arr 0 (fromIntegral n)
192+ return $ Some chunkText $ decodeChunk (codepoint, state)
193+ in loop (ptr `plusPtr` off)
194+ desc = "Data.Text.Encoding.decodeUtf8With': Invalid UTF-8 stream"
195+{- INLINE[0] decodeUtf8With' #-}
196+
142197-- | Decode a 'ByteString' containing UTF-8 encoded text that is known
143198-- to be valid.
144199--
@@ -296,5 +351,10 @@ foreign import ccall unsafe "_hs_text_decode_utf8" c_decode_utf8
296351 :: MutableByteArray# s -> Ptr CSize
297352 -> Ptr Word8 -> Ptr Word8 -> IO (Ptr Word8)
298353
354+foreign import ccall unsafe "_hs_text_decode_utf8_state" c_decode_utf8_with_state
355+ :: MutableByteArray# s -> Ptr CSize
356+ -> Ptr Word8 -> Ptr Word8
357+ -> Ptr Word32 -> Ptr Word32 -> IO (Ptr Word8)
358+
299359foreign import ccall unsafe "_hs_text_decode_latin1" c_decode_latin1
300360 :: MutableByteArray# s -> Ptr Word8 -> Ptr Word8 -> IO ()