packages feed

text-rope-0.3: bench/Main.hs

-- |
-- Copyright:   (c) 2021-2022 Andrew Lelechenko
-- Licence:     BSD3
-- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>

{-# LANGUAGE CPP #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

module Main
  ( main
  ) where

import Prelude (fromIntegral, (<$>), (*))
import Paths_text_rope (getDataFileName)
import Data.Function (($), (.))
import Data.Int (Int)
import Data.List (foldl', take, zip, length)
import Data.Maybe (fromJust)
import Data.Monoid (Monoid, mappend)
import Data.Proxy (Proxy(..))
import Data.Semigroup ((<>))
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Data.Word (Word)
import System.IO (IO)
import System.IO.Unsafe (unsafePerformIO)
import System.Random (randomRs, mkStdGen)
import Test.Tasty.Bench (defaultMain, bgroup, bench, nf, bcompare)

import qualified Data.Text.Rope as CharRope
import qualified Data.Text.Utf8.Rope as Utf8Rope
import qualified Data.Text.Utf16.Rope as Utf16Rope
import qualified Data.Text.Mixed.Rope as Mixed
#ifdef MIN_VERSION_core_text
import qualified Core.Text.Rope as CoreText
#endif
#ifdef MIN_VERSION_rope_utf16_splay
import qualified Data.Rope.UTF16 as RopeSplay
#endif
#ifdef MIN_VERSION_yi_rope
import qualified Yi.Rope as YiRope
#endif

data CodePoint
data Utf8
data Utf16

main :: IO ()
main = defaultMain
  [ bgroup "Split at position"
    [ bgroup "Unicode"
      [ bench "text-rope" $ nf (editByPosition (Proxy @CodePoint) (Proxy @CharRope.Rope)) txt
      , bench "text-rope-mixed" $ nf (editByPosition (Proxy @CodePoint) (Proxy @Mixed.Rope)) txt
#ifdef MIN_VERSION_yi_rope
      , bcompare "$NF == \"text-rope\" && $(NF-1) == \"Unicode\" && $(NF-2) == \"Split at position\""
      $ bench "yi-rope" $ nf (editByPosition (Proxy @CodePoint) (Proxy @YiRope.YiString)) txt
#endif
      ]
    , bgroup "UTF-16"
      [ bench "text-rope" $ nf (editByPosition (Proxy @Utf16) (Proxy @Utf16Rope.Rope)) txt
      , bench "text-rope-mixed" $ nf (editByPosition (Proxy @Utf16) (Proxy @Mixed.Rope)) txt
#ifdef MIN_VERSION_rope_utf16_splay
      , bcompare "$NF == \"text-rope\" && $(NF-1) == \"UTF-16\" && $(NF-2) == \"Split at position\""
      $ bench "rope-utf16-splay" $ nf (editByPosition (Proxy @Utf16) (Proxy @RopeSplay.Rope)) txt
#endif
      ]
    , bgroup "UTF-8"
      [ bench "text-rope" $ nf (editByPosition (Proxy @Utf8) (Proxy @Utf8Rope.Rope)) txtUtf8
      , bench "text-rope-mixed" $ nf (editByPosition (Proxy @Utf8) (Proxy @Mixed.Rope)) txtUtf8
      ]
    ]
  , bgroup "Split at offset"
    [ bgroup "Unicode"
      [ bench "text-rope" $ nf (editByOffset (Proxy @CodePoint) (Proxy @CharRope.Rope)) txt
      , bench "text-rope-mixed" $ nf (editByOffset (Proxy @CodePoint) (Proxy @Mixed.Rope)) txt
#ifdef MIN_VERSION_core_text
      , bcompare "$NF == \"text-rope\" && $(NF-1) == \"Unicode\" && $(NF-2) == \"Split at offset\""
      $ bench "core-text" $ nf (editByOffset (Proxy @CodePoint) (Proxy @CoreText.Rope)) txt
#endif
#ifdef MIN_VERSION_yi_rope
      , bcompare "$NF == \"text-rope\" && $(NF-1) == \"Unicode\" && $(NF-2) == \"Split at offset\""
      $ bench "yi-rope" $ nf (editByOffset (Proxy @Utf16) (Proxy @YiRope.YiString)) txt
#endif
      ]
    , bgroup "UTF-16"
      [ bench "text-rope" $ nf (editByOffset (Proxy @Utf16) (Proxy @Utf16Rope.Rope)) txt
      , bench "text-rope-mixed" $ nf (editByOffset (Proxy @Utf16) (Proxy @Mixed.Rope)) txt
#ifdef MIN_VERSION_rope_utf16_splay
      , bcompare "$NF == \"text-rope\" && $(NF-1) == \"UTF-16\" && $(NF-2) == \"Split at offset\""
      $ bench "rope-utf16-splay" $ nf (editByOffset (Proxy @Utf16) (Proxy @RopeSplay.Rope)) txt
#endif
      ]
    , bgroup "UTF-8"
      [ bench "text-rope" $ nf (editByOffset (Proxy @Utf8) (Proxy @Utf8Rope.Rope)) txtUtf8
      , bench "text-rope-mixed" $ nf (editByOffset (Proxy @Utf8) (Proxy @Mixed.Rope)) txtUtf8
      ]
    ]
  ]

scale :: Int
scale = 1

txt :: T.Text
txt = unsafePerformIO $ do
  fn <- getDataFileName "bench/bench.txt"
  T.replicate scale <$> T.readFile fn
{-# NOINLINE txt #-}

txtUtf8 :: T.Text
txtUtf8 = unsafePerformIO $ do
  fn <- getDataFileName "bench/bench-utf8.txt"
  T.replicate scale <$> T.readFile fn
{-# NOINLINE txtUtf8 #-}

randomOffsets :: [Word]
randomOffsets = take (1000 * scale) $
  randomRs (0, fromIntegral $ T.length txt) (mkStdGen 33)
{-# NOINLINE randomOffsets #-}

randomPositions :: [(Word, Word)]
randomPositions = take (1000 * scale) $ zip ls cs
  where
    ls = randomRs (0, fromIntegral $ length $ T.lines txt) (mkStdGen 42)
    -- assuming reasonable line length is < 80
    cs = randomRs (0, 80) (mkStdGen 24)
{-# NOINLINE randomPositions #-}

class Monoid a => Textable a where
  fromText :: T.Text -> a
  toText :: a -> T.Text

class Textable t => Splittable u t where
  splitAt :: Proxy u -> Word -> t -> (t, t)

class Splittable u t => SplittableAtPosition u t where
  splitAtPosition :: Proxy u -> Word -> Word -> t -> (t, t)

instance Textable CharRope.Rope where
  fromText = CharRope.fromText
  toText = CharRope.toText

instance Splittable CodePoint CharRope.Rope where
  splitAt _ = CharRope.splitAt

instance SplittableAtPosition CodePoint CharRope.Rope where
  splitAtPosition _ l c = CharRope.splitAtPosition (CharRope.Position l c)

instance Textable Utf8Rope.Rope where
  fromText = Utf8Rope.fromText
  toText = Utf8Rope.toText

instance Splittable Utf8 Utf8Rope.Rope where
  splitAt _ = (fromJust . ) . Utf8Rope.splitAt

instance SplittableAtPosition Utf8 Utf8Rope.Rope where
  splitAtPosition _ l c = fromJust . Utf8Rope.splitAtPosition (Utf8Rope.Position l c)

instance Textable Utf16Rope.Rope where
  fromText = Utf16Rope.fromText
  toText = Utf16Rope.toText

instance Splittable Utf16 Utf16Rope.Rope where
  splitAt _ = (fromJust . ) . Utf16Rope.splitAt

instance SplittableAtPosition Utf16 Utf16Rope.Rope where
  splitAtPosition _ l c = fromJust . Utf16Rope.splitAtPosition (Utf16Rope.Position l c)

instance Textable Mixed.Rope where
  fromText = Mixed.fromText
  toText = Mixed.toText

instance Splittable CodePoint Mixed.Rope where
  splitAt _ = Mixed.charSplitAt

instance SplittableAtPosition CodePoint Mixed.Rope where
  splitAtPosition _ l c = Mixed.charSplitAtPosition (CharRope.Position l c)

instance Splittable Utf8 Mixed.Rope where
  splitAt _ = (fromJust . ) . Mixed.utf8SplitAt

instance SplittableAtPosition Utf8 Mixed.Rope where
  splitAtPosition _ l c = fromJust . Mixed.utf8SplitAtPosition (Utf8Rope.Position l c)

instance Splittable Utf16 Mixed.Rope where
  splitAt _ = (fromJust . ) . Mixed.utf16SplitAt

instance SplittableAtPosition Utf16 Mixed.Rope where
  splitAtPosition _ l c = fromJust . Mixed.utf16SplitAtPosition (Utf16Rope.Position l c)

#ifdef MIN_VERSION_core_text
instance Textable CoreText.Rope where
  fromText = CoreText.intoRope
  toText = CoreText.fromRope

instance SplittableAtPosition CodePoint CoreText.Rope where
  splitAt _ = CoreText.splitRope . fromIntegral
#endif

#ifdef MIN_VERSION_yi_rope
instance Textable YiRope.YiString where
  fromText = YiRope.fromText
  toText = YiRope.toText

instance Splittable CodePoint YiRope.YiString where
  splitAt _ = YiRope.splitAt . fromIntegral

instance SplittableAtPosition CodePoint YiRope.YiString where
  splitAtPosition _ l c orig = (before `mappend` mid, after)
    where
      (before, after') = YiRope.splitAtLine (fromIntegral l) orig
      (mid, after) = YiRope.splitAt (fromIntegral c) after'
#endif

#ifdef MIN_VERSION_rope_utf16_splay
instance Textable RopeSplay.Rope where
  fromText = RopeSplay.fromText
  toText = RopeSplay.toText

instance Splittable Utf16 RopeSplay.Rope where
  splitAt _ = RopeSplay.splitAt . fromIntegral

instance SplittableAtPosition Utf16 RopeSplay.Rope where
  splitAtPosition _ l c orig = RopeSplay.splitAt k orig
    where
      k = RopeSplay.rowColumnCodeUnits (RopeSplay.RowColumn (fromIntegral l) (fromIntegral c)) orig
#endif

editByOffset :: forall u t. Splittable u t => Proxy u -> Proxy t -> T.Text -> T.Text
editByOffset _ _ txt = (toText @t) $ foldl' edit (fromText txt) randomOffsets
  where
    edit orig c = before `mappend` mid `mappend` after
      where
        (before, after') = splitAt (Proxy @u) c orig
        -- edit 10 characters
        (mid, after) = splitAt (Proxy @u) 10 after'

editByPosition :: forall u t. SplittableAtPosition u t => Proxy u -> Proxy t -> T.Text -> T.Text
editByPosition _ _ txt = (toText @t) $ foldl' edit (fromText txt) randomPositions
  where
    edit orig (l, c) = before `mappend` mid `mappend` after
      where
        (before, after') = splitAtPosition (Proxy @u) l c orig
        -- edit 10 characters
        (mid, after) = splitAt (Proxy @u) 10 after'