diff options
| -rw-r--r-- | Cargo.lock | 34 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | src/commands/roll.rs | 243 | ||||
| -rw-r--r-- | src/main.rs | 4 |
4 files changed, 242 insertions, 41 deletions
@@ -941,6 +941,15 @@ dependencies = [ ] [[package]] +name = "nom" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "num-integer" version = "0.1.39" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1212,6 +1221,18 @@ dependencies = [ [[package]] name = "rand" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ @@ -1589,6 +1610,14 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "statrs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "string" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1733,12 +1762,14 @@ dependencies = [ "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "nom 4.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "serenity 0.5.11 (git+https://github.com/mammothbane/serenity)", "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "statrs 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "timeago 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2194,6 +2225,7 @@ dependencies = [ "checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b" +"checksum nom 4.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4836e9d6036552017e107edc598c97b2dee245161ff1b1ad4af215004774b354" "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" "checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba" @@ -2227,6 +2259,7 @@ dependencies = [ "checksum r2d2 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5d746fc8a0dab19ccea7ff73ad535854e90ddb3b4b8cdce953dd5cd0b2e7bd22" "checksum rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" "checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +"checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" @@ -2270,6 +2303,7 @@ dependencies = [ "checksum smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c4488ae950c49d403731982257768f48fada354a5203fe81f9bb6f43ca9002be" "checksum sodiumoxide 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "71c0682b4406fa25d621b19d2e70b5f6c8627e39b4b7ce0e24b2ef05d0fbe1ca" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" +"checksum statrs 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "10102ac8d55e35db2b3fafc26f81ba8647da2e15879ab686a67e6d19af2685e8" "checksum string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b639411d0b9c738748b5397d5ceba08e648f4f1992231aa859af1a017f31f60b" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" @@ -27,6 +27,8 @@ regex = "~1.1" itertools = "^0.8" serde_json = "~1.0" timeago = "^0.1" +nom = { version = "~4.2", features = ["verbose-errors"] } +statrs = "^0.10" [dependencies.serenity] default-features = false 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; |
