use regex::Regex;
use serde_json::{Value, json};
use super::helpers::to_string_cow;
use super::variable;
use crate::constants::INVALID_ARGS;
use crate::node::{MetadataHint, ReduceHint};
use crate::{CompiledNode, ContextStack, DataLogic, Result, error::Error};
#[inline]
pub fn evaluate_cat(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
let mut result = String::with_capacity(args.len() * 16);
for arg in args {
let value = engine.evaluate_node_cow(arg, context)?;
if let Value::Array(arr) = value.as_ref() {
for item in arr {
result.push_str(&to_string_cow(item));
}
} else {
result.push_str(&to_string_cow(value.as_ref()));
}
}
Ok(Value::String(result))
}
#[inline]
pub fn evaluate_substr(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.is_empty() {
return Ok(Value::String(String::new()));
}
let string_val = engine.evaluate_node(&args[0], context)?;
let string: std::borrow::Cow<str> = match &string_val {
Value::String(s) => std::borrow::Cow::Borrowed(s.as_str()),
_ => std::borrow::Cow::Owned(string_val.to_string()),
};
let char_count = string.chars().count();
let start = if args.len() > 1 {
if let CompiledNode::Value { value } = &args[1] {
value.as_i64().unwrap_or(0)
} else {
let start_val = engine.evaluate_node(&args[1], context)?;
start_val.as_i64().unwrap_or(0)
}
} else {
0
};
let length = if args.len() > 2 {
if let CompiledNode::Value { value } = &args[2] {
value.as_i64()
} else {
let length_val = engine.evaluate_node(&args[2], context)?;
length_val.as_i64()
}
} else {
None
};
let actual_start = if start < 0 {
let abs_start = start.saturating_abs() as usize;
char_count.saturating_sub(abs_start)
} else {
(start as usize).min(char_count)
};
let result = if let Some(len) = length {
if len < 0 {
let end_pos = if len < 0 {
let abs_end = len.saturating_abs() as usize;
char_count.saturating_sub(abs_end)
} else {
0
};
if end_pos > actual_start {
string
.chars()
.skip(actual_start)
.take(end_pos - actual_start)
.collect()
} else {
String::new()
}
} else if len == 0 {
String::new()
} else {
let take_count = (len as usize).min(char_count.saturating_sub(actual_start));
string.chars().skip(actual_start).take(take_count).collect()
}
} else {
string.chars().skip(actual_start).collect()
};
Ok(Value::String(result))
}
#[inline]
pub fn evaluate_in(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.len() < 2 {
return Ok(Value::Bool(false));
}
let needle = engine.evaluate_node_cow(&args[0], context)?;
let haystack = engine.evaluate_node_cow(&args[1], context)?;
let result = match haystack.as_ref() {
Value::String(s) => match needle.as_ref() {
Value::String(n) => s.contains(n.as_str()),
_ => false,
},
Value::Array(arr) => arr.iter().any(|v| v == needle.as_ref()),
_ => false,
};
Ok(Value::Bool(result))
}
#[inline]
pub fn evaluate_length(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.is_empty() || args.len() > 1 {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
if let CompiledNode::CompiledVar {
scope_level: 0,
segments,
reduce_hint: ReduceHint::None,
metadata_hint: MetadataHint::None,
..
} = &args[0]
{
if let Some(val) = variable::try_traverse_segments(context.current().data(), segments) {
return match val {
Value::String(s) => Ok(Value::Number(serde_json::Number::from(
s.chars().count() as i64
))),
Value::Array(arr) => Ok(Value::Number(serde_json::Number::from(arr.len() as i64))),
_ => Err(Error::InvalidArguments(INVALID_ARGS.into())),
};
}
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let value = engine.evaluate_node_cow(&args[0], context)?;
match value.as_ref() {
Value::String(s) => {
let char_count = s.chars().count();
if char_count > i64::MAX as usize {
return Err(Error::InvalidArguments("String too long".to_string()));
}
Ok(Value::Number(serde_json::Number::from(char_count as i64)))
}
Value::Array(arr) => {
if arr.len() > i64::MAX as usize {
return Err(Error::InvalidArguments("Array too long".to_string()));
}
Ok(Value::Number(serde_json::Number::from(arr.len() as i64)))
}
Value::Null | Value::Number(_) | Value::Bool(_) | Value::Object(_) => {
Err(Error::InvalidArguments(INVALID_ARGS.into()))
}
}
}
#[inline]
pub fn evaluate_starts_with(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.len() < 2 {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let text = engine.evaluate_node(&args[0], context)?;
let text_str = text.as_str().unwrap_or("");
if let CompiledNode::Value {
value: Value::String(p),
..
} = &args[1]
{
return Ok(Value::Bool(text_str.starts_with(p.as_str())));
}
let pattern = engine.evaluate_node(&args[1], context)?;
let pattern_str = pattern.as_str().unwrap_or("");
Ok(Value::Bool(text_str.starts_with(pattern_str)))
}
#[inline]
pub fn evaluate_ends_with(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.len() < 2 {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let text = engine.evaluate_node(&args[0], context)?;
let text_str = text.as_str().unwrap_or("");
if let CompiledNode::Value {
value: Value::String(p),
..
} = &args[1]
{
return Ok(Value::Bool(text_str.ends_with(p.as_str())));
}
let pattern = engine.evaluate_node(&args[1], context)?;
let pattern_str = pattern.as_str().unwrap_or("");
Ok(Value::Bool(text_str.ends_with(pattern_str)))
}
#[inline]
pub fn evaluate_upper(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.is_empty() {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let value = engine.evaluate_node(&args[0], context)?;
let already_upper = value
.as_str()
.is_some_and(|s| s.is_ascii() && !s.bytes().any(|b| b.is_ascii_lowercase()));
if already_upper {
return Ok(value);
}
let text = value.as_str().unwrap_or("");
Ok(Value::String(text.to_uppercase()))
}
#[inline]
pub fn evaluate_lower(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.is_empty() {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let value = engine.evaluate_node(&args[0], context)?;
let already_lower = value
.as_str()
.is_some_and(|s| s.is_ascii() && !s.bytes().any(|b| b.is_ascii_uppercase()));
if already_lower {
return Ok(value);
}
let text = value.as_str().unwrap_or("");
Ok(Value::String(text.to_lowercase()))
}
#[inline]
pub fn evaluate_trim(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.is_empty() {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let value = engine.evaluate_node(&args[0], context)?;
let needs_trim = value.as_str().is_some_and(|s| {
!s.is_empty() && {
s.starts_with(|c: char| c.is_whitespace()) || s.ends_with(|c: char| c.is_whitespace())
}
});
if !needs_trim {
return match &value {
Value::String(_) => Ok(value),
_ => Ok(Value::String(String::new())),
};
}
let text = value.as_str().unwrap_or("");
Ok(Value::String(text.trim().to_string()))
}
#[inline]
pub fn evaluate_split(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.len() < 2 {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let text = engine.evaluate_node(&args[0], context)?;
let text_str = text.as_str().unwrap_or("");
if let CompiledNode::Value {
value: Value::String(delim),
..
} = &args[1]
{
return split_normal(text_str, delim.as_str());
}
let delimiter = engine.evaluate_node(&args[1], context)?;
let delimiter_str = delimiter.as_str().unwrap_or("");
if delimiter_str.contains("(?P<") {
match Regex::new(delimiter_str) {
Ok(re) => {
let capture_names: Vec<_> = re.capture_names().flatten().collect();
if !capture_names.is_empty() {
if let Some(captures) = re.captures(text_str) {
let mut result = serde_json::Map::new();
for name in capture_names {
if let Some(m) = captures.name(name) {
result.insert(
name.to_string(),
Value::String(m.as_str().to_string()),
);
}
}
return Ok(Value::Object(result));
} else {
return Ok(Value::Object(serde_json::Map::new()));
}
}
}
Err(_) => {
}
}
}
split_normal(text_str, delimiter_str)
}
#[inline]
pub fn evaluate_split_with_regex(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
regex: &Regex,
capture_names: &[Box<str>],
) -> Result<Value> {
if args.is_empty() {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let text = engine.evaluate_node(&args[0], context)?;
let text_str = text.as_str().unwrap_or("");
if let Some(captures) = regex.captures(text_str) {
let mut result = serde_json::Map::new();
for name in capture_names {
if let Some(m) = captures.name(name) {
result.insert(name.to_string(), Value::String(m.as_str().to_string()));
}
}
Ok(Value::Object(result))
} else {
Ok(Value::Object(serde_json::Map::new()))
}
}
#[inline]
fn split_normal(text_str: &str, delimiter_str: &str) -> Result<Value> {
if text_str.is_empty() {
Ok(json!([""]))
} else if delimiter_str.is_empty() {
let chars: Vec<Value> = text_str
.chars()
.map(|c| Value::String(c.to_string()))
.collect();
Ok(Value::Array(chars))
} else {
let parts: Vec<Value> = text_str
.split(delimiter_str)
.map(|s| Value::String(s.to_string()))
.collect();
Ok(Value::Array(parts))
}
}