aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorNathan Perry <avaglir@gmail.com>2019-03-03 20:56:35 -0500
committerNathan Perry <avaglir@gmail.com>2019-03-03 20:56:35 -0500
commitfc1331fc8593ec88d122918e7f97e59d1d51b3fe (patch)
treea83203377313d39075fef3fca6ab75d9a2b9d446 /src
parente9e683d0030d9a459ecd6183c8250d0dc42662ef (diff)
initial rework of rolling framework
Diffstat (limited to 'src')
-rw-r--r--src/commands/roll.rs243
-rw-r--r--src/main.rs4
2 files changed, 206 insertions, 41 deletions
diff --git a/src/commands/roll.rs b/src/commands/roll.rs
index b3f7a38..8087d80 100644
--- a/src/commands/roll.rs
+++ b/src/commands/roll.rs
@@ -1,63 +1,224 @@
+use failure::err_msg;
+use nom::{
+ self,
+ digit,
+ types::CompleteStr,
+};
use rand::prelude::*;
-use regex::Regex;
use serenity::{
framework::standard::Args,
model::channel::Message,
prelude::*,
};
+use statrs;
use crate::{
commands::send,
Result,
};
-lazy_static! {
- static ref ROLL_REGEX: Regex = Regex::new(r"([0-9]+)?(?:d([0-9]+)(?:\s+\+\s+([0-9]+))?)")
- .expect("error parsing roll regex");
+#[derive(Clone, Debug, PartialEq)]
+enum CalcExpr {
+ Binary(BinOp, Box<CalcExpr>, Box<CalcExpr>),
+ Unary(UnaryOp, Box<CalcExpr>),
+ Term(f64),
}
-pub fn roll(_ctx: &mut Context, msg: &Message, args: Args) -> Result<()> {
- let captures = match ROLL_REGEX.captures(args.full()) {
- Some(captures) => captures,
- None => return send(msg.channel_id, "conway is a goldfish", msg.tts),
- };
+impl CalcExpr {
+ fn parse<S: AsRef<str>>(input: S) -> Result<Box<Self>> {
+ parse_expr(CompleteStr(input.as_ref()))
+ .map(|(_, res)| res)
+ .map_err(|e| err_msg(format!("couldn't parse: {}", e)))
+ }
+
+ fn compute(self: Box<Self>) -> f64 {
+ use self::CalcExpr::*;
+ use self::BinOp::*;
+ use self::UnaryOp::*;
- let dice_count = match captures.get(1) {
- Some(x) => {
- match x.as_str().parse::<usize>() {
- Ok(x) => x,
- Err(e) => {
- send(msg.channel_id, "conway is a goldfish", msg.tts)?;
- return Err(e.into());
- },
- }
- },
- None => 1,
- };
+ let s = *self;
+ match s {
+ 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;
- if dice_count > 1000000 {
- send(msg.channel_id, "no.", msg.tts)?;
- return Ok(());
+ 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();
+
+ 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(),
+ }
+ },
+ Term(v) => v,
+ }
}
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+enum BinOp {
+ Add,
+ Sub,
+ Mul,
+ Div,
+ Mod,
+ Pow,
+ Min,
+ Max,
+ DiceRoll,
+}
- let faces = match captures.get(2).unwrap().as_str().parse::<usize>() {
- Ok(faces) => faces,
- Err(e) => {
- send(msg.channel_id, "conway is a goldfish", msg.tts)?;
- return Err(e.into())
- },
- };
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+enum UnaryOp {
+ Log,
+ Sqrt,
+ Sgn,
+ Exp,
+ Sin,
+ Cos,
+ Tan,
+ Factorial,
+ Neg,
+}
+
+fn parse_expr(input: CompleteStr) -> nom::IResult<CompleteStr, Box<CalcExpr>> {
+ ws!(input, alt_complete!(
+ parse_infix |
+ parse_dice |
+ parse_binary_prefix |
+ parse_suffix |
+ parse_prefix |
+ parse_term_or_paren
+ ))
+}
+
+fn parse_term_or_paren(input: CompleteStr) -> nom::IResult<CompleteStr, Box<CalcExpr>> {
+ ws!(input, alt_complete!(
+ delimited!(char!('('), parse_expr, char!(')')) |
+ do_parse!(
+ dat: flat_map!(digit, parse_to!(f64)) >>
+ (Box::new(CalcExpr::Term(dat)))
+ )
+ ))
+}
- let adjust = match captures.get(3).map(|adjust| adjust.as_str().parse::<usize>()).transpose() {
- Ok(adjust) => adjust.unwrap_or(0),
- Err(e) => {
- send(msg.channel_id, "conway is a goldfish", msg.tts)?;
- return Err(e.into())
- },
- };
+fn parse_dice(input: CompleteStr) -> nom::IResult<CompleteStr, Box<CalcExpr>> {
+ ws!(input, do_parse!(
+ tpl: separated_pair!(parse_term_or_paren, ws!(char!('d')), parse_term_or_paren) >>
+ ({
+ let (expr1, expr2) = tpl;
+ Box::new(CalcExpr::Binary(BinOp::DiceRoll, expr1, expr2))
+ })
+ ))
+}
- let mut rng = thread_rng();
- let total = (0..dice_count).map(|_| rng.gen_range(0, faces)).sum::<usize>() + adjust + dice_count;
+fn parse_infix(input: CompleteStr) -> nom::IResult<CompleteStr, Box<CalcExpr>> {
+ ws!(input, do_parse!(
+ tpl: tuple!(parse_term_or_paren, ws!(one_of!("+-*/%^")), parse_term_or_paren) >>
+ ({
+ let (expr1, op, expr2) = tpl;
+ let op = match op {
+ '+' => BinOp::Add,
+ '-' => BinOp::Sub,
+ '*' => BinOp::Mul,
+ '/' => BinOp::Div,
+ '%' => BinOp::Mod,
+ '^' => BinOp::Pow,
+ _ => unreachable!(),
+ };
+ Box::new(CalcExpr::Binary(op, expr1, expr2))
+ })
+ ))
+}
- send(msg.channel_id, &format!("{}", total), msg.tts)
+fn parse_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 }
+ | char!('-') => { |_| UnaryOp::Neg }
+ )) >>
+ expr: parse_term_or_paren >>
+ (Box::new(CalcExpr::Unary(op, expr)))
+ ))
+}
+
+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: parse_term_or_paren >>
+ expr2: parse_term_or_paren >>
+ (Box::new(CalcExpr::Binary(op, expr1, expr2)))
+ ))
+}
+
+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)))
+ ))
+}
+
+#[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.));
+ }
+
+ #[test]
+ fn test_parens() {
+ let (s, expr) = parse_expr("(123)".into()).unwrap();
+ assert_eq!(s.0, "");
+ assert_eq!(expr, box CalcExpr::Term(123.));
+ }
+
+ #[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.)))
+ }
+
+}
+
+pub fn roll(_ctx: &mut Context, msg: &Message, args: Args) -> Result<()> {
+ let expr = CalcExpr::parse(args.rest())?;
+ send(msg.channel_id, &format!("{}", expr.compute()), msg.tts)
}
diff --git a/src/main.rs b/src/main.rs
index 1116b1f..2f6126a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,6 +2,8 @@
#![feature(try_trait)]
#![feature(pattern)]
+#![feature(box_syntax, box_patterns)]
+
extern crate chrono;
#[cfg(feature = "diesel")]
#[macro_use] extern crate diesel;
@@ -13,11 +15,13 @@ extern crate fern;
#[cfg_attr(test, macro_use)] extern crate itertools;
#[macro_use] extern crate lazy_static;
#[macro_use] extern crate log;
+#[macro_use] extern crate nom;
extern crate rand;
extern crate regex;
extern crate serde_json;
extern crate serenity;
extern crate sha1;
+extern crate statrs;
extern crate time;
extern crate timeago;
extern crate typemap;