packages feed

libclang-bindings-0.1.0.0: src/Clang/Enum/Bitfield.hs

module Clang.Enum.Bitfield (
    BitfieldEnum(..)
  , IsSingleFlag(..)
    -- * API
  , bitfieldEnum
  , fromBitfieldEnum
  , flagIsSet
  ) where

import Data.Bits
import Data.Foldable qualified as Foldable
import Data.Typeable
import Foreign.C
import GHC.Generics (Generic)
import GHC.Show (appPrec1, showSpace)

{-------------------------------------------------------------------------------
  Definition
-------------------------------------------------------------------------------}

-- | Single flags
--
-- See 'BitfieldEnum' for discussion.
class Typeable hs => IsSingleFlag hs where
  flagToC :: hs -> CUInt

-- | Enum that corresponds to a bitfield
--
-- Some C enumerations are defined like this:
--
-- > enum Flags {
-- >   Flag1 = 0x00,
-- >   Flag2 = 0x01,
-- >   Flag3 = 0x02,
-- >   Flag4 = 0x04,
-- >   Flag5 = 0x08,
-- >   ..
-- > };
--
-- The intention then is that these flags are ORed together to select multiple
-- flags. We term this a "bitfield enum": the @flag@ type is intended to be an
-- ADT with a 'IsSingleFlag' instance, mapping ADT constructors to the values from
-- the enum. Using @hsc2hs@, such an instance might look like
--
-- > data Flags = Flag1 | Flag2 | Flag3 | Flag 4 | Flag5
-- >
-- > instance IsSingleFlag Flags where
-- >   flagToC Flag1 = #const Flag1
-- >   flagToC Flag2 = #const Flag2
-- >   flagToC Flag3 = #const Flag3
-- >   flagToC Flag4 = #const Flag4
-- >   flagToC Flag5 = #const Flag5
newtype BitfieldEnum hs = BitfieldEnum CUInt
  deriving stock (Eq, Ord, Generic)

-- | 'Semigroup' instance corresponds to set union
instance Semigroup (BitfieldEnum hs) where
  BitfieldEnum a <> BitfieldEnum b = BitfieldEnum (a .|. b)

-- | 'Monoid' instance corresponds to set union
--
-- This means that the neutral element 'mempty' is the empty set.
instance Monoid (BitfieldEnum hs) where
  mempty = BitfieldEnum 0

{-------------------------------------------------------------------------------
  Showing values
-------------------------------------------------------------------------------}

instance (IsSingleFlag hs, Enum hs, Bounded hs, Show hs)
      => Show (BitfieldEnum hs) where
  showsPrec p i = showParen (p >= appPrec1) $
      either (uncurry showC) showHs $ showBitfieldEnum i
    where
      showC :: CUInt -> TypeRep -> ShowS
      showC c typ =
            showString "BitfieldEnum @"
          . showsPrec appPrec1 typ
          . showSpace
          . showsPrec appPrec1 c

      showHs :: [hs] -> ShowS
      showHs hs =
             showString "simpleEnum "
           . showsPrec appPrec1 hs

-- | Internal auxiliary for showing 'BitfieldEnum'
showBitfieldEnum :: forall hs.
     (IsSingleFlag hs, Enum hs, Bounded hs)
  => BitfieldEnum hs -> Either (CUInt, TypeRep) [hs]
showBitfieldEnum =
    either (Left . showC) Right . fromBitfieldEnum
  where
    showC :: CUInt -> (CUInt, TypeRep)
    showC c = (c, typeRep (Proxy @hs))

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

-- | Construct 'BitfieldEnum'
bitfieldEnum :: IsSingleFlag hs => [hs] -> BitfieldEnum hs
bitfieldEnum = BitfieldEnum . Foldable.foldl' (.|.) 0 . map flagToC

-- | Check if the given flag is set
flagIsSet :: IsSingleFlag hs => BitfieldEnum hs -> hs -> Bool
flagIsSet (BitfieldEnum i) flag = (i .&. flagToC flag) /= 0

-- | All set flags
--
-- This is @O(n)@ in the number of constructs of the @flag@ ADT; while that is
-- technically speaking a constant, making this function @O(1)@, this is still
-- a relatively expensive function. Consider using 'flagIsSet' instead.
--
-- Returns a 'Left' value if some bits in the enum did not correspond to any
-- known @hs@ flag.
--
-- NOTE: The @Enum@ and @Bounded@ instances are simply used to enumerate all
-- flags. Their definition has no bearing on the generated C code, and can
-- simply be derived.
fromBitfieldEnum :: forall hs.
     (IsSingleFlag hs, Enum hs, Bounded hs)
  => BitfieldEnum hs -> Either CUInt [hs]
fromBitfieldEnum i@(BitfieldEnum c)
  | bitfieldEnum allRecognized == i
  = Right allRecognized

  | otherwise
  = Left c
  where
    allRecognized :: [hs]
    allRecognized = [flag | flag <- [minBound .. maxBound], flagIsSet i flag]