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
+128
-55
No files matched your search
+9
-6
@@ -10,9 +10,11 @@ fn main() -> io::Result<()> {
|
|||||||
|
|
||||||
match cli.action {
|
match cli.action {
|
||||||
Some(cli::Action::Evaluate(args)) => {
|
Some(cli::Action::Evaluate(args)) => {
|
||||||
let expr = args.expression.join(" ");
|
let mut ctx = math::Context::default();
|
||||||
let expr = math::parse(&expr);
|
|
||||||
println!("{}", math::eval(expr));
|
let result = ctx.run_string(&args.expression.join(" "));
|
||||||
|
println!("{}", result);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
None => repl(),
|
None => repl(),
|
||||||
@@ -20,6 +22,8 @@ fn main() -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn repl() -> io::Result<()> {
|
fn repl() -> io::Result<()> {
|
||||||
|
let mut ctx = math::Context::default();
|
||||||
|
|
||||||
if io::stdin().is_terminal() {
|
if io::stdin().is_terminal() {
|
||||||
println!("mcl: math command line | ctrl + d to exit");
|
println!("mcl: math command line | ctrl + d to exit");
|
||||||
}
|
}
|
||||||
@@ -31,9 +35,8 @@ fn repl() -> io::Result<()> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let line = line.unwrap();
|
let result = ctx.run_string(&line.unwrap());
|
||||||
let parsed = math::parse(line.trim());
|
println!("{}", result);
|
||||||
println!("{}", math::eval(parsed));
|
|
||||||
|
|
||||||
prompt()?;
|
prompt()?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
|
|
||||||
pub fn get(name: &str) -> Option<Decimal> {
|
use crate::math::{context::Context, types::Expression};
|
||||||
match &name.to_lowercase()[..] {
|
|
||||||
"pi" => Some(Decimal::PI),
|
impl Context {
|
||||||
"e" => Some(Decimal::E),
|
pub fn register_math_constants(&mut self) {
|
||||||
_ => None,
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
-16
@@ -1,39 +1,32 @@
|
|||||||
use rust_decimal::MathematicalOps;
|
use rust_decimal::MathematicalOps;
|
||||||
|
|
||||||
use crate::math::{
|
use crate::math::{
|
||||||
constants, functions,
|
context::Context,
|
||||||
types::{Expression, Operator},
|
types::{Expression, Operator},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn eval_expr(expr: Expression) -> Expression {
|
impl Context {
|
||||||
|
pub fn eval_expr(&self, expr: Expression) -> Expression {
|
||||||
match expr {
|
match expr {
|
||||||
Expression::Number(_) => expr,
|
Expression::Number(_) => expr,
|
||||||
Expression::Variable(name) => {
|
Expression::Variable(name) => {
|
||||||
if let Some(value) = constants::get(&name) {
|
if let Some(value) = self.get_variable(&name) {
|
||||||
Expression::Number(value)
|
value.clone()
|
||||||
} else {
|
} else {
|
||||||
Expression::Variable(name)
|
Expression::Variable(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expression::Fraction(numberator, denominator) => {
|
Expression::Fraction(numberator, denominator) => {
|
||||||
// TODO: implement proper fraction handling
|
// TODO: implement proper fraction handling
|
||||||
eval_expr(Expression::Operation(
|
self.eval_expr(Expression::Operation(
|
||||||
Operator::Divide,
|
Operator::Divide,
|
||||||
numberator,
|
numberator,
|
||||||
denominator,
|
denominator,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Expression::Operation(operator, left, right) => eval_operation(operator, *left, *right),
|
Expression::Operation(operator, left, right) => {
|
||||||
Expression::Function(name, params) => {
|
let left = self.eval_expr(*left);
|
||||||
let params = params.into_iter().map(eval_expr).collect();
|
let right = self.eval_expr(*right);
|
||||||
functions::eval_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) {
|
match (&left, &right) {
|
||||||
(Expression::Number(left), Expression::Number(right)) => match operator {
|
(Expression::Number(left), Expression::Number(right)) => match operator {
|
||||||
Operator::Add => Expression::Number(left + right),
|
Operator::Add => Expression::Number(left + right),
|
||||||
@@ -46,3 +39,7 @@ fn eval_operation(operator: Operator, left: Expression, right: Expression) -> Ex
|
|||||||
_ => Expression::Operation(operator, Box::new(left), Box::new(right)),
|
_ => Expression::Operation(operator, Box::new(left), Box::new(right)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Expression::Function(name, params) => self.apply_function(&name, params),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
-11
@@ -1,19 +1,35 @@
|
|||||||
|
use crate::math::context::Context;
|
||||||
use crate::math::types::Expression;
|
use crate::math::types::Expression;
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
use rust_decimal::MathematicalOps;
|
use rust_decimal::MathematicalOps;
|
||||||
use rust_decimal::prelude::ToPrimitive;
|
use rust_decimal::prelude::ToPrimitive;
|
||||||
|
|
||||||
pub fn eval_function(func_name: &str, params: Vec<Expression>) -> Expression {
|
impl Context {
|
||||||
match func_name {
|
pub fn register_builtin_functions(&mut self) {
|
||||||
"sin" => map_decimal(¶ms[0], |d| d.sin()),
|
self.register_function("abs", |params: Vec<Expression>| {
|
||||||
"cos" => map_decimal(¶ms[0], |d| d.cos()),
|
map_decimal(¶ms[0], |d| d.abs())
|
||||||
"tan" => map_decimal(¶ms[0], |d| d.tan()),
|
});
|
||||||
"asin" => map_decimal(¶ms[0], |d| map_f64(&d, |f| f.asin())),
|
self.register_function("sqrt", |params: Vec<Expression>| {
|
||||||
"acos" => map_decimal(¶ms[0], |d| map_f64(&d, |f| f.acos())),
|
map_decimal(¶ms[0], |d| d.abs().sqrt().unwrap())
|
||||||
"atan" => map_decimal(¶ms[0], |d| map_f64(&d, |f| f.atan())),
|
});
|
||||||
"abs" => map_decimal(¶ms[0], |d| d.abs()),
|
self.register_function("sin", |params: Vec<Expression>| {
|
||||||
"sqrt" => map_decimal(¶ms[0], |d| d.abs().sqrt().expect("impossible")),
|
map_decimal(¶ms[0], |d| d.sin())
|
||||||
_ => panic!("unknown function"),
|
});
|
||||||
|
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 tests;
|
||||||
|
|
||||||
mod constants;
|
mod constants;
|
||||||
|
mod context;
|
||||||
mod display;
|
mod display;
|
||||||
mod eval;
|
mod eval;
|
||||||
mod functions;
|
mod functions;
|
||||||
@@ -12,12 +13,4 @@ mod parser;
|
|||||||
|
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
pub fn parse(source: &str) -> Expression {
|
pub use context::Context;
|
||||||
let lexer = lexer::from(source);
|
|
||||||
let mut parser = parser::from(lexer.collect());
|
|
||||||
parser.parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eval(expr: Expression) -> Expression {
|
|
||||||
eval::eval_expr(expr)
|
|
||||||
}
|
|
||||||
+13
-7
@@ -1,26 +1,29 @@
|
|||||||
use rust_decimal::{Decimal, dec};
|
use rust_decimal::{Decimal, dec};
|
||||||
|
|
||||||
use crate::math::{
|
use crate::math::{
|
||||||
eval::eval_expr,
|
self,
|
||||||
types::{Expression, Operator},
|
types::{Expression, Operator},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_eval_expr_number() {
|
fn test_eval_expr_number() {
|
||||||
let expr = Expression::Number(dec!(1));
|
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]
|
#[test]
|
||||||
fn test_eval_expr_variable() {
|
fn test_eval_expr_variable() {
|
||||||
let expr = Expression::Variable("x".to_owned());
|
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]
|
#[test]
|
||||||
fn test_eval_expr_variable_constant() {
|
fn test_eval_expr_variable_constant() {
|
||||||
let expr = Expression::Variable("pi".to_owned());
|
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]
|
#[test]
|
||||||
@@ -29,13 +32,15 @@ fn test_eval_expr_fraction() {
|
|||||||
Box::new(Expression::Number(dec!(1))),
|
Box::new(Expression::Number(dec!(1))),
|
||||||
Box::new(Expression::Number(dec!(2))),
|
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]
|
#[test]
|
||||||
fn test_eval_expr_function() {
|
fn test_eval_expr_function() {
|
||||||
let expr = Expression::Function("sqrt".to_owned(), vec![Expression::Number(dec!(4))]);
|
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]
|
#[test]
|
||||||
@@ -45,5 +50,6 @@ fn test_eval_operation() {
|
|||||||
Box::new(Expression::Number(dec!(2))),
|
Box::new(Expression::Number(dec!(2))),
|
||||||
Box::new(Expression::Number(dec!(3))),
|
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;
|
use rust_decimal::Decimal;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum Expression {
|
pub enum Expression {
|
||||||
Number(Decimal),
|
Number(Decimal),
|
||||||
Fraction(Box<Expression>, Box<Expression>),
|
Fraction(Box<Expression>, Box<Expression>),
|
||||||
|
|||||||
Reference in new issue
Block a user