New FileModuleResolver.

This commit is contained in:
Stephen Chung 2020-09-24 22:50:28 +08:00
parent b8aeaa84de
commit c4ec93080e
7 changed files with 352 additions and 110 deletions

View File

@ -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
==============

View File

@ -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!'

View File

@ -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.<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. |
| `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.<br/>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.<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) |
| `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 |
| `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`

View File

@ -1502,7 +1502,9 @@ impl Engine {
args: A,
) -> Result<T, Box<EvalAltResult>> {
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<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.
@ -1592,16 +1596,14 @@ impl Engine {
lib: impl AsRef<Module>,
name: &str,
this_ptr: &mut Option<&mut Dynamic>,
arg_values: &mut [Dynamic],
args: &mut [&mut Dynamic],
) -> FuncReturn<Dynamic> {
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()

View File

@ -67,7 +67,11 @@ pub struct Module {
all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>,
/// 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.
type_iterators: HashMap<TypeId, IteratorFn>,
@ -97,7 +101,7 @@ impl fmt::Debug for Module {
.join(", "),
self.functions
.values()
.map(|(_, _, _, f)| f.to_string())
.map(|(_, _, _, _, f)| f.to_string())
.collect::<Vec<_>>()
.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<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.
///
/// 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<Item = &(String, FnAccess, StaticVec<TypeId>, Func)> {
) -> impl Iterator<Item = &(String, FnAccess, usize, Option<StaticVec<TypeId>>, Func)> {
self.functions.values()
}
@ -1119,17 +1150,19 @@ impl Module {
pub fn iter_script_fn<'a>(&'a self) -> impl Iterator<Item = Shared<ScriptFnDef>> + '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<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.
///
/// 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)
}
}

View File

@ -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())

View File

@ -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);
}