aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock34
-rw-r--r--Cargo.toml2
-rw-r--r--src/commands/roll.rs243
-rw-r--r--src/main.rs4
4 files changed, 242 insertions, 41 deletions
diff --git a/Cargo.lock b/Cargo.lock
index cb13a8a..987871f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index efa64b5..7b8bc33 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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;