use std::rc::Rc;
use crate::error::{Error, Result};
use crate::eval::Evaluator;
use crate::value::{Function, Map, Value};
pub(crate) struct Regex {
pub re: regress::Regex,
pub pattern: String,
}
impl Regex {
pub fn new(pattern: &str, flags: &str) -> Result<Regex> {
let re = regress::Regex::with_flags(pattern, flags).map_err(|e| {
Error::new("S0302")
.with("value", pattern)
.with("message", e.to_string())
})?;
Ok(Regex {
re,
pattern: pattern.to_owned(),
})
}
}
fn utf16_offset(text: &str, byte: usize) -> f64 {
text[..byte].encode_utf16().count() as f64
}
pub(crate) fn byte_offset(text: &str, utf16: usize) -> usize {
let mut units = 0;
for (i, c) in text.char_indices() {
if units >= utf16 {
return i;
}
units += c.len_utf16();
}
text.len()
}
pub(crate) fn exec(ev: &Evaluator, re: &Rc<Regex>, text: &Rc<str>, from: usize) -> Result<Value> {
if from > text.len() {
return Ok(Value::Undefined);
}
let Some(m) = ev.find_regex(&re.re, text, from)? else {
return Ok(Value::Undefined);
};
let range = m.range();
let groups: Vec<Value> = m
.captures
.iter()
.map(|g| match g {
Some(r) => Value::string(&text[r.clone()]),
None => Value::Undefined,
})
.collect();
let mut obj = Map::default();
obj.insert("match".into(), Value::string(&text[range.clone()]));
obj.insert(
"start".into(),
Value::Number(utf16_offset(text, range.start)),
);
obj.insert("end".into(), Value::Number(utf16_offset(text, range.end)));
obj.insert("groups".into(), Value::array(groups, 0));
obj.insert(
"next".into(),
Value::function(Function::RegexNext {
re: re.clone(),
text: text.clone(),
last: range.end,
}),
);
Ok(Value::object(obj))
}
pub(crate) fn next(ev: &Evaluator, re: &Rc<Regex>, text: &Rc<str>, last: usize) -> Result<Value> {
if last >= text.len() {
return Ok(Value::Undefined);
}
let result = exec(ev, re, text, last)?;
if let Value::Object(o) = &result
&& o.borrow().get("match").and_then(Value::as_str) == Some("")
{
return Err(Error::new("D1004").with("value", re.pattern.as_str()));
}
Ok(result)
}