MAJOR refactoring

This commit is contained in:
Simon Gardling
2022-05-03 11:50:13 -04:00
parent 8952552eef
commit 13a47ec30b
12 changed files with 243 additions and 218 deletions

View File

@@ -1,2 +1,4 @@
#![feature(const_trait_impl)]
pub mod parsing;
pub mod suggestions;

View File

@@ -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 {

View File

@@ -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 {