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.
20-- The 'String' type in this module is just a synonym for the type @List Char@.
21-- It provides better performance compared to the standard Haskell @String@
22-- type and can be used almost as a drop-in replacement, especially when used
23-- with @OverloadedStrings@ extension, with little differences.
25-- See "Streamly.List", <src/docs/streamly-vs-lists.md> for more details and
26-- <src/test/PureStreams.hs> for comprehensive usage examples.
28-- TBD write a note on performance comparison with text, bytestrings, lists and
35 -- * Encoding and Decoding
37 , encodeChar8Unchecked
43 -- * Unicode aware operations
49 -- * Operations on character strings
50 , strip -- (dropAround isSpace)
63import Control.Monad.IO.Class (MonadIO)
65import Data.Word (Word8)
66import GHC.Base (unsafeChr)
67import Streamly (IsStream, MonadAsync)
68import Prelude hiding (String, lines, words, unlines, unwords)
69import Streamly.Fold (Fold)
70import Streamly.Mem.Array (Array, toArray)
72import qualified Streamly.Prelude as S
73import qualified Streamly.Fold as FL
74import qualified Streamly.Mem.Array.Types as A (unlines)
75import qualified Streamly.Mem.Array as A
76import qualified Streamly.Streams.StreamD as D
78-- type String = List Char
80-------------------------------------------------------------------------------
81-- Encoding/Decoding Characters
82-------------------------------------------------------------------------------
84-- decodeWith :: TextEncoding -> t m Word8 -> t m Char
85-- decodeWith = undefined
87-------------------------------------------------------------------------------
88-- Encoding/Decoding Unicode Characters
89-------------------------------------------------------------------------------
91-- | Decode a stream of bytes to Unicode characters by mapping each byte to a
92-- corresponding Unicode 'Char' in 0-255 range.
93{-# INLINE decodeChar8 #-}
94decodeChar8 :: (IsStream t, Monad m) => t m Word8 -> t m Char
95decodeChar8 = S.map (unsafeChr . fromIntegral)
97-- | Encode a stream of Unicode characters to bytes by mapping each character
98-- to a byte in 0-255 range. Throws an error if the input stream contains
99-- characters beyond 255.
100{-# INLINE encodeChar8 #-}
101encodeChar8 :: (IsStream t, Monad m) => t m Char -> t m Word8
102encodeChar8 = S.map convert
105 let codepoint = ord c
106 in if codepoint > 255
107 then error $ "Streamly.String.encodeChar8 invalid \
108 \input char codepoint " ++ show codepoint
109 else fromIntegral codepoint
111-- | Like 'encodeChar8' but silently truncates and maps input characters beyond
112-- 255 to (incorrect) chars in 0-255 range. No error or exception is thrown
113-- when such truncation occurs.
114{-# INLINE encodeChar8Unchecked #-}
115encodeChar8Unchecked :: (IsStream t, Monad m) => t m Char -> t m Word8
116encodeChar8Unchecked = S.map (fromIntegral . ord)
118-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
119-- The incoming stream is truncated if an invalid codepoint is encountered.
120{-# INLINE decodeUtf8 #-}
121decodeUtf8 :: (Monad m, IsStream t) => t m Word8 -> t m Char
122decodeUtf8 = D.fromStreamD . D.decodeUtf8 . D.toStreamD
124-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream.
125{-# INLINE encodeUtf8 #-}
126encodeUtf8 :: (Monad m, IsStream t) => t m Char -> t m Word8
127encodeUtf8 = D.fromStreamD . D.encodeUtf8 . D.toStreamD
130-------------------------------------------------------------------------------
131-- Unicode aware operations on strings
132-------------------------------------------------------------------------------
134toCaseFold :: IsStream t => t m Char -> t m Char
135toCaseFold = undefined
137toLower :: IsStream t => t m Char -> t m Char
140toUpper :: IsStream t => t m Char -> t m Char
143toTitle :: IsStream t => t m Char -> t m Char
146-------------------------------------------------------------------------------
147-- Utility operations on strings
148-------------------------------------------------------------------------------
150strip :: IsStream t => t m Char -> t m Char
153stripEnd :: IsStream t => t m Char -> t m Char
157-- | Remove leading whitespace from a String.
159-- > stripStart = S.dropWhile isSpace
160{-# INLINE stripStart #-}
161stripStart :: (Monad m, IsStream t) => t m Char -> t m Char
162stripStart = S.dropWhile isSpace
164-- | Fold each line of the stream using the supplied Fold
165-- and stream the result.
167-- >>> foldLines "lines\nthis\nstring\n\n\n" FL.toList
168-- ["lines", "this", "string"]
169{-# INLINE foldLines #-}
170foldLines :: (Monad m, IsStream t) => t m Char -> Fold m Char b -> t m b
171foldLines = flip (FL.splitSuffixBy (== '\n'))
173-- | Fold each word of the stream using the supplied Fold
174-- and stream the result.
176-- >>> foldWords "fold these words" FL.toList
177-- ["fold", "these", "words"]
178{-# INLINE foldWords #-}
179foldWords :: (Monad m, IsStream t) => t m Char -> Fold m Char b -> t m b
180foldWords = flip (FL.wordsBy isSpace)
182foreign import ccall unsafe "u_iswspace"
183 iswspace :: Int -> Int
185-- | Code copied from base/Data.Char to INLINE it
186{-# INLINE isSpace #-}
187isSpace :: Char -> Bool
189 | uc <= 0x377 = uc == 32 || uc - 0x9 <= 4 || uc == 0xa0
190 | otherwise = iswspace (ord c) /= 0
192 uc = fromIntegral (ord c) :: Word
194-- | Break a string up into a list of strings at newline characters.
195-- The resulting strings do not contain newlines.
197-- >>> lines "lines\nthis\nstring\n\n\n"
198-- ["lines","this","string"]
200-- If you're dealing with lines of massive length, consider using
201-- 'foldLines' instead.
203lines :: (MonadIO m, IsStream t) => t m Char -> t m (Array Char)
204lines = FL.splitSuffixBy (== '\n') toArray
206-- | Break a string up into a list of strings, which were delimited
207-- by characters representing white space.
209-- >>> words "A newline\nis considered white space?"
210-- ["A", "newline", "is", "considered", "white", "space?"]
212-- If you're dealing with words of massive length, consider using
213-- 'foldWords' instead.
215words :: (MonadIO m, IsStream t) => t m Char -> t m (Array Char)
216words = FL.wordsBy isSpace toArray
218-- | Flattens the stream of 'Array' 'Char', after appending a terminating
219-- newline to each string.
221-- 'unlines' is an inverse operation to 'lines'.
223-- >>> unlines ["lines", "this", "string"]
224-- "lines\nthis\nstring\n"
226-- Note that, in general
228-- > unlines . lines /= id
229{-# INLINE unlines #-}
230unlines :: (MonadIO m, IsStream t) => t m (Array Char) -> t m Char
231unlines = D.fromStreamD . A.unlines '\n' . D.toStreamD
233-- | Flattens the stream of 'Array' 'Char', after appending a separating
234-- space to each string.
236-- 'unwords' is an inverse operation to 'words'.
238-- >>> unwords ["unwords", "this", "string"]
239-- "unwords this string"
241-- Note that, in general
243-- > unwords . words /= id
244{-# INLINE unwords #-}
245unwords :: (MonadAsync m, IsStream t) => t m (Array Char) -> t m Char
246unwords = 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