use regex::escape;
use crate::{Environment, FALSE_SYMBOL, TRUE_SYMBOL, eval::apply::eval_apply, value::Value};
thread_local! {
static REGEX_CACHE: std::cell::RefCell<rustc_hash::FxHashMap<std::sync::Arc<str>, regex::Regex>>
= std::cell::RefCell::new(rustc_hash::FxHashMap::default());
}
pub(crate) fn try_regex_cache(pattern: &std::sync::Arc<str>) -> Result<regex::Regex, std::sync::Arc<str>> {
REGEX_CACHE.with(|cache| {
let mut cache = cache.borrow_mut();
if let Some(re) = cache.get(pattern) {
return Ok(re.clone());
}
match regex::RegexBuilder::new(pattern).build() {
Ok(re) => {
cache.insert(pattern.clone(), re.clone());
Ok(re)
}
Err(e) => Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::regex::try_regex_cache]: ",
"Invalid regex rules `{}` with `{}`.",
),
pattern, e
))),
}
})
}
pub(crate) fn re_match(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
if let [r, s] = args {
match (eval_apply(r, env.clone())?, eval_apply(s, env)?) {
(Value::String(pattern), Value::String(value)) => {
let pat = try_regex_cache(&pattern)?;
Ok(if pat.is_match(value.as_ref()) {
TRUE_SYMBOL.clone()
} else {
FALSE_SYMBOL.clone()
})
}
(Value::String(_), e) | (e, _) => Err(std::sync::Arc::from(format!(
concat!("Error[ksl::builtin::ReMatch]: ", "Unexpected value: `{}`."),
e
))),
}
} else {
Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::ReMatch]: ",
"Expected 2 parameters, but {} were passed."
),
args.len()
)))
}
}
pub(crate) fn re_capture(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
if let [r, s, atms] = args {
match (
eval_apply(r, env.clone())?,
eval_apply(s, env.clone())?,
eval_apply(atms, env)?,
) {
(Value::String(pattern), Value::String(value), Value::List(atoms)) => {
let pat = try_regex_cache(&pattern)?;
match pat.captures(value.as_ref()) {
Some(caps) => Ok(Value::List(
atoms
.iter()
.map(|atom| match atom {
Value::Atom(a) => Ok(match caps.name(a.as_ref()) {
Some(val) => Value::String(std::sync::Arc::from(val.as_str())),
None => Value::Unit,
}),
e => Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::ReCapture]: ",
"Unexpected value: `{}`."
),
e
))),
})
.collect::<Result<std::sync::Arc<[Value]>, std::sync::Arc<str>>>()?,
)),
None => Err(std::sync::Arc::from(concat!(
"Error[ksl::builtin::ReCapture]: ",
"Failed to capture values."
))),
}
}
(Value::String(_), Value::String(_), e) | (Value::String(_), e, _) | (e, _, _) => {
Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::ReCapture]: ",
"Unexpected value: `{}`."
),
e
)))
}
}
} else {
Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::ReCapture]: ",
"Expected 3 parameters, but {} were passed."
),
args.len()
)))
}
}
pub(crate) fn fstr(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
if let [pattern, obj] = args {
match (eval_apply(pattern, env.clone())?, eval_apply(obj, env)?) {
(Value::String(pats), Value::Object(tag, valt)) if tag.as_ref() == "Args" => {
let key_re = try_regex_cache(&std::sync::Arc::from(format!(
"@({})",
valt.keys()
.map(|k| escape(k.as_ref()))
.collect::<Vec<String>>()
.join("|")
)))?;
Ok(Value::String(std::sync::Arc::from(key_re.replace_all(
pats.as_ref(),
|cap: ®ex::Captures| {
let name = &cap[1];
match valt.get(name) {
Some(v) => match v.as_ref() {
Value::String(s) => s.as_ref().to_owned(),
v => v.to_string(),
},
None => name.to_string(),
}
},
))))
}
(Value::String(_), e) => Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::Fstr]: ",
"Expected a `Pattern` object as arguments, but got: `{}`."
),
e
))),
(e, _) => Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::Fstr]: ",
"Expected a string as pattern, but got: `{}`."
),
e
))),
}
} else {
Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::Fstr]: ",
"Expected 2 parameter, but {} were passed."
),
args.len()
)))
}
}
pub(crate) fn chn(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
if let [e] = args {
match eval_apply(e, env)? {
Value::String(s) => {
if s.chars().count() == 1
&& let Some(ch) = s.chars().next()
{
Ok(Value::Number(u32::from(ch) as f64))
} else {
Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::Chn]: ",
"Invalid single character: `{}`."
),
s
)))
}
}
Value::Number(n) => {
let code = n.trunc() as u32;
if let Some(ch) = char::from_u32(code) {
Ok(Value::String(std::sync::Arc::from(String::from(ch))))
} else {
Err(std::sync::Arc::from(format!(
concat!("Error[ksl::builtin::Chn]: ", "Invalid unicode: `{}`."),
n
)))
}
}
e => Err(std::sync::Arc::from(format!(
concat!("Error[ksl::builtin::Chn]: ", "Unexpected value: `{}`."),
e
))),
}
} else {
Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::Chn]: ",
"Expected 1 parameter, but {} were passed."
),
args.len()
)))
}
}