diff --git a/CHANGELOG.md b/CHANGELOG.md index 74369277..0d99ecf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,29 @@ Rhai Release Notes ================== +Version 1.5.0 +============= + +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`). +* 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/Cargo.toml b/Cargo.toml index c5b66344..006c67bf 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" @@ -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/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] 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/event_handler_js/main.rs b/examples/event_handler_js/main.rs new file mode 100644 index 00000000..770127ce --- /dev/null +++ b/examples/event_handler_js/main.rs @@ -0,0 +1,168 @@ +//! Implementation of the Event Handler With State Pattern - JS Style +use rhai::{Dynamic, Engine, Scope, AST}; + +#[cfg(not(feature = "no_object"))] +use rhai::Map; + +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))| { + #[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, + 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!("=================================="); + + 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!"); +} + +#[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_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..750d6742 --- /dev/null +++ b/examples/event_handler_main/main.rs @@ -0,0 +1,140 @@ +//! 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))| { + #[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, + value_is_shared, + *value.read_lock::().unwrap(), + ) + }); + println!(); +} + +#[cfg(not(feature = "no_function"))] +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!"); +} + +#[cfg(feature = "no_function")] +pub fn main() { + panic!("This example does not run under 'no_function'.") +} 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..62e0a15f --- /dev/null +++ b/examples/event_handler_map/main.rs @@ -0,0 +1,153 @@ +//! Implementation of the Event Handler With State Pattern - Map Style +use rhai::{Dynamic, Engine, Scope, AST}; + +#[cfg(not(feature = "no_object"))] +use rhai::Map; + +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))| { + #[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, + 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!("=================================="); + + 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!"); +} + +#[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_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); +} 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); 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/eval/chaining.rs b/src/eval/chaining.rs index 85aae79c..d2589f0b 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -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,13 @@ 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 = super::calc_index(len, index, true, || { + ERR::ErrorArrayBounds(len, index, pos).into() + })?; - 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 +802,14 @@ 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 = super::calc_index(len, index, true, || { + ERR::ErrorArrayBounds(len, index, pos).into() + })?; - 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 +821,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 +838,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 +845,49 @@ 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 = super::calc_index(BITS, start, false, || { + ERR::ErrorBitFieldBounds(BITS, start, pos).into() + })?; + let end = super::calc_index(BITS, end, false, || { + ERR::ErrorBitFieldBounds(BITS, end, pos).into() + })?; + + 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::UNSIGNED_INT).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 = super::calc_index(BITS, start, false, || { + ERR::ErrorBitFieldBounds(BITS, start, pos).into() + })?; + let end = super::calc_index(BITS, end, false, || { + ERR::ErrorBitFieldBounds(BITS, end, pos).into() + })?; + + 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::UNSIGNED_INT).pow((end - start + 1) as u32) - 1) + as crate::INT) << start, ) } @@ -956,39 +910,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 = super::calc_index(BITS, index, true, || { + ERR::ErrorBitFieldBounds(BITS, index, pos).into() + })?; + + let bit_value = (*value & (1 << bit)) != 0; Ok(Target::Bit { source: target, value: bit_value.into(), - bit: offset, + bit: bit as u8, }) } @@ -997,14 +932,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 +949,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 +968,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 +977,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/stmt.rs b/src/eval/stmt.rs index 9dbf5433..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. @@ -322,11 +318,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 +344,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; @@ -466,7 +460,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(); @@ -494,24 +488,28 @@ 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); + scope.push(counter.name.clone(), 0 as INT); scope.len() - 1 } else { usize::MAX }; - scope.push(unsafe_cast_var_name_to_lifetime(name), ()); + + scope.push(var_name.clone(), ()); 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 { #[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(); @@ -548,7 +546,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; @@ -563,13 +566,17 @@ impl Engine { Err(err) => match *err { ERR::LoopBreak(false, _) => (), ERR::LoopBreak(true, _) => break, - _ => return Err(err), + _ => { + loop_result = Err(err); + break; + } }, } } scope.rewind(orig_scope_len); - Ok(Dynamic::UNIT) + + loop_result } else { Err(ERR::ErrorFor(expr.position()).into()) } @@ -582,7 +589,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) @@ -632,9 +639,9 @@ 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, .. }| scope.push(name.clone(), err_value)); let result = self.eval_stmt_block( scope, global, state, lib, this_ptr, catch_stmt, true, level, @@ -685,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 { @@ -697,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 @@ -705,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/eval/target.rs b/src/eval/target.rs index 4c644fdd..f31647f9 100644 --- a/src/eval/target.rs +++ b/src/eval/target.rs @@ -6,6 +6,74 @@ 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] +#[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) + }) + } 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 return a default value or an error. +#[inline] +#[allow(dead_code)] +pub fn calc_index( + length: usize, + start: crate::INT, + negative_count_from_end: bool, + err: impl Fn() -> Result, +) -> Result { + if start < 0 { + if negative_count_from_end { + // Count from end if negative + #[cfg(not(feature = "unchecked"))] + return match start.checked_abs() { + Some(positive_start) => { + if (positive_start as usize) > length { + err() + } else { + Ok(length - (positive_start as usize)) + } + } + None => err(), + }; + #[cfg(feature = "unchecked")] + return Ok(length - (start.abs() as usize)); + } else { + err() + } + } else if start as usize >= length { + err() + } 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/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/func/script.rs b/src/func/script.rs index cc37333a..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,18 +73,10 @@ 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))) - .map(|(name, value)| { - let var_name: std::borrow::Cow<'_, str> = - 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/lib.rs b/src/lib.rs index 3c6e616e..df685645 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())?; //! @@ -113,6 +113,7 @@ pub type INT = i32; #[cfg(not(feature = "only_i32"))] #[allow(non_camel_case_types)] type UNSIGNED_INT = u64; + /// The unsigned system integer base type. /// It is defined as [`u32`] since the `only_i32` feature is used. /// @@ -365,17 +366,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/mod.rs b/src/module/mod.rs index b70bf3da..dc0dcc76 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,59 +43,111 @@ 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) { + #[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, + } + } +} + +/// 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 { + /// Format a return type to be display-friendly. + /// + /// `()` 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>"; + 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")] #[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() { + let return_type = Self::format_return_type(&self.metadata.return_type); + + 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(")"); + sig.push(')'); - match self.return_type_name.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.params { + for x in 0..self.metadata.params { sig.push('_'); - if x < self.params - 1 { + if x < self.metadata.params - 1 { sig.push_str(", "); } } - if self.func.is_script() { - sig.push(')'); - } else { - sig.push_str(")"); + sig.push(')'); - match self.return_type_name.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); } } } @@ -372,7 +423,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 +529,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 +571,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 +701,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 +751,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 +763,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 +848,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 +916,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 +1429,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 +1459,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 +1532,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 +1554,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 +1672,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 +1747,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 +1755,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/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..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 } @@ -193,8 +202,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); @@ -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 @@ -238,8 +250,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 +262,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"))] @@ -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 c2ad47af..83af8683 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -2,10 +2,11 @@ #![allow(non_snake_case)] use crate::engine::OP_EQUALS; +use crate::eval::{calc_index, calc_offset_len}; use crate::plugin::*; use crate::{ def_package, Array, Dynamic, ExclusiveRange, FnPtr, InclusiveRange, NativeCallContext, - Position, RhaiResultOf, ERR, INT, + Position, RhaiResultOf, StaticVec, ERR, INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -25,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() { @@ -55,25 +94,56 @@ pub mod array_functions { array1 } } - pub fn insert(array: &mut Array, position: INT, item: Dynamic) { + /// 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); - } 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); } } + /// 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, @@ -90,39 +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()); - } - - #[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; - - if check_sizes { - let arr = mem::take(array); - let mut arr = Dynamic::from_array(arr); - - while array.len() < len { - arr.write_lock::().unwrap().push(item.clone()); - - #[cfg(not(feature = "unchecked"))] - _ctx.engine().ensure_data_size_within_limits(&arr)?; + { + 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(), + ); } - *array = arr.into_array().unwrap(); - } else { - array.resize(len, item); + let check_sizes = match item.0 { + crate::types::dynamic::Union::Array(_, _, _) + | crate::types::dynamic::Union::Str(_, _, _) => true, + #[cfg(not(feature = "no_object"))] + crate::types::dynamic::Union::Map(_, _, _) => true, + _ => false, + }; + + if check_sizes { + let mut arr_len = array.len(); + let mut arr = Dynamic::from_array(mem::take(array)); + + let (mut a1, mut m1, mut s1) = crate::Engine::calc_data_sizes(&arr, true); + let (a2, m2, s2) = crate::Engine::calc_data_sizes(&item, true); + + { + let mut guard = arr.write_lock::().unwrap(); + + while arr_len < len { + a1 += a2; + m1 += m2; + s1 += s2; + + _ctx.engine() + .raise_err_if_over_data_size_limit((a1, m1, s1), Position::NONE)?; + + guard.push(item.clone()); + arr_len += 1; + } + } + + *array = arr.into_array().unwrap(); + } else { + array.resize(len, item); + } } + #[cfg(feature = "unchecked")] + array.resize(len, item); + Ok(()) } + /// Remove the last element from the array and return it. + /// + /// If the array is empty, `()` is returned. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3]; + /// + /// print(x.pop()); // prints 3 + /// + /// print(x); // prints "[1, 2]" + /// ``` pub fn pop(array: &mut Array) -> Dynamic { if array.is_empty() { Dynamic::UNIT @@ -130,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 @@ -137,116 +249,240 @@ pub mod array_functions { array.remove(0) } } - pub fn remove(array: &mut Array, len: INT) -> Dynamic { - if len < 0 || (len as usize) >= array.len() { - Dynamic::UNIT - } else { - array.remove(len as usize) - } + /// Remove the element at the specified `index` from the array and return it. + /// + /// * If `index` < 0, position counts from the end of the array (`-1` is the last element). + /// * If `index` < -length of array, `()` is returned. + /// * If `index` ≥ length of array, `()` is returned. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3]; + /// + /// print(x.remove(1)); // prints 2 + /// + /// print(x); // prints "[1, 3]" + /// + /// print(x.remove(-2)); // prints 1 + /// + /// print(x); // prints "[3]" + /// ``` + pub fn remove(array: &mut Array, index: INT) -> Dynamic { + let index = match calc_index(array.len(), index, true, || Err(())) { + Ok(n) => n, + Err(_) => return Dynamic::UNIT, + }; + + array.remove(index) } + /// Clear the array. pub fn clear(array: &mut Array) { if !array.is_empty() { array.clear(); } } + /// Cut off the array at the specified length. + /// + /// * If `len` ≤ 0, the array is cleared. + /// * If `len` ≥ length of array, the array is not truncated. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// x.truncate(3); + /// + /// print(x); // prints "[1, 2, 3]" + /// + /// x.truncate(10); + /// + /// print(x); // prints "[1, 2, 3]" + /// ``` pub fn truncate(array: &mut Array, len: INT) { if !array.is_empty() { - if len >= 0 { + 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() && 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); } } } + /// Reverse all the elements in the array. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// x.reverse(); + /// + /// print(x); // prints "[5, 4, 3, 2, 1]" + /// ``` pub fn reverse(array: &mut Array) { if !array.is_empty() { array.reverse(); } } + /// Replace an exclusive range of the array with another array. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// let y = [7, 8, 9, 10]; + /// + /// x.splice(1..3, y); + /// + /// print(x); // prints "[1, 7, 8, 9, 10, 4, 5]" + /// ``` #[rhai_fn(name = "splice")] pub fn splice_range(array: &mut Array, range: ExclusiveRange, replace: Array) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); splice(array, start, end - start, replace) } + /// Replace an inclusive range of the array with another array. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// let y = [7, 8, 9, 10]; + /// + /// x.splice(1..=3, y); + /// + /// print(x); // prints "[1, 7, 8, 9, 10, 5]" + /// ``` #[rhai_fn(name = "splice")] pub fn splice_inclusive_range(array: &mut Array, range: InclusiveRange, replace: Array) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); splice(array, start, end - start + 1, replace) } + /// Replace a portion of the array with another array. + /// + /// * If `start` < 0, position counts from the end of the array (`-1` is the last element). + /// * If `start` < -length of array, position counts from the beginning of the array. + /// * If `start` ≥ length of array, the other array is appended to the end of the array. + /// * If `len` ≤ 0, the other array is inserted into the array at the `start` position without replacing any element. + /// * If `start` position + `len` ≥ length of array, entire portion of the array after the `start` position is replaced. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// let y = [7, 8, 9, 10]; + /// + /// x.splice(1, 2, y); + /// + /// print(x); // prints "[1, 7, 8, 9, 10, 4, 5]" + /// + /// x.splice(-5, 4, y); + /// + /// print(x); // prints "[1, 7, 7, 8, 9, 10, 5]" + /// ``` pub fn splice(array: &mut Array, start: INT, len: INT, replace: Array) { if array.is_empty() { *array = replace; return; } - let start = 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); + } } + /// Copy an exclusive range of the array and return it as a new array. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// print(x.extract(1..3)); // prints "[2, 3]" + /// + /// print(x); // prints "[1, 2, 3, 4, 5]" + /// ``` #[rhai_fn(name = "extract")] pub fn extract_range(array: &mut Array, range: ExclusiveRange) -> Array { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); extract(array, start, end - start) } + /// Copy an inclusive range of the array and return it as a new array. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// print(x.extract(1..=3)); // prints "[2, 3, 4]" + /// + /// print(x); // prints "[1, 2, 3, 4, 5]" + /// ``` #[rhai_fn(name = "extract")] pub fn extract_inclusive_range(array: &mut Array, range: InclusiveRange) -> Array { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); extract(array, start, end - start + 1) } + /// Copy a portion of the array and return it as a new array. + /// + /// * If `start` < 0, position counts from the end of the array (`-1` is the last element). + /// * If `start` < -length of array, position counts from the beginning of the array. + /// * If `start` ≥ length of array, an empty array is returned. + /// * If `len` ≤ 0, an empty array is returned. + /// * If `start` position + `len` ≥ length of array, entire portion of the array after the `start` position is copied and returned. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3, 4, 5]; + /// + /// print(x.extract(1, 3)); // prints "[2, 3, 4]" + /// + /// print(x.extract(-3, 2)); // prints "[3, 4]" + /// + /// print(x); // prints "[1, 2, 3, 4, 5]" + /// ``` pub fn extract(array: &mut Array, start: INT, len: INT) -> Array { if array.is_empty() || len <= 0 { return Array::new(); } - let start = 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() @@ -254,27 +490,63 @@ 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() { - 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(), index, INT::MAX); + + if start == 0 { + if len >= array.len() { mem::take(array) + } else { + let mut result = Array::new(); + result.extend(array.drain(array.len() - len..)); + result } - } else if start as usize >= array.len() { + } else if start >= array.len() { Array::new() } else { let mut result = Array::new(); @@ -282,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() { @@ -315,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, @@ -324,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() { @@ -360,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, @@ -401,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, @@ -413,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, @@ -424,16 +851,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 @@ -458,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, @@ -478,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, @@ -489,16 +981,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 @@ -528,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, @@ -537,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() { @@ -571,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() { @@ -613,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, @@ -637,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) @@ -645,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, @@ -703,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, @@ -712,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, @@ -763,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, @@ -772,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 { @@ -802,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 { @@ -874,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() { @@ -917,53 +1890,159 @@ 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 { 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() + } } + /// 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() { @@ -1007,56 +2086,153 @@ 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 { 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 + } } + /// 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() { @@ -1091,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 57155f68..4bd5ec08 100644 --- a/src/packages/bit_field.rs +++ b/src/packages/bit_field.rs @@ -1,7 +1,10 @@ #![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, UNSIGNED_INT, +}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -18,118 +21,145 @@ 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, 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).into() + })?; - 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) } + /// 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, 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).into() + })?; - 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(()) } + /// 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, index: INT, bits: INT) -> RhaiResultOf { - if bits < 1 { + pub fn get_bits(value: INT, start: 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, start, true, || { + ERR::ErrorBitFieldBounds(BITS, start, Position::NONE).into() + })?; - 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 UNSIGNED_INT).pow(bits as u32) - 1) as crate::INT; + + 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, @@ -140,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, @@ -150,48 +191,51 @@ 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, 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).into() + })?; - 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 UNSIGNED_INT).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..ca240df3 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_index, calc_offset_len}; use crate::plugin::*; use crate::{ def_package, Blob, Dynamic, ExclusiveRange, InclusiveRange, NativeCallContext, Position, @@ -13,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::(); @@ -27,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, @@ -55,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() { @@ -72,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() { @@ -86,34 +164,65 @@ pub mod blob_functions { blob1 } } - pub fn insert(blob: &mut Blob, position: 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); - } 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() { - blob.push(item); + blob.push(value); + return; + } + + let (index, _) = calc_offset_len(blob.len(), index, 0); + + if index >= blob.len() { + blob.push(value); } else { - blob.insert(position as usize, 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 @@ -125,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 @@ -137,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 @@ -144,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 { @@ -165,224 +347,472 @@ 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() && 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); } } } + /// 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; 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); + } } + /// 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(); } - 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() + } } + /// 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() { - 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 (index, len) = calc_offset_len(blob.len(), index, INT::MAX); + + if index == 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 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(); } - 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() + } } + /// 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(); } - 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 + } } +} +#[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 { 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::(); + } let len = usize::min(len, INT_BYTES); @@ -427,31 +857,81 @@ 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 { 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::(); + } let len = usize::min(len, INT_BYTES); @@ -463,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); @@ -495,108 +975,22 @@ 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 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 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::(); - - 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 { 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::(); + } let len = usize::min(len, FLOAT_BYTES); let buf = if is_le { @@ -607,66 +1001,53 @@ 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() { 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/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 6dbd8eaf..fe844e70 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).into() + })?; let len = if len < 0 { 0 @@ -266,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::>(); @@ -281,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);", + "/// }", + "/// ```" ]); )* }; @@ -303,8 +309,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 +321,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); } @@ -379,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")] @@ -441,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 @@ -453,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); @@ -461,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 @@ -491,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); @@ -499,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); @@ -526,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/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/map_basic.rs b/src/packages/map_basic.rs index 525ce5c0..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() { @@ -77,8 +149,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); @@ -91,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 { @@ -105,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 a6dfa939..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::*; @@ -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)); } } @@ -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 { @@ -522,8 +634,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 +646,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..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,114 +191,156 @@ 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) } - #[cfg(not(target_arch = "wasm32"))] - #[cfg(not(target_arch = "wasm64"))] + #[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 5f6721bf..3eacee0b 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! { @@ -26,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"))] @@ -56,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"))] @@ -141,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)?; @@ -194,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 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/serde/metadata.rs b/src/serde/metadata.rs index ce5f3f3d..3ccadfb3 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> { @@ -89,15 +70,18 @@ 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_name: 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>, } impl PartialOrd for FnMetadata<'_> { - fn partial_cmp(&self, other: &Self) -> Option { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } @@ -105,41 +89,39 @@ 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 { - let base_hash = calc_fn_hash(&info.name, info.params); +impl<'a> From<&'a FuncInfo> for FnMetadata<'a> { + fn from(info: &'a FuncInfo) -> Self { + 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, ':'); - let name = match seg.next().map(&str::trim).unwrap_or("_") { + let name = match seg.next().unwrap().trim() { "_" => None, s => Some(s), }; @@ -147,10 +129,8 @@ impl<'a> From<&'a crate::module::FuncInfo> for FnMetadata<'a> { FnParam { name, typ } }) .collect(), - return_type_name: match info.return_type_name.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")] @@ -164,7 +144,8 @@ impl<'a> From<&'a crate::module::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()) }, diff --git a/src/tests.rs b/src/tests.rs index ce0a89b3..f4d32675 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 }); + + #[cfg(target_pointer_width = "64")] + { + assert_eq!(size_of::(), 400); + assert_eq!(size_of::(), 80); + 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 + } + ); + } } 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..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. @@ -115,7 +121,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)?; @@ -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)] 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/arrays.rs b/tests/arrays.rs index ce1a0028..e0dc764d 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -22,12 +22,48 @@ 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")? .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 75% rename from tests/bit_shift.rs rename to tests/bit_fields.rs index b077c5a6..f2cffb32 100644 --- a/tests/bit_shift.rs +++ b/tests/bit_fields.rs @@ -27,10 +27,25 @@ 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")] + #[cfg(not(feature = "only_i32"))] + { + 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::( diff --git a/tests/serde.rs b/tests/serde.rs index 5e20f04a..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; @@ -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(()) } 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};