use core::marker::PhantomData;
pub trait EffectId {
const ID: u64;
const NAME: &'static str;
#[inline]
fn mask() -> u128 {
1u128 << Self::ID
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EffectSet<const MASK: u128>;
impl<const MASK: u128> EffectSet<MASK> {
pub const MASK: u128 = MASK;
#[inline]
pub const fn new() -> Self {
EffectSet
}
#[inline]
pub const fn is_empty() -> bool {
MASK == 0
}
#[inline]
pub const fn count() -> u32 {
MASK.count_ones()
}
#[inline]
pub const fn contains(effect_id: u64) -> bool {
(MASK >> effect_id) & 1 == 1
}
#[inline]
pub fn iter() -> EffectSetIter {
EffectSetIter {
mask: MASK,
position: 0,
}
}
}
impl<const MASK: u128> Default for EffectSet<MASK> {
#[inline]
fn default() -> Self {
Self::new()
}
}
pub struct EffectSetIter {
mask: u128,
position: u64,
}
impl Iterator for EffectSetIter {
type Item = u64;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
while self.position < 128 {
let pos = self.position;
self.position += 1;
if (self.mask >> pos) & 1 == 1 {
return Some(pos);
}
}
None
}
}
pub type EffectSetVacuus = EffectSet<0>;
pub type Pure = EffectSetVacuus;
#[inline(always)]
pub fn assert_has_effect<R: EffectRow, const EFFECT_ID: u64>() {
const {
assert!(
(R::MASK >> EFFECT_ID) & 1 == 1,
"effect row is missing a required effect"
);
}
}
#[inline(always)]
pub fn assert_without_effect<R: EffectRow, const EFFECT_ID: u64>() {
const {
assert!(
(R::MASK >> EFFECT_ID) & 1 == 0,
"effect row unexpectedly contains an excluded effect"
);
}
}
#[inline]
#[must_use]
pub const fn union(a: u128, b: u128) -> u128 {
a | b
}
#[inline]
#[must_use]
pub const fn intersection(a: u128, b: u128) -> u128 {
a & b
}
#[inline]
#[must_use]
pub const fn difference(a: u128, b: u128) -> u128 {
a & !b
}
#[inline]
#[must_use]
pub const fn extend(set: u128, effect_id: u64) -> u128 {
set | (1 << effect_id)
}
#[inline]
#[must_use]
pub const fn contract(set: u128, effect_id: u64) -> u128 {
set & !(1 << effect_id)
}
#[inline(always)]
pub fn assert_subrow<R: EffectRow, const SUPER: u128>() {
const {
assert!(
(R::MASK & SUPER) == R::MASK,
"effect row is not a subset of the target row"
);
}
}
#[inline(always)]
pub fn assert_disjoint<R: EffectRow, const OTHER: u128>() {
const { assert!((R::MASK & OTHER) == 0, "effect rows are not disjoint") }
}
pub trait EffectRow: Send + Sync + 'static {
const MASK: u128;
}
impl<const MASK: u128> EffectRow for EffectSet<MASK> {
const MASK: u128 = MASK;
}
#[inline(always)]
pub fn assert_has_effect_type<R: EffectRow, E: EffectId>() {
const {
assert!(
(R::MASK >> E::ID) & 1 == 1,
"effect row is missing a required typed effect"
);
}
}
pub mod builtin_ids {
pub const IO: u64 = 0;
pub const STATE: u64 = 1;
pub const READER: u64 = 2;
pub const WRITER: u64 = 3;
pub const ERROR: u64 = 4;
pub const ASYNC: u64 = 5;
pub const RANDOM: u64 = 6;
pub const TIME: u64 = 7;
pub const RESOURCE: u64 = 8;
pub const CONCURRENCY: u64 = 9;
pub const USER_START: u64 = 32;
}
pub struct IoEffectV2;
impl EffectId for IoEffectV2 {
const ID: u64 = builtin_ids::IO;
const NAME: &'static str = "IO";
}
pub struct StateEffectV2<S>(PhantomData<S>);
impl<S> EffectId for StateEffectV2<S> {
const ID: u64 = builtin_ids::STATE;
const NAME: &'static str = "State";
}
pub struct ReaderEffectV2<R>(PhantomData<R>);
impl<R> EffectId for ReaderEffectV2<R> {
const ID: u64 = builtin_ids::READER;
const NAME: &'static str = "Reader";
}
pub struct WriterEffectV2<W>(PhantomData<W>);
impl<W> EffectId for WriterEffectV2<W> {
const ID: u64 = builtin_ids::WRITER;
const NAME: &'static str = "Writer";
}
pub struct ErrorEffectV2<E>(PhantomData<E>);
impl<E> EffectId for ErrorEffectV2<E> {
const ID: u64 = builtin_ids::ERROR;
const NAME: &'static str = "Error";
}
pub struct AsyncEffectV2;
impl EffectId for AsyncEffectV2 {
const ID: u64 = builtin_ids::ASYNC;
const NAME: &'static str = "Async";
}
impl EffectId for super::common::IoEffectus {
const ID: u64 = builtin_ids::IO;
const NAME: &'static str = "IO";
}
impl<S: Send + Sync + 'static> EffectId for super::common::StatusEffectus<S> {
const ID: u64 = builtin_ids::STATE;
const NAME: &'static str = "State";
}
impl<R: Send + Sync + 'static> EffectId for super::common::ReaderEffectus<R> {
const ID: u64 = builtin_ids::READER;
const NAME: &'static str = "Reader";
}
impl<W: Send + Sync + 'static> EffectId for super::common::ScriptorEffectus<W> {
const ID: u64 = builtin_ids::WRITER;
const NAME: &'static str = "Writer";
}
impl<E: Send + Sync + 'static> EffectId for super::common::ErrorEffectus<E> {
const ID: u64 = builtin_ids::ERROR;
const NAME: &'static str = "Error";
}
impl EffectId for super::common::AsyncEffectus {
const ID: u64 = builtin_ids::ASYNC;
const NAME: &'static str = "Async";
}
impl EffectId for super::common::RandomEffectus {
const ID: u64 = builtin_ids::RANDOM;
const NAME: &'static str = "Random";
}
impl EffectId for super::common::TempusEffectus {
const ID: u64 = builtin_ids::TIME;
const NAME: &'static str = "Time";
}
impl<R: Send + Sync + 'static> EffectId for super::common::ResourceEffectus<R> {
const ID: u64 = builtin_ids::RESOURCE;
const NAME: &'static str = "Resource";
}
pub type IoRow = EffectSet<{ 1 << builtin_ids::IO }>;
pub type StateRow = EffectSet<{ 1 << builtin_ids::STATE }>;
pub type ReaderRow = EffectSet<{ 1 << builtin_ids::READER }>;
pub type ErrorRow = EffectSet<{ 1 << builtin_ids::ERROR }>;
pub type IoStateRow = EffectSet<{ (1 << builtin_ids::IO) | (1 << builtin_ids::STATE) }>;
pub type IoStateErrorRow =
EffectSet<{ (1 << builtin_ids::IO) | (1 << builtin_ids::STATE) | (1 << builtin_ids::ERROR) }>;
impl<const MASK: u128> core::fmt::Display for EffectSet<MASK> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if MASK == 0 {
return write!(f, "Pure");
}
write!(f, "{{")?;
let mut first = true;
for id in Self::iter() {
if !first {
write!(f, ", ")?;
}
first = false;
match id {
0 => write!(f, "IO")?,
1 => write!(f, "State")?,
2 => write!(f, "Reader")?,
3 => write!(f, "Writer")?,
4 => write!(f, "Error")?,
5 => write!(f, "Async")?,
6 => write!(f, "Random")?,
7 => write!(f, "Time")?,
8 => write!(f, "Resource")?,
9 => write!(f, "Concurrency")?,
n => write!(f, "Effect#{n}")?,
}
}
write!(f, "}}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_effect_set_empty() {
type Empty = EffectSet<0>;
assert!(Empty::is_empty());
assert_eq!(Empty::count(), 0);
}
#[test]
fn test_effect_set_single() {
type IoOnly = EffectSet<1>;
assert!(!IoOnly::is_empty());
assert_eq!(IoOnly::count(), 1);
assert!(IoOnly::contains(0));
assert!(!IoOnly::contains(1));
}
#[test]
fn test_effect_set_multiple() {
type IoAndState = EffectSet<3>;
assert_eq!(IoAndState::count(), 2);
assert!(IoAndState::contains(0));
assert!(IoAndState::contains(1));
assert!(!IoAndState::contains(2));
}
#[test]
fn test_effect_set_iter() {
type Effects = EffectSet<{ (1 << 0) | (1 << 2) | (1 << 5) }>;
let ids: alloc::vec::Vec<_> = Effects::iter().collect();
assert_eq!(ids, alloc::vec![0, 2, 5]);
}
#[test]
fn test_effect_id_trait() {
assert_eq!(IoEffectV2::ID, 0);
assert_eq!(IoEffectV2::NAME, "IO");
assert_eq!(IoEffectV2::mask(), 1);
}
#[test]
fn test_union() {
type A = EffectSet<0b0011>; type B = EffectSet<0b0110>; type Ab = EffectSet<{ union(A::MASK, B::MASK) }>;
assert_eq!(Ab::MASK, 0b0111); assert!(Ab::contains(0));
assert!(Ab::contains(1));
assert!(Ab::contains(2));
}
#[test]
fn test_intersection() {
type A = EffectSet<0b0011>;
type B = EffectSet<0b0110>;
type Ab = EffectSet<{ intersection(A::MASK, B::MASK) }>;
assert_eq!(Ab::MASK, 0b0010); assert!(!Ab::contains(0));
assert!(Ab::contains(1));
assert!(!Ab::contains(2));
}
#[test]
fn test_difference() {
type A = EffectSet<0b0111>; type B = EffectSet<0b0011>; type Diff = EffectSet<{ difference(A::MASK, B::MASK) }>;
assert_eq!(Diff::MASK, 0b0100); }
#[test]
fn test_extend() {
type Base = EffectSet<0b0001>;
type Extended = EffectSet<{ extend(Base::MASK, 2) }>;
assert_eq!(Extended::MASK, 0b0101);
assert!(Extended::contains(0));
assert!(Extended::contains(2));
}
#[test]
fn test_contract() {
type Base = EffectSet<0b0111>;
type Contracted = EffectSet<{ contract(Base::MASK, 1) }>;
assert_eq!(Contracted::MASK, 0b0101);
assert!(Contracted::contains(0));
assert!(!Contracted::contains(1));
assert!(Contracted::contains(2));
}
#[test]
fn test_display() {
use alloc::format;
assert_eq!(format!("{}", EffectSet::<0>::new()), "Pure");
assert_eq!(format!("{}", EffectSet::<1>::new()), "{IO}");
assert_eq!(format!("{}", EffectSet::<3>::new()), "{IO, State}");
}
fn requires_io<R: EffectRow>() {
assert_has_effect::<R, 0>();
}
fn requires_state<R: EffectRow>() {
assert_has_effect::<R, 1>();
}
fn requires_io_and_state<R: EffectRow>() {
assert_has_effect::<R, 0>();
assert_has_effect::<R, 1>();
}
#[test]
fn test_has_effect_compiles() {
requires_io::<EffectSet<1>>();
requires_io::<EffectSet<3>>();
requires_state::<EffectSet<2>>();
requires_state::<EffectSet<3>>();
requires_io_and_state::<EffectSet<3>>();
}
fn requires_subrow<R: EffectRow>() {
assert_subrow::<R, 0b111>();
}
#[test]
fn test_subrow_compiles() {
requires_subrow::<EffectSet<0b000>>(); requires_subrow::<EffectSet<0b001>>(); requires_subrow::<EffectSet<0b011>>(); requires_subrow::<EffectSet<0b111>>(); }
#[test]
fn test_without_effect_and_disjoint_compile() {
assert_without_effect::<EffectSet<0b010>, 0>();
assert_disjoint::<EffectSet<0b001>, 0b110>();
assert_has_effect_type::<EffectSet<1>, IoEffectV2>();
}
}