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
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"
));

View File

@ -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();
}
}

View File

@ -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<String>) -> 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<String> = 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<String> = 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::<Vec<&String>>();
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::<Vec<&String>>();
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::<Vec<(String, String)>>()
Some((first, last))
})
.collect::<Vec<(String, String)>>()
}

View File

@ -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},
};

View File

@ -3,176 +3,176 @@ use std::collections::HashMap;
#[derive(Clone, PartialEq)]
pub struct FlatExWrapper {
func: Option<FlatEx<f64>>,
func_str: Option<String>,
func: Option<FlatEx<f64>>,
func_str: Option<String>,
}
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<f64>) -> Self {
Self {
func: Some(f),
func_str: None,
}
}
#[inline]
const fn new(f: FlatEx<f64>) -> 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<usize, FlatExWrapper>,
/// Temporary cache for nth derivative
nth_derivative: HashMap<usize, FlatExWrapper>,
}
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<Self, String> {
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<Self, String> {
if func_str.is_empty() {
return Ok(Self {
function: FlatExWrapper::EMPTY,
nth_derivative: HashMap::new(),
});
}
let function = FlatExWrapper::new({
let parse_result = exmex::parse::<f64>(func_str);
let function = FlatExWrapper::new({
let parse_result = exmex::parse::<f64>(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::<Vec<&String>>();
if var_names != ["x"] {
let var_names_not_x: Vec<&String> = var_names
.iter()
.filter(|ele| ele != &"x")
.collect::<Vec<&String>>();
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("*")
}

View File

@ -1,204 +1,208 @@
use crate::parsing::is_variable;
pub fn split_function(input: &str, split: SplitType) -> Vec<String> {
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::<Vec<char>>(),
split,
)
.iter()
.map(|x| x.replace('\u{1fc93}', "exp")) // Convert back to `exp` text
.collect::<Vec<String>>()
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::<Vec<char>>(),
split,
)
.iter()
.map(|x| x.replace('\u{1fc93}', "exp")) // Convert back to `exp` text
.collect::<Vec<String>>()
}
// 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<String> {
// 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<String> = std::vec::from_elem(chars[0].to_string(), 1);
// Resulting split-up data
let mut data: Vec<String> = 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::<Vec<String>>();
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::<Vec<String>>();
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,
);
}

View File

@ -14,106 +14,112 @@ macro_rules! test_print {
/// Generate a hint based on the input `input`, returns an `Option<String>`
pub fn generate_hint<'a>(input: &str) -> &'a Hint<'a> {
if input.is_empty() {
&HINT_EMPTY
} else {
let chars: Vec<char> = input.chars().collect::<Vec<char>>();
if input.is_empty() {
&HINT_EMPTY
} else {
let chars: Vec<char> = input.chars().collect::<Vec<char>>();
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<String> {
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"));