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 (void)
85import Control.Monad.Catch (MonadThrow(..), MonadCatch)
86import Control.Monad.IO.Class (MonadIO, liftIO)
87import Data.Bits (shiftR, shiftL, (.|.), (.&.))
88import Data.Char (chr, ord)
89#if MIN_VERSION_base(4,17,0)
90import Data.Char (generalCategory, GeneralCategory(Space))
92import Data.Word (Word8)
93import Foreign.Marshal.Alloc (mallocBytes)
94import Foreign.Storable (Storable(..))
95import Fusion.Plugin.Types (Fuse(..))
96import GHC.Base (assert, unsafeChr)
97import GHC.IO.Encoding.Failure (isSurrogate)
98import GHC.Ptr (Ptr (..), plusPtr)
99import System.IO.Unsafe (unsafePerformIO)
100import Streamly.Internal.Data.Array.Unboxed (Array)
101import Streamly.Internal.Data.Array.Unboxed.Mut.Type (ArrayContents)
102import Streamly.Internal.Data.Fold (Fold)
103import Streamly.Internal.Data.Stream.IsStream.Type
104 (IsStream, fromStreamD, toStreamD, adapt)
105import Streamly.Internal.Data.Stream.Serial (SerialT)
106import Streamly.Internal.Data.Stream.StreamD (Stream(..), Step (..))
107import Streamly.Internal.Data.SVar (adaptState)
108import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
109import Streamly.Internal.Data.Unboxed (peekWith)
110import Streamly.Internal.Data.Unfold.Type (Unfold(..))
111import Streamly.Internal.System.IO (unsafeInlineIO)
113import qualified Streamly.Internal.Data.Fold as Fold
114import qualified Streamly.Internal.Data.Unfold as Unfold
115import qualified Streamly.Internal.Data.Parser as Parser
116import qualified Streamly.Internal.Data.Parser.ParserD as ParserD
117import qualified Streamly.Internal.Data.Stream.Serial as Serial
118import qualified Streamly.Internal.Data.Array.Unboxed as Array
119import qualified Streamly.Internal.Data.Array.Unboxed.Type as A
120import qualified Streamly.Internal.Data.Stream.IsStream as S
121import qualified Streamly.Internal.Data.Stream.StreamD as D
123import Prelude hiding (lines, words, unlines, unwords)
127-- >>> import Prelude hiding (lines, words, unlines, unwords)
128-- >>> import qualified Streamly.Prelude as Stream
129-- >>> import qualified Streamly.Data.Fold as Fold
130-- >>> import Streamly.Internal.Unicode.Stream
132-------------------------------------------------------------------------------
134-------------------------------------------------------------------------------
136-- | Decode a stream of bytes to Unicode characters by mapping each byte to a
137-- corresponding Unicode 'Char' in 0-255 range.
139-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
142{-# INLINE decodeLatin1 #-}
143decodeLatin1 :: (IsStream t, Monad m) => t m Word8 -> t m Char
144decodeLatin1 = S.map (unsafeChr . fromIntegral)
146-------------------------------------------------------------------------------
148-------------------------------------------------------------------------------
150-- | Encode a stream of Unicode characters to bytes by mapping each character
151-- to a byte in 0-255 range. Throws an error if the input stream contains
152-- characters beyond 255.
155{-# INLINE encodeLatin1' #-}
156encodeLatin1' :: (IsStream t, Monad m) => t m Char -> t m Word8
157encodeLatin1' = S.map convert
160 let codepoint = ord c
161 in if codepoint > 255
162 then error $ "Streamly.Unicode.encodeLatin1 invalid " ++
163 "input char codepoint " ++ show codepoint
164 else fromIntegral codepoint
166-- XXX Should we instead replace the invalid chars by NUL or whitespace or some
167-- other control char? That may affect the perf a bit but may be a better
170-- | Like 'encodeLatin1'' but silently maps input codepoints beyond 255 to
171-- arbitrary Latin1 chars in 0-255 range. No error or exception is thrown when
172-- such mapping occurs.
174-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
176-- /Since: 0.8.0 (Lenient Behaviour)/
177{-# INLINE encodeLatin1 #-}
178encodeLatin1 :: (IsStream t, Monad m) => t m Char -> t m Word8
179encodeLatin1 = S.map (fromIntegral . ord)
181-- | Like 'encodeLatin1' but drops the input characters beyond 255.
184{-# INLINE encodeLatin1_ #-}
185encodeLatin1_ :: (IsStream t, Monad m) => t m Char -> t m Word8
186encodeLatin1_ = S.map (fromIntegral . ord) . S.filter (<= chr 255)
188-- | Same as 'encodeLatin1'
190{-# DEPRECATED encodeLatin1Lax "Please use 'encodeLatin1' instead" #-}
191{-# INLINE encodeLatin1Lax #-}
192encodeLatin1Lax :: (IsStream t, Monad m) => t m Char -> t m Word8
193encodeLatin1Lax = encodeLatin1
195-------------------------------------------------------------------------------
197-------------------------------------------------------------------------------
199-- Int helps in cheaper conversion from Int to Char
201type DecodeState = Word8
203-- We can divide the errors in three general categories:
204-- * A non-starter was encountered in a begin state
205-- * A starter was encountered without completing a codepoint
206-- * The last codepoint was not complete (input underflow)
208-- Need to separate resumable and non-resumable error. In case of non-resumable
209-- error we can also provide the failing byte. In case of resumable error the
210-- state can be opaque.
212data DecodeError = DecodeError !DecodeState !CodePoint deriving Show
214-- See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
216-- XXX Use names decodeSuccess = 0, decodeFailure = 12
218decodeTable :: [Word8]
220 -- The first part of the table maps bytes to character classes that
221 -- to reduce the size of the transition table and create bitmasks.
222 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,
223 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,
224 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,
225 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,
226 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,
227 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,
228 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,
229 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,
231 -- The second part is a transition table that maps a combination
232 -- of a state of the automaton and a character class to a state.
233 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
234 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
235 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
236 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
237 12,36,12,12,12,12,12,12,12,12,12,12
240{-# INLINE utf8dLength #-}
242utf8dLength = length decodeTable
244-- | We do not want to garbage collect this and free the memory, we want to
245-- keep this persistent. We don't know how to do that with GHC without having a
246-- reference in some global structure. So we use a hack, use mallocBytes so
247-- that the GC has no way to free it.
248{-# NOINLINE utf8d #-}
250utf8d = unsafePerformIO $ do
251 let size = utf8dLength
252 p <- liftIO $ mallocBytes size
254 (Fold.foldlM' (\b a -> poke b a >> return (b `plusPtr` 1)) (return p))
255 (D.fromList decodeTable)
258-- | Return element at the specified index without checking the bounds.
259-- and without touching the foreign ptr.
260{-# INLINE_NORMAL unsafePeekElemOff #-}
261unsafePeekElemOff :: forall a. Storable a => Ptr a -> Int -> a
262unsafePeekElemOff p i =
263 let !x = unsafeInlineIO $ peekElemOff p i
266-- XXX We can use a fromPtr stream to implement it.
267{-# INLINE showMemory #-}
269 forall a. (Show a, Storable a) => Ptr a -> Ptr a -> String
272 let cur1 = cur `plusPtr` sizeOf (undefined :: a)
273 in show (unsafeInlineIO $ peek cur) ++ " " ++ showMemory cur1 end
276-- decode is split into two separate cases to avoid branching instructions.
277-- From the higher level flow we already know which case we are in so we can
278-- call the appropriate decode function.
280-- When the state is 0
281{-# INLINE decode0 #-}
282decode0 :: Ptr Word8 -> Word8 -> Tuple' DecodeState CodePoint
284 let !t = table `unsafePeekElemOff` fromIntegral byte
285 !codep' = (0xff `shiftR` fromIntegral t) .&. fromIntegral byte
286 !state' = table `unsafePeekElemOff` (256 + fromIntegral t)
287 in assert ((byte > 0x7f || error showByte)
288 && (state' /= 0 || error (showByte ++ showTable)))
289 (Tuple' state' codep')
293 utf8tableEnd = table `plusPtr` 364
294 showByte = "Streamly: decode0: byte: " ++ show byte
295 showTable = " table: " ++ showMemory table utf8tableEnd
297-- When the state is not 0
298{-# INLINE decode1 #-}
304 -> Tuple' DecodeState CodePoint
305decode1 table state codep byte =
306 -- Remember codep is Int type!
307 -- Can it be unsafe to convert the resulting Int to Char?
308 let !t = table `unsafePeekElemOff` fromIntegral byte
309 !codep' = (fromIntegral byte .&. 0x3f) .|. (codep `shiftL` 6)
310 !state' = table `unsafePeekElemOff`
311 (256 + fromIntegral state + fromIntegral t)
312 in assert (codep' <= 0x10FFFF
313 || error (showByte ++ showState state codep))
314 (Tuple' state' codep')
317 utf8tableEnd = table `plusPtr` 364
318 showByte = "Streamly: decode1: byte: " ++ show byte
320 " state: " ++ show st ++
321 " codepoint: " ++ show cp ++
322 " table: " ++ showMemory table utf8tableEnd
324-------------------------------------------------------------------------------
325-- Resumable UTF-8 decoding
326-------------------------------------------------------------------------------
328-- Strangely, GHCJS hangs linking template-haskell with this
330{-# ANN type UTF8DecodeState Fuse #-}
332data UTF8DecodeState s a
334 | UTF8DecodeInit1 s Word8
335 | UTF8DecodeFirst s Word8
336 | UTF8Decoding s !DecodeState !CodePoint
337 | YieldAndContinue a (UTF8DecodeState s a)
340{-# INLINE_NORMAL resumeDecodeUtf8EitherD #-}
341resumeDecodeUtf8EitherD
346 -> Stream m (Either DecodeError Char)
347resumeDecodeUtf8EitherD dst codep (Stream step state) =
350 then UTF8DecodeInit state
351 else UTF8Decoding state dst codep
352 in Stream (step' utf8d) stt
354 {-# INLINE_LATE step' #-}
355 step' _ gst (UTF8DecodeInit st) = do
356 r <- step (adaptState gst) st
358 Yield x s -> Skip (UTF8DecodeInit1 s x)
359 Skip s -> Skip (UTF8DecodeInit s)
362 step' _ _ (UTF8DecodeInit1 st x) = do
363 -- Note: It is important to use a ">" instead of a "<=" test
364 -- here for GHC to generate code layout for default branch
365 -- prediction for the common case. This is fragile and might
366 -- change with the compiler versions, we need a more reliable
367 -- "likely" primitive to control branch predication.
370 return $ Skip $ YieldAndContinue
371 (Right $ unsafeChr (fromIntegral x))
373 -- Using a separate state here generates a jump to a
374 -- separate code block in the core which seems to perform
375 -- slightly better for the non-ascii case.
376 True -> return $ Skip $ UTF8DecodeFirst st x
378 -- XXX should we merge it with UTF8DecodeInit1?
379 step' table _ (UTF8DecodeFirst st x) = do
380 let (Tuple' sv cp) = decode0 table x
384 Skip $ YieldAndContinue (Left $ DecodeError 0 (fromIntegral x))
386 0 -> error "unreachable state"
387 _ -> Skip (UTF8Decoding st sv cp)
389 -- We recover by trying the new byte x a starter of a new codepoint.
390 -- XXX on error need to report the next byte "x" as well.
391 -- XXX need to use the same recovery in array decoding routine as well
392 step' table gst (UTF8Decoding st statePtr codepointPtr) = do
393 r <- step (adaptState gst) st
396 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
399 0 -> Skip $ YieldAndContinue (Right $ unsafeChr cp)
402 Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr)
403 (UTF8DecodeInit1 s x)
404 _ -> Skip (UTF8Decoding s sv cp)
405 Skip s -> return $ Skip (UTF8Decoding s statePtr codepointPtr)
406 Stop -> return $ Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr) Done
408 step' _ _ (YieldAndContinue c s) = return $ Yield c s
409 step' _ _ Done = return Stop
411-- XXX We can use just one API, and define InitState = 0 and InitCodePoint = 0
412-- to use as starting state.
414{-# INLINE_NORMAL decodeUtf8EitherD #-}
415decodeUtf8EitherD :: Monad m
416 => Stream m Word8 -> Stream m (Either DecodeError Char)
417decodeUtf8EitherD = resumeDecodeUtf8EitherD 0 0
422{-# INLINE decodeUtf8Either #-}
423decodeUtf8Either :: (Monad m, IsStream t)
424 => t m Word8 -> t m (Either DecodeError Char)
425decodeUtf8Either = fromStreamD . decodeUtf8EitherD . toStreamD
430{-# INLINE resumeDecodeUtf8Either #-}
431resumeDecodeUtf8Either
432 :: (Monad m, IsStream t)
436 -> t m (Either DecodeError Char)
437resumeDecodeUtf8Either st cp =
438 fromStreamD . resumeDecodeUtf8EitherD st cp . toStreamD
440-------------------------------------------------------------------------------
442-------------------------------------------------------------------------------
444data CodingFailureMode
445 = TransliterateCodingFailure
446 | ErrorOnCodingFailure
447 | DropOnCodingFailure
450{-# INLINE replacementChar #-}
451replacementChar :: Char
452replacementChar = '\xFFFD'
454data UTF8CharDecodeState a
456 | UTF8CharDecoding !DecodeState !CodePoint
458{-# INLINE parseCharUtf8WithD #-}
460 MonadThrow m => CodingFailureMode -> ParserD.Parser m Word8 Char
461parseCharUtf8WithD cfm = ParserD.Parser (step' utf8d) initial extract
465 prefix = "Streamly.Internal.Data.Stream.parseCharUtf8WithD:"
467 {-# INLINE initial #-}
468 initial = return $ ParserD.IPartial UTF8CharDecodeInit
470 handleError err souldBackTrack =
472 ErrorOnCodingFailure -> ParserD.Error err
473 TransliterateCodingFailure ->
474 case souldBackTrack of
475 True -> ParserD.Done 1 replacementChar
476 False -> ParserD.Done 0 replacementChar
477 DropOnCodingFailure ->
478 case souldBackTrack of
479 True -> ParserD.Continue 1 UTF8CharDecodeInit
480 False -> ParserD.Continue 0 UTF8CharDecodeInit
483 step' table UTF8CharDecodeInit x =
484 -- Note: It is important to use a ">" instead of a "<=" test
485 -- here for GHC to generate code layout for default branch
486 -- prediction for the common case. This is fragile and might
487 -- change with the compiler versions, we need a more reliable
488 -- "likely" primitive to control branch predication.
489 return $ case x > 0x7f of
490 False -> ParserD.Done 0 $ unsafeChr $ fromIntegral x
492 let (Tuple' sv cp) = decode0 table x
496 ++ "Invalid first UTF8 byte" ++ show x
497 in handleError msg False
498 0 -> error $ prefix ++ "unreachable state"
499 _ -> ParserD.Continue 0 (UTF8CharDecoding sv cp)
501 step' table (UTF8CharDecoding statePtr codepointPtr) x = return $
502 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
504 0 -> ParserD.Done 0 $ unsafeChr cp
507 ++ "Invalid subsequent UTF8 byte"
511 ++ "accumulated value"
513 in handleError msg True
514 _ -> ParserD.Continue 0 (UTF8CharDecoding sv cp)
516 {-# INLINE extract #-}
517 extract UTF8CharDecodeInit = error $ prefix ++ "Not enough input"
518 extract (UTF8CharDecoding _ _) =
520 ErrorOnCodingFailure ->
521 throwM $ ParserD.ParseError $ prefix ++ "Not enough input"
522 TransliterateCodingFailure -> return replacementChar
523 -- XXX We shouldn't error out here. There is no way to represent an
524 -- empty parser result unless we return a "Maybe" type.
525 DropOnCodingFailure -> error $ prefix ++ "Not enough input"
527-- XXX This should ideally accept a "CodingFailureMode" and perform appropriate
528-- error handling. This isn't possible now as "TransliterateCodingFailure"'s
529-- workflow requires backtracking 1 element. This can be revisited once "Fold"
530-- supports backtracking.
531{-# INLINE writeCharUtf8' #-}
532writeCharUtf8' :: MonadThrow m => Fold m Word8 Char
533writeCharUtf8' = ParserD.toFold (parseCharUtf8WithD ErrorOnCodingFailure)
535-- XXX The initial idea was to have "parseCharUtf8" and offload the error
536-- handling to another parser. So, say we had "parseCharUtf8'",
538-- >>> parseCharUtf8Smart = parseCharUtf8' <|> Parser.fromPure replacementChar
540-- But unfortunately parseCharUtf8Smart used in conjunction with "parseMany" -
541-- that is "parseMany parseCharUtf8Smart" on a stream causes the heap to
542-- overflow. Even a heap size of 500 MB was not sufficient.
544-- This needs to be investigated futher.
545{-# INLINE parseCharUtf8With #-}
547 MonadCatch m => CodingFailureMode -> Parser.Parser m Word8 Char
548parseCharUtf8With = ParserD.toParserK . parseCharUtf8WithD
550-- XXX write it as a parser and use parseMany to decode a stream, need to check
551-- if that preserves the same performance. Or we can use a resumable parser
552-- that parses a chunk at a time.
554-- XXX Implement this in terms of decodeUtf8Either. Need to make sure that
555-- decodeUtf8Either preserves the performance characterstics.
557{-# INLINE_NORMAL decodeUtf8WithD #-}
558decodeUtf8WithD :: Monad m
559 => CodingFailureMode -> Stream m Word8 -> Stream m Char
560decodeUtf8WithD cfm (Stream step state) =
561 Stream (step' utf8d) (UTF8DecodeInit state)
565 prefix = "Streamly.Internal.Data.Stream.StreamD.decodeUtf8With: "
567 {-# INLINE handleError #-}
570 ErrorOnCodingFailure -> error e
571 TransliterateCodingFailure -> YieldAndContinue replacementChar s
572 DropOnCodingFailure -> s
574 {-# INLINE handleUnderflow #-}
577 ErrorOnCodingFailure -> error $ prefix ++ "Not enough input"
578 TransliterateCodingFailure -> YieldAndContinue replacementChar Done
579 DropOnCodingFailure -> Done
581 {-# INLINE_LATE step' #-}
582 step' _ gst (UTF8DecodeInit st) = do
583 r <- step (adaptState gst) st
585 Yield x s -> Skip (UTF8DecodeInit1 s x)
586 Skip s -> Skip (UTF8DecodeInit s)
589 step' _ _ (UTF8DecodeInit1 st x) = do
590 -- Note: It is important to use a ">" instead of a "<=" test
591 -- here for GHC to generate code layout for default branch
592 -- prediction for the common case. This is fragile and might
593 -- change with the compiler versions, we need a more reliable
594 -- "likely" primitive to control branch predication.
597 return $ Skip $ YieldAndContinue
598 (unsafeChr (fromIntegral x))
600 -- Using a separate state here generates a jump to a
601 -- separate code block in the core which seems to perform
602 -- slightly better for the non-ascii case.
603 True -> return $ Skip $ UTF8DecodeFirst st x
605 -- XXX should we merge it with UTF8DecodeInit1?
606 step' table _ (UTF8DecodeFirst st x) = do
607 let (Tuple' sv cp) = decode0 table x
611 let msg = prefix ++ "Invalid first UTF8 byte " ++ show x
612 in Skip $ handleError msg (UTF8DecodeInit st)
613 0 -> error "unreachable state"
614 _ -> Skip (UTF8Decoding st sv cp)
616 -- We recover by trying the new byte x as a starter of a new codepoint.
617 -- XXX need to use the same recovery in array decoding routine as well
618 step' table gst (UTF8Decoding st statePtr codepointPtr) = do
619 r <- step (adaptState gst) st
622 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
624 0 -> Skip $ YieldAndContinue
625 (unsafeChr cp) (UTF8DecodeInit s)
628 ++ "Invalid subsequent UTF8 byte "
632 ++ " accumulated value "
634 in Skip $ handleError msg (UTF8DecodeInit1 s x)
635 _ -> Skip (UTF8Decoding s sv cp)
637 Skip (UTF8Decoding s statePtr codepointPtr)
638 Stop -> return $ Skip handleUnderflow
640 step' _ _ (YieldAndContinue c s) = return $ Yield c s
641 step' _ _ Done = return Stop
643{-# INLINE decodeUtf8D #-}
644decodeUtf8D :: Monad m => Stream m Word8 -> Stream m Char
645decodeUtf8D = decodeUtf8WithD TransliterateCodingFailure
647-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
648-- Any invalid codepoint encountered is replaced with the unicode replacement
651-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
653-- /Since: 0.8.0 (Lenient Behaviour)/
654{-# INLINE decodeUtf8 #-}
655decodeUtf8 :: (Monad m, IsStream t) => t m Word8 -> t m Char
656decodeUtf8 = fromStreamD . decodeUtf8D . toStreamD
658{-# INLINE decodeUtf8D' #-}
659decodeUtf8D' :: Monad m => Stream m Word8 -> Stream m Char
660decodeUtf8D' = decodeUtf8WithD ErrorOnCodingFailure
662-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
663-- The function throws an error if an invalid codepoint is encountered.
666{-# INLINE decodeUtf8' #-}
667decodeUtf8' :: (Monad m, IsStream t) => t m Word8 -> t m Char
668decodeUtf8' = fromStreamD . decodeUtf8D' . toStreamD
670{-# INLINE decodeUtf8D_ #-}
671decodeUtf8D_ :: Monad m => Stream m Word8 -> Stream m Char
672decodeUtf8D_ = decodeUtf8WithD DropOnCodingFailure
674-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
675-- Any invalid codepoint encountered is dropped.
678{-# INLINE decodeUtf8_ #-}
679decodeUtf8_ :: (Monad m, IsStream t) => t m Word8 -> t m Char
680decodeUtf8_ = fromStreamD . decodeUtf8D_ . toStreamD
682-- | Same as 'decodeUtf8'
684{-# DEPRECATED decodeUtf8Lax "Please use 'decodeUtf8' instead" #-}
685{-# INLINE decodeUtf8Lax #-}
686decodeUtf8Lax :: (IsStream t, Monad m) => t m Word8 -> t m Char
687decodeUtf8Lax = decodeUtf8
689-------------------------------------------------------------------------------
690-- Decoding Array Streams
691-------------------------------------------------------------------------------
694{-# ANN type FlattenState Fuse #-}
697 = OuterLoop s !(Maybe (DecodeState, CodePoint))
698 | InnerLoopDecodeInit s (ArrayContents a) !Int !Int
699 | InnerLoopDecodeFirst s (ArrayContents a) !Int !Int Word8
700 | InnerLoopDecoding s (ArrayContents a) !Int !Int
701 !DecodeState !CodePoint
702 | YAndC !Char (FlattenState s a) -- These constructors can be
703 -- encoded in the UTF8DecodeState
704 -- type, I prefer to keep these
705 -- flat even though that means
706 -- coming up with new names
709-- The normal decodeUtf8 above should fuse with flattenArrays
710-- to create this exact code but it doesn't for some reason, as of now this
711-- remains the fastest way I could figure out to decodeUtf8.
713-- XXX Add Proper error messages
714{-# INLINE_NORMAL decodeUtf8ArraysWithD #-}
715decodeUtf8ArraysWithD ::
718 -> Stream m (A.Array Word8)
720decodeUtf8ArraysWithD cfm (Stream step state) =
721 Stream (step' utf8d) (OuterLoop state Nothing)
723 {-# INLINE transliterateOrError #-}
724 transliterateOrError e s =
726 ErrorOnCodingFailure -> error e
727 TransliterateCodingFailure -> YAndC replacementChar s
728 DropOnCodingFailure -> s
729 {-# INLINE inputUnderflow #-}
732 ErrorOnCodingFailure ->
734 show "Streamly.Internal.Data.Stream.StreamD."
735 ++ "decodeUtf8ArraysWith: Input Underflow"
736 TransliterateCodingFailure -> YAndC replacementChar D
737 DropOnCodingFailure -> D
738 {-# INLINE_LATE step' #-}
739 step' _ gst (OuterLoop st Nothing) = do
740 r <- step (adaptState gst) st
743 Yield A.Array {..} s ->
744 Skip (InnerLoopDecodeInit s arrContents arrStart aEnd)
745 Skip s -> Skip (OuterLoop s Nothing)
747 step' _ gst (OuterLoop st dst@(Just (ds, cp))) = do
748 r <- step (adaptState gst) st
751 Yield A.Array {..} s ->
752 Skip (InnerLoopDecoding s arrContents arrStart aEnd ds cp)
753 Skip s -> Skip (OuterLoop s dst)
754 Stop -> Skip inputUnderflow
755 step' _ _ (InnerLoopDecodeInit st _ p end)
757 return $ Skip $ OuterLoop st Nothing
758 step' _ _ (InnerLoopDecodeInit st contents p end) = do
759 x <- liftIO $ peekWith contents p
760 -- Note: It is important to use a ">" instead of a "<=" test here for
761 -- GHC to generate code layout for default branch prediction for the
762 -- common case. This is fragile and might change with the compiler
763 -- versions, we need a more reliable "likely" primitive to control
764 -- branch predication.
767 return $ Skip $ YAndC
768 (unsafeChr (fromIntegral x))
769 (InnerLoopDecodeInit st contents (p + 1) end)
770 -- Using a separate state here generates a jump to a separate code
771 -- block in the core which seems to perform slightly better for the
773 True -> return $ Skip $ InnerLoopDecodeFirst st contents p end x
775 step' table _ (InnerLoopDecodeFirst st contents p end x) = do
776 let (Tuple' sv cp) = decode0 table x
783 "Streamly.Internal.Data.Stream.StreamD."
784 ++ "decodeUtf8ArraysWith: Invalid UTF8"
785 ++ " codepoint encountered"
787 (InnerLoopDecodeInit st contents (p + 1) end)
788 0 -> error "unreachable state"
789 _ -> Skip (InnerLoopDecoding st contents (p + 1) end sv cp)
790 step' _ _ (InnerLoopDecoding st _ p end sv cp)
791 | p == end = return $ Skip $ OuterLoop st (Just (sv, cp))
792 step' table _ (InnerLoopDecoding st contents p end statePtr codepointPtr) = do
793 x <- liftIO $ peekWith contents p
794 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
801 (InnerLoopDecodeInit st contents (p + 1) end)
806 "Streamly.Internal.Data.Stream.StreamD."
807 ++ "decodeUtf8ArraysWith: Invalid UTF8"
808 ++ " codepoint encountered"
810 (InnerLoopDecodeInit st contents (p + 1) end)
813 (InnerLoopDecoding st contents (p + 1) end sv cp)
814 step' _ _ (YAndC c s) = return $ Yield c s
815 step' _ _ D = return Stop
817{-# INLINE decodeUtf8ArraysD #-}
820 => Stream m (A.Array Word8)
822decodeUtf8ArraysD = decodeUtf8ArraysWithD TransliterateCodingFailure
827{-# INLINE decodeUtf8Arrays #-}
829 (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
831 fromStreamD . decodeUtf8ArraysD . toStreamD
833{-# INLINE decodeUtf8ArraysD' #-}
836 => Stream m (A.Array Word8)
838decodeUtf8ArraysD' = decodeUtf8ArraysWithD ErrorOnCodingFailure
843{-# INLINE decodeUtf8Arrays' #-}
844decodeUtf8Arrays' :: (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
845decodeUtf8Arrays' = fromStreamD . decodeUtf8ArraysD' . toStreamD
847{-# INLINE decodeUtf8ArraysD_ #-}
850 => Stream m (A.Array Word8)
852decodeUtf8ArraysD_ = decodeUtf8ArraysWithD DropOnCodingFailure
857{-# INLINE decodeUtf8Arrays_ #-}
859 (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
861 fromStreamD . decodeUtf8ArraysD_ . toStreamD
863-------------------------------------------------------------------------------
864-- Encoding Unicode (UTF-8) Characters
865-------------------------------------------------------------------------------
867data WList = WCons !Word8 !WList | WNil
869-- UTF-8 primitives, Lifted from GHC.IO.Encoding.UTF8.
873ord2 c = assert (n >= 0x80 && n <= 0x07ff) (WCons x1 (WCons x2 WNil))
876 x1 = fromIntegral $ (n `shiftR` 6) + 0xC0
877 x2 = fromIntegral $ (n .&. 0x3F) + 0x80
881ord3 c = assert (n >= 0x0800 && n <= 0xffff) (WCons x1 (WCons x2 (WCons x3 WNil)))
884 x1 = fromIntegral $ (n `shiftR` 12) + 0xE0
885 x2 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80
886 x3 = fromIntegral $ (n .&. 0x3F) + 0x80
890ord4 c = assert (n >= 0x10000) (WCons x1 (WCons x2 (WCons x3 (WCons x4 WNil))))
893 x1 = fromIntegral $ (n `shiftR` 18) + 0xF0
894 x2 = fromIntegral $ ((n `shiftR` 12) .&. 0x3F) + 0x80
895 x3 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80
896 x4 = fromIntegral $ (n .&. 0x3F) + 0x80
898{-# INLINE_NORMAL readCharUtf8With #-}
899readCharUtf8With :: Monad m => WList -> Unfold m Char Word8
900readCharUtf8With surr = Unfold step inject
905 return $ case ord c of
906 x | x <= 0x7F -> fromIntegral x `WCons` WNil
907 | x <= 0x7FF -> ord2 c
908 | x <= 0xFFFF -> if isSurrogate c then surr else ord3 c
909 | otherwise -> ord4 c
911 {-# INLINE_LATE step #-}
912 step WNil = return Stop
913 step (WCons x xs) = return $ Yield x xs
915{-# INLINE_NORMAL readCharUtf8' #-}
916readCharUtf8' :: Monad m => Unfold m Char Word8
919 error "Streamly.Internal.Unicode.readCharUtf8': Encountered a surrogate"
921-- More yield points improve performance, but I am not sure if they can cause
922-- too much code bloat or some trouble with fusion. So keeping only two yield
923-- points for now, one for the ascii chars (fast path) and one for all other
925{-# INLINE_NORMAL encodeUtf8D' #-}
926encodeUtf8D' :: Monad m => Stream m Char -> Stream m Word8
927encodeUtf8D' = D.unfoldMany readCharUtf8'
929-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. When
930-- any invalid character (U+D800-U+D8FF) is encountered in the input stream the
931-- function errors out.
934{-# INLINE encodeUtf8' #-}
935encodeUtf8' :: (Monad m, IsStream t) => t m Char -> t m Word8
936encodeUtf8' = fromStreamD . encodeUtf8D' . toStreamD
938{-# INLINE_NORMAL readCharUtf8 #-}
939readCharUtf8 :: Monad m => Unfold m Char Word8
940readCharUtf8 = readCharUtf8With $ WCons 239 (WCons 191 (WCons 189 WNil))
942-- | See section "3.9 Unicode Encoding Forms" in
943-- https://www.unicode.org/versions/Unicode13.0.0/UnicodeStandard-13.0.pdf
945{-# INLINE_NORMAL encodeUtf8D #-}
946encodeUtf8D :: Monad m => Stream m Char -> Stream m Word8
947encodeUtf8D = D.unfoldMany readCharUtf8
949-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. Any
950-- Invalid characters (U+D800-U+D8FF) in the input stream are replaced by the
951-- Unicode replacement character U+FFFD.
953-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
955-- /Since: 0.8.0 (Lenient Behaviour)/
956{-# INLINE encodeUtf8 #-}
957encodeUtf8 :: (Monad m, IsStream t) => t m Char -> t m Word8
958encodeUtf8 = fromStreamD . encodeUtf8D . toStreamD
960{-# INLINE_NORMAL readCharUtf8_ #-}
961readCharUtf8_ :: Monad m => Unfold m Char Word8
962readCharUtf8_ = readCharUtf8With WNil
964{-# INLINE_NORMAL encodeUtf8D_ #-}
965encodeUtf8D_ :: Monad m => Stream m Char -> Stream m Word8
966encodeUtf8D_ = D.unfoldMany readCharUtf8_
968-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. Any
969-- Invalid characters (U+D800-U+D8FF) in the input stream are dropped.
972{-# INLINE encodeUtf8_ #-}
973encodeUtf8_ :: (Monad m, IsStream t) => t m Char -> t m Word8
974encodeUtf8_ = fromStreamD . encodeUtf8D_ . toStreamD
976-- | Same as 'encodeUtf8'
978{-# DEPRECATED encodeUtf8Lax "Please use 'encodeUtf8' instead" #-}
979{-# INLINE encodeUtf8Lax #-}
980encodeUtf8Lax :: (IsStream t, Monad m) => t m Char -> t m Word8
981encodeUtf8Lax = encodeUtf8
983-------------------------------------------------------------------------------
984-- Encode streams of containers
985-------------------------------------------------------------------------------
987-- | Encode a container to @Array Word8@ provided an unfold to covert it to a
988-- Char stream and an encoding function.
991{-# INLINE encodeObject #-}
992encodeObject :: MonadIO m =>
993 (SerialT m Char -> SerialT m Word8)
997encodeObject encode u = S.fold Array.write . encode . S.unfold u
999-- | Encode a stream of container objects using the supplied encoding scheme.
1000-- Each object is encoded as an @Array Word8@.
1003{-# INLINE encodeObjects #-}
1004encodeObjects :: (MonadIO m, IsStream t) =>
1005 (SerialT m Char -> SerialT m Word8)
1008 -> t m (Array Word8)
1009encodeObjects encode u = adapt . Serial.mapM (encodeObject encode u) . adapt
1011-- | Encode a stream of 'String' using the supplied encoding scheme. Each
1012-- string is encoded as an @Array Word8@.
1015{-# INLINE encodeStrings #-}
1016encodeStrings :: (MonadIO m, IsStream t) =>
1017 (SerialT m Char -> SerialT m Word8) -> t m String -> t m (Array Word8)
1018encodeStrings encode = encodeObjects encode Unfold.fromList
1021-------------------------------------------------------------------------------
1022-- Utility operations on strings
1023-------------------------------------------------------------------------------
1025strip :: IsStream t => t m Char -> t m Char
1028stripTail :: IsStream t => t m Char -> t m Char
1029stripTail = undefined
1032-- | Remove leading whitespace from a string.
1034-- > stripHead = S.dropWhile isSpace
1037{-# INLINE stripHead #-}
1038stripHead :: (Monad m, IsStream t) => t m Char -> t m Char
1039stripHead = S.dropWhile isSpace
1041-- | Fold each line of the stream using the supplied 'Fold'
1042-- and stream the result.
1044-- >>> Stream.toList $ lines Fold.toList (Stream.fromList "lines\nthis\nstring\n\n\n")
1045-- ["lines","this","string","",""]
1047-- > lines = S.splitOnSuffix (== '\n')
1051lines :: (Monad m, IsStream t) => Fold m Char b -> t m Char -> t m b
1052lines = S.splitOnSuffix (== '\n')
1054#if !MIN_VERSION_base(4,17,0)
1055foreign import ccall unsafe "u_iswspace"
1056 iswspace :: Int -> Int
1059-- | Code copied from base/Data.Char to INLINE it
1060{-# INLINE isSpace #-}
1061isSpace :: Char -> Bool
1063 | uc <= 0x377 = uc == 32 || uc - 0x9 <= 4 || uc == 0xa0
1064#if MIN_VERSION_base(4,17,0)
1065 | otherwise = generalCategory c == Space
1067 | otherwise = iswspace (ord c) /= 0
1070 uc = fromIntegral (ord c) :: Word
1072-- | Fold each word of the stream using the supplied 'Fold'
1073-- and stream the result.
1075-- >>> Stream.toList $ words Fold.toList (Stream.fromList "fold these words")
1076-- ["fold","these","words"]
1078-- > words = S.wordsBy isSpace
1082words :: (Monad m, IsStream t) => Fold m Char b -> t m Char -> t m b
1083words = S.wordsBy isSpace
1085-- | Unfold a stream to character streams using the supplied 'Unfold'
1086-- and concat the results suffixing a newline character @\\n@ to each stream.
1089-- unlines = Stream.interposeSuffix '\n'
1090-- unlines = Stream.intercalateSuffix Unfold.fromList "\n"
1094{-# INLINE unlines #-}
1095unlines :: (MonadIO m, IsStream t) => Unfold m a Char -> t m a -> t m Char
1096unlines = S.interposeSuffix '\n'
1098-- | Unfold the elements of a stream to character streams using the supplied
1099-- 'Unfold' and concat the results with a whitespace character infixed between
1103-- unwords = Stream.interpose ' '
1104-- unwords = Stream.intercalate Unfold.fromList " "
1108{-# INLINE unwords #-}
1109unwords :: (MonadIO m, IsStream t) => Unfold m a Char -> t m a -> t m Char
1110unwords = 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