@@ -16,20 +16,24 @@ module Streamly.Internal.Unicode.Stream
16 -- * Construction (Decoding)
16 -- * Construction (Decoding)
17 decodeLatin1
17 decodeLatin1
18 , decodeUtf8
18 , decodeUtf8
19− , decodeUtf8Lax
19+ , decodeUtf8'
20 , DecodeError(..)
21 , DecodeError(..)
21 , DecodeState
22 , DecodeState
22 , CodePoint
23 , CodePoint
23 , decodeUtf8Either
24 , decodeUtf8Either
24 , resumeDecodeUtf8Either
25 , resumeDecodeUtf8Either
25 , decodeUtf8Arrays
26 , decodeUtf8Arrays
26− , decodeUtf8ArraysLenient
27+ , decodeUtf8Arrays'
28 -- * Elimination (Encoding)
30 -- * Elimination (Encoding)
29 , encodeLatin1
31 , encodeLatin1
30− , encodeLatin1Lax
32+ , encodeLatin1'
31 , encodeUtf8
34 , encodeUtf8
32− , encodeUtf8Lax
35+ , encodeUtf8'
34 -- * Operations on character strings
38 -- * Operations on character strings
35 , strip -- (dropAround isSpace)
39 , strip -- (dropAround isSpace)
@@ -38,12 +42,16 @@ module Streamly.Internal.Unicode.Stream
39 -- * StreamD UTF8 Encoding / Decoding transformations.
43 -- * StreamD UTF8 Encoding / Decoding transformations.
40 , decodeUtf8D
44 , decodeUtf8D
41 , encodeUtf8D
47 , encodeUtf8D
42− , decodeUtf8LenientD
48+ , encodeUtf8D'
43 , decodeUtf8EitherD
50 , decodeUtf8EitherD
44 , resumeDecodeUtf8EitherD
51 , resumeDecodeUtf8EitherD
45 , decodeUtf8ArraysD
52 , decodeUtf8ArraysD
46− , decodeUtf8ArraysLenientD
53+ , decodeUtf8ArraysD'
48 -- * Transformation
56 -- * Transformation
49 , stripStart
57 , stripStart
@@ -51,12 +59,17 @@ module Streamly.Internal.Unicode.Stream
57import Control.Monad.IO.Class (MonadIO, liftIO)
70import Control.Monad.IO.Class (MonadIO, liftIO)
58import Data.Bits (shiftR, shiftL, (.|.), (.&.))
71import Data.Bits (shiftR, shiftL, (.|.), (.&.))
59−import Data.Char (ord)
72+import Data.Char (chr, ord)
60import Data.Word (Word8)
73import Data.Word (Word8)
61import Foreign.ForeignPtr (touchForeignPtr)
74import Foreign.ForeignPtr (touchForeignPtr)
62import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
75import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
@@ -118,6 +131,7 @@ ord4 c = assert (n >= 0x10000) (WCons x1 (WCons x2 (WCons x3 (WCons x4 WNil))))
118data CodingFailureMode
131data CodingFailureMode
119 = TransliterateCodingFailure
132 = TransliterateCodingFailure
120 | ErrorOnCodingFailure
133 | ErrorOnCodingFailure
134+ | DropOnCodingFailure
121 deriving (Show)
135 deriving (Show)
123{-# INLINE replacementChar #-}
137{-# INLINE replacementChar #-}
@@ -266,12 +280,14 @@ decodeUtf8WithD cfm (Stream step state) =
266 case cfm of
280 case cfm of
267 ErrorOnCodingFailure -> error e
281 ErrorOnCodingFailure -> error e
268 TransliterateCodingFailure -> YieldAndContinue replacementChar s
282 TransliterateCodingFailure -> YieldAndContinue replacementChar s
283+ DropOnCodingFailure -> s
270 {-# INLINE handleUnderflow #-}
285 {-# INLINE handleUnderflow #-}
271 handleUnderflow =
286 handleUnderflow =
272 case cfm of
287 case cfm of
273 ErrorOnCodingFailure -> error $ prefix ++ "Not enough input"
288 ErrorOnCodingFailure -> error $ prefix ++ "Not enough input"
274 TransliterateCodingFailure -> YieldAndContinue replacementChar Done
289 TransliterateCodingFailure -> YieldAndContinue replacementChar Done
290+ DropOnCodingFailure -> Done
276 {-# INLINE_LATE step' #-}
292 {-# INLINE_LATE step' #-}
277 step' _ gst (FreshPointDecodeInit st) = do
293 step' _ gst (FreshPointDecodeInit st) = do
@@ -337,11 +353,15 @@ decodeUtf8WithD cfm (Stream step state) =
338{-# INLINE decodeUtf8D #-}
354{-# INLINE decodeUtf8D #-}
339decodeUtf8D :: Monad m => Stream m Word8 -> Stream m Char
355decodeUtf8D :: Monad m => Stream m Word8 -> Stream m Char
340−decodeUtf8D = decodeUtf8WithD ErrorOnCodingFailure
356+decodeUtf8D = decodeUtf8WithD TransliterateCodingFailure
358+{-# INLINE decodeUtf8D' #-}
359+decodeUtf8D' :: Monad m => Stream m Word8 -> Stream m Char
360+decodeUtf8D' = decodeUtf8WithD ErrorOnCodingFailure
342−{-# INLINE decodeUtf8LenientD #-}
362+{-# INLINE decodeUtf8D_ #-}
343−decodeUtf8LenientD :: Monad m => Stream m Word8 -> Stream m Char
363+decodeUtf8D_ :: Monad m => Stream m Word8 -> Stream m Char
344−decodeUtf8LenientD = decodeUtf8WithD TransliterateCodingFailure
364+decodeUtf8D_ = decodeUtf8WithD DropOnCodingFailure
346{-# INLINE_NORMAL resumeDecodeUtf8EitherD #-}
366{-# INLINE_NORMAL resumeDecodeUtf8EitherD #-}
347resumeDecodeUtf8EitherD
367resumeDecodeUtf8EitherD
@@ -455,6 +475,7 @@ decodeUtf8ArraysWithD cfm (Stream step state) =
455 case cfm of
475 case cfm of
456 ErrorOnCodingFailure -> error e
476 ErrorOnCodingFailure -> error e
457 TransliterateCodingFailure -> YAndC replacementChar s
477 TransliterateCodingFailure -> YAndC replacementChar s
478+ DropOnCodingFailure -> s
458 {-# INLINE inputUnderflow #-}
479 {-# INLINE inputUnderflow #-}
459 inputUnderflow =
480 inputUnderflow =
460 case cfm of
481 case cfm of
@@ -462,6 +483,7 @@ decodeUtf8ArraysWithD cfm (Stream step state) =
463 "Streamly.Internal.Data.Stream.StreamD.decodeUtf8ArraysWith: Input Underflow"
484 "Streamly.Internal.Data.Stream.StreamD.decodeUtf8ArraysWith: Input Underflow"
464 TransliterateCodingFailure -> YAndC replacementChar D
485 TransliterateCodingFailure -> YAndC replacementChar D
486+ DropOnCodingFailure -> D
465 {-# INLINE_LATE step' #-}
487 {-# INLINE_LATE step' #-}
466 step' _ gst (OuterLoop st Nothing) = do
488 step' _ gst (OuterLoop st Nothing) = do
467 r <- step (adaptState gst) st
489 r <- step (adaptState gst) st
@@ -541,14 +563,21 @@ decodeUtf8ArraysD ::
541 MonadIO m
563 MonadIO m
542 => Stream m (A.Array Word8)
564 => Stream m (A.Array Word8)
543 -> Stream m Char
565 -> Stream m Char
544−decodeUtf8ArraysD = decodeUtf8ArraysWithD ErrorOnCodingFailure
566+decodeUtf8ArraysD = decodeUtf8ArraysWithD TransliterateCodingFailure
568+{-# INLINE decodeUtf8ArraysD' #-}
569+decodeUtf8ArraysD' ::
571+ => Stream m (A.Array Word8)
573+decodeUtf8ArraysD' = decodeUtf8ArraysWithD ErrorOnCodingFailure
546−{-# INLINE decodeUtf8ArraysLenientD #-}
575+{-# INLINE decodeUtf8ArraysD_ #-}
547−decodeUtf8ArraysLenientD ::
576+decodeUtf8ArraysD_ ::
548 MonadIO m
577 MonadIO m
549 => Stream m (A.Array Word8)
578 => Stream m (A.Array Word8)
550 -> Stream m Char
579 -> Stream m Char
551−decodeUtf8ArraysLenientD = decodeUtf8ArraysWithD TransliterateCodingFailure
580+decodeUtf8ArraysD_ = decodeUtf8ArraysWithD DropOnCodingFailure
553data EncodeState s = EncodeState s !WList
582data EncodeState s = EncodeState s !WList
@@ -556,9 +585,9 @@ data EncodeState s = EncodeState s !WList
556-- too much code bloat or some trouble with fusion. So keeping only two yield
585-- too much code bloat or some trouble with fusion. So keeping only two yield
557-- points for now, one for the ascii chars (fast path) and one for all other
586-- points for now, one for the ascii chars (fast path) and one for all other
558-- paths (slow path).
587-- paths (slow path).
559−{-# INLINE_NORMAL encodeUtf8D #-}
588+{-# INLINE_NORMAL encodeUtf8D' #-}
560−encodeUtf8D :: Monad m => Stream m Char -> Stream m Word8
589+encodeUtf8D' :: Monad m => Stream m Char -> Stream m Word8
561−encodeUtf8D (Stream step state) = Stream step' (EncodeState state WNil)
590+encodeUtf8D' (Stream step state) = Stream step' (EncodeState state WNil)
563 {-# INLINE_LATE step' #-}
592 {-# INLINE_LATE step' #-}
564 step' gst (EncodeState st WNil) = do
593 step' gst (EncodeState st WNil) = do
@@ -585,7 +614,9 @@ encodeUtf8D (Stream step state) = Stream step' (EncodeState state WNil)
585-- | Decode a stream of bytes to Unicode characters by mapping each byte to a
614-- | Decode a stream of bytes to Unicode characters by mapping each byte to a
586-- corresponding Unicode 'Char' in 0-255 range.
615-- corresponding Unicode 'Char' in 0-255 range.
588−-- /Since: 0.7.0/
617+-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
589{-# INLINE decodeLatin1 #-}
620{-# INLINE decodeLatin1 #-}
590decodeLatin1 :: (IsStream t, Monad m) => t m Word8 -> t m Char
621decodeLatin1 :: (IsStream t, Monad m) => t m Word8 -> t m Char
591decodeLatin1 = S.map (unsafeChr . fromIntegral)
622decodeLatin1 = S.map (unsafeChr . fromIntegral)
@@ -594,10 +625,10 @@ decodeLatin1 = S.map (unsafeChr . fromIntegral)
594-- to a byte in 0-255 range. Throws an error if the input stream contains
625-- to a byte in 0-255 range. Throws an error if the input stream contains
595-- characters beyond 255.
626-- characters beyond 255.
597−-- /Since: 0.7.0/
628+-- @since 0.8.0
598−{-# INLINE encodeLatin1 #-}
629+{-# INLINE encodeLatin1' #-}
599−encodeLatin1 :: (IsStream t, Monad m) => t m Char -> t m Word8
630+encodeLatin1' :: (IsStream t, Monad m) => t m Char -> t m Word8
600−encodeLatin1 = S.map convert
631+encodeLatin1' = S.map convert
602 convert c =
633 convert c =
603 let codepoint = ord c
634 let codepoint = ord c
@@ -606,38 +637,71 @@ encodeLatin1 = S.map convert
606 "input char codepoint " ++ show codepoint
637 "input char codepoint " ++ show codepoint
607 else fromIntegral codepoint
638 else fromIntegral codepoint
609−-- | Like 'encodeLatin1' but silently truncates and maps input characters beyond
640+-- | Like 'encodeLatin1'' but silently truncates and maps input characters beyond
610-- 255 to (incorrect) chars in 0-255 range. No error or exception is thrown
641-- 255 to (incorrect) chars in 0-255 range. No error or exception is thrown
611-- when such truncation occurs.
642-- when such truncation occurs.
613−-- /Since: 0.7.0/
644+-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
646+-- /Since: 0.8.0 (Lenient Behaviour)/
647+{-# INLINE encodeLatin1 #-}
648+encodeLatin1 :: (IsStream t, Monad m) => t m Char -> t m Word8
649+encodeLatin1 = S.map (fromIntegral . ord)
651+-- | Like 'encodeLatin1' but drops the input characters beyond 255.
654+{-# INLINE encodeLatin1_ #-}
655+encodeLatin1_ :: (IsStream t, Monad m) => t m Char -> t m Word8
656+encodeLatin1_ = S.map (fromIntegral . ord) . S.filter (<= chr (255))
658+-- | Same as 'encodeLatin1'
660+{-# DEPRECATED encodeLatin1Lax "Please use 'encodeLatin1' instead" #-}
614{-# INLINE encodeLatin1Lax #-}
661{-# INLINE encodeLatin1Lax #-}
615encodeLatin1Lax :: (IsStream t, Monad m) => t m Char -> t m Word8
662encodeLatin1Lax :: (IsStream t, Monad m) => t m Char -> t m Word8
616−encodeLatin1Lax = S.map (fromIntegral . ord)
663+encodeLatin1Lax = encodeLatin1
618-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
665-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
619−-- The incoming stream is truncated if an invalid codepoint is encountered.
666+-- The function throws an error if an invalid codepoint is encountered.
621−-- /Since: 0.7.0/
668+-- @since 0.8.0
622−{-# INLINE decodeUtf8 #-}
669+{-# INLINE decodeUtf8' #-}
623−decodeUtf8 :: (Monad m, IsStream t) => t m Word8 -> t m Char
670+decodeUtf8' :: (Monad m, IsStream t) => t m Word8 -> t m Char
624−decodeUtf8 = D.fromStreamD . decodeUtf8D . D.toStreamD
671+decodeUtf8' = D.fromStreamD . decodeUtf8D' . D.toStreamD
628-- /Internal/
675-- /Internal/
629−{-# INLINE decodeUtf8Arrays #-}
676+{-# INLINE decodeUtf8Arrays' #-}
630−decodeUtf8Arrays :: (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
677+decodeUtf8Arrays' :: (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
631−decodeUtf8Arrays = D.fromStreamD . decodeUtf8ArraysD . D.toStreamD
678+decodeUtf8Arrays' = D.fromStreamD . decodeUtf8ArraysD' . D.toStreamD
633-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
680-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
634-- Any invalid codepoint encountered is replaced with the unicode replacement
681-- Any invalid codepoint encountered is replaced with the unicode replacement
635-- character.
682-- character.
637−-- /Since: 0.7.0/
684+-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
686+-- /Since: 0.8.0 (Lenient Behaviour)/
687+{-# INLINE decodeUtf8 #-}
688+decodeUtf8 :: (Monad m, IsStream t) => t m Word8 -> t m Char
689+decodeUtf8 = D.fromStreamD . decodeUtf8D . D.toStreamD
691+-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
692+-- Any invalid codepoint encountered is dropped.
695+{-# INLINE decodeUtf8_ #-}
696+decodeUtf8_ :: (Monad m, IsStream t) => t m Word8 -> t m Char
697+decodeUtf8_ = D.fromStreamD . decodeUtf8D_ . D.toStreamD
699+-- | Same as 'decodeUtf8'
701+{-# DEPRECATED decodeUtf8Lax "Please use 'decodeUtf8' instead" #-}
638{-# INLINE decodeUtf8Lax #-}
702{-# INLINE decodeUtf8Lax #-}
639−decodeUtf8Lax :: (Monad m, IsStream t) => t m Word8 -> t m Char
703+decodeUtf8Lax :: (IsStream t, Monad m) => t m Word8 -> t m Char
640−decodeUtf8Lax = D.fromStreamD . decodeUtf8LenientD . D.toStreamD
704+decodeUtf8Lax = decodeUtf8
@@ -663,25 +727,36 @@ resumeDecodeUtf8Either st cp =
665-- /Internal/
729-- /Internal/
666−{-# INLINE decodeUtf8ArraysLenient #-}
730+{-# INLINE decodeUtf8Arrays #-}
667−decodeUtf8ArraysLenient ::
731+decodeUtf8Arrays ::
668 (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
732 (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
669−decodeUtf8ArraysLenient =
733+decodeUtf8Arrays =
670− D.fromStreamD . decodeUtf8ArraysLenientD . D.toStreamD
734+ D.fromStreamD . decodeUtf8ArraysD . D.toStreamD
672−-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream.
736+-- |
674−-- /Since: 0.7.0/
738+-- /Internal/
739+{-# INLINE decodeUtf8Arrays_ #-}
741+ (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
743+ D.fromStreamD . decodeUtf8ArraysD_ . D.toStreamD
745+-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. When
746+-- any invalid character (U+D800-U+D8FF) is encountered in the input stream the
747+-- function errors out.
675−{-# INLINE encodeUtf8 #-}
750+{-# INLINE encodeUtf8' #-}
676−encodeUtf8 :: (Monad m, IsStream t) => t m Char -> t m Word8
751+encodeUtf8' :: (Monad m, IsStream t) => t m Char -> t m Word8
677−encodeUtf8 = D.fromStreamD . encodeUtf8D . D.toStreamD
752+encodeUtf8' = D.fromStreamD . encodeUtf8D' . D.toStreamD
679-- | See section "3.9 Unicode Encoding Forms" in
754-- | See section "3.9 Unicode Encoding Forms" in
680-- https://www.unicode.org/versions/Unicode13.0.0/UnicodeStandard-13.0.pdf
755-- https://www.unicode.org/versions/Unicode13.0.0/UnicodeStandard-13.0.pdf
682−{-# INLINE_NORMAL encodeUtf8LaxD #-}
757+{-# INLINE_NORMAL encodeUtf8D #-}
683−encodeUtf8LaxD :: Monad m => Stream m Char -> Stream m Word8
758+encodeUtf8D :: Monad m => Stream m Char -> Stream m Word8
684−encodeUtf8LaxD (Stream step state) = Stream step' (EncodeState state WNil)
759+encodeUtf8D (Stream step state) = Stream step' (EncodeState state WNil)
686 {-# INLINE_LATE step' #-}
761 {-# INLINE_LATE step' #-}
687 step' gst (EncodeState st WNil) = do
762 step' gst (EncodeState st WNil) = do
@@ -710,10 +785,51 @@ encodeUtf8LaxD (Stream step state) = Stream step' (EncodeState state WNil)
710-- Invalid characters (U+D800-U+D8FF) in the input stream are replaced by the
785-- Invalid characters (U+D800-U+D8FF) in the input stream are replaced by the
711-- Unicode replacement character U+FFFD.
786-- Unicode replacement character U+FFFD.
713−-- /Since: 0.8.0/
788+-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
790+-- /Since: 0.8.0 (Lenient Behaviour)/
791+{-# INLINE encodeUtf8 #-}
792+encodeUtf8 :: (Monad m, IsStream t) => t m Char -> t m Word8
793+encodeUtf8 = D.fromStreamD . encodeUtf8D . D.toStreamD
795+{-# INLINE_NORMAL encodeUtf8D_ #-}
796+encodeUtf8D_ :: Monad m => Stream m Char -> Stream m Word8
797+encodeUtf8D_ (Stream step state) = Stream step' (EncodeState state WNil)
799+ {-# INLINE_LATE step' #-}
800+ step' gst (EncodeState st WNil) = do
801+ r <- step (adaptState gst) st
807+ Yield (fromIntegral x) (EncodeState s WNil)
808+ | x <= 0x7FF -> Skip (EncodeState s (ord2 c))
813+ else Skip (EncodeState s (ord3 c))
814+ | otherwise -> Skip (EncodeState s (ord4 c))
815+ Skip s -> Skip (EncodeState s WNil)
817+ step' _ (EncodeState s (WCons x xs)) = return $ Yield x (EncodeState s xs)
819+-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. Any
820+-- Invalid characters (U+D800-U+D8FF) in the input stream are dropped.
823+{-# INLINE encodeUtf8_ #-}
824+encodeUtf8_ :: (Monad m, IsStream t) => t m Char -> t m Word8
825+encodeUtf8_ = D.fromStreamD . encodeUtf8D_ . D.toStreamD
827+-- | Same as 'encodeUtf8'
829+{-# DEPRECATED encodeUtf8Lax "Please use 'encodeUtf8' instead" #-}
714{-# INLINE encodeUtf8Lax #-}
830{-# INLINE encodeUtf8Lax #-}
715−encodeUtf8Lax :: (Monad m, IsStream t) => t m Char -> t m Word8
831+encodeUtf8Lax :: (IsStream t, Monad m) => t m Char -> t m Word8
716−encodeUtf8Lax = D.fromStreamD . encodeUtf8LaxD . D.toStreamD
832+encodeUtf8Lax = encodeUtf8
719-------------------------------------------------------------------------------
835-------------------------------------------------------------------------------