From d9c6c8143ebd9b5c286525f7e56f91285a8f1d49 Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Tue, 29 Mar 2022 16:50:28 -0400 Subject: [PATCH] use phf (generate hashmap at compiletime) --- Cargo.toml | 2 + build.rs | 118 +++++++++++++++++++++++++++++++++++++++++++++ src/misc.rs | 37 -------------- src/suggestions.rs | 89 +++++----------------------------- 4 files changed, 133 insertions(+), 113 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d4d7f05..a157ec9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,10 +38,12 @@ serde_json = "1.0.79" tracing = "0.1.32" itertools = "0.10.3" static_assertions = "1.1.0" +phf = "0.10.1" [build-dependencies] shadow-rs = "0.11.0" command-run = "1.1.1" +phf_codegen = "0.10.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] instant = { version = "0.1.12" } diff --git a/build.rs b/build.rs index fe3b018..3e7e192 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,9 @@ +use std::collections::HashSet; +use std::env; +use std::fs::File; +use std::io::{BufWriter, Write}; +use std::path::Path; + fn main() { // rebuild if new commit or contents of `assets` folder changed println!("cargo:rerun-if-changed=.git/logs/HEAD"); @@ -7,4 +13,116 @@ fn main() { .enable_capture() .run(); shadow_rs::new().unwrap(); + + generate_hashmap(); +} + +fn generate_hashmap() { + let path = Path::new(&env::var("OUT_DIR").unwrap()).join("codegen.rs"); + let mut file = BufWriter::new(File::create(&path).unwrap()); + + write!( + &mut file, + "static COMPLETION_HASHMAP: phf::Map<&'static str, &'static str> = {}", + compile_hashmap().build() + ) + .unwrap(); + write!(&mut file, ";\n").unwrap(); +} + +/// List of supported functions from exmex +const SUPPORTED_FUNCTIONS: [&str; 22] = [ + "abs", "signum", "sin", "cos", "tan", "asin", "acos", "atan", "sinh", "cosh", "tanh", "floor", + "round", "ceil", "trunc", "fract", "exp", "sqrt", "cbrt", "ln", "log2", "log10", +]; + +const QUOTE: char = '"'; +fn compile_hashmap() -> phf_codegen::Map { + let mut tuple_list: Vec<(String, String)> = Vec::new(); + + for entry in SUPPORTED_FUNCTIONS + .iter() + .map(|entry| format!("{}(", entry)) + .collect::>() + { + for i in 1..entry.len() { + let (first, last) = entry.split_at(i); + tuple_list.push((first.to_string(), last.to_string())); + } + } + + let mut output: phf_codegen::Map = phf_codegen::Map::new(); + + let key_list: Vec = tuple_list.iter().map(|(key, _)| key.clone()).collect(); + + let mut seen = HashSet::new(); + for (key, value) in tuple_list.clone() { + if seen.contains(&key) { + continue; + } + + seen.insert(key.clone()); + + let duplicate_num = key_list.iter().filter(|ele| **ele == key).count(); + if 1 == duplicate_num { + output.entry(key, &(QUOTE.to_string() + &value + "E.to_string())); + continue; + } + + let same_keys_merged: Vec = tuple_list + .iter() + .filter(|(a, _)| **a == key) + .map(|(a, b)| a.clone() + b) + .collect(); + + let merged_key_value = key.clone() + &value; + + let mut common_substr: Option = None; + for same_key in same_keys_merged { + if let Some(common_substr_unwrapped) = common_substr { + common_substr = common_substring(&common_substr_unwrapped, &same_key); + } else { + common_substr = common_substring(&same_key, &merged_key_value) + } + + if common_substr.is_none() { + break; + } + } + + if let Some(common_substr_unwrapped) = common_substr { + if !common_substr_unwrapped.is_empty() { + output.entry( + key.clone(), + &(QUOTE.to_string() + + &common_substr_unwrapped.replace(&key, "") + + "E.to_string()), + ); + } + } + } + + output +} + +fn common_substring<'a>(a: &'a str, b: &'a str) -> Option { + let a_chars: Vec = a.chars().collect(); + let b_chars: Vec = b.chars().collect(); + if a_chars[0] != b_chars[0] { + return None; + } + + let mut last_value: String = a_chars[0].to_string(); + let max_common_i = std::cmp::min(a.len(), b.len()) - 1; + for i in 1..=max_common_i { + let a_i = a_chars[i]; + let b_i = b_chars[i]; + if a_i == b_i { + last_value += &a_i.to_string() + } else { + break; + } + } + + Some(last_value) } diff --git a/src/misc.rs b/src/misc.rs index e5c9d73..940757d 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -313,28 +313,6 @@ pub fn step_helper(max_i: usize, min_x: f64, step: f64) -> Vec { .collect() } -pub fn common_substring<'a>(a: &'a str, b: &'a str) -> Option { - let a_chars: Vec = a.chars().collect(); - let b_chars: Vec = b.chars().collect(); - if a_chars[0] != b_chars[0] { - return None; - } - - let mut last_value: String = a_chars[0].to_string(); - let max_common_i = std::cmp::min(a.len(), b.len()) - 1; - for i in 1..=max_common_i { - let a_i = a_chars[i]; - let b_i = b_chars[i]; - if a_i == b_i { - last_value += &a_i.to_string() - } else { - break; - } - } - - Some(last_value) -} - pub fn chars_take(chars: &[char], i: usize) -> String { if i > chars.len() { panic!("chars_take: i is larget than chars.len()"); @@ -436,21 +414,6 @@ mod tests { } } - /// Tests [`common_substring`] - #[test] - fn common_substring_test() { - let values = HashMap::from([ - (("test", "text"), Some("te")), - (("lol", "text"), None), - (("sin(", "sinh("), Some("sin")), - (("aaa", "bbb"), None), - ]); - - for ((key_a, key_b), value) in values { - assert_eq!(common_substring(key_a, key_b), value.map(|e| e.to_owned())); - } - } - #[test] fn chars_take_test() { let values = HashMap::from([(("test", 2), "st"), (("cool text", 4), "text")]); diff --git a/src/suggestions.rs b/src/suggestions.rs index 891a5ff..d3f9686 100644 --- a/src/suggestions.rs +++ b/src/suggestions.rs @@ -1,5 +1,4 @@ -use crate::misc::{chars_take, common_substring}; -use std::collections::{HashMap, HashSet}; +use crate::misc::chars_take; /// Generate a hint based on the input `input`, returns an `Option` pub fn generate_hint(input: &str) -> Option { @@ -54,75 +53,7 @@ pub fn generate_hint(input: &str) -> Option { None } -/// Creates hashmap used for function name completion -fn gen_completion_hashmap(input: Vec) -> HashMap { - let mut tuple_list: Vec<(String, String)> = Vec::new(); - - for entry in input { - for i in 1..entry.len() { - let (first, last) = entry.split_at(i); - tuple_list.push((first.to_string(), last.to_string())); - } - } - - let mut output: HashMap = HashMap::new(); - - let key_list: Vec = tuple_list.iter().map(|(key, _)| key.clone()).collect(); - - let mut seen = HashSet::new(); - for (key, value) in tuple_list.clone() { - if seen.contains(&key) { - continue; - } - - seen.insert(key.clone()); - - let duplicate_num = key_list.iter().filter(|ele| **ele == key).count(); - if 1 == duplicate_num { - output.insert(key, value); - continue; - } - - let same_keys_merged: Vec = tuple_list - .iter() - .filter(|(a, _)| **a == key) - .map(|(a, b)| a.clone() + b) - .collect(); - - let merged_key_value = key.clone() + &value; - - let mut common_substr: Option = None; - for same_key in same_keys_merged { - if let Some(common_substr_unwrapped) = common_substr { - common_substr = common_substring(&common_substr_unwrapped, &same_key); - } else { - common_substr = common_substring(&same_key, &merged_key_value) - } - - if common_substr.is_none() { - break; - } - } - - if let Some(common_substr_unwrapped) = common_substr { - if !common_substr_unwrapped.is_empty() { - output.insert(key.clone(), common_substr_unwrapped.replace(&key, "")); - } - } - } - - output -} - -/// List of supported functions from exmex -const SUPPORTED_FUNCTIONS: [&str; 22] = [ - "abs", "signum", "sin", "cos", "tan", "asin", "acos", "atan", "sinh", "cosh", "tanh", "floor", - "round", "ceil", "trunc", "fract", "exp", "sqrt", "cbrt", "ln", "log2", "log10", -]; - -lazy_static::lazy_static! { - static ref COMPLETION_HASHMAP: HashMap = gen_completion_hashmap(SUPPORTED_FUNCTIONS.to_vec().iter().map(|ele| ele.to_string() + "(").collect()); -} +include!(concat!(env!("OUT_DIR"), "/codegen.rs")); /// Gets completion from `COMPLETION_HASHMAP` pub fn get_completion(key: String) -> Option { @@ -135,7 +66,7 @@ pub fn get_completion(key: String) -> Option { if data_x.is_empty() { None } else { - Some(data_x.clone()) + Some(data_x.to_string()) } } None => None, @@ -144,6 +75,8 @@ pub fn get_completion(key: String) -> Option { #[cfg(test)] mod tests { + use std::collections::HashMap; + use super::*; /// Tests to make sure hints are properly outputed based on input @@ -193,10 +126,14 @@ mod tests { fn hashmap_test_gen() -> HashMap> { let mut values: HashMap> = HashMap::new(); - let processed_func: Vec = SUPPORTED_FUNCTIONS - .iter() - .map(|ele| ele.to_string() + "(") - .collect(); + let processed_func: Vec = [ + "abs", "signum", "sin", "cos", "tan", "asin", "acos", "atan", "sinh", "cosh", "tanh", + "floor", "round", "ceil", "trunc", "fract", "exp", "sqrt", "cbrt", "ln", "log2", + "log10", + ] + .iter() + .map(|ele| ele.to_string() + "(") + .collect(); let mut data_tuple: Vec<(String, Option)> = Vec::new(); for func in processed_func.iter() {