diff --git a/CHANGELOG.md b/CHANGELOG.md
index 486e4c83..1222f2f5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,27 @@
Rhai Release Notes
==================
+Version 0.19.16
+===============
+
+Bug fixes
+---------
+
+* Property setter op-assignments now work properly.
+
+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
===============
@@ -16,12 +37,12 @@ an object map is small.
`HashMap` and `BTreeMap` have almost identical public API's so this change is unlikely to break
existing code.
-[`SmartString`](https://crates.io/crates/smartstring) is used to store identifiers (which tends to
+[`SmartString`](https://crates.io/crates/smartstring) is used to store identifiers (which tend to
be short, fewer than 23 characters, and ASCII-based) because they can usually be stored inline.
`Map` keys now also use [`SmartString`](https://crates.io/crates/smartstring).
In addition, there is now support for line continuation in strings (put `\` at the end of line) as
-well as multi-line literal strings (wrapped by back-ticks: \`...\`
).
+well as multi-line literal strings (wrapped by back-ticks: `` `...` ``).
Finally, all function signature/metadata methods are now grouped under the umbrella `metadata` feature.
This avoids spending precious resources maintaining metadata for functions for the vast majority of
@@ -45,7 +66,7 @@ Breaking changes
* The _reflections_ API such as `Engine::gen_fn_signatures`, `Module::update_fn_metadata` etc. are put under the `metadata` feature gate.
* The shebang `#!` is now a reserved symbol.
* Shebangs at the very beginning of script files are skipped when loading them.
-* [`smartstring`](https://crates.io/crates/smartstring) is used for identifiers by default. Currently, a PR branch is pulled because it breaks on `no-std` builds. The official crate will be used once `smartstring` is fixed to support `no-std`.
+* [`SmartString`](https://crates.io/crates/smartstring) is used for identifiers by default. Currently, a PR branch is pulled for `no-std` builds. The official crate will be used once `SmartString` is fixed to support `no-std`.
* `Map` is now an alias to `BTreeMap` instead of `HashMap` because most object maps hold few properties.
* `EvalAltResult::FnWrongDefinition` is renamed `WrongFnDefinition` for consistency.
diff --git a/Cargo.toml b/Cargo.toml
index cf75cb52..90100ff3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,7 +3,7 @@ members = [".", "codegen"]
[package]
name = "rhai"
-version = "0.19.15"
+version = "0.19.16"
edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
description = "Embedded scripting for Rust"
diff --git a/README.md b/README.md
index 8042448b..e748286a 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,7 @@ Rhai - Embedded Scripting for Rust

[](https://github.com/rhaiscript/rhai/actions)
+[](https://github.com/rhaiscript/rhai)
[](https://github.com/license/rhaiscript/rhai)
[](https://crates.io/crates/rhai/)
[](https://crates.io/crates/rhai/)
@@ -34,8 +35,8 @@ Standard features
* Freely pass Rust values into a script as [variables](https://rhai.rs/book/language/variables.html)/[constants](https://rhai.rs/book/language/constants.html) via an external [`Scope`](https://rhai.rs/book/rust/scope.html) - all clonable Rust types are supported; no need to implement any special trait. Or tap directly into the [variable resolution process](https://rhai.rs/book/engine/var.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.
-* 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)).
+* Relatively little `unsafe` code (yes there are some for performance reasons), and `unsafe` code is only used for type casting, never to get around the borrow checker.
+* 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).
diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml
index c4b22ab2..e24f866c 100644
--- a/codegen/Cargo.toml
+++ b/codegen/Cargo.toml
@@ -16,7 +16,7 @@ default = []
metadata = []
[dev-dependencies]
-rhai = { path = "..", version = "^0.19.15" }
+rhai = { path = "..", version = "0.19" }
trybuild = "1"
[dependencies]
diff --git a/scripts/fibonacci.rhai b/scripts/fibonacci.rhai
index 3455c5f6..14a35d5e 100644
--- a/scripts/fibonacci.rhai
+++ b/scripts/fibonacci.rhai
@@ -1,7 +1,7 @@
// This script calculates the n-th Fibonacci number using a really dumb algorithm
// to test the speed of the scripting engine.
-const target = 30;
+const target = 28;
fn fib(n) {
if n < 2 {
@@ -11,16 +11,20 @@ fn fib(n) {
}
}
+print("Running Fibonacci(28) x 5 times...");
print("Ready... Go!");
+let result;
let now = timestamp();
-let result = fib(target);
+for n in range(0, 5) {
+ result = fib(target);
+}
print("Finished. Run time = " + now.elapsed + " seconds.");
print("Fibonacci number #" + target + " = " + result);
-if result != 832_040 {
- print("The answer is WRONG! Should be 832,040!");
-}
\ No newline at end of file
+if result != 317_811 {
+ print("The answer is WRONG! Should be 317,811!");
+}
diff --git a/scripts/module.rhai b/scripts/module.rhai
index 0ad387c1..77ad12ac 100644
--- a/scripts/module.rhai
+++ b/scripts/module.rhai
@@ -1,3 +1,3 @@
-import "scripts/loop";
+import "loop";
print("Module test!");
diff --git a/src/bin/rhai-repl.rs b/src/bin/rhai-repl.rs
index 5dc2fd75..3f556f05 100644
--- a/src/bin/rhai-repl.rs
+++ b/src/bin/rhai-repl.rs
@@ -4,6 +4,7 @@ use std::{
env,
fs::File,
io::{stdin, stdout, Read, Write},
+ path::Path,
process::exit,
};
@@ -64,28 +65,39 @@ 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);
}
@@ -93,10 +105,12 @@ fn main() {
.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());
@@ -112,7 +126,7 @@ fn main() {
has_init_scripts = true;
- println!("Script '{}' loaded.", filename);
+ println!("Script '{}' loaded.", filename.to_string_lossy());
}
if has_init_scripts {
@@ -124,17 +138,26 @@ fn main() {
#[cfg(not(feature = "no_optimize"))]
engine.set_optimization_level(rhai::OptimizationLevel::None);
+ // Set a file module resolver without caching
+ #[cfg(not(feature = "no_module"))]
+ {
+ let mut resolver = rhai::module_resolvers::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");
diff --git a/src/bin/rhai-run.rs b/src/bin/rhai-run.rs
index 268390a1..bdef6b64 100644
--- a/src/bin/rhai-run.rs
+++ b/src/bin/rhai-run.rs
@@ -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) {
@@ -38,6 +38,14 @@ 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"))]
@@ -45,7 +53,11 @@ fn main() {
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,
@@ -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);
}
@@ -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)
+ .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());
diff --git a/src/engine.rs b/src/engine.rs
index 362fb0b4..15ed6f36 100644
--- a/src/engine.rs
+++ b/src/engine.rs
@@ -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)]
@@ -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] {
@@ -712,12 +712,12 @@ pub struct Engine {
pub(crate) module_resolver: Box,
/// A map mapping type names to pretty-print names.
- pub(crate) type_names: BTreeMap,
+ pub(crate) type_names: BTreeMap,
/// A set of symbols to disable.
- pub(crate) disabled_symbols: BTreeSet,
+ pub(crate) disabled_symbols: BTreeSet,
/// A map containing custom keywords and precedence to recognize.
- pub(crate) custom_keywords: BTreeMap>,
+ pub(crate) custom_keywords: BTreeMap>,
/// Custom syntax.
pub(crate) custom_syntax: BTreeMap,
/// Callback closure for resolving variable access.
@@ -1205,12 +1205,28 @@ impl Engine {
Ok((val.take_or_clone(), false))
}
- // xxx.id = ???
+ // xxx.id op= ???
Expr::Property(x) if new_val.is_some() => {
- let (_, (setter, hash_set), Ident { pos, .. }) = x.as_ref();
+ let ((getter, hash_get), (setter, hash_set), Ident { pos, .. }) =
+ x.as_ref();
+ let ((mut new_val, new_pos), (op_info, op_pos)) = new_val.unwrap();
+
+ if op_info.is_some() {
+ let hash = FnCallHash::from_native(*hash_get);
+ let mut args = [target.as_mut()];
+ let (mut orig_val, _) = self.exec_fn_call(
+ mods, state, lib, getter, hash, &mut args, is_ref, true, *pos,
+ None, level,
+ )?;
+ let obj_ptr = (&mut orig_val).into();
+ self.eval_op_assignment(
+ mods, state, lib, op_info, op_pos, obj_ptr, new_val, new_pos,
+ )?;
+ new_val = orig_val;
+ }
+
let hash = FnCallHash::from_native(*hash_set);
- let mut new_val = new_val;
- let mut args = [target_val, &mut (new_val.as_mut().unwrap().0).0];
+ let mut args = [target.as_mut(), &mut new_val];
self.exec_fn_call(
mods, state, lib, setter, hash, &mut args, is_ref, true, *pos, None,
level,
@@ -2433,19 +2449,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() {
@@ -2650,7 +2669,7 @@ impl Engine {
pub fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
self.type_names
.get(name)
- .map(String::as_str)
+ .map(|s| s.as_str())
.unwrap_or_else(|| map_std_type_name(name))
}
diff --git a/src/engine_api.rs b/src/engine_api.rs
index 3f1e1674..b1a68ea9 100644
--- a/src/engine_api.rs
+++ b/src/engine_api.rs
@@ -317,10 +317,10 @@ impl Engine {
/// ```
#[cfg(not(feature = "no_object"))]
#[inline(always)]
- pub fn register_get(
+ pub fn register_get(
&mut self,
name: &str,
- get_fn: impl Fn(&mut T) -> U + SendSync + 'static,
+ get_fn: impl Fn(&mut T) -> V + SendSync + 'static,
) -> &mut Self {
use crate::engine::make_getter;
self.register_fn(&make_getter(name), get_fn)
@@ -364,10 +364,10 @@ impl Engine {
/// ```
#[cfg(not(feature = "no_object"))]
#[inline(always)]
- pub fn register_get_result(
+ pub fn register_get_result(
&mut self,
name: &str,
- get_fn: impl Fn(&mut T) -> Result> + SendSync + 'static,
+ get_fn: impl Fn(&mut T) -> Result> + SendSync + 'static,
) -> &mut Self {
use crate::engine::make_getter;
self.register_result_fn(&make_getter(name), get_fn)
@@ -410,10 +410,10 @@ impl Engine {
/// ```
#[cfg(not(feature = "no_object"))]
#[inline(always)]
- pub fn register_set(
+ pub fn register_set(
&mut self,
name: &str,
- set_fn: impl Fn(&mut T, U) + SendSync + 'static,
+ set_fn: impl Fn(&mut T, V) + SendSync + 'static,
) -> &mut Self {
use crate::engine::make_setter;
self.register_fn(&make_setter(name), set_fn)
@@ -459,13 +459,13 @@ impl Engine {
/// ```
#[cfg(not(feature = "no_object"))]
#[inline(always)]
- pub fn register_set_result(
+ pub fn register_set_result(
&mut self,
name: &str,
- set_fn: impl Fn(&mut T, U) -> Result<(), Box> + SendSync + 'static,
+ set_fn: impl Fn(&mut T, V) -> Result<(), Box> + SendSync + 'static,
) -> &mut Self {
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)
})
}
@@ -510,11 +510,11 @@ impl Engine {
/// ```
#[cfg(not(feature = "no_object"))]
#[inline(always)]
- pub fn register_get_set(
+ pub fn register_get_set(
&mut self,
name: &str,
- get_fn: impl Fn(&mut T) -> U + SendSync + 'static,
- set_fn: impl Fn(&mut T, U) + SendSync + 'static,
+ get_fn: impl Fn(&mut T) -> V + SendSync + 'static,
+ set_fn: impl Fn(&mut T, V) + SendSync + 'static,
) -> &mut Self {
self.register_get(name, get_fn).register_set(name, set_fn)
}
@@ -562,9 +562,9 @@ impl Engine {
/// ```
#[cfg(not(feature = "no_index"))]
#[inline(always)]
- pub fn register_indexer_get(
+ pub fn register_indexer_get(
&mut self,
- get_fn: impl Fn(&mut T, X) -> U + SendSync + 'static,
+ get_fn: impl Fn(&mut T, X) -> V + SendSync + 'static,
) -> &mut Self {
if TypeId::of::() == TypeId::of::() {
panic!("Cannot register indexer for arrays.");
@@ -631,10 +631,10 @@ impl Engine {
pub fn register_indexer_get_result<
T: Variant + Clone,
X: Variant + Clone,
- U: Variant + Clone,
+ V: Variant + Clone,
>(
&mut self,
- get_fn: impl Fn(&mut T, X) -> Result> + SendSync + 'static,
+ get_fn: impl Fn(&mut T, X) -> Result> + SendSync + 'static,
) -> &mut Self {
if TypeId::of::() == TypeId::of::() {
panic!("Cannot register indexer for arrays.");
@@ -696,9 +696,9 @@ impl Engine {
/// ```
#[cfg(not(feature = "no_index"))]
#[inline(always)]
- pub fn register_indexer_set(
+ pub fn register_indexer_set(
&mut self,
- set_fn: impl Fn(&mut T, X, U) + SendSync + 'static,
+ set_fn: impl Fn(&mut T, X, V) + SendSync + 'static,
) -> &mut Self {
if TypeId::of::() == TypeId::of::() {
panic!("Cannot register indexer for arrays.");
@@ -766,10 +766,10 @@ impl Engine {
pub fn register_indexer_set_result<
T: Variant + Clone,
X: Variant + Clone,
- U: Variant + Clone,
+ V: Variant + Clone,
>(
&mut self,
- set_fn: impl Fn(&mut T, X, U) -> Result<(), Box> + SendSync + 'static,
+ set_fn: impl Fn(&mut T, X, V) -> Result<(), Box> + SendSync + 'static,
) -> &mut Self {
if TypeId::of::() == TypeId::of::() {
panic!("Cannot register indexer for arrays.");
@@ -787,7 +787,7 @@ impl Engine {
self.register_result_fn(
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`].
@@ -834,10 +834,10 @@ impl Engine {
/// ```
#[cfg(not(feature = "no_index"))]
#[inline(always)]
- pub fn register_indexer_get_set(
+ pub fn register_indexer_get_set(
&mut self,
- get_fn: impl Fn(&mut T, X) -> U + SendSync + 'static,
- set_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, V) -> () + SendSync + 'static,
) -> &mut Self {
self.register_indexer_get(get_fn)
.register_indexer_set(set_fn)
@@ -1070,7 +1070,7 @@ impl Engine {
match self
.module_resolver
- .resolve_ast(self, &path, Position::NONE)
+ .resolve_ast(self, None, &path, Position::NONE)
{
Some(Ok(module_ast)) => {
collect_imports(&module_ast, &mut resolver, &mut imports)
@@ -1081,6 +1081,7 @@ impl Engine {
let module = shared_take_or_clone(self.module_resolver.resolve(
self,
+ None,
&path,
Position::NONE,
)?);
@@ -1977,7 +1978,7 @@ impl Engine {
crate::optimize::optimize_into_ast(self, scope, stmt.into_vec(), lib, optimization_level)
}
/// 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:
/// 1) Functions registered into the global namespace
diff --git a/src/engine_settings.rs b/src/engine_settings.rs
index b666714e..017d19b0 100644
--- a/src/engine_settings.rs
+++ b/src/engine_settings.rs
@@ -1,9 +1,9 @@
//! Configuration settings for [`Engine`].
-use crate::engine::Precedence;
use crate::stdlib::{format, string::String};
use crate::token::Token;
use crate::Engine;
+use crate::{engine::Precedence, Identifier};
#[cfg(not(feature = "unchecked"))]
use crate::stdlib::num::{NonZeroU64, NonZeroUsize};
@@ -236,7 +236,7 @@ impl Engine {
/// # }
/// ```
#[inline(always)]
- pub fn disable_symbol(&mut self, symbol: impl Into) -> &mut Self {
+ pub fn disable_symbol(&mut self, symbol: impl Into) -> &mut Self {
self.disabled_symbols.insert(symbol.into());
self
}
@@ -270,7 +270,7 @@ impl Engine {
/// ```
pub fn register_custom_operator(
&mut self,
- keyword: impl AsRef + Into,
+ keyword: impl AsRef + Into,
precedence: u8,
) -> Result<&mut Self, String> {
let precedence = Precedence::new(precedence);
diff --git a/src/fn_native.rs b/src/fn_native.rs
index f14ce5e6..3625cd08 100644
--- a/src/fn_native.rs
+++ b/src/fn_native.rs
@@ -100,7 +100,7 @@ impl<'a> NativeCallContext<'a> {
}
}
/// _(INTERNALS)_ Create a new [`NativeCallContext`].
- /// Available under the `internals` feature only.
+ /// Exported under the `internals` feature only.
#[cfg(feature = "internals")]
#[cfg(not(feature = "no_module"))]
#[inline(always)]
@@ -150,7 +150,7 @@ impl<'a> NativeCallContext<'a> {
self.mods.iter().flat_map(|&m| m.iter_raw())
}
/// _(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)]
@@ -163,7 +163,7 @@ impl<'a> NativeCallContext<'a> {
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] {
diff --git a/src/fn_register.rs b/src/fn_register.rs
index 134e55ad..48fae4b8 100644
--- a/src/fn_register.rs
+++ b/src/fn_register.rs
@@ -60,15 +60,15 @@ pub trait RegisterNativeFunction {
/// Get the type ID's of this function's parameters.
fn param_types() -> Box<[TypeId]>;
/// Get the type names of this function's parameters.
- /// Available under the `metadata` feature only.
+ /// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")]
fn param_names() -> Box<[&'static str]>;
/// 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")]
fn return_type() -> TypeId;
/// 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")]
fn return_type_name() -> &'static str;
}
diff --git a/src/module/mod.rs b/src/module/mod.rs
index 8ae91968..34b23693 100644
--- a/src/module/mod.rs
+++ b/src/module/mod.rs
@@ -67,7 +67,7 @@ pub struct FuncInfo {
impl FuncInfo {
/// Generate a signature of the function.
- /// Available under the `metadata` feature only.
+ /// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")]
pub fn gen_signature(&self) -> String {
let mut sig = format!("{}(", self.name);
@@ -363,7 +363,7 @@ impl 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")]
#[inline(always)]
pub fn gen_fn_signatures(&self) -> impl Iterator- + '_ {
@@ -605,7 +605,7 @@ impl Module {
}
/// 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.
///
diff --git a/src/module/resolvers/collection.rs b/src/module/resolvers/collection.rs
index 03a967b3..f974ce09 100644
--- a/src/module/resolvers/collection.rs
+++ b/src/module/resolvers/collection.rs
@@ -111,11 +111,12 @@ impl ModuleResolver for ModuleResolversCollection {
fn resolve(
&self,
engine: &Engine,
+ source_path: Option<&str>,
path: &str,
pos: Position,
) -> Result, Box> {
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),
Err(err) => match *err {
EvalAltResult::ErrorModuleNotFound(_, _) => continue,
diff --git a/src/module/resolvers/dummy.rs b/src/module/resolvers/dummy.rs
index d238b448..157de26d 100644
--- a/src/module/resolvers/dummy.rs
+++ b/src/module/resolvers/dummy.rs
@@ -40,6 +40,7 @@ impl ModuleResolver for DummyModuleResolver {
fn resolve(
&self,
_: &Engine,
+ _: Option<&str>,
path: &str,
pos: Position,
) -> Result, Box> {
diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs
index 79ba4cf0..2915eb08 100644
--- a/src/module/resolvers/file.rs
+++ b/src/module/resolvers/file.rs
@@ -3,9 +3,10 @@ use crate::stdlib::{
collections::BTreeMap,
io::Error as IoError,
path::{Path, PathBuf},
- string::String,
};
-use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
+use crate::{Engine, EvalAltResult, Identifier, 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.
///
@@ -39,8 +40,8 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
/// ```
#[derive(Debug)]
pub struct FileModuleResolver {
- base_path: PathBuf,
- extension: String,
+ base_path: Option,
+ extension: Identifier,
cache_enabled: bool,
#[cfg(not(feature = "sync"))]
@@ -52,11 +53,33 @@ pub struct FileModuleResolver {
impl Default for FileModuleResolver {
#[inline(always)]
fn default() -> Self {
- Self::new_with_path(PathBuf::default())
+ Self::new()
}
}
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.
///
/// The default extension is `.rhai`.
@@ -76,7 +99,31 @@ impl FileModuleResolver {
/// ```
#[inline(always)]
pub fn new_with_path(path: impl Into) -> 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) -> 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.
@@ -97,47 +144,25 @@ impl FileModuleResolver {
#[inline(always)]
pub fn new_with_path_and_extension(
path: impl Into,
- extension: impl Into,
+ extension: impl Into,
) -> Self {
Self {
- base_path: path.into(),
+ base_path: Some(path.into()),
extension: extension.into(),
cache_enabled: true,
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.
#[inline(always)]
- pub fn base_path(&self) -> &Path {
- self.base_path.as_ref()
+ pub fn base_path(&self) -> Option<&Path> {
+ self.base_path.as_ref().map(PathBuf::as_ref)
}
/// Set the base path for script files.
#[inline(always)]
pub fn set_base_path(&mut self, path: impl Into) -> &mut Self {
- self.base_path = path.into();
+ self.base_path = Some(path.into());
self
}
@@ -149,7 +174,7 @@ impl FileModuleResolver {
/// Set the script file extension.
#[inline(always)]
- pub fn set_extension(&mut self, extension: impl Into) -> &mut Self {
+ pub fn set_extension(&mut self, extension: impl Into) -> &mut Self {
self.extension = extension.into();
self
}
@@ -168,12 +193,12 @@ impl FileModuleResolver {
/// Is a particular path cached?
#[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 {
return false;
}
- let file_path = self.get_file_path(path);
+ let file_path = self.get_file_path(path, source_path);
#[cfg(not(feature = "sync"))]
return self.cache.borrow_mut().contains_key(&file_path);
@@ -192,8 +217,12 @@ impl FileModuleResolver {
///
/// The next time this path is resolved, the script file will be loaded once again.
#[inline(always)]
- pub fn clear_cache_for_path(&mut self, path: &str) -> Option> {
- let file_path = self.get_file_path(path);
+ pub fn clear_cache_for_path(
+ &mut self,
+ path: &str,
+ source_path: Option<&str>,
+ ) -> Option> {
+ let file_path = self.get_file_path(path, source_path);
#[cfg(not(feature = "sync"))]
return self
@@ -210,10 +239,23 @@ impl FileModuleResolver {
.map(|(_, v)| v);
}
/// Construct a full file path.
- fn get_file_path(&self, path: &str) -> PathBuf {
- let mut file_path = self.base_path.clone();
- file_path.push(path);
- file_path.set_extension(&self.extension); // Force extension
+ fn get_file_path(&self, path: &str, source_path: Option<&str>) -> PathBuf {
+ 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);
+ } else {
+ file_path = path.into();
+ }
+
+ file_path.set_extension(self.extension.as_str()); // Force extension
file_path
}
}
@@ -222,11 +264,16 @@ impl ModuleResolver for FileModuleResolver {
fn resolve(
&self,
engine: &Engine,
+ source_path: Option<&str>,
path: &str,
pos: Position,
) -> Result, Box> {
+ // 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
- 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
if self.is_cache_enabled() {
@@ -276,11 +323,12 @@ impl ModuleResolver for FileModuleResolver {
fn resolve_ast(
&self,
engine: &Engine,
+ source_path: Option<&str>,
path: &str,
pos: Position,
) -> Option>> {
// 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
match engine.compile_file(file_path).map_err(|err| match *err {
diff --git a/src/module/resolvers/mod.rs b/src/module/resolvers/mod.rs
index 84b3448f..56eb6c03 100644
--- a/src/module/resolvers/mod.rs
+++ b/src/module/resolvers/mod.rs
@@ -25,6 +25,7 @@ pub trait ModuleResolver: SendSync {
fn resolve(
&self,
engine: &Engine,
+ source_path: Option<&str>,
path: &str,
pos: Position,
) -> Result, Box>;
@@ -42,6 +43,7 @@ pub trait ModuleResolver: SendSync {
fn resolve_ast(
&self,
engine: &Engine,
+ source_path: Option<&str>,
path: &str,
pos: Position,
) -> Option>> {
diff --git a/src/module/resolvers/stat.rs b/src/module/resolvers/stat.rs
index 89cc458f..11c99c76 100644
--- a/src/module/resolvers/stat.rs
+++ b/src/module/resolvers/stat.rs
@@ -1,5 +1,5 @@
-use crate::stdlib::{boxed::Box, collections::BTreeMap, ops::AddAssign, string::String};
-use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
+use crate::stdlib::{boxed::Box, collections::BTreeMap, ops::AddAssign};
+use crate::{Engine, EvalAltResult, Identifier, Module, ModuleResolver, Position, Shared};
/// A static [module][Module] resolution service that serves [modules][Module] added into it.
///
@@ -19,7 +19,7 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
/// engine.set_module_resolver(resolver);
/// ```
#[derive(Debug, Clone, Default)]
-pub struct StaticModuleResolver(BTreeMap>);
+pub struct StaticModuleResolver(BTreeMap>);
impl StaticModuleResolver {
/// Create a new [`StaticModuleResolver`].
@@ -44,7 +44,7 @@ impl StaticModuleResolver {
}
/// Add a [module][Module] keyed by its path.
#[inline(always)]
- pub fn insert(&mut self, path: impl Into, mut module: Module) {
+ pub fn insert(&mut self, path: impl Into, mut module: Module) {
module.build_index();
self.0.insert(path.into(), module.into());
}
@@ -70,13 +70,13 @@ impl StaticModuleResolver {
}
/// Get a mutable iterator of all the modules.
#[inline(always)]
- pub fn into_iter(self) -> impl Iterator
- )> {
+ pub fn into_iter(self) -> impl Iterator
- )> {
self.0.into_iter()
}
/// Get an iterator of all the [module][Module] paths.
#[inline(always)]
pub fn paths(&self) -> impl Iterator
- {
- self.0.keys().map(String::as_str)
+ self.0.keys().map(|s| s.as_str())
}
/// Get an iterator of all the [modules][Module].
#[inline(always)]
@@ -115,6 +115,7 @@ impl ModuleResolver for StaticModuleResolver {
fn resolve(
&self,
_: &Engine,
+ _: Option<&str>,
path: &str,
pos: Position,
) -> Result, Box> {
diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs
index 69f1e392..2cceda30 100644
--- a/src/packages/string_basic.rs
+++ b/src/packages/string_basic.rs
@@ -10,8 +10,8 @@ use crate::Array;
#[cfg(not(feature = "no_object"))]
use crate::Map;
-#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
-const FUNC_TO_DEBUG: &'static str = "to_debug";
+pub const FUNC_TO_STRING: &'static str = "to_string";
+pub const FUNC_TO_DEBUG: &'static str = "to_debug";
def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, {
combine_with_exported_module!(lib, "print_debug", print_debug_functions);
@@ -19,9 +19,8 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin
// Register print and debug
-#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline(always)]
-fn print_with_func(
+pub fn print_with_func(
fn_name: &str,
ctx: &NativeCallContext,
value: &mut Dynamic,
@@ -39,17 +38,25 @@ fn print_with_func(
mod print_debug_functions {
use crate::ImmutableString;
- #[rhai_fn(name = "print", name = "to_string", pure)]
- pub fn print_generic(item: &mut Dynamic) -> ImmutableString {
- item.to_string().into()
+ #[rhai_fn(name = "print", pure)]
+ pub fn print_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString {
+ print_with_func(FUNC_TO_STRING, &ctx, item)
}
- #[rhai_fn(name = "debug", name = "to_debug", pure)]
- pub fn debug_generic(item: &mut Dynamic) -> ImmutableString {
- format!("{:?}", item).into()
+ #[rhai_fn(name = "to_string", pure)]
+ pub fn to_string_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString {
+ ctx.engine().map_type_name(&item.to_string()).into()
+ }
+ #[rhai_fn(name = "debug", pure)]
+ pub fn debug_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString {
+ print_with_func(FUNC_TO_DEBUG, &ctx, item)
+ }
+ #[rhai_fn(name = "to_debug", pure)]
+ pub fn to_debug_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString {
+ ctx.engine().map_type_name(&format!("{:?}", item)).into()
}
#[rhai_fn(name = "print", name = "debug")]
pub fn print_empty_string() -> ImmutableString {
- "".to_string().into()
+ Default::default()
}
#[rhai_fn(name = "print", name = "to_string")]
pub fn print_string(s: ImmutableString) -> ImmutableString {
diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs
index 3477c7c2..817a43bb 100644
--- a/src/packages/string_more.rs
+++ b/src/packages/string_more.rs
@@ -6,6 +6,8 @@ use crate::stdlib::{
};
use crate::{def_package, Dynamic, ImmutableString, StaticVec, INT};
+use super::string_basic::{print_with_func, FUNC_TO_STRING};
+
def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, {
combine_with_exported_module!(lib, "string", string_functions);
@@ -21,22 +23,30 @@ mod string_functions {
use crate::ImmutableString;
#[rhai_fn(name = "+", name = "append")]
- pub fn add_append(string: &str, item: Dynamic) -> ImmutableString {
- format!("{}{}", string, item).into()
+ pub fn add_append(ctx: NativeCallContext, string: &str, mut item: Dynamic) -> ImmutableString {
+ let s = print_with_func(FUNC_TO_STRING, &ctx, &mut item);
+ format!("{}{}", string, s).into()
}
#[rhai_fn(name = "+", pure)]
- pub fn add_prepend(item: &mut Dynamic, string: &str) -> ImmutableString {
- format!("{}{}", item, string).into()
+ pub fn add_prepend(
+ ctx: NativeCallContext,
+ item: &mut Dynamic,
+ string: &str,
+ ) -> ImmutableString {
+ let s = print_with_func(FUNC_TO_STRING, &ctx, item);
+ format!("{}{}", s, string).into()
}
#[rhai_fn(name = "+")]
- pub fn add_append_unit(string: ImmutableString, _x: ()) -> ImmutableString {
+ pub fn add_append_unit(string: ImmutableString, _item: ()) -> ImmutableString {
string
}
#[rhai_fn(name = "+")]
- pub fn add_prepend_unit(_x: (), string: ImmutableString) -> ImmutableString {
+ pub fn add_prepend_unit(_item: (), string: ImmutableString) -> ImmutableString {
string
}
+ #[rhai_fn(name = "+=")]
+ pub fn add_append_assign_unit(_string: &mut ImmutableString, _item: ()) {}
#[rhai_fn(name = "len", get = "len")]
pub fn len(string: &str) -> INT {
diff --git a/src/parser.rs b/src/parser.rs
index 0925cc38..8fb73c08 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -1621,7 +1621,7 @@ fn parse_binary_op(
Token::Custom(c) => state
.engine
.custom_keywords
- .get(c)
+ .get(c.as_str())
.cloned()
.ok_or_else(|| PERR::Reserved(c.clone()).into_err(*current_pos))?,
Token::Reserved(c) if !is_valid_identifier(c.chars()) => {
@@ -1646,7 +1646,7 @@ fn parse_binary_op(
Token::Custom(c) => state
.engine
.custom_keywords
- .get(c)
+ .get(c.as_str())
.cloned()
.ok_or_else(|| PERR::Reserved(c.clone()).into_err(*next_pos))?,
Token::Reserved(c) if !is_valid_identifier(c.chars()) => {
@@ -1753,7 +1753,7 @@ fn parse_binary_op(
if state
.engine
.custom_keywords
- .get(&s)
+ .get(s.as_str())
.map_or(false, Option::is_some) =>
{
let hash = calc_fn_hash(empty(), &s, 2);
diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs
index dbb92ce3..96b67acd 100644
--- a/src/serde/metadata.rs
+++ b/src/serde/metadata.rs
@@ -212,7 +212,7 @@ impl From<&crate::Module> for ModuleMetadata {
impl Engine {
/// _(METADATA)_ Generate a list of all functions (including those defined in an
/// [`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:
/// 1) Functions defined in an [`AST`][crate::AST]
@@ -221,9 +221,10 @@ impl Engine {
/// 4) Functions in global modules (optional)
pub fn gen_fn_metadata_with_ast_to_json(
&self,
- _ast: &AST,
+ ast: &AST,
include_global: bool,
) -> serde_json::Result {
+ let _ast = ast;
let mut global: ModuleMetadata = Default::default();
if include_global {
diff --git a/src/token.rs b/src/token.rs
index 0aea77b5..20990833 100644
--- a/src/token.rs
+++ b/src/token.rs
@@ -1828,7 +1828,7 @@ impl<'a> Iterator for TokenIterator<'a> {
None => return None,
// Reserved keyword/symbol
Some((Token::Reserved(s), pos)) => (match
- (s.as_str(), self.engine.custom_keywords.contains_key(&s))
+ (s.as_str(), self.engine.custom_keywords.contains_key(s.as_str()))
{
("===", false) => Token::LexError(LERR::ImproperSymbol(s,
"'===' is not a valid operator. This is not JavaScript! Should it be '=='?".to_string(),
@@ -1869,7 +1869,7 @@ impl<'a> Iterator for TokenIterator<'a> {
(_, false) => Token::Reserved(s),
}, pos),
// Custom keyword
- Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.contains_key(&s) => {
+ Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.contains_key(s.as_str()) => {
(Token::Custom(s), pos)
}
// Custom standard keyword/symbol - must be disabled
diff --git a/tests/functions.rs b/tests/functions.rs
index 5cbdcf3f..16ea7253 100644
--- a/tests/functions.rs
+++ b/tests/functions.rs
@@ -1,84 +1,37 @@
#![cfg(not(feature = "no_function"))]
-use rhai::{Engine, EvalAltResult, FnNamespace, Module, NativeCallContext, ParseErrorType, INT};
+use rhai::{Engine, EvalAltResult, FnNamespace, Module, ParseErrorType, Shared, INT};
+#[cfg(not(feature = "no_object"))]
+#[cfg(not(feature = "sync"))]
#[test]
-fn test_functions() -> Result<(), Box> {
- let engine = Engine::new();
+fn test_functions_trait_object() -> Result<(), Box> {
+ trait TestTrait {
+ fn greet(&self) -> INT;
+ }
- assert_eq!(engine.eval::("fn add(x, n) { x + n } add(40, 2)")?, 42);
+ #[derive(Debug, Clone)]
+ struct ABC(INT);
- assert_eq!(
- engine.eval::("fn add(x, n,) { x + n } add(40, 2,)")?,
- 42
- );
+ impl TestTrait for ABC {
+ fn greet(&self) -> INT {
+ self.0
+ }
+ }
- assert_eq!(
- engine.eval::("fn add(x, n) { x + n } let a = 40; add(a, 2); a")?,
- 40
- );
+ type MySharedTestTrait = Shared;
- #[cfg(not(feature = "no_object"))]
- assert_eq!(
- engine.eval::("fn add(n) { this + n } let x = 40; x.add(2)")?,
- 42
- );
-
- #[cfg(not(feature = "no_object"))]
- assert_eq!(
- engine.eval::("fn add(n) { this += n; } let x = 40; x.add(2); x")?,
- 42
- );
-
- assert_eq!(engine.eval::("fn mul2(x) { x * 2 } mul2(21)")?, 42);
-
- assert_eq!(
- engine.eval::("fn mul2(x) { x *= 2 } let a = 21; mul2(a); a")?,
- 21
- );
-
- #[cfg(not(feature = "no_object"))]
- assert_eq!(
- engine.eval::("fn mul2() { this * 2 } let x = 21; x.mul2()")?,
- 42
- );
-
- #[cfg(not(feature = "no_object"))]
- assert_eq!(
- engine.eval::("fn mul2() { this *= 2; } let x = 21; x.mul2(); x")?,
- 42
- );
-
- Ok(())
-}
-
-#[cfg(not(feature = "no_module"))]
-#[cfg(not(feature = "unchecked"))]
-#[test]
-fn test_functions_context() -> Result<(), Box> {
let mut engine = Engine::new();
- engine.set_max_modules(40);
- engine.register_fn("test", |context: NativeCallContext, x: INT| {
- context.engine().max_modules() as INT + x
- });
+ engine
+ .register_type_with_name::("MySharedTestTrait")
+ .register_fn("new_ts", || Shared::new(ABC(42)) as MySharedTestTrait)
+ .register_fn("greet", |x: MySharedTestTrait| x.greet());
- assert_eq!(engine.eval::("test(2)")?, 42);
-
- Ok(())
-}
-
-#[test]
-fn test_functions_params() -> Result<(), Box> {
- let engine = Engine::new();
-
- // Expect duplicated parameters error
assert_eq!(
- *engine
- .compile("fn hello(x, x) { x }")
- .expect_err("should be error")
- .0,
- ParseErrorType::FnDuplicatedParam("hello".to_string(), "x".to_string())
+ engine.eval::("type_of(new_ts())")?,
+ "MySharedTestTrait"
);
+ assert_eq!(engine.eval::("let x = new_ts(); greet(x)")?, 42);
Ok(())
}
@@ -110,152 +63,3 @@ fn test_functions_namespaces() -> Result<(), Box> {
Ok(())
}
-
-#[test]
-fn test_function_pointers() -> Result<(), Box> {
- let engine = Engine::new();
-
- assert_eq!(engine.eval::(r#"type_of(Fn("abc"))"#)?, "Fn");
-
- assert_eq!(
- engine.eval::(
- r#"
- fn foo(x) { 40 + x }
-
- let f = Fn("foo");
- call(f, 2)
- "#
- )?,
- 42
- );
-
- #[cfg(not(feature = "no_object"))]
- assert_eq!(
- engine.eval::(
- r#"
- fn foo(x) { 40 + x }
-
- let fn_name = "f";
- fn_name += "oo";
-
- let f = Fn(fn_name);
- f.call(2)
- "#
- )?,
- 42
- );
-
- #[cfg(not(feature = "no_object"))]
- assert!(matches!(
- *engine.eval::(r#"let f = Fn("abc"); f.call(0)"#).expect_err("should error"),
- EvalAltResult::ErrorFunctionNotFound(f, _) if f.starts_with("abc (")
- ));
-
- #[cfg(not(feature = "no_object"))]
- assert_eq!(
- engine.eval::(
- r#"
- fn foo(x) { 40 + x }
-
- let x = #{ action: Fn("foo") };
- x.action.call(2)
- "#
- )?,
- 42
- );
-
- #[cfg(not(feature = "no_object"))]
- assert_eq!(
- engine.eval::(
- r#"
- fn foo(x) { this.data += x; }
-
- let x = #{ data: 40, action: Fn("foo") };
- x.action(2);
- x.data
- "#
- )?,
- 42
- );
-
- Ok(())
-}
-
-#[test]
-#[cfg(not(feature = "no_closure"))]
-fn test_function_captures() -> Result<(), Box> {
- let engine = Engine::new();
-
- assert_eq!(
- engine.eval::(
- r#"
- fn foo(y) { x += y; x }
-
- let x = 41;
- let y = 999;
-
- foo!(1) + x
- "#
- )?,
- 83
- );
-
- assert!(engine
- .eval::(
- r#"
- fn foo(y) { x += y; x }
-
- let x = 41;
- let y = 999;
-
- foo(1) + x
- "#
- )
- .is_err());
-
- #[cfg(not(feature = "no_object"))]
- assert!(matches!(
- *engine
- .compile(
- r#"
- fn foo() { this += x; }
-
- let x = 41;
- let y = 999;
-
- y.foo!();
- "#
- )
- .expect_err("should error")
- .0,
- ParseErrorType::MalformedCapture(_)
- ));
-
- Ok(())
-}
-
-#[test]
-fn test_function_is_def() -> Result<(), Box> {
- let engine = Engine::new();
-
- assert!(engine.eval::(
- r#"
- fn foo(x) { x + 1 }
- is_def_fn("foo", 1)
- "#
- )?);
- assert!(!engine.eval::(
- r#"
- fn foo(x) { x + 1 }
- is_def_fn("bar", 1)
- "#
- )?);
- assert!(!engine.eval::(
- r#"
- fn foo(x) { x + 1 }
- is_def_fn("foo", 0)
- "#
- )?);
-
- Ok(())
-}
diff --git a/tests/get_set.rs b/tests/get_set.rs
index 79acc67d..a55f5854 100644
--- a/tests/get_set.rs
+++ b/tests/get_set.rs
@@ -128,3 +128,36 @@ fn test_get_set_chain() -> Result<(), Box> {
Ok(())
}
+
+#[test]
+fn test_get_set_op_assignment() -> Result<(), Box> {
+ #[derive(Clone, Debug, Eq, PartialEq)]
+ struct Num(INT);
+
+ impl Num {
+ fn get(&mut self) -> INT {
+ self.0
+ }
+ fn set(&mut self, x: INT) {
+ self.0 = x;
+ }
+ }
+
+ let mut engine = Engine::new();
+
+ engine
+ .register_type::()
+ .register_fn("new_ts", || Num(40))
+ .register_get_set("v", Num::get, Num::set);
+
+ assert_eq!(
+ engine.eval::("let a = new_ts(); a.v = a.v + 2; a")?,
+ Num(42)
+ );
+ assert_eq!(
+ engine.eval::("let a = new_ts(); a.v += 2; a")?,
+ Num(42)
+ );
+
+ Ok(())
+}
diff --git a/tests/internal_fn.rs b/tests/internal_fn.rs
index 70a94a7c..b7bee8b6 100644
--- a/tests/internal_fn.rs
+++ b/tests/internal_fn.rs
@@ -18,11 +18,54 @@ fn test_internal_fn() -> Result<(), Box> {
assert_eq!(engine.eval::("fn bob() { return 4; 5 } bob()")?, 4);
+ assert_eq!(engine.eval::("fn add(x, n) { x + n } add(40, 2)")?, 42);
+
+ assert_eq!(
+ engine.eval::("fn add(x, n,) { x + n } add(40, 2,)")?,
+ 42
+ );
+
+ assert_eq!(
+ engine.eval::("fn add(x, n) { x + n } let a = 40; add(a, 2); a")?,
+ 40
+ );
+
+ #[cfg(not(feature = "no_object"))]
+ assert_eq!(
+ engine.eval::("fn add(n) { this + n } let x = 40; x.add(2)")?,
+ 42
+ );
+
+ #[cfg(not(feature = "no_object"))]
+ assert_eq!(
+ engine.eval::("fn add(n) { this += n; } let x = 40; x.add(2); x")?,
+ 42
+ );
+
+ assert_eq!(engine.eval::("fn mul2(x) { x * 2 } mul2(21)")?, 42);
+
+ assert_eq!(
+ engine.eval::("fn mul2(x) { x *= 2 } let a = 21; mul2(a); a")?,
+ 21
+ );
+
+ #[cfg(not(feature = "no_object"))]
+ assert_eq!(
+ engine.eval::("fn mul2() { this * 2 } let x = 21; x.mul2()")?,
+ 42
+ );
+
+ #[cfg(not(feature = "no_object"))]
+ assert_eq!(
+ engine.eval::("fn mul2() { this *= 2; } let x = 21; x.mul2(); x")?,
+ 42
+ );
+
Ok(())
}
#[test]
-fn test_big_internal_fn() -> Result<(), Box> {
+fn test_internal_fn_big() -> Result<(), Box> {
let engine = Engine::new();
assert_eq!(
@@ -73,3 +116,168 @@ fn test_internal_fn_overloading() -> Result<(), Box> {
Ok(())
}
+
+#[test]
+fn test_internal_fn_params() -> Result<(), Box> {
+ let engine = Engine::new();
+
+ // Expect duplicated parameters error
+ assert_eq!(
+ *engine
+ .compile("fn hello(x, x) { x }")
+ .expect_err("should be error")
+ .0,
+ ParseErrorType::FnDuplicatedParam("hello".to_string(), "x".to_string())
+ );
+
+ Ok(())
+}
+
+#[test]
+fn test_function_pointers() -> Result<(), Box> {
+ let engine = Engine::new();
+
+ assert_eq!(engine.eval::(r#"type_of(Fn("abc"))"#)?, "Fn");
+
+ assert_eq!(
+ engine.eval::(
+ r#"
+ fn foo(x) { 40 + x }
+
+ let f = Fn("foo");
+ call(f, 2)
+ "#
+ )?,
+ 42
+ );
+
+ #[cfg(not(feature = "no_object"))]
+ assert_eq!(
+ engine.eval::(
+ r#"
+ fn foo(x) { 40 + x }
+
+ let fn_name = "f";
+ fn_name += "oo";
+
+ let f = Fn(fn_name);
+ f.call(2)
+ "#
+ )?,
+ 42
+ );
+
+ #[cfg(not(feature = "no_object"))]
+ assert!(matches!(
+ *engine.eval::(r#"let f = Fn("abc"); f.call(0)"#).expect_err("should error"),
+ EvalAltResult::ErrorFunctionNotFound(f, _) if f.starts_with("abc (")
+ ));
+
+ #[cfg(not(feature = "no_object"))]
+ assert_eq!(
+ engine.eval::(
+ r#"
+ fn foo(x) { 40 + x }
+
+ let x = #{ action: Fn("foo") };
+ x.action.call(2)
+ "#
+ )?,
+ 42
+ );
+
+ #[cfg(not(feature = "no_object"))]
+ assert_eq!(
+ engine.eval::(
+ r#"
+ fn foo(x) { this.data += x; }
+
+ let x = #{ data: 40, action: Fn("foo") };
+ x.action(2);
+ x.data
+ "#
+ )?,
+ 42
+ );
+
+ Ok(())
+}
+
+#[test]
+#[cfg(not(feature = "no_closure"))]
+fn test_internal_fn_captures() -> Result<(), Box> {
+ let engine = Engine::new();
+
+ assert_eq!(
+ engine.eval::(
+ r#"
+ fn foo(y) { x += y; x }
+
+ let x = 41;
+ let y = 999;
+
+ foo!(1) + x
+ "#
+ )?,
+ 83
+ );
+
+ assert!(engine
+ .eval::(
+ r#"
+ fn foo(y) { x += y; x }
+
+ let x = 41;
+ let y = 999;
+
+ foo(1) + x
+ "#
+ )
+ .is_err());
+
+ #[cfg(not(feature = "no_object"))]
+ assert!(matches!(
+ *engine
+ .compile(
+ r#"
+ fn foo() { this += x; }
+
+ let x = 41;
+ let y = 999;
+
+ y.foo!();
+ "#
+ )
+ .expect_err("should error")
+ .0,
+ ParseErrorType::MalformedCapture(_)
+ ));
+
+ Ok(())
+}
+
+#[test]
+fn test_internal_fn_is_def() -> Result<(), Box> {
+ let engine = Engine::new();
+
+ assert!(engine.eval::(
+ r#"
+ fn foo(x) { x + 1 }
+ is_def_fn("foo", 1)
+ "#
+ )?);
+ assert!(!engine.eval::(
+ r#"
+ fn foo(x) { x + 1 }
+ is_def_fn("bar", 1)
+ "#
+ )?);
+ assert!(!engine.eval::(
+ r#"
+ fn foo(x) { x + 1 }
+ is_def_fn("foo", 0)
+ "#
+ )?);
+
+ Ok(())
+}
diff --git a/tests/native.rs b/tests/native.rs
index 209c4a57..2112f6f7 100644
--- a/tests/native.rs
+++ b/tests/native.rs
@@ -1,8 +1,24 @@
use rhai::{Dynamic, Engine, EvalAltResult, NativeCallContext, INT};
use std::any::TypeId;
+#[cfg(not(feature = "no_module"))]
+#[cfg(not(feature = "unchecked"))]
#[test]
fn test_native_context() -> Result<(), Box> {
+ let mut engine = Engine::new();
+
+ engine.set_max_modules(40);
+ engine.register_fn("test", |context: NativeCallContext, x: INT| {
+ context.engine().max_modules() as INT + x
+ });
+
+ assert_eq!(engine.eval::("test(2)")?, 42);
+
+ Ok(())
+}
+
+#[test]
+fn test_native_context_fn_name() -> Result<(), Box> {
fn add_double(
context: NativeCallContext,
args: &mut [&mut Dynamic],
@@ -21,14 +37,14 @@ fn test_native_context() -> Result<(), Box> {
add_double,
)
.register_raw_fn(
- "adbl",
+ "append_x2",
&[TypeId::of::(), TypeId::of::()],
add_double,
);
assert_eq!(engine.eval::("add_double(40, 1)")?, "add_double_42");
- assert_eq!(engine.eval::("adbl(40, 1)")?, "adbl_42");
+ assert_eq!(engine.eval::("append_x2(40, 1)")?, "append_x2_42");
Ok(())
}
diff --git a/tests/string.rs b/tests/string.rs
index 2e2d74a7..92145239 100644
--- a/tests/string.rs
+++ b/tests/string.rs
@@ -222,6 +222,41 @@ fn test_string_substring() -> Result<(), Box> {
Ok(())
}
+#[cfg(not(feature = "no_object"))]
+#[test]
+fn test_string_format() -> Result<(), Box> {
+ #[derive(Debug, Clone)]
+ struct TestStruct {
+ field: i64,
+ }
+
+ let mut engine = Engine::new();
+
+ engine
+ .register_type_with_name::("TestStruct")
+ .register_fn("new_ts", || TestStruct { field: 42 })
+ .register_fn("to_string", |ts: TestStruct| format!("TS={}", ts.field))
+ .register_fn("to_debug", |ts: TestStruct| {
+ format!("!!!TS={}!!!", ts.field)
+ });
+
+ assert_eq!(
+ engine.eval::(r#"let x = new_ts(); "foo" + x"#)?,
+ "fooTS=42"
+ );
+ assert_eq!(
+ engine.eval::(r#"let x = new_ts(); x + "foo""#)?,
+ "TS=42foo"
+ );
+ #[cfg(not(feature = "no_index"))]
+ assert_eq!(
+ engine.eval::(r#"let x = [new_ts()]; "foo" + x"#)?,
+ "foo[!!!TS=42!!!]"
+ );
+
+ Ok(())
+}
+
#[test]
fn test_string_fn() -> Result<(), Box> {
let mut engine = Engine::new();