From 6048c5804b89b7741974bb3afc1bb7747ea83291 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 11 Jan 2022 12:05:01 +0800 Subject: [PATCH 01/27] Bump version. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c5b66344..8d2b2cd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [".", "codegen"] [package] name = "rhai" -version = "1.4.0" +version = "1.5.0" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] description = "Embedded scripting for Rust" From f0e9d4a557074f9bda53a41079d9a41b3b083792 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 11 Jan 2022 22:12:46 +0800 Subject: [PATCH 02/27] Use run and i64 instead of eval and INT for examples. --- examples/arrays_and_structs.rs | 4 ++-- examples/custom_types_and_methods.rs | 4 ++-- examples/hello.rs | 4 ++-- examples/reuse_scope.rs | 10 +++++----- examples/simple_fn.rs | 6 +++--- examples/strings.rs | 14 +++++++------- examples/threading.rs | 8 ++++---- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/examples/arrays_and_structs.rs b/examples/arrays_and_structs.rs index 8b29d1a8..dab0880b 100644 --- a/examples/arrays_and_structs.rs +++ b/examples/arrays_and_structs.rs @@ -1,8 +1,8 @@ -use rhai::{Engine, EvalAltResult, INT}; +use rhai::{Engine, EvalAltResult}; #[derive(Debug, Clone)] struct TestStruct { - x: INT, + x: i64, } impl TestStruct { diff --git a/examples/custom_types_and_methods.rs b/examples/custom_types_and_methods.rs index fe1777f5..b52d0a27 100644 --- a/examples/custom_types_and_methods.rs +++ b/examples/custom_types_and_methods.rs @@ -1,8 +1,8 @@ -use rhai::{Engine, EvalAltResult, INT}; +use rhai::{Engine, EvalAltResult}; #[derive(Debug, Clone)] struct TestStruct { - x: INT, + x: i64, } impl TestStruct { diff --git a/examples/hello.rs b/examples/hello.rs index 7de8898e..580315dd 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -1,11 +1,11 @@ -use rhai::{Engine, EvalAltResult, INT}; +use rhai::{Engine, EvalAltResult}; fn main() -> Result<(), Box> { let engine = Engine::new(); engine.run(r#"print("hello, world!")"#)?; - let result = engine.eval::("40 + 2")?; + let result = engine.eval::("40 + 2")?; println!("Answer: {}", result); // prints 42 diff --git a/examples/reuse_scope.rs b/examples/reuse_scope.rs index e7446e55..240d48c4 100644 --- a/examples/reuse_scope.rs +++ b/examples/reuse_scope.rs @@ -1,20 +1,20 @@ -use rhai::{Engine, EvalAltResult, Scope, INT}; +use rhai::{Engine, EvalAltResult, Scope}; fn main() -> Result<(), Box> { let engine = Engine::new(); let mut scope = Scope::new(); - engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?; + engine.run_with_scope(&mut scope, "let x = 4 + 5")?; - println!("x = {}", scope.get_value::("x").unwrap()); + println!("x = {}", scope.get_value::("x").unwrap()); for _ in 0..10 { - let result = engine.eval_with_scope::(&mut scope, "x += 1; x")?; + let result = engine.eval_with_scope::(&mut scope, "x += 1; x")?; println!("result: {}", result); } - println!("x = {}", scope.get_value::("x").unwrap()); + println!("x = {}", scope.get_value::("x").unwrap()); Ok(()) } diff --git a/examples/simple_fn.rs b/examples/simple_fn.rs index 9b9b000f..814e9ccf 100644 --- a/examples/simple_fn.rs +++ b/examples/simple_fn.rs @@ -1,6 +1,6 @@ -use rhai::{Engine, EvalAltResult, INT}; +use rhai::{Engine, EvalAltResult}; -fn add(x: INT, y: INT) -> INT { +fn add(x: i64, y: i64) -> i64 { x + y } @@ -9,7 +9,7 @@ fn main() -> Result<(), Box> { engine.register_fn("add", add); - let result = engine.eval::("add(40, 2)")?; + let result = engine.eval::("add(40, 2)")?; println!("Answer: {}", result); // prints 42 diff --git a/examples/strings.rs b/examples/strings.rs index 6d0489ef..a0997da2 100644 --- a/examples/strings.rs +++ b/examples/strings.rs @@ -1,6 +1,6 @@ ///! This example registers a variety of functions that operate on strings. ///! Remember to use `ImmutableString` or `&str` instead of `String` as parameters. -use rhai::{Engine, EvalAltResult, ImmutableString, Scope, INT}; +use rhai::{Engine, EvalAltResult, ImmutableString, Scope}; use std::io::{stdin, stdout, Write}; /// Trim whitespace from a string. The original string argument is changed. @@ -15,26 +15,26 @@ fn trim_string(s: &mut ImmutableString) { /// This version simply counts the number of _bytes_ in the UTF-8 representation. /// /// This version uses `&str`. -fn count_string_bytes(s: &str) -> INT { - s.len() as INT +fn count_string_bytes(s: &str) -> i64 { + s.len() as i64 } /// This version uses `ImmutableString` and `&str`. -fn find_substring(s: ImmutableString, sub: &str) -> INT { - s.find(sub).map(|x| x as INT).unwrap_or(-1) +fn find_substring(s: ImmutableString, sub: &str) -> i64 { + s.find(sub).map(|x| x as i64).unwrap_or(-1) } fn main() -> Result<(), Box> { // Create a `raw` Engine with no built-in string functions. let mut engine = Engine::new_raw(); - // Register string functions engine + // Register string functions .register_fn("trim", trim_string) .register_fn("len", count_string_bytes) .register_fn("index_of", find_substring) // Register string functions using closures - .register_fn("display", |label: &str, value: INT| { + .register_fn("display", |label: &str, value: i64| { println!("{}: {}", label, value) }) .register_fn("display", |label: ImmutableString, value: &str| { diff --git a/examples/threading.rs b/examples/threading.rs index f96a8866..d28f62d7 100644 --- a/examples/threading.rs +++ b/examples/threading.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, INT}; +use rhai::Engine; #[cfg(feature = "sync")] use std::sync::Mutex; @@ -23,12 +23,12 @@ fn main() { #[cfg(not(feature = "sync"))] engine .register_fn("get", move || rx_script.recv().unwrap_or_default()) - .register_fn("put", move |v: INT| tx_script.send(v).unwrap()); + .register_fn("put", move |v: i64| tx_script.send(v).unwrap()); #[cfg(feature = "sync")] engine .register_fn("get", move || rx_script.lock().unwrap().recv().unwrap()) - .register_fn("put", move |v: INT| { + .register_fn("put", move |v: i64| { tx_script.lock().unwrap().send(v).unwrap() }); @@ -54,7 +54,7 @@ fn main() { println!("Starting main loop..."); - let mut value: INT = 0; + let mut value: i64 = 0; while value < 10 { println!("Value: {}", value); From 37dbc68bf5b8a67a125adb3c4ac82f55312e67fc Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 12 Jan 2022 08:12:28 +0800 Subject: [PATCH 03/27] Use target_family for wasm. --- Cargo.toml | 5 +--- src/api/deprecated.rs | 6 ++-- src/api/files.rs | 3 +- src/engine.rs | 58 +++++++++++++----------------------- src/func/builtin.rs | 3 +- src/lib.rs | 12 ++++---- src/module/resolvers/file.rs | 3 +- src/module/resolvers/mod.rs | 3 +- src/packages/arithmetic.rs | 12 ++++---- src/packages/iter_basic.rs | 8 ++--- src/packages/logic.rs | 8 ++--- src/packages/math_basic.rs | 16 +++++----- src/packages/string_basic.rs | 4 +-- src/packages/time_basic.rs | 5 ++-- src/parser.rs | 18 ++++++++++- src/types/dynamic.rs | 14 ++++----- src/types/error.rs | 2 +- tests/time.rs | 3 +- 18 files changed, 82 insertions(+), 101 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8d2b2cd1..006c67bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,10 +97,7 @@ default-features = false features = ["maths"] optional = true -[target.'cfg(target_arch = "wasm32")'.dependencies] -instant = { version = "0.1.10" } # WASM implementation of std::time::Instant - -[target.'cfg(target_arch = "wasm64")'.dependencies] +[target.'cfg(target_family = "wasm")'.dependencies] instant = { version = "0.1.10" } # WASM implementation of std::time::Instant [package.metadata.docs.rs] diff --git a/src/api/deprecated.rs b/src/api/deprecated.rs index 166daf4f..dd8ebd7a 100644 --- a/src/api/deprecated.rs +++ b/src/api/deprecated.rs @@ -20,8 +20,7 @@ impl Engine { /// This method will be removed in the next major version. #[deprecated(since = "1.1.0", note = "use `run_file` instead")] #[cfg(not(feature = "no_std"))] - #[cfg(not(target_arch = "wasm32"))] - #[cfg(not(target_arch = "wasm64"))] + #[cfg(not(target_family = "wasm"))] #[inline(always)] pub fn consume_file(&self, path: std::path::PathBuf) -> RhaiResultOf<()> { self.run_file(path) @@ -39,8 +38,7 @@ impl Engine { /// This method will be removed in the next major version. #[deprecated(since = "1.1.0", note = "use `run_file_with_scope` instead")] #[cfg(not(feature = "no_std"))] - #[cfg(not(target_arch = "wasm32"))] - #[cfg(not(target_arch = "wasm64"))] + #[cfg(not(target_family = "wasm"))] #[inline(always)] pub fn consume_file_with_scope( &self, diff --git a/src/api/files.rs b/src/api/files.rs index 82267f01..8c058f27 100644 --- a/src/api/files.rs +++ b/src/api/files.rs @@ -1,7 +1,6 @@ //! Module that defines the public file-based API of [`Engine`]. #![cfg(not(feature = "no_std"))] -#![cfg(not(target_arch = "wasm32"))] -#![cfg(not(target_arch = "wasm64"))] +#![cfg(not(target_family = "wasm"))] use crate::types::dynamic::Variant; use crate::{Engine, RhaiResultOf, Scope, AST, ERR}; diff --git a/src/engine.rs b/src/engine.rs index 0487f652..8bbe3046 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -203,40 +203,6 @@ pub fn make_setter(id: &str) -> Identifier { buf } -/// Is this function an anonymous function? -#[cfg(not(feature = "no_function"))] -#[inline(always)] -#[must_use] -pub fn is_anonymous_fn(fn_name: &str) -> bool { - fn_name.starts_with(FN_ANONYMOUS) -} - -/// Print to `stdout` -#[inline] -#[allow(unused_variables)] -fn print_to_stdout(s: &str) { - #[cfg(not(feature = "no_std"))] - #[cfg(not(target_arch = "wasm32"))] - #[cfg(not(target_arch = "wasm64"))] - println!("{}", s); -} - -/// Debug to `stdout` -#[inline] -#[allow(unused_variables)] -fn debug_to_stdout(s: &str, source: Option<&str>, pos: Position) { - #[cfg(not(feature = "no_std"))] - #[cfg(not(target_arch = "wasm32"))] - #[cfg(not(target_arch = "wasm64"))] - if let Some(source) = source { - println!("{}{:?} | {}", source, pos, s); - } else if pos.is_none() { - println!("{}", s); - } else { - println!("{:?} | {}", pos, s); - } -} - impl Engine { /// Create a new [`Engine`]. #[inline] @@ -247,16 +213,32 @@ impl Engine { #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_std"))] - #[cfg(not(target_arch = "wasm32"))] - #[cfg(not(target_arch = "wasm64"))] + #[cfg(not(target_family = "wasm"))] { engine.module_resolver = Some(Box::new(crate::module::resolvers::FileModuleResolver::new())); } // default print/debug implementations - engine.print = Some(Box::new(print_to_stdout)); - engine.debug = Some(Box::new(debug_to_stdout)); + #[cfg(not(feature = "no_std"))] + #[cfg(not(target_family = "wasm"))] + { + engine.print = Some(Box::new(|s| println!("{}", s))); + engine.debug = Some(Box::new(|s, source, pos| { + if let Some(source) = source { + println!("{}{:?} | {}", source, pos, s); + } else if pos.is_none() { + println!("{}", s); + } else { + println!("{:?} | {}", pos, s); + } + })); + } + #[cfg(any(feature = "no_std", target_family = "wasm"))] + { + engine.print = None; + engine.debug = None; + } engine.register_global_module(StandardPackage::new().as_shared_module()); diff --git a/src/func/builtin.rs b/src/func/builtin.rs index a84b437c..f2284fbd 100644 --- a/src/func/builtin.rs +++ b/src/func/builtin.rs @@ -41,8 +41,7 @@ fn is_numeric(type_id: TypeId) -> bool { #[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i32"))] - #[cfg(not(target_arch = "wasm32"))] - #[cfg(not(target_arch = "wasm64"))] + #[cfg(not(target_family = "wasm"))] let result = result || type_id == TypeId::of::() || type_id == TypeId::of::(); #[cfg(not(feature = "no_float"))] diff --git a/src/lib.rs b/src/lib.rs index 3c6e616e..39e02851 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,8 +41,8 @@ //! engine.register_fn("compute", compute_something); //! //! # #[cfg(not(feature = "no_std"))] -//! # #[cfg(not(target_arch = "wasm32"))] -//! # #[cfg(not(target_arch = "wasm64"))] +//! # #[cfg(not(target_family = "wasm"))] +//! # //! // Evaluate the script, expecting a 'bool' result //! let result = engine.eval_file::("my_script.rhai".into())?; //! @@ -365,17 +365,15 @@ compile_error!("`wasm-bindgen` cannot be used with `no-std`"); #[cfg(feature = "stdweb")] compile_error!("`stdweb` cannot be used with `no-std`"); -#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] +#[cfg(target_family = "wasm")] #[cfg(feature = "no_std")] compile_error!("`no_std` cannot be used for WASM target"); -#[cfg(not(target_arch = "wasm32"))] -#[cfg(not(target_arch = "wasm64"))] +#[cfg(not(target_family = "wasm"))] #[cfg(feature = "wasm-bindgen")] compile_error!("`wasm-bindgen` cannot be used for non-WASM target"); -#[cfg(not(target_arch = "wasm32"))] -#[cfg(not(target_arch = "wasm64"))] +#[cfg(not(target_family = "wasm"))] #[cfg(feature = "stdweb")] compile_error!("`stdweb` cannot be used non-WASM target"); diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index 6d65a063..71828d81 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -1,6 +1,5 @@ #![cfg(not(feature = "no_std"))] -#![cfg(not(target_arch = "wasm32"))] -#![cfg(not(target_arch = "wasm64"))] +#![cfg(not(target_family = "wasm"))] use crate::func::native::shared_write_lock; use crate::{ diff --git a/src/module/resolvers/mod.rs b/src/module/resolvers/mod.rs index 82d350c8..cf14c2b8 100644 --- a/src/module/resolvers/mod.rs +++ b/src/module/resolvers/mod.rs @@ -11,8 +11,7 @@ mod stat; pub use collection::ModuleResolversCollection; pub use dummy::DummyModuleResolver; #[cfg(not(feature = "no_std"))] -#[cfg(not(target_arch = "wasm32"))] -#[cfg(not(target_arch = "wasm64"))] +#[cfg(not(target_family = "wasm"))] pub use file::FileModuleResolver; pub use stat::StaticModuleResolver; diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index b908cb49..7d135620 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -193,8 +193,8 @@ def_package! { reg_functions!(lib += arith_numbers; i8, u8, i16, u16, i32, u32, u64); reg_functions!(lib += signed_numbers; i8, i16, i32); - #[cfg(not(target_arch = "wasm32"))] - #[cfg(not(target_arch = "wasm64"))] + #[cfg(not(target_family = "wasm"))] + { reg_functions!(lib += arith_num_128; i128, u128); reg_functions!(lib += signed_num_128; i128); @@ -238,8 +238,8 @@ gen_arithmetic_functions!(arith_numbers => i8, u8, i16, u16, i32, u32, u64); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] -#[cfg(not(target_arch = "wasm32"))] -#[cfg(not(target_arch = "wasm64"))] +#[cfg(not(target_family = "wasm"))] + gen_arithmetic_functions!(arith_num_128 => i128, u128); gen_signed_functions!(signed_basic => INT); @@ -250,8 +250,8 @@ gen_signed_functions!(signed_numbers => i8, i16, i32); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] -#[cfg(not(target_arch = "wasm32"))] -#[cfg(not(target_arch = "wasm64"))] +#[cfg(not(target_family = "wasm"))] + gen_signed_functions!(signed_num_128 => i128); #[cfg(not(feature = "no_float"))] diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index 6dbd8eaf..eca73120 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -303,8 +303,8 @@ def_package! { { reg_range!(lib | "range" => i8, u8, i16, u16, i32, u32, i64, u64); - #[cfg(not(target_arch = "wasm32"))] - #[cfg(not(target_arch = "wasm64"))] + #[cfg(not(target_family = "wasm"))] + reg_range!(lib | "range" => i128, u128); } @@ -315,8 +315,8 @@ def_package! { { reg_range!(lib | step "range" => i8, u8, i16, u16, i32, u32, i64, u64); - #[cfg(not(target_arch = "wasm32"))] - #[cfg(not(target_arch = "wasm64"))] + #[cfg(not(target_family = "wasm"))] + reg_range!(lib | step "range" => i128, u128); } diff --git a/src/packages/logic.rs b/src/packages/logic.rs index 0e435c1e..e577be58 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -47,8 +47,8 @@ def_package! { { reg_functions!(lib += numbers; i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - #[cfg(not(target_arch = "wasm64"))] + #[cfg(not(target_family = "wasm"))] + reg_functions!(lib += num_128; i128, u128); } @@ -71,8 +71,8 @@ gen_cmp_functions!(numbers => i8, u8, i16, u16, i32, u32, u64); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] -#[cfg(not(target_arch = "wasm32"))] -#[cfg(not(target_arch = "wasm64"))] +#[cfg(not(target_family = "wasm"))] + gen_cmp_functions!(num_128 => i128, u128); #[cfg(not(feature = "no_float"))] diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index a6dfa939..83f2efac 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -66,8 +66,8 @@ def_package! { { reg_functions!(lib += numbers_to_int::to_int(i8, u8, i16, u16, i32, u32, i64, u64)); - #[cfg(not(target_arch = "wasm32"))] - #[cfg(not(target_arch = "wasm64"))] + #[cfg(not(target_family = "wasm"))] + reg_functions!(lib += num_128_to_int::to_int(i128, u128)); } @@ -86,8 +86,8 @@ def_package! { { reg_functions!(lib += numbers_to_float::to_float(i8, u8, i16, u16, i32, u32, i64, u32)); - #[cfg(not(target_arch = "wasm32"))] - #[cfg(not(target_arch = "wasm64"))] + #[cfg(not(target_family = "wasm"))] + reg_functions!(lib += num_128_to_float::to_float(i128, u128)); } } @@ -522,8 +522,8 @@ gen_conversion_as_functions!(numbers_to_float => to_float (i8, u8, i16, u16, i32 #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] -#[cfg(not(target_arch = "wasm32"))] -#[cfg(not(target_arch = "wasm64"))] +#[cfg(not(target_family = "wasm"))] + gen_conversion_as_functions!(num_128_to_float => to_float (i128, u128) -> FLOAT); gen_conversion_as_functions!(basic_to_int => to_int (char) -> INT); @@ -534,8 +534,8 @@ gen_conversion_as_functions!(numbers_to_int => to_int (i8, u8, i16, u16, i32, u3 #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] -#[cfg(not(target_arch = "wasm32"))] -#[cfg(not(target_arch = "wasm64"))] +#[cfg(not(target_family = "wasm"))] + gen_conversion_as_functions!(num_128_to_int => to_int (i128, u128) -> INT); #[cfg(feature = "decimal")] diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 736fdd95..1debcbb2 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -274,8 +274,8 @@ mod number_formatting { to_binary(value) } - #[cfg(not(target_arch = "wasm32"))] - #[cfg(not(target_arch = "wasm64"))] + #[cfg(not(target_family = "wasm"))] + pub mod num_128 { #[rhai_fn(name = "to_hex")] pub fn u128_to_hex(value: u128) -> ImmutableString { diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 5f6721bf..54254611 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -7,11 +7,10 @@ use crate::{def_package, Dynamic, EvalAltResult, RhaiResult, RhaiResultOf, INT}; #[cfg(not(feature = "no_float"))] use crate::FLOAT; -#[cfg(not(target_arch = "wasm32"))] -#[cfg(not(target_arch = "wasm64"))] +#[cfg(not(target_family = "wasm"))] use std::time::{Duration, Instant}; -#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] +#[cfg(target_family = "wasm")] use instant::{Duration, Instant}; def_package! { diff --git a/src/parser.rs b/src/parser.rs index c4c5311f..9bfc84f3 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -242,6 +242,22 @@ impl ParseSettings { } } +/// Make an anonymous function. +#[cfg(not(feature = "no_function"))] +#[inline] +#[must_use] +pub fn make_anonymous_fn(hash: u64) -> String { + format!("{}{:016x}", crate::engine::FN_ANONYMOUS, hash) +} + +/// Is this function an anonymous function? +#[cfg(not(feature = "no_function"))] +#[inline(always)] +#[must_use] +pub fn is_anonymous_fn(fn_name: &str) -> bool { + fn_name.starts_with(crate::engine::FN_ANONYMOUS) +} + impl Expr { /// Convert a [`Variable`][Expr::Variable] into a [`Property`][Expr::Property]. /// All other variants are untouched. @@ -3253,7 +3269,7 @@ fn parse_anon_fn( params.iter().for_each(|p| p.hash(hasher)); body.hash(hasher); let hash = hasher.finish(); - let fn_name = state.get_identifier("", format!("{}{:016x}", crate::engine::FN_ANONYMOUS, hash)); + let fn_name = state.get_identifier("", make_anonymous_fn(hash)); // Define the function let script = ScriptFnDef { diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index 3cb2f857..800ffb2b 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -15,12 +15,11 @@ use std::{ }; #[cfg(not(feature = "no_std"))] -#[cfg(not(target_arch = "wasm32"))] -#[cfg(not(target_arch = "wasm64"))] +#[cfg(not(target_family = "wasm"))] use std::time::Instant; #[cfg(not(feature = "no_std"))] -#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] +#[cfg(target_family = "wasm")] use instant::Instant; /// The message: data type was checked @@ -525,8 +524,7 @@ impl Hash for Dynamic { value_any.downcast_ref::().expect(CHECKED).hash(state); } - #[cfg(not(target_arch = "wasm32"))] - #[cfg(not(target_arch = "wasm64"))] + #[cfg(not(target_family = "wasm"))] if type_id == TypeId::of::() { TypeId::of::().hash(state); value_any.downcast_ref::().expect(CHECKED).hash(state); @@ -650,8 +648,7 @@ impl fmt::Display for Dynamic { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] - #[cfg(not(target_arch = "wasm32"))] - #[cfg(not(target_arch = "wasm64"))] + #[cfg(not(target_family = "wasm"))] if _type_id == TypeId::of::() { return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { @@ -756,8 +753,7 @@ impl fmt::Debug for Dynamic { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] - #[cfg(not(target_arch = "wasm32"))] - #[cfg(not(target_arch = "wasm64"))] + #[cfg(not(target_family = "wasm"))] if _type_id == TypeId::of::() { return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { diff --git a/src/types/error.rs b/src/types/error.rs index 78cf4e24..c07e0fca 100644 --- a/src/types/error.rs +++ b/src/types/error.rs @@ -115,7 +115,7 @@ impl fmt::Display for EvalAltResult { Self::ErrorParsing(p, _) => write!(f, "Syntax error: {}", p)?, #[cfg(not(feature = "no_function"))] - Self::ErrorInFunctionCall(s, src, err, _) if crate::engine::is_anonymous_fn(s) => { + Self::ErrorInFunctionCall(s, src, err, _) if crate::parser::is_anonymous_fn(s) => { write!(f, "{} in call to closure", err)?; if !src.is_empty() { write!(f, " @ '{}'", src)?; diff --git a/tests/time.rs b/tests/time.rs index 03df04bc..7a35da15 100644 --- a/tests/time.rs +++ b/tests/time.rs @@ -1,6 +1,5 @@ #![cfg(not(feature = "no_std"))] -#![cfg(not(target_arch = "wasm32"))] -#![cfg(not(target_arch = "wasm64"))] +#![cfg(not(target_family = "wasm"))] use rhai::{Engine, EvalAltResult}; From 4b4a6c944d9b22982619415345b4571aaf0c6d9b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 12 Jan 2022 11:01:03 +0800 Subject: [PATCH 04/27] Fix test. --- src/tests.rs | 46 +++++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/src/tests.rs b/src/tests.rs index ce0a89b3..322e6cdb 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -10,7 +10,7 @@ fn check_struct_sizes() { const PACKED: bool = cfg!(all( target_pointer_width = "32", feature = "only_i32", - feature = "no_float" + any(feature = "no_float", feature = "f32_float") )); assert_eq!(size_of::(), if PACKED { 8 } else { 16 }); @@ -19,24 +19,28 @@ fn check_struct_sizes() { size_of::(), if cfg!(feature = "no_position") { 0 } else { 4 } ); - assert_eq!(size_of::(), 16); - assert_eq!(size_of::>(), 16); - assert_eq!(size_of::(), 32); - assert_eq!(size_of::>(), 32); - assert_eq!(size_of::(), 80); - assert_eq!(size_of::(), 464); - assert_eq!(size_of::(), 56); - assert_eq!( - size_of::(), - if cfg!(feature = "no_position") { 8 } else { 16 } - ); - assert_eq!(size_of::(), 72); - assert_eq!( - size_of::(), - if cfg!(feature = "no_position") { - 64 - } else { - 72 - } - ); + assert_eq!(size_of::(), if PACKED { 12 } else { 16 }); + assert_eq!(size_of::>(), if PACKED { 12 } else { 16 }); + assert_eq!(size_of::(), if PACKED { 24 } else { 32 }); + assert_eq!(size_of::>(), if PACKED { 24 } else { 32 }); + assert_eq!(size_of::(), if PACKED { 40 } else { 80 }); + assert_eq!(size_of::(), if PACKED { 232 } else { 464 }); + + #[cfg(target_pointer_width = "64")] + { + assert_eq!(size_of::(), 56); + assert_eq!( + size_of::(), + if cfg!(feature = "no_position") { 8 } else { 16 } + ); + assert_eq!(size_of::(), 72); + assert_eq!( + size_of::(), + if cfg!(feature = "no_position") { + 64 + } else { + 72 + } + ); + } } From 0f4e8848f957e08bc34fa8643b151d47d33a4fa1 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 13 Jan 2022 18:13:27 +0800 Subject: [PATCH 05/27] Extract index calculataion into functions. --- CHANGELOG.md | 9 + src/eval/chaining.rs | 163 +++++------------- src/eval/mod.rs | 2 +- src/eval/target.rs | 68 +++++++- src/lib.rs | 2 +- src/packages/array_basic.rs | 169 +++++------------- src/packages/bit_field.rs | 157 +++++------------ src/packages/blob_basic.rs | 237 +++++++------------------- src/packages/iter_basic.rs | 28 +-- src/packages/map_basic.rs | 5 +- src/packages/math_basic.rs | 4 +- src/tokenizer.rs | 4 +- tests/arrays.rs | 24 +++ tests/{bit_shift.rs => bit_fields.rs} | 14 ++ 14 files changed, 328 insertions(+), 558 deletions(-) rename tests/{bit_shift.rs => bit_fields.rs} (76%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74369277..1340b2e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ Rhai Release Notes ================== +Version 1.5.0 +============= + +Bug fixes +--------- + +* `set_bit` for bit-flags with negative index now works correctly. + + Version 1.4.0 ============= diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index 85aae79c..2812a163 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -1,7 +1,7 @@ //! Types to support chaining operations (i.e. indexing and dotting). #![cfg(any(not(feature = "no_index"), not(feature = "no_object")))] -use super::{EvalState, GlobalRuntimeState, Target}; +use super::{calc_index, EvalState, GlobalRuntimeState, Target}; use crate::ast::{Expr, OpAssignment}; use crate::types::dynamic::Union; use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, ERR}; @@ -771,7 +771,7 @@ impl Engine { lib: &[&Module], target: &'t mut Dynamic, idx: Dynamic, - idx_pos: Position, + pos: Position, add_if_not_found: bool, use_indexers: bool, level: usize, @@ -788,38 +788,12 @@ impl Engine { // val_array[idx] let index = idx .as_int() - .map_err(|typ| self.make_type_mismatch_err::(typ, idx_pos))?; + .map_err(|typ| self.make_type_mismatch_err::(typ, pos))?; + let len = arr.len(); + let arr_idx = + calc_index(len, index, true, || ERR::ErrorArrayBounds(len, index, pos))?; - let arr_len = arr.len(); - - #[cfg(not(feature = "unchecked"))] - let arr_idx = if index < 0 { - // Count from end if negative - arr_len - - index - .checked_abs() - .ok_or_else(|| ERR::ErrorArrayBounds(arr_len, index, idx_pos)) - .and_then(|n| { - if n as usize > arr_len { - Err(ERR::ErrorArrayBounds(arr_len, index, idx_pos).into()) - } else { - Ok(n as usize) - } - })? - } else { - index as usize - }; - #[cfg(feature = "unchecked")] - let arr_idx = if index < 0 { - // Count from end if negative - arr_len - index.abs() as usize - } else { - index as usize - }; - - arr.get_mut(arr_idx) - .map(Target::from) - .ok_or_else(|| ERR::ErrorArrayBounds(arr_len, index, idx_pos).into()) + Ok(arr.get_mut(arr_idx).map(Target::from).unwrap()) } #[cfg(not(feature = "no_index"))] @@ -827,39 +801,13 @@ impl Engine { // val_blob[idx] let index = idx .as_int() - .map_err(|typ| self.make_type_mismatch_err::(typ, idx_pos))?; + .map_err(|typ| self.make_type_mismatch_err::(typ, pos))?; + let len = arr.len(); + let arr_idx = + calc_index(len, index, true, || ERR::ErrorArrayBounds(len, index, pos))?; - let arr_len = arr.len(); + let value = arr.get(arr_idx).map(|&v| (v as crate::INT).into()).unwrap(); - #[cfg(not(feature = "unchecked"))] - let arr_idx = if index < 0 { - // Count from end if negative - arr_len - - index - .checked_abs() - .ok_or_else(|| ERR::ErrorArrayBounds(arr_len, index, idx_pos)) - .and_then(|n| { - if n as usize > arr_len { - Err(ERR::ErrorArrayBounds(arr_len, index, idx_pos).into()) - } else { - Ok(n as usize) - } - })? - } else { - index as usize - }; - #[cfg(feature = "unchecked")] - let arr_idx = if index < 0 { - // Count from end if negative - arr_len - index.abs() as usize - } else { - index as usize - }; - - let value = arr - .get(arr_idx) - .map(|&v| (v as crate::INT).into()) - .ok_or_else(|| Box::new(ERR::ErrorArrayBounds(arr_len, index, idx_pos)))?; Ok(Target::BlobByte { source: target, value, @@ -871,7 +819,7 @@ impl Engine { Dynamic(Union::Map(map, _, _)) => { // val_map[idx] let index = idx.read_lock::().ok_or_else(|| { - self.make_type_mismatch_err::(idx.type_name(), idx_pos) + self.make_type_mismatch_err::(idx.type_name(), pos) })?; if _add_if_not_found && !map.contains_key(index.as_str()) { @@ -888,11 +836,6 @@ impl Engine { Dynamic(Union::Int(value, _, _)) if idx.is::() || idx.is::() => { - #[cfg(not(feature = "only_i32"))] - type BASE = u64; - #[cfg(feature = "only_i32")] - type BASE = u32; - // val_int[range] const BITS: usize = std::mem::size_of::() * 8; @@ -900,40 +843,47 @@ impl Engine { let start = range.start; let end = range.end; - if start < 0 || start as usize >= BITS { - return Err(ERR::ErrorBitFieldBounds(BITS, start, idx_pos).into()); - } else if end < 0 || end as usize >= BITS { - return Err(ERR::ErrorBitFieldBounds(BITS, end, idx_pos).into()); - } else if end <= start { + let start = calc_index(BITS, start, false, || { + ERR::ErrorBitFieldBounds(BITS, start, pos) + })?; + let end = calc_index(BITS, end, false, || { + ERR::ErrorBitFieldBounds(BITS, end, pos) + })?; + + if end <= start { (0, 0) - } else if end as usize == BITS && start == 0 { + } else if end == BITS && start == 0 { // -1 = all bits set (0, -1) } else { ( start as u8, // 2^bits - 1 - (((2 as BASE).pow((end - start) as u32) - 1) as crate::INT) << start, + (((2 as crate::UINT).pow((end - start) as u32) - 1) as crate::INT) + << start, ) } } else if let Some(range) = idx.read_lock::() { let start = *range.start(); let end = *range.end(); - if start < 0 || start as usize >= BITS { - return Err(ERR::ErrorBitFieldBounds(BITS, start, idx_pos).into()); - } else if end < 0 || end as usize >= BITS { - return Err(ERR::ErrorBitFieldBounds(BITS, end, idx_pos).into()); - } else if end < start { + let start = calc_index(BITS, start, false, || { + ERR::ErrorBitFieldBounds(BITS, start, pos) + })?; + let end = calc_index(BITS, end, false, || { + ERR::ErrorBitFieldBounds(BITS, end, pos) + })?; + + if end < start { (0, 0) - } else if end as usize == BITS - 1 && start == 0 { + } else if end == BITS - 1 && start == 0 { // -1 = all bits set (0, -1) } else { ( start as u8, // 2^bits - 1 - (((2 as BASE).pow((end - start + 1) as u32) - 1) as crate::INT) + (((2 as crate::UINT).pow((end - start + 1) as u32) - 1) as crate::INT) << start, ) } @@ -956,39 +906,20 @@ impl Engine { // val_int[idx] let index = idx .as_int() - .map_err(|typ| self.make_type_mismatch_err::(typ, idx_pos))?; + .map_err(|typ| self.make_type_mismatch_err::(typ, pos))?; const BITS: usize = std::mem::size_of::() * 8; - let (bit_value, offset) = if index >= 0 { - let offset = index as usize; - ( - if offset >= BITS { - return Err(ERR::ErrorBitFieldBounds(BITS, index, idx_pos).into()); - } else { - (*value & (1 << offset)) != 0 - }, - offset as u8, - ) - } else if let Some(abs_index) = index.checked_abs() { - let offset = abs_index as usize; - ( - // Count from end if negative - if offset > BITS { - return Err(ERR::ErrorBitFieldBounds(BITS, index, idx_pos).into()); - } else { - (*value & (1 << (BITS - offset))) != 0 - }, - offset as u8, - ) - } else { - return Err(ERR::ErrorBitFieldBounds(BITS, index, idx_pos).into()); - }; + let bit = calc_index(BITS, index, true, || { + ERR::ErrorBitFieldBounds(BITS, index, pos) + })?; + + let bit_value = (*value & (1 << bit)) != 0; Ok(Target::Bit { source: target, value: bit_value.into(), - bit: offset, + bit: bit as u8, }) } @@ -997,14 +928,14 @@ impl Engine { // val_string[idx] let index = idx .as_int() - .map_err(|typ| self.make_type_mismatch_err::(typ, idx_pos))?; + .map_err(|typ| self.make_type_mismatch_err::(typ, pos))?; let (ch, offset) = if index >= 0 { let offset = index as usize; ( s.chars().nth(offset).ok_or_else(|| { let chars_len = s.chars().count(); - ERR::ErrorStringBounds(chars_len, index, idx_pos) + ERR::ErrorStringBounds(chars_len, index, pos) })?, offset, ) @@ -1014,13 +945,13 @@ impl Engine { // Count from end if negative s.chars().rev().nth(offset - 1).ok_or_else(|| { let chars_len = s.chars().count(); - ERR::ErrorStringBounds(chars_len, index, idx_pos) + ERR::ErrorStringBounds(chars_len, index, pos) })?, offset, ) } else { let chars_len = s.chars().count(); - return Err(ERR::ErrorStringBounds(chars_len, index, idx_pos).into()); + return Err(ERR::ErrorStringBounds(chars_len, index, pos).into()); }; Ok(Target::StringChar { @@ -1033,8 +964,6 @@ impl Engine { _ if use_indexers => { let args = &mut [target, &mut idx]; let hash_get = crate::ast::FnCallHashes::from_native(global.hash_idx_get()); - let idx_pos = Position::NONE; - self.exec_fn_call( global, state, @@ -1044,7 +973,7 @@ impl Engine { args, true, true, - idx_pos, + Position::NONE, None, level, ) diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 32f6430e..9429672e 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -12,4 +12,4 @@ pub use chaining::{ChainArgument, ChainType}; pub use eval_context::EvalContext; pub use eval_state::EvalState; pub use global_state::GlobalRuntimeState; -pub use target::Target; +pub use target::{calc_index, calc_offset_len, Target}; diff --git a/src/eval/target.rs b/src/eval/target.rs index 4c644fdd..a2462212 100644 --- a/src/eval/target.rs +++ b/src/eval/target.rs @@ -1,11 +1,77 @@ //! Type to hold a mutable reference to the target of an evaluation. use crate::types::dynamic::Variant; -use crate::{Dynamic, RhaiResultOf}; +use crate::{Dynamic, EvalAltResult, RhaiResultOf, INT}; use std::ops::{Deref, DerefMut}; #[cfg(feature = "no_std")] use std::prelude::v1::*; +// Calculate an offset+len pair given an actual length of the underlying array. +// +// Negative starting positions count from the end. +// +// Values going over bounds are limited to the actual length. +#[inline(always)] +pub fn calc_offset_len(length: usize, start: INT, len: INT) -> (usize, usize) { + let start = if start < 0 { + start.checked_abs().map_or(0, |positive_start| { + length - usize::min(positive_start as usize, length) + }) + } else if start as usize >= length { + return (length, 0); + } else { + start as usize + }; + + let len = if len <= 0 { + 0 + } else if len as usize > length - start { + length - start + } else { + len as usize + }; + + (start, len) +} + +// Calculate an offset+len pair given an actual length of the underlying array. +// +// Negative starting positions count from the end. +// +// Values going over bounds call the provided closure to create an error. +#[inline(always)] +pub fn calc_index( + length: usize, + start: INT, + negative_count_from_end: bool, + err: impl Fn() -> EvalAltResult, +) -> RhaiResultOf { + if start < 0 { + if negative_count_from_end { + // Count from end if negative + #[cfg(not(feature = "unchecked"))] + return start + .checked_abs() + .ok_or_else(|| err().into()) + .and_then(|positive_start| { + if (positive_start as usize) > length { + Err(err().into()) + } else { + Ok(length - (positive_start as usize)) + } + }); + #[cfg(feature = "unchecked")] + return Ok(actual - (start.abs() as usize)); + } else { + Err(err().into()) + } + } else if start as usize >= length { + Err(err().into()) + } else { + Ok(start as usize) + } +} + /// A type that encapsulates a mutation target for an expression with side effects. #[derive(Debug)] pub enum Target<'a> { diff --git a/src/lib.rs b/src/lib.rs index 39e02851..44cd7d70 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,7 +112,7 @@ pub type INT = i32; /// If the `only_i32` feature is enabled, this will be [`u32`] instead. #[cfg(not(feature = "only_i32"))] #[allow(non_camel_case_types)] -type UNSIGNED_INT = u64; +type UINT = u64; /// The unsigned system integer base type. /// It is defined as [`u32`] since the `only_i32` feature is used. /// diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index c2ad47af..52b1e16c 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -2,6 +2,7 @@ #![allow(non_snake_case)] use crate::engine::OP_EQUALS; +use crate::eval::calc_offset_len; use crate::plugin::*; use crate::{ def_package, Array, Dynamic, ExclusiveRange, FnPtr, InclusiveRange, NativeCallContext, @@ -55,23 +56,18 @@ pub mod array_functions { array1 } } - pub fn insert(array: &mut Array, position: INT, item: Dynamic) { + pub fn insert(array: &mut Array, index: INT, item: Dynamic) { if array.is_empty() { array.push(item); - } else if position < 0 { - if let Some(n) = position.checked_abs() { - if n as usize > array.len() { - array.insert(0, item); - } else { - array.insert(array.len() - n as usize, item); - } - } else { - array.insert(0, item); - } - } else if (position as usize) >= array.len() { + return; + } + + let (index, _) = calc_offset_len(array.len(), index, 0); + + if index >= array.len() { array.push(item); } else { - array.insert(position as usize, item); + array.insert(index, item); } } #[rhai_fn(return_raw)] @@ -137,11 +133,11 @@ pub mod array_functions { array.remove(0) } } - pub fn remove(array: &mut Array, len: INT) -> Dynamic { - if len < 0 || (len as usize) >= array.len() { + pub fn remove(array: &mut Array, index: INT) -> Dynamic { + if index < 0 || (index as usize) >= array.len() { Dynamic::UNIT } else { - array.remove(len as usize) + array.remove(index as usize) } } pub fn clear(array: &mut Array) { @@ -190,27 +186,13 @@ pub mod array_functions { return; } - let start = if start < 0 { - let arr_len = array.len(); - start - .checked_abs() - .map_or(0, |n| arr_len - usize::min(n as usize, arr_len)) - } else if start as usize >= array.len() { + let (start, len) = calc_offset_len(array.len(), start, len); + + if start >= array.len() { array.extend(replace); - return; } else { - start as usize - }; - - let len = if len < 0 { - 0 - } else if len as usize > array.len() - start { - array.len() - start - } else { - len as usize - }; - - array.splice(start..start + len, replace.into_iter()); + array.splice(start..start + len, replace); + } } #[rhai_fn(name = "extract")] pub fn extract_range(array: &mut Array, range: ExclusiveRange) -> Array { @@ -229,24 +211,7 @@ pub mod array_functions { return Array::new(); } - let start = if start < 0 { - let arr_len = array.len(); - start - .checked_abs() - .map_or(0, |n| arr_len - usize::min(n as usize, arr_len)) - } else if start as usize >= array.len() { - return Array::new(); - } else { - start as usize - }; - - let len = if len <= 0 { - 0 - } else if len as usize > array.len() - start { - array.len() - start - } else { - len as usize - }; + let (start, len) = calc_offset_len(array.len(), start, len); if len == 0 { Array::new() @@ -261,20 +226,20 @@ pub mod array_functions { #[rhai_fn(name = "split")] pub fn split_at(array: &mut Array, start: INT) -> Array { if array.is_empty() { - Array::new() - } else if start < 0 { - if let Some(n) = start.checked_abs() { - if n as usize > array.len() { - mem::take(array) - } else { - let mut result = Array::new(); - result.extend(array.drain(array.len() - n as usize..)); - result - } - } else { + return Array::new(); + } + + let (start, len) = calc_offset_len(array.len(), start, INT::MAX); + + if start == 0 { + if len >= array.len() { mem::take(array) + } else { + let mut result = Array::new(); + result.extend(array.drain(array.len() - len..)); + result } - } else if start as usize >= array.len() { + } else if start >= array.len() { Array::new() } else { let mut result = Array::new(); @@ -424,16 +389,7 @@ pub mod array_functions { return Ok(-1); } - let start = if start < 0 { - let arr_len = array.len(); - start - .checked_abs() - .map_or(0, |n| arr_len - usize::min(n as usize, arr_len)) - } else if start as usize >= array.len() { - return Ok(-1); - } else { - start as usize - }; + let (start, _) = calc_offset_len(array.len(), start, 0); for (i, item) in array.iter_mut().enumerate().skip(start) { if ctx @@ -489,16 +445,7 @@ pub mod array_functions { return Ok(-1); } - let start = if start < 0 { - let arr_len = array.len(); - start - .checked_abs() - .map_or(0, |n| arr_len - usize::min(n as usize, arr_len)) - } else if start as usize >= array.len() { - return Ok(-1); - } else { - start as usize - }; + let (start, _) = calc_offset_len(array.len(), start, 0); for (i, item) in array.iter().enumerate().skip(start) { if filter @@ -943,26 +890,13 @@ pub mod array_functions { return Array::new(); } - let start = if start < 0 { - let arr_len = array.len(); - start - .checked_abs() - .map_or(0, |n| arr_len - usize::min(n as usize, arr_len)) - } else if start as usize >= array.len() { - return Array::new(); - } else { - start as usize - }; + let (start, len) = calc_offset_len(array.len(), start, len); - let len = if len <= 0 { - 0 - } else if len as usize > array.len() - start { - array.len() - start + if len == 0 { + Array::new() } else { - len as usize - }; - - array.drain(start..start + len).collect() + array.drain(start..start + len).collect() + } } #[rhai_fn(return_raw)] pub fn retain(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf { @@ -1033,29 +967,16 @@ pub mod array_functions { return Array::new(); } - let start = if start < 0 { - let arr_len = array.len(); - start - .checked_abs() - .map_or(0, |n| arr_len - usize::min(n as usize, arr_len)) - } else if start as usize >= array.len() { - return mem::take(array); + let (start, len) = calc_offset_len(array.len(), start, len); + + if len == 0 { + Array::new() } else { - start as usize - }; + let mut drained: Array = array.drain(..start).collect(); + drained.extend(array.drain(len..)); - let len = if len < 0 { - 0 - } else if len as usize > array.len() - start { - array.len() - start - } else { - len as usize - }; - - let mut drained: Array = array.drain(..start).collect(); - drained.extend(array.drain(len..)); - - drained + drained + } } #[rhai_fn(name = "==", return_raw, pure)] pub fn equals(ctx: NativeCallContext, array1: &mut Array, array2: Array) -> RhaiResultOf { diff --git a/src/packages/bit_field.rs b/src/packages/bit_field.rs index 57155f68..92f9c6c6 100644 --- a/src/packages/bit_field.rs +++ b/src/packages/bit_field.rs @@ -1,7 +1,8 @@ #![allow(non_snake_case)] +use crate::eval::calc_index; use crate::plugin::*; -use crate::{def_package, ExclusiveRange, InclusiveRange, Position, RhaiResultOf, ERR, INT}; +use crate::{def_package, ExclusiveRange, InclusiveRange, Position, RhaiResultOf, ERR, INT, UINT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -19,62 +20,27 @@ mod bit_field_functions { const BITS: usize = std::mem::size_of::() * 8; #[rhai_fn(return_raw)] - pub fn get_bit(value: INT, index: INT) -> RhaiResultOf { - if index >= 0 { - let offset = index as usize; + pub fn get_bit(value: INT, bit: INT) -> RhaiResultOf { + let bit = calc_index(BITS, bit, true, || { + ERR::ErrorBitFieldBounds(BITS, bit, Position::NONE) + })?; - if offset >= BITS { - Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()) - } else { - Ok((value & (1 << offset)) != 0) - } - } else if let Some(abs_index) = index.checked_abs() { - let offset = abs_index as usize; - - // Count from end if negative - if offset > BITS { - Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()) - } else { - Ok((value & (1 << (BITS - offset))) != 0) - } - } else { - Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()) - } + Ok((value & (1 << bit)) != 0) } #[rhai_fn(return_raw)] - pub fn set_bit(value: &mut INT, index: INT, new_value: bool) -> RhaiResultOf<()> { - if index >= 0 { - let offset = index as usize; + pub fn set_bit(value: &mut INT, bit: INT, new_value: bool) -> RhaiResultOf<()> { + let bit = calc_index(BITS, bit, true, || { + ERR::ErrorBitFieldBounds(BITS, bit, Position::NONE) + })?; - if offset >= BITS { - Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()) - } else { - let mask = 1 << offset; - if new_value { - *value |= mask; - } else { - *value &= !mask; - } - Ok(()) - } - } else if let Some(abs_index) = index.checked_abs() { - let offset = abs_index as usize; - - // Count from end if negative - if offset > BITS { - Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()) - } else { - let mask = 1 << offset; - if new_value { - *value |= mask; - } else { - *value &= !mask; - } - Ok(()) - } + let mask = 1 << bit; + if new_value { + *value |= mask; } else { - Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()) + *value &= !mask; } + + Ok(()) } #[rhai_fn(name = "get_bits", return_raw)] pub fn get_bits_range(value: INT, range: ExclusiveRange) -> RhaiResultOf { @@ -89,46 +55,29 @@ mod bit_field_functions { get_bits(value, from, to - from + 1) } #[rhai_fn(return_raw)] - pub fn get_bits(value: INT, index: INT, bits: INT) -> RhaiResultOf { - if bits < 1 { + pub fn get_bits(value: INT, bit: INT, bits: INT) -> RhaiResultOf { + if bits <= 0 { return Ok(0); } - let offset = if index >= 0 { - let offset = index as usize; + let bit = calc_index(BITS, bit, true, || { + ERR::ErrorBitFieldBounds(BITS, bit, Position::NONE) + })?; - if offset >= BITS { - return Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()); - } - - offset - } else if let Some(abs_index) = index.checked_abs() { - let offset = abs_index as usize; - - // Count from end if negative - if offset > BITS { - return Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()); - } - BITS - offset - } else { - return Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()); - }; - - let bits = if offset + bits as usize > BITS { - BITS - offset + let bits = if bit + bits as usize > BITS { + BITS - bit } else { bits as usize }; - let mut base = 1; - let mut mask = 0; - - for _ in 0..bits { - mask |= base; - base <<= 1; + if bit == 0 && bits == BITS { + return Ok(value); } - Ok(((value & (mask << index)) >> index) & mask) + // 2^bits - 1 + let mask = ((2 as UINT).pow(bits as u32) - 1) as crate::INT; + + Ok(((value & (mask << bit)) >> bit) & mask) } #[rhai_fn(name = "set_bits", return_raw)] pub fn set_bits_range( @@ -151,47 +100,31 @@ mod bit_field_functions { set_bits(value, from, to - from + 1, new_value) } #[rhai_fn(return_raw)] - pub fn set_bits(value: &mut INT, index: INT, bits: INT, new_value: INT) -> RhaiResultOf<()> { - if bits < 1 { + pub fn set_bits(value: &mut INT, bit: INT, bits: INT, new_value: INT) -> RhaiResultOf<()> { + if bits <= 0 { return Ok(()); } - let offset = if index >= 0 { - let offset = index as usize; + let bit = calc_index(BITS, bit, true, || { + ERR::ErrorBitFieldBounds(BITS, bit, Position::NONE) + })?; - if offset >= BITS { - return Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()); - } - - offset - } else if let Some(abs_index) = index.checked_abs() { - let offset = abs_index as usize; - - // Count from end if negative - if offset > BITS { - return Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()); - } - BITS - offset - } else { - return Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()); - }; - - let bits = if offset + bits as usize > BITS { - BITS - offset + let bits = if bit + bits as usize > BITS { + BITS - bit } else { bits as usize }; - let mut base = 1; - let mut mask = 0; - - for _ in 0..bits { - mask |= base; - base <<= 1; + if bit == 0 && bits == BITS { + *value = new_value; + return Ok(()); } - *value &= !(mask << index); - *value |= (new_value & mask) << index; + // 2^bits - 1 + let mask = ((2 as UINT).pow(bits as u32) - 1) as crate::INT; + + *value &= !(mask << bit); + *value |= (new_value & mask) << bit; Ok(()) } diff --git a/src/packages/blob_basic.rs b/src/packages/blob_basic.rs index 06e8ada8..92c42c4a 100644 --- a/src/packages/blob_basic.rs +++ b/src/packages/blob_basic.rs @@ -1,6 +1,7 @@ #![cfg(not(feature = "no_index"))] #![allow(non_snake_case)] +use crate::eval::calc_offset_len; use crate::plugin::*; use crate::{ def_package, Blob, Dynamic, ExclusiveRange, InclusiveRange, NativeCallContext, Position, @@ -86,25 +87,20 @@ pub mod blob_functions { blob1 } } - pub fn insert(blob: &mut Blob, position: INT, item: INT) { + pub fn insert(blob: &mut Blob, index: INT, item: INT) { let item = (item & 0x000000ff) as u8; if blob.is_empty() { blob.push(item); - } else if position < 0 { - if let Some(n) = position.checked_abs() { - if n as usize > blob.len() { - blob.insert(0, item); - } else { - blob.insert(blob.len() - n as usize, item); - } - } else { - blob.insert(0, item); - } - } else if (position as usize) >= blob.len() { + return; + } + + let (index, _) = calc_offset_len(blob.len(), index, 0); + + if index >= blob.len() { blob.push(item); } else { - blob.insert(position as usize, item); + blob.insert(index, item); } } #[rhai_fn(return_raw)] @@ -196,28 +192,14 @@ pub mod blob_functions { *blob = replace; return; } - let blob_len = blob.len(); - let start = if start < 0 { - start - .checked_abs() - .map_or(0, |n| blob_len - usize::min(n as usize, blob_len)) - } else if start as usize >= blob_len { + let (start, len) = calc_offset_len(blob.len(), start, len); + + if len == 0 { blob.extend(replace); - return; } else { - start as usize - }; - - let len = if len < 0 { - 0 - } else if len as usize > blob_len - start { - blob_len - start - } else { - len as usize - }; - - blob.splice(start..start + len, replace.into_iter()); + blob.splice(start..start + len, replace); + } } #[rhai_fn(name = "extract")] pub fn extract_range(blob: &mut Blob, range: ExclusiveRange) -> Blob { @@ -235,25 +217,14 @@ pub mod blob_functions { if blob.is_empty() || len <= 0 { return Blob::new(); } - let blob_len = blob.len(); - let start = if start < 0 { - start - .checked_abs() - .map_or(0, |n| blob_len - usize::min(n as usize, blob_len)) - } else if start as usize >= blob_len { - return Blob::new(); + let (start, len) = calc_offset_len(blob.len(), start, len); + + if len == 0 { + Blob::new() } else { - start as usize - }; - - let len = if len as usize > blob_len - start { - blob_len - start - } else { - len as usize - }; - - blob[start..start + len].to_vec() + blob[start..start + len].to_vec() + } } #[rhai_fn(name = "extract")] pub fn extract_tail(blob: &mut Blob, start: INT) -> Blob { @@ -262,20 +233,20 @@ pub mod blob_functions { #[rhai_fn(name = "split")] pub fn split_at(blob: &mut Blob, start: INT) -> Blob { if blob.is_empty() { - Blob::new() - } else if start < 0 { - if let Some(n) = start.checked_abs() { - if n as usize > blob.len() { - mem::take(blob) - } else { - let mut result = Blob::new(); - result.extend(blob.drain(blob.len() - n as usize..)); - result - } - } else { + return Blob::new(); + } + + let (start, len) = calc_offset_len(blob.len(), start, INT::MAX); + + if start == 0 { + if len > blob.len() { mem::take(blob) + } else { + let mut result = Blob::new(); + result.extend(blob.drain(blob.len() - len..)); + result } - } else if start as usize >= blob.len() { + } else if start >= blob.len() { Blob::new() } else { let mut result = Blob::new(); @@ -299,25 +270,14 @@ pub mod blob_functions { if blob.is_empty() || len <= 0 { return Blob::new(); } - let blob_len = blob.len(); - let start = if start < 0 { - start - .checked_abs() - .map_or(0, |n| blob_len - usize::min(n as usize, blob_len)) - } else if start as usize >= blob_len { - return Blob::new(); + let (start, len) = calc_offset_len(blob.len(), start, len); + + if len == 0 { + Blob::new() } else { - start as usize - }; - - let len = if len as usize > blob_len - start { - blob_len - start - } else { - len as usize - }; - - blob.drain(start..start + len).collect() + blob.drain(start..start + len).collect() + } } #[rhai_fn(name = "retain")] pub fn retain_range(blob: &mut Blob, range: ExclusiveRange) -> Blob { @@ -335,28 +295,17 @@ pub mod blob_functions { if blob.is_empty() || len <= 0 { return Blob::new(); } - let blob_len = blob.len(); - let start = if start < 0 { - start - .checked_abs() - .map_or(0, |n| blob_len - usize::min(n as usize, blob_len)) - } else if start as usize >= blob_len { - return mem::take(blob); + let (start, len) = calc_offset_len(blob.len(), start, len); + + if len == 0 { + mem::take(blob) } else { - start as usize - }; + let mut drained: Blob = blob.drain(..start).collect(); + drained.extend(blob.drain(len..)); - let len = if len as usize > blob_len - start { - blob_len - start - } else { - len as usize - }; - - let mut drained: Blob = blob.drain(..start).collect(); - drained.extend(blob.drain(len..)); - - drained + drained + } } #[inline] @@ -364,23 +313,11 @@ pub mod blob_functions { if blob.is_empty() || len <= 0 { return 0; } - let blob_len = blob.len(); + let (start, len) = calc_offset_len(blob.len(), start, len); - let start = if start < 0 { - start - .checked_abs() - .map_or(0, |n| blob_len - usize::min(n as usize, blob_len)) - } else if start as usize >= blob_len { + if len == 0 { return 0; - } else { - start as usize - }; - - let len = if len as usize > blob_len - start { - blob_len - start - } else { - len as usize - }; + } const INT_BYTES: usize = mem::size_of::(); @@ -433,23 +370,12 @@ pub mod blob_functions { if blob.is_empty() || len <= 0 { return; } - let blob_len = blob.len(); - let start = if start < 0 { - start - .checked_abs() - .map_or(0, |n| blob_len - usize::min(n as usize, blob_len)) - } else if start as usize >= blob_len { + let (start, len) = calc_offset_len(blob.len(), start, len); + + if len == 0 { return; - } else { - start as usize - }; - - let len = if len as usize > blob_len - start { - blob_len - start - } else { - len as usize - }; + } const INT_BYTES: usize = mem::size_of::(); @@ -502,23 +428,12 @@ pub mod blob_functions { if blob.is_empty() || len <= 0 { return 0.0; } - let blob_len = blob.len(); - let start = if start < 0 { - start - .checked_abs() - .map_or(0, |n| blob_len - usize::min(n as usize, blob_len)) - } else if start as usize >= blob_len { + let (start, len) = calc_offset_len(blob.len(), start, len); + + if len == 0 { return 0.0; - } else { - start as usize - }; - - let len = if len as usize > blob_len - start { - blob_len - start - } else { - len as usize - }; + } const FLOAT_BYTES: usize = mem::size_of::(); @@ -578,23 +493,12 @@ pub mod blob_functions { if blob.is_empty() || len <= 0 { return; } - let blob_len = blob.len(); - let start = if start < 0 { - start - .checked_abs() - .map_or(0, |n| blob_len - usize::min(n as usize, blob_len)) - } else if start as usize >= blob_len { + let (start, len) = calc_offset_len(blob.len(), start, len); + + if len == 0 { return; - } else { - start as usize - }; - - let len = if len as usize > blob_len - start { - blob_len - start - } else { - len as usize - }; + } const FLOAT_BYTES: usize = mem::size_of::(); @@ -650,23 +554,12 @@ pub mod blob_functions { if len <= 0 || blob.is_empty() || string.is_empty() { return; } - let blob_len = blob.len(); - let start = if start < 0 { - start - .checked_abs() - .map_or(0, |n| blob_len - usize::min(n as usize, blob_len)) - } else if start as usize >= blob_len { + let (start, len) = calc_offset_len(blob.len(), start, len); + + if len == 0 { return; - } else { - start as usize - }; - - let len = if len as usize > blob_len - start { - blob_len - start - } else { - len as usize - }; + } let len = usize::min(len, string.len()); diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index eca73120..aae50762 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -1,3 +1,4 @@ +use crate::eval::calc_index; use crate::plugin::*; use crate::types::dynamic::Variant; use crate::{def_package, ExclusiveRange, InclusiveRange, RhaiResultOf, INT}; @@ -120,30 +121,9 @@ const BITS: usize = std::mem::size_of::() * 8; impl BitRange { pub fn new(value: INT, from: INT, len: INT) -> RhaiResultOf { - let from = if from >= 0 { - let offset = from as usize; - - #[cfg(not(feature = "unchecked"))] - if offset >= BITS { - return Err(crate::ERR::ErrorBitFieldBounds(BITS, from, Position::NONE).into()); - } - offset - } else { - #[cfg(not(feature = "unchecked"))] - if let Some(abs_from) = from.checked_abs() { - if (abs_from as usize) > BITS { - return Err(crate::ERR::ErrorBitFieldBounds(BITS, from, Position::NONE).into()); - } - BITS - (abs_from as usize) - } else { - return Err(crate::ERR::ErrorBitFieldBounds(BITS, from, Position::NONE).into()); - } - - #[cfg(feature = "unchecked")] - { - BITS - (from.abs() as usize) - } - }; + let from = calc_index(BITS, from, true, || { + crate::ERR::ErrorBitFieldBounds(BITS, from, Position::NONE) + })?; let len = if len < 0 { 0 diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 525ce5c0..4aaa3575 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -77,8 +77,9 @@ mod map_functions { for (m1, v1) in map1.iter_mut() { if let Some(v2) = map2.get_mut(m1) { let equals = ctx - .call_fn_raw(OP_EQUALS, true, false, &mut [v1, v2]) - .map(|v| v.as_bool().unwrap_or(false))?; + .call_fn_raw(OP_EQUALS, true, false, &mut [v1, v2])? + .as_bool() + .unwrap_or(false); if !equals { return Ok(false); diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 83f2efac..594ddb6b 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -1,7 +1,7 @@ #![allow(non_snake_case)] use crate::plugin::*; -use crate::{def_package, Position, RhaiResultOf, ERR, INT, UNSIGNED_INT}; +use crate::{def_package, Position, RhaiResultOf, ERR, INT, UINT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -118,7 +118,7 @@ mod int_functions { .into()); } - UNSIGNED_INT::from_str_radix(string.trim(), radix as u32) + UINT::from_str_radix(string.trim(), radix as u32) .map(|v| v as INT) .map_err(|err| { ERR::ErrorArithmetic( diff --git a/src/tokenizer.rs b/src/tokenizer.rs index df7508da..2d925d5e 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -5,7 +5,7 @@ use crate::engine::{ KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, }; use crate::func::native::OnParseTokenCallback; -use crate::{Engine, LexError, StaticVec, INT, UNSIGNED_INT}; +use crate::{Engine, LexError, StaticVec, INT, UINT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ @@ -1530,7 +1530,7 @@ fn get_next_token_inner( .filter(|&&c| c != NUMBER_SEPARATOR) .collect(); - UNSIGNED_INT::from_str_radix(&out, radix) + UINT::from_str_radix(&out, radix) .map(|v| v as INT) .map(Token::IntegerConstant) .unwrap_or_else(|_| { diff --git a/tests/arrays.rs b/tests/arrays.rs index ce1a0028..096be4de 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -28,6 +28,30 @@ fn test_arrays() -> Result<(), Box> { .into_typed_array::()?, [1, 6, 3] ); + assert_eq!( + engine + .eval::("let y = [1, 2, 3]; extract(y, 1, 10)")? + .into_typed_array::()?, + vec![2, 3] + ); + assert_eq!( + engine + .eval::("let y = [1, 2, 3]; extract(y, -3, 1)")? + .into_typed_array::()?, + vec![1] + ); + assert_eq!( + engine + .eval::("let y = [1, 2, 3]; extract(y, -99, 2)")? + .into_typed_array::()?, + vec![1, 2] + ); + assert_eq!( + engine + .eval::("let y = [1, 2, 3]; extract(y, 99, 1)")? + .into_typed_array::()?, + vec![] as Vec + ); #[cfg(not(feature = "no_object"))] { diff --git a/tests/bit_shift.rs b/tests/bit_fields.rs similarity index 76% rename from tests/bit_shift.rs rename to tests/bit_fields.rs index b077c5a6..d275622b 100644 --- a/tests/bit_shift.rs +++ b/tests/bit_fields.rs @@ -27,10 +27,24 @@ fn test_bit_fields() -> Result<(), Box> { ); assert_eq!(engine.eval::("let x = 10; get_bits(x, 1, 3)")?, 5); assert_eq!(engine.eval::("let x = 10; x[1..=3]")?, 5); + assert!(engine.eval::("let x = 10; x[1..99]").is_err()); + assert!(engine.eval::("let x = 10; x[-1..3]").is_err()); assert_eq!( engine.eval::("let x = 10; set_bits(x, 1, 3, 7); x")?, 14 ); + #[cfg(target_pointer_width = "64")] + { + assert_eq!(engine.eval::("let x = 255; get_bits(x, -60, 2)")?, 3); + assert_eq!( + engine.eval::("let x = 0; set_bits(x, -64, 1, 15); x")?, + 1 + ); + assert_eq!( + engine.eval::("let x = 0; set_bits(x, -60, 2, 15); x")?, + 0b00110000 + ); + } assert_eq!(engine.eval::("let x = 10; x[1..4] = 7; x")?, 14); assert_eq!( engine.eval::( From a3a527923a318111baa23959131bf95ea366ea1a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 13 Jan 2022 18:13:38 +0800 Subject: [PATCH 06/27] Fix metadata param name in JSON. --- CHANGELOG.md | 1 + src/serde/metadata.rs | 36 +++++++----------------------------- 2 files changed, 8 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1340b2e6..520cf6d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Bug fixes --------- * `set_bit` for bit-flags with negative index now works correctly. +* Misnamed `params` field `name` in the JSON output of `Engine::gen_fn_metadata_to_json` is fixed (was incorrectly named `type`). Version 1.4.0 diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index ce5f3f3d..ae1584a8 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -2,7 +2,7 @@ #![cfg(feature = "metadata")] -use crate::module::calc_native_fn_hash; +use crate::module::{calc_native_fn_hash, FuncInfo}; use crate::{calc_fn_hash, Engine, AST}; use serde::{Deserialize, Serialize}; #[cfg(feature = "no_std")] @@ -48,34 +48,15 @@ impl From for FnAccess { } } -#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct FnParam<'a> { - #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] pub name: Option<&'a str>, #[serde(rename = "type", skip_serializing_if = "Option::is_none")] pub typ: Option<&'a str>, } -impl PartialOrd for FnParam<'_> { - fn partial_cmp(&self, other: &Self) -> Option { - Some(match self.name.partial_cmp(&other.name).expect("succeed") { - Ordering::Less => Ordering::Less, - Ordering::Greater => Ordering::Greater, - Ordering::Equal => self.typ.partial_cmp(&other.typ).expect("succeed"), - }) - } -} - -impl Ord for FnParam<'_> { - fn cmp(&self, other: &Self) -> Ordering { - match self.name.cmp(&other.name) { - Ordering::Equal => self.typ.cmp(&other.typ), - cmp => cmp, - } - } -} - #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct FnMetadata<'a> { @@ -105,17 +86,14 @@ impl PartialOrd for FnMetadata<'_> { impl Ord for FnMetadata<'_> { fn cmp(&self, other: &Self) -> Ordering { match self.name.cmp(&other.name) { - Ordering::Equal => match self.num_params.cmp(&other.num_params) { - Ordering::Equal => self.params.cmp(&other.params), - cmp => cmp, - }, + Ordering::Equal => self.num_params.cmp(&other.num_params), cmp => cmp, } } } -impl<'a> From<&'a crate::module::FuncInfo> for FnMetadata<'a> { - fn from(info: &'a crate::module::FuncInfo) -> Self { +impl<'a> From<&'a FuncInfo> for FnMetadata<'a> { + fn from(info: &'a FuncInfo) -> Self { let base_hash = calc_fn_hash(&info.name, info.params); let (typ, full_hash) = if info.func.is_script() { (FnType::Script, base_hash) @@ -139,7 +117,7 @@ impl<'a> From<&'a crate::module::FuncInfo> for FnMetadata<'a> { .iter() .map(|s| { let mut seg = s.splitn(2, ':'); - let name = match seg.next().map(&str::trim).unwrap_or("_") { + let name = match seg.next().unwrap().trim() { "_" => None, s => Some(s), }; From 5dae2b07f72d1ee8bfe4945bc7b1740aab5c580c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 13 Jan 2022 19:02:37 +0800 Subject: [PATCH 07/27] Add to_dynamic test for Option. --- tests/serde.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/serde.rs b/tests/serde.rs index 5e20f04a..08bbeccc 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -751,7 +751,7 @@ fn test_serde_json() -> serde_json::Result<()> { #[test] #[cfg(not(feature = "no_object"))] fn test_serde_optional() -> Result<(), Box> { - #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] struct TestStruct { foo: Option, } @@ -774,6 +774,24 @@ fn test_serde_optional() -> Result<(), Box> { assert_eq!(from_dynamic::(&r)?, TestStruct { foo: None }); + let ts = TestStruct { foo: Some('a') }; + + let r = to_dynamic(&ts)?; + + let map = r.cast::(); + + assert_eq!(map.len(), 1); + assert_eq!(map.get("foo").unwrap().as_char().unwrap(), 'a'); + + let ts = TestStruct { foo: None }; + + let r = to_dynamic(&ts)?; + + let map = r.cast::(); + + assert_eq!(map.len(), 1); + assert_eq!(map.get("foo").unwrap().as_unit().unwrap(), ()); + Ok(()) } From 09aa9fc3dbea71d144f5699ffe082e5d49493083 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 13 Jan 2022 19:07:56 +0800 Subject: [PATCH 08/27] Extract metadata into separate type. --- src/module/mod.rs | 176 ++++++++++++++++++++++++++---------------- src/serde/metadata.rs | 24 +++--- 2 files changed, 123 insertions(+), 77 deletions(-) diff --git a/src/module/mod.rs b/src/module/mod.rs index b70bf3da..5ca45b60 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -15,6 +15,7 @@ use crate::{ use std::prelude::v1::*; use std::{ any::TypeId, + cmp::Ordering, collections::{BTreeMap, BTreeSet}, fmt, iter::{empty, once}, @@ -31,11 +32,9 @@ pub enum FnNamespace { Internal, } -/// Data structure containing a single registered function. -#[derive(Debug, Clone)] -pub struct FuncInfo { - /// Function instance. - pub func: Shared, +/// A type containing all metadata for a registered function. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct FnMetadata { /// Function namespace. pub namespace: FnNamespace, /// Function access mode. @@ -44,37 +43,65 @@ pub struct FuncInfo { pub name: Identifier, /// Number of parameters. pub params: usize, - /// Parameter types (if applicable). - pub param_types: StaticVec, /// Parameter names and types (if available). #[cfg(feature = "metadata")] - pub param_names_and_types: StaticVec, + pub params_info: StaticVec, /// Return type name. #[cfg(feature = "metadata")] - pub return_type_name: Identifier, + pub return_type: Identifier, /// Comments. #[cfg(feature = "metadata")] pub comments: Option]>>, } +impl PartialOrd for FnMetadata { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for FnMetadata { + fn cmp(&self, other: &Self) -> Ordering { + match self.name.cmp(&other.name) { + Ordering::Equal => match self.params.cmp(&other.params) { + Ordering::Equal => self.params_info.cmp(&other.params_info), + cmp => cmp, + }, + cmp => cmp, + } + } +} + +/// A type containing a single registered function. +#[derive(Debug, Clone)] +pub struct FuncInfo { + /// Function instance. + pub func: Shared, + /// Parameter types (if applicable). + pub param_types: StaticVec, + /// Function metadata. + pub metadata: FnMetadata, +} + impl FuncInfo { /// Generate a signature of the function. /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] #[must_use] pub fn gen_signature(&self) -> String { - let mut sig = format!("{}(", self.name); + let mut sig = format!("{}(", self.metadata.name); - if !self.param_names_and_types.is_empty() { + if !self.metadata.params_info.is_empty() { let params: StaticVec<_> = self - .param_names_and_types + .metadata + .params_info .iter() .map(|s| s.as_str()) .collect(); sig.push_str(¶ms.join(", ")); sig.push_str(")"); - match self.return_type_name.as_str() { + match self.metadata.return_type.as_str() { "" | "()" => (), ty => { sig.push_str(" -> "); @@ -82,9 +109,9 @@ impl FuncInfo { } } } else { - for x in 0..self.params { + for x in 0..self.metadata.params { sig.push('_'); - if x < self.params - 1 { + if x < self.metadata.params - 1 { sig.push_str(", "); } } @@ -94,7 +121,7 @@ impl FuncInfo { } else { sig.push_str(")"); - match self.return_type_name.as_str() { + match self.metadata.return_type.as_str() { "()" => (), _ => sig.push_str(" -> ?"), } @@ -372,7 +399,7 @@ impl Module { #[inline] pub fn gen_fn_signatures(&self) -> impl Iterator + '_ { self.iter_fn() - .filter(|&f| match f.access { + .filter(|&f| match f.metadata.access { FnAccess::Public => true, FnAccess::Private => false, }) @@ -478,22 +505,24 @@ impl Module { let num_params = fn_def.params.len(); let hash_script = crate::calc_fn_hash(&fn_def.name, num_params); #[cfg(feature = "metadata")] - let param_names_and_types = fn_def.params.iter().cloned().collect(); + let params_info = fn_def.params.iter().cloned().collect(); self.functions.insert( hash_script, FuncInfo { - name: fn_def.name.clone(), - namespace: FnNamespace::Internal, - access: fn_def.access, - params: num_params, - param_types: StaticVec::new_const(), - #[cfg(feature = "metadata")] - param_names_and_types, - #[cfg(feature = "metadata")] - return_type_name: "Dynamic".into(), - #[cfg(feature = "metadata")] - comments: None, + metadata: FnMetadata { + name: fn_def.name.clone(), + namespace: FnNamespace::Internal, + access: fn_def.access, + params: num_params, + #[cfg(feature = "metadata")] + params_info, + #[cfg(feature = "metadata")] + return_type: "Dynamic".into(), + #[cfg(feature = "metadata")] + comments: None, + }, func: Into::::into(fn_def).into(), + param_types: StaticVec::new_const(), } .into(), ); @@ -518,7 +547,7 @@ impl Module { let name = name.as_ref(); self.iter_fn() - .find(|f| f.params == num_params && f.name == name) + .find(|f| f.metadata.params == num_params && f.metadata.name == name) .and_then(|f| f.func.get_script_fn_def()) } } @@ -648,14 +677,14 @@ impl Module { .collect(); if let Some(f) = self.functions.get_mut(&hash_fn) { - let (param_names, return_type_name) = if param_names.len() > f.params { + let (param_names, return_type_name) = if param_names.len() > f.metadata.params { let return_type = param_names.pop().unwrap(); (param_names, return_type) } else { (param_names, Default::default()) }; - f.param_names_and_types = param_names; - f.return_type_name = return_type_name; + f.metadata.params_info = param_names; + f.metadata.return_type = return_type_name; } self @@ -698,7 +727,7 @@ impl Module { if !comments.is_empty() { let f = self.functions.get_mut(&hash_fn).unwrap(); - f.comments = Some(comments.iter().map(|s| s.as_ref().into()).collect()); + f.metadata.comments = Some(comments.iter().map(|s| s.as_ref().into()).collect()); } self @@ -710,7 +739,7 @@ impl Module { #[inline] pub fn update_fn_namespace(&mut self, hash_fn: u64, namespace: FnNamespace) -> &mut Self { if let Some(f) = self.functions.get_mut(&hash_fn) { - f.namespace = namespace; + f.metadata.namespace = namespace; self.indexed = false; self.contains_indexed_global_functions = false; } @@ -795,18 +824,20 @@ impl Module { self.functions.insert( hash_fn, FuncInfo { - name: name.as_ref().into(), - namespace, - access, - params: param_types.len(), - param_types, - #[cfg(feature = "metadata")] - param_names_and_types: param_names, - #[cfg(feature = "metadata")] - return_type_name, - #[cfg(feature = "metadata")] - comments: None, + metadata: FnMetadata { + name: name.as_ref().into(), + namespace, + access, + params: param_types.len(), + #[cfg(feature = "metadata")] + params_info: param_names, + #[cfg(feature = "metadata")] + return_type: return_type_name, + #[cfg(feature = "metadata")] + comments: None, + }, func: func.into(), + param_types, } .into(), ); @@ -861,7 +892,7 @@ impl Module { if !comments.is_empty() { let f = self.functions.get_mut(&hash).unwrap(); - f.comments = Some(comments.iter().map(|s| s.as_ref().into()).collect()); + f.metadata.comments = Some(comments.iter().map(|s| s.as_ref().into()).collect()); } hash @@ -1374,11 +1405,11 @@ impl Module { .iter() .filter(|&(_, f)| { _filter( - f.namespace, - f.access, + f.metadata.namespace, + f.metadata.access, f.func.is_script(), - f.name.as_str(), - f.params, + f.metadata.name.as_str(), + f.metadata.params, ) }) .map(|(&k, v)| (k, v.clone())), @@ -1404,7 +1435,12 @@ impl Module { .into_iter() .filter(|(_, f)| { if f.func.is_script() { - filter(f.namespace, f.access, f.name.as_str(), f.params) + filter( + f.metadata.namespace, + f.metadata.access, + f.metadata.name.as_str(), + f.metadata.params, + ) } else { false } @@ -1472,10 +1508,10 @@ impl Module { > + '_ { self.iter_fn().filter(|&f| f.func.is_script()).map(|f| { ( - f.namespace, - f.access, - f.name.as_str(), - f.params, + f.metadata.namespace, + f.metadata.access, + f.metadata.name.as_str(), + f.metadata.params, f.func.get_script_fn_def().expect("script-defined function"), ) }) @@ -1494,9 +1530,14 @@ impl Module { pub fn iter_script_fn_info( &self, ) -> impl Iterator { - self.iter_fn() - .filter(|&f| f.func.is_script()) - .map(|f| (f.namespace, f.access, f.name.as_str(), f.params)) + self.iter_fn().filter(|&f| f.func.is_script()).map(|f| { + ( + f.metadata.namespace, + f.metadata.access, + f.metadata.name.as_str(), + f.metadata.params, + ) + }) } /// _(internals)_ Get an iterator over all script-defined functions in the [`Module`]. @@ -1607,7 +1648,7 @@ impl Module { if ast.has_functions() { ast.shared_lib() .iter_fn() - .filter(|&f| match f.access { + .filter(|&f| match f.metadata.access { FnAccess::Public => true, FnAccess::Private => false, }) @@ -1682,7 +1723,7 @@ impl Module { // Index all Rust functions module.functions.iter().for_each(|(&hash, f)| { - match f.namespace { + match f.metadata.namespace { FnNamespace::Global => { // Flatten all functions with global namespace functions.insert(hash, f.func.clone()); @@ -1690,20 +1731,23 @@ impl Module { } FnNamespace::Internal => (), } - match f.access { + match f.metadata.access { FnAccess::Public => (), FnAccess::Private => return, // Do not index private functions } if !f.func.is_script() { - let hash_qualified_fn = - calc_native_fn_hash(path.iter().cloned(), f.name.as_str(), &f.param_types); + let hash_qualified_fn = calc_native_fn_hash( + path.iter().cloned(), + f.metadata.name.as_str(), + &f.param_types, + ); functions.insert(hash_qualified_fn, f.func.clone()); } else if cfg!(not(feature = "no_function")) { let hash_qualified_script = crate::calc_qualified_fn_hash( path.iter().cloned(), - f.name.as_str(), - f.params, + f.metadata.name.as_str(), + f.metadata.params, ); functions.insert(hash_qualified_script, f.func.clone()); } diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index ae1584a8..fd2f7df0 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -71,14 +71,14 @@ struct FnMetadata<'a> { #[serde(default, skip_serializing_if = "Vec::is_empty")] pub params: Vec>, #[serde(default, skip_serializing_if = "Option::is_none")] - pub return_type_name: Option<&'a str>, + pub return_type: Option<&'a str>, pub signature: String, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub doc_comments: Vec<&'a str>, } impl PartialOrd for FnMetadata<'_> { - fn partial_cmp(&self, other: &Self) -> Option { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } @@ -94,26 +94,27 @@ impl Ord for FnMetadata<'_> { impl<'a> From<&'a FuncInfo> for FnMetadata<'a> { fn from(info: &'a FuncInfo) -> Self { - let base_hash = calc_fn_hash(&info.name, info.params); + let base_hash = calc_fn_hash(&info.metadata.name, info.metadata.params); let (typ, full_hash) = if info.func.is_script() { (FnType::Script, base_hash) } else { ( FnType::Native, - calc_native_fn_hash(empty::<&str>(), &info.name, &info.param_types), + calc_native_fn_hash(empty::<&str>(), &info.metadata.name, &info.param_types), ) }; Self { base_hash, full_hash, - namespace: info.namespace.into(), - access: info.access.into(), - name: info.name.to_string(), + namespace: info.metadata.namespace.into(), + access: info.metadata.access.into(), + name: info.metadata.name.to_string(), typ, - num_params: info.params, + num_params: info.metadata.params, params: info - .param_names_and_types + .metadata + .params_info .iter() .map(|s| { let mut seg = s.splitn(2, ':'); @@ -125,7 +126,7 @@ impl<'a> From<&'a FuncInfo> for FnMetadata<'a> { FnParam { name, typ } }) .collect(), - return_type_name: match info.return_type_name.as_str() { + return_type: match info.metadata.return_type.as_str() { "" | "()" => None, ty => Some(ty), }, @@ -142,7 +143,8 @@ impl<'a> From<&'a FuncInfo> for FnMetadata<'a> { .as_ref() .map_or_else(|| Vec::new(), |v| v.iter().map(|s| &**s).collect()) } else { - info.comments + info.metadata + .comments .as_ref() .map_or_else(|| Vec::new(), |v| v.iter().map(|s| &**s).collect()) }, From 510c201d2f2d6747af0988694b83cc5ff7639857 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 13 Jan 2022 21:01:56 +0800 Subject: [PATCH 09/27] Fix build. --- src/module/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/module/mod.rs b/src/module/mod.rs index 5ca45b60..60dfb65b 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -63,10 +63,13 @@ impl PartialOrd for FnMetadata { impl Ord for FnMetadata { fn cmp(&self, other: &Self) -> Ordering { match self.name.cmp(&other.name) { + #[cfg(feature = "metadata")] Ordering::Equal => match self.params.cmp(&other.params) { Ordering::Equal => self.params_info.cmp(&other.params_info), cmp => cmp, }, + #[cfg(not(feature = "metadata"))] + Ordering::Equal => self.params.cmp(&other.params), cmp => cmp, } } From 5e32af0ceb826dc1d33ae43ad0fc2169b9e3a179 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 13 Jan 2022 21:08:36 +0800 Subject: [PATCH 10/27] Fix unchecked build. --- src/eval/target.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/eval/target.rs b/src/eval/target.rs index a2462212..e8efefb9 100644 --- a/src/eval/target.rs +++ b/src/eval/target.rs @@ -61,7 +61,7 @@ pub fn calc_index( } }); #[cfg(feature = "unchecked")] - return Ok(actual - (start.abs() as usize)); + return Ok(length - (start.abs() as usize)); } else { Err(err().into()) } From 5ab7c59ba05cbf0409e222fe923fb0935aa25f51 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 13 Jan 2022 22:05:07 +0800 Subject: [PATCH 11/27] Fix Array::pad infinite loop. --- CHANGELOG.md | 1 + src/packages/array_basic.rs | 28 ++++++++++++++++++++++------ tests/arrays.rs | 12 ++++++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 520cf6d5..b8374485 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Version 1.5.0 Bug fixes --------- +* Padding arrays with another array via `pad` no longer loops indefinitely. * `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`). diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 52b1e16c..857572ad 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -102,14 +102,30 @@ pub mod array_functions { let check_sizes = false; if check_sizes { - let arr = mem::take(array); - let mut arr = Dynamic::from_array(arr); + let mut arr_len = array.len(); + let mut arr = Dynamic::from_array(mem::take(array)); - while array.len() < len { - arr.write_lock::().unwrap().push(item.clone()); + #[cfg(not(feature = "unchecked"))] + let (mut a1, mut m1, mut s1) = Engine::calc_data_sizes(&arr, true); + #[cfg(not(feature = "unchecked"))] + let (a2, m2, s2) = Engine::calc_data_sizes(&item, true); - #[cfg(not(feature = "unchecked"))] - _ctx.engine().ensure_data_size_within_limits(&arr)?; + { + let mut guard = arr.write_lock::().unwrap(); + + while arr_len < len { + #[cfg(not(feature = "unchecked"))] + { + a1 += a2; + m1 += m2; + s1 += s2; + + _ctx.engine() + .raise_err_if_over_data_size_limit((a1, m1, s1), Position::NONE)?; + } + guard.push(item.clone()); + arr_len += 1; + } } *array = arr.into_array().unwrap(); diff --git a/tests/arrays.rs b/tests/arrays.rs index 096be4de..e0dc764d 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -22,6 +22,18 @@ fn test_arrays() -> Result<(), Box> { assert_eq!(engine.eval::("let y = [1, 2, 3]; y[-3]")?, 1); assert!(engine.eval::("let y = [1, 2, 3]; 2 in y")?); assert_eq!(engine.eval::("let y = [1, 2, 3]; y += 4; y[3]")?, 4); + assert_eq!( + engine.eval::("let y = [1, 2, 3]; pad(y, 5, 42); len(y)")?, + 5 + ); + assert_eq!( + engine.eval::("let y = [1, 2, 3]; pad(y, 5, [42]); len(y)")?, + 5 + ); + assert_eq!( + engine.eval::("let y = [1, 2, 3]; pad(y, 5, [42, 999, 123]); y[4][0]")?, + 42 + ); assert_eq!( engine .eval::("let y = [1, 2, 3]; y[1] += 4; y")? From 25f54c0ea5c81c5a30dd199acfd84af62d1b635d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 13 Jan 2022 22:51:10 +0800 Subject: [PATCH 12/27] Pretty-display return types. --- CHANGELOG.md | 5 +++++ src/module/mod.rs | 48 ++++++++++++++++++++++++++++++------------- src/serde/metadata.rs | 11 +++++----- 3 files changed, 45 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8374485..edf84e76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ Bug fixes * `set_bit` for bit-flags with negative index now works correctly. * Misnamed `params` field `name` in the JSON output of `Engine::gen_fn_metadata_to_json` is fixed (was incorrectly named `type`). +Enhancements +------------ + +* Formatting of return types in functions metadata info is improved. + Version 1.4.0 ============= diff --git a/src/module/mod.rs b/src/module/mod.rs index 60dfb65b..b21d93c2 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -87,6 +87,27 @@ pub struct FuncInfo { } impl FuncInfo { + /// Format a return type to be display-friendly. + /// + /// `()` is cleared. + /// [`RhaiResult`][crate::RhaiResult] and [`RhaiResultOf`] are expanded. + pub fn format_return_type(typ: &str) -> std::borrow::Cow { + const RHAI_RESULT_TYPE: &str = "RhaiResult"; + const RHAI_RESULT_TYPE_EXPAND: &str = "Result>"; + const RHAI_RESULT_OF_TYPE: &str = "RhaiResultOf<"; + const RHAI_RESULT_OF_TYPE_EXPAND: &str = "Result<{}, Box>"; + + match typ { + "" | "()" => "".into(), + RHAI_RESULT_TYPE => RHAI_RESULT_TYPE_EXPAND.into(), + ty if ty.starts_with(RHAI_RESULT_OF_TYPE) && ty.ends_with(">") => { + RHAI_RESULT_OF_TYPE_EXPAND + .replace("{}", ty[RHAI_RESULT_OF_TYPE.len()..ty.len() - 1].trim()) + .into() + } + ty => ty.into(), + } + } /// Generate a signature of the function. /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] @@ -94,6 +115,8 @@ impl FuncInfo { pub fn gen_signature(&self) -> String { let mut sig = format!("{}(", self.metadata.name); + let return_type = Self::format_return_type(&self.metadata.return_type); + if !self.metadata.params_info.is_empty() { let params: StaticVec<_> = self .metadata @@ -102,14 +125,11 @@ impl FuncInfo { .map(|s| s.as_str()) .collect(); sig.push_str(¶ms.join(", ")); - sig.push_str(")"); + sig.push(')'); - match self.metadata.return_type.as_str() { - "" | "()" => (), - ty => { - sig.push_str(" -> "); - sig.push_str(ty); - } + if !return_type.is_empty() { + sig.push_str(" -> "); + sig.push_str(&return_type); } } else { for x in 0..self.metadata.params { @@ -119,14 +139,14 @@ impl FuncInfo { } } - if self.func.is_script() { - sig.push(')'); - } else { - sig.push_str(")"); + sig.push(')'); - match self.metadata.return_type.as_str() { - "()" => (), - _ => sig.push_str(" -> ?"), + if !self.func.is_script() { + sig.push(')'); + + if !return_type.is_empty() { + sig.push_str(" -> "); + sig.push_str(&return_type); } } } diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index fd2f7df0..3ccadfb3 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -70,8 +70,11 @@ struct FnMetadata<'a> { pub num_params: usize, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub params: Vec>, + // No idea why the following is needed otherwise serde comes back with a lifetime error #[serde(default, skip_serializing_if = "Option::is_none")] - pub return_type: Option<&'a str>, + pub _dummy: Option<&'a str>, + #[serde(default, skip_serializing_if = "String::is_empty")] + pub return_type: String, pub signature: String, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub doc_comments: Vec<&'a str>, @@ -126,10 +129,8 @@ impl<'a> From<&'a FuncInfo> for FnMetadata<'a> { FnParam { name, typ } }) .collect(), - return_type: match info.metadata.return_type.as_str() { - "" | "()" => None, - ty => Some(ty), - }, + _dummy: None, + return_type: FuncInfo::format_return_type(&info.metadata.return_type).into_owned(), signature: info.gen_signature(), doc_comments: if info.func.is_script() { #[cfg(feature = "no_function")] From 96764c0d2d7dba97dfa130171a0bb43318762a00 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 13 Jan 2022 22:51:56 +0800 Subject: [PATCH 13/27] Fix UINT -> UNSIGNED_INT. --- src/eval/chaining.rs | 6 ++++-- src/lib.rs | 3 ++- src/packages/bit_field.rs | 8 +++++--- src/packages/math_basic.rs | 4 ++-- src/tokenizer.rs | 4 ++-- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index 2812a163..3a776af9 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -859,7 +859,8 @@ impl Engine { ( start as u8, // 2^bits - 1 - (((2 as crate::UINT).pow((end - start) as u32) - 1) as crate::INT) + (((2 as crate::UNSIGNED_INT).pow((end - start) as u32) - 1) + as crate::INT) << start, ) } @@ -883,7 +884,8 @@ impl Engine { ( start as u8, // 2^bits - 1 - (((2 as crate::UINT).pow((end - start + 1) as u32) - 1) as crate::INT) + (((2 as crate::UNSIGNED_INT).pow((end - start + 1) as u32) - 1) + as crate::INT) << start, ) } diff --git a/src/lib.rs b/src/lib.rs index 44cd7d70..df685645 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,7 +112,8 @@ pub type INT = i32; /// If the `only_i32` feature is enabled, this will be [`u32`] instead. #[cfg(not(feature = "only_i32"))] #[allow(non_camel_case_types)] -type UINT = u64; +type UNSIGNED_INT = u64; + /// The unsigned system integer base type. /// It is defined as [`u32`] since the `only_i32` feature is used. /// diff --git a/src/packages/bit_field.rs b/src/packages/bit_field.rs index 92f9c6c6..6d6ae617 100644 --- a/src/packages/bit_field.rs +++ b/src/packages/bit_field.rs @@ -2,7 +2,9 @@ use crate::eval::calc_index; use crate::plugin::*; -use crate::{def_package, ExclusiveRange, InclusiveRange, Position, RhaiResultOf, ERR, INT, UINT}; +use crate::{ + def_package, ExclusiveRange, InclusiveRange, Position, RhaiResultOf, ERR, INT, UNSIGNED_INT, +}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -75,7 +77,7 @@ mod bit_field_functions { } // 2^bits - 1 - let mask = ((2 as UINT).pow(bits as u32) - 1) as crate::INT; + let mask = ((2 as UNSIGNED_INT).pow(bits as u32) - 1) as crate::INT; Ok(((value & (mask << bit)) >> bit) & mask) } @@ -121,7 +123,7 @@ mod bit_field_functions { } // 2^bits - 1 - let mask = ((2 as UINT).pow(bits as u32) - 1) as crate::INT; + let mask = ((2 as UNSIGNED_INT).pow(bits as u32) - 1) as crate::INT; *value &= !(mask << bit); *value |= (new_value & mask) << bit; diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 594ddb6b..83f2efac 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -1,7 +1,7 @@ #![allow(non_snake_case)] use crate::plugin::*; -use crate::{def_package, Position, RhaiResultOf, ERR, INT, UINT}; +use crate::{def_package, Position, RhaiResultOf, ERR, INT, UNSIGNED_INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -118,7 +118,7 @@ mod int_functions { .into()); } - UINT::from_str_radix(string.trim(), radix as u32) + UNSIGNED_INT::from_str_radix(string.trim(), radix as u32) .map(|v| v as INT) .map_err(|err| { ERR::ErrorArithmetic( diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 2d925d5e..df7508da 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -5,7 +5,7 @@ use crate::engine::{ KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, }; use crate::func::native::OnParseTokenCallback; -use crate::{Engine, LexError, StaticVec, INT, UINT}; +use crate::{Engine, LexError, StaticVec, INT, UNSIGNED_INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ @@ -1530,7 +1530,7 @@ fn get_next_token_inner( .filter(|&&c| c != NUMBER_SEPARATOR) .collect(); - UINT::from_str_radix(&out, radix) + UNSIGNED_INT::from_str_radix(&out, radix) .map(|v| v as INT) .map(Token::IntegerConstant) .unwrap_or_else(|_| { From e2e0b8d083038e94bc9cd389f8cf0fed9fbafc54 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 14 Jan 2022 10:04:24 +0800 Subject: [PATCH 14/27] Fix Array::chop. --- CHANGELOG.md | 1 + src/packages/array_basic.rs | 8 ++++---- src/packages/blob_basic.rs | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edf84e76..4127dde4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Bug fixes --------- * Padding arrays with another array via `pad` no longer loops indefinitely. +* `chop` for arrays and BLOB's now works properly. * `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`). diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 857572ad..007a7fdc 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -171,11 +171,11 @@ pub mod array_functions { } } pub fn chop(array: &mut Array, len: INT) { - if !array.is_empty() && len as usize >= array.len() { - if len >= 0 { - array.drain(0..array.len() - len as usize); - } else { + if !array.is_empty() { + if len <= 0 { array.clear(); + } else if (len as usize) < array.len() { + array.drain(0..array.len() - len as usize); } } } diff --git a/src/packages/blob_basic.rs b/src/packages/blob_basic.rs index 92c42c4a..ff14fe3c 100644 --- a/src/packages/blob_basic.rs +++ b/src/packages/blob_basic.rs @@ -162,11 +162,11 @@ pub mod blob_functions { } } pub fn chop(blob: &mut Blob, len: INT) { - if !blob.is_empty() && len as usize >= blob.len() { - if len >= 0 { - blob.drain(0..blob.len() - len as usize); - } else { + if !blob.is_empty() { + if len <= 0 { blob.clear(); + } else if (len as usize) < blob.len() { + blob.drain(0..blob.len() - len as usize); } } } From ae77582028cd68541d29068d822494c9f562b6bd Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 14 Jan 2022 21:49:38 +0800 Subject: [PATCH 15/27] Minor refactor. --- src/eval/stmt.rs | 18 ++++++++---------- src/module/mod.rs | 1 + src/types/error.rs | 35 +++++++++++++++++++++++------------ src/types/fn_ptr.rs | 4 ++-- 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 9dbf5433..cb2fa390 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -322,11 +322,10 @@ impl Engine { if let Some(t) = table.get(&hash) { if let Some(ref c) = t.0 { if self - .eval_expr(scope, global, state, lib, this_ptr, &c, level) - .and_then(|v| { - v.as_bool().map_err(|typ| { - self.make_type_mismatch_err::(typ, c.position()) - }) + .eval_expr(scope, global, state, lib, this_ptr, &c, level)? + .as_bool() + .map_err(|typ| { + self.make_type_mismatch_err::(typ, c.position()) })? { Some(&t.1) @@ -349,11 +348,10 @@ impl Engine { { if let Some(c) = condition { if !self - .eval_expr(scope, global, state, lib, this_ptr, &c, level) - .and_then(|v| { - v.as_bool().map_err(|typ| { - self.make_type_mismatch_err::(typ, c.position()) - }) + .eval_expr(scope, global, state, lib, this_ptr, &c, level)? + .as_bool() + .map_err(|typ| { + self.make_type_mismatch_err::(typ, c.position()) })? { continue; diff --git a/src/module/mod.rs b/src/module/mod.rs index b21d93c2..dc0dcc76 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -91,6 +91,7 @@ impl FuncInfo { /// /// `()` is cleared. /// [`RhaiResult`][crate::RhaiResult] and [`RhaiResultOf`] are expanded. + #[cfg(feature = "metadata")] pub fn format_return_type(typ: &str) -> std::borrow::Cow { const RHAI_RESULT_TYPE: &str = "RhaiResult"; const RHAI_RESULT_TYPE_EXPAND: &str = "Result>"; diff --git a/src/types/error.rs b/src/types/error.rs index c07e0fca..da507a9b 100644 --- a/src/types/error.rs +++ b/src/types/error.rs @@ -1,6 +1,6 @@ //! Module containing error definitions for the evaluation process. -use crate::{Dynamic, ImmutableString, ParseErrorType, Position, RhaiError, INT}; +use crate::{Dynamic, ImmutableString, ParseErrorType, Position, INT}; #[cfg(feature = "no_std")] use core_error::Error; #[cfg(not(feature = "no_std"))] @@ -34,22 +34,28 @@ pub enum EvalAltResult { ErrorVariableNotFound(String, Position), /// Call to an unknown function. Wrapped value is the function signature. ErrorFunctionNotFound(String, Position), - /// An error has occurred inside a called function. - /// Wrapped values are the function name, function source, and the interior error. - ErrorInFunctionCall(String, String, RhaiError, Position), /// Usage of an unknown [module][crate::Module]. Wrapped value is the [module][crate::Module] name. ErrorModuleNotFound(String, Position), + + /// An error has occurred inside a called function. + /// Wrapped values are the function name, function source, and the interior error. + ErrorInFunctionCall(String, String, Box, Position), /// An error has occurred while loading a [module][crate::Module]. /// Wrapped value are the [module][crate::Module] name and the interior error. - ErrorInModule(String, RhaiError, Position), + ErrorInModule(String, Box, Position), + /// Access to `this` that is not bound. ErrorUnboundThis(Position), + /// Data is not of the required type. /// Wrapped values are the type requested and type of the actual result. ErrorMismatchDataType(String, String, Position), /// Returned type is not the same as the required output type. /// Wrapped values are the type requested and type of the actual result. ErrorMismatchOutputType(String, String, Position), + /// Trying to index into a type that has no indexer function defined. Wrapped value is the type name. + ErrorIndexingType(String, Position), + /// Array access out-of-bounds. /// Wrapped values are the current number of elements in the array and the index number. ErrorArrayBounds(usize, INT, Position), @@ -59,10 +65,10 @@ pub enum EvalAltResult { /// Bit-field indexing out-of-bounds. /// Wrapped values are the current number of bits in the bit-field and the index number. ErrorBitFieldBounds(usize, INT, Position), - /// Trying to index into a type that has no indexer function defined. Wrapped value is the type name. - ErrorIndexingType(String, Position), - /// The `for` statement encounters a type that is not an iterator. + + /// The `for` statement encounters a type that is not iterable. ErrorFor(Position), + /// Data race detected when accessing a variable. Wrapped value is the variable name. ErrorDataRace(String, Position), /// Assignment to a constant variable. Wrapped value is the variable name. @@ -145,7 +151,7 @@ impl fmt::Display for EvalAltResult { }?, Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered for {}", s)?, Self::ErrorUnboundThis(_) => f.write_str("'this' is not bound")?, - Self::ErrorFor(_) => f.write_str("For loop expects a type with an iterator defined")?, + Self::ErrorFor(_) => f.write_str("For loop expects a type that is iterable")?, Self::ErrorTooManyOperations(_) => f.write_str("Too many operations")?, Self::ErrorTooManyModules(_) => f.write_str("Too many modules imported")?, Self::ErrorStackOverflow(_) => f.write_str("Stack overflow")?, @@ -233,7 +239,7 @@ impl> From for EvalAltResult { } } -impl> From for RhaiError { +impl> From for Box { #[inline(never)] fn from(err: T) -> Self { EvalAltResult::ErrorRuntime(err.as_ref().to_string().into(), Position::NONE).into() @@ -277,8 +283,13 @@ impl EvalAltResult { | Self::ErrorArithmetic(_, _) | Self::ErrorRuntime(_, _) => true, - Self::ErrorCustomSyntax(_, _, _) - | Self::ErrorTooManyOperations(_) + // Custom syntax raises errors only when they are compiled by one + // [`Engine`][crate::Engine] and run by another, causing a mismatch. + // + // Therefore, this error should not be catchable. + Self::ErrorCustomSyntax(_, _, _) => false, + + Self::ErrorTooManyOperations(_) | Self::ErrorTooManyModules(_) | Self::ErrorStackOverflow(_) | Self::ErrorDataTooLarge(_, _) diff --git a/src/types/fn_ptr.rs b/src/types/fn_ptr.rs index 249e50a0..08712902 100644 --- a/src/types/fn_ptr.rs +++ b/src/types/fn_ptr.rs @@ -43,8 +43,8 @@ impl FnPtr { /// Create a new function pointer without checking its parameters. #[inline(always)] #[must_use] - pub(crate) fn new_unchecked(name: Identifier, curry: StaticVec) -> Self { - Self(name, curry) + pub(crate) fn new_unchecked(name: impl Into, curry: StaticVec) -> Self { + Self(name.into(), curry) } /// Get the name of the function. #[inline(always)] From 2677e7dc8a750eaab7b0e8f66085a8bcee7b77c3 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 14 Jan 2022 23:19:27 +0800 Subject: [PATCH 16/27] Fix test for only_i32. --- tests/bit_fields.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/bit_fields.rs b/tests/bit_fields.rs index d275622b..f2cffb32 100644 --- a/tests/bit_fields.rs +++ b/tests/bit_fields.rs @@ -34,6 +34,7 @@ fn test_bit_fields() -> Result<(), Box> { 14 ); #[cfg(target_pointer_width = "64")] + #[cfg(not(feature = "only_i32"))] { assert_eq!(engine.eval::("let x = 255; get_bits(x, -60, 2)")?, 3); assert_eq!( From a8ec984b0f12015c060f934f12b4b11b5405ec56 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 15 Jan 2022 10:18:16 +0800 Subject: [PATCH 17/27] Fix unsafe violation in for loop. --- CHANGELOG.md | 1 + src/eval/stmt.rs | 34 ++++++++++++++++++++++++++-------- src/func/script.rs | 10 +++++++--- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4127dde4..994b36c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Bug fixes * `chop` for arrays and BLOB's now works properly. * `set_bit` for bit-flags with negative index now works correctly. * Misnamed `params` field `name` in the JSON output of `Engine::gen_fn_metadata_to_json` is fixed (was incorrectly named `type`). +* Fixes a potential `unsafe` violation in `for` loop. Enhancements ------------ diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index cb2fa390..aff46fe6 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -464,7 +464,7 @@ impl Engine { // For loop Stmt::For(expr, x, _) => { - let (Ident { name, .. }, counter, statements) = x.as_ref(); + let (Ident { name: var_name, .. }, counter, statements) = x.as_ref(); let iter_obj = self .eval_expr(scope, global, state, lib, this_ptr, expr, level)? .flatten(); @@ -492,14 +492,23 @@ impl Engine { // Add the loop variables let orig_scope_len = scope.len(); let counter_index = if let Some(counter) = counter { - scope.push(unsafe_cast_var_name_to_lifetime(&counter.name), 0 as INT); + // Loop variables are always removed at the end of the statement + // so this cast is safe. + let counter_name = unsafe_cast_var_name_to_lifetime(&counter.name); + scope.push(counter_name, 0 as INT); scope.len() - 1 } else { usize::MAX }; - scope.push(unsafe_cast_var_name_to_lifetime(name), ()); + + // Loop variables are always removed at the end of the statement + // so this cast is safe. + let var_name = unsafe_cast_var_name_to_lifetime(var_name); + scope.push(var_name, ()); let index = scope.len() - 1; + let mut loop_result = Ok(Dynamic::UNIT); + for (x, iter_value) in func(iter_obj).enumerate() { // Increment counter if counter_index < usize::MAX { @@ -546,7 +555,12 @@ impl Engine { } #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, statements.position())?; + if let Err(err) = + self.inc_operations(&mut global.num_operations, statements.position()) + { + loop_result = Err(err); + break; + } if statements.is_empty() { continue; @@ -567,7 +581,8 @@ impl Engine { } scope.rewind(orig_scope_len); - Ok(Dynamic::UNIT) + + loop_result } else { Err(ERR::ErrorFor(expr.position()).into()) } @@ -580,7 +595,7 @@ impl Engine { // Try/Catch statement Stmt::TryCatch(x, _) => { - let (try_stmt, err_var, catch_stmt) = x.as_ref(); + let (try_stmt, err_var_name, catch_stmt) = x.as_ref(); let result = self .eval_stmt_block(scope, global, state, lib, this_ptr, try_stmt, true, level) @@ -630,8 +645,11 @@ impl Engine { let orig_scope_len = scope.len(); - err_var.as_ref().map(|Ident { name, .. }| { - scope.push(unsafe_cast_var_name_to_lifetime(name), err_value) + err_var_name.as_ref().map(|Ident { name, .. }| { + // Catch error variables are always removed from after the block + // so this cast is safe. + let var_name = unsafe_cast_var_name_to_lifetime(name); + scope.push(var_name, err_value) }); let result = self.eval_stmt_block( diff --git a/src/func/script.rs b/src/func/script.rs index cc37333a..51d7305e 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -74,14 +74,18 @@ impl Engine { let orig_mods_len = global.num_imported_modules(); // Put arguments into scope as variables - // Actually consume the arguments instead of cloning them scope.extend( fn_def .params .iter() - .zip(args.iter_mut().map(|v| mem::take(*v))) + .zip(args.into_iter().map(|v| { + // Actually consume the arguments instead of cloning them + mem::take(*v) + })) .map(|(name, value)| { - let var_name: std::borrow::Cow<'_, str> = + // Arguments are always removed at the end of the call, + // so this cast is safe. + let var_name: std::borrow::Cow<_> = unsafe_cast_var_name_to_lifetime(name).into(); (var_name, value) }), From 2a8a8c00f5a528485fa6cbf88c5f6a0168310a6a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 15 Jan 2022 10:24:08 +0800 Subject: [PATCH 18/27] Refine calc_index API. --- src/eval/chaining.rs | 20 +++++++++++--------- src/eval/target.rs | 26 +++++++++++++------------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index 3a776af9..6ac788fd 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -790,8 +790,9 @@ impl Engine { .as_int() .map_err(|typ| self.make_type_mismatch_err::(typ, pos))?; let len = arr.len(); - let arr_idx = - calc_index(len, index, true, || ERR::ErrorArrayBounds(len, index, pos))?; + let arr_idx = calc_index(len, index, true, || { + ERR::ErrorArrayBounds(len, index, pos).into() + })?; Ok(arr.get_mut(arr_idx).map(Target::from).unwrap()) } @@ -803,8 +804,9 @@ impl Engine { .as_int() .map_err(|typ| self.make_type_mismatch_err::(typ, pos))?; let len = arr.len(); - let arr_idx = - calc_index(len, index, true, || ERR::ErrorArrayBounds(len, index, pos))?; + let arr_idx = calc_index(len, index, true, || { + ERR::ErrorArrayBounds(len, index, pos).into() + })?; let value = arr.get(arr_idx).map(|&v| (v as crate::INT).into()).unwrap(); @@ -844,10 +846,10 @@ impl Engine { let end = range.end; let start = calc_index(BITS, start, false, || { - ERR::ErrorBitFieldBounds(BITS, start, pos) + ERR::ErrorBitFieldBounds(BITS, start, pos).into() })?; let end = calc_index(BITS, end, false, || { - ERR::ErrorBitFieldBounds(BITS, end, pos) + ERR::ErrorBitFieldBounds(BITS, end, pos).into() })?; if end <= start { @@ -869,10 +871,10 @@ impl Engine { let end = *range.end(); let start = calc_index(BITS, start, false, || { - ERR::ErrorBitFieldBounds(BITS, start, pos) + ERR::ErrorBitFieldBounds(BITS, start, pos).into() })?; let end = calc_index(BITS, end, false, || { - ERR::ErrorBitFieldBounds(BITS, end, pos) + ERR::ErrorBitFieldBounds(BITS, end, pos).into() })?; if end < start { @@ -913,7 +915,7 @@ impl Engine { const BITS: usize = std::mem::size_of::() * 8; let bit = calc_index(BITS, index, true, || { - ERR::ErrorBitFieldBounds(BITS, index, pos) + ERR::ErrorBitFieldBounds(BITS, index, pos).into() })?; let bit_value = (*value & (1 << bit)) != 0; diff --git a/src/eval/target.rs b/src/eval/target.rs index e8efefb9..4209c3fa 100644 --- a/src/eval/target.rs +++ b/src/eval/target.rs @@ -1,7 +1,7 @@ //! Type to hold a mutable reference to the target of an evaluation. use crate::types::dynamic::Variant; -use crate::{Dynamic, EvalAltResult, RhaiResultOf, INT}; +use crate::{Dynamic, RhaiResultOf, INT}; use std::ops::{Deref, DerefMut}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -38,35 +38,35 @@ pub fn calc_offset_len(length: usize, start: INT, len: INT) -> (usize, usize) { // // Negative starting positions count from the end. // -// Values going over bounds call the provided closure to create an error. +// Values going over bounds call the provided closure to return a default value or an error. #[inline(always)] -pub fn calc_index( +pub fn calc_index( length: usize, start: INT, negative_count_from_end: bool, - err: impl Fn() -> EvalAltResult, -) -> RhaiResultOf { + err: impl Fn() -> Result, +) -> Result { if start < 0 { if negative_count_from_end { // Count from end if negative #[cfg(not(feature = "unchecked"))] - return start - .checked_abs() - .ok_or_else(|| err().into()) - .and_then(|positive_start| { + return match start.checked_abs() { + Some(positive_start) => { if (positive_start as usize) > length { - Err(err().into()) + err() } else { Ok(length - (positive_start as usize)) } - }); + } + None => err(), + }; #[cfg(feature = "unchecked")] return Ok(length - (start.abs() as usize)); } else { - Err(err().into()) + err() } } else if start as usize >= length { - Err(err().into()) + err() } else { Ok(start as usize) } From 00b189d0c6e12dc1d9e6e3a41c40789fdea5268e Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 15 Jan 2022 11:26:43 +0800 Subject: [PATCH 19/27] Replace Cow in Scope with SmartString. --- CHANGELOG.md | 1 + src/eval/stmt.rs | 56 +++++++++++++++------------------ src/func/script.rs | 21 +++---------- src/tests.rs | 4 +-- src/types/scope.rs | 77 +++++++++++++++++++++++++--------------------- src/unsafe.rs | 17 ---------- tests/serde.rs | 2 +- 7 files changed, 75 insertions(+), 103 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 994b36c9..79d1bf39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Enhancements ------------ * Formatting of return types in functions metadata info is improved. +* Use `SmartString` for `Scope` variable names and remove `unsafe` lifetime casting. Version 1.4.0 diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index aff46fe6..aef4a313 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -3,15 +3,11 @@ use super::{EvalState, GlobalRuntimeState, Target}; use crate::ast::{Expr, Ident, OpAssignment, Stmt, AST_OPTION_FLAGS::*}; use crate::func::get_hasher; -use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::types::dynamic::{AccessMode, Union}; use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR, INT}; +use std::hash::{Hash, Hasher}; #[cfg(feature = "no_std")] use std::prelude::v1::*; -use std::{ - borrow::Cow, - hash::{Hash, Hasher}, -}; impl Engine { /// Evaluate a statements block. @@ -492,19 +488,13 @@ impl Engine { // Add the loop variables let orig_scope_len = scope.len(); let counter_index = if let Some(counter) = counter { - // Loop variables are always removed at the end of the statement - // so this cast is safe. - let counter_name = unsafe_cast_var_name_to_lifetime(&counter.name); - scope.push(counter_name, 0 as INT); + scope.push(counter.name.clone(), 0 as INT); scope.len() - 1 } else { usize::MAX }; - // Loop variables are always removed at the end of the statement - // so this cast is safe. - let var_name = unsafe_cast_var_name_to_lifetime(var_name); - scope.push(var_name, ()); + scope.push(var_name.clone(), ()); let index = scope.len() - 1; let mut loop_result = Ok(Dynamic::UNIT); @@ -514,11 +504,12 @@ impl Engine { if counter_index < usize::MAX { #[cfg(not(feature = "unchecked"))] if x > INT::MAX as usize { - return Err(ERR::ErrorArithmetic( + loop_result = Err(ERR::ErrorArithmetic( format!("for-loop counter overflow: {}", x), counter.as_ref().expect("`Some`").pos, ) .into()); + break; } let index_value = (x as INT).into(); @@ -575,7 +566,10 @@ impl Engine { Err(err) => match *err { ERR::LoopBreak(false, _) => (), ERR::LoopBreak(true, _) => break, - _ => return Err(err), + _ => { + loop_result = Err(err); + break; + } }, } } @@ -645,12 +639,9 @@ impl Engine { let orig_scope_len = scope.len(); - err_var_name.as_ref().map(|Ident { name, .. }| { - // Catch error variables are always removed from after the block - // so this cast is safe. - let var_name = unsafe_cast_var_name_to_lifetime(name); - scope.push(var_name, err_value) - }); + err_var_name + .as_ref() + .map(|Ident { name, .. }| scope.push(name.clone(), err_value)); let result = self.eval_stmt_block( scope, global, state, lib, this_ptr, catch_stmt, true, level, @@ -701,7 +692,7 @@ impl Engine { // Let/const statement Stmt::Var(expr, x, options, _) => { - let name = &x.name; + let var_name = &x.name; let entry_type = if options.contains(AST_OPTION_CONSTANT) { AccessMode::ReadOnly } else { @@ -713,7 +704,7 @@ impl Engine { .eval_expr(scope, global, state, lib, this_ptr, expr, level)? .flatten(); - let (var_name, _alias): (Cow<'_, str>, _) = if !rewind_scope { + let _alias = if !rewind_scope { #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_module"))] if state.scope_level == 0 @@ -721,23 +712,26 @@ impl Engine { && lib.iter().any(|&m| !m.is_empty()) { // Add a global constant if at top level and there are functions - global.set_constant(name.clone(), value.clone()); + global.set_constant(var_name.clone(), value.clone()); } - ( - name.to_string().into(), - if export { Some(name.clone()) } else { None }, - ) + if export { + Some(var_name) + } else { + None + } } else if export { unreachable!("exported variable not on global level"); } else { - (unsafe_cast_var_name_to_lifetime(name).into(), None) + None }; - scope.push_dynamic_value(var_name, entry_type, value); + scope.push_dynamic_value(var_name.clone(), entry_type, value); #[cfg(not(feature = "no_module"))] - _alias.map(|alias| scope.add_entry_alias(scope.len() - 1, alias)); + if let Some(alias) = _alias { + scope.add_entry_alias(scope.len() - 1, alias.clone()); + } Ok(Dynamic::UNIT) } diff --git a/src/func/script.rs b/src/func/script.rs index 51d7305e..baf4a66c 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -4,7 +4,6 @@ use super::call::FnCallArgs; use crate::ast::ScriptFnDef; use crate::eval::{EvalState, GlobalRuntimeState}; -use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::{Dynamic, Engine, Module, Position, RhaiError, RhaiResult, Scope, StaticVec, ERR}; use std::mem; #[cfg(feature = "no_std")] @@ -74,22 +73,10 @@ impl Engine { let orig_mods_len = global.num_imported_modules(); // Put arguments into scope as variables - scope.extend( - fn_def - .params - .iter() - .zip(args.into_iter().map(|v| { - // Actually consume the arguments instead of cloning them - mem::take(*v) - })) - .map(|(name, value)| { - // Arguments are always removed at the end of the call, - // so this cast is safe. - let var_name: std::borrow::Cow<_> = - unsafe_cast_var_name_to_lifetime(name).into(); - (var_name, value) - }), - ); + scope.extend(fn_def.params.iter().cloned().zip(args.into_iter().map(|v| { + // Actually consume the arguments instead of cloning them + mem::take(*v) + }))); // Merge in encapsulated environment, if any let mut lib_merged = StaticVec::with_capacity(lib.len() + 1); diff --git a/src/tests.rs b/src/tests.rs index 322e6cdb..f4d32675 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -23,11 +23,11 @@ fn check_struct_sizes() { assert_eq!(size_of::>(), if PACKED { 12 } else { 16 }); assert_eq!(size_of::(), if PACKED { 24 } else { 32 }); assert_eq!(size_of::>(), if PACKED { 24 } else { 32 }); - assert_eq!(size_of::(), if PACKED { 40 } else { 80 }); - assert_eq!(size_of::(), if PACKED { 232 } else { 464 }); #[cfg(target_pointer_width = "64")] { + assert_eq!(size_of::(), 400); + assert_eq!(size_of::(), 80); assert_eq!(size_of::(), 56); assert_eq!( size_of::(), diff --git a/src/types/scope.rs b/src/types/scope.rs index 84c4c446..56f21748 100644 --- a/src/types/scope.rs +++ b/src/types/scope.rs @@ -3,10 +3,12 @@ use super::dynamic::{AccessMode, Variant}; use crate::{Dynamic, Identifier, StaticVec}; use smallvec::SmallVec; -use std::iter::FromIterator; #[cfg(feature = "no_std")] use std::prelude::v1::*; -use std::{borrow::Cow, iter::Extend}; +use std::{ + iter::{Extend, FromIterator}, + marker::PhantomData, +}; /// Keep a number of entries inline (since [`Dynamic`] is usually small enough). const SCOPE_ENTRIES_INLINED: usize = 8; @@ -14,6 +16,11 @@ const SCOPE_ENTRIES_INLINED: usize = 8; /// Type containing information about the current scope. Useful for keeping state between /// [`Engine`][crate::Engine] evaluation runs. /// +/// # Lifetime +/// +/// Currently the lifetime parameter is not used, but it is not guaranteed to remain unused for +/// future versions. Until then, `'static` can be used. +/// /// # Thread Safety /// /// Currently, [`Scope`] is neither [`Send`] nor [`Sync`]. Turn on the `sync` feature to make it @@ -47,7 +54,7 @@ const SCOPE_ENTRIES_INLINED: usize = 8; // // [`Scope`] is implemented as two [`Vec`]'s of exactly the same length. Variables data (name, // type, etc.) is manually split into two equal-length arrays. That's because variable names take -// up the most space, with [`Cow`][Cow] being four words long, but in the vast majority of +// up the most space, with [`Identifier`] being four words long, but in the vast majority of // cases the name is NOT used to look up a variable. Variable lookup is usually via direct // indexing, by-passing the name altogether. // @@ -58,25 +65,30 @@ pub struct Scope<'a> { /// Current value of the entry. values: SmallVec<[Dynamic; SCOPE_ENTRIES_INLINED]>, /// (Name, aliases) of the entry. - names: SmallVec<[(Cow<'a, str>, Option>>); SCOPE_ENTRIES_INLINED]>, + names: SmallVec<[(Identifier, Option>>); SCOPE_ENTRIES_INLINED]>, + /// Phantom to keep the lifetime parameter in order not to break existing code. + phantom: PhantomData<&'a ()>, } -impl<'a> IntoIterator for Scope<'a> { - type Item = (Cow<'a, str>, Dynamic); - type IntoIter = Box + 'a>; +impl IntoIterator for Scope<'_> { + type Item = (String, Dynamic, Vec); + type IntoIter = Box>; #[inline] fn into_iter(self) -> Self::IntoIter { - Box::new( - self.values - .into_iter() - .zip(self.names.into_iter()) - .map(|(value, (name, _))| (name, value)), - ) + Box::new(self.values.into_iter().zip(self.names.into_iter()).map( + |(value, (name, alias))| { + ( + name.into(), + value, + alias.map(|a| a.to_vec()).unwrap_or_default(), + ) + }, + )) } } -impl<'a> Scope<'a> { +impl Scope<'_> { /// Create a new [`Scope`]. /// /// # Example @@ -95,6 +107,7 @@ impl<'a> Scope<'a> { Self { values: SmallVec::new_const(), names: SmallVec::new_const(), + phantom: PhantomData, } } /// Empty the [`Scope`]. @@ -171,11 +184,7 @@ impl<'a> Scope<'a> { /// assert_eq!(my_scope.get_value::("x").expect("x should exist"), 42); /// ``` #[inline(always)] - pub fn push( - &mut self, - name: impl Into>, - value: impl Variant + Clone, - ) -> &mut Self { + pub fn push(&mut self, name: impl Into, value: impl Variant + Clone) -> &mut Self { self.push_dynamic_value(name, AccessMode::ReadWrite, Dynamic::from(value)) } /// Add (push) a new [`Dynamic`] entry to the [`Scope`]. @@ -191,7 +200,7 @@ impl<'a> Scope<'a> { /// assert_eq!(my_scope.get_value::("x").expect("x should exist"), 42); /// ``` #[inline(always)] - pub fn push_dynamic(&mut self, name: impl Into>, value: Dynamic) -> &mut Self { + pub fn push_dynamic(&mut self, name: impl Into, value: Dynamic) -> &mut Self { self.push_dynamic_value(name, value.access_mode(), value) } /// Add (push) a new constant to the [`Scope`]. @@ -212,7 +221,7 @@ impl<'a> Scope<'a> { #[inline(always)] pub fn push_constant( &mut self, - name: impl Into>, + name: impl Into, value: impl Variant + Clone, ) -> &mut Self { self.push_dynamic_value(name, AccessMode::ReadOnly, Dynamic::from(value)) @@ -235,7 +244,7 @@ impl<'a> Scope<'a> { #[inline(always)] pub fn push_constant_dynamic( &mut self, - name: impl Into>, + name: impl Into, value: Dynamic, ) -> &mut Self { self.push_dynamic_value(name, AccessMode::ReadOnly, value) @@ -244,7 +253,7 @@ impl<'a> Scope<'a> { #[inline] pub(crate) fn push_dynamic_value( &mut self, - name: impl Into>, + name: impl Into, access: AccessMode, mut value: Dynamic, ) -> &mut Self { @@ -301,7 +310,7 @@ impl<'a> Scope<'a> { #[inline] #[must_use] pub fn contains(&self, name: &str) -> bool { - self.names.iter().any(|(key, _)| name == key.as_ref()) + self.names.iter().any(|(key, _)| name == key) } /// Find an entry in the [`Scope`], starting from the last. #[inline] @@ -314,7 +323,7 @@ impl<'a> Scope<'a> { .rev() // Always search a Scope in reverse order .enumerate() .find_map(|(i, (key, _))| { - if name == key.as_ref() { + if name == key { let index = len - 1 - i; Some((index, self.values[index].access_mode())) } else { @@ -343,7 +352,7 @@ impl<'a> Scope<'a> { .iter() .rev() .enumerate() - .find(|(_, (key, _))| name == key.as_ref()) + .find(|(_, (key, _))| name == key) .and_then(|(index, _)| self.values[len - 1 - index].flatten_clone().try_cast()) } /// Check if the named entry in the [`Scope`] is constant. @@ -398,7 +407,7 @@ impl<'a> Scope<'a> { #[inline] pub fn set_or_push( &mut self, - name: impl AsRef + Into>, + name: impl AsRef + Into, value: impl Variant + Clone, ) -> &mut Self { match self.get_index(name.as_ref()) { @@ -437,7 +446,7 @@ impl<'a> Scope<'a> { #[inline] pub fn set_value( &mut self, - name: impl AsRef + Into>, + name: impl AsRef + Into, value: impl Variant + Clone, ) -> &mut Self { match self.get_index(name.as_ref()) { @@ -535,9 +544,7 @@ impl<'a> Scope<'a> { /// Get an iterator to entries in the [`Scope`]. #[inline] #[allow(dead_code)] - pub(crate) fn into_iter( - self, - ) -> impl Iterator, Dynamic, Vec)> { + pub(crate) fn into_iter(self) -> impl Iterator)> { self.names .into_iter() .zip(self.values.into_iter()) @@ -597,7 +604,7 @@ impl<'a> Scope<'a> { } } -impl<'a, K: Into>> Extend<(K, Dynamic)> for Scope<'a> { +impl> Extend<(K, Dynamic)> for Scope<'_> { #[inline] fn extend>(&mut self, iter: T) { iter.into_iter().for_each(|(name, value)| { @@ -606,7 +613,7 @@ impl<'a, K: Into>> Extend<(K, Dynamic)> for Scope<'a> { } } -impl<'a, K: Into>> FromIterator<(K, Dynamic)> for Scope<'a> { +impl> FromIterator<(K, Dynamic)> for Scope<'_> { #[inline] fn from_iter>(iter: T) -> Self { let mut scope = Self::new(); @@ -615,7 +622,7 @@ impl<'a, K: Into>> FromIterator<(K, Dynamic)> for Scope<'a> { } } -impl<'a, K: Into>> Extend<(K, bool, Dynamic)> for Scope<'a> { +impl> Extend<(K, bool, Dynamic)> for Scope<'_> { #[inline] fn extend>(&mut self, iter: T) { iter.into_iter().for_each(|(name, is_constant, value)| { @@ -632,7 +639,7 @@ impl<'a, K: Into>> Extend<(K, bool, Dynamic)> for Scope<'a> { } } -impl<'a, K: Into>> FromIterator<(K, bool, Dynamic)> for Scope<'a> { +impl> FromIterator<(K, bool, Dynamic)> for Scope<'_> { #[inline] fn from_iter>(iter: T) -> Self { let mut scope = Self::new(); diff --git a/src/unsafe.rs b/src/unsafe.rs index ab272fa8..9c1eb467 100644 --- a/src/unsafe.rs +++ b/src/unsafe.rs @@ -52,20 +52,3 @@ pub fn unsafe_cast_box(item: Box) -> Option { None } } - -/// # DANGEROUS!!! -/// -/// A dangerous function that blindly casts a `&str` from one lifetime to a `&str` of -/// another lifetime. This is mainly used to let us push a block-local variable into the -/// current [`Scope`][crate::Scope] without cloning the variable name. Doing this is safe because all local -/// variables in the [`Scope`][crate::Scope] are cleared out before existing the block. -/// -/// Force-casting a local variable's lifetime to the current [`Scope`][crate::Scope]'s larger lifetime saves -/// on allocations and string cloning, thus avoids us having to maintain a chain of [`Scope`][crate::Scope]'s. -#[inline(always)] -#[must_use] -pub fn unsafe_cast_var_name_to_lifetime<'s>(name: &str) -> &'s str { - // WARNING - force-cast the variable name into the scope's lifetime to avoid cloning it - // this is safe because all local variables are cleared at the end of the block - unsafe { mem::transmute(name) } -} diff --git a/tests/serde.rs b/tests/serde.rs index 08bbeccc..b6541d3d 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -12,7 +12,7 @@ use rhai::Array; use rhai::Map; #[cfg(not(feature = "no_float"))] use rhai::FLOAT; -#[cfg(not(feature = "no_float"))] +#[cfg(feature = "no_float")] #[cfg(feature = "decimal")] use rust_decimal::Decimal; From e24848668a7adf619a631093a0e834e166bb5cb8 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 15 Jan 2022 11:35:44 +0800 Subject: [PATCH 20/27] Fix build. --- src/packages/bit_field.rs | 8 ++++---- src/packages/iter_basic.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/packages/bit_field.rs b/src/packages/bit_field.rs index 6d6ae617..9b0e8bd9 100644 --- a/src/packages/bit_field.rs +++ b/src/packages/bit_field.rs @@ -24,7 +24,7 @@ mod bit_field_functions { #[rhai_fn(return_raw)] pub fn get_bit(value: INT, bit: INT) -> RhaiResultOf { let bit = calc_index(BITS, bit, true, || { - ERR::ErrorBitFieldBounds(BITS, bit, Position::NONE) + ERR::ErrorBitFieldBounds(BITS, bit, Position::NONE).into() })?; Ok((value & (1 << bit)) != 0) @@ -32,7 +32,7 @@ mod bit_field_functions { #[rhai_fn(return_raw)] pub fn set_bit(value: &mut INT, bit: INT, new_value: bool) -> RhaiResultOf<()> { let bit = calc_index(BITS, bit, true, || { - ERR::ErrorBitFieldBounds(BITS, bit, Position::NONE) + ERR::ErrorBitFieldBounds(BITS, bit, Position::NONE).into() })?; let mask = 1 << bit; @@ -63,7 +63,7 @@ mod bit_field_functions { } let bit = calc_index(BITS, bit, true, || { - ERR::ErrorBitFieldBounds(BITS, bit, Position::NONE) + ERR::ErrorBitFieldBounds(BITS, bit, Position::NONE).into() })?; let bits = if bit + bits as usize > BITS { @@ -108,7 +108,7 @@ mod bit_field_functions { } let bit = calc_index(BITS, bit, true, || { - ERR::ErrorBitFieldBounds(BITS, bit, Position::NONE) + ERR::ErrorBitFieldBounds(BITS, bit, Position::NONE).into() })?; let bits = if bit + bits as usize > BITS { diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index aae50762..d06931f9 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -122,7 +122,7 @@ const BITS: usize = std::mem::size_of::() * 8; impl BitRange { pub fn new(value: INT, from: INT, len: INT) -> RhaiResultOf { let from = calc_index(BITS, from, true, || { - crate::ERR::ErrorBitFieldBounds(BITS, from, Position::NONE) + crate::ERR::ErrorBitFieldBounds(BITS, from, Position::NONE).into() })?; let len = if len < 0 { From 36677613409e915e12363dd3a3b9ac24f7080a9c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 15 Jan 2022 23:34:38 +0800 Subject: [PATCH 21/27] Fix bugs and add comments to standard library. --- CHANGELOG.md | 4 + src/packages/arithmetic.rs | 33 + src/packages/array_basic.rs | 1413 ++++++++++++++++++++++++++++++++-- src/packages/bit_field.rs | 115 ++- src/packages/blob_basic.rs | 698 ++++++++++++++--- src/packages/fn_basic.rs | 128 +-- src/packages/iter_basic.rs | 292 ++++++- src/packages/lang_core.rs | 108 +++ src/packages/map_basic.rs | 106 +++ src/packages/math_basic.rs | 190 ++++- src/packages/string_basic.rs | 72 +- src/packages/string_more.rs | 562 +++++++++++++- src/packages/time_basic.rs | 17 + 13 files changed, 3375 insertions(+), 363 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79d1bf39..0d99ecf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,12 +12,16 @@ Bug fixes * `set_bit` for bit-flags with negative index now works correctly. * Misnamed `params` field `name` in the JSON output of `Engine::gen_fn_metadata_to_json` is fixed (was incorrectly named `type`). * Fixes a potential `unsafe` violation in `for` loop. +* Missing `to_hex`, `to_octal` and `to_binary` for `i128` and `u128` are added. +* `remove` for arrays and BLOB's now treat negative index correctly. +* `parse_int` now works properly for negative numbers. Enhancements ------------ * Formatting of return types in functions metadata info is improved. * Use `SmartString` for `Scope` variable names and remove `unsafe` lifetime casting. +* Functions in the standard library now have doc-comments (which can be obtained via `Engine::gen_fn_metadata_to_json`). Version 1.4.0 diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 7d135620..8eae8764 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -121,14 +121,17 @@ macro_rules! gen_arithmetic_functions { pub fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type { x ^ y } + /// Return true if the number is zero. #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: $arg_type) -> bool { x == 0 } + /// Return true if the number is odd. #[rhai_fn(get = "is_odd", name = "is_odd")] pub fn is_odd(x: $arg_type) -> bool { x % 2 != 0 } + /// Return true if the number is even. #[rhai_fn(get = "is_even", name = "is_even")] pub fn is_even(x: $arg_type) -> bool { x % 2 == 0 @@ -157,6 +160,7 @@ macro_rules! gen_signed_functions { pub fn plus(x: $arg_type) -> $arg_type { x } + /// Return the absolute value of the number. #[rhai_fn(return_raw)] pub fn abs(x: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { @@ -165,6 +169,11 @@ macro_rules! gen_signed_functions { Ok(x.abs()) } } + /// Return the sign (as an integer) of the number according to the following: + /// + /// * `0` if the number is zero + /// * `1` if the number is positive + /// * `-1` if the number is negative pub fn sign(x: $arg_type) -> INT { x.signum() as INT } @@ -216,14 +225,17 @@ def_package! { #[export_module] mod int_functions { + /// Return true if the number is zero. #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: INT) -> bool { x == 0 } + /// Return true if the number is odd. #[rhai_fn(get = "is_odd", name = "is_odd")] pub fn is_odd(x: INT) -> bool { x % 2 != 0 } + /// Return true if the number is even. #[rhai_fn(get = "is_even", name = "is_even")] pub fn is_even(x: INT) -> bool { x % 2 == 0 @@ -334,9 +346,15 @@ mod f32_functions { pub fn plus(x: f32) -> f32 { x } + /// Return the absolute value of the floating-point number. pub fn abs(x: f32) -> f32 { x.abs() } + /// Return the sign (as an integer) of the floating-point number according to the following: + /// + /// * `0` if the number is zero + /// * `1` if the number is positive + /// * `-1` if the number is negative #[rhai_fn(return_raw)] pub fn sign(x: f32) -> RhaiResultOf { match x.signum() { @@ -345,6 +363,7 @@ mod f32_functions { x => Ok(x as INT), } } + /// Return true if the floating-point number is zero. #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: f32) -> bool { x == 0.0 @@ -442,9 +461,15 @@ mod f64_functions { pub fn plus(x: f64) -> f64 { x } + /// Return the absolute value of the floating-point number. pub fn abs(x: f64) -> f64 { x.abs() } + /// Return the sign (as an integer) of the floating-point number according to the following: + /// + /// * `0` if the number is zero + /// * `1` if the number is positive + /// * `-1` if the number is negative #[rhai_fn(return_raw)] pub fn sign(x: f64) -> RhaiResultOf { match x.signum() { @@ -453,6 +478,7 @@ mod f64_functions { x => Ok(x as INT), } } + /// Return true if the floating-point number is zero. #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: f64) -> bool { x == 0.0 @@ -536,9 +562,15 @@ pub mod decimal_functions { pub fn plus(x: Decimal) -> Decimal { x } + /// Return the absolute value of the decimal number. pub fn abs(x: Decimal) -> Decimal { x.abs() } + /// Return the sign (as an integer) of the decimal number according to the following: + /// + /// * `0` if the number is zero + /// * `1` if the number is positive + /// * `-1` if the number is negative pub fn sign(x: Decimal) -> INT { if x == Decimal::zero() { 0 @@ -548,6 +580,7 @@ pub mod decimal_functions { 1 } } + /// Return true if the decimal number is zero. #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: Decimal) -> bool { x.is_zero() diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 007a7fdc..528a0a32 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -2,11 +2,11 @@ #![allow(non_snake_case)] use crate::engine::OP_EQUALS; -use crate::eval::calc_offset_len; +use crate::eval::{calc_index, calc_offset_len}; use crate::plugin::*; use crate::{ - def_package, Array, Dynamic, ExclusiveRange, FnPtr, InclusiveRange, NativeCallContext, - Position, RhaiResultOf, ERR, INT, + def_package, Array, Dynamic, Engine, ExclusiveRange, FnPtr, InclusiveRange, NativeCallContext, + Position, RhaiResultOf, StaticVec, ERR, INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -26,22 +26,60 @@ def_package! { #[export_module] pub mod array_functions { + /// Number of elements in the array. #[rhai_fn(name = "len", get = "len", pure)] pub fn len(array: &mut Array) -> INT { array.len() as INT } + /// Add a new element, which is not another array, to the end of the array. + /// + /// If `item` is `Array`, then `append` is more specific and will be called instead. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3]; + /// + /// x.push("hello"); + /// + /// print(x); // prints [1, 2, 3, "hello"] + /// ``` pub fn push(array: &mut Array, item: Dynamic) { array.push(item); } - pub fn append(array1: &mut Array, array2: Array) { - if !array2.is_empty() { - if array1.is_empty() { - *array1 = array2; + /// Add all the elements of another array to the end of the array. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3]; + /// let y = [true, 'x']; + /// + /// x.push(y); + /// + /// print(x); // prints "[1, 2, 3, true, 'x']" + /// ``` + pub fn append(array: &mut Array, new_array: Array) { + if !new_array.is_empty() { + if array.is_empty() { + *array = new_array; } else { - array1.extend(array2); + array.extend(new_array); } } } + /// Combine two arrays into a new array and return it. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3]; + /// let y = [true, 'x']; + /// + /// print(x + y); // prints "[1, 2, 3, true, 'x']" + /// + /// print(x); // prints "[1, 2, 3" + /// ``` #[rhai_fn(name = "+")] pub fn concat(array1: Array, array2: Array) -> Array { if !array2.is_empty() { @@ -56,6 +94,25 @@ pub mod array_functions { array1 } } + /// Add a new element into the array at a particular `index` position. + /// + /// * If `index` < 0, position counts from the end of the array (`-1` is the last element). + /// * If `index` < -length of array, the element is added to the beginning of the array. + /// * If `index` ≥ length of array, the element is appended to the end of the array. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3]; + /// + /// x.insert(0, "hello"); + /// + /// x.insert(2, true); + /// + /// x.insert(-2, 42); + /// + /// print(x); // prints ["hello", 1, true, 2, 42, 3] + /// ``` pub fn insert(array: &mut Array, index: INT, item: Dynamic) { if array.is_empty() { array.push(item); @@ -70,6 +127,23 @@ pub mod array_functions { array.insert(index, item); } } + /// Pad the array to at least the specified length with copies of a specified element. + /// + /// If `len` ≤ length of array, no padding is done. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3]; + /// + /// x.pad(5, 42); + /// + /// print(x); // prints "[1, 2, 3, 42, 42]" + /// + /// x.pad(3, 123); + /// + /// print(x); // prints "[1, 2, 3, 42, 42]" + /// ``` #[rhai_fn(return_raw)] pub fn pad( ctx: NativeCallContext, @@ -86,55 +160,68 @@ pub mod array_functions { // Check if array will be over max size limit #[cfg(not(feature = "unchecked"))] - if _ctx.engine().max_array_size() > 0 && len > _ctx.engine().max_array_size() { - return Err(ERR::ErrorDataTooLarge("Size of array".to_string(), Position::NONE).into()); - } + { + if _ctx.engine().max_array_size() > 0 && len > _ctx.engine().max_array_size() { + return Err( + ERR::ErrorDataTooLarge("Size of array".to_string(), Position::NONE).into(), + ); + } - #[cfg(not(feature = "unchecked"))] - let check_sizes = match item.0 { - crate::types::dynamic::Union::Array(_, _, _) - | crate::types::dynamic::Union::Str(_, _, _) => true, - #[cfg(not(feature = "no_object"))] - crate::types::dynamic::Union::Map(_, _, _) => true, - _ => false, - }; - #[cfg(feature = "unchecked")] - let check_sizes = false; + let check_sizes = match item.0 { + crate::types::dynamic::Union::Array(_, _, _) + | crate::types::dynamic::Union::Str(_, _, _) => true, + #[cfg(not(feature = "no_object"))] + crate::types::dynamic::Union::Map(_, _, _) => true, + _ => false, + }; - if check_sizes { - let mut arr_len = array.len(); - let mut arr = Dynamic::from_array(mem::take(array)); + if check_sizes { + let mut arr_len = array.len(); + let mut arr = Dynamic::from_array(mem::take(array)); - #[cfg(not(feature = "unchecked"))] - let (mut a1, mut m1, mut s1) = Engine::calc_data_sizes(&arr, true); - #[cfg(not(feature = "unchecked"))] - let (a2, m2, s2) = Engine::calc_data_sizes(&item, true); + let (mut a1, mut m1, mut s1) = Engine::calc_data_sizes(&arr, true); + let (a2, m2, s2) = Engine::calc_data_sizes(&item, true); - { - let mut guard = arr.write_lock::().unwrap(); + { + let mut guard = arr.write_lock::().unwrap(); - while arr_len < len { - #[cfg(not(feature = "unchecked"))] - { + while arr_len < len { a1 += a2; m1 += m2; s1 += s2; _ctx.engine() .raise_err_if_over_data_size_limit((a1, m1, s1), Position::NONE)?; - } - guard.push(item.clone()); - arr_len += 1; - } - } - *array = arr.into_array().unwrap(); - } else { - array.resize(len, item); + guard.push(item.clone()); + arr_len += 1; + } + } + + *array = arr.into_array().unwrap(); + } else { + array.resize(len, item); + } } + #[cfg(feature = "unchecked")] + array.resize(len, item); + Ok(()) } + /// Remove the last element from the array and return it. + /// + /// If the array is empty, `()` is returned. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3]; + /// + /// print(x.pop()); // prints 3 + /// + /// print(x); // prints "[1, 2]" + /// ``` pub fn pop(array: &mut Array) -> Dynamic { if array.is_empty() { Dynamic::UNIT @@ -142,6 +229,19 @@ pub mod array_functions { array.pop().unwrap_or_else(|| Dynamic::UNIT) } } + /// Remove the first element from the array and return it. + /// + /// If the array is empty, `()` is returned. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3]; + /// + /// print(x.shift()); // prints 1 + /// + /// print(x); // prints "[2, 3]" + /// ``` pub fn shift(array: &mut Array) -> Dynamic { if array.is_empty() { Dynamic::UNIT @@ -149,27 +249,84 @@ pub mod array_functions { array.remove(0) } } + /// Remove the element at the specified `index` from the array and return it. + /// + /// * If `index` < 0, position counts from the end of the array (`-1` is the last element). + /// * If `index` < -length of array, `()` is returned. + /// * If `index` ≥ length of array, `()` is returned. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3]; + /// + /// print(x.remove(1)); // prints 2 + /// + /// print(x); // prints "[1, 3]" + /// + /// print(x.remove(-2)); // prints 1 + /// + /// print(x); // prints "[3]" + /// ``` pub fn remove(array: &mut Array, index: INT) -> Dynamic { - if index < 0 || (index as usize) >= array.len() { - Dynamic::UNIT - } else { - array.remove(index as usize) - } + let index = match calc_index(array.len(), index, true, || Err(())) { + Ok(n) => n, + Err(_) => return Dynamic::UNIT, + }; + + array.remove(index) } + /// Clear the array. pub fn clear(array: &mut Array) { if !array.is_empty() { array.clear(); } } + /// Cut off the array at the specified length. + /// + /// * If `len` ≤ 0, the array is cleared. + /// * If `len` ≥ length of array, the array is not truncated. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// x.truncate(3); + /// + /// print(x); // prints "[1, 2, 3]" + /// + /// x.truncate(10); + /// + /// print(x); // prints "[1, 2, 3]" + /// ``` pub fn truncate(array: &mut Array, len: INT) { if !array.is_empty() { - if len >= 0 { + if len > 0 { array.truncate(len as usize); } else { array.clear(); } } } + /// Cut off the head of the array, leaving a tail of the specified length. + /// + /// * If `len` ≤ 0, the array is cleared. + /// * If `len` ≥ length of array, the array is not modified. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// x.chop(3); + /// + /// print(x); // prints "[3, 4, 5]" + /// + /// x.chop(10); + /// + /// print(x); // prints "[3, 4, 5]" + /// ``` pub fn chop(array: &mut Array, len: INT) { if !array.is_empty() { if len <= 0 { @@ -179,23 +336,80 @@ pub mod array_functions { } } } + /// Reverse all the elements in the array. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// x.reverse(); + /// + /// print(x); // prints "[5, 4, 3, 2, 1]" + /// ``` pub fn reverse(array: &mut Array) { if !array.is_empty() { array.reverse(); } } + /// Replace an exclusive range of the array with another array. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// let y = [7, 8, 9, 10]; + /// + /// x.splice(1..3, y); + /// + /// print(x); // prints "[1, 7, 8, 9, 10, 4, 5]" + /// ``` #[rhai_fn(name = "splice")] pub fn splice_range(array: &mut Array, range: ExclusiveRange, replace: Array) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); splice(array, start, end - start, replace) } + /// Replace an inclusive range of the array with another array. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// let y = [7, 8, 9, 10]; + /// + /// x.splice(1..=3, y); + /// + /// print(x); // prints "[1, 7, 8, 9, 10, 5]" + /// ``` #[rhai_fn(name = "splice")] pub fn splice_inclusive_range(array: &mut Array, range: InclusiveRange, replace: Array) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); splice(array, start, end - start + 1, replace) } + /// Replace a portion of the array with another array. + /// + /// * If `start` < 0, position counts from the end of the array (`-1` is the last element). + /// * If `start` < -length of array, position counts from the beginning of the array. + /// * If `start` ≥ length of array, the other array is appended to the end of the array. + /// * If `len` ≤ 0, the other array is inserted into the array at the `start` position without replacing any element. + /// * If `start` position + `len` ≥ length of array, entire portion of the array after the `start` position is replaced. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// let y = [7, 8, 9, 10]; + /// + /// x.splice(1, 2, y); + /// + /// print(x); // prints "[1, 7, 8, 9, 10, 4, 5]" + /// + /// x.splice(-5, 4, y); + /// + /// print(x); // prints "[1, 7, 7, 8, 9, 10, 5]" + /// ``` pub fn splice(array: &mut Array, start: INT, len: INT, replace: Array) { if array.is_empty() { *array = replace; @@ -210,18 +424,59 @@ pub mod array_functions { array.splice(start..start + len, replace); } } + /// Copy an exclusive range of the array and return it as a new array. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// print(x.extract(1..3)); // prints "[2, 3]" + /// + /// print(x); // prints "[1, 2, 3, 4, 5]" + /// ``` #[rhai_fn(name = "extract")] pub fn extract_range(array: &mut Array, range: ExclusiveRange) -> Array { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); extract(array, start, end - start) } + /// Copy an inclusive range of the array and return it as a new array. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// print(x.extract(1..=3)); // prints "[2, 3, 4]" + /// + /// print(x); // prints "[1, 2, 3, 4, 5]" + /// ``` #[rhai_fn(name = "extract")] pub fn extract_inclusive_range(array: &mut Array, range: InclusiveRange) -> Array { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); extract(array, start, end - start + 1) } + /// Copy a portion of the array and return it as a new array. + /// + /// * If `start` < 0, position counts from the end of the array (`-1` is the last element). + /// * If `start` < -length of array, position counts from the beginning of the array. + /// * If `start` ≥ length of array, an empty array is returned. + /// * If `len` ≤ 0, an empty array is returned. + /// * If `start` position + `len` ≥ length of array, entire portion of the array after the `start` position is copied and returned. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// print(x.extract(1, 3)); // prints "[2, 3, 4]" + /// + /// print(x.extract(-3, 2)); // prints "[3, 4]" + /// + /// print(x); // prints "[1, 2, 3, 4, 5]" + /// ``` pub fn extract(array: &mut Array, start: INT, len: INT) -> Array { if array.is_empty() || len <= 0 { return Array::new(); @@ -235,17 +490,53 @@ pub mod array_functions { array[start..start + len].to_vec() } } + /// Copy a portion of the array beginning at the `start` position till the end and return it as + /// a new array. + /// + /// * If `start` < 0, position counts from the end of the array (`-1` is the last element). + /// * If `start` < -length of array, the entire array is copied and returned. + /// * If `start` ≥ length of array, an empty array is returned. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// print(x.extract(2)); // prints "[3, 4, 5]" + /// + /// print(x.extract(-3)); // prints "[3, 4, 5]" + /// + /// print(x); // prints "[1, 2, 3, 4, 5]" + /// ``` #[rhai_fn(name = "extract")] pub fn extract_tail(array: &mut Array, start: INT) -> Array { extract(array, start, INT::MAX) } + /// Cut off the array at `index` and return it as a new array. + /// + /// * If `index` < 0, position counts from the end of the array (`-1` is the last element). + /// * If `index` is zero, the entire array is cut and returned. + /// * If `index` < -length of array, the entire array is cut and returned. + /// * If `index` ≥ length of array, nothing is cut from the array and an empty array is returned. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.split(2); + /// + /// print(y); // prints "[3, 4, 5]" + /// + /// print(x); // prints "[1, 2]" + /// ``` #[rhai_fn(name = "split")] - pub fn split_at(array: &mut Array, start: INT) -> Array { + pub fn split_at(array: &mut Array, index: INT) -> Array { if array.is_empty() { return Array::new(); } - let (start, len) = calc_offset_len(array.len(), start, INT::MAX); + let (start, len) = calc_offset_len(array.len(), index, INT::MAX); if start == 0 { if len >= array.len() { @@ -263,6 +554,27 @@ pub mod array_functions { result } } + /// Iterate through all the elements in the array, applying a `mapper` function to each element + /// in turn, and return the results as a new array. + /// + /// # Function Parameters + /// + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.map(|v| v * v); + /// + /// print(y); // prints "[1, 4, 9, 16, 25]" + /// + /// let y = x.map(|v, i| v * i); + /// + /// print(y); // prints "[0, 2, 6, 12, 20]" + /// ``` #[rhai_fn(return_raw, pure)] pub fn map(ctx: NativeCallContext, array: &mut Array, mapper: FnPtr) -> RhaiResultOf { if array.is_empty() { @@ -296,8 +608,35 @@ pub mod array_functions { Ok(ar) } + /// Iterate through all the elements in the array, applying a function named by `mapper` to each + /// element in turn, and return the results as a new array. + /// + /// # Function Parameters + /// + /// A function with the same name as the value of `mapper` must exist taking these parameters: + /// + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// fn square(x) { x * x } + /// + /// fn multiply(x, i) { x * i } + /// + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.map("square"); + /// + /// print(y); // prints "[1, 4, 9, 16, 25]" + /// + /// let y = x.map("multiply"); + /// + /// print(y); // prints "[0, 2, 6, 12, 20]" + /// ``` #[rhai_fn(name = "map", return_raw, pure)] - pub fn map_with_fn_name( + pub fn map_by_fn_name( ctx: NativeCallContext, array: &mut Array, mapper: &str, @@ -305,6 +644,27 @@ pub mod array_functions { map(ctx, array, FnPtr::new(mapper)?) } + /// Iterate through all the elements in the array, applying a `filter` function to each element + /// in turn, and return a copy of all elements (in order) that return `true` as a new array. + /// + /// # Function Parameters + /// + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.filter(|v| v >= 3); + /// + /// print(y); // prints "[3, 4, 5]" + /// + /// let y = x.filter(|v, i| v * i >= 10); + /// + /// print(y); // prints "[12, 20]" + /// ``` #[rhai_fn(return_raw, pure)] pub fn filter(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf { if array.is_empty() { @@ -341,14 +701,56 @@ pub mod array_functions { Ok(ar) } + /// Iterate through all the elements in the array, applying a function named by `filter` to each + /// element in turn, and return a copy of all elements (in order) that return `true` as a new array. + /// + /// # Function Parameters + /// + /// A function with the same name as the value of `filter` must exist taking these parameters: + /// + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// fn screen(x, i) { x * i >= 10 } + /// + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.filter("is_odd"); + /// + /// print(y); // prints "[1, 3, 5]" + /// + /// let y = x.filter("screen"); + /// + /// print(y); // prints "[12, 20]" + /// ``` #[rhai_fn(name = "filter", return_raw, pure)] - pub fn filter_with_fn_name( + pub fn filter_by_fn_name( ctx: NativeCallContext, array: &mut Array, filter_func: &str, ) -> RhaiResultOf { filter(ctx, array, FnPtr::new(filter_func)?) } + /// Return `true` if the array contains an element that equals `value`. + /// + /// The operator `==` is used to compare elements with `value` and must be defined, + /// otherwise `false` is assumed. + /// + /// This function also drives the `in` operator. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// // The 'in' operator calls 'contains' in the background + /// if 4 in x { + /// print("found!"); + /// } + /// ``` #[rhai_fn(return_raw, pure)] pub fn contains( ctx: NativeCallContext, @@ -382,6 +784,23 @@ pub mod array_functions { Ok(false) } + /// Find the first element in the array that equals a particular `value` and return its index. + /// If no element equals `value`, `-1` is returned. + /// + /// The operator `==` is used to compare elements with `value` and must be defined, + /// otherwise `false` is assumed. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5]; + /// + /// print(x.index_of(4)); // prints 3 (first index) + /// + /// print(x.index_of(9)); // prints -1 + /// + /// print(x.index_of("foo")); // prints -1: strings do not equal numbers + /// ``` #[rhai_fn(return_raw, pure)] pub fn index_of( ctx: NativeCallContext, @@ -394,6 +813,33 @@ pub mod array_functions { index_of_starting_from(ctx, array, value, 0) } } + /// Find the first element in the array, starting from a particular `start` position, that + /// equals a particular `value` and return its index. If no element equals `value`, `-1` is returned. + /// + /// * If `start` < 0, position counts from the end of the array (`-1` is the last element). + /// * If `start` < -length of array, position counts from the beginning of the array. + /// * If `start` ≥ length of array, `-1` is returned. + /// + /// The operator `==` is used to compare elements with `value` and must be defined, + /// otherwise `false` is assumed. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5]; + /// + /// print(x.index_of(4, 2)); // prints 3 + /// + /// print(x.index_of(4, 5)); // prints 7 + /// + /// print(x.index_of(4, 15)); // prints -1: nothing found past end of array + /// + /// print(x.index_of(4, -5)); // prints 11: -5 = start from index 8 + /// + /// print(x.index_of(9, 1)); // prints -1: nothing equals 9 + /// + /// print(x.index_of("foo", 1)); // prints -1: strings do not equal numbers + /// ``` #[rhai_fn(name = "index_of", return_raw, pure)] pub fn index_of_starting_from( ctx: NativeCallContext, @@ -430,14 +876,26 @@ pub mod array_functions { Ok(-1 as INT) } - #[rhai_fn(name = "index_of", return_raw, pure)] - pub fn index_of_with_fn_name( - ctx: NativeCallContext, - array: &mut Array, - filter: &str, - ) -> RhaiResultOf { - index_of_filter(ctx, array, FnPtr::new(filter)?) - } + /// Iterate through all the elements in the array, applying a `filter` function to each element + /// in turn, and return the index of the first element that returns `true`. + /// If no element returns `true`, `-1` is returned. + /// + /// # Function Parameters + /// + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5]; + /// + /// print(x.index_of(|v| v > 3)); // prints 3: 4 > 3 + /// + /// print(x.index_of(|v| v > 8)); // prints -1: nothing is > 8 + /// + /// print(x.index_of(|v, i| v * i > 20)); // prints 7: 4 * 7 > 20 + /// ``` #[rhai_fn(name = "index_of", return_raw, pure)] pub fn index_of_filter( ctx: NativeCallContext, @@ -450,6 +908,68 @@ pub mod array_functions { index_of_filter_starting_from(ctx, array, filter, 0) } } + /// Iterate through all the elements in the array, applying a function named by `filter` to each + /// element in turn, and return the index of the first element that returns `true`. + /// If no element returns `true`, `-1` is returned. + /// + /// # Function Parameters + /// + /// A function with the same name as the value of `filter` must exist taking these parameters: + /// + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// fn is_special(x) { x > 3 } + /// + /// fn is_dumb(x) { x > 8 } + /// + /// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5]; + /// + /// print(x.index_of("is_special")); // prints 3 + /// + /// print(x.index_of("is_dumb")); // prints -1 + /// ``` + #[rhai_fn(name = "index_of", return_raw, pure)] + pub fn index_of_by_fn_name( + ctx: NativeCallContext, + array: &mut Array, + filter: &str, + ) -> RhaiResultOf { + index_of_filter(ctx, array, FnPtr::new(filter)?) + } + /// Iterate through all the elements in the array, starting from a particular `start` position, + /// applying a `filter` function to each element in turn, and return the index of the first + /// element that returns `true`. If no element returns `true`, `-1` is returned. + /// + /// * If `start` < 0, position counts from the end of the array (`-1` is the last element). + /// * If `start` < -length of array, position counts from the beginning of the array. + /// * If `start` ≥ length of array, `-1` is returned. + /// + /// # Function Parameters + /// + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5]; + /// + /// print(x.index_of(|v| v > 1, 3)); // prints 5: 2 > 1 + /// + /// print(x.index_of(|v| v < 2, 9)); // prints -1: nothing < 2 past index 9 + /// + /// print(x.index_of(|v| v > 1, 15)); // prints -1: nothing found past end of array + /// + /// print(x.index_of(|v| v > 1, -5)); // prints 9: -5 = start from index 8 + /// + /// print(x.index_of(|v| v > 1, -99)); // prints 1: -99 = start from beginning + /// + /// print(x.index_of(|v, i| v * i > 20, 8)); // prints 10: 3 * 10 > 20 + /// ``` #[rhai_fn(name = "index_of", return_raw, pure)] pub fn index_of_filter_starting_from( ctx: NativeCallContext, @@ -491,8 +1011,46 @@ pub mod array_functions { Ok(-1 as INT) } + /// Iterate through all the elements in the array, starting from a particular `start` position, + /// applying a function named by `filter` to each element in turn, and return the index of the + /// first element that returns `true`. If no element returns `true`, `-1` is returned. + /// + /// * If `start` < 0, position counts from the end of the array (`-1` is the last element). + /// * If `start` < -length of array, position counts from the beginning of the array. + /// * If `start` ≥ length of array, `-1` is returned. + /// + /// # Function Parameters + /// + /// A function with the same name as the value of `filter` must exist taking these parameters: + /// + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// fn plural(x) { x > 1 } + /// + /// fn singular(x) { x < 2 } + /// + /// fn screen(x, i) { x * i > 20 } + /// + /// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5]; + /// + /// print(x.index_of("plural", 3)); // prints 5: 2 > 1 + /// + /// print(x.index_of("singular", 9)); // prints -1: nothing < 2 past index 9 + /// + /// print(x.index_of("plural", 15)); // prints -1: nothing found past end of array + /// + /// print(x.index_of("plural", -5)); // prints 9: -5 = start from index 8 + /// + /// print(x.index_of("plural", -99)); // prints 1: -99 = start from beginning + /// + /// print(x.index_of("screen", 8)); // prints 10: 3 * 10 > 20 + /// ``` #[rhai_fn(name = "index_of", return_raw, pure)] - pub fn index_of_with_fn_name_filter_starting_from( + pub fn index_of_by_fn_name_starting_from( ctx: NativeCallContext, array: &mut Array, filter: &str, @@ -500,6 +1058,24 @@ pub mod array_functions { ) -> RhaiResultOf { index_of_filter_starting_from(ctx, array, FnPtr::new(filter)?, start) } + /// Return `true` if any element in the array that returns `true` when applied the `filter` function. + /// + /// # Function Parameters + /// + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5]; + /// + /// print(x.some(|v| v > 3)); // prints true + /// + /// print(x.some(|v| v > 10)); // prints false + /// + /// print(x.some(|v, i| i > v)); // prints true + /// ``` #[rhai_fn(return_raw, pure)] pub fn some(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf { if array.is_empty() { @@ -534,14 +1110,59 @@ pub mod array_functions { Ok(false) } + /// Return `true` if any element in the array that returns `true` when applied a function named + /// by `filter`. + /// + /// # Function Parameters + /// + /// A function with the same name as the value of `filter` must exist taking these parameters: + /// + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// fn large(x) { x > 3 } + /// + /// fn huge(x) { x > 10 } + /// + /// fn screen(x, i) { i > x } + /// + /// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5]; + /// + /// print(x.some("large")); // prints true + /// + /// print(x.some("huge")); // prints false + /// + /// print(x.some("screen")); // prints true + /// ``` #[rhai_fn(name = "some", return_raw, pure)] - pub fn some_with_fn_name( + pub fn some_by_fn_name( ctx: NativeCallContext, array: &mut Array, filter: &str, ) -> RhaiResultOf { some(ctx, array, FnPtr::new(filter)?) } + /// Return `true` if all elements in the array return `true` when applied the `filter` function. + /// + /// # Function Parameters + /// + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5]; + /// + /// print(x.all(|v| v > 3)); // prints false + /// + /// print(x.all(|v| v > 1)); // prints true + /// + /// print(x.all(|v, i| i > v)); // prints false + /// ``` #[rhai_fn(return_raw, pure)] pub fn all(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf { if array.is_empty() { @@ -576,18 +1197,76 @@ pub mod array_functions { Ok(true) } + /// Return `true` if all elements in the array return `true` when applied a function named by `filter`. + /// + /// # Function Parameters + /// + /// A function with the same name as the value of `filter` must exist taking these parameters: + /// + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5]; + /// + /// print(x.all(|v| v > 3)); // prints false + /// + /// print(x.all(|v| v > 1)); // prints true + /// + /// print(x.all(|v, i| i > v)); // prints false + /// ``` #[rhai_fn(name = "all", return_raw, pure)] - pub fn all_with_fn_name( + pub fn all_by_fn_name( ctx: NativeCallContext, array: &mut Array, filter: &str, ) -> RhaiResultOf { all(ctx, array, FnPtr::new(filter)?) } + /// Remove duplicated _consecutive_ elements from the array. + /// + /// The operator `==` is used to compare elements and must be defined, + /// otherwise `false` is assumed. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 2, 2, 3, 4, 3, 3, 2, 1]; + /// + /// x.dedup(); + /// + /// print(x); // prints "[1, 2, 3, 4, 3, 2, 1]" + /// ``` #[rhai_fn(return_raw)] pub fn dedup(ctx: NativeCallContext, array: &mut Array) -> RhaiResultOf<()> { - dedup_with_fn_name(ctx, array, OP_EQUALS) + let comparer = FnPtr::new_unchecked(OP_EQUALS, StaticVec::new_const()); + dedup_by_comparer(ctx, array, comparer) } + /// Remove duplicated _consecutive_ elements from the array that return `true` when applied the + /// `comparer` function. + /// + /// No element is removed if the correct `comparer` function does not exist. + /// + /// # Function Parameters + /// + /// * `element1`: copy of the current array element to compare + /// * `element2`: copy of the next array element to compare + /// + /// ## Return Value + /// + /// `true` if `element1 == element2`, otherwise `false`. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 2, 2, 3, 1, 2, 3, 4, 3, 3, 2, 1]; + /// + /// x.dedup(|a, b| a >= b); + /// + /// print(x); // prints "[1, 2, 3, 4]" + /// ``` #[rhai_fn(name = "dedup", return_raw)] pub fn dedup_by_comparer( ctx: NativeCallContext, @@ -600,7 +1279,7 @@ pub mod array_functions { array.dedup_by(|x, y| { comparer - .call_raw(&ctx, None, [x.clone(), y.clone()]) + .call_raw(&ctx, None, [y.clone(), x.clone()]) .unwrap_or_else(|_| Dynamic::FALSE) .as_bool() .unwrap_or(false) @@ -608,26 +1287,123 @@ pub mod array_functions { Ok(()) } + /// Remove duplicated _consecutive_ elements from the array that return `true` when applied a + /// function named by `comparer`. + /// + /// No element is removed if the correct `comparer` function does not exist. + /// + /// # Function Parameters + /// + /// * `element1`: copy of the current array element to compare + /// * `element2`: copy of the next array element to compare + /// + /// ## Return Value + /// + /// `true` if `element1 == element2`, otherwise `false`. + /// + /// # Example + /// + /// ```rhai + /// fn declining(a, b) { a >= b } + /// + /// let x = [1, 2, 2, 2, 3, 1, 2, 3, 4, 3, 3, 2, 1]; + /// + /// x.dedup("declining"); + /// + /// print(x); // prints "[1, 2, 3, 4]" + /// ``` #[rhai_fn(name = "dedup", return_raw)] - fn dedup_with_fn_name( + pub fn dedup_by_fn_name( ctx: NativeCallContext, array: &mut Array, comparer: &str, ) -> RhaiResultOf<()> { dedup_by_comparer(ctx, array, FnPtr::new(comparer)?) } + /// Reduce an array by iterating through all elements while applying the `reducer` function. + /// + /// # Function Parameters + /// + /// * `result`: accumulated result, initially `()` + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.reduce(|r, v| v + if r == () { 0 } else { r }); + /// + /// print(y); // prints 15 + /// + /// let y = x.reduce(|r, v, i| v + i + if r == () { 0 } else { r }); + /// + /// print(y); // prints 25 + /// ``` #[rhai_fn(return_raw, pure)] pub fn reduce(ctx: NativeCallContext, array: &mut Array, reducer: FnPtr) -> RhaiResult { reduce_with_initial(ctx, array, reducer, Dynamic::UNIT) } + /// Reduce an array by iterating through all elements while applying a function named by `reducer`. + /// + /// # Function Parameters + /// + /// A function with the same name as the value of `reducer` must exist taking these parameters: + /// + /// * `result`: accumulated result, initially `()` + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// fn process(r, x) { + /// x + if r == () { 0 } else { r } + /// } + /// fn process_extra(r, x, i) { + /// x + i + if r == () { 0 } else { r } + /// } + /// + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.reduce("process"); + /// + /// print(y); // prints 15 + /// + /// let y = x.reduce("process_extra"); + /// + /// print(y); // prints 25 + /// ``` #[rhai_fn(name = "reduce", return_raw, pure)] - pub fn reduce_with_fn_name( + pub fn reduce_by_fn_name( ctx: NativeCallContext, array: &mut Array, reducer: &str, ) -> RhaiResult { reduce(ctx, array, FnPtr::new(reducer)?) } + /// Reduce an array by iterating through all elements while applying the `reducer` function. + /// + /// # Function Parameters + /// + /// * `result`: accumulated result, starting with the value of `initial` + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.reduce(|r, v| v + r, 5); + /// + /// print(y); // prints 20 + /// + /// let y = x.reduce(|r, v, i| v + i + r, 5); + /// + /// print(y); // prints 30 + /// ``` #[rhai_fn(name = "reduce", return_raw, pure)] pub fn reduce_with_initial( ctx: NativeCallContext, @@ -666,8 +1442,35 @@ pub mod array_functions { Ok(result) } + /// Reduce an array by iterating through all elements while applying a function named by `reducer`. + /// + /// # Function Parameters + /// + /// A function with the same name as the value of `reducer` must exist taking these parameters: + /// + /// * `result`: accumulated result, starting with the value of `initial` + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// fn process(r, x) { x + r } + /// + /// fn process_extra(r, x, i) { x + i + r } + /// + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.reduce("process", 5); + /// + /// print(y); // prints 20 + /// + /// let y = x.reduce("process_extra", 5); + /// + /// print(y); // prints 30 + /// ``` #[rhai_fn(name = "reduce", return_raw, pure)] - pub fn reduce_with_fn_name_with_initial( + pub fn reduce_by_fn_name_with_initial( ctx: NativeCallContext, array: &mut Array, reducer: &str, @@ -675,18 +1478,93 @@ pub mod array_functions { ) -> RhaiResult { reduce_with_initial(ctx, array, FnPtr::new(reducer)?, initial) } + /// Reduce an array by iterating through all elements, in _reverse_ order, + /// while applying the `reducer` function. + /// + /// # Function Parameters + /// + /// * `result`: accumulated result, initially `()` + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.reduce_rev(|r, v| v + if r == () { 0 } else { r }); + /// + /// print(y); // prints 15 + /// + /// let y = x.reduce_rev(|r, v, i| v + i + if r == () { 0 } else { r }); + /// + /// print(y); // prints 25 + /// ``` #[rhai_fn(return_raw, pure)] pub fn reduce_rev(ctx: NativeCallContext, array: &mut Array, reducer: FnPtr) -> RhaiResult { reduce_rev_with_initial(ctx, array, reducer, Dynamic::UNIT) } + /// Reduce an array by iterating through all elements, in _reverse_ order, + /// while applying a function named by `reducer`. + /// + /// # Function Parameters + /// + /// A function with the same name as the value of `reducer` must exist taking these parameters: + /// + /// * `result`: accumulated result, initially `()` + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// fn process(r, x) { + /// x + if r == () { 0 } else { r } + /// } + /// fn process_extra(r, x, i) { + /// x + i + if r == () { 0 } else { r } + /// } + /// + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.reduce_rev("process"); + /// + /// print(y); // prints 15 + /// + /// let y = x.reduce_rev("process_extra"); + /// + /// print(y); // prints 25 + /// ``` #[rhai_fn(name = "reduce_rev", return_raw, pure)] - pub fn reduce_rev_with_fn_name( + pub fn reduce_rev_by_fn_name( ctx: NativeCallContext, array: &mut Array, reducer: &str, ) -> RhaiResult { reduce_rev(ctx, array, FnPtr::new(reducer)?) } + /// Reduce an array by iterating through all elements, in _reverse_ order, + /// while applying the `reducer` function. + /// + /// # Function Parameters + /// + /// * `result`: accumulated result, starting with the value of `initial` + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.reduce_rev(|r, v| v + r, 5); + /// + /// print(y); // prints 20 + /// + /// let y = x.reduce_rev(|r, v, i| v + i + r, 5); + /// + /// print(y); // prints 30 + /// ``` #[rhai_fn(name = "reduce_rev", return_raw, pure)] pub fn reduce_rev_with_initial( ctx: NativeCallContext, @@ -726,8 +1604,36 @@ pub mod array_functions { Ok(result) } + /// Reduce an array by iterating through all elements, in _reverse_ order, + /// while applying a function named by `reducer`. + /// + /// # Function Parameters + /// + /// A function with the same name as the value of `reducer` must exist taking these parameters: + /// + /// * `result`: accumulated result, starting with the value of `initial` + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// fn process(r, x) { x + r } + /// + /// fn process_extra(r, x, i) { x + i + r } + /// + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.reduce_rev("process", 5); + /// + /// print(y); // prints 20 + /// + /// let y = x.reduce_rev("process_extra", 5); + /// + /// print(y); // prints 30 + /// ``` #[rhai_fn(name = "reduce_rev", return_raw, pure)] - pub fn reduce_rev_with_fn_name_with_initial( + pub fn reduce_rev_by_fn_name_with_initial( ctx: NativeCallContext, array: &mut Array, reducer: &str, @@ -735,14 +1641,29 @@ pub mod array_functions { ) -> RhaiResult { reduce_rev_with_initial(ctx, array, FnPtr::new(reducer)?, initial) } - #[rhai_fn(name = "sort", return_raw)] - pub fn sort_with_fn_name( - ctx: NativeCallContext, - array: &mut Array, - comparer: &str, - ) -> RhaiResultOf<()> { - sort(ctx, array, FnPtr::new(comparer)?) - } + /// Sort the array based on applying the `comparer` function. + /// + /// # Function Parameters + /// + /// * `element1`: copy of the current array element to compare + /// * `element2`: copy of the next array element to compare + /// + /// ## Return Value + /// + /// * Any integer > 0 if `element1 > element2` + /// * Zero if `element1 == element2` + /// * Any integer < 0 if `element1 < element2` + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10]; + /// + /// // Do comparisons in reverse + /// x.sort(|a, b| if a > b { -1 } else if a < b { 1 } else { 0 }); + /// + /// print(x); // prints "[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]" + /// ``` #[rhai_fn(return_raw)] pub fn sort(ctx: NativeCallContext, array: &mut Array, comparer: FnPtr) -> RhaiResultOf<()> { if array.len() <= 1 { @@ -765,6 +1686,70 @@ pub mod array_functions { Ok(()) } + /// Sort the array based on applying a function named by `comparer`. + /// + /// # Function Parameters + /// + /// A function with the same name as the value of `comparer` must exist taking these parameters: + /// + /// * `element1`: copy of the current array element to compare + /// * `element2`: copy of the next array element to compare + /// + /// ## Return Value + /// + /// * Any integer > 0 if `element1 > element2` + /// * Zero if `element1 == element2` + /// * Any integer < 0 if `element1 < element2` + /// + /// # Example + /// + /// ```rhai + /// fn reverse(a, b) { + /// if a > b { + /// -1 + /// } else if a < b { + /// 1 + /// } else { + /// 0 + /// } + /// } + /// let x = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10]; + /// + /// x.sort("reverse"); + /// + /// print(x); // prints "[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]" + /// ``` + #[rhai_fn(name = "sort", return_raw)] + pub fn sort_by_fn_name( + ctx: NativeCallContext, + array: &mut Array, + comparer: &str, + ) -> RhaiResultOf<()> { + sort(ctx, array, FnPtr::new(comparer)?) + } + /// Sort the array. + /// + /// All elements in the array must be of the same data type. + /// + /// # Supported Data Types + /// + /// * integer numbers + /// * floating-point numbers + /// * decimal numbers + /// * characters + /// * strings + /// * booleans + /// * `()` + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10]; + /// + /// x.sort(); + /// + /// print(x); // prints "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]" + /// ``` #[rhai_fn(name = "sort", return_raw)] pub fn sort_with_builtin(array: &mut Array) -> RhaiResultOf<()> { if array.len() <= 1 { @@ -837,6 +1822,31 @@ pub mod array_functions { Ok(()) } + /// Remove all elements in the array that returns `true` when applied the `filter` function and + /// return them as a new array. + /// + /// # Function Parameters + /// + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.drain(|v| v < 3); + /// + /// print(x); // prints "[3, 4, 5]" + /// + /// print(y); // prints "[1, 2]" + /// + /// let z = x.drain(|v, i| v + i > 5); + /// + /// print(x); // prints "[3, 4]" + /// + /// print(z); // prints "[5]" + /// ``` #[rhai_fn(return_raw)] pub fn drain(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf { if array.is_empty() { @@ -880,26 +1890,120 @@ pub mod array_functions { Ok(drained) } + /// Remove all elements in the array that returns `true` when applied a function named by `filter` + /// and return them as a new array. + /// + /// # Function Parameters + /// + /// A function with the same name as the value of `filter` must exist taking these parameters: + /// + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// fn small(x) { x < 3 } + /// + /// fn screen(x, i) { x + i > 5 } + /// + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.drain("small"); + /// + /// print(x); // prints "[3, 4, 5]" + /// + /// print(y); // prints "[1, 2]" + /// + /// let z = x.drain("screen"); + /// + /// print(x); // prints "[3, 4]" + /// + /// print(z); // prints "[5]" + /// ``` #[rhai_fn(name = "drain", return_raw)] - pub fn drain_with_fn_name( + pub fn drain_by_fn_name( ctx: NativeCallContext, array: &mut Array, filter: &str, ) -> RhaiResultOf { drain(ctx, array, FnPtr::new(filter)?) } + /// Remove all elements in the array within an exclusive range and return them as a new array. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.drain(1..3); + /// + /// print(x); // prints "[1, 4, 5]" + /// + /// print(y); // prints "[2, 3]" + /// + /// let z = x.drain(2..3); + /// + /// print(x); // prints "[1, 4]" + /// + /// print(z); // prints "[5]" + /// ``` #[rhai_fn(name = "drain")] pub fn drain_exclusive_range(array: &mut Array, range: ExclusiveRange) -> Array { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); drain_range(array, start, end - start) } + /// Remove all elements in the array within an inclusive range and return them as a new array. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.drain(1..=2); + /// + /// print(x); // prints "[1, 4, 5]" + /// + /// print(y); // prints "[2, 3]" + /// + /// let z = x.drain(2..=2); + /// + /// print(x); // prints "[1, 4]" + /// + /// print(z); // prints "[5]" + /// ``` #[rhai_fn(name = "drain")] pub fn drain_inclusive_range(array: &mut Array, range: InclusiveRange) -> Array { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); drain_range(array, start, end - start + 1) } + /// Remove all elements within a portion of the array and return them as a new array. + /// + /// * If `start` < 0, position counts from the end of the array (`-1` is the last element). + /// * If `start` < -length of array, position counts from the beginning of the array. + /// * If `start` ≥ length of array, no element is removed and an empty array is returned. + /// * If `len` ≤ 0, no element is removed and an empty array is returned. + /// * If `start` position + `len` ≥ length of array, entire portion of the array after the `start` position is removed and returned. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.drain(1, 2); + /// + /// print(x); // prints "[1, 4, 5]" + /// + /// print(y); // prints "[2, 3]" + /// + /// let z = x.drain(-1, 1); + /// + /// print(x); // prints "[1, 4]" + /// + /// print(z); // prints "[5]" + /// ``` #[rhai_fn(name = "drain")] pub fn drain_range(array: &mut Array, start: INT, len: INT) -> Array { if array.is_empty() || len <= 0 { @@ -914,6 +2018,31 @@ pub mod array_functions { array.drain(start..start + len).collect() } } + /// Remove all elements in the array that do not return `true` when applied the `filter` + /// function and return them as a new array. + /// + /// # Function Parameters + /// + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.retain(|v| v >= 3); + /// + /// print(x); // prints "[3, 4, 5]" + /// + /// print(y); // prints "[1, 2]" + /// + /// let z = x.retain(|v, i| v + i <= 5); + /// + /// print(x); // prints "[3, 4]" + /// + /// print(z); // prints "[5]" + /// ``` #[rhai_fn(return_raw)] pub fn retain(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf { if array.is_empty() { @@ -957,26 +2086,120 @@ pub mod array_functions { Ok(drained) } + /// Remove all elements in the array that do not return `true` when applied a function named by + /// `filter` and return them as a new array. + /// + /// # Function Parameters + /// + /// A function with the same name as the value of `filter` must exist taking these parameters: + /// + /// * `element`: copy of array element + /// * `index` _(optional)_: current index in the array + /// + /// # Example + /// + /// ```rhai + /// fn large(x) { x >= 3 } + /// + /// fn screen(x, i) { x + i <= 5 } + /// + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.retain("large"); + /// + /// print(x); // prints "[3, 4, 5]" + /// + /// print(y); // prints "[1, 2]" + /// + /// let z = x.retain("screen"); + /// + /// print(x); // prints "[3, 4]" + /// + /// print(z); // prints "[5]" + /// ``` #[rhai_fn(name = "retain", return_raw)] - pub fn retain_with_fn_name( + pub fn retain_by_fn_name( ctx: NativeCallContext, array: &mut Array, filter: &str, ) -> RhaiResultOf { retain(ctx, array, FnPtr::new(filter)?) } + /// Remove all elements in the array not within an exclusive range and return them as a new array. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.retain(1..4); + /// + /// print(x); // prints "[2, 3, 4]" + /// + /// print(y); // prints "[1, 5]" + /// + /// let z = x.retain(1..3); + /// + /// print(x); // prints "[3, 4]" + /// + /// print(z); // prints "[1]" + /// ``` #[rhai_fn(name = "retain")] pub fn retain_exclusive_range(array: &mut Array, range: ExclusiveRange) -> Array { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); retain_range(array, start, end - start) } + /// Remove all elements in the array not within an inclusive range and return them as a new array. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.retain(1..=3); + /// + /// print(x); // prints "[2, 3, 4]" + /// + /// print(y); // prints "[1, 5]" + /// + /// let z = x.retain(1..=2); + /// + /// print(x); // prints "[3, 4]" + /// + /// print(z); // prints "[1]" + /// ``` #[rhai_fn(name = "retain")] pub fn retain_inclusive_range(array: &mut Array, range: InclusiveRange) -> Array { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); retain_range(array, start, end - start + 1) } + /// Remove all elements not within a portion of the array and return them as a new array. + /// + /// * If `start` < 0, position counts from the end of the array (`-1` is the last element). + /// * If `start` < -length of array, position counts from the beginning of the array. + /// * If `start` ≥ length of array, all elements are removed returned. + /// * If `len` ≤ 0, all elements are removed and returned. + /// * If `start` position + `len` ≥ length of array, entire portion of the array before the `start` position is removed and returned. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// let y = x.retain(1, 2); + /// + /// print(x); // prints "[2, 3]" + /// + /// print(y); // prints "[1, 4, 5]" + /// + /// let z = x.retain(-1, 1); + /// + /// print(x); // prints "[3]" + /// + /// print(z); // prints "[2]" + /// ``` #[rhai_fn(name = "retain")] pub fn retain_range(array: &mut Array, start: INT, len: INT) -> Array { if array.is_empty() || len <= 0 { @@ -994,6 +2217,22 @@ pub mod array_functions { drained } } + /// Return `true` if two arrays are equal (i.e. all elements are equal and in the same order). + /// + /// The operator `==` is used to compare elements and must be defined, + /// otherwise `false` is assumed. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// let y = [1, 2, 3, 4, 5]; + /// let z = [1, 2, 3, 4]; + /// + /// print(x == y); // prints true + /// + /// print(x == z); // prints false + /// ``` #[rhai_fn(name = "==", return_raw, pure)] pub fn equals(ctx: NativeCallContext, array1: &mut Array, array2: Array) -> RhaiResultOf { if array1.len() != array2.len() { @@ -1028,6 +2267,22 @@ pub mod array_functions { Ok(true) } + /// Return `true` if two arrays are not-equal (i.e. any element not equal or not in the same order). + /// + /// The operator `==` is used to compare elements and must be defined, + /// otherwise `false` is assumed. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// let y = [1, 2, 3, 4, 5]; + /// let z = [1, 2, 3, 4]; + /// + /// print(x != y); // prints false + /// + /// print(x != z); // prints true + /// ``` #[rhai_fn(name = "!=", return_raw, pure)] pub fn not_equals( ctx: NativeCallContext, diff --git a/src/packages/bit_field.rs b/src/packages/bit_field.rs index 9b0e8bd9..4bd5ec08 100644 --- a/src/packages/bit_field.rs +++ b/src/packages/bit_field.rs @@ -21,6 +21,21 @@ def_package! { mod bit_field_functions { const BITS: usize = std::mem::size_of::() * 8; + /// Return `true` if the specified `bit` in the number is set. + /// + /// If `bit` < 0, position counts from the MSB (Most Significant Bit). + /// + /// # Example + /// + /// ```rhai + /// let x = 123456; + /// + /// print(x.get_bit(5)); // prints false + /// + /// print(x.get_bit(6)); // prints true + /// + /// print(x.get_bit(-48)); // prints true on 64-bit + /// ``` #[rhai_fn(return_raw)] pub fn get_bit(value: INT, bit: INT) -> RhaiResultOf { let bit = calc_index(BITS, bit, true, || { @@ -29,6 +44,28 @@ mod bit_field_functions { Ok((value & (1 << bit)) != 0) } + /// Set the specified `bit` in the number if the new value is `true`. + /// Clear the `bit` if the new value is `false`. + /// + /// If `bit` < 0, position counts from the MSB (Most Significant Bit). + /// + /// # Example + /// + /// ```rhai + /// let x = 123456; + /// + /// x.set_bit(5, true); + /// + /// print(x); // prints 123488 + /// + /// x.set_bit(6, false); + /// + /// print(x); // prints 123424 + /// + /// x.set_bit(-48, false); + /// + /// print(x); // prints 57888 on 64-bit + /// ``` #[rhai_fn(return_raw)] pub fn set_bit(value: &mut INT, bit: INT, new_value: bool) -> RhaiResultOf<()> { let bit = calc_index(BITS, bit, true, || { @@ -44,26 +81,57 @@ mod bit_field_functions { Ok(()) } + /// Return an exclusive range of bits in the number as a new number. + /// + /// # Example + /// + /// ```rhai + /// let x = 123456; + /// + /// print(x.get_bits(5..10)); // print 18 + /// ``` #[rhai_fn(name = "get_bits", return_raw)] pub fn get_bits_range(value: INT, range: ExclusiveRange) -> RhaiResultOf { let from = INT::max(range.start, 0); let to = INT::max(range.end, from); get_bits(value, from, to - from) } + /// Return an inclusive range of bits in the number as a new number. + /// + /// # Example + /// + /// ```rhai + /// let x = 123456; + /// + /// print(x.get_bits(5..=9)); // print 18 + /// ``` #[rhai_fn(name = "get_bits", return_raw)] pub fn get_bits_range_inclusive(value: INT, range: InclusiveRange) -> RhaiResultOf { let from = INT::max(*range.start(), 0); let to = INT::max(*range.end(), from - 1); get_bits(value, from, to - from + 1) } + /// Return a portion of bits in the number as a new number. + /// + /// * If `start` < 0, position counts from the MSB (Most Significant Bit). + /// * If `bits` ≤ 0, zero is returned. + /// * If `start` position + `bits` ≥ total number of bits, the bits after the `start` position are returned. + /// + /// # Example + /// + /// ```rhai + /// let x = 123456; + /// + /// print(x.get_bits(5, 8)); // print 18 + /// ``` #[rhai_fn(return_raw)] - pub fn get_bits(value: INT, bit: INT, bits: INT) -> RhaiResultOf { + pub fn get_bits(value: INT, start: INT, bits: INT) -> RhaiResultOf { if bits <= 0 { return Ok(0); } - let bit = calc_index(BITS, bit, true, || { - ERR::ErrorBitFieldBounds(BITS, bit, Position::NONE).into() + let bit = calc_index(BITS, start, true, || { + ERR::ErrorBitFieldBounds(BITS, start, Position::NONE).into() })?; let bits = if bit + bits as usize > BITS { @@ -81,6 +149,17 @@ mod bit_field_functions { Ok(((value & (mask << bit)) >> bit) & mask) } + /// Replace an exclusive range of bits in the number with a new value. + /// + /// # Example + /// + /// ```rhai + /// let x = 123456; + /// + /// x.set_bits(5..10, 42); + /// + /// print(x); // print 123200 + /// ``` #[rhai_fn(name = "set_bits", return_raw)] pub fn set_bits_range( value: &mut INT, @@ -91,6 +170,17 @@ mod bit_field_functions { let to = INT::max(range.end, from); set_bits(value, from, to - from, new_value) } + /// Replace an inclusive range of bits in the number with a new value. + /// + /// # Example + /// + /// ```rhai + /// let x = 123456; + /// + /// x.set_bits(5..=9, 42); + /// + /// print(x); // print 123200 + /// ``` #[rhai_fn(name = "set_bits", return_raw)] pub fn set_bits_range_inclusive( value: &mut INT, @@ -101,6 +191,25 @@ mod bit_field_functions { let to = INT::max(*range.end(), from - 1); set_bits(value, from, to - from + 1, new_value) } + /// Replace a portion of bits in the number with a new value. + /// + /// * If `start` < 0, position counts from the MSB (Most Significant Bit). + /// * If `bits` ≤ 0, the number is not modified. + /// * If `start` position + `bits` ≥ total number of bits, the bits after the `start` position are replaced. + /// + /// # Example + /// + /// ```rhai + /// let x = 123456; + /// + /// x.set_bits(5, 8, 42); + /// + /// print(x); // prints 124224 + /// + /// x.set_bits(-16, 10, 42); + /// + /// print(x); // prints 11821949021971776 on 64-bit + /// ``` #[rhai_fn(return_raw)] pub fn set_bits(value: &mut INT, bit: INT, bits: INT, new_value: INT) -> RhaiResultOf<()> { if bits <= 0 { diff --git a/src/packages/blob_basic.rs b/src/packages/blob_basic.rs index ff14fe3c..ca240df3 100644 --- a/src/packages/blob_basic.rs +++ b/src/packages/blob_basic.rs @@ -1,7 +1,7 @@ #![cfg(not(feature = "no_index"))] #![allow(non_snake_case)] -use crate::eval::calc_offset_len; +use crate::eval::{calc_index, calc_offset_len}; use crate::plugin::*; use crate::{ def_package, Blob, Dynamic, ExclusiveRange, InclusiveRange, NativeCallContext, Position, @@ -14,12 +14,26 @@ use std::{any::TypeId, mem}; #[cfg(not(feature = "no_float"))] use crate::FLOAT; +const INT_BYTES: usize = mem::size_of::(); + +#[cfg(not(feature = "no_float"))] +const FLOAT_BYTES: usize = mem::size_of::(); + def_package! { /// Package of basic BLOB utilities. crate::BasicBlobPackage => |lib| { lib.standard = true; combine_with_exported_module!(lib, "blob", blob_functions); + combine_with_exported_module!(lib, "parse_int", parse_int_functions); + combine_with_exported_module!(lib, "write_int", write_int_functions); + combine_with_exported_module!(lib, "write_string", write_string_functions); + + #[cfg(not(feature = "no_float"))] + { + combine_with_exported_module!(lib, "parse_float", parse_float_functions); + combine_with_exported_module!(lib, "write_float", write_float_functions); + } // Register blob iterator lib.set_iterable::(); @@ -28,13 +42,38 @@ def_package! { #[export_module] pub mod blob_functions { + /// Return a new, empty BLOB. pub const fn blob() -> Blob { Blob::new() } + /// Return a new BLOB of the specified length, filled with zeros. + /// + /// If `len` ≤ 0, an empty BLOB is returned. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(10); + /// + /// print(b); // prints "[0000000000000000 0000]" + /// ``` #[rhai_fn(name = "blob", return_raw)] pub fn blob_with_capacity(ctx: NativeCallContext, len: INT) -> RhaiResultOf { blob_with_capacity_and_value(ctx, len, 0) } + /// Return a new BLOB of the specified length, filled with copies of the initial `value`. + /// + /// If `len` ≤ 0, an empty BLOB is returned. + /// + /// Only the lower 8 bits of the initial `value` are used; all other bits are ignored. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(10, 0x42); + /// + /// print(b); // prints "[4242424242424242 4242]" + /// ``` #[rhai_fn(name = "blob", return_raw)] pub fn blob_with_capacity_and_value( ctx: NativeCallContext, @@ -56,14 +95,40 @@ pub mod blob_functions { blob.resize(len, (value & 0x000000ff) as u8); Ok(blob) } + /// Return the length of the BLOB. #[rhai_fn(name = "len", get = "len", pure)] pub fn len(blob: &mut Blob) -> INT { blob.len() as INT } - pub fn push(blob: &mut Blob, item: INT) { - let item = (item & 0x000000ff) as u8; - blob.push(item); + /// Add a new byte `value` to the end of the BLOB. + /// + /// Only the lower 8 bits of the `value` are used; all other bits are ignored. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b.push(0x42); + /// + /// print(b); // prints "[42]" + /// ``` + pub fn push(blob: &mut Blob, value: INT) { + let value = (value & 0x000000ff) as u8; + blob.push(value); } + /// Add another BLOB to the end of the BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(5, 0x42); + /// let b2 = blob(3, 0x11); + /// + /// b1.push(b2); + /// + /// print(b1); // prints "[4242424242111111]" + /// ``` pub fn append(blob: &mut Blob, y: Blob) { if !y.is_empty() { if blob.is_empty() { @@ -73,6 +138,18 @@ pub mod blob_functions { } } } + /// Add another BLOB to the end of the BLOB, returning it as a new BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(5, 0x42); + /// let b2 = blob(3, 0x11); + /// + /// print(b1 + b2); // prints "[4242424242111111]" + /// + /// print(b1); // prints "[4242424242]" + /// ``` #[rhai_fn(name = "+")] pub fn concat(blob1: Blob, blob2: Blob) -> Blob { if !blob2.is_empty() { @@ -87,29 +164,65 @@ pub mod blob_functions { blob1 } } - pub fn insert(blob: &mut Blob, index: INT, item: INT) { - let item = (item & 0x000000ff) as u8; + /// Add a byte `value` to the BLOB at a particular `index` position. + /// + /// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `index` < -length of BLOB, the byte value is added to the beginning of the BLOB. + /// * If `index` ≥ length of BLOB, the byte value is appended to the end of the BLOB. + /// + /// Only the lower 8 bits of the `value` are used; all other bits are ignored. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(5, 0x42); + /// + /// b.insert(2, 0x18); + /// + /// print(b); // prints "[4242184242]" + /// ``` + pub fn insert(blob: &mut Blob, index: INT, value: INT) { + let value = (value & 0x000000ff) as u8; if blob.is_empty() { - blob.push(item); + blob.push(value); return; } let (index, _) = calc_offset_len(blob.len(), index, 0); if index >= blob.len() { - blob.push(item); + blob.push(value); } else { - blob.insert(index, item); + blob.insert(index, value); } } + /// Pad the BLOB to at least the specified length with copies of a specified byte `value`. + /// + /// If `len` ≤ length of BLOB, no padding is done. + /// + /// Only the lower 8 bits of the `value` are used; all other bits are ignored. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(3, 0x42); + /// + /// b.pad(5, 0x18) + /// + /// print(b); // prints "[4242421818]" + /// + /// b.pad(3, 0xab) + /// + /// print(b); // prints "[4242421818]" + /// ``` #[rhai_fn(return_raw)] - pub fn pad(ctx: NativeCallContext, blob: &mut Blob, len: INT, item: INT) -> RhaiResultOf<()> { + pub fn pad(ctx: NativeCallContext, blob: &mut Blob, len: INT, value: INT) -> RhaiResultOf<()> { if len <= 0 { return Ok(()); } - let item = (item & 0x000000ff) as u8; + let value = (value & 0x000000ff) as u8; let _ctx = ctx; // Check if blob will be over max size limit @@ -121,11 +234,26 @@ pub mod blob_functions { } if len as usize > blob.len() { - blob.resize(len as usize, item); + blob.resize(len as usize, value); } Ok(()) } + /// Remove the last byte from the BLOB and return it. + /// + /// If the BLOB is empty, zero is returned. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// print(b.pop()); // prints 5 + /// + /// print(b); // prints "[01020304]" + /// ``` pub fn pop(blob: &mut Blob) -> INT { if blob.is_empty() { 0 @@ -133,6 +261,21 @@ pub mod blob_functions { blob.pop().map_or_else(|| 0, |v| v as INT) } } + /// Remove the first byte from the BLOB and return it. + /// + /// If the BLOB is empty, zero is returned. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// print(b.shift()); // prints 1 + /// + /// print(b); // prints "[02030405]" + /// ``` pub fn shift(blob: &mut Blob) -> INT { if blob.is_empty() { 0 @@ -140,18 +283,61 @@ pub mod blob_functions { blob.remove(0) as INT } } - pub fn remove(blob: &mut Blob, len: INT) -> INT { - if len < 0 || (len as usize) >= blob.len() { - 0 - } else { - blob.remove(len as usize) as INT - } + /// Remove the byte at the specified `index` from the BLOB and return it. + /// + /// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `index` < -length of BLOB, zero is returned. + /// * If `index` ≥ length of BLOB, zero is returned. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// print(x.remove(1)); // prints 2 + /// + /// print(x); // prints "[01030405]" + /// + /// print(x.remove(-2)); // prints 4 + /// + /// print(x); // prints "[010305]" + /// ``` + pub fn remove(blob: &mut Blob, index: INT) -> INT { + let index = match calc_index(blob.len(), index, true, || Err(())) { + Ok(n) => n, + Err(_) => return 0, + }; + + blob.remove(index) as INT } + /// Clear the BLOB. pub fn clear(blob: &mut Blob) { if !blob.is_empty() { blob.clear(); } } + /// Cut off the BLOB at the specified length. + /// + /// * If `len` ≤ 0, the BLOB is cleared. + /// * If `len` ≥ length of BLOB, the BLOB is not truncated. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// b.truncate(3); + /// + /// print(b); // prints "[010203]" + /// + /// b.truncate(10); + /// + /// print(b); // prints "[010203]" + /// ``` pub fn truncate(blob: &mut Blob, len: INT) { if !blob.is_empty() { if len >= 0 { @@ -161,6 +347,26 @@ pub mod blob_functions { } } } + /// Cut off the head of the BLOB, leaving a tail of the specified length. + /// + /// * If `len` ≤ 0, the BLOB is cleared. + /// * If `len` ≥ length of BLOB, the BLOB is not modified. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// b.chop(3); + /// + /// print(b); // prints "[030405]" + /// + /// b.chop(10); + /// + /// print(b); // prints "[030405]" + /// ``` pub fn chop(blob: &mut Blob, len: INT) { if !blob.is_empty() { if len <= 0 { @@ -170,23 +376,84 @@ pub mod blob_functions { } } } + /// Reverse the BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// print(b); // prints "[0102030405]" + /// + /// b.reverse(); + /// + /// print(b); // prints "[0504030201]" + /// ``` pub fn reverse(blob: &mut Blob) { if !blob.is_empty() { blob.reverse(); } } + /// Replace an exclusive range of the BLOB with another BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(10, 0x42); + /// let b2 = blob(5, 0x18); + /// + /// b1.splice(1..4, b2); + /// + /// print(b1); // prints "[4218181818184242 42424242]" + /// ``` #[rhai_fn(name = "splice")] pub fn splice_range(blob: &mut Blob, range: ExclusiveRange, replace: Blob) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); splice(blob, start, end - start, replace) } + /// Replace an inclusive range of the BLOB with another BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(10, 0x42); + /// let b2 = blob(5, 0x18); + /// + /// b1.splice(1..=4, b2); + /// + /// print(b1); // prints "[4218181818184242 424242]" + /// ``` #[rhai_fn(name = "splice")] pub fn splice_range_inclusive(blob: &mut Blob, range: InclusiveRange, replace: Blob) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); splice(blob, start, end - start + 1, replace) } + /// Replace a portion of the BLOB with another BLOB. + /// + /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. + /// * If `start` ≥ length of BLOB, the other BLOB is appended to the end of the BLOB. + /// * If `len` ≤ 0, the other BLOB is inserted into the BLOB at the `start` position without replacing anything. + /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is replaced. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(10, 0x42); + /// let b2 = blob(5, 0x18); + /// + /// b1.splice(1, 3, b2); + /// + /// print(b1); // prints "[4218181818184242 42424242]" + /// + /// b1.splice(-5, 4, b2); + /// + /// print(b1); // prints "[4218181818184218 1818181842]" + /// ``` pub fn splice(blob: &mut Blob, start: INT, len: INT, replace: Blob) { if blob.is_empty() { *blob = replace; @@ -201,18 +468,65 @@ pub mod blob_functions { blob.splice(start..start + len, replace); } } + /// Copy an exclusive range of the BLOB and return it as a new BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// print(b.extract(1..3)); // prints "[0203]" + /// + /// print(b); // prints "[0102030405]" + /// ``` #[rhai_fn(name = "extract")] pub fn extract_range(blob: &mut Blob, range: ExclusiveRange) -> Blob { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); extract(blob, start, end - start) } + /// Copy an inclusive range of the BLOB and return it as a new BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// print(b.extract(1..=3)); // prints "[020304]" + /// + /// print(b); // prints "[0102030405]" + /// ``` #[rhai_fn(name = "extract")] pub fn extract_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> Blob { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); extract(blob, start, end - start + 1) } + /// Copy a portion of the BLOB and return it as a new BLOB. + /// + /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. + /// * If `start` ≥ length of BLOB, an empty BLOB is returned. + /// * If `len` ≤ 0, an empty BLOB is returned. + /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is copied and returned. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// print(b.extract(1, 3)); // prints "[020303]" + /// + /// print(b.extract(-3, 2)); // prints "[0304]" + /// + /// print(b); // prints "[0102030405]" + /// ``` pub fn extract(blob: &mut Blob, start: INT, len: INT) -> Blob { if blob.is_empty() || len <= 0 { return Blob::new(); @@ -226,19 +540,59 @@ pub mod blob_functions { blob[start..start + len].to_vec() } } + /// Copy a portion of the BLOB beginning at the `start` position till the end and return it as + /// a new BLOB. + /// + /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `start` < -length of BLOB, the entire BLOB is copied and returned. + /// * If `start` ≥ length of BLOB, an empty BLOB is returned. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// print(b.extract(2)); // prints "[030405]" + /// + /// print(b.extract(-3)); // prints "[030405]" + /// + /// print(b); // prints "[0102030405]" + /// ``` #[rhai_fn(name = "extract")] pub fn extract_tail(blob: &mut Blob, start: INT) -> Blob { extract(blob, start, INT::MAX) } + /// Cut off the BLOB at `index` and return it as a new BLOB. + /// + /// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `index` is zero, the entire BLOB is cut and returned. + /// * If `index` < -length of BLOB, the entire BLOB is cut and returned. + /// * If `index` ≥ length of BLOB, nothing is cut from the BLOB and an empty BLOB is returned. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(); + /// + /// b1 += 1; b1 += 2; b1 += 3; b1 += 4; b1 += 5; + /// + /// let b2 = b1.split(2); + /// + /// print(b2); // prints "[030405]" + /// + /// print(b1); // prints "[0102]" + /// ``` #[rhai_fn(name = "split")] - pub fn split_at(blob: &mut Blob, start: INT) -> Blob { + pub fn split_at(blob: &mut Blob, index: INT) -> Blob { if blob.is_empty() { return Blob::new(); } - let (start, len) = calc_offset_len(blob.len(), start, INT::MAX); + let (index, len) = calc_offset_len(blob.len(), index, INT::MAX); - if start == 0 { + if index == 0 { if len > blob.len() { mem::take(blob) } else { @@ -246,26 +600,95 @@ pub mod blob_functions { result.extend(blob.drain(blob.len() - len..)); result } - } else if start >= blob.len() { + } else if index >= blob.len() { Blob::new() } else { let mut result = Blob::new(); - result.extend(blob.drain(start as usize..)); + result.extend(blob.drain(index as usize..)); result } } + /// Remove all bytes in the BLOB within an exclusive range and return them as a new BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(); + /// + /// b1 += 1; b1 += 2; b1 += 3; b1 += 4; b1 += 5; + /// + /// let b2 = b1.drain(1..3); + /// + /// print(b1); // prints "[010405]" + /// + /// print(b2); // prints "[0203]" + /// + /// let b3 = b1.drain(2..3); + /// + /// print(b1); // prints "[0104]" + /// + /// print(b3); // prints "[05]" + /// ``` #[rhai_fn(name = "drain")] pub fn drain_range(blob: &mut Blob, range: ExclusiveRange) -> Blob { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); drain(blob, start, end - start) } + /// Remove all bytes in the BLOB within an inclusive range and return them as a new BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(); + /// + /// b1 += 1; b1 += 2; b1 += 3; b1 += 4; b1 += 5; + /// + /// let b2 = b1.drain(1..=2); + /// + /// print(b1); // prints "[010405]" + /// + /// print(b2); // prints "[0203]" + /// + /// let b3 = b1.drain(2..=2); + /// + /// print(b1); // prints "[0104]" + /// + /// print(b3); // prints "[05]" + /// ``` #[rhai_fn(name = "drain")] pub fn drain_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> Blob { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); drain(blob, start, end - start + 1) } + /// Remove all bytes within a portion of the BLOB and return them as a new BLOB. + /// + /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. + /// * If `start` ≥ length of BLOB, nothing is removed and an empty BLOB is returned. + /// * If `len` ≤ 0, nothing is removed and an empty BLOB is returned. + /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is removed and returned. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(); + /// + /// b1 += 1; b1 += 2; b1 += 3; b1 += 4; b1 += 5; + /// + /// let b2 = b1.drain(1, 2); + /// + /// print(b1); // prints "[010405]" + /// + /// print(b2); // prints "[0203]" + /// + /// let b3 = b1.drain(-1, 1); + /// + /// print(b3); // prints "[0104]" + /// + /// print(z); // prints "[5]" + /// ``` pub fn drain(blob: &mut Blob, start: INT, len: INT) -> Blob { if blob.is_empty() || len <= 0 { return Blob::new(); @@ -279,18 +702,87 @@ pub mod blob_functions { blob.drain(start..start + len).collect() } } + /// Remove all bytes in the BLOB not within an exclusive range and return them as a new BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(); + /// + /// b1 += 1; b1 += 2; b1 += 3; b1 += 4; b1 += 5; + /// + /// let b2 = b1.retain(1..4); + /// + /// print(b1); // prints "[020304]" + /// + /// print(b2); // prints "[0105]" + /// + /// let b3 = b1.retain(1..3); + /// + /// print(b1); // prints "[0304]" + /// + /// print(b2); // prints "[01]" + /// ``` #[rhai_fn(name = "retain")] pub fn retain_range(blob: &mut Blob, range: ExclusiveRange) -> Blob { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); retain(blob, start, end - start) } + /// Remove all bytes in the BLOB not within an inclusive range and return them as a new BLOB. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(); + /// + /// b1 += 1; b1 += 2; b1 += 3; b1 += 4; b1 += 5; + /// + /// let b2 = b1.retain(1..=3); + /// + /// print(b1); // prints "[020304]" + /// + /// print(b2); // prints "[0105]" + /// + /// let b3 = b1.retain(1..=2); + /// + /// print(b1); // prints "[0304]" + /// + /// print(b2); // prints "[01]" + /// ``` #[rhai_fn(name = "retain")] pub fn retain_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> Blob { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); retain(blob, start, end - start + 1) } + /// Remove all bytes not within a portion of the BLOB and return them as a new BLOB. + /// + /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. + /// * If `start` ≥ length of BLOB, all elements are removed returned. + /// * If `len` ≤ 0, all elements are removed and returned. + /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB before the `start` position is removed and returned. + /// + /// # Example + /// + /// ```rhai + /// let b1 = blob(); + /// + /// b1 += 1; b1 += 2; b1 += 3; b1 += 4; b1 += 5; + /// + /// let b2 = b1.retain(1, 2); + /// + /// print(b1); // prints "[0203]" + /// + /// print(b2); // prints "[010405]" + /// + /// let b3 = b1.retain(-1, 1); + /// + /// print(b1); // prints "[03]" + /// + /// print(b3); // prints "[02]" + /// ``` pub fn retain(blob: &mut Blob, start: INT, len: INT) -> Blob { if blob.is_empty() || len <= 0 { return Blob::new(); @@ -307,7 +799,10 @@ pub mod blob_functions { drained } } +} +#[export_module] +mod parse_int_functions { #[inline] fn parse_int(blob: &mut Blob, start: INT, len: INT, is_le: bool) -> INT { if blob.is_empty() || len <= 0 { @@ -319,8 +814,6 @@ pub mod blob_functions { return 0; } - const INT_BYTES: usize = mem::size_of::(); - let len = usize::min(len, INT_BYTES); let mut buf = [0_u8; INT_BYTES]; @@ -364,7 +857,70 @@ pub mod blob_functions { pub fn parse_be_int(blob: &mut Blob, start: INT, len: INT) -> INT { parse_int(blob, start, len, false) } +} +#[cfg(not(feature = "no_float"))] +#[export_module] +mod parse_float_functions { + #[inline] + fn parse_float(blob: &mut Blob, start: INT, len: INT, is_le: bool) -> FLOAT { + if blob.is_empty() || len <= 0 { + return 0.0; + } + + let (start, len) = calc_offset_len(blob.len(), start, len); + + if len == 0 { + return 0.0; + } + + let len = usize::min(len, FLOAT_BYTES); + + let mut buf = [0_u8; FLOAT_BYTES]; + + buf[..len].copy_from_slice(&blob[start..][..len]); + + if is_le { + FLOAT::from_le_bytes(buf) + } else { + FLOAT::from_be_bytes(buf) + } + } + + #[rhai_fn(name = "parse_le_float")] + pub fn parse_le_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + parse_le_float(blob, start, end - start) + } + #[rhai_fn(name = "parse_le_float")] + pub fn parse_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + parse_le_float(blob, start, end - start + 1) + } + pub fn parse_le_float(blob: &mut Blob, start: INT, len: INT) -> FLOAT { + parse_float(blob, start, len, true) + } + #[rhai_fn(name = "parse_be_float")] + pub fn parse_be_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + parse_be_float(blob, start, end - start) + } + #[rhai_fn(name = "parse_be_float")] + pub fn parse_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + parse_be_float(blob, start, end - start + 1) + } + pub fn parse_be_float(blob: &mut Blob, start: INT, len: INT) -> FLOAT { + parse_float(blob, start, len, false) + } +} + +#[export_module] +mod write_int_functions { #[inline] fn write_int(blob: &mut Blob, start: INT, len: INT, value: INT, is_le: bool) { if blob.is_empty() || len <= 0 { @@ -377,8 +933,6 @@ pub mod blob_functions { return; } - const INT_BYTES: usize = mem::size_of::(); - let len = usize::min(len, INT_BYTES); let buf = if is_le { @@ -389,13 +943,13 @@ pub mod blob_functions { blob[start..][..len].copy_from_slice(&buf[..len]); } - #[rhai_fn(name = "write_le_int")] + #[rhai_fn(name = "write_le")] pub fn write_le_int_range(blob: &mut Blob, range: ExclusiveRange, value: INT) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); write_le_int(blob, start, end - start, value) } - #[rhai_fn(name = "write_le_int")] + #[rhai_fn(name = "write_le")] pub fn write_le_int_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: INT) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); @@ -421,73 +975,11 @@ pub mod blob_functions { pub fn write_be_int(blob: &mut Blob, start: INT, len: INT, value: INT) { write_int(blob, start, len, value, false) } +} - #[cfg(not(feature = "no_float"))] - #[inline] - fn parse_float(blob: &mut Blob, start: INT, len: INT, is_le: bool) -> FLOAT { - if blob.is_empty() || len <= 0 { - return 0.0; - } - - let (start, len) = calc_offset_len(blob.len(), start, len); - - if len == 0 { - return 0.0; - } - - const FLOAT_BYTES: usize = mem::size_of::(); - - let len = usize::min(len, FLOAT_BYTES); - - let mut buf = [0_u8; FLOAT_BYTES]; - - buf[..len].copy_from_slice(&blob[start..][..len]); - - if is_le { - FLOAT::from_le_bytes(buf) - } else { - FLOAT::from_be_bytes(buf) - } - } - - #[cfg(not(feature = "no_float"))] - #[rhai_fn(name = "parse_le_float")] - pub fn parse_le_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT { - let start = INT::max(range.start, 0); - let end = INT::max(range.end, start); - parse_le_float(blob, start, end - start) - } - #[cfg(not(feature = "no_float"))] - #[rhai_fn(name = "parse_le_float")] - pub fn parse_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT { - let start = INT::max(*range.start(), 0); - let end = INT::max(*range.end(), start); - parse_le_float(blob, start, end - start + 1) - } - #[cfg(not(feature = "no_float"))] - pub fn parse_le_float(blob: &mut Blob, start: INT, len: INT) -> FLOAT { - parse_float(blob, start, len, true) - } - #[cfg(not(feature = "no_float"))] - #[rhai_fn(name = "parse_be_float")] - pub fn parse_be_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT { - let start = INT::max(range.start, 0); - let end = INT::max(range.end, start); - parse_be_float(blob, start, end - start) - } - #[cfg(not(feature = "no_float"))] - #[rhai_fn(name = "parse_be_float")] - pub fn parse_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT { - let start = INT::max(*range.start(), 0); - let end = INT::max(*range.end(), start); - parse_be_float(blob, start, end - start + 1) - } - #[cfg(not(feature = "no_float"))] - pub fn parse_be_float(blob: &mut Blob, start: INT, len: INT) -> FLOAT { - parse_float(blob, start, len, false) - } - - #[cfg(not(feature = "no_float"))] +#[cfg(not(feature = "no_float"))] +#[export_module] +mod write_float_functions { #[inline] fn write_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT, is_le: bool) { if blob.is_empty() || len <= 0 { @@ -500,8 +992,6 @@ pub mod blob_functions { return; } - const FLOAT_BYTES: usize = mem::size_of::(); - let len = usize::min(len, FLOAT_BYTES); let buf = if is_le { value.to_le_bytes() @@ -511,44 +1001,42 @@ pub mod blob_functions { blob[start..][..len].copy_from_slice(&buf[..len]); } - #[cfg(not(feature = "no_float"))] - #[rhai_fn(name = "write_le_float")] + #[rhai_fn(name = "write_le")] pub fn write_le_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); write_le_float(blob, start, end - start, value) } - #[cfg(not(feature = "no_float"))] - #[rhai_fn(name = "write_le_float")] + #[rhai_fn(name = "write_le")] pub fn write_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); write_le_float(blob, start, end - start + 1, value) } - #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "write_le")] pub fn write_le_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) { write_float(blob, start, len, value, true) } - #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "write_be")] pub fn write_be_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); write_be_float(blob, start, end - start, value) } - #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "write_be")] pub fn write_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); write_be_float(blob, start, end - start + 1, value) } - #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "write_be")] pub fn write_be_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) { write_float(blob, start, len, value, false) } +} + +#[export_module] +mod write_string_functions { #[inline] fn write_string(blob: &mut Blob, start: INT, len: INT, string: &str, ascii_only: bool) { if len <= 0 || blob.is_empty() || string.is_empty() { diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index 66be2772..48aeb3d8 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -14,122 +14,34 @@ def_package! { #[export_module] mod fn_ptr_functions { + /// Return the name of the function. + /// + /// # Example + /// + /// ```rhai + /// fn double(x) { x * 2 } + /// + /// let f = Fn("double"); + /// + /// print(f.name); // prints "double" + /// ``` #[rhai_fn(name = "name", get = "name", pure)] pub fn name(fn_ptr: &mut FnPtr) -> ImmutableString { fn_ptr.fn_name_raw().into() } + /// Return `true` if the function is an anonymous function. + /// + /// # Example + /// + /// ```rhai + /// let f = |x| x * 2; + /// + /// print(f.is_anonymous); // prints true + /// ``` #[cfg(not(feature = "no_function"))] #[rhai_fn(name = "is_anonymous", get = "is_anonymous", pure)] pub fn is_anonymous(fn_ptr: &mut FnPtr) -> bool { fn_ptr.is_anonymous() } - - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "no_index"))] - #[cfg(not(feature = "no_object"))] - pub fn get_fn_metadata_list(ctx: NativeCallContext) -> crate::Array { - collect_fn_metadata(ctx) - } -} - -#[cfg(not(feature = "no_function"))] -#[cfg(not(feature = "no_index"))] -#[cfg(not(feature = "no_object"))] -fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { - use crate::{ast::ScriptFnDef, Array, Identifier, Map}; - use std::collections::BTreeSet; - - // Create a metadata record for a function. - fn make_metadata( - dict: &BTreeSet, - namespace: Option, - func: &ScriptFnDef, - ) -> Map { - const DICT: &str = "key exists"; - - let mut map = Map::new(); - - if let Some(ns) = namespace { - map.insert(dict.get("namespace").expect(DICT).clone(), ns.into()); - } - map.insert( - dict.get("name").expect(DICT).clone(), - func.name.clone().into(), - ); - map.insert( - dict.get("access").expect(DICT).clone(), - match func.access { - FnAccess::Public => dict.get("public").expect(DICT).clone(), - FnAccess::Private => dict.get("private").expect(DICT).clone(), - } - .into(), - ); - map.insert( - dict.get("is_anonymous").expect(DICT).clone(), - func.name.starts_with(crate::engine::FN_ANONYMOUS).into(), - ); - map.insert( - dict.get("params").expect(DICT).clone(), - func.params - .iter() - .cloned() - .map(Into::into) - .collect::() - .into(), - ); - - map - } - - // Intern strings - let dict: BTreeSet = [ - "namespace", - "name", - "access", - "public", - "private", - "is_anonymous", - "params", - ] - .iter() - .map(|&s| s.into()) - .collect(); - - let mut _list = ctx.iter_namespaces().flat_map(Module::iter_script_fn).fold( - Array::new(), - |mut list, (_, _, _, _, f)| { - list.push(make_metadata(&dict, None, f).into()); - list - }, - ); - - #[cfg(not(feature = "no_module"))] - { - // Recursively scan modules for script-defined functions. - fn scan_module( - list: &mut Array, - dict: &BTreeSet, - namespace: Identifier, - module: &Module, - ) { - module.iter_script_fn().for_each(|(_, _, _, _, f)| { - list.push(make_metadata(dict, Some(namespace.clone()), f).into()) - }); - module.iter_sub_modules().for_each(|(ns, m)| { - let ns = format!( - "{}{}{}", - namespace, - crate::tokenizer::Token::DoubleColon.literal_syntax(), - ns - ); - scan_module(list, dict, ns.into(), m.as_ref()) - }); - } - - ctx.iter_imports_raw() - .for_each(|(ns, m)| scan_module(&mut _list, &dict, ns.clone(), m.as_ref())); - } - - _list } diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index d06931f9..fe844e70 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -246,10 +246,20 @@ macro_rules! reg_range { let _hash = $lib.set_native_fn($x, |from: $y, to: $y| Ok(from..to)); #[cfg(feature = "metadata")] - $lib.update_fn_metadata(_hash, &[ + $lib.update_fn_metadata_with_comments(_hash, [ concat!("from: ", stringify!($y)), concat!("to: ", stringify!($y)), - concat!("Iterator") + concat!("Iterator"), + ], [ + "/// Return an iterator over the range of `from..to`.", + "///", + "/// # Example", + "///", + "/// ```rhai", + "/// for n in range(8, 18) {", + "/// print(n);", + "/// }", + "/// ```" ]); $lib.set_iterator::>(); @@ -261,11 +271,27 @@ macro_rules! reg_range { let _hash = $lib.set_native_fn($x, |from: $y, to: $y, step: $y| StepRange::new(from, to, step)); #[cfg(feature = "metadata")] - $lib.update_fn_metadata(_hash, &[ + $lib.update_fn_metadata_with_comments(_hash, [ concat!("from: ", stringify!($y)), concat!("to: ", stringify!($y)), concat!("step: ", stringify!($y)), concat!("Iterator") + ], [ + "/// Return an iterator over the range of `from..to`, each iterator increasing by `step`.", + "///", + "/// If `from` > `to` and `step` < 0, the iteration goes backwards.", + "///", + "/// # Example", + "///", + "/// ```rhai", + "/// for n in range(8, 18, 3) {", + "/// print(n);", + "/// }", + "///", + "/// for n in range(18, 8, -3) {", + "/// print(n);", + "/// }", + "/// ```" ]); )* }; @@ -359,7 +385,27 @@ def_package! { let _hash = lib.set_native_fn("range", StepFloatRange::new); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["from: FLOAT", "to: FLOAT", "step: FLOAT", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["from: FLOAT", "to: FLOAT", "step: FLOAT", "Iterator"], + [ + "/// Return an iterator over the range of `from..to`, each iterator increasing by `step`.", + "///", + "/// If `from` > `to` and `step` < 0, the iteration goes backwards.", + "///", + "/// # Example", + "///", + "/// ```rhai", + "/// for n in range(8.0, 18.0, 3.0) {", + "/// print(n);", + "/// }", + "///", + "/// for n in range(18.0, 8.0, -3.0) {", + "/// print(n);", + "/// }", + "/// ```" + ] + ); } #[cfg(feature = "decimal")] @@ -421,7 +467,15 @@ def_package! { let _hash = lib.set_native_fn("range", StepDecimalRange::new); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["from: Decimal", "to: Decimal", "step: Decimal", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["from: Decimal", "to: Decimal", "step: Decimal", "Iterator"], + [ + "/// Return an iterator over the range of `from..to`, each iterator increasing by `step`.", + "///", + "/// If `from` > `to` and `step` < 0, the iteration goes backwards.", + ] + ); } // Register string iterator @@ -433,7 +487,21 @@ def_package! { Ok(CharsStream::new(string, from, to - from)) }); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["string: &str", "range: Range", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["string: &str", "range: Range", "Iterator"], + [ + "/// Return an iterator over an exclusive range of characters in the string.", + "///", + "/// # Example", + "///", + "/// ```rhai", + r#"/// for ch in "hello, world!".chars(2..5) {"#, + "/// print(ch);", + "/// }", + "/// ```" + ] + ); let _hash = lib.set_native_fn("chars", |string, range: InclusiveRange| { let from = INT::max(*range.start(), 0); @@ -441,25 +509,105 @@ def_package! { Ok(CharsStream::new(string, from, to-from + 1)) }); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["string: &str", "range: RangeInclusive", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["string: &str", "range: RangeInclusive", "Iterator"], + [ + "/// Return an iterator over an inclusive range of characters in the string.", + "///", + "/// # Example", + "///", + "/// ```rhai", + r#"/// for ch in "hello, world!".chars(2..=6) {"#, + "/// print(ch);", + "/// }", + "/// ```" + ] + ); let _hash = lib.set_native_fn("chars", |string, from, len| Ok(CharsStream::new(string, from, len))); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["string: &str", "from: INT", "len: INT", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["string: &str", "start: INT", "len: INT", "Iterator"], + [ + "/// Return an iterator over a portion of characters in the string.", + "///", + "/// * If `start` < 0, position counts from the end of the string (`-1` is the last character).", + "/// * If `start` < -length of string, position counts from the beginning of the string.", + "/// * If `start` ≥ length of string, an empty iterator is returned.", + "/// * If `len` ≤ 0, an empty iterator is returned.", + "/// * If `start` position + `len` ≥ length of string, all characters of the string after the `start` position are iterated.", + "///", + "/// # Example", + "///", + "/// ```rhai", + r#"/// for ch in "hello, world!".chars(2, 4) {"#, + "/// print(ch);", + "/// }", + "/// ```" + ] + ); let _hash = lib.set_native_fn("chars", |string, from| Ok(CharsStream::new(string, from, INT::MAX))); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["string: &str", "from: INT", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["string: &str", "from: INT", "Iterator"], + [ + "/// Return an iterator over the characters in the string starting from the `start` position.", + "///", + "/// * If `start` < 0, position counts from the end of the string (`-1` is the last character).", + "/// * If `start` < -length of string, position counts from the beginning of the string.", + "/// * If `start` ≥ length of string, an empty iterator is returned.", + "///", + "/// # Example", + "///", + "/// ```rhai", + r#"/// for ch in "hello, world!".chars(2) {"#, + "/// print(ch);", + "/// }", + "/// ```" + ] + ); let _hash = lib.set_native_fn("chars", |string| Ok(CharsStream::new(string, 0, INT::MAX))); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["string: &str", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["string: &str", "Iterator"], + [ + "/// Return an iterator over the characters in the string.", + "///", + "/// # Example", + "///", + "/// ```rhai", + r#"/// for ch in "hello, world!".chars() {"#, + "/// print(ch);", + "/// }", + "/// ```" + ] + ); #[cfg(not(feature = "no_object"))] { let _hash = lib.set_getter_fn("chars", |string: &mut ImmutableString| Ok(CharsStream::new(string, 0, INT::MAX))); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["string: &mut ImmutableString", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["string: &mut ImmutableString", "Iterator"], + [ + "/// Return an iterator over all the characters in the string.", + "///", + "/// # Example", + "///", + "/// ```rhai", + r#"/// for ch in "hello, world!".chars {"#, + "/// print(ch);", + "/// }", + "/// ```" + ] + ); } // Register bit-field iterator @@ -471,7 +619,23 @@ def_package! { BitRange::new(value, from, to - from) }); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["value: INT", "range: Range", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["value: INT", "range: Range", "Iterator"], + [ + "/// Return an iterator over an exclusive range of bits in the number.", + "///", + "/// # Example", + "///", + "/// ```rhai", + "/// let x = 123456;", + "///", + "/// for bit in x.bits(10..24) {", + "/// print(bit);", + "/// }", + "/// ```" + ] + ); let _hash = lib.set_native_fn("bits", |value, range: InclusiveRange| { let from = INT::max(*range.start(), 0); @@ -479,25 +643,111 @@ def_package! { BitRange::new(value, from, to - from + 1) }); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["value: INT", "range: RangeInclusive", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["value: INT", "range: RangeInclusive", "Iterator"], + [ + "/// Return an iterator over an inclusive range of bits in the number.", + "///", + "/// # Example", + "///", + "/// ```rhai", + "/// let x = 123456;", + "///", + "/// for bit in x.bits(10..=23) {", + "/// print(bit);", + "/// }", + "/// ```" + ] + ); let _hash = lib.set_native_fn("bits", BitRange::new); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "len: INT", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["value: INT", "from: INT", "len: INT", "Iterator"], + [ + "/// Return an iterator over a portion of bits in the number.", + "///", + "/// * If `start` < 0, position counts from the MSB (Most Significant Bit)>.", + "/// * If `len` ≤ 0, an empty iterator is returned.", + "/// * If `start` position + `len` ≥ length of string, all bits of the number after the `start` position are iterated.", + "///", + "/// # Example", + "///", + "/// ```rhai", + "/// let x = 123456;", + "///", + "/// for bit in x.bits(10, 8) {", + "/// print(bit);", + "/// }", + "/// ```" + ] + ); let _hash = lib.set_native_fn("bits", |value, from| BitRange::new(value, from, INT::MAX)); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["value: INT", "from: INT", "Iterator"], + [ + "/// Return an iterator over the bits in the number starting from the specified `start` position.", + "///", + "/// If `start` < 0, position counts from the MSB (Most Significant Bit)>.", + "///", + "/// # Example", + "///", + "/// ```rhai", + "/// let x = 123456;", + "///", + "/// for bit in x.bits(10) {", + "/// print(bit);", + "/// }", + "/// ```" + ] + ); let _hash = lib.set_native_fn("bits", |value| BitRange::new(value, 0, INT::MAX) ); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["value: INT", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["value: INT", "Iterator"], + [ + "/// Return an iterator over all the bits in the number.", + "///", + "/// # Example", + "///", + "/// ```rhai", + "/// let x = 123456;", + "///", + "/// for bit in x.bits() {", + "/// print(bit);", + "/// }", + "/// ```" + ] + ); #[cfg(not(feature = "no_object"))] { let _hash = lib.set_getter_fn("bits", |value: &mut INT| BitRange::new(*value, 0, INT::MAX) ); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["value: &mut INT", "range: Range", "Iterator"]); + lib.update_fn_metadata_with_comments( + _hash, + ["value: &mut INT", "range: Range", "Iterator"], + [ + "/// Return an iterator over all the bits in the number.", + "///", + "/// # Example", + "///", + "/// ```rhai", + "/// let x = 123456;", + "///", + "/// for bit in x.bits {", + "/// print(bit);", + "/// }", + "/// ```" + ] + ); } combine_with_exported_module!(lib, "range", range_functions); @@ -506,37 +756,45 @@ def_package! { #[export_module] mod range_functions { + /// Return the start of the exclusive range. #[rhai_fn(get = "start", name = "start", pure)] pub fn start(range: &mut ExclusiveRange) -> INT { range.start } + /// Return the end of the exclusive range. #[rhai_fn(get = "end", name = "end", pure)] pub fn end(range: &mut ExclusiveRange) -> INT { range.end } + /// Return `true` if the range is inclusive. #[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)] pub fn is_inclusive(range: &mut ExclusiveRange) -> bool { let _range = range; false } + /// Return `true` if the range is exclusive. #[rhai_fn(get = "is_exclusive", name = "is_exclusive", pure)] pub fn is_exclusive(range: &mut ExclusiveRange) -> bool { let _range = range; true } + /// Return the start of the inclusive range. #[rhai_fn(get = "start", name = "start", pure)] pub fn start_inclusive(range: &mut InclusiveRange) -> INT { *range.start() } + /// Return the end of the inclusive range. #[rhai_fn(get = "end", name = "end", pure)] pub fn end_inclusive(range: &mut InclusiveRange) -> INT { *range.end() } + /// Return `true` if the range is inclusive. #[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)] pub fn is_inclusive_inclusive(range: &mut InclusiveRange) -> bool { let _range = range; true } + /// Return `true` if the range is exclusive. #[rhai_fn(get = "is_exclusive", name = "is_exclusive", pure)] pub fn is_exclusive_inclusive(range: &mut InclusiveRange) -> bool { let _range = range; diff --git a/src/packages/lang_core.rs b/src/packages/lang_core.rs index a84b6628..ea1057c8 100644 --- a/src/packages/lang_core.rs +++ b/src/packages/lang_core.rs @@ -53,4 +53,112 @@ mod core_functions { Ok(()) } } + + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_index"))] + #[cfg(not(feature = "no_object"))] + pub fn get_fn_metadata_list(ctx: NativeCallContext) -> crate::Array { + collect_fn_metadata(ctx) + } +} + +#[cfg(not(feature = "no_function"))] +#[cfg(not(feature = "no_index"))] +#[cfg(not(feature = "no_object"))] +fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { + use crate::{ast::ScriptFnDef, Array, Identifier, Map}; + use std::collections::BTreeSet; + + // Create a metadata record for a function. + fn make_metadata( + dict: &BTreeSet, + namespace: Option, + func: &ScriptFnDef, + ) -> Map { + const DICT: &str = "key exists"; + + let mut map = Map::new(); + + if let Some(ns) = namespace { + map.insert(dict.get("namespace").expect(DICT).clone(), ns.into()); + } + map.insert( + dict.get("name").expect(DICT).clone(), + func.name.clone().into(), + ); + map.insert( + dict.get("access").expect(DICT).clone(), + match func.access { + FnAccess::Public => dict.get("public").expect(DICT).clone(), + FnAccess::Private => dict.get("private").expect(DICT).clone(), + } + .into(), + ); + map.insert( + dict.get("is_anonymous").expect(DICT).clone(), + func.name.starts_with(crate::engine::FN_ANONYMOUS).into(), + ); + map.insert( + dict.get("params").expect(DICT).clone(), + func.params + .iter() + .cloned() + .map(Into::into) + .collect::() + .into(), + ); + + map + } + + // Intern strings + let dict: BTreeSet = [ + "namespace", + "name", + "access", + "public", + "private", + "is_anonymous", + "params", + ] + .iter() + .map(|&s| s.into()) + .collect(); + + let mut _list = ctx.iter_namespaces().flat_map(Module::iter_script_fn).fold( + Array::new(), + |mut list, (_, _, _, _, f)| { + list.push(make_metadata(&dict, None, f).into()); + list + }, + ); + + #[cfg(not(feature = "no_module"))] + { + // Recursively scan modules for script-defined functions. + fn scan_module( + list: &mut Array, + dict: &BTreeSet, + namespace: Identifier, + module: &Module, + ) { + module.iter_script_fn().for_each(|(_, _, _, _, f)| { + list.push(make_metadata(dict, Some(namespace.clone()), f).into()) + }); + module.iter_sub_modules().for_each(|(ns, m)| { + let ns = format!( + "{}{}{}", + namespace, + crate::tokenizer::Token::DoubleColon.literal_syntax(), + ns + ); + scan_module(list, dict, ns.into(), m.as_ref()) + }); + } + + ctx.iter_imports_raw() + .for_each(|(ns, m)| scan_module(&mut _list, &dict, ns.clone(), m.as_ref())); + } + + _list } diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 4aaa3575..497bb0cc 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -20,15 +20,32 @@ def_package! { #[export_module] mod map_functions { + /// Return the number of properties in the object map. #[rhai_fn(pure)] pub fn len(map: &mut Map) -> INT { map.len() as INT } + /// Clear the object map. pub fn clear(map: &mut Map) { if !map.is_empty() { map.clear(); } } + /// Remove any property of the specified `name` from the object map, returning its value. + /// + /// If the property does not exist, `()` is returned. + /// + /// # Example + /// + /// ```rhai + /// let m = #{a:1, b:2, c:3}; + /// + /// let x = m.remove("b"); + /// + /// print(x); // prints 2 + /// + /// print(m); // prints "#{a:1, c:3}" + /// ``` pub fn remove(map: &mut Map, name: ImmutableString) -> Dynamic { if !map.is_empty() { map.remove(name.as_str()).unwrap_or_else(|| Dynamic::UNIT) @@ -36,12 +53,38 @@ mod map_functions { Dynamic::UNIT } } + /// Add all property values of another object map into the object map. + /// Existing property values of the same names are replaced. + /// + /// # Example + /// + /// ```rhai + /// let m = #{a:1, b:2, c:3}; + /// let n = #{a: 42, d:0}; + /// + /// m.mixin(n); + /// + /// print(m); // prints "#{a:42, b:2, c:3, d:0}" + /// ``` #[rhai_fn(name = "mixin", name = "+=")] pub fn mixin(map: &mut Map, map2: Map) { if !map2.is_empty() { map.extend(map2.into_iter()); } } + /// Make a copy of the object map, add all property values of another object map + /// (existing property values of the same names are replaced), then returning it. + /// + /// # Example + /// + /// ```rhai + /// let m = #{a:1, b:2, c:3}; + /// let n = #{a: 42, d:0}; + /// + /// print(m + n); // prints "#{a:42, b:2, c:3, d:0}" + /// + /// print(m); // prints "#{a:1, b:2, c:3}" + /// ``` #[rhai_fn(name = "+")] pub fn merge(map1: Map, map2: Map) -> Map { if map2.is_empty() { @@ -54,6 +97,19 @@ mod map_functions { map1 } } + /// Add all property values of another object map into the object map. + /// Only properties that do not originally exist in the object map are added. + /// + /// # Example + /// + /// ```rhai + /// let m = #{a:1, b:2, c:3}; + /// let n = #{a: 42, d:0}; + /// + /// m.fill_with(n); + /// + /// print(m); // prints "#{a:1, b:2, c:3, d:0}" + /// ``` pub fn fill_with(map: &mut Map, map2: Map) { if !map2.is_empty() { if map.is_empty() { @@ -65,6 +121,22 @@ mod map_functions { } } } + /// Return `true` if two object maps are equal (i.e. all property values are equal). + /// + /// The operator `==` is used to compare property values and must be defined, + /// otherwise `false` is assumed. + /// + /// # Example + /// + /// ```rhai + /// let m1 = #{a:1, b:2, c:3}; + /// let m2 = #{a:1, b:2, c:3}; + /// let m3 = #{a:1, c:3}; + /// + /// print(m1 == m2); // prints true + /// + /// print(m1 == m3); // prints false + /// ``` #[rhai_fn(name = "==", return_raw, pure)] pub fn equals(ctx: NativeCallContext, map1: &mut Map, map2: Map) -> RhaiResultOf { if map1.len() != map2.len() { @@ -92,11 +164,36 @@ mod map_functions { Ok(true) } + /// Return `true` if two object maps are not equal (i.e. at least one property value is not equal). + /// + /// The operator `==` is used to compare property values and must be defined, + /// otherwise `false` is assumed. + /// + /// # Example + /// + /// ```rhai + /// let m1 = #{a:1, b:2, c:3}; + /// let m2 = #{a:1, b:2, c:3}; + /// let m3 = #{a:1, c:3}; + /// + /// print(m1 != m2); // prints false + /// + /// print(m1 != m3); // prints true + /// ``` #[rhai_fn(name = "!=", return_raw, pure)] pub fn not_equals(ctx: NativeCallContext, map1: &mut Map, map2: Map) -> RhaiResultOf { equals(ctx, map1, map2).map(|r| !r) } + /// Return an array with all the property names in the object map. + /// + /// # Example + /// + /// ```rhai + /// let m = #{a:1, b:2, c:3}; + /// + /// print(m.keys()); // prints ["a", "b", "c"] + /// ``` #[cfg(not(feature = "no_index"))] #[rhai_fn(pure)] pub fn keys(map: &mut Map) -> Array { @@ -106,6 +203,15 @@ mod map_functions { map.keys().cloned().map(Into::into).collect() } } + /// Return an array with all the property values in the object map. + /// + /// # Example + /// + /// ```rhai + /// let m = #{a:1, b:2, c:3}; + /// + /// print(m.values()); // prints "[1, 2, 3]"" + /// ``` #[cfg(not(feature = "no_index"))] #[rhai_fn(pure)] pub fn values(map: &mut Map) -> Array { diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 83f2efac..6dd8f630 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -1,7 +1,7 @@ #![allow(non_snake_case)] use crate::plugin::*; -use crate::{def_package, Position, RhaiResultOf, ERR, INT, UNSIGNED_INT}; +use crate::{def_package, Position, RhaiResultOf, ERR, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -108,6 +108,34 @@ def_package! { #[export_module] mod int_functions { + /// Parse a string into an integer number. + /// + /// # Example + /// + /// ```rhai + /// let x = parse_int("123"); + /// + /// print(x); // prints 123 + /// ``` + #[rhai_fn(name = "parse_int", return_raw)] + pub fn parse_int(string: &str) -> RhaiResultOf { + parse_int_radix(string, 10) + } + /// Parse a string into an integer number of the specified `radix`. + /// + /// `radix` must be between 2 and 36. + /// + /// # Example + /// + /// ```rhai + /// let x = parse_int("123"); + /// + /// print(x); // prints 123 + /// + /// let y = parse_int("123abc", 16); + /// + /// print(y); // prints 1194684 (0x123abc) + /// ``` #[rhai_fn(name = "parse_int", return_raw)] pub fn parse_int_radix(string: &str, radix: INT) -> RhaiResultOf { if !(2..=36).contains(&radix) { @@ -118,19 +146,13 @@ mod int_functions { .into()); } - UNSIGNED_INT::from_str_radix(string.trim(), radix as u32) - .map(|v| v as INT) - .map_err(|err| { - ERR::ErrorArithmetic( - format!("Error parsing integer number '{}': {}", string, err), - Position::NONE, - ) - .into() - }) - } - #[rhai_fn(name = "parse_int", return_raw)] - pub fn parse_int(string: &str) -> RhaiResultOf { - parse_int_radix(string, 10) + INT::from_str_radix(string.trim(), radix as u32).map_err(|err| { + ERR::ErrorArithmetic( + format!("Error parsing integer number '{}': {}", string, err), + Position::NONE, + ) + .into() + }) } } @@ -139,46 +161,60 @@ mod int_functions { mod trig_functions { use crate::FLOAT; + /// Return the sine of the floating-point number in radians. pub fn sin(x: FLOAT) -> FLOAT { x.sin() } + /// Return the cosine of the floating-point number in radians. pub fn cos(x: FLOAT) -> FLOAT { x.cos() } + /// Return the tangent of the floating-point number in radians. pub fn tan(x: FLOAT) -> FLOAT { x.tan() } + /// Return the hyperbolic sine of the floating-point number in radians. pub fn sinh(x: FLOAT) -> FLOAT { x.sinh() } + /// Return the hyperbolic cosine of the floating-point number in radians. pub fn cosh(x: FLOAT) -> FLOAT { x.cosh() } + /// Return the hyperbolic tangent of the floating-point number in radians. pub fn tanh(x: FLOAT) -> FLOAT { x.tanh() } + /// Return the arc-sine of the floating-point number, in radians. pub fn asin(x: FLOAT) -> FLOAT { x.asin() } + /// Return the arc-cosine of the floating-point number, in radians. pub fn acos(x: FLOAT) -> FLOAT { x.acos() } + /// Return the arc-tangent of the floating-point number, in radians. pub fn atan(x: FLOAT) -> FLOAT { x.atan() } + /// Return the arc-tangent of the floating-point numbers `x` and `y`, in radians. #[rhai_fn(name = "atan")] pub fn atan2(x: FLOAT, y: FLOAT) -> FLOAT { x.atan2(y) } + /// Return the arc-hyperbolic-sine of the floating-point number, in radians. pub fn asinh(x: FLOAT) -> FLOAT { x.asinh() } + /// Return the arc-hyperbolic-cosine of the floating-point number, in radians. pub fn acosh(x: FLOAT) -> FLOAT { x.acosh() } + /// Return the arc-hyperbolic-tangent of the floating-point number, in radians. pub fn atanh(x: FLOAT) -> FLOAT { x.atanh() } + /// Return the hypotenuse of a triangle with sides `x` and `y`. pub fn hypot(x: FLOAT, y: FLOAT) -> FLOAT { x.hypot(y) } @@ -189,6 +225,7 @@ mod trig_functions { mod float_functions { use crate::FLOAT; + /// Return the natural number _e_. #[rhai_fn(name = "E")] pub fn e() -> FLOAT { #[cfg(not(feature = "f32_float"))] @@ -196,6 +233,7 @@ mod float_functions { #[cfg(feature = "f32_float")] return std::f32::consts::E; } + /// Return the number π. #[rhai_fn(name = "PI")] pub fn pi() -> FLOAT { #[cfg(not(feature = "f32_float"))] @@ -203,60 +241,77 @@ mod float_functions { #[cfg(feature = "f32_float")] return std::f32::consts::PI; } + /// Convert degrees to radians. pub fn to_radians(x: FLOAT) -> FLOAT { x.to_radians() } + /// Convert radians to degrees. pub fn to_degrees(x: FLOAT) -> FLOAT { x.to_degrees() } + /// Return the square root of the floating-point number. pub fn sqrt(x: FLOAT) -> FLOAT { x.sqrt() } + /// Return the exponential of the floating-point number. pub fn exp(x: FLOAT) -> FLOAT { x.exp() } + /// Return the natural log of the floating-point number. pub fn ln(x: FLOAT) -> FLOAT { x.ln() } + /// Return the log of the floating-point number with `base`. pub fn log(x: FLOAT, base: FLOAT) -> FLOAT { x.log(base) } + /// Return the log of the floating-point number with base 10. #[rhai_fn(name = "log")] pub fn log10(x: FLOAT) -> FLOAT { x.log10() } + /// Return the largest whole number less than or equals to the floating-point number. #[rhai_fn(name = "floor", get = "floor")] pub fn floor(x: FLOAT) -> FLOAT { x.floor() } + /// Return the smallest whole number larger than or equals to the floating-point number. #[rhai_fn(name = "ceiling", get = "ceiling")] pub fn ceiling(x: FLOAT) -> FLOAT { x.ceil() } + /// Return the nearest whole number closest to the floating-point number. + /// Rounds away from zero. #[rhai_fn(name = "round", get = "round")] pub fn round(x: FLOAT) -> FLOAT { x.round() } + /// Return the integral part of the floating-point number. #[rhai_fn(name = "int", get = "int")] pub fn int(x: FLOAT) -> FLOAT { x.trunc() } + /// Return the fractional part of the floating-point number. #[rhai_fn(name = "fraction", get = "fraction")] pub fn fraction(x: FLOAT) -> FLOAT { x.fract() } + /// Return `true` if the floating-point number is `NaN` (Not A Number). #[rhai_fn(name = "is_nan", get = "is_nan")] pub fn is_nan(x: FLOAT) -> bool { x.is_nan() } + /// Return `true` if the floating-point number is finite. #[rhai_fn(name = "is_finite", get = "is_finite")] pub fn is_finite(x: FLOAT) -> bool { x.is_finite() } + /// Return `true` if the floating-point number is infinite. #[rhai_fn(name = "is_infinite", get = "is_infinite")] pub fn is_infinite(x: FLOAT) -> bool { x.is_infinite() } + /// Return the integral part of the floating-point number. #[rhai_fn(name = "to_int", return_raw)] pub fn f32_to_int(x: f32) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) && x > (INT::MAX as f32) { @@ -268,6 +323,7 @@ mod float_functions { Ok(x.trunc() as INT) } } + /// Return the integral part of the floating-point number. #[rhai_fn(name = "to_int", return_raw)] pub fn f64_to_int(x: f64) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) && x > (INT::MAX as f64) { @@ -279,6 +335,15 @@ mod float_functions { Ok(x.trunc() as INT) } } + /// Parse a string into a floating-point number. + /// + /// # Example + /// + /// ```rhai + /// let x = parse_int("123.456"); + /// + /// print(x); // prints 123.456 + /// ``` #[rhai_fn(return_raw)] pub fn parse_float(string: &str) -> RhaiResultOf { string.trim().parse::().map_err(|err| { @@ -289,6 +354,7 @@ mod float_functions { .into() }) } + /// Convert the 32-bit floating-point number to 64-bit. #[cfg(not(feature = "f32_float"))] #[rhai_fn(name = "to_float")] pub fn f32_to_f64(x: f32) -> f64 { @@ -306,36 +372,52 @@ mod decimal_functions { #[cfg(not(feature = "no_float"))] use std::convert::TryFrom; + /// Return the natural number _e_. #[cfg(feature = "no_float")] #[rhai_fn(name = "PI")] pub fn pi() -> Decimal { Decimal::PI } + /// Return the number π. #[cfg(feature = "no_float")] #[rhai_fn(name = "E")] pub fn e() -> Decimal { Decimal::E } + /// Parse a string into a decimal number. + /// + /// # Example + /// + /// ```rhai + /// let x = parse_float("123.456"); + /// + /// print(x); // prints 123.456 + /// ``` #[cfg(feature = "no_float")] #[rhai_fn(return_raw)] pub fn parse_float(s: &str) -> RhaiResultOf { parse_decimal(s) } + /// Return the sine of the decimal number in radians. pub fn sin(x: Decimal) -> Decimal { x.sin() } + /// Return the cosine of the decimal number in radians. pub fn cos(x: Decimal) -> Decimal { x.cos() } + /// Return the tangent of the decimal number in radians. pub fn tan(x: Decimal) -> Decimal { x.tan() } + /// Return the square root of the decimal number. #[rhai_fn(return_raw)] pub fn sqrt(x: Decimal) -> RhaiResultOf { x.sqrt() .ok_or_else(|| make_err(format!("Error taking the square root of {}", x,))) } + /// Return the exponential of the decimal number. #[rhai_fn(return_raw)] pub fn exp(x: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { @@ -345,6 +427,7 @@ mod decimal_functions { Ok(x.exp()) } } + /// Return the natural log of the decimal number. #[rhai_fn(return_raw)] pub fn ln(x: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { @@ -354,6 +437,7 @@ mod decimal_functions { Ok(x.ln()) } } + /// Return the log of the decimal number with base 10. #[rhai_fn(name = "log", return_raw)] pub fn log10(x: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { @@ -363,106 +447,131 @@ mod decimal_functions { Ok(x.log10()) } } + /// Return the largest whole number less than or equals to the decimal number. #[rhai_fn(name = "floor", get = "floor")] pub fn floor(x: Decimal) -> Decimal { x.floor() } + /// Return the smallest whole number larger than or equals to the decimal number. #[rhai_fn(name = "ceiling", get = "ceiling")] pub fn ceiling(x: Decimal) -> Decimal { x.ceil() } + /// Return the nearest whole number closest to the decimal number. + /// Always round mid-point towards the closest even number. #[rhai_fn(name = "round", get = "round")] pub fn round(x: Decimal) -> Decimal { x.round() } + /// Round the decimal number to the specified number of `digits` after the decimal point and return it. + /// Always round mid-point towards the closest even number. #[rhai_fn(name = "round", return_raw)] - pub fn round_dp(x: Decimal, dp: INT) -> RhaiResultOf { + pub fn round_dp(x: Decimal, digits: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { - if dp < 0 { + if digits < 0 { return Err(make_err(format!( "Invalid number of digits for rounding: {}", - dp + digits ))); } - if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { + if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { return Ok(x); } } - Ok(x.round_dp(dp as u32)) + Ok(x.round_dp(digits as u32)) } + /// Round the decimal number to the specified number of `digits` after the decimal point and return it. + /// Always round away from zero. #[rhai_fn(return_raw)] - pub fn round_up(x: Decimal, dp: INT) -> RhaiResultOf { + pub fn round_up(x: Decimal, digits: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { - if dp < 0 { + if digits < 0 { return Err(make_err(format!( "Invalid number of digits for rounding: {}", - dp + digits ))); } - if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { + if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { return Ok(x); } } - Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::AwayFromZero)) + Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::AwayFromZero)) } + /// Round the decimal number to the specified number of `digits` after the decimal point and return it. + /// Always round towards zero. #[rhai_fn(return_raw)] - pub fn round_down(x: Decimal, dp: INT) -> RhaiResultOf { + pub fn round_down(x: Decimal, digits: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { - if dp < 0 { + if digits < 0 { return Err(make_err(format!( "Invalid number of digits for rounding: {}", - dp + digits ))); } - if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { + if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { return Ok(x); } } - Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::ToZero)) + Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::ToZero)) } + /// Round the decimal number to the specified number of `digits` after the decimal point and return it. + /// Always round mid-points away from zero. #[rhai_fn(return_raw)] - pub fn round_half_up(x: Decimal, dp: INT) -> RhaiResultOf { + pub fn round_half_up(x: Decimal, digits: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { - if dp < 0 { + if digits < 0 { return Err(make_err(format!( "Invalid number of digits for rounding: {}", - dp + digits ))); } - if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { + if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { return Ok(x); } } - Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::MidpointAwayFromZero)) + Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::MidpointAwayFromZero)) } + /// Round the decimal number to the specified number of `digits` after the decimal point and return it. + /// Always round mid-points towards zero. #[rhai_fn(return_raw)] - pub fn round_half_down(x: Decimal, dp: INT) -> RhaiResultOf { + pub fn round_half_down(x: Decimal, digits: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { - if dp < 0 { + if digits < 0 { return Err(make_err(format!( "Invalid number of digits for rounding: {}", - dp + digits ))); } - if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { + if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { return Ok(x); } } - Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::MidpointTowardZero)) + Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::MidpointTowardZero)) } + /// Return the integral part of the decimal number. #[rhai_fn(name = "int", get = "int")] pub fn int(x: Decimal) -> Decimal { x.trunc() } + /// Return the fractional part of the decimal number. #[rhai_fn(name = "fraction", get = "fraction")] pub fn fraction(x: Decimal) -> Decimal { x.fract() } + /// Parse a string into a decimal number. + /// + /// # Example + /// + /// ```rhai + /// let x = parse_decimal("123.456"); + /// + /// print(x); // prints 123.456 + /// ``` #[rhai_fn(return_raw)] pub fn parse_decimal(string: &str) -> RhaiResultOf { Decimal::from_str(string) @@ -476,6 +585,7 @@ mod decimal_functions { }) } + /// Convert the floating-point number to decimal. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "to_decimal", return_raw)] pub fn f32_to_decimal(x: f32) -> RhaiResultOf { @@ -487,6 +597,7 @@ mod decimal_functions { .into() }) } + /// Convert the floating-point number to decimal. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "to_decimal", return_raw)] pub fn f64_to_decimal(x: f64) -> RhaiResultOf { @@ -498,6 +609,7 @@ mod decimal_functions { .into() }) } + /// Convert the decimal number to floating-point. #[cfg(not(feature = "no_float"))] #[rhai_fn(return_raw)] pub fn to_float(x: Decimal) -> RhaiResultOf { diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 1debcbb2..1e4dd59e 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -46,56 +46,68 @@ pub fn print_with_func( mod print_debug_functions { use crate::ImmutableString; + /// Convert the value of the `item` into a string. #[rhai_fn(name = "print", pure)] pub fn print_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { print_with_func(FUNC_TO_STRING, &ctx, item) } + /// Convert the value of the `item` into a string. #[rhai_fn(name = "to_string", pure)] pub fn to_string_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { ctx.engine().map_type_name(&item.to_string()).into() } + /// Convert the value of the `item` into a string in debug format. #[rhai_fn(name = "debug", pure)] pub fn debug_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { print_with_func(FUNC_TO_DEBUG, &ctx, item) } + /// Convert the value of the `item` into a string in debug format. #[rhai_fn(name = "to_debug", pure)] pub fn to_debug_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { ctx.engine().map_type_name(&format!("{:?}", item)).into() } + /// Return the empty string. #[rhai_fn(name = "print", name = "debug")] pub fn print_empty_string(ctx: NativeCallContext) -> ImmutableString { ctx.engine().const_empty_string() } + /// Return the `string`. #[rhai_fn(name = "print", name = "to_string")] - pub fn print_string(s: ImmutableString) -> ImmutableString { - s + pub fn print_string(string: ImmutableString) -> ImmutableString { + string } + /// Convert the function pointer into a string in debug format. #[rhai_fn(name = "debug", name = "to_debug", pure)] pub fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString { f.to_string().into() } + /// Convert the value of `number` into a string. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "print", name = "to_string")] pub fn print_f64(number: f64) -> ImmutableString { crate::ast::FloatWrapper::new(number).to_string().into() } + /// Convert the value of `number` into a string. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "print", name = "to_string")] pub fn print_f32(number: f32) -> ImmutableString { crate::ast::FloatWrapper::new(number).to_string().into() } + /// Convert the value of `number` into a string. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_f64(number: f64) -> ImmutableString { format!("{:?}", crate::ast::FloatWrapper::new(number)).into() } + /// Convert the value of `number` into a string. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_f32(number: f32) -> ImmutableString { format!("{:?}", crate::ast::FloatWrapper::new(number)).into() } + /// Convert the array into a string. #[cfg(not(feature = "no_index"))] #[rhai_fn( name = "print", @@ -119,6 +131,8 @@ mod print_debug_functions { result.push(']'); result.into() } + + /// Convert the object map into a string. #[cfg(not(feature = "no_object"))] #[rhai_fn( name = "print", @@ -148,27 +162,27 @@ mod print_debug_functions { #[export_module] mod number_formatting { - #[rhai_fn(skip)] - pub fn to_hex(value: T) -> ImmutableString { + fn to_hex(value: T) -> ImmutableString { format!("{:x}", value).into() } - #[rhai_fn(skip)] - pub fn to_octal(value: T) -> ImmutableString { + fn to_octal(value: T) -> ImmutableString { format!("{:o}", value).into() } - #[rhai_fn(skip)] - pub fn to_binary(value: T) -> ImmutableString { + fn to_binary(value: T) -> ImmutableString { format!("{:b}", value).into() } + /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn int_to_hex(value: INT) -> ImmutableString { to_hex(value) } + /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn int_to_octal(value: INT) -> ImmutableString { to_octal(value) } + /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn int_to_binary(value: INT) -> ImmutableString { to_binary(value) @@ -177,98 +191,122 @@ mod number_formatting { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] pub mod numbers { + /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn u8_to_hex(value: u8) -> ImmutableString { to_hex(value) } + /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn u16_to_hex(value: u16) -> ImmutableString { to_hex(value) } + /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn u32_to_hex(value: u32) -> ImmutableString { to_hex(value) } + /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn u64_to_hex(value: u64) -> ImmutableString { to_hex(value) } + /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn i8_to_hex(value: i8) -> ImmutableString { to_hex(value) } + /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn i16_to_hex(value: i16) -> ImmutableString { to_hex(value) } + /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn i32_to_hex(value: i32) -> ImmutableString { to_hex(value) } + /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn i64_to_hex(value: i64) -> ImmutableString { to_hex(value) } + /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn u8_to_octal(value: u8) -> ImmutableString { to_octal(value) } + /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn u16_to_octal(value: u16) -> ImmutableString { to_octal(value) } + /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn u32_to_octal(value: u32) -> ImmutableString { to_octal(value) } + /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn u64_to_octal(value: u64) -> ImmutableString { to_octal(value) } + /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn i8_to_octal(value: i8) -> ImmutableString { to_octal(value) } + /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn i16_to_octal(value: i16) -> ImmutableString { to_octal(value) } + /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn i32_to_octal(value: i32) -> ImmutableString { to_octal(value) } + /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn i64_to_octal(value: i64) -> ImmutableString { to_octal(value) } + /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn u8_to_binary(value: u8) -> ImmutableString { to_binary(value) } + /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn u16_to_binary(value: u16) -> ImmutableString { to_binary(value) } + /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn u32_to_binary(value: u32) -> ImmutableString { to_binary(value) } + /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn u64_to_binary(value: u64) -> ImmutableString { to_binary(value) } + /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn i8_to_binary(value: i8) -> ImmutableString { to_binary(value) } + /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn i16_to_binary(value: i16) -> ImmutableString { to_binary(value) } + /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn i32_to_binary(value: i32) -> ImmutableString { to_binary(value) } + /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn i64_to_binary(value: i64) -> ImmutableString { to_binary(value) @@ -277,14 +315,32 @@ mod number_formatting { #[cfg(not(target_family = "wasm"))] pub mod num_128 { + /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn u128_to_hex(value: u128) -> ImmutableString { to_hex(value) } + /// Convert the `value` into a string in hex format. + #[rhai_fn(name = "to_hex")] + pub fn i128_to_hex(value: i128) -> ImmutableString { + to_hex(value) + } + /// Convert the `value` into a string in octal format. + #[rhai_fn(name = "to_octal")] + pub fn u128_to_octal(value: u128) -> ImmutableString { + to_octal(value) + } + /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn i128_to_octal(value: i128) -> ImmutableString { to_octal(value) } + /// Convert the `value` into a string in binary format. + #[rhai_fn(name = "to_binary")] + pub fn u128_to_binary(value: u128) -> ImmutableString { + to_binary(value) + } + /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn i128_to_binary(value: i128) -> ImmutableString { to_binary(value) diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 515dda99..7ab926e2 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -73,6 +73,15 @@ mod string_functions { string } + /// Return the length of the string, in number of characters. + /// + /// # Example + /// + /// ```rhai + /// let text = "朝には紅顔ありて夕べには白骨となる"; + /// + /// print(text.len); // prints 17 + /// ``` #[rhai_fn(name = "len", get = "len")] pub fn len(string: &str) -> INT { if string.is_empty() { @@ -81,6 +90,15 @@ mod string_functions { string.chars().count() as INT } } + /// Return the length of the string, in number of bytes used to store it in UTF-8 encoding. + /// + /// # Example + /// + /// ```rhai + /// let text = "朝には紅顔ありて夕べには白骨となる"; + /// + /// print(text.bytes); // prints 51 + /// ``` #[rhai_fn(name = "bytes", get = "bytes")] pub fn bytes(string: &str) -> INT { if string.is_empty() { @@ -89,18 +107,59 @@ mod string_functions { string.len() as INT } } + /// Remove all occurrences of a sub-string from the string. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foobar!"; + /// + /// text.remove("hello"); + /// + /// print(text); // prints ", world! , foobar!" + /// ``` pub fn remove(string: &mut ImmutableString, sub_string: ImmutableString) { *string -= sub_string; } + /// Remove all occurrences of a character from the string. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foobar!"; + /// + /// text.remove("o"); + /// + /// print(text); // prints "hell, wrld! hell, fbar!" + /// ``` #[rhai_fn(name = "remove")] pub fn remove_char(string: &mut ImmutableString, character: char) { *string -= character; } + /// Clear the string, making it empty. pub fn clear(string: &mut ImmutableString) { if !string.is_empty() { string.make_mut().clear(); } } + /// Cut off the string at the specified number of characters. + /// + /// * If `len` ≤ 0, the string is cleared. + /// * If `len` ≥ length of string, the string is not truncated. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foobar!"; + /// + /// text.truncate(13); + /// + /// print(text); // prints "hello, world!" + /// + /// x.truncate(10); + /// + /// print(text); // prints "hello, world!" + /// ``` pub fn truncate(string: &mut ImmutableString, len: INT) { if len > 0 { let chars: StaticVec<_> = string.chars().collect(); @@ -111,6 +170,15 @@ mod string_functions { string.make_mut().clear(); } } + /// Remove whitespace characters from both ends of the string. + /// + /// # Example + /// + /// ```rhai + /// let text = " hello "; + /// + /// print(text.trim()); // prints "hello" + /// ``` pub fn trim(string: &mut ImmutableString) { let trimmed = string.trim(); @@ -118,6 +186,19 @@ mod string_functions { *string = trimmed.to_string().into(); } } + /// Remove the last character from the string and return it. + /// + /// If the string is empty, `()` is returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.pop()); // prints '!' + /// + /// print(text); // prints "hello, world" + /// ``` pub fn pop(string: &mut ImmutableString) -> Dynamic { if string.is_empty() { Dynamic::UNIT @@ -128,6 +209,21 @@ mod string_functions { } } } + /// Remove the a specified number of characters from the end of the string and return it as a + /// new string. + /// + /// * If `len` ≤ 0, the string is not modified and an empty string is returned. + /// * If `len` ≥ length of string, the string is cleared and the entire string returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.pop(4)); // prints "rld!" + /// + /// print(text); // prints "hello, wo" + /// ``` #[rhai_fn(name = "pop")] pub fn pop_string( ctx: NativeCallContext, @@ -150,6 +246,17 @@ mod string_functions { chars.into_iter().rev().collect::().into() } + /// Convert the string to all upper-case and return it as a new string. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!" + /// + /// print(text.to_upper()); // prints "HELLO, WORLD!" + /// + /// print(text); // prints "hello, world!" + /// ``` pub fn to_upper(string: ImmutableString) -> ImmutableString { if string.is_empty() { string @@ -157,11 +264,33 @@ mod string_functions { string.to_uppercase().into() } } + /// Convert the string to all upper-case. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!" + /// + /// text.make_upper(); + /// + /// print(text); // prints "HELLO, WORLD!"; + /// ``` pub fn make_upper(string: &mut ImmutableString) { if !string.is_empty() { *string = string.to_uppercase().into(); } } + /// Convert the string to all lower-case and return it as a new string. + /// + /// # Example + /// + /// ```rhai + /// let text = "HELLO, WORLD!" + /// + /// print(text.to_lower()); // prints "hello, world!" + /// + /// print(text); // prints "HELLO, WORLD!" + /// ``` pub fn to_lower(string: ImmutableString) -> ImmutableString { if string.is_empty() { string @@ -169,12 +298,34 @@ mod string_functions { string.to_lowercase().into() } } + /// Convert the string to all lower-case. + /// + /// # Example + /// + /// ```rhai + /// let text = "HELLO, WORLD!" + /// + /// text.make_lower(); + /// + /// print(text); // prints "hello, world!"; + /// ``` pub fn make_lower(string: &mut ImmutableString) { if !string.is_empty() { *string = string.to_lowercase().into(); } } + /// Convert the character to upper-case and return it as a new character. + /// + /// # Example + /// + /// ```rhai + /// let ch = 'a'; + /// + /// print(ch.to_upper()); // prints 'A' + /// + /// print(ch); // prints 'a' + /// ``` #[rhai_fn(name = "to_upper")] pub fn to_upper_char(character: char) -> char { let mut stream = character.to_uppercase(); @@ -185,10 +336,32 @@ mod string_functions { ch } } + /// Convert the character to upper-case. + /// + /// # Example + /// + /// ```rhai + /// let ch = 'a'; + /// + /// ch.make_upper(); + /// + /// print(ch); // prints 'A' + /// ``` #[rhai_fn(name = "make_upper")] pub fn make_upper_char(character: &mut char) { *character = to_upper_char(*character) } + /// Convert the character to lower-case and return it as a new character. + /// + /// # Example + /// + /// ```rhai + /// let ch = 'A'; + /// + /// print(ch.to_lower()); // prints 'a' + /// + /// print(ch); // prints 'A' + /// ``` #[rhai_fn(name = "to_lower")] pub fn to_lower_char(character: char) -> char { let mut stream = character.to_lowercase(); @@ -199,11 +372,41 @@ mod string_functions { ch } } + /// Convert the character to lower-case. + /// + /// # Example + /// + /// ```rhai + /// let ch = 'A'; + /// + /// ch.make_lower(); + /// + /// print(ch); // prints 'a' + /// ``` #[rhai_fn(name = "make_lower")] pub fn make_lower_char(character: &mut char) { *character = to_lower_char(*character) } + /// Find the specified `character` in the string, starting from the specified `start` position, + /// and return the first index where it is found. + /// If the `character` is not found, `-1` is returned. + /// + /// * If `start` < 0, position counts from the end of the string (`-1` is the last character). + /// * If `start` < -length of string, position counts from the beginning of the string. + /// * If `start` ≥ length of string, `-1` is returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.index_of('l', 5)); // prints 10 (first index after 5) + /// + /// print(text.index_of('o', -7)); // prints 8 + /// + /// print(text.index_of('x', 0)); // prints -1 + /// ``` #[rhai_fn(name = "index_of")] pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT { if string.is_empty() { @@ -243,6 +446,18 @@ mod string_functions { .map(|index| string[0..start + index].chars().count() as INT) .unwrap_or(-1 as INT) } + /// Find the specified `character` in the string and return the first index where it is found. + /// If the `character` is not found, `-1` is returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.index_of('l')); // prints 2 (first index) + /// + /// print(text.index_of('x')); // prints -1 + /// ``` #[rhai_fn(name = "index_of")] pub fn index_of_char(string: &str, character: char) -> INT { if string.is_empty() { @@ -254,6 +469,25 @@ mod string_functions { .unwrap_or(-1 as INT) } } + /// Find the specified sub-string in the string, starting from the specified `start` position, + /// and return the first index where it is found. + /// If the sub-string is not found, `-1` is returned. + /// + /// * If `start` < 0, position counts from the end of the string (`-1` is the last character). + /// * If `start` < -length of string, position counts from the beginning of the string. + /// * If `start` ≥ length of string, `-1` is returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foobar!"; + /// + /// print(text.index_of("ll", 5)); // prints 16 (first index after 5) + /// + /// print(text.index_of("ll", -15)); // prints 16 + /// + /// print(text.index_of("xx", 0)); // prints -1 + /// ``` #[rhai_fn(name = "index_of")] pub fn index_of_string_starting_from(string: &str, find_string: &str, start: INT) -> INT { if string.is_empty() { @@ -293,6 +527,18 @@ mod string_functions { .map(|index| string[0..start + index].chars().count() as INT) .unwrap_or(-1 as INT) } + /// Find the specified `character` in the string and return the first index where it is found. + /// If the `character` is not found, `-1` is returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foobar!"; + /// + /// print(text.index_of("ll")); // prints 2 (first index) + /// + /// print(text.index_of("xx:)); // prints -1 + /// ``` #[rhai_fn(name = "index_of")] pub fn index_of(string: &str, find_string: &str) -> INT { if string.is_empty() { @@ -305,6 +551,15 @@ mod string_functions { } } + /// Copy an exclusive range of characters from the string and return it as a new string. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.sub_string(3..7)); // prints "lo, " + /// ``` #[rhai_fn(name = "sub_string")] pub fn sub_string_range( ctx: NativeCallContext, @@ -315,6 +570,15 @@ mod string_functions { let end = INT::max(range.end, start); sub_string(ctx, string, start, end - start) } + /// Copy an inclusive range of characters from the string and return it as a new string. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.sub_string(3..=7)); // prints "lo, w" + /// ``` #[rhai_fn(name = "sub_string")] pub fn sub_string_inclusive_range( ctx: NativeCallContext, @@ -325,6 +589,23 @@ mod string_functions { let end = INT::max(*range.end(), start); sub_string(ctx, string, start, end - start + 1) } + /// Copy a portion of the string and return it as a new string. + /// + /// * If `start` < 0, position counts from the end of the string (`-1` is the last character). + /// * If `start` < -length of string, position counts from the beginning of the string. + /// * If `start` ≥ length of string, an empty string is returned. + /// * If `len` ≤ 0, an empty string is returned. + /// * If `start` position + `len` ≥ length of string, entire portion of the string after the `start` position is copied and returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.sub_string(3, 4)); // prints "lo, " + /// + /// print(text.sub_string(-8, 3)); // prints ", w" + /// ``` pub fn sub_string( ctx: NativeCallContext, string: &str, @@ -374,6 +655,22 @@ mod string_functions { .collect::() .into() } + /// Copy a portion of the string beginning at the `start` position till the end and return it as + /// a new string. + /// + /// * If `start` < 0, position counts from the end of the string (`-1` is the last character). + /// * If `start` < -length of string, the entire string is copied and returned. + /// * If `start` ≥ length of string, an empty string is returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.sub_string(5)); // prints ", world!" + /// + /// print(text.sub_string(-5)); // prints "orld!" + /// ``` #[rhai_fn(name = "sub_string")] pub fn sub_string_starting_from( ctx: NativeCallContext, @@ -388,18 +685,62 @@ mod string_functions { } } + /// Remove all characters from the string except those within an exclusive range. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// text.crop(2..8); + /// + /// print(text); // prints "llo, w" + /// ``` #[rhai_fn(name = "crop")] pub fn crop_range(string: &mut ImmutableString, range: ExclusiveRange) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); crop(string, start, end - start) } + /// Remove all characters from the string except those within an inclusive range. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// text.crop(2..=8); + /// + /// print(text); // prints "llo, wo" + /// ``` #[rhai_fn(name = "crop")] pub fn crop_inclusive_range(string: &mut ImmutableString, range: InclusiveRange) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); crop(string, start, end - start + 1) } + + /// Remove all characters from the string except those within a range. + /// + /// * If `start` < 0, position counts from the end of the string (`-1` is the last character). + /// * If `start` < -length of string, position counts from the beginning of the string. + /// * If `start` ≥ length of string, the entire string is cleared. + /// * If `len` ≤ 0, the entire string is cleared. + /// * If `start` position + `len` ≥ length of string, only the portion of the string after the `start` position is retained. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// text.crop(2, 8); + /// + /// print(text); // prints "llo, wor" + /// + /// text.crop(-5, 3); + /// + /// print(text); // prints ", w" + /// ``` #[rhai_fn(name = "crop")] pub fn crop(string: &mut ImmutableString, start: INT, len: INT) { if string.is_empty() { @@ -443,17 +784,58 @@ mod string_functions { copy.clear(); copy.extend(chars.iter().skip(offset).take(len)); } + /// Remove all characters from the string except until the `start` position. + /// + /// * If `start` < 0, position counts from the end of the string (`-1` is the last character). + /// * If `start` < -length of string, the string is not modified. + /// * If `start` ≥ length of string, the entire string is cleared. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// text.crop(5); + /// + /// print(text); // prints ", world!" + /// + /// text.crop(-3); + /// + /// print(text); // prints "ld!" + /// ``` #[rhai_fn(name = "crop")] pub fn crop_string_starting_from(string: &mut ImmutableString, start: INT) { crop(string, start, string.len() as INT); } + /// Replace all occurrences of the specified sub-string in the string with another string. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foobar!"; + /// + /// text.replace("hello", "hey"); + /// + /// print(text); // prints "hey, world! hey, foobar!" + /// ``` #[rhai_fn(name = "replace")] pub fn replace(string: &mut ImmutableString, find_string: &str, substitute_string: &str) { if !string.is_empty() { *string = string.replace(find_string, substitute_string).into(); } } + /// Replace all occurrences of the specified sub-string in the string with the specified character. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foobar!"; + /// + /// text.replace("hello", '*'); + /// + /// print(text); // prints "*, world! *, foobar!" + /// ``` #[rhai_fn(name = "replace")] pub fn replace_string_with_char( string: &mut ImmutableString, @@ -466,6 +848,17 @@ mod string_functions { .into(); } } + /// Replace all occurrences of the specified character in the string with another string. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foobar!"; + /// + /// text.replace('l', "(^)"); + /// + /// print(text); // prints "he(^)(^)o, wor(^)d! he(^)(^)o, foobar!" + /// ``` #[rhai_fn(name = "replace")] pub fn replace_char_with_string( string: &mut ImmutableString, @@ -478,6 +871,17 @@ mod string_functions { .into(); } } + /// Replace all occurrences of the specified character in the string with another character. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foobar!"; + /// + /// text.replace("l", '*'); + /// + /// print(text); // prints "he**o, wor*d! he**o, foobar!" + /// ``` #[rhai_fn(name = "replace")] pub fn replace_char( string: &mut ImmutableString, @@ -494,6 +898,23 @@ mod string_functions { } } + /// Pad the string to at least the specified number of characters with the specified `character`. + /// + /// If `len` ≤ length of string, no padding is done. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello"; + /// + /// text.pad(8, '!'); + /// + /// print(text); // prints "hello!!!" + /// + /// text.pad(5, '*'); + /// + /// print(text); // prints "hello!!!" + /// ``` #[rhai_fn(return_raw)] pub fn pad( ctx: NativeCallContext, @@ -538,6 +959,23 @@ mod string_functions { Ok(()) } + /// Pad the string to at least the specified number of characters with the specified string. + /// + /// If `len` ≤ length of string, no padding is done. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello"; + /// + /// text.pad(10, "(!)"); + /// + /// print(text); // prints "hello(!)(!)" + /// + /// text.pad(8, '***'); + /// + /// print(text); // prints "hello(!)(!)" + /// ``` #[rhai_fn(name = "pad", return_raw)] pub fn pad_with_string( ctx: NativeCallContext, @@ -594,6 +1032,14 @@ mod string_functions { pub mod arrays { use crate::{Array, ImmutableString}; + /// Return an array containing all the characters of the string. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello"; + /// + /// print(text.split()); // prints "['h', 'e', 'l', 'l', 'o']" #[rhai_fn(name = "split")] pub fn chars(string: &str) -> Array { if string.is_empty() { @@ -602,10 +1048,32 @@ mod string_functions { string.chars().map(Into::into).collect() } } + /// Split the string into two at the specified `index` position and return it both strings + /// as an array. + /// + /// The character at the `index` position (if any) is returned in the _second_ string. + /// + /// * If `index` < 0, position counts from the end of the string (`-1` is the last character). + /// * If `index` < -length of string, it is equivalent to cutting at position 0. + /// * If `index` ≥ length of string, it is equivalent to cutting at the end of the string. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.split(6)); // prints ["hello,", " world!"] + /// + /// print(text.split(13)); // prints ["hello, world!", ""] + /// + /// print(text.split(-6)); // prints ["hello, ", "world!"] + /// + /// print(text.split(-99)); // prints ["", "hello, world!"] + /// ``` #[rhai_fn(name = "split")] - pub fn split_at(ctx: NativeCallContext, string: ImmutableString, start: INT) -> Array { - if start <= 0 { - if let Some(n) = start.checked_abs() { + pub fn split_at(ctx: NativeCallContext, string: ImmutableString, index: INT) -> Array { + if index <= 0 { + if let Some(n) = index.checked_abs() { let num_chars = string.chars().count(); if n as usize > num_chars { vec![ctx.engine().const_empty_string().into(), string.into()] @@ -618,41 +1086,127 @@ mod string_functions { vec![ctx.engine().const_empty_string().into(), string.into()] } } else { - let prefix: String = string.chars().take(start as usize).collect(); + let prefix: String = string.chars().take(index as usize).collect(); let prefix_len = prefix.len(); vec![prefix.into(), string[prefix_len..].into()] } } + /// Split the string into segments based on a `delimiter` string, returning an array of the segments. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foo!"; + /// + /// print(text.split("ll")); // prints ["he", "o, world! he", "o, foo!"] + /// ``` pub fn split(string: &str, delimiter: &str) -> Array { string.split(delimiter).map(Into::into).collect() } + /// Split the string into at most the specified number of `segments` based on a `delimiter` string, + /// returning an array of the segments. + /// + /// If `segments` < 1, only one segment is returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foo!"; + /// + /// print(text.split("ll", 2)); // prints ["he", "o, world! hello, foo!"] + /// ``` #[rhai_fn(name = "split")] pub fn splitn(string: &str, delimiter: &str, segments: INT) -> Array { let pieces: usize = if segments < 1 { 1 } else { segments as usize }; string.splitn(pieces, delimiter).map(Into::into).collect() } + /// Split the string into segments based on a `delimiter` character, returning an array of the segments. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foo!"; + /// + /// print(text.split('l')); // prints ["he", "", "o, wor", "d! he", "", "o, foo!"] + /// ``` #[rhai_fn(name = "split")] pub fn split_char(string: &str, delimiter: char) -> Array { string.split(delimiter).map(Into::into).collect() } + /// Split the string into at most the specified number of `segments` based on a `delimiter` character, + /// returning an array of the segments. + /// + /// If `segments` < 1, only one segment is returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foo!"; + /// + /// print(text.split('l', 3)); // prints ["he", "", "o, world! hello, foo!"] + /// ``` #[rhai_fn(name = "split")] pub fn splitn_char(string: &str, delimiter: char, segments: INT) -> Array { let pieces: usize = if segments < 1 { 1 } else { segments as usize }; string.splitn(pieces, delimiter).map(Into::into).collect() } + /// Split the string into segments based on a `delimiter` string, returning an array of the + /// segments in _reverse_ order. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foo!"; + /// + /// print(text.split_rev("ll")); // prints ["o, foo!", "o, world! he", "he"] + /// ``` #[rhai_fn(name = "split_rev")] pub fn rsplit(string: &str, delimiter: &str) -> Array { string.rsplit(delimiter).map(Into::into).collect() } + /// Split the string into at most a specified number of `segments` based on a `delimiter` string, + /// returning an array of the segments in _reverse_ order. + /// + /// If `segments` < 1, only one segment is returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foo!"; + /// + /// print(text.split_rev("ll", 2)); // prints ["o, foo!", "hello, world! he"] + /// ``` #[rhai_fn(name = "split_rev")] pub fn rsplitn(string: &str, delimiter: &str, segments: INT) -> Array { let pieces: usize = if segments < 1 { 1 } else { segments as usize }; string.rsplitn(pieces, delimiter).map(Into::into).collect() } + /// Split the string into segments based on a `delimiter` character, returning an array of + /// the segments in _reverse_ order. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foo!"; + /// + /// print(text.split_rev('l')); // prints ["o, foo!", "", "d! he", "o, wor", "", "he"] + /// ``` #[rhai_fn(name = "split_rev")] pub fn rsplit_char(string: &str, delimiter: char) -> Array { string.rsplit(delimiter).map(Into::into).collect() } + /// Split the string into at most the specified number of `segments` based on a `delimiter` character, + /// returning an array of the segments. + /// + /// If `segments` < 1, only one segment is returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world! hello, foo!"; + /// + /// print(text.split('l', 3)); // prints ["o, foo!", "", "hello, world! he" + /// ``` #[rhai_fn(name = "split_rev")] pub fn rsplitn_char(string: &str, delimiter: char, segments: INT) -> Array { let pieces: usize = if segments < 1 { 1 } else { segments as usize }; diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 54254611..3eacee0b 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -25,10 +25,12 @@ def_package! { #[export_module] mod time_functions { + /// Create a timestamp containing the current system time. pub fn timestamp() -> Instant { Instant::now() } + /// Return the number of seconds between the current system time and the timestamp. #[rhai_fn(name = "elapsed", get = "elapsed", return_raw)] pub fn elapsed(timestamp: Instant) -> RhaiResult { #[cfg(not(feature = "no_float"))] @@ -55,6 +57,7 @@ mod time_functions { } } + /// Return the number of seconds between two timestamps. #[rhai_fn(return_raw, name = "-")] pub fn time_diff(timestamp1: Instant, timestamp2: Instant) -> RhaiResult { #[cfg(not(feature = "no_float"))] @@ -140,19 +143,23 @@ mod time_functions { } } + /// Add the specified number of `seconds` to the timestamp and return it as a new timestamp. #[rhai_fn(return_raw, name = "+")] pub fn add(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf { add_impl(timestamp, seconds) } + /// Add the specified number of `seconds` to the timestamp. #[rhai_fn(return_raw, name = "+=")] pub fn add_assign(timestamp: &mut Instant, seconds: FLOAT) -> RhaiResultOf<()> { *timestamp = add_impl(*timestamp, seconds)?; Ok(()) } + /// Subtract the specified number of `seconds` from the timestamp and return it as a new timestamp. #[rhai_fn(return_raw, name = "-")] pub fn subtract(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf { subtract_impl(timestamp, seconds) } + /// Subtract the specified number of `seconds` from the timestamp. #[rhai_fn(return_raw, name = "-=")] pub fn subtract_assign(timestamp: &mut Instant, seconds: FLOAT) -> RhaiResultOf<()> { *timestamp = subtract_impl(*timestamp, seconds)?; @@ -193,45 +200,55 @@ mod time_functions { } } + /// Add the specified number of `seconds` to the timestamp and return it as a new timestamp. #[rhai_fn(return_raw, name = "+")] pub fn add(timestamp: Instant, seconds: INT) -> RhaiResultOf { add_impl(timestamp, seconds) } + /// Add the specified number of `seconds` to the timestamp. #[rhai_fn(return_raw, name = "+=")] pub fn add_assign(timestamp: &mut Instant, seconds: INT) -> RhaiResultOf<()> { *timestamp = add_impl(*timestamp, seconds)?; Ok(()) } + /// Subtract the specified number of `seconds` from the timestamp and return it as a new timestamp. #[rhai_fn(return_raw, name = "-")] pub fn subtract(timestamp: Instant, seconds: INT) -> RhaiResultOf { subtract_impl(timestamp, seconds) } + /// Subtract the specified number of `seconds` from the timestamp. #[rhai_fn(return_raw, name = "-=")] pub fn subtract_assign(timestamp: &mut Instant, seconds: INT) -> RhaiResultOf<()> { *timestamp = subtract_impl(*timestamp, seconds)?; Ok(()) } + /// Return `true` if two timestamps are equal. #[rhai_fn(name = "==")] pub fn eq(timestamp1: Instant, timestamp2: Instant) -> bool { timestamp1 == timestamp2 } + /// Return `true` if two timestamps are not equal. #[rhai_fn(name = "!=")] pub fn ne(timestamp1: Instant, timestamp2: Instant) -> bool { timestamp1 != timestamp2 } + /// Return `true` if the first timestamp is earlier than the second. #[rhai_fn(name = "<")] pub fn lt(timestamp1: Instant, timestamp2: Instant) -> bool { timestamp1 < timestamp2 } + /// Return `true` if the first timestamp is earlier than or equals to the second. #[rhai_fn(name = "<=")] pub fn lte(timestamp1: Instant, timestamp2: Instant) -> bool { timestamp1 <= timestamp2 } + /// Return `true` if the first timestamp is later than the second. #[rhai_fn(name = ">")] pub fn gt(timestamp1: Instant, timestamp2: Instant) -> bool { timestamp1 > timestamp2 } + /// Return `true` if the first timestamp is later than or equals to the second. #[rhai_fn(name = ">=")] pub fn gte(timestamp1: Instant, timestamp2: Instant) -> bool { timestamp1 >= timestamp2 From d843baca12797f58709bffda200f99ae43f5812c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 16 Jan 2022 22:45:25 +0800 Subject: [PATCH 22/27] Add event handler examples. --- examples/event_handler_js/main.rs | 153 ++++++++++++++++++++++++ examples/event_handler_js/script.rhai | 49 ++++++++ examples/event_handler_main/main.rs | 129 ++++++++++++++++++++ examples/event_handler_main/script.rhai | 56 +++++++++ examples/event_handler_map/main.rs | 138 +++++++++++++++++++++ examples/event_handler_map/script.rhai | 57 +++++++++ 6 files changed, 582 insertions(+) create mode 100644 examples/event_handler_js/main.rs create mode 100644 examples/event_handler_js/script.rhai create mode 100644 examples/event_handler_main/main.rs create mode 100644 examples/event_handler_main/script.rhai create mode 100644 examples/event_handler_map/main.rs create mode 100644 examples/event_handler_map/script.rhai diff --git a/examples/event_handler_js/main.rs b/examples/event_handler_js/main.rs new file mode 100644 index 00000000..e47233d6 --- /dev/null +++ b/examples/event_handler_js/main.rs @@ -0,0 +1,153 @@ +//! Implementation of the Event Handler With State Pattern - JS Style +use rhai::{Dynamic, Engine, Map, Scope, AST}; + +use std::io::{stdin, stdout, Write}; + +const SCRIPT_FILE: &str = "event_handler_js/script.rhai"; + +#[derive(Debug)] +struct Handler { + pub engine: Engine, + pub scope: Scope<'static>, + pub states: Dynamic, + pub ast: AST, +} + +fn print_scope(scope: &Scope) { + scope + .iter_raw() + .enumerate() + .for_each(|(i, (name, constant, value))| { + println!( + "[{}] {}{}{} = {:?}", + i + 1, + if constant { "const " } else { "" }, + name, + if value.is_shared() { " (shared)" } else { "" }, + *value.read_lock::().unwrap(), + ) + }); + println!(); +} + +pub fn main() { + println!("Events Handler Example - JS Style"); + println!("=================================="); + + let mut input = String::new(); + + // Read script file + print!("Script file [{}]: ", SCRIPT_FILE); + stdout().flush().expect("flush stdout"); + + input.clear(); + + stdin().read_line(&mut input).expect("read input"); + + let path = match input.trim() { + "" => SCRIPT_FILE, + path => path, + }; + + // Create Engine + let engine = Engine::new(); + + // Use an object map to hold state + let mut states = Map::new(); + + // Default states can be added + states.insert("bool_state".into(), Dynamic::FALSE); + + // Convert the object map into 'Dynamic' + let mut states: Dynamic = states.into(); + + // Create a custom 'Scope' to hold state + let mut scope = Scope::new(); + + // Add any system-provided state into the custom 'Scope'. + // Constants can be used to optimize the script. + scope.push_constant("MY_CONSTANT", 42_i64); + + // Compile the handler script. + println!("> Loading script file: {}", path); + + let ast = match engine.compile_file_with_scope(&mut scope, path.into()) { + Ok(ast) => ast, + Err(err) => { + eprintln!("! Error: {}", err); + println!("Cannot continue. Bye!"); + return; + } + }; + + println!("> Script file loaded."); + println!(); + println!("quit = exit program"); + println!("scope = print scope"); + println!("states = print states"); + println!("event arg = run function with argument"); + println!(); + + // Run the 'init' function to initialize the state, retaining variables. + let result = engine.call_fn_raw(&mut scope, &ast, false, true, "init", Some(&mut states), []); + + if let Err(err) = result { + eprintln!("! {}", err) + } + + // Create handler instance + let mut handler = Handler { + engine, + scope, + states, + ast, + }; + + // Events loop + loop { + print!("event> "); + stdout().flush().expect("flush stdout"); + + // Read event + input.clear(); + stdin().read_line(&mut input).expect("read input"); + + let mut fields = input.trim().splitn(2, ' '); + + let event = fields.next().expect("event").trim(); + let arg = fields.next().unwrap_or(""); + + // Process event + match event { + "quit" => break, + + "scope" => { + print_scope(&handler.scope); + continue; + } + + "states" => { + println!("{:?}", handler.states); + println!(); + continue; + } + + // Map all other events to function calls + _ => { + let engine = &handler.engine; + let scope = &mut handler.scope; + let ast = &handler.ast; + let this_ptr = Some(&mut handler.states); + + let result = + engine.call_fn_raw(scope, ast, false, true, event, this_ptr, [arg.into()]); + + if let Err(err) = result { + eprintln!("! {}", err) + } + } + } + } + + println!("Bye!"); +} diff --git a/examples/event_handler_js/script.rhai b/examples/event_handler_js/script.rhai new file mode 100644 index 00000000..7c488515 --- /dev/null +++ b/examples/event_handler_js/script.rhai @@ -0,0 +1,49 @@ +// Implementation of the Event Handler With State Pattern - JS Style + +/// Initialize user-provided state. +fn init() { + // Can detect system-provided default states! + // Add 'bool_state' as new state variable if one does not exist + if !("bool_state" in this) { + this.bool_state = false; + } + // Add 'value' as new state variable (overwrites any existing) + this.value = 0; + + // Can also add OOP-style functions! + this.log = |x| print(`State = ${this.value}, data = ${x}`); +} + +/// 'start' event handler +fn start(data) { + if this.bool_state { + throw "Already started!"; + } + if this.value <= 0 { + throw "Conditions not yet ready to start!"; + } + this.bool_state = true; + this.value += parse_int(data); + + // Constant 'MY_CONSTANT' in custom scope is also visible! + print(`MY_CONSTANT = ${MY_CONSTANT}`); +} + +/// 'end' event handler +fn end(data) { + if !this.bool_state { + throw "Not yet started!"; + } + if this.value > 0 { + throw "Conditions not yet ready to end!"; + } + this.bool_state = false; + this.value = parse_int(data); +} + +/// 'update' event handler +fn update(data) { + let data = parse_int(data); + this.value += data; + this.log(data); +} diff --git a/examples/event_handler_main/main.rs b/examples/event_handler_main/main.rs new file mode 100644 index 00000000..5fddb1af --- /dev/null +++ b/examples/event_handler_main/main.rs @@ -0,0 +1,129 @@ +//! Implementation of the Event Handler With State Pattern - Main Style +use rhai::{Dynamic, Engine, Scope, AST}; + +use std::io::{stdin, stdout, Write}; + +const SCRIPT_FILE: &str = "event_handler_main/script.rhai"; + +#[derive(Debug)] +struct Handler { + pub engine: Engine, + pub scope: Scope<'static>, + pub ast: AST, +} + +fn print_scope(scope: &Scope) { + scope + .iter_raw() + .enumerate() + .for_each(|(i, (name, constant, value))| { + println!( + "[{}] {}{}{} = {:?}", + i + 1, + if constant { "const " } else { "" }, + name, + if value.is_shared() { " (shared)" } else { "" }, + *value.read_lock::().unwrap(), + ) + }); + println!(); +} + +pub fn main() { + println!("Events Handler Example - Main Style"); + println!("==================================="); + + let mut input = String::new(); + + // Read script file + print!("Script file [{}]: ", SCRIPT_FILE); + stdout().flush().expect("flush stdout"); + + input.clear(); + + stdin().read_line(&mut input).expect("read input"); + + let path = match input.trim() { + "" => SCRIPT_FILE, + path => path, + }; + + // Create Engine + let engine = Engine::new(); + + // Create a custom 'Scope' to hold state + let mut scope = Scope::new(); + + // Add any system-provided state into the custom 'Scope'. + // Constants can be used to optimize the script. + scope.push_constant("MY_CONSTANT", 42_i64); + + // Compile the handler script. + println!("> Loading script file: {}", path); + + let ast = match engine.compile_file_with_scope(&mut scope, path.into()) { + Ok(ast) => ast, + Err(err) => { + eprintln!("! Error: {}", err); + println!("Cannot continue. Bye!"); + return; + } + }; + + println!("> Script file loaded."); + println!(); + println!("quit = exit program"); + println!("scope = print scope"); + println!("event arg = run function with argument"); + println!(); + + // Run the 'init' function to initialize the state, retaining variables. + let result = engine.call_fn_raw(&mut scope, &ast, false, false, "init", None, []); + + if let Err(err) = result { + eprintln!("! {}", err) + } + + // Create handler instance + let mut handler = Handler { engine, scope, ast }; + + // Events loop + loop { + print!("event> "); + stdout().flush().expect("flush stdout"); + + // Read event + input.clear(); + stdin().read_line(&mut input).expect("read input"); + + let mut fields = input.trim().splitn(2, ' '); + + let event = fields.next().expect("event").trim(); + let arg = fields.next().unwrap_or(""); + + // Process event + match event { + "quit" => break, + + "scope" => { + print_scope(&handler.scope); + continue; + } + + // Map all other events to function calls + _ => { + let engine = &handler.engine; + let scope = &mut handler.scope; + let ast = &handler.ast; + + let result: Result<(), _> = engine.call_fn(scope, ast, event, (arg.to_string(),)); + + if let Err(err) = result { + eprintln!("! {}", err) + } + } + } + } + + println!("Bye!"); +} diff --git a/examples/event_handler_main/script.rhai b/examples/event_handler_main/script.rhai new file mode 100644 index 00000000..641c2372 --- /dev/null +++ b/examples/event_handler_main/script.rhai @@ -0,0 +1,56 @@ +// Implementation of the Event Handler With State Pattern - Main Style + +/// Initialize user-provided state (shadows system-provided state, if any). +fn init() { + // Add 'bool_state' and 'obj_state' as new state variables + let bool_state = false; + let value = 0; + + // Constants can also be added! + const EXTRA_CONSTANT = "hello, world!"; +} + +/// Without 'OOP' support, the can only be a function. +fn log(value, data) { + print(`State = ${value}, data = ${data}`); +} + +/// 'start' event handler +fn start(data) { + if bool_state { + throw "Already started!"; + } + if value <= 0 { + throw "Conditions not yet ready to start!"; + } + bool_state = true; + + // Constants 'MY_CONSTANT' and 'EXTRA_CONSTANT' + // in custom scope are also visible! + print(`MY_CONSTANT = ${MY_CONSTANT}`); + print(`EXTRA_CONSTANT = ${EXTRA_CONSTANT}`); + + value += parse_int(data); +} + +/// 'end' event handler +fn end(data) { + if !bool_state { + throw "Not yet started!"; + } + if value > 0 { + throw "Conditions not yet ready to end!"; + } + bool_state = false; + value = parse_int(data); +} + +/// 'update' event handler +fn update(data) { + let data = parse_int(data); + + value += data; + + // Without OOP support, can only call function + log(value, data); +} diff --git a/examples/event_handler_map/main.rs b/examples/event_handler_map/main.rs new file mode 100644 index 00000000..5a6c8aa1 --- /dev/null +++ b/examples/event_handler_map/main.rs @@ -0,0 +1,138 @@ +//! Implementation of the Event Handler With State Pattern - Map Style +use rhai::{Dynamic, Engine, Map, Scope, AST}; + +use std::io::{stdin, stdout, Write}; + +const SCRIPT_FILE: &str = "event_handler_map/script.rhai"; + +#[derive(Debug)] +struct Handler { + pub engine: Engine, + pub scope: Scope<'static>, + pub ast: AST, +} + +fn print_scope(scope: &Scope) { + scope + .iter_raw() + .enumerate() + .for_each(|(i, (name, constant, value))| { + println!( + "[{}] {}{}{} = {:?}", + i + 1, + if constant { "const " } else { "" }, + name, + if value.is_shared() { " (shared)" } else { "" }, + *value.read_lock::().unwrap(), + ) + }); + println!(); +} + +pub fn main() { + println!("Events Handler Example - Map Style"); + println!("=================================="); + + let mut input = String::new(); + + // Read script file + print!("Script file [{}]: ", SCRIPT_FILE); + stdout().flush().expect("flush stdout"); + + input.clear(); + + stdin().read_line(&mut input).expect("read input"); + + let path = match input.trim() { + "" => SCRIPT_FILE, + path => path, + }; + + // Create Engine + let engine = Engine::new(); + + // Create a custom 'Scope' to hold state + let mut scope = Scope::new(); + + // Add any system-provided state into the custom 'Scope'. + // Constants can be used to optimize the script. + scope.push_constant("MY_CONSTANT", 42_i64); + + // Use an object map to hold state + let mut states = Map::new(); + + // Default states can be added + states.insert("bool_state".into(), Dynamic::FALSE); + + // Add the main states-holding object map and call it 'state' + scope.push("state", Map::new()); + + // Compile the handler script. + println!("> Loading script file: {}", path); + + let ast = match engine.compile_file_with_scope(&mut scope, path.into()) { + Ok(ast) => ast, + Err(err) => { + eprintln!("! Error: {}", err); + println!("Cannot continue. Bye!"); + return; + } + }; + + println!("> Script file loaded."); + println!(); + println!("quit = exit program"); + println!("scope = print scope"); + println!("event arg = run function with argument"); + println!(); + + // Run the 'init' function to initialize the state, retaining variables. + let result: Result<(), _> = engine.call_fn(&mut scope, &ast, "init", ()); + + if let Err(err) = result { + eprintln!("! {}", err) + } + + // Create handler instance + let mut handler = Handler { engine, scope, ast }; + + // Events loop + loop { + print!("event> "); + stdout().flush().expect("flush stdout"); + + // Read event + input.clear(); + stdin().read_line(&mut input).expect("read input"); + + let mut fields = input.trim().splitn(2, ' '); + + let event = fields.next().expect("event").trim(); + let arg = fields.next().unwrap_or(""); + + // Process event + match event { + "quit" => break, + + "scope" => { + print_scope(&handler.scope); + continue; + } + + // Map all other events to function calls + _ => { + let engine = &handler.engine; + let scope = &mut handler.scope; + let ast = &handler.ast; + + let result: Result<(), _> = engine.call_fn(scope, ast, event, (arg.to_string(),)); + + if let Err(err) = result { + eprintln!("! {}", err) + } + } + } + } + + println!("Bye!"); +} diff --git a/examples/event_handler_map/script.rhai b/examples/event_handler_map/script.rhai new file mode 100644 index 00000000..988ca170 --- /dev/null +++ b/examples/event_handler_map/script.rhai @@ -0,0 +1,57 @@ +// Implementation of the Event Handler With State Pattern - Map Style + +/// Initialize user-provided state. +/// State is stored inside an object map bound to 'state'. +fn init() { + // Add 'bool_state' as new state variable if one does not exist + if !("bool_state" in state) { + state.bool_state = false; + } + // Add 'obj_state' as new state variable (overwrites any existing) + state.value = 0; + + // Can also add OOP-style functions! + state.log = |x| print(`State = ${this.value}, data = ${x}`); +} + +/// 'start' event handler +fn start(data) { + // Can detect system-provided default states! + // Access state variables in 'state' + if state.bool_state { + throw "Already started!"; + } + + // New values can be added to the state + state.start_mode = data; + + if state.value <= 0 { + throw "Conditions not yet ready to start!"; + } + state.bool_state = true; + state.value = parse_int(data); + + // Constant 'MY_CONSTANT' in custom scope is also visible! + print(`MY_CONSTANT = ${MY_CONSTANT}`); +} + +/// 'end' event handler +fn end(data) { + if !state.bool_state || !("start_mode" in state) { + throw "Not yet started!"; + } + if state.value > 0 { + throw "Conditions not yet ready to end!"; + } + state.bool_state = false; + state.value = parse_int(data); +} + +/// 'update' event handler +fn update(data) { + let data = parse_int(data); + state.value += data; + + // Call user-defined function OOP-style! + state.log(data); +} From 146129279c7e325d1bbea0cedc5a657f25b0734a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 16 Jan 2022 22:45:49 +0800 Subject: [PATCH 23/27] Refine examples. --- examples/arrays_and_structs.rs | 10 +++------- examples/custom_types_and_methods.rs | 8 ++------ examples/serde.rs | 23 ++++++++--------------- examples/strings.rs | 6 ++++-- src/eval/chaining.rs | 16 ++++++++-------- src/eval/target.rs | 12 +++++++----- 6 files changed, 32 insertions(+), 43 deletions(-) diff --git a/examples/arrays_and_structs.rs b/examples/arrays_and_structs.rs index dab0880b..c27e997d 100644 --- a/examples/arrays_and_structs.rs +++ b/examples/arrays_and_structs.rs @@ -1,3 +1,6 @@ +#![cfg(not(feature = "no_index"))] +#![cfg(not(feature = "no_object"))] + use rhai::{Engine, EvalAltResult}; #[derive(Debug, Clone)] @@ -14,8 +17,6 @@ impl TestStruct { } } -#[cfg(not(feature = "no_index"))] -#[cfg(not(feature = "no_object"))] fn main() -> Result<(), Box> { let mut engine = Engine::new(); @@ -46,8 +47,3 @@ fn main() -> Result<(), Box> { Ok(()) } - -#[cfg(any(feature = "no_index", feature = "no_object"))] -fn main() { - panic!("This example does not run under 'no_index' or 'no_object'.") -} diff --git a/examples/custom_types_and_methods.rs b/examples/custom_types_and_methods.rs index b52d0a27..f420acd1 100644 --- a/examples/custom_types_and_methods.rs +++ b/examples/custom_types_and_methods.rs @@ -1,3 +1,5 @@ +#![cfg(not(feature = "no_object"))] + use rhai::{Engine, EvalAltResult}; #[derive(Debug, Clone)] @@ -15,7 +17,6 @@ impl TestStruct { } } -#[cfg(not(feature = "no_object"))] fn main() -> Result<(), Box> { let mut engine = Engine::new(); @@ -36,8 +37,3 @@ fn main() -> Result<(), Box> { Ok(()) } - -#[cfg(feature = "no_object")] -fn main() { - panic!("This example does not run under 'no_object'."); -} diff --git a/examples/serde.rs b/examples/serde.rs index f5554889..dd4e915f 100644 --- a/examples/serde.rs +++ b/examples/serde.rs @@ -1,19 +1,6 @@ -#[cfg(any(not(feature = "serde"), feature = "no_object"))] -fn main() { - println!("This example requires the 'serde' feature to run."); - println!("Try: cargo run --features serde --example serde"); -} +#![cfg(feature = "serde")] +#![cfg(not(feature = "no_object"))] -#[cfg(feature = "serde")] -#[cfg(not(feature = "no_object"))] -fn main() { - example::ser(); - println!(); - example::de(); -} - -#[cfg(feature = "serde")] -#[cfg(not(feature = "no_object"))] mod example { use rhai::serde::{from_dynamic, to_dynamic}; use rhai::{Dynamic, Engine, Map}; @@ -88,3 +75,9 @@ mod example { println!("Deserialized to struct: {:#?}", x); } } + +fn main() { + example::ser(); + println!(); + example::de(); +} diff --git a/examples/strings.rs b/examples/strings.rs index a0997da2..33af68c0 100644 --- a/examples/strings.rs +++ b/examples/strings.rs @@ -1,5 +1,7 @@ ///! This example registers a variety of functions that operate on strings. ///! Remember to use `ImmutableString` or `&str` instead of `String` as parameters. +#![cfg(not(feature = "no_object"))] + use rhai::{Engine, EvalAltResult, ImmutableString, Scope}; use std::io::{stdin, stdout, Write}; @@ -64,10 +66,10 @@ fn main() -> Result<(), Box> { engine.run_with_scope( &mut scope, r#" - display("Length", x.len()); + display("Length", x.len); x.trim(); display("Trimmed", x); - display("Trimmed Length", x.len()); + display("Trimmed Length", x.len); display("Index of \"!!!\"", x.index_of("!!!")); "#, )?; diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index 6ac788fd..d2589f0b 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -1,7 +1,7 @@ //! Types to support chaining operations (i.e. indexing and dotting). #![cfg(any(not(feature = "no_index"), not(feature = "no_object")))] -use super::{calc_index, EvalState, GlobalRuntimeState, Target}; +use super::{EvalState, GlobalRuntimeState, Target}; use crate::ast::{Expr, OpAssignment}; use crate::types::dynamic::Union; use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, ERR}; @@ -790,7 +790,7 @@ impl Engine { .as_int() .map_err(|typ| self.make_type_mismatch_err::(typ, pos))?; let len = arr.len(); - let arr_idx = calc_index(len, index, true, || { + let arr_idx = super::calc_index(len, index, true, || { ERR::ErrorArrayBounds(len, index, pos).into() })?; @@ -804,7 +804,7 @@ impl Engine { .as_int() .map_err(|typ| self.make_type_mismatch_err::(typ, pos))?; let len = arr.len(); - let arr_idx = calc_index(len, index, true, || { + let arr_idx = super::calc_index(len, index, true, || { ERR::ErrorArrayBounds(len, index, pos).into() })?; @@ -845,10 +845,10 @@ impl Engine { let start = range.start; let end = range.end; - let start = calc_index(BITS, start, false, || { + let start = super::calc_index(BITS, start, false, || { ERR::ErrorBitFieldBounds(BITS, start, pos).into() })?; - let end = calc_index(BITS, end, false, || { + let end = super::calc_index(BITS, end, false, || { ERR::ErrorBitFieldBounds(BITS, end, pos).into() })?; @@ -870,10 +870,10 @@ impl Engine { let start = *range.start(); let end = *range.end(); - let start = calc_index(BITS, start, false, || { + let start = super::calc_index(BITS, start, false, || { ERR::ErrorBitFieldBounds(BITS, start, pos).into() })?; - let end = calc_index(BITS, end, false, || { + let end = super::calc_index(BITS, end, false, || { ERR::ErrorBitFieldBounds(BITS, end, pos).into() })?; @@ -914,7 +914,7 @@ impl Engine { const BITS: usize = std::mem::size_of::() * 8; - let bit = calc_index(BITS, index, true, || { + let bit = super::calc_index(BITS, index, true, || { ERR::ErrorBitFieldBounds(BITS, index, pos).into() })?; diff --git a/src/eval/target.rs b/src/eval/target.rs index 4209c3fa..f31647f9 100644 --- a/src/eval/target.rs +++ b/src/eval/target.rs @@ -1,7 +1,7 @@ //! Type to hold a mutable reference to the target of an evaluation. use crate::types::dynamic::Variant; -use crate::{Dynamic, RhaiResultOf, INT}; +use crate::{Dynamic, RhaiResultOf}; use std::ops::{Deref, DerefMut}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -11,8 +11,9 @@ use std::prelude::v1::*; // Negative starting positions count from the end. // // Values going over bounds are limited to the actual length. -#[inline(always)] -pub fn calc_offset_len(length: usize, start: INT, len: INT) -> (usize, usize) { +#[inline] +#[allow(dead_code)] +pub fn calc_offset_len(length: usize, start: crate::INT, len: crate::INT) -> (usize, usize) { let start = if start < 0 { start.checked_abs().map_or(0, |positive_start| { length - usize::min(positive_start as usize, length) @@ -39,10 +40,11 @@ pub fn calc_offset_len(length: usize, start: INT, len: INT) -> (usize, usize) { // Negative starting positions count from the end. // // Values going over bounds call the provided closure to return a default value or an error. -#[inline(always)] +#[inline] +#[allow(dead_code)] pub fn calc_index( length: usize, - start: INT, + start: crate::INT, negative_count_from_end: bool, err: impl Fn() -> Result, ) -> Result { From 5935a889587333aa658c83629d1896469cc0e7ff Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 16 Jan 2022 22:50:39 +0800 Subject: [PATCH 24/27] Revert "Refine examples." This reverts commit 146129279c7e325d1bbea0cedc5a657f25b0734a. --- examples/arrays_and_structs.rs | 10 +++++++--- examples/custom_types_and_methods.rs | 8 ++++++-- examples/serde.rs | 23 +++++++++++++++-------- examples/strings.rs | 6 ++---- src/eval/chaining.rs | 16 ++++++++-------- src/eval/target.rs | 12 +++++------- 6 files changed, 43 insertions(+), 32 deletions(-) diff --git a/examples/arrays_and_structs.rs b/examples/arrays_and_structs.rs index c27e997d..dab0880b 100644 --- a/examples/arrays_and_structs.rs +++ b/examples/arrays_and_structs.rs @@ -1,6 +1,3 @@ -#![cfg(not(feature = "no_index"))] -#![cfg(not(feature = "no_object"))] - use rhai::{Engine, EvalAltResult}; #[derive(Debug, Clone)] @@ -17,6 +14,8 @@ impl TestStruct { } } +#[cfg(not(feature = "no_index"))] +#[cfg(not(feature = "no_object"))] fn main() -> Result<(), Box> { let mut engine = Engine::new(); @@ -47,3 +46,8 @@ fn main() -> Result<(), Box> { Ok(()) } + +#[cfg(any(feature = "no_index", feature = "no_object"))] +fn main() { + panic!("This example does not run under 'no_index' or 'no_object'.") +} diff --git a/examples/custom_types_and_methods.rs b/examples/custom_types_and_methods.rs index f420acd1..b52d0a27 100644 --- a/examples/custom_types_and_methods.rs +++ b/examples/custom_types_and_methods.rs @@ -1,5 +1,3 @@ -#![cfg(not(feature = "no_object"))] - use rhai::{Engine, EvalAltResult}; #[derive(Debug, Clone)] @@ -17,6 +15,7 @@ impl TestStruct { } } +#[cfg(not(feature = "no_object"))] fn main() -> Result<(), Box> { let mut engine = Engine::new(); @@ -37,3 +36,8 @@ fn main() -> Result<(), Box> { Ok(()) } + +#[cfg(feature = "no_object")] +fn main() { + panic!("This example does not run under 'no_object'."); +} diff --git a/examples/serde.rs b/examples/serde.rs index dd4e915f..f5554889 100644 --- a/examples/serde.rs +++ b/examples/serde.rs @@ -1,6 +1,19 @@ -#![cfg(feature = "serde")] -#![cfg(not(feature = "no_object"))] +#[cfg(any(not(feature = "serde"), feature = "no_object"))] +fn main() { + println!("This example requires the 'serde' feature to run."); + println!("Try: cargo run --features serde --example serde"); +} +#[cfg(feature = "serde")] +#[cfg(not(feature = "no_object"))] +fn main() { + example::ser(); + println!(); + example::de(); +} + +#[cfg(feature = "serde")] +#[cfg(not(feature = "no_object"))] mod example { use rhai::serde::{from_dynamic, to_dynamic}; use rhai::{Dynamic, Engine, Map}; @@ -75,9 +88,3 @@ mod example { println!("Deserialized to struct: {:#?}", x); } } - -fn main() { - example::ser(); - println!(); - example::de(); -} diff --git a/examples/strings.rs b/examples/strings.rs index 33af68c0..a0997da2 100644 --- a/examples/strings.rs +++ b/examples/strings.rs @@ -1,7 +1,5 @@ ///! This example registers a variety of functions that operate on strings. ///! Remember to use `ImmutableString` or `&str` instead of `String` as parameters. -#![cfg(not(feature = "no_object"))] - use rhai::{Engine, EvalAltResult, ImmutableString, Scope}; use std::io::{stdin, stdout, Write}; @@ -66,10 +64,10 @@ fn main() -> Result<(), Box> { engine.run_with_scope( &mut scope, r#" - display("Length", x.len); + display("Length", x.len()); x.trim(); display("Trimmed", x); - display("Trimmed Length", x.len); + display("Trimmed Length", x.len()); display("Index of \"!!!\"", x.index_of("!!!")); "#, )?; diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index d2589f0b..6ac788fd 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -1,7 +1,7 @@ //! Types to support chaining operations (i.e. indexing and dotting). #![cfg(any(not(feature = "no_index"), not(feature = "no_object")))] -use super::{EvalState, GlobalRuntimeState, Target}; +use super::{calc_index, EvalState, GlobalRuntimeState, Target}; use crate::ast::{Expr, OpAssignment}; use crate::types::dynamic::Union; use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, ERR}; @@ -790,7 +790,7 @@ impl Engine { .as_int() .map_err(|typ| self.make_type_mismatch_err::(typ, pos))?; let len = arr.len(); - let arr_idx = super::calc_index(len, index, true, || { + let arr_idx = calc_index(len, index, true, || { ERR::ErrorArrayBounds(len, index, pos).into() })?; @@ -804,7 +804,7 @@ impl Engine { .as_int() .map_err(|typ| self.make_type_mismatch_err::(typ, pos))?; let len = arr.len(); - let arr_idx = super::calc_index(len, index, true, || { + let arr_idx = calc_index(len, index, true, || { ERR::ErrorArrayBounds(len, index, pos).into() })?; @@ -845,10 +845,10 @@ impl Engine { let start = range.start; let end = range.end; - let start = super::calc_index(BITS, start, false, || { + let start = calc_index(BITS, start, false, || { ERR::ErrorBitFieldBounds(BITS, start, pos).into() })?; - let end = super::calc_index(BITS, end, false, || { + let end = calc_index(BITS, end, false, || { ERR::ErrorBitFieldBounds(BITS, end, pos).into() })?; @@ -870,10 +870,10 @@ impl Engine { let start = *range.start(); let end = *range.end(); - let start = super::calc_index(BITS, start, false, || { + let start = calc_index(BITS, start, false, || { ERR::ErrorBitFieldBounds(BITS, start, pos).into() })?; - let end = super::calc_index(BITS, end, false, || { + let end = calc_index(BITS, end, false, || { ERR::ErrorBitFieldBounds(BITS, end, pos).into() })?; @@ -914,7 +914,7 @@ impl Engine { const BITS: usize = std::mem::size_of::() * 8; - let bit = super::calc_index(BITS, index, true, || { + let bit = calc_index(BITS, index, true, || { ERR::ErrorBitFieldBounds(BITS, index, pos).into() })?; diff --git a/src/eval/target.rs b/src/eval/target.rs index f31647f9..4209c3fa 100644 --- a/src/eval/target.rs +++ b/src/eval/target.rs @@ -1,7 +1,7 @@ //! Type to hold a mutable reference to the target of an evaluation. use crate::types::dynamic::Variant; -use crate::{Dynamic, RhaiResultOf}; +use crate::{Dynamic, RhaiResultOf, INT}; use std::ops::{Deref, DerefMut}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -11,9 +11,8 @@ use std::prelude::v1::*; // Negative starting positions count from the end. // // Values going over bounds are limited to the actual length. -#[inline] -#[allow(dead_code)] -pub fn calc_offset_len(length: usize, start: crate::INT, len: crate::INT) -> (usize, usize) { +#[inline(always)] +pub fn calc_offset_len(length: usize, start: INT, len: INT) -> (usize, usize) { let start = if start < 0 { start.checked_abs().map_or(0, |positive_start| { length - usize::min(positive_start as usize, length) @@ -40,11 +39,10 @@ pub fn calc_offset_len(length: usize, start: crate::INT, len: crate::INT) -> (us // Negative starting positions count from the end. // // Values going over bounds call the provided closure to return a default value or an error. -#[inline] -#[allow(dead_code)] +#[inline(always)] pub fn calc_index( length: usize, - start: crate::INT, + start: INT, negative_count_from_end: bool, err: impl Fn() -> Result, ) -> Result { From 24b68971a889edcbb95d7a827296db0f91e45ede Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 16 Jan 2022 22:54:28 +0800 Subject: [PATCH 25/27] Fix offset calc. --- src/eval/chaining.rs | 16 ++++++++-------- src/eval/target.rs | 12 +++++++----- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index 6ac788fd..d2589f0b 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -1,7 +1,7 @@ //! Types to support chaining operations (i.e. indexing and dotting). #![cfg(any(not(feature = "no_index"), not(feature = "no_object")))] -use super::{calc_index, EvalState, GlobalRuntimeState, Target}; +use super::{EvalState, GlobalRuntimeState, Target}; use crate::ast::{Expr, OpAssignment}; use crate::types::dynamic::Union; use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, ERR}; @@ -790,7 +790,7 @@ impl Engine { .as_int() .map_err(|typ| self.make_type_mismatch_err::(typ, pos))?; let len = arr.len(); - let arr_idx = calc_index(len, index, true, || { + let arr_idx = super::calc_index(len, index, true, || { ERR::ErrorArrayBounds(len, index, pos).into() })?; @@ -804,7 +804,7 @@ impl Engine { .as_int() .map_err(|typ| self.make_type_mismatch_err::(typ, pos))?; let len = arr.len(); - let arr_idx = calc_index(len, index, true, || { + let arr_idx = super::calc_index(len, index, true, || { ERR::ErrorArrayBounds(len, index, pos).into() })?; @@ -845,10 +845,10 @@ impl Engine { let start = range.start; let end = range.end; - let start = calc_index(BITS, start, false, || { + let start = super::calc_index(BITS, start, false, || { ERR::ErrorBitFieldBounds(BITS, start, pos).into() })?; - let end = calc_index(BITS, end, false, || { + let end = super::calc_index(BITS, end, false, || { ERR::ErrorBitFieldBounds(BITS, end, pos).into() })?; @@ -870,10 +870,10 @@ impl Engine { let start = *range.start(); let end = *range.end(); - let start = calc_index(BITS, start, false, || { + let start = super::calc_index(BITS, start, false, || { ERR::ErrorBitFieldBounds(BITS, start, pos).into() })?; - let end = calc_index(BITS, end, false, || { + let end = super::calc_index(BITS, end, false, || { ERR::ErrorBitFieldBounds(BITS, end, pos).into() })?; @@ -914,7 +914,7 @@ impl Engine { const BITS: usize = std::mem::size_of::() * 8; - let bit = calc_index(BITS, index, true, || { + let bit = super::calc_index(BITS, index, true, || { ERR::ErrorBitFieldBounds(BITS, index, pos).into() })?; diff --git a/src/eval/target.rs b/src/eval/target.rs index 4209c3fa..f31647f9 100644 --- a/src/eval/target.rs +++ b/src/eval/target.rs @@ -1,7 +1,7 @@ //! Type to hold a mutable reference to the target of an evaluation. use crate::types::dynamic::Variant; -use crate::{Dynamic, RhaiResultOf, INT}; +use crate::{Dynamic, RhaiResultOf}; use std::ops::{Deref, DerefMut}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -11,8 +11,9 @@ use std::prelude::v1::*; // Negative starting positions count from the end. // // Values going over bounds are limited to the actual length. -#[inline(always)] -pub fn calc_offset_len(length: usize, start: INT, len: INT) -> (usize, usize) { +#[inline] +#[allow(dead_code)] +pub fn calc_offset_len(length: usize, start: crate::INT, len: crate::INT) -> (usize, usize) { let start = if start < 0 { start.checked_abs().map_or(0, |positive_start| { length - usize::min(positive_start as usize, length) @@ -39,10 +40,11 @@ pub fn calc_offset_len(length: usize, start: INT, len: INT) -> (usize, usize) { // Negative starting positions count from the end. // // Values going over bounds call the provided closure to return a default value or an error. -#[inline(always)] +#[inline] +#[allow(dead_code)] pub fn calc_index( length: usize, - start: INT, + start: crate::INT, negative_count_from_end: bool, err: impl Fn() -> Result, ) -> Result { From e366bd2106d9b27455d6ed926157b00806a92b94 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 16 Jan 2022 23:15:19 +0800 Subject: [PATCH 26/27] Pass metadata feature to rhai. --- codegen/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 8c471813..79a179b1 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -16,7 +16,7 @@ default = [] metadata = [] [dev-dependencies] -rhai = { path = "..", version = "1.4" } +rhai = { path = "..", version = "1.4", features = ["metadata"] } trybuild = "1" [dependencies] From 57cfd6a35407c79c09a72a3229a5c9f8f0e5f7f8 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 16 Jan 2022 23:15:37 +0800 Subject: [PATCH 27/27] Fix builds. --- examples/event_handler_js/main.rs | 19 +++++++++++++++++-- examples/event_handler_main/main.rs | 13 ++++++++++++- examples/event_handler_map/main.rs | 19 +++++++++++++++++-- src/packages/array_basic.rs | 6 +++--- 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/examples/event_handler_js/main.rs b/examples/event_handler_js/main.rs index e47233d6..770127ce 100644 --- a/examples/event_handler_js/main.rs +++ b/examples/event_handler_js/main.rs @@ -1,5 +1,8 @@ //! Implementation of the Event Handler With State Pattern - JS Style -use rhai::{Dynamic, Engine, Map, Scope, AST}; +use rhai::{Dynamic, Engine, Scope, AST}; + +#[cfg(not(feature = "no_object"))] +use rhai::Map; use std::io::{stdin, stdout, Write}; @@ -18,18 +21,25 @@ fn print_scope(scope: &Scope) { .iter_raw() .enumerate() .for_each(|(i, (name, constant, value))| { + #[cfg(not(feature = "no_closure"))] + let value_is_shared = if value.is_shared() { " (shared)" } else { "" }; + #[cfg(feature = "no_closure")] + let value_is_shared = ""; + println!( "[{}] {}{}{} = {:?}", i + 1, if constant { "const " } else { "" }, name, - if value.is_shared() { " (shared)" } else { "" }, + value_is_shared, *value.read_lock::().unwrap(), ) }); println!(); } +#[cfg(not(feature = "no_function"))] +#[cfg(not(feature = "no_object"))] pub fn main() { println!("Events Handler Example - JS Style"); println!("=================================="); @@ -151,3 +161,8 @@ pub fn main() { println!("Bye!"); } + +#[cfg(any(feature = "no_function", feature = "no_object"))] +pub fn main() { + panic!("This example does not run under 'no_function' or 'no_object'.") +} diff --git a/examples/event_handler_main/main.rs b/examples/event_handler_main/main.rs index 5fddb1af..750d6742 100644 --- a/examples/event_handler_main/main.rs +++ b/examples/event_handler_main/main.rs @@ -17,18 +17,24 @@ fn print_scope(scope: &Scope) { .iter_raw() .enumerate() .for_each(|(i, (name, constant, value))| { + #[cfg(not(feature = "no_closure"))] + let value_is_shared = if value.is_shared() { " (shared)" } else { "" }; + #[cfg(feature = "no_closure")] + let value_is_shared = ""; + println!( "[{}] {}{}{} = {:?}", i + 1, if constant { "const " } else { "" }, name, - if value.is_shared() { " (shared)" } else { "" }, + value_is_shared, *value.read_lock::().unwrap(), ) }); println!(); } +#[cfg(not(feature = "no_function"))] pub fn main() { println!("Events Handler Example - Main Style"); println!("==================================="); @@ -127,3 +133,8 @@ pub fn main() { println!("Bye!"); } + +#[cfg(feature = "no_function")] +pub fn main() { + panic!("This example does not run under 'no_function'.") +} diff --git a/examples/event_handler_map/main.rs b/examples/event_handler_map/main.rs index 5a6c8aa1..62e0a15f 100644 --- a/examples/event_handler_map/main.rs +++ b/examples/event_handler_map/main.rs @@ -1,5 +1,8 @@ //! Implementation of the Event Handler With State Pattern - Map Style -use rhai::{Dynamic, Engine, Map, Scope, AST}; +use rhai::{Dynamic, Engine, Scope, AST}; + +#[cfg(not(feature = "no_object"))] +use rhai::Map; use std::io::{stdin, stdout, Write}; @@ -17,18 +20,25 @@ fn print_scope(scope: &Scope) { .iter_raw() .enumerate() .for_each(|(i, (name, constant, value))| { + #[cfg(not(feature = "no_closure"))] + let value_is_shared = if value.is_shared() { " (shared)" } else { "" }; + #[cfg(feature = "no_closure")] + let value_is_shared = ""; + println!( "[{}] {}{}{} = {:?}", i + 1, if constant { "const " } else { "" }, name, - if value.is_shared() { " (shared)" } else { "" }, + value_is_shared, *value.read_lock::().unwrap(), ) }); println!(); } +#[cfg(not(feature = "no_function"))] +#[cfg(not(feature = "no_object"))] pub fn main() { println!("Events Handler Example - Map Style"); println!("=================================="); @@ -136,3 +146,8 @@ pub fn main() { println!("Bye!"); } + +#[cfg(any(feature = "no_function", feature = "no_object"))] +pub fn main() { + panic!("This example does not run under 'no_function' or 'no_object'.") +} diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 528a0a32..83af8683 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -5,7 +5,7 @@ use crate::engine::OP_EQUALS; use crate::eval::{calc_index, calc_offset_len}; use crate::plugin::*; use crate::{ - def_package, Array, Dynamic, Engine, ExclusiveRange, FnPtr, InclusiveRange, NativeCallContext, + def_package, Array, Dynamic, ExclusiveRange, FnPtr, InclusiveRange, NativeCallContext, Position, RhaiResultOf, StaticVec, ERR, INT, }; #[cfg(feature = "no_std")] @@ -179,8 +179,8 @@ pub mod array_functions { let mut arr_len = array.len(); let mut arr = Dynamic::from_array(mem::take(array)); - let (mut a1, mut m1, mut s1) = Engine::calc_data_sizes(&arr, true); - let (a2, m2, s2) = Engine::calc_data_sizes(&item, true); + let (mut a1, mut m1, mut s1) = crate::Engine::calc_data_sizes(&arr, true); + let (a2, m2, s2) = crate::Engine::calc_data_sizes(&item, true); { let mut guard = arr.write_lock::().unwrap();