use crate::error::PdfError;
use std::collections::{HashMap, HashSet, VecDeque};
use std::fmt;
#[derive(Debug, Clone)]
pub struct CalculationEngine {
field_values: HashMap<String, FieldValue>,
calculations: HashMap<String, Calculation>,
dependencies: HashMap<String, HashSet<String>>,
calculation_order: Vec<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum FieldValue {
Number(f64),
Text(String),
Boolean(bool),
Empty,
}
impl FieldValue {
pub fn to_number(&self) -> f64 {
match self {
FieldValue::Number(n) => *n,
FieldValue::Text(s) => s.parse::<f64>().unwrap_or(0.0),
FieldValue::Boolean(b) => {
if *b {
1.0
} else {
0.0
}
}
FieldValue::Empty => 0.0,
}
}
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
match self {
FieldValue::Number(n) => {
if n.fract() == 0.0 {
format!("{:.0}", n)
} else {
format!("{:.2}", n)
}
}
FieldValue::Text(s) => s.clone(),
FieldValue::Boolean(b) => b.to_string(),
FieldValue::Empty => String::new(),
}
}
}
#[derive(Debug, Clone)]
pub enum Calculation {
Arithmetic(ArithmeticExpression),
Function(CalculationFunction),
JavaScript(String),
Constant(FieldValue),
}
#[derive(Debug, Clone)]
pub struct ArithmeticExpression {
tokens: Vec<ExpressionToken>,
}
#[derive(Debug, Clone)]
pub enum ExpressionToken {
Field(String),
Number(f64),
Operator(Operator),
LeftParen,
RightParen,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Operator {
Add,
Subtract,
Multiply,
Divide,
Modulo,
Power,
}
impl Operator {
pub fn precedence(&self) -> i32 {
match self {
Operator::Power => 3,
Operator::Multiply | Operator::Divide | Operator::Modulo => 2,
Operator::Add | Operator::Subtract => 1,
}
}
pub fn apply(&self, left: f64, right: f64) -> f64 {
match self {
Operator::Add => left + right,
Operator::Subtract => left - right,
Operator::Multiply => left * right,
Operator::Divide => {
if right != 0.0 {
left / right
} else {
f64::INFINITY }
}
Operator::Modulo => {
if right != 0.0 {
left % right
} else {
0.0
}
}
Operator::Power => left.powf(right),
}
}
}
#[derive(Debug, Clone)]
pub enum CalculationFunction {
Sum(Vec<String>),
Average(Vec<String>),
Min(Vec<String>),
Max(Vec<String>),
Product(Vec<String>),
Count(Vec<String>),
If {
condition_field: String,
true_value: Box<Calculation>,
false_value: Box<Calculation>,
},
}
#[allow(clippy::derivable_impls)]
impl Default for CalculationEngine {
fn default() -> Self {
Self {
field_values: HashMap::new(),
calculations: HashMap::new(),
dependencies: HashMap::new(),
calculation_order: Vec::new(),
}
}
}
impl CalculationEngine {
pub fn new() -> Self {
Self::default()
}
pub fn set_field_value(&mut self, field_name: impl Into<String>, value: FieldValue) {
let field_name = field_name.into();
self.field_values.insert(field_name.clone(), value);
self.recalculate_dependents(&field_name);
}
pub fn get_field_value(&self, field_name: &str) -> Option<&FieldValue> {
self.field_values.get(field_name)
}
pub fn add_calculation(
&mut self,
field_name: impl Into<String>,
calculation: Calculation,
) -> Result<(), PdfError> {
let field_name = field_name.into();
let deps = self.extract_dependencies(&calculation);
if self.would_create_cycle(&field_name, &deps) {
return Err(PdfError::InvalidStructure(format!(
"Circular dependency detected for field '{}'",
field_name
)));
}
for dep in &deps {
self.dependencies
.entry(dep.clone())
.or_default()
.insert(field_name.clone());
}
self.calculations.insert(field_name.clone(), calculation);
self.update_calculation_order()?;
self.calculate_field(&field_name)?;
Ok(())
}
#[allow(clippy::only_used_in_recursion)]
fn extract_dependencies(&self, calculation: &Calculation) -> HashSet<String> {
let mut deps = HashSet::new();
match calculation {
Calculation::Arithmetic(expr) => {
for token in &expr.tokens {
if let ExpressionToken::Field(field_name) = token {
deps.insert(field_name.clone());
}
}
}
Calculation::Function(func) => match func {
CalculationFunction::Sum(fields)
| CalculationFunction::Average(fields)
| CalculationFunction::Min(fields)
| CalculationFunction::Max(fields)
| CalculationFunction::Product(fields)
| CalculationFunction::Count(fields) => {
deps.extend(fields.iter().cloned());
}
CalculationFunction::If {
condition_field,
true_value,
false_value,
} => {
deps.insert(condition_field.clone());
deps.extend(self.extract_dependencies(true_value));
deps.extend(self.extract_dependencies(false_value));
}
},
Calculation::JavaScript(_) => {
}
Calculation::Constant(_) => {
}
}
deps
}
fn would_create_cycle(&self, field: &str, new_deps: &HashSet<String>) -> bool {
for dep in new_deps {
if dep == field {
return true; }
if self.depends_on(dep, field) {
return true;
}
}
false
}
fn depends_on(&self, field_a: &str, field_b: &str) -> bool {
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
queue.push_back(field_a.to_string());
while let Some(current) = queue.pop_front() {
if current == field_b {
return true;
}
if visited.contains(¤t) {
continue;
}
visited.insert(current.clone());
if let Some(calc) = self.calculations.get(¤t) {
let deps = self.extract_dependencies(calc);
for dep in deps {
queue.push_back(dep);
}
}
}
false
}
fn update_calculation_order(&mut self) -> Result<(), PdfError> {
let mut order = Vec::new();
let mut visited = HashSet::new();
let mut visiting = HashSet::new();
for field in self.calculations.keys() {
if !visited.contains(field) {
self.topological_sort(field, &mut visited, &mut visiting, &mut order)?;
}
}
self.calculation_order = order;
Ok(())
}
fn topological_sort(
&self,
field: &str,
visited: &mut HashSet<String>,
visiting: &mut HashSet<String>,
order: &mut Vec<String>,
) -> Result<(), PdfError> {
if visiting.contains(field) {
return Err(PdfError::InvalidStructure(
"Circular dependency detected".to_string(),
));
}
if visited.contains(field) {
return Ok(());
}
visiting.insert(field.to_string());
if let Some(calc) = self.calculations.get(field) {
let deps = self.extract_dependencies(calc);
for dep in deps {
if self.calculations.contains_key(&dep) {
self.topological_sort(&dep, visited, visiting, order)?;
}
}
}
visiting.remove(field);
visited.insert(field.to_string());
order.push(field.to_string());
Ok(())
}
fn recalculate_dependents(&mut self, changed_field: &str) {
let _ = self.update_calculation_order();
let mut fields_to_recalc = HashSet::new();
if let Some(dependents) = self.dependencies.get(changed_field) {
fields_to_recalc.extend(dependents.clone());
}
let calc_order = self.calculation_order.clone();
for field in calc_order {
if fields_to_recalc.contains(&field) {
let _ = self.calculate_field(&field);
if let Some(deps) = self.dependencies.get(&field).cloned() {
fields_to_recalc.extend(deps);
}
}
}
}
pub fn calculate_field(&mut self, field_name: &str) -> Result<(), PdfError> {
if let Some(calculation) = self.calculations.get(field_name).cloned() {
let value = self.evaluate_calculation(&calculation)?;
self.field_values.insert(field_name.to_string(), value);
}
Ok(())
}
fn evaluate_calculation(&self, calculation: &Calculation) -> Result<FieldValue, PdfError> {
match calculation {
Calculation::Arithmetic(expr) => {
let result = self.evaluate_expression(expr)?;
Ok(FieldValue::Number(result))
}
Calculation::Function(func) => self.evaluate_function(func),
Calculation::JavaScript(code) => {
self.evaluate_javascript(code)
}
Calculation::Constant(value) => Ok(value.clone()),
}
}
fn evaluate_expression(&self, expr: &ArithmeticExpression) -> Result<f64, PdfError> {
let postfix = self.infix_to_postfix(&expr.tokens)?;
let mut stack = Vec::new();
for token in postfix {
match token {
ExpressionToken::Number(n) => stack.push(n),
ExpressionToken::Field(field_name) => {
let value = self
.field_values
.get(&field_name)
.map(|v| v.to_number())
.unwrap_or(0.0);
stack.push(value);
}
ExpressionToken::Operator(op) => {
if stack.len() < 2 {
return Err(PdfError::InvalidStructure("Invalid expression".to_string()));
}
let right = stack.pop().unwrap_or(0.0);
let left = stack.pop().unwrap_or(0.0);
stack.push(op.apply(left, right));
}
_ => {}
}
}
stack
.pop()
.ok_or_else(|| PdfError::InvalidStructure("Invalid expression".to_string()))
}
fn infix_to_postfix(
&self,
tokens: &[ExpressionToken],
) -> Result<Vec<ExpressionToken>, PdfError> {
let mut output = Vec::new();
let mut operators = Vec::new();
for token in tokens {
match token {
ExpressionToken::Number(_) | ExpressionToken::Field(_) => {
output.push(token.clone());
}
ExpressionToken::Operator(op) => {
while let Some(ExpressionToken::Operator(top_op)) = operators.last() {
if top_op.precedence() >= op.precedence() {
if let Some(operator) = operators.pop() {
output.push(operator);
}
} else {
break;
}
}
operators.push(token.clone());
}
ExpressionToken::LeftParen => {
operators.push(token.clone());
}
ExpressionToken::RightParen => {
while let Some(op) = operators.pop() {
if matches!(op, ExpressionToken::LeftParen) {
break;
}
output.push(op);
}
}
}
}
while let Some(op) = operators.pop() {
output.push(op);
}
Ok(output)
}
fn evaluate_function(&self, func: &CalculationFunction) -> Result<FieldValue, PdfError> {
match func {
CalculationFunction::Sum(fields) => {
let sum = fields
.iter()
.filter_map(|f| self.field_values.get(f))
.map(|v| v.to_number())
.sum();
Ok(FieldValue::Number(sum))
}
CalculationFunction::Average(fields) => {
let values: Vec<f64> = fields
.iter()
.filter_map(|f| self.field_values.get(f))
.map(|v| v.to_number())
.collect();
if values.is_empty() {
Ok(FieldValue::Number(0.0))
} else {
let avg = values.iter().sum::<f64>() / values.len() as f64;
Ok(FieldValue::Number(avg))
}
}
CalculationFunction::Min(fields) => {
let min = fields
.iter()
.filter_map(|f| self.field_values.get(f))
.map(|v| v.to_number())
.filter(|n| !n.is_nan()) .min_by(|a, b| a.total_cmp(b))
.unwrap_or(0.0);
Ok(FieldValue::Number(min))
}
CalculationFunction::Max(fields) => {
let max = fields
.iter()
.filter_map(|f| self.field_values.get(f))
.map(|v| v.to_number())
.filter(|n| !n.is_nan()) .max_by(|a, b| a.total_cmp(b))
.unwrap_or(0.0);
Ok(FieldValue::Number(max))
}
CalculationFunction::Product(fields) => {
let product = fields
.iter()
.filter_map(|f| self.field_values.get(f))
.map(|v| v.to_number())
.product();
Ok(FieldValue::Number(product))
}
CalculationFunction::Count(fields) => {
let count = fields
.iter()
.filter_map(|f| self.field_values.get(f))
.filter(|v| !matches!(v, FieldValue::Empty))
.count() as f64;
Ok(FieldValue::Number(count))
}
CalculationFunction::If {
condition_field,
true_value,
false_value,
} => {
let condition = self
.field_values
.get(condition_field)
.map(|v| match v {
FieldValue::Boolean(b) => *b,
FieldValue::Number(n) => *n != 0.0,
FieldValue::Text(s) => !s.is_empty(),
FieldValue::Empty => false,
})
.unwrap_or(false);
if condition {
self.evaluate_calculation(true_value)
} else {
self.evaluate_calculation(false_value)
}
}
}
}
fn evaluate_javascript(&self, _code: &str) -> Result<FieldValue, PdfError> {
Ok(FieldValue::Empty)
}
pub fn recalculate_all(&mut self) -> Result<(), PdfError> {
for field in self.calculation_order.clone() {
self.calculate_field(&field)?;
}
Ok(())
}
pub fn remove_calculation(&mut self, field_name: &str) {
if self.calculations.remove(field_name).is_some() {
self.calculation_order.retain(|f| f != field_name);
self.dependencies.values_mut().for_each(|deps| {
deps.remove(field_name);
});
self.dependencies.remove(field_name);
self.field_values.remove(field_name);
}
}
pub fn get_summary(&self) -> CalculationSummary {
CalculationSummary {
total_fields: self.field_values.len(),
calculated_fields: self.calculations.len(),
dependencies: self.dependencies.len(),
calculation_order: self.calculation_order.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct CalculationSummary {
pub total_fields: usize,
pub calculated_fields: usize,
pub dependencies: usize,
pub calculation_order: Vec<String>,
}
impl fmt::Display for CalculationSummary {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Calculation Summary:\n\
- Total fields: {}\n\
- Calculated fields: {}\n\
- Dependencies: {}\n\
- Calculation order: {}",
self.total_fields,
self.calculated_fields,
self.dependencies,
self.calculation_order.join(" -> ")
)
}
}
impl ArithmeticExpression {
pub fn from_string(expr: &str) -> Result<Self, PdfError> {
let tokens = Self::tokenize(expr)?;
Ok(Self { tokens })
}
fn tokenize(expr: &str) -> Result<Vec<ExpressionToken>, PdfError> {
let mut tokens = Vec::new();
let mut chars = expr.chars().peekable();
if expr.trim().is_empty() {
return Err(PdfError::InvalidFormat("Empty expression".to_string()));
}
while let Some(ch) = chars.next() {
match ch {
' ' | '\t' | '\n' => continue,
'+' => tokens.push(ExpressionToken::Operator(Operator::Add)),
'-' => tokens.push(ExpressionToken::Operator(Operator::Subtract)),
'*' => tokens.push(ExpressionToken::Operator(Operator::Multiply)),
'/' => tokens.push(ExpressionToken::Operator(Operator::Divide)),
'%' => tokens.push(ExpressionToken::Operator(Operator::Modulo)),
'^' => tokens.push(ExpressionToken::Operator(Operator::Power)),
'(' => tokens.push(ExpressionToken::LeftParen),
')' => tokens.push(ExpressionToken::RightParen),
'0'..='9' | '.' => {
let mut num_str = String::new();
num_str.push(ch);
while let Some(&next_ch) = chars.peek() {
if next_ch.is_ascii_digit() || next_ch == '.' {
if let Some(consumed_ch) = chars.next() {
num_str.push(consumed_ch);
} else {
break; }
} else {
break;
}
}
let num = num_str
.parse::<f64>()
.map_err(|_| PdfError::InvalidFormat("Invalid number".to_string()))?;
tokens.push(ExpressionToken::Number(num));
}
'a'..='z' | 'A'..='Z' | '_' => {
let mut field_name = String::new();
field_name.push(ch);
while let Some(&next_ch) = chars.peek() {
if next_ch.is_alphanumeric() || next_ch == '_' {
if let Some(consumed_ch) = chars.next() {
field_name.push(consumed_ch);
} else {
break; }
} else {
break;
}
}
tokens.push(ExpressionToken::Field(field_name));
}
_ => {
return Err(PdfError::InvalidFormat(format!(
"Invalid character in expression: '{}'",
ch
)));
}
}
}
Self::validate_tokens(&tokens)?;
Ok(tokens)
}
fn validate_tokens(tokens: &[ExpressionToken]) -> Result<(), PdfError> {
if tokens.is_empty() {
return Err(PdfError::InvalidFormat("Empty expression".to_string()));
}
let mut paren_count = 0;
let mut last_was_operator = true;
for token in tokens.iter() {
match token {
ExpressionToken::LeftParen => {
paren_count += 1;
last_was_operator = true; }
ExpressionToken::RightParen => {
paren_count -= 1;
if paren_count < 0 {
return Err(PdfError::InvalidFormat(
"Unbalanced parentheses".to_string(),
));
}
last_was_operator = false;
}
ExpressionToken::Operator(_) => {
if last_was_operator {
return Err(PdfError::InvalidFormat(
"Invalid operator sequence".to_string(),
));
}
last_was_operator = true;
}
ExpressionToken::Number(_) | ExpressionToken::Field(_) => {
last_was_operator = false;
}
}
}
if paren_count != 0 {
return Err(PdfError::InvalidFormat(
"Unbalanced parentheses".to_string(),
));
}
if last_was_operator {
return Err(PdfError::InvalidFormat(
"Expression ends with operator".to_string(),
));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_field_value_conversion() {
assert_eq!(FieldValue::Number(42.5).to_number(), 42.5);
assert_eq!(FieldValue::Text("123".to_string()).to_number(), 123.0);
assert_eq!(FieldValue::Boolean(true).to_number(), 1.0);
assert_eq!(FieldValue::Empty.to_number(), 0.0);
}
#[test]
fn test_arithmetic_expression() {
let expr = ArithmeticExpression::from_string("2 + 3 * 4").unwrap();
assert_eq!(expr.tokens.len(), 5);
}
#[test]
fn test_calculation_engine() {
let mut engine = CalculationEngine::new();
engine.set_field_value("quantity", FieldValue::Number(5.0));
engine.set_field_value("price", FieldValue::Number(10.0));
let expr = ArithmeticExpression::from_string("quantity * price").unwrap();
engine
.add_calculation("total", Calculation::Arithmetic(expr))
.unwrap();
let total = engine.get_field_value("total").unwrap();
assert_eq!(total.to_number(), 50.0);
}
#[test]
fn test_sum_function() {
let mut engine = CalculationEngine::new();
engine.set_field_value("field1", FieldValue::Number(10.0));
engine.set_field_value("field2", FieldValue::Number(20.0));
engine.set_field_value("field3", FieldValue::Number(30.0));
let calc = Calculation::Function(CalculationFunction::Sum(vec![
"field1".to_string(),
"field2".to_string(),
"field3".to_string(),
]));
engine.add_calculation("total", calc).unwrap();
let total = engine.get_field_value("total").unwrap();
assert_eq!(total.to_number(), 60.0);
}
#[test]
fn test_circular_dependency_detection() {
let mut engine = CalculationEngine::new();
let expr1 = ArithmeticExpression::from_string("fieldB + 1").unwrap();
engine
.add_calculation("fieldA", Calculation::Arithmetic(expr1))
.unwrap();
let expr2 = ArithmeticExpression::from_string("fieldA + 1").unwrap();
let result = engine.add_calculation("fieldB", Calculation::Arithmetic(expr2));
assert!(result.is_err());
}
#[test]
fn test_field_value_conversions() {
let num_val = FieldValue::Number(42.5);
assert_eq!(num_val.to_number(), 42.5);
assert_eq!(num_val.to_string(), "42.50");
let int_val = FieldValue::Number(100.0);
assert_eq!(int_val.to_string(), "100");
let text_val = FieldValue::Text("123.45".to_string());
assert_eq!(text_val.to_number(), 123.45);
assert_eq!(text_val.to_string(), "123.45");
let non_numeric_text = FieldValue::Text("hello".to_string());
assert_eq!(non_numeric_text.to_number(), 0.0);
let true_val = FieldValue::Boolean(true);
assert_eq!(true_val.to_number(), 1.0);
assert_eq!(true_val.to_string(), "true");
let false_val = FieldValue::Boolean(false);
assert_eq!(false_val.to_number(), 0.0);
assert_eq!(false_val.to_string(), "false");
let empty_val = FieldValue::Empty;
assert_eq!(empty_val.to_number(), 0.0);
assert_eq!(empty_val.to_string(), "");
}
#[test]
fn test_complex_arithmetic_expressions() {
let mut engine = CalculationEngine::new();
engine.set_field_value("a", FieldValue::Number(10.0));
engine.set_field_value("b", FieldValue::Number(5.0));
engine.set_field_value("c", FieldValue::Number(2.0));
let expr = ArithmeticExpression::from_string("(a + b) * c").unwrap();
engine
.add_calculation("result1", Calculation::Arithmetic(expr))
.unwrap();
let result = engine.get_field_value("result1").unwrap();
assert_eq!(result.to_number(), 30.0);
let expr2 = ArithmeticExpression::from_string("a + b - c * 2 / 4").unwrap();
engine
.add_calculation("result2", Calculation::Arithmetic(expr2))
.unwrap();
let result2 = engine.get_field_value("result2").unwrap();
assert_eq!(result2.to_number(), 14.0); }
#[test]
fn test_calculation_functions() {
let mut engine = CalculationEngine::new();
engine.set_field_value("val1", FieldValue::Number(100.0));
engine.set_field_value("val2", FieldValue::Number(50.0));
engine.set_field_value("val3", FieldValue::Number(25.0));
engine.set_field_value("val4", FieldValue::Number(75.0));
let avg_calc = Calculation::Function(CalculationFunction::Average(vec![
"val1".to_string(),
"val2".to_string(),
"val3".to_string(),
"val4".to_string(),
]));
engine.add_calculation("average", avg_calc).unwrap();
let avg = engine.get_field_value("average").unwrap();
assert_eq!(avg.to_number(), 62.5);
let min_calc = Calculation::Function(CalculationFunction::Min(vec![
"val1".to_string(),
"val2".to_string(),
"val3".to_string(),
"val4".to_string(),
]));
engine.add_calculation("minimum", min_calc).unwrap();
let min = engine.get_field_value("minimum").unwrap();
assert_eq!(min.to_number(), 25.0);
let max_calc = Calculation::Function(CalculationFunction::Max(vec![
"val1".to_string(),
"val2".to_string(),
"val3".to_string(),
"val4".to_string(),
]));
engine.add_calculation("maximum", max_calc).unwrap();
let max = engine.get_field_value("maximum").unwrap();
assert_eq!(max.to_number(), 100.0);
}
#[test]
fn test_calculation_order_dependencies() {
let mut engine = CalculationEngine::new();
engine.set_field_value("base", FieldValue::Number(10.0));
let expr1 = ArithmeticExpression::from_string("base * 2").unwrap();
engine
.add_calculation("level1", Calculation::Arithmetic(expr1))
.unwrap();
let expr2 = ArithmeticExpression::from_string("level1 + 5").unwrap();
engine
.add_calculation("level2", Calculation::Arithmetic(expr2))
.unwrap();
let expr3 = ArithmeticExpression::from_string("level2 / 5").unwrap();
engine
.add_calculation("level3", Calculation::Arithmetic(expr3))
.unwrap();
assert_eq!(engine.calculation_order.len(), 3);
assert_eq!(engine.calculation_order[0], "level1");
assert_eq!(engine.calculation_order[1], "level2");
assert_eq!(engine.calculation_order[2], "level3");
assert_eq!(engine.get_field_value("level1").unwrap().to_number(), 20.0);
assert_eq!(engine.get_field_value("level2").unwrap().to_number(), 25.0);
assert_eq!(engine.get_field_value("level3").unwrap().to_number(), 5.0);
}
#[test]
fn test_field_update_recalculation() {
let mut engine = CalculationEngine::new();
engine.set_field_value("price", FieldValue::Number(10.0));
engine.set_field_value("quantity", FieldValue::Number(5.0));
let expr = ArithmeticExpression::from_string("price * quantity").unwrap();
engine
.add_calculation("total", Calculation::Arithmetic(expr))
.unwrap();
assert_eq!(engine.get_field_value("total").unwrap().to_number(), 50.0);
engine.set_field_value("price", FieldValue::Number(15.0));
assert_eq!(engine.get_field_value("total").unwrap().to_number(), 75.0);
engine.set_field_value("quantity", FieldValue::Number(10.0));
assert_eq!(engine.get_field_value("total").unwrap().to_number(), 150.0);
}
#[test]
fn test_edge_cases_division_by_zero() {
let mut engine = CalculationEngine::new();
engine.set_field_value("numerator", FieldValue::Number(100.0));
engine.set_field_value("denominator", FieldValue::Number(0.0));
let expr = ArithmeticExpression::from_string("numerator / denominator").unwrap();
engine
.add_calculation("result", Calculation::Arithmetic(expr))
.unwrap();
let result = engine.get_field_value("result").unwrap();
assert!(result.to_number().is_infinite());
}
#[test]
fn test_mixed_value_types() {
let mut engine = CalculationEngine::new();
engine.set_field_value("num", FieldValue::Number(10.0));
engine.set_field_value("text_num", FieldValue::Text("20".to_string()));
engine.set_field_value("bool_val", FieldValue::Boolean(true));
engine.set_field_value("empty", FieldValue::Empty);
let calc = Calculation::Function(CalculationFunction::Sum(vec![
"num".to_string(),
"text_num".to_string(),
"bool_val".to_string(),
"empty".to_string(),
]));
engine.add_calculation("total", calc).unwrap();
let total = engine.get_field_value("total").unwrap();
assert_eq!(total.to_number(), 31.0); }
#[test]
fn test_constant_calculations() {
let mut engine = CalculationEngine::new();
engine
.add_calculation("pi", Calculation::Constant(FieldValue::Number(3.14159)))
.unwrap();
engine
.add_calculation(
"label",
Calculation::Constant(FieldValue::Text("Total:".to_string())),
)
.unwrap();
engine
.add_calculation("enabled", Calculation::Constant(FieldValue::Boolean(true)))
.unwrap();
assert_eq!(engine.get_field_value("pi").unwrap().to_number(), 3.14159);
assert_eq!(
engine.get_field_value("label").unwrap().to_string(),
"Total:"
);
assert_eq!(
*engine.get_field_value("enabled").unwrap(),
FieldValue::Boolean(true)
);
}
#[test]
fn test_expression_parsing_errors() {
assert!(ArithmeticExpression::from_string("").is_err());
assert!(ArithmeticExpression::from_string("(a + b").is_err()); assert!(ArithmeticExpression::from_string("a + + b").is_err()); assert!(ArithmeticExpression::from_string("* a + b").is_err()); assert!(ArithmeticExpression::from_string("a b +").is_err()); }
#[test]
fn test_multiple_dependencies() {
let mut engine = CalculationEngine::new();
engine.set_field_value("a", FieldValue::Number(5.0));
engine.set_field_value("b", FieldValue::Number(10.0));
let expr1 = ArithmeticExpression::from_string("a + b").unwrap();
engine
.add_calculation("c", Calculation::Arithmetic(expr1))
.unwrap();
let expr2 = ArithmeticExpression::from_string("a * 2").unwrap();
engine
.add_calculation("d", Calculation::Arithmetic(expr2))
.unwrap();
let expr3 = ArithmeticExpression::from_string("c + d").unwrap();
engine
.add_calculation("e", Calculation::Arithmetic(expr3))
.unwrap();
assert_eq!(engine.get_field_value("c").unwrap().to_number(), 15.0);
assert_eq!(engine.get_field_value("d").unwrap().to_number(), 10.0);
assert_eq!(engine.get_field_value("e").unwrap().to_number(), 25.0);
engine.set_field_value("a", FieldValue::Number(10.0));
assert_eq!(engine.get_field_value("c").unwrap().to_number(), 20.0);
assert_eq!(engine.get_field_value("d").unwrap().to_number(), 20.0);
assert_eq!(engine.get_field_value("e").unwrap().to_number(), 40.0);
}
#[test]
fn test_calculation_removal() {
let mut engine = CalculationEngine::new();
engine.set_field_value("x", FieldValue::Number(10.0));
let expr = ArithmeticExpression::from_string("x * 2").unwrap();
engine
.add_calculation("y", Calculation::Arithmetic(expr))
.unwrap();
assert_eq!(engine.get_field_value("y").unwrap().to_number(), 20.0);
engine.remove_calculation("y");
assert!(engine.get_field_value("y").is_none());
engine.set_field_value("y", FieldValue::Number(100.0));
assert_eq!(engine.get_field_value("y").unwrap().to_number(), 100.0);
}
#[test]
fn test_large_calculation_chain() {
let mut engine = CalculationEngine::new();
engine.set_field_value("f0", FieldValue::Number(1.0));
for i in 1..20 {
let prev = format!("f{}", i - 1);
let curr = format!("f{}", i);
let expr = ArithmeticExpression::from_string(&format!("{} + 1", prev)).unwrap();
engine
.add_calculation(&curr, Calculation::Arithmetic(expr))
.unwrap();
}
assert_eq!(engine.get_field_value("f19").unwrap().to_number(), 20.0);
engine.set_field_value("f0", FieldValue::Number(10.0));
assert_eq!(engine.get_field_value("f19").unwrap().to_number(), 29.0);
}
#[test]
fn test_operator_precedence() {
let mut engine = CalculationEngine::new();
engine.set_field_value("a", FieldValue::Number(2.0));
engine.set_field_value("b", FieldValue::Number(3.0));
engine.set_field_value("c", FieldValue::Number(4.0));
let expr = ArithmeticExpression::from_string("a + b * c").unwrap();
engine
.add_calculation("result", Calculation::Arithmetic(expr))
.unwrap();
assert_eq!(engine.get_field_value("result").unwrap().to_number(), 14.0);
let expr2 = ArithmeticExpression::from_string("(a + b) * c").unwrap();
engine
.add_calculation("result2", Calculation::Arithmetic(expr2))
.unwrap();
assert_eq!(engine.get_field_value("result2").unwrap().to_number(), 20.0);
}
#[test]
fn test_negative_numbers() {
let mut engine = CalculationEngine::new();
engine.set_field_value("positive", FieldValue::Number(10.0));
engine.set_field_value("negative", FieldValue::Number(-5.0));
let expr = ArithmeticExpression::from_string("positive + negative").unwrap();
engine
.add_calculation("result", Calculation::Arithmetic(expr))
.unwrap();
assert_eq!(engine.get_field_value("result").unwrap().to_number(), 5.0);
let expr2 = ArithmeticExpression::from_string("negative * negative").unwrap();
engine
.add_calculation("result2", Calculation::Arithmetic(expr2))
.unwrap();
assert_eq!(engine.get_field_value("result2").unwrap().to_number(), 25.0);
}
#[test]
fn test_floating_point_precision() {
let mut engine = CalculationEngine::new();
engine.set_field_value("a", FieldValue::Number(0.1));
engine.set_field_value("b", FieldValue::Number(0.2));
let expr = ArithmeticExpression::from_string("a + b").unwrap();
engine
.add_calculation("result", Calculation::Arithmetic(expr))
.unwrap();
let result = engine.get_field_value("result").unwrap().to_number();
assert!((result - 0.3).abs() < 0.0001);
}
#[test]
fn test_empty_field_references() {
let mut engine = CalculationEngine::new();
let expr = ArithmeticExpression::from_string("missing1 + missing2").unwrap();
engine
.add_calculation("result", Calculation::Arithmetic(expr))
.unwrap();
assert_eq!(engine.get_field_value("result").unwrap().to_number(), 0.0);
engine.set_field_value("missing1", FieldValue::Number(10.0));
assert_eq!(engine.get_field_value("result").unwrap().to_number(), 10.0);
}
#[test]
fn test_calculation_with_product_function() {
let mut engine = CalculationEngine::new();
engine.set_field_value("f1", FieldValue::Number(2.0));
engine.set_field_value("f2", FieldValue::Number(3.0));
engine.set_field_value("f3", FieldValue::Number(4.0));
engine.set_field_value("f4", FieldValue::Number(5.0));
let calc = Calculation::Function(CalculationFunction::Product(vec![
"f1".to_string(),
"f2".to_string(),
"f3".to_string(),
"f4".to_string(),
]));
engine.add_calculation("product", calc).unwrap();
let product = engine.get_field_value("product").unwrap();
assert_eq!(product.to_number(), 120.0); }
#[test]
fn test_complex_dependency_graph() {
let mut engine = CalculationEngine::new();
engine.set_field_value("a", FieldValue::Number(10.0));
let expr_b = ArithmeticExpression::from_string("a * 2").unwrap();
engine
.add_calculation("b", Calculation::Arithmetic(expr_b))
.unwrap();
let expr_c = ArithmeticExpression::from_string("a + 5").unwrap();
engine
.add_calculation("c", Calculation::Arithmetic(expr_c))
.unwrap();
let expr_d = ArithmeticExpression::from_string("b + c").unwrap();
engine
.add_calculation("d", Calculation::Arithmetic(expr_d))
.unwrap();
assert_eq!(engine.get_field_value("b").unwrap().to_number(), 20.0);
assert_eq!(engine.get_field_value("c").unwrap().to_number(), 15.0);
assert_eq!(engine.get_field_value("d").unwrap().to_number(), 35.0);
engine.set_field_value("a", FieldValue::Number(20.0));
assert_eq!(engine.get_field_value("b").unwrap().to_number(), 40.0);
assert_eq!(engine.get_field_value("c").unwrap().to_number(), 25.0);
assert_eq!(engine.get_field_value("d").unwrap().to_number(), 65.0);
}
#[test]
fn test_field_value_conversions_extended() {
assert_eq!(FieldValue::Number(42.5).to_number(), 42.5);
assert_eq!(FieldValue::Text("123.45".to_string()).to_number(), 123.45);
assert_eq!(FieldValue::Text("invalid".to_string()).to_number(), 0.0);
assert_eq!(FieldValue::Boolean(true).to_number(), 1.0);
assert_eq!(FieldValue::Boolean(false).to_number(), 0.0);
assert_eq!(FieldValue::Empty.to_number(), 0.0);
assert_eq!(FieldValue::Number(42.0).to_string(), "42");
assert_eq!(FieldValue::Number(42.5).to_string(), "42.50");
assert_eq!(FieldValue::Text("hello".to_string()).to_string(), "hello");
assert_eq!(FieldValue::Boolean(true).to_string(), "true");
assert_eq!(FieldValue::Boolean(false).to_string(), "false");
assert_eq!(FieldValue::Empty.to_string(), "");
}
#[test]
fn test_min_max_functions() {
let mut engine = CalculationEngine::new();
engine.set_field_value("a", FieldValue::Number(10.0));
engine.set_field_value("b", FieldValue::Number(5.0));
engine.set_field_value("c", FieldValue::Number(15.0));
engine.set_field_value("d", FieldValue::Number(8.0));
let min_calc = Calculation::Function(CalculationFunction::Min(vec![
"a".to_string(),
"b".to_string(),
"c".to_string(),
"d".to_string(),
]));
engine.add_calculation("min_val", min_calc).unwrap();
assert_eq!(engine.get_field_value("min_val").unwrap().to_number(), 5.0);
let max_calc = Calculation::Function(CalculationFunction::Max(vec![
"a".to_string(),
"b".to_string(),
"c".to_string(),
"d".to_string(),
]));
engine.add_calculation("max_val", max_calc).unwrap();
assert_eq!(engine.get_field_value("max_val").unwrap().to_number(), 15.0);
}
#[test]
fn test_count_function() {
let mut engine = CalculationEngine::new();
engine.set_field_value("f1", FieldValue::Number(10.0));
engine.set_field_value("f2", FieldValue::Empty);
engine.set_field_value("f3", FieldValue::Text("text".to_string()));
engine.set_field_value("f4", FieldValue::Number(0.0));
let count_calc = Calculation::Function(CalculationFunction::Count(vec![
"f1".to_string(),
"f2".to_string(),
"f3".to_string(),
"f4".to_string(),
]));
engine.add_calculation("count", count_calc).unwrap();
assert_eq!(engine.get_field_value("count").unwrap().to_number(), 3.0);
}
#[test]
fn test_if_function() {
let mut engine = CalculationEngine::new();
engine.set_field_value("condition", FieldValue::Boolean(true));
let if_calc = Calculation::Function(CalculationFunction::If {
condition_field: "condition".to_string(),
true_value: Box::new(Calculation::Constant(FieldValue::Number(100.0))),
false_value: Box::new(Calculation::Constant(FieldValue::Number(200.0))),
});
engine.add_calculation("result", if_calc).unwrap();
assert_eq!(engine.get_field_value("result").unwrap().to_number(), 100.0);
engine.set_field_value("condition", FieldValue::Boolean(false));
assert_eq!(engine.get_field_value("result").unwrap().to_number(), 200.0);
engine.set_field_value("condition", FieldValue::Number(5.0));
assert_eq!(engine.get_field_value("result").unwrap().to_number(), 100.0);
engine.set_field_value("condition", FieldValue::Number(0.0));
assert_eq!(engine.get_field_value("result").unwrap().to_number(), 200.0);
}
#[test]
fn test_modulo_and_power_operations() {
let mut engine = CalculationEngine::new();
engine.set_field_value("a", FieldValue::Number(10.0));
engine.set_field_value("b", FieldValue::Number(3.0));
let mod_expr = ArithmeticExpression::from_string("a % b").unwrap();
engine
.add_calculation("mod_result", Calculation::Arithmetic(mod_expr))
.unwrap();
assert_eq!(
engine.get_field_value("mod_result").unwrap().to_number(),
1.0
);
let pow_expr = ArithmeticExpression::from_string("b ^ 3").unwrap();
engine
.add_calculation("pow_result", Calculation::Arithmetic(pow_expr))
.unwrap();
assert_eq!(
engine.get_field_value("pow_result").unwrap().to_number(),
27.0
);
}
#[test]
fn test_calculation_summary() {
let mut engine = CalculationEngine::new();
engine.set_field_value("a", FieldValue::Number(10.0));
engine.set_field_value("b", FieldValue::Number(20.0));
let expr = ArithmeticExpression::from_string("a + b").unwrap();
engine
.add_calculation("sum", Calculation::Arithmetic(expr))
.unwrap();
let summary = engine.get_summary();
assert_eq!(summary.total_fields, 3); assert_eq!(summary.calculated_fields, 1); assert_eq!(summary.calculation_order.len(), 1);
assert_eq!(summary.calculation_order[0], "sum");
let summary_str = format!("{}", summary);
assert!(summary_str.contains("Total fields: 3"));
assert!(summary_str.contains("Calculated fields: 1"));
}
#[test]
fn test_recalculate_all() {
let mut engine = CalculationEngine::new();
engine.set_field_value("x", FieldValue::Number(5.0));
engine.set_field_value("y", FieldValue::Number(10.0));
let expr1 = ArithmeticExpression::from_string("x + y").unwrap();
engine
.add_calculation("sum", Calculation::Arithmetic(expr1))
.unwrap();
let expr2 = ArithmeticExpression::from_string("sum * 2").unwrap();
engine
.add_calculation("double", Calculation::Arithmetic(expr2))
.unwrap();
assert_eq!(engine.get_field_value("sum").unwrap().to_number(), 15.0);
assert_eq!(engine.get_field_value("double").unwrap().to_number(), 30.0);
engine.recalculate_all().unwrap();
assert_eq!(engine.get_field_value("sum").unwrap().to_number(), 15.0);
assert_eq!(engine.get_field_value("double").unwrap().to_number(), 30.0);
}
#[test]
fn test_javascript_calculation() {
let mut engine = CalculationEngine::new();
let js_calc = Calculation::JavaScript("var sum = a + b;".to_string());
engine.add_calculation("js_result", js_calc).unwrap();
assert_eq!(
*engine.get_field_value("js_result").unwrap(),
FieldValue::Empty
);
}
#[test]
fn test_division_by_zero() {
let mut engine = CalculationEngine::new();
engine.set_field_value("numerator", FieldValue::Number(100.0));
engine.set_field_value("denominator", FieldValue::Number(0.0));
let expr = ArithmeticExpression {
tokens: vec![
ExpressionToken::Field("numerator".to_string()),
ExpressionToken::Operator(Operator::Divide),
ExpressionToken::Field("denominator".to_string()),
],
};
let _ = engine.add_calculation("result", Calculation::Arithmetic(expr));
let result = engine.calculate_field("result");
match result {
Ok(_) => {
let value = engine.get_field_value("result");
assert!(
matches!(value, Some(FieldValue::Number(n)) if n.is_infinite() || n.is_nan()),
"Division by zero should produce infinity or NaN, got: {:?}",
value
);
}
Err(_) => {
}
}
}
#[test]
fn test_circular_reference_detection() {
let mut engine = CalculationEngine::new();
let _ = engine.add_calculation(
"field_a",
Calculation::Arithmetic(ArithmeticExpression {
tokens: vec![
ExpressionToken::Field("field_b".to_string()),
ExpressionToken::Number(1.0),
ExpressionToken::Operator(Operator::Add),
],
}),
);
let _ = engine.add_calculation(
"field_b",
Calculation::Arithmetic(ArithmeticExpression {
tokens: vec![
ExpressionToken::Field("field_c".to_string()),
ExpressionToken::Number(2.0),
ExpressionToken::Operator(Operator::Add),
],
}),
);
let _ = engine.add_calculation(
"field_c",
Calculation::Arithmetic(ArithmeticExpression {
tokens: vec![
ExpressionToken::Field("field_a".to_string()),
ExpressionToken::Number(3.0),
ExpressionToken::Operator(Operator::Add),
],
}),
);
let result = engine.update_calculation_order();
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_non_numeric_calculation() {
let mut engine = CalculationEngine::new();
engine.set_field_value("text_field", FieldValue::Text("not a number".to_string()));
engine.set_field_value("numeric_field", FieldValue::Number(42.0));
let expr = ArithmeticExpression {
tokens: vec![
ExpressionToken::Field("text_field".to_string()),
ExpressionToken::Field("numeric_field".to_string()),
ExpressionToken::Operator(Operator::Add),
],
};
let _ = engine.add_calculation("result", Calculation::Arithmetic(expr));
let _ = engine.calculate_field("result");
if let Some(FieldValue::Number(n)) = engine.get_field_value("result") {
assert_eq!(*n, 42.0); }
}
#[test]
fn test_empty_field_calculation() {
let mut engine = CalculationEngine::new();
let expr = ArithmeticExpression {
tokens: vec![
ExpressionToken::Field("undefined1".to_string()),
ExpressionToken::Field("undefined2".to_string()),
ExpressionToken::Operator(Operator::Multiply),
],
};
let _ = engine.add_calculation("result", Calculation::Arithmetic(expr));
let _ = engine.calculate_field("result");
if let Some(FieldValue::Number(n)) = engine.get_field_value("result") {
assert_eq!(*n, 0.0); }
}
#[test]
fn test_max_function_with_empty_fields() {
let mut engine = CalculationEngine::new();
engine.set_field_value("val1", FieldValue::Number(10.0));
engine.set_field_value("val2", FieldValue::Empty);
engine.set_field_value("val3", FieldValue::Number(25.0));
engine.set_field_value("val4", FieldValue::Text("invalid".to_string()));
let _ = engine.add_calculation(
"max_result",
Calculation::Function(CalculationFunction::Max(vec![
"val1".to_string(),
"val2".to_string(),
"val3".to_string(),
"val4".to_string(),
])),
);
let _ = engine.calculate_field("max_result");
if let Some(FieldValue::Number(n)) = engine.get_field_value("max_result") {
assert_eq!(*n, 25.0); }
}
#[test]
fn test_expression_parsing_comprehensive_edge_cases() {
let test_cases = vec![
("((a + b)", "Mismatched left parentheses"),
("a + b))", "Mismatched right parentheses"),
("a ++ b", "Double operators"),
("+ a", "Leading operator"),
("a +", "Trailing operator"),
("5..3", "Double decimal point"),
("3.14.159", "Multiple decimal points"),
("a + * b", "Consecutive operators"),
("(a + b) * ", "Operator without operand"),
("@#$%", "Invalid characters"),
("", "Empty expression"),
(" \t\n ", "Whitespace only"),
];
for (expr, description) in test_cases {
let result = ArithmeticExpression::from_string(expr);
assert!(
result.is_err(),
"Expression '{}' should fail parsing: {}",
expr,
description
);
}
let valid_cases = vec![
("()", 0.0), ("a b", 0.0), ("123abc", 123.0), ];
let mut engine = CalculationEngine::new();
engine.set_field_value("a", FieldValue::Number(5.0));
engine.set_field_value("b", FieldValue::Number(3.0));
for (i, (expr, _expected)) in valid_cases.iter().enumerate() {
let result = ArithmeticExpression::from_string(expr);
match result {
Ok(parsed_expr) => {
let calc_name = format!("edge_valid_{}", i);
let add_result =
engine.add_calculation(&calc_name, Calculation::Arithmetic(parsed_expr));
let _ = add_result;
}
Err(_) => {
}
}
}
}
#[test]
fn test_arithmetic_overflow_edge_cases() {
let mut engine = CalculationEngine::new();
engine.set_field_value("max_val", FieldValue::Number(f64::MAX));
engine.set_field_value("min_val", FieldValue::Number(f64::MIN));
engine.set_field_value("infinity", FieldValue::Number(f64::INFINITY));
engine.set_field_value("neg_infinity", FieldValue::Number(f64::NEG_INFINITY));
engine.set_field_value("zero", FieldValue::Number(0.0));
engine.set_field_value("small", FieldValue::Number(f64::MIN_POSITIVE));
let overflow_expr = ArithmeticExpression::from_string("max_val * 2").unwrap();
engine
.add_calculation("overflow_result", Calculation::Arithmetic(overflow_expr))
.unwrap();
let overflow_result = engine.get_field_value("overflow_result").unwrap();
assert!(overflow_result.to_number().is_infinite());
let inf_expr = ArithmeticExpression::from_string("infinity + 100").unwrap();
engine
.add_calculation("inf_result", Calculation::Arithmetic(inf_expr))
.unwrap();
let inf_result = engine.get_field_value("inf_result").unwrap();
assert_eq!(inf_result.to_number(), f64::INFINITY);
let nan_expr = ArithmeticExpression::from_string("infinity - infinity").unwrap();
engine
.add_calculation("nan_result", Calculation::Arithmetic(nan_expr))
.unwrap();
let nan_result = engine.get_field_value("nan_result").unwrap();
assert!(nan_result.to_number().is_nan());
}
#[test]
fn test_complex_financial_calculations() {
let mut engine = CalculationEngine::new();
engine.set_field_value("unit_price", FieldValue::Number(19.99));
engine.set_field_value("quantity", FieldValue::Number(150.0));
engine.set_field_value("discount_rate", FieldValue::Number(0.15)); engine.set_field_value("tax_rate", FieldValue::Number(0.08)); engine.set_field_value("shipping_base", FieldValue::Number(25.0));
engine.set_field_value("shipping_per_item", FieldValue::Number(1.50));
let subtotal_expr = ArithmeticExpression::from_string("unit_price * quantity").unwrap();
engine
.add_calculation("subtotal", Calculation::Arithmetic(subtotal_expr))
.unwrap();
let discount_expr = ArithmeticExpression::from_string("subtotal * discount_rate").unwrap();
engine
.add_calculation("discount_amount", Calculation::Arithmetic(discount_expr))
.unwrap();
let after_discount_expr =
ArithmeticExpression::from_string("subtotal - discount_amount").unwrap();
engine
.add_calculation(
"after_discount",
Calculation::Arithmetic(after_discount_expr),
)
.unwrap();
let shipping_expr =
ArithmeticExpression::from_string("shipping_base + quantity * shipping_per_item")
.unwrap();
engine
.add_calculation("shipping", Calculation::Arithmetic(shipping_expr))
.unwrap();
let pretax_expr = ArithmeticExpression::from_string("after_discount + shipping").unwrap();
engine
.add_calculation("pretax_total", Calculation::Arithmetic(pretax_expr))
.unwrap();
let tax_expr = ArithmeticExpression::from_string("pretax_total * tax_rate").unwrap();
engine
.add_calculation("tax_amount", Calculation::Arithmetic(tax_expr))
.unwrap();
let total_expr = ArithmeticExpression::from_string("pretax_total + tax_amount").unwrap();
engine
.add_calculation("final_total", Calculation::Arithmetic(total_expr))
.unwrap();
let subtotal = engine.get_field_value("subtotal").unwrap().to_number();
assert!(
(subtotal - 2998.5).abs() < 0.01,
"Subtotal calculation incorrect: expected 2998.5, got {}",
subtotal
);
let discount_amount = engine
.get_field_value("discount_amount")
.unwrap()
.to_number();
assert!(
(discount_amount - 449.775).abs() < 0.01,
"Discount amount calculation incorrect: expected 449.775, got {}",
discount_amount
);
let after_discount = engine
.get_field_value("after_discount")
.unwrap()
.to_number();
assert!(
(after_discount - 2548.725).abs() < 0.01,
"After discount calculation incorrect: expected 2548.725, got {}",
after_discount
);
assert_eq!(
engine.get_field_value("shipping").unwrap().to_number(),
250.0
);
let pretax_total = engine.get_field_value("pretax_total").unwrap().to_number();
assert!(
(pretax_total - 2798.725).abs() < 0.01,
"Pretax total calculation incorrect: expected 2798.725, got {}",
pretax_total
);
let final_total = engine.get_field_value("final_total").unwrap().to_number();
let tax_amount = engine.get_field_value("tax_amount").unwrap().to_number();
let pretax_total = engine.get_field_value("pretax_total").unwrap().to_number();
let expected_tax = pretax_total * 0.08;
let expected_final = pretax_total + expected_tax;
assert!(
(tax_amount - expected_tax).abs() < 0.01,
"Tax amount calculation incorrect: expected {}, got {}",
expected_tax,
tax_amount
);
assert!(
(final_total - expected_final).abs() < 0.01,
"Final total calculation incorrect: expected {}, got {}",
expected_final,
final_total
);
}
#[test]
fn test_deeply_nested_expressions() {
let mut engine = CalculationEngine::new();
engine.set_field_value("a", FieldValue::Number(2.0));
engine.set_field_value("b", FieldValue::Number(3.0));
engine.set_field_value("c", FieldValue::Number(4.0));
engine.set_field_value("d", FieldValue::Number(5.0));
let deep_expr = ArithmeticExpression::from_string(
"(((a + b) * (c - d)) / ((a * b) + (c / d))) ^ 2 + ((a - b) * (c + d))",
)
.unwrap();
engine
.add_calculation("deep_result", Calculation::Arithmetic(deep_expr))
.unwrap();
let result = engine.get_field_value("deep_result").unwrap();
let result_num = result.to_number();
assert!(
result_num.is_finite() || result_num.is_nan(),
"Deep expression should produce a finite number or NaN, got {}",
result_num
);
let chain_expr = ArithmeticExpression::from_string(
"a + b - c + d * a / b + c - d ^ 2 + a * b * c / d - a + b + c - d",
)
.unwrap();
engine
.add_calculation("chain_result", Calculation::Arithmetic(chain_expr))
.unwrap();
let chain_result = engine.get_field_value("chain_result").unwrap();
assert!(chain_result.to_number().is_finite());
}
#[test]
fn test_comprehensive_function_combinations() {
let mut engine = CalculationEngine::new();
let field_names: Vec<String> = (1..=10).map(|i| format!("val{}", i)).collect();
let values = vec![10.0, 20.0, 5.0, 15.0, 25.0, 8.0, 30.0, 12.0, 18.0, 22.0];
for (name, value) in field_names.iter().zip(values.iter()) {
engine.set_field_value(name, FieldValue::Number(*value));
}
let sum_calc = Calculation::Function(CalculationFunction::Sum(field_names.clone()));
engine.add_calculation("total_sum", sum_calc).unwrap();
assert_eq!(
engine.get_field_value("total_sum").unwrap().to_number(),
165.0
);
engine.set_field_value("condition1", FieldValue::Boolean(true));
engine.set_field_value("condition2", FieldValue::Boolean(false));
let nested_if = Calculation::Function(CalculationFunction::If {
condition_field: "condition1".to_string(),
true_value: Box::new(Calculation::Function(CalculationFunction::If {
condition_field: "condition2".to_string(),
true_value: Box::new(Calculation::Constant(FieldValue::Number(100.0))),
false_value: Box::new(Calculation::Constant(FieldValue::Number(200.0))),
})),
false_value: Box::new(Calculation::Constant(FieldValue::Number(300.0))),
});
engine
.add_calculation("nested_if_result", nested_if)
.unwrap();
assert_eq!(
engine
.get_field_value("nested_if_result")
.unwrap()
.to_number(),
200.0
);
let product_calc =
Calculation::Function(CalculationFunction::Product(field_names[0..3].to_vec()));
engine
.add_calculation("product_result", product_calc)
.unwrap();
assert_eq!(
engine
.get_field_value("product_result")
.unwrap()
.to_number(),
1000.0
); }
#[test]
fn test_error_recovery_and_handling() {
let mut engine = CalculationEngine::new();
engine.set_field_value("valid1", FieldValue::Number(10.0));
engine.set_field_value("valid2", FieldValue::Number(20.0));
let expr_with_invalid =
ArithmeticExpression::from_string("valid1 + nonexistent + valid2").unwrap();
engine
.add_calculation("mixed_result", Calculation::Arithmetic(expr_with_invalid))
.unwrap();
assert_eq!(
engine.get_field_value("mixed_result").unwrap().to_number(),
30.0
);
let empty_sum = Calculation::Function(CalculationFunction::Sum(vec![]));
engine.add_calculation("empty_sum", empty_sum).unwrap();
assert_eq!(
engine.get_field_value("empty_sum").unwrap().to_number(),
0.0
);
let mixed_avg = Calculation::Function(CalculationFunction::Average(vec![
"valid1".to_string(),
"nonexistent1".to_string(),
"valid2".to_string(),
"nonexistent2".to_string(),
]));
engine.add_calculation("mixed_avg", mixed_avg).unwrap();
let avg_result = engine.get_field_value("mixed_avg").unwrap().to_number();
assert!(
avg_result == 7.5 || avg_result == 15.0,
"Average result should be either 7.5 or 15.0, got {}",
avg_result
);
}
#[test]
fn test_real_world_business_scenarios() {
let mut engine = CalculationEngine::new();
engine.set_field_value("principal", FieldValue::Number(200000.0)); engine.set_field_value("annual_rate", FieldValue::Number(0.035)); engine.set_field_value("years", FieldValue::Number(30.0));
let monthly_rate_expr = ArithmeticExpression::from_string("annual_rate / 12").unwrap();
engine
.add_calculation("monthly_rate", Calculation::Arithmetic(monthly_rate_expr))
.unwrap();
let total_payments_expr = ArithmeticExpression::from_string("years * 12").unwrap();
engine
.add_calculation(
"total_payments",
Calculation::Arithmetic(total_payments_expr),
)
.unwrap();
engine.set_field_value("hourly_rate", FieldValue::Number(25.50));
engine.set_field_value("hours_worked", FieldValue::Number(42.5));
engine.set_field_value("overtime_multiplier", FieldValue::Number(1.5));
engine.set_field_value("standard_hours", FieldValue::Number(40.0));
let regular_pay_expr =
ArithmeticExpression::from_string("standard_hours * hourly_rate").unwrap();
engine
.add_calculation("regular_pay", Calculation::Arithmetic(regular_pay_expr))
.unwrap();
engine.set_field_value("overtime_hours", FieldValue::Number(2.5));
let overtime_expr =
ArithmeticExpression::from_string("overtime_hours * hourly_rate * overtime_multiplier")
.unwrap();
engine
.add_calculation("overtime_pay", Calculation::Arithmetic(overtime_expr))
.unwrap();
let gross_expr = ArithmeticExpression::from_string("regular_pay + overtime_pay").unwrap();
engine
.add_calculation("gross_pay", Calculation::Arithmetic(gross_expr))
.unwrap();
assert_eq!(
engine.get_field_value("regular_pay").unwrap().to_number(),
1020.0
); assert_eq!(
engine.get_field_value("overtime_pay").unwrap().to_number(),
95.625
); assert_eq!(
engine.get_field_value("gross_pay").unwrap().to_number(),
1115.625
);
engine.set_field_value("batch1_qty", FieldValue::Number(100.0));
engine.set_field_value("batch1_cost", FieldValue::Number(10.50));
engine.set_field_value("batch2_qty", FieldValue::Number(75.0));
engine.set_field_value("batch2_cost", FieldValue::Number(11.25));
engine.set_field_value("batch3_qty", FieldValue::Number(50.0));
engine.set_field_value("batch3_cost", FieldValue::Number(12.00));
let batch1_value_expr =
ArithmeticExpression::from_string("batch1_qty * batch1_cost").unwrap();
engine
.add_calculation("batch1_value", Calculation::Arithmetic(batch1_value_expr))
.unwrap();
let batch2_value_expr =
ArithmeticExpression::from_string("batch2_qty * batch2_cost").unwrap();
engine
.add_calculation("batch2_value", Calculation::Arithmetic(batch2_value_expr))
.unwrap();
let batch3_value_expr =
ArithmeticExpression::from_string("batch3_qty * batch3_cost").unwrap();
engine
.add_calculation("batch3_value", Calculation::Arithmetic(batch3_value_expr))
.unwrap();
let total_inventory_calc = Calculation::Function(CalculationFunction::Sum(vec![
"batch1_value".to_string(),
"batch2_value".to_string(),
"batch3_value".to_string(),
]));
engine
.add_calculation("total_inventory", total_inventory_calc)
.unwrap();
assert_eq!(
engine.get_field_value("batch1_value").unwrap().to_number(),
1050.0
);
assert_eq!(
engine.get_field_value("batch2_value").unwrap().to_number(),
843.75
);
assert_eq!(
engine.get_field_value("batch3_value").unwrap().to_number(),
600.0
);
assert_eq!(
engine
.get_field_value("total_inventory")
.unwrap()
.to_number(),
2493.75
);
}
#[test]
fn test_special_number_values() {
let mut engine = CalculationEngine::new();
engine.set_field_value("nan_val", FieldValue::Number(f64::NAN));
engine.set_field_value("normal_val", FieldValue::Number(10.0));
let nan_expr = ArithmeticExpression::from_string("nan_val + normal_val").unwrap();
engine
.add_calculation("nan_result", Calculation::Arithmetic(nan_expr))
.unwrap();
let result = engine.get_field_value("nan_result").unwrap().to_number();
assert!(result.is_nan());
let sum_with_nan = Calculation::Function(CalculationFunction::Sum(vec![
"nan_val".to_string(),
"normal_val".to_string(),
]));
engine.add_calculation("sum_nan", sum_with_nan).unwrap();
let sum_result = engine.get_field_value("sum_nan").unwrap().to_number();
assert!(sum_result.is_nan());
engine.set_field_value("val1", FieldValue::Number(5.0));
engine.set_field_value("val2", FieldValue::Number(15.0));
let max_with_nan = Calculation::Function(CalculationFunction::Max(vec![
"nan_val".to_string(),
"val1".to_string(),
"val2".to_string(),
]));
engine.add_calculation("max_nan", max_with_nan).unwrap();
let max_result = engine.get_field_value("max_nan").unwrap().to_number();
assert_eq!(max_result, 15.0); }
#[test]
fn test_precision_and_rounding_scenarios() {
let mut engine = CalculationEngine::new();
engine.set_field_value("precise1", FieldValue::Number(1.0 / 3.0));
engine.set_field_value("precise2", FieldValue::Number(2.0 / 3.0));
let precision_expr = ArithmeticExpression::from_string("precise1 + precise2").unwrap();
engine
.add_calculation("precision_result", Calculation::Arithmetic(precision_expr))
.unwrap();
let result = engine
.get_field_value("precision_result")
.unwrap()
.to_number();
assert!((result - 1.0).abs() < 1e-15);
engine.set_field_value("tiny", FieldValue::Number(1e-100));
engine.set_field_value("huge", FieldValue::Number(1e100));
let scale_expr = ArithmeticExpression::from_string("tiny * huge").unwrap();
engine
.add_calculation("scale_result", Calculation::Arithmetic(scale_expr))
.unwrap();
let scale_result = engine.get_field_value("scale_result").unwrap().to_number();
assert!((scale_result - 1.0).abs() < 1e-14);
engine.set_field_value("price", FieldValue::Number(19.999));
engine.set_field_value("quantity", FieldValue::Number(3.0));
let financial_expr = ArithmeticExpression::from_string("price * quantity").unwrap();
engine
.add_calculation("financial_result", Calculation::Arithmetic(financial_expr))
.unwrap();
let financial_result = engine
.get_field_value("financial_result")
.unwrap()
.to_number();
assert!((financial_result - 59.997).abs() < 1e-10);
}
#[test]
fn test_extreme_calculation_chains() {
let mut engine = CalculationEngine::new();
engine.set_field_value("seed", FieldValue::Number(1.0));
for i in 1..=50 {
let prev = if i == 1 {
"seed".to_string()
} else {
format!("chain_{}", i - 1)
};
let current = format!("chain_{}", i);
let expr = ArithmeticExpression::from_string(&format!("{} + 1", prev)).unwrap();
engine
.add_calculation(¤t, Calculation::Arithmetic(expr))
.unwrap();
}
assert_eq!(
engine.get_field_value("chain_50").unwrap().to_number(),
51.0
);
engine.set_field_value("seed", FieldValue::Number(10.0));
assert_eq!(
engine.get_field_value("chain_50").unwrap().to_number(),
60.0
);
engine.set_field_value("base", FieldValue::Number(5.0));
for i in 1..=20 {
let field_name = format!("derived_{}", i);
let expr = ArithmeticExpression::from_string(&format!("base * {}", i)).unwrap();
engine
.add_calculation(&field_name, Calculation::Arithmetic(expr))
.unwrap();
}
for i in 1..=20 {
let field_name = format!("derived_{}", i);
let expected = 5.0 * i as f64;
assert_eq!(
engine.get_field_value(&field_name).unwrap().to_number(),
expected
);
}
engine.set_field_value("base", FieldValue::Number(10.0));
for i in 1..=20 {
let field_name = format!("derived_{}", i);
let expected = 10.0 * i as f64;
assert_eq!(
engine.get_field_value(&field_name).unwrap().to_number(),
expected
);
}
}
#[test]
fn test_comprehensive_operator_combinations() {
let mut engine = CalculationEngine::new();
engine.set_field_value("a", FieldValue::Number(12.0));
engine.set_field_value("b", FieldValue::Number(4.0));
engine.set_field_value("c", FieldValue::Number(3.0));
engine.set_field_value("d", FieldValue::Number(2.0));
let test_cases = vec![
("a + b * c - d", 22.0), ("a / b + c * d", 9.0), ("a % b + c ^ d", 9.0), ("(a + b) * (c - d)", 16.0), ("a ^ d / b + c", 39.0), ("a - b / c + d * c", 16.67), ];
for (i, (expr_str, expected)) in test_cases.iter().enumerate() {
let expr = ArithmeticExpression::from_string(expr_str).unwrap();
let field_name = format!("test_{}", i);
engine
.add_calculation(&field_name, Calculation::Arithmetic(expr))
.unwrap();
let result = engine.get_field_value(&field_name).unwrap().to_number();
assert!(
(result - expected).abs() < 0.1,
"Expression '{}' expected {}, got {}",
expr_str,
expected,
result
);
}
}
#[test]
fn test_conditional_calculation_complexity() {
let mut engine = CalculationEngine::new();
engine.set_field_value("customer_type", FieldValue::Text("premium".to_string()));
engine.set_field_value("order_amount", FieldValue::Number(1000.0));
engine.set_field_value("is_premium", FieldValue::Boolean(true));
engine.set_field_value("is_bulk_order", FieldValue::Boolean(true));
let premium_discount = Calculation::Function(CalculationFunction::If {
condition_field: "is_premium".to_string(),
true_value: Box::new(Calculation::Constant(FieldValue::Number(0.15))), false_value: Box::new(Calculation::Constant(FieldValue::Number(0.05))), });
engine
.add_calculation("base_discount", premium_discount)
.unwrap();
let bulk_discount = Calculation::Function(CalculationFunction::If {
condition_field: "is_bulk_order".to_string(),
true_value: Box::new(Calculation::Constant(FieldValue::Number(0.05))), false_value: Box::new(Calculation::Constant(FieldValue::Number(0.0))),
});
engine.add_calculation("bulk_bonus", bulk_discount).unwrap();
let total_discount_expr =
ArithmeticExpression::from_string("base_discount + bulk_bonus").unwrap();
engine
.add_calculation(
"total_discount_rate",
Calculation::Arithmetic(total_discount_expr),
)
.unwrap();
let discount_amount_expr =
ArithmeticExpression::from_string("order_amount * total_discount_rate").unwrap();
engine
.add_calculation(
"discount_amount",
Calculation::Arithmetic(discount_amount_expr),
)
.unwrap();
let final_amount_expr =
ArithmeticExpression::from_string("order_amount - discount_amount").unwrap();
engine
.add_calculation("final_amount", Calculation::Arithmetic(final_amount_expr))
.unwrap();
assert_eq!(
engine.get_field_value("base_discount").unwrap().to_number(),
0.15
);
assert_eq!(
engine.get_field_value("bulk_bonus").unwrap().to_number(),
0.05
);
assert_eq!(
engine
.get_field_value("total_discount_rate")
.unwrap()
.to_number(),
0.20
);
assert_eq!(
engine
.get_field_value("discount_amount")
.unwrap()
.to_number(),
200.0
);
assert_eq!(
engine.get_field_value("final_amount").unwrap().to_number(),
800.0
);
engine.set_field_value("is_premium", FieldValue::Boolean(false));
assert_eq!(
engine.get_field_value("base_discount").unwrap().to_number(),
0.05
);
assert_eq!(
engine.get_field_value("final_amount").unwrap().to_number(),
900.0
); }
#[test]
fn test_field_value_type_edge_cases() {
let mut engine = CalculationEngine::new();
let edge_cases = vec![
("", 0.0), ("0", 0.0), ("0.0", 0.0), ("-0", 0.0), ("123.456", 123.456), ("-123.456", -123.456), ("1.23e10", 1.23e10), ("1.23E-5", 1.23e-5), ("inf", f64::INFINITY), ("-inf", f64::NEG_INFINITY), ("nan", f64::NAN), ("not_a_number", 0.0), ("123abc", 0.0), (" 456 ", 0.0), ];
for (i, (text_val, expected)) in edge_cases.iter().enumerate() {
let field_name = format!("edge_case_{}", i);
engine.set_field_value(&field_name, FieldValue::Text((*text_val).to_string()));
let result = engine.get_field_value(&field_name).unwrap().to_number();
if expected.is_nan() {
assert!(
result.is_nan() || result == 0.0,
"Text '{}' should convert to NaN or 0.0, got {}",
text_val,
result
);
} else if expected.is_infinite() {
assert_eq!(
result, *expected,
"Text '{}' should convert to {}, got {}",
text_val, expected, result
);
} else {
assert!(
(result - expected).abs() < 1e-10,
"Text '{}' should convert to {}, got {}",
text_val,
expected,
result
);
}
}
}
#[test]
fn test_calculation_engine_state_management() {
let mut engine = CalculationEngine::new();
let summary = engine.get_summary();
assert_eq!(summary.total_fields, 0);
assert_eq!(summary.calculated_fields, 0);
engine.set_field_value("input1", FieldValue::Number(10.0));
engine.set_field_value("input2", FieldValue::Number(20.0));
let expr = ArithmeticExpression::from_string("input1 + input2").unwrap();
engine
.add_calculation("output", Calculation::Arithmetic(expr))
.unwrap();
let summary_after = engine.get_summary();
assert_eq!(summary_after.total_fields, 3); assert_eq!(summary_after.calculated_fields, 1); assert_eq!(summary_after.calculation_order, vec!["output".to_string()]);
engine.remove_calculation("output");
let summary_removed = engine.get_summary();
assert_eq!(summary_removed.total_fields, 2); assert_eq!(summary_removed.calculated_fields, 0);
assert_eq!(summary_removed.calculation_order.len(), 0);
let display_str = format!("{}", summary_removed);
assert!(display_str.contains("Total fields: 2"));
assert!(display_str.contains("Calculated fields: 0"));
}
#[test]
fn test_calculation_error_boundary_conditions() {
let mut engine = CalculationEngine::new();
engine.set_field_value("existing", FieldValue::Number(42.0));
assert_eq!(
engine.get_field_value("existing").unwrap().to_number(),
42.0
);
let expr = ArithmeticExpression::from_string("10 + 5").unwrap();
engine
.add_calculation("existing", Calculation::Arithmetic(expr))
.unwrap();
assert_eq!(
engine.get_field_value("existing").unwrap().to_number(),
15.0
);
engine.set_field_value("base1", FieldValue::Number(5.0));
engine.set_field_value("base2", FieldValue::Number(10.0));
let calc1 = ArithmeticExpression::from_string("base1 * 2").unwrap();
let calc2 = ArithmeticExpression::from_string("base2 / 2").unwrap();
engine
.add_calculation("independent1", Calculation::Arithmetic(calc1))
.unwrap();
engine
.add_calculation("independent2", Calculation::Arithmetic(calc2))
.unwrap();
assert_eq!(
engine.get_field_value("independent1").unwrap().to_number(),
10.0
);
assert_eq!(
engine.get_field_value("independent2").unwrap().to_number(),
5.0
);
engine.recalculate_all().unwrap();
assert_eq!(
engine.get_field_value("existing").unwrap().to_number(),
15.0
);
assert_eq!(
engine.get_field_value("independent1").unwrap().to_number(),
10.0
);
assert_eq!(
engine.get_field_value("independent2").unwrap().to_number(),
5.0
);
}
#[test]
fn test_calculation_stress_and_boundary_conditions() {
let mut engine = CalculationEngine::new();
for i in 0..100 {
let field_name = format!("rapid_field_{}", i);
engine.set_field_value(&field_name, FieldValue::Number(i as f64));
}
let field_refs = (0..50)
.map(|i| format!("rapid_field_{}", i))
.collect::<Vec<_>>();
let sum_calc = Calculation::Function(CalculationFunction::Sum(field_refs.clone()));
engine.add_calculation("rapid_sum", sum_calc).unwrap();
let avg_calc = Calculation::Function(CalculationFunction::Average(field_refs));
engine.add_calculation("rapid_avg", avg_calc).unwrap();
assert_eq!(
engine.get_field_value("rapid_sum").unwrap().to_number(),
1225.0
);
assert_eq!(
engine.get_field_value("rapid_avg").unwrap().to_number(),
24.5
);
for update_round in 0..10 {
for i in 0..50 {
let field_name = format!("rapid_field_{}", i);
let new_value = (i as f64) * (update_round as f64 + 1.0);
engine.set_field_value(&field_name, FieldValue::Number(new_value));
}
let current_sum = engine.get_field_value("rapid_sum").unwrap().to_number();
let expected_sum = 1225.0 * (update_round as f64 + 1.0);
assert!(
(current_sum - expected_sum).abs() < 0.01,
"Sum calculation incorrect in round {}: expected {}, got {}",
update_round,
expected_sum,
current_sum
);
}
}
#[test]
fn test_calculation_engine_memory_and_cleanup() {
let mut engine = CalculationEngine::new();
for i in 0..50 {
let field_name = format!("temp_field_{}", i);
engine.set_field_value(&field_name, FieldValue::Number(i as f64));
let expr = ArithmeticExpression::from_string(&format!("temp_field_{} * 2", i)).unwrap();
let calc_name = format!("temp_calc_{}", i);
engine
.add_calculation(&calc_name, Calculation::Arithmetic(expr))
.unwrap();
}
for i in 0..50 {
let calc_name = format!("temp_calc_{}", i);
let expected = (i as f64) * 2.0;
assert_eq!(
engine.get_field_value(&calc_name).unwrap().to_number(),
expected
);
}
for i in 0..25 {
let calc_name = format!("temp_calc_{}", i);
engine.remove_calculation(&calc_name);
}
for i in 0..25 {
let calc_name = format!("temp_calc_{}", i);
assert!(engine.get_field_value(&calc_name).is_none());
}
for i in 25..50 {
let calc_name = format!("temp_calc_{}", i);
let expected = (i as f64) * 2.0;
assert_eq!(
engine.get_field_value(&calc_name).unwrap().to_number(),
expected
);
}
let summary = engine.get_summary();
assert_eq!(summary.total_fields, 75);
assert_eq!(summary.calculated_fields, 25);
}
#[test]
fn test_maximum_expression_complexity() {
let mut engine = CalculationEngine::new();
for i in 1..=10 {
let field_name = format!("x{}", i);
engine.set_field_value(&field_name, FieldValue::Number(i as f64));
}
let complex_expr = ArithmeticExpression::from_string(
"((x1 + x2) * (x3 - x4) / (x5 + 1)) ^ 2 + ((x6 * x7) % (x8 + x9)) - x10",
)
.unwrap();
engine
.add_calculation("max_complexity", Calculation::Arithmetic(complex_expr))
.unwrap();
let result = engine
.get_field_value("max_complexity")
.unwrap()
.to_number();
assert!(
result.is_finite(),
"Maximum complexity expression should produce a finite result, got {}",
result
);
engine.set_field_value("condition_flag", FieldValue::Boolean(true));
let conditional_complex = Calculation::Function(CalculationFunction::If {
condition_field: "condition_flag".to_string(),
true_value: Box::new(Calculation::Function(CalculationFunction::Sum(vec![
"x1".to_string(),
"x2".to_string(),
"x3".to_string(),
"x4".to_string(),
"x5".to_string(),
]))),
false_value: Box::new(Calculation::Function(CalculationFunction::Product(vec![
"x6".to_string(),
"x7".to_string(),
"x8".to_string(),
]))),
});
engine
.add_calculation("conditional_complex", conditional_complex)
.unwrap();
let conditional_result = engine
.get_field_value("conditional_complex")
.unwrap()
.to_number();
assert_eq!(conditional_result, 15.0);
engine.set_field_value("condition_flag", FieldValue::Boolean(false));
let switched_result = engine
.get_field_value("conditional_complex")
.unwrap()
.to_number();
assert_eq!(switched_result, 336.0); }
#[test]
fn test_calculation_order_determinism() {
let mut engine1 = CalculationEngine::new();
let mut engine2 = CalculationEngine::new();
for i in 1..=5 {
let field_name = format!("base{}", i);
let value = FieldValue::Number(i as f64 * 10.0);
engine1.set_field_value(&field_name, value.clone());
engine2.set_field_value(&field_name, value);
}
let calculations = vec![
("calc1", "base1 + base2"), ("calc2", "base3 * base4"), ("calc3", "base5 / base1"), ("calc4", "base2 - base3"), ];
for (name, expr) in &calculations {
let parsed_expr = ArithmeticExpression::from_string(expr).unwrap();
engine1
.add_calculation(*name, Calculation::Arithmetic(parsed_expr))
.unwrap();
}
for (name, expr) in calculations.iter().rev() {
let parsed_expr = ArithmeticExpression::from_string(expr).unwrap();
engine2
.add_calculation(*name, Calculation::Arithmetic(parsed_expr))
.unwrap();
}
let expected_results = vec![
("calc1", 30.0),
("calc2", 1200.0),
("calc3", 5.0),
("calc4", -10.0),
];
for (field_name, expected) in expected_results {
let result1 = engine1.get_field_value(field_name).unwrap().to_number();
let result2 = engine2.get_field_value(field_name).unwrap().to_number();
assert_eq!(
result1, expected,
"Engine1 calculation {} should be {}, got {}",
field_name, expected, result1
);
assert_eq!(
result2, expected,
"Engine2 calculation {} should be {}, got {}",
field_name, expected, result2
);
assert_eq!(
result1, result2,
"Both engines should produce same result for {}: engine1={}, engine2={}",
field_name, result1, result2
);
}
let summary1 = engine1.get_summary();
let summary2 = engine2.get_summary();
assert_eq!(summary1.total_fields, summary2.total_fields);
assert_eq!(summary1.calculated_fields, summary2.calculated_fields);
assert_eq!(
summary1.calculation_order.len(),
summary2.calculation_order.len()
);
}
}