packages feed

integer-types-0.0.0.1: test/Integer/Gen.hs

module Integer.Gen
  (
    GenIntegral (integral),
    GenFinite (finite),
    astronomical,
  )
  where

import Essentials

import Data.Int (Int)
import Data.Word (Word)
import Integer (BoundedBelow (..), Integer, Natural, Positive, Sign (..),
                Signed (..))

import qualified Hedgehog
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import qualified Prelude as Bounded (Bounded (..))
import qualified Prelude as Num (Integral (..), Num (..), (+), (^))

---

class (Num.Integral a, Show a) => GenIntegral a
  where
    -- | Generators for 'Integer', 'Natural', 'Positive',
    -- or 'Signed' selected from one of three methods:
    --
    -- * small numbers (magnitude less than ten)
    -- * large numbers (well in excess of 64-bit)
    -- * numbers at or around a bound of 'Int' or 'Word'
    integral :: Hedgehog.Gen a

instance GenIntegral Integer  where integral = integer
instance GenIntegral Natural  where integral = boundedBelow
instance GenIntegral Positive where integral = boundedBelow
instance GenIntegral Signed   where integral = signed

---

class (Num.Integral a, Bounded.Bounded a, Show a) => GenFinite a
  where
    finite :: Hedgehog.Gen a

instance GenFinite Int where finite = defaultFinite

instance GenFinite Word where finite = defaultFinite

defaultFinite :: (Num.Integral a, Bounded.Bounded a) => Hedgehog.Gen a
defaultFinite = Gen.choice
    [ Gen.integral $ Range.linear Bounded.minBound Bounded.maxBound
    , Gen.integral $ Range.linear Bounded.maxBound Bounded.minBound
    ]

---

smol :: Num.Integral a => a
smol = 10

astronomical :: Num.Integral a => a
astronomical = 2 Num.^ (99 :: Integer)

bigRange :: Num.Integral a => Range.Range a
bigRange = Range.exponential smol astronomical

---

integer :: Hedgehog.Gen Integer
integer = Gen.choice [smolInteger, nearFiniteBoundInteger, bigInteger]

smolInteger :: Hedgehog.Gen Integer
smolInteger = Gen.integral $ Range.linearFrom 0 (Num.negate smol) smol

bigInteger :: Hedgehog.Gen Integer
bigInteger = Gen.element [id, Num.negate] <*> Gen.integral bigRange

nearFiniteBoundInteger :: Hedgehog.Gen Integer
nearFiniteBoundInteger = Gen.element [id, Num.negate] <*> nearPositiveFiniteBound

---

boundedBelow :: forall a. (BoundedBelow a, Num.Integral a) => Hedgehog.Gen a
boundedBelow = Gen.choice [smolBoundedBelow, nearPositiveFiniteBound, bigBoundedBelow]

smolBoundedBelow :: forall a. (BoundedBelow a, Num.Integral a) => Hedgehog.Gen a
smolBoundedBelow = fmap Num.fromInteger $ Gen.integral $ Range.linear (Num.toInteger $ minBound @a) smol

bigBoundedBelow :: forall a. (BoundedBelow a, Num.Integral a) => Hedgehog.Gen a
bigBoundedBelow = fmap Num.fromInteger $ Gen.integral bigRange

nearPositiveFiniteBound :: forall a. Num.Integral a => Hedgehog.Gen a
nearPositiveFiniteBound = fmap Num.fromInteger $
    pure (Num.+)
    <*> Gen.element
      [ Num.toInteger (Bounded.maxBound :: Int)
      , Num.toInteger (Bounded.maxBound :: Word)
      ]
    <*> smolInteger

---

signed :: Hedgehog.Gen Signed
signed = Gen.choice [smolSigned, nearFiniteBoundSigned, bigSigned]

smolSigned :: Hedgehog.Gen Signed
smolSigned = Gen.frequency
    [ (,) 1 $ pure Zero
    , (,) 9 $ pure NonZero <*> sign <*> smolBoundedBelow
    ]

bigSigned :: Hedgehog.Gen Signed
bigSigned = pure NonZero <*> sign <*> bigBoundedBelow

nearFiniteBoundSigned :: Hedgehog.Gen Signed
nearFiniteBoundSigned = pure NonZero <*> sign <*> nearPositiveFiniteBound

---

sign :: Hedgehog.Gen Sign
sign = Gen.element [PlusSign, MinusSign]