yi-rope 0.2.1.12 → 0.2.2.0
raw patch · 4 files changed
+87/−3 lines, 4 files
Files
- bench/MainBenchmarkSuite.hs +23/−1
- src/Yi/Rope.hs +48/−1
- test/Yi/RopeSpec.hs +15/−0
- yi-rope.cabal +1/−1
bench/MainBenchmarkSuite.hs view
@@ -2,10 +2,11 @@ {-# LANGUAGE OverloadedStrings #-} module Main where +import Data.Char (isSpace) import Control.DeepSeq import Criterion.Main import qualified Criterion.Main as C-import Data.Text (unlines, Text, replicate, unpack)+import Data.Text (unlines, Text, replicate) import Prelude hiding (unlines) import qualified Yi.Rope as F @@ -133,6 +134,23 @@ , onTextGroup "words" F.words ] +spanBreakBench :: [Benchmark]+spanBreakBench =+ [ onTextGroup "spanTrue" $ F.span (const True)+ , onTextGroup "spanFalse" $ F.span (const False)+ , onTextGroup "spanSpace" $ F.span isSpace+ , onTextGroup "breakTrue" $ F.break (const True)+ , onTextGroup "breakFalse" $ F.break (const False)+ , onTextGroup "breakSpace" $ F.break isSpace+ ]++foldBench :: [Benchmark]+foldBench =+ [ onTextGroup "foldCount" $ F.foldl' (\x _ -> x + 1) (0 :: Integer)+ , onTextGroup "foldId" $ F.foldl' F.snoc F.empty+ , onTextGroup "foldReverse" $ F.foldl' (\x y -> F.cons y x) F.empty+ ]+ main :: IO () main = defaultMain $ [ onIntGroup "drop" F.drop@@ -157,5 +175,9 @@ , onTextGroup "any bad, (== '×')" $ F.any (== '×') , onTextGroup "all OK (/= '×')" $ F.all (== '×') , onTextGroup "all bad, (== '中')" $ F.all (== '中')+ , onTextGroup "init" F.init+ , onTextGroup "tail" F.tail ] ++ splitBench ++ wordsBench+ ++ spanBreakBench+ ++ foldBench
src/Yi/Rope.hs view
@@ -44,7 +44,8 @@ Yi.Rope.intercalate, Yi.Rope.intersperse, Yi.Rope.filter, Yi.Rope.map, Yi.Rope.words, Yi.Rope.unwords,- Yi.Rope.split,+ Yi.Rope.split, Yi.Rope.init, Yi.Rope.tail,+ Yi.Rope.span, Yi.Rope.break, Yi.Rope.foldl', -- * IO Yi.Rope.readFile, Yi.Rope.readFile', Yi.Rope.writeFile,@@ -279,6 +280,22 @@ EmptyR -> Nothing _ :> Chunk _ x -> if TX.null x then Nothing else Just (TX.last x) +-- | Takes every character but the last one: returns Nothing on empty+-- string.+init :: YiString -> Maybe YiString+init (YiString t) = case viewr t of+ EmptyR -> Nothing+ ts :> Chunk 0 _ -> Yi.Rope.init (YiString ts)+ ts :> Chunk l x -> Just . YiString $ ts |- Chunk (l - 1) (TX.init x)++-- | Takes the tail of the underlying string. If the string is empty+-- to begin with, returns Nothing.+tail :: YiString -> Maybe YiString+tail (YiString t) = case viewr t of+ EmptyR -> Nothing+ ts :> Chunk 0 _ -> Yi.Rope.tail (YiString ts)+ ts :> Chunk l x -> Just . YiString $ ts |- Chunk (l - 1) (TX.tail x)+ -- | Splits the string at given character position. -- -- If @position <= 0@ then the left string is empty and the right string@@ -393,6 +410,24 @@ r = TX.reverse . TX.takeWhile p . TX.reverse $ x l' = TX.length r ++-- | Returns a pair whose first element is the longest prefix+-- (possibly empty) of t of elements that satisfy p, and whose second+-- is the remainder of the string. See also 'TX.span'.+--+-- This implementation uses 'Yi.Rope.splitAt' which actually is just+-- as fast as hand-unrolling the tree. GHC sure is great!+span :: (Char -> Bool) -> YiString -> (YiString, YiString)+span p y = let x = Yi.Rope.takeWhile p y+ in case Yi.Rope.splitAt (Yi.Rope.length x) y of+ -- Re-using ‘x’ seems to gain us a minor performance+ -- boost.+ (_, y') -> (x, y')++-- | Just like 'Yi.Rope.span' but with the predicate negated.+break :: (Char -> Bool) -> YiString -> (YiString, YiString)+break p = Yi.Rope.span (not . p)+ -- | Concatenates the list of 'YiString's after inserting the -- user-provided 'YiString' between the elements. --@@ -644,6 +679,18 @@ -- in itself. split :: (Char -> Bool) -> YiString -> [YiString] split p = fmap fromText . TX.split p . toText++-- | Left fold.+--+-- Benchmarks show that folding is actually Pretty Damn Slow™: consider+-- whether folding is really the best thing to use in your scenario.+foldl' :: (a -> Char -> a) -> a -> YiString -> a+foldl' f a = go a . fromRope+ where+ go acc t = case viewl t of+ EmptyL -> acc+ Chunk _ x :< ts -> let r = TX.foldl' f acc x+ in r `seq` go r ts -- | Helper function doing conversions of to and from underlying -- 'TX.Text'. You should aim to implement everything in terms of
test/Yi/RopeSpec.hs view
@@ -84,3 +84,18 @@ R.toText (R.unwords (R.fromText <$> t)) `shouldBe` T.unwords t prop "\\p -> R.split p ~ T.split p $ isUpper" $ \t -> R.toText <$> R.split isUpper (R.fromText t) `shouldBe` T.split isUpper t+ prop "non-empty s ⊢ R.init s ~ T.init s" $ \t ->+ let t' = t `T.snoc` 'a' -- ensure non-empty+ in (fmap R.toText . R.init . R.fromText) t' `shouldBe` (Just . T.init) t'+ prop "non-empty s ⊢ R.tail s ~ T.tail s" $ \t ->+ let t' = t `T.snoc` 'a'+ in (fmap R.toText . R.tail . R.fromText) t' `shouldBe` (Just . T.tail) t'+ prop "\\p -> R.span p ~ T.span p $ isUpper" $ \t ->+ let (f, s) `shouldBeT` y = (R.toText f, R.toText s) `shouldBe` y+ in (R.span isUpper . R.fromText) t `shouldBeT` T.span isUpper t+ prop "\\p -> R.break p ~ T.break p $ isUpper" $ \t ->+ let (f, s) `shouldBeT` y = (R.toText f, R.toText s) `shouldBe` y+ in (R.break isUpper . R.fromText) t `shouldBeT` T.break isUpper t+ prop "\\p -> R.foldl' p ~ T.foldl' p $ \\x _ -> x + 1" $ \t ->+ let f x _ = x + (1 :: Integer)+ in (R.foldl' f 0 . R.fromText) t `shouldBe` T.foldl' f 0 t
yi-rope.cabal view
@@ -1,5 +1,5 @@ name: yi-rope-version: 0.2.1.12+version: 0.2.2.0 synopsis: A rope data structure used by Yi description: A rope data structure used by Yi license: GPL-3