packages feed

myers-diff-0.3.0.0: test-lib/TestLib/Generators.hs

{-# OPTIONS_GHC -fno-warn-missing-export-lists #-}

module TestLib.Generators where

import Data.Function
import Data.String.Interpolate
import Data.Text as T
import Test.QuickCheck as Q
import Test.QuickCheck.Instances.Text ()


newtype InsertOrDelete = InsertOrDelete (Text, Text)
  deriving (Show, Eq)
instance Arbitrary InsertOrDelete where
  arbitrary = InsertOrDelete <$> oneof [arbitraryInsert, arbitraryDelete]

newtype DocInsertOrDelete = DocInsertOrDelete (Text, Text)
  deriving (Show, Eq)
instance Arbitrary DocInsertOrDelete where
  arbitrary = DocInsertOrDelete <$> oneof [arbitraryDocInsert, arbitraryDocDelete]

newtype MultiInsertOrDelete = MultiInsertOrDelete (Text, Text)
  deriving (Show, Eq)
instance Arbitrary MultiInsertOrDelete where
  arbitrary = arbitrary >>= (MultiInsertOrDelete <$>) . arbitraryChangesSized

newtype DocMultiInsertOrDelete = DocMultiInsertOrDelete (Text, Text)
  deriving (Show, Eq)
instance Arbitrary DocMultiInsertOrDelete where
  arbitrary = arbitrary >>= (DocMultiInsertOrDelete <$>) . arbitraryChangesSized

-- * Apply a series of changes

arbitraryChangesSized :: Text -> Gen (Text, Text)
arbitraryChangesSized initial = sized $ \n -> flip fix (n, initial) $ \loop -> \case
  (0, x) -> return (initial, x)
  (j, cur) -> do
    -- Prefer inserts to deletes at a 3:1 ratio
    next <- oneof [arbitraryInsertOn cur, arbitraryInsertOn cur, arbitraryInsertOn cur, arbitraryDeleteOn cur]
    loop (j - 1, next)

-- * Inserts and deletes

-- | Arbitrarily insert up to size parameter characters
arbitraryDocInsert :: Gen (Text, Text)
arbitraryDocInsert = arbitraryDoc >>= (\initial -> (initial, ) <$> arbitraryInsertOn initial)

arbitraryInsert :: Gen (Text, Text)
arbitraryInsert = arbitrary >>= (\initial -> (initial, ) <$> arbitraryInsertOn initial)

arbitraryInsertOn :: Text -> Gen Text
arbitraryInsertOn initial = do
  toInsert <- arbitrary

  pos <- chooseInt (0, T.length initial)
  let (x, y) = T.splitAt pos initial

  return (x <> toInsert <> y)


arbitraryDelete :: Gen (Text, Text)
arbitraryDelete = arbitrary >>= (\initial -> (initial, ) <$> arbitraryDeleteOn initial)

arbitraryDocDelete :: Gen (Text, Text)
arbitraryDocDelete = arbitraryDoc >>= (\initial -> (initial, ) <$> arbitraryDeleteOn initial)

-- | Arbitrarily delete up to size parameter characters
arbitraryDeleteOn :: Text -> Gen Text
arbitraryDeleteOn initial = do
  size <- getSize
  amountToDelete :: Int <- chooseInt (1, size)

  pos1 <- chooseInt (0, max 0 (T.length initial - 1))
  pos2 <- chooseInt (pos1, min (pos1 + amountToDelete) (T.length initial))

  let (x, y) = T.splitAt pos1 initial
  let (_, z) = T.splitAt (pos2 - pos1) y

  return (x <> z)

-- * Docs

arbitraryLine :: Gen Text
arbitraryLine = oneof [pure "", arbitrary]

arbitraryDoc :: Gen Text
arbitraryDoc = T.intercalate "\n" <$> listOf arbitraryLine

arbitraryAlphanumericString :: Gen Text
arbitraryAlphanumericString = T.pack <$> listOf alphaNumericChar

alphaNumericChar :: Gen Char
alphaNumericChar = elements (['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ [' '])

file1 :: Text
file1 =
  [__i|foo = 42
       :t foo

       homophones <- readFile "homophones.list"

       putStrLn "HI"

       abc

       import Data.Aeson as A

       -- | Here's a nice comment on bar
       bar :: IO ()
       bar = do
         putStrLn "hello"
         putStrLn "world"
      |]

file2 :: Text
file2 =
  [__i|foo = 42
       :t foo

       homophones <- readFile "homophones.list"

       putStrLn "HI"

       a

       import Data.Aeson as A

       -- | Here's a nice comment on bar
       bar :: IO ()
       bar = do
         putStrLn "hello"
         putStrLn "world"
      |]