bitmasks-0.0.1: src/Data/Bitmask/Internal.hs
--------------------------------------------------------------------------------
-- |
--
-- Module : Data.Bitmask.Internal
-- Description : Bitmasks
-- Copyright : (c) Alice Rixte 2025
-- License : BSD 3
-- Maintainer : alice.rixte@u-bordeaux.fr
-- Stability : stable
-- Portability : portable
--
--
-- Bitmasks for efficient storing of boolean flags
--
-- = WARNING
--
-- This module is considered __internal__.
--
-- The Package Versioning Policy __does not apply__.
--
-- The contents of this module may change __in any way whatsoever__
-- and __without any warning__ between minor versions of this package.
--
-- Authors importing this module are expected to track development
-- closely.
--
--------------------------------------------------------------------------------
module Data.Bitmask.Internal
( Bitmask(..)
, Bitmask8
, Bitmask16
, Bitmask32
, Bitmask64
-- ** Conversion to and from bits
, fromBits
, toBits
-- ** Check bitmask validity
, checkBitmask
-- ** Bitmask creation
, noFlag
, allFlags
, fromFlags
, toFlags
, fromExceptFlags
, toExceptFlags
, fromFlagsBool
, toFlagsBool
-- ** Flag querying
, getFlag
, getFlags
-- ** Flag modification
, insertFlag
, insertFlags
, addFlag
, addFlags
, deleteFlag
, deleteFlags
, flipFlag
, flipFlags
, setFlag
, setFlags
, modifyFlag
, modifyFlags
) where
import Data.Word
import Data.Bits
-- | A bitmask that contains boolean flags
--
-- * The 'flag' type should be an enumeration type (i.e. an instance of 'Enum').
--
-- * The 'w' type should be an integral type (e.g. 'Word8', 'Word32', etc.) that
-- supports bitwise operations.
--
-- * The number of bits in 'w' must be at least as many as the number of
-- constructors in 'flag'.
--
-- [Usage:]
--
-- @
-- data PizzaTopping =
-- Cheese
-- | Mushrooms
-- | Pineapple
-- | Ham
-- deriving (Show, Eq, Bounded, Enum)
--
-- type PizzaMask = Bitmask Word8 PizzaTopping
-- @
--
newtype Bitmask w flag = Bitmask w
deriving (Eq, Ord, Show, Bits)
type Bitmask8 = Bitmask Word8
type Bitmask16 = Bitmask Word16
type Bitmask32 = Bitmask Word32
type Bitmask64 = Bitmask Word64
-- | Create a bitmask from raw bits.
--
fromBits :: w -> Bitmask w flag
fromBits = Bitmask
-- | Convert a bitmask to raw bits.
--
toBits :: Bitmask w flag -> w
toBits (Bitmask w) = w
-- | Check that a bitmask can represent all flags.
--
-- >>> checkBitmask (allFlags :: Bitmask8 PizzaTopping)
-- True
--
checkBitmask :: forall flag w. (FiniteBits w, Enum flag, Bounded flag)
=> Bitmask w flag -> Bool
checkBitmask (Bitmask w) =
finiteBitSize w >= (fromEnum (maxBound :: flag) + 1)
---------------------- Creation and conversion to lists ----------------------
-- | A bitmask with all flags set to 'False'.
--
-- >>> getFlag Mushrooms (noFlag :: PizzaMask)
-- False
--
noFlag :: Bits w => Bitmask w flag
noFlag = Bitmask zeroBits
-- | A bitmask with all flags set to 'True'.
--
-- >>> getFlag Mushrooms (allFlags :: PizzaMask)
-- True
--
allFlags :: (FiniteBits w, Enum flag) => Bitmask w flag
allFlags = Bitmask oneBits
-- | Create a bitmask from a list of flags to set to 'True'.
--
-- >>> hawaiian = fromFlags [Pineapple, Ham, Cheese] :: PizzaMask
--
fromFlags :: (Bits w, Enum flag) => [flag] -> Bitmask w flag
fromFlags = foldr insertFlag noFlag
-- | Convert a bitmask to a list of flags that are set to 'True'.
--
-- >>> toFlags hawaiian
-- [Cheese,Pineapple,Ham]
--
toFlags :: forall flag w. (FiniteBits w, Enum flag, Bounded flag)
=> Bitmask w flag -> [flag]
toFlags bm@(Bitmask w) =
let n = finiteBitSize w in
filter (`getFlag` bm)
[toEnum i | i <- [0 .. min (n - 1) (fromEnum (maxBound :: flag))]]
-- | Create a bitmask from a list of flags to set to 'False'
--
-- >>> veggie = fromExceptFlags [Ham] :: PizzaMask
--
fromExceptFlags :: (FiniteBits w, Enum flag) => [flag] -> Bitmask w flag
fromExceptFlags = foldr deleteFlag allFlags
-- | Convert a bitmask to a list of flags that are set to 'False'.
--
-- >>> toExceptFlags veggie
-- [Ham]
toExceptFlags :: forall flag w. (FiniteBits w, Enum flag, Bounded flag)
=> Bitmask w flag -> [flag]
toExceptFlags bm@(Bitmask w) =
let n = finiteBitSize w in
filter (not . (`getFlag` bm))
[toEnum i | i <- [0 .. min (n - 1) (fromEnum (maxBound :: flag))]]
-- | Convert an association list of flags and boolean values to a bitmask.
--
-- >>> funghi = fromFlagsBool [(Cheese, True), (Ham, False), (Mushrooms, True)] :: PizzaMask
fromFlagsBool :: forall flag w. (Bits w, Enum flag)
=> [(flag, Bool)] -> Bitmask w flag
fromFlagsBool = foldr (uncurry setFlag) noFlag
-- | Convert a bitmask to an association list of flags and boolean values.
--
-- >>> toFlagsBool funghi
-- [(Cheese,True),(Mushrooms,True),(Pineapple,False),(Ham,False)]
--
toFlagsBool :: forall flag w. (FiniteBits w, Enum flag, Bounded flag)
=> Bitmask w flag -> [(flag, Bool)]
toFlagsBool bm@(Bitmask w) =
let n = finiteBitSize w in
[(toEnum i, getFlag (toEnum i) bm)
| i <- [0 .. min (n - 1) (fromEnum (maxBound :: flag))]]
---------------------- Querying and modifying flags ----------------------
-- | Get a flag from a bitmask.
--
-- >>> getFlag Mushrooms hawaiian
-- False
--
getFlag :: (Bits w, Enum flag) => flag -> Bitmask w flag -> Bool
getFlag flag (Bitmask w) = testBit w (fromEnum flag)
-- | Get multiple flags from a bitmask.
--
-- >>> getFlags [Cheese, Mushrooms] hawaiian
-- [True,False]
--
getFlags :: (Bits w, Enum flag) => [flag] -> Bitmask w flag -> [Bool]
getFlags fs bm = map (`getFlag` bm) fs
-- | Add a flag to a bitmask (set it to 'True').
--
-- >>> margherita = addFlag Cheese (noFlag :: PizzaMask)
--
addFlag :: (Bits w, Enum flag) =>
flag -> Bitmask w flag -> Bitmask w flag
addFlag f = setFlag f True
{-# DEPRECATED addFlag "Use insertFlag instead." #-}
-- | Add multiple flags to a bitmask (set them to 'True').
--
-- >>> hawaiian = addFlags [Pineapple, Ham] margherita
--
addFlags :: (Bits w, Enum flag)
=> [flag] -> Bitmask w flag -> Bitmask w flag
addFlags fs bm = foldr addFlag bm fs
{-# DEPRECATED addFlags "Use insertFlags instead." #-}
-- | Add a flag to a bitmask (set it to 'True').
--
-- >>> margherita = insertFlag Cheese (noFlag :: PizzaMask)
--
insertFlag :: (Bits w, Enum flag) =>
flag -> Bitmask w flag -> Bitmask w flag
insertFlag f = setFlag f True
-- | Add multiple flags to a bitmask (set them to 'True').
--
-- >>> hawaiian = insertFlags [Pineapple, Ham] margherita
--
insertFlags :: (Bits w, Enum flag)
=> [flag] -> Bitmask w flag -> Bitmask w flag
insertFlags fs bm = foldr insertFlag bm fs
-- | Remove a flag from a bitmask (set it to 'False').
--
-- >>> veggie = deleteFlag Ham (allFlags :: PizzaMask)
--
deleteFlag :: (Bits w, Enum flag) =>
flag -> Bitmask w flag -> Bitmask w flag
deleteFlag f = setFlag f False
-- | Remove multiple flags from a bitmask (set them to 'False').
--
-- >>> picky = deleteFlags [Pineapple, Ham] (allFlags :: PizzaMask)
--
deleteFlags :: (Bits w, Enum flag)
=> [flag] -> Bitmask w flag -> Bitmask w flag
deleteFlags fs bm = foldr deleteFlag bm fs
-- | Set a flag in a bitmask.
--
-- >>> funghi = setFlag Mushrooms True margherita
setFlag :: (Bits w, Enum flag) =>
flag -> Bool -> Bitmask w flag -> Bitmask w flag
setFlag flag value (Bitmask w) = Bitmask $
if value then
setBit w (fromEnum flag)
else
clearBit w (fromEnum flag)
-- | Set multiple flags in a bitmask.
--
-- >>> hawaiian = setFlags [(Ham, True), (Pineapple, True)] margherita
--
setFlags :: (Bits w, Enum flag) =>
[(flag, Bool)] -> Bitmask w flag -> Bitmask w flag
setFlags fs bm = foldr (uncurry setFlag) bm fs
-- | Flip a flag in a bitmask.
--
-- >>> margherita = flipFlag Cheese (noFlag :: PizzaMask)
flipFlag :: (Bits w, Enum flag) =>
flag -> Bitmask w flag -> Bitmask w flag
flipFlag flag (Bitmask w) = Bitmask $ complementBit w (fromEnum flag)
-- | Flip multiple flags in a bitmask.
--
-- >>> funghi = flipFlags [Mushrooms, Ham, Pineapple] hawaiian
flipFlags :: (Bits w, Enum flag) =>
[flag] -> Bitmask w flag -> Bitmask w flag
flipFlags fs bm = foldr flipFlag bm fs
-- | Modify a flag in a bitmask.
--
-- >>> veggie = modifyFlag Ham not (allFlags :: PizzaMask)
--
modifyFlag :: (Bits w, Enum flag) =>
flag -> (Bool -> Bool) -> Bitmask w flag -> Bitmask w flag
modifyFlag flag f (Bitmask w) = Bitmask $
if f (testBit w (fromEnum flag)) then
setBit w (fromEnum flag)
else
clearBit w (fromEnum flag)
-- | Modify multiple flags in a bitmask.
--
-- >>> picky = modifyFlags [Pineapple, Ham] not (allFlags :: PizzaMask)
--
modifyFlags :: (Bits w, Enum flag) =>
[flag] -> (Bool -> Bool) -> Bitmask w flag -> Bitmask w flag
modifyFlags fs f bm = foldr (`modifyFlag` f) bm fs