diff --git a/RELEASES.md b/RELEASES.md
index 56326471..78716df6 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -26,7 +26,8 @@ New features
* Scripted functions are allowed in packages.
* `parse_int` and `parse_float` 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
==============
diff --git a/doc/src/language/fn-namespaces.md b/doc/src/language/fn-namespaces.md
index 15d86ddb..27f005c3 100644
--- a/doc/src/language/fn-namespaces.md
+++ b/doc/src/language/fn-namespaces.md
@@ -10,7 +10,7 @@ Each Function is a Separate Compilation Unit
This means that individual functions can be separated, exported, re-grouped, imported,
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.
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
let ast1 = engine.compile(
r#"
- fn message() { "Hello!" } // greeting message
+ fn get_message() { "Hello!" } // greeting message
fn say_hello() {
- print(message()); // prints message
+ print(get_message()); // prints message
}
say_hello();
@@ -54,7 +54,7 @@ let ast1 = engine.compile(
)?;
// 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
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 |
----------------
-fn message() { "Hello!" }
+fn get_message() { "Hello!" }
---------------
@@ -82,40 +82,52 @@ fn message() { "Hello!" }
fn say_hello() {
import "message" as msg;
- print(msg::message());
+ print(msg::get_message());
}
say_hello();
```
-Module Namespaces
------------------
+Namespace Consideration When Not Using `FileModuleResolver`
+---------------------------------------------------------
[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.
-There is a catch, though, if functions in a module script refer to global functions
-defined _within the script_. When called later, those functions will be searched in the
-current global namespace and may not be found.
+The [`FileModuleResolver`][module resolver] encapsulates the namespace inside the module itself,
+so everything works as expected. A function defined in the module script cannot access functions
+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
+Using GlobalFileModuleResolver
+==============================
+
-----------------
| greeting.rhai |
-----------------
-fn message() { "Hello!" };
+fn get_message() { "Hello!" };
-fn say_hello() { print(message()); }
-
-say_hello(); // 'message' is looked up in the global namespace
+fn say_hello() {
+ print(get_message()); // 'get_message' is looked up in the global namespace
+ // when exported
+}
+say_hello(); // Here, 'get_message' is found in the module namespace
---------------
| script.rhai |
---------------
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),
@@ -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
function named '`message`' in the global namespace.
-Therefore, when writing functions for a [module], make sure that those functions are as _pure_
-as possible and avoid cross-calling them from each other. A [function pointer] is a valid technique
-to call another function within a module-defined function:
+Therefore, when writing functions for a [module] intended for the [`GlobalFileModuleResolver`][module resolver],
+make sure that those functions are as _pure_ as possible and avoid cross-calling them from each other.
+
+A [function pointer] is a valid technique to call another function in an environment-independent manner:
```rust
-----------------
| greeting.rhai |
-----------------
-fn message() { "Hello!" };
+fn get_message() { "Hello!" };
fn say_hello(msg_func) { // 'msg_func' is a 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() {
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!'
diff --git a/doc/src/rust/modules/resolvers.md b/doc/src/rust/modules/resolvers.md
index 4a3b97e7..92fa6074 100644
--- a/doc/src/rust/modules/resolvers.md
+++ b/doc/src/rust/modules/resolvers.md
@@ -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.
-| Module Resolver | Description |
-| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `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.
The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.
`FileModuleResolver::create_module()` loads a script file and returns a module. |
-| `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. |
-| `ModuleResolversCollection` | A collection of module resolvers. Modules will be resolved from each resolver in sequential order.
This is useful when multiple types of modules are needed simultaneously. |
+| 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.
The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.
`FileModuleResolver::create_module()` loads a script file and returns a module. | Module (cannot access global namespace) |
+| `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.
**Note:** All functions are assumed absolutely _pure_ and cannot cross-call each other.
The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.
`FileModuleResolver::create_module()` loads a script file and returns a module. | Global |
+| `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.
This is useful when multiple types of modules are needed simultaneously. | Global |
Set into `Engine`
diff --git a/src/api.rs b/src/api.rs
index 944b0be5..15998614 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -1502,7 +1502,9 @@ impl Engine {
args: A,
) -> Result> {
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());
@@ -1574,7 +1576,9 @@ impl Engine {
mut this_ptr: Option<&mut Dynamic>,
mut arg_values: impl AsMut<[Dynamic]>,
) -> FuncReturn {
- 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.
@@ -1592,16 +1596,14 @@ impl Engine {
lib: impl AsRef,
name: &str,
this_ptr: &mut Option<&mut Dynamic>,
- arg_values: &mut [Dynamic],
+ args: &mut [&mut Dynamic],
) -> FuncReturn {
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)
.ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::none()))?;
let mut state = State::new();
let mut mods = Imports::new();
- let args = args.as_mut();
// Check for data race.
if cfg!(not(feature = "no_closure")) {
@@ -1634,8 +1636,8 @@ impl Engine {
let lib = if cfg!(not(feature = "no_function")) {
ast.lib()
.iter_fn()
- .filter(|(_, _, _, f)| f.is_script())
- .map(|(_, _, _, f)| f.get_fn_def().clone())
+ .filter(|(_, _, _, _, f)| f.is_script())
+ .map(|(_, _, _, _, f)| f.get_fn_def().clone())
.collect()
} else {
Default::default()
diff --git a/src/module.rs b/src/module.rs
index dd089c91..4d6cfa79 100644
--- a/src/module.rs
+++ b/src/module.rs
@@ -67,7 +67,11 @@ pub struct Module {
all_variables: HashMap,
/// External Rust functions.
- functions: HashMap, Func), StraightHasherBuilder>,
+ functions: HashMap<
+ u64,
+ (String, FnAccess, usize, Option>, Func),
+ StraightHasherBuilder,
+ >,
/// Iterator functions, keyed by the type producing the iterator.
type_iterators: HashMap,
@@ -97,7 +101,7 @@ impl fmt::Debug for Module {
.join(", "),
self.functions
.values()
- .map(|(_, _, _, f)| f.to_string())
+ .map(|(_, _, _, _, f)| f.to_string())
.collect::>()
.join(", "),
)
@@ -258,20 +262,22 @@ impl 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.
- 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.
- 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(
hash_script,
(
fn_def.name.to_string(),
fn_def.access,
- Default::default(),
+ num_params,
+ None,
fn_def.into(),
),
);
self.indexed = false;
- self
+ hash_script
}
/// Does a sub-module exist in the module?
@@ -362,7 +368,7 @@ impl Module {
} else if public_only {
self.functions
.get(&hash_fn)
- .map(|(_, access, _, _)| match access {
+ .map(|(_, access, _, _, _)| match access {
FnAccess::Public => true,
FnAccess::Private => false,
})
@@ -412,7 +418,7 @@ impl Module {
let hash_fn = calc_fn_hash(empty(), &name, args_len, arg_types.iter().cloned());
self.functions
- .insert(hash_fn, (name, access, params, func.into()));
+ .insert(hash_fn, (name, access, args_len, Some(params), func.into()));
self.indexed = false;
@@ -487,6 +493,31 @@ impl Module {
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,
+ num_args: usize,
+ func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn + 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.
///
/// If there is a similar existing Rust function, it is replaced.
@@ -979,7 +1010,7 @@ impl Module {
} else {
self.functions
.get(&hash_fn)
- .and_then(|(_, access, _, f)| match access {
+ .and_then(|(_, access, _, _, f)| match access {
_ if !public_only => Some(f),
FnAccess::Public => Some(f),
FnAccess::Private => None,
@@ -1028,14 +1059,14 @@ impl Module {
/// Merge another module into this module.
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.
pub(crate) fn merge_filtered(
&mut self,
other: &Self,
- _filter: &impl Fn(FnAccess, &str, usize) -> bool,
+ mut _filter: &mut impl FnMut(FnAccess, &str, usize) -> bool,
) -> &mut Self {
#[cfg(not(feature = "no_function"))]
for (k, v) in &other.modules {
@@ -1055,7 +1086,7 @@ impl Module {
other
.functions
.iter()
- .filter(|(_, (_, _, _, v))| match v {
+ .filter(|(_, (_, _, _, _, v))| match v {
#[cfg(not(feature = "no_function"))]
Func::Script(ref f) => _filter(f.access, f.name.as_str(), f.params.len()),
_ => true,
@@ -1076,9 +1107,9 @@ impl Module {
#[cfg(not(feature = "no_function"))]
pub(crate) fn retain_functions(
&mut self,
- filter: impl Fn(FnAccess, &str, usize) -> bool,
+ mut filter: impl FnMut(FnAccess, &str, usize) -> bool,
) -> &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()),
_ => true,
});
@@ -1110,7 +1141,7 @@ impl Module {
/// Get an iterator to the functions in the module.
pub(crate) fn iter_fn(
&self,
- ) -> impl Iterator- , Func)> {
+ ) -> impl Iterator
- >, Func)> {
self.functions.values()
}
@@ -1119,17 +1150,19 @@ impl Module {
pub fn iter_script_fn<'a>(&'a self) -> impl Iterator
- > + 'a {
self.functions
.values()
- .map(|(_, _, _, f)| f)
+ .map(|(_, _, _, _, f)| f)
.filter(|f| f.is_script())
.map(|f| f.get_shared_fn_def())
}
#[cfg(not(feature = "no_function"))]
- pub fn iter_script_fn_info(&self, action: impl Fn(FnAccess, &str, usize)) {
- self.functions.iter().for_each(|(_, (_, _, _, v))| match v {
- Func::Script(ref f) => action(f.access, f.name.as_str(), f.params.len()),
- _ => (),
- });
+ pub fn iter_script_fn_info(&self, mut action: impl FnMut(FnAccess, &str, usize)) {
+ self.functions
+ .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`.
@@ -1202,7 +1235,7 @@ impl Module {
variables.push((hash_var, value.clone()));
}
// 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 {
// Private functions are not exported
FnAccess::Private => continue,
@@ -1210,31 +1243,31 @@ impl Module {
}
#[cfg(not(feature = "no_function"))]
- if func.is_script() {
- let fn_def = func.get_shared_fn_def();
- // Qualifiers + function name + number of arguments.
- let hash_qualified_script = calc_fn_hash(
- qualifiers.iter().map(|&v| v),
- &fn_def.name,
- fn_def.params.len(),
- empty(),
- );
- functions.push((hash_qualified_script, fn_def.into()));
+ if params.is_none() {
+ let hash_qualified_script = if qualifiers.is_empty() {
+ hash
+ } else {
+ // Qualifiers + function name + number of arguments.
+ calc_fn_hash(qualifiers.iter().map(|&v| v), &name, *num_args, empty())
+ };
+ functions.push((hash_qualified_script, func.clone()));
continue;
}
- // 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 =
- calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty());
- // 2) Calculate a second hash with no qualifiers, empty function name,
- // zero number of arguments, and the actual list of argument `TypeId`'.s
- let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned());
- // 3) The final hash is the XOR of the two hashes.
- let hash_qualified_fn = hash_qualified_script ^ hash_fn_args;
+ if let Some(params) = params {
+ // 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 =
+ calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty());
+ // 2) Calculate a second hash with no qualifiers, empty function name,
+ // zero number of arguments, and the actual list of argument `TypeId`'.s
+ let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned());
+ // 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;
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
- pub use super::file::FileModuleResolver;
+ pub use super::file::{FileModuleResolver, GlobalFileModuleResolver};
pub use super::stat::StaticModuleResolver;
}
#[cfg(feature = "no_module")]
@@ -1363,6 +1396,181 @@ mod file {
use super::*;
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>,
+
+ #[cfg(feature = "sync")]
+ cache: RwLock>,
+ }
+
+ 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>(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, E: Into>(
+ 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>(
+ &self,
+ engine: &Engine,
+ path: &str,
+ ) -> Result> {
+ self.resolve(engine, path, Default::default())
+ }
+ }
+
+ impl ModuleResolver for GlobalFileModuleResolver {
+ fn resolve(
+ &self,
+ engine: &Engine,
+ path: &str,
+ pos: Position,
+ ) -> Result> {
+ // 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.
///
/// 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.set_extension(&self.extension); // Force extension
- let scope = Default::default();
-
// See if it is cached
- let (module, ast) = {
+ let exists = {
#[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),
- )
- }
+ c.contains_key(&file_path)
};
- 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
#[cfg(not(feature = "sync"))]
- self.cache.borrow_mut().insert(file_path, ast);
+ self.cache.borrow_mut().insert(file_path.clone(), ast);
#[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)
}
}
diff --git a/src/optimize.rs b/src/optimize.rs
index 83f828f4..b45332f7 100644
--- a/src/optimize.rs
+++ b/src/optimize.rs
@@ -575,7 +575,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
// First search in functions lib (can override built-in)
// 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; }
let fn_def = f.get_fn_def();
fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len())
diff --git a/src/parser.rs b/src/parser.rs
index d06daf1b..9366a3d6 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -130,10 +130,10 @@ impl AST {
/// This operation is cheap because functions are shared.
pub fn clone_functions_only_filtered(
&self,
- filter: impl Fn(FnAccess, &str, usize) -> bool,
+ mut filter: impl FnMut(FnAccess, &str, usize) -> bool,
) -> Self {
let mut functions: Module = Default::default();
- functions.merge_filtered(&self.1, &filter);
+ functions.merge_filtered(&self.1, &mut filter);
Self(Default::default(), functions)
}
@@ -250,7 +250,7 @@ impl AST {
pub fn merge_filtered(
&self,
other: &Self,
- filter: impl Fn(FnAccess, &str, usize) -> bool,
+ mut filter: impl FnMut(FnAccess, &str, usize) -> bool,
) -> Self {
let Self(statements, functions) = self;
@@ -266,7 +266,7 @@ impl AST {
};
let mut functions = functions.clone();
- functions.merge_filtered(&other.1, &filter);
+ functions.merge_filtered(&other.1, &mut filter);
Self::new(ast, functions)
}
@@ -295,13 +295,13 @@ impl AST {
/// # }
/// ```
#[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);
}
/// Iterate through all functions
#[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);
}