use std::str::FromStr;
use nanorand::Rng;
use once_cell::sync::Lazy;
use pest::{
iterators::Pair,
prec_climber::{Assoc, Operator as PCOperator, PrecClimber},
};
static CLIMBER: Lazy<PrecClimber<Rule>> = Lazy::new(|| {
PrecClimber::new(vec![
PCOperator::new(Rule::op_add, Assoc::Left) | PCOperator::new(Rule::op_sub, Assoc::Left),
PCOperator::new(Rule::op_multiply, Assoc::Left),
])
});
use crate::{
config::Limit,
error::{CompileError, ParseEnumError},
parser::Rule,
roll::{DiceRoll, ItemRoll, RollTree, RollTreeNode},
tree::{BinaryTree, BinaryTreeNode},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PostProcessor {
Sum,
Avg,
Max,
Min,
}
impl FromStr for PostProcessor {
type Err = ParseEnumError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let res = match s.to_ascii_lowercase().as_str() {
"sum" => Self::Sum,
"avg" => Self::Avg,
"max" => Self::Max,
"min" => Self::Min,
_ => return Err(ParseEnumError),
};
Ok(res)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Dice {
pub times: u64,
pub sided: u64,
pub pp: PostProcessor,
}
impl Dice {
#[must_use]
pub const fn new(n: u64, m: u64) -> Self {
Self::new_with_pp(n, m, PostProcessor::Sum)
}
#[must_use]
pub const fn new_with_pp(n: u64, m: u64, pp: PostProcessor) -> Self {
Self {
times: n,
sided: m,
pp,
}
}
#[allow(clippy::cast_sign_loss)] fn from_pair(pair: Pair<'_, Rule>, limit: &mut Limit<'_>) -> Result<Self, CompileError> {
assert_eq!(pair.as_rule(), Rule::dice);
limit.inc_item_count()?;
let mut pairs = pair.into_inner();
let times = pairs.next().unwrap().as_str().parse::<i64>()?;
let sided = pairs.next().unwrap().as_str().parse::<i64>()?;
limit.check_dice(times, sided)?;
limit.inc_roll_times(times as u64)?;
let pp = pairs
.next()
.map_or(PostProcessor::Sum, |s| s.as_str().parse().unwrap());
Ok(Self {
times: times as u64,
sided: sided as u64,
pp,
})
}
#[must_use]
pub fn roll(&self) -> DiceRoll {
let points = (0..self.times)
.map(|_| nanorand::tls_rng().generate_range(1..=self.sided))
.collect();
DiceRoll::new(points, self.pp)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Item {
Number(i64),
Dice(Dice),
Parentheses(Box<AstTreeNode>),
}
impl Item {
fn from_pair(pair: Pair<'_, Rule>, limit: &mut Limit<'_>) -> Result<Self, CompileError> {
assert_eq!(pair.as_rule(), Rule::item);
let expr = pair.into_inner().next().unwrap();
let result = match expr.as_rule() {
Rule::number => {
limit.inc_item_count()?;
let x = expr.as_str().parse::<i64>()?;
limit.check_number_item(x)?;
Self::Number(x)
}
Rule::dice => Self::Dice(Dice::from_pair(expr, limit)?),
Rule::parentheses => Self::Parentheses(Box::new(AstTreeNode::from_pair(
expr.into_inner().next().unwrap(),
limit,
)?)),
_ => unreachable!(),
};
Ok(result)
}
#[must_use]
pub fn roll(&self) -> ItemRoll {
match self {
Self::Dice(d) => ItemRoll::Dice(d.roll()),
Self::Number(x) => ItemRoll::Number(*x),
Self::Parentheses(e) => ItemRoll::Parentheses(Box::new(e.roll())),
}
}
#[must_use]
pub const fn is_number(&self) -> bool {
std::matches!(self, Item::Number(_))
}
#[must_use]
pub const fn is_dice(&self) -> bool {
std::matches!(self, Item::Dice(_))
}
#[must_use]
pub const fn is_expr(&self) -> bool {
std::matches!(self, Item::Parentheses(_))
}
#[must_use]
pub const fn as_number(&self) -> Option<i64> {
match self {
Self::Number(x) => Some(*x),
_ => None,
}
}
#[must_use]
pub const fn as_dice(&self) -> Option<&Dice> {
match self {
Self::Dice(dice) => Some(dice),
_ => None,
}
}
#[must_use]
pub const fn as_expr(&self) -> Option<&AstTreeNode> {
match self {
Self::Parentheses(e) => Some(e),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Operator {
Add,
Minus,
Multiply,
}
impl FromStr for Operator {
type Err = ParseEnumError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let op = match s {
"+" => Self::Add,
"-" => Self::Minus,
"x" | "*" => Self::Multiply,
_ => return Err(ParseEnumError),
};
Ok(op)
}
}
pub type AstTree = BinaryTree<Item, Operator>;
impl AstTree {
pub fn roll(&self) -> RollTree {
RollTree::new(self.left.roll(), self.right.roll(), self.mid)
}
}
pub type AstTreeNode = BinaryTreeNode<Item, Operator>;
impl AstTreeNode {
pub(crate) fn from_pair(
pair: Pair<'_, Rule>, limit: &mut Limit<'_>,
) -> Result<Self, CompileError> {
let pairs = pair.into_inner();
CLIMBER.climb(
pairs,
|p| {
let item = Item::from_pair(p, limit)?;
Ok(Self::Leaf(item))
},
|left, op, right| {
Ok(Self::Tree(AstTree::new(
left?,
right?,
Operator::from_str(op.as_str()).unwrap(),
)))
},
)
}
pub fn roll(&self) -> RollTreeNode {
match self {
Self::Leaf(item) => RollTreeNode::Leaf(item.roll()),
Self::Tree(tree) => RollTreeNode::Tree(tree.roll()),
}
}
}