From da0c3ebb78c2b8236222906162fe5fc0f1bde533 Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Mon, 11 Apr 2022 10:16:53 -0400 Subject: [PATCH] refactoring of error handling --- src/function.rs | 30 ++++++++++++---------- src/math_app.rs | 17 +++++++------ src/parsing.rs | 68 ++++++++++++++++++++++++------------------------- src/widgets.rs | 24 ++++++++++++----- 4 files changed, 76 insertions(+), 63 deletions(-) diff --git a/src/function.rs b/src/function.rs index 358be49..2c9623d 100644 --- a/src/function.rs +++ b/src/function.rs @@ -67,7 +67,7 @@ impl Default for FunctionEntry { /// Creates default FunctionEntry instance (which is empty) fn default() -> FunctionEntry { FunctionEntry { - function: BackingFunction::new(""), + function: BackingFunction::new("").unwrap(), raw_func_str: String::new(), min_x: -1.0, max_x: 1.0, @@ -90,9 +90,7 @@ impl FunctionEntry { let mut output_string: String = self.raw_func_str.clone(); self.autocomplete.ui(ui, &mut output_string, i); - if output_string != self.raw_func_str { - self.update_string(&output_string); - } + self.update_string(output_string.as_str()); } /// Get function's cached test result @@ -100,18 +98,24 @@ impl FunctionEntry { /// Update function string and test it fn update_string(&mut self, raw_func_str: &str) { - let processed_func = process_func_str(raw_func_str); - let output = crate::parsing::test_func(&processed_func); - self.raw_func_str = raw_func_str.to_string(); - if output.is_some() { - self.test_result = output; + if raw_func_str == self.raw_func_str { return; - } else { - self.test_result = None; } - self.function = BackingFunction::new(&processed_func); - self.invalidate_whole(); + self.raw_func_str = raw_func_str.to_string(); + let processed_func = process_func_str(raw_func_str); + let new_func_result = BackingFunction::new(&processed_func); + + match new_func_result { + Ok(new_function) => { + self.test_result = None; + self.function = new_function; + self.invalidate_whole(); + } + Err(error) => { + self.test_result = Some(error); + } + } } /// Get function that can be used to calculate integral based on Riemann Sum type diff --git a/src/math_app.rs b/src/math_app.rs index 62d41d4..7c36f2f 100644 --- a/src/math_app.rs +++ b/src/math_app.rs @@ -364,7 +364,7 @@ impl MathApp { "Right", ); }); - let riemann_changed = prev_sum == self.settings.riemann_sum; + let riemann_changed = prev_sum != self.settings.riemann_sum; // Config options for Extrema and roots let mut extrema_toggled: bool = false; @@ -646,20 +646,21 @@ impl epi::App for MathApp { // parsing) CentralPanel::default().show(ctx, |ui| { // Display an error if it exists - let errors_formatted: Vec = self + let errors_formatted: String = self .functions .iter() .map(|func| func.get_test_result()) .enumerate() .filter(|(_, error)| error.is_some()) - .map(|(i, error)| format!("(Function #{}) {}\n", i, error.as_ref().unwrap())) - .collect(); + .map(|(i, error)| { + // use unwrap_unchecked as None Errors are already filtered out + unsafe { format!("(Function #{}) {}\n", i, error.as_ref().unwrap_unchecked()) } + }) + .collect::(); - if errors_formatted.len() > 0 { + if !errors_formatted.is_empty() { ui.centered_and_justified(|ui| { - errors_formatted.iter().for_each(|string| { - ui.heading(string); - }) + ui.heading(errors_formatted); }); return; } diff --git a/src/parsing.rs b/src/parsing.rs index c8be2e8..399a050 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -20,10 +20,36 @@ pub struct BackingFunction { impl BackingFunction { /// Create new [`BackingFunction`] instance - pub fn new(func_str: &str) -> Self { + pub fn new(func_str: &str) -> Result { let function = match func_str { "" => EMPTY_FUNCTION.clone(), - _ => exmex::parse::(func_str).unwrap(), + _ => { + let parse_result = exmex::parse::(func_str); + + match &parse_result { + Err(e) => return Err(e.to_string()), + Ok(_) => { + let var_names = parse_result.as_ref().unwrap().var_names().to_vec(); + + if var_names != ["x"] { + let var_names_not_x: Vec<&String> = var_names + .iter() + .filter(|ele| ele != &"x") + .collect::>(); + + return Err(match var_names_not_x.len() { + 1 => { + format!("Error: invalid variable: {}", var_names_not_x[0]) + } + _ => { + format!("Error: invalid variables: {:?}", var_names_not_x) + } + }); + } + } + } + parse_result.unwrap() + } }; let derivative_1 = function @@ -35,12 +61,12 @@ impl BackingFunction { .partial_iter([0, 0].iter()) .unwrap_or_else(|_| EMPTY_FUNCTION.clone()); - Self { + Ok(Self { function, derivative_1, derivative_1_str, derivative_2, - } + }) } /// Returns Mathematical representation of the function's derivative @@ -164,36 +190,6 @@ pub fn process_func_str(function_in: &str) -> String { .replace('\u{1fc93}', "exp") } -/// 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. -pub fn test_func(function_string: &str) -> Option { - if function_string.is_empty() { - return None; - } - - let parse_result = exmex::parse::(function_string); - - match parse_result { - Err(e) => Some(e.to_string()), - Ok(_) => { - let var_names = parse_result.unwrap().var_names().to_vec(); - - if var_names != ["x"] { - let var_names_not_x: Vec<&String> = var_names - .iter() - .filter(|ele| ele != &"x") - .collect::>(); - - return match var_names_not_x.len() { - 1 => Some(format!("Error: invalid variable: {}", var_names_not_x[0])), - _ => Some(format!("Error: invalid variables: {:?}", var_names_not_x)), - }; - } - - None - } - } -} - #[cfg(test)] mod tests { use super::*; @@ -201,7 +197,9 @@ mod tests { use std::collections::HashMap; /// returns if function with string `func_str` is valid after processing through [`process_func_str`] - fn func_is_valid(func_str: &str) -> bool { test_func(&process_func_str(func_str)).is_none() } + fn func_is_valid(func_str: &str) -> bool { + BackingFunction::new(&process_func_str(func_str)).is_ok() + } /// Used for testing: passes function to [`process_func_str`] before running [`test_func`]. if `expect_valid` == `true`, it expects no errors to be created. fn test_func_helper(func_str: &str, expect_valid: bool) { diff --git a/src/widgets.rs b/src/widgets.rs index b1e9d97..0bc6aba 100644 --- a/src/widgets.rs +++ b/src/widgets.rs @@ -33,7 +33,7 @@ impl<'a> Default for AutoComplete<'a> { } impl<'a> AutoComplete<'a> { - fn changed(&mut self, string: &str) { + fn update(&mut self, string: &str) { let new_func_option = Some(string.to_string()); if self.string != new_func_option { self.string = new_func_option; @@ -45,12 +45,17 @@ impl<'a> AutoComplete<'a> { if movement == &Movement::None { return; } + + // self.string needs to be Some for this to work __DO NOT REMOVE THIS ASSERT__ assert!(self.string.is_some()); match self.hint { HintEnum::Many(hints) => { if movement == &Movement::Complete { - *self.string.as_mut().unwrap() += hints[self.i]; + // use unwrap_unchecked as self.string is already asserted as Some + unsafe { + *self.string.as_mut().unwrap_unchecked() += hints[self.i]; + } return; } @@ -59,9 +64,11 @@ impl<'a> AutoComplete<'a> { match movement { Movement::Up => { + // subtract one, if fail, set to max_i value. self.i = self.i.checked_sub(1).unwrap_or(max_i); } Movement::Down => { + // add one, if resulting value is above maximum i value, set i to 0 self.i += 1; if self.i > max_i { self.i = 0; @@ -72,7 +79,10 @@ impl<'a> AutoComplete<'a> { } HintEnum::Single(hint) => { if movement == &Movement::Complete { - *self.string.as_mut().unwrap() += hint; + // use unwrap_unchecked as self.string is already asserted as Some + unsafe { + *self.string.as_mut().unwrap_unchecked() += hint; + } } } HintEnum::None => {} @@ -83,7 +93,7 @@ impl<'a> AutoComplete<'a> { let mut movement: Movement = Movement::default(); // update self - self.changed(string); + self.update(string); let mut func_edit = egui::TextEdit::singleline(string) .hint_forward(true) // Make the hint appear after the last text in the textbox @@ -100,12 +110,12 @@ impl<'a> AutoComplete<'a> { let enter_pressed = ui.input_mut().consume_key(Modifiers::NONE, Key::Enter); let tab_pressed = ui.input_mut().consume_key(Modifiers::NONE, Key::Tab); if enter_pressed | tab_pressed | ui.input().key_pressed(Key::ArrowRight) { + println!("complete"); movement = Movement::Complete; } if let HintEnum::Single(single_hint) = self.hint { - let func_edit_2 = func_edit; - func_edit = func_edit_2.hint_text(*single_hint); + func_edit = func_edit.hint_text(*single_hint); } let re = func_edit.id(te_id).ui(ui); @@ -171,7 +181,7 @@ mod tests { fn auto_complete_helper(string: &str, movement: Movement) -> (AutoComplete, String) { let mut auto_complete = AutoComplete::default(); - auto_complete.changed(string); + auto_complete.update(string); auto_complete.interact_back(&movement); let output_string = auto_complete.clone().string;