Skip to content

Commit

Permalink
Allow cross-loading of relative file paths in FileModuleResolver.
Browse files Browse the repository at this point in the history
  • Loading branch information
schungx committed Apr 2, 2021
1 parent 294d233 commit 889edbe
Show file tree
Hide file tree
Showing 16 changed files with 213 additions and 94 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ Rhai Release Notes
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
===============
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Rhai - Embedded Scripting for Rust

![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)
[![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)
[![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/)
Expand Down Expand Up @@ -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).
* 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).
* 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).
* 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).
Expand Down
2 changes: 1 addition & 1 deletion scripts/module.rhai
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import "scripts/loop";
import "loop";

print("Module test!");
50 changes: 36 additions & 14 deletions src/bin/rhai-repl.rs
Original file line number Diff line number Diff line change
@@ -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::{
env,
fs::File,
io::{stdin, stdout, Read, Write},
path::Path,
process::exit,
};

Expand Down Expand Up @@ -64,39 +67,52 @@ fn main() {
#[cfg(not(feature = "no_module"))]
#[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
let mut contents = String::new();
let mut has_init_scripts = false;

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

let mut f = match File::open(&filename) {
Err(err) => {
eprintln!("Error reading script file: {}\n{}", filename, err);
eprintln!(
"Error reading script file: {}\n{}",
filename.to_string_lossy(),
err
);
exit(1);
}
Ok(f) => f,
};

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

let module = match engine
.compile(&contents)
.map_err(|err| err.into())
.and_then(|mut ast| {
ast.set_source(&filename);
ast.set_source(filename.to_string_lossy());
Module::eval_ast_as_new(Default::default(), &ast, &engine)
}) {
Err(err) => {
let filename = filename.to_string_lossy();

eprintln!("{:=<1$}", "", filename.len());
eprintln!("{}", filename);
eprintln!("{:=<1$}", "", filename.len());
Expand All @@ -112,7 +128,7 @@ fn main() {

has_init_scripts = true;

println!("Script '{}' loaded.", filename);
println!("Script '{}' loaded.", filename.to_string_lossy());
}

if has_init_scripts {
Expand All @@ -124,17 +140,23 @@ fn main() {
#[cfg(not(feature = "no_optimize"))]
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();

// REPL loop
let mut input = String::new();
let mut main_ast: AST = Default::default();
let mut ast_u: AST = Default::default();
let mut ast: AST = Default::default();

// Make Engine immutable
let engine = engine;

// REPL loop
'main_loop: loop {
print!("rhai-repl> ");
stdout().flush().expect("couldn't flush stdout");
Expand Down
33 changes: 29 additions & 4 deletions src/bin/rhai-run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use rhai::{Engine, EvalAltResult, Position};
#[cfg(not(feature = "no_optimize"))]
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_line(lines: &[&str], pos: Position, err_msg: &str) {
Expand Down Expand Up @@ -38,14 +38,26 @@ fn main() {
let mut contents = String::new();

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

#[cfg(not(feature = "no_optimize"))]
engine.set_optimization_level(OptimizationLevel::Full);

let mut f = match File::open(&filename) {
Err(err) => {
eprintln!("Error reading script file: {}\n{}", filename, err);
eprintln!(
"Error reading script file: {}\n{}",
filename.to_string_lossy(),
err
);
exit(1);
}
Ok(f) => f,
Expand All @@ -54,7 +66,11 @@ fn main() {
contents.clear();

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

Expand All @@ -65,7 +81,16 @@ fn main() {
&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!("{}", filename);
eprintln!("{:=<1$}", "", filename.len());
Expand Down
11 changes: 7 additions & 4 deletions src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,7 @@ impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_> {
self.mods.iter()
}
/// _(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(not(feature = "no_module"))]
#[inline(always)]
Expand All @@ -658,7 +658,7 @@ impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_> {
self.lib.iter().cloned()
}
/// _(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")]
#[inline(always)]
pub fn namespaces(&self) -> &[&Module] {
Expand Down Expand Up @@ -2433,19 +2433,22 @@ impl Engine {
{
use crate::ModuleResolver;

let source = state.source.as_ref().map(|s| s.as_str());
let expr_pos = expr.position();

let module = state
.resolver
.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)),
Err(err) => match *err {
EvalAltResult::ErrorModuleNotFound(_, _) => None,
_ => 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 !module.is_indexed() {
Expand Down
Loading

0 comments on commit 889edbe

Please sign in to comment.