New FileModuleResolver.
This commit is contained in:
parent
b8aeaa84de
commit
c4ec93080e
@ -26,7 +26,8 @@ New features
|
|||||||
* Scripted functions are allowed in packages.
|
* Scripted functions are allowed in packages.
|
||||||
* `parse_int` and `parse_float` functions.
|
* `parse_int` and `parse_float` functions.
|
||||||
* `AST::iter_functions` and `Module::iter_script_fn_info` to iterate functions.
|
* `AST::iter_functions` and `Module::iter_script_fn_info` to iterate functions.
|
||||||
|
* Functions iteration functions now take `FnMut` instead of `Fn`.
|
||||||
|
* New `FileModuleResolver` that encapsulates the entire `AST` of the module script, allowing function cross-calling. The old version is renamed `MergingFileModuleResolver`.
|
||||||
|
|
||||||
Version 0.18.3
|
Version 0.18.3
|
||||||
==============
|
==============
|
||||||
|
@ -10,7 +10,7 @@ Each Function is a Separate Compilation Unit
|
|||||||
This means that individual functions can be separated, exported, re-grouped, imported,
|
This means that individual functions can be separated, exported, re-grouped, imported,
|
||||||
and generally mix-'n-match-ed with other completely unrelated scripts.
|
and generally mix-'n-match-ed with other completely unrelated scripts.
|
||||||
|
|
||||||
For example, the `AST::merge` method allows merging all functions in one [`AST`] into another,
|
For example, the `AST::merge` method allows Global all functions in one [`AST`] into another,
|
||||||
forming a new, combined, group of functions.
|
forming a new, combined, group of functions.
|
||||||
|
|
||||||
In general, there are two types of _namespaces_ where functions are looked up:
|
In general, there are two types of _namespaces_ where functions are looked up:
|
||||||
@ -43,10 +43,10 @@ This aspect is very similar to JavaScript before ES6 modules.
|
|||||||
// Compile a script into AST
|
// Compile a script into AST
|
||||||
let ast1 = engine.compile(
|
let ast1 = engine.compile(
|
||||||
r#"
|
r#"
|
||||||
fn message() { "Hello!" } // greeting message
|
fn get_message() { "Hello!" } // greeting message
|
||||||
|
|
||||||
fn say_hello() {
|
fn say_hello() {
|
||||||
print(message()); // prints message
|
print(get_message()); // prints message
|
||||||
}
|
}
|
||||||
|
|
||||||
say_hello();
|
say_hello();
|
||||||
@ -54,7 +54,7 @@ let ast1 = engine.compile(
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Compile another script with an overriding function
|
// Compile another script with an overriding function
|
||||||
let ast2 = engine.compile(r#"fn message() { "Boo!" }"#)?;
|
let ast2 = engine.compile(r#"fn get_message() { "Boo!" }"#)?;
|
||||||
|
|
||||||
// Merge the two AST's
|
// Merge the two AST's
|
||||||
let ast = ast1.merge(ast2); // 'message' will be overwritten
|
let ast = ast1.merge(ast2); // 'message' will be overwritten
|
||||||
@ -73,7 +73,7 @@ i.e. define the function in a separate module and then [`import`] it:
|
|||||||
| message.rhai |
|
| message.rhai |
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
fn message() { "Hello!" }
|
fn get_message() { "Hello!" }
|
||||||
|
|
||||||
|
|
||||||
---------------
|
---------------
|
||||||
@ -82,40 +82,52 @@ fn message() { "Hello!" }
|
|||||||
|
|
||||||
fn say_hello() {
|
fn say_hello() {
|
||||||
import "message" as msg;
|
import "message" as msg;
|
||||||
print(msg::message());
|
print(msg::get_message());
|
||||||
}
|
}
|
||||||
say_hello();
|
say_hello();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Module Namespaces
|
Namespace Consideration When Not Using `FileModuleResolver`
|
||||||
-----------------
|
---------------------------------------------------------
|
||||||
|
|
||||||
[Modules] can be dynamically loaded into a Rhai script using the [`import`] keyword.
|
[Modules] can be dynamically loaded into a Rhai script using the [`import`] keyword.
|
||||||
When that happens, functions defined within the [module] can be called with a _qualified_ name.
|
When that happens, functions defined within the [module] can be called with a _qualified_ name.
|
||||||
|
|
||||||
There is a catch, though, if functions in a module script refer to global functions
|
The [`FileModuleResolver`][module resolver] encapsulates the namespace inside the module itself,
|
||||||
defined _within the script_. When called later, those functions will be searched in the
|
so everything works as expected. A function defined in the module script cannot access functions
|
||||||
current global namespace and may not be found.
|
defined in the calling script, but it can freely call functions defined within the same module script.
|
||||||
|
|
||||||
|
There is a catch, though. When using anything other than the [`FileModuleResolver`][module resolver],
|
||||||
|
functions in a module script refer to functions defined in the _global namespace_.
|
||||||
|
When called later, those functions may not be found.
|
||||||
|
|
||||||
|
When using the [`GlobalFileModuleResolver`][module resolver], for example:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
Using GlobalFileModuleResolver
|
||||||
|
==============================
|
||||||
|
|
||||||
-----------------
|
-----------------
|
||||||
| greeting.rhai |
|
| greeting.rhai |
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
fn message() { "Hello!" };
|
fn get_message() { "Hello!" };
|
||||||
|
|
||||||
fn say_hello() { print(message()); }
|
fn say_hello() {
|
||||||
|
print(get_message()); // 'get_message' is looked up in the global namespace
|
||||||
say_hello(); // 'message' is looked up in the global namespace
|
// when exported
|
||||||
|
}
|
||||||
|
|
||||||
|
say_hello(); // Here, 'get_message' is found in the module namespace
|
||||||
|
|
||||||
---------------
|
---------------
|
||||||
| script.rhai |
|
| script.rhai |
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
import "greeting" as g;
|
import "greeting" as g;
|
||||||
g::say_hello(); // <- error: function not found - 'message'
|
g::say_hello(); // <- error: function not found - 'get_message'
|
||||||
|
// because it does not exist in the global namespace
|
||||||
```
|
```
|
||||||
|
|
||||||
In the example above, although the module `greeting.rhai` loads fine (`"Hello!"` is printed),
|
In the example above, although the module `greeting.rhai` loads fine (`"Hello!"` is printed),
|
||||||
@ -123,22 +135,23 @@ the subsequent call using the _namespace-qualified_ function name fails to find
|
|||||||
'`message`' which now essentially becomes `g::message`. The call fails as there is no more
|
'`message`' which now essentially becomes `g::message`. The call fails as there is no more
|
||||||
function named '`message`' in the global namespace.
|
function named '`message`' in the global namespace.
|
||||||
|
|
||||||
Therefore, when writing functions for a [module], make sure that those functions are as _pure_
|
Therefore, when writing functions for a [module] intended for the [`GlobalFileModuleResolver`][module resolver],
|
||||||
as possible and avoid cross-calling them from each other. A [function pointer] is a valid technique
|
make sure that those functions are as _pure_ as possible and avoid cross-calling them from each other.
|
||||||
to call another function within a module-defined function:
|
|
||||||
|
A [function pointer] is a valid technique to call another function in an environment-independent manner:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
-----------------
|
-----------------
|
||||||
| greeting.rhai |
|
| greeting.rhai |
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
fn message() { "Hello!" };
|
fn get_message() { "Hello!" };
|
||||||
|
|
||||||
fn say_hello(msg_func) { // 'msg_func' is a function pointer
|
fn say_hello(msg_func) { // 'msg_func' is a function pointer
|
||||||
print(msg_func.call()); // call via the function pointer
|
print(msg_func.call()); // call via the function pointer
|
||||||
}
|
}
|
||||||
|
|
||||||
say_hello(); // 'message' is looked up in the global namespace
|
say_hello(Fn("get_message"));
|
||||||
|
|
||||||
|
|
||||||
---------------
|
---------------
|
||||||
@ -149,7 +162,7 @@ import "greeting" as g;
|
|||||||
|
|
||||||
fn my_msg() {
|
fn my_msg() {
|
||||||
import "greeting" as g; // <- must import again here...
|
import "greeting" as g; // <- must import again here...
|
||||||
g::message() // <- ... otherwise will not find module 'g'
|
g::get_message() // <- ... otherwise will not find module 'g'
|
||||||
}
|
}
|
||||||
|
|
||||||
g::say_hello(Fn("my_msg")); // prints 'Hello!'
|
g::say_hello(Fn("my_msg")); // prints 'Hello!'
|
||||||
|
@ -16,11 +16,12 @@ which simply loads a script file based on the path (with `.rhai` extension attac
|
|||||||
|
|
||||||
Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace.
|
Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace.
|
||||||
|
|
||||||
| Module Resolver | Description |
|
| Module Resolver | Description | Namespace |
|
||||||
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------: |
|
||||||
| `FileModuleResolver` | The default module resolution service, not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.<br/>`FileModuleResolver::create_module()` loads a script file and returns a module. |
|
| `FileModuleResolver` | The default module resolution service, not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.<br/>`FileModuleResolver::create_module()` loads a script file and returns a module. | Module (cannot access global namespace) |
|
||||||
| `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. |
|
| `GlobalFileModuleResolver` | A simpler but more efficient version of `FileModuleResolver`, intended for short utility modules. Not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.<br/>**Note:** All functions are assumed absolutely _pure_ and cannot cross-call each other.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.<br/>`FileModuleResolver::create_module()` loads a script file and returns a module. | Global |
|
||||||
| `ModuleResolversCollection` | A collection of module resolvers. Modules will be resolved from each resolver in sequential order.<br/>This is useful when multiple types of modules are needed simultaneously. |
|
| `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. | Global |
|
||||||
|
| `ModuleResolversCollection` | A collection of module resolvers. Modules will be resolved from each resolver in sequential order.<br/>This is useful when multiple types of modules are needed simultaneously. | Global |
|
||||||
|
|
||||||
|
|
||||||
Set into `Engine`
|
Set into `Engine`
|
||||||
|
16
src/api.rs
16
src/api.rs
@ -1502,7 +1502,9 @@ impl Engine {
|
|||||||
args: A,
|
args: A,
|
||||||
) -> Result<T, Box<EvalAltResult>> {
|
) -> Result<T, Box<EvalAltResult>> {
|
||||||
let mut arg_values = args.into_vec();
|
let mut arg_values = args.into_vec();
|
||||||
let result = self.call_fn_dynamic_raw(scope, ast, name, &mut None, arg_values.as_mut())?;
|
let mut args: StaticVec<_> = arg_values.as_mut().iter_mut().collect();
|
||||||
|
|
||||||
|
let result = self.call_fn_dynamic_raw(scope, ast, name, &mut None, args.as_mut())?;
|
||||||
|
|
||||||
let typ = self.map_type_name(result.type_name());
|
let typ = self.map_type_name(result.type_name());
|
||||||
|
|
||||||
@ -1574,7 +1576,9 @@ impl Engine {
|
|||||||
mut this_ptr: Option<&mut Dynamic>,
|
mut this_ptr: Option<&mut Dynamic>,
|
||||||
mut arg_values: impl AsMut<[Dynamic]>,
|
mut arg_values: impl AsMut<[Dynamic]>,
|
||||||
) -> FuncReturn<Dynamic> {
|
) -> FuncReturn<Dynamic> {
|
||||||
self.call_fn_dynamic_raw(scope, lib, name, &mut this_ptr, arg_values.as_mut())
|
let mut args: StaticVec<_> = arg_values.as_mut().iter_mut().collect();
|
||||||
|
|
||||||
|
self.call_fn_dynamic_raw(scope, lib, name, &mut this_ptr, args.as_mut())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call a script function defined in an `AST` with multiple `Dynamic` arguments.
|
/// Call a script function defined in an `AST` with multiple `Dynamic` arguments.
|
||||||
@ -1592,16 +1596,14 @@ impl Engine {
|
|||||||
lib: impl AsRef<Module>,
|
lib: impl AsRef<Module>,
|
||||||
name: &str,
|
name: &str,
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
arg_values: &mut [Dynamic],
|
args: &mut [&mut Dynamic],
|
||||||
) -> FuncReturn<Dynamic> {
|
) -> FuncReturn<Dynamic> {
|
||||||
let lib = lib.as_ref();
|
let lib = lib.as_ref();
|
||||||
let mut args: StaticVec<_> = arg_values.iter_mut().collect();
|
|
||||||
let fn_def = get_script_function_by_signature(lib, name, args.len(), true)
|
let fn_def = get_script_function_by_signature(lib, name, args.len(), true)
|
||||||
.ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::none()))?;
|
.ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::none()))?;
|
||||||
|
|
||||||
let mut state = State::new();
|
let mut state = State::new();
|
||||||
let mut mods = Imports::new();
|
let mut mods = Imports::new();
|
||||||
let args = args.as_mut();
|
|
||||||
|
|
||||||
// Check for data race.
|
// Check for data race.
|
||||||
if cfg!(not(feature = "no_closure")) {
|
if cfg!(not(feature = "no_closure")) {
|
||||||
@ -1634,8 +1636,8 @@ impl Engine {
|
|||||||
let lib = if cfg!(not(feature = "no_function")) {
|
let lib = if cfg!(not(feature = "no_function")) {
|
||||||
ast.lib()
|
ast.lib()
|
||||||
.iter_fn()
|
.iter_fn()
|
||||||
.filter(|(_, _, _, f)| f.is_script())
|
.filter(|(_, _, _, _, f)| f.is_script())
|
||||||
.map(|(_, _, _, f)| f.get_fn_def().clone())
|
.map(|(_, _, _, _, f)| f.get_fn_def().clone())
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
Default::default()
|
Default::default()
|
||||||
|
361
src/module.rs
361
src/module.rs
@ -67,7 +67,11 @@ pub struct Module {
|
|||||||
all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>,
|
all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>,
|
||||||
|
|
||||||
/// External Rust functions.
|
/// External Rust functions.
|
||||||
functions: HashMap<u64, (String, FnAccess, StaticVec<TypeId>, Func), StraightHasherBuilder>,
|
functions: HashMap<
|
||||||
|
u64,
|
||||||
|
(String, FnAccess, usize, Option<StaticVec<TypeId>>, Func),
|
||||||
|
StraightHasherBuilder,
|
||||||
|
>,
|
||||||
|
|
||||||
/// Iterator functions, keyed by the type producing the iterator.
|
/// Iterator functions, keyed by the type producing the iterator.
|
||||||
type_iterators: HashMap<TypeId, IteratorFn>,
|
type_iterators: HashMap<TypeId, IteratorFn>,
|
||||||
@ -97,7 +101,7 @@ impl fmt::Debug for Module {
|
|||||||
.join(", "),
|
.join(", "),
|
||||||
self.functions
|
self.functions
|
||||||
.values()
|
.values()
|
||||||
.map(|(_, _, _, f)| f.to_string())
|
.map(|(_, _, _, _, f)| f.to_string())
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(", "),
|
.join(", "),
|
||||||
)
|
)
|
||||||
@ -258,20 +262,22 @@ impl Module {
|
|||||||
/// Set a script-defined function into the module.
|
/// Set a script-defined function into the module.
|
||||||
///
|
///
|
||||||
/// If there is an existing function of the same name and number of arguments, it is replaced.
|
/// If there is an existing function of the same name and number of arguments, it is replaced.
|
||||||
pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) -> &mut Self {
|
pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) -> u64 {
|
||||||
// None + function name + number of arguments.
|
// None + function name + number of arguments.
|
||||||
let hash_script = calc_fn_hash(empty(), &fn_def.name, fn_def.params.len(), empty());
|
let num_params = fn_def.params.len();
|
||||||
|
let hash_script = calc_fn_hash(empty(), &fn_def.name, num_params, empty());
|
||||||
self.functions.insert(
|
self.functions.insert(
|
||||||
hash_script,
|
hash_script,
|
||||||
(
|
(
|
||||||
fn_def.name.to_string(),
|
fn_def.name.to_string(),
|
||||||
fn_def.access,
|
fn_def.access,
|
||||||
Default::default(),
|
num_params,
|
||||||
|
None,
|
||||||
fn_def.into(),
|
fn_def.into(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
self.indexed = false;
|
self.indexed = false;
|
||||||
self
|
hash_script
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Does a sub-module exist in the module?
|
/// Does a sub-module exist in the module?
|
||||||
@ -362,7 +368,7 @@ impl Module {
|
|||||||
} else if public_only {
|
} else if public_only {
|
||||||
self.functions
|
self.functions
|
||||||
.get(&hash_fn)
|
.get(&hash_fn)
|
||||||
.map(|(_, access, _, _)| match access {
|
.map(|(_, access, _, _, _)| match access {
|
||||||
FnAccess::Public => true,
|
FnAccess::Public => true,
|
||||||
FnAccess::Private => false,
|
FnAccess::Private => false,
|
||||||
})
|
})
|
||||||
@ -412,7 +418,7 @@ impl Module {
|
|||||||
let hash_fn = calc_fn_hash(empty(), &name, args_len, arg_types.iter().cloned());
|
let hash_fn = calc_fn_hash(empty(), &name, args_len, arg_types.iter().cloned());
|
||||||
|
|
||||||
self.functions
|
self.functions
|
||||||
.insert(hash_fn, (name, access, params, func.into()));
|
.insert(hash_fn, (name, access, args_len, Some(params), func.into()));
|
||||||
|
|
||||||
self.indexed = false;
|
self.indexed = false;
|
||||||
|
|
||||||
@ -487,6 +493,31 @@ impl Module {
|
|||||||
self.set_fn(name, Public, arg_types, Func::from_method(Box::new(f)))
|
self.set_fn(name, Public, arg_types, Func::from_method(Box::new(f)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set a raw function but with a signature that is a scripted function, but the implementation is in Rust.
|
||||||
|
pub(crate) fn set_raw_fn_as_scripted(
|
||||||
|
&mut self,
|
||||||
|
name: impl Into<String>,
|
||||||
|
num_args: usize,
|
||||||
|
func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn<Dynamic> + SendSync + 'static,
|
||||||
|
) -> u64 {
|
||||||
|
// None + function name + number of arguments.
|
||||||
|
let name = name.into();
|
||||||
|
let hash_script = calc_fn_hash(empty(), &name, num_args, empty());
|
||||||
|
let f = move |engine: &Engine, lib: &Module, args: &mut FnCallArgs| func(engine, lib, args);
|
||||||
|
self.functions.insert(
|
||||||
|
hash_script,
|
||||||
|
(
|
||||||
|
name,
|
||||||
|
FnAccess::Public,
|
||||||
|
num_args,
|
||||||
|
None,
|
||||||
|
Func::from_pure(Box::new(f)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
self.indexed = false;
|
||||||
|
hash_script
|
||||||
|
}
|
||||||
|
|
||||||
/// Set a Rust function taking no parameters into the module, returning a hash key.
|
/// 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.
|
/// If there is a similar existing Rust function, it is replaced.
|
||||||
@ -979,7 +1010,7 @@ impl Module {
|
|||||||
} else {
|
} else {
|
||||||
self.functions
|
self.functions
|
||||||
.get(&hash_fn)
|
.get(&hash_fn)
|
||||||
.and_then(|(_, access, _, f)| match access {
|
.and_then(|(_, access, _, _, f)| match access {
|
||||||
_ if !public_only => Some(f),
|
_ if !public_only => Some(f),
|
||||||
FnAccess::Public => Some(f),
|
FnAccess::Public => Some(f),
|
||||||
FnAccess::Private => None,
|
FnAccess::Private => None,
|
||||||
@ -1028,14 +1059,14 @@ impl Module {
|
|||||||
|
|
||||||
/// Merge another module into this module.
|
/// Merge another module into this module.
|
||||||
pub fn merge(&mut self, other: &Self) -> &mut Self {
|
pub fn merge(&mut self, other: &Self) -> &mut Self {
|
||||||
self.merge_filtered(other, &|_, _, _| 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, with only selected script-defined functions based on a filter predicate.
|
||||||
pub(crate) fn merge_filtered(
|
pub(crate) fn merge_filtered(
|
||||||
&mut self,
|
&mut self,
|
||||||
other: &Self,
|
other: &Self,
|
||||||
_filter: &impl Fn(FnAccess, &str, usize) -> bool,
|
mut _filter: &mut impl FnMut(FnAccess, &str, usize) -> bool,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
for (k, v) in &other.modules {
|
for (k, v) in &other.modules {
|
||||||
@ -1055,7 +1086,7 @@ impl Module {
|
|||||||
other
|
other
|
||||||
.functions
|
.functions
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, (_, _, _, v))| match v {
|
.filter(|(_, (_, _, _, _, v))| match v {
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
Func::Script(ref f) => _filter(f.access, f.name.as_str(), f.params.len()),
|
Func::Script(ref f) => _filter(f.access, f.name.as_str(), f.params.len()),
|
||||||
_ => true,
|
_ => true,
|
||||||
@ -1076,9 +1107,9 @@ impl Module {
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub(crate) fn retain_functions(
|
pub(crate) fn retain_functions(
|
||||||
&mut self,
|
&mut self,
|
||||||
filter: impl Fn(FnAccess, &str, usize) -> bool,
|
mut filter: impl FnMut(FnAccess, &str, usize) -> bool,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.functions.retain(|_, (_, _, _, v)| match v {
|
self.functions.retain(|_, (_, _, _, _, v)| match v {
|
||||||
Func::Script(ref f) => filter(f.access, f.name.as_str(), f.params.len()),
|
Func::Script(ref f) => filter(f.access, f.name.as_str(), f.params.len()),
|
||||||
_ => true,
|
_ => true,
|
||||||
});
|
});
|
||||||
@ -1110,7 +1141,7 @@ impl Module {
|
|||||||
/// Get an iterator to the functions in the module.
|
/// Get an iterator to the functions in the module.
|
||||||
pub(crate) fn iter_fn(
|
pub(crate) fn iter_fn(
|
||||||
&self,
|
&self,
|
||||||
) -> impl Iterator<Item = &(String, FnAccess, StaticVec<TypeId>, Func)> {
|
) -> impl Iterator<Item = &(String, FnAccess, usize, Option<StaticVec<TypeId>>, Func)> {
|
||||||
self.functions.values()
|
self.functions.values()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1119,17 +1150,19 @@ impl Module {
|
|||||||
pub fn iter_script_fn<'a>(&'a self) -> impl Iterator<Item = Shared<ScriptFnDef>> + 'a {
|
pub fn iter_script_fn<'a>(&'a self) -> impl Iterator<Item = Shared<ScriptFnDef>> + 'a {
|
||||||
self.functions
|
self.functions
|
||||||
.values()
|
.values()
|
||||||
.map(|(_, _, _, f)| f)
|
.map(|(_, _, _, _, f)| f)
|
||||||
.filter(|f| f.is_script())
|
.filter(|f| f.is_script())
|
||||||
.map(|f| f.get_shared_fn_def())
|
.map(|f| f.get_shared_fn_def())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub fn iter_script_fn_info(&self, action: impl Fn(FnAccess, &str, usize)) {
|
pub fn iter_script_fn_info(&self, mut action: impl FnMut(FnAccess, &str, usize)) {
|
||||||
self.functions.iter().for_each(|(_, (_, _, _, v))| match v {
|
self.functions
|
||||||
Func::Script(ref f) => action(f.access, f.name.as_str(), f.params.len()),
|
.iter()
|
||||||
_ => (),
|
.for_each(|(_, (_, _, _, _, v))| match v {
|
||||||
});
|
Func::Script(ref f) => action(f.access, f.name.as_str(), f.params.len()),
|
||||||
|
_ => (),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new `Module` by evaluating an `AST`.
|
/// Create a new `Module` by evaluating an `AST`.
|
||||||
@ -1202,7 +1235,7 @@ impl Module {
|
|||||||
variables.push((hash_var, value.clone()));
|
variables.push((hash_var, value.clone()));
|
||||||
}
|
}
|
||||||
// Index all Rust functions
|
// Index all Rust functions
|
||||||
for (name, access, params, func) in module.functions.values() {
|
for (&hash, (name, access, num_args, params, func)) in module.functions.iter() {
|
||||||
match access {
|
match access {
|
||||||
// Private functions are not exported
|
// Private functions are not exported
|
||||||
FnAccess::Private => continue,
|
FnAccess::Private => continue,
|
||||||
@ -1210,31 +1243,31 @@ impl Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
if func.is_script() {
|
if params.is_none() {
|
||||||
let fn_def = func.get_shared_fn_def();
|
let hash_qualified_script = if qualifiers.is_empty() {
|
||||||
// Qualifiers + function name + number of arguments.
|
hash
|
||||||
let hash_qualified_script = calc_fn_hash(
|
} else {
|
||||||
qualifiers.iter().map(|&v| v),
|
// Qualifiers + function name + number of arguments.
|
||||||
&fn_def.name,
|
calc_fn_hash(qualifiers.iter().map(|&v| v), &name, *num_args, empty())
|
||||||
fn_def.params.len(),
|
};
|
||||||
empty(),
|
functions.push((hash_qualified_script, func.clone()));
|
||||||
);
|
|
||||||
functions.push((hash_qualified_script, fn_def.into()));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Qualified Rust functions are indexed in two steps:
|
if let Some(params) = params {
|
||||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
// Qualified Rust functions are indexed in two steps:
|
||||||
// i.e. qualifiers + function name + number of arguments.
|
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||||
let hash_qualified_script =
|
// i.e. qualifiers + function name + number of arguments.
|
||||||
calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty());
|
let hash_qualified_script =
|
||||||
// 2) Calculate a second hash with no qualifiers, empty function name,
|
calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty());
|
||||||
// zero number of arguments, and the actual list of argument `TypeId`'.s
|
// 2) Calculate a second hash with no qualifiers, empty function name,
|
||||||
let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned());
|
// zero number of arguments, and the actual list of argument `TypeId`'.s
|
||||||
// 3) The final hash is the XOR of the two hashes.
|
let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned());
|
||||||
let hash_qualified_fn = hash_qualified_script ^ hash_fn_args;
|
// 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.push((hash_qualified_fn, func.clone()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1349,7 +1382,7 @@ pub mod resolvers {
|
|||||||
pub use super::collection::ModuleResolversCollection;
|
pub use super::collection::ModuleResolversCollection;
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub use super::file::FileModuleResolver;
|
pub use super::file::{FileModuleResolver, GlobalFileModuleResolver};
|
||||||
pub use super::stat::StaticModuleResolver;
|
pub use super::stat::StaticModuleResolver;
|
||||||
}
|
}
|
||||||
#[cfg(feature = "no_module")]
|
#[cfg(feature = "no_module")]
|
||||||
@ -1363,6 +1396,181 @@ mod file {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::stdlib::path::PathBuf;
|
use crate::stdlib::path::PathBuf;
|
||||||
|
|
||||||
|
/// Module resolution service that loads module script files from the file system.
|
||||||
|
///
|
||||||
|
/// All functions in each module are treated as strictly _pure_ and cannot refer to
|
||||||
|
/// other functions within the same module. Functions are searched in the _global_ namespace.
|
||||||
|
///
|
||||||
|
/// For simple utility libraries, this usually performs better than the full `FileModuleResolver`.
|
||||||
|
///
|
||||||
|
/// Script files are cached so they are are not reloaded and recompiled in subsequent requests.
|
||||||
|
///
|
||||||
|
/// The `new_with_path` and `new_with_path_and_extension` constructor functions
|
||||||
|
/// allow specification of a base directory with module path used as a relative path offset
|
||||||
|
/// to the base directory. The script file is then forced to be in a specified extension
|
||||||
|
/// (default `.rhai`).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::Engine;
|
||||||
|
/// use rhai::module_resolvers::GlobalFileModuleResolver;
|
||||||
|
///
|
||||||
|
/// // Create a new 'GlobalFileModuleResolver' loading scripts from the 'scripts' subdirectory
|
||||||
|
/// // with file extension '.x'.
|
||||||
|
/// let resolver = GlobalFileModuleResolver::new_with_path_and_extension("./scripts", "x");
|
||||||
|
///
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
/// engine.set_module_resolver(Some(resolver));
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GlobalFileModuleResolver {
|
||||||
|
path: PathBuf,
|
||||||
|
extension: String,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
cache: RefCell<HashMap<PathBuf, AST>>,
|
||||||
|
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
cache: RwLock<HashMap<PathBuf, AST>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for GlobalFileModuleResolver {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new_with_path(PathBuf::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlobalFileModuleResolver {
|
||||||
|
/// Create a new `GlobalFileModuleResolver` with a specific base path.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::Engine;
|
||||||
|
/// use rhai::module_resolvers::GlobalFileModuleResolver;
|
||||||
|
///
|
||||||
|
/// // Create a new 'GlobalFileModuleResolver' loading scripts from the 'scripts' subdirectory
|
||||||
|
/// // with file extension '.rhai' (the default).
|
||||||
|
/// let resolver = GlobalFileModuleResolver::new_with_path("./scripts");
|
||||||
|
///
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
/// engine.set_module_resolver(Some(resolver));
|
||||||
|
/// ```
|
||||||
|
pub fn new_with_path<P: Into<PathBuf>>(path: P) -> Self {
|
||||||
|
Self::new_with_path_and_extension(path, "rhai")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `GlobalFileModuleResolver` with a specific base path and file extension.
|
||||||
|
///
|
||||||
|
/// The default extension is `.rhai`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::Engine;
|
||||||
|
/// use rhai::module_resolvers::GlobalFileModuleResolver;
|
||||||
|
///
|
||||||
|
/// // Create a new 'GlobalFileModuleResolver' loading scripts from the 'scripts' subdirectory
|
||||||
|
/// // with file extension '.x'.
|
||||||
|
/// let resolver = GlobalFileModuleResolver::new_with_path_and_extension("./scripts", "x");
|
||||||
|
///
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
/// engine.set_module_resolver(Some(resolver));
|
||||||
|
/// ```
|
||||||
|
pub fn new_with_path_and_extension<P: Into<PathBuf>, E: Into<String>>(
|
||||||
|
path: P,
|
||||||
|
extension: E,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
path: path.into(),
|
||||||
|
extension: extension.into(),
|
||||||
|
cache: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `GlobalFileModuleResolver` with the current directory as base path.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::Engine;
|
||||||
|
/// use rhai::module_resolvers::GlobalFileModuleResolver;
|
||||||
|
///
|
||||||
|
/// // Create a new 'GlobalFileModuleResolver' loading scripts from the current directory
|
||||||
|
/// // with file extension '.rhai' (the default).
|
||||||
|
/// let resolver = GlobalFileModuleResolver::new();
|
||||||
|
///
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
/// engine.set_module_resolver(Some(resolver));
|
||||||
|
/// ```
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a `Module` from a file path.
|
||||||
|
pub fn create_module<P: Into<PathBuf>>(
|
||||||
|
&self,
|
||||||
|
engine: &Engine,
|
||||||
|
path: &str,
|
||||||
|
) -> Result<Module, Box<EvalAltResult>> {
|
||||||
|
self.resolve(engine, path, Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModuleResolver for GlobalFileModuleResolver {
|
||||||
|
fn resolve(
|
||||||
|
&self,
|
||||||
|
engine: &Engine,
|
||||||
|
path: &str,
|
||||||
|
pos: Position,
|
||||||
|
) -> Result<Module, Box<EvalAltResult>> {
|
||||||
|
// Construct the script file path
|
||||||
|
let mut file_path = self.path.clone();
|
||||||
|
file_path.push(path);
|
||||||
|
file_path.set_extension(&self.extension); // Force extension
|
||||||
|
|
||||||
|
let scope = Default::default();
|
||||||
|
|
||||||
|
// See if it is cached
|
||||||
|
let (module, ast) = {
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
let c = self.cache.borrow();
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
let c = self.cache.read().unwrap();
|
||||||
|
|
||||||
|
if let Some(ast) = c.get(&file_path) {
|
||||||
|
(
|
||||||
|
Module::eval_ast_as_new(scope, ast, engine)
|
||||||
|
.map_err(|err| err.new_position(pos))?,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Load the file and compile it if not found
|
||||||
|
let ast = engine
|
||||||
|
.compile_file(file_path.clone())
|
||||||
|
.map_err(|err| err.new_position(pos))?;
|
||||||
|
|
||||||
|
(
|
||||||
|
Module::eval_ast_as_new(scope, &ast, engine)
|
||||||
|
.map_err(|err| err.new_position(pos))?,
|
||||||
|
Some(ast),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(ast) = ast {
|
||||||
|
// Put it into the cache
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
self.cache.borrow_mut().insert(file_path, ast);
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
self.cache.write().unwrap().insert(file_path, ast);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(module)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Module resolution service that loads module script files from the file system.
|
/// Module resolution service that loads module script files from the file system.
|
||||||
///
|
///
|
||||||
/// Script files are cached so they are are not reloaded and recompiled in subsequent requests.
|
/// Script files are cached so they are are not reloaded and recompiled in subsequent requests.
|
||||||
@ -1492,43 +1700,60 @@ mod file {
|
|||||||
file_path.push(path);
|
file_path.push(path);
|
||||||
file_path.set_extension(&self.extension); // Force extension
|
file_path.set_extension(&self.extension); // Force extension
|
||||||
|
|
||||||
let scope = Default::default();
|
|
||||||
|
|
||||||
// See if it is cached
|
// See if it is cached
|
||||||
let (module, ast) = {
|
let exists = {
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
let c = self.cache.borrow();
|
let c = self.cache.borrow();
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
let c = self.cache.read().unwrap();
|
let c = self.cache.read().unwrap();
|
||||||
|
|
||||||
if let Some(ast) = c.get(&file_path) {
|
c.contains_key(&file_path)
|
||||||
(
|
|
||||||
Module::eval_ast_as_new(scope, ast, engine)
|
|
||||||
.map_err(|err| err.new_position(pos))?,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// Load the file and compile it if not found
|
|
||||||
let ast = engine
|
|
||||||
.compile_file(file_path.clone())
|
|
||||||
.map_err(|err| err.new_position(pos))?;
|
|
||||||
|
|
||||||
(
|
|
||||||
Module::eval_ast_as_new(scope, &ast, engine)
|
|
||||||
.map_err(|err| err.new_position(pos))?,
|
|
||||||
Some(ast),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(ast) = ast {
|
if !exists {
|
||||||
|
// Load the file and compile it if not found
|
||||||
|
let ast = engine
|
||||||
|
.compile_file(file_path.clone())
|
||||||
|
.map_err(|err| err.new_position(pos))?;
|
||||||
|
|
||||||
// Put it into the cache
|
// Put it into the cache
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
self.cache.borrow_mut().insert(file_path, ast);
|
self.cache.borrow_mut().insert(file_path.clone(), ast);
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
self.cache.write().unwrap().insert(file_path, ast);
|
self.cache.write().unwrap().insert(file_path.clone(), ast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
let c = self.cache.borrow();
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
let c = self.cache.read().unwrap();
|
||||||
|
|
||||||
|
let ast = c.get(&file_path).unwrap();
|
||||||
|
|
||||||
|
let mut module = Module::eval_ast_as_new(Scope::new(), ast, engine)?;
|
||||||
|
|
||||||
|
ast.iter_functions(|access, name, num_args| match access {
|
||||||
|
FnAccess::Private => (),
|
||||||
|
FnAccess::Public => {
|
||||||
|
let fn_name = name.to_string();
|
||||||
|
let ast_lib = ast.lib().clone();
|
||||||
|
|
||||||
|
module.set_raw_fn_as_scripted(
|
||||||
|
name,
|
||||||
|
num_args,
|
||||||
|
move |engine: &Engine, _, args: &mut [&mut Dynamic]| {
|
||||||
|
engine.call_fn_dynamic_raw(
|
||||||
|
&mut Scope::new(),
|
||||||
|
&ast_lib,
|
||||||
|
&fn_name,
|
||||||
|
&mut None,
|
||||||
|
args,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Ok(module)
|
Ok(module)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -575,7 +575,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
|||||||
|
|
||||||
// First search in functions lib (can override built-in)
|
// First search in functions lib (can override built-in)
|
||||||
// Cater for both normal function call style and method call style (one additional arguments)
|
// Cater for both normal function call style and method call style (one additional arguments)
|
||||||
let has_script_fn = cfg!(not(feature = "no_function")) && state.lib.iter_fn().find(|(_, _, _, f)| {
|
let has_script_fn = cfg!(not(feature = "no_function")) && state.lib.iter_fn().find(|(_, _, _, _,f)| {
|
||||||
if !f.is_script() { return false; }
|
if !f.is_script() { return false; }
|
||||||
let fn_def = f.get_fn_def();
|
let fn_def = f.get_fn_def();
|
||||||
fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len())
|
fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len())
|
||||||
|
@ -130,10 +130,10 @@ impl AST {
|
|||||||
/// This operation is cheap because functions are shared.
|
/// This operation is cheap because functions are shared.
|
||||||
pub fn clone_functions_only_filtered(
|
pub fn clone_functions_only_filtered(
|
||||||
&self,
|
&self,
|
||||||
filter: impl Fn(FnAccess, &str, usize) -> bool,
|
mut filter: impl FnMut(FnAccess, &str, usize) -> bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut functions: Module = Default::default();
|
let mut functions: Module = Default::default();
|
||||||
functions.merge_filtered(&self.1, &filter);
|
functions.merge_filtered(&self.1, &mut filter);
|
||||||
Self(Default::default(), functions)
|
Self(Default::default(), functions)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,7 +250,7 @@ impl AST {
|
|||||||
pub fn merge_filtered(
|
pub fn merge_filtered(
|
||||||
&self,
|
&self,
|
||||||
other: &Self,
|
other: &Self,
|
||||||
filter: impl Fn(FnAccess, &str, usize) -> bool,
|
mut filter: impl FnMut(FnAccess, &str, usize) -> bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let Self(statements, functions) = self;
|
let Self(statements, functions) = self;
|
||||||
|
|
||||||
@ -266,7 +266,7 @@ impl AST {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut functions = functions.clone();
|
let mut functions = functions.clone();
|
||||||
functions.merge_filtered(&other.1, &filter);
|
functions.merge_filtered(&other.1, &mut filter);
|
||||||
|
|
||||||
Self::new(ast, functions)
|
Self::new(ast, functions)
|
||||||
}
|
}
|
||||||
@ -295,13 +295,13 @@ impl AST {
|
|||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub fn retain_functions(&mut self, filter: impl Fn(FnAccess, &str, usize) -> bool) {
|
pub fn retain_functions(&mut self, filter: impl FnMut(FnAccess, &str, usize) -> bool) {
|
||||||
self.1.retain_functions(filter);
|
self.1.retain_functions(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate through all functions
|
/// Iterate through all functions
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub fn iter_functions(&self, action: impl Fn(FnAccess, &str, usize)) {
|
pub fn iter_functions(&self, action: impl FnMut(FnAccess, &str, usize)) {
|
||||||
self.1.iter_script_fn_info(action);
|
self.1.iter_script_fn_info(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user