deleted arena allocator for the tree

This commit is contained in:
Lilith Schier 2026-08-11 17:43:53 +02:00
parent b437cefe5c
commit 25d72e9633
5 changed files with 115 additions and 158 deletions

View file

@ -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<AstNode>,
pub(crate) root: AstNodeId,
}
pub type AstNodeId = id_arena::Id<AstNode>;
pub type ExpBox = Box<Expression>;
#[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<AstNodeId>,
pub(crate) size: AstNodeId,
pub(crate) kh: Option<AstNodeId>,
pub(crate) kl: Option<AstNodeId>,
pub(crate) dh: Option<AstNodeId>,
pub(crate) dl: Option<AstNodeId>,
pub(crate) count: Option<ExpBox>,
pub(crate) size: ExpBox,
pub(crate) kh: Option<ExpBox>,
pub(crate) kl: Option<ExpBox>,
pub(crate) dh: Option<ExpBox>,
pub(crate) dl: Option<ExpBox>,
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<String> {
pub fn collect_evaluation(&self, mut rng: impl Rng) -> anyhow::Result<String> {
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<f64> {
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<f64> {
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<f64> {
let node = &self.arena[node_id];
fn sample(&self, rng: &mut impl Rng) -> anyhow::Result<f64> {
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::<f64>()
}
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<f64> {
let node = &self.arena[node_id];
fn avg(&self) -> anyhow::Result<f64> {
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,

View file

@ -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() {

View file

@ -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<Arena<AstNode>>,
}
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<Expression, ParseError> {
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<Expression, ParseError> {
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::<f64>())
.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 { .. });
}
}