use crate::ast::{Expr, ParserError};
use crate::ast_evaluation::{FixedSource, FixedValue, RandCrateSource, StochasticEvaluable};
use std::fmt::{Debug, Display};
use std::str::FromStr;
use thiserror::Error;
mod ast;
mod ast_evaluation;
#[derive(Error, Debug)]
pub enum EvaluateError {
#[error("Reroll value ({reroll}) is higher than the number of dice sides ({dice_sides})")]
RerollValueHigherThanDiceSides {
reroll: u32,
dice_sides: u32,
},
#[error("Minimum value ({minimum}) is higher than the dice sides ({dice_size})")]
MinimumValueHigherThanDiceSides {
minimum: u32,
dice_size: u32,
},
#[error("Maximum value cannot be 0")]
MaximumValueCannotBe0,
#[error(transparent)]
OtherErrors(#[from] Box<dyn std::error::Error + Send + Sync>),
}
pub trait Evaluable<Out> {
fn evaluate(&self) -> Result<Out, EvaluateError>;
}
pub struct Dice {
pub sides: u32,
}
impl From<u32> for Dice {
fn from(sides: u32) -> Self {
Dice { sides }
}
}
impl Evaluable<u32> for Dice {
#[mutants::skip] fn evaluate(&self) -> Result<u32, EvaluateError> {
let result = rand::random_range(1..self.sides + 1);
Ok(result)
}
}
#[derive(Debug, Error)]
pub enum DiceExpressionParsingError {
#[error("Could not parse '{parsed}: {err}'")]
ExpressionParsingFailed {
parsed: String,
err: ParserError,
},
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct DiceExpression {
expr: Expr,
}
impl Display for DiceExpression {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.expr)
}
}
impl FromStr for DiceExpression {
type Err = DiceExpressionParsingError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let expr = ast::Parser::parse(s).map_err(|err| {
DiceExpressionParsingError::ExpressionParsingFailed {
parsed: s.to_string(),
err,
}
})?;
Ok(DiceExpression { expr })
}
}
impl Evaluable<i64> for DiceExpression {
#[mutants::skip] fn evaluate(&self) -> Result<i64, EvaluateError> {
self.expr.evaluate(&mut RandCrateSource::new())
}
}
impl DiceExpression {
pub fn minimum(&self) -> Result<i64, EvaluateError> {
self.expr.evaluate(&mut FixedSource {
value: FixedValue::Minimum,
})
}
pub fn maximum(&self) -> Result<i64, EvaluateError> {
self.expr.evaluate(&mut FixedSource {
value: FixedValue::Maximum,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_minimum() {
let expr = DiceExpression::from_str("d6").unwrap();
let minimum = expr.minimum().unwrap();
assert_eq!(1, minimum);
}
#[test]
fn test_minimum_addition() {
let expr = DiceExpression::from_str("d6 + 2").unwrap();
let minimum = expr.minimum().unwrap();
assert_eq!(3, minimum);
}
#[test]
fn test_maximum() {
let expr = DiceExpression::from_str("d6").unwrap();
let minimum = expr.maximum().unwrap();
assert_eq!(6, minimum);
}
}