use tabs, not spaces

This commit is contained in:
Simon Gardling
2022-03-10 09:01:21 -05:00
parent f851b365ab
commit dd6d8c80b8
11 changed files with 1417 additions and 1417 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,100 +1,100 @@
use eframe::{
egui::{
plot::{BarChart, Line, PlotUi, Points, Value, Values},
widgets::plot::Bar,
},
epaint::Color32,
egui::{
plot::{BarChart, Line, PlotUi, Points, Value, Values},
widgets::plot::Bar,
},
epaint::Color32,
};
use crate::misc::digits_precision;
#[derive(Clone)]
pub struct FunctionOutput {
pub(crate) back: Option<Vec<Value>>,
pub(crate) integral: Option<(Vec<Bar>, f64)>,
pub(crate) derivative: Option<Vec<Value>>,
pub(crate) extrema: Option<Vec<Value>>,
pub(crate) roots: Option<Vec<Value>>,
pub(crate) back: Option<Vec<Value>>,
pub(crate) integral: Option<(Vec<Bar>, f64)>,
pub(crate) derivative: Option<Vec<Value>>,
pub(crate) extrema: Option<Vec<Value>>,
pub(crate) roots: Option<Vec<Value>>,
}
impl FunctionOutput {
pub fn new_empty() -> Self {
Self {
back: None,
integral: None,
derivative: None,
extrema: None,
roots: None,
}
}
pub fn new_empty() -> Self {
Self {
back: None,
integral: None,
derivative: None,
extrema: None,
roots: None,
}
}
pub fn invalidate_whole(&mut self) {
self.back = None;
self.integral = None;
self.derivative = None;
self.extrema = None;
self.roots = None;
}
pub fn invalidate_whole(&mut self) {
self.back = None;
self.integral = None;
self.derivative = None;
self.extrema = None;
self.roots = None;
}
pub fn invalidate_back(&mut self) { self.back = None; }
pub fn invalidate_back(&mut self) { self.back = None; }
pub fn invalidate_integral(&mut self) { self.integral = None; }
pub fn invalidate_integral(&mut self) { self.integral = None; }
pub fn invalidate_derivative(&mut self) { self.derivative = None; }
pub fn invalidate_derivative(&mut self) { self.derivative = None; }
pub fn invalidate_points(&mut self) {
self.extrema = None;
self.roots = None;
}
pub fn invalidate_points(&mut self) {
self.extrema = None;
self.roots = None;
}
pub fn display(
&self, plot_ui: &mut PlotUi, func_str: &str, derivative_str: &str, step: f64,
derivative_enabled: bool,
) -> f64 {
plot_ui.line(
Line::new(Values::from_values(self.back.clone().unwrap()))
.color(Color32::RED)
.name(func_str),
);
pub fn display(
&self, plot_ui: &mut PlotUi, func_str: &str, derivative_str: &str, step: f64,
derivative_enabled: bool,
) -> f64 {
plot_ui.line(
Line::new(Values::from_values(self.back.clone().unwrap()))
.color(Color32::RED)
.name(func_str),
);
if derivative_enabled {
if let Some(derivative_data) = self.derivative.clone() {
plot_ui.line(
Line::new(Values::from_values(derivative_data))
.color(Color32::GREEN)
.name(derivative_str),
);
}
}
if derivative_enabled {
if let Some(derivative_data) = self.derivative.clone() {
plot_ui.line(
Line::new(Values::from_values(derivative_data))
.color(Color32::GREEN)
.name(derivative_str),
);
}
}
if let Some(extrema_data) = self.extrema.clone() {
plot_ui.points(
Points::new(Values::from_values(extrema_data))
.color(Color32::YELLOW)
.name("Extrema")
.radius(5.0),
);
}
if let Some(extrema_data) = self.extrema.clone() {
plot_ui.points(
Points::new(Values::from_values(extrema_data))
.color(Color32::YELLOW)
.name("Extrema")
.radius(5.0),
);
}
if let Some(roots_data) = self.roots.clone() {
plot_ui.points(
Points::new(Values::from_values(roots_data))
.color(Color32::LIGHT_BLUE)
.name("Root")
.radius(5.0),
);
}
if let Some(roots_data) = self.roots.clone() {
plot_ui.points(
Points::new(Values::from_values(roots_data))
.color(Color32::LIGHT_BLUE)
.name("Root")
.radius(5.0),
);
}
if let Some(integral_data) = self.integral.clone() {
plot_ui.bar_chart(
BarChart::new(integral_data.0)
.color(Color32::BLUE)
.width(step),
);
if let Some(integral_data) = self.integral.clone() {
plot_ui.bar_chart(
BarChart::new(integral_data.0)
.color(Color32::BLUE)
.width(step),
);
digits_precision(integral_data.1, 8)
} else {
f64::NAN
}
}
digits_precision(integral_data.1, 8)
} else {
f64::NAN
}
}
}

