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)
33 -- * Operations on character strings
34 , strip -- (dropAround isSpace)
38 -- * StreamD UTF8 Encoding / Decoding transformations.
43 , resumeDecodeUtf8EitherD
45 , decodeUtf8ArraysLenientD
56import Control.Monad.IO.Class (MonadIO, liftIO)
57import Data.Bits (shiftR, shiftL, (.|.), (.&.))
59import Data.Word (Word8)
60import Foreign.ForeignPtr (touchForeignPtr)
61import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
62import Foreign.Storable (Storable(..))
63import GHC.Base (assert, unsafeChr)
64import GHC.ForeignPtr (ForeignPtr (..))
65import GHC.IO.Encoding.Failure (isSurrogate)
66import GHC.Ptr (Ptr (..), plusPtr)
67import Prelude hiding (String, lines, words, unlines, unwords)
68import System.IO.Unsafe (unsafePerformIO)
70import Streamly (IsStream)
71import Streamly.Data.Fold (Fold)
72import Streamly.Memory.Array (Array)
73import Streamly.Internal.Data.Unfold (Unfold)
74import Streamly.Internal.Data.SVar (adaptState)
75import Streamly.Internal.Data.Stream.StreamD (Stream(..), Step (..))
76import Streamly.Internal.Data.Strict (Tuple'(..))
78#if __GLASGOW_HASKELL__ < 800
79import Streamly.Internal.Data.Stream.StreamD (pattern Stream)
82import qualified Streamly.Internal.Memory.Array.Types as A
83import qualified Streamly.Internal.Prelude as S
84import qualified Streamly.Internal.Data.Stream.StreamD as D
86-------------------------------------------------------------------------------
87-- Encoding/Decoding Unicode (UTF-8) Characters
88-------------------------------------------------------------------------------
90-- UTF-8 primitives, Lifted from GHC.IO.Encoding.UTF8.
92data WList = WCons !Word8 !WList | WNil
96ord2 c = assert (n >= 0x80 && n <= 0x07ff) (WCons x1 (WCons x2 WNil))
99 x1 = fromIntegral $ (n `shiftR` 6) + 0xC0
100 x2 = fromIntegral $ (n .&. 0x3F) + 0x80
104ord3 c = assert (n >= 0x0800 && n <= 0xffff) (WCons x1 (WCons x2 (WCons x3 WNil)))
107 x1 = fromIntegral $ (n `shiftR` 12) + 0xE0
108 x2 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80
109 x3 = fromIntegral $ (n .&. 0x3F) + 0x80
113ord4 c = assert (n >= 0x10000) (WCons x1 (WCons x2 (WCons x3 (WCons x4 WNil))))
116 x1 = fromIntegral $ (n `shiftR` 18) + 0xF0
117 x2 = fromIntegral $ ((n `shiftR` 12) .&. 0x3F) + 0x80
118 x3 = fromIntegral $ ((n `shiftR` 6) .&. 0x3F) + 0x80
119 x4 = fromIntegral $ (n .&. 0x3F) + 0x80
121data CodingFailureMode
122 = TransliterateCodingFailure
123 | ErrorOnCodingFailure
126{-# INLINE replacementChar #-}
127replacementChar :: Char
128replacementChar = '\xFFFD'
130-- Int helps in cheaper conversion from Int to Char
132type DecodeState = Word8
134-- See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
136-- XXX Use names decodeSuccess = 0, decodeFailure = 12
138decodeTable :: [Word8]
140 -- The first part of the table maps bytes to character classes that
141 -- to reduce the size of the transition table and create bitmasks.
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 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,
144 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,
145 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,
146 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,
147 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,
148 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,
149 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,
151 -- The second part is a transition table that maps a combination
152 -- of a state of the automaton and a character class to a state.
153 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
154 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
155 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
156 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
157 12,36,12,12,12,12,12,12,12,12,12,12
160{-# NOINLINE utf8d #-}
161utf8d :: A.Array Word8
164 -- Aligning to cacheline makes a barely noticeable difference
165 -- XXX currently alignment is not implemented for unmanaged allocation
166 $ D.runFold (A.writeNAlignedUnmanaged 64 (length decodeTable))
167 (D.fromList decodeTable)
169-- | Return element at the specified index without checking the bounds.
170-- and without touching the foreign ptr.
171{-# INLINE_NORMAL unsafePeekElemOff #-}
172unsafePeekElemOff :: forall a. Storable a => Ptr a -> Int -> a
173unsafePeekElemOff p i = let !x = A.unsafeInlineIO $ peekElemOff p i in x
175-- decode is split into two separate cases to avoid branching instructions.
176-- From the higher level flow we already know which case we are in so we can
177-- call the appropriate decode function.
179-- When the state is 0
180{-# INLINE decode0 #-}
181decode0 :: Ptr Word8 -> Word8 -> Tuple' DecodeState CodePoint
183 let !t = table `unsafePeekElemOff` fromIntegral byte
184 !codep' = (0xff `shiftR` fromIntegral t) .&. fromIntegral byte
185 !state' = table `unsafePeekElemOff` (256 + fromIntegral t)
186 in assert ((byte > 0x7f || error showByte)
187 && (state' /= 0 || error (showByte ++ showTable)))
188 (Tuple' state' codep')
193 let !(Ptr addr) = table
194 end = table `plusPtr` 364
195 in A.Array (ForeignPtr addr undefined) end :: A.Array Word8
196 showByte = "Streamly: decode0: byte: " ++ show byte
197 showTable = " table: " ++ show utf8table
199-- When the state is not 0
200{-# INLINE decode1 #-}
206 -> Tuple' DecodeState CodePoint
207decode1 table state codep byte =
208 -- Remember codep is Int type!
209 -- Can it be unsafe to convert the resulting Int to Char?
210 let !t = table `unsafePeekElemOff` fromIntegral byte
211 !codep' = (fromIntegral byte .&. 0x3f) .|. (codep `shiftL` 6)
212 !state' = table `unsafePeekElemOff`
213 (256 + fromIntegral state + fromIntegral t)
214 in assert (codep' <= 0x10FFFF
215 || error (showByte ++ showState state codep))
216 (Tuple' state' codep')
220 let !(Ptr addr) = table
221 end = table `plusPtr` 364
222 in A.Array (ForeignPtr addr undefined) end :: A.Array Word8
223 showByte = "Streamly: decode1: byte: " ++ show byte
225 " state: " ++ show st ++
226 " codepoint: " ++ show cp ++
227 " table: " ++ show utf8table
229-- We can divide the errors in three general categories:
230-- * A non-starter was encountered in a begin state
231-- * A starter was encountered without completing a codepoint
232-- * The last codepoint was not complete (input underflow)
234data DecodeError = DecodeError !DecodeState !CodePoint deriving Show
237 = FreshPointDecodeInit s
238 | FreshPointDecodeInit1 s Word8
239 | FreshPointDecodeFirst s Word8
240 | FreshPointDecoding s !DecodeState !CodePoint
241 | YieldAndContinue a (FreshPoint s a)
244-- XXX Add proper error messages
245-- XXX Implement this in terms of decodeUtf8Either
246{-# INLINE_NORMAL decodeUtf8WithD #-}
247decodeUtf8WithD :: Monad m => CodingFailureMode -> Stream m Word8 -> Stream m Char
248decodeUtf8WithD cfm (Stream step state) =
249 let A.Array p _ = utf8d
250 !ptr = unsafeForeignPtrToPtr p
251 in Stream (step' ptr) (FreshPointDecodeInit state)
253 {-# INLINE transliterateOrError #-}
254 transliterateOrError e s =
256 ErrorOnCodingFailure -> error e
257 TransliterateCodingFailure -> YieldAndContinue replacementChar s
258 {-# INLINE inputUnderflow #-}
261 ErrorOnCodingFailure ->
262 error "Streamly.Internal.Data.Stream.StreamD.decodeUtf8With: Input Underflow"
263 TransliterateCodingFailure -> YieldAndContinue replacementChar Done
264 {-# INLINE_LATE step' #-}
265 step' _ gst (FreshPointDecodeInit st) = do
266 r <- step (adaptState gst) st
268 Yield x s -> Skip (FreshPointDecodeInit1 s x)
269 Skip s -> Skip (FreshPointDecodeInit s)
272 step' _ _ (FreshPointDecodeInit1 st x) = do
273 -- Note: It is important to use a ">" instead of a "<=" test
274 -- here for GHC to generate code layout for default branch
275 -- prediction for the common case. This is fragile and might
276 -- change with the compiler versions, we need a more reliable
277 -- "likely" primitive to control branch predication.
280 return $ Skip $ YieldAndContinue
281 (unsafeChr (fromIntegral x))
282 (FreshPointDecodeInit st)
283 -- Using a separate state here generates a jump to a
284 -- separate code block in the core which seems to perform
285 -- slightly better for the non-ascii case.
286 True -> return $ Skip $ FreshPointDecodeFirst st x
288 -- XXX should we merge it with FreshPointDecodeInit1?
289 step' table _ (FreshPointDecodeFirst st x) = do
290 let (Tuple' sv cp) = decode0 table x
296 "Streamly.Internal.Data.Stream.StreamD.decodeUtf8With: Invalid UTF8 codepoint encountered"
297 (FreshPointDecodeInit st)
298 0 -> error "unreachable state"
299 _ -> Skip (FreshPointDecoding st sv cp)
301 -- We recover by trying the new byte x a starter of a new codepoint.
302 -- XXX need to use the same recovery in array decoding routine as well
303 step' table gst (FreshPointDecoding st statePtr codepointPtr) = do
304 r <- step (adaptState gst) st
307 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
310 0 -> Skip $ YieldAndContinue (unsafeChr cp)
311 (FreshPointDecodeInit s)
315 "Streamly.Internal.Data.Stream.StreamD.decodeUtf8With: Invalid UTF8 codepoint encountered"
316 (FreshPointDecodeInit1 s x)
317 _ -> Skip (FreshPointDecoding s sv cp)
318 Skip s -> return $ Skip (FreshPointDecoding s statePtr codepointPtr)
319 Stop -> return $ Skip inputUnderflow
321 step' _ _ (YieldAndContinue c s) = return $ Yield c s
322 step' _ _ Done = return Stop
324{-# INLINE decodeUtf8D #-}
325decodeUtf8D :: Monad m => Stream m Word8 -> Stream m Char
326decodeUtf8D = decodeUtf8WithD ErrorOnCodingFailure
328{-# INLINE decodeUtf8LenientD #-}
329decodeUtf8LenientD :: Monad m => Stream m Word8 -> Stream m Char
330decodeUtf8LenientD = decodeUtf8WithD TransliterateCodingFailure
332{-# INLINE_NORMAL resumeDecodeUtf8EitherD #-}
333resumeDecodeUtf8EitherD
338 -> Stream m (Either DecodeError Char)
339resumeDecodeUtf8EitherD dst codep (Stream step state) =
340 let A.Array p _ = utf8d
341 !ptr = unsafeForeignPtrToPtr p
344 then FreshPointDecodeInit state
345 else FreshPointDecoding state dst codep
346 in Stream (step' ptr) stt
348 {-# INLINE_LATE step' #-}
349 step' _ gst (FreshPointDecodeInit st) = do
350 r <- step (adaptState gst) st
352 Yield x s -> Skip (FreshPointDecodeInit1 s x)
353 Skip s -> Skip (FreshPointDecodeInit s)
356 step' _ _ (FreshPointDecodeInit1 st x) = do
357 -- Note: It is important to use a ">" instead of a "<=" test
358 -- here for GHC to generate code layout for default branch
359 -- prediction for the common case. This is fragile and might
360 -- change with the compiler versions, we need a more reliable
361 -- "likely" primitive to control branch predication.
364 return $ Skip $ YieldAndContinue
365 (Right $ unsafeChr (fromIntegral x))
366 (FreshPointDecodeInit st)
367 -- Using a separate state here generates a jump to a
368 -- separate code block in the core which seems to perform
369 -- slightly better for the non-ascii case.
370 True -> return $ Skip $ FreshPointDecodeFirst st x
372 -- XXX should we merge it with FreshPointDecodeInit1?
373 step' table _ (FreshPointDecodeFirst st x) = do
374 let (Tuple' sv cp) = decode0 table x
378 Skip $ YieldAndContinue (Left $ DecodeError 0 (fromIntegral x))
379 (FreshPointDecodeInit st)
380 0 -> error "unreachable state"
381 _ -> Skip (FreshPointDecoding st sv cp)
383 -- We recover by trying the new byte x a starter of a new codepoint.
384 -- XXX need to use the same recovery in array decoding routine as well
385 step' table gst (FreshPointDecoding st statePtr codepointPtr) = do
386 r <- step (adaptState gst) st
389 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
392 0 -> Skip $ YieldAndContinue (Right $ unsafeChr cp)
393 (FreshPointDecodeInit s)
395 Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr)
396 (FreshPointDecodeInit1 s x)
397 _ -> Skip (FreshPointDecoding s sv cp)
398 Skip s -> return $ Skip (FreshPointDecoding s statePtr codepointPtr)
399 Stop -> return $ Skip $ YieldAndContinue (Left $ DecodeError statePtr codepointPtr) Done
401 step' _ _ (YieldAndContinue c s) = return $ Yield c s
402 step' _ _ Done = return Stop
404{-# INLINE_NORMAL decodeUtf8EitherD #-}
405decodeUtf8EitherD :: Monad m
406 => Stream m Word8 -> Stream m (Either DecodeError Char)
407decodeUtf8EitherD = resumeDecodeUtf8EitherD 0 0
410 = OuterLoop s !(Maybe (DecodeState, CodePoint))
411 | InnerLoopDecodeInit s (ForeignPtr a) !(Ptr a) !(Ptr a)
412 | InnerLoopDecodeFirst s (ForeignPtr a) !(Ptr a) !(Ptr a) Word8
413 | InnerLoopDecoding s (ForeignPtr a) !(Ptr a) !(Ptr a)
414 !DecodeState !CodePoint
415 | YAndC !Char (FlattenState s a) -- These constructors can be
416 -- encoded in the FreshPoint
417 -- type, I prefer to keep these
418 -- flat even though that means
419 -- coming up with new names
422-- The normal decodeUtf8 above should fuse with flattenArrays
423-- to create this exact code but it doesn't for some reason, as of now this
424-- remains the fastest way I could figure out to decodeUtf8.
426-- XXX Add Proper error messages
427{-# INLINE_NORMAL decodeUtf8ArraysWithD #-}
428decodeUtf8ArraysWithD ::
431 -> Stream m (A.Array Word8)
433decodeUtf8ArraysWithD cfm (Stream step state) =
434 let A.Array p _ = utf8d
435 !ptr = unsafeForeignPtrToPtr p
436 in Stream (step' ptr) (OuterLoop state Nothing)
438 {-# INLINE transliterateOrError #-}
439 transliterateOrError e s =
441 ErrorOnCodingFailure -> error e
442 TransliterateCodingFailure -> YAndC replacementChar s
443 {-# INLINE inputUnderflow #-}
446 ErrorOnCodingFailure ->
448 "Streamly.Internal.Data.Stream.StreamD.decodeUtf8ArraysWith: Input Underflow"
449 TransliterateCodingFailure -> YAndC replacementChar D
450 {-# INLINE_LATE step' #-}
451 step' _ gst (OuterLoop st Nothing) = do
452 r <- step (adaptState gst) st
455 Yield A.Array {..} s ->
456 let p = unsafeForeignPtrToPtr aStart
457 in Skip (InnerLoopDecodeInit s aStart p aEnd)
458 Skip s -> Skip (OuterLoop s Nothing)
460 step' _ gst (OuterLoop st dst@(Just (ds, cp))) = do
461 r <- step (adaptState gst) st
464 Yield A.Array {..} s ->
465 let p = unsafeForeignPtrToPtr aStart
466 in Skip (InnerLoopDecoding s aStart p aEnd ds cp)
467 Skip s -> Skip (OuterLoop s dst)
468 Stop -> Skip inputUnderflow
469 step' _ _ (InnerLoopDecodeInit st startf p end)
471 liftIO $ touchForeignPtr startf
472 return $ Skip $ OuterLoop st Nothing
473 step' _ _ (InnerLoopDecodeInit st startf p end) = do
475 -- Note: It is important to use a ">" instead of a "<=" test here for
476 -- GHC to generate code layout for default branch prediction for the
477 -- common case. This is fragile and might change with the compiler
478 -- versions, we need a more reliable "likely" primitive to control
479 -- branch predication.
482 return $ Skip $ YAndC
483 (unsafeChr (fromIntegral x))
484 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
485 -- Using a separate state here generates a jump to a separate code
486 -- block in the core which seems to perform slightly better for the
488 True -> return $ Skip $ InnerLoopDecodeFirst st startf p end x
490 step' table _ (InnerLoopDecodeFirst st startf p end x) = do
491 let (Tuple' sv cp) = decode0 table x
497 "Streamly.Internal.Data.Stream.StreamD.decodeUtf8ArraysWith: Invalid UTF8 codepoint encountered"
498 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
499 0 -> error "unreachable state"
500 _ -> Skip (InnerLoopDecoding st startf (p `plusPtr` 1) end sv cp)
501 step' _ _ (InnerLoopDecoding st startf p end sv cp)
503 liftIO $ touchForeignPtr startf
504 return $ Skip $ OuterLoop st (Just (sv, cp))
505 step' table _ (InnerLoopDecoding st startf p end statePtr codepointPtr) = do
507 let (Tuple' sv cp) = decode1 table statePtr codepointPtr x
514 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
518 "Streamly.Internal.Data.Stream.StreamD.decodeUtf8ArraysWith: Invalid UTF8 codepoint encountered"
519 (InnerLoopDecodeInit st startf (p `plusPtr` 1) end)
520 _ -> Skip (InnerLoopDecoding st startf (p `plusPtr` 1) end sv cp)
521 step' _ _ (YAndC c s) = return $ Yield c s
522 step' _ _ D = return Stop
524{-# INLINE decodeUtf8ArraysD #-}
527 => Stream m (A.Array Word8)
529decodeUtf8ArraysD = decodeUtf8ArraysWithD ErrorOnCodingFailure
531{-# INLINE decodeUtf8ArraysLenientD #-}
532decodeUtf8ArraysLenientD ::
534 => Stream m (A.Array Word8)
536decodeUtf8ArraysLenientD = decodeUtf8ArraysWithD TransliterateCodingFailure
538data EncodeState s = EncodeState s !WList
540-- More yield points improve performance, but I am not sure if they can cause
541-- too much code bloat or some trouble with fusion. So keeping only two yield
542-- points for now, one for the ascii chars (fast path) and one for all other
544{-# INLINE_NORMAL encodeUtf8D #-}
545encodeUtf8D :: Monad m => Stream m Char -> Stream m Word8
546encodeUtf8D (Stream step state) = Stream step' (EncodeState state WNil)
548 {-# INLINE_LATE step' #-}
549 step' gst (EncodeState st WNil) = do
550 r <- step (adaptState gst) st
557 Yield (fromIntegral x) (EncodeState s WNil)
558 | x <= 0x7FF -> Skip (EncodeState s (ord2 c))
562 "Streamly.Internal.Data.Stream.StreamD.encodeUtf8: Encountered a surrogate"
563 else Skip (EncodeState s (ord3 c))
564 | otherwise -> Skip (EncodeState s (ord4 c))
565 Skip s -> Skip (EncodeState s WNil)
567 step' _ (EncodeState s (WCons x xs)) = return $ Yield x (EncodeState s xs)
570-- | Decode a stream of bytes to Unicode characters by mapping each byte to a
571-- corresponding Unicode 'Char' in 0-255 range.
574{-# INLINE decodeLatin1 #-}
575decodeLatin1 :: (IsStream t, Monad m) => t m Word8 -> t m Char
576decodeLatin1 = S.map (unsafeChr . fromIntegral)
578-- | Encode a stream of Unicode characters to bytes by mapping each character
579-- to a byte in 0-255 range. Throws an error if the input stream contains
580-- characters beyond 255.
583{-# INLINE encodeLatin1 #-}
584encodeLatin1 :: (IsStream t, Monad m) => t m Char -> t m Word8
585encodeLatin1 = S.map convert
588 let codepoint = ord c
589 in if codepoint > 255
590 then error $ "Streamly.String.encodeLatin1 invalid " ++
591 "input char codepoint " ++ show codepoint
592 else fromIntegral codepoint
594-- | Like 'encodeLatin1' but silently truncates and maps input characters beyond
595-- 255 to (incorrect) chars in 0-255 range. No error or exception is thrown
596-- when such truncation occurs.
599{-# INLINE encodeLatin1Lax #-}
600encodeLatin1Lax :: (IsStream t, Monad m) => t m Char -> t m Word8
601encodeLatin1Lax = S.map (fromIntegral . ord)
603-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
604-- The incoming stream is truncated if an invalid codepoint is encountered.
607{-# INLINE decodeUtf8 #-}
608decodeUtf8 :: (Monad m, IsStream t) => t m Word8 -> t m Char
609decodeUtf8 = D.fromStreamD . decodeUtf8D . D.toStreamD
614{-# INLINE decodeUtf8Arrays #-}
615decodeUtf8Arrays :: (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
616decodeUtf8Arrays = D.fromStreamD . decodeUtf8ArraysD . D.toStreamD
618-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
619-- Any invalid codepoint encountered is replaced with the unicode replacement
623{-# INLINE decodeUtf8Lax #-}
624decodeUtf8Lax :: (Monad m, IsStream t) => t m Word8 -> t m Char
625decodeUtf8Lax = D.fromStreamD . decodeUtf8LenientD . D.toStreamD
630{-# INLINE decodeUtf8Either #-}
631decodeUtf8Either :: (Monad m, IsStream t)
632 => t m Word8 -> t m (Either DecodeError Char)
633decodeUtf8Either = D.fromStreamD . decodeUtf8EitherD . D.toStreamD
638{-# INLINE resumeDecodeUtf8Either #-}
639resumeDecodeUtf8Either
640 :: (Monad m, IsStream t)
644 -> t m (Either DecodeError Char)
645resumeDecodeUtf8Either st cp =
646 D.fromStreamD . resumeDecodeUtf8EitherD st cp . D.toStreamD
651{-# INLINE decodeUtf8ArraysLenient #-}
652decodeUtf8ArraysLenient ::
653 (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
654decodeUtf8ArraysLenient =
655 D.fromStreamD . decodeUtf8ArraysLenientD . D.toStreamD
657-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream.
660{-# INLINE encodeUtf8 #-}
661encodeUtf8 :: (Monad m, IsStream t) => t m Char -> t m Word8
662encodeUtf8 = D.fromStreamD . encodeUtf8D . D.toStreamD
665-------------------------------------------------------------------------------
666-- Utility operations on strings
667-------------------------------------------------------------------------------
669strip :: IsStream t => t m Char -> t m Char
672stripEnd :: IsStream t => t m Char -> t m Char
676-- | Remove leading whitespace from a string.
678-- > stripStart = S.dropWhile isSpace
681{-# INLINE stripStart #-}
682stripStart :: (Monad m, IsStream t) => t m Char -> t m Char
683stripStart = S.dropWhile isSpace
685-- | Fold each line of the stream using the supplied 'Fold'
686-- and stream the result.
688-- >>> S.toList $ lines FL.toList (S.fromList "lines\nthis\nstring\n\n\n")
689-- ["lines", "this", "string", "", ""]
691-- > lines = S.splitOnSuffix (== '\n')
695lines :: (Monad m, IsStream t) => Fold m Char b -> t m Char -> t m b
696lines = S.splitOnSuffix (== '\n')
698foreign import ccall unsafe "u_iswspace"
699 iswspace :: Int -> Int
701-- | Code copied from base/Data.Char to INLINE it
702{-# INLINE isSpace #-}
703isSpace :: Char -> Bool
705 | uc <= 0x377 = uc == 32 || uc - 0x9 <= 4 || uc == 0xa0
706 | otherwise = iswspace (ord c) /= 0
708 uc = fromIntegral (ord c) :: Word
710-- | Fold each word of the stream using the supplied 'Fold'
711-- and stream the result.
713-- >>> S.toList $ words FL.toList (S.fromList "fold these words")
714-- ["fold", "these", "words"]
716-- > words = S.wordsBy isSpace
720words :: (Monad m, IsStream t) => Fold m Char b -> t m Char -> t m b
721words = S.wordsBy isSpace
723-- | Unfold a stream to character streams using the supplied 'Unfold'
724-- and concat the results suffixing a newline character @\\n@ to each stream.
727{-# INLINE unlines #-}
728unlines :: (MonadIO m, IsStream t) => Unfold m a Char -> t m a -> t m Char
729unlines = S.interposeSuffix '\n'
731-- | Unfold the elements of a stream to character streams using the supplied
732-- 'Unfold' and concat the results with a whitespace character infixed between
736{-# INLINE unwords #-}
737unwords :: (MonadIO m, IsStream t) => Unfold m a Char -> t m a -> t m Char
738unwords = 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