Compare commits

...

3 commits

Author SHA1 Message Date
9dd6bee0e3 fixed exploding dice bug 2026-08-20 01:30:31 +02:00
9b78cf2786 added exponentials 2026-08-18 20:36:18 +02:00
074c7a10e4 added decimals 2026-08-18 20:28:25 +02:00
2 changed files with 39 additions and 11 deletions

View file

@ -17,6 +17,7 @@ pub enum Expression {
Mul(ExpBox, ExpBox), Mul(ExpBox, ExpBox),
Div(ExpBox, ExpBox), Div(ExpBox, ExpBox),
IntDiv(ExpBox, ExpBox), IntDiv(ExpBox, ExpBox),
Pow(ExpBox, ExpBox),
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
@ -121,9 +122,6 @@ impl Expression {
// Exploding dice are early, they add rolls // Exploding dice are early, they add rolls
if let Some(fragments) = x { if let Some(fragments) = x {
write!(w, "x")?; write!(w, "x")?;
if size < 2 {
bail!("Infinite explosion.")
}
let mut comparers = Vec::with_capacity(fragments.len()); let mut comparers = Vec::with_capacity(fragments.len());
for fragment in fragments { for fragment in fragments {
comparers.push(fragment.comparer(rng, w)?); comparers.push(fragment.comparer(rng, w)?);
@ -265,6 +263,12 @@ impl Expression {
let rhs = rhs_node.evaluate(rng, w, precedence)?; let rhs = rhs_node.evaluate(rng, w, precedence)?;
lhs.div_euclid(rhs) 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 { if needs_parens {
write!(w, ")")?; write!(w, ")")?;
@ -294,7 +298,7 @@ impl Expression {
} else { } else {
1 1
}; };
if count > 1_000_000 { if count > DICE_POOL_LIMIT {
bail!("Too many dice.") bail!("Too many dice.")
}; };
let size = size.avg()? as i64; let size = size.avg()? as i64;
@ -326,6 +330,7 @@ impl Expression {
Expression::Mul(lhs, rhs) => lhs.avg()? * rhs.avg()?, Expression::Mul(lhs, rhs) => lhs.avg()? * rhs.avg()?,
Expression::Div(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::IntDiv(lhs, rhs) => lhs.avg()?.div_euclid(rhs.avg()?),
Expression::Pow(lhs, rhs) => lhs.avg()?.powf(rhs.avg()?),
}; };
Ok(result) Ok(result)
} }
@ -377,6 +382,7 @@ impl Expression {
match self { match self {
Const(_) => 1, Const(_) => 1,
Dice { .. } => 2, Dice { .. } => 2,
Pow(_, _) => 4,
Neg(_) => 5, Neg(_) => 5,
Mul(_, _) | Div(_, _) | IntDiv(_, _) => 7, Mul(_, _) | Div(_, _) | IntDiv(_, _) => 7,
Add(_, _) | Sub(_, _) => 8, Add(_, _) | Sub(_, _) => 8,
@ -480,7 +486,7 @@ impl<'a> WitnessSet for DiscordMdWitnessSet<'a> {
type Error = DiscordMdWitnessError; type Error = DiscordMdWitnessError;
fn witness_roll(&mut self, dice: DiceRoll) -> Result<Self::Ok, Self::Error> { fn witness_roll(&mut self, dice: DiceRoll) -> Result<Self::Ok, Self::Error> {
if self.parent.dice_written >= 100 { if self.parent.dice_written >= 50 {
if !self.dice_elided { if !self.dice_elided {
write!(self.parent.buffer, "…")?; write!(self.parent.buffer, "…")?;
self.dice_elided = true; self.dice_elided = true;

View file

@ -1,5 +1,10 @@
use crate::dice::{CompareFragment, DiceFormula, Expression::{self, Const}}; use crate::dice::{
CompareFragment, DiceFormula,
Expression::{self, Const},
};
use Assoc::Left; use Assoc::Left;
use nom::character::complete::digit0;
use nom::combinator::recognize;
use nom::{ use nom::{
Parser, Parser,
branch::alt, branch::alt,
@ -89,6 +94,7 @@ fn expr<'c, 'i>(
complete(unary_op(5, tag("-"))), complete(unary_op(5, tag("-"))),
fail(), fail(),
complete(alt(( 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("//")), 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 { Ok(match op {
Prefix("-", x) => Neg(Box::new(x)), 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) => Mul(Box::new(lhs), Box::new(rhs)),
Binary(lhs, "//", rhs) => IntDiv(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)), Binary(lhs, "/", rhs) => Div(Box::new(lhs), Box::new(rhs)),
@ -115,7 +122,10 @@ fn expr<'c, 'i>(
} }
fn number(i: &str) -> IResult<&str, Expression> { fn number(i: &str) -> IResult<&str, Expression> {
map_res(digit1(), |s: &str| s.parse::<f64>()) map_res(
alt((recognize((digit0, tag("."), digit1())), digit1())),
|s: &str| s.parse::<f64>(),
)
.map(Const) .map(Const)
.parse_complete(i) .parse_complete(i)
} }
@ -154,6 +164,18 @@ mod test {
assert_matches!(expression, Expression::Dice { .. }); 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] #[test]
pub fn parse_bare_dice() { pub fn parse_bare_dice() {
let expression = parse("d8").unwrap(); let expression = parse("d8").unwrap();
@ -162,7 +184,7 @@ mod test {
#[test] #[test]
pub fn parse_math() { 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 { .. }); assert_matches!(expression, Expression::Add { .. });
} }