on 7d92943 help sign in
11 files changed+95−44
since splitsnapshots
unifiedsplit
.hgtags+1 Data/Text.hs+1−1 Data/Text/Foreign.hs+1−1 Data/Text/Internal/Fusion/Common.hs+10−9 Data/Text/Lazy/Read.hs+3−3 Data/Text/Read.hs+3−3 changelog.md+18−1 tests/Tests/Properties.hs+37−20 tests/Tests/QuickCheckUtils.hs+1−1 tests/text-tests.cabal+18−3 text.cabal+2−2
.hgtags+1
@@ -64,3 +64,4 @@ f8a60279237c1c28e094868740d9188dd94fcc9e 1.2.0.0
6464631fd055e27fa10983e17e9ad36d1b285b85c661 1.2.1.3
65658221a68ed5ec37028357df9c6c25f52320e54ad9 1.2.2.0
6666ce1c5ac65eb9f5580114b4e380092b05a485e95d 1.2.2.1
67+d72c634a91ffe27ec5cd03921dd53efc8129d8f6 1.2.2.2
Data/Text.hs+1−1
@@ -1099,8 +1099,8 @@ takeEnd n t@(Text arr off len)
10991099iterNEnd :: Int -> Text -> Int
11001100iterNEnd n t@(Text _arr _off len) = loop (len-1) n
11011101 where loop i !m
1102+ | m <= 0 = i+1
11021103 | i <= 0 = 0
1103− | m <= 1 = i
11041104 | otherwise = loop (i+d) (m-1)
11051105 where d = reverseIter_ t i
11061106
Data/Text/Foreign.hs+1−1
@@ -108,7 +108,7 @@ takeWord16 (I16 n) t@(Text arr off len)
108108 | n >= len || m >= len = t
109109 | otherwise = Text arr off m
110110 where
111− m | w < 0xDB00 || w > 0xD8FF = n
111+ m | w < 0xD800 || w > 0xDBFF = n
112112 | otherwise = n+1
113113 w = A.unsafeIndex arr (off+n-1)
114114
Data/Text/Internal/Fusion/Common.hs+10−9
@@ -107,7 +107,7 @@ import Prelude (Bool(..), Char, Eq(..), Int, Integral, Maybe(..),
107107import qualified Data.List as L
108108import qualified Prelude as P
109109import Data.Bits (shiftL)
110−import Data.Char (isLetter)
110+import Data.Char (isLetter, isSpace)
111111import Data.Int (Int64)
112112import Data.Text.Internal.Fusion.Types
113113import Data.Text.Internal.Fusion.CaseMapping (foldMapping, lowerMapping, titleMapping,
@@ -462,15 +462,16 @@ toTitle (Stream next0 s0 len) = Stream next (CC (False :*: s0) '\0' '\0') len
462462 where
463463 next (CC (letter :*: s) '\0' _) =
464464 case next0 s of
465− Done -> Done
466− Skip s' -> Skip (CC (letter :*: s') '\0' '\0')
465+ Done -> Done
466+ Skip s' -> Skip (CC (letter :*: s') '\0' '\0')
467467 Yield c s'
468− | letter' -> if letter
469− then lowerMapping c (letter' :*: s')
470− else titleMapping c (letter' :*: s')
471− | otherwise -> Yield c (CC (letter' :*: s') '\0' '\0')
472− where letter' = isLetter c
473− next (CC s a b) = Yield a (CC s b '\0')
468+ | nonSpace -> if letter
469+ then lowerMapping c (nonSpace :*: s')
470+ else titleMapping c (letter' :*: s')
471+ | otherwise -> Yield c (CC (letter' :*: s') '\0' '\0')
472+ where nonSpace = P.not (isSpace c)
473+ letter' = isLetter c
474+ next (CC s a b) = Yield a (CC s b '\0')
474475{-# INLINE [0] toTitle #-}
475476
476477data Justify i s = Just1 !i !s
Data/Text/Lazy/Read.hs+3−3
@@ -68,9 +68,9 @@ decimal txt
6868 go n d = (n * 10 + fromIntegral (digitToInt d))
6969
7070-- | Read a hexadecimal integer, consisting of an optional leading
71−-- @\"0x\"@ followed by at least one decimal digit. Input is consumed
72−-- until a non-hex-digit or end of string is reached. This function
73−-- is case insensitive.
71+-- @\"0x\"@ followed by at least one hexadecimal digit. Input is
72+-- consumed until a non-hex-digit or end of string is reached.
73+-- This function is case insensitive.
7474--
7575-- This function does not handle leading sign characters. If you need
7676-- to handle signed input, use @'signed' 'hexadecimal'@.
Data/Text/Read.hs+3−3
@@ -67,9 +67,9 @@ decimal txt
6767 go n d = (n * 10 + fromIntegral (digitToInt d))
6868
6969-- | Read a hexadecimal integer, consisting of an optional leading
70−-- @\"0x\"@ followed by at least one decimal digit. Input is consumed
71−-- until a non-hex-digit or end of string is reached. This function
72−-- is case insensitive.
70+-- @\"0x\"@ followed by at least one hexadecimal digit. Input is
71+-- consumed until a non-hex-digit or end of string is reached.
72+-- This function is case insensitive.
7373--
7474-- This function does not handle leading sign characters. If you need
7575-- to handle signed input, use @'signed' 'hexadecimal'@.
changelog.md+18−1
@@ -1,10 +1,27 @@
1+1.2.2.2
2+
3+* The `toTitle` function now correctly handles letters that
4+ immediately follow punctuation. Before, `"there's"` would turn into
5+ `"There'S"`. Now, it becomes `"There's"`.
6+
7+* The implementation of unstreaming is faster, resulting in operations
8+ such as `map` and `intersperse` speeding up by up to 30%, with
9+ smaller code generated.
10+
11+* The optimised length comparison function is now more likely to be
12+ used after some rewrite rule tweaking.
13+
14+* Bug fix: an off-by-one bug in `takeEnd` is fixed.
15+
16+* Bug fix: a logic error in `takeWord16` is fixed.
17+
1181.2.2.1
219
320* The switch to `integer-pure` in 1.2.2.0 was apparently mistaken.
421 The build flag has been renamed accordingly. Your army of diligent
522 maintainers apologizes for the churn.
623
7−* Spec compliance: toCaseFold now follows the Unicode 8.0 spec
24+* Spec compliance: `toCaseFold` now follows the Unicode 8.0 spec
825 (updated from 7.0)
926
1027* An STG lint error has been fixed
tests/Tests/Properties.hs+37−20
@@ -11,7 +11,7 @@ module Tests.Properties
1111import Control.Applicative ((<$>), (<*>))
1212import Control.Arrow ((***), second)
1313import Data.Bits ((.&.))
14−import Data.Char (chr, isDigit, isHexDigit, isLower, isSpace, isUpper, ord)
14+import Data.Char (chr, isDigit, isHexDigit, isLower, isSpace, isLetter, isUpper, ord)
1515import Data.Int (Int8, Int16, Int32, Int64)
1616import Data.Monoid (Monoid(..))
1717import Data.String (IsString(fromString))
@@ -23,6 +23,7 @@ import Data.Text.Internal.Search (indices)
2323import Data.Text.Lazy.Read as TL
2424import Data.Text.Read as T
2525import Data.Word (Word, Word8, Word16, Word32, Word64)
26+import Data.Maybe (mapMaybe)
2627import Numeric (showEFloat, showFFloat, showGFloat, showHex)
2728import Prelude hiding (replicate)
2829import Test.Framework (Test, testGroup)
@@ -37,6 +38,7 @@ import qualified Control.Exception as Exception
3738import qualified Data.Bits as Bits (shiftL, shiftR)
3839import qualified Data.ByteString as B
3940import qualified Data.ByteString.Lazy as BL
41+import qualified Data.Char as C
4042import qualified Data.List as L
4143import qualified Data.Text as T
4244import qualified Data.Text.Encoding as E
@@ -60,10 +62,10 @@ t_pack_unpack = (T.unpack . T.pack) `eq` id
6062tl_pack_unpack = (TL.unpack . TL.pack) `eq` id
6163t_stream_unstream = (S.unstream . S.stream) `eq` id
6264tl_stream_unstream = (SL.unstream . SL.stream) `eq` id
63−t_reverse_stream t = (S.reverse . S.reverseStream) t == t
64−t_singleton c = [c] == (T.unpack . T.singleton) c
65−tl_singleton c = [c] == (TL.unpack . TL.singleton) c
66−tl_unstreamChunks x = f 11 x == f 1000 x
65+t_reverse_stream t = (S.reverse . S.reverseStream) t === t
66+t_singleton c = [c] === (T.unpack . T.singleton) c
67+tl_singleton c = [c] === (TL.unpack . TL.singleton) c
68+tl_unstreamChunks x = f 11 x === f 1000 x
6769 where f n = SL.unstreamChunks n . S.streamList
6870tl_chunk_unchunk = (TL.fromChunks . TL.toChunks) `eq` id
6971tl_from_to_strict = (TL.fromStrict . TL.toStrict) `eq` id
@@ -74,13 +76,13 @@ encodeL1 = B.pack . map (fromIntegral . fromEnum) . T.unpack
7476encodeLazyL1 :: TL.Text -> BL.ByteString
7577encodeLazyL1 = BL.fromChunks . map encodeL1 . TL.toChunks
7678
77−t_ascii t = E.decodeASCII (E.encodeUtf8 a) == a
79+t_ascii t = E.decodeASCII (E.encodeUtf8 a) === a
7880 where a = T.map (\c -> chr (ord c `mod` 128)) t
79−tl_ascii t = EL.decodeASCII (EL.encodeUtf8 a) == a
81+tl_ascii t = EL.decodeASCII (EL.encodeUtf8 a) === a
8082 where a = TL.map (\c -> chr (ord c `mod` 128)) t
81−t_latin1 t = E.decodeLatin1 (encodeL1 a) == a
83+t_latin1 t = E.decodeLatin1 (encodeL1 a) === a
8284 where a = T.map (\c -> chr (ord c `mod` 256)) t
83−tl_latin1 t = EL.decodeLatin1 (encodeLazyL1 a) == a
85+tl_latin1 t = EL.decodeLatin1 (encodeLazyL1 a) === a
8486 where a = TL.map (\c -> chr (ord c `mod` 256)) t
8587t_utf8 = forAll genUnicode $ (E.decodeUtf8 . E.encodeUtf8) `eq` id
8688t_utf8' = forAll genUnicode $ (E.decodeUtf8' . E.encodeUtf8) `eq` (id . Right)
@@ -111,7 +113,7 @@ t_utf8_undecoded = forAll genUnicode $ \t ->
111113 let b = E.encodeUtf8 t
112114 ls = concatMap (leftover . E.encodeUtf8 . T.singleton) . T.unpack $ t
113115 leftover = (++ [B.empty]) . init . tail . B.inits
114− in (map snd . feedChunksOf 1 E.streamDecodeUtf8) b == ls
116+ in (map snd . feedChunksOf 1 E.streamDecodeUtf8) b === ls
115117
116118data Badness = Solo | Leading | Trailing
117119 deriving (Eq, Show)
@@ -201,8 +203,8 @@ t_mconcat = unsquare $
201203 mconcat `eq` (unpackS . mconcat . L.map T.pack)
202204tl_mconcat = unsquare $
203205 mconcat `eq` (unpackS . mconcat . L.map TL.pack)
204−t_mempty = mempty == (unpackS (mempty :: T.Text))
205−tl_mempty = mempty == (unpackS (mempty :: TL.Text))
206+t_mempty = mempty === (unpackS (mempty :: T.Text))
207+tl_mempty = mempty === (unpackS (mempty :: TL.Text))
206208t_IsString = fromString `eqP` (T.unpack . fromString)
207209tl_IsString = fromString `eqP` (TL.unpack . fromString)
208210
@@ -326,6 +328,19 @@ t_toUpper_upper t = p (T.toUpper t) >= p t
326328 where p = T.length . T.filter isUpper
327329tl_toUpper_upper t = p (TL.toUpper t) >= p t
328330 where p = TL.length . TL.filter isUpper
331+t_toTitle_title t = all (<= 1) (caps w)
332+ where caps = fmap (T.length . T.filter isUpper) . T.words . T.toTitle
333+ -- TIL: there exist uppercase-only letters
334+ w = T.filter (\c -> if C.isUpper c then C.toLower c /= c else True) t
335+t_toTitle_1stNotLower = and . notLow . T.toTitle . T.filter stable
336+ where notLow = mapMaybe (fmap (not . isLower) . (T.find isLetter)) . T.words
337+ -- Surprise! The Spanish/Portuguese ordinal indicators changed
338+ -- from category Ll (letter, lowercase) to Lo (letter, other)
339+ -- in Unicode 7.0
340+ -- Oh, and there exist lowercase-only letters (see previous test)
341+ stable c = if isLower c
342+ then C.toUpper c /= c
343+ else c /= '\170' && c /= '\186'
329344
330345justifyLeft k c xs = xs ++ L.replicate (k - length xs) c
331346justifyRight m n xs = L.replicate (m - length xs) n ++ xs
@@ -720,7 +735,7 @@ tl_indices (NotEmpty s) = lazyIndices s `eq` S.indices s
720735 conc = T.concat . TL.toChunks
721736t_indices_occurs = unsquare $ \(NotEmpty t) ts ->
722737 let s = T.intercalate t ts
723− in Slow.indices t s == indices t s
738+ in Slow.indices t s === indices t s
724739
725740-- Bit shifts.
726741shiftL w = forAll (choose (0,width-1)) $ \k -> Bits.shiftL w k == U.shiftL w k
@@ -812,18 +827,18 @@ tb_formatRealFloat_double (a::Double) = tb_formatRealFloat a
812827-- Reading.
813828
814829t_decimal (n::Int) s =
815− T.signed T.decimal (T.pack (show n) `T.append` t) == Right (n,t)
830+ T.signed T.decimal (T.pack (show n) `T.append` t) === Right (n,t)
816831 where t = T.dropWhile isDigit s
817832tl_decimal (n::Int) s =
818− TL.signed TL.decimal (TL.pack (show n) `TL.append` t) == Right (n,t)
833+ TL.signed TL.decimal (TL.pack (show n) `TL.append` t) === Right (n,t)
819834 where t = TL.dropWhile isDigit s
820835t_hexadecimal m s ox =
821− T.hexadecimal (T.concat [p, T.pack (showHex n ""), t]) == Right (n,t)
836+ T.hexadecimal (T.concat [p, T.pack (showHex n ""), t]) === Right (n,t)
822837 where t = T.dropWhile isHexDigit s
823838 p = if ox then "0x" else ""
824839 n = getPositive m :: Int
825840tl_hexadecimal m s ox =
826− TL.hexadecimal (TL.concat [p, TL.pack (showHex n ""), t]) == Right (n,t)
841+ TL.hexadecimal (TL.concat [p, TL.pack (showHex n ""), t]) === Right (n,t)
827842 where t = TL.dropWhile isHexDigit s
828843 p = if ox then "0x" else ""
829844 n = getPositive m :: Int
@@ -867,11 +882,11 @@ tl_write_read_line e m b t = write_read head TL.filter TL.hPutStrLn
867882
868883t_dropWord16 m t = dropWord16 m t `T.isSuffixOf` t
869884t_takeWord16 m t = takeWord16 m t `T.isPrefixOf` t
870−t_take_drop_16 m t = T.append (takeWord16 n t) (dropWord16 n t) == t
885+t_take_drop_16 m t = T.append (takeWord16 n t) (dropWord16 n t) === t
871886 where n = small m
872887t_use_from t = monadicIO $ assert . (==t) =<< run (useAsPtr t fromPtr)
873888
874−t_copy t = T.copy t == t
889+t_copy t = T.copy t === t
875890
876891-- Regression tests.
877892s_filter_eq s = S.filter p t == S.streamList (filter p s)
@@ -1030,7 +1045,9 @@ tests =
10301045 testProperty "tl_toLower_lower" tl_toLower_lower,
10311046 testProperty "t_toUpper_length" t_toUpper_length,
10321047 testProperty "t_toUpper_upper" t_toUpper_upper,
1033− testProperty "tl_toUpper_upper" tl_toUpper_upper
1048+ testProperty "tl_toUpper_upper" tl_toUpper_upper,
1049+ testProperty "t_toTitle_title" t_toTitle_title,
1050+ testProperty "t_toTitle_1stNotLower" t_toTitle_1stNotLower
10341051 ],
10351052
10361053 testGroup "justification" [
tests/Tests/QuickCheckUtils.hs+1−1
@@ -106,7 +106,7 @@ smallArbitrary = sized $ \n -> resize (smallish n) arbitrary
106106 where smallish = round . (sqrt :: Double -> Double) . fromIntegral . abs
107107
108108instance Arbitrary T.Text where
109− arbitrary = T.pack `fmap` arbitrary
109+ arbitrary = T.pack `fmap` string
110110 shrink = map T.pack . shrink . T.unpack
111111
112112instance Arbitrary TL.Text where
tests/text-tests.cabal+18−3
@@ -20,6 +20,11 @@ flag hpc
2020 default: False
2121 manual: True
2222
23+flag bytestring-builder
24+ description: Depend on the bytestring-builder package for backwards compatibility.
25+ default: False
26+ manual: False
27+
2328executable text-tests
2429 main-is: Tests.hs
2530
@@ -48,16 +53,21 @@ executable text-tests
4853 HUnit >= 1.2,
4954 QuickCheck >= 2.7,
5055 base == 4.*,
51− bytestring,
5256 deepseq,
5357 directory,
54− quickcheck-unicode,
58+ quickcheck-unicode >= 1.0.1.0,
5559 random,
5660 test-framework >= 0.4,
5761 test-framework-hunit >= 0.2,
5862 test-framework-quickcheck2 >= 0.2,
5963 text-tests
6064
65+ if flag(bytestring-builder)
66+ build-depends: bytestring >= 0.9 && < 0.10.4,
67+ bytestring-builder >= 0.10.4
68+ else
69+ build-depends: bytestring >= 0.10.4
70+
6171executable text-tests-stdio
6272 main-is: Tests/IO.hs
6373
@@ -137,7 +147,12 @@ library
137147 array,
138148 base == 4.*,
139149 binary,
140− bytestring,
141150 deepseq,
142151 ghc-prim,
143152 integer-gmp
153+
154+ if flag(bytestring-builder)
155+ build-depends: bytestring >= 0.9 && < 0.10.4,
156+ bytestring-builder >= 0.10.4
157+ else
158+ build-depends: bytestring >= 0.10.4
text.cabal+2−2
@@ -1,5 +1,5 @@
11name: text
2−version: 1.2.2.1
2+version: 1.2.2.2
33homepage: https://github.com/bos/text
44bug-reports: https://github.com/bos/text/issues
55synopsis: An efficient packed Unicode text type.
@@ -180,7 +180,7 @@ test-suite tests
180180 deepseq,
181181 directory,
182182 ghc-prim,
183− quickcheck-unicode,
183+ quickcheck-unicode >= 1.0.1.0,
184184 random,
185185 test-framework >= 0.4,
186186 test-framework-hunit >= 0.2,