packages feed

clash-finite-1.0.0.0: tests/Clash/Tests/Laws/Finite.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}

module Clash.Tests.Laws.Finite (tests) where

import Prelude hiding (reverse)

import Control.DeepSeq (NFData)
import Control.Monad (forM_)
import Data.Constraint (Dict(..))
import Data.Functor.Compose (Compose(..))
import Data.Functor.Const (Const(..))
import Data.Functor.Identity (Identity(..))
import Data.Functor.Product (Product)
import Data.Functor.Sum (Sum)
import Data.Int (Int8, Int16)
#if MIN_VERSION_base(4,15,0)
import Data.Ord (Down(..))
#endif
import Data.Proxy (Proxy(..))
import Data.Typeable (Typeable, typeRep)
import Data.Void (Void)
import Data.Word (Word8, Word16)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (Assertion, (@=?), testCase)

import Clash.Class.Finite (Finite(..))
import Clash.Promoted.Nat (SNatLE(..), SNat(..), compareSNat)
import Clash.Sized.BitVector (BitVector, Bit)
import Clash.Sized.Index (Index)
import Clash.Sized.RTree (RTree)
import Clash.Sized.Signed (Signed)
import Clash.Sized.Unsigned (Unsigned)
import Clash.Sized.Vector (Vec, indicesI, iterateI, reverse)

indexOrderLaw ::
  forall a.
  (NFData a, Show a, Finite a) =>
  Proxy a ->
  Assertion
indexOrderLaw Proxy =
  index <$> elements @a @=? indicesI

forwardIterateLaw ::
  forall a.
  (NFData a, Show a, Eq a, Finite a) =>
  Proxy a ->
  Assertion
forwardIterateLaw Proxy =
  iterateI (>>= succMaybe) (lowestMaybe @a) @=? Just <$> elements @a

backwardIterateLaw ::
  forall a.
  (NFData a, Show a, Eq a, Finite a) =>
  Proxy a ->
  Assertion
backwardIterateLaw Proxy =
  iterateI (>>= predMaybe) (highestMaybe @a) @=? Just <$> reverse (elements @a)

indexIsomorphismLaw ::
  forall a.
  (NFData a, Show a, Eq a, Finite a) =>
  Proxy a ->
  Assertion
indexIsomorphismLaw Proxy =
  ith . index <$> elements @a @=? elements @a

minimumPredecessor ::
  forall a.
  (NFData a, Show a, Eq a, Finite a) =>
  Proxy a ->
  Assertion
minimumPredecessor Proxy =
  (lowestMaybe >>= predMaybe @a) @=? Nothing

maximumSuccessor ::
  forall a.
  (NFData a, Show a, Eq a, Finite a) =>
  Proxy a ->
  Assertion
maximumSuccessor Proxy =
  (highestMaybe >>= succMaybe @a) @=? Nothing

extremes ::
  forall a.
  (NFData a, Show a, Eq a, Finite a) =>
  Proxy a ->
  Assertion
extremes Proxy = case compareSNat (SNat @1) (SNat @(ElementCount a)) of
  SNatLE -> do
    lowestMaybe @a @=? Just lowest
    highestMaybe @a @=? Just highest
  SNatGT -> do
    lowestMaybe @a @=? Nothing
    highestMaybe @a @=? Nothing

boundedCompatibility ::
  forall a.
  (NFData a, Show a, Eq a, Finite a) =>
  Maybe (Dict (Bounded a)) ->
  Assertion
boundedCompatibility = \case
  Nothing -> return ()
  Just Dict -> case compareSNat (SNat @1) (SNat @(ElementCount a)) of
    SNatGT -> return ()
    SNatLE -> do
      lowest @a @=? minBound @a
      highest @a @=? maxBound @a

enumCompatibility ::
  forall a.
  (NFData a, Show a, Eq a, Finite a) =>
  Maybe (Dict (Enum a)) ->
  Assertion
enumCompatibility = \case
  Nothing -> return ()
  Just Dict -> forM_ (elements @a) $ \x -> do
    maybe (return ()) (@=? succ x) $ succMaybe x
    maybe (return ()) (@=? pred x) $ predMaybe x

finiteLaws ::
  forall a.
  (NFData a, Show a, Eq a, Finite a) =>
  Maybe (Dict (Bounded a)) ->
  Maybe (Dict (Enum a)) ->
  [TestTree]
finiteLaws mBounded mEnum =
  [ testCase "Index Order"           $ indexOrderLaw proxy
  , testCase "Forward Iterate"       $ forwardIterateLaw proxy
  , testCase "Backward Iterate"      $ backwardIterateLaw proxy
  , testCase "Index Isomorphism"     $ indexIsomorphismLaw proxy
  , testCase "Minimum Predecessor"   $ minimumPredecessor proxy
  , testCase "Maximum Successor"     $ maximumSuccessor proxy
  , testCase "Extremes"              $ extremes proxy
  , testCase "Bounded Compatibility" $ boundedCompatibility mBounded
  , testCase "Enum Compatibility"    $ enumCompatibility mEnum
  ]
 where
  proxy :: Proxy a
  proxy = Proxy

testFiniteLaws ::
  forall a.
  (NFData a, Show a, Eq a, Finite a, Typeable a) =>
  Maybe (Dict (Bounded a)) ->
  Maybe (Dict (Enum a)) ->
  TestTree
