back to meval
This commit is contained in:
parent
bd86998755
commit
0e2a8fa667
@ -27,7 +27,7 @@ include-flate = { git = "https://github.com/Titaniumtown/include-flate.git" }
|
|||||||
shadow-rs = { version = "0.9", default-features = false }
|
shadow-rs = { version = "0.9", default-features = false }
|
||||||
const_format = { version = "0.2.22", default-features = false, features = ["fmt"] }
|
const_format = { version = "0.2.22", default-features = false, features = ["fmt"] }
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
evalexpr = { git = "https://github.com/Titaniumtown/evalexpr.git" }
|
meval = { git = "https://github.com/Titaniumtown/meval-rs.git" }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
shadow-rs = "0.9"
|
shadow-rs = "0.9"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use crate::function::{FunctionEntry, RiemannSum};
|
use crate::function::{FunctionEntry, RiemannSum};
|
||||||
use crate::misc::{add_asterisks, digits_precision};
|
use crate::misc::digits_precision;
|
||||||
use crate::parsing::test_func;
|
use crate::parsing::{add_asterisks, test_func};
|
||||||
|
|
||||||
use const_format::formatc;
|
use const_format::formatc;
|
||||||
use eframe::{egui, epi};
|
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 = add_asterisks(self.func_strs[i].clone());
|
||||||
// let proc_func_str = 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 {
|
if let Some(test_output_value) = func_test_output {
|
||||||
self.last_error.push((i, test_output_value));
|
self.last_error.push((i, test_output_value));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -46,7 +46,7 @@ impl FunctionEntry {
|
|||||||
// Creates Empty Function instance
|
// Creates Empty Function instance
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Self {
|
Self {
|
||||||
function: BackingFunction::new("x^2").unwrap(),
|
function: BackingFunction::new("x^2"),
|
||||||
func_str: String::new(),
|
func_str: String::new(),
|
||||||
min_x: -1.0,
|
min_x: -1.0,
|
||||||
max_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 the function string changes, just wipe and restart from scratch
|
||||||
if func_str != self.func_str {
|
if func_str != self.func_str {
|
||||||
self.func_str = func_str.clone();
|
self.func_str = func_str.clone();
|
||||||
self.function = BackingFunction::new(&func_str).unwrap();
|
self.function = BackingFunction::new(&func_str);
|
||||||
self.back_cache = None;
|
self.back_cache = None;
|
||||||
self.front_cache = None;
|
self.front_cache = None;
|
||||||
self.derivative_cache = None;
|
self.derivative_cache = None;
|
||||||
|
|||||||
139
src/misc.rs
139
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<char> = "xeπ".chars().collect();
|
|
||||||
let letters: Vec<char> = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
||||||
.chars()
|
|
||||||
.collect();
|
|
||||||
let numbers: Vec<char> = "0123456789".chars().collect();
|
|
||||||
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_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<String> {
|
|
||||||
// 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 {
|
pub struct SteppedVector {
|
||||||
data: Vec<f64>,
|
data: Vec<f64>,
|
||||||
min: f64,
|
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);
|
let large_number: f64 = 10.0_f64.powf(digits as f64);
|
||||||
(x * large_number).round() / large_number
|
(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)");
|
|
||||||
}
|
|
||||||
|
|||||||
183
src/parsing.rs
183
src/parsing.rs
@ -1,53 +1,25 @@
|
|||||||
use evalexpr::*;
|
use meval::Expr;
|
||||||
|
|
||||||
pub const EPSILON: f64 = 5.0e-7;
|
pub const EPSILON: f64 = 5.0e-7;
|
||||||
pub const DOUBLE_EPSILON: f64 = 10.0e-7;
|
pub const DOUBLE_EPSILON: f64 = 10.0e-7;
|
||||||
|
|
||||||
pub fn test_func(func_str: &str) -> Option<String> {
|
type BoxFunction = Box<dyn Fn(f64) -> f64>;
|
||||||
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()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct BackingFunction {
|
pub struct BackingFunction {
|
||||||
node: Node,
|
function: BoxFunction,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BackingFunction {
|
impl BackingFunction {
|
||||||
pub fn new(func_str: &str) -> Result<Self, String> {
|
pub fn new(func_str: &str) -> Self {
|
||||||
let precompiled = build_operator_tree(func_str);
|
Self {
|
||||||
|
function: Box::new({
|
||||||
match precompiled {
|
let expr: Expr = func_str.parse().unwrap();
|
||||||
Ok(_) => Ok(Self {
|
expr.bind("x").unwrap()
|
||||||
node: precompiled.unwrap(),
|
|
||||||
}),
|
}),
|
||||||
Err(e) => Err(e.to_string()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, _x: f64) -> f64 {
|
pub fn get(&self, x: f64) -> f64 { (self.function)(x) }
|
||||||
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 derivative(&self, x: f64, n: u64) -> f64 {
|
pub fn derivative(&self, x: f64, n: u64) -> f64 {
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
@ -59,3 +31,140 @@ impl BackingFunction {
|
|||||||
(y2 - y1) / DOUBLE_EPSILON
|
(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<char> = "xeπ".chars().collect();
|
||||||
|
let letters: Vec<char> = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
.chars()
|
||||||
|
.collect();
|
||||||
|
let numbers: Vec<char> = "0123456789".chars().collect();
|
||||||
|
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_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<String> {
|
||||||
|
// 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)");
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user