diff --git a/src/dice.rs b/src/dice.rs index 522f3f6..9c1b21e 100644 --- a/src/dice.rs +++ b/src/dice.rs @@ -17,6 +17,7 @@ pub enum Expression { Mul(ExpBox, ExpBox), Div(ExpBox, ExpBox), IntDiv(ExpBox, ExpBox), + Pow(ExpBox, ExpBox), } #[derive(Debug, PartialEq, Clone)] @@ -121,9 +122,6 @@ impl Expression { // Exploding dice are early, they add rolls if let Some(fragments) = x { write!(w, "x")?; - if size < 2 { - bail!("Infinite explosion.") - } let mut comparers = Vec::with_capacity(fragments.len()); for fragment in fragments { comparers.push(fragment.comparer(rng, w)?); @@ -265,6 +263,12 @@ impl Expression { let rhs = rhs_node.evaluate(rng, w, precedence)?; lhs.div_euclid(rhs) } + Pow(lhs_node, rhs_node) => { + let lhs = lhs_node.evaluate(rng, w, precedence)?; + write!(w, " ^ ")?; + let rhs = rhs_node.evaluate(rng, w, precedence)?; + lhs.powf(rhs) + } }; if needs_parens { write!(w, ")")?; @@ -294,7 +298,7 @@ impl Expression { } else { 1 }; - if count > 1_000_000 { + if count > DICE_POOL_LIMIT { bail!("Too many dice.") }; let size = size.avg()? as i64; @@ -326,6 +330,7 @@ impl Expression { Expression::Mul(lhs, rhs) => lhs.avg()? * rhs.avg()?, Expression::Div(lhs, rhs) => lhs.avg()? / rhs.avg()?, Expression::IntDiv(lhs, rhs) => lhs.avg()?.div_euclid(rhs.avg()?), + Expression::Pow(lhs, rhs) => lhs.avg()?.powf(rhs.avg()?), }; Ok(result) } @@ -377,6 +382,7 @@ impl Expression { match self { Const(_) => 1, Dice { .. } => 2, + Pow(_, _) => 4, Neg(_) => 5, Mul(_, _) | Div(_, _) | IntDiv(_, _) => 7, Add(_, _) | Sub(_, _) => 8, @@ -480,7 +486,7 @@ impl<'a> WitnessSet for DiscordMdWitnessSet<'a> { type Error = DiscordMdWitnessError; fn witness_roll(&mut self, dice: DiceRoll) -> Result { - if self.parent.dice_written >= 100 { + if self.parent.dice_written >= 50 { if !self.dice_elided { write!(self.parent.buffer, "…")?; self.dice_elided = true; diff --git a/src/parsing.rs b/src/parsing.rs index 1a74101..81dc420 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -1,5 +1,10 @@ -use crate::dice::{CompareFragment, DiceFormula, Expression::{self, Const}}; +use crate::dice::{ + CompareFragment, DiceFormula, + Expression::{self, Const}, +}; use Assoc::Left; +use nom::character::complete::digit0; +use nom::combinator::recognize; use nom::{ Parser, branch::alt, @@ -89,6 +94,7 @@ fn expr<'c, 'i>( complete(unary_op(5, tag("-"))), fail(), complete(alt(( + binary_op(4, Left, spaced_op("^")), binary_op(7, Left, spaced_op("*")), binary_op(7, Left, spaced_op("//")), binary_op(7, Left, spaced_op("/")), @@ -101,6 +107,7 @@ fn expr<'c, 'i>( Ok(match op { Prefix("-", x) => Neg(Box::new(x)), + Binary(lhs, "^", rhs) => Pow(Box::new(lhs), Box::new(rhs)), Binary(lhs, "*", rhs) => Mul(Box::new(lhs), Box::new(rhs)), Binary(lhs, "//", rhs) => IntDiv(Box::new(lhs), Box::new(rhs)), Binary(lhs, "/", rhs) => Div(Box::new(lhs), Box::new(rhs)), @@ -115,9 +122,12 @@ fn expr<'c, 'i>( } fn number(i: &str) -> IResult<&str, Expression> { - map_res(digit1(), |s: &str| s.parse::()) - .map(Const) - .parse_complete(i) + map_res( + alt((recognize((digit0, tag("."), digit1())), digit1())), + |s: &str| s.parse::(), + ) + .map(Const) + .parse_complete(i) } fn basic_operand<'c, 'i>( @@ -128,7 +138,7 @@ fn basic_operand<'c, 'i>( fn compare_fragment<'c, 'i>( ctx: &'c Context, -) -> impl Parser<&'i str, Output = CompareFragment, Error = InternalError<&'i str>> + use<'c, 'i> { +) -> impl Parser<&'i str, Output = CompareFragment, Error = InternalError<&'i str>> + use<'c, 'i> { use CompareFragment::*; alt(( preceded(tag("="), basic_operand(ctx)).map(|x| Eq(Box::new(x))), @@ -154,6 +164,18 @@ mod test { assert_matches!(expression, Expression::Dice { .. }); } + #[test] + pub fn parse_decimal() { + let expression = parse("2.5+2.5").unwrap(); + assert_matches!(expression, Expression::Add { .. }); + } + + #[test] + pub fn parse_decimal2() { + let expression = parse(".4*.8").unwrap(); + assert_matches!(expression, Expression::Mul { .. }); + } + #[test] pub fn parse_bare_dice() { let expression = parse("d8").unwrap(); @@ -162,7 +184,7 @@ mod test { #[test] pub fn parse_math() { - let expression = parse("8 * 2 + 2 * 5").unwrap(); + let expression = parse("8 * 2 + 2 * 5 ^ 4").unwrap(); assert_matches!(expression, Expression::Add { .. }); }