2-- Module : Streamly.Data.Internal.Unicode.Stream
3-- Copyright : (c) 2018 Composewell Technologies
4-- (c) Bjoern Hoehrmann 2008-2009
7-- Maintainer : streamly@composewell.com
8-- Stability : experimental
14module Streamly.Internal.Data.Unicode.Stream
16 -- * Construction (Decoding)
24 , resumeDecodeUtf8Either
26 , decodeUtf8ArraysLenient
28 -- * Elimination (Encoding)
34 -- * Operations on character strings
35 , strip -- (dropAround isSpace)
39 -- * StreamD UTF8 Encoding / Decoding transformations.
44 , resumeDecodeUtf8EitherD
46 , decodeUtf8ArraysLenientD
57import Control.Monad.IO.Class (MonadIO, liftIO)
58import Data.Bits (shiftR, shiftL, (.|.), (.&.))
60import Data.Word (Word8)
61import Foreign.ForeignPtr (touchForeignPtr)
62import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
63import Foreign.Storable (Storable(..))
64import GHC.Base (assert, unsafeChr)
65import GHC.ForeignPtr (ForeignPtr (..))
66import GHC.IO.Encoding.Failure (isSurrogate)
67import GHC.Ptr (Ptr (..), plusPtr)
68import System.IO.Unsafe (unsafePerformIO)
69import Streamly (IsStream)
70import Streamly.Data.Fold (Fold)
71import Streamly.Memory.Array (Array)
72import Streamly.Internal.Data.Unfold (Unfold)
73import Streamly.Internal.Data.SVar (adaptState)
74import Streamly.Internal.Data.Stream.StreamD (Stream(..), Step (..))
75import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
77import qualified Streamly.Internal.Memory.Array.Types as A
78import qualified Streamly.Internal.Data.Stream.IsStream as S
79import qualified Streamly.Internal.Data.Stream.StreamD as D
81import Prelude hiding (String, lines, words, unlines, unwords)
83-------------------------------------------------------------------------------
84-- Encoding/Decoding Unicode (UTF-8) Characters
85-------------------------------------------------------------------------------
87-- UTF-8 primitives, Lifted from GHC.IO.Encoding.UTF8.
89data WList = WCons !Word8 !WList | WNil
93ord2 c = assert (n >= 0x80 && n <= 0x07ff) (WCons x1 (WCons x2 WNil))
96 x1 = fromIntegral $ (n `shiftR` 6) + 0xC0
97 x2 = fromIntegral $ (n .&. 0x3F) + 0x80
101ord3 c = assert (n >= 0x0800 && n <= 0xffff) (WCons x1 (WCons x2 (WCons x3 WNil)))
104 x1 = fromIntegral $ (n `shiftR` 12) + 0xE0
105 x2 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80
106 x3 = fromIntegral $ (n .&. 0x3F) + 0x80
110ord4 c = assert (n >= 0x10000) (WCons x1 (WCons x2 (WCons x3 (WCons x4 WNil))))
113 x1 = fromIntegral $ (n `shiftR` 18) + 0xF0
114 x2 = fromIntegral $ ((n `shiftR` 12) .&. 0x3F) + 0x80
115 x3 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80
116 x4 = fromIntegral $ (n .&. 0x3F) + 0x80
118data CodingFailureMode
119 = TransliterateCodingFailure
120 | ErrorOnCodingFailure
123{-# INLINE replacementChar #-}
124replacementChar :: Char
125replacementChar = '\xFFFD'
127-- Int helps in cheaper conversion from Int to Char
129type DecodeState = Word8
131-- See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
133-- XXX Use names decodeSuccess = 0, decodeFailure = 12
135decodeTable :: [Word8]
137 -- The first part of the table maps bytes to character classes that
138 -- to reduce the size of the transition table and create bitmasks.
139 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,
140 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,
141 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,
142 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,
143 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,
144 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,
145 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,
146 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,
148 -- The second part is a transition table that maps a combination
149 -- of a state of the automaton and a character class to a state.
150 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
151 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
152 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
153 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
154 12,36,12,12,12,12,12,12,12,12,12,12
157{-# NOINLINE utf8d #-}
158utf8d :: A.Array Word8
161 -- Aligning to cacheline makes a barely noticeable difference
162 -- XXX currently alignment is not implemented for unmanaged allocation
163 $ D.runFold (A.writeNAlignedUnmanaged 64 (length decodeTable))
164 (D.fromList decodeTable)
166-- | Return element at the specified index without checking the bounds.
167-- and without touching the foreign ptr.
168{-# INLINE_NORMAL unsafePeekElemOff #-}
169unsafePeekElemOff :: forall a. Storable a => Ptr a -> Int -> a
170unsafePeekElemOff p i = let !x = A.unsafeInlineIO $ peekElemOff p i in x
172-- decode is split into two separate cases to avoid branching instructions.
173-- From the higher level flow we already know which case we are in so we can
174-- call the appropriate decode function.
176-- When the state is 0
177{-# INLINE decode0 #-}
178decode0 :: Ptr Word8 -> Word8 -> Tuple' DecodeState CodePoint
180 let !t = table `unsafePeekElemOff` fromIntegral byte
181 !codep' = (0xff `shiftR` fromIntegral t) .&. fromIntegral byte
182 !state' = table `unsafePeekElemOff` (256 + fromIntegral t)
183 in assert ((byte > 0x7f || error showByte)
184 && (state' /= 0 || error (showByte ++ showTable)))
185 (Tuple' state' codep')
190 let !(Ptr addr) = table
191 end = table `plusPtr` 364
192 in A.Array (ForeignPtr addr undefined) end :: A.Array Word8
193 showByte = "Streamly: decode0: byte: " ++ show byte
194 showTable = " table: " ++ show utf8table
196-- When the state is not 0
197{-# INLINE decode1 #-}
203 -> Tuple' DecodeState CodePoint
204decode1 table state codep byte =
205 -- Remember codep is Int type!
206 -- Can it be unsafe to convert the resulting Int to Char?
207 let !t = table `unsafePeekElemOff` fromIntegral byte
208 !codep' = (fromIntegral byte .&. 0x3f) .|. (codep `shiftL` 6)
209 !state' = table `unsafePeekElemOff`
210 (256 + fromIntegral state + fromIntegral t)
211 in assert (codep' <= 0x10FFFF
212 || error (showByte ++ showState state codep))
213 (Tuple' state' codep')
217 let !(Ptr addr) = table
218 end = table `plusPtr` 364
219 in A.Array (ForeignPtr addr undefined) end :: A.Array Word8
220 showByte = "Streamly: decode1: byte: " ++ show byte
222 " state: " ++ show st ++
223 " codepoint: " ++ show cp ++
224 " table: " ++ show utf8table
226-- We can divide the errors in three general categories:
227-- * A non-starter was encountered in a begin state
228-- * A starter was encountered without completing a codepoint
229-- * The last codepoint was not complete (input underflow)
231-- Need to separate resumable and non-resumable error. In case of non-resumable
232-- error we can also provide the failing byte. In case of resumable error the
233-- state can be opaque.
235data DecodeError = DecodeError !DecodeState !CodePoint deriving Show
238 = FreshPointDecodeInit s
239 | FreshPointDecodeInit1 s Word8
240 | FreshPointDecodeFirst s Word8
241 | FreshPointDecoding s !DecodeState !CodePoint
242 | YieldAndContinue a (FreshPoint s a)
245-- XXX write it as a parser and use parseMany to decode a stream, need to check
246-- if that preserves the same performance. Or we can use a resumable parser
247-- that parses a chunk at a time.
249-- XXX Implement this in terms of decodeUtf8Either. Need to make sure that
250-- decodeUtf8Either preserves the performance characterstics.
252{-# INLINE_NORMAL decodeUtf8WithD #-}
253decodeUtf8WithD :: Monad m
254 => CodingFailureMode -> Stream m Word8 -> Stream m Char
255decodeUtf8WithD cfm (Stream step state) =
256 let A.Array p _ = utf8d
257 !ptr = unsafeForeignPtrToPtr p
258 in Stream (step' ptr) (FreshPointDecodeInit state)
262 prefix = "Streamly.Internal.Data.Stream.StreamD.decodeUtf8With: "
264 {-# INLINE handleError #-}
267 ErrorOnCodingFailure -> error e
268 TransliterateCodingFailure -> YieldAndContinue replacementChar s
270 {-# INLINE handleUnderflow #-}
273 ErrorOnCodingFailure -> error $ prefix ++ "Not enough input"
274 TransliterateCodingFailure -> YieldAndContinue replacementChar Done
276 {-# INLINE_LATE step' #-}
277 step' _ gst (FreshPointDecodeInit st) = do
278 r <- step (adaptState gst) st
280 Yield x s -> Skip (FreshPointDecodeInit1 s x)
281 Skip s -> Skip (FreshPointDecodeInit s)
284 step' _ _ (FreshPointDecodeInit1 st x) = do
285 -- Note: It is important to use a ">" instead of a "<=" test
286 -- here for GHC to generate code layout for default branch
287 -- prediction for the common case. This is fragile and might
288 -- change with the compiler versions, we need a more reliable
289 -- "likely" primitive to control branch predication.
292 return $ Skip $ YieldAndContinue
293 (unsafeChr (fromIntegral x))
294 (FreshPointDecodeInit st)
295 -- Using a separate state here generates a jump to a
296 -- separate code block in the core which seems to perform
297 -- slightly better for the non-ascii case.
298 True -> return $ Skip $ FreshPointDecodeFirst st x
300 -- XXX should we merge it with FreshPointDecodeInit1?
301 step' table _ (FreshPointDecodeFirst st x) = do
302 let (Tuple' sv cp) = decode0 table x
306 let msg = prefix ++ "Invalid first UTF8 byte " ++ show x
307 in Skip $ handleError msg (FreshPointDecodeInit st)
308 0 -> error "unreachable state"
309 _ -> Skip (FreshPointDecoding st sv cp)
311 -- We recover by trying the new byte x as a starter of a new codepoint.
312 -- XXX need to use the same recovery in array decoding routine as well
313 step' table gst (FreshPointDecoding st statePtr codepointPtr) = do
314 r <- step (adaptState gst) st
317 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
319 0 -> Skip $ YieldAndContinue
320 (unsafeChr cp) (FreshPointDecodeInit s)
323 ++ "Invalid subsequent UTF8 byte "
327 ++ " accumulated value "
329 in Skip $ handleError msg (FreshPointDecodeInit1 s x)
330 _ -> Skip (FreshPointDecoding s sv cp)
332 Skip (FreshPointDecoding s statePtr codepointPtr)
333 Stop -> return $ Skip handleUnderflow
335 step' _ _ (YieldAndContinue c s) = return $ Yield c s
336 step' _ _ Done = return Stop
338{-# INLINE decodeUtf8D #-}
339decodeUtf8D :: Monad m => Stream m Word8 -> Stream m Char
340decodeUtf8D = decodeUtf8WithD ErrorOnCodingFailure
342{-# INLINE decodeUtf8LenientD #-}
343decodeUtf8LenientD :: Monad m => Stream m Word8 -> Stream m Char
344decodeUtf8LenientD = decodeUtf8WithD TransliterateCodingFailure
346{-# INLINE_NORMAL resumeDecodeUtf8EitherD #-}
347resumeDecodeUtf8EitherD
352 -> Stream m (Either DecodeError Char)
353resumeDecodeUtf8EitherD dst codep (Stream step state) =
354 let A.Array p _ = utf8d
355 !ptr = unsafeForeignPtrToPtr p
358 then FreshPointDecodeInit state
359 else FreshPointDecoding state dst codep
360 in Stream (step' ptr) stt
362 {-# INLINE_LATE step' #-}
363 step' _ gst (FreshPointDecodeInit st) = do
364 r <- step (adaptState gst) st
366 Yield x s -> Skip (FreshPointDecodeInit1 s x)
367 Skip s -> Skip (FreshPointDecodeInit s)
370 step' _ _ (FreshPointDecodeInit1 st x) = do
371 -- Note: It is important to use a ">" instead of a "<=" test
372 -- here for GHC to generate code layout for default branch
373 -- prediction for the common case. This is fragile and might
374 -- change with the compiler versions, we need a more reliable
375 -- "likely" primitive to control branch predication.
378 return $ Skip $ YieldAndContinue
379 (Right $ unsafeChr (fromIntegral x))
380 (FreshPointDecodeInit st)
381 -- Using a separate state here generates a jump to a
382 -- separate code block in the core which seems to perform
383 -- slightly better for the non-ascii case.
384 True -> return $ Skip $ FreshPointDecodeFirst st x
386 -- XXX should we merge it with FreshPointDecodeInit1?
387 step' table _ (FreshPointDecodeFirst st x) = do
388 let (Tuple' sv cp) = decode0 table x
392 Skip $ YieldAndContinue (Left $ DecodeError 0 (fromIntegral x))
393 (FreshPointDecodeInit st)
394 0 -> error "unreachable state"
395 _ -> Skip (FreshPointDecoding st sv cp)
397 -- We recover by trying the new byte x a starter of a new codepoint.
398 -- XXX on error need to report the next byte "x" as well.
399 -- XXX need to use the same recovery in array decoding routine as well
400 step' table gst (FreshPointDecoding st statePtr codepointPtr) = do
401 r <- step (adaptState gst) st
404 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
407 0 -> Skip $ YieldAndContinue (Right $ unsafeChr cp)
408 (FreshPointDecodeInit s)
410 Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr)
411 (FreshPointDecodeInit1 s x)
412 _ -> Skip (FreshPointDecoding s sv cp)
413 Skip s -> return $ Skip (FreshPointDecoding s statePtr codepointPtr)
414 Stop -> return $ Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr) Done
416 step' _ _ (YieldAndContinue c s) = return $ Yield c s
417 step' _ _ Done = return Stop
419{-# INLINE_NORMAL decodeUtf8EitherD #-}
420decodeUtf8EitherD :: Monad m
421 => Stream m Word8 -> Stream m (Either DecodeError Char)
422decodeUtf8EitherD = resumeDecodeUtf8EitherD 0 0
425 = OuterLoop s !(Maybe (DecodeState, CodePoint))
426 | InnerLoopDecodeInit s (ForeignPtr a) !(Ptr a) !(Ptr a)
427 | InnerLoopDecodeFirst s (ForeignPtr a) !(Ptr a) !(Ptr a) Word8
428 | InnerLoopDecoding s (ForeignPtr a) !(Ptr a) !(Ptr a)
429 !DecodeState !CodePoint
430 | YAndC !Char (FlattenState s a) -- These constructors can be
431 -- encoded in the FreshPoint
432 -- type, I prefer to keep these
433 -- flat even though that means
434 -- coming up with new names
437-- The normal decodeUtf8 above should fuse with flattenArrays
438-- to create this exact code but it doesn't for some reason, as of now this
439-- remains the fastest way I could figure out to decodeUtf8.
441-- XXX Add Proper error messages
442{-# INLINE_NORMAL decodeUtf8ArraysWithD #-}
443decodeUtf8ArraysWithD ::
446 -> Stream m (A.Array Word8)
448decodeUtf8ArraysWithD cfm (Stream step state) =
449 let A.Array p _ = utf8d
450 !ptr = unsafeForeignPtrToPtr p
451 in Stream (step' ptr) (OuterLoop state Nothing)
453 {-# INLINE transliterateOrError #-}
454 transliterateOrError e s =
456 ErrorOnCodingFailure -> error e
457 TransliterateCodingFailure -> YAndC replacementChar s
458 {-# INLINE inputUnderflow #-}
461 ErrorOnCodingFailure ->
463 "Streamly.Internal.Data.Stream.StreamD.decodeUtf8ArraysWith: Input Underflow"
464 TransliterateCodingFailure -> YAndC replacementChar D
465 {-# INLINE_LATE step' #-}
466 step' _ gst (OuterLoop st Nothing) = do
467 r <- step (adaptState gst) st
470 Yield A.Array {..} s ->
471 let p = unsafeForeignPtrToPtr aStart
472 in Skip (InnerLoopDecodeInit s aStart p aEnd)
473 Skip s -> Skip (OuterLoop s Nothing)
475 step' _ gst (OuterLoop st dst@(Just (ds, cp))) = do
476 r <- step (adaptState gst) st
479 Yield A.Array {..} s ->
480 let p = unsafeForeignPtrToPtr aStart
481 in Skip (InnerLoopDecoding s aStart p aEnd ds cp)
482 Skip s -> Skip (OuterLoop s dst)
483 Stop -> Skip inputUnderflow
484 step' _ _ (InnerLoopDecodeInit st startf p end)
486 liftIO $ touchForeignPtr startf
487 return $ Skip $ OuterLoop st Nothing
488 step' _ _ (InnerLoopDecodeInit st startf p end) = do
490 -- Note: It is important to use a ">" instead of a "<=" test here for
491 -- GHC to generate code layout for default branch prediction for the
492 -- common case. This is fragile and might change with the compiler
493 -- versions, we need a more reliable "likely" primitive to control
494 -- branch predication.
497 return $ Skip $ YAndC
498 (unsafeChr (fromIntegral x))
499 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
500 -- Using a separate state here generates a jump to a separate code
501 -- block in the core which seems to perform slightly better for the
503 True -> return $ Skip $ InnerLoopDecodeFirst st startf p end x
505 step' table _ (InnerLoopDecodeFirst st startf p end x) = do
506 let (Tuple' sv cp) = decode0 table x
512 "Streamly.Internal.Data.Stream.StreamD.decodeUtf8ArraysWith: Invalid UTF8 codepoint encountered"
513 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
514 0 -> error "unreachable state"
515 _ -> Skip (InnerLoopDecoding st startf (p `plusPtr` 1) end sv cp)
516 step' _ _ (InnerLoopDecoding st startf p end sv cp)
518 liftIO $ touchForeignPtr startf
519 return $ Skip $ OuterLoop st (Just (sv, cp))
520 step' table _ (InnerLoopDecoding st startf p end statePtr codepointPtr) = do
522 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
529 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
533 "Streamly.Internal.Data.Stream.StreamD.decodeUtf8ArraysWith: Invalid UTF8 codepoint encountered"
534 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
535 _ -> Skip (InnerLoopDecoding st startf (p `plusPtr` 1) end sv cp)
536 step' _ _ (YAndC c s) = return $ Yield c s
537 step' _ _ D = return Stop
539{-# INLINE decodeUtf8ArraysD #-}
542 => Stream m (A.Array Word8)
544decodeUtf8ArraysD = decodeUtf8ArraysWithD ErrorOnCodingFailure
546{-# INLINE decodeUtf8ArraysLenientD #-}
547decodeUtf8ArraysLenientD ::
549 => Stream m (A.Array Word8)
551decodeUtf8ArraysLenientD = decodeUtf8ArraysWithD TransliterateCodingFailure
553data EncodeState s = EncodeState s !WList
555-- More yield points improve performance, but I am not sure if they can cause
556-- too much code bloat or some trouble with fusion. So keeping only two yield
557-- points for now, one for the ascii chars (fast path) and one for all other
559{-# INLINE_NORMAL encodeUtf8D #-}
560encodeUtf8D :: Monad m => Stream m Char -> Stream m Word8
561encodeUtf8D (Stream step state) = Stream step' (EncodeState state WNil)
563 {-# INLINE_LATE step' #-}
564 step' gst (EncodeState st WNil) = do
565 r <- step (adaptState gst) st
572 Yield (fromIntegral x) (EncodeState s WNil)
573 | x <= 0x7FF -> Skip (EncodeState s (ord2 c))
577 "Streamly.Internal.Data.Stream.StreamD.encodeUtf8: Encountered a surrogate"
578 else Skip (EncodeState s (ord3 c))
579 | otherwise -> Skip (EncodeState s (ord4 c))
580 Skip s -> Skip (EncodeState s WNil)
582 step' _ (EncodeState s (WCons x xs)) = return $ Yield x (EncodeState s xs)
585-- | Decode a stream of bytes to Unicode characters by mapping each byte to a
586-- corresponding Unicode 'Char' in 0-255 range.
589{-# INLINE decodeLatin1 #-}
590decodeLatin1 :: (IsStream t, Monad m) => t m Word8 -> t m Char
591decodeLatin1 = S.map (unsafeChr . fromIntegral)
593-- | Encode a stream of Unicode characters to bytes by mapping each character
594-- to a byte in 0-255 range. Throws an error if the input stream contains
595-- characters beyond 255.
598{-# INLINE encodeLatin1 #-}
599encodeLatin1 :: (IsStream t, Monad m) => t m Char -> t m Word8
600encodeLatin1 = S.map convert
603 let codepoint = ord c
604 in if codepoint > 255
605 then error $ "Streamly.String.encodeLatin1 invalid " ++
606 "input char codepoint " ++ show codepoint
607 else fromIntegral codepoint
609-- | Like 'encodeLatin1' but silently truncates and maps input characters beyond
610-- 255 to (incorrect) chars in 0-255 range. No error or exception is thrown
611-- when such truncation occurs.
614{-# INLINE encodeLatin1Lax #-}
615encodeLatin1Lax :: (IsStream t, Monad m) => t m Char -> t m Word8
616encodeLatin1Lax = S.map (fromIntegral . ord)
618-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
619-- The incoming stream is truncated if an invalid codepoint is encountered.
622{-# INLINE decodeUtf8 #-}
623decodeUtf8 :: (Monad m, IsStream t) => t m Word8 -> t m Char
624decodeUtf8 = D.fromStreamD . decodeUtf8D . D.toStreamD
629{-# INLINE decodeUtf8Arrays #-}
630decodeUtf8Arrays :: (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
631decodeUtf8Arrays = D.fromStreamD . decodeUtf8ArraysD . D.toStreamD
633-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
634-- Any invalid codepoint encountered is replaced with the unicode replacement
638{-# INLINE decodeUtf8Lax #-}
639decodeUtf8Lax :: (Monad m, IsStream t) => t m Word8 -> t m Char
640decodeUtf8Lax = D.fromStreamD . decodeUtf8LenientD . D.toStreamD
645{-# INLINE decodeUtf8Either #-}
646decodeUtf8Either :: (Monad m, IsStream t)
647 => t m Word8 -> t m (Either DecodeError Char)
648decodeUtf8Either = D.fromStreamD . decodeUtf8EitherD . D.toStreamD
653{-# INLINE resumeDecodeUtf8Either #-}
654resumeDecodeUtf8Either
655 :: (Monad m, IsStream t)
659 -> t m (Either DecodeError Char)
660resumeDecodeUtf8Either st cp =
661 D.fromStreamD . resumeDecodeUtf8EitherD st cp . D.toStreamD
666{-# INLINE decodeUtf8ArraysLenient #-}
667decodeUtf8ArraysLenient ::
668 (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
669decodeUtf8ArraysLenient =
670 D.fromStreamD . decodeUtf8ArraysLenientD . D.toStreamD
672-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream.
675{-# INLINE encodeUtf8 #-}
676encodeUtf8 :: (Monad m, IsStream t) => t m Char -> t m Word8
677encodeUtf8 = D.fromStreamD . encodeUtf8D . D.toStreamD
679-- | See section "3.9 Unicode Encoding Forms" in
680-- https://www.unicode.org/versions/Unicode13.0.0/UnicodeStandard-13.0.pdf
682{-# INLINE_NORMAL encodeUtf8LaxD #-}
683encodeUtf8LaxD :: Monad m => Stream m Char -> Stream m Word8
684encodeUtf8LaxD (Stream step state) = Stream step' (EncodeState state WNil)
686 {-# INLINE_LATE step' #-}
687 step' gst (EncodeState st WNil) = do
688 r <- step (adaptState gst) st
694 Yield (fromIntegral x) (EncodeState s WNil)
695 | x <= 0x7FF -> Skip (EncodeState s (ord2 c))
701 (WCons 239 (WCons 191 (WCons 189 WNil)))
702 else Skip (EncodeState s (ord3 c))
703 | otherwise -> Skip (EncodeState s (ord4 c))
704 Skip s -> Skip (EncodeState s WNil)
706 step' _ (EncodeState s (WCons x xs)) = return $ Yield x (EncodeState s xs)
709-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. Any
710-- Invalid characters (U+D800-U+D8FF) in the input stream are replaced by the
711-- Unicode replacement character U+FFFD.
714{-# INLINE encodeUtf8Lax #-}
715encodeUtf8Lax :: (Monad m, IsStream t) => t m Char -> t m Word8
716encodeUtf8Lax = D.fromStreamD . encodeUtf8LaxD . D.toStreamD
719-------------------------------------------------------------------------------
720-- Utility operations on strings
721-------------------------------------------------------------------------------
723strip :: IsStream t => t m Char -> t m Char
726stripEnd :: IsStream t => t m Char -> t m Char
730-- | Remove leading whitespace from a string.
732-- > stripStart = S.dropWhile isSpace
735{-# INLINE stripStart #-}
736stripStart :: (Monad m, IsStream t) => t m Char -> t m Char
737stripStart = S.dropWhile isSpace
739-- | Fold each line of the stream using the supplied 'Fold'
740-- and stream the result.
742-- >>> S.toList $ lines FL.toList (S.fromList "lines\nthis\nstring\n\n\n")
743-- ["lines", "this", "string", "", ""]
745-- > lines = S.splitOnSuffix (== '\n')
749lines :: (Monad m, IsStream t) => Fold m Char b -> t m Char -> t m b
750lines = S.splitOnSuffix (== '\n')
752foreign import ccall unsafe "u_iswspace"
753 iswspace :: Int -> Int
755-- | Code copied from base/Data.Char to INLINE it
756{-# INLINE isSpace #-}
757isSpace :: Char -> Bool
759 | uc <= 0x377 = uc == 32 || uc - 0x9 <= 4 || uc == 0xa0
760 | otherwise = iswspace (ord c) /= 0
762 uc = fromIntegral (ord c) :: Word
764-- | Fold each word of the stream using the supplied 'Fold'
765-- and stream the result.
767-- >>> S.toList $ words FL.toList (S.fromList "fold these words")
768-- ["fold", "these", "words"]
770-- > words = S.wordsBy isSpace
774words :: (Monad m, IsStream t) => Fold m Char b -> t m Char -> t m b
775words = S.wordsBy isSpace
777-- | Unfold a stream to character streams using the supplied 'Unfold'
778-- and concat the results suffixing a newline character @\\n@ to each stream.
781{-# INLINE unlines #-}
782unlines :: (MonadIO m, IsStream t) => Unfold m a Char -> t m a -> t m Char
783unlines = S.interposeSuffix '\n'
785-- | Unfold the elements of a stream to character streams using the supplied
786-- 'Unfold' and concat the results with a whitespace character infixed between
790{-# INLINE unwords #-}
791unwords :: (MonadIO m, IsStream t) => Unfold m a Char -> t m a -> t m Char
792unwords = 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