MAJOR refactoring
This commit is contained in:
@@ -1,2 +1,4 @@
|
||||
#![feature(const_trait_impl)]
|
||||
|
||||
pub mod parsing;
|
||||
pub mod suggestions;
|
||||
|
||||
@@ -150,95 +150,11 @@ pub fn is_number(c: &char) -> bool { NUMBERS.contains(&c) }
|
||||
/// In the future I may want to completely rewrite this or implement this natively in exmex.
|
||||
// TODO: use `split_function` here instead of this janky code
|
||||
pub fn process_func_str(function_in: &str) -> String {
|
||||
let function = function_in
|
||||
.replace("log10(", "log(") // log10 -> log
|
||||
.replace("pi", "π") // pi -> π
|
||||
.replace("exp", "\u{1fc93}") // replace 'exp' with this random unicode character because it can't be parsed correctly
|
||||
.replace("**", "^"); // alternate exponential representation
|
||||
let function_chars: Vec<char> = function.chars().collect();
|
||||
let mut output_string: String = String::new();
|
||||
for (i, c) in function_chars.iter().enumerate() {
|
||||
let mut add_asterisk: bool = false;
|
||||
|
||||
let prev_prev_prev_char = if i > 2 {
|
||||
*function_chars.get(i - 3).unwrap()
|
||||
} else {
|
||||
' '
|
||||
};
|
||||
|
||||
let prev_prev_char = if i > 1 {
|
||||
*function_chars.get(i - 2).unwrap()
|
||||
} else {
|
||||
' '
|
||||
};
|
||||
|
||||
let prev_char = if i > 0 {
|
||||
*function_chars.get(i - 1).unwrap()
|
||||
} else {
|
||||
' '
|
||||
};
|
||||
|
||||
let c_is_number = is_number(c);
|
||||
let c_is_letter = is_letter(c);
|
||||
let c_is_variable = is_variable(c);
|
||||
let prev_char_is_variable = is_variable(&prev_char);
|
||||
let prev_char_is_number = is_number(&prev_char);
|
||||
|
||||
// makes special case for log with base of a 1-2 digit number
|
||||
if ((prev_prev_prev_char == 'l')
|
||||
&& (prev_prev_char == 'o')
|
||||
&& (prev_char == 'g')
|
||||
&& c_is_number)
|
||||
| ((prev_prev_char == 'c') && (prev_char == 'e') && (*c == 'i'))
|
||||
{
|
||||
output_string += &c.to_string();
|
||||
continue;
|
||||
}
|
||||
|
||||
let c_letters_var = c_is_letter | c_is_variable;
|
||||
let prev_letters_var = prev_char_is_variable | is_letter(&prev_char);
|
||||
|
||||
if prev_char == ')' {
|
||||
// cases like `)x`, `)2`, and `)(`
|
||||
if c_letters_var | c_is_number | (*c == '(') {
|
||||
add_asterisk = true;
|
||||
}
|
||||
} else if *c == '(' {
|
||||
// cases like `x(` and `2(`
|
||||
if (prev_char_is_variable | prev_char_is_number) && !is_letter(&prev_prev_char) {
|
||||
add_asterisk = true;
|
||||
}
|
||||
} else if prev_char_is_number {
|
||||
// cases like `2x` and `2sin(x)`
|
||||
if c_letters_var {
|
||||
add_asterisk = true;
|
||||
}
|
||||
} else if c_is_letter {
|
||||
// cases like `e2` and `xx`
|
||||
if prev_char_is_number
|
||||
| (prev_char_is_variable && c_is_variable)
|
||||
| prev_char_is_variable
|
||||
| (prev_char == 'π')
|
||||
{
|
||||
add_asterisk = true;
|
||||
}
|
||||
} else if (c_is_number | c_letters_var) && prev_letters_var {
|
||||
// cases like `x2` and `xx`
|
||||
add_asterisk = true;
|
||||
}
|
||||
|
||||
// if add_asterisk is true, add the asterisk
|
||||
if add_asterisk {
|
||||
output_string += "*";
|
||||
}
|
||||
|
||||
// push current char to `output_string` (which is eventually returned)
|
||||
output_string += &c.to_string();
|
||||
if function_in.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
output_string
|
||||
.replace("log(", "log10(")
|
||||
.replace('\u{1fc93}', "exp")
|
||||
crate::suggestions::split_function(&function_in).join("*")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -326,6 +242,7 @@ mod tests {
|
||||
("pisin(x)", "π*sin(x)"),
|
||||
("e^sin(x)", "e^sin(x)"),
|
||||
("x**2", "x^2"),
|
||||
("(x+1)(x-3)", "(x+1)*(x-3)"),
|
||||
]);
|
||||
|
||||
for (key, value) in values {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::parsing::is_number;
|
||||
use crate::parsing::{is_letter, is_number, is_variable};
|
||||
|
||||
pub const HINT_EMPTY: Hint = Hint::Single("x^2");
|
||||
const HINT_CLOSED_PARENS: Hint = Hint::Single(")");
|
||||
@@ -13,7 +13,17 @@ macro_rules! test_print {
|
||||
}
|
||||
|
||||
pub fn split_function(input: &str) -> Vec<String> {
|
||||
split_function_chars(&input.chars().collect::<Vec<char>>())
|
||||
split_function_chars(
|
||||
&input
|
||||
.replace("pi", "π")
|
||||
.replace("**", "^")
|
||||
.replace("exp", "\u{1fc93}")
|
||||
.chars()
|
||||
.collect::<Vec<char>>(),
|
||||
)
|
||||
.iter()
|
||||
.map(|x| x.replace("\u{1fc93}", "exp"))
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
||||
fn split_function_chars(chars: &[char]) -> Vec<String> {
|
||||
@@ -22,19 +32,120 @@ fn split_function_chars(chars: &[char]) -> Vec<String> {
|
||||
let mut split: Vec<String> = Vec::new();
|
||||
|
||||
let mut buffer: Vec<char> = Vec::new();
|
||||
|
||||
#[derive(Default)]
|
||||
struct BoolSlice {
|
||||
closing_parens: bool,
|
||||
number: bool,
|
||||
letter: bool,
|
||||
variable: bool,
|
||||
masked_num: bool,
|
||||
masked_var: bool,
|
||||
exists: bool,
|
||||
}
|
||||
|
||||
impl BoolSlice {
|
||||
#[inline]
|
||||
fn is_variable(&self) -> bool { self.variable && !self.masked_var }
|
||||
|
||||
#[inline]
|
||||
fn is_number(&self) -> bool { self.number && !self.masked_num }
|
||||
}
|
||||
let mut prev_char: BoolSlice = BoolSlice::default();
|
||||
|
||||
for c in chars {
|
||||
buffer.push(*c);
|
||||
if *c == ')' {
|
||||
split.push(buffer.iter().collect::<String>());
|
||||
buffer.clear();
|
||||
continue;
|
||||
}
|
||||
let mut curr_c = BoolSlice {
|
||||
closing_parens: c == &')',
|
||||
number: is_number(c),
|
||||
letter: is_letter(c),
|
||||
variable: is_variable(c),
|
||||
masked_num: if is_number(c) {
|
||||
prev_char.masked_num
|
||||
} else {
|
||||
false
|
||||
},
|
||||
masked_var: if is_variable(c) {
|
||||
prev_char.masked_var
|
||||
} else {
|
||||
false
|
||||
},
|
||||
exists: true,
|
||||
};
|
||||
|
||||
let buffer_string = buffer.iter().collect::<String>();
|
||||
|
||||
if ((&buffer_string == "log") | (&buffer_string == "log1")) && is_number(&c) {
|
||||
continue;
|
||||
// Check if prev_char is valid
|
||||
if prev_char.exists {
|
||||
// if previous char was a masked number, and current char is a number, mask current char's variable status
|
||||
if prev_char.masked_num && curr_c.number {
|
||||
curr_c.masked_num = true;
|
||||
}
|
||||
|
||||
// if previous char was a masked variable, and current char is a variable, mask current char's variable status
|
||||
if prev_char.masked_var && curr_c.variable {
|
||||
curr_c.masked_var = true;
|
||||
}
|
||||
|
||||
// if letter and not a variable (or a masked variable)
|
||||
if prev_char.letter && !(prev_char.variable && !prev_char.masked_var) {
|
||||
// mask number status if current char is number
|
||||
if curr_c.number {
|
||||
curr_c.masked_num = true;
|
||||
}
|
||||
|
||||
// mask variable status if current char is a variable
|
||||
if curr_c.variable {
|
||||
curr_c.masked_var = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut do_split = false;
|
||||
|
||||
if prev_char.closing_parens {
|
||||
// cases like `)x`, `)2`, and `)(`
|
||||
if (c == &'(')
|
||||
| (curr_c.letter && !curr_c.is_variable())
|
||||
| curr_c.is_variable()
|
||||
| curr_c.is_number()
|
||||
{
|
||||
do_split = true;
|
||||
}
|
||||
} else if c == &'(' {
|
||||
// cases like `x(` and `2(`
|
||||
if (prev_char.is_variable() | prev_char.is_number()) && !prev_char.letter {
|
||||
do_split = true;
|
||||
}
|
||||
} else if prev_char.is_number() {
|
||||
// cases like `2x` and `2sin(x)`
|
||||
if curr_c.is_variable() | curr_c.letter {
|
||||
do_split = true;
|
||||
}
|
||||
} else if curr_c.is_variable() | curr_c.letter {
|
||||
// cases like `e2` and `xx`
|
||||
if prev_char.is_number()
|
||||
| (prev_char.is_variable() && curr_c.is_variable())
|
||||
| prev_char.is_variable()
|
||||
{
|
||||
do_split = true;
|
||||
}
|
||||
} else if (curr_c.is_number() | curr_c.letter | curr_c.is_variable())
|
||||
&& (prev_char.is_number() | prev_char.letter)
|
||||
{
|
||||
// cases like `x2` and `xx`
|
||||
do_split = true;
|
||||
} else if curr_c.is_number() && prev_char.is_variable() {
|
||||
do_split = true;
|
||||
}
|
||||
|
||||
// split and append buffer
|
||||
if do_split {
|
||||
split.push(buffer_string);
|
||||
buffer.clear();
|
||||
}
|
||||
|
||||
buffer.push(*c);
|
||||
prev_char = curr_c;
|
||||
}
|
||||
|
||||
if !buffer.is_empty() {
|
||||
@@ -75,10 +186,6 @@ pub enum Hint<'a> {
|
||||
None,
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Debug for Hint<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self) }
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Display for Hint<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
@@ -95,6 +202,12 @@ impl<'a> std::fmt::Display for Hint<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Hint<'a> {
|
||||
pub fn is_none(&self) -> bool { matches!(self, Hint::None) }
|
||||
|
||||
@@ -163,11 +276,17 @@ mod tests {
|
||||
.flatten()
|
||||
.filter(|func| !SUPPORTED_FUNCTIONS.contains(&func.as_str()))
|
||||
.for_each(|key| {
|
||||
println!("{}", key);
|
||||
if super::generate_hint(&key).is_none() {
|
||||
let split = super::split_function(&key);
|
||||
|
||||
if split.len() != 1 {
|
||||
panic!("failed: {} (len: {}, split: {:?})", key, split.len(), split);
|
||||
}
|
||||
|
||||
let generated_hint = super::generate_hint(&key);
|
||||
if generated_hint.is_none() {
|
||||
println!("success: {}", key);
|
||||
} else {
|
||||
panic!("failed: {}", key);
|
||||
panic!("failed: {} (Hint: '{}')", key, generated_hint.to_string());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -179,6 +298,7 @@ mod tests {
|
||||
("cos(", vec!["cos("]),
|
||||
("cos(x)sin(x)", vec!["cos(x)", "sin(x)"]),
|
||||
("aaaaaaaaaaa", vec!["aaaaaaaaaaa"]),
|
||||
("emax(x)", vec!["e", "max(x)"]),
|
||||
]);
|
||||
|
||||
for (key, value) in values {
|
||||
|
||||
Reference in New Issue
Block a user