packages feed

sdl3-bindgen-sys-0.0.0.1: runtime/HsBindgen/Runtime/CEnum.hs

-- | C enumerations
--
-- This module is intended to be imported qualified.
--
-- > import HsBindgen.Runtime.Prelude
-- > import HsBindgen.Runtime.CEnum qualified as CEnum
module HsBindgen.Runtime.CEnum (
  -- * Type classes
  CEnum (..),
  SequentialCEnum (..),

  -- * Deriving via support
  AsCEnum (..),
  AsSequentialCEnum (..),

  -- * API
  getNames,

  -- * Instance support
  DeclaredValues,
  declaredValuesFromList,
  show,
  shows,
  showsWrappedUndeclared,
  readEither,
  readPrec,
  readPrecWrappedUndeclared,
  seqIsDeclared,
  seqMkDeclared,

  -- ** Exceptions
  CEnumException (..),
) where

import Control.Exception (Exception (displayException), throw)
import Data.Bifunctor (Bifunctor (first))
import Data.Coerce (Coercible, coerce)
import Data.List qualified as List
import Data.List.NonEmpty (NonEmpty ((:|)))
import Data.List.NonEmpty qualified as NonEmpty
import Data.Map.Strict (Map)
import Data.Map.Strict qualified as Map
import Data.Proxy (Proxy (Proxy))
import GHC.Show (appPrec, appPrec1, showSpace)
import Text.ParserCombinators.ReadP qualified as ReadP
import Text.ParserCombinators.ReadPrec qualified as ReadPrec
import Text.Read (ReadPrec, minPrec, (+++))
import Text.Read qualified as Read
import Text.Read.Lex (Lexeme (..), expect)
import Prelude hiding (show, shows)
import Prelude qualified

{-------------------------------------------------------------------------------
  Type classes
-------------------------------------------------------------------------------}

-- | C enumeration
--
-- This class implements an API for Haskell representations of C enumerations.
-- C @enum@ declarations only declare values; they do not limit the range of the
-- corresponding integral type.  They may have negative values, non-sequential
-- values, and multiple names for a single value.
--
-- At a low level, @hs-bindgen@ generates a @newtype@ wrapper around the
-- integral representation type to represent a C @enum@. An instance of this
-- class is generated automatically. A 'Show' instance defined using 'shows' is
-- also generated by default. 'Bounded' and 'Prelude.Enum' instances are /not/
-- generated automatically because values do not technically need to be
-- declared. Users may optionally derive these instances using 'AsCEnum' or
-- 'AsSequentialCEnum' when appropriate.
--
-- This class may also be used with Haskell sum-type representations of
-- enumerations.
class (Integral (CEnumZ a)) => CEnum a where
  -- | Integral representation type
  type CEnumZ a

  -- | Construct a value from the integral representation
  --
  -- prop> fromCEnum . toCEnum === id
  toCEnum :: CEnumZ a -> a
  default toCEnum :: (Coercible a (CEnumZ a)) => CEnumZ a -> a
  toCEnum = coerce

  -- | Get the integral representation for a value
  --
  -- prop> toCEnum . fromCEnum === id
  --
  -- If @a@ has an 'Ord' instance, it should be compatible with the 'Ord'
  -- instance on the underlying integral value:
  --
  -- prop> \x y -> (x <= y) === (fromCEnum x <= fromCEnum y)
  fromCEnum :: a -> CEnumZ a
  default fromCEnum :: (Coercible a (CEnumZ a)) => a -> CEnumZ a
  fromCEnum = coerce

  -- | Declared values and associated names
  declaredValues :: proxy a -> DeclaredValues a

  -- | Show undeclared value
  --
  -- Like any 'Show' related function, this should generate a valid Haskell
  -- expression. In this case, a valid Haskell expression for values /outside/
  -- of the set of declared values (that is, for which 'isDeclared' will return
  -- 'False').
  --
  -- The default definition just shows the underlying integer value; this is
  -- valid if the Haskell wrapper has a 'Num' instance. If the Haskell type is
  -- simply a newtype wrapper around the underlying C type, you can use
  -- 'showsWrappedUndeclared'. Finally, if the Haskell type /cannot/ represent
  -- undeclared values, this can be defined using @error@.
  --
  -- > showsUndeclared _ = \_ x ->
  -- >   error $ "Unexpected value " ++ show x ++ " for type Foo"
  showsUndeclared :: proxy a -> Int -> CEnumZ a -> ShowS
  default showsUndeclared
    :: (Show (CEnumZ a))
    => proxy a -> Int -> CEnumZ a -> ShowS
  showsUndeclared _ = showsPrec

  -- | Read undeclared value
  --
  -- See 'showsUndeclared', 'showsWrappedUndeclared', and
  -- 'readPrecWrappedUndeclared'.
  readPrecUndeclared :: ReadPrec a

  -- | Determine if the specified value is declared
  --
  -- This has a default definition in terms of 'declaredValues', but you may
  -- wish to override this with a more efficient implementation (in particular,
  -- see 'seqIsDeclared').
  isDeclared :: a -> Bool
  isDeclared x = (fromCEnum x) `Map.member` getIntegralToDeclaredValues (Proxy :: Proxy a)

  -- | Construct a value only if it is declared
  --
  -- See also 'seqMkDeclared'.
  mkDeclared :: CEnumZ a -> Maybe a
  mkDeclared i
    | i `Map.member` getIntegralToDeclaredValues (Proxy :: Proxy a) = Just (toCEnum i)
    | otherwise = Nothing