View File

@@ -8,27 +8,27 @@ mod misc;
mod parsing;
cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] {
use misc::log_helper;
use wasm_bindgen::prelude::*;
if #[cfg(target_arch = "wasm32")] {
use misc::log_helper;
use wasm_bindgen::prelude::*;
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen(start)]
pub fn start() -> Result<(), wasm_bindgen::JsValue> {
log_helper("Initializing...");
#[wasm_bindgen(start)]
pub fn start() -> Result<(), wasm_bindgen::JsValue> {
log_helper("Initializing...");
// Used in order to hook into `panic!()` to log in the browser's console
log_helper("Initializing panic hooks...");
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
log_helper("Initialized panic hooks!");
// Used in order to hook into `panic!()` to log in the browser's console
log_helper("Initializing panic hooks...");
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
log_helper("Initialized panic hooks!");
log_helper("Finished initializing!");
log_helper("Finished initializing!");
log_helper("Starting App...");
let app = egui_app::MathApp::default();
eframe::start_web("canvas", Box::new(app))
}
}
log_helper("Starting App...");
let app = egui_app::MathApp::default();
eframe::start_web("canvas", Box::new(app))
}
}
}

View File

@@ -9,11 +9,11 @@ mod parsing;
// For running the program natively! (Because why not?)
#[cfg(not(target_arch = "wasm32"))]
fn main() {
let app = egui_app::MathApp::default();
let options = eframe::NativeOptions {
transparent: true,
drag_and_drop_support: true,
..Default::default()
};
eframe::run_native(Box::new(app), options);
let app = egui_app::MathApp::default();
let options = eframe::NativeOptions {
transparent: true,
drag_and_drop_support: true,
..Default::default()
};
eframe::run_native(Box::new(app), options);
}

View File

@@ -1,92 +1,92 @@
cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] {
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
// Use `js_namespace` here to bind `console.log(..)` instead of just
// `log(..)`
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
if #[cfg(target_arch = "wasm32")] {
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
// Use `js_namespace` here to bind `console.log(..)` instead of just
// `log(..)`
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[allow(dead_code)]
pub fn log_helper(s: &str) {
log(s);
}
#[allow(dead_code)]
pub fn log_helper(s: &str) {
log(s);
}
#[allow(dead_code)]
#[allow(unused_variables)]
pub fn debug_log(s: &str) {
#[cfg(debug_assertions)]
log(s);
}
} else {
#[allow(dead_code)]
pub fn log_helper(s: &str) {
println!("{}", s);
}
#[allow(dead_code)]
#[allow(unused_variables)]
pub fn debug_log(s: &str) {
#[cfg(debug_assertions)]
log(s);
}
} else {
#[allow(dead_code)]
pub fn log_helper(s: &str) {
println!("{}", s);
}
#[allow(dead_code)]
#[allow(unused_variables)]
pub fn debug_log(s: &str) {
#[cfg(debug_assertions)]
println!("{}", s);
}
}
#[allow(dead_code)]
#[allow(unused_variables)]
pub fn debug_log(s: &str) {
#[cfg(debug_assertions)]
println!("{}", s);
}
}
}
pub struct SteppedVector {
data: Vec<f64>,
min: f64,
max: f64,
step: f64,
data: Vec<f64>,
min: f64,
max: f64,
step: f64,
}
impl SteppedVector {
pub fn get_index(&self, x: f64) -> Option<usize> {
if (x > self.max) | (self.min > x) {
return None;
}
pub fn get_index(&self, x: f64) -> Option<usize> {
if (x > self.max) | (self.min > x) {
return None;
}
// Should work....
let possible_i = ((x + self.min) / self.step) as usize;
if self.data[possible_i] == x {
Some(possible_i)
} else {
None
}
// Should work....
let possible_i = ((x + self.min) / self.step) as usize;
if self.data[possible_i] == x {
Some(possible_i)
} else {
None
}
// Not really needed as the above code should handle everything
/*
for (i, ele) in self.data.iter().enumerate() {
if ele > &x {
return None;
} else if &x == ele {
return Some(i);
}
}
None
*/
}
// Not really needed as the above code should handle everything
/*
for (i, ele) in self.data.iter().enumerate() {
if ele > &x {
return None;
} else if &x == ele {
return Some(i);
}
}
None
*/
}
}
impl From<Vec<f64>> for SteppedVector {
// Note: input `data` is assumed to be sorted from min to max
fn from(data: Vec<f64>) -> SteppedVector {
let max = data[0];
let min = data[data.len() - 1];
let step = (max - min).abs() / ((data.len() - 1) as f64);
SteppedVector {
data,
min,
max,
step,
}
}
// Note: input `data` is assumed to be sorted from min to max
fn from(data: Vec<f64>) -> SteppedVector {
let max = data[0];
let min = data[data.len() - 1];
let step = (max - min).abs() / ((data.len() - 1) as f64);
SteppedVector {
data,
min,
max,
step,
}
}
}
// Rounds f64 to specific number of digits
pub fn digits_precision(x: f64, digits: usize) -> f64 {
let large_number: f64 = 10.0_f64.powf(digits as f64);
(x * large_number).round() / large_number
let large_number: f64 = 10.0_f64.powf(digits as f64);
(x * large_number).round() / large_number
}

