🙃
This commit is contained in:
@@ -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(
|
|
||||||
args,
|
|
||||||
precs,
|
|
||||||
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(),
|
|
||||||
)?,
|
)?,
|
||||||
|
"SUM" => eval_numeric_func(args, precs, grid, |nums| Ok(nums.iter().sum()), "SUM")?,
|
||||||
|
"PROD" => {
|
||||||
|
eval_numeric_func(args, precs, grid, |nums| Ok(nums.iter().product()), "PROD")?
|
||||||
|
}
|
||||||
"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)) => {
|
||||||
|
|||||||
107
backend/src/evaluator/numerics.rs
Normal file
107
backend/src/evaluator/numerics.rs
Normal 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"
|
||||||
|
);
|
||||||
|
|
||||||
|
// -------------------------------------------------- //
|
||||||
@@ -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,53 +53,55 @@ 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 {
|
return Err(LeadErr {
|
||||||
title: "Evaluation error.".into(),
|
title: "Evaluation error.".into(),
|
||||||
desc: format!(
|
desc: format!(
|
||||||
"Found non-cellref in RANGE during {func_name} evaluation."
|
"Expected numeric types for {func_name} function."
|
||||||
),
|
),
|
||||||
code: LeadErrCode::Server,
|
code: LeadErrCode::Unsupported,
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
},
|
||||||
if matches!(*eval2, Eval::Literal(Literal::Number(_)) | Eval::Unset) {
|
_ => {
|
||||||
numeric_args.push(*eval2);
|
return Err(LeadErr {
|
||||||
} else {
|
title: "Evaluation error.".into(),
|
||||||
return Err(LeadErr {
|
desc: format!(
|
||||||
title: "Evaluation error.".into(),
|
"Found non-cellref in RANGE during {func_name} evaluation."
|
||||||
desc: format!("Expected numeric types for {func_name} function."),
|
),
|
||||||
code: LeadErrCode::Unsupported,
|
code: LeadErrCode::Server,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} 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."),
|
||||||
code: LeadErrCode::Unsupported,
|
code: LeadErrCode::Unsupported,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = func(numeric_args)?;
|
let res = func(&numbers)?;
|
||||||
Ok(Eval::Literal(Literal::Number(res)))
|
Ok(Eval::Literal(Literal::Number(res)))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user