use crate::{Result, ZuzuRustError};
use super::super::{Runtime, Value};
use super::common::{
collection_get, collection_product, collection_sum, expect_function, make_iterator,
optional_count, require_arity, require_arity_range, signed_index, stored_arg, unique_values,
CollectionTarget,
};
fn random_index(max: usize) -> Result<usize> {
if max == 0 {
return Ok(0);
}
let mut bytes = [0_u8; 8];
getrandom::getrandom(&mut bytes)
.map_err(|err| ZuzuRustError::runtime(format!("random failed: {err}")))?;
Ok((u64::from_le_bytes(bytes) % max as u64) as usize)
}
fn shuffled_values(values: &[Value]) -> Result<Vec<Value>> {
let mut out = values.to_vec();
for idx in (1..out.len()).rev() {
let swap_idx = random_index(idx + 1)?;
out.swap(idx, swap_idx);
}
Ok(out)
}
impl Runtime {
pub(in crate::runtime) fn call_array_method(
&self,
values: &mut Vec<Value>,
name: &str,
args: &[Value],
) -> Result<Value> {
match name {
"append" | "push" | "add" => {
values.extend(args.iter().cloned().map(Value::into_shared_if_composite));
Ok(Value::Array(values.clone()))
}
"push_weak" => {
values.extend(args.iter().map(|value| stored_arg(value, true)));
Ok(Value::Array(values.clone()))
}
"copy" => {
require_arity(name, args, 0)?;
Ok(Value::Array(values.clone()))
}
"pop" => {
require_arity(name, args, 0)?;
Ok(values.pop().unwrap_or(Value::Null))
}
"prepend" | "unshift" => {
let mut out = args
.iter()
.cloned()
.map(Value::into_shared_if_composite)
.collect::<Vec<_>>();
out.extend(values.iter().cloned());
*values = out;
Ok(Value::Array(values.clone()))
}
"unshift_weak" => {
let mut out = args
.iter()
.map(|value| stored_arg(value, true))
.collect::<Vec<_>>();
out.extend(values.iter().cloned());
*values = out;
Ok(Value::Array(values.clone()))
}
"shift" => {
require_arity(name, args, 0)?;
if values.is_empty() {
Ok(Value::Null)
} else {
Ok(values.remove(0))
}
}
"length" | "count" => {
require_arity(name, args, 0)?;
Ok(Value::Number(values.len() as f64))
}
"empty" | "is_empty" => {
require_arity(name, args, 0)?;
Ok(Value::Boolean(values.is_empty()))
}
"get" => collection_get(values, args),
"set" => {
require_arity(name, args, 2)?;
let index = signed_index(values.len(), &args[0]);
if index < 0 {
return Err(ZuzuRustError::runtime("Array index is out of range"));
}
let index = index as usize;
if index >= values.len() {
values.resize(index, Value::Null);
values.push(args[1].clone().into_shared_if_composite());
} else {
values[index] = args[1].clone().into_shared_if_composite();
}
Ok(Value::Array(values.clone()))
}
"set_weak" => {
require_arity(name, args, 2)?;
let index = signed_index(values.len(), &args[0]);
if index < 0 {
return Err(ZuzuRustError::runtime("Array index is out of range"));
}
let index = index as usize;
let value = stored_arg(&args[1], true);
if index >= values.len() {
values.resize(index, Value::Null);
values.push(value);
} else {
values[index] = value;
}
Ok(Value::Array(values.clone()))
}
"clear" => {
require_arity(name, args, 0)?;
values.clear();
Ok(Value::Array(values.clone()))
}
"to_Array" => {
require_arity(name, args, 0)?;
Ok(Value::Array(values.clone()))
}
"to_Set" => Ok(Value::Set(unique_values(values))),
"to_Bag" => Ok(Value::Bag(values.clone())),
"to_Iterator" => {
require_arity(name, args, 0)?;
Ok(make_iterator(values.clone()))
}
"sort" => {
require_arity(name, args, 1)?;
let cmp = expect_function(&args[0], "Collection sort expects a function callback")?;
let mut out = values.clone();
out.sort_by(|left, right| {
self.compare_via_callback(&cmp, left.clone(), right.clone())
.unwrap_or(std::cmp::Ordering::Equal)
});
Ok(Value::Array(out))
}
"sortstr" => {
require_arity(name, args, 0)?;
let mut out = values.clone();
out.sort_by_key(|value| value.render());
Ok(Value::Array(out))
}
"sortnum" => {
require_arity(name, args, 0)?;
let mut out = values.clone();
out.sort_by(|left, right| {
let lhs = left.to_number().unwrap_or(0.0);
let rhs = right.to_number().unwrap_or(0.0);
lhs.partial_cmp(&rhs).unwrap_or(std::cmp::Ordering::Equal)
});
for value in &mut out {
if let Ok(number) = value.to_number() {
*value = Value::Number(number);
}
}
Ok(Value::Array(out))
}
"reverse" => {
require_arity(name, args, 0)?;
let mut out = values.clone();
out.reverse();
Ok(Value::Array(out))
}
"head" => {
let n = optional_count(args, 1)?;
Ok(Value::Array(values.iter().take(n).cloned().collect()))
}
"tail" => {
let n = optional_count(args, 1)?;
if n == 0 {
return Ok(Value::Array(Vec::new()));
}
let start = values.len().saturating_sub(n);
Ok(Value::Array(values[start..].to_vec()))
}
"slice" => {
if args.is_empty() || args.len() > 2 {
return Err(ZuzuRustError::runtime(
"slice() expects one or two arguments",
));
}
let len = values.len() as isize;
let mut start = args[0].to_number().unwrap_or(0.0) as isize;
if start < 0 {
start += len;
}
start = start.clamp(0, len);
let end = if args.len() == 2 {
let mut end = args[1].to_number().unwrap_or(len as f64) as isize;
if end < 0 {
end += len;
}
end.clamp(0, len)
} else {
len
};
let from = start.min(end) as usize;
let to = start.max(end) as usize;
Ok(Value::Array(values[from..to].to_vec()))
}
"join" => {
require_arity_range(name, args, 1, 2)?;
let separator = self.value_to_operator_string(&args[0])?;
let mut fallback_string = None;
let mut out = Vec::new();
for value in values.iter() {
match self.value_to_operator_string(value) {
Ok(text) => out.push(text),
Err(_) if args.len() == 2 => {
if let Value::Function(function) = &args[1] {
let replacement = self.await_if_task(self.call_function(
function,
vec![value.clone()],
Vec::new(),
)?)?;
out.push(self.value_to_operator_string(&replacement)?);
} else {
if fallback_string.is_none() {
fallback_string =
Some(self.value_to_operator_string(&args[1])?);
}
out.push(fallback_string.clone().unwrap_or_default());
}
}
Err(err) => return Err(err),
}
}
Ok(Value::String(out.join(&separator)))
}
"sum" => {
require_arity(name, args, 0)?;
Ok(Value::Number(collection_sum(values)?))
}
"product" => {
require_arity(name, args, 0)?;
Ok(Value::Number(collection_product(values)?))
}
"shuffle" => {
require_arity(name, args, 0)?;
Ok(Value::Array(shuffled_values(values)?))
}
"sample" => {
let n = optional_count(args, 1)?;
Ok(Value::Array(
shuffled_values(values)?.into_iter().take(n).collect(),
))
}
"contains" => {
require_arity(name, args, 1)?;
Ok(Value::Boolean(
values.iter().any(|value| value.strict_eq(&args[0])),
))
}
"map" => self.map_values(values, args, "map", CollectionTarget::Array),
"grep" => self.filter_values(values, args, "grep", CollectionTarget::Array),
"any" => self.any_values(values, args),
"all" => self.all_values(values, args),
"first" => self.first_value(values, args),
"remove" => {
require_arity(name, args, 1)?;
let pred =
expect_function(&args[0], "Collection method expects a function callback")?;
values.retain(|value| {
!self
.predicate_callback(&pred, value.clone())
.unwrap_or(false)
});
Ok(Value::Array(values.clone()))
}
"first_index" => {
require_arity(name, args, 1)?;
let pred =
expect_function(&args[0], "Collection method expects a function callback")?;
for (index, value) in values.iter().enumerate() {
if self.predicate_callback(&pred, value.clone())? {
return Ok(Value::Number(index as f64));
}
}
Ok(Value::Number(-1.0))
}
"for_each_value" => {
require_arity(name, args, 1)?;
let func =
expect_function(&args[0], "Collection method expects a function callback")?;
for value in values.iter().cloned() {
let _ = self.call_function(&func, vec![value], Vec::new())?;
}
Ok(Value::Array(values.clone()))
}
"reduce" => self.reduce_values(values, args, false),
"reductions" => self.reduce_values(values, args, true),
other => Err(ZuzuRustError::thrown(format!(
"unsupported Array method '{}'",
other
))),
}
}
}