2020-11-08 16:00:03 +01:00
|
|
|
use crate::stdlib::{
|
2020-12-26 06:05:57 +01:00
|
|
|
boxed::Box,
|
|
|
|
collections::HashMap,
|
|
|
|
io::Error as IoError,
|
|
|
|
path::{Path, PathBuf},
|
|
|
|
string::String,
|
2020-11-08 16:00:03 +01:00
|
|
|
};
|
2020-11-27 16:37:59 +01:00
|
|
|
use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
|
2020-09-25 04:59:21 +02:00
|
|
|
|
2021-01-02 16:30:10 +01:00
|
|
|
/// [Module] resolution service that loads [module][Module] script files from the file system.
|
2020-09-25 04:59:21 +02:00
|
|
|
///
|
|
|
|
/// Script files are cached so they are are not reloaded and recompiled in subsequent requests.
|
|
|
|
///
|
2020-10-16 17:41:56 +02:00
|
|
|
/// # Function Namespace
|
|
|
|
///
|
2021-01-02 16:30:10 +01:00
|
|
|
/// When a function within a script file module is called, all functions in the _global_ namespace
|
2020-10-16 17:41:56 +02:00
|
|
|
/// plus all those defined within the same module are _merged_ into a _unified_ namespace before
|
2021-01-02 16:30:10 +01:00
|
|
|
/// the call. Therefore, functions in a module script can always cross-call each other.
|
2020-10-16 17:41:56 +02:00
|
|
|
///
|
2020-10-27 04:30:38 +01:00
|
|
|
/// # Example
|
2020-09-25 04:59:21 +02:00
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// 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();
|
|
|
|
///
|
2020-12-26 06:05:57 +01:00
|
|
|
/// engine.set_module_resolver(resolver);
|
2020-09-25 04:59:21 +02:00
|
|
|
/// ```
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct FileModuleResolver {
|
2020-12-28 02:49:54 +01:00
|
|
|
base_path: PathBuf,
|
2020-09-25 04:59:21 +02:00
|
|
|
extension: String,
|
2020-11-27 16:37:59 +01:00
|
|
|
|
|
|
|
#[cfg(not(feature = "sync"))]
|
|
|
|
cache: crate::stdlib::cell::RefCell<HashMap<PathBuf, Shared<Module>>>,
|
|
|
|
#[cfg(feature = "sync")]
|
|
|
|
cache: crate::stdlib::sync::RwLock<HashMap<PathBuf, Shared<Module>>>,
|
2020-09-25 04:59:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for FileModuleResolver {
|
2020-10-08 16:25:50 +02:00
|
|
|
#[inline(always)]
|
2020-09-25 04:59:21 +02:00
|
|
|
fn default() -> Self {
|
|
|
|
Self::new_with_path(PathBuf::default())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FileModuleResolver {
|
2020-11-20 09:52:28 +01:00
|
|
|
/// Create a new [`FileModuleResolver`] with a specific base path.
|
2020-09-25 04:59:21 +02:00
|
|
|
///
|
2021-01-02 16:30:10 +01:00
|
|
|
/// The default extension is `.rhai`.
|
|
|
|
///
|
2020-10-27 04:30:38 +01:00
|
|
|
/// # Example
|
2020-09-25 04:59:21 +02:00
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// 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();
|
2020-12-26 06:05:57 +01:00
|
|
|
/// engine.set_module_resolver(resolver);
|
2020-09-25 04:59:21 +02:00
|
|
|
/// ```
|
2020-10-08 16:25:50 +02:00
|
|
|
#[inline(always)]
|
2020-12-26 06:05:57 +01:00
|
|
|
pub fn new_with_path(path: impl Into<PathBuf>) -> Self {
|
2020-09-25 04:59:21 +02:00
|
|
|
Self::new_with_path_and_extension(path, "rhai")
|
|
|
|
}
|
|
|
|
|
2020-11-20 09:52:28 +01:00
|
|
|
/// Create a new [`FileModuleResolver`] with a specific base path and file extension.
|
2020-09-25 04:59:21 +02:00
|
|
|
///
|
2020-10-27 04:30:38 +01:00
|
|
|
/// # Example
|
2020-09-25 04:59:21 +02:00
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// 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();
|
2020-12-26 06:05:57 +01:00
|
|
|
/// engine.set_module_resolver(resolver);
|
2020-09-25 04:59:21 +02:00
|
|
|
/// ```
|
2020-10-08 16:25:50 +02:00
|
|
|
#[inline(always)]
|
2020-12-26 06:05:57 +01:00
|
|
|
pub fn new_with_path_and_extension(
|
|
|
|
path: impl Into<PathBuf>,
|
|
|
|
extension: impl Into<String>,
|
2020-09-25 04:59:21 +02:00
|
|
|
) -> Self {
|
|
|
|
Self {
|
2020-12-28 02:49:54 +01:00
|
|
|
base_path: path.into(),
|
2020-09-25 04:59:21 +02:00
|
|
|
extension: extension.into(),
|
|
|
|
cache: Default::default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-20 09:52:28 +01:00
|
|
|
/// Create a new [`FileModuleResolver`] with the current directory as base path.
|
2020-09-25 04:59:21 +02:00
|
|
|
///
|
2021-01-02 16:30:10 +01:00
|
|
|
/// The default extension is `.rhai`.
|
|
|
|
///
|
2020-10-27 04:30:38 +01:00
|
|
|
/// # Example
|
2020-09-25 04:59:21 +02:00
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// 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();
|
2020-12-26 06:05:57 +01:00
|
|
|
/// engine.set_module_resolver(resolver);
|
2020-09-25 04:59:21 +02:00
|
|
|
/// ```
|
2020-10-08 16:25:50 +02:00
|
|
|
#[inline(always)]
|
2020-09-25 04:59:21 +02:00
|
|
|
pub fn new() -> Self {
|
|
|
|
Default::default()
|
|
|
|
}
|
2020-12-26 06:05:57 +01:00
|
|
|
|
|
|
|
/// Get the base path for script files.
|
|
|
|
#[inline(always)]
|
2020-12-28 02:49:54 +01:00
|
|
|
pub fn base_path(&self) -> &Path {
|
|
|
|
self.base_path.as_ref()
|
2020-12-26 06:05:57 +01:00
|
|
|
}
|
|
|
|
/// Set the base path for script files.
|
|
|
|
#[inline(always)]
|
2020-12-28 02:49:54 +01:00
|
|
|
pub fn set_base_path(&mut self, path: impl Into<PathBuf>) -> &mut Self {
|
|
|
|
self.base_path = path.into();
|
2020-12-26 06:05:57 +01:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the script file extension.
|
|
|
|
#[inline(always)]
|
|
|
|
pub fn extension(&self) -> &str {
|
|
|
|
&self.extension
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set the script file extension.
|
|
|
|
#[inline(always)]
|
|
|
|
pub fn set_extension(&mut self, extension: impl Into<String>) -> &mut Self {
|
|
|
|
self.extension = extension.into();
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Empty the internal cache.
|
|
|
|
#[inline(always)]
|
|
|
|
pub fn clear_cache(&mut self) {
|
|
|
|
#[cfg(not(feature = "sync"))]
|
|
|
|
self.cache.borrow_mut().clear();
|
|
|
|
#[cfg(feature = "sync")]
|
|
|
|
self.cache.write().unwrap().clear();
|
|
|
|
}
|
|
|
|
|
2021-01-02 16:30:10 +01:00
|
|
|
/// Remove the specified path from internal cache.
|
|
|
|
///
|
|
|
|
/// The next time this path is resolved, the script file will be loaded once again.
|
2020-12-26 06:05:57 +01:00
|
|
|
#[inline(always)]
|
|
|
|
pub fn clear_cache_for_path(&mut self, path: impl AsRef<Path>) -> Option<Shared<Module>> {
|
|
|
|
#[cfg(not(feature = "sync"))]
|
|
|
|
return self
|
|
|
|
.cache
|
|
|
|
.borrow_mut()
|
|
|
|
.remove_entry(path.as_ref())
|
|
|
|
.map(|(_, v)| v);
|
|
|
|
#[cfg(feature = "sync")]
|
|
|
|
return self
|
|
|
|
.cache
|
|
|
|
.write()
|
|
|
|
.unwrap()
|
|
|
|
.remove_entry(path.as_ref())
|
|
|
|
.map(|(_, v)| v);
|
|
|
|
}
|
2020-09-25 04:59:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ModuleResolver for FileModuleResolver {
|
|
|
|
fn resolve(
|
|
|
|
&self,
|
|
|
|
engine: &Engine,
|
|
|
|
path: &str,
|
|
|
|
pos: Position,
|
2020-11-07 16:33:21 +01:00
|
|
|
) -> Result<Shared<Module>, Box<EvalAltResult>> {
|
2020-09-25 04:59:21 +02:00
|
|
|
// Construct the script file path
|
2020-12-28 02:49:54 +01:00
|
|
|
let mut file_path = self.base_path.clone();
|
2020-09-25 04:59:21 +02:00
|
|
|
file_path.push(path);
|
|
|
|
file_path.set_extension(&self.extension); // Force extension
|
|
|
|
|
|
|
|
// See if it is cached
|
2021-01-03 06:30:01 +01:00
|
|
|
{
|
2020-09-25 04:59:21 +02:00
|
|
|
#[cfg(not(feature = "sync"))]
|
|
|
|
let c = self.cache.borrow();
|
|
|
|
#[cfg(feature = "sync")]
|
|
|
|
let c = self.cache.read().unwrap();
|
|
|
|
|
2020-11-07 16:33:21 +01:00
|
|
|
if let Some(module) = c.get(&file_path) {
|
2021-01-03 06:30:01 +01:00
|
|
|
return Ok(module.clone());
|
2020-11-08 16:00:03 +01:00
|
|
|
}
|
2021-01-03 06:30:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Load the script file and compile it
|
|
|
|
let scope = Default::default();
|
|
|
|
|
|
|
|
let mut ast = engine
|
|
|
|
.compile_file(file_path.clone())
|
|
|
|
.map_err(|err| match *err {
|
|
|
|
EvalAltResult::ErrorSystem(_, err) if err.is::<IoError>() => {
|
|
|
|
Box::new(EvalAltResult::ErrorModuleNotFound(path.to_string(), pos))
|
|
|
|
}
|
|
|
|
_ => Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos)),
|
2020-11-08 16:00:03 +01:00
|
|
|
})?;
|
2020-11-07 16:33:21 +01:00
|
|
|
|
2021-01-03 06:30:01 +01:00
|
|
|
ast.set_source(Some(path));
|
2020-09-25 04:59:21 +02:00
|
|
|
|
2021-01-03 06:30:01 +01:00
|
|
|
// Make a module from the AST
|
|
|
|
let m: Shared<Module> = Module::eval_ast_as_new(scope, &ast, engine)
|
|
|
|
.map_err(|err| Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos)))?
|
|
|
|
.into();
|
|
|
|
|
|
|
|
// Put it into the cache
|
|
|
|
#[cfg(not(feature = "sync"))]
|
|
|
|
self.cache.borrow_mut().insert(file_path, m.clone());
|
|
|
|
#[cfg(feature = "sync")]
|
|
|
|
self.cache.write().unwrap().insert(file_path, module);
|
2020-09-25 04:59:21 +02:00
|
|
|
|
2021-01-03 06:30:01 +01:00
|
|
|
Ok(m)
|
2020-09-25 04:59:21 +02:00
|
|
|
}
|
|
|
|
}
|