eset 0.1.0

A simple library for flags like enums.
Documentation
//! A simple library for flags like enums.
//!
//! Unlike crates like `enumset` or `bitflags`,
//! this crate relies entirely on rust's type system to function,
//! with no derive macros required.
//!
//! This crate does not provide derive functionalities but instead rely on
//! the user to implement the [`ReprEnum`] trait. The user can choose to use
//! crates like [`num_enum`](https://docs.rs/num_enum/latest/num_enum/) and
//! [`strum`](https://docs.rs/strum/latest/strum/) to provide additional functionalities.

#![no_std]
use core::fmt::{Debug, Display};
use core::ops::Shl;
use core::str::FromStr;

mod operators;
mod serialize;
mod macros;

mod sealed {
    use core::ops::{Not, BitAnd, BitOr, BitXor, BitAndAssign, BitOrAssign, BitXorAssign, AddAssign};
    use core::ops::{Shl, Shr};
    use core::fmt::LowerHex;

    macro_rules! impl_num {
        ($($ty: ty),*) => {
            $(impl Number for $ty {
                const ZERO: Self = 0;
                const ONE: Self = 1;
            })*
        };
    }

    pub trait Number: Sized + Eq + Copy + LowerHex +
            Not<Output=Self> + AddAssign +
            BitAnd<Output=Self> + BitOr<Output=Self> + BitXor<Output=Self> +
            BitAndAssign + BitOrAssign + BitXorAssign{
        const ZERO: Self;
        const ONE: Self;
    }
    //impl_num!(u8, u16, u32, u64, u128, usize);
    impl_num!(u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize);

    pub trait Shift<T>: Sized {
        fn ashl(self, n: T) -> Self;
        fn ashr(self, n: T) -> Self;
    }

    macro_rules! impl_shift {
        ($($unsigned: ty, $signed: ty),*) => {
            $(
                impl<T> Shift<T> for $unsigned where Self: Shl<T, Output=Self> + Shr<T, Output=Self>{
                    fn ashl(self, n: T) -> Self {
                        self << n
                    }
                    fn ashr(self, n: T) -> Self {
                        self >> n
                    }
                }
                impl<T> Shift<T> for $signed where $unsigned: Shl<T, Output=$unsigned> + Shr<T, Output=$unsigned> {
                    fn ashl(self, n: T) -> Self {
                        (((self as $unsigned) << n) as $signed)
                    }
                    fn ashr(self, n: T) -> Self {
                        (((self as $unsigned) >> n) as $signed)
                    }
                }
            )*
        };
    }
    impl_shift!(u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize);

}

use sealed::{Number, Shift};

/// A trait indicating a type is a enum and can be made into an enum set.
///
/// # [`Repr`](ReprEnum::FlagsRepr)
/// The integer representation of the enum.
/// Requires `into` casting from the enum and `try_into` casting to the enum.
///
/// Use [`num_enum`](https://docs.rs/num_enum/latest/num_enum/) to generate these implementations.
///
/// # [`FlagsRepr`](ReprEnum::FlagsRepr)
/// The flags representation of the enum,
/// this should usually be a larger type like [`u64`] or [`u128`].
/// Currently signed numbers are allowed but negative signed numbers are not supported.

pub trait ReprEnum: Sized + Into<Self::Repr>{
    type Repr: TryInto<Self> + Number;
    type FlagsRepr: Shl<Self::Repr, Output = Self::FlagsRepr> + Number + Shift<Self::Repr> + Shift<u32>;
}

/// An enum set for a fieldless enum T.
///
/// # Operators
///
/// * `!`: bitwise not
/// * `|`: bitwise or, or union
/// * `&`: bitwise and, or intersection
/// * `^`: bitwise xor, or exclusive or
/// * `-`: difference, `a - b` is equivalent to `a ^ (a & b)`
/// * `+`: alias for union
///
/// Operators are not availble to T by default unless T implements Into<ESet<T>>.
/// See [`derive_eset`].
///
/// If the enum implements [`FromStr`],
/// this type provides parsing from CSV like syntax with a given `SEP`.
/// You can use a crate like [`strum`](https://docs.rs/strum/latest/strum/)
/// for this type of functionality.
///
/// If `serde` is enabled, ths type serializes into a CSV like string in
/// human readable formats and its integer flags representation in
/// non-human-readable formats.
///
/// While this type is perfectly usable,
/// the user should alias this type ASAP with a fixed SEP for better ergonomics.
/// ```
/// # use eset::*;
/// type Csv<T> = ESet<T, ','>;
/// ```
/// This struct does not maintain the invarient that all bits correspond to valid T,
/// instead invalid bits are ignored during iteration and parsing. This makes
/// operations like bitwise not and binary serialization safe to do.
#[derive(Clone, Copy, Hash)]
pub struct ESet<T: ReprEnum, const SEP: char='|'>(T::FlagsRepr);


impl<T: ReprEnum, const S: char> ESet<T, S>{

    /// The empty ESet.
    pub const NONE: Self = Self(T::FlagsRepr::ZERO);

    #[inline]
    #[must_use]
    /// Create a new `Eset` from an single enum.
    pub fn new(item: T) -> Self{
        Self(T::FlagsRepr::ONE << item.into())
    }

