From c4ec93080e58eb1f48ca4042e35f018ef209e1d3 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 24 Sep 2020 22:50:28 +0800 Subject: [PATCH] New FileModuleResolver. --- RELEASES.md | 3 +- doc/src/language/fn-namespaces.md | 57 +++-- doc/src/rust/modules/resolvers.md | 11 +- src/api.rs | 16 +- src/module.rs | 361 ++++++++++++++++++++++++------ src/optimize.rs | 2 +- src/parser.rs | 12 +- 7 files changed, 352 insertions(+), 110 deletions(-) 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); }