use super::eager::Cx;
use super::extract::{Any, RestArgs};
use crate::Result;
use crate::arena::{DataValue, data_to_str};
#[cfg(feature = "ext-string")]
use {crate::arena::ContextStack, bumpalo::Bump};
#[inline]
pub(crate) fn concat<'a>(cx: &mut Cx<'_, 'a>, parts: RestArgs<'a, Any>) -> Result<&'a str> {
if parts.is_empty() {
return Ok("");
}
let arena = cx.arena;
let mut buf = bumpalo::collections::String::with_capacity_in(parts.len() * 8, arena);
let mut start = 0;
if parts.len() == 1 {
let av = parts.get(0, cx)?;
match av {
DataValue::Array(items) => {
cx.charge(items.len() as u64)?;
for it in *items {
let piece = data_to_str(it, arena);
cx.charge_bytes(piece.len())?;
buf.push_str(piece);
}
start = 1;
}
_ => {
let piece = data_to_str(av, arena);
cx.charge_bytes(piece.len())?;
return Ok(piece);
}
}
}
for i in start..parts.len() {
match parts.get(i, cx)? {
DataValue::Array(items) => {
cx.charge(items.len() as u64)?;
for it in *items {
let piece = data_to_str(it, arena);
cx.charge_bytes(piece.len())?;
buf.push_str(piece);
}
}
av => {
let piece = data_to_str(av, arena);
cx.charge_bytes(piece.len())?;
buf.push_str(piece);
}
}
}
Ok(buf.into_bump_str())
}
#[inline]
pub(crate) fn substr<'a>(
cx: &mut Cx<'_, 'a>,
string: &'a str,
start: Option<i64>,
length: Option<i64>,
) -> Result<&'a str> {
cx.charge_bytes(string.len())?;
let start = start.unwrap_or(0);
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)
};
Ok(&string[byte_start..byte_end])
}
#[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]
pub(crate) fn in_<'a>(
cx: &mut Cx<'_, 'a>,
needle: &'a DataValue<'a>,
haystack: &'a DataValue<'a>,
) -> Result<bool> {
Ok(match haystack {
DataValue::String(h) => match needle {
DataValue::String(n) => {
cx.charge_bytes(h.len())?;
h.contains(*n)
}
_ => false,
},
DataValue::Array(items) => {
crate::operators::comparison::slice_contains_strict(items, needle, cx.engine, cx.ctx)?
}
_ => false,
})
}
#[cfg(feature = "ext-string")]
#[inline]
pub(crate) fn starts_with<'a>(cx: &mut Cx<'_, 'a>, text: &'a str, prefix: &'a str) -> Result<bool> {
cx.charge_bytes(prefix.len().min(text.len()))?;
Ok(text.starts_with(prefix))
}
#[cfg(feature = "ext-string")]
#[inline]
pub(crate) fn ends_with<'a>(cx: &mut Cx<'_, 'a>, text: &'a str, suffix: &'a str) -> Result<bool> {
cx.charge_bytes(suffix.len().min(text.len()))?;
Ok(text.ends_with(suffix))
}
#[cfg(feature = "ext-string")]
#[inline]
pub(crate) fn upper<'a>(cx: &mut Cx<'_, 'a>, s: &'a str) -> Result<&'a str> {
cx.charge_bytes(s.len())?;
if s.is_ascii() {
return Ok(ascii_case(s, cx.arena, str::make_ascii_uppercase));
}
Ok(cx.arena.alloc_str(&s.to_uppercase()))
}
#[cfg(feature = "ext-string")]
#[inline]
fn ascii_case<'a>(s: &str, arena: &'a Bump, recase: fn(&mut str)) -> &'a str {
let buf = arena.alloc_str(s);
recase(buf);
buf
}
#[cfg(feature = "ext-string")]
#[inline]
pub(crate) fn lower<'a>(cx: &mut Cx<'_, 'a>, s: &'a str) -> Result<&'a str> {
cx.charge_bytes(s.len())?;
if s.is_ascii() {
return Ok(ascii_case(s, cx.arena, str::make_ascii_lowercase));
}
Ok(cx.arena.alloc_str(&s.to_lowercase()))
}
#[cfg(feature = "ext-string")]
#[inline]
pub(crate) fn trim<'a>(cx: &mut Cx<'_, 'a>, s: &'a str) -> Result<&'a str> {
cx.charge_bytes(s.len())?;
Ok(s.trim())
}
#[cfg(feature = "ext-string")]
#[inline]
pub(crate) fn split<'a>(
cx: &mut Cx<'_, 'a>,
text: &'a str,
delim: &'a str,
) -> Result<&'a DataValue<'a>> {
cx.charge_bytes(text.len())?;
split_arena_normal(text, delim, cx.ctx, cx.arena)
}
#[cfg(feature = "ext-string")]
#[inline]
fn split_arena_normal<'a>(
text: &'a str,
delim: &str,
ctx: &mut ContextStack<'_>,
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 parts = text.chars().count();
ctx.charge(parts as u64)?;
let mut items: bumpalo::collections::Vec<'a, DataValue<'a>> =
bumpalo::collections::Vec::with_capacity_in(parts, arena);
for (at, c) in text.char_indices() {
items.push(DataValue::String(&text[at..at + c.len_utf8()]));
}
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(part));
}
ctx.charge(items.len() as u64)?;
Ok(arena.alloc(DataValue::Array(items.into_bump_slice())))
}