From 36677613409e915e12363dd3a3b9ac24f7080a9c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 15 Jan 2022 23:34:38 +0800 Subject: [PATCH] Fix bugs and add comments to standard library. --- CHANGELOG.md | 4 + src/packages/arithmetic.rs | 33 + src/packages/array_basic.rs | 1413 ++++++++++++++++++++++++++++++++-- src/packages/bit_field.rs | 115 ++- src/packages/blob_basic.rs | 698 ++++++++++++++--- src/packages/fn_basic.rs | 128 +-- src/packages/iter_basic.rs | 292 ++++++- src/packages/lang_core.rs | 108 +++ src/packages/map_basic.rs | 106 +++ src/packages/math_basic.rs | 190 ++++- src/packages/string_basic.rs | 72 +- src/packages/string_more.rs | 562 +++++++++++++- src/packages/time_basic.rs | 17 + 13 files changed, 3375 insertions(+), 363 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79d1bf39..0d99ecf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,12 +12,16 @@ Bug fixes * `set_bit` for bit-flags with negative index now works correctly. * Misnamed `params` field `name` in the JSON output of `Engine::gen_fn_metadata_to_json` is fixed (was incorrectly named `type`). * Fixes a potential `unsafe` violation in `for` loop. +* Missing `to_hex`, `to_octal` and `to_binary` for `i128` and `u128` are added. +* `remove` for arrays and BLOB's now treat negative index correctly. +* `parse_int` now works properly for negative numbers. Enhancements ------------ * Formatting of return types in functions metadata info is improved. * Use `SmartString` for `Scope` variable names and remove `unsafe` lifetime casting. +* Functions in the standard library now have doc-comments (which can be obtained via `Engine::gen_fn_metadata_to_json`). Version 1.4.0 diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 7d135620..8eae8764 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -121,14 +121,17 @@ macro_rules! gen_arithmetic_functions { pub fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type { x ^ y } + /// Return true if the number is zero. #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: $arg_type) -> bool { x == 0 } + /// Return true if the number is odd. #[rhai_fn(get = "is_odd", name = "is_odd")] pub fn is_odd(x: $arg_type) -> bool { x % 2 != 0 } + /// Return true if the number is even. #[rhai_fn(get = "is_even", name = "is_even")] pub fn is_even(x: $arg_type) -> bool { x % 2 == 0 @@ -157,6 +160,7 @@ macro_rules! gen_signed_functions { pub fn plus(x: $arg_type) -> $arg_type { x } + /// Return the absolute value of the number. #[rhai_fn(return_raw)] pub fn abs(x: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { @@ -165,6 +169,11 @@ macro_rules! gen_signed_functions { Ok(x.abs()) } } + /// Return the sign (as an integer) of the number according to the following: + /// + /// * `0` if the number is zero + /// * `1` if the number is positive + /// * `-1` if the number is negative pub fn sign(x: $arg_type) -> INT { x.signum() as INT } @@ -216,14 +225,17 @@ def_package! { #[export_module] mod int_functions { + /// Return true if the number is zero. #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: INT) -> bool { x == 0 } + /// Return true if the number is odd. #[rhai_fn(get = "is_odd", name = "is_odd")] pub fn is_odd(x: INT) -> bool { x % 2 != 0 } + /// Return true if the number is even. #[rhai_fn(get = "is_even", name = "is_even")] pub fn is_even(x: INT) -> bool { x % 2 == 0 @@ -334,9 +346,15 @@ mod f32_functions { pub fn plus(x: f32) -> f32 { x } + /// Return the absolute value of the floating-point number. pub fn abs(x: f32) -> f32 { x.abs() } + /// Return the sign (as an integer) of the floating-point number according to the following: + /// + /// * `0` if the number is zero + /// * `1` if the number is positive + /// * `-1` if the number is negative #[rhai_fn(return_raw)] pub fn sign(x: f32) -> RhaiResultOf { match x.signum() { @@ -345,6 +363,7 @@ mod f32_functions { x => Ok(x as INT), } } + /// Return true if the floating-point number is zero. #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: f32) -> bool { x == 0.0 @@ -442,9 +461,15 @@ mod f64_functions { pub fn plus(x: f64) -> f64 { x } + /// Return the absolute value of the floating-point number. pub fn abs(x: f64) -> f64 { x.abs() } + /// Return the sign (as an integer) of the floating-point number according to the following: + /// + /// * `0` if the number is zero + /// * `1` if the number is positive + /// * `-1` if the number is negative #[rhai_fn(return_raw)] pub fn sign(x: f64) -> RhaiResultOf { match x.signum() { @@ -453,6 +478,7 @@ mod f64_functions { x => Ok(x as INT), } } + /// Return true if the floating-point number is zero. #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: f64) -> bool { x == 0.0 @@ -536,9 +562,15 @@ pub mod decimal_functions { pub fn plus(x: Decimal) -> Decimal { x } + /// Return the absolute value of the decimal number. pub fn abs(x: Decimal) -> Decimal { x.abs() } + /// Return the sign (as an integer) of the decimal number according to the following: + /// + /// * `0` if the number is zero + /// * `1` if the number is positive + /// * `-1` if the number is negative pub fn sign(x: Decimal) -> INT { if x == Decimal::zero() { 0 @@ -548,6 +580,7 @@ pub mod decimal_functions { 1 } } + /// Return true if the decimal number is zero. #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: Decimal) -> bool { x.is_zero() diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 007a7fdc..528a0a32 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -2,11 +2,11 @@ #![allow(non_snake_case)] use crate::engine::OP_EQUALS; -use crate::eval::calc_offset_len; +use crate::eval::{calc_index, calc_offset_len}; use crate::plugin::*; use crate::{ - def_package, Array, Dynamic, ExclusiveRange, FnPtr, InclusiveRange, NativeCallContext, - Position, RhaiResultOf, ERR, INT, + def_package, Array, Dynamic, Engine, ExclusiveRange, FnPtr, InclusiveRange, NativeCallContext, + Position, RhaiResultOf, StaticVec, ERR, INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -26,22 +26,60 @@ def_package! { #[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 } + /// 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); } - pub fn append(array1: &mut Array, array2: Array) { - if !array2.is_empty() { - if array1.is_empty() { - *array1 = array2; + /// 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 { - array1.extend(array2); + 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() { @@ -56,6 +94,25 @@ pub mod array_functions { 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); @@ -70,6 +127,23 @@ pub mod array_functions { 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, @@ -86,55 +160,68 @@ pub mod array_functions { // 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()); - } + { + 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(), + ); + } - #[cfg(not(feature = "unchecked"))] - 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, - }; - #[cfg(feature = "unchecked")] - let check_sizes = false; + 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)); + if check_sizes { + let mut arr_len = array.len(); + let mut arr = Dynamic::from_array(mem::take(array)); - #[cfg(not(feature = "unchecked"))] - let (mut a1, mut m1, mut s1) = Engine::calc_data_sizes(&arr, true); - #[cfg(not(feature = "unchecked"))] - let (a2, m2, s2) = Engine::calc_data_sizes(&item, true); + let (mut a1, mut m1, mut s1) = Engine::calc_data_sizes(&arr, true); + let (a2, m2, s2) = Engine::calc_data_sizes(&item, true); - { - let mut guard = arr.write_lock::().unwrap(); + { + let mut guard = arr.write_lock::().unwrap(); - while arr_len < len { - #[cfg(not(feature = "unchecked"))] - { + 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); + 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 @@ -142,6 +229,19 @@ pub mod array_functions { 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 @@ -149,27 +249,84 @@ pub mod array_functions { 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 { - if index < 0 || (index as usize) >= array.len() { - Dynamic::UNIT - } else { - array.remove(index as usize) - } + 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 { + 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 { @@ -179,23 +336,80 @@ pub mod array_functions { } } } + /// 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; @@ -210,18 +424,59 @@ pub mod array_functions { 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(); @@ -235,17 +490,53 @@ pub mod array_functions { 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, start: INT) -> Array { + 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(), start, INT::MAX); + let (start, len) = calc_offset_len(array.len(), index, INT::MAX); if start == 0 { if len >= array.len() { @@ -263,6 +554,27 @@ pub mod array_functions { 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 { if array.is_empty() { @@ -296,8 +608,35 @@ pub mod array_functions { 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_with_fn_name( + pub fn map_by_fn_name( ctx: NativeCallContext, array: &mut Array, mapper: &str, @@ -305,6 +644,27 @@ pub mod array_functions { 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 { if array.is_empty() { @@ -341,14 +701,56 @@ pub mod array_functions { 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_with_fn_name( + pub fn filter_by_fn_name( ctx: NativeCallContext, array: &mut Array, filter_func: &str, ) -> RhaiResultOf { 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, @@ -382,6 +784,23 @@ pub mod array_functions { 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, @@ -394,6 +813,33 @@ pub mod array_functions { 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, @@ -430,14 +876,26 @@ pub mod array_functions { Ok(-1 as INT) } - #[rhai_fn(name = "index_of", return_raw, pure)] - pub fn index_of_with_fn_name( - ctx: NativeCallContext, - array: &mut Array, - filter: &str, - ) -> RhaiResultOf { - index_of_filter(ctx, array, FnPtr::new(filter)?) - } + /// 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, @@ -450,6 +908,68 @@ pub mod array_functions { 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 { + 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, @@ -491,8 +1011,46 @@ pub mod array_functions { 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_with_fn_name_filter_starting_from( + pub fn index_of_by_fn_name_starting_from( ctx: NativeCallContext, array: &mut Array, filter: &str, @@ -500,6 +1058,24 @@ pub mod array_functions { ) -> RhaiResultOf { 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 { if array.is_empty() { @@ -534,14 +1110,59 @@ pub mod array_functions { 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_with_fn_name( + pub fn some_by_fn_name( ctx: NativeCallContext, array: &mut Array, filter: &str, ) -> RhaiResultOf { 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 { if array.is_empty() { @@ -576,18 +1197,76 @@ pub mod array_functions { 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_with_fn_name( + pub fn all_by_fn_name( ctx: NativeCallContext, array: &mut Array, filter: &str, ) -> RhaiResultOf { 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<()> { - dedup_with_fn_name(ctx, array, OP_EQUALS) + 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, @@ -600,7 +1279,7 @@ pub mod array_functions { array.dedup_by(|x, y| { comparer - .call_raw(&ctx, None, [x.clone(), y.clone()]) + .call_raw(&ctx, None, [y.clone(), x.clone()]) .unwrap_or_else(|_| Dynamic::FALSE) .as_bool() .unwrap_or(false) @@ -608,26 +1287,123 @@ pub mod array_functions { 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)] - fn dedup_with_fn_name( + 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_with_fn_name( + 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, @@ -666,8 +1442,35 @@ pub mod array_functions { 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_with_fn_name_with_initial( + pub fn reduce_by_fn_name_with_initial( ctx: NativeCallContext, array: &mut Array, reducer: &str, @@ -675,18 +1478,93 @@ pub mod array_functions { ) -> 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_with_fn_name( + 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, @@ -726,8 +1604,36 @@ pub mod array_functions { 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_with_fn_name_with_initial( + pub fn reduce_rev_by_fn_name_with_initial( ctx: NativeCallContext, array: &mut Array, reducer: &str, @@ -735,14 +1641,29 @@ pub mod array_functions { ) -> RhaiResult { reduce_rev_with_initial(ctx, array, FnPtr::new(reducer)?, initial) } - #[rhai_fn(name = "sort", return_raw)] - pub fn sort_with_fn_name( - ctx: NativeCallContext, - array: &mut Array, - comparer: &str, - ) -> RhaiResultOf<()> { - sort(ctx, array, FnPtr::new(comparer)?) - } + /// 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 { @@ -765,6 +1686,70 @@ pub mod array_functions { 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 { @@ -837,6 +1822,31 @@ pub mod array_functions { 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 { if array.is_empty() { @@ -880,26 +1890,120 @@ pub mod array_functions { 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_with_fn_name( + pub fn drain_by_fn_name( ctx: NativeCallContext, array: &mut Array, filter: &str, ) -> RhaiResultOf { 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 { @@ -914,6 +2018,31 @@ pub mod array_functions { 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 { if array.is_empty() { @@ -957,26 +2086,120 @@ pub mod array_functions { 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_with_fn_name( + pub fn retain_by_fn_name( ctx: NativeCallContext, array: &mut Array, filter: &str, ) -> RhaiResultOf { 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 { @@ -994,6 +2217,22 @@ pub mod array_functions { 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 { if array1.len() != array2.len() { @@ -1028,6 +2267,22 @@ pub mod array_functions { 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, diff --git a/src/packages/bit_field.rs b/src/packages/bit_field.rs index 9b0e8bd9..4bd5ec08 100644 --- a/src/packages/bit_field.rs +++ b/src/packages/bit_field.rs @@ -21,6 +21,21 @@ def_package! { mod bit_field_functions { const BITS: usize = std::mem::size_of::() * 8; + /// Return `true` if the specified `bit` in the number is set. + /// + /// If `bit` < 0, position counts from the MSB (Most Significant Bit). + /// + /// # Example + /// + /// ```rhai + /// let x = 123456; + /// + /// print(x.get_bit(5)); // prints false + /// + /// print(x.get_bit(6)); // prints true + /// + /// print(x.get_bit(-48)); // prints true on 64-bit + /// ``` #[rhai_fn(return_raw)] pub fn get_bit(value: INT, bit: INT) -> RhaiResultOf { let bit = calc_index(BITS, bit, true, || { @@ -29,6 +44,28 @@ mod bit_field_functions { Ok((value & (1 << bit)) != 0) } + /// Set the specified `bit` in the number if the new value is `true`. + /// Clear the `bit` if the new value is `false`. + /// + /// If `bit` < 0, position counts from the MSB (Most Significant Bit). + /// + /// # Example + /// + /// ```rhai + /// let x = 123456; + /// + /// x.set_bit(5, true); + /// + /// print(x); // prints 123488 + /// + /// x.set_bit(6, false); + /// + /// print(x); // prints 123424 + /// + /// x.set_bit(-48, false); + /// + /// print(x); // prints 57888 on 64-bit + /// ``` #[rhai_fn(return_raw)] pub fn set_bit(value: &mut INT, bit: INT, new_value: bool) -> RhaiResultOf<()> { let bit = calc_index(BITS, bit, true, || { @@ -44,26 +81,57 @@ mod bit_field_functions { Ok(()) } + /// Return an exclusive range of bits in the number as a new number. + /// + /// # Example + /// + /// ```rhai + /// let x = 123456; + /// + /// print(x.get_bits(5..10)); // print 18 + /// ``` #[rhai_fn(name = "get_bits", return_raw)] pub fn get_bits_range(value: INT, range: ExclusiveRange) -> RhaiResultOf { let from = INT::max(range.start, 0); let to = INT::max(range.end, from); get_bits(value, from, to - from) } + /// Return an inclusive range of bits in the number as a new number. + /// + /// # Example + /// + /// ```rhai + /// let x = 123456; + /// + /// print(x.get_bits(5..=9)); // print 18 + /// ``` #[rhai_fn(name = "get_bits", return_raw)] pub fn get_bits_range_inclusive(value: INT, range: InclusiveRange) -> RhaiResultOf { let from = INT::max(*range.start(), 0); let to = INT::max(*range.end(), from - 1); get_bits(value, from, to - from + 1) } + /// Return a portion of bits in the number as a new number. + /// + /// * If `start` < 0, position counts from the MSB (Most Significant Bit). + /// * If `bits` ≤ 0, zero is returned. + /// * If `start` position + `bits` ≥ total number of bits, the bits after the `start` position are returned. + /// + /// # Example + /// + /// ```rhai + /// let x = 123456; + /// + /// print(x.get_bits(5, 8)); // print 18 + /// ``` #[rhai_fn(return_raw)] - pub fn get_bits(value: INT, bit: INT, bits: INT) -> RhaiResultOf { + pub fn get_bits(value: INT, start: INT, bits: INT) -> RhaiResultOf { if bits <= 0 { return Ok(0); } - let bit = calc_index(BITS, bit, true, || { - ERR::ErrorBitFieldBounds(BITS, bit, Position::NONE).into() + let bit = calc_index(BITS, start, true, || { + ERR::ErrorBitFieldBounds(BITS, start, Position::NONE).into() })?; let bits = if bit + bits as usize > BITS { @@ -81,6 +149,17 @@ mod bit_field_functions { Ok(((value & (mask << bit)) >> bit) & mask) } + /// Replace an exclusive range of bits in the number with a new value. + /// + /// # Example + /// + /// ```rhai + /// let x = 123456; + /// + /// x.set_bits(5..10, 42); + /// + /// print(x); // print 123200 + /// ``` #[rhai_fn(name = "set_bits", return_raw)] pub fn set_bits_range( value: &mut INT, @@ -91,6 +170,17 @@ mod bit_field_functions { let to = INT::max(range.end, from); set_bits(value, from, to - from, new_value) } + /// Replace an inclusive range of bits in the number with a new value. + /// + /// # Example + /// + /// ```rhai + /// let x = 123456; + /// + /// x.set_bits(5..=9, 42); + /// + /// print(x); // print 123200 + /// ``` #[rhai_fn(name = "set_bits", return_raw)] pub fn set_bits_range_inclusive( value: &mut INT, @@ -101,6 +191,25 @@ mod bit_field_functions { let to = INT::max(*range.end(), from - 1); set_bits(value, from, to - from + 1, new_value) } + /// Replace a portion of bits in the number with a new value. + /// + /// * If `start` < 0, position counts from the MSB (Most Significant Bit). + /// * If `bits` ≤ 0, the number is not modified. + /// * If `start` position + `bits` ≥ total number of bits, the bits after the `start` position are replaced. + /// + /// # Example + /// + /// ```rhai + /// let x = 123456; + /// + /// x.set_bits(5, 8, 42); + /// + /// print(x); // prints 124224 + /// + /// x.set_bits(-16, 10, 42); + /// + /// print(x); // prints 11821949021971776 on 64-bit + /// ``` #[rhai_fn(return_raw)] pub fn set_bits(value: &mut INT, bit: INT, bits: INT, new_value: INT) -> RhaiResultOf<()> { if bits <= 0 { diff --git a/src/packages/blob_basic.rs b/src/packages/blob_basic.rs index ff14fe3c..ca240df3 100644 --- a/src/packages/blob_basic.rs +++ b/src/packages/blob_basic.rs @@ -1,7 +1,7 @@ #![cfg(not(feature = "no_index"))] #![allow(non_snake_case)] -use crate::eval::calc_offset_len; +use crate::eval::{calc_index, calc_offset_len}; use crate::plugin::*; use crate::{ def_package, Blob, Dynamic, ExclusiveRange, InclusiveRange, NativeCallContext, Position, @@ -14,12 +14,26 @@ use std::{any::TypeId, mem}; #[cfg(not(feature = "no_float"))] use crate::FLOAT; +const INT_BYTES: usize = mem::size_of::(); + +#[cfg(not(feature = "no_float"))] +const FLOAT_BYTES: usize = mem::size_of::(); + def_package! { /// Package of basic BLOB utilities. crate::BasicBlobPackage => |lib| { lib.standard = true; combine_with_exported_module!(lib, "blob", blob_functions); + combine_with_exported_module!(lib, "parse_int", parse_int_functions); + combine_with_exported_module!(lib, "write_int", write_int_functions); + combine_with_exported_module!(lib, "write_string", write_string_functions); + + #[cfg(not(feature = "no_float"))] + { + combine_with_exported_module!(lib, "parse_float", parse_float_functions); + combine_with_exported_module!(lib, "write_float", write_float_functions); + } // Register blob iterator lib.set_iterable::(); @@ -28,13 +42,38 @@ def_package! { #[export_module] pub mod blob_functions { + /// Return a new, empty BLOB. pub const fn blob() -> Blob { Blob::new() } + /// Return a new BLOB of the specified length, filled with zeros. + /// + /// If `len` ≤ 0, an empty BLOB is returned. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(10); + /// + /// print(b); // prints "[0000000000000000 0000]" + /// ``` #[rhai_fn(name = "blob", return_raw)] pub fn blob_with_capacity(ctx: NativeCallContext, len: INT) -> RhaiResultOf { blob_with_capacity_and_value(ctx, len, 0) } + /// Return a new BLOB of the specified length, filled with copies of the initial `value`. + /// + /// If `len` ≤ 0, an empty BLOB is returned. + /// + /// Only the lower 8 bits of the initial `value` are used; all other bits are ignored. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(10, 0x42); + /// + /// print(b); // prints "[4242424242424242 4242]" + /// ``` #[rhai_fn(name = "blob", return_raw)] pub fn blob_with_capacity_and_value( ctx: NativeCallContext, @@ -56,14 +95,40 @@ pub mod blob_functions { blob.resize(len, (value & 0x000000ff) as u8); Ok(blob) } + /// Return the length of the BLOB. #[rhai_fn(name = "len", get = "len", pure)] pub fn len(blob: &mut Blob) -> INT { blob.len() as INT } - pub fn push(blob: &mut Blob, item: INT) { - let item = (item & 0x000000ff) as u8; - blob.push(item); + /// Add a new byte `value` to the end of the BLOB. + /// + /// Only the lower 8 bits of the `value` are used; all other bits are ignored. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b.push(0x42); + /// + /// print(b); // prints "[42]" + /// ``` + pub fn push(blob: &mut Blob, value: INT) { + let value = (value & 0x000000ff) as u8; + blob.push(value); } + /// Add another BLOB to the end of the BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(5, 0x42); + /// let b2 = blob(3, 0x11); + /// + /// b1.push(b2); + /// + /// print(b1); // prints "[4242424242111111]" + /// ``` pub fn append(blob: &mut Blob, y: Blob) { if !y.is_empty() { if blob.is_empty() { @@ -73,6 +138,18 @@ pub mod blob_functions { } } } + /// Add another BLOB to the end of the BLOB, returning it as a new BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(5, 0x42); + /// let b2 = blob(3, 0x11); + /// + /// print(b1 + b2); // prints "[4242424242111111]" + /// + /// print(b1); // prints "[4242424242]" + /// ``` #[rhai_fn(name = "+")] pub fn concat(blob1: Blob, blob2: Blob) -> Blob { if !blob2.is_empty() { @@ -87,29 +164,65 @@ pub mod blob_functions { blob1 } } - pub fn insert(blob: &mut Blob, index: INT, item: INT) { - let item = (item & 0x000000ff) as u8; + /// Add a byte `value` to the BLOB at a particular `index` position. + /// + /// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `index` < -length of BLOB, the byte value is added to the beginning of the BLOB. + /// * If `index` ≥ length of BLOB, the byte value is appended to the end of the BLOB. + /// + /// Only the lower 8 bits of the `value` are used; all other bits are ignored. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(5, 0x42); + /// + /// b.insert(2, 0x18); + /// + /// print(b); // prints "[4242184242]" + /// ``` + pub fn insert(blob: &mut Blob, index: INT, value: INT) { + let value = (value & 0x000000ff) as u8; if blob.is_empty() { - blob.push(item); + blob.push(value); return; } let (index, _) = calc_offset_len(blob.len(), index, 0); if index >= blob.len() { - blob.push(item); + blob.push(value); } else { - blob.insert(index, item); + blob.insert(index, value); } } + /// Pad the BLOB to at least the specified length with copies of a specified byte `value`. + /// + /// If `len` ≤ length of BLOB, no padding is done. + /// + /// Only the lower 8 bits of the `value` are used; all other bits are ignored. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(3, 0x42); + /// + /// b.pad(5, 0x18) + /// + /// print(b); // prints "[4242421818]" + /// + /// b.pad(3, 0xab) + /// + /// print(b); // prints "[4242421818]" + /// ``` #[rhai_fn(return_raw)] - pub fn pad(ctx: NativeCallContext, blob: &mut Blob, len: INT, item: INT) -> RhaiResultOf<()> { + pub fn pad(ctx: NativeCallContext, blob: &mut Blob, len: INT, value: INT) -> RhaiResultOf<()> { if len <= 0 { return Ok(()); } - let item = (item & 0x000000ff) as u8; + let value = (value & 0x000000ff) as u8; let _ctx = ctx; // Check if blob will be over max size limit @@ -121,11 +234,26 @@ pub mod blob_functions { } if len as usize > blob.len() { - blob.resize(len as usize, item); + blob.resize(len as usize, value); } Ok(()) } + /// Remove the last byte from the BLOB and return it. + /// + /// If the BLOB is empty, zero is returned. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// print(b.pop()); // prints 5 + /// + /// print(b); // prints "[01020304]" + /// ``` pub fn pop(blob: &mut Blob) -> INT { if blob.is_empty() { 0 @@ -133,6 +261,21 @@ pub mod blob_functions { blob.pop().map_or_else(|| 0, |v| v as INT) } } + /// Remove the first byte from the BLOB and return it. + /// + /// If the BLOB is empty, zero is returned. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// print(b.shift()); // prints 1 + /// + /// print(b); // prints "[02030405]" + /// ``` pub fn shift(blob: &mut Blob) -> INT { if blob.is_empty() { 0 @@ -140,18 +283,61 @@ pub mod blob_functions { blob.remove(0) as INT } } - pub fn remove(blob: &mut Blob, len: INT) -> INT { - if len < 0 || (len as usize) >= blob.len() { - 0 - } else { - blob.remove(len as usize) as INT - } + /// Remove the byte at the specified `index` from the BLOB and return it. + /// + /// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `index` < -length of BLOB, zero is returned. + /// * If `index` ≥ length of BLOB, zero is returned. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// print(x.remove(1)); // prints 2 + /// + /// print(x); // prints "[01030405]" + /// + /// print(x.remove(-2)); // prints 4 + /// + /// print(x); // prints "[010305]" + /// ``` + pub fn remove(blob: &mut Blob, index: INT) -> INT { + let index = match calc_index(blob.len(), index, true, || Err(())) { + Ok(n) => n, + Err(_) => return 0, + }; + + blob.remove(index) as INT } + /// Clear the BLOB. pub fn clear(blob: &mut Blob) { if !blob.is_empty() { blob.clear(); } } + /// Cut off the BLOB at the specified length. + /// + /// * If `len` ≤ 0, the BLOB is cleared. + /// * If `len` ≥ length of BLOB, the BLOB is not truncated. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// b.truncate(3); + /// + /// print(b); // prints "[010203]" + /// + /// b.truncate(10); + /// + /// print(b); // prints "[010203]" + /// ``` pub fn truncate(blob: &mut Blob, len: INT) { if !blob.is_empty() { if len >= 0 { @@ -161,6 +347,26 @@ pub mod blob_functions { } } } + /// Cut off the head of the BLOB, leaving a tail of the specified length. + /// + /// * If `len` ≤ 0, the BLOB is cleared. + /// * If `len` ≥ length of BLOB, the BLOB is not modified. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// b.chop(3); + /// + /// print(b); // prints "[030405]" + /// + /// b.chop(10); + /// + /// print(b); // prints "[030405]" + /// ``` pub fn chop(blob: &mut Blob, len: INT) { if !blob.is_empty() { if len <= 0 { @@ -170,23 +376,84 @@ pub mod blob_functions { } } } + /// Reverse the BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// print(b); // prints "[0102030405]" + /// + /// b.reverse(); + /// + /// print(b); // prints "[0504030201]" + /// ``` pub fn reverse(blob: &mut Blob) { if !blob.is_empty() { blob.reverse(); } } + /// Replace an exclusive range of the BLOB with another BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(10, 0x42); + /// let b2 = blob(5, 0x18); + /// + /// b1.splice(1..4, b2); + /// + /// print(b1); // prints "[4218181818184242 42424242]" + /// ``` #[rhai_fn(name = "splice")] pub fn splice_range(blob: &mut Blob, range: ExclusiveRange, replace: Blob) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); splice(blob, start, end - start, replace) } + /// Replace an inclusive range of the BLOB with another BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(10, 0x42); + /// let b2 = blob(5, 0x18); + /// + /// b1.splice(1..=4, b2); + /// + /// print(b1); // prints "[4218181818184242 424242]" + /// ``` #[rhai_fn(name = "splice")] pub fn splice_range_inclusive(blob: &mut Blob, range: InclusiveRange, replace: Blob) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); splice(blob, start, end - start + 1, replace) } + /// Replace a portion of the BLOB with another BLOB. + /// + /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. + /// * If `start` ≥ length of BLOB, the other BLOB is appended to the end of the BLOB. + /// * If `len` ≤ 0, the other BLOB is inserted into the BLOB at the `start` position without replacing anything. + /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is replaced. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(10, 0x42); + /// let b2 = blob(5, 0x18); + /// + /// b1.splice(1, 3, b2); + /// + /// print(b1); // prints "[4218181818184242 42424242]" + /// + /// b1.splice(-5, 4, b2); + /// + /// print(b1); // prints "[4218181818184218 1818181842]" + /// ``` pub fn splice(blob: &mut Blob, start: INT, len: INT, replace: Blob) { if blob.is_empty() { *blob = replace; @@ -201,18 +468,65 @@ pub mod blob_functions { blob.splice(start..start + len, replace); } } + /// Copy an exclusive range of the BLOB and return it as a new BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// print(b.extract(1..3)); // prints "[0203]" + /// + /// print(b); // prints "[0102030405]" + /// ``` #[rhai_fn(name = "extract")] pub fn extract_range(blob: &mut Blob, range: ExclusiveRange) -> Blob { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); extract(blob, start, end - start) } + /// Copy an inclusive range of the BLOB and return it as a new BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// print(b.extract(1..=3)); // prints "[020304]" + /// + /// print(b); // prints "[0102030405]" + /// ``` #[rhai_fn(name = "extract")] pub fn extract_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> Blob { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); extract(blob, start, end - start + 1) } + /// Copy a portion of the BLOB and return it as a new BLOB. + /// + /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. + /// * If `start` ≥ length of BLOB, an empty BLOB is returned. + /// * If `len` ≤ 0, an empty BLOB is returned. + /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is copied and returned. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// print(b.extract(1, 3)); // prints "[020303]" + /// + /// print(b.extract(-3, 2)); // prints "[0304]" + /// + /// print(b); // prints "[0102030405]" + /// ``` pub fn extract(blob: &mut Blob, start: INT, len: INT) -> Blob { if blob.is_empty() || len <= 0 { return Blob::new(); @@ -226,19 +540,59 @@ pub mod blob_functions { blob[start..start + len].to_vec() } } + /// Copy a portion of the BLOB beginning at the `start` position till the end and return it as + /// a new BLOB. + /// + /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `start` < -length of BLOB, the entire BLOB is copied and returned. + /// * If `start` ≥ length of BLOB, an empty BLOB is returned. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// print(b.extract(2)); // prints "[030405]" + /// + /// print(b.extract(-3)); // prints "[030405]" + /// + /// print(b); // prints "[0102030405]" + /// ``` #[rhai_fn(name = "extract")] pub fn extract_tail(blob: &mut Blob, start: INT) -> Blob { extract(blob, start, INT::MAX) } + /// Cut off the BLOB at `index` and return it as a new BLOB. + /// + /// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `index` is zero, the entire BLOB is cut and returned. + /// * If `index` < -length of BLOB, the entire BLOB is cut and returned. + /// * If `index` ≥ length of BLOB, nothing is cut from the BLOB and an empty BLOB is returned. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(); + /// + /// b1 += 1; b1 += 2; b1 += 3; b1 += 4; b1 += 5; + /// + /// let b2 = b1.split(2); + /// + /// print(b2); // prints "[030405]" + /// + /// print(b1); // prints "[0102]" + /// ``` #[rhai_fn(name = "split")] - pub fn split_at(blob: &mut Blob, start: INT) -> Blob { + pub fn split_at(blob: &mut Blob, index: INT) -> Blob { if blob.is_empty() { return Blob::new(); } - let (start, len) = calc_offset_len(blob.len(), start, INT::MAX); + let (index, len) = calc_offset_len(blob.len(), index, INT::MAX); - if start == 0 { + if index == 0 { if len > blob.len() { mem::take(blob) } else { @@ -246,26 +600,95 @@ pub mod blob_functions { result.extend(blob.drain(blob.len() - len..)); result } - } else if start >= blob.len() { + } else if index >= blob.len() { Blob::new() } else { let mut result = Blob::new(); - result.extend(blob.drain(start as usize..)); + result.extend(blob.drain(index as usize..)); result } } + /// Remove all bytes in the BLOB within an exclusive range and return them as a new BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(); + /// + /// b1 += 1; b1 += 2; b1 += 3; b1 += 4; b1 += 5; + /// + /// let b2 = b1.drain(1..3); + /// + /// print(b1); // prints "[010405]" + /// + /// print(b2); // prints "[0203]" + /// + /// let b3 = b1.drain(2..3); + /// + /// print(b1); // prints "[0104]" + /// + /// print(b3); // prints "[05]" + /// ``` #[rhai_fn(name = "drain")] pub fn drain_range(blob: &mut Blob, range: ExclusiveRange) -> Blob { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); drain(blob, start, end - start) } + /// Remove all bytes in the BLOB within an inclusive range and return them as a new BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(); + /// + /// b1 += 1; b1 += 2; b1 += 3; b1 += 4; b1 += 5; + /// + /// let b2 = b1.drain(1..=2); + /// + /// print(b1); // prints "[010405]" + /// + /// print(b2); // prints "[0203]" + /// + /// let b3 = b1.drain(2..=2); + /// + /// print(b1); // prints "[0104]" + /// + /// print(b3); // prints "[05]" + /// ``` #[rhai_fn(name = "drain")] pub fn drain_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> Blob { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); drain(blob, start, end - start + 1) } + /// Remove all bytes within a portion of the BLOB and return them as a new BLOB. + /// + /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. + /// * If `start` ≥ length of BLOB, nothing is removed and an empty BLOB is returned. + /// * If `len` ≤ 0, nothing is removed and an empty BLOB is returned. + /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is removed and returned. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(); + /// + /// b1 += 1; b1 += 2; b1 += 3; b1 += 4; b1 += 5; + /// + /// let b2 = b1.drain(1, 2); + /// + /// print(b1); // prints "[010405]" + /// + /// print(b2); // prints "[0203]" + /// + /// let b3 = b1.drain(-1, 1); + /// + /// print(b3); // prints "[0104]" + /// + /// print(z); // prints "[5]" + /// ``` pub fn drain(blob: &mut Blob, start: INT, len: INT) -> Blob { if blob.is_empty() || len <= 0 { return Blob::new(); @@ -279,18 +702,87 @@ pub mod blob_functions { blob.drain(start..start + len).collect() } } + /// Remove all bytes in the BLOB not within an exclusive range and return them as a new BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(); + /// + /// b1 += 1; b1 += 2; b1 += 3; b1 += 4; b1 += 5; + /// + /// let b2 = b1.retain(1..4); + /// + /// print(b1); // prints "[020304]" + /// + /// print(b2); // prints "[0105]" + /// + /// let b3 = b1.retain(1..3); + /// + /// print(b1); // prints "[0304]" + /// + /// print(b2); // prints "[01]" + /// ``` #[rhai_fn(name = "retain")] pub fn retain_range(blob: &mut Blob, range: ExclusiveRange) -> Blob { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); retain(blob, start, end - start) } + /// Remove all bytes in the BLOB not within an inclusive range and return them as a new BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(); + /// + /// b1 += 1; b1 += 2; b1 += 3; b1 += 4; b1 += 5; + /// + /// let b2 = b1.retain(1..=3); + /// + /// print(b1); // prints "[020304]" + /// + /// print(b2); // prints "[0105]" + /// + /// let b3 = b1.retain(1..=2); + /// + /// print(b1); // prints "[0304]" + /// + /// print(b2); // prints "[01]" + /// ``` #[rhai_fn(name = "retain")] pub fn retain_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> Blob { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); retain(blob, start, end - start + 1) } + /// Remove all bytes not within a portion of the BLOB and return them as a new BLOB. + /// + /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. + /// * If `start` ≥ length of BLOB, all elements are removed returned. + /// * If `len` ≤ 0, all elements are removed and returned. + /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB before the `start` position is removed and returned. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(); + /// + /// b1 += 1; b1 += 2; b1 += 3; b1 += 4; b1 += 5; + /// + /// let b2 = b1.retain(1, 2); + /// + /// print(b1); // prints "[0203]" + /// + /// print(b2); // prints "[010405]" + /// + /// let b3 = b1.retain(-1, 1); + /// + /// print(b1); // prints "[03]" + /// + /// print(b3); // prints "[02]" + /// ``` pub fn retain(blob: &mut Blob, start: INT, len: INT) -> Blob { if blob.is_empty() || len <= 0 { return Blob::new(); @@ -307,7 +799,10 @@ pub mod blob_functions { drained } } +} +#[export_module] +mod parse_int_functions { #[inline] fn parse_int(blob: &mut Blob, start: INT, len: INT, is_le: bool) -> INT { if blob.is_empty() || len <= 0 { @@ -319,8 +814,6 @@ pub mod blob_functions { return 0; } - const INT_BYTES: usize = mem::size_of::(); - let len = usize::min(len, INT_BYTES); let mut buf = [0_u8; INT_BYTES]; @@ -364,7 +857,70 @@ pub mod blob_functions { pub fn parse_be_int(blob: &mut Blob, start: INT, len: INT) -> INT { parse_int(blob, start, len, false) } +} +#[cfg(not(feature = "no_float"))] +#[export_module] +mod parse_float_functions { + #[inline] + fn parse_float(blob: &mut Blob, start: INT, len: INT, is_le: bool) -> FLOAT { + if blob.is_empty() || len <= 0 { + return 0.0; + } + + let (start, len) = calc_offset_len(blob.len(), start, len); + + if len == 0 { + return 0.0; + } + + let len = usize::min(len, FLOAT_BYTES); + + let mut buf = [0_u8; FLOAT_BYTES]; + + buf[..len].copy_from_slice(&blob[start..][..len]); + + if is_le { + FLOAT::from_le_bytes(buf) + } else { + FLOAT::from_be_bytes(buf) + } + } + + #[rhai_fn(name = "parse_le_float")] + pub fn parse_le_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + parse_le_float(blob, start, end - start) + } + #[rhai_fn(name = "parse_le_float")] + pub fn parse_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + parse_le_float(blob, start, end - start + 1) + } + pub fn parse_le_float(blob: &mut Blob, start: INT, len: INT) -> FLOAT { + parse_float(blob, start, len, true) + } + #[rhai_fn(name = "parse_be_float")] + pub fn parse_be_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + parse_be_float(blob, start, end - start) + } + #[rhai_fn(name = "parse_be_float")] + pub fn parse_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + parse_be_float(blob, start, end - start + 1) + } + pub fn parse_be_float(blob: &mut Blob, start: INT, len: INT) -> FLOAT { + parse_float(blob, start, len, false) + } +} + +#[export_module] +mod write_int_functions { #[inline] fn write_int(blob: &mut Blob, start: INT, len: INT, value: INT, is_le: bool) { if blob.is_empty() || len <= 0 { @@ -377,8 +933,6 @@ pub mod blob_functions { return; } - const INT_BYTES: usize = mem::size_of::(); - let len = usize::min(len, INT_BYTES); let buf = if is_le { @@ -389,13 +943,13 @@ pub mod blob_functions { blob[start..][..len].copy_from_slice(&buf[..len]); } - #[rhai_fn(name = "write_le_int")] + #[rhai_fn(name = "write_le")] pub fn write_le_int_range(blob: &mut Blob, range: ExclusiveRange, value: INT) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); write_le_int(blob, start, end - start, value) } - #[rhai_fn(name = "write_le_int")] + #[rhai_fn(name = "write_le")] pub fn write_le_int_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: INT) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); @@ -421,73 +975,11 @@ pub mod blob_functions { pub fn write_be_int(blob: &mut Blob, start: INT, len: INT, value: INT) { write_int(blob, start, len, value, false) } +} - #[cfg(not(feature = "no_float"))] - #[inline] - fn parse_float(blob: &mut Blob, start: INT, len: INT, is_le: bool) -> FLOAT { - if blob.is_empty() || len <= 0 { - return 0.0; - } - - let (start, len) = calc_offset_len(blob.len(), start, len); - - if len == 0 { - return 0.0; - } - - const FLOAT_BYTES: usize = mem::size_of::(); - - let len = usize::min(len, FLOAT_BYTES); - - let mut buf = [0_u8; FLOAT_BYTES]; - - buf[..len].copy_from_slice(&blob[start..][..len]); - - if is_le { - FLOAT::from_le_bytes(buf) - } else { - FLOAT::from_be_bytes(buf) - } - } - - #[cfg(not(feature = "no_float"))] - #[rhai_fn(name = "parse_le_float")] - pub fn parse_le_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT { - let start = INT::max(range.start, 0); - let end = INT::max(range.end, start); - parse_le_float(blob, start, end - start) - } - #[cfg(not(feature = "no_float"))] - #[rhai_fn(name = "parse_le_float")] - pub fn parse_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT { - let start = INT::max(*range.start(), 0); - let end = INT::max(*range.end(), start); - parse_le_float(blob, start, end - start + 1) - } - #[cfg(not(feature = "no_float"))] - pub fn parse_le_float(blob: &mut Blob, start: INT, len: INT) -> FLOAT { - parse_float(blob, start, len, true) - } - #[cfg(not(feature = "no_float"))] - #[rhai_fn(name = "parse_be_float")] - pub fn parse_be_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT { - let start = INT::max(range.start, 0); - let end = INT::max(range.end, start); - parse_be_float(blob, start, end - start) - } - #[cfg(not(feature = "no_float"))] - #[rhai_fn(name = "parse_be_float")] - pub fn parse_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT { - let start = INT::max(*range.start(), 0); - let end = INT::max(*range.end(), start); - parse_be_float(blob, start, end - start + 1) - } - #[cfg(not(feature = "no_float"))] - pub fn parse_be_float(blob: &mut Blob, start: INT, len: INT) -> FLOAT { - parse_float(blob, start, len, false) - } - - #[cfg(not(feature = "no_float"))] +#[cfg(not(feature = "no_float"))] +#[export_module] +mod write_float_functions { #[inline] fn write_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT, is_le: bool) { if blob.is_empty() || len <= 0 { @@ -500,8 +992,6 @@ pub mod blob_functions { return; } - const FLOAT_BYTES: usize = mem::size_of::(); - let len = usize::min(len, FLOAT_BYTES); let buf = if is_le { value.to_le_bytes() @@ -511,44 +1001,42 @@ pub mod blob_functions { blob[start..][..len].copy_from_slice(&buf[..len]); } - #[cfg(not(feature = "no_float"))] - #[rhai_fn(name = "write_le_float")] + #[rhai_fn(name = "write_le")] pub fn write_le_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); write_le_float(blob, start, end - start, value) } - #[cfg(not(feature = "no_float"))] - #[rhai_fn(name = "write_le_float")] + #[rhai_fn(name = "write_le")] pub fn write_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); write_le_float(blob, start, end - start + 1, value) } - #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "write_le")] pub fn write_le_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) { write_float(blob, start, len, value, true) } - #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "write_be")] pub fn write_be_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); write_be_float(blob, start, end - start, value) } - #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "write_be")] pub fn write_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); write_be_float(blob, start, end - start + 1, value) } - #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "write_be")] pub fn write_be_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) { write_float(blob, start, len, value, false) } +} + +#[export_module] +mod write_string_functions { #[inline] fn write_string(blob: &mut Blob, start: INT, len: INT, string: &str, ascii_only: bool) { if len <= 0 || blob.is_empty() || string.is_empty() { diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index 66be2772..48aeb3d8 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -14,122 +14,34 @@ def_package! { #[export_module] mod fn_ptr_functions { + /// Return the name of the function. + /// + /// # Example + /// + /// ```rhai + /// fn double(x) { x * 2 } + /// + /// let f = Fn("double"); + /// + /// print(f.name); // prints "double" + /// ``` #[rhai_fn(name = "name", get = "name", pure)] pub fn name(fn_ptr: &mut FnPtr) -> ImmutableString { fn_ptr.fn_name_raw().into() } + /// Return `true` if the function is an anonymous function. + /// + /// # Example + /// + /// ```rhai + /// let f = |x| x * 2; + /// + /// print(f.is_anonymous); // prints true + /// ``` #[cfg(not(feature = "no_function"))] #[rhai_fn(name = "is_anonymous", get = "is_anonymous", pure)] pub fn is_anonymous(fn_ptr: &mut FnPtr) -> bool { fn_ptr.is_anonymous() } - - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "no_index"))] - #[cfg(not(feature = "no_object"))] - pub fn get_fn_metadata_list(ctx: NativeCallContext) -> crate::Array { - collect_fn_metadata(ctx) - } -} - -#[cfg(not(feature = "no_function"))] -#[cfg(not(feature = "no_index"))] -#[cfg(not(feature = "no_object"))] -fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { - use crate::{ast::ScriptFnDef, Array, Identifier, Map}; - use std::collections::BTreeSet; - - // Create a metadata record for a function. - fn make_metadata( - dict: &BTreeSet, - namespace: Option, - func: &ScriptFnDef, - ) -> Map { - const DICT: &str = "key exists"; - - let mut map = Map::new(); - - if let Some(ns) = namespace { - map.insert(dict.get("namespace").expect(DICT).clone(), ns.into()); - } - map.insert( - dict.get("name").expect(DICT).clone(), - func.name.clone().into(), - ); - map.insert( - dict.get("access").expect(DICT).clone(), - match func.access { - FnAccess::Public => dict.get("public").expect(DICT).clone(), - FnAccess::Private => dict.get("private").expect(DICT).clone(), - } - .into(), - ); - map.insert( - dict.get("is_anonymous").expect(DICT).clone(), - func.name.starts_with(crate::engine::FN_ANONYMOUS).into(), - ); - map.insert( - dict.get("params").expect(DICT).clone(), - func.params - .iter() - .cloned() - .map(Into::into) - .collect::() - .into(), - ); - - map - } - - // Intern strings - let dict: BTreeSet = [ - "namespace", - "name", - "access", - "public", - "private", - "is_anonymous", - "params", - ] - .iter() - .map(|&s| s.into()) - .collect(); - - let mut _list = ctx.iter_namespaces().flat_map(Module::iter_script_fn).fold( - Array::new(), - |mut list, (_, _, _, _, f)| { - list.push(make_metadata(&dict, None, f).into()); - list - }, - ); - - #[cfg(not(feature = "no_module"))] - { - // Recursively scan modules for script-defined functions. - fn scan_module( - list: &mut Array, - dict: &BTreeSet, - namespace: Identifier, - module: &Module, - ) { - module.iter_script_fn().for_each(|(_, _, _, _, f)| { - list.push(make_metadata(dict, Some(namespace.clone()), f).into()) - }); - module.iter_sub_modules().for_each(|(ns, m)| { - let ns = format!( - "{}{}{}", - namespace, - crate::tokenizer::Token::DoubleColon.literal_syntax(), - ns - ); - scan_module(list, dict, ns.into(), m.as_ref()) - }); - } - - ctx.iter_imports_raw() - .for_each(|(ns, m)| scan_module(&mut _list, &dict, ns.clone(), m.as_ref())); - } - - _list } diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index d06931f9..fe844e70 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -246,10 +246,20 @@ macro_rules! reg_range { let _hash = $lib.set_native_fn($x, |from: $y, to: $y| Ok(from..to)); #[cfg(feature = "metadata")] - $lib.update_fn_metadata(_hash, &[ + $lib.update_fn_metadata_with_comments(_hash, [ concat!("from: ", stringify!($y)), concat!("to: ", stringify!($y)), - concat!("Iterator") + concat!("Iterator"), + ], [ + "/// Return an iterator over the range of `from..to`.", + "///", + "/// # Example", + "///", + "/// ```rhai", + "/// for n in range(8, 18) {", + "/// print(n);", + "/// }", + "/// ```" ]); $lib.set_iterator::>(); @@ -261,11 +271,27 @@ macro_rules! reg_range { let _hash = $lib.set_native_fn($x, |from: $y, to: $y, step: $y| StepRange::new(from, to, step)); #[cfg(feature = "metadata")] - $lib.update_fn_metadata(_hash, &[ + $lib.update_fn_metadata_with_comments(_hash, [ concat!("from: ", stringify!($y)), concat!("to: ", stringify!($y)), concat!("step: ", stringify!($y)), concat!("Iterator") + ], [ + "/// Return an iterator over the range of `from..to`, each iterator increasing by `step`.", + "///", + "/// If `from` > `to` and `step` < 0, the iteration goes backwards.", + "///", + "/// # Example", + "///", + "/// ```rhai", + "/// for n in range(8, 18, 3) {", + "/// print(n);", + "/// }", + "///", + "/// for n in range(18, 8, -3) {", + "/// print(n);", + "/// }", + "/// ```" ]); )* }; @@ -359,7 +385,27 @@ def_package! { let _hash = lib.set_native_fn("range", StepFloatRange::new); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["from: FLOAT", "to: FLOAT", "step: FLOAT", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["from: FLOAT", "to: FLOAT", "step: FLOAT", "Iterator"], + [ + "/// Return an iterator over the range of `from..to`, each iterator increasing by `step`.", + "///", + "/// If `from` > `to` and `step` < 0, the iteration goes backwards.", + "///", + "/// # Example", + "///", + "/// ```rhai", + "/// for n in range(8.0, 18.0, 3.0) {", + "/// print(n);", + "/// }", + "///", + "/// for n in range(18.0, 8.0, -3.0) {", + "/// print(n);", + "/// }", + "/// ```" + ] + ); } #[cfg(feature = "decimal")] @@ -421,7 +467,15 @@ def_package! { let _hash = lib.set_native_fn("range", StepDecimalRange::new); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["from: Decimal", "to: Decimal", "step: Decimal", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["from: Decimal", "to: Decimal", "step: Decimal", "Iterator"], + [ + "/// Return an iterator over the range of `from..to`, each iterator increasing by `step`.", + "///", + "/// If `from` > `to` and `step` < 0, the iteration goes backwards.", + ] + ); } // Register string iterator @@ -433,7 +487,21 @@ def_package! { Ok(CharsStream::new(string, from, to - from)) }); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["string: &str", "range: Range", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["string: &str", "range: Range", "Iterator"], + [ + "/// Return an iterator over an exclusive range of characters in the string.", + "///", + "/// # Example", + "///", + "/// ```rhai", + r#"/// for ch in "hello, world!".chars(2..5) {"#, + "/// print(ch);", + "/// }", + "/// ```" + ] + ); let _hash = lib.set_native_fn("chars", |string, range: InclusiveRange| { let from = INT::max(*range.start(), 0); @@ -441,25 +509,105 @@ def_package! { Ok(CharsStream::new(string, from, to-from + 1)) }); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["string: &str", "range: RangeInclusive", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["string: &str", "range: RangeInclusive", "Iterator"], + [ + "/// Return an iterator over an inclusive range of characters in the string.", + "///", + "/// # Example", + "///", + "/// ```rhai", + r#"/// for ch in "hello, world!".chars(2..=6) {"#, + "/// print(ch);", + "/// }", + "/// ```" + ] + ); let _hash = lib.set_native_fn("chars", |string, from, len| Ok(CharsStream::new(string, from, len))); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["string: &str", "from: INT", "len: INT", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["string: &str", "start: INT", "len: INT", "Iterator"], + [ + "/// Return an iterator over a portion of characters in the string.", + "///", + "/// * If `start` < 0, position counts from the end of the string (`-1` is the last character).", + "/// * If `start` < -length of string, position counts from the beginning of the string.", + "/// * If `start` ≥ length of string, an empty iterator is returned.", + "/// * If `len` ≤ 0, an empty iterator is returned.", + "/// * If `start` position + `len` ≥ length of string, all characters of the string after the `start` position are iterated.", + "///", + "/// # Example", + "///", + "/// ```rhai", + r#"/// for ch in "hello, world!".chars(2, 4) {"#, + "/// print(ch);", + "/// }", + "/// ```" + ] + ); let _hash = lib.set_native_fn("chars", |string, from| Ok(CharsStream::new(string, from, INT::MAX))); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["string: &str", "from: INT", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["string: &str", "from: INT", "Iterator"], + [ + "/// Return an iterator over the characters in the string starting from the `start` position.", + "///", + "/// * If `start` < 0, position counts from the end of the string (`-1` is the last character).", + "/// * If `start` < -length of string, position counts from the beginning of the string.", + "/// * If `start` ≥ length of string, an empty iterator is returned.", + "///", + "/// # Example", + "///", + "/// ```rhai", + r#"/// for ch in "hello, world!".chars(2) {"#, + "/// print(ch);", + "/// }", + "/// ```" + ] + ); let _hash = lib.set_native_fn("chars", |string| Ok(CharsStream::new(string, 0, INT::MAX))); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["string: &str", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["string: &str", "Iterator"], + [ + "/// Return an iterator over the characters in the string.", + "///", + "/// # Example", + "///", + "/// ```rhai", + r#"/// for ch in "hello, world!".chars() {"#, + "/// print(ch);", + "/// }", + "/// ```" + ] + ); #[cfg(not(feature = "no_object"))] { let _hash = lib.set_getter_fn("chars", |string: &mut ImmutableString| Ok(CharsStream::new(string, 0, INT::MAX))); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["string: &mut ImmutableString", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["string: &mut ImmutableString", "Iterator"], + [ + "/// Return an iterator over all the characters in the string.", + "///", + "/// # Example", + "///", + "/// ```rhai", + r#"/// for ch in "hello, world!".chars {"#, + "/// print(ch);", + "/// }", + "/// ```" + ] + ); } // Register bit-field iterator @@ -471,7 +619,23 @@ def_package! { BitRange::new(value, from, to - from) }); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["value: INT", "range: Range", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["value: INT", "range: Range", "Iterator"], + [ + "/// Return an iterator over an exclusive range of bits in the number.", + "///", + "/// # Example", + "///", + "/// ```rhai", + "/// let x = 123456;", + "///", + "/// for bit in x.bits(10..24) {", + "/// print(bit);", + "/// }", + "/// ```" + ] + ); let _hash = lib.set_native_fn("bits", |value, range: InclusiveRange| { let from = INT::max(*range.start(), 0); @@ -479,25 +643,111 @@ def_package! { BitRange::new(value, from, to - from + 1) }); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["value: INT", "range: RangeInclusive", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["value: INT", "range: RangeInclusive", "Iterator"], + [ + "/// Return an iterator over an inclusive range of bits in the number.", + "///", + "/// # Example", + "///", + "/// ```rhai", + "/// let x = 123456;", + "///", + "/// for bit in x.bits(10..=23) {", + "/// print(bit);", + "/// }", + "/// ```" + ] + ); let _hash = lib.set_native_fn("bits", BitRange::new); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "len: INT", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["value: INT", "from: INT", "len: INT", "Iterator"], + [ + "/// Return an iterator over a portion of bits in the number.", + "///", + "/// * If `start` < 0, position counts from the MSB (Most Significant Bit)>.", + "/// * If `len` ≤ 0, an empty iterator is returned.", + "/// * If `start` position + `len` ≥ length of string, all bits of the number after the `start` position are iterated.", + "///", + "/// # Example", + "///", + "/// ```rhai", + "/// let x = 123456;", + "///", + "/// for bit in x.bits(10, 8) {", + "/// print(bit);", + "/// }", + "/// ```" + ] + ); let _hash = lib.set_native_fn("bits", |value, from| BitRange::new(value, from, INT::MAX)); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["value: INT", "from: INT", "Iterator"], + [ + "/// Return an iterator over the bits in the number starting from the specified `start` position.", + "///", + "/// If `start` < 0, position counts from the MSB (Most Significant Bit)>.", + "///", + "/// # Example", + "///", + "/// ```rhai", + "/// let x = 123456;", + "///", + "/// for bit in x.bits(10) {", + "/// print(bit);", + "/// }", + "/// ```" + ] + ); let _hash = lib.set_native_fn("bits", |value| BitRange::new(value, 0, INT::MAX) ); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["value: INT", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["value: INT", "Iterator"], + [ + "/// Return an iterator over all the bits in the number.", + "///", + "/// # Example", + "///", + "/// ```rhai", + "/// let x = 123456;", + "///", + "/// for bit in x.bits() {", + "/// print(bit);", + "/// }", + "/// ```" + ] + ); #[cfg(not(feature = "no_object"))] { let _hash = lib.set_getter_fn("bits", |value: &mut INT| BitRange::new(*value, 0, INT::MAX) ); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["value: &mut INT", "range: Range", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["value: &mut INT", "range: Range", "Iterator"], + [ + "/// Return an iterator over all the bits in the number.", + "///", + "/// # Example", + "///", + "/// ```rhai", + "/// let x = 123456;", + "///", + "/// for bit in x.bits {", + "/// print(bit);", + "/// }", + "/// ```" + ] + ); } combine_with_exported_module!(lib, "range", range_functions); @@ -506,37 +756,45 @@ def_package! { #[export_module] mod range_functions { + /// Return the start of the exclusive range. #[rhai_fn(get = "start", name = "start", pure)] pub fn start(range: &mut ExclusiveRange) -> INT { range.start } + /// Return the end of the exclusive range. #[rhai_fn(get = "end", name = "end", pure)] pub fn end(range: &mut ExclusiveRange) -> INT { range.end } + /// Return `true` if the range is inclusive. #[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)] pub fn is_inclusive(range: &mut ExclusiveRange) -> bool { let _range = range; false } + /// Return `true` if the range is exclusive. #[rhai_fn(get = "is_exclusive", name = "is_exclusive", pure)] pub fn is_exclusive(range: &mut ExclusiveRange) -> bool { let _range = range; true } + /// Return the start of the inclusive range. #[rhai_fn(get = "start", name = "start", pure)] pub fn start_inclusive(range: &mut InclusiveRange) -> INT { *range.start() } + /// Return the end of the inclusive range. #[rhai_fn(get = "end", name = "end", pure)] pub fn end_inclusive(range: &mut InclusiveRange) -> INT { *range.end() } + /// Return `true` if the range is inclusive. #[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)] pub fn is_inclusive_inclusive(range: &mut InclusiveRange) -> bool { let _range = range; true } + /// Return `true` if the range is exclusive. #[rhai_fn(get = "is_exclusive", name = "is_exclusive", pure)] pub fn is_exclusive_inclusive(range: &mut InclusiveRange) -> bool { let _range = range; diff --git a/src/packages/lang_core.rs b/src/packages/lang_core.rs index a84b6628..ea1057c8 100644 --- a/src/packages/lang_core.rs +++ b/src/packages/lang_core.rs @@ -53,4 +53,112 @@ mod core_functions { Ok(()) } } + + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_index"))] + #[cfg(not(feature = "no_object"))] + pub fn get_fn_metadata_list(ctx: NativeCallContext) -> crate::Array { + collect_fn_metadata(ctx) + } +} + +#[cfg(not(feature = "no_function"))] +#[cfg(not(feature = "no_index"))] +#[cfg(not(feature = "no_object"))] +fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { + use crate::{ast::ScriptFnDef, Array, Identifier, Map}; + use std::collections::BTreeSet; + + // Create a metadata record for a function. + fn make_metadata( + dict: &BTreeSet, + namespace: Option, + func: &ScriptFnDef, + ) -> Map { + const DICT: &str = "key exists"; + + let mut map = Map::new(); + + if let Some(ns) = namespace { + map.insert(dict.get("namespace").expect(DICT).clone(), ns.into()); + } + map.insert( + dict.get("name").expect(DICT).clone(), + func.name.clone().into(), + ); + map.insert( + dict.get("access").expect(DICT).clone(), + match func.access { + FnAccess::Public => dict.get("public").expect(DICT).clone(), + FnAccess::Private => dict.get("private").expect(DICT).clone(), + } + .into(), + ); + map.insert( + dict.get("is_anonymous").expect(DICT).clone(), + func.name.starts_with(crate::engine::FN_ANONYMOUS).into(), + ); + map.insert( + dict.get("params").expect(DICT).clone(), + func.params + .iter() + .cloned() + .map(Into::into) + .collect::() + .into(), + ); + + map + } + + // Intern strings + let dict: BTreeSet = [ + "namespace", + "name", + "access", + "public", + "private", + "is_anonymous", + "params", + ] + .iter() + .map(|&s| s.into()) + .collect(); + + let mut _list = ctx.iter_namespaces().flat_map(Module::iter_script_fn).fold( + Array::new(), + |mut list, (_, _, _, _, f)| { + list.push(make_metadata(&dict, None, f).into()); + list + }, + ); + + #[cfg(not(feature = "no_module"))] + { + // Recursively scan modules for script-defined functions. + fn scan_module( + list: &mut Array, + dict: &BTreeSet, + namespace: Identifier, + module: &Module, + ) { + module.iter_script_fn().for_each(|(_, _, _, _, f)| { + list.push(make_metadata(dict, Some(namespace.clone()), f).into()) + }); + module.iter_sub_modules().for_each(|(ns, m)| { + let ns = format!( + "{}{}{}", + namespace, + crate::tokenizer::Token::DoubleColon.literal_syntax(), + ns + ); + scan_module(list, dict, ns.into(), m.as_ref()) + }); + } + + ctx.iter_imports_raw() + .for_each(|(ns, m)| scan_module(&mut _list, &dict, ns.clone(), m.as_ref())); + } + + _list } diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 4aaa3575..497bb0cc 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -20,15 +20,32 @@ def_package! { #[export_module] mod map_functions { + /// Return the number of properties in the object map. #[rhai_fn(pure)] pub fn len(map: &mut Map) -> INT { map.len() as INT } + /// Clear the object map. pub fn clear(map: &mut Map) { if !map.is_empty() { map.clear(); } } + /// Remove any property of the specified `name` from the object map, returning its value. + /// + /// If the property does not exist, `()` is returned. + /// + /// # Example + /// + /// ```rhai + /// let m = #{a:1, b:2, c:3}; + /// + /// let x = m.remove("b"); + /// + /// print(x); // prints 2 + /// + /// print(m); // prints "#{a:1, c:3}" + /// ``` pub fn remove(map: &mut Map, name: ImmutableString) -> Dynamic { if !map.is_empty() { map.remove(name.as_str()).unwrap_or_else(|| Dynamic::UNIT) @@ -36,12 +53,38 @@ mod map_functions { Dynamic::UNIT } } + /// Add all property values of another object map into the object map. + /// Existing property values of the same names are replaced. + /// + /// # Example + /// + /// ```rhai + /// let m = #{a:1, b:2, c:3}; + /// let n = #{a: 42, d:0}; + /// + /// m.mixin(n); + /// + /// print(m); // prints "#{a:42, b:2, c:3, d:0}" + /// ``` #[rhai_fn(name = "mixin", name = "+=")] pub fn mixin(map: &mut Map, map2: Map) { if !map2.is_empty() { map.extend(map2.into_iter()); } } + /// Make a copy of the object map, add all property values of another object map + /// (existing property values of the same names are replaced), then returning it. + /// + /// # Example + /// + /// ```rhai + /// let m = #{a:1, b:2, c:3}; + /// let n = #{a: 42, d:0}; + /// + /// print(m + n); // prints "#{a:42, b:2, c:3, d:0}" + /// + /// print(m); // prints "#{a:1, b:2, c:3}" + /// ``` #[rhai_fn(name = "+")] pub fn merge(map1: Map, map2: Map) -> Map { if map2.is_empty() { @@ -54,6 +97,19 @@ mod map_functions { map1 } } + /// Add all property values of another object map into the object map. + /// Only properties that do not originally exist in the object map are added. + /// + /// # Example + /// + /// ```rhai + /// let m = #{a:1, b:2, c:3}; + /// let n = #{a: 42, d:0}; + /// + /// m.fill_with(n); + /// + /// print(m); // prints "#{a:1, b:2, c:3, d:0}" + /// ``` pub fn fill_with(map: &mut Map, map2: Map) { if !map2.is_empty() { if map.is_empty() { @@ -65,6 +121,22 @@ mod map_functions { } } } + /// Return `true` if two object maps are equal (i.e. all property values are equal). + /// + /// The operator `==` is used to compare property values and must be defined, + /// otherwise `false` is assumed. + /// + /// # Example + /// + /// ```rhai + /// let m1 = #{a:1, b:2, c:3}; + /// let m2 = #{a:1, b:2, c:3}; + /// let m3 = #{a:1, c:3}; + /// + /// print(m1 == m2); // prints true + /// + /// print(m1 == m3); // prints false + /// ``` #[rhai_fn(name = "==", return_raw, pure)] pub fn equals(ctx: NativeCallContext, map1: &mut Map, map2: Map) -> RhaiResultOf { if map1.len() != map2.len() { @@ -92,11 +164,36 @@ mod map_functions { Ok(true) } + /// Return `true` if two object maps are not equal (i.e. at least one property value is not equal). + /// + /// The operator `==` is used to compare property values and must be defined, + /// otherwise `false` is assumed. + /// + /// # Example + /// + /// ```rhai + /// let m1 = #{a:1, b:2, c:3}; + /// let m2 = #{a:1, b:2, c:3}; + /// let m3 = #{a:1, c:3}; + /// + /// print(m1 != m2); // prints false + /// + /// print(m1 != m3); // prints true + /// ``` #[rhai_fn(name = "!=", return_raw, pure)] pub fn not_equals(ctx: NativeCallContext, map1: &mut Map, map2: Map) -> RhaiResultOf { equals(ctx, map1, map2).map(|r| !r) } + /// Return an array with all the property names in the object map. + /// + /// # Example + /// + /// ```rhai + /// let m = #{a:1, b:2, c:3}; + /// + /// print(m.keys()); // prints ["a", "b", "c"] + /// ``` #[cfg(not(feature = "no_index"))] #[rhai_fn(pure)] pub fn keys(map: &mut Map) -> Array { @@ -106,6 +203,15 @@ mod map_functions { map.keys().cloned().map(Into::into).collect() } } + /// Return an array with all the property values in the object map. + /// + /// # Example + /// + /// ```rhai + /// let m = #{a:1, b:2, c:3}; + /// + /// print(m.values()); // prints "[1, 2, 3]"" + /// ``` #[cfg(not(feature = "no_index"))] #[rhai_fn(pure)] pub fn values(map: &mut Map) -> Array { diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 83f2efac..6dd8f630 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -1,7 +1,7 @@ #![allow(non_snake_case)] use crate::plugin::*; -use crate::{def_package, Position, RhaiResultOf, ERR, INT, UNSIGNED_INT}; +use crate::{def_package, Position, RhaiResultOf, ERR, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -108,6 +108,34 @@ def_package! { #[export_module] mod int_functions { + /// Parse a string into an integer number. + /// + /// # Example + /// + /// ```rhai + /// let x = parse_int("123"); + /// + /// print(x); // prints 123 + /// ``` + #[rhai_fn(name = "parse_int", return_raw)] + pub fn parse_int(string: &str) -> RhaiResultOf { + parse_int_radix(string, 10) + } + /// Parse a string into an integer number of the specified `radix`. + /// + /// `radix` must be between 2 and 36. + /// + /// # Example + /// + /// ```rhai + /// let x = parse_int("123"); + /// + /// print(x); // prints 123 + /// + /// let y = parse_int("123abc", 16); + /// + /// print(y); // prints 1194684 (0x123abc) + /// ``` #[rhai_fn(name = "parse_int", return_raw)] pub fn parse_int_radix(string: &str, radix: INT) -> RhaiResultOf { if !(2..=36).contains(&radix) { @@ -118,19 +146,13 @@ mod int_functions { .into()); } - UNSIGNED_INT::from_str_radix(string.trim(), radix as u32) - .map(|v| v as INT) - .map_err(|err| { - ERR::ErrorArithmetic( - format!("Error parsing integer number '{}': {}", string, err), - Position::NONE, - ) - .into() - }) - } - #[rhai_fn(name = "parse_int", return_raw)] - pub fn parse_int(string: &str) -> RhaiResultOf { - parse_int_radix(string, 10) + INT::from_str_radix(string.trim(), radix as u32).map_err(|err| { + ERR::ErrorArithmetic( + format!("Error parsing integer number '{}': {}", string, err), + Position::NONE, + ) + .into() + }) } } @@ -139,46 +161,60 @@ mod int_functions { mod trig_functions { use crate::FLOAT; + /// Return the sine of the floating-point number in radians. pub fn sin(x: FLOAT) -> FLOAT { x.sin() } + /// Return the cosine of the floating-point number in radians. pub fn cos(x: FLOAT) -> FLOAT { x.cos() } + /// Return the tangent of the floating-point number in radians. pub fn tan(x: FLOAT) -> FLOAT { x.tan() } + /// Return the hyperbolic sine of the floating-point number in radians. pub fn sinh(x: FLOAT) -> FLOAT { x.sinh() } + /// Return the hyperbolic cosine of the floating-point number in radians. pub fn cosh(x: FLOAT) -> FLOAT { x.cosh() } + /// Return the hyperbolic tangent of the floating-point number in radians. pub fn tanh(x: FLOAT) -> FLOAT { x.tanh() } + /// Return the arc-sine of the floating-point number, in radians. pub fn asin(x: FLOAT) -> FLOAT { x.asin() } + /// Return the arc-cosine of the floating-point number, in radians. pub fn acos(x: FLOAT) -> FLOAT { x.acos() } + /// Return the arc-tangent of the floating-point number, in radians. pub fn atan(x: FLOAT) -> FLOAT { x.atan() } + /// Return the arc-tangent of the floating-point numbers `x` and `y`, in radians. #[rhai_fn(name = "atan")] pub fn atan2(x: FLOAT, y: FLOAT) -> FLOAT { x.atan2(y) } + /// Return the arc-hyperbolic-sine of the floating-point number, in radians. pub fn asinh(x: FLOAT) -> FLOAT { x.asinh() } + /// Return the arc-hyperbolic-cosine of the floating-point number, in radians. pub fn acosh(x: FLOAT) -> FLOAT { x.acosh() } + /// Return the arc-hyperbolic-tangent of the floating-point number, in radians. pub fn atanh(x: FLOAT) -> FLOAT { x.atanh() } + /// Return the hypotenuse of a triangle with sides `x` and `y`. pub fn hypot(x: FLOAT, y: FLOAT) -> FLOAT { x.hypot(y) } @@ -189,6 +225,7 @@ mod trig_functions { mod float_functions { use crate::FLOAT; + /// Return the natural number _e_. #[rhai_fn(name = "E")] pub fn e() -> FLOAT { #[cfg(not(feature = "f32_float"))] @@ -196,6 +233,7 @@ mod float_functions { #[cfg(feature = "f32_float")] return std::f32::consts::E; } + /// Return the number π. #[rhai_fn(name = "PI")] pub fn pi() -> FLOAT { #[cfg(not(feature = "f32_float"))] @@ -203,60 +241,77 @@ mod float_functions { #[cfg(feature = "f32_float")] return std::f32::consts::PI; } + /// Convert degrees to radians. pub fn to_radians(x: FLOAT) -> FLOAT { x.to_radians() } + /// Convert radians to degrees. pub fn to_degrees(x: FLOAT) -> FLOAT { x.to_degrees() } + /// Return the square root of the floating-point number. pub fn sqrt(x: FLOAT) -> FLOAT { x.sqrt() } + /// Return the exponential of the floating-point number. pub fn exp(x: FLOAT) -> FLOAT { x.exp() } + /// Return the natural log of the floating-point number. pub fn ln(x: FLOAT) -> FLOAT { x.ln() } + /// Return the log of the floating-point number with `base`. pub fn log(x: FLOAT, base: FLOAT) -> FLOAT { x.log(base) } + /// Return the log of the floating-point number with base 10. #[rhai_fn(name = "log")] pub fn log10(x: FLOAT) -> FLOAT { x.log10() } + /// Return the largest whole number less than or equals to the floating-point number. #[rhai_fn(name = "floor", get = "floor")] pub fn floor(x: FLOAT) -> FLOAT { x.floor() } + /// Return the smallest whole number larger than or equals to the floating-point number. #[rhai_fn(name = "ceiling", get = "ceiling")] pub fn ceiling(x: FLOAT) -> FLOAT { x.ceil() } + /// Return the nearest whole number closest to the floating-point number. + /// Rounds away from zero. #[rhai_fn(name = "round", get = "round")] pub fn round(x: FLOAT) -> FLOAT { x.round() } + /// Return the integral part of the floating-point number. #[rhai_fn(name = "int", get = "int")] pub fn int(x: FLOAT) -> FLOAT { x.trunc() } + /// Return the fractional part of the floating-point number. #[rhai_fn(name = "fraction", get = "fraction")] pub fn fraction(x: FLOAT) -> FLOAT { x.fract() } + /// Return `true` if the floating-point number is `NaN` (Not A Number). #[rhai_fn(name = "is_nan", get = "is_nan")] pub fn is_nan(x: FLOAT) -> bool { x.is_nan() } + /// Return `true` if the floating-point number is finite. #[rhai_fn(name = "is_finite", get = "is_finite")] pub fn is_finite(x: FLOAT) -> bool { x.is_finite() } + /// Return `true` if the floating-point number is infinite. #[rhai_fn(name = "is_infinite", get = "is_infinite")] pub fn is_infinite(x: FLOAT) -> bool { x.is_infinite() } + /// Return the integral part of the floating-point number. #[rhai_fn(name = "to_int", return_raw)] pub fn f32_to_int(x: f32) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) && x > (INT::MAX as f32) { @@ -268,6 +323,7 @@ mod float_functions { Ok(x.trunc() as INT) } } + /// Return the integral part of the floating-point number. #[rhai_fn(name = "to_int", return_raw)] pub fn f64_to_int(x: f64) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) && x > (INT::MAX as f64) { @@ -279,6 +335,15 @@ mod float_functions { Ok(x.trunc() as INT) } } + /// Parse a string into a floating-point number. + /// + /// # Example + /// + /// ```rhai + /// let x = parse_int("123.456"); + /// + /// print(x); // prints 123.456 + /// ``` #[rhai_fn(return_raw)] pub fn parse_float(string: &str) -> RhaiResultOf { string.trim().parse::().map_err(|err| { @@ -289,6 +354,7 @@ mod float_functions { .into() }) } + /// Convert the 32-bit floating-point number to 64-bit. #[cfg(not(feature = "f32_float"))] #[rhai_fn(name = "to_float")] pub fn f32_to_f64(x: f32) -> f64 { @@ -306,36 +372,52 @@ mod decimal_functions { #[cfg(not(feature = "no_float"))] use std::convert::TryFrom; + /// Return the natural number _e_. #[cfg(feature = "no_float")] #[rhai_fn(name = "PI")] pub fn pi() -> Decimal { Decimal::PI } + /// Return the number π. #[cfg(feature = "no_float")] #[rhai_fn(name = "E")] pub fn e() -> Decimal { Decimal::E } + /// Parse a string into a decimal number. + /// + /// # Example + /// + /// ```rhai + /// let x = parse_float("123.456"); + /// + /// print(x); // prints 123.456 + /// ``` #[cfg(feature = "no_float")] #[rhai_fn(return_raw)] pub fn parse_float(s: &str) -> RhaiResultOf { parse_decimal(s) } + /// Return the sine of the decimal number in radians. pub fn sin(x: Decimal) -> Decimal { x.sin() } + /// Return the cosine of the decimal number in radians. pub fn cos(x: Decimal) -> Decimal { x.cos() } + /// Return the tangent of the decimal number in radians. pub fn tan(x: Decimal) -> Decimal { x.tan() } + /// Return the square root of the decimal number. #[rhai_fn(return_raw)] pub fn sqrt(x: Decimal) -> RhaiResultOf { x.sqrt() .ok_or_else(|| make_err(format!("Error taking the square root of {}", x,))) } + /// Return the exponential of the decimal number. #[rhai_fn(return_raw)] pub fn exp(x: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { @@ -345,6 +427,7 @@ mod decimal_functions { Ok(x.exp()) } } + /// Return the natural log of the decimal number. #[rhai_fn(return_raw)] pub fn ln(x: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { @@ -354,6 +437,7 @@ mod decimal_functions { Ok(x.ln()) } } + /// Return the log of the decimal number with base 10. #[rhai_fn(name = "log", return_raw)] pub fn log10(x: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { @@ -363,106 +447,131 @@ mod decimal_functions { Ok(x.log10()) } } + /// Return the largest whole number less than or equals to the decimal number. #[rhai_fn(name = "floor", get = "floor")] pub fn floor(x: Decimal) -> Decimal { x.floor() } + /// Return the smallest whole number larger than or equals to the decimal number. #[rhai_fn(name = "ceiling", get = "ceiling")] pub fn ceiling(x: Decimal) -> Decimal { x.ceil() } + /// Return the nearest whole number closest to the decimal number. + /// Always round mid-point towards the closest even number. #[rhai_fn(name = "round", get = "round")] pub fn round(x: Decimal) -> Decimal { x.round() } + /// Round the decimal number to the specified number of `digits` after the decimal point and return it. + /// Always round mid-point towards the closest even number. #[rhai_fn(name = "round", return_raw)] - pub fn round_dp(x: Decimal, dp: INT) -> RhaiResultOf { + pub fn round_dp(x: Decimal, digits: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { - if dp < 0 { + if digits < 0 { return Err(make_err(format!( "Invalid number of digits for rounding: {}", - dp + digits ))); } - if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { + if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { return Ok(x); } } - Ok(x.round_dp(dp as u32)) + Ok(x.round_dp(digits as u32)) } + /// Round the decimal number to the specified number of `digits` after the decimal point and return it. + /// Always round away from zero. #[rhai_fn(return_raw)] - pub fn round_up(x: Decimal, dp: INT) -> RhaiResultOf { + pub fn round_up(x: Decimal, digits: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { - if dp < 0 { + if digits < 0 { return Err(make_err(format!( "Invalid number of digits for rounding: {}", - dp + digits ))); } - if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { + if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { return Ok(x); } } - Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::AwayFromZero)) + Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::AwayFromZero)) } + /// Round the decimal number to the specified number of `digits` after the decimal point and return it. + /// Always round towards zero. #[rhai_fn(return_raw)] - pub fn round_down(x: Decimal, dp: INT) -> RhaiResultOf { + pub fn round_down(x: Decimal, digits: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { - if dp < 0 { + if digits < 0 { return Err(make_err(format!( "Invalid number of digits for rounding: {}", - dp + digits ))); } - if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { + if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { return Ok(x); } } - Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::ToZero)) + Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::ToZero)) } + /// Round the decimal number to the specified number of `digits` after the decimal point and return it. + /// Always round mid-points away from zero. #[rhai_fn(return_raw)] - pub fn round_half_up(x: Decimal, dp: INT) -> RhaiResultOf { + pub fn round_half_up(x: Decimal, digits: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { - if dp < 0 { + if digits < 0 { return Err(make_err(format!( "Invalid number of digits for rounding: {}", - dp + digits ))); } - if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { + if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { return Ok(x); } } - Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::MidpointAwayFromZero)) + Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::MidpointAwayFromZero)) } + /// Round the decimal number to the specified number of `digits` after the decimal point and return it. + /// Always round mid-points towards zero. #[rhai_fn(return_raw)] - pub fn round_half_down(x: Decimal, dp: INT) -> RhaiResultOf { + pub fn round_half_down(x: Decimal, digits: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { - if dp < 0 { + if digits < 0 { return Err(make_err(format!( "Invalid number of digits for rounding: {}", - dp + digits ))); } - if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { + if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { return Ok(x); } } - Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::MidpointTowardZero)) + Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::MidpointTowardZero)) } + /// Return the integral part of the decimal number. #[rhai_fn(name = "int", get = "int")] pub fn int(x: Decimal) -> Decimal { x.trunc() } + /// Return the fractional part of the decimal number. #[rhai_fn(name = "fraction", get = "fraction")] pub fn fraction(x: Decimal) -> Decimal { x.fract() } + /// Parse a string into a decimal number. + /// + /// # Example + /// + /// ```rhai + /// let x = parse_decimal("123.456"); + /// + /// print(x); // prints 123.456 + /// ``` #[rhai_fn(return_raw)] pub fn parse_decimal(string: &str) -> RhaiResultOf { Decimal::from_str(string) @@ -476,6 +585,7 @@ mod decimal_functions { }) } + /// Convert the floating-point number to decimal. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "to_decimal", return_raw)] pub fn f32_to_decimal(x: f32) -> RhaiResultOf { @@ -487,6 +597,7 @@ mod decimal_functions { .into() }) } + /// Convert the floating-point number to decimal. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "to_decimal", return_raw)] pub fn f64_to_decimal(x: f64) -> RhaiResultOf { @@ -498,6 +609,7 @@ mod decimal_functions { .into() }) } + /// Convert the decimal number to floating-point. #[cfg(not(feature = "no_float"))] #[rhai_fn(return_raw)] pub fn to_float(x: Decimal) -> RhaiResultOf { diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 1debcbb2..1e4dd59e 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -46,56 +46,68 @@ pub fn print_with_func( mod print_debug_functions { use crate::ImmutableString; + /// Convert the value of the `item` into a string. #[rhai_fn(name = "print", pure)] pub fn print_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { print_with_func(FUNC_TO_STRING, &ctx, item) } + /// Convert the value of the `item` into a string. #[rhai_fn(name = "to_string", pure)] pub fn to_string_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { ctx.engine().map_type_name(&item.to_string()).into() } + /// Convert the value of the `item` into a string in debug format. #[rhai_fn(name = "debug", pure)] pub fn debug_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { print_with_func(FUNC_TO_DEBUG, &ctx, item) } + /// Convert the value of the `item` into a string in debug format. #[rhai_fn(name = "to_debug", pure)] pub fn to_debug_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { ctx.engine().map_type_name(&format!("{:?}", item)).into() } + /// Return the empty string. #[rhai_fn(name = "print", name = "debug")] pub fn print_empty_string(ctx: NativeCallContext) -> ImmutableString { ctx.engine().const_empty_string() } + /// Return the `string`. #[rhai_fn(name = "print", name = "to_string")] - pub fn print_string(s: ImmutableString) -> ImmutableString { - s + pub fn print_string(string: ImmutableString) -> ImmutableString { + string } + /// Convert the function pointer into a string in debug format. #[rhai_fn(name = "debug", name = "to_debug", pure)] pub fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString { f.to_string().into() } + /// Convert the value of `number` into a string. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "print", name = "to_string")] pub fn print_f64(number: f64) -> ImmutableString { crate::ast::FloatWrapper::new(number).to_string().into() } + /// Convert the value of `number` into a string. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "print", name = "to_string")] pub fn print_f32(number: f32) -> ImmutableString { crate::ast::FloatWrapper::new(number).to_string().into() } + /// Convert the value of `number` into a string. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_f64(number: f64) -> ImmutableString { format!("{:?}", crate::ast::FloatWrapper::new(number)).into() } + /// Convert the value of `number` into a string. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_f32(number: f32) -> ImmutableString { format!("{:?}", crate::ast::FloatWrapper::new(number)).into() } + /// Convert the array into a string. #[cfg(not(feature = "no_index"))] #[rhai_fn( name = "print", @@ -119,6 +131,8 @@ mod print_debug_functions { result.push(']'); result.into() } + + /// Convert the object map into a string. #[cfg(not(feature = "no_object"))] #[rhai_fn( name = "print", @@ -148,27 +162,27 @@ mod print_debug_functions { #[export_module] mod number_formatting { - #[rhai_fn(skip)] - pub fn to_hex(value: T) -> ImmutableString { + fn to_hex(value: T) -> ImmutableString { format!("{:x}", value).into() } - #[rhai_fn(skip)] - pub fn to_octal(value: T) -> ImmutableString { + fn to_octal(value: T) -> ImmutableString { format!("{:o}", value).into() } - #[rhai_fn(skip)] - pub fn to_binary(value: T) -> ImmutableString { + fn to_binary(value: T) -> ImmutableString { format!("{:b}", value).into() } + /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn int_to_hex(value: INT) -> ImmutableString { to_hex(value) } + /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn int_to_octal(value: INT) -> ImmutableString { to_octal(value) } + /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn int_to_binary(value: INT) -> ImmutableString { to_binary(value) @@ -177,98 +191,122 @@ mod number_formatting { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] pub mod numbers { + /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn u8_to_hex(value: u8) -> ImmutableString { to_hex(value) } + /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn u16_to_hex(value: u16) -> ImmutableString { to_hex(value) } + /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn u32_to_hex(value: u32) -> ImmutableString { to_hex(value) } + /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn u64_to_hex(value: u64) -> ImmutableString { to_hex(value) } + /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn i8_to_hex(value: i8) -> ImmutableString { to_hex(value) } + /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn i16_to_hex(value: i16) -> ImmutableString { to_hex(value) } + /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn i32_to_hex(value: i32) -> ImmutableString { to_hex(value) } + /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn i64_to_hex(value: i64) -> ImmutableString { to_hex(value) } + /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn u8_to_octal(value: u8) -> ImmutableString { to_octal(value) } + /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn u16_to_octal(value: u16) -> ImmutableString { to_octal(value) } + /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn u32_to_octal(value: u32) -> ImmutableString { to_octal(value) } + /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn u64_to_octal(value: u64) -> ImmutableString { to_octal(value) } + /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn i8_to_octal(value: i8) -> ImmutableString { to_octal(value) } + /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn i16_to_octal(value: i16) -> ImmutableString { to_octal(value) } + /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn i32_to_octal(value: i32) -> ImmutableString { to_octal(value) } + /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn i64_to_octal(value: i64) -> ImmutableString { to_octal(value) } + /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn u8_to_binary(value: u8) -> ImmutableString { to_binary(value) } + /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn u16_to_binary(value: u16) -> ImmutableString { to_binary(value) } + /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn u32_to_binary(value: u32) -> ImmutableString { to_binary(value) } + /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn u64_to_binary(value: u64) -> ImmutableString { to_binary(value) } + /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn i8_to_binary(value: i8) -> ImmutableString { to_binary(value) } + /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn i16_to_binary(value: i16) -> ImmutableString { to_binary(value) } + /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn i32_to_binary(value: i32) -> ImmutableString { to_binary(value) } + /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn i64_to_binary(value: i64) -> ImmutableString { to_binary(value) @@ -277,14 +315,32 @@ mod number_formatting { #[cfg(not(target_family = "wasm"))] pub mod num_128 { + /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn u128_to_hex(value: u128) -> ImmutableString { to_hex(value) } + /// Convert the `value` into a string in hex format. + #[rhai_fn(name = "to_hex")] + pub fn i128_to_hex(value: i128) -> ImmutableString { + to_hex(value) + } + /// Convert the `value` into a string in octal format. + #[rhai_fn(name = "to_octal")] + pub fn u128_to_octal(value: u128) -> ImmutableString { + to_octal(value) + } + /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn i128_to_octal(value: i128) -> ImmutableString { to_octal(value) } + /// Convert the `value` into a string in binary format. + #[rhai_fn(name = "to_binary")] + pub fn u128_to_binary(value: u128) -> ImmutableString { + to_binary(value) + } + /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn i128_to_binary(value: i128) -> ImmutableString { to_binary(value) diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 515dda99..7ab926e2 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -73,6 +73,15 @@ mod string_functions { string } + /// Return the length of the string, in number of characters. + /// + /// # Example + /// + /// ```rhai + /// let text = "朝には紅顔ありて夕べには白骨となる"; + /// + /// print(text.len); // prints 17 + /// ``` #[rhai_fn(name = "len", get = "len")] pub fn len(string: &str) -> INT { if string.is_empty() { @@ -81,6 +90,15 @@ mod string_functions { string.chars().count() as INT } } + /// Return the length of the string, in number of bytes used to store it in UTF-8 encoding. + /// + /// # Example + /// + /// ```rhai + /// let text = "朝には紅顔ありて夕べには白骨となる"; + /// + /// print(text.bytes); // prints 51 + /// ``` #[rhai_fn(name = "bytes", get = "bytes")] pub fn bytes(string: &str) -> INT { if string.is_empty() { @@ -89,18 +107,59 @@ mod string_functions { string.len() as INT } } + /// Remove all occurrences of a sub-string from the string. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foobar!"; + /// + /// text.remove("hello"); + /// + /// print(text); // prints ", world! , foobar!" + /// ``` pub fn remove(string: &mut ImmutableString, sub_string: ImmutableString) { *string -= sub_string; } + /// Remove all occurrences of a character from the string. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foobar!"; + /// + /// text.remove("o"); + /// + /// print(text); // prints "hell, wrld! hell, fbar!" + /// ``` #[rhai_fn(name = "remove")] pub fn remove_char(string: &mut ImmutableString, character: char) { *string -= character; } + /// Clear the string, making it empty. pub fn clear(string: &mut ImmutableString) { if !string.is_empty() { string.make_mut().clear(); } } + /// Cut off the string at the specified number of characters. + /// + /// * If `len` ≤ 0, the string is cleared. + /// * If `len` ≥ length of string, the string is not truncated. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foobar!"; + /// + /// text.truncate(13); + /// + /// print(text); // prints "hello, world!" + /// + /// x.truncate(10); + /// + /// print(text); // prints "hello, world!" + /// ``` pub fn truncate(string: &mut ImmutableString, len: INT) { if len > 0 { let chars: StaticVec<_> = string.chars().collect(); @@ -111,6 +170,15 @@ mod string_functions { string.make_mut().clear(); } } + /// Remove whitespace characters from both ends of the string. + /// + /// # Example + /// + /// ```rhai + /// let text = " hello "; + /// + /// print(text.trim()); // prints "hello" + /// ``` pub fn trim(string: &mut ImmutableString) { let trimmed = string.trim(); @@ -118,6 +186,19 @@ mod string_functions { *string = trimmed.to_string().into(); } } + /// Remove the last character from the string and return it. + /// + /// If the string is empty, `()` is returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.pop()); // prints '!' + /// + /// print(text); // prints "hello, world" + /// ``` pub fn pop(string: &mut ImmutableString) -> Dynamic { if string.is_empty() { Dynamic::UNIT @@ -128,6 +209,21 @@ mod string_functions { } } } + /// Remove the a specified number of characters from the end of the string and return it as a + /// new string. + /// + /// * If `len` ≤ 0, the string is not modified and an empty string is returned. + /// * If `len` ≥ length of string, the string is cleared and the entire string returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.pop(4)); // prints "rld!" + /// + /// print(text); // prints "hello, wo" + /// ``` #[rhai_fn(name = "pop")] pub fn pop_string( ctx: NativeCallContext, @@ -150,6 +246,17 @@ mod string_functions { chars.into_iter().rev().collect::().into() } + /// Convert the string to all upper-case and return it as a new string. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!" + /// + /// print(text.to_upper()); // prints "HELLO, WORLD!" + /// + /// print(text); // prints "hello, world!" + /// ``` pub fn to_upper(string: ImmutableString) -> ImmutableString { if string.is_empty() { string @@ -157,11 +264,33 @@ mod string_functions { string.to_uppercase().into() } } + /// Convert the string to all upper-case. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!" + /// + /// text.make_upper(); + /// + /// print(text); // prints "HELLO, WORLD!"; + /// ``` pub fn make_upper(string: &mut ImmutableString) { if !string.is_empty() { *string = string.to_uppercase().into(); } } + /// Convert the string to all lower-case and return it as a new string. + /// + /// # Example + /// + /// ```rhai + /// let text = "HELLO, WORLD!" + /// + /// print(text.to_lower()); // prints "hello, world!" + /// + /// print(text); // prints "HELLO, WORLD!" + /// ``` pub fn to_lower(string: ImmutableString) -> ImmutableString { if string.is_empty() { string @@ -169,12 +298,34 @@ mod string_functions { string.to_lowercase().into() } } + /// Convert the string to all lower-case. + /// + /// # Example + /// + /// ```rhai + /// let text = "HELLO, WORLD!" + /// + /// text.make_lower(); + /// + /// print(text); // prints "hello, world!"; + /// ``` pub fn make_lower(string: &mut ImmutableString) { if !string.is_empty() { *string = string.to_lowercase().into(); } } + /// Convert the character to upper-case and return it as a new character. + /// + /// # Example + /// + /// ```rhai + /// let ch = 'a'; + /// + /// print(ch.to_upper()); // prints 'A' + /// + /// print(ch); // prints 'a' + /// ``` #[rhai_fn(name = "to_upper")] pub fn to_upper_char(character: char) -> char { let mut stream = character.to_uppercase(); @@ -185,10 +336,32 @@ mod string_functions { ch } } + /// Convert the character to upper-case. + /// + /// # Example + /// + /// ```rhai + /// let ch = 'a'; + /// + /// ch.make_upper(); + /// + /// print(ch); // prints 'A' + /// ``` #[rhai_fn(name = "make_upper")] pub fn make_upper_char(character: &mut char) { *character = to_upper_char(*character) } + /// Convert the character to lower-case and return it as a new character. + /// + /// # Example + /// + /// ```rhai + /// let ch = 'A'; + /// + /// print(ch.to_lower()); // prints 'a' + /// + /// print(ch); // prints 'A' + /// ``` #[rhai_fn(name = "to_lower")] pub fn to_lower_char(character: char) -> char { let mut stream = character.to_lowercase(); @@ -199,11 +372,41 @@ mod string_functions { ch } } + /// Convert the character to lower-case. + /// + /// # Example + /// + /// ```rhai + /// let ch = 'A'; + /// + /// ch.make_lower(); + /// + /// print(ch); // prints 'a' + /// ``` #[rhai_fn(name = "make_lower")] pub fn make_lower_char(character: &mut char) { *character = to_lower_char(*character) } + /// Find the specified `character` in the string, starting from the specified `start` position, + /// and return the first index where it is found. + /// If the `character` is not found, `-1` is returned. + /// + /// * If `start` < 0, position counts from the end of the string (`-1` is the last character). + /// * If `start` < -length of string, position counts from the beginning of the string. + /// * If `start` ≥ length of string, `-1` is returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.index_of('l', 5)); // prints 10 (first index after 5) + /// + /// print(text.index_of('o', -7)); // prints 8 + /// + /// print(text.index_of('x', 0)); // prints -1 + /// ``` #[rhai_fn(name = "index_of")] pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT { if string.is_empty() { @@ -243,6 +446,18 @@ mod string_functions { .map(|index| string[0..start + index].chars().count() as INT) .unwrap_or(-1 as INT) } + /// Find the specified `character` in the string and return the first index where it is found. + /// If the `character` is not found, `-1` is returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.index_of('l')); // prints 2 (first index) + /// + /// print(text.index_of('x')); // prints -1 + /// ``` #[rhai_fn(name = "index_of")] pub fn index_of_char(string: &str, character: char) -> INT { if string.is_empty() { @@ -254,6 +469,25 @@ mod string_functions { .unwrap_or(-1 as INT) } } + /// Find the specified sub-string in the string, starting from the specified `start` position, + /// and return the first index where it is found. + /// If the sub-string is not found, `-1` is returned. + /// + /// * If `start` < 0, position counts from the end of the string (`-1` is the last character). + /// * If `start` < -length of string, position counts from the beginning of the string. + /// * If `start` ≥ length of string, `-1` is returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foobar!"; + /// + /// print(text.index_of("ll", 5)); // prints 16 (first index after 5) + /// + /// print(text.index_of("ll", -15)); // prints 16 + /// + /// print(text.index_of("xx", 0)); // prints -1 + /// ``` #[rhai_fn(name = "index_of")] pub fn index_of_string_starting_from(string: &str, find_string: &str, start: INT) -> INT { if string.is_empty() { @@ -293,6 +527,18 @@ mod string_functions { .map(|index| string[0..start + index].chars().count() as INT) .unwrap_or(-1 as INT) } + /// Find the specified `character` in the string and return the first index where it is found. + /// If the `character` is not found, `-1` is returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foobar!"; + /// + /// print(text.index_of("ll")); // prints 2 (first index) + /// + /// print(text.index_of("xx:)); // prints -1 + /// ``` #[rhai_fn(name = "index_of")] pub fn index_of(string: &str, find_string: &str) -> INT { if string.is_empty() { @@ -305,6 +551,15 @@ mod string_functions { } } + /// Copy an exclusive range of characters from the string and return it as a new string. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.sub_string(3..7)); // prints "lo, " + /// ``` #[rhai_fn(name = "sub_string")] pub fn sub_string_range( ctx: NativeCallContext, @@ -315,6 +570,15 @@ mod string_functions { let end = INT::max(range.end, start); sub_string(ctx, string, start, end - start) } + /// Copy an inclusive range of characters from the string and return it as a new string. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.sub_string(3..=7)); // prints "lo, w" + /// ``` #[rhai_fn(name = "sub_string")] pub fn sub_string_inclusive_range( ctx: NativeCallContext, @@ -325,6 +589,23 @@ mod string_functions { let end = INT::max(*range.end(), start); sub_string(ctx, string, start, end - start + 1) } + /// Copy a portion of the string and return it as a new string. + /// + /// * If `start` < 0, position counts from the end of the string (`-1` is the last character). + /// * If `start` < -length of string, position counts from the beginning of the string. + /// * If `start` ≥ length of string, an empty string is returned. + /// * If `len` ≤ 0, an empty string is returned. + /// * If `start` position + `len` ≥ length of string, entire portion of the string after the `start` position is copied and returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.sub_string(3, 4)); // prints "lo, " + /// + /// print(text.sub_string(-8, 3)); // prints ", w" + /// ``` pub fn sub_string( ctx: NativeCallContext, string: &str, @@ -374,6 +655,22 @@ mod string_functions { .collect::() .into() } + /// Copy a portion of the string beginning at the `start` position till the end and return it as + /// a new string. + /// + /// * If `start` < 0, position counts from the end of the string (`-1` is the last character). + /// * If `start` < -length of string, the entire string is copied and returned. + /// * If `start` ≥ length of string, an empty string is returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.sub_string(5)); // prints ", world!" + /// + /// print(text.sub_string(-5)); // prints "orld!" + /// ``` #[rhai_fn(name = "sub_string")] pub fn sub_string_starting_from( ctx: NativeCallContext, @@ -388,18 +685,62 @@ mod string_functions { } } + /// Remove all characters from the string except those within an exclusive range. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// text.crop(2..8); + /// + /// print(text); // prints "llo, w" + /// ``` #[rhai_fn(name = "crop")] pub fn crop_range(string: &mut ImmutableString, range: ExclusiveRange) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); crop(string, start, end - start) } + /// Remove all characters from the string except those within an inclusive range. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// text.crop(2..=8); + /// + /// print(text); // prints "llo, wo" + /// ``` #[rhai_fn(name = "crop")] pub fn crop_inclusive_range(string: &mut ImmutableString, range: InclusiveRange) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); crop(string, start, end - start + 1) } + + /// Remove all characters from the string except those within a range. + /// + /// * If `start` < 0, position counts from the end of the string (`-1` is the last character). + /// * If `start` < -length of string, position counts from the beginning of the string. + /// * If `start` ≥ length of string, the entire string is cleared. + /// * If `len` ≤ 0, the entire string is cleared. + /// * If `start` position + `len` ≥ length of string, only the portion of the string after the `start` position is retained. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// text.crop(2, 8); + /// + /// print(text); // prints "llo, wor" + /// + /// text.crop(-5, 3); + /// + /// print(text); // prints ", w" + /// ``` #[rhai_fn(name = "crop")] pub fn crop(string: &mut ImmutableString, start: INT, len: INT) { if string.is_empty() { @@ -443,17 +784,58 @@ mod string_functions { copy.clear(); copy.extend(chars.iter().skip(offset).take(len)); } + /// Remove all characters from the string except until the `start` position. + /// + /// * If `start` < 0, position counts from the end of the string (`-1` is the last character). + /// * If `start` < -length of string, the string is not modified. + /// * If `start` ≥ length of string, the entire string is cleared. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// text.crop(5); + /// + /// print(text); // prints ", world!" + /// + /// text.crop(-3); + /// + /// print(text); // prints "ld!" + /// ``` #[rhai_fn(name = "crop")] pub fn crop_string_starting_from(string: &mut ImmutableString, start: INT) { crop(string, start, string.len() as INT); } + /// Replace all occurrences of the specified sub-string in the string with another string. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foobar!"; + /// + /// text.replace("hello", "hey"); + /// + /// print(text); // prints "hey, world! hey, foobar!" + /// ``` #[rhai_fn(name = "replace")] pub fn replace(string: &mut ImmutableString, find_string: &str, substitute_string: &str) { if !string.is_empty() { *string = string.replace(find_string, substitute_string).into(); } } + /// Replace all occurrences of the specified sub-string in the string with the specified character. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foobar!"; + /// + /// text.replace("hello", '*'); + /// + /// print(text); // prints "*, world! *, foobar!" + /// ``` #[rhai_fn(name = "replace")] pub fn replace_string_with_char( string: &mut ImmutableString, @@ -466,6 +848,17 @@ mod string_functions { .into(); } } + /// Replace all occurrences of the specified character in the string with another string. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foobar!"; + /// + /// text.replace('l', "(^)"); + /// + /// print(text); // prints "he(^)(^)o, wor(^)d! he(^)(^)o, foobar!" + /// ``` #[rhai_fn(name = "replace")] pub fn replace_char_with_string( string: &mut ImmutableString, @@ -478,6 +871,17 @@ mod string_functions { .into(); } } + /// Replace all occurrences of the specified character in the string with another character. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foobar!"; + /// + /// text.replace("l", '*'); + /// + /// print(text); // prints "he**o, wor*d! he**o, foobar!" + /// ``` #[rhai_fn(name = "replace")] pub fn replace_char( string: &mut ImmutableString, @@ -494,6 +898,23 @@ mod string_functions { } } + /// Pad the string to at least the specified number of characters with the specified `character`. + /// + /// If `len` ≤ length of string, no padding is done. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello"; + /// + /// text.pad(8, '!'); + /// + /// print(text); // prints "hello!!!" + /// + /// text.pad(5, '*'); + /// + /// print(text); // prints "hello!!!" + /// ``` #[rhai_fn(return_raw)] pub fn pad( ctx: NativeCallContext, @@ -538,6 +959,23 @@ mod string_functions { Ok(()) } + /// Pad the string to at least the specified number of characters with the specified string. + /// + /// If `len` ≤ length of string, no padding is done. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello"; + /// + /// text.pad(10, "(!)"); + /// + /// print(text); // prints "hello(!)(!)" + /// + /// text.pad(8, '***'); + /// + /// print(text); // prints "hello(!)(!)" + /// ``` #[rhai_fn(name = "pad", return_raw)] pub fn pad_with_string( ctx: NativeCallContext, @@ -594,6 +1032,14 @@ mod string_functions { pub mod arrays { use crate::{Array, ImmutableString}; + /// Return an array containing all the characters of the string. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello"; + /// + /// print(text.split()); // prints "['h', 'e', 'l', 'l', 'o']" #[rhai_fn(name = "split")] pub fn chars(string: &str) -> Array { if string.is_empty() { @@ -602,10 +1048,32 @@ mod string_functions { string.chars().map(Into::into).collect() } } + /// Split the string into two at the specified `index` position and return it both strings + /// as an array. + /// + /// The character at the `index` position (if any) is returned in the _second_ string. + /// + /// * If `index` < 0, position counts from the end of the string (`-1` is the last character). + /// * If `index` < -length of string, it is equivalent to cutting at position 0. + /// * If `index` ≥ length of string, it is equivalent to cutting at the end of the string. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.split(6)); // prints ["hello,", " world!"] + /// + /// print(text.split(13)); // prints ["hello, world!", ""] + /// + /// print(text.split(-6)); // prints ["hello, ", "world!"] + /// + /// print(text.split(-99)); // prints ["", "hello, world!"] + /// ``` #[rhai_fn(name = "split")] - pub fn split_at(ctx: NativeCallContext, string: ImmutableString, start: INT) -> Array { - if start <= 0 { - if let Some(n) = start.checked_abs() { + pub fn split_at(ctx: NativeCallContext, string: ImmutableString, index: INT) -> Array { + if index <= 0 { + if let Some(n) = index.checked_abs() { let num_chars = string.chars().count(); if n as usize > num_chars { vec![ctx.engine().const_empty_string().into(), string.into()] @@ -618,41 +1086,127 @@ mod string_functions { vec![ctx.engine().const_empty_string().into(), string.into()] } } else { - let prefix: String = string.chars().take(start as usize).collect(); + let prefix: String = string.chars().take(index as usize).collect(); let prefix_len = prefix.len(); vec![prefix.into(), string[prefix_len..].into()] } } + /// Split the string into segments based on a `delimiter` string, returning an array of the segments. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foo!"; + /// + /// print(text.split("ll")); // prints ["he", "o, world! he", "o, foo!"] + /// ``` pub fn split(string: &str, delimiter: &str) -> Array { string.split(delimiter).map(Into::into).collect() } + /// Split the string into at most the specified number of `segments` based on a `delimiter` string, + /// returning an array of the segments. + /// + /// If `segments` < 1, only one segment is returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foo!"; + /// + /// print(text.split("ll", 2)); // prints ["he", "o, world! hello, foo!"] + /// ``` #[rhai_fn(name = "split")] pub fn splitn(string: &str, delimiter: &str, segments: INT) -> Array { let pieces: usize = if segments < 1 { 1 } else { segments as usize }; string.splitn(pieces, delimiter).map(Into::into).collect() } + /// Split the string into segments based on a `delimiter` character, returning an array of the segments. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foo!"; + /// + /// print(text.split('l')); // prints ["he", "", "o, wor", "d! he", "", "o, foo!"] + /// ``` #[rhai_fn(name = "split")] pub fn split_char(string: &str, delimiter: char) -> Array { string.split(delimiter).map(Into::into).collect() } + /// Split the string into at most the specified number of `segments` based on a `delimiter` character, + /// returning an array of the segments. + /// + /// If `segments` < 1, only one segment is returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foo!"; + /// + /// print(text.split('l', 3)); // prints ["he", "", "o, world! hello, foo!"] + /// ``` #[rhai_fn(name = "split")] pub fn splitn_char(string: &str, delimiter: char, segments: INT) -> Array { let pieces: usize = if segments < 1 { 1 } else { segments as usize }; string.splitn(pieces, delimiter).map(Into::into).collect() } + /// Split the string into segments based on a `delimiter` string, returning an array of the + /// segments in _reverse_ order. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foo!"; + /// + /// print(text.split_rev("ll")); // prints ["o, foo!", "o, world! he", "he"] + /// ``` #[rhai_fn(name = "split_rev")] pub fn rsplit(string: &str, delimiter: &str) -> Array { string.rsplit(delimiter).map(Into::into).collect() } + /// Split the string into at most a specified number of `segments` based on a `delimiter` string, + /// returning an array of the segments in _reverse_ order. + /// + /// If `segments` < 1, only one segment is returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foo!"; + /// + /// print(text.split_rev("ll", 2)); // prints ["o, foo!", "hello, world! he"] + /// ``` #[rhai_fn(name = "split_rev")] pub fn rsplitn(string: &str, delimiter: &str, segments: INT) -> Array { let pieces: usize = if segments < 1 { 1 } else { segments as usize }; string.rsplitn(pieces, delimiter).map(Into::into).collect() } + /// Split the string into segments based on a `delimiter` character, returning an array of + /// the segments in _reverse_ order. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foo!"; + /// + /// print(text.split_rev('l')); // prints ["o, foo!", "", "d! he", "o, wor", "", "he"] + /// ``` #[rhai_fn(name = "split_rev")] pub fn rsplit_char(string: &str, delimiter: char) -> Array { string.rsplit(delimiter).map(Into::into).collect() } + /// Split the string into at most the specified number of `segments` based on a `delimiter` character, + /// returning an array of the segments. + /// + /// If `segments` < 1, only one segment is returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foo!"; + /// + /// print(text.split('l', 3)); // prints ["o, foo!", "", "hello, world! he" + /// ``` #[rhai_fn(name = "split_rev")] pub fn rsplitn_char(string: &str, delimiter: char, segments: INT) -> Array { let pieces: usize = if segments < 1 { 1 } else { segments as usize }; diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 54254611..3eacee0b 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -25,10 +25,12 @@ def_package! { #[export_module] mod time_functions { + /// Create a timestamp containing the current system time. pub fn timestamp() -> Instant { Instant::now() } + /// Return the number of seconds between the current system time and the timestamp. #[rhai_fn(name = "elapsed", get = "elapsed", return_raw)] pub fn elapsed(timestamp: Instant) -> RhaiResult { #[cfg(not(feature = "no_float"))] @@ -55,6 +57,7 @@ mod time_functions { } } + /// Return the number of seconds between two timestamps. #[rhai_fn(return_raw, name = "-")] pub fn time_diff(timestamp1: Instant, timestamp2: Instant) -> RhaiResult { #[cfg(not(feature = "no_float"))] @@ -140,19 +143,23 @@ mod time_functions { } } + /// Add the specified number of `seconds` to the timestamp and return it as a new timestamp. #[rhai_fn(return_raw, name = "+")] pub fn add(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf { add_impl(timestamp, seconds) } + /// Add the specified number of `seconds` to the timestamp. #[rhai_fn(return_raw, name = "+=")] pub fn add_assign(timestamp: &mut Instant, seconds: FLOAT) -> RhaiResultOf<()> { *timestamp = add_impl(*timestamp, seconds)?; Ok(()) } + /// Subtract the specified number of `seconds` from the timestamp and return it as a new timestamp. #[rhai_fn(return_raw, name = "-")] pub fn subtract(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf { subtract_impl(timestamp, seconds) } + /// Subtract the specified number of `seconds` from the timestamp. #[rhai_fn(return_raw, name = "-=")] pub fn subtract_assign(timestamp: &mut Instant, seconds: FLOAT) -> RhaiResultOf<()> { *timestamp = subtract_impl(*timestamp, seconds)?; @@ -193,45 +200,55 @@ mod time_functions { } } + /// Add the specified number of `seconds` to the timestamp and return it as a new timestamp. #[rhai_fn(return_raw, name = "+")] pub fn add(timestamp: Instant, seconds: INT) -> RhaiResultOf { add_impl(timestamp, seconds) } + /// Add the specified number of `seconds` to the timestamp. #[rhai_fn(return_raw, name = "+=")] pub fn add_assign(timestamp: &mut Instant, seconds: INT) -> RhaiResultOf<()> { *timestamp = add_impl(*timestamp, seconds)?; Ok(()) } + /// Subtract the specified number of `seconds` from the timestamp and return it as a new timestamp. #[rhai_fn(return_raw, name = "-")] pub fn subtract(timestamp: Instant, seconds: INT) -> RhaiResultOf { subtract_impl(timestamp, seconds) } + /// Subtract the specified number of `seconds` from the timestamp. #[rhai_fn(return_raw, name = "-=")] pub fn subtract_assign(timestamp: &mut Instant, seconds: INT) -> RhaiResultOf<()> { *timestamp = subtract_impl(*timestamp, seconds)?; Ok(()) } + /// Return `true` if two timestamps are equal. #[rhai_fn(name = "==")] pub fn eq(timestamp1: Instant, timestamp2: Instant) -> bool { timestamp1 == timestamp2 } + /// Return `true` if two timestamps are not equal. #[rhai_fn(name = "!=")] pub fn ne(timestamp1: Instant, timestamp2: Instant) -> bool { timestamp1 != timestamp2 } + /// Return `true` if the first timestamp is earlier than the second. #[rhai_fn(name = "<")] pub fn lt(timestamp1: Instant, timestamp2: Instant) -> bool { timestamp1 < timestamp2 } + /// Return `true` if the first timestamp is earlier than or equals to the second. #[rhai_fn(name = "<=")] pub fn lte(timestamp1: Instant, timestamp2: Instant) -> bool { timestamp1 <= timestamp2 } + /// Return `true` if the first timestamp is later than the second. #[rhai_fn(name = ">")] pub fn gt(timestamp1: Instant, timestamp2: Instant) -> bool { timestamp1 > timestamp2 } + /// Return `true` if the first timestamp is later than or equals to the second. #[rhai_fn(name = ">=")] pub fn gte(timestamp1: Instant, timestamp2: Instant) -> bool { timestamp1 >= timestamp2