Compare commits

..
2 Commits
Author SHA1 Message Date
ozzy 52bae6b034 fix(eval): allow expressions in functions
added:
- pass context to function calls
fixed:
- functions not evaluating expressions inside their params
2026-09-28 20:32:43 -05:00
ozzy 48a9837bef feat(eval): add rounding functions
added:
- `round(num, dec)` -> rounds `num` to `dec` amount of decimal places with `RoundingStrategy::MidpointAwayFromZero`
- `floor(num)` -> floors `num` to the nearest integer
- `ceil(num)` -> ceils `num` to the nearest integer
fixed:
- use proper function types instead of function pointers for the util functions in the builtin function registry
2026-09-28 19:51:56 -05:00
3 changed files with 67 additions and 23 deletions

No files matched your search

+3 -3
View File
@@ -2,7 +2,7 @@ use std::collections::HashMap;
use crate::math::{lexer, parser, types::Expression};
type FunctionCallback = Box<dyn Fn(Vec<Expression>) -> Expression>;
type FunctionCallback = Box<dyn Fn(&Context, Vec<Expression>) -> Expression>;
pub struct Context {
variables: HashMap<String, Expression>,
@@ -26,7 +26,7 @@ impl Context {
pub fn register_function<F>(&mut self, name: &str, callback: F)
where
F: Fn(Vec<Expression>) -> Expression + 'static,
F: Fn(&Context, Vec<Expression>) -> Expression + 'static,
{
self.functions.insert(name.to_owned(), Box::new(callback));
}
@@ -38,7 +38,7 @@ impl Context {
}
let func = func.unwrap();
func(params)
func(self, params)
}
pub fn get_variable(&self, name: &str) -> Option<&Expression> {
+64 -18
View File
@@ -2,45 +2,91 @@ use crate::math::context::Context;
use crate::math::types::Expression;
use rust_decimal::Decimal;
use rust_decimal::MathematicalOps;
use rust_decimal::RoundingStrategy;
use rust_decimal::dec;
use rust_decimal::prelude::ToPrimitive;
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("abs", |ctx, params: Vec<Expression>| {
map_decimal(&ctx.eval_expr(params[0].clone()), |d| d.abs())
});
self.register_function("sqrt", |params: Vec<Expression>| {
map_decimal(&params[0], |d| d.abs().sqrt().unwrap())
self.register_function("sqrt", |ctx, params: Vec<Expression>| {
map_decimal(&ctx.eval_expr(params[0].clone()), |d| {
d.abs().sqrt().unwrap()
})
});
self.register_function("sin", |params: Vec<Expression>| {
map_decimal(&params[0], |d| d.sin())
self.register_function("sin", |ctx, params: Vec<Expression>| {
map_decimal(&ctx.eval_expr(params[0].clone()), |d| d.sin())
});
self.register_function("cos", |params: Vec<Expression>| {
map_decimal(&params[0], |d| d.cos())
self.register_function("cos", |ctx, params: Vec<Expression>| {
map_decimal(&ctx.eval_expr(params[0].clone()), |d| d.cos())
});
self.register_function("tan", |params: Vec<Expression>| {
map_decimal(&params[0], |d| d.tan())
self.register_function("tan", |ctx, params: Vec<Expression>| {
map_decimal(&ctx.eval_expr(params[0].clone()), |d| d.tan())
});
self.register_function("asin", |params: Vec<Expression>| {
map_decimal(&params[0], |d| map_f64(&d, |f| f.asin()))
self.register_function("asin", |ctx, params: Vec<Expression>| {
map_decimal(&ctx.eval_expr(params[0].clone()), |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("acos", |ctx, params: Vec<Expression>| {
map_decimal(&ctx.eval_expr(params[0].clone()), |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()))
self.register_function("atan", |ctx, params: Vec<Expression>| {
map_decimal(&ctx.eval_expr(params[0].clone()), |d| {
map_f64(&d, |f| f.atan())
})
});
self.register_function("round", |ctx, params: Vec<Expression>| {
let decimal = if params.len() == 2 {
if let Some(d) = get_decimal(&params[1]) {
d
} else {
dec!(0)
}
} else {
dec!(0)
}
.to_u32()
.expect("unable to parse decimal number");
map_decimal(&ctx.eval_expr(params[0].clone()), |d| {
d.round_dp_with_strategy(decimal, RoundingStrategy::MidpointAwayFromZero)
})
});
self.register_function("floor", |ctx, params: Vec<Expression>| {
map_decimal(&ctx.eval_expr(params[0].clone()), |d| d.floor())
});
self.register_function("ceil", |ctx, params: Vec<Expression>| {
map_decimal(&ctx.eval_expr(params[0].clone()), |d| d.ceil())
});
}
}
fn map_decimal(expr: &Expression, f: fn(Decimal) -> Decimal) -> Expression {
fn get_decimal(expr: &Expression) -> Option<Decimal> {
match expr {
Expression::Number(d) => Some(*d),
_ => None,
}
}
fn map_decimal<F>(expr: &Expression, f: F) -> Expression
where
F: Fn(Decimal) -> Decimal,
{
match expr {
Expression::Number(d) => Expression::Number(f(*d)),
_ => panic!("expected decimal"),
}
}
fn map_f64(decimal: &Decimal, f: fn(f64) -> f64) -> Decimal {
fn map_f64<F>(decimal: &Decimal, f: F) -> Decimal
where
F: Fn(f64) -> f64,
{
let num = decimal.to_f64().expect("unable to unwrap decimal");
let num = f(num);
Decimal::from_f64_retain(num).expect("unable to wrap decimal")
-2
View File
@@ -1,5 +1,3 @@
use crate::math::types::Expression;
#[cfg(test)]
mod tests;