parser: cargo fmt
This commit is contained in:
parent
2378f719a7
commit
9d96977785
@ -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"
|
||||||
));
|
));
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)>>()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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("*")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"));
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user