use std::{fmt, str};
use chumsky::prelude::*;
use crate::{
dice::{
modifier::{Condition, Modifier},
Dice,
},
expr::Expr,
};
#[must_use]
pub fn dice_part<'src>() -> impl Parser<'src, &'src str, Dice, extra::Err<Rich<'src, char>>> + Copy {
text::int(10)
.labelled("dice count")
.or_not()
.then_ignore(just('d'))
.then(text::int(10).labelled("dice sides"))
.then(modifier_list_part())
.try_map(|((count, sides), modifiers), span| {
let count = count
.unwrap_or("1")
.parse()
.map_err(|err| Rich::custom(span, format!("Dice count: {err}")))?;
let sides = sides
.parse()
.map_err(|err| Rich::custom(span, format!("Dice sides: {err}")))?;
Ok(Dice {
count,
sides,
modifiers,
})
})
.labelled("dice set")
}
#[must_use]
pub fn dice<'src>() -> impl Parser<'src, &'src str, Dice, extra::Err<Rich<'src, char>>> + Copy {
dice_part().then_ignore(end())
}
#[must_use]
pub fn modifier_part<'src>() -> impl Parser<'src, &'src str, Modifier, extra::Err<Rich<'src, char>>> + Copy {
let condition = condition_part();
choice((
just('r')
.ignored()
.then(just('r').ignored().or_not().map(|r| r.is_some()))
.then(condition)
.map(|(((), recurse), cond)| Modifier::Reroll { cond, recurse }),
just('x')
.ignored()
.then(just('o').ignored().or_not().map(|o| o.is_none()))
.then(condition.or_not())
.map(|(((), recurse), cond)| Modifier::Explode { cond, recurse }),
just("kl")
.ignored()
.then(text::int(10).labelled("keep lowest count").or_not())
.try_map(|((), count), span| {
let count = count
.unwrap_or("1")
.parse()
.map_err(|err| Rich::custom(span, format!("Keep lowest count: {err}")))?;
Ok(Modifier::KeepLow(count))
}),
just('k')
.ignored()
.then_ignore(just('h').or_not())
.then(text::int(10).labelled("keep highest count").or_not())
.try_map(|((), count), span| {
let count = count
.unwrap_or("1")
.parse()
.map_err(|err| Rich::custom(span, format!("Keep highest count: {err}")))?;
Ok(Modifier::KeepHigh(count))
}),
just("min")
.ignored()
.then(text::int(10).labelled("min roll value"))
.try_map(|((), min): ((), &str), span| {
let min = min
.parse()
.map_err(|err| Rich::custom(span, format!("Minimum: {err}")))?;
Ok(Modifier::Min(min))
}),
just("max")
.ignored()
.then(text::int(10).labelled("max roll value"))
.try_map(|((), max): ((), &str), span| {
let max = max
.parse()
.map_err(|err| Rich::custom(span, format!("Maximum: {err}")))?;
Ok(Modifier::Max(max))
}),
))
.labelled("dice modifier")
}
#[must_use]
pub fn modifier<'src>() -> impl Parser<'src, &'src str, Modifier, extra::Err<Rich<'src, char>>> + Copy {
modifier_part().then_ignore(end())
}
#[must_use]
pub fn modifier_list_part<'src>() -> impl Parser<'src, &'src str, Vec<Modifier>, extra::Err<Rich<'src, char>>> + Copy {
modifier_part().repeated().collect()
}
#[must_use]
pub fn modifier_list<'src>() -> impl Parser<'src, &'src str, Vec<Modifier>, extra::Err<Rich<'src, char>>> + Copy {
modifier_list_part().then_ignore(end())
}
#[must_use]
pub fn condition_part<'src>() -> impl Parser<'src, &'src str, Condition, extra::Err<Rich<'src, char>>> + Copy {
choice((
just(">=").to(Condition::Gte as fn(u8) -> _),
just("<=").to(Condition::Lte as fn(u8) -> _),
just('>').to(Condition::Gt as fn(u8) -> _),
just('<').to(Condition::Lt as fn(u8) -> _),
just('=').to(Condition::Eq as fn(u8) -> _),
))
.labelled("condition symbol")
.or_not()
.then(text::int::<&'src str, _, _>(10).labelled("condition number"))
.try_map(|(condfn, val), span| {
let val = val
.parse()
.map_err(|err| Rich::custom(span, format!("Modifier condition: {err}")))?;
Ok(condfn.map_or_else(|| Condition::Eq(val), |condfn| condfn(val)))
})
.labelled("modifier condition")
}
#[must_use]
pub fn condition<'src>() -> impl Parser<'src, &'src str, Condition, extra::Err<Rich<'src, char>>> + Copy {
condition_part().then_ignore(end())
}
#[must_use]
pub fn expr_part<'src>() -> impl Parser<'src, &'src str, Expr, extra::Err<Rich<'src, char>>> + Clone {
let op = |c| just(c).padded();
recursive(|expr| {
let int = text::int(10)
.try_map(|s: &str, span| {
s.parse()
.map(Expr::Num)
.map_err(|err| Rich::custom(span, format!("{err}")))
})
.labelled("number");
let dice = dice_part().map(Expr::Dice);
let atom = dice
.or(int)
.or(expr.delimited_by(just('('), just(')')).labelled("group"))
.padded();
let unary = op('-').repeated().foldr(atom, |_op, rhs| Expr::Neg(Box::new(rhs)));
let product = unary.clone().foldl(
choice((
op('*').to(Expr::Mul as fn(_, _) -> _),
op('/').to(Expr::DivDown as fn(_, _) -> _),
op('\\').to(Expr::DivUp as fn(_, _) -> _),
))
.then(unary)
.repeated(),
|lhs, (op, rhs)| op(Box::new(lhs), Box::new(rhs)),
);
product.clone().foldl(
choice((
op('+').to(Expr::Add as fn(_, _) -> _),
op('-').to(Expr::Sub as fn(_, _) -> _),
))
.then(product)
.repeated(),
|lhs, (op, rhs)| op(Box::new(lhs), Box::new(rhs)),
)
})
}
#[must_use]
pub fn expr<'src>() -> impl Parser<'src, &'src str, Expr, extra::Err<Rich<'src, char>>> + Clone {
expr_part().then_ignore(end())
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Error {
pub details: String,
}
#[allow(clippy::absolute_paths)]
impl std::error::Error for Error {
fn description(&self) -> &str {
&self.details
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.details)
}
}
impl str::FromStr for Dice {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let lc = s.to_lowercase();
let result = dice().parse(&lc).into_result().map_err(|errs| Error {
details: errs.iter().map(ToString::to_string).collect::<Vec<_>>().join("; "),
});
result
}
}
impl str::FromStr for Modifier {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let lc = s.to_lowercase();
let result = modifier().parse(&lc).into_result().map_err(|errs| Error {
details: errs.iter().map(ToString::to_string).collect::<Vec<_>>().join("; "),
});
result
}
}
impl str::FromStr for Condition {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
condition().parse(s).into_result().map_err(|errs| Error {
details: errs.iter().map(ToString::to_string).collect::<Vec<_>>().join("; "),
})
}
}
impl str::FromStr for Expr {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let lc = s.to_lowercase();
let result = expr().parse(&lc).into_result().map_err(|errs| Error {
details: errs.iter().map(ToString::to_string).collect::<Vec<_>>().join("; "),
});
result
}
}
pub trait GenParser<T> {
#[must_use]
fn parser<'src>() -> impl Parser<'src, &'src str, T, extra::Err<Rich<'src, char>>> + Clone;
#[must_use]
fn part_parser<'src>() -> impl Parser<'src, &'src str, T, extra::Err<Rich<'src, char>>> + Clone;
}
impl GenParser<Dice> for Dice {
#[inline]
#[allow(refining_impl_trait)]
fn parser<'src>() -> impl Parser<'src, &'src str, Self, extra::Err<Rich<'src, char>>> + Copy {
dice()
}
#[inline]
#[allow(refining_impl_trait)]
fn part_parser<'src>() -> impl Parser<'src, &'src str, Self, extra::Err<Rich<'src, char>>> + Copy {
dice_part()
}
}
impl GenParser<Modifier> for Modifier {
#[inline]
#[allow(refining_impl_trait)]
fn parser<'src>() -> impl Parser<'src, &'src str, Self, extra::Err<Rich<'src, char>>> + Copy {
modifier()
}
#[inline]
#[allow(refining_impl_trait)]
fn part_parser<'src>() -> impl Parser<'src, &'src str, Self, extra::Err<Rich<'src, char>>> + Copy {
modifier_part()
}
}
impl GenParser<Condition> for Condition {
#[inline]
#[allow(refining_impl_trait)]
fn parser<'src>() -> impl Parser<'src, &'src str, Self, extra::Err<Rich<'src, char>>> + Copy {
condition()
}
#[inline]
#[allow(refining_impl_trait)]
fn part_parser<'src>() -> impl Parser<'src, &'src str, Self, extra::Err<Rich<'src, char>>> + Copy {
condition_part()
}
}
impl GenParser<Expr> for Expr {
#[inline]
fn parser<'src>() -> impl Parser<'src, &'src str, Self, extra::Err<Rich<'src, char>>> + Clone {
expr()
}
#[inline]
fn part_parser<'src>() -> impl Parser<'src, &'src str, Self, extra::Err<Rich<'src, char>>> + Clone {
expr_part()
}
}