Fix bugs and add comments to standard library.

This commit is contained in:
Stephen Chung 2022-01-15 23:34:38 +08:00
parent e24848668a
commit 3667761340
13 changed files with 3375 additions and 363 deletions

View File

@ -12,12 +12,16 @@ Bug fixes
* `set_bit` for bit-flags with negative index now works correctly. * `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`). * 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. * 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 Enhancements
------------ ------------
* Formatting of return types in functions metadata info is improved. * Formatting of return types in functions metadata info is improved.
* Use `SmartString` for `Scope` variable names and remove `unsafe` lifetime casting. * 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 Version 1.4.0

View File

@ -121,14 +121,17 @@ macro_rules! gen_arithmetic_functions {
pub fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type { pub fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type {
x ^ y x ^ y
} }
/// Return true if the number is zero.
#[rhai_fn(get = "is_zero", name = "is_zero")] #[rhai_fn(get = "is_zero", name = "is_zero")]
pub fn is_zero(x: $arg_type) -> bool { pub fn is_zero(x: $arg_type) -> bool {
x == 0 x == 0
} }
/// Return true if the number is odd.
#[rhai_fn(get = "is_odd", name = "is_odd")] #[rhai_fn(get = "is_odd", name = "is_odd")]
pub fn is_odd(x: $arg_type) -> bool { pub fn is_odd(x: $arg_type) -> bool {
x % 2 != 0 x % 2 != 0
} }
/// Return true if the number is even.
#[rhai_fn(get = "is_even", name = "is_even")] #[rhai_fn(get = "is_even", name = "is_even")]
pub fn is_even(x: $arg_type) -> bool { pub fn is_even(x: $arg_type) -> bool {
x % 2 == 0 x % 2 == 0
@ -157,6 +160,7 @@ macro_rules! gen_signed_functions {
pub fn plus(x: $arg_type) -> $arg_type { pub fn plus(x: $arg_type) -> $arg_type {
x x
} }
/// Return the absolute value of the number.
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn abs(x: $arg_type) -> RhaiResultOf<$arg_type> { pub fn abs(x: $arg_type) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
@ -165,6 +169,11 @@ macro_rules! gen_signed_functions {
Ok(x.abs()) 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 { pub fn sign(x: $arg_type) -> INT {
x.signum() as INT x.signum() as INT
} }
@ -216,14 +225,17 @@ def_package! {
#[export_module] #[export_module]
mod int_functions { mod int_functions {
/// Return true if the number is zero.
#[rhai_fn(get = "is_zero", name = "is_zero")] #[rhai_fn(get = "is_zero", name = "is_zero")]
pub fn is_zero(x: INT) -> bool { pub fn is_zero(x: INT) -> bool {
x == 0 x == 0
} }
/// Return true if the number is odd.
#[rhai_fn(get = "is_odd", name = "is_odd")] #[rhai_fn(get = "is_odd", name = "is_odd")]
pub fn is_odd(x: INT) -> bool { pub fn is_odd(x: INT) -> bool {
x % 2 != 0 x % 2 != 0
} }
/// Return true if the number is even.
#[rhai_fn(get = "is_even", name = "is_even")] #[rhai_fn(get = "is_even", name = "is_even")]
pub fn is_even(x: INT) -> bool { pub fn is_even(x: INT) -> bool {
x % 2 == 0 x % 2 == 0
@ -334,9 +346,15 @@ mod f32_functions {
pub fn plus(x: f32) -> f32 { pub fn plus(x: f32) -> f32 {
x x
} }
/// Return the absolute value of the floating-point number.
pub fn abs(x: f32) -> f32 { pub fn abs(x: f32) -> f32 {
x.abs() 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)] #[rhai_fn(return_raw)]
pub fn sign(x: f32) -> RhaiResultOf<INT> { pub fn sign(x: f32) -> RhaiResultOf<INT> {
match x.signum() { match x.signum() {
@ -345,6 +363,7 @@ mod f32_functions {
x => Ok(x as INT), x => Ok(x as INT),
} }
} }
/// Return true if the floating-point number is zero.
#[rhai_fn(get = "is_zero", name = "is_zero")] #[rhai_fn(get = "is_zero", name = "is_zero")]
pub fn is_zero(x: f32) -> bool { pub fn is_zero(x: f32) -> bool {
x == 0.0 x == 0.0
@ -442,9 +461,15 @@ mod f64_functions {
pub fn plus(x: f64) -> f64 { pub fn plus(x: f64) -> f64 {
x x
} }
/// Return the absolute value of the floating-point number.
pub fn abs(x: f64) -> f64 { pub fn abs(x: f64) -> f64 {
x.abs() 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)] #[rhai_fn(return_raw)]
pub fn sign(x: f64) -> RhaiResultOf<INT> { pub fn sign(x: f64) -> RhaiResultOf<INT> {
match x.signum() { match x.signum() {
@ -453,6 +478,7 @@ mod f64_functions {
x => Ok(x as INT), x => Ok(x as INT),
} }
} }
/// Return true if the floating-point number is zero.
#[rhai_fn(get = "is_zero", name = "is_zero")] #[rhai_fn(get = "is_zero", name = "is_zero")]
pub fn is_zero(x: f64) -> bool { pub fn is_zero(x: f64) -> bool {
x == 0.0 x == 0.0
@ -536,9 +562,15 @@ pub mod decimal_functions {
pub fn plus(x: Decimal) -> Decimal { pub fn plus(x: Decimal) -> Decimal {
x x
} }
/// Return the absolute value of the decimal number.
pub fn abs(x: Decimal) -> Decimal { pub fn abs(x: Decimal) -> Decimal {
x.abs() 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 { pub fn sign(x: Decimal) -> INT {
if x == Decimal::zero() { if x == Decimal::zero() {
0 0
@ -548,6 +580,7 @@ pub mod decimal_functions {
1 1
} }
} }
/// Return true if the decimal number is zero.
#[rhai_fn(get = "is_zero", name = "is_zero")] #[rhai_fn(get = "is_zero", name = "is_zero")]
pub fn is_zero(x: Decimal) -> bool { pub fn is_zero(x: Decimal) -> bool {
x.is_zero() x.is_zero()

File diff suppressed because it is too large Load Diff

View File

@ -21,6 +21,21 @@ def_package! {
mod bit_field_functions { mod bit_field_functions {
const BITS: usize = std::mem::size_of::<INT>() * 8; const BITS: usize = std::mem::size_of::<INT>() * 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)] #[rhai_fn(return_raw)]
pub fn get_bit(value: INT, bit: INT) -> RhaiResultOf<bool> { pub fn get_bit(value: INT, bit: INT) -> RhaiResultOf<bool> {
let bit = calc_index(BITS, bit, true, || { let bit = calc_index(BITS, bit, true, || {
@ -29,6 +44,28 @@ mod bit_field_functions {
Ok((value & (1 << bit)) != 0) 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)] #[rhai_fn(return_raw)]
pub fn set_bit(value: &mut INT, bit: INT, new_value: bool) -> RhaiResultOf<()> { pub fn set_bit(value: &mut INT, bit: INT, new_value: bool) -> RhaiResultOf<()> {
let bit = calc_index(BITS, bit, true, || { let bit = calc_index(BITS, bit, true, || {
@ -44,26 +81,57 @@ mod bit_field_functions {
Ok(()) 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)] #[rhai_fn(name = "get_bits", return_raw)]
pub fn get_bits_range(value: INT, range: ExclusiveRange) -> RhaiResultOf<INT> { pub fn get_bits_range(value: INT, range: ExclusiveRange) -> RhaiResultOf<INT> {
let from = INT::max(range.start, 0); let from = INT::max(range.start, 0);
let to = INT::max(range.end, from); let to = INT::max(range.end, from);
get_bits(value, from, to - 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)] #[rhai_fn(name = "get_bits", return_raw)]
pub fn get_bits_range_inclusive(value: INT, range: InclusiveRange) -> RhaiResultOf<INT> { pub fn get_bits_range_inclusive(value: INT, range: InclusiveRange) -> RhaiResultOf<INT> {
let from = INT::max(*range.start(), 0); let from = INT::max(*range.start(), 0);
let to = INT::max(*range.end(), from - 1); let to = INT::max(*range.end(), from - 1);
get_bits(value, from, to - 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)] #[rhai_fn(return_raw)]
pub fn get_bits(value: INT, bit: INT, bits: INT) -> RhaiResultOf<INT> { pub fn get_bits(value: INT, start: INT, bits: INT) -> RhaiResultOf<INT> {
if bits <= 0 { if bits <= 0 {
return Ok(0); return Ok(0);
} }
let bit = calc_index(BITS, bit, true, || { let bit = calc_index(BITS, start, true, || {
ERR::ErrorBitFieldBounds(BITS, bit, Position::NONE).into() ERR::ErrorBitFieldBounds(BITS, start, Position::NONE).into()
})?; })?;
let bits = if bit + bits as usize > BITS { let bits = if bit + bits as usize > BITS {
@ -81,6 +149,17 @@ mod bit_field_functions {
Ok(((value & (mask << bit)) >> bit) & mask) 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)] #[rhai_fn(name = "set_bits", return_raw)]
pub fn set_bits_range( pub fn set_bits_range(
value: &mut INT, value: &mut INT,
@ -91,6 +170,17 @@ mod bit_field_functions {
let to = INT::max(range.end, from); let to = INT::max(range.end, from);
set_bits(value, from, to - from, new_value) 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)] #[rhai_fn(name = "set_bits", return_raw)]
pub fn set_bits_range_inclusive( pub fn set_bits_range_inclusive(
value: &mut INT, value: &mut INT,
@ -101,6 +191,25 @@ mod bit_field_functions {
let to = INT::max(*range.end(), from - 1); let to = INT::max(*range.end(), from - 1);
set_bits(value, from, to - from + 1, new_value) 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)] #[rhai_fn(return_raw)]
pub fn set_bits(value: &mut INT, bit: INT, bits: INT, new_value: INT) -> RhaiResultOf<()> { pub fn set_bits(value: &mut INT, bit: INT, bits: INT, new_value: INT) -> RhaiResultOf<()> {
if bits <= 0 { if bits <= 0 {

View File

@ -1,7 +1,7 @@
#![cfg(not(feature = "no_index"))] #![cfg(not(feature = "no_index"))]
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::eval::calc_offset_len; use crate::eval::{calc_index, calc_offset_len};
use crate::plugin::*; use crate::plugin::*;
use crate::{ use crate::{
def_package, Blob, Dynamic, ExclusiveRange, InclusiveRange, NativeCallContext, Position, def_package, Blob, Dynamic, ExclusiveRange, InclusiveRange, NativeCallContext, Position,
@ -14,12 +14,26 @@ use std::{any::TypeId, mem};
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::FLOAT; use crate::FLOAT;
const INT_BYTES: usize = mem::size_of::<INT>();
#[cfg(not(feature = "no_float"))]
const FLOAT_BYTES: usize = mem::size_of::<FLOAT>();
def_package! { def_package! {
/// Package of basic BLOB utilities. /// Package of basic BLOB utilities.
crate::BasicBlobPackage => |lib| { crate::BasicBlobPackage => |lib| {
lib.standard = true; lib.standard = true;
combine_with_exported_module!(lib, "blob", blob_functions); 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 // Register blob iterator
lib.set_iterable::<Blob>(); lib.set_iterable::<Blob>();
@ -28,13 +42,38 @@ def_package! {
#[export_module] #[export_module]
pub mod blob_functions { pub mod blob_functions {
/// Return a new, empty BLOB.
pub const fn blob() -> Blob { pub const fn blob() -> Blob {
Blob::new() 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)] #[rhai_fn(name = "blob", return_raw)]
pub fn blob_with_capacity(ctx: NativeCallContext, len: INT) -> RhaiResultOf<Blob> { pub fn blob_with_capacity(ctx: NativeCallContext, len: INT) -> RhaiResultOf<Blob> {
blob_with_capacity_and_value(ctx, len, 0) 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)] #[rhai_fn(name = "blob", return_raw)]
pub fn blob_with_capacity_and_value( pub fn blob_with_capacity_and_value(
ctx: NativeCallContext, ctx: NativeCallContext,
@ -56,14 +95,40 @@ pub mod blob_functions {
blob.resize(len, (value & 0x000000ff) as u8); blob.resize(len, (value & 0x000000ff) as u8);
Ok(blob) Ok(blob)
} }
/// Return the length of the BLOB.
#[rhai_fn(name = "len", get = "len", pure)] #[rhai_fn(name = "len", get = "len", pure)]
pub fn len(blob: &mut Blob) -> INT { pub fn len(blob: &mut Blob) -> INT {
blob.len() as INT blob.len() as INT
} }
pub fn push(blob: &mut Blob, item: INT) { /// Add a new byte `value` to the end of the BLOB.
let item = (item & 0x000000ff) as u8; ///
blob.push(item); /// 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) { pub fn append(blob: &mut Blob, y: Blob) {
if !y.is_empty() { if !y.is_empty() {
if blob.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 = "+")] #[rhai_fn(name = "+")]
pub fn concat(blob1: Blob, blob2: Blob) -> Blob { pub fn concat(blob1: Blob, blob2: Blob) -> Blob {
if !blob2.is_empty() { if !blob2.is_empty() {
@ -87,29 +164,65 @@ pub mod blob_functions {
blob1 blob1
} }
} }
pub fn insert(blob: &mut Blob, index: INT, item: INT) { /// Add a byte `value` to the BLOB at a particular `index` position.
let item = (item & 0x000000ff) as u8; ///
/// * 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() { if blob.is_empty() {
blob.push(item); blob.push(value);
return; return;
} }
let (index, _) = calc_offset_len(blob.len(), index, 0); let (index, _) = calc_offset_len(blob.len(), index, 0);
if index >= blob.len() { if index >= blob.len() {
blob.push(item); blob.push(value);
} else { } 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)] #[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 { if len <= 0 {
return Ok(()); return Ok(());
} }
let item = (item & 0x000000ff) as u8; let value = (value & 0x000000ff) as u8;
let _ctx = ctx; let _ctx = ctx;
// Check if blob will be over max size limit // Check if blob will be over max size limit
@ -121,11 +234,26 @@ pub mod blob_functions {
} }
if len as usize > blob.len() { if len as usize > blob.len() {
blob.resize(len as usize, item); blob.resize(len as usize, value);
} }
Ok(()) 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 { pub fn pop(blob: &mut Blob) -> INT {
if blob.is_empty() { if blob.is_empty() {
0 0
@ -133,6 +261,21 @@ pub mod blob_functions {
blob.pop().map_or_else(|| 0, |v| v as INT) 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 { pub fn shift(blob: &mut Blob) -> INT {
if blob.is_empty() { if blob.is_empty() {
0 0
@ -140,18 +283,61 @@ pub mod blob_functions {
blob.remove(0) as INT blob.remove(0) as INT
} }
} }
pub fn remove(blob: &mut Blob, len: INT) -> INT { /// Remove the byte at the specified `index` from the BLOB and return it.
if len < 0 || (len as usize) >= blob.len() { ///
0 /// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last byte).
} else { /// * If `index` < -length of BLOB, zero is returned.
blob.remove(len as usize) as INT /// * 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) { pub fn clear(blob: &mut Blob) {
if !blob.is_empty() { if !blob.is_empty() {
blob.clear(); 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) { pub fn truncate(blob: &mut Blob, len: INT) {
if !blob.is_empty() { if !blob.is_empty() {
if len >= 0 { 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) { pub fn chop(blob: &mut Blob, len: INT) {
if !blob.is_empty() { if !blob.is_empty() {
if len <= 0 { 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) { pub fn reverse(blob: &mut Blob) {
if !blob.is_empty() { if !blob.is_empty() {
blob.reverse(); 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")] #[rhai_fn(name = "splice")]
pub fn splice_range(blob: &mut Blob, range: ExclusiveRange, replace: Blob) { pub fn splice_range(blob: &mut Blob, range: ExclusiveRange, replace: Blob) {
let start = INT::max(range.start, 0); let start = INT::max(range.start, 0);
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
splice(blob, start, end - start, replace) 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")] #[rhai_fn(name = "splice")]
pub fn splice_range_inclusive(blob: &mut Blob, range: InclusiveRange, replace: Blob) { pub fn splice_range_inclusive(blob: &mut Blob, range: InclusiveRange, replace: Blob) {
let start = INT::max(*range.start(), 0); let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start); let end = INT::max(*range.end(), start);
splice(blob, start, end - start + 1, replace) 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) { pub fn splice(blob: &mut Blob, start: INT, len: INT, replace: Blob) {
if blob.is_empty() { if blob.is_empty() {
*blob = replace; *blob = replace;
@ -201,18 +468,65 @@ pub mod blob_functions {
blob.splice(start..start + len, replace); 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")] #[rhai_fn(name = "extract")]
pub fn extract_range(blob: &mut Blob, range: ExclusiveRange) -> Blob { pub fn extract_range(blob: &mut Blob, range: ExclusiveRange) -> Blob {
let start = INT::max(range.start, 0); let start = INT::max(range.start, 0);
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
extract(blob, start, 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")] #[rhai_fn(name = "extract")]
pub fn extract_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> Blob { pub fn extract_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> Blob {
let start = INT::max(*range.start(), 0); let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start); let end = INT::max(*range.end(), start);
extract(blob, start, end - start + 1) 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 { pub fn extract(blob: &mut Blob, start: INT, len: INT) -> Blob {
if blob.is_empty() || len <= 0 { if blob.is_empty() || len <= 0 {
return Blob::new(); return Blob::new();
@ -226,19 +540,59 @@ pub mod blob_functions {
blob[start..start + len].to_vec() 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")] #[rhai_fn(name = "extract")]
pub fn extract_tail(blob: &mut Blob, start: INT) -> Blob { pub fn extract_tail(blob: &mut Blob, start: INT) -> Blob {
extract(blob, start, INT::MAX) 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")] #[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() { if blob.is_empty() {
return Blob::new(); 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() { if len > blob.len() {
mem::take(blob) mem::take(blob)
} else { } else {
@ -246,26 +600,95 @@ pub mod blob_functions {
result.extend(blob.drain(blob.len() - len..)); result.extend(blob.drain(blob.len() - len..));
result result
} }
} else if start >= blob.len() { } else if index >= blob.len() {
Blob::new() Blob::new()
} else { } else {
let mut result = Blob::new(); let mut result = Blob::new();
result.extend(blob.drain(start as usize..)); result.extend(blob.drain(index as usize..));
result 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")] #[rhai_fn(name = "drain")]
pub fn drain_range(blob: &mut Blob, range: ExclusiveRange) -> Blob { pub fn drain_range(blob: &mut Blob, range: ExclusiveRange) -> Blob {
let start = INT::max(range.start, 0); let start = INT::max(range.start, 0);
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
drain(blob, start, 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")] #[rhai_fn(name = "drain")]
pub fn drain_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> Blob { pub fn drain_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> Blob {
let start = INT::max(*range.start(), 0); let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start); let end = INT::max(*range.end(), start);
drain(blob, start, end - start + 1) 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 { pub fn drain(blob: &mut Blob, start: INT, len: INT) -> Blob {
if blob.is_empty() || len <= 0 { if blob.is_empty() || len <= 0 {
return Blob::new(); return Blob::new();
@ -279,18 +702,87 @@ pub mod blob_functions {
blob.drain(start..start + len).collect() 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")] #[rhai_fn(name = "retain")]
pub fn retain_range(blob: &mut Blob, range: ExclusiveRange) -> Blob { pub fn retain_range(blob: &mut Blob, range: ExclusiveRange) -> Blob {
let start = INT::max(range.start, 0); let start = INT::max(range.start, 0);
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
retain(blob, start, 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")] #[rhai_fn(name = "retain")]
pub fn retain_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> Blob { pub fn retain_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> Blob {
let start = INT::max(*range.start(), 0); let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start); let end = INT::max(*range.end(), start);
retain(blob, start, end - start + 1) 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 { pub fn retain(blob: &mut Blob, start: INT, len: INT) -> Blob {
if blob.is_empty() || len <= 0 { if blob.is_empty() || len <= 0 {
return Blob::new(); return Blob::new();
@ -307,7 +799,10 @@ pub mod blob_functions {
drained drained
} }
} }
}
#[export_module]
mod parse_int_functions {
#[inline] #[inline]
fn parse_int(blob: &mut Blob, start: INT, len: INT, is_le: bool) -> INT { fn parse_int(blob: &mut Blob, start: INT, len: INT, is_le: bool) -> INT {
if blob.is_empty() || len <= 0 { if blob.is_empty() || len <= 0 {
@ -319,8 +814,6 @@ pub mod blob_functions {
return 0; return 0;
} }
const INT_BYTES: usize = mem::size_of::<INT>();
let len = usize::min(len, INT_BYTES); let len = usize::min(len, INT_BYTES);
let mut buf = [0_u8; 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 { pub fn parse_be_int(blob: &mut Blob, start: INT, len: INT) -> INT {
parse_int(blob, start, len, false) 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] #[inline]
fn write_int(blob: &mut Blob, start: INT, len: INT, value: INT, is_le: bool) { fn write_int(blob: &mut Blob, start: INT, len: INT, value: INT, is_le: bool) {
if blob.is_empty() || len <= 0 { if blob.is_empty() || len <= 0 {
@ -377,8 +933,6 @@ pub mod blob_functions {
return; return;
} }
const INT_BYTES: usize = mem::size_of::<INT>();
let len = usize::min(len, INT_BYTES); let len = usize::min(len, INT_BYTES);
let buf = if is_le { let buf = if is_le {
@ -389,13 +943,13 @@ pub mod blob_functions {
blob[start..][..len].copy_from_slice(&buf[..len]); 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) { pub fn write_le_int_range(blob: &mut Blob, range: ExclusiveRange, value: INT) {
let start = INT::max(range.start, 0); let start = INT::max(range.start, 0);
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
write_le_int(blob, start, end - start, value) 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) { pub fn write_le_int_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: INT) {
let start = INT::max(*range.start(), 0); let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start); 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) { pub fn write_be_int(blob: &mut Blob, start: INT, len: INT, value: INT) {
write_int(blob, start, len, value, false) write_int(blob, start, len, value, false)
} }
}
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[inline] #[export_module]
fn parse_float(blob: &mut Blob, start: INT, len: INT, is_le: bool) -> FLOAT { mod write_float_functions {
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::<FLOAT>();
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"))]
#[inline] #[inline]
fn write_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT, is_le: bool) { fn write_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT, is_le: bool) {
if blob.is_empty() || len <= 0 { if blob.is_empty() || len <= 0 {
@ -500,8 +992,6 @@ pub mod blob_functions {
return; return;
} }
const FLOAT_BYTES: usize = mem::size_of::<FLOAT>();
let len = usize::min(len, FLOAT_BYTES); let len = usize::min(len, FLOAT_BYTES);
let buf = if is_le { let buf = if is_le {
value.to_le_bytes() value.to_le_bytes()
@ -511,44 +1001,42 @@ pub mod blob_functions {
blob[start..][..len].copy_from_slice(&buf[..len]); blob[start..][..len].copy_from_slice(&buf[..len]);
} }
#[cfg(not(feature = "no_float"))] #[rhai_fn(name = "write_le")]
#[rhai_fn(name = "write_le_float")]
pub fn write_le_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) { pub fn write_le_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) {
let start = INT::max(range.start, 0); let start = INT::max(range.start, 0);
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
write_le_float(blob, start, end - start, value) write_le_float(blob, start, end - start, value)
} }
#[cfg(not(feature = "no_float"))] #[rhai_fn(name = "write_le")]
#[rhai_fn(name = "write_le_float")]
pub fn write_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) { pub fn write_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) {
let start = INT::max(*range.start(), 0); let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start); let end = INT::max(*range.end(), start);
write_le_float(blob, start, end - start + 1, value) write_le_float(blob, start, end - start + 1, value)
} }
#[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "write_le")] #[rhai_fn(name = "write_le")]
pub fn write_le_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) { pub fn write_le_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) {
write_float(blob, start, len, value, true) write_float(blob, start, len, value, true)
} }
#[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "write_be")] #[rhai_fn(name = "write_be")]
pub fn write_be_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) { pub fn write_be_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) {
let start = INT::max(range.start, 0); let start = INT::max(range.start, 0);
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
write_be_float(blob, start, end - start, value) write_be_float(blob, start, end - start, value)
} }
#[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "write_be")] #[rhai_fn(name = "write_be")]
pub fn write_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) { pub fn write_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) {
let start = INT::max(*range.start(), 0); let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start); let end = INT::max(*range.end(), start);
write_be_float(blob, start, end - start + 1, value) write_be_float(blob, start, end - start + 1, value)
} }
#[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "write_be")] #[rhai_fn(name = "write_be")]
pub fn write_be_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) { pub fn write_be_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) {
write_float(blob, start, len, value, false) write_float(blob, start, len, value, false)
} }
}
#[export_module]
mod write_string_functions {
#[inline] #[inline]
fn write_string(blob: &mut Blob, start: INT, len: INT, string: &str, ascii_only: bool) { fn write_string(blob: &mut Blob, start: INT, len: INT, string: &str, ascii_only: bool) {
if len <= 0 || blob.is_empty() || string.is_empty() { if len <= 0 || blob.is_empty() || string.is_empty() {

View File

@ -14,122 +14,34 @@ def_package! {
#[export_module] #[export_module]
mod fn_ptr_functions { 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)] #[rhai_fn(name = "name", get = "name", pure)]
pub fn name(fn_ptr: &mut FnPtr) -> ImmutableString { pub fn name(fn_ptr: &mut FnPtr) -> ImmutableString {
fn_ptr.fn_name_raw().into() 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"))] #[cfg(not(feature = "no_function"))]
#[rhai_fn(name = "is_anonymous", get = "is_anonymous", pure)] #[rhai_fn(name = "is_anonymous", get = "is_anonymous", pure)]
pub fn is_anonymous(fn_ptr: &mut FnPtr) -> bool { pub fn is_anonymous(fn_ptr: &mut FnPtr) -> bool {
fn_ptr.is_anonymous() 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<Identifier>,
namespace: Option<Identifier>,
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::<Array>()
.into(),
);
map
}
// Intern strings
let dict: BTreeSet<Identifier> = [
"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<Identifier>,
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
} }

View File

@ -246,10 +246,20 @@ macro_rules! reg_range {
let _hash = $lib.set_native_fn($x, |from: $y, to: $y| Ok(from..to)); let _hash = $lib.set_native_fn($x, |from: $y, to: $y| Ok(from..to));
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
$lib.update_fn_metadata(_hash, &[ $lib.update_fn_metadata_with_comments(_hash, [
concat!("from: ", stringify!($y)), concat!("from: ", stringify!($y)),
concat!("to: ", stringify!($y)), concat!("to: ", stringify!($y)),
concat!("Iterator<Item=", stringify!($y), ">") concat!("Iterator<Item=", stringify!($y), ">"),
], [
"/// Return an iterator over the range of `from..to`.",
"///",
"/// # Example",
"///",
"/// ```rhai",
"/// for n in range(8, 18) {",
"/// print(n);",
"/// }",
"/// ```"
]); ]);
$lib.set_iterator::<RangeInclusive<$y>>(); $lib.set_iterator::<RangeInclusive<$y>>();
@ -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)); let _hash = $lib.set_native_fn($x, |from: $y, to: $y, step: $y| StepRange::new(from, to, step));
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
$lib.update_fn_metadata(_hash, &[ $lib.update_fn_metadata_with_comments(_hash, [
concat!("from: ", stringify!($y)), concat!("from: ", stringify!($y)),
concat!("to: ", stringify!($y)), concat!("to: ", stringify!($y)),
concat!("step: ", stringify!($y)), concat!("step: ", stringify!($y)),
concat!("Iterator<Item=", stringify!($y), ">") concat!("Iterator<Item=", stringify!($y), ">")
], [
"/// 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); let _hash = lib.set_native_fn("range", StepFloatRange::new);
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["from: FLOAT", "to: FLOAT", "step: FLOAT", "Iterator<Item=FLOAT>"]); lib.update_fn_metadata_with_comments(
_hash,
["from: FLOAT", "to: FLOAT", "step: FLOAT", "Iterator<Item=FLOAT>"],
[
"/// 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")] #[cfg(feature = "decimal")]
@ -421,7 +467,15 @@ def_package! {
let _hash = lib.set_native_fn("range", StepDecimalRange::new); let _hash = lib.set_native_fn("range", StepDecimalRange::new);
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["from: Decimal", "to: Decimal", "step: Decimal", "Iterator<Item=Decimal>"]); lib.update_fn_metadata_with_comments(
_hash,
["from: Decimal", "to: Decimal", "step: Decimal", "Iterator<Item=Decimal>"],
[
"/// 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 // Register string iterator
@ -433,7 +487,21 @@ def_package! {
Ok(CharsStream::new(string, from, to - from)) Ok(CharsStream::new(string, from, to - from))
}); });
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &str", "range: Range<INT>", "Iterator<Item=char>"]); lib.update_fn_metadata_with_comments(
_hash,
["string: &str", "range: Range<INT>", "Iterator<Item=char>"],
[
"/// 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 _hash = lib.set_native_fn("chars", |string, range: InclusiveRange| {
let from = INT::max(*range.start(), 0); let from = INT::max(*range.start(), 0);
@ -441,25 +509,105 @@ def_package! {
Ok(CharsStream::new(string, from, to-from + 1)) Ok(CharsStream::new(string, from, to-from + 1))
}); });
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &str", "range: RangeInclusive<INT>", "Iterator<Item=char>"]); lib.update_fn_metadata_with_comments(
_hash,
["string: &str", "range: RangeInclusive<INT>", "Iterator<Item=char>"],
[
"/// 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))); let _hash = lib.set_native_fn("chars", |string, from, len| Ok(CharsStream::new(string, from, len)));
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &str", "from: INT", "len: INT", "Iterator<Item=char>"]); lib.update_fn_metadata_with_comments(
_hash,
["string: &str", "start: INT", "len: INT", "Iterator<Item=char>"],
[
"/// 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))); let _hash = lib.set_native_fn("chars", |string, from| Ok(CharsStream::new(string, from, INT::MAX)));
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &str", "from: INT", "Iterator<Item=char>"]); lib.update_fn_metadata_with_comments(
_hash,
["string: &str", "from: INT", "Iterator<Item=char>"],
[
"/// 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))); let _hash = lib.set_native_fn("chars", |string| Ok(CharsStream::new(string, 0, INT::MAX)));
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &str", "Iterator<Item=char>"]); lib.update_fn_metadata_with_comments(
_hash,
["string: &str", "Iterator<Item=char>"],
[
"/// 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"))] #[cfg(not(feature = "no_object"))]
{ {
let _hash = lib.set_getter_fn("chars", |string: &mut ImmutableString| Ok(CharsStream::new(string, 0, INT::MAX))); let _hash = lib.set_getter_fn("chars", |string: &mut ImmutableString| Ok(CharsStream::new(string, 0, INT::MAX)));
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &mut ImmutableString", "Iterator<Item=char>"]); lib.update_fn_metadata_with_comments(
_hash,
["string: &mut ImmutableString", "Iterator<Item=char>"],
[
"/// 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 // Register bit-field iterator
@ -471,7 +619,23 @@ def_package! {
BitRange::new(value, from, to - from) BitRange::new(value, from, to - from)
}); });
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "range: Range<INT>", "Iterator<Item=bool>"]); lib.update_fn_metadata_with_comments(
_hash,
["value: INT", "range: Range<INT>", "Iterator<Item=bool>"],
[
"/// 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 _hash = lib.set_native_fn("bits", |value, range: InclusiveRange| {
let from = INT::max(*range.start(), 0); let from = INT::max(*range.start(), 0);
@ -479,25 +643,111 @@ def_package! {
BitRange::new(value, from, to - from + 1) BitRange::new(value, from, to - from + 1)
}); });
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "range: RangeInclusive<INT>", "Iterator<Item=bool>"]); lib.update_fn_metadata_with_comments(
_hash,
["value: INT", "range: RangeInclusive<INT>", "Iterator<Item=bool>"],
[
"/// 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); let _hash = lib.set_native_fn("bits", BitRange::new);
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "len: INT", "Iterator<Item=bool>"]); lib.update_fn_metadata_with_comments(
_hash,
["value: INT", "from: INT", "len: INT", "Iterator<Item=bool>"],
[
"/// 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)); let _hash = lib.set_native_fn("bits", |value, from| BitRange::new(value, from, INT::MAX));
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "Iterator<Item=bool>"]); lib.update_fn_metadata_with_comments(
_hash,
["value: INT", "from: INT", "Iterator<Item=bool>"],
[
"/// 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) ); let _hash = lib.set_native_fn("bits", |value| BitRange::new(value, 0, INT::MAX) );
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "Iterator<Item=bool>"]); lib.update_fn_metadata_with_comments(
_hash,
["value: INT", "Iterator<Item=bool>"],
[
"/// 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"))] #[cfg(not(feature = "no_object"))]
{ {
let _hash = lib.set_getter_fn("bits", |value: &mut INT| BitRange::new(*value, 0, INT::MAX) ); let _hash = lib.set_getter_fn("bits", |value: &mut INT| BitRange::new(*value, 0, INT::MAX) );
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: &mut INT", "range: Range<INT>", "Iterator<Item=bool>"]); lib.update_fn_metadata_with_comments(
_hash,
["value: &mut INT", "range: Range<INT>", "Iterator<Item=bool>"],
[
"/// 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); combine_with_exported_module!(lib, "range", range_functions);
@ -506,37 +756,45 @@ def_package! {
#[export_module] #[export_module]
mod range_functions { mod range_functions {
/// Return the start of the exclusive range.
#[rhai_fn(get = "start", name = "start", pure)] #[rhai_fn(get = "start", name = "start", pure)]
pub fn start(range: &mut ExclusiveRange) -> INT { pub fn start(range: &mut ExclusiveRange) -> INT {
range.start range.start
} }
/// Return the end of the exclusive range.
#[rhai_fn(get = "end", name = "end", pure)] #[rhai_fn(get = "end", name = "end", pure)]
pub fn end(range: &mut ExclusiveRange) -> INT { pub fn end(range: &mut ExclusiveRange) -> INT {
range.end range.end
} }
/// Return `true` if the range is inclusive.
#[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)] #[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)]
pub fn is_inclusive(range: &mut ExclusiveRange) -> bool { pub fn is_inclusive(range: &mut ExclusiveRange) -> bool {
let _range = range; let _range = range;
false false
} }
/// Return `true` if the range is exclusive.
#[rhai_fn(get = "is_exclusive", name = "is_exclusive", pure)] #[rhai_fn(get = "is_exclusive", name = "is_exclusive", pure)]
pub fn is_exclusive(range: &mut ExclusiveRange) -> bool { pub fn is_exclusive(range: &mut ExclusiveRange) -> bool {
let _range = range; let _range = range;
true true
} }
/// Return the start of the inclusive range.
#[rhai_fn(get = "start", name = "start", pure)] #[rhai_fn(get = "start", name = "start", pure)]
pub fn start_inclusive(range: &mut InclusiveRange) -> INT { pub fn start_inclusive(range: &mut InclusiveRange) -> INT {
*range.start() *range.start()
} }
/// Return the end of the inclusive range.
#[rhai_fn(get = "end", name = "end", pure)] #[rhai_fn(get = "end", name = "end", pure)]
pub fn end_inclusive(range: &mut InclusiveRange) -> INT { pub fn end_inclusive(range: &mut InclusiveRange) -> INT {
*range.end() *range.end()
} }
/// Return `true` if the range is inclusive.
#[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)] #[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)]
pub fn is_inclusive_inclusive(range: &mut InclusiveRange) -> bool { pub fn is_inclusive_inclusive(range: &mut InclusiveRange) -> bool {
let _range = range; let _range = range;
true true
} }
/// Return `true` if the range is exclusive.
#[rhai_fn(get = "is_exclusive", name = "is_exclusive", pure)] #[rhai_fn(get = "is_exclusive", name = "is_exclusive", pure)]
pub fn is_exclusive_inclusive(range: &mut InclusiveRange) -> bool { pub fn is_exclusive_inclusive(range: &mut InclusiveRange) -> bool {
let _range = range; let _range = range;

View File

@ -53,4 +53,112 @@ mod core_functions {
Ok(()) 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<Identifier>,
namespace: Option<Identifier>,
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::<Array>()
.into(),
);
map
}
// Intern strings
let dict: BTreeSet<Identifier> = [
"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<Identifier>,
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
} }

View File

@ -20,15 +20,32 @@ def_package! {
#[export_module] #[export_module]
mod map_functions { mod map_functions {
/// Return the number of properties in the object map.
#[rhai_fn(pure)] #[rhai_fn(pure)]
pub fn len(map: &mut Map) -> INT { pub fn len(map: &mut Map) -> INT {
map.len() as INT map.len() as INT
} }
/// Clear the object map.
pub fn clear(map: &mut Map) { pub fn clear(map: &mut Map) {
if !map.is_empty() { if !map.is_empty() {
map.clear(); 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 { pub fn remove(map: &mut Map, name: ImmutableString) -> Dynamic {
if !map.is_empty() { if !map.is_empty() {
map.remove(name.as_str()).unwrap_or_else(|| Dynamic::UNIT) map.remove(name.as_str()).unwrap_or_else(|| Dynamic::UNIT)
@ -36,12 +53,38 @@ mod map_functions {
Dynamic::UNIT 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 = "+=")] #[rhai_fn(name = "mixin", name = "+=")]
pub fn mixin(map: &mut Map, map2: Map) { pub fn mixin(map: &mut Map, map2: Map) {
if !map2.is_empty() { if !map2.is_empty() {
map.extend(map2.into_iter()); 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 = "+")] #[rhai_fn(name = "+")]
pub fn merge(map1: Map, map2: Map) -> Map { pub fn merge(map1: Map, map2: Map) -> Map {
if map2.is_empty() { if map2.is_empty() {
@ -54,6 +97,19 @@ mod map_functions {
map1 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) { pub fn fill_with(map: &mut Map, map2: Map) {
if !map2.is_empty() { if !map2.is_empty() {
if map.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)] #[rhai_fn(name = "==", return_raw, pure)]
pub fn equals(ctx: NativeCallContext, map1: &mut Map, map2: Map) -> RhaiResultOf<bool> { pub fn equals(ctx: NativeCallContext, map1: &mut Map, map2: Map) -> RhaiResultOf<bool> {
if map1.len() != map2.len() { if map1.len() != map2.len() {
@ -92,11 +164,36 @@ mod map_functions {
Ok(true) 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)] #[rhai_fn(name = "!=", return_raw, pure)]
pub fn not_equals(ctx: NativeCallContext, map1: &mut Map, map2: Map) -> RhaiResultOf<bool> { pub fn not_equals(ctx: NativeCallContext, map1: &mut Map, map2: Map) -> RhaiResultOf<bool> {
equals(ctx, map1, map2).map(|r| !r) 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"))] #[cfg(not(feature = "no_index"))]
#[rhai_fn(pure)] #[rhai_fn(pure)]
pub fn keys(map: &mut Map) -> Array { pub fn keys(map: &mut Map) -> Array {
@ -106,6 +203,15 @@ mod map_functions {
map.keys().cloned().map(Into::into).collect() 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"))] #[cfg(not(feature = "no_index"))]
#[rhai_fn(pure)] #[rhai_fn(pure)]
pub fn values(map: &mut Map) -> Array { pub fn values(map: &mut Map) -> Array {

View File

@ -1,7 +1,7 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::plugin::*; 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")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -108,6 +108,34 @@ def_package! {
#[export_module] #[export_module]
mod int_functions { 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<INT> {
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)] #[rhai_fn(name = "parse_int", return_raw)]
pub fn parse_int_radix(string: &str, radix: INT) -> RhaiResultOf<INT> { pub fn parse_int_radix(string: &str, radix: INT) -> RhaiResultOf<INT> {
if !(2..=36).contains(&radix) { if !(2..=36).contains(&radix) {
@ -118,9 +146,7 @@ mod int_functions {
.into()); .into());
} }
UNSIGNED_INT::from_str_radix(string.trim(), radix as u32) INT::from_str_radix(string.trim(), radix as u32).map_err(|err| {
.map(|v| v as INT)
.map_err(|err| {
ERR::ErrorArithmetic( ERR::ErrorArithmetic(
format!("Error parsing integer number '{}': {}", string, err), format!("Error parsing integer number '{}': {}", string, err),
Position::NONE, Position::NONE,
@ -128,10 +154,6 @@ mod int_functions {
.into() .into()
}) })
} }
#[rhai_fn(name = "parse_int", return_raw)]
pub fn parse_int(string: &str) -> RhaiResultOf<INT> {
parse_int_radix(string, 10)
}
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -139,46 +161,60 @@ mod int_functions {
mod trig_functions { mod trig_functions {
use crate::FLOAT; use crate::FLOAT;
/// Return the sine of the floating-point number in radians.
pub fn sin(x: FLOAT) -> FLOAT { pub fn sin(x: FLOAT) -> FLOAT {
x.sin() x.sin()
} }
/// Return the cosine of the floating-point number in radians.
pub fn cos(x: FLOAT) -> FLOAT { pub fn cos(x: FLOAT) -> FLOAT {
x.cos() x.cos()
} }
/// Return the tangent of the floating-point number in radians.
pub fn tan(x: FLOAT) -> FLOAT { pub fn tan(x: FLOAT) -> FLOAT {
x.tan() x.tan()
} }
/// Return the hyperbolic sine of the floating-point number in radians.
pub fn sinh(x: FLOAT) -> FLOAT { pub fn sinh(x: FLOAT) -> FLOAT {
x.sinh() x.sinh()
} }
/// Return the hyperbolic cosine of the floating-point number in radians.
pub fn cosh(x: FLOAT) -> FLOAT { pub fn cosh(x: FLOAT) -> FLOAT {
x.cosh() x.cosh()
} }
/// Return the hyperbolic tangent of the floating-point number in radians.
pub fn tanh(x: FLOAT) -> FLOAT { pub fn tanh(x: FLOAT) -> FLOAT {
x.tanh() x.tanh()
} }
/// Return the arc-sine of the floating-point number, in radians.
pub fn asin(x: FLOAT) -> FLOAT { pub fn asin(x: FLOAT) -> FLOAT {
x.asin() x.asin()
} }
/// Return the arc-cosine of the floating-point number, in radians.
pub fn acos(x: FLOAT) -> FLOAT { pub fn acos(x: FLOAT) -> FLOAT {
x.acos() x.acos()
} }
/// Return the arc-tangent of the floating-point number, in radians.
pub fn atan(x: FLOAT) -> FLOAT { pub fn atan(x: FLOAT) -> FLOAT {
x.atan() x.atan()
} }
/// Return the arc-tangent of the floating-point numbers `x` and `y`, in radians.
#[rhai_fn(name = "atan")] #[rhai_fn(name = "atan")]
pub fn atan2(x: FLOAT, y: FLOAT) -> FLOAT { pub fn atan2(x: FLOAT, y: FLOAT) -> FLOAT {
x.atan2(y) x.atan2(y)
} }
/// Return the arc-hyperbolic-sine of the floating-point number, in radians.
pub fn asinh(x: FLOAT) -> FLOAT { pub fn asinh(x: FLOAT) -> FLOAT {
x.asinh() x.asinh()
} }
/// Return the arc-hyperbolic-cosine of the floating-point number, in radians.
pub fn acosh(x: FLOAT) -> FLOAT { pub fn acosh(x: FLOAT) -> FLOAT {
x.acosh() x.acosh()
} }
/// Return the arc-hyperbolic-tangent of the floating-point number, in radians.
pub fn atanh(x: FLOAT) -> FLOAT { pub fn atanh(x: FLOAT) -> FLOAT {
x.atanh() x.atanh()
} }
/// Return the hypotenuse of a triangle with sides `x` and `y`.
pub fn hypot(x: FLOAT, y: FLOAT) -> FLOAT { pub fn hypot(x: FLOAT, y: FLOAT) -> FLOAT {
x.hypot(y) x.hypot(y)
} }
@ -189,6 +225,7 @@ mod trig_functions {
mod float_functions { mod float_functions {
use crate::FLOAT; use crate::FLOAT;
/// Return the natural number _e_.
#[rhai_fn(name = "E")] #[rhai_fn(name = "E")]
pub fn e() -> FLOAT { pub fn e() -> FLOAT {
#[cfg(not(feature = "f32_float"))] #[cfg(not(feature = "f32_float"))]
@ -196,6 +233,7 @@ mod float_functions {
#[cfg(feature = "f32_float")] #[cfg(feature = "f32_float")]
return std::f32::consts::E; return std::f32::consts::E;
} }
/// Return the number π.
#[rhai_fn(name = "PI")] #[rhai_fn(name = "PI")]
pub fn pi() -> FLOAT { pub fn pi() -> FLOAT {
#[cfg(not(feature = "f32_float"))] #[cfg(not(feature = "f32_float"))]
@ -203,60 +241,77 @@ mod float_functions {
#[cfg(feature = "f32_float")] #[cfg(feature = "f32_float")]
return std::f32::consts::PI; return std::f32::consts::PI;
} }
/// Convert degrees to radians.
pub fn to_radians(x: FLOAT) -> FLOAT { pub fn to_radians(x: FLOAT) -> FLOAT {
x.to_radians() x.to_radians()
} }
/// Convert radians to degrees.
pub fn to_degrees(x: FLOAT) -> FLOAT { pub fn to_degrees(x: FLOAT) -> FLOAT {
x.to_degrees() x.to_degrees()
} }
/// Return the square root of the floating-point number.
pub fn sqrt(x: FLOAT) -> FLOAT { pub fn sqrt(x: FLOAT) -> FLOAT {
x.sqrt() x.sqrt()
} }
/// Return the exponential of the floating-point number.
pub fn exp(x: FLOAT) -> FLOAT { pub fn exp(x: FLOAT) -> FLOAT {
x.exp() x.exp()
} }
/// Return the natural log of the floating-point number.
pub fn ln(x: FLOAT) -> FLOAT { pub fn ln(x: FLOAT) -> FLOAT {
x.ln() x.ln()
} }
/// Return the log of the floating-point number with `base`.
pub fn log(x: FLOAT, base: FLOAT) -> FLOAT { pub fn log(x: FLOAT, base: FLOAT) -> FLOAT {
x.log(base) x.log(base)
} }
/// Return the log of the floating-point number with base 10.
#[rhai_fn(name = "log")] #[rhai_fn(name = "log")]
pub fn log10(x: FLOAT) -> FLOAT { pub fn log10(x: FLOAT) -> FLOAT {
x.log10() x.log10()
} }
/// Return the largest whole number less than or equals to the floating-point number.
#[rhai_fn(name = "floor", get = "floor")] #[rhai_fn(name = "floor", get = "floor")]
pub fn floor(x: FLOAT) -> FLOAT { pub fn floor(x: FLOAT) -> FLOAT {
x.floor() x.floor()
} }
/// Return the smallest whole number larger than or equals to the floating-point number.
#[rhai_fn(name = "ceiling", get = "ceiling")] #[rhai_fn(name = "ceiling", get = "ceiling")]
pub fn ceiling(x: FLOAT) -> FLOAT { pub fn ceiling(x: FLOAT) -> FLOAT {
x.ceil() x.ceil()
} }
/// Return the nearest whole number closest to the floating-point number.
/// Rounds away from zero.
#[rhai_fn(name = "round", get = "round")] #[rhai_fn(name = "round", get = "round")]
pub fn round(x: FLOAT) -> FLOAT { pub fn round(x: FLOAT) -> FLOAT {
x.round() x.round()
} }
/// Return the integral part of the floating-point number.
#[rhai_fn(name = "int", get = "int")] #[rhai_fn(name = "int", get = "int")]
pub fn int(x: FLOAT) -> FLOAT { pub fn int(x: FLOAT) -> FLOAT {
x.trunc() x.trunc()
} }
/// Return the fractional part of the floating-point number.
#[rhai_fn(name = "fraction", get = "fraction")] #[rhai_fn(name = "fraction", get = "fraction")]
pub fn fraction(x: FLOAT) -> FLOAT { pub fn fraction(x: FLOAT) -> FLOAT {
x.fract() x.fract()
} }
/// Return `true` if the floating-point number is `NaN` (Not A Number).
#[rhai_fn(name = "is_nan", get = "is_nan")] #[rhai_fn(name = "is_nan", get = "is_nan")]
pub fn is_nan(x: FLOAT) -> bool { pub fn is_nan(x: FLOAT) -> bool {
x.is_nan() x.is_nan()
} }
/// Return `true` if the floating-point number is finite.
#[rhai_fn(name = "is_finite", get = "is_finite")] #[rhai_fn(name = "is_finite", get = "is_finite")]
pub fn is_finite(x: FLOAT) -> bool { pub fn is_finite(x: FLOAT) -> bool {
x.is_finite() x.is_finite()
} }
/// Return `true` if the floating-point number is infinite.
#[rhai_fn(name = "is_infinite", get = "is_infinite")] #[rhai_fn(name = "is_infinite", get = "is_infinite")]
pub fn is_infinite(x: FLOAT) -> bool { pub fn is_infinite(x: FLOAT) -> bool {
x.is_infinite() x.is_infinite()
} }
/// Return the integral part of the floating-point number.
#[rhai_fn(name = "to_int", return_raw)] #[rhai_fn(name = "to_int", return_raw)]
pub fn f32_to_int(x: f32) -> RhaiResultOf<INT> { pub fn f32_to_int(x: f32) -> RhaiResultOf<INT> {
if cfg!(not(feature = "unchecked")) && x > (INT::MAX as f32) { if cfg!(not(feature = "unchecked")) && x > (INT::MAX as f32) {
@ -268,6 +323,7 @@ mod float_functions {
Ok(x.trunc() as INT) Ok(x.trunc() as INT)
} }
} }
/// Return the integral part of the floating-point number.
#[rhai_fn(name = "to_int", return_raw)] #[rhai_fn(name = "to_int", return_raw)]
pub fn f64_to_int(x: f64) -> RhaiResultOf<INT> { pub fn f64_to_int(x: f64) -> RhaiResultOf<INT> {
if cfg!(not(feature = "unchecked")) && x > (INT::MAX as f64) { if cfg!(not(feature = "unchecked")) && x > (INT::MAX as f64) {
@ -279,6 +335,15 @@ mod float_functions {
Ok(x.trunc() as INT) 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)] #[rhai_fn(return_raw)]
pub fn parse_float(string: &str) -> RhaiResultOf<FLOAT> { pub fn parse_float(string: &str) -> RhaiResultOf<FLOAT> {
string.trim().parse::<FLOAT>().map_err(|err| { string.trim().parse::<FLOAT>().map_err(|err| {
@ -289,6 +354,7 @@ mod float_functions {
.into() .into()
}) })
} }
/// Convert the 32-bit floating-point number to 64-bit.
#[cfg(not(feature = "f32_float"))] #[cfg(not(feature = "f32_float"))]
#[rhai_fn(name = "to_float")] #[rhai_fn(name = "to_float")]
pub fn f32_to_f64(x: f32) -> f64 { pub fn f32_to_f64(x: f32) -> f64 {
@ -306,36 +372,52 @@ mod decimal_functions {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use std::convert::TryFrom; use std::convert::TryFrom;
/// Return the natural number _e_.
#[cfg(feature = "no_float")] #[cfg(feature = "no_float")]
#[rhai_fn(name = "PI")] #[rhai_fn(name = "PI")]
pub fn pi() -> Decimal { pub fn pi() -> Decimal {
Decimal::PI Decimal::PI
} }
/// Return the number π.
#[cfg(feature = "no_float")] #[cfg(feature = "no_float")]
#[rhai_fn(name = "E")] #[rhai_fn(name = "E")]
pub fn e() -> Decimal { pub fn e() -> Decimal {
Decimal::E 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")] #[cfg(feature = "no_float")]
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn parse_float(s: &str) -> RhaiResultOf<Decimal> { pub fn parse_float(s: &str) -> RhaiResultOf<Decimal> {
parse_decimal(s) parse_decimal(s)
} }
/// Return the sine of the decimal number in radians.
pub fn sin(x: Decimal) -> Decimal { pub fn sin(x: Decimal) -> Decimal {
x.sin() x.sin()
} }
/// Return the cosine of the decimal number in radians.
pub fn cos(x: Decimal) -> Decimal { pub fn cos(x: Decimal) -> Decimal {
x.cos() x.cos()
} }
/// Return the tangent of the decimal number in radians.
pub fn tan(x: Decimal) -> Decimal { pub fn tan(x: Decimal) -> Decimal {
x.tan() x.tan()
} }
/// Return the square root of the decimal number.
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn sqrt(x: Decimal) -> RhaiResultOf<Decimal> { pub fn sqrt(x: Decimal) -> RhaiResultOf<Decimal> {
x.sqrt() x.sqrt()
.ok_or_else(|| make_err(format!("Error taking the square root of {}", x,))) .ok_or_else(|| make_err(format!("Error taking the square root of {}", x,)))
} }
/// Return the exponential of the decimal number.
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn exp(x: Decimal) -> RhaiResultOf<Decimal> { pub fn exp(x: Decimal) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
@ -345,6 +427,7 @@ mod decimal_functions {
Ok(x.exp()) Ok(x.exp())
} }
} }
/// Return the natural log of the decimal number.
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn ln(x: Decimal) -> RhaiResultOf<Decimal> { pub fn ln(x: Decimal) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
@ -354,6 +437,7 @@ mod decimal_functions {
Ok(x.ln()) Ok(x.ln())
} }
} }
/// Return the log of the decimal number with base 10.
#[rhai_fn(name = "log", return_raw)] #[rhai_fn(name = "log", return_raw)]
pub fn log10(x: Decimal) -> RhaiResultOf<Decimal> { pub fn log10(x: Decimal) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
@ -363,106 +447,131 @@ mod decimal_functions {
Ok(x.log10()) Ok(x.log10())
} }
} }
/// Return the largest whole number less than or equals to the decimal number.
#[rhai_fn(name = "floor", get = "floor")] #[rhai_fn(name = "floor", get = "floor")]
pub fn floor(x: Decimal) -> Decimal { pub fn floor(x: Decimal) -> Decimal {
x.floor() x.floor()
} }
/// Return the smallest whole number larger than or equals to the decimal number.
#[rhai_fn(name = "ceiling", get = "ceiling")] #[rhai_fn(name = "ceiling", get = "ceiling")]
pub fn ceiling(x: Decimal) -> Decimal { pub fn ceiling(x: Decimal) -> Decimal {
x.ceil() 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")] #[rhai_fn(name = "round", get = "round")]
pub fn round(x: Decimal) -> Decimal { pub fn round(x: Decimal) -> Decimal {
x.round() 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)] #[rhai_fn(name = "round", return_raw)]
pub fn round_dp(x: Decimal, dp: INT) -> RhaiResultOf<Decimal> { pub fn round_dp(x: Decimal, digits: INT) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
if dp < 0 { if digits < 0 {
return Err(make_err(format!( return Err(make_err(format!(
"Invalid number of digits for rounding: {}", "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); 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)] #[rhai_fn(return_raw)]
pub fn round_up(x: Decimal, dp: INT) -> RhaiResultOf<Decimal> { pub fn round_up(x: Decimal, digits: INT) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
if dp < 0 { if digits < 0 {
return Err(make_err(format!( return Err(make_err(format!(
"Invalid number of digits for rounding: {}", "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); 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)] #[rhai_fn(return_raw)]
pub fn round_down(x: Decimal, dp: INT) -> RhaiResultOf<Decimal> { pub fn round_down(x: Decimal, digits: INT) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
if dp < 0 { if digits < 0 {
return Err(make_err(format!( return Err(make_err(format!(
"Invalid number of digits for rounding: {}", "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); 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)] #[rhai_fn(return_raw)]
pub fn round_half_up(x: Decimal, dp: INT) -> RhaiResultOf<Decimal> { pub fn round_half_up(x: Decimal, digits: INT) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
if dp < 0 { if digits < 0 {
return Err(make_err(format!( return Err(make_err(format!(
"Invalid number of digits for rounding: {}", "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); 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)] #[rhai_fn(return_raw)]
pub fn round_half_down(x: Decimal, dp: INT) -> RhaiResultOf<Decimal> { pub fn round_half_down(x: Decimal, digits: INT) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
if dp < 0 { if digits < 0 {
return Err(make_err(format!( return Err(make_err(format!(
"Invalid number of digits for rounding: {}", "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); 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")] #[rhai_fn(name = "int", get = "int")]
pub fn int(x: Decimal) -> Decimal { pub fn int(x: Decimal) -> Decimal {
x.trunc() x.trunc()
} }
/// Return the fractional part of the decimal number.
#[rhai_fn(name = "fraction", get = "fraction")] #[rhai_fn(name = "fraction", get = "fraction")]
pub fn fraction(x: Decimal) -> Decimal { pub fn fraction(x: Decimal) -> Decimal {
x.fract() 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)] #[rhai_fn(return_raw)]
pub fn parse_decimal(string: &str) -> RhaiResultOf<Decimal> { pub fn parse_decimal(string: &str) -> RhaiResultOf<Decimal> {
Decimal::from_str(string) Decimal::from_str(string)
@ -476,6 +585,7 @@ mod decimal_functions {
}) })
} }
/// Convert the floating-point number to decimal.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "to_decimal", return_raw)] #[rhai_fn(name = "to_decimal", return_raw)]
pub fn f32_to_decimal(x: f32) -> RhaiResultOf<Decimal> { pub fn f32_to_decimal(x: f32) -> RhaiResultOf<Decimal> {
@ -487,6 +597,7 @@ mod decimal_functions {
.into() .into()
}) })
} }
/// Convert the floating-point number to decimal.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "to_decimal", return_raw)] #[rhai_fn(name = "to_decimal", return_raw)]
pub fn f64_to_decimal(x: f64) -> RhaiResultOf<Decimal> { pub fn f64_to_decimal(x: f64) -> RhaiResultOf<Decimal> {
@ -498,6 +609,7 @@ mod decimal_functions {
.into() .into()
}) })
} }
/// Convert the decimal number to floating-point.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn to_float(x: Decimal) -> RhaiResultOf<FLOAT> { pub fn to_float(x: Decimal) -> RhaiResultOf<FLOAT> {

View File

@ -46,56 +46,68 @@ pub fn print_with_func(
mod print_debug_functions { mod print_debug_functions {
use crate::ImmutableString; use crate::ImmutableString;
/// Convert the value of the `item` into a string.
#[rhai_fn(name = "print", pure)] #[rhai_fn(name = "print", pure)]
pub fn print_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { pub fn print_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString {
print_with_func(FUNC_TO_STRING, &ctx, item) print_with_func(FUNC_TO_STRING, &ctx, item)
} }
/// Convert the value of the `item` into a string.
#[rhai_fn(name = "to_string", pure)] #[rhai_fn(name = "to_string", pure)]
pub fn to_string_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { pub fn to_string_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString {
ctx.engine().map_type_name(&item.to_string()).into() 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)] #[rhai_fn(name = "debug", pure)]
pub fn debug_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { pub fn debug_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString {
print_with_func(FUNC_TO_DEBUG, &ctx, item) 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)] #[rhai_fn(name = "to_debug", pure)]
pub fn to_debug_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { pub fn to_debug_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString {
ctx.engine().map_type_name(&format!("{:?}", item)).into() ctx.engine().map_type_name(&format!("{:?}", item)).into()
} }
/// Return the empty string.
#[rhai_fn(name = "print", name = "debug")] #[rhai_fn(name = "print", name = "debug")]
pub fn print_empty_string(ctx: NativeCallContext) -> ImmutableString { pub fn print_empty_string(ctx: NativeCallContext) -> ImmutableString {
ctx.engine().const_empty_string() ctx.engine().const_empty_string()
} }
/// Return the `string`.
#[rhai_fn(name = "print", name = "to_string")] #[rhai_fn(name = "print", name = "to_string")]
pub fn print_string(s: ImmutableString) -> ImmutableString { pub fn print_string(string: ImmutableString) -> ImmutableString {
s string
} }
/// Convert the function pointer into a string in debug format.
#[rhai_fn(name = "debug", name = "to_debug", pure)] #[rhai_fn(name = "debug", name = "to_debug", pure)]
pub fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString { pub fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString {
f.to_string().into() f.to_string().into()
} }
/// Convert the value of `number` into a string.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "print", name = "to_string")] #[rhai_fn(name = "print", name = "to_string")]
pub fn print_f64(number: f64) -> ImmutableString { pub fn print_f64(number: f64) -> ImmutableString {
crate::ast::FloatWrapper::new(number).to_string().into() crate::ast::FloatWrapper::new(number).to_string().into()
} }
/// Convert the value of `number` into a string.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "print", name = "to_string")] #[rhai_fn(name = "print", name = "to_string")]
pub fn print_f32(number: f32) -> ImmutableString { pub fn print_f32(number: f32) -> ImmutableString {
crate::ast::FloatWrapper::new(number).to_string().into() crate::ast::FloatWrapper::new(number).to_string().into()
} }
/// Convert the value of `number` into a string.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "debug", name = "to_debug")] #[rhai_fn(name = "debug", name = "to_debug")]
pub fn debug_f64(number: f64) -> ImmutableString { pub fn debug_f64(number: f64) -> ImmutableString {
format!("{:?}", crate::ast::FloatWrapper::new(number)).into() format!("{:?}", crate::ast::FloatWrapper::new(number)).into()
} }
/// Convert the value of `number` into a string.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "debug", name = "to_debug")] #[rhai_fn(name = "debug", name = "to_debug")]
pub fn debug_f32(number: f32) -> ImmutableString { pub fn debug_f32(number: f32) -> ImmutableString {
format!("{:?}", crate::ast::FloatWrapper::new(number)).into() format!("{:?}", crate::ast::FloatWrapper::new(number)).into()
} }
/// Convert the array into a string.
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
#[rhai_fn( #[rhai_fn(
name = "print", name = "print",
@ -119,6 +131,8 @@ mod print_debug_functions {
result.push(']'); result.push(']');
result.into() result.into()
} }
/// Convert the object map into a string.
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[rhai_fn( #[rhai_fn(
name = "print", name = "print",
@ -148,27 +162,27 @@ mod print_debug_functions {
#[export_module] #[export_module]
mod number_formatting { mod number_formatting {
#[rhai_fn(skip)] fn to_hex<T: LowerHex>(value: T) -> ImmutableString {
pub fn to_hex<T: LowerHex>(value: T) -> ImmutableString {
format!("{:x}", value).into() format!("{:x}", value).into()
} }
#[rhai_fn(skip)] fn to_octal<T: Octal>(value: T) -> ImmutableString {
pub fn to_octal<T: Octal>(value: T) -> ImmutableString {
format!("{:o}", value).into() format!("{:o}", value).into()
} }
#[rhai_fn(skip)] fn to_binary<T: Binary>(value: T) -> ImmutableString {
pub fn to_binary<T: Binary>(value: T) -> ImmutableString {
format!("{:b}", value).into() format!("{:b}", value).into()
} }
/// Convert the `value` into a string in hex format.
#[rhai_fn(name = "to_hex")] #[rhai_fn(name = "to_hex")]
pub fn int_to_hex(value: INT) -> ImmutableString { pub fn int_to_hex(value: INT) -> ImmutableString {
to_hex(value) to_hex(value)
} }
/// Convert the `value` into a string in octal format.
#[rhai_fn(name = "to_octal")] #[rhai_fn(name = "to_octal")]
pub fn int_to_octal(value: INT) -> ImmutableString { pub fn int_to_octal(value: INT) -> ImmutableString {
to_octal(value) to_octal(value)
} }
/// Convert the `value` into a string in binary format.
#[rhai_fn(name = "to_binary")] #[rhai_fn(name = "to_binary")]
pub fn int_to_binary(value: INT) -> ImmutableString { pub fn int_to_binary(value: INT) -> ImmutableString {
to_binary(value) to_binary(value)
@ -177,98 +191,122 @@ mod number_formatting {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
pub mod numbers { pub mod numbers {
/// Convert the `value` into a string in hex format.
#[rhai_fn(name = "to_hex")] #[rhai_fn(name = "to_hex")]
pub fn u8_to_hex(value: u8) -> ImmutableString { pub fn u8_to_hex(value: u8) -> ImmutableString {
to_hex(value) to_hex(value)
} }
/// Convert the `value` into a string in hex format.
#[rhai_fn(name = "to_hex")] #[rhai_fn(name = "to_hex")]
pub fn u16_to_hex(value: u16) -> ImmutableString { pub fn u16_to_hex(value: u16) -> ImmutableString {
to_hex(value) to_hex(value)
} }
/// Convert the `value` into a string in hex format.
#[rhai_fn(name = "to_hex")] #[rhai_fn(name = "to_hex")]
pub fn u32_to_hex(value: u32) -> ImmutableString { pub fn u32_to_hex(value: u32) -> ImmutableString {
to_hex(value) to_hex(value)
} }
/// Convert the `value` into a string in hex format.
#[rhai_fn(name = "to_hex")] #[rhai_fn(name = "to_hex")]
pub fn u64_to_hex(value: u64) -> ImmutableString { pub fn u64_to_hex(value: u64) -> ImmutableString {
to_hex(value) to_hex(value)
} }
/// Convert the `value` into a string in hex format.
#[rhai_fn(name = "to_hex")] #[rhai_fn(name = "to_hex")]
pub fn i8_to_hex(value: i8) -> ImmutableString { pub fn i8_to_hex(value: i8) -> ImmutableString {
to_hex(value) to_hex(value)
} }
/// Convert the `value` into a string in hex format.
#[rhai_fn(name = "to_hex")] #[rhai_fn(name = "to_hex")]
pub fn i16_to_hex(value: i16) -> ImmutableString { pub fn i16_to_hex(value: i16) -> ImmutableString {
to_hex(value) to_hex(value)
} }
/// Convert the `value` into a string in hex format.
#[rhai_fn(name = "to_hex")] #[rhai_fn(name = "to_hex")]
pub fn i32_to_hex(value: i32) -> ImmutableString { pub fn i32_to_hex(value: i32) -> ImmutableString {
to_hex(value) to_hex(value)
} }
/// Convert the `value` into a string in hex format.
#[rhai_fn(name = "to_hex")] #[rhai_fn(name = "to_hex")]
pub fn i64_to_hex(value: i64) -> ImmutableString { pub fn i64_to_hex(value: i64) -> ImmutableString {
to_hex(value) to_hex(value)
} }
/// Convert the `value` into a string in octal format.
#[rhai_fn(name = "to_octal")] #[rhai_fn(name = "to_octal")]
pub fn u8_to_octal(value: u8) -> ImmutableString { pub fn u8_to_octal(value: u8) -> ImmutableString {
to_octal(value) to_octal(value)
} }
/// Convert the `value` into a string in octal format.
#[rhai_fn(name = "to_octal")] #[rhai_fn(name = "to_octal")]
pub fn u16_to_octal(value: u16) -> ImmutableString { pub fn u16_to_octal(value: u16) -> ImmutableString {
to_octal(value) to_octal(value)
} }
/// Convert the `value` into a string in octal format.
#[rhai_fn(name = "to_octal")] #[rhai_fn(name = "to_octal")]
pub fn u32_to_octal(value: u32) -> ImmutableString { pub fn u32_to_octal(value: u32) -> ImmutableString {
to_octal(value) to_octal(value)
} }
/// Convert the `value` into a string in octal format.
#[rhai_fn(name = "to_octal")] #[rhai_fn(name = "to_octal")]
pub fn u64_to_octal(value: u64) -> ImmutableString { pub fn u64_to_octal(value: u64) -> ImmutableString {
to_octal(value) to_octal(value)
} }
/// Convert the `value` into a string in octal format.
#[rhai_fn(name = "to_octal")] #[rhai_fn(name = "to_octal")]
pub fn i8_to_octal(value: i8) -> ImmutableString { pub fn i8_to_octal(value: i8) -> ImmutableString {
to_octal(value) to_octal(value)
} }
/// Convert the `value` into a string in octal format.
#[rhai_fn(name = "to_octal")] #[rhai_fn(name = "to_octal")]
pub fn i16_to_octal(value: i16) -> ImmutableString { pub fn i16_to_octal(value: i16) -> ImmutableString {
to_octal(value) to_octal(value)
} }
/// Convert the `value` into a string in octal format.
#[rhai_fn(name = "to_octal")] #[rhai_fn(name = "to_octal")]
pub fn i32_to_octal(value: i32) -> ImmutableString { pub fn i32_to_octal(value: i32) -> ImmutableString {
to_octal(value) to_octal(value)
} }
/// Convert the `value` into a string in octal format.
#[rhai_fn(name = "to_octal")] #[rhai_fn(name = "to_octal")]
pub fn i64_to_octal(value: i64) -> ImmutableString { pub fn i64_to_octal(value: i64) -> ImmutableString {
to_octal(value) to_octal(value)
} }
/// Convert the `value` into a string in binary format.
#[rhai_fn(name = "to_binary")] #[rhai_fn(name = "to_binary")]
pub fn u8_to_binary(value: u8) -> ImmutableString { pub fn u8_to_binary(value: u8) -> ImmutableString {
to_binary(value) to_binary(value)
} }
/// Convert the `value` into a string in binary format.
#[rhai_fn(name = "to_binary")] #[rhai_fn(name = "to_binary")]
pub fn u16_to_binary(value: u16) -> ImmutableString { pub fn u16_to_binary(value: u16) -> ImmutableString {
to_binary(value) to_binary(value)
} }
/// Convert the `value` into a string in binary format.
#[rhai_fn(name = "to_binary")] #[rhai_fn(name = "to_binary")]
pub fn u32_to_binary(value: u32) -> ImmutableString { pub fn u32_to_binary(value: u32) -> ImmutableString {
to_binary(value) to_binary(value)
} }
/// Convert the `value` into a string in binary format.
#[rhai_fn(name = "to_binary")] #[rhai_fn(name = "to_binary")]
pub fn u64_to_binary(value: u64) -> ImmutableString { pub fn u64_to_binary(value: u64) -> ImmutableString {
to_binary(value) to_binary(value)
} }
/// Convert the `value` into a string in binary format.
#[rhai_fn(name = "to_binary")] #[rhai_fn(name = "to_binary")]
pub fn i8_to_binary(value: i8) -> ImmutableString { pub fn i8_to_binary(value: i8) -> ImmutableString {
to_binary(value) to_binary(value)
} }
/// Convert the `value` into a string in binary format.
#[rhai_fn(name = "to_binary")] #[rhai_fn(name = "to_binary")]
pub fn i16_to_binary(value: i16) -> ImmutableString { pub fn i16_to_binary(value: i16) -> ImmutableString {
to_binary(value) to_binary(value)
} }
/// Convert the `value` into a string in binary format.
#[rhai_fn(name = "to_binary")] #[rhai_fn(name = "to_binary")]
pub fn i32_to_binary(value: i32) -> ImmutableString { pub fn i32_to_binary(value: i32) -> ImmutableString {
to_binary(value) to_binary(value)
} }
/// Convert the `value` into a string in binary format.
#[rhai_fn(name = "to_binary")] #[rhai_fn(name = "to_binary")]
pub fn i64_to_binary(value: i64) -> ImmutableString { pub fn i64_to_binary(value: i64) -> ImmutableString {
to_binary(value) to_binary(value)
@ -277,14 +315,32 @@ mod number_formatting {
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]
pub mod num_128 { pub mod num_128 {
/// Convert the `value` into a string in hex format.
#[rhai_fn(name = "to_hex")] #[rhai_fn(name = "to_hex")]
pub fn u128_to_hex(value: u128) -> ImmutableString { pub fn u128_to_hex(value: u128) -> ImmutableString {
to_hex(value) 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")] #[rhai_fn(name = "to_octal")]
pub fn i128_to_octal(value: i128) -> ImmutableString { pub fn i128_to_octal(value: i128) -> ImmutableString {
to_octal(value) 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")] #[rhai_fn(name = "to_binary")]
pub fn i128_to_binary(value: i128) -> ImmutableString { pub fn i128_to_binary(value: i128) -> ImmutableString {
to_binary(value) to_binary(value)

View File

@ -73,6 +73,15 @@ mod string_functions {
string 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")] #[rhai_fn(name = "len", get = "len")]
pub fn len(string: &str) -> INT { pub fn len(string: &str) -> INT {
if string.is_empty() { if string.is_empty() {
@ -81,6 +90,15 @@ mod string_functions {
string.chars().count() as INT 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")] #[rhai_fn(name = "bytes", get = "bytes")]
pub fn bytes(string: &str) -> INT { pub fn bytes(string: &str) -> INT {
if string.is_empty() { if string.is_empty() {
@ -89,18 +107,59 @@ mod string_functions {
string.len() as INT 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) { pub fn remove(string: &mut ImmutableString, sub_string: ImmutableString) {
*string -= sub_string; *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")] #[rhai_fn(name = "remove")]
pub fn remove_char(string: &mut ImmutableString, character: char) { pub fn remove_char(string: &mut ImmutableString, character: char) {
*string -= character; *string -= character;
} }
/// Clear the string, making it empty.
pub fn clear(string: &mut ImmutableString) { pub fn clear(string: &mut ImmutableString) {
if !string.is_empty() { if !string.is_empty() {
string.make_mut().clear(); 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) { pub fn truncate(string: &mut ImmutableString, len: INT) {
if len > 0 { if len > 0 {
let chars: StaticVec<_> = string.chars().collect(); let chars: StaticVec<_> = string.chars().collect();
@ -111,6 +170,15 @@ mod string_functions {
string.make_mut().clear(); 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) { pub fn trim(string: &mut ImmutableString) {
let trimmed = string.trim(); let trimmed = string.trim();
@ -118,6 +186,19 @@ mod string_functions {
*string = trimmed.to_string().into(); *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 { pub fn pop(string: &mut ImmutableString) -> Dynamic {
if string.is_empty() { if string.is_empty() {
Dynamic::UNIT 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")] #[rhai_fn(name = "pop")]
pub fn pop_string( pub fn pop_string(
ctx: NativeCallContext, ctx: NativeCallContext,
@ -150,6 +246,17 @@ mod string_functions {
chars.into_iter().rev().collect::<SmartString>().into() chars.into_iter().rev().collect::<SmartString>().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 { pub fn to_upper(string: ImmutableString) -> ImmutableString {
if string.is_empty() { if string.is_empty() {
string string
@ -157,11 +264,33 @@ mod string_functions {
string.to_uppercase().into() 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) { pub fn make_upper(string: &mut ImmutableString) {
if !string.is_empty() { if !string.is_empty() {
*string = string.to_uppercase().into(); *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 { pub fn to_lower(string: ImmutableString) -> ImmutableString {
if string.is_empty() { if string.is_empty() {
string string
@ -169,12 +298,34 @@ mod string_functions {
string.to_lowercase().into() 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) { pub fn make_lower(string: &mut ImmutableString) {
if !string.is_empty() { if !string.is_empty() {
*string = string.to_lowercase().into(); *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")] #[rhai_fn(name = "to_upper")]
pub fn to_upper_char(character: char) -> char { pub fn to_upper_char(character: char) -> char {
let mut stream = character.to_uppercase(); let mut stream = character.to_uppercase();
@ -185,10 +336,32 @@ mod string_functions {
ch ch
} }
} }
/// Convert the character to upper-case.
///
/// # Example
///
/// ```rhai
/// let ch = 'a';
///
/// ch.make_upper();
///
/// print(ch); // prints 'A'
/// ```
#[rhai_fn(name = "make_upper")] #[rhai_fn(name = "make_upper")]
pub fn make_upper_char(character: &mut char) { pub fn make_upper_char(character: &mut char) {
*character = to_upper_char(*character) *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")] #[rhai_fn(name = "to_lower")]
pub fn to_lower_char(character: char) -> char { pub fn to_lower_char(character: char) -> char {
let mut stream = character.to_lowercase(); let mut stream = character.to_lowercase();
@ -199,11 +372,41 @@ mod string_functions {
ch ch
} }
} }
/// Convert the character to lower-case.
///
/// # Example
///
/// ```rhai
/// let ch = 'A';
///
/// ch.make_lower();
///
/// print(ch); // prints 'a'
/// ```
#[rhai_fn(name = "make_lower")] #[rhai_fn(name = "make_lower")]
pub fn make_lower_char(character: &mut char) { pub fn make_lower_char(character: &mut char) {
*character = to_lower_char(*character) *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")] #[rhai_fn(name = "index_of")]
pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT { pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT {
if string.is_empty() { if string.is_empty() {
@ -243,6 +446,18 @@ mod string_functions {
.map(|index| string[0..start + index].chars().count() as INT) .map(|index| string[0..start + index].chars().count() as INT)
.unwrap_or(-1 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")] #[rhai_fn(name = "index_of")]
pub fn index_of_char(string: &str, character: char) -> INT { pub fn index_of_char(string: &str, character: char) -> INT {
if string.is_empty() { if string.is_empty() {
@ -254,6 +469,25 @@ mod string_functions {
.unwrap_or(-1 as INT) .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")] #[rhai_fn(name = "index_of")]
pub fn index_of_string_starting_from(string: &str, find_string: &str, start: INT) -> INT { pub fn index_of_string_starting_from(string: &str, find_string: &str, start: INT) -> INT {
if string.is_empty() { if string.is_empty() {
@ -293,6 +527,18 @@ mod string_functions {
.map(|index| string[0..start + index].chars().count() as INT) .map(|index| string[0..start + index].chars().count() as INT)
.unwrap_or(-1 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")] #[rhai_fn(name = "index_of")]
pub fn index_of(string: &str, find_string: &str) -> INT { pub fn index_of(string: &str, find_string: &str) -> INT {
if string.is_empty() { 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")] #[rhai_fn(name = "sub_string")]
pub fn sub_string_range( pub fn sub_string_range(
ctx: NativeCallContext, ctx: NativeCallContext,
@ -315,6 +570,15 @@ mod string_functions {
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
sub_string(ctx, string, start, 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")] #[rhai_fn(name = "sub_string")]
pub fn sub_string_inclusive_range( pub fn sub_string_inclusive_range(
ctx: NativeCallContext, ctx: NativeCallContext,
@ -325,6 +589,23 @@ mod string_functions {
let end = INT::max(*range.end(), start); let end = INT::max(*range.end(), start);
sub_string(ctx, string, start, end - start + 1) 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( pub fn sub_string(
ctx: NativeCallContext, ctx: NativeCallContext,
string: &str, string: &str,
@ -374,6 +655,22 @@ mod string_functions {
.collect::<String>() .collect::<String>()
.into() .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")] #[rhai_fn(name = "sub_string")]
pub fn sub_string_starting_from( pub fn sub_string_starting_from(
ctx: NativeCallContext, 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")] #[rhai_fn(name = "crop")]
pub fn crop_range(string: &mut ImmutableString, range: ExclusiveRange) { pub fn crop_range(string: &mut ImmutableString, range: ExclusiveRange) {
let start = INT::max(range.start, 0); let start = INT::max(range.start, 0);
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
crop(string, start, 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")] #[rhai_fn(name = "crop")]
pub fn crop_inclusive_range(string: &mut ImmutableString, range: InclusiveRange) { pub fn crop_inclusive_range(string: &mut ImmutableString, range: InclusiveRange) {
let start = INT::max(*range.start(), 0); let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start); let end = INT::max(*range.end(), start);
crop(string, start, end - start + 1) 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")] #[rhai_fn(name = "crop")]
pub fn crop(string: &mut ImmutableString, start: INT, len: INT) { pub fn crop(string: &mut ImmutableString, start: INT, len: INT) {
if string.is_empty() { if string.is_empty() {
@ -443,17 +784,58 @@ mod string_functions {
copy.clear(); copy.clear();
copy.extend(chars.iter().skip(offset).take(len)); 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")] #[rhai_fn(name = "crop")]
pub fn crop_string_starting_from(string: &mut ImmutableString, start: INT) { pub fn crop_string_starting_from(string: &mut ImmutableString, start: INT) {
crop(string, start, string.len() as 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")] #[rhai_fn(name = "replace")]
pub fn replace(string: &mut ImmutableString, find_string: &str, substitute_string: &str) { pub fn replace(string: &mut ImmutableString, find_string: &str, substitute_string: &str) {
if !string.is_empty() { if !string.is_empty() {
*string = string.replace(find_string, substitute_string).into(); *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")] #[rhai_fn(name = "replace")]
pub fn replace_string_with_char( pub fn replace_string_with_char(
string: &mut ImmutableString, string: &mut ImmutableString,
@ -466,6 +848,17 @@ mod string_functions {
.into(); .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")] #[rhai_fn(name = "replace")]
pub fn replace_char_with_string( pub fn replace_char_with_string(
string: &mut ImmutableString, string: &mut ImmutableString,
@ -478,6 +871,17 @@ mod string_functions {
.into(); .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")] #[rhai_fn(name = "replace")]
pub fn replace_char( pub fn replace_char(
string: &mut ImmutableString, 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)] #[rhai_fn(return_raw)]
pub fn pad( pub fn pad(
ctx: NativeCallContext, ctx: NativeCallContext,
@ -538,6 +959,23 @@ mod string_functions {
Ok(()) 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)] #[rhai_fn(name = "pad", return_raw)]
pub fn pad_with_string( pub fn pad_with_string(
ctx: NativeCallContext, ctx: NativeCallContext,
@ -594,6 +1032,14 @@ mod string_functions {
pub mod arrays { pub mod arrays {
use crate::{Array, ImmutableString}; 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")] #[rhai_fn(name = "split")]
pub fn chars(string: &str) -> Array { pub fn chars(string: &str) -> Array {
if string.is_empty() { if string.is_empty() {
@ -602,10 +1048,32 @@ mod string_functions {
string.chars().map(Into::into).collect() 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")] #[rhai_fn(name = "split")]
pub fn split_at(ctx: NativeCallContext, string: ImmutableString, start: INT) -> Array { pub fn split_at(ctx: NativeCallContext, string: ImmutableString, index: INT) -> Array {
if start <= 0 { if index <= 0 {
if let Some(n) = start.checked_abs() { if let Some(n) = index.checked_abs() {
let num_chars = string.chars().count(); let num_chars = string.chars().count();
if n as usize > num_chars { if n as usize > num_chars {
vec![ctx.engine().const_empty_string().into(), string.into()] 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()] vec![ctx.engine().const_empty_string().into(), string.into()]
} }
} else { } 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(); let prefix_len = prefix.len();
vec![prefix.into(), string[prefix_len..].into()] 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 { pub fn split(string: &str, delimiter: &str) -> Array {
string.split(delimiter).map(Into::into).collect() 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")] #[rhai_fn(name = "split")]
pub fn splitn(string: &str, delimiter: &str, segments: INT) -> Array { pub fn splitn(string: &str, delimiter: &str, segments: INT) -> Array {
let pieces: usize = if segments < 1 { 1 } else { segments as usize }; let pieces: usize = if segments < 1 { 1 } else { segments as usize };
string.splitn(pieces, delimiter).map(Into::into).collect() 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")] #[rhai_fn(name = "split")]
pub fn split_char(string: &str, delimiter: char) -> Array { pub fn split_char(string: &str, delimiter: char) -> Array {
string.split(delimiter).map(Into::into).collect() 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")] #[rhai_fn(name = "split")]
pub fn splitn_char(string: &str, delimiter: char, segments: INT) -> Array { pub fn splitn_char(string: &str, delimiter: char, segments: INT) -> Array {
let pieces: usize = if segments < 1 { 1 } else { segments as usize }; let pieces: usize = if segments < 1 { 1 } else { segments as usize };
string.splitn(pieces, delimiter).map(Into::into).collect() 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")] #[rhai_fn(name = "split_rev")]
pub fn rsplit(string: &str, delimiter: &str) -> Array { pub fn rsplit(string: &str, delimiter: &str) -> Array {
string.rsplit(delimiter).map(Into::into).collect() 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")] #[rhai_fn(name = "split_rev")]
pub fn rsplitn(string: &str, delimiter: &str, segments: INT) -> Array { pub fn rsplitn(string: &str, delimiter: &str, segments: INT) -> Array {
let pieces: usize = if segments < 1 { 1 } else { segments as usize }; let pieces: usize = if segments < 1 { 1 } else { segments as usize };
string.rsplitn(pieces, delimiter).map(Into::into).collect() 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")] #[rhai_fn(name = "split_rev")]
pub fn rsplit_char(string: &str, delimiter: char) -> Array { pub fn rsplit_char(string: &str, delimiter: char) -> Array {
string.rsplit(delimiter).map(Into::into).collect() 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")] #[rhai_fn(name = "split_rev")]
pub fn rsplitn_char(string: &str, delimiter: char, segments: INT) -> Array { pub fn rsplitn_char(string: &str, delimiter: char, segments: INT) -> Array {
let pieces: usize = if segments < 1 { 1 } else { segments as usize }; let pieces: usize = if segments < 1 { 1 } else { segments as usize };

View File

@ -25,10 +25,12 @@ def_package! {
#[export_module] #[export_module]
mod time_functions { mod time_functions {
/// Create a timestamp containing the current system time.
pub fn timestamp() -> Instant { pub fn timestamp() -> Instant {
Instant::now() Instant::now()
} }
/// Return the number of seconds between the current system time and the timestamp.
#[rhai_fn(name = "elapsed", get = "elapsed", return_raw)] #[rhai_fn(name = "elapsed", get = "elapsed", return_raw)]
pub fn elapsed(timestamp: Instant) -> RhaiResult { pub fn elapsed(timestamp: Instant) -> RhaiResult {
#[cfg(not(feature = "no_float"))] #[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 = "-")] #[rhai_fn(return_raw, name = "-")]
pub fn time_diff(timestamp1: Instant, timestamp2: Instant) -> RhaiResult { pub fn time_diff(timestamp1: Instant, timestamp2: Instant) -> RhaiResult {
#[cfg(not(feature = "no_float"))] #[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 = "+")] #[rhai_fn(return_raw, name = "+")]
pub fn add(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf<Instant> { pub fn add(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf<Instant> {
add_impl(timestamp, seconds) add_impl(timestamp, seconds)
} }
/// Add the specified number of `seconds` to the timestamp.
#[rhai_fn(return_raw, name = "+=")] #[rhai_fn(return_raw, name = "+=")]
pub fn add_assign(timestamp: &mut Instant, seconds: FLOAT) -> RhaiResultOf<()> { pub fn add_assign(timestamp: &mut Instant, seconds: FLOAT) -> RhaiResultOf<()> {
*timestamp = add_impl(*timestamp, seconds)?; *timestamp = add_impl(*timestamp, seconds)?;
Ok(()) Ok(())
} }
/// Subtract the specified number of `seconds` from the timestamp and return it as a new timestamp.
#[rhai_fn(return_raw, name = "-")] #[rhai_fn(return_raw, name = "-")]
pub fn subtract(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf<Instant> { pub fn subtract(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf<Instant> {
subtract_impl(timestamp, seconds) subtract_impl(timestamp, seconds)
} }
/// Subtract the specified number of `seconds` from the timestamp.
#[rhai_fn(return_raw, name = "-=")] #[rhai_fn(return_raw, name = "-=")]
pub fn subtract_assign(timestamp: &mut Instant, seconds: FLOAT) -> RhaiResultOf<()> { pub fn subtract_assign(timestamp: &mut Instant, seconds: FLOAT) -> RhaiResultOf<()> {
*timestamp = subtract_impl(*timestamp, seconds)?; *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 = "+")] #[rhai_fn(return_raw, name = "+")]
pub fn add(timestamp: Instant, seconds: INT) -> RhaiResultOf<Instant> { pub fn add(timestamp: Instant, seconds: INT) -> RhaiResultOf<Instant> {
add_impl(timestamp, seconds) add_impl(timestamp, seconds)
} }
/// Add the specified number of `seconds` to the timestamp.
#[rhai_fn(return_raw, name = "+=")] #[rhai_fn(return_raw, name = "+=")]
pub fn add_assign(timestamp: &mut Instant, seconds: INT) -> RhaiResultOf<()> { pub fn add_assign(timestamp: &mut Instant, seconds: INT) -> RhaiResultOf<()> {
*timestamp = add_impl(*timestamp, seconds)?; *timestamp = add_impl(*timestamp, seconds)?;
Ok(()) Ok(())
} }
/// Subtract the specified number of `seconds` from the timestamp and return it as a new timestamp.
#[rhai_fn(return_raw, name = "-")] #[rhai_fn(return_raw, name = "-")]
pub fn subtract(timestamp: Instant, seconds: INT) -> RhaiResultOf<Instant> { pub fn subtract(timestamp: Instant, seconds: INT) -> RhaiResultOf<Instant> {
subtract_impl(timestamp, seconds) subtract_impl(timestamp, seconds)
} }
/// Subtract the specified number of `seconds` from the timestamp.
#[rhai_fn(return_raw, name = "-=")] #[rhai_fn(return_raw, name = "-=")]
pub fn subtract_assign(timestamp: &mut Instant, seconds: INT) -> RhaiResultOf<()> { pub fn subtract_assign(timestamp: &mut Instant, seconds: INT) -> RhaiResultOf<()> {
*timestamp = subtract_impl(*timestamp, seconds)?; *timestamp = subtract_impl(*timestamp, seconds)?;
Ok(()) Ok(())
} }
/// Return `true` if two timestamps are equal.
#[rhai_fn(name = "==")] #[rhai_fn(name = "==")]
pub fn eq(timestamp1: Instant, timestamp2: Instant) -> bool { pub fn eq(timestamp1: Instant, timestamp2: Instant) -> bool {
timestamp1 == timestamp2 timestamp1 == timestamp2
} }
/// Return `true` if two timestamps are not equal.
#[rhai_fn(name = "!=")] #[rhai_fn(name = "!=")]
pub fn ne(timestamp1: Instant, timestamp2: Instant) -> bool { pub fn ne(timestamp1: Instant, timestamp2: Instant) -> bool {
timestamp1 != timestamp2 timestamp1 != timestamp2
} }
/// Return `true` if the first timestamp is earlier than the second.
#[rhai_fn(name = "<")] #[rhai_fn(name = "<")]
pub fn lt(timestamp1: Instant, timestamp2: Instant) -> bool { pub fn lt(timestamp1: Instant, timestamp2: Instant) -> bool {
timestamp1 < timestamp2 timestamp1 < timestamp2
} }
/// Return `true` if the first timestamp is earlier than or equals to the second.
#[rhai_fn(name = "<=")] #[rhai_fn(name = "<=")]
pub fn lte(timestamp1: Instant, timestamp2: Instant) -> bool { pub fn lte(timestamp1: Instant, timestamp2: Instant) -> bool {
timestamp1 <= timestamp2 timestamp1 <= timestamp2
} }
/// Return `true` if the first timestamp is later than the second.
#[rhai_fn(name = ">")] #[rhai_fn(name = ">")]
pub fn gt(timestamp1: Instant, timestamp2: Instant) -> bool { pub fn gt(timestamp1: Instant, timestamp2: Instant) -> bool {
timestamp1 > timestamp2 timestamp1 > timestamp2
} }
/// Return `true` if the first timestamp is later than or equals to the second.
#[rhai_fn(name = ">=")] #[rhai_fn(name = ">=")]
pub fn gte(timestamp1: Instant, timestamp2: Instant) -> bool { pub fn gte(timestamp1: Instant, timestamp2: Instant) -> bool {
timestamp1 >= timestamp2 timestamp1 >= timestamp2