From 9d9697778504a5b4305ea0587f8832f0358b6e60 Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Wed, 3 Dec 2025 19:54:16 -0500 Subject: [PATCH] parser: cargo fmt --- parsing/build.rs | 54 ++--- parsing/src/autocomplete.rs | 172 +++++++-------- parsing/src/autocomplete_hashmap.rs | 113 +++++----- parsing/src/lib.rs | 10 +- parsing/src/parsing.rs | 276 ++++++++++++------------ parsing/src/splitting.rs | 324 ++++++++++++++-------------- parsing/src/suggestions.rs | 156 +++++++------- 7 files changed, 558 insertions(+), 547 deletions(-) diff --git a/parsing/build.rs b/parsing/build.rs index 7eef5f5..f07d5e7 100644 --- a/parsing/build.rs +++ b/parsing/build.rs @@ -5,46 +5,46 @@ use std::path::Path; /// REMEMBER TO UPDATE THIS IF EXMEX ADDS NEW FUNCTIONS const SUPPORTED_FUNCTIONS: [&str; 22] = [ - "abs", "signum", "sin", "cos", "tan", "asin", "acos", "atan", "sinh", "cosh", "tanh", "floor", - "round", "ceil", "trunc", "fract", "exp", "sqrt", "cbrt", "ln", "log2", "log10", + "abs", "signum", "sin", "cos", "tan", "asin", "acos", "atan", "sinh", "cosh", "tanh", "floor", + "round", "ceil", "trunc", "fract", "exp", "sqrt", "cbrt", "ln", "log2", "log10", ]; fn main() { - println!("cargo:rerun-if-changed=src/*"); + println!("cargo:rerun-if-changed=src/*"); - generate_hashmap(); + generate_hashmap(); } fn generate_hashmap() { - let path = Path::new(&env::var("OUT_DIR").unwrap()).join("codegen.rs"); - let mut file = BufWriter::new(File::create(path).expect("Could not create file")); + let path = Path::new(&env::var("OUT_DIR").unwrap()).join("codegen.rs"); + let mut file = BufWriter::new(File::create(path).expect("Could not create file")); - let string_hashmap = - compile_hashmap(SUPPORTED_FUNCTIONS.iter().map(|a| a.to_string()).collect()); + let string_hashmap = + compile_hashmap(SUPPORTED_FUNCTIONS.iter().map(|a| a.to_string()).collect()); - let mut hashmap = phf_codegen::Map::new(); + let mut hashmap = phf_codegen::Map::new(); - for (key, value) in string_hashmap.iter() { - hashmap.entry(key, value); - } + for (key, value) in string_hashmap.iter() { + hashmap.entry(key, value); + } - write!( - &mut file, - "static COMPLETION_HASHMAP: phf::Map<&'static str, Hint> = {};", - hashmap.build() - ) - .expect("Could not write to file"); + write!( + &mut file, + "static COMPLETION_HASHMAP: phf::Map<&'static str, Hint> = {};", + hashmap.build() + ) + .expect("Could not write to file"); - write!( - &mut file, - "#[allow(dead_code)] pub const SUPPORTED_FUNCTIONS: [&str; {}] = {:?};", - SUPPORTED_FUNCTIONS.len(), - SUPPORTED_FUNCTIONS.to_vec() - ) - .expect("Could not write to file"); + write!( + &mut file, + "#[allow(dead_code)] pub const SUPPORTED_FUNCTIONS: [&str; {}] = {:?};", + SUPPORTED_FUNCTIONS.len(), + SUPPORTED_FUNCTIONS.to_vec() + ) + .expect("Could not write to file"); } include!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/src/autocomplete_hashmap.rs" + env!("CARGO_MANIFEST_DIR"), + "/src/autocomplete_hashmap.rs" )); diff --git a/parsing/src/autocomplete.rs b/parsing/src/autocomplete.rs index 2bf5c49..4f2cf8d 100644 --- a/parsing/src/autocomplete.rs +++ b/parsing/src/autocomplete.rs @@ -4,113 +4,113 @@ use crate::{generate_hint, Hint, HINT_EMPTY}; #[derive(PartialEq, Debug)] pub enum Movement { - Complete, - #[allow(dead_code)] - Down, - #[allow(dead_code)] - Up, - None, + Complete, + #[allow(dead_code)] + Down, + #[allow(dead_code)] + Up, + None, } impl Movement { - pub const fn is_none(&self) -> bool { - matches!(&self, &Self::None) - } + pub const fn is_none(&self) -> bool { + matches!(&self, &Self::None) + } - pub const fn is_complete(&self) -> bool { - matches!(&self, &Self::Complete) - } + pub const fn is_complete(&self) -> bool { + matches!(&self, &Self::Complete) + } } impl Default for Movement { - fn default() -> Self { - Self::None - } + fn default() -> Self { + Self::None + } } #[derive(Clone, PartialEq)] pub struct AutoComplete<'a> { - pub i: usize, - pub hint: &'a Hint<'a>, - pub string: String, + pub i: usize, + pub hint: &'a Hint<'a>, + pub string: String, } impl<'a> Default for AutoComplete<'a> { - fn default() -> AutoComplete<'a> { - AutoComplete::EMPTY - } + fn default() -> AutoComplete<'a> { + AutoComplete::EMPTY + } } impl<'a> AutoComplete<'a> { - pub const EMPTY: AutoComplete<'a> = Self { - i: 0, - hint: &HINT_EMPTY, - string: String::new(), - }; + pub const EMPTY: AutoComplete<'a> = Self { + i: 0, + hint: &HINT_EMPTY, + string: String::new(), + }; - #[allow(dead_code)] - pub fn update_string(&mut self, string: &str) { - if self.string != string { - // catch empty strings here to avoid call to `generate_hint` and unnecessary logic - if string.is_empty() { - *self = Self::EMPTY; - } else { - self.string = string.to_owned(); - self.do_update_logic(); - } - } - } + #[allow(dead_code)] + pub fn update_string(&mut self, string: &str) { + if self.string != string { + // catch empty strings here to avoid call to `generate_hint` and unnecessary logic + if string.is_empty() { + *self = Self::EMPTY; + } else { + self.string = string.to_owned(); + self.do_update_logic(); + } + } + } - /// Runs update logic assuming that a change to `self.string` has been made - fn do_update_logic(&mut self) { - self.i = 0; - self.hint = generate_hint(&self.string); - } + /// Runs update logic assuming that a change to `self.string` has been made + fn do_update_logic(&mut self) { + self.i = 0; + self.hint = generate_hint(&self.string); + } - #[allow(dead_code)] - pub fn register_movement(&mut self, movement: &Movement) { - if movement.is_none() | self.hint.is_none() { - return; - } + #[allow(dead_code)] + pub fn register_movement(&mut self, movement: &Movement) { + if movement.is_none() | self.hint.is_none() { + return; + } - match self.hint { - Hint::Many(hints) => { - // Impossible for plural hints to be singular or non-existant - debug_assert!(hints.len() > 1); // check on debug + match self.hint { + Hint::Many(hints) => { + // Impossible for plural hints to be singular or non-existant + debug_assert!(hints.len() > 1); // check on debug - match movement { - Movement::Up => { - // Wrap self.i to maximum `i` value if needed - if self.i == 0 { - self.i = hints.len() - 1; - } else { - self.i -= 1; - } - } - Movement::Down => { - // Add one, if resulting value is above maximum `i` value, set `i` to 0 - self.i += 1; - if self.i > (hints.len() - 1) { - self.i = 0; - } - } - Movement::Complete => { - self.apply_hint(unsafe { hints.get_unchecked(self.i) }); - } - _ => unsafe { unreachable_unchecked() }, - } - } - Hint::Single(hint) => { - if movement.is_complete() { - self.apply_hint(hint); - } - } - Hint::None => unsafe { unreachable_unchecked() }, - } - } + match movement { + Movement::Up => { + // Wrap self.i to maximum `i` value if needed + if self.i == 0 { + self.i = hints.len() - 1; + } else { + self.i -= 1; + } + } + Movement::Down => { + // Add one, if resulting value is above maximum `i` value, set `i` to 0 + self.i += 1; + if self.i > (hints.len() - 1) { + self.i = 0; + } + } + Movement::Complete => { + self.apply_hint(unsafe { hints.get_unchecked(self.i) }); + } + _ => unsafe { unreachable_unchecked() }, + } + } + Hint::Single(hint) => { + if movement.is_complete() { + self.apply_hint(hint); + } + } + Hint::None => unsafe { unreachable_unchecked() }, + } + } - pub fn apply_hint(&mut self, hint: &str) { - self.string.push_str(hint); - self.do_update_logic(); - } + pub fn apply_hint(&mut self, hint: &str) { + self.string.push_str(hint); + self.do_update_logic(); + } } diff --git a/parsing/src/autocomplete_hashmap.rs b/parsing/src/autocomplete_hashmap.rs index 537d046..d47fe92 100644 --- a/parsing/src/autocomplete_hashmap.rs +++ b/parsing/src/autocomplete_hashmap.rs @@ -3,81 +3,82 @@ use std::collections::HashSet; /// https://www.dotnetperls.com/sort-rust fn compare_len_reverse_alpha(a: &String, b: &String) -> Ordering { - match a.len().cmp(&b.len()) { - Ordering::Equal => b.cmp(a), - order => order, - } + match a.len().cmp(&b.len()) { + Ordering::Equal => b.cmp(a), + order => order, + } } /// Generates hashmap (well really a vector of tuple of strings that are then turned into a hashmap by phf) #[allow(dead_code)] pub fn compile_hashmap(data: Vec) -> Vec<(String, String)> { - let mut seen = HashSet::new(); + let mut seen = HashSet::new(); - let tuple_list_1: Vec<(String, String)> = data - .iter() - .map(|e| e.to_string() + "(") - .flat_map(|func| all_possible_splits(func, &mut seen)) - .collect(); + let tuple_list_1: Vec<(String, String)> = data + .iter() + .map(|e| e.to_string() + "(") + .flat_map(|func| all_possible_splits(func, &mut seen)) + .collect(); - let keys: Vec<&String> = tuple_list_1.iter().map(|(a, _)| a).collect(); - let mut output: Vec<(String, String)> = Vec::new(); - let mut seen_3: HashSet = HashSet::new(); + let keys: Vec<&String> = tuple_list_1.iter().map(|(a, _)| a).collect(); + let mut output: Vec<(String, String)> = Vec::new(); + let mut seen_3: HashSet = HashSet::new(); - for (key, value) in tuple_list_1.iter() { - if seen_3.contains(key) { - continue; - } + for (key, value) in tuple_list_1.iter() { + if seen_3.contains(key) { + continue; + } - seen_3.insert(key.clone()); + seen_3.insert(key.clone()); - let count_keys = keys.iter().filter(|a| a == &&key).count(); + let count_keys = keys.iter().filter(|a| a == &&key).count(); - match count_keys.cmp(&1usize) { - Ordering::Less => { - panic!("Number of values for {key} is 0!"); - } - Ordering::Greater => { - let mut multi_data = tuple_list_1 - .iter() - .filter(|(a, _)| a == key) - .map(|(_, b)| b) - .collect::>(); - multi_data.sort_unstable_by(|a, b| compare_len_reverse_alpha(a, b)); - output.push((key.clone(), format!("Hint::Many(&{:?})", multi_data))); - } - Ordering::Equal => output.push((key.clone(), format!(r#"Hint::Single("{}")"#, value))), - } - } + match count_keys.cmp(&1usize) { + Ordering::Less => { + panic!("Number of values for {key} is 0!"); + } + Ordering::Greater => { + let mut multi_data = tuple_list_1 + .iter() + .filter(|(a, _)| a == key) + .map(|(_, b)| b) + .collect::>(); + multi_data.sort_unstable_by(|a, b| compare_len_reverse_alpha(a, b)); + output.push((key.clone(), format!("Hint::Many(&{:?})", multi_data))); + } + Ordering::Equal => output.push((key.clone(), format!(r#"Hint::Single("{}")"#, value))), + } + } - // sort - output.sort_unstable_by(|a, b| { - let new_a = format!(r#"("{}", {})"#, a.0, a.1); - let new_b = format!(r#"("{}", {})"#, b.0, b.1); + // sort + output.sort_unstable_by(|a, b| { + let new_a = format!(r#"("{}", {})"#, a.0, a.1); + let new_b = format!(r#"("{}", {})"#, b.0, b.1); - compare_len_reverse_alpha(&new_b, &new_a) - }); + compare_len_reverse_alpha(&new_b, &new_a) + }); - output + output } /// Returns a vector of all possible splitting combinations of a strings #[allow(dead_code)] fn all_possible_splits( - func: String, seen: &mut HashSet<(String, String)>, + func: String, + seen: &mut HashSet<(String, String)>, ) -> Vec<(String, String)> { - (1..func.len()) - .map(|i| { - let (first, last) = func.split_at(i); - (first.to_string(), last.to_string()) - }) - .flat_map(|(first, last)| { - if seen.contains(&(first.clone(), last.clone())) { - return None; - } - seen.insert((first.to_string(), last.to_string())); + (1..func.len()) + .map(|i| { + let (first, last) = func.split_at(i); + (first.to_string(), last.to_string()) + }) + .flat_map(|(first, last)| { + if seen.contains(&(first.clone(), last.clone())) { + return None; + } + seen.insert((first.to_string(), last.to_string())); - Some((first, last)) - }) - .collect::>() + Some((first, last)) + }) + .collect::>() } diff --git a/parsing/src/lib.rs b/parsing/src/lib.rs index 3af4cdc..6e24d68 100644 --- a/parsing/src/lib.rs +++ b/parsing/src/lib.rs @@ -5,9 +5,9 @@ mod splitting; mod suggestions; pub use crate::{ - autocomplete::{AutoComplete, Movement}, - autocomplete_hashmap::compile_hashmap, - parsing::{process_func_str, BackingFunction, FlatExWrapper}, - splitting::{split_function, split_function_chars, SplitType}, - suggestions::{generate_hint, get_last_term, Hint, HINT_EMPTY, SUPPORTED_FUNCTIONS}, + autocomplete::{AutoComplete, Movement}, + autocomplete_hashmap::compile_hashmap, + parsing::{process_func_str, BackingFunction, FlatExWrapper}, + splitting::{split_function, split_function_chars, SplitType}, + suggestions::{generate_hint, get_last_term, Hint, HINT_EMPTY, SUPPORTED_FUNCTIONS}, }; diff --git a/parsing/src/parsing.rs b/parsing/src/parsing.rs index fbb8b2f..4aa8c7d 100644 --- a/parsing/src/parsing.rs +++ b/parsing/src/parsing.rs @@ -3,176 +3,176 @@ use std::collections::HashMap; #[derive(Clone, PartialEq)] pub struct FlatExWrapper { - func: Option>, - func_str: Option, + func: Option>, + func_str: Option, } impl FlatExWrapper { - const EMPTY: FlatExWrapper = FlatExWrapper { - func: None, - func_str: None, - }; + const EMPTY: FlatExWrapper = FlatExWrapper { + func: None, + func_str: None, + }; - #[inline] - const fn new(f: FlatEx) -> Self { - Self { - func: Some(f), - func_str: None, - } - } + #[inline] + const fn new(f: FlatEx) -> Self { + Self { + func: Some(f), + func_str: None, + } + } - #[inline] - const fn is_none(&self) -> bool { - self.func.is_none() - } + #[inline] + const fn is_none(&self) -> bool { + self.func.is_none() + } - #[inline] - pub fn eval(&self, x: &[f64]) -> f64 { - self.func - .as_ref() - .map(|f| f.eval(x).unwrap_or(f64::NAN)) - .unwrap_or(f64::NAN) - } + #[inline] + pub fn eval(&self, x: &[f64]) -> f64 { + self.func + .as_ref() + .map(|f| f.eval(x).unwrap_or(f64::NAN)) + .unwrap_or(f64::NAN) + } - #[inline] - fn partial(&self, x: usize) -> Self { - self.func - .as_ref() - .map(|f| f.clone().partial(x).map(Self::new).unwrap_or(Self::EMPTY)) - .unwrap_or(Self::EMPTY) - } + #[inline] + fn partial(&self, x: usize) -> Self { + self.func + .as_ref() + .map(|f| f.clone().partial(x).map(Self::new).unwrap_or(Self::EMPTY)) + .unwrap_or(Self::EMPTY) + } - #[inline] - fn get_string(&mut self) -> String { - match self.func_str { - Some(ref func_str) => func_str.clone(), - None => { - let calculated = self.func.as_ref().map(|f| f.unparse()).unwrap_or(""); - self.func_str = Some(calculated.to_owned()); - calculated.to_owned() - } - } - } + #[inline] + fn get_string(&mut self) -> String { + match self.func_str { + Some(ref func_str) => func_str.clone(), + None => { + let calculated = self.func.as_ref().map(|f| f.unparse()).unwrap_or(""); + self.func_str = Some(calculated.to_owned()); + calculated.to_owned() + } + } + } - #[inline] - fn partial_iter(&self, n: usize) -> Self { - self.func - .as_ref() - .map(|f| { - f.clone() - .partial_iter((0..=n).map(|_| 0)) - .map(Self::new) - .unwrap_or(Self::EMPTY) - }) - .unwrap_or(Self::EMPTY) - } + #[inline] + fn partial_iter(&self, n: usize) -> Self { + self.func + .as_ref() + .map(|f| { + f.clone() + .partial_iter((0..n).map(|_| 0)) + .map(Self::new) + .unwrap_or(Self::EMPTY) + }) + .unwrap_or(Self::EMPTY) + } } impl Default for FlatExWrapper { - fn default() -> FlatExWrapper { - FlatExWrapper::EMPTY - } + fn default() -> FlatExWrapper { + FlatExWrapper::EMPTY + } } /// Function that includes f(x), f'(x), f'(x)'s string representation, and f''(x) #[derive(Clone, PartialEq)] pub struct BackingFunction { - /// f(x) - function: FlatExWrapper, + /// f(x) + function: FlatExWrapper, - /// Temporary cache for nth derivative - nth_derivative: HashMap, + /// Temporary cache for nth derivative + nth_derivative: HashMap, } impl Default for BackingFunction { - fn default() -> Self { - Self::new("").unwrap() - } + fn default() -> Self { + Self::new("").unwrap() + } } impl BackingFunction { - pub const fn is_none(&self) -> bool { - self.function.is_none() - } + pub const fn is_none(&self) -> bool { + self.function.is_none() + } - /// Create new [`BackingFunction`] instance - pub fn new(func_str: &str) -> Result { - if func_str.is_empty() { - return Ok(Self { - function: FlatExWrapper::EMPTY, - nth_derivative: HashMap::new(), - }); - } + /// Create new [`BackingFunction`] instance + pub fn new(func_str: &str) -> Result { + if func_str.is_empty() { + return Ok(Self { + function: FlatExWrapper::EMPTY, + nth_derivative: HashMap::new(), + }); + } - let function = FlatExWrapper::new({ - let parse_result = exmex::parse::(func_str); + let function = FlatExWrapper::new({ + let parse_result = exmex::parse::(func_str); - match &parse_result { - Err(e) => return Err(e.to_string()), - Ok(ok_result) => { - let var_names = ok_result.var_names().to_vec(); + match &parse_result { + Err(e) => return Err(e.to_string()), + Ok(ok_result) => { + let var_names = ok_result.var_names().to_vec(); - if var_names != ["x"] { - let var_names_not_x: Vec<&String> = var_names - .iter() - .filter(|ele| ele != &"x") - .collect::>(); + if var_names != ["x"] { + let var_names_not_x: Vec<&String> = var_names + .iter() + .filter(|ele| ele != &"x") + .collect::>(); - return Err(format!( - "Error: invalid variable{}", - match var_names_not_x.len() { - 1 => String::from(": ") + var_names_not_x[0].as_str(), - _ => format!("s: {:?}", var_names_not_x), - } - )); - } - } - } - unsafe { parse_result.unwrap_unchecked() } - }); + return Err(format!( + "Error: invalid variable{}", + match var_names_not_x.len() { + 1 => String::from(": ") + var_names_not_x[0].as_str(), + _ => format!("s: {:?}", var_names_not_x), + } + )); + } + } + } + unsafe { parse_result.unwrap_unchecked() } + }); - Ok(Self { - function, + Ok(Self { + function, - nth_derivative: HashMap::new(), - }) - } + nth_derivative: HashMap::new(), + }) + } - // TODO rewrite this logic, it's a mess - pub fn generate_derivative(&mut self, derivative: usize) { - if derivative == 0 { - return; - } + // TODO rewrite this logic, it's a mess + pub fn generate_derivative(&mut self, derivative: usize) { + if derivative == 0 { + return; + } - if !self.nth_derivative.contains_key(&derivative) { - let new_func = self.function.partial_iter(derivative); - self.nth_derivative.insert(derivative, new_func.clone()); - } - } + if !self.nth_derivative.contains_key(&derivative) { + let new_func = self.function.partial_iter(derivative); + self.nth_derivative.insert(derivative, new_func.clone()); + } + } - pub fn get_function_derivative(&self, derivative: usize) -> &FlatExWrapper { - if derivative == 0 { - return &self.function; - } else { - return self - .nth_derivative - .get(&derivative) - .unwrap_or(&FlatExWrapper::EMPTY); - } - } + pub fn get_function_derivative(&self, derivative: usize) -> &FlatExWrapper { + if derivative == 0 { + return &self.function; + } else { + return self + .nth_derivative + .get(&derivative) + .unwrap_or(&FlatExWrapper::EMPTY); + } + } - pub fn get(&mut self, derivative: usize, x: f64) -> f64 { - self.get_function_derivative(derivative).eval(&[x]) - } + pub fn get(&mut self, derivative: usize, x: f64) -> f64 { + self.get_function_derivative(derivative).eval(&[x]) + } } fn prettyify_function_str(func: &str) -> String { - let new_str = func.replace("{x}", "x"); + let new_str = func.replace("{x}", "x"); - if &new_str == "0/0" { - "Undefined".to_owned() - } else { - new_str - } + if &new_str == "0/0" { + "Undefined".to_owned() + } else { + new_str + } } // pub const VALID_VARIABLES: [char; 3] = ['x', 'e', 'π']; @@ -180,15 +180,15 @@ fn prettyify_function_str(func: &str) -> String { /// Case insensitive checks for if `c` is a character used to represent a variable #[inline] pub const fn is_variable(c: &char) -> bool { - let c = c.to_ascii_lowercase(); - (c == 'x') | (c == 'e') | (c == 'π') + let c = c.to_ascii_lowercase(); + (c == 'x') | (c == 'e') | (c == 'π') } /// Adds asterisks where needed in a function pub fn process_func_str(function_in: &str) -> String { - if function_in.is_empty() { - return String::new(); - } + if function_in.is_empty() { + return String::new(); + } - crate::split_function(function_in, crate::SplitType::Multiplication).join("*") + crate::split_function(function_in, crate::SplitType::Multiplication).join("*") } diff --git a/parsing/src/splitting.rs b/parsing/src/splitting.rs index efeda1b..f26eb2c 100644 --- a/parsing/src/splitting.rs +++ b/parsing/src/splitting.rs @@ -1,204 +1,208 @@ use crate::parsing::is_variable; pub fn split_function(input: &str, split: SplitType) -> Vec { - split_function_chars( - &input - .replace("pi", "π") // replace "pi" text with pi symbol - .replace("**", "^") // support alternate manner of expressing exponents - .replace("exp", "\u{1fc93}") // stop-gap solution to fix the `exp` function - .chars() - .collect::>(), - split, - ) - .iter() - .map(|x| x.replace('\u{1fc93}', "exp")) // Convert back to `exp` text - .collect::>() + split_function_chars( + &input + .replace("pi", "π") // replace "pi" text with pi symbol + .replace("**", "^") // support alternate manner of expressing exponents + .replace("exp", "\u{1fc93}") // stop-gap solution to fix the `exp` function + .chars() + .collect::>(), + split, + ) + .iter() + .map(|x| x.replace('\u{1fc93}', "exp")) // Convert back to `exp` text + .collect::>() } // Specifies how to split a function #[derive(PartialEq, Debug, Copy, Clone)] pub enum SplitType { - Multiplication, - Term, + Multiplication, + Term, } /// Used to store info about a character struct BoolSlice { - closing_parens: bool, - open_parens: bool, - number: bool, - letter: bool, - variable: bool, - masked_num: bool, - masked_var: bool, + closing_parens: bool, + open_parens: bool, + number: bool, + letter: bool, + variable: bool, + masked_num: bool, + masked_var: bool, } impl BoolSlice { - 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 { - closing_parens: *c == ')', - open_parens: *c == '(', - number: isnumber, - letter: c.is_ascii_alphabetic(), - variable: isvariable, - masked_num: match isnumber { - true => prev_masked_num, - false => false, - }, - masked_var: match isvariable { - true => prev_masked_var, - false => false, - }, - } - } + 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 { + closing_parens: *c == ')', + open_parens: *c == '(', + number: isnumber, + letter: c.is_ascii_alphabetic(), + variable: isvariable, + masked_num: match isnumber { + true => prev_masked_num, + false => false, + }, + masked_var: match isvariable { + true => prev_masked_var, + false => false, + }, + } + } - const fn is_unmasked_variable(&self) -> bool { self.variable && !self.masked_var } + const fn is_unmasked_variable(&self) -> bool { + self.variable && !self.masked_var + } - const fn is_unmasked_number(&self) -> bool { self.number && !self.masked_num } + const fn is_unmasked_number(&self) -> bool { + self.number && !self.masked_num + } - const fn calculate_mask(&mut self, other: &BoolSlice) { - if other.masked_num && self.number { - // If previous char was a masked number, and current char is a number, mask current char's variable status - self.masked_num = true; - } else if other.masked_var && self.variable { - // If previous char was a masked variable, and current char is a variable, mask current char's variable status - self.masked_var = true; - } else if other.letter && !other.is_unmasked_variable() { - self.masked_num = self.number; - self.masked_var = self.variable; - } - } + const fn calculate_mask(&mut self, other: &BoolSlice) { + if other.masked_num && self.number { + // If previous char was a masked number, and current char is a number, mask current char's variable status + self.masked_num = true; + } else if other.masked_var && self.variable { + // If previous char was a masked variable, and current char is a variable, mask current char's variable status + self.masked_var = true; + } else if other.letter && !other.is_unmasked_variable() { + self.masked_num = self.number; + self.masked_var = self.variable; + } + } - const fn splitable(&self, c: &char, other: &BoolSlice, split: &SplitType) -> bool { - if (*c == '*') | (matches!(split, &SplitType::Term) && other.open_parens) { - true - } else if other.closing_parens { - // Cases like `)x`, `)2`, and `)(` - return (*c == '(') - | (self.letter && !self.is_unmasked_variable()) - | self.is_unmasked_variable() - | self.is_unmasked_number(); - } else if *c == '(' { - // Cases like `x(` and `2(` - return (other.is_unmasked_variable() | other.is_unmasked_number()) && !other.letter; - } else if other.is_unmasked_number() { - // Cases like `2x` and `2sin(x)` - return self.is_unmasked_variable() | self.letter; - } else if self.is_unmasked_variable() | self.letter { - // Cases like `e2` and `xx` - return other.is_unmasked_number() - | (other.is_unmasked_variable() && self.is_unmasked_variable()) - | other.is_unmasked_variable(); - } else if (self.is_unmasked_number() | self.letter | self.is_unmasked_variable()) - && (other.is_unmasked_number() | other.letter) - { - return true; - } else { - return self.is_unmasked_number() && other.is_unmasked_variable(); - } - } + const fn splitable(&self, c: &char, other: &BoolSlice, split: &SplitType) -> bool { + if (*c == '*') | (matches!(split, &SplitType::Term) && other.open_parens) { + true + } else if other.closing_parens { + // Cases like `)x`, `)2`, and `)(` + return (*c == '(') + | (self.letter && !self.is_unmasked_variable()) + | self.is_unmasked_variable() + | self.is_unmasked_number(); + } else if *c == '(' { + // Cases like `x(` and `2(` + return (other.is_unmasked_variable() | other.is_unmasked_number()) && !other.letter; + } else if other.is_unmasked_number() { + // Cases like `2x` and `2sin(x)` + return self.is_unmasked_variable() | self.letter; + } else if self.is_unmasked_variable() | self.letter { + // Cases like `e2` and `xx` + return other.is_unmasked_number() + | (other.is_unmasked_variable() && self.is_unmasked_variable()) + | other.is_unmasked_variable(); + } else if (self.is_unmasked_number() | self.letter | self.is_unmasked_variable()) + && (other.is_unmasked_number() | other.letter) + { + return true; + } else { + return self.is_unmasked_number() && other.is_unmasked_variable(); + } + } } // Splits a function (which is represented as an array of characters) based off of the value of SplitType pub fn split_function_chars(chars: &[char], split: SplitType) -> Vec { - // Catch some basic cases - match chars.len() { - 0 => return Vec::new(), - 1 => return vec![chars[0].to_string()], - _ => {} - } + // Catch some basic cases + match chars.len() { + 0 => return Vec::new(), + 1 => return vec![chars[0].to_string()], + _ => {} + } - // Resulting split-up data - let mut data: Vec = std::vec::from_elem(chars[0].to_string(), 1); + // Resulting split-up data + let mut data: Vec = std::vec::from_elem(chars[0].to_string(), 1); - // Setup first char here - let mut prev_char: BoolSlice = BoolSlice::from_char(&chars[0], false, false); + // Setup first char here + let mut prev_char: BoolSlice = BoolSlice::from_char(&chars[0], false, false); - let mut last = unsafe { data.last_mut().unwrap_unchecked() }; + let mut last = unsafe { data.last_mut().unwrap_unchecked() }; - // Iterate through all chars excluding the first one - for c in chars.iter().skip(1) { - // Set data about current character - let mut curr_c = BoolSlice::from_char(c, prev_char.masked_num, prev_char.masked_var); + // Iterate through all chars excluding the first one + for c in chars.iter().skip(1) { + // Set data about current character + let mut curr_c = BoolSlice::from_char(c, prev_char.masked_num, prev_char.masked_var); - curr_c.calculate_mask(&prev_char); + curr_c.calculate_mask(&prev_char); - // Append split - if curr_c.splitable(c, &prev_char, &split) { - // create new buffer - data.push(String::new()); - last = unsafe { data.last_mut().unwrap_unchecked() }; - } + // Append split + if curr_c.splitable(c, &prev_char, &split) { + // create new buffer + data.push(String::new()); + last = unsafe { data.last_mut().unwrap_unchecked() }; + } - // Exclude asterisks - if c != &'*' { - last.push(*c); - } + // Exclude asterisks + if c != &'*' { + last.push(*c); + } - // Move current character data to `prev_char` - prev_char = curr_c; - } + // Move current character data to `prev_char` + prev_char = curr_c; + } - data + data } #[cfg(test)] fn assert_test(input: &str, expected: &[&str], split: SplitType) { - let output = split_function(input, split); - let expected_owned = expected - .iter() - .map(|&x| x.to_owned()) - .collect::>(); - if output != expected_owned { - panic!( - "split type: {:?} of {} resulted in {:?} not {:?}", - split, input, output, expected - ); - } + let output = split_function(input, split); + let expected_owned = expected + .iter() + .map(|&x| x.to_owned()) + .collect::>(); + if output != expected_owned { + panic!( + "split type: {:?} of {} resulted in {:?} not {:?}", + split, input, output, expected + ); + } } #[test] fn split_function_test() { - assert_test( - "sin(x)cos(x)", - &["sin(x)", "cos(x)"], - SplitType::Multiplication, - ); + assert_test( + "sin(x)cos(x)", + &["sin(x)", "cos(x)"], + SplitType::Multiplication, + ); - assert_test( - "tanh(cos(x)xx)cos(x)", - &["tanh(cos(x)", "x", "x)", "cos(x)"], - SplitType::Multiplication, - ); + assert_test( + "tanh(cos(x)xx)cos(x)", + &["tanh(cos(x)", "x", "x)", "cos(x)"], + SplitType::Multiplication, + ); - assert_test( - "tanh(sin(cos(x)xsin(x)))", - &["tanh(sin(cos(x)", "x", "sin(x)))"], - SplitType::Multiplication, - ); + assert_test( + "tanh(sin(cos(x)xsin(x)))", + &["tanh(sin(cos(x)", "x", "sin(x)))"], + SplitType::Multiplication, + ); - // Some test cases from https://github.com/GraphiteEditor/Graphite/blob/2515620a77478e57c255cd7d97c13cc7065dd99d/frontend/wasm/src/editor_api.rs#L829-L840 - assert_test("2pi", &["2", "π"], SplitType::Multiplication); - assert_test("sin(2pi)", &["sin(2", "π)"], SplitType::Multiplication); - assert_test("2sin(pi)", &["2", "sin(π)"], SplitType::Multiplication); - assert_test( - "2sin(3(4 + 5))", - &["2", "sin(3", "(4 + 5))"], - SplitType::Multiplication, - ); - assert_test("3abs(-4)", &["3", "abs(-4)"], SplitType::Multiplication); - assert_test("-1(4)", &["-1", "(4)"], SplitType::Multiplication); - assert_test("(-1)4", &["(-1)", "4"], SplitType::Multiplication); - assert_test( - "(((-1)))(4)", - &["(((-1)))", "(4)"], - SplitType::Multiplication, - ); - assert_test( - "2sin(π) + 2cos(tau)", - &["2", "sin(π) + 2", "cos(tau)"], - SplitType::Multiplication, - ); + // Some test cases from https://github.com/GraphiteEditor/Graphite/blob/2515620a77478e57c255cd7d97c13cc7065dd99d/frontend/wasm/src/editor_api.rs#L829-L840 + assert_test("2pi", &["2", "π"], SplitType::Multiplication); + assert_test("sin(2pi)", &["sin(2", "π)"], SplitType::Multiplication); + assert_test("2sin(pi)", &["2", "sin(π)"], SplitType::Multiplication); + assert_test( + "2sin(3(4 + 5))", + &["2", "sin(3", "(4 + 5))"], + SplitType::Multiplication, + ); + assert_test("3abs(-4)", &["3", "abs(-4)"], SplitType::Multiplication); + assert_test("-1(4)", &["-1", "(4)"], SplitType::Multiplication); + assert_test("(-1)4", &["(-1)", "4"], SplitType::Multiplication); + assert_test( + "(((-1)))(4)", + &["(((-1)))", "(4)"], + SplitType::Multiplication, + ); + assert_test( + "2sin(π) + 2cos(tau)", + &["2", "sin(π) + 2", "cos(tau)"], + SplitType::Multiplication, + ); } diff --git a/parsing/src/suggestions.rs b/parsing/src/suggestions.rs index ee2568b..e1a38cd 100644 --- a/parsing/src/suggestions.rs +++ b/parsing/src/suggestions.rs @@ -14,106 +14,112 @@ macro_rules! test_print { /// Generate a hint based on the input `input`, returns an `Option` pub fn generate_hint<'a>(input: &str) -> &'a Hint<'a> { - if input.is_empty() { - &HINT_EMPTY - } else { - let chars: Vec = input.chars().collect::>(); + if input.is_empty() { + &HINT_EMPTY + } else { + let chars: Vec = input.chars().collect::>(); - let key = get_last_term(&chars); - match key { - Some(key) => { - if let Some(hint) = COMPLETION_HASHMAP.get(&key) { - return hint; - } - } - None => { - return &Hint::None; - } - } + let key = get_last_term(&chars); + match key { + Some(key) => { + if let Some(hint) = COMPLETION_HASHMAP.get(&key) { + return hint; + } + } + None => { + return &Hint::None; + } + } - let mut open_parens: usize = 0; - let mut closed_parens: usize = 0; - chars.iter().for_each(|chr| match *chr { - '(' => open_parens += 1, - ')' => closed_parens += 1, - _ => {} - }); + let mut open_parens: usize = 0; + let mut closed_parens: usize = 0; + chars.iter().for_each(|chr| match *chr { + '(' => open_parens += 1, + ')' => closed_parens += 1, + _ => {} + }); - if open_parens > closed_parens { - return &HINT_CLOSED_PARENS; - } + if open_parens > closed_parens { + return &HINT_CLOSED_PARENS; + } - &Hint::None - } + &Hint::None + } } pub fn get_last_term(chars: &[char]) -> Option { - if chars.is_empty() { - return None; - } + if chars.is_empty() { + return None; + } - let mut result = split_function_chars(chars, SplitType::Term); - result.pop() + let mut result = split_function_chars(chars, SplitType::Term); + result.pop() } #[derive(PartialEq, Clone, Copy)] pub enum Hint<'a> { - Single(&'a str), - Many(&'a [&'a str]), - None, + Single(&'a str), + Many(&'a [&'a str]), + None, } impl<'a> std::fmt::Display for Hint<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Hint::Single(single_data) => { - write!(f, "{}", single_data) - } - Hint::Many(multi_data) => { - write!(f, "{:?}", multi_data) - } - Hint::None => { - write!(f, "None") - } - } - } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Hint::Single(single_data) => { + write!(f, "{}", single_data) + } + Hint::Many(multi_data) => { + write!(f, "{:?}", multi_data) + } + Hint::None => { + write!(f, "None") + } + } + } } impl<'a> std::fmt::Debug for Hint<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - std::fmt::Display::fmt(self, f) - } + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(self, f) + } } impl<'a> Hint<'a> { - #[inline] - pub const fn is_none(&self) -> bool { matches!(&self, &Hint::None) } + #[inline] + pub const fn is_none(&self) -> bool { + matches!(&self, &Hint::None) + } - #[inline] - #[allow(dead_code)] - pub const fn is_some(&self) -> bool { !self.is_none() } + #[inline] + #[allow(dead_code)] + pub const fn is_some(&self) -> bool { + !self.is_none() + } - #[inline] - #[allow(dead_code)] - pub const fn is_single(&self) -> bool { matches!(&self, &Hint::Single(_)) } + #[inline] + #[allow(dead_code)] + pub const fn is_single(&self) -> bool { + matches!(&self, &Hint::Single(_)) + } - #[inline] - #[allow(dead_code)] - pub const fn single(&self) -> Option<&str> { - match self { - Hint::Single(data) => Some(data), - _ => None, - } - } + #[inline] + #[allow(dead_code)] + pub const fn single(&self) -> Option<&str> { + match self { + Hint::Single(data) => Some(data), + _ => None, + } + } - #[inline] - #[allow(dead_code)] - pub const fn many(&self) -> Option<&[&str]> { - match self { - Hint::Many(data) => Some(data), - _ => None, - } - } + #[inline] + #[allow(dead_code)] + pub const fn many(&self) -> Option<&[&str]> { + match self { + Hint::Many(data) => Some(data), + _ => None, + } + } } include!(concat!(env!("OUT_DIR"), "/codegen.rs"));