From 93f53fa41766cc3da910ac80e501eee9ef76af67 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 25 Sep 2020 10:59:21 +0800 Subject: [PATCH] Refactor module.rs into files structure. --- src/lib.rs | 7 +- src/{module.rs => module/mod.rs} | 587 +--------------------------- src/module/resolvers/collection.rs | 80 ++++ src/module/resolvers/file.rs | 209 ++++++++++ src/module/resolvers/global_file.rs | 189 +++++++++ src/module/resolvers/mod.rs | 40 ++ src/module/resolvers/stat.rs | 97 +++++ 7 files changed, 629 insertions(+), 580 deletions(-) rename src/{module.rs => module/mod.rs} (70%) create mode 100644 src/module/resolvers/collection.rs create mode 100644 src/module/resolvers/file.rs create mode 100644 src/module/resolvers/global_file.rs create mode 100644 src/module/resolvers/mod.rs create mode 100644 src/module/resolvers/stat.rs diff --git a/src/lib.rs b/src/lib.rs index c0d00fab..e88cf7e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,8 +93,13 @@ pub use result::EvalAltResult; pub use scope::Scope; pub use syntax::{EvalContext, Expression}; pub use token::Position; + +#[cfg(feature = "internals")] pub use utils::calc_fn_hash; +#[cfg(not(feature = "internals"))] +pub(crate) use utils::calc_fn_hash; + pub use rhai_codegen::*; #[cfg(not(feature = "no_function"))] @@ -118,8 +123,6 @@ pub use parser::FLOAT; pub use module::ModuleResolver; /// Module containing all built-in _module resolvers_ available to Rhai. -/// -/// Not available under the `no_module` feature. #[cfg(not(feature = "no_module"))] pub mod module_resolvers { pub use crate::module::resolvers::*; diff --git a/src/module.rs b/src/module/mod.rs similarity index 70% rename from src/module.rs rename to src/module/mod.rs index 532467d9..ea773abc 100644 --- a/src/module.rs +++ b/src/module/mod.rs @@ -38,16 +38,6 @@ use crate::stdlib::{ vec::Vec, }; -#[cfg(not(feature = "no_std"))] -#[cfg(not(feature = "no_module"))] -#[cfg(not(feature = "sync"))] -use crate::stdlib::cell::RefCell; - -#[cfg(not(feature = "no_std"))] -#[cfg(not(feature = "no_module"))] -#[cfg(feature = "sync")] -use crate::stdlib::sync::RwLock; - /// Return type of module-level Rust function. pub type FuncReturn = Result>; @@ -494,8 +484,12 @@ 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. + /// Set a raw function but with a signature that is a scripted function (meaning that the types + /// are not determined), but the implementation is in Rust. #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_module"))] + #[cfg(not(feature = "no_std"))] + #[cfg(not(target_arch = "wasm32"))] pub(crate) fn set_raw_fn_as_scripted( &mut self, name: impl Into, @@ -1372,573 +1366,10 @@ impl ModuleRef { } } -/// Trait that encapsulates a module resolution service. -pub trait ModuleResolver: SendSync { - /// Resolve a module based on a path string. - fn resolve(&self, _: &Engine, path: &str, pos: Position) -> Result>; -} +/// Re-export module resolver trait. +#[cfg(not(feature = "no_module"))] +pub use resolvers::ModuleResolver; /// Re-export module resolvers. #[cfg(not(feature = "no_module"))] -pub mod resolvers { - pub use super::collection::ModuleResolversCollection; - #[cfg(not(feature = "no_std"))] - #[cfg(not(target_arch = "wasm32"))] - pub use super::file::{FileModuleResolver, GlobalFileModuleResolver}; - pub use super::stat::StaticModuleResolver; -} -#[cfg(feature = "no_module")] -pub mod resolvers {} - -/// Script file-based module resolver. -#[cfg(not(feature = "no_module"))] -#[cfg(not(feature = "no_std"))] -#[cfg(not(target_arch = "wasm32"))] -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 independent 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. - /// - /// 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::FileModuleResolver; - /// - /// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory - /// // with file extension '.x'. - /// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x"); - /// - /// let mut engine = Engine::new(); - /// engine.set_module_resolver(Some(resolver)); - /// ``` - #[derive(Debug)] - pub struct FileModuleResolver { - path: PathBuf, - extension: String, - - #[cfg(not(feature = "sync"))] - cache: RefCell>, - - #[cfg(feature = "sync")] - cache: RwLock>, - } - - impl Default for FileModuleResolver { - fn default() -> Self { - Self::new_with_path(PathBuf::default()) - } - } - - impl FileModuleResolver { - /// Create a new `FileModuleResolver` with a specific base path. - /// - /// # Examples - /// - /// ``` - /// use rhai::Engine; - /// use rhai::module_resolvers::FileModuleResolver; - /// - /// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory - /// // with file extension '.rhai' (the default). - /// let resolver = FileModuleResolver::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 `FileModuleResolver` with a specific base path and file extension. - /// - /// The default extension is `.rhai`. - /// - /// # Examples - /// - /// ``` - /// use rhai::Engine; - /// use rhai::module_resolvers::FileModuleResolver; - /// - /// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory - /// // with file extension '.x'. - /// let resolver = FileModuleResolver::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 `FileModuleResolver` with the current directory as base path. - /// - /// # Examples - /// - /// ``` - /// use rhai::Engine; - /// use rhai::module_resolvers::FileModuleResolver; - /// - /// // Create a new 'FileModuleResolver' loading scripts from the current directory - /// // with file extension '.rhai' (the default). - /// let resolver = FileModuleResolver::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 FileModuleResolver { - 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 - - // See if it is cached - let exists = { - #[cfg(not(feature = "sync"))] - let c = self.cache.borrow(); - #[cfg(feature = "sync")] - let c = self.cache.read().unwrap(); - - c.contains_key(&file_path) - }; - - 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.clone(), ast); - #[cfg(feature = "sync")] - 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)?; - - #[cfg(not(feature = "no_function"))] - 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) - } - } -} - -/// Static module resolver. -#[cfg(not(feature = "no_module"))] -mod stat { - use super::*; - - /// Module resolution service that serves modules added into it. - /// - /// # Examples - /// - /// ``` - /// use rhai::{Engine, Module}; - /// use rhai::module_resolvers::StaticModuleResolver; - /// - /// let mut resolver = StaticModuleResolver::new(); - /// - /// let module = Module::new(); - /// resolver.insert("hello".to_string(), module); - /// - /// let mut engine = Engine::new(); - /// engine.set_module_resolver(Some(resolver)); - /// ``` - #[derive(Debug, Clone, Default)] - pub struct StaticModuleResolver(HashMap); - - impl StaticModuleResolver { - /// Create a new `StaticModuleResolver`. - /// - /// # Examples - /// - /// ``` - /// use rhai::{Engine, Module}; - /// use rhai::module_resolvers::StaticModuleResolver; - /// - /// let mut resolver = StaticModuleResolver::new(); - /// - /// let module = Module::new(); - /// resolver.insert("hello", module); - /// - /// let mut engine = Engine::new(); - /// engine.set_module_resolver(Some(resolver)); - /// ``` - pub fn new() -> Self { - Default::default() - } - } - - impl StaticModuleResolver { - /// Add a module keyed by its path. - pub fn insert>(&mut self, path: S, mut module: Module) { - module.index_all_sub_modules(); - self.0.insert(path.into(), module); - } - /// Remove a module given its path. - pub fn remove(&mut self, path: &str) -> Option { - self.0.remove(path) - } - /// Does the path exist? - pub fn contains_path(&self, path: &str) -> bool { - self.0.contains_key(path) - } - /// Get an iterator of all the modules. - pub fn iter(&self) -> impl Iterator { - self.0.iter().map(|(k, v)| (k.as_str(), v)) - } - /// Get a mutable iterator of all the modules. - pub fn iter_mut(&mut self) -> impl Iterator { - self.0.iter_mut().map(|(k, v)| (k.as_str(), v)) - } - /// Get an iterator of all the module paths. - pub fn paths(&self) -> impl Iterator { - self.0.keys().map(String::as_str) - } - /// Get an iterator of all the modules. - pub fn values(&self) -> impl Iterator { - self.0.values() - } - /// Get a mutable iterator of all the modules. - pub fn values_mut(&mut self) -> impl Iterator { - self.0.values_mut() - } - /// Remove all modules. - pub fn clear(&mut self) { - self.0.clear(); - } - } - - impl ModuleResolver for StaticModuleResolver { - fn resolve( - &self, - _: &Engine, - path: &str, - pos: Position, - ) -> Result> { - self.0 - .get(path) - .cloned() - .ok_or_else(|| EvalAltResult::ErrorModuleNotFound(path.into(), pos).into()) - } - } -} - -/// Module resolver collection. -#[cfg(not(feature = "no_module"))] -mod collection { - use super::*; - - /// Module resolution service that holds a collection of module resolves, - /// to be searched in sequential order. - /// - /// # Examples - /// - /// ``` - /// use rhai::{Engine, Module}; - /// use rhai::module_resolvers::{StaticModuleResolver, ModuleResolversCollection}; - /// - /// let mut collection = ModuleResolversCollection::new(); - /// - /// let resolver = StaticModuleResolver::new(); - /// collection.push(resolver); - /// - /// let mut engine = Engine::new(); - /// engine.set_module_resolver(Some(collection)); - /// ``` - #[derive(Default)] - pub struct ModuleResolversCollection(Vec>); - - impl ModuleResolversCollection { - /// Create a new `ModuleResolversCollection`. - /// - /// # Examples - /// - /// ``` - /// use rhai::{Engine, Module}; - /// use rhai::module_resolvers::{StaticModuleResolver, ModuleResolversCollection}; - /// - /// let mut collection = ModuleResolversCollection::new(); - /// - /// let resolver = StaticModuleResolver::new(); - /// collection.push(resolver); - /// - /// let mut engine = Engine::new(); - /// engine.set_module_resolver(Some(collection)); - /// ``` - pub fn new() -> Self { - Default::default() - } - } - - impl ModuleResolversCollection { - /// Add a module keyed by its path. - pub fn push(&mut self, resolver: impl ModuleResolver + 'static) { - self.0.push(Box::new(resolver)); - } - /// Get an iterator of all the module resolvers. - pub fn iter(&self) -> impl Iterator { - self.0.iter().map(|v| v.as_ref()) - } - /// Remove all module resolvers. - pub fn clear(&mut self) { - self.0.clear(); - } - } - - impl ModuleResolver for ModuleResolversCollection { - fn resolve( - &self, - engine: &Engine, - path: &str, - pos: Position, - ) -> Result> { - for resolver in self.0.iter() { - if let Ok(module) = resolver.resolve(engine, path, pos) { - return Ok(module); - } - } - - EvalAltResult::ErrorModuleNotFound(path.into(), pos).into() - } - } -} +pub mod resolvers; diff --git a/src/module/resolvers/collection.rs b/src/module/resolvers/collection.rs new file mode 100644 index 00000000..b8cc2729 --- /dev/null +++ b/src/module/resolvers/collection.rs @@ -0,0 +1,80 @@ +use crate::engine::Engine; +use crate::module::{Module, ModuleResolver}; +use crate::result::EvalAltResult; +use crate::token::Position; + +use crate::stdlib::{boxed::Box, vec::Vec}; + +/// Module resolution service that holds a collection of module resolves, +/// to be searched in sequential order. +/// +/// # Examples +/// +/// ``` +/// use rhai::{Engine, Module}; +/// use rhai::module_resolvers::{StaticModuleResolver, ModuleResolversCollection}; +/// +/// let mut collection = ModuleResolversCollection::new(); +/// +/// let resolver = StaticModuleResolver::new(); +/// collection.push(resolver); +/// +/// let mut engine = Engine::new(); +/// engine.set_module_resolver(Some(collection)); +/// ``` +#[derive(Default)] +pub struct ModuleResolversCollection(Vec>); + +impl ModuleResolversCollection { + /// Create a new `ModuleResolversCollection`. + /// + /// # Examples + /// + /// ``` + /// use rhai::{Engine, Module}; + /// use rhai::module_resolvers::{StaticModuleResolver, ModuleResolversCollection}; + /// + /// let mut collection = ModuleResolversCollection::new(); + /// + /// let resolver = StaticModuleResolver::new(); + /// collection.push(resolver); + /// + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(Some(collection)); + /// ``` + pub fn new() -> Self { + Default::default() + } +} + +impl ModuleResolversCollection { + /// Add a module keyed by its path. + pub fn push(&mut self, resolver: impl ModuleResolver + 'static) { + self.0.push(Box::new(resolver)); + } + /// Get an iterator of all the module resolvers. + pub fn iter(&self) -> impl Iterator { + self.0.iter().map(|v| v.as_ref()) + } + /// Remove all module resolvers. + pub fn clear(&mut self) { + self.0.clear(); + } +} + +impl ModuleResolver for ModuleResolversCollection { + fn resolve( + &self, + engine: &Engine, + path: &str, + pos: Position, + ) -> Result> { + for resolver in self.0.iter() { + if let Ok(module) = resolver.resolve(engine, path, pos) { + return Ok(module); + } + } + + EvalAltResult::ErrorModuleNotFound(path.into(), pos).into() + } +} diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs new file mode 100644 index 00000000..92243cb3 --- /dev/null +++ b/src/module/resolvers/file.rs @@ -0,0 +1,209 @@ +use crate::any::Dynamic; +use crate::engine::Engine; +use crate::module::{Module, ModuleResolver}; +use crate::parser::{FnAccess, AST}; +use crate::result::EvalAltResult; +use crate::scope::Scope; +use crate::token::Position; + +use crate::stdlib::{ + boxed::Box, + collections::HashMap, + path::PathBuf, + string::{String, ToString}, +}; + +#[cfg(not(feature = "sync"))] +use crate::stdlib::cell::RefCell; + +#[cfg(feature = "sync")] +use crate::stdlib::sync::RwLock; + +/// 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. +/// +/// 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::FileModuleResolver; +/// +/// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory +/// // with file extension '.x'. +/// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x"); +/// +/// let mut engine = Engine::new(); +/// +/// engine.set_module_resolver(Some(resolver)); +/// ``` +#[derive(Debug)] +pub struct FileModuleResolver { + path: PathBuf, + extension: String, + + #[cfg(not(feature = "sync"))] + cache: RefCell>, + + #[cfg(feature = "sync")] + cache: RwLock>, +} + +impl Default for FileModuleResolver { + fn default() -> Self { + Self::new_with_path(PathBuf::default()) + } +} + +impl FileModuleResolver { + /// Create a new `FileModuleResolver` with a specific base path. + /// + /// # Examples + /// + /// ``` + /// use rhai::Engine; + /// use rhai::module_resolvers::FileModuleResolver; + /// + /// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory + /// // with file extension '.rhai' (the default). + /// let resolver = FileModuleResolver::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 `FileModuleResolver` with a specific base path and file extension. + /// + /// The default extension is `.rhai`. + /// + /// # Examples + /// + /// ``` + /// use rhai::Engine; + /// use rhai::module_resolvers::FileModuleResolver; + /// + /// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory + /// // with file extension '.x'. + /// let resolver = FileModuleResolver::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 `FileModuleResolver` with the current directory as base path. + /// + /// # Examples + /// + /// ``` + /// use rhai::Engine; + /// use rhai::module_resolvers::FileModuleResolver; + /// + /// // Create a new 'FileModuleResolver' loading scripts from the current directory + /// // with file extension '.rhai' (the default). + /// let resolver = FileModuleResolver::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 FileModuleResolver { + 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 + + // See if it is cached + let exists = { + #[cfg(not(feature = "sync"))] + let c = self.cache.borrow(); + #[cfg(feature = "sync")] + let c = self.cache.read().unwrap(); + + c.contains_key(&file_path) + }; + + 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.clone(), ast); + #[cfg(feature = "sync")] + 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)?; + + #[cfg(not(feature = "no_function"))] + 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/module/resolvers/global_file.rs b/src/module/resolvers/global_file.rs new file mode 100644 index 00000000..f0b0cce8 --- /dev/null +++ b/src/module/resolvers/global_file.rs @@ -0,0 +1,189 @@ +use crate::engine::Engine; +use crate::module::{Module, ModuleResolver}; +use crate::parser::AST; +use crate::result::EvalAltResult; +use crate::token::Position; + +use crate::stdlib::{boxed::Box, collections::HashMap, path::PathBuf, string::String}; + +#[cfg(not(feature = "sync"))] +use crate::stdlib::cell::RefCell; + +#[cfg(feature = "sync")] +use crate::stdlib::sync::RwLock; + +/// Module resolution service that loads module script files from the file system. +/// +/// All functions in each module are treated as strictly independent 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) + } +} diff --git a/src/module/resolvers/mod.rs b/src/module/resolvers/mod.rs new file mode 100644 index 00000000..b9da048d --- /dev/null +++ b/src/module/resolvers/mod.rs @@ -0,0 +1,40 @@ +use crate::engine::Engine; +use crate::fn_native::SendSync; +use crate::module::Module; +use crate::result::EvalAltResult; +use crate::token::Position; + +use crate::stdlib::boxed::Box; + +mod collection; +pub use collection::ModuleResolversCollection; + +#[cfg(not(feature = "no_std"))] +#[cfg(not(target_arch = "wasm32"))] +mod file; + +#[cfg(not(feature = "no_std"))] +#[cfg(not(target_arch = "wasm32"))] +pub use file::FileModuleResolver; + +#[cfg(not(feature = "no_std"))] +#[cfg(not(target_arch = "wasm32"))] +mod global_file; + +#[cfg(not(feature = "no_std"))] +#[cfg(not(target_arch = "wasm32"))] +pub use global_file::GlobalFileModuleResolver; + +mod stat; +pub use stat::StaticModuleResolver; + +/// Trait that encapsulates a module resolution service. +pub trait ModuleResolver: SendSync { + /// Resolve a module based on a path string. + fn resolve( + &self, + engine: &Engine, + path: &str, + pos: Position, + ) -> Result>; +} diff --git a/src/module/resolvers/stat.rs b/src/module/resolvers/stat.rs new file mode 100644 index 00000000..5903ce57 --- /dev/null +++ b/src/module/resolvers/stat.rs @@ -0,0 +1,97 @@ +use crate::engine::Engine; +use crate::module::{Module, ModuleResolver}; +use crate::result::EvalAltResult; +use crate::token::Position; + +use crate::stdlib::{boxed::Box, collections::HashMap, string::String}; + +/// Module resolution service that serves modules added into it. +/// +/// # Examples +/// +/// ``` +/// use rhai::{Engine, Module}; +/// use rhai::module_resolvers::StaticModuleResolver; +/// +/// let mut resolver = StaticModuleResolver::new(); +/// +/// let module = Module::new(); +/// resolver.insert("hello".to_string(), module); +/// +/// let mut engine = Engine::new(); +/// +/// engine.set_module_resolver(Some(resolver)); +/// ``` +#[derive(Debug, Clone, Default)] +pub struct StaticModuleResolver(HashMap); + +impl StaticModuleResolver { + /// Create a new `StaticModuleResolver`. + /// + /// # Examples + /// + /// ``` + /// use rhai::{Engine, Module}; + /// use rhai::module_resolvers::StaticModuleResolver; + /// + /// let mut resolver = StaticModuleResolver::new(); + /// + /// let module = Module::new(); + /// resolver.insert("hello", module); + /// + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(Some(resolver)); + /// ``` + pub fn new() -> Self { + Default::default() + } +} + +impl StaticModuleResolver { + /// Add a module keyed by its path. + pub fn insert>(&mut self, path: S, mut module: Module) { + module.index_all_sub_modules(); + self.0.insert(path.into(), module); + } + /// Remove a module given its path. + pub fn remove(&mut self, path: &str) -> Option { + self.0.remove(path) + } + /// Does the path exist? + pub fn contains_path(&self, path: &str) -> bool { + self.0.contains_key(path) + } + /// Get an iterator of all the modules. + pub fn iter(&self) -> impl Iterator { + self.0.iter().map(|(k, v)| (k.as_str(), v)) + } + /// Get a mutable iterator of all the modules. + pub fn iter_mut(&mut self) -> impl Iterator { + self.0.iter_mut().map(|(k, v)| (k.as_str(), v)) + } + /// Get an iterator of all the module paths. + pub fn paths(&self) -> impl Iterator { + self.0.keys().map(String::as_str) + } + /// Get an iterator of all the modules. + pub fn values(&self) -> impl Iterator { + self.0.values() + } + /// Get a mutable iterator of all the modules. + pub fn values_mut(&mut self) -> impl Iterator { + self.0.values_mut() + } + /// Remove all modules. + pub fn clear(&mut self) { + self.0.clear(); + } +} + +impl ModuleResolver for StaticModuleResolver { + fn resolve(&self, _: &Engine, path: &str, pos: Position) -> Result> { + self.0 + .get(path) + .cloned() + .ok_or_else(|| EvalAltResult::ErrorModuleNotFound(path.into(), pos).into()) + } +}