on 40a3168 help sign in
Data/Text.hs+12
since splitsnapshots
unifiedsplit
@@ -60,6 +60,7 @@ module Data.Text
60 , snoc
60 , snoc
61 , append
61 , append
62 , uncons
62 , uncons
63+ , unsnoc
63 , head
64 , head
64 , last
65 , last
65 , tail
66 , tail
@@ -545,6 +546,17 @@ init (Text arr off len) | len <= 0 = emptyError "init"
545 unstream (S.init (stream t)) = init t
546 unstream (S.init (stream t)) = init t
546 #-}
547 #-}
547
548
549+-- | /O(1)/ Returns all but the last character and the last character of a
550+-- 'Text', or 'Nothing' if empty.
551+unsnoc :: Text -> Maybe (Text, Char)
552+unsnoc (Text arr off len)
553+ | len <= 0 = Nothing
554+ | n < 0xDC00 || n > 0xDFFF = Just (text arr off (len-1), unsafeChr n)
555+ | otherwise = Just (text arr off (len-2), U16.chr2 n0 n)
556+ where n = A.unsafeIndex arr (off+len-1)
557+ n0 = A.unsafeIndex arr (off+len-2)
558+{-# INLINE [1] unsnoc #-}
559+
548-- | /O(1)/ Tests whether a 'Text' is empty or not. Subject to
560-- | /O(1)/ Tests whether a 'Text' is empty or not. Subject to
549-- fusion.
561-- fusion.
550null :: Text -> Bool
562null :: Text -> Bool
Data/Text/Internal/Fusion/Common.hs+10−9
@@ -107,7 +107,7 @@ import Prelude (Bool(..), Char, Eq(..), Int, Integral, Maybe(..),
107import qualified Data.List as L
107import qualified Data.List as L
108import qualified Prelude as P
108import qualified Prelude as P
109import Data.Bits (shiftL)
109import Data.Bits (shiftL)
110−import Data.Char (isLetter)
110+import Data.Char (isLetter, isSpace)
111import Data.Int (Int64)
111import Data.Int (Int64)
112import Data.Text.Internal.Fusion.Types
112import Data.Text.Internal.Fusion.Types
113import Data.Text.Internal.Fusion.CaseMapping (foldMapping, lowerMapping, titleMapping,
113import 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
462 where
462 where
463 next (CC (letter :*: s) '\0' _) =
463 next (CC (letter :*: s) '\0' _) =
464 case next0 s of
464 case next0 s of
465− Done -> Done
465+ Done -> Done
466− Skip s' -> Skip (CC (letter :*: s') '\0' '\0')
466+ Skip s' -> Skip (CC (letter :*: s') '\0' '\0')
467 Yield c s'
467 Yield c s'
468− | letter' -> if letter
468+ | nonSpace -> if letter
469− then lowerMapping c (letter' :*: s')
469+ then lowerMapping c (nonSpace :*: s')
470− else titleMapping c (letter' :*: s')
470+ else titleMapping c (letter' :*: s')
471− | otherwise -> Yield c (CC (letter' :*: s') '\0' '\0')
471+ | otherwise -> Yield c (CC (letter' :*: s') '\0' '\0')
472+ where nonSpace = P.not (isSpace c)
472− where letter' = isLetter c
473+ letter' = isLetter c
473− next (CC s a b) = Yield a (CC s b '\0')
474+ next (CC s a b) = Yield a (CC s b '\0')
474{-# INLINE [0] toTitle #-}
475{-# INLINE [0] toTitle #-}
475
476
476data Justify i s = Just1 !i !s
477data Justify i s = Just1 !i !s
Data/Text/Lazy.hs+10
@@ -68,6 +68,7 @@ module Data.Text.Lazy
68 , snoc
68 , snoc
69 , append
69 , append
70 , uncons
70 , uncons
71+ , unsnoc
71 , head
72 , head
72 , last
73 , last
73 , tail
74 , tail
@@ -561,6 +562,15 @@ init Empty = emptyError "init"
561 unstream (S.init (stream t)) = init t
562 unstream (S.init (stream t)) = init t
562 #-}
563 #-}
563
564
565+-- | /O(n\/c)/ Returns the 'init' and 'last' of a 'Text', or 'Nothing' if
566+-- empty.
567+--
568+-- * It is no faster than using 'init' and 'last'.
569+unsnoc :: Text -> Maybe (Text, Char)
570+unsnoc Empty = Nothing
571+unsnoc ts@(Chunk _ _) = Just (init ts, last ts)
572+{-# INLINE unsnoc #-}
573+
564-- | /O(1)/ Tests whether a 'Text' is empty or not. Subject to
574-- | /O(1)/ Tests whether a 'Text' is empty or not. Subject to
565-- fusion.
575-- fusion.
566null :: Text -> Bool
576null :: Text -> Bool
Data/Text/Lazy/Read.hs+3−3
@@ -68,9 +68,9 @@ decimal txt
68 go n d = (n * 10 + fromIntegral (digitToInt d))
68 go n d = (n * 10 + fromIntegral (digitToInt d))
69
69
70-- | Read a hexadecimal integer, consisting of an optional leading
70-- | Read a hexadecimal integer, consisting of an optional leading
71−-- @\"0x\"@ followed by at least one decimal digit. Input is consumed
71+-- @\"0x\"@ followed by at least one hexadecimal digit. Input is
72−-- until a non-hex-digit or end of string is reached. This function
72+-- consumed until a non-hex-digit or end of string is reached.
73−-- is case insensitive.
73+-- This function is case insensitive.
74--
74--
75-- This function does not handle leading sign characters. If you need
75-- This function does not handle leading sign characters. If you need
76-- to handle signed input, use @'signed' 'hexadecimal'@.
76-- to handle signed input, use @'signed' 'hexadecimal'@.
Data/Text/Read.hs+3−3
@@ -67,9 +67,9 @@ decimal txt
67 go n d = (n * 10 + fromIntegral (digitToInt d))
67 go n d = (n * 10 + fromIntegral (digitToInt d))
68
68
69-- | Read a hexadecimal integer, consisting of an optional leading
69-- | Read a hexadecimal integer, consisting of an optional leading
70−-- @\"0x\"@ followed by at least one decimal digit. Input is consumed
70+-- @\"0x\"@ followed by at least one hexadecimal digit. Input is
71−-- until a non-hex-digit or end of string is reached. This function
71+-- consumed until a non-hex-digit or end of string is reached.
72−-- is case insensitive.
72+-- This function is case insensitive.
73--
73--
74-- This function does not handle leading sign characters. If you need
74-- This function does not handle leading sign characters. If you need
75-- to handle signed input, use @'signed' 'hexadecimal'@.
75-- to handle signed input, use @'signed' 'hexadecimal'@.
changelog.md+11
@@ -1,3 +1,14 @@
1+* The `toTitle` function now correctly handles letters that
2+ immediately follow puncutation. Before, `"there's"` would turn into
3+ `"There'S"`. Now, it becomes `"There's"`.
4+
5+* The implementation of unstreaming is faster, resulting in operations
6+ such as `map` and `intersperse` speeding up by up to 30%, with
7+ smaller code generated.
8+
9+* The optimised length comparison function is now more likely to be
10+ used after some rewrite rule tweaking.
11+
11.2.2.1
121.2.2.1
2
13
3* The switch to `integer-pure` in 1.2.2.0 was apparently mistaken.
14* The switch to `integer-pure` in 1.2.2.0 was apparently mistaken.
tests/Tests/Properties.hs+23−3
@@ -9,9 +9,9 @@ module Tests.Properties
9 ) where
9 ) where
10
10
11import Control.Applicative ((<$>), (<*>))
11import Control.Applicative ((<$>), (<*>))
12−import Control.Arrow ((***), second)
12+import Control.Arrow ((***), first, second)
13import Data.Bits ((.&.))
13import 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)
15import Data.Int (Int8, Int16, Int32, Int64)
15import Data.Int (Int8, Int16, Int32, Int64)
16import Data.Monoid (Monoid(..))
16import Data.Monoid (Monoid(..))
17import Data.String (IsString(fromString))
17import Data.String (IsString(fromString))
@@ -23,6 +23,7 @@ import Data.Text.Internal.Search (indices)
23import Data.Text.Lazy.Read as TL
23import Data.Text.Lazy.Read as TL
24import Data.Text.Read as T
24import Data.Text.Read as T
25import Data.Word (Word, Word8, Word16, Word32, Word64)
25import Data.Word (Word, Word8, Word16, Word32, Word64)
26+import Data.Maybe (mapMaybe)
26import Numeric (showEFloat, showFFloat, showGFloat, showHex)
27import Numeric (showEFloat, showFFloat, showGFloat, showHex)
27import Prelude hiding (replicate)
28import Prelude hiding (replicate)
28import Test.Framework (Test, testGroup)
29import Test.Framework (Test, testGroup)
@@ -229,6 +230,13 @@ sf_uncons p = (uncons . L.filter p) `eqP`
229 (fmap (second unpackS) . S.uncons . S.filter p)
230 (fmap (second unpackS) . S.uncons . S.filter p)
230t_uncons = uncons `eqP` (fmap (second unpackS) . T.uncons)
231t_uncons = uncons `eqP` (fmap (second unpackS) . T.uncons)
231tl_uncons = uncons `eqP` (fmap (second unpackS) . TL.uncons)
232tl_uncons = uncons `eqP` (fmap (second unpackS) . TL.uncons)
233+
234+unsnoc xs@(_:_) = Just (init xs, last xs)
235+unsnoc [] = Nothing
236+
237+t_unsnoc = unsnoc `eqP` (fmap (first unpackS) . T.unsnoc)
238+tl_unsnoc = unsnoc `eqP` (fmap (first unpackS) . TL.unsnoc)
239+
232s_head = head `eqP` S.head
240s_head = head `eqP` S.head
233sf_head p = (head . L.filter p) `eqP` (S.head . S.filter p)
241sf_head p = (head . L.filter p) `eqP` (S.head . S.filter p)
234t_head = head `eqP` T.head
242t_head = head `eqP` T.head
@@ -326,6 +334,14 @@ t_toUpper_upper t = p (T.toUpper t) >= p t
326 where p = T.length . T.filter isUpper
334 where p = T.length . T.filter isUpper
327tl_toUpper_upper t = p (TL.toUpper t) >= p t
335tl_toUpper_upper t = p (TL.toUpper t) >= p t
328 where p = TL.length . TL.filter isUpper
336 where p = TL.length . TL.filter isUpper
337+t_toTitle_title t = all (<= 1) (caps t)
338+ where caps = fmap (T.length . T.filter isUpper) . T.words . T.toTitle
339+t_toTitle_1stNotLower = and . notLow . T.toTitle . T.filter stable
340+ where notLow = mapMaybe (fmap (not . isLower) . (T.find isLetter)) . T.words
341+ -- Surprise! The Spanish/Portuguese ordinal indicators changed
342+ -- from category Ll (letter, lowercase) to Lo (letter, other)
343+ -- in Unicode 7.0
344+ stable c = c /= '\170' && c /= '\186'
329
345
330justifyLeft k c xs = xs ++ L.replicate (k - length xs) c
346justifyLeft k c xs = xs ++ L.replicate (k - length xs) c
331justifyRight m n xs = L.replicate (m - length xs) n ++ xs
347justifyRight m n xs = L.replicate (m - length xs) n ++ xs
@@ -967,6 +983,8 @@ tests =
967 testProperty "sf_uncons" sf_uncons,
983 testProperty "sf_uncons" sf_uncons,
968 testProperty "t_uncons" t_uncons,
984 testProperty "t_uncons" t_uncons,
969 testProperty "tl_uncons" tl_uncons,
985 testProperty "tl_uncons" tl_uncons,
986+ testProperty "t_unsnoc" t_unsnoc,
987+ testProperty "tl_unsnoc" tl_unsnoc,
970 testProperty "s_head" s_head,
988 testProperty "s_head" s_head,
971 testProperty "sf_head" sf_head,
989 testProperty "sf_head" sf_head,
972 testProperty "t_head" t_head,
990 testProperty "t_head" t_head,
@@ -1030,7 +1048,9 @@ tests =
1030 testProperty "tl_toLower_lower" tl_toLower_lower,
1048 testProperty "tl_toLower_lower" tl_toLower_lower,
1031 testProperty "t_toUpper_length" t_toUpper_length,
1049 testProperty "t_toUpper_length" t_toUpper_length,
1032 testProperty "t_toUpper_upper" t_toUpper_upper,
1050 testProperty "t_toUpper_upper" t_toUpper_upper,
1033− testProperty "tl_toUpper_upper" tl_toUpper_upper
1051+ testProperty "tl_toUpper_upper" tl_toUpper_upper,
1052+ testProperty "t_toTitle_title" t_toTitle_title,
1053+ testProperty "t_toTitle_1stNotLower" t_toTitle_1stNotLower
1034 ],
1054 ],
1035
1055
1036 testGroup "justification" [
1056 testGroup "justification" [
tests/text-tests.cabal+17−2
@@ -20,6 +20,11 @@ flag hpc
20 default: False
20 default: False
21 manual: True
21 manual: True
22
22
23+flag bytestring-builder
24+ description: Depend on the bytestring-builder package for backwards compatibility.
25+ default: False
26+ manual: False
27+
23executable text-tests
28executable text-tests
24 main-is: Tests.hs
29 main-is: Tests.hs
25
30
@@ -48,7 +53,6 @@ executable text-tests
48 HUnit >= 1.2,
53 HUnit >= 1.2,
49 QuickCheck >= 2.7,
54 QuickCheck >= 2.7,
50 base == 4.*,
55 base == 4.*,
51− bytestring,
52 deepseq,
56 deepseq,
53 directory,
57 directory,
54 quickcheck-unicode,
58 quickcheck-unicode,
@@ -58,6 +62,12 @@ executable text-tests
58 test-framework-quickcheck2 >= 0.2,
62 test-framework-quickcheck2 >= 0.2,
59 text-tests
63 text-tests
60
64
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+
61executable text-tests-stdio
71executable text-tests-stdio
62 main-is: Tests/IO.hs
72 main-is: Tests/IO.hs
63
73
@@ -137,7 +147,12 @@ library
137 array,
147 array,
138 base == 4.*,
148 base == 4.*,
139 binary,
149 binary,
140− bytestring,
141 deepseq,
150 deepseq,
142 ghc-prim,
151 ghc-prim,
143 integer-gmp
152 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