testFiniteLaws mBounded mEnum =
  testGroup (show (typeRep proxy)) $ finiteLaws mBounded mEnum
 where
  proxy :: Proxy a
  proxy = Proxy

tests :: TestTree
tests = testGroup "Finite"
  [ testFiniteLaws noBInst  $ noEInst  @Void
  , testFiniteLaws hasBInst $ hasEInst @()
  , testFiniteLaws hasBInst $ hasEInst @Bit
  , testFiniteLaws hasBInst $ hasEInst @Bool
  , testFiniteLaws hasBInst $ hasEInst @Ordering

  , testFiniteLaws hasBInst $ hasEInst @Char
  , testFiniteLaws hasBInst $ hasEInst @Int8
  , testFiniteLaws hasBInst $ hasEInst @Int16
  , testFiniteLaws hasBInst $ hasEInst @Word8
  , testFiniteLaws hasBInst $ hasEInst @Word16

  , testFiniteLaws hasBInst $ hasEInst @(BitVector 0)
  , testFiniteLaws hasBInst $ hasEInst @(BitVector 1)
  , testFiniteLaws hasBInst $ hasEInst @(BitVector 8)

  , testFiniteLaws hasBInst $ hasEInst @(Index 0)
  , testFiniteLaws hasBInst $ hasEInst @(Index 1)
  , testFiniteLaws hasBInst $ hasEInst @(Index 128)

  , testFiniteLaws hasBInst $ hasEInst @(Signed 0)
  , testFiniteLaws hasBInst $ hasEInst @(Signed 1)
  , testFiniteLaws hasBInst $ hasEInst @(Signed 8)

  , testFiniteLaws hasBInst $ hasEInst @(Unsigned 0)
  , testFiniteLaws hasBInst $ hasEInst @(Unsigned 1)
  , testFiniteLaws hasBInst $ hasEInst @(Unsigned 8)

  , testFiniteLaws noBInst  $ noEInst  @(Maybe (Index 0))
  , testFiniteLaws noBInst  $ noEInst  @(Maybe (Index 1))
  , testFiniteLaws noBInst  $ noEInst  @(Maybe (Index 27))

  , testFiniteLaws noBInst  $ noEInst  @(Either Void (Index 0))
  , testFiniteLaws noBInst  $ noEInst  @(Either Void (Index 1))
  , testFiniteLaws noBInst  $ noEInst  @(Either Void (Index 27))
  , testFiniteLaws noBInst  $ noEInst  @(Either Bool (Index 0))
  , testFiniteLaws noBInst  $ noEInst  @(Either Bool (Index 1))
  , testFiniteLaws noBInst  $ noEInst  @(Either Bool (Index 27))

  , testFiniteLaws noBInst  $ noEInst  @(Compose Maybe Maybe Bool)
  , testFiniteLaws hasBInst $ hasEInst @(Const Bool [Int])
#if MIN_VERSION_base(4,15,0)
  , testFiniteLaws hasBInst $ hasEInst @(Down Bool)
#endif
  , testFiniteLaws hasBInst $ hasEInst @(Identity Bool)
  , testFiniteLaws noBInst  $ noEInst  @(Product Maybe Maybe Bit)
  , testFiniteLaws noBInst  $ noEInst  @(Sum Maybe Maybe Bit)

  , testFiniteLaws noBInst  $ noEInst  @(Vec 0 Void)
  , testFiniteLaws noBInst  $ noEInst  @(Vec 1 Void)
  , testFiniteLaws noBInst  $ noEInst  @(Vec 16 Void)
  , testFiniteLaws noBInst  $ noEInst  @(Vec 0 Bool)
  , testFiniteLaws noBInst  $ noEInst  @(Vec 1 Bool)
  , testFiniteLaws noBInst  $ noEInst  @(Vec 16 Bool)

  , testFiniteLaws noBInst  $ noEInst  @(RTree 0 Void)
  , testFiniteLaws noBInst  $ noEInst  @(RTree 1 Void)
  , testFiniteLaws noBInst  $ noEInst  @(RTree 4 Void)
  , testFiniteLaws noBInst  $ noEInst  @(RTree 0 Bool)
  , testFiniteLaws noBInst  $ noEInst  @(RTree 1 Bool)
  , testFiniteLaws noBInst  $ noEInst  @(RTree 4 Bool)

  , testFiniteLaws noBInst  $ noEInst  @(Void, Void)
  , testFiniteLaws noBInst  $ noEInst  @(Bool, Void)
  , testFiniteLaws noBInst  $ noEInst  @(Void, Bool)
  , testFiniteLaws noBInst  $ noEInst  @(Bool, Bool)

  , testFiniteLaws noBInst  $ noEInst  @(Bool, Bool, Bool)
  , testFiniteLaws noBInst  $ noEInst  @(Void, Bool, Bool)
  , testFiniteLaws noBInst  $ noEInst  @(Bool, Void, Bool)
  , testFiniteLaws noBInst  $ noEInst  @(Bool, Bool, Void)

  , testFiniteLaws noBInst  $ noEInst  @(Bool, Bool, Bool, Bool)
  ]
 where
  noBInst :: Maybe (Dict (Bounded a))
  noBInst = Nothing

  hasBInst :: Bounded a => Maybe (Dict (Bounded a))
  hasBInst = Just Dict

  noEInst :: Maybe (Dict (Enum a))
  noEInst = Nothing

  hasEInst :: Enum a => Maybe (Dict (Enum a))
  hasEInst = Just Dict