diff --git a/Cargo.toml b/Cargo.toml index a2d3553..c75f7e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ debug = true [dependencies] translit = {version = "0.5.0", optional = true } regex = {version = "1.10.4", optional = true } +regex-cache = {version = "0.2.1", optional = true } [features] default = [ @@ -23,5 +24,5 @@ default = [ ] transliteration = ["translit"] -regexp = ["regex"] +regexp = ["regex", "regex-cache"] diff --git a/dmsrc/regexp.dm b/dmsrc/regexp.dm new file mode 100644 index 0000000..18e69f3 --- /dev/null +++ b/dmsrc/regexp.dm @@ -0,0 +1 @@ +#define rustutils_regex_replace(text, re, re_params, replacement) CALL_LIB(RUST_UTILS, "regex_replace")(text, re, re_params, replacement) diff --git a/src/regexp.rs b/src/regexp.rs index 9c0cec2..0457808 100644 --- a/src/regexp.rs +++ b/src/regexp.rs @@ -1,4 +1,37 @@ -use regex::Regex; +use regex_cache::RegexCache; +use std::sync::Mutex; + +const CACHE_SIZE: usize = 128; + +fn init_regex_cache() -> RegexCache { + RegexCache::new(CACHE_SIZE) +} + +static RE_CACHE: Mutex> = Mutex::new(None); + + +fn compile_regex(pattern: &str) -> regex::Regex { + let mut cache = RE_CACHE.lock().unwrap(); + if let Some(ref mut cache) = *cache { + cache.compile(pattern).unwrap().clone() + } else { + *cache = Some(init_regex_cache()); + init_regex_cache().compile(pattern).unwrap().clone() + } +} + +byond_fn!(fn regex_replace (text, re, re_params, replacement) { + Some(regexp_replace(text, re, re_params, replacement)) +}); + +fn regexp_replace(text: &str, re: &str, re_params: &str, replacement: &str) -> String { + let pattern = format!(r"(?{}){}", re_params, re); + let re = compile_regex(pattern.as_str()); + + re.replace_all(text, replacement).to_string() +} + + #[cfg(test)] mod tests { @@ -6,14 +39,18 @@ mod tests { #[test] fn regexp_test() { - let pattern = r"(?i)\bго+л\b"; + let pattern = r"\bго+л\b"; - let re = Regex::new(pattern).unwrap(); + let pattern_flags = "i"; let input = "Сука Гооооооооооооооол в гооооландские ворота."; - let output = re.replace_all(input, "поподание мячем"); + let expected_output = "Сука попадание мячем в гооооландские ворота."; + + let replacement = "попадание мячем"; - assert_eq!(output, "Сука поподание мячем в гооооландские ворота."); + for _ in 1..2 { + assert_eq!(expected_output, regexp_replace(input, pattern, pattern_flags, replacement)); + } } } diff --git a/tests/dm-tests.rs b/tests/dm-tests.rs index adb89f4..a383d88 100644 --- a/tests/dm-tests.rs +++ b/tests/dm-tests.rs @@ -1,10 +1,17 @@ use std::process::{Command, Output}; +#[cfg(feature = "transliteration")] #[test] fn transliteration() { run_dm_tests("transliteration"); } +#[cfg(feature = "regexp")] +#[test] +fn regexp() { + run_dm_tests("regexp"); +} + fn run_dm_tests(name: &str) { std::env::remove_var("RUST_BACKTRACE"); diff --git a/tests/dm/regexp.dme b/tests/dm/regexp.dme new file mode 100644 index 0000000..435d371 --- /dev/null +++ b/tests/dm/regexp.dme @@ -0,0 +1,19 @@ +#include "common.dm" + +var/input = "Сука Гооооооооооооооол в гооооландские ворота." +var/pattern = @"\bго+л\b" +var/pattern_flags = "i" +var/replacement = "попадание мячем" + +var/expected_output = "Сука попадание мячем в гооооландские ворота." + +/test/proc/test_regexp_replace() + var/output = rustutils_regex_replace(input, pattern, pattern_flags, replacement) + ASSERT(output == expected_output) + +/test/proc/test_regex_time() + var/start = world.timeofday + for(var/i in 1 to 1000) + var/output = rustutils_regex_replace(input, pattern, pattern_flags, replacement) + var/end = world.timeofday + ASSERT(end - start < 10)