1{-# LANGUAGE FlexibleContexts #-}
3-- Module : Streamly.String
4-- Copyright : (c) 2018 Composewell Technologies
7-- Maintainer : harendra.kumar@gmail.com
8-- Stability : experimental
11-- Stream of 'Char' are the equivalent of Haskell Strings but without any
12-- performance issues that are usually associated with the 'String' type.
13-- Haskell lists are not suitable for storage in GC memory. We use streams for
14-- processing and arrays for storing. We can use the usual stream processing
15-- routines on these 'Char' streams. Stream processing is very efficient due to
16-- stream fusion. If strings are to be stored or buffered in memory, they can
17-- be encoded to 'Word8' arrays using the encoding routines provided in the
18-- module. Therefore, a separate type for text representation is not required.
19-- A @Stream Identity Char@ can be used almost as a drop-in replacement for the
20-- standard Haskell @String@, especially when used with @OverloadedStrings@
21-- extension, with little differences.
23-- The 'String' type in this module is just a synonym for the type @List Char@.
24-- It provides better performance compared to the standard Haskell @String@
25-- type and can be used almost as a drop-in replacement, especially when used
26-- with @OverloadedStrings@ extension, with little differences.
28-- See "Streamly.List", <src/docs/streamly-vs-lists.md> for more details and
29-- <src/test/PureStreams.hs> for comprehensive usage examples.
31-- TBD write a note on performance comparison with text, bytestrings, lists and
38 -- * Encoding and Decoding
40 , encodeChar8Unchecked
46 -- * Unicode aware operations
52 -- * Operations on character strings
53 , strip -- (dropAround isSpace)
66import Control.Monad.IO.Class (MonadIO)
68import Data.Word (Word8)
69import GHC.Base (unsafeChr)
70import Streamly (IsStream, MonadAsync)
71import Prelude hiding (String, lines, words, unlines, unwords)
72import Streamly.Fold (Fold)
73import Streamly.Mem.Array (Array, toArray)
75import qualified Streamly.Prelude as S
76import qualified Streamly.Fold as FL
77import qualified Streamly.Mem.Array.Types as A (unlines)
78import qualified Streamly.Mem.Array as A
79import qualified Streamly.Streams.StreamD as D
81-- type String = List Char
83-------------------------------------------------------------------------------
84-- Encoding/Decoding Characters
85-------------------------------------------------------------------------------
87-- decodeWith :: TextEncoding -> t m Word8 -> t m Char
88-- decodeWith = undefined
90-------------------------------------------------------------------------------
91-- Encoding/Decoding Unicode Characters
92-------------------------------------------------------------------------------
94-- | Decode a stream of bytes to Unicode characters by mapping each byte to a
95-- corresponding Unicode 'Char' in 0-255 range.
96{-# INLINE decodeChar8 #-}
97decodeChar8 :: (IsStream t, Monad m) => t m Word8 -> t m Char
98decodeChar8 = S.map (unsafeChr . fromIntegral)
100-- | Encode a stream of Unicode characters to bytes by mapping each character
101-- to a byte in 0-255 range. Throws an error if the input stream contains
102-- characters beyond 255.
103{-# INLINE encodeChar8 #-}
104encodeChar8 :: (IsStream t, Monad m) => t m Char -> t m Word8
105encodeChar8 = S.map convert
108 let codepoint = ord c
109 in if codepoint > 255
110 then error $ "Streamly.String.encodeChar8 invalid \
111 \input char codepoint " ++ show codepoint
112 else fromIntegral codepoint
114-- | Like 'encodeChar8' but silently truncates and maps input characters beyond
115-- 255 to (incorrect) chars in 0-255 range. No error or exception is thrown
116-- when such truncation occurs.
117{-# INLINE encodeChar8Unchecked #-}
118encodeChar8Unchecked :: (IsStream t, Monad m) => t m Char -> t m Word8
119encodeChar8Unchecked = S.map (fromIntegral . ord)
121-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
122-- The incoming stream is truncated if an invalid codepoint is encountered.
123{-# INLINE decodeUtf8 #-}
124decodeUtf8 :: (Monad m, IsStream t) => t m Word8 -> t m Char
125decodeUtf8 = D.fromStreamD . D.decodeUtf8 . D.toStreamD
127-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream.
128{-# INLINE encodeUtf8 #-}
129encodeUtf8 :: (Monad m, IsStream t) => t m Char -> t m Word8
130encodeUtf8 = D.fromStreamD . D.encodeUtf8 . D.toStreamD
133-------------------------------------------------------------------------------
134-- Unicode aware operations on strings
135-------------------------------------------------------------------------------
137toCaseFold :: IsStream t => t m Char -> t m Char
138toCaseFold = undefined
140toLower :: IsStream t => t m Char -> t m Char
143toUpper :: IsStream t => t m Char -> t m Char
146toTitle :: IsStream t => t m Char -> t m Char
149-------------------------------------------------------------------------------
150-- Utility operations on strings
151-------------------------------------------------------------------------------
153strip :: IsStream t => t m Char -> t m Char
156stripEnd :: IsStream t => t m Char -> t m Char
160-- | Remove leading whitespace from a String.
162-- > stripStart = S.dropWhile isSpace
163{-# INLINE stripStart #-}
164stripStart :: (Monad m, IsStream t) => t m Char -> t m Char
165stripStart = S.dropWhile isSpace
167-- | Fold each line of the stream using the supplied Fold
168-- and stream the result.
170-- >>> S.toList $ foldLines (S.fromList "lines\nthis\nstring\n\n\n") FL.toList
171-- ["lines", "this", "string", "", ""]
172{-# INLINE foldLines #-}
173foldLines :: (Monad m, IsStream t) => t m Char -> Fold m Char b -> t m b
174foldLines = flip (FL.splitOnSuffix (== '\n'))
176-- | Fold each word of the stream using the supplied Fold
177-- and stream the result.
179-- >>> S.toList $ foldWords (S.fromList "fold these words") FL.toList
180-- ["fold", "these", "words"]
181{-# INLINE foldWords #-}
182foldWords :: (Monad m, IsStream t) => t m Char -> Fold m Char b -> t m b
183foldWords = flip (FL.wordsBy isSpace)
185foreign import ccall unsafe "u_iswspace"
186 iswspace :: Int -> Int
188-- | Code copied from base/Data.Char to INLINE it
189{-# INLINE isSpace #-}
190isSpace :: Char -> Bool
192 | uc <= 0x377 = uc == 32 || uc - 0x9 <= 4 || uc == 0xa0
193 | otherwise = iswspace (ord c) /= 0
195 uc = fromIntegral (ord c) :: Word
197-- | Break a string up into a list of strings at newline characters.
198-- The resulting strings do not contain newlines.
200-- >>> S.toList $ lines $ S.fromList "lines\nthis\nstring\n\n\n"
201-- ["lines","this","string","",""]
203-- If you're dealing with lines of massive length, consider using
204-- 'foldLines' instead.
206lines :: (MonadIO m, IsStream t) => t m Char -> t m (Array Char)
207lines = FL.splitOnSuffix (== '\n') toArray
209-- | Break a string up into a list of strings, which were delimited
210-- by characters representing white space.
212-- >>> S.toList $ words $ S.fromList "A newline\nis considered white space?"
213-- ["A", "newline", "is", "considered", "white", "space?"]
215-- If you're dealing with words of massive length, consider using
216-- 'foldWords' instead.
218words :: (MonadIO m, IsStream t) => t m Char -> t m (Array Char)
219words = FL.wordsBy isSpace toArray
221-- | Flattens the stream of @Array Char@, after appending a terminating
222-- newline to each string.
224-- 'unlines' is an inverse operation to 'lines'.
226-- >>> S.toList $ unlines $ S.fromList ["lines", "this", "string"]
227-- "lines\nthis\nstring\n"
229-- Note that, in general
231-- > unlines . lines /= id
232{-# INLINE unlines #-}
233unlines :: (MonadIO m, IsStream t) => t m (Array Char) -> t m Char
234unlines = D.fromStreamD . A.unlines '\n' . D.toStreamD
236-- | Flattens the stream of @Array Char@, after appending a separating
237-- space to each string.
239-- 'unwords' is an inverse operation to 'words'.
241-- >>> S.toList $ unwords $ S.fromList ["unwords", "this", "string"]
242-- "unwords this string"
244-- Note that, in general
246-- > unwords . words /= id
247{-# INLINE unwords #-}
248unwords :: (MonadAsync m, IsStream t) => t m (Array Char) -> t m Char
249unwords = A.flattenArrays . (S.intersperse (A.fromList " "))
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