From 04efebb7ca75d096962fcc28ef71ae532f6c5f4b Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Fri, 25 Mar 2022 15:58:15 -0400 Subject: [PATCH] Add optional regex flags to matches filter --- Cargo.toml | 2 +- .../vocabulary/filters/field_match_filter.rs | 75 +++++++++++++++++-- mps-interpreter/tests/single_line.rs | 6 ++ 3 files changed, 76 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index acfe213..3fb944c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ mps-player = { version = "0.7.0", path = "./mps-player", default-features = fals mps-player = { version = "0.7.0", path = "./mps-player", features = ["mpris-player"] } [profile.release] -debug = true +debug = false strip = true lto = true codegen-units = 4 diff --git a/mps-interpreter/src/lang/vocabulary/filters/field_match_filter.rs b/mps-interpreter/src/lang/vocabulary/filters/field_match_filter.rs index ff32faa..3eaa936 100644 --- a/mps-interpreter/src/lang/vocabulary/filters/field_match_filter.rs +++ b/mps-interpreter/src/lang/vocabulary/filters/field_match_filter.rs @@ -1,7 +1,7 @@ use std::collections::VecDeque; use std::fmt::{Debug, Display, Error, Formatter}; -use regex::Regex; +use regex::{Regex, RegexBuilder}; use super::field_filter::{FieldFilterErrorHandling, VariableOrValue}; use crate::lang::utility::{assert_name, assert_token, assert_token_raw, check_name}; @@ -20,6 +20,7 @@ pub struct FieldRegexFilter { field_errors: FieldFilterErrorHandling, val: VariableOrValue, regex_cache: Option<(String, Regex)>, + regex_options: u8, } impl Display for FieldRegexFilter { @@ -46,12 +47,21 @@ impl MpsFilterPredicate for FieldRegexFilter { // non-string values will be stopped at parse-time, so this should never occur _ => Err(RuntimeMsg("Value is not type String".to_string())), }?; - let pattern = if let Some((_, regex_c)) = &self.regex_cache { - regex_c + let pattern = if let Some((val, regex_c)) = &self.regex_cache { + if val == variable { + regex_c + } else { + // only rebuild regex when variable's value changes + let regex_c = build_regex(variable, self.regex_options) + .map_err(|e| RuntimeMsg(format!("Regex compile error: {}", e)))?; + self.regex_cache = Some((variable.to_owned(), regex_c)); + &self.regex_cache.as_ref().unwrap().1 + } } else { - let regex_c = Regex::new(variable) + // build empty cache + let regex_c = build_regex(variable, self.regex_options) .map_err(|e| RuntimeMsg(format!("Regex compile error: {}", e)))?; - self.regex_cache = Some((variable.clone(), regex_c)); + self.regex_cache = Some((variable.to_owned(), regex_c)); &self.regex_cache.as_ref().unwrap().1 }; if let Some(field) = music_item_lut.field(&self.field_name) { @@ -74,6 +84,7 @@ impl MpsFilterPredicate for FieldRegexFilter { } fn reset(&mut self) -> Result<(), RuntimeMsg> { + //self.regex_cache = None; Ok(()) } } @@ -124,7 +135,8 @@ impl MpsFilterFactory for FieldRegexFilterFactory { MpsToken::Literal("regex_string".into()), tokens, )?; - let regex_c = Regex::new(&literal).map_err(|_| SyntaxError { + let re_flags = regex_flags(tokens)?; + let regex_c = build_regex(&literal, re_flags).map_err(|_| SyntaxError { line: 0, token: MpsToken::Literal("[valid regex]".to_string()), got: Some(MpsToken::Literal(literal.clone())), @@ -137,6 +149,7 @@ impl MpsFilterFactory for FieldRegexFilterFactory { field_errors: error_handling, val: value, regex_cache: Some(compiled_cache), + regex_options: re_flags, }) } else { let variable = VariableOrValue::Variable(assert_token( @@ -153,11 +166,61 @@ impl MpsFilterFactory for FieldRegexFilterFactory { field_errors: FieldFilterErrorHandling::Error, val: variable, regex_cache: None, + regex_options: regex_flags(tokens)?, }) } } } +#[inline] +fn regex_flags(tokens: &mut VecDeque) -> Result { + // syntax: , "flags" + let mut result = 0_u8; + if tokens.is_empty() { + Ok(result) + } else { + assert_token_raw(MpsToken::Comma, tokens)?; + let flags = assert_token( + |t| match t { + MpsToken::Literal(s) => Some(s), + _ => None, + }, + MpsToken::Literal("[one or more of imsUux]".into()), + tokens, + )?; + // build flag byte + for c in flags.chars() { + match c { + 'i' => result |= 1 << 0, + 'm' => result |= 1 << 1, + 's' => result |= 1 << 2, + 'U' => result |= 1 << 3, + 'u' => result |= 1 << 4, + 'x' => result |= 1 << 5, + c => return Err(SyntaxError{ + line: 0, + token: MpsToken::Literal("[one or more of imsUux]".to_string()), + got: Some(MpsToken::Literal(format!("{}", c))), + }), + } + } + Ok(result) + } +} + +#[inline] +fn build_regex(pattern: &str, flags: u8) -> Result { + println!("Compiling"); + RegexBuilder::new(pattern) + .case_insensitive((flags & (1 << 0)) != 0) + .multi_line((flags & (1 << 1)) != 0) + .dot_matches_new_line((flags & (1 << 2)) != 0) + .swap_greed((flags & (1 << 3)) != 0) + .unicode((flags & (1 << 4)) != 0) + .ignore_whitespace((flags & (1 << 5)) != 0) + .build() +} + pub type FieldRegexFilterStatementFactory = MpsFilterStatementFactory; diff --git a/mps-interpreter/tests/single_line.rs b/mps-interpreter/tests/single_line.rs index 45f7529..73023c0 100644 --- a/mps-interpreter/tests/single_line.rs +++ b/mps-interpreter/tests/single_line.rs @@ -444,6 +444,12 @@ fn execute_regexfilter_line() -> Result<(), MpsError> { "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).(artist? matches `Bruno Mars`)", false, true, + )?; + // regex options + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).(artist? matches `bruno mars`, `i`)", + false, + true, ) }