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:
ozzy committed 2026-09-28 19:32:31 -05:00
1 parent 1b3ae22707
commit 143a1ee22f
8 files changed
+128 -55

No files matched your search

+9 -6
View File
@@ -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()?;
}
+6 -5
View File
@@ -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));
}
}
+57
View File
@@ -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
View File
@@ -1,39 +1,32 @@
use rust_decimal::MathematicalOps;
use crate::math::{
constants, functions,
context::Context,
types::{Expression, Operator},
};
pub fn eval_expr(expr: Expression) -> Expression {
impl Context {
pub fn eval_expr(&self, expr: Expression) -> Expression {
match expr {
Expression::Number(_) => expr,
Expression::Variable(name) => {
if let Some(value) = constants::get(&name) {
Expression::Number(value)
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(
self.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)
}
}
}
fn eval_operation(operator: Operator, left: Expression, right: Expression) -> Expression {
let left = eval_expr(left);
let right = eval_expr(right);
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),
@@ -46,3 +39,7 @@ fn eval_operation(operator: Operator, left: Expression, right: Expression) -> Ex
_ => Expression::Operation(operator, Box::new(left), Box::new(right)),
}
}
Expression::Function(name, params) => self.apply_function(&name, params),
}
}
}
+27 -11
View File
@@ -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(&params[0], |d| d.sin()),
"cos" => map_decimal(&params[0], |d| d.cos()),
"tan" => map_decimal(&params[0], |d| d.tan()),
"asin" => map_decimal(&params[0], |d| map_f64(&d, |f| f.asin())),
"acos" => map_decimal(&params[0], |d| map_f64(&d, |f| f.acos())),
"atan" => map_decimal(&params[0], |d| map_f64(&d, |f| f.atan())),
"abs" => map_decimal(&params[0], |d| d.abs()),
"sqrt" => map_decimal(&params[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(&params[0], |d| d.abs())
});
self.register_function("sqrt", |params: Vec<Expression>| {
map_decimal(&params[0], |d| d.abs().sqrt().unwrap())
});
self.register_function("sin", |params: Vec<Expression>| {
map_decimal(&params[0], |d| d.sin())
});
self.register_function("cos", |params: Vec<Expression>| {
map_decimal(&params[0], |d| d.cos())
});
self.register_function("tan", |params: Vec<Expression>| {
map_decimal(&params[0], |d| d.tan())
});
self.register_function("asin", |params: Vec<Expression>| {
map_decimal(&params[0], |d| map_f64(&d, |f| f.asin()))
});
self.register_function("acos", |params: Vec<Expression>| {
map_decimal(&params[0], |d| map_f64(&d, |f| f.acos()))
});
self.register_function("atan", |params: Vec<Expression>| {
map_decimal(&params[0], |d| map_f64(&d, |f| f.atan()))
});
}
}
+2 -9
View File
@@ -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
View File
@@ -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
View File
@@ -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>),