Merge pull request #685 from schungx/master

New array APIs.
This commit is contained in:
Stephen Chung 2022-12-26 09:04:55 +08:00 committed by GitHub
commit dac4846742
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 740 additions and 426 deletions

View File

@ -55,6 +55,11 @@ Net features
* A compact script compresses better than one with liberal whitespaces and comments. * A compact script compresses better than one with liberal whitespaces and comments.
* Unlike some uglifiers or minifiers, `Engine::compact_script` does not optimize the script in any way, nor does it rename variables. * Unlike some uglifiers or minifiers, `Engine::compact_script` does not optimize the script in any way, nor does it rename variables.
### Enhanced array API
* Array methods that take a function pointer, usually a closure (e.g. `map`, `filter`, `index_of` etc.), can now provide a closure with one few parameter but binds the first parameter to `this`.
* This vastly improves performance when working with arrays of object maps by avoiding unnecessary cloning of large types.
Enhancements Enhancements
------------ ------------

View File

@ -35,45 +35,87 @@ unicode-xid = { version = "0.2", default-features = false, optional = true }
rust_decimal = { version = "1.16", default-features = false, features = ["maths"], optional = true } rust_decimal = { version = "1.16", default-features = false, features = ["maths"], optional = true }
getrandom = { version = "0.2", optional = true } getrandom = { version = "0.2", optional = true }
rustyline = { version = "10", optional = true } rustyline = { version = "10", optional = true }
document-features = { version = "0.2", optional = true }
[dev-dependencies] [dev-dependencies]
rmp-serde = "1.1" rmp-serde = "1.1"
serde_json = { version = "1.0", default-features = false, features = ["alloc"] } serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
[features] [features]
## Default features: `std`, uses runtime random numbers for hashing.
default = ["std", "ahash/runtime-rng"] # ahash/runtime-rng trumps ahash/compile-time-rng default = ["std", "ahash/runtime-rng"] # ahash/runtime-rng trumps ahash/compile-time-rng
## Standard features: uses compile-time random number for hashing.
std = ["ahash/std", "num-traits/std", "smartstring/std"] std = ["ahash/std", "num-traits/std", "smartstring/std"]
unchecked = [] # disable safety checks
sync = [] # restrict to only types that implement Send + Sync
no_position = [] # do not track position in the parser
no_optimize = [] # no script optimizer
no_float = [] # no floating-point
f32_float = [] # set FLOAT=f32
only_i32 = [] # set INT=i32 (useful for 32-bit systems)
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types
decimal = ["rust_decimal"] # add the Decimal number type
no_index = [] # no arrays and indexing
no_object = [] # no custom objects
no_time = [] # no timestamps
no_function = ["no_closure"] # no script-defined functions (meaning no closures)
no_closure = [] # no automatic sharing and capture of anonymous functions to external variables
no_module = [] # no modules
no_custom_syntax = [] # no custom syntax or custom operators
unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers.
metadata = ["serde", "serde_json", "rhai_codegen/metadata", "smartstring/serde"] # enable exporting functions metadata
internals = [] # expose internal data structures
debugging = ["internals"] # enable debugging
serde = ["dep:serde", "smartstring/serde", "smallvec/serde"] # implement serde for rhai types
# compiling for no-std #! ### Enable Special Functionalities
no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "hashbrown", "no_time"]
# compiling for WASM ## Require that all data types implement `Send + Sync` (for multi-threaded usage).
wasm-bindgen = ["getrandom/js", "instant/wasm-bindgen"] sync = []
stdweb = ["getrandom/js", "instant/stdweb"] ## Add support for the [`Decimal`](https://crates.io/crates/rust_decimal) data type (acts as the system floating-point type under `no_float`).
decimal = ["rust_decimal"]
## Enable serialization/deserialization of Rhai data types via [`serde`](https://crates.io/crates/serde).
serde = ["dep:serde", "smartstring/serde", "smallvec/serde"]
## Allow [Unicode Standard Annex #31](https://unicode.org/reports/tr31/) for identifiers.
unicode-xid-ident = ["unicode-xid"]
## Enable functions metadata (including doc-comments); implies [`serde`](#feature-serde).
metadata = ["serde", "serde_json", "rhai_codegen/metadata", "smartstring/serde"]
## Expose internal data structures (e.g. `AST` nodes).
internals = []
## Enable the debugging interface (implies [`internals`](#feature-internals)).
debugging = ["internals"]
## Features and dependencies required by `bin` tools: `decimal`, `metadata`, `serde`, `debugging` and [`rustyline`](https://crates.io/crates/rustyline).
bin-features = ["decimal", "metadata", "serde", "debugging", "rustyline"]
# compiling bin tools #! ### System Configuration Features
bin-features = ["decimal", "metadata", "serde", "debugging", "rustyline"]
## Use `f32` instead of `f64` as the system floating-point number type.
f32_float = []
## Use `i32` instead of `i64` for the system integer number type (useful for 32-bit architectures).
## All other integer types (e.g. `u8`) are disabled.
only_i32 = []
## Disable all integer types (e.g. `u8`) other than `i64`.
only_i64 = []
#! ### Disable Language Features
## Remove support for floating-point numbers.
no_float = []
## Remove support for arrays and indexing.
no_index = []
## Remove support for custom types, properties, method-style calls and object maps.
no_object = []
## Remove support for time-stamps.
no_time = []
## Remove support for script-defined functions (implies [`no_closure`](#feature-no_closure)).
no_function = ["no_closure"]
## Remove support for capturing external variables in anonymous functions (i.e. closures).
no_closure = []
## Remove support for loading external modules.
no_module = []
## Remove support for custom syntax.
no_custom_syntax = []
#! ### Performance-Related Features
## Disable all safety checks.
unchecked = []
## Do not track position when parsing.
no_position = []
## Disable the script optimizer.
no_optimize = []
#! ### Compiling for `no-std`
## Turn on `no-std` compilation (nightly only).
no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "hashbrown", "no_time"]
#! ### JavaScript Interface for WASM
## Use [`wasm-bindgen`](https://crates.io/crates/wasm-bindgen) as JavaScript interface.
wasm-bindgen = ["getrandom/js", "instant/wasm-bindgen"]
## Use [`stdweb`](https://crates.io/crates/stdweb) as JavaScript interface.
stdweb = ["getrandom/js", "instant/stdweb"]
[[bin]] [[bin]]
name = "rhai-repl" name = "rhai-repl"
@ -104,7 +146,7 @@ codegen-units = 1
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]
features = ["metadata", "serde", "internals", "decimal", "debugging"] features = ["document-features", "metadata", "serde", "internals", "decimal", "debugging"]
[patch.crates-io] [patch.crates-io]
# Notice that a custom modified version of `rustyline` is used which supports bracketed paste on Windows. # Notice that a custom modified version of `rustyline` is used which supports bracketed paste on Windows.

View File

