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 Fusion.Plugin.Types (Fuse(..))
78import GHC.Base (assert, unsafeChr)
79import GHC.ForeignPtr (ForeignPtr (..))
80import GHC.IO.Encoding.Failure (isSurrogate)
81import GHC.Ptr (Ptr (..), plusPtr)
82import System.IO.Unsafe (unsafePerformIO)
83import Streamly.Data.Fold (Fold)
84import Streamly.Data.Array.Foreign (Array)
85import Streamly.Internal.Data.Unfold (Unfold)
86import Streamly.Internal.Data.SVar (adaptState)
87import Streamly.Internal.Data.Stream.IsStream (IsStream)
88import Streamly.Internal.Data.Stream.StreamD (Stream(..), Step (..))
89import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
91import qualified Streamly.Internal.Data.Array.Foreign.Type as A
92import qualified Streamly.Internal.Data.Stream.IsStream as S
93import qualified Streamly.Internal.Data.Stream.StreamD as D
95import Prelude hiding (String, lines, words, unlines, unwords)
99-- >>> import Prelude hiding (String, lines, words, unlines, unwords)
100-- >>> import qualified Streamly.Prelude as Stream
101-- >>> import qualified Streamly.Data.Fold as Fold
102-- >>> import Streamly.Internal.Unicode.Stream
104-------------------------------------------------------------------------------
105-- Encoding/Decoding Unicode (UTF-8) Characters
106-------------------------------------------------------------------------------
108-- UTF-8 primitives, Lifted from GHC.IO.Encoding.UTF8.
110data WList = WCons !Word8 !WList | WNil
114ord2 c = assert (n >= 0x80 && n <= 0x07ff) (WCons x1 (WCons x2 WNil))
117 x1 = fromIntegral $ (n `shiftR` 6) + 0xC0
118 x2 = fromIntegral $ (n .&. 0x3F) + 0x80
122ord3 c = assert (n >= 0x0800 && n <= 0xffff) (WCons x1 (WCons x2 (WCons x3 WNil)))
125 x1 = fromIntegral $ (n `shiftR` 12) + 0xE0
126 x2 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80
127 x3 = fromIntegral $ (n .&. 0x3F) + 0x80
131ord4 c = assert (n >= 0x10000) (WCons x1 (WCons x2 (WCons x3 (WCons x4 WNil))))
134 x1 = fromIntegral $ (n `shiftR` 18) + 0xF0
135 x2 = fromIntegral $ ((n `shiftR` 12) .&. 0x3F) + 0x80
136 x3 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80
137 x4 = fromIntegral $ (n .&. 0x3F) + 0x80
139data CodingFailureMode
140 = TransliterateCodingFailure
141 | ErrorOnCodingFailure
142 | DropOnCodingFailure
145{-# INLINE replacementChar #-}
146replacementChar :: Char
147replacementChar = '\xFFFD'
149-- Int helps in cheaper conversion from Int to Char
151type DecodeState = Word8
153-- See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
155-- XXX Use names decodeSuccess = 0, decodeFailure = 12
157decodeTable :: [Word8]
159 -- The first part of the table maps bytes to character classes that
160 -- to reduce the size of the transition table and create bitmasks.
161 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,
162 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,
163 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,
164 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,
165 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,
166 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,
167 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,
168 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,
170 -- The second part is a transition table that maps a combination
171 -- of a state of the automaton and a character class to a state.
172 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
173 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
174 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
175 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
176 12,36,12,12,12,12,12,12,12,12,12,12
179{-# NOINLINE utf8d #-}
180utf8d :: A.Array Word8
183 -- Aligning to cacheline makes a barely noticeable difference
184 -- XXX currently alignment is not implemented for unmanaged allocation
185 $ D.fold (A.writeNAlignedUnmanaged 64 (length decodeTable))
186 (D.fromList decodeTable)
188-- | Return element at the specified index without checking the bounds.
189-- and without touching the foreign ptr.
190{-# INLINE_NORMAL unsafePeekElemOff #-}
191unsafePeekElemOff :: forall a. Storable a => Ptr a -> Int -> a
192unsafePeekElemOff p i = let !x = A.unsafeInlineIO $ peekElemOff p i in x
194-- decode is split into two separate cases to avoid branching instructions.
195-- From the higher level flow we already know which case we are in so we can
196-- call the appropriate decode function.
198-- When the state is 0
199{-# INLINE decode0 #-}
200decode0 :: Ptr Word8 -> Word8 -> Tuple' DecodeState CodePoint
202 let !t = table `unsafePeekElemOff` fromIntegral byte
203 !codep' = (0xff `shiftR` fromIntegral t) .&. fromIntegral byte
204 !state' = table `unsafePeekElemOff` (256 + fromIntegral t)
205 in assert ((byte > 0x7f || error showByte)
206 && (state' /= 0 || error (showByte ++ showTable)))
207 (Tuple' state' codep')
212 let !(Ptr addr) = table
213 end = table `plusPtr` 364
214 in A.Array (ForeignPtr addr undefined) end :: A.Array Word8
215 showByte = "Streamly: decode0: byte: " ++ show byte
216 showTable = " table: " ++ show utf8table
218-- When the state is not 0
219{-# INLINE decode1 #-}
225 -> Tuple' DecodeState CodePoint
226decode1 table state codep byte =
227 -- Remember codep is Int type!
228 -- Can it be unsafe to convert the resulting Int to Char?
229 let !t = table `unsafePeekElemOff` fromIntegral byte
230 !codep' = (fromIntegral byte .&. 0x3f) .|. (codep `shiftL` 6)
231 !state' = table `unsafePeekElemOff`
232 (256 + fromIntegral state + fromIntegral t)
233 in assert (codep' <= 0x10FFFF
234 || error (showByte ++ showState state codep))
235 (Tuple' state' codep')
239 let !(Ptr addr) = table
240 end = table `plusPtr` 364
241 in A.Array (ForeignPtr addr undefined) end :: A.Array Word8
242 showByte = "Streamly: decode1: byte: " ++ show byte
244 " state: " ++ show st ++
245 " codepoint: " ++ show cp ++
246 " table: " ++ show utf8table
248-- We can divide the errors in three general categories:
249-- * A non-starter was encountered in a begin state
250-- * A starter was encountered without completing a codepoint
251-- * The last codepoint was not complete (input underflow)
253-- Need to separate resumable and non-resumable error. In case of non-resumable
254-- error we can also provide the failing byte. In case of resumable error the
255-- state can be opaque.
257data DecodeError = DecodeError !DecodeState !CodePoint deriving Show
259{-# ANN type FreshPoint Fuse #-}
261 = FreshPointDecodeInit s
262 | FreshPointDecodeInit1 s Word8
263 | FreshPointDecodeFirst s Word8
264 | FreshPointDecoding s !DecodeState !CodePoint
265 | YieldAndContinue a (FreshPoint s a)
268-- XXX write it as a parser and use parseMany to decode a stream, need to check
269-- if that preserves the same performance. Or we can use a resumable parser
270-- that parses a chunk at a time.
272-- XXX Implement this in terms of decodeUtf8Either. Need to make sure that
273-- decodeUtf8Either preserves the performance characterstics.
275{-# INLINE_NORMAL decodeUtf8WithD #-}
276decodeUtf8WithD :: Monad m
277 => CodingFailureMode -> Stream m Word8 -> Stream m Char
278decodeUtf8WithD cfm (Stream step state) =
279 let A.Array p _ = utf8d
280 !ptr = unsafeForeignPtrToPtr p
281 in Stream (step' ptr) (FreshPointDecodeInit state)
285 prefix = "Streamly.Internal.Data.Stream.StreamD.decodeUtf8With: "
287 {-# INLINE handleError #-}
290 ErrorOnCodingFailure -> error e
291 TransliterateCodingFailure -> YieldAndContinue replacementChar s
292 DropOnCodingFailure -> s
294 {-# INLINE handleUnderflow #-}
297 ErrorOnCodingFailure -> error $ prefix ++ "Not enough input"
298 TransliterateCodingFailure -> YieldAndContinue replacementChar Done
299 DropOnCodingFailure -> Done
301 {-# INLINE_LATE step' #-}
302 step' _ gst (FreshPointDecodeInit st) = do
303 r <- step (adaptState gst) st
305 Yield x s -> Skip (FreshPointDecodeInit1 s x)
306 Skip s -> Skip (FreshPointDecodeInit s)
309 step' _ _ (FreshPointDecodeInit1 st x) = do
310 -- Note: It is important to use a ">" instead of a "<=" test
311 -- here for GHC to generate code layout for default branch
312 -- prediction for the common case. This is fragile and might
313 -- change with the compiler versions, we need a more reliable
314 -- "likely" primitive to control branch predication.
317 return $ Skip $ YieldAndContinue
318 (unsafeChr (fromIntegral x))
319 (FreshPointDecodeInit st)
320 -- Using a separate state here generates a jump to a
321 -- separate code block in the core which seems to perform
322 -- slightly better for the non-ascii case.
323 True -> return $ Skip $ FreshPointDecodeFirst st x
325 -- XXX should we merge it with FreshPointDecodeInit1?
326 step' table _ (FreshPointDecodeFirst st x) = do
327 let (Tuple' sv cp) = decode0 table x
331 let msg = prefix ++ "Invalid first UTF8 byte " ++ show x
332 in Skip $ handleError msg (FreshPointDecodeInit st)
333 0 -> error "unreachable state"
334 _ -> Skip (FreshPointDecoding st sv cp)
336 -- We recover by trying the new byte x as a starter of a new codepoint.
337 -- XXX need to use the same recovery in array decoding routine as well
338 step' table gst (FreshPointDecoding st statePtr codepointPtr) = do
339 r <- step (adaptState gst) st
342 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
344 0 -> Skip $ YieldAndContinue
345 (unsafeChr cp) (FreshPointDecodeInit s)
348 ++ "Invalid subsequent UTF8 byte "
352 ++ " accumulated value "
354 in Skip $ handleError msg (FreshPointDecodeInit1 s x)
355 _ -> Skip (FreshPointDecoding s sv cp)
357 Skip (FreshPointDecoding s statePtr codepointPtr)
358 Stop -> return $ Skip handleUnderflow
360 step' _ _ (YieldAndContinue c s) = return $ Yield c s
361 step' _ _ Done = return Stop
363{-# INLINE decodeUtf8D #-}
364decodeUtf8D :: Monad m => Stream m Word8 -> Stream m Char
365decodeUtf8D = decodeUtf8WithD TransliterateCodingFailure
367{-# INLINE decodeUtf8D' #-}
368decodeUtf8D' :: Monad m => Stream m Word8 -> Stream m Char
369decodeUtf8D' = decodeUtf8WithD ErrorOnCodingFailure
371{-# INLINE decodeUtf8D_ #-}
372decodeUtf8D_ :: Monad m => Stream m Word8 -> Stream m Char
373decodeUtf8D_ = decodeUtf8WithD DropOnCodingFailure
375{-# INLINE_NORMAL resumeDecodeUtf8EitherD #-}
376resumeDecodeUtf8EitherD
381 -> Stream m (Either DecodeError Char)
382resumeDecodeUtf8EitherD dst codep (Stream step state) =
383 let A.Array p _ = utf8d
384 !ptr = unsafeForeignPtrToPtr p
387 then FreshPointDecodeInit state
388 else FreshPointDecoding state dst codep
389 in Stream (step' ptr) stt
391 {-# INLINE_LATE step' #-}
392 step' _ gst (FreshPointDecodeInit st) = do
393 r <- step (adaptState gst) st
395 Yield x s -> Skip (FreshPointDecodeInit1 s x)
396 Skip s -> Skip (FreshPointDecodeInit s)
399 step' _ _ (FreshPointDecodeInit1 st x) = do
400 -- Note: It is important to use a ">" instead of a "<=" test
401 -- here for GHC to generate code layout for default branch
402 -- prediction for the common case. This is fragile and might
403 -- change with the compiler versions, we need a more reliable
404 -- "likely" primitive to control branch predication.
407 return $ Skip $ YieldAndContinue
408 (Right $ unsafeChr (fromIntegral x))
409 (FreshPointDecodeInit st)
410 -- Using a separate state here generates a jump to a
411 -- separate code block in the core which seems to perform
412 -- slightly better for the non-ascii case.
413 True -> return $ Skip $ FreshPointDecodeFirst st x
415 -- XXX should we merge it with FreshPointDecodeInit1?
416 step' table _ (FreshPointDecodeFirst st x) = do
417 let (Tuple' sv cp) = decode0 table x
421 Skip $ YieldAndContinue (Left $ DecodeError 0 (fromIntegral x))
422 (FreshPointDecodeInit st)
423 0 -> error "unreachable state"
424 _ -> Skip (FreshPointDecoding st sv cp)
426 -- We recover by trying the new byte x a starter of a new codepoint.
427 -- XXX on error need to report the next byte "x" as well.
428 -- XXX need to use the same recovery in array decoding routine as well
429 step' table gst (FreshPointDecoding st statePtr codepointPtr) = do
430 r <- step (adaptState gst) st
433 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
436 0 -> Skip $ YieldAndContinue (Right $ unsafeChr cp)
437 (FreshPointDecodeInit s)
439 Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr)
440 (FreshPointDecodeInit1 s x)
441 _ -> Skip (FreshPointDecoding s sv cp)
442 Skip s -> return $ Skip (FreshPointDecoding s statePtr codepointPtr)
443 Stop -> return $ Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr) Done
445 step' _ _ (YieldAndContinue c s) = return $ Yield c s
446 step' _ _ Done = return Stop
448{-# INLINE_NORMAL decodeUtf8EitherD #-}
449decodeUtf8EitherD :: Monad m
450 => Stream m Word8 -> Stream m (Either DecodeError Char)
451decodeUtf8EitherD = resumeDecodeUtf8EitherD 0 0
453{-# ANN type FlattenState Fuse #-}
455 = OuterLoop s !(Maybe (DecodeState, CodePoint))
456 | InnerLoopDecodeInit s (ForeignPtr a) !(Ptr a) !(Ptr a)
457 | InnerLoopDecodeFirst s (ForeignPtr a) !(Ptr a) !(Ptr a) Word8
458 | InnerLoopDecoding s (ForeignPtr a) !(Ptr a) !(Ptr a)
459 !DecodeState !CodePoint
460 | YAndC !Char (FlattenState s a) -- These constructors can be
461 -- encoded in the FreshPoint
462 -- type, I prefer to keep these
463 -- flat even though that means
464 -- coming up with new names
467-- The normal decodeUtf8 above should fuse with flattenArrays
468-- to create this exact code but it doesn't for some reason, as of now this
469-- remains the fastest way I could figure out to decodeUtf8.
471-- XXX Add Proper error messages
472{-# INLINE_NORMAL decodeUtf8ArraysWithD #-}
473decodeUtf8ArraysWithD ::
476 -> Stream m (A.Array Word8)
478decodeUtf8ArraysWithD cfm (Stream step state) =
479 let A.Array p _ = utf8d
480 !ptr = unsafeForeignPtrToPtr p
481 in Stream (step' ptr) (OuterLoop state Nothing)
483 {-# INLINE transliterateOrError #-}
484 transliterateOrError e s =
486 ErrorOnCodingFailure -> error e
487 TransliterateCodingFailure -> YAndC replacementChar s
488 DropOnCodingFailure -> s
489 {-# INLINE inputUnderflow #-}
492 ErrorOnCodingFailure ->
494 "Streamly.Internal.Data.Stream.StreamD.decodeUtf8ArraysWith: Input Underflow"
495 TransliterateCodingFailure -> YAndC replacementChar D
496 DropOnCodingFailure -> D
497 {-# INLINE_LATE step' #-}
498 step' _ gst (OuterLoop st Nothing) = do
499 r <- step (adaptState gst) st
502 Yield A.Array {..} s ->
503 let p = unsafeForeignPtrToPtr aStart
504 in Skip (InnerLoopDecodeInit s aStart p aEnd)
505 Skip s -> Skip (OuterLoop s Nothing)
507 step' _ gst (OuterLoop st dst@(Just (ds, cp))) = do
508 r <- step (adaptState gst) st
511 Yield A.Array {..} s ->
512 let p = unsafeForeignPtrToPtr aStart
513 in Skip (InnerLoopDecoding s aStart p aEnd ds cp)
514 Skip s -> Skip (OuterLoop s dst)
515 Stop -> Skip inputUnderflow
516 step' _ _ (InnerLoopDecodeInit st startf p end)
518 liftIO $ touchForeignPtr startf
519 return $ Skip $ OuterLoop st Nothing
520 step' _ _ (InnerLoopDecodeInit st startf p end) = do
522 -- Note: It is important to use a ">" instead of a "<=" test here for
523 -- GHC to generate code layout for default branch prediction for the
524 -- common case. This is fragile and might change with the compiler
525 -- versions, we need a more reliable "likely" primitive to control
526 -- branch predication.
529 return $ Skip $ YAndC
530 (unsafeChr (fromIntegral x))
531 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
532 -- Using a separate state here generates a jump to a separate code
533 -- block in the core which seems to perform slightly better for the
535 True -> return $ Skip $ InnerLoopDecodeFirst st startf p end x
537 step' table _ (InnerLoopDecodeFirst st startf p end x) = do
538 let (Tuple' sv cp) = decode0 table x
544 "Streamly.Internal.Data.Stream.StreamD.decodeUtf8ArraysWith: Invalid UTF8 codepoint encountered"
545 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
546 0 -> error "unreachable state"
547 _ -> Skip (InnerLoopDecoding st startf (p `plusPtr` 1) end sv cp)
548 step' _ _ (InnerLoopDecoding st startf p end sv cp)
550 liftIO $ touchForeignPtr startf
551 return $ Skip $ OuterLoop st (Just (sv, cp))
552 step' table _ (InnerLoopDecoding st startf p end statePtr codepointPtr) = do
554 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
561 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
565 "Streamly.Internal.Data.Stream.StreamD.decodeUtf8ArraysWith: Invalid UTF8 codepoint encountered"
566 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
567 _ -> Skip (InnerLoopDecoding st startf (p `plusPtr` 1) end sv cp)
568 step' _ _ (YAndC c s) = return $ Yield c s
569 step' _ _ D = return Stop
571{-# INLINE decodeUtf8ArraysD #-}
574 => Stream m (A.Array Word8)
576decodeUtf8ArraysD = decodeUtf8ArraysWithD TransliterateCodingFailure
578{-# INLINE decodeUtf8ArraysD' #-}
581 => Stream m (A.Array Word8)
583decodeUtf8ArraysD' = decodeUtf8ArraysWithD ErrorOnCodingFailure
585{-# INLINE decodeUtf8ArraysD_ #-}
588 => Stream m (A.Array Word8)
590decodeUtf8ArraysD_ = decodeUtf8ArraysWithD DropOnCodingFailure
592{-# ANN type EncodeState Fuse #-}
593data EncodeState s = EncodeState s !WList
595-- More yield points improve performance, but I am not sure if they can cause
596-- too much code bloat or some trouble with fusion. So keeping only two yield
597-- points for now, one for the ascii chars (fast path) and one for all other
599{-# INLINE_NORMAL encodeUtf8D' #-}
600encodeUtf8D' :: Monad m => Stream m Char -> Stream m Word8
601encodeUtf8D' (Stream step state) = Stream step' (EncodeState state WNil)
603 {-# INLINE_LATE step' #-}
604 step' gst (EncodeState st WNil) = do
605 r <- step (adaptState gst) st
612 Yield (fromIntegral x) (EncodeState s WNil)
613 | x <= 0x7FF -> Skip (EncodeState s (ord2 c))
617 "Streamly.Internal.Data.Stream.StreamD.encodeUtf8: Encountered a surrogate"
618 else Skip (EncodeState s (ord3 c))
619 | otherwise -> Skip (EncodeState s (ord4 c))
620 Skip s -> Skip (EncodeState s WNil)
622 step' _ (EncodeState s (WCons x xs)) = return $ Yield x (EncodeState s xs)
625-- | Decode a stream of bytes to Unicode characters by mapping each byte to a
626-- corresponding Unicode 'Char' in 0-255 range.
628-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
631{-# INLINE decodeLatin1 #-}
632decodeLatin1 :: (IsStream t, Monad m) => t m Word8 -> t m Char
633decodeLatin1 = S.map (unsafeChr . fromIntegral)
635-- | Encode a stream of Unicode characters to bytes by mapping each character
636-- to a byte in 0-255 range. Throws an error if the input stream contains
637-- characters beyond 255.
640{-# INLINE encodeLatin1' #-}
641encodeLatin1' :: (IsStream t, Monad m) => t m Char -> t m Word8
642encodeLatin1' = S.map convert
645 let codepoint = ord c
646 in if codepoint > 255
647 then error $ "Streamly.String.encodeLatin1 invalid " ++
648 "input char codepoint " ++ show codepoint
649 else fromIntegral codepoint
651-- | Like 'encodeLatin1'' but silently truncates and maps input characters beyond
652-- 255 to (incorrect) chars in 0-255 range. No error or exception is thrown
653-- when such truncation occurs.
655-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
657-- /Since: 0.8.0 (Lenient Behaviour)/
658{-# INLINE encodeLatin1 #-}
659encodeLatin1 :: (IsStream t, Monad m) => t m Char -> t m Word8
660encodeLatin1 = S.map (fromIntegral . ord)
662-- | Like 'encodeLatin1' but drops the input characters beyond 255.
665{-# INLINE encodeLatin1_ #-}
666encodeLatin1_ :: (IsStream t, Monad m) => t m Char -> t m Word8
667encodeLatin1_ = S.map (fromIntegral . ord) . S.filter (<= chr (255))
669-- | Same as 'encodeLatin1'
671{-# DEPRECATED encodeLatin1Lax "Please use 'encodeLatin1' instead" #-}
672{-# INLINE encodeLatin1Lax #-}
673encodeLatin1Lax :: (IsStream t, Monad m) => t m Char -> t m Word8
674encodeLatin1Lax = encodeLatin1
676-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
677-- The function throws an error if an invalid codepoint is encountered.
680{-# INLINE decodeUtf8' #-}
681decodeUtf8' :: (Monad m, IsStream t) => t m Word8 -> t m Char
682decodeUtf8' = D.fromStreamD . decodeUtf8D' . D.toStreamD
687{-# INLINE decodeUtf8Arrays' #-}
688decodeUtf8Arrays' :: (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
689decodeUtf8Arrays' = D.fromStreamD . decodeUtf8ArraysD' . D.toStreamD
691-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
692-- Any invalid codepoint encountered is replaced with the unicode replacement
695-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
697-- /Since: 0.8.0 (Lenient Behaviour)/
698{-# INLINE decodeUtf8 #-}
699decodeUtf8 :: (Monad m, IsStream t) => t m Word8 -> t m Char
700decodeUtf8 = D.fromStreamD . decodeUtf8D . D.toStreamD
702-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
703-- Any invalid codepoint encountered is dropped.
706{-# INLINE decodeUtf8_ #-}
707decodeUtf8_ :: (Monad m, IsStream t) => t m Word8 -> t m Char
708decodeUtf8_ = D.fromStreamD . decodeUtf8D_ . D.toStreamD
710-- | Same as 'decodeUtf8'
712{-# DEPRECATED decodeUtf8Lax "Please use 'decodeUtf8' instead" #-}
713{-# INLINE decodeUtf8Lax #-}
714decodeUtf8Lax :: (IsStream t, Monad m) => t m Word8 -> t m Char
715decodeUtf8Lax = decodeUtf8
720{-# INLINE decodeUtf8Either #-}
721decodeUtf8Either :: (Monad m, IsStream t)
722 => t m Word8 -> t m (Either DecodeError Char)
723decodeUtf8Either = D.fromStreamD . decodeUtf8EitherD . D.toStreamD
728{-# INLINE resumeDecodeUtf8Either #-}
729resumeDecodeUtf8Either
730 :: (Monad m, IsStream t)
734 -> t m (Either DecodeError Char)
735resumeDecodeUtf8Either st cp =
736 D.fromStreamD . resumeDecodeUtf8EitherD st cp . D.toStreamD
741{-# INLINE decodeUtf8Arrays #-}
743 (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
745 D.fromStreamD . decodeUtf8ArraysD . D.toStreamD
750{-# INLINE decodeUtf8Arrays_ #-}
752 (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
754 D.fromStreamD . decodeUtf8ArraysD_ . D.toStreamD
756-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. When
757-- any invalid character (U+D800-U+D8FF) is encountered in the input stream the
758-- function errors out.
761{-# INLINE encodeUtf8' #-}
762encodeUtf8' :: (Monad m, IsStream t) => t m Char -> t m Word8
763encodeUtf8' = D.fromStreamD . encodeUtf8D' . D.toStreamD
765-- | See section "3.9 Unicode Encoding Forms" in
766-- https://www.unicode.org/versions/Unicode13.0.0/UnicodeStandard-13.0.pdf
768{-# INLINE_NORMAL encodeUtf8D #-}
769encodeUtf8D :: Monad m => Stream m Char -> Stream m Word8
770encodeUtf8D (Stream step state) = Stream step' (EncodeState state WNil)
772 {-# INLINE_LATE step' #-}
773 step' gst (EncodeState st WNil) = do
774 r <- step (adaptState gst) st
780 Yield (fromIntegral x) (EncodeState s WNil)
781 | x <= 0x7FF -> Skip (EncodeState s (ord2 c))
787 (WCons 239 (WCons 191 (WCons 189 WNil)))
788 else Skip (EncodeState s (ord3 c))
789 | otherwise -> Skip (EncodeState s (ord4 c))
790 Skip s -> Skip (EncodeState s WNil)
792 step' _ (EncodeState s (WCons x xs)) = return $ Yield x (EncodeState s xs)
795-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. Any
796-- Invalid characters (U+D800-U+D8FF) in the input stream are replaced by the
797-- Unicode replacement character U+FFFD.
799-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
801-- /Since: 0.8.0 (Lenient Behaviour)/
802{-# INLINE encodeUtf8 #-}
803encodeUtf8 :: (Monad m, IsStream t) => t m Char -> t m Word8
804encodeUtf8 = D.fromStreamD . encodeUtf8D . D.toStreamD
806{-# INLINE_NORMAL encodeUtf8D_ #-}
807encodeUtf8D_ :: Monad m => Stream m Char -> Stream m Word8
808encodeUtf8D_ (Stream step state) = Stream step' (EncodeState state WNil)
810 {-# INLINE_LATE step' #-}
811 step' gst (EncodeState st WNil) = do
812 r <- step (adaptState gst) st
818 Yield (fromIntegral x) (EncodeState s WNil)
819 | x <= 0x7FF -> Skip (EncodeState s (ord2 c))
824 else Skip (EncodeState s (ord3 c))
825 | otherwise -> Skip (EncodeState s (ord4 c))
826 Skip s -> Skip (EncodeState s WNil)
828 step' _ (EncodeState s (WCons x xs)) = return $ Yield x (EncodeState s xs)
830-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. Any
831-- Invalid characters (U+D800-U+D8FF) in the input stream are dropped.
834{-# INLINE encodeUtf8_ #-}
835encodeUtf8_ :: (Monad m, IsStream t) => t m Char -> t m Word8
836encodeUtf8_ = D.fromStreamD . encodeUtf8D_ . D.toStreamD
838-- | Same as 'encodeUtf8'
840{-# DEPRECATED encodeUtf8Lax "Please use 'encodeUtf8' instead" #-}
841{-# INLINE encodeUtf8Lax #-}
842encodeUtf8Lax :: (IsStream t, Monad m) => t m Char -> t m Word8
843encodeUtf8Lax = encodeUtf8
846-------------------------------------------------------------------------------
847-- Utility operations on strings
848-------------------------------------------------------------------------------
850strip :: IsStream t => t m Char -> t m Char
853stripEnd :: IsStream t => t m Char -> t m Char
857-- | Remove leading whitespace from a string.
859-- > stripStart = S.dropWhile isSpace
862{-# INLINE stripStart #-}
863stripStart :: (Monad m, IsStream t) => t m Char -> t m Char
864stripStart = S.dropWhile isSpace
866-- | Fold each line of the stream using the supplied 'Fold'
867-- and stream the result.
869-- >>> Stream.toList $ lines Fold.toList (Stream.fromList "lines\nthis\nstring\n\n\n")
870-- ["lines","this","string","",""]
872-- > lines = S.splitOnSuffix (== '\n')
876lines :: (Monad m, IsStream t) => Fold m Char b -> t m Char -> t m b
877lines = S.splitOnSuffix (== '\n')
879foreign import ccall unsafe "u_iswspace"
880 iswspace :: Int -> Int
882-- | Code copied from base/Data.Char to INLINE it
883{-# INLINE isSpace #-}
884isSpace :: Char -> Bool
886 | uc <= 0x377 = uc == 32 || uc - 0x9 <= 4 || uc == 0xa0
887 | otherwise = iswspace (ord c) /= 0
889 uc = fromIntegral (ord c) :: Word
891-- | Fold each word of the stream using the supplied 'Fold'
892-- and stream the result.
894-- >>> Stream.toList $ words Fold.toList (Stream.fromList "fold these words")
895-- ["fold","these","words"]
897-- > words = S.wordsBy isSpace
901words :: (Monad m, IsStream t) => Fold m Char b -> t m Char -> t m b
902words = S.wordsBy isSpace
904-- | Unfold a stream to character streams using the supplied 'Unfold'
905-- and concat the results suffixing a newline character @\\n@ to each stream.
908-- unlines = Stream.interposeSuffix '\n'
912{-# INLINE unlines #-}
913unlines :: (MonadIO m, IsStream t) => Unfold m a Char -> t m a -> t m Char
914unlines = S.interposeSuffix '\n'
916-- | Unfold the elements of a stream to character streams using the supplied
917-- 'Unfold' and concat the results with a whitespace character infixed between
921-- unwords = Stream.interpose ' '
925{-# INLINE unwords #-}
926unwords :: (MonadIO m, IsStream t) => Unfold m a Char -> t m a -> t m Char
927unwords = 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