aboutsummaryrefslogtreecommitdiff
path: root/src/commands
diff options
context:
space:
mode:
authorNathan Perry <avaglir@gmail.com>2019-03-29 15:20:27 -0400
committerNathan Perry <avaglir@gmail.com>2019-03-29 15:20:27 -0400
commit5f63ea4a1991159348c2e7d7f519c3ac6cd46454 (patch)
tree4af4ba0814287dd4ce6e7144c614f7def495c162 /src/commands
parent2685e6028dd775bcd618a3d8d2b22e32730454a3 (diff)
switch over to pest
Diffstat (limited to 'src/commands')
-rw-r--r--src/commands/calc.pest79
-rw-r--r--src/commands/mod.rs5
-rw-r--r--src/commands/roll.rs422
3 files changed, 208 insertions, 298 deletions
diff --git a/src/commands/calc.pest b/src/commands/calc.pest
new file mode 100644
index 0000000..07eeddb
--- /dev/null
+++ b/src/commands/calc.pest
@@ -0,0 +1,79 @@
+num = {
+ hex
+ | oct
+ | binary
+ | float
+}
+
+float = @{ int ~ ( "." ~ ASCII_DIGIT*)? ~ (^"e" ~ int)? }
+ int = { "-"? ~ ASCII_DIGIT+ }
+
+hex = @{ "0x" ~ ASCII_HEX_DIGIT+ }
+oct = @{ "0o" ~ ASCII_OCT_DIGIT+ }
+binary = @{ "0b" ~ ASCII_BIN_DIGIT+ }
+
+infix = _{ add | sub | mul | div | modulo }
+ add = { "+" }
+ sub = { "-" }
+ modulo = { "%" | "mod" }
+ mul = { "*" }
+ div = { "/" }
+
+tight_infix = _{ dice | pow }
+ dice = { "d" }
+ pow = { "^" }
+
+trig = _{ sin | cos | tan | asin | acos | atan }
+ sin = { "sin" }
+ cos = { "cos" }
+ tan = { "tan" }
+ asin = { "asin" }
+ acos = { "acos" }
+ atan = { "atan" }
+
+htrig = _{ sinh | cosh | tanh | asinh | acosh | atanh }
+ sinh = { "sinh" }
+ cosh = { "cosh" }
+ tanh = { "tanh" }
+ asinh = { "asinh" }
+ acosh = { "acosh" }
+ atanh = { "atanh" }
+
+unary_prefix = _{ log | sqrt | sgn | htrig | trig | exp | abs | ceil | floor | round }
+ log = { "log" | "ln" }
+ sqrt = { "sqrt" }
+ sgn = { "sgn" }
+ exp = { "exp" }
+ abs = { "abs" }
+ ceil = { "ceil" }
+ floor = { "floor" }
+ round = { "round" }
+
+binary_prefix = _{ min | max | atan2 }
+ min = { "min" }
+ max = { "max" }
+ atan2 = { "atan2" }
+
+suffix = _{ factorial }
+ factorial = { "!" }
+
+term = _{ num | "(" ~ expr ~ ")" }
+
+suffix_expr = { term ~ suffix }
+unary_expr = ${ unary_prefix ~ ws+ ~ outfix_expr }
+binary_expr = ${ binary_prefix ~ ws+ ~ outfix_expr ~ ws+ ~ outfix_expr }
+
+tight = _{ (suffix_expr | term) ~ (tight_infix ~ tight)* }
+
+expr = { outfix_expr ~ (infix ~ outfix_expr)* }
+
+outfix_expr = _{
+ tight |
+ binary_expr |
+ unary_expr
+}
+
+calc = _{ SOI ~ expr ~ EOI }
+
+ws = _{ " " | "\t" | "\n" }
+WHITESPACE = _{ ws }
diff --git a/src/commands/mod.rs b/src/commands/mod.rs
index 1582977..6596a32 100644
--- a/src/commands/mod.rs
+++ b/src/commands/mod.rs
@@ -60,11 +60,6 @@ pub fn register_commands(f: StandardFramework) -> StandardFramework {
.desc("simulate rolling dice")
.guild_only(true)
.exec(roll::roll))
- .command("debug_expr", |c| c
- .desc("debug calculator expression")
- .owners_only(true)
- .exec(roll::debug_expr)
- )
.unrecognised_command(|ctx, msg, unrec| {
let url = match msg.content.split_whitespace().skip(1).next() {
Some(x) if x.starts_with("http") => x,
diff --git a/src/commands/roll.rs b/src/commands/roll.rs
index bdb729e..b447692 100644
--- a/src/commands/roll.rs
+++ b/src/commands/roll.rs
@@ -1,10 +1,3 @@
-use failure::Error;
-use itertools::Itertools;
-use nom::{
- self,
- double,
- types::CompleteStr,
-};
use rand::prelude::*;
use serenity::{
framework::standard::Args,
@@ -18,334 +11,177 @@ use crate::{
Result,
};
-#[derive(Clone, Debug, PartialEq)]
-enum CalcExpr {
- Binary(BinOp, Box<CalcExpr>, Box<CalcExpr>),
- Unary(UnaryOp, Box<CalcExpr>),
- Term(f64),
-}
-
-#[derive(Clone, Debug, PartialEq, Fail)]
-enum CalcParseError {
- #[fail(display = "couldn't consume entire expression. parsed: {:?}, remaining: '{}'.", parsed, remaining)]
- NotReadToEnd {
- parsed: Box<CalcExpr>,
- remaining: String,
- },
- #[fail(display = "nom error: {}", _0)]
- Nom(String),
-}
+#[derive(Parser)]
+#[grammar = "commands/calc.pest"]
+struct Calc;
-impl CalcExpr {
- pub fn parse<S: AsRef<str>>(input: S) -> Result<Box<Self>> {
- parse_expr(CompleteStr(input.as_ref()))
- .map_err(|e| CalcParseError::Nom(format!("{}", e)))
- .and_then(|(s, res)| {
- if s.len() != 0 {
- Err(CalcParseError::NotReadToEnd {
- parsed: res,
- remaining: s.as_ref().to_owned(),
- })
- } else {
- Ok(res)
- }
- })
- .map_err(Error::from)
- }
+impl Calc {
+ fn eval<S: AsRef<str>>(s: S) -> Result<f64> {
+ use pest::{
+ Parser,
+ prec_climber::PrecClimber,
+ iterators::{Pair, Pairs},
+ };
- pub fn pretty(&self) -> String {
- format!("```\n{}\n```", self.pretty_helper(0))
- }
+ use self::Rule::*;
- fn pretty_helper(&self, depth: usize) -> String {
- match self {
- CalcExpr::Binary(op, e1, e2) => {
- let lines = vec! {
- format!("{}{:?} ({}) {{", "\t".repeat(depth), op, self.compute()),
- e1.pretty_helper(depth + 1),
- e2.pretty_helper(depth + 1),
- "\t".repeat(depth) + "}",
+ lazy_static! {
+ static ref CLIMBER: PrecClimber<self::Rule> = {
+ use pest::prec_climber::{
+ Operator,
+ Assoc::*,
};
- lines.into_iter().join("\n")
- },
- CalcExpr::Unary(op, e) => {
- let lines = vec! {
- format!("{}{:?} ({}) {{", "\t".repeat(depth), op, self.compute()),
- e.pretty_helper(depth + 1),
- "\t".repeat(depth) + "}",
- };
-
- lines.into_iter().join("\n")
- },
- CalcExpr::Term(val) => {
- format!("{}{}", "\t".repeat(depth), val)
- }
+ PrecClimber::new(vec![
+ Operator::new(add, Left) | Operator::new(sub, Left) | Operator::new(modulo, Left),
+ Operator::new(mul, Left) | Operator::new(div, Left),
+ Operator::new(dice, Left),
+ Operator::new(pow, Right),
+ ])
+ };
}
- }
- pub fn compute(&self) -> f64 {
- use self::CalcExpr::*;
- use self::BinOp::*;
- use self::UnaryOp::*;
+ let result = Calc::parse(calc, s.as_ref())?;
- match self {
- Binary(bop, e1, e2) => {
- let r1 = e1.compute();
- let r2 = e2.compute();
- match bop {
- Add => r1 + r2,
- Sub => r1 - r2,
- Mul => r1 * r2,
- Div => r1 / r2,
- Mod => r1 % r2,
- Pow => r1.powf(r2),
- Min => r1.min(r2),
- Max => r1.max(r2),
- DiceRoll => {
- let dice_count = r1 as usize;
- let dice_faces = r2 as usize;
+ fn eval_single_pair(pair: Pair<self::Rule>) -> f64 {
+ match pair.as_rule() {
+ oct | hex | binary => {
+ let base = match pair.as_rule() {
+ hex => 16,
+ oct => 8,
+ binary => 2,
+ _ => unreachable!(),
+ };
- let mut rng = thread_rng();
- (0..dice_count).map(|_| rng.gen_range(1, dice_faces + 1)).sum::<usize>() as f64
- }
- }
- },
- Unary(uop, e) => {
- let r = e.compute();
+ u64::from_str_radix(&pair.as_str()[2..], base).unwrap() as f64
+ },
+ float => pair.as_str().parse::<f64>().unwrap(),
+ expr | num => eval_expr(pair.into_inner()),
+ unary_expr => {
+ let mut p = pair.into_inner();
- match uop {
- Neg => -r,
- Log => r.ln(),
- Sqrt => r.sqrt(),
- Sgn => r.signum(),
- Sin => r.sin(),
- Cos => r.cos(),
- Tan => r.tan(),
- Factorial => statrs::function::gamma::gamma(r),
- Exp => r.exp(),
- Abs => r.abs(),
- Ceil => r.ceil(),
- Floor => r.floor(),
- Round => r.round(),
- }
- },
- Term(v) => *v,
- }
- }
-}
+ let op = p.next().unwrap();
+ let arg = eval_expr(p);
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-enum BinOp {
- Add,
- Sub,
- Mul,
- Div,
- Mod,
- Pow,
- Min,
- Max,
- DiceRoll,
-}
+ match op.as_rule() {
+ log => arg.ln(),
+ sqrt => arg.sqrt(),
+ sgn => arg.signum(),
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-enum UnaryOp {
- Log,
- Sqrt,
- Sgn,
- Exp,
- Sin,
- Cos,
- Tan,
- Factorial,
- Neg,
- Ceil,
- Floor,
- Abs,
- Round,
-}
+ sin => arg.sin(),
+ cos => arg.cos(),
+ tan => arg.tan(),
+ asin => arg.asin(),
+ acos => arg.acos(),
+ atan => arg.atan(),
-fn parse_expr(input: CompleteStr) -> nom::IResult<CompleteStr, Box<CalcExpr>> {
- ws!(input, up_to_add_sub_mod)
-}
+ sinh => arg.sinh(),
+ cosh => arg.cosh(),
+ tanh => arg.tanh(),
+ asinh => arg.asinh(),
+ acosh => arg.acosh(),
+ atanh => arg.atanh(),
-fn parse_add_sub_mod(input: CompleteStr) -> nom::IResult<CompleteStr, Box<CalcExpr>> {
- ws!(input, do_parse!(
- tpl: tuple!(up_to_div_mul, ws!(one_of!("+-%")), parse_expr) >>
- ({
- let (expr1, op, expr2) = tpl;
- let op = match op {
- '+' => BinOp::Add,
- '-' => BinOp::Sub,
- '%' => BinOp::Mod,
- _ => unreachable!(),
- };
- Box::new(CalcExpr::Binary(op, expr1, expr2))
- })
- ))
-}
+ exp => arg.exp(),
+ abs => arg.abs(),
+ ceil => arg.ceil(),
+ floor => arg.floor(),
+ round => arg.round(),
+ _ => unreachable!(),
+ }
+ },
+ binary_expr => {
+ let mut p = pair.into_inner();
-fn parse_div_mul(input: CompleteStr) -> nom::IResult<CompleteStr, Box<CalcExpr>> {
- ws!(input, do_parse!(
- tpl: tuple!(up_to_binary_prefix, ws!(one_of!("/*")), parse_expr) >>
- ({
- let (expr1, op, expr2) = tpl;
- let op = match op {
- '*' => BinOp::Mul,
- '/' => BinOp::Div,
- '^' => BinOp::Pow,
- _ => unreachable!(),
- };
- Box::new(CalcExpr::Binary(op, expr1, expr2))
- })
- ))
-}
+ let op = p.next().unwrap();
-fn parse_binary_prefix(input: CompleteStr) -> nom::IResult<CompleteStr, Box<CalcExpr>> {
- ws!(input, do_parse!(
- op: ws!(alt_complete!(
- tag!("min") => { |_| BinOp::Min } |
- tag!("max") => { |_| BinOp::Max }
- )) >>
- expr1: up_to_unary_prefix >>
- expr2: up_to_unary_prefix >>
- (Box::new(CalcExpr::Binary(op, expr1, expr2)))
- ))
-}
+ let arg1 = eval_single_pair(p.next().unwrap());
+ let arg2 = eval_single_pair(p.next().unwrap());
-fn parse_unary_prefix(input: CompleteStr) -> nom::IResult<CompleteStr, Box<CalcExpr>> {
- ws!(input, do_parse!(
- op: ws!(alt_complete!(
- tag!("log") => { |_| UnaryOp::Log }
- | tag!("sqrt") => { |_| UnaryOp::Sqrt }
- | tag!("sin") => { |_| UnaryOp::Sin }
- | tag!("cos") => { |_| UnaryOp::Cos }
- | tag!("tan") => { |_| UnaryOp::Tan }
- | tag!("sgn") => { |_| UnaryOp::Sgn }
- | tag!("exp") => { |_| UnaryOp::Exp }
- | tag!("ceil") => { |_| UnaryOp::Ceil }
- | tag!("floor") => { |_| UnaryOp::Floor }
- | tag!("abs") => { |_| UnaryOp::Abs }
- | tag!("round") => { |_| UnaryOp::Round }
- )) >>
- expr: up_to_dice >>
- (Box::new(CalcExpr::Unary(op, expr)))
- ))
-}
+ assert!(p.next().is_none());
-fn parse_dice(input: CompleteStr) -> nom::IResult<CompleteStr, Box<CalcExpr>> {
- ws!(input, do_parse!(
- tpl: separated_pair!(up_to_pow, ws!(char!('d')), up_to_pow) >>
- ({
- let (expr1, expr2) = tpl;
- Box::new(CalcExpr::Binary(BinOp::DiceRoll, expr1, expr2))
- })
- ))
-}
+ 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();
-fn parse_pow(input: CompleteStr) -> nom::IResult<CompleteStr, Box<CalcExpr>> {
- ws!(input, do_parse!(
- tpl: separated_pair!(up_to_neg, ws!(char!('^')), up_to_neg) >>
- ({
- let (expr1, expr2) = tpl;
- Box::new(CalcExpr::Binary(BinOp::Pow, expr1, expr2))
- })
- ))
-}
+ let arg = eval_expr(p.next().unwrap().into_inner());
+ let op = p.next().unwrap();
-fn parse_neg(input: CompleteStr) -> nom::IResult<CompleteStr, Box<CalcExpr>> {
- ws!(input, do_parse!(
- expr: ws!(preceded!(char!('-'), up_to_suffix)) >>
- (Box::new(CalcExpr::Unary(UnaryOp::Neg, expr)))
- ))
-}
+ assert!(p.next().is_none());
-fn parse_suffix(input: CompleteStr) -> nom::IResult<CompleteStr, Box<CalcExpr>> {
- ws!(input, do_parse!(
- expr: terminated!(parse_term_or_paren, ws!(tag!("!"))) >>
- (Box::new(CalcExpr::Unary(UnaryOp::Factorial, expr)))
- ))
-}
+ match op.as_rule() {
+ factorial => statrs::function::gamma::gamma(arg + 1.),
+ _ => unreachable!(),
+ }
+ },
+ _ => unreachable!(),
+ }
+ }
-fn parse_term_or_paren(input: CompleteStr) -> nom::IResult<CompleteStr, Box<CalcExpr>> {
- ws!(input, alt_complete!(
- delimited!(char!('('), parse_expr, char!(')')) |
- do_parse!(
- dat: double >>
- (Box::new(CalcExpr::Term(dat)))
- )
- ))
-}
+ fn eval_expr(p: Pairs<self::Rule>) -> f64 {
+ CLIMBER.climb(
+ p,
+ eval_single_pair,
+ |lhs: f64, op, rhs: f64| 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;
-macro_rules! up_to {
- ($up_to_name:ident, $fn_name:ident, $prev:ident) => (
- fn $up_to_name(input: CompleteStr) -> nom::IResult<CompleteStr, Box<CalcExpr>> {
- alt_complete!(input, $fn_name | $prev)
+ let mut rng = thread_rng();
+ (0..dice_count).map(|_| rng.gen_range(1, dice_faces + 1)).sum::<usize>() as f64
+ },
+ _ => unreachable!(),
+ }
+ )
}
- )
-}
-up_to! { up_to_add_sub_mod, parse_add_sub_mod, up_to_div_mul }
-up_to! { up_to_div_mul, parse_div_mul, up_to_binary_prefix }
-up_to! { up_to_binary_prefix, parse_binary_prefix, up_to_unary_prefix }
-up_to! { up_to_unary_prefix, parse_unary_prefix, up_to_dice }
-up_to! { up_to_dice, parse_dice, up_to_pow }
-up_to! { up_to_pow, parse_pow, up_to_neg }
-up_to! { up_to_neg, parse_neg, up_to_suffix }
-up_to! { up_to_suffix, parse_suffix, parse_term_or_paren }
+ Ok(eval_expr(result))
+ }
+}
#[cfg(test)]
mod test {
use super::*;
#[test]
- fn test_parse_usize() {
- let (s, expr) = parse_expr("123".into()).unwrap();
- assert_eq!(s.0, "");
- assert_eq!(expr, box CalcExpr::Term(123.));
+ 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_parens() {
- let (s, expr) = parse_expr("(123)".into()).unwrap();
- assert_eq!(s.0, "");
- assert_eq!(expr, box CalcExpr::Term(123.));
+ fn test_binary_unary() {
+ assert_eq!(3.0f64.ln(), Calc::eval("max log 3 log 2").unwrap());
}
#[test]
- fn test_infix() {
- let (s, expr) = parse_expr("1 + 2".into()).unwrap();
- assert_eq!(s.0, "");
- assert_eq!(expr, box CalcExpr::Binary(BinOp::Add, box CalcExpr::Term(1.), box CalcExpr::Term(2.)))
+ fn test_prefix_suffix() {
+ assert!(6. - Calc::eval("abs 3!").unwrap() < 0.0001);
}
-
}
pub fn roll(_ctx: &mut Context, msg: &Message, args: Args) -> Result<()> {
- match CalcExpr::parse(args.rest()) {
- Ok(expr) => send(msg.channel_id, &format!("{}", expr.compute()), msg.tts),
- Err(e) => {
- let parse_err = e.downcast::<CalcParseError>().unwrap();
- if let CalcParseError::NotReadToEnd { remaining, .. } = parse_err {
- error!("parsing '{}': failed to consume '{}'", args.rest(), remaining);
- send(msg.channel_id, "I COULDN'T READ THAT YOU FUCK", msg.tts)
- } else {
- Err(parse_err.into())
- }
+ match Calc::eval(args.rest()) {
+ Ok(result) => {
+ debug!("got calc result '{}'", result);
+ send(msg.channel_id, &format!("{}", result), msg.tts)
},
- }
-}
-
-pub fn debug_expr(_ctx: &mut Context, msg: &Message, args: Args) -> Result<()> {
- match CalcExpr::parse(args.rest()) {
- Ok(expr) => send(msg.channel_id, &expr.pretty(), false),
Err(e) => {
- let parse_err = e.downcast::<CalcParseError>().unwrap();
- if let CalcParseError::NotReadToEnd { remaining, parsed } = parse_err {
- send(msg.channel_id, &format!("parsed this expr: {}\nwith remaining text: '{}'", parsed.pretty(), remaining), false)
- } else {
- Err(parse_err.into())
- }
- }
+ error!("error encountered reading calc '{}': {}", args.rest(), e);
+ send(msg.channel_id, "I COULDN'T READ THAT YOU FUCK", msg.tts)
+ },
}
}