diff --git a/Cargo.toml b/Cargo.toml index db72ea25..0c47eb7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ categories = [ "no-std", "embedded", "wasm", "parser-implementations" ] [dependencies] smallvec = { version = "1.4.2", default-features = false } -rhai_codegen = { version = "0.2", path = "codegen" } +rhai_codegen = { version = "0.3", path = "codegen" } [features] default = [] diff --git a/RELEASES.md b/RELEASES.md index 7d904b64..7f19875a 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -6,11 +6,16 @@ Version 0.19.6 This version adds the `switch` statement. +It also allows exposing selected module functions (usually methods) to the global namespace. + New features ------------ * `switch` statement. -* `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. +* `Engine::register_module` to register a module as a sub-module in the global namespace. +* `Module::get_fn_namespace` and `Module::set_fn_namespace` can expose a module function to the global namespace. This is convenient when registering an API for a custom type. +* `set_exported_global_fn!` macro to register a plugin function and expose it to the global namespace. +* `#[rhai_fn(gobal)]` and `#[rhai_fn(internal)]` attributes to determine whether a function defined in a plugin module should be exposed to the global namespace. This is convenient when defining an API for a custom type. Enhancements ------------ diff --git a/doc/src/language/for.md b/doc/src/language/for.md index 8fa560d2..4985b7aa 100644 --- a/doc/src/language/for.md +++ b/doc/src/language/for.md @@ -3,7 +3,7 @@ {{#include ../links.md}} -Iterating through a range or an [array], or any type with a registered _iterator_, +Iterating through a range or an [array], or any type with a registered _type iterator_, is provided by the `for` ... `in` loop. Like C, `continue` can be used to skip to the next iteration, by-passing all following statements; diff --git a/doc/src/language/modules/index.md b/doc/src/language/modules/index.md index 9df73faf..14931c3a 100644 --- a/doc/src/language/modules/index.md +++ b/doc/src/language/modules/index.md @@ -6,7 +6,7 @@ Modules Rhai allows organizing code (functions, both Rust-based or script-based, and variables) into _modules_. Modules can be disabled via the [`no_module`] feature. -A module is of the type `Module` and holds a collection of functions, variables, iterators and sub-modules. +A module is of the type `Module` and holds a collection of functions, variables, type iterators and sub-modules. It may be created entirely from Rust functions, or it may encapsulate a Rhai script together with the functions and variables defined by that script. diff --git a/doc/src/language/try-catch.md b/doc/src/language/try-catch.md index 5c3b9e1f..c28236c5 100644 --- a/doc/src/language/try-catch.md +++ b/doc/src/language/try-catch.md @@ -91,7 +91,7 @@ Many script-oriented exceptions can be caught via `try` ... `catch`: | [Array]/[string] indexing out-of-bounds | error message [string] | | Indexing with an inappropriate data type | error message [string] | | Error in a dot expression | error message [string] | -| `for` statement without an iterator | error message [string] | +| `for` statement without an type iterator | error message [string] | | Error in an `in` expression | error message [string] | | Data race detected | error message [string] | diff --git a/doc/src/patterns/enums.md b/doc/src/patterns/enums.md index a33c0303..86f3504e 100644 --- a/doc/src/patterns/enums.md +++ b/doc/src/patterns/enums.md @@ -37,7 +37,7 @@ mod MyEnumModule { MyEnum::Baz(val1, val2) } // Access to fields - #[rhai_fn(get = "enum_type")] + #[rhai_fn(global, get = "enum_type")] pub fn get_type(a: &mut MyEnum) -> String { match a { MyEnum::Foo => "Foo".to_string(), @@ -45,7 +45,7 @@ mod MyEnumModule { MyEnum::Baz(_, _) => "Baz".to_string() } } - #[rhai_fn(get = "field_0")] + #[rhai_fn(global, get = "field_0")] pub fn get_field_0(a: &mut MyEnum) -> Dynamic { match a { MyEnum::Foo => Dynamic::UNIT, @@ -53,7 +53,7 @@ mod MyEnumModule { MyEnum::Baz(x, _) => Dynamic::from(x) } } - #[rhai_fn(get = "field_1")] + #[rhai_fn(global, get = "field_1")] pub fn get_field_1(a: &mut MyEnum) -> Dynamic { match a { MyEnum::Foo | MyEnum::Bar(_) => Dynamic::UNIT, @@ -61,41 +61,41 @@ mod MyEnumModule { } } // Printing - #[rhai(name = "to_string", name = "print", name = "debug")] + #[rhai(global, name = "to_string", name = "print", name = "debug")] pub fn to_string(a: &mut MyEnum) -> String { format!("{:?}", a)) } - #[rhai_fn(name = "+")] + #[rhai_fn(global, name = "+")] pub fn add_to_str(s: &str, a: MyEnum) -> String { format!("{}{:?}", s, a)) } - #[rhai_fn(name = "+")] + #[rhai_fn(global, name = "+")] pub fn add_str(a: &mut MyEnum, s: &str) -> String { format!("{:?}", a).push_str(s)) } - #[rhai_fn(name = "+=")] + #[rhai_fn(global, name = "+=")] pub fn append_to_str(s: &mut ImmutableString, a: MyEnum) -> String { s += a.to_string()) } // '==' and '!=' operators - #[rhai_fn(name = "==")] + #[rhai_fn(global, name = "==")] pub fn eq(a: &mut MyEnum, b: MyEnum) -> bool { a == &b } - #[rhai_fn(name = "!=")] + #[rhai_fn(global, name = "!=")] pub fn neq(a: &mut MyEnum, b: MyEnum) -> bool { a != &b } // Array functions - #[rhai_fn(name = "push")] + #[rhai_fn(global, name = "push")] pub fn append_to_array(list: &mut Array, item: MyEnum) { list.push(Dynamic::from(item))); } - #[rhai_fn(name = "+=")] + #[rhai_fn(global, name = "+=")] pub fn append_to_array_op(list: &mut Array, item: MyEnum) { list.push(Dynamic::from(item))); } - #[rhai_fn(name = "insert")] + #[rhai_fn(global, name = "insert")] pub fn insert_to_array(list: &mut Array, position: i64, item: MyEnum) { if position <= 0 { list.insert(0, Dynamic::from(item)); @@ -105,7 +105,7 @@ mod MyEnumModule { list.insert(position as usize, Dynamic::from(item)); } } - #[rhai_fn(name = "pad")] + #[rhai_fn(global, 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); } } diff --git a/doc/src/plugins/module.md b/doc/src/plugins/module.md index 34d198cc..9c4c6ccd 100644 --- a/doc/src/plugins/module.md +++ b/doc/src/plugins/module.md @@ -54,6 +54,8 @@ mod my_module { mystic_number() } // This function will be registered as 'increment'. + // It will also be exposed to the global namespace since 'global' is set. + #[rhai_fn(global)] pub fn increment(num: &mut i64) { *num += 1; } @@ -159,10 +161,12 @@ 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. +Any functions (usually _methods_) defined in the module with `#[rhai_fn(global)]`, as well as +all _type iterators_, are automatically exposed to the _global_ namespace, so iteration, +[getters/setters] and [indexers] for [custom types] can work as expected. -Therefore, in the example able, `increment` works fine when called in method-call style: +Therefore, in the example above, the `increment` method (defined with `#[rhai_fn(global)]`) +works fine when called in method-call style: ```rust let x = 42; diff --git a/doc/src/rust/modules/create.md b/doc/src/rust/modules/create.md index c47fa6bf..7f58b103 100644 --- a/doc/src/rust/modules/create.md +++ b/doc/src/rust/modules/create.md @@ -60,17 +60,22 @@ 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. +`Module::set_fn_namespace` can expose functions (usually _methods_) in the module +to the _global_ namespace, 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. ```rust -use rhai::{Engine, Module}; +use rhai::{Engine, Module, FnNamespace}; let mut module = Module::new(); // new module -module.set_fn_1_mut("inc", // add new method +let hash = module.set_fn_1_mut("inc", // add new method |x: &mut i64| Ok(x+1) ); +// Expose 'inc' to the global namespace (default is 'Internal') +module.set_fn_namespace(hash, FnNamespace::Global); + // Load the module into the Engine as a sub-module named 'calc' let mut engine = Engine::new(); engine.register_module("calc", module); diff --git a/doc/src/rust/modules/index.md b/doc/src/rust/modules/index.md index 9df73faf..14931c3a 100644 --- a/doc/src/rust/modules/index.md +++ b/doc/src/rust/modules/index.md @@ -6,7 +6,7 @@ Modules Rhai allows organizing code (functions, both Rust-based or script-based, and variables) into _modules_. Modules can be disabled via the [`no_module`] feature. -A module is of the type `Module` and holds a collection of functions, variables, iterators and sub-modules. +A module is of the type `Module` and holds a collection of functions, variables, type iterators and sub-modules. It may be created entirely from Rust functions, or it may encapsulate a Rhai script together with the functions and variables defined by that script. diff --git a/src/ast.rs b/src/ast.rs index 180cf638..575ccf53 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -17,7 +17,9 @@ use crate::stdlib::{ use crate::syntax::FnCustomSyntaxEval; use crate::token::Token; use crate::utils::StraightHasherBuilder; -use crate::{Dynamic, FnPtr, ImmutableString, Module, Position, Shared, StaticVec, INT, NO_POS}; +use crate::{ + Dynamic, FnNamespace, FnPtr, ImmutableString, Module, Position, Shared, StaticVec, INT, NO_POS, +}; #[cfg(not(feature = "no_float"))] use crate::FLOAT; @@ -28,7 +30,7 @@ use crate::Array; #[cfg(not(feature = "no_object"))] use crate::Map; -/// A type representing the access mode of a scripted function. +/// A type representing the access mode of a function. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum FnAccess { /// Public function. @@ -37,16 +39,6 @@ pub enum FnAccess { Private, } -impl fmt::Display for FnAccess { - #[inline(always)] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Private => write!(f, "private"), - Self::Public => write!(f, "public"), - } - } -} - impl FnAccess { /// Is this access mode private? #[inline(always)] @@ -178,7 +170,7 @@ impl AST { #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn clone_functions_only(&self) -> Self { - self.clone_functions_only_filtered(|_, _, _| true) + self.clone_functions_only_filtered(|_, _, _, _, _| true) } /// Clone the `AST`'s functions into a new `AST` based on a filter predicate. /// No statements are cloned. @@ -188,7 +180,7 @@ impl AST { #[inline(always)] pub fn clone_functions_only_filtered( &self, - mut filter: impl FnMut(FnAccess, &str, usize) -> bool, + mut filter: impl FnMut(FnNamespace, FnAccess, bool, &str, usize) -> bool, ) -> Self { let mut functions: Module = Default::default(); functions.merge_filtered(&self.1, &mut filter); @@ -251,7 +243,7 @@ impl AST { /// ``` #[inline(always)] pub fn merge(&self, other: &Self) -> Self { - self.merge_filtered(other, |_, _, _| true) + self.merge_filtered(other, |_, _, _, _, _| true) } /// Combine one `AST` with another. The second `AST` is consumed. /// @@ -303,7 +295,7 @@ impl AST { /// ``` #[inline(always)] pub fn combine(&mut self, other: Self) -> &mut Self { - self.combine_filtered(other, |_, _, _| true) + self.combine_filtered(other, |_, _, _, _, _| true) } /// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version /// is returned. @@ -339,7 +331,8 @@ impl AST { /// "#)?; /// /// // Merge 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1' - /// let ast = ast1.merge_filtered(&ast2, |_, name, params| name == "error" && params == 0); + /// let ast = ast1.merge_filtered(&ast2, |_, _, script, name, params| + /// script && name == "error" && params == 0); /// /// // 'ast' is essentially: /// // @@ -360,7 +353,7 @@ impl AST { pub fn merge_filtered( &self, other: &Self, - mut filter: impl FnMut(FnAccess, &str, usize) -> bool, + mut filter: impl FnMut(FnNamespace, FnAccess, bool, &str, usize) -> bool, ) -> Self { let Self(statements, functions) = self; @@ -413,7 +406,8 @@ impl AST { /// "#)?; /// /// // Combine 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1' - /// ast1.combine_filtered(ast2, |_, name, params| name == "error" && params == 0); + /// ast1.combine_filtered(ast2, |_, _, script, name, params| + /// script && name == "error" && params == 0); /// /// // 'ast1' is essentially: /// // @@ -434,7 +428,7 @@ impl AST { pub fn combine_filtered( &mut self, other: Self, - mut filter: impl FnMut(FnAccess, &str, usize) -> bool, + mut filter: impl FnMut(FnNamespace, FnAccess, bool, &str, usize) -> bool, ) -> &mut Self { let Self(ref mut statements, ref mut functions) = self; statements.extend(other.0.into_iter()); @@ -459,22 +453,25 @@ impl AST { /// "#)?; /// /// // Remove all functions except 'foo(_)' - /// ast.retain_functions(|_, name, params| name == "foo" && params == 1); + /// ast.retain_functions(|_, _, name, params| name == "foo" && params == 1); /// # } /// # Ok(()) /// # } /// ``` #[cfg(not(feature = "no_function"))] #[inline(always)] - pub fn retain_functions(&mut self, filter: impl FnMut(FnAccess, &str, usize) -> bool) { - self.1.retain_functions(filter); + pub fn retain_functions( + &mut self, + filter: impl FnMut(FnNamespace, FnAccess, &str, usize) -> bool, + ) { + self.1.retain_script_functions(filter); } /// Iterate through all functions #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn iter_functions<'a>( &'a self, - ) -> impl Iterator)> + 'a { + ) -> impl Iterator)> + 'a { self.1.iter_script_fn() } /// Clear all function definitions in the `AST`. diff --git a/src/engine.rs b/src/engine.rs index 30c7348a..44af641d 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -52,7 +52,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, bool, Shared)>); +pub struct Imports(StaticVec<(ImmutableString, Shared)>); impl Imports { /// Get the length of this stack of imported modules. @@ -65,7 +65,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 { @@ -73,21 +73,12 @@ 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(), 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())); + self.0.push((name.into(), module.into())); } /// Truncate the stack of imported modules to a particular length. pub fn truncate(&mut self, size: usize) { @@ -95,58 +86,49 @@ 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, fixed, module)| (name.as_str(), *fixed, module.clone())) + .map(|(name, module)| (name.as_str(), 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)) + self.0.iter().any(|(_, m)| 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)) + .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)) + self.0.iter().any(|(_, m)| 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)) + .find_map(|(_, m)| m.get_qualified_iter(id)) } } diff --git a/src/engine_api.rs b/src/engine_api.rs index 6856a205..20187b2e 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -12,7 +12,8 @@ use crate::stdlib::{ }; use crate::utils::get_hasher; use crate::{ - scope::Scope, Dynamic, Engine, EvalAltResult, NativeCallContext, ParseError, AST, NO_POS, + scope::Scope, Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, NativeCallContext, + ParseError, AST, NO_POS, }; #[cfg(not(feature = "no_index"))] @@ -55,7 +56,8 @@ impl Engine { + SendSync + 'static, ) -> &mut Self { - self.global_module.set_raw_fn(name, arg_types, func); + self.global_module + .set_raw_fn(name, FnNamespace::Global, FnAccess::Public, arg_types, func); self } /// Register a custom type for use with the `Engine`. @@ -751,9 +753,9 @@ impl Engine { // 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_fixed(name, module); + self.global_sub_modules.push(name, module); } else { - self.global_sub_modules.push_fixed(name, module); + self.global_sub_modules.push(name, module); } self } diff --git a/src/fn_register.rs b/src/fn_register.rs index a045466c..79e68958 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -2,12 +2,13 @@ #![allow(non_snake_case)] -use crate::ast::FnAccess; use crate::dynamic::{DynamicWriteLock, Variant}; use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync}; use crate::r#unsafe::unsafe_cast_box; use crate::stdlib::{any::TypeId, boxed::Box, mem, string::String}; -use crate::{Dynamic, Engine, EvalAltResult, ImmutableString, NativeCallContext}; +use crate::{ + Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, ImmutableString, NativeCallContext, +}; /// Trait to register custom functions with the `Engine`. pub trait RegisterFn { @@ -186,7 +187,7 @@ macro_rules! def_register { { #[inline] fn register_fn(&mut self, name: &str, f: FN) -> &mut Self { - self.global_module.set_fn(name, FnAccess::Public, + self.global_module.set_fn(name, FnNamespace::Global, FnAccess::Public, &[$(map_type_id::<$par>()),*], CallableFunction::$abi(make_func!(f : map_dynamic ; $($par => $let => $clone => $arg),*)) ); @@ -201,7 +202,7 @@ macro_rules! def_register { { #[inline] fn register_result_fn(&mut self, name: &str, f: FN) -> &mut Self { - self.global_module.set_fn(name, FnAccess::Public, + self.global_module.set_fn(name, FnNamespace::Global, FnAccess::Public, &[$(map_type_id::<$par>()),*], CallableFunction::$abi(make_func!(f : map_result ; $($par => $let => $clone => $arg),*)) ); diff --git a/src/lib.rs b/src/lib.rs index 496b60c6..292521f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -111,12 +111,12 @@ pub type FLOAT = f64; #[cfg(feature = "f32_float")] pub type FLOAT = f32; -pub use ast::AST; +pub use ast::{FnAccess, AST}; pub use dynamic::Dynamic; pub use engine::{Engine, EvalContext}; pub use fn_native::{FnPtr, NativeCallContext}; pub use fn_register::{RegisterFn, RegisterResultFn}; -pub use module::Module; +pub use module::{FnNamespace, Module}; pub use parse_error::{LexError, ParseError, ParseErrorType}; pub use result::EvalAltResult; pub use scope::Scope; @@ -135,9 +135,6 @@ pub(crate) use utils::{calc_native_fn_hash, calc_script_fn_hash}; pub use rhai_codegen::*; -#[cfg(not(feature = "no_function"))] -pub use ast::FnAccess; - #[cfg(not(feature = "no_function"))] pub use fn_func::Func; diff --git a/src/module/mod.rs b/src/module/mod.rs index 16f5e98d..f14f191d 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -30,11 +30,44 @@ use crate::Array; #[cfg(not(feature = "no_object"))] use crate::Map; +#[cfg(not(feature = "no_function"))] +pub type SharedScriptFnDef = Shared; + +/// A type representing the namespace of a function. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +pub enum FnNamespace { + /// Global namespace. + Global, + /// Internal only. + Internal, +} + +impl FnNamespace { + /// Is this namespace global? + #[inline(always)] + pub fn is_global(self) -> bool { + match self { + Self::Global => true, + Self::Internal => false, + } + } + /// Is this namespace internal? + #[inline(always)] + pub fn is_internal(self) -> bool { + match self { + Self::Global => false, + Self::Internal => true, + } + } +} + /// Data structure containing a single registered function. #[derive(Debug, Clone)] pub struct FuncInfo { /// Function instance. pub func: CallableFunction, + /// Function namespace. + pub namespace: FnNamespace, /// Function access mode. pub access: FnAccess, /// Function name. @@ -281,7 +314,7 @@ impl Module { /// If there is an existing function of the same name and number of arguments, it is replaced. #[cfg(not(feature = "no_function"))] #[inline] - pub(crate) fn set_script_fn(&mut self, fn_def: Shared) -> u64 { + pub(crate) fn set_script_fn(&mut self, fn_def: SharedScriptFnDef) -> u64 { // None + function name + number of arguments. let num_params = fn_def.params.len(); let hash_script = crate::calc_script_fn_hash(empty(), &fn_def.name, num_params); @@ -289,6 +322,7 @@ impl Module { hash_script, FuncInfo { name: fn_def.name.to_string(), + namespace: FnNamespace::Internal, access: fn_def.access, params: num_params, types: None, @@ -307,7 +341,7 @@ impl Module { name: &str, num_params: usize, public_only: bool, - ) -> Option<&Shared> { + ) -> Option<&SharedScriptFnDef> { self.functions .values() .find( @@ -439,6 +473,7 @@ impl Module { pub fn set_fn( &mut self, name: impl Into, + namespace: FnNamespace, access: FnAccess, arg_types: &[TypeId], func: CallableFunction, @@ -463,6 +498,7 @@ impl Module { hash_fn, FuncInfo { name, + namespace, access, params: params.len(), types: Some(params), @@ -506,10 +542,11 @@ impl Module { /// # Example /// /// ``` - /// use rhai::Module; + /// use rhai::{Module, FnNamespace, FnAccess}; /// /// let mut module = Module::new(); /// let hash = module.set_raw_fn("double_or_not", + /// FnNamespace::Internal, FnAccess::Public, /// // Pass parameter types via a slice with TypeId's /// &[std::any::TypeId::of::(), std::any::TypeId::of::()], /// // Fixed closure signature @@ -538,6 +575,8 @@ impl Module { pub fn set_raw_fn( &mut self, name: impl Into, + namespace: FnNamespace, + access: FnAccess, arg_types: &[TypeId], func: impl Fn(NativeCallContext, &mut FnCallArgs) -> Result> + SendSync @@ -545,14 +584,40 @@ impl Module { ) -> u64 { let f = move |ctx: NativeCallContext, args: &mut FnCallArgs| func(ctx, args).map(Dynamic::from); + self.set_fn( name, - FnAccess::Public, + namespace, + access, arg_types, CallableFunction::from_method(Box::new(f)), ) } + /// Get the namespace of a registered function. + /// Returns `None` if a function with the hash does not exist. + /// + /// The `u64` hash is calculated by the function `crate::calc_native_fn_hash`. + #[inline(always)] + pub fn get_fn_namespace(&self, hash: u64) -> Option { + self.functions.get(&hash).map(|f| f.namespace) + } + + /// Set the namespace of a registered function. + /// Returns the original namespace or `None` if a function with the hash does not exist. + /// + /// The `u64` hash is calculated by the function `crate::calc_native_fn_hash`. + #[inline] + pub fn set_fn_namespace(&mut self, hash: u64, namespace: FnNamespace) -> Option { + if let Some(f) = self.functions.get_mut(&hash) { + let old_ns = f.namespace; + f.namespace = namespace; + Some(old_ns) + } else { + None + } + } + /// Set a Rust function taking no parameters into the module, returning a hash key. /// /// If there is a similar existing Rust function, it is replaced. @@ -576,6 +641,7 @@ impl Module { let arg_types = []; self.set_fn( name, + FnNamespace::Internal, FnAccess::Public, &arg_types, CallableFunction::from_pure(Box::new(f)), @@ -607,6 +673,7 @@ impl Module { let arg_types = [TypeId::of::()]; self.set_fn( name, + FnNamespace::Internal, FnAccess::Public, &arg_types, CallableFunction::from_pure(Box::new(f)), @@ -638,6 +705,7 @@ impl Module { let arg_types = [TypeId::of::()]; self.set_fn( name, + FnNamespace::Internal, FnAccess::Public, &arg_types, CallableFunction::from_method(Box::new(f)), @@ -697,6 +765,7 @@ impl Module { let arg_types = [TypeId::of::(), TypeId::of::()]; self.set_fn( name, + FnNamespace::Internal, FnAccess::Public, &arg_types, CallableFunction::from_pure(Box::new(f)), @@ -734,6 +803,7 @@ impl Module { let arg_types = [TypeId::of::(), TypeId::of::()]; self.set_fn( name, + FnNamespace::Internal, FnAccess::Public, &arg_types, CallableFunction::from_method(Box::new(f)), @@ -847,6 +917,7 @@ impl Module { let arg_types = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; self.set_fn( name, + FnNamespace::Internal, FnAccess::Public, &arg_types, CallableFunction::from_pure(Box::new(f)), @@ -890,6 +961,7 @@ impl Module { let arg_types = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; self.set_fn( name, + FnNamespace::Internal, FnAccess::Public, &arg_types, CallableFunction::from_method(Box::new(f)), @@ -948,6 +1020,7 @@ impl Module { let arg_types = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; self.set_fn( crate::engine::FN_IDX_SET, + FnNamespace::Internal, FnAccess::Public, &arg_types, CallableFunction::from_method(Box::new(f)), @@ -1038,6 +1111,7 @@ impl Module { ]; self.set_fn( name, + FnNamespace::Internal, FnAccess::Public, &arg_types, CallableFunction::from_pure(Box::new(f)), @@ -1088,6 +1162,7 @@ impl Module { ]; self.set_fn( name, + FnNamespace::Internal, FnAccess::Public, &arg_types, CallableFunction::from_method(Box::new(f)), @@ -1195,14 +1270,14 @@ impl Module { /// Merge another module into this module. #[inline(always)] pub fn merge(&mut self, other: &Self) -> &mut Self { - self.merge_filtered(other, &mut |_, _, _| true) + self.merge_filtered(other, &mut |_, _, _, _, _| true) } - /// Merge another module into this module, with only selected script-defined functions based on a filter predicate. + /// Merge another module into this module based on a filter predicate. pub(crate) fn merge_filtered( &mut self, other: &Self, - mut _filter: &mut impl FnMut(FnAccess, &str, usize) -> bool, + mut _filter: &mut impl FnMut(FnNamespace, FnAccess, bool, &str, usize) -> bool, ) -> &mut Self { #[cfg(not(feature = "no_function"))] other.modules.iter().for_each(|(k, v)| { @@ -1220,13 +1295,27 @@ impl Module { other .functions .iter() - .filter(|(_, FuncInfo { func, .. })| match func { - #[cfg(not(feature = "no_function"))] - CallableFunction::Script(f) => { - _filter(f.access, f.name.as_str(), f.params.len()) - } - _ => true, - }) + .filter( + |( + _, + FuncInfo { + namespace, + access, + name, + params, + func, + .. + }, + )| { + _filter( + *namespace, + *access, + func.is_script(), + name.as_str(), + *params, + ) + }, + ) .map(|(&k, v)| (k, v.clone())), ); @@ -1238,18 +1327,30 @@ impl Module { self } - /// Filter out the functions, retaining only some based on a filter predicate. + /// Filter out the functions, retaining only some script-defined functions based on a filter predicate. #[cfg(not(feature = "no_function"))] #[inline] - pub(crate) fn retain_functions( + pub(crate) fn retain_script_functions( &mut self, - mut filter: impl FnMut(FnAccess, &str, usize) -> bool, + mut filter: impl FnMut(FnNamespace, FnAccess, &str, usize) -> bool, ) -> &mut Self { - self.functions - .retain(|_, FuncInfo { func, .. }| match func { - CallableFunction::Script(f) => filter(f.access, f.name.as_str(), f.params.len()), - _ => true, - }); + self.functions.retain( + |_, + FuncInfo { + namespace, + access, + name, + params, + func, + .. + }| { + if func.is_script() { + filter(*namespace, *access, name.as_str(), *params) + } else { + false + } + }, + ); self.all_functions.clear(); self.all_variables.clear(); @@ -1293,16 +1394,25 @@ impl Module { #[inline(always)] pub(crate) fn iter_script_fn<'a>( &'a self, - ) -> impl Iterator)> + 'a { - self.functions - .values() - .map(|f| &f.func) - .filter(|f| f.is_script()) - .map(CallableFunction::get_fn_def) - .map(|f| { - let func = f.clone(); - (f.access, f.name.as_str(), f.params.len(), func) - }) + ) -> impl Iterator + 'a { + self.functions.values().filter(|f| f.func.is_script()).map( + |FuncInfo { + namespace, + access, + name, + params, + func, + .. + }| { + ( + *namespace, + *access, + name.as_str(), + *params, + func.get_fn_def().clone(), + ) + }, + ) } /// Get an iterator over all script-defined functions in the module. @@ -1314,14 +1424,17 @@ impl Module { #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "internals"))] #[inline(always)] - pub fn iter_script_fn_info(&self) -> impl Iterator { + pub fn iter_script_fn_info( + &self, + ) -> impl Iterator { self.functions.values().filter(|f| f.func.is_script()).map( |FuncInfo { name, + namespace, access, params, .. - }| (*access, name.as_str(), *params), + }| (*namespace, *access, name.as_str(), *params), ) } @@ -1338,7 +1451,7 @@ impl Module { #[inline(always)] pub fn iter_script_fn_info( &self, - ) -> impl Iterator)> { + ) -> impl Iterator { self.iter_script_fn() } @@ -1392,13 +1505,10 @@ impl Module { // Extra modules left in the scope become sub-modules let mut func_mods: crate::engine::Imports = Default::default(); - 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); - }); + mods.into_iter().skip(orig_mods_len).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"))] @@ -1406,8 +1516,8 @@ impl Module { let ast_lib: Shared = ast.lib().clone().into(); ast.iter_functions() - .filter(|(access, _, _, _)| !access.is_private()) - .for_each(|(_, _, _, func)| { + .filter(|(_, access, _, _, _)| !access.is_private()) + .for_each(|(_, _, _, _, func)| { // Encapsulate AST environment let mut func = func.as_ref().clone(); func.lib = Some(ast_lib.clone()); @@ -1463,6 +1573,7 @@ impl Module { &hash, FuncInfo { name, + namespace, params, types, func, @@ -1470,22 +1581,20 @@ impl Module { }, )| { // Flatten all methods so they can be available without namespace qualifiers - #[cfg(not(feature = "no_object"))] - if func.is_method() { + if namespace.is_global() { functions.insert(hash, func.clone()); } + // Qualifiers + function name + number of arguments. + let hash_qualified_script = + crate::calc_script_fn_hash(qualifiers.iter().cloned(), name, *params); + if let Some(param_types) = types { assert_eq!(*params, param_types.len()); // Namespace-qualified Rust functions are indexed in two steps: // 1) Calculate a hash in a similar manner to script-defined functions, // i.e. qualifiers + function name + number of arguments. - let hash_qualified_script = crate::calc_script_fn_hash( - qualifiers.iter().cloned(), - name, - *params, - ); // 2) Calculate a second hash with no qualifiers, empty function name, // and the actual list of argument `TypeId`'.s let hash_fn_args = crate::calc_native_fn_hash( @@ -1498,17 +1607,6 @@ impl Module { functions.insert(hash_qualified_fn, func.clone()); } else if cfg!(not(feature = "no_function")) { - let hash_qualified_script = - if cfg!(feature = "no_object") && qualifiers.is_empty() { - hash - } else { - // Qualifiers + function name + number of arguments. - crate::calc_script_fn_hash( - qualifiers.iter().map(|&v| v), - &name, - *params, - ) - }; functions.insert(hash_qualified_script, func.clone()); } }, diff --git a/src/parser.rs b/src/parser.rs index 9a389e5f..795993de 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -25,8 +25,8 @@ use crate::syntax::CustomSyntax; use crate::token::{is_keyword_function, is_valid_identifier, Token, TokenStream}; use crate::utils::{get_hasher, StraightHasherBuilder}; use crate::{ - calc_script_fn_hash, Dynamic, Engine, ImmutableString, LexError, ParseError, ParseErrorType, - Position, Scope, StaticVec, AST, NO_POS, + calc_script_fn_hash, Dynamic, Engine, FnAccess, ImmutableString, LexError, ParseError, + ParseErrorType, Position, Scope, StaticVec, AST, NO_POS, }; #[cfg(not(feature = "no_float"))] @@ -2371,9 +2371,9 @@ fn parse_stmt( Token::Fn | Token::Private => { let access = if matches!(token, Token::Private) { eat_token(input, Token::Private); - crate::ast::FnAccess::Private + FnAccess::Private } else { - crate::ast::FnAccess::Public + FnAccess::Public }; match input.next().unwrap() { @@ -2554,7 +2554,7 @@ fn parse_fn( input: &mut TokenStream, state: &mut ParseState, lib: &mut FunctionsLib, - access: crate::ast::FnAccess, + access: FnAccess, mut settings: ParseSettings, ) -> Result { #[cfg(not(feature = "unchecked"))] @@ -2789,7 +2789,7 @@ fn parse_anon_fn( // Define the function let script = ScriptFnDef { name: fn_name.clone(), - access: crate::ast::FnAccess::Public, + access: FnAccess::Public, params, #[cfg(not(feature = "no_closure"))] externals: Default::default(), diff --git a/src/plugin.rs b/src/plugin.rs index ca10fdae..66783bbd 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -1,11 +1,10 @@ //! Module defining macros for developing _plugins_. -pub use crate::ast::FnAccess; pub use crate::fn_native::{CallableFunction, FnCallArgs}; pub use crate::stdlib::{any::TypeId, boxed::Box, format, mem, string::ToString, vec as new_vec}; pub use crate::{ - Dynamic, Engine, EvalAltResult, ImmutableString, Module, NativeCallContext, RegisterFn, - RegisterResultFn, + Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, ImmutableString, Module, + NativeCallContext, RegisterFn, RegisterResultFn, }; #[cfg(not(features = "no_module"))] diff --git a/src/utils.rs b/src/utils.rs index 173ea9bf..ab9c661d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -105,15 +105,17 @@ pub fn get_hasher() -> impl Hasher { /// /// The first module name is skipped. Hashing starts from the _second_ module in the chain. fn calc_fn_hash<'a>( - modules: impl Iterator, + mut modules: impl Iterator, fn_name: &str, num: Option, params: impl Iterator, ) -> u64 { let s = &mut get_hasher(); + // Hash a boolean indicating whether the hash is namespace-qualified. + modules.next().is_some().hash(s); // We always skip the first module - modules.skip(1).for_each(|m| m.hash(s)); + modules.for_each(|m| m.hash(s)); s.write(fn_name.as_bytes()); if let Some(num) = num { s.write_usize(num); diff --git a/tests/closures.rs b/tests/closures.rs index 1d80f46e..fd5f6c56 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -267,7 +267,7 @@ fn test_closures_external() -> Result<(), Box> { let fn_ptr = engine.eval_ast::(&ast)?; // Get rid of the script, retaining only functions - ast.retain_functions(|_, _, _| true); + ast.retain_functions(|_, _, _, _| true); // Closure 'f' captures: the engine, the AST, and the curried function pointer let f = move |x: INT| fn_ptr.call_dynamic((&engine, &[ast.as_ref()]).into(), None, [x.into()]); diff --git a/tests/modules.rs b/tests/modules.rs index bd4f7730..400e7c54 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -1,7 +1,7 @@ #![cfg(not(feature = "no_module"))] use rhai::{ - module_resolvers::StaticModuleResolver, Dynamic, Engine, EvalAltResult, ImmutableString, - Module, ParseError, ParseErrorType, Scope, INT, + module_resolvers::StaticModuleResolver, Dynamic, Engine, EvalAltResult, FnNamespace, + ImmutableString, Module, ParseError, ParseErrorType, Scope, INT, }; #[test] @@ -23,7 +23,8 @@ 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)); + let hash_super_inc = sub_module2.set_fn_1_mut("super_inc", |x: &mut INT| Ok(*x + 1)); + sub_module2.set_fn_namespace(hash_super_inc, FnNamespace::Global); sub_module.set_sub_module("universe", sub_module2); module.set_sub_module("life", sub_module);