2-- Module : Streamly.Internal.Unicode.Stream
3-- Copyright : (c) 2018 Composewell Technologies
4-- (c) Bjoern Hoehrmann 2008-2009
6-- License : BSD-3-Clause
7-- Maintainer : streamly@composewell.com
8-- Stability : experimental
12module Streamly.Internal.Unicode.Stream
14 -- * Construction (Decoding)
22 -- ** Resumable UTF-8 Decoding
27 , resumeDecodeUtf8Either
29 -- ** UTF-8 Array Stream Decoding
34 -- * Elimination (Encoding)
46 -- * Operations on character strings
47 , strip -- (dropAround isSpace)
58 -- * StreamD UTF8 Encoding / Decoding transformations.
66 , resumeDecodeUtf8EitherD
80import Control.Monad.IO.Class (MonadIO, liftIO)
81import Data.Bits (shiftR, shiftL, (.|.), (.&.))
82import Data.Char (chr, ord)
83import Data.Word (Word8)
84import Foreign.Storable (Storable(..))
85import Fusion.Plugin.Types (Fuse(..))
86import GHC.Base (assert, unsafeChr)
87import GHC.IO.Encoding.Failure (isSurrogate)
88import GHC.Ptr (Ptr (..), plusPtr)
89import System.IO.Unsafe (unsafePerformIO)
90import Streamly.Internal.Data.Array.Foreign (Array)
91import Streamly.Internal.Data.Array.Foreign.Mut.Type (ArrayContents, touch)
92import Streamly.Internal.Data.Fold (Fold)
93import Streamly.Internal.Data.Stream.IsStream.Type
94 (IsStream, fromStreamD, toStreamD, adapt)
95import Streamly.Internal.Data.Stream.Serial (SerialT)
96import Streamly.Internal.Data.Stream.StreamD (Stream(..), Step (..))
97import Streamly.Internal.Data.SVar (adaptState)
98import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
99import Streamly.Internal.Data.Unfold (Unfold)
100import Streamly.Internal.System.IO (unsafeInlineIO)
102import qualified Streamly.Internal.Data.Unfold as Unfold
103import qualified Streamly.Internal.Data.Stream.Serial as Serial
104import qualified Streamly.Internal.Data.Array.Foreign as Array
105import qualified Streamly.Internal.Data.Array.Foreign.Type as A
106import qualified Streamly.Internal.Data.Stream.IsStream as S
107import qualified Streamly.Internal.Data.Stream.StreamD as D
109import Prelude hiding (lines, words, unlines, unwords)
113-- >>> import Prelude hiding (lines, words, unlines, unwords)
114-- >>> import qualified Streamly.Prelude as Stream
115-- >>> import qualified Streamly.Data.Fold as Fold
116-- >>> import Streamly.Internal.Unicode.Stream
118-------------------------------------------------------------------------------
120-------------------------------------------------------------------------------
122-- | Decode a stream of bytes to Unicode characters by mapping each byte to a
123-- corresponding Unicode 'Char' in 0-255 range.
125-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
128{-# INLINE decodeLatin1 #-}
129decodeLatin1 :: (IsStream t, Monad m) => t m Word8 -> t m Char
130decodeLatin1 = S.map (unsafeChr . fromIntegral)
132-------------------------------------------------------------------------------
134-------------------------------------------------------------------------------
136-- | Encode a stream of Unicode characters to bytes by mapping each character
137-- to a byte in 0-255 range. Throws an error if the input stream contains
138-- characters beyond 255.
141{-# INLINE encodeLatin1' #-}
142encodeLatin1' :: (IsStream t, Monad m) => t m Char -> t m Word8
143encodeLatin1' = S.map convert
146 let codepoint = ord c
147 in if codepoint > 255
148 then error $ "Streamly.Unicode.encodeLatin1 invalid " ++
149 "input char codepoint " ++ show codepoint
150 else fromIntegral codepoint
152-- XXX Should we instead replace the invalid chars by NUL or whitespace or some
153-- other control char? That may affect the perf a bit but may be a better
156-- | Like 'encodeLatin1'' but silently maps input codepoints beyond 255 to
157-- arbitrary Latin1 chars in 0-255 range. No error or exception is thrown when
158-- such mapping occurs.
160-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
162-- /Since: 0.8.0 (Lenient Behaviour)/
163{-# INLINE encodeLatin1 #-}
164encodeLatin1 :: (IsStream t, Monad m) => t m Char -> t m Word8
165encodeLatin1 = S.map (fromIntegral . ord)
167-- | Like 'encodeLatin1' but drops the input characters beyond 255.
170{-# INLINE encodeLatin1_ #-}
171encodeLatin1_ :: (IsStream t, Monad m) => t m Char -> t m Word8
172encodeLatin1_ = S.map (fromIntegral . ord) . S.filter (<= chr 255)
174-- | Same as 'encodeLatin1'
176{-# DEPRECATED encodeLatin1Lax "Please use 'encodeLatin1' instead" #-}
177{-# INLINE encodeLatin1Lax #-}
178encodeLatin1Lax :: (IsStream t, Monad m) => t m Char -> t m Word8
179encodeLatin1Lax = encodeLatin1
181-------------------------------------------------------------------------------
183-------------------------------------------------------------------------------
185-- Int helps in cheaper conversion from Int to Char
187type DecodeState = Word8
189-- We can divide the errors in three general categories:
190-- * A non-starter was encountered in a begin state
191-- * A starter was encountered without completing a codepoint
192-- * The last codepoint was not complete (input underflow)
194-- Need to separate resumable and non-resumable error. In case of non-resumable
195-- error we can also provide the failing byte. In case of resumable error the
196-- state can be opaque.
198data DecodeError = DecodeError !DecodeState !CodePoint deriving Show
200-- See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
202-- XXX Use names decodeSuccess = 0, decodeFailure = 12
204decodeTable :: [Word8]
206 -- The first part of the table maps bytes to character classes that
207 -- to reduce the size of the transition table and create bitmasks.
208 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,
209 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,
210 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,
211 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,
212 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,
213 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,
214 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,
215 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,
217 -- The second part is a transition table that maps a combination
218 -- of a state of the automaton and a character class to a state.
219 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
220 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
221 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
222 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
223 12,36,12,12,12,12,12,12,12,12,12,12
226{-# NOINLINE utf8d #-}
227utf8d :: A.Array Word8
230 -- Aligning to cacheline makes a barely noticeable difference
231 -- XXX currently alignment is not implemented for unmanaged allocation
232 $ D.fold (A.writeNAlignedUnmanaged 64 (length decodeTable))
233 (D.fromList decodeTable)
235-- | Return element at the specified index without checking the bounds.
236-- and without touching the foreign ptr.
237{-# INLINE_NORMAL unsafePeekElemOff #-}
238unsafePeekElemOff :: forall a. Storable a => Ptr a -> Int -> a
239unsafePeekElemOff p i = let !x = unsafeInlineIO $ peekElemOff p i in x
241-- decode is split into two separate cases to avoid branching instructions.
242-- From the higher level flow we already know which case we are in so we can
243-- call the appropriate decode function.
245-- When the state is 0
246{-# INLINE decode0 #-}
247decode0 :: Ptr Word8 -> Word8 -> Tuple' DecodeState CodePoint
249 let !t = table `unsafePeekElemOff` fromIntegral byte
250 !codep' = (0xff `shiftR` fromIntegral t) .&. fromIntegral byte
251 !state' = table `unsafePeekElemOff` (256 + fromIntegral t)
252 in assert ((byte > 0x7f || error showByte)
253 && (state' /= 0 || error (showByte ++ showTable)))
254 (Tuple' state' codep')
259 let end = table `plusPtr` 364
260 in A.Array undefined table end :: A.Array Word8
261 showByte = "Streamly: decode0: byte: " ++ show byte
262 showTable = " table: " ++ show utf8table
264-- When the state is not 0
265{-# INLINE decode1 #-}
271 -> Tuple' DecodeState CodePoint
272decode1 table state codep byte =
273 -- Remember codep is Int type!
274 -- Can it be unsafe to convert the resulting Int to Char?
275 let !t = table `unsafePeekElemOff` fromIntegral byte
276 !codep' = (fromIntegral byte .&. 0x3f) .|. (codep `shiftL` 6)
277 !state' = table `unsafePeekElemOff`
278 (256 + fromIntegral state + fromIntegral t)
279 in assert (codep' <= 0x10FFFF
280 || error (showByte ++ showState state codep))
281 (Tuple' state' codep')
285 let end = table `plusPtr` 364
286 in A.Array undefined table end :: A.Array Word8
287 showByte = "Streamly: decode1: byte: " ++ show byte
289 " state: " ++ show st ++
290 " codepoint: " ++ show cp ++
291 " table: " ++ show utf8table
293-------------------------------------------------------------------------------
294-- Resumable UTF-8 decoding
295-------------------------------------------------------------------------------
297-- Strangely, GHCJS hangs linking template-haskell with this
299{-# ANN type UTF8DecodeState Fuse #-}
301data UTF8DecodeState s a
303 | UTF8DecodeInit1 s Word8
304 | UTF8DecodeFirst s Word8
305 | UTF8Decoding s !DecodeState !CodePoint
306 | YieldAndContinue a (UTF8DecodeState s a)
309{-# INLINE_NORMAL resumeDecodeUtf8EitherD #-}
310resumeDecodeUtf8EitherD
315 -> Stream m (Either DecodeError Char)
316resumeDecodeUtf8EitherD dst codep (Stream step state) =
317 let A.Array _ p _ = utf8d
321 then UTF8DecodeInit state
322 else UTF8Decoding state dst codep
323 in Stream (step' ptr) stt
325 {-# INLINE_LATE step' #-}
326 step' _ gst (UTF8DecodeInit st) = do
327 r <- step (adaptState gst) st
329 Yield x s -> Skip (UTF8DecodeInit1 s x)
330 Skip s -> Skip (UTF8DecodeInit s)
333 step' _ _ (UTF8DecodeInit1 st x) = do
334 -- Note: It is important to use a ">" instead of a "<=" test
335 -- here for GHC to generate code layout for default branch
336 -- prediction for the common case. This is fragile and might
337 -- change with the compiler versions, we need a more reliable
338 -- "likely" primitive to control branch predication.
341 return $ Skip $ YieldAndContinue
342 (Right $ unsafeChr (fromIntegral x))
344 -- Using a separate state here generates a jump to a
345 -- separate code block in the core which seems to perform
346 -- slightly better for the non-ascii case.
347 True -> return $ Skip $ UTF8DecodeFirst st x
349 -- XXX should we merge it with UTF8DecodeInit1?
350 step' table _ (UTF8DecodeFirst st x) = do
351 let (Tuple' sv cp) = decode0 table x
355 Skip $ YieldAndContinue (Left $ DecodeError 0 (fromIntegral x))
357 0 -> error "unreachable state"
358 _ -> Skip (UTF8Decoding st sv cp)
360 -- We recover by trying the new byte x a starter of a new codepoint.
361 -- XXX on error need to report the next byte "x" as well.
362 -- XXX need to use the same recovery in array decoding routine as well
363 step' table gst (UTF8Decoding st statePtr codepointPtr) = do
364 r <- step (adaptState gst) st
367 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
370 0 -> Skip $ YieldAndContinue (Right $ unsafeChr cp)
373 Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr)
374 (UTF8DecodeInit1 s x)
375 _ -> Skip (UTF8Decoding s sv cp)
376 Skip s -> return $ Skip (UTF8Decoding s statePtr codepointPtr)
377 Stop -> return $ Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr) Done
379 step' _ _ (YieldAndContinue c s) = return $ Yield c s
380 step' _ _ Done = return Stop
382-- XXX We can use just one API, and define InitState = 0 and InitCodePoint = 0
383-- to use as starting state.
385{-# INLINE_NORMAL decodeUtf8EitherD #-}
386decodeUtf8EitherD :: Monad m
387 => Stream m Word8 -> Stream m (Either DecodeError Char)
388decodeUtf8EitherD = resumeDecodeUtf8EitherD 0 0
393{-# INLINE decodeUtf8Either #-}
394decodeUtf8Either :: (Monad m, IsStream t)
395 => t m Word8 -> t m (Either DecodeError Char)
396decodeUtf8Either = fromStreamD . decodeUtf8EitherD . toStreamD
401{-# INLINE resumeDecodeUtf8Either #-}
402resumeDecodeUtf8Either
403 :: (Monad m, IsStream t)
407 -> t m (Either DecodeError Char)
408resumeDecodeUtf8Either st cp =
409 fromStreamD . resumeDecodeUtf8EitherD st cp . toStreamD
411-------------------------------------------------------------------------------
413-------------------------------------------------------------------------------
415data CodingFailureMode
416 = TransliterateCodingFailure
417 | ErrorOnCodingFailure
418 | DropOnCodingFailure
421{-# INLINE replacementChar #-}
422replacementChar :: Char
423replacementChar = '\xFFFD'
425-- XXX write it as a parser and use parseMany to decode a stream, need to check
426-- if that preserves the same performance. Or we can use a resumable parser
427-- that parses a chunk at a time.
429-- XXX Implement this in terms of decodeUtf8Either. Need to make sure that
430-- decodeUtf8Either preserves the performance characterstics.
432{-# INLINE_NORMAL decodeUtf8WithD #-}
433decodeUtf8WithD :: Monad m
434 => CodingFailureMode -> Stream m Word8 -> Stream m Char
435decodeUtf8WithD cfm (Stream step state) =
436 let A.Array _ ptr _ = utf8d
437 in Stream (step' ptr) (UTF8DecodeInit state)
441 prefix = "Streamly.Internal.Data.Stream.StreamD.decodeUtf8With: "
443 {-# INLINE handleError #-}
446 ErrorOnCodingFailure -> error e
447 TransliterateCodingFailure -> YieldAndContinue replacementChar s
448 DropOnCodingFailure -> s
450 {-# INLINE handleUnderflow #-}
453 ErrorOnCodingFailure -> error $ prefix ++ "Not enough input"
454 TransliterateCodingFailure -> YieldAndContinue replacementChar Done
455 DropOnCodingFailure -> Done
457 {-# INLINE_LATE step' #-}
458 step' _ gst (UTF8DecodeInit st) = do
459 r <- step (adaptState gst) st
461 Yield x s -> Skip (UTF8DecodeInit1 s x)
462 Skip s -> Skip (UTF8DecodeInit s)
465 step' _ _ (UTF8DecodeInit1 st x) = do
466 -- Note: It is important to use a ">" instead of a "<=" test
467 -- here for GHC to generate code layout for default branch
468 -- prediction for the common case. This is fragile and might
469 -- change with the compiler versions, we need a more reliable
470 -- "likely" primitive to control branch predication.
473 return $ Skip $ YieldAndContinue
474 (unsafeChr (fromIntegral x))
476 -- Using a separate state here generates a jump to a
477 -- separate code block in the core which seems to perform
478 -- slightly better for the non-ascii case.
479 True -> return $ Skip $ UTF8DecodeFirst st x
481 -- XXX should we merge it with UTF8DecodeInit1?
482 step' table _ (UTF8DecodeFirst st x) = do
483 let (Tuple' sv cp) = decode0 table x
487 let msg = prefix ++ "Invalid first UTF8 byte " ++ show x
488 in Skip $ handleError msg (UTF8DecodeInit st)
489 0 -> error "unreachable state"
490 _ -> Skip (UTF8Decoding st sv cp)
492 -- We recover by trying the new byte x as a starter of a new codepoint.
493 -- XXX need to use the same recovery in array decoding routine as well
494 step' table gst (UTF8Decoding st statePtr codepointPtr) = do
495 r <- step (adaptState gst) st
498 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
500 0 -> Skip $ YieldAndContinue
501 (unsafeChr cp) (UTF8DecodeInit s)
504 ++ "Invalid subsequent UTF8 byte "
508 ++ " accumulated value "
510 in Skip $ handleError msg (UTF8DecodeInit1 s x)
511 _ -> Skip (UTF8Decoding s sv cp)
513 Skip (UTF8Decoding s statePtr codepointPtr)
514 Stop -> return $ Skip handleUnderflow
516 step' _ _ (YieldAndContinue c s) = return $ Yield c s
517 step' _ _ Done = return Stop
519{-# INLINE decodeUtf8D #-}
520decodeUtf8D :: Monad m => Stream m Word8 -> Stream m Char
521decodeUtf8D = decodeUtf8WithD TransliterateCodingFailure
523-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
524-- Any invalid codepoint encountered is replaced with the unicode replacement
527-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
529-- /Since: 0.8.0 (Lenient Behaviour)/
530{-# INLINE decodeUtf8 #-}
531decodeUtf8 :: (Monad m, IsStream t) => t m Word8 -> t m Char
532decodeUtf8 = fromStreamD . decodeUtf8D . toStreamD
534{-# INLINE decodeUtf8D' #-}
535decodeUtf8D' :: Monad m => Stream m Word8 -> Stream m Char
536decodeUtf8D' = decodeUtf8WithD ErrorOnCodingFailure
538-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
539-- The function throws an error if an invalid codepoint is encountered.
542{-# INLINE decodeUtf8' #-}
543decodeUtf8' :: (Monad m, IsStream t) => t m Word8 -> t m Char
544decodeUtf8' = fromStreamD . decodeUtf8D' . toStreamD
546{-# INLINE decodeUtf8D_ #-}
547decodeUtf8D_ :: Monad m => Stream m Word8 -> Stream m Char
548decodeUtf8D_ = decodeUtf8WithD DropOnCodingFailure
550-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
551-- Any invalid codepoint encountered is dropped.
554{-# INLINE decodeUtf8_ #-}
555decodeUtf8_ :: (Monad m, IsStream t) => t m Word8 -> t m Char
556decodeUtf8_ = fromStreamD . decodeUtf8D_ . toStreamD
558-- | Same as 'decodeUtf8'
560{-# DEPRECATED decodeUtf8Lax "Please use 'decodeUtf8' instead" #-}
561{-# INLINE decodeUtf8Lax #-}
562decodeUtf8Lax :: (IsStream t, Monad m) => t m Word8 -> t m Char
563decodeUtf8Lax = decodeUtf8
565-------------------------------------------------------------------------------
566-- Decoding Array Streams
567-------------------------------------------------------------------------------
570{-# ANN type FlattenState Fuse #-}
573 = OuterLoop s !(Maybe (DecodeState, CodePoint))
574 | InnerLoopDecodeInit s ArrayContents !(Ptr a) !(Ptr a)
575 | InnerLoopDecodeFirst s ArrayContents !(Ptr a) !(Ptr a) Word8
576 | InnerLoopDecoding s ArrayContents !(Ptr a) !(Ptr a)
577 !DecodeState !CodePoint
578 | YAndC !Char (FlattenState s a) -- These constructors can be
579 -- encoded in the UTF8DecodeState
580 -- type, I prefer to keep these
581 -- flat even though that means
582 -- coming up with new names
585-- The normal decodeUtf8 above should fuse with flattenArrays
586-- to create this exact code but it doesn't for some reason, as of now this
587-- remains the fastest way I could figure out to decodeUtf8.
589-- XXX Add Proper error messages
590{-# INLINE_NORMAL decodeUtf8ArraysWithD #-}
591decodeUtf8ArraysWithD ::
594 -> Stream m (A.Array Word8)
596decodeUtf8ArraysWithD cfm (Stream step state) =
597 let A.Array _ ptr _ = utf8d
598 in Stream (step' ptr) (OuterLoop state Nothing)
600 {-# INLINE transliterateOrError #-}
601 transliterateOrError e s =
603 ErrorOnCodingFailure -> error e
604 TransliterateCodingFailure -> YAndC replacementChar s
605 DropOnCodingFailure -> s
606 {-# INLINE inputUnderflow #-}
609 ErrorOnCodingFailure ->
611 show "Streamly.Internal.Data.Stream.StreamD."
612 ++ "decodeUtf8ArraysWith: Input Underflow"
613 TransliterateCodingFailure -> YAndC replacementChar D
614 DropOnCodingFailure -> D
615 {-# INLINE_LATE step' #-}
616 step' _ gst (OuterLoop st Nothing) = do
617 r <- step (adaptState gst) st
620 Yield A.Array {..} s ->
621 Skip (InnerLoopDecodeInit s arrContents arrStart aEnd)
622 Skip s -> Skip (OuterLoop s Nothing)
624 step' _ gst (OuterLoop st dst@(Just (ds, cp))) = do
625 r <- step (adaptState gst) st
628 Yield A.Array {..} s ->
629 Skip (InnerLoopDecoding s arrContents arrStart aEnd ds cp)
630 Skip s -> Skip (OuterLoop s dst)
631 Stop -> Skip inputUnderflow
632 step' _ _ (InnerLoopDecodeInit st startf p end)
634 liftIO $ touch startf
635 return $ Skip $ OuterLoop st Nothing
636 step' _ _ (InnerLoopDecodeInit st startf p end) = do
638 -- Note: It is important to use a ">" instead of a "<=" test here for
639 -- GHC to generate code layout for default branch prediction for the
640 -- common case. This is fragile and might change with the compiler
641 -- versions, we need a more reliable "likely" primitive to control
642 -- branch predication.
645 return $ Skip $ YAndC
646 (unsafeChr (fromIntegral x))
647 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
648 -- Using a separate state here generates a jump to a separate code
649 -- block in the core which seems to perform slightly better for the
651 True -> return $ Skip $ InnerLoopDecodeFirst st startf p end x
653 step' table _ (InnerLoopDecodeFirst st startf p end x) = do
654 let (Tuple' sv cp) = decode0 table x
661 "Streamly.Internal.Data.Stream.StreamD."
662 ++ "decodeUtf8ArraysWith: Invalid UTF8"
663 ++ " codepoint encountered"
665 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
666 0 -> error "unreachable state"
667 _ -> Skip (InnerLoopDecoding st startf (p `plusPtr` 1) end sv cp)
668 step' _ _ (InnerLoopDecoding st startf p end sv cp)
670 liftIO $ touch startf
671 return $ Skip $ OuterLoop st (Just (sv, cp))
672 step' table _ (InnerLoopDecoding st startf p end statePtr codepointPtr) = do
674 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
681 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
686 "Streamly.Internal.Data.Stream.StreamD."
687 ++ "decodeUtf8ArraysWith: Invalid UTF8"
688 ++ " codepoint encountered"
690 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
693 (InnerLoopDecoding st startf (p `plusPtr` 1) end sv cp)
694 step' _ _ (YAndC c s) = return $ Yield c s
695 step' _ _ D = return Stop
697{-# INLINE decodeUtf8ArraysD #-}
700 => Stream m (A.Array Word8)
702decodeUtf8ArraysD = decodeUtf8ArraysWithD TransliterateCodingFailure
707{-# INLINE decodeUtf8Arrays #-}
709 (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
711 fromStreamD . decodeUtf8ArraysD . toStreamD
713{-# INLINE decodeUtf8ArraysD' #-}
716 => Stream m (A.Array Word8)
718decodeUtf8ArraysD' = decodeUtf8ArraysWithD ErrorOnCodingFailure
723{-# INLINE decodeUtf8Arrays' #-}
724decodeUtf8Arrays' :: (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
725decodeUtf8Arrays' = fromStreamD . decodeUtf8ArraysD' . toStreamD
727{-# INLINE decodeUtf8ArraysD_ #-}
730 => Stream m (A.Array Word8)
732decodeUtf8ArraysD_ = decodeUtf8ArraysWithD DropOnCodingFailure
737{-# INLINE decodeUtf8Arrays_ #-}
739 (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
741 fromStreamD . decodeUtf8ArraysD_ . toStreamD
743-------------------------------------------------------------------------------
744-- Encoding Unicode (UTF-8) Characters
745-------------------------------------------------------------------------------
747data WList = WCons !Word8 !WList | WNil
749-- UTF-8 primitives, Lifted from GHC.IO.Encoding.UTF8.
753ord2 c = assert (n >= 0x80 && n <= 0x07ff) (WCons x1 (WCons x2 WNil))
756 x1 = fromIntegral $ (n `shiftR` 6) + 0xC0
757 x2 = fromIntegral $ (n .&. 0x3F) + 0x80
761ord3 c = assert (n >= 0x0800 && n <= 0xffff) (WCons x1 (WCons x2 (WCons x3 WNil)))
764 x1 = fromIntegral $ (n `shiftR` 12) + 0xE0
765 x2 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80
766 x3 = fromIntegral $ (n .&. 0x3F) + 0x80
770ord4 c = assert (n >= 0x10000) (WCons x1 (WCons x2 (WCons x3 (WCons x4 WNil))))
773 x1 = fromIntegral $ (n `shiftR` 18) + 0xF0
774 x2 = fromIntegral $ ((n `shiftR` 12) .&. 0x3F) + 0x80
775 x3 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80
776 x4 = fromIntegral $ (n .&. 0x3F) + 0x80
779{-# ANN type EncodeState Fuse #-}
781data EncodeState s = EncodeState s !WList
784{-# ANN type InvalidAction Fuse #-}
787 DropInvalid | ErrorInvalid | IgnoreInvalid | ReplaceInvalid
789replaceInvalid :: s -> Step (EncodeState s) a
791 Skip $ EncodeState s (WCons 239 (WCons 191 (WCons 189 WNil)))
793dropInvalid :: s -> Step (EncodeState s) a
794dropInvalid s = Skip (EncodeState s WNil)
796errorOnInvalid :: s -> Step (EncodeState s) a
799 show "Streamly.Internal.Data.Stream.StreamD.encodeUtf8:"
800 ++ "Encountered a surrogate"
802{-# INLINE_NORMAL encodeUtf8DGeneric #-}
808encodeUtf8DGeneric act (Stream step state) =
809 Stream step' (EncodeState state WNil)
813 {-# INLINE_LATE step' #-}
814 step' gst (EncodeState st WNil) = do
815 r <- step (adaptState gst) st
821 Yield (fromIntegral x) (EncodeState s WNil)
822 | x <= 0x7FF -> Skip (EncodeState s (ord2 c))
828 else Skip (EncodeState s (ord3 c))
832 then errorOnInvalid s
833 else Skip (EncodeState s (ord3 c))
836 Skip (EncodeState s (ord3 c))
840 then replaceInvalid s
841 else Skip (EncodeState s (ord3 c))
843 | otherwise -> Skip (EncodeState s (ord4 c))
844 Skip s -> Skip (EncodeState s WNil)
846 step' _ (EncodeState s (WCons x xs)) = return $ Yield x (EncodeState s xs)
848-- More yield points improve performance, but I am not sure if they can cause
849-- too much code bloat or some trouble with fusion. So keeping only two yield
850-- points for now, one for the ascii chars (fast path) and one for all other
852{-# INLINE_NORMAL encodeUtf8D' #-}
853encodeUtf8D' :: Monad m => Stream m Char -> Stream m Word8
854encodeUtf8D' = encodeUtf8DGeneric ErrorInvalid
856-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. When
857-- any invalid character (U+D800-U+D8FF) is encountered in the input stream the
858-- function errors out.
861{-# INLINE encodeUtf8' #-}
862encodeUtf8' :: (Monad m, IsStream t) => t m Char -> t m Word8
863encodeUtf8' = fromStreamD . encodeUtf8D' . toStreamD
865-- | See section "3.9 Unicode Encoding Forms" in
866-- https://www.unicode.org/versions/Unicode13.0.0/UnicodeStandard-13.0.pdf
868{-# INLINE_NORMAL encodeUtf8D #-}
869encodeUtf8D :: Monad m => Stream m Char -> Stream m Word8
870encodeUtf8D = encodeUtf8DGeneric ReplaceInvalid
872-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. Any
873-- Invalid characters (U+D800-U+D8FF) in the input stream are replaced by the
874-- Unicode replacement character U+FFFD.
876-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
878-- /Since: 0.8.0 (Lenient Behaviour)/
879{-# INLINE encodeUtf8 #-}
880encodeUtf8 :: (Monad m, IsStream t) => t m Char -> t m Word8
881encodeUtf8 = fromStreamD . encodeUtf8D . toStreamD
883{-# INLINE_NORMAL encodeUtf8D_ #-}
884encodeUtf8D_ :: Monad m => Stream m Char -> Stream m Word8
885encodeUtf8D_ = encodeUtf8DGeneric DropInvalid
887-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. Any
888-- Invalid characters (U+D800-U+D8FF) in the input stream are dropped.
891{-# INLINE encodeUtf8_ #-}
892encodeUtf8_ :: (Monad m, IsStream t) => t m Char -> t m Word8
893encodeUtf8_ = fromStreamD . encodeUtf8D_ . toStreamD
895-- | Same as 'encodeUtf8'
897{-# DEPRECATED encodeUtf8Lax "Please use 'encodeUtf8' instead" #-}
898{-# INLINE encodeUtf8Lax #-}
899encodeUtf8Lax :: (IsStream t, Monad m) => t m Char -> t m Word8
900encodeUtf8Lax = encodeUtf8
902-------------------------------------------------------------------------------
903-- Encode streams of containers
904-------------------------------------------------------------------------------
906-- | Encode a container to @Array Word8@ provided an unfold to covert it to a
907-- Char stream and an encoding function.
910{-# INLINE encodeObject #-}
911encodeObject :: MonadIO m =>
912 (SerialT m Char -> SerialT m Word8)
916encodeObject encode u = S.fold Array.write . encode . S.unfold u
918-- | Encode a stream of container objects using the supplied encoding scheme.
919-- Each object is encoded as an @Array Word8@.
922{-# INLINE encodeObjects #-}
923encodeObjects :: (MonadIO m, IsStream t) =>
924 (SerialT m Char -> SerialT m Word8)
928encodeObjects encode u = adapt . Serial.mapM (encodeObject encode u) . adapt
930-- | Encode a stream of 'String' using the supplied encoding scheme. Each
931-- string is encoded as an @Array Word8@.
934{-# INLINE encodeStrings #-}
935encodeStrings :: (MonadIO m, IsStream t) =>
936 (SerialT m Char -> SerialT m Word8) -> t m String -> t m (Array Word8)
937encodeStrings encode = encodeObjects encode Unfold.fromList
940-------------------------------------------------------------------------------
941-- Utility operations on strings
942-------------------------------------------------------------------------------
944strip :: IsStream t => t m Char -> t m Char
947stripTail :: IsStream t => t m Char -> t m Char
951-- | Remove leading whitespace from a string.
953-- > stripHead = S.dropWhile isSpace
956{-# INLINE stripHead #-}
957stripHead :: (Monad m, IsStream t) => t m Char -> t m Char
958stripHead = S.dropWhile isSpace
960-- | Fold each line of the stream using the supplied 'Fold'
961-- and stream the result.
963-- >>> Stream.toList $ lines Fold.toList (Stream.fromList "lines\nthis\nstring\n\n\n")
964-- ["lines","this","string","",""]
966-- > lines = S.splitOnSuffix (== '\n')
970lines :: (Monad m, IsStream t) => Fold m Char b -> t m Char -> t m b
971lines = S.splitOnSuffix (== '\n')
973foreign import ccall unsafe "u_iswspace"
974 iswspace :: Int -> Int
976-- | Code copied from base/Data.Char to INLINE it
977{-# INLINE isSpace #-}
978isSpace :: Char -> Bool
980 | uc <= 0x377 = uc == 32 || uc - 0x9 <= 4 || uc == 0xa0
981 | otherwise = iswspace (ord c) /= 0
983 uc = fromIntegral (ord c) :: Word
985-- | Fold each word of the stream using the supplied 'Fold'
986-- and stream the result.
988-- >>> Stream.toList $ words Fold.toList (Stream.fromList "fold these words")
989-- ["fold","these","words"]
991-- > words = S.wordsBy isSpace
995words :: (Monad m, IsStream t) => Fold m Char b -> t m Char -> t m b
996words = S.wordsBy isSpace
998-- | Unfold a stream to character streams using the supplied 'Unfold'
999-- and concat the results suffixing a newline character @\\n@ to each stream.
1002-- unlines = Stream.interposeSuffix '\n'
1003-- unlines = Stream.intercalateSuffix Unfold.fromList "\n"
1007{-# INLINE unlines #-}
1008unlines :: (MonadIO m, IsStream t) => Unfold m a Char -> t m a -> t m Char
1009unlines = S.interposeSuffix '\n'
1011-- | Unfold the elements of a stream to character streams using the supplied
1012-- 'Unfold' and concat the results with a whitespace character infixed between
1016-- unwords = Stream.interpose ' '
1017-- unwords = Stream.intercalate Unfold.fromList " "
1021{-# INLINE unwords #-}
1022unwords :: (MonadIO m, IsStream t) => Unfold m a Char -> t m a -> t m Char
1023unwords = S.interpose ' '
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list