packages feed

fs-sim-0.5.0.0: test/Test/Util/RefEnv.hs

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TupleSections #-}

-- | Environments of QSM references.
module Test.Util.RefEnv
  ( RefEnv
  -- opaque
  , elems
  , empty
  , filter
  , fromList
  , keys
  , lookup
  , null
  , reverseLookup
  , singleton
  , toList
  , union
  , (!)
  ) where

import Data.Bifunctor
import Data.Functor.Classes
import Data.List (intercalate)
import GHC.Generics (Generic)
import GHC.Stack
import Test.StateMachine (Reference)
import Test.StateMachine.TreeDiff
import qualified Test.StateMachine.Types.Rank2 as Rank2
import Prelude hiding (filter, lookup, null)
import qualified Prelude

data RefEnv k a r = RefEnv {toList :: [(Reference k r, a)]}
  deriving (Generic, ToExpr, Show)

-- | Extend mapping
--
-- We don't insist that the keys are disjoint, but if the same key appears
-- twice, the value must agree.
extendMapping ::
  forall k v.
  (Eq k, Eq v, Show k, Show v, HasCallStack) =>
  [(k, v)] -> -- Mapping known to have duplicate keys
  [(k, v)] -> -- With potential duplicates
  [(k, v)]
extendMapping acc [] = acc
extendMapping acc ((k, v) : kvs) =
  case Prelude.lookup k acc of
    Just v' | v /= v' -> error $ renderError v'
    _otherwise -> extendMapping ((k, v) : acc) kvs
 where
  renderError :: v -> String
  renderError v' =
    intercalate
      " "
      [ "Key"
      , show k
      , "with two different values"
      , show v
      , "and"
      , show v'
      ]

fromList ::
  (Eq k, Show k, Eq a, Show a, Eq1 r, Show1 r, HasCallStack) =>
  [(Reference k r, a)] -> RefEnv k a r
fromList = RefEnv . extendMapping []

instance Rank2.Functor (RefEnv k a) where
  fmap f (RefEnv ras) =
    RefEnv $
      fmap (first (Rank2.fmap f)) ras

instance Rank2.Foldable (RefEnv k a) where
  foldMap f (RefEnv ras) =
    foldMap (Rank2.foldMap f . fst) ras

instance Rank2.Traversable (RefEnv k a) where
  traverse f (RefEnv ras) =
    RefEnv
      <$> traverse (\(r, a) -> (,a) <$> Rank2.traverse f r) ras

union ::
  (Eq k, Show k, Eq a, Show a, Eq1 r, Show1 r, HasCallStack) =>
  RefEnv k a r -> RefEnv k a r -> RefEnv k a r
union (RefEnv ras1) (RefEnv ras2) = RefEnv (extendMapping ras1 ras2)

-- | Empty environment
--
-- In most context 'mempty' can be used, but the 'Monoid' instance requires
-- equality, which 'empty' does not.
empty :: RefEnv k a r
empty = RefEnv []

lookup :: (Eq k, Eq1 r) => Reference k r -> RefEnv k a r -> Maybe a
lookup r (RefEnv ras) = Prelude.lookup r ras

(!) :: (Eq k, Eq1 r) => RefEnv k a r -> Reference k r -> a
env ! r = case lookup r env of
  Just a -> a
  Nothing -> error "(RefEnv.!): key not found"

keys :: RefEnv k a r -> [Reference k r]
keys (RefEnv ras) = map fst ras

elems :: RefEnv k a r -> [a]
elems (RefEnv ras) = map snd ras

null :: RefEnv k a r -> Bool
null (RefEnv ras) = Prelude.null ras

singleton :: Reference k r -> a -> RefEnv k a r
singleton r a = RefEnv [(r, a)]

filter :: (a -> Bool) -> RefEnv k a r -> RefEnv k a r
filter p (RefEnv ras) = RefEnv (Prelude.filter (p . snd) ras)

reverseLookup :: (a -> Bool) -> RefEnv k a r -> [Reference k r]
reverseLookup p = keys . filter p