on master help sign in
Add new encoding/decoding routines.

- Add encodeUtf8_, encodeLatin1_, encodeUtf8', encodeLatin1', decodeUtf8_, decodeUtf8', decodeUtf8Arrays_, decodeUtf8Arrays'. - encodeLatin1, encodeUtf8 don't fail when they encounter invalid characters. Their stricter variants encodeLatin1', encodeUtf8' do. - encodeUtf8_, encodeLatin1_ drop the invalid characters. - decodeUtf8 replaces any invalid character encountered with the Unicode replacement character where as decodeUtf8' fails. - decodeUtf8Arrays doesn't fail on invalid characters anymore it replaces them with the Unicode replacement character. - Deprecate decodeUtf8Lax, encodeLatin1Lax, encodeUtf8Lax. - Update Changelog.md.

commit25f76d5524b5ca2ed1c54d51333e0156a10ebeee authorpranaysashank <pranaysashank@composewell.com> dateMon 14 Sep 2020, 15:54 (6 years ago) parentad05792
12 files changed+249−116
Changelog.md+18−3 benchmark/Streamly/Benchmark/FileSystem/Handle/Read.hs+14−14 benchmark/Streamly/Benchmark/FileSystem/Handle/ReadWrite.hs+21−21 examples/FileSinkServer.hs+1−1 examples/WordCount.hs+1−1 src/Streamly/Data/Unicode/Stream.hs+7−5 src/Streamly/Internal/FileSystem/Event/Darwin.hs+1−1 src/Streamly/Internal/FileSystem/Event/Linux.hs+1−1 src/Streamly/Internal/Unicode/Stream.hs+168−52 src/Streamly/Unicode/Stream.hs+4−4 test/Streamly/Test/FileSystem/Event.hs+1−1 test/String.hs+12−12
Changelog.md+18−3
@@ -1,5 +1,10 @@
11## Unreleased
22
3+### Enhancements
4+
5+* New encoding/decoding routines, `encodeUtf8'`, `encodeLatin1'`, `decodeUtf8'`,
6+ are added, these routines fail when they encounter any invalid characters.
7+
38### Breaking changes
49
510* Deprecate `Streamly.Memory.Array` in favor of `Streamly.Data.Array.Storable.Foreign`
@@ -14,6 +19,12 @@
1419 constraint.
1520* Change the associativity of combinators `serial`, `wSerial`,
1621 `ahead`, `async`, `wAsync`, `parallel` to be the same as `<>`.
22+* `encodeUtf8`, `decodeUtf8` now replace any invalid character encountered
23+ during encoding/decoding with the Unicode replacement character. Use
24+ `encodeUtf8'`, `decodeUtf8'` to recover the previous functionality.
25+* `encodeLatin1` now silently truncates any character beyond 255 to incorrect
26+ characters in the input stream. Use `encodeLatin1'` to recover previous
27+ functionality.
1728* Drop support for GHC 7.10.3.
1829
1930### Bug Fixes
@@ -33,9 +44,13 @@
3344* The `Streamly` module is now deprecated, its functionality is subsumed
3445 by `Streamly.Prelude`.
3546* Some functions from `Streamly` module have been renamed in `Streamly.Prelude` module:
36− * `foldWith` has been replaced by `concatFoldableWith`
37− * `foldMapWith` has been replaced by `concatMapFoldableWith`
38− * `forEachWith` has been replaced by `concatForFoldableWith`
47+ * `foldWith` to `concatFoldableWith`
48+ * `foldMapWith` to `concatMapFoldableWith`
49+ * `forEachWith` to `concatForFoldableWith`
50+* The following encoding/decoding routines have been renamed:
51+ * `encodeUtf8Lax` to `encodeUtf8`
52+ * `encodeLatin1Lax` to `encodeLatin1`
53+ * `decodeUtf8Lenient` to `decodeUtf8`
3954
4055## 0.7.2
4156
benchmark/Streamly/Benchmark/FileSystem/Handle/Read.hs+14−14
@@ -120,12 +120,12 @@ inspect $ hasNoTypeClasses 'toChunksCountBytes
120120inspect $ 'toChunksCountBytes `hasNoType` ''Step
121121#endif
122122
123−toChunksDecodeUtf8ArraysLenient :: Handle -> IO ()
124−toChunksDecodeUtf8ArraysLenient =
125− S.drain . IUS.decodeUtf8ArraysLenient . IFH.toChunks
123+toChunksDecodeUtf8Arrays :: Handle -> IO ()
124+toChunksDecodeUtf8Arrays =
125+ S.drain . IUS.decodeUtf8Arrays . IFH.toChunks
126126
127127#ifdef INSPECTION
128−inspect $ hasNoTypeClasses 'toChunksDecodeUtf8ArraysLenient
128+inspect $ hasNoTypeClasses 'toChunksDecodeUtf8Arrays
129129-- inspect $ 'toChunksDecodeUtf8ArraysLenient `hasNoType` ''Step
130130#endif
131131
@@ -146,8 +146,8 @@ o_1_space_read_chunked env =
146146 toChunksSplitOn inH
147147 , mkBench "countBytes" env $ \inH _ ->
148148 toChunksCountBytes inH
149− , mkBenchSmall "US.decodeUtf8ArraysLenient" env $ \inH _ ->
150− toChunksDecodeUtf8ArraysLenient inH
149+ , mkBenchSmall "US.decodeUtf8Arrays" env $ \inH _ ->
150+ toChunksDecodeUtf8Arrays inH
151151 ]
152152 ]
153153
@@ -240,14 +240,14 @@ readDecodeLatin1 inh =
240240 $ SS.decodeLatin1
241241 $ S.unfold FH.read inh
242242
243−readDecodeUtf8Lax :: Handle -> IO ()
244−readDecodeUtf8Lax inh =
243+readDecodeUtf8 :: Handle -> IO ()
244+readDecodeUtf8 inh =
245245 S.drain
246− $ SS.decodeUtf8Lax
246+ $ SS.decodeUtf8
247247 $ S.unfold FH.read inh
248248
249249#ifdef INSPECTION
250−inspect $ hasNoTypeClasses 'readDecodeUtf8Lax
250+inspect $ hasNoTypeClasses 'readDecodeUtf8
251251-- inspect $ 'readDecodeUtf8Lax `hasNoType` ''Step
252252#endif
253253
@@ -273,8 +273,8 @@ o_1_space_reduce_read env =
273273 readCountWords inh
274274
275275 -- read with utf8 decoding
276− , mkBenchSmall "SS.decodeUtf8Lax" env $ \inh _ ->
277− readDecodeUtf8Lax inh
276+ , mkBenchSmall "SS.decodeUtf8" env $ \inh _ ->
277+ readDecodeUtf8 inh
278278 ]
279279 ]
280280
@@ -505,14 +505,14 @@ o_1_space_reduce_read_split env =
505505splitOnSeqUtf8 :: String -> Handle -> IO Int
506506splitOnSeqUtf8 str inh =
507507 (S.length $ IP.splitOnSeq (A.fromList str) FL.drain
508− $ IUS.decodeUtf8ArraysLenient
508+ $ IUS.decodeUtf8Arrays
509509 $ IFH.toChunks inh) -- >>= print
510510
511511o_1_space_reduce_toChunks_split :: BenchEnv -> [Benchmark]
512512o_1_space_reduce_toChunks_split env =
513513 [ bgroup "reduce/toChunks"
514514 [ mkBenchSmall ("S.splitOnSeqUtf8 \"abcdefgh\" FL.drain "
515− ++ ". US.decodeUtf8ArraysLenient") env $ \inh _ ->
515+ ++ ". US.decodeUtf8Arrays") env $ \inh _ ->
516516 splitOnSeqUtf8 "abcdefgh" inh
517517 , mkBenchSmall "S.splitOnSeqUtf8 \"abcdefghijklmnopqrstuvwxyz\" FL.drain"
518518 env $ \inh _ -> splitOnSeqUtf8 "abcdefghijklmnopqrstuvwxyz" inh
benchmark/Streamly/Benchmark/FileSystem/Handle/ReadWrite.hs+21−21
@@ -71,8 +71,8 @@ inspect $ 'copyChunks `hasNoType` ''Step
7171copyCodecUtf8ArraysLenient :: Handle -> Handle -> IO ()
7272copyCodecUtf8ArraysLenient inh outh =
7373 S.fold (FH.write outh)
74− $ SS.encodeUtf8
75− $ IUS.decodeUtf8ArraysLenient
74+ $ SS.encodeUtf8'
75+ $ IUS.decodeUtf8Arrays
7676 $ IFH.toChunks inh
7777
7878#ifdef INSPECTION
@@ -194,7 +194,7 @@ inspect $ 'copyStream `hasNoType` ''Strict.Tuple3' -- FH.write/lchunksOf
194194copyStreamLatin1 :: Handle -> Handle -> IO ()
195195copyStreamLatin1 inh outh =
196196 S.fold (FH.write outh)
197− $ SS.encodeLatin1
197+ $ SS.encodeLatin1'
198198 $ SS.decodeLatin1
199199 $ S.unfold FH.read inh
200200
@@ -208,30 +208,30 @@ inspect $ 'copyStreamLatin1 `hasNoType` ''Strict.Tuple3' -- FH.write/lchunksOf
208208#endif
209209
210210-- | Copy file
211−_copyStreamUtf8 :: Handle -> Handle -> IO ()
212−_copyStreamUtf8 inh outh =
211+_copyStreamUtf8' :: Handle -> Handle -> IO ()
212+_copyStreamUtf8' inh outh =
213213 S.fold (FH.write outh)
214− $ SS.encodeUtf8
215− $ SS.decodeUtf8
214+ $ SS.encodeUtf8'
215+ $ SS.decodeUtf8'
216216 $ S.unfold FH.read inh
217217
218218#ifdef INSPECTION
219−inspect $ hasNoTypeClasses '_copyStreamUtf8
219+inspect $ hasNoTypeClasses '_copyStreamUtf8'
220220-- inspect $ '_copyStreamUtf8 `hasNoType` ''Step
221221-- inspect $ '_copyStreamUtf8 `hasNoType` ''AT.FlattenState
222222-- inspect $ '_copyStreamUtf8 `hasNoType` ''D.ConcatMapUState
223223#endif
224224
225225-- | Copy file
226−copyStreamUtf8Lax :: Handle -> Handle -> IO ()
227−copyStreamUtf8Lax inh outh =
226+copyStreamUtf8 :: Handle -> Handle -> IO ()
227+copyStreamUtf8 inh outh =
228228 S.fold (FH.write outh)
229229 $ SS.encodeUtf8
230− $ SS.decodeUtf8Lax
230+ $ SS.decodeUtf8
231231 $ S.unfold FH.read inh
232232
233233#ifdef INSPECTION
234−inspect $ hasNoTypeClasses 'copyStreamUtf8Lax
234+inspect $ hasNoTypeClasses 'copyStreamUtf8
235235-- inspect $ 'copyStreamUtf8Lax `hasNoType` ''Step
236236-- inspect $ 'copyStreamUtf8Lax `hasNoType` ''AT.FlattenState
237237-- inspect $ 'copyStreamUtf8Lax `hasNoType` ''D.ConcatMapUState
@@ -245,14 +245,14 @@ o_1_space_copy_read env =
245245 , mkBench "rawToFile" env $ \inh outh ->
246246 copyStream inh outh
247247 -- This needs an ascii file, as decode just errors out.
248− , mkBench "SS.encodeLatin1 . SS.decodeLatin1" env $ \inh outh ->
248+ , mkBench "SS.encodeLatin1' . SS.decodeLatin1" env $ \inh outh ->
249249 copyStreamLatin1 inh outh
250250#ifdef DEVBUILD
251251 , mkBench "copyUtf8" env $ \inh outh ->
252− _copyStreamUtf8 inh outh
252+ _copyStreamUtf8' inh outh
253253#endif
254254 , mkBenchSmall "SS.encodeUtf8 . SS.decodeUtf8Lax" env $ \inh outh ->
255− copyStreamUtf8Lax inh outh
255+ copyStreamUtf8 inh outh
256256 ]
257257 ]
258258
@@ -471,7 +471,7 @@ o_1_space_copy_toChunks_group_ungroup env =
471471linesUnlinesCopy :: Handle -> Handle -> IO ()
472472linesUnlinesCopy inh outh =
473473 S.fold (FH.write outh)
474− $ SS.encodeLatin1
474+ $ SS.encodeLatin1'
475475 $ IUS.unlines IUF.fromList
476476 $ S.splitOnSuffix (== '\n') FL.toList
477477 $ SS.decodeLatin1
@@ -489,7 +489,7 @@ linesUnlinesArrayWord8Copy inh outh =
489489linesUnlinesArrayCharCopy :: Handle -> Handle -> IO ()
490490linesUnlinesArrayCharCopy inh outh =
491491 S.fold (FH.write outh)
492− $ SS.encodeLatin1
492+ $ SS.encodeLatin1'
493493 $ IUA.unlines
494494 $ IUA.lines
495495 $ SS.decodeLatin1
@@ -507,9 +507,9 @@ inspect $ hasNoTypeClassesExcept 'linesUnlinesArrayCharCopy [''Storable]
507507linesUnlinesArrayUtf8Copy :: Handle -> Handle -> IO ()
508508linesUnlinesArrayUtf8Copy inh outh =
509509 S.fold (FH.write outh)
510− $ SS.encodeLatin1
510+ $ SS.encodeLatin1'
511511 $ IP.intercalate (A.fromList [10]) (pipe SS.decodeUtf8P A.read)
512− $ S.splitOnSuffix (== '\n') (IFL.lmap SS.encodeUtf8 A.write)
512+ $ S.splitOnSuffix (== '\n') (IFL.lmap SS.encodeUtf8' A.write)
513513 $ SS.decodeLatin1
514514 $ S.unfold FH.read inh
515515-}
@@ -531,7 +531,7 @@ inspect $ hasNoTypeClasses 'wordsUnwordsCopyWord8
531531wordsUnwordsCopy :: Handle -> Handle -> IO ()
532532wordsUnwordsCopy inh outh =
533533 S.fold (FH.write outh)
534− $ SS.encodeLatin1
534+ $ SS.encodeLatin1'
535535 $ IUS.unwords IUF.fromList
536536 -- XXX This pipeline does not fuse with wordsBy but fuses with splitOn
537537 -- with -funfolding-use-threshold=300. With wordsBy it does not fuse
@@ -553,7 +553,7 @@ wordsUnwordsCopy inh outh =
553553wordsUnwordsCharArrayCopy :: Handle -> Handle -> IO ()
554554wordsUnwordsCharArrayCopy inh outh =
555555 S.fold (FH.write outh)
556− $ SS.encodeLatin1
556+ $ SS.encodeLatin1'
557557 $ IUA.unwords
558558 $ IUA.words
559559 $ SS.decodeLatin1
examples/FileSinkServer.hs+1−1
@@ -23,7 +23,7 @@ main = do
2323 file <- fmap head getArgs
2424 withFile file AppendMode
2525 (\src -> S.fold (FH.write src)
26− $ encodeLatin1Lax
26+ $ encodeLatin1
2727 $ S.concatUnfold A.read
2828 $ S.concatMapWith S.parallel use
2929 $ S.unfold TCP.acceptOnPort 8090)
examples/WordCount.hs+1−1
@@ -335,7 +335,7 @@ countCharSerial (Counts l w c wasSpace) ch =
335335_wc_mwl_serial :: Handle -> IO ()
336336_wc_mwl_serial src = print =<< (
337337 S.foldl' countCharSerial (Counts 0 0 0 True)
338− $ S.decodeUtf8Lax
338+ $ S.decodeUtf8
339339 $ S.unfold FH.read src)
340340
341341-------------------------------------------------------------------------------
src/Streamly/Data/Unicode/Stream.hs+7−5
@@ -33,7 +33,7 @@
3333-- stream as @Array Char@ using the array 'Streamly.Data.Array.Storable.Foreign.write' fold.
3434-- The 'Array' type provides a more compact representation and pinned memory
3535-- reducing GC overhead. If space efficiency is a concern you can use
36−-- 'encodeUtf8' on the 'Char' stream before writing it to an 'Array' providing
36+-- 'encodeUtf8'' on the 'Char' stream before writing it to an 'Array' providing
3737-- an even more compact representation.
3838--
3939-- = String Literals
@@ -65,18 +65,15 @@
6565-- lived strings in memory.
6666--
6767module Streamly.Data.Unicode.Stream
68− {-# DEPRECATED "Use Streamly.Unicode.Stream instead" #-}
68+ {-# DEPRECATED "Use \"Streamly.Unicode.Stream\" instead" #-}
6969 (
7070 -- * Construction (Decoding)
7171 decodeLatin1
7272 , decodeUtf8
73− , decodeUtf8Lax
7473
7574 -- * Elimination (Encoding)
7675 , encodeLatin1
77− , encodeLatin1Lax
7876 , encodeUtf8
79− , encodeUtf8Lax
8077 {-
8178 -- * Operations on character strings
8279 , strip -- (dropAround isSpace)
@@ -90,6 +87,11 @@ module Streamly.Data.Unicode.Stream
9087 -- , words
9188 -- , unlines
9289 -- , unwords
90+
91+ -- * Deprecations
92+ , decodeUtf8Lax
93+ , encodeLatin1Lax
94+ , encodeUtf8Lax
9395 )
9496where
9597
src/Streamly/Internal/FileSystem/Event/Darwin.hs+1−1
@@ -980,7 +980,7 @@ isLastHardLink = getFlag kFSEventStreamEventFlagItemIsLastHardlink
980980-- | Convert an 'Event' record to a String representation.
981981showEvent :: Event -> String
982982showEvent ev@Event{..} =
983− let path = runIdentity $ S.toList $ U.decodeUtf8 $ A.toStream eventAbsPath
983+ let path = runIdentity $ S.toList $ U.decodeUtf8' $ A.toStream eventAbsPath
984984 in "--------------------------"
985985 ++ "\nId = " ++ show eventId
986986 ++ "\nPath = " ++ show path
src/Streamly/Internal/FileSystem/Event/Linux.hs+1−1
@@ -552,7 +552,7 @@ foreign import ccall unsafe
552552 :: CInt -> CString -> CUInt -> IO CInt
553553
554554utf8ToString :: Array Word8 -> String
555−utf8ToString = runIdentity . S.toList . U.decodeUtf8 . A.toStream
555+utf8ToString = runIdentity . S.toList . U.decodeUtf8' . A.toStream
556556
557557#if !MIN_VERSION_base(4,10,0)
558558-- | Turn an existing Handle into a file descriptor. This function throws an
src/Streamly/Internal/Unicode/Stream.hs+168−52
@@ -16,20 +16,24 @@ module Streamly.Internal.Unicode.Stream
1616 -- * Construction (Decoding)
1717 decodeLatin1
1818 , decodeUtf8
19− , decodeUtf8Lax
19+ , decodeUtf8'
20+ , decodeUtf8_
2021 , DecodeError(..)
2122 , DecodeState
2223 , CodePoint
2324 , decodeUtf8Either
2425 , resumeDecodeUtf8Either
2526 , decodeUtf8Arrays
26− , decodeUtf8ArraysLenient
27+ , decodeUtf8Arrays'
28+ , decodeUtf8Arrays_
2729
2830 -- * Elimination (Encoding)
2931 , encodeLatin1
30− , encodeLatin1Lax
32+ , encodeLatin1'
33+ , encodeLatin1_
3134 , encodeUtf8
32− , encodeUtf8Lax
35+ , encodeUtf8'
36+ , encodeUtf8_
3337 {-
3438 -- * Operations on character strings
3539 , strip -- (dropAround isSpace)
@@ -38,12 +42,16 @@ module Streamly.Internal.Unicode.Stream
3842
3943 -- * StreamD UTF8 Encoding / Decoding transformations.
4044 , decodeUtf8D
45+ , decodeUtf8D'
46+ , decodeUtf8D_
4147 , encodeUtf8D
42− , decodeUtf8LenientD
48+ , encodeUtf8D'
49+ , encodeUtf8D_
4350 , decodeUtf8EitherD
4451 , resumeDecodeUtf8EitherD
4552 , decodeUtf8ArraysD
46− , decodeUtf8ArraysLenientD
53+ , decodeUtf8ArraysD'
54+ , decodeUtf8ArraysD_
4755
4856 -- * Transformation
4957 , stripStart
@@ -51,12 +59,17 @@ module Streamly.Internal.Unicode.Stream
5159 , words
5260 , unlines
5361 , unwords
62+
63+ -- * Deprecations
64+ , decodeUtf8Lax
65+ , encodeLatin1Lax
66+ , encodeUtf8Lax
5467 )
5568where
5669
5770import Control.Monad.IO.Class (MonadIO, liftIO)
5871import Data.Bits (shiftR, shiftL, (.|.), (.&.))
59−import Data.Char (ord)
72+import Data.Char (chr, ord)
6073import Data.Word (Word8)
6174import Foreign.ForeignPtr (touchForeignPtr)
6275import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
@@ -118,6 +131,7 @@ ord4 c = assert (n >= 0x10000) (WCons x1 (WCons x2 (WCons x3 (WCons x4 WNil))))
118131data CodingFailureMode
119132 = TransliterateCodingFailure
120133 | ErrorOnCodingFailure
134+ | DropOnCodingFailure
121135 deriving (Show)
122136
123137{-# INLINE replacementChar #-}
@@ -266,12 +280,14 @@ decodeUtf8WithD cfm (Stream step state) =
266280 case cfm of
267281 ErrorOnCodingFailure -> error e
268282 TransliterateCodingFailure -> YieldAndContinue replacementChar s
283+ DropOnCodingFailure -> s
269284
270285 {-# INLINE handleUnderflow #-}
271286 handleUnderflow =
272287 case cfm of
273288 ErrorOnCodingFailure -> error $ prefix ++ "Not enough input"
274289 TransliterateCodingFailure -> YieldAndContinue replacementChar Done
290+ DropOnCodingFailure -> Done
275291
276292 {-# INLINE_LATE step' #-}
277293 step' _ gst (FreshPointDecodeInit st) = do
@@ -337,11 +353,15 @@ decodeUtf8WithD cfm (Stream step state) =
337353
338354{-# INLINE decodeUtf8D #-}
339355decodeUtf8D :: Monad m => Stream m Word8 -> Stream m Char
340−decodeUtf8D = decodeUtf8WithD ErrorOnCodingFailure
356+decodeUtf8D = decodeUtf8WithD TransliterateCodingFailure
357+
358+{-# INLINE decodeUtf8D' #-}
359+decodeUtf8D' :: Monad m => Stream m Word8 -> Stream m Char
360+decodeUtf8D' = decodeUtf8WithD ErrorOnCodingFailure
341361
342−{-# INLINE decodeUtf8LenientD #-}
343−decodeUtf8LenientD :: Monad m => Stream m Word8 -> Stream m Char
344−decodeUtf8LenientD = decodeUtf8WithD TransliterateCodingFailure
362+{-# INLINE decodeUtf8D_ #-}
363+decodeUtf8D_ :: Monad m => Stream m Word8 -> Stream m Char
364+decodeUtf8D_ = decodeUtf8WithD DropOnCodingFailure
345365
346366{-# INLINE_NORMAL resumeDecodeUtf8EitherD #-}
347367resumeDecodeUtf8EitherD
@@ -455,6 +475,7 @@ decodeUtf8ArraysWithD cfm (Stream step state) =
455475 case cfm of
456476 ErrorOnCodingFailure -> error e
457477 TransliterateCodingFailure -> YAndC replacementChar s
478+ DropOnCodingFailure -> s
458479 {-# INLINE inputUnderflow #-}
459480 inputUnderflow =
460481 case cfm of
@@ -462,6 +483,7 @@ decodeUtf8ArraysWithD cfm (Stream step state) =
462483 error
463484 "Streamly.Internal.Data.Stream.StreamD.decodeUtf8ArraysWith: Input Underflow"
464485 TransliterateCodingFailure -> YAndC replacementChar D
486+ DropOnCodingFailure -> D
465487 {-# INLINE_LATE step' #-}
466488 step' _ gst (OuterLoop st Nothing) = do
467489 r <- step (adaptState gst) st
@@ -541,14 +563,21 @@ decodeUtf8ArraysD ::
541563 MonadIO m
542564 => Stream m (A.Array Word8)
543565 -> Stream m Char
544−decodeUtf8ArraysD = decodeUtf8ArraysWithD ErrorOnCodingFailure
566+decodeUtf8ArraysD = decodeUtf8ArraysWithD TransliterateCodingFailure
567+
568+{-# INLINE decodeUtf8ArraysD' #-}
569+decodeUtf8ArraysD' ::
570+ MonadIO m
571+ => Stream m (A.Array Word8)
572+ -> Stream m Char
573+decodeUtf8ArraysD' = decodeUtf8ArraysWithD ErrorOnCodingFailure
545574
546−{-# INLINE decodeUtf8ArraysLenientD #-}
547−decodeUtf8ArraysLenientD ::
575+{-# INLINE decodeUtf8ArraysD_ #-}
576+decodeUtf8ArraysD_ ::
548577 MonadIO m
549578 => Stream m (A.Array Word8)
550579 -> Stream m Char
551−decodeUtf8ArraysLenientD = decodeUtf8ArraysWithD TransliterateCodingFailure
580+decodeUtf8ArraysD_ = decodeUtf8ArraysWithD DropOnCodingFailure
552581
553582data EncodeState s = EncodeState s !WList
554583
@@ -556,9 +585,9 @@ data EncodeState s = EncodeState s !WList
556585-- too much code bloat or some trouble with fusion. So keeping only two yield
557586-- points for now, one for the ascii chars (fast path) and one for all other
558587-- paths (slow path).
559−{-# INLINE_NORMAL encodeUtf8D #-}
560−encodeUtf8D :: Monad m => Stream m Char -> Stream m Word8
561−encodeUtf8D (Stream step state) = Stream step' (EncodeState state WNil)
588+{-# INLINE_NORMAL encodeUtf8D' #-}
589+encodeUtf8D' :: Monad m => Stream m Char -> Stream m Word8
590+encodeUtf8D' (Stream step state) = Stream step' (EncodeState state WNil)
562591 where
563592 {-# INLINE_LATE step' #-}
564593 step' gst (EncodeState st WNil) = do
@@ -585,7 +614,9 @@ encodeUtf8D (Stream step state) = Stream step' (EncodeState state WNil)
585614-- | Decode a stream of bytes to Unicode characters by mapping each byte to a
586615-- corresponding Unicode 'Char' in 0-255 range.
587616--
588−-- /Since: 0.7.0/
617+-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
618+--
619+-- @since 0.8.0
589620{-# INLINE decodeLatin1 #-}
590621decodeLatin1 :: (IsStream t, Monad m) => t m Word8 -> t m Char
591622decodeLatin1 = S.map (unsafeChr . fromIntegral)
@@ -594,10 +625,10 @@ decodeLatin1 = S.map (unsafeChr . fromIntegral)
594625-- to a byte in 0-255 range. Throws an error if the input stream contains
595626-- characters beyond 255.
596627--
597−-- /Since: 0.7.0/
598−{-# INLINE encodeLatin1 #-}
599−encodeLatin1 :: (IsStream t, Monad m) => t m Char -> t m Word8
600−encodeLatin1 = S.map convert
628+-- @since 0.8.0
629+{-# INLINE encodeLatin1' #-}
630+encodeLatin1' :: (IsStream t, Monad m) => t m Char -> t m Word8
631+encodeLatin1' = S.map convert
601632 where
602633 convert c =
603634 let codepoint = ord c
@@ -606,38 +637,71 @@ encodeLatin1 = S.map convert
606637 "input char codepoint " ++ show codepoint
607638 else fromIntegral codepoint
608639
609−-- | Like 'encodeLatin1' but silently truncates and maps input characters beyond
640+-- | Like 'encodeLatin1'' but silently truncates and maps input characters beyond
610641-- 255 to (incorrect) chars in 0-255 range. No error or exception is thrown
611642-- when such truncation occurs.
612643--
613−-- /Since: 0.7.0/
644+-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
645+--
646+-- /Since: 0.8.0 (Lenient Behaviour)/
647+{-# INLINE encodeLatin1 #-}
648+encodeLatin1 :: (IsStream t, Monad m) => t m Char -> t m Word8
649+encodeLatin1 = S.map (fromIntegral . ord)
650+
651+-- | Like 'encodeLatin1' but drops the input characters beyond 255.
652+--
653+-- @since 0.8.0
654+{-# INLINE encodeLatin1_ #-}
655+encodeLatin1_ :: (IsStream t, Monad m) => t m Char -> t m Word8
656+encodeLatin1_ = S.map (fromIntegral . ord) . S.filter (<= chr (255))
657+
658+-- | Same as 'encodeLatin1'
659+--
660+{-# DEPRECATED encodeLatin1Lax "Please use 'encodeLatin1' instead" #-}
614661{-# INLINE encodeLatin1Lax #-}
615662encodeLatin1Lax :: (IsStream t, Monad m) => t m Char -> t m Word8
616−encodeLatin1Lax = S.map (fromIntegral . ord)
663+encodeLatin1Lax = encodeLatin1
617664
618665-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
619−-- The incoming stream is truncated if an invalid codepoint is encountered.
666+-- The function throws an error if an invalid codepoint is encountered.
620667--
621−-- /Since: 0.7.0/
622−{-# INLINE decodeUtf8 #-}
623−decodeUtf8 :: (Monad m, IsStream t) => t m Word8 -> t m Char
624−decodeUtf8 = D.fromStreamD . decodeUtf8D . D.toStreamD
668+-- @since 0.8.0
669+{-# INLINE decodeUtf8' #-}
670+decodeUtf8' :: (Monad m, IsStream t) => t m Word8 -> t m Char
671+decodeUtf8' = D.fromStreamD . decodeUtf8D' . D.toStreamD
625672
626673-- |
627674--
628675-- /Internal/
629−{-# INLINE decodeUtf8Arrays #-}
630−decodeUtf8Arrays :: (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
631−decodeUtf8Arrays = D.fromStreamD . decodeUtf8ArraysD . D.toStreamD
676+{-# INLINE decodeUtf8Arrays' #-}
677+decodeUtf8Arrays' :: (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
678+decodeUtf8Arrays' = D.fromStreamD . decodeUtf8ArraysD' . D.toStreamD
632679
633680-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
634681-- Any invalid codepoint encountered is replaced with the unicode replacement
635682-- character.
636683--
637−-- /Since: 0.7.0/
684+-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
685+--
686+-- /Since: 0.8.0 (Lenient Behaviour)/
687+{-# INLINE decodeUtf8 #-}
688+decodeUtf8 :: (Monad m, IsStream t) => t m Word8 -> t m Char
689+decodeUtf8 = D.fromStreamD . decodeUtf8D . D.toStreamD
690+
691+-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
692+-- Any invalid codepoint encountered is dropped.
693+--
694+-- @since 0.8.0
695+{-# INLINE decodeUtf8_ #-}
696+decodeUtf8_ :: (Monad m, IsStream t) => t m Word8 -> t m Char
697+decodeUtf8_ = D.fromStreamD . decodeUtf8D_ . D.toStreamD
698+
699+-- | Same as 'decodeUtf8'
700+--
701+{-# DEPRECATED decodeUtf8Lax "Please use 'decodeUtf8' instead" #-}
638702{-# INLINE decodeUtf8Lax #-}
639−decodeUtf8Lax :: (Monad m, IsStream t) => t m Word8 -> t m Char
640−decodeUtf8Lax = D.fromStreamD . decodeUtf8LenientD . D.toStreamD
703+decodeUtf8Lax :: (IsStream t, Monad m) => t m Word8 -> t m Char
704+decodeUtf8Lax = decodeUtf8
641705
642706-- |
643707--
@@ -663,25 +727,36 @@ resumeDecodeUtf8Either st cp =
663727-- |
664728--
665729-- /Internal/
666−{-# INLINE decodeUtf8ArraysLenient #-}
667−decodeUtf8ArraysLenient ::
730+{-# INLINE decodeUtf8Arrays #-}
731+decodeUtf8Arrays ::
668732 (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
669−decodeUtf8ArraysLenient =
670− D.fromStreamD . decodeUtf8ArraysLenientD . D.toStreamD
733+decodeUtf8Arrays =
734+ D.fromStreamD . decodeUtf8ArraysD . D.toStreamD
671735
672−-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream.
736+-- |
673737--
674−-- /Since: 0.7.0/
675−{-# INLINE encodeUtf8 #-}
676−encodeUtf8 :: (Monad m, IsStream t) => t m Char -> t m Word8
677−encodeUtf8 = D.fromStreamD . encodeUtf8D . D.toStreamD
738+-- /Internal/
739+{-# INLINE decodeUtf8Arrays_ #-}
740+decodeUtf8Arrays_ ::
741+ (MonadIO m, IsStream t) => t m (Array Word8) -> t m Char
742+decodeUtf8Arrays_ =
743+ D.fromStreamD . decodeUtf8ArraysD_ . D.toStreamD
744+
745+-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. When
746+-- any invalid character (U+D800-U+D8FF) is encountered in the input stream the
747+-- function errors out.
748+--
749+-- @since 0.8.0
750+{-# INLINE encodeUtf8' #-}
751+encodeUtf8' :: (Monad m, IsStream t) => t m Char -> t m Word8
752+encodeUtf8' = D.fromStreamD . encodeUtf8D' . D.toStreamD
678753
679754-- | See section "3.9 Unicode Encoding Forms" in
680755-- https://www.unicode.org/versions/Unicode13.0.0/UnicodeStandard-13.0.pdf
681756--
682−{-# INLINE_NORMAL encodeUtf8LaxD #-}
683−encodeUtf8LaxD :: Monad m => Stream m Char -> Stream m Word8
684−encodeUtf8LaxD (Stream step state) = Stream step' (EncodeState state WNil)
757+{-# INLINE_NORMAL encodeUtf8D #-}
758+encodeUtf8D :: Monad m => Stream m Char -> Stream m Word8
759+encodeUtf8D (Stream step state) = Stream step' (EncodeState state WNil)
685760 where
686761 {-# INLINE_LATE step' #-}
687762 step' gst (EncodeState st WNil) = do
@@ -710,10 +785,51 @@ encodeUtf8LaxD (Stream step state) = Stream step' (EncodeState state WNil)
710785-- Invalid characters (U+D800-U+D8FF) in the input stream are replaced by the
711786-- Unicode replacement character U+FFFD.
712787--
713−-- /Since: 0.8.0/
788+-- /Since: 0.7.0 ("Streamly.Data.Unicode.Stream")/
789+--
790+-- /Since: 0.8.0 (Lenient Behaviour)/
791+{-# INLINE encodeUtf8 #-}
792+encodeUtf8 :: (Monad m, IsStream t) => t m Char -> t m Word8
793+encodeUtf8 = D.fromStreamD . encodeUtf8D . D.toStreamD
794+
795+{-# INLINE_NORMAL encodeUtf8D_ #-}
796+encodeUtf8D_ :: Monad m => Stream m Char -> Stream m Word8
797+encodeUtf8D_ (Stream step state) = Stream step' (EncodeState state WNil)
798+ where
799+ {-# INLINE_LATE step' #-}
800+ step' gst (EncodeState st WNil) = do
801+ r <- step (adaptState gst) st
802+ return $
803+ case r of
804+ Yield c s ->
805+ case ord c of
806+ x | x <= 0x7F ->
807+ Yield (fromIntegral x) (EncodeState s WNil)
808+ | x <= 0x7FF -> Skip (EncodeState s (ord2 c))
809+ | x <= 0xFFFF ->
810+ if isSurrogate c
811+ then Skip $
812+ EncodeState s WNil
813+ else Skip (EncodeState s (ord3 c))
814+ | otherwise -> Skip (EncodeState s (ord4 c))
815+ Skip s -> Skip (EncodeState s WNil)
816+ Stop -> Stop
817+ step' _ (EncodeState s (WCons x xs)) = return $ Yield x (EncodeState s xs)
818+
819+-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. Any
820+-- Invalid characters (U+D800-U+D8FF) in the input stream are dropped.
821+--
822+-- @since 0.8.0
823+{-# INLINE encodeUtf8_ #-}
824+encodeUtf8_ :: (Monad m, IsStream t) => t m Char -> t m Word8
825+encodeUtf8_ = D.fromStreamD . encodeUtf8D_ . D.toStreamD
826+
827+-- | Same as 'encodeUtf8'
828+--
829+{-# DEPRECATED encodeUtf8Lax "Please use 'encodeUtf8' instead" #-}
714830{-# INLINE encodeUtf8Lax #-}
715−encodeUtf8Lax :: (Monad m, IsStream t) => t m Char -> t m Word8
716−encodeUtf8Lax = D.fromStreamD . encodeUtf8LaxD . D.toStreamD
831+encodeUtf8Lax :: (IsStream t, Monad m) => t m Char -> t m Word8
832+encodeUtf8Lax = encodeUtf8
717833
718834{-
719835-------------------------------------------------------------------------------
src/Streamly/Unicode/Stream.hs+4−4
@@ -33,7 +33,7 @@
3333-- stream as @Array Char@ using the array 'Streamly.Data.Array.Storable.Foreign.write' fold.
3434-- The 'Array' type provides a more compact representation and pinned memory
3535-- reducing GC overhead. If space efficiency is a concern you can use
36−-- 'encodeUtf8' on the 'Char' stream before writing it to an 'Array' providing
36+-- 'encodeUtf8'' on the 'Char' stream before writing it to an 'Array' providing
3737-- an even more compact representation.
3838--
3939-- = String Literals
@@ -69,13 +69,13 @@ module Streamly.Unicode.Stream
6969 -- * Construction (Decoding)
7070 decodeLatin1
7171 , decodeUtf8
72− , decodeUtf8Lax
72+ , decodeUtf8'
7373
7474 -- * Elimination (Encoding)
7575 , encodeLatin1
76− , encodeLatin1Lax
76+ , encodeLatin1'
7777 , encodeUtf8
78− , encodeUtf8Lax
78+ , encodeUtf8'
7979 {-
8080 -- * Operations on character strings
8181 , strip -- (dropAround isSpace)
test/Streamly/Test/FileSystem/Event.hs+1−1
@@ -36,7 +36,7 @@ import qualified Streamly.Internal.Data.Array.Storable.Foreign as Array
3636-------------------------------------------------------------------------------
3737
3838toUtf8 :: MonadIO m => String -> m (Array Word8)
39−toUtf8 = Array.fromStream . Unicode.encodeUtf8 . Stream.fromList
39+toUtf8 = Array.fromStream . Unicode.encodeUtf8' . Stream.fromList
4040
4141-------------------------------------------------------------------------------
4242-- Main
test/String.hs+12−12
@@ -33,29 +33,29 @@ genUnicode = listOf arbitraryUnicodeChar
3333genWord8 :: Gen [Word8]
3434genWord8 = listOf arbitrary
3535
36−propDecodeEncodeId :: Property
37−propDecodeEncodeId =
36+propDecodeEncodeId' :: Property
37+propDecodeEncodeId' =
3838 forAll genUnicode $ \list ->
3939 monadicIO $ do
40− let wrds = SS.encodeUtf8 $ S.fromList list
41− chrs <- S.toList $ SS.decodeUtf8 wrds
40+ let wrds = SS.encodeUtf8' $ S.fromList list
41+ chrs <- S.toList $ SS.decodeUtf8' wrds
4242 assert (chrs == list)
4343
4444-- XXX need to use invalid characters
45−propDecodeEncodeIdLenient :: Property
46−propDecodeEncodeIdLenient =
45+propDecodeEncodeId :: Property
46+propDecodeEncodeId =
4747 forAll genUnicode $ \list ->
4848 monadicIO $ do
4949 let wrds = SS.encodeUtf8 $ S.fromList list
50− chrs <- S.toList $ SS.decodeUtf8Lax wrds
50+ chrs <- S.toList $ SS.decodeUtf8 wrds
5151 assert (chrs == list)
5252
5353propDecodeEncodeIdArrays :: Property
5454propDecodeEncodeIdArrays =
5555 forAll genUnicode $ \list ->
5656 monadicIO $ do
57− let wrds = SS.encodeUtf8 $ S.fromList list
58− chrs <- S.toList $ IUS.decodeUtf8ArraysLenient
57+ let wrds = SS.encodeUtf8' $ S.fromList list
58+ chrs <- S.toList $ IUS.decodeUtf8Arrays
5959 (S.fold A.write wrds)
6060 assert (chrs == list)
6161
@@ -117,9 +117,9 @@ main = hspec
117117 $ modifyMaxSuccess (const 1000)
118118 $ do
119119 describe "UTF8 - Encoding / Decoding" $ do
120− prop "decodeUtf8 . encodeUtf8 == id" $ propDecodeEncodeId
121− prop "decodeUtf8Lax . encodeUtf8 == id" $ propDecodeEncodeIdLenient
122− prop "decodeUtf8ArraysLenient . encodeUtf8 == id"
120+ prop "decodeUtf8' . encodeUtf8' == id" $ propDecodeEncodeId'
121+ prop "decodeUtf8 . encodeUtf8' == id" $ propDecodeEncodeId
122+ prop "decodeUtf8Arrays . encodeUtf8' == id"
123123 $ propDecodeEncodeIdArrays
124124 prop "Streamly.Data.String.lines == Prelude.lines" $ testLines
125125 prop "Arrays Streamly.Data.String.lines == Prelude.lines" $ testLinesArray