on master help sign in
Implement the other API enhancements in the Lazy module.
9858770Bryan O'Sullivan
Data/Text/Lazy.hs+149−16
@@ -1,4 +1,5 @@
1{-# OPTIONS_GHC -fno-warn-orphans #-}
1{-# OPTIONS_GHC -fno-warn-orphans #-}
2+{-# LANGUAGE BangPatterns #-}
2-- |
3-- |
3-- Module : Data.Text.Lazy
4-- Module : Data.Text.Lazy
4-- Copyright : (c) Bryan O'Sullivan 2009
5-- Copyright : (c) Bryan O'Sullivan 2009
@@ -57,6 +58,7 @@ module Data.Text.Lazy
57 , intersperse
58 , intersperse
58 , transpose
59 , transpose
59 , reverse
60 , reverse
61+ , replace
60
62
61 -- ** Case conversion
63 -- ** Case conversion
62 -- $case
64 -- $case
@@ -109,6 +111,11 @@ module Data.Text.Lazy
109 , drop
111 , drop
110 , takeWhile
112 , takeWhile
111 , dropWhile
113 , dropWhile
114+ , dropWhileEnd
115+ , dropAround
116+ , strip
117+ , stripStart
118+ , stripEnd
112 , splitAt
119 , splitAt
113 , span
120 , span
114 , break
121 , break
@@ -118,7 +125,10 @@ module Data.Text.Lazy
118 , tails
125 , tails
119
126
120 -- ** Breaking into many substrings
127 -- ** Breaking into many substrings
128+ -- $split
121 , split
129 , split
130+ , splitTimes
131+ , splitTimesEnd
122 , splitWith
132 , splitWith
123 , chunksOf
133 , chunksOf
124 -- , breakSubstring
134 -- , breakSubstring
@@ -160,7 +170,7 @@ module Data.Text.Lazy
160
170
161import Prelude (Char, Bool(..), Int, Maybe(..), String,
171import Prelude (Char, Bool(..), Int, Maybe(..), String,
162 Eq(..), Ord(..), Read(..), Show(..),
172 Eq(..), Ord(..), Read(..), Show(..),
163− (&&), (+), (-), (.), ($), (++),
173+ (&&), (||), (+), (-), (.), ($), (++),
164 div, flip, fromIntegral, not, otherwise)
174 div, flip, fromIntegral, not, otherwise)
165import qualified Prelude as P
175import qualified Prelude as P
166import Data.Int (Int64)
176import Data.Int (Int64)
@@ -430,6 +440,14 @@ reverse = rev Empty
430 where rev a Empty = a
440 where rev a Empty = a
431 rev a (Chunk t ts) = rev (Chunk (T.reverse t) a) ts
441 rev a (Chunk t ts) = rev (Chunk (T.reverse t) a) ts
432
442
443+-- | /O(m)*O(n)/ Replace every occurrence of one substring with another.
444+replace :: Text -- ^ Text to search for
445+ -> Text -- ^ Replacement text
446+ -> Text -- ^ Input text
447+ -> Text
448+replace s d = intercalate d . split s
449+{-# INLINE replace #-}
450+
433-- ----------------------------------------------------------------------------
451-- ----------------------------------------------------------------------------
434-- ** Case conversions (folds)
452-- ** Case conversions (folds)
435
453
@@ -662,7 +680,7 @@ take i t0 = take' i t0
662-- | /O(n)/ 'drop' @n@, applied to a 'Text', returns the suffix of the
680-- | /O(n)/ 'drop' @n@, applied to a 'Text', returns the suffix of the
663-- 'Text' of length @n@, or the empty 'Text' if @n@ is greater than the
681-- 'Text' of length @n@, or the empty 'Text' if @n@ is greater than the
664-- length of the 'Text'. Subject to fusion.
682-- length of the 'Text'. Subject to fusion.
665−drop :: Int -> Text -> Text
683+drop :: Int64 -> Text -> Text
666drop i t0
684drop i t0
667 | i <= 0 = t0
685 | i <= 0 = t0
668 | otherwise = drop' i t0
686 | otherwise = drop' i t0
@@ -718,6 +736,52 @@ dropWhile p t0 = dropWhile' t0
718"LAZY TEXT dropWhile -> unfused" [1] forall p t.
736"LAZY TEXT dropWhile -> unfused" [1] forall p t.
719 unstream (S.dropWhile p (stream t)) = dropWhile p t
737 unstream (S.dropWhile p (stream t)) = dropWhile p t
720 #-}
738 #-}
739+-- | /O(n)/ 'dropWhileEnd' @p@ @t@ returns the prefix remaining after
740+-- dropping characters that fail the predicate @p@ from the end of
741+-- @t@.
742+-- Examples:
743+--
744+-- > dropWhileEnd (=='.') "foo..." == "foo"
745+dropWhileEnd :: (Char -> Bool) -> Text -> Text
746+dropWhileEnd p = go
747+ where go Empty = Empty
748+ go (Chunk t Empty) = if T.null t'
749+ then Empty
750+ else Chunk t' Empty
751+ where t' = T.dropWhileEnd p t
752+ go (Chunk t ts) = case go ts of
753+ Empty -> go (Chunk t Empty)
754+ ts' -> Chunk t ts'
755+{-# INLINE dropWhileEnd #-}
756+
757+-- | /O(n)/ 'dropAround' @p@ @t@ returns the substring remaining after
758+-- dropping characters that fail the predicate @p@ from both the
759+-- beginning and end of @t@. Subject to fusion.
760+dropAround :: (Char -> Bool) -> Text -> Text
761+dropAround p = dropWhile p . dropWhileEnd p
762+{-# INLINE [1] dropAround #-}
763+
764+-- | /O(n)/ Remove leading white space from a string. Equivalent to:
765+--
766+-- > dropWhile isSpace
767+stripStart :: Text -> Text
768+stripStart = dropWhile isSpace
769+{-# INLINE [1] stripStart #-}
770+
771+-- | /O(n)/ Remove trailing white space from a string. Equivalent to:
772+--
773+-- > dropWhileEnd isSpace
774+stripEnd :: Text -> Text
775+stripEnd = dropWhileEnd isSpace
776+{-# INLINE [1] stripEnd #-}
777+
778+-- | /O(n)/ Remove leading and trailing white space from a string.
779+-- Equivalent to:
780+--
781+-- > dropAround isSpace
782+strip :: Text -> Text
783+strip = dropAround isSpace
784+{-# INLINE [1] strip #-}
721
785
722-- | /O(n)/ 'splitAt' @n t@ returns a pair whose first element is a
786-- | /O(n)/ 'splitAt' @n t@ returns a pair whose first element is a
723-- prefix of @t@ of length @n@, and whose second is the remainder of
787-- prefix of @t@ of length @n@, and whose second is the remainder of
@@ -791,24 +855,93 @@ tails ts@(Chunk t ts')
791 | T.length t == 1 = ts : tails ts'
855 | T.length t == 1 = ts : tails ts'
792 | otherwise = ts : tails (Chunk (T.unsafeTail t) ts')
856 | otherwise = ts : tails (Chunk (T.unsafeTail t) ts')
793
857
858+-- $split
859+--
860+-- Splitting functions in this library do not perform character-wise
861+-- copies to create substrings; they just construct new 'Text's that
862+-- are slices of the original.
863+
794−-- | /O(n)/ Break a 'Text' into pieces separated by the byte
864+-- | /O(m)*O(n)/ Break a 'Text' into pieces separated by the first
795−-- argument, consuming the delimiter. I.e.
865+-- 'Text' argument, consuming the delimiter. Examples:
796--
866--
797−-- > split '\n' "a\nb\nd\ne" == ["a","b","d","e"]
867+-- > split "\r\n" "a\r\nb\r\nd\r\ne" == ["a","b","d","e"]
798−-- > split 'a' "aXaXaXa" == ["","X","X","X",""]
868+-- > split "aaa" "aaaXaaaXaaaXaaa" == ["","X","X","X",""]
799−-- > split 'x' "x" == ["",""]
869+-- > split "x" "x" == ["",""]
800--
870--
801-- and
871-- and
802--
872--
803−-- > intercalate [c] . split c == id
873+-- > intercalate s . split s == id
874+-- > split (singleton c) == splitWith (==c)
875+split :: Text -- ^ Text to split on
876+ -> Text -- ^ Input text
877+ -> [Text]
878+split pat src0
879+ | l == 0 = [src0]
804−-- > split == splitWith . (==)
880+ | l == 1 = splitWith (== (head pat)) src0
805−--
881+ | otherwise = go src0
806−-- As for all splitting functions in this library, this function does
882+ where
807−-- not copy the substrings, it just constructs new 'Text's that are
883+ l = length pat
808−-- slices of the original.
884+ go src = search 0 src
809−split :: Char -> Text -> [Text]
885+ where
810−split c = splitWith (==c)
886+ search !n !s
887+ | null s = [src] -- not found
888+ | pat `isPrefixOf` s = take n src : go (drop l s)
889+ | otherwise = search (n+1) (tail s)
890+{-# INLINE [1] split #-}
891+
892+{-# RULES
893+"LAZY TEXT split/singleton -> splitWith/==" [~1] forall c t.
894+ split (singleton c) t = splitWith (==c) t
895+ #-}
896+
897+-- | /O(m)*O(n)/ Break a 'Text' into pieces at most @k@ times,
898+-- treating the first 'Text' argument as the delimiter to break on,
899+-- and consuming the delimiter. The last element of the list contains
900+-- the remaining text after the number of times to split has been
901+-- reached. A value of zero or less for @k@ causes no splitting to
902+-- occur.
903+--
904+-- Examples:
905+--
906+-- > splitTimes 0 "//" "a//b//c" == ["a//b//c"]
907+-- > splitTimes 2 ":" "a:b:c:d:e" == ["a","b","c:d:e"]
908+-- > splitTimes 100 "???" "a????b" == ["a","?b"]
909+--
910+-- and
911+--
912+-- > intercalate s . splitTimes k s == id
913+splitTimes :: Int64 -- ^ Maximum number of times to split
914+ -> Text -- ^ Text to split on
915+ -> Text -- ^ Input text
916+ -> [Text]
917+splitTimes k pat src0
918+ | k <= 0 || l == 0 = [src0]
919+ | otherwise = go k src0
920+ where
921+ l = length pat
922+ go !i src = search 0 src
923+ where
924+ search !n !s
925+ | i == 0 || null s = [src] -- not found or limit reached
926+ | pat `isPrefixOf` s = take n src : go (i-1) (drop l s)
927+ | otherwise = search (n+1) (tail s)
928+{-# INLINE splitTimes #-}
929+
930+-- | /O(m)*O(n)/ Break a 'Text' into pieces at most @k@ times, like
931+-- 'splitTimes', but start from the end of the input and work towards
932+-- the start.
933+--
934+-- Examples:
935+--
936+-- > splitTimes 2 "::" "a::b::c::d::e" == ["a","b","c::d::e"]
937+-- > splitTimesEnd 2 "::" "a::b::c::d::e" == ["a::b::c","d","e"]
938+splitTimesEnd :: Int64 -- ^ Maximum number of times to split
939+ -> Text -- ^ Text to split on
940+ -> Text -- ^ Input text
941+ -> [Text]
942+splitTimesEnd k pat src =
943+ L.reverse . L.map reverse $ splitTimes k (reverse pat) (reverse src)
811−{-# INLINE split #-}
944+{-# INLINE splitTimesEnd #-}
812
945
813-- | /O(n)/ Splits a 'Text' into components delimited by separators,
946-- | /O(n)/ Splits a 'Text' into components delimited by separators,
814-- where the predicate returns True for a separator element. The
947-- where the predicate returns True for a separator element. The
tests/Properties.hs+14−2
@@ -205,6 +205,7 @@ tl_reverse = L.reverse `eqP` (unpackS . TL.reverse)
205t_reverse_short n = L.reverse `eqP` (unpackS . S.reverse . shorten n . S.stream)
205t_reverse_short n = L.reverse `eqP` (unpackS . S.reverse . shorten n . S.stream)
206
206
207t_replace s d = (L.intercalate d . split s) `eqP` (unpackS . T.replace (T.pack s) (T.pack d))
207t_replace s d = (L.intercalate d . split s) `eqP` (unpackS . T.replace (T.pack s) (T.pack d))
208+tl_replace s d = (L.intercalate d . split s) `eqP` (unpackS . TL.replace (TL.pack s) (TL.pack d))
208
209
209split :: (Eq a) => [a] -> [a] -> [[a]]
210split :: (Eq a) => [a] -> [a] -> [[a]]
210split pat src0
211split pat src0
@@ -333,7 +334,7 @@ tl_take n = L.take n `eqP` (unpackS . TL.take (fromIntegral n))
333s_drop n = L.drop n `eqP` (unpackS . S.drop n)
334s_drop n = L.drop n `eqP` (unpackS . S.drop n)
334sf_drop p n = (L.drop n . L.filter p) `eqP` (unpackS . S.drop n . S.filter p)
335sf_drop p n = (L.drop n . L.filter p) `eqP` (unpackS . S.drop n . S.filter p)
335t_drop n = L.drop n `eqP` (unpackS . T.drop n)
336t_drop n = L.drop n `eqP` (unpackS . T.drop n)
336−tl_drop n = L.drop n `eqP` (unpackS . TL.drop n)
337+tl_drop n = L.drop n `eqP` (unpackS . TL.drop (fromIntegral n))
337s_takeWhile p = L.takeWhile p `eqP` (unpackS . S.takeWhile p)
338s_takeWhile p = L.takeWhile p `eqP` (unpackS . S.takeWhile p)
338sf_takeWhile q p = (L.takeWhile p . L.filter q) `eqP` (unpackS . S.takeWhile p . S.filter q)
339sf_takeWhile q p = (L.takeWhile p . L.filter q) `eqP` (unpackS . S.takeWhile p . S.filter q)
339t_takeWhile p = L.takeWhile p `eqP` (unpackS . T.takeWhile p)
340t_takeWhile p = L.takeWhile p `eqP` (unpackS . T.takeWhile p)
@@ -343,10 +344,15 @@ sf_dropWhile q p = (L.dropWhile p . L.filter q) `eqP` (unpackS . S.dropWhile p
343t_dropWhile p = L.dropWhile p `eqP` (unpackS . T.dropWhile p)
344t_dropWhile p = L.dropWhile p `eqP` (unpackS . T.dropWhile p)
344tl_dropWhile p = L.dropWhile p `eqP` (unpackS . S.dropWhile p)
345tl_dropWhile p = L.dropWhile p `eqP` (unpackS . S.dropWhile p)
345t_dropWhileEnd p = (L.reverse . L.dropWhile p . L.reverse) `eqP` (unpackS . T.dropWhileEnd p)
346t_dropWhileEnd p = (L.reverse . L.dropWhile p . L.reverse) `eqP` (unpackS . T.dropWhileEnd p)
347+tl_dropWhileEnd p = (L.reverse . L.dropWhile p . L.reverse) `eqP` (unpackS . TL.dropWhileEnd p)
346t_dropAround p = (L.dropWhile p . L.reverse . L.dropWhile p . L.reverse) `eqP` (unpackS . T.dropAround p)
348t_dropAround p = (L.dropWhile p . L.reverse . L.dropWhile p . L.reverse) `eqP` (unpackS . T.dropAround p)
349+tl_dropAround p = (L.dropWhile p . L.reverse . L.dropWhile p . L.reverse) `eqP` (unpackS . TL.dropAround p)
347t_stripStart = T.dropWhile isSpace `eq` T.stripStart
350t_stripStart = T.dropWhile isSpace `eq` T.stripStart
351+tl_stripStart = TL.dropWhile isSpace `eq` TL.stripStart
348t_stripEnd = T.dropWhileEnd isSpace `eq` T.stripEnd
352t_stripEnd = T.dropWhileEnd isSpace `eq` T.stripEnd
353+tl_stripEnd = TL.dropWhileEnd isSpace `eq` TL.stripEnd
349t_strip = T.dropAround isSpace `eq` T.strip
354t_strip = T.dropAround isSpace `eq` T.strip
355+tl_strip = TL.dropAround isSpace `eq` TL.strip
350t_splitAt n = L.splitAt n `eqP` (unpack2 . T.splitAt n)
356t_splitAt n = L.splitAt n `eqP` (unpack2 . T.splitAt n)
351tl_splitAt n = L.splitAt n `eqP` (unpack2 . TL.splitAt (fromIntegral n))
357tl_splitAt n = L.splitAt n `eqP` (unpack2 . TL.splitAt (fromIntegral n))
352t_span p = L.span p `eqP` (unpack2 . T.span p)
358t_span p = L.span p `eqP` (unpack2 . T.span p)
@@ -370,7 +376,7 @@ t_splitTimes_split k t = T.splitTimes k t `eq` \u ->
370 (a,b) -> a ++ [T.intercalate t b]
376 (a,b) -> a ++ [T.intercalate t b]
371t_splitTimesEnd_i k t = id `eq` (T.intercalate t . T.splitTimesEnd k t)
377t_splitTimesEnd_i k t = id `eq` (T.intercalate t . T.splitTimesEnd k t)
372t_splitTimesEnd_split t = T.splitTimesEnd maxBound t `eq` T.split t
378t_splitTimesEnd_split t = T.splitTimesEnd maxBound t `eq` T.split t
373−tl_split_i c = id `eq` (TL.intercalate (TL.singleton c) . TL.split c)
379+tl_split_i t = id `eq` (TL.intercalate t . TL.split t)
374
380
375t_splitWith p = splitWith p `eqP` (map unpackS . T.splitWith p)
381t_splitWith p = splitWith p `eqP` (map unpackS . T.splitWith p)
376t_splitWith_count c = (L.length . T.splitWith (==c)) `eq` ((1+) . T.count c)
382t_splitWith_count c = (L.length . T.splitWith (==c)) `eq` ((1+) . T.count c)
@@ -603,6 +609,7 @@ tests = [
603 testProperty "tl_reverse" tl_reverse,
609 testProperty "tl_reverse" tl_reverse,
604 testProperty "t_reverse_short" t_reverse_short,
610 testProperty "t_reverse_short" t_reverse_short,
605 testProperty "t_replace" t_replace,
611 testProperty "t_replace" t_replace,
612+ testProperty "tl_replace" tl_replace,
606
613
607 testGroup "case conversion" [
614 testGroup "case conversion" [
608 testProperty "s_toCaseFold_length" s_toCaseFold_length,
615 testProperty "s_toCaseFold_length" s_toCaseFold_length,
@@ -715,10 +722,15 @@ tests = [
715 testProperty "t_dropWhile" t_dropWhile,
722 testProperty "t_dropWhile" t_dropWhile,
716 testProperty "tl_dropWhile" tl_dropWhile,
723 testProperty "tl_dropWhile" tl_dropWhile,
717 testProperty "t_dropWhileEnd" t_dropWhileEnd,
724 testProperty "t_dropWhileEnd" t_dropWhileEnd,
725+ testProperty "tl_dropWhileEnd" tl_dropWhileEnd,
718 testProperty "t_dropAround" t_dropAround,
726 testProperty "t_dropAround" t_dropAround,
727+ testProperty "tl_dropAround" tl_dropAround,
719 testProperty "t_stripStart" t_stripStart,
728 testProperty "t_stripStart" t_stripStart,
729+ testProperty "tl_stripStart" tl_stripStart,
720 testProperty "t_stripEnd" t_stripEnd,
730 testProperty "t_stripEnd" t_stripEnd,
731+ testProperty "tl_stripEnd" tl_stripEnd,
721 testProperty "t_strip" t_strip,
732 testProperty "t_strip" t_strip,
733+ testProperty "tl_strip" tl_strip,
722 testProperty "t_splitAt" t_splitAt,
734 testProperty "t_splitAt" t_splitAt,
723 testProperty "tl_splitAt" tl_splitAt,
735 testProperty "tl_splitAt" tl_splitAt,
724 testProperty "t_span" t_span,
736 testProperty "t_span" t_span,