add eval command
This commit is contained in:
23
src/commands/eval/mod.rs
Normal file
23
src/commands/eval/mod.rs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
use crate::common::{Context, Error};
|
||||||
|
|
||||||
|
mod tokenizer;
|
||||||
|
mod parse;
|
||||||
|
|
||||||
|
fn evaluate(expr: &str) -> Result<f64, Error> {
|
||||||
|
let tokens = tokenizer::Token::tokenize(expr)?;
|
||||||
|
let mut tokens = tokens.iter();
|
||||||
|
|
||||||
|
let tree = parse::ParseTree::new(&mut tokens)?;
|
||||||
|
|
||||||
|
Ok(tree.evaluate())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluates an expression (uses Polish Notation)
|
||||||
|
#[poise::command(slash_command, prefix_command)]
|
||||||
|
pub async fn eval(ctx: Context<'_>,
|
||||||
|
#[rest]
|
||||||
|
expr: String) -> Result<(), Error>
|
||||||
|
{
|
||||||
|
ctx.reply(format!("{}", evaluate(&expr)?)).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
57
src/commands/eval/parse.rs
Normal file
57
src/commands/eval/parse.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
use std::fmt::Display;
|
||||||
|
use super::tokenizer::{Token, Op};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ParseTree {
|
||||||
|
Leaf(Token),
|
||||||
|
Branch(Op, Box<ParseTree>, Box<ParseTree>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ParseError {
|
||||||
|
UnexpectedEndInput,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ParseError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ParseError::UnexpectedEndInput => write!(f, "Input ended unexpectedly"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for ParseError {}
|
||||||
|
|
||||||
|
impl ParseTree {
|
||||||
|
pub fn new<'a, I: Iterator<Item = &'a Token>>(tokens: &mut I) -> Result<Self, ParseError> {
|
||||||
|
if let Some(token) = tokens.next() {
|
||||||
|
match token {
|
||||||
|
Token::Scalar(_) | Token::Identifier(_) => Ok(Self::Leaf(token.clone())),
|
||||||
|
Token::Operator(op) => {
|
||||||
|
let left = ParseTree::new(tokens)?;
|
||||||
|
let right = ParseTree::new(tokens)?;
|
||||||
|
|
||||||
|
Ok(Self::Branch(op.clone(), Box::new(left), Box::new(right)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(ParseError::UnexpectedEndInput)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn evaluate(&self) -> f64 {
|
||||||
|
match self {
|
||||||
|
ParseTree::Leaf(Token::Scalar(value)) => *value,
|
||||||
|
ParseTree::Leaf(Token::Identifier(_)) => unimplemented!(),
|
||||||
|
ParseTree::Leaf(Token::Operator(_)) => panic!("This absolutely should not happen"),
|
||||||
|
ParseTree::Branch(op, left, right) => match op {
|
||||||
|
Op::Add => left.evaluate() + right.evaluate(),
|
||||||
|
Op::Sub => left.evaluate() - right.evaluate(),
|
||||||
|
Op::Mul => left.evaluate() * right.evaluate(),
|
||||||
|
Op::Div => left.evaluate() / right.evaluate(),
|
||||||
|
Op::Exp => left.evaluate().powf(right.evaluate()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
94
src/commands/eval/tokenizer.rs
Normal file
94
src/commands/eval/tokenizer.rs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
use std::error;
|
||||||
|
use crate::common::Error;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Op {
|
||||||
|
Add,
|
||||||
|
Sub,
|
||||||
|
Mul,
|
||||||
|
Div,
|
||||||
|
Exp,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Token {
|
||||||
|
Identifier(String),
|
||||||
|
Scalar(f64),
|
||||||
|
Operator(Op),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct ParseError(String);
|
||||||
|
|
||||||
|
impl Display for ParseError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for ParseError {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Token {
|
||||||
|
pub fn identifier(str: String) -> Token {
|
||||||
|
Token::Identifier(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scalar(value: f64) -> Token {
|
||||||
|
Token::Scalar(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add() -> Token {
|
||||||
|
Token::Operator(Op::Add)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sub() -> Token {
|
||||||
|
Token::Operator(Op::Sub)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mul() -> Token {
|
||||||
|
Token::Operator(Op::Mul)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn div() -> Token {
|
||||||
|
Token::Operator(Op::Div)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exp() -> Token {
|
||||||
|
Token::Operator(Op::Exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tokenize(s: &str) -> Result<Vec<Self>, Error> {
|
||||||
|
s.split_whitespace().map(Token::from_str).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Token {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
// First check if s is an operator
|
||||||
|
"+" => Ok(Token::add()),
|
||||||
|
"-" => Ok(Token::sub()),
|
||||||
|
"*" => Ok(Token::mul()),
|
||||||
|
"**" => Ok(Token::exp()),
|
||||||
|
"/" => Ok(Token::div()),
|
||||||
|
_ => {
|
||||||
|
if s.starts_with(|c| char::is_digit(c, 10)) {
|
||||||
|
Ok(Token::scalar(s.parse()?))
|
||||||
|
} else if s.starts_with(char::is_alphabetic)
|
||||||
|
&& s.chars().skip(1).all(char::is_alphanumeric) {
|
||||||
|
Ok(Token::identifier(s.to_string()))
|
||||||
|
} else {
|
||||||
|
Err(Box::new(ParseError(format!("Failed to parse \"{}\"", s))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ mod ping;
|
|||||||
mod dox;
|
mod dox;
|
||||||
mod yeehaw;
|
mod yeehaw;
|
||||||
mod gambling;
|
mod gambling;
|
||||||
|
mod eval;
|
||||||
|
|
||||||
pub fn commands() -> Vec<Command<Data, Error>> {
|
pub fn commands() -> Vec<Command<Data, Error>> {
|
||||||
vec![
|
vec![
|
||||||
@@ -14,5 +15,6 @@ pub fn commands() -> Vec<Command<Data, Error>> {
|
|||||||
gambling::balance::balance(),
|
gambling::balance::balance(),
|
||||||
gambling::give::give(),
|
gambling::give::give(),
|
||||||
gambling::wager::wager(),
|
gambling::wager::wager(),
|
||||||
|
eval::eval(),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user