This commit is contained in:
2025-09-15 03:17:19 +10:00
parent 482f0120df
commit 80dbb09db0
3 changed files with 172 additions and 215 deletions

View File

@@ -3,13 +3,14 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
cell::CellRef, cell::CellRef,
common::{LeadErr, LeadErrCode, Literal}, common::{LeadErr, LeadErrCode, Literal},
evaluator::utils::*, evaluator::{numerics::*, utils::*},
grid::Grid, grid::Grid,
parser::*, parser::*,
}; };
use std::{collections::HashSet, f64, fmt}; use std::{collections::HashSet, f64, fmt};
mod numerics;
mod utils; mod utils;
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
@@ -124,81 +125,29 @@ fn evaluate_expr(
precs, precs,
grid, grid,
|nums| { |nums| {
let mut res = 0.0; if nums.is_empty() {
let mut count = 0;
for eval in nums {
match eval {
Eval::Literal(Literal::Number(num)) => {
res += num;
count += 1;
}
Eval::Unset => {}
_ => unreachable!(),
}
}
if count == 0 {
Err(LeadErr { Err(LeadErr {
title: "Evaluation error.".into(), title: "Evaluation error.".into(),
desc: "Attempted to divide by zero.".into(), desc: "Attempted to divide by zero.".into(),
code: LeadErrCode::DivZero, code: LeadErrCode::DivZero,
}) })
} else { } else {
Ok(res / count as f64) Ok(nums.iter().sum::<f64>() / nums.len() as f64)
} }
}, },
"AVG".into(), "AVG",
)?, )?,
"SUM" => eval_numeric_func( "SUM" => eval_numeric_func(args, precs, grid, |nums| Ok(nums.iter().sum()), "SUM")?,
args, "PROD" => {
precs, eval_numeric_func(args, precs, grid, |nums| Ok(nums.iter().product()), "PROD")?
grid,
|nums| {
Ok(nums
.iter()
.filter_map(|e| {
if let Eval::Literal(Literal::Number(n)) = e {
Some(n)
} else {
None
} }
})
.sum())
},
"SUM".into(),
)?,
"PROD" => eval_numeric_func(
args,
precs,
grid,
|nums| {
Ok(nums
.iter()
.filter_map(|e| {
if let Eval::Literal(Literal::Number(n)) = e {
Some(n)
} else {
None
}
})
.product())
},
"PROD".into(),
)?,
"MAX" => eval_numeric_func( "MAX" => eval_numeric_func(
args, args,
precs, precs,
grid, grid,
|nums| { |nums| {
nums.iter() nums.iter()
.filter_map(|e| { .cloned()
if let Eval::Literal(Literal::Number(n)) = e {
Some(*n) // deref to f64
} else {
None
}
})
.max_by(|a, b| a.partial_cmp(b).unwrap()) .max_by(|a, b| a.partial_cmp(b).unwrap())
.ok_or(LeadErr { .ok_or(LeadErr {
title: "Evaluation error.".into(), title: "Evaluation error.".into(),
@@ -206,7 +155,7 @@ fn evaluate_expr(
code: LeadErrCode::Unsupported, code: LeadErrCode::Unsupported,
}) })
}, },
"MAX".into(), "MAX",
)?, )?,
"MIN" => eval_numeric_func( "MIN" => eval_numeric_func(
args, args,
@@ -214,13 +163,7 @@ fn evaluate_expr(
grid, grid,
|nums| { |nums| {
nums.iter() nums.iter()
.filter_map(|e| { .cloned()
if let Eval::Literal(Literal::Number(n)) = e {
Some(*n) // deref to f64
} else {
None
}
})
.min_by(|a, b| a.partial_cmp(b).unwrap()) .min_by(|a, b| a.partial_cmp(b).unwrap())
.ok_or(LeadErr { .ok_or(LeadErr {
title: "Evaluation error.".into(), title: "Evaluation error.".into(),
@@ -228,17 +171,21 @@ fn evaluate_expr(
code: LeadErrCode::Unsupported, code: LeadErrCode::Unsupported,
}) })
}, },
"MIN".into(), "MIN",
)?, )?,
"EXP" => eval_single_arg_numeric(args, precs, grid, |x| x.exp(), "EXP".into())?, "ABS" => eval_abs(args, precs, grid)?,
"SIN" => eval_single_arg_numeric(args, precs, grid, |x| x.sin(), "SIN".into())?, "LOG" => eval_log(args, precs, grid)?,
"COS" => eval_single_arg_numeric(args, precs, grid, |x| x.cos(), "COS".into())?, "SQRT" => eval_sqrt(args, precs, grid)?,
"TAN" => eval_single_arg_numeric(args, precs, grid, |x| x.tan(), "TAN".into())?, "EXP" => eval_exp(args, precs, grid)?,
"ASIN" => eval_single_arg_numeric(args, precs, grid, |x| x.asin(), "ASIN".into())?, "SIN" => eval_sin(args, precs, grid)?,
"ACOS" => eval_single_arg_numeric(args, precs, grid, |x| x.acos(), "ACOS".into())?, "COS" => eval_cos(args, precs, grid)?,
"ATAN" => eval_single_arg_numeric(args, precs, grid, |x| x.atan(), "ATAN".into())?, "TAN" => eval_tan(args, precs, grid)?,
"PI" => eval_const(args, Eval::Literal(Literal::Number(f64::consts::PI)))?, "ASIN" => eval_asin(args, precs, grid)?,
"TAU" => eval_const(args, Eval::Literal(Literal::Number(f64::consts::TAU)))?, "ACOS" => eval_acos(args, precs, grid)?,
"ATAN" => eval_atan(args, precs, grid)?,
"PI" => eval_pi(args)?,
"TAU" => eval_tau(args)?,
"SQRT2" => eval_sqrt2(args)?,
it => { it => {
return Err(LeadErr { return Err(LeadErr {
title: "Evaluation error.".into(), title: "Evaluation error.".into(),
@@ -319,77 +266,6 @@ fn eval_range(
} }
} }
fn eval_avg(
args: &Vec<Expr>,
precs: &mut HashSet<CellRef>,
grid: Option<&Grid>,
) -> Result<Eval, LeadErr> {
let mut res = 0.0;
let mut count = 0;
for arg in args {
match evaluate_expr(arg, precs, grid)? {
Eval::Literal(Literal::Number(num)) => {
res += num;
count += 1;
}
Eval::Range(range) => {
for cell in range {
let Eval::CellRef { eval, reference: _ } = cell else {
return Err(LeadErr {
title: "Evaluation error.".into(),
desc: "Found non-cellref in RANGE during AVG evaluation.".into(),
code: LeadErrCode::Server,
});
};
if let Eval::Literal(Literal::Number(num)) = *eval {
res += num;
count += 1;
} else if matches!(*eval, Eval::Unset) {
continue;
} else {
return Err(LeadErr {
title: "Evaluation error.".into(),
desc: "Expected numeric types for AVG function.".into(),
code: LeadErrCode::Unsupported,
});
}
}
}
_ => {
return Err(LeadErr {
title: "Evaluation error.".into(),
desc: "Expected numeric types for AVG function.".into(),
code: LeadErrCode::Unsupported,
});
}
}
}
if count == 0 {
Err(LeadErr {
title: "Evaluation error.".into(),
desc: "Attempted to divide by zero.".into(),
code: LeadErrCode::DivZero,
})
} else {
Ok(Eval::Literal(Literal::Number(res / count as f64)))
}
}
fn eval_const(args: &Vec<Expr>, value: Eval) -> Result<Eval, LeadErr> {
if args.len() != 0 {
return Err(LeadErr {
title: "Evaluation error.".into(),
desc: format!("PI function requires no arguments."),
code: LeadErrCode::Invalid,
});
}
Ok(value)
}
fn eval_add(lval: &Eval, rval: &Eval) -> Result<Eval, LeadErr> { fn eval_add(lval: &Eval, rval: &Eval) -> Result<Eval, LeadErr> {
match (lval, rval) { match (lval, rval) {
(Eval::Literal(a), Eval::Literal(b)) => { (Eval::Literal(a), Eval::Literal(b)) => {

View File

@@ -0,0 +1,107 @@
use std::collections::HashSet;
use crate::{
cell::CellRef,
common::{LeadErr, LeadErrCode, Literal},
evaluator::{Eval, evaluate_expr},
grid::Grid,
parser::Expr,
};
// -------------------------------------------------- //
fn eval_unary_numeric(
args: &Vec<Expr>,
precs: &mut HashSet<CellRef>,
grid: Option<&Grid>,
func: fn(f64) -> f64,
func_name: &str,
) -> Result<Eval, LeadErr> {
if args.len() != 1 {
return Err(LeadErr {
title: "Evaluation error.".into(),
desc: format!("{func_name} function requires a single argument."),
code: LeadErrCode::Invalid,
});
}
let err = LeadErr {
title: "Evaluation error.".into(),
desc: format!("{func_name} function requires a numeric argument."),
code: LeadErrCode::TypeErr,
};
match evaluate_expr(&args[0], precs, grid)? {
Eval::Literal(Literal::Number(num)) => Ok(Eval::Literal(Literal::Number(func(num)))),
Eval::CellRef { eval, .. } => match *eval {
Eval::Literal(Literal::Number(n)) => Ok(Eval::Literal(Literal::Number(func(n)))),
_ => Err(err),
},
_ => Err(err),
}
}
macro_rules! unary_numeric_func {
($fn_name:ident, $func:expr, $label:expr) => {
pub fn $fn_name(
args: &Vec<Expr>,
precs: &mut HashSet<CellRef>,
grid: Option<&Grid>,
) -> Result<Eval, LeadErr> {
eval_unary_numeric(args, precs, grid, $func, $label)
}
};
}
unary_numeric_func!(eval_exp, |x| x.exp(), "EXP");
unary_numeric_func!(eval_log, |x| x.ln(), "LOG");
unary_numeric_func!(eval_sqrt, |x| x.sqrt(), "SQRT");
unary_numeric_func!(eval_abs, |x| x.abs(), "ABS");
unary_numeric_func!(eval_sin, |x| x.sin(), "SIN");
unary_numeric_func!(eval_cos, |x| x.cos(), "COS");
unary_numeric_func!(eval_tan, |x| x.tan(), "TAN");
unary_numeric_func!(eval_asin, |x| x.asin(), "ASIN");
unary_numeric_func!(eval_acos, |x| x.acos(), "ACOS");
unary_numeric_func!(eval_atan, |x| x.atan(), "ATAN");
// -------------------------------------------------- //
fn eval_const(args: &Vec<Expr>, value: Eval, label: &str) -> Result<Eval, LeadErr> {
if args.len() != 0 {
return Err(LeadErr {
title: "Evaluation error.".into(),
desc: format!("{label} function requires no arguments."),
code: LeadErrCode::Invalid,
});
}
Ok(value)
}
macro_rules! const_numeric_func {
($fn_name:ident, $value:expr, $label:expr) => {
pub fn $fn_name(args: &Vec<Expr>) -> Result<Eval, LeadErr> {
eval_const(args, $value, $label)
}
};
}
const_numeric_func!(
eval_pi,
Eval::Literal(Literal::Number(std::f64::consts::PI)),
"PI"
);
const_numeric_func!(
eval_tau,
Eval::Literal(Literal::Number(std::f64::consts::TAU)),
"TAU"
);
const_numeric_func!(
eval_sqrt2,
Eval::Literal(Literal::Number(std::f64::consts::SQRT_2)),
"SQRT2"
);
// -------------------------------------------------- //

View File

@@ -1,4 +1,4 @@
use std::{collections::HashSet, default}; use std::collections::HashSet;
use crate::{ use crate::{
cell::CellRef, cell::CellRef,
@@ -8,34 +8,6 @@ use crate::{
parser::Expr, parser::Expr,
}; };
pub fn eval_single_arg_numeric(
args: &Vec<Expr>,
precs: &mut HashSet<CellRef>,
grid: Option<&Grid>,
func: fn(f64) -> f64,
func_name: String,
) -> Result<Eval, LeadErr> {
if args.len() != 1 {
return Err(LeadErr {
title: "Evaluation error.".into(),
desc: format!("{func_name} function requires a single argument."),
code: LeadErrCode::Invalid,
});
}
let err = LeadErr {
title: "Evaluation error.".into(),
desc: format!("{func_name} function requires a numeric argument."),
code: LeadErrCode::TypeErr,
};
match evaluate_expr(&args[0], precs, grid)? {
Eval::Literal(Literal::Number(num)) => Ok(Eval::Literal(Literal::Number(func(num)))),
Eval::CellRef { eval, .. } => match *eval {
Eval::Literal(Literal::Number(n)) => Ok(Eval::Literal(Literal::Number(func(n)))),
_ => Err(err),
},
_ => Err(err),
}
}
pub fn eval_n_arg_numeric( pub fn eval_n_arg_numeric(
n: usize, n: usize,
@@ -81,24 +53,34 @@ pub fn eval_numeric_func(
args: &Vec<Expr>, args: &Vec<Expr>,
precs: &mut HashSet<CellRef>, precs: &mut HashSet<CellRef>,
grid: Option<&Grid>, grid: Option<&Grid>,
func: fn(Vec<Eval>) -> Result<f64, LeadErr>, func: impl Fn(&[f64]) -> Result<f64, LeadErr>,
func_name: String, func_name: &str,
) -> Result<Eval, LeadErr> { ) -> Result<Eval, LeadErr> {
let mut numeric_args = Vec::new(); let mut numbers = Vec::new();
for arg in args { for arg in args {
let eval = evaluate_expr(arg, precs, grid)?; let eval = evaluate_expr(arg, precs, grid)?;
if matches!(eval, Eval::Literal(Literal::Number(_)) | Eval::Unset) { match eval {
numeric_args.push(eval); Eval::Literal(Literal::Number(n)) => numbers.push(n),
} else if matches!(eval, Eval::Range(_)) { Eval::Unset => {} // skip
if let Eval::Range(range) = eval { Eval::Range(range) => {
for cell in range { for cell in range {
let Eval::CellRef { match cell {
eval: eval2, Eval::CellRef { eval: boxed, .. } => match *boxed {
reference: _, Eval::Literal(Literal::Number(n)) => numbers.push(n),
} = cell Eval::Unset => {}
else { _ => {
return Err(LeadErr {
title: "Evaluation error.".into(),
desc: format!(
"Expected numeric types for {func_name} function."
),
code: LeadErrCode::Unsupported,
});
}
},
_ => {
return Err(LeadErr { return Err(LeadErr {
title: "Evaluation error.".into(), title: "Evaluation error.".into(),
desc: format!( desc: format!(
@@ -106,11 +88,11 @@ pub fn eval_numeric_func(
), ),
code: LeadErrCode::Server, code: LeadErrCode::Server,
}); });
}; }
}
if matches!(*eval2, Eval::Literal(Literal::Number(_)) | Eval::Unset) { }
numeric_args.push(*eval2); }
} else { _ => {
return Err(LeadErr { return Err(LeadErr {
title: "Evaluation error.".into(), title: "Evaluation error.".into(),
desc: format!("Expected numeric types for {func_name} function."), desc: format!("Expected numeric types for {func_name} function."),
@@ -119,15 +101,7 @@ pub fn eval_numeric_func(
} }
} }
} }
} else {
return Err(LeadErr {
title: "Evaluation error.".into(),
desc: format!("Expected numeric types for {func_name} function."),
code: LeadErrCode::Unsupported,
});
}
}
let res = func(numeric_args)?; let res = func(&numbers)?;
Ok(Eval::Literal(Literal::Number(res))) Ok(Eval::Literal(Literal::Number(res)))
} }