From c4b82ff65045d410f73f127bc7c5e09a3c1d7bba Mon Sep 17 00:00:00 2001 From: Rushmore75 Date: Wed, 12 Nov 2025 10:40:18 -0700 Subject: [PATCH] start on #13 --- src/app/logic/calc.rs | 75 +++++++++++++++++++++++++++++++++++ src/app/logic/ctx.rs | 92 +++++++++++++++++++++++++++++++++---------- 2 files changed, 147 insertions(+), 20 deletions(-) diff --git a/src/app/logic/calc.rs b/src/app/logic/calc.rs index 80aa5cd..7e17aba 100644 --- a/src/app/logic/calc.rs +++ b/src/app/logic/calc.rs @@ -148,6 +148,10 @@ impl Grid { // panic!("Will not be able to parse this equation, cell {e} not found") return None } + EvalexprError::TypeError { expected: _, actual: _ } => { + // IE: You put a string into a function that wants a float + return None + } _ => panic!("{}", e), }, } @@ -431,3 +435,74 @@ fn grid_max() { assert_eq!(mx, 1); assert_eq!(my, 5); } + +#[test] +fn avg_function() { + let mut grid = Grid::new(); + + grid.set_cell("A0", "=avg(5)".to_string()); + let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); + let res = grid.evaluate(&cell.to_string()); + assert!(res.is_some()); + assert_eq!(res.unwrap(), 5.); + + grid.set_cell("A0", "=avg(5,10)".to_string()); + let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); + let res = grid.evaluate(&cell.to_string()); + assert!(res.is_some()); + assert_eq!(res.unwrap(), 7.5); + + grid.set_cell("A0", "=avg(5,10,15)".to_string()); + let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); + let res = grid.evaluate(&cell.to_string()); + assert!(res.is_some()); + assert_eq!(res.unwrap(), 10.); + + grid.set_cell("A0", "=avg(foo)".to_string()); + let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); + let res = grid.evaluate(&cell.to_string()); + assert!(res.is_none()); + + grid.set_cell("A0", "=avg(1, foo)".to_string()); + let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); + let res = grid.evaluate(&cell.to_string()); + assert!(res.is_none()); + + grid.set_cell("A0", "=avg()".to_string()); + let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); + let res = grid.evaluate(&cell.to_string()); + assert!(res.is_none()); +} + +#[test] +fn sum_function() { + let mut grid = Grid::new(); + + grid.set_cell("A0", "=sum(5)".to_string()); + let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); + let res = grid.evaluate(&cell.to_string()); + assert!(res.is_some()); + assert_eq!(res.unwrap(), 5.); + + grid.set_cell("A0", "=sum(5,10)".to_string()); + let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); + let res = grid.evaluate(&cell.to_string()); + assert!(res.is_some()); + assert_eq!(res.unwrap(), 15.); + + grid.set_cell("A0", "=sum(foo)".to_string()); + let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); + let res = grid.evaluate(&cell.to_string()); + assert!(res.is_none()); + + grid.set_cell("A0", "=sum(1, foo)".to_string()); + let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); + let res = grid.evaluate(&cell.to_string()); + assert!(res.is_none()); + + grid.set_cell("A0", "=sum()".to_string()); + let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); + let res = grid.evaluate(&cell.to_string()); + assert!(res.is_none()); +} + diff --git a/src/app/logic/ctx.rs b/src/app/logic/ctx.rs index e0b6cf0..fbc0020 100644 --- a/src/app/logic/ctx.rs +++ b/src/app/logic/ctx.rs @@ -1,24 +1,76 @@ -use std::{collections::HashMap, sync::RwLock}; +use std::{collections::HashMap, process::id, sync::RwLock}; use evalexpr::{error::EvalexprResultValue, *}; use crate::app::logic::calc::Grid; -pub struct CallbackContext<'a, T: EvalexprNumericTypes = DefaultNumericTypes> { +pub struct CallbackContext<'a> { variables: &'a Grid, eval_depth: RwLock, - functions: HashMap>, + functions: HashMap>, /// True if builtin functions are disabled. without_builtin_functions: bool, } -impl<'a, NumericTypes: EvalexprNumericTypes> CallbackContext<'a, NumericTypes> { +impl<'a> CallbackContext<'a> { + fn get_functions() -> HashMap> { + let mut functions = HashMap::new(); + + functions.insert( + "avg".to_string(), + Function::new(|arg| { + if arg.is_tuple() { + let args = arg.as_tuple()?; + let mut total: f64 = 0.; + let mut count = 0f64; + for i in args { + total += i.as_number()?; + count += 1.; + } + let res = total / count; + Ok(Value::Float(res)) + } else if arg.is_float() { + Ok(Value::Float(arg.as_float()?)) + } else { + Err(EvalexprError::TypeError { + expected: vec![ValueType::Float], + actual: arg.clone(), + }) + } + }), + ); + + functions.insert( + "sum".to_string(), + Function::new(|arg| { + if arg.is_tuple() { + let args = arg.as_tuple()?; + let mut total: f64 = 0.; + for i in args { + total += i.as_number()?; + } + let res = total; + Ok(Value::Float(res)) + } else if arg.is_float() { + Ok(Value::Float(arg.as_float()?)) + } else { + Err(EvalexprError::TypeError { + expected: vec![ValueType::Float], + actual: arg.clone(), + }) + } + }), + ); + + functions + } + pub fn new(grid: &'a Grid) -> Self { Self { eval_depth: RwLock::new(0), variables: grid, - functions: Default::default(), + functions: Self::get_functions(), without_builtin_functions: false, } } @@ -37,12 +89,11 @@ impl<'a, NumericTypes: EvalexprNumericTypes> CallbackContext<'a, NumericTypes> { } } -impl<'a> Context for CallbackContext<'a, DefaultNumericTypes> { +impl<'a> Context for CallbackContext<'a> { type NumericTypes = DefaultNumericTypes; fn get_value(&self, identifier: &str) -> Option> { if let Some(v) = self.variables.get_cell(identifier) { - match v { super::calc::CellType::Number(n) => return Some(Value::Float(n.to_owned())), super::calc::CellType::String(_) => return None, @@ -52,12 +103,12 @@ impl<'a> Context for CallbackContext<'a, DefaultNumericTypes> { } if let Ok(depth) = self.eval_depth.read() { if *depth > 10 { - return None - } + return None; + } } else { // It would be unsafe to continue to process without knowing how // deep we've gone. - return None + return None; } match eval_with_context(&eq[1..], self) { Ok(e) => return Some(e), @@ -67,14 +118,14 @@ impl<'a> Context for CallbackContext<'a, DefaultNumericTypes> { // If the variable isn't found, that's ~~probably~~ because // of recursive reference, considering all references // are grabbed straight from the table. - return None - }, + return None; + } - e => panic!("> Error {e}\n> Equation: '{eq}'"), + e => panic!("> Error {e}\n> Equation: '{eq}'"), } - }, + } } - }, + } } } return None; @@ -85,17 +136,18 @@ impl<'a> Context for CallbackContext<'a, DefaultNumericTypes> { identifier: &str, argument: &Value, ) -> EvalexprResultValue { - todo!() + if let Some(function) = self.functions.get(identifier) { + function.call(argument) + } else { + Err(EvalexprError::FunctionIdentifierNotFound(identifier.to_string())) + } } fn are_builtin_functions_disabled(&self) -> bool { self.without_builtin_functions } - fn set_builtin_functions_disabled( - &mut self, - disabled: bool, - ) -> EvalexprResult<(), Self::NumericTypes> { + fn set_builtin_functions_disabled(&mut self, disabled: bool) -> EvalexprResult<(), Self::NumericTypes> { self.without_builtin_functions = disabled; Ok(()) }