From c5babac3dac26bf2262dbef0e9efcf6768a123b4 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 30 Nov 2020 22:02:32 +0800 Subject: [PATCH 1/2] Quote map property names. --- src/packages/string_basic.rs | 3 +-- tests/print.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 44ab4e7e..69e53bf1 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -172,8 +172,7 @@ mod print_debug_functions { let len = map.len(); map.iter_mut().enumerate().for_each(|(i, (k, v))| { - result.push_str(k); - result.push_str(": "); + result.push_str(&format!("{:?}: ", k)); result.push_str(&print_with_func(FUNC_TO_DEBUG, &ctx, v)); if i < len - 1 { result.push_str(", "); diff --git a/tests/print.rs b/tests/print.rs index 7967670a..3d4c34a7 100644 --- a/tests/print.rs +++ b/tests/print.rs @@ -73,6 +73,6 @@ fn test_print_custom_type() -> Result<(), Box> { x.to_string() "# )? - .contains("e: hello: 42")); + .contains(r#""e": hello: 42"#)); Ok(()) } From bd523338020b8d7feba3d66ceadca91f1b5a171a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 7 Dec 2020 21:54:52 +0800 Subject: [PATCH 2/2] Add namespace test. --- doc/src/language/fn-namespaces.md | 47 +++++++++++++++++++++++-------- src/module/mod.rs | 12 ++++++++ tests/functions.rs | 27 +++++++++++++++++- 3 files changed, 73 insertions(+), 13 deletions(-) diff --git a/doc/src/language/fn-namespaces.md b/doc/src/language/fn-namespaces.md index ba551367..61111f65 100644 --- a/doc/src/language/fn-namespaces.md +++ b/doc/src/language/fn-namespaces.md @@ -15,28 +15,51 @@ allow combining all functions in one [`AST`] into another, forming a new, unifie In general, there are two types of _namespaces_ where functions are looked up: -| Namespace | Source | Lookup method | Sub-modules? | Variables? | -| --------- | ------------------------------------------------------------------------------------- | --------------------------------- | :----------: | :--------: | -| Global | 1) `Engine::register_XXX` API
2) [`AST`] being evaluated
3) [packages] loaded | simple function name | ignored | ignored | -| Module | [`Module`] | namespace-qualified function name | yes | yes | +| Namespace | How Many | Source | Lookup method | Sub-modules? | Variables? | +| --------- | :------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | :----------: | :--------: | +| Global | One | 1) [`AST`] being evaluated
2) `Engine::register_XXX` API
3) [packages] loaded
4) functions in [modules] loaded via `Engine::register_module` and marked _global_ | simple function name | ignored | ignored | +| Module | Many | [`Module`] | namespace-qualified function name | yes | yes | + + +Module Namespace +---------------- + +There can be multiple module namespaces at any time during a script evaluation, loaded via the +[`import`] statement. + +Functions and variables in module namespaces are isolated and encapsulated within their own environments. + +They must be called or accessed in a _namespace-qualified_ manner. + +```rust +import "my_module" as m; // new module namespace 'm' created via 'import' + +let x = m::calc_result(); // namespace-qualified function call + +let y = m::MY_NUMBER; // namespace-qualified variable (constant) access + +let x = calc_result(); // <- error: function 'calc_result' not found + // in global namespace! +``` Global Namespace ---------------- -There is one _global_ namespace for every [`Engine`], which includes: +There is one _global_ namespace for every [`Engine`], which includes (in the following search order): -* All the native Rust functions registered via the `Engine::register_XXX` API. +* All functions defined in the [`AST`] currently being evaluated. -* All the Rust functions defined in [packages] that are loaded into the [`Engine`]. +* All native Rust functions and iterators registered via the `Engine::register_XXX` API. -* All the [modules] imported via [`import`] statements. +* All functions and iterators defined in [packages] that are loaded into the [`Engine`]. -In addition, during evaluation of an [`AST`], all script-defined functions bundled together within -the [`AST`] are added to the global namespace and override any existing registered functions of -the same names and number of parameters. +* Functions defined in [modules] loaded via `Engine::register_module` that are specifically marked + for exposure to the global namespace (e.g. via the `#[rhai(global)]` attribute in a [plugin module]). + +Anywhere in a Rhai script, when a function call is made, the function is searched within the +global namespace, in the above search order. -Anywhere in a Rhai script, when a function call is made, it is searched within the global namespace. Therefore, function calls in Rhai are _late_ bound - meaning that the function called cannot be determined or guaranteed and there is no way to _lock down_ the function being called. This aspect is very similar to JavaScript before ES6 modules. diff --git a/src/module/mod.rs b/src/module/mod.rs index 482dea48..1fde1d37 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -514,6 +514,18 @@ impl Module { self } + /// Update the namespace of a registered function. + /// + /// The [`u64`] hash is calculated either by the function [`crate::calc_native_fn_hash`] or + /// the function [`crate::calc_script_fn_hash`]. + pub fn update_fn_namespace(&mut self, hash_fn: u64, namespace: FnNamespace) -> &mut Self { + if let Some(f) = self.functions.get_mut(&hash_fn) { + f.namespace = namespace; + } + self.indexed = false; + self + } + /// Set a Rust function into the module, returning a hash key. /// /// If there is an existing Rust function of the same hash, it is replaced. diff --git a/tests/functions.rs b/tests/functions.rs index e04f7ae3..2034095f 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -1,5 +1,5 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Engine, EvalAltResult, ParseErrorType, INT}; +use rhai::{Engine, EvalAltResult, FnNamespace, Module, ParseErrorType, RegisterFn, INT}; #[test] fn test_functions() -> Result<(), Box> { @@ -51,6 +51,31 @@ fn test_functions() -> Result<(), Box> { Ok(()) } +#[cfg(not(feature = "no_function"))] +#[test] +fn test_functions_namespaces() -> Result<(), Box> { + let mut engine = Engine::new(); + + #[cfg(not(feature = "no_module"))] + { + let mut m = Module::new(); + let hash = m.set_fn_0("test", || Ok(999 as INT)); + m.update_fn_namespace(hash, FnNamespace::Global); + + engine.register_module("hello", m); + + assert_eq!(engine.eval::("test()")?, 999); + assert_eq!(engine.eval::("fn test() { 123 } test()")?, 123); + } + + engine.register_fn("test", || 42 as INT); + + assert_eq!(engine.eval::("test()")?, 42); + assert_eq!(engine.eval::("fn test() { 123 } test()")?, 123); + + Ok(()) +} + #[test] fn test_function_pointers() -> Result<(), Box> { let engine = Engine::new();