better autocomplete system
This commit is contained in:
parent
309b53cf4b
commit
f9e523f320
@ -1,8 +1,8 @@
|
|||||||
|
use crate::consts::*;
|
||||||
use crate::function::{FunctionEntry, Riemann, DEFAULT_FUNCTION_ENTRY};
|
use crate::function::{FunctionEntry, Riemann, DEFAULT_FUNCTION_ENTRY};
|
||||||
use crate::misc::{dyn_mut_iter, option_vec_printer, JsonFileOutput, SerdeValueHelper};
|
use crate::misc::{dyn_mut_iter, option_vec_printer, JsonFileOutput, SerdeValueHelper};
|
||||||
use crate::parsing::{generate_hint, process_func_str, test_func};
|
use crate::parsing::{process_func_str, test_func};
|
||||||
|
use crate::suggestions::generate_hint;
|
||||||
use crate::consts::*;
|
|
||||||
use eframe::{egui, epi};
|
use eframe::{egui, epi};
|
||||||
use egui::plot::Plot;
|
use egui::plot::Plot;
|
||||||
use egui::{
|
use egui::{
|
||||||
|
|||||||
@ -10,6 +10,7 @@ mod function;
|
|||||||
mod function_output;
|
mod function_output;
|
||||||
mod misc;
|
mod misc;
|
||||||
mod parsing;
|
mod parsing;
|
||||||
|
mod suggestions;
|
||||||
|
|
||||||
cfg_if::cfg_if! {
|
cfg_if::cfg_if! {
|
||||||
if #[cfg(target_arch = "wasm32")] {
|
if #[cfg(target_arch = "wasm32")] {
|
||||||
|
|||||||
@ -9,6 +9,7 @@ mod function;
|
|||||||
mod function_output;
|
mod function_output;
|
||||||
mod misc;
|
mod misc;
|
||||||
mod parsing;
|
mod parsing;
|
||||||
|
mod suggestions;
|
||||||
|
|
||||||
// For running the program natively! (Because why not?)
|
// For running the program natively! (Because why not?)
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
|||||||
58
src/misc.rs
58
src/misc.rs
@ -313,6 +313,36 @@ pub fn step_helper(max_i: usize, min_x: f64, step: f64) -> Vec<f64> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn common_substring<'a>(a: &'a str, b: &'a str) -> Option<String> {
|
||||||
|
let a_chars: Vec<char> = a.chars().collect();
|
||||||
|
let b_chars: Vec<char> = b.chars().collect();
|
||||||
|
if a_chars[0] != b_chars[0] {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut last_value: String = a_chars[0].to_string();
|
||||||
|
let max_common_i = std::cmp::min(a.len(), b.len()) - 1;
|
||||||
|
for i in 1..=max_common_i {
|
||||||
|
let a_i = a_chars[i];
|
||||||
|
let b_i = b_chars[i];
|
||||||
|
if a_i == b_i {
|
||||||
|
last_value += &a_i.to_string()
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(last_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn chars_take(chars: &Vec<char>, i: usize) -> String {
|
||||||
|
if i > chars.len() {
|
||||||
|
panic!("chars_take: i is larget than chars.len()");
|
||||||
|
}
|
||||||
|
|
||||||
|
chars.iter().rev().take(i).rev().collect::<String>()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -380,6 +410,7 @@ mod tests {
|
|||||||
assert_eq!(resolution_helper(3, -2.0, 1.0), vec![-2.0, -1.0, 0.0]);
|
assert_eq!(resolution_helper(3, -2.0, 1.0), vec![-2.0, -1.0, 0.0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tests [`option_vec_printer`]
|
||||||
#[test]
|
#[test]
|
||||||
fn option_vec_printer_test() {
|
fn option_vec_printer_test() {
|
||||||
let values_strings: HashMap<Vec<Option<&str>>, &str> = HashMap::from([
|
let values_strings: HashMap<Vec<Option<&str>>, &str> = HashMap::from([
|
||||||
@ -404,4 +435,31 @@ mod tests {
|
|||||||
assert_eq!(option_vec_printer(key), value);
|
assert_eq!(option_vec_printer(key), value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tests [`common_substring`]
|
||||||
|
#[test]
|
||||||
|
fn common_substring_test() {
|
||||||
|
let values = HashMap::from([
|
||||||
|
(("test", "text"), Some("te")),
|
||||||
|
(("lol", "text"), None),
|
||||||
|
(("sin(", "sinh("), Some("sin")),
|
||||||
|
(("aaa", "bbb"), None),
|
||||||
|
]);
|
||||||
|
|
||||||
|
for ((key_a, key_b), value) in values {
|
||||||
|
assert_eq!(common_substring(key_a, key_b), value.map(|e| e.to_owned()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn chars_take_test() {
|
||||||
|
let values = HashMap::from([(("test", 2), "st"), (("cool text", 4), "text")]);
|
||||||
|
|
||||||
|
for ((in_str, i), value) in values {
|
||||||
|
assert_eq!(
|
||||||
|
chars_take(&in_str.chars().collect::<Vec<char>>(), i),
|
||||||
|
value.to_owned()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
157
src/parsing.rs
157
src/parsing.rs
@ -204,136 +204,6 @@ pub fn test_func(function_string: &str) -> Option<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_hint(input: &str) -> String {
|
|
||||||
if input.is_empty() {
|
|
||||||
return "x^2".to_owned();
|
|
||||||
}
|
|
||||||
|
|
||||||
let chars: Vec<char> = input.chars().collect();
|
|
||||||
|
|
||||||
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 ")".to_owned();
|
|
||||||
}
|
|
||||||
|
|
||||||
let len = chars.len();
|
|
||||||
|
|
||||||
if chars.len() >= 5 {
|
|
||||||
let result_two = match (chars[len - 5].to_string()
|
|
||||||
+ &chars[len - 4].to_string()
|
|
||||||
+ &chars[len - 3].to_string()
|
|
||||||
+ &chars[len - 2].to_string()
|
|
||||||
+ &chars[len - 1].to_string())
|
|
||||||
.as_str()
|
|
||||||
{
|
|
||||||
"round" => Some("("),
|
|
||||||
"fract" => Some("("),
|
|
||||||
"trunc" => Some("("),
|
|
||||||
"floor" => Some("("),
|
|
||||||
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(output) = result_two {
|
|
||||||
return output.to_owned();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if chars.len() >= 4 {
|
|
||||||
let result_two = match (chars[len - 4].to_string()
|
|
||||||
+ &chars[len - 3].to_string()
|
|
||||||
+ &chars[len - 2].to_string()
|
|
||||||
+ &chars[len - 1].to_string())
|
|
||||||
.as_str()
|
|
||||||
{
|
|
||||||
"asin" => Some("("),
|
|
||||||
"acos" => Some("("),
|
|
||||||
"atan" => Some("("),
|
|
||||||
|
|
||||||
"sinh" => Some("("),
|
|
||||||
"cosh" => Some("("),
|
|
||||||
"tanh" => Some("("),
|
|
||||||
|
|
||||||
"ceil" => Some("("),
|
|
||||||
"roun" => Some("d("),
|
|
||||||
"sqrt" => Some("("),
|
|
||||||
"cbrt" => Some("("),
|
|
||||||
"floo" => Some("r("),
|
|
||||||
|
|
||||||
"frac" => Some("t("),
|
|
||||||
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(output) = result_two {
|
|
||||||
return output.to_owned();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if chars.len() >= 3 {
|
|
||||||
let result_two = match (chars[len - 3].to_string()
|
|
||||||
+ &chars[len - 2].to_string()
|
|
||||||
+ &chars[len - 1].to_string())
|
|
||||||
.as_str()
|
|
||||||
{
|
|
||||||
"flo" => Some("or("),
|
|
||||||
"log" => Some("("),
|
|
||||||
"abs" => Some("("),
|
|
||||||
|
|
||||||
"sin" => Some("("),
|
|
||||||
"cos" => Some("("),
|
|
||||||
"tan" => Some("("),
|
|
||||||
|
|
||||||
"asi" => Some("n("),
|
|
||||||
"aco" => Some("s("),
|
|
||||||
"ata" => Some("n("),
|
|
||||||
"exp" => Some("("),
|
|
||||||
"fra" => Some("ct("),
|
|
||||||
"cbr" => Some("t("),
|
|
||||||
"cei" => Some("l("),
|
|
||||||
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(output) = result_two {
|
|
||||||
return output.to_owned();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if chars.len() >= 2 {
|
|
||||||
let result_two = match (chars[len - 2].to_string() + &chars[len - 1].to_string()).as_str() {
|
|
||||||
"lo" => Some("g("),
|
|
||||||
"si" => Some("n("),
|
|
||||||
"ab" => Some("s("),
|
|
||||||
"co" => Some("s("),
|
|
||||||
"ta" => Some("n("),
|
|
||||||
"as" => Some("in("),
|
|
||||||
"ac" => Some("os("),
|
|
||||||
"at" => Some("an("),
|
|
||||||
"ln" => Some("("),
|
|
||||||
"fl" => Some("oor("),
|
|
||||||
"sq" => Some("rt("),
|
|
||||||
"ex" => Some("p("),
|
|
||||||
"ce" => Some("il("),
|
|
||||||
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(output) = result_two {
|
|
||||||
return output.to_owned();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -442,31 +312,4 @@ mod tests {
|
|||||||
test_process_helper(key, value);
|
test_process_helper(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests to make sure hints are properly outputed based on input
|
|
||||||
#[test]
|
|
||||||
fn hint_test() {
|
|
||||||
let values = HashMap::from([
|
|
||||||
("", "x^2"),
|
|
||||||
("sin(x", ")"),
|
|
||||||
("sin(x)", ""),
|
|
||||||
("x^x", ""),
|
|
||||||
("(x+1)(x-1", ")"),
|
|
||||||
("lo", "g("),
|
|
||||||
("log", "("),
|
|
||||||
("asi", "n("),
|
|
||||||
("si", "n("),
|
|
||||||
("asin", "("),
|
|
||||||
("fl", "oor("),
|
|
||||||
("ata", "n("),
|
|
||||||
("at", "an("),
|
|
||||||
("roun", "d("),
|
|
||||||
("floo", "r("),
|
|
||||||
("flo", "or("),
|
|
||||||
]);
|
|
||||||
|
|
||||||
for (key, value) in values {
|
|
||||||
assert_eq!(generate_hint(key), value.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
237
src/suggestions.rs
Normal file
237
src/suggestions.rs
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
use crate::misc::{chars_take, common_substring};
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
pub fn generate_hint(input: &str) -> String {
|
||||||
|
if input.is_empty() {
|
||||||
|
return "x^2".to_owned();
|
||||||
|
}
|
||||||
|
|
||||||
|
let chars: Vec<char> = input.chars().collect();
|
||||||
|
|
||||||
|
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 ")".to_owned();
|
||||||
|
}
|
||||||
|
|
||||||
|
let len = chars.len();
|
||||||
|
|
||||||
|
if len >= 5 {
|
||||||
|
let result_five = get_completion(chars_take(&chars, 5));
|
||||||
|
|
||||||
|
if let Some(output) = result_five {
|
||||||
|
return output.to_owned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len >= 4 {
|
||||||
|
let result_four = get_completion(chars_take(&chars, 4));
|
||||||
|
|
||||||
|
if let Some(output) = result_four {
|
||||||
|
return output.to_owned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len >= 3 {
|
||||||
|
let result_three = get_completion(chars_take(&chars, 3));
|
||||||
|
|
||||||
|
if let Some(output) = result_three {
|
||||||
|
return output.to_owned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len >= 2 {
|
||||||
|
let result_two = get_completion(chars_take(&chars, 2));
|
||||||
|
if let Some(output) = result_two {
|
||||||
|
return output.to_owned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_completion_hashmap(input: Vec<String>) -> HashMap<String, String> {
|
||||||
|
let mut tuple_list: Vec<(String, String)> = Vec::new();
|
||||||
|
|
||||||
|
for entry in input {
|
||||||
|
for i in 1..=entry.len() {
|
||||||
|
let (first, last) = entry.split_at(i);
|
||||||
|
tuple_list.push((first.to_string(), last.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut output: HashMap<String, String> = HashMap::new();
|
||||||
|
|
||||||
|
let key_list: Vec<String> = tuple_list.iter().map(|(key, _)| key.clone()).collect();
|
||||||
|
|
||||||
|
let mut seen = HashSet::new();
|
||||||
|
for (key, value) in tuple_list.clone() {
|
||||||
|
if seen.contains(&key) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
seen.insert(key.clone());
|
||||||
|
|
||||||
|
let duplicate_num = key_list.iter().filter(|ele| **ele == key).count();
|
||||||
|
if 1 == duplicate_num {
|
||||||
|
output.insert(key, value);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let same_keys_merged: Vec<String> = tuple_list
|
||||||
|
.iter()
|
||||||
|
.filter(|(a, _)| **a == key)
|
||||||
|
.map(|(a, b)| a.clone() + b)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let merged_key_value = key.clone() + &value;
|
||||||
|
|
||||||
|
let mut common_substr: Option<String> = None;
|
||||||
|
for same_key in same_keys_merged {
|
||||||
|
if let Some(common_substr_unwrapped) = common_substr {
|
||||||
|
common_substr = common_substring(&common_substr_unwrapped, &same_key);
|
||||||
|
} else {
|
||||||
|
common_substr = common_substring(&same_key, &merged_key_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if common_substr.is_none() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(common_substr_unwrapped) = common_substr {
|
||||||
|
println!("{}", common_substr_unwrapped);
|
||||||
|
output.insert(key.clone(), common_substr_unwrapped.replace(&key, ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// println!("{:?}", output);
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
];
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref COMPLETION_HASHMAP: HashMap<String, String> = gen_completion_hashmap(SUPPORTED_FUNCTIONS.to_vec().iter().map(|ele| ele.to_string() + "(").collect());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_completion(key: String) -> Option<String> {
|
||||||
|
if key.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match COMPLETION_HASHMAP.get(&key) {
|
||||||
|
Some(data_x) => {
|
||||||
|
if data_x.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(data_x.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Tests to make sure hints are properly outputed based on input
|
||||||
|
#[test]
|
||||||
|
fn hint_test() {
|
||||||
|
let values = HashMap::from([
|
||||||
|
("", "x^2"),
|
||||||
|
("sin(x", ")"),
|
||||||
|
("sin(x)", ""),
|
||||||
|
("x^x", ""),
|
||||||
|
("(x+1)(x-1", ")"),
|
||||||
|
// ("lo", "g("),
|
||||||
|
// ("log", "("),
|
||||||
|
("asi", "n("),
|
||||||
|
("asin", "("),
|
||||||
|
("fl", "oor("),
|
||||||
|
("ata", "n("),
|
||||||
|
("at", "an("),
|
||||||
|
("roun", "d("),
|
||||||
|
("floo", "r("),
|
||||||
|
("flo", "or("),
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (key, value) in values {
|
||||||
|
println!("{} + {}", key, value);
|
||||||
|
assert_eq!(generate_hint(key), value.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn completion_hashmap_test() {
|
||||||
|
let mut values: HashMap<String, Option<String>> = HashMap::new();
|
||||||
|
|
||||||
|
let processed_func: Vec<String> = SUPPORTED_FUNCTIONS
|
||||||
|
.iter()
|
||||||
|
.map(|ele| ele.to_string() + "(")
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut data_tuple: Vec<(String, Option<String>)> = Vec::new();
|
||||||
|
for func in processed_func.iter() {
|
||||||
|
for i in 1..=func.len() {
|
||||||
|
let (first, last) = func.split_at(i);
|
||||||
|
let value = match last {
|
||||||
|
"" => None,
|
||||||
|
x => Some(x.to_string()),
|
||||||
|
};
|
||||||
|
data_tuple.push((first.to_string(), value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let key_list: Vec<String> = data_tuple.iter().map(|(a, _)| a.clone()).collect();
|
||||||
|
|
||||||
|
for (key, value) in data_tuple {
|
||||||
|
if key_list.iter().filter(|a| **a == key).count() == 1 {
|
||||||
|
values.insert(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let values_old = values.clone();
|
||||||
|
values = values
|
||||||
|
.iter()
|
||||||
|
.filter(|(key, _)| values_old.iter().filter(|(a, _)| a == key).count() == 1)
|
||||||
|
.map(|(a, b)| (a.to_string(), b.clone()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let manual_values: Vec<(&str, Option<&str>)> =
|
||||||
|
vec![("sin", None), ("cos", None), ("tan", None)];
|
||||||
|
|
||||||
|
for (key, value) in manual_values {
|
||||||
|
values.insert(
|
||||||
|
key.to_string(),
|
||||||
|
match value {
|
||||||
|
Some(x) => Some(x.to_string()),
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (key, value) in values {
|
||||||
|
println!(
|
||||||
|
"{} + {}",
|
||||||
|
key,
|
||||||
|
match value.clone() {
|
||||||
|
Some(x) => x.clone(),
|
||||||
|
None => "(No completion)".to_string(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(get_completion(key.to_string()), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user