View File

@@ -1,54 +1,54 @@
use exmex::prelude::*;
lazy_static::lazy_static! {
static ref EMPTY_FUNCTION: FlatEx<f64> = exmex::parse::<f64>("0/0").unwrap();
static ref EMPTY_FUNCTION: FlatEx<f64> = exmex::parse::<f64>("0/0").unwrap();
}
#[derive(Clone)]
pub struct BackingFunction {
function: FlatEx<f64>,
derivative_1: FlatEx<f64>,
derivative_2: FlatEx<f64>,
function: FlatEx<f64>,
derivative_1: FlatEx<f64>,
derivative_2: FlatEx<f64>,
}
impl BackingFunction {
pub fn new(func_str: &str) -> Self {
let function = exmex::parse::<f64>(func_str).unwrap();
let derivative_1 = function
.partial(0)
.unwrap_or_else(|_| EMPTY_FUNCTION.clone());
let derivative_2 = function
.partial_iter([0, 0].iter())
.unwrap_or_else(|_| EMPTY_FUNCTION.clone());
pub fn new(func_str: &str) -> Self {
let function = exmex::parse::<f64>(func_str).unwrap();
let derivative_1 = function
.partial(0)
.unwrap_or_else(|_| EMPTY_FUNCTION.clone());
let derivative_2 = function
.partial_iter([0, 0].iter())
.unwrap_or_else(|_| EMPTY_FUNCTION.clone());
Self {
function,
derivative_1,
derivative_2,
}
}
Self {
function,
derivative_1,
derivative_2,
}
}
pub fn get_derivative_str(&self) -> String {
String::from(self.derivative_1.unparse()).replace("{x}", "x")
}
pub fn get_derivative_str(&self) -> String {
String::from(self.derivative_1.unparse()).replace("{x}", "x")
}
pub fn get(&self, x: f64) -> f64 { self.function.eval(&[x]).unwrap_or(f64::NAN) }
pub fn get(&self, x: f64) -> f64 { self.function.eval(&[x]).unwrap_or(f64::NAN) }
pub fn get_derivative_1(&self, x: f64) -> f64 {
self.derivative_1.eval(&[x]).unwrap_or(f64::NAN)
}
pub fn get_derivative_1(&self, x: f64) -> f64 {
self.derivative_1.eval(&[x]).unwrap_or(f64::NAN)
}
pub fn get_derivative_2(&self, x: f64) -> f64 {
self.derivative_2.eval(&[x]).unwrap_or(f64::NAN)
}
pub fn get_derivative_2(&self, x: f64) -> f64 {
self.derivative_2.eval(&[x]).unwrap_or(f64::NAN)
}
}
lazy_static::lazy_static! {
static ref VALID_VARIABLES: Vec<char> = "xXeEπ".chars().collect();
static ref LETTERS: Vec<char> = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
.chars()
.collect();
static ref NUMBERS: Vec<char> = "0123456789".chars().collect();
static ref VALID_VARIABLES: Vec<char> = "xXeEπ".chars().collect();
static ref LETTERS: Vec<char> = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
.chars()
.collect();
static ref NUMBERS: Vec<char> = "0123456789".chars().collect();
}
/*
@@ -57,173 +57,173 @@ One limitation though, variables with multiple characters like `pi` cannot be mu
In the future I may want to completely rewrite this or implement this natively in exmex.
*/
pub fn process_func_str(function_in: String) -> String {
let function = function_in.replace("log10(", "log(").replace("pi", "π"); // pi -> π and log10 -> log
let function_chars: Vec<char> = function.chars().collect();
let mut output_string: String = String::new();
let mut prev_chars: Vec<char> = Vec::new();
for c in function_chars {
let mut add_asterisk: bool = false;
let prev_chars_len = prev_chars.len();
let function = function_in.replace("log10(", "log(").replace("pi", "π"); // pi -> π and log10 -> log
let function_chars: Vec<char> = function.chars().collect();
let mut output_string: String = String::new();
let mut prev_chars: Vec<char> = Vec::new();
for c in function_chars {
let mut add_asterisk: bool = false;
let prev_chars_len = prev_chars.len();
let prev_prev_prev_char = if prev_chars_len >= 3 {
*prev_chars.get(prev_chars_len - 3).unwrap()
} else {
' '
};
let prev_prev_prev_char = if prev_chars_len >= 3 {
*prev_chars.get(prev_chars_len - 3).unwrap()
} else {
' '
};
let prev_prev_char = if prev_chars_len >= 2 {
*prev_chars.get(prev_chars_len - 2).unwrap()
} else {
' '
};
let prev_prev_char = if prev_chars_len >= 2 {
*prev_chars.get(prev_chars_len - 2).unwrap()
} else {
' '
};
let prev_char = if prev_chars_len >= 1 {
*prev_chars.get(prev_chars_len - 1).unwrap()
} else {
' '
};
let prev_char = if prev_chars_len >= 1 {
*prev_chars.get(prev_chars_len - 1).unwrap()
} else {
' '
};
if (prev_prev_prev_char == 'l')
&& (prev_prev_char == 'o')
&& (prev_char == 'g')
&& (NUMBERS.contains(&c))
{
prev_chars.push(c);
output_string += &c.to_string();
continue;
}
if (prev_prev_prev_char == 'l')
&& (prev_prev_char == 'o')
&& (prev_char == 'g')
&& (NUMBERS.contains(&c))
{
prev_chars.push(c);
output_string += &c.to_string();
continue;
}
let c_letters_var = LETTERS.contains(&c) | VALID_VARIABLES.contains(&c);
let prev_letters_var = VALID_VARIABLES.contains(&prev_char) | LETTERS.contains(&prev_char);
let c_letters_var = LETTERS.contains(&c) | VALID_VARIABLES.contains(&c);
let prev_letters_var = VALID_VARIABLES.contains(&prev_char) | LETTERS.contains(&prev_char);
if prev_char == ')' {
if (c == '(') | NUMBERS.contains(&c) | c_letters_var {
add_asterisk = true;
}
} else if c == '(' {
if (VALID_VARIABLES.contains(&prev_char)
| (')' == prev_char)
| NUMBERS.contains(&prev_char))
&& !LETTERS.contains(&prev_prev_char)
{
add_asterisk = true;
}
} else if NUMBERS.contains(&prev_char) {
if (c == '(') | c_letters_var {
add_asterisk = true;
}
} else if LETTERS.contains(&c) {
if NUMBERS.contains(&prev_char)
| (VALID_VARIABLES.contains(&prev_char) && VALID_VARIABLES.contains(&c))
{
add_asterisk = true;
}
} else if (NUMBERS.contains(&c) | c_letters_var) && prev_letters_var {
add_asterisk = true;
}
if prev_char == ')' {
if (c == '(') | NUMBERS.contains(&c) | c_letters_var {
add_asterisk = true;
}
} else if c == '(' {
if (VALID_VARIABLES.contains(&prev_char)
| (')' == prev_char)
| NUMBERS.contains(&prev_char))
&& !LETTERS.contains(&prev_prev_char)
{
add_asterisk = true;
}
} else if NUMBERS.contains(&prev_char) {
if (c == '(') | c_letters_var {
add_asterisk = true;
}
} else if LETTERS.contains(&c) {
if NUMBERS.contains(&prev_char)
| (VALID_VARIABLES.contains(&prev_char) && VALID_VARIABLES.contains(&c))
{
add_asterisk = true;
}
} else if (NUMBERS.contains(&c) | c_letters_var) && prev_letters_var {
add_asterisk = true;
}
if add_asterisk {
output_string += "*";
}
if add_asterisk {
output_string += "*";
}
prev_chars.push(c);
output_string += &c.to_string();
}
prev_chars.push(c);
output_string += &c.to_string();
}
output_string.replace("log(", "log10(")
output_string.replace("log(", "log10(")
}
// Tests function to make sure it's able to be parsed. Returns the string of the Error produced, or an empty string if it runs successfully.
pub fn test_func(function_string: &str) -> Option<String> {
let parse_result = exmex::parse::<f64>(function_string);
let parse_result = exmex::parse::<f64>(function_string);
match parse_result {
Err(e) => Some(e.to_string()),
Ok(_) => {
let var_names = parse_result.unwrap().var_names().to_vec();
match parse_result {
Err(e) => Some(e.to_string()),
Ok(_) => {
let var_names = parse_result.unwrap().var_names().to_vec();
if var_names != ["x"] {
let var_names_not_x: Vec<String> = var_names
.iter()
.filter(|ele| *ele != &"x".to_owned())
.cloned()
.collect::<Vec<String>>();
if var_names != ["x"] {
let var_names_not_x: Vec<String> = var_names
.iter()
.filter(|ele| *ele != &"x".to_owned())
.cloned()
.collect::<Vec<String>>();
return match var_names_not_x.len() {
1 => {
let var_name = &var_names_not_x[0];
if var_name == "e" {
Some(String::from(
"If trying to use Euler's number, please use an uppercase E",
))
} else {
Some(format!("Error: invalid variable: {}", var_name))
}
}
_ => Some(format!("Error: invalid variables: {:?}", var_names_not_x)),
};
}
return match var_names_not_x.len() {
1 => {
let var_name = &var_names_not_x[0];
if var_name == "e" {
Some(String::from(
"If trying to use Euler's number, please use an uppercase E",
))
} else {
Some(format!("Error: invalid variable: {}", var_name))
}
}
_ => Some(format!("Error: invalid variables: {:?}", var_names_not_x)),
};
}
None
}
}
None
}
}
}
// Used for testing: passes function to `add_asterisks` before running `test_func`
#[cfg(test)]
fn test_func_helper(function_string: &str) -> Option<String> {
test_func(&process_func_str(function_string.to_string()))
test_func(&process_func_str(function_string.to_string()))
}
#[test]
fn test_func_test() {
// These shouldn't fail
assert!(test_func_helper("x^2").is_none());
assert!(test_func_helper("2x").is_none());
// assert!(test_func_helper("e^x").is_none()); // need to fix!!! PR to exmex
assert!(test_func_helper("E^x").is_none());
assert!(test_func_helper("log10(x)").is_none());
assert!(test_func_helper("xxxxx").is_none());
// These shouldn't fail
assert!(test_func_helper("x^2").is_none());
assert!(test_func_helper("2x").is_none());
// assert!(test_func_helper("e^x").is_none()); // need to fix!!! PR to exmex
assert!(test_func_helper("E^x").is_none());
assert!(test_func_helper("log10(x)").is_none());
assert!(test_func_helper("xxxxx").is_none());
// Expect these to fail
assert!(test_func_helper("a").is_some());
assert!(test_func_helper("l^2").is_some());
assert!(test_func_helper("log222(x)").is_some());
assert!(test_func_helper("abcdef").is_some());
// Expect these to fail
assert!(test_func_helper("a").is_some());
assert!(test_func_helper("l^2").is_some());
assert!(test_func_helper("log222(x)").is_some());
assert!(test_func_helper("abcdef").is_some());
}
// Tests to make sure my cursed function works as intended
#[test]
fn func_process_test() {
assert_eq!(&process_func_str("2x".to_string()), "2*x");
assert_eq!(&process_func_str("x2".to_string()), "x*2");
assert_eq!(&process_func_str("x(1+3)".to_string()), "x*(1+3)");
assert_eq!(&process_func_str("(1+3)x".to_string()), "(1+3)*x");
assert_eq!(&process_func_str("sin(x)".to_string()), "sin(x)");
assert_eq!(&process_func_str("2sin(x)".to_string()), "2*sin(x)");
assert_eq!(&process_func_str("max(x)".to_string()), "max(x)");
assert_eq!(&process_func_str("2e^x".to_string()), "2*e^x");
assert_eq!(&process_func_str("2max(x)".to_string()), "2*max(x)");
assert_eq!(&process_func_str("cos(sin(x))".to_string()), "cos(sin(x))");
assert_eq!(&process_func_str("x^(1+2x)".to_string()), "x^(1+2*x)");
assert_eq!(
&process_func_str("(x+2)x(1+3)".to_string()),
"(x+2)*x*(1+3)"
);
assert_eq!(&process_func_str("(x+2)(1+3)".to_string()), "(x+2)*(1+3)");
assert_eq!(&process_func_str("xxx".to_string()), "x*x*x");
assert_eq!(&process_func_str("eee".to_string()), "e*e*e");
assert_eq!(&process_func_str("pi(x+2)".to_string()), "π*(x+2)");
assert_eq!(&process_func_str("(x)pi".to_string()), "(x)*π");
assert_eq!(&process_func_str("2e".to_string()), "2*e");
assert_eq!(&process_func_str("2log10(x)".to_string()), "2*log10(x)");
assert_eq!(&process_func_str("2log(x)".to_string()), "2*log10(x)");
assert_eq!(&process_func_str("x!".to_string()), "x!");
assert_eq!(&process_func_str("pipipipipipi".to_string()), "π*π*π*π*π*π");
assert_eq!(&process_func_str("10pi".to_string()), "10*π");
assert_eq!(&process_func_str("pi10".to_string()), "π*10");
assert_eq!(&process_func_str("2x".to_string()), "2*x");
assert_eq!(&process_func_str("x2".to_string()), "x*2");
assert_eq!(&process_func_str("x(1+3)".to_string()), "x*(1+3)");
assert_eq!(&process_func_str("(1+3)x".to_string()), "(1+3)*x");
assert_eq!(&process_func_str("sin(x)".to_string()), "sin(x)");
assert_eq!(&process_func_str("2sin(x)".to_string()), "2*sin(x)");
assert_eq!(&process_func_str("max(x)".to_string()), "max(x)");
assert_eq!(&process_func_str("2e^x".to_string()), "2*e^x");
assert_eq!(&process_func_str("2max(x)".to_string()), "2*max(x)");
assert_eq!(&process_func_str("cos(sin(x))".to_string()), "cos(sin(x))");
assert_eq!(&process_func_str("x^(1+2x)".to_string()), "x^(1+2*x)");
assert_eq!(
&process_func_str("(x+2)x(1+3)".to_string()),
"(x+2)*x*(1+3)"
);
assert_eq!(&process_func_str("(x+2)(1+3)".to_string()), "(x+2)*(1+3)");
assert_eq!(&process_func_str("xxx".to_string()), "x*x*x");
assert_eq!(&process_func_str("eee".to_string()), "e*e*e");
assert_eq!(&process_func_str("pi(x+2)".to_string()), "π*(x+2)");
assert_eq!(&process_func_str("(x)pi".to_string()), "(x)*π");
assert_eq!(&process_func_str("2e".to_string()), "2*e");
assert_eq!(&process_func_str("2log10(x)".to_string()), "2*log10(x)");
assert_eq!(&process_func_str("2log(x)".to_string()), "2*log10(x)");
assert_eq!(&process_func_str("x!".to_string()), "x!");
assert_eq!(&process_func_str("pipipipipipi".to_string()), "π*π*π*π*π*π");
assert_eq!(&process_func_str("10pi".to_string()), "10*π");
assert_eq!(&process_func_str("pi10".to_string()), "π*10");
// Need to fix these checks, maybe I need to rewrite the whole asterisk adding system... (or just implement these changes into meval-rs, idk)
// assert_eq!(&add_asterisks("emax(x)".to_string()), "e*max(x)");
// assert_eq!(&add_asterisks("pisin(x)".to_string()), "pi*sin(x)");
// Need to fix these checks, maybe I need to rewrite the whole asterisk adding system... (or just implement these changes into meval-rs, idk)
// assert_eq!(&add_asterisks("emax(x)".to_string()), "e*max(x)");
// assert_eq!(&add_asterisks("pisin(x)".to_string()), "pi*sin(x)");
}