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)
18 , CodingFailureMode(..)
25 -- ** Resumable UTF-8 Decoding
30 , resumeDecodeUtf8Either
32 -- ** UTF-8 Array Stream Decoding
37 -- * Elimination (Encoding)
50 -- * Operations on character strings
51 , strip -- (dropAround isSpace)
62 -- * StreamD UTF8 Encoding / Decoding transformations.
70 , resumeDecodeUtf8EitherD
84import Control.Monad.Catch (MonadThrow(..), MonadCatch)
85import Control.Monad.IO.Class (MonadIO, liftIO)
86import Data.Bits (shiftR, shiftL, (.|.), (.&.))
87import Data.Char (chr, ord)
88#if MIN_VERSION_base(4,17,0)
89import Data.Char (generalCategory, GeneralCategory(Space))
91import Data.Word (Word8)
92import Foreign.Storable (Storable(..))
93import Fusion.Plugin.Types (Fuse(..))
94import GHC.Base (assert, unsafeChr)
95import GHC.IO.Encoding.Failure (isSurrogate)
96import GHC.Ptr (Ptr (..), plusPtr)
97import System.IO.Unsafe (unsafePerformIO)
98import Streamly.Internal.Data.Array.Foreign (Array)
99import Streamly.Internal.Data.Array.Foreign.Mut.Type (ArrayContents, touch)
100import Streamly.Internal.Data.Fold (Fold)
101import Streamly.Internal.Data.Stream.IsStream.Type
102 (IsStream, fromStreamD, toStreamD, adapt)
103import Streamly.Internal.Data.Stream.Serial (SerialT)
104import Streamly.Internal.Data.Stream.StreamD (Stream(..), Step (..))
105import Streamly.Internal.Data.SVar (adaptState)
106import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
107import Streamly.Internal.Data.Unfold.Type (Unfold(..))
108import Streamly.Internal.System.IO (unsafeInlineIO)
110import qualified Streamly.Internal.Data.Unfold as Unfold
111import qualified Streamly.Internal.Data.Parser as Parser
112import qualified Streamly.Internal.Data.Parser.ParserD as ParserD
113import qualified Streamly.Internal.Data.Stream.Serial as Serial
114import qualified Streamly.Internal.Data.Array.Foreign as Array
115import qualified Streamly.Internal.Data.Array.Foreign.Type as A
116import qualified Streamly.Internal.Data.Stream.IsStream as S
117import qualified Streamly.Internal.Data.Stream.StreamD as D
119import Prelude hiding (lines, words, unlines, unwords)
123-- >>> import Prelude hiding (lines, words, unlines, unwords)
124-- >>> import qualified Streamly.Prelude as Stream
125-- >>> import qualified Streamly.Data.Fold as Fold
126-- >>> import Streamly.Internal.Unicode.Stream
128-------------------------------------------------------------------------------
130-------------------------------------------------------------------------------
132-- | Decode a stream of bytes to Unicode characters by mapping each byte to a
133-- corresponding Unicode 'Char' in 0-255 range.
135-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
138{-# INLINE decodeLatin1 #-}
139decodeLatin1 :: (IsStream t, Monad m) => t m Word8 -> t m Char
140decodeLatin1 = S.map (unsafeChr . fromIntegral)
142-------------------------------------------------------------------------------
144-------------------------------------------------------------------------------
146-- | Encode a stream of Unicode characters to bytes by mapping each character
147-- to a byte in 0-255 range. Throws an error if the input stream contains
148-- characters beyond 255.
151{-# INLINE encodeLatin1' #-}
152encodeLatin1' :: (IsStream t, Monad m) => t m Char -> t m Word8
153encodeLatin1' = S.map convert
156 let codepoint = ord c
157 in if codepoint > 255
158 then error $ "Streamly.Unicode.encodeLatin1 invalid " ++
159 "input char codepoint " ++ show codepoint
160 else fromIntegral codepoint
162-- XXX Should we instead replace the invalid chars by NUL or whitespace or some
163-- other control char? That may affect the perf a bit but may be a better
166-- | Like 'encodeLatin1'' but silently maps input codepoints beyond 255 to
167-- arbitrary Latin1 chars in 0-255 range. No error or exception is thrown when
168-- such mapping occurs.
170-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
172-- /Since: 0.8.0 (Lenient Behaviour)/
173{-# INLINE encodeLatin1 #-}
174encodeLatin1 :: (IsStream t, Monad m) => t m Char -> t m Word8
175encodeLatin1 = S.map (fromIntegral . ord)
177-- | Like 'encodeLatin1' but drops the input characters beyond 255.
180{-# INLINE encodeLatin1_ #-}
181encodeLatin1_ :: (IsStream t, Monad m) => t m Char -> t m Word8
182encodeLatin1_ = S.map (fromIntegral . ord) . S.filter (<= chr 255)
184-- | Same as 'encodeLatin1'
186{-# DEPRECATED encodeLatin1Lax "Please use 'encodeLatin1' instead" #-}
187{-# INLINE encodeLatin1Lax #-}
188encodeLatin1Lax :: (IsStream t, Monad m) => t m Char -> t m Word8
189encodeLatin1Lax = encodeLatin1
191-------------------------------------------------------------------------------
193-------------------------------------------------------------------------------
195-- Int helps in cheaper conversion from Int to Char
197type DecodeState = Word8
199-- We can divide the errors in three general categories:
200-- * A non-starter was encountered in a begin state
201-- * A starter was encountered without completing a codepoint
202-- * The last codepoint was not complete (input underflow)
204-- Need to separate resumable and non-resumable error. In case of non-resumable
205-- error we can also provide the failing byte. In case of resumable error the
206-- state can be opaque.
208data DecodeError = DecodeError !DecodeState !CodePoint deriving Show
210-- See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
212-- XXX Use names decodeSuccess = 0, decodeFailure = 12
214decodeTable :: [Word8]
216 -- The first part of the table maps bytes to character classes that
217 -- to reduce the size of the transition table and create bitmasks.
218 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,
219 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,
220 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,
221 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,
222 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,
223 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,
224 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,
225 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,
227 -- The second part is a transition table that maps a combination
228 -- of a state of the automaton and a character class to a state.
229 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
230 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
231 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
232 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
233 12,36,12,12,12,12,12,12,12,12,12,12
236{-# NOINLINE utf8d #-}
237utf8d :: A.Array Word8
240 -- Aligning to cacheline makes a barely noticeable difference
241 -- XXX currently alignment is not implemented for unmanaged allocation
242 $ D.fold (A.writeNAlignedUnmanaged 64 (length decodeTable))
243 (D.fromList decodeTable)
245-- | Return element at the specified index without checking the bounds.
246-- and without touching the foreign ptr.
247{-# INLINE_NORMAL unsafePeekElemOff #-}
248unsafePeekElemOff :: forall a. Storable a => Ptr a -> Int -> a
249unsafePeekElemOff p i = let !x = unsafeInlineIO $ peekElemOff p i in x
251-- decode is split into two separate cases to avoid branching instructions.
252-- From the higher level flow we already know which case we are in so we can
253-- call the appropriate decode function.
255-- When the state is 0
256{-# INLINE decode0 #-}
257decode0 :: Ptr Word8 -> Word8 -> Tuple' DecodeState CodePoint
259 let !t = table `unsafePeekElemOff` fromIntegral byte
260 !codep' = (0xff `shiftR` fromIntegral t) .&. fromIntegral byte
261 !state' = table `unsafePeekElemOff` (256 + fromIntegral t)
262 in assert ((byte > 0x7f || error showByte)
263 && (state' /= 0 || error (showByte ++ showTable)))
264 (Tuple' state' codep')
269 let end = table `plusPtr` 364
270 in A.Array undefined table end :: A.Array Word8
271 showByte = "Streamly: decode0: byte: " ++ show byte
272 showTable = " table: " ++ show utf8table
274-- When the state is not 0
275{-# INLINE decode1 #-}
281 -> Tuple' DecodeState CodePoint
282decode1 table state codep byte =
283 -- Remember codep is Int type!
284 -- Can it be unsafe to convert the resulting Int to Char?
285 let !t = table `unsafePeekElemOff` fromIntegral byte
286 !codep' = (fromIntegral byte .&. 0x3f) .|. (codep `shiftL` 6)
287 !state' = table `unsafePeekElemOff`
288 (256 + fromIntegral state + fromIntegral t)
289 in assert (codep' <= 0x10FFFF
290 || error (showByte ++ showState state codep))
291 (Tuple' state' codep')
295 let end = table `plusPtr` 364
296 in A.Array undefined table end :: A.Array Word8
297 showByte = "Streamly: decode1: byte: " ++ show byte
299 " state: " ++ show st ++
300 " codepoint: " ++ show cp ++
301 " table: " ++ show utf8table
303-------------------------------------------------------------------------------
304-- Resumable UTF-8 decoding
305-------------------------------------------------------------------------------
307-- Strangely, GHCJS hangs linking template-haskell with this
309{-# ANN type UTF8DecodeState Fuse #-}
311data UTF8DecodeState s a
313 | UTF8DecodeInit1 s Word8
314 | UTF8DecodeFirst s Word8
315 | UTF8Decoding s !DecodeState !CodePoint
316 | YieldAndContinue a (UTF8DecodeState s a)
319{-# INLINE_NORMAL resumeDecodeUtf8EitherD #-}
320resumeDecodeUtf8EitherD
325 -> Stream m (Either DecodeError Char)
326resumeDecodeUtf8EitherD dst codep (Stream step state) =
327 let A.Array _ p _ = utf8d
331 then UTF8DecodeInit state
332 else UTF8Decoding state dst codep
333 in Stream (step' ptr) stt
335 {-# INLINE_LATE step' #-}
336 step' _ gst (UTF8DecodeInit st) = do
337 r <- step (adaptState gst) st
339 Yield x s -> Skip (UTF8DecodeInit1 s x)
340 Skip s -> Skip (UTF8DecodeInit s)
343 step' _ _ (UTF8DecodeInit1 st x) = do
344 -- Note: It is important to use a ">" instead of a "<=" test
345 -- here for GHC to generate code layout for default branch
346 -- prediction for the common case. This is fragile and might
347 -- change with the compiler versions, we need a more reliable
348 -- "likely" primitive to control branch predication.
351 return $ Skip $ YieldAndContinue
352 (Right $ unsafeChr (fromIntegral x))
354 -- Using a separate state here generates a jump to a
355 -- separate code block in the core which seems to perform
356 -- slightly better for the non-ascii case.
357 True -> return $ Skip $ UTF8DecodeFirst st x
359 -- XXX should we merge it with UTF8DecodeInit1?
360 step' table _ (UTF8DecodeFirst st x) = do
361 let (Tuple' sv cp) = decode0 table x
365 Skip $ YieldAndContinue (Left $ DecodeError 0 (fromIntegral x))
367 0 -> error "unreachable state"
368 _ -> Skip (UTF8Decoding st sv cp)
370 -- We recover by trying the new byte x a starter of a new codepoint.
371 -- XXX on error need to report the next byte "x" as well.
372 -- XXX need to use the same recovery in array decoding routine as well
373 step' table gst (UTF8Decoding st statePtr codepointPtr) = do
374 r <- step (adaptState gst) st
377 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
380 0 -> Skip $ YieldAndContinue (Right $ unsafeChr cp)
383 Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr)
384 (UTF8DecodeInit1 s x)
385 _ -> Skip (UTF8Decoding s sv cp)
386 Skip s -> return $ Skip (UTF8Decoding s statePtr codepointPtr)
387 Stop -> return $ Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr) Done
389 step' _ _ (YieldAndContinue c s) = return $ Yield c s
390 step' _ _ Done = return Stop
392-- XXX We can use just one API, and define InitState = 0 and InitCodePoint = 0
393-- to use as starting state.
395{-# INLINE_NORMAL decodeUtf8EitherD #-}
396decodeUtf8EitherD :: Monad m
397 => Stream m Word8 -> Stream m (Either DecodeError Char)
398decodeUtf8EitherD = resumeDecodeUtf8EitherD 0 0
403{-# INLINE decodeUtf8Either #-}
404decodeUtf8Either :: (Monad m, IsStream t)
405 => t m Word8 -> t m (Either DecodeError Char)
406decodeUtf8Either = fromStreamD . decodeUtf8EitherD . toStreamD
411{-# INLINE resumeDecodeUtf8Either #-}
412resumeDecodeUtf8Either
413 :: (Monad m, IsStream t)
417 -> t m (Either DecodeError Char)
418resumeDecodeUtf8Either st cp =
419 fromStreamD . resumeDecodeUtf8EitherD st cp . toStreamD
421-------------------------------------------------------------------------------
423-------------------------------------------------------------------------------
425data CodingFailureMode
426 = TransliterateCodingFailure
427 | ErrorOnCodingFailure
428 | DropOnCodingFailure
431{-# INLINE replacementChar #-}
432replacementChar :: Char
433replacementChar = '\xFFFD'
435data UTF8CharDecodeState a
437 | UTF8CharDecoding !DecodeState !CodePoint
439{-# INLINE parseCharUtf8WithD #-}
441 MonadThrow m => CodingFailureMode -> ParserD.Parser m Word8 Char
442parseCharUtf8WithD cfm =
443 let A.Array _ ptr _ = utf8d
444 in ParserD.Parser (step' ptr) initial extract
448 prefix = "Streamly.Internal.Data.Stream.parseCharUtf8WithD:"
450 {-# INLINE initial #-}
451 initial = return $ ParserD.IPartial UTF8CharDecodeInit
453 handleError err souldBackTrack =
455 ErrorOnCodingFailure -> ParserD.Error err
456 TransliterateCodingFailure ->
457 case souldBackTrack of
458 True -> ParserD.Done 1 replacementChar
459 False -> ParserD.Done 0 replacementChar
460 DropOnCodingFailure ->
461 case souldBackTrack of
462 True -> ParserD.Continue 1 UTF8CharDecodeInit
463 False -> ParserD.Continue 0 UTF8CharDecodeInit
466 step' table UTF8CharDecodeInit x =
467 -- Note: It is important to use a ">" instead of a "<=" test
468 -- here for GHC to generate code layout for default branch
469 -- prediction for the common case. This is fragile and might
470 -- change with the compiler versions, we need a more reliable
471 -- "likely" primitive to control branch predication.
472 return $ case x > 0x7f of
473 False -> ParserD.Done 0 $ unsafeChr $ fromIntegral x
475 let (Tuple' sv cp) = decode0 table x
479 ++ "Invalid first UTF8 byte" ++ show x
480 in handleError msg False
481 0 -> error $ prefix ++ "unreachable state"
482 _ -> ParserD.Continue 0 (UTF8CharDecoding sv cp)
484 step' table (UTF8CharDecoding statePtr codepointPtr) x = return $
485 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
487 0 -> ParserD.Done 0 $ unsafeChr cp
490 ++ "Invalid subsequent UTF8 byte"
494 ++ "accumulated value"
496 in handleError msg True
497 _ -> ParserD.Continue 0 (UTF8CharDecoding sv cp)
499 {-# INLINE extract #-}
500 extract UTF8CharDecodeInit = error $ prefix ++ "Not enough input"
501 extract (UTF8CharDecoding _ _) =
503 ErrorOnCodingFailure ->
504 throwM $ ParserD.ParseError $ prefix ++ "Not enough input"
505 TransliterateCodingFailure -> return replacementChar
506 -- XXX We shouldn't error out here. There is no way to represent an
507 -- empty parser result unless we return a "Maybe" type.
508 DropOnCodingFailure -> error $ prefix ++ "Not enough input"
510-- XXX This should ideally accept a "CodingFailureMode" and perform appropriate
511-- error handling. This isn't possible now as "TransliterateCodingFailure"'s
512-- workflow requires backtracking 1 element. This can be revisited once "Fold"
513-- supports backtracking.
514{-# INLINE writeCharUtf8' #-}
515writeCharUtf8' :: MonadThrow m => Fold m Word8 Char
516writeCharUtf8' = ParserD.toFold (parseCharUtf8WithD ErrorOnCodingFailure)
518-- XXX The initial idea was to have "parseCharUtf8" and offload the error
519-- handling to another parser. So, say we had "parseCharUtf8'",
521-- >>> parseCharUtf8Smart = parseCharUtf8' <|> Parser.fromPure replacementChar
523-- But unfortunately parseCharUtf8Smart used in conjunction with "parseMany" -
524-- that is "parseMany parseCharUtf8Smart" on a stream causes the heap to
525-- overflow. Even a heap size of 500 MB was not sufficient.
527-- This needs to be investigated futher.
528{-# INLINE parseCharUtf8With #-}
530 MonadCatch m => CodingFailureMode -> Parser.Parser m Word8 Char
531parseCharUtf8With = ParserD.toParserK . parseCharUtf8WithD
533-- XXX write it as a parser and use parseMany to decode a stream, need to check
534-- if that preserves the same performance. Or we can use a resumable parser
535-- that parses a chunk at a time.
537-- XXX Implement this in terms of decodeUtf8Either. Need to make sure that
538-- decodeUtf8Either preserves the performance characterstics.
540{-# INLINE_NORMAL decodeUtf8WithD #-}
541decodeUtf8WithD :: Monad m
542 => CodingFailureMode -> Stream m Word8 -> Stream m Char
543decodeUtf8WithD cfm (Stream step state) =
544 let A.Array _ ptr _ = utf8d
545 in Stream (step' ptr) (UTF8DecodeInit state)
549 prefix = "Streamly.Internal.Data.Stream.StreamD.decodeUtf8With: "
551 {-# INLINE handleError #-}
554 ErrorOnCodingFailure -> error e
555 TransliterateCodingFailure -> YieldAndContinue replacementChar s
556 DropOnCodingFailure -> s
558 {-# INLINE handleUnderflow #-}
561 ErrorOnCodingFailure -> error $ prefix ++ "Not enough input"
562 TransliterateCodingFailure -> YieldAndContinue replacementChar Done
563 DropOnCodingFailure -> Done
565 {-# INLINE_LATE step' #-}
566 step' _ gst (UTF8DecodeInit st) = do
567 r <- step (adaptState gst) st
569 Yield x s -> Skip (UTF8DecodeInit1 s x)
570 Skip s -> Skip (UTF8DecodeInit s)
573 step' _ _ (UTF8DecodeInit1 st x) = do
574 -- Note: It is important to use a ">" instead of a "<=" test
575 -- here for GHC to generate code layout for default branch
576 -- prediction for the common case. This is fragile and might
577 -- change with the compiler versions, we need a more reliable
578 -- "likely" primitive to control branch predication.
581 return $ Skip $ YieldAndContinue
582 (unsafeChr (fromIntegral x))
584 -- Using a separate state here generates a jump to a
585 -- separate code block in the core which seems to perform
586 -- slightly better for the non-ascii case.
587 True -> return $ Skip $ UTF8DecodeFirst st x
589 -- XXX should we merge it with UTF8DecodeInit1?
590 step' table _ (UTF8DecodeFirst st x) = do
591 let (Tuple' sv cp) = decode0 table x
595 let msg = prefix ++ "Invalid first UTF8 byte " ++ show x
596 in Skip $ handleError msg (UTF8DecodeInit st)
597 0 -> error "unreachable state"
598 _ -> Skip (UTF8Decoding st sv cp)
600 -- We recover by trying the new byte x as a starter of a new codepoint.
601 -- XXX need to use the same recovery in array decoding routine as well
602 step' table gst (UTF8Decoding st statePtr codepointPtr) = do
603 r <- step (adaptState gst) st
606 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
608 0 -> Skip $ YieldAndContinue
609 (unsafeChr cp) (UTF8DecodeInit s)
612 ++ "Invalid subsequent UTF8 byte "
616 ++ " accumulated value "
618 in Skip $ handleError msg (UTF8DecodeInit1 s x)
619 _ -> Skip (UTF8Decoding s sv cp)
621 Skip (UTF8Decoding s statePtr codepointPtr)
622 Stop -> return $ Skip handleUnderflow
624 step' _ _ (YieldAndContinue c s) = return $ Yield c s
625 step' _ _ Done = return Stop
627{-# INLINE decodeUtf8D #-}
628decodeUtf8D :: Monad m => Stream m Word8 -> Stream m Char
629decodeUtf8D = decodeUtf8WithD TransliterateCodingFailure
631-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
632-- Any invalid codepoint encountered is replaced with the unicode replacement
635-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
637-- /Since: 0.8.0 (Lenient Behaviour)/
638{-# INLINE decodeUtf8 #-}
639decodeUtf8 :: (Monad m, IsStream t) => t m Word8 -> t m Char
640decodeUtf8 = fromStreamD . decodeUtf8D . toStreamD
642{-# INLINE decodeUtf8D' #-}
643decodeUtf8D' :: Monad m => Stream m Word8 -> Stream m Char
644decodeUtf8D' = decodeUtf8WithD ErrorOnCodingFailure
646-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
647-- The function throws an error if an invalid codepoint is encountered.
650{-# INLINE decodeUtf8' #-}
651decodeUtf8' :: (Monad m, IsStream t) => t m Word8 -> t m Char
652decodeUtf8' = fromStreamD . decodeUtf8D' . toStreamD
654{-# INLINE decodeUtf8D_ #-}
655decodeUtf8D_ :: Monad m => Stream m Word8 -> Stream m Char
656decodeUtf8D_ = decodeUtf8WithD DropOnCodingFailure
658-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
659-- Any invalid codepoint encountered is dropped.
662{-# INLINE decodeUtf8_ #-}
663decodeUtf8_ :: (Monad m, IsStream t) => t m Word8 -> t m Char
664decodeUtf8_ = fromStreamD . decodeUtf8D_ . toStreamD
666-- | Same as 'decodeUtf8'
668{-# DEPRECATED decodeUtf8Lax "Please use 'decodeUtf8' instead" #-}
669{-# INLINE decodeUtf8Lax #-}
670decodeUtf8Lax :: (IsStream t, Monad m) => t m Word8 -> t m Char
671decodeUtf8Lax = decodeUtf8
673-------------------------------------------------------------------------------
674-- Decoding Array Streams
675-------------------------------------------------------------------------------
678{-# ANN type FlattenState Fuse #-}
681 = OuterLoop s !(Maybe (DecodeState, CodePoint))
682 | InnerLoopDecodeInit s ArrayContents !(Ptr a) !(Ptr a)
683 | InnerLoopDecodeFirst s ArrayContents !(Ptr a) !(Ptr a) Word8
684 | InnerLoopDecoding s ArrayContents !(Ptr a) !(Ptr a)
685 !DecodeState !CodePoint
686 | YAndC !Char (FlattenState s a) -- These constructors can be
687 -- encoded in the UTF8DecodeState
688 -- type, I prefer to keep these
689 -- flat even though that means
690 -- coming up with new names
693-- The normal decodeUtf8 above should fuse with flattenArrays
694-- to create this exact code but it doesn't for some reason, as of now this
695-- remains the fastest way I could figure out to decodeUtf8.
697-- XXX Add Proper error messages
698{-# INLINE_NORMAL decodeUtf8ArraysWithD #-}
699decodeUtf8ArraysWithD ::
702 -> Stream m (A.Array Word8)
704decodeUtf8ArraysWithD cfm (Stream step state) =
705 let A.Array _ ptr _ = utf8d
706 in Stream (step' ptr) (OuterLoop state Nothing)
708 {-# INLINE transliterateOrError #-}
709 transliterateOrError e s =
711 ErrorOnCodingFailure -> error e
712 TransliterateCodingFailure -> YAndC replacementChar s
713 DropOnCodingFailure -> s
714 {-# INLINE inputUnderflow #-}
717 ErrorOnCodingFailure ->
719 show "Streamly.Internal.Data.Stream.StreamD."
720 ++ "decodeUtf8ArraysWith: Input Underflow"
721 TransliterateCodingFailure -> YAndC replacementChar D
722 DropOnCodingFailure -> D
723 {-# INLINE_LATE step' #-}
724 step' _ gst (OuterLoop st Nothing) = do
725 r <- step (adaptState gst) st
728 Yield A.Array {..} s ->
729 Skip (InnerLoopDecodeInit s arrContents arrStart aEnd)
730 Skip s -> Skip (OuterLoop s Nothing)
732 step' _ gst (OuterLoop st dst@(Just (ds, cp))) = do
733 r <- step (adaptState gst) st
736 Yield A.Array {..} s ->
737 Skip (InnerLoopDecoding s arrContents arrStart aEnd ds cp)
738 Skip s -> Skip (OuterLoop s dst)
739 Stop -> Skip inputUnderflow
740 step' _ _ (InnerLoopDecodeInit st startf p end)
742 liftIO $ touch startf
743 return $ Skip $ OuterLoop st Nothing
744 step' _ _ (InnerLoopDecodeInit st startf p end) = do
746 -- Note: It is important to use a ">" instead of a "<=" test here for
747 -- GHC to generate code layout for default branch prediction for the
748 -- common case. This is fragile and might change with the compiler
749 -- versions, we need a more reliable "likely" primitive to control
750 -- branch predication.
753 return $ Skip $ YAndC
754 (unsafeChr (fromIntegral x))
755 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
756 -- Using a separate state here generates a jump to a separate code
757 -- block in the core which seems to perform slightly better for the
759 True -> return $ Skip $ InnerLoopDecodeFirst st startf p end x
761 step' table _ (InnerLoopDecodeFirst st startf p end x) = do
762 let (Tuple' sv cp) = decode0 table x
769 "Streamly.Internal.Data.Stream.StreamD."
770 ++ "decodeUtf8ArraysWith: Invalid UTF8"
771 ++ " codepoint encountered"
773 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
774 0 -> error "unreachable state"
775 _ -> Skip (InnerLoopDecoding st startf (p `plusPtr` 1) end sv cp)
776 step' _ _ (InnerLoopDecoding st startf p end sv cp)
778 liftIO $ touch startf
779 return $ Skip $ OuterLoop st (Just (sv, cp))
780 step' table _ (InnerLoopDecoding st startf p end statePtr codepointPtr) = do
782 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
789 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
794 "Streamly.Internal.Data.Stream.StreamD."
795 ++ "decodeUtf8ArraysWith: Invalid UTF8"
796 ++ " codepoint encountered"
798 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
801 (InnerLoopDecoding st startf (p `plusPtr` 1) end sv cp)
802 step' _ _ (YAndC c s) = return $ Yield c s
803 step' _ _ D = return Stop
805{-# INLINE decodeUtf8ArraysD #-}
808 => Stream m (A.Array Word8)
810decodeUtf8ArraysD = decodeUtf8ArraysWithD TransliterateCodingFailure
815{-# INLINE decodeUtf8Arrays #-}
817 (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
819 fromStreamD . decodeUtf8ArraysD . toStreamD
821{-# INLINE decodeUtf8ArraysD' #-}
824 => Stream m (A.Array Word8)
826decodeUtf8ArraysD' = decodeUtf8ArraysWithD ErrorOnCodingFailure
831{-# INLINE decodeUtf8Arrays' #-}
832decodeUtf8Arrays' :: (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
833decodeUtf8Arrays' = fromStreamD . decodeUtf8ArraysD' . toStreamD
835{-# INLINE decodeUtf8ArraysD_ #-}
838 => Stream m (A.Array Word8)
840decodeUtf8ArraysD_ = decodeUtf8ArraysWithD DropOnCodingFailure
845{-# INLINE decodeUtf8Arrays_ #-}
847 (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
849 fromStreamD . decodeUtf8ArraysD_ . toStreamD
851-------------------------------------------------------------------------------
852-- Encoding Unicode (UTF-8) Characters
853-------------------------------------------------------------------------------
855data WList = WCons !Word8 !WList | WNil
857-- UTF-8 primitives, Lifted from GHC.IO.Encoding.UTF8.
861ord2 c = assert (n >= 0x80 && n <= 0x07ff) (WCons x1 (WCons x2 WNil))
864 x1 = fromIntegral $ (n `shiftR` 6) + 0xC0
865 x2 = fromIntegral $ (n .&. 0x3F) + 0x80
869ord3 c = assert (n >= 0x0800 && n <= 0xffff) (WCons x1 (WCons x2 (WCons x3 WNil)))
872 x1 = fromIntegral $ (n `shiftR` 12) + 0xE0
873 x2 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80
874 x3 = fromIntegral $ (n .&. 0x3F) + 0x80
878ord4 c = assert (n >= 0x10000) (WCons x1 (WCons x2 (WCons x3 (WCons x4 WNil))))
881 x1 = fromIntegral $ (n `shiftR` 18) + 0xF0
882 x2 = fromIntegral $ ((n `shiftR` 12) .&. 0x3F) + 0x80
883 x3 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80
884 x4 = fromIntegral $ (n .&. 0x3F) + 0x80
886{-# INLINE_NORMAL readCharUtf8With #-}
887readCharUtf8With :: Monad m => WList -> Unfold m Char Word8
888readCharUtf8With surr = Unfold step inject
893 return $ case ord c of
894 x | x <= 0x7F -> fromIntegral x `WCons` WNil
895 | x <= 0x7FF -> ord2 c
896 | x <= 0xFFFF -> if isSurrogate c then surr else ord3 c
897 | otherwise -> ord4 c
899 {-# INLINE_LATE step #-}
900 step WNil = return Stop
901 step (WCons x xs) = return $ Yield x xs
903{-# INLINE_NORMAL readCharUtf8' #-}
904readCharUtf8' :: Monad m => Unfold m Char Word8
907 error "Streamly.Internal.Unicode.readCharUtf8': Encountered a surrogate"
909-- More yield points improve performance, but I am not sure if they can cause
910-- too much code bloat or some trouble with fusion. So keeping only two yield
911-- points for now, one for the ascii chars (fast path) and one for all other
913{-# INLINE_NORMAL encodeUtf8D' #-}
914encodeUtf8D' :: Monad m => Stream m Char -> Stream m Word8
915encodeUtf8D' = D.unfoldMany readCharUtf8'
917-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. When
918-- any invalid character (U+D800-U+D8FF) is encountered in the input stream the
919-- function errors out.
922{-# INLINE encodeUtf8' #-}
923encodeUtf8' :: (Monad m, IsStream t) => t m Char -> t m Word8
924encodeUtf8' = fromStreamD . encodeUtf8D' . toStreamD
926{-# INLINE_NORMAL readCharUtf8 #-}
927readCharUtf8 :: Monad m => Unfold m Char Word8
928readCharUtf8 = readCharUtf8With $ WCons 239 (WCons 191 (WCons 189 WNil))
930-- | See section "3.9 Unicode Encoding Forms" in
931-- https://www.unicode.org/versions/Unicode13.0.0/UnicodeStandard-13.0.pdf
933{-# INLINE_NORMAL encodeUtf8D #-}
934encodeUtf8D :: Monad m => Stream m Char -> Stream m Word8
935encodeUtf8D = D.unfoldMany readCharUtf8
937-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. Any
938-- Invalid characters (U+D800-U+D8FF) in the input stream are replaced by the
939-- Unicode replacement character U+FFFD.
941-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
943-- /Since: 0.8.0 (Lenient Behaviour)/
944{-# INLINE encodeUtf8 #-}
945encodeUtf8 :: (Monad m, IsStream t) => t m Char -> t m Word8
946encodeUtf8 = fromStreamD . encodeUtf8D . toStreamD
948{-# INLINE_NORMAL readCharUtf8_ #-}
949readCharUtf8_ :: Monad m => Unfold m Char Word8
950readCharUtf8_ = readCharUtf8With WNil
952{-# INLINE_NORMAL encodeUtf8D_ #-}
953encodeUtf8D_ :: Monad m => Stream m Char -> Stream m Word8
954encodeUtf8D_ = D.unfoldMany readCharUtf8_
956-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. Any
957-- Invalid characters (U+D800-U+D8FF) in the input stream are dropped.
960{-# INLINE encodeUtf8_ #-}
961encodeUtf8_ :: (Monad m, IsStream t) => t m Char -> t m Word8
962encodeUtf8_ = fromStreamD . encodeUtf8D_ . toStreamD
964-- | Same as 'encodeUtf8'
966{-# DEPRECATED encodeUtf8Lax "Please use 'encodeUtf8' instead" #-}
967{-# INLINE encodeUtf8Lax #-}
968encodeUtf8Lax :: (IsStream t, Monad m) => t m Char -> t m Word8
969encodeUtf8Lax = encodeUtf8
971-------------------------------------------------------------------------------
972-- Encode streams of containers
973-------------------------------------------------------------------------------
975-- | Encode a container to @Array Word8@ provided an unfold to covert it to a
976-- Char stream and an encoding function.
979{-# INLINE encodeObject #-}
980encodeObject :: MonadIO m =>
981 (SerialT m Char -> SerialT m Word8)
985encodeObject encode u = S.fold Array.write . encode . S.unfold u
987-- | Encode a stream of container objects using the supplied encoding scheme.
988-- Each object is encoded as an @Array Word8@.
991{-# INLINE encodeObjects #-}
992encodeObjects :: (MonadIO m, IsStream t) =>
993 (SerialT m Char -> SerialT m Word8)
997encodeObjects encode u = adapt . Serial.mapM (encodeObject encode u) . adapt
999-- | Encode a stream of 'String' using the supplied encoding scheme. Each
1000-- string is encoded as an @Array Word8@.
1003{-# INLINE encodeStrings #-}
1004encodeStrings :: (MonadIO m, IsStream t) =>
1005 (SerialT m Char -> SerialT m Word8) -> t m String -> t m (Array Word8)
1006encodeStrings encode = encodeObjects encode Unfold.fromList
1009-------------------------------------------------------------------------------
1010-- Utility operations on strings
1011-------------------------------------------------------------------------------
1013strip :: IsStream t => t m Char -> t m Char
1016stripTail :: IsStream t => t m Char -> t m Char
1017stripTail = undefined
1020-- | Remove leading whitespace from a string.
1022-- > stripHead = S.dropWhile isSpace
1025{-# INLINE stripHead #-}
1026stripHead :: (Monad m, IsStream t) => t m Char -> t m Char
1027stripHead = S.dropWhile isSpace
1029-- | Fold each line of the stream using the supplied 'Fold'
1030-- and stream the result.
1032-- >>> Stream.toList $ lines Fold.toList (Stream.fromList "lines\nthis\nstring\n\n\n")
1033-- ["lines","this","string","",""]
1035-- > lines = S.splitOnSuffix (== '\n')
1039lines :: (Monad m, IsStream t) => Fold m Char b -> t m Char -> t m b
1040lines = S.splitOnSuffix (== '\n')
1042#if !MIN_VERSION_base(4,17,0)
1043foreign import ccall unsafe "u_iswspace"
1044 iswspace :: Int -> Int
1047-- | Code copied from base/Data.Char to INLINE it
1048{-# INLINE isSpace #-}
1049isSpace :: Char -> Bool
1051 | uc <= 0x377 = uc == 32 || uc - 0x9 <= 4 || uc == 0xa0
1052#if MIN_VERSION_base(4,17,0)
1053 | otherwise = generalCategory c == Space
1055 | otherwise = iswspace (ord c) /= 0
1058 uc = fromIntegral (ord c) :: Word
1060-- | Fold each word of the stream using the supplied 'Fold'
1061-- and stream the result.
1063-- >>> Stream.toList $ words Fold.toList (Stream.fromList "fold these words")
1064-- ["fold","these","words"]
1066-- > words = S.wordsBy isSpace
1070words :: (Monad m, IsStream t) => Fold m Char b -> t m Char -> t m b
1071words = S.wordsBy isSpace
1073-- | Unfold a stream to character streams using the supplied 'Unfold'
1074-- and concat the results suffixing a newline character @\\n@ to each stream.
1077-- unlines = Stream.interposeSuffix '\n'
1078-- unlines = Stream.intercalateSuffix Unfold.fromList "\n"
1082{-# INLINE unlines #-}
1083unlines :: (MonadIO m, IsStream t) => Unfold m a Char -> t m a -> t m Char
1084unlines = S.interposeSuffix '\n'
1086-- | Unfold the elements of a stream to character streams using the supplied
1087-- 'Unfold' and concat the results with a whitespace character infixed between
1091-- unwords = Stream.interpose ' '
1092-- unwords = Stream.intercalate Unfold.fromList " "
1096{-# INLINE unwords #-}
1097unwords :: (MonadIO m, IsStream t) => Unfold m a Char -> t m a -> t m Char
1098unwords = 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