diff options
Diffstat (limited to 'src/commands/roll.rs')
| -rw-r--r-- | src/commands/roll.rs | 212 |
1 files changed, 8 insertions, 204 deletions
diff --git a/src/commands/roll.rs b/src/commands/roll.rs index abd7466..fd3102c 100644 --- a/src/commands/roll.rs +++ b/src/commands/roll.rs @@ -1,214 +1,18 @@ -use lazy_static::lazy_static; -use log::{ - debug, - error, -}; -use pest::iterators::Pair; -use rand::prelude::*; -use std::result::Result as StdResult; -use thiserror::Error; - -use crate::{ - util, - PoiseContext, -}; - -#[derive(pest_derive::Parser)] -#[grammar = "commands/calc.pest"] -struct Calc; - -#[derive(Copy, Clone, Error, Debug, PartialEq, Eq, Hash)] -pub(crate) enum CalcError { - #[error("pest was unable to parse the input")] - Pest, - - #[error("invalid number format")] - NumberFormat, - - #[error("bad argument count")] - ArgCount, -} - -impl Calc { - fn eval<S: AsRef<str>>(s: S) -> StdResult<f64, CalcError> { - use pest::{ - iterators::Pairs, - pratt_parser::PrattParser, - Parser, - }; - - use self::Rule::*; - - lazy_static! { - static ref CLIMBER: PrattParser<Rule> = { - use pest::pratt_parser::{ - Assoc::*, - Op, - }; - - PrattParser::new() - .op(Op::infix(add, Left) | Op::infix(sub, Left) | Op::infix(modulo, Left)) - .op(Op::infix(mul, Left) | Op::infix(div, Left)) - .op(Op::infix(dice, Left)) - .op(Op::infix(pow, Right)) - .op(Op::postfix(EOI)) // discarded below - }; - } - - let result = Calc::parse(calc, s.as_ref()).map_err(|_| CalcError::Pest)?; - - fn eval_single_pair(pair: Pair<Rule>) -> StdResult<f64, CalcError> { - let result = match pair.as_rule() { - oct | hex | binary => { - let base = match pair.as_rule() { - hex => 16, - oct => 8, - binary => 2, - _ => unreachable!(), - }; - - u64::from_str_radix(&pair.as_str()[2..], base) - .map_err(|_| CalcError::NumberFormat)? as f64 - }, - float => pair.as_str().parse::<f64>().map_err(|_| CalcError::NumberFormat)?, - expr | num => eval_expr(pair.into_inner())?, - unary_expr => { - let mut p = pair.into_inner(); - - let op = p.next().ok_or(CalcError::ArgCount)?; - let arg = eval_expr(p)?; - - match op.as_rule() { - log => arg.ln(), - sqrt => arg.sqrt(), - sgn => arg.signum(), - - sin => arg.sin(), - cos => arg.cos(), - tan => arg.tan(), - asin => arg.asin(), - acos => arg.acos(), - atan => arg.atan(), - - sinh => arg.sinh(), - cosh => arg.cosh(), - tanh => arg.tanh(), - asinh => arg.asinh(), - acosh => arg.acosh(), - atanh => arg.atanh(), - - exp => arg.exp(), - abs => arg.abs(), - ceil => arg.ceil(), - floor => arg.floor(), - round => arg.round(), - _ => unreachable!(), - } - }, - binary_expr => { - let mut p = pair.into_inner(); - - let op = p.next().ok_or(CalcError::ArgCount)?; - - let arg1 = eval_single_pair(p.next().ok_or(CalcError::ArgCount)?)?; - let arg2 = eval_single_pair(p.next().ok_or(CalcError::ArgCount)?)?; - - assert!(p.next().is_none()); - - match op.as_rule() { - min => arg1.min(arg2), - max => arg1.max(arg2), - atan2 => arg1.atan2(arg2), - _ => unreachable!(), - } - }, - suffix_expr => { - let mut p = pair.into_inner(); - - let arg = eval_expr(p.next().ok_or(CalcError::ArgCount)?.into_inner())?; - let op = p.next().ok_or(CalcError::ArgCount)?; - - assert!(p.next().is_none()); - - match op.as_rule() { - factorial => statrs::function::gamma::gamma(arg + 1.), - _ => unreachable!(), - } - }, - _ => unreachable!(), - }; - - Ok(result) - } - - fn eval_expr(p: Pairs<Rule>) -> StdResult<f64, CalcError> { - CLIMBER - .map_primary(eval_single_pair) - .map_infix(|lhs, op, rhs| { - let lhs = lhs?; - let rhs = rhs?; - - let result = match op.as_rule() { - add => lhs + rhs, - sub => lhs - rhs, - mul => lhs * rhs, - div => lhs / rhs, - pow => lhs.powf(rhs), - dice => { - let dice_count = lhs as usize; - let dice_faces = rhs as usize; - - let mut rng = thread_rng(); - (0..dice_count) - .map(|_| rng.gen_range(1..(dice_faces + 1))) - .sum::<usize>() as f64 - }, - _ => unreachable!(), - }; - - Ok(result) - }) - .map_postfix(|arg, _post| arg) // discard EOI - .parse(p) - } - - eval_expr(result) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_calc_basics() { - assert_eq!(3., Calc::eval("1 + 2").unwrap()); - assert_eq!(3.0f64.ln(), Calc::eval("log 3").unwrap()); - assert!(6. - Calc::eval("3!").unwrap() < 0.0001); - assert_eq!(3., Calc::eval("max 3 2").unwrap()); - } - - #[test] - fn test_binary_unary() { - assert_eq!(3.0f64.ln(), Calc::eval("max log 3 log 2").unwrap()); - } - - #[test] - fn test_prefix_suffix() { - assert!(6. - Calc::eval("abs 3!").unwrap() < 0.0001); - } -} +use crate::util; /// Roll some number of dice or perform a calculation. #[poise::command(prefix_command, guild_only, aliases("calc", "calculate"))] -pub async fn roll(ctx: PoiseContext<'_>, #[rest] rest: String) -> anyhow::Result<()> { - match Calc::eval(&rest) { +pub async fn roll<U: Send + Sync>( + ctx: poise::Context<'_, U, anyhow::Error>, + #[rest] rest: String, +) -> anyhow::Result<()> { + match thulani_calc::Calc::eval(&rest) { Ok(result) => { - debug!("got calc result '{}'", result); + log::debug!("got calc result '{}'", result); util::reply(ctx, result.to_string()).await?; }, Err(e) => { - error!("error encountered reading calc '{}': {}", rest, e); + log::error!("error encountered reading calc '{}': {}", rest, e); util::reply(ctx, "I COULDN'T READ THAT YOU FUCK").await?; }, } |
