refactor(eval): move to context system
started the move to a context based system this is so that user defined variables and functions can be added in the future, as well as scripting
This commit is contained in:
8 files changed
+150
-77
No files matched your search
+9
-6
@@ -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()?;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use rust_decimal::Decimal;
|
||||
|
||||
pub fn get(name: &str) -> Option<Decimal> {
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::math::{lexer, parser, types::Expression};
|
||||
|
||||
type FunctionCallback = Box<dyn Fn(Vec<Expression>) -> Expression>;
|
||||
|
||||
pub struct Context {
|
||||
variables: HashMap<String, Expression>,
|
||||
functions: HashMap<String, FunctionCallback>,
|
||||
}
|
||||
|
||||
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<F>(&mut self, name: &str, callback: F)
|
||||
where
|
||||
F: Fn(Vec<Expression>) -> Expression + 'static,
|
||||
{
|
||||
self.functions.insert(name.to_owned(), Box::new(callback));
|
||||
}
|
||||
|
||||
pub fn apply_function(&self, name: &str, params: Vec<Expression>) -> 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())
|
||||
}
|
||||
}
|
||||
+35
-38
@@ -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)),
|
||||
}
|
||||
}
|
||||
+27
-11
@@ -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>) -> 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<Expression>| {
|
||||
map_decimal(¶ms[0], |d| d.abs())
|
||||
});
|
||||
self.register_function("sqrt", |params: Vec<Expression>| {
|
||||
map_decimal(¶ms[0], |d| d.abs().sqrt().unwrap())
|
||||
});
|
||||
self.register_function("sin", |params: Vec<Expression>| {
|
||||
map_decimal(¶ms[0], |d| d.sin())
|
||||
});
|
||||
self.register_function("cos", |params: Vec<Expression>| {
|
||||
map_decimal(¶ms[0], |d| d.cos())
|
||||
});
|
||||
self.register_function("tan", |params: Vec<Expression>| {
|
||||
map_decimal(¶ms[0], |d| d.tan())
|
||||
});
|
||||
self.register_function("asin", |params: Vec<Expression>| {
|
||||
map_decimal(¶ms[0], |d| map_f64(&d, |f| f.asin()))
|
||||
});
|
||||
self.register_function("acos", |params: Vec<Expression>| {
|
||||
map_decimal(¶ms[0], |d| map_f64(&d, |f| f.acos()))
|
||||
});
|
||||
self.register_function("atan", |params: Vec<Expression>| {
|
||||
map_decimal(¶ms[0], |d| map_f64(&d, |f| f.atan()))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-9
@@ -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;
|
||||
+13
-7
@@ -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)));
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
use rust_decimal::Decimal;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Expression {
|
||||
Number(Decimal),
|
||||
Fraction(Box<Expression>, Box<Expression>),
|
||||
|
||||
Reference in new issue
Block a user