Merge pull request #507 from schungx/master

Revise library and fix bugs.
This commit is contained in:
Stephen Chung 2022-01-16 23:30:40 +08:00 committed by GitHub
commit 2805690b11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 4792 additions and 1285 deletions

View File

@ -1,6 +1,29 @@
Rhai Release Notes 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 Version 1.4.0
============= =============

View File

@ -3,7 +3,7 @@ members = [".", "codegen"]
[package] [package]
name = "rhai" name = "rhai"
version = "1.4.0" version = "1.5.0"
edition = "2018" edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
description = "Embedded scripting for Rust" description = "Embedded scripting for Rust"
@ -97,10 +97,7 @@ default-features = false
features = ["maths"] features = ["maths"]
optional = true optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_family = "wasm")'.dependencies]
instant = { version = "0.1.10" } # WASM implementation of std::time::Instant
[target.'cfg(target_arch = "wasm64")'.dependencies]
instant = { version = "0.1.10" } # WASM implementation of std::time::Instant instant = { version = "0.1.10" } # WASM implementation of std::time::Instant
[package.metadata.docs.rs] [package.metadata.docs.rs]

View File

@ -16,7 +16,7 @@ default = []
metadata = [] metadata = []
[dev-dependencies] [dev-dependencies]
rhai = { path = "..", version = "1.4" } rhai = { path = "..", version = "1.4", features = ["metadata"] }
trybuild = "1" trybuild = "1"
[dependencies] [dependencies]

View File

@ -1,8 +1,8 @@
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct TestStruct { struct TestStruct {
x: INT, x: i64,
} }
impl TestStruct { impl TestStruct {

View File

@ -1,8 +1,8 @@
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct TestStruct { struct TestStruct {
x: INT, x: i64,
} }
impl TestStruct { impl TestStruct {

View File

@ -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::<Dynamic>().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'.")
}

View File

@ -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);
}

View File

@ -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::<Dynamic>().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'.")
}

View File

@ -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);
}

View File

@ -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::<Dynamic>().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'.")
}

View File

@ -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);
}

View File

@ -1,11 +1,11 @@
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult};
fn main() -> Result<(), Box<EvalAltResult>> { fn main() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
engine.run(r#"print("hello, world!")"#)?; engine.run(r#"print("hello, world!")"#)?;
let result = engine.eval::<INT>("40 + 2")?; let result = engine.eval::<i64>("40 + 2")?;
println!("Answer: {}", result); // prints 42 println!("Answer: {}", result); // prints 42

View File

@ -1,20 +1,20 @@
use rhai::{Engine, EvalAltResult, Scope, INT}; use rhai::{Engine, EvalAltResult, Scope};
fn main() -> Result<(), Box<EvalAltResult>> { fn main() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
let mut scope = Scope::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::<INT>("x").unwrap()); println!("x = {}", scope.get_value::<i64>("x").unwrap());
for _ in 0..10 { for _ in 0..10 {
let result = engine.eval_with_scope::<INT>(&mut scope, "x += 1; x")?; let result = engine.eval_with_scope::<i64>(&mut scope, "x += 1; x")?;
println!("result: {}", result); println!("result: {}", result);
} }
println!("x = {}", scope.get_value::<INT>("x").unwrap()); println!("x = {}", scope.get_value::<i64>("x").unwrap());
Ok(()) Ok(())
} }

View File

