parser: cargo fmt

This commit is contained in:
Simon Gardling 2025-12-03 19:54:16 -05:00
parent 2378f719a7
commit 9d96977785
Signed by: titaniumtown
GPG Key ID: 9AB28AC10ECE533D
7 changed files with 558 additions and 547 deletions

View File

@ -5,46 +5,46 @@ use std::path::Path;
/// REMEMBER TO UPDATE THIS IF EXMEX ADDS NEW FUNCTIONS /// REMEMBER TO UPDATE THIS IF EXMEX ADDS NEW FUNCTIONS
const SUPPORTED_FUNCTIONS: [&str; 22] = [ const SUPPORTED_FUNCTIONS: [&str; 22] = [
"abs", "signum", "sin", "cos", "tan", "asin", "acos", "atan", "sinh", "cosh", "tanh", "floor", "abs", "signum", "sin", "cos", "tan", "asin", "acos", "atan", "sinh", "cosh", "tanh", "floor",
"round", "ceil", "trunc", "fract", "exp", "sqrt", "cbrt", "ln", "log2", "log10", "round", "ceil", "trunc", "fract", "exp", "sqrt", "cbrt", "ln", "log2", "log10",
]; ];
fn main() { fn main() {
println!("cargo:rerun-if-changed=src/*"); println!("cargo:rerun-if-changed=src/*");
generate_hashmap(); generate_hashmap();
} }
fn generate_hashmap() { fn generate_hashmap() {
let path = Path::new(&env::var("OUT_DIR").unwrap()).join("codegen.rs"); 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 mut file = BufWriter::new(File::create(path).expect("Could not create file"));
let string_hashmap = let string_hashmap =
compile_hashmap(SUPPORTED_FUNCTIONS.iter().map(|a| a.to_string()).collect()); 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() { for (key, value) in string_hashmap.iter() {
hashmap.entry(key, value); hashmap.entry(key, value);
} }
write!( write!(
&mut file, &mut file,
"static COMPLETION_HASHMAP: phf::Map<&'static str, Hint> = {};", "static COMPLETION_HASHMAP: phf::Map<&'static str, Hint> = {};",
hashmap.build() hashmap.build()
) )
.expect("Could not write to file"); .expect("Could not write to file");
write!( write!(
&mut file, &mut file,
"#[allow(dead_code)] pub const SUPPORTED_FUNCTIONS: [&str; {}] = {:?};", "#[allow(dead_code)] pub const SUPPORTED_FUNCTIONS: [&str; {}] = {:?};",
SUPPORTED_FUNCTIONS.len(), SUPPORTED_FUNCTIONS.len(),
SUPPORTED_FUNCTIONS.to_vec() SUPPORTED_FUNCTIONS.to_vec()
) )
.expect("Could not write to file"); .expect("Could not write to file");
} }
include!(concat!( include!(concat!(
env!("CARGO_MANIFEST_DIR"), env!("CARGO_MANIFEST_DIR"),
"/src/autocomplete_hashmap.rs" "/src/autocomplete_hashmap.rs"
)); ));

View File

