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

23
Cargo.lock generated
View file

@ -380,12 +380,6 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
name = "either"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
[[package]] [[package]]
name = "extract_map" name = "extract_map"
version = "0.3.1" version = "0.3.1"
@ -786,12 +780,6 @@ dependencies = [
"zerovec", "zerovec",
] ]
[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]] [[package]]
name = "ident_case" name = "ident_case"
version = "1.0.1" version = "1.0.1"
@ -825,15 +813,6 @@ version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78"
[[package]]
name = "itertools"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.18" version = "1.0.18"
@ -916,8 +895,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"dotenv", "dotenv",
"id-arena",
"itertools",
"nom", "nom",
"nom-language", "nom-language",
"rand 0.10.2", "rand 0.10.2",

View file

@ -13,7 +13,5 @@ anyhow = "1.0.104"
nom = "8.0.0" nom = "8.0.0"
thiserror = "2.0.20" thiserror = "2.0.20"
nom-language = "0.1.0" nom-language = "0.1.0"
id-arena = "2.3.0"
rand_xoshiro = "0.8.1" rand_xoshiro = "0.8.1"
rand = "0.10.2" rand = "0.10.2"
itertools = "0.15.0"

View file

@ -1,68 +1,68 @@
use anyhow::bail; use anyhow::bail;
use id_arena::Arena;
use rand::{Rng, RngExt}; use rand::{Rng, RngExt};
use std::fmt::Write; use std::fmt::Write;
#[derive(Debug)] pub type ExpBox = Box<Expression>;
pub struct Expression {
pub(crate) arena: Arena<AstNode>,
pub(crate) root: AstNodeId,
}
pub type AstNodeId = id_arena::Id<AstNode>;
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum AstNode { pub enum Expression {
Const(f64), Const(f64),
Dice(DiceFormula), Dice(DiceFormula),
Neg(AstNodeId), Neg(ExpBox),
Add(AstNodeId, AstNodeId), Add(ExpBox, ExpBox),
Sub(AstNodeId, AstNodeId), Sub(ExpBox, ExpBox),
Mul(AstNodeId, AstNodeId), Mul(ExpBox, ExpBox),
Div(AstNodeId, AstNodeId), Div(ExpBox, ExpBox),
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct DiceFormula { pub struct DiceFormula {
pub(crate) count: Option<AstNodeId>, pub(crate) count: Option<ExpBox>,
pub(crate) size: AstNodeId, pub(crate) size: ExpBox,
pub(crate) kh: Option<AstNodeId>, pub(crate) kh: Option<ExpBox>,
pub(crate) kl: Option<AstNodeId>, pub(crate) kl: Option<ExpBox>,
pub(crate) dh: Option<AstNodeId>, pub(crate) dh: Option<ExpBox>,
pub(crate) dl: Option<AstNodeId>, pub(crate) dl: Option<ExpBox>,
pub(crate) x: bool, pub(crate) x: bool,
} }
pub enum CompareFragment {
Eq(ExpBox),
Gt(ExpBox),
Gte(ExpBox),
Lt(ExpBox),
Lte(ExpBox),
}
const DICE_POOL_LIMIT: usize = 100_000; const DICE_POOL_LIMIT: usize = 100_000;
impl Expression { 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 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}")) Ok(format!("**{total}** = {result}"))
} }
pub fn average(&self, rng: &mut impl Rng) -> anyhow::Result<f64> { 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); return Ok(avg);
} }
let mut average = self.node_sample(rng, self.root)?; let mut average = self.sample(rng)?;
for idx in 1..3000 { for idx in 1..3000 {
average = 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) Ok(average)
} }
fn node_evaluate( fn evaluate(
&self, &self,
rng: &mut impl Rng, rng: &mut impl Rng,
node_id: AstNodeId,
output: &mut String, output: &mut String,
outer_precedence: i64, outer_precedence: i64,
) -> anyhow::Result<f64> { ) -> anyhow::Result<f64> {
use AstNode::*; use Expression::*;
let node = &self.arena[node_id]; let node = self;
let precedence = node.precedence(); let precedence = node.precedence();
let needs_parens = precedence > outer_precedence; let needs_parens = precedence > outer_precedence;
if needs_parens { if needs_parens {
@ -83,7 +83,7 @@ impl Expression {
x, x,
}) => { }) => {
let count = if let Some(count_node) = count_node { 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 { } else {
1 1
}; };
@ -91,7 +91,7 @@ impl Expression {
bail!("Too many dice.") bail!("Too many dice.")
}; };
output.push_str("d"); 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 { if size < 1 {
bail!("Invalid die size.") 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"); output.push_str("kh");
Some(self.node_evaluate(rng, *id, output, precedence)? as usize) Some(child.evaluate(rng, output, precedence)? as usize)
} else { } else {
None None
}; };
let kl = if let Some(id) = kl { let kl = if let Some(child) = kl {
output.push_str("kl"); output.push_str("kl");
Some(self.node_evaluate(rng, *id, output, precedence)? as usize) Some(child.evaluate(rng, output, precedence)? as usize)
} else { } else {
None None
}; };
let dh = if let Some(id) = dh { let dh = if let Some(child) = dh {
output.push_str("dh"); output.push_str("dh");
Some(self.node_evaluate(rng, *id, output, precedence)? as usize) Some(child.evaluate(rng, output, precedence)? as usize)
} else { } else {
None None
}; };
let dl = if let Some(id) = dl { let dl = if let Some(child) = dl {
output.push_str("dl"); output.push_str("dl");
Some(self.node_evaluate(rng, *id, output, precedence)? as usize) Some(child.evaluate(rng, output, precedence)? as usize)
} else { } else {
None None
}; };
@ -201,31 +201,31 @@ impl Expression {
} }
Neg(inner_node) => { Neg(inner_node) => {
output.push_str("-"); output.push_str("-");
let inner = self.node_evaluate(rng, *inner_node, output, precedence)?; let inner = inner_node.evaluate(rng, output, precedence)?;
-inner -inner
} }
Add(lhs_node, rhs_node) => { 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(" + "); output.push_str(" + ");
let rhs = self.node_evaluate(rng, *rhs_node, output, precedence)?; let rhs = rhs_node.evaluate(rng, output, precedence)?;
lhs + rhs lhs + rhs
} }
Sub(lhs_node, rhs_node) => { 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(" - "); output.push_str(" - ");
let rhs = self.node_evaluate(rng, *rhs_node, output, precedence)?; let rhs = rhs_node.evaluate(rng, output, precedence)?;
lhs - rhs lhs - rhs
} }
Mul(lhs_node, rhs_node) => { 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(" × "); output.push_str(" × ");
let rhs = self.node_evaluate(rng, *rhs_node, output, precedence)?; let rhs = rhs_node.evaluate(rng, output, precedence)?;
lhs * rhs lhs * rhs
} }
Div(lhs_node, rhs_node) => { 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(" ÷ "); output.push_str(" ÷ ");
let rhs = self.node_evaluate(rng, *rhs_node, output, precedence)?; let rhs = rhs_node.evaluate(rng, output, precedence)?;
lhs / rhs lhs / rhs
} }
}; };
@ -235,11 +235,11 @@ impl Expression {
Ok(result) Ok(result)
} }
fn node_sample(&self, rng: &mut impl Rng, node_id: AstNodeId) -> anyhow::Result<f64> { fn sample(&self, rng: &mut impl Rng) -> anyhow::Result<f64> {
let node = &self.arena[node_id]; let node = self;
let result = match node { let result = match node {
AstNode::Const(x) => *x, Expression::Const(x) => *x,
AstNode::Dice(DiceFormula { Expression::Dice(DiceFormula {
count, count,
size, size,
kh, kh,
@ -249,14 +249,14 @@ impl Expression {
x, x,
}) => { }) => {
let count = if let Some(count) = count { let count = if let Some(count) = count {
self.node_sample(rng, *count)? as usize count.sample(rng)? as usize
} else { } else {
1 1
}; };
if count > DICE_POOL_LIMIT { if count > DICE_POOL_LIMIT {
bail!("Too many dice.") bail!("Too many dice.")
}; };
let size = self.node_sample(rng, *size)? as i64; let size = size.sample(rng)? as i64;
if size < 1 { if size < 1 {
bail!("Invalid die size.") bail!("Invalid die size.")
} }
@ -279,23 +279,23 @@ impl Expression {
} }
rolls.sort(); rolls.sort();
let kh = if let Some(id) = kh { let kh = if let Some(child) = kh {
self.node_sample(rng, *id)? as usize child.sample(rng)? as usize
} else { } else {
rolls.len() rolls.len()
}; };
let kl = if let Some(id) = kl { let kl = if let Some(child) = kl {
self.node_sample(rng, *id)? as usize child.sample(rng)? as usize
} else { } else {
rolls.len() rolls.len()
}; };
let dh = if let Some(id) = dh { let dh = if let Some(child) = dh {
self.node_sample(rng, *id)? as usize child.sample(rng)? as usize
} else { } else {
0 0
}; };
let dl = if let Some(id) = dl { let dl = if let Some(child) = dl {
self.node_sample(rng, *id)? as usize child.sample(rng)? as usize
} else { } else {
0 0
}; };
@ -308,20 +308,20 @@ impl Expression {
.map(|x| x as f64) .map(|x| x as f64)
.sum::<f64>() .sum::<f64>()
} }
AstNode::Neg(id) => -self.node_sample(rng, *id)?, Expression::Neg(id) => -id.sample(rng)?,
AstNode::Add(lhs, rhs) => self.node_sample(rng, *lhs)? + self.node_sample(rng, *rhs)?, Expression::Add(lhs, rhs) => lhs.sample(rng)? + rhs.sample(rng)?,
AstNode::Sub(lhs, rhs) => self.node_sample(rng, *lhs)? - self.node_sample(rng, *rhs)?, Expression::Sub(lhs, rhs) => lhs.sample(rng)? - rhs.sample(rng)?,
AstNode::Mul(lhs, rhs) => self.node_sample(rng, *lhs)? * self.node_sample(rng, *rhs)?, Expression::Mul(lhs, rhs) => lhs.sample(rng)? * rhs.sample(rng)?,
AstNode::Div(lhs, rhs) => self.node_sample(rng, *lhs)? / self.node_sample(rng, *rhs)?, Expression::Div(lhs, rhs) => lhs.sample(rng)? / rhs.sample(rng)?,
}; };
Ok(result) Ok(result)
} }
fn node_average(&self, node_id: AstNodeId) -> anyhow::Result<f64> { fn avg(&self) -> anyhow::Result<f64> {
let node = &self.arena[node_id]; let node = self;
let result = match node { let result = match node {
AstNode::Const(x) => *x, Expression::Const(x) => *x,
AstNode::Dice(DiceFormula { Expression::Dice(DiceFormula {
count, count,
size, size,
kh, kh,
@ -331,14 +331,14 @@ impl Expression {
x, x,
}) => { }) => {
let count = if let Some(count) = count { let count = if let Some(count) = count {
self.node_average(*count)? as usize count.avg()? as usize
} else { } else {
1 1
}; };
if count > 1_000_000 { if count > 1_000_000 {
bail!("Too many dice.") bail!("Too many dice.")
}; };
let size = self.node_average(*size)? as i64; let size = size.avg()? as i64;
if size < 1 { if size < 1 {
bail!("Invalid die size.") bail!("Invalid die size.")
} }
@ -361,19 +361,19 @@ impl Expression {
count as f64 * (size as f64 + 1f64) * 0.5 count as f64 * (size as f64 + 1f64) * 0.5
} }
AstNode::Neg(id) => -self.node_average(*id)?, Expression::Neg(child) => -child.avg()?,
AstNode::Add(lhs, rhs) => self.node_average(*lhs)? + self.node_average(*rhs)?, Expression::Add(lhs, rhs) => lhs.avg()? + rhs.avg()?,
AstNode::Sub(lhs, rhs) => self.node_average(*lhs)? - self.node_average(*rhs)?, Expression::Sub(lhs, rhs) => lhs.avg()? - rhs.avg()?,
AstNode::Mul(lhs, rhs) => self.node_average(*lhs)? * self.node_average(*rhs)?, Expression::Mul(lhs, rhs) => lhs.avg()? * rhs.avg()?,
AstNode::Div(lhs, rhs) => self.node_average(*lhs)? / self.node_average(*rhs)?, Expression::Div(lhs, rhs) => lhs.avg()? / rhs.avg()?,
}; };
Ok(result) Ok(result)
} }
} }
impl AstNode { impl Expression {
fn precedence(&self) -> i64 { fn precedence(&self) -> i64 {
use AstNode::*; use Expression::*;
match self { match self {
Const(_) => 1, Const(_) => 1,
Dice { .. } => 2, Dice { .. } => 2,

View file

@ -201,7 +201,7 @@ pub async fn roll(ctx: &Context, command: &CommandInteraction) -> anyhow::Result
} }
for _ in 0..repeat { for _ in 0..repeat {
text_components.push(CreateContainerComponent::TextDisplay( 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() { if fixed_seed.is_some() {

View file

@ -1,7 +1,8 @@
use crate::dice::AstNode::Const; use crate::dice::{
use crate::dice::{AstNode, AstNodeId, DiceFormula, Expression}; DiceFormula,
Expression::{self, Const},
};
use Assoc::Left; use Assoc::Left;
use id_arena::Arena;
use nom::{ use nom::{
Parser, Parser,
branch::alt, branch::alt,
@ -18,7 +19,6 @@ use nom_language::{
error::{VerboseError, convert_error}, error::{VerboseError, convert_error},
precedence::{Assoc, Operation, binary_op, precedence, unary_op}, precedence::{Assoc, Operation, binary_op, precedence, unary_op},
}; };
use std::cell::RefCell;
use thiserror::Error; use thiserror::Error;
use tracing::{error, instrument}; use tracing::{error, instrument};
@ -31,17 +31,10 @@ pub enum ParseError {
ParseError(String), ParseError(String),
} }
struct Context { struct Context;
arena: RefCell<Arena<AstNode>>,
}
impl Context { impl Context {
fn new() -> Self { fn new() -> Self {
Self { Self
arena: RefCell::new(Arena::new()),
}
}
fn alloc(&self, node: AstNode) -> AstNodeId {
self.arena.borrow_mut().alloc(node)
} }
} }
@ -50,11 +43,7 @@ pub fn parse(formula: &str) -> Result<Expression, ParseError> {
let context = Context::new(); let context = Context::new();
let result = root(&context).parse_complete(formula); let result = root(&context).parse_complete(formula);
match result { match result {
Ok((_, expr)) => { Ok((_, expr)) => Ok(expr),
let mut arena = context.arena.into_inner();
let root = arena.alloc(expr);
Ok(Expression { arena, root })
}
Err(nom::Err::Error(err) | nom::Err::Failure(err)) => { Err(nom::Err::Error(err) | nom::Err::Failure(err)) => {
Err(ParseError::ParseError(convert_error(formula, err))) Err(ParseError::ParseError(convert_error(formula, err)))
} }
@ -67,14 +56,14 @@ pub fn parse(formula: &str) -> Result<Expression, ParseError> {
fn root<'c, 'i>( fn root<'c, 'i>(
ctx: &'c Context, 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)))) cut(all_consuming(complete(expr(ctx))))
} }
fn expr<'c, 'i>( fn expr<'c, 'i>(
ctx: &'c Context, 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> {
use crate::dice::AstNode::*; use crate::dice::Expression::*;
|i| { |i| {
let dice_formula = ( let dice_formula = (
opt(basic_operand(ctx)), opt(basic_operand(ctx)),
@ -89,19 +78,16 @@ fn expr<'c, 'i>(
) )
.map(|(count, size, (kh, kl, dh, dl, x))| { .map(|(count, size, (kh, kl, dh, dl, x))| {
Dice(DiceFormula { Dice(DiceFormula {
count: count.map(|n| ctx.alloc(n)), count: count.map(|n| Box::new(n)),
size: ctx.alloc(size), size: Box::new(size),
kh: kh.map(|n| ctx.alloc(n.unwrap_or(Const(1f64)))), kh: kh.map(|n| Box::new(n.unwrap_or(Const(1f64)))),
kl: kl.map(|n| ctx.alloc(n.unwrap_or(Const(1f64)))), kl: kl.map(|n| Box::new(n.unwrap_or(Const(1f64)))),
dh: dh.map(|n| ctx.alloc(n.unwrap_or(Const(1f64)))), dh: dh.map(|n| Box::new(n.unwrap_or(Const(1f64)))),
dl: dl.map(|n| ctx.alloc(n.unwrap_or(Const(1f64)))), dl: dl.map(|n| Box::new(n.unwrap_or(Const(1f64)))),
x: x.is_some(), x: x.is_some(),
}) })
}); });
let operand = alt(( let operand = alt((dice_formula, basic_operand(ctx)));
dice_formula,
basic_operand(ctx),
));
precedence( precedence(
complete(unary_op(5, tag("-"))), complete(unary_op(5, tag("-"))),
fail(), fail(),
@ -112,15 +98,15 @@ fn expr<'c, 'i>(
binary_op(8, Left, spaced_op("-")), binary_op(8, Left, spaced_op("-")),
))), ))),
complete(operand), complete(operand),
|op: Operation<&str, &str, &str, AstNode>| { |op: Operation<&str, &str, &str, Expression>| {
use nom_language::precedence::Operation::*; use nom_language::precedence::Operation::*;
Ok(match op { Ok(match op {
Prefix("-", x) => Neg(ctx.alloc(x)), Prefix("-", x) => Neg(Box::new(x)),
Binary(lhs, "*", rhs) => Mul(ctx.alloc(lhs), ctx.alloc(rhs)), Binary(lhs, "*", rhs) => Mul(Box::new(lhs), Box::new(rhs)),
Binary(lhs, "/", rhs) => Div(ctx.alloc(lhs), ctx.alloc(rhs)), Binary(lhs, "/", rhs) => Div(Box::new(lhs), Box::new(rhs)),
Binary(lhs, "+", rhs) => Add(ctx.alloc(lhs), ctx.alloc(rhs)), Binary(lhs, "+", rhs) => Add(Box::new(lhs), Box::new(rhs)),
Binary(lhs, "-", rhs) => Sub(ctx.alloc(lhs), ctx.alloc(rhs)), Binary(lhs, "-", rhs) => Sub(Box::new(lhs), Box::new(rhs)),
_ => return Err("Invalid combination"), _ => 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_res(digit1(), |s: &str| s.parse::<f64>())
.map(Const) .map(Const)
.parse_complete(i) .parse_complete(i)
@ -137,14 +123,10 @@ fn number(i: &str) -> IResult<&str, AstNode> {
fn basic_operand<'c, 'i>( fn basic_operand<'c, 'i>(
ctx: &'c Context, 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> {
alt(( alt((number, delimited(tag("("), expr(ctx), tag(")"))))
number,
delimited(tag("("), expr(ctx), tag(")")),
))
} }
fn spaced_op(t: &str) -> impl Parser<&str, Output = &str, Error = VerboseError<&str>> { fn spaced_op(t: &str) -> impl Parser<&str, Output = &str, Error = VerboseError<&str>> {
delimited(many0(tag(" ")), tag(t), many0(tag(" "))) delimited(many0(tag(" ")), tag(t), many0(tag(" ")))
} }
@ -157,48 +139,48 @@ mod test {
#[test] #[test]
pub fn parse_basic() { pub fn parse_basic() {
let expression = parse("2d6").unwrap(); let expression = parse("2d6").unwrap();
assert_matches!(expression.arena[expression.root], AstNode::Dice { .. }); assert_matches!(expression, Expression::Dice { .. });
} }
#[test] #[test]
pub fn parse_bare_dice() { pub fn parse_bare_dice() {
let expression = parse("d8").unwrap(); let expression = parse("d8").unwrap();
assert_matches!(expression.arena[expression.root], AstNode::Dice { .. }); assert_matches!(expression, Expression::Dice { .. });
} }
#[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").unwrap();
assert_matches!(expression.arena[expression.root], AstNode::Add { .. }); assert_matches!(expression, Expression::Add { .. });
} }
#[test] #[test]
pub fn parse_kh() { pub fn parse_kh() {
let expression = parse("2d20kh").unwrap(); let expression = parse("2d20kh").unwrap();
assert_matches!(expression.arena[expression.root], AstNode::Dice { .. }); assert_matches!(expression, Expression::Dice { .. });
} }
#[test] #[test]
pub fn parse_kh1() { pub fn parse_kh1() {
let expression = parse("2d20kh1").unwrap(); let expression = parse("2d20kh1").unwrap();
assert_matches!(expression.arena[expression.root], AstNode::Dice { .. }); assert_matches!(expression, Expression::Dice { .. });
} }
#[test] #[test]
pub fn parse_kh_nested() { pub fn parse_kh_nested() {
let expression = parse("2d(10*2)kh(2-3)").unwrap(); let expression = parse("2d(10*2)kh(2-3)").unwrap();
assert_matches!(expression.arena[expression.root], AstNode::Dice { .. }); assert_matches!(expression, Expression::Dice { .. });
} }
#[test] #[test]
pub fn parse_dl() { pub fn parse_dl() {
let expression = parse("2d20dl").unwrap(); let expression = parse("2d20dl").unwrap();
assert_matches!(expression.arena[expression.root], AstNode::Dice { .. }); assert_matches!(expression, Expression::Dice { .. });
} }
#[test] #[test]
pub fn parse_dl1() { pub fn parse_dl1() {
let expression = parse("2d20dl1").unwrap(); let expression = parse("2d20dl1").unwrap();
assert_matches!(expression.arena[expression.root], AstNode::Dice { .. }); assert_matches!(expression, Expression::Dice { .. });
} }
} }