mice 0.11.1

messing with dice
Documentation
//! This module provides facilities for rejecting dice expressions
//! not supported by your chosen backend.
//!
//! The old dice backends no longer support the full list of features
//! the parser is allowed to accept.
//! While they are on the road to deprecation, the MIR based pipeline isn't ready yet.

/// A description of which backends support a given dice expression,
/// or set of features.
#[derive(Debug)]
#[non_exhaustive]
pub struct Support {
    /// [`interp`](crate::interp)
    pub ast_interp: bool,
    /// [`stack`](crate::stack)
    pub stack: bool,
    /// [`mir`](crate::mir)
    pub mir: MirSupport,
}
/// A description of which *MIR-based* backends support a
/// given dice expression, or set of features.
#[derive(Debug)]
#[non_exhaustive]
pub struct MirSupport {
    /// [`mir::stack`](crate::mir::stack)
    pub stack: bool,
}

/// A backend specifier.
#[derive(Debug)]
#[non_exhaustive]
pub enum Target {
    AstInterp,
    Stack,
    Mir(MirTarget),
}
/// A *MIR-specific* backend specifier.
#[derive(Debug)]
#[non_exhaustive]
pub enum MirTarget {
    Stack,
}

impl Target {
    /// Get the list of features supported by a backend.
    pub fn features(&self) -> Features {
        match self {
            Target::AstInterp | Target::Stack => Features {
                basic: true,
                keep_high: true,
                keep_low: false,
                filters: false,
                explosion: false,
            },
            Target::Mir(MirTarget::Stack) => Features {
                basic: true,
                keep_high: true,
                keep_low: true,
                filters: false,
                explosion: true,
            },
        }
    }

    /// Check whether a list of features is supported by a backend.
    // `(supported && used) || !used` reduces to `!used || supported`
    pub fn supports(&self, features: Features) -> bool {
        let supported = self.features();
        (!features.basic || supported.basic)
            && (!features.keep_high || supported.keep_high)
            && (!features.keep_low || supported.keep_low)
            && (!features.filters || supported.filters)
            && (!features.explosion || supported.explosion)
    }
}

/// A description of features a dice expression uses.
#[derive(Default, Debug, Copy, Clone)]
#[non_exhaustive]
pub struct Features {
    /// Basic dice terms `NdN`, and basic arithmetic `+` and `-`.
    pub basic: bool,
    /// The "keep high" filter.
    pub keep_high: bool,
    /// The "keep low" filter.
    pub keep_low: bool,
    /// The other filters:
    /// - "drop high"
    /// - "drop low"
    pub filters: bool,
    /// Dice explosion.
    pub explosion: bool,
}

impl Features {
    /// Get the list of backends that support a list of features.
    pub fn supported_by(&self) -> Support {
        Support {
            ast_interp: Target::AstInterp.supports(*self),
            stack: Target::Stack.supports(*self),
            mir: MirSupport {
                stack: Target::Mir(MirTarget::Stack).supports(*self),
            },
        }
    }

    /// Get the list of features used by a dice expression.
    pub fn of(expression: &crate::parse::Program) -> Self {
        use crate::for_;
        use crate::parse::Term;
        let mut features = Features::default();
        for_! [ (term, _ancestors) in expression.postorder() => {
            match term {
                Term::Constant(_) | Term::DiceRoll(_, _) | Term::Add(_, _) |
                Term::Subtract(_, _) | Term::UnarySubtract(_) |
                Term::UnaryAdd(_) => features.basic = true,
                Term::KeepHigh(_, _) => features.keep_high = true,
                Term::KeepLow(_, _) => features.keep_low = true,
                Term::Explode(_) => features.explosion = true,
            }
        }];
        features
    }
}

/// Type level reification of stuff. Just [`Target`] for now.
/// We use this in place of full const generics.
pub mod type_level {
    /// A type level reification of [`Target`](crate::backend_support::Target).
    #[allow(non_snake_case)]
    pub mod Target {
        mod seal {
            /// A non-publically exported trait.
            pub trait Sealed {}
            /// A type level reification of [`Target`](crate::backend_support::Target).
            pub trait Target: Sealed + Into<crate::backend_support::Target> {
                fn reify() -> crate::backend_support::Target;
            }
        }
        pub use seal::Target;
        macro_rules! decl_backends {
            (@[$m:ident [$($b:ident => $c:ident),*]]) => {
                #[allow(non_snake_case)]
                pub mod $m {
                    use super::seal::Target;
                    $(use crate::backend_support:: $c;
                      pub struct $b;
                      impl super::seal::Sealed for $b {}
                      impl From<$b> for crate::backend_support::Target {
                          fn from($b: $b) -> Self {
                              crate::backend_support::Target :: $m ( $c :: $b )
                          }
                      }
                      impl Target for $b {
                          fn reify() -> crate::backend_support::Target {
                              $b.into()
                          }
                      })*
                }
            };
            (@[$t:ident]) => {
                pub struct $t;
                impl seal::Sealed for $t {}
                impl From<$t> for crate::backend_support::Target {
                    fn from($t: $t) -> Self {
                        crate::backend_support::Target:: $t
                    }
                }
                impl Target for $t {
                    fn reify() -> crate::backend_support::Target {
                        $t.into()
                    }
                }
            };
            ($( $a:tt $([$($b:ident => $c:ident),*])? ),*) => {
                $(decl_backends! {@[
                    $a $([$($b => $c),*])?
                ]})*
            };
        }
        decl_backends![AstInterp, Stack, Mir[Stack => MirTarget]];
    }
}
pub use type_level::Target as TyTarget;

/// Error for when a dice program uses unsupported features.
pub struct UnsupportedFeature;

/// A wrapper around [`Program`](crate::parse::Program) that enforces having
/// been checked for feature support on a particular backend.
#[derive(::derive_more::Deref)]
pub struct FeatureCheckedProgram<'a, Target> {
    #[deref]
    program: &'a crate::parse::Program,
    _target: ::core::marker::PhantomData<Target>,
}
impl<'a, T: TyTarget::Target> FeatureCheckedProgram<'a, T> {
    pub fn check(program: &'a crate::parse::Program) -> Result<Self, UnsupportedFeature> {
        if T::reify().supports(Features::of(program)) {
            Ok(Self {
                program,
                _target: ::core::marker::PhantomData,
            })
        } else {
            Err(UnsupportedFeature)
        }
    }
}