@ -4,113 +4,113 @@ use crate::{generate_hint, Hint, HINT_EMPTY};
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub enum Movement { pub enum Movement {
Complete, Complete,
#[allow(dead_code)] #[allow(dead_code)]
Down, Down,
#[allow(dead_code)] #[allow(dead_code)]
Up, Up,
None, None,
} }
impl Movement { impl Movement {
pub const fn is_none(&self) -> bool { pub const fn is_none(&self) -> bool {
matches!(&self, &Self::None) matches!(&self, &Self::None)
} }
pub const fn is_complete(&self) -> bool { pub const fn is_complete(&self) -> bool {
matches!(&self, &Self::Complete) matches!(&self, &Self::Complete)
} }
} }
impl Default for Movement { impl Default for Movement {
fn default() -> Self { fn default() -> Self {
Self::None Self::None
} }
} }
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct AutoComplete<'a> { pub struct AutoComplete<'a> {
pub i: usize, pub i: usize,
pub hint: &'a Hint<'a>, pub hint: &'a Hint<'a>,
pub string: String, pub string: String,
} }
impl<'a> Default for AutoComplete<'a> { impl<'a> Default for AutoComplete<'a> {
fn default() -> AutoComplete<'a> { fn default() -> AutoComplete<'a> {
AutoComplete::EMPTY AutoComplete::EMPTY
} }
} }
impl<'a> AutoComplete<'a> { impl<'a> AutoComplete<'a> {
pub const EMPTY: AutoComplete<'a> = Self { pub const EMPTY: AutoComplete<'a> = Self {
i: 0, i: 0,
hint: &HINT_EMPTY, hint: &HINT_EMPTY,
string: String::new(), string: String::new(),
}; };
#[allow(dead_code)] #[allow(dead_code)]
pub fn update_string(&mut self, string: &str) { pub fn update_string(&mut self, string: &str) {
if self.string != string { if self.string != string {
// catch empty strings here to avoid call to `generate_hint` and unnecessary logic // catch empty strings here to avoid call to `generate_hint` and unnecessary logic
if string.is_empty() { if string.is_empty() {
*self = Self::EMPTY; *self = Self::EMPTY;
} else { } else {
self.string = string.to_owned(); self.string = string.to_owned();
self.do_update_logic(); self.do_update_logic();
} }
} }
} }
/// Runs update logic assuming that a change to `self.string` has been made /// Runs update logic assuming that a change to `self.string` has been made
fn do_update_logic(&mut self) { fn do_update_logic(&mut self) {
self.i = 0; self.i = 0;
self.hint = generate_hint(&self.string); self.hint = generate_hint(&self.string);
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn register_movement(&mut self, movement: &Movement) { pub fn register_movement(&mut self, movement: &Movement) {
if movement.is_none() | self.hint.is_none() { if movement.is_none() | self.hint.is_none() {
return; return;
} }
match self.hint { match self.hint {
Hint::Many(hints) => { Hint::Many(hints) => {
// Impossible for plural hints to be singular or non-existant // Impossible for plural hints to be singular or non-existant
debug_assert!(hints.len() > 1); // check on debug debug_assert!(hints.len() > 1); // check on debug
match movement { match movement {
Movement::Up => { Movement::Up => {
// Wrap self.i to maximum `i` value if needed // Wrap self.i to maximum `i` value if needed
if self.i == 0 { if self.i == 0 {
self.i = hints.len() - 1; self.i = hints.len() - 1;
} else { } else {
self.i -= 1; self.i -= 1;
} }
} }
Movement::Down => { Movement::Down => {
// Add one, if resulting value is above maximum `i` value, set `i` to 0 // Add one, if resulting value is above maximum `i` value, set `i` to 0
self.i += 1; self.i += 1;
if self.i > (hints.len() - 1) { if self.i > (hints.len() - 1) {
self.i = 0; self.i = 0;
} }
} }
Movement::Complete => { Movement::Complete => {
self.apply_hint(unsafe { hints.get_unchecked(self.i) }); self.apply_hint(unsafe { hints.get_unchecked(self.i) });
} }
_ => unsafe { unreachable_unchecked() }, _ => unsafe { unreachable_unchecked() },
} }
} }
Hint::Single(hint) => { Hint::Single(hint) => {
if movement.is_complete() { if movement.is_complete() {
self.apply_hint(hint); self.apply_hint(hint);
} }
} }
Hint::None => unsafe { unreachable_unchecked() }, Hint::None => unsafe { unreachable_unchecked() },
} }
} }
pub fn apply_hint(&mut self, hint: &str) { pub fn apply_hint(&mut self, hint: &str) {
self.string.push_str(hint); self.string.push_str(hint);
self.do_update_logic(); self.do_update_logic();
} }
} }

View File

@ -3,81 +3,82 @@ use std::collections::HashSet;
/// https://www.dotnetperls.com/sort-rust /// https://www.dotnetperls.com/sort-rust
fn compare_len_reverse_alpha(a: &String, b: &String) -> Ordering { fn compare_len_reverse_alpha(a: &String, b: &String) -> Ordering {
match a.len().cmp(&b.len()) { match a.len().cmp(&b.len()) {
Ordering::Equal => b.cmp(a), Ordering::Equal => b.cmp(a),
order => order, order => order,
} }
} }
/// Generates hashmap (well really a vector of tuple of strings that are then turned into a hashmap by phf) /// Generates hashmap (well really a vector of tuple of strings that are then turned into a hashmap by phf)
#[allow(dead_code)] #[allow(dead_code)]
pub fn compile_hashmap(data: Vec<String>) -> Vec<(String, String)> { pub fn compile_hashmap(data: Vec<String>) -> Vec<(String, String)> {
let mut seen = HashSet::new(); let mut seen = HashSet::new();
let tuple_list_1: Vec<(String, String)> = data let tuple_list_1: Vec<(String, String)> = data
.iter() .iter()
.map(|e| e.to_string() + "(") .map(|e| e.to_string() + "(")
.flat_map(|func| all_possible_splits(func, &mut seen)) .flat_map(|func| all_possible_splits(func, &mut seen))
.collect(); .collect();
let keys: Vec<&String> = tuple_list_1.iter().map(|(a, _)| a).collect(); let keys: Vec<&String> = tuple_list_1.iter().map(|(a, _)| a).collect();
let mut output: Vec<(String, String)> = Vec::new(); let mut output: Vec<(String, String)> = Vec::new();
let mut seen_3: HashSet<String> = HashSet::new(); let mut seen_3: HashSet<String> = HashSet::new();
for (key, value) in tuple_list_1.iter() { for (key, value) in tuple_list_1.iter() {
if seen_3.contains(key) { if seen_3.contains(key) {
continue; 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) { match count_keys.cmp(&1usize) {
Ordering::Less => { Ordering::Less => {
panic!("Number of values for {key} is 0!"); panic!("Number of values for {key} is 0!");
} }
Ordering::Greater => { Ordering::Greater => {
let mut multi_data = tuple_list_1 let mut multi_data = tuple_list_1
.iter() .iter()
.filter(|(a, _)| a == key) .filter(|(a, _)| a == key)
.map(|(_, b)| b) .map(|(_, b)| b)
.collect::<Vec<&String>>(); .collect::<Vec<&String>>();
multi_data.sort_unstable_by(|a, b| compare_len_reverse_alpha(a, b)); multi_data.sort_unstable_by(|a, b| compare_len_reverse_alpha(a, b));
output.push((key.clone(), format!("Hint::Many(&{:?})", multi_data))); output.push((key.clone(), format!("Hint::Many(&{:?})", multi_data)));
} }
Ordering::Equal => output.push((key.clone(), format!(r#"Hint::Single("{}")"#, value))), Ordering::Equal => output.push((key.clone(), format!(r#"Hint::Single("{}")"#, value))),
} }
} }
// sort // sort
output.sort_unstable_by(|a, b| { output.sort_unstable_by(|a, b| {
let new_a = format!(r#"("{}", {})"#, a.0, a.1); let new_a = format!(r#"("{}", {})"#, a.0, a.1);
let new_b = format!(r#"("{}", {})"#, b.0, b.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 /// Returns a vector of all possible splitting combinations of a strings
#[allow(dead_code)] #[allow(dead_code)]
fn all_possible_splits( fn all_possible_splits(
func: String, seen: &mut HashSet<(String, String)>, func: String,
seen: &mut HashSet<(String, String)>,
) -> Vec<(String, String)> { ) -> Vec<(String, String)> {
(1..func.len()) (1..func.len())
.map(|i| { .map(|i| {
let (first, last) = func.split_at(i); let (first, last) = func.split_at(i);
(first.to_string(), last.to_string()) (first.to_string(), last.to_string())
}) })
.flat_map(|(first, last)| { .flat_map(|(first, last)| {
if seen.contains(&(first.clone(), last.clone())) { if seen.contains(&(first.clone(), last.clone())) {
return None; return None;
} }
seen.insert((first.to_string(), last.to_string())); seen.insert((first.to_string(), last.to_string()));
Some((first, last)) Some((first, last))
}) })
.collect::<Vec<(String, String)>>() .collect::<Vec<(String, String)>>()
} }

View File

@ -5,9 +5,9 @@ mod splitting;
mod suggestions; mod suggestions;
pub use crate::{ pub use crate::{
autocomplete::{AutoComplete, Movement}, autocomplete::{AutoComplete, Movement},
autocomplete_hashmap::compile_hashmap, autocomplete_hashmap::compile_hashmap,
parsing::{process_func_str, BackingFunction, FlatExWrapper}, parsing::{process_func_str, BackingFunction, FlatExWrapper},
splitting::{split_function, split_function_chars, SplitType}, splitting::{split_function, split_function_chars, SplitType},
suggestions::{generate_hint, get_last_term, Hint, HINT_EMPTY, SUPPORTED_FUNCTIONS}, suggestions::{generate_hint, get_last_term, Hint, HINT_EMPTY, SUPPORTED_FUNCTIONS},
}; };

View File

@ -3,176 +3,176 @@ use std::collections::HashMap;
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct FlatExWrapper { pub struct FlatExWrapper {
func: Option<FlatEx<f64>>, func: Option<FlatEx<f64>>,
func_str: Option<String>, func_str: Option<String>,
} }
impl FlatExWrapper { impl FlatExWrapper {
const EMPTY: FlatExWrapper = FlatExWrapper { const EMPTY: FlatExWrapper = FlatExWrapper {
func: None, func: None,
func_str: None, func_str: None,
}; };
#[inline] #[inline]
const fn new(f: FlatEx<f64>) -> Self { const fn new(f: FlatEx<f64>) -> Self {
Self { Self {
func: Some(f), func: Some(f),
func_str: None, func_str: None,
} }
} }
#[inline] #[inline]
const fn is_none(&self) -> bool { const fn is_none(&self) -> bool {
self.func.is_none() self.func.is_none()
} }
#[inline] #[inline]
pub fn eval(&self, x: &[f64]) -> f64 { pub fn eval(&self, x: &[f64]) -> f64 {
self.func self.func
.as_ref() .as_ref()
.map(|f| f.eval(x).unwrap_or(f64::NAN)) .map(|f| f.eval(x).unwrap_or(f64::NAN))
.unwrap_or(f64::NAN) .unwrap_or(f64::NAN)
} }
#[inline] #[inline]
fn partial(&self, x: usize) -> Self { fn partial(&self, x: usize) -> Self {
self.func self.func
.as_ref() .as_ref()
.map(|f| f.clone().partial(x).map(Self::new).unwrap_or(Self::EMPTY)) .map(|f| f.clone().partial(x).map(Self::new).unwrap_or(Self::EMPTY))
.unwrap_or(Self::EMPTY) .unwrap_or(Self::EMPTY)
} }
#[inline] #[inline]
fn get_string(&mut self) -> String { fn get_string(&mut self) -> String {
match self.func_str { match self.func_str {
Some(ref func_str) => func_str.clone(), Some(ref func_str) => func_str.clone(),
None => { None => {
let calculated = self.func.as_ref().map(|f| f.unparse()).unwrap_or(""); let calculated = self.func.as_ref().map(|f| f.unparse()).unwrap_or("");
self.func_str = Some(calculated.to_owned()); self.func_str = Some(calculated.to_owned());
calculated.to_owned() calculated.to_owned()
} }
} }
} }
#[inline] #[inline]
fn partial_iter(&self, n: usize) -> Self { fn partial_iter(&self, n: usize) -> Self {
self.func self.func
.as_ref() .as_ref()
.map(|f| { .map(|f| {
f.clone() f.clone()
.partial_iter((0..=n).map(|_| 0)) .partial_iter((0..n).map(|_| 0))
.map(Self::new) .map(Self::new)
.unwrap_or(Self::EMPTY) .unwrap_or(Self::EMPTY)
}) })
.unwrap_or(Self::EMPTY) .unwrap_or(Self::EMPTY)
} }
} }
impl Default for FlatExWrapper { impl Default for FlatExWrapper {
fn default() -> FlatExWrapper { fn default() -> FlatExWrapper {
FlatExWrapper::EMPTY FlatExWrapper::EMPTY
} }
} }
/// Function that includes f(x), f'(x), f'(x)'s string representation, and f''(x) /// Function that includes f(x), f'(x), f'(x)'s string representation, and f''(x)
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct BackingFunction { pub struct BackingFunction {
/// f(x) /// f(x)
function: FlatExWrapper, function: FlatExWrapper,
/// Temporary cache for nth derivative /// Temporary cache for nth derivative
nth_derivative: HashMap<usize, FlatExWrapper>, nth_derivative: HashMap<usize, FlatExWrapper>,
} }
impl Default for BackingFunction { impl Default for BackingFunction {
fn default() -> Self { fn default() -> Self {
Self::new("").unwrap() Self::new("").unwrap()
} }
} }
impl BackingFunction { impl BackingFunction {
pub const fn is_none(&self) -> bool { pub const fn is_none(&self) -> bool {
self.function.is_none() self.function.is_none()
} }
/// Create new [`BackingFunction`] instance /// Create new [`BackingFunction`] instance
pub fn new(func_str: &str) -> Result<Self, String> { pub fn new(func_str: &str) -> Result<Self, String> {
if func_str.is_empty() { if func_str.is_empty() {
return Ok(Self { return Ok(Self {
function: FlatExWrapper::EMPTY, function: FlatExWrapper::EMPTY,
nth_derivative: HashMap::new(), nth_derivative: HashMap::new(),
}); });
} }
let function = FlatExWrapper::new({ let function = FlatExWrapper::new({
let parse_result = exmex::parse::<f64>(func_str); let parse_result = exmex::parse::<f64>(func_str);
match &parse_result { match &parse_result {
Err(e) => return Err(e.to_string()), Err(e) => return Err(e.to_string()),
Ok(ok_result) => { Ok(ok_result) => {
let var_names = ok_result.var_names().to_vec(); let var_names = ok_result.var_names().to_vec();
if var_names != ["x"] { if var_names != ["x"] {
let var_names_not_x: Vec<&String> = var_names let var_names_not_x: Vec<&String> = var_names
.iter() .iter()
.filter(|ele| ele != &"x") .filter(|ele| ele != &"x")
.collect::<Vec<&String>>(); .collect::<Vec<&String>>();
return Err(format!( return Err(format!(
"Error: invalid variable{}", "Error: invalid variable{}",
match var_names_not_x.len() { match var_names_not_x.len() {
1 => String::from(": ") + var_names_not_x[0].as_str(), 1 => String::from(": ") + var_names_not_x[0].as_str(),
_ => format!("s: {:?}", var_names_not_x), _ => format!("s: {:?}", var_names_not_x),
} }
)); ));
} }
} }
} }
unsafe { parse_result.unwrap_unchecked() } unsafe { parse_result.unwrap_unchecked() }
}); });
Ok(Self { Ok(Self {
function, function,
nth_derivative: HashMap::new(), nth_derivative: HashMap::new(),
}) })
} }
// TODO rewrite this logic, it's a mess // TODO rewrite this logic, it's a mess
pub fn generate_derivative(&mut self, derivative: usize) { pub fn generate_derivative(&mut self, derivative: usize) {
if derivative == 0 { if derivative == 0 {
return; return;
} }
if !self.nth_derivative.contains_key(&derivative) { if !self.nth_derivative.contains_key(&derivative) {
let new_func = self.function.partial_iter(derivative); let new_func = self.function.partial_iter(derivative);
self.nth_derivative.insert(derivative, new_func.clone()); self.nth_derivative.insert(derivative, new_func.clone());
} }
} }
pub fn get_function_derivative(&self, derivative: usize) -> &FlatExWrapper { pub fn get_function_derivative(&self, derivative: usize) -> &FlatExWrapper {
if derivative == 0 { if derivative == 0 {
return &self.function; return &self.function;
} else { } else {
return self return self
.nth_derivative .nth_derivative
.get(&derivative) .get(&derivative)
.unwrap_or(&FlatExWrapper::EMPTY); .unwrap_or(&FlatExWrapper::EMPTY);
} }
} }
pub fn get(&mut self, derivative: usize, x: f64) -> f64 { pub fn get(&mut self, derivative: usize, x: f64) -> f64 {
self.get_function_derivative(derivative).eval(&[x]) self.get_function_derivative(derivative).eval(&[x])
} }
} }
fn prettyify_function_str(func: &str) -> String { 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" { if &new_str == "0/0" {
"Undefined".to_owned() "Undefined".to_owned()
} else { } else {
new_str new_str
} }
} }
// pub const VALID_VARIABLES: [char; 3] = ['x', 'e', 'π']; // 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 /// Case insensitive checks for if `c` is a character used to represent a variable
#[inline] #[inline]
pub const fn is_variable(c: &char) -> bool { pub const fn is_variable(c: &char) -> bool {
let c = c.to_ascii_lowercase(); let c = c.to_ascii_lowercase();
(c == 'x') | (c == 'e') | (c == 'π') (c == 'x') | (c == 'e') | (c == 'π')
} }
/// Adds asterisks where needed in a function /// Adds asterisks where needed in a function
pub fn process_func_str(function_in: &str) -> String { pub fn process_func_str(function_in: &str) -> String {
if function_in.is_empty() { if function_in.is_empty() {
return String::new(); return String::new();
} }
crate::split_function(function_in, crate::SplitType::Multiplication).join("*") crate::split_function(function_in, crate::SplitType::Multiplication).join("*")
} }

View File

@ -1,204 +1,208 @@
use crate::parsing::is_variable; use crate::parsing::is_variable;
pub fn split_function(input: &str, split: SplitType) -> Vec<String> { pub fn split_function(input: &str, split: SplitType) -> Vec<String> {
split_function_chars( split_function_chars(
&input &input
.replace("pi", "π") // replace "pi" text with pi symbol .replace("pi", "π") // replace "pi" text with pi symbol
.replace("**", "^") // support alternate manner of expressing exponents .replace("**", "^") // support alternate manner of expressing exponents
.replace("exp", "\u{1fc93}") // stop-gap solution to fix the `exp` function .replace("exp", "\u{1fc93}") // stop-gap solution to fix the `exp` function
.chars() .chars()
.collect::<Vec<char>>(), .collect::<Vec<char>>(),
split, split,
) )
.iter() .iter()
.map(|x| x.replace('\u{1fc93}', "exp")) // Convert back to `exp` text .map(|x| x.replace('\u{1fc93}', "exp")) // Convert back to `exp` text
.collect::<Vec<String>>() .collect::<Vec<String>>()
} }
// Specifies how to split a function // Specifies how to split a function
#[derive(PartialEq, Debug, Copy, Clone)] #[derive(PartialEq, Debug, Copy, Clone)]
pub enum SplitType { pub enum SplitType {
Multiplication, Multiplication,
Term, Term,
} }
/// Used to store info about a character /// Used to store info about a character
struct BoolSlice { struct BoolSlice {
closing_parens: bool, closing_parens: bool,
open_parens: bool, open_parens: bool,
number: bool, number: bool,
letter: bool, letter: bool,
variable: bool, variable: bool,
masked_num: bool, masked_num: bool,
masked_var: bool, masked_var: bool,
} }
impl BoolSlice { impl BoolSlice {
const 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 isnumber = c.is_ascii_digit();
let isvariable = is_variable(c); let isvariable = is_variable(c);
Self { Self {
closing_parens: *c == ')', closing_parens: *c == ')',
open_parens: *c == '(', open_parens: *c == '(',
number: isnumber, number: isnumber,
letter: c.is_ascii_alphabetic(), letter: c.is_ascii_alphabetic(),
variable: isvariable, variable: isvariable,
masked_num: match isnumber { masked_num: match isnumber {
true => prev_masked_num, true => prev_masked_num,
false => false, false => false,
}, },
masked_var: match isvariable { masked_var: match isvariable {
true => prev_masked_var, true => prev_masked_var,
false => false, 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) { const fn calculate_mask(&mut self, other: &BoolSlice) {
if other.masked_num && self.number { 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 // If previous char was a masked number, and current char is a number, mask current char's variable status
self.masked_num = true; self.masked_num = true;
} else if other.masked_var && self.variable { } 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 // If previous char was a masked variable, and current char is a variable, mask current char's variable status
self.masked_var = true; self.masked_var = true;
} else if other.letter && !other.is_unmasked_variable() { } else if other.letter && !other.is_unmasked_variable() {
self.masked_num = self.number; self.masked_num = self.number;
self.masked_var = self.variable; self.masked_var = self.variable;
} }
} }
const fn splitable(&self, c: &char, other: &BoolSlice, split: &SplitType) -> bool { const fn splitable(&self, c: &char, other: &BoolSlice, split: &SplitType) -> bool {
if (*c == '*') | (matches!(split, &SplitType::Term) && other.open_parens) { if (*c == '*') | (matches!(split, &SplitType::Term) && other.open_parens) {
true true
} else if other.closing_parens { } else if other.closing_parens {
// Cases like `)x`, `)2`, and `)(` // Cases like `)x`, `)2`, and `)(`
return (*c == '(') return (*c == '(')
| (self.letter && !self.is_unmasked_variable()) | (self.letter && !self.is_unmasked_variable())
| self.is_unmasked_variable() | self.is_unmasked_variable()
| self.is_unmasked_number(); | self.is_unmasked_number();
} else if *c == '(' { } else if *c == '(' {
// Cases like `x(` and `2(` // Cases like `x(` and `2(`
return (other.is_unmasked_variable() | other.is_unmasked_number()) && !other.letter; return (other.is_unmasked_variable() | other.is_unmasked_number()) && !other.letter;
} else if other.is_unmasked_number() { } else if other.is_unmasked_number() {
// Cases like `2x` and `2sin(x)` // Cases like `2x` and `2sin(x)`
return self.is_unmasked_variable() | self.letter; return self.is_unmasked_variable() | self.letter;
} else if self.is_unmasked_variable() | self.letter { } else if self.is_unmasked_variable() | self.letter {
// Cases like `e2` and `xx` // Cases like `e2` and `xx`
return other.is_unmasked_number() return other.is_unmasked_number()
| (other.is_unmasked_variable() && self.is_unmasked_variable()) | (other.is_unmasked_variable() && self.is_unmasked_variable())
| other.is_unmasked_variable(); | other.is_unmasked_variable();
} else if (self.is_unmasked_number() | self.letter | self.is_unmasked_variable()) } else if (self.is_unmasked_number() | self.letter | self.is_unmasked_variable())
&& (other.is_unmasked_number() | other.letter) && (other.is_unmasked_number() | other.letter)
{ {
return true; return true;
} else { } else {
return self.is_unmasked_number() && other.is_unmasked_variable(); 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 // 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<String> { pub fn split_function_chars(chars: &[char], split: SplitType) -> Vec<String> {
// Catch some basic cases // Catch some basic cases
match chars.len() { match chars.len() {
0 => return Vec::new(), 0 => return Vec::new(),
1 => return vec![chars[0].to_string()], 1 => return vec![chars[0].to_string()],
_ => {} _ => {}
} }
// Resulting split-up data // Resulting split-up data
let mut data: Vec<String> = std::vec::from_elem(chars[0].to_string(), 1); let mut data: Vec<String> = std::vec::from_elem(chars[0].to_string(), 1);
// Setup first char here // Setup first char here
let mut prev_char: BoolSlice = BoolSlice::from_char(&chars[0], false, false); 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 // Iterate through all chars excluding the first one
for c in chars.iter().skip(1) { for c in chars.iter().skip(1) {
// Set data about current character // Set data about current character
let mut curr_c = BoolSlice::from_char(c, prev_char.masked_num, prev_char.masked_var); 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 // Append split
if curr_c.splitable(c, &prev_char, &split) { if curr_c.splitable(c, &prev_char, &split) {
// create new buffer // create new buffer
data.push(String::new()); data.push(String::new());
last = unsafe { data.last_mut().unwrap_unchecked() }; last = unsafe { data.last_mut().unwrap_unchecked() };
} }
// Exclude asterisks // Exclude asterisks
if c != &'*' { if c != &'*' {
last.push(*c); last.push(*c);
} }
// Move current character data to `prev_char` // Move current character data to `prev_char`
prev_char = curr_c; prev_char = curr_c;
} }
data data
} }
#[cfg(test)] #[cfg(test)]
fn assert_test(input: &str, expected: &[&str], split: SplitType) { fn assert_test(input: &str, expected: &[&str], split: SplitType) {
let output = split_function(input, split); let output = split_function(input, split);
let expected_owned = expected let expected_owned = expected
.iter() .iter()
.map(|&x| x.to_owned()) .map(|&x| x.to_owned())
.collect::<Vec<String>>(); .collect::<Vec<String>>();
if output != expected_owned { if output != expected_owned {
panic!( panic!(
"split type: {:?} of {} resulted in {:?} not {:?}", "split type: {:?} of {} resulted in {:?} not {:?}",
split, input, output, expected split, input, output, expected
); );
} }
} }
#[test] #[test]
fn split_function_test() { fn split_function_test() {
assert_test( assert_test(
"sin(x)cos(x)", "sin(x)cos(x)",
&["sin(x)", "cos(x)"], &["sin(x)", "cos(x)"],
SplitType::Multiplication, SplitType::Multiplication,
); );
assert_test( assert_test(
"tanh(cos(x)xx)cos(x)", "tanh(cos(x)xx)cos(x)",
&["tanh(cos(x)", "x", "x)", "cos(x)"], &["tanh(cos(x)", "x", "x)", "cos(x)"],
SplitType::Multiplication, SplitType::Multiplication,
); );
assert_test( assert_test(
"tanh(sin(cos(x)xsin(x)))", "tanh(sin(cos(x)xsin(x)))",
&["tanh(sin(cos(x)", "x", "sin(x)))"], &["tanh(sin(cos(x)", "x", "sin(x)))"],
SplitType::Multiplication, SplitType::Multiplication,
); );
// Some test cases from https://github.com/GraphiteEditor/Graphite/blob/2515620a77478e57c255cd7d97c13cc7065dd99d/frontend/wasm/src/editor_api.rs#L829-L840 // 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("2pi", &["2", "π"], SplitType::Multiplication);
assert_test("sin(2pi)", &["sin(2", "π)"], SplitType::Multiplication); assert_test("sin(2pi)", &["sin(2", "π)"], SplitType::Multiplication);
assert_test("2sin(pi)", &["2", "sin(π)"], SplitType::Multiplication); assert_test("2sin(pi)", &["2", "sin(π)"], SplitType::Multiplication);
assert_test( assert_test(
"2sin(3(4 + 5))", "2sin(3(4 + 5))",
&["2", "sin(3", "(4 + 5))"], &["2", "sin(3", "(4 + 5))"],
SplitType::Multiplication, SplitType::Multiplication,
); );
assert_test("3abs(-4)", &["3", "abs(-4)"], 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("(-1)4", &["(-1)", "4"], SplitType::Multiplication);
assert_test( assert_test(
"(((-1)))(4)", "(((-1)))(4)",
&["(((-1)))", "(4)"], &["(((-1)))", "(4)"],
SplitType::Multiplication, SplitType::Multiplication,
); );
assert_test( assert_test(
"2sin(π) + 2cos(tau)", "2sin(π) + 2cos(tau)",
&["2", "sin(π) + 2", "cos(tau)"], &["2", "sin(π) + 2", "cos(tau)"],
SplitType::Multiplication, SplitType::Multiplication,
); );
} }

View File

@ -14,106 +14,112 @@ macro_rules! test_print {
/// Generate a hint based on the input `input`, returns an `Option<String>` /// Generate a hint based on the input `input`, returns an `Option<String>`
pub fn generate_hint<'a>(input: &str) -> &'a Hint<'a> { pub fn generate_hint<'a>(input: &str) -> &'a Hint<'a> {
if input.is_empty() { if input.is_empty() {
&HINT_EMPTY &HINT_EMPTY
} else { } else {
let chars: Vec<char> = input.chars().collect::<Vec<char>>(); let chars: Vec<char> = input.chars().collect::<Vec<char>>();
let key = get_last_term(&chars); let key = get_last_term(&chars);
match key { match key {
Some(key) => { Some(key) => {
if let Some(hint) = COMPLETION_HASHMAP.get(&key) { if let Some(hint) = COMPLETION_HASHMAP.get(&key) {
return hint; return hint;
} }
} }
None => { None => {
return &Hint::None; return &Hint::None;
} }
} }
let mut open_parens: usize = 0; let mut open_parens: usize = 0;
let mut closed_parens: usize = 0; let mut closed_parens: usize = 0;
chars.iter().for_each(|chr| match *chr { chars.iter().for_each(|chr| match *chr {
'(' => open_parens += 1, '(' => open_parens += 1,
')' => closed_parens += 1, ')' => closed_parens += 1,
_ => {} _ => {}
}); });
if open_parens > closed_parens { if open_parens > closed_parens {
return &HINT_CLOSED_PARENS; return &HINT_CLOSED_PARENS;
} }
&Hint::None &Hint::None
} }
} }
pub fn get_last_term(chars: &[char]) -> Option<String> { pub fn get_last_term(chars: &[char]) -> Option<String> {
if chars.is_empty() { if chars.is_empty() {
return None; return None;
} }
let mut result = split_function_chars(chars, SplitType::Term); let mut result = split_function_chars(chars, SplitType::Term);
result.pop() result.pop()
} }
#[derive(PartialEq, Clone, Copy)] #[derive(PartialEq, Clone, Copy)]
pub enum Hint<'a> { pub enum Hint<'a> {
Single(&'a str), Single(&'a str),
Many(&'a [&'a str]), Many(&'a [&'a str]),
None, None,
} }
impl<'a> std::fmt::Display for Hint<'a> { impl<'a> std::fmt::Display for Hint<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Hint::Single(single_data) => { Hint::Single(single_data) => {
write!(f, "{}", single_data) write!(f, "{}", single_data)
} }
Hint::Many(multi_data) => { Hint::Many(multi_data) => {
write!(f, "{:?}", multi_data) write!(f, "{:?}", multi_data)
} }
Hint::None => { Hint::None => {
write!(f, "None") write!(f, "None")
} }
} }
} }
} }
impl<'a> std::fmt::Debug for Hint<'a> { impl<'a> std::fmt::Debug for Hint<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Display::fmt(self, f) std::fmt::Display::fmt(self, f)
} }
} }
impl<'a> Hint<'a> { impl<'a> Hint<'a> {
#[inline] #[inline]
pub const fn is_none(&self) -> bool { matches!(&self, &Hint::None) } pub const fn is_none(&self) -> bool {
matches!(&self, &Hint::None)
}
#[inline] #[inline]
#[allow(dead_code)] #[allow(dead_code)]
pub const fn is_some(&self) -> bool { !self.is_none() } pub const fn is_some(&self) -> bool {
!self.is_none()
}
#[inline] #[inline]
#[allow(dead_code)] #[allow(dead_code)]
pub const fn is_single(&self) -> bool { matches!(&self, &Hint::Single(_)) } pub const fn is_single(&self) -> bool {
matches!(&self, &Hint::Single(_))
}
#[inline] #[inline]
#[allow(dead_code)] #[allow(dead_code)]
pub const fn single(&self) -> Option<&str> { pub const fn single(&self) -> Option<&str> {
match self { match self {
Hint::Single(data) => Some(data), Hint::Single(data) => Some(data),
_ => None, _ => None,
} }
} }
#[inline] #[inline]
#[allow(dead_code)] #[allow(dead_code)]
pub const fn many(&self) -> Option<&[&str]> { pub const fn many(&self) -> Option<&[&str]> {
match self { match self {
Hint::Many(data) => Some(data), Hint::Many(data) => Some(data),
_ => None, _ => None,
} }
} }
} }
include!(concat!(env!("OUT_DIR"), "/codegen.rs")); include!(concat!(env!("OUT_DIR"), "/codegen.rs"));