From ef02150afdf3daf7634003affdf6ac0e78a3960d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 16 Nov 2020 14:07:48 +0800 Subject: [PATCH] Expose methods for Engine::register_module. --- RELEASES.md | 2 +- doc/src/patterns/enums.md | 182 ++++++++++++++++++++++----------- doc/src/plugins/module.md | 17 ++- doc/src/rust/modules/create.md | 23 ++++- src/engine.rs | 73 ++++++++++--- src/engine_api.rs | 4 +- src/fn_call.rs | 46 +++++---- src/fn_native.rs | 6 +- src/module/mod.rs | 92 +++++++++++++---- src/optimize.rs | 2 +- src/packages/mod.rs | 18 +--- tests/for.rs | 44 +++++++- tests/modules.rs | 25 +++-- 13 files changed, 385 insertions(+), 149 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 22339cef..7d904b64 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -10,7 +10,7 @@ New features ------------ * `switch` statement. -* `Engine::register_module` to register a module as a sub-module in the global namespace. +* `Engine::register_module` to register a module as a sub-module in the global namespace, while at the same time exposing its method functions globally. This is convenient when registering an API for a custom type. Enhancements ------------ diff --git a/doc/src/patterns/enums.md b/doc/src/patterns/enums.md index b27eba08..a33c0303 100644 --- a/doc/src/patterns/enums.md +++ b/doc/src/patterns/enums.md @@ -12,8 +12,10 @@ enum variants or to extract internal data from them. Simulate an Enum API -------------------- +A [plugin module] is extremely handy in creating an entire API for a custom enum type. + ```rust -use rhai::{Engine, RegisterFn, Dynamic, EvalAltResult}; +use rhai::{Engine, Dynamic, EvalAltResult}; use rhai::plugin::*; #[derive(Debug, Clone, Eq, PartialEq, Hash)] @@ -25,48 +27,76 @@ enum MyEnum { // Create a plugin module with functions constructing the 'MyEnum' variants #[export_module] -pub mod MyEnumModule { - // 'MyEnum' variants +mod MyEnumModule { + // Constructors for 'MyEnum' variants pub const Foo: &MyEnum = MyEnum::Foo; - pub fn Bar(value: i64) -> MyEnum { MyEnum::Bar(value) } - pub fn Baz(val1: String, val2: bool) -> MyEnum { MyEnum::Baz(val1, val2) } -} - -let mut engine = Engine::new(); - -// Register API for 'MyEnum' -engine - // Register enum custom type - .register_type_with_name::("MyEnum") - // Register access to fields - .register_get("type", |a: &mut MyEnum| match a { - MyEnum::Foo => "Foo".to_string(), - MyEnum::Bar(_) => "Bar".to_string(), - MyEnum::Baz(_, _) => "Baz".to_string() - }) - .register_get("field_0", |a: &mut MyEnum| match a { - MyEnum::Foo => Dynamic::UNIT, - MyEnum::Bar(x) => Dynamic::from(x), - MyEnum::Baz(x, _) => Dynamic::from(x) - }) - .register_get("field_1", |a: &mut MyEnum| match a { - MyEnum::Foo | MyEnum::Bar(_) => Dynamic::UNIT, - MyEnum::Baz(_, x) => Dynamic::from(x) - }) - // Register printing - .register_fn("to_string", |a: &mut MyEnum| format!("{:?}", a)) - .register_fn("print", |a: &mut MyEnum| format!("{:?}", a)) - .register_fn("debug", |a: &mut MyEnum| format!("{:?}", a)) - .register_fn("+", |s: &str, a: MyEnum| format!("{}{:?}", s, a)) - .register_fn("+", |a: &mut MyEnum, s: &str| format!("{:?}", a).push_str(s)) - .register_fn("+=", |s: &mut ImmutableString, a: MyEnum| s += a.to_string()) - // Register '==' and '!=' operators - .register_fn("==", |a: &mut MyEnum, b: MyEnum| a == &b) - .register_fn("!=", |a: &mut MyEnum, b: MyEnum| a != &b) - // Register array functions - .register_fn("push", |list: &mut Array, item: MyEnum| list.push(Dynamic::from(item))) - .register_fn("+=", |list: &mut Array, item: MyEnum| list.push(Dynamic::from(item))) - .register_fn("insert", |list: &mut Array, position: i64, item: MyEnum| { + pub fn Bar(value: i64) -> MyEnum { + MyEnum::Bar(value) + } + pub fn Baz(val1: String, val2: bool) -> MyEnum { + MyEnum::Baz(val1, val2) + } + // Access to fields + #[rhai_fn(get = "enum_type")] + pub fn get_type(a: &mut MyEnum) -> String { + match a { + MyEnum::Foo => "Foo".to_string(), + MyEnum::Bar(_) => "Bar".to_string(), + MyEnum::Baz(_, _) => "Baz".to_string() + } + } + #[rhai_fn(get = "field_0")] + pub fn get_field_0(a: &mut MyEnum) -> Dynamic { + match a { + MyEnum::Foo => Dynamic::UNIT, + MyEnum::Bar(x) => Dynamic::from(x), + MyEnum::Baz(x, _) => Dynamic::from(x) + } + } + #[rhai_fn(get = "field_1")] + pub fn get_field_1(a: &mut MyEnum) -> Dynamic { + match a { + MyEnum::Foo | MyEnum::Bar(_) => Dynamic::UNIT, + MyEnum::Baz(_, x) => Dynamic::from(x) + } + } + // Printing + #[rhai(name = "to_string", name = "print", name = "debug")] + pub fn to_string(a: &mut MyEnum) -> String { + format!("{:?}", a)) + } + #[rhai_fn(name = "+")] + pub fn add_to_str(s: &str, a: MyEnum) -> String { + format!("{}{:?}", s, a)) + } + #[rhai_fn(name = "+")] + pub fn add_str(a: &mut MyEnum, s: &str) -> String { + format!("{:?}", a).push_str(s)) + } + #[rhai_fn(name = "+=")] + pub fn append_to_str(s: &mut ImmutableString, a: MyEnum) -> String { + s += a.to_string()) + } + // '==' and '!=' operators + #[rhai_fn(name = "==")] + pub fn eq(a: &mut MyEnum, b: MyEnum) -> bool { + a == &b + } + #[rhai_fn(name = "!=")] + pub fn neq(a: &mut MyEnum, b: MyEnum) -> bool { + a != &b + } + // Array functions + #[rhai_fn(name = "push")] + pub fn append_to_array(list: &mut Array, item: MyEnum) { + list.push(Dynamic::from(item))); + } + #[rhai_fn(name = "+=")] + pub fn append_to_array_op(list: &mut Array, item: MyEnum) { + list.push(Dynamic::from(item))); + } + #[rhai_fn(name = "insert")] + pub fn insert_to_array(list: &mut Array, position: i64, item: MyEnum) { if position <= 0 { list.insert(0, Dynamic::from(item)); } else if (position as usize) >= list.len() - 1 { @@ -74,17 +104,22 @@ engine } else { list.insert(position as usize, Dynamic::from(item)); } - }).register_fn("pad", |list: &mut Array, len: i64, item: MyEnum| { + } + #[rhai_fn(name = "pad")] + pub fn pad_array(list: &mut Array, len: i64, item: MyEnum) { if len as usize > list.len() { list.resize(len as usize, item); } - }) - // Load the module as the module namespace "MyEnum" + } +} + +let mut engine = Engine::new(); + +// Load the module as the module namespace "MyEnum" +engine + .register_type_with_name::("MyEnum") .register_module("MyEnum", exported_module!(MyEnumModule)); ``` -Instead of registering all these manually, it is often convenient to wrap them up into -a [custom package] that can be loaded into any [`Engine`]. - -With this API in place, working with enums will be almost the same as in Rust: +With this API in place, working with enums feels almost the same as in Rust: ```rust let x = MyEnum::Foo; @@ -99,11 +134,11 @@ y != MyEnum::Bar(0); // Detect enum types -x.type == "Foo"; +x.enum_type == "Foo"; -y.type == "Bar"; +y.enum_type == "Bar"; -z.type == "Baz"; +z.enum_type == "Baz"; // Extract enum fields @@ -116,24 +151,49 @@ z.field_0 == "hello"; z.field_1 == true; ``` +Since enums are internally treated as [custom types], they are not _literals_ and cannot be +used as a match case in `switch` expressions. This is quite a limitation because the equivalent +`match` statement is commonly used in Rust to work with enums and bind variables to +variant-internal data. + +It is possible, however, to `switch` through enum variants based on their types: + +```c +switch x.enum_type { + "Foo" => ..., + "Bar" => { + let value = foo.field_0; + ... + } + "Baz" => { + let val1 = foo.field_0; + let val2 = foo.field_1; + ... + } +} +``` + Use `switch` Through Arrays --------------------------- -Since enums are internally treated as [custom types], they are not _literals_ and cannot be -used as a match case in `switch` expressions. This is quite a limitation because the equivalent -`match` statement is commonly used in Rust to work with enums. - -One way to work with Rust enums in a `switch` expression is through exposing the internal data -of each enum variant as an [array], usually with the name of the variant as the first item: +Another way to work with Rust enums in a `switch` expression is through exposing the internal data +of each enum variant as a variable-length [array], usually with the name of the variant as +the first item for convenience: ```rust use rhai::Array; -engine.register_get("enum_data", |x: &mut Enum} { +engine.register_get("enum_data", |x: &mut Enum| { match x { - Enum::Foo => vec!["Foo".into()] as Array, - Enum::Bar(value) => vec!["Bar".into(), (*value).into()] as Array, + Enum::Foo => vec![ + "Foo".into() + ] as Array, + + Enum::Bar(value) => vec![ + "Bar".into(), (*value).into() + ] as Array, + Enum::Baz(val1, val2) => vec![ "Baz".into(), val1.clone().into(), (*val2).into() ] as Array diff --git a/doc/src/plugins/module.md b/doc/src/plugins/module.md index 29bac196..1bd676c5 100644 --- a/doc/src/plugins/module.md +++ b/doc/src/plugins/module.md @@ -123,10 +123,10 @@ x == 43; Notice that, when using a [module] as a [package], only functions registered at the _top level_ can be accessed. Variables as well as sub-modules are ignored. -### Use `Engine::load_module` +### Use `Engine::register_module` Another simple way to load this into an [`Engine`] is, again, to use the `exported_module!` macro -to turn it into a normal Rhai [module], then use the `Engine::load_module` method on it: +to turn it into a normal Rhai [module], then use the `Engine::register_module` method on it: ```rust fn main() { @@ -136,7 +136,7 @@ fn main() { let module = exported_module!(my_module); // A module can simply be loaded as a globally-available module. - engine.load_module("service", module); + engine.register_module("service", module); } ``` @@ -159,6 +159,17 @@ service::increment(x); x == 43; ``` +`Engine::register_module` also exposes all _methods_ and _iterators_ from the module to the +_global_ namespace, so [getters/setters] and [indexers] for [custom types] work as expected. + +Therefore, in the example able, `increment` works fine when called in method-call style: + +```rust +let x = 42; +x.increment(); +x == 43; +``` + ### Use as loadable `Module` Using this directly as a dynamically-loadable Rhai [module] is almost the same, except that a diff --git a/doc/src/rust/modules/create.md b/doc/src/rust/modules/create.md index 0a163837..c47fa6bf 100644 --- a/doc/src/rust/modules/create.md +++ b/doc/src/rust/modules/create.md @@ -45,7 +45,7 @@ engine.eval::("inc(41)")? == 42; // no need to import module Make the `Module` a Global Module ------------------------------------ -`Engine::load_module` loads a [module] and makes it available globally under a specific namespace. +`Engine::register_module` loads a [module] and makes it available globally under a specific namespace. ```rust use rhai::{Engine, Module}; @@ -55,11 +55,30 @@ module.set_fn_1("inc", |x: i64| Ok(x+1)); // use the 'set_fn_XXX' API to add f // Load the module into the Engine as a sub-module named 'calc' let mut engine = Engine::new(); -engine.load_module("calc", module); +engine.register_module("calc", module); engine.eval::("calc::inc(41)")? == 42; // refer to the 'Calc' module ``` +`Engine::register_module` also exposes all _methods_ and _iterators_ from the module to the +_global_ namespace, so [getters/setters] and [indexers] for [custom types] work as expected. + +```rust +use rhai::{Engine, Module}; + +let mut module = Module::new(); // new module +module.set_fn_1_mut("inc", // add new method + |x: &mut i64| Ok(x+1) +); + +// Load the module into the Engine as a sub-module named 'calc' +let mut engine = Engine::new(); +engine.register_module("calc", module); + +// The method 'inc' works as expected because it is exposed to the global namespace +engine.eval::("let x = 41; x.inc()")? == 42; +``` + Make the `Module` Dynamically Loadable ------------------------------------- diff --git a/src/engine.rs b/src/engine.rs index af9b5b82..41c560b2 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -3,7 +3,7 @@ use crate::ast::{BinaryExpr, Expr, FnCallExpr, Ident, IdentX, ReturnType, Stmt}; use crate::dynamic::{map_std_type_name, Dynamic, Union, Variant}; use crate::fn_call::run_builtin_op_assignment; -use crate::fn_native::{Callback, FnPtr, OnVarCallback, Shared}; +use crate::fn_native::{CallableFunction, Callback, FnPtr, IteratorFn, OnVarCallback, Shared}; use crate::module::{Module, NamespaceRef}; use crate::optimize::OptimizationLevel; use crate::packages::{Package, PackagesCollection, StandardPackage}; @@ -85,7 +85,7 @@ pub const TYPICAL_MAP_SIZE: usize = 8; // Small maps are typical // We cannot use &str or Cow here because `eval` may load a module and the module name will live beyond // the AST of the eval script text. The best we can do is a shared reference. #[derive(Debug, Clone, Default)] -pub struct Imports(StaticVec<(ImmutableString, Shared)>); +pub struct Imports(StaticVec<(ImmutableString, bool, Shared)>); impl Imports { /// Get the length of this stack of imported modules. @@ -98,7 +98,7 @@ impl Imports { } /// Get the imported module at a particular index. pub fn get(&self, index: usize) -> Option> { - self.0.get(index).map(|(_, m)| m).cloned() + self.0.get(index).map(|(_, _, m)| m).cloned() } /// Get the index of an imported module by name. pub fn find(&self, name: &str) -> Option { @@ -106,12 +106,21 @@ impl Imports { .iter() .enumerate() .rev() - .find(|(_, (key, _))| key.as_str() == name) + .find(|(_, (key, _, _))| key.as_str() == name) .map(|(index, _)| index) } /// Push an imported module onto the stack. pub fn push(&mut self, name: impl Into, module: impl Into>) { - self.0.push((name.into(), module.into())); + self.0.push((name.into(), false, module.into())); + } + /// Push a fixed module onto the stack. + #[cfg(not(feature = "no_module"))] + pub(crate) fn push_fixed( + &mut self, + name: impl Into, + module: impl Into>, + ) { + self.0.push((name.into(), true, module.into())); } /// Truncate the stack of imported modules to a particular length. pub fn truncate(&mut self, size: usize) { @@ -119,26 +128,59 @@ impl Imports { } /// Get an iterator to this stack of imported modules. #[allow(dead_code)] - pub fn iter(&self) -> impl Iterator)> { + pub fn iter(&self) -> impl Iterator)> { self.0 .iter() - .map(|(name, module)| (name.as_str(), module.clone())) + .map(|(name, fixed, module)| (name.as_str(), *fixed, module.clone())) } /// Get an iterator to this stack of imported modules. #[allow(dead_code)] pub(crate) fn iter_raw<'a>( &'a self, - ) -> impl Iterator)> + 'a { + ) -> impl Iterator)> + 'a { self.0.iter().cloned() } /// Get a consuming iterator to this stack of imported modules. - pub fn into_iter(self) -> impl Iterator)> { + pub fn into_iter(self) -> impl Iterator)> { self.0.into_iter() } /// Add a stream of imported modules. - pub fn extend(&mut self, stream: impl Iterator)>) { + pub fn extend( + &mut self, + stream: impl Iterator)>, + ) { self.0.extend(stream) } + /// Does the specified function hash key exist in this stack of imported modules? + #[allow(dead_code)] + pub fn contains_fn(&self, hash: u64) -> bool { + self.0 + .iter() + .any(|(_, fixed, m)| *fixed && m.contains_qualified_fn(hash)) + } + /// Get specified function via its hash key. + pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> { + self.0 + .iter() + .rev() + .filter(|&&(_, fixed, _)| fixed) + .find_map(|(_, _, m)| m.get_qualified_fn(hash)) + } + /// Does the specified TypeId iterator exist in this stack of imported modules? + #[allow(dead_code)] + pub fn contains_iter(&self, id: TypeId) -> bool { + self.0 + .iter() + .any(|(_, fixed, m)| *fixed && m.contains_qualified_iter(id)) + } + /// Get the specified TypeId iterator. + pub fn get_iter(&self, id: TypeId) -> Option { + self.0 + .iter() + .rev() + .filter(|&&(_, fixed, _)| fixed) + .find_map(|(_, _, m)| m.get_qualified_iter(id)) + } } #[cfg(not(feature = "unchecked"))] @@ -1947,7 +1989,8 @@ impl Engine { match self .global_module .get_fn(hash_fn, false) - .or_else(|| self.packages.get_fn(hash_fn, false)) + .or_else(|| self.packages.get_fn(hash_fn)) + .or_else(|| mods.get_fn(hash_fn)) { // op= function registered as method Some(func) if func.is_method() => { @@ -1965,9 +2008,10 @@ impl Engine { // Overriding exact implementation if func.is_plugin_fn() { - func.get_plugin_fn().call((self, mods, lib).into(), args)?; + func.get_plugin_fn() + .call((self, &*mods, lib).into(), args)?; } else { - func.get_native_fn()((self, mods, lib).into(), args)?; + func.get_native_fn()((self, &*mods, lib).into(), args)?; } } // Built-in op-assignment function @@ -2142,7 +2186,8 @@ impl Engine { let func = self .global_module .get_iter(iter_type) - .or_else(|| self.packages.get_iter(iter_type)); + .or_else(|| self.packages.get_iter(iter_type)) + .or_else(|| mods.get_iter(iter_type)); if let Some(func) = func { // Add the loop variable diff --git a/src/engine_api.rs b/src/engine_api.rs index e4ece6f8..60cc8603 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -811,9 +811,9 @@ impl Engine { // Index the module (making a clone copy if necessary) if it is not indexed let mut module = shared_take_or_clone(module); module.build_index(); - self.global_sub_modules.push(name, module); + self.global_sub_modules.push_fixed(name, module); } else { - self.global_sub_modules.push(name, module); + self.global_sub_modules.push_fixed(name, module); } self } diff --git a/src/fn_call.rs b/src/fn_call.rs index 7373c285..6b9f833b 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -175,7 +175,7 @@ impl Engine { /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! pub(crate) fn call_native_fn( &self, - mods: &mut Imports, + mods: &Imports, state: &mut State, lib: &[&Module], fn_name: &str, @@ -192,7 +192,8 @@ impl Engine { // Then search packages let func = //lib.get_fn(hash_fn, pub_only) self.global_module.get_fn(hash_fn, pub_only) - .or_else(|| self.packages.get_fn(hash_fn, pub_only)); + .or_else(|| self.packages.get_fn(hash_fn)) + .or_else(|| mods.get_fn(hash_fn)); if let Some(func) = func { assert!(func.is_native()); @@ -428,6 +429,7 @@ impl Engine { #[inline] pub(crate) fn has_override_by_name_and_arguments( &self, + mods: &Imports, lib: &[&Module], fn_name: &str, arg_types: impl AsRef<[TypeId]>, @@ -437,13 +439,14 @@ impl Engine { let hash_fn = calc_native_fn_hash(empty(), fn_name, arg_types.iter().cloned()); let hash_script = calc_script_fn_hash(empty(), fn_name, arg_types.len()); - self.has_override(lib, hash_fn, hash_script, pub_only) + self.has_override(mods, lib, hash_fn, hash_script, pub_only) } // Has a system function an override? #[inline(always)] pub(crate) fn has_override( &self, + mods: &Imports, lib: &[&Module], hash_fn: u64, hash_script: u64, @@ -456,10 +459,13 @@ impl Engine { //|| lib.iter().any(|&m| m.contains_fn(hash_fn, pub_only)) // Then check registered functions //|| self.global_module.contains_fn(hash_script, pub_only) - || self.global_module.contains_fn(hash_fn, pub_only) + || self.global_module.contains_fn(hash_fn, false) // Then check packages - || self.packages.contains_fn(hash_script, pub_only) - || self.packages.contains_fn(hash_fn, pub_only) + || self.packages.contains_fn(hash_script) + || self.packages.contains_fn(hash_fn) + // Then check imported modules + || mods.contains_fn(hash_script) + || mods.contains_fn(hash_fn) } /// Perform an actual function call, native Rust or scripted, taking care of special functions. @@ -497,7 +503,8 @@ impl Engine { match fn_name { // type_of KEYWORD_TYPE_OF - if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => + if args.len() == 1 + && !self.has_override(mods, lib, hash_fn, hash_script, pub_only) => { Ok(( self.map_type_name(args[0].type_name()).to_string().into(), @@ -508,7 +515,8 @@ impl Engine { // Fn/eval - reaching this point it must be a method-style call, mostly like redirected // by a function pointer so it isn't caught at parse time. KEYWORD_FN_PTR | KEYWORD_EVAL - if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => + if args.len() == 1 + && !self.has_override(mods, lib, hash_fn, hash_script, pub_only) => { EvalAltResult::ErrorRuntime( format!( @@ -523,16 +531,14 @@ impl Engine { // Script-like function found #[cfg(not(feature = "no_function"))] - _ if lib.iter().any(|&m| m.contains_fn(hash_script, pub_only)) - //|| self.global_module.contains_fn(hash_script, pub_only) - || self.packages.contains_fn(hash_script, pub_only) => - { + _ if self.has_override(mods, lib, 0, hash_script, pub_only) => { // Get function let func = lib .iter() .find_map(|&m| m.get_fn(hash_script, pub_only)) //.or_else(|| self.global_module.get_fn(hash_script, pub_only)) - .or_else(|| self.packages.get_fn(hash_script, pub_only)) + .or_else(|| self.packages.get_fn(hash_script)) + //.or_else(|| mods.get_fn(hash_script)) .unwrap(); if func.is_script() { @@ -860,7 +866,7 @@ impl Engine { let hash_fn = calc_native_fn_hash(empty(), fn_name, once(TypeId::of::())); - if !self.has_override(lib, hash_fn, hash_script, pub_only) { + if !self.has_override(mods, lib, hash_fn, hash_script, pub_only) { // Fn - only in function call style return self .eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)? @@ -916,7 +922,7 @@ impl Engine { if name == KEYWORD_FN_PTR_CALL && args_expr.len() >= 1 - && !self.has_override(lib, 0, hash_script, pub_only) + && !self.has_override(mods, lib, 0, hash_script, pub_only) { let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; @@ -946,7 +952,7 @@ impl Engine { if name == KEYWORD_IS_DEF_VAR && args_expr.len() == 1 { let hash_fn = calc_native_fn_hash(empty(), name, once(TypeId::of::())); - if !self.has_override(lib, hash_fn, hash_script, pub_only) { + if !self.has_override(mods, lib, hash_fn, hash_script, pub_only) { let var_name = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; let var_name = var_name.as_str().map_err(|err| { @@ -966,7 +972,7 @@ impl Engine { .cloned(), ); - if !self.has_override(lib, hash_fn, hash_script, pub_only) { + if !self.has_override(mods, lib, hash_fn, hash_script, pub_only) { let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; let num_params = @@ -993,7 +999,7 @@ impl Engine { if name == KEYWORD_EVAL && args_expr.len() == 1 { let hash_fn = calc_native_fn_hash(empty(), name, once(TypeId::of::())); - if !self.has_override(lib, hash_fn, hash_script, pub_only) { + if !self.has_override(mods, lib, hash_fn, hash_script, pub_only) { // eval - only in function call style let prev_len = scope.len(); let script = @@ -1194,7 +1200,7 @@ impl Engine { Some(f) if f.is_plugin_fn() => f .get_plugin_fn() .clone() - .call((self, mods, lib).into(), args.as_mut()), + .call((self, &*mods, lib).into(), args.as_mut()), Some(f) if f.is_native() => { if !f.is_method() { // Clone first argument @@ -1205,7 +1211,7 @@ impl Engine { } } - f.get_native_fn().clone()((self, mods, lib).into(), args.as_mut()) + f.get_native_fn().clone()((self, &*mods, lib).into(), args.as_mut()) } Some(_) => unreachable!(), None if def_val.is_some() => Ok(def_val.unwrap().into()), diff --git a/src/fn_native.rs b/src/fn_native.rs index 4e4fc6a7..83f3a7a7 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -56,10 +56,10 @@ pub struct NativeCallContext<'e, 'a, 'm, 'pm: 'm> { lib: &'m [&'pm Module], } -impl<'e, 'a, 'm, 'pm: 'm, M: AsRef<[&'pm Module]> + ?Sized> - From<(&'e Engine, &'a mut Imports, &'m M)> for NativeCallContext<'e, 'a, 'm, 'pm> +impl<'e, 'a, 'm, 'pm: 'm, M: AsRef<[&'pm Module]> + ?Sized> From<(&'e Engine, &'a Imports, &'m M)> + for NativeCallContext<'e, 'a, 'm, 'pm> { - fn from(value: (&'e Engine, &'a mut Imports, &'m M)) -> Self { + fn from(value: (&'e Engine, &'a Imports, &'m M)) -> Self { Self { engine: value.0, mods: Some(value.1), diff --git a/src/module/mod.rs b/src/module/mod.rs index ebd15a3a..580414f8 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -73,11 +73,13 @@ pub struct Module { all_variables: HashMap, /// External Rust functions. functions: HashMap, - /// Flattened collection of all external Rust functions, native or scripted, + /// Flattened collection of all external Rust functions, native or scripted. /// including those in sub-modules. all_functions: HashMap, /// Iterator functions, keyed by the type producing the iterator. type_iterators: HashMap, + /// Flattened collection of iterator functions, including those in sub-modules. + all_type_iterators: HashMap, /// Is the module indexed? indexed: bool, } @@ -91,6 +93,7 @@ impl Default for Module { functions: HashMap::with_capacity_and_hasher(64, StraightHasherBuilder), all_functions: HashMap::with_capacity_and_hasher(256, StraightHasherBuilder), type_iterators: Default::default(), + all_type_iterators: Default::default(), indexed: false, } } @@ -181,6 +184,7 @@ impl Module { && self.all_variables.is_empty() && self.modules.is_empty() && self.type_iterators.is_empty() + && self.all_type_iterators.is_empty() } /// Is the module indexed? @@ -1123,6 +1127,15 @@ impl Module { } } + /// Does the particular namespace-qualified function exist in the module? + /// + /// The `u64` hash is calculated by the function `crate::calc_native_fn_hash` and must match + /// the hash calculated by `build_index`. + #[inline] + pub fn contains_qualified_fn(&self, hash_fn: u64) -> bool { + self.all_functions.contains_key(&hash_fn) + } + /// Get a namespace-qualified function. /// Name and Position in `EvalAltResult` are None and must be set afterwards. /// @@ -1143,6 +1156,7 @@ impl Module { self.type_iterators.extend(other.type_iterators.into_iter()); self.all_functions.clear(); self.all_variables.clear(); + self.all_type_iterators.clear(); self.indexed = false; self } @@ -1160,6 +1174,7 @@ impl Module { self.type_iterators.extend(other.type_iterators.into_iter()); self.all_functions.clear(); self.all_variables.clear(); + self.all_type_iterators.clear(); self.indexed = false; self } @@ -1186,6 +1201,7 @@ impl Module { }); self.all_functions.clear(); self.all_variables.clear(); + self.all_type_iterators.clear(); self.indexed = false; self } @@ -1231,6 +1247,7 @@ impl Module { self.type_iterators.extend(other.type_iterators.iter()); self.all_functions.clear(); self.all_variables.clear(); + self.all_type_iterators.clear(); self.indexed = false; self } @@ -1250,6 +1267,7 @@ impl Module { self.all_functions.clear(); self.all_variables.clear(); + self.all_type_iterators.clear(); self.indexed = false; self } @@ -1388,10 +1406,13 @@ impl Module { // Extra modules left in the scope become sub-modules let mut func_mods: Imports = Default::default(); - mods.into_iter().skip(orig_mods_len).for_each(|(alias, m)| { - func_mods.push(alias.clone(), m.clone()); - module.set_sub_module(alias, m); - }); + mods.into_iter() + .skip(orig_mods_len) + .filter(|&(_, fixed, _)| !fixed) + .for_each(|(alias, _, m)| { + func_mods.push(alias.clone(), m.clone()); + module.set_sub_module(alias, m); + }); // Non-private functions defined become module functions #[cfg(not(feature = "no_function"))] @@ -1422,13 +1443,14 @@ impl Module { fn index_module<'a>( module: &'a Module, qualifiers: &mut Vec<&'a str>, - variables: &mut Vec<(u64, Dynamic)>, - functions: &mut Vec<(u64, CallableFunction)>, + variables: &mut HashMap, + functions: &mut HashMap, + type_iterators: &mut HashMap, ) { module.modules.iter().for_each(|(name, m)| { // Index all the sub-modules first. qualifiers.push(name); - index_module(m, qualifiers, variables, functions); + index_module(m, qualifiers, variables, functions, type_iterators); qualifiers.pop(); }); @@ -1436,8 +1458,14 @@ impl Module { module.variables.iter().for_each(|(var_name, value)| { // Qualifiers + variable name let hash_var = calc_script_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0); - variables.push((hash_var, value.clone())); + variables.insert(hash_var, value.clone()); }); + + // Index type iterators + module.type_iterators.iter().for_each(|(&type_id, func)| { + type_iterators.insert(type_id, func.clone()); + }); + // Index all Rust functions module .functions @@ -1445,7 +1473,7 @@ impl Module { .filter(|(_, FuncInfo { access, .. })| access.is_public()) .for_each( |( - &_hash, + &hash, FuncInfo { name, params, @@ -1454,6 +1482,12 @@ impl Module { .. }, )| { + // Flatten all methods so they can be available without namespace qualifiers + #[cfg(not(feature = "no_object"))] + if func.is_method() { + functions.insert(hash, func.clone()); + } + if let Some(param_types) = types { assert_eq!(*params, param_types.len()); @@ -1469,15 +1503,17 @@ impl Module { // 3) The final hash is the XOR of the two hashes. let hash_qualified_fn = hash_qualified_script ^ hash_fn_args; - functions.push((hash_qualified_fn, func.clone())); + functions.insert(hash_qualified_fn, func.clone()); } else if cfg!(not(feature = "no_function")) { - let hash_qualified_script = if qualifiers.is_empty() { - _hash + let hash_qualified_script = if cfg!(feature = "no_object") + && qualifiers.is_empty() + { + hash } else { // Qualifiers + function name + number of arguments. calc_script_fn_hash(qualifiers.iter().map(|&v| v), &name, *params) }; - functions.push((hash_qualified_script, func.clone())); + functions.insert(hash_qualified_script, func.clone()); } }, ); @@ -1485,19 +1521,32 @@ impl Module { if !self.indexed { let mut qualifiers = Vec::with_capacity(4); - let mut variables = Vec::with_capacity(16); - let mut functions = Vec::with_capacity(256); + let mut variables = HashMap::with_capacity_and_hasher(16, StraightHasherBuilder); + let mut functions = HashMap::with_capacity_and_hasher(256, StraightHasherBuilder); + let mut type_iterators = HashMap::with_capacity(16); qualifiers.push("root"); - index_module(self, &mut qualifiers, &mut variables, &mut functions); + index_module( + self, + &mut qualifiers, + &mut variables, + &mut functions, + &mut type_iterators, + ); - self.all_variables = variables.into_iter().collect(); - self.all_functions = functions.into_iter().collect(); + self.all_variables = variables; + self.all_functions = functions; + self.all_type_iterators = type_iterators; self.indexed = true; } } + /// Does a type iterator exist in the entire module tree? + pub fn contains_qualified_iter(&self, id: TypeId) -> bool { + self.all_type_iterators.contains_key(&id) + } + /// Does a type iterator exist in the module? pub fn contains_iter(&self, id: TypeId) -> bool { self.type_iterators.contains_key(&id) @@ -1532,6 +1581,11 @@ impl Module { }) } + /// Get the specified type iterator. + pub(crate) fn get_qualified_iter(&self, id: TypeId) -> Option { + self.all_type_iterators.get(&id).cloned() + } + /// Get the specified type iterator. pub(crate) fn get_iter(&self, id: TypeId) -> Option { self.type_iterators.get(&id).cloned() diff --git a/src/optimize.rs b/src/optimize.rs index 071dfe8f..673a9ac5 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -673,7 +673,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(state.lib, x.name.as_ref(), arg_types.as_ref(), false) { + if !state.engine.has_override_by_name_and_arguments(&state.engine.global_sub_modules, 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/packages/mod.rs b/src/packages/mod.rs index 563da34a..df067fe1 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -64,16 +64,12 @@ impl PackagesCollection { } /// Does the specified function hash key exist in the `PackagesCollection`? #[allow(dead_code)] - pub fn contains_fn(&self, hash: u64, public_only: bool) -> bool { - self.0.iter().any(|p| p.contains_fn(hash, public_only)) + pub fn contains_fn(&self, hash: u64) -> bool { + self.0.iter().any(|p| p.contains_fn(hash, false)) } /// Get specified function via its hash key. - pub fn get_fn(&self, hash: u64, public_only: bool) -> Option<&CallableFunction> { - self.0 - .iter() - .map(|p| p.get_fn(hash, public_only)) - .find(|f| f.is_some()) - .flatten() + pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> { + self.0.iter().find_map(|p| p.get_fn(hash, false)) } /// Does the specified TypeId iterator exist in the `PackagesCollection`? #[allow(dead_code)] @@ -82,11 +78,7 @@ impl PackagesCollection { } /// Get the specified TypeId iterator. pub fn get_iter(&self, id: TypeId) -> Option { - self.0 - .iter() - .map(|p| p.get_iter(id)) - .find(|f| f.is_some()) - .flatten() + self.0.iter().find_map(|p| p.get_iter(id)) } } diff --git a/tests/for.rs b/tests/for.rs index 0de226f0..56899fd1 100644 --- a/tests/for.rs +++ b/tests/for.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult, INT}; +use rhai::{Engine, EvalAltResult, Module, INT}; #[cfg(not(feature = "no_index"))] #[test] @@ -75,3 +75,45 @@ fn test_for_object() -> Result<(), Box> { Ok(()) } + +#[derive(Debug, Clone)] +struct MyIterableType(String); + +impl IntoIterator for MyIterableType { + type Item = char; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.chars().collect::>().into_iter() + } +} + +#[cfg(not(feature = "no_module"))] +#[test] +fn test_for_module_iterator() -> Result<(), Box> { + let mut engine = Engine::new(); + + // Set a type iterator deep inside a nested module chain + let mut sub_module = Module::new(); + sub_module.set_iterable::(); + sub_module.set_fn_0("new_ts", || Ok(MyIterableType("hello".to_string()))); + + let mut module = Module::new(); + module.set_sub_module("inner", sub_module); + + engine.register_module("testing", module); + + let script = r#" + let item = testing::inner::new_ts(); + let result = ""; + + for x in item { + result += x; + } + result + "#; + + assert_eq!(engine.eval::(script)?, "hello"); + + Ok(()) +} diff --git a/tests/modules.rs b/tests/modules.rs index 5bdbf99c..bd4f7730 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -23,6 +23,7 @@ fn test_module_sub_module() -> Result<(), Box> { sub_module2.set_var("answer", 41 as INT); let hash_inc = sub_module2.set_fn_1("inc", |x: INT| Ok(x + 1)); + sub_module2.set_fn_1_mut("super_inc", |x: &mut INT| Ok(*x + 1)); sub_module.set_sub_module("universe", sub_module2); module.set_sub_module("life", sub_module); @@ -39,26 +40,32 @@ fn test_module_sub_module() -> Result<(), Box> { assert_eq!(m2.get_var_value::("answer").unwrap(), 41); - let mut resolver = StaticModuleResolver::new(); - resolver.insert("question", module); - let mut engine = Engine::new(); - engine.set_module_resolver(Some(resolver)); + engine.register_module("question", module); + assert_eq!(engine.eval::("question::MYSTIC_NUMBER")?, 42); + assert!(engine.eval::("MYSTIC_NUMBER").is_err()); assert_eq!( - engine.eval::(r#"import "question" as q; q::MYSTIC_NUMBER"#)?, + engine.eval::("question::life::universe::answer + 1")?, 42 ); assert_eq!( - engine.eval::(r#"import "question" as q; q::life::universe::answer + 1"#)?, + engine.eval::("question::life::universe::inc(question::life::universe::answer)")?, 42 ); + assert!(engine + .eval::("inc(question::life::universe::answer)") + .is_err()); + + #[cfg(not(feature = "no_object"))] assert_eq!( - engine.eval::( - r#"import "question" as q; q::life::universe::inc(q::life::universe::answer)"# - )?, + engine.eval::("super_inc(question::life::universe::answer)")?, 42 ); + #[cfg(feature = "no_object")] + assert!(engine + .eval::("super_inc(question::life::universe::answer)") + .is_err()); Ok(()) }