use crate::{CompiledNode, Engine, Result};
use crate::arena::{ContextStack, DataValue, data_to_str};
use bumpalo::Bump;
#[inline]
pub(crate) fn evaluate_concat<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
let mut buf = bumpalo::collections::String::new_in(arena);
for arg in args {
let av = engine.dispatch_node(arg, ctx, arena)?;
match av {
DataValue::Array(items) => {
for it in *items {
buf.push_str(data_to_str(it, arena));
}
}
_ => buf.push_str(data_to_str(av, arena)),
}
}
Ok(arena.alloc(DataValue::String(buf.into_bump_str())))
}
#[inline]
pub(crate) fn evaluate_substr<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
if args.is_empty() {
return Ok(crate::arena::singletons::singleton_empty_string());
}
let s_av = engine.dispatch_node(&args[0], ctx, arena)?;
let string = data_to_str(s_av, arena);
let char_count = string.chars().count();
let start: i64 = substr_arg_i64(args.get(1), ctx, engine, arena)?.unwrap_or(0);
let length: Option<i64> = substr_arg_i64(args.get(2), ctx, engine, arena)?;
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_str: String = if let Some(len) = length {
if len < 0 {
let abs_end = len.saturating_abs() as usize;
let end_pos = char_count.saturating_sub(abs_end);
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()
};
if result_str.is_empty() {
return Ok(crate::arena::singletons::singleton_empty_string());
}
Ok(arena.alloc(DataValue::String(arena.alloc_str(&result_str))))
}
#[inline]
fn substr_arg_i64<'a>(
arg: Option<&'a CompiledNode>,
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<Option<i64>> {
let Some(node) = arg else { return Ok(None) };
if let CompiledNode::Value { value, .. } = node {
return Ok(value.as_i64());
}
Ok(engine.dispatch_node(node, ctx, arena)?.as_i64())
}
#[inline]
pub(crate) fn evaluate_in<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
if args.len() < 2 {
return Ok(crate::arena::singletons::singleton_false());
}
let needle = engine.dispatch_node(&args[0], ctx, arena)?;
let haystack = engine.dispatch_node(&args[1], ctx, arena)?;
let result = match haystack {
DataValue::String(h) => match needle {
DataValue::String(n) => h.contains(*n),
_ => false,
},
DataValue::Array(items) => items.iter().any(|it| {
crate::operators::comparison::compare_equals(it, needle, true, engine).unwrap_or(false)
}),
_ => false,
};
Ok(crate::arena::singletons::singleton_bool(result))
}
#[cfg(feature = "ext-string")]
#[inline]
pub(crate) fn evaluate_starts_with<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
if args.len() < 2 {
return Err(crate::Error::invalid_args());
}
let s = engine.dispatch_node(&args[0], ctx, arena)?;
let p = engine.dispatch_node(&args[1], ctx, arena)?;
let s_str = data_to_str(s, arena);
let p_str = data_to_str(p, arena);
Ok(crate::arena::singletons::singleton_bool(
s_str.starts_with(p_str),
))
}
#[cfg(feature = "ext-string")]
#[inline]
pub(crate) fn evaluate_ends_with<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
if args.len() < 2 {
return Err(crate::Error::invalid_args());
}
let s = engine.dispatch_node(&args[0], ctx, arena)?;
let p = engine.dispatch_node(&args[1], ctx, arena)?;
let s_str = data_to_str(s, arena);
let p_str = data_to_str(p, arena);
Ok(crate::arena::singletons::singleton_bool(
s_str.ends_with(p_str),
))
}
#[cfg(feature = "ext-string")]
#[inline]
pub(crate) fn evaluate_upper<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
if args.is_empty() {
return Err(crate::Error::invalid_args());
}
let av = engine.dispatch_node(&args[0], ctx, arena)?;
let s = data_to_str(av, arena);
Ok(arena.alloc(DataValue::String(arena.alloc_str(&s.to_uppercase()))))
}
#[cfg(feature = "ext-string")]
#[inline]
pub(crate) fn evaluate_lower<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
if args.is_empty() {
return Err(crate::Error::invalid_args());
}
let av = engine.dispatch_node(&args[0], ctx, arena)?;
let s = data_to_str(av, arena);
Ok(arena.alloc(DataValue::String(arena.alloc_str(&s.to_lowercase()))))
}
#[cfg(feature = "ext-string")]
#[inline]
pub(crate) fn evaluate_trim<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
if args.is_empty() {
return Err(crate::Error::invalid_args());
}
let av = engine.dispatch_node(&args[0], ctx, arena)?;
let s = data_to_str(av, arena);
Ok(arena.alloc(DataValue::String(arena.alloc_str(s.trim()))))
}
#[cfg(feature = "ext-string")]
#[inline]
pub(crate) fn evaluate_split<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
if args.len() < 2 {
return Err(crate::Error::invalid_args());
}
let text_av = engine.dispatch_node(&args[0], ctx, arena)?;
let text_str: &'a str = match text_av {
DataValue::String(s) => s,
_ => data_to_str(text_av, arena),
};
let delim_str: &'a str = if let CompiledNode::Value {
value: datavalue::OwnedDataValue::String(s),
..
} = &args[1]
{
arena.alloc_str(s)
} else {
let av = engine.dispatch_node(&args[1], ctx, arena)?;
match av {
DataValue::String(s) => s,
_ => data_to_str(av, arena),
}
};
split_arena_normal(text_str, delim_str, arena)
}
#[cfg(feature = "ext-string")]
#[inline]
fn split_arena_normal<'a>(text: &str, delim: &str, arena: &'a Bump) -> Result<&'a DataValue<'a>> {
if text.is_empty() {
let item: &'a str = "";
let slice = bumpalo::vec![in arena; DataValue::String(item)].into_bump_slice();
return Ok(arena.alloc(DataValue::Array(slice)));
}
if delim.is_empty() {
let mut items: bumpalo::collections::Vec<'a, DataValue<'a>> =
bumpalo::collections::Vec::with_capacity_in(text.chars().count(), arena);
for c in text.chars() {
let mut buf = bumpalo::collections::String::new_in(arena);
buf.push(c);
items.push(DataValue::String(buf.into_bump_str()));
}
return Ok(arena.alloc(DataValue::Array(items.into_bump_slice())));
}
let mut items: bumpalo::collections::Vec<'a, DataValue<'a>> =
bumpalo::collections::Vec::new_in(arena);
for part in text.split(delim) {
items.push(DataValue::String(arena.alloc_str(part)));
}
Ok(arena.alloc(DataValue::Array(items.into_bump_slice())))
}