deleted arena allocator for the tree
This commit is contained in:
parent
b437cefe5c
commit
25d72e9633
5 changed files with 115 additions and 158 deletions
23
Cargo.lock
generated
23
Cargo.lock
generated
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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"
|
|
||||||
|
|
|
||||||
160
src/dice.rs
160
src/dice.rs
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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 { .. });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue