diff --git a/TODO.md b/TODO.md index b2cbace..9906cc7 100644 --- a/TODO.md +++ b/TODO.md @@ -20,3 +20,4 @@ 10. Create actual icon(s) for PWA/favicon (using placeholder from eframe_template) 11. Fix mobile text input 12. Don't use json for text storage +13. Write custom plotter diff --git a/parsing/src/parsing.rs b/parsing/src/parsing.rs index 6b9e42b..b2e6bb7 100644 --- a/parsing/src/parsing.rs +++ b/parsing/src/parsing.rs @@ -1,7 +1,7 @@ use exmex::prelude::*; #[derive(Clone)] -pub struct FlatExWrapper { +pub(crate) struct FlatExWrapper { func: Option>, } @@ -172,10 +172,18 @@ fn prettyify_function_str(func: &str) -> String { } } -pub const VALID_VARIABLES: [char; 3] = ['x', 'e', 'π']; +// pub const VALID_VARIABLES: [char; 3] = ['x', 'e', 'π']; +/// Case insensitive checks for if `c` is a character used to represent a variable #[inline] -pub fn is_variable(c: &char) -> bool { VALID_VARIABLES.contains(&c.to_ascii_lowercase()) } +pub const fn is_variable(c: &char) -> bool { + match c.to_ascii_lowercase() { + 'x' => true, + 'e' => true, + 'π' => true, + _ => false, + } +} /// Adds asterisks where needed in a function pub fn process_func_str(function_in: &str) -> String { diff --git a/parsing/src/suggestions.rs b/parsing/src/suggestions.rs index ede714c..2067e2b 100644 --- a/parsing/src/suggestions.rs +++ b/parsing/src/suggestions.rs @@ -1,4 +1,4 @@ -use std::{intrinsics::assume, mem}; +use std::intrinsics::assume; use crate::parsing::is_variable; @@ -28,7 +28,6 @@ pub fn split_function(input: &str) -> Vec { .collect::>() } -// __REVIEW__ pub fn split_function_chars(chars: &[char]) -> Vec { if chars.is_empty() { return Vec::new(); @@ -45,11 +44,12 @@ pub fn split_function_chars(chars: &[char]) -> Vec { } // Resulting split-up data - let mut data: Vec> = Vec::with_capacity(chars.len()); + let mut data: Vec = Vec::with_capacity(chars.len()); - // Buffer used to store data ready to be appended - let mut buffer: Vec<&char> = Vec::with_capacity(chars.len()); + // Need to start out with an empty string + data.push(String::new()); + /// Used to store info about a character struct BoolSlice { closing_parens: bool, number: bool, @@ -60,7 +60,7 @@ pub fn split_function_chars(chars: &[char]) -> Vec { } impl BoolSlice { - fn from_char(c: &char, prev_masked_num: bool, prev_masked_var: bool) -> Self { + const fn from_char(c: &char, prev_masked_num: bool, prev_masked_var: bool) -> Self { let isnumber = c.is_ascii_digit(); let isvariable = is_variable(c); Self { @@ -81,11 +81,42 @@ pub fn split_function_chars(chars: &[char]) -> Vec { const fn is_variable(&self) -> bool { self.variable && !self.masked_var } const fn is_number(&self) -> bool { self.number && !self.masked_num } + + const fn splitable(&self, c: &char, other: &BoolSlice) -> bool { + if other.closing_parens { + // Cases like `)x`, `)2`, and `)(` + return (*c == '(') + | (self.letter && !self.is_variable()) + | self.is_variable() | self.is_number(); + } else if *c == '(' { + // Cases like `x(` and `2(` + return (other.is_variable() | other.is_number()) && !other.letter; + } else if other.is_number() { + // Cases like `2x` and `2sin(x)` + return self.is_variable() | self.letter; + } else if self.is_variable() | self.letter { + // Cases like `e2` and `xx` + return other.is_number() + | (other.is_variable() && self.is_variable()) + | other.is_variable(); + } else if (self.is_number() | self.letter | self.is_variable()) + && (other.is_number() | other.letter) + { + return true; + } else if self.is_number() && other.is_variable() { + // Cases like `x2` + return true; + } else { + return false; + } + } } // Setup first char here let mut prev_char: BoolSlice = BoolSlice::from_char(&chars[0], false, false); - buffer.push(&chars[0]); + + let mut last = unsafe { data.last_mut().unwrap_unchecked() }; + last.push(chars[0]); // Iterate through all chars excluding the first one for c in chars.iter().skip(1) { @@ -115,59 +146,19 @@ pub fn split_function_chars(chars: &[char]) -> Vec { } } - let mut do_split = false; - - if prev_char.closing_parens { - // Cases like `)x`, `)2`, and `)(` - do_split = (c == &'(') - | (curr_c.letter && !curr_c.is_variable()) - | curr_c.is_variable() - | curr_c.is_number(); - } else if c == &'(' { - // Cases like `x(` and `2(` - do_split = (prev_char.is_variable() | prev_char.is_number()) && !prev_char.letter; - } else if prev_char.is_number() { - // Cases like `2x` and `2sin(x)` - do_split = curr_c.is_variable() | curr_c.letter; - } else if curr_c.is_variable() | curr_c.letter { - // Cases like `e2` and `xx` - do_split = prev_char.is_number() - | (prev_char.is_variable() && curr_c.is_variable()) - | prev_char.is_variable() - } else if (curr_c.is_number() | curr_c.letter | curr_c.is_variable()) - && (prev_char.is_number() | prev_char.letter) - { - do_split = true; - } else if curr_c.is_number() && prev_char.is_variable() { - // Cases like `x2` - do_split = true; - } - // Append split - if do_split { - data.push(Vec::new()); - - // Don't deinitialize `buffer`, simply swap data between the new last element of `data` with buffer! - unsafe { - mem::swap(data.last_mut().unwrap_unchecked(), &mut buffer); - } + if curr_c.splitable(c, &prev_char) { + data.push(String::new()); + last = unsafe { data.last_mut().unwrap_unchecked() }; } - // Add character to buffer - buffer.push(c); + last.push(*c); // Move current character data to `prev_char` prev_char = curr_c; } - // If there is still data in the buffer, append it - if !buffer.is_empty() { - data.push(buffer); - } - - data.into_iter() - .map(|e| e.iter().map(|c| *c).collect::()) - .collect::>() + data } /// Generate a hint based on the input `input`, returns an `Option` diff --git a/src/function_entry.rs b/src/function_entry.rs index 2a6b07e..2c8173c 100644 --- a/src/function_entry.rs +++ b/src/function_entry.rs @@ -68,30 +68,30 @@ pub struct FunctionEntry { impl const Default for FunctionEntry { /// Creates default FunctionEntry instance (which is empty) - fn default() -> FunctionEntry { - FunctionEntry { - function: BackingFunction::EMPTY, - raw_func_str: String::new(), - min_x: -1.0, - max_x: 1.0, - integral: false, - derivative: false, - nth_derviative: false, - back_data: Vec::new(), - integral_data: None, - derivative_data: Vec::new(), - extrema_data: Vec::new(), - root_data: Vec::new(), - nth_derivative_data: None, - autocomplete: AutoComplete::default(), - test_result: None, - curr_nth: 3, - settings_opened: false, - } - } + fn default() -> FunctionEntry { FunctionEntry::EMPTY } } impl FunctionEntry { + pub const EMPTY: FunctionEntry = FunctionEntry { + function: BackingFunction::EMPTY, + raw_func_str: String::new(), + min_x: -1.0, + max_x: 1.0, + integral: false, + derivative: false, + nth_derviative: false, + back_data: Vec::new(), + integral_data: None, + derivative_data: Vec::new(), + extrema_data: Vec::new(), + root_data: Vec::new(), + nth_derivative_data: None, + autocomplete: AutoComplete::default(), + test_result: None, + curr_nth: 3, + settings_opened: false, + }; + pub fn settings_window(&mut self, ctx: &Context) { let mut invalidate_nth = false; egui::Window::new(format!("Settings: {}", self.raw_func_str)) @@ -605,7 +605,7 @@ mod tests { fn do_test(sum: Riemann, area_target: f64) { let settings = app_settings_constructor(sum, -1.0, 1.0, 10, 10); - let mut function = FunctionEntry::default(); + let mut function = FunctionEntry::EMPTY; function.update_string("x^2"); function.integral = true; function.derivative = true; diff --git a/src/function_manager.rs b/src/function_manager.rs index cfaa3c4..f0a6cc7 100644 --- a/src/function_manager.rs +++ b/src/function_manager.rs @@ -14,7 +14,10 @@ pub struct FunctionManager { impl Default for FunctionManager { fn default() -> Self { Self { - functions: vec![(Uuid::new_v4(), FunctionEntry::default())], + functions: vec![( + uuid!("684fc8be-4ba0-408d-96ef-480b0642126f"), // Random uuid here to avoid call to `Uuid::new_v4()` + FunctionEntry::EMPTY, + )], } } } @@ -196,10 +199,7 @@ impl FunctionManager { } } - pub fn new_function(&mut self) { - self.functions - .push((Uuid::new_v4(), FunctionEntry::default())); - } + pub fn new_function(&mut self) { self.functions.push((Uuid::new_v4(), FunctionEntry::EMPTY)); } pub fn any_using_integral(&self) -> bool { self.functions.iter().any(|(_, func)| func.integral) diff --git a/src/lib.rs b/src/lib.rs index e04894f..1eac02d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,9 @@ #[macro_use] extern crate static_assertions; +#[macro_use] +extern crate uuid; + mod consts; mod function_entry; mod function_manager; diff --git a/src/main.rs b/src/main.rs index 5bfdce3..a1260f2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,9 @@ #[macro_use] extern crate static_assertions; +#[macro_use] +extern crate uuid; + mod consts; mod function_entry; mod function_manager; diff --git a/src/math_app.rs b/src/math_app.rs index 3a104dd..76ec71c 100644 --- a/src/math_app.rs +++ b/src/math_app.rs @@ -107,6 +107,7 @@ pub struct MathApp { /// Contains the list of Areas calculated (the vector of f64) and time it took for the last frame (the Duration). Stored in a Tuple. last_info: (Vec>, Option), + /// Whether or not dark mode is enabled dark_mode: bool, /// Stores opened windows/elements for later reference