refactoring of error handling
This commit is contained in:
parent
6217f0aff4
commit
da0c3ebb78
@ -67,7 +67,7 @@ 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(""),
|
function: BackingFunction::new("").unwrap(),
|
||||||
raw_func_str: String::new(),
|
raw_func_str: String::new(),
|
||||||
min_x: -1.0,
|
min_x: -1.0,
|
||||||
max_x: 1.0,
|
max_x: 1.0,
|
||||||
@ -90,9 +90,7 @@ impl FunctionEntry {
|
|||||||
let mut output_string: String = self.raw_func_str.clone();
|
let mut output_string: String = self.raw_func_str.clone();
|
||||||
self.autocomplete.ui(ui, &mut output_string, i);
|
self.autocomplete.ui(ui, &mut output_string, i);
|
||||||
|
|
||||||
if output_string != self.raw_func_str {
|
self.update_string(output_string.as_str());
|
||||||
self.update_string(&output_string);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get function's cached test result
|
/// Get function's cached test result
|
||||||
@ -100,19 +98,25 @@ impl FunctionEntry {
|
|||||||
|
|
||||||
/// Update function string and test it
|
/// Update function string and test it
|
||||||
fn update_string(&mut self, raw_func_str: &str) {
|
fn update_string(&mut self, raw_func_str: &str) {
|
||||||
let processed_func = process_func_str(raw_func_str);
|
if raw_func_str == self.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;
|
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
self.test_result = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.function = BackingFunction::new(&processed_func);
|
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();
|
self.invalidate_whole();
|
||||||
}
|
}
|
||||||
|
Err(error) => {
|
||||||
|
self.test_result = Some(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get function that can be used to calculate integral based on Riemann Sum type
|
/// Get function that can be used to calculate integral based on Riemann Sum type
|
||||||
fn get_sum_func(&self, sum: Riemann) -> FunctionHelper {
|
fn get_sum_func(&self, sum: Riemann) -> FunctionHelper {
|
||||||
|
|||||||
@ -364,7 +364,7 @@ impl MathApp {
|
|||||||
"Right",
|
"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
|
// Config options for Extrema and roots
|
||||||
let mut extrema_toggled: bool = false;
|
let mut extrema_toggled: bool = false;
|
||||||
@ -646,20 +646,21 @@ impl epi::App for MathApp {
|
|||||||
// parsing)
|
// parsing)
|
||||||
CentralPanel::default().show(ctx, |ui| {
|
CentralPanel::default().show(ctx, |ui| {
|
||||||
// Display an error if it exists
|
// Display an error if it exists
|
||||||
let errors_formatted: Vec<String> = self
|
let errors_formatted: String = self
|
||||||
.functions
|
.functions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|func| func.get_test_result())
|
.map(|func| func.get_test_result())
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(_, error)| error.is_some())
|
.filter(|(_, error)| error.is_some())
|
||||||
.map(|(i, error)| format!("(Function #{}) {}\n", i, error.as_ref().unwrap()))
|
.map(|(i, error)| {
|
||||||
.collect();
|
// use unwrap_unchecked as None Errors are already filtered out
|
||||||
|
unsafe { format!("(Function #{}) {}\n", i, error.as_ref().unwrap_unchecked()) }
|
||||||
if errors_formatted.len() > 0 {
|
|
||||||
ui.centered_and_justified(|ui| {
|
|
||||||
errors_formatted.iter().for_each(|string| {
|
|
||||||
ui.heading(string);
|
|
||||||
})
|
})
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
if !errors_formatted.is_empty() {
|
||||||
|
ui.centered_and_justified(|ui| {
|
||||||
|
ui.heading(errors_formatted);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,10 +20,36 @@ 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) -> Result<Self, String> {
|
||||||
let function = match func_str {
|
let function = match func_str {
|
||||||
"" => EMPTY_FUNCTION.clone(),
|
"" => EMPTY_FUNCTION.clone(),
|
||||||
_ => exmex::parse::<f64>(func_str).unwrap(),
|
_ => {
|
||||||
|
let parse_result = exmex::parse::<f64>(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::<Vec<&String>>();
|
||||||
|
|
||||||
|
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
|
let derivative_1 = function
|
||||||
@ -35,12 +61,12 @@ impl BackingFunction {
|
|||||||
.partial_iter([0, 0].iter())
|
.partial_iter([0, 0].iter())
|
||||||
.unwrap_or_else(|_| EMPTY_FUNCTION.clone());
|
.unwrap_or_else(|_| EMPTY_FUNCTION.clone());
|
||||||
|
|
||||||
Self {
|
Ok(Self {
|
||||||
function,
|
function,
|
||||||
derivative_1,
|
derivative_1,
|
||||||
derivative_1_str,
|
derivative_1_str,
|
||||||
derivative_2,
|
derivative_2,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns Mathematical representation of the function's derivative
|
/// 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")
|
.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<String> {
|
|
||||||
if function_string.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let parse_result = exmex::parse::<f64>(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::<Vec<&String>>();
|
|
||||||
|
|
||||||
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -201,7 +197,9 @@ mod tests {
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// returns if function with string `func_str` is valid after processing through [`process_func_str`]
|
/// 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.
|
/// 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) {
|
fn test_func_helper(func_str: &str, expect_valid: bool) {
|
||||||
|
|||||||
@ -33,7 +33,7 @@ impl<'a> Default for AutoComplete<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> 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());
|
let new_func_option = Some(string.to_string());
|
||||||
if self.string != new_func_option {
|
if self.string != new_func_option {
|
||||||
self.string = new_func_option;
|
self.string = new_func_option;
|
||||||
@ -45,12 +45,17 @@ impl<'a> AutoComplete<'a> {
|
|||||||
if movement == &Movement::None {
|
if movement == &Movement::None {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// self.string needs to be Some for this to work __DO NOT REMOVE THIS ASSERT__
|
||||||
assert!(self.string.is_some());
|
assert!(self.string.is_some());
|
||||||
|
|
||||||
match self.hint {
|
match self.hint {
|
||||||
HintEnum::Many(hints) => {
|
HintEnum::Many(hints) => {
|
||||||
if movement == &Movement::Complete {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,9 +64,11 @@ impl<'a> AutoComplete<'a> {
|
|||||||
|
|
||||||
match movement {
|
match movement {
|
||||||
Movement::Up => {
|
Movement::Up => {
|
||||||
|
// subtract one, if fail, set to max_i value.
|
||||||
self.i = self.i.checked_sub(1).unwrap_or(max_i);
|
self.i = self.i.checked_sub(1).unwrap_or(max_i);
|
||||||
}
|
}
|
||||||
Movement::Down => {
|
Movement::Down => {
|
||||||
|
// add one, if resulting value is above maximum i value, set i to 0
|
||||||
self.i += 1;
|
self.i += 1;
|
||||||
if self.i > max_i {
|
if self.i > max_i {
|
||||||
self.i = 0;
|
self.i = 0;
|
||||||
@ -72,7 +79,10 @@ impl<'a> AutoComplete<'a> {
|
|||||||
}
|
}
|
||||||
HintEnum::Single(hint) => {
|
HintEnum::Single(hint) => {
|
||||||
if movement == &Movement::Complete {
|
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 => {}
|
HintEnum::None => {}
|
||||||
@ -83,7 +93,7 @@ impl<'a> AutoComplete<'a> {
|
|||||||
let mut movement: Movement = Movement::default();
|
let mut movement: Movement = Movement::default();
|
||||||
|
|
||||||
// update self
|
// update self
|
||||||
self.changed(string);
|
self.update(string);
|
||||||
|
|
||||||
let mut func_edit = egui::TextEdit::singleline(string)
|
let mut func_edit = egui::TextEdit::singleline(string)
|
||||||
.hint_forward(true) // Make the hint appear after the last text in the textbox
|
.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 enter_pressed = ui.input_mut().consume_key(Modifiers::NONE, Key::Enter);
|
||||||
let tab_pressed = ui.input_mut().consume_key(Modifiers::NONE, Key::Tab);
|
let tab_pressed = ui.input_mut().consume_key(Modifiers::NONE, Key::Tab);
|
||||||
if enter_pressed | tab_pressed | ui.input().key_pressed(Key::ArrowRight) {
|
if enter_pressed | tab_pressed | ui.input().key_pressed(Key::ArrowRight) {
|
||||||
|
println!("complete");
|
||||||
movement = Movement::Complete;
|
movement = Movement::Complete;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let HintEnum::Single(single_hint) = self.hint {
|
if let HintEnum::Single(single_hint) = self.hint {
|
||||||
let func_edit_2 = func_edit;
|
func_edit = func_edit.hint_text(*single_hint);
|
||||||
func_edit = func_edit_2.hint_text(*single_hint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let re = func_edit.id(te_id).ui(ui);
|
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) {
|
fn auto_complete_helper(string: &str, movement: Movement) -> (AutoComplete, String) {
|
||||||
let mut auto_complete = AutoComplete::default();
|
let mut auto_complete = AutoComplete::default();
|
||||||
auto_complete.changed(string);
|
auto_complete.update(string);
|
||||||
auto_complete.interact_back(&movement);
|
auto_complete.interact_back(&movement);
|
||||||
|
|
||||||
let output_string = auto_complete.clone().string;
|
let output_string = auto_complete.clone().string;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user