Allow cross-loading of relative file paths in FileModuleResolver.

This commit is contained in:
Stephen Chung 2021-04-02 12:34:39 +08:00
parent 294d233c02
commit 889edbef71
16 changed files with 219 additions and 100 deletions

View File

@ -4,6 +4,18 @@ Rhai Release Notes
Version 0.19.16 Version 0.19.16
=============== ===============
Breaking changes
----------------
* `ModuleResolver` trait methods take an additional parameter `source_path` that contains the path of the current environment. This is to facilitate loading other script files always from the current directory.
* `FileModuleResolver` now resolves relative paths under the source path if there is no base path set.
* `FileModuleResolver::base_path` now returns `Option<&str>` which is `None` if there is no base path set.
New features
------------
* `FileModuleResolver` resolves relative paths under the parent path (i.e. the path holding the script that does the loading). This allows seamless cross-loading of scripts from a directory hierarchy instead of having all relative paths load from the current working directory.
Version 0.19.15 Version 0.19.15
=============== ===============

View File

@ -3,6 +3,7 @@ Rhai - Embedded Scripting for Rust
![GitHub last commit](https://img.shields.io/github/last-commit/rhaiscript/rhai?logo=github) ![GitHub last commit](https://img.shields.io/github/last-commit/rhaiscript/rhai?logo=github)
[![Build Status](https://github.com/rhaiscript/rhai/workflows/Build/badge.svg)](https://github.com/rhaiscript/rhai/actions) [![Build Status](https://github.com/rhaiscript/rhai/workflows/Build/badge.svg)](https://github.com/rhaiscript/rhai/actions)
[![stars](https://img.shields.io/github/stars/rhaiscript/rhai?logo=github)](https://github.com/rhaiscript/rhai)
[![license](https://img.shields.io/crates/l/rhai)](https://github.com/license/rhaiscript/rhai) [![license](https://img.shields.io/crates/l/rhai)](https://github.com/license/rhaiscript/rhai)
[![crates.io](https://img.shields.io/crates/v/rhai?logo=rust)](https://crates.io/crates/rhai/) [![crates.io](https://img.shields.io/crates/v/rhai?logo=rust)](https://crates.io/crates/rhai/)
[![crates.io](https://img.shields.io/crates/d/rhai?logo=rust)](https://crates.io/crates/rhai/) [![crates.io](https://img.shields.io/crates/d/rhai?logo=rust)](https://crates.io/crates/rhai/)
@ -35,7 +36,7 @@ Standard features
* Built-in support for most common [data types](https://rhai.rs/book/language/values-and-types.html) including booleans, [integers](https://rhai.rs/book/language/numbers.html), [floating-point numbers](https://rhai.rs/book/language/numbers.html) (including [`Decimal`](https://crates.io/crates/rust_decimal)), [strings](https://rhai.rs/book/language/strings-chars.html), [Unicode characters](https://rhai.rs/book/language/strings-chars.html), [arrays](https://rhai.rs/book/language/arrays.html) and [object maps](https://rhai.rs/book/language/object-maps.html). * Built-in support for most common [data types](https://rhai.rs/book/language/values-and-types.html) including booleans, [integers](https://rhai.rs/book/language/numbers.html), [floating-point numbers](https://rhai.rs/book/language/numbers.html) (including [`Decimal`](https://crates.io/crates/rust_decimal)), [strings](https://rhai.rs/book/language/strings-chars.html), [Unicode characters](https://rhai.rs/book/language/strings-chars.html), [arrays](https://rhai.rs/book/language/arrays.html) and [object maps](https://rhai.rs/book/language/object-maps.html).
* Easily [call a script-defined function](https://rhai.rs/book/engine/call-fn.html) from Rust. * Easily [call a script-defined function](https://rhai.rs/book/engine/call-fn.html) from Rust.
* Relatively little `unsafe` code (yes there are some for performance reasons). * Relatively little `unsafe` code (yes there are some for performance reasons).
* Few dependencies (currently only [`smallvec`](https://crates.io/crates/smallvec), [`num-traits`](https://crates.io/crates/num-traits), [`ahash`](https://crates.io/crates/ahash)) and [`smartstring`](https://crates.io/crates/smartstring)). * Few dependencies - currently only [`smallvec`](https://crates.io/crates/smallvec), [`num-traits`](https://crates.io/crates/num-traits), [`ahash`](https://crates.io/crates/ahash) and [`smartstring`](https://crates.io/crates/smartstring).
* Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature). * Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature).
* Compile once to [AST](https://rhai.rs/book/engine/compile.html) form for repeated evaluations. * Compile once to [AST](https://rhai.rs/book/engine/compile.html) form for repeated evaluations.
* Scripts are [optimized](https://rhai.rs/book/engine/optimize.html) (useful for template-based machine-generated scripts). * Scripts are [optimized](https://rhai.rs/book/engine/optimize.html) (useful for template-based machine-generated scripts).

View File

@ -1,3 +1,3 @@
import "scripts/loop"; import "loop";
print("Module test!"); print("Module test!");

View File

@ -1,9 +1,12 @@
use rhai::{Dynamic, Engine, EvalAltResult, Module, Scope, AST}; use rhai::{
module_resolvers::FileModuleResolver, Dynamic, Engine, EvalAltResult, Module, Scope, AST,
};
use std::{ use std::{
env, env,
fs::File, fs::File,
io::{stdin, stdout, Read, Write}, io::{stdin, stdout, Read, Write},
path::Path,
process::exit, process::exit,
}; };
@ -64,28 +67,39 @@ fn main() {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
{ {
// Set a file module resolver without caching
let mut resolver = rhai::module_resolvers::FileModuleResolver::new();
resolver.enable_cache(false);
engine.set_module_resolver(resolver);
// Load init scripts // Load init scripts
let mut contents = String::new(); let mut contents = String::new();
let mut has_init_scripts = false; let mut has_init_scripts = false;
for filename in env::args().skip(1) { for filename in env::args().skip(1) {
let filename = match Path::new(&filename).canonicalize() {
Err(err) => {
eprintln!("Error script file path: {}\n{}", filename, err);
exit(1);
}
Ok(f) => f,
};
contents.clear(); contents.clear();
let mut f = match File::open(&filename) { let mut f = match File::open(&filename) {
Err(err) => { Err(err) => {
eprintln!("Error reading script file: {}\n{}", filename, err); eprintln!(
"Error reading script file: {}\n{}",
filename.to_string_lossy(),
err
);
exit(1); exit(1);
} }
Ok(f) => f, Ok(f) => f,
}; };
if let Err(err) = f.read_to_string(&mut contents) { if let Err(err) = f.read_to_string(&mut contents) {
println!("Error reading script file: {}\n{}", filename, err); println!(
"Error reading script file: {}\n{}",
filename.to_string_lossy(),
err
);
exit(1); exit(1);
} }
@ -93,10 +107,12 @@ fn main() {
.compile(&contents) .compile(&contents)
.map_err(|err| err.into()) .map_err(|err| err.into())
.and_then(|mut ast| { .and_then(|mut ast| {
ast.set_source(&filename); ast.set_source(filename.to_string_lossy());
Module::eval_ast_as_new(Default::default(), &ast, &engine) Module::eval_ast_as_new(Default::default(), &ast, &engine)
}) { }) {
Err(err) => { Err(err) => {
let filename = filename.to_string_lossy();
eprintln!("{:=<1$}", "", filename.len()); eprintln!("{:=<1$}", "", filename.len());
eprintln!("{}", filename); eprintln!("{}", filename);
eprintln!("{:=<1$}", "", filename.len()); eprintln!("{:=<1$}", "", filename.len());
@ -112,7 +128,7 @@ fn main() {
has_init_scripts = true; has_init_scripts = true;
println!("Script '{}' loaded.", filename); println!("Script '{}' loaded.", filename.to_string_lossy());
} }
if has_init_scripts { if has_init_scripts {
@ -124,17 +140,23 @@ fn main() {
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
engine.set_optimization_level(rhai::OptimizationLevel::None); engine.set_optimization_level(rhai::OptimizationLevel::None);
// Set a file module resolver without caching
let mut resolver = FileModuleResolver::new();
resolver.enable_cache(false);
engine.set_module_resolver(resolver);
// Make Engine immutable
let engine = engine;
// Create scope
let mut scope = Scope::new(); let mut scope = Scope::new();
// REPL loop
let mut input = String::new(); let mut input = String::new();
let mut main_ast: AST = Default::default(); let mut main_ast: AST = Default::default();
let mut ast_u: AST = Default::default(); let mut ast_u: AST = Default::default();
let mut ast: AST = Default::default(); let mut ast: AST = Default::default();
// Make Engine immutable
let engine = engine;
// REPL loop
'main_loop: loop { 'main_loop: loop {
print!("rhai-repl> "); print!("rhai-repl> ");
stdout().flush().expect("couldn't flush stdout"); stdout().flush().expect("couldn't flush stdout");

View File

@ -3,7 +3,7 @@ use rhai::{Engine, EvalAltResult, Position};
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
use rhai::OptimizationLevel; use rhai::OptimizationLevel;
use std::{env, fs::File, io::Read, process::exit}; use std::{env, fs::File, io::Read, path::Path, process::exit};
fn eprint_error(input: &str, mut err: EvalAltResult) { fn eprint_error(input: &str, mut err: EvalAltResult) {
fn eprint_line(lines: &[&str], pos: Position, err_msg: &str) { fn eprint_line(lines: &[&str], pos: Position, err_msg: &str) {
@ -38,6 +38,14 @@ fn main() {
let mut contents = String::new(); let mut contents = String::new();
for filename in env::args().skip(1) { for filename in env::args().skip(1) {
let filename = match Path::new(&filename).canonicalize() {
Err(err) => {
eprintln!("Error script file path: {}\n{}", filename, err);
exit(1);
}
Ok(f) => f,
};
let mut engine = Engine::new(); let mut engine = Engine::new();
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
@ -45,7 +53,11 @@ fn main() {
let mut f = match File::open(&filename) { let mut f = match File::open(&filename) {
Err(err) => { Err(err) => {
eprintln!("Error reading script file: {}\n{}", filename, err); eprintln!(
"Error reading script file: {}\n{}",
filename.to_string_lossy(),
err
);
exit(1); exit(1);
} }
Ok(f) => f, Ok(f) => f,
@ -54,7 +66,11 @@ fn main() {
contents.clear(); contents.clear();
if let Err(err) = f.read_to_string(&mut contents) { if let Err(err) = f.read_to_string(&mut contents) {
eprintln!("Error reading script file: {}\n{}", filename, err); eprintln!(
"Error reading script file: {}\n{}",
filename.to_string_lossy(),
err
);
exit(1); exit(1);
} }
@ -65,7 +81,16 @@ fn main() {
&contents[..] &contents[..]
}; };
if let Err(err) = engine.consume(contents) { if let Err(err) = engine
.compile(contents)
.map_err(|err| Box::new(err.into()) as Box<EvalAltResult>)
.and_then(|mut ast| {
ast.set_source(filename.to_string_lossy());
engine.consume_ast(&ast)
})
{
let filename = filename.to_string_lossy();
eprintln!("{:=<1$}", "", filename.len()); eprintln!("{:=<1$}", "", filename.len());
eprintln!("{}", filename); eprintln!("{}", filename);
eprintln!("{:=<1$}", "", filename.len()); eprintln!("{:=<1$}", "", filename.len());

View File

@ -645,7 +645,7 @@ impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_> {
self.mods.iter() self.mods.iter()
} }
/// _(INTERNALS)_ The current set of modules imported via `import` statements. /// _(INTERNALS)_ The current set of modules imported via `import` statements.
/// Available under the `internals` feature only. /// Exported under the `internals` feature only.
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
@ -658,7 +658,7 @@ impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_> {
self.lib.iter().cloned() self.lib.iter().cloned()
} }
/// _(INTERNALS)_ The current set of namespaces containing definitions of all script-defined functions. /// _(INTERNALS)_ The current set of namespaces containing definitions of all script-defined functions.
/// Available under the `internals` feature only. /// Exported under the `internals` feature only.
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[inline(always)] #[inline(always)]
pub fn namespaces(&self) -> &[&Module] { pub fn namespaces(&self) -> &[&Module] {
@ -2433,19 +2433,22 @@ impl Engine {
{ {
use crate::ModuleResolver; use crate::ModuleResolver;
let source = state.source.as_ref().map(|s| s.as_str());
let expr_pos = expr.position(); let expr_pos = expr.position();
let module = state let module = state
.resolver .resolver
.as_ref() .as_ref()
.and_then(|r| match r.resolve(self, &path, expr_pos) { .and_then(|r| match r.resolve(self, source, &path, expr_pos) {
Ok(m) => return Some(Ok(m)), Ok(m) => return Some(Ok(m)),
Err(err) => match *err { Err(err) => match *err {
EvalAltResult::ErrorModuleNotFound(_, _) => None, EvalAltResult::ErrorModuleNotFound(_, _) => None,
_ => return Some(Err(err)), _ => return Some(Err(err)),
}, },
}) })
.unwrap_or_else(|| self.module_resolver.resolve(self, &path, expr_pos))?; .unwrap_or_else(|| {
self.module_resolver.resolve(self, source, &path, expr_pos)
})?;
if let Some(name) = export.as_ref().map(|x| x.name.clone()) { if let Some(name) = export.as_ref().map(|x| x.name.clone()) {
if !module.is_indexed() { if !module.is_indexed() {

View File

@ -317,10 +317,10 @@ impl Engine {
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[inline(always)] #[inline(always)]
pub fn register_get<T: Variant + Clone, U: Variant + Clone>( pub fn register_get<T: Variant + Clone, V: Variant + Clone>(
&mut self, &mut self,
name: &str, name: &str,
get_fn: impl Fn(&mut T) -> U + SendSync + 'static, get_fn: impl Fn(&mut T) -> V + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
use crate::engine::make_getter; use crate::engine::make_getter;
self.register_fn(&make_getter(name), get_fn) self.register_fn(&make_getter(name), get_fn)
@ -364,10 +364,10 @@ impl Engine {
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[inline(always)] #[inline(always)]
pub fn register_get_result<T: Variant + Clone, U: Variant + Clone>( pub fn register_get_result<T: Variant + Clone, V: Variant + Clone>(
&mut self, &mut self,
name: &str, name: &str,
get_fn: impl Fn(&mut T) -> Result<U, Box<EvalAltResult>> + SendSync + 'static, get_fn: impl Fn(&mut T) -> Result<V, Box<EvalAltResult>> + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
use crate::engine::make_getter; use crate::engine::make_getter;
self.register_result_fn(&make_getter(name), get_fn) self.register_result_fn(&make_getter(name), get_fn)
@ -410,10 +410,10 @@ impl Engine {
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[inline(always)] #[inline(always)]
pub fn register_set<T: Variant + Clone, U: Variant + Clone>( pub fn register_set<T: Variant + Clone, V: Variant + Clone>(
&mut self, &mut self,
name: &str, name: &str,
set_fn: impl Fn(&mut T, U) + SendSync + 'static, set_fn: impl Fn(&mut T, V) + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
use crate::engine::make_setter; use crate::engine::make_setter;
self.register_fn(&make_setter(name), set_fn) self.register_fn(&make_setter(name), set_fn)
@ -459,13 +459,13 @@ impl Engine {
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[inline(always)] #[inline(always)]
pub fn register_set_result<T: Variant + Clone, U: Variant + Clone>( pub fn register_set_result<T: Variant + Clone, V: Variant + Clone>(
&mut self, &mut self,
name: &str, name: &str,
set_fn: impl Fn(&mut T, U) -> Result<(), Box<EvalAltResult>> + SendSync + 'static, set_fn: impl Fn(&mut T, V) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
use crate::engine::make_setter; use crate::engine::make_setter;
self.register_result_fn(&make_setter(name), move |obj: &mut T, value: U| { self.register_result_fn(&make_setter(name), move |obj: &mut T, value: V| {
set_fn(obj, value) set_fn(obj, value)
}) })
} }
@ -510,11 +510,11 @@ impl Engine {
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[inline(always)] #[inline(always)]
pub fn register_get_set<T: Variant + Clone, U: Variant + Clone>( pub fn register_get_set<T: Variant + Clone, V: Variant + Clone>(
&mut self, &mut self,
name: &str, name: &str,
get_fn: impl Fn(&mut T) -> U + SendSync + 'static, get_fn: impl Fn(&mut T) -> V + SendSync + 'static,
set_fn: impl Fn(&mut T, U) + SendSync + 'static, set_fn: impl Fn(&mut T, V) + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
self.register_get(name, get_fn).register_set(name, set_fn) self.register_get(name, get_fn).register_set(name, set_fn)
} }
@ -562,9 +562,9 @@ impl Engine {
/// ``` /// ```
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
#[inline(always)] #[inline(always)]
pub fn register_indexer_get<T: Variant + Clone, X: Variant + Clone, U: Variant + Clone>( pub fn register_indexer_get<T: Variant + Clone, X: Variant + Clone, V: Variant + Clone>(
&mut self, &mut self,
get_fn: impl Fn(&mut T, X) -> U + SendSync + 'static, get_fn: impl Fn(&mut T, X) -> V + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
if TypeId::of::<T>() == TypeId::of::<Array>() { if TypeId::of::<T>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays."); panic!("Cannot register indexer for arrays.");
@ -631,10 +631,10 @@ impl Engine {
pub fn register_indexer_get_result< pub fn register_indexer_get_result<
T: Variant + Clone, T: Variant + Clone,
X: Variant + Clone, X: Variant + Clone,
U: Variant + Clone, V: Variant + Clone,
>( >(
&mut self, &mut self,
get_fn: impl Fn(&mut T, X) -> Result<U, Box<EvalAltResult>> + SendSync + 'static, get_fn: impl Fn(&mut T, X) -> Result<V, Box<EvalAltResult>> + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
if TypeId::of::<T>() == TypeId::of::<Array>() { if TypeId::of::<T>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays."); panic!("Cannot register indexer for arrays.");
@ -696,9 +696,9 @@ impl Engine {
/// ``` /// ```
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
#[inline(always)] #[inline(always)]
pub fn register_indexer_set<T: Variant + Clone, X: Variant + Clone, U: Variant + Clone>( pub fn register_indexer_set<T: Variant + Clone, X: Variant + Clone, V: Variant + Clone>(
&mut self, &mut self,
set_fn: impl Fn(&mut T, X, U) + SendSync + 'static, set_fn: impl Fn(&mut T, X, V) + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
if TypeId::of::<T>() == TypeId::of::<Array>() { if TypeId::of::<T>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays."); panic!("Cannot register indexer for arrays.");
@ -766,10 +766,10 @@ impl Engine {
pub fn register_indexer_set_result< pub fn register_indexer_set_result<
T: Variant + Clone, T: Variant + Clone,
X: Variant + Clone, X: Variant + Clone,
U: Variant + Clone, V: Variant + Clone,
>( >(
&mut self, &mut self,
set_fn: impl Fn(&mut T, X, U) -> Result<(), Box<EvalAltResult>> + SendSync + 'static, set_fn: impl Fn(&mut T, X, V) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
if TypeId::of::<T>() == TypeId::of::<Array>() { if TypeId::of::<T>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays."); panic!("Cannot register indexer for arrays.");
@ -787,7 +787,7 @@ impl Engine {
self.register_result_fn( self.register_result_fn(
crate::engine::FN_IDX_SET, crate::engine::FN_IDX_SET,
move |obj: &mut T, index: X, value: U| set_fn(obj, index, value), move |obj: &mut T, index: X, value: V| set_fn(obj, index, value),
) )
} }
/// Short-hand for register both index getter and setter functions for a custom type with the [`Engine`]. /// Short-hand for register both index getter and setter functions for a custom type with the [`Engine`].
@ -834,10 +834,10 @@ impl Engine {
/// ``` /// ```
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
#[inline(always)] #[inline(always)]
pub fn register_indexer_get_set<T: Variant + Clone, X: Variant + Clone, U: Variant + Clone>( pub fn register_indexer_get_set<T: Variant + Clone, X: Variant + Clone, V: Variant + Clone>(
&mut self, &mut self,
get_fn: impl Fn(&mut T, X) -> U + SendSync + 'static, get_fn: impl Fn(&mut T, X) -> V + SendSync + 'static,
set_fn: impl Fn(&mut T, X, U) -> () + SendSync + 'static, set_fn: impl Fn(&mut T, X, V) -> () + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
self.register_indexer_get(get_fn) self.register_indexer_get(get_fn)
.register_indexer_set(set_fn) .register_indexer_set(set_fn)
@ -1070,7 +1070,7 @@ impl Engine {
match self match self
.module_resolver .module_resolver
.resolve_ast(self, &path, Position::NONE) .resolve_ast(self, None, &path, Position::NONE)
{ {
Some(Ok(module_ast)) => { Some(Ok(module_ast)) => {
collect_imports(&module_ast, &mut resolver, &mut imports) collect_imports(&module_ast, &mut resolver, &mut imports)
@ -1081,6 +1081,7 @@ impl Engine {
let module = shared_take_or_clone(self.module_resolver.resolve( let module = shared_take_or_clone(self.module_resolver.resolve(
self, self,
None,
&path, &path,
Position::NONE, Position::NONE,
)?); )?);
@ -1977,7 +1978,7 @@ impl Engine {
crate::optimize::optimize_into_ast(self, scope, stmt.into_vec(), lib, optimization_level) crate::optimize::optimize_into_ast(self, scope, stmt.into_vec(), lib, optimization_level)
} }
/// Generate a list of all registered functions. /// Generate a list of all registered functions.
/// Available under the `metadata` feature only. /// Exported under the `metadata` feature only.
/// ///
/// Functions from the following sources are included, in order: /// Functions from the following sources are included, in order:
/// 1) Functions registered into the global namespace /// 1) Functions registered into the global namespace

View File

@ -100,7 +100,7 @@ impl<'a> NativeCallContext<'a> {
} }
} }
/// _(INTERNALS)_ Create a new [`NativeCallContext`]. /// _(INTERNALS)_ Create a new [`NativeCallContext`].
/// Available under the `internals` feature only. /// Exported under the `internals` feature only.
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
@ -150,7 +150,7 @@ impl<'a> NativeCallContext<'a> {
self.mods.iter().flat_map(|&m| m.iter_raw()) self.mods.iter().flat_map(|&m| m.iter_raw())
} }
/// _(INTERNALS)_ The current set of modules imported via `import` statements. /// _(INTERNALS)_ The current set of modules imported via `import` statements.
/// Available under the `internals` feature only. /// Exported under the `internals` feature only.
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
@ -163,7 +163,7 @@ impl<'a> NativeCallContext<'a> {
self.lib.iter().cloned() self.lib.iter().cloned()
} }
/// _(INTERNALS)_ The current set of namespaces containing definitions of all script-defined functions. /// _(INTERNALS)_ The current set of namespaces containing definitions of all script-defined functions.
/// Available under the `internals` feature only. /// Exported under the `internals` feature only.
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[inline(always)] #[inline(always)]
pub fn namespaces(&self) -> &[&Module] { pub fn namespaces(&self) -> &[&Module] {

View File

@ -60,15 +60,15 @@ pub trait RegisterNativeFunction<Args, Result> {
/// Get the type ID's of this function's parameters. /// Get the type ID's of this function's parameters.
fn param_types() -> Box<[TypeId]>; fn param_types() -> Box<[TypeId]>;
/// Get the type names of this function's parameters. /// Get the type names of this function's parameters.
/// Available under the `metadata` feature only. /// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
fn param_names() -> Box<[&'static str]>; fn param_names() -> Box<[&'static str]>;
/// Get the type ID of this function's return value. /// Get the type ID of this function's return value.
/// Available under the `metadata` feature only. /// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
fn return_type() -> TypeId; fn return_type() -> TypeId;
/// Get the type name of this function's return value. /// Get the type name of this function's return value.
/// Available under the `metadata` feature only. /// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
fn return_type_name() -> &'static str; fn return_type_name() -> &'static str;
} }

View File

@ -67,7 +67,7 @@ pub struct FuncInfo {
impl FuncInfo { impl FuncInfo {
/// Generate a signature of the function. /// Generate a signature of the function.
/// Available under the `metadata` feature only. /// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
pub fn gen_signature(&self) -> String { pub fn gen_signature(&self) -> String {
let mut sig = format!("{}(", self.name); let mut sig = format!("{}(", self.name);
@ -363,7 +363,7 @@ impl Module {
} }
/// Generate signatures for all the non-private functions in the [`Module`]. /// Generate signatures for all the non-private functions in the [`Module`].
/// Available under the `metadata` feature only. /// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
#[inline(always)] #[inline(always)]
pub fn gen_fn_signatures(&self) -> impl Iterator<Item = String> + '_ { pub fn gen_fn_signatures(&self) -> impl Iterator<Item = String> + '_ {
@ -605,7 +605,7 @@ impl Module {
} }
/// Update the metadata (parameter names/types and return type) of a registered function. /// Update the metadata (parameter names/types and return type) of a registered function.
/// Available under the `metadata` feature only. /// Exported under the `metadata` feature only.
/// ///
/// The [`u64`] hash is returned by the [`set_native_fn`][Module::set_native_fn] call. /// The [`u64`] hash is returned by the [`set_native_fn`][Module::set_native_fn] call.
/// ///

View File

@ -111,11 +111,12 @@ impl ModuleResolver for ModuleResolversCollection {
fn resolve( fn resolve(
&self, &self,
engine: &Engine, engine: &Engine,
source_path: Option<&str>,
path: &str, path: &str,
pos: Position, pos: Position,
) -> Result<Shared<Module>, Box<EvalAltResult>> { ) -> Result<Shared<Module>, Box<EvalAltResult>> {
for resolver in self.0.iter() { for resolver in self.0.iter() {
match resolver.resolve(engine, path, pos) { match resolver.resolve(engine, source_path, path, pos) {
Ok(module) => return Ok(module), Ok(module) => return Ok(module),
Err(err) => match *err { Err(err) => match *err {
EvalAltResult::ErrorModuleNotFound(_, _) => continue, EvalAltResult::ErrorModuleNotFound(_, _) => continue,

View File

@ -40,6 +40,7 @@ impl ModuleResolver for DummyModuleResolver {
fn resolve( fn resolve(
&self, &self,
_: &Engine, _: &Engine,
_: Option<&str>,
path: &str, path: &str,
pos: Position, pos: Position,
) -> Result<Shared<Module>, Box<EvalAltResult>> { ) -> Result<Shared<Module>, Box<EvalAltResult>> {

View File

@ -7,6 +7,8 @@ use crate::stdlib::{
}; };
use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared}; use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
pub const RHAI_SCRIPT_EXTENSION: &str = "rhai";
/// A [module][Module] resolution service that loads [module][Module] script files from the file system. /// A [module][Module] resolution service that loads [module][Module] script files from the file system.
/// ///
/// ## Caching /// ## Caching
@ -39,7 +41,7 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
/// ``` /// ```
#[derive(Debug)] #[derive(Debug)]
pub struct FileModuleResolver { pub struct FileModuleResolver {
base_path: PathBuf, base_path: Option<PathBuf>,
extension: String, extension: String,
cache_enabled: bool, cache_enabled: bool,
@ -52,11 +54,33 @@ pub struct FileModuleResolver {
impl Default for FileModuleResolver { impl Default for FileModuleResolver {
#[inline(always)] #[inline(always)]
fn default() -> Self { fn default() -> Self {
Self::new_with_path(PathBuf::default()) Self::new()
} }
} }
impl FileModuleResolver { impl FileModuleResolver {
/// Create a new [`FileModuleResolver`] with the current directory as base path.
///
/// The default extension is `.rhai`.
///
/// # Example
///
/// ```
/// 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(resolver);
/// ```
#[inline(always)]
pub fn new() -> Self {
Self::new_with_extension(RHAI_SCRIPT_EXTENSION)
}
/// Create a new [`FileModuleResolver`] with a specific base path. /// Create a new [`FileModuleResolver`] with a specific base path.
/// ///
/// The default extension is `.rhai`. /// The default extension is `.rhai`.
@ -76,7 +100,31 @@ impl FileModuleResolver {
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn new_with_path(path: impl Into<PathBuf>) -> Self { pub fn new_with_path(path: impl Into<PathBuf>) -> Self {
Self::new_with_path_and_extension(path, "rhai") Self::new_with_path_and_extension(path, RHAI_SCRIPT_EXTENSION)
}
/// Create a new [`FileModuleResolver`] with a file extension.
///
/// # Example
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::FileModuleResolver;
///
/// // Create a new 'FileModuleResolver' loading scripts with file extension '.rhai' (the default).
/// let resolver = FileModuleResolver::new_with_extension("rhai");
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(resolver);
/// ```
#[inline(always)]
pub fn new_with_extension(extension: impl Into<String>) -> Self {
Self {
base_path: None,
extension: extension.into(),
cache_enabled: true,
cache: Default::default(),
}
} }
/// Create a new [`FileModuleResolver`] with a specific base path and file extension. /// Create a new [`FileModuleResolver`] with a specific base path and file extension.
@ -100,44 +148,22 @@ impl FileModuleResolver {
extension: impl Into<String>, extension: impl Into<String>,
) -> Self { ) -> Self {
Self { Self {
base_path: path.into(), base_path: Some(path.into()),
extension: extension.into(), extension: extension.into(),
cache_enabled: true, cache_enabled: true,
cache: Default::default(), cache: Default::default(),
} }
} }
/// Create a new [`FileModuleResolver`] with the current directory as base path.
///
/// The default extension is `.rhai`.
///
/// # Example
///
/// ```
/// 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(resolver);
/// ```
#[inline(always)]
pub fn new() -> Self {
Default::default()
}
/// Get the base path for script files. /// Get the base path for script files.
#[inline(always)] #[inline(always)]
pub fn base_path(&self) -> &Path { pub fn base_path(&self) -> Option<&Path> {
self.base_path.as_ref() self.base_path.as_ref().map(PathBuf::as_ref)
} }
/// Set the base path for script files. /// Set the base path for script files.
#[inline(always)] #[inline(always)]
pub fn set_base_path(&mut self, path: impl Into<PathBuf>) -> &mut Self { pub fn set_base_path(&mut self, path: impl Into<PathBuf>) -> &mut Self {
self.base_path = path.into(); self.base_path = Some(path.into());
self self
} }
@ -168,12 +194,12 @@ impl FileModuleResolver {
/// Is a particular path cached? /// Is a particular path cached?
#[inline(always)] #[inline(always)]
pub fn is_cached(&self, path: &str) -> bool { pub fn is_cached(&self, path: &str, source_path: Option<&str>) -> bool {
if !self.cache_enabled { if !self.cache_enabled {
return false; return false;
} }
let file_path = self.get_file_path(path); let file_path = self.get_file_path(path, source_path);
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
return self.cache.borrow_mut().contains_key(&file_path); return self.cache.borrow_mut().contains_key(&file_path);
@ -192,8 +218,12 @@ impl FileModuleResolver {
/// ///
/// The next time this path is resolved, the script file will be loaded once again. /// The next time this path is resolved, the script file will be loaded once again.
#[inline(always)] #[inline(always)]
pub fn clear_cache_for_path(&mut self, path: &str) -> Option<Shared<Module>> { pub fn clear_cache_for_path(
let file_path = self.get_file_path(path); &mut self,
path: &str,
source_path: Option<&str>,
) -> Option<Shared<Module>> {
let file_path = self.get_file_path(path, source_path);
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
return self return self
@ -210,9 +240,22 @@ impl FileModuleResolver {
.map(|(_, v)| v); .map(|(_, v)| v);
} }
/// Construct a full file path. /// Construct a full file path.
fn get_file_path(&self, path: &str) -> PathBuf { fn get_file_path(&self, path: &str, source_path: Option<&str>) -> PathBuf {
let mut file_path = self.base_path.clone(); let path = Path::new(path);
let mut file_path;
if path.is_relative() {
file_path = self
.base_path
.clone()
.or_else(|| source_path.map(|p| p.into()))
.unwrap_or_default();
file_path.push(path); file_path.push(path);
} else {
file_path = path.into();
}
file_path.set_extension(&self.extension); // Force extension file_path.set_extension(&self.extension); // Force extension
file_path file_path
} }
@ -222,11 +265,16 @@ impl ModuleResolver for FileModuleResolver {
fn resolve( fn resolve(
&self, &self,
engine: &Engine, engine: &Engine,
source_path: Option<&str>,
path: &str, path: &str,
pos: Position, pos: Position,
) -> Result<Shared<Module>, Box<EvalAltResult>> { ) -> Result<Shared<Module>, Box<EvalAltResult>> {
// Load relative paths from source if there is no base path specified
let source_path =
source_path.and_then(|p| Path::new(p).parent().map(|p| p.to_string_lossy()));
// Construct the script file path // Construct the script file path
let file_path = self.get_file_path(path); let file_path = self.get_file_path(path, source_path.as_ref().map(|p| p.as_ref()));
// See if it is cached // See if it is cached
if self.is_cache_enabled() { if self.is_cache_enabled() {
@ -276,11 +324,12 @@ impl ModuleResolver for FileModuleResolver {
fn resolve_ast( fn resolve_ast(
&self, &self,
engine: &Engine, engine: &Engine,
source_path: Option<&str>,
path: &str, path: &str,
pos: Position, pos: Position,
) -> Option<Result<crate::AST, Box<EvalAltResult>>> { ) -> Option<Result<crate::AST, Box<EvalAltResult>>> {
// Construct the script file path // Construct the script file path
let file_path = self.get_file_path(path); let file_path = self.get_file_path(path, source_path);
// Load the script file and compile it // Load the script file and compile it
match engine.compile_file(file_path).map_err(|err| match *err { match engine.compile_file(file_path).map_err(|err| match *err {

View File

@ -25,6 +25,7 @@ pub trait ModuleResolver: SendSync {
fn resolve( fn resolve(
&self, &self,
engine: &Engine, engine: &Engine,
source_path: Option<&str>,
path: &str, path: &str,
pos: Position, pos: Position,
) -> Result<Shared<Module>, Box<EvalAltResult>>; ) -> Result<Shared<Module>, Box<EvalAltResult>>;
@ -42,6 +43,7 @@ pub trait ModuleResolver: SendSync {
fn resolve_ast( fn resolve_ast(
&self, &self,
engine: &Engine, engine: &Engine,
source_path: Option<&str>,
path: &str, path: &str,
pos: Position, pos: Position,
) -> Option<Result<AST, Box<EvalAltResult>>> { ) -> Option<Result<AST, Box<EvalAltResult>>> {

View File

@ -115,6 +115,7 @@ impl ModuleResolver for StaticModuleResolver {
fn resolve( fn resolve(
&self, &self,
_: &Engine, _: &Engine,
_: Option<&str>,
path: &str, path: &str,
pos: Position, pos: Position,
) -> Result<Shared<Module>, Box<EvalAltResult>> { ) -> Result<Shared<Module>, Box<EvalAltResult>> {

View File

@ -212,7 +212,7 @@ impl From<&crate::Module> for ModuleMetadata {
impl Engine { impl Engine {
/// _(METADATA)_ Generate a list of all functions (including those defined in an /// _(METADATA)_ Generate a list of all functions (including those defined in an
/// [`AST`][crate::AST]) in JSON format. /// [`AST`][crate::AST]) in JSON format.
/// Available under the `metadata` feature only. /// Exported under the `metadata` feature only.
/// ///
/// Functions from the following sources are included: /// Functions from the following sources are included:
/// 1) Functions defined in an [`AST`][crate::AST] /// 1) Functions defined in an [`AST`][crate::AST]
@ -221,9 +221,10 @@ impl Engine {
/// 4) Functions in global modules (optional) /// 4) Functions in global modules (optional)
pub fn gen_fn_metadata_with_ast_to_json( pub fn gen_fn_metadata_with_ast_to_json(
&self, &self,
_ast: &AST, ast: &AST,
include_global: bool, include_global: bool,
) -> serde_json::Result<String> { ) -> serde_json::Result<String> {
let _ast = ast;
let mut global: ModuleMetadata = Default::default(); let mut global: ModuleMetadata = Default::default();
if include_global { if include_global {