@ -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 x + y
} }
@ -9,7 +9,7 @@ fn main() -> Result<(), Box<EvalAltResult>> {
engine.register_fn("add", add); engine.register_fn("add", add);
let result = engine.eval::<INT>("add(40, 2)")?; let result = engine.eval::<i64>("add(40, 2)")?;
println!("Answer: {}", result); // prints 42 println!("Answer: {}", result); // prints 42

View File

@ -1,6 +1,6 @@
///! This example registers a variety of functions that operate on strings. ///! This example registers a variety of functions that operate on strings.
///! Remember to use `ImmutableString` or `&str` instead of `String` as parameters. ///! 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}; use std::io::{stdin, stdout, Write};
/// Trim whitespace from a string. The original string argument is changed. /// 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 simply counts the number of _bytes_ in the UTF-8 representation.
/// ///
/// This version uses `&str`. /// This version uses `&str`.
fn count_string_bytes(s: &str) -> INT { fn count_string_bytes(s: &str) -> i64 {
s.len() as INT s.len() as i64
} }
/// This version uses `ImmutableString` and `&str`. /// This version uses `ImmutableString` and `&str`.
fn find_substring(s: ImmutableString, sub: &str) -> INT { fn find_substring(s: ImmutableString, sub: &str) -> i64 {
s.find(sub).map(|x| x as INT).unwrap_or(-1) s.find(sub).map(|x| x as i64).unwrap_or(-1)
} }
fn main() -> Result<(), Box<EvalAltResult>> { fn main() -> Result<(), Box<EvalAltResult>> {
// Create a `raw` Engine with no built-in string functions. // Create a `raw` Engine with no built-in string functions.
let mut engine = Engine::new_raw(); let mut engine = Engine::new_raw();
// Register string functions
engine engine
// Register string functions
.register_fn("trim", trim_string) .register_fn("trim", trim_string)
.register_fn("len", count_string_bytes) .register_fn("len", count_string_bytes)
.register_fn("index_of", find_substring) .register_fn("index_of", find_substring)
// Register string functions using closures // Register string functions using closures
.register_fn("display", |label: &str, value: INT| { .register_fn("display", |label: &str, value: i64| {
println!("{}: {}", label, value) println!("{}: {}", label, value)
}) })
.register_fn("display", |label: ImmutableString, value: &str| { .register_fn("display", |label: ImmutableString, value: &str| {

View File

@ -1,4 +1,4 @@
use rhai::{Engine, INT}; use rhai::Engine;
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
use std::sync::Mutex; use std::sync::Mutex;
@ -23,12 +23,12 @@ fn main() {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
engine engine
.register_fn("get", move || rx_script.recv().unwrap_or_default()) .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")] #[cfg(feature = "sync")]
engine engine
.register_fn("get", move || rx_script.lock().unwrap().recv().unwrap()) .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() tx_script.lock().unwrap().send(v).unwrap()
}); });
@ -54,7 +54,7 @@ fn main() {
println!("Starting main loop..."); println!("Starting main loop...");
let mut value: INT = 0; let mut value: i64 = 0;
while value < 10 { while value < 10 {
println!("Value: {}", value); println!("Value: {}", value);

View File

@ -20,8 +20,7 @@ impl Engine {
/// This method will be removed in the next major version. /// This method will be removed in the next major version.
#[deprecated(since = "1.1.0", note = "use `run_file` instead")] #[deprecated(since = "1.1.0", note = "use `run_file` instead")]
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
#[cfg(not(target_arch = "wasm64"))]
#[inline(always)] #[inline(always)]
pub fn consume_file(&self, path: std::path::PathBuf) -> RhaiResultOf<()> { pub fn consume_file(&self, path: std::path::PathBuf) -> RhaiResultOf<()> {
self.run_file(path) self.run_file(path)
@ -39,8 +38,7 @@ impl Engine {
/// This method will be removed in the next major version. /// This method will be removed in the next major version.
#[deprecated(since = "1.1.0", note = "use `run_file_with_scope` instead")] #[deprecated(since = "1.1.0", note = "use `run_file_with_scope` instead")]
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
#[cfg(not(target_arch = "wasm64"))]
#[inline(always)] #[inline(always)]
pub fn consume_file_with_scope( pub fn consume_file_with_scope(
&self, &self,

View File

@ -1,7 +1,6 @@
//! Module that defines the public file-based API of [`Engine`]. //! Module that defines the public file-based API of [`Engine`].
#![cfg(not(feature = "no_std"))] #![cfg(not(feature = "no_std"))]
#![cfg(not(target_arch = "wasm32"))] #![cfg(not(target_family = "wasm"))]
#![cfg(not(target_arch = "wasm64"))]
use crate::types::dynamic::Variant; use crate::types::dynamic::Variant;
use crate::{Engine, RhaiResultOf, Scope, AST, ERR}; use crate::{Engine, RhaiResultOf, Scope, AST, ERR};

View File

@ -203,40 +203,6 @@ pub fn make_setter(id: &str) -> Identifier {
buf 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 { impl Engine {
/// Create a new [`Engine`]. /// Create a new [`Engine`].
#[inline] #[inline]
@ -247,16 +213,32 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
#[cfg(not(target_arch = "wasm64"))]
{ {
engine.module_resolver = engine.module_resolver =
Some(Box::new(crate::module::resolvers::FileModuleResolver::new())); Some(Box::new(crate::module::resolvers::FileModuleResolver::new()));
} }
// default print/debug implementations // default print/debug implementations
engine.print = Some(Box::new(print_to_stdout)); #[cfg(not(feature = "no_std"))]
engine.debug = Some(Box::new(debug_to_stdout)); #[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()); engine.register_global_module(StandardPackage::new().as_shared_module());

View File

@ -771,7 +771,7 @@ impl Engine {
lib: &[&Module], lib: &[&Module],
target: &'t mut Dynamic, target: &'t mut Dynamic,
idx: Dynamic, idx: Dynamic,
idx_pos: Position, pos: Position,
add_if_not_found: bool, add_if_not_found: bool,
use_indexers: bool, use_indexers: bool,
level: usize, level: usize,
@ -788,38 +788,13 @@ impl Engine {
// val_array[idx] // val_array[idx]
let index = idx let index = idx
.as_int() .as_int()
.map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, idx_pos))?; .map_err(|typ| self.make_type_mismatch_err::<crate::INT>(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(); Ok(arr.get_mut(arr_idx).map(Target::from).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
};
arr.get_mut(arr_idx)
.map(Target::from)
.ok_or_else(|| ERR::ErrorArrayBounds(arr_len, index, idx_pos).into())
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -827,39 +802,14 @@ impl Engine {
// val_blob[idx] // val_blob[idx]
let index = idx let index = idx
.as_int() .as_int()
.map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, idx_pos))?; .map_err(|typ| self.make_type_mismatch_err::<crate::INT>(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 { Ok(Target::BlobByte {
source: target, source: target,
value, value,
@ -871,7 +821,7 @@ impl Engine {
Dynamic(Union::Map(map, _, _)) => { Dynamic(Union::Map(map, _, _)) => {
// val_map[idx] // val_map[idx]
let index = idx.read_lock::<crate::ImmutableString>().ok_or_else(|| { let index = idx.read_lock::<crate::ImmutableString>().ok_or_else(|| {
self.make_type_mismatch_err::<crate::ImmutableString>(idx.type_name(), idx_pos) self.make_type_mismatch_err::<crate::ImmutableString>(idx.type_name(), pos)
})?; })?;
if _add_if_not_found && !map.contains_key(index.as_str()) { if _add_if_not_found && !map.contains_key(index.as_str()) {
@ -888,11 +838,6 @@ impl Engine {
Dynamic(Union::Int(value, _, _)) Dynamic(Union::Int(value, _, _))
if idx.is::<crate::ExclusiveRange>() || idx.is::<crate::InclusiveRange>() => if idx.is::<crate::ExclusiveRange>() || idx.is::<crate::InclusiveRange>() =>
{ {
#[cfg(not(feature = "only_i32"))]
type BASE = u64;
#[cfg(feature = "only_i32")]
type BASE = u32;
// val_int[range] // val_int[range]
const BITS: usize = std::mem::size_of::<crate::INT>() * 8; const BITS: usize = std::mem::size_of::<crate::INT>() * 8;
@ -900,40 +845,49 @@ impl Engine {
let start = range.start; let start = range.start;
let end = range.end; let end = range.end;
if start < 0 || start as usize >= BITS { let start = super::calc_index(BITS, start, false, || {
return Err(ERR::ErrorBitFieldBounds(BITS, start, idx_pos).into()); ERR::ErrorBitFieldBounds(BITS, start, pos).into()
} else if end < 0 || end as usize >= BITS { })?;
return Err(ERR::ErrorBitFieldBounds(BITS, end, idx_pos).into()); let end = super::calc_index(BITS, end, false, || {
} else if end <= start { ERR::ErrorBitFieldBounds(BITS, end, pos).into()
})?;
if end <= start {
(0, 0) (0, 0)
} else if end as usize == BITS && start == 0 { } else if end == BITS && start == 0 {
// -1 = all bits set // -1 = all bits set
(0, -1) (0, -1)
} else { } else {
( (
start as u8, start as u8,
// 2^bits - 1 // 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::<crate::InclusiveRange>() { } else if let Some(range) = idx.read_lock::<crate::InclusiveRange>() {
let start = *range.start(); let start = *range.start();
let end = *range.end(); let end = *range.end();
if start < 0 || start as usize >= BITS { let start = super::calc_index(BITS, start, false, || {
return Err(ERR::ErrorBitFieldBounds(BITS, start, idx_pos).into()); ERR::ErrorBitFieldBounds(BITS, start, pos).into()
} else if end < 0 || end as usize >= BITS { })?;
return Err(ERR::ErrorBitFieldBounds(BITS, end, idx_pos).into()); let end = super::calc_index(BITS, end, false, || {
} else if end < start { ERR::ErrorBitFieldBounds(BITS, end, pos).into()
})?;
if end < start {
(0, 0) (0, 0)
} else if end as usize == BITS - 1 && start == 0 { } else if end == BITS - 1 && start == 0 {
// -1 = all bits set // -1 = all bits set
(0, -1) (0, -1)
} else { } else {
( (
start as u8, start as u8,
// 2^bits - 1 // 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, << start,
) )
} }
@ -956,39 +910,20 @@ impl Engine {
// val_int[idx] // val_int[idx]
let index = idx let index = idx
.as_int() .as_int()
.map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, idx_pos))?; .map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, pos))?;
const BITS: usize = std::mem::size_of::<crate::INT>() * 8; const BITS: usize = std::mem::size_of::<crate::INT>() * 8;
let (bit_value, offset) = if index >= 0 { let bit = super::calc_index(BITS, index, true, || {
let offset = index as usize; ERR::ErrorBitFieldBounds(BITS, index, pos).into()
( })?;
if offset >= BITS {
return Err(ERR::ErrorBitFieldBounds(BITS, index, idx_pos).into()); let bit_value = (*value & (1 << bit)) != 0;
} 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());
};
Ok(Target::Bit { Ok(Target::Bit {
source: target, source: target,
value: bit_value.into(), value: bit_value.into(),
bit: offset, bit: bit as u8,
}) })
} }
@ -997,14 +932,14 @@ impl Engine {
// val_string[idx] // val_string[idx]
let index = idx let index = idx
.as_int() .as_int()
.map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, idx_pos))?; .map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, pos))?;
let (ch, offset) = if index >= 0 { let (ch, offset) = if index >= 0 {
let offset = index as usize; let offset = index as usize;
( (
s.chars().nth(offset).ok_or_else(|| { s.chars().nth(offset).ok_or_else(|| {
let chars_len = s.chars().count(); let chars_len = s.chars().count();
ERR::ErrorStringBounds(chars_len, index, idx_pos) ERR::ErrorStringBounds(chars_len, index, pos)
})?, })?,
offset, offset,
) )
@ -1014,13 +949,13 @@ impl Engine {
// Count from end if negative // Count from end if negative
s.chars().rev().nth(offset - 1).ok_or_else(|| { s.chars().rev().nth(offset - 1).ok_or_else(|| {
let chars_len = s.chars().count(); let chars_len = s.chars().count();
ERR::ErrorStringBounds(chars_len, index, idx_pos) ERR::ErrorStringBounds(chars_len, index, pos)
})?, })?,
offset, offset,
) )
} else { } else {
let chars_len = s.chars().count(); 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 { Ok(Target::StringChar {
@ -1033,8 +968,6 @@ impl Engine {
_ if use_indexers => { _ if use_indexers => {
let args = &mut [target, &mut idx]; let args = &mut [target, &mut idx];
let hash_get = crate::ast::FnCallHashes::from_native(global.hash_idx_get()); let hash_get = crate::ast::FnCallHashes::from_native(global.hash_idx_get());
let idx_pos = Position::NONE;
self.exec_fn_call( self.exec_fn_call(
global, global,
state, state,
@ -1044,7 +977,7 @@ impl Engine {
args, args,
true, true,
true, true,
idx_pos, Position::NONE,
None, None,
level, level,
) )

View File

@ -12,4 +12,4 @@ pub use chaining::{ChainArgument, ChainType};
pub use eval_context::EvalContext; pub use eval_context::EvalContext;
pub use eval_state::EvalState; pub use eval_state::EvalState;
pub use global_state::GlobalRuntimeState; pub use global_state::GlobalRuntimeState;
pub use target::Target; pub use target::{calc_index, calc_offset_len, Target};

View File

@ -3,15 +3,11 @@
use super::{EvalState, GlobalRuntimeState, Target}; use super::{EvalState, GlobalRuntimeState, Target};
use crate::ast::{Expr, Ident, OpAssignment, Stmt, AST_OPTION_FLAGS::*}; use crate::ast::{Expr, Ident, OpAssignment, Stmt, AST_OPTION_FLAGS::*};
use crate::func::get_hasher; use crate::func::get_hasher;
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
use crate::types::dynamic::{AccessMode, Union}; use crate::types::dynamic::{AccessMode, Union};
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR, INT}; use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR, INT};
use std::hash::{Hash, Hasher};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{
borrow::Cow,
hash::{Hash, Hasher},
};
impl Engine { impl Engine {
/// Evaluate a statements block. /// Evaluate a statements block.
@ -322,11 +318,10 @@ impl Engine {
if let Some(t) = table.get(&hash) { if let Some(t) = table.get(&hash) {
if let Some(ref c) = t.0 { if let Some(ref c) = t.0 {
if self if self
.eval_expr(scope, global, state, lib, this_ptr, &c, level) .eval_expr(scope, global, state, lib, this_ptr, &c, level)?
.and_then(|v| { .as_bool()
v.as_bool().map_err(|typ| { .map_err(|typ| {
self.make_type_mismatch_err::<bool>(typ, c.position()) self.make_type_mismatch_err::<bool>(typ, c.position())
})
})? })?
{ {
Some(&t.1) Some(&t.1)
@ -349,11 +344,10 @@ impl Engine {
{ {
if let Some(c) = condition { if let Some(c) = condition {
if !self if !self
.eval_expr(scope, global, state, lib, this_ptr, &c, level) .eval_expr(scope, global, state, lib, this_ptr, &c, level)?
.and_then(|v| { .as_bool()
v.as_bool().map_err(|typ| { .map_err(|typ| {
self.make_type_mismatch_err::<bool>(typ, c.position()) self.make_type_mismatch_err::<bool>(typ, c.position())
})
})? })?
{ {
continue; continue;
@ -466,7 +460,7 @@ impl Engine {
// For loop // For loop
Stmt::For(expr, x, _) => { 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 let iter_obj = self
.eval_expr(scope, global, state, lib, this_ptr, expr, level)? .eval_expr(scope, global, state, lib, this_ptr, expr, level)?
.flatten(); .flatten();
@ -494,24 +488,28 @@ impl Engine {
// Add the loop variables // Add the loop variables
let orig_scope_len = scope.len(); let orig_scope_len = scope.len();
let counter_index = if let Some(counter) = counter { 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 scope.len() - 1
} else { } else {
usize::MAX usize::MAX
}; };
scope.push(unsafe_cast_var_name_to_lifetime(name), ());
scope.push(var_name.clone(), ());
let index = scope.len() - 1; let index = scope.len() - 1;
let mut loop_result = Ok(Dynamic::UNIT);
for (x, iter_value) in func(iter_obj).enumerate() { for (x, iter_value) in func(iter_obj).enumerate() {
// Increment counter // Increment counter
if counter_index < usize::MAX { if counter_index < usize::MAX {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if x > INT::MAX as usize { if x > INT::MAX as usize {
return Err(ERR::ErrorArithmetic( loop_result = Err(ERR::ErrorArithmetic(
format!("for-loop counter overflow: {}", x), format!("for-loop counter overflow: {}", x),
counter.as_ref().expect("`Some`").pos, counter.as_ref().expect("`Some`").pos,
) )
.into()); .into());
break;
} }
let index_value = (x as INT).into(); let index_value = (x as INT).into();
@ -548,7 +546,12 @@ impl Engine {
} }
#[cfg(not(feature = "unchecked"))] #[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() { if statements.is_empty() {
continue; continue;
@ -563,13 +566,17 @@ impl Engine {
Err(err) => match *err { Err(err) => match *err {
ERR::LoopBreak(false, _) => (), ERR::LoopBreak(false, _) => (),
ERR::LoopBreak(true, _) => break, ERR::LoopBreak(true, _) => break,
_ => return Err(err), _ => {
loop_result = Err(err);
break;
}
}, },
} }
} }
scope.rewind(orig_scope_len); scope.rewind(orig_scope_len);
Ok(Dynamic::UNIT)
loop_result
} else { } else {
Err(ERR::ErrorFor(expr.position()).into()) Err(ERR::ErrorFor(expr.position()).into())
} }
@ -582,7 +589,7 @@ impl Engine {
// Try/Catch statement // Try/Catch statement
Stmt::TryCatch(x, _) => { 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 let result = self
.eval_stmt_block(scope, global, state, lib, this_ptr, try_stmt, true, level) .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(); let orig_scope_len = scope.len();
err_var.as_ref().map(|Ident { name, .. }| { err_var_name
scope.push(unsafe_cast_var_name_to_lifetime(name), err_value) .as_ref()
}); .map(|Ident { name, .. }| scope.push(name.clone(), err_value));
let result = self.eval_stmt_block( let result = self.eval_stmt_block(
scope, global, state, lib, this_ptr, catch_stmt, true, level, scope, global, state, lib, this_ptr, catch_stmt, true, level,
@ -685,7 +692,7 @@ impl Engine {
// Let/const statement // Let/const statement
Stmt::Var(expr, x, options, _) => { Stmt::Var(expr, x, options, _) => {
let name = &x.name; let var_name = &x.name;
let entry_type = if options.contains(AST_OPTION_CONSTANT) { let entry_type = if options.contains(AST_OPTION_CONSTANT) {
AccessMode::ReadOnly AccessMode::ReadOnly
} else { } else {
@ -697,7 +704,7 @@ impl Engine {
.eval_expr(scope, global, state, lib, this_ptr, expr, level)? .eval_expr(scope, global, state, lib, this_ptr, expr, level)?
.flatten(); .flatten();
let (var_name, _alias): (Cow<'_, str>, _) = if !rewind_scope { let _alias = if !rewind_scope {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
if state.scope_level == 0 if state.scope_level == 0
@ -705,23 +712,26 @@ impl Engine {
&& lib.iter().any(|&m| !m.is_empty()) && lib.iter().any(|&m| !m.is_empty())
{ {
// Add a global constant if at top level and there are functions // 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());
} }
( if export {
name.to_string().into(), Some(var_name)
if export { Some(name.clone()) } else { None }, } else {
) None
}
} else if export { } else if export {
unreachable!("exported variable not on global level"); unreachable!("exported variable not on global level");
} else { } 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"))] #[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) Ok(Dynamic::UNIT)
} }

View File

@ -6,6 +6,74 @@ use std::ops::{Deref, DerefMut};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; 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<E>(
length: usize,
start: crate::INT,
negative_count_from_end: bool,
err: impl Fn() -> Result<usize, E>,
) -> Result<usize, E> {
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. /// A type that encapsulates a mutation target for an expression with side effects.
#[derive(Debug)] #[derive(Debug)]
pub enum Target<'a> { pub enum Target<'a> {

View File

@ -41,8 +41,7 @@ fn is_numeric(type_id: TypeId) -> bool {
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
#[cfg(not(target_arch = "wasm64"))]
let result = result || type_id == TypeId::of::<u128>() || type_id == TypeId::of::<i128>(); let result = result || type_id == TypeId::of::<u128>() || type_id == TypeId::of::<i128>();
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]

View File

@ -4,7 +4,6 @@
use super::call::FnCallArgs; use super::call::FnCallArgs;
use crate::ast::ScriptFnDef; use crate::ast::ScriptFnDef;
use crate::eval::{EvalState, GlobalRuntimeState}; 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 crate::{Dynamic, Engine, Module, Position, RhaiError, RhaiResult, Scope, StaticVec, ERR};
use std::mem; use std::mem;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
@ -74,18 +73,10 @@ impl Engine {
let orig_mods_len = global.num_imported_modules(); let orig_mods_len = global.num_imported_modules();
// Put arguments into scope as variables // Put arguments into scope as variables
// Actually consume the arguments instead of cloning them scope.extend(fn_def.params.iter().cloned().zip(args.into_iter().map(|v| {
scope.extend( // Actually consume the arguments instead of cloning them
fn_def mem::take(*v)
.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)
}),
);
// Merge in encapsulated environment, if any // Merge in encapsulated environment, if any
let mut lib_merged = StaticVec::with_capacity(lib.len() + 1); let mut lib_merged = StaticVec::with_capacity(lib.len() + 1);

View File

@ -41,8 +41,8 @@
//! engine.register_fn("compute", compute_something); //! engine.register_fn("compute", compute_something);
//! //!
//! # #[cfg(not(feature = "no_std"))] //! # #[cfg(not(feature = "no_std"))]
//! # #[cfg(not(target_arch = "wasm32"))] //! # #[cfg(not(target_family = "wasm"))]
//! # #[cfg(not(target_arch = "wasm64"))] //! #
//! // Evaluate the script, expecting a 'bool' result //! // Evaluate the script, expecting a 'bool' result
//! let result = engine.eval_file::<bool>("my_script.rhai".into())?; //! let result = engine.eval_file::<bool>("my_script.rhai".into())?;
//! //!
@ -113,6 +113,7 @@ pub type INT = i32;
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
type UNSIGNED_INT = u64; type UNSIGNED_INT = u64;
/// The unsigned system integer base type. /// The unsigned system integer base type.
/// It is defined as [`u32`] since the `only_i32` feature is used. /// 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")] #[cfg(feature = "stdweb")]
compile_error!("`stdweb` cannot be used with `no-std`"); 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")] #[cfg(feature = "no_std")]
compile_error!("`no_std` cannot be used for WASM target"); compile_error!("`no_std` cannot be used for WASM target");
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
#[cfg(not(target_arch = "wasm64"))]
#[cfg(feature = "wasm-bindgen")] #[cfg(feature = "wasm-bindgen")]
compile_error!("`wasm-bindgen` cannot be used for non-WASM target"); compile_error!("`wasm-bindgen` cannot be used for non-WASM target");
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
#[cfg(not(target_arch = "wasm64"))]
#[cfg(feature = "stdweb")] #[cfg(feature = "stdweb")]
compile_error!("`stdweb` cannot be used non-WASM target"); compile_error!("`stdweb` cannot be used non-WASM target");

View File

@ -15,6 +15,7 @@ use crate::{
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{ use std::{
any::TypeId, any::TypeId,
cmp::Ordering,
collections::{BTreeMap, BTreeSet}, collections::{BTreeMap, BTreeSet},
fmt, fmt,
iter::{empty, once}, iter::{empty, once},
@ -31,11 +32,9 @@ pub enum FnNamespace {
Internal, Internal,
} }
/// Data structure containing a single registered function. /// A type containing all metadata for a registered function.
#[derive(Debug, Clone)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct FuncInfo { pub struct FnMetadata {
/// Function instance.
pub func: Shared<CallableFunction>,
/// Function namespace. /// Function namespace.
pub namespace: FnNamespace, pub namespace: FnNamespace,
/// Function access mode. /// Function access mode.
@ -44,59 +43,111 @@ pub struct FuncInfo {
pub name: Identifier, pub name: Identifier,
/// Number of parameters. /// Number of parameters.
pub params: usize, pub params: usize,
/// Parameter types (if applicable).
pub param_types: StaticVec<TypeId>,
/// Parameter names and types (if available). /// Parameter names and types (if available).
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
pub param_names_and_types: StaticVec<Identifier>, pub params_info: StaticVec<Identifier>,
/// Return type name. /// Return type name.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
pub return_type_name: Identifier, pub return_type: Identifier,
/// Comments. /// Comments.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
pub comments: Option<Box<[Box<str>]>>, pub comments: Option<Box<[Box<str>]>>,
} }
impl PartialOrd for FnMetadata {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
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<CallableFunction>,
/// Parameter types (if applicable).
pub param_types: StaticVec<TypeId>,
/// Function metadata.
pub metadata: FnMetadata,
}
impl FuncInfo { impl FuncInfo {
/// Format a return type to be display-friendly.
///
/// `()` is cleared.
/// [`RhaiResult`][crate::RhaiResult] and [`RhaiResultOf<T>`] are expanded.
#[cfg(feature = "metadata")]
pub fn format_return_type(typ: &str) -> std::borrow::Cow<str> {
const RHAI_RESULT_TYPE: &str = "RhaiResult";
const RHAI_RESULT_TYPE_EXPAND: &str = "Result<Dynamic, Box<EvalAltResult>>";
const RHAI_RESULT_OF_TYPE: &str = "RhaiResultOf<";
const RHAI_RESULT_OF_TYPE_EXPAND: &str = "Result<{}, Box<EvalAltResult>>";
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. /// Generate a signature of the function.
/// Exported under the `metadata` feature only. /// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
#[must_use] #[must_use]
pub fn gen_signature(&self) -> String { 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 let params: StaticVec<_> = self
.param_names_and_types .metadata
.params_info
.iter() .iter()
.map(|s| s.as_str()) .map(|s| s.as_str())
.collect(); .collect();
sig.push_str(&params.join(", ")); sig.push_str(&params.join(", "));
sig.push_str(")"); sig.push(')');
match self.return_type_name.as_str() { if !return_type.is_empty() {
"" | "()" => (), sig.push_str(" -> ");
ty => { sig.push_str(&return_type);
sig.push_str(" -> ");
sig.push_str(ty);
}
} }
} else { } else {
for x in 0..self.params { for x in 0..self.metadata.params {
sig.push('_'); sig.push('_');
if x < self.params - 1 { if x < self.metadata.params - 1 {
sig.push_str(", "); sig.push_str(", ");
} }
} }
if self.func.is_script() { sig.push(')');
sig.push(')');
} else {
sig.push_str(")");
match self.return_type_name.as_str() { if !self.func.is_script() {
"()" => (), sig.push(')');
_ => sig.push_str(" -> ?"),
if !return_type.is_empty() {
sig.push_str(" -> ");
sig.push_str(&return_type);
} }
} }
} }
@ -372,7 +423,7 @@ impl Module {
#[inline] #[inline]
pub fn gen_fn_signatures(&self) -> impl Iterator<Item = String> + '_ { pub fn gen_fn_signatures(&self) -> impl Iterator<Item = String> + '_ {
self.iter_fn() self.iter_fn()
.filter(|&f| match f.access { .filter(|&f| match f.metadata.access {
FnAccess::Public => true, FnAccess::Public => true,
FnAccess::Private => false, FnAccess::Private => false,
}) })
@ -478,22 +529,24 @@ impl Module {
let num_params = fn_def.params.len(); let num_params = fn_def.params.len();
let hash_script = crate::calc_fn_hash(&fn_def.name, num_params); let hash_script = crate::calc_fn_hash(&fn_def.name, num_params);
#[cfg(feature = "metadata")] #[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( self.functions.insert(
hash_script, hash_script,
FuncInfo { FuncInfo {
name: fn_def.name.clone(), metadata: FnMetadata {
namespace: FnNamespace::Internal, name: fn_def.name.clone(),
access: fn_def.access, namespace: FnNamespace::Internal,
params: num_params, access: fn_def.access,
param_types: StaticVec::new_const(), params: num_params,
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
param_names_and_types, params_info,
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
return_type_name: "Dynamic".into(), return_type: "Dynamic".into(),
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
comments: None, comments: None,
},
func: Into::<CallableFunction>::into(fn_def).into(), func: Into::<CallableFunction>::into(fn_def).into(),
param_types: StaticVec::new_const(),
} }
.into(), .into(),
); );
@ -518,7 +571,7 @@ impl Module {
let name = name.as_ref(); let name = name.as_ref();
self.iter_fn() 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()) .and_then(|f| f.func.get_script_fn_def())
} }
} }
@ -648,14 +701,14 @@ impl Module {
.collect(); .collect();
if let Some(f) = self.functions.get_mut(&hash_fn) { 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(); let return_type = param_names.pop().unwrap();
(param_names, return_type) (param_names, return_type)
} else { } else {
(param_names, Default::default()) (param_names, Default::default())
}; };
f.param_names_and_types = param_names; f.metadata.params_info = param_names;
f.return_type_name = return_type_name; f.metadata.return_type = return_type_name;
} }
self self
@ -698,7 +751,7 @@ impl Module {
if !comments.is_empty() { if !comments.is_empty() {
let f = self.functions.get_mut(&hash_fn).unwrap(); 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 self
@ -710,7 +763,7 @@ impl Module {
#[inline] #[inline]
pub fn update_fn_namespace(&mut self, hash_fn: u64, namespace: FnNamespace) -> &mut Self { pub fn update_fn_namespace(&mut self, hash_fn: u64, namespace: FnNamespace) -> &mut Self {
if let Some(f) = self.functions.get_mut(&hash_fn) { if let Some(f) = self.functions.get_mut(&hash_fn) {
f.namespace = namespace; f.metadata.namespace = namespace;
self.indexed = false; self.indexed = false;
self.contains_indexed_global_functions = false; self.contains_indexed_global_functions = false;
} }
@ -795,18 +848,20 @@ impl Module {
self.functions.insert( self.functions.insert(
hash_fn, hash_fn,
FuncInfo { FuncInfo {
name: name.as_ref().into(), metadata: FnMetadata {
namespace, name: name.as_ref().into(),
access, namespace,
params: param_types.len(), access,
param_types, params: param_types.len(),
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
param_names_and_types: param_names, params_info: param_names,
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
return_type_name, return_type: return_type_name,
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
comments: None, comments: None,
},
func: func.into(), func: func.into(),
param_types,
} }
.into(), .into(),
); );
@ -861,7 +916,7 @@ impl Module {
if !comments.is_empty() { if !comments.is_empty() {
let f = self.functions.get_mut(&hash).unwrap(); 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 hash
@ -1374,11 +1429,11 @@ impl Module {
.iter() .iter()
.filter(|&(_, f)| { .filter(|&(_, f)| {
_filter( _filter(
f.namespace, f.metadata.namespace,
f.access, f.metadata.access,
f.func.is_script(), f.func.is_script(),
f.name.as_str(), f.metadata.name.as_str(),
f.params, f.metadata.params,
) )
}) })
.map(|(&k, v)| (k, v.clone())), .map(|(&k, v)| (k, v.clone())),
@ -1404,7 +1459,12 @@ impl Module {
.into_iter() .into_iter()
.filter(|(_, f)| { .filter(|(_, f)| {
if f.func.is_script() { 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 { } else {
false false
} }
@ -1472,10 +1532,10 @@ impl Module {
> + '_ { > + '_ {
self.iter_fn().filter(|&f| f.func.is_script()).map(|f| { self.iter_fn().filter(|&f| f.func.is_script()).map(|f| {
( (
f.namespace, f.metadata.namespace,
f.access, f.metadata.access,
f.name.as_str(), f.metadata.name.as_str(),
f.params, f.metadata.params,
f.func.get_script_fn_def().expect("script-defined function"), f.func.get_script_fn_def().expect("script-defined function"),
) )
}) })
@ -1494,9 +1554,14 @@ impl Module {
pub fn iter_script_fn_info( pub fn iter_script_fn_info(
&self, &self,
) -> impl Iterator<Item = (FnNamespace, FnAccess, &str, usize)> { ) -> impl Iterator<Item = (FnNamespace, FnAccess, &str, usize)> {
self.iter_fn() self.iter_fn().filter(|&f| f.func.is_script()).map(|f| {
.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,
)
})
} }
/// _(internals)_ Get an iterator over all script-defined functions in the [`Module`]. /// _(internals)_ Get an iterator over all script-defined functions in the [`Module`].
@ -1607,7 +1672,7 @@ impl Module {
if ast.has_functions() { if ast.has_functions() {
ast.shared_lib() ast.shared_lib()
.iter_fn() .iter_fn()
.filter(|&f| match f.access { .filter(|&f| match f.metadata.access {
FnAccess::Public => true, FnAccess::Public => true,
FnAccess::Private => false, FnAccess::Private => false,
}) })
@ -1682,7 +1747,7 @@ impl Module {
// Index all Rust functions // Index all Rust functions
module.functions.iter().for_each(|(&hash, f)| { module.functions.iter().for_each(|(&hash, f)| {
match f.namespace { match f.metadata.namespace {
FnNamespace::Global => { FnNamespace::Global => {
// Flatten all functions with global namespace // Flatten all functions with global namespace
functions.insert(hash, f.func.clone()); functions.insert(hash, f.func.clone());
@ -1690,20 +1755,23 @@ impl Module {
} }
FnNamespace::Internal => (), FnNamespace::Internal => (),
} }
match f.access { match f.metadata.access {
FnAccess::Public => (), FnAccess::Public => (),
FnAccess::Private => return, // Do not index private functions FnAccess::Private => return, // Do not index private functions
} }
if !f.func.is_script() { if !f.func.is_script() {
let hash_qualified_fn = let hash_qualified_fn = calc_native_fn_hash(
calc_native_fn_hash(path.iter().cloned(), f.name.as_str(), &f.param_types); path.iter().cloned(),
f.metadata.name.as_str(),
&f.param_types,
);
functions.insert(hash_qualified_fn, f.func.clone()); functions.insert(hash_qualified_fn, f.func.clone());
} else if cfg!(not(feature = "no_function")) { } else if cfg!(not(feature = "no_function")) {
let hash_qualified_script = crate::calc_qualified_fn_hash( let hash_qualified_script = crate::calc_qualified_fn_hash(
path.iter().cloned(), path.iter().cloned(),
f.name.as_str(), f.metadata.name.as_str(),
f.params, f.metadata.params,
); );
functions.insert(hash_qualified_script, f.func.clone()); functions.insert(hash_qualified_script, f.func.clone());
} }

View File

@ -1,6 +1,5 @@
#![cfg(not(feature = "no_std"))] #![cfg(not(feature = "no_std"))]
#![cfg(not(target_arch = "wasm32"))] #![cfg(not(target_family = "wasm"))]
#![cfg(not(target_arch = "wasm64"))]
use crate::func::native::shared_write_lock; use crate::func::native::shared_write_lock;
use crate::{ use crate::{

View File

@ -11,8 +11,7 @@ mod stat;
pub use collection::ModuleResolversCollection; pub use collection::ModuleResolversCollection;
pub use dummy::DummyModuleResolver; pub use dummy::DummyModuleResolver;
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
#[cfg(not(target_arch = "wasm64"))]
pub use file::FileModuleResolver; pub use file::FileModuleResolver;
pub use stat::StaticModuleResolver; pub use stat::StaticModuleResolver;

View File

@ -121,14 +121,17 @@ macro_rules! gen_arithmetic_functions {
pub fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type { pub fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type {
x ^ y x ^ y
} }
/// Return true if the number is zero.
#[rhai_fn(get = "is_zero", name = "is_zero")] #[rhai_fn(get = "is_zero", name = "is_zero")]
pub fn is_zero(x: $arg_type) -> bool { pub fn is_zero(x: $arg_type) -> bool {
x == 0 x == 0
} }
/// Return true if the number is odd.
#[rhai_fn(get = "is_odd", name = "is_odd")] #[rhai_fn(get = "is_odd", name = "is_odd")]
pub fn is_odd(x: $arg_type) -> bool { pub fn is_odd(x: $arg_type) -> bool {
x % 2 != 0 x % 2 != 0
} }
/// Return true if the number is even.
#[rhai_fn(get = "is_even", name = "is_even")] #[rhai_fn(get = "is_even", name = "is_even")]
pub fn is_even(x: $arg_type) -> bool { pub fn is_even(x: $arg_type) -> bool {
x % 2 == 0 x % 2 == 0
@ -157,6 +160,7 @@ macro_rules! gen_signed_functions {
pub fn plus(x: $arg_type) -> $arg_type { pub fn plus(x: $arg_type) -> $arg_type {
x x
} }
/// Return the absolute value of the number.
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn abs(x: $arg_type) -> RhaiResultOf<$arg_type> { pub fn abs(x: $arg_type) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
@ -165,6 +169,11 @@ macro_rules! gen_signed_functions {
Ok(x.abs()) Ok(x.abs())
} }
} }
/// Return the sign (as an integer) of the number according to the following:
///
/// * `0` if the number is zero
/// * `1` if the number is positive
/// * `-1` if the number is negative
pub fn sign(x: $arg_type) -> INT { pub fn sign(x: $arg_type) -> INT {
x.signum() as INT x.signum() as INT
} }
@ -193,8 +202,8 @@ def_package! {
reg_functions!(lib += arith_numbers; i8, u8, i16, u16, i32, u32, u64); reg_functions!(lib += arith_numbers; i8, u8, i16, u16, i32, u32, u64);
reg_functions!(lib += signed_numbers; i8, i16, i32); reg_functions!(lib += signed_numbers; i8, i16, i32);
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
#[cfg(not(target_arch = "wasm64"))]
{ {
reg_functions!(lib += arith_num_128; i128, u128); reg_functions!(lib += arith_num_128; i128, u128);
reg_functions!(lib += signed_num_128; i128); reg_functions!(lib += signed_num_128; i128);
@ -216,14 +225,17 @@ def_package! {
#[export_module] #[export_module]
mod int_functions { mod int_functions {
/// Return true if the number is zero.
#[rhai_fn(get = "is_zero", name = "is_zero")] #[rhai_fn(get = "is_zero", name = "is_zero")]
pub fn is_zero(x: INT) -> bool { pub fn is_zero(x: INT) -> bool {
x == 0 x == 0
} }
/// Return true if the number is odd.
#[rhai_fn(get = "is_odd", name = "is_odd")] #[rhai_fn(get = "is_odd", name = "is_odd")]
pub fn is_odd(x: INT) -> bool { pub fn is_odd(x: INT) -> bool {
x % 2 != 0 x % 2 != 0
} }
/// Return true if the number is even.
#[rhai_fn(get = "is_even", name = "is_even")] #[rhai_fn(get = "is_even", name = "is_even")]
pub fn is_even(x: INT) -> bool { pub fn is_even(x: INT) -> bool {
x % 2 == 0 x % 2 == 0
@ -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_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
#[cfg(not(target_arch = "wasm64"))]
gen_arithmetic_functions!(arith_num_128 => i128, u128); gen_arithmetic_functions!(arith_num_128 => i128, u128);
gen_signed_functions!(signed_basic => INT); 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_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
#[cfg(not(target_arch = "wasm64"))]
gen_signed_functions!(signed_num_128 => i128); gen_signed_functions!(signed_num_128 => i128);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -334,9 +346,15 @@ mod f32_functions {
pub fn plus(x: f32) -> f32 { pub fn plus(x: f32) -> f32 {
x x
} }
/// Return the absolute value of the floating-point number.
pub fn abs(x: f32) -> f32 { pub fn abs(x: f32) -> f32 {
x.abs() x.abs()
} }
/// Return the sign (as an integer) of the floating-point number according to the following:
///
/// * `0` if the number is zero
/// * `1` if the number is positive
/// * `-1` if the number is negative
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn sign(x: f32) -> RhaiResultOf<INT> { pub fn sign(x: f32) -> RhaiResultOf<INT> {
match x.signum() { match x.signum() {
@ -345,6 +363,7 @@ mod f32_functions {
x => Ok(x as INT), x => Ok(x as INT),
} }
} }
/// Return true if the floating-point number is zero.
#[rhai_fn(get = "is_zero", name = "is_zero")] #[rhai_fn(get = "is_zero", name = "is_zero")]
pub fn is_zero(x: f32) -> bool { pub fn is_zero(x: f32) -> bool {
x == 0.0 x == 0.0
@ -442,9 +461,15 @@ mod f64_functions {
pub fn plus(x: f64) -> f64 { pub fn plus(x: f64) -> f64 {
x x
} }
/// Return the absolute value of the floating-point number.
pub fn abs(x: f64) -> f64 { pub fn abs(x: f64) -> f64 {
x.abs() x.abs()
} }
/// Return the sign (as an integer) of the floating-point number according to the following:
///
/// * `0` if the number is zero
/// * `1` if the number is positive
/// * `-1` if the number is negative
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn sign(x: f64) -> RhaiResultOf<INT> { pub fn sign(x: f64) -> RhaiResultOf<INT> {
match x.signum() { match x.signum() {
@ -453,6 +478,7 @@ mod f64_functions {
x => Ok(x as INT), x => Ok(x as INT),
} }
} }
/// Return true if the floating-point number is zero.
#[rhai_fn(get = "is_zero", name = "is_zero")] #[rhai_fn(get = "is_zero", name = "is_zero")]
pub fn is_zero(x: f64) -> bool { pub fn is_zero(x: f64) -> bool {
x == 0.0 x == 0.0
@ -536,9 +562,15 @@ pub mod decimal_functions {
pub fn plus(x: Decimal) -> Decimal { pub fn plus(x: Decimal) -> Decimal {
x x
} }
/// Return the absolute value of the decimal number.
pub fn abs(x: Decimal) -> Decimal { pub fn abs(x: Decimal) -> Decimal {
x.abs() x.abs()
} }
/// Return the sign (as an integer) of the decimal number according to the following:
///
/// * `0` if the number is zero
/// * `1` if the number is positive
/// * `-1` if the number is negative
pub fn sign(x: Decimal) -> INT { pub fn sign(x: Decimal) -> INT {
if x == Decimal::zero() { if x == Decimal::zero() {
0 0
@ -548,6 +580,7 @@ pub mod decimal_functions {
1 1
} }
} }
/// Return true if the decimal number is zero.
#[rhai_fn(get = "is_zero", name = "is_zero")] #[rhai_fn(get = "is_zero", name = "is_zero")]
pub fn is_zero(x: Decimal) -> bool { pub fn is_zero(x: Decimal) -> bool {
x.is_zero() x.is_zero()

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,10 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::eval::calc_index;
use crate::plugin::*; 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")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -18,118 +21,145 @@ def_package! {
mod bit_field_functions { mod bit_field_functions {
const BITS: usize = std::mem::size_of::<INT>() * 8; const BITS: usize = std::mem::size_of::<INT>() * 8;
/// Return `true` if the specified `bit` in the number is set.
///
/// If `bit` < 0, position counts from the MSB (Most Significant Bit).
///
/// # Example
///
/// ```rhai
/// let x = 123456;
///
/// print(x.get_bit(5)); // prints false
///
/// print(x.get_bit(6)); // prints true
///
/// print(x.get_bit(-48)); // prints true on 64-bit
/// ```
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn get_bit(value: INT, index: INT) -> RhaiResultOf<bool> { pub fn get_bit(value: INT, bit: INT) -> RhaiResultOf<bool> {
if index >= 0 { let bit = calc_index(BITS, bit, true, || {
let offset = index as usize; ERR::ErrorBitFieldBounds(BITS, bit, Position::NONE).into()
})?;
if offset >= BITS { Ok((value & (1 << bit)) != 0)
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())
}
} }
/// Set the specified `bit` in the number if the new value is `true`.
/// Clear the `bit` if the new value is `false`.
///
/// If `bit` < 0, position counts from the MSB (Most Significant Bit).
///
/// # Example
///
/// ```rhai
/// let x = 123456;
///
/// x.set_bit(5, true);
///
/// print(x); // prints 123488
///
/// x.set_bit(6, false);
///
/// print(x); // prints 123424
///
/// x.set_bit(-48, false);
///
/// print(x); // prints 57888 on 64-bit
/// ```
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn set_bit(value: &mut INT, index: INT, new_value: bool) -> RhaiResultOf<()> { pub fn set_bit(value: &mut INT, bit: INT, new_value: bool) -> RhaiResultOf<()> {
if index >= 0 { let bit = calc_index(BITS, bit, true, || {
let offset = index as usize; ERR::ErrorBitFieldBounds(BITS, bit, Position::NONE).into()
})?;
if offset >= BITS { let mask = 1 << bit;
Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()) if new_value {
} else { *value |= mask;
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(())
}
} else { } 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)] #[rhai_fn(name = "get_bits", return_raw)]
pub fn get_bits_range(value: INT, range: ExclusiveRange) -> RhaiResultOf<INT> { pub fn get_bits_range(value: INT, range: ExclusiveRange) -> RhaiResultOf<INT> {
let from = INT::max(range.start, 0); let from = INT::max(range.start, 0);
let to = INT::max(range.end, from); let to = INT::max(range.end, from);
get_bits(value, from, to - from) get_bits(value, from, to - from)
} }
/// Return an inclusive range of bits in the number as a new number.
///
/// # Example
///
/// ```rhai
/// let x = 123456;
///
/// print(x.get_bits(5..=9)); // print 18
/// ```
#[rhai_fn(name = "get_bits", return_raw)] #[rhai_fn(name = "get_bits", return_raw)]
pub fn get_bits_range_inclusive(value: INT, range: InclusiveRange) -> RhaiResultOf<INT> { pub fn get_bits_range_inclusive(value: INT, range: InclusiveRange) -> RhaiResultOf<INT> {
let from = INT::max(*range.start(), 0); let from = INT::max(*range.start(), 0);
let to = INT::max(*range.end(), from - 1); let to = INT::max(*range.end(), from - 1);
get_bits(value, from, to - from + 1) get_bits(value, from, to - from + 1)
} }
/// Return a portion of bits in the number as a new number.
///
/// * If `start` < 0, position counts from the MSB (Most Significant Bit).
/// * If `bits` ≤ 0, zero is returned.
/// * If `start` position + `bits` ≥ total number of bits, the bits after the `start` position are returned.
///
/// # Example
///
/// ```rhai
/// let x = 123456;
///
/// print(x.get_bits(5, 8)); // print 18
/// ```
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn get_bits(value: INT, index: INT, bits: INT) -> RhaiResultOf<INT> { pub fn get_bits(value: INT, start: INT, bits: INT) -> RhaiResultOf<INT> {
if bits < 1 { if bits <= 0 {
return Ok(0); return Ok(0);
} }
let offset = if index >= 0 { let bit = calc_index(BITS, start, true, || {
let offset = index as usize; ERR::ErrorBitFieldBounds(BITS, start, Position::NONE).into()
})?;
if offset >= BITS { let bits = if bit + bits as usize > BITS {
return Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()); BITS - bit
}
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
} else { } else {
bits as usize bits as usize
}; };
let mut base = 1; if bit == 0 && bits == BITS {
let mut mask = 0; return Ok(value);
for _ in 0..bits {
mask |= base;
base <<= 1;
} }
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)] #[rhai_fn(name = "set_bits", return_raw)]
pub fn set_bits_range( pub fn set_bits_range(
value: &mut INT, value: &mut INT,
@ -140,6 +170,17 @@ mod bit_field_functions {
let to = INT::max(range.end, from); let to = INT::max(range.end, from);
set_bits(value, from, to - from, new_value) set_bits(value, from, to - from, new_value)
} }
/// Replace an inclusive range of bits in the number with a new value.
///
/// # Example
///
/// ```rhai
/// let x = 123456;
///
/// x.set_bits(5..=9, 42);
///
/// print(x); // print 123200
/// ```
#[rhai_fn(name = "set_bits", return_raw)] #[rhai_fn(name = "set_bits", return_raw)]
pub fn set_bits_range_inclusive( pub fn set_bits_range_inclusive(
value: &mut INT, value: &mut INT,
@ -150,48 +191,51 @@ mod bit_field_functions {
let to = INT::max(*range.end(), from - 1); let to = INT::max(*range.end(), from - 1);
set_bits(value, from, to - from + 1, new_value) set_bits(value, from, to - from + 1, new_value)
} }
/// Replace a portion of bits in the number with a new value.
///
/// * If `start` < 0, position counts from the MSB (Most Significant Bit).
/// * If `bits` ≤ 0, the number is not modified.
/// * If `start` position + `bits` ≥ total number of bits, the bits after the `start` position are replaced.
///
/// # Example
///
/// ```rhai
/// let x = 123456;
///
/// x.set_bits(5, 8, 42);
///
/// print(x); // prints 124224
///
/// x.set_bits(-16, 10, 42);
///
/// print(x); // prints 11821949021971776 on 64-bit
/// ```
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn set_bits(value: &mut INT, index: INT, bits: INT, new_value: INT) -> RhaiResultOf<()> { pub fn set_bits(value: &mut INT, bit: INT, bits: INT, new_value: INT) -> RhaiResultOf<()> {
if bits < 1 { if bits <= 0 {
return Ok(()); return Ok(());
} }
let offset = if index >= 0 { let bit = calc_index(BITS, bit, true, || {
let offset = index as usize; ERR::ErrorBitFieldBounds(BITS, bit, Position::NONE).into()
})?;
if offset >= BITS { let bits = if bit + bits as usize > BITS {
return Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()); BITS - bit
}
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
} else { } else {
bits as usize bits as usize
}; };
let mut base = 1; if bit == 0 && bits == BITS {
let mut mask = 0; *value = new_value;
return Ok(());
for _ in 0..bits {
mask |= base;
base <<= 1;
} }
*value &= !(mask << index); // 2^bits - 1
*value |= (new_value & mask) << index; let mask = ((2 as UNSIGNED_INT).pow(bits as u32) - 1) as crate::INT;
*value &= !(mask << bit);
*value |= (new_value & mask) << bit;
Ok(()) Ok(())
} }

File diff suppressed because it is too large Load Diff

View File

@ -14,122 +14,34 @@ def_package! {
#[export_module] #[export_module]
mod fn_ptr_functions { mod fn_ptr_functions {
/// Return the name of the function.
///
/// # Example
///
/// ```rhai
/// fn double(x) { x * 2 }
///
/// let f = Fn("double");
///
/// print(f.name); // prints "double"
/// ```
#[rhai_fn(name = "name", get = "name", pure)] #[rhai_fn(name = "name", get = "name", pure)]
pub fn name(fn_ptr: &mut FnPtr) -> ImmutableString { pub fn name(fn_ptr: &mut FnPtr) -> ImmutableString {
fn_ptr.fn_name_raw().into() fn_ptr.fn_name_raw().into()
} }
/// Return `true` if the function is an anonymous function.
///
/// # Example
///
/// ```rhai
/// let f = |x| x * 2;
///
/// print(f.is_anonymous); // prints true
/// ```
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[rhai_fn(name = "is_anonymous", get = "is_anonymous", pure)] #[rhai_fn(name = "is_anonymous", get = "is_anonymous", pure)]
pub fn is_anonymous(fn_ptr: &mut FnPtr) -> bool { pub fn is_anonymous(fn_ptr: &mut FnPtr) -> bool {
fn_ptr.is_anonymous() fn_ptr.is_anonymous()
} }
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
pub fn get_fn_metadata_list(ctx: NativeCallContext) -> crate::Array {
collect_fn_metadata(ctx)
}
}
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
use crate::{ast::ScriptFnDef, Array, Identifier, Map};
use std::collections::BTreeSet;
// Create a metadata record for a function.
fn make_metadata(
dict: &BTreeSet<Identifier>,
namespace: Option<Identifier>,
func: &ScriptFnDef,
) -> Map {
const DICT: &str = "key exists";
let mut map = Map::new();
if let Some(ns) = namespace {
map.insert(dict.get("namespace").expect(DICT).clone(), ns.into());
}
map.insert(
dict.get("name").expect(DICT).clone(),
func.name.clone().into(),
);
map.insert(
dict.get("access").expect(DICT).clone(),
match func.access {
FnAccess::Public => dict.get("public").expect(DICT).clone(),
FnAccess::Private => dict.get("private").expect(DICT).clone(),
}
.into(),
);
map.insert(
dict.get("is_anonymous").expect(DICT).clone(),
func.name.starts_with(crate::engine::FN_ANONYMOUS).into(),
);
map.insert(
dict.get("params").expect(DICT).clone(),
func.params
.iter()
.cloned()
.map(Into::into)
.collect::<Array>()
.into(),
);
map
}
// Intern strings
let dict: BTreeSet<Identifier> = [
"namespace",
"name",
"access",
"public",
"private",
"is_anonymous",
"params",
]
.iter()
.map(|&s| s.into())
.collect();
let mut _list = ctx.iter_namespaces().flat_map(Module::iter_script_fn).fold(
Array::new(),
|mut list, (_, _, _, _, f)| {
list.push(make_metadata(&dict, None, f).into());
list
},
);
#[cfg(not(feature = "no_module"))]
{
// Recursively scan modules for script-defined functions.
fn scan_module(
list: &mut Array,
dict: &BTreeSet<Identifier>,
namespace: Identifier,
module: &Module,
) {
module.iter_script_fn().for_each(|(_, _, _, _, f)| {
list.push(make_metadata(dict, Some(namespace.clone()), f).into())
});
module.iter_sub_modules().for_each(|(ns, m)| {
let ns = format!(
"{}{}{}",
namespace,
crate::tokenizer::Token::DoubleColon.literal_syntax(),
ns
);
scan_module(list, dict, ns.into(), m.as_ref())
});
}
ctx.iter_imports_raw()
.for_each(|(ns, m)| scan_module(&mut _list, &dict, ns.clone(), m.as_ref()));
}
_list
} }

View File

@ -1,3 +1,4 @@
use crate::eval::calc_index;
use crate::plugin::*; use crate::plugin::*;
use crate::types::dynamic::Variant; use crate::types::dynamic::Variant;
use crate::{def_package, ExclusiveRange, InclusiveRange, RhaiResultOf, INT}; use crate::{def_package, ExclusiveRange, InclusiveRange, RhaiResultOf, INT};
@ -120,30 +121,9 @@ const BITS: usize = std::mem::size_of::<INT>() * 8;
impl BitRange { impl BitRange {
pub fn new(value: INT, from: INT, len: INT) -> RhaiResultOf<Self> { pub fn new(value: INT, from: INT, len: INT) -> RhaiResultOf<Self> {
let from = if from >= 0 { let from = calc_index(BITS, from, true, || {
let offset = from as usize; crate::ERR::ErrorBitFieldBounds(BITS, from, Position::NONE).into()
})?;
#[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 len = if len < 0 { let len = if len < 0 {
0 0
@ -266,10 +246,20 @@ macro_rules! reg_range {
let _hash = $lib.set_native_fn($x, |from: $y, to: $y| Ok(from..to)); let _hash = $lib.set_native_fn($x, |from: $y, to: $y| Ok(from..to));
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
$lib.update_fn_metadata(_hash, &[ $lib.update_fn_metadata_with_comments(_hash, [
concat!("from: ", stringify!($y)), concat!("from: ", stringify!($y)),
concat!("to: ", stringify!($y)), concat!("to: ", stringify!($y)),
concat!("Iterator<Item=", stringify!($y), ">") concat!("Iterator<Item=", stringify!($y), ">"),
], [
"/// Return an iterator over the range of `from..to`.",
"///",
"/// # Example",
"///",
"/// ```rhai",
"/// for n in range(8, 18) {",
"/// print(n);",
"/// }",
"/// ```"
]); ]);
$lib.set_iterator::<RangeInclusive<$y>>(); $lib.set_iterator::<RangeInclusive<$y>>();
@ -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)); let _hash = $lib.set_native_fn($x, |from: $y, to: $y, step: $y| StepRange::new(from, to, step));
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
$lib.update_fn_metadata(_hash, &[ $lib.update_fn_metadata_with_comments(_hash, [
concat!("from: ", stringify!($y)), concat!("from: ", stringify!($y)),
concat!("to: ", stringify!($y)), concat!("to: ", stringify!($y)),
concat!("step: ", stringify!($y)), concat!("step: ", stringify!($y)),
concat!("Iterator<Item=", stringify!($y), ">") concat!("Iterator<Item=", stringify!($y), ">")
], [
"/// Return an iterator over the range of `from..to`, each iterator increasing by `step`.",
"///",
"/// If `from` > `to` and `step` < 0, the iteration goes backwards.",
"///",
"/// # Example",
"///",
"/// ```rhai",
"/// for n in range(8, 18, 3) {",
"/// print(n);",
"/// }",
"///",
"/// for n in range(18, 8, -3) {",
"/// print(n);",
"/// }",
"/// ```"
]); ]);
)* )*
}; };
@ -303,8 +309,8 @@ def_package! {
{ {
reg_range!(lib | "range" => i8, u8, i16, u16, i32, u32, i64, u64); reg_range!(lib | "range" => i8, u8, i16, u16, i32, u32, i64, u64);
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
#[cfg(not(target_arch = "wasm64"))]
reg_range!(lib | "range" => i128, u128); 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); reg_range!(lib | step "range" => i8, u8, i16, u16, i32, u32, i64, u64);
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
#[cfg(not(target_arch = "wasm64"))]
reg_range!(lib | step "range" => i128, u128); reg_range!(lib | step "range" => i128, u128);
} }
@ -379,7 +385,27 @@ def_package! {
let _hash = lib.set_native_fn("range", StepFloatRange::new); let _hash = lib.set_native_fn("range", StepFloatRange::new);
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["from: FLOAT", "to: FLOAT", "step: FLOAT", "Iterator<Item=FLOAT>"]); lib.update_fn_metadata_with_comments(
_hash,
["from: FLOAT", "to: FLOAT", "step: FLOAT", "Iterator<Item=FLOAT>"],
[
"/// Return an iterator over the range of `from..to`, each iterator increasing by `step`.",
"///",
"/// If `from` > `to` and `step` < 0, the iteration goes backwards.",
"///",
"/// # Example",
"///",
"/// ```rhai",
"/// for n in range(8.0, 18.0, 3.0) {",
"/// print(n);",
"/// }",
"///",
"/// for n in range(18.0, 8.0, -3.0) {",
"/// print(n);",
"/// }",
"/// ```"
]
);
} }
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
@ -441,7 +467,15 @@ def_package! {
let _hash = lib.set_native_fn("range", StepDecimalRange::new); let _hash = lib.set_native_fn("range", StepDecimalRange::new);
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["from: Decimal", "to: Decimal", "step: Decimal", "Iterator<Item=Decimal>"]); lib.update_fn_metadata_with_comments(
_hash,
["from: Decimal", "to: Decimal", "step: Decimal", "Iterator<Item=Decimal>"],
[
"/// Return an iterator over the range of `from..to`, each iterator increasing by `step`.",
"///",
"/// If `from` > `to` and `step` < 0, the iteration goes backwards.",
]
);
} }
// Register string iterator // Register string iterator
@ -453,7 +487,21 @@ def_package! {
Ok(CharsStream::new(string, from, to - from)) Ok(CharsStream::new(string, from, to - from))
}); });
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &str", "range: Range<INT>", "Iterator<Item=char>"]); lib.update_fn_metadata_with_comments(
_hash,
["string: &str", "range: Range<INT>", "Iterator<Item=char>"],
[
"/// Return an iterator over an exclusive range of characters in the string.",
"///",
"/// # Example",
"///",
"/// ```rhai",
r#"/// for ch in "hello, world!".chars(2..5) {"#,
"/// print(ch);",
"/// }",
"/// ```"
]
);
let _hash = lib.set_native_fn("chars", |string, range: InclusiveRange| { let _hash = lib.set_native_fn("chars", |string, range: InclusiveRange| {
let from = INT::max(*range.start(), 0); let from = INT::max(*range.start(), 0);
@ -461,25 +509,105 @@ def_package! {
Ok(CharsStream::new(string, from, to-from + 1)) Ok(CharsStream::new(string, from, to-from + 1))
}); });
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &str", "range: RangeInclusive<INT>", "Iterator<Item=char>"]); lib.update_fn_metadata_with_comments(
_hash,
["string: &str", "range: RangeInclusive<INT>", "Iterator<Item=char>"],
[
"/// Return an iterator over an inclusive range of characters in the string.",
"///",
"/// # Example",
"///",
"/// ```rhai",
r#"/// for ch in "hello, world!".chars(2..=6) {"#,
"/// print(ch);",
"/// }",
"/// ```"
]
);
let _hash = lib.set_native_fn("chars", |string, from, len| Ok(CharsStream::new(string, from, len))); let _hash = lib.set_native_fn("chars", |string, from, len| Ok(CharsStream::new(string, from, len)));
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &str", "from: INT", "len: INT", "Iterator<Item=char>"]); lib.update_fn_metadata_with_comments(
_hash,
["string: &str", "start: INT", "len: INT", "Iterator<Item=char>"],
[
"/// Return an iterator over a portion of characters in the string.",
"///",
"/// * If `start` < 0, position counts from the end of the string (`-1` is the last character).",
"/// * If `start` < -length of string, position counts from the beginning of the string.",
"/// * If `start` ≥ length of string, an empty iterator is returned.",
"/// * If `len` ≤ 0, an empty iterator is returned.",
"/// * If `start` position + `len` ≥ length of string, all characters of the string after the `start` position are iterated.",
"///",
"/// # Example",
"///",
"/// ```rhai",
r#"/// for ch in "hello, world!".chars(2, 4) {"#,
"/// print(ch);",
"/// }",
"/// ```"
]
);
let _hash = lib.set_native_fn("chars", |string, from| Ok(CharsStream::new(string, from, INT::MAX))); let _hash = lib.set_native_fn("chars", |string, from| Ok(CharsStream::new(string, from, INT::MAX)));
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &str", "from: INT", "Iterator<Item=char>"]); lib.update_fn_metadata_with_comments(
_hash,
["string: &str", "from: INT", "Iterator<Item=char>"],
[
"/// Return an iterator over the characters in the string starting from the `start` position.",
"///",
"/// * If `start` < 0, position counts from the end of the string (`-1` is the last character).",
"/// * If `start` < -length of string, position counts from the beginning of the string.",
"/// * If `start` ≥ length of string, an empty iterator is returned.",
"///",
"/// # Example",
"///",
"/// ```rhai",
r#"/// for ch in "hello, world!".chars(2) {"#,
"/// print(ch);",
"/// }",
"/// ```"
]
);
let _hash = lib.set_native_fn("chars", |string| Ok(CharsStream::new(string, 0, INT::MAX))); let _hash = lib.set_native_fn("chars", |string| Ok(CharsStream::new(string, 0, INT::MAX)));
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &str", "Iterator<Item=char>"]); lib.update_fn_metadata_with_comments(
_hash,
["string: &str", "Iterator<Item=char>"],
[
"/// Return an iterator over the characters in the string.",
"///",
"/// # Example",
"///",
"/// ```rhai",
r#"/// for ch in "hello, world!".chars() {"#,
"/// print(ch);",
"/// }",
"/// ```"
]
);
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
{ {
let _hash = lib.set_getter_fn("chars", |string: &mut ImmutableString| Ok(CharsStream::new(string, 0, INT::MAX))); let _hash = lib.set_getter_fn("chars", |string: &mut ImmutableString| Ok(CharsStream::new(string, 0, INT::MAX)));
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &mut ImmutableString", "Iterator<Item=char>"]); lib.update_fn_metadata_with_comments(
_hash,
["string: &mut ImmutableString", "Iterator<Item=char>"],
[
"/// Return an iterator over all the characters in the string.",
"///",
"/// # Example",
"///",
"/// ```rhai",
r#"/// for ch in "hello, world!".chars {"#,
"/// print(ch);",
"/// }",
"/// ```"
]
);
} }
// Register bit-field iterator // Register bit-field iterator
@ -491,7 +619,23 @@ def_package! {
BitRange::new(value, from, to - from) BitRange::new(value, from, to - from)
}); });
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "range: Range<INT>", "Iterator<Item=bool>"]); lib.update_fn_metadata_with_comments(
_hash,
["value: INT", "range: Range<INT>", "Iterator<Item=bool>"],
[
"/// Return an iterator over an exclusive range of bits in the number.",
"///",
"/// # Example",
"///",
"/// ```rhai",
"/// let x = 123456;",
"///",
"/// for bit in x.bits(10..24) {",
"/// print(bit);",
"/// }",
"/// ```"
]
);
let _hash = lib.set_native_fn("bits", |value, range: InclusiveRange| { let _hash = lib.set_native_fn("bits", |value, range: InclusiveRange| {
let from = INT::max(*range.start(), 0); let from = INT::max(*range.start(), 0);
@ -499,25 +643,111 @@ def_package! {
BitRange::new(value, from, to - from + 1) BitRange::new(value, from, to - from + 1)
}); });
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "range: RangeInclusive<INT>", "Iterator<Item=bool>"]); lib.update_fn_metadata_with_comments(
_hash,
["value: INT", "range: RangeInclusive<INT>", "Iterator<Item=bool>"],
[
"/// Return an iterator over an inclusive range of bits in the number.",
"///",
"/// # Example",
"///",
"/// ```rhai",
"/// let x = 123456;",
"///",
"/// for bit in x.bits(10..=23) {",
"/// print(bit);",
"/// }",
"/// ```"
]
);
let _hash = lib.set_native_fn("bits", BitRange::new); let _hash = lib.set_native_fn("bits", BitRange::new);
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "len: INT", "Iterator<Item=bool>"]); lib.update_fn_metadata_with_comments(
_hash,
["value: INT", "from: INT", "len: INT", "Iterator<Item=bool>"],
[
"/// Return an iterator over a portion of bits in the number.",
"///",
"/// * If `start` < 0, position counts from the MSB (Most Significant Bit)>.",
"/// * If `len` ≤ 0, an empty iterator is returned.",
"/// * If `start` position + `len` ≥ length of string, all bits of the number after the `start` position are iterated.",
"///",
"/// # Example",
"///",
"/// ```rhai",
"/// let x = 123456;",
"///",
"/// for bit in x.bits(10, 8) {",
"/// print(bit);",
"/// }",
"/// ```"
]
);
let _hash = lib.set_native_fn("bits", |value, from| BitRange::new(value, from, INT::MAX)); let _hash = lib.set_native_fn("bits", |value, from| BitRange::new(value, from, INT::MAX));
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "Iterator<Item=bool>"]); lib.update_fn_metadata_with_comments(
_hash,
["value: INT", "from: INT", "Iterator<Item=bool>"],
[
"/// Return an iterator over the bits in the number starting from the specified `start` position.",
"///",
"/// If `start` < 0, position counts from the MSB (Most Significant Bit)>.",
"///",
"/// # Example",
"///",
"/// ```rhai",
"/// let x = 123456;",
"///",
"/// for bit in x.bits(10) {",
"/// print(bit);",
"/// }",
"/// ```"
]
);
let _hash = lib.set_native_fn("bits", |value| BitRange::new(value, 0, INT::MAX) ); let _hash = lib.set_native_fn("bits", |value| BitRange::new(value, 0, INT::MAX) );
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "Iterator<Item=bool>"]); lib.update_fn_metadata_with_comments(
_hash,
["value: INT", "Iterator<Item=bool>"],
[
"/// Return an iterator over all the bits in the number.",
"///",
"/// # Example",
"///",
"/// ```rhai",
"/// let x = 123456;",
"///",
"/// for bit in x.bits() {",
"/// print(bit);",
"/// }",
"/// ```"
]
);
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
{ {
let _hash = lib.set_getter_fn("bits", |value: &mut INT| BitRange::new(*value, 0, INT::MAX) ); let _hash = lib.set_getter_fn("bits", |value: &mut INT| BitRange::new(*value, 0, INT::MAX) );
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: &mut INT", "range: Range<INT>", "Iterator<Item=bool>"]); lib.update_fn_metadata_with_comments(
_hash,
["value: &mut INT", "range: Range<INT>", "Iterator<Item=bool>"],
[
"/// Return an iterator over all the bits in the number.",
"///",
"/// # Example",
"///",
"/// ```rhai",
"/// let x = 123456;",
"///",
"/// for bit in x.bits {",
"/// print(bit);",
"/// }",
"/// ```"
]
);
} }
combine_with_exported_module!(lib, "range", range_functions); combine_with_exported_module!(lib, "range", range_functions);
@ -526,37 +756,45 @@ def_package! {
#[export_module] #[export_module]
mod range_functions { mod range_functions {
/// Return the start of the exclusive range.
#[rhai_fn(get = "start", name = "start", pure)] #[rhai_fn(get = "start", name = "start", pure)]
pub fn start(range: &mut ExclusiveRange) -> INT { pub fn start(range: &mut ExclusiveRange) -> INT {
range.start range.start
} }
/// Return the end of the exclusive range.
#[rhai_fn(get = "end", name = "end", pure)] #[rhai_fn(get = "end", name = "end", pure)]
pub fn end(range: &mut ExclusiveRange) -> INT { pub fn end(range: &mut ExclusiveRange) -> INT {
range.end range.end
} }
/// Return `true` if the range is inclusive.
#[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)] #[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)]
pub fn is_inclusive(range: &mut ExclusiveRange) -> bool { pub fn is_inclusive(range: &mut ExclusiveRange) -> bool {
let _range = range; let _range = range;
false false
} }
/// Return `true` if the range is exclusive.
#[rhai_fn(get = "is_exclusive", name = "is_exclusive", pure)] #[rhai_fn(get = "is_exclusive", name = "is_exclusive", pure)]
pub fn is_exclusive(range: &mut ExclusiveRange) -> bool { pub fn is_exclusive(range: &mut ExclusiveRange) -> bool {
let _range = range; let _range = range;
true true
} }
/// Return the start of the inclusive range.
#[rhai_fn(get = "start", name = "start", pure)] #[rhai_fn(get = "start", name = "start", pure)]
pub fn start_inclusive(range: &mut InclusiveRange) -> INT { pub fn start_inclusive(range: &mut InclusiveRange) -> INT {
*range.start() *range.start()
} }
/// Return the end of the inclusive range.
#[rhai_fn(get = "end", name = "end", pure)] #[rhai_fn(get = "end", name = "end", pure)]
pub fn end_inclusive(range: &mut InclusiveRange) -> INT { pub fn end_inclusive(range: &mut InclusiveRange) -> INT {
*range.end() *range.end()
} }
/// Return `true` if the range is inclusive.
#[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)] #[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)]
pub fn is_inclusive_inclusive(range: &mut InclusiveRange) -> bool { pub fn is_inclusive_inclusive(range: &mut InclusiveRange) -> bool {
let _range = range; let _range = range;
true true
} }
/// Return `true` if the range is exclusive.
#[rhai_fn(get = "is_exclusive", name = "is_exclusive", pure)] #[rhai_fn(get = "is_exclusive", name = "is_exclusive", pure)]
pub fn is_exclusive_inclusive(range: &mut InclusiveRange) -> bool { pub fn is_exclusive_inclusive(range: &mut InclusiveRange) -> bool {
let _range = range; let _range = range;

View File

@ -53,4 +53,112 @@ mod core_functions {
Ok(()) Ok(())
} }
} }
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
pub fn get_fn_metadata_list(ctx: NativeCallContext) -> crate::Array {
collect_fn_metadata(ctx)
}
}
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
use crate::{ast::ScriptFnDef, Array, Identifier, Map};
use std::collections::BTreeSet;
// Create a metadata record for a function.
fn make_metadata(
dict: &BTreeSet<Identifier>,
namespace: Option<Identifier>,
func: &ScriptFnDef,
) -> Map {
const DICT: &str = "key exists";
let mut map = Map::new();
if let Some(ns) = namespace {
map.insert(dict.get("namespace").expect(DICT).clone(), ns.into());
}
map.insert(
dict.get("name").expect(DICT).clone(),
func.name.clone().into(),
);
map.insert(
dict.get("access").expect(DICT).clone(),
match func.access {
FnAccess::Public => dict.get("public").expect(DICT).clone(),
FnAccess::Private => dict.get("private").expect(DICT).clone(),
}
.into(),
);
map.insert(
dict.get("is_anonymous").expect(DICT).clone(),
func.name.starts_with(crate::engine::FN_ANONYMOUS).into(),
);
map.insert(
dict.get("params").expect(DICT).clone(),
func.params
.iter()
.cloned()
.map(Into::into)
.collect::<Array>()
.into(),
);
map
}
// Intern strings
let dict: BTreeSet<Identifier> = [
"namespace",
"name",
"access",
"public",
"private",
"is_anonymous",
"params",
]
.iter()
.map(|&s| s.into())
.collect();
let mut _list = ctx.iter_namespaces().flat_map(Module::iter_script_fn).fold(
Array::new(),
|mut list, (_, _, _, _, f)| {
list.push(make_metadata(&dict, None, f).into());
list
},
);
#[cfg(not(feature = "no_module"))]
{
// Recursively scan modules for script-defined functions.
fn scan_module(
list: &mut Array,
dict: &BTreeSet<Identifier>,
namespace: Identifier,
module: &Module,
) {
module.iter_script_fn().for_each(|(_, _, _, _, f)| {
list.push(make_metadata(dict, Some(namespace.clone()), f).into())
});
module.iter_sub_modules().for_each(|(ns, m)| {
let ns = format!(
"{}{}{}",
namespace,
crate::tokenizer::Token::DoubleColon.literal_syntax(),
ns
);
scan_module(list, dict, ns.into(), m.as_ref())
});
}
ctx.iter_imports_raw()
.for_each(|(ns, m)| scan_module(&mut _list, &dict, ns.clone(), m.as_ref()));
}
_list
} }

View File

@ -47,8 +47,8 @@ def_package! {
{ {
reg_functions!(lib += numbers; i8, u8, i16, u16, i32, u32, u64); reg_functions!(lib += numbers; i8, u8, i16, u16, i32, u32, u64);
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
#[cfg(not(target_arch = "wasm64"))]
reg_functions!(lib += num_128; i128, u128); 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_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
#[cfg(not(target_arch = "wasm64"))]
gen_cmp_functions!(num_128 => i128, u128); gen_cmp_functions!(num_128 => i128, u128);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]

View File

@ -20,15 +20,32 @@ def_package! {
#[export_module] #[export_module]
mod map_functions { mod map_functions {
/// Return the number of properties in the object map.
#[rhai_fn(pure)] #[rhai_fn(pure)]
pub fn len(map: &mut Map) -> INT { pub fn len(map: &mut Map) -> INT {
map.len() as INT map.len() as INT
} }
/// Clear the object map.
pub fn clear(map: &mut Map) { pub fn clear(map: &mut Map) {
if !map.is_empty() { if !map.is_empty() {
map.clear(); map.clear();
} }
} }
/// Remove any property of the specified `name` from the object map, returning its value.
///
/// If the property does not exist, `()` is returned.
///
/// # Example
///
/// ```rhai
/// let m = #{a:1, b:2, c:3};
///
/// let x = m.remove("b");
///
/// print(x); // prints 2
///
/// print(m); // prints "#{a:1, c:3}"
/// ```
pub fn remove(map: &mut Map, name: ImmutableString) -> Dynamic { pub fn remove(map: &mut Map, name: ImmutableString) -> Dynamic {
if !map.is_empty() { if !map.is_empty() {
map.remove(name.as_str()).unwrap_or_else(|| Dynamic::UNIT) map.remove(name.as_str()).unwrap_or_else(|| Dynamic::UNIT)
@ -36,12 +53,38 @@ mod map_functions {
Dynamic::UNIT Dynamic::UNIT
} }
} }
/// Add all property values of another object map into the object map.
/// Existing property values of the same names are replaced.
///
/// # Example
///
/// ```rhai
/// let m = #{a:1, b:2, c:3};
/// let n = #{a: 42, d:0};
///
/// m.mixin(n);
///
/// print(m); // prints "#{a:42, b:2, c:3, d:0}"
/// ```
#[rhai_fn(name = "mixin", name = "+=")] #[rhai_fn(name = "mixin", name = "+=")]
pub fn mixin(map: &mut Map, map2: Map) { pub fn mixin(map: &mut Map, map2: Map) {
if !map2.is_empty() { if !map2.is_empty() {
map.extend(map2.into_iter()); map.extend(map2.into_iter());
} }
} }
/// Make a copy of the object map, add all property values of another object map
/// (existing property values of the same names are replaced), then returning it.
///
/// # Example
///
/// ```rhai
/// let m = #{a:1, b:2, c:3};
/// let n = #{a: 42, d:0};
///
/// print(m + n); // prints "#{a:42, b:2, c:3, d:0}"
///
/// print(m); // prints "#{a:1, b:2, c:3}"
/// ```
#[rhai_fn(name = "+")] #[rhai_fn(name = "+")]
pub fn merge(map1: Map, map2: Map) -> Map { pub fn merge(map1: Map, map2: Map) -> Map {
if map2.is_empty() { if map2.is_empty() {
@ -54,6 +97,19 @@ mod map_functions {
map1 map1
} }
} }
/// Add all property values of another object map into the object map.
/// Only properties that do not originally exist in the object map are added.
///
/// # Example
///
/// ```rhai
/// let m = #{a:1, b:2, c:3};
/// let n = #{a: 42, d:0};
///
/// m.fill_with(n);
///
/// print(m); // prints "#{a:1, b:2, c:3, d:0}"
/// ```
pub fn fill_with(map: &mut Map, map2: Map) { pub fn fill_with(map: &mut Map, map2: Map) {
if !map2.is_empty() { if !map2.is_empty() {
if map.is_empty() { if map.is_empty() {
@ -65,6 +121,22 @@ mod map_functions {
} }
} }
} }
/// Return `true` if two object maps are equal (i.e. all property values are equal).
///
/// The operator `==` is used to compare property values and must be defined,
/// otherwise `false` is assumed.
///
/// # Example
///
/// ```rhai
/// let m1 = #{a:1, b:2, c:3};
/// let m2 = #{a:1, b:2, c:3};
/// let m3 = #{a:1, c:3};
///
/// print(m1 == m2); // prints true
///
/// print(m1 == m3); // prints false
/// ```
#[rhai_fn(name = "==", return_raw, pure)] #[rhai_fn(name = "==", return_raw, pure)]
pub fn equals(ctx: NativeCallContext, map1: &mut Map, map2: Map) -> RhaiResultOf<bool> { pub fn equals(ctx: NativeCallContext, map1: &mut Map, map2: Map) -> RhaiResultOf<bool> {
if map1.len() != map2.len() { if map1.len() != map2.len() {
@ -77,8 +149,9 @@ mod map_functions {
for (m1, v1) in map1.iter_mut() { for (m1, v1) in map1.iter_mut() {
if let Some(v2) = map2.get_mut(m1) { if let Some(v2) = map2.get_mut(m1) {
let equals = ctx let equals = ctx
.call_fn_raw(OP_EQUALS, true, false, &mut [v1, v2]) .call_fn_raw(OP_EQUALS, true, false, &mut [v1, v2])?
.map(|v| v.as_bool().unwrap_or(false))?; .as_bool()
.unwrap_or(false);
if !equals { if !equals {
return Ok(false); return Ok(false);
@ -91,11 +164,36 @@ mod map_functions {
Ok(true) Ok(true)
} }
/// Return `true` if two object maps are not equal (i.e. at least one property value is not equal).
///
/// The operator `==` is used to compare property values and must be defined,
/// otherwise `false` is assumed.
///
/// # Example
///
/// ```rhai
/// let m1 = #{a:1, b:2, c:3};
/// let m2 = #{a:1, b:2, c:3};
/// let m3 = #{a:1, c:3};
///
/// print(m1 != m2); // prints false
///
/// print(m1 != m3); // prints true
/// ```
#[rhai_fn(name = "!=", return_raw, pure)] #[rhai_fn(name = "!=", return_raw, pure)]
pub fn not_equals(ctx: NativeCallContext, map1: &mut Map, map2: Map) -> RhaiResultOf<bool> { pub fn not_equals(ctx: NativeCallContext, map1: &mut Map, map2: Map) -> RhaiResultOf<bool> {
equals(ctx, map1, map2).map(|r| !r) equals(ctx, map1, map2).map(|r| !r)
} }
/// Return an array with all the property names in the object map.
///
/// # Example
///
/// ```rhai
/// let m = #{a:1, b:2, c:3};
///
/// print(m.keys()); // prints ["a", "b", "c"]
/// ```
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
#[rhai_fn(pure)] #[rhai_fn(pure)]
pub fn keys(map: &mut Map) -> Array { pub fn keys(map: &mut Map) -> Array {
@ -105,6 +203,15 @@ mod map_functions {
map.keys().cloned().map(Into::into).collect() map.keys().cloned().map(Into::into).collect()
} }
} }
/// Return an array with all the property values in the object map.
///
/// # Example
///
/// ```rhai
/// let m = #{a:1, b:2, c:3};
///
/// print(m.values()); // prints "[1, 2, 3]""
/// ```
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
#[rhai_fn(pure)] #[rhai_fn(pure)]
pub fn values(map: &mut Map) -> Array { pub fn values(map: &mut Map) -> Array {

View File

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

View File

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

View File

@ -73,6 +73,15 @@ mod string_functions {
string string
} }
/// Return the length of the string, in number of characters.
///
/// # Example
///
/// ```rhai
/// let text = "朝には紅顔ありて夕べには白骨となる";
///
/// print(text.len); // prints 17
/// ```
#[rhai_fn(name = "len", get = "len")] #[rhai_fn(name = "len", get = "len")]
pub fn len(string: &str) -> INT { pub fn len(string: &str) -> INT {
if string.is_empty() { if string.is_empty() {
@ -81,6 +90,15 @@ mod string_functions {
string.chars().count() as INT string.chars().count() as INT
} }
} }
/// Return the length of the string, in number of bytes used to store it in UTF-8 encoding.
///
/// # Example
///
/// ```rhai
/// let text = "朝には紅顔ありて夕べには白骨となる";
///
/// print(text.bytes); // prints 51
/// ```
#[rhai_fn(name = "bytes", get = "bytes")] #[rhai_fn(name = "bytes", get = "bytes")]
pub fn bytes(string: &str) -> INT { pub fn bytes(string: &str) -> INT {
if string.is_empty() { if string.is_empty() {
@ -89,18 +107,59 @@ mod string_functions {
string.len() as INT string.len() as INT
} }
} }
/// Remove all occurrences of a sub-string from the string.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world! hello, foobar!";
///
/// text.remove("hello");
///
/// print(text); // prints ", world! , foobar!"
/// ```
pub fn remove(string: &mut ImmutableString, sub_string: ImmutableString) { pub fn remove(string: &mut ImmutableString, sub_string: ImmutableString) {
*string -= sub_string; *string -= sub_string;
} }
/// Remove all occurrences of a character from the string.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world! hello, foobar!";
///
/// text.remove("o");
///
/// print(text); // prints "hell, wrld! hell, fbar!"
/// ```
#[rhai_fn(name = "remove")] #[rhai_fn(name = "remove")]
pub fn remove_char(string: &mut ImmutableString, character: char) { pub fn remove_char(string: &mut ImmutableString, character: char) {
*string -= character; *string -= character;
} }
/// Clear the string, making it empty.
pub fn clear(string: &mut ImmutableString) { pub fn clear(string: &mut ImmutableString) {
if !string.is_empty() { if !string.is_empty() {
string.make_mut().clear(); string.make_mut().clear();
} }
} }
/// Cut off the string at the specified number of characters.
///
/// * If `len` ≤ 0, the string is cleared.
/// * If `len` ≥ length of string, the string is not truncated.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world! hello, foobar!";
///
/// text.truncate(13);
///
/// print(text); // prints "hello, world!"
///
/// x.truncate(10);
///
/// print(text); // prints "hello, world!"
/// ```
pub fn truncate(string: &mut ImmutableString, len: INT) { pub fn truncate(string: &mut ImmutableString, len: INT) {
if len > 0 { if len > 0 {
let chars: StaticVec<_> = string.chars().collect(); let chars: StaticVec<_> = string.chars().collect();
@ -111,6 +170,15 @@ mod string_functions {
string.make_mut().clear(); string.make_mut().clear();
} }
} }
/// Remove whitespace characters from both ends of the string.
///
/// # Example
///
/// ```rhai
/// let text = " hello ";
///
/// print(text.trim()); // prints "hello"
/// ```
pub fn trim(string: &mut ImmutableString) { pub fn trim(string: &mut ImmutableString) {
let trimmed = string.trim(); let trimmed = string.trim();
@ -118,6 +186,19 @@ mod string_functions {
*string = trimmed.to_string().into(); *string = trimmed.to_string().into();
} }
} }
/// Remove the last character from the string and return it.
///
/// If the string is empty, `()` is returned.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world!";
///
/// print(text.pop()); // prints '!'
///
/// print(text); // prints "hello, world"
/// ```
pub fn pop(string: &mut ImmutableString) -> Dynamic { pub fn pop(string: &mut ImmutableString) -> Dynamic {
if string.is_empty() { if string.is_empty() {
Dynamic::UNIT Dynamic::UNIT
@ -128,6 +209,21 @@ mod string_functions {
} }
} }
} }
/// Remove the a specified number of characters from the end of the string and return it as a
/// new string.
///
/// * If `len` ≤ 0, the string is not modified and an empty string is returned.
/// * If `len` ≥ length of string, the string is cleared and the entire string returned.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world!";
///
/// print(text.pop(4)); // prints "rld!"
///
/// print(text); // prints "hello, wo"
/// ```
#[rhai_fn(name = "pop")] #[rhai_fn(name = "pop")]
pub fn pop_string( pub fn pop_string(
ctx: NativeCallContext, ctx: NativeCallContext,
@ -150,6 +246,17 @@ mod string_functions {
chars.into_iter().rev().collect::<SmartString>().into() chars.into_iter().rev().collect::<SmartString>().into()
} }
/// Convert the string to all upper-case and return it as a new string.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world!"
///
/// print(text.to_upper()); // prints "HELLO, WORLD!"
///
/// print(text); // prints "hello, world!"
/// ```
pub fn to_upper(string: ImmutableString) -> ImmutableString { pub fn to_upper(string: ImmutableString) -> ImmutableString {
if string.is_empty() { if string.is_empty() {
string string
@ -157,11 +264,33 @@ mod string_functions {
string.to_uppercase().into() string.to_uppercase().into()
} }
} }
/// Convert the string to all upper-case.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world!"
///
/// text.make_upper();
///
/// print(text); // prints "HELLO, WORLD!";
/// ```
pub fn make_upper(string: &mut ImmutableString) { pub fn make_upper(string: &mut ImmutableString) {
if !string.is_empty() { if !string.is_empty() {
*string = string.to_uppercase().into(); *string = string.to_uppercase().into();
} }
} }
/// Convert the string to all lower-case and return it as a new string.
///
/// # Example
///
/// ```rhai
/// let text = "HELLO, WORLD!"
///
/// print(text.to_lower()); // prints "hello, world!"
///
/// print(text); // prints "HELLO, WORLD!"
/// ```
pub fn to_lower(string: ImmutableString) -> ImmutableString { pub fn to_lower(string: ImmutableString) -> ImmutableString {
if string.is_empty() { if string.is_empty() {
string string
@ -169,12 +298,34 @@ mod string_functions {
string.to_lowercase().into() string.to_lowercase().into()
} }
} }
/// Convert the string to all lower-case.
///
/// # Example
///
/// ```rhai
/// let text = "HELLO, WORLD!"
///
/// text.make_lower();
///
/// print(text); // prints "hello, world!";
/// ```
pub fn make_lower(string: &mut ImmutableString) { pub fn make_lower(string: &mut ImmutableString) {
if !string.is_empty() { if !string.is_empty() {
*string = string.to_lowercase().into(); *string = string.to_lowercase().into();
} }
} }
/// Convert the character to upper-case and return it as a new character.
///
/// # Example
///
/// ```rhai
/// let ch = 'a';
///
/// print(ch.to_upper()); // prints 'A'
///
/// print(ch); // prints 'a'
/// ```
#[rhai_fn(name = "to_upper")] #[rhai_fn(name = "to_upper")]
pub fn to_upper_char(character: char) -> char { pub fn to_upper_char(character: char) -> char {
let mut stream = character.to_uppercase(); let mut stream = character.to_uppercase();
@ -185,10 +336,32 @@ mod string_functions {
ch ch
} }
} }
/// Convert the character to upper-case.
///
/// # Example
///
/// ```rhai
/// let ch = 'a';
///
/// ch.make_upper();
///
/// print(ch); // prints 'A'
/// ```
#[rhai_fn(name = "make_upper")] #[rhai_fn(name = "make_upper")]
pub fn make_upper_char(character: &mut char) { pub fn make_upper_char(character: &mut char) {
*character = to_upper_char(*character) *character = to_upper_char(*character)
} }
/// Convert the character to lower-case and return it as a new character.
///
/// # Example
///
/// ```rhai
/// let ch = 'A';
///
/// print(ch.to_lower()); // prints 'a'
///
/// print(ch); // prints 'A'
/// ```
#[rhai_fn(name = "to_lower")] #[rhai_fn(name = "to_lower")]
pub fn to_lower_char(character: char) -> char { pub fn to_lower_char(character: char) -> char {
let mut stream = character.to_lowercase(); let mut stream = character.to_lowercase();
@ -199,11 +372,41 @@ mod string_functions {
ch ch
} }
} }
/// Convert the character to lower-case.
///
/// # Example
///
/// ```rhai
/// let ch = 'A';
///
/// ch.make_lower();
///
/// print(ch); // prints 'a'
/// ```
#[rhai_fn(name = "make_lower")] #[rhai_fn(name = "make_lower")]
pub fn make_lower_char(character: &mut char) { pub fn make_lower_char(character: &mut char) {
*character = to_lower_char(*character) *character = to_lower_char(*character)
} }
/// Find the specified `character` in the string, starting from the specified `start` position,
/// and return the first index where it is found.
/// If the `character` is not found, `-1` is returned.
///
/// * If `start` < 0, position counts from the end of the string (`-1` is the last character).
/// * If `start` < -length of string, position counts from the beginning of the string.
/// * If `start` ≥ length of string, `-1` is returned.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world!";
///
/// print(text.index_of('l', 5)); // prints 10 (first index after 5)
///
/// print(text.index_of('o', -7)); // prints 8
///
/// print(text.index_of('x', 0)); // prints -1
/// ```
#[rhai_fn(name = "index_of")] #[rhai_fn(name = "index_of")]
pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT { pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT {
if string.is_empty() { if string.is_empty() {
@ -243,6 +446,18 @@ mod string_functions {
.map(|index| string[0..start + index].chars().count() as INT) .map(|index| string[0..start + index].chars().count() as INT)
.unwrap_or(-1 as INT) .unwrap_or(-1 as INT)
} }
/// Find the specified `character` in the string and return the first index where it is found.
/// If the `character` is not found, `-1` is returned.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world!";
///
/// print(text.index_of('l')); // prints 2 (first index)
///
/// print(text.index_of('x')); // prints -1
/// ```
#[rhai_fn(name = "index_of")] #[rhai_fn(name = "index_of")]
pub fn index_of_char(string: &str, character: char) -> INT { pub fn index_of_char(string: &str, character: char) -> INT {
if string.is_empty() { if string.is_empty() {
@ -254,6 +469,25 @@ mod string_functions {
.unwrap_or(-1 as INT) .unwrap_or(-1 as INT)
} }
} }
/// Find the specified sub-string in the string, starting from the specified `start` position,
/// and return the first index where it is found.
/// If the sub-string is not found, `-1` is returned.
///
/// * If `start` < 0, position counts from the end of the string (`-1` is the last character).
/// * If `start` < -length of string, position counts from the beginning of the string.
/// * If `start` ≥ length of string, `-1` is returned.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world! hello, foobar!";
///
/// print(text.index_of("ll", 5)); // prints 16 (first index after 5)
///
/// print(text.index_of("ll", -15)); // prints 16
///
/// print(text.index_of("xx", 0)); // prints -1
/// ```
#[rhai_fn(name = "index_of")] #[rhai_fn(name = "index_of")]
pub fn index_of_string_starting_from(string: &str, find_string: &str, start: INT) -> INT { pub fn index_of_string_starting_from(string: &str, find_string: &str, start: INT) -> INT {
if string.is_empty() { if string.is_empty() {
@ -293,6 +527,18 @@ mod string_functions {
.map(|index| string[0..start + index].chars().count() as INT) .map(|index| string[0..start + index].chars().count() as INT)
.unwrap_or(-1 as INT) .unwrap_or(-1 as INT)
} }
/// Find the specified `character` in the string and return the first index where it is found.
/// If the `character` is not found, `-1` is returned.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world! hello, foobar!";
///
/// print(text.index_of("ll")); // prints 2 (first index)
///
/// print(text.index_of("xx:)); // prints -1
/// ```
#[rhai_fn(name = "index_of")] #[rhai_fn(name = "index_of")]
pub fn index_of(string: &str, find_string: &str) -> INT { pub fn index_of(string: &str, find_string: &str) -> INT {
if string.is_empty() { if string.is_empty() {
@ -305,6 +551,15 @@ mod string_functions {
} }
} }
/// Copy an exclusive range of characters from the string and return it as a new string.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world!";
///
/// print(text.sub_string(3..7)); // prints "lo, "
/// ```
#[rhai_fn(name = "sub_string")] #[rhai_fn(name = "sub_string")]
pub fn sub_string_range( pub fn sub_string_range(
ctx: NativeCallContext, ctx: NativeCallContext,
@ -315,6 +570,15 @@ mod string_functions {
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
sub_string(ctx, string, start, end - start) sub_string(ctx, string, start, end - start)
} }
/// Copy an inclusive range of characters from the string and return it as a new string.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world!";
///
/// print(text.sub_string(3..=7)); // prints "lo, w"
/// ```
#[rhai_fn(name = "sub_string")] #[rhai_fn(name = "sub_string")]
pub fn sub_string_inclusive_range( pub fn sub_string_inclusive_range(
ctx: NativeCallContext, ctx: NativeCallContext,
@ -325,6 +589,23 @@ mod string_functions {
let end = INT::max(*range.end(), start); let end = INT::max(*range.end(), start);
sub_string(ctx, string, start, end - start + 1) sub_string(ctx, string, start, end - start + 1)
} }
/// Copy a portion of the string and return it as a new string.
///
/// * If `start` < 0, position counts from the end of the string (`-1` is the last character).
/// * If `start` < -length of string, position counts from the beginning of the string.
/// * If `start` ≥ length of string, an empty string is returned.
/// * If `len` ≤ 0, an empty string is returned.
/// * If `start` position + `len` ≥ length of string, entire portion of the string after the `start` position is copied and returned.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world!";
///
/// print(text.sub_string(3, 4)); // prints "lo, "
///
/// print(text.sub_string(-8, 3)); // prints ", w"
/// ```
pub fn sub_string( pub fn sub_string(
ctx: NativeCallContext, ctx: NativeCallContext,
string: &str, string: &str,
@ -374,6 +655,22 @@ mod string_functions {
.collect::<String>() .collect::<String>()
.into() .into()
} }
/// Copy a portion of the string beginning at the `start` position till the end and return it as
/// a new string.
///
/// * If `start` < 0, position counts from the end of the string (`-1` is the last character).
/// * If `start` < -length of string, the entire string is copied and returned.
/// * If `start` ≥ length of string, an empty string is returned.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world!";
///
/// print(text.sub_string(5)); // prints ", world!"
///
/// print(text.sub_string(-5)); // prints "orld!"
/// ```
#[rhai_fn(name = "sub_string")] #[rhai_fn(name = "sub_string")]
pub fn sub_string_starting_from( pub fn sub_string_starting_from(
ctx: NativeCallContext, ctx: NativeCallContext,
@ -388,18 +685,62 @@ mod string_functions {
} }
} }
/// Remove all characters from the string except those within an exclusive range.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world!";
///
/// text.crop(2..8);
///
/// print(text); // prints "llo, w"
/// ```
#[rhai_fn(name = "crop")] #[rhai_fn(name = "crop")]
pub fn crop_range(string: &mut ImmutableString, range: ExclusiveRange) { pub fn crop_range(string: &mut ImmutableString, range: ExclusiveRange) {
let start = INT::max(range.start, 0); let start = INT::max(range.start, 0);
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
crop(string, start, end - start) crop(string, start, end - start)
} }
/// Remove all characters from the string except those within an inclusive range.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world!";
///
/// text.crop(2..=8);
///
/// print(text); // prints "llo, wo"
/// ```
#[rhai_fn(name = "crop")] #[rhai_fn(name = "crop")]
pub fn crop_inclusive_range(string: &mut ImmutableString, range: InclusiveRange) { pub fn crop_inclusive_range(string: &mut ImmutableString, range: InclusiveRange) {
let start = INT::max(*range.start(), 0); let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start); let end = INT::max(*range.end(), start);
crop(string, start, end - start + 1) crop(string, start, end - start + 1)
} }
/// Remove all characters from the string except those within a range.
///
/// * If `start` < 0, position counts from the end of the string (`-1` is the last character).
/// * If `start` < -length of string, position counts from the beginning of the string.
/// * If `start` ≥ length of string, the entire string is cleared.
/// * If `len` ≤ 0, the entire string is cleared.
/// * If `start` position + `len` ≥ length of string, only the portion of the string after the `start` position is retained.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world!";
///
/// text.crop(2, 8);
///
/// print(text); // prints "llo, wor"
///
/// text.crop(-5, 3);
///
/// print(text); // prints ", w"
/// ```
#[rhai_fn(name = "crop")] #[rhai_fn(name = "crop")]
pub fn crop(string: &mut ImmutableString, start: INT, len: INT) { pub fn crop(string: &mut ImmutableString, start: INT, len: INT) {
if string.is_empty() { if string.is_empty() {
@ -443,17 +784,58 @@ mod string_functions {
copy.clear(); copy.clear();
copy.extend(chars.iter().skip(offset).take(len)); copy.extend(chars.iter().skip(offset).take(len));
} }
/// Remove all characters from the string except until the `start` position.
///
/// * If `start` < 0, position counts from the end of the string (`-1` is the last character).
/// * If `start` < -length of string, the string is not modified.
/// * If `start` ≥ length of string, the entire string is cleared.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world!";
///
/// text.crop(5);
///
/// print(text); // prints ", world!"
///
/// text.crop(-3);
///
/// print(text); // prints "ld!"
/// ```
#[rhai_fn(name = "crop")] #[rhai_fn(name = "crop")]
pub fn crop_string_starting_from(string: &mut ImmutableString, start: INT) { pub fn crop_string_starting_from(string: &mut ImmutableString, start: INT) {
crop(string, start, string.len() as INT); crop(string, start, string.len() as INT);
} }
/// Replace all occurrences of the specified sub-string in the string with another string.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world! hello, foobar!";
///
/// text.replace("hello", "hey");
///
/// print(text); // prints "hey, world! hey, foobar!"
/// ```
#[rhai_fn(name = "replace")] #[rhai_fn(name = "replace")]
pub fn replace(string: &mut ImmutableString, find_string: &str, substitute_string: &str) { pub fn replace(string: &mut ImmutableString, find_string: &str, substitute_string: &str) {
if !string.is_empty() { if !string.is_empty() {
*string = string.replace(find_string, substitute_string).into(); *string = string.replace(find_string, substitute_string).into();
} }
} }
/// Replace all occurrences of the specified sub-string in the string with the specified character.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world! hello, foobar!";
///
/// text.replace("hello", '*');
///
/// print(text); // prints "*, world! *, foobar!"
/// ```
#[rhai_fn(name = "replace")] #[rhai_fn(name = "replace")]
pub fn replace_string_with_char( pub fn replace_string_with_char(
string: &mut ImmutableString, string: &mut ImmutableString,
@ -466,6 +848,17 @@ mod string_functions {
.into(); .into();
} }
} }
/// Replace all occurrences of the specified character in the string with another string.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world! hello, foobar!";
///
/// text.replace('l', "(^)");
///
/// print(text); // prints "he(^)(^)o, wor(^)d! he(^)(^)o, foobar!"
/// ```
#[rhai_fn(name = "replace")] #[rhai_fn(name = "replace")]
pub fn replace_char_with_string( pub fn replace_char_with_string(
string: &mut ImmutableString, string: &mut ImmutableString,
@ -478,6 +871,17 @@ mod string_functions {
.into(); .into();
} }
} }
/// Replace all occurrences of the specified character in the string with another character.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world! hello, foobar!";
///
/// text.replace("l", '*');
///
/// print(text); // prints "he**o, wor*d! he**o, foobar!"
/// ```
#[rhai_fn(name = "replace")] #[rhai_fn(name = "replace")]
pub fn replace_char( pub fn replace_char(
string: &mut ImmutableString, string: &mut ImmutableString,
@ -494,6 +898,23 @@ mod string_functions {
} }
} }
/// Pad the string to at least the specified number of characters with the specified `character`.
///
/// If `len` ≤ length of string, no padding is done.
///
/// # Example
///
/// ```rhai
/// let text = "hello";
///
/// text.pad(8, '!');
///
/// print(text); // prints "hello!!!"
///
/// text.pad(5, '*');
///
/// print(text); // prints "hello!!!"
/// ```
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn pad( pub fn pad(
ctx: NativeCallContext, ctx: NativeCallContext,
@ -538,6 +959,23 @@ mod string_functions {
Ok(()) Ok(())
} }
/// Pad the string to at least the specified number of characters with the specified string.
///
/// If `len` ≤ length of string, no padding is done.
///
/// # Example
///
/// ```rhai
/// let text = "hello";
///
/// text.pad(10, "(!)");
///
/// print(text); // prints "hello(!)(!)"
///
/// text.pad(8, '***');
///
/// print(text); // prints "hello(!)(!)"
/// ```
#[rhai_fn(name = "pad", return_raw)] #[rhai_fn(name = "pad", return_raw)]
pub fn pad_with_string( pub fn pad_with_string(
ctx: NativeCallContext, ctx: NativeCallContext,
@ -594,6 +1032,14 @@ mod string_functions {
pub mod arrays { pub mod arrays {
use crate::{Array, ImmutableString}; use crate::{Array, ImmutableString};
/// Return an array containing all the characters of the string.
///
/// # Example
///
/// ```rhai
/// let text = "hello";
///
/// print(text.split()); // prints "['h', 'e', 'l', 'l', 'o']"
#[rhai_fn(name = "split")] #[rhai_fn(name = "split")]
pub fn chars(string: &str) -> Array { pub fn chars(string: &str) -> Array {
if string.is_empty() { if string.is_empty() {
@ -602,10 +1048,32 @@ mod string_functions {
string.chars().map(Into::into).collect() string.chars().map(Into::into).collect()
} }
} }
/// Split the string into two at the specified `index` position and return it both strings
/// as an array.
///
/// The character at the `index` position (if any) is returned in the _second_ string.
///
/// * If `index` < 0, position counts from the end of the string (`-1` is the last character).
/// * If `index` < -length of string, it is equivalent to cutting at position 0.
/// * If `index` ≥ length of string, it is equivalent to cutting at the end of the string.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world!";
///
/// print(text.split(6)); // prints ["hello,", " world!"]
///
/// print(text.split(13)); // prints ["hello, world!", ""]
///
/// print(text.split(-6)); // prints ["hello, ", "world!"]
///
/// print(text.split(-99)); // prints ["", "hello, world!"]
/// ```
#[rhai_fn(name = "split")] #[rhai_fn(name = "split")]
pub fn split_at(ctx: NativeCallContext, string: ImmutableString, start: INT) -> Array { pub fn split_at(ctx: NativeCallContext, string: ImmutableString, index: INT) -> Array {
if start <= 0 { if index <= 0 {
if let Some(n) = start.checked_abs() { if let Some(n) = index.checked_abs() {
let num_chars = string.chars().count(); let num_chars = string.chars().count();
if n as usize > num_chars { if n as usize > num_chars {
vec![ctx.engine().const_empty_string().into(), string.into()] vec![ctx.engine().const_empty_string().into(), string.into()]
@ -618,41 +1086,127 @@ mod string_functions {
vec![ctx.engine().const_empty_string().into(), string.into()] vec![ctx.engine().const_empty_string().into(), string.into()]
} }
} else { } else {
let prefix: String = string.chars().take(start as usize).collect(); let prefix: String = string.chars().take(index as usize).collect();
let prefix_len = prefix.len(); let prefix_len = prefix.len();
vec![prefix.into(), string[prefix_len..].into()] vec![prefix.into(), string[prefix_len..].into()]
} }
} }
/// Split the string into segments based on a `delimiter` string, returning an array of the segments.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world! hello, foo!";
///
/// print(text.split("ll")); // prints ["he", "o, world! he", "o, foo!"]
/// ```
pub fn split(string: &str, delimiter: &str) -> Array { pub fn split(string: &str, delimiter: &str) -> Array {
string.split(delimiter).map(Into::into).collect() string.split(delimiter).map(Into::into).collect()
} }
/// Split the string into at most the specified number of `segments` based on a `delimiter` string,
/// returning an array of the segments.
///
/// If `segments` < 1, only one segment is returned.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world! hello, foo!";
///
/// print(text.split("ll", 2)); // prints ["he", "o, world! hello, foo!"]
/// ```
#[rhai_fn(name = "split")] #[rhai_fn(name = "split")]
pub fn splitn(string: &str, delimiter: &str, segments: INT) -> Array { pub fn splitn(string: &str, delimiter: &str, segments: INT) -> Array {
let pieces: usize = if segments < 1 { 1 } else { segments as usize }; let pieces: usize = if segments < 1 { 1 } else { segments as usize };
string.splitn(pieces, delimiter).map(Into::into).collect() string.splitn(pieces, delimiter).map(Into::into).collect()
} }
/// Split the string into segments based on a `delimiter` character, returning an array of the segments.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world! hello, foo!";
///
/// print(text.split('l')); // prints ["he", "", "o, wor", "d! he", "", "o, foo!"]
/// ```
#[rhai_fn(name = "split")] #[rhai_fn(name = "split")]
pub fn split_char(string: &str, delimiter: char) -> Array { pub fn split_char(string: &str, delimiter: char) -> Array {
string.split(delimiter).map(Into::into).collect() string.split(delimiter).map(Into::into).collect()
} }
/// Split the string into at most the specified number of `segments` based on a `delimiter` character,
/// returning an array of the segments.
///
/// If `segments` < 1, only one segment is returned.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world! hello, foo!";
///
/// print(text.split('l', 3)); // prints ["he", "", "o, world! hello, foo!"]
/// ```
#[rhai_fn(name = "split")] #[rhai_fn(name = "split")]
pub fn splitn_char(string: &str, delimiter: char, segments: INT) -> Array { pub fn splitn_char(string: &str, delimiter: char, segments: INT) -> Array {
let pieces: usize = if segments < 1 { 1 } else { segments as usize }; let pieces: usize = if segments < 1 { 1 } else { segments as usize };
string.splitn(pieces, delimiter).map(Into::into).collect() string.splitn(pieces, delimiter).map(Into::into).collect()
} }
/// Split the string into segments based on a `delimiter` string, returning an array of the
/// segments in _reverse_ order.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world! hello, foo!";
///
/// print(text.split_rev("ll")); // prints ["o, foo!", "o, world! he", "he"]
/// ```
#[rhai_fn(name = "split_rev")] #[rhai_fn(name = "split_rev")]
pub fn rsplit(string: &str, delimiter: &str) -> Array { pub fn rsplit(string: &str, delimiter: &str) -> Array {
string.rsplit(delimiter).map(Into::into).collect() string.rsplit(delimiter).map(Into::into).collect()
} }
/// Split the string into at most a specified number of `segments` based on a `delimiter` string,
/// returning an array of the segments in _reverse_ order.
///
/// If `segments` < 1, only one segment is returned.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world! hello, foo!";
///
/// print(text.split_rev("ll", 2)); // prints ["o, foo!", "hello, world! he"]
/// ```
#[rhai_fn(name = "split_rev")] #[rhai_fn(name = "split_rev")]
pub fn rsplitn(string: &str, delimiter: &str, segments: INT) -> Array { pub fn rsplitn(string: &str, delimiter: &str, segments: INT) -> Array {
let pieces: usize = if segments < 1 { 1 } else { segments as usize }; let pieces: usize = if segments < 1 { 1 } else { segments as usize };
string.rsplitn(pieces, delimiter).map(Into::into).collect() string.rsplitn(pieces, delimiter).map(Into::into).collect()
} }
/// Split the string into segments based on a `delimiter` character, returning an array of
/// the segments in _reverse_ order.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world! hello, foo!";
///
/// print(text.split_rev('l')); // prints ["o, foo!", "", "d! he", "o, wor", "", "he"]
/// ```
#[rhai_fn(name = "split_rev")] #[rhai_fn(name = "split_rev")]
pub fn rsplit_char(string: &str, delimiter: char) -> Array { pub fn rsplit_char(string: &str, delimiter: char) -> Array {
string.rsplit(delimiter).map(Into::into).collect() string.rsplit(delimiter).map(Into::into).collect()
} }
/// Split the string into at most the specified number of `segments` based on a `delimiter` character,
/// returning an array of the segments.
///
/// If `segments` < 1, only one segment is returned.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world! hello, foo!";
///
/// print(text.split('l', 3)); // prints ["o, foo!", "", "hello, world! he"
/// ```
#[rhai_fn(name = "split_rev")] #[rhai_fn(name = "split_rev")]
pub fn rsplitn_char(string: &str, delimiter: char, segments: INT) -> Array { pub fn rsplitn_char(string: &str, delimiter: char, segments: INT) -> Array {
let pieces: usize = if segments < 1 { 1 } else { segments as usize }; let pieces: usize = if segments < 1 { 1 } else { segments as usize };

View File

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

View File

@ -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 { impl Expr {
/// Convert a [`Variable`][Expr::Variable] into a [`Property`][Expr::Property]. /// Convert a [`Variable`][Expr::Variable] into a [`Property`][Expr::Property].
/// All other variants are untouched. /// All other variants are untouched.
@ -3253,7 +3269,7 @@ fn parse_anon_fn(
params.iter().for_each(|p| p.hash(hasher)); params.iter().for_each(|p| p.hash(hasher));
body.hash(hasher); body.hash(hasher);
let hash = hasher.finish(); 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 // Define the function
let script = ScriptFnDef { let script = ScriptFnDef {

View File

@ -2,7 +2,7 @@
#![cfg(feature = "metadata")] #![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 crate::{calc_fn_hash, Engine, AST};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
@ -48,34 +48,15 @@ impl From<crate::FnAccess> for FnAccess {
} }
} }
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct FnParam<'a> { struct FnParam<'a> {
#[serde(rename = "type", skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<&'a str>, pub name: Option<&'a str>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")] #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub typ: Option<&'a str>, pub typ: Option<&'a str>,
} }
impl PartialOrd for FnParam<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
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)] #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct FnMetadata<'a> { struct FnMetadata<'a> {
@ -89,15 +70,18 @@ struct FnMetadata<'a> {
pub num_params: usize, pub num_params: usize,
#[serde(default, skip_serializing_if = "Vec::is_empty")] #[serde(default, skip_serializing_if = "Vec::is_empty")]
pub params: Vec<FnParam<'a>>, pub params: Vec<FnParam<'a>>,
// No idea why the following is needed otherwise serde comes back with a lifetime error
#[serde(default, skip_serializing_if = "Option::is_none")] #[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, pub signature: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")] #[serde(default, skip_serializing_if = "Vec::is_empty")]
pub doc_comments: Vec<&'a str>, pub doc_comments: Vec<&'a str>,
} }
impl PartialOrd for FnMetadata<'_> { impl PartialOrd for FnMetadata<'_> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
} }
} }
@ -105,41 +89,39 @@ impl PartialOrd for FnMetadata<'_> {
impl Ord for FnMetadata<'_> { impl Ord for FnMetadata<'_> {
fn cmp(&self, other: &Self) -> Ordering { fn cmp(&self, other: &Self) -> Ordering {
match self.name.cmp(&other.name) { match self.name.cmp(&other.name) {
Ordering::Equal => match self.num_params.cmp(&other.num_params) { Ordering::Equal => self.num_params.cmp(&other.num_params),
Ordering::Equal => self.params.cmp(&other.params),
cmp => cmp,
},
cmp => cmp, cmp => cmp,
} }
} }
} }
impl<'a> From<&'a crate::module::FuncInfo> for FnMetadata<'a> { impl<'a> From<&'a FuncInfo> for FnMetadata<'a> {
fn from(info: &'a crate::module::FuncInfo) -> Self { fn from(info: &'a FuncInfo) -> Self {
let base_hash = calc_fn_hash(&info.name, info.params); let base_hash = calc_fn_hash(&info.metadata.name, info.metadata.params);
let (typ, full_hash) = if info.func.is_script() { let (typ, full_hash) = if info.func.is_script() {
(FnType::Script, base_hash) (FnType::Script, base_hash)
} else { } else {
( (
FnType::Native, 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 { Self {
base_hash, base_hash,
full_hash, full_hash,
namespace: info.namespace.into(), namespace: info.metadata.namespace.into(),
access: info.access.into(), access: info.metadata.access.into(),
name: info.name.to_string(), name: info.metadata.name.to_string(),
typ, typ,
num_params: info.params, num_params: info.metadata.params,
params: info params: info
.param_names_and_types .metadata
.params_info
.iter() .iter()
.map(|s| { .map(|s| {
let mut seg = s.splitn(2, ':'); let mut seg = s.splitn(2, ':');
let name = match seg.next().map(&str::trim).unwrap_or("_") { let name = match seg.next().unwrap().trim() {
"_" => None, "_" => None,
s => Some(s), s => Some(s),
}; };
@ -147,10 +129,8 @@ impl<'a> From<&'a crate::module::FuncInfo> for FnMetadata<'a> {
FnParam { name, typ } FnParam { name, typ }
}) })
.collect(), .collect(),
return_type_name: match info.return_type_name.as_str() { _dummy: None,
"" | "()" => None, return_type: FuncInfo::format_return_type(&info.metadata.return_type).into_owned(),
ty => Some(ty),
},
signature: info.gen_signature(), signature: info.gen_signature(),
doc_comments: if info.func.is_script() { doc_comments: if info.func.is_script() {
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
@ -164,7 +144,8 @@ impl<'a> From<&'a crate::module::FuncInfo> for FnMetadata<'a> {
.as_ref() .as_ref()
.map_or_else(|| Vec::new(), |v| v.iter().map(|s| &**s).collect()) .map_or_else(|| Vec::new(), |v| v.iter().map(|s| &**s).collect())
} else { } else {
info.comments info.metadata
.comments
.as_ref() .as_ref()
.map_or_else(|| Vec::new(), |v| v.iter().map(|s| &**s).collect()) .map_or_else(|| Vec::new(), |v| v.iter().map(|s| &**s).collect())
}, },

View File

@ -10,7 +10,7 @@ fn check_struct_sizes() {
const PACKED: bool = cfg!(all( const PACKED: bool = cfg!(all(
target_pointer_width = "32", target_pointer_width = "32",
feature = "only_i32", feature = "only_i32",
feature = "no_float" any(feature = "no_float", feature = "f32_float")
)); ));
assert_eq!(size_of::<Dynamic>(), if PACKED { 8 } else { 16 }); assert_eq!(size_of::<Dynamic>(), if PACKED { 8 } else { 16 });
@ -19,24 +19,28 @@ fn check_struct_sizes() {
size_of::<Position>(), size_of::<Position>(),
if cfg!(feature = "no_position") { 0 } else { 4 } if cfg!(feature = "no_position") { 0 } else { 4 }
); );
assert_eq!(size_of::<ast::Expr>(), 16); assert_eq!(size_of::<ast::Expr>(), if PACKED { 12 } else { 16 });
assert_eq!(size_of::<Option<ast::Expr>>(), 16); assert_eq!(size_of::<Option<ast::Expr>>(), if PACKED { 12 } else { 16 });
assert_eq!(size_of::<ast::Stmt>(), 32); assert_eq!(size_of::<ast::Stmt>(), if PACKED { 24 } else { 32 });
assert_eq!(size_of::<Option<ast::Stmt>>(), 32); assert_eq!(size_of::<Option<ast::Stmt>>(), if PACKED { 24 } else { 32 });
assert_eq!(size_of::<FnPtr>(), 80);
assert_eq!(size_of::<Scope>(), 464); #[cfg(target_pointer_width = "64")]
assert_eq!(size_of::<LexError>(), 56); {
assert_eq!( assert_eq!(size_of::<Scope>(), 400);
size_of::<ParseError>(), assert_eq!(size_of::<FnPtr>(), 80);
if cfg!(feature = "no_position") { 8 } else { 16 } assert_eq!(size_of::<LexError>(), 56);
); assert_eq!(
assert_eq!(size_of::<EvalAltResult>(), 72); size_of::<ParseError>(),
assert_eq!( if cfg!(feature = "no_position") { 8 } else { 16 }
size_of::<NativeCallContext>(), );
if cfg!(feature = "no_position") { assert_eq!(size_of::<EvalAltResult>(), 72);
64 assert_eq!(
} else { size_of::<NativeCallContext>(),
72 if cfg!(feature = "no_position") {
} 64
); } else {
72
}
);
}
} }

View File

@ -15,12 +15,11 @@ use std::{
}; };
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
#[cfg(not(target_arch = "wasm64"))]
use std::time::Instant; use std::time::Instant;
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] #[cfg(target_family = "wasm")]
use instant::Instant; use instant::Instant;
/// The message: data type was checked /// The message: data type was checked
@ -525,8 +524,7 @@ impl Hash for Dynamic {
value_any.downcast_ref::<i64>().expect(CHECKED).hash(state); value_any.downcast_ref::<i64>().expect(CHECKED).hash(state);
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
#[cfg(not(target_arch = "wasm64"))]
if type_id == TypeId::of::<u128>() { if type_id == TypeId::of::<u128>() {
TypeId::of::<u128>().hash(state); TypeId::of::<u128>().hash(state);
value_any.downcast_ref::<u128>().expect(CHECKED).hash(state); value_any.downcast_ref::<u128>().expect(CHECKED).hash(state);
@ -650,8 +648,7 @@ impl fmt::Display for Dynamic {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
#[cfg(not(target_arch = "wasm64"))]
if _type_id == TypeId::of::<u128>() { if _type_id == TypeId::of::<u128>() {
return fmt::Display::fmt(_value_any.downcast_ref::<u128>().expect(CHECKED), f); return fmt::Display::fmt(_value_any.downcast_ref::<u128>().expect(CHECKED), f);
} else if _type_id == TypeId::of::<i128>() { } else if _type_id == TypeId::of::<i128>() {
@ -756,8 +753,7 @@ impl fmt::Debug for Dynamic {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
#[cfg(not(target_arch = "wasm64"))]
if _type_id == TypeId::of::<u128>() { if _type_id == TypeId::of::<u128>() {
return fmt::Debug::fmt(_value_any.downcast_ref::<u128>().expect(CHECKED), f); return fmt::Debug::fmt(_value_any.downcast_ref::<u128>().expect(CHECKED), f);
} else if _type_id == TypeId::of::<i128>() { } else if _type_id == TypeId::of::<i128>() {

View File

@ -1,6 +1,6 @@
//! Module containing error definitions for the evaluation process. //! 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")] #[cfg(feature = "no_std")]
use core_error::Error; use core_error::Error;
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
@ -34,22 +34,28 @@ pub enum EvalAltResult {
ErrorVariableNotFound(String, Position), ErrorVariableNotFound(String, Position),
/// Call to an unknown function. Wrapped value is the function signature. /// Call to an unknown function. Wrapped value is the function signature.
ErrorFunctionNotFound(String, Position), 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. /// Usage of an unknown [module][crate::Module]. Wrapped value is the [module][crate::Module] name.
ErrorModuleNotFound(String, Position), 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<Self>, Position),
/// An error has occurred while loading a [module][crate::Module]. /// An error has occurred while loading a [module][crate::Module].
/// Wrapped value are the [module][crate::Module] name and the interior error. /// Wrapped value are the [module][crate::Module] name and the interior error.
ErrorInModule(String, RhaiError, Position), ErrorInModule(String, Box<Self>, Position),
/// Access to `this` that is not bound. /// Access to `this` that is not bound.
ErrorUnboundThis(Position), ErrorUnboundThis(Position),
/// Data is not of the required type. /// Data is not of the required type.
/// Wrapped values are the type requested and type of the actual result. /// Wrapped values are the type requested and type of the actual result.
ErrorMismatchDataType(String, String, Position), ErrorMismatchDataType(String, String, Position),
/// Returned type is not the same as the required output type. /// Returned type is not the same as the required output type.
/// Wrapped values are the type requested and type of the actual result. /// Wrapped values are the type requested and type of the actual result.
ErrorMismatchOutputType(String, String, Position), 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. /// Array access out-of-bounds.
/// Wrapped values are the current number of elements in the array and the index number. /// Wrapped values are the current number of elements in the array and the index number.
ErrorArrayBounds(usize, INT, Position), ErrorArrayBounds(usize, INT, Position),
@ -59,10 +65,10 @@ pub enum EvalAltResult {
/// Bit-field indexing out-of-bounds. /// Bit-field indexing out-of-bounds.
/// Wrapped values are the current number of bits in the bit-field and the index number. /// Wrapped values are the current number of bits in the bit-field and the index number.
ErrorBitFieldBounds(usize, INT, Position), 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 iterable.
/// The `for` statement encounters a type that is not an iterator.
ErrorFor(Position), ErrorFor(Position),
/// Data race detected when accessing a variable. Wrapped value is the variable name. /// Data race detected when accessing a variable. Wrapped value is the variable name.
ErrorDataRace(String, Position), ErrorDataRace(String, Position),
/// Assignment to a constant variable. Wrapped value is the variable name. /// 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)?, Self::ErrorParsing(p, _) => write!(f, "Syntax error: {}", p)?,
#[cfg(not(feature = "no_function"))] #[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)?; write!(f, "{} in call to closure", err)?;
if !src.is_empty() { if !src.is_empty() {
write!(f, " @ '{}'", src)?; write!(f, " @ '{}'", src)?;
@ -145,7 +151,7 @@ impl fmt::Display for EvalAltResult {
}?, }?,
Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered for {}", s)?, Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered for {}", s)?,
Self::ErrorUnboundThis(_) => f.write_str("'this' is not bound")?, 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::ErrorTooManyOperations(_) => f.write_str("Too many operations")?,
Self::ErrorTooManyModules(_) => f.write_str("Too many modules imported")?, Self::ErrorTooManyModules(_) => f.write_str("Too many modules imported")?,
Self::ErrorStackOverflow(_) => f.write_str("Stack overflow")?, Self::ErrorStackOverflow(_) => f.write_str("Stack overflow")?,
@ -233,7 +239,7 @@ impl<T: AsRef<str>> From<T> for EvalAltResult {
} }
} }
impl<T: AsRef<str>> From<T> for RhaiError { impl<T: AsRef<str>> From<T> for Box<EvalAltResult> {
#[inline(never)] #[inline(never)]
fn from(err: T) -> Self { fn from(err: T) -> Self {
EvalAltResult::ErrorRuntime(err.as_ref().to_string().into(), Position::NONE).into() EvalAltResult::ErrorRuntime(err.as_ref().to_string().into(), Position::NONE).into()
@ -277,8 +283,13 @@ impl EvalAltResult {
| Self::ErrorArithmetic(_, _) | Self::ErrorArithmetic(_, _)
| Self::ErrorRuntime(_, _) => true, | Self::ErrorRuntime(_, _) => true,
Self::ErrorCustomSyntax(_, _, _) // Custom syntax raises errors only when they are compiled by one
| Self::ErrorTooManyOperations(_) // [`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::ErrorTooManyModules(_)
| Self::ErrorStackOverflow(_) | Self::ErrorStackOverflow(_)
| Self::ErrorDataTooLarge(_, _) | Self::ErrorDataTooLarge(_, _)

View File

@ -43,8 +43,8 @@ impl FnPtr {
/// Create a new function pointer without checking its parameters. /// Create a new function pointer without checking its parameters.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub(crate) fn new_unchecked(name: Identifier, curry: StaticVec<Dynamic>) -> Self { pub(crate) fn new_unchecked(name: impl Into<Identifier>, curry: StaticVec<Dynamic>) -> Self {
Self(name, curry) Self(name.into(), curry)
} }
/// Get the name of the function. /// Get the name of the function.
#[inline(always)] #[inline(always)]

View File

@ -3,10 +3,12 @@
use super::dynamic::{AccessMode, Variant}; use super::dynamic::{AccessMode, Variant};
use crate::{Dynamic, Identifier, StaticVec}; use crate::{Dynamic, Identifier, StaticVec};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::iter::FromIterator;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; 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). /// Keep a number of entries inline (since [`Dynamic`] is usually small enough).
const SCOPE_ENTRIES_INLINED: usize = 8; 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 /// Type containing information about the current scope. Useful for keeping state between
/// [`Engine`][crate::Engine] evaluation runs. /// [`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 /// # Thread Safety
/// ///
/// Currently, [`Scope`] is neither [`Send`] nor [`Sync`]. Turn on the `sync` feature to make it /// 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, // [`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 // type, etc.) is manually split into two equal-length arrays. That's because variable names take
// up the most space, with [`Cow<str>`][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 // cases the name is NOT used to look up a variable. Variable lookup is usually via direct
// indexing, by-passing the name altogether. // indexing, by-passing the name altogether.
// //
@ -58,25 +65,30 @@ pub struct Scope<'a> {
/// Current value of the entry. /// Current value of the entry.
values: SmallVec<[Dynamic; SCOPE_ENTRIES_INLINED]>, values: SmallVec<[Dynamic; SCOPE_ENTRIES_INLINED]>,
/// (Name, aliases) of the entry. /// (Name, aliases) of the entry.
names: SmallVec<[(Cow<'a, str>, Option<Box<StaticVec<Identifier>>>); SCOPE_ENTRIES_INLINED]>, names: SmallVec<[(Identifier, Option<Box<StaticVec<Identifier>>>); 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> { impl IntoIterator for Scope<'_> {
type Item = (Cow<'a, str>, Dynamic); type Item = (String, Dynamic, Vec<Identifier>);
type IntoIter = Box<dyn Iterator<Item = Self::Item> + 'a>; type IntoIter = Box<dyn Iterator<Item = Self::Item>>;
#[inline] #[inline]
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
Box::new( Box::new(self.values.into_iter().zip(self.names.into_iter()).map(
self.values |(value, (name, alias))| {
.into_iter() (
.zip(self.names.into_iter()) name.into(),
.map(|(value, (name, _))| (name, value)), value,
) alias.map(|a| a.to_vec()).unwrap_or_default(),
)
},
))
} }
} }
impl<'a> Scope<'a> { impl Scope<'_> {
/// Create a new [`Scope`]. /// Create a new [`Scope`].
/// ///
/// # Example /// # Example
@ -95,6 +107,7 @@ impl<'a> Scope<'a> {
Self { Self {
values: SmallVec::new_const(), values: SmallVec::new_const(),
names: SmallVec::new_const(), names: SmallVec::new_const(),
phantom: PhantomData,
} }
} }
/// Empty the [`Scope`]. /// Empty the [`Scope`].
@ -171,11 +184,7 @@ impl<'a> Scope<'a> {
/// assert_eq!(my_scope.get_value::<i64>("x").expect("x should exist"), 42); /// assert_eq!(my_scope.get_value::<i64>("x").expect("x should exist"), 42);
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn push( pub fn push(&mut self, name: impl Into<Identifier>, value: impl Variant + Clone) -> &mut Self {
&mut self,
name: impl Into<Cow<'a, str>>,
value: impl Variant + Clone,
) -> &mut Self {
self.push_dynamic_value(name, AccessMode::ReadWrite, Dynamic::from(value)) self.push_dynamic_value(name, AccessMode::ReadWrite, Dynamic::from(value))
} }
/// Add (push) a new [`Dynamic`] entry to the [`Scope`]. /// Add (push) a new [`Dynamic`] entry to the [`Scope`].
@ -191,7 +200,7 @@ impl<'a> Scope<'a> {
/// assert_eq!(my_scope.get_value::<i64>("x").expect("x should exist"), 42); /// assert_eq!(my_scope.get_value::<i64>("x").expect("x should exist"), 42);
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn push_dynamic(&mut self, name: impl Into<Cow<'a, str>>, value: Dynamic) -> &mut Self { pub fn push_dynamic(&mut self, name: impl Into<Identifier>, value: Dynamic) -> &mut Self {
self.push_dynamic_value(name, value.access_mode(), value) self.push_dynamic_value(name, value.access_mode(), value)
} }
/// Add (push) a new constant to the [`Scope`]. /// Add (push) a new constant to the [`Scope`].
@ -212,7 +221,7 @@ impl<'a> Scope<'a> {
#[inline(always)] #[inline(always)]
pub fn push_constant( pub fn push_constant(
&mut self, &mut self,
name: impl Into<Cow<'a, str>>, name: impl Into<Identifier>,
value: impl Variant + Clone, value: impl Variant + Clone,
) -> &mut Self { ) -> &mut Self {
self.push_dynamic_value(name, AccessMode::ReadOnly, Dynamic::from(value)) self.push_dynamic_value(name, AccessMode::ReadOnly, Dynamic::from(value))
@ -235,7 +244,7 @@ impl<'a> Scope<'a> {
#[inline(always)] #[inline(always)]
pub fn push_constant_dynamic( pub fn push_constant_dynamic(
&mut self, &mut self,
name: impl Into<Cow<'a, str>>, name: impl Into<Identifier>,
value: Dynamic, value: Dynamic,
) -> &mut Self { ) -> &mut Self {
self.push_dynamic_value(name, AccessMode::ReadOnly, value) self.push_dynamic_value(name, AccessMode::ReadOnly, value)
@ -244,7 +253,7 @@ impl<'a> Scope<'a> {
#[inline] #[inline]
pub(crate) fn push_dynamic_value( pub(crate) fn push_dynamic_value(
&mut self, &mut self,
name: impl Into<Cow<'a, str>>, name: impl Into<Identifier>,
access: AccessMode, access: AccessMode,
mut value: Dynamic, mut value: Dynamic,
) -> &mut Self { ) -> &mut Self {
@ -301,7 +310,7 @@ impl<'a> Scope<'a> {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn contains(&self, name: &str) -> bool { 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. /// Find an entry in the [`Scope`], starting from the last.
#[inline] #[inline]
@ -314,7 +323,7 @@ impl<'a> Scope<'a> {
.rev() // Always search a Scope in reverse order .rev() // Always search a Scope in reverse order
.enumerate() .enumerate()
.find_map(|(i, (key, _))| { .find_map(|(i, (key, _))| {
if name == key.as_ref() { if name == key {
let index = len - 1 - i; let index = len - 1 - i;
Some((index, self.values[index].access_mode())) Some((index, self.values[index].access_mode()))
} else { } else {
@ -343,7 +352,7 @@ impl<'a> Scope<'a> {
.iter() .iter()
.rev() .rev()
.enumerate() .enumerate()
.find(|(_, (key, _))| name == key.as_ref()) .find(|(_, (key, _))| name == key)
.and_then(|(index, _)| self.values[len - 1 - index].flatten_clone().try_cast()) .and_then(|(index, _)| self.values[len - 1 - index].flatten_clone().try_cast())
} }
/// Check if the named entry in the [`Scope`] is constant. /// Check if the named entry in the [`Scope`] is constant.
@ -398,7 +407,7 @@ impl<'a> Scope<'a> {
#[inline] #[inline]
pub fn set_or_push( pub fn set_or_push(
&mut self, &mut self,
name: impl AsRef<str> + Into<Cow<'a, str>>, name: impl AsRef<str> + Into<Identifier>,
value: impl Variant + Clone, value: impl Variant + Clone,
) -> &mut Self { ) -> &mut Self {
match self.get_index(name.as_ref()) { match self.get_index(name.as_ref()) {
@ -437,7 +446,7 @@ impl<'a> Scope<'a> {
#[inline] #[inline]
pub fn set_value( pub fn set_value(
&mut self, &mut self,
name: impl AsRef<str> + Into<Cow<'a, str>>, name: impl AsRef<str> + Into<Identifier>,
value: impl Variant + Clone, value: impl Variant + Clone,
) -> &mut Self { ) -> &mut Self {
match self.get_index(name.as_ref()) { match self.get_index(name.as_ref()) {
@ -535,9 +544,7 @@ impl<'a> Scope<'a> {
/// Get an iterator to entries in the [`Scope`]. /// Get an iterator to entries in the [`Scope`].
#[inline] #[inline]
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn into_iter( pub(crate) fn into_iter(self) -> impl Iterator<Item = (Identifier, Dynamic, Vec<Identifier>)> {
self,
) -> impl Iterator<Item = (Cow<'a, str>, Dynamic, Vec<Identifier>)> {
self.names self.names
.into_iter() .into_iter()
.zip(self.values.into_iter()) .zip(self.values.into_iter())
@ -597,7 +604,7 @@ impl<'a> Scope<'a> {
} }
} }
impl<'a, K: Into<Cow<'a, str>>> Extend<(K, Dynamic)> for Scope<'a> { impl<K: Into<Identifier>> Extend<(K, Dynamic)> for Scope<'_> {
#[inline] #[inline]
fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) {
iter.into_iter().for_each(|(name, value)| { iter.into_iter().for_each(|(name, value)| {
@ -606,7 +613,7 @@ impl<'a, K: Into<Cow<'a, str>>> Extend<(K, Dynamic)> for Scope<'a> {
} }
} }
impl<'a, K: Into<Cow<'a, str>>> FromIterator<(K, Dynamic)> for Scope<'a> { impl<K: Into<Identifier>> FromIterator<(K, Dynamic)> for Scope<'_> {
#[inline] #[inline]
fn from_iter<T: IntoIterator<Item = (K, Dynamic)>>(iter: T) -> Self { fn from_iter<T: IntoIterator<Item = (K, Dynamic)>>(iter: T) -> Self {
let mut scope = Self::new(); let mut scope = Self::new();
@ -615,7 +622,7 @@ impl<'a, K: Into<Cow<'a, str>>> FromIterator<(K, Dynamic)> for Scope<'a> {
} }
} }
impl<'a, K: Into<Cow<'a, str>>> Extend<(K, bool, Dynamic)> for Scope<'a> { impl<K: Into<Identifier>> Extend<(K, bool, Dynamic)> for Scope<'_> {
#[inline] #[inline]
fn extend<T: IntoIterator<Item = (K, bool, Dynamic)>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item = (K, bool, Dynamic)>>(&mut self, iter: T) {
iter.into_iter().for_each(|(name, is_constant, value)| { iter.into_iter().for_each(|(name, is_constant, value)| {
@ -632,7 +639,7 @@ impl<'a, K: Into<Cow<'a, str>>> Extend<(K, bool, Dynamic)> for Scope<'a> {
} }
} }
impl<'a, K: Into<Cow<'a, str>>> FromIterator<(K, bool, Dynamic)> for Scope<'a> { impl<K: Into<Identifier>> FromIterator<(K, bool, Dynamic)> for Scope<'_> {
#[inline] #[inline]
fn from_iter<T: IntoIterator<Item = (K, bool, Dynamic)>>(iter: T) -> Self { fn from_iter<T: IntoIterator<Item = (K, bool, Dynamic)>>(iter: T) -> Self {
let mut scope = Self::new(); let mut scope = Self::new();

View File

@ -52,20 +52,3 @@ pub fn unsafe_cast_box<X: Any, T: Any>(item: Box<X>) -> Option<T> {
None 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) }
}

View File

@ -22,12 +22,48 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y[-3]")?, 1); assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y[-3]")?, 1);
assert!(engine.eval::<bool>("let y = [1, 2, 3]; 2 in y")?); assert!(engine.eval::<bool>("let y = [1, 2, 3]; 2 in y")?);
assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y += 4; y[3]")?, 4); assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y += 4; y[3]")?, 4);
assert_eq!(
engine.eval::<INT>("let y = [1, 2, 3]; pad(y, 5, 42); len(y)")?,
5
);
assert_eq!(
engine.eval::<INT>("let y = [1, 2, 3]; pad(y, 5, [42]); len(y)")?,
5
);
assert_eq!(
engine.eval::<INT>("let y = [1, 2, 3]; pad(y, 5, [42, 999, 123]); y[4][0]")?,
42
);
assert_eq!( assert_eq!(
engine engine
.eval::<Dynamic>("let y = [1, 2, 3]; y[1] += 4; y")? .eval::<Dynamic>("let y = [1, 2, 3]; y[1] += 4; y")?
.into_typed_array::<INT>()?, .into_typed_array::<INT>()?,
[1, 6, 3] [1, 6, 3]
); );
assert_eq!(
engine
.eval::<Dynamic>("let y = [1, 2, 3]; extract(y, 1, 10)")?
.into_typed_array::<INT>()?,
vec![2, 3]
);
assert_eq!(
engine
.eval::<Dynamic>("let y = [1, 2, 3]; extract(y, -3, 1)")?
.into_typed_array::<INT>()?,
vec![1]
);
assert_eq!(
engine
.eval::<Dynamic>("let y = [1, 2, 3]; extract(y, -99, 2)")?
.into_typed_array::<INT>()?,
vec![1, 2]
);
assert_eq!(
engine
.eval::<Dynamic>("let y = [1, 2, 3]; extract(y, 99, 1)")?
.into_typed_array::<INT>()?,
vec![] as Vec<INT>
);
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
{ {

View File

@ -27,10 +27,25 @@ fn test_bit_fields() -> Result<(), Box<EvalAltResult>> {
); );
assert_eq!(engine.eval::<INT>("let x = 10; get_bits(x, 1, 3)")?, 5); assert_eq!(engine.eval::<INT>("let x = 10; get_bits(x, 1, 3)")?, 5);
assert_eq!(engine.eval::<INT>("let x = 10; x[1..=3]")?, 5); assert_eq!(engine.eval::<INT>("let x = 10; x[1..=3]")?, 5);
assert!(engine.eval::<INT>("let x = 10; x[1..99]").is_err());
assert!(engine.eval::<INT>("let x = 10; x[-1..3]").is_err());
assert_eq!( assert_eq!(
engine.eval::<INT>("let x = 10; set_bits(x, 1, 3, 7); x")?, engine.eval::<INT>("let x = 10; set_bits(x, 1, 3, 7); x")?,
14 14
); );
#[cfg(target_pointer_width = "64")]
#[cfg(not(feature = "only_i32"))]
{
assert_eq!(engine.eval::<INT>("let x = 255; get_bits(x, -60, 2)")?, 3);
assert_eq!(
engine.eval::<INT>("let x = 0; set_bits(x, -64, 1, 15); x")?,
1
);
assert_eq!(
engine.eval::<INT>("let x = 0; set_bits(x, -60, 2, 15); x")?,
0b00110000
);
}
assert_eq!(engine.eval::<INT>("let x = 10; x[1..4] = 7; x")?, 14); assert_eq!(engine.eval::<INT>("let x = 10; x[1..4] = 7; x")?, 14);
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(

View File

@ -12,7 +12,7 @@ use rhai::Array;
use rhai::Map; use rhai::Map;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use rhai::FLOAT; use rhai::FLOAT;
#[cfg(not(feature = "no_float"))] #[cfg(feature = "no_float")]
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
use rust_decimal::Decimal; use rust_decimal::Decimal;
@ -751,7 +751,7 @@ fn test_serde_json() -> serde_json::Result<()> {
#[test] #[test]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
fn test_serde_optional() -> Result<(), Box<EvalAltResult>> { fn test_serde_optional() -> Result<(), Box<EvalAltResult>> {
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
struct TestStruct { struct TestStruct {
foo: Option<char>, foo: Option<char>,
} }
@ -774,6 +774,24 @@ fn test_serde_optional() -> Result<(), Box<EvalAltResult>> {
assert_eq!(from_dynamic::<TestStruct>(&r)?, TestStruct { foo: None }); assert_eq!(from_dynamic::<TestStruct>(&r)?, TestStruct { foo: None });
let ts = TestStruct { foo: Some('a') };
let r = to_dynamic(&ts)?;
let map = r.cast::<Map>();
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::<Map>();
assert_eq!(map.len(), 1);
assert_eq!(map.get("foo").unwrap().as_unit().unwrap(), ());
Ok(()) Ok(())
} }

View File

@ -1,6 +1,5 @@
#![cfg(not(feature = "no_std"))] #![cfg(not(feature = "no_std"))]
#![cfg(not(target_arch = "wasm32"))] #![cfg(not(target_family = "wasm"))]
#![cfg(not(target_arch = "wasm64"))]
use rhai::{Engine, EvalAltResult}; use rhai::{Engine, EvalAltResult};