diff --git a/src/main.rs b/src/main.rs index 0a0b5d9..52952d8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,9 +10,11 @@ fn main() -> io::Result<()> { match cli.action { Some(cli::Action::Evaluate(args)) => { - let expr = args.expression.join(" "); - let expr = math::parse(&expr); - println!("{}", math::eval(expr)); + let mut ctx = math::Context::default(); + + let result = ctx.run_string(&args.expression.join(" ")); + println!("{}", result); + Ok(()) } None => repl(), @@ -20,6 +22,8 @@ fn main() -> io::Result<()> { } fn repl() -> io::Result<()> { + let mut ctx = math::Context::default(); + if io::stdin().is_terminal() { println!("mcl: math command line | ctrl + d to exit"); } @@ -31,9 +35,8 @@ fn repl() -> io::Result<()> { break; } - let line = line.unwrap(); - let parsed = math::parse(line.trim()); - println!("{}", math::eval(parsed)); + let result = ctx.run_string(&line.unwrap()); + println!("{}", result); prompt()?; } diff --git a/src/math/constants.rs b/src/math/constants.rs index bce5a39..0924ac3 100644 --- a/src/math/constants.rs +++ b/src/math/constants.rs @@ -1,9 +1,10 @@ use rust_decimal::Decimal; -pub fn get(name: &str) -> Option { - match &name.to_lowercase()[..] { - "pi" => Some(Decimal::PI), - "e" => Some(Decimal::E), - _ => None, +use crate::math::{context::Context, types::Expression}; + +impl Context { + pub fn register_math_constants(&mut self) { + self.set_variable("pi", Expression::Number(Decimal::PI)); + self.set_variable("e", Expression::Number(Decimal::E)); } } diff --git a/src/math/context.rs b/src/math/context.rs new file mode 100644 index 0000000..caabdcc --- /dev/null +++ b/src/math/context.rs @@ -0,0 +1,57 @@ +use std::collections::HashMap; + +use crate::math::{lexer, parser, types::Expression}; + +type FunctionCallback = Box) -> Expression>; + +pub struct Context { + variables: HashMap, + functions: HashMap, +} + +impl Context { + pub fn new() -> Self { + Self { + variables: HashMap::new(), + functions: HashMap::new(), + } + } + + pub fn default() -> Self { + let mut instance = Self::new(); + instance.register_math_constants(); + instance.register_builtin_functions(); + instance + } + + pub fn register_function(&mut self, name: &str, callback: F) + where + F: Fn(Vec) -> Expression + 'static, + { + self.functions.insert(name.to_owned(), Box::new(callback)); + } + + pub fn apply_function(&self, name: &str, params: Vec) -> Expression { + let func = self.functions.get(name); + if func.is_none() { + panic!("unable to find function: {}", name); + } + + let func = func.unwrap(); + func(params) + } + + pub fn get_variable(&self, name: &str) -> Option<&Expression> { + self.variables.get(name) + } + + pub fn set_variable(&mut self, name: &str, expr: Expression) { + self.variables.insert(name.to_owned(), expr); + } + + pub fn run_string(&mut self, code: &str) -> Expression { + let lexer = lexer::from(code); + let mut parser = parser::from(lexer.collect()); + self.eval_expr(parser.parse()) + } +} diff --git a/src/math/eval.rs b/src/math/eval.rs index f21904a..334e17f 100644 --- a/src/math/eval.rs +++ b/src/math/eval.rs @@ -1,48 +1,45 @@ use rust_decimal::MathematicalOps; use crate::math::{ - constants, functions, + context::Context, types::{Expression, Operator}, }; -pub fn eval_expr(expr: Expression) -> Expression { - match expr { - Expression::Number(_) => expr, - Expression::Variable(name) => { - if let Some(value) = constants::get(&name) { - Expression::Number(value) - } else { - Expression::Variable(name) +impl Context { + pub fn eval_expr(&self, expr: Expression) -> Expression { + match expr { + Expression::Number(_) => expr, + Expression::Variable(name) => { + if let Some(value) = self.get_variable(&name) { + value.clone() + } else { + Expression::Variable(name) + } } - } - Expression::Fraction(numberator, denominator) => { - // TODO: implement proper fraction handling - eval_expr(Expression::Operation( - Operator::Divide, - numberator, - denominator, - )) - } - Expression::Operation(operator, left, right) => eval_operation(operator, *left, *right), - Expression::Function(name, params) => { - let params = params.into_iter().map(eval_expr).collect(); - functions::eval_function(&name, params) + Expression::Fraction(numberator, denominator) => { + // TODO: implement proper fraction handling + self.eval_expr(Expression::Operation( + Operator::Divide, + numberator, + denominator, + )) + } + Expression::Operation(operator, left, right) => { + let left = self.eval_expr(*left); + let right = self.eval_expr(*right); + match (&left, &right) { + (Expression::Number(left), Expression::Number(right)) => match operator { + Operator::Add => Expression::Number(left + right), + Operator::Subtract => Expression::Number(left - right), + Operator::Multiply => Expression::Number(left * right), + Operator::Divide => Expression::Number(left / right), + Operator::Modulo => Expression::Number(left % right), + Operator::Power => Expression::Number(left.powd(*right)), + }, + _ => Expression::Operation(operator, Box::new(left), Box::new(right)), + } + } + Expression::Function(name, params) => self.apply_function(&name, params), } } } - -fn eval_operation(operator: Operator, left: Expression, right: Expression) -> Expression { - let left = eval_expr(left); - let right = eval_expr(right); - match (&left, &right) { - (Expression::Number(left), Expression::Number(right)) => match operator { - Operator::Add => Expression::Number(left + right), - Operator::Subtract => Expression::Number(left - right), - Operator::Multiply => Expression::Number(left * right), - Operator::Divide => Expression::Number(left / right), - Operator::Modulo => Expression::Number(left % right), - Operator::Power => Expression::Number(left.powd(*right)), - }, - _ => Expression::Operation(operator, Box::new(left), Box::new(right)), - } -} diff --git a/src/math/functions.rs b/src/math/functions.rs index 16d53d4..ee0f59c 100644 --- a/src/math/functions.rs +++ b/src/math/functions.rs @@ -1,19 +1,35 @@ +use crate::math::context::Context; use crate::math::types::Expression; use rust_decimal::Decimal; use rust_decimal::MathematicalOps; use rust_decimal::prelude::ToPrimitive; -pub fn eval_function(func_name: &str, params: Vec) -> Expression { - match func_name { - "sin" => map_decimal(¶ms[0], |d| d.sin()), - "cos" => map_decimal(¶ms[0], |d| d.cos()), - "tan" => map_decimal(¶ms[0], |d| d.tan()), - "asin" => map_decimal(¶ms[0], |d| map_f64(&d, |f| f.asin())), - "acos" => map_decimal(¶ms[0], |d| map_f64(&d, |f| f.acos())), - "atan" => map_decimal(¶ms[0], |d| map_f64(&d, |f| f.atan())), - "abs" => map_decimal(¶ms[0], |d| d.abs()), - "sqrt" => map_decimal(¶ms[0], |d| d.abs().sqrt().expect("impossible")), - _ => panic!("unknown function"), +impl Context { + pub fn register_builtin_functions(&mut self) { + self.register_function("abs", |params: Vec| { + map_decimal(¶ms[0], |d| d.abs()) + }); + self.register_function("sqrt", |params: Vec| { + map_decimal(¶ms[0], |d| d.abs().sqrt().unwrap()) + }); + self.register_function("sin", |params: Vec| { + map_decimal(¶ms[0], |d| d.sin()) + }); + self.register_function("cos", |params: Vec| { + map_decimal(¶ms[0], |d| d.cos()) + }); + self.register_function("tan", |params: Vec| { + map_decimal(¶ms[0], |d| d.tan()) + }); + self.register_function("asin", |params: Vec| { + map_decimal(¶ms[0], |d| map_f64(&d, |f| f.asin())) + }); + self.register_function("acos", |params: Vec| { + map_decimal(¶ms[0], |d| map_f64(&d, |f| f.acos())) + }); + self.register_function("atan", |params: Vec| { + map_decimal(¶ms[0], |d| map_f64(&d, |f| f.atan())) + }); } } diff --git a/src/math/mod.rs b/src/math/mod.rs index 105326f..2ed2081 100644 --- a/src/math/mod.rs +++ b/src/math/mod.rs @@ -4,6 +4,7 @@ use crate::math::types::Expression; mod tests; mod constants; +mod context; mod display; mod eval; mod functions; @@ -12,12 +13,4 @@ mod parser; pub mod types; -pub fn parse(source: &str) -> Expression { - let lexer = lexer::from(source); - let mut parser = parser::from(lexer.collect()); - parser.parse() -} - -pub fn eval(expr: Expression) -> Expression { - eval::eval_expr(expr) -} +pub use context::Context; diff --git a/src/math/tests/eval.rs b/src/math/tests/eval.rs index 958f721..65a8ddd 100644 --- a/src/math/tests/eval.rs +++ b/src/math/tests/eval.rs @@ -1,26 +1,29 @@ use rust_decimal::{Decimal, dec}; use crate::math::{ - eval::eval_expr, + self, types::{Expression, Operator}, }; #[test] fn test_eval_expr_number() { let expr = Expression::Number(dec!(1)); - assert_eq!(eval_expr(expr), Expression::Number(dec!(1))); + let ctx = math::Context::default(); + assert_eq!(ctx.eval_expr(expr), Expression::Number(dec!(1))); } #[test] fn test_eval_expr_variable() { let expr = Expression::Variable("x".to_owned()); - assert_eq!(eval_expr(expr), Expression::Variable("x".to_owned())); + let ctx = math::Context::default(); + assert_eq!(ctx.eval_expr(expr), Expression::Variable("x".to_owned())); } #[test] fn test_eval_expr_variable_constant() { let expr = Expression::Variable("pi".to_owned()); - assert_eq!(eval_expr(expr), Expression::Number(Decimal::PI)); + let ctx = math::Context::default(); + assert_eq!(ctx.eval_expr(expr), Expression::Number(Decimal::PI)); } #[test] @@ -29,13 +32,15 @@ fn test_eval_expr_fraction() { Box::new(Expression::Number(dec!(1))), Box::new(Expression::Number(dec!(2))), ); - assert_eq!(eval_expr(expr), Expression::Number(dec!(0.5))); + let ctx = math::Context::default(); + assert_eq!(ctx.eval_expr(expr), Expression::Number(dec!(0.5))); } #[test] fn test_eval_expr_function() { let expr = Expression::Function("sqrt".to_owned(), vec![Expression::Number(dec!(4))]); - assert_eq!(eval_expr(expr), Expression::Number(dec!(2))); + let ctx = math::Context::default(); + assert_eq!(ctx.eval_expr(expr), Expression::Number(dec!(2))); } #[test] @@ -45,5 +50,6 @@ fn test_eval_operation() { Box::new(Expression::Number(dec!(2))), Box::new(Expression::Number(dec!(3))), ); - assert_eq!(eval_expr(expr), Expression::Number(dec!(8))); + let ctx = math::Context::default(); + assert_eq!(ctx.eval_expr(expr), Expression::Number(dec!(8))); } diff --git a/src/math/types.rs b/src/math/types.rs index 680a140..b5e01eb 100644 --- a/src/math/types.rs +++ b/src/math/types.rs @@ -1,6 +1,6 @@ use rust_decimal::Decimal; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum Expression { Number(Decimal), Fraction(Box, Box),