use crate::executor::expression::{ExpressionError, ExpressionErrorType};
use linkrs_core::value::NullType;
use linkrs_core::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RegexFunction {
RegexMatch,
RegexReplace,
RegexFind,
RegexpFullMatch,
RegexpExtract,
RegexpExtractAll,
RegexpSplitToArray,
}
impl RegexFunction {
pub fn name(&self) -> &str {
match self {
RegexFunction::RegexMatch => "regex_match",
RegexFunction::RegexReplace => "regex_replace",
RegexFunction::RegexFind => "regex_find",
RegexFunction::RegexpFullMatch => "regexp_full_match",
RegexFunction::RegexpExtract => "regexp_extract",
RegexFunction::RegexpExtractAll => "regexp_extract_all",
RegexFunction::RegexpSplitToArray => "regexp_split_to_array",
}
}
pub fn arity(&self) -> usize {
match self {
RegexFunction::RegexMatch => 2,
RegexFunction::RegexReplace => 3,
RegexFunction::RegexFind => 2,
RegexFunction::RegexpFullMatch => 2,
RegexFunction::RegexpExtract => 2,
RegexFunction::RegexpExtractAll => 2,
RegexFunction::RegexpSplitToArray => 2,
}
}
pub fn is_variadic(&self) -> bool {
false
}
pub fn description(&self) -> &str {
match self {
RegexFunction::RegexMatch => "regular expression matching (math.)",
RegexFunction::RegexReplace => "regular expression substitution",
RegexFunction::RegexFind => "regular expression lookup (computing)",
RegexFunction::RegexpFullMatch => "Check if string fully matches regex pattern",
RegexFunction::RegexpExtract => "Extract first match of regex pattern",
RegexFunction::RegexpExtractAll => "Extract all matches of regex pattern",
RegexFunction::RegexpSplitToArray => "Split string by regex pattern into array",
}
}
pub fn execute(&self, args: &[Value]) -> Result<Value, ExpressionError> {
if !self.is_variadic() && args.len() != self.arity() {
return Err(ExpressionError::invalid_arity(
self.name(),
self.arity(),
args.len(),
));
}
match self {
RegexFunction::RegexMatch => match (&args[0], &args[1]) {
(Value::String(s), Value::String(pattern)) => {
let regex = regex::Regex::new(pattern).map_err(|_| {
ExpressionError::new(
ExpressionErrorType::InvalidOperation,
format!("Invalid regular expression: {}", pattern),
)
})?;
Ok(Value::Bool(regex.is_match(s)))
}
(Value::Null(_), _) | (_, Value::Null(_)) => Ok(Value::Null(NullType::Null)),
_ => Err(ExpressionError::type_error(
"The regex_match function requires the string type",
)),
},
RegexFunction::RegexReplace => match (&args[0], &args[1], &args[2]) {
(Value::String(s), Value::String(pattern), Value::String(replacement)) => {
let regex = regex::Regex::new(pattern).map_err(|_| {
ExpressionError::new(
ExpressionErrorType::InvalidOperation,
format!("Invalid regular expression: {}", pattern),
)
})?;
Ok(Value::string(regex.replace_all(s, replacement.as_str())))
}
(Value::Null(_), _, _) | (_, Value::Null(_), _) | (_, _, Value::Null(_)) => {
Ok(Value::Null(NullType::Null))
}
_ => Err(ExpressionError::type_error(
"The regex_replace function requires the string type",
)),
},
RegexFunction::RegexFind => match (&args[0], &args[1]) {
(Value::String(s), Value::String(pattern)) => {
let regex = regex::Regex::new(pattern).map_err(|_| {
ExpressionError::new(
ExpressionErrorType::InvalidOperation,
format!("Invalid regular expression: {}", pattern),
)
})?;
if let Some(matched) = regex.find(s) {
Ok(Value::string(matched.as_str()))
} else {
Ok(Value::Null(NullType::Null))
}
}
(Value::Null(_), _) | (_, Value::Null(_)) => Ok(Value::Null(NullType::Null)),
_ => Err(ExpressionError::type_error(
"The regex_find function requires the string type",
)),
},
RegexFunction::RegexpFullMatch => match (&args[0], &args[1]) {
(Value::String(s), Value::String(pattern)) => {
let regex = regex::Regex::new(pattern).map_err(|_| {
ExpressionError::new(
ExpressionErrorType::InvalidOperation,
format!("Invalid regular expression: {}", pattern),
)
})?;
Ok(Value::Bool(
regex.is_match(s) && regex.find(s).is_some_and(|m| m.as_str() == s),
))
}
(Value::Null(_), _) | (_, Value::Null(_)) => Ok(Value::Null(NullType::Null)),
_ => Err(ExpressionError::type_error(
"regexp_full_match requires string arguments",
)),
},
RegexFunction::RegexpExtract => match (&args[0], &args[1]) {
(Value::String(s), Value::String(pattern)) => {
let regex = regex::Regex::new(pattern).map_err(|_| {
ExpressionError::new(
ExpressionErrorType::InvalidOperation,
format!("Invalid regular expression: {}", pattern),
)
})?;
if let Some(matched) = regex.find(s) {
Ok(Value::string(matched.as_str()))
} else {
Ok(Value::Null(NullType::Null))
}
}
(Value::Null(_), _) | (_, Value::Null(_)) => Ok(Value::Null(NullType::Null)),
_ => Err(ExpressionError::type_error(
"regexp_extract requires string arguments",
)),
},
RegexFunction::RegexpExtractAll => match (&args[0], &args[1]) {
(Value::String(s), Value::String(pattern)) => {
let regex = regex::Regex::new(pattern).map_err(|_| {
ExpressionError::new(
ExpressionErrorType::InvalidOperation,
format!("Invalid regular expression: {}", pattern),
)
})?;
let matches: Vec<Value> = regex
.find_iter(s)
.map(|m| Value::string(m.as_str()))
.collect();
Ok(Value::list(linkrs_core::value::list::List {
values: matches,
}))
}
(Value::Null(_), _) | (_, Value::Null(_)) => Ok(Value::Null(NullType::Null)),
_ => Err(ExpressionError::type_error(
"regexp_extract_all requires string arguments",
)),
},
RegexFunction::RegexpSplitToArray => match (&args[0], &args[1]) {
(Value::String(s), Value::String(pattern)) => {
let regex = regex::Regex::new(pattern).map_err(|_| {
ExpressionError::new(
ExpressionErrorType::InvalidOperation,
format!("Invalid regular expression: {}", pattern),
)
})?;
let parts: Vec<Value> = regex.split(s).map(Value::string).collect();
Ok(Value::list(linkrs_core::value::list::List {
values: parts,
}))
}
(Value::Null(_), _) | (_, Value::Null(_)) => Ok(Value::Null(NullType::Null)),
_ => Err(ExpressionError::type_error(
"regexp_split_to_array requires string arguments",
)),
},
}
}
pub fn execute_with_cache(
&self,
args: &[Value],
_cache: &mut (),
) -> Result<Value, ExpressionError> {
self.execute(args)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_regexp_full_match() {
let func = RegexFunction::RegexpFullMatch;
let result = func
.execute(&[Value::string("hello123"), Value::string(r"^[a-z]+\d+$")])
.expect("Execution should succeed");
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_regexp_full_match_no_match() {
let func = RegexFunction::RegexpFullMatch;
let result = func
.execute(&[Value::string("hello world"), Value::string(r"^\d+$")])
.expect("Execution should succeed");
assert_eq!(result, Value::Bool(false));
}
#[test]
fn test_regexp_extract() {
let func = RegexFunction::RegexpExtract;
let result = func
.execute(&[Value::string("abc123def"), Value::string(r"\d+")])
.expect("Execution should succeed");
assert_eq!(result, Value::string("123"));
}
#[test]
fn test_regexp_extract_no_match() {
let func = RegexFunction::RegexpExtract;
let result = func
.execute(&[Value::string("abc"), Value::string(r"\d+")])
.expect("Execution should succeed");
assert_eq!(result, Value::Null(NullType::Null));
}
#[test]
fn test_regexp_extract_all() {
let func = RegexFunction::RegexpExtractAll;
let result = func
.execute(&[Value::string("abc123def456"), Value::string(r"\d+")])
.expect("Execution should succeed");
assert_eq!(
result,
Value::list(linkrs_core::value::list::List {
values: vec![Value::string("123"), Value::string("456")]
})
);
}
#[test]
fn test_regexp_split_to_array() {
let func = RegexFunction::RegexpSplitToArray;
let result = func
.execute(&[Value::string("abc123def456"), Value::string(r"\d+")])
.expect("Execution should succeed");
assert_eq!(
result,
Value::list(linkrs_core::value::list::List {
values: vec![
Value::string("abc"),
Value::string("def"),
Value::string("")
]
})
);
}
}