From bd8699875508b5f292f99d46738ec9d67f3dde13 Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Fri, 4 Mar 2022 16:29:45 -0500 Subject: [PATCH] lots --- Cargo.toml | 2 +- src/egui_app.rs | 33 ++++++++++++------ src/function.rs | 92 ++++++++++++++++++++++++------------------------- src/lib.rs | 1 + src/main.rs | 1 + src/misc.rs | 89 +++++++++++++++-------------------------------- src/parsing.rs | 61 ++++++++++++++++++++++++++++++++ 7 files changed, 159 insertions(+), 120 deletions(-) create mode 100644 src/parsing.rs diff --git a/Cargo.toml b/Cargo.toml index c59bb3e..dce2f79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,12 +22,12 @@ opt-level = 2 lto = false [dependencies] -meval = { git = "https://github.com/Titaniumtown/meval-rs.git" } eframe = { git = "https://github.com/Titaniumtown/egui", default-features = false, features = ["egui_glow"] } 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" } [build-dependencies] shadow-rs = "0.9" diff --git a/src/egui_app.rs b/src/egui_app.rs index 2a30da7..f08b2cf 100644 --- a/src/egui_app.rs +++ b/src/egui_app.rs @@ -1,5 +1,7 @@ use crate::function::{FunctionEntry, RiemannSum}; -use crate::misc::{add_asterisks, digits_precision, test_func}; +use crate::misc::{add_asterisks, digits_precision}; +use crate::parsing::test_func; + use const_format::formatc; use eframe::{egui, epi}; use egui::plot::Plot; @@ -141,7 +143,7 @@ pub struct MathApp { func_strs: Vec, // Stores last error from parsing functions (used to display the same error when side panel is minimized) - last_error: String, + last_error: Vec<(usize, String)>, // Stores font data that's used when displaying text font: FontData, @@ -158,7 +160,7 @@ impl Default for MathApp { Self { functions: vec![FunctionEntry::empty().integral(true)], func_strs: vec![String::from(DEFAULT_FUNCION)], - last_error: String::new(), + last_error: Vec::new(), font: FontData::from_static(&FONT_FILE), last_info: (vec![0.0], Duration::ZERO), settings: AppSettings::default(), @@ -230,7 +232,6 @@ impl MathApp { ); let mut remove_i: Option = None; - self.last_error = String::new(); for (i, function) in self.functions.iter_mut().enumerate() { let mut integral_toggle: bool = false; let mut derivative_toggle: bool = false; @@ -286,8 +287,8 @@ impl MathApp { if ui .add(Button::new("d/dx")) .on_hover_text(match derivative_enabled { - true => "Don't Calculate Derivative", - false => "Calculate Derivative", + true => "Don't Differentiate", + false => "Differentiate", }) .clicked() { @@ -297,12 +298,14 @@ impl MathApp { ui.text_edit_singleline(&mut self.func_strs[i]); }); - if !self.func_strs[i].is_empty() { + if (self.func_strs[i].clone() != function.get_func_str()) + | self.last_error.iter().any(|ele| ele.0 == i) + { let proc_func_str = add_asterisks(self.func_strs[i].clone()); - let func_test_output = test_func(proc_func_str.clone()); + // let proc_func_str = self.func_strs[i].clone(); + let func_test_output = test_func(&proc_func_str); if let Some(test_output_value) = func_test_output { - self.last_error += - &format!("(Function #{}) {}\n", i, test_output_value); + self.last_error.push((i, test_output_value)); } else { function.update( proc_func_str, @@ -321,6 +324,12 @@ impl MathApp { Some(self.settings.integral_num), Some(self.settings.sum), ); + self.last_error = self + .last_error + .iter() + .filter(|(i_ele, _)| i_ele != &i) + .map(|(a, b)| (*a, b.clone())) + .collect(); } } else { function.empty_func_str(); @@ -470,7 +479,9 @@ impl epi::App for MathApp { CentralPanel::default().show(ctx, |ui| { if !self.last_error.is_empty() { ui.centered_and_justified(|ui| { - ui.heading(self.last_error.clone()); + self.last_error.iter().for_each(|ele| { + ui.heading(&(&format!("(Function #{}) {}\n", ele.0, ele.1)).to_string()); + }) }); return; } diff --git a/src/function.rs b/src/function.rs index 621d145..daa62f8 100644 --- a/src/function.rs +++ b/src/function.rs @@ -1,13 +1,14 @@ #![allow(clippy::too_many_arguments)] // Clippy, shut #[allow(unused_imports)] -use crate::misc::{debug_log, BackingFunction, BoxFunction, SteppedVector, EPSILON}; +use crate::misc::{debug_log, SteppedVector}; + +use crate::parsing::BackingFunction; use eframe::egui::{ plot::{BarChart, Line, Value, Values}, widgets::plot::Bar, }; -use meval::Expr; use std::fmt::{self, Debug}; #[derive(PartialEq, Debug, Copy, Clone)] @@ -41,14 +42,11 @@ pub struct FunctionEntry { sum: RiemannSum, } -// x^2 function, set here so we don't have to regenerate it every time a new function is made -fn default_function(x: f64) -> f64 { x.powi(2) } - impl FunctionEntry { // Creates Empty Function instance pub fn empty() -> Self { Self { - function: BackingFunction::new(Box::new(default_function)), + function: BackingFunction::new("x^2").unwrap(), func_str: String::new(), min_x: -1.0, max_x: 1.0, @@ -76,10 +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(Box::new({ - let expr: Expr = func_str.parse().unwrap(); - expr.bind("x").unwrap() - })); + self.function = BackingFunction::new(&func_str).unwrap(); self.back_cache = None; self.front_cache = None; self.derivative_cache = None; @@ -245,7 +240,7 @@ impl FunctionEntry { let step = (self.integral_min_x - self.integral_max_x).abs() / (self.integral_num as f64); let mut area: f64 = 0.0; - let data2: Vec<(f64, f64, f64)> = (1..=self.integral_num) + let data2: Vec<(f64, f64, f64)> = (0..self.integral_num) .map(|e| { let x: f64 = ((e as f64) * step) + self.integral_min_x; let step_offset = step * x.signum(); // store the offset here so it doesn't have to be calculated multiple times @@ -293,16 +288,19 @@ impl FunctionEntry { self } + #[allow(dead_code)] pub fn integral_num(mut self, integral_num: usize) -> Self { self.integral_num = integral_num; self } + #[allow(dead_code)] pub fn pixel_width(mut self, pixel_width: usize) -> Self { self.pixel_width = pixel_width; self } + #[allow(dead_code)] pub fn integral_bounds(mut self, min_x: f64, max_x: f64) -> Self { if min_x >= max_x { panic!("integral_bounds: min_x is larger than max_x"); @@ -353,9 +351,10 @@ fn left_function_test() { (0.8, 1.5999999999349868), ]; - let area_target = 0.8720000000000001; + let area_target = 0.9600000000000001; let vec_bars_target = vec![ + 1.44, 1.0, 0.6400000000000001, 0.3599999999999998, @@ -365,20 +364,19 @@ fn left_function_test() { 0.16000000000000011, 0.3600000000000001, 0.6400000000000001, - 1.0, ]; let vec_integral_target = vec![ - (-0.9, 0.2), - (-0.7, 0.32800000000000007), - (-0.4999999999999999, 0.4), - (-0.29999999999999993, 0.432), - (0.1, 0.432), - (0.30000000000000016, 0.44), - (0.5000000000000001, 0.47200000000000003), - (0.7000000000000001, 0.544), - (0.9, 0.672), - (1.1, 0.8720000000000001), + (-1.1, 0.288), + (-0.9, 0.488), + (-0.7, 0.616), + (-0.4999999999999999, 0.688), + (-0.29999999999999993, 0.72), + (0.1, 0.72), + (0.30000000000000016, 0.728), + (0.5000000000000001, 0.76), + (0.7000000000000001, 0.8320000000000001), + (0.9, 0.9600000000000001), ]; { @@ -476,9 +474,10 @@ fn middle_function_test() { (0.8, 1.5999999999349868), ]; - let area_target = 0.9200000000000002; + let area_target = 0.92; let vec_bars_target = vec![ + 1.22, 0.8200000000000001, 0.5, 0.2599999999999999, @@ -488,20 +487,19 @@ fn middle_function_test() { 0.2600000000000001, 0.5000000000000001, 0.8200000000000001, - 1.22, ]; let vec_integral_target = vec![ - (-0.9, 0.16400000000000003), - (-0.7, 0.264), - (-0.4999999999999999, 0.316), - (-0.29999999999999993, 0.336), - (0.1, 0.34), - (0.30000000000000016, 0.36000000000000004), - (0.5000000000000001, 0.4120000000000001), - (0.7000000000000001, 0.5120000000000001), - (0.9, 0.6760000000000002), - (1.1, 0.9200000000000002), + (-1.1, 0.244), + (-0.9, 0.40800000000000003), + (-0.7, 0.508), + (-0.4999999999999999, 0.5599999999999999), + (-0.29999999999999993, 0.58), + (0.1, 0.584), + (0.30000000000000016, 0.604), + (0.5000000000000001, 0.656), + (0.7000000000000001, 0.756), + (0.9, 0.92), ]; { @@ -599,9 +597,10 @@ fn right_function_test() { (0.8, 1.5999999999349868), ]; - let area_target = 0.9680000000000002; + let area_target = 0.8800000000000001; let vec_bars_target = vec![ + 1.0, 0.6400000000000001, 0.36, 0.15999999999999992, @@ -611,20 +610,19 @@ fn right_function_test() { 0.3600000000000001, 0.6400000000000001, 1.0, - 1.44, ]; let vec_integral_target = vec![ - (-0.9, 0.12800000000000003), - (-0.7, 0.2), - (-0.4999999999999999, 0.23199999999999998), - (-0.29999999999999993, 0.24), - (0.1, 0.248), - (0.30000000000000016, 0.28), - (0.5000000000000001, 0.35200000000000004), - (0.7000000000000001, 0.4800000000000001), - (0.9, 0.6800000000000002), - (1.1, 0.9680000000000002), + (-1.1, 0.2), + (-0.9, 0.32800000000000007), + (-0.7, 0.4000000000000001), + (-0.4999999999999999, 0.43200000000000005), + (-0.29999999999999993, 0.44000000000000006), + (0.1, 0.44800000000000006), + (0.30000000000000016, 0.4800000000000001), + (0.5000000000000001, 0.5520000000000002), + (0.7000000000000001, 0.6800000000000002), + (0.9, 0.8800000000000001), ]; { diff --git a/src/lib.rs b/src/lib.rs index 39eed1f..f8507b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ mod egui_app; mod function; mod misc; +mod parsing; cfg_if::cfg_if! { if #[cfg(target_arch = "wasm32")] { diff --git a/src/main.rs b/src/main.rs index 82167f8..0523209 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod egui_app; mod function; mod misc; +mod parsing; // For running the program natively! (Because why not?) #[cfg(not(target_arch = "wasm32"))] diff --git a/src/misc.rs b/src/misc.rs index 036745a..66b6ca0 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -1,67 +1,32 @@ -use meval::Expr; - -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; -#[cfg(target_arch = "wasm32")] -#[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) { - #[cfg(target_arch = "wasm32")] - log(s); - - #[cfg(not(target_arch = "wasm32"))] - println!("{}", s); -} - -#[cfg(debug_assertions)] -#[allow(dead_code)] -pub fn debug_log(s: &str) { - #[cfg(target_arch = "wasm32")] - log(s); - - #[cfg(not(target_arch = "wasm32"))] - println!("{}", s); -} - -#[cfg(not(debug_assertions))] -#[allow(dead_code)] -pub fn debug_log(_s: &str) {} - -pub const EPSILON: f64 = 5.0e-7; - -pub type BoxFunction = Box f64>; - -pub struct BackingFunction { - function: BoxFunction, -} - -impl BackingFunction { - pub fn new(function: BoxFunction) -> BackingFunction { BackingFunction { function } } - - pub fn get(&self, x: f64) -> f64 { (self.function)(x) } - - pub fn derivative(&self, x: f64, n: u64) -> f64 { - if n == 0 { - return self.get(x); +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); } - let (x1, x2) = (x - EPSILON, x + EPSILON); - let (y1, y2) = (self.derivative(x1, n - 1), (self.derivative(x2, n - 1))); - (y2 - y1) / (EPSILON * 2.0) - } -} + #[allow(dead_code)] + pub fn log_helper(s: &str) { + log(s); + } -impl From for BackingFunction { - fn from(boxfunction: BoxFunction) -> BackingFunction { - BackingFunction { - function: boxfunction, + #[allow(dead_code)] + pub fn debug_log(s: &str) { + log(s); + } + } else { + #[allow(dead_code)] + pub fn log_helper(s: &str) { + println!("{}", s); + } + + #[allow(dead_code)] + pub fn debug_log(s: &str) { + println!("{}", s); } } } @@ -137,6 +102,7 @@ pub fn add_asterisks(function_in: String) -> 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 @@ -166,6 +132,7 @@ pub fn test_func(function_string: String) -> Option { None } +*/ pub struct SteppedVector { data: Vec, diff --git a/src/parsing.rs b/src/parsing.rs new file mode 100644 index 0000000..ed25339 --- /dev/null +++ b/src/parsing.rs @@ -0,0 +1,61 @@ +use evalexpr::*; + +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()), + } +} + +pub struct BackingFunction { + node: Node, +} + +impl BackingFunction { + pub fn new(func_str: &str) -> Result { + let precompiled = build_operator_tree(func_str); + + match precompiled { + Ok(_) => Ok(Self { + node: precompiled.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 derivative(&self, x: f64, n: u64) -> f64 { + if n == 0 { + return self.get(x); + } + + let (x1, x2) = (x - EPSILON, x + EPSILON); + let (y1, y2) = (self.derivative(x1, n - 1), (self.derivative(x2, n - 1))); + (y2 - y1) / DOUBLE_EPSILON + } +}