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
14module Streamly.Internal.Unicode.Stream
16 -- * Construction (Decoding)
25 , resumeDecodeUtf8Either
30 -- * Elimination (Encoding)
38 -- * Operations on character strings
39 , strip -- (dropAround isSpace)
43 -- * StreamD UTF8 Encoding / Decoding transformations.
51 , resumeDecodeUtf8EitherD
70import Control.Monad.IO.Class (MonadIO, liftIO)
71import Data.Bits (shiftR, shiftL, (.|.), (.&.))
72import Data.Char (chr, ord)
73import Data.Word (Word8)
74import Foreign.ForeignPtr (touchForeignPtr)
75import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
76import Foreign.Storable (Storable(..))
77import GHC.Base (assert, unsafeChr)
78import GHC.ForeignPtr (ForeignPtr (..))
79import GHC.IO.Encoding.Failure (isSurrogate)
80import GHC.Ptr (Ptr (..), plusPtr)
81import System.IO.Unsafe (unsafePerformIO)
82import Streamly.Data.Fold (Fold)
83import Streamly.Data.Array.Storable.Foreign (Array)
84import Streamly.Internal.Data.Unfold (Unfold)
85import Streamly.Internal.Data.SVar (adaptState)
86import Streamly.Internal.Data.Stream.IsStream (IsStream)
87import Streamly.Internal.Data.Stream.StreamD (Stream(..), Step (..))
88import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
90import qualified Streamly.Internal.Data.Array.Storable.Foreign.Types as A
91import qualified Streamly.Internal.Data.Stream.IsStream as S
92import qualified Streamly.Internal.Data.Stream.StreamD as D
94import Prelude hiding (String, lines, words, unlines, unwords)
96-------------------------------------------------------------------------------
97-- Encoding/Decoding Unicode (UTF-8) Characters
98-------------------------------------------------------------------------------
100-- UTF-8 primitives, Lifted from GHC.IO.Encoding.UTF8.
102data WList = WCons !Word8 !WList | WNil
106ord2 c = assert (n >= 0x80 && n <= 0x07ff) (WCons x1 (WCons x2 WNil))
109 x1 = fromIntegral $ (n `shiftR` 6) + 0xC0
110 x2 = fromIntegral $ (n .&. 0x3F) + 0x80
114ord3 c = assert (n >= 0x0800 && n <= 0xffff) (WCons x1 (WCons x2 (WCons x3 WNil)))
117 x1 = fromIntegral $ (n `shiftR` 12) + 0xE0
118 x2 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80
119 x3 = fromIntegral $ (n .&. 0x3F) + 0x80
123ord4 c = assert (n >= 0x10000) (WCons x1 (WCons x2 (WCons x3 (WCons x4 WNil))))
126 x1 = fromIntegral $ (n `shiftR` 18) + 0xF0
127 x2 = fromIntegral $ ((n `shiftR` 12) .&. 0x3F) + 0x80
128 x3 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80
129 x4 = fromIntegral $ (n .&. 0x3F) + 0x80
131data CodingFailureMode
132 = TransliterateCodingFailure
133 | ErrorOnCodingFailure
134 | DropOnCodingFailure
137{-# INLINE replacementChar #-}
138replacementChar :: Char
139replacementChar = '\xFFFD'
141-- Int helps in cheaper conversion from Int to Char
143type DecodeState = Word8
145-- See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
147-- XXX Use names decodeSuccess = 0, decodeFailure = 12
149decodeTable :: [Word8]
151 -- The first part of the table maps bytes to character classes that
152 -- to reduce the size of the transition table and create bitmasks.
153 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,
154 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,
155 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,
156 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,
157 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,
158 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,
159 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,
160 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,
162 -- The second part is a transition table that maps a combination
163 -- of a state of the automaton and a character class to a state.
164 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
165 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
166 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
167 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
168 12,36,12,12,12,12,12,12,12,12,12,12
171{-# NOINLINE utf8d #-}
172utf8d :: A.Array Word8
175 -- Aligning to cacheline makes a barely noticeable difference
176 -- XXX currently alignment is not implemented for unmanaged allocation
177 $ D.runFold (A.writeNAlignedUnmanaged 64 (length decodeTable))
178 (D.fromList decodeTable)
180-- | Return element at the specified index without checking the bounds.
181-- and without touching the foreign ptr.
182{-# INLINE_NORMAL unsafePeekElemOff #-}
183unsafePeekElemOff :: forall a. Storable a => Ptr a -> Int -> a
184unsafePeekElemOff p i = let !x = A.unsafeInlineIO $ peekElemOff p i in x
186-- decode is split into two separate cases to avoid branching instructions.
187-- From the higher level flow we already know which case we are in so we can
188-- call the appropriate decode function.
190-- When the state is 0
191{-# INLINE decode0 #-}
192decode0 :: Ptr Word8 -> Word8 -> Tuple' DecodeState CodePoint
194 let !t = table `unsafePeekElemOff` fromIntegral byte
195 !codep' = (0xff `shiftR` fromIntegral t) .&. fromIntegral byte
196 !state' = table `unsafePeekElemOff` (256 + fromIntegral t)
197 in assert ((byte > 0x7f || error showByte)
198 && (state' /= 0 || error (showByte ++ showTable)))
199 (Tuple' state' codep')
204 let !(Ptr addr) = table
205 end = table `plusPtr` 364
206 in A.Array (ForeignPtr addr undefined) end :: A.Array Word8
207 showByte = "Streamly: decode0: byte: " ++ show byte
208 showTable = " table: " ++ show utf8table
210-- When the state is not 0
211{-# INLINE decode1 #-}
217 -> Tuple' DecodeState CodePoint
218decode1 table state codep byte =
219 -- Remember codep is Int type!
220 -- Can it be unsafe to convert the resulting Int to Char?
221 let !t = table `unsafePeekElemOff` fromIntegral byte
222 !codep' = (fromIntegral byte .&. 0x3f) .|. (codep `shiftL` 6)
223 !state' = table `unsafePeekElemOff`
224 (256 + fromIntegral state + fromIntegral t)
225 in assert (codep' <= 0x10FFFF
226 || error (showByte ++ showState state codep))
227 (Tuple' state' codep')
231 let !(Ptr addr) = table
232 end = table `plusPtr` 364
233 in A.Array (ForeignPtr addr undefined) end :: A.Array Word8
234 showByte = "Streamly: decode1: byte: " ++ show byte
236 " state: " ++ show st ++
237 " codepoint: " ++ show cp ++
238 " table: " ++ show utf8table
240-- We can divide the errors in three general categories:
241-- * A non-starter was encountered in a begin state
242-- * A starter was encountered without completing a codepoint
243-- * The last codepoint was not complete (input underflow)
245-- Need to separate resumable and non-resumable error. In case of non-resumable
246-- error we can also provide the failing byte. In case of resumable error the
247-- state can be opaque.
249data DecodeError = DecodeError !DecodeState !CodePoint deriving Show
252 = FreshPointDecodeInit s
253 | FreshPointDecodeInit1 s Word8
254 | FreshPointDecodeFirst s Word8
255 | FreshPointDecoding s !DecodeState !CodePoint
256 | YieldAndContinue a (FreshPoint s a)
259-- XXX write it as a parser and use parseMany to decode a stream, need to check
260-- if that preserves the same performance. Or we can use a resumable parser
261-- that parses a chunk at a time.
263-- XXX Implement this in terms of decodeUtf8Either. Need to make sure that
264-- decodeUtf8Either preserves the performance characterstics.
266{-# INLINE_NORMAL decodeUtf8WithD #-}
267decodeUtf8WithD :: Monad m
268 => CodingFailureMode -> Stream m Word8 -> Stream m Char
269decodeUtf8WithD cfm (Stream step state) =
270 let A.Array p _ = utf8d
271 !ptr = unsafeForeignPtrToPtr p
272 in Stream (step' ptr) (FreshPointDecodeInit state)
276 prefix = "Streamly.Internal.Data.Stream.StreamD.decodeUtf8With: "
278 {-# INLINE handleError #-}
281 ErrorOnCodingFailure -> error e
282 TransliterateCodingFailure -> YieldAndContinue replacementChar s
283 DropOnCodingFailure -> s
285 {-# INLINE handleUnderflow #-}
288 ErrorOnCodingFailure -> error $ prefix ++ "Not enough input"
289 TransliterateCodingFailure -> YieldAndContinue replacementChar Done
290 DropOnCodingFailure -> Done
292 {-# INLINE_LATE step' #-}
293 step' _ gst (FreshPointDecodeInit st) = do
294 r <- step (adaptState gst) st
296 Yield x s -> Skip (FreshPointDecodeInit1 s x)
297 Skip s -> Skip (FreshPointDecodeInit s)
300 step' _ _ (FreshPointDecodeInit1 st x) = do
301 -- Note: It is important to use a ">" instead of a "<=" test
302 -- here for GHC to generate code layout for default branch
303 -- prediction for the common case. This is fragile and might
304 -- change with the compiler versions, we need a more reliable
305 -- "likely" primitive to control branch predication.
308 return $ Skip $ YieldAndContinue
309 (unsafeChr (fromIntegral x))
310 (FreshPointDecodeInit st)
311 -- Using a separate state here generates a jump to a
312 -- separate code block in the core which seems to perform
313 -- slightly better for the non-ascii case.
314 True -> return $ Skip $ FreshPointDecodeFirst st x
316 -- XXX should we merge it with FreshPointDecodeInit1?
317 step' table _ (FreshPointDecodeFirst st x) = do
318 let (Tuple' sv cp) = decode0 table x
322 let msg = prefix ++ "Invalid first UTF8 byte " ++ show x
323 in Skip $ handleError msg (FreshPointDecodeInit st)
324 0 -> error "unreachable state"
325 _ -> Skip (FreshPointDecoding st sv cp)
327 -- We recover by trying the new byte x as a starter of a new codepoint.
328 -- XXX need to use the same recovery in array decoding routine as well
329 step' table gst (FreshPointDecoding st statePtr codepointPtr) = do
330 r <- step (adaptState gst) st
333 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
335 0 -> Skip $ YieldAndContinue
336 (unsafeChr cp) (FreshPointDecodeInit s)
339 ++ "Invalid subsequent UTF8 byte "
343 ++ " accumulated value "
345 in Skip $ handleError msg (FreshPointDecodeInit1 s x)
346 _ -> Skip (FreshPointDecoding s sv cp)
348 Skip (FreshPointDecoding s statePtr codepointPtr)
349 Stop -> return $ Skip handleUnderflow
351 step' _ _ (YieldAndContinue c s) = return $ Yield c s
352 step' _ _ Done = return Stop
354{-# INLINE decodeUtf8D #-}
355decodeUtf8D :: Monad m => Stream m Word8 -> Stream m Char
356decodeUtf8D = decodeUtf8WithD TransliterateCodingFailure
358{-# INLINE decodeUtf8D' #-}
359decodeUtf8D' :: Monad m => Stream m Word8 -> Stream m Char
360decodeUtf8D' = decodeUtf8WithD ErrorOnCodingFailure
362{-# INLINE decodeUtf8D_ #-}
363decodeUtf8D_ :: Monad m => Stream m Word8 -> Stream m Char
364decodeUtf8D_ = decodeUtf8WithD DropOnCodingFailure
366{-# INLINE_NORMAL resumeDecodeUtf8EitherD #-}
367resumeDecodeUtf8EitherD
372 -> Stream m (Either DecodeError Char)
373resumeDecodeUtf8EitherD dst codep (Stream step state) =
374 let A.Array p _ = utf8d
375 !ptr = unsafeForeignPtrToPtr p
378 then FreshPointDecodeInit state
379 else FreshPointDecoding state dst codep
380 in Stream (step' ptr) stt
382 {-# INLINE_LATE step' #-}
383 step' _ gst (FreshPointDecodeInit st) = do
384 r <- step (adaptState gst) st
386 Yield x s -> Skip (FreshPointDecodeInit1 s x)
387 Skip s -> Skip (FreshPointDecodeInit s)
390 step' _ _ (FreshPointDecodeInit1 st x) = do
391 -- Note: It is important to use a ">" instead of a "<=" test
392 -- here for GHC to generate code layout for default branch
393 -- prediction for the common case. This is fragile and might
394 -- change with the compiler versions, we need a more reliable
395 -- "likely" primitive to control branch predication.
398 return $ Skip $ YieldAndContinue
399 (Right $ unsafeChr (fromIntegral x))
400 (FreshPointDecodeInit st)
401 -- Using a separate state here generates a jump to a
402 -- separate code block in the core which seems to perform
403 -- slightly better for the non-ascii case.
404 True -> return $ Skip $ FreshPointDecodeFirst st x
406 -- XXX should we merge it with FreshPointDecodeInit1?
407 step' table _ (FreshPointDecodeFirst st x) = do
408 let (Tuple' sv cp) = decode0 table x
412 Skip $ YieldAndContinue (Left $ DecodeError 0 (fromIntegral x))
413 (FreshPointDecodeInit st)
414 0 -> error "unreachable state"
415 _ -> Skip (FreshPointDecoding st sv cp)
417 -- We recover by trying the new byte x a starter of a new codepoint.
418 -- XXX on error need to report the next byte "x" as well.
419 -- XXX need to use the same recovery in array decoding routine as well
420 step' table gst (FreshPointDecoding st statePtr codepointPtr) = do
421 r <- step (adaptState gst) st
424 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
427 0 -> Skip $ YieldAndContinue (Right $ unsafeChr cp)
428 (FreshPointDecodeInit s)
430 Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr)
431 (FreshPointDecodeInit1 s x)
432 _ -> Skip (FreshPointDecoding s sv cp)
433 Skip s -> return $ Skip (FreshPointDecoding s statePtr codepointPtr)
434 Stop -> return $ Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr) Done
436 step' _ _ (YieldAndContinue c s) = return $ Yield c s
437 step' _ _ Done = return Stop
439{-# INLINE_NORMAL decodeUtf8EitherD #-}
440decodeUtf8EitherD :: Monad m
441 => Stream m Word8 -> Stream m (Either DecodeError Char)
442decodeUtf8EitherD = resumeDecodeUtf8EitherD 0 0
445 = OuterLoop s !(Maybe (DecodeState, CodePoint))
446 | InnerLoopDecodeInit s (ForeignPtr a) !(Ptr a) !(Ptr a)
447 | InnerLoopDecodeFirst s (ForeignPtr a) !(Ptr a) !(Ptr a) Word8
448 | InnerLoopDecoding s (ForeignPtr a) !(Ptr a) !(Ptr a)
449 !DecodeState !CodePoint
450 | YAndC !Char (FlattenState s a) -- These constructors can be
451 -- encoded in the FreshPoint
452 -- type, I prefer to keep these
453 -- flat even though that means
454 -- coming up with new names
457-- The normal decodeUtf8 above should fuse with flattenArrays
458-- to create this exact code but it doesn't for some reason, as of now this
459-- remains the fastest way I could figure out to decodeUtf8.
461-- XXX Add Proper error messages
462{-# INLINE_NORMAL decodeUtf8ArraysWithD #-}
463decodeUtf8ArraysWithD ::
466 -> Stream m (A.Array Word8)
468decodeUtf8ArraysWithD cfm (Stream step state) =
469 let A.Array p _ = utf8d
470 !ptr = unsafeForeignPtrToPtr p
471 in Stream (step' ptr) (OuterLoop state Nothing)
473 {-# INLINE transliterateOrError #-}
474 transliterateOrError e s =
476 ErrorOnCodingFailure -> error e
477 TransliterateCodingFailure -> YAndC replacementChar s
478 DropOnCodingFailure -> s
479 {-# INLINE inputUnderflow #-}
482 ErrorOnCodingFailure ->
484 "Streamly.Internal.Data.Stream.StreamD.decodeUtf8ArraysWith: Input Underflow"
485 TransliterateCodingFailure -> YAndC replacementChar D
486 DropOnCodingFailure -> D
487 {-# INLINE_LATE step' #-}
488 step' _ gst (OuterLoop st Nothing) = do
489 r <- step (adaptState gst) st
492 Yield A.Array {..} s ->
493 let p = unsafeForeignPtrToPtr aStart
494 in Skip (InnerLoopDecodeInit s aStart p aEnd)
495 Skip s -> Skip (OuterLoop s Nothing)
497 step' _ gst (OuterLoop st dst@(Just (ds, cp))) = do
498 r <- step (adaptState gst) st
501 Yield A.Array {..} s ->
502 let p = unsafeForeignPtrToPtr aStart
503 in Skip (InnerLoopDecoding s aStart p aEnd ds cp)
504 Skip s -> Skip (OuterLoop s dst)
505 Stop -> Skip inputUnderflow
506 step' _ _ (InnerLoopDecodeInit st startf p end)
508 liftIO $ touchForeignPtr startf
509 return $ Skip $ OuterLoop st Nothing
510 step' _ _ (InnerLoopDecodeInit st startf p end) = do
512 -- Note: It is important to use a ">" instead of a "<=" test here for
513 -- GHC to generate code layout for default branch prediction for the
514 -- common case. This is fragile and might change with the compiler
515 -- versions, we need a more reliable "likely" primitive to control
516 -- branch predication.
519 return $ Skip $ YAndC
520 (unsafeChr (fromIntegral x))
521 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
522 -- Using a separate state here generates a jump to a separate code
523 -- block in the core which seems to perform slightly better for the
525 True -> return $ Skip $ InnerLoopDecodeFirst st startf p end x
527 step' table _ (InnerLoopDecodeFirst st startf p end x) = do
528 let (Tuple' sv cp) = decode0 table x
534 "Streamly.Internal.Data.Stream.StreamD.decodeUtf8ArraysWith: Invalid UTF8 codepoint encountered"
535 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
536 0 -> error "unreachable state"
537 _ -> Skip (InnerLoopDecoding st startf (p `plusPtr` 1) end sv cp)
538 step' _ _ (InnerLoopDecoding st startf p end sv cp)
540 liftIO $ touchForeignPtr startf
541 return $ Skip $ OuterLoop st (Just (sv, cp))
542 step' table _ (InnerLoopDecoding st startf p end statePtr codepointPtr) = do
544 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
551 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
555 "Streamly.Internal.Data.Stream.StreamD.decodeUtf8ArraysWith: Invalid UTF8 codepoint encountered"
556 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
557 _ -> Skip (InnerLoopDecoding st startf (p `plusPtr` 1) end sv cp)
558 step' _ _ (YAndC c s) = return $ Yield c s
559 step' _ _ D = return Stop
561{-# INLINE decodeUtf8ArraysD #-}
564 => Stream m (A.Array Word8)
566decodeUtf8ArraysD = decodeUtf8ArraysWithD TransliterateCodingFailure
568{-# INLINE decodeUtf8ArraysD' #-}
571 => Stream m (A.Array Word8)
573decodeUtf8ArraysD' = decodeUtf8ArraysWithD ErrorOnCodingFailure
575{-# INLINE decodeUtf8ArraysD_ #-}
578 => Stream m (A.Array Word8)
580decodeUtf8ArraysD_ = decodeUtf8ArraysWithD DropOnCodingFailure
582data EncodeState s = EncodeState s !WList
584-- More yield points improve performance, but I am not sure if they can cause
585-- too much code bloat or some trouble with fusion. So keeping only two yield
586-- points for now, one for the ascii chars (fast path) and one for all other
588{-# INLINE_NORMAL encodeUtf8D' #-}
589encodeUtf8D' :: Monad m => Stream m Char -> Stream m Word8
590encodeUtf8D' (Stream step state) = Stream step' (EncodeState state WNil)
592 {-# INLINE_LATE step' #-}
593 step' gst (EncodeState st WNil) = do
594 r <- step (adaptState gst) st
601 Yield (fromIntegral x) (EncodeState s WNil)
602 | x <= 0x7FF -> Skip (EncodeState s (ord2 c))
606 "Streamly.Internal.Data.Stream.StreamD.encodeUtf8: Encountered a surrogate"
607 else Skip (EncodeState s (ord3 c))
608 | otherwise -> Skip (EncodeState s (ord4 c))
609 Skip s -> Skip (EncodeState s WNil)
611 step' _ (EncodeState s (WCons x xs)) = return $ Yield x (EncodeState s xs)
614-- | Decode a stream of bytes to Unicode characters by mapping each byte to a
615-- corresponding Unicode 'Char' in 0-255 range.
617-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
620{-# INLINE decodeLatin1 #-}
621decodeLatin1 :: (IsStream t, Monad m) => t m Word8 -> t m Char
622decodeLatin1 = S.map (unsafeChr . fromIntegral)
624-- | Encode a stream of Unicode characters to bytes by mapping each character
625-- to a byte in 0-255 range. Throws an error if the input stream contains
626-- characters beyond 255.
629{-# INLINE encodeLatin1' #-}
630encodeLatin1' :: (IsStream t, Monad m) => t m Char -> t m Word8
631encodeLatin1' = S.map convert
634 let codepoint = ord c
635 in if codepoint > 255
636 then error $ "Streamly.String.encodeLatin1 invalid " ++
637 "input char codepoint " ++ show codepoint
638 else fromIntegral codepoint
640-- | Like 'encodeLatin1'' but silently truncates and maps input characters beyond
641-- 255 to (incorrect) chars in 0-255 range. No error or exception is thrown
642-- when such truncation occurs.
644-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
646-- /Since: 0.8.0 (Lenient Behaviour)/
647{-# INLINE encodeLatin1 #-}
648encodeLatin1 :: (IsStream t, Monad m) => t m Char -> t m Word8
649encodeLatin1 = S.map (fromIntegral . ord)
651-- | Like 'encodeLatin1' but drops the input characters beyond 255.
654{-# INLINE encodeLatin1_ #-}
655encodeLatin1_ :: (IsStream t, Monad m) => t m Char -> t m Word8
656encodeLatin1_ = S.map (fromIntegral . ord) . S.filter (<= chr (255))
658-- | Same as 'encodeLatin1'
660{-# DEPRECATED encodeLatin1Lax "Please use 'encodeLatin1' instead" #-}
661{-# INLINE encodeLatin1Lax #-}
662encodeLatin1Lax :: (IsStream t, Monad m) => t m Char -> t m Word8
663encodeLatin1Lax = encodeLatin1
665-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
666-- The function throws an error if an invalid codepoint is encountered.
669{-# INLINE decodeUtf8' #-}
670decodeUtf8' :: (Monad m, IsStream t) => t m Word8 -> t m Char
671decodeUtf8' = D.fromStreamD . decodeUtf8D' . D.toStreamD
676{-# INLINE decodeUtf8Arrays' #-}
677decodeUtf8Arrays' :: (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
678decodeUtf8Arrays' = D.fromStreamD . decodeUtf8ArraysD' . D.toStreamD
680-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
681-- Any invalid codepoint encountered is replaced with the unicode replacement
684-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
686-- /Since: 0.8.0 (Lenient Behaviour)/
687{-# INLINE decodeUtf8 #-}
688decodeUtf8 :: (Monad m, IsStream t) => t m Word8 -> t m Char
689decodeUtf8 = D.fromStreamD . decodeUtf8D . D.toStreamD
691-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
692-- Any invalid codepoint encountered is dropped.
695{-# INLINE decodeUtf8_ #-}
696decodeUtf8_ :: (Monad m, IsStream t) => t m Word8 -> t m Char
697decodeUtf8_ = D.fromStreamD . decodeUtf8D_ . D.toStreamD
699-- | Same as 'decodeUtf8'
701{-# DEPRECATED decodeUtf8Lax "Please use 'decodeUtf8' instead" #-}
702{-# INLINE decodeUtf8Lax #-}
703decodeUtf8Lax :: (IsStream t, Monad m) => t m Word8 -> t m Char
704decodeUtf8Lax = decodeUtf8
709{-# INLINE decodeUtf8Either #-}
710decodeUtf8Either :: (Monad m, IsStream t)
711 => t m Word8 -> t m (Either DecodeError Char)
712decodeUtf8Either = D.fromStreamD . decodeUtf8EitherD . D.toStreamD
717{-# INLINE resumeDecodeUtf8Either #-}
718resumeDecodeUtf8Either
719 :: (Monad m, IsStream t)
723 -> t m (Either DecodeError Char)
724resumeDecodeUtf8Either st cp =
725 D.fromStreamD . resumeDecodeUtf8EitherD st cp . D.toStreamD
730{-# INLINE decodeUtf8Arrays #-}
732 (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
734 D.fromStreamD . decodeUtf8ArraysD . D.toStreamD
739{-# INLINE decodeUtf8Arrays_ #-}
741 (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
743 D.fromStreamD . decodeUtf8ArraysD_ . D.toStreamD
745-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. When
746-- any invalid character (U+D800-U+D8FF) is encountered in the input stream the
747-- function errors out.
750{-# INLINE encodeUtf8' #-}
751encodeUtf8' :: (Monad m, IsStream t) => t m Char -> t m Word8
752encodeUtf8' = D.fromStreamD . encodeUtf8D' . D.toStreamD
754-- | See section "3.9 Unicode Encoding Forms" in
755-- https://www.unicode.org/versions/Unicode13.0.0/UnicodeStandard-13.0.pdf
757{-# INLINE_NORMAL encodeUtf8D #-}
758encodeUtf8D :: Monad m => Stream m Char -> Stream m Word8
759encodeUtf8D (Stream step state) = Stream step' (EncodeState state WNil)
761 {-# INLINE_LATE step' #-}
762 step' gst (EncodeState st WNil) = do
763 r <- step (adaptState gst) st
769 Yield (fromIntegral x) (EncodeState s WNil)
770 | x <= 0x7FF -> Skip (EncodeState s (ord2 c))
776 (WCons 239 (WCons 191 (WCons 189 WNil)))
777 else Skip (EncodeState s (ord3 c))
778 | otherwise -> Skip (EncodeState s (ord4 c))
779 Skip s -> Skip (EncodeState s WNil)
781 step' _ (EncodeState s (WCons x xs)) = return $ Yield x (EncodeState s xs)
784-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. Any
785-- Invalid characters (U+D800-U+D8FF) in the input stream are replaced by the
786-- Unicode replacement character U+FFFD.
788-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
790-- /Since: 0.8.0 (Lenient Behaviour)/
791{-# INLINE encodeUtf8 #-}
792encodeUtf8 :: (Monad m, IsStream t) => t m Char -> t m Word8
793encodeUtf8 = D.fromStreamD . encodeUtf8D . D.toStreamD
795{-# INLINE_NORMAL encodeUtf8D_ #-}
796encodeUtf8D_ :: Monad m => Stream m Char -> Stream m Word8
797encodeUtf8D_ (Stream step state) = Stream step' (EncodeState state WNil)
799 {-# INLINE_LATE step' #-}
800 step' gst (EncodeState st WNil) = do
801 r <- step (adaptState gst) st
807 Yield (fromIntegral x) (EncodeState s WNil)
808 | x <= 0x7FF -> Skip (EncodeState s (ord2 c))
813 else Skip (EncodeState s (ord3 c))
814 | otherwise -> Skip (EncodeState s (ord4 c))
815 Skip s -> Skip (EncodeState s WNil)
817 step' _ (EncodeState s (WCons x xs)) = return $ Yield x (EncodeState s xs)
819-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. Any
820-- Invalid characters (U+D800-U+D8FF) in the input stream are dropped.
823{-# INLINE encodeUtf8_ #-}
824encodeUtf8_ :: (Monad m, IsStream t) => t m Char -> t m Word8
825encodeUtf8_ = D.fromStreamD . encodeUtf8D_ . D.toStreamD
827-- | Same as 'encodeUtf8'
829{-# DEPRECATED encodeUtf8Lax "Please use 'encodeUtf8' instead" #-}
830{-# INLINE encodeUtf8Lax #-}
831encodeUtf8Lax :: (IsStream t, Monad m) => t m Char -> t m Word8
832encodeUtf8Lax = encodeUtf8
835-------------------------------------------------------------------------------
836-- Utility operations on strings
837-------------------------------------------------------------------------------
839strip :: IsStream t => t m Char -> t m Char
842stripEnd :: IsStream t => t m Char -> t m Char
846-- | Remove leading whitespace from a string.
848-- > stripStart = S.dropWhile isSpace
851{-# INLINE stripStart #-}
852stripStart :: (Monad m, IsStream t) => t m Char -> t m Char
853stripStart = S.dropWhile isSpace
855-- | Fold each line of the stream using the supplied 'Fold'
856-- and stream the result.
858-- >>> S.toList $ lines FL.toList (S.fromList "lines\nthis\nstring\n\n\n")
859-- ["lines", "this", "string", "", ""]
861-- > lines = S.splitOnSuffix (== '\n')
865lines :: (Monad m, IsStream t) => Fold m Char b -> t m Char -> t m b
866lines = S.splitOnSuffix (== '\n')
868foreign import ccall unsafe "u_iswspace"
869 iswspace :: Int -> Int
871-- | Code copied from base/Data.Char to INLINE it
872{-# INLINE isSpace #-}
873isSpace :: Char -> Bool
875 | uc <= 0x377 = uc == 32 || uc - 0x9 <= 4 || uc == 0xa0
876 | otherwise = iswspace (ord c) /= 0
878 uc = fromIntegral (ord c) :: Word
880-- | Fold each word of the stream using the supplied 'Fold'
881-- and stream the result.
883-- >>> S.toList $ words FL.toList (S.fromList "fold these words")
884-- ["fold", "these", "words"]
886-- > words = S.wordsBy isSpace
890words :: (Monad m, IsStream t) => Fold m Char b -> t m Char -> t m b
891words = S.wordsBy isSpace
893-- | Unfold a stream to character streams using the supplied 'Unfold'
894-- and concat the results suffixing a newline character @\\n@ to each stream.
897{-# INLINE unlines #-}
898unlines :: (MonadIO m, IsStream t) => Unfold m a Char -> t m a -> t m Char
899unlines = S.interposeSuffix '\n'
901-- | Unfold the elements of a stream to character streams using the supplied
902-- 'Unfold' and concat the results with a whitespace character infixed between
906{-# INLINE unwords #-}
907unwords :: (MonadIO m, IsStream t) => Unfold m a Char -> t m a -> t m Char
908unwords = 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