-- | C enumeration with sequential values
--
-- 'Bounded' and 'Enum' methods may be implemented more efficiently when the
-- values of an enumeration are sequential.  An instance of this class is
-- generated automatically in this case.  Users may optionally derive these
-- instances using 'AsSequentialCEnum' when appropriate.
--
-- This class may also be used with Haskell sum-type representations of
-- enumerations.
--
-- prop> all isDeclared [minDeclaredValue..maxDeclaredValue]
class (CEnum a) => SequentialCEnum a where
  -- | The minimum declared value
  --
  -- prop> minDeclaredValue == minimum (filter isDeclared (map toCEnum [minBound..]))
  minDeclaredValue :: a

  -- | The maximum declared value
  --
  -- prop> maxDeclaredValue == maximum (filter isDeclared (map toCEnum [minBound..]))
  maxDeclaredValue :: a

{-------------------------------------------------------------------------------
  API
-------------------------------------------------------------------------------}

-- | Get all names associated with a value
--
-- An empty list is returned when the specified value is not declared.
getNames :: forall a. (CEnum a) => a -> [String]
getNames x =
  maybe [] NonEmpty.toList $
    Map.lookup (fromCEnum x) (getIntegralToDeclaredValues (Proxy :: Proxy a))

{-------------------------------------------------------------------------------
  Instance support
-------------------------------------------------------------------------------}

-- | Declared values (opaque)
data DeclaredValues a = DeclaredValues
  { integralToDeclaredValues :: !(Map (CEnumZ a) (NonEmpty String))
  , declaredValueToIntegral :: !(Map String (CEnumZ a))
  }

-- | Construct t'DeclaredValues' from a list of values and associated names
declaredValuesFromList
  :: (Ord (CEnumZ a))
  => [(CEnumZ a, NonEmpty String)]
  -> DeclaredValues a
declaredValuesFromList xs =
  DeclaredValues
    { integralToDeclaredValues = Map.fromList xs
    , declaredValueToIntegral =
        Map.fromList [(n, i) | (i, ns) <- xs, n <- NonEmpty.toList ns]
    }

declaredValuesList :: DeclaredValues a -> [String]
declaredValuesList = Map.keys . declaredValueToIntegral

-- | Show the specified value
--
-- Examples for a hypothetical enumeration type, using generated defaults:
--
-- > showCEnum StatusOK == "StatusOK"
--
-- > showCEnum (StatusCode 418) == "StatusCode 418"
show :: forall a. (CEnum a) => a -> String
show x = shows 0 x ""

