#![allow(clippy::as_conversions)]
use crate::ast::{Expr, Ref};
use crate::builtins;
use crate::builtins::utils::{enforce_limit, ensure_args_count, ensure_numeric, ensure_string};
use crate::lexer::Span;
use crate::value::Value;
use crate::*;
use anyhow::{bail, Result};
use regex::{Regex, RegexBuilder};
const REGEX_SIZE_LIMIT: usize = 100 * 1024;
fn compile_regex(pattern: &str) -> core::result::Result<Regex, regex::Error> {
RegexBuilder::new(pattern)
.size_limit(REGEX_SIZE_LIMIT)
.build()
}
fn get_or_compile_regex(pattern: &str) -> core::result::Result<Regex, regex::Error> {
#[cfg(feature = "cache")]
{
{
let mut cache = crate::cache::REGEX_CACHE.lock();
if let Some(re) = cache.get(pattern) {
return Ok(re.clone());
}
}
let re = compile_regex(pattern)?;
{
let mut cache = crate::cache::REGEX_CACHE.lock();
cache.put(alloc::string::String::from(pattern), re.clone());
Ok(re)
}
}
#[cfg(not(feature = "cache"))]
{
compile_regex(pattern)
}
}
fn compile_regex_for_builtin(span: &Span, pattern: &str) -> Result<Regex> {
get_or_compile_regex(pattern).map_err(|e| match e {
regex::Error::CompiledTooBig(_) => {
anyhow::Error::new(crate::utils::limits::LimitError::RegexSizeLimitExceeded {
limit: REGEX_SIZE_LIMIT,
})
}
_ => anyhow::anyhow!(span.error("invalid regex")),
})
}
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert(
"regex.find_all_string_submatch_n",
(find_all_string_submatch_n, 3),
);
m.insert("regex.find_n", (find_n, 3));
m.insert("regex.is_valid", (is_valid, 1));
m.insert("regex.match", (regex_match, 2));
m.insert("regex.replace", (regex_replace, 3));
m.insert("regex.split", (regex_split, 2));
m.insert("regex.template_match", (regex_template_match, 4));
}
fn find_all_string_submatch_n(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "regex.find_all_string_submatch_n";
ensure_args_count(span, name, params, args, 3)?;
let pattern = ensure_string(name, ¶ms[0], &args[0])?;
let value = ensure_string(name, ¶ms[1], &args[1])?;
let n = ensure_numeric(name, ¶ms[2], &args[2])?;
let re = compile_regex_for_builtin(params[0].span(), &pattern)?;
if !n.is_integer() {
bail!(params[2].span().error("n must be an integer"));
}
let n = match n.as_i64() {
Some(n) if n < 0 => usize::MAX,
Some(n) => n as usize,
None => usize::MAX,
};
Ok(Value::from_array(
re.captures_iter(&value)
.map(|capture| {
let groups = capture
.iter()
.map(|group| {
let value = Value::String(match group {
Some(s) => s.as_str().into(),
_ => "".into(),
});
enforce_limit()?;
Ok(value)
})
.collect::<Result<Vec<Value>>>()?;
let array = Value::from_array(groups);
enforce_limit()?;
Ok(array)
})
.take(n)
.collect::<Result<Vec<Value>>>()?,
))
}
fn find_n(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "regex.find_n";
ensure_args_count(span, name, params, args, 3)?;
let pattern = ensure_string(name, ¶ms[0], &args[0])?;
let value = ensure_string(name, ¶ms[1], &args[1])?;
let n = ensure_numeric(name, ¶ms[2], &args[2])?;
let re = compile_regex_for_builtin(params[0].span(), &pattern)?;
if !n.is_integer() {
bail!(params[2].span().error("n must be an integer"));
}
let n = match n.as_i64() {
Some(n) if n < 0 => usize::MAX,
Some(n) => n as usize,
None => usize::MAX,
};
Ok(Value::from_array(
re.find_iter(&value)
.map(|m| {
let value = Value::String(m.as_str().into());
enforce_limit()?;
Ok(value)
})
.take(n)
.collect::<Result<Vec<Value>>>()?,
))
}
fn is_valid(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "regex.is_valid";
ensure_args_count(span, name, params, args, 1)?;
let pattern = match ensure_string(name, ¶ms[0], &args[0]) {
Ok(p) => p,
Err(_) => return Ok(Value::Bool(false)),
};
match get_or_compile_regex(&pattern) {
Ok(_) => Ok(Value::Bool(true)),
Err(regex::Error::CompiledTooBig(_)) => Err(anyhow::Error::new(
crate::utils::limits::LimitError::RegexSizeLimitExceeded {
limit: REGEX_SIZE_LIMIT,
},
)),
Err(_) => Ok(Value::Bool(false)),
}
}
pub fn regex_match(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "regex.match";
ensure_args_count(span, name, params, args, 2)?;
let pattern = ensure_string(name, ¶ms[0], &args[0])?;
let value = ensure_string(name, ¶ms[1], &args[1])?;
let re = compile_regex_for_builtin(params[0].span(), &pattern)?;
Ok(Value::Bool(re.is_match(&value)))
}
fn regex_replace(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "regex.replace";
ensure_args_count(span, name, params, args, 3)?;
let s = ensure_string(name, ¶ms[0], &args[0])?;
let pattern = ensure_string(name, ¶ms[1], &args[1])?;
let value = ensure_string(name, ¶ms[2], &args[2])?;
let re = match get_or_compile_regex(&pattern) {
Ok(p) => p,
Err(regex::Error::CompiledTooBig(_)) => {
return Err(anyhow::Error::new(
crate::utils::limits::LimitError::RegexSizeLimitExceeded {
limit: REGEX_SIZE_LIMIT,
},
));
}
_ => return Ok(Value::Undefined),
};
Ok(Value::String(re.replace_all(&s, value.as_ref()).into()))
}
fn regex_split(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "regex.split";
ensure_args_count(span, name, params, args, 2)?;
let pattern = ensure_string(name, ¶ms[0], &args[0])?;
let value = ensure_string(name, ¶ms[1], &args[1])?;
let re = compile_regex_for_builtin(params[0].span(), &pattern)?;
Ok(Value::from_array(
re.split(&value)
.map(|s| {
let value = Value::String(s.into());
enforce_limit()?;
Ok(value)
})
.collect::<Result<Vec<Value>>>()?,
))
}
fn regex_template_match(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "regex.template_match";
ensure_args_count(span, name, params, args, 4)?;
let template = ensure_string(name, ¶ms[0], &args[0])?;
let value = ensure_string(name, ¶ms[1], &args[1])?;
let delimiter_start = ensure_string(name, ¶ms[2], &args[2])?;
let delimiter_end = ensure_string(name, ¶ms[3], &args[3])?;
let delimiter_start = delimiter_start.as_ref();
let delimiter_end = delimiter_end.as_ref();
let mut template = template.as_ref();
let mut value = value.as_ref();
while let (Some(start), Some(end)) =
(template.find(delimiter_start), template.find(delimiter_end))
{
if start >= end {
return Ok(Value::Undefined);
}
if template[0..start] != value[0..start] {
return Ok(Value::Bool(false));
}
let re = compile_regex_for_builtin(
params[0].span(),
&template[start + delimiter_start.len()..end],
)?;
value = &value[start..];
let m = match re.find(value) {
Some(m) if m.start() == 0 => m,
_ => return Ok(Value::Bool(false)),
};
value = &value[m.len()..];
template = &template[end + delimiter_end.len()..];
}
Ok(Value::Bool(template == value))
}