    #[inline]
    #[must_use]
    /// Create a new `Eset` from an iterator of enums.
    pub fn new_flags<TIter>(item: impl IntoIterator<Item = TIter>) -> Self where Self: FromIterator<TIter>{
        Self::from_iter(item)
    }

    #[inline]
    #[must_use]
    /// Check if the `ESet` is empty.
    pub fn is_empty(&self) -> bool{
        self.0 == T::FlagsRepr::ZERO
    }

    #[inline]
    #[must_use]
    /// Check if the `ESet` contains a specific enum.
    pub fn contains(&self, item: T) -> bool{
        self.0 & (T::FlagsRepr::ONE.ashl(item.into())) != T::FlagsRepr::ZERO
    }

    #[inline]
    /// mutate the `ESet` by inserting an enum.
    pub fn insert(&mut self, item: T) -> &mut Self{
        self.0 |= T::FlagsRepr::ONE.ashl(item.into());
        self
    }

    #[inline]
    /// mutate the `ESet` by removing an enum.
    pub fn remove(&mut self, item: T) -> &mut Self{
        self.0 ^= self.0 & (T::FlagsRepr::ONE.ashl(item.into()));
        self
    }

    #[inline]
    #[must_use]
    /// Create a new `ESet` by inserting an enum.
    pub fn with(self, item: T) -> Self{
        Self(self.0 | (T::FlagsRepr::ONE << item.into()))
    }

    #[inline]
    #[must_use]
    /// Create a new `ESet` by removing an enum.
    pub fn without(self, item: T) -> Self{
        Self(self.0 ^ (self.0 & (T::FlagsRepr::ONE << item.into())))
    }

    #[inline]
    #[must_use]
    /// Convert a `ESet` into one with a different separator.
    pub const fn with_separator<const SEP: char>(self) -> ESet<T, SEP>{
        ESet(self.0)
    }

    #[inline]
    #[must_use]
    /// Iterate over enums in the `ESet`.
    pub fn iter(&self) -> ESetIter<T, S>{
        self.into_iter()
    }
}

impl<T: ReprEnum, const S: char> Default for ESet<T, S>{
    fn default() -> Self {
        Self::NONE
    }
}

/// Enum iterator for ESet
///
/// This iterator yields enums in the ESet and ignores invalid values.
pub struct ESetIter<T: ReprEnum, const SEP: char> {
    flag: T::FlagsRepr,
    cursor: T::Repr,
}

impl<T: ReprEnum, const S: char> Iterator for ESetIter<T, S>  {
    type Item = T;

    fn next(&mut self) -> Option<Self::Item> {
        if self.flag == T::FlagsRepr::ZERO {
            None
        } else if self.flag & T::FlagsRepr::ONE == T::FlagsRepr::ONE {
            let result = self.cursor.try_into();
            self.flag = self.flag.ashr(1);
            self.cursor += T::Repr::ONE;
            Some(result.ok()?)
        } else {
            self.flag = self.flag.ashr(1);
            self.cursor += T::Repr::ONE;
            self.next()
        }
    }
}


impl<T: ReprEnum, const S: char> IntoIterator for ESet<T, S> {
    type Item = T;
    type IntoIter = ESetIter<T, S>;
    fn into_iter(self) -> Self::IntoIter {
        ESetIter {
            flag: self.0,
            cursor: T::Repr::ZERO,
        }
    }
}

impl<T: ReprEnum, const S: char> IntoIterator for &ESet<T, S> {
    type Item = T;
    type IntoIter = ESetIter<T, S>;
    fn into_iter(self) -> Self::IntoIter {
        ESetIter {
            flag: self.0,
            cursor: T::Repr::ZERO,
        }
    }
}

impl<T: ReprEnum, const S: char> FromIterator<T> for ESet<T, S> {
    fn from_iter<A: IntoIterator<Item = T>>(iter: A) -> Self {
        let mut result = Self::NONE;
        for i in iter{
            result.0 |= Self::new(i).0;
        }
        result
    }
}

impl<'t, T: ReprEnum, const S: char> FromIterator<&'t T> for ESet<T, S> where T: 't + Copy{
    fn from_iter<A: IntoIterator<Item = &'t T>>(iter: A) -> Self {
        let mut result = Self::NONE;
        for i in iter{
            result.0 |= Self::new(*i).0;
        }
        result
    }
}

impl<T: ReprEnum, const S: char> Debug for ESet<T, S> where T: Debug{
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        let mut set = f.debug_set();
        for item in self.iter() {
            set.entry(&item);
        }
        set.finish()
    }
}

impl<T: ReprEnum, const S: char> Display for ESet<T, S> where T: Display{
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        let mut iter = self.into_iter();
        if let Some(first) = iter.next(){
            first.fmt(f)?;
        }
        for item in iter {
            <char as Display>::fmt(&S, f)?;
            item.fmt(f)?;
        }
        Ok(())
    }
}

impl<T: ReprEnum, const S: char> FromStr for ESet<T, S> where T: FromStr{
    type Err = T::Err;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut result = Self::NONE;
        for slice in s.split(S){
            result.0 |= Self::new(T::from_str(slice)?).0;
        }
        Ok(result)
    }
}