use seq_core::seqstring::global_string;
use seq_core::stack::{Stack, pop, push};
use seq_core::value::{Value, VariantData};
use regex::Regex;
use std::sync::Arc;
fn make_list(items: Vec<Value>) -> Value {
Value::Variant(Arc::new(VariantData::new(
global_string("List".to_string()),
items,
)))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_regex_match(stack: Stack) -> Stack {
assert!(!stack.is_null(), "regex.match?: stack is empty");
let (stack, pattern_val) = unsafe { pop(stack) };
let (stack, text_val) = unsafe { pop(stack) };
match (text_val, pattern_val) {
(Value::String(text), Value::String(pattern)) => {
let result = match Regex::new(pattern.as_str()) {
Ok(re) => re.is_match(text.as_str()),
Err(_) => false, };
unsafe { push(stack, Value::Bool(result)) }
}
_ => panic!("regex.match?: expected two Strings on stack"),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_regex_find(stack: Stack) -> Stack {
assert!(!stack.is_null(), "regex.find: stack is empty");
let (stack, pattern_val) = unsafe { pop(stack) };
let (stack, text_val) = unsafe { pop(stack) };
match (text_val, pattern_val) {
(Value::String(text), Value::String(pattern)) => {
match Regex::new(pattern.as_str()) {
Ok(re) => match re.find(text.as_str()) {
Some(m) => {
let stack = unsafe {
push(stack, Value::String(global_string(m.as_str().to_string())))
};
unsafe { push(stack, Value::Bool(true)) }
}
None => {
let stack =
unsafe { push(stack, Value::String(global_string(String::new()))) };
unsafe { push(stack, Value::Bool(false)) }
}
},
Err(_) => {
let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
unsafe { push(stack, Value::Bool(false)) }
}
}
}
_ => panic!("regex.find: expected two Strings on stack"),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_regex_find_all(stack: Stack) -> Stack {
assert!(!stack.is_null(), "regex.find-all: stack is empty");
let (stack, pattern_val) = unsafe { pop(stack) };
let (stack, text_val) = unsafe { pop(stack) };
match (text_val, pattern_val) {
(Value::String(text), Value::String(pattern)) => match Regex::new(pattern.as_str()) {
Ok(re) => {
let matches: Vec<Value> = re
.find_iter(text.as_str())
.map(|m| Value::String(global_string(m.as_str().to_string())))
.collect();
let stack = unsafe { push(stack, make_list(matches)) };
unsafe { push(stack, Value::Bool(true)) }
}
Err(_) => {
let stack = unsafe { push(stack, make_list(vec![])) };
unsafe { push(stack, Value::Bool(false)) }
}
},
_ => panic!("regex.find-all: expected two Strings on stack"),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_regex_replace(stack: Stack) -> Stack {
assert!(!stack.is_null(), "regex.replace: stack is empty");
let (stack, replacement_val) = unsafe { pop(stack) };
let (stack, pattern_val) = unsafe { pop(stack) };
let (stack, text_val) = unsafe { pop(stack) };
match (text_val, pattern_val, replacement_val) {
(Value::String(text), Value::String(pattern), Value::String(replacement)) => {
match Regex::new(pattern.as_str()) {
Ok(re) => {
let result = re.replace(text.as_str(), replacement.as_str()).into_owned();
let stack = unsafe { push(stack, Value::String(global_string(result))) };
unsafe { push(stack, Value::Bool(true)) }
}
Err(_) => {
let stack = unsafe {
push(
stack,
Value::String(global_string(text.as_str().to_string())),
)
};
unsafe { push(stack, Value::Bool(false)) }
}
}
}
_ => panic!("regex.replace: expected three Strings on stack"),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_regex_replace_all(stack: Stack) -> Stack {
assert!(!stack.is_null(), "regex.replace-all: stack is empty");
let (stack, replacement_val) = unsafe { pop(stack) };
let (stack, pattern_val) = unsafe { pop(stack) };
let (stack, text_val) = unsafe { pop(stack) };
match (text_val, pattern_val, replacement_val) {
(Value::String(text), Value::String(pattern), Value::String(replacement)) => {
match Regex::new(pattern.as_str()) {
Ok(re) => {
let result = re
.replace_all(text.as_str(), replacement.as_str())
.into_owned();
let stack = unsafe { push(stack, Value::String(global_string(result))) };
unsafe { push(stack, Value::Bool(true)) }
}
Err(_) => {
let stack = unsafe {
push(
stack,
Value::String(global_string(text.as_str().to_string())),
)
};
unsafe { push(stack, Value::Bool(false)) }
}
}
}
_ => panic!("regex.replace-all: expected three Strings on stack"),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_regex_captures(stack: Stack) -> Stack {
assert!(!stack.is_null(), "regex.captures: stack is empty");
let (stack, pattern_val) = unsafe { pop(stack) };
let (stack, text_val) = unsafe { pop(stack) };
match (text_val, pattern_val) {
(Value::String(text), Value::String(pattern)) => {
match Regex::new(pattern.as_str()) {
Ok(re) => match re.captures(text.as_str()) {
Some(caps) => {
let groups: Vec<Value> = caps
.iter()
.skip(1)
.map(|m| match m {
Some(m) => Value::String(global_string(m.as_str().to_string())),
None => Value::String(global_string(String::new())),
})
.collect();
let stack = unsafe { push(stack, make_list(groups)) };
unsafe { push(stack, Value::Bool(true)) }
}
None => {
let stack = unsafe { push(stack, make_list(vec![])) };
unsafe { push(stack, Value::Bool(false)) }
}
},
Err(_) => {
let stack = unsafe { push(stack, make_list(vec![])) };
unsafe { push(stack, Value::Bool(false)) }
}
}
}
_ => panic!("regex.captures: expected two Strings on stack"),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_regex_split(stack: Stack) -> Stack {
assert!(!stack.is_null(), "regex.split: stack is empty");
let (stack, pattern_val) = unsafe { pop(stack) };
let (stack, text_val) = unsafe { pop(stack) };
match (text_val, pattern_val) {
(Value::String(text), Value::String(pattern)) => match Regex::new(pattern.as_str()) {
Ok(re) => {
let parts: Vec<Value> = re
.split(text.as_str())
.map(|s| Value::String(global_string(s.to_string())))
.collect();
let stack = unsafe { push(stack, make_list(parts)) };
unsafe { push(stack, Value::Bool(true)) }
}
Err(_) => {
let parts = vec![Value::String(global_string(text.as_str().to_string()))];
let stack = unsafe { push(stack, make_list(parts)) };
unsafe { push(stack, Value::Bool(false)) }
}
},
_ => panic!("regex.split: expected two Strings on stack"),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_regex_valid(stack: Stack) -> Stack {
assert!(!stack.is_null(), "regex.valid?: stack is empty");
let (stack, pattern_val) = unsafe { pop(stack) };
match pattern_val {
Value::String(pattern) => {
let is_valid = Regex::new(pattern.as_str()).is_ok();
unsafe { push(stack, Value::Bool(is_valid)) }
}
_ => panic!("regex.valid?: expected String on stack"),
}
}
#[cfg(test)]
mod tests {
use super::*;
use seq_core::stack::alloc_stack;
#[test]
fn test_regex_match() {
let stack = alloc_stack();
let stack = unsafe {
push(
stack,
Value::String(global_string("hello world".to_string())),
)
};
let stack = unsafe { push(stack, Value::String(global_string("wo.ld".to_string()))) };
let stack = unsafe { patch_seq_regex_match(stack) };
let (_, value) = unsafe { pop(stack) };
assert_eq!(value, Value::Bool(true));
}
#[test]
fn test_regex_match_no_match() {
let stack = alloc_stack();
let stack = unsafe { push(stack, Value::String(global_string("hello".to_string()))) };
let stack = unsafe { push(stack, Value::String(global_string("xyz".to_string()))) };
let stack = unsafe { patch_seq_regex_match(stack) };
let (_, value) = unsafe { pop(stack) };
assert_eq!(value, Value::Bool(false));
}
#[test]
fn test_regex_find() {
let stack = alloc_stack();
let stack = unsafe { push(stack, Value::String(global_string("a1 b2 c3".to_string()))) };
let stack = unsafe {
push(
stack,
Value::String(global_string("[a-z][0-9]".to_string())),
)
};
let stack = unsafe { patch_seq_regex_find(stack) };
let (stack, success) = unsafe { pop(stack) };
let (_, matched) = unsafe { pop(stack) };
assert_eq!(success, Value::Bool(true));
if let Value::String(s) = matched {
assert_eq!(s.as_str(), "a1");
} else {
panic!("expected String");
}
}
#[test]
fn test_regex_find_all() {
let stack = alloc_stack();
let stack = unsafe { push(stack, Value::String(global_string("a1 b2 c3".to_string()))) };
let stack = unsafe {
push(
stack,
Value::String(global_string("[a-z][0-9]".to_string())),
)
};
let stack = unsafe { patch_seq_regex_find_all(stack) };
let (stack, success) = unsafe { pop(stack) };
assert_eq!(success, Value::Bool(true));
let (_, list_val) = unsafe { pop(stack) };
if let Value::Variant(v) = list_val {
assert_eq!(v.fields.len(), 3);
if let Value::String(s) = &v.fields[0] {
assert_eq!(s.as_str(), "a1");
}
if let Value::String(s) = &v.fields[1] {
assert_eq!(s.as_str(), "b2");
}
if let Value::String(s) = &v.fields[2] {
assert_eq!(s.as_str(), "c3");
}
} else {
panic!("expected Variant (List)");
}
}
#[test]
fn test_regex_replace() {
let stack = alloc_stack();
let stack = unsafe {
push(
stack,
Value::String(global_string("hello world".to_string())),
)
};
let stack = unsafe { push(stack, Value::String(global_string("world".to_string()))) };
let stack = unsafe { push(stack, Value::String(global_string("Seq".to_string()))) };
let stack = unsafe { patch_seq_regex_replace(stack) };
let (stack, success) = unsafe { pop(stack) };
assert_eq!(success, Value::Bool(true));
let (_, result) = unsafe { pop(stack) };
if let Value::String(s) = result {
assert_eq!(s.as_str(), "hello Seq");
} else {
panic!("expected String");
}
}
#[test]
fn test_regex_replace_all() {
let stack = alloc_stack();
let stack = unsafe { push(stack, Value::String(global_string("a1 b2 c3".to_string()))) };
let stack = unsafe { push(stack, Value::String(global_string("[0-9]".to_string()))) };
let stack = unsafe { push(stack, Value::String(global_string("X".to_string()))) };
let stack = unsafe { patch_seq_regex_replace_all(stack) };
let (stack, success) = unsafe { pop(stack) };
assert_eq!(success, Value::Bool(true));
let (_, result) = unsafe { pop(stack) };
if let Value::String(s) = result {
assert_eq!(s.as_str(), "aX bX cX");
} else {
panic!("expected String");
}
}
#[test]
fn test_regex_captures() {
let stack = alloc_stack();
let stack = unsafe {
push(
stack,
Value::String(global_string("2024-01-15".to_string())),
)
};
let stack = unsafe {
push(
stack,
Value::String(global_string(r"(\d+)-(\d+)-(\d+)".to_string())),
)
};
let stack = unsafe { patch_seq_regex_captures(stack) };
let (stack, success) = unsafe { pop(stack) };
let (_, groups) = unsafe { pop(stack) };
assert_eq!(success, Value::Bool(true));
if let Value::Variant(v) = groups {
assert_eq!(v.fields.len(), 3);
if let Value::String(s) = &v.fields[0] {
assert_eq!(s.as_str(), "2024");
}
if let Value::String(s) = &v.fields[1] {
assert_eq!(s.as_str(), "01");
}
if let Value::String(s) = &v.fields[2] {
assert_eq!(s.as_str(), "15");
}
} else {
panic!("expected Variant (List)");
}
}
#[test]
fn test_regex_split() {
let stack = alloc_stack();
let stack = unsafe { push(stack, Value::String(global_string("a1b2c3".to_string()))) };
let stack = unsafe { push(stack, Value::String(global_string("[0-9]".to_string()))) };
let stack = unsafe { patch_seq_regex_split(stack) };
let (stack, success) = unsafe { pop(stack) };
assert_eq!(success, Value::Bool(true));
let (_, result) = unsafe { pop(stack) };
if let Value::Variant(v) = result {
assert_eq!(v.fields.len(), 4); if let Value::String(s) = &v.fields[0] {
assert_eq!(s.as_str(), "a");
}
if let Value::String(s) = &v.fields[1] {
assert_eq!(s.as_str(), "b");
}
if let Value::String(s) = &v.fields[2] {
assert_eq!(s.as_str(), "c");
}
} else {
panic!("expected Variant (List)");
}
}
#[test]
fn test_regex_valid() {
let stack = alloc_stack();
let stack = unsafe { push(stack, Value::String(global_string("[a-z]+".to_string()))) };
let stack = unsafe { patch_seq_regex_valid(stack) };
let (_, result) = unsafe { pop(stack) };
assert_eq!(result, Value::Bool(true));
let stack = alloc_stack();
let stack = unsafe { push(stack, Value::String(global_string("[invalid".to_string()))) };
let stack = unsafe { patch_seq_regex_valid(stack) };
let (_, result) = unsafe { pop(stack) };
assert_eq!(result, Value::Bool(false));
}
#[test]
fn test_invalid_regex_graceful() {
let stack = alloc_stack();
let stack = unsafe { push(stack, Value::String(global_string("test".to_string()))) };
let stack = unsafe { push(stack, Value::String(global_string("[invalid".to_string()))) };
let stack = unsafe { patch_seq_regex_match(stack) };
let (_, result) = unsafe { pop(stack) };
assert_eq!(result, Value::Bool(false));
}
}