@ -126,7 +126,10 @@ pub fn export_fn(
args: proc_macro::TokenStream, args: proc_macro::TokenStream,
input: proc_macro::TokenStream, input: proc_macro::TokenStream,
) -> proc_macro::TokenStream { ) -> proc_macro::TokenStream {
let mut output = proc_macro2::TokenStream::from(input.clone()); let mut output = quote! {
#[allow(clippy::needless_pass_by_value)]
};
output.extend(proc_macro2::TokenStream::from(input.clone()));
let parsed_params = match crate::attrs::outer_item_attributes(args.into(), "export_fn") { let parsed_params = match crate::attrs::outer_item_attributes(args.into(), "export_fn") {
Ok(args) => args, Ok(args) => args,

View File

@ -300,6 +300,7 @@ impl Module {
// Regenerate the module with the new content added. // Regenerate the module with the new content added.
Ok(quote! { Ok(quote! {
#(#mod_attrs)* #(#mod_attrs)*
#[allow(clippy::needless_pass_by_value)]
#mod_vis mod #mod_name { #mod_vis mod #mod_name {
#(#orig_content)* #(#orig_content)*
#(#inner_modules)* #(#inner_modules)*
@ -310,6 +311,7 @@ impl Module {
// Regenerate the original module as-is. // Regenerate the original module as-is.
Ok(quote! { Ok(quote! {
#(#mod_attrs)* #(#mod_attrs)*
#[allow(clippy::needless_pass_by_value)]
#mod_vis mod #mod_name { #mod_vis mod #mod_name {
#(#orig_content)* #(#orig_content)*
} }

View File

@ -328,7 +328,7 @@ pub fn check_rename_collisions(fns: &[ExportedFn]) -> Result<(), syn::Error> {
for (name, fn_name) in names { for (name, fn_name) in names {
let current_span = item_fn.params().span.unwrap(); let current_span = item_fn.params().span.unwrap();
let key = make_key(&name, item_fn); let key = make_key(name, item_fn);
if let Some(other_span) = renames.insert(key, current_span) { if let Some(other_span) = renames.insert(key, current_span) {
let mut err = syn::Error::new( let mut err = syn::Error::new(
current_span, current_span,

View File

@ -320,6 +320,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod empty { pub mod empty {
#[allow(unused_imports)] #[allow(unused_imports)]
use super::*; use super::*;
@ -354,6 +355,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_fn { pub mod one_fn {
pub fn get_mystic_number() -> INT { pub fn get_mystic_number() -> INT {
42 42
@ -432,6 +434,7 @@ mod generate_tests {
*/ */
/// Another line! /// Another line!
/// Final line! /// Final line!
#[allow(clippy::needless_pass_by_value)]
pub mod one_fn { pub mod one_fn {
/// This is a doc-comment. /// This is a doc-comment.
/// Another line. /// Another line.
@ -502,6 +505,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_global_fn { pub mod one_global_fn {
pub fn add_one_to(x: INT) -> INT { pub fn add_one_to(x: INT) -> INT {
x + 1 x + 1
@ -561,6 +565,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_fn { pub mod one_fn {
pub fn add_one_to(x: INT) -> INT { pub fn add_one_to(x: INT) -> INT {
x + 1 x + 1
@ -626,6 +631,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod two_fns { pub mod two_fns {
pub fn add_one_to(x: INT) -> INT { pub fn add_one_to(x: INT) -> INT {
x + 1 x + 1
@ -714,6 +720,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_fn { pub mod one_fn {
pub fn add_together(x: INT, y: INT) -> INT { pub fn add_together(x: INT, y: INT) -> INT {
x + y x + y
@ -775,6 +782,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_fn { pub mod one_fn {
pub fn add_together(x: INT, y: INT) -> INT { pub fn add_together(x: INT, y: INT) -> INT {
x + y x + y
@ -848,6 +856,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_constant { pub mod one_constant {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Foo(pub INT); pub struct Foo(pub INT);
@ -915,6 +924,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_constant { pub mod one_constant {
pub const MYSTIC_NUMBER: INT = 42; pub const MYSTIC_NUMBER: INT = 42;
#[allow(unused_imports)] #[allow(unused_imports)]
@ -950,6 +960,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_constant { pub mod one_constant {
pub use rhai::INT; pub use rhai::INT;
pub const MYSTIC_NUMBER: INT = 42; pub const MYSTIC_NUMBER: INT = 42;
@ -987,6 +998,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_fn { pub mod one_fn {
fn get_mystic_number() -> INT { fn get_mystic_number() -> INT {
42 42
@ -1025,6 +1037,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_fn { pub mod one_fn {
pub fn get_mystic_number() -> INT { pub fn get_mystic_number() -> INT {
42 42
@ -1066,10 +1079,12 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_fn { pub mod one_fn {
pub fn get_mystic_number() -> INT { pub fn get_mystic_number() -> INT {
42 42
} }
#[allow(clippy::needless_pass_by_value)]
pub mod inner_secrets { pub mod inner_secrets {
pub const SECRET_NUMBER: INT = 86; pub const SECRET_NUMBER: INT = 86;
} }
@ -1125,6 +1140,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_constant { pub mod one_constant {
const MYSTIC_NUMBER: INT = 42; const MYSTIC_NUMBER: INT = 42;
#[allow(unused_imports)] #[allow(unused_imports)]
@ -1160,6 +1176,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod str_fn { pub mod str_fn {
pub fn print_out_to(x: &str) { pub fn print_out_to(x: &str) {
x + 1 x + 1
@ -1219,6 +1236,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod str_fn { pub mod str_fn {
pub fn print_out_to(x: String) { pub fn print_out_to(x: String) {
x + 1 x + 1
@ -1279,6 +1297,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod ref_fn { pub mod ref_fn {
pub fn foo(x: &mut FLOAT, y: INT) -> FLOAT { pub fn foo(x: &mut FLOAT, y: INT) -> FLOAT {
*x + y as FLOAT *x + y as FLOAT
@ -1339,6 +1358,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod ref_fn { pub mod ref_fn {
pub fn increment(x: &mut FLOAT) { pub fn increment(x: &mut FLOAT) {
*x += 1.0 as FLOAT; *x += 1.0 as FLOAT;
@ -1400,7 +1420,9 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_fn { pub mod one_fn {
#[allow(clippy::needless_pass_by_value)]
pub mod it_is { pub mod it_is {
pub fn increment(x: &mut FLOAT) { pub fn increment(x: &mut FLOAT) {
*x += 1.0 as FLOAT; *x += 1.0 as FLOAT;
@ -1483,9 +1505,10 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_fn { pub mod one_fn {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[allow(clippy::needless_pass_by_value)]
pub mod it_is { pub mod it_is {
pub fn increment(x: &mut FLOAT) { pub fn increment(x: &mut FLOAT) {
*x += 1.0 as FLOAT; *x += 1.0 as FLOAT;
@ -1572,6 +1595,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_fn { pub mod one_fn {
pub fn int_foo(x: &mut u64) -> u64 { pub fn int_foo(x: &mut u64) -> u64 {
(*x) * (*x) (*x) * (*x)
@ -1632,6 +1656,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_fn { pub mod one_fn {
pub fn int_foo(x: &mut u64) -> u64 { pub fn int_foo(x: &mut u64) -> u64 {
(*x) * (*x) (*x) * (*x)
@ -1695,6 +1720,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_fn { pub mod one_fn {
pub fn int_foo(x: &mut u64, y: u64) { pub fn int_foo(x: &mut u64, y: u64) {
*x = y * y *x = y * y
@ -1756,6 +1782,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_fn { pub mod one_fn {
pub fn int_foo(x: &mut u64, y: u64) { pub fn int_foo(x: &mut u64, y: u64) {
*x = y * y *x = y * y
@ -1820,6 +1847,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_index_fn { pub mod one_index_fn {
pub fn get_by_index(x: &mut MyCollection, i: u64) -> FLOAT { pub fn get_by_index(x: &mut MyCollection, i: u64) -> FLOAT {
x.get(i) x.get(i)
@ -1883,6 +1911,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_index_fn { pub mod one_index_fn {
#[cfg(hello)] #[cfg(hello)]
#[some_other_attr] #[some_other_attr]
@ -1950,6 +1979,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_index_fn { pub mod one_index_fn {
pub fn get_by_index(x: &mut MyCollection, i: u64) -> FLOAT { pub fn get_by_index(x: &mut MyCollection, i: u64) -> FLOAT {
x.get(i) x.get(i)
@ -2014,6 +2044,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_index_fn { pub mod one_index_fn {
pub fn set_by_index(x: &mut MyCollection, i: u64, item: FLOAT) { pub fn set_by_index(x: &mut MyCollection, i: u64, item: FLOAT) {
x.entry(i).set(item) x.entry(i).set(item)
@ -2076,6 +2107,7 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_index_fn { pub mod one_index_fn {
pub fn set_by_index(x: &mut MyCollection, i: u64, item: FLOAT) { pub fn set_by_index(x: &mut MyCollection, i: u64, item: FLOAT) {
x.entry(i).set(item) x.entry(i).set(item)
@ -2140,7 +2172,9 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod one_constant { pub mod one_constant {
#[allow(clippy::needless_pass_by_value)]
pub mod it_is { pub mod it_is {
pub const MYSTIC_NUMBER: INT = 42; pub const MYSTIC_NUMBER: INT = 42;
#[allow(unused_imports)] #[allow(unused_imports)]
@ -2201,7 +2235,9 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod two_constants { pub mod two_constants {
#[allow(clippy::needless_pass_by_value)]
pub mod first_is { pub mod first_is {
pub const MYSTIC_NUMBER: INT = 42; pub const MYSTIC_NUMBER: INT = 42;
#[allow(unused_imports)] #[allow(unused_imports)]
@ -2221,6 +2257,7 @@ mod generate_tests {
if flatten {} else {} if flatten {} else {}
} }
} }
#[allow(clippy::needless_pass_by_value)]
pub mod second_is { pub mod second_is {
#[cfg(hello)] #[cfg(hello)]
pub const SPECIAL_CPU_NUMBER: INT = 68000; pub const SPECIAL_CPU_NUMBER: INT = 68000;
@ -2303,12 +2340,16 @@ mod generate_tests {
}; };
let expected_tokens = quote! { let expected_tokens = quote! {
#[allow(clippy::needless_pass_by_value)]
pub mod heap_root { pub mod heap_root {
pub const VALUE: INT = 100; pub const VALUE: INT = 100;
#[allow(clippy::needless_pass_by_value)]
pub mod left { pub mod left {
pub const VALUE: INT = 19; pub const VALUE: INT = 19;
#[allow(clippy::needless_pass_by_value)]
pub mod left { pub mod left {
pub const VALUE: INT = 17; pub const VALUE: INT = 17;
#[allow(clippy::needless_pass_by_value)]
pub mod left { pub mod left {
pub const VALUE: INT = 2; pub const VALUE: INT = 2;
#[allow(unused_imports)] #[allow(unused_imports)]
@ -2328,6 +2369,7 @@ mod generate_tests {
if flatten {} else {} if flatten {} else {}
} }
} }
#[allow(clippy::needless_pass_by_value)]
pub mod right { pub mod right {
pub const VALUE: INT = 7; pub const VALUE: INT = 7;
#[allow(unused_imports)] #[allow(unused_imports)]
@ -2371,6 +2413,7 @@ mod generate_tests {
} }
} }
} }
#[allow(clippy::needless_pass_by_value)]
pub mod right { pub mod right {
pub const VALUE: INT = 3; pub const VALUE: INT = 3;
#[allow(unused_imports)] #[allow(unused_imports)]
@ -2414,8 +2457,10 @@ mod generate_tests {
} }
} }
} }
#[allow(clippy::needless_pass_by_value)]
pub mod right { pub mod right {
pub const VALUE: INT = 36; pub const VALUE: INT = 36;
#[allow(clippy::needless_pass_by_value)]
pub mod left { pub mod left {
pub const VALUE: INT = 25; pub const VALUE: INT = 25;
#[allow(unused_imports)] #[allow(unused_imports)]
@ -2435,6 +2480,7 @@ mod generate_tests {
if flatten {} else {} if flatten {} else {}
} }
} }
#[allow(clippy::needless_pass_by_value)]
pub mod right { pub mod right {
pub const VALUE: INT = 1; pub const VALUE: INT = 1;
#[allow(unused_imports)] #[allow(unused_imports)]

View File

@ -273,7 +273,7 @@ impl Engine {
if self.is_debugger_registered() { if self.is_debugger_registered() {
global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate; global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate;
let node = &crate::ast::Stmt::Noop(Position::NONE); let node = &crate::ast::Stmt::Noop(Position::NONE);
self.run_debugger(global, caches, scope, this_ptr.as_deref_mut(), node)?; self.run_debugger(global, caches, scope, this_ptr, node)?;
} }
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]

View File

@ -674,7 +674,7 @@ pub mod deprecated_array_functions {
#[rhai_fn(name = "map", return_raw)] #[rhai_fn(name = "map", return_raw)]
pub fn map_by_fn_name( pub fn map_by_fn_name(
ctx: NativeCallContext, ctx: NativeCallContext,
array: Array, array: &mut Array,
mapper: &str, mapper: &str,
) -> RhaiResultOf<Array> { ) -> RhaiResultOf<Array> {
map(ctx, array, FnPtr::new(mapper)?) map(ctx, array, FnPtr::new(mapper)?)
@ -712,7 +712,7 @@ pub mod deprecated_array_functions {
#[rhai_fn(name = "filter", return_raw)] #[rhai_fn(name = "filter", return_raw)]
pub fn filter_by_fn_name( pub fn filter_by_fn_name(
ctx: NativeCallContext, ctx: NativeCallContext,
array: Array, array: &mut Array,
filter_func: &str, filter_func: &str,
) -> RhaiResultOf<Array> { ) -> RhaiResultOf<Array> {
filter(ctx, array, FnPtr::new(filter_func)?) filter(ctx, array, FnPtr::new(filter_func)?)

View File

@ -107,7 +107,7 @@ impl Engine {
#[inline] #[inline]
pub fn compile_file_with_scope(&self, scope: &Scope, path: PathBuf) -> RhaiResultOf<AST> { pub fn compile_file_with_scope(&self, scope: &Scope, path: PathBuf) -> RhaiResultOf<AST> {
Self::read_file(&path).and_then(|contents| { Self::read_file(&path).and_then(|contents| {
let mut ast = self.compile_with_scope(scope, &contents)?; let mut ast = self.compile_with_scope(scope, contents)?;
ast.set_source(path.to_string_lossy().as_ref()); ast.set_source(path.to_string_lossy().as_ref());
Ok(ast) Ok(ast)
}) })

View File

@ -162,7 +162,7 @@ pub fn format_map_as_json(map: &Map) -> String {
result.push(','); result.push(',');
} }
write!(result, "{:?}", key).unwrap(); write!(result, "{key:?}").unwrap();
result.push(':'); result.push(':');
if let Some(val) = value.read_lock::<Map>() { if let Some(val) = value.read_lock::<Map>() {
@ -170,7 +170,7 @@ pub fn format_map_as_json(map: &Map) -> String {
} else if value.is_unit() { } else if value.is_unit() {
result.push_str("null"); result.push_str("null");
} else { } else {
write!(result, "{:?}", value).unwrap(); write!(result, "{value:?}").unwrap();
} }
} }

View File

@ -207,13 +207,11 @@ impl Engine {
/// Get the default value of the custom state for each evaluation run. /// Get the default value of the custom state for each evaluation run.
#[inline(always)] #[inline(always)]
#[must_use]
pub const fn default_tag(&self) -> &Dynamic { pub const fn default_tag(&self) -> &Dynamic {
&self.def_tag &self.def_tag
} }
/// Get a mutable reference to the default value of the custom state for each evaluation run. /// Get a mutable reference to the default value of the custom state for each evaluation run.
#[inline(always)] #[inline(always)]
#[must_use]
pub fn default_tag_mut(&mut self) -> &mut Dynamic { pub fn default_tag_mut(&mut self) -> &mut Dynamic {
&mut self.def_tag &mut self.def_tag
} }

View File

@ -117,7 +117,7 @@ impl fmt::Debug for FnCallHashes {
return if script == self.native { return if script == self.native {
fmt::Debug::fmt(&self.native, f) fmt::Debug::fmt(&self.native, f)
} else { } else {
write!(f, "({}, {})", script, self.native) write!(f, "({script}, {})", self.native)
}; };
} }

View File

@ -22,8 +22,8 @@ pub use namespace_none::Namespace;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub use script_fn::{ScriptFnDef, ScriptFnMetadata}; pub use script_fn::{ScriptFnDef, ScriptFnMetadata};
pub use stmt::{ pub use stmt::{
CaseBlocksList, ConditionalExpr, OpAssignment, RangeCase, Stmt, StmtBlock, StmtBlockContainer, CaseBlocksList, ConditionalExpr, FlowControl, OpAssignment, RangeCase, Stmt, StmtBlock,
SwitchCasesCollection, TryCatchBlock, StmtBlockContainer, SwitchCasesCollection,
}; };
/// _(internals)_ Placeholder for a script-defined function. /// _(internals)_ Placeholder for a script-defined function.

View File

@ -71,16 +71,16 @@ impl OpAssignment {
/// ///
/// Panics if the token is not an op-assignment operator. /// Panics if the token is not an op-assignment operator.
#[must_use] #[must_use]
pub fn new_op_assignment_from_token(op: Token, pos: Position) -> Self { pub fn new_op_assignment_from_token(op_assign: Token, pos: Position) -> Self {
let op_raw = op let op = op_assign
.get_base_op_from_assignment() .get_base_op_from_assignment()
.expect("op-assignment operator"); .expect("op-assignment operator");
Self { Self {
hash_op_assign: calc_fn_hash(None, op.literal_syntax(), 2), hash_op_assign: calc_fn_hash(None, op_assign.literal_syntax(), 2),
hash_op: calc_fn_hash(None, op_raw.literal_syntax(), 2), hash_op: calc_fn_hash(None, op.literal_syntax(), 2),
op_assign: op.clone(), op_assign,
op: op_raw, op,
pos, pos,
} }
} }
@ -292,18 +292,6 @@ pub struct SwitchCasesCollection {
pub def_case: Option<usize>, pub def_case: Option<usize>,
} }
/// _(internals)_ A `try-catch` block.
/// Exported under the `internals` feature only.
#[derive(Debug, Clone, Hash)]
pub struct TryCatchBlock {
/// `try` block.
pub try_block: StmtBlock,
/// `catch` variable, if any.
pub catch_var: Ident,
/// `catch` block.
pub catch_block: StmtBlock,
}
/// Number of items to keep inline for [`StmtBlockContainer`]. /// Number of items to keep inline for [`StmtBlockContainer`].
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
const STMT_BLOCK_INLINE_SIZE: usize = 8; const STMT_BLOCK_INLINE_SIZE: usize = 8;
@ -532,6 +520,22 @@ impl Extend<Stmt> for StmtBlock {
} }
} }
/// _(internals)_ A flow control block containing:
/// * an expression,
/// * a statements body
/// * an alternate statements body
///
/// Exported under the `internals` feature only.
#[derive(Debug, Clone, Hash)]
pub struct FlowControl {
/// Flow control expression.
pub expr: Expr,
/// Main body.
pub body: StmtBlock,
/// Branch body.
pub branch: StmtBlock,
}
/// _(internals)_ A statement. /// _(internals)_ A statement.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
@ -540,7 +544,7 @@ pub enum Stmt {
/// No-op. /// No-op.
Noop(Position), Noop(Position),
/// `if` expr `{` stmt `}` `else` `{` stmt `}` /// `if` expr `{` stmt `}` `else` `{` stmt `}`
If(Box<(Expr, StmtBlock, StmtBlock)>, Position), If(Box<FlowControl>, Position),
/// `switch` expr `{` literal or range or _ `if` condition `=>` stmt `,` ... `}` /// `switch` expr `{` literal or range or _ `if` condition `=>` stmt `,` ... `}`
/// ///
/// ### Data Structure /// ### Data Structure
@ -552,16 +556,16 @@ pub enum Stmt {
/// `while` expr `{` stmt `}` | `loop` `{` stmt `}` /// `while` expr `{` stmt `}` | `loop` `{` stmt `}`
/// ///
/// If the guard expression is [`UNIT`][Expr::Unit], then it is a `loop` statement. /// If the guard expression is [`UNIT`][Expr::Unit], then it is a `loop` statement.
While(Box<(Expr, StmtBlock)>, Position), While(Box<FlowControl>, Position),
/// `do` `{` stmt `}` `while`|`until` expr /// `do` `{` stmt `}` `while`|`until` expr
/// ///
/// ### Flags /// ### Flags
/// ///
/// * [`NONE`][ASTFlags::NONE] = `while` /// * [`NONE`][ASTFlags::NONE] = `while`
/// * [`NEGATED`][ASTFlags::NEGATED] = `until` /// * [`NEGATED`][ASTFlags::NEGATED] = `until`
Do(Box<(Expr, StmtBlock)>, ASTFlags, Position), Do(Box<FlowControl>, ASTFlags, Position),
/// `for` `(` id `,` counter `)` `in` expr `{` stmt `}` /// `for` `(` id `,` counter `)` `in` expr `{` stmt `}`
For(Box<(Ident, Ident, Expr, StmtBlock)>, Position), For(Box<(Ident, Ident, FlowControl)>, Position),
/// \[`export`\] `let`|`const` id `=` expr /// \[`export`\] `let`|`const` id `=` expr
/// ///
/// ### Flags /// ### Flags
@ -579,7 +583,7 @@ pub enum Stmt {
/// `{` stmt`;` ... `}` /// `{` stmt`;` ... `}`
Block(Box<StmtBlock>), Block(Box<StmtBlock>),
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}` /// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
TryCatch(Box<TryCatchBlock>, Position), TryCatch(Box<FlowControl>, Position),
/// [expression][Expr] /// [expression][Expr]
Expr(Box<Expr>), Expr(Box<Expr>),
/// `continue`/`break` expr /// `continue`/`break` expr
@ -815,7 +819,9 @@ impl Stmt {
Self::Noop(..) => true, Self::Noop(..) => true,
Self::Expr(expr) => expr.is_pure(), Self::Expr(expr) => expr.is_pure(),
Self::If(x, ..) => { Self::If(x, ..) => {
x.0.is_pure() && x.1.iter().all(Self::is_pure) && x.2.iter().all(Self::is_pure) x.expr.is_pure()
&& x.body.iter().all(Self::is_pure)
&& x.branch.iter().all(Self::is_pure)
} }
Self::Switch(x, ..) => { Self::Switch(x, ..) => {
let (expr, sw) = &**x; let (expr, sw) = &**x;
@ -833,10 +839,10 @@ impl Stmt {
} }
// Loops that exit can be pure because it can never be infinite. // Loops that exit can be pure because it can never be infinite.
Self::While(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) => true, Self::While(x, ..) if matches!(x.expr, Expr::BoolConstant(false, ..)) => true,
Self::Do(x, options, ..) if matches!(x.0, Expr::BoolConstant(..)) => match x.0 { Self::Do(x, options, ..) if matches!(x.expr, Expr::BoolConstant(..)) => match x.expr {
Expr::BoolConstant(cond, ..) if cond == options.contains(ASTFlags::NEGATED) => { Expr::BoolConstant(cond, ..) if cond == options.contains(ASTFlags::NEGATED) => {
x.1.iter().all(Self::is_pure) x.body.iter().all(Self::is_pure)
} }
_ => false, _ => false,
}, },
@ -846,13 +852,15 @@ impl Stmt {
// For loops can be pure because if the iterable is pure, it is finite, // For loops can be pure because if the iterable is pure, it is finite,
// so infinite loops can never occur. // so infinite loops can never occur.
Self::For(x, ..) => x.2.is_pure() && x.3.iter().all(Self::is_pure), Self::For(x, ..) => x.2.expr.is_pure() && x.2.body.iter().all(Self::is_pure),
Self::Var(..) | Self::Assignment(..) | Self::FnCall(..) => false, Self::Var(..) | Self::Assignment(..) | Self::FnCall(..) => false,
Self::Block(block, ..) => block.iter().all(Self::is_pure), Self::Block(block, ..) => block.iter().all(Self::is_pure),
Self::BreakLoop(..) | Self::Return(..) => false, Self::BreakLoop(..) | Self::Return(..) => false,
Self::TryCatch(x, ..) => { Self::TryCatch(x, ..) => {
x.try_block.iter().all(Self::is_pure) && x.catch_block.iter().all(Self::is_pure) x.expr.is_pure()
&& x.body.iter().all(Self::is_pure)
&& x.branch.iter().all(Self::is_pure)
} }
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -947,15 +955,15 @@ impl Stmt {
} }
} }
Self::If(x, ..) => { Self::If(x, ..) => {
if !x.0.walk(path, on_node) { if !x.expr.walk(path, on_node) {
return false; return false;
} }
for s in &x.1 { for s in &x.body {
if !s.walk(path, on_node) { if !s.walk(path, on_node) {
return false; return false;
} }
} }
for s in &x.2 { for s in &x.branch {
if !s.walk(path, on_node) { if !s.walk(path, on_node) {
return false; return false;
} }
@ -996,20 +1004,20 @@ impl Stmt {
} }
} }
Self::While(x, ..) | Self::Do(x, ..) => { Self::While(x, ..) | Self::Do(x, ..) => {
if !x.0.walk(path, on_node) { if !x.expr.walk(path, on_node) {
return false; return false;
} }
for s in x.1.statements() { for s in x.body.statements() {
if !s.walk(path, on_node) { if !s.walk(path, on_node) {
return false; return false;
} }
} }
} }
Self::For(x, ..) => { Self::For(x, ..) => {
if !x.2.walk(path, on_node) { if !x.2.expr.walk(path, on_node) {
return false; return false;
} }
for s in &x.3 { for s in &x.2.body {
if !s.walk(path, on_node) { if !s.walk(path, on_node) {
return false; return false;
} }
@ -1038,12 +1046,12 @@ impl Stmt {
} }
} }
Self::TryCatch(x, ..) => { Self::TryCatch(x, ..) => {
for s in &x.try_block { for s in &x.body {
if !s.walk(path, on_node) { if !s.walk(path, on_node) {
return false; return false;
} }
} }
for s in &x.catch_block { for s in &x.branch {
if !s.walk(path, on_node) { if !s.walk(path, on_node) {
return false; return false;
} }

View File

@ -254,12 +254,13 @@ impl Engine {
})?; })?;
let bit_value = (*value & (1 << bit)) != 0; let bit_value = (*value & (1 << bit)) != 0;
#[allow(clippy::cast_possible_truncation)]
let bit = bit as u8;
Ok(Target::Bit { Ok(Target::Bit {
source: target, source: target,
value: bit_value.into(), value: bit_value.into(),
#[allow(clippy::cast_possible_truncation)] bit,
bit: bit as u8,
}) })
} }
@ -470,13 +471,8 @@ impl Engine {
if chain_type == ChainType::Dotting && !x.is_qualified() => if chain_type == ChainType::Dotting && !x.is_qualified() =>
{ {
for expr in &x.args { for expr in &x.args {
let arg_value = self.get_arg_value( let tp = this_ptr.as_deref_mut();
global, let arg_value = self.get_arg_value(global, caches, scope, tp, expr)?;
caches,
scope,
this_ptr.as_deref_mut(),
expr,
)?;
_arg_values.push(arg_value.0.flatten()); _arg_values.push(arg_value.0.flatten());
} }
} }
@ -528,7 +524,7 @@ impl Engine {
&self, &self,
global: &mut GlobalRuntimeState, global: &mut GlobalRuntimeState,
caches: &mut Caches, caches: &mut Caches,
mut this_ptr: Option<&mut Dynamic>, this_ptr: Option<&mut Dynamic>,
root: &Expr, root: &Expr,
parent: &Expr, parent: &Expr,
target: &mut Target, target: &mut Target,
@ -539,6 +535,9 @@ impl Engine {
let is_ref_mut = target.is_ref(); let is_ref_mut = target.is_ref();
let op_pos = parent.position(); let op_pos = parent.position();
#[cfg(feature = "debugging")]
let mut this_ptr = this_ptr;
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let scope = &mut Scope::new(); let scope = &mut Scope::new();
@ -837,17 +836,13 @@ impl Engine {
// {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr // {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr
Expr::Index(x, ..) | Expr::Dot(x, ..) if target.is_map() => { Expr::Index(x, ..) | Expr::Dot(x, ..) if target.is_map() => {
let _node = &x.lhs; let _node = &x.lhs;
let mut _this_ptr = this_ptr;
let _tp = _this_ptr.as_deref_mut();
let val_target = &mut match x.lhs { let val_target = &mut match x.lhs {
Expr::Property(ref p, pos) => { Expr::Property(ref p, pos) => {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
self.run_debugger( self.run_debugger(global, caches, scope, _tp, _node)?;
global,
caches,
scope,
this_ptr.as_deref_mut(),
_node,
)?;
let index = &mut p.2.clone().into(); let index = &mut p.2.clone().into();
self.get_indexed_mut( self.get_indexed_mut(
@ -857,13 +852,8 @@ impl Engine {
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
Expr::MethodCall(ref x, pos) if !x.is_qualified() => { Expr::MethodCall(ref x, pos) if !x.is_qualified() => {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let reset = self.run_debugger_with_reset( let reset = self
global, .run_debugger_with_reset(global, caches, scope, _tp, _node)?;
caches,
scope,
this_ptr.as_deref_mut(),
_node,
)?;
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset)); auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset));
@ -892,25 +882,21 @@ impl Engine {
}; };
self.eval_dot_index_chain_raw( self.eval_dot_index_chain_raw(
global, caches, this_ptr, root, rhs, val_target, &x.rhs, idx_values, global, caches, _this_ptr, root, rhs, val_target, &x.rhs, idx_values,
new_val, new_val,
) )
} }
// xxx.sub_lhs[expr] | xxx.sub_lhs.expr // xxx.sub_lhs[expr] | xxx.sub_lhs.expr
Expr::Index(x, ..) | Expr::Dot(x, ..) => { Expr::Index(x, ..) | Expr::Dot(x, ..) => {
let _node = &x.lhs; let _node = &x.lhs;
let mut _this_ptr = this_ptr;
let _tp = _this_ptr.as_deref_mut();
match x.lhs { match x.lhs {
// xxx.prop[expr] | xxx.prop.expr // xxx.prop[expr] | xxx.prop.expr
Expr::Property(ref p, pos) => { Expr::Property(ref p, pos) => {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
self.run_debugger( self.run_debugger(global, caches, scope, _tp, _node)?;
global,
caches,
scope,
this_ptr.as_deref_mut(),
_node,
)?;
let ((getter, hash_get), (setter, hash_set), name) = &**p; let ((getter, hash_get), (setter, hash_set), name) = &**p;
let args = &mut [target.as_mut()]; let args = &mut [target.as_mut()];
@ -942,7 +928,7 @@ impl Engine {
let val = &mut (&mut val).into(); let val = &mut (&mut val).into();
let (result, may_be_changed) = self.eval_dot_index_chain_raw( let (result, may_be_changed) = self.eval_dot_index_chain_raw(
global, caches, this_ptr, root, rhs, val, &x.rhs, idx_values, global, caches, _this_ptr, root, rhs, val, &x.rhs, idx_values,
new_val, new_val,
)?; )?;
@ -986,11 +972,7 @@ impl Engine {
let val = { let val = {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let reset = self.run_debugger_with_reset( let reset = self.run_debugger_with_reset(
global, global, caches, scope, _tp, _node,
caches,
scope,
this_ptr.as_deref_mut(),
_node,
)?; )?;
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset)); auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset));
@ -1014,7 +996,7 @@ impl Engine {
let val = &mut val.into(); let val = &mut val.into();
self.eval_dot_index_chain_raw( self.eval_dot_index_chain_raw(
global, caches, this_ptr, root, rhs, val, &x.rhs, idx_values, global, caches, _this_ptr, root, rhs, val, &x.rhs, idx_values,
new_val, new_val,
) )
} }

View File

@ -385,13 +385,11 @@ impl Debugger {
} }
/// Get the custom state. /// Get the custom state.
#[inline(always)] #[inline(always)]
#[must_use]
pub const fn state(&self) -> &Dynamic { pub const fn state(&self) -> &Dynamic {
&self.state &self.state
} }
/// Get a mutable reference to the custom state. /// Get a mutable reference to the custom state.
#[inline(always)] #[inline(always)]
#[must_use]
pub fn state_mut(&mut self) -> &mut Dynamic { pub fn state_mut(&mut self) -> &mut Dynamic {
&mut self.state &mut self.state
} }

View File

@ -72,13 +72,11 @@ impl<'a, 's, 'ps, 'g, 'c, 't> EvalContext<'a, 's, 'ps, 'g, 'c, 't> {
} }
/// Custom state kept in a [`Dynamic`]. /// Custom state kept in a [`Dynamic`].
#[inline(always)] #[inline(always)]
#[must_use]
pub const fn tag(&self) -> &Dynamic { pub const fn tag(&self) -> &Dynamic {
&self.global.tag &self.global.tag
} }
/// Mutable reference to the custom state kept in a [`Dynamic`]. /// Mutable reference to the custom state kept in a [`Dynamic`].
#[inline(always)] #[inline(always)]
#[must_use]
pub fn tag_mut(&mut self) -> &mut Dynamic { pub fn tag_mut(&mut self) -> &mut Dynamic {
&mut self.global.tag &mut self.global.tag
} }

View File

@ -317,6 +317,7 @@ impl GlobalRuntimeState {
/// ///
/// Panics if the debugging interface is not set. /// Panics if the debugging interface is not set.
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
#[must_use]
pub fn debugger(&self) -> &super::Debugger { pub fn debugger(&self) -> &super::Debugger {
self.debugger.as_ref().unwrap() self.debugger.as_ref().unwrap()
} }
@ -326,6 +327,7 @@ impl GlobalRuntimeState {
/// ///
/// Panics if the debugging interface is not set. /// Panics if the debugging interface is not set.
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
#[must_use]
pub fn debugger_mut(&mut self) -> &mut super::Debugger { pub fn debugger_mut(&mut self) -> &mut super::Debugger {
self.debugger.as_deref_mut().unwrap() self.debugger.as_deref_mut().unwrap()
} }

View File

@ -3,7 +3,7 @@
use super::{Caches, EvalContext, GlobalRuntimeState, Target}; use super::{Caches, EvalContext, GlobalRuntimeState, Target};
use crate::api::events::VarDefInfo; use crate::api::events::VarDefInfo;
use crate::ast::{ use crate::ast::{
ASTFlags, BinaryExpr, Expr, OpAssignment, Stmt, SwitchCasesCollection, TryCatchBlock, ASTFlags, BinaryExpr, Expr, FlowControl, OpAssignment, Stmt, SwitchCasesCollection,
}; };
use crate::func::{get_builtin_op_assignment_fn, get_hasher}; use crate::func::{get_builtin_op_assignment_fn, get_hasher};
use crate::types::dynamic::{AccessMode, Union}; use crate::types::dynamic::{AccessMode, Union};
@ -310,7 +310,11 @@ impl Engine {
// If statement // If statement
Stmt::If(x, ..) => { Stmt::If(x, ..) => {
let (expr, if_block, else_block) = &**x; let FlowControl {
expr,
body: if_block,
branch: else_block,
} = &**x;
let guard_val = self let guard_val = self
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)? .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)?
@ -402,8 +406,10 @@ impl Engine {
} }
// Loop // Loop
Stmt::While(x, ..) if matches!(x.0, Expr::Unit(..) | Expr::BoolConstant(true, ..)) => { Stmt::While(x, ..)
let (.., body) = &**x; if matches!(x.expr, Expr::Unit(..) | Expr::BoolConstant(true, ..)) =>
{
let FlowControl { body, .. } = &**x;
if body.is_empty() { if body.is_empty() {
loop { loop {
@ -427,7 +433,7 @@ impl Engine {
// While loop // While loop
Stmt::While(x, ..) => { Stmt::While(x, ..) => {
let (expr, body) = &**x; let FlowControl { expr, body, .. } = &**x;
loop { loop {
let condition = self let condition = self
@ -458,7 +464,7 @@ impl Engine {
// Do loop // Do loop
Stmt::Do(x, options, ..) => { Stmt::Do(x, options, ..) => {
let (expr, body) = &**x; let FlowControl { expr, body, .. } = &**x;
let is_while = !options.contains(ASTFlags::NEGATED); let is_while = !options.contains(ASTFlags::NEGATED);
loop { loop {
@ -488,7 +494,7 @@ impl Engine {
// For loop // For loop
Stmt::For(x, ..) => { Stmt::For(x, ..) => {
let (var_name, counter, expr, statements) = &**x; let (var_name, counter, FlowControl { expr, body, .. }) = &**x;
let iter_obj = self let iter_obj = self
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)? .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)?
@ -566,15 +572,15 @@ impl Engine {
*scope.get_mut_by_index(index).write_lock().unwrap() = value; *scope.get_mut_by_index(index).write_lock().unwrap() = value;
// Run block // Run block
self.track_operation(global, statements.position())?; self.track_operation(global, body.position())?;
if statements.is_empty() { if body.is_empty() {
continue; continue;
} }
let this_ptr = this_ptr.as_deref_mut(); let this_ptr = this_ptr.as_deref_mut();
match self.eval_stmt_block(global, caches, scope, this_ptr, statements, true) { match self.eval_stmt_block(global, caches, scope, this_ptr, body, true) {
Ok(_) => (), Ok(_) => (),
Err(err) => match *err { Err(err) => match *err {
ERR::LoopBreak(false, ..) => (), ERR::LoopBreak(false, ..) => (),
@ -605,10 +611,10 @@ impl Engine {
// Try/Catch statement // Try/Catch statement
Stmt::TryCatch(x, ..) => { Stmt::TryCatch(x, ..) => {
let TryCatchBlock { let FlowControl {
try_block, body: try_block,
catch_var, expr: catch_var,
catch_block, branch: catch_block,
} = &**x; } = &**x;
match self.eval_stmt_block( match self.eval_stmt_block(
@ -659,10 +665,10 @@ impl Engine {
}; };
// Restore scope at end of block // Restore scope at end of block
auto_restore! { scope if !catch_var.is_empty() => rewind; let orig_scope_len = scope.len(); } auto_restore! { scope if !catch_var.is_unit() => rewind; let orig_scope_len = scope.len(); }
if !catch_var.is_empty() { if let Expr::Variable(x, ..) = catch_var {
scope.push(catch_var.name.clone(), err_value); scope.push(x.3.clone(), err_value);
} }
let this_ptr = this_ptr.as_deref_mut(); let this_ptr = this_ptr.as_deref_mut();

View File

@ -235,7 +235,6 @@ impl<'a> Target<'a> {
/// Get the source [`Dynamic`] of the [`Target`]. /// Get the source [`Dynamic`] of the [`Target`].
#[allow(dead_code)] #[allow(dead_code)]
#[inline] #[inline]
#[must_use]
pub fn source(&self) -> &Dynamic { pub fn source(&self) -> &Dynamic {
match self { match self {
Self::RefMut(r) => r, Self::RefMut(r) => r,
@ -399,7 +398,6 @@ impl Deref for Target<'_> {
impl AsRef<Dynamic> for Target<'_> { impl AsRef<Dynamic> for Target<'_> {
#[inline(always)] #[inline(always)]
#[must_use]
fn as_ref(&self) -> &Dynamic { fn as_ref(&self) -> &Dynamic {
self self
} }
@ -407,7 +405,6 @@ impl AsRef<Dynamic> for Target<'_> {
impl Borrow<Dynamic> for Target<'_> { impl Borrow<Dynamic> for Target<'_> {
#[inline(always)] #[inline(always)]
#[must_use]
fn borrow(&self) -> &Dynamic { fn borrow(&self) -> &Dynamic {
self self
} }
@ -432,7 +429,6 @@ impl DerefMut for Target<'_> {
impl AsMut<Dynamic> for Target<'_> { impl AsMut<Dynamic> for Target<'_> {
#[inline(always)] #[inline(always)]
#[must_use]
fn as_mut(&mut self) -> &mut Dynamic { fn as_mut(&mut self) -> &mut Dynamic {
self self
} }

View File

@ -71,11 +71,13 @@ fn is_numeric(type_id: TypeId) -> bool {
/// A function that returns `true`. /// A function that returns `true`.
#[inline(always)] #[inline(always)]
#[allow(clippy::unnecessary_wraps)]
fn const_true_fn(_: Option<NativeCallContext>, _: &mut [&mut Dynamic]) -> RhaiResult { fn const_true_fn(_: Option<NativeCallContext>, _: &mut [&mut Dynamic]) -> RhaiResult {
Ok(Dynamic::TRUE) Ok(Dynamic::TRUE)
} }
/// A function that returns `false`. /// A function that returns `false`.
#[inline(always)] #[inline(always)]
#[allow(clippy::unnecessary_wraps)]
fn const_false_fn(_: Option<NativeCallContext>, _: &mut [&mut Dynamic]) -> RhaiResult { fn const_false_fn(_: Option<NativeCallContext>, _: &mut [&mut Dynamic]) -> RhaiResult {
Ok(Dynamic::FALSE) Ok(Dynamic::FALSE)
} }

View File

@ -182,7 +182,7 @@ impl Engine {
match cache.map.entry(hash) { match cache.map.entry(hash) {
Entry::Occupied(entry) => entry.into_mut().as_ref(), Entry::Occupied(entry) => entry.into_mut().as_ref(),
Entry::Vacant(entry) => { Entry::Vacant(entry) => {
let num_args = args.as_deref().map_or(0, |a| a.len()); let num_args = args.as_deref().map_or(0, FnCallArgs::len);
let mut max_bitmask = 0; // One above maximum bitmask based on number of parameters. let mut max_bitmask = 0; // One above maximum bitmask based on number of parameters.
// Set later when a specific matching function is not found. // Set later when a specific matching function is not found.
let mut bitmask = 1usize; // Bitmask of which parameter to replace with `Dynamic` let mut bitmask = 1usize; // Bitmask of which parameter to replace with `Dynamic`
@ -1167,12 +1167,12 @@ impl Engine {
.as_int() .as_int()
.map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, arg_pos))?; .map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, arg_pos))?;
return Ok(if !(0..=crate::MAX_USIZE_INT).contains(&num_params) { return Ok(if (0..=crate::MAX_USIZE_INT).contains(&num_params) {
false #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
} else {
#[allow(clippy::cast_sign_loss)]
let hash_script = calc_fn_hash(None, &fn_name, num_params as usize); let hash_script = calc_fn_hash(None, &fn_name, num_params as usize);
self.has_script_fn(global, caches, hash_script) self.has_script_fn(global, caches, hash_script)
} else {
false
} }
.into()); .into());
} }
@ -1588,7 +1588,7 @@ impl Engine {
.0 .0
.flatten(); .flatten();
return value.as_bool().and_then(|r| Ok((!r).into())).or_else(|_| { return value.as_bool().map(|r| (!r).into()).or_else(|_| {
let operand = &mut [&mut value]; let operand = &mut [&mut value];
self.exec_fn_call( self.exec_fn_call(
global, caches, None, name, op_token, *hashes, operand, false, false, pos, global, caches, None, name, op_token, *hashes, operand, false, false, pos,

View File

@ -201,7 +201,7 @@ impl<'a> NativeCallContext<'a> {
pub fn store_data(&self) -> NativeCallContextStore { pub fn store_data(&self) -> NativeCallContextStore {
NativeCallContextStore { NativeCallContextStore {
fn_name: self.fn_name.to_string(), fn_name: self.fn_name.to_string(),
source: self.source.map(|s| s.to_string()), source: self.source.map(ToString::to_string),
global: self.global.clone(), global: self.global.clone(),
pos: self.pos, pos: self.pos,
} }

View File

@ -39,7 +39,6 @@ pub struct Mut<T>(T);
/// Dereference into [`DynamicWriteLock`] /// Dereference into [`DynamicWriteLock`]
#[inline(always)] #[inline(always)]
#[must_use]
pub fn by_ref<T: Variant + Clone>(data: &mut Dynamic) -> DynamicWriteLock<T> { pub fn by_ref<T: Variant + Clone>(data: &mut Dynamic) -> DynamicWriteLock<T> {
// Directly cast the &mut Dynamic into DynamicWriteLock to access the underlying data. // Directly cast the &mut Dynamic into DynamicWriteLock to access the underlying data.
data.write_lock::<T>().expect("checked") data.write_lock::<T>().expect("checked")

View File

@ -134,7 +134,6 @@ impl Engine {
fn_def.name.to_string(), fn_def.name.to_string(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
_environ _environ
.as_deref()
.and_then(|environ| environ.lib.id()) .and_then(|environ| environ.lib.id())
.unwrap_or_else(|| global.source().unwrap_or("")) .unwrap_or_else(|| global.source().unwrap_or(""))
.to_string(), .to_string(),

View File

@ -52,7 +52,11 @@
//! } //! }
//! ``` //! ```
//! //!
//! # Documentation //! # Features
//!
#![cfg_attr(feature = "document-features", doc = document_features::document_features!(feature_label = "<span id=\"feature-{feature}\">**`{feature}`**</span>"))]
//!
//! # On-Line Documentation
//! //!
//! See [The Rhai Book](https://rhai.rs/book) for details on the Rhai scripting engine and language. //! See [The Rhai Book](https://rhai.rs/book) for details on the Rhai scripting engine and language.
@ -61,7 +65,7 @@
// #![warn(clippy::all)] // #![warn(clippy::all)]
// #![warn(clippy::pedantic)] // #![warn(clippy::pedantic)]
// #![warn(clippy::nursery)] // #![warn(clippy::nursery)]
// #![warn(clippy::cargo)] #![warn(clippy::cargo)]
// #![warn(clippy::undocumented_unsafe_blocks)] // #![warn(clippy::undocumented_unsafe_blocks)]
#![allow(clippy::unit_arg)] #![allow(clippy::unit_arg)]
#![allow(clippy::missing_errors_doc)] #![allow(clippy::missing_errors_doc)]
@ -74,6 +78,13 @@
#![allow(clippy::box_collection)] #![allow(clippy::box_collection)]
#![allow(clippy::too_many_arguments)] #![allow(clippy::too_many_arguments)]
#![allow(clippy::upper_case_acronyms)] #![allow(clippy::upper_case_acronyms)]
#![allow(clippy::match_same_arms)]
// The lints below can be turned off to reduce signal/noise ratio
// #![allow(clippy::too_many_lines)]
// #![allow(clippy::let_underscore_drop)]
// #![allow(clippy::absurd_extreme_comparisons)]
// #![allow(clippy::unnecessary_cast)]
// #![allow(clippy::wildcard_imports)]
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
extern crate alloc; extern crate alloc;
@ -325,8 +336,8 @@ pub use parser::ParseState;
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
pub use ast::{ pub use ast::{
ASTFlags, ASTNode, BinaryExpr, ConditionalExpr, Expr, FnCallExpr, FnCallHashes, Ident, ASTFlags, ASTNode, BinaryExpr, ConditionalExpr, Expr, FlowControl, FnCallExpr, FnCallHashes,
OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, SwitchCasesCollection, TryCatchBlock, Ident, OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, SwitchCasesCollection,
}; };
#[cfg(feature = "internals")] #[cfg(feature = "internals")]

View File

@ -1962,7 +1962,7 @@ impl Module {
#[inline] #[inline]
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn iter_fn(&self) -> impl Iterator<Item = &FuncInfo> { pub(crate) fn iter_fn(&self) -> impl Iterator<Item = &FuncInfo> {
self.functions.iter().flat_map(|m| m.values()) self.functions.iter().flat_map(StraightHashMap::values)
} }
/// Get an iterator over all script-defined functions in the [`Module`]. /// Get an iterator over all script-defined functions in the [`Module`].
@ -2187,9 +2187,11 @@ impl Module {
let f = module.functions.as_mut().unwrap().get_mut(&hash).unwrap(); let f = module.functions.as_mut().unwrap().get_mut(&hash).unwrap();
// Encapsulate AST environment // Encapsulate AST environment
match &mut f.func { if let CallableFunction::Script {
CallableFunction::Script { environ: e, .. } => *e = Some(environ.clone()), environ: ref mut e, ..
_ => (), } = f.func
{
*e = Some(environ.clone())
} }
}); });

View File

@ -2,7 +2,8 @@
#![cfg(not(feature = "no_optimize"))] #![cfg(not(feature = "no_optimize"))]
use crate::ast::{ use crate::ast::{
ASTFlags, Expr, OpAssignment, Stmt, StmtBlock, StmtBlockContainer, SwitchCasesCollection, ASTFlags, Expr, FlowControl, OpAssignment, Stmt, StmtBlock, StmtBlockContainer,
SwitchCasesCollection,
}; };
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF}; use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF};
use crate::eval::{Caches, GlobalRuntimeState}; use crate::eval::{Caches, GlobalRuntimeState};
@ -430,8 +431,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
} }
// if expr {} // if expr {}
Stmt::If(x, ..) if x.1.is_empty() && x.2.is_empty() => { Stmt::If(x, ..) if x.body.is_empty() && x.branch.is_empty() => {
let condition = &mut x.0; let condition = &mut x.expr;
state.set_dirty(); state.set_dirty();
let pos = condition.start_position(); let pos = condition.start_position();
@ -452,8 +453,10 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
}; };
} }
// if false { if_block } -> Noop // if false { if_block } -> Noop
Stmt::If(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) && x.2.is_empty() => { Stmt::If(x, ..)
if let Expr::BoolConstant(false, pos) = x.0 { if matches!(x.expr, Expr::BoolConstant(false, ..)) && x.branch.is_empty() =>
{
if let Expr::BoolConstant(false, pos) = x.expr {
state.set_dirty(); state.set_dirty();
*stmt = Stmt::Noop(pos); *stmt = Stmt::Noop(pos);
} else { } else {
@ -461,33 +464,31 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
} }
} }
// if false { if_block } else { else_block } -> else_block // if false { if_block } else { else_block } -> else_block
Stmt::If(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) => { Stmt::If(x, ..) if matches!(x.expr, Expr::BoolConstant(false, ..)) => {
state.set_dirty(); state.set_dirty();
*stmt = let body = mem::take(&mut *x.branch);
match optimize_stmt_block(mem::take(&mut *x.2), state, preserve_result, true, false) *stmt = match optimize_stmt_block(body, state, preserve_result, true, false) {
{ statements if statements.is_empty() => Stmt::Noop(x.branch.position()),
statements if statements.is_empty() => Stmt::Noop(x.2.position()), statements => (statements, x.branch.span()).into(),
statements => (statements, x.2.span()).into(), }
}
} }
// if true { if_block } else { else_block } -> if_block // if true { if_block } else { else_block } -> if_block
Stmt::If(x, ..) if matches!(x.0, Expr::BoolConstant(true, ..)) => { Stmt::If(x, ..) if matches!(x.expr, Expr::BoolConstant(true, ..)) => {
state.set_dirty(); state.set_dirty();
*stmt = let body = mem::take(&mut *x.body);
match optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false) *stmt = match optimize_stmt_block(body, state, preserve_result, true, false) {
{ statements if statements.is_empty() => Stmt::Noop(x.body.position()),
statements if statements.is_empty() => Stmt::Noop(x.1.position()), statements => (statements, x.body.span()).into(),
statements => (statements, x.1.span()).into(), }
}
} }
// if expr { if_block } else { else_block } // if expr { if_block } else { else_block }
Stmt::If(x, ..) => { Stmt::If(x, ..) => {
let (condition, body, other) = &mut **x; let FlowControl { expr, body, branch } = &mut **x;
optimize_expr(condition, state, false); optimize_expr(expr, state, false);
**body = let statements = mem::take(&mut **body);
optimize_stmt_block(mem::take(&mut **body), state, preserve_result, true, false); **body = optimize_stmt_block(statements, state, preserve_result, true, false);
**other = let statements = mem::take(&mut **branch);
optimize_stmt_block(mem::take(&mut **other), state, preserve_result, true, false); **branch = optimize_stmt_block(statements, state, preserve_result, true, false);
} }
// switch const { ... } // switch const { ... }
@ -524,7 +525,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def } // switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def }
optimize_expr(&mut b.condition, state, false); optimize_expr(&mut b.condition, state, false);
let else_stmt = match def_case { let branch = match def_case {
Some(index) => { Some(index) => {
let mut def_stmt = let mut def_stmt =
Stmt::Expr(mem::take(&mut expressions[*index].expr).into()); Stmt::Expr(mem::take(&mut expressions[*index].expr).into());
@ -533,14 +534,11 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
} }
_ => StmtBlock::NONE, _ => StmtBlock::NONE,
}; };
let body = Stmt::Expr(mem::take(&mut b.expr).into()).into();
let expr = mem::take(&mut b.condition);
*stmt = Stmt::If( *stmt = Stmt::If(
( FlowControl { expr, body, branch }.into(),
mem::take(&mut b.condition),
Stmt::Expr(mem::take(&mut b.expr).into()).into(),
else_stmt,
)
.into(),
match_expr.start_position(), match_expr.start_position(),
); );
} }
@ -585,12 +583,12 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
optimize_stmt(&mut statements, state, true); optimize_stmt(&mut statements, state, true);
*stmt = statements; *stmt = statements;
} else { } else {
let mut condition = mem::take(&mut range_block.condition); let mut expr = mem::take(&mut range_block.condition);
// switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def } // switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def }
optimize_expr(&mut condition, state, false); optimize_expr(&mut expr, state, false);
let else_stmt = match def_case { let branch = match def_case {
Some(index) => { Some(index) => {
let mut def_stmt = let mut def_stmt =
Stmt::Expr(mem::take(&mut expressions[*index].expr).into()); Stmt::Expr(mem::take(&mut expressions[*index].expr).into());
@ -600,12 +598,12 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
_ => StmtBlock::NONE, _ => StmtBlock::NONE,
}; };
let if_stmt = let body =
Stmt::Expr(mem::take(&mut expressions[r.index()].expr).into()) Stmt::Expr(mem::take(&mut expressions[r.index()].expr).into())
.into(); .into();
*stmt = Stmt::If( *stmt = Stmt::If(
(condition, if_stmt, else_stmt).into(), FlowControl { expr, body, branch }.into(),
match_expr.start_position(), match_expr.start_position(),
); );
} }
@ -738,7 +736,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
} }
// while false { block } -> Noop // while false { block } -> Noop
Stmt::While(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) => match x.0 { Stmt::While(x, ..) if matches!(x.expr, Expr::BoolConstant(false, ..)) => match x.expr {
Expr::BoolConstant(false, pos) => { Expr::BoolConstant(false, pos) => {
state.set_dirty(); state.set_dirty();
*stmt = Stmt::Noop(pos); *stmt = Stmt::Noop(pos);
@ -747,22 +745,22 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
}, },
// while expr { block } // while expr { block }
Stmt::While(x, ..) => { Stmt::While(x, ..) => {
let (condition, body) = &mut **x; let FlowControl { expr, body, .. } = &mut **x;
optimize_expr(condition, state, false); optimize_expr(expr, state, false);
if let Expr::BoolConstant(true, pos) = condition { if let Expr::BoolConstant(true, pos) = expr {
*condition = Expr::Unit(*pos); *expr = Expr::Unit(*pos);
} }
**body = optimize_stmt_block(mem::take(&mut **body), state, false, true, false); **body = optimize_stmt_block(mem::take(&mut **body), state, false, true, false);
} }
// do { block } while|until expr // do { block } while|until expr
Stmt::Do(x, ..) => { Stmt::Do(x, ..) => {
optimize_expr(&mut x.0, state, false); optimize_expr(&mut x.expr, state, false);
*x.1 = optimize_stmt_block(mem::take(&mut *x.1), state, false, true, false); *x.body = optimize_stmt_block(mem::take(&mut *x.body), state, false, true, false);
} }
// for id in expr { block } // for id in expr { block }
Stmt::For(x, ..) => { Stmt::For(x, ..) => {
optimize_expr(&mut x.2, state, false); optimize_expr(&mut x.2.expr, state, false);
*x.3 = optimize_stmt_block(mem::take(&mut *x.3), state, false, true, false); *x.2.body = optimize_stmt_block(mem::take(&mut *x.2.body), state, false, true, false);
} }
// let id = expr; // let id = expr;
Stmt::Var(x, options, ..) if !options.contains(ASTFlags::CONSTANT) => { Stmt::Var(x, options, ..) if !options.contains(ASTFlags::CONSTANT) => {
@ -791,21 +789,19 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
} }
} }
// try { pure try_block } catch ( var ) { catch_block } -> try_block // try { pure try_block } catch ( var ) { catch_block } -> try_block
Stmt::TryCatch(x, ..) if x.try_block.iter().all(Stmt::is_pure) => { Stmt::TryCatch(x, ..) if x.body.iter().all(Stmt::is_pure) => {
// If try block is pure, there will never be any exceptions // If try block is pure, there will never be any exceptions
state.set_dirty(); state.set_dirty();
*stmt = ( *stmt = (
optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false), optimize_stmt_block(mem::take(&mut *x.body), state, false, true, false),
x.try_block.span(), x.body.span(),
) )
.into(); .into();
} }
// try { try_block } catch ( var ) { catch_block } // try { try_block } catch ( var ) { catch_block }
Stmt::TryCatch(x, ..) => { Stmt::TryCatch(x, ..) => {
*x.try_block = *x.body = optimize_stmt_block(mem::take(&mut *x.body), state, false, true, false);
optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false); *x.branch = optimize_stmt_block(mem::take(&mut *x.branch), state, false, true, false);
*x.catch_block =
optimize_stmt_block(mem::take(&mut *x.catch_block), state, false, true, false);
} }
// expr(stmt) // expr(stmt)
@ -932,6 +928,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
} }
// lhs[rhs] // lhs[rhs]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
Expr::Index(x, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) { Expr::Index(x, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) {
// array[int] // array[int]
(Expr::Array(a, pos), Expr::IntegerConstant(i, ..)) if *i >= 0 && *i <= crate::MAX_USIZE_INT && (*i as usize) < a.len() && a.iter().all(Expr::is_pure) => { (Expr::Array(a, pos), Expr::IntegerConstant(i, ..)) if *i >= 0 && *i <= crate::MAX_USIZE_INT && (*i as usize) < a.len() && a.iter().all(Expr::is_pure) => {
@ -1339,7 +1336,11 @@ impl Engine {
let lib: crate::Shared<_> = { let lib: crate::Shared<_> = {
let mut module = crate::Module::new(); let mut module = crate::Module::new();
if optimization_level != OptimizationLevel::None { if optimization_level == OptimizationLevel::None {
for fn_def in functions {
module.set_script_fn(fn_def);
}
} else {
// We only need the script library's signatures for optimization purposes // We only need the script library's signatures for optimization purposes
let mut lib2 = crate::Module::new(); let mut lib2 = crate::Module::new();
@ -1365,10 +1366,6 @@ impl Engine {
*fn_def.body = self.optimize_top_level(body, scope, lib2, optimization_level); *fn_def.body = self.optimize_top_level(body, scope, lib2, optimization_level);
module.set_script_fn(fn_def);
}
} else {
for fn_def in functions {
module.set_script_fn(fn_def); module.set_script_fn(fn_def);
} }
} }

View File

@ -276,6 +276,7 @@ gen_signed_functions!(signed_num_128 => i128);
#[export_module] #[export_module]
mod f32_functions { mod f32_functions {
#[cfg(not(feature = "f32_float"))] #[cfg(not(feature = "f32_float"))]
#[allow(clippy::cast_precision_loss)]
pub mod basic_arithmetic { pub mod basic_arithmetic {
#[rhai_fn(name = "+")] #[rhai_fn(name = "+")]
pub fn add(x: f32, y: f32) -> f32 { pub fn add(x: f32, y: f32) -> f32 {
@ -381,6 +382,7 @@ mod f32_functions {
"Number raised to too large an index: {x} ** {y}" "Number raised to too large an index: {x} ** {y}"
))) )))
} else { } else {
#[allow(clippy::cast_possible_truncation)]
Ok(x.powi(y as i32)) Ok(x.powi(y as i32))
} }
} }

View File

@ -220,14 +220,18 @@ pub mod array_functions {
len: INT, len: INT,
item: Dynamic, item: Dynamic,
) -> RhaiResultOf<()> { ) -> RhaiResultOf<()> {
let len = len.min(MAX_USIZE_INT); if len <= 0 {
return Ok(());
}
if len <= 0 || (len as usize) <= array.len() { #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let len = len.min(MAX_USIZE_INT) as usize;
if len <= array.len() {
return Ok(()); return Ok(());
} }
let _ctx = ctx; let _ctx = ctx;
let len = len as usize;
// Check if array will be over max size limit // Check if array will be over max size limit
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -373,11 +377,16 @@ pub mod array_functions {
/// print(x); // prints "[1, 2, 3]" /// print(x); // prints "[1, 2, 3]"
/// ``` /// ```
pub fn truncate(array: &mut Array, len: INT) { pub fn truncate(array: &mut Array, len: INT) {
if len <= 0 {
array.clear();
return;
}
if !array.is_empty() { if !array.is_empty() {
let len = len.min(MAX_USIZE_INT); #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let len = len.min(MAX_USIZE_INT) as usize;
if len > 0 { if len > 0 {
array.truncate(len as usize); array.truncate(len);
} else { } else {
array.clear(); array.clear();
} }
@ -402,13 +411,18 @@ pub mod array_functions {
/// print(x); // prints "[3, 4, 5]" /// print(x); // prints "[3, 4, 5]"
/// ``` /// ```
pub fn chop(array: &mut Array, len: INT) { pub fn chop(array: &mut Array, len: INT) {
if len <= 0 {
array.clear();
return;
}
if !array.is_empty() { if !array.is_empty() {
let len = len.min(MAX_USIZE_INT); #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let len = len.min(MAX_USIZE_INT) as usize;
if len <= 0 { if len <= 0 {
array.clear(); array.clear();
} else if (len as usize) < array.len() { } else if len < array.len() {
array.drain(0..array.len() - len as usize); array.drain(0..array.len() - len);
} }
} }
} }
@ -633,6 +647,10 @@ pub mod array_functions {
/// Iterate through all the elements in the array, applying a `mapper` function to each element /// Iterate through all the elements in the array, applying a `mapper` function to each element
/// in turn, and return the results as a new array. /// in turn, and return the results as a new array.
/// ///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// # Function Parameters /// # Function Parameters
/// ///
/// * `element`: copy of array element /// * `element`: copy of array element
@ -652,17 +670,17 @@ pub mod array_functions {
/// print(y); // prints "[0, 2, 6, 12, 20]" /// print(y); // prints "[0, 2, 6, 12, 20]"
/// ``` /// ```
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn map(ctx: NativeCallContext, array: Array, map: FnPtr) -> RhaiResultOf<Array> { pub fn map(ctx: NativeCallContext, array: &mut Array, map: FnPtr) -> RhaiResultOf<Array> {
if array.is_empty() { if array.is_empty() {
return Ok(array); return Ok(Array::new());
} }
let mut ar = Array::with_capacity(array.len()); let mut ar = Array::with_capacity(array.len());
for (i, item) in array.into_iter().enumerate() { for (i, item) in array.iter_mut().enumerate() {
let ex = [(i as INT).into()]; let ex = [(i as INT).into()];
ar.push(map.call_raw_with_extra_args("map", &ctx, None, [item], ex)?); ar.push(map.call_raw_with_extra_args("map", &ctx, Some(item), [], ex)?);
} }
Ok(ar) Ok(ar)
@ -671,6 +689,10 @@ pub mod array_functions {
/// Iterate through all the elements in the array, applying a `filter` function to each element /// Iterate through all the elements in the array, applying a `filter` function to each element
/// in turn, and return a copy of all elements (in order) that return `true` as a new array. /// in turn, and return a copy of all elements (in order) that return `true` as a new array.
/// ///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// # Function Parameters /// # Function Parameters
/// ///
/// * `element`: copy of array element /// * `element`: copy of array element
@ -690,22 +712,22 @@ pub mod array_functions {
/// print(y); // prints "[12, 20]" /// print(y); // prints "[12, 20]"
/// ``` /// ```
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn filter(ctx: NativeCallContext, array: Array, filter: FnPtr) -> RhaiResultOf<Array> { pub fn filter(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf<Array> {
if array.is_empty() { if array.is_empty() {
return Ok(array); return Ok(Array::new());
} }
let mut ar = Array::new(); let mut ar = Array::new();
for (i, item) in array.into_iter().enumerate() { for (i, item) in array.iter_mut().enumerate() {
let ex = [(i as INT).into()]; let ex = [(i as INT).into()];
if filter if filter
.call_raw_with_extra_args("filter", &ctx, None, [item.clone()], ex)? .call_raw_with_extra_args("filter", &ctx, Some(item), [], ex)?
.as_bool() .as_bool()
.unwrap_or(false) .unwrap_or(false)
{ {
ar.push(item); ar.push(item.clone());
} }
} }
@ -857,6 +879,10 @@ pub mod array_functions {
/// in turn, and return the index of the first element that returns `true`. /// in turn, and return the index of the first element that returns `true`.
/// If no element returns `true`, `-1` is returned. /// If no element returns `true`, `-1` is returned.
/// ///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// # Function Parameters /// # Function Parameters
/// ///
/// * `element`: copy of array element /// * `element`: copy of array element
@ -893,6 +919,10 @@ pub mod array_functions {
/// * If `start` < -length of array, position counts from the beginning of the array. /// * If `start` < -length of array, position counts from the beginning of the array.
/// * If `start` ≥ length of array, `-1` is returned. /// * If `start` ≥ length of array, `-1` is returned.
/// ///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// # Function Parameters /// # Function Parameters
/// ///
/// * `element`: copy of array element /// * `element`: copy of array element
@ -928,11 +958,11 @@ pub mod array_functions {
let (start, ..) = calc_offset_len(array.len(), start, 0); let (start, ..) = calc_offset_len(array.len(), start, 0);
for (i, item) in array.iter().enumerate().skip(start) { for (i, item) in array.iter_mut().enumerate().skip(start) {
let ex = [(i as INT).into()]; let ex = [(i as INT).into()];
if filter if filter
.call_raw_with_extra_args("index_of", &ctx, None, [item.clone()], ex)? .call_raw_with_extra_args("index_of", &ctx, Some(item), [], ex)?
.as_bool() .as_bool()
.unwrap_or(false) .unwrap_or(false)
{ {
@ -946,6 +976,10 @@ pub mod array_functions {
/// in turn, and return a copy of the first element that returns `true`. If no element returns /// in turn, and return a copy of the first element that returns `true`. If no element returns
/// `true`, `()` is returned. /// `true`, `()` is returned.
/// ///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// # Function Parameters /// # Function Parameters
/// ///
/// * `element`: copy of array element /// * `element`: copy of array element
@ -958,7 +992,7 @@ pub mod array_functions {
/// ///
/// print(x.find(|v| v > 3)); // prints 5: 5 > 3 /// print(x.find(|v| v > 3)); // prints 5: 5 > 3
/// ///
/// x.find(|v| v > 13) ?? print("not found"); // prints "not found": nothing is > 13 /// print(x.find(|v| v > 13) ?? "not found"); // prints "not found": nothing is > 13
/// ///
/// print(x.find(|v, i| v * i > 13)); // prints 5: 3 * 5 > 13 /// print(x.find(|v, i| v * i > 13)); // prints 5: 3 * 5 > 13
/// ``` /// ```
@ -974,6 +1008,10 @@ pub mod array_functions {
/// * If `start` < -length of array, position counts from the beginning of the array. /// * If `start` < -length of array, position counts from the beginning of the array.
/// * If `start` ≥ length of array, `-1` is returned. /// * If `start` ≥ length of array, `-1` is returned.
/// ///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// # Function Parameters /// # Function Parameters
/// ///
/// * `element`: copy of array element /// * `element`: copy of array element
@ -986,9 +1024,9 @@ pub mod array_functions {
/// ///
/// print(x.find(|v| v > 1, 2)); // prints 3: 3 > 1 /// print(x.find(|v| v > 1, 2)); // prints 3: 3 > 1
/// ///
/// x.find(|v| v < 2, 3) ?? print("not found"); // prints "not found": nothing < 2 past index 3 /// print(x.find(|v| v < 2, 3) ?? "not found"); // prints "not found": nothing < 2 past index 3
/// ///
/// x.find(|v| v > 1, 8) ?? print("not found"); // prints "not found": nothing found past end of array /// print(x.find(|v| v > 1, 8) ?? "not found"); // prints "not found": nothing found past end of array
/// ///
/// print(x.find(|v| v > 1, -3)); // prints 5: -3 = start from index 4 /// print(x.find(|v| v > 1, -3)); // prints 5: -3 = start from index 4
/// ///
@ -1014,6 +1052,10 @@ pub mod array_functions {
/// Iterate through all the elements in the array, applying a `mapper` function to each element /// Iterate through all the elements in the array, applying a `mapper` function to each element
/// in turn, and return the first result that is not `()`. Otherwise, `()` is returned. /// in turn, and return the first result that is not `()`. Otherwise, `()` is returned.
/// ///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// # Function Parameters /// # Function Parameters
/// ///
/// * `element`: copy of array element /// * `element`: copy of array element
@ -1026,7 +1068,9 @@ pub mod array_functions {
/// ///
/// print(x.find_map(|v| v.alice)); // prints 1 /// print(x.find_map(|v| v.alice)); // prints 1
/// ///
/// x.find_map(|v| v.dave) ?? print("not found"); // prints "not found" /// print(x.find_map(|v| v.dave) ?? "not found"); // prints "not found"
///
/// print(x.find_map(|| this.dave) ?? "not found"); // prints "not found"
/// ``` /// ```
#[rhai_fn(return_raw, pure)] #[rhai_fn(return_raw, pure)]
pub fn find_map(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResult { pub fn find_map(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResult {
@ -1040,6 +1084,10 @@ pub mod array_functions {
/// * If `start` < -length of array, position counts from the beginning of the array. /// * If `start` < -length of array, position counts from the beginning of the array.
/// * If `start` ≥ length of array, `-1` is returned. /// * If `start` ≥ length of array, `-1` is returned.
/// ///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// # Function Parameters /// # Function Parameters
/// ///
/// * `element`: copy of array element /// * `element`: copy of array element
@ -1052,13 +1100,17 @@ pub mod array_functions {
/// ///
/// print(x.find_map(|v| v.alice, 2)); // prints 0 /// print(x.find_map(|v| v.alice, 2)); // prints 0
/// ///
/// x.find_map(|v| v.bob, 4) ?? print("not found"); // prints "not found" /// print(x.find_map(|v| v.bob, 4) ?? "not found"); // prints "not found"
/// ///
/// x.find_map(|v| v.alice, 8) ?? print("not found"); // prints "not found" /// print(x.find_map(|v| v.alice, 8) ?? "not found"); // prints "not found"
///
/// print(x.find_map(|| this.alice, 8) ?? "not found"); // prints "not found"
/// ///
/// print(x.find_map(|v| v.bob, -4)); // prints 3: -4 = start from index 2 /// print(x.find_map(|v| v.bob, -4)); // prints 3: -4 = start from index 2
/// ///
/// print(x.find_map(|v| v.alice, -99)); // prints 1: -99 = start from beginning /// print(x.find_map(|v| v.alice, -99)); // prints 1: -99 = start from beginning
///
/// print(x.find_map(|| this.alice, -99)); // prints 1: -99 = start from beginning
/// ``` /// ```
#[rhai_fn(name = "find_map", return_raw, pure)] #[rhai_fn(name = "find_map", return_raw, pure)]
pub fn find_map_starting_from( pub fn find_map_starting_from(
@ -1073,11 +1125,10 @@ pub mod array_functions {
let (start, ..) = calc_offset_len(array.len(), start, 0); let (start, ..) = calc_offset_len(array.len(), start, 0);
for (i, item) in array.iter().enumerate().skip(start) { for (i, item) in array.iter_mut().enumerate().skip(start) {
let ex = [(i as INT).into()]; let ex = [(i as INT).into()];
let value = let value = filter.call_raw_with_extra_args("find_map", &ctx, Some(item), [], ex)?;
filter.call_raw_with_extra_args("find_map", &ctx, None, [item.clone()], ex)?;
if !value.is_unit() { if !value.is_unit() {
return Ok(value); return Ok(value);
@ -1088,6 +1139,10 @@ pub mod array_functions {
} }
/// Return `true` if any element in the array that returns `true` when applied the `filter` function. /// Return `true` if any element in the array that returns `true` when applied the `filter` function.
/// ///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// # Function Parameters /// # Function Parameters
/// ///
/// * `element`: copy of array element /// * `element`: copy of array element
@ -1110,11 +1165,11 @@ pub mod array_functions {
return Ok(false); return Ok(false);
} }
for (i, item) in array.iter().enumerate() { for (i, item) in array.iter_mut().enumerate() {
let ex = [(i as INT).into()]; let ex = [(i as INT).into()];
if filter if filter
.call_raw_with_extra_args("some", &ctx, None, [item.clone()], ex)? .call_raw_with_extra_args("some", &ctx, Some(item), [], ex)?
.as_bool() .as_bool()
.unwrap_or(false) .unwrap_or(false)
{ {
@ -1126,6 +1181,10 @@ pub mod array_functions {
} }
/// Return `true` if all elements in the array return `true` when applied the `filter` function. /// Return `true` if all elements in the array return `true` when applied the `filter` function.
/// ///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// # Function Parameters /// # Function Parameters
/// ///
/// * `element`: copy of array element /// * `element`: copy of array element
@ -1148,11 +1207,11 @@ pub mod array_functions {
return Ok(true); return Ok(true);
} }
for (i, item) in array.iter().enumerate() { for (i, item) in array.iter_mut().enumerate() {
let ex = [(i as INT).into()]; let ex = [(i as INT).into()];
if !filter if !filter
.call_raw_with_extra_args("all", &ctx, None, [item.clone()], ex)? .call_raw_with_extra_args("all", &ctx, Some(item), [], ex)?
.as_bool() .as_bool()
.unwrap_or(false) .unwrap_or(false)
{ {
@ -1178,7 +1237,7 @@ pub mod array_functions {
/// ``` /// ```
pub fn dedup(ctx: NativeCallContext, array: &mut Array) { pub fn dedup(ctx: NativeCallContext, array: &mut Array) {
let comparer = FnPtr::new_unchecked(OP_EQUALS, StaticVec::new_const()); let comparer = FnPtr::new_unchecked(OP_EQUALS, StaticVec::new_const());
dedup_by_comparer(ctx, array, comparer) dedup_by_comparer(ctx, array, comparer);
} }
/// Remove duplicated _consecutive_ elements from the array that return `true` when applied the /// Remove duplicated _consecutive_ elements from the array that return `true` when applied the
/// `comparer` function. /// `comparer` function.
@ -1502,6 +1561,10 @@ pub mod array_functions {
/// Remove all elements in the array that returns `true` when applied the `filter` function and /// Remove all elements in the array that returns `true` when applied the `filter` function and
/// return them as a new array. /// return them as a new array.
/// ///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// # Function Parameters /// # Function Parameters
/// ///
/// * `element`: copy of array element /// * `element`: copy of array element
@ -1539,7 +1602,7 @@ pub mod array_functions {
let ex = [(i as INT).into()]; let ex = [(i as INT).into()];
if filter if filter
.call_raw_with_extra_args("drain", &ctx, None, [array[x].clone()], ex)? .call_raw_with_extra_args("drain", &ctx, Some(&mut array[x]), [], ex)?
.as_bool() .as_bool()
.unwrap_or(false) .unwrap_or(false)
{ {
@ -1645,6 +1708,10 @@ pub mod array_functions {
/// Remove all elements in the array that do not return `true` when applied the `filter` /// Remove all elements in the array that do not return `true` when applied the `filter`
/// function and return them as a new array. /// function and return them as a new array.
/// ///
/// # No Function Parameter
///
/// Array element (mutable) is bound to `this`.
///
/// # Function Parameters /// # Function Parameters
/// ///
/// * `element`: copy of array element /// * `element`: copy of array element
@ -1682,7 +1749,7 @@ pub mod array_functions {
let ex = [(i as INT).into()]; let ex = [(i as INT).into()];
if filter if filter
.call_raw_with_extra_args("retain", &ctx, None, [array[x].clone()], ex)? .call_raw_with_extra_args("retain", &ctx, Some(&mut array[x]), [], ex)?
.as_bool() .as_bool()
.unwrap_or(false) .unwrap_or(false)
{ {

View File

@ -132,6 +132,7 @@ mod bit_field_functions {
ERR::ErrorBitFieldBounds(INT_BITS, start, Position::NONE).into() ERR::ErrorBitFieldBounds(INT_BITS, start, Position::NONE).into()
})?; })?;
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let bits = if bit + bits as usize > INT_BITS { let bits = if bit + bits as usize > INT_BITS {
INT_BITS - bit INT_BITS - bit
} else { } else {
@ -143,6 +144,7 @@ mod bit_field_functions {
} }
// 2^bits - 1 // 2^bits - 1
#[allow(clippy::cast_possible_truncation)]
let mask = ((2 as UNSIGNED_INT).pow(bits as u32) - 1) as INT; let mask = ((2 as UNSIGNED_INT).pow(bits as u32) - 1) as INT;
Ok(((value & (mask << bit)) >> bit) & mask) Ok(((value & (mask << bit)) >> bit) & mask)
@ -218,6 +220,7 @@ mod bit_field_functions {
ERR::ErrorBitFieldBounds(INT_BITS, bit, Position::NONE).into() ERR::ErrorBitFieldBounds(INT_BITS, bit, Position::NONE).into()
})?; })?;
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let bits = if bit + bits as usize > INT_BITS { let bits = if bit + bits as usize > INT_BITS {
INT_BITS - bit INT_BITS - bit
} else { } else {
@ -230,6 +233,7 @@ mod bit_field_functions {
} }
// 2^bits - 1 // 2^bits - 1
#[allow(clippy::cast_possible_truncation)]
let mask = ((2 as UNSIGNED_INT).pow(bits as u32) - 1) as INT; let mask = ((2 as UNSIGNED_INT).pow(bits as u32) - 1) as INT;
*value &= !(mask << bit); *value &= !(mask << bit);

View File

@ -75,7 +75,8 @@ pub mod blob_functions {
len: INT, len: INT,
value: INT, value: INT,
) -> RhaiResultOf<Blob> { ) -> RhaiResultOf<Blob> {
let len = len.min(MAX_USIZE_INT).max(0) as usize; #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let len = len.clamp(0, MAX_USIZE_INT) as usize;
let _ctx = ctx; let _ctx = ctx;
// Check if blob will be over max size limit // Check if blob will be over max size limit
@ -83,6 +84,7 @@ pub mod blob_functions {
_ctx.engine().throw_on_size((len, 0, 0))?; _ctx.engine().throw_on_size((len, 0, 0))?;
let mut blob = Blob::new(); let mut blob = Blob::new();
#[allow(clippy::cast_sign_loss)]
blob.resize(len, (value & 0x0000_00ff) as u8); blob.resize(len, (value & 0x0000_00ff) as u8);
Ok(blob) Ok(blob)
} }
@ -155,6 +157,7 @@ pub mod blob_functions {
/// ``` /// ```
#[rhai_fn(name = "contains")] #[rhai_fn(name = "contains")]
pub fn contains(blob: &mut Blob, value: INT) -> bool { pub fn contains(blob: &mut Blob, value: INT) -> bool {
#[allow(clippy::cast_sign_loss)]
blob.contains(&((value & 0x0000_00ff) as u8)) blob.contains(&((value & 0x0000_00ff) as u8))
} }
/// Get the byte value at the `index` position in the BLOB. /// Get the byte value at the `index` position in the BLOB.
@ -221,6 +224,7 @@ pub mod blob_functions {
let (index, ..) = calc_offset_len(blob.len(), index, 0); let (index, ..) = calc_offset_len(blob.len(), index, 0);
#[allow(clippy::cast_sign_loss)]
if index < blob.len() { if index < blob.len() {
blob[index] = (value & 0x0000_00ff) as u8; blob[index] = (value & 0x0000_00ff) as u8;
} }
@ -240,6 +244,7 @@ pub mod blob_functions {
/// ``` /// ```
#[rhai_fn(name = "push", name = "append")] #[rhai_fn(name = "push", name = "append")]
pub fn push(blob: &mut Blob, value: INT) { pub fn push(blob: &mut Blob, value: INT) {
#[allow(clippy::cast_sign_loss)]
blob.push((value & 0x0000_00ff) as u8); blob.push((value & 0x0000_00ff) as u8);
} }
/// Add another BLOB to the end of the BLOB. /// Add another BLOB to the end of the BLOB.
@ -315,6 +320,7 @@ pub mod blob_functions {
/// print(b); // prints "[4242184242]" /// print(b); // prints "[4242184242]"
/// ``` /// ```
pub fn insert(blob: &mut Blob, index: INT, value: INT) { pub fn insert(blob: &mut Blob, index: INT, value: INT) {
#[allow(clippy::cast_sign_loss)]
let value = (value & 0x0000_00ff) as u8; let value = (value & 0x0000_00ff) as u8;
if blob.is_empty() { if blob.is_empty() {
@ -354,8 +360,10 @@ pub mod blob_functions {
if len <= 0 { if len <= 0 {
return Ok(()); return Ok(());
} }
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let len = len.min(MAX_USIZE_INT) as usize; let len = len.min(MAX_USIZE_INT) as usize;
#[allow(clippy::cast_sign_loss)]
let value = (value & 0x0000_00ff) as u8; let value = (value & 0x0000_00ff) as u8;
let _ctx = ctx; let _ctx = ctx;
@ -470,11 +478,16 @@ pub mod blob_functions {
/// print(b); // prints "[010203]" /// print(b); // prints "[010203]"
/// ``` /// ```
pub fn truncate(blob: &mut Blob, len: INT) { pub fn truncate(blob: &mut Blob, len: INT) {
if len <= 0 {
blob.clear();
return;
}
if !blob.is_empty() { if !blob.is_empty() {
let len = len.min(MAX_USIZE_INT); #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let len = len.min(MAX_USIZE_INT) as usize;
if len > 0 { if len > 0 {
blob.truncate(len as usize); blob.truncate(len);
} else { } else {
blob.clear(); blob.clear();
} }
@ -500,6 +513,7 @@ pub mod blob_functions {
/// ///
/// print(b); // prints "[030405]" /// print(b); // prints "[030405]"
/// ``` /// ```
#[allow(clippy::cast_sign_loss, clippy::needless_pass_by_value)]
pub fn chop(blob: &mut Blob, len: INT) { pub fn chop(blob: &mut Blob, len: INT) {
if !blob.is_empty() { if !blob.is_empty() {
if len <= 0 { if len <= 0 {

View File

@ -22,6 +22,7 @@ use rust_decimal::Decimal;
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[inline(always)] #[inline(always)]
#[allow(clippy::needless_pass_by_value)]
fn std_add<T>(x: T, y: T) -> Option<T> fn std_add<T>(x: T, y: T) -> Option<T>
where where
T: num_traits::CheckedAdd<Output = T>, T: num_traits::CheckedAdd<Output = T>,
@ -30,6 +31,7 @@ where
} }
#[inline(always)] #[inline(always)]
#[allow(dead_code)] #[allow(dead_code)]
#[allow(clippy::unnecessary_wraps, clippy::needless_pass_by_value)]
fn regular_add<T>(x: T, y: T) -> Option<T> fn regular_add<T>(x: T, y: T) -> Option<T>
where where
T: std::ops::Add<Output = T>, T: std::ops::Add<Output = T>,
@ -123,6 +125,7 @@ impl<T: Copy + PartialOrd> FusedIterator for StepRange<T> {}
pub struct BitRange(INT, usize); pub struct BitRange(INT, usize);
impl BitRange { impl BitRange {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
pub fn new(value: INT, from: INT, len: INT) -> RhaiResultOf<Self> { pub fn new(value: INT, from: INT, len: INT) -> RhaiResultOf<Self> {
let from = calc_index(INT_BITS, from, true, || { let from = calc_index(INT_BITS, from, true, || {
ERR::ErrorBitFieldBounds(INT_BITS, from, Position::NONE).into() ERR::ErrorBitFieldBounds(INT_BITS, from, Position::NONE).into()
@ -174,6 +177,7 @@ impl ExactSizeIterator for BitRange {
pub struct CharsStream(Vec<char>, usize); pub struct CharsStream(Vec<char>, usize);
impl CharsStream { impl CharsStream {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
pub fn new(string: &str, from: INT, len: INT) -> Self { pub fn new(string: &str, from: INT, len: INT) -> Self {
if len <= 0 || from > MAX_USIZE_INT { if len <= 0 || from > MAX_USIZE_INT {
return Self(Vec::new(), 0); return Self(Vec::new(), 0);

View File

@ -153,8 +153,9 @@ mod reflection_functions {
/// Return an array of object maps containing metadata of all script-defined functions /// Return an array of object maps containing metadata of all script-defined functions
/// matching the specified name and arity (number of parameters). /// matching the specified name and arity (number of parameters).
#[rhai_fn(name = "get_fn_metadata_list")] #[rhai_fn(name = "get_fn_metadata_list")]
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
pub fn get_fn_metadata2(ctx: NativeCallContext, name: &str, params: INT) -> Array { pub fn get_fn_metadata2(ctx: NativeCallContext, name: &str, params: INT) -> Array {
if params < 0 || params > crate::MAX_USIZE_INT { if !(0..=crate::MAX_USIZE_INT).contains(&params) {
Array::new() Array::new()
} else { } else {
collect_fn_metadata(ctx, |_, _, n, p, _| p == (params as usize) && n == name) collect_fn_metadata(ctx, |_, _, n, p, _| p == (params as usize) && n == name)

View File

@ -477,7 +477,7 @@ mod decimal_functions {
} }
} }
#[allow(clippy::cast_sign_loss)] #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
Ok(x.round_dp(digits 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. /// Round the decimal number to the specified number of `digits` after the decimal point and return it.
@ -495,7 +495,7 @@ mod decimal_functions {
} }
} }
#[allow(clippy::cast_sign_loss)] #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
Ok(x.round_dp_with_strategy(digits 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. /// Round the decimal number to the specified number of `digits` after the decimal point and return it.
@ -513,7 +513,7 @@ mod decimal_functions {
} }
} }
#[allow(clippy::cast_sign_loss)] #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
Ok(x.round_dp_with_strategy(digits 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. /// Round the decimal number to the specified number of `digits` after the decimal point and return it.
@ -531,7 +531,7 @@ mod decimal_functions {
} }
} }
#[allow(clippy::cast_sign_loss)] #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
Ok(x.round_dp_with_strategy(digits 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. /// Round the decimal number to the specified number of `digits` after the decimal point and return it.
@ -549,33 +549,34 @@ mod decimal_functions {
} }
} }
#[allow(clippy::cast_sign_loss)] #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::MidpointTowardZero)) Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::MidpointTowardZero))
} }
/// Convert the decimal number into an integer. /// Convert the decimal number into an integer.
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn to_int(x: Decimal) -> RhaiResultOf<INT> { pub fn to_int(x: Decimal) -> RhaiResultOf<INT> {
let n = x.to_i64().and_then(|n| { x.to_i64()
#[cfg(feature = "only_i32")] .and_then(|n| {
return if n > (INT::MAX as i64) || n < (INT::MIN as i64) { #[cfg(feature = "only_i32")]
None return if n > (INT::MAX as i64) || n < (INT::MIN as i64) {
} else { None
Some(n as i32) } else {
}; Some(n as i32)
};
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
return Some(n); return Some(n);
}); })
.map_or_else(
n.map_or_else( || {
|| { Err(ERR::ErrorArithmetic(
Err( format!("Integer overflow: to_int({x})"),
ERR::ErrorArithmetic(format!("Integer overflow: to_int({x})"), Position::NONE) Position::NONE,
.into(), )
) .into())
}, },
Ok, Ok,
) )
} }
/// Return the integral part of the decimal number. /// Return the integral part of the decimal number.
#[rhai_fn(name = "int", get = "int")] #[rhai_fn(name = "int", get = "int")]

View File

@ -95,11 +95,11 @@ mod string_functions {
#[rhai_fn(name = "+=")] #[rhai_fn(name = "+=")]
pub fn add_assign_append_str(string1: &mut ImmutableString, string2: ImmutableString) { pub fn add_assign_append_str(string1: &mut ImmutableString, string2: ImmutableString) {
*string1 += string2 *string1 += string2;
} }
#[rhai_fn(name = "+=", pure)] #[rhai_fn(name = "+=", pure)]
pub fn add_assign_append_char(string: &mut ImmutableString, character: char) { pub fn add_assign_append_char(string: &mut ImmutableString, character: char) {
*string += character *string += character;
} }
#[rhai_fn(name = "+=")] #[rhai_fn(name = "+=")]
pub fn add_assign_append_unit(string: &mut ImmutableString, item: ()) { pub fn add_assign_append_unit(string: &mut ImmutableString, item: ()) {
@ -246,9 +246,9 @@ mod string_functions {
pub fn clear(string: &mut ImmutableString) { pub fn clear(string: &mut ImmutableString) {
if !string.is_empty() { if !string.is_empty() {
if let Some(s) = string.get_mut() { if let Some(s) = string.get_mut() {
s.clear() s.clear();
} else { } else {
*string = ImmutableString::new() *string = ImmutableString::new();
} }
} }
} }
@ -272,7 +272,7 @@ mod string_functions {
/// ``` /// ```
pub fn truncate(string: &mut ImmutableString, len: INT) { pub fn truncate(string: &mut ImmutableString, len: INT) {
if len > 0 { if len > 0 {
#[allow(clippy::cast_sign_loss)] #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let len = len.min(MAX_USIZE_INT) as usize; let len = len.min(MAX_USIZE_INT) as usize;
let chars: StaticVec<_> = string.chars().collect(); let chars: StaticVec<_> = string.chars().collect();
let copy = string.make_mut(); let copy = string.make_mut();
@ -355,6 +355,7 @@ mod string_functions {
if string.is_empty() || len <= 0 { if string.is_empty() || len <= 0 {
return ctx.engine().const_empty_string(); return ctx.engine().const_empty_string();
} }
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let len = len.min(MAX_USIZE_INT) as usize; let len = len.min(MAX_USIZE_INT) as usize;
let mut chars = StaticVec::<char>::with_capacity(len); let mut chars = StaticVec::<char>::with_capacity(len);
@ -598,6 +599,7 @@ mod string_functions {
return -1; return -1;
} }
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let start = if start < 0 { let start = if start < 0 {
let abs_start = start.unsigned_abs(); let abs_start = start.unsigned_abs();
@ -608,6 +610,7 @@ mod string_functions {
let abs_start = abs_start as usize; let abs_start = abs_start as usize;
let chars: Vec<_> = string.chars().collect(); let chars: Vec<_> = string.chars().collect();
let num_chars = chars.len(); let num_chars = chars.len();
if abs_start > num_chars { if abs_start > num_chars {
0 0
} else { } else {
@ -680,6 +683,7 @@ mod string_functions {
return -1; return -1;
} }
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let start = if start < 0 { let start = if start < 0 {
let abs_start = start.unsigned_abs(); let abs_start = start.unsigned_abs();
@ -690,6 +694,7 @@ mod string_functions {
let abs_start = abs_start as usize; let abs_start = abs_start as usize;
let chars = string.chars().collect::<Vec<_>>(); let chars = string.chars().collect::<Vec<_>>();
let num_chars = chars.len(); let num_chars = chars.len();
if abs_start > num_chars { if abs_start > num_chars {
0 0
} else { } else {
@ -763,9 +768,12 @@ mod string_functions {
return Dynamic::UNIT; return Dynamic::UNIT;
} }
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let index = index as usize;
string string
.chars() .chars()
.nth(index as usize) .nth(index)
.map_or_else(|| Dynamic::UNIT, Into::into) .map_or_else(|| Dynamic::UNIT, Into::into)
} else { } else {
// Count from end if negative // Count from end if negative
@ -775,10 +783,13 @@ mod string_functions {
return Dynamic::UNIT; return Dynamic::UNIT;
} }
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let abs_index = abs_index as usize;
string string
.chars() .chars()
.rev() .rev()
.nth((abs_index as usize) - 1) .nth(abs_index - 1)
.map_or_else(|| Dynamic::UNIT, Into::into) .map_or_else(|| Dynamic::UNIT, Into::into)
} }
} }
@ -811,6 +822,7 @@ mod string_functions {
return; return;
} }
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let index = index as usize; let index = index as usize;
*string = string *string = string
@ -825,6 +837,7 @@ mod string_functions {
return; return;
} }
#[allow(clippy::cast_possible_truncation)]
let abs_index = abs_index as usize; let abs_index = abs_index as usize;
let string_len = string.chars().count(); let string_len = string.chars().count();
@ -894,13 +907,14 @@ mod string_functions {
/// ///
/// print(text.sub_string(-8, 3)); // prints ", w" /// print(text.sub_string(-8, 3)); // prints ", w"
/// ``` /// ```
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
pub fn sub_string( pub fn sub_string(
ctx: NativeCallContext, ctx: NativeCallContext,
string: &str, string: &str,
start: INT, start: INT,
len: INT, len: INT,
) -> ImmutableString { ) -> ImmutableString {
if string.is_empty() { if string.is_empty() || len <= 0 {
return ctx.engine().const_empty_string(); return ctx.engine().const_empty_string();
} }
@ -915,8 +929,11 @@ mod string_functions {
return ctx.engine().const_empty_string(); return ctx.engine().const_empty_string();
} }
#[allow(clippy::cast_possible_truncation)]
let abs_start = abs_start as usize; let abs_start = abs_start as usize;
chars.extend(string.chars()); chars.extend(string.chars());
if abs_start > chars.len() { if abs_start > chars.len() {
0 0
} else { } else {
@ -932,10 +949,12 @@ mod string_functions {
chars.extend(string.chars()); chars.extend(string.chars());
} }
let len = if offset + len as usize > chars.len() { let len = len.min(MAX_USIZE_INT) as usize;
let len = if offset + len > chars.len() {
chars.len() - offset chars.len() - offset
} else { } else {
len as usize len
}; };
chars chars
@ -988,10 +1007,10 @@ mod string_functions {
/// print(text); // prints "llo, w" /// 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(ctx: NativeCallContext, 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(ctx, string, start, end - start);
} }
/// Remove all characters from the string except those within an inclusive `range`. /// Remove all characters from the string except those within an inclusive `range`.
/// ///
@ -1005,10 +1024,14 @@ mod string_functions {
/// print(text); // prints "llo, wo" /// 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(
ctx: NativeCallContext,
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(ctx, string, start, end - start + 1);
} }
/// Remove all characters from the string except those within a range. /// Remove all characters from the string except those within a range.
@ -1033,10 +1056,15 @@ mod string_functions {
/// print(text); // prints ", w" /// print(text); // prints ", w"
/// ``` /// ```
#[rhai_fn(name = "crop")] #[rhai_fn(name = "crop")]
pub fn crop(string: &mut ImmutableString, start: INT, len: INT) { #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
pub fn crop(ctx: NativeCallContext, string: &mut ImmutableString, start: INT, len: INT) {
if string.is_empty() { if string.is_empty() {
return; return;
} }
if len <= 0 {
*string = ctx.engine().const_empty_string();
return;
}
let mut chars = StaticVec::with_capacity(string.len()); let mut chars = StaticVec::with_capacity(string.len());
@ -1051,7 +1079,9 @@ mod string_functions {
} }
let abs_start = abs_start as usize; let abs_start = abs_start as usize;
chars.extend(string.chars()); chars.extend(string.chars());
if abs_start > chars.len() { if abs_start > chars.len() {
0 0
} else { } else {
@ -1068,10 +1098,12 @@ mod string_functions {
chars.extend(string.chars()); chars.extend(string.chars());
} }
let len = if offset + len as usize > chars.len() { let len = len.min(MAX_USIZE_INT) as usize;
let len = if offset + len > chars.len() {
chars.len() - offset chars.len() - offset
} else { } else {
len as usize len
}; };
let copy = string.make_mut(); let copy = string.make_mut();
@ -1098,8 +1130,12 @@ mod string_functions {
/// print(text); // prints "ld!" /// 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(
crop(string, start, string.len() as INT); ctx: NativeCallContext,
string: &mut ImmutableString,
start: INT,
) {
crop(ctx, string, start, string.len() as INT);
} }
/// Replace all occurrences of the specified sub-string in the string with another string. /// Replace all occurrences of the specified sub-string in the string with another string.
@ -1219,6 +1255,7 @@ mod string_functions {
if len <= 0 { if len <= 0 {
return Ok(()); return Ok(());
} }
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let len = len.min(MAX_USIZE_INT) as usize; let len = len.min(MAX_USIZE_INT) as usize;
let _ctx = ctx; let _ctx = ctx;
@ -1275,6 +1312,7 @@ mod string_functions {
if len <= 0 { if len <= 0 {
return Ok(()); return Ok(());
} }
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let len = len.min(MAX_USIZE_INT) as usize; let len = len.min(MAX_USIZE_INT) as usize;
let _ctx = ctx; let _ctx = ctx;
@ -1339,6 +1377,7 @@ mod string_functions {
/// print(text.split(-99)); // prints ["", "hello, world!"] /// print(text.split(-99)); // prints ["", "hello, world!"]
/// ``` /// ```
#[rhai_fn(name = "split")] #[rhai_fn(name = "split")]
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
pub fn split_at(ctx: NativeCallContext, string: &mut ImmutableString, index: INT) -> Array { pub fn split_at(ctx: NativeCallContext, string: &mut ImmutableString, index: INT) -> Array {
if index <= 0 { if index <= 0 {
let abs_index = index.unsigned_abs(); let abs_index = index.unsigned_abs();
@ -1349,9 +1388,9 @@ mod string_functions {
string.as_str().into(), string.as_str().into(),
]; ];
} }
let abs_index = abs_index as usize; let abs_index = abs_index as usize;
let num_chars = string.chars().count(); let num_chars = string.chars().count();
if abs_index > num_chars { if abs_index > num_chars {
vec![ vec![
ctx.engine().const_empty_string().into(), ctx.engine().const_empty_string().into(),
@ -1432,7 +1471,11 @@ mod string_functions {
/// print(text.split("ll", 2)); // prints ["he", "o, world! hello, foo!"] /// print(text.split("ll", 2)); // prints ["he", "o, world! hello, foo!"]
/// ``` /// ```
#[rhai_fn(name = "split")] #[rhai_fn(name = "split")]
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
pub fn splitn(string: &str, delimiter: &str, segments: INT) -> Array { pub fn splitn(string: &str, delimiter: &str, segments: INT) -> Array {
if segments < 1 {
return [string.into()].into();
}
let segments = segments.min(MAX_USIZE_INT) as usize; let segments = segments.min(MAX_USIZE_INT) as usize;
let pieces: usize = if segments < 1 { 1 } else { segments }; let pieces: usize = if segments < 1 { 1 } else { segments };
string.splitn(pieces, delimiter).map(Into::into).collect() string.splitn(pieces, delimiter).map(Into::into).collect()
@ -1463,7 +1506,11 @@ mod string_functions {
/// print(text.split('l', 3)); // prints ["he", "", "o, world! hello, foo!"] /// print(text.split('l', 3)); // prints ["he", "", "o, world! hello, foo!"]
/// ``` /// ```
#[rhai_fn(name = "split")] #[rhai_fn(name = "split")]
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
pub fn splitn_char(string: &str, delimiter: char, segments: INT) -> Array { pub fn splitn_char(string: &str, delimiter: char, segments: INT) -> Array {
if segments < 1 {
return [string.into()].into();
}
let segments = segments.min(MAX_USIZE_INT) as usize; let segments = segments.min(MAX_USIZE_INT) as usize;
let pieces: usize = if segments < 1 { 1 } else { segments }; let pieces: usize = if segments < 1 { 1 } else { segments };
string.splitn(pieces, delimiter).map(Into::into).collect() string.splitn(pieces, delimiter).map(Into::into).collect()
@ -1495,7 +1542,11 @@ mod string_functions {
/// print(text.split_rev("ll", 2)); // prints ["o, foo!", "hello, world! he"] /// print(text.split_rev("ll", 2)); // prints ["o, foo!", "hello, world! he"]
/// ``` /// ```
#[rhai_fn(name = "split_rev")] #[rhai_fn(name = "split_rev")]
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
pub fn rsplitn(string: &str, delimiter: &str, segments: INT) -> Array { pub fn rsplitn(string: &str, delimiter: &str, segments: INT) -> Array {
if segments < 1 {
return [string.into()].into();
}
let segments = segments.min(MAX_USIZE_INT) as usize; let segments = segments.min(MAX_USIZE_INT) as usize;
let pieces: usize = if segments < 1 { 1 } else { segments }; let pieces: usize = if segments < 1 { 1 } else { segments };
string.rsplitn(pieces, delimiter).map(Into::into).collect() string.rsplitn(pieces, delimiter).map(Into::into).collect()
@ -1527,7 +1578,11 @@ mod string_functions {
/// print(text.split('l', 3)); // prints ["o, foo!", "", "hello, world! he" /// print(text.split('l', 3)); // prints ["o, foo!", "", "hello, world! he"
/// ``` /// ```
#[rhai_fn(name = "split_rev")] #[rhai_fn(name = "split_rev")]
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
pub fn rsplitn_char(string: &str, delimiter: char, segments: INT) -> Array { pub fn rsplitn_char(string: &str, delimiter: char, segments: INT) -> Array {
if segments < 1 {
return [string.into()].into();
}
let segments = segments.min(MAX_USIZE_INT) as usize; let segments = segments.min(MAX_USIZE_INT) as usize;
let pieces: usize = if segments < 1 { 1 } else { segments }; let pieces: usize = if segments < 1 { 1 } else { segments };
string.rsplitn(pieces, delimiter).map(Into::into).collect() string.rsplitn(pieces, delimiter).map(Into::into).collect()

View File

@ -114,11 +114,12 @@ mod time_functions {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub mod float_functions { pub mod float_functions {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
fn add_impl(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf<Instant> { fn add_impl(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf<Instant> {
if seconds < 0.0 { if seconds < 0.0 {
subtract_impl(timestamp, -seconds) subtract_impl(timestamp, -seconds)
} else if cfg!(not(feature = "unchecked")) { } else if cfg!(not(feature = "unchecked")) {
if seconds > (INT::MAX as FLOAT) { if seconds > (INT::MAX as FLOAT).min(u64::MAX as FLOAT) {
Err(make_arithmetic_err(format!( Err(make_arithmetic_err(format!(
"Integer overflow for timestamp add: {seconds}" "Integer overflow for timestamp add: {seconds}"
))) )))
@ -135,20 +136,21 @@ mod time_functions {
Ok(timestamp + Duration::from_millis((seconds * 1000.0) as u64)) Ok(timestamp + Duration::from_millis((seconds * 1000.0) as u64))
} }
} }
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
fn subtract_impl(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf<Instant> { fn subtract_impl(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf<Instant> {
if seconds < 0.0 { if seconds < 0.0 {
add_impl(timestamp, -seconds) add_impl(timestamp, -seconds)
} else if cfg!(not(feature = "unchecked")) { } else if cfg!(not(feature = "unchecked")) {
if seconds > (INT::MAX as FLOAT) { if seconds > (INT::MAX as FLOAT).min(u64::MAX as FLOAT) {
Err(make_arithmetic_err(format!( Err(make_arithmetic_err(format!(
"Integer overflow for timestamp add: {seconds}" "Integer overflow for timestamp subtract: {seconds}"
))) )))
} else { } else {
timestamp timestamp
.checked_sub(Duration::from_millis((seconds * 1000.0) as u64)) .checked_sub(Duration::from_millis((seconds * 1000.0) as u64))
.ok_or_else(|| { .ok_or_else(|| {
make_arithmetic_err(format!( make_arithmetic_err(format!(
"Timestamp overflow when adding {seconds} second(s)" "Timestamp overflow when subtracting {seconds} second(s)"
)) ))
}) })
} }
@ -182,6 +184,7 @@ mod time_functions {
} }
fn add_impl(timestamp: Instant, seconds: INT) -> RhaiResultOf<Instant> { fn add_impl(timestamp: Instant, seconds: INT) -> RhaiResultOf<Instant> {
#[allow(clippy::cast_sign_loss)]
if seconds < 0 { if seconds < 0 {
subtract_impl(timestamp, -seconds) subtract_impl(timestamp, -seconds)
} else if cfg!(not(feature = "unchecked")) { } else if cfg!(not(feature = "unchecked")) {
@ -197,6 +200,7 @@ mod time_functions {
} }
} }
fn subtract_impl(timestamp: Instant, seconds: INT) -> RhaiResultOf<Instant> { fn subtract_impl(timestamp: Instant, seconds: INT) -> RhaiResultOf<Instant> {
#[allow(clippy::cast_sign_loss)]
if seconds < 0 { if seconds < 0 {
add_impl(timestamp, -seconds) add_impl(timestamp, -seconds)
} else if cfg!(not(feature = "unchecked")) { } else if cfg!(not(feature = "unchecked")) {
@ -204,7 +208,7 @@ mod time_functions {
.checked_sub(Duration::from_secs(seconds as u64)) .checked_sub(Duration::from_secs(seconds as u64))
.ok_or_else(|| { .ok_or_else(|| {
make_arithmetic_err(format!( make_arithmetic_err(format!(
"Timestamp overflow when adding {seconds} second(s)" "Timestamp overflow when subtracting {seconds} second(s)"
)) ))
}) })
} else { } else {

View File

@ -3,9 +3,9 @@
use crate::api::events::VarDefInfo; use crate::api::events::VarDefInfo;
use crate::api::options::LangOptions; use crate::api::options::LangOptions;
use crate::ast::{ use crate::ast::{
ASTFlags, BinaryExpr, CaseBlocksList, ConditionalExpr, Expr, FnCallExpr, FnCallHashes, Ident, ASTFlags, BinaryExpr, CaseBlocksList, ConditionalExpr, Expr, FlowControl, FnCallExpr,
Namespace, OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, StmtBlockContainer, FnCallHashes, Ident, Namespace, OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock,
SwitchCasesCollection, TryCatchBlock, StmtBlockContainer, SwitchCasesCollection,
}; };
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS}; use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
use crate::eval::{Caches, GlobalRuntimeState}; use crate::eval::{Caches, GlobalRuntimeState};
@ -26,6 +26,7 @@ use bitflags::bitflags;
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
convert::TryFrom,
fmt, fmt,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
num::{NonZeroU8, NonZeroUsize}, num::{NonZeroU8, NonZeroUsize},
@ -147,7 +148,7 @@ impl<'e, 's> ParseState<'e, 's> {
.stack .stack
.as_deref() .as_deref()
.into_iter() .into_iter()
.flat_map(|s| s.iter_rev_raw()) .flat_map(Scope::iter_rev_raw)
.enumerate() .enumerate()
.find(|&(.., (n, ..))| { .find(|&(.., (n, ..))| {
if n == SCOPE_SEARCH_BARRIER_MARKER { if n == SCOPE_SEARCH_BARRIER_MARKER {
@ -363,7 +364,7 @@ pub fn make_anonymous_fn(hash: u64) -> Identifier {
use std::fmt::Write; use std::fmt::Write;
let mut buf = Identifier::new_const(); let mut buf = Identifier::new_const();
write!(&mut buf, "{}{:016x}", crate::engine::FN_ANONYMOUS, hash).unwrap(); write!(&mut buf, "{}{hash:016x}", crate::engine::FN_ANONYMOUS).unwrap();
buf buf
} }
@ -1198,14 +1199,14 @@ impl Engine {
}; };
let (action_expr, need_comma) = let (action_expr, need_comma) =
if !settings.has_flag(ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS) { if settings.has_flag(ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS) {
(self.parse_expr(input, state, lib, settings)?, true)
} else {
let stmt = self.parse_stmt(input, state, lib, settings)?; let stmt = self.parse_stmt(input, state, lib, settings)?;
let need_comma = !stmt.is_self_terminated(); let need_comma = !stmt.is_self_terminated();
let stmt_block: StmtBlock = stmt.into(); let stmt_block: StmtBlock = stmt.into();
(Expr::Stmt(stmt_block.into()), need_comma) (Expr::Stmt(stmt_block.into()), need_comma)
} else {
(self.parse_expr(input, state, lib, settings)?, true)
}; };
let has_condition = !matches!(condition, Expr::BoolConstant(true, ..)); let has_condition = !matches!(condition, Expr::BoolConstant(true, ..));
@ -1293,8 +1294,8 @@ impl Engine {
let cases = SwitchCasesCollection { let cases = SwitchCasesCollection {
expressions, expressions,
cases, cases,
def_case,
ranges, ranges,
def_case,
}; };
Ok(Stmt::Switch((item, cases).into(), settings.pos)) Ok(Stmt::Switch((item, cases).into(), settings.pos))
@ -1636,13 +1637,9 @@ impl Engine {
); );
} }
let short_index = index.and_then(|x| { let short_index = index
if x.get() <= u8::MAX as usize { .and_then(|x| u8::try_from(x.get()).ok())
NonZeroU8::new(x.get() as u8) .and_then(NonZeroU8::new);
} else {
None
}
});
let name = state.get_interned_string(*s); let name = state.get_interned_string(*s);
Expr::Variable((index, ns, 0, name).into(), short_index, settings.pos) Expr::Variable((index, ns, 0, name).into(), short_index, settings.pos)
} }
@ -2023,16 +2020,16 @@ impl Engine {
} }
} }
let op_info = if op != NO_TOKEN { let op_info = if op == NO_TOKEN {
OpAssignment::new_op_assignment_from_token(op, op_pos)
} else {
OpAssignment::new_assignment(op_pos) OpAssignment::new_assignment(op_pos)
} else {
OpAssignment::new_op_assignment_from_token(op, op_pos)
}; };
match lhs { match lhs {
// const_expr = rhs // const_expr = rhs
ref expr if expr.is_constant() => { ref expr if expr.is_constant() => {
Err(PERR::AssignmentToConstant("".into()).into_err(lhs.start_position())) Err(PERR::AssignmentToConstant(String::new()).into_err(lhs.start_position()))
} }
// var (non-indexed) = rhs // var (non-indexed) = rhs
Expr::Variable(ref x, None, _) if x.0.is_none() => { Expr::Variable(ref x, None, _) if x.0.is_none() => {
@ -2384,7 +2381,7 @@ impl Engine {
let not_base = FnCallExpr { let not_base = FnCallExpr {
namespace: Namespace::NONE, namespace: Namespace::NONE,
name: state.get_interned_string(op), name: state.get_interned_string(op),
hashes: FnCallHashes::from_native(calc_fn_hash(None, op, 1).into()), hashes: FnCallHashes::from_native(calc_fn_hash(None, op, 1)),
args, args,
op_token: Token::Bang, op_token: Token::Bang,
capture_parent_scope: false, capture_parent_scope: false,
@ -2652,14 +2649,14 @@ impl Engine {
// if guard { if_body } // if guard { if_body }
ensure_not_statement_expr(input, "a boolean")?; ensure_not_statement_expr(input, "a boolean")?;
let guard = self let expr = self
.parse_expr(input, state, lib, settings)? .parse_expr(input, state, lib, settings)?
.ensure_bool_expr()?; .ensure_bool_expr()?;
ensure_not_assignment(input)?; ensure_not_assignment(input)?;
let if_body = self.parse_block(input, state, lib, settings)?; let body = self.parse_block(input, state, lib, settings)?.into();
// if guard { if_body } else ... // if guard { if_body } else ...
let else_body = if match_token(input, Token::Else).0 { let branch = if match_token(input, Token::Else).0 {
if let (Token::If, ..) = input.peek().expect(NEVER_ENDS) { if let (Token::If, ..) = input.peek().expect(NEVER_ENDS) {
// if guard { if_body } else if ... // if guard { if_body } else if ...
self.parse_if(input, state, lib, settings)? self.parse_if(input, state, lib, settings)?
@ -2669,10 +2666,11 @@ impl Engine {
} }
} else { } else {
Stmt::Noop(Position::NONE) Stmt::Noop(Position::NONE)
}; }
.into();
Ok(Stmt::If( Ok(Stmt::If(
(guard, if_body.into(), else_body.into()).into(), FlowControl { expr, body, branch }.into(),
settings.pos, settings.pos,
)) ))
} }
@ -2688,7 +2686,7 @@ impl Engine {
let mut settings = settings.level_up()?; let mut settings = settings.level_up()?;
// while|loops ... // while|loops ...
let (guard, token_pos) = match input.next().expect(NEVER_ENDS) { let (expr, token_pos) = match input.next().expect(NEVER_ENDS) {
(Token::While, pos) => { (Token::While, pos) => {
ensure_not_statement_expr(input, "a boolean")?; ensure_not_statement_expr(input, "a boolean")?;
let expr = self let expr = self
@ -2703,9 +2701,13 @@ impl Engine {
settings.pos = token_pos; settings.pos = token_pos;
settings.flags |= ParseSettingFlags::BREAKABLE; settings.flags |= ParseSettingFlags::BREAKABLE;
let body = self.parse_block(input, state, lib, settings)?; let body = self.parse_block(input, state, lib, settings)?.into();
let branch = StmtBlock::NONE;
Ok(Stmt::While((guard, body.into()).into(), settings.pos)) Ok(Stmt::While(
FlowControl { expr, body, branch }.into(),
settings.pos,
))
} }
/// Parse a do loop. /// Parse a do loop.
@ -2725,7 +2727,7 @@ impl Engine {
// do { body } [while|until] guard // do { body } [while|until] guard
let body = self.parse_block(input, state, lib, settings)?; let body = self.parse_block(input, state, lib, settings)?.into();
let negated = match input.next().expect(NEVER_ENDS) { let negated = match input.next().expect(NEVER_ENDS) {
(Token::While, ..) => ASTFlags::NONE, (Token::While, ..) => ASTFlags::NONE,
@ -2743,12 +2745,18 @@ impl Engine {
} }
ensure_not_statement_expr(input, "a boolean")?; ensure_not_statement_expr(input, "a boolean")?;
let guard = self let expr = self
.parse_expr(input, state, lib, settings)? .parse_expr(input, state, lib, settings)?
.ensure_bool_expr()?; .ensure_bool_expr()?;
ensure_not_assignment(input)?; ensure_not_assignment(input)?;
Ok(Stmt::Do((guard, body.into()).into(), negated, settings.pos)) let branch = StmtBlock::NONE;
Ok(Stmt::Do(
FlowControl { expr, body, branch }.into(),
negated,
settings.pos,
))
} }
/// Parse a for loop. /// Parse a for loop.
@ -2840,12 +2848,14 @@ impl Engine {
}; };
settings.flags |= ParseSettingFlags::BREAKABLE; settings.flags |= ParseSettingFlags::BREAKABLE;
let body = self.parse_block(input, state, lib, settings)?; let body = self.parse_block(input, state, lib, settings)?.into();
state.stack.as_deref_mut().unwrap().rewind(prev_stack_len); state.stack.as_deref_mut().unwrap().rewind(prev_stack_len);
let branch = StmtBlock::NONE;
Ok(Stmt::For( Ok(Stmt::For(
Box::new((loop_var, counter_var, expr, body.into())), Box::new((loop_var, counter_var, FlowControl { expr, body, branch })),
settings.pos, settings.pos,
)) ))
} }
@ -3480,7 +3490,7 @@ impl Engine {
settings.pos = eat_token(input, Token::Try); settings.pos = eat_token(input, Token::Try);
// try { try_block } // try { try_block }
let try_block = self.parse_block(input, state, lib, settings)?; let body = self.parse_block(input, state, lib, settings)?.into();
// try { try_block } catch // try { try_block } catch
let (matched, catch_pos) = match_token(input, Token::Catch); let (matched, catch_pos) = match_token(input, Token::Catch);
@ -3519,20 +3529,23 @@ impl Engine {
}; };
// try { try_block } catch ( var ) { catch_block } // try { try_block } catch ( var ) { catch_block }
let catch_block = self.parse_block(input, state, lib, settings)?; let branch = self.parse_block(input, state, lib, settings)?.into();
if !catch_var.is_empty() { let expr = if !catch_var.is_empty() {
// Remove the error variable from the stack // Remove the error variable from the stack
state.stack.as_deref_mut().unwrap().pop(); state.stack.as_deref_mut().unwrap().pop();
}
Expr::Variable(
(None, Namespace::default(), 0, catch_var.name).into(),
None,
catch_var.pos,
)
} else {
Expr::Unit(catch_var.pos)
};
Ok(Stmt::TryCatch( Ok(Stmt::TryCatch(
TryCatchBlock { FlowControl { body, expr, branch }.into(),
try_block: try_block.into(),
catch_var,
catch_block: catch_block.into(),
}
.into(),
settings.pos, settings.pos,
)) ))
} }
@ -3661,7 +3674,7 @@ impl Engine {
Some(n) if !is_func && n.get() <= u8::MAX as usize => NonZeroU8::new(n.get() as u8), Some(n) if !is_func && n.get() <= u8::MAX as usize => NonZeroU8::new(n.get() as u8),
_ => None, _ => None,
}; };
Expr::Variable((index, Default::default(), 0, name).into(), idx, pos) Expr::Variable((index, Namespace::default(), 0, name).into(), idx, pos)
})); }));
let expr = FnCallExpr { let expr = FnCallExpr {

View File

@ -98,6 +98,7 @@ impl Serialize for Scope<'_> {
pub is_constant: bool, pub is_constant: bool,
} }
#[allow(clippy::trivially_copy_pass_by_ref)]
fn is_false(value: &bool) -> bool { fn is_false(value: &bool) -> bool {
!value !value
} }

View File

@ -21,12 +21,15 @@ use std::{
#[derive(Debug, Clone, Eq, PartialEq, Default, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Default, Hash)]
pub struct TokenizerControlBlock { pub struct TokenizerControlBlock {
/// Is the current tokenizer position within an interpolated text string? /// Is the current tokenizer position within an interpolated text string?
///
/// This flag allows switching the tokenizer back to _text_ parsing after an interpolation stream. /// This flag allows switching the tokenizer back to _text_ parsing after an interpolation stream.
pub is_within_text: bool, pub is_within_text: bool,
/// Global comments. /// Global comments.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
pub global_comments: String, pub global_comments: String,
/// Whitespace-compressed version of the script (if any). /// Whitespace-compressed version of the script (if any).
///
/// Set to `Some` in order to collect a compressed script.
pub compressed: Option<String>, pub compressed: Option<String>,
} }
@ -882,7 +885,9 @@ pub struct TokenizeState {
pub include_comments: bool, pub include_comments: bool,
/// Is the current tokenizer position within the text stream of an interpolated string? /// Is the current tokenizer position within the text stream of an interpolated string?
pub is_within_text_terminated_by: Option<char>, pub is_within_text_terminated_by: Option<char>,
/// Last token /// Textual syntax of the current token, if any.
///
/// Set to `Some` to begin tracking this information.
pub last_token: Option<SmartString>, pub last_token: Option<SmartString>,
} }
@ -961,10 +966,10 @@ pub fn parse_string_literal(
let mut skip_whitespace_until = 0; let mut skip_whitespace_until = 0;
state.is_within_text_terminated_by = Some(termination_char); state.is_within_text_terminated_by = Some(termination_char);
state.last_token.as_mut().map(|last| { if let Some(ref mut last) = state.last_token {
last.clear(); last.clear();
last.push(termination_char); last.push(termination_char);
}); }
loop { loop {
assert!( assert!(
@ -983,7 +988,7 @@ pub fn parse_string_literal(
break; break;
} }
None if allow_line_continuation && !escape.is_empty() => { None if allow_line_continuation && !escape.is_empty() => {
assert_eq!(escape, "\\", "unexpected escape {} at end of line", escape); assert_eq!(escape, "\\", "unexpected escape {escape} at end of line");
pos.advance(); pos.advance();
break; break;
} }
@ -994,7 +999,9 @@ pub fn parse_string_literal(
} }
}; };
state.last_token.as_mut().map(|last| last.push(next_char)); if let Some(ref mut last) = state.last_token {
last.push(next_char);
}
// String interpolation? // String interpolation?
if allow_interpolation if allow_interpolation
@ -1015,10 +1022,9 @@ pub fn parse_string_literal(
// Double wrapper // Double wrapper
if stream.peek_next().map_or(false, |c| c == termination_char) { if stream.peek_next().map_or(false, |c| c == termination_char) {
eat_next(stream, pos); eat_next(stream, pos);
state if let Some(ref mut last) = state.last_token {
.last_token last.push(termination_char);
.as_mut() }
.map(|last| last.push(termination_char));
} else { } else {
state.is_within_text_terminated_by = None; state.is_within_text_terminated_by = None;
break; break;
@ -1075,9 +1081,11 @@ pub fn parse_string_literal(
.get_next() .get_next()
.ok_or_else(|| (LERR::MalformedEscapeSequence(seq.to_string()), *pos))?; .ok_or_else(|| (LERR::MalformedEscapeSequence(seq.to_string()), *pos))?;
state.last_token.as_mut().map(|last| last.push(c));
seq.push(c);
pos.advance(); pos.advance();
seq.push(c);
if let Some(ref mut last) = state.last_token {
last.push(c);
}
out_val *= 16; out_val *= 16;
out_val += c out_val += c
@ -1106,7 +1114,7 @@ pub fn parse_string_literal(
// Line continuation // Line continuation
'\n' if allow_line_continuation && !escape.is_empty() => { '\n' if allow_line_continuation && !escape.is_empty() => {
assert_eq!(escape, "\\", "unexpected escape {} at end of line", escape); assert_eq!(escape, "\\", "unexpected escape {escape} at end of line");
escape.clear(); escape.clear();
pos.new_line(); pos.new_line();
@ -1256,7 +1264,7 @@ fn get_next_token_inner(
state: &mut TokenizeState, state: &mut TokenizeState,
pos: &mut Position, pos: &mut Position,
) -> Option<(Token, Position)> { ) -> Option<(Token, Position)> {
state.last_token.as_mut().map(|last| last.clear()); state.last_token.as_mut().map(SmartString::clear);
// Still inside a comment? // Still inside a comment?
if state.comment_level > 0 { if state.comment_level > 0 {
@ -1338,7 +1346,7 @@ fn get_next_token_inner(
pos.advance(); pos.advance();
} }
// _ - cannot follow a decimal point // _ - cannot follow a decimal point
'_' => { NUMBER_SEPARATOR => {
stream.unget(next_char); stream.unget(next_char);
break; break;
} }
@ -1416,7 +1424,9 @@ fn get_next_token_inner(
negated_pos negated_pos
}); });
state.last_token.as_mut().map(|last| *last = result.clone()); if let Some(ref mut last) = state.last_token {
*last = result.clone();
}
// Parse number // Parse number
let token = radix_base.map_or_else( let token = radix_base.map_or_else(
@ -1471,17 +1481,11 @@ fn get_next_token_inner(
// letter or underscore ... // letter or underscore ...
#[cfg(not(feature = "unicode-xid-ident"))] #[cfg(not(feature = "unicode-xid-ident"))]
('a'..='z' | '_' | 'A'..='Z', ..) => { ('a'..='z' | '_' | 'A'..='Z', ..) => {
return Some( return Some(parse_identifier_token(stream, state, pos, start_pos, c));
parse_identifier_token(stream, state, pos, start_pos, c)
.unwrap_or_else(|err| (Token::LexError(err.into()), start_pos)),
);
} }
#[cfg(feature = "unicode-xid-ident")] #[cfg(feature = "unicode-xid-ident")]
(ch, ..) if unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' => { (ch, ..) if unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' => {
return Some( return Some(parse_identifier_token(stream, state, pos, start_pos, c));
parse_identifier_token(stream, state, pos, start_pos, c)
.unwrap_or_else(|err| (Token::LexError(err.into()), start_pos)),
);
} }
// " - string literal // " - string literal
@ -1966,40 +1970,42 @@ fn parse_identifier_token(
pos: &mut Position, pos: &mut Position,
start_pos: Position, start_pos: Position,
first_char: char, first_char: char,
) -> Result<(Token, Position), LexError> { ) -> (Token, Position) {
let mut identifier = SmartString::new_const(); let mut identifier = SmartString::new_const();
identifier.push(first_char); identifier.push(first_char);
state.last_token.as_mut().map(|last| { if let Some(ref mut last) = state.last_token {
last.clear(); last.clear();
last.push(first_char); last.push(first_char);
}); }
while let Some(next_char) = stream.peek_next() { while let Some(next_char) = stream.peek_next() {
match next_char { match next_char {
x if is_id_continue(x) => { x if is_id_continue(x) => {
eat_next(stream, pos); eat_next(stream, pos);
identifier.push(x); identifier.push(x);
state.last_token.as_mut().map(|last| last.push(x)); if let Some(ref mut last) = state.last_token {
last.push(x);
}
} }
_ => break, _ => break,
} }
} }
if let Some(token) = Token::lookup_symbol_from_syntax(&identifier) { if let Some(token) = Token::lookup_symbol_from_syntax(&identifier) {
return Ok((token, start_pos)); return (token, start_pos);
} }
if Token::is_reserved_keyword(&identifier) { if Token::is_reserved_keyword(&identifier) {
return Ok((Token::Reserved(Box::new(identifier)), start_pos)); return (Token::Reserved(Box::new(identifier)), start_pos);
} }
if !is_valid_identifier(&identifier) { if !is_valid_identifier(&identifier) {
return Ok(( return (
Token::LexError(LERR::MalformedIdentifier(identifier.to_string()).into()), Token::LexError(LERR::MalformedIdentifier(identifier.to_string()).into()),
start_pos, start_pos,
)); );
} }
Ok((Token::Identifier(identifier.into()), start_pos)) (Token::Identifier(identifier.into()), start_pos)
} }
/// Is a keyword allowed as a function? /// Is a keyword allowed as a function?
@ -2256,9 +2262,10 @@ impl<'a> Iterator for TokenIterator<'a> {
}; };
// Run the mapper, if any // Run the mapper, if any
let token = match self.token_mapper { let token = if let Some(func) = self.token_mapper {
Some(map_func) => map_func(token, pos, &self.state), func(token, pos, &self.state)
None => token, } else {
token
}; };
// Collect the compressed script, if needed // Collect the compressed script, if needed
@ -2285,21 +2292,19 @@ impl<'a> Iterator for TokenIterator<'a> {
buf = last_token.clone(); buf = last_token.clone();
} }
if !buf.is_empty() { if !buf.is_empty() && !compressed.is_empty() {
if !compressed.is_empty() { let cur = buf.chars().next().unwrap();
if cur == '_' || is_id_first_alphabetic(cur) || is_id_continue(cur) {
let prev = compressed.chars().last().unwrap(); let prev = compressed.chars().last().unwrap();
let cur = buf.chars().next().unwrap();
if (prev == '_' || is_id_first_alphabetic(prev) || is_id_continue(prev)) if prev == '_' || is_id_first_alphabetic(prev) || is_id_continue(prev) {
&& (cur == '_'
|| is_id_first_alphabetic(cur)
|| is_id_continue(cur))
{
compressed.push(' '); compressed.push(' ');
} }
} }
compressed.push_str(&buf);
} }
compressed.push_str(&buf);
} }
} }
} }

View File

@ -2055,6 +2055,7 @@ impl Dynamic {
/// ///
/// Shared values are _NOT_ scanned. /// Shared values are _NOT_ scanned.
#[inline] #[inline]
#[allow(clippy::only_used_in_recursion)]
pub fn deep_scan(&mut self, mut filter: impl FnMut(&mut Self)) { pub fn deep_scan(&mut self, mut filter: impl FnMut(&mut Self)) {
fn scan_inner(value: &mut Dynamic, filter: &mut impl FnMut(&mut Dynamic)) { fn scan_inner(value: &mut Dynamic, filter: &mut impl FnMut(&mut Dynamic)) {
match &mut value.0 { match &mut value.0 {

View File

@ -37,11 +37,11 @@ impl Hash for FnPtr {
self.curry.hash(state); self.curry.hash(state);
// Hash the shared [`EncapsulatedEnviron`] by hashing its shared pointer. // Hash the shared [`EncapsulatedEnviron`] by hashing its shared pointer.
self.environ.as_ref().map(|e| Shared::as_ptr(e)).hash(state); self.environ.as_ref().map(Shared::as_ptr).hash(state);
// Hash the linked [`ScriptFnDef`][crate::ast::ScriptFnDef] by hashing its shared pointer. // Hash the linked [`ScriptFnDef`][crate::ast::ScriptFnDef] by hashing its shared pointer.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
self.fn_def.as_ref().map(|f| Shared::as_ptr(f)).hash(state); self.fn_def.as_ref().map(Shared::as_ptr).hash(state);
} }
} }
@ -124,7 +124,6 @@ impl FnPtr {
} }
/// Get the curried arguments. /// Get the curried arguments.
#[inline(always)] #[inline(always)]
#[must_use]
pub fn curry(&self) -> &[Dynamic] { pub fn curry(&self) -> &[Dynamic] {
self.curry.as_ref() self.curry.as_ref()
} }
@ -304,15 +303,14 @@ impl FnPtr {
global.level += 1; global.level += 1;
let caches = &mut crate::eval::Caches::new(); let caches = &mut crate::eval::Caches::new();
let mut this_ptr = this_ptr;
return context.engine().call_script_fn( return context.engine().call_script_fn(
global, global,
caches, caches,
&mut crate::Scope::new(), &mut crate::Scope::new(),
this_ptr.as_deref_mut(), this_ptr,
self.encapsulated_environ(), self.encapsulated_environ(),
&fn_def, fn_def,
args, args,
true, true,
context.position(), context.position(),
@ -361,6 +359,9 @@ impl FnPtr {
/// Make a call to a function pointer with either a specified number of arguments, or with extra /// Make a call to a function pointer with either a specified number of arguments, or with extra
/// arguments attached. /// arguments attached.
/// ///
/// If `this_ptr` is provided, it is first provided to script-defined functions bound to `this`.
/// When an appropriate function is not found, it is then removed and mapped to the first parameter.
///
/// This is useful for calling predicate closures within an iteration loop where the extra argument /// This is useful for calling predicate closures within an iteration loop where the extra argument
/// is the current element's index. /// is the current element's index.
/// ///
@ -382,6 +383,9 @@ impl FnPtr {
/// or with extra arguments attached. /// or with extra arguments attached.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///
/// If `this_ptr` is provided, it is first provided to script-defined functions bound to `this`.
/// When an appropriate function is not found, it is then removed and mapped to the first parameter.
///
/// This is useful for calling predicate closures within an iteration loop where the extra /// This is useful for calling predicate closures within an iteration loop where the extra
/// argument is the current element's index. /// argument is the current element's index.
/// ///
@ -411,24 +415,46 @@ impl FnPtr {
) -> RhaiResult { ) -> RhaiResult {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
if let Some(arity) = self.fn_def().map(|f| f.params.len()) { if let Some(arity) = self.fn_def().map(|f| f.params.len()) {
if arity == N + 1 && this_ptr.is_some() {
let mut args = FnArgsVec::with_capacity(items.len() + 1);
args.push(this_ptr.as_mut().unwrap().clone());
args.extend(items);
return self.call_raw(ctx, None, args);
}
if arity == N { if arity == N {
return self.call_raw(&ctx, None, items); return self.call_raw(ctx, this_ptr, items);
} }
if arity == N + E { if arity == N + E {
let mut items2 = FnArgsVec::with_capacity(items.len() + extras.len()); let mut items2 = FnArgsVec::with_capacity(items.len() + extras.len());
items2.extend(IntoIterator::into_iter(items)); items2.extend(IntoIterator::into_iter(items));
items2.extend(IntoIterator::into_iter(extras)); items2.extend(IntoIterator::into_iter(extras));
return self.call_raw(&ctx, this_ptr, items2); return self.call_raw(ctx, this_ptr, items2);
} }
} }
self.call_raw(&ctx, this_ptr.as_deref_mut(), items.clone()) self.call_raw(ctx, this_ptr.as_deref_mut(), items.clone())
.or_else(|err| match *err {
ERR::ErrorFunctionNotFound(sig, ..)
if this_ptr.is_some() && sig.starts_with(self.fn_name()) =>
{
let mut args = FnArgsVec::with_capacity(items.len() + 1);
args.push(this_ptr.as_mut().unwrap().clone());
args.extend(IntoIterator::into_iter(items.clone()));
self.call_raw(ctx, this_ptr.as_deref_mut(), args)
}
_ => Err(err),
})
.or_else(|err| match *err { .or_else(|err| match *err {
ERR::ErrorFunctionNotFound(sig, ..) if sig.starts_with(self.fn_name()) => { ERR::ErrorFunctionNotFound(sig, ..) if sig.starts_with(self.fn_name()) => {
let mut items2 = FnArgsVec::with_capacity(items.len() + extras.len()); let mut args = FnArgsVec::with_capacity(
items2.extend(IntoIterator::into_iter(items)); items.len() + extras.len() + if this_ptr.is_some() { 1 } else { 0 },
items2.extend(IntoIterator::into_iter(extras)); );
self.call_raw(&ctx, this_ptr, items2) if let Some(ref mut this_ptr) = this_ptr {
args.push(this_ptr.clone());
}
args.extend(IntoIterator::into_iter(items));
args.extend(IntoIterator::into_iter(extras));
self.call_raw(ctx, this_ptr, args)
} }
_ => Err(err), _ => Err(err),
}) })

View File

@ -220,7 +220,7 @@ impl fmt::Display for ParseErrorType {
Self::ModuleUndefined(s) => write!(f, "Undefined module: {s}"), Self::ModuleUndefined(s) => write!(f, "Undefined module: {s}"),
Self::MismatchedType(r, a) => write!(f, "Expecting {r}, not {a}"), Self::MismatchedType(r, a) => write!(f, "Expecting {r}, not {a}"),
Self::ExprExpected(s) => write!(f, "Expecting {} expression", s), Self::ExprExpected(s) => write!(f, "Expecting {s} expression"),
Self::MissingToken(token, s) => write!(f, "Expecting '{token}' {s}"), Self::MissingToken(token, s) => write!(f, "Expecting '{token}' {s}"),
Self::MissingSymbol(s) if s.is_empty() => f.write_str("Expecting a symbol"), Self::MissingSymbol(s) if s.is_empty() => f.write_str("Expecting a symbol"),

View File

@ -78,9 +78,7 @@ impl Position {
assert!(!self.is_none(), "cannot advance Position::none"); assert!(!self.is_none(), "cannot advance Position::none");
// Advance up to maximum position // Advance up to maximum position
if self.pos < u16::MAX { self.pos = self.pos.saturating_add(1);
self.pos += 1;
}
} }
/// Go backwards by one character position. /// Go backwards by one character position.
/// ///
@ -136,7 +134,7 @@ impl Position {
#[inline] #[inline]
pub(crate) fn debug_print(self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { pub(crate) fn debug_print(self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.is_none() { if !self.is_none() {
write!(_f, " @ {:?}", self)?; write!(_f, " @ {self:?}")?;
} }
Ok(()) Ok(())
} }
@ -166,12 +164,10 @@ impl fmt::Debug for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_none() { if self.is_none() {
f.write_str("none") f.write_str("none")
} else if self.is_beginning_of_line() {
write!(f, "{}", self.line)
} else { } else {
if self.is_beginning_of_line() { write!(f, "{}:{}", self.line, self.pos)
write!(f, "{}", self.line)
} else {
write!(f, "{}:{}", self.line, self.pos)
}
} }
} }
} }
@ -261,10 +257,10 @@ impl fmt::Display for Span {
match (self.start(), self.end()) { match (self.start(), self.end()) {
(Position::NONE, Position::NONE) => write!(_f, "{:?}", Position::NONE), (Position::NONE, Position::NONE) => write!(_f, "{:?}", Position::NONE),
(Position::NONE, end) => write!(_f, "..{:?}", end), (Position::NONE, end) => write!(_f, "..{end:?}"),
(start, Position::NONE) => write!(_f, "{:?}", start), (start, Position::NONE) => write!(_f, "{start:?}"),
(start, end) if start.line() != end.line() => { (start, end) if start.line() != end.line() => {
write!(_f, "{:?}-{:?}", start, end) write!(_f, "{start:?}-{end:?}")
} }
(start, end) => write!( (start, end) => write!(
_f, _f,

View File

@ -696,7 +696,6 @@ impl Scope<'_> {
/// ///
/// Panics if the index is out of bounds. /// Panics if the index is out of bounds.
#[inline] #[inline]
#[must_use]
pub(crate) fn get_mut_by_index(&mut self, index: usize) -> &mut Dynamic { pub(crate) fn get_mut_by_index(&mut self, index: usize) -> &mut Dynamic {
self.values.get_mut(index).unwrap() self.values.get_mut(index).unwrap()
} }

View File

@ -298,6 +298,7 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("[1].map(|x| x + 41)[0]")?, 42); assert_eq!(engine.eval::<INT>("[1].map(|x| x + 41)[0]")?, 42);
assert_eq!(engine.eval::<INT>("[1].map(|| this + 41)[0]")?, 42);
assert_eq!(engine.eval::<INT>("([1].map(|x| x + 41))[0]")?, 42); assert_eq!(engine.eval::<INT>("([1].map(|x| x + 41))[0]")?, 42);
assert_eq!( assert_eq!(
engine.eval::<INT>("let c = 40; let y = 1; [1].map(|x, i| c + x + y + i)[0]")?, engine.eval::<INT>("let c = 40; let y = 1; [1].map(|x, i| c + x + y + i)[0]")?,
@ -316,6 +317,18 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
[3] [3]
); );
assert_eq!(
engine
.eval::<Dynamic>(
"
let x = [1, 2, 3];
x.filter(|| this > 2)
"
)?
.into_typed_array::<INT>()?,
[3]
);
assert_eq!( assert_eq!(
engine engine
.eval::<Dynamic>( .eval::<Dynamic>(
@ -340,6 +353,18 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
[2, 4, 6] [2, 4, 6]
); );
assert_eq!(
engine
.eval::<Dynamic>(
"
let x = [1, 2, 3];
x.map(|| this * 2)
"
)?
.into_typed_array::<INT>()?,
[2, 4, 6]
);
assert_eq!( assert_eq!(
engine engine
.eval::<Dynamic>( .eval::<Dynamic>(