diff --git a/Cargo.toml b/Cargo.toml index dce2f79..fecd6ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ include-flate = { git = "https://github.com/Titaniumtown/include-flate.git" } shadow-rs = { version = "0.9", default-features = false } const_format = { version = "0.2.22", default-features = false, features = ["fmt"] } cfg-if = "1.0.0" -evalexpr = { git = "https://github.com/Titaniumtown/evalexpr.git" } +meval = { git = "https://github.com/Titaniumtown/meval-rs.git" } [build-dependencies] shadow-rs = "0.9" diff --git a/src/egui_app.rs b/src/egui_app.rs index f08b2cf..46d0a69 100644 --- a/src/egui_app.rs +++ b/src/egui_app.rs @@ -1,6 +1,6 @@ use crate::function::{FunctionEntry, RiemannSum}; -use crate::misc::{add_asterisks, digits_precision}; -use crate::parsing::test_func; +use crate::misc::digits_precision; +use crate::parsing::{add_asterisks, test_func}; use const_format::formatc; use eframe::{egui, epi}; @@ -303,7 +303,7 @@ impl MathApp { { let proc_func_str = add_asterisks(self.func_strs[i].clone()); // let proc_func_str = self.func_strs[i].clone(); - let func_test_output = test_func(&proc_func_str); + let func_test_output = test_func(proc_func_str.clone()); if let Some(test_output_value) = func_test_output { self.last_error.push((i, test_output_value)); } else { diff --git a/src/function.rs b/src/function.rs index daa62f8..ca6abbe 100644 --- a/src/function.rs +++ b/src/function.rs @@ -46,7 +46,7 @@ impl FunctionEntry { // Creates Empty Function instance pub fn empty() -> Self { Self { - function: BackingFunction::new("x^2").unwrap(), + function: BackingFunction::new("x^2"), func_str: String::new(), min_x: -1.0, max_x: 1.0, @@ -74,7 +74,7 @@ impl FunctionEntry { // If the function string changes, just wipe and restart from scratch if func_str != self.func_str { self.func_str = func_str.clone(); - self.function = BackingFunction::new(&func_str).unwrap(); + self.function = BackingFunction::new(&func_str); self.back_cache = None; self.front_cache = None; self.derivative_cache = None; diff --git a/src/misc.rs b/src/misc.rs index 66b6ca0..697a0a0 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -31,109 +31,6 @@ cfg_if::cfg_if! { } } -/* -EXTREMELY Janky function that tries to put asterisks in the proper places to be parsed. This is so cursed. But it works, and I hopefully won't ever have to touch it again. -One limitation though, variables with multiple characters like `pi` cannot be multiplied (like `pipipipi` won't result in `pi*pi*pi*pi`). But that's such a niche use case (and that same thing could be done by using exponents) that it doesn't really matter. -In the future I may want to completely rewrite this or implement this natively into mevel-rs (which would probably be good to do) -*/ -pub fn add_asterisks(function_in: String) -> String { - let function = function_in.replace("log10(", "log(").replace("pi", "π"); // pi -> π and log10 -> log - let valid_variables: Vec = "xeπ".chars().collect(); - let letters: Vec = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - .chars() - .collect(); - let numbers: Vec = "0123456789".chars().collect(); - let function_chars: Vec = function.chars().collect(); - let mut output_string: String = String::new(); - let mut prev_chars: Vec = Vec::new(); - for c in function_chars { - let mut add_asterisk: bool = false; - let prev_chars_len = prev_chars.len(); - - 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 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 add_asterisk { - output_string += "*"; - } - - prev_chars.push(c); - output_string += &c.to_string(); - } - - output_string.replace('π', "pi") // π -> pi -} - -/* -// 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: String) -> Option { - // Factorials do not work, and it would be really difficult to make them work - if function_string.contains('!') { - return Some("Factorials are unsupported".to_string()); - } - - let new_func_str: String = add_asterisks(function_string); - let expr_result = new_func_str.parse(); - let expr_error = match &expr_result { - Ok(_) => None, - Err(error) => Some(format!("{}", error)), - }; - if expr_error.is_some() { - return expr_error; - } - - let expr: Expr = expr_result.unwrap(); - let func_result = expr.bind("x"); - let func_error = match &func_result { - Ok(_) => None, - Err(error) => Some(format!("{}", error)), - }; - if func_error.is_some() { - return func_error; - } - - None -} -*/ - pub struct SteppedVector { data: Vec, min: f64, @@ -190,39 +87,3 @@ 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 } - -// Tests to make sure my cursed function works as intended -#[test] -fn asterisk_test() { - assert_eq!(&add_asterisks("2x".to_string()), "2*x"); - assert_eq!(&add_asterisks("x2".to_string()), "x*2"); - assert_eq!(&add_asterisks("x(1+3)".to_string()), "x*(1+3)"); - assert_eq!(&add_asterisks("(1+3)x".to_string()), "(1+3)*x"); - assert_eq!(&add_asterisks("sin(x)".to_string()), "sin(x)"); - assert_eq!(&add_asterisks("2sin(x)".to_string()), "2*sin(x)"); - assert_eq!(&add_asterisks("max(x)".to_string()), "max(x)"); - assert_eq!(&add_asterisks("2e^x".to_string()), "2*e^x"); - assert_eq!(&add_asterisks("2max(x)".to_string()), "2*max(x)"); - assert_eq!(&add_asterisks("cos(sin(x))".to_string()), "cos(sin(x))"); - assert_eq!(&add_asterisks("x^(1+2x)".to_string()), "x^(1+2*x)"); - assert_eq!(&add_asterisks("(x+2)x(1+3)".to_string()), "(x+2)*x*(1+3)"); - assert_eq!(&add_asterisks("(x+2)(1+3)".to_string()), "(x+2)*(1+3)"); - assert_eq!(&add_asterisks("xxx".to_string()), "x*x*x"); - assert_eq!(&add_asterisks("eee".to_string()), "e*e*e"); - assert_eq!(&add_asterisks("pi(x+2)".to_string()), "pi*(x+2)"); - assert_eq!(&add_asterisks("(x)pi".to_string()), "(x)*pi"); - assert_eq!(&add_asterisks("2e".to_string()), "2*e"); - assert_eq!(&add_asterisks("2log10(x)".to_string()), "2*log(x)"); - assert_eq!(&add_asterisks("2log(x)".to_string()), "2*log(x)"); - assert_eq!(&add_asterisks("x!".to_string()), "x!"); - assert_eq!( - &add_asterisks("pipipipipipi".to_string()), - "pi*pi*pi*pi*pi*pi" - ); - assert_eq!(&add_asterisks("10pi".to_string()), "10*pi"); - assert_eq!(&add_asterisks("pi10".to_string()), "pi*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)"); -} diff --git a/src/parsing.rs b/src/parsing.rs index ed25339..504deaa 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -1,53 +1,25 @@ -use evalexpr::*; +use meval::Expr; pub const EPSILON: f64 = 5.0e-7; pub const DOUBLE_EPSILON: f64 = 10.0e-7; -pub fn test_func(func_str: &str) -> Option { - let precompiled = build_operator_tree(&("x = 10;".to_owned() + func_str)); - - match precompiled { - Ok(_) => { - let precompiled2 = precompiled.unwrap().eval(); - match precompiled2 { - Ok(_) => None, - Err(e) => Some(e.to_string()), - } - } - Err(e) => Some(e.to_string()), - } -} +type BoxFunction = Box f64>; pub struct BackingFunction { - node: Node, + function: BoxFunction, } impl BackingFunction { - pub fn new(func_str: &str) -> Result { - let precompiled = build_operator_tree(func_str); - - match precompiled { - Ok(_) => Ok(Self { - node: precompiled.unwrap(), + pub fn new(func_str: &str) -> Self { + Self { + function: Box::new({ + let expr: Expr = func_str.parse().unwrap(); + expr.bind("x").unwrap() }), - Err(e) => Err(e.to_string()), } } - pub fn get(&self, _x: f64) -> f64 { - let context: HashMapContext = context_map! { - "x" => 0, - "e" => std::f64::consts::E, - "pi" => std::f64::consts::PI, - } - .unwrap(); - - self.node - .eval_with_context(&context) - .unwrap() - .as_number() - .unwrap() - } + pub fn get(&self, x: f64) -> f64 { (self.function)(x) } pub fn derivative(&self, x: f64, n: u64) -> f64 { if n == 0 { @@ -59,3 +31,140 @@ impl BackingFunction { (y2 - y1) / DOUBLE_EPSILON } } + +/* +EXTREMELY Janky function that tries to put asterisks in the proper places to be parsed. This is so cursed. But it works, and I hopefully won't ever have to touch it again. +One limitation though, variables with multiple characters like `pi` cannot be multiplied (like `pipipipi` won't result in `pi*pi*pi*pi`). But that's such a niche use case (and that same thing could be done by using exponents) that it doesn't really matter. +In the future I may want to completely rewrite this or implement this natively into mevel-rs (which would probably be good to do) +*/ +pub fn add_asterisks(function_in: String) -> String { + let function = function_in.replace("log10(", "log(").replace("pi", "π"); // pi -> π and log10 -> log + let valid_variables: Vec = "xeπ".chars().collect(); + let letters: Vec = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + .chars() + .collect(); + let numbers: Vec = "0123456789".chars().collect(); + let function_chars: Vec = function.chars().collect(); + let mut output_string: String = String::new(); + let mut prev_chars: Vec = Vec::new(); + for c in function_chars { + let mut add_asterisk: bool = false; + let prev_chars_len = prev_chars.len(); + + 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 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 add_asterisk { + output_string += "*"; + } + + prev_chars.push(c); + output_string += &c.to_string(); + } + + output_string.replace('π', "pi") // π -> pi +} + +// 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: String) -> Option { + // Factorials do not work, and it would be really difficult to make them work + if function_string.contains('!') { + return Some("Factorials are unsupported".to_string()); + } + + let new_func_str: String = add_asterisks(function_string); + let expr_result = new_func_str.parse(); + let expr_error = match &expr_result { + Ok(_) => None, + Err(error) => Some(format!("{}", error)), + }; + if expr_error.is_some() { + return expr_error; + } + + let expr: Expr = expr_result.unwrap(); + let func_result = expr.bind("x"); + let func_error = match &func_result { + Ok(_) => None, + Err(error) => Some(format!("{}", error)), + }; + if func_error.is_some() { + return func_error; + } + + None +} + +// Tests to make sure my cursed function works as intended +#[test] +fn asterisk_test() { + assert_eq!(&add_asterisks("2x".to_string()), "2*x"); + assert_eq!(&add_asterisks("x2".to_string()), "x*2"); + assert_eq!(&add_asterisks("x(1+3)".to_string()), "x*(1+3)"); + assert_eq!(&add_asterisks("(1+3)x".to_string()), "(1+3)*x"); + assert_eq!(&add_asterisks("sin(x)".to_string()), "sin(x)"); + assert_eq!(&add_asterisks("2sin(x)".to_string()), "2*sin(x)"); + assert_eq!(&add_asterisks("max(x)".to_string()), "max(x)"); + assert_eq!(&add_asterisks("2e^x".to_string()), "2*e^x"); + assert_eq!(&add_asterisks("2max(x)".to_string()), "2*max(x)"); + assert_eq!(&add_asterisks("cos(sin(x))".to_string()), "cos(sin(x))"); + assert_eq!(&add_asterisks("x^(1+2x)".to_string()), "x^(1+2*x)"); + assert_eq!(&add_asterisks("(x+2)x(1+3)".to_string()), "(x+2)*x*(1+3)"); + assert_eq!(&add_asterisks("(x+2)(1+3)".to_string()), "(x+2)*(1+3)"); + assert_eq!(&add_asterisks("xxx".to_string()), "x*x*x"); + assert_eq!(&add_asterisks("eee".to_string()), "e*e*e"); + assert_eq!(&add_asterisks("pi(x+2)".to_string()), "pi*(x+2)"); + assert_eq!(&add_asterisks("(x)pi".to_string()), "(x)*pi"); + assert_eq!(&add_asterisks("2e".to_string()), "2*e"); + assert_eq!(&add_asterisks("2log10(x)".to_string()), "2*log(x)"); + assert_eq!(&add_asterisks("2log(x)".to_string()), "2*log(x)"); + assert_eq!(&add_asterisks("x!".to_string()), "x!"); + assert_eq!( + &add_asterisks("pipipipipipi".to_string()), + "pi*pi*pi*pi*pi*pi" + ); + assert_eq!(&add_asterisks("10pi".to_string()), "10*pi"); + assert_eq!(&add_asterisks("pi10".to_string()), "pi*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)"); +}