diff --git a/Cargo.lock b/Cargo.lock index 7a7e1eb..5407df7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -380,12 +380,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" -[[package]] -name = "either" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" - [[package]] name = "extract_map" version = "0.3.1" @@ -786,12 +780,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -825,15 +813,6 @@ version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" -[[package]] -name = "itertools" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.18" @@ -916,8 +895,6 @@ version = "0.1.0" dependencies = [ "anyhow", "dotenv", - "id-arena", - "itertools", "nom", "nom-language", "rand 0.10.2", diff --git a/Cargo.toml b/Cargo.toml index f2ee7b2..e9dc6fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,5 @@ anyhow = "1.0.104" nom = "8.0.0" thiserror = "2.0.20" nom-language = "0.1.0" -id-arena = "2.3.0" rand_xoshiro = "0.8.1" rand = "0.10.2" -itertools = "0.15.0" diff --git a/src/dice.rs b/src/dice.rs index c034882..937eb28 100644 --- a/src/dice.rs +++ b/src/dice.rs @@ -1,68 +1,68 @@ use anyhow::bail; -use id_arena::Arena; use rand::{Rng, RngExt}; use std::fmt::Write; -#[derive(Debug)] -pub struct Expression { - pub(crate) arena: Arena, - pub(crate) root: AstNodeId, -} - -pub type AstNodeId = id_arena::Id; +pub type ExpBox = Box; #[derive(Debug, PartialEq, Clone)] -pub enum AstNode { +pub enum Expression { Const(f64), Dice(DiceFormula), - Neg(AstNodeId), - Add(AstNodeId, AstNodeId), - Sub(AstNodeId, AstNodeId), - Mul(AstNodeId, AstNodeId), - Div(AstNodeId, AstNodeId), + Neg(ExpBox), + Add(ExpBox, ExpBox), + Sub(ExpBox, ExpBox), + Mul(ExpBox, ExpBox), + Div(ExpBox, ExpBox), } #[derive(Debug, PartialEq, Clone)] pub struct DiceFormula { - pub(crate) count: Option, - pub(crate) size: AstNodeId, - pub(crate) kh: Option, - pub(crate) kl: Option, - pub(crate) dh: Option, - pub(crate) dl: Option, + pub(crate) count: Option, + pub(crate) size: ExpBox, + pub(crate) kh: Option, + pub(crate) kl: Option, + pub(crate) dh: Option, + pub(crate) dl: Option, pub(crate) x: bool, } +pub enum CompareFragment { + Eq(ExpBox), + Gt(ExpBox), + Gte(ExpBox), + Lt(ExpBox), + Lte(ExpBox), +} + const DICE_POOL_LIMIT: usize = 100_000; impl Expression { - pub fn evaluate(&self, mut rng: impl Rng) -> anyhow::Result { + pub fn collect_evaluation(&self, mut rng: impl Rng) -> anyhow::Result { let mut result = String::new(); - let total = self.node_evaluate(&mut rng, self.root, &mut result, 100)?; + let total = self.evaluate(&mut rng, &mut result, 100)?; Ok(format!("**{total}** = {result}")) } pub fn average(&self, rng: &mut impl Rng) -> anyhow::Result { - if let Ok(avg) = self.node_average(self.root) { + if let Ok(avg) = self.avg() { return Ok(avg); } - let mut average = self.node_sample(rng, self.root)?; + let mut average = self.sample(rng)?; for idx in 1..3000 { average = - (average * idx as f64 + self.node_sample(rng, self.root)?) / (idx as f64 + 1f64); + (average * idx as f64 + self.sample(rng)?) / (idx as f64 + 1f64); } Ok(average) } - fn node_evaluate( + fn evaluate( &self, rng: &mut impl Rng, - node_id: AstNodeId, output: &mut String, outer_precedence: i64, ) -> anyhow::Result { - use AstNode::*; - let node = &self.arena[node_id]; + use Expression::*; + let node = self; let precedence = node.precedence(); let needs_parens = precedence > outer_precedence; if needs_parens { @@ -83,7 +83,7 @@ impl Expression { x, }) => { let count = if let Some(count_node) = count_node { - self.node_evaluate(rng, *count_node, output, precedence)? as usize + count_node.evaluate(rng, output, precedence)? as usize } else { 1 }; @@ -91,7 +91,7 @@ impl Expression { bail!("Too many dice.") }; output.push_str("d"); - let size = self.node_evaluate(rng, *size_node, output, precedence)? as i64; + let size = size_node.evaluate(rng, output, precedence)? as i64; if size < 1 { bail!("Invalid die size.") } @@ -114,27 +114,27 @@ impl Expression { } } - let kh = if let Some(id) = kh { + let kh = if let Some(child) = kh { output.push_str("kh"); - Some(self.node_evaluate(rng, *id, output, precedence)? as usize) + Some(child.evaluate(rng, output, precedence)? as usize) } else { None }; - let kl = if let Some(id) = kl { + let kl = if let Some(child) = kl { output.push_str("kl"); - Some(self.node_evaluate(rng, *id, output, precedence)? as usize) + Some(child.evaluate(rng, output, precedence)? as usize) } else { None }; - let dh = if let Some(id) = dh { + let dh = if let Some(child) = dh { output.push_str("dh"); - Some(self.node_evaluate(rng, *id, output, precedence)? as usize) + Some(child.evaluate(rng, output, precedence)? as usize) } else { None }; - let dl = if let Some(id) = dl { + let dl = if let Some(child) = dl { output.push_str("dl"); - Some(self.node_evaluate(rng, *id, output, precedence)? as usize) + Some(child.evaluate(rng, output, precedence)? as usize) } else { None }; @@ -201,31 +201,31 @@ impl Expression { } Neg(inner_node) => { output.push_str("-"); - let inner = self.node_evaluate(rng, *inner_node, output, precedence)?; + let inner = inner_node.evaluate(rng, output, precedence)?; -inner } Add(lhs_node, rhs_node) => { - let lhs = self.node_evaluate(rng, *lhs_node, output, precedence)?; + let lhs = lhs_node.evaluate(rng, output, precedence)?; output.push_str(" + "); - let rhs = self.node_evaluate(rng, *rhs_node, output, precedence)?; + let rhs = rhs_node.evaluate(rng, output, precedence)?; lhs + rhs } Sub(lhs_node, rhs_node) => { - let lhs = self.node_evaluate(rng, *lhs_node, output, precedence)?; + let lhs = lhs_node.evaluate(rng, output, precedence)?; output.push_str(" - "); - let rhs = self.node_evaluate(rng, *rhs_node, output, precedence)?; + let rhs = rhs_node.evaluate(rng, output, precedence)?; lhs - rhs } Mul(lhs_node, rhs_node) => { - let lhs = self.node_evaluate(rng, *lhs_node, output, precedence)?; + let lhs = lhs_node.evaluate(rng, output, precedence)?; output.push_str(" × "); - let rhs = self.node_evaluate(rng, *rhs_node, output, precedence)?; + let rhs = rhs_node.evaluate(rng, output, precedence)?; lhs * rhs } Div(lhs_node, rhs_node) => { - let lhs = self.node_evaluate(rng, *lhs_node, output, precedence)?; + let lhs = lhs_node.evaluate(rng, output, precedence)?; output.push_str(" ÷ "); - let rhs = self.node_evaluate(rng, *rhs_node, output, precedence)?; + let rhs = rhs_node.evaluate(rng, output, precedence)?; lhs / rhs } }; @@ -235,11 +235,11 @@ impl Expression { Ok(result) } - fn node_sample(&self, rng: &mut impl Rng, node_id: AstNodeId) -> anyhow::Result { - let node = &self.arena[node_id]; + fn sample(&self, rng: &mut impl Rng) -> anyhow::Result { + let node = self; let result = match node { - AstNode::Const(x) => *x, - AstNode::Dice(DiceFormula { + Expression::Const(x) => *x, + Expression::Dice(DiceFormula { count, size, kh, @@ -249,14 +249,14 @@ impl Expression { x, }) => { let count = if let Some(count) = count { - self.node_sample(rng, *count)? as usize + count.sample(rng)? as usize } else { 1 }; if count > DICE_POOL_LIMIT { bail!("Too many dice.") }; - let size = self.node_sample(rng, *size)? as i64; + let size = size.sample(rng)? as i64; if size < 1 { bail!("Invalid die size.") } @@ -279,23 +279,23 @@ impl Expression { } rolls.sort(); - let kh = if let Some(id) = kh { - self.node_sample(rng, *id)? as usize + let kh = if let Some(child) = kh { + child.sample(rng)? as usize } else { rolls.len() }; - let kl = if let Some(id) = kl { - self.node_sample(rng, *id)? as usize + let kl = if let Some(child) = kl { + child.sample(rng)? as usize } else { rolls.len() }; - let dh = if let Some(id) = dh { - self.node_sample(rng, *id)? as usize + let dh = if let Some(child) = dh { + child.sample(rng)? as usize } else { 0 }; - let dl = if let Some(id) = dl { - self.node_sample(rng, *id)? as usize + let dl = if let Some(child) = dl { + child.sample(rng)? as usize } else { 0 }; @@ -308,20 +308,20 @@ impl Expression { .map(|x| x as f64) .sum::() } - AstNode::Neg(id) => -self.node_sample(rng, *id)?, - AstNode::Add(lhs, rhs) => self.node_sample(rng, *lhs)? + self.node_sample(rng, *rhs)?, - AstNode::Sub(lhs, rhs) => self.node_sample(rng, *lhs)? - self.node_sample(rng, *rhs)?, - AstNode::Mul(lhs, rhs) => self.node_sample(rng, *lhs)? * self.node_sample(rng, *rhs)?, - AstNode::Div(lhs, rhs) => self.node_sample(rng, *lhs)? / self.node_sample(rng, *rhs)?, + Expression::Neg(id) => -id.sample(rng)?, + Expression::Add(lhs, rhs) => lhs.sample(rng)? + rhs.sample(rng)?, + Expression::Sub(lhs, rhs) => lhs.sample(rng)? - rhs.sample(rng)?, + Expression::Mul(lhs, rhs) => lhs.sample(rng)? * rhs.sample(rng)?, + Expression::Div(lhs, rhs) => lhs.sample(rng)? / rhs.sample(rng)?, }; Ok(result) } - fn node_average(&self, node_id: AstNodeId) -> anyhow::Result { - let node = &self.arena[node_id]; + fn avg(&self) -> anyhow::Result { + let node = self; let result = match node { - AstNode::Const(x) => *x, - AstNode::Dice(DiceFormula { + Expression::Const(x) => *x, + Expression::Dice(DiceFormula { count, size, kh, @@ -331,14 +331,14 @@ impl Expression { x, }) => { let count = if let Some(count) = count { - self.node_average(*count)? as usize + count.avg()? as usize } else { 1 }; if count > 1_000_000 { bail!("Too many dice.") }; - let size = self.node_average(*size)? as i64; + let size = size.avg()? as i64; if size < 1 { bail!("Invalid die size.") } @@ -361,19 +361,19 @@ impl Expression { count as f64 * (size as f64 + 1f64) * 0.5 } - AstNode::Neg(id) => -self.node_average(*id)?, - AstNode::Add(lhs, rhs) => self.node_average(*lhs)? + self.node_average(*rhs)?, - AstNode::Sub(lhs, rhs) => self.node_average(*lhs)? - self.node_average(*rhs)?, - AstNode::Mul(lhs, rhs) => self.node_average(*lhs)? * self.node_average(*rhs)?, - AstNode::Div(lhs, rhs) => self.node_average(*lhs)? / self.node_average(*rhs)?, + Expression::Neg(child) => -child.avg()?, + Expression::Add(lhs, rhs) => lhs.avg()? + rhs.avg()?, + Expression::Sub(lhs, rhs) => lhs.avg()? - rhs.avg()?, + Expression::Mul(lhs, rhs) => lhs.avg()? * rhs.avg()?, + Expression::Div(lhs, rhs) => lhs.avg()? / rhs.avg()?, }; Ok(result) } } -impl AstNode { +impl Expression { fn precedence(&self) -> i64 { - use AstNode::*; + use Expression::*; match self { Const(_) => 1, Dice { .. } => 2, diff --git a/src/main.rs b/src/main.rs index c442fdc..0b5a0f3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -201,7 +201,7 @@ pub async fn roll(ctx: &Context, command: &CommandInteraction) -> anyhow::Result } for _ in 0..repeat { text_components.push(CreateContainerComponent::TextDisplay( - CreateTextDisplay::new(format!("{}\n", expression.evaluate(&mut rng)?)), + CreateTextDisplay::new(format!("{}\n", expression.collect_evaluation(&mut rng)?)), )); } if fixed_seed.is_some() { diff --git a/src/parsing.rs b/src/parsing.rs index 51fb174..d93b987 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -1,7 +1,8 @@ -use crate::dice::AstNode::Const; -use crate::dice::{AstNode, AstNodeId, DiceFormula, Expression}; +use crate::dice::{ + DiceFormula, + Expression::{self, Const}, +}; use Assoc::Left; -use id_arena::Arena; use nom::{ Parser, branch::alt, @@ -18,7 +19,6 @@ use nom_language::{ error::{VerboseError, convert_error}, precedence::{Assoc, Operation, binary_op, precedence, unary_op}, }; -use std::cell::RefCell; use thiserror::Error; use tracing::{error, instrument}; @@ -31,17 +31,10 @@ pub enum ParseError { ParseError(String), } -struct Context { - arena: RefCell>, -} +struct Context; impl Context { fn new() -> Self { - Self { - arena: RefCell::new(Arena::new()), - } - } - fn alloc(&self, node: AstNode) -> AstNodeId { - self.arena.borrow_mut().alloc(node) + Self } } @@ -50,11 +43,7 @@ pub fn parse(formula: &str) -> Result { let context = Context::new(); let result = root(&context).parse_complete(formula); match result { - Ok((_, expr)) => { - let mut arena = context.arena.into_inner(); - let root = arena.alloc(expr); - Ok(Expression { arena, root }) - } + Ok((_, expr)) => Ok(expr), Err(nom::Err::Error(err) | nom::Err::Failure(err)) => { Err(ParseError::ParseError(convert_error(formula, err))) } @@ -67,14 +56,14 @@ pub fn parse(formula: &str) -> Result { fn root<'c, 'i>( ctx: &'c Context, -) -> impl Parser<&'i str, Output = AstNode, Error = InternalError<&'i str>> + use<'c, 'i> { +) -> impl Parser<&'i str, Output = Expression, Error = InternalError<&'i str>> + use<'c, 'i> { cut(all_consuming(complete(expr(ctx)))) } fn expr<'c, 'i>( ctx: &'c Context, -) -> impl Parser<&'i str, Output = AstNode, Error = InternalError<&'i str>> + use<'c, 'i> { - use crate::dice::AstNode::*; +) -> impl Parser<&'i str, Output = Expression, Error = InternalError<&'i str>> + use<'c, 'i> { + use crate::dice::Expression::*; |i| { let dice_formula = ( opt(basic_operand(ctx)), @@ -89,19 +78,16 @@ fn expr<'c, 'i>( ) .map(|(count, size, (kh, kl, dh, dl, x))| { Dice(DiceFormula { - count: count.map(|n| ctx.alloc(n)), - size: ctx.alloc(size), - kh: kh.map(|n| ctx.alloc(n.unwrap_or(Const(1f64)))), - kl: kl.map(|n| ctx.alloc(n.unwrap_or(Const(1f64)))), - dh: dh.map(|n| ctx.alloc(n.unwrap_or(Const(1f64)))), - dl: dl.map(|n| ctx.alloc(n.unwrap_or(Const(1f64)))), + count: count.map(|n| Box::new(n)), + size: Box::new(size), + kh: kh.map(|n| Box::new(n.unwrap_or(Const(1f64)))), + kl: kl.map(|n| Box::new(n.unwrap_or(Const(1f64)))), + dh: dh.map(|n| Box::new(n.unwrap_or(Const(1f64)))), + dl: dl.map(|n| Box::new(n.unwrap_or(Const(1f64)))), x: x.is_some(), }) }); - let operand = alt(( - dice_formula, - basic_operand(ctx), - )); + let operand = alt((dice_formula, basic_operand(ctx))); precedence( complete(unary_op(5, tag("-"))), fail(), @@ -112,15 +98,15 @@ fn expr<'c, 'i>( binary_op(8, Left, spaced_op("-")), ))), complete(operand), - |op: Operation<&str, &str, &str, AstNode>| { + |op: Operation<&str, &str, &str, Expression>| { use nom_language::precedence::Operation::*; Ok(match op { - Prefix("-", x) => Neg(ctx.alloc(x)), - Binary(lhs, "*", rhs) => Mul(ctx.alloc(lhs), ctx.alloc(rhs)), - Binary(lhs, "/", rhs) => Div(ctx.alloc(lhs), ctx.alloc(rhs)), - Binary(lhs, "+", rhs) => Add(ctx.alloc(lhs), ctx.alloc(rhs)), - Binary(lhs, "-", rhs) => Sub(ctx.alloc(lhs), ctx.alloc(rhs)), + Prefix("-", x) => Neg(Box::new(x)), + Binary(lhs, "*", rhs) => Mul(Box::new(lhs), Box::new(rhs)), + Binary(lhs, "/", rhs) => Div(Box::new(lhs), Box::new(rhs)), + Binary(lhs, "+", rhs) => Add(Box::new(lhs), Box::new(rhs)), + Binary(lhs, "-", rhs) => Sub(Box::new(lhs), Box::new(rhs)), _ => return Err("Invalid combination"), }) }, @@ -129,7 +115,7 @@ fn expr<'c, 'i>( } } -fn number(i: &str) -> IResult<&str, AstNode> { +fn number(i: &str) -> IResult<&str, Expression> { map_res(digit1(), |s: &str| s.parse::()) .map(Const) .parse_complete(i) @@ -137,14 +123,10 @@ fn number(i: &str) -> IResult<&str, AstNode> { fn basic_operand<'c, 'i>( ctx: &'c Context, -) -> impl Parser<&'i str, Output = AstNode, Error = InternalError<&'i str>> + use<'c, 'i> { - alt(( - number, - delimited(tag("("), expr(ctx), tag(")")), - )) +) -> impl Parser<&'i str, Output = Expression, Error = InternalError<&'i str>> + use<'c, 'i> { + alt((number, delimited(tag("("), expr(ctx), tag(")")))) } - fn spaced_op(t: &str) -> impl Parser<&str, Output = &str, Error = VerboseError<&str>> { delimited(many0(tag(" ")), tag(t), many0(tag(" "))) } @@ -157,48 +139,48 @@ mod test { #[test] pub fn parse_basic() { let expression = parse("2d6").unwrap(); - assert_matches!(expression.arena[expression.root], AstNode::Dice { .. }); + assert_matches!(expression, Expression::Dice { .. }); } #[test] pub fn parse_bare_dice() { let expression = parse("d8").unwrap(); - assert_matches!(expression.arena[expression.root], AstNode::Dice { .. }); + assert_matches!(expression, Expression::Dice { .. }); } #[test] pub fn parse_math() { let expression = parse("8 * 2 + 2 * 5").unwrap(); - assert_matches!(expression.arena[expression.root], AstNode::Add { .. }); + assert_matches!(expression, Expression::Add { .. }); } #[test] pub fn parse_kh() { let expression = parse("2d20kh").unwrap(); - assert_matches!(expression.arena[expression.root], AstNode::Dice { .. }); + assert_matches!(expression, Expression::Dice { .. }); } #[test] pub fn parse_kh1() { let expression = parse("2d20kh1").unwrap(); - assert_matches!(expression.arena[expression.root], AstNode::Dice { .. }); + assert_matches!(expression, Expression::Dice { .. }); } #[test] pub fn parse_kh_nested() { let expression = parse("2d(10*2)kh(2-3)").unwrap(); - assert_matches!(expression.arena[expression.root], AstNode::Dice { .. }); + assert_matches!(expression, Expression::Dice { .. }); } #[test] pub fn parse_dl() { let expression = parse("2d20dl").unwrap(); - assert_matches!(expression.arena[expression.root], AstNode::Dice { .. }); + assert_matches!(expression, Expression::Dice { .. }); } #[test] pub fn parse_dl1() { let expression = parse("2d20dl1").unwrap(); - assert_matches!(expression.arena[expression.root], AstNode::Dice { .. }); + assert_matches!(expression, Expression::Dice { .. }); } }