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.ForeignPtr (touchForeignPtr)
85import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
86import Foreign.Storable (Storable(..))
87import Fusion.Plugin.Types (Fuse(..))
88import GHC.Base (assert, unsafeChr)
89import GHC.ForeignPtr (ForeignPtr (..))
90import GHC.IO.Encoding.Failure (isSurrogate)
91import GHC.Ptr (Ptr (..), plusPtr)
92import System.IO.Unsafe (unsafePerformIO)
93import Streamly.Internal.Data.Array.Foreign (Array)
94import Streamly.Internal.Data.Fold (Fold)
95import Streamly.Internal.Data.Stream.IsStream.Type
96 (IsStream, fromStreamD, toStreamD, adapt)
97import Streamly.Internal.Data.Stream.Serial (SerialT)
98import Streamly.Internal.Data.Stream.StreamD (Stream(..), Step (..))
99import Streamly.Internal.Data.SVar (adaptState)
100import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
101import Streamly.Internal.Data.Unfold (Unfold)
102import Streamly.Internal.System.IO (unsafeInlineIO)
104import qualified Streamly.Internal.Data.Unfold as Unfold
105import qualified Streamly.Internal.Data.Stream.Serial as Serial
106import qualified Streamly.Internal.Data.Array.Foreign as Array
107import qualified Streamly.Internal.Data.Array.Foreign.Type as A
108import qualified Streamly.Internal.Data.Stream.IsStream as S
109import qualified Streamly.Internal.Data.Stream.StreamD as D
111import Prelude hiding (lines, words, unlines, unwords)
115-- >>> import Prelude hiding (lines, words, unlines, unwords)
116-- >>> import qualified Streamly.Prelude as Stream
117-- >>> import qualified Streamly.Data.Fold as Fold
118-- >>> import Streamly.Internal.Unicode.Stream
120-------------------------------------------------------------------------------
122-------------------------------------------------------------------------------
124-- | Decode a stream of bytes to Unicode characters by mapping each byte to a
125-- corresponding Unicode 'Char' in 0-255 range.
127-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
130{-# INLINE decodeLatin1 #-}
131decodeLatin1 :: (IsStream t, Monad m) => t m Word8 -> t m Char
132decodeLatin1 = S.map (unsafeChr . fromIntegral)
134-------------------------------------------------------------------------------
136-------------------------------------------------------------------------------
138-- | Encode a stream of Unicode characters to bytes by mapping each character
139-- to a byte in 0-255 range. Throws an error if the input stream contains
140-- characters beyond 255.
143{-# INLINE encodeLatin1' #-}
144encodeLatin1' :: (IsStream t, Monad m) => t m Char -> t m Word8
145encodeLatin1' = S.map convert
148 let codepoint = ord c
149 in if codepoint > 255
150 then error $ "Streamly.Unicode.encodeLatin1 invalid " ++
151 "input char codepoint " ++ show codepoint
152 else fromIntegral codepoint
154-- XXX Should we instead replace the invalid chars by NUL or whitespace or some
155-- other control char? That may affect the perf a bit but may be a better
158-- | Like 'encodeLatin1'' but silently maps input codepoints beyond 255 to
159-- arbitrary Latin1 chars in 0-255 range. No error or exception is thrown when
160-- such mapping occurs.
162-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
164-- /Since: 0.8.0 (Lenient Behaviour)/
165{-# INLINE encodeLatin1 #-}
166encodeLatin1 :: (IsStream t, Monad m) => t m Char -> t m Word8
167encodeLatin1 = S.map (fromIntegral . ord)
169-- | Like 'encodeLatin1' but drops the input characters beyond 255.
172{-# INLINE encodeLatin1_ #-}
173encodeLatin1_ :: (IsStream t, Monad m) => t m Char -> t m Word8
174encodeLatin1_ = S.map (fromIntegral . ord) . S.filter (<= chr 255)
176-- | Same as 'encodeLatin1'
178{-# DEPRECATED encodeLatin1Lax "Please use 'encodeLatin1' instead" #-}
179{-# INLINE encodeLatin1Lax #-}
180encodeLatin1Lax :: (IsStream t, Monad m) => t m Char -> t m Word8
181encodeLatin1Lax = encodeLatin1
183-------------------------------------------------------------------------------
185-------------------------------------------------------------------------------
187-- Int helps in cheaper conversion from Int to Char
189type DecodeState = Word8
191-- We can divide the errors in three general categories:
192-- * A non-starter was encountered in a begin state
193-- * A starter was encountered without completing a codepoint
194-- * The last codepoint was not complete (input underflow)
196-- Need to separate resumable and non-resumable error. In case of non-resumable
197-- error we can also provide the failing byte. In case of resumable error the
198-- state can be opaque.
200data DecodeError = DecodeError !DecodeState !CodePoint deriving Show
202-- See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
204-- XXX Use names decodeSuccess = 0, decodeFailure = 12
206decodeTable :: [Word8]
208 -- The first part of the table maps bytes to character classes that
209 -- to reduce the size of the transition table and create bitmasks.
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 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,
213 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,
214 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,
215 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,
216 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,
217 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,
219 -- The second part is a transition table that maps a combination
220 -- of a state of the automaton and a character class to a state.
221 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
222 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
223 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
224 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
225 12,36,12,12,12,12,12,12,12,12,12,12
228{-# NOINLINE utf8d #-}
229utf8d :: A.Array Word8
232 -- Aligning to cacheline makes a barely noticeable difference
233 -- XXX currently alignment is not implemented for unmanaged allocation
234 $ D.fold (A.writeNAlignedUnmanaged 64 (length decodeTable))
235 (D.fromList decodeTable)
237-- | Return element at the specified index without checking the bounds.
238-- and without touching the foreign ptr.
239{-# INLINE_NORMAL unsafePeekElemOff #-}
240unsafePeekElemOff :: forall a. Storable a => Ptr a -> Int -> a
241unsafePeekElemOff p i = let !x = unsafeInlineIO $ peekElemOff p i in x
243-- decode is split into two separate cases to avoid branching instructions.
244-- From the higher level flow we already know which case we are in so we can
245-- call the appropriate decode function.
247-- When the state is 0
248{-# INLINE decode0 #-}
249decode0 :: Ptr Word8 -> Word8 -> Tuple' DecodeState CodePoint
251 let !t = table `unsafePeekElemOff` fromIntegral byte
252 !codep' = (0xff `shiftR` fromIntegral t) .&. fromIntegral byte
253 !state' = table `unsafePeekElemOff` (256 + fromIntegral t)
254 in assert ((byte > 0x7f || error showByte)
255 && (state' /= 0 || error (showByte ++ showTable)))
256 (Tuple' state' codep')
261 let !(Ptr addr) = table
262 end = table `plusPtr` 364
263 in A.Array (ForeignPtr addr undefined) end :: A.Array Word8
264 showByte = "Streamly: decode0: byte: " ++ show byte
265 showTable = " table: " ++ show utf8table
267-- When the state is not 0
268{-# INLINE decode1 #-}
274 -> Tuple' DecodeState CodePoint
275decode1 table state codep byte =
276 -- Remember codep is Int type!
277 -- Can it be unsafe to convert the resulting Int to Char?
278 let !t = table `unsafePeekElemOff` fromIntegral byte
279 !codep' = (fromIntegral byte .&. 0x3f) .|. (codep `shiftL` 6)
280 !state' = table `unsafePeekElemOff`
281 (256 + fromIntegral state + fromIntegral t)
282 in assert (codep' <= 0x10FFFF
283 || error (showByte ++ showState state codep))
284 (Tuple' state' codep')
288 let !(Ptr addr) = table
289 end = table `plusPtr` 364
290 in A.Array (ForeignPtr addr undefined) end :: A.Array Word8
291 showByte = "Streamly: decode1: byte: " ++ show byte
293 " state: " ++ show st ++
294 " codepoint: " ++ show cp ++
295 " table: " ++ show utf8table
297-------------------------------------------------------------------------------
298-- Resumable UTF-8 decoding
299-------------------------------------------------------------------------------
301-- Strangely, GHCJS hangs linking template-haskell with this
303{-# ANN type UTF8DecodeState Fuse #-}
305data UTF8DecodeState s a
307 | UTF8DecodeInit1 s Word8
308 | UTF8DecodeFirst s Word8
309 | UTF8Decoding s !DecodeState !CodePoint
310 | YieldAndContinue a (UTF8DecodeState s a)
313{-# INLINE_NORMAL resumeDecodeUtf8EitherD #-}
314resumeDecodeUtf8EitherD
319 -> Stream m (Either DecodeError Char)
320resumeDecodeUtf8EitherD dst codep (Stream step state) =
321 let A.Array p _ = utf8d
322 !ptr = unsafeForeignPtrToPtr p
325 then UTF8DecodeInit state
326 else UTF8Decoding state dst codep
327 in Stream (step' ptr) stt
329 {-# INLINE_LATE step' #-}
330 step' _ gst (UTF8DecodeInit st) = do
331 r <- step (adaptState gst) st
333 Yield x s -> Skip (UTF8DecodeInit1 s x)
334 Skip s -> Skip (UTF8DecodeInit s)
337 step' _ _ (UTF8DecodeInit1 st x) = do
338 -- Note: It is important to use a ">" instead of a "<=" test
339 -- here for GHC to generate code layout for default branch
340 -- prediction for the common case. This is fragile and might
341 -- change with the compiler versions, we need a more reliable
342 -- "likely" primitive to control branch predication.
345 return $ Skip $ YieldAndContinue
346 (Right $ unsafeChr (fromIntegral x))
348 -- Using a separate state here generates a jump to a
349 -- separate code block in the core which seems to perform
350 -- slightly better for the non-ascii case.
351 True -> return $ Skip $ UTF8DecodeFirst st x
353 -- XXX should we merge it with UTF8DecodeInit1?
354 step' table _ (UTF8DecodeFirst st x) = do
355 let (Tuple' sv cp) = decode0 table x
359 Skip $ YieldAndContinue (Left $ DecodeError 0 (fromIntegral x))
361 0 -> error "unreachable state"
362 _ -> Skip (UTF8Decoding st sv cp)
364 -- We recover by trying the new byte x a starter of a new codepoint.
365 -- XXX on error need to report the next byte "x" as well.
366 -- XXX need to use the same recovery in array decoding routine as well
367 step' table gst (UTF8Decoding st statePtr codepointPtr) = do
368 r <- step (adaptState gst) st
371 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
374 0 -> Skip $ YieldAndContinue (Right $ unsafeChr cp)
377 Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr)
378 (UTF8DecodeInit1 s x)
379 _ -> Skip (UTF8Decoding s sv cp)
380 Skip s -> return $ Skip (UTF8Decoding s statePtr codepointPtr)
381 Stop -> return $ Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr) Done
383 step' _ _ (YieldAndContinue c s) = return $ Yield c s
384 step' _ _ Done = return Stop
386-- XXX We can use just one API, and define InitState = 0 and InitCodePoint = 0
387-- to use as starting state.
389{-# INLINE_NORMAL decodeUtf8EitherD #-}
390decodeUtf8EitherD :: Monad m
391 => Stream m Word8 -> Stream m (Either DecodeError Char)
392decodeUtf8EitherD = resumeDecodeUtf8EitherD 0 0
397{-# INLINE decodeUtf8Either #-}
398decodeUtf8Either :: (Monad m, IsStream t)
399 => t m Word8 -> t m (Either DecodeError Char)
400decodeUtf8Either = fromStreamD . decodeUtf8EitherD . toStreamD
405{-# INLINE resumeDecodeUtf8Either #-}
406resumeDecodeUtf8Either
407 :: (Monad m, IsStream t)
411 -> t m (Either DecodeError Char)
412resumeDecodeUtf8Either st cp =
413 fromStreamD . resumeDecodeUtf8EitherD st cp . toStreamD
415-------------------------------------------------------------------------------
417-------------------------------------------------------------------------------
419data CodingFailureMode
420 = TransliterateCodingFailure
421 | ErrorOnCodingFailure
422 | DropOnCodingFailure
425{-# INLINE replacementChar #-}
426replacementChar :: Char
427replacementChar = '\xFFFD'
429-- XXX write it as a parser and use parseMany to decode a stream, need to check
430-- if that preserves the same performance. Or we can use a resumable parser
431-- that parses a chunk at a time.
433-- XXX Implement this in terms of decodeUtf8Either. Need to make sure that
434-- decodeUtf8Either preserves the performance characterstics.
436{-# INLINE_NORMAL decodeUtf8WithD #-}
437decodeUtf8WithD :: Monad m
438 => CodingFailureMode -> Stream m Word8 -> Stream m Char
439decodeUtf8WithD cfm (Stream step state) =
440 let A.Array p _ = utf8d
441 !ptr = unsafeForeignPtrToPtr p
442 in Stream (step' ptr) (UTF8DecodeInit state)
446 prefix = "Streamly.Internal.Data.Stream.StreamD.decodeUtf8With: "
448 {-# INLINE handleError #-}
451 ErrorOnCodingFailure -> error e
452 TransliterateCodingFailure -> YieldAndContinue replacementChar s
453 DropOnCodingFailure -> s
455 {-# INLINE handleUnderflow #-}
458 ErrorOnCodingFailure -> error $ prefix ++ "Not enough input"
459 TransliterateCodingFailure -> YieldAndContinue replacementChar Done
460 DropOnCodingFailure -> Done
462 {-# INLINE_LATE step' #-}
463 step' _ gst (UTF8DecodeInit st) = do
464 r <- step (adaptState gst) st
466 Yield x s -> Skip (UTF8DecodeInit1 s x)
467 Skip s -> Skip (UTF8DecodeInit s)
470 step' _ _ (UTF8DecodeInit1 st x) = do
471 -- Note: It is important to use a ">" instead of a "<=" test
472 -- here for GHC to generate code layout for default branch
473 -- prediction for the common case. This is fragile and might
474 -- change with the compiler versions, we need a more reliable
475 -- "likely" primitive to control branch predication.
478 return $ Skip $ YieldAndContinue
479 (unsafeChr (fromIntegral x))
481 -- Using a separate state here generates a jump to a
482 -- separate code block in the core which seems to perform
483 -- slightly better for the non-ascii case.
484 True -> return $ Skip $ UTF8DecodeFirst st x
486 -- XXX should we merge it with UTF8DecodeInit1?
487 step' table _ (UTF8DecodeFirst st x) = do
488 let (Tuple' sv cp) = decode0 table x
492 let msg = prefix ++ "Invalid first UTF8 byte " ++ show x
493 in Skip $ handleError msg (UTF8DecodeInit st)
494 0 -> error "unreachable state"
495 _ -> Skip (UTF8Decoding st sv cp)
497 -- We recover by trying the new byte x as a starter of a new codepoint.
498 -- XXX need to use the same recovery in array decoding routine as well
499 step' table gst (UTF8Decoding st statePtr codepointPtr) = do
500 r <- step (adaptState gst) st
503 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
505 0 -> Skip $ YieldAndContinue
506 (unsafeChr cp) (UTF8DecodeInit s)
509 ++ "Invalid subsequent UTF8 byte "
513 ++ " accumulated value "
515 in Skip $ handleError msg (UTF8DecodeInit1 s x)
516 _ -> Skip (UTF8Decoding s sv cp)
518 Skip (UTF8Decoding s statePtr codepointPtr)
519 Stop -> return $ Skip handleUnderflow
521 step' _ _ (YieldAndContinue c s) = return $ Yield c s
522 step' _ _ Done = return Stop
524{-# INLINE decodeUtf8D #-}
525decodeUtf8D :: Monad m => Stream m Word8 -> Stream m Char
526decodeUtf8D = decodeUtf8WithD TransliterateCodingFailure
528-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
529-- Any invalid codepoint encountered is replaced with the unicode replacement
532-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
534-- /Since: 0.8.0 (Lenient Behaviour)/
535{-# INLINE decodeUtf8 #-}
536decodeUtf8 :: (Monad m, IsStream t) => t m Word8 -> t m Char
537decodeUtf8 = fromStreamD . decodeUtf8D . toStreamD
539{-# INLINE decodeUtf8D' #-}
540decodeUtf8D' :: Monad m => Stream m Word8 -> Stream m Char
541decodeUtf8D' = decodeUtf8WithD ErrorOnCodingFailure
543-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
544-- The function throws an error if an invalid codepoint is encountered.
547{-# INLINE decodeUtf8' #-}
548decodeUtf8' :: (Monad m, IsStream t) => t m Word8 -> t m Char
549decodeUtf8' = fromStreamD . decodeUtf8D' . toStreamD
551{-# INLINE decodeUtf8D_ #-}
552decodeUtf8D_ :: Monad m => Stream m Word8 -> Stream m Char
553decodeUtf8D_ = decodeUtf8WithD DropOnCodingFailure
555-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
556-- Any invalid codepoint encountered is dropped.
559{-# INLINE decodeUtf8_ #-}
560decodeUtf8_ :: (Monad m, IsStream t) => t m Word8 -> t m Char
561decodeUtf8_ = fromStreamD . decodeUtf8D_ . toStreamD
563-- | Same as 'decodeUtf8'
565{-# DEPRECATED decodeUtf8Lax "Please use 'decodeUtf8' instead" #-}
566{-# INLINE decodeUtf8Lax #-}
567decodeUtf8Lax :: (IsStream t, Monad m) => t m Word8 -> t m Char
568decodeUtf8Lax = decodeUtf8
570-------------------------------------------------------------------------------
571-- Decoding Array Streams
572-------------------------------------------------------------------------------
575{-# ANN type FlattenState Fuse #-}
578 = OuterLoop s !(Maybe (DecodeState, CodePoint))
579 | InnerLoopDecodeInit s (ForeignPtr a) !(Ptr a) !(Ptr a)
580 | InnerLoopDecodeFirst s (ForeignPtr a) !(Ptr a) !(Ptr a) Word8
581 | InnerLoopDecoding s (ForeignPtr a) !(Ptr a) !(Ptr a)
582 !DecodeState !CodePoint
583 | YAndC !Char (FlattenState s a) -- These constructors can be
584 -- encoded in the UTF8DecodeState
585 -- type, I prefer to keep these
586 -- flat even though that means
587 -- coming up with new names
590-- The normal decodeUtf8 above should fuse with flattenArrays
591-- to create this exact code but it doesn't for some reason, as of now this
592-- remains the fastest way I could figure out to decodeUtf8.
594-- XXX Add Proper error messages
595{-# INLINE_NORMAL decodeUtf8ArraysWithD #-}
596decodeUtf8ArraysWithD ::
599 -> Stream m (A.Array Word8)
601decodeUtf8ArraysWithD cfm (Stream step state) =
602 let A.Array p _ = utf8d
603 !ptr = unsafeForeignPtrToPtr p
604 in Stream (step' ptr) (OuterLoop state Nothing)
606 {-# INLINE transliterateOrError #-}
607 transliterateOrError e s =
609 ErrorOnCodingFailure -> error e
610 TransliterateCodingFailure -> YAndC replacementChar s
611 DropOnCodingFailure -> s
612 {-# INLINE inputUnderflow #-}
615 ErrorOnCodingFailure ->
617 show "Streamly.Internal.Data.Stream.StreamD."
618 ++ "decodeUtf8ArraysWith: Input Underflow"
619 TransliterateCodingFailure -> YAndC replacementChar D
620 DropOnCodingFailure -> D
621 {-# INLINE_LATE step' #-}
622 step' _ gst (OuterLoop st Nothing) = do
623 r <- step (adaptState gst) st
626 Yield A.Array {..} s ->
627 let p = unsafeForeignPtrToPtr aStart
628 in Skip (InnerLoopDecodeInit s aStart p aEnd)
629 Skip s -> Skip (OuterLoop s Nothing)
631 step' _ gst (OuterLoop st dst@(Just (ds, cp))) = do
632 r <- step (adaptState gst) st
635 Yield A.Array {..} s ->
636 let p = unsafeForeignPtrToPtr aStart
637 in Skip (InnerLoopDecoding s aStart p aEnd ds cp)
638 Skip s -> Skip (OuterLoop s dst)
639 Stop -> Skip inputUnderflow
640 step' _ _ (InnerLoopDecodeInit st startf p end)
642 liftIO $ touchForeignPtr startf
643 return $ Skip $ OuterLoop st Nothing
644 step' _ _ (InnerLoopDecodeInit st startf p end) = do
646 -- Note: It is important to use a ">" instead of a "<=" test here for
647 -- GHC to generate code layout for default branch prediction for the
648 -- common case. This is fragile and might change with the compiler
649 -- versions, we need a more reliable "likely" primitive to control
650 -- branch predication.
653 return $ Skip $ YAndC
654 (unsafeChr (fromIntegral x))
655 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
656 -- Using a separate state here generates a jump to a separate code
657 -- block in the core which seems to perform slightly better for the
659 True -> return $ Skip $ InnerLoopDecodeFirst st startf p end x
661 step' table _ (InnerLoopDecodeFirst st startf p end x) = do
662 let (Tuple' sv cp) = decode0 table x
669 "Streamly.Internal.Data.Stream.StreamD."
670 ++ "decodeUtf8ArraysWith: Invalid UTF8"
671 ++ " codepoint encountered"
673 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
674 0 -> error "unreachable state"
675 _ -> Skip (InnerLoopDecoding st startf (p `plusPtr` 1) end sv cp)
676 step' _ _ (InnerLoopDecoding st startf p end sv cp)
678 liftIO $ touchForeignPtr startf
679 return $ Skip $ OuterLoop st (Just (sv, cp))
680 step' table _ (InnerLoopDecoding st startf p end statePtr codepointPtr) = do
682 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
689 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
694 "Streamly.Internal.Data.Stream.StreamD."
695 ++ "decodeUtf8ArraysWith: Invalid UTF8"
696 ++ " codepoint encountered"
698 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
701 (InnerLoopDecoding st startf (p `plusPtr` 1) end sv cp)
702 step' _ _ (YAndC c s) = return $ Yield c s
703 step' _ _ D = return Stop
705{-# INLINE decodeUtf8ArraysD #-}
708 => Stream m (A.Array Word8)
710decodeUtf8ArraysD = decodeUtf8ArraysWithD TransliterateCodingFailure
715{-# INLINE decodeUtf8Arrays #-}
717 (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
719 fromStreamD . decodeUtf8ArraysD . toStreamD
721{-# INLINE decodeUtf8ArraysD' #-}
724 => Stream m (A.Array Word8)
726decodeUtf8ArraysD' = decodeUtf8ArraysWithD ErrorOnCodingFailure
731{-# INLINE decodeUtf8Arrays' #-}
732decodeUtf8Arrays' :: (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
733decodeUtf8Arrays' = fromStreamD . decodeUtf8ArraysD' . toStreamD
735{-# INLINE decodeUtf8ArraysD_ #-}
738 => Stream m (A.Array Word8)
740decodeUtf8ArraysD_ = decodeUtf8ArraysWithD DropOnCodingFailure
745{-# INLINE decodeUtf8Arrays_ #-}
747 (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
749 fromStreamD . decodeUtf8ArraysD_ . toStreamD
751-------------------------------------------------------------------------------
752-- Encoding Unicode (UTF-8) Characters
753-------------------------------------------------------------------------------
755data WList = WCons !Word8 !WList | WNil
757-- UTF-8 primitives, Lifted from GHC.IO.Encoding.UTF8.
761ord2 c = assert (n >= 0x80 && n <= 0x07ff) (WCons x1 (WCons x2 WNil))
764 x1 = fromIntegral $ (n `shiftR` 6) + 0xC0
765 x2 = fromIntegral $ (n .&. 0x3F) + 0x80
769ord3 c = assert (n >= 0x0800 && n <= 0xffff) (WCons x1 (WCons x2 (WCons x3 WNil)))
772 x1 = fromIntegral $ (n `shiftR` 12) + 0xE0
773 x2 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80
774 x3 = fromIntegral $ (n .&. 0x3F) + 0x80
778ord4 c = assert (n >= 0x10000) (WCons x1 (WCons x2 (WCons x3 (WCons x4 WNil))))
781 x1 = fromIntegral $ (n `shiftR` 18) + 0xF0
782 x2 = fromIntegral $ ((n `shiftR` 12) .&. 0x3F) + 0x80
783 x3 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80
784 x4 = fromIntegral $ (n .&. 0x3F) + 0x80
787{-# ANN type EncodeState Fuse #-}
789data EncodeState s = EncodeState s !WList
792{-# ANN type InvalidAction Fuse #-}
795 DropInvalid | ErrorInvalid | IgnoreInvalid | ReplaceInvalid
797replaceInvalid :: s -> Step (EncodeState s) a
799 Skip $ EncodeState s (WCons 239 (WCons 191 (WCons 189 WNil)))
801dropInvalid :: s -> Step (EncodeState s) a
802dropInvalid s = Skip (EncodeState s WNil)
804errorOnInvalid :: s -> Step (EncodeState s) a
807 show "Streamly.Internal.Data.Stream.StreamD.encodeUtf8:"
808 ++ "Encountered a surrogate"
810{-# INLINE_NORMAL encodeUtf8DGeneric #-}
816encodeUtf8DGeneric act (Stream step state) =
817 Stream step' (EncodeState state WNil)
821 {-# INLINE_LATE step' #-}
822 step' gst (EncodeState st WNil) = do
823 r <- step (adaptState gst) st
829 Yield (fromIntegral x) (EncodeState s WNil)
830 | x <= 0x7FF -> Skip (EncodeState s (ord2 c))
836 else Skip (EncodeState s (ord3 c))
840 then errorOnInvalid s
841 else Skip (EncodeState s (ord3 c))
844 Skip (EncodeState s (ord3 c))
848 then replaceInvalid s
849 else Skip (EncodeState s (ord3 c))
851 | otherwise -> Skip (EncodeState s (ord4 c))
852 Skip s -> Skip (EncodeState s WNil)
854 step' _ (EncodeState s (WCons x xs)) = return $ Yield x (EncodeState s xs)
856-- More yield points improve performance, but I am not sure if they can cause
857-- too much code bloat or some trouble with fusion. So keeping only two yield
858-- points for now, one for the ascii chars (fast path) and one for all other
860{-# INLINE_NORMAL encodeUtf8D' #-}
861encodeUtf8D' :: Monad m => Stream m Char -> Stream m Word8
862encodeUtf8D' = encodeUtf8DGeneric ErrorInvalid
864-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. When
865-- any invalid character (U+D800-U+D8FF) is encountered in the input stream the
866-- function errors out.
869{-# INLINE encodeUtf8' #-}
870encodeUtf8' :: (Monad m, IsStream t) => t m Char -> t m Word8
871encodeUtf8' = fromStreamD . encodeUtf8D' . toStreamD
873-- | See section "3.9 Unicode Encoding Forms" in
874-- https://www.unicode.org/versions/Unicode13.0.0/UnicodeStandard-13.0.pdf
876{-# INLINE_NORMAL encodeUtf8D #-}
877encodeUtf8D :: Monad m => Stream m Char -> Stream m Word8
878encodeUtf8D = encodeUtf8DGeneric ReplaceInvalid
880-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. Any
881-- Invalid characters (U+D800-U+D8FF) in the input stream are replaced by the
882-- Unicode replacement character U+FFFD.
884-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
886-- /Since: 0.8.0 (Lenient Behaviour)/
887{-# INLINE encodeUtf8 #-}
888encodeUtf8 :: (Monad m, IsStream t) => t m Char -> t m Word8
889encodeUtf8 = fromStreamD . encodeUtf8D . toStreamD
891{-# INLINE_NORMAL encodeUtf8D_ #-}
892encodeUtf8D_ :: Monad m => Stream m Char -> Stream m Word8
893encodeUtf8D_ = encodeUtf8DGeneric DropInvalid
895-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. Any
896-- Invalid characters (U+D800-U+D8FF) in the input stream are dropped.
899{-# INLINE encodeUtf8_ #-}
900encodeUtf8_ :: (Monad m, IsStream t) => t m Char -> t m Word8
901encodeUtf8_ = fromStreamD . encodeUtf8D_ . toStreamD
903-- | Same as 'encodeUtf8'
905{-# DEPRECATED encodeUtf8Lax "Please use 'encodeUtf8' instead" #-}
906{-# INLINE encodeUtf8Lax #-}
907encodeUtf8Lax :: (IsStream t, Monad m) => t m Char -> t m Word8
908encodeUtf8Lax = encodeUtf8
910-------------------------------------------------------------------------------
911-- Encode streams of containers
912-------------------------------------------------------------------------------
914-- | Encode a container to @Array Word8@ provided an unfold to covert it to a
915-- Char stream and an encoding function.
918{-# INLINE encodeObject #-}
919encodeObject :: MonadIO m =>
920 (SerialT m Char -> SerialT m Word8)
924encodeObject encode u = S.fold Array.write . encode . S.unfold u
926-- | Encode a stream of container objects using the supplied encoding scheme.
927-- Each object is encoded as an @Array Word8@.
930{-# INLINE encodeObjects #-}
931encodeObjects :: (MonadIO m, IsStream t) =>
932 (SerialT m Char -> SerialT m Word8)
936encodeObjects encode u = adapt . Serial.mapM (encodeObject encode u) . adapt
938-- | Encode a stream of 'String' using the supplied encoding scheme. Each
939-- string is encoded as an @Array Word8@.
942{-# INLINE encodeStrings #-}
943encodeStrings :: (MonadIO m, IsStream t) =>
944 (SerialT m Char -> SerialT m Word8) -> t m String -> t m (Array Word8)
945encodeStrings encode = encodeObjects encode Unfold.fromList
948-------------------------------------------------------------------------------
949-- Utility operations on strings
950-------------------------------------------------------------------------------
952strip :: IsStream t => t m Char -> t m Char
955stripTail :: IsStream t => t m Char -> t m Char
959-- | Remove leading whitespace from a string.
961-- > stripHead = S.dropWhile isSpace
964{-# INLINE stripHead #-}
965stripHead :: (Monad m, IsStream t) => t m Char -> t m Char
966stripHead = S.dropWhile isSpace
968-- | Fold each line of the stream using the supplied 'Fold'
969-- and stream the result.
971-- >>> Stream.toList $ lines Fold.toList (Stream.fromList "lines\nthis\nstring\n\n\n")
972-- ["lines","this","string","",""]
974-- > lines = S.splitOnSuffix (== '\n')
978lines :: (Monad m, IsStream t) => Fold m Char b -> t m Char -> t m b
979lines = S.splitOnSuffix (== '\n')
981foreign import ccall unsafe "u_iswspace"
982 iswspace :: Int -> Int
984-- | Code copied from base/Data.Char to INLINE it
985{-# INLINE isSpace #-}
986isSpace :: Char -> Bool
988 | uc <= 0x377 = uc == 32 || uc - 0x9 <= 4 || uc == 0xa0
989 | otherwise = iswspace (ord c) /= 0
991 uc = fromIntegral (ord c) :: Word
993-- | Fold each word of the stream using the supplied 'Fold'
994-- and stream the result.
996-- >>> Stream.toList $ words Fold.toList (Stream.fromList "fold these words")
997-- ["fold","these","words"]
999-- > words = S.wordsBy isSpace
1003words :: (Monad m, IsStream t) => Fold m Char b -> t m Char -> t m b
1004words = S.wordsBy isSpace
1006-- | Unfold a stream to character streams using the supplied 'Unfold'
1007-- and concat the results suffixing a newline character @\\n@ to each stream.
1010-- unlines = Stream.interposeSuffix '\n'
1011-- unlines = Stream.intercalateSuffix Unfold.fromList "\n"
1015{-# INLINE unlines #-}
1016unlines :: (MonadIO m, IsStream t) => Unfold m a Char -> t m a -> t m Char
1017unlines = S.interposeSuffix '\n'
1019-- | Unfold the elements of a stream to character streams using the supplied
1020-- 'Unfold' and concat the results with a whitespace character infixed between
1024-- unwords = Stream.interpose ' '
1025-- unwords = Stream.intercalate Unfold.fromList " "
1029{-# INLINE unwords #-}
1030unwords :: (MonadIO m, IsStream t) => Unfold m a Char -> t m a -> t m Char
1031unwords = 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