rhai/src/packages/array_basic.rs
2022-02-10 12:33:48 +08:00

2359 lines
72 KiB
Rust

#![cfg(not(feature = "no_index"))]
use crate::engine::OP_EQUALS;
use crate::eval::{calc_index, calc_offset_len};
use crate::plugin::*;
use crate::{
def_package, Array, Dynamic, ExclusiveRange, FnPtr, InclusiveRange, NativeCallContext,
Position, RhaiResultOf, StaticVec, ERR, INT,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{any::TypeId, cmp::Ordering, mem};
def_package! {
/// Package of basic array utilities.
pub BasicArrayPackage(lib) {
lib.standard = true;
combine_with_exported_module!(lib, "array", array_functions);
// Register array iterator
lib.set_iterable::<Array>();
}
}
#[export_module]
pub mod array_functions {
/// Number of elements in the array.
#[rhai_fn(name = "len", get = "len", pure)]
pub fn len(array: &mut Array) -> INT {
array.len() as INT
}
/// Get a copy of the element at the `index` position in the array.
///
/// * If `index` < 0, position counts from the end of the array (`-1` is the last element).
/// * If `index` < -length of array, `()` is returned.
/// * If `index` ≥ length of array, `()` is returned.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3];
///
/// print(x.get(0)); // prints 1
///
/// print(x.get(-1)); // prints 3
///
/// print(x.get(99)); // prints empty (for '()')
/// ```
pub fn get(array: &mut Array, index: INT) -> Dynamic {
if array.is_empty() {
return Dynamic::UNIT;
}
let (index, ..) = calc_offset_len(array.len(), index, 0);
if index >= array.len() {
Dynamic::UNIT
} else {
array[index].clone()
}
}
/// Set the element at the `index` position in the array to a new `value`.
///
/// * If `index` < 0, position counts from the end of the array (`-1` is the last element).
/// * If `index` < -length of array, the array is not modified.
/// * If `index` ≥ length of array, the array is not modified.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3];
///
/// x.set(0, 42);
///
/// print(x); // prints "[42, 2, 3]"
///
/// x.set(-3, 0);
///
/// print(x); // prints "[0, 2, 3]"
///
/// x.set(99, 123);
///
/// print(x); // prints "[0, 2, 3]"
/// ```
pub fn set(array: &mut Array, index: INT, value: Dynamic) {
if array.is_empty() {
return;
}
let (index, ..) = calc_offset_len(array.len(), index, 0);
if index < array.len() {
array[index] = value;
}
}
/// Add a new element, which is not another array, to the end of the array.
///
/// If `item` is `Array`, then `append` is more specific and will be called instead.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3];
///
/// x.push("hello");
///
/// print(x); // prints [1, 2, 3, "hello"]
/// ```
pub fn push(array: &mut Array, item: Dynamic) {
array.push(item);
}
/// Add all the elements of another array to the end of the array.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3];
/// let y = [true, 'x'];
///
/// x.push(y);
///
/// print(x); // prints "[1, 2, 3, true, 'x']"
/// ```
pub fn append(array: &mut Array, new_array: Array) {
if !new_array.is_empty() {
if array.is_empty() {
*array = new_array;
} else {
array.extend(new_array);
}
}
}
/// Combine two arrays into a new array and return it.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3];
/// let y = [true, 'x'];
///
/// print(x + y); // prints "[1, 2, 3, true, 'x']"
///
/// print(x); // prints "[1, 2, 3"
/// ```
#[rhai_fn(name = "+")]
pub fn concat(array1: Array, array2: Array) -> Array {
if !array2.is_empty() {
if array1.is_empty() {
array2
} else {
let mut array = array1;
array.extend(array2);
array
}
} else {
array1
}
}
/// Add a new element into the array at a particular `index` position.
///
/// * If `index` < 0, position counts from the end of the array (`-1` is the last element).
/// * If `index` < -length of array, the element is added to the beginning of the array.
/// * If `index` ≥ length of array, the element is appended to the end of the array.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3];
///
/// x.insert(0, "hello");
///
/// x.insert(2, true);
///
/// x.insert(-2, 42);
///
/// print(x); // prints ["hello", 1, true, 2, 42, 3]
/// ```
pub fn insert(array: &mut Array, index: INT, item: Dynamic) {
if array.is_empty() {
array.push(item);
return;
}
let (index, ..) = calc_offset_len(array.len(), index, 0);
if index >= array.len() {
array.push(item);
} else {
array.insert(index, item);
}
}
/// Pad the array to at least the specified length with copies of a specified element.
///
/// If `len` ≤ length of array, no padding is done.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3];
///
/// x.pad(5, 42);
///
/// print(x); // prints "[1, 2, 3, 42, 42]"
///
/// x.pad(3, 123);
///
/// print(x); // prints "[1, 2, 3, 42, 42]"
/// ```
#[rhai_fn(return_raw)]
pub fn pad(
ctx: NativeCallContext,
array: &mut Array,
len: INT,
item: Dynamic,
) -> RhaiResultOf<()> {
if len <= 0 || (len as usize) <= array.len() {
return Ok(());
}
let _ctx = ctx;
let len = len as usize;
// Check if array will be over max size limit
#[cfg(not(feature = "unchecked"))]
{
if _ctx.engine().max_array_size() > 0 && len > _ctx.engine().max_array_size() {
return Err(
ERR::ErrorDataTooLarge("Size of array".to_string(), Position::NONE).into(),
);
}
let check_sizes = match item.0 {
crate::types::dynamic::Union::Array(..) | crate::types::dynamic::Union::Str(..) => {
true
}
#[cfg(not(feature = "no_object"))]
crate::types::dynamic::Union::Map(..) => true,
_ => false,
};
if check_sizes {
let mut arr_len = array.len();
let mut arr = Dynamic::from_array(mem::take(array));
let (mut a1, mut m1, mut s1) = crate::Engine::calc_data_sizes(&arr, true);
let (a2, m2, s2) = crate::Engine::calc_data_sizes(&item, true);
{
let mut guard = arr.write_lock::<Array>().unwrap();
while arr_len < len {
a1 += a2;
m1 += m2;
s1 += s2;
_ctx.engine()
.raise_err_if_over_data_size_limit((a1, m1, s1), Position::NONE)?;
guard.push(item.clone());
arr_len += 1;
}
}
*array = arr.into_array().unwrap();
} else {
array.resize(len, item);
}
}
#[cfg(feature = "unchecked")]
array.resize(len, item);
Ok(())
}
/// Remove the last element from the array and return it.
///
/// If the array is empty, `()` is returned.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3];
///
/// print(x.pop()); // prints 3
///
/// print(x); // prints "[1, 2]"
/// ```
pub fn pop(array: &mut Array) -> Dynamic {
if array.is_empty() {
Dynamic::UNIT
} else {
array.pop().unwrap_or_else(|| Dynamic::UNIT)
}
}
/// Remove the first element from the array and return it.
///
/// If the array is empty, `()` is returned.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3];
///
/// print(x.shift()); // prints 1
///
/// print(x); // prints "[2, 3]"
/// ```
pub fn shift(array: &mut Array) -> Dynamic {
if array.is_empty() {
Dynamic::UNIT
} else {
array.remove(0)
}
}
/// Remove the element at the specified `index` from the array and return it.
///
/// * If `index` < 0, position counts from the end of the array (`-1` is the last element).
/// * If `index` < -length of array, `()` is returned.
/// * If `index` ≥ length of array, `()` is returned.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3];
///
/// print(x.remove(1)); // prints 2
///
/// print(x); // prints "[1, 3]"
///
/// print(x.remove(-2)); // prints 1
///
/// print(x); // prints "[3]"
/// ```
pub fn remove(array: &mut Array, index: INT) -> Dynamic {
let index = match calc_index(array.len(), index, true, || Err(())) {
Ok(n) => n,
Err(_) => return Dynamic::UNIT,
};
array.remove(index)
}
/// Clear the array.
pub fn clear(array: &mut Array) {
if !array.is_empty() {
array.clear();
}
}
/// Cut off the array at the specified length.
///
/// * If `len` ≤ 0, the array is cleared.
/// * If `len` ≥ length of array, the array is not truncated.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// x.truncate(3);
///
/// print(x); // prints "[1, 2, 3]"
///
/// x.truncate(10);
///
/// print(x); // prints "[1, 2, 3]"
/// ```
pub fn truncate(array: &mut Array, len: INT) {
if !array.is_empty() {
if len > 0 {
array.truncate(len as usize);
} else {
array.clear();
}
}
}
/// Cut off the head of the array, leaving a tail of the specified length.
///
/// * If `len` ≤ 0, the array is cleared.
/// * If `len` ≥ length of array, the array is not modified.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// x.chop(3);
///
/// print(x); // prints "[3, 4, 5]"
///
/// x.chop(10);
///
/// print(x); // prints "[3, 4, 5]"
/// ```
pub fn chop(array: &mut Array, len: INT) {
if !array.is_empty() {
if len <= 0 {
array.clear();
} else if (len as usize) < array.len() {
array.drain(0..array.len() - len as usize);
}
}
}
/// Reverse all the elements in the array.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// x.reverse();
///
/// print(x); // prints "[5, 4, 3, 2, 1]"
/// ```
pub fn reverse(array: &mut Array) {
if !array.is_empty() {
array.reverse();
}
}
/// Replace an exclusive range of the array with another array.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
/// let y = [7, 8, 9, 10];
///
/// x.splice(1..3, y);
///
/// print(x); // prints "[1, 7, 8, 9, 10, 4, 5]"
/// ```
#[rhai_fn(name = "splice")]
pub fn splice_range(array: &mut Array, range: ExclusiveRange, replace: Array) {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
splice(array, start, end - start, replace)
}
/// Replace an inclusive range of the array with another array.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
/// let y = [7, 8, 9, 10];
///
/// x.splice(1..=3, y);
///
/// print(x); // prints "[1, 7, 8, 9, 10, 5]"
/// ```
#[rhai_fn(name = "splice")]
pub fn splice_inclusive_range(array: &mut Array, range: InclusiveRange, replace: Array) {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
splice(array, start, end - start + 1, replace)
}
/// Replace a portion of the array with another array.
///
/// * If `start` < 0, position counts from the end of the array (`-1` is the last element).
/// * If `start` < -length of array, position counts from the beginning of the array.
/// * If `start` ≥ length of array, the other array is appended to the end of the array.
/// * If `len` ≤ 0, the other array is inserted into the array at the `start` position without replacing any element.
/// * If `start` position + `len` ≥ length of array, entire portion of the array after the `start` position is replaced.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
/// let y = [7, 8, 9, 10];
///
/// x.splice(1, 2, y);
///
/// print(x); // prints "[1, 7, 8, 9, 10, 4, 5]"
///
/// x.splice(-5, 4, y);
///
/// print(x); // prints "[1, 7, 7, 8, 9, 10, 5]"
/// ```
pub fn splice(array: &mut Array, start: INT, len: INT, replace: Array) {
if array.is_empty() {
*array = replace;
return;
}
let (start, len) = calc_offset_len(array.len(), start, len);
if start >= array.len() {
array.extend(replace);
} else {
array.splice(start..start + len, replace);
}
}
/// Copy an exclusive range of the array and return it as a new array.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// print(x.extract(1..3)); // prints "[2, 3]"
///
/// print(x); // prints "[1, 2, 3, 4, 5]"
/// ```
#[rhai_fn(name = "extract")]
pub fn extract_range(array: &mut Array, range: ExclusiveRange) -> Array {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
extract(array, start, end - start)
}
/// Copy an inclusive range of the array and return it as a new array.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// print(x.extract(1..=3)); // prints "[2, 3, 4]"
///
/// print(x); // prints "[1, 2, 3, 4, 5]"
/// ```
#[rhai_fn(name = "extract")]
pub fn extract_inclusive_range(array: &mut Array, range: InclusiveRange) -> Array {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
extract(array, start, end - start + 1)
}
/// Copy a portion of the array and return it as a new array.
///
/// * If `start` < 0, position counts from the end of the array (`-1` is the last element).
/// * If `start` < -length of array, position counts from the beginning of the array.
/// * If `start` ≥ length of array, an empty array is returned.
/// * If `len` ≤ 0, an empty array is returned.
/// * If `start` position + `len` ≥ length of array, entire portion of the array after the `start` position is copied and returned.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// print(x.extract(1, 3)); // prints "[2, 3, 4]"
///
/// print(x.extract(-3, 2)); // prints "[3, 4]"
///
/// print(x); // prints "[1, 2, 3, 4, 5]"
/// ```
pub fn extract(array: &mut Array, start: INT, len: INT) -> Array {
if array.is_empty() || len <= 0 {
return Array::new();
}
let (start, len) = calc_offset_len(array.len(), start, len);
if len == 0 {
Array::new()
} else {
array[start..start + len].to_vec()
}
}
/// Copy a portion of the array beginning at the `start` position till the end and return it as
/// a new array.
///
/// * If `start` < 0, position counts from the end of the array (`-1` is the last element).
/// * If `start` < -length of array, the entire array is copied and returned.
/// * If `start` ≥ length of array, an empty array is returned.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// print(x.extract(2)); // prints "[3, 4, 5]"
///
/// print(x.extract(-3)); // prints "[3, 4, 5]"
///
/// print(x); // prints "[1, 2, 3, 4, 5]"
/// ```
#[rhai_fn(name = "extract")]
pub fn extract_tail(array: &mut Array, start: INT) -> Array {
extract(array, start, INT::MAX)
}
/// Cut off the array at `index` and return it as a new array.
///
/// * If `index` < 0, position counts from the end of the array (`-1` is the last element).
/// * If `index` is zero, the entire array is cut and returned.
/// * If `index` < -length of array, the entire array is cut and returned.
/// * If `index` ≥ length of array, nothing is cut from the array and an empty array is returned.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.split(2);
///
/// print(y); // prints "[3, 4, 5]"
///
/// print(x); // prints "[1, 2]"
/// ```
#[rhai_fn(name = "split")]
pub fn split_at(array: &mut Array, index: INT) -> Array {
if array.is_empty() {
return Array::new();
}
let (start, len) = calc_offset_len(array.len(), index, INT::MAX);
if start == 0 {
if len >= array.len() {
mem::take(array)
} else {
let mut result = Array::new();
result.extend(array.drain(array.len() - len..));
result
}
} else if start >= array.len() {
Array::new()
} else {
let mut result = Array::new();
result.extend(array.drain(start as usize..));
result
}
}
/// Iterate through all the elements in the array, applying a `mapper` function to each element
/// in turn, and return the results as a new array.
///
/// # Function Parameters
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.map(|v| v * v);
///
/// print(y); // prints "[1, 4, 9, 16, 25]"
///
/// let y = x.map(|v, i| v * i);
///
/// print(y); // prints "[0, 2, 6, 12, 20]"
/// ```
#[rhai_fn(return_raw, pure)]
pub fn map(ctx: NativeCallContext, array: &mut Array, mapper: FnPtr) -> RhaiResultOf<Array> {
if array.is_empty() {
return Ok(array.clone());
}
let mut ar = Array::with_capacity(array.len());
for (i, item) in array.iter().enumerate() {
ar.push(
mapper
.call_raw(&ctx, None, [item.clone()])
.or_else(|err| match *err {
ERR::ErrorFunctionNotFound(fn_sig, ..)
if fn_sig.starts_with(mapper.fn_name()) =>
{
mapper.call_raw(&ctx, None, [item.clone(), (i as INT).into()])
}
_ => Err(err),
})
.map_err(|err| {
Box::new(ERR::ErrorInFunctionCall(
"map".to_string(),
ctx.source().unwrap_or("").to_string(),
err,
Position::NONE,
))
})?,
);
}
Ok(ar)
}
/// Iterate through all the elements in the array, applying a function named by `mapper` to each
/// element in turn, and return the results as a new array.
///
/// # Function Parameters
///
/// A function with the same name as the value of `mapper` must exist taking these parameters:
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn square(x) { x * x }
///
/// fn multiply(x, i) { x * i }
///
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.map("square");
///
/// print(y); // prints "[1, 4, 9, 16, 25]"
///
/// let y = x.map("multiply");
///
/// print(y); // prints "[0, 2, 6, 12, 20]"
/// ```
#[rhai_fn(name = "map", return_raw, pure)]
pub fn map_by_fn_name(
ctx: NativeCallContext,
array: &mut Array,
mapper: &str,
) -> RhaiResultOf<Array> {
map(ctx, array, FnPtr::new(mapper)?)
}
/// Iterate through all the elements in the array, applying a `filter` function to each element
/// in turn, and return a copy of all elements (in order) that return `true` as a new array.
///
/// # Function Parameters
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.filter(|v| v >= 3);
///
/// print(y); // prints "[3, 4, 5]"
///
/// let y = x.filter(|v, i| v * i >= 10);
///
/// print(y); // prints "[12, 20]"
/// ```
#[rhai_fn(return_raw, pure)]
pub fn filter(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf<Array> {
if array.is_empty() {
return Ok(array.clone());
}
let mut ar = Array::new();
for (i, item) in array.iter().enumerate() {
if filter
.call_raw(&ctx, None, [item.clone()])
.or_else(|err| match *err {
ERR::ErrorFunctionNotFound(fn_sig, ..)
if fn_sig.starts_with(filter.fn_name()) =>
{
filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()])
}
_ => Err(err),
})
.map_err(|err| {
Box::new(ERR::ErrorInFunctionCall(
"filter".to_string(),
ctx.source().unwrap_or("").to_string(),
err,
Position::NONE,
))
})?
.as_bool()
.unwrap_or(false)
{
ar.push(item.clone());
}
}
Ok(ar)
}
/// Iterate through all the elements in the array, applying a function named by `filter` to each
/// element in turn, and return a copy of all elements (in order) that return `true` as a new array.
///
/// # Function Parameters
///
/// A function with the same name as the value of `filter` must exist taking these parameters:
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn screen(x, i) { x * i >= 10 }
///
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.filter("is_odd");
///
/// print(y); // prints "[1, 3, 5]"
///
/// let y = x.filter("screen");
///
/// print(y); // prints "[12, 20]"
/// ```
#[rhai_fn(name = "filter", return_raw, pure)]
pub fn filter_by_fn_name(
ctx: NativeCallContext,
array: &mut Array,
filter_func: &str,
) -> RhaiResultOf<Array> {
filter(ctx, array, FnPtr::new(filter_func)?)
}
/// Return `true` if the array contains an element that equals `value`.
///
/// The operator `==` is used to compare elements with `value` and must be defined,
/// otherwise `false` is assumed.
///
/// This function also drives the `in` operator.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// // The 'in' operator calls 'contains' in the background
/// if 4 in x {
/// print("found!");
/// }
/// ```
#[rhai_fn(return_raw, pure)]
pub fn contains(
ctx: NativeCallContext,
array: &mut Array,
value: Dynamic,
) -> RhaiResultOf<bool> {
if array.is_empty() {
return Ok(false);
}
for item in array.iter_mut() {
if ctx
.call_fn_raw(OP_EQUALS, true, false, &mut [item, &mut value.clone()])
.or_else(|err| match *err {
ERR::ErrorFunctionNotFound(ref fn_sig, ..) if fn_sig.starts_with(OP_EQUALS) => {
if item.type_id() == value.type_id() {
// No default when comparing same type
Err(err)
} else {
Ok(Dynamic::FALSE)
}
}
_ => Err(err),
})?
.as_bool()
.unwrap_or(false)
{
return Ok(true);
}
}
Ok(false)
}
/// Find the first element in the array that equals a particular `value` and return its index.
/// If no element equals `value`, `-1` is returned.
///
/// The operator `==` is used to compare elements with `value` and must be defined,
/// otherwise `false` is assumed.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5];
///
/// print(x.index_of(4)); // prints 3 (first index)
///
/// print(x.index_of(9)); // prints -1
///
/// print(x.index_of("foo")); // prints -1: strings do not equal numbers
/// ```
#[rhai_fn(return_raw, pure)]
pub fn index_of(
ctx: NativeCallContext,
array: &mut Array,
value: Dynamic,
) -> RhaiResultOf<INT> {
if array.is_empty() {
Ok(-1)
} else {
index_of_starting_from(ctx, array, value, 0)
}
}
/// Find the first element in the array, starting from a particular `start` position, that
/// equals a particular `value` and return its index. If no element equals `value`, `-1` is returned.
///
/// * If `start` < 0, position counts from the end of the array (`-1` is the last element).
/// * If `start` < -length of array, position counts from the beginning of the array.
/// * If `start` ≥ length of array, `-1` is returned.
///
/// The operator `==` is used to compare elements with `value` and must be defined,
/// otherwise `false` is assumed.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5];
///
/// print(x.index_of(4, 2)); // prints 3
///
/// print(x.index_of(4, 5)); // prints 7
///
/// print(x.index_of(4, 15)); // prints -1: nothing found past end of array
///
/// print(x.index_of(4, -5)); // prints 11: -5 = start from index 8
///
/// print(x.index_of(9, 1)); // prints -1: nothing equals 9
///
/// print(x.index_of("foo", 1)); // prints -1: strings do not equal numbers
/// ```
#[rhai_fn(name = "index_of", return_raw, pure)]
pub fn index_of_starting_from(
ctx: NativeCallContext,
array: &mut Array,
value: Dynamic,
start: INT,
) -> RhaiResultOf<INT> {
if array.is_empty() {
return Ok(-1);
}
let (start, ..) = calc_offset_len(array.len(), start, 0);
for (i, item) in array.iter_mut().enumerate().skip(start) {
if ctx
.call_fn_raw(OP_EQUALS, true, false, &mut [item, &mut value.clone()])
.or_else(|err| match *err {
ERR::ErrorFunctionNotFound(ref fn_sig, ..) if fn_sig.starts_with(OP_EQUALS) => {
if item.type_id() == value.type_id() {
// No default when comparing same type
Err(err)
} else {
Ok(Dynamic::FALSE)
}
}
_ => Err(err),
})?
.as_bool()
.unwrap_or(false)
{
return Ok(i as INT);
}
}
Ok(-1 as INT)
}
/// Iterate through all the elements in the array, applying a `filter` function to each element
/// in turn, and return the index of the first element that returns `true`.
/// If no element returns `true`, `-1` is returned.
///
/// # Function Parameters
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5];
///
/// print(x.index_of(|v| v > 3)); // prints 3: 4 > 3
///
/// print(x.index_of(|v| v > 8)); // prints -1: nothing is > 8
///
/// print(x.index_of(|v, i| v * i > 20)); // prints 7: 4 * 7 > 20
/// ```
#[rhai_fn(name = "index_of", return_raw, pure)]
pub fn index_of_filter(
ctx: NativeCallContext,
array: &mut Array,
filter: FnPtr,
) -> RhaiResultOf<INT> {
if array.is_empty() {
Ok(-1)
} else {
index_of_filter_starting_from(ctx, array, filter, 0)
}
}
/// Iterate through all the elements in the array, applying a function named by `filter` to each
/// element in turn, and return the index of the first element that returns `true`.
/// If no element returns `true`, `-1` is returned.
///
/// # Function Parameters
///
/// A function with the same name as the value of `filter` must exist taking these parameters:
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn is_special(x) { x > 3 }
///
/// fn is_dumb(x) { x > 8 }
///
/// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5];
///
/// print(x.index_of("is_special")); // prints 3
///
/// print(x.index_of("is_dumb")); // prints -1
/// ```
#[rhai_fn(name = "index_of", return_raw, pure)]
pub fn index_of_by_fn_name(
ctx: NativeCallContext,
array: &mut Array,
filter: &str,
) -> RhaiResultOf<INT> {
index_of_filter(ctx, array, FnPtr::new(filter)?)
}
/// Iterate through all the elements in the array, starting from a particular `start` position,
/// applying a `filter` function to each element in turn, and return the index of the first
/// element that returns `true`. If no element returns `true`, `-1` is returned.
///
/// * If `start` < 0, position counts from the end of the array (`-1` is the last element).
/// * If `start` < -length of array, position counts from the beginning of the array.
/// * If `start` ≥ length of array, `-1` is returned.
///
/// # Function Parameters
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5];
///
/// print(x.index_of(|v| v > 1, 3)); // prints 5: 2 > 1
///
/// print(x.index_of(|v| v < 2, 9)); // prints -1: nothing < 2 past index 9
///
/// print(x.index_of(|v| v > 1, 15)); // prints -1: nothing found past end of array
///
/// print(x.index_of(|v| v > 1, -5)); // prints 9: -5 = start from index 8
///
/// print(x.index_of(|v| v > 1, -99)); // prints 1: -99 = start from beginning
///
/// print(x.index_of(|v, i| v * i > 20, 8)); // prints 10: 3 * 10 > 20
/// ```
#[rhai_fn(name = "index_of", return_raw, pure)]
pub fn index_of_filter_starting_from(
ctx: NativeCallContext,
array: &mut Array,
filter: FnPtr,
start: INT,
) -> RhaiResultOf<INT> {
if array.is_empty() {
return Ok(-1);
}
let (start, ..) = calc_offset_len(array.len(), start, 0);
for (i, item) in array.iter().enumerate().skip(start) {
if filter
.call_raw(&ctx, None, [item.clone()])
.or_else(|err| match *err {
ERR::ErrorFunctionNotFound(fn_sig, ..)
if fn_sig.starts_with(filter.fn_name()) =>
{
filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()])
}
_ => Err(err),
})
.map_err(|err| {
Box::new(ERR::ErrorInFunctionCall(
"index_of".to_string(),
ctx.source().unwrap_or("").to_string(),
err,
Position::NONE,
))
})?
.as_bool()
.unwrap_or(false)
{
return Ok(i as INT);
}
}
Ok(-1 as INT)
}
/// Iterate through all the elements in the array, starting from a particular `start` position,
/// applying a function named by `filter` to each element in turn, and return the index of the
/// first element that returns `true`. If no element returns `true`, `-1` is returned.
///
/// * If `start` < 0, position counts from the end of the array (`-1` is the last element).
/// * If `start` < -length of array, position counts from the beginning of the array.
/// * If `start` ≥ length of array, `-1` is returned.
///
/// # Function Parameters
///
/// A function with the same name as the value of `filter` must exist taking these parameters:
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn plural(x) { x > 1 }
///
/// fn singular(x) { x < 2 }
///
/// fn screen(x, i) { x * i > 20 }
///
/// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5];
///
/// print(x.index_of("plural", 3)); // prints 5: 2 > 1
///
/// print(x.index_of("singular", 9)); // prints -1: nothing < 2 past index 9
///
/// print(x.index_of("plural", 15)); // prints -1: nothing found past end of array
///
/// print(x.index_of("plural", -5)); // prints 9: -5 = start from index 8
///
/// print(x.index_of("plural", -99)); // prints 1: -99 = start from beginning
///
/// print(x.index_of("screen", 8)); // prints 10: 3 * 10 > 20
/// ```
#[rhai_fn(name = "index_of", return_raw, pure)]
pub fn index_of_by_fn_name_starting_from(
ctx: NativeCallContext,
array: &mut Array,
filter: &str,
start: INT,
) -> RhaiResultOf<INT> {
index_of_filter_starting_from(ctx, array, FnPtr::new(filter)?, start)
}
/// Return `true` if any element in the array that returns `true` when applied the `filter` function.
///
/// # Function Parameters
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5];
///
/// print(x.some(|v| v > 3)); // prints true
///
/// print(x.some(|v| v > 10)); // prints false
///
/// print(x.some(|v, i| i > v)); // prints true
/// ```
#[rhai_fn(return_raw, pure)]
pub fn some(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf<bool> {
if array.is_empty() {
return Ok(false);
}
for (i, item) in array.iter().enumerate() {
if filter
.call_raw(&ctx, None, [item.clone()])
.or_else(|err| match *err {
ERR::ErrorFunctionNotFound(fn_sig, ..)
if fn_sig.starts_with(filter.fn_name()) =>
{
filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()])
}
_ => Err(err),
})
.map_err(|err| {
Box::new(ERR::ErrorInFunctionCall(
"some".to_string(),
ctx.source().unwrap_or("").to_string(),
err,
Position::NONE,
))
})?
.as_bool()
.unwrap_or(false)
{
return Ok(true);
}
}
Ok(false)
}
/// Return `true` if any element in the array that returns `true` when applied a function named
/// by `filter`.
///
/// # Function Parameters
///
/// A function with the same name as the value of `filter` must exist taking these parameters:
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn large(x) { x > 3 }
///
/// fn huge(x) { x > 10 }
///
/// fn screen(x, i) { i > x }
///
/// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5];
///
/// print(x.some("large")); // prints true
///
/// print(x.some("huge")); // prints false
///
/// print(x.some("screen")); // prints true
/// ```
#[rhai_fn(name = "some", return_raw, pure)]
pub fn some_by_fn_name(
ctx: NativeCallContext,
array: &mut Array,
filter: &str,
) -> RhaiResultOf<bool> {
some(ctx, array, FnPtr::new(filter)?)
}
/// Return `true` if all elements in the array return `true` when applied the `filter` function.
///
/// # Function Parameters
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5];
///
/// print(x.all(|v| v > 3)); // prints false
///
/// print(x.all(|v| v > 1)); // prints true
///
/// print(x.all(|v, i| i > v)); // prints false
/// ```
#[rhai_fn(return_raw, pure)]
pub fn all(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf<bool> {
if array.is_empty() {
return Ok(true);
}
for (i, item) in array.iter().enumerate() {
if !filter
.call_raw(&ctx, None, [item.clone()])
.or_else(|err| match *err {
ERR::ErrorFunctionNotFound(fn_sig, ..)
if fn_sig.starts_with(filter.fn_name()) =>
{
filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()])
}
_ => Err(err),
})
.map_err(|err| {
Box::new(ERR::ErrorInFunctionCall(
"all".to_string(),
ctx.source().unwrap_or("").to_string(),
err,
Position::NONE,
))
})?
.as_bool()
.unwrap_or(false)
{
return Ok(false);
}
}
Ok(true)
}
/// Return `true` if all elements in the array return `true` when applied a function named by `filter`.
///
/// # Function Parameters
///
/// A function with the same name as the value of `filter` must exist taking these parameters:
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5];
///
/// print(x.all(|v| v > 3)); // prints false
///
/// print(x.all(|v| v > 1)); // prints true
///
/// print(x.all(|v, i| i > v)); // prints false
/// ```
#[rhai_fn(name = "all", return_raw, pure)]
pub fn all_by_fn_name(
ctx: NativeCallContext,
array: &mut Array,
filter: &str,
) -> RhaiResultOf<bool> {
all(ctx, array, FnPtr::new(filter)?)
}
/// Remove duplicated _consecutive_ elements from the array.
///
/// The operator `==` is used to compare elements and must be defined,
/// otherwise `false` is assumed.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 2, 2, 3, 4, 3, 3, 2, 1];
///
/// x.dedup();
///
/// print(x); // prints "[1, 2, 3, 4, 3, 2, 1]"
/// ```
#[rhai_fn(return_raw)]
pub fn dedup(ctx: NativeCallContext, array: &mut Array) -> RhaiResultOf<()> {
let comparer = FnPtr::new_unchecked(OP_EQUALS, StaticVec::new_const());
dedup_by_comparer(ctx, array, comparer)
}
/// Remove duplicated _consecutive_ elements from the array that return `true` when applied the
/// `comparer` function.
///
/// No element is removed if the correct `comparer` function does not exist.
///
/// # Function Parameters
///
/// * `element1`: copy of the current array element to compare
/// * `element2`: copy of the next array element to compare
///
/// ## Return Value
///
/// `true` if `element1 == element2`, otherwise `false`.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 2, 2, 3, 1, 2, 3, 4, 3, 3, 2, 1];
///
/// x.dedup(|a, b| a >= b);
///
/// print(x); // prints "[1, 2, 3, 4]"
/// ```
#[rhai_fn(name = "dedup", return_raw)]
pub fn dedup_by_comparer(
ctx: NativeCallContext,
array: &mut Array,
comparer: FnPtr,
) -> RhaiResultOf<()> {
if array.is_empty() {
return Ok(());
}
array.dedup_by(|x, y| {
comparer
.call_raw(&ctx, None, [y.clone(), x.clone()])
.unwrap_or_else(|_| Dynamic::FALSE)
.as_bool()
.unwrap_or(false)
});
Ok(())
}
/// Remove duplicated _consecutive_ elements from the array that return `true` when applied a
/// function named by `comparer`.
///
/// No element is removed if the correct `comparer` function does not exist.
///
/// # Function Parameters
///
/// * `element1`: copy of the current array element to compare
/// * `element2`: copy of the next array element to compare
///
/// ## Return Value
///
/// `true` if `element1 == element2`, otherwise `false`.
///
/// # Example
///
/// ```rhai
/// fn declining(a, b) { a >= b }
///
/// let x = [1, 2, 2, 2, 3, 1, 2, 3, 4, 3, 3, 2, 1];
///
/// x.dedup("declining");
///
/// print(x); // prints "[1, 2, 3, 4]"
/// ```
#[rhai_fn(name = "dedup", return_raw)]
pub fn dedup_by_fn_name(
ctx: NativeCallContext,
array: &mut Array,
comparer: &str,
) -> RhaiResultOf<()> {
dedup_by_comparer(ctx, array, FnPtr::new(comparer)?)
}
/// Reduce an array by iterating through all elements while applying the `reducer` function.
///
/// # Function Parameters
///
/// * `result`: accumulated result, initially `()`
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.reduce(|r, v| v + if r == () { 0 } else { r });
///
/// print(y); // prints 15
///
/// let y = x.reduce(|r, v, i| v + i + if r == () { 0 } else { r });
///
/// print(y); // prints 25
/// ```
#[rhai_fn(return_raw, pure)]
pub fn reduce(ctx: NativeCallContext, array: &mut Array, reducer: FnPtr) -> RhaiResult {
reduce_with_initial(ctx, array, reducer, Dynamic::UNIT)
}
/// Reduce an array by iterating through all elements while applying a function named by `reducer`.
///
/// # Function Parameters
///
/// A function with the same name as the value of `reducer` must exist taking these parameters:
///
/// * `result`: accumulated result, initially `()`
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn process(r, x) {
/// x + if r == () { 0 } else { r }
/// }
/// fn process_extra(r, x, i) {
/// x + i + if r == () { 0 } else { r }
/// }
///
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.reduce("process");
///
/// print(y); // prints 15
///
/// let y = x.reduce("process_extra");
///
/// print(y); // prints 25
/// ```
#[rhai_fn(name = "reduce", return_raw, pure)]
pub fn reduce_by_fn_name(
ctx: NativeCallContext,
array: &mut Array,
reducer: &str,
) -> RhaiResult {
reduce(ctx, array, FnPtr::new(reducer)?)
}
/// Reduce an array by iterating through all elements while applying the `reducer` function.
///
/// # Function Parameters
///
/// * `result`: accumulated result, starting with the value of `initial`
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.reduce(|r, v| v + r, 5);
///
/// print(y); // prints 20
///
/// let y = x.reduce(|r, v, i| v + i + r, 5);
///
/// print(y); // prints 30
/// ```
#[rhai_fn(name = "reduce", return_raw, pure)]
pub fn reduce_with_initial(
ctx: NativeCallContext,
array: &mut Array,
reducer: FnPtr,
initial: Dynamic,
) -> RhaiResult {
if array.is_empty() {
return Ok(initial);
}
let mut result = initial;
for (i, item) in array.iter().enumerate() {
let item = item.clone();
result = reducer
.call_raw(&ctx, None, [result.clone(), item.clone()])
.or_else(|err| match *err {
ERR::ErrorFunctionNotFound(fn_sig, ..)
if fn_sig.starts_with(reducer.fn_name()) =>
{
reducer.call_raw(&ctx, None, [result, item, (i as INT).into()])
}
_ => Err(err),
})
.map_err(|err| {
Box::new(ERR::ErrorInFunctionCall(
"reduce".to_string(),
ctx.source().unwrap_or("").to_string(),
err,
Position::NONE,
))
})?;
}
Ok(result)
}
/// Reduce an array by iterating through all elements while applying a function named by `reducer`.
///
/// # Function Parameters
///
/// A function with the same name as the value of `reducer` must exist taking these parameters:
///
/// * `result`: accumulated result, starting with the value of `initial`
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn process(r, x) { x + r }
///
/// fn process_extra(r, x, i) { x + i + r }
///
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.reduce("process", 5);
///
/// print(y); // prints 20
///
/// let y = x.reduce("process_extra", 5);
///
/// print(y); // prints 30
/// ```
#[rhai_fn(name = "reduce", return_raw, pure)]
pub fn reduce_by_fn_name_with_initial(
ctx: NativeCallContext,
array: &mut Array,
reducer: &str,
initial: Dynamic,
) -> RhaiResult {
reduce_with_initial(ctx, array, FnPtr::new(reducer)?, initial)
}
/// Reduce an array by iterating through all elements, in _reverse_ order,
/// while applying the `reducer` function.
///
/// # Function Parameters
///
/// * `result`: accumulated result, initially `()`
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.reduce_rev(|r, v| v + if r == () { 0 } else { r });
///
/// print(y); // prints 15
///
/// let y = x.reduce_rev(|r, v, i| v + i + if r == () { 0 } else { r });
///
/// print(y); // prints 25
/// ```
#[rhai_fn(return_raw, pure)]
pub fn reduce_rev(ctx: NativeCallContext, array: &mut Array, reducer: FnPtr) -> RhaiResult {
reduce_rev_with_initial(ctx, array, reducer, Dynamic::UNIT)
}
/// Reduce an array by iterating through all elements, in _reverse_ order,
/// while applying a function named by `reducer`.
///
/// # Function Parameters
///
/// A function with the same name as the value of `reducer` must exist taking these parameters:
///
/// * `result`: accumulated result, initially `()`
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn process(r, x) {
/// x + if r == () { 0 } else { r }
/// }
/// fn process_extra(r, x, i) {
/// x + i + if r == () { 0 } else { r }
/// }
///
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.reduce_rev("process");
///
/// print(y); // prints 15
///
/// let y = x.reduce_rev("process_extra");
///
/// print(y); // prints 25
/// ```
#[rhai_fn(name = "reduce_rev", return_raw, pure)]
pub fn reduce_rev_by_fn_name(
ctx: NativeCallContext,
array: &mut Array,
reducer: &str,
) -> RhaiResult {
reduce_rev(ctx, array, FnPtr::new(reducer)?)
}
/// Reduce an array by iterating through all elements, in _reverse_ order,
/// while applying the `reducer` function.
///
/// # Function Parameters
///
/// * `result`: accumulated result, starting with the value of `initial`
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.reduce_rev(|r, v| v + r, 5);
///
/// print(y); // prints 20
///
/// let y = x.reduce_rev(|r, v, i| v + i + r, 5);
///
/// print(y); // prints 30
/// ```
#[rhai_fn(name = "reduce_rev", return_raw, pure)]
pub fn reduce_rev_with_initial(
ctx: NativeCallContext,
array: &mut Array,
reducer: FnPtr,
initial: Dynamic,
) -> RhaiResult {
if array.is_empty() {
return Ok(initial);
}
let mut result = initial;
let len = array.len();
for (i, item) in array.iter().rev().enumerate() {
let item = item.clone();
result = reducer
.call_raw(&ctx, None, [result.clone(), item.clone()])
.or_else(|err| match *err {
ERR::ErrorFunctionNotFound(fn_sig, ..)
if fn_sig.starts_with(reducer.fn_name()) =>
{
reducer.call_raw(&ctx, None, [result, item, ((len - 1 - i) as INT).into()])
}
_ => Err(err),
})
.map_err(|err| {
Box::new(ERR::ErrorInFunctionCall(
"reduce_rev".to_string(),
ctx.source().unwrap_or("").to_string(),
err,
Position::NONE,
))
})?;
}
Ok(result)
}
/// Reduce an array by iterating through all elements, in _reverse_ order,
/// while applying a function named by `reducer`.
///
/// # Function Parameters
///
/// A function with the same name as the value of `reducer` must exist taking these parameters:
///
/// * `result`: accumulated result, starting with the value of `initial`
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn process(r, x) { x + r }
///
/// fn process_extra(r, x, i) { x + i + r }
///
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.reduce_rev("process", 5);
///
/// print(y); // prints 20
///
/// let y = x.reduce_rev("process_extra", 5);
///
/// print(y); // prints 30
/// ```
#[rhai_fn(name = "reduce_rev", return_raw, pure)]
pub fn reduce_rev_by_fn_name_with_initial(
ctx: NativeCallContext,
array: &mut Array,
reducer: &str,
initial: Dynamic,
) -> RhaiResult {
reduce_rev_with_initial(ctx, array, FnPtr::new(reducer)?, initial)
}
/// Sort the array based on applying the `comparer` function.
///
/// # Function Parameters
///
/// * `element1`: copy of the current array element to compare
/// * `element2`: copy of the next array element to compare
///
/// ## Return Value
///
/// * Any integer > 0 if `element1 > element2`
/// * Zero if `element1 == element2`
/// * Any integer < 0 if `element1 < element2`
///
/// # Example
///
/// ```rhai
/// let x = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10];
///
/// // Do comparisons in reverse
/// x.sort(|a, b| if a > b { -1 } else if a < b { 1 } else { 0 });
///
/// print(x); // prints "[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]"
/// ```
#[rhai_fn(return_raw)]
pub fn sort(ctx: NativeCallContext, array: &mut Array, comparer: FnPtr) -> RhaiResultOf<()> {
if array.len() <= 1 {
return Ok(());
}
array.sort_by(|x, y| {
comparer
.call_raw(&ctx, None, [x.clone(), y.clone()])
.ok()
.and_then(|v| v.as_int().ok())
.map(|v| match v {
v if v > 0 => Ordering::Greater,
v if v < 0 => Ordering::Less,
0 => Ordering::Equal,
_ => unreachable!("v is {}", v),
})
.unwrap_or_else(|| x.type_id().cmp(&y.type_id()))
});
Ok(())
}
/// Sort the array based on applying a function named by `comparer`.
///
/// # Function Parameters
///
/// A function with the same name as the value of `comparer` must exist taking these parameters:
///
/// * `element1`: copy of the current array element to compare
/// * `element2`: copy of the next array element to compare
///
/// ## Return Value
///
/// * Any integer > 0 if `element1 > element2`
/// * Zero if `element1 == element2`
/// * Any integer < 0 if `element1 < element2`
///
/// # Example
///
/// ```rhai
/// fn reverse(a, b) {
/// if a > b {
/// -1
/// } else if a < b {
/// 1
/// } else {
/// 0
/// }
/// }
/// let x = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10];
///
/// x.sort("reverse");
///
/// print(x); // prints "[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]"
/// ```
#[rhai_fn(name = "sort", return_raw)]
pub fn sort_by_fn_name(
ctx: NativeCallContext,
array: &mut Array,
comparer: &str,
) -> RhaiResultOf<()> {
sort(ctx, array, FnPtr::new(comparer)?)
}
/// Sort the array.
///
/// All elements in the array must be of the same data type.
///
/// # Supported Data Types
///
/// * integer numbers
/// * floating-point numbers
/// * decimal numbers
/// * characters
/// * strings
/// * booleans
/// * `()`
///
/// # Example
///
/// ```rhai
/// let x = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10];
///
/// x.sort();
///
/// print(x); // prints "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
/// ```
#[rhai_fn(name = "sort", return_raw)]
pub fn sort_with_builtin(array: &mut Array) -> RhaiResultOf<()> {
if array.len() <= 1 {
return Ok(());
}
let type_id = array[0].type_id();
if array.iter().any(|a| a.type_id() != type_id) {
return Err(ERR::ErrorFunctionNotFound(
"sort() cannot be called with elements of different types".into(),
Position::NONE,
)
.into());
}
if type_id == TypeId::of::<INT>() {
array.sort_by(|a, b| {
let a = a.as_int().expect("`INT`");
let b = b.as_int().expect("`INT`");
a.cmp(&b)
});
return Ok(());
}
if type_id == TypeId::of::<char>() {
array.sort_by(|a, b| {
let a = a.as_char().expect("char");
let b = b.as_char().expect("char");
a.cmp(&b)
});
return Ok(());
}
#[cfg(not(feature = "no_float"))]
if type_id == TypeId::of::<crate::FLOAT>() {
array.sort_by(|a, b| {
let a = a.as_float().expect("`FLOAT`");
let b = b.as_float().expect("`FLOAT`");
a.partial_cmp(&b).unwrap_or(Ordering::Equal)
});
return Ok(());
}
if type_id == TypeId::of::<ImmutableString>() {
array.sort_by(|a, b| {
let a = a.read_lock::<ImmutableString>().expect("`ImmutableString`");
let b = b.read_lock::<ImmutableString>().expect("`ImmutableString`");
a.as_str().cmp(b.as_str())
});
return Ok(());
}
#[cfg(feature = "decimal")]
if type_id == TypeId::of::<rust_decimal::Decimal>() {
array.sort_by(|a, b| {
let a = a.as_decimal().expect("`Decimal`");
let b = b.as_decimal().expect("`Decimal`");
a.cmp(&b)
});
return Ok(());
}
if type_id == TypeId::of::<bool>() {
array.sort_by(|a, b| {
let a = a.as_bool().expect("`bool`");
let b = b.as_bool().expect("`bool`");
a.cmp(&b)
});
return Ok(());
}
if type_id == TypeId::of::<()>() {
return Ok(());
}
Ok(())
}
/// Remove all elements in the array that returns `true` when applied the `filter` function and
/// return them as a new array.
///
/// # Function Parameters
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.drain(|v| v < 3);
///
/// print(x); // prints "[3, 4, 5]"
///
/// print(y); // prints "[1, 2]"
///
/// let z = x.drain(|v, i| v + i > 5);
///
/// print(x); // prints "[3, 4]"
///
/// print(z); // prints "[5]"
/// ```
#[rhai_fn(return_raw)]
pub fn drain(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf<Array> {
if array.is_empty() {
return Ok(Array::new());
}
let mut drained = Array::with_capacity(array.len());
let mut i = 0;
let mut x = 0;
while x < array.len() {
if filter
.call_raw(&ctx, None, [array[x].clone()])
.or_else(|err| match *err {
ERR::ErrorFunctionNotFound(fn_sig, ..)
if fn_sig.starts_with(filter.fn_name()) =>
{
filter.call_raw(&ctx, None, [array[x].clone(), (i as INT).into()])
}
_ => Err(err),
})
.map_err(|err| {
Box::new(ERR::ErrorInFunctionCall(
"drain".to_string(),
ctx.source().unwrap_or("").to_string(),
err,
Position::NONE,
))
})?
.as_bool()
.unwrap_or(false)
{
drained.push(array.remove(x));
} else {
x += 1;
}
i += 1;
}
Ok(drained)
}
/// Remove all elements in the array that returns `true` when applied a function named by `filter`
/// and return them as a new array.
///
/// # Function Parameters
///
/// A function with the same name as the value of `filter` must exist taking these parameters:
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn small(x) { x < 3 }
///
/// fn screen(x, i) { x + i > 5 }
///
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.drain("small");
///
/// print(x); // prints "[3, 4, 5]"
///
/// print(y); // prints "[1, 2]"
///
/// let z = x.drain("screen");
///
/// print(x); // prints "[3, 4]"
///
/// print(z); // prints "[5]"
/// ```
#[rhai_fn(name = "drain", return_raw)]
pub fn drain_by_fn_name(
ctx: NativeCallContext,
array: &mut Array,
filter: &str,
) -> RhaiResultOf<Array> {
drain(ctx, array, FnPtr::new(filter)?)
}
/// Remove all elements in the array within an exclusive `range` and return them as a new array.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.drain(1..3);
///
/// print(x); // prints "[1, 4, 5]"
///
/// print(y); // prints "[2, 3]"
///
/// let z = x.drain(2..3);
///
/// print(x); // prints "[1, 4]"
///
/// print(z); // prints "[5]"
/// ```
#[rhai_fn(name = "drain")]
pub fn drain_exclusive_range(array: &mut Array, range: ExclusiveRange) -> Array {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
drain_range(array, start, end - start)
}
/// Remove all elements in the array within an inclusive `range` and return them as a new array.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.drain(1..=2);
///
/// print(x); // prints "[1, 4, 5]"
///
/// print(y); // prints "[2, 3]"
///
/// let z = x.drain(2..=2);
///
/// print(x); // prints "[1, 4]"
///
/// print(z); // prints "[5]"
/// ```
#[rhai_fn(name = "drain")]
pub fn drain_inclusive_range(array: &mut Array, range: InclusiveRange) -> Array {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
drain_range(array, start, end - start + 1)
}
/// Remove all elements within a portion of the array and return them as a new array.
///
/// * If `start` < 0, position counts from the end of the array (`-1` is the last element).
/// * If `start` < -length of array, position counts from the beginning of the array.
/// * If `start` ≥ length of array, no element is removed and an empty array is returned.
/// * If `len` ≤ 0, no element is removed and an empty array is returned.
/// * If `start` position + `len` ≥ length of array, entire portion of the array after the `start` position is removed and returned.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.drain(1, 2);
///
/// print(x); // prints "[1, 4, 5]"
///
/// print(y); // prints "[2, 3]"
///
/// let z = x.drain(-1, 1);
///
/// print(x); // prints "[1, 4]"
///
/// print(z); // prints "[5]"
/// ```
#[rhai_fn(name = "drain")]
pub fn drain_range(array: &mut Array, start: INT, len: INT) -> Array {
if array.is_empty() || len <= 0 {
return Array::new();
}
let (start, len) = calc_offset_len(array.len(), start, len);
if len == 0 {
Array::new()
} else {
array.drain(start..start + len).collect()
}
}
/// Remove all elements in the array that do not return `true` when applied the `filter`
/// function and return them as a new array.
///
/// # Function Parameters
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.retain(|v| v >= 3);
///
/// print(x); // prints "[3, 4, 5]"
///
/// print(y); // prints "[1, 2]"
///
/// let z = x.retain(|v, i| v + i <= 5);
///
/// print(x); // prints "[3, 4]"
///
/// print(z); // prints "[5]"
/// ```
#[rhai_fn(return_raw)]
pub fn retain(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf<Array> {
if array.is_empty() {
return Ok(Array::new());
}
let mut drained = Array::new();
let mut i = 0;
let mut x = 0;
while x < array.len() {
if !filter
.call_raw(&ctx, None, [array[x].clone()])
.or_else(|err| match *err {
ERR::ErrorFunctionNotFound(fn_sig, ..)
if fn_sig.starts_with(filter.fn_name()) =>
{
filter.call_raw(&ctx, None, [array[x].clone(), (i as INT).into()])
}
_ => Err(err),
})
.map_err(|err| {
Box::new(ERR::ErrorInFunctionCall(
"retain".to_string(),
ctx.source().unwrap_or("").to_string(),
err,
Position::NONE,
))
})?
.as_bool()
.unwrap_or(false)
{
drained.push(array.remove(x));
} else {
x += 1;
}
i += 1;
}
Ok(drained)
}
/// Remove all elements in the array that do not return `true` when applied a function named by
/// `filter` and return them as a new array.
///
/// # Function Parameters
///
/// A function with the same name as the value of `filter` must exist taking these parameters:
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn large(x) { x >= 3 }
///
/// fn screen(x, i) { x + i <= 5 }
///
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.retain("large");
///
/// print(x); // prints "[3, 4, 5]"
///
/// print(y); // prints "[1, 2]"
///
/// let z = x.retain("screen");
///
/// print(x); // prints "[3, 4]"
///
/// print(z); // prints "[5]"
/// ```
#[rhai_fn(name = "retain", return_raw)]
pub fn retain_by_fn_name(
ctx: NativeCallContext,
array: &mut Array,
filter: &str,
) -> RhaiResultOf<Array> {
retain(ctx, array, FnPtr::new(filter)?)
}
/// Remove all elements in the array not within an exclusive `range` and return them as a new array.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.retain(1..4);
///
/// print(x); // prints "[2, 3, 4]"
///
/// print(y); // prints "[1, 5]"
///
/// let z = x.retain(1..3);
///
/// print(x); // prints "[3, 4]"
///
/// print(z); // prints "[1]"
/// ```
#[rhai_fn(name = "retain")]
pub fn retain_exclusive_range(array: &mut Array, range: ExclusiveRange) -> Array {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
retain_range(array, start, end - start)
}
/// Remove all elements in the array not within an inclusive `range` and return them as a new array.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.retain(1..=3);
///
/// print(x); // prints "[2, 3, 4]"
///
/// print(y); // prints "[1, 5]"
///
/// let z = x.retain(1..=2);
///
/// print(x); // prints "[3, 4]"
///
/// print(z); // prints "[1]"
/// ```
#[rhai_fn(name = "retain")]
pub fn retain_inclusive_range(array: &mut Array, range: InclusiveRange) -> Array {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
retain_range(array, start, end - start + 1)
}
/// Remove all elements not within a portion of the array and return them as a new array.
///
/// * If `start` < 0, position counts from the end of the array (`-1` is the last element).
/// * If `start` < -length of array, position counts from the beginning of the array.
/// * If `start` ≥ length of array, all elements are removed returned.
/// * If `len` ≤ 0, all elements are removed and returned.
/// * If `start` position + `len` ≥ length of array, entire portion of the array before the `start` position is removed and returned.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.retain(1, 2);
///
/// print(x); // prints "[2, 3]"
///
/// print(y); // prints "[1, 4, 5]"
///
/// let z = x.retain(-1, 1);
///
/// print(x); // prints "[3]"
///
/// print(z); // prints "[2]"
/// ```
#[rhai_fn(name = "retain")]
pub fn retain_range(array: &mut Array, start: INT, len: INT) -> Array {
if array.is_empty() || len <= 0 {
return Array::new();
}
let (start, len) = calc_offset_len(array.len(), start, len);
if len == 0 {
Array::new()
} else {
let mut drained: Array = array.drain(..start).collect();
drained.extend(array.drain(len..));
drained
}
}
/// Return `true` if two arrays are equal (i.e. all elements are equal and in the same order).
///
/// The operator `==` is used to compare elements and must be defined,
/// otherwise `false` is assumed.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
/// let y = [1, 2, 3, 4, 5];
/// let z = [1, 2, 3, 4];
///
/// print(x == y); // prints true
///
/// print(x == z); // prints false
/// ```
#[rhai_fn(name = "==", return_raw, pure)]
pub fn equals(ctx: NativeCallContext, array1: &mut Array, array2: Array) -> RhaiResultOf<bool> {
if array1.len() != array2.len() {
return Ok(false);
}
if array1.is_empty() {
return Ok(true);
}
let mut array2 = array2;
for (a1, a2) in array1.iter_mut().zip(array2.iter_mut()) {
if !ctx
.call_fn_raw(OP_EQUALS, true, false, &mut [a1, a2])
.or_else(|err| match *err {
ERR::ErrorFunctionNotFound(ref fn_sig, ..) if fn_sig.starts_with(OP_EQUALS) => {
if a1.type_id() == a2.type_id() {
// No default when comparing same type
Err(err)
} else {
Ok(Dynamic::FALSE)
}
}
_ => Err(err),
})?
.as_bool()
.unwrap_or(false)
{
return Ok(false);
}
}
Ok(true)
}
/// Return `true` if two arrays are not-equal (i.e. any element not equal or not in the same order).
///
/// The operator `==` is used to compare elements and must be defined,
/// otherwise `false` is assumed.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
/// let y = [1, 2, 3, 4, 5];
/// let z = [1, 2, 3, 4];
///
/// print(x != y); // prints false
///
/// print(x != z); // prints true
/// ```
#[rhai_fn(name = "!=", return_raw, pure)]
pub fn not_equals(
ctx: NativeCallContext,
array1: &mut Array,
array2: Array,
) -> RhaiResultOf<bool> {
equals(ctx, array1, array2).map(|r| !r)
}
}