diff --git a/RELEASES.md b/RELEASES.md index 33680b02..ea15648f 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -13,11 +13,13 @@ Breaking changes * `Engine::load_package` is renamed `Engine::register_global_module` and now must explicitly pass a shared [`Module`]. * `Engine::register_module` is renamed `Engine::register_static_module` and now must explicitly pass a shared [`Module`]. * `Package::get` is renamed `Package::as_shared_module`. +* `Engine::set_module_resolver` now takes a straight module resolver instead of an `Option`. To disable module resolving, use the new `DummyModuleResolver`. Enhancements ------------ * `Scope` is now `Clone + Hash`. +* `Engine::register_static_module` now supports sub-module paths (e.g. `foo::bar::baz`). Version 0.19.8 diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index ea341e1b..d71054a0 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -21,7 +21,7 @@ //! let m = exported_module!(advanced_math); //! let mut r = StaticModuleResolver::new(); //! r.insert("Math::Advanced", m); -//! engine.set_module_resolver(Some(r)); +//! engine.set_module_resolver(r); //! //! assert_eq!(engine.eval::( //! r#" @@ -51,7 +51,7 @@ //! set_exported_fn!(m, "euclidean_distance", distance_function); //! let mut r = StaticModuleResolver::new(); //! r.insert("Math::Advanced", m); -//! engine.set_module_resolver(Some(r)); +//! engine.set_module_resolver(r); //! //! assert_eq!(engine.eval::( //! r#" diff --git a/codegen/tests/test_functions.rs b/codegen/tests/test_functions.rs index 590ac6e9..303076e2 100644 --- a/codegen/tests/test_functions.rs +++ b/codegen/tests/test_functions.rs @@ -18,14 +18,11 @@ fn raw_fn_test() -> Result<(), Box> { engine.register_fn("get_mystic_number", || 42 as FLOAT); let mut m = Module::new(); rhai::set_exported_fn!(m, "euclidean_distance", raw_fn::distance_function); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); assert_eq!( engine.eval::( - r#"import "Math::Advanced" as math; - let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"# + r#"let x = Math::Advanced::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"# )?, 41.0 ); @@ -48,16 +45,13 @@ fn raw_fn_mut_test() -> Result<(), Box> { engine.register_fn("get_mystic_number", || 42 as FLOAT); let mut m = Module::new(); rhai::set_exported_fn!(m, "add_in_place", raw_fn_mut::add_in_place); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); assert_eq!( engine.eval::( - r#"import "Math::Advanced" as math; - let x = get_mystic_number(); - math::add_in_place(x, 1.0); - x"# + r#"let x = get_mystic_number(); + Math::Advanced::add_in_place(x, 1.0); + x"# )?, 43.0 ); @@ -80,16 +74,10 @@ fn raw_fn_str_test() -> Result<(), Box> { engine.register_fn("get_mystic_number", || 42 as FLOAT); let mut m = Module::new(); rhai::set_exported_fn!(m, "write_out_str", raw_fn_str::write_out_str); - let mut r = StaticModuleResolver::new(); - r.insert("Host::IO", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Host::IO", m.into()); assert_eq!( - engine.eval::( - r#"import "Host::IO" as io; - let x = io::write_out_str("hello world!"); - x"# - )?, + engine.eval::(r#"let x = Host::IO::write_out_str("hello world!"); x"#)?, true ); Ok(()) @@ -138,18 +126,16 @@ fn mut_opaque_ref_test() -> Result<(), Box> { rhai::set_exported_fn!(m, "new_message", mut_opaque_ref::new_message); rhai::set_exported_fn!(m, "new_os_message", mut_opaque_ref::new_os_message); rhai::set_exported_fn!(m, "write_out_message", mut_opaque_ref::write_out_message); - let mut r = StaticModuleResolver::new(); - r.insert("Host::Msg", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Host::Msg", m.into()); assert_eq!( engine.eval::( - r#"import "Host::Msg" as msg; - let message1 = msg::new_message(true, "it worked"); - let ok1 = msg::write_out_message(message1); - let message2 = msg::new_os_message(true, 0); - let ok2 = msg::write_out_message(message2); - ok1 && ok2"# + r#" + let message1 = Host::Msg::new_message(true, "it worked"); + let ok1 = Host::Msg::write_out_message(message1); + let message2 = Host::Msg::new_os_message(true, 0); + let ok2 = Host::Msg::write_out_message(message2); + ok1 && ok2"# )?, true ); @@ -179,14 +165,11 @@ fn raw_returning_fn_test() -> Result<(), Box> { engine.register_fn("get_mystic_number", || 42 as FLOAT); let mut m = Module::new(); rhai::set_exported_fn!(m, "euclidean_distance", raw_returning_fn::distance_function); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); assert_eq!( engine.eval::( - r#"import "Math::Advanced" as math; - let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"# + r#"let x = Math::Advanced::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"# )?, 41.0 ); diff --git a/codegen/tests/test_modules.rs b/codegen/tests/test_modules.rs index 4c043298..4993c5c3 100644 --- a/codegen/tests/test_modules.rs +++ b/codegen/tests/test_modules.rs @@ -12,14 +12,8 @@ pub mod empty_module { fn empty_module_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::empty_module::EmptyModule); - let mut r = StaticModuleResolver::new(); - r.insert("Module::Empty", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Module::Empty", m.into()); - assert_eq!( - engine.eval::(r#"import "Module::Empty" as m; 42"#)?, - 42 - ); Ok(()) } @@ -39,16 +33,10 @@ pub mod one_fn_module { fn one_fn_module_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::one_fn_module::advanced_math); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); assert_eq!( - engine.eval::( - r#"import "Math::Advanced" as math; - let m = math::get_mystic_number(); - m"# - )?, + engine.eval::(r#"let m = Math::Advanced::get_mystic_number();m"#)?, 42.0 ); Ok(()) @@ -73,16 +61,14 @@ pub mod one_fn_and_const_module { fn one_fn_and_const_module_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::one_fn_and_const_module::advanced_math); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); assert_eq!( engine.eval::( - r#"import "Math::Advanced" as math; - let m = math::MYSTIC_NUMBER; - let x = math::euclidean_distance(0.0, 1.0, 0.0, m); - x"# + r#" + let m = Math::Advanced::MYSTIC_NUMBER; + let x = Math::Advanced::euclidean_distance(0.0, 1.0, 0.0, m); + x"# )?, 41.0 ); @@ -105,16 +91,10 @@ pub mod raw_fn_str_module { fn raw_fn_str_module_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::raw_fn_str_module::host_io); - let mut r = StaticModuleResolver::new(); - r.insert("Host::IO", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Host::IO", m.into()); assert_eq!( - engine.eval::( - r#"import "Host::IO" as io; - let x = io::write_out_str("hello world!"); - x"# - )?, + engine.eval::(r#"let x = Host::IO::write_out_str("hello world!"); x"#)?, true ); Ok(()) @@ -162,19 +142,17 @@ pub mod mut_opaque_ref_module { fn mut_opaque_ref_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::mut_opaque_ref_module::host_msg); - let mut r = StaticModuleResolver::new(); - r.insert("Host::Msg", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Host::Msg", m.into()); assert_eq!( engine.eval::( - r#"import "Host::Msg" as msg; - let success = "it worked"; - let message1 = msg::new_message(true, success); - let ok1 = msg::write_out_message(message1); - let message2 = msg::new_os_message(true, 0); - let ok2 = msg::write_out_message(message2); - ok1 && ok2"# + r#" + let success = "it worked"; + let message1 = Host::Msg::new_message(true, success); + let ok1 = Host::Msg::write_out_message(message1); + let message2 = Host::Msg::new_os_message(true, 0); + let ok2 = Host::Msg::write_out_message(message2); + ok1 && ok2"# )?, true ); @@ -204,18 +182,16 @@ fn duplicate_fn_rename_test() -> Result<(), Box> { let mut engine = Engine::new(); engine.register_fn("get_mystic_number", || 42 as FLOAT); let m = rhai::exported_module!(crate::duplicate_fn_rename::my_adds); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); let output_array = engine.eval::( - r#"import "Math::Advanced" as math; - let fx = get_mystic_number(); - let fy = math::add(fx, 1.0); - let ix = 42; - let iy = math::add(ix, 1); - [fy, iy] - "#, + r#" + let fx = get_mystic_number(); + let fy = Math::Advanced::add(fx, 1.0); + let ix = 42; + let iy = Math::Advanced::add(ix, 1); + [fy, iy] + "#, )?; assert_eq!(&output_array[0].as_float().unwrap(), &43.0); assert_eq!(&output_array[1].as_int().unwrap(), &43); @@ -329,20 +305,18 @@ mod export_by_prefix { fn export_by_prefix_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::export_by_prefix::my_adds); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); let output_array = engine.eval::( - r#"import "Math::Advanced" as math; - let ex = 41.0; - let fx = math::foo_add_f(ex, 1.0); - let gx = math::foo_m(41.0, 1.0); - let ei = 41; - let fi = math::bar_add_i(ei, 1); - let gi = math::foo_n(41, 1); - [fx, gx, fi, gi] - "#, + r#" + let ex = 41.0; + let fx = Math::Advanced::foo_add_f(ex, 1.0); + let gx = Math::Advanced::foo_m(41.0, 1.0); + let ei = 41; + let fi = Math::Advanced::bar_add_i(ei, 1); + let gi = Math::Advanced::foo_n(41, 1); + [fx, gx, fi, gi] + "#, )?; assert_eq!(&output_array[0].as_float().unwrap(), &42.0); assert_eq!(&output_array[1].as_float().unwrap(), &42.0); @@ -350,24 +324,24 @@ fn export_by_prefix_test() -> Result<(), Box> { assert_eq!(&output_array[3].as_int().unwrap(), &42); assert!(matches!(*engine.eval::( - r#"import "Math::Advanced" as math; - let ex = 41.0; - let fx = math::foo_add_float2(ex, 1.0); - fx - "#).unwrap_err(), - EvalAltResult::ErrorFunctionNotFound(s, p) - if s == "math::foo_add_float2 (f64, f64)" - && p == rhai::Position::new(3, 23))); + r#" + let ex = 41.0; + let fx = Math::Advanced::foo_add_float2(ex, 1.0); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "Math::Advanced::foo_add_float2 (f64, f64)" + && p == rhai::Position::new(3, 34))); assert!(matches!(*engine.eval::( - r#"import "Math::Advanced" as math; - let ex = 41.0; - let fx = math::bar_m(ex, 1.0); - fx - "#).unwrap_err(), - EvalAltResult::ErrorFunctionNotFound(s, p) - if s == "math::bar_m (f64, f64)" - && p == rhai::Position::new(3, 23))); + r#" + let ex = 41.0; + let fx = Math::Advanced::bar_m(ex, 1.0); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "Math::Advanced::bar_m (f64, f64)" + && p == rhai::Position::new(3, 34))); Ok(()) } @@ -413,20 +387,18 @@ mod export_all { fn export_all_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::export_all::my_adds); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); let output_array = engine.eval::( - r#"import "Math::Advanced" as math; - let ex = 41.0; - let fx = math::foo_add_f(ex, 1.0); - let gx = math::foo_m(41.0, 1.0); - let ei = 41; - let fi = math::foo_add_i(ei, 1); - let gi = math::foo_n(41, 1); - [fx, gx, fi, gi] - "#, + r#" + let ex = 41.0; + let fx = Math::Advanced::foo_add_f(ex, 1.0); + let gx = Math::Advanced::foo_m(41.0, 1.0); + let ei = 41; + let fi = Math::Advanced::foo_add_i(ei, 1); + let gi = Math::Advanced::foo_n(41, 1); + [fx, gx, fi, gi] + "#, )?; assert_eq!(&output_array[0].as_float().unwrap(), &42.0); assert_eq!(&output_array[1].as_float().unwrap(), &42.0); @@ -434,14 +406,14 @@ fn export_all_test() -> Result<(), Box> { assert_eq!(&output_array[3].as_int().unwrap(), &42); assert!(matches!(*engine.eval::( - r#"import "Math::Advanced" as math; - let ex = 41; - let fx = math::foo_p(ex, 1); - fx - "#).unwrap_err(), - EvalAltResult::ErrorFunctionNotFound(s, p) - if s == "math::foo_p (i64, i64)" - && p == rhai::Position::new(3, 23))); + r#" + let ex = 41; + let fx = Math::Advanced::foo_p(ex, 1); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "Math::Advanced::foo_p (i64, i64)" + && p == rhai::Position::new(3, 34))); Ok(()) } diff --git a/codegen/tests/test_nested.rs b/codegen/tests/test_nested.rs index 42781be1..a7f898b5 100644 --- a/codegen/tests/test_nested.rs +++ b/codegen/tests/test_nested.rs @@ -20,16 +20,10 @@ pub mod one_fn_module_nested_attr { fn one_fn_module_nested_attr_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::one_fn_module_nested_attr::advanced_math); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); assert_eq!( - engine.eval::( - r#"import "Math::Advanced" as math; - let m = math::get_mystic_number(); - m"# - )?, + engine.eval::(r#"let m = Math::Advanced::get_mystic_number(); m"#)?, 42.0 ); Ok(()) @@ -56,16 +50,10 @@ pub mod one_fn_submodule_nested_attr { fn one_fn_submodule_nested_attr_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::one_fn_submodule_nested_attr::advanced_math); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); assert_eq!( - engine.eval::( - r#"import "Math::Advanced" as math; - let m = math::constants::get_mystic_number(); - m"# - )?, + engine.eval::(r#"let m = Math::Advanced::constants::get_mystic_number(); m"#)?, 42.0 ); Ok(()) @@ -131,26 +119,24 @@ mod export_nested_by_prefix { fn export_nested_by_prefix_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::export_nested_by_prefix::my_adds); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); let output_array = engine.eval::( - r#"import "Math::Advanced" as math; - let ex = 41.0; - let fx = math::foo_first_adders::add_float(ex, 1.0); + r#" + let ex = 41.0; + let fx = Math::Advanced::foo_first_adders::add_float(ex, 1.0); - let ei = 41; - let fi = math::foo_first_adders::add_int(ei, 1); + let ei = 41; + let fi = Math::Advanced::foo_first_adders::add_int(ei, 1); - let gx = 41.0; - let hx = math::foo_second_adders::add_float(gx, 1.0); + let gx = 41.0; + let hx = Math::Advanced::foo_second_adders::add_float(gx, 1.0); - let gi = 41; - let hi = math::foo_second_adders::add_int(gi, 1); + let gi = 41; + let hi = Math::Advanced::foo_second_adders::add_int(gi, 1); - [fx, hx, fi, hi] - "#, + [fx, hx, fi, hi] + "#, )?; assert_eq!(&output_array[0].as_float().unwrap(), &42.0); assert_eq!(&output_array[1].as_float().unwrap(), &42.0); @@ -158,44 +144,44 @@ fn export_nested_by_prefix_test() -> Result<(), Box> { assert_eq!(&output_array[3].as_int().unwrap(), &42); assert!(matches!(*engine.eval::( - r#"import "Math::Advanced" as math; - let ex = 41.0; - let fx = math::foo_third_adders::add_float(ex, 1.0); - fx - "#).unwrap_err(), - EvalAltResult::ErrorFunctionNotFound(s, p) - if s == "math::foo_third_adders::add_float (f64, f64)" - && p == rhai::Position::new(3, 41))); + r#" + let ex = 41.0; + let fx = Math::Advanced::foo_third_adders::add_float(ex, 1.0); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "Math::Advanced::foo_third_adders::add_float (f64, f64)" + && p == rhai::Position::new(3, 52))); assert!(matches!(*engine.eval::( - r#"import "Math::Advanced" as math; - let ex = 41; - let fx = math::foo_third_adders::add_int(ex, 1); - fx - "#).unwrap_err(), - EvalAltResult::ErrorFunctionNotFound(s, p) - if s == "math::foo_third_adders::add_int (i64, i64)" - && p == rhai::Position::new(3, 41))); + r#" + let ex = 41; + let fx = Math::Advanced::foo_third_adders::add_int(ex, 1); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "Math::Advanced::foo_third_adders::add_int (i64, i64)" + && p == rhai::Position::new(3, 52))); assert!(matches!(*engine.eval::( - r#"import "Math::Advanced" as math; - let ex = 41; - let fx = math::bar_fourth_adders::add_int(ex, 1); - fx - "#).unwrap_err(), - EvalAltResult::ErrorFunctionNotFound(s, p) - if s == "math::bar_fourth_adders::add_int (i64, i64)" - && p == rhai::Position::new(3, 42))); + r#" + let ex = 41; + let fx = Math::Advanced::bar_fourth_adders::add_int(ex, 1); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "Math::Advanced::bar_fourth_adders::add_int (i64, i64)" + && p == rhai::Position::new(3, 53))); assert!(matches!(*engine.eval::( - r#"import "Math::Advanced" as math; - let ex = 41.0; - let fx = math::bar_fourth_adders::add_float(ex, 1.0); - fx - "#).unwrap_err(), - EvalAltResult::ErrorFunctionNotFound(s, p) - if s == "math::bar_fourth_adders::add_float (f64, f64)" - && p == rhai::Position::new(3, 42))); + r#" + let ex = 41.0; + let fx = Math::Advanced::bar_fourth_adders::add_float(ex, 1.0); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "Math::Advanced::bar_fourth_adders::add_float (f64, f64)" + && p == rhai::Position::new(3, 53))); Ok(()) } diff --git a/doc/src/rust/modules/create.md b/doc/src/rust/modules/create.md index d31ccf42..d6d3496a 100644 --- a/doc/src/rust/modules/create.md +++ b/doc/src/rust/modules/create.md @@ -25,7 +25,6 @@ Use Case 1 - Make the `Module` Globally Available `Engine::register_global_module` registers a shared [module] into the _global_ namespace. All [functions] and [type iterators] can be accessed without _namespace qualifiers_. - Variables and sub-modules are **ignored**. This is by far the easiest way to expose a module's functionalities to Rhai. @@ -35,11 +34,11 @@ use rhai::{Engine, Module}; let mut module = Module::new(); // new module -// Use the 'set_fn_XXX' API to add functions. -let hash = module.set_fn_1("inc", |x: i64| Ok(x+1)); +// Use the 'Module::set_fn_XXX' API to add functions. +let hash = module.set_fn_1("inc", |x: i64| Ok(x + 1)); // Remember to update the parameter names/types and return type metadata. -// 'set_fn_XXX' by default does not set function metadata. +// 'Module::set_fn_XXX' by default does not set function metadata. module.update_fn_metadata(hash, ["x: i64", "i64"]); // Register the module into the global namespace of the Engine. @@ -49,6 +48,19 @@ engine.register_global_module(module.into()); engine.eval::("inc(41)")? == 42; // no need to import module ``` +Registering a [module] via `Engine::register_global_module` is essentially the _same_ +as calling `Engine::register_fn` (or any of the `Engine::register_XXX` API) individually +on each top-level function within that [module]. In fact, the actual implementation of +`Engine::register_fn` etc. simply adds the function to an internal [module]! + +```rust +// The above is essentially the same as: +let mut engine = Engine::new(); + +engine.register_fn("inc", |x: i64| x + 1); + +engine.eval::("inc(41)")? == 42; // no need to import module +``` Use Case 2 - Make the `Module` a Static Module --------------------------------------------- @@ -60,25 +72,27 @@ use rhai::{Engine, Module}; let mut module = Module::new(); // new module -// Use the 'set_fn_XXX' API to add functions. -let hash = module.set_fn_1("inc", |x: i64| Ok(x+1)); +// Use the 'Module::set_fn_XXX' API to add functions. +let hash = module.set_fn_1("inc", |x: i64| Ok(x + 1)); // Remember to update the parameter names/types and return type metadata. -// 'set_fn_XXX' by default does not set function metadata. +// 'Module::set_fn_XXX' by default does not set function metadata. module.update_fn_metadata(hash, ["x: i64", "i64"]); -// Register the module into the Engine as a static module namespace 'calc' +// Register the module into the Engine as the static module namespace path +// 'services::calc' let mut engine = Engine::new(); -engine.register_static_module("calc", module.into()); +engine.register_static_module("services::calc", module.into()); -engine.eval::("calc::inc(41)")? == 42; // refer to the 'Calc' module +// refer to the 'services::calc' module +engine.eval::("services::calc::inc(41)")? == 42; ``` ### Expose Functions to the Global Namespace -`Module::set_fn_mut` and `Module::set_fn_XXX_mut` can optionally expose functions (usually _methods_) -in the module to the _global_ namespace, so [getters/setters] and [indexers] for [custom types] -can work as expected. +The `Module::set_fn_XXX_mut` API methods can optionally expose functions in the [module] +to the _global_ namespace by setting the `namespace` parameter to `FnNamespace::Global`, +so [getters/setters] and [indexers] for [custom types] can work as expected. [Type iterators], because of their special nature, are _always_ exposed to the _global_ namespace. @@ -87,19 +101,24 @@ use rhai::{Engine, Module, FnNamespace}; let mut module = Module::new(); // new module -// Expose method 'inc' to the global namespace (default is 'Internal') -let hash = module.set_fn_1_mut("inc", FnNamespace::Global, |x: &mut i64| Ok(x+1)); +// Expose method 'inc' to the global namespace (default is 'FnNamespace::Internal') +let hash = module.set_fn_1_mut("inc", FnNamespace::Global, |x: &mut i64| Ok(x + 1)); // Remember to update the parameter names/types and return type metadata. -// 'set_fn_XXX' by default does not set function metadata. +// 'Module::set_fn_XXX_mut' by default does not set function metadata. module.update_fn_metadata(hash, ["x: &mut i64", "i64"]); // Register the module into the Engine as a static module namespace 'calc' let mut engine = Engine::new(); engine.register_static_module("calc", module.into()); -// The method 'inc' works as expected because it is exposed to the global namespace +// 'inc' works when qualified by the namespace +engine.eval::("calc::inc(41)")? == 42; + +// 'inc' also works without a namespace qualifier +// because it is exposed to the global namespace engine.eval::("let x = 41; x.inc()")? == 42; +engine.eval::("let x = 41; inc(x)")? == 42; ``` @@ -118,7 +137,7 @@ use rhai::module_resolvers::StaticModuleResolver; let mut module = Module::new(); // new module module.set_var("answer", 41_i64); // variable 'answer' under module -module.set_fn_1("inc", |x: i64| Ok(x+1)); // use the 'set_fn_XXX' API to add functions +module.set_fn_1("inc", |x: i64| Ok(x + 1)); // use the 'set_fn_XXX' API to add functions // Create the module resolver let mut resolver = StaticModuleResolver::new(); @@ -129,7 +148,7 @@ resolver.insert("question", module); // Set the module resolver into the 'Engine' let mut engine = Engine::new(); -engine.set_module_resolver(Some(resolver)); +engine.set_module_resolver(resolver); // Use namespace-qualified variables engine.eval::(r#"import "question" as q; q::answer + 1"#)? == 42; diff --git a/doc/src/rust/modules/imp-resolver.md b/doc/src/rust/modules/imp-resolver.md index 0b8f789c..1094e208 100644 --- a/doc/src/rust/modules/imp-resolver.md +++ b/doc/src/rust/modules/imp-resolver.md @@ -62,7 +62,7 @@ impl ModuleResolver for MyModuleResolver { let mut engine = Engine::new(); // Set the custom module resolver into the 'Engine'. -engine.set_module_resolver(Some(MyModuleResolver {})); +engine.set_module_resolver(MyModuleResolver {}); engine.consume(r#" import "hello" as foo; // this 'import' statement will call diff --git a/doc/src/rust/modules/resolvers.md b/doc/src/rust/modules/resolvers.md index a56b9b14..c7ba3196 100644 --- a/doc/src/rust/modules/resolvers.md +++ b/doc/src/rust/modules/resolvers.md @@ -153,13 +153,19 @@ A collection of module resolvers. Modules will be resolved from each resolver in This is useful when multiple types of modules are needed simultaneously. +`DummyResolversCollection` +------------------------- + +This module resolver acts as a _dummy_ and always fails all module resolution calls. + + Set into `Engine` ----------------- An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`: ```rust -use rhai::module_resolvers::StaticModuleResolver; +use rhai::module_resolvers::{DummyModuleResolver, StaticModuleResolver}; // Create a module resolver let resolver = StaticModuleResolver::new(); @@ -167,8 +173,9 @@ let resolver = StaticModuleResolver::new(); // Register functions into 'resolver'... // Use the module resolver -engine.set_module_resolver(Some(resolver)); +engine.set_module_resolver(resolver); -// Effectively disable 'import' statements by setting module resolver to 'None' -engine.set_module_resolver(None); +// Effectively disable 'import' statements by setting module resolver to +// the 'DummyModuleResolver' which acts as... well... a dummy. +engine.set_module_resolver(DummyModuleResolver::new()); ``` diff --git a/src/dynamic.rs b/src/dynamic.rs index c6efb8b8..ba4abb5d 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -748,13 +748,16 @@ impl Dynamic { Self(Union::Variant(Box::new(boxed), AccessMode::ReadWrite)) } - /// Turn the [`Dynamic`] value into a shared [`Dynamic`] value backed by an [`Rc`][std::rc::Rc]`<`[`RefCell`][std::cell::RefCell]`<`[`Dynamic`]`>>` - /// or [`Arc`][std::sync::Arc]`<`[`RwLock`][std::sync::RwLock]`<`[`Dynamic`]`>>` depending on the `sync` feature. + /// Turn the [`Dynamic`] value into a shared [`Dynamic`] value backed by an + /// [`Rc`][std::rc::Rc]`<`[`RefCell`][std::cell::RefCell]`<`[`Dynamic`]`>>` or + /// [`Arc`][std::sync::Arc]`<`[`RwLock`][std::sync::RwLock]`<`[`Dynamic`]`>>` + /// depending on the `sync` feature. /// /// Shared [`Dynamic`] values are relatively cheap to clone as they simply increment the /// reference counts. /// - /// Shared [`Dynamic`] values can be converted seamlessly to and from ordinary [`Dynamic`] values. + /// Shared [`Dynamic`] values can be converted seamlessly to and from ordinary [`Dynamic`] + /// values. /// /// If the [`Dynamic`] value is already shared, this method returns itself. /// @@ -970,8 +973,8 @@ impl Dynamic { /// /// If the [`Dynamic`] is not a shared value, it returns itself. /// - /// If the [`Dynamic`] is a shared value, it returns the shared value if there are - /// no outstanding references, or a cloned copy. + /// If the [`Dynamic`] is a shared value, it returns the shared value if there are no + /// outstanding references, or a cloned copy. #[inline(always)] pub fn flatten(self) -> Self { match self.0 { diff --git a/src/engine.rs b/src/engine.rs index bbef90bd..c3396a83 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -18,7 +18,7 @@ use crate::stdlib::{ collections::{HashMap, HashSet}, fmt, format, hash::{Hash, Hasher}, - iter::{empty, once}, + iter::{empty, once, FromIterator}, num::NonZeroU64, num::NonZeroUsize, ops::DerefMut, @@ -137,6 +137,22 @@ impl Imports { } } +impl<'a, T: IntoIterator)>> From for Imports { + fn from(value: T) -> Self { + Self( + value + .into_iter() + .map(|(k, v)| (k.clone(), v.clone())) + .collect(), + ) + } +} +impl FromIterator<(ImmutableString, Shared)> for Imports { + fn from_iter)>>(iter: T) -> Self { + Self(iter.into_iter().collect()) + } +} + #[cfg(not(feature = "unchecked"))] #[cfg(debug_assertions)] pub const MAX_CALL_STACK_DEPTH: usize = 8; @@ -609,11 +625,11 @@ pub struct Engine { /// A collection of all modules loaded into the global namespace of the Engine. pub(crate) global_modules: StaticVec>, /// A collection of all sub-modules directly loaded into the Engine. - pub(crate) global_sub_modules: Imports, + pub(crate) global_sub_modules: HashMap>, /// A module resolution service. #[cfg(not(feature = "no_module"))] - pub(crate) module_resolver: Option>, + pub(crate) module_resolver: Box, /// A hashmap mapping type names to pretty-print names. pub(crate) type_names: HashMap, @@ -745,7 +761,7 @@ impl Engine { #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_std"))] #[cfg(not(target_arch = "wasm32"))] - module_resolver: Some(Box::new(crate::module::resolvers::FileModuleResolver::new())), + module_resolver: Box::new(crate::module::resolvers::FileModuleResolver::new()), #[cfg(not(feature = "no_module"))] #[cfg(any(feature = "no_std", target_arch = "wasm32",))] module_resolver: None, @@ -808,7 +824,7 @@ impl Engine { global_sub_modules: Default::default(), #[cfg(not(feature = "no_module"))] - module_resolver: None, + module_resolver: Box::new(crate::module::resolvers::DummyModuleResolver::new()), type_names: Default::default(), disabled_symbols: Default::default(), @@ -849,15 +865,15 @@ impl Engine { /// Search for a variable within the scope or within imports, /// depending on whether the variable name is namespace-qualified. - pub(crate) fn search_namespace<'s, 'a>( + pub(crate) fn search_namespace<'s>( &self, scope: &'s mut Scope, mods: &mut Imports, state: &mut State, lib: &[&Module], this_ptr: &'s mut Option<&mut Dynamic>, - expr: &'a Expr, - ) -> Result<(Target<'s>, &'a str, Position), Box> { + expr: &Expr, + ) -> Result<(Target<'s>, Position), Box> { match expr { Expr::Variable(v) => match v.as_ref() { // Qualified variable @@ -876,7 +892,7 @@ impl Engine { // Module variables are constant let mut target = target.clone(); target.set_access_mode(AccessMode::ReadOnly); - Ok((target.into(), name, *pos)) + Ok((target.into(), *pos)) } // Normal variable access _ => self.search_scope_only(scope, mods, state, lib, this_ptr, expr), @@ -886,15 +902,15 @@ impl Engine { } /// Search for a variable within the scope - pub(crate) fn search_scope_only<'s, 'a>( + pub(crate) fn search_scope_only<'s>( &self, scope: &'s mut Scope, mods: &mut Imports, state: &mut State, lib: &[&Module], this_ptr: &'s mut Option<&mut Dynamic>, - expr: &'a Expr, - ) -> Result<(Target<'s>, &'a str, Position), Box> { + expr: &Expr, + ) -> Result<(Target<'s>, Position), Box> { let (index, _, Ident { name, pos }) = match expr { Expr::Variable(v) => v.as_ref(), _ => unreachable!(), @@ -903,7 +919,7 @@ impl Engine { // Check if the variable is `this` if name.as_str() == KEYWORD_THIS { if let Some(val) = this_ptr { - return Ok(((*val).into(), KEYWORD_THIS, *pos)); + return Ok(((*val).into(), *pos)); } else { return EvalAltResult::ErrorUnboundThis(*pos).into(); } @@ -931,7 +947,7 @@ impl Engine { resolve_var(name, index, &context).map_err(|err| err.fill_position(*pos))? { result.set_access_mode(AccessMode::ReadOnly); - return Ok((result.into(), name, *pos)); + return Ok((result.into(), *pos)); } } @@ -945,7 +961,7 @@ impl Engine { .0 }; - let val = scope.get_mut(index); + let val = scope.get_mut_by_index(index); // Check for data race - probably not necessary because the only place it should conflict is in a method call // when the object variable is also used as a parameter. @@ -953,7 +969,7 @@ impl Engine { // return EvalAltResult::ErrorDataRace(name.into(), *pos).into(); // } - Ok((val.into(), name, *pos)) + Ok((val.into(), *pos)) } /// Chain-evaluate a dot/index chain. @@ -1300,7 +1316,7 @@ impl Engine { self.inc_operations(state, *var_pos)?; - let (target, _, pos) = + let (target, pos) = self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?; // Constants cannot be modified @@ -1594,7 +1610,7 @@ impl Engine { match expr { // var - point directly to the value Expr::Variable(_) => { - let (mut target, _, pos) = + let (mut target, pos) = self.search_namespace(scope, mods, state, lib, this_ptr, expr)?; // If necessary, constants are cloned @@ -1720,7 +1736,7 @@ impl Engine { } } Expr::Variable(_) => { - let (val, _, _) = self.search_namespace(scope, mods, state, lib, this_ptr, expr)?; + let (val, _) = self.search_namespace(scope, mods, state, lib, this_ptr, expr)?; Ok(val.take_or_clone()) } Expr::Property(_) => unreachable!(), @@ -1926,11 +1942,15 @@ impl Engine { let mut rhs_val = self .eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)? .flatten(); - let (mut lhs_ptr, name, pos) = + let (mut lhs_ptr, pos) = self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?; if !lhs_ptr.is_ref() { - return EvalAltResult::ErrorAssignmentToConstant(name.to_string(), pos).into(); + return EvalAltResult::ErrorAssignmentToConstant( + lhs_expr.get_variable_access(false).unwrap().to_string(), + pos, + ) + .into(); } self.inc_operations(state, pos)?; @@ -1938,7 +1958,7 @@ impl Engine { if lhs_ptr.as_ref().is_read_only() { // Assignment to constant variable Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( - name.to_string(), + lhs_expr.get_variable_access(false).unwrap().to_string(), pos, ))) } else if op.is_empty() { @@ -2194,7 +2214,7 @@ impl Engine { state.scope_level += 1; for iter_value in func(iter_obj) { - let loop_var = scope.get_mut(index); + let loop_var = scope.get_mut_by_index(index); let value = iter_value.flatten(); if cfg!(not(feature = "no_closure")) && loop_var.is_shared() { @@ -2360,31 +2380,24 @@ impl Engine { .eval_expr(scope, mods, state, lib, this_ptr, &expr, level)? .try_cast::() { - if let Some(resolver) = &self.module_resolver { - let module = resolver.resolve(self, &path, expr.position())?; + let module = self.module_resolver.resolve(self, &path, expr.position())?; - if let Some(name_def) = alias { - if !module.is_indexed() { - // Index the module (making a clone copy if necessary) if it is not indexed - let mut module = crate::fn_native::shared_take_or_clone(module); - module.build_index(); - mods.push(name_def.name.clone(), module); - } else { - mods.push(name_def.name.clone(), module); - } - // When imports list is modified, clear the functions lookup cache - state.functions_cache.clear(); + if let Some(name_def) = alias { + if !module.is_indexed() { + // Index the module (making a clone copy if necessary) if it is not indexed + let mut module = crate::fn_native::shared_take_or_clone(module); + module.build_index(); + mods.push(name_def.name.clone(), module); + } else { + mods.push(name_def.name.clone(), module); } - - state.modules += 1; - - Ok(Dynamic::UNIT) - } else { - Err( - EvalAltResult::ErrorModuleNotFound(path.to_string(), expr.position()) - .into(), - ) + // When imports list is modified, clear the functions lookup cache + state.functions_cache.clear(); } + + state.modules += 1; + + Ok(Dynamic::UNIT) } else { Err(self.make_type_mismatch_err::("", expr.position())) } @@ -2410,7 +2423,7 @@ impl Engine { #[cfg(not(feature = "no_closure"))] Stmt::Share(x) => { if let Some((index, _)) = scope.get_index(&x.name) { - let val = scope.get_mut(index); + let val = scope.get_mut_by_index(index); if !val.is_shared() { // Replace the variable with a shared value. diff --git a/src/engine_api.rs b/src/engine_api.rs index 0a38b820..a5720708 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -209,9 +209,10 @@ impl Engine { pub fn register_get( &mut self, name: &str, - callback: impl Fn(&mut T) -> U + SendSync + 'static, + get_fn: impl Fn(&mut T) -> U + SendSync + 'static, ) -> &mut Self { - crate::RegisterFn::register_fn(self, &crate::engine::make_getter(name), callback) + use crate::{engine::make_getter, RegisterFn}; + self.register_fn(&make_getter(name), get_fn) } /// Register a getter function for a member of a registered type with the [`Engine`]. /// @@ -255,13 +256,10 @@ impl Engine { pub fn register_get_result( &mut self, name: &str, - callback: impl Fn(&mut T) -> Result> + SendSync + 'static, + get_fn: impl Fn(&mut T) -> Result> + SendSync + 'static, ) -> &mut Self { - crate::RegisterResultFn::register_result_fn( - self, - &crate::engine::make_getter(name), - callback, - ) + use crate::{engine::make_getter, RegisterResultFn}; + self.register_result_fn(&make_getter(name), get_fn) } /// Register a setter function for a member of a registered type with the [`Engine`]. /// @@ -304,9 +302,10 @@ impl Engine { pub fn register_set( &mut self, name: &str, - callback: impl Fn(&mut T, U) + SendSync + 'static, + set_fn: impl Fn(&mut T, U) + SendSync + 'static, ) -> &mut Self { - crate::RegisterFn::register_fn(self, &crate::engine::make_setter(name), callback) + use crate::{engine::make_setter, RegisterFn}; + self.register_fn(&make_setter(name), set_fn) } /// Register a setter function for a member of a registered type with the [`Engine`]. /// @@ -352,13 +351,12 @@ impl Engine { pub fn register_set_result( &mut self, name: &str, - callback: impl Fn(&mut T, U) -> Result<(), Box> + SendSync + 'static, + set_fn: impl Fn(&mut T, U) -> Result<(), Box> + SendSync + 'static, ) -> &mut Self { - crate::RegisterResultFn::register_result_fn( - self, - &crate::engine::make_setter(name), - move |obj: &mut T, value: U| callback(obj, value).map(Into::into), - ) + use crate::{engine::make_setter, RegisterResultFn}; + self.register_result_fn(&make_setter(name), move |obj: &mut T, value: U| { + set_fn(obj, value).map(Into::into) + }) } /// Short-hand for registering both getter and setter functions /// of a registered type with the [`Engine`]. @@ -453,7 +451,7 @@ impl Engine { #[inline(always)] pub fn register_indexer_get( &mut self, - callback: impl Fn(&mut T, X) -> U + SendSync + 'static, + get_fn: impl Fn(&mut T, X) -> U + SendSync + 'static, ) -> &mut Self { if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); @@ -469,7 +467,8 @@ impl Engine { panic!("Cannot register indexer for strings."); } - crate::RegisterFn::register_fn(self, crate::engine::FN_IDX_GET, callback) + use crate::{engine::FN_IDX_GET, RegisterFn}; + self.register_fn(FN_IDX_GET, get_fn) } /// Register an index getter for a custom type with the [`Engine`]. /// @@ -518,7 +517,7 @@ impl Engine { #[inline(always)] pub fn register_indexer_get_result( &mut self, - callback: impl Fn(&mut T, X) -> Result> + SendSync + 'static, + get_fn: impl Fn(&mut T, X) -> Result> + SendSync + 'static, ) -> &mut Self { if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); @@ -534,7 +533,8 @@ impl Engine { panic!("Cannot register indexer for strings."); } - crate::RegisterResultFn::register_result_fn(self, crate::engine::FN_IDX_GET, callback) + use crate::{engine::FN_IDX_GET, RegisterResultFn}; + self.register_result_fn(FN_IDX_GET, get_fn) } /// Register an index setter for a custom type with the [`Engine`]. /// @@ -581,7 +581,7 @@ impl Engine { #[inline(always)] pub fn register_indexer_set( &mut self, - callback: impl Fn(&mut T, X, U) + SendSync + 'static, + set_fn: impl Fn(&mut T, X, U) + SendSync + 'static, ) -> &mut Self { if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); @@ -597,7 +597,8 @@ impl Engine { panic!("Cannot register indexer for strings."); } - crate::RegisterFn::register_fn(self, crate::engine::FN_IDX_SET, callback) + use crate::{engine::FN_IDX_SET, RegisterFn}; + self.register_fn(FN_IDX_SET, set_fn) } /// Register an index setter for a custom type with the [`Engine`]. /// @@ -651,7 +652,7 @@ impl Engine { U: Variant + Clone, >( &mut self, - callback: impl Fn(&mut T, X, U) -> Result<(), Box> + SendSync + 'static, + set_fn: impl Fn(&mut T, X, U) -> Result<(), Box> + SendSync + 'static, ) -> &mut Self { if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); @@ -667,11 +668,10 @@ impl Engine { panic!("Cannot register indexer for strings."); } - crate::RegisterResultFn::register_result_fn( - self, - crate::engine::FN_IDX_SET, - move |obj: &mut T, index: X, value: U| callback(obj, index, value).map(Into::into), - ) + use crate::{engine::FN_IDX_SET, RegisterResultFn}; + self.register_result_fn(FN_IDX_SET, move |obj: &mut T, index: X, value: U| { + set_fn(obj, index, value).map(Into::into) + }) } /// Short-hand for register both index getter and setter functions for a custom type with the [`Engine`]. /// @@ -755,7 +755,7 @@ impl Engine { /// /// ``` /// # fn main() -> Result<(), Box> { - /// use rhai::{Engine, Module}; + /// use rhai::{Engine, Shared, Module}; /// /// let mut engine = Engine::new(); /// @@ -763,9 +763,18 @@ impl Engine { /// let mut module = Module::new(); /// module.set_fn_1("calc", |x: i64| Ok(x + 1)); /// - /// // Register the module as a fixed sub-module - /// engine.register_static_module("CalcService", module.into()); + /// let module: Shared = module.into(); /// + /// // Register the module as a fixed sub-module + /// engine.register_static_module("foo::bar::baz", module.clone()); + /// + /// // Multiple registrations to the same partial path is also OK! + /// engine.register_static_module("foo::bar::hello", module.clone()); + /// + /// engine.register_static_module("CalcService", module); + /// + /// assert_eq!(engine.eval::("foo::bar::baz::calc(41)")?, 42); + /// assert_eq!(engine.eval::("foo::bar::hello::calc(41)")?, 42); /// assert_eq!(engine.eval::("CalcService::calc(41)")?, 42); /// # Ok(()) /// # } @@ -773,19 +782,49 @@ impl Engine { #[cfg(not(feature = "no_module"))] pub fn register_static_module( &mut self, - name: impl Into, + name: impl AsRef, module: Shared, ) -> &mut Self { - if !module.is_indexed() { - // Index the module (making a clone copy if necessary) if it is not indexed - let mut module = crate::fn_native::shared_take_or_clone(module); - module.build_index(); - self.global_sub_modules.push(name, module); - } else { - self.global_sub_modules.push(name, module); + fn register_static_module_raw( + root: &mut crate::stdlib::collections::HashMap>, + name: impl AsRef, + module: Shared, + ) { + let separator = crate::token::Token::DoubleColon.syntax(); + + if !name.as_ref().contains(separator.as_ref()) { + if !module.is_indexed() { + // Index the module (making a clone copy if necessary) if it is not indexed + let mut module = crate::fn_native::shared_take_or_clone(module); + module.build_index(); + root.insert(name.as_ref().trim().into(), module.into()); + } else { + root.insert(name.as_ref().trim().into(), module); + } + } else { + let mut iter = name.as_ref().splitn(2, separator.as_ref()); + let sub_module = iter.next().unwrap().trim(); + let remainder = iter.next().unwrap().trim(); + + if !root.contains_key(sub_module) { + let mut m: Module = Default::default(); + register_static_module_raw(m.sub_modules_mut(), remainder, module); + m.build_index(); + root.insert(sub_module.into(), m.into()); + } else { + let m = root.remove(sub_module).unwrap(); + let mut m = crate::fn_native::shared_take_or_clone(m); + register_static_module_raw(m.sub_modules_mut(), remainder, module); + m.build_index(); + root.insert(sub_module.into(), m.into()); + } + } } + + register_static_module_raw(&mut self.global_sub_modules, name.as_ref(), module); self } + /// Register a shared [`Module`][crate::Module] as a static module namespace with the [`Engine`]. /// /// ## Deprecated @@ -796,7 +835,7 @@ impl Engine { #[deprecated = "use `register_static_module` instead"] pub fn register_module( &mut self, - name: impl Into, + name: impl AsRef, module: impl Into>, ) -> &mut Self { self.register_static_module(name, module.into()) @@ -1407,7 +1446,7 @@ impl Engine { scope: &mut Scope, ast: &AST, ) -> Result> { - let mods = &mut self.global_sub_modules.clone(); + let mods = &mut (&self.global_sub_modules).into(); let result = self.eval_ast_with_scope_raw(scope, mods, ast)?; @@ -1493,7 +1532,7 @@ impl Engine { scope: &mut Scope, ast: &AST, ) -> Result<(), Box> { - let mods = &mut self.global_sub_modules.clone(); + let mods = &mut (&self.global_sub_modules).into(); let state = &mut State { source: ast.clone_source(), ..Default::default() @@ -1539,12 +1578,12 @@ impl Engine { /// ``` #[cfg(not(feature = "no_function"))] #[inline] - pub fn call_fn( + pub fn call_fn( &self, scope: &mut Scope, ast: &AST, name: &str, - args: A, + args: impl crate::fn_args::FuncArgs, ) -> Result> { let mut arg_values = args.into_vec(); let mut args: crate::StaticVec<_> = arg_values.as_mut().iter_mut().collect(); @@ -1650,7 +1689,7 @@ impl Engine { .ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::NONE))?; let mut state = Default::default(); - let mut mods = self.global_sub_modules.clone(); + let mut mods = (&self.global_sub_modules).into(); // Check for data race. if cfg!(not(feature = "no_closure")) { diff --git a/src/engine_settings.rs b/src/engine_settings.rs index 7d5d17f4..56e9104c 100644 --- a/src/engine_settings.rs +++ b/src/engine_settings.rs @@ -168,9 +168,9 @@ impl Engine { #[inline(always)] pub fn set_module_resolver( &mut self, - resolver: Option, + resolver: impl crate::ModuleResolver + 'static, ) -> &mut Self { - self.module_resolver = resolver.map(|f| Box::new(f) as Box); + self.module_resolver = Box::new(resolver); self } /// Disable a particular keyword or operator in the language. diff --git a/src/fn_call.rs b/src/fn_call.rs index 2fe6add9..8b42985f 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -1041,7 +1041,7 @@ impl Engine { .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) .collect::>()?; - let (mut target, _, pos) = + let (mut target, pos) = self.search_namespace(scope, mods, state, lib, this_ptr, &args_expr[0])?; if target.as_ref().is_read_only() { @@ -1137,7 +1137,7 @@ impl Engine { .collect::>()?; // Get target reference to first argument - let (target, _, pos) = + let (target, pos) = self.search_scope_only(scope, mods, state, lib, this_ptr, &args_expr[0])?; self.inc_operations(state, pos)?; diff --git a/src/fn_func.rs b/src/fn_func.rs index 94b3cba5..1791c0ba 100644 --- a/src/fn_func.rs +++ b/src/fn_func.rs @@ -27,15 +27,15 @@ pub trait Func { /// // Func takes two type parameters: /// // 1) a tuple made up of the types of the script function's parameters /// // 2) the return type of the script function - /// // + /// /// // 'func' will have type Box Result>> and is callable! /// let func = Func::<(i64, String), bool>::create_from_ast( /// // ^^^^^^^^^^^^^ function parameter types in tuple /// - /// engine, // the 'Engine' is consumed into the closure - /// ast, // the 'AST' - /// "calc" // the entry-point function name - /// ); + /// engine, // the 'Engine' is consumed into the closure + /// ast, // the 'AST' + /// "calc" // the entry-point function name + /// ); /// /// func(123, "hello".to_string())? == false; // call the anonymous function /// # Ok(()) @@ -58,15 +58,15 @@ pub trait Func { /// // Func takes two type parameters: /// // 1) a tuple made up of the types of the script function's parameters /// // 2) the return type of the script function - /// // + /// /// // 'func' will have type Box Result>> and is callable! /// let func = Func::<(i64, String), bool>::create_from_script( /// // ^^^^^^^^^^^^^ function parameter types in tuple /// - /// engine, // the 'Engine' is consumed into the closure - /// script, // the script, notice number of parameters must match - /// "calc" // the entry-point function name - /// )?; + /// engine, // the 'Engine' is consumed into the closure + /// script, // the script, notice number of parameters must match + /// "calc" // the entry-point function name + /// )?; /// /// func(123, "hello".to_string())? == false; // call the anonymous function /// # Ok(()) diff --git a/src/fn_native.rs b/src/fn_native.rs index 71a3a775..68b5b0ce 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -3,7 +3,14 @@ use crate::ast::{FnAccess, ScriptFnDef}; use crate::engine::Imports; use crate::plugin::PluginFunction; -use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, iter::empty, mem, string::String}; +use crate::stdlib::{ + boxed::Box, + convert::{TryFrom, TryInto}, + fmt, + iter::empty, + mem, + string::String, +}; use crate::token::is_valid_identifier; use crate::{ calc_script_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ImmutableString, Module, @@ -153,25 +160,13 @@ impl<'e, 's, 'a, 'm, 'pm> NativeCallContext<'e, 's, 'a, 'm, 'pm> { args: &mut [&mut Dynamic], def_value: Option<&Dynamic>, ) -> Result> { - let mut mods = self.mods.cloned().unwrap_or_default(); - - let hash_script = calc_script_fn_hash( - empty(), - fn_name, - if is_method { - args.len() - 1 - } else { - args.len() - }, - ); - self.engine() .exec_fn_call( - &mut mods, + &mut self.mods.cloned().unwrap_or_default(), &mut Default::default(), self.lib, fn_name, - hash_script, + calc_script_fn_hash(empty(), fn_name, args.len() - if is_method { 1 } else { 0 }), args, is_method, is_method, @@ -225,11 +220,15 @@ pub type FnCallArgs<'a> = [&'a mut Dynamic]; /// A general function pointer, which may carry additional (i.e. curried) argument values /// to be passed onto a function during a call. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct FnPtr(ImmutableString, StaticVec); impl FnPtr { /// Create a new function pointer. + pub fn new(name: impl Into) -> Result> { + name.into().try_into() + } + /// Create a new function pointer without checking its parameters. #[inline(always)] pub(crate) fn new_unchecked( name: impl Into, @@ -257,7 +256,24 @@ impl FnPtr { pub fn curry(&self) -> &[Dynamic] { self.1.as_ref() } - /// Does this function pointer refer to an anonymous function? + /// Add a new curried argument. + #[inline(always)] + pub fn add_curry(&mut self, value: Dynamic) -> &mut Self { + self.1.push(value); + self + } + /// Set curried arguments to the function pointer. + #[inline(always)] + pub fn set_curry(&mut self, values: impl IntoIterator) -> &mut Self { + self.1 = values.into_iter().collect(); + self + } + /// Is the function pointer curried? + #[inline(always)] + pub fn is_curried(&self) -> bool { + !self.1.is_empty() + } + /// Does the function pointer refer to an anonymous function? #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn is_anonymous(&self) -> bool { diff --git a/src/module/mod.rs b/src/module/mod.rs index 8d5c880d..95b079ae 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -35,9 +35,9 @@ use crate::Map; /// A type representing the namespace of a function. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum FnNamespace { - /// Global namespace. + /// Expose to global namespace. Global, - /// Internal only. + /// Module namespace only. Internal, } @@ -465,6 +465,16 @@ impl Module { .map(|FuncInfo { func, .. }| func.get_fn_def()) } + /// Get a mutable reference to the underlying [`HashMap`] of sub-modules. + #[inline(always)] + pub(crate) fn sub_modules_mut(&mut self) -> &mut HashMap> { + // We must assume that the user has changed the sub-modules + // (otherwise why take a mutable reference?) + self.indexed = false; + + &mut self.modules + } + /// Does a sub-module exist in the module? /// /// # Example @@ -1699,7 +1709,7 @@ impl Module { ast: &crate::AST, engine: &crate::Engine, ) -> Result> { - let mut mods = engine.global_sub_modules.clone(); + let mut mods: crate::engine::Imports = (&engine.global_sub_modules).into(); let orig_mods_len = mods.len(); // Run the script diff --git a/src/module/resolvers/collection.rs b/src/module/resolvers/collection.rs index 71c3fe27..30039234 100644 --- a/src/module/resolvers/collection.rs +++ b/src/module/resolvers/collection.rs @@ -16,7 +16,7 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared}; /// collection.push(resolver); /// /// let mut engine = Engine::new(); -/// engine.set_module_resolver(Some(collection)); +/// engine.set_module_resolver(collection); /// ``` #[derive(Default)] pub struct ModuleResolversCollection(Vec>); @@ -36,16 +36,41 @@ impl ModuleResolversCollection { /// collection.push(resolver); /// /// let mut engine = Engine::new(); - /// engine.set_module_resolver(Some(collection)); + /// engine.set_module_resolver(collection); /// ``` #[inline(always)] pub fn new() -> Self { Default::default() } - /// Add a module keyed by its path. + /// Append a module resolver to the end. #[inline(always)] - pub fn push(&mut self, resolver: impl ModuleResolver + 'static) { + pub fn push(&mut self, resolver: impl ModuleResolver + 'static) -> &mut Self { self.0.push(Box::new(resolver)); + self + } + /// Insert a module resolver to an offset index. + /// + /// # Panics + /// + /// Panics if the index is out of bounds. + #[inline(always)] + pub fn insert(&mut self, index: usize, resolver: impl ModuleResolver + 'static) -> &mut Self { + self.0.insert(index, Box::new(resolver)); + self + } + /// Remove the last module resolver from the end, if any. + #[inline(always)] + pub fn pop(&mut self) -> Option> { + self.0.pop() + } + /// Remove a module resolver at an offset index. + /// + /// # Panics + /// + /// Panics if the index is out of bounds. + #[inline(always)] + pub fn remove(&mut self, index: usize) -> Box { + self.0.remove(index) } /// Get an iterator of all the module resolvers. #[inline(always)] @@ -59,8 +84,9 @@ impl ModuleResolversCollection { } /// Remove all module resolvers. #[inline(always)] - pub fn clear(&mut self) { + pub fn clear(&mut self) -> &mut Self { self.0.clear(); + self } /// Is this [`ModuleResolversCollection`] empty? #[inline(always)] @@ -75,8 +101,9 @@ impl ModuleResolversCollection { /// Add another [`ModuleResolversCollection`] to the end of this collection. /// The other [`ModuleResolversCollection`] is consumed. #[inline(always)] - pub fn append(&mut self, other: Self) { + pub fn append(&mut self, other: Self) -> &mut Self { self.0.extend(other.0.into_iter()); + self } } diff --git a/src/module/resolvers/dummy.rs b/src/module/resolvers/dummy.rs new file mode 100644 index 00000000..8ed3b3f0 --- /dev/null +++ b/src/module/resolvers/dummy.rs @@ -0,0 +1,48 @@ +use crate::stdlib::boxed::Box; +use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared}; + +/// Empty/disabled module resolution service that acts as a dummy. +/// +/// # Example +/// +/// ``` +/// use rhai::{Engine, Module}; +/// use rhai::module_resolvers::DummyModuleResolver; +/// +/// let resolver = DummyModuleResolver::new(); +/// let mut engine = Engine::new(); +/// engine.set_module_resolver(resolver); +/// ``` +#[derive(Debug, Copy, Eq, PartialEq, Clone, Default, Hash)] +pub struct DummyModuleResolver; + +impl DummyModuleResolver { + /// Create a new [`DummyModuleResolver`]. + /// + /// # Example + /// + /// ``` + /// use rhai::{Engine, Module}; + /// use rhai::module_resolvers::DummyModuleResolver; + /// + /// let resolver = DummyModuleResolver::new(); + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(resolver); + /// ``` + #[inline(always)] + pub fn new() -> Self { + Default::default() + } +} + +impl ModuleResolver for DummyModuleResolver { + #[inline(always)] + fn resolve( + &self, + _: &Engine, + path: &str, + pos: Position, + ) -> Result, Box> { + EvalAltResult::ErrorModuleNotFound(path.into(), pos).into() + } +} diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index 0831ff25..b3be9bce 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -1,5 +1,9 @@ use crate::stdlib::{ - boxed::Box, collections::HashMap, io::Error as IoError, path::PathBuf, string::String, + boxed::Box, + collections::HashMap, + io::Error as IoError, + path::{Path, PathBuf}, + string::String, }; use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared}; @@ -31,7 +35,7 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared}; /// /// let mut engine = Engine::new(); /// -/// engine.set_module_resolver(Some(resolver)); +/// engine.set_module_resolver(resolver); /// ``` #[derive(Debug)] pub struct FileModuleResolver { @@ -65,10 +69,10 @@ impl FileModuleResolver { /// let resolver = FileModuleResolver::new_with_path("./scripts"); /// /// let mut engine = Engine::new(); - /// engine.set_module_resolver(Some(resolver)); + /// engine.set_module_resolver(resolver); /// ``` #[inline(always)] - pub fn new_with_path>(path: P) -> Self { + pub fn new_with_path(path: impl Into) -> Self { Self::new_with_path_and_extension(path, "rhai") } @@ -87,12 +91,12 @@ impl FileModuleResolver { /// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x"); /// /// let mut engine = Engine::new(); - /// engine.set_module_resolver(Some(resolver)); + /// engine.set_module_resolver(resolver); /// ``` #[inline(always)] - pub fn new_with_path_and_extension, E: Into>( - path: P, - extension: E, + pub fn new_with_path_and_extension( + path: impl Into, + extension: impl Into, ) -> Self { Self { path: path.into(), @@ -114,12 +118,64 @@ impl FileModuleResolver { /// let resolver = FileModuleResolver::new(); /// /// let mut engine = Engine::new(); - /// engine.set_module_resolver(Some(resolver)); + /// engine.set_module_resolver(resolver); /// ``` #[inline(always)] pub fn new() -> Self { Default::default() } + + /// Get the base path for script files. + #[inline(always)] + pub fn path(&self) -> &Path { + self.path.as_ref() + } + /// Set the base path for script files. + #[inline(always)] + pub fn set_path(&mut self, path: impl Into) -> &mut Self { + self.path = path.into(); + self + } + + /// Get the script file extension. + #[inline(always)] + pub fn extension(&self) -> &str { + &self.extension + } + + /// Set the script file extension. + #[inline(always)] + pub fn set_extension(&mut self, extension: impl Into) -> &mut Self { + self.extension = extension.into(); + self + } + + /// Empty the internal cache. + #[inline(always)] + pub fn clear_cache(&mut self) { + #[cfg(not(feature = "sync"))] + self.cache.borrow_mut().clear(); + #[cfg(feature = "sync")] + self.cache.write().unwrap().clear(); + } + + /// Empty the internal cache. + #[inline(always)] + pub fn clear_cache_for_path(&mut self, path: impl AsRef) -> Option> { + #[cfg(not(feature = "sync"))] + return self + .cache + .borrow_mut() + .remove_entry(path.as_ref()) + .map(|(_, v)| v); + #[cfg(feature = "sync")] + return self + .cache + .write() + .unwrap() + .remove_entry(path.as_ref()) + .map(|(_, v)| v); + } } impl ModuleResolver for FileModuleResolver { diff --git a/src/module/resolvers/mod.rs b/src/module/resolvers/mod.rs index d131a1d0..f257f0e8 100644 --- a/src/module/resolvers/mod.rs +++ b/src/module/resolvers/mod.rs @@ -2,6 +2,9 @@ use crate::fn_native::SendSync; use crate::stdlib::boxed::Box; use crate::{Engine, EvalAltResult, Module, Position, Shared}; +mod dummy; +pub use dummy::DummyModuleResolver; + mod collection; pub use collection::ModuleResolversCollection; diff --git a/src/module/resolvers/stat.rs b/src/module/resolvers/stat.rs index 553a462f..311d92d0 100644 --- a/src/module/resolvers/stat.rs +++ b/src/module/resolvers/stat.rs @@ -16,7 +16,7 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared}; /// /// let mut engine = Engine::new(); /// -/// engine.set_module_resolver(Some(resolver)); +/// engine.set_module_resolver(resolver); /// ``` #[derive(Debug, Clone, Default)] pub struct StaticModuleResolver(HashMap>); @@ -36,7 +36,7 @@ impl StaticModuleResolver { /// resolver.insert("hello", module); /// /// let mut engine = Engine::new(); - /// engine.set_module_resolver(Some(resolver)); + /// engine.set_module_resolver(resolver); /// ``` #[inline(always)] pub fn new() -> Self { @@ -60,8 +60,13 @@ impl StaticModuleResolver { } /// 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.clone())) + 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)] @@ -75,8 +80,8 @@ impl StaticModuleResolver { } /// Get an iterator of all the modules. #[inline(always)] - pub fn values<'a>(&'a self) -> impl Iterator> + 'a { - self.0.values().map(|m| m.clone()) + pub fn values(&self) -> impl Iterator> { + self.0.values().map(|m| m) } /// Remove all modules. #[inline(always)] @@ -95,6 +100,8 @@ impl StaticModuleResolver { } /// Merge another [`StaticModuleResolver`] into this. /// The other [`StaticModuleResolver`] is consumed. + /// + /// Existing modules of the same path name are overwritten. #[inline(always)] pub fn merge(&mut self, other: Self) { if !other.is_empty() { diff --git a/src/optimize.rs b/src/optimize.rs index 06c94f46..d6e3099f 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -2,7 +2,7 @@ use crate::ast::{Expr, ScriptFnDef, Stmt}; use crate::dynamic::AccessMode; -use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF}; +use crate::engine::{Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF}; use crate::fn_call::run_builtin_binary_op; use crate::parser::map_dynamic_to_expr; use crate::stdlib::{ @@ -62,6 +62,8 @@ struct State<'a> { variables: Vec<(String, AccessMode, Expr)>, /// An [`Engine`] instance for eager function evaluation. engine: &'a Engine, + /// Collection of sub-modules. + mods: Imports, /// [Module] containing script-defined functions. lib: &'a [&'a Module], /// Optimization level. @@ -76,6 +78,7 @@ impl<'a> State<'a> { changed: false, variables: vec![], engine, + mods: (&engine.global_sub_modules).into(), lib, optimization_level: level, } @@ -655,7 +658,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect(); // Search for overloaded operators (can override built-in). - if !state.engine.has_override_by_name_and_arguments(Some(&state.engine.global_sub_modules), state.lib, x.name.as_ref(), arg_types.as_ref(), false) { + if !state.engine.has_override_by_name_and_arguments(Some(&state.mods), state.lib, x.name.as_ref(), arg_types.as_ref(), false) { if let Some(result) = run_builtin_binary_op(x.name.as_ref(), &arg_values[0], &arg_values[1]) .ok().flatten() .and_then(|result| map_dynamic_to_expr(result, *pos)) diff --git a/src/parse_error.rs b/src/parse_error.rs index 7419fb98..a9ce4e7f 100644 --- a/src/parse_error.rs +++ b/src/parse_error.rs @@ -91,13 +91,16 @@ pub enum ParseErrorType { UnknownOperator(String), /// Expecting a particular token but not finding one. Wrapped values are the token and description. MissingToken(String, String), - /// An expression in function call arguments `()` has syntax error. Wrapped value is the error description (if any). + /// An expression in function call arguments `()` has syntax error. Wrapped value is the error + /// description (if any). MalformedCallExpr(String), - /// An expression in indexing brackets `[]` has syntax error. Wrapped value is the error description (if any). + /// An expression in indexing brackets `[]` has syntax error. Wrapped value is the error + /// description (if any). /// /// Never appears under the `no_index` feature. MalformedIndexExpr(String), - /// An expression in an `in` expression has syntax error. Wrapped value is the error description (if any). + /// An expression in an `in` expression has syntax error. Wrapped value is the error description + /// (if any). /// /// Never appears under the `no_object` and `no_index` features combination. MalformedInExpr(String), @@ -137,7 +140,8 @@ pub enum ParseErrorType { /// /// Never appears under the `no_function` feature. FnMissingParams(String), - /// A function definition has duplicated parameters. Wrapped values are the function name and parameter name. + /// A function definition has duplicated parameters. Wrapped values are the function name and + /// parameter name. /// /// Never appears under the `no_function` feature. FnDuplicatedParam(String, String), diff --git a/src/parser.rs b/src/parser.rs index 5a3b9747..e3e521eb 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1325,6 +1325,7 @@ fn parse_unary( } } +/// Make an assignment statement. fn make_assignment_stmt<'a>( fn_name: Cow<'static, str>, state: &mut ParseState, diff --git a/src/plugin.rs b/src/plugin.rs index d07cc188..9882951f 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -13,8 +13,8 @@ pub use rhai_codegen::*; pub use rhai_codegen::{export_fn, register_exported_fn}; /// Trait implemented by a _plugin function_. -/// This trait should not be used directly. /// +/// This trait should not be used directly. /// Use the `#[export_module]` and `#[export_fn]` procedural attributes instead. pub trait PluginFunction { /// Call the plugin function with the arguments provided. diff --git a/src/result.rs b/src/result.rs index 7719f225..02a0f5c8 100644 --- a/src/result.rs +++ b/src/result.rs @@ -55,8 +55,8 @@ pub enum EvalAltResult { /// String indexing out-of-bounds. /// Wrapped values are the current number of characters in the string and the index number. ErrorStringBounds(usize, INT, Position), - /// Trying to index into a type that is not an array, an object map, or a string, and has no indexer function defined. - /// Wrapped value is the type name. + /// Trying to index into a type that is not an array, an object map, or a string, and has no + /// indexer function defined. Wrapped value is the type name. ErrorIndexingType(String, Position), /// Invalid arguments for `in` operator. ErrorInExpr(Position), diff --git a/src/scope.rs b/src/scope.rs index 6695ab0a..3498dce3 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -41,9 +41,11 @@ use crate::{Dynamic, ImmutableString, StaticVec}; // [`Scope`] is implemented as two [`Vec`]'s of exactly the same length. Variables data (name, type, etc.) // is manually split into two equal-length arrays. That's because variable names take up the most space, // with [`Cow`][Cow] being four words long, but in the vast majority of cases the name is NOT used to -/// look up a variable. Variable lookup is usually via direct indexing, by-passing the name altogether. +// look up a variable. Variable lookup is usually via direct indexing, by-passing the name altogether. // // Since [`Dynamic`] is reasonably small, packing it tightly improves cache locality when variables are accessed. +// +// The alias is `Box`'ed because it occurs infrequently. #[derive(Debug, Clone, Hash)] pub struct Scope<'a> { /// Current value of the entry. @@ -356,8 +358,35 @@ impl<'a> Scope<'a> { self } /// Get a mutable reference to an entry in the [`Scope`]. + /// + /// If the entry by the specified name is not found, of if it is read-only, + /// [`None`] is returned. + /// + /// # Example + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push("x", 42_i64); + /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); + /// + /// let ptr = my_scope.get_mut("x").unwrap(); + /// *ptr = 123_i64.into(); + /// + /// assert_eq!(my_scope.get_value::("x").unwrap(), 123); + /// ``` + pub fn get_mut(&mut self, name: &str) -> Option<&mut Dynamic> { + self.get_index(name) + .and_then(move |(index, access)| match access { + AccessMode::ReadWrite => Some(self.get_mut_by_index(index)), + AccessMode::ReadOnly => None, + }) + } + /// Get a mutable reference to an entry in the [`Scope`] based on the index. #[inline(always)] - pub(crate) fn get_mut(&mut self, index: usize) -> &mut Dynamic { + pub(crate) fn get_mut_by_index(&mut self, index: usize) -> &mut Dynamic { self.values.get_mut(index).expect("invalid index in Scope") } /// Update the access type of an entry in the [`Scope`]. diff --git a/src/utils.rs b/src/utils.rs index 5a9be55d..52dfda99 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -63,7 +63,8 @@ pub fn get_hasher() -> impl Hasher { s } -/// _(INTERNALS)_ Calculate a [`NonZeroU64`] hash key from a namespace-qualified function name and parameter types. +/// _(INTERNALS)_ Calculate a [`NonZeroU64`] hash key from a namespace-qualified function name and +/// parameter types. /// Exported under the `internals` feature only. /// /// Module names are passed in via `&str` references from an iterator. @@ -108,6 +109,7 @@ pub fn calc_script_fn_hash<'a>( /// # Note /// /// The first module name is skipped. Hashing starts from the _second_ module in the chain. +#[inline(always)] fn calc_fn_hash<'a>( mut modules: impl Iterator, fn_name: &str, diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 7b959472..c95e2853 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -82,7 +82,7 @@ fn test_call_fn_private() -> Result<(), Box> { let ast = engine.compile("private fn add(x, n, ) { x + n }")?; assert!(matches!( - *engine.call_fn::<_, INT>(&mut scope, &ast, "add", (40 as INT, 2 as INT)) + *(engine.call_fn(&mut scope, &ast, "add", (40 as INT, 2 as INT)) as Result>) .expect_err("should error"), EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "add" )); diff --git a/tests/closures.rs b/tests/closures.rs index 7c74e9bd..792f1ca7 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -239,7 +239,7 @@ fn test_closures_shared_obj() -> Result<(), Box> { let f = move |p1: TestStruct, p2: TestStruct| -> Result<(), Box> { let action_ptr = res["action"].clone().cast::(); let name = action_ptr.fn_name(); - engine.call_fn::<_, ()>(&mut Scope::new(), &ast, name, (p1, p2)) + engine.call_fn(&mut Scope::new(), &ast, name, (p1, p2)) }; // Test closure diff --git a/tests/modules.rs b/tests/modules.rs index b07d4d5d..26c4af8c 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -101,7 +101,7 @@ fn test_module_resolver() -> Result<(), Box> { resolver.insert("hello", module); let mut engine = Engine::new(); - engine.set_module_resolver(Some(resolver)); + engine.set_module_resolver(resolver); assert_eq!( engine.eval::( @@ -295,13 +295,13 @@ fn test_module_from_ast() -> Result<(), Box> { "#, )?; - engine.set_module_resolver(Some(resolver1)); + engine.set_module_resolver(resolver1); let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?; let mut resolver2 = StaticModuleResolver::new(); resolver2.insert("testing", module); - engine.set_module_resolver(Some(resolver2)); + engine.set_module_resolver(resolver2); assert_eq!( engine.eval::(r#"import "testing" as ttt; ttt::abc"#)?, @@ -384,7 +384,7 @@ fn test_module_str() -> Result<(), Box> { let mut static_modules = rhai::module_resolvers::StaticModuleResolver::new(); static_modules.insert("test", module); - engine.set_module_resolver(Some(static_modules)); + engine.set_module_resolver(static_modules); assert_eq!( engine.eval::(r#"import "test" as test; test::test("test");"#)?, @@ -418,7 +418,7 @@ fn test_module_ast_namespace() -> Result<(), Box> { let mut resolver = StaticModuleResolver::new(); resolver.insert("testing", module); - engine.set_module_resolver(Some(resolver)); + engine.set_module_resolver(resolver); assert_eq!( engine.eval::(r#"import "testing" as t; t::foo(41)"#)?, @@ -466,7 +466,7 @@ fn test_module_ast_namespace2() -> Result<(), Box> { let module = Module::eval_ast_as_new(Scope::new(), &module_ast, &engine)?; let mut static_modules = rhai::module_resolvers::StaticModuleResolver::new(); static_modules.insert("test_module", module); - engine.set_module_resolver(Some(static_modules)); + engine.set_module_resolver(static_modules); engine.consume(SCRIPT)?;