From 020064a79eb7e2cabe9b07f6660ed8e0510cf1fa Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Thu, 12 May 2022 10:57:10 -0400 Subject: [PATCH] code improvements --- Cargo.lock | 16 +++ Cargo.toml | 1 + parsing/build.rs | 2 +- parsing/src/autocomplete.rs | 112 +++++++++++++++++ ...lete_helper.rs => autocomplete_hashmap.rs} | 0 parsing/src/lib.rs | 10 +- parsing/src/parsing.rs | 6 +- parsing/src/suggestions.rs | 118 +----------------- src/function_entry.rs | 2 +- src/lib.rs | 4 +- src/math_app.rs | 48 +++---- src/misc.rs | 43 +++++-- tests/misc.rs | 22 +++- tests/parsing.rs | 2 + 14 files changed, 221 insertions(+), 165 deletions(-) create mode 100644 parsing/src/autocomplete.rs rename parsing/src/{autocomplete_helper.rs => autocomplete_hashmap.rs} (100%) diff --git a/Cargo.lock b/Cargo.lock index 52fd3a8..4d30e2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -197,6 +197,15 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +[[package]] +name = "byte-unit" +version = "4.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ebf10dda65f19ff0f42ea15572a359ed60d7fc74fdc984d90310937be0014b" +dependencies = [ + "utf8-width", +] + [[package]] name = "bytemuck" version = "1.9.1" @@ -2358,6 +2367,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8-width" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1" + [[package]] name = "uuid" version = "0.8.2" @@ -2768,6 +2783,7 @@ dependencies = [ "base64", "benchmarks", "bincode", + "byte-unit", "cfg-if 1.0.0", "console_error_panic_hook", "const_format", diff --git a/Cargo.toml b/Cargo.toml index 3d97992..278656f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ uuid = { version = "1", features = ["v4", "fast-rng", "js"] } bincode = "1.3" serde = "1" base64 = { git = "https://github.com/marshallpierce/rust-base64.git" } +byte-unit = "4.0.14" [dev-dependencies] benchmarks = { path = "./benchmarks" } diff --git a/parsing/build.rs b/parsing/build.rs index e9fdaf1..6777916 100644 --- a/parsing/build.rs +++ b/parsing/build.rs @@ -51,5 +51,5 @@ fn generate_hashmap() { include!(concat!( env!("CARGO_MANIFEST_DIR"), - "/src/autocomplete_helper.rs" + "/src/autocomplete_hashmap.rs" )); diff --git a/parsing/src/autocomplete.rs b/parsing/src/autocomplete.rs new file mode 100644 index 0000000..80d87ee --- /dev/null +++ b/parsing/src/autocomplete.rs @@ -0,0 +1,112 @@ +use std::intrinsics::assume; + +use crate::{generate_hint, Hint, HINT_EMPTY}; + +#[derive(PartialEq, Debug)] +pub enum Movement { + Complete, + #[allow(dead_code)] + Down, + #[allow(dead_code)] + Up, + None, +} + +impl Movement { + pub const fn is_none(&self) -> bool { matches!(&self, &Self::None) } + + pub const fn is_complete(&self) -> bool { matches!(&self, &Self::Complete) } +} + +impl const Default for Movement { + fn default() -> Self { Self::None } +} + +#[derive(Clone, PartialEq)] +pub struct AutoComplete<'a> { + pub i: usize, + pub hint: &'a Hint<'a>, + pub string: String, +} + +impl<'a> const Default for AutoComplete<'a> { + fn default() -> AutoComplete<'a> { AutoComplete::EMPTY } +} + +impl<'a> AutoComplete<'a> { + const EMPTY: AutoComplete<'a> = Self { + i: 0, + hint: &HINT_EMPTY, + string: String::new(), + }; + + #[allow(dead_code)] + pub fn update_string(&mut self, string: &str) { + if self.string != string { + // catch empty strings here to avoid call to `generate_hint` and unnecessary logic + if string.is_empty() { + *self = Self::EMPTY; + } else { + self.string = string.to_owned(); + self.do_update_logic(); + } + } + } + + /// Runs update logic assuming that a change to `self.string` has been made + fn do_update_logic(&mut self) { + self.i = 0; + self.hint = generate_hint(&self.string); + } + + #[allow(dead_code)] + pub fn register_movement(&mut self, movement: &Movement) { + if movement.is_none() { + return; + } + + match self.hint { + Hint::Many(hints) => { + // Impossible for plural hints to be singular or non-existant + unsafe { + assume(hints.len() > 1); + assume(!hints.is_empty()); + } + + match movement { + Movement::Up => { + // if self.i is below 1, it's at + match self.i { + 0 => self.i = hints.len() - 1, + _ => self.i -= 1, + } + } + Movement::Down => { + // add one, if resulting value is above maximum i value, set i to 0 + self.i += 1; + if self.i > (hints.len() - 1) { + self.i = 0; + } + } + Movement::Complete => { + unsafe { assume(hints.len() >= (self.i + 1)) } + + self.apply_hint(hints[self.i]); + } + _ => unreachable!(), + } + } + Hint::Single(hint) => { + if movement.is_complete() { + self.apply_hint(hint); + } + } + _ => unreachable!(), + } + } + + pub fn apply_hint(&mut self, hint: &str) { + self.string.push_str(hint); + self.do_update_logic(); + } +} diff --git a/parsing/src/autocomplete_helper.rs b/parsing/src/autocomplete_hashmap.rs similarity index 100% rename from parsing/src/autocomplete_helper.rs rename to parsing/src/autocomplete_hashmap.rs diff --git a/parsing/src/lib.rs b/parsing/src/lib.rs index c588ba9..fa75d1e 100644 --- a/parsing/src/lib.rs +++ b/parsing/src/lib.rs @@ -3,15 +3,17 @@ #![feature(const_default_impls)] #![feature(const_mut_refs)] -mod autocomplete_helper; +mod autocomplete; +mod autocomplete_hashmap; mod parsing; mod suggestions; pub use crate::{ - autocomplete_helper::compile_hashmap, + autocomplete::{AutoComplete, Movement}, + autocomplete_hashmap::compile_hashmap, parsing::{process_func_str, BackingFunction}, suggestions::{ - generate_hint, get_last_term, split_function, split_function_chars, AutoComplete, Hint, - Movement, HINT_EMPTY, SUPPORTED_FUNCTIONS, + generate_hint, get_last_term, split_function, split_function_chars, Hint, HINT_EMPTY, + SUPPORTED_FUNCTIONS, }, }; diff --git a/parsing/src/parsing.rs b/parsing/src/parsing.rs index ac01cf2..e1abff8 100644 --- a/parsing/src/parsing.rs +++ b/parsing/src/parsing.rs @@ -1,6 +1,6 @@ use exmex::prelude::*; -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub(crate) struct FlatExWrapper { func: Option>, } @@ -45,7 +45,7 @@ impl const Default for FlatExWrapper { } /// Function that includes f(x), f'(x), f'(x)'s string representation, and f''(x) -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub struct BackingFunction { /// f(x) function: FlatExWrapper, @@ -73,7 +73,7 @@ impl BackingFunction { nth_derivative: None, }; - pub fn is_none(&self) -> bool { self.function.is_none() } + pub const fn is_none(&self) -> bool { self.function.is_none() } /// Create new [`BackingFunction`] instance pub fn new(func_str: &str) -> Result { diff --git a/parsing/src/suggestions.rs b/parsing/src/suggestions.rs index 70d6ffa..40f0664 100644 --- a/parsing/src/suggestions.rs +++ b/parsing/src/suggestions.rs @@ -205,15 +205,16 @@ pub fn generate_hint<'a>(input: &str) -> &'a Hint<'a> { pub fn get_last_term(chars: &[char]) -> String { assert!(!chars.is_empty()); + let result = split_function_chars(chars, SplitType::Term); unsafe { - split_function_chars(chars, SplitType::Term) - .last() - .unwrap_unchecked() - .to_owned() + assume(!result.is_empty()); + assume(result.len() > 0); + result.last().unwrap_unchecked() } + .to_owned() } -#[derive(PartialEq)] +#[derive(PartialEq, Clone, Copy)] pub enum Hint<'a> { Single(&'a str), Many(&'a [&'a str]), @@ -253,110 +254,3 @@ impl<'a> Hint<'a> { } include!(concat!(env!("OUT_DIR"), "/codegen.rs")); - -#[derive(PartialEq, Debug)] -pub enum Movement { - Complete, - #[allow(dead_code)] - Down, - #[allow(dead_code)] - Up, - None, -} - -impl Movement { - pub const fn is_none(&self) -> bool { matches!(&self, Self::None) } -} - -impl const Default for Movement { - fn default() -> Self { Self::None } -} - -#[derive(Clone)] -pub struct AutoComplete<'a> { - pub i: usize, - pub hint: &'a Hint<'a>, - pub string: String, -} - -impl<'a> const Default for AutoComplete<'a> { - fn default() -> AutoComplete<'a> { AutoComplete::EMPTY } -} - -impl<'a> AutoComplete<'a> { - const EMPTY: AutoComplete<'a> = Self { - i: 0, - hint: &HINT_EMPTY, - string: String::new(), - }; - - #[allow(dead_code)] - pub fn update_string(&mut self, string: &str) { - if self.string != string { - // catch empty strings here to avoid call to `generate_hint` and unnecessary logic - if string.is_empty() { - *self = Self::EMPTY; - } else { - self.string = string.to_owned(); - self.do_update_logic(); - } - } - } - - /// Runs update logic assuming that a change to `self.string` has been made - fn do_update_logic(&mut self) { - self.i = 0; - self.hint = generate_hint(&self.string); - } - - #[allow(dead_code)] - pub fn register_movement(&mut self, movement: &Movement) { - if movement.is_none() { - return; - } - - match self.hint { - Hint::Many(hints) => { - // Impossible for plural hints to be singular or non-existant - unsafe { - assume(hints.len() > 1); - assume(!hints.is_empty()); - } - - match movement { - Movement::Up => { - // if self.i is below 1, it's at - match self.i { - 0 => self.i = hints.len() - 1, - _ => self.i -= 1, - } - } - Movement::Down => { - // add one, if resulting value is above maximum i value, set i to 0 - self.i += 1; - if self.i > (hints.len() - 1) { - self.i = 0; - } - } - Movement::Complete => { - unsafe { assume(hints.len() >= (self.i + 1)) } - - self.apply_hint(hints[self.i]); - } - _ => unreachable!(), - } - } - Hint::Single(hint) => { - if movement == &Movement::Complete { - self.apply_hint(hint); - } - } - _ => unreachable!(), - } - } - - pub fn apply_hint(&mut self, hint: &str) { - self.string.push_str(hint); - self.do_update_logic(); - } -} diff --git a/src/function_entry.rs b/src/function_entry.rs index 80ae2e6..9e13d1d 100644 --- a/src/function_entry.rs +++ b/src/function_entry.rs @@ -32,7 +32,7 @@ impl fmt::Display for Riemann { } /// `FunctionEntry` is a function that can calculate values, integrals, derivatives, etc etc -#[derive(Clone)] +#[derive(PartialEq, Clone)] pub struct FunctionEntry { /// The `BackingFunction` instance that is used to generate `f(x)`, `f'(x)`, and `f''(x)` function: BackingFunction, diff --git a/src/lib.rs b/src/lib.rs index f787792..4f793a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,8 +24,8 @@ pub use crate::{ function_entry::{FunctionEntry, Riemann}, math_app::AppSettings, misc::{ - decimal_round, option_vec_printer, resolution_helper, storage_create, storage_read, - SteppedVector, + decimal_round, format_bytes, hashed_storage_create, hashed_storage_read, + option_vec_printer, resolution_helper, SteppedVector, }, }; diff --git a/src/math_app.rs b/src/math_app.rs index 637bddf..63c73aa 100644 --- a/src/math_app.rs +++ b/src/math_app.rs @@ -125,29 +125,34 @@ impl MathApp { tracing::info!("Web Info: {:?}", web_info); } - let window = web_sys::window().expect("Could not get web_sys window"); + fn get_window() -> web_sys::Window { + web_sys::window().expect("Could not get web_sys window") + } - let document = window.document().expect("Could not get web_sys document"); + fn get_localstorage() -> web_sys::Storage { + get_window().local_storage().expect("failed to get localstorage1").expect("failed to get localstorage2") + } - let loading_element = document + let loading_element = get_window().document() + .expect("Could not get web_sys document") .get_element_by_id("loading") .expect("Couldn't get loading indicator element") .dyn_into::() .unwrap(); + fn update_loading(loading_element: &web_sys::HtmlElement, add: i32) { - let value = - unsafe { loading_element.get_attribute("value").unwrap_unchecked().parse::().unwrap_unchecked() }; - loading_element.set_attribute("value", &(add + value).to_string()).unwrap(); + let value = loading_element.get_attribute("value").expect("unable to get loading_elements's `value`").parse::().expect("unable to parse value as i32"); + loading_element.set_attribute("value", &(add + value).to_string()).expect("unable to set loading_element's `value`"); } const DATA_NAME: &str = "YTBN-DECOMPRESSED"; fn get_storage_decompressed() -> Option> { - if let Ok(Some(data)) = web_sys::window().expect("Could not get web_sys window").local_storage().unwrap().unwrap().get_item("YTBN-DECOMPRESSED") { - let (commit, cached_data) = crate::misc::storage_read(data); + if let Ok(Some(data)) = get_localstorage().get_item(DATA_NAME) { + let (commit, cached_data) = crate::misc::hashed_storage_read(data); if commit == build::SHORT_COMMIT { - tracing::info!("Reading decompression cache"); + tracing::info!("Reading decompression cache. Bytes: {}, or: {}", cached_data.len(), crate::misc::format_bytes(cached_data.len())); return Some(cached_data.to_vec()); } else { tracing::info!("Decompression cache are invalid due to differing commits (build: {}, previous: {})", build::SHORT_COMMIT, commit); @@ -155,33 +160,28 @@ impl MathApp { // is invalid None } - } else { None } } fn set_storage_decompressed(data: &Vec) { - if let Ok(Some(local_storage)) = web_sys::window().expect("Could not get web_sys window").local_storage() { - tracing::info!("Setting decompression cache"); - let saved_data = &crate::misc::storage_create(&build::SHORT_COMMIT.chars().map(|c| c as u8).collect::>(), data.as_slice()); - tracing::info!("Data has length of {}", saved_data.len()); - local_storage.set_item("YTBN-DECOMPRESSED", saved_data).expect("failed to set local storage cache"); - } else { - panic!("unable to get local storage") - } + tracing::info!("Setting decompression cache"); + let saved_data = &crate::misc::hashed_storage_create(&build::SHORT_COMMIT.chars().map(|c| c as u8).collect::>(), data.as_slice()); + tracing::info!("Bytes: {}, or: {}", saved_data.len(), crate::misc::format_bytes(data.len())); + get_localstorage().set_item(DATA_NAME, saved_data).expect("failed to set local storage cache"); } fn get_functions() -> Option { - if let Ok(Some(data)) = web_sys::window().expect("Could not get web_sys window").local_storage().unwrap().unwrap().get_item("YTBN-FUNCTIONS") { - let (commit, func_data) = crate::misc::storage_read(data); + if let Ok(Some(data)) = get_localstorage().get_item("YTBN-FUNCTIONS") { + let (commit, func_data) = crate::misc::hashed_storage_read(data); if commit == build::SHORT_COMMIT { - tracing::info!("Reading old functions"); + tracing::info!("Reading previous function data"); let function_manager: FunctionManager = bincode::deserialize(&func_data).unwrap(); return Some(function_manager); } else { - tracing::info!("Old functions are invalid due to differing commits (build: {}, previous: {})", build::SHORT_COMMIT, commit); + tracing::info!("Previous functions are invalid due to differing commits (build: {}, previous: {})", build::SHORT_COMMIT, commit); // is invalid None } @@ -603,14 +603,14 @@ impl App for MathApp { .local_storage() { tracing::info!("Setting current functions"); - let saved_data = &crate::misc::storage_create( + let saved_data = &crate::misc::hashed_storage_create( &build::SHORT_COMMIT .chars() .map(|c| c as u8) .collect::>(), bincode::serialize(&self.functions).unwrap().as_slice(), ); - tracing::info!("Data has length of {}", saved_data.len()); + tracing::info!("Bytes: {}", saved_data.len()); local_storage .set_item("YTBN-FUNCTIONS", saved_data) .expect("failed to set local function storage"); diff --git a/src/misc.rs b/src/misc.rs index 41ca3b7..e13cc32 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -251,8 +251,9 @@ where T: ToString, { let max_i: i32 = (data.len() as i32) - 1; - "[".to_owned() - + &data + [ + "[", + &data .iter() .map(|x| { x.as_ref() @@ -268,8 +269,10 @@ where } }) .collect::>() - .concat() - + "]" + .concat(), + "]", + ] + .concat() } /// Returns a vector of length `max_i` starting at value `min_x` with resolution of `resolution` @@ -284,22 +287,36 @@ pub fn step_helper(max_i: usize, min_x: &f64, step: &f64) -> Vec { (0..max_i).map(|x| (x as f64 * step) + min_x).collect() } -#[allow(dead_code)] -pub fn storage_create(commit: &[u8], data: &[u8]) -> String { - assert_eq!(commit.len(), 8); +const HASH_LENGTH: usize = 8; - let mut new_data = commit.to_vec(); - let mut data = data.to_vec(); - new_data.append(&mut data); +#[allow(dead_code)] +pub fn hashed_storage_create(hash: &[u8], data: &[u8]) -> String { + debug_assert_eq!(hash.len(), HASH_LENGTH); + debug_assert!(data.len() > 0); + + let new_data = [hash, data].concat(); + debug_assert_eq!(new_data.len(), data.len() + hash.len()); base64::encode(new_data) } #[allow(dead_code)] -pub fn storage_read(data: String) -> (String, Vec) { +pub fn hashed_storage_read(data: String) -> (String, Vec) { let decoded_1 = base64::decode(data).expect("unable to read data"); - let (commit, cached_data) = decoded_1.split_at(8); + debug_assert!(decoded_1.len() > HASH_LENGTH); + + let (hash, cached_data) = decoded_1.split_at(8); + debug_assert_eq!(hash.len(), HASH_LENGTH); + debug_assert!(cached_data.len() > 0); + ( - commit.iter().map(|c| *c as char).collect::(), + hash.iter().map(|c| *c as char).collect::(), cached_data.to_vec(), ) } + +#[allow(dead_code)] +pub fn format_bytes(bytes: usize) -> String { + byte_unit::Byte::from_bytes(bytes as u128) + .get_appropriate_unit(false) + .to_string() +} diff --git a/tests/misc.rs b/tests/misc.rs index 7c053bf..b858dd9 100644 --- a/tests/misc.rs +++ b/tests/misc.rs @@ -1,5 +1,3 @@ -use ytbn_graphing_software::{storage_create, storage_read}; - /// Tests [`SteppedVector`] to ensure everything works properly (helped me find a bunch of issues) #[test] fn stepped_vector() { @@ -97,15 +95,29 @@ fn option_vec_printer() { } #[test] -fn storage() { +fn hash_b64_storage() { + use ytbn_graphing_software::{hashed_storage_create, hashed_storage_read}; + let commit = "abcdefeg".chars().map(|c| c as u8).collect::>(); let data = "really cool data" .chars() .map(|c| c as u8) .collect::>(); - let storage = storage_create(commit.as_slice(), data.as_slice()); + let storage = hashed_storage_create(commit.as_slice(), data.as_slice()); - let read = storage_read(storage); + let read = hashed_storage_read(storage); assert_eq!(read.0.chars().map(|c| c as u8).collect::>(), commit); assert_eq!(read.1, data); } + +#[test] +fn format_bytes() { + use std::collections::HashMap; + use ytbn_graphing_software::format_bytes; + + let values: HashMap = HashMap::from([(1000, "1000 B"), (10000, "10.00 KB")]); + + for (key, value) in values { + assert_eq!(format_bytes(key), value); + } +} diff --git a/tests/parsing.rs b/tests/parsing.rs index f3437c4..f5f80c1 100644 --- a/tests/parsing.rs +++ b/tests/parsing.rs @@ -198,6 +198,7 @@ fn split_function() { ("sin(x)*cos(x)", vec!["sin(x)", "cos(x)"]), ("x*x", vec!["x", "x"]), ("10*10", vec!["10", "10"]), + ("a1b2c3d4", vec!["a1b2c3d4"]), ]); for (key, value) in values { @@ -240,6 +241,7 @@ fn get_last_term() { ("x*x", "x"), ("10*10", "10"), ("sin(cos", "cos"), + ("exp(cos(exp(sin", "sin"), ]); for (key, value) in values {