@@ -1,5 +1,10 @@
1{-# OPTIONS_HADDOCK hide #-}
1{-# OPTIONS_HADDOCK hide #-}
2+{-# LANGUAGE BangPatterns #-}
2{-# LANGUAGE FlexibleContexts #-}
4{-# LANGUAGE FlexibleContexts #-}
5+{-# LANGUAGE PatternSynonyms #-}
6+{-# LANGUAGE RankNTypes #-}
7+{-# LANGUAGE RecordWildCards #-}
5-- Module : Streamly.Data.Internal.Unicode.Stream
10-- Module : Streamly.Data.Internal.Unicode.Stream
@@ -10,15 +15,18 @@
10-- Stability : experimental
15-- Stability : experimental
11-- Portability : GHC
16-- Portability : GHC
13module Streamly.Internal.Data.Unicode.Stream
21module Streamly.Internal.Data.Unicode.Stream
15 -- * Construction (Decoding)
23 -- * Construction (Decoding)
16 decodeLatin1
24 decodeLatin1
17 , decodeUtf8
25 , decodeUtf8
18 , decodeUtf8Lax
26 , decodeUtf8Lax
19− , D.DecodeError(..)
27+ , DecodeError(..)
20− , D.DecodeState
28+ , DecodeState
21− , D.CodePoint
29+ , CodePoint
22 , decodeUtf8Either
30 , decodeUtf8Either
23 , resumeDecodeUtf8Either
31 , resumeDecodeUtf8Either
24 , decodeUtf8Arrays
32 , decodeUtf8Arrays
@@ -33,6 +41,16 @@ module Streamly.Internal.Data.Unicode.Stream
33 , strip -- (dropAround isSpace)
41 , strip -- (dropAround isSpace)
34 , stripEnd
42 , stripEnd
45+ -- * StreamD UTF8 Encoding / Decoding transformations.
50+ , resumeDecodeUtf8EitherD
52+ , decodeUtf8ArraysLenientD
36 -- * Transformation
54 -- * Transformation
37 , stripStart
55 , stripStart
@@ -42,23 +60,519 @@ module Streamly.Internal.Data.Unicode.Stream
45−import Control.Monad.IO.Class (MonadIO)
63+import Control.Monad.IO.Class (MonadIO, liftIO)
64+import Data.Bits (shiftR, shiftL, (.|.), (.&.))
46import Data.Char (ord)
65import Data.Char (ord)
47import Data.Word (Word8)
66import Data.Word (Word8)
67+import Foreign.ForeignPtr (touchForeignPtr)
68+import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
69+import Foreign.Storable (Storable(..))
48−import GHC.Base (unsafeChr)
70+import GHC.Base (assert, unsafeChr)
49−import Streamly (IsStream)
71+import GHC.ForeignPtr (ForeignPtr (..))
72+import GHC.IO.Encoding.Failure (isSurrogate)
73+import GHC.Ptr (Ptr (..), plusPtr)
50import Prelude hiding (String, lines, words, unlines, unwords)
74import Prelude hiding (String, lines, words, unlines, unwords)
75+import System.IO.Unsafe (unsafePerformIO)
77+import Streamly (IsStream)
51import Streamly.Data.Fold (Fold)
78import Streamly.Data.Fold (Fold)
52import Streamly.Memory.Array (Array)
79import Streamly.Memory.Array (Array)
53import Streamly.Internal.Data.Unfold (Unfold)
80import Streamly.Internal.Data.Unfold (Unfold)
81+import Streamly.Internal.Data.SVar (adaptState)
82+import Streamly.Internal.Data.Stream.StreamD (Stream(..), Step (..))
83+import Streamly.Internal.Data.Strict (Tuple'(..))
85+#if __GLASGOW_HASKELL__ < 800
86+import Streamly.Internal.Data.Stream.StreamD (pattern Stream)
89+import qualified Streamly.Internal.Memory.Array.Types as A
55import qualified Streamly.Internal.Prelude as S
90import qualified Streamly.Internal.Prelude as S
56import qualified Streamly.Internal.Data.Stream.StreamD as D
91import qualified Streamly.Internal.Data.Stream.StreamD as D
58-------------------------------------------------------------------------------
93-------------------------------------------------------------------------------
59−-- Encoding/Decoding Unicode Characters
94+-- Encoding/Decoding Unicode (UTF-8) Characters
60-------------------------------------------------------------------------------
95-------------------------------------------------------------------------------
97+-- UTF-8 primitives, Lifted from GHC.IO.Encoding.UTF8.
99+data WList = WCons !Word8 !WList | WNil
102+ord2 :: Char -> WList
103+ord2 c = assert (n >= 0x80 && n <= 0x07ff) (WCons x1 (WCons x2 WNil))
106+ x1 = fromIntegral $ (n `shiftR` 6) + 0xC0
107+ x2 = fromIntegral $ (n .&. 0x3F) + 0x80
110+ord3 :: Char -> WList
111+ord3 c = assert (n >= 0x0800 && n <= 0xffff) (WCons x1 (WCons x2 (WCons x3 WNil)))
114+ x1 = fromIntegral $ (n `shiftR` 12) + 0xE0
115+ x2 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80
116+ x3 = fromIntegral $ (n .&. 0x3F) + 0x80
119+ord4 :: Char -> WList
120+ord4 c = assert (n >= 0x10000) (WCons x1 (WCons x2 (WCons x3 (WCons x4 WNil))))
123+ x1 = fromIntegral $ (n `shiftR` 18) + 0xF0
124+ x2 = fromIntegral $ ((n `shiftR` 12) .&. 0x3F) + 0x80
125+ x3 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80
126+ x4 = fromIntegral $ (n .&. 0x3F) + 0x80
128+data CodingFailureMode
129+ = TransliterateCodingFailure
130+ | ErrorOnCodingFailure
133+{-# INLINE replacementChar #-}
134+replacementChar :: Char
135+replacementChar = '\xFFFD'
137+-- Int helps in cheaper conversion from Int to Char
139+type DecodeState = Word8
141+-- See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
143+-- XXX Use names decodeSuccess = 0, decodeFailure = 12
145+decodeTable :: [Word8]
147+ -- The first part of the table maps bytes to character classes that
148+ -- to reduce the size of the transition table and create bitmasks.
149+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
150+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
151+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
152+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
153+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
154+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
155+ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
156+ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,
158+ -- The second part is a transition table that maps a combination
159+ -- of a state of the automaton and a character class to a state.
160+ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
161+ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
162+ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
163+ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
164+ 12,36,12,12,12,12,12,12,12,12,12,12
167+utf8d :: A.Array Word8
170+ -- Aligning to cacheline makes a barely noticeable difference
171+ -- XXX currently alignment is not implemented for unmanaged allocation
172+ $ D.runFold (A.writeNAlignedUnmanaged 64 (length decodeTable))
173+ (D.fromList decodeTable)
175+-- | Return element at the specified index without checking the bounds.
176+-- and without touching the foreign ptr.
177+{-# INLINE_NORMAL unsafePeekElemOff #-}
178+unsafePeekElemOff :: forall a. Storable a => Ptr a -> Int -> a
179+unsafePeekElemOff p i = let !x = A.unsafeInlineIO $ peekElemOff p i in x
181+-- decode is split into two separate cases to avoid branching instructions.
182+-- From the higher level flow we already know which case we are in so we can
183+-- call the appropriate decode function.
185+-- When the state is 0
186+{-# INLINE decode0 #-}
187+decode0 :: Ptr Word8 -> Word8 -> Tuple' DecodeState CodePoint
189+ let !t = table `unsafePeekElemOff` fromIntegral byte
190+ !codep' = (0xff `shiftR` (fromIntegral t)) .&. fromIntegral byte
191+ !state' = table `unsafePeekElemOff` (256 + fromIntegral t)
192+ in assert ((byte > 0x7f || error showByte)
193+ && (state' /= 0 || error (showByte ++ showTable)))
194+ (Tuple' state' codep')
199+ let !(Ptr addr) = table
200+ end = table `plusPtr` 364
201+ in A.Array (ForeignPtr addr undefined) end end :: A.Array Word8
202+ showByte = "Streamly: decode0: byte: " ++ show byte
203+ showTable = " table: " ++ show utf8table
205+-- When the state is not 0
206+{-# INLINE decode1 #-}
212+ -> Tuple' DecodeState CodePoint
213+decode1 table state codep byte =
214+ -- Remember codep is Int type!
215+ -- Can it be unsafe to convert the resulting Int to Char?
216+ let !t = table `unsafePeekElemOff` fromIntegral byte
217+ !codep' = (fromIntegral byte .&. 0x3f) .|. (codep `shiftL` 6)
218+ !state' = table `unsafePeekElemOff`
219+ (256 + fromIntegral state + fromIntegral t)
220+ in assert (codep' <= 0x10FFFF
221+ || error (showByte ++ showState state codep))
222+ (Tuple' state' codep')
226+ let !(Ptr addr) = table
227+ end = table `plusPtr` 364
228+ in A.Array (ForeignPtr addr undefined) end end :: A.Array Word8
229+ showByte = "Streamly: decode1: byte: " ++ show byte
231+ " state: " ++ show st ++
232+ " codepoint: " ++ show cp ++
233+ " table: " ++ show utf8table
235+-- We can divide the errors in three general categories:
236+-- * A non-starter was encountered in a begin state
237+-- * A starter was encountered without completing a codepoint
238+-- * The last codepoint was not complete (input underflow)
240+data DecodeError = DecodeError !DecodeState !CodePoint deriving Show
243+ = FreshPointDecodeInit s
244+ | FreshPointDecodeInit1 s Word8
245+ | FreshPointDecodeFirst s Word8
246+ | FreshPointDecoding s !DecodeState !CodePoint
247+ | YieldAndContinue a (FreshPoint s a)
250+-- XXX Add proper error messages
251+-- XXX Implement this in terms of decodeUtf8Either
252+{-# INLINE_NORMAL decodeUtf8WithD #-}
253+decodeUtf8WithD :: Monad m => CodingFailureMode -> Stream m Word8 -> Stream m Char
254+decodeUtf8WithD cfm (Stream step state) =
255+ let A.Array p _ _ = utf8d
256+ !ptr = (unsafeForeignPtrToPtr p)
257+ in Stream (step' ptr) (FreshPointDecodeInit state)
259+ {-# INLINE transliterateOrError #-}
260+ transliterateOrError e s =
262+ ErrorOnCodingFailure -> error e
263+ TransliterateCodingFailure -> YieldAndContinue replacementChar s
264+ {-# INLINE inputUnderflow #-}
267+ ErrorOnCodingFailure ->
268+ error "Streamly.Internal.Data.Stream.StreamD.decodeUtf8With: Input Underflow"
269+ TransliterateCodingFailure -> YieldAndContinue replacementChar Done
270+ {-# INLINE_LATE step' #-}
271+ step' _ gst (FreshPointDecodeInit st) = do
272+ r <- step (adaptState gst) st
274+ Yield x s -> Skip (FreshPointDecodeInit1 s x)
275+ Skip s -> Skip (FreshPointDecodeInit s)
278+ step' _ _ (FreshPointDecodeInit1 st x) = do
279+ -- Note: It is important to use a ">" instead of a "<=" test
280+ -- here for GHC to generate code layout for default branch
281+ -- prediction for the common case. This is fragile and might
282+ -- change with the compiler versions, we need a more reliable
283+ -- "likely" primitive to control branch predication.
286+ return $ Skip $ YieldAndContinue
287+ (unsafeChr (fromIntegral x))
288+ (FreshPointDecodeInit st)
289+ -- Using a separate state here generates a jump to a
290+ -- separate code block in the core which seems to perform
291+ -- slightly better for the non-ascii case.
292+ True -> return $ Skip $ FreshPointDecodeFirst st x
294+ -- XXX should we merge it with FreshPointDecodeInit1?
295+ step' table _ (FreshPointDecodeFirst st x) = do
296+ let (Tuple' sv cp) = decode0 table x
301+ transliterateOrError
302+ "Streamly.Internal.Data.Stream.StreamD.decodeUtf8With: Invalid UTF8 codepoint encountered"
303+ (FreshPointDecodeInit st)
304+ 0 -> error "unreachable state"
305+ _ -> Skip (FreshPointDecoding st sv cp)
307+ -- We recover by trying the new byte x a starter of a new codepoint.
308+ -- XXX need to use the same recovery in array decoding routine as well
309+ step' table gst (FreshPointDecoding st statePtr codepointPtr) = do
310+ r <- step (adaptState gst) st
313+ let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
316+ 0 -> Skip $ YieldAndContinue (unsafeChr cp)
317+ (FreshPointDecodeInit s)
320+ transliterateOrError
321+ "Streamly.Internal.Data.Stream.StreamD.decodeUtf8With: Invalid UTF8 codepoint encountered"
322+ (FreshPointDecodeInit1 s x)
323+ _ -> Skip (FreshPointDecoding s sv cp)
324+ Skip s -> return $ Skip (FreshPointDecoding s statePtr codepointPtr)
325+ Stop -> return $ Skip inputUnderflow
327+ step' _ _ (YieldAndContinue c s) = return $ Yield c s
328+ step' _ _ Done = return Stop
330+{-# INLINE decodeUtf8D #-}
331+decodeUtf8D :: Monad m => Stream m Word8 -> Stream m Char
332+decodeUtf8D = decodeUtf8WithD ErrorOnCodingFailure
334+{-# INLINE decodeUtf8LenientD #-}
335+decodeUtf8LenientD :: Monad m => Stream m Word8 -> Stream m Char
336+decodeUtf8LenientD = decodeUtf8WithD TransliterateCodingFailure
338+{-# INLINE_NORMAL resumeDecodeUtf8EitherD #-}
339+resumeDecodeUtf8EitherD
344+ -> Stream m (Either DecodeError Char)
345+resumeDecodeUtf8EitherD dst codep (Stream step state) =
346+ let A.Array p _ _ = utf8d
347+ !ptr = (unsafeForeignPtrToPtr p)
350+ then FreshPointDecodeInit state
351+ else FreshPointDecoding state dst codep
352+ in Stream (step' ptr) stt
354+ {-# INLINE_LATE step' #-}
355+ step' _ gst (FreshPointDecodeInit st) = do
356+ r <- step (adaptState gst) st
358+ Yield x s -> Skip (FreshPointDecodeInit1 s x)
359+ Skip s -> Skip (FreshPointDecodeInit s)
362+ step' _ _ (FreshPointDecodeInit1 st x) = do
363+ -- Note: It is important to use a ">" instead of a "<=" test
364+ -- here for GHC to generate code layout for default branch
365+ -- prediction for the common case. This is fragile and might
366+ -- change with the compiler versions, we need a more reliable
367+ -- "likely" primitive to control branch predication.
370+ return $ Skip $ YieldAndContinue
371+ (Right $ unsafeChr (fromIntegral x))
372+ (FreshPointDecodeInit st)
373+ -- Using a separate state here generates a jump to a
374+ -- separate code block in the core which seems to perform
375+ -- slightly better for the non-ascii case.
376+ True -> return $ Skip $ FreshPointDecodeFirst st x
378+ -- XXX should we merge it with FreshPointDecodeInit1?
379+ step' table _ (FreshPointDecodeFirst st x) = do
380+ let (Tuple' sv cp) = decode0 table x
384+ Skip $ YieldAndContinue (Left $ DecodeError 0 (fromIntegral x))
385+ (FreshPointDecodeInit st)
386+ 0 -> error "unreachable state"
387+ _ -> Skip (FreshPointDecoding st sv cp)
389+ -- We recover by trying the new byte x a starter of a new codepoint.
390+ -- XXX need to use the same recovery in array decoding routine as well
391+ step' table gst (FreshPointDecoding st statePtr codepointPtr) = do
392+ r <- step (adaptState gst) st
395+ let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
398+ 0 -> Skip $ YieldAndContinue (Right $ unsafeChr cp)
399+ (FreshPointDecodeInit s)
401+ Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr)
402+ (FreshPointDecodeInit1 s x)
403+ _ -> Skip (FreshPointDecoding s sv cp)
404+ Skip s -> return $ Skip (FreshPointDecoding s statePtr codepointPtr)
405+ Stop -> return $ Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr) Done
407+ step' _ _ (YieldAndContinue c s) = return $ Yield c s
408+ step' _ _ Done = return Stop
410+{-# INLINE_NORMAL decodeUtf8EitherD #-}
411+decodeUtf8EitherD :: Monad m
412+ => Stream m Word8 -> Stream m (Either DecodeError Char)
413+decodeUtf8EitherD = resumeDecodeUtf8EitherD 0 0
415+data FlattenState s a
416+ = OuterLoop s !(Maybe (DecodeState, CodePoint))
417+ | InnerLoopDecodeInit s (ForeignPtr a) !(Ptr a) !(Ptr a)
418+ | InnerLoopDecodeFirst s (ForeignPtr a) !(Ptr a) !(Ptr a) Word8
419+ | InnerLoopDecoding s (ForeignPtr a) !(Ptr a) !(Ptr a)
420+ !DecodeState !CodePoint
421+ | YAndC !Char (FlattenState s a) -- These constructors can be
422+ -- encoded in the FreshPoint
423+ -- type, I prefer to keep these
424+ -- flat even though that means
425+ -- coming up with new names
428+-- The normal decodeUtf8 above should fuse with flattenArrays
429+-- to create this exact code but it doesn't for some reason, as of now this
430+-- remains the fastest way I could figure out to decodeUtf8.
432+-- XXX Add Proper error messages
433+{-# INLINE_NORMAL decodeUtf8ArraysWithD #-}
434+decodeUtf8ArraysWithD ::
436+ => CodingFailureMode
437+ -> Stream m (A.Array Word8)
439+decodeUtf8ArraysWithD cfm (Stream step state) =
440+ let A.Array p _ _ = utf8d
441+ !ptr = (unsafeForeignPtrToPtr p)
442+ in Stream (step' ptr) (OuterLoop state Nothing)
444+ {-# INLINE transliterateOrError #-}
445+ transliterateOrError e s =
447+ ErrorOnCodingFailure -> error e
448+ TransliterateCodingFailure -> YAndC replacementChar s
449+ {-# INLINE inputUnderflow #-}
452+ ErrorOnCodingFailure ->
454+ "Streamly.Internal.Data.Stream.StreamD.decodeUtf8ArraysWith: Input Underflow"
455+ TransliterateCodingFailure -> YAndC replacementChar D
456+ {-# INLINE_LATE step' #-}
457+ step' _ gst (OuterLoop st Nothing) = do
458+ r <- step (adaptState gst) st
461+ Yield A.Array {..} s ->
462+ let p = unsafeForeignPtrToPtr aStart
463+ in Skip (InnerLoopDecodeInit s aStart p aEnd)
464+ Skip s -> Skip (OuterLoop s Nothing)
466+ step' _ gst (OuterLoop st dst@(Just (ds, cp))) = do
467+ r <- step (adaptState gst) st
470+ Yield A.Array {..} s ->
471+ let p = unsafeForeignPtrToPtr aStart
472+ in Skip (InnerLoopDecoding s aStart p aEnd ds cp)
473+ Skip s -> Skip (OuterLoop s dst)
474+ Stop -> Skip inputUnderflow
475+ step' _ _ (InnerLoopDecodeInit st startf p end)
477+ liftIO $ touchForeignPtr startf
478+ return $ Skip $ OuterLoop st Nothing
479+ step' _ _ (InnerLoopDecodeInit st startf p end) = do
480+ x <- liftIO $ peek p
481+ -- Note: It is important to use a ">" instead of a "<=" test here for
482+ -- GHC to generate code layout for default branch prediction for the
483+ -- common case. This is fragile and might change with the compiler
484+ -- versions, we need a more reliable "likely" primitive to control
485+ -- branch predication.
488+ return $ Skip $ YAndC
489+ (unsafeChr (fromIntegral x))
490+ (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
491+ -- Using a separate state here generates a jump to a separate code
492+ -- block in the core which seems to perform slightly better for the
494+ True -> return $ Skip $ InnerLoopDecodeFirst st startf p end x
496+ step' table _ (InnerLoopDecodeFirst st startf p end x) = do
497+ let (Tuple' sv cp) = decode0 table x
502+ transliterateOrError
503+ "Streamly.Internal.Data.Stream.StreamD.decodeUtf8ArraysWith: Invalid UTF8 codepoint encountered"
504+ (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
505+ 0 -> error "unreachable state"
506+ _ -> Skip (InnerLoopDecoding st startf (p `plusPtr` 1) end sv cp)
507+ step' _ _ (InnerLoopDecoding st startf p end sv cp)
509+ liftIO $ touchForeignPtr startf
510+ return $ Skip $ OuterLoop st (Just (sv, cp))
511+ step' table _ (InnerLoopDecoding st startf p end statePtr codepointPtr) = do
512+ x <- liftIO $ peek p
513+ let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
520+ (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
523+ transliterateOrError
524+ "Streamly.Internal.Data.Stream.StreamD.decodeUtf8ArraysWith: Invalid UTF8 codepoint encountered"
525+ (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
526+ _ -> Skip (InnerLoopDecoding st startf (p `plusPtr` 1) end sv cp)
527+ step' _ _ (YAndC c s) = return $ Yield c s
528+ step' _ _ D = return Stop
530+{-# INLINE decodeUtf8ArraysD #-}
533+ => Stream m (A.Array Word8)
535+decodeUtf8ArraysD = decodeUtf8ArraysWithD ErrorOnCodingFailure
537+{-# INLINE decodeUtf8ArraysLenientD #-}
538+decodeUtf8ArraysLenientD ::
540+ => Stream m (A.Array Word8)
542+decodeUtf8ArraysLenientD = decodeUtf8ArraysWithD TransliterateCodingFailure
544+data EncodeState s = EncodeState s !WList
546+-- More yield points improve performance, but I am not sure if they can cause
547+-- too much code bloat or some trouble with fusion. So keeping only two yield
548+-- points for now, one for the ascii chars (fast path) and one for all other
549+-- paths (slow path).
550+{-# INLINE_NORMAL encodeUtf8D #-}
551+encodeUtf8D :: Monad m => Stream m Char -> Stream m Word8
552+encodeUtf8D (Stream step state) = Stream step' (EncodeState state WNil)
554+ {-# INLINE_LATE step' #-}
555+ step' gst (EncodeState st WNil) = do
556+ r <- step (adaptState gst) st
563+ Yield (fromIntegral x) (EncodeState s WNil)
564+ | x <= 0x7FF -> Skip (EncodeState s (ord2 c))
568+ "Streamly.Internal.Data.Stream.StreamD.encodeUtf8: Encountered a surrogate"
569+ else Skip (EncodeState s (ord3 c))
570+ | otherwise -> Skip (EncodeState s (ord4 c))
571+ Skip s -> Skip (EncodeState s WNil)
573+ step' _ (EncodeState s (WCons x xs)) = return $ Yield x (EncodeState s xs)
62-- | Decode a stream of bytes to Unicode characters by mapping each byte to a
576-- | Decode a stream of bytes to Unicode characters by mapping each byte to a
63-- corresponding Unicode 'Char' in 0-255 range.
577-- corresponding Unicode 'Char' in 0-255 range.
@@ -79,8 +593,8 @@ encodeLatin1 = S.map convert
79 convert c =
593 convert c =
80 let codepoint = ord c
594 let codepoint = ord c
81 in if codepoint > 255
595 in if codepoint > 255
82− then error $ "Streamly.String.encodeLatin1 invalid \
596+ then error $ "Streamly.String.encodeLatin1 invalid " ++
83− \input char codepoint " ++ show codepoint
597+ "input char codepoint " ++ show codepoint
84 else fromIntegral codepoint
598 else fromIntegral codepoint
86-- | Like 'encodeLatin1' but silently truncates and maps input characters beyond
600-- | Like 'encodeLatin1' but silently truncates and maps input characters beyond
@@ -98,14 +612,14 @@ encodeLatin1Lax = S.map (fromIntegral . ord)
98-- /Since: 0.7.0/
612-- /Since: 0.7.0/
99{-# INLINE decodeUtf8 #-}
613{-# INLINE decodeUtf8 #-}
100decodeUtf8 :: (Monad m, IsStream t) => t m Word8 -> t m Char
614decodeUtf8 :: (Monad m, IsStream t) => t m Word8 -> t m Char
101−decodeUtf8 = D.fromStreamD . D.decodeUtf8 . D.toStreamD
615+decodeUtf8 = D.fromStreamD . decodeUtf8D . D.toStreamD
105-- /Internal/
619-- /Internal/
106{-# INLINE decodeUtf8Arrays #-}
620{-# INLINE decodeUtf8Arrays #-}
107decodeUtf8Arrays :: (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
621decodeUtf8Arrays :: (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
108−decodeUtf8Arrays = D.fromStreamD . D.decodeUtf8Arrays . D.toStreamD
622+decodeUtf8Arrays = D.fromStreamD . decodeUtf8ArraysD . D.toStreamD
110-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
624-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
111-- Any invalid codepoint encountered is replaced with the unicode replacement
625-- Any invalid codepoint encountered is replaced with the unicode replacement
@@ -114,15 +628,15 @@ decodeUtf8Arrays = D.fromStreamD . D.decodeUtf8Arrays . D.toStreamD
114-- /Since: 0.7.0/
628-- /Since: 0.7.0/
115{-# INLINE decodeUtf8Lax #-}
629{-# INLINE decodeUtf8Lax #-}
116decodeUtf8Lax :: (Monad m, IsStream t) => t m Word8 -> t m Char
630decodeUtf8Lax :: (Monad m, IsStream t) => t m Word8 -> t m Char
117−decodeUtf8Lax = D.fromStreamD . D.decodeUtf8Lenient . D.toStreamD
631+decodeUtf8Lax = D.fromStreamD . decodeUtf8LenientD . D.toStreamD
121-- /Internal/
635-- /Internal/
122{-# INLINE decodeUtf8Either #-}
636{-# INLINE decodeUtf8Either #-}
123decodeUtf8Either :: (Monad m, IsStream t)
637decodeUtf8Either :: (Monad m, IsStream t)
124− => t m Word8 -> t m (Either D.DecodeError Char)
638+ => t m Word8 -> t m (Either DecodeError Char)
125−decodeUtf8Either = D.fromStreamD . D.decodeUtf8Either . D.toStreamD
639+decodeUtf8Either = D.fromStreamD . decodeUtf8EitherD . D.toStreamD
@@ -130,12 +644,12 @@ decodeUtf8Either = D.fromStreamD . D.decodeUtf8Either . D.toStreamD
130{-# INLINE resumeDecodeUtf8Either #-}
644{-# INLINE resumeDecodeUtf8Either #-}
131resumeDecodeUtf8Either
645resumeDecodeUtf8Either
132 :: (Monad m, IsStream t)
646 :: (Monad m, IsStream t)
133− => D.DecodeState
647+ => DecodeState
134− -> D.CodePoint
648+ -> CodePoint
135 -> t m Word8
649 -> t m Word8
136− -> t m (Either D.DecodeError Char)
650+ -> t m (Either DecodeError Char)
137resumeDecodeUtf8Either st cp =
651resumeDecodeUtf8Either st cp =
138− D.fromStreamD . D.resumeDecodeUtf8Either st cp . D.toStreamD
652+ D.fromStreamD . resumeDecodeUtf8EitherD st cp . D.toStreamD
@@ -144,14 +658,14 @@ resumeDecodeUtf8Either st cp =
144decodeUtf8ArraysLenient ::
658decodeUtf8ArraysLenient ::
145 (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
659 (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
146decodeUtf8ArraysLenient =
660decodeUtf8ArraysLenient =
147− D.fromStreamD . D.decodeUtf8ArraysLenient . D.toStreamD
661+ D.fromStreamD . decodeUtf8ArraysLenientD . D.toStreamD
149-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream.
663-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream.
151-- /Since: 0.7.0/
665-- /Since: 0.7.0/
152{-# INLINE encodeUtf8 #-}
666{-# INLINE encodeUtf8 #-}
153encodeUtf8 :: (Monad m, IsStream t) => t m Char -> t m Word8
667encodeUtf8 :: (Monad m, IsStream t) => t m Char -> t m Word8
154−encodeUtf8 = D.fromStreamD . D.encodeUtf8 . D.toStreamD
668+encodeUtf8 = D.fromStreamD . encodeUtf8D . D.toStreamD
157-------------------------------------------------------------------------------
671-------------------------------------------------------------------------------