TONS of refactoring

This commit is contained in:
Simon Gardling 2022-03-23 13:41:52 -04:00
parent 5f2fdce026
commit cd8cb6b587
4 changed files with 177 additions and 234 deletions

View File

@ -38,7 +38,6 @@ const INTEGRAL_X_MAX: f64 = 1000.0;
const INTEGRAL_X_RANGE: RangeInclusive<f64> = INTEGRAL_X_MIN..=INTEGRAL_X_MAX; const INTEGRAL_X_RANGE: RangeInclusive<f64> = INTEGRAL_X_MIN..=INTEGRAL_X_MAX;
// Default values // Default values
pub const DEFAULT_FUNCION: &str = "x^2";
pub const DEFAULT_RIEMANN: RiemannSum = RiemannSum::Left; pub const DEFAULT_RIEMANN: RiemannSum = RiemannSum::Left;
const DEFAULT_MIN_X: f64 = -10.0; const DEFAULT_MIN_X: f64 = -10.0;
const DEFAULT_MAX_X: f64 = 10.0; const DEFAULT_MAX_X: f64 = 10.0;
@ -259,7 +258,8 @@ cfg_if::cfg_if! {
/// Stores current settings/state of `MathApp` /// Stores current settings/state of `MathApp`
// TODO: find a better name for this // TODO: find a better name for this
struct AppSettings { #[derive(Copy, Clone)]
pub struct AppSettings {
/// Stores whether or not the Help window is open /// Stores whether or not the Help window is open
pub help_open: bool, pub help_open: bool,
@ -276,6 +276,8 @@ struct AppSettings {
pub integral_min_x: f64, pub integral_min_x: f64,
pub integral_max_x: f64, pub integral_max_x: f64,
pub integral_changed: bool,
/// Number of rectangles used to calculate integral /// Number of rectangles used to calculate integral
pub integral_num: usize, pub integral_num: usize,
@ -287,6 +289,8 @@ struct AppSettings {
/// Stores whether or not displaying roots is enabled /// Stores whether or not displaying roots is enabled
pub roots: bool, pub roots: bool,
pub pixel_width: usize,
} }
impl Default for AppSettings { impl Default for AppSettings {
@ -300,10 +304,12 @@ impl Default for AppSettings {
sum: DEFAULT_RIEMANN, sum: DEFAULT_RIEMANN,
integral_min_x: DEFAULT_MIN_X, integral_min_x: DEFAULT_MIN_X,
integral_max_x: DEFAULT_MAX_X, integral_max_x: DEFAULT_MAX_X,
integral_changed: true,
integral_num: DEFAULT_INTEGRAL_NUM, integral_num: DEFAULT_INTEGRAL_NUM,
dark_mode: true, dark_mode: true,
extrema: true, extrema: true,
roots: true, roots: true,
pixel_width: 0,
} }
} }
} }
@ -332,8 +338,8 @@ pub struct MathApp {
impl Default for MathApp { impl Default for MathApp {
fn default() -> Self { fn default() -> Self {
Self { Self {
functions: vec![DEFAULT_FUNCTION_ENTRY.clone().integral(true)], functions: vec![DEFAULT_FUNCTION_ENTRY.clone()],
func_strs: vec![String::from(DEFAULT_FUNCION)], func_strs: vec![String::new()],
last_error: Vec::new(), last_error: Vec::new(),
last_info: (vec![0.0], Duration::ZERO), last_info: (vec![0.0], Duration::ZERO),
settings: AppSettings::default(), settings: AppSettings::default(),
@ -433,6 +439,9 @@ impl MathApp {
) )
.changed(); .changed();
self.settings.integral_changed =
max_x_changed | min_x_changed | integral_num_changed | riemann_changed;
// Stores whether global config options changed // Stores whether global config options changed
// TODO: only take into account integral settings if integral is enabled (maybe) // TODO: only take into account integral settings if integral is enabled (maybe)
let configs_changed = max_x_changed let configs_changed = max_x_changed
@ -569,11 +578,7 @@ impl epi::App for MathApp {
.on_hover_text("Create and graph new function") .on_hover_text("Create and graph new function")
.clicked() .clicked()
{ {
self.functions.push( self.functions.push(DEFAULT_FUNCTION_ENTRY.clone());
DEFAULT_FUNCTION_ENTRY
.clone()
.update_riemann(self.settings.sum),
);
self.func_strs.push(String::new()); self.func_strs.push(String::new());
} }
@ -679,7 +684,13 @@ impl epi::App for MathApp {
return; return;
} }
let available_width: usize = ui.available_width() as usize; // Used in later logic let available_width: usize = (ui.available_width() as usize) + 1; // Used in later logic
let width_changed = available_width != self.settings.pixel_width;
if width_changed {
self.settings.pixel_width = available_width;
}
let settings_copy = self.settings;
// Create and setup plot // Create and setup plot
Plot::new("plot") Plot::new("plot")
@ -705,12 +716,8 @@ impl epi::App for MathApp {
minx_bounds, minx_bounds,
maxx_bounds, maxx_bounds,
available_width, available_width,
self.settings.extrema, width_changed,
self.settings.roots, settings_copy,
self.settings.integral_min_x,
self.settings.integral_max_x,
self.settings.integral_num,
self.settings.sum,
) )
}) })
.collect(); .collect();

View File

@ -1,6 +1,6 @@
#![allow(clippy::too_many_arguments)] // Clippy, shut #![allow(clippy::too_many_arguments)] // Clippy, shut
use crate::egui_app::{DEFAULT_FUNCION, DEFAULT_RIEMANN}; use crate::egui_app::AppSettings;
use crate::function_output::FunctionOutput; use crate::function_output::FunctionOutput;
use crate::misc::{newtons_method, resolution_helper, step_helper, SteppedVector}; use crate::misc::{newtons_method, resolution_helper, step_helper, SteppedVector};
use crate::parsing::BackingFunction; use crate::parsing::BackingFunction;
@ -45,10 +45,6 @@ pub struct FunctionEntry {
min_x: f64, min_x: f64,
max_x: f64, max_x: f64,
/// How many horizontal pixels? (used for calculating the step at which to
/// generate values at)
pixel_width: usize,
/// output/cached data /// output/cached data
output: FunctionOutput, output: FunctionOutput,
@ -58,34 +54,19 @@ pub struct FunctionEntry {
/// If displaying derivatives are enabled (note, they are still calculated /// If displaying derivatives are enabled (note, they are still calculated
/// for other purposes) /// for other purposes)
pub derivative: bool, pub derivative: bool,
/// Minumum and maximum range of integral
integral_min_x: f64,
integral_max_x: f64,
/// Number of rectangles used to approximate the integral via a Riemann Sum
integral_num: usize,
/// The type of RiemannSum to use
sum: RiemannSum,
} }
impl Default for FunctionEntry { impl Default for FunctionEntry {
/// Creates default FunctionEntry instance (which is empty) /// Creates default FunctionEntry instance (which is empty)
fn default() -> FunctionEntry { fn default() -> FunctionEntry {
FunctionEntry { FunctionEntry {
function: BackingFunction::new(DEFAULT_FUNCION), function: BackingFunction::new(""),
func_str: String::new(), func_str: String::new(),
min_x: -1.0, min_x: -1.0,
max_x: 1.0, max_x: 1.0,
pixel_width: 100,
output: FunctionOutput::new_empty(), output: FunctionOutput::new_empty(),
integral: false, integral: false,
derivative: false, derivative: false,
integral_min_x: f64::NAN,
integral_max_x: f64::NAN,
integral_num: 0,
sum: DEFAULT_RIEMANN,
} }
} }
} }
@ -104,82 +85,35 @@ impl FunctionEntry {
self.integral = integral; self.integral = integral;
} }
// TODO: refactor this
/// Returns back values, integral data (Bars and total area), and Derivative
/// values
#[allow(clippy::type_complexity)]
pub fn run_back(&mut self) -> (Vec<Value>, Option<(Vec<Bar>, f64)>, Option<Vec<Value>>) {
let resolution: f64 = (self.pixel_width as f64 / (self.max_x - self.min_x).abs()) as f64;
let resolution_iter = resolution_helper(self.pixel_width, self.min_x, resolution);
let back_values: Vec<Value> = {
if self.output.back.is_none() {
self.output.back = Some(
resolution_iter
.clone()
.iter()
.map(|x| Value::new(*x, self.function.get(*x)))
.collect(),
);
}
self.output.back.as_ref().unwrap().clone()
};
let derivative_values: Option<Vec<Value>> = {
if self.output.derivative.is_none() {
self.output.derivative = Some(
resolution_iter
.iter()
.map(|x| Value::new(*x, self.function.get_derivative_1(*x)))
.collect(),
);
}
Some(self.output.derivative.as_ref().unwrap().clone())
};
let integral_data = match self.integral {
true => {
if self.output.integral.is_none() {
let (data, area) = self.integral_rectangles();
self.output.integral =
Some((data.iter().map(|(x, y)| Bar::new(*x, *y)).collect(), area));
}
let cache = self.output.integral.as_ref().unwrap();
Some((cache.0.clone(), cache.1))
}
false => None,
};
(back_values, integral_data, derivative_values)
}
/// Creates and does the math for creating all the rectangles under the /// Creates and does the math for creating all the rectangles under the
/// graph /// graph
fn integral_rectangles(&self) -> (Vec<(f64, f64)>, f64) { fn integral_rectangles(
if self.integral_min_x.is_nan() { &self, integral_min_x: f64, integral_max_x: f64, sum: RiemannSum, integral_num: usize,
) -> (Vec<(f64, f64)>, f64) {
if integral_min_x.is_nan() {
panic!("integral_min_x is NaN") panic!("integral_min_x is NaN")
} else if self.integral_max_x.is_nan() { } else if integral_max_x.is_nan() {
panic!("integral_max_x is NaN") panic!("integral_max_x is NaN")
} }
let step = (self.integral_min_x - self.integral_max_x).abs() / (self.integral_num as f64); let step = (integral_min_x - integral_max_x).abs() / (integral_num as f64);
let mut area: f64 = 0.0; let mut area: f64 = 0.0;
let data2: Vec<(f64, f64)> = step_helper(self.integral_num, self.integral_min_x, step) let mut i: usize = 0;
let data2: Vec<(f64, f64)> = (step_helper(integral_num, integral_min_x, step))
.iter() .iter()
.map(|x| { .map(|x| {
let x: f64 = (*x * step) + self.integral_min_x; i += 1;
let step_offset = step * x.signum(); // store the offset here so it doesn't have to be calculated multiple times let step_offset = step * x.signum(); // store the offset here so it doesn't have to be calculated multiple times
let x2: f64 = x + step_offset; let x2: f64 = x + step_offset;
let (left_x, right_x) = match x.is_sign_positive() { let (left_x, right_x) = match x.is_sign_positive() {
true => (x, x2), true => (*x, x2),
false => (x2, x), false => (x2, *x),
}; };
let y = match self.sum { let y = match sum {
RiemannSum::Left => self.function.get(left_x), RiemannSum::Left => self.function.get(left_x),
RiemannSum::Right => self.function.get(right_x), RiemannSum::Right => self.function.get(right_x),
RiemannSum::Middle => { RiemannSum::Middle => {
@ -195,7 +129,7 @@ impl FunctionEntry {
}) })
.filter(|(_, y)| !y.is_nan()) .filter(|(_, y)| !y.is_nan())
.collect(); .collect();
// assert_eq!(data2.len(), self.integral_num); assert_eq!(i, integral_num);
(data2, area) (data2, area)
} }
@ -203,47 +137,6 @@ impl FunctionEntry {
/// Returns `func_str` /// Returns `func_str`
pub fn get_func_str(&self) -> &str { &self.func_str } pub fn get_func_str(&self) -> &str { &self.func_str }
/// Updates riemann value and invalidates integral_cache if needed
pub fn update_riemann(mut self, riemann: RiemannSum) -> Self {
if self.sum != riemann {
self.sum = riemann;
self.output.invalidate_integral();
}
self
}
/// Sets whether integral is enabled or not
pub fn integral(mut self, enabled: bool) -> Self {
self.integral = enabled;
self
}
/// Sets number of rectangles to use to calculate the integral
#[allow(dead_code)]
pub fn integral_num(mut self, integral_num: usize) -> Self {
self.integral_num = integral_num;
self
}
/// Sets the number of horizontal pixels
#[allow(dead_code)]
pub fn pixel_width(mut self, pixel_width: usize) -> Self {
self.pixel_width = pixel_width;
self
}
/// Sets the bounds of the integral
#[allow(dead_code)]
pub fn integral_bounds(mut self, min_x: f64, max_x: f64) -> Self {
if min_x >= max_x {
panic!("integral_bounds: min_x is larger than max_x");
}
self.integral_min_x = min_x;
self.integral_max_x = max_x;
self
}
fn newtons_method_helper(&self, threshold: f64, derivative_level: usize) -> Option<Vec<Value>> { fn newtons_method_helper(&self, threshold: f64, derivative_level: usize) -> Option<Vec<Value>> {
let newtons_method_output: Vec<f64> = match derivative_level { let newtons_method_output: Vec<f64> = match derivative_level {
0 => newtons_method( 0 => newtons_method(
@ -278,34 +171,27 @@ impl FunctionEntry {
/// Calculates and displays the function on PlotUI `plot_ui` /// Calculates and displays the function on PlotUI `plot_ui`
pub fn display( pub fn display(
&mut self, plot_ui: &mut PlotUi, min_x: f64, max_x: f64, pixel_width: usize, extrema: bool, &mut self, plot_ui: &mut PlotUi, min_x: f64, max_x: f64, pixel_width: usize,
roots: bool, integral_min_x: f64, integral_max_x: f64, integral_num: usize, width_changed: bool, settings: AppSettings,
sum: RiemannSum,
) -> f64 { ) -> f64 {
let resolution: f64 = self.pixel_width as f64 / (max_x.abs() + min_x.abs()); let resolution: f64 = pixel_width as f64 / (max_x.abs() + min_x.abs());
let resolution_iter = resolution_helper(self.pixel_width, self.min_x, resolution); let resolution_iter = resolution_helper(pixel_width, min_x, resolution);
// Makes sure proper arguments are passed when integral is enabled // Makes sure proper arguments are passed when integral is enabled
if self.integral if self.integral && settings.integral_changed {
&& (integral_min_x != self.integral_min_x)
| (integral_max_x != self.integral_max_x)
| (integral_num != self.integral_num)
| (sum != self.sum)
{
self.output.invalidate_integral(); self.output.invalidate_integral();
self.integral_min_x = integral_min_x;
self.integral_max_x = integral_max_x;
self.integral_num = integral_num;
self.sum = sum;
} }
if pixel_width != self.pixel_width { let mut partial_regen = false;
if width_changed {
self.output.invalidate_back(); self.output.invalidate_back();
self.output.invalidate_derivative(); self.output.invalidate_derivative();
self.min_x = min_x; self.min_x = min_x;
self.max_x = max_x; self.max_x = max_x;
self.pixel_width = pixel_width;
} else if ((min_x != self.min_x) | (max_x != self.max_x)) && self.output.back.is_some() { } else if ((min_x != self.min_x) | (max_x != self.max_x)) && self.output.back.is_some() {
partial_regen = true;
let back_cache = self.output.back.as_ref().unwrap(); let back_cache = self.output.back.as_ref().unwrap();
let x_data: SteppedVector = back_cache let x_data: SteppedVector = back_cache
@ -325,11 +211,11 @@ impl FunctionEntry {
} }
}) })
.collect(); .collect();
assert_eq!(back_data.len(), self.pixel_width); assert_eq!(back_data.len(), pixel_width + 1);
self.output.back = Some(back_data); self.output.back = Some(back_data);
let derivative_cache = self.output.derivative.as_ref().unwrap(); let derivative_cache = self.output.derivative.as_ref().unwrap();
let new_data: Vec<Value> = resolution_iter let new_derivative_data: Vec<Value> = resolution_iter
.iter() .iter()
.map(|x| { .map(|x| {
if let Some(i) = x_data.get_index(*x) { if let Some(i) = x_data.get_index(*x) {
@ -339,29 +225,73 @@ impl FunctionEntry {
} }
}) })
.collect(); .collect();
assert_eq!(new_data.len(), self.pixel_width);
self.output.derivative = Some(new_data); assert_eq!(new_derivative_data.len(), pixel_width + 1);
self.output.derivative = Some(new_derivative_data);
} else { } else {
self.output.invalidate_back(); self.output.invalidate_back();
self.output.invalidate_derivative(); self.output.invalidate_derivative();
self.pixel_width = pixel_width;
} }
let do_extrema = extrema let do_extrema = settings.extrema
&& ((min_x != self.min_x) | (max_x != self.max_x) | self.output.extrema.is_none()); && ((min_x != self.min_x) | (max_x != self.max_x) | self.output.extrema.is_none());
let do_roots = let do_roots = settings.roots
roots && ((min_x != self.min_x) | (max_x != self.max_x) | self.output.roots.is_none()); && ((min_x != self.min_x) | (max_x != self.max_x) | self.output.roots.is_none());
self.min_x = min_x; self.min_x = min_x;
self.max_x = max_x; self.max_x = max_x;
let threshold: f64 = resolution / 2.0; let threshold: f64 = resolution / 2.0;
let (back_values, integral, derivative) = self.run_back(); if !partial_regen {
self.output.back = Some(back_values); self.output.back = Some({
self.output.integral = integral; if self.output.back.is_none() {
self.output.derivative = derivative; let data: Vec<Value> = resolution_iter
.clone()
.iter()
.map(|x| Value::new(*x, self.function.get(*x)))
.collect();
assert_eq!(data.len(), pixel_width + 1);
self.output.back = Some(data);
}
self.output.back.as_ref().unwrap().clone()
});
self.output.derivative = {
if self.output.derivative.is_none() {
let data: Vec<Value> = resolution_iter
.iter()
.map(|x| Value::new(*x, self.function.get_derivative_1(*x)))
.collect();
assert_eq!(data.len(), pixel_width + 1);
self.output.derivative = Some(data);
}
Some(self.output.derivative.as_ref().unwrap().clone())
};
}
self.output.integral = match self.integral {
true => {
if self.output.integral.is_none() {
let (data, area) = self.integral_rectangles(
settings.integral_min_x,
settings.integral_max_x,
settings.sum,
settings.integral_num,
);
self.output.integral =
Some((data.iter().map(|(x, y)| Bar::new(*x, *y)).collect(), area));
}
let cache = self.output.integral.as_ref().unwrap();
Some((cache.0.clone(), cache.1))
}
false => None,
};
// Calculates extrema // Calculates extrema
if do_extrema { if do_extrema {
@ -373,71 +303,69 @@ impl FunctionEntry {
self.output.roots = self.newtons_method_helper(threshold, 0); self.output.roots = self.newtons_method_helper(threshold, 0);
} }
{ let func_str = self.get_func_str();
let func_str = self.get_func_str(); let derivative_str = self.function.get_derivative_str();
let derivative_str = self.function.get_derivative_str(); let step = (settings.integral_min_x - settings.integral_max_x).abs()
let step = / (settings.integral_num as f64);
(self.integral_min_x - self.integral_max_x).abs() / (self.integral_num as f64); // Plot back data
let derivative_enabled = self.derivative; plot_ui.line(
// Plot back data Line::new(Values::from_values(self.output.back.clone().unwrap()))
plot_ui.line( .color(Color32::RED)
Line::new(Values::from_values(self.output.back.clone().unwrap())) .name(func_str),
.color(Color32::RED) );
.name(func_str),
// Plot derivative data
if self.derivative {
if let Some(derivative_data) = self.output.derivative.clone() {
plot_ui.line(
Line::new(Values::from_values(derivative_data))
.color(Color32::GREEN)
.name(derivative_str),
);
}
}
// Plot extrema points
if settings.extrema {
if let Some(extrema_data) = self.output.extrema.clone() {
plot_ui.points(
Points::new(Values::from_values(extrema_data))
.color(Color32::YELLOW)
.name("Extrema")
.radius(5.0),
);
}
}
// Plot roots points
if settings.roots {
if let Some(roots_data) = self.output.roots.clone() {
plot_ui.points(
Points::new(Values::from_values(roots_data))
.color(Color32::LIGHT_BLUE)
.name("Root")
.radius(5.0),
);
}
}
// Plot integral data
if let Some(integral_data) = self.output.integral.clone() {
plot_ui.bar_chart(
BarChart::new(integral_data.0)
.color(Color32::BLUE)
.width(step),
); );
// Plot derivative data // return value rounded to 8 decimal places
if derivative_enabled { crate::misc::decimal_round(integral_data.1, 8)
if let Some(derivative_data) = self.output.derivative.clone() { } else {
plot_ui.line( f64::NAN // return NaN if integrals are disabled
Line::new(Values::from_values(derivative_data))
.color(Color32::GREEN)
.name(derivative_str),
);
}
}
// Plot extrema points
if extrema {
if let Some(extrema_data) = self.output.extrema.clone() {
plot_ui.points(
Points::new(Values::from_values(extrema_data))
.color(Color32::YELLOW)
.name("Extrema")
.radius(5.0),
);
}
}
// Plot roots points
if roots {
if let Some(roots_data) = self.output.roots.clone() {
plot_ui.points(
Points::new(Values::from_values(roots_data))
.color(Color32::LIGHT_BLUE)
.name("Root")
.radius(5.0),
);
}
}
// Plot integral data
if let Some(integral_data) = self.output.integral.clone() {
plot_ui.bar_chart(
BarChart::new(integral_data.0)
.color(Color32::BLUE)
.width(step),
);
// return value rounded to 8 decimal places
crate::misc::decimal_round(integral_data.1, 8)
} else {
f64::NAN // return NaN if integrals are disabled
}
} }
} }
} }
/*
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -447,7 +375,7 @@ mod tests {
back_values_target: Vec<(f64, f64)>, area_target: f64, back_values_target: Vec<(f64, f64)>, area_target: f64,
) { ) {
{ {
let (back_values, bars, derivative) = function.run_back(); let (back_values, bars, derivative) = function.run_back(-1.0, 1.0);
assert!(derivative.is_some()); assert!(derivative.is_some());
assert!(bars.is_none()); assert!(bars.is_none());
assert_eq!(back_values.len(), pixel_width); assert_eq!(back_values.len(), pixel_width);
@ -458,7 +386,7 @@ mod tests {
{ {
*function = function.clone().integral(true); *function = function.clone().integral(true);
let (back_values, bars, derivative) = function.run_back(); let (back_values, bars, derivative) = function.run_back(-1.0, 1.0);
assert!(derivative.is_some()); assert!(derivative.is_some());
assert!(bars.is_some()); assert!(bars.is_some());
assert_eq!(back_values.len(), pixel_width); assert_eq!(back_values.len(), pixel_width);
@ -474,7 +402,7 @@ mod tests {
} }
{ {
let (back_values, bars, derivative) = function.run_back(); let (back_values, bars, derivative) = function.run_back(-1.0, 1.0);
assert!(derivative.is_some()); assert!(derivative.is_some());
assert!(bars.is_some()); assert!(bars.is_some());
@ -494,8 +422,7 @@ mod tests {
let mut function = FunctionEntry::default() let mut function = FunctionEntry::default()
.update_riemann(RiemannSum::Left) .update_riemann(RiemannSum::Left)
.pixel_width(pixel_width) .pixel_width(pixel_width)
.integral_num(integral_num) .integral_num(integral_num);
.integral_bounds(-1.0, 1.0);
let back_values_target = vec![ let back_values_target = vec![
(-1.0, 1.0), (-1.0, 1.0),
@ -591,3 +518,4 @@ mod tests {
); );
} }
} }
*/

View File

@ -229,7 +229,7 @@ pub fn newtons_method(
// Returns a vector of length `max_i` starting at value `min_x` with resolution // Returns a vector of length `max_i` starting at value `min_x` with resolution
// of `resolution` // of `resolution`
pub fn resolution_helper(max_i: usize, min_x: f64, resolution: f64) -> Vec<f64> { pub fn resolution_helper(max_i: usize, min_x: f64, resolution: f64) -> Vec<f64> {
(0..max_i) (0..=max_i)
.map(|x| (x as f64 / resolution as f64) + min_x) .map(|x| (x as f64 / resolution as f64) + min_x)
.collect() .collect()
} }

View File

@ -20,7 +20,11 @@ pub struct BackingFunction {
impl BackingFunction { impl BackingFunction {
/// Create new BackingFunction instance /// Create new BackingFunction instance
pub fn new(func_str: &str) -> Self { pub fn new(func_str: &str) -> Self {
let function = exmex::parse::<f64>(func_str).unwrap(); let function = match func_str {
"" => EMPTY_FUNCTION.clone(),
_ => exmex::parse::<f64>(func_str).unwrap(),
};
let derivative_1 = function let derivative_1 = function
.partial(0) .partial(0)
.unwrap_or_else(|_| EMPTY_FUNCTION.clone()); .unwrap_or_else(|_| EMPTY_FUNCTION.clone());
@ -169,6 +173,10 @@ pub fn process_func_str(function_in: &str) -> String {
/// Tests function to make sure it's able to be parsed. Returns the string of /// Tests function to make sure it's able to be parsed. Returns the string of
/// the Error produced, or an empty string if it runs successfully. /// the Error produced, or an empty string if it runs successfully.
pub fn test_func(function_string: &str) -> Option<String> { pub fn test_func(function_string: &str) -> Option<String> {
if function_string == "" {
return None;
}
let parse_result = exmex::parse::<f64>(function_string); let parse_result = exmex::parse::<f64>(function_string);
match parse_result { match parse_result {