-- | Generalization of 'Prelude.show' (akin to 'Prelude.shows').
--
-- This function may be used in the definition of a 'Show' instance for a
-- @newtype@ representation of a C enumeration.
--
-- When the value is declared, a corresponding name is returned.  Otherwise,
-- 'showsUndeclared' is called.
shows :: forall a. (CEnum a) => Int -> a -> ShowS
shows prec x =
  case Map.lookup i (getIntegralToDeclaredValues (Proxy :: Proxy a)) of
    Just (name :| _names) -> showString name
    Nothing -> showsUndeclared (Proxy :: Proxy a) prec i
 where
  i :: CEnumZ a
  i = fromCEnum x

-- | Read a 'CEnum' from string
--
-- Examples for a hypothetical enumeration type, using generated defaults:
--
-- > (readEitherCEnum "StatusCode 200" :: StatusCode) == Right StatusOK
--
-- > (readEitherCEnum "StatusOK" :: StatusCode) == Right StatusOK
--
-- > (readEitherCEnum "StatusCode 123" :: StatusCode) == Right (StatusCode 123)
readEither :: forall a. (CEnum a) => String -> Either String a
readEither s =
  case [x | (x, "") <- ReadPrec.readPrec_to_S read' minPrec s] of
    [x] -> Right x
    [] -> Left "readEitherCEnum: no parse"
    _xs -> Left "readEitherCEnum: ambiguous parse"
 where
  read' =
    do
      x <- readPrec
      ReadPrec.lift ReadP.skipSpaces
      return x

-- | Helper function for defining 'showsUndeclared'
--
-- This helper can be used in the case where @a@ is a newtype wrapper around
-- the underlying @CEnumZ a@.
showsWrappedUndeclared
  :: (Show (CEnumZ a))
  => String -> proxy a -> Int -> CEnumZ a -> ShowS
showsWrappedUndeclared constructorName _ p x =
  showParen (p >= appPrec1) $
    showString constructorName
      . showSpace
      . showsPrec appPrec1 x

-- | Read a declared 'CEnum' value
readPrecDeclaredValue :: forall proxy a. (CEnum a) => proxy a -> ReadPrec a
readPrecDeclaredValue proxy = Read.parens $ ReadPrec.prec appPrec1 $ do
  declaredValue <-
    ReadPrec.lift $
      ReadP.choice $
        map ReadP.string $
          declaredValuesList $
            declaredValues proxy
  pure $ toCEnum $ (declaredValueToIntegral $ declaredValues proxy) Map.! declaredValue

-- | Helper function for defining 'readPrecUndeclared'
--
-- This helper can be used in the case where @a@ is a newtype wrapper around
-- the underlying @CEnumZ a@.
readPrecWrappedUndeclared
  :: forall a. (CEnum a, Read (CEnumZ a)) => String -> ReadPrec a
readPrecWrappedUndeclared constructorName = Read.parens $ ReadPrec.prec appPrec $ do
  ReadPrec.lift $ expect $ Ident constructorName
  n <- Read.step (Read.readPrec :: ReadPrec (CEnumZ a))
  pure $ toCEnum n

-- | Read a 'CEnum' from string
--
-- This function may be used in the definition of a 'Read' instance for a
-- @newtype@ representation of a C enumeration.
readPrec :: forall a. (CEnum a) => ReadPrec a
readPrec = readPrecDeclaredValue (Proxy :: Proxy a) +++ readPrecUndeclared

-- | Determine if the specified value is declared
--
-- This implementation is optimized for 'SequentialCEnum'.
seqIsDeclared :: forall a. (SequentialCEnum a) => a -> Bool
seqIsDeclared x = i >= minZ && i <= maxZ
 where
  minZ, maxZ, i :: CEnumZ a
  minZ = fromCEnum (minDeclaredValue @a)
  maxZ = fromCEnum (maxDeclaredValue @a)
  i = fromCEnum x

-- | Construct a value only if it is declared
--
-- This implementation is optimized for 'SequentialCEnum'.
seqMkDeclared :: forall a. (SequentialCEnum a) => CEnumZ a -> Maybe a
seqMkDeclared i
  | i >= minZ && i <= maxZ = Just (toCEnum i)
  | otherwise = Nothing
 where
  minZ, maxZ :: CEnumZ a
  minZ = fromCEnum (minDeclaredValue @a)
  maxZ = fromCEnum (maxDeclaredValue @a)

{-------------------------------------------------------------------------------
  Deriving via support
-------------------------------------------------------------------------------}

-- | Type used to derive classes using @DerivingVia@ a type with a 'CEnum'
-- instance
--
-- When the values are sequential, 'AsSequentialCEnum' provides better
-- performance and should therefore be used instead.
--
-- The following classes may be derived:
--
-- * 'Bounded' may be derived using the bounds of the declared values.  This is
--   /not/ derived by default.
-- * 'Enum' may be derived using the bounds of the declared values.  This
--   instance assumes that only the declared values are valid and throws a
--   'CEnumException' if passed a value that is not declared.  This is /not/
--   derived by default.
--
-- For /declared/ values we have
--
-- prop> toEnum   === coerce       . toCENum   . fromIntegral
-- prop> fromEnum === fromIntegral . fromCEnum . coerce
--
-- In addition we guarantee that where 'pred' or 'succ' are defined, we have
--
-- prop> \x -> (pred x < x) && (x < succ x)
newtype AsCEnum a = WrapCEnum {unwrapCEnum :: a}

instance (CEnum a) => Bounded (AsCEnum a) where
  minBound = WrapCEnum minBoundGen
  maxBound = WrapCEnum maxBoundGen

instance (CEnum a) => Enum (AsCEnum a) where
  succ = WrapCEnum . succGen . unwrapCEnum
  pred = WrapCEnum . predGen . unwrapCEnum

  toEnum = WrapCEnum . toEnumGen
  fromEnum = fromEnumGen . unwrapCEnum

  enumFrom (WrapCEnum x) = WrapCEnum <$> enumFromGen x
  enumFromThen (WrapCEnum x) (WrapCEnum y) = WrapCEnum <$> enumFromThenGen x y
  enumFromTo (WrapCEnum x) (WrapCEnum z) = WrapCEnum <$> enumFromToGen x z
  enumFromThenTo (WrapCEnum x) (WrapCEnum y) (WrapCEnum z) =
    WrapCEnum <$> enumFromThenToGen x y z

-- | Type used to derive classes using @DerivingVia@ a type with a
-- 'SequentialCEnum' instance
--
-- The following classes may be derived:
--
-- * 'Bounded' may be derived using the bounds of the declared values.  This is
--   /not/ derived by default.
-- * 'Enum' may be derived using the bounds of the declared values.  This
--   instance assumes that only the declared values are valid and throws a
--   'CEnumException' if passed a value that is not declared.  This is /not/
--   derived by default.
--
-- 'AsSequentialCEnum' should have the same properties as 'AsCEnum'.
newtype AsSequentialCEnum a = WrapSequentialCEnum {unwrapSequentialCEnum :: a}

instance (SequentialCEnum a) => Bounded (AsSequentialCEnum a) where
  minBound = WrapSequentialCEnum minBoundSeq
  maxBound = WrapSequentialCEnum maxBoundSeq

instance (SequentialCEnum a) => Enum (AsSequentialCEnum a) where
  succ = WrapSequentialCEnum . succSeq . unwrapSequentialCEnum
  pred = WrapSequentialCEnum . predSeq . unwrapSequentialCEnum

  toEnum = WrapSequentialCEnum . toEnumSeq
  fromEnum = fromEnumSeq . unwrapSequentialCEnum

  enumFrom (WrapSequentialCEnum x) = WrapSequentialCEnum <$> enumFromSeq x
  enumFromThen (WrapSequentialCEnum x) (WrapSequentialCEnum y) =
    WrapSequentialCEnum <$> enumFromThenSeq x y
  enumFromTo (WrapSequentialCEnum x) (WrapSequentialCEnum z) =
    WrapSequentialCEnum <$> enumFromToSeq x z
  enumFromThenTo
    (WrapSequentialCEnum x)
    (WrapSequentialCEnum y)
    (WrapSequentialCEnum z) =
      WrapSequentialCEnum <$> enumFromThenToSeq x y z

{-------------------------------------------------------------------------------
  Exceptions
-------------------------------------------------------------------------------}

-- | Exceptions used by optional C enumeration instances
data CEnumException
  = CEnumNotDeclared Integer
  | CEnumNoSuccessor Integer
  | CEnumNoPredecessor Integer
  | CEnumEmpty
  | CEnumFromEqThen Integer
  deriving stock (Eq, Show)

instance Exception CEnumException where
  displayException = \case
    CEnumNotDeclared i -> "C enumeration value not declared: " ++ Prelude.show i
    CEnumNoSuccessor i ->
      "C enumeration value has no declared successor: " ++ Prelude.show i
    CEnumNoPredecessor i ->
      "C enumeration value has no declared predecessor: " ++ Prelude.show i
    CEnumEmpty -> "C enumeration has no declared values"
    CEnumFromEqThen i -> "enumeration from and then values equal: " ++ Prelude.show i

{-------------------------------------------------------------------------------
  Bounded instance implementation
-------------------------------------------------------------------------------}

minBoundGen :: forall a. (CEnum a) => a
minBoundGen = case Map.lookupMin (getIntegralToDeclaredValues (Proxy :: Proxy a)) of
  Just (i, _names) -> toCEnum i
  Nothing -> throw CEnumEmpty

minBoundSeq :: (SequentialCEnum a) => a
minBoundSeq = minDeclaredValue

maxBoundGen :: forall a. (CEnum a) => a
maxBoundGen = case Map.lookupMax (getIntegralToDeclaredValues (Proxy :: Proxy a)) of
  Just (k, _names) -> toCEnum k
  Nothing -> throw CEnumEmpty

maxBoundSeq :: (SequentialCEnum a) => a
maxBoundSeq = maxDeclaredValue

{-------------------------------------------------------------------------------
  Enum instance implementation
-------------------------------------------------------------------------------}

succGen :: forall a. (CEnum a) => a -> a
succGen x = either (throw . CEnumNotDeclared) id $ do
  (_ltMap, gtMap) <- splitMap i (getIntegralToDeclaredValues (Proxy :: Proxy a))
  case Map.lookupMin gtMap of
    Just (j, _names) -> return $ toCEnum j
    Nothing -> throw $ CEnumNoSuccessor (toInteger i)
 where
  i :: CEnumZ a
  i = fromCEnum x

succSeq :: forall a. (SequentialCEnum a) => a -> a
succSeq x
  | i >= minZ && i < maxZ = toCEnum (i + 1)
  | i == maxZ = throw $ CEnumNoSuccessor (toInteger i)
  | otherwise = throw $ CEnumNotDeclared (toInteger i)
 where
  minZ, maxZ, i :: CEnumZ a
  minZ = fromCEnum (minDeclaredValue @a)
  maxZ = fromCEnum (maxDeclaredValue @a)
  i = fromCEnum x

predGen :: forall a. (CEnum a) => a -> a
predGen y = either (throw . CEnumNotDeclared) id $ do
  (ltMap, _gtMap) <- splitMap j (getIntegralToDeclaredValues (Proxy :: Proxy a))
  case Map.lookupMax ltMap of
    Just (i, _names) -> return $ toCEnum i
    Nothing -> throw $ CEnumNoPredecessor (toInteger j)
 where
  j :: CEnumZ a
  j = fromCEnum y

predSeq :: forall a. (SequentialCEnum a) => a -> a
predSeq y
  | j > minZ && j <= maxZ = toCEnum (j - 1)
  | j == minZ = throw $ CEnumNoPredecessor (toInteger j)
  | otherwise = throw $ CEnumNotDeclared (toInteger j)
 where
  minZ, maxZ, j :: CEnumZ a
  minZ = fromCEnum (minDeclaredValue @a)
  maxZ = fromCEnum (maxDeclaredValue @a)
  j = fromCEnum y

toEnumGen :: (CEnum a) => Int -> a
toEnumGen i = case mkDeclared (fromIntegral i) of
  Just x -> x
  Nothing -> throw $ CEnumNotDeclared (toInteger i)

toEnumSeq :: forall a. (SequentialCEnum a) => Int -> a
toEnumSeq n
  | i >= minZ && i <= maxZ = toCEnum i
  | otherwise = throw $ CEnumNotDeclared (toInteger i)
 where
  minZ, maxZ, i :: CEnumZ a
  minZ = fromCEnum (minDeclaredValue @a)
  maxZ = fromCEnum (maxDeclaredValue @a)
  i = fromIntegral n

fromEnumGen :: forall a. (CEnum a) => a -> Int
fromEnumGen x
  | i `Map.member` getIntegralToDeclaredValues (Proxy :: Proxy a) = fromIntegral i
  | otherwise = throw $ CEnumNotDeclared (toInteger i)
 where
  i :: CEnumZ a
  i = fromCEnum x

fromEnumSeq :: forall a. (SequentialCEnum a) => a -> Int
fromEnumSeq x
  | i >= minZ && i <= maxZ = fromIntegral i
  | otherwise = throw $ CEnumNotDeclared (toInteger i)
 where
  minZ, maxZ, i :: CEnumZ a
  minZ = fromCEnum (minDeclaredValue @a)
  maxZ = fromCEnum (maxDeclaredValue @a)
  i = fromCEnum x

enumFromGen :: forall a. (CEnum a) => a -> [a]
enumFromGen x = either (throw . CEnumNotDeclared) id $ do
  (_ltMap, gtMap) <- splitMap i (getIntegralToDeclaredValues (Proxy :: Proxy a))
  return $ x : map toCEnum (Map.keys gtMap)
 where
  i :: CEnumZ a
  i = fromCEnum x

enumFromSeq :: forall a. (SequentialCEnum a) => a -> [a]
enumFromSeq x
  | i >= minZ && i <= maxZ = map toCEnum [i .. maxZ]
  | otherwise = throw $ CEnumNotDeclared (toInteger i)
 where
  minZ, maxZ, i :: CEnumZ a
  minZ = fromCEnum (minDeclaredValue @a)
  maxZ = fromCEnum (maxDeclaredValue @a)
  i = fromCEnum x

enumFromThenGen :: forall a. (CEnum a) => a -> a -> [a]
enumFromThenGen x y = case compare i j of
  LT -> either (throw . CEnumNotDeclared) id $ do
    (_ltIMap, gtIMap) <- splitMap i (getIntegralToDeclaredValues (Proxy :: Proxy a))
    (ltJMap, gtJMap) <- splitMap j gtIMap
    let w = Map.size ltJMap + 1
        js = j : Map.keys gtJMap
    return $ x : map (toCEnum . NonEmpty.head) (nonEmptyChunksOf w js)
  GT -> either (throw . CEnumNotDeclared) id $ do
    (ltIMap, _gtIMap) <- splitMap i (getIntegralToDeclaredValues (Proxy :: Proxy a))
    (ltJMap, gtJMap) <- splitMap j ltIMap
    let w = Map.size gtJMap + 1
        js = j : reverse (Map.keys ltJMap)
    return $ x : map (toCEnum . NonEmpty.head) (nonEmptyChunksOf w js)
  EQ -> throw $ CEnumFromEqThen (toInteger i)
 where
  i, j :: CEnumZ a
  i = fromCEnum x
  j = fromCEnum y

enumFromThenSeq :: forall a. (SequentialCEnum a) => a -> a -> [a]
enumFromThenSeq x y
  | i == j = throw $ CEnumFromEqThen (toInteger i)
  | i < minZ || i > maxZ = throw $ CEnumNotDeclared (toInteger i)
  | j < minZ || j > maxZ = throw $ CEnumNotDeclared (toInteger j)
  | i < j = map toCEnum [i, j .. maxZ]
  | otherwise = map toCEnum [i, j .. minZ]
 where
  minZ, maxZ, i, j :: CEnumZ a
  minZ = fromCEnum (minDeclaredValue @a)
  maxZ = fromCEnum (maxDeclaredValue @a)
  i = fromCEnum x
  j = fromCEnum y

enumFromToGen :: forall a. (CEnum a) => a -> a -> [a]
enumFromToGen x z = either (throw . CEnumNotDeclared) id $ do
  (_ltIMap, gtIMap) <- splitMap i (getIntegralToDeclaredValues (Proxy :: Proxy a))
  if i == k then
    return [x]
  else do
    (ltKMap, _gtKMap) <- splitMap k gtIMap
    return $ x : map toCEnum (Map.keys ltKMap) ++ [z]
 where
  i, k :: CEnumZ a
  i = fromCEnum x
  k = fromCEnum z

enumFromToSeq :: forall a. (SequentialCEnum a) => a -> a -> [a]
enumFromToSeq x z
  | i < minZ || i > maxZ = throw $ CEnumNotDeclared (toInteger i)
  | k < minZ || k > maxZ = throw $ CEnumNotDeclared (toInteger k)
  | otherwise = map toCEnum [i .. k]
 where
  minZ, maxZ, i, k :: CEnumZ a
  minZ = fromCEnum (minDeclaredValue @a)
  maxZ = fromCEnum (maxDeclaredValue @a)
  i = fromCEnum x
  k = fromCEnum z

enumFromThenToGen :: forall a. (CEnum a) => a -> a -> a -> [a]
enumFromThenToGen x y z = case compare i j of
  LT -> either (throw . CEnumNotDeclared) id $ do
    (_ltIMap, gtIMap) <- splitMap i (getIntegralToDeclaredValues (Proxy :: Proxy a))
    (ltJMap, gtJMap) <- splitMap j gtIMap
    (ltKMap, _gtKMap) <- splitMap k gtJMap
    let w = Map.size ltJMap + 1
        js = j : Map.keys ltKMap ++ [k]
    return $ x : map (toCEnum . NonEmpty.head) (nonEmptyChunksOf w js)
  GT -> either (throw . CEnumNotDeclared) id $ do
    (ltIMap, _gtIMap) <- splitMap i (getIntegralToDeclaredValues (Proxy :: Proxy a))
    (ltJMap, gtJMap) <- splitMap j ltIMap
    (_ltKMap, gtKMap) <- splitMap k ltJMap
    let w = Map.size gtJMap + 1
        js = j : reverse (k : Map.keys gtKMap)
    return $ x : map (toCEnum . NonEmpty.head) (nonEmptyChunksOf w js)
  EQ -> throw $ CEnumFromEqThen (toInteger i)
 where
  i, j, k :: CEnumZ a
  i = fromCEnum x
  j = fromCEnum y
  k = fromCEnum z

enumFromThenToSeq :: forall a. (SequentialCEnum a) => a -> a -> a -> [a]
enumFromThenToSeq x y z
  | i == j = throw $ CEnumFromEqThen (toInteger i)
  | i < minZ || i > maxZ = throw $ CEnumNotDeclared (toInteger i)
  | j < minZ || j > maxZ = throw $ CEnumNotDeclared (toInteger j)
  | k < minZ || k > maxZ = throw $ CEnumNotDeclared (toInteger k)
  | otherwise = map toCEnum [i, j .. k]
 where
  minZ, maxZ, i, j, k :: CEnumZ a
  minZ = fromCEnum (minDeclaredValue @a)
  maxZ = fromCEnum (maxDeclaredValue @a)
  i = fromCEnum x
  j = fromCEnum y
  k = fromCEnum z

{-------------------------------------------------------------------------------
  Auxiliary Functions
-------------------------------------------------------------------------------}

getIntegralToDeclaredValues :: (CEnum a) => proxy a -> Map (CEnumZ a) (NonEmpty String)
getIntegralToDeclaredValues = integralToDeclaredValues . declaredValues

nonEmptyChunksOf :: Int -> [a] -> [NonEmpty a]
nonEmptyChunksOf n xs
  | n > 0 = aux xs
  | otherwise = error $ "nonEmptyChunksOf: n must be positive, got " ++ Prelude.show n
 where
  aux :: [a] -> [NonEmpty a]
  aux xs' = case first NonEmpty.nonEmpty (List.splitAt n xs') of
    (Just ne, rest) -> ne : aux rest
    (Nothing, _rest) -> []

splitMap :: (Integral k) => k -> Map k v -> Either Integer (Map k v, Map k v)
splitMap n m = case Map.splitLookup n m of
  (ltMap, Just{}, gtMap) -> Right (ltMap, gtMap)
  (_ltMap, Nothing, _gtMap) -> Left (toInteger n)