rhai/src/packages/array_basic.rs
2023-03-08 21:47:57 +08:00

1960 lines
60 KiB
Rust

#![cfg(not(feature = "no_index"))]
use crate::api::deprecated::deprecated_array_functions;
use crate::engine::OP_EQUALS;
use crate::eval::{calc_index, calc_offset_len, calc_array_sizes};
use crate::module::ModuleFlags;
use crate::plugin::*;
use crate::{
def_package, Array, Dynamic, ExclusiveRange, FnPtr, InclusiveRange, NativeCallContext,
Position, RhaiResultOf, StaticVec, ERR, INT, MAX_USIZE_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.flags |= ModuleFlags::STANDARD_LIB;
combine_with_exported_module!(lib, "array", array_functions);
combine_with_exported_module!(lib, "deprecated_array", deprecated_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
}
/// Return true if the array is empty.
#[rhai_fn(name = "is_empty", get = "is_empty", pure)]
pub fn is_empty(array: &mut Array) -> bool {
array.len() == 0
}
/// 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.append(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() {
array1
} else if array1.is_empty() {
array2
} else {
let mut array = array1;
array.extend(array2);
array
}
}
/// 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 {
return Ok(());
}
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let len = len.min(MAX_USIZE_INT) as usize;
if len <= array.len() {
return Ok(());
}
let _ctx = ctx;
// Check if array will be over max size limit
#[cfg(not(feature = "unchecked"))]
if _ctx.engine().max_array_size() > 0 {
let pad = len - array.len();
let (a, m, s) = calc_array_sizes(array);
let (ax, mx, sx) = item.calc_data_sizes(true);
_ctx.engine()
.throw_on_size((a + pad + ax * pad, m + mx * pad, s + sx * pad))?;
}
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(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 len <= 0 {
array.clear();
return;
}
if !array.is_empty() {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let len = len.min(MAX_USIZE_INT) as usize;
if len > 0 {
array.truncate(len);
} 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 len <= 0 {
array.clear();
return;
}
if !array.is_empty() {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let len = len.min(MAX_USIZE_INT) as usize;
if len <= 0 {
array.clear();
} else if len < array.len() {
array.drain(0..array.len() - len);
}
}
}
/// 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 `process` function to each element in turn.
/// Each element is bound to `this` before calling the function.
///
/// # Function Parameters
///
/// * `this`: bound to array element (mutable)
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// x.for_each(|| this *= this);
///
/// print(x); // prints "[1, 4, 9, 16, 25]"
///
/// x.for_each(|i| this *= i);
///
/// print(x); // prints "[0, 2, 6, 12, 20]"
/// ```
#[rhai_fn(return_raw)]
pub fn for_each(ctx: NativeCallContext, array: &mut Array, map: FnPtr) -> RhaiResultOf<()> {
if array.is_empty() {
return Ok(());
}
for (i, item) in array.iter_mut().enumerate() {
let ex = [(i as INT).into()];
let _ = map.call_raw_with_extra_args("map", &ctx, Some(item), [], ex, None)?;
}
Ok(())
}
/// 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.
///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// This method is marked _pure_; the `mapper` function should not mutate array elements.
///
/// # 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, map: FnPtr) -> RhaiResultOf<Array> {
if array.is_empty() {
return Ok(Array::new());
}
let mut ar = Array::with_capacity(array.len());
for (i, item) in array.iter_mut().enumerate() {
let ex = [(i as INT).into()];
ar.push(map.call_raw_with_extra_args("map", &ctx, Some(item), [], ex, Some(0))?);
}
Ok(ar)
}
/// 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.
///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// This method is marked _pure_; the `filter` function should not mutate array elements.
///
/// # 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::new());
}
let mut ar = Array::new();
for (i, item) in array.iter_mut().enumerate() {
let ex = [(i as INT).into()];
if filter
.call_raw_with_extra_args("filter", &ctx, Some(item), [], ex, Some(0))?
.as_bool()
.unwrap_or(false)
{
ar.push(item.clone());
}
}
Ok(ar)
}
/// 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 {
if ctx
.call_native_fn_raw(OP_EQUALS, true, &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_native_fn_raw(OP_EQUALS, true, &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.
///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// This method is marked _pure_; the `filter` function should not mutate array elements.
///
/// # 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, 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.
///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// This method is marked _pure_; the `filter` function should not mutate array elements.
///
/// # 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_mut().enumerate().skip(start) {
let ex = [(i as INT).into()];
if filter
.call_raw_with_extra_args("index_of", &ctx, Some(item), [], ex, Some(0))?
.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 a copy of the first element that returns `true`. If no element returns
/// `true`, `()` is returned.
///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// # Function Parameters
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 5, 8, 13];
///
/// print(x.find(|v| v > 3)); // prints 5: 5 > 3
///
/// print(x.find(|v| v > 13) ?? "not found"); // prints "not found": nothing is > 13
///
/// print(x.find(|v, i| v * i > 13)); // prints 5: 3 * 5 > 13
/// ```
#[rhai_fn(return_raw, pure)]
pub fn find(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResult {
find_starting_from(ctx, array, filter, 0)
}
/// 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 a copy of the first element
/// that returns `true`. If no element returns `true`, `()` 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.
///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// This method is marked _pure_; the `filter` function should not mutate array elements.
///
/// # Function Parameters
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 5, 8, 13];
///
/// print(x.find(|v| v > 1, 2)); // prints 3: 3 > 1
///
/// print(x.find(|v| v < 2, 3) ?? "not found"); // prints "not found": nothing < 2 past index 3
///
/// print(x.find(|v| v > 1, 8) ?? "not found"); // prints "not found": nothing found past end of array
///
/// print(x.find(|v| v > 1, -3)); // prints 5: -3 = start from index 4
///
/// print(x.find(|v| v > 0, -99)); // prints 1: -99 = start from beginning
///
/// print(x.find(|v, i| v * i > 6, 3)); // prints 5: 5 * 4 > 6
/// ```
#[rhai_fn(name = "find", return_raw, pure)]
pub fn find_starting_from(
ctx: NativeCallContext,
array: &mut Array,
filter: FnPtr,
start: INT,
) -> RhaiResult {
let index = index_of_filter_starting_from(ctx, array, filter, start)?;
if index < 0 {
return Ok(Dynamic::UNIT);
}
Ok(get(array, index))
}
/// Iterate through all the elements in the array, applying a `mapper` function to each element
/// in turn, and return the first result that is not `()`. Otherwise, `()` is returned.
///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// This method is marked _pure_; the `mapper` function should not mutate array elements.
///
/// # Function Parameters
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// let x = [#{alice: 1}, #{bob: 2}, #{clara: 3}];
///
/// print(x.find_map(|v| v.alice)); // prints 1
///
/// print(x.find_map(|v| v.dave) ?? "not found"); // prints "not found"
///
/// print(x.find_map(|| this.dave) ?? "not found"); // prints "not found"
/// ```
#[rhai_fn(return_raw, pure)]
pub fn find_map(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResult {
find_map_starting_from(ctx, array, filter, 0)
}
/// Iterate through all the elements in the array, starting from a particular `start` position,
/// applying a `mapper` function to each element in turn, and return the first result that is not `()`.
/// Otherwise, `()` 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.
///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// This method is marked _pure_; the `mapper` function should not mutate array elements.
///
/// # Function Parameters
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// let x = [#{alice: 1}, #{bob: 2}, #{bob: 3}, #{clara: 3}, #{alice: 0}, #{clara: 5}];
///
/// print(x.find_map(|v| v.alice, 2)); // prints 0
///
/// print(x.find_map(|v| v.bob, 4) ?? "not found"); // prints "not found"
///
/// print(x.find_map(|v| v.alice, 8) ?? "not found"); // prints "not found"
///
/// print(x.find_map(|| this.alice, 8) ?? "not found"); // prints "not found"
///
/// print(x.find_map(|v| v.bob, -4)); // prints 3: -4 = start from index 2
///
/// print(x.find_map(|v| v.alice, -99)); // prints 1: -99 = start from beginning
///
/// print(x.find_map(|| this.alice, -99)); // prints 1: -99 = start from beginning
/// ```
#[rhai_fn(name = "find_map", return_raw, pure)]
pub fn find_map_starting_from(
ctx: NativeCallContext,
array: &mut Array,
filter: FnPtr,
start: INT,
) -> RhaiResult {
if array.is_empty() {
return Ok(Dynamic::UNIT);
}
let (start, ..) = calc_offset_len(array.len(), start, 0);
for (i, item) in array.iter_mut().enumerate().skip(start) {
let ex = [(i as INT).into()];
let value =
filter.call_raw_with_extra_args("find_map", &ctx, Some(item), [], ex, Some(0))?;
if !value.is_unit() {
return Ok(value);
}
}
Ok(Dynamic::UNIT)
}
/// Return `true` if any element in the array that returns `true` when applied the `filter` function.
///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// This method is marked _pure_; the `filter` function should not mutate array elements.
///
/// # 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_mut().enumerate() {
let ex = [(i as INT).into()];
if filter
.call_raw_with_extra_args("some", &ctx, Some(item), [], ex, Some(0))?
.as_bool()
.unwrap_or(false)
{
return Ok(true);
}
}
Ok(false)
}
/// Return `true` if all elements in the array return `true` when applied the `filter` function.
///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// This method is marked _pure_; the `filter` function should not mutate array elements.
///
/// # 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_mut().enumerate() {
let ex = [(i as INT).into()];
if !filter
.call_raw_with_extra_args("all", &ctx, Some(item), [], ex, Some(0))?
.as_bool()
.unwrap_or(false)
{
return Ok(false);
}
}
Ok(true)
}
/// 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]"
/// ```
pub fn dedup(ctx: NativeCallContext, array: &mut Array) {
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")]
pub fn dedup_by_comparer(ctx: NativeCallContext, array: &mut Array, comparer: FnPtr) {
if array.is_empty() {
return;
}
array.dedup_by(|x, y| {
comparer
.call_raw(&ctx, None, [y.clone(), x.clone()])
.unwrap_or(Dynamic::FALSE)
.as_bool()
.unwrap_or(false)
});
}
/// Reduce an array by iterating through all elements while applying the `reducer` function.
///
/// # Function Parameters
///
/// * `result`: accumulated result, initially `()`
/// * `element`: copy of array element, or bound to `this` if omitted
/// * `index` _(optional)_: current index in the array
///
/// This method is marked _pure_; the `reducer` function should not mutate array elements.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.reduce(|r, v| v + (r ?? 0));
///
/// print(y); // prints 15
///
/// let y = x.reduce(|r, v, i| v + i + (r ?? 0));
///
/// 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 the `reducer` function.
///
/// # Function Parameters
///
/// * `result`: accumulated result, starting with the value of `initial`
/// * `element`: copy of array element, or bound to `this` if omitted
/// * `index` _(optional)_: current index in the array
///
/// This method is marked _pure_; the `reducer` function should not mutate array elements.
///
/// # 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);
}
array
.iter_mut()
.enumerate()
.try_fold(initial, |result, (i, item)| {
let ex = [(i as INT).into()];
reducer.call_raw_with_extra_args("reduce", &ctx, Some(item), [result], ex, Some(1))
})
}
/// 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, or bound to `this` if omitted
/// * `index` _(optional)_: current index in the array
///
/// This method is marked _pure_; the `reducer` function should not mutate array elements.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.reduce_rev(|r, v| v + (r ?? 0));
///
/// print(y); // prints 15
///
/// let y = x.reduce_rev(|r, v, i| v + i + (r ?? 0));
///
/// 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 the `reducer` function.
///
/// # Function Parameters
///
/// * `result`: accumulated result, starting with the value of `initial`
/// * `element`: copy of array element, or bound to `this` if omitted
/// * `index` _(optional)_: current index in the array
///
/// This method is marked _pure_; the `reducer` function should not mutate array elements.
///
/// # 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 len = array.len();
array
.iter_mut()
.rev()
.enumerate()
.try_fold(initial, |result, (i, item)| {
let ex = [((len - 1 - i) as INT).into()];
reducer.call_raw_with_extra_args(
"reduce_rev",
&ctx,
Some(item),
[result],
ex,
Some(1),
)
})
}
/// 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]"
/// ```
pub fn sort(ctx: NativeCallContext, array: &mut Array, comparer: FnPtr) {
if array.len() <= 1 {
return;
}
array.sort_by(|x, y| {
comparer
.call_raw(&ctx, None, [x.clone(), y.clone()])
.ok()
.and_then(|v| v.as_int().ok())
.map_or_else(
|| x.type_id().cmp(&y.type_id()),
|v| match v {
v if v > 0 => Ordering::Greater,
v if v < 0 => Ordering::Less,
0 => Ordering::Equal,
_ => unreachable!("v is {}", v),
},
)
});
}
/// 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.
///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// # 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() {
let ex = [(i as INT).into()];
if filter
.call_raw_with_extra_args("drain", &ctx, Some(&mut array[x]), [], ex, Some(0))?
.as_bool()
.unwrap_or(false)
{
drained.push(array.remove(x));
} else {
x += 1;
}
i += 1;
}
Ok(drained)
}
/// 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.
///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// # 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() {
let ex = [(i as INT).into()];
if filter
.call_raw_with_extra_args("retain", &ctx, Some(&mut array[x]), [], ex, Some(0))?
.as_bool()
.unwrap_or(false)
{
x += 1;
} else {
drained.push(array.remove(x));
}
i += 1;
}
Ok(drained)
}
/// 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_native_fn_raw(OP_EQUALS, true, &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)
}
}