on master help sign in
rename Unicode encode/decode ops

* refactor the Unicode stream module in Stream/Char/Array modules

commit011299be7030bb7c979f45d46537874c33237f1e authorHarendra Kumar <harendra.kumar@gmail.com> dateMon 4 Nov 2019, 02:34 (7 years ago) parent44e3cb3
11 files changed+286−219
benchmark/FileIO.hs+4−4 examples/FileSinkServer.hs+2−2 examples/WordClassifier.hs+3−2 examples/WordCount.hs+1−1 src/Streamly/Benchmark/FileIO/Stream.hs+40−39 src/Streamly/Data/Unicode/Stream.hs+23−26 src/Streamly/Internal/Data/Unicode/Char.hs+49 src/Streamly/Internal/Data/Unicode/Stream.hs+68−137 src/Streamly/Internal/Memory/Unicode/Array.hs+85 streamly.cabal+2 test/String.hs+9−8
benchmark/FileIO.hs+4−4
@@ -155,12 +155,12 @@ main = do
155155 , mkBench "read-word8" href $ do
156156 Handles inh _ <- readIORef href
157157 BFS.readWord8 inh
158− , mkBench "read-char8" href $ do
158+ , mkBench "read-latin1" href $ do
159159 Handles inh _ <- readIORef href
160− BFS.decodeChar8 inh
160+ BFS.decodeLatin1 inh
161161 , mkBench "read-utf8" href $ do
162162 Handles inh _ <- readIORef href
163− BFS.decodeUtf8Lenient inh
163+ BFS.decodeUtf8Lax inh
164164 ]
165165 , bgroup "copyArray"
166166 [ mkBench "copy" href $ do
@@ -176,7 +176,7 @@ main = do
176176 ]
177177 -- This needs an ascii file, as decode just errors out.
178178 , bgroup "decode-encode"
179− [ mkBench "char8" href $ do
179+ [ mkBench "latin1" href $ do
180180 Handles inh outh <- readIORef href
181181 BFS.copyCodecChar8 inh outh
182182 , mkBench "utf8-arrays" href $ do
examples/FileSinkServer.hs+2−2
@@ -24,7 +24,7 @@ main = do
2424 file <- fmap head getArgs
2525 withFile file AppendMode
2626 (\src -> S.fold (FH.write src)
27− $ encodeChar8Unchecked
27+ $ encodeLatin1Lax
2828 $ S.concatUnfold A.read
2929 $ S.concatMapWith parallel use
3030 $ S.unfold TCP.acceptOnPort 8090)
@@ -34,5 +34,5 @@ main = do
3434 use sk = S.finally (liftIO $ close sk) (recv sk)
3535 recv =
3636 S.splitWithSuffix (== '\n') A.write
37− . decodeChar8
37+ . decodeLatin1
3838 . S.unfold NS.read
examples/WordClassifier.hs+3−2
@@ -17,6 +17,7 @@ import qualified Data.List as List
1717import qualified Data.Ord as Ord
1818import Foreign.Storable (Storable(..))
1919import qualified Streamly.Data.Unicode.Stream as Streamly
20+import qualified Streamly.Internal.Data.Unicode.Stream as Streamly
2021import qualified Streamly.FileSystem.Handle as FH
2122import qualified Streamly.Data.Fold as FL
2223import qualified Streamly.Internal.Data.Fold as IFL
@@ -56,9 +57,9 @@ main =
5657 name <- fmap head getArgs
5758 src <- openFile name ReadMode
5859 Streamly.unfold FH.read src -- SerialT IO Word8
59− & Streamly.decodeChar8 -- SerialT IO Char
60+ & Streamly.decodeLatin1 -- SerialT IO Char
6061 & Streamly.map toLower -- SerialT IO Char
61− & Streamly.foldWords FL.toList -- SerialT IO String
62+ & Streamly.words FL.toList -- SerialT IO String
6263 & Streamly.filter (all isAlpha)
6364 & Streamly.foldlM' (flip (Map.alterF alter)) Map.empty
6465 & fmap Map.toList
examples/WordCount.hs+1−1
@@ -336,7 +336,7 @@ countCharSerial (Counts l w c wasSpace) ch =
336336_wc_mwl_serial :: Handle -> IO ()
337337_wc_mwl_serial src = print =<< (
338338 S.foldl' countCharSerial (Counts 0 0 0 True)
339− $ S.decodeUtf8Lenient
339+ $ S.decodeUtf8Lax
340340 $ S.unfold FH.read src)
341341
342342-------------------------------------------------------------------------------
src/Streamly/Benchmark/FileIO/Stream.hs+40−39
@@ -47,10 +47,10 @@ module Streamly.Benchmark.FileIO.Stream
4747 , wordsUnwordsCopy
4848 , wordsUnwordsCharArrayCopy
4949 , readWord8
50− , decodeChar8
50+ , decodeLatin1
5151 , copyCodecChar8
5252 , copyCodecUtf8
53− , decodeUtf8Lenient
53+ , decodeUtf8Lax
5454 , copyCodecUtf8Lenient
5555 , chunksOf
5656 , chunksOfD
@@ -79,6 +79,7 @@ import qualified Streamly.Data.Fold as FL
7979-- import qualified Streamly.Internal.Data.Fold as IFL
8080import qualified Streamly.Data.Unicode.Stream as SS
8181import qualified Streamly.Internal.Data.Unicode.Stream as IUS
82+import qualified Streamly.Internal.Memory.Unicode.Array as IUA
8283import qualified Streamly.Internal.Data.Unfold as IUF
8384import qualified Streamly.Internal.Prelude as IP
8485import qualified Streamly.Streams.StreamD as D
@@ -119,8 +120,8 @@ inspect $ 'countBytes `hasNoType` ''D.ConcatMapUState
119120countLines :: Handle -> IO Int
120121countLines =
121122 S.length
122− . SS.foldLines FL.drain
123− . SS.decodeChar8
123+ . IUS.lines FL.drain
124+ . SS.decodeLatin1
124125 . S.unfold FH.read
125126
126127#ifdef INSPECTION
@@ -135,8 +136,8 @@ inspect $ 'countLines `hasNoType` ''D.ConcatMapUState
135136countWords :: Handle -> IO Int
136137countWords =
137138 S.length
138− . SS.foldWords FL.drain
139− . SS.decodeChar8
139+ . IUS.words FL.drain
140+ . SS.decodeLatin1
140141 . S.unfold FH.read
141142
142143#ifdef INSPECTION
@@ -150,8 +151,8 @@ inspect $ hasNoTypeClasses 'countWords
150151countLinesU :: Handle -> IO Int
151152countLinesU inh =
152153 S.length
153− $ SS.foldLines FL.drain
154− $ SS.decodeChar8
154+ $ IUS.lines FL.drain
155+ $ SS.decodeLatin1
155156 $ S.concatUnfold A.read (IFH.toStreamArrays inh)
156157
157158#ifdef INSPECTION
@@ -304,11 +305,11 @@ inspect $ 'copy `hasNoType` ''D.ConcatMapUState
304305readWord8 :: Handle -> IO ()
305306readWord8 inh = S.drain $ S.unfold FH.read inh
306307
307−{-# INLINE decodeChar8 #-}
308−decodeChar8 :: Handle -> IO ()
309−decodeChar8 inh =
308+{-# INLINE decodeLatin1 #-}
309+decodeLatin1 :: Handle -> IO ()
310+decodeLatin1 inh =
310311 S.drain
311− $ SS.decodeChar8
312+ $ SS.decodeLatin1
312313 $ S.unfold FH.read inh
313314
314315-- | Copy file
@@ -316,8 +317,8 @@ decodeChar8 inh =
316317copyCodecChar8 :: Handle -> Handle -> IO ()
317318copyCodecChar8 inh outh =
318319 S.fold (FH.write outh)
319− $ SS.encodeChar8
320− $ SS.decodeChar8
320+ $ SS.encodeLatin1
321+ $ SS.decodeLatin1
321322 $ S.unfold FH.read inh
322323
323324#ifdef INSPECTION
@@ -327,18 +328,18 @@ inspect $ 'copyCodecChar8 `hasNoType` ''AT.FlattenState
327328inspect $ 'copyCodecChar8 `hasNoType` ''D.ConcatMapUState
328329#endif
329330
330−{-# INLINE decodeUtf8Lenient #-}
331−decodeUtf8Lenient :: Handle -> IO ()
332−decodeUtf8Lenient inh =
331+{-# INLINE decodeUtf8Lax #-}
332+decodeUtf8Lax :: Handle -> IO ()
333+decodeUtf8Lax inh =
333334 S.drain
334− $ SS.decodeUtf8Lenient
335+ $ SS.decodeUtf8Lax
335336 $ S.unfold FH.read inh
336337
337338#ifdef INSPECTION
338−inspect $ hasNoTypeClasses 'decodeUtf8Lenient
339−-- inspect $ 'decodeUtf8Lenient `hasNoType` ''Step
340−-- inspect $ 'decodeUtf8Lenient `hasNoType` ''AT.FlattenState
341−-- inspect $ 'decodeUtf8Lenient `hasNoType` ''D.ConcatMapUState
339+inspect $ hasNoTypeClasses 'decodeUtf8Lax
340+-- inspect $ 'decodeUtf8Lax `hasNoType` ''Step
341+-- inspect $ 'decodeUtf8Lax `hasNoType` ''AT.FlattenState
342+-- inspect $ 'decodeUtf8Lax `hasNoType` ''D.ConcatMapUState
342343#endif
343344
344345-- | Copy file
@@ -363,7 +364,7 @@ copyCodecUtf8Lenient :: Handle -> Handle -> IO ()
363364copyCodecUtf8Lenient inh outh =
364365 S.fold (FH.write outh)
365366 $ SS.encodeUtf8
366− $ SS.decodeUtf8Lenient
367+ $ SS.decodeUtf8Lax
367368 $ S.unfold FH.read inh
368369
369370#ifdef INSPECTION
@@ -411,10 +412,10 @@ inspect $ 'chunksOfD `hasNoType` ''D.ConcatMapUState
411412linesUnlinesCopy :: Handle -> Handle -> IO ()
412413linesUnlinesCopy inh outh =
413414 S.fold (FH.write outh)
414− $ SS.encodeChar8
415− $ SS.unfoldLines IUF.fromList
415+ $ SS.encodeLatin1
416+ $ IUS.unlines IUF.fromList
416417 $ S.splitOnSuffix (== '\n') FL.toList
417− $ SS.decodeChar8
418+ $ SS.decodeLatin1
418419 $ S.unfold FH.read inh
419420
420421{-# INLINE linesUnlinesArrayWord8Copy #-}
@@ -431,10 +432,10 @@ linesUnlinesArrayWord8Copy inh outh =
431432linesUnlinesArrayCharCopy :: Handle -> Handle -> IO ()
432433linesUnlinesArrayCharCopy inh outh =
433434 S.fold (FH.write outh)
434− $ SS.encodeChar8
435− $ IUS.unlines
436− $ IUS.lines
437− $ SS.decodeChar8
435+ $ SS.encodeLatin1
436+ $ IUA.unlines
437+ $ IUA.lines
438+ $ SS.decodeLatin1
438439 $ S.unfold FH.read inh
439440
440441#ifdef INSPECTION
@@ -451,10 +452,10 @@ inspect $ hasNoTypeClassesExcept 'linesUnlinesArrayCharCopy [''Storable]
451452linesUnlinesArrayUtf8Copy :: Handle -> Handle -> IO ()
452453linesUnlinesArrayUtf8Copy inh outh =
453454 S.fold (FH.write outh)
454− $ SS.encodeChar8
455+ $ SS.encodeLatin1
455456 $ IP.intercalate (A.fromList [10]) (pipe SS.decodeUtf8P A.read)
456457 $ S.splitOnSuffix (== '\n') (IFL.lmap SS.encodeUtf8 A.write)
457− $ SS.decodeChar8
458+ $ SS.decodeLatin1
458459 $ S.unfold FH.read inh
459460-}
460461
@@ -500,8 +501,8 @@ inspect $ hasNoTypeClasses 'wordsUnwordsCopyWord8
500501wordsUnwordsCopy :: Handle -> Handle -> IO ()
501502wordsUnwordsCopy inh outh =
502503 S.fold (FH.write outh)
503− $ SS.encodeChar8
504− $ SS.unfoldWords IUF.fromList
504+ $ SS.encodeLatin1
505+ $ IUS.unwords IUF.fromList
505506 -- XXX This pipeline does not fuse with wordsBy but fuses with splitOn
506507 -- with -funfolding-use-threshold=300. With wordsBy it does not fuse
507508 -- even with high limits for inlining and spec-constr ghc options. With
@@ -511,7 +512,7 @@ wordsUnwordsCopy inh outh =
511512 -- this case could be an unknown issue, need more investigation.
512513 $ S.wordsBy isSpace FL.toList
513514 -- -- $ S.splitOn isSpace FL.toList
514− $ SS.decodeChar8
515+ $ SS.decodeLatin1
515516 $ S.unfold FH.read inh
516517
517518#ifdef INSPECTION
@@ -525,10 +526,10 @@ wordsUnwordsCopy inh outh =
525526wordsUnwordsCharArrayCopy :: Handle -> Handle -> IO ()
526527wordsUnwordsCharArrayCopy inh outh =
527528 S.fold (FH.write outh)
528− $ SS.encodeChar8
529− $ IUS.unwords
530− $ IUS.words
531− $ SS.decodeChar8
529+ $ SS.encodeLatin1
530+ $ IUA.unwords
531+ $ IUA.words
532+ $ SS.decodeLatin1
532533 $ S.unfold FH.read inh
533534
534535lf :: Word8
src/Streamly/Data/Unicode/Stream.hs+23−26
@@ -8,25 +8,21 @@
88-- Stability : experimental
99-- Portability : GHC
1010--
11−-- A unicode string can be represented either as a stream of 'Char' e.g.
12−-- 'SerialT' 'Identity' 'Char' or as 'Array' 'Char'. Unicode text processing
13−-- can be done efficiently by applying stream operations and folds on the
14−-- stream of 'Char'. When using 'Array' 'Char' direct array operations can be
15−-- applied where available or the array can be read into a stream and then
16−-- processed using stream operations. Use 'Array' 'Char' when you need to store
17−-- or buffer strings temporarily in memory. Streams in 'Identity' monad and
18−-- 'Array' 'Char' are instances of 'IsString' and 'IsList', therefore,
11+-- A byte stream of Unicode text can be decoded into a stream of 'Char' and
12+-- processed efficiently using regular stream processing operations. A stream
13+-- of 'Char' can be encoded into a byte stream using UTF formats and written to
14+-- IO devices.
15+--
16+-- If you have to store a unicode string in memory you can either write it as a
17+-- pure stream "SerialT Identity Char" or write the 'Char' stream as "Array
18+-- Char". If space efficiency is a concern you can UTF8 encode the 'Char'
19+-- stream and then write it as "Array Word8". "SerialT Identity Char" and
20+-- "Array Char" are instances of 'IsString' and 'IsList', therefore,
1921-- 'OverloadedStrings' and 'OverloadedLists' extensions can be used for
2022-- convenience.
2123--
22−-- 'Array' 'Char' is usually perfectly fine to buffer short to medium or even
23−-- large amounts of text in memory. Also, it is computationally efficient as
24−-- there is no encoding/decoding involved. We recommend using true streaming
25−-- operations to avoid buffering large amounts of data as long as possible e.g.
26−-- use `foldLines` instead of `lines`. However, if for some reason you are
27−-- buffering very large amounts of text in memory and are worried about space
28−-- efficiency you can use 'encodeUtf8' on the stream to convert it to a utf8
29−-- encoded 'Array' 'Word8'.
24+-- Some experimental APIs to conveniently process text using "Array Char"
25+-- represenation can be found in "Streamly.Internal.Unicode.Array".
3026--
3127-- Please note the following:
3228--
@@ -43,13 +39,13 @@
4339module Streamly.Data.Unicode.Stream
4440 (
4541 -- * Construction (Decoding)
46− decodeChar8
42+ decodeLatin1
4743 , decodeUtf8
48− , decodeUtf8Lenient
44+ , decodeUtf8Lax
4945
5046 -- * Elimination (Encoding)
51− , encodeChar8
52− , encodeChar8Unchecked
47+ , encodeLatin1
48+ , encodeLatin1Lax
5349 , encodeUtf8
5450 {-
5551 -- * Operations on character strings
@@ -57,14 +53,15 @@ module Streamly.Data.Unicode.Stream
5753 , stripEnd
5854 , stripStart
5955 -}
60− -- * Transformation
61− , foldLines
62− , foldWords
63− , unfoldLines
64− , unfoldWords
56+ -- Not exposing these yet as we have consider these with respect to Unicode
57+ -- segmentation routines which are yet to be implemented.
58+ -- -- * Transformation
59+ -- , lines
60+ -- , words
61+ -- , unlines
62+ -- , unwords
6563 )
6664where
6765
6866import Streamly.Internal.Data.Unicode.Stream
6967import Prelude hiding (lines, words, unlines, unwords)
70−import qualified Streamly.Streams.StreamD as D
src/Streamly/Internal/Data/Unicode/Char.hsnew file+49
@@ -0,0 +1,49 @@
1+{-# LANGUAGE FlexibleContexts #-}
2+-- |
3+-- Module : Streamly.Data.Internal.Unicode.Char
4+-- Copyright : (c) 2018 Composewell Technologies
5+--
6+-- License : BSD3
7+-- Maintainer : streamly@composewell.com
8+-- Stability : experimental
9+-- Portability : GHC
10+--
11+module Streamly.Internal.Data.Unicode.Char
12+ (
13+ -- * Unicode aware operations
14+ {-
15+ toCaseFold
16+ , toLower
17+ , toUpper
18+ , toTitle
19+ -}
20+ )
21+where
22+
23+-- import Streamly (IsStream)
24+
25+-------------------------------------------------------------------------------
26+-- Unicode aware operations on strings
27+-------------------------------------------------------------------------------
28+
29+{-
30+-- |
31+-- /undefined/
32+toCaseFold :: IsStream t => Char -> t m Char
33+toCaseFold = undefined
34+
35+-- |
36+-- /undefined/
37+toLower :: IsStream t => Char -> t m Char
38+toLower = undefined
39+
40+-- |
41+-- /undefined/
42+toUpper :: IsStream t => Char -> t m Char
43+toUpper = undefined
44+
45+-- |
46+-- /undefined/
47+toTitle :: IsStream t => Char -> t m Char
48+toTitle = undefined
49+-}
src/Streamly/Internal/Data/Unicode/Stream.hs+68−137
@@ -11,9 +11,9 @@
1111module Streamly.Internal.Data.Unicode.Stream
1212 (
1313 -- * Construction (Decoding)
14− decodeChar8
14+ decodeLatin1
1515 , decodeUtf8
16− , decodeUtf8Lenient
16+ , decodeUtf8Lax
1717 , D.DecodeError(..)
1818 , D.DecodeState
1919 , D.CodePoint
@@ -23,27 +23,16 @@ module Streamly.Internal.Data.Unicode.Stream
2323 , decodeUtf8ArraysLenient
2424
2525 -- * Elimination (Encoding)
26− , encodeChar8
27− , encodeChar8Unchecked
26+ , encodeLatin1
27+ , encodeLatin1Lax
2828 , encodeUtf8
29−{-
30− -- * Unicode aware operations
31− , toCaseFold
32− , toLower
33− , toUpper
34− , toTitle
35−
29+ {-
3630 -- * Operations on character strings
3731 , strip -- (dropAround isSpace)
38− , stripEnd-}
32+ , stripEnd
33+ -}
3934 -- * Transformation
4035 , stripStart
41− , foldLines
42− , foldWords
43− , unfoldLines
44− , unfoldWords
45−
46− -- * Streams of Strings
4736 , lines
4837 , words
4938 , unlines
@@ -55,62 +44,63 @@ import Control.Monad.IO.Class (MonadIO)
5544import Data.Char (ord)
5645import Data.Word (Word8)
5746import GHC.Base (unsafeChr)
58−import Streamly (IsStream, MonadAsync)
47+import Streamly (IsStream)
5948import Prelude hiding (String, lines, words, unlines, unwords)
6049import Streamly.Data.Fold (Fold)
6150import Streamly.Memory.Array (Array)
6251import Streamly.Internal.Data.Unfold (Unfold)
6352
6453import qualified Streamly.Internal.Prelude as S
65−import qualified Streamly.Memory.Array as A
6654import qualified Streamly.Streams.StreamD as D
6755
68−-- type String = List Char
69−
70−-------------------------------------------------------------------------------
71−-- Encoding/Decoding Characters
72−-------------------------------------------------------------------------------
73−
74−-- decodeWith :: TextEncoding -> t m Word8 -> t m Char
75−-- decodeWith = undefined
76−
7756-------------------------------------------------------------------------------
7857-- Encoding/Decoding Unicode Characters
7958-------------------------------------------------------------------------------
8059
8160-- | Decode a stream of bytes to Unicode characters by mapping each byte to a
8261-- corresponding Unicode 'Char' in 0-255 range.
83−{-# INLINE decodeChar8 #-}
84−decodeChar8 :: (IsStream t, Monad m) => t m Word8 -> t m Char
85−decodeChar8 = S.map (unsafeChr . fromIntegral)
62+--
63+-- /Since: 0.7.0/
64+{-# INLINE decodeLatin1 #-}
65+decodeLatin1 :: (IsStream t, Monad m) => t m Word8 -> t m Char
66+decodeLatin1 = S.map (unsafeChr . fromIntegral)
8667
8768-- | Encode a stream of Unicode characters to bytes by mapping each character
8869-- to a byte in 0-255 range. Throws an error if the input stream contains
8970-- characters beyond 255.
90−{-# INLINE encodeChar8 #-}
91−encodeChar8 :: (IsStream t, Monad m) => t m Char -> t m Word8
92−encodeChar8 = S.map convert
71+--
72+-- /Since: 0.7.0/
73+{-# INLINE encodeLatin1 #-}
74+encodeLatin1 :: (IsStream t, Monad m) => t m Char -> t m Word8
75+encodeLatin1 = S.map convert
9376 where
9477 convert c =
9578 let codepoint = ord c
9679 in if codepoint > 255
97− then error $ "Streamly.String.encodeChar8 invalid \
80+ then error $ "Streamly.String.encodeLatin1 invalid \
9881 \input char codepoint " ++ show codepoint
9982 else fromIntegral codepoint
10083
101−-- | Like 'encodeChar8' but silently truncates and maps input characters beyond
84+-- | Like 'encodeLatin1' but silently truncates and maps input characters beyond
10285-- 255 to (incorrect) chars in 0-255 range. No error or exception is thrown
10386-- when such truncation occurs.
104−{-# INLINE encodeChar8Unchecked #-}
105−encodeChar8Unchecked :: (IsStream t, Monad m) => t m Char -> t m Word8
106−encodeChar8Unchecked = S.map (fromIntegral . ord)
87+--
88+-- /Since: 0.7.0/
89+{-# INLINE encodeLatin1Lax #-}
90+encodeLatin1Lax :: (IsStream t, Monad m) => t m Char -> t m Word8
91+encodeLatin1Lax = S.map (fromIntegral . ord)
10792
10893-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
10994-- The incoming stream is truncated if an invalid codepoint is encountered.
95+--
96+-- /Since: 0.7.0/
11097{-# INLINE decodeUtf8 #-}
11198decodeUtf8 :: (Monad m, IsStream t) => t m Word8 -> t m Char
11299decodeUtf8 = D.fromStreamD . D.decodeUtf8 . D.toStreamD
113100
101+-- |
102+--
103+-- /Internal/
114104{-# INLINE decodeUtf8Arrays #-}
115105decodeUtf8Arrays :: (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
116106decodeUtf8Arrays = D.fromStreamD . D.decodeUtf8Arrays . D.toStreamD
@@ -118,15 +108,23 @@ decodeUtf8Arrays = D.fromStreamD . D.decodeUtf8Arrays . D.toStreamD
118108-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
119109-- Any invalid codepoint encountered is replaced with the unicode replacement
120110-- character.
121−{-# INLINE decodeUtf8Lenient #-}
122−decodeUtf8Lenient :: (Monad m, IsStream t) => t m Word8 -> t m Char
123−decodeUtf8Lenient = D.fromStreamD . D.decodeUtf8Lenient . D.toStreamD
111+--
112+-- /Since: 0.7.0/
113+{-# INLINE decodeUtf8Lax #-}
114+decodeUtf8Lax :: (Monad m, IsStream t) => t m Word8 -> t m Char
115+decodeUtf8Lax = D.fromStreamD . D.decodeUtf8Lenient . D.toStreamD
124116
117+-- |
118+--
119+-- /Internal/
125120{-# INLINE decodeUtf8Either #-}
126121decodeUtf8Either :: (Monad m, IsStream t)
127122 => t m Word8 -> t m (Either D.DecodeError Char)
128123decodeUtf8Either = D.fromStreamD . D.decodeUtf8Either . D.toStreamD
129124
125+-- |
126+--
127+-- /Internal/
130128{-# INLINE resumeDecodeUtf8Either #-}
131129resumeDecodeUtf8Either
132130 :: (Monad m, IsStream t)
@@ -137,6 +135,9 @@ resumeDecodeUtf8Either
137135resumeDecodeUtf8Either st cp =
138136 D.fromStreamD . D.resumeDecodeUtf8Either st cp . D.toStreamD
139137
138+-- |
139+--
140+-- /Internal/
140141{-# INLINE decodeUtf8ArraysLenient #-}
141142decodeUtf8ArraysLenient ::
142143 (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
@@ -144,27 +145,13 @@ decodeUtf8ArraysLenient =
144145 D.fromStreamD . D.decodeUtf8ArraysLenient . D.toStreamD
145146
146147-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream.
148+--
149+-- /Since: 0.7.0/
147150{-# INLINE encodeUtf8 #-}
148151encodeUtf8 :: (Monad m, IsStream t) => t m Char -> t m Word8
149152encodeUtf8 = D.fromStreamD . D.encodeUtf8 . D.toStreamD
150153
151154{-
152−-------------------------------------------------------------------------------
153−-- Unicode aware operations on strings
154−-------------------------------------------------------------------------------
155−
156−toCaseFold :: IsStream t => t m Char -> t m Char
157−toCaseFold = undefined
158−
159−toLower :: IsStream t => t m Char -> t m Char
160−toLower = undefined
161−
162−toUpper :: IsStream t => t m Char -> t m Char
163−toUpper = undefined
164−
165−toTitle :: IsStream t => t m Char -> t m Char
166−toTitle = undefined
167−
168155-------------------------------------------------------------------------------
169156-- Utility operations on strings
170157-------------------------------------------------------------------------------
@@ -179,6 +166,8 @@ stripEnd = undefined
179166-- | Remove leading whitespace from a string.
180167--
181168-- > stripStart = S.dropWhile isSpace
169+--
170+-- /Internal/
182171{-# INLINE stripStart #-}
183172stripStart :: (Monad m, IsStream t) => t m Char -> t m Char
184173stripStart = S.dropWhile isSpace
@@ -186,26 +175,15 @@ stripStart = S.dropWhile isSpace
186175-- | Fold each line of the stream using the supplied 'Fold'
187176-- and stream the result.
188177--
189−-- >>> S.toList $ foldLines FL.toList (S.fromList "lines\nthis\nstring\n\n\n")
178+-- >>> S.toList $ lines FL.toList (S.fromList "lines\nthis\nstring\n\n\n")
190179-- ["lines", "this", "string", "", ""]
191180--
192−-- > foldLines = S.splitOnSuffix (== '\n')
193−--
194−{-# INLINE foldLines #-}
195−foldLines :: (Monad m, IsStream t) => Fold m Char b -> t m Char -> t m b
196−foldLines = S.splitOnSuffix (== '\n')
197−
198−-- | Fold each word of the stream using the supplied 'Fold'
199−-- and stream the result.
200−--
201−-- >>> S.toList $ foldWords FL.toList (S.fromList "fold these words")
202−-- ["fold", "these", "words"]
203−--
204−-- > foldWords = S.wordsBy isSpace
181+-- > lines = S.splitOnSuffix (== '\n')
205182--
206−{-# INLINE foldWords #-}
207−foldWords :: (Monad m, IsStream t) => Fold m Char b -> t m Char -> t m b
208−foldWords = S.wordsBy isSpace
183+-- /Internal/
184+{-# INLINE lines #-}
185+lines :: (Monad m, IsStream t) => Fold m Char b -> t m Char -> t m b
186+lines = S.splitOnSuffix (== '\n')
209187
210188foreign import ccall unsafe "u_iswspace"
211189 iswspace :: Int -> Int
@@ -219,79 +197,32 @@ isSpace c
219197 where
220198 uc = fromIntegral (ord c) :: Word
221199
222−-- | Break a string up into a stream of strings at newline characters.
223−-- The resulting strings do not contain newlines.
224−--
225−-- > lines = foldLines A.write
226−--
227−-- >>> S.toList $ lines $ S.fromList "lines\nthis\nstring\n\n\n"
228−-- ["lines","this","string","",""]
229−--
230−-- If you're dealing with lines of massive length, consider using
231−-- 'foldLines' instead to avoid buffering the data in 'Array'.
232−{-# INLINE lines #-}
233−lines :: (MonadIO m, IsStream t) => t m Char -> t m (Array Char)
234−lines = foldLines A.write
235−
236−-- | Break a string up into a stream of strings, which were delimited
237−-- by characters representing white space.
200+-- | Fold each word of the stream using the supplied 'Fold'
201+-- and stream the result.
238202--
239−-- > words = foldWords A.write
203+-- >>> S.toList $ words FL.toList (S.fromList "fold these words")
204+-- ["fold", "these", "words"]
240205--
241−-- >>> S.toList $ words $ S.fromList "A newline\nis considered white space?"
242−-- ["A", "newline", "is", "considered", "white", "space?"]
206+-- > words = S.wordsBy isSpace
243207--
244−-- If you're dealing with words of massive length, consider using
245−-- 'foldWords' instead to avoid buffering the data in 'Array'.
208+-- /Internal/
246209{-# INLINE words #-}
247−words :: (MonadIO m, IsStream t) => t m Char -> t m (Array Char)
248−words = foldWords A.write
210+words :: (Monad m, IsStream t) => Fold m Char b -> t m Char -> t m b
211+words = S.wordsBy isSpace
249212
250213-- | Unfold a stream to character streams using the supplied 'Unfold'
251214-- and concat the results suffixing a newline character @\\n@ to each stream.
252215--
253−{-# INLINE unfoldLines #-}
254−unfoldLines :: (MonadIO m, IsStream t) => Unfold m a Char -> t m a -> t m Char
255−unfoldLines = S.interposeSuffix '\n'
256−
257−-- | Flattens the stream of @Array Char@, after appending a terminating
258−-- newline to each string.
259−--
260−-- 'unlines' is an inverse operation to 'lines'.
261−--
262−-- >>> S.toList $ unlines $ S.fromList ["lines", "this", "string"]
263−-- "lines\nthis\nstring\n"
264−--
265−-- > unlines = unfoldLines A.read
266−--
267−-- Note that, in general
268−--
269−-- > unlines . lines /= id
216+-- /Internal/
270217{-# INLINE unlines #-}
271−unlines :: (MonadIO m, IsStream t) => t m (Array Char) -> t m Char
272−unlines = unfoldLines A.read
218+unlines :: (MonadIO m, IsStream t) => Unfold m a Char -> t m a -> t m Char
219+unlines = S.interposeSuffix '\n'
273220
274221-- | Unfold the elements of a stream to character streams using the supplied
275222-- 'Unfold' and concat the results with a whitespace character infixed between
276223-- the streams.
277224--
278−{-# INLINE unfoldWords #-}
279−unfoldWords :: (MonadIO m, IsStream t) => Unfold m a Char -> t m a -> t m Char
280−unfoldWords = S.interpose ' '
281−
282−-- | Flattens the stream of @Array Char@, after appending a separating
283−-- space to each string.
284−--
285−-- 'unwords' is an inverse operation to 'words'.
286−--
287−-- >>> S.toList $ unwords $ S.fromList ["unwords", "this", "string"]
288−-- "unwords this string"
289−--
290−-- > unwords = unfoldWords A.read
291−--
292−-- Note that, in general
293−--
294−-- > unwords . words /= id
225+-- /Internal/
295226{-# INLINE unwords #-}
296−unwords :: (MonadAsync m, IsStream t) => t m (Array Char) -> t m Char
297−unwords = unfoldWords A.read
227+unwords :: (MonadIO m, IsStream t) => Unfold m a Char -> t m a -> t m Char
228+unwords = S.interpose ' '
src/Streamly/Internal/Memory/Unicode/Array.hsnew file+85
@@ -0,0 +1,85 @@
1+{-# LANGUAGE FlexibleContexts #-}
2+-- |
3+-- Module : Streamly.Memory.Internal.Unicode.Array
4+-- Copyright : (c) 2018 Composewell Technologies
5+--
6+-- License : BSD3
7+-- Maintainer : streamly@composewell.com
8+-- Stability : experimental
9+-- Portability : GHC
10+--
11+module Streamly.Internal.Memory.Unicode.Array
12+ (
13+ -- * Streams of Strings
14+ lines
15+ , words
16+ , unlines
17+ , unwords
18+ )
19+where
20+
21+import Control.Monad.IO.Class (MonadIO)
22+import Streamly (IsStream, MonadAsync)
23+import Prelude hiding (String, lines, words, unlines, unwords)
24+import Streamly.Memory.Array (Array)
25+
26+import qualified Streamly.Internal.Data.Unicode.Stream as S
27+import qualified Streamly.Memory.Array as A
28+
29+-- | Break a string up into a stream of strings at newline characters.
30+-- The resulting strings do not contain newlines.
31+--
32+-- > lines = S.lines A.write
33+--
34+-- >>> S.toList $ lines $ S.fromList "lines\nthis\nstring\n\n\n"
35+-- ["lines","this","string","",""]
36+--
37+{-# INLINE lines #-}
38+lines :: (MonadIO m, IsStream t) => t m Char -> t m (Array Char)
39+lines = S.lines A.write
40+
41+-- | Break a string up into a stream of strings, which were delimited
42+-- by characters representing white space.
43+--
44+-- > words = S.words A.write
45+--
46+-- >>> S.toList $ words $ S.fromList "A newline\nis considered white space?"
47+-- ["A", "newline", "is", "considered", "white", "space?"]
48+--
49+{-# INLINE words #-}
50+words :: (MonadIO m, IsStream t) => t m Char -> t m (Array Char)
51+words = S.words A.write
52+
53+-- | Flattens the stream of @Array Char@, after appending a terminating
54+-- newline to each string.
55+--
56+-- 'unlines' is an inverse operation to 'lines'.
57+--
58+-- >>> S.toList $ unlines $ S.fromList ["lines", "this", "string"]
59+-- "lines\nthis\nstring\n"
60+--
61+-- > unlines = S.unlines A.read
62+--
63+-- Note that, in general
64+--
65+-- > unlines . lines /= id
66+{-# INLINE unlines #-}
67+unlines :: (MonadIO m, IsStream t) => t m (Array Char) -> t m Char
68+unlines = S.unlines A.read
69+
70+-- | Flattens the stream of @Array Char@, after appending a separating
71+-- space to each string.
72+--
73+-- 'unwords' is an inverse operation to 'words'.
74+--
75+-- >>> S.toList $ unwords $ S.fromList ["unwords", "this", "string"]
76+-- "unwords this string"
77+--
78+-- > unwords = S.unwords A.read
79+--
80+-- Note that, in general
81+--
82+-- > unwords . words /= id
83+{-# INLINE unwords #-}
84+unwords :: (MonadAsync m, IsStream t) => t m (Array Char) -> t m Char
85+unwords = S.unwords A.read
streamly.cabal+2
@@ -313,6 +313,8 @@ library
313313 , Streamly.Internal.FileSystem.Handle
314314 , Streamly.Internal.FileSystem.File
315315 , Streamly.Internal.Data.Unicode.Stream
316+ , Streamly.Internal.Data.Unicode.Char
317+ , Streamly.Internal.Memory.Unicode.Array
316318 , Streamly.Internal.Prelude
317319 if !impl(ghcjs)
318320 exposed-modules:
test/String.hs+9−8
@@ -17,6 +17,7 @@ import qualified Streamly.Internal.Memory.ArrayStream as AS
1717import qualified Streamly.Prelude as S
1818import qualified Streamly.Data.Unicode.Stream as SS
1919import qualified Streamly.Internal.Data.Unicode.Stream as IUS
20+import qualified Streamly.Internal.Memory.Unicode.Array as IUA
2021
2122-- Coverage build takes too long with default number of tests
2223{-
@@ -49,7 +50,7 @@ propDecodeEncodeIdLenient =
4950 forAll genUnicode $ \list ->
5051 monadicIO $ do
5152 let wrds = SS.encodeUtf8 $ S.fromList list
52− chrs <- S.toList $ SS.decodeUtf8Lenient wrds
53+ chrs <- S.toList $ SS.decodeUtf8Lax wrds
5354 assert (chrs == list)
5455
5556propDecodeEncodeIdArrays :: Property
@@ -67,7 +68,7 @@ testLines =
6768 monadicIO $ do
6869 xs <- S.toList
6970 $ S.map A.toList
70− $ IUS.lines
71+ $ IUA.lines
7172 $ S.fromList list
7273 assert (xs == lines list)
7374
@@ -88,7 +89,7 @@ testWords =
8889 monadicIO $ do
8990 xs <- S.toList
9091 $ S.map A.toList
91− $ IUS.words
92+ $ IUA.words
9293 $ S.fromList list
9394 assert (xs == words list)
9495
@@ -97,8 +98,8 @@ testUnlines =
9798 forAll genUnicode $ \list ->
9899 monadicIO $ do
99100 xs <- S.toList
100− $ IUS.unlines
101− $ IUS.lines
101+ $ IUA.unlines
102+ $ IUA.lines
102103 $ S.fromList list
103104 assert (xs == unlines (lines list))
104105
@@ -108,8 +109,8 @@ testUnwords =
108109 monadicIO $ do
109110 xs <- run
110111 $ S.toList
111− $ IUS.unwords
112− $ IUS.words
112+ $ IUA.unwords
113+ $ IUA.words
113114 $ S.fromList list
114115 assert (xs == unwords (words list))
115116
@@ -120,7 +121,7 @@ main = hspec
120121 $ do
121122 describe "UTF8 - Encoding / Decoding" $ do
122123 prop "decodeUtf8 . encodeUtf8 == id" $ propDecodeEncodeId
123− prop "decodeUtf8Lenient . encodeUtf8 == id" $ propDecodeEncodeIdLenient
124+ prop "decodeUtf8Lax . encodeUtf8 == id" $ propDecodeEncodeIdLenient
124125 prop "decodeUtf8ArraysLenient . encodeUtf8 == id"
125126 $ propDecodeEncodeIdArrays
126127 prop "Streamly.Data.String.lines == Prelude.lines" $ testLines