diff --git a/RELEASES.md b/RELEASES.md index ba52e9cc..e3daa084 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -13,13 +13,23 @@ Breaking changes * The `merge_namespaces` parameter to `Module::eval_ast_as_new` is removed and now defaults to `true`. * `GlobalFileModuleResolver` is removed because its performance gain over the `FileModuleResolver` is no longer very significant. * The following `EvalAltResult` variants are removed and merged into `EvalAltResult::ErrorMismatchDataType`: `ErrorCharMismatch`, `ErrorNumericIndexExpr`, `ErrorStringIndexExpr`, `ErrorImportExpr`, `ErrorLogicGuard`, `ErrorBooleanArgMismatch` +* `Scope::iter_raw` returns an iterator with an additional field indicating whether the variable is constant or not. +* `rhai::ser` and `rhai::de` namespaces are merged into `rhai::serde`. New features ------------ +* `const` statements can now take any expression (or none at all) instead of only constant values. * `OptimizationLevel::Simple` now eagerly evaluates built-in binary operators of primary types (if not overloaded). * Added `is_def_var()` to detect if variable is defined, and `is_def_fn()` to detect if script function is defined. * `Dynamic::from(&str)` now constructs a `Dynamic` with a copy of the string as value. +* `AST::combine` and `AST::combine_filtered` allows combining two `AST`'s without creating a new one. + +Enhancements +------------ + +* Many one-liners and few-liners are now marked `#[inline]` or `[inline(always)]`, just in case it helps when LTO is not turned on. +* Default call stack depth for `debug` builds is reduced to 12 (from 16). Version 0.19.0 diff --git a/benches/README.md b/benches/README.md new file mode 100644 index 00000000..1c6d87ce --- /dev/null +++ b/benches/README.md @@ -0,0 +1,14 @@ +Micro Benchmarks +================ + +Micro benchmarks to measure the speed of Rhai. + +As with all micro benchmarks, take these with a grain of salt. + + +How to Run +---------- + +```bash +cargo +nightly bench +``` diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index e8cc0ee5..8a2d3456 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -3,7 +3,7 @@ name = "rhai_codegen" version = "0.1.0" edition = "2018" authors = ["jhwgh1968"] -description = "Proceducral macro support package for Rhai, a scripting language for Rust" +description = "Procedural macro support package for Rhai, a scripting language for Rust" homepage = "https://schungx.github.io/rhai/plugins/index.html" repository = "https://github.com/jonathandturner/rhai" license = "MIT OR Apache-2.0" diff --git a/codegen/README.md b/codegen/README.md new file mode 100644 index 00000000..0625bff9 --- /dev/null +++ b/codegen/README.md @@ -0,0 +1,4 @@ +Procedural Macros for Plugins +============================ + +This crate holds procedural macros for code generation, supporting Rhai's plugins system. diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index a3266d78..fd406370 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -106,13 +106,23 @@ mod test; /// /// # Usage /// -/// ```,ignore +/// ``` +/// # use rhai::{Engine, EvalAltResult}; /// use rhai::plugin::*; /// /// #[export_fn] -/// fn my_plugin_function(...) { -/// ... +/// fn my_plugin_function(x: i64) -> i64 { +/// x * 2 /// } +/// +/// # fn main() -> Result<(), Box> { +/// let mut engine = Engine::new(); +/// +/// register_exported_fn!(engine, "func", my_plugin_function); +/// +/// assert_eq!(engine.eval::("func(21)")?, 42); +/// # Ok(()) +/// # } /// ``` #[proc_macro_attribute] pub fn export_fn( @@ -138,13 +148,26 @@ pub fn export_fn( /// /// # Usage /// -/// ```,ignore +/// ``` +/// # use rhai::{Engine, Module, EvalAltResult}; /// use rhai::plugin::*; /// /// #[export_module] /// mod my_plugin_module { -/// ... +/// pub fn foo(x: i64) -> i64 { x * 2 } +/// pub fn bar() -> i64 { 21 } /// } +/// +/// # fn main() -> Result<(), Box> { +/// let mut engine = Engine::new(); +/// +/// let module = exported_module!(my_plugin_module); +/// +/// engine.load_package(module); +/// +/// assert_eq!(engine.eval::("foo(bar())")?, 42); +/// # Ok(()) +/// # } /// ``` #[proc_macro_attribute] pub fn export_module( @@ -164,19 +187,30 @@ pub fn export_module( proc_macro::TokenStream::from(tokens) } -/// Macro to generate a Rhai `Module` from a _plugin module_. +/// Macro to generate a Rhai `Module` from a _plugin module_ defined via `#[export_module]`. /// /// # Usage /// -/// ```,ignore +/// ``` +/// # use rhai::{Engine, Module, EvalAltResult}; /// use rhai::plugin::*; /// /// #[export_module] /// mod my_plugin_module { -/// ... +/// pub fn foo(x: i64) -> i64 { x * 2 } +/// pub fn bar() -> i64 { 21 } /// } /// +/// # fn main() -> Result<(), Box> { +/// let mut engine = Engine::new(); +/// /// let module = exported_module!(my_plugin_module); +/// +/// engine.load_package(module); +/// +/// assert_eq!(engine.eval::("foo(bar())")?, 42); +/// # Ok(()) +/// # } /// ``` #[proc_macro] pub fn exported_module(module_path: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -203,17 +237,27 @@ pub fn exported_module(module_path: proc_macro::TokenStream) -> proc_macro::Toke /// /// # Usage /// -/// ```,ignore +/// ``` +/// # use rhai::{Engine, Module, EvalAltResult}; /// use rhai::plugin::*; /// /// #[export_module] /// mod my_plugin_module { -/// ... +/// pub fn foo(x: i64) -> i64 { x * 2 } +/// pub fn bar() -> i64 { 21 } /// } /// -/// let mut module = Module::new(); +/// # fn main() -> Result<(), Box> { +/// let mut engine = Engine::new(); /// +/// let mut module = Module::new(); /// combine_with_exported_module!(&mut module, "my_plugin_module_ID", my_plugin_module); +/// +/// engine.load_package(module); +/// +/// assert_eq!(engine.eval::("foo(bar())")?, 42); +/// # Ok(()) +/// # } /// ``` #[proc_macro] pub fn combine_with_exported_module(args: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -228,21 +272,27 @@ pub fn combine_with_exported_module(args: proc_macro::TokenStream) -> proc_macro proc_macro::TokenStream::from(tokens) } -/// Macro to register a _plugin function_ into an `Engine`. +/// Macro to register a _plugin function_ (defined via `#[export_fn]`) into an `Engine`. /// /// # Usage /// -/// ```,ignore +/// ``` +/// # use rhai::{Engine, EvalAltResult}; /// use rhai::plugin::*; /// /// #[export_fn] -/// fn my_plugin_function(...) { -/// ... +/// fn my_plugin_function(x: i64) -> i64 { +/// x * 2 /// } /// +/// # fn main() -> Result<(), Box> { /// let mut engine = Engine::new(); /// -/// register_exported_fn!(engine, "calc", my_plugin_function); +/// register_exported_fn!(engine, "func", my_plugin_function); +/// +/// assert_eq!(engine.eval::("func(21)")?, 42); +/// # Ok(()) +/// # } /// ``` #[proc_macro] pub fn register_exported_fn(args: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -262,17 +312,26 @@ pub fn register_exported_fn(args: proc_macro::TokenStream) -> proc_macro::TokenS /// /// # Usage /// -/// ```,ignore +/// ``` +/// # use rhai::{Engine, EvalAltResult}; /// use rhai::plugin::*; /// /// #[export_fn] -/// fn my_plugin_function(...) { -/// ... +/// fn my_plugin_function(x: i64) -> i64 { +/// x * 2 /// } /// -/// let mut module = Module::new(); +/// # fn main() -> Result<(), Box> { +/// let mut engine = Engine::new(); /// -/// set_exported_fn!(module, "calc", my_plugin_function); +/// let mut module = Module::new(); +/// set_exported_fn!(module, "func", my_plugin_function); +/// +/// engine.load_package(module); +/// +/// assert_eq!(engine.eval::("func(21)")?, 42); +/// # Ok(()) +/// # } /// ``` #[proc_macro] pub fn set_exported_fn(args: proc_macro::TokenStream) -> proc_macro::TokenStream { diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 00000000..63d79bc4 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,41 @@ +The Rhai Book +============= + +[_The Rhai Book_](https://schungx.github.io/rhai) serves as Rhai's primary +documentation and tutorial resource. + + +How to Build from Source +------------------------ + +* Install [`mdbook`](https://github.com/rust-lang/mdBook): + +```bash +cargo install mdbook +``` + +* Install [`mdbook-tera`](https://github.com/avitex/mdbook-tera) (for templating): + +```bash +cargo install mdbook-tera +``` + +* Run build in source directory: + +```bash +cd doc +mdbook build +``` + + +Configuration Settings +---------------------- + +Settings stored in `context.json`: + +| Setting | Description | +| ---------- | ------------------------------------------------------------------------------------------------- | +| `version` | version of Rhai | +| `repoHome` | points to the [root of the GitHub repo](https://github.com/jonathandturner/rhai/blob/master) | +| `repoTree` | points to the [root of the GitHub repo tree](https://github.com/jonathandturner/rhai/tree/master) | +| `rootUrl` | sub-directory for the root domain, e.g. `/rhai` | diff --git a/doc/src/language/constants.md b/doc/src/language/constants.md index 81ad1ce6..da2a13be 100644 --- a/doc/src/language/constants.md +++ b/doc/src/language/constants.md @@ -15,12 +15,10 @@ print(x * 2); // prints 84 x = 123; // <- syntax error: cannot assign to constant ``` -Unlike variables which need not have initial values (default to [`()`]), -constants must be assigned one, and it must be a [_literal value_](../appendix/literals.md), -not an expression. - ```rust -const x = 40 + 2; // <- syntax error: cannot assign expression to constant +const x; // 'x' is a constant '()' + +const x = 40 + 2; // 'x' is a constant 42 ``` @@ -33,9 +31,7 @@ running with that [`Scope`]. When added to a custom [`Scope`], a constant can hold any value, not just a literal value. It is very useful to have a constant value hold a [custom type], which essentially acts -as a [_singleton_](../patterns/singleton.md). The singleton object can be modified via its -registered API - being a constant only prevents it from being re-assigned or operated upon by Rhai; -mutating it via a Rust function is still allowed. +as a [_singleton_](../patterns/singleton.md). ```rust use rhai::{Engine, Scope}; @@ -58,3 +54,11 @@ engine.consume_with_scope(&mut scope, r" print(MY_NUMBER.value); // prints 42 ")?; ``` + + +Constants Can be Modified, Just Not Reassigned +--------------------------------------------- + +A custom type stored as a constant can be modified via its registered API - +being a constant only prevents it from being re-assigned or operated upon by Rhai; +mutating it via a Rust function is still allowed. diff --git a/doc/src/language/fn-closure.md b/doc/src/language/fn-closure.md index b620e503..56c79cfd 100644 --- a/doc/src/language/fn-closure.md +++ b/doc/src/language/fn-closure.md @@ -49,7 +49,7 @@ f.call(2) == 42; // the value of 'x' is 40 because 'x' is sha // The above de-sugars into this: fn anon$1001(x, y) { x + y } // parameter 'x' is inserted -make_shared(x); // convert variable 'x' into a shared value +$make_shared(x); // convert variable 'x' into a shared value let f = Fn("anon$1001").curry(x); // shared 'x' is curried @@ -57,6 +57,24 @@ f.call(2) == 42; ``` +Constants are Not Captured +-------------------------- + +Constants are never shared. Their values are simply cloned. + +```rust +const x = 42; // constant variable 'x' + +let f = |y| x += y; // constant 'x' is cloned and not captured + +x.is_shared() == false; // 'x' is not shared + +f.call(10); // the cloned copy of 'x' is changed + +x == 42; // 'x' is not changed +``` + + Beware: Captured Variables are Truly Shared ------------------------------------------ @@ -64,7 +82,7 @@ The example below is a typical tutorial sample for many languages to illustrate that may accompany capturing external scope variables in closures. It prints `9`, `9`, `9`, ... `9`, `9`, not `0`, `1`, `2`, ... `8`, `9`, because there is -ever only one captured variable, and all ten closures capture the _same_ variable. +ever only _one_ captured variable, and all ten closures capture the _same_ variable. ```rust let funcs = []; @@ -78,7 +96,7 @@ funcs.len() == 10; // 10 closures stored in the array funcs[0].type_of() == "Fn"; // make sure these are closures for f in funcs { - f.call(); // all the references to 'i' are the same variable! + f.call(); // all references to 'i' are the same variable! } ``` diff --git a/doc/src/language/fn-namespaces.md b/doc/src/language/fn-namespaces.md index e1a4a55e..0e3819b2 100644 --- a/doc/src/language/fn-namespaces.md +++ b/doc/src/language/fn-namespaces.md @@ -10,8 +10,8 @@ Each Function is a Separate Compilation Unit This means that individual functions can be separated, exported, re-grouped, imported, and generally mix-'n-match-ed with other completely unrelated scripts. -For example, the `AST::merge` method allows merging all functions in one [`AST`] into another, -forming a new, combined, group of functions. +For example, the `AST::merge` and `AST::combine` methods (or the equivalent `+` and `+=` operators) +allow combining all functions in one [`AST`] into another, forming a new, unified, group of functions. In general, there are two types of _namespaces_ where functions are looked up: @@ -58,10 +58,10 @@ let ast1 = engine.compile( // Compile another script with an overriding function let ast2 = engine.compile(r#"fn get_message() { "Boo!" }"#)?; -// Merge the two AST's -let ast = ast1.merge(ast2); // 'message' will be overwritten +// Combine the two AST's +ast1 += ast2; // 'message' will be overwritten -engine.consume_ast(&ast)?; // prints 'Boo!' +engine.consume_ast(&ast1)?; // prints 'Boo!' ``` Therefore, care must be taken when _cross-calling_ functions to make sure that the correct diff --git a/doc/src/patterns/multi-layer.md b/doc/src/patterns/multi-layer.md index 8a296c72..9092ca82 100644 --- a/doc/src/patterns/multi-layer.md +++ b/doc/src/patterns/multi-layer.md @@ -29,8 +29,8 @@ Key Concepts * The lowest layer script is compiled into a base [`AST`]. -* Higher layer scripts are also compiled into [`AST`] and _merged_ into the base using `AST::merge`, - overriding any existing functions. +* Higher layer scripts are also compiled into [`AST`] and _combined_ into the base using `AST::combine` + (or the `+=` operator), overriding any existing functions. Examples @@ -83,7 +83,7 @@ fn baz() { print("hey!"); } fn foo(x) { x + 42 } ``` -Load and merge them sequentially: +Load and combine them sequentially: ```rust let engine = Engine::new(); @@ -91,17 +91,17 @@ let engine = Engine::new(); // Compile the baseline default implementations. let mut ast = engine.compile_file("default.rhai".into())?; -// Merge in the first layer. +// Combine the first layer. let lowest = engine.compile_file("lowest.rhai".into())?; -ast = ast.merge(&lowest); +ast += lowest; -// Merge in the second layer. +// Combine the second layer. let middle = engine.compile_file("middle.rhai".into())?; -ast = ast.merge(&middle); +ast += lowest; -// Merge in the third layer. +// Combine the third layer. let highest = engine.compile_file("highest.rhai".into())?; -ast = ast.merge(&highest); +ast += lowest; // Now, 'ast' contains the following functions: // diff --git a/doc/src/rust/serde.md b/doc/src/rust/serde.md index 7b9a12f1..a1a01ae2 100644 --- a/doc/src/rust/serde.md +++ b/doc/src/rust/serde.md @@ -14,12 +14,12 @@ A [`Dynamic`] can be seamlessly converted to and from a type that implements Serialization ------------- -The function `rhai::ser::to_dynamic` automatically converts any Rust type that implements +The function `rhai::serde::to_dynamic` automatically converts any Rust type that implements [`serde::Serialize`](https://docs.serde.rs/serde/trait.Serialize.html) into a [`Dynamic`]. This is usually not necessary because using [`Dynamic::from`][`Dynamic`] is much easier and is essentially the same thing. The only difference is treatment for integer values. `Dynamic::from` will keep the different -integer types intact, while `rhai::ser::to_dynamic` will convert them all into [`INT`][standard types] +integer types intact, while `rhai::serde::to_dynamic` will convert them all into [`INT`][standard types] (i.e. the system integer type which is `i64` or `i32` depending on the [`only_i32`] feature). In particular, Rust `struct`'s (or any type that is marked as a `serde` map) are converted into [object maps] @@ -27,11 +27,11 @@ while Rust `Vec`'s (or any type that is marked as a `serde` sequence) are conver While it is also simple to serialize a Rust type to `JSON` via `serde`, then use [`Engine::parse_json`]({{rootUrl}}/language/json.md) to convert it into an [object map], -`rhai::ser::to_dynamic` serializes it to [`Dynamic`] directly via `serde` without going through the `JSON` step. +`rhai::serde::to_dynamic` serializes it to [`Dynamic`] directly via `serde` without going through the `JSON` step. ```rust use rhai::{Dynamic, Map}; -use rhai::ser::to_dynamic; +use rhai::serde::to_dynamic; #[derive(Debug, serde::Serialize)] struct Point { @@ -64,7 +64,7 @@ map.is::() == true; Deserialization --------------- -The function `rhai::de::from_dynamic` automatically converts a [`Dynamic`] value into any Rust type +The function `rhai::serde::from_dynamic` automatically converts a [`Dynamic`] value into any Rust type that implements [`serde::Deserialize`](https://docs.serde.rs/serde/trait.Deserialize.html). In particular, [object maps] are converted into Rust `struct`'s (or any type that is marked as @@ -73,7 +73,7 @@ as a `serde` sequence). ```rust use rhai::{Engine, Dynamic}; -use rhai::de::from_dynamic; +use rhai::serde::from_dynamic; #[derive(Debug, serde::Deserialize)] struct Point { diff --git a/doc/src/safety/max-call-stack.md b/doc/src/safety/max-call-stack.md index 2d33d653..374dc2a6 100644 --- a/doc/src/safety/max-call-stack.md +++ b/doc/src/safety/max-call-stack.md @@ -6,7 +6,7 @@ Maximum Call Stack Depth Limit How Stack Usage by Scripts ------------------------------- -Rhai by default limits function calls to a maximum depth of 128 levels (16 levels in debug build). +Rhai by default limits function calls to a maximum depth of 128 levels (12 levels in debug build). This limit may be changed via the `Engine::set_max_call_levels` method. @@ -17,9 +17,9 @@ This check can be disabled via the [`unchecked`] feature for higher performance ```rust let mut engine = Engine::new(); -engine.set_max_call_levels(10); // allow only up to 10 levels of function calls +engine.set_max_call_levels(10); // allow only up to 10 levels of function calls -engine.set_max_call_levels(0); // allow no function calls at all (max depth = zero) +engine.set_max_call_levels(0); // allow no function calls at all (max depth = zero) ``` diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..610f388e --- /dev/null +++ b/examples/README.md @@ -0,0 +1,12 @@ +Sample Applications +=================== + +Sample applications written in Rhai. + + +How to Run +---------- + +```bash +cargo run --example sample_app_to_run +``` diff --git a/examples/arrays_and_structs.rs b/examples/arrays_and_structs.rs index 5e604120..6aa9fafa 100644 --- a/examples/arrays_and_structs.rs +++ b/examples/arrays_and_structs.rs @@ -20,10 +20,10 @@ impl TestStruct { fn main() { let mut engine = Engine::new(); - engine.register_type::(); - - engine.register_fn("update", TestStruct::update); - engine.register_fn("new_ts", TestStruct::new); + engine + .register_type::() + .register_fn("update", TestStruct::update) + .register_fn("new_ts", TestStruct::new); println!( "{:?}", diff --git a/examples/custom_types_and_methods.rs b/examples/custom_types_and_methods.rs index d152dc9b..4a8e260a 100644 --- a/examples/custom_types_and_methods.rs +++ b/examples/custom_types_and_methods.rs @@ -19,10 +19,10 @@ impl TestStruct { fn main() -> Result<(), Box> { let mut engine = Engine::new(); - engine.register_type::(); - - engine.register_fn("update", TestStruct::update); - engine.register_fn("new_ts", TestStruct::new); + engine + .register_type::() + .register_fn("update", TestStruct::update) + .register_fn("new_ts", TestStruct::new); let result = engine.eval::("let x = new_ts(); x.update(); x")?; diff --git a/examples/hello.rs b/examples/hello.rs index af5e8b9c..00913e2c 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -1,8 +1,7 @@ -use rhai::{packages::*, Engine, EvalAltResult, INT}; +use rhai::{Engine, EvalAltResult, INT}; fn main() -> Result<(), Box> { - let mut engine = Engine::new_raw(); - engine.load_package(ArithmeticPackage::new().get()); + let engine = Engine::new(); let result = engine.eval::("40 + 2")?; diff --git a/examples/repl.rs b/examples/repl.rs index 5a76b93c..f97bc0ba 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -114,15 +114,19 @@ fn main() { } "exit" | "quit" => break, // quit "scope" => { - scope.iter_raw().enumerate().for_each(|(i, (name, value))| { - println!( - "[{}] {}{} = {:?}", - i + 1, - name, - if value.is_shared() { " (shared)" } else { "" }, - *value.read_lock::().unwrap(), - ) - }); + scope + .iter_raw() + .enumerate() + .for_each(|(i, (name, constant, value))| { + println!( + "[{}] {}{}{} = {:?}", + i + 1, + if constant { "const " } else { "" }, + name, + if value.is_shared() { " (shared)" } else { "" }, + *value.read_lock::().unwrap(), + ) + }); continue; } "astu" => { @@ -155,7 +159,7 @@ fn main() { } // Merge the AST into the main - main_ast = main_ast.merge(&ast); + main_ast += ast.clone(); // Evaluate engine.eval_ast_with_scope::(&mut scope, &main_ast) diff --git a/examples/serde.rs b/examples/serde.rs index 52c39b51..1868f44b 100644 --- a/examples/serde.rs +++ b/examples/serde.rs @@ -13,7 +13,7 @@ fn main() { #[cfg(feature = "serde")] mod example { - use rhai::{de::from_dynamic, ser::to_dynamic}; + use rhai::serde::{from_dynamic, to_dynamic}; use rhai::{Dynamic, Engine, Map}; use serde::{Deserialize, Serialize}; @@ -56,7 +56,7 @@ mod example { let result: Dynamic = engine .eval( r#" - ##{ + #{ a: 42, b: [ "hello", "world" ], c: true, diff --git a/examples/simple_fn.rs b/examples/simple_fn.rs index 21f532ae..c0c59bad 100644 --- a/examples/simple_fn.rs +++ b/examples/simple_fn.rs @@ -1,12 +1,12 @@ use rhai::{Engine, EvalAltResult, RegisterFn, INT}; +fn add(x: INT, y: INT) -> INT { + x + y +} + fn main() -> Result<(), Box> { let mut engine = Engine::new(); - fn add(x: INT, y: INT) -> INT { - x + y - } - engine.register_fn("add", add); let result = engine.eval::("add(40, 2)")?; diff --git a/examples/strings.rs b/examples/strings.rs index e9d53016..f24e9545 100644 --- a/examples/strings.rs +++ b/examples/strings.rs @@ -29,17 +29,17 @@ fn main() -> Result<(), Box> { let mut engine = Engine::new_raw(); // Register string functions - engine.register_fn("trim", trim_string); - engine.register_fn("len", count_string_bytes); - engine.register_fn("index_of", find_substring); - - // Register string functions using closures - engine.register_fn("display", |label: &str, x: INT| { - println!("{}: {}", label, x) - }); - engine.register_fn("display", |label: ImmutableString, x: &str| { - println!(r#"{}: "{}""#, label, x) // Quote the input string - }); + engine + .register_fn("trim", trim_string) + .register_fn("len", count_string_bytes) + .register_fn("index_of", find_substring) + .register_fn("display", |label: &str, x: INT| { + // Register string functions using closures + println!("{}: {}", label, x) + }) + .register_fn("display", |label: ImmutableString, x: &str| { + println!(r#"{}: "{}""#, label, x) // Quote the input string + }); let mut scope = Scope::new(); let mut input = String::new(); diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 00000000..ca204dd9 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,20 @@ +Testing Scripts +=============== + +Testing scripts written in Rhai. + + +How to Run +---------- + +Compile the `rhai_runner` example: + +```bash +cargo build --example rhai_runner +``` + +Run it: + +```bash +./target/debug/examples/rhai_runner ./scripts/test_script_to_run.rhai +``` diff --git a/src/any.rs b/src/any.rs index 3dfbad6b..407a25ca 100644 --- a/src/any.rs +++ b/src/any.rs @@ -541,6 +541,8 @@ impl Dynamic { /// ``` #[inline(always)] pub fn from(value: T) -> Self { + // Coded this way in order to maximally leverage potentials for dead-code removal. + if TypeId::of::() == TypeId::of::() { return ::downcast_ref::(&value) .unwrap() @@ -677,6 +679,8 @@ impl Dynamic { /// ``` #[inline(always)] pub fn try_cast(self) -> Option { + // Coded this way in order to maximally leverage potentials for dead-code removal. + match self.0 { #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] @@ -964,6 +968,8 @@ impl Dynamic { /// Returns `None` if the cast fails, or if the value is shared. #[inline(always)] pub(crate) fn downcast_ref(&self) -> Option<&T> { + // Coded this way in order to maximally leverage potentials for dead-code removal. + if TypeId::of::() == TypeId::of::() { return match &self.0 { Union::Int(value) => ::downcast_ref::(value), @@ -1052,6 +1058,8 @@ impl Dynamic { /// Returns `None` if the cast fails, or if the value is shared. #[inline(always)] pub(crate) fn downcast_mut(&mut self) -> Option<&mut T> { + // Coded this way in order to maximally leverage potentials for dead-code removal. + if TypeId::of::() == TypeId::of::() { return match &mut self.0 { Union::Int(value) => ::downcast_mut::(value), diff --git a/src/api.rs b/src/api.rs index 4e772f27..53a78e95 100644 --- a/src/api.rs +++ b/src/api.rs @@ -28,7 +28,7 @@ use crate::{ use crate::fn_register::{RegisterFn, RegisterResultFn}; #[cfg(not(feature = "no_function"))] -use crate::{fn_args::FuncArgs, fn_call::ensure_no_data_race, utils::StaticVec}; +use crate::{fn_args::FuncArgs, fn_call::ensure_no_data_race, StaticVec}; #[cfg(not(feature = "no_optimize"))] use crate::optimize::optimize_into_ast; @@ -64,6 +64,7 @@ impl Engine { /// /// To access the first mutable parameter, use `args.get_mut(0).unwrap()` #[deprecated(note = "this function is volatile and may change")] + #[inline(always)] pub fn register_raw_fn( &mut self, name: &str, @@ -111,6 +112,7 @@ impl Engine { /// # } /// ``` #[cfg(not(feature = "no_object"))] + #[inline(always)] pub fn register_type(&mut self) -> &mut Self { self.register_type_with_name::(type_name::()) } @@ -159,6 +161,7 @@ impl Engine { /// # } /// ``` #[cfg(not(feature = "no_object"))] + #[inline(always)] pub fn register_type_with_name(&mut self, name: &str) -> &mut Self { if self.type_names.is_none() { self.type_names = Some(Default::default()); @@ -173,6 +176,7 @@ impl Engine { /// Register an iterator adapter for a type with the `Engine`. /// This is an advanced feature. + #[inline(always)] pub fn register_iterator(&mut self, f: IteratorFn) -> &mut Self { self.global_module.set_iter(TypeId::of::(), f); self @@ -214,6 +218,7 @@ impl Engine { /// # } /// ``` #[cfg(not(feature = "no_object"))] + #[inline(always)] pub fn register_get( &mut self, name: &str, @@ -265,6 +270,7 @@ impl Engine { /// # } /// ``` #[cfg(not(feature = "no_object"))] + #[inline(always)] pub fn register_get_result( &mut self, name: &str, @@ -310,6 +316,7 @@ impl Engine { /// # } /// ``` #[cfg(not(feature = "no_object"))] + #[inline(always)] pub fn register_set( &mut self, name: &str, @@ -363,6 +370,7 @@ impl Engine { /// # } /// ``` #[cfg(not(feature = "no_object"))] + #[inline(always)] pub fn register_set_result( &mut self, name: &str, @@ -417,6 +425,7 @@ impl Engine { /// # } /// ``` #[cfg(not(feature = "no_object"))] + #[inline(always)] pub fn register_get_set( &mut self, name: &str, @@ -472,6 +481,7 @@ impl Engine { /// # } /// ``` #[cfg(not(feature = "no_index"))] + #[inline(always)] pub fn register_indexer_get( &mut self, callback: impl Fn(&mut T, X) -> U + SendSync + 'static, @@ -543,6 +553,7 @@ impl Engine { /// # } /// ``` #[cfg(not(feature = "no_index"))] + #[inline(always)] pub fn register_indexer_get_result( &mut self, callback: impl Fn(&mut T, X) -> Result> + SendSync + 'static, @@ -610,6 +621,7 @@ impl Engine { /// # } /// ``` #[cfg(not(feature = "no_index"))] + #[inline(always)] pub fn register_indexer_set( &mut self, callback: impl Fn(&mut T, X, U) + SendSync + 'static, @@ -682,6 +694,7 @@ impl Engine { /// # } /// ``` #[cfg(not(feature = "no_index"))] + #[inline(always)] pub fn register_indexer_set_result( &mut self, callback: impl Fn(&mut T, X, U) -> Result<(), Box> + SendSync + 'static, @@ -752,6 +765,7 @@ impl Engine { /// # } /// ``` #[cfg(not(feature = "no_index"))] + #[inline(always)] pub fn register_indexer_get_set( &mut self, getter: impl Fn(&mut T, X) -> U + SendSync + 'static, @@ -785,6 +799,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` + #[inline(always)] pub fn compile(&self, script: &str) -> Result { self.compile_with_scope(&Scope::new(), script) } @@ -827,6 +842,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` + #[inline(always)] pub fn compile_with_scope(&self, scope: &Scope, script: &str) -> Result { self.compile_scripts_with_scope(scope, &[script]) } @@ -877,6 +893,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` + #[inline(always)] pub fn compile_scripts_with_scope( &self, scope: &Scope, @@ -886,6 +903,7 @@ impl Engine { } /// Join a list of strings and compile into an `AST` using own scope at a specific optimization level. + #[inline(always)] pub(crate) fn compile_with_scope_and_optimization_level( &self, scope: &Scope, @@ -899,6 +917,7 @@ impl Engine { /// Read the contents of a file into a string. #[cfg(not(feature = "no_std"))] #[cfg(not(target_arch = "wasm32"))] + #[inline] fn read_file(path: PathBuf) -> Result> { let mut f = File::open(path.clone()).map_err(|err| { EvalAltResult::ErrorReadingScriptFile(path.clone(), Position::none(), err) @@ -935,6 +954,7 @@ impl Engine { /// ``` #[cfg(not(feature = "no_std"))] #[cfg(not(target_arch = "wasm32"))] + #[inline(always)] pub fn compile_file(&self, path: PathBuf) -> Result> { self.compile_file_with_scope(&Scope::new(), path) } @@ -972,6 +992,7 @@ impl Engine { /// ``` #[cfg(not(feature = "no_std"))] #[cfg(not(target_arch = "wasm32"))] + #[inline(always)] pub fn compile_file_with_scope( &self, scope: &Scope, @@ -1081,6 +1102,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` + #[inline(always)] pub fn compile_expression(&self, script: &str) -> Result { self.compile_expression_with_scope(&Scope::new(), script) } @@ -1124,6 +1146,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` + #[inline] pub fn compile_expression_with_scope( &self, scope: &Scope, @@ -1154,6 +1177,7 @@ impl Engine { /// ``` #[cfg(not(feature = "no_std"))] #[cfg(not(target_arch = "wasm32"))] + #[inline(always)] pub fn eval_file(&self, path: PathBuf) -> Result> { Self::read_file(path).and_then(|contents| self.eval::(&contents)) } @@ -1179,6 +1203,7 @@ impl Engine { /// ``` #[cfg(not(feature = "no_std"))] #[cfg(not(target_arch = "wasm32"))] + #[inline(always)] pub fn eval_file_with_scope( &self, scope: &mut Scope, @@ -1201,6 +1226,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` + #[inline(always)] pub fn eval(&self, script: &str) -> Result> { self.eval_with_scope(&mut Scope::new(), script) } @@ -1227,6 +1253,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` + #[inline] pub fn eval_with_scope( &self, scope: &mut Scope, @@ -1254,6 +1281,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` + #[inline(always)] pub fn eval_expression( &self, script: &str, @@ -1279,6 +1307,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` + #[inline] pub fn eval_expression_with_scope( &self, scope: &mut Scope, @@ -1311,6 +1340,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` + #[inline(always)] pub fn eval_ast(&self, ast: &AST) -> Result> { self.eval_ast_with_scope(&mut Scope::new(), ast) } @@ -1344,6 +1374,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` + #[inline] pub fn eval_ast_with_scope( &self, scope: &mut Scope, @@ -1365,6 +1396,7 @@ impl Engine { } /// Evaluate an `AST` with own scope. + #[inline] pub(crate) fn eval_ast_with_scope_raw<'a>( &self, scope: &mut Scope, @@ -1389,6 +1421,7 @@ impl Engine { /// Useful for when you don't need the result, but still need to keep track of possible errors. #[cfg(not(feature = "no_std"))] #[cfg(not(target_arch = "wasm32"))] + #[inline(always)] pub fn consume_file(&self, path: PathBuf) -> Result<(), Box> { Self::read_file(path).and_then(|contents| self.consume(&contents)) } @@ -1397,6 +1430,7 @@ impl Engine { /// Useful for when you don't need the result, but still need to keep track of possible errors. #[cfg(not(feature = "no_std"))] #[cfg(not(target_arch = "wasm32"))] + #[inline(always)] pub fn consume_file_with_scope( &self, scope: &mut Scope, @@ -1407,12 +1441,14 @@ impl Engine { /// Evaluate a string, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. + #[inline(always)] pub fn consume(&self, script: &str) -> Result<(), Box> { self.consume_with_scope(&mut Scope::new(), script) } /// Evaluate a string with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. + #[inline] pub fn consume_with_scope( &self, scope: &mut Scope, @@ -1426,12 +1462,14 @@ impl Engine { /// Evaluate an AST, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. + #[inline(always)] pub fn consume_ast(&self, ast: &AST) -> Result<(), Box> { self.consume_ast_with_scope(&mut Scope::new(), ast) } /// Evaluate an `AST` with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. + #[inline] pub fn consume_ast_with_scope( &self, scope: &mut Scope, @@ -1491,6 +1529,7 @@ impl Engine { /// # } /// ``` #[cfg(not(feature = "no_function"))] + #[inline] pub fn call_fn( &self, scope: &mut Scope, @@ -1565,6 +1604,7 @@ impl Engine { /// # } /// ``` #[cfg(not(feature = "no_function"))] + #[inline(always)] pub fn call_fn_dynamic( &self, scope: &mut Scope, @@ -1587,6 +1627,7 @@ impl Engine { /// Do not use the arguments after this call. If they are needed afterwards, /// clone them _before_ calling this function. #[cfg(not(feature = "no_function"))] + #[inline] pub(crate) fn call_fn_dynamic_raw( &self, scope: &mut Scope, @@ -1624,6 +1665,7 @@ impl Engine { /// compiled just once. Before evaluation, constants are passed into the `Engine` via an external scope /// (i.e. with `scope.push_constant(...)`). Then, the `AST is cloned and the copy re-optimized before running. #[cfg(not(feature = "no_optimize"))] + #[inline] pub fn optimize_ast( &self, scope: &Scope, @@ -1678,6 +1720,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` + #[inline(always)] pub fn on_progress( &mut self, callback: impl Fn(&u64) -> bool + SendSync + 'static, @@ -1710,6 +1753,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` + #[inline(always)] pub fn on_print(&mut self, callback: impl Fn(&str) + SendSync + 'static) -> &mut Self { self.print = Box::new(callback); self @@ -1739,6 +1783,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` + #[inline(always)] pub fn on_debug(&mut self, callback: impl Fn(&str) + SendSync + 'static) -> &mut Self { self.debug = Box::new(callback); self diff --git a/src/engine.rs b/src/engine.rs index 63fb15ea..42c023e9 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,7 +1,6 @@ //! Main module defining the script evaluation `Engine`. use crate::any::{map_std_type_name, Dynamic, Union}; -use crate::calc_fn_hash; use crate::fn_call::run_builtin_op_assignment; use crate::fn_native::{Callback, FnPtr}; use crate::module::{Module, ModuleRef}; @@ -13,7 +12,7 @@ use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::syntax::{CustomSyntax, EvalContext}; use crate::token::Position; -use crate::utils::StaticVec; +use crate::{calc_fn_hash, StaticVec}; #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] use crate::any::Variant; @@ -75,7 +74,7 @@ pub type Imports = Vec<(ImmutableString, Module)>; #[cfg(not(feature = "unchecked"))] #[cfg(debug_assertions)] -pub const MAX_CALL_STACK_DEPTH: usize = 16; +pub const MAX_CALL_STACK_DEPTH: usize = 12; #[cfg(not(feature = "unchecked"))] #[cfg(debug_assertions)] pub const MAX_EXPR_DEPTH: usize = 32; @@ -440,6 +439,7 @@ impl fmt::Debug for Engine { } impl Default for Engine { + #[inline(always)] fn default() -> Self { Self::new() } @@ -606,6 +606,7 @@ pub fn search_scope_only<'s, 'a>( impl Engine { /// Create a new `Engine` + #[inline(always)] pub fn new() -> Self { // Create the new scripting Engine let mut engine = Self { @@ -661,6 +662,7 @@ impl Engine { /// Create a new `Engine` with minimal built-in functions. /// Use the `load_package` method to load additional packages of functions. + #[inline(always)] pub fn new_raw() -> Self { Self { id: None, @@ -1777,39 +1779,26 @@ impl Engine { Stmt::ReturnWithVal(_) => unreachable!(), - // Let statement - Stmt::Let(x) if x.1.is_some() => { + // Let/const statement + Stmt::Let(x) | Stmt::Const(x) => { let ((var_name, _), expr, _) = x.as_ref(); - let expr = expr.as_ref().unwrap(); - let val = self - .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? - .flatten(); + let entry_type = match stmt { + Stmt::Let(_) => ScopeEntryType::Normal, + Stmt::Const(_) => ScopeEntryType::Constant, + _ => unreachable!(), + }; + + let val = if let Some(expr) = expr { + self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? + .flatten() + } else { + ().into() + }; let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); - scope.push_dynamic_value(var_name, ScopeEntryType::Normal, val, false); + scope.push_dynamic_value(var_name, entry_type, val, false); Ok(Default::default()) } - Stmt::Let(x) => { - let ((var_name, _), _, _) = x.as_ref(); - let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); - scope.push(var_name, ()); - Ok(Default::default()) - } - - // Const statement - Stmt::Const(x) if x.1.is_constant() => { - let ((var_name, _), expr, _) = x.as_ref(); - let val = self - .eval_expr(scope, mods, state, lib, this_ptr, &expr, level)? - .flatten(); - let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); - scope.push_dynamic_value(var_name, ScopeEntryType::Constant, val, true); - Ok(Default::default()) - } - - // Const expression not constant - Stmt::Const(_) => unreachable!(), - // Import statement #[cfg(not(feature = "no_module"))] Stmt::Import(x) => { @@ -2040,6 +2029,7 @@ impl Engine { } /// Make a Box>. + #[inline(always)] pub fn make_type_mismatch_err(&self, typ: &str, pos: Position) -> Box { EvalAltResult::ErrorMismatchDataType( typ.into(), diff --git a/src/error.rs b/src/error.rs index 2c210d60..1cdfb7aa 100644 --- a/src/error.rs +++ b/src/error.rs @@ -60,6 +60,7 @@ impl fmt::Display for LexError { impl LexError { /// Convert a `LexError` into a `ParseError`. + #[inline(always)] pub fn into_err(&self, pos: Position) -> ParseError { ParseError(Box::new(self.into()), pos) } @@ -99,8 +100,6 @@ pub enum ParseErrorType { /// /// Never appears under the `no_object` feature. DuplicatedProperty(String), - /// Invalid expression assigned to constant. Wrapped value is the name of the constant. - ForbiddenConstantExpr(String), /// Missing a property name for custom types and maps. /// /// Never appears under the `no_object` feature. @@ -157,6 +156,7 @@ pub enum ParseErrorType { impl ParseErrorType { /// Make a `ParseError` using the current type and position. + #[inline(always)] pub(crate) fn into_err(self, pos: Position) -> ParseError { ParseError(Box::new(self), pos) } @@ -172,7 +172,6 @@ impl ParseErrorType { Self::MalformedInExpr(_) => "Invalid 'in' expression", Self::MalformedCapture(_) => "Invalid capturing", Self::DuplicatedProperty(_) => "Duplicated property in object map literal", - Self::ForbiddenConstantExpr(_) => "Expecting a constant", Self::PropertyExpected => "Expecting name of a property", Self::VariableExpected => "Expecting name of a variable", Self::Reserved(_) => "Invalid use of reserved keyword", @@ -199,9 +198,6 @@ impl fmt::Display for ParseErrorType { Self::BadInput(s) | ParseErrorType::MalformedCallExpr(s) => { f.write_str(if s.is_empty() { self.desc() } else { s }) } - Self::ForbiddenConstantExpr(s) => { - write!(f, "Expecting a constant to assign to '{}'", s) - } Self::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s), Self::MalformedIndexExpr(s) | Self::MalformedInExpr(s) | Self::MalformedCapture(s) => { @@ -247,6 +243,7 @@ impl fmt::Display for ParseErrorType { } impl From<&LexError> for ParseErrorType { + #[inline(always)] fn from(err: &LexError) -> Self { match err { LexError::StringTooLong(max) => { @@ -264,6 +261,7 @@ pub struct ParseError(pub Box, pub Position); impl Error for ParseError {} impl fmt::Display for ParseError { + #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0, f)?; @@ -277,12 +275,14 @@ impl fmt::Display for ParseError { } impl From for Box { + #[inline(always)] fn from(err: ParseErrorType) -> Self { Box::new(EvalAltResult::ErrorParsing(err, Position::none())) } } impl From for Box { + #[inline(always)] fn from(err: ParseError) -> Self { Box::new(EvalAltResult::ErrorParsing(*err.0, err.1)) } diff --git a/src/fn_args.rs b/src/fn_args.rs index e5a22390..07405c0f 100644 --- a/src/fn_args.rs +++ b/src/fn_args.rs @@ -3,7 +3,7 @@ #![allow(non_snake_case)] use crate::any::{Dynamic, Variant}; -use crate::utils::StaticVec; +use crate::StaticVec; /// Trait that represents arguments to a function call. /// Any data type that can be converted into a `Vec` can be used @@ -19,6 +19,7 @@ macro_rules! impl_args { ($($p:ident),*) => { impl<$($p: Variant + Clone),*> FuncArgs for ($($p,)*) { + #[inline] fn into_vec(self) -> StaticVec { let ($($p,)*) = self; diff --git a/src/fn_call.rs b/src/fn_call.rs index c9439637..7dc97319 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -1,7 +1,6 @@ //! Implement function-calling mechanism for `Engine`. use crate::any::Dynamic; -use crate::calc_fn_hash; use crate::engine::{ search_imports, search_namespace, search_scope_only, Engine, Imports, State, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_FN, @@ -16,7 +15,7 @@ use crate::result::EvalAltResult; use crate::scope::Scope; use crate::stdlib::ops::Deref; use crate::token::Position; -use crate::utils::StaticVec; +use crate::{calc_fn_hash, StaticVec}; #[cfg(not(feature = "no_function"))] use crate::{ @@ -439,6 +438,7 @@ impl Engine { } // Has a system function an override? + #[inline] pub(crate) fn has_override_by_name_and_arguments( &self, lib: &Module, @@ -459,6 +459,7 @@ impl Engine { } // Has a system function an override? + #[inline(always)] pub(crate) fn has_override( &self, lib: &Module, diff --git a/src/fn_func.rs b/src/fn_func.rs index 751ade9d..331f7926 100644 --- a/src/fn_func.rs +++ b/src/fn_func.rs @@ -96,6 +96,7 @@ macro_rules! def_anonymous_fn { #[cfg(not(feature = "sync"))] type Output = Box Result>>; + #[inline(always)] fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output { let fn_name = entry_point.to_string(); @@ -104,6 +105,7 @@ macro_rules! def_anonymous_fn { }) } + #[inline(always)] fn create_from_script(self, script: &str, entry_point: &str) -> Result { let ast = self.compile(script)?; Ok(Func::<($($par,)*), RET>::create_from_ast(self, ast, entry_point)) diff --git a/src/fn_native.rs b/src/fn_native.rs index df9e3948..01c1f17b 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -10,7 +10,7 @@ use crate::token::{is_valid_identifier, Position}; use crate::utils::ImmutableString; #[cfg(not(feature = "no_function"))] -use crate::{calc_fn_hash, module::FuncReturn, utils::StaticVec}; +use crate::{calc_fn_hash, module::FuncReturn, StaticVec}; use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, string::String, vec::Vec}; @@ -78,22 +78,27 @@ pub struct FnPtr(ImmutableString, Vec); impl FnPtr { /// Create a new function pointer. + #[inline(always)] pub(crate) fn new_unchecked>(name: S, curry: Vec) -> Self { Self(name.into(), curry) } /// Get the name of the function. + #[inline(always)] pub fn fn_name(&self) -> &str { self.get_fn_name().as_ref() } /// Get the name of the function. + #[inline(always)] pub(crate) fn get_fn_name(&self) -> &ImmutableString { &self.0 } /// Get the underlying data of the function pointer. + #[inline(always)] pub(crate) fn take_data(self) -> (ImmutableString, Vec) { (self.0, self.1) } /// Get the curried arguments. + #[inline(always)] pub fn curry(&self) -> &[Dynamic] { &self.1 } @@ -153,6 +158,7 @@ impl FnPtr { } impl fmt::Display for FnPtr { + #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Fn({})", self.0) } @@ -161,6 +167,7 @@ impl fmt::Display for FnPtr { impl TryFrom for FnPtr { type Error = Box; + #[inline(always)] fn try_from(value: ImmutableString) -> Result { if is_valid_identifier(value.chars()) { Ok(Self(value, Default::default())) @@ -173,6 +180,7 @@ impl TryFrom for FnPtr { impl TryFrom for FnPtr { type Error = Box; + #[inline(always)] fn try_from(value: String) -> Result { let s: ImmutableString = value.into(); Self::try_from(s) @@ -182,6 +190,7 @@ impl TryFrom for FnPtr { impl TryFrom<&str> for FnPtr { type Error = Box; + #[inline(always)] fn try_from(value: &str) -> Result { let s: ImmutableString = value.into(); Self::try_from(s) @@ -399,26 +408,31 @@ impl CallableFunction { } } /// Create a new `CallableFunction::Pure`. + #[inline(always)] pub fn from_pure(func: Box) -> Self { Self::Pure(func.into()) } /// Create a new `CallableFunction::Method`. + #[inline(always)] pub fn from_method(func: Box) -> Self { Self::Method(func.into()) } /// Create a new `CallableFunction::Plugin`. + #[inline(always)] pub fn from_plugin(func: impl PluginFunction + 'static + SendSync) -> Self { Self::Plugin((Box::new(func) as Box).into()) } } impl From for CallableFunction { + #[inline(always)] fn from(func: IteratorFn) -> Self { Self::Iterator(func) } } impl From for CallableFunction { + #[inline(always)] fn from(_func: ScriptFnDef) -> Self { #[cfg(feature = "no_function")] unreachable!(); @@ -429,6 +443,7 @@ impl From for CallableFunction { } impl From> for CallableFunction { + #[inline(always)] fn from(_func: Shared) -> Self { #[cfg(feature = "no_function")] unreachable!(); @@ -439,12 +454,14 @@ impl From> for CallableFunction { } impl From for CallableFunction { + #[inline(always)] fn from(func: T) -> Self { Self::from_plugin(func) } } impl From> for CallableFunction { + #[inline(always)] fn from(func: Shared) -> Self { Self::Plugin(func.into()) } diff --git a/src/fn_register.rs b/src/fn_register.rs index 3df06d5e..d8bbb09a 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -188,6 +188,7 @@ macro_rules! def_register { RET: Variant + Clone > RegisterFn for Engine { + #[inline] fn register_fn(&mut self, name: &str, f: FN) -> &mut Self { self.global_module.set_fn(name, FnAccess::Public, &[$(map_type_id::<$par>()),*], @@ -202,6 +203,7 @@ macro_rules! def_register { FN: Fn($($param),*) -> Result> + SendSync + 'static, > RegisterResultFn for Engine { + #[inline] fn register_result_fn(&mut self, name: &str, f: FN) -> &mut Self { self.global_module.set_fn(name, FnAccess::Public, &[$(map_type_id::<$par>()),*], diff --git a/src/lib.rs b/src/lib.rs index f61af46c..2cb73e0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ //! # Rhai - embedded scripting for Rust //! -//! Rhai is a tiny, simple and very fast embedded scripting language for Rust +//! Rhai is a tiny, simple and fast embedded scripting language for Rust //! that gives you a safe and easy way to add scripting to your applications. //! It provides a familiar syntax based on JavaScript and Rust and a simple Rust interface. //! Here is a quick example. @@ -74,7 +74,7 @@ pub mod plugin; mod result; mod scope; #[cfg(feature = "serde")] -mod serde; +mod serde_impl; mod settings; mod stdlib; mod syntax; @@ -124,21 +124,14 @@ pub use module::ModuleResolver; /// Module containing all built-in _module resolvers_ available to Rhai. #[cfg(not(feature = "no_module"))] -pub mod module_resolvers { - pub use crate::module::resolvers::*; -} +pub use crate::module::resolvers as module_resolvers; -/// _[SERDE]_ Serialization support for [`serde`](https://crates.io/crates/serde). +/// _[SERDE]_ Serialization and deserialization support for [`serde`](https://crates.io/crates/serde). /// Exported under the `serde` feature. #[cfg(feature = "serde")] -pub mod ser { - pub use crate::serde::ser::to_dynamic; -} -/// _[SERDE]_ Deserialization support for [`serde`](https://crates.io/crates/serde). -/// Exported under the `serde` feature. -#[cfg(feature = "serde")] -pub mod de { - pub use crate::serde::de::from_dynamic; +pub mod serde { + pub use super::serde_impl::de::from_dynamic; + pub use super::serde_impl::ser::to_dynamic; } #[cfg(not(feature = "no_optimize"))] @@ -166,6 +159,14 @@ pub use engine::{Imports, Limits, State as EvalState}; #[deprecated(note = "this type is volatile and may change")] pub use module::ModuleRef; +/// _[INTERNALS]_ Alias to [`smallvec::SmallVec<[T; 4]>`](https://crates.io/crates/smallvec), +/// which is a specialized `Vec` backed by a small, fixed-size array when there are <= 4 items stored. +/// Exported under the `internals` feature only. +#[cfg(not(feature = "internals"))] +type StaticVec = smallvec::SmallVec<[T; 4]>; + +/// _[INTERNALS]_ Alias to [`smallvec::SmallVec<[T; 4]>`](https://crates.io/crates/smallvec), +/// which is a specialized `Vec` backed by a small, fixed-size array when there are <= 4 items stored. +/// Exported under the `internals` feature only. #[cfg(feature = "internals")] -#[deprecated(note = "this type is volatile and may change")] -pub use utils::StaticVec; +pub type StaticVec = smallvec::SmallVec<[T; 4]>; diff --git a/src/module/mod.rs b/src/module/mod.rs index 6d9681e6..3056d19b 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1,14 +1,14 @@ //! Module defining external-loaded modules for Rhai. use crate::any::{Dynamic, Variant}; -use crate::calc_fn_hash; use crate::engine::Engine; use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn, SendSync}; use crate::fn_register::by_value as cast_arg; use crate::parser::FnAccess; use crate::result::EvalAltResult; use crate::token::{Position, Token}; -use crate::utils::{ImmutableString, StaticVec, StraightHasherBuilder}; +use crate::utils::{ImmutableString, StraightHasherBuilder}; +use crate::{calc_fn_hash, StaticVec}; #[cfg(not(feature = "no_function"))] use crate::{fn_native::Shared, parser::ScriptFnDef}; @@ -33,7 +33,7 @@ use crate::stdlib::{ fmt, format, iter::empty, num::NonZeroUsize, - ops::{Deref, DerefMut}, + ops::{Add, AddAssign, Deref, DerefMut}, string::{String, ToString}, vec::Vec, }; @@ -103,6 +103,7 @@ impl fmt::Debug for Module { } impl Clone for Module { + #[inline(always)] fn clone(&self) -> Self { // Only clone the index at the top level Self { @@ -115,6 +116,7 @@ impl Clone for Module { } impl AsRef for Module { + #[inline(always)] fn as_ref(&self) -> &Module { self } @@ -132,6 +134,7 @@ impl Module { /// module.set_var("answer", 42_i64); /// assert_eq!(module.get_var_value::("answer").unwrap(), 42); /// ``` + #[inline(always)] pub fn new() -> Self { Default::default() } @@ -147,6 +150,7 @@ impl Module { /// module.set_var("answer", 42_i64); /// assert_eq!(module.get_var_value::("answer").unwrap(), 42); /// ``` + #[inline(always)] pub fn new_with_capacity(capacity: usize) -> Self { Self { functions: HashMap::with_capacity_and_hasher(capacity, StraightHasherBuilder), @@ -164,6 +168,7 @@ impl Module { /// let module = Module::new(); /// assert!(module.is_empty()); /// ``` + #[inline(always)] pub fn is_empty(&self) -> bool { self.functions.is_empty() && self.all_functions.is_empty() @@ -174,6 +179,7 @@ impl Module { } /// Clone the module, optionally skipping the index. + #[inline(always)] fn do_clone(&self, clone_index: bool) -> Self { Self { modules: if clone_index { @@ -202,6 +208,7 @@ impl Module { /// module.set_var("answer", 42_i64); /// assert!(module.contains_var("answer")); /// ``` + #[inline(always)] pub fn contains_var(&self, name: &str) -> bool { self.variables.contains_key(name) } @@ -217,6 +224,7 @@ impl Module { /// module.set_var("answer", 42_i64); /// assert_eq!(module.get_var_value::("answer").unwrap(), 42); /// ``` + #[inline(always)] pub fn get_var_value(&self, name: &str) -> Option { self.get_var(name).and_then(Dynamic::try_cast::) } @@ -232,6 +240,7 @@ impl Module { /// module.set_var("answer", 42_i64); /// assert_eq!(module.get_var("answer").unwrap().cast::(), 42); /// ``` + #[inline(always)] pub fn get_var(&self, name: &str) -> Option { self.variables.get(name).cloned() } @@ -249,6 +258,7 @@ impl Module { /// module.set_var("answer", 42_i64); /// assert_eq!(module.get_var_value::("answer").unwrap(), 42); /// ``` + #[inline(always)] pub fn set_var(&mut self, name: impl Into, value: impl Variant + Clone) -> &mut Self { self.variables.insert(name.into(), Dynamic::from(value)); self.indexed = false; @@ -259,6 +269,7 @@ impl Module { /// Name and Position in `EvalAltResult` are None and must be set afterwards. /// /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. + #[inline(always)] pub(crate) fn get_qualified_var_mut( &mut self, hash_var: u64, @@ -276,6 +287,7 @@ impl Module { /// /// If there is an existing function of the same name and number of arguments, it is replaced. #[cfg(not(feature = "no_function"))] + #[inline] pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) -> u64 { // None + function name + number of arguments. let num_params = fn_def.params.len(); @@ -296,6 +308,7 @@ impl Module { /// Get a script-defined function in the module based on name and number of parameters. #[cfg(not(feature = "no_function"))] + #[inline(always)] pub fn get_script_fn( &self, name: &str, @@ -324,6 +337,7 @@ impl Module { /// module.set_sub_module("question", sub_module); /// assert!(module.contains_sub_module("question")); /// ``` + #[inline(always)] pub fn contains_sub_module(&self, name: &str) -> bool { self.modules.contains_key(name) } @@ -340,6 +354,7 @@ impl Module { /// module.set_sub_module("question", sub_module); /// assert!(module.get_sub_module("question").is_some()); /// ``` + #[inline(always)] pub fn get_sub_module(&self, name: &str) -> Option<&Module> { self.modules.get(name) } @@ -356,6 +371,7 @@ impl Module { /// module.set_sub_module("question", sub_module); /// assert!(module.get_sub_module_mut("question").is_some()); /// ``` + #[inline(always)] pub fn get_sub_module_mut(&mut self, name: &str) -> Option<&mut Module> { self.modules.get_mut(name) } @@ -374,6 +390,7 @@ impl Module { /// module.set_sub_module("question", sub_module); /// assert!(module.get_sub_module("question").is_some()); /// ``` + #[inline(always)] pub fn set_sub_module(&mut self, name: impl Into, sub_module: Module) -> &mut Self { self.modules.insert(name.into(), sub_module.into()); self.indexed = false; @@ -394,6 +411,7 @@ impl Module { /// let hash = module.set_fn_0("calc", || Ok(42_i64)); /// assert!(module.contains_fn(hash, true)); /// ``` + #[inline] pub fn contains_fn(&self, hash_fn: u64, public_only: bool) -> bool { if hash_fn == 0 { false @@ -513,6 +531,7 @@ impl Module { /// /// assert!(module.contains_fn(hash, true)); /// ``` + #[inline] pub fn set_raw_fn( &mut self, name: impl Into, @@ -534,6 +553,7 @@ impl Module { /// are not determined), but the implementation is in Rust. #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_module"))] + #[inline] pub(crate) fn set_raw_fn_as_scripted( &mut self, name: impl Into, @@ -571,6 +591,7 @@ impl Module { /// let hash = module.set_fn_0("calc", || Ok(42_i64)); /// assert!(module.contains_fn(hash, true)); /// ``` + #[inline] pub fn set_fn_0( &mut self, name: impl Into, @@ -599,6 +620,7 @@ impl Module { /// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1)); /// assert!(module.contains_fn(hash, true)); /// ``` + #[inline] pub fn set_fn_1( &mut self, name: impl Into, @@ -629,6 +651,7 @@ impl Module { /// let hash = module.set_fn_1_mut("calc", |x: &mut i64| { *x += 1; Ok(*x) }); /// assert!(module.contains_fn(hash, true)); /// ``` + #[inline] pub fn set_fn_1_mut( &mut self, name: impl Into, @@ -660,6 +683,7 @@ impl Module { /// assert!(module.contains_fn(hash, true)); /// ``` #[cfg(not(feature = "no_object"))] + #[inline] pub fn set_getter_fn( &mut self, name: impl Into, @@ -683,6 +707,7 @@ impl Module { /// }); /// assert!(module.contains_fn(hash, true)); /// ``` + #[inline] pub fn set_fn_2( &mut self, name: impl Into, @@ -719,6 +744,7 @@ impl Module { /// }); /// assert!(module.contains_fn(hash, true)); /// ``` + #[inline] pub fn set_fn_2_mut( &mut self, name: impl Into, @@ -757,6 +783,7 @@ impl Module { /// assert!(module.contains_fn(hash, true)); /// ``` #[cfg(not(feature = "no_object"))] + #[inline] pub fn set_setter_fn( &mut self, name: impl Into, @@ -787,6 +814,7 @@ impl Module { /// assert!(module.contains_fn(hash, true)); /// ``` #[cfg(not(feature = "no_index"))] + #[inline] pub fn set_indexer_get_fn( &mut self, func: impl Fn(&mut A, B) -> FuncReturn + SendSync + 'static, @@ -823,6 +851,7 @@ impl Module { /// }); /// assert!(module.contains_fn(hash, true)); /// ``` + #[inline] pub fn set_fn_3< A: Variant + Clone, B: Variant + Clone, @@ -865,6 +894,7 @@ impl Module { /// }); /// assert!(module.contains_fn(hash, true)); /// ``` + #[inline] pub fn set_fn_3_mut< A: Variant + Clone, B: Variant + Clone, @@ -914,6 +944,7 @@ impl Module { /// assert!(module.contains_fn(hash, true)); /// ``` #[cfg(not(feature = "no_index"))] + #[inline] pub fn set_indexer_set_fn( &mut self, func: impl Fn(&mut A, B, C) -> FuncReturn<()> + SendSync + 'static, @@ -977,6 +1008,7 @@ impl Module { /// assert!(module.contains_fn(hash_set, true)); /// ``` #[cfg(not(feature = "no_index"))] + #[inline] pub fn set_indexer_get_set_fn( &mut self, getter: impl Fn(&mut A, B) -> FuncReturn + SendSync + 'static, @@ -1003,6 +1035,7 @@ impl Module { /// }); /// assert!(module.contains_fn(hash, true)); /// ``` + #[inline] pub fn set_fn_4< A: Variant + Clone, B: Variant + Clone, @@ -1052,6 +1085,7 @@ impl Module { /// }); /// assert!(module.contains_fn(hash, true)); /// ``` + #[inline] pub fn set_fn_4_mut< A: Variant + Clone, B: Variant + Clone, @@ -1089,6 +1123,7 @@ impl Module { /// /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. /// It is also returned by the `set_fn_XXX` calls. + #[inline(always)] pub(crate) fn get_fn(&self, hash_fn: u64, public_only: bool) -> Option<&CallableFunction> { if hash_fn == 0 { None @@ -1108,12 +1143,14 @@ impl Module { /// /// The `u64` hash is calculated by the function `crate::calc_fn_hash` and must match /// the hash calculated by `index_all_sub_modules`. + #[inline(always)] pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&CallableFunction> { self.all_functions.get(&hash_qualified_fn) } /// Combine another module into this module. /// The other module is consumed to merge into this module. + #[inline] pub fn combine(&mut self, other: Self) -> &mut Self { if !other.modules.is_empty() { self.modules.extend(other.modules.into_iter()); @@ -1136,6 +1173,7 @@ impl Module { /// Combine another module into this module. /// The other module is consumed to merge into this module. /// Sub-modules are flattened onto the root module, with higher level overriding lower level. + #[inline] pub fn combine_flatten(&mut self, other: Self) -> &mut Self { if !other.modules.is_empty() { other.modules.into_iter().for_each(|(_, m)| { @@ -1158,6 +1196,7 @@ impl Module { } /// Merge another module into this module. + #[inline(always)] pub fn merge(&mut self, other: &Self) -> &mut Self { self.merge_filtered(other, &mut |_, _, _| true) } @@ -1213,6 +1252,7 @@ impl Module { /// Filter out the functions, retaining only some based on a filter predicate. #[cfg(not(feature = "no_function"))] + #[inline] pub(crate) fn retain_functions( &mut self, mut filter: impl FnMut(FnAccess, &str, usize) -> bool, @@ -1229,6 +1269,7 @@ impl Module { } /// Get the number of variables, functions and type iterators in the module. + #[inline(always)] pub fn count(&self) -> (usize, usize, usize) { ( self.variables.len(), @@ -1238,11 +1279,14 @@ impl Module { } /// Get an iterator to the variables in the module. + #[inline(always)] pub fn iter_var(&self) -> impl Iterator { self.variables.iter() } /// Get an iterator to the functions in the module. + #[cfg(not(feature = "no_optimize"))] + #[inline(always)] pub(crate) fn iter_fn(&self) -> impl Iterator { self.functions.values() } @@ -1255,6 +1299,7 @@ impl Module { /// 3) Number of parameters. /// 4) Shared reference to function definition `ScriptFnDef`. #[cfg(not(feature = "no_function"))] + #[inline(always)] pub(crate) fn iter_script_fn<'a>( &'a self, ) -> impl Iterator)> + 'a { @@ -1277,6 +1322,7 @@ impl Module { /// 3) Number of parameters. #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "internals"))] + #[inline(always)] pub fn iter_script_fn_info(&self) -> impl Iterator { self.functions .values() @@ -1546,6 +1592,31 @@ impl From> for ModuleRef { } } +impl> Add for &Module { + type Output = Module; + + fn add(self, rhs: M) -> Self::Output { + let mut module = self.clone(); + module.merge(rhs.as_ref()); + module + } +} + +impl> Add for Module { + type Output = Self; + + fn add(mut self, rhs: M) -> Self::Output { + self.merge(rhs.as_ref()); + self + } +} + +impl> AddAssign for Module { + fn add_assign(&mut self, rhs: M) { + self.combine(rhs.into()); + } +} + impl ModuleRef { pub(crate) fn index(&self) -> Option { self.1 diff --git a/src/module/resolvers/collection.rs b/src/module/resolvers/collection.rs index e513f51e..6a57e1fe 100644 --- a/src/module/resolvers/collection.rs +++ b/src/module/resolvers/collection.rs @@ -3,7 +3,7 @@ use crate::module::{Module, ModuleResolver}; use crate::result::EvalAltResult; use crate::token::Position; -use crate::stdlib::{boxed::Box, vec::Vec}; +use crate::stdlib::{boxed::Box, ops::AddAssign, vec::Vec}; /// Module resolution service that holds a collection of module resolves, /// to be searched in sequential order. @@ -42,24 +42,48 @@ impl ModuleResolversCollection { /// let mut engine = Engine::new(); /// engine.set_module_resolver(Some(collection)); /// ``` + #[inline(always)] pub fn new() -> Self { Default::default() } -} - -impl ModuleResolversCollection { /// Add a module keyed by its path. + #[inline(always)] pub fn push(&mut self, resolver: impl ModuleResolver + 'static) { self.0.push(Box::new(resolver)); } /// Get an iterator of all the module resolvers. + #[inline(always)] pub fn iter(&self) -> impl Iterator { self.0.iter().map(|v| v.as_ref()) } + /// Get a mutable iterator of all the modules. + #[inline(always)] + pub fn into_iter(self) -> impl Iterator> { + self.0.into_iter() + } /// Remove all module resolvers. + #[inline(always)] pub fn clear(&mut self) { self.0.clear(); } + /// Is this `ModuleResolversCollection` empty? + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + /// Get the number of module resolvers in this `ModuleResolversCollection`. + #[inline(always)] + pub fn len(&self) -> usize { + self.0.len() + } + /// Add another `ModuleResolversCollection` to the end of this collection. + /// The other `ModuleResolversCollection` is consumed. + #[inline(always)] + pub fn append(&mut self, other: Self) { + if !other.is_empty() { + self.0.extend(other.0.into_iter()); + } + } } impl ModuleResolver for ModuleResolversCollection { @@ -83,3 +107,10 @@ impl ModuleResolver for ModuleResolversCollection { EvalAltResult::ErrorModuleNotFound(path.into(), pos).into() } } + +impl AddAssign for ModuleResolversCollection { + #[inline(always)] + fn add_assign(&mut self, rhs: M) { + self.push(rhs); + } +} diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index 11bfb5b1..33204dd8 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -48,6 +48,7 @@ pub struct FileModuleResolver { } impl Default for FileModuleResolver { + #[inline(always)] fn default() -> Self { Self::new_with_path(PathBuf::default()) } @@ -69,6 +70,7 @@ impl FileModuleResolver { /// let mut engine = Engine::new(); /// engine.set_module_resolver(Some(resolver)); /// ``` + #[inline(always)] pub fn new_with_path>(path: P) -> Self { Self::new_with_path_and_extension(path, "rhai") } @@ -90,6 +92,7 @@ impl FileModuleResolver { /// let mut engine = Engine::new(); /// engine.set_module_resolver(Some(resolver)); /// ``` + #[inline(always)] pub fn new_with_path_and_extension, E: Into>( path: P, extension: E, @@ -116,11 +119,13 @@ impl FileModuleResolver { /// let mut engine = Engine::new(); /// engine.set_module_resolver(Some(resolver)); /// ``` + #[inline(always)] pub fn new() -> Self { Default::default() } /// Create a `Module` from a file path. + #[inline(always)] pub fn create_module>( &self, engine: &Engine, diff --git a/src/module/resolvers/stat.rs b/src/module/resolvers/stat.rs index 563f5074..be556d56 100644 --- a/src/module/resolvers/stat.rs +++ b/src/module/resolvers/stat.rs @@ -3,7 +3,7 @@ use crate::module::{Module, ModuleResolver}; use crate::result::EvalAltResult; use crate::token::Position; -use crate::stdlib::{boxed::Box, collections::HashMap, string::String}; +use crate::stdlib::{boxed::Box, collections::HashMap, ops::AddAssign, string::String}; /// Module resolution service that serves modules added into it. /// @@ -42,51 +42,82 @@ impl StaticModuleResolver { /// let mut engine = Engine::new(); /// engine.set_module_resolver(Some(resolver)); /// ``` + #[inline(always)] pub fn new() -> Self { Default::default() } -} - -impl StaticModuleResolver { /// Add a module keyed by its path. + #[inline(always)] pub fn insert>(&mut self, path: S, module: Module) { self.0.insert(path.into(), module); } /// Remove a module given its path. + #[inline(always)] pub fn remove(&mut self, path: &str) -> Option { self.0.remove(path) } /// Does the path exist? + #[inline(always)] pub fn contains_path(&self, path: &str) -> bool { self.0.contains_key(path) } /// Get an iterator of all the modules. + #[inline(always)] pub fn iter(&self) -> impl Iterator { self.0.iter().map(|(k, v)| (k.as_str(), v)) } /// Get a mutable iterator of all the modules. + #[inline(always)] pub fn iter_mut(&mut self) -> impl Iterator { self.0.iter_mut().map(|(k, v)| (k.as_str(), v)) } + /// Get a mutable iterator of all the modules. + #[inline(always)] + pub fn into_iter(self) -> impl Iterator { + self.0.into_iter() + } /// Get an iterator of all the module paths. + #[inline(always)] pub fn paths(&self) -> impl Iterator { self.0.keys().map(String::as_str) } /// Get an iterator of all the modules. + #[inline(always)] pub fn values(&self) -> impl Iterator { self.0.values() } /// Get a mutable iterator of all the modules. + #[inline(always)] pub fn values_mut(&mut self) -> impl Iterator { self.0.values_mut() } /// Remove all modules. + #[inline(always)] pub fn clear(&mut self) { self.0.clear(); } + /// Is this `StaticModuleResolver` empty? + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + /// Get the number of modules in this `StaticModuleResolver`. + #[inline(always)] + pub fn len(&self) -> usize { + self.0.len() + } + /// Merge another `StaticModuleResolver` into this. + /// The other `StaticModuleResolver` is consumed. + #[inline(always)] + pub fn merge(&mut self, other: Self) { + if !other.is_empty() { + self.0.extend(other.0.into_iter()); + } + } } impl ModuleResolver for StaticModuleResolver { + #[inline(always)] fn resolve(&self, _: &Engine, path: &str, pos: Position) -> Result> { self.0 .get(path) @@ -94,3 +125,10 @@ impl ModuleResolver for StaticModuleResolver { .ok_or_else(|| EvalAltResult::ErrorModuleNotFound(path.into(), pos).into()) } } + +impl AddAssign for StaticModuleResolver { + #[inline(always)] + fn add_assign(&mut self, rhs: Self) { + self.merge(rhs); + } +} diff --git a/src/optimize.rs b/src/optimize.rs index 73c5e015..a314cdac 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1,17 +1,16 @@ //! Module implementing the AST optimizer. use crate::any::Dynamic; -use crate::calc_fn_hash; use crate::engine::{ - Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_IS_DEF_FN, KEYWORD_IS_DEF_VAR, - KEYWORD_PRINT, KEYWORD_TYPE_OF, + Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_IS_DEF_FN, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, + KEYWORD_TYPE_OF, }; use crate::fn_call::run_builtin_binary_op; -use crate::fn_native::FnPtr; use crate::module::Module; use crate::parser::{map_dynamic_to_expr, Expr, ScriptFnDef, Stmt, AST}; -use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; -use crate::utils::StaticVec; +use crate::scope::{Entry as ScopeEntry, Scope}; +use crate::token::{is_valid_identifier, Position}; +use crate::{calc_fn_hash, StaticVec}; #[cfg(not(feature = "no_function"))] use crate::parser::ReturnType; @@ -21,7 +20,6 @@ use crate::parser::CustomExpr; use crate::stdlib::{ boxed::Box, - convert::TryFrom, iter::empty, string::{String, ToString}, vec, @@ -44,22 +42,26 @@ pub enum OptimizationLevel { impl OptimizationLevel { /// Is the `OptimizationLevel` None. + #[inline(always)] pub fn is_none(self) -> bool { self == Self::None } /// Is the `OptimizationLevel` Simple. #[cfg(not(feature = "no_optimize"))] + #[inline(always)] pub fn is_simple(self) -> bool { self == Self::Simple } /// Is the `OptimizationLevel` Full. #[cfg(not(feature = "no_optimize"))] + #[inline(always)] pub fn is_full(self) -> bool { self == Self::Full } } /// Mutable state throughout an optimization pass. +#[derive(Debug, Clone)] struct State<'a> { /// Has the AST been changed during this pass? changed: bool, @@ -75,6 +77,7 @@ struct State<'a> { impl<'a> State<'a> { /// Create a new State. + #[inline(always)] pub fn new(engine: &'a Engine, lib: &'a Module, level: OptimizationLevel) -> Self { Self { changed: false, @@ -85,30 +88,37 @@ impl<'a> State<'a> { } } /// Reset the state from dirty to clean. + #[inline(always)] pub fn reset(&mut self) { self.changed = false; } /// Set the AST state to be dirty (i.e. changed). + #[inline(always)] pub fn set_dirty(&mut self) { self.changed = true; } /// Is the AST dirty (i.e. changed)? + #[inline(always)] pub fn is_dirty(&self) -> bool { self.changed } /// Does a constant exist? + #[inline(always)] pub fn contains_constant(&self, name: &str) -> bool { self.constants.iter().any(|(n, _)| n == name) } /// Prune the list of constants back to a specified size. + #[inline(always)] pub fn restore_constants(&mut self, len: usize) { self.constants.truncate(len) } /// Add a new constant to the list. + #[inline(always)] pub fn push_constant(&mut self, name: &str, value: Expr) { self.constants.push((name.into(), value)) } /// Look up a constant from the list. + #[inline] pub fn find_constant(&self, name: &str) -> Option<&Expr> { for (n, expr) in self.constants.iter().rev() { if n == name { @@ -271,12 +281,24 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { let mut result: Vec<_> = x.0.into_iter() .map(|stmt| match stmt { - // Add constant into the state - Stmt::Const(v) => { - let ((name, pos), expr, _) = *v; - state.push_constant(&name, expr); - state.set_dirty(); - Stmt::Noop(pos) // No need to keep constants + // Add constant literals into the state + Stmt::Const(mut v) => { + if let Some(expr) = v.1 { + let expr = optimize_expr(expr, state); + + if expr.is_literal() { + state.set_dirty(); + state.push_constant(&(v.0).0, expr); + Stmt::Noop(pos) // No need to keep constants + } else { + v.1 = Some(expr); + Stmt::Const(v) + } + } else { + state.set_dirty(); + state.push_constant(&(v.0).0, Expr::Unit((v.0).1)); + Stmt::Noop(pos) // No need to keep constants + } } // Optimize the statement _ => optimize_stmt(stmt, state, preserve_result), @@ -299,8 +321,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { while let Some(expr) = result.pop() { match expr { - Stmt::Let(x) if x.1.is_none() => removed = true, - Stmt::Let(x) if x.1.is_some() => removed = x.1.unwrap().is_pure(), + Stmt::Let(x) => removed = x.1.as_ref().map(Expr::is_pure).unwrap_or(true), #[cfg(not(feature = "no_module"))] Stmt::Import(x) => removed = x.0.is_pure(), _ => { @@ -334,9 +355,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { } match stmt { - Stmt::ReturnWithVal(_) | Stmt::Break(_) => { - dead_code = true; - } + Stmt::ReturnWithVal(_) | Stmt::Break(_) => dead_code = true, _ => (), } @@ -387,11 +406,11 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // These keywords are handled specially const DONT_EVAL_KEYWORDS: &[&str] = &[ - KEYWORD_PRINT, - KEYWORD_DEBUG, - KEYWORD_EVAL, - KEYWORD_IS_DEF_FN, - KEYWORD_IS_DEF_VAR, + KEYWORD_PRINT, // side effects + KEYWORD_DEBUG, // side effects + KEYWORD_EVAL, // arbitrary scripts + KEYWORD_IS_DEF_FN, // functions collection is volatile + KEYWORD_IS_DEF_VAR, // variables scope is volatile ]; match expr { @@ -553,35 +572,18 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { }, // Do not call some special keywords - Expr::FnCall(mut x) if DONT_EVAL_KEYWORDS.contains(&(x.0).0.as_ref())=> { + Expr::FnCall(mut x) if DONT_EVAL_KEYWORDS.contains(&(x.0).0.as_ref()) => { x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect(); Expr::FnCall(x) } - // Fn("...") - Expr::FnCall(x) - if x.1.is_none() - && (x.0).0 == KEYWORD_FN_PTR - && x.3.len() == 1 - && matches!(x.3[0], Expr::StringConstant(_)) - => { - if let Expr::StringConstant(s) = &x.3[0] { - if let Ok(fn_ptr) = FnPtr::try_from(s.0.as_str()) { - Expr::FnPointer(Box::new((fn_ptr.take_data().0, s.1))) - } else { - Expr::FnCall(x) - } - } else { - unreachable!() - } - } - - // Call built-in functions + // Call built-in operators Expr::FnCall(mut x) if x.1.is_none() // Non-qualified && state.optimization_level == OptimizationLevel::Simple // simple optimizations && x.3.len() == 2 // binary call && x.3.iter().all(Expr::is_constant) // all arguments are constants + && !is_valid_identifier((x.0).0.chars()) // cannot be scripted => { let ((name, _, _, pos), _, _, args, _) = x.as_mut(); @@ -698,14 +700,21 @@ fn optimize( // Add constants from the scope into the state scope .to_iter() - .filter(|ScopeEntry { typ, expr, .. }| { - // Get all the constants with definite constant expressions - *typ == ScopeEntryType::Constant - && expr.as_ref().map(|v| v.is_constant()).unwrap_or(false) - }) - .for_each(|ScopeEntry { name, expr, .. }| { - state.push_constant(name.as_ref(), expr.as_ref().unwrap().as_ref().clone()) - }); + // Get all the constants that can be made into a constant literal. + .filter(|ScopeEntry { typ, .. }| typ.is_constant()) + .for_each( + |ScopeEntry { + name, expr, value, .. + }| { + if let Some(val) = expr + .as_ref() + .map(|expr| expr.as_ref().clone()) + .or_else(|| map_dynamic_to_expr(value.clone(), Position::none())) + { + state.push_constant(name.as_ref(), val); + } + }, + ); let orig_constants_len = state.constants.len(); @@ -722,12 +731,28 @@ fn optimize( .into_iter() .enumerate() .map(|(i, stmt)| { - match &stmt { - Stmt::Const(v) => { + match stmt { + Stmt::Const(mut v) => { // Load constants - let ((name, _), expr, _) = v.as_ref(); - state.push_constant(&name, expr.clone()); - stmt // Keep it in the global scope + if let Some(expr) = v.1 { + let expr = optimize_expr(expr, &mut state); + + if expr.is_literal() { + state.push_constant(&(v.0).0, expr.clone()); + } + + v.1 = if expr.is_unit() { + state.set_dirty(); + None + } else { + Some(expr) + }; + } else { + state.push_constant(&(v.0).0, Expr::Unit((v.0).1)); + } + + // Keep it in the global scope + Stmt::Const(v) } _ => { // Keep all variable declarations at this level diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index f963e778..c848fbe3 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -16,8 +16,8 @@ use num_traits::float::Float; use crate::stdlib::{format, string::String}; #[inline(always)] -pub fn make_err(msg: String) -> Box { - EvalAltResult::ErrorArithmetic(msg, Position::none()).into() +pub fn make_err(msg: impl Into) -> Box { + EvalAltResult::ErrorArithmetic(msg.into(), Position::none()).into() } macro_rules! gen_arithmetic_functions { @@ -28,7 +28,6 @@ macro_rules! gen_arithmetic_functions { #[export_module] pub mod functions { #[rhai_fn(name = "+", return_raw)] - #[inline] pub fn add(x: $arg_type, y: $arg_type) -> Result> { if cfg!(not(feature = "unchecked")) { x.checked_add(y).ok_or_else(|| make_err(format!("Addition overflow: {} + {}", x, y))).map(Dynamic::from) @@ -37,7 +36,6 @@ macro_rules! gen_arithmetic_functions { } } #[rhai_fn(name = "-", return_raw)] - #[inline] pub fn subtract(x: $arg_type, y: $arg_type) -> Result> { if cfg!(not(feature = "unchecked")) { x.checked_sub(y).ok_or_else(|| make_err(format!("Subtraction overflow: {} - {}", x, y))).map(Dynamic::from) @@ -46,7 +44,6 @@ macro_rules! gen_arithmetic_functions { } } #[rhai_fn(name = "*", return_raw)] - #[inline] pub fn multiply(x: $arg_type, y: $arg_type) -> Result> { if cfg!(not(feature = "unchecked")) { x.checked_mul(y).ok_or_else(|| make_err(format!("Multiplication overflow: {} * {}", x, y))).map(Dynamic::from) @@ -55,7 +52,6 @@ macro_rules! gen_arithmetic_functions { } } #[rhai_fn(name = "/", return_raw)] - #[inline] pub fn divide(x: $arg_type, y: $arg_type) -> Result> { if cfg!(not(feature = "unchecked")) { // Detect division by zero @@ -69,7 +65,6 @@ macro_rules! gen_arithmetic_functions { } } #[rhai_fn(name = "%", return_raw)] - #[inline] pub fn modulo(x: $arg_type, y: $arg_type) -> Result> { if cfg!(not(feature = "unchecked")) { x.checked_rem(y).ok_or_else(|| make_err(format!("Modulo division by zero or overflow: {} % {}", x, y))).map(Dynamic::from) @@ -78,7 +73,6 @@ macro_rules! gen_arithmetic_functions { } } #[rhai_fn(name = "~", return_raw)] - #[inline] pub fn power(x: INT, y: INT) -> Result> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { @@ -94,7 +88,6 @@ macro_rules! gen_arithmetic_functions { } #[rhai_fn(name = "<<", return_raw)] - #[inline] pub fn shift_left(x: $arg_type, y: INT) -> Result> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { @@ -109,7 +102,6 @@ macro_rules! gen_arithmetic_functions { } } #[rhai_fn(name = ">>", return_raw)] - #[inline] pub fn shift_right(x: $arg_type, y: INT) -> Result> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { @@ -124,17 +116,14 @@ macro_rules! gen_arithmetic_functions { } } #[rhai_fn(name = "&")] - #[inline(always)] pub fn binary_and(x: $arg_type, y: $arg_type) -> $arg_type { x & y } #[rhai_fn(name = "|")] - #[inline(always)] pub fn binary_or(x: $arg_type, y: $arg_type) -> $arg_type { x | y } #[rhai_fn(name = "^")] - #[inline(always)] pub fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type { x ^ y } @@ -151,7 +140,6 @@ macro_rules! gen_signed_functions { #[export_module] pub mod functions { #[rhai_fn(name = "-", return_raw)] - #[inline] pub fn neg(x: $arg_type) -> Result> { if cfg!(not(feature = "unchecked")) { x.checked_neg().ok_or_else(|| make_err(format!("Negation overflow: -{}", x))).map(Dynamic::from) @@ -160,7 +148,6 @@ macro_rules! gen_signed_functions { } } #[rhai_fn(return_raw)] - #[inline] pub fn abs(x: $arg_type) -> Result> { if cfg!(not(feature = "unchecked")) { x.checked_abs().ok_or_else(|| make_err(format!("Negation overflow: -{}", x))).map(Dynamic::from) @@ -168,7 +155,6 @@ macro_rules! gen_signed_functions { Ok(Dynamic::from(x.abs())) } } - #[inline] pub fn sign(x: $arg_type) -> INT { if x == 0 { 0 @@ -239,40 +225,32 @@ gen_signed_functions!(signed_num_128 => i128); #[export_module] mod f32_functions { #[rhai_fn(name = "+")] - #[inline(always)] pub fn add(x: f32, y: f32) -> f32 { x + y } #[rhai_fn(name = "-")] - #[inline(always)] pub fn subtract(x: f32, y: f32) -> f32 { x - y } #[rhai_fn(name = "*")] - #[inline(always)] pub fn multiply(x: f32, y: f32) -> f32 { x * y } #[rhai_fn(name = "/")] - #[inline(always)] pub fn divide(x: f32, y: f32) -> f32 { x / y } #[rhai_fn(name = "%")] - #[inline(always)] pub fn modulo(x: f32, y: f32) -> f32 { x % y } #[rhai_fn(name = "-")] - #[inline(always)] pub fn neg(x: f32) -> f32 { -x } - #[inline(always)] pub fn abs(x: f32) -> f32 { x.abs() } - #[inline] pub fn sign(x: f32) -> INT { if x == 0.0 { 0 @@ -283,12 +261,10 @@ mod f32_functions { } } #[rhai_fn(name = "~", return_raw)] - #[inline(always)] pub fn pow_f_f(x: f32, y: f32) -> Result> { Ok(Dynamic::from(x.powf(y))) } #[rhai_fn(name = "~", return_raw)] - #[inline] pub fn pow_f_i(x: f32, y: INT) -> Result> { if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) { Err(make_err(format!( @@ -305,15 +281,12 @@ mod f32_functions { #[export_module] mod f64_functions { #[rhai_fn(name = "-")] - #[inline(always)] pub fn neg(x: f64) -> f64 { -x } - #[inline(always)] pub fn abs(x: f64) -> f64 { x.abs() } - #[inline] pub fn sign(x: f64) -> INT { if x == 0.0 { 0 @@ -324,7 +297,6 @@ mod f64_functions { } } #[rhai_fn(name = "~", return_raw)] - #[inline] pub fn pow_f_i(x: FLOAT, y: INT) -> Result> { if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) { Err(make_err(format!( diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 1647a156..d1f497f9 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -29,7 +29,6 @@ macro_rules! gen_array_functions { #[export_module] pub mod functions { #[rhai_fn(name = "push", name = "+=")] - #[inline(always)] pub fn push(list: &mut Array, item: $arg_type) { list.push(Dynamic::from(item)); } @@ -89,22 +88,18 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { #[export_module] mod array_functions { #[rhai_fn(name = "len", get = "len")] - #[inline(always)] pub fn len(list: &mut Array) -> INT { list.len() as INT } #[rhai_fn(name = "append", name = "+=")] - #[inline(always)] pub fn append(x: &mut Array, y: Array) { x.extend(y); } #[rhai_fn(name = "+")] - #[inline] pub fn concat(mut x: Array, y: Array) -> Array { x.extend(y); x } - #[inline] pub fn pop(list: &mut Array) -> Dynamic { list.pop().unwrap_or_else(|| ().into()) } @@ -122,7 +117,6 @@ mod array_functions { list.remove(len as usize) } } - #[inline(always)] pub fn clear(list: &mut Array) { list.clear(); } @@ -133,7 +127,6 @@ mod array_functions { list.clear(); } } - #[inline(always)] pub fn reverse(list: &mut Array) { list.reverse(); } diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index 5ffb6cb1..495a08c3 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -9,7 +9,6 @@ def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, { #[export_module] mod fn_ptr_functions { #[rhai_fn(name = "name", get = "name")] - #[inline(always)] pub fn name(f: &mut FnPtr) -> ImmutableString { f.get_fn_name().clone() } diff --git a/src/packages/logic.rs b/src/packages/logic.rs index 554f30e3..ec8cf9a7 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -9,32 +9,26 @@ macro_rules! gen_cmp_functions { #[export_module] pub mod functions { #[rhai_fn(name = "<")] - #[inline(always)] pub fn lt(x: $arg_type, y: $arg_type) -> bool { x < y } #[rhai_fn(name = "<=")] - #[inline(always)] pub fn lte(x: $arg_type, y: $arg_type) -> bool { x <= y } #[rhai_fn(name = ">")] - #[inline(always)] pub fn gt(x: $arg_type, y: $arg_type) -> bool { x > y } #[rhai_fn(name = ">=")] - #[inline(always)] pub fn gte(x: $arg_type, y: $arg_type) -> bool { x >= y } #[rhai_fn(name = "==")] - #[inline(always)] pub fn eq(x: $arg_type, y: $arg_type) -> bool { x == y } #[rhai_fn(name = "!=")] - #[inline(always)] pub fn ne(x: $arg_type, y: $arg_type) -> bool { x != y } @@ -67,7 +61,6 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, { // Logic operators #[export_fn] -#[inline(always)] fn not(x: bool) -> bool { !x } diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 61e3e330..099f546c 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -14,19 +14,15 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { #[export_module] mod map_functions { - #[inline(always)] pub fn has(map: &mut Map, prop: ImmutableString) -> bool { map.contains_key(&prop) } - #[inline(always)] pub fn len(map: &mut Map) -> INT { map.len() as INT } - #[inline(always)] pub fn clear(map: &mut Map) { map.clear(); } - #[inline(always)] pub fn remove(x: &mut Map, name: ImmutableString) -> Dynamic { x.remove(&name).unwrap_or_else(|| ().into()) } diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 45ce2fe3..ae665b18 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -31,7 +31,6 @@ macro_rules! gen_conversion_functions { use super::super::*; #[export_fn] - #[inline(always)] pub fn $func_name(x: $arg_type) -> $result_type { x as $result_type } @@ -86,51 +85,39 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { mod trig_functions { use crate::parser::FLOAT; - #[inline(always)] pub fn sin(x: FLOAT) -> FLOAT { x.to_radians().sin() } - #[inline(always)] pub fn cos(x: FLOAT) -> FLOAT { x.to_radians().cos() } - #[inline(always)] pub fn tan(x: FLOAT) -> FLOAT { x.to_radians().tan() } - #[inline(always)] pub fn sinh(x: FLOAT) -> FLOAT { x.to_radians().sinh() } - #[inline(always)] pub fn cosh(x: FLOAT) -> FLOAT { x.to_radians().cosh() } - #[inline(always)] pub fn tanh(x: FLOAT) -> FLOAT { x.to_radians().tanh() } - #[inline(always)] pub fn asin(x: FLOAT) -> FLOAT { x.asin().to_degrees() } - #[inline(always)] pub fn acos(x: FLOAT) -> FLOAT { x.acos().to_degrees() } - #[inline(always)] pub fn atan(x: FLOAT) -> FLOAT { x.atan().to_degrees() } - #[inline(always)] pub fn asinh(x: FLOAT) -> FLOAT { x.asinh().to_degrees() } - #[inline(always)] pub fn acosh(x: FLOAT) -> FLOAT { x.acosh().to_degrees() } - #[inline(always)] pub fn atanh(x: FLOAT) -> FLOAT { x.atanh().to_degrees() } @@ -141,68 +128,54 @@ mod trig_functions { mod float_functions { use crate::parser::FLOAT; - #[inline(always)] pub fn sqrt(x: FLOAT) -> FLOAT { x.sqrt() } - #[inline(always)] pub fn exp(x: FLOAT) -> FLOAT { x.exp() } - #[inline(always)] pub fn ln(x: FLOAT) -> FLOAT { x.ln() } - #[inline(always)] pub fn log(x: FLOAT, base: FLOAT) -> FLOAT { x.log(base) } - #[inline(always)] pub fn log10(x: FLOAT) -> FLOAT { x.log10() } #[rhai_fn(name = "floor", get = "floor")] - #[inline(always)] pub fn floor(x: FLOAT) -> FLOAT { x.floor() } #[rhai_fn(name = "ceiling", get = "ceiling")] - #[inline(always)] pub fn ceiling(x: FLOAT) -> FLOAT { x.ceil() } #[rhai_fn(name = "round", get = "round")] - #[inline(always)] pub fn round(x: FLOAT) -> FLOAT { x.ceil() } #[rhai_fn(name = "int", get = "int")] - #[inline(always)] pub fn int(x: FLOAT) -> FLOAT { x.trunc() } #[rhai_fn(name = "fraction", get = "fraction")] - #[inline(always)] pub fn fraction(x: FLOAT) -> FLOAT { x.fract() } #[rhai_fn(name = "is_nan", get = "is_nan")] - #[inline(always)] pub fn is_nan(x: FLOAT) -> bool { x.is_nan() } #[rhai_fn(name = "is_finite", get = "is_finite")] - #[inline(always)] pub fn is_finite(x: FLOAT) -> bool { x.is_finite() } #[rhai_fn(name = "is_infinite", get = "is_infinite")] - #[inline(always)] pub fn is_infinite(x: FLOAT) -> bool { x.is_infinite() } #[rhai_fn(name = "to_int", return_raw)] - #[inline] pub fn f32_to_int(x: f32) -> Result> { if cfg!(not(feature = "unchecked")) && x > (MAX_INT as f32) { EvalAltResult::ErrorArithmetic( @@ -215,7 +188,6 @@ mod float_functions { } } #[rhai_fn(name = "to_int", return_raw)] - #[inline] pub fn f64_to_int(x: f64) -> Result> { if cfg!(not(feature = "unchecked")) && x > (MAX_INT as f64) { EvalAltResult::ErrorArithmetic( @@ -229,7 +201,6 @@ mod float_functions { } #[rhai_fn(return_raw)] - #[inline] pub fn parse_float(s: &str) -> Result> { s.trim() .parse::() @@ -291,7 +262,6 @@ fn parse_int_radix(s: &str, radix: INT) -> Result> { } #[export_fn(return_raw)] -#[inline(always)] fn parse_int(s: &str) -> Result> { parse_int_radix(s, 10) } diff --git a/src/packages/mod.rs b/src/packages/mod.rs index 1c6dc3a9..d7c7d023 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -2,7 +2,7 @@ use crate::fn_native::{CallableFunction, IteratorFn, Shared}; use crate::module::Module; -use crate::utils::StaticVec; +use crate::StaticVec; use crate::stdlib::any::TypeId; diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index de7a0484..2733532d 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -26,7 +26,6 @@ macro_rules! gen_functions { use super::super::*; #[export_fn] - #[inline(always)] pub fn to_string_func(x: &mut $arg_type) -> ImmutableString { super::super::$fn_name(x) } @@ -124,30 +123,24 @@ gen_functions!(print_array => to_debug(Array)); // Register print and debug #[export_fn] -#[inline(always)] fn print_empty_string() -> ImmutableString { "".to_string().into() } #[export_fn] -#[inline(always)] fn print_unit(_x: ()) -> ImmutableString { "".to_string().into() } #[export_fn] -#[inline(always)] fn print_string(s: ImmutableString) -> ImmutableString { s } #[export_fn] -#[inline(always)] fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString { to_string(f) } -#[inline(always)] fn to_string(x: &mut T) -> ImmutableString { x.to_string().into() } -#[inline] fn to_debug(x: &mut T) -> ImmutableString { format!("{:?}", x).into() } @@ -155,7 +148,7 @@ fn to_debug(x: &mut T) -> ImmutableString { #[cfg(not(feature = "no_object"))] mod format_map { use super::*; - #[inline] + #[export_fn] pub fn format_map(x: &mut Map) -> ImmutableString { format!("#{:?}", x).into() diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 331ff4d9..e89e03f8 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -6,7 +6,7 @@ use crate::engine::Engine; use crate::fn_native::FnPtr; use crate::parser::{ImmutableString, INT}; use crate::plugin::*; -use crate::utils::StaticVec; +use crate::StaticVec; #[cfg(not(feature = "unchecked"))] use crate::{result::EvalAltResult, token::Position}; @@ -23,13 +23,11 @@ macro_rules! gen_concat_functions { #[export_module] pub mod functions { #[rhai_fn(name = "+")] - #[inline] pub fn append_func(x: &str, y: $arg_type) -> String { format!("{}{}", x, y) } #[rhai_fn(name = "+")] - #[inline] pub fn prepend_func(x: &mut $arg_type, y: &str) -> String { format!("{}{}", x, y) } @@ -133,34 +131,28 @@ gen_concat_functions!(float => f32, f64); #[export_module] mod string_functions { #[rhai_fn(name = "+")] - #[inline(always)] pub fn add_append_unit(s: ImmutableString, _x: ()) -> ImmutableString { s } #[rhai_fn(name = "+")] - #[inline(always)] pub fn add_prepend_unit(_x: (), s: ImmutableString) -> ImmutableString { s } #[rhai_fn(name = "+=")] - #[inline(always)] pub fn append_char(s: &mut ImmutableString, ch: char) { *s += ch; } #[rhai_fn(name = "+=")] - #[inline(always)] pub fn append_string(s: &mut ImmutableString, add: ImmutableString) { *s += &add; } #[rhai_fn(name = "len", get = "len")] - #[inline(always)] pub fn len(s: &str) -> INT { s.chars().count() as INT } - #[inline(always)] pub fn clear(s: &mut ImmutableString) { s.make_mut().clear(); } @@ -183,11 +175,9 @@ mod string_functions { } #[rhai_fn(name = "contains")] - #[inline(always)] pub fn contains_char(s: &str, ch: char) -> bool { s.contains(ch) } - #[inline(always)] pub fn contains(s: &str, find: ImmutableString) -> bool { s.contains(find.as_str()) } @@ -263,7 +253,6 @@ mod string_functions { .into() } #[rhai_fn(name = "sub_string")] - #[inline(always)] pub fn sub_string_starting_from(s: &str, start: INT) -> ImmutableString { let len = s.len() as INT; sub_string(s, start, len) @@ -296,28 +285,23 @@ mod string_functions { copy.extend(chars.iter().skip(offset).take(len)); } #[rhai_fn(name = "crop")] - #[inline(always)] pub fn crop_string_starting_from(s: &mut ImmutableString, start: INT) { crop(s, start, s.len() as INT); } #[rhai_fn(name = "replace")] - #[inline(always)] pub fn replace(s: &mut ImmutableString, find: ImmutableString, sub: ImmutableString) { *s = s.replace(find.as_str(), sub.as_str()).into(); } #[rhai_fn(name = "replace")] - #[inline(always)] pub fn replace_string_with_char(s: &mut ImmutableString, find: ImmutableString, sub: char) { *s = s.replace(find.as_str(), &sub.to_string()).into(); } #[rhai_fn(name = "replace")] - #[inline(always)] pub fn replace_char_with_string(s: &mut ImmutableString, find: char, sub: ImmutableString) { *s = s.replace(&find.to_string(), sub.as_str()).into(); } #[rhai_fn(name = "replace")] - #[inline(always)] pub fn replace_char(s: &mut ImmutableString, find: char, sub: char) { *s = s.replace(&find.to_string(), &sub.to_string()).into(); } @@ -327,24 +311,20 @@ mod string_functions { use crate::engine::Array; #[rhai_fn(name = "+")] - #[inline] pub fn append(x: &str, y: Array) -> String { format!("{}{:?}", x, y) } #[rhai_fn(name = "+")] - #[inline] pub fn prepend(x: &mut Array, y: &str) -> String { format!("{:?}{}", x, y) } - #[inline(always)] pub fn split(s: &str, delimiter: ImmutableString) -> Array { s.split(delimiter.as_str()) .map(Into::::into) .collect() } #[rhai_fn(name = "split")] - #[inline(always)] pub fn split_char(s: &str, delimiter: char) -> Array { s.split(delimiter).map(Into::::into).collect() } @@ -355,12 +335,10 @@ mod string_functions { use crate::engine::Map; #[rhai_fn(name = "+")] - #[inline] pub fn append(x: &str, y: Map) -> String { format!("{}#{:?}", x, y) } #[rhai_fn(name = "+")] - #[inline] pub fn prepend(x: &mut Map, y: &str) -> String { format!("#{:?}{}", x, y) } diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 1fee278b..65b3ffaf 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -26,7 +26,6 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, { #[export_module] mod time_functions { - #[inline(always)] pub fn timestamp() -> Instant { Instant::now() } @@ -35,7 +34,11 @@ mod time_functions { pub fn elapsed(timestamp: &mut Instant) -> Result> { #[cfg(not(feature = "no_float"))] { - Ok((timestamp.elapsed().as_secs_f64() as FLOAT).into()) + if *timestamp > Instant::now() { + Err(make_arithmetic_err("Time-stamp is later than now")) + } else { + Ok((timestamp.elapsed().as_secs_f64() as FLOAT).into()) + } } #[cfg(feature = "no_float")] @@ -47,6 +50,8 @@ mod time_functions { "Integer overflow for timestamp.elapsed: {}", seconds ))) + } else if *timestamp > Instant::now() { + Err(make_arithmetic_err("Time-stamp is later than now")) } else { Ok((seconds as INT).into()) } @@ -212,32 +217,26 @@ mod time_functions { } #[rhai_fn(name = "==")] - #[inline(always)] pub fn eq(x: Instant, y: Instant) -> bool { x == y } #[rhai_fn(name = "!=")] - #[inline(always)] pub fn ne(x: Instant, y: Instant) -> bool { x != y } #[rhai_fn(name = "<")] - #[inline(always)] pub fn lt(x: Instant, y: Instant) -> bool { x < y } #[rhai_fn(name = "<=")] - #[inline(always)] pub fn lte(x: Instant, y: Instant) -> bool { x <= y } #[rhai_fn(name = ">")] - #[inline(always)] pub fn gt(x: Instant, y: Instant) -> bool { x > y } #[rhai_fn(name = ">=")] - #[inline(always)] pub fn gte(x: Instant, y: Instant) -> bool { x >= y } diff --git a/src/parser.rs b/src/parser.rs index a4dd3b1c..4e932fe9 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,7 +1,6 @@ //! Main module defining the lexer and parser. use crate::any::{Dynamic, Union}; -use crate::calc_fn_hash; use crate::engine::{Engine, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::fn_native::{FnPtr, Shared}; @@ -10,7 +9,8 @@ use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::syntax::FnCustomSyntaxEval; use crate::token::{is_keyword_function, is_valid_identifier, Position, Token, TokenStream}; -use crate::utils::{StaticVec, StraightHasherBuilder}; +use crate::utils::StraightHasherBuilder; +use crate::{calc_fn_hash, StaticVec}; #[cfg(not(feature = "no_index"))] use crate::engine::Array; @@ -31,7 +31,7 @@ use crate::stdlib::{ hash::{Hash, Hasher}, iter::empty, num::NonZeroUsize, - ops::Add, + ops::{Add, AddAssign}, string::{String, ToString}, vec, vec::Vec, @@ -85,12 +85,14 @@ pub struct AST( impl AST { /// Create a new `AST`. + #[inline(always)] pub fn new(statements: Vec, lib: Module) -> Self { Self(statements, lib) } /// Get the statements. #[cfg(not(feature = "internals"))] + #[inline(always)] pub(crate) fn statements(&self) -> &[Stmt] { &self.0 } @@ -99,17 +101,20 @@ impl AST { /// Exported under the `internals` feature only. #[cfg(feature = "internals")] #[deprecated(note = "this method is volatile and may change")] + #[inline(always)] pub fn statements(&self) -> &[Stmt] { &self.0 } /// Get a mutable reference to the statements. + #[inline(always)] pub(crate) fn statements_mut(&mut self) -> &mut Vec { &mut self.0 } /// Get the internal `Module` containing all script-defined functions. #[cfg(not(feature = "internals"))] + #[inline(always)] pub(crate) fn lib(&self) -> &Module { &self.1 } @@ -118,6 +123,7 @@ impl AST { /// Exported under the `internals` feature only. #[cfg(feature = "internals")] #[deprecated(note = "this method is volatile and may change")] + #[inline(always)] pub fn lib(&self) -> &Module { &self.1 } @@ -126,6 +132,7 @@ impl AST { /// No statements are cloned. /// /// This operation is cheap because functions are shared. + #[inline(always)] pub fn clone_functions_only(&self) -> Self { self.clone_functions_only_filtered(|_, _, _| true) } @@ -134,6 +141,7 @@ impl AST { /// No statements are cloned. /// /// This operation is cheap because functions are shared. + #[inline(always)] pub fn clone_functions_only_filtered( &self, mut filter: impl FnMut(FnAccess, &str, usize) -> bool, @@ -145,6 +153,7 @@ impl AST { /// Clone the `AST`'s script statements into a new `AST`. /// No functions are cloned. + #[inline(always)] pub fn clone_statements_only(&self) -> Self { Self(self.0.clone(), Default::default()) } @@ -152,7 +161,7 @@ impl AST { /// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version /// is returned. /// - /// The second `AST` is simply appended to the end of the first _without any processing_. + /// Statements in the second `AST` are simply appended to the end of the first _without any processing_. /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. /// Of course, if the first `AST` uses a `return` statement at the end, then /// the second `AST` will essentially be dead code. @@ -198,14 +207,68 @@ impl AST { /// # Ok(()) /// # } /// ``` + #[inline(always)] pub fn merge(&self, other: &Self) -> Self { self.merge_filtered(other, |_, _, _| true) } + /// Combine one `AST` with another. The second `AST` is consumed. + /// + /// Statements in the second `AST` are simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. + /// Of course, if the first `AST` uses a `return` statement at the end, then + /// the second `AST` will essentially be dead code. + /// + /// All script-defined functions in the second `AST` overwrite similarly-named functions + /// in the first `AST` with the same number of parameters. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// let mut ast1 = engine.compile(r#" + /// fn foo(x) { 42 + x } + /// foo(1) + /// "#)?; + /// + /// let ast2 = engine.compile(r#" + /// fn foo(n) { "hello" + n } + /// foo("!") + /// "#)?; + /// + /// ast1.combine(ast2); // Combine 'ast2' into 'ast1' + /// + /// // Notice that using the '+=' operator also works: + /// // ast1 += ast2; + /// + /// // 'ast1' is essentially: + /// // + /// // fn foo(n) { "hello" + n } // <- definition of first 'foo' is overwritten + /// // foo(1) // <- notice this will be "hello1" instead of 43, + /// // // but it is no longer the return value + /// // foo("!") // returns "hello!" + /// + /// // Evaluate it + /// assert_eq!(engine.eval_ast::(&ast1)?, "hello!"); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn combine(&mut self, other: Self) -> &mut Self { + self.combine_filtered(other, |_, _, _| true) + } + /// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version /// is returned. /// - /// The second `AST` is simply appended to the end of the first _without any processing_. + /// Statements in the second `AST` are simply appended to the end of the first _without any processing_. /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. /// Of course, if the first `AST` uses a `return` statement at the end, then /// the second `AST` will essentially be dead code. @@ -253,6 +316,7 @@ impl AST { /// # Ok(()) /// # } /// ``` + #[inline] pub fn merge_filtered( &self, other: &Self, @@ -277,6 +341,73 @@ impl AST { Self::new(ast, functions) } + /// Combine one `AST` with another. The second `AST` is consumed. + /// + /// Statements in the second `AST` are simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. + /// Of course, if the first `AST` uses a `return` statement at the end, then + /// the second `AST` will essentially be dead code. + /// + /// All script-defined functions in the second `AST` are first selected based on a filter + /// predicate, then overwrite similarly-named functions in the first `AST` with the + /// same number of parameters. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// let mut ast1 = engine.compile(r#" + /// fn foo(x) { 42 + x } + /// foo(1) + /// "#)?; + /// + /// let ast2 = engine.compile(r#" + /// fn foo(n) { "hello" + n } + /// fn error() { 0 } + /// foo("!") + /// "#)?; + /// + /// // Combine 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1' + /// ast1.combine_filtered(ast2, |_, name, params| name == "error" && params == 0); + /// + /// // 'ast1' is essentially: + /// // + /// // fn foo(n) { 42 + n } // <- definition of 'ast1::foo' is not overwritten + /// // // because 'ast2::foo' is filtered away + /// // foo(1) // <- notice this will be 43 instead of "hello1", + /// // // but it is no longer the return value + /// // fn error() { 0 } // <- this function passes the filter and is merged + /// // foo("!") // <- returns "42!" + /// + /// // Evaluate it + /// assert_eq!(engine.eval_ast::(&ast1)?, "42!"); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn combine_filtered( + &mut self, + other: Self, + mut filter: impl FnMut(FnAccess, &str, usize) -> bool, + ) -> &mut Self { + let Self(ref mut statements, ref mut functions) = self; + + if !other.0.is_empty() { + statements.extend(other.0.into_iter()); + } + + functions.merge_filtered(&other.1, &mut filter); + + self + } + /// Filter out the functions, retaining only some based on a filter predicate. /// /// # Example @@ -301,12 +432,14 @@ impl AST { /// # } /// ``` #[cfg(not(feature = "no_function"))] + #[inline(always)] pub fn retain_functions(&mut self, filter: impl FnMut(FnAccess, &str, usize) -> bool) { self.1.retain_functions(filter); } /// Iterate through all functions #[cfg(not(feature = "no_function"))] + #[inline(always)] pub fn iter_functions<'a>( &'a self, ) -> impl Iterator)> + 'a { @@ -315,31 +448,43 @@ impl AST { /// Clear all function definitions in the `AST`. #[cfg(not(feature = "no_function"))] + #[inline(always)] pub fn clear_functions(&mut self) { self.1 = Default::default(); } /// Clear all statements in the `AST`, leaving only function definitions. + #[inline(always)] pub fn clear_statements(&mut self) { self.0 = vec![]; } } -impl Add for &AST { +impl> Add for &AST { type Output = AST; - fn add(self, rhs: Self) -> Self::Output { - self.merge(rhs) + #[inline(always)] + fn add(self, rhs: A) -> Self::Output { + self.merge(rhs.as_ref()) + } +} + +impl> AddAssign for AST { + #[inline(always)] + fn add_assign(&mut self, rhs: A) { + self.combine(rhs.into()); } } impl AsRef<[Stmt]> for AST { + #[inline(always)] fn as_ref(&self) -> &[Stmt] { self.statements() } } impl AsRef for AST { + #[inline(always)] fn as_ref(&self) -> &Module { self.lib() } @@ -355,6 +500,7 @@ pub enum FnAccess { } impl fmt::Display for FnAccess { + #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Private => write!(f, "private"), @@ -365,6 +511,7 @@ impl fmt::Display for FnAccess { impl FnAccess { /// Is this access mode private? + #[inline(always)] pub fn is_private(self) -> bool { match self { Self::Public => false, @@ -372,6 +519,7 @@ impl FnAccess { } } /// Is this access mode public? + #[inline(always)] pub fn is_public(self) -> bool { match self { Self::Public => true, @@ -463,6 +611,7 @@ struct ParseState<'e> { impl<'e> ParseState<'e> { /// Create a new `ParseState`. + #[inline(always)] pub fn new( engine: &'e Engine, #[cfg(not(feature = "unchecked"))] max_expr_depth: usize, @@ -490,6 +639,7 @@ impl<'e> ParseState<'e> { /// The return value is the offset to be deducted from `Stack::len`, /// i.e. the top element of the `ParseState` is offset 1. /// Return `None` when the variable name is not found in the `stack`. + #[inline] fn access_var(&mut self, name: &str, _pos: Position) -> Option { let index = self .stack @@ -515,6 +665,7 @@ impl<'e> ParseState<'e> { /// The return value is the offset to be deducted from `Stack::len`, /// i.e. the top element of the `ParseState` is offset 1. /// Return `None` when the variable name is not found in the `ParseState`. + #[inline(always)] pub fn find_module(&self, name: &str) -> Option { self.modules .iter() @@ -548,6 +699,7 @@ struct ParseSettings { impl ParseSettings { /// Create a new `ParseSettings` with one higher expression level. + #[inline(always)] pub fn level_up(&self) -> Self { Self { level: self.level + 1, @@ -556,6 +708,7 @@ impl ParseSettings { } /// Make sure that the current level of expression nesting is within the maximum limit. #[cfg(not(feature = "unchecked"))] + #[inline] pub fn ensure_level_within_max_limit(&self, limit: usize) -> Result<(), ParseError> { if limit == 0 { Ok(()) @@ -587,7 +740,7 @@ pub enum Stmt { /// let id = expr Let(Box<((String, Position), Option, Position)>), /// const id = expr - Const(Box<((String, Position), Expr, Position)>), + Const(Box<((String, Position), Option, Position)>), /// { stmt; ... } Block(Box<(StaticVec, Position)>), /// expr @@ -615,6 +768,7 @@ pub enum Stmt { } impl Default for Stmt { + #[inline(always)] fn default() -> Self { Self::Noop(Default::default()) } @@ -737,12 +891,14 @@ impl Stmt { pub struct CustomExpr(pub StaticVec, pub Shared); impl fmt::Debug for CustomExpr { + #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.0, f) } } impl Hash for CustomExpr { + #[inline(always)] fn hash(&self, state: &mut H) { self.0.hash(state); } @@ -763,6 +919,7 @@ pub struct FloatWrapper(pub FLOAT, pub Position); #[cfg(not(feature = "no_float"))] impl Hash for FloatWrapper { + #[inline(always)] fn hash(&self, state: &mut H) { state.write(&self.0.to_le_bytes()); self.1.hash(state); @@ -845,6 +1002,7 @@ pub enum Expr { } impl Default for Expr { + #[inline(always)] fn default() -> Self { Self::Unit(Default::default()) } @@ -1006,6 +1164,48 @@ impl Expr { } } + /// Is the expression the unit `()` literal? + #[inline(always)] + pub fn is_unit(&self) -> bool { + match self { + Self::Unit(_) => true, + _ => false, + } + } + + /// Is the expression a simple constant literal? + pub fn is_literal(&self) -> bool { + match self { + Self::Expr(x) => x.is_literal(), + + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(_) => true, + + Self::IntegerConstant(_) + | Self::CharConstant(_) + | Self::StringConstant(_) + | Self::FnPointer(_) + | Self::True(_) + | Self::False(_) + | Self::Unit(_) => true, + + // An array literal is literal if all items are literals + Self::Array(x) => x.0.iter().all(Self::is_literal), + + // An map literal is literal if all items are literals + Self::Map(x) => x.0.iter().map(|(_, expr)| expr).all(Self::is_literal), + + // Check in expression + Self::In(x) => match (&x.0, &x.1) { + (Self::StringConstant(_), Self::StringConstant(_)) + | (Self::CharConstant(_), Self::StringConstant(_)) => true, + _ => false, + }, + + _ => false, + } + } + /// Is the expression a constant? pub fn is_constant(&self) -> bool { match self { @@ -1092,6 +1292,7 @@ impl Expr { /// Convert a `Variable` into a `Property`. All other variants are untouched. #[cfg(not(feature = "no_object"))] + #[inline] pub(crate) fn into_property(self) -> Self { match self { Self::Variable(x) if x.1.is_none() => { @@ -2684,45 +2885,23 @@ fn parse_let( }; // let name = ... - if match_token(input, Token::Equals)? { + let init_value = if match_token(input, Token::Equals)? { // let name = expr - let init_value = parse_expr(input, state, lib, settings.level_up())?; - - match var_type { - // let name = expr - ScopeEntryType::Normal => { - state.stack.push((name.clone(), ScopeEntryType::Normal)); - Ok(Stmt::Let(Box::new(( - (name, pos), - Some(init_value), - token_pos, - )))) - } - // const name = { expr:constant } - ScopeEntryType::Constant if init_value.is_constant() => { - state.stack.push((name.clone(), ScopeEntryType::Constant)); - Ok(Stmt::Const(Box::new(((name, pos), init_value, token_pos)))) - } - // const name = expr: error - ScopeEntryType::Constant => { - Err(PERR::ForbiddenConstantExpr(name).into_err(init_value.position())) - } - } + Some(parse_expr(input, state, lib, settings.level_up())?) } else { - // let name - match var_type { - ScopeEntryType::Normal => { - state.stack.push((name.clone(), ScopeEntryType::Normal)); - Ok(Stmt::Let(Box::new(((name, pos), None, token_pos)))) - } - ScopeEntryType::Constant => { - state.stack.push((name.clone(), ScopeEntryType::Constant)); - Ok(Stmt::Const(Box::new(( - (name, pos), - Expr::Unit(pos), - token_pos, - )))) - } + None + }; + + match var_type { + // let name = expr + ScopeEntryType::Normal => { + state.stack.push((name.clone(), ScopeEntryType::Normal)); + Ok(Stmt::Let(Box::new(((name, pos), init_value, token_pos)))) + } + // const name = { expr:constant } + ScopeEntryType::Constant => { + state.stack.push((name.clone(), ScopeEntryType::Constant)); + Ok(Stmt::Const(Box::new(((name, pos), init_value, token_pos)))) } } } @@ -3487,6 +3666,7 @@ impl Engine { } /// Run the parser on an input stream, returning an AST. + #[inline(always)] pub(crate) fn parse( &self, input: &mut TokenStream, diff --git a/src/result.rs b/src/result.rs index b5a359be..b459f6f5 100644 --- a/src/result.rs +++ b/src/result.rs @@ -252,6 +252,7 @@ impl fmt::Display for EvalAltResult { } impl> From for Box { + #[inline(always)] fn from(err: T) -> Self { Box::new(EvalAltResult::ErrorRuntime( err.as_ref().to_string(), @@ -337,6 +338,7 @@ impl EvalAltResult { /// Consume the current `EvalAltResult` and return a new one with the specified `Position` /// if the current position is `Position::None`. + #[inline(always)] pub(crate) fn new_position(mut self: Box, new_position: Position) -> Box { if self.position().is_none() { self.set_position(new_position); @@ -346,6 +348,7 @@ impl EvalAltResult { } impl From for Result> { + #[inline(always)] fn from(err: EvalAltResult) -> Self { Err(err.into()) } diff --git a/src/scope.rs b/src/scope.rs index 1e489f0a..ad021f7e 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -15,6 +15,17 @@ pub enum EntryType { Constant, } +impl EntryType { + /// Is this entry constant? + #[inline(always)] + pub fn is_constant(&self) -> bool { + match self { + Self::Normal => false, + Self::Constant => true, + } + } +} + /// An entry in the Scope. #[derive(Debug, Clone)] pub struct Entry<'a> { @@ -74,6 +85,7 @@ impl<'a> Scope<'a> { /// my_scope.push("x", 42_i64); /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` + #[inline(always)] pub fn new() -> Self { Default::default() } @@ -97,6 +109,7 @@ impl<'a> Scope<'a> { /// assert_eq!(my_scope.len(), 0); /// assert!(my_scope.is_empty()); /// ``` + #[inline(always)] pub fn clear(&mut self) -> &mut Self { self.0.clear(); self @@ -115,6 +128,7 @@ impl<'a> Scope<'a> { /// my_scope.push("x", 42_i64); /// assert_eq!(my_scope.len(), 1); /// ``` + #[inline(always)] pub fn len(&self) -> usize { self.0.len() } @@ -132,6 +146,7 @@ impl<'a> Scope<'a> { /// my_scope.push("x", 42_i64); /// assert!(!my_scope.is_empty()); /// ``` + #[inline(always)] pub fn is_empty(&self) -> bool { self.0.len() == 0 } @@ -148,6 +163,7 @@ impl<'a> Scope<'a> { /// my_scope.push("x", 42_i64); /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` + #[inline(always)] pub fn push>, T: Variant + Clone>( &mut self, name: K, @@ -168,6 +184,7 @@ impl<'a> Scope<'a> { /// my_scope.push_dynamic("x", Dynamic::from(42_i64)); /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` + #[inline(always)] pub fn push_dynamic>>(&mut self, name: K, value: Dynamic) -> &mut Self { self.push_dynamic_value(name, EntryType::Normal, value, false) } @@ -190,6 +207,7 @@ impl<'a> Scope<'a> { /// my_scope.push_constant("x", 42_i64); /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` + #[inline(always)] pub fn push_constant>, T: Variant + Clone>( &mut self, name: K, @@ -217,6 +235,7 @@ impl<'a> Scope<'a> { /// my_scope.push_constant_dynamic("x", Dynamic::from(42_i64)); /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` + #[inline(always)] pub fn push_constant_dynamic>>( &mut self, name: K, @@ -226,6 +245,7 @@ impl<'a> Scope<'a> { } /// Add (push) a new entry with a `Dynamic` value to the Scope. + #[inline] pub(crate) fn push_dynamic_value>>( &mut self, name: K, @@ -276,6 +296,7 @@ impl<'a> Scope<'a> { /// assert_eq!(my_scope.len(), 0); /// assert!(my_scope.is_empty()); /// ``` + #[inline(always)] pub fn rewind(&mut self, size: usize) -> &mut Self { self.0.truncate(size); self @@ -294,6 +315,7 @@ impl<'a> Scope<'a> { /// assert!(my_scope.contains("x")); /// assert!(!my_scope.contains("y")); /// ``` + #[inline(always)] pub fn contains(&self, name: &str) -> bool { self.0 .iter() @@ -302,6 +324,7 @@ impl<'a> Scope<'a> { } /// Find an entry in the Scope, starting from the last. + #[inline(always)] pub(crate) fn get_index(&self, name: &str) -> Option<(usize, EntryType)> { self.0 .iter() @@ -317,6 +340,7 @@ impl<'a> Scope<'a> { } /// Get an entry in the Scope, starting from the last. + #[inline(always)] pub(crate) fn get_entry(&self, name: &str) -> Option<&Entry> { self.0 .iter() @@ -336,6 +360,7 @@ impl<'a> Scope<'a> { /// my_scope.push("x", 42_i64); /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` + #[inline(always)] pub fn get_value(&self, name: &str) -> Option { self.get_entry(name) .and_then(|Entry { value, .. }| value.flatten_clone().try_cast()) @@ -362,6 +387,7 @@ impl<'a> Scope<'a> { /// my_scope.set_value("x", 0_i64); /// assert_eq!(my_scope.get_value::("x").unwrap(), 0); /// ``` + #[inline(always)] pub fn set_value(&mut self, name: &'a str, value: T) -> &mut Self { match self.get_index(name) { None => { @@ -376,6 +402,7 @@ impl<'a> Scope<'a> { } /// Get a mutable reference to an entry in the Scope. + #[inline(always)] pub(crate) fn get_mut(&mut self, index: usize) -> (&mut Dynamic, EntryType) { let entry = self.0.get_mut(index).expect("invalid index in Scope"); (&mut entry.value, entry.typ) @@ -383,6 +410,7 @@ impl<'a> Scope<'a> { /// Update the access type of an entry in the Scope. #[cfg(not(feature = "no_module"))] + #[inline(always)] pub(crate) fn set_entry_alias(&mut self, index: usize, alias: String) -> &mut Self { let entry = self.0.get_mut(index).expect("invalid index in Scope"); entry.alias = Some(Box::new(alias)); @@ -391,6 +419,7 @@ impl<'a> Scope<'a> { /// Clone the Scope, keeping only the last instances of each variable name. /// Shadowed variables are omitted in the copy. + #[inline] pub(crate) fn flatten_clone(&self) -> Self { let mut entries: Vec = Default::default(); @@ -408,11 +437,13 @@ impl<'a> Scope<'a> { } /// Get an iterator to entries in the Scope. + #[inline(always)] pub(crate) fn into_iter(self) -> impl Iterator> { self.0.into_iter() } /// Get an iterator to entries in the Scope in reverse order. + #[inline(always)] pub(crate) fn to_iter(&self) -> impl Iterator { self.0.iter().rev() // Always search a Scope in reverse order } @@ -427,33 +458,40 @@ impl<'a> Scope<'a> { /// let mut my_scope = Scope::new(); /// /// my_scope.push("x", 42_i64); - /// my_scope.push("foo", "hello".to_string()); + /// my_scope.push_constant("foo", "hello".to_string()); /// /// let mut iter = my_scope.iter(); /// - /// let (name, value) = iter.next().unwrap(); + /// let (name, constant, value) = iter.next().unwrap(); /// assert_eq!(name, "x"); + /// assert!(!constant); /// assert_eq!(value.cast::(), 42); /// - /// let (name, value) = iter.next().unwrap(); + /// let (name, constant, value) = iter.next().unwrap(); /// assert_eq!(name, "foo"); + /// assert!(constant); /// assert_eq!(value.cast::(), "hello"); /// ``` - pub fn iter(&self) -> impl Iterator { + #[inline(always)] + pub fn iter(&self) -> impl Iterator { self.iter_raw() - .map(|(name, value)| (name, value.flatten_clone())) + .map(|(name, constant, value)| (name, constant, value.flatten_clone())) } /// Get an iterator to entries in the Scope. /// Shared values are not expanded. - pub fn iter_raw(&self) -> impl Iterator { - self.0 - .iter() - .map(|Entry { name, value, .. }| (name.as_ref(), value)) + #[inline(always)] + pub fn iter_raw(&self) -> impl Iterator { + self.0.iter().map( + |Entry { + name, typ, value, .. + }| { (name.as_ref(), typ.is_constant(), value) }, + ) } } impl<'a, K: Into>> iter::Extend<(K, EntryType, Dynamic)> for Scope<'a> { + #[inline(always)] fn extend>(&mut self, iter: T) { self.0 .extend(iter.into_iter().map(|(name, typ, value)| Entry { diff --git a/src/serde/de.rs b/src/serde_impl/de.rs similarity index 98% rename from src/serde/de.rs rename to src/serde_impl/de.rs index 69a45765..ad73731a 100644 --- a/src/serde/de.rs +++ b/src/serde_impl/de.rs @@ -23,14 +23,6 @@ use serde::de::{EnumAccess, VariantAccess}; use crate::stdlib::{any::type_name, boxed::Box, fmt, string::ToString}; -#[cfg(not(feature = "no_std"))] -#[cfg(not(target_arch = "wasm32"))] -use crate::stdlib::time::Instant; - -#[cfg(not(feature = "no_std"))] -#[cfg(target_arch = "wasm32")] -use instant::Instant; - /// Deserializer for `Dynamic` which is kept as a reference. /// /// The reference is necessary because the deserialized type may hold references @@ -86,7 +78,7 @@ impl<'de> DynamicDeserializer<'de> { /// # #[cfg(not(feature = "no_object"))] /// # { /// use rhai::{Dynamic, Array, Map, INT}; -/// use rhai::de::from_dynamic; +/// use rhai::serde::from_dynamic; /// use serde::Deserialize; /// /// #[derive(Debug, Deserialize, PartialEq)] diff --git a/src/serde/mod.rs b/src/serde_impl/mod.rs similarity index 100% rename from src/serde/mod.rs rename to src/serde_impl/mod.rs diff --git a/src/serde/ser.rs b/src/serde_impl/ser.rs similarity index 99% rename from src/serde/ser.rs rename to src/serde_impl/ser.rs index f1a2ab77..90dab3c5 100644 --- a/src/serde/ser.rs +++ b/src/serde_impl/ser.rs @@ -55,7 +55,7 @@ impl DynamicSerializer { /// # #[cfg(not(feature = "no_float"))] /// # { /// use rhai::{Dynamic, Array, Map, INT}; -/// use rhai::ser::to_dynamic; +/// use rhai::serde::to_dynamic; /// use serde::Serialize; /// /// #[derive(Debug, serde::Serialize, PartialEq)] diff --git a/src/serde/str.rs b/src/serde_impl/str.rs similarity index 100% rename from src/serde/str.rs rename to src/serde_impl/str.rs diff --git a/src/settings.rs b/src/settings.rs index 4f0f7ccf..c10f5aa7 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -21,6 +21,7 @@ impl Engine { /// /// When searching for functions, packages loaded later are preferred. /// In other words, loaded packages are searched in reverse order. + #[inline(always)] pub fn load_package(&mut self, package: impl Into) -> &mut Self { // Push the package to the top - packages are searched in reverse order self.packages.push(package.into()); @@ -31,6 +32,7 @@ impl Engine { /// /// Not available under the `no_optimize` feature. #[cfg(not(feature = "no_optimize"))] + #[inline(always)] pub fn set_optimization_level(&mut self, optimization_level: OptimizationLevel) -> &mut Self { self.optimization_level = optimization_level; self @@ -41,6 +43,7 @@ impl Engine { /// /// Not available under the `no_optimize` feature. #[cfg(not(feature = "no_optimize"))] + #[inline(always)] pub fn optimization_level(&self) -> OptimizationLevel { self.optimization_level } @@ -48,6 +51,7 @@ impl Engine { /// Set the maximum levels of function calls allowed for a script in order to avoid /// infinite recursion and stack overflows. #[cfg(not(feature = "unchecked"))] + #[inline(always)] pub fn set_max_call_levels(&mut self, levels: usize) -> &mut Self { self.limits.max_call_stack_depth = levels; self @@ -55,6 +59,7 @@ impl Engine { /// The maximum levels of function calls allowed for a script. #[cfg(not(feature = "unchecked"))] + #[inline(always)] pub fn max_call_levels(&self) -> usize { self.limits.max_call_stack_depth } @@ -62,6 +67,7 @@ impl Engine { /// Set the maximum number of operations allowed for a script to run to avoid /// consuming too much resources (0 for unlimited). #[cfg(not(feature = "unchecked"))] + #[inline(always)] pub fn set_max_operations(&mut self, operations: u64) -> &mut Self { self.limits.max_operations = if operations == u64::MAX { 0 @@ -73,12 +79,14 @@ impl Engine { /// The maximum number of operations allowed for a script to run (0 for unlimited). #[cfg(not(feature = "unchecked"))] + #[inline(always)] pub fn max_operations(&self) -> u64 { self.limits.max_operations } /// Set the maximum number of imported modules allowed for a script. #[cfg(not(feature = "unchecked"))] + #[inline(always)] pub fn set_max_modules(&mut self, modules: usize) -> &mut Self { self.limits.max_modules = modules; self @@ -86,12 +94,14 @@ impl Engine { /// The maximum number of imported modules allowed for a script. #[cfg(not(feature = "unchecked"))] + #[inline(always)] pub fn max_modules(&self) -> usize { self.limits.max_modules } /// Set the depth limits for expressions (0 for unlimited). #[cfg(not(feature = "unchecked"))] + #[inline(always)] pub fn set_max_expr_depths( &mut self, max_expr_depth: usize, @@ -112,18 +122,21 @@ impl Engine { /// The depth limit for expressions (0 for unlimited). #[cfg(not(feature = "unchecked"))] + #[inline(always)] pub fn max_expr_depth(&self) -> usize { self.limits.max_expr_depth } /// The depth limit for expressions in functions (0 for unlimited). #[cfg(not(feature = "unchecked"))] + #[inline(always)] pub fn max_function_expr_depth(&self) -> usize { self.limits.max_function_expr_depth } /// Set the maximum length of strings (0 for unlimited). #[cfg(not(feature = "unchecked"))] + #[inline(always)] pub fn set_max_string_size(&mut self, max_size: usize) -> &mut Self { self.limits.max_string_size = if max_size == usize::MAX { 0 } else { max_size }; self @@ -131,6 +144,7 @@ impl Engine { /// The maximum length of strings (0 for unlimited). #[cfg(not(feature = "unchecked"))] + #[inline(always)] pub fn max_string_size(&self) -> usize { self.limits.max_string_size } @@ -138,6 +152,7 @@ impl Engine { /// Set the maximum length of arrays (0 for unlimited). #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_index"))] + #[inline(always)] pub fn set_max_array_size(&mut self, max_size: usize) -> &mut Self { self.limits.max_array_size = if max_size == usize::MAX { 0 } else { max_size }; self @@ -146,6 +161,7 @@ impl Engine { /// The maximum length of arrays (0 for unlimited). #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_index"))] + #[inline(always)] pub fn max_array_size(&self) -> usize { self.limits.max_array_size } @@ -153,6 +169,7 @@ impl Engine { /// Set the maximum length of object maps (0 for unlimited). #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_object"))] + #[inline(always)] pub fn set_max_map_size(&mut self, max_size: usize) -> &mut Self { self.limits.max_map_size = if max_size == usize::MAX { 0 } else { max_size }; self @@ -161,6 +178,7 @@ impl Engine { /// The maximum length of object maps (0 for unlimited). #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_object"))] + #[inline(always)] pub fn max_map_size(&self) -> usize { self.limits.max_map_size } @@ -169,6 +187,7 @@ impl Engine { /// /// Not available under the `no_module` feature. #[cfg(not(feature = "no_module"))] + #[inline(always)] pub fn set_module_resolver( &mut self, resolver: Option, @@ -213,6 +232,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` + #[inline(always)] pub fn disable_symbol(&mut self, symbol: &str) -> &mut Self { if self.disabled_symbols.is_none() { self.disabled_symbols = Some(Default::default()); diff --git a/src/syntax.rs b/src/syntax.rs index 9f3f7f36..35c6b00e 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -9,7 +9,7 @@ use crate::parser::Expr; use crate::result::EvalAltResult; use crate::scope::Scope; use crate::token::{is_valid_identifier, Position, Token}; -use crate::utils::StaticVec; +use crate::StaticVec; use crate::stdlib::{ boxed::Box, @@ -36,6 +36,7 @@ pub type FnCustomSyntaxEval = dyn Fn(&Engine, &mut EvalContext, &mut Scope, &[Ex pub struct Expression<'a>(&'a Expr); impl<'a> From<&'a Expr> for Expression<'a> { + #[inline(always)] fn from(expr: &'a Expr) -> Self { Self(expr) } @@ -43,6 +44,7 @@ impl<'a> From<&'a Expr> for Expression<'a> { impl Expression<'_> { /// If this expression is a variable name, return it. Otherwise `None`. + #[inline(always)] pub fn get_variable_name(&self) -> Option<&str> { match self.0 { Expr::Variable(x) => Some((x.0).0.as_str()), @@ -50,10 +52,12 @@ impl Expression<'_> { } } /// Get the expression. + #[inline(always)] pub(crate) fn expr(&self) -> &Expr { &self.0 } /// Get the position of this expression. + #[inline(always)] pub fn position(&self) -> Position { self.0.position() } @@ -67,6 +71,7 @@ pub struct CustomSyntax { } impl fmt::Debug for CustomSyntax { + #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.segments, f) } @@ -191,6 +196,7 @@ impl Engine { /// ## WARNING - Low Level API /// /// This function is very low level. It evaluates an expression from an AST. + #[inline(always)] pub fn eval_expression_tree( &self, context: &mut EvalContext, diff --git a/src/token.rs b/src/token.rs index 97a65862..40555217 100644 --- a/src/token.rs +++ b/src/token.rs @@ -10,7 +10,7 @@ use crate::engine::KEYWORD_IS_SHARED; use crate::error::LexError; use crate::parser::INT; -use crate::utils::StaticVec; +use crate::StaticVec; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; @@ -55,6 +55,7 @@ impl Position { /// # Panics /// /// Panics if `line` is zero. + #[inline(always)] pub fn new(line: u16, position: u16) -> Self { assert!(line != 0, "line cannot be zero"); @@ -65,6 +66,7 @@ impl Position { } /// Get the line number (1-based), or `None` if there is no position. + #[inline(always)] pub fn line(&self) -> Option { if self.is_none() { None @@ -74,6 +76,7 @@ impl Position { } /// Get the character position (1-based), or `None` if at beginning of a line. + #[inline(always)] pub fn position(&self) -> Option { if self.is_none() || self.pos == 0 { None @@ -83,6 +86,7 @@ impl Position { } /// Advance by one character position. + #[inline(always)] pub(crate) fn advance(&mut self) { assert!(!self.is_none(), "cannot advance Position::none"); @@ -97,6 +101,7 @@ impl Position { /// # Panics /// /// Panics if already at beginning of a line - cannot rewind to a previous line. + #[inline(always)] pub(crate) fn rewind(&mut self) { assert!(!self.is_none(), "cannot rewind Position::none"); assert!(self.pos > 0, "cannot rewind at position 0"); @@ -104,6 +109,7 @@ impl Position { } /// Advance to the next line. + #[inline(always)] pub(crate) fn new_line(&mut self) { assert!(!self.is_none(), "cannot advance Position::none"); @@ -115,23 +121,27 @@ impl Position { } /// Create a `Position` representing no position. + #[inline(always)] pub fn none() -> Self { Self { line: 0, pos: 0 } } /// Is there no `Position`? + #[inline(always)] pub fn is_none(&self) -> bool { self.line == 0 && self.pos == 0 } } impl Default for Position { + #[inline(always)] fn default() -> Self { Self::new(1, 0) } } impl fmt::Display for Position { + #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_none() { write!(f, "none") @@ -142,6 +152,7 @@ impl fmt::Display for Position { } impl fmt::Debug for Position { + #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}:{}", self.line, self.pos) } @@ -521,6 +532,7 @@ impl Token { } // Is this token EOF? + #[inline(always)] pub fn is_eof(&self) -> bool { use Token::*; @@ -677,6 +689,7 @@ impl Token { } /// Is this token a reserved symbol? + #[inline(always)] pub fn is_reserved(&self) -> bool { match self { Self::Reserved(_) => true, @@ -695,6 +708,7 @@ impl Token { } /// Is this token a custom keyword? + #[inline(always)] pub fn is_custom(&self) -> bool { match self { Self::Custom(_) => true, @@ -704,6 +718,7 @@ impl Token { } impl From for String { + #[inline(always)] fn from(token: Token) -> Self { token.syntax().into() } @@ -879,6 +894,7 @@ pub fn parse_string_literal( } /// Consume the next character. +#[inline(always)] fn eat_next(stream: &mut impl InputStream, pos: &mut Position) -> Option { pos.advance(); stream.get_next() @@ -937,6 +953,7 @@ fn scan_comment( /// ## WARNING /// /// This type is volatile and may change. +#[inline] pub fn get_next_token( stream: &mut impl InputStream, state: &mut TokenizeState, @@ -953,6 +970,7 @@ pub fn get_next_token( } /// Test if the given character is a hex character. +#[inline(always)] fn is_hex_char(c: char) -> bool { match c { 'a'..='f' => true, @@ -963,6 +981,7 @@ fn is_hex_char(c: char) -> bool { } /// Test if the given character is an octal character. +#[inline(always)] fn is_octal_char(c: char) -> bool { match c { '0'..='7' => true, @@ -971,6 +990,7 @@ fn is_octal_char(c: char) -> bool { } /// Test if the given character is a binary character. +#[inline(always)] fn is_binary_char(c: char) -> bool { match c { '0' | '1' => true, @@ -1533,6 +1553,7 @@ pub struct MultiInputsStream<'a> { impl InputStream for MultiInputsStream<'_> { /// Buffer a character. + #[inline(always)] fn unread(&mut self, ch: char) { self.buf = Some(ch); } @@ -1692,6 +1713,7 @@ impl<'a> Iterator for TokenIterator<'a, '_> { } /// Tokenize an input text stream. +#[inline] pub fn lex<'a, 'e>( input: &'a [&'a str], map: Option Token>>, diff --git a/src/utils.rs b/src/utils.rs index 94110d1e..06a7ef53 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -21,8 +21,6 @@ use crate::stdlib::collections::hash_map::DefaultHasher; #[cfg(feature = "no_std")] use ahash::AHasher; -use smallvec::SmallVec; - /// A hasher that only takes one single `u64` and returns it as a hash key. /// /// # Panics @@ -32,9 +30,11 @@ use smallvec::SmallVec; pub struct StraightHasher(u64); impl Hasher for StraightHasher { + #[inline(always)] fn finish(&self) -> u64 { self.0 } + #[inline(always)] fn write(&mut self, bytes: &[u8]) { let mut key = [0_u8; 8]; key.copy_from_slice(&bytes[..8]); // Panics if fewer than 8 bytes @@ -44,6 +44,7 @@ impl Hasher for StraightHasher { impl StraightHasher { /// Create a `StraightHasher`. + #[inline(always)] pub fn new() -> Self { Self(0) } @@ -56,6 +57,7 @@ pub struct StraightHasherBuilder; impl BuildHasher for StraightHasherBuilder { type Hasher = StraightHasher; + #[inline(always)] fn build_hasher(&self) -> Self::Hasher { StraightHasher::new() } @@ -89,11 +91,6 @@ pub fn calc_fn_hash<'a>( s.finish() } -/// _[INTERNALS]_ Alias to [`smallvec::SmallVec<[T; 4]>`](https://crates.io/crates/smallvec), -/// which is a specialized `Vec` backed by a small, fixed-size array when there are <= 4 items stored. -/// Exported under the `internals` feature only. -pub type StaticVec = SmallVec<[T; 4]>; - /// The system immutable string type. /// /// An `ImmutableString` wraps an `Rc` (or `Arc` under the `sync` feature) @@ -132,47 +129,55 @@ pub struct ImmutableString(Shared); impl Deref for ImmutableString { type Target = String; + #[inline(always)] fn deref(&self) -> &Self::Target { &self.0 } } impl AsRef for ImmutableString { + #[inline(always)] fn as_ref(&self) -> &String { &self.0 } } impl Borrow for ImmutableString { + #[inline(always)] fn borrow(&self) -> &String { &self.0 } } impl Borrow for ImmutableString { + #[inline(always)] fn borrow(&self) -> &str { self.0.as_str() } } impl From<&str> for ImmutableString { + #[inline(always)] fn from(value: &str) -> Self { Self(value.to_string().into()) } } impl From for ImmutableString { + #[inline(always)] fn from(value: String) -> Self { Self(value.into()) } } impl From> for ImmutableString { + #[inline(always)] fn from(value: Box) -> Self { Self(value.into()) } } impl From for String { + #[inline(always)] fn from(value: ImmutableString) -> Self { value.into_owned() } @@ -181,42 +186,49 @@ impl From for String { impl FromStr for ImmutableString { type Err = (); + #[inline(always)] fn from_str(s: &str) -> Result { Ok(Self(s.to_string().into())) } } impl FromIterator for ImmutableString { + #[inline(always)] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } } impl<'a> FromIterator<&'a char> for ImmutableString { + #[inline(always)] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().cloned().collect::().into()) } } impl<'a> FromIterator<&'a str> for ImmutableString { + #[inline(always)] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } } impl<'a> FromIterator for ImmutableString { + #[inline(always)] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } } impl fmt::Display for ImmutableString { + #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self.0.as_str(), f) } } impl fmt::Debug for ImmutableString { + #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self.0.as_str(), f) } @@ -225,6 +237,7 @@ impl fmt::Debug for ImmutableString { impl Add for ImmutableString { type Output = Self; + #[inline] fn add(mut self, rhs: Self) -> Self::Output { if rhs.is_empty() { self @@ -240,6 +253,7 @@ impl Add for ImmutableString { impl Add for &ImmutableString { type Output = ImmutableString; + #[inline] fn add(self, rhs: Self) -> Self::Output { if rhs.is_empty() { self.clone() @@ -254,6 +268,7 @@ impl Add for &ImmutableString { } impl AddAssign<&ImmutableString> for ImmutableString { + #[inline] fn add_assign(&mut self, rhs: &ImmutableString) { if !rhs.is_empty() { if self.is_empty() { @@ -266,6 +281,7 @@ impl AddAssign<&ImmutableString> for ImmutableString { } impl AddAssign for ImmutableString { + #[inline] fn add_assign(&mut self, rhs: ImmutableString) { if !rhs.is_empty() { if self.is_empty() { @@ -280,6 +296,7 @@ impl AddAssign for ImmutableString { impl Add<&str> for ImmutableString { type Output = Self; + #[inline] fn add(mut self, rhs: &str) -> Self::Output { if rhs.is_empty() { self @@ -293,6 +310,7 @@ impl Add<&str> for ImmutableString { impl Add<&str> for &ImmutableString { type Output = ImmutableString; + #[inline] fn add(self, rhs: &str) -> Self::Output { if rhs.is_empty() { self.clone() @@ -305,6 +323,7 @@ impl Add<&str> for &ImmutableString { } impl AddAssign<&str> for ImmutableString { + #[inline(always)] fn add_assign(&mut self, rhs: &str) { if !rhs.is_empty() { self.make_mut().push_str(rhs); @@ -315,6 +334,7 @@ impl AddAssign<&str> for ImmutableString { impl Add for ImmutableString { type Output = Self; + #[inline] fn add(mut self, rhs: String) -> Self::Output { if rhs.is_empty() { self @@ -330,6 +350,7 @@ impl Add for ImmutableString { impl Add for &ImmutableString { type Output = ImmutableString; + #[inline] fn add(self, rhs: String) -> Self::Output { if rhs.is_empty() { self.clone() @@ -346,6 +367,7 @@ impl Add for &ImmutableString { impl Add for ImmutableString { type Output = Self; + #[inline(always)] fn add(mut self, rhs: char) -> Self::Output { self.make_mut().push(rhs); self @@ -355,6 +377,7 @@ impl Add for ImmutableString { impl Add for &ImmutableString { type Output = ImmutableString; + #[inline(always)] fn add(self, rhs: char) -> Self::Output { let mut s = self.clone(); s.make_mut().push(rhs); @@ -363,42 +386,49 @@ impl Add for &ImmutableString { } impl AddAssign for ImmutableString { + #[inline(always)] fn add_assign(&mut self, rhs: char) { self.make_mut().push(rhs); } } impl> PartialEq for ImmutableString { + #[inline(always)] fn eq(&self, other: &S) -> bool { self.as_str().eq(other.as_ref()) } } impl PartialEq for str { + #[inline(always)] fn eq(&self, other: &ImmutableString) -> bool { self.eq(other.as_str()) } } impl PartialEq for String { + #[inline(always)] fn eq(&self, other: &ImmutableString) -> bool { self.eq(other.as_str()) } } impl> PartialOrd for ImmutableString { + #[inline(always)] fn partial_cmp(&self, other: &S) -> Option { self.as_str().partial_cmp(other.as_ref()) } } impl PartialOrd for str { + #[inline(always)] fn partial_cmp(&self, other: &ImmutableString) -> Option { self.partial_cmp(other.as_str()) } } impl PartialOrd for String { + #[inline(always)] fn partial_cmp(&self, other: &ImmutableString) -> Option { self.as_str().partial_cmp(other.as_str()) } @@ -407,12 +437,14 @@ impl PartialOrd for String { impl ImmutableString { /// Consume the `ImmutableString` and convert it into a `String`. /// If there are other references to the same string, a cloned copy is returned. + #[inline(always)] pub fn into_owned(mut self) -> String { self.make_mut(); // Make sure it is unique reference shared_take(self.0) // Should succeed } /// Make sure that the `ImmutableString` is unique (i.e. no other outstanding references). /// Then return a mutable reference to the `String`. + #[inline(always)] pub fn make_mut(&mut self) -> &mut String { shared_make_mut(&mut self.0) } diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..c5b5e042 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,12 @@ +Tests +===== + +Rhai engine tests. + + +How to Run +---------- + +```bash +cargo test +``` diff --git a/tests/optimizer.rs b/tests/optimizer.rs index 2a75e916..4e66552c 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -1,6 +1,6 @@ #![cfg(not(feature = "no_optimize"))] -use rhai::{Engine, EvalAltResult, OptimizationLevel, INT}; +use rhai::{Engine, EvalAltResult, OptimizationLevel, RegisterFn, INT}; #[test] fn test_optimizer_run() -> Result<(), Box> { @@ -28,6 +28,23 @@ fn test_optimizer_run() -> Result<(), Box> { engine.set_optimization_level(OptimizationLevel::Full); run_test(&mut engine)?; + // Override == operator + engine.register_fn("==", |_x: INT, _y: INT| false); + + engine.set_optimization_level(OptimizationLevel::Simple); + + assert_eq!( + engine.eval::(r"if 1 == 1 || 2 > 3 { 42 } else { 123 }")?, + 123 + ); + + engine.set_optimization_level(OptimizationLevel::Full); + + assert_eq!( + engine.eval::(r"if 1 == 1 || 2 > 3 { 42 } else { 123 }")?, + 123 + ); + Ok(()) } diff --git a/tests/serde.rs b/tests/serde.rs index 005e4e8c..75291413 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -1,6 +1,9 @@ #![cfg(feature = "serde")] -use rhai::{de::from_dynamic, ser::to_dynamic, Dynamic, Engine, EvalAltResult, INT}; +use rhai::{ + serde::{from_dynamic, to_dynamic}, + Dynamic, Engine, EvalAltResult, INT, +}; use serde::{Deserialize, Serialize}; #[cfg(not(feature = "no_index"))] @@ -139,12 +142,10 @@ fn test_serde_ser_externally_tagged_enum() -> Result<(), Box> { assert!(content.is_empty()); } - { - let mut map = to_dynamic(MyEnum::VariantNewtype(123))?.cast::(); - let content = map.remove("VariantNewtype").unwrap(); - assert!(map.is_empty()); - assert_eq!(Ok(123), content.as_int()); - } + let mut map = to_dynamic(MyEnum::VariantNewtype(123))?.cast::(); + let content = map.remove("VariantNewtype").unwrap(); + assert!(map.is_empty()); + assert_eq!(Ok(123), content.as_int()); #[cfg(not(feature = "no_index"))] { @@ -156,20 +157,16 @@ fn test_serde_ser_externally_tagged_enum() -> Result<(), Box> { assert_eq!(Ok(456), content[1].as_int()); } - { - let mut map = to_dynamic(MyEnum::VariantEmptyStruct {})?.cast::(); - let map_inner = map.remove("VariantEmptyStruct").unwrap().cast::(); - assert!(map.is_empty()); - assert!(map_inner.is_empty()); - } + let mut map = to_dynamic(MyEnum::VariantEmptyStruct {})?.cast::(); + let map_inner = map.remove("VariantEmptyStruct").unwrap().cast::(); + assert!(map.is_empty()); + assert!(map_inner.is_empty()); - { - let mut map = to_dynamic(MyEnum::VariantStruct { a: 123 })?.cast::(); - let mut map_inner = map.remove("VariantStruct").unwrap().cast::(); - assert!(map.is_empty()); - assert_eq!(Ok(123), map_inner.remove("a").unwrap().as_int()); - assert!(map_inner.is_empty()); - } + let mut map = to_dynamic(MyEnum::VariantStruct { a: 123 })?.cast::(); + let mut map_inner = map.remove("VariantStruct").unwrap().cast::(); + assert!(map.is_empty()); + assert_eq!(Ok(123), map_inner.remove("a").unwrap().as_int()); + assert!(map_inner.is_empty()); Ok(()) } @@ -217,11 +214,9 @@ fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box> { }, } - { - let mut map = to_dynamic(MyEnum::VariantUnit)?.cast::(); - assert_eq!(Ok("VariantUnit"), map.remove("tag").unwrap().as_str()); - assert!(map.is_empty()); - } + let mut map = to_dynamic(MyEnum::VariantUnit)?.cast::(); + assert_eq!(Ok("VariantUnit"), map.remove("tag").unwrap().as_str()); + assert!(map.is_empty()); #[cfg(not(feature = "no_index"))] { @@ -232,13 +227,11 @@ fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box> { assert!(content.is_empty()); } - { - let mut map = to_dynamic(MyEnum::VariantNewtype(123))?.cast::(); - assert_eq!(Ok("VariantNewtype"), map.remove("tag").unwrap().as_str()); - let content = map.remove("content").unwrap(); - assert!(map.is_empty()); - assert_eq!(Ok(123), content.as_int()); - } + let mut map = to_dynamic(MyEnum::VariantNewtype(123))?.cast::(); + assert_eq!(Ok("VariantNewtype"), map.remove("tag").unwrap().as_str()); + let content = map.remove("content").unwrap(); + assert!(map.is_empty()); + assert_eq!(Ok(123), content.as_int()); #[cfg(not(feature = "no_index"))] { @@ -251,25 +244,21 @@ fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box> { assert_eq!(Ok(456), content[1].as_int()); } - { - let mut map = to_dynamic(MyEnum::VariantEmptyStruct {})?.cast::(); - assert_eq!( - Ok("VariantEmptyStruct"), - map.remove("tag").unwrap().as_str() - ); - let map_inner = map.remove("content").unwrap().cast::(); - assert!(map.is_empty()); - assert!(map_inner.is_empty()); - } + let mut map = to_dynamic(MyEnum::VariantEmptyStruct {})?.cast::(); + assert_eq!( + Ok("VariantEmptyStruct"), + map.remove("tag").unwrap().as_str() + ); + let map_inner = map.remove("content").unwrap().cast::(); + assert!(map.is_empty()); + assert!(map_inner.is_empty()); - { - let mut map = to_dynamic(MyEnum::VariantStruct { a: 123 })?.cast::(); - assert_eq!(Ok("VariantStruct"), map.remove("tag").unwrap().as_str()); - let mut map_inner = map.remove("content").unwrap().cast::(); - assert!(map.is_empty()); - assert_eq!(Ok(123), map_inner.remove("a").unwrap().as_int()); - assert!(map_inner.is_empty()); - } + let mut map = to_dynamic(MyEnum::VariantStruct { a: 123 })?.cast::(); + assert_eq!(Ok("VariantStruct"), map.remove("tag").unwrap().as_str()); + let mut map_inner = map.remove("content").unwrap().cast::(); + assert!(map.is_empty()); + assert_eq!(Ok(123), map_inner.remove("a").unwrap().as_int()); + assert!(map_inner.is_empty()); Ok(()) } @@ -285,22 +274,16 @@ fn test_serde_ser_untagged_enum() -> Result<(), Box> { VariantStruct2 { b: i32 }, } - { - let map = to_dynamic(MyEnum::VariantEmptyStruct {})?.cast::(); - assert!(map.is_empty()); - } + let map = to_dynamic(MyEnum::VariantEmptyStruct {})?.cast::(); + assert!(map.is_empty()); - { - let mut map = to_dynamic(MyEnum::VariantStruct1 { a: 123 })?.cast::(); - assert_eq!(Ok(123), map.remove("a").unwrap().as_int()); - assert!(map.is_empty()); - } + let mut map = to_dynamic(MyEnum::VariantStruct1 { a: 123 })?.cast::(); + assert_eq!(Ok(123), map.remove("a").unwrap().as_int()); + assert!(map.is_empty()); - { - let mut map = to_dynamic(MyEnum::VariantStruct2 { b: 123 })?.cast::(); - assert_eq!(Ok(123), map.remove("b").unwrap().as_int()); - assert!(map.is_empty()); - } + let mut map = to_dynamic(MyEnum::VariantStruct2 { b: 123 })?.cast::(); + assert_eq!(Ok(123), map.remove("b").unwrap().as_int()); + assert!(map.is_empty()); Ok(()) } @@ -433,15 +416,11 @@ fn test_serde_de_unit_enum() -> Result<(), Box> { VariantBar, } - { - let d = Dynamic::from("VariantFoo".to_string()); - assert_eq!(MyEnum::VariantFoo, from_dynamic(&d)?); - } + let d = Dynamic::from("VariantFoo".to_string()); + assert_eq!(MyEnum::VariantFoo, from_dynamic(&d)?); - { - let d = Dynamic::from("VariantBar".to_string()); - assert_eq!(MyEnum::VariantBar, from_dynamic(&d)?); - } + let d = Dynamic::from("VariantBar".to_string()); + assert_eq!(MyEnum::VariantBar, from_dynamic(&d)?); Ok(()) } @@ -464,10 +443,8 @@ fn test_serde_de_externally_tagged_enum() -> Result<(), Box> { }, } - { - let d = Dynamic::from("VariantUnit".to_string()); - assert_eq!(MyEnum::VariantUnit, from_dynamic(&d).unwrap()); - } + let d = Dynamic::from("VariantUnit".to_string()); + assert_eq!(MyEnum::VariantUnit, from_dynamic(&d).unwrap()); #[cfg(not(feature = "no_index"))] { @@ -480,14 +457,12 @@ fn test_serde_de_externally_tagged_enum() -> Result<(), Box> { ); } - { - let mut map_outer = Map::new(); - map_outer.insert("VariantNewtype".into(), (123 as INT).into()); - assert_eq!( - MyEnum::VariantNewtype(123), - from_dynamic(&map_outer.into()).unwrap() - ); - } + let mut map_outer = Map::new(); + map_outer.insert("VariantNewtype".into(), (123 as INT).into()); + assert_eq!( + MyEnum::VariantNewtype(123), + from_dynamic(&map_outer.into()).unwrap() + ); #[cfg(not(feature = "no_index"))] { @@ -500,26 +475,22 @@ fn test_serde_de_externally_tagged_enum() -> Result<(), Box> { ); } - { - let map_inner = Map::new(); - let mut map_outer = Map::new(); - map_outer.insert("VariantEmptyStruct".into(), map_inner.into()); - assert_eq!( - MyEnum::VariantEmptyStruct {}, - from_dynamic(&map_outer.into()).unwrap() - ); - } + let map_inner = Map::new(); + let mut map_outer = Map::new(); + map_outer.insert("VariantEmptyStruct".into(), map_inner.into()); + assert_eq!( + MyEnum::VariantEmptyStruct {}, + from_dynamic(&map_outer.into()).unwrap() + ); - { - let mut map_inner = Map::new(); - map_inner.insert("a".into(), (123 as INT).into()); - let mut map_outer = Map::new(); - map_outer.insert("VariantStruct".into(), map_inner.into()); - assert_eq!( - MyEnum::VariantStruct { a: 123 }, - from_dynamic(&map_outer.into()).unwrap() - ); - } + let mut map_inner = Map::new(); + map_inner.insert("a".into(), (123 as INT).into()); + let mut map_outer = Map::new(); + map_outer.insert("VariantStruct".into(), map_inner.into()); + assert_eq!( + MyEnum::VariantStruct { a: 123 }, + from_dynamic(&map_outer.into()).unwrap() + ); Ok(()) } @@ -534,24 +505,20 @@ fn test_serde_de_internally_tagged_enum() -> Result<(), Box> { VariantStruct { a: i32 }, } - { - let mut map = Map::new(); - map.insert("tag".into(), "VariantStruct".into()); - map.insert("a".into(), (123 as INT).into()); - assert_eq!( - MyEnum::VariantStruct { a: 123 }, - from_dynamic(&map.into()).unwrap() - ); - } + let mut map = Map::new(); + map.insert("tag".into(), "VariantStruct".into()); + map.insert("a".into(), (123 as INT).into()); + assert_eq!( + MyEnum::VariantStruct { a: 123 }, + from_dynamic(&map.into()).unwrap() + ); - { - let mut map = Map::new(); - map.insert("tag".into(), "VariantEmptyStruct".into()); - assert_eq!( - MyEnum::VariantEmptyStruct {}, - from_dynamic(&map.into()).unwrap() - ); - } + let mut map = Map::new(); + map.insert("tag".into(), "VariantEmptyStruct".into()); + assert_eq!( + MyEnum::VariantEmptyStruct {}, + from_dynamic(&map.into()).unwrap() + ); Ok(()) } @@ -574,14 +541,12 @@ fn test_serde_de_adjacently_tagged_enum() -> Result<(), Box> { }, } - { - let mut map_outer = Map::new(); - map_outer.insert("tag".into(), "VariantUnit".into()); - assert_eq!( - MyEnum::VariantUnit, - from_dynamic(&map_outer.into()).unwrap() - ); - } + let mut map_outer = Map::new(); + map_outer.insert("tag".into(), "VariantUnit".into()); + assert_eq!( + MyEnum::VariantUnit, + from_dynamic(&map_outer.into()).unwrap() + ); #[cfg(not(feature = "no_index"))] { @@ -595,15 +560,13 @@ fn test_serde_de_adjacently_tagged_enum() -> Result<(), Box> { ); } - { - let mut map_outer = Map::new(); - map_outer.insert("tag".into(), "VariantNewtype".into()); - map_outer.insert("content".into(), (123 as INT).into()); - assert_eq!( - MyEnum::VariantNewtype(123), - from_dynamic(&map_outer.into()).unwrap() - ); - } + let mut map_outer = Map::new(); + map_outer.insert("tag".into(), "VariantNewtype".into()); + map_outer.insert("content".into(), (123 as INT).into()); + assert_eq!( + MyEnum::VariantNewtype(123), + from_dynamic(&map_outer.into()).unwrap() + ); #[cfg(not(feature = "no_index"))] { @@ -617,28 +580,24 @@ fn test_serde_de_adjacently_tagged_enum() -> Result<(), Box> { ); } - { - let map_inner = Map::new(); - let mut map_outer = Map::new(); - map_outer.insert("tag".into(), "VariantEmptyStruct".into()); - map_outer.insert("content".into(), map_inner.into()); - assert_eq!( - MyEnum::VariantEmptyStruct {}, - from_dynamic(&map_outer.into()).unwrap() - ); - } + let map_inner = Map::new(); + let mut map_outer = Map::new(); + map_outer.insert("tag".into(), "VariantEmptyStruct".into()); + map_outer.insert("content".into(), map_inner.into()); + assert_eq!( + MyEnum::VariantEmptyStruct {}, + from_dynamic(&map_outer.into()).unwrap() + ); - { - let mut map_inner = Map::new(); - map_inner.insert("a".into(), (123 as INT).into()); - let mut map_outer = Map::new(); - map_outer.insert("tag".into(), "VariantStruct".into()); - map_outer.insert("content".into(), map_inner.into()); - assert_eq!( - MyEnum::VariantStruct { a: 123 }, - from_dynamic(&map_outer.into()).unwrap() - ); - } + let mut map_inner = Map::new(); + map_inner.insert("a".into(), (123 as INT).into()); + let mut map_outer = Map::new(); + map_outer.insert("tag".into(), "VariantStruct".into()); + map_outer.insert("content".into(), map_inner.into()); + assert_eq!( + MyEnum::VariantStruct { a: 123 }, + from_dynamic(&map_outer.into()).unwrap() + ); Ok(()) } @@ -654,31 +613,25 @@ fn test_serde_de_untagged_enum() -> Result<(), Box> { VariantStruct2 { b: i32 }, } - { - let map = Map::new(); - assert_eq!( - MyEnum::VariantEmptyStruct {}, - from_dynamic(&map.into()).unwrap() - ); - } + let map = Map::new(); + assert_eq!( + MyEnum::VariantEmptyStruct {}, + from_dynamic(&map.into()).unwrap() + ); - { - let mut map = Map::new(); - map.insert("a".into(), (123 as INT).into()); - assert_eq!( - MyEnum::VariantStruct1 { a: 123 }, - from_dynamic(&map.into()).unwrap() - ); - } + let mut map = Map::new(); + map.insert("a".into(), (123 as INT).into()); + assert_eq!( + MyEnum::VariantStruct1 { a: 123 }, + from_dynamic(&map.into()).unwrap() + ); - { - let mut map = Map::new(); - map.insert("b".into(), (123 as INT).into()); - assert_eq!( - MyEnum::VariantStruct2 { b: 123 }, - from_dynamic(&map.into()).unwrap() - ); - } + let mut map = Map::new(); + map.insert("b".into(), (123 as INT).into()); + assert_eq!( + MyEnum::VariantStruct2 { b: 123 }, + from_dynamic(&map.into()).unwrap() + ); Ok(()) } diff --git a/tests/stack.rs b/tests/stack.rs index b7ff9a57..03edee0e 100644 --- a/tests/stack.rs +++ b/tests/stack.rs @@ -10,10 +10,10 @@ fn test_stack_overflow_fn_calls() -> Result<(), Box> { engine.eval::( r" fn foo(n) { if n <= 1 { 0 } else { n + foo(n-1) } } - foo(8) + foo(7) ", )?, - 35 + 27 ); #[cfg(not(feature = "unchecked"))]