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 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 (byte_start, byte_end) = if string.is_ascii() {
let n = string.len();
let byte_start = if start < 0 {
n.saturating_sub(start.saturating_abs() as usize)
} else {
(start as usize).min(n)
};
let byte_end = match length {
Some(len) if len < 0 => n
.saturating_sub(len.saturating_abs() as usize)
.max(byte_start),
Some(len) => byte_start.saturating_add(len as usize).min(n),
None => n,
};
(byte_start, byte_end)
} else {
let char_count = if start < 0 || matches!(length, Some(len) if len < 0) {
string.chars().count()
} else {
0
};
let actual_start = if start < 0 {
let abs_start = start.saturating_abs() as usize;
char_count.saturating_sub(abs_start)
} else {
start as usize
};
let take: Option<usize> = match length {
Some(len) if len < 0 => {
let abs_end = len.saturating_abs() as usize;
let end_pos = char_count.saturating_sub(abs_end);
Some(end_pos.saturating_sub(actual_start.min(char_count)))
}
Some(len) => Some(len as usize),
None => None,
};
let byte_start = char_to_byte_offset(string, actual_start);
let byte_end = match take {
Some(n) => byte_start + char_to_byte_offset(&string[byte_start..], n),
None => string.len(),
};
(byte_start, byte_end)
};
let result = &string[byte_start..byte_end];
if result.is_empty() {
return Ok(crate::arena::singletons::singleton_empty_string());
}
Ok(arena.alloc(DataValue::String(result)))
}
#[inline]
pub(crate) fn char_to_byte_offset(s: &str, n: usize) -> usize {
if n == 0 {
return 0;
}
s.char_indices().nth(n).map_or(s.len(), |(b, _)| b)
}
#[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);
let mut buf = bumpalo::collections::String::with_capacity_in(s.len(), arena);
for c in s.chars() {
for u in c.to_uppercase() {
buf.push(u);
}
}
Ok(arena.alloc(DataValue::String(buf.into_bump_str())))
}
#[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);
let mut buf = bumpalo::collections::String::with_capacity_in(s.len(), arena);
for c in s.chars() {
for l in c.to_lowercase() {
buf.push(l);
}
}
Ok(arena.alloc(DataValue::String(buf.into_bump_str())))
}
#[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(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())))
}