Merge pull request #387 from schungx/master

Fix property setter op-assignment bug.
This commit is contained in:
Stephen Chung 2021-04-03 12:04:26 +08:00 committed by GitHub
commit fa63f2a0ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 637 additions and 376 deletions

View File

@ -1,6 +1,27 @@
Rhai Release Notes 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 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 `HashMap` and `BTreeMap` have almost identical public API's so this change is unlikely to break
existing code. 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. 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). `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 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: <code>\`...\`</code>). well as multi-line literal strings (wrapped by back-ticks: `` `...` ``).
Finally, all function signature/metadata methods are now grouped under the umbrella `metadata` feature. 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 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 _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. * The shebang `#!` is now a reserved symbol.
* Shebangs at the very beginning of script files are skipped when loading them. * 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<SmartString, Dynamic>` instead of `HashMap` because most object maps hold few properties. * `Map` is now an alias to `BTreeMap<SmartString, Dynamic>` instead of `HashMap` because most object maps hold few properties.
* `EvalAltResult::FnWrongDefinition` is renamed `WrongFnDefinition` for consistency. * `EvalAltResult::FnWrongDefinition` is renamed `WrongFnDefinition` for consistency.

View File

@ -3,7 +3,7 @@ members = [".", "codegen"]
[package] [package]
name = "rhai" name = "rhai"
version = "0.19.15" version = "0.19.16"
edition = "2018" edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
description = "Embedded scripting for Rust" description = "Embedded scripting for Rust"

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/)
@ -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). * 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). * 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), 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)). * 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

@ -16,7 +16,7 @@ default = []
metadata = [] metadata = []
[dev-dependencies] [dev-dependencies]
rhai = { path = "..", version = "^0.19.15" } rhai = { path = "..", version = "0.19" }
trybuild = "1" trybuild = "1"
[dependencies] [dependencies]

View File

@ -1,7 +1,7 @@
// This script calculates the n-th Fibonacci number using a really dumb algorithm // This script calculates the n-th Fibonacci number using a really dumb algorithm
// to test the speed of the scripting engine. // to test the speed of the scripting engine.
const target = 30; const target = 28;
fn fib(n) { fn fib(n) {
if n < 2 { if n < 2 {
@ -11,16 +11,20 @@ fn fib(n) {
} }
} }
print("Running Fibonacci(28) x 5 times...");
print("Ready... Go!"); print("Ready... Go!");
let result;
let now = timestamp(); let now = timestamp();
let result = fib(target); for n in range(0, 5) {
result = fib(target);
}
print("Finished. Run time = " + now.elapsed + " seconds."); print("Finished. Run time = " + now.elapsed + " seconds.");
print("Fibonacci number #" + target + " = " + result); print("Fibonacci number #" + target + " = " + result);
if result != 832_040 { if result != 317_811 {
print("The answer is WRONG! Should be 832,040!"); print("The answer is WRONG! Should be 317,811!");
} }

View File

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

View File

@ -4,6 +4,7 @@ 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 +65,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 +105,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 +126,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 +138,26 @@ 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
#[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(); 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] {
@ -712,12 +712,12 @@ pub struct Engine {
pub(crate) module_resolver: Box<dyn crate::ModuleResolver>, pub(crate) module_resolver: Box<dyn crate::ModuleResolver>,
/// A map mapping type names to pretty-print names. /// A map mapping type names to pretty-print names.
pub(crate) type_names: BTreeMap<String, String>, pub(crate) type_names: BTreeMap<Identifier, Identifier>,
/// A set of symbols to disable. /// A set of symbols to disable.
pub(crate) disabled_symbols: BTreeSet<String>, pub(crate) disabled_symbols: BTreeSet<Identifier>,
/// A map containing custom keywords and precedence to recognize. /// A map containing custom keywords and precedence to recognize.
pub(crate) custom_keywords: BTreeMap<String, Option<Precedence>>, pub(crate) custom_keywords: BTreeMap<Identifier, Option<Precedence>>,
/// Custom syntax. /// Custom syntax.
pub(crate) custom_syntax: BTreeMap<Identifier, CustomSyntax>, pub(crate) custom_syntax: BTreeMap<Identifier, CustomSyntax>,
/// Callback closure for resolving variable access. /// Callback closure for resolving variable access.
@ -1205,12 +1205,28 @@ impl Engine {
Ok((val.take_or_clone(), false)) Ok((val.take_or_clone(), false))
} }
// xxx.id = ??? // xxx.id op= ???
Expr::Property(x) if new_val.is_some() => { 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 hash = FnCallHash::from_native(*hash_set);
let mut new_val = new_val; let mut args = [target.as_mut(), &mut new_val];
let mut args = [target_val, &mut (new_val.as_mut().unwrap().0).0];
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, setter, hash, &mut args, is_ref, true, *pos, None, mods, state, lib, setter, hash, &mut args, is_ref, true, *pos, None,
level, level,
@ -2433,19 +2449,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() {
@ -2650,7 +2669,7 @@ impl Engine {
pub fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str { pub fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
self.type_names self.type_names
.get(name) .get(name)
.map(String::as_str) .map(|s| s.as_str())
.unwrap_or_else(|| map_std_type_name(name)) .unwrap_or_else(|| map_std_type_name(name))
} }

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

@ -1,9 +1,9 @@
//! Configuration settings for [`Engine`]. //! Configuration settings for [`Engine`].
use crate::engine::Precedence;
use crate::stdlib::{format, string::String}; use crate::stdlib::{format, string::String};
use crate::token::Token; use crate::token::Token;
use crate::Engine; use crate::Engine;
use crate::{engine::Precedence, Identifier};
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
use crate::stdlib::num::{NonZeroU64, NonZeroUsize}; use crate::stdlib::num::{NonZeroU64, NonZeroUsize};
@ -236,7 +236,7 @@ impl Engine {
/// # } /// # }
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn disable_symbol(&mut self, symbol: impl Into<String>) -> &mut Self { pub fn disable_symbol(&mut self, symbol: impl Into<Identifier>) -> &mut Self {
self.disabled_symbols.insert(symbol.into()); self.disabled_symbols.insert(symbol.into());
self self
} }
@ -270,7 +270,7 @@ impl Engine {
/// ``` /// ```
pub fn register_custom_operator( pub fn register_custom_operator(
&mut self, &mut self,
keyword: impl AsRef<str> + Into<String>, keyword: impl AsRef<str> + Into<Identifier>,
precedence: u8, precedence: u8,
) -> Result<&mut Self, String> { ) -> Result<&mut Self, String> {
let precedence = Precedence::new(precedence); let precedence = Precedence::new(precedence);

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

@ -3,9 +3,10 @@ use crate::stdlib::{
collections::BTreeMap, collections::BTreeMap,
io::Error as IoError, io::Error as IoError,
path::{Path, PathBuf}, 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. /// 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)] #[derive(Debug)]
pub struct FileModuleResolver { pub struct FileModuleResolver {
base_path: PathBuf, base_path: Option<PathBuf>,
extension: String, extension: Identifier,
cache_enabled: bool, cache_enabled: bool,
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
@ -52,11 +53,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 +99,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<Identifier>) -> 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.
@ -97,47 +144,25 @@ impl FileModuleResolver {
#[inline(always)] #[inline(always)]
pub fn new_with_path_and_extension( pub fn new_with_path_and_extension(
path: impl Into<PathBuf>, path: impl Into<PathBuf>,
extension: impl Into<String>, extension: impl Into<Identifier>,
) -> 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
} }
@ -149,7 +174,7 @@ impl FileModuleResolver {
/// Set the script file extension. /// Set the script file extension.
#[inline(always)] #[inline(always)]
pub fn set_extension(&mut self, extension: impl Into<String>) -> &mut Self { pub fn set_extension(&mut self, extension: impl Into<Identifier>) -> &mut Self {
self.extension = extension.into(); self.extension = extension.into();
self self
} }
@ -168,12 +193,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 +217,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,10 +239,23 @@ 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);
file_path.set_extension(&self.extension); // Force extension } else {
file_path = path.into();
}
file_path.set_extension(self.extension.as_str()); // Force extension
file_path file_path
} }
} }
@ -222,11 +264,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 +323,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

@ -1,5 +1,5 @@
use crate::stdlib::{boxed::Box, collections::BTreeMap, ops::AddAssign, string::String}; use crate::stdlib::{boxed::Box, collections::BTreeMap, ops::AddAssign};
use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared}; use crate::{Engine, EvalAltResult, Identifier, Module, ModuleResolver, Position, Shared};
/// A static [module][Module] resolution service that serves [modules][Module] added into it. /// 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); /// engine.set_module_resolver(resolver);
/// ``` /// ```
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct StaticModuleResolver(BTreeMap<String, Shared<Module>>); pub struct StaticModuleResolver(BTreeMap<Identifier, Shared<Module>>);
impl StaticModuleResolver { impl StaticModuleResolver {
/// Create a new [`StaticModuleResolver`]. /// Create a new [`StaticModuleResolver`].
@ -44,7 +44,7 @@ impl StaticModuleResolver {
} }
/// Add a [module][Module] keyed by its path. /// Add a [module][Module] keyed by its path.
#[inline(always)] #[inline(always)]
pub fn insert(&mut self, path: impl Into<String>, mut module: Module) { pub fn insert(&mut self, path: impl Into<Identifier>, mut module: Module) {
module.build_index(); module.build_index();
self.0.insert(path.into(), module.into()); self.0.insert(path.into(), module.into());
} }
@ -70,13 +70,13 @@ impl StaticModuleResolver {
} }
/// Get a mutable iterator of all the modules. /// Get a mutable iterator of all the modules.
#[inline(always)] #[inline(always)]
pub fn into_iter(self) -> impl Iterator<Item = (String, Shared<Module>)> { pub fn into_iter(self) -> impl Iterator<Item = (Identifier, Shared<Module>)> {
self.0.into_iter() self.0.into_iter()
} }
/// Get an iterator of all the [module][Module] paths. /// Get an iterator of all the [module][Module] paths.
#[inline(always)] #[inline(always)]
pub fn paths(&self) -> impl Iterator<Item = &str> { pub fn paths(&self) -> impl Iterator<Item = &str> {
self.0.keys().map(String::as_str) self.0.keys().map(|s| s.as_str())
} }
/// Get an iterator of all the [modules][Module]. /// Get an iterator of all the [modules][Module].
#[inline(always)] #[inline(always)]
@ -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

@ -10,8 +10,8 @@ use crate::Array;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::Map; use crate::Map;
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] pub const FUNC_TO_STRING: &'static str = "to_string";
const FUNC_TO_DEBUG: &'static str = "to_debug"; pub const FUNC_TO_DEBUG: &'static str = "to_debug";
def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, { def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, {
combine_with_exported_module!(lib, "print_debug", print_debug_functions); 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 // Register print and debug
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline(always)] #[inline(always)]
fn print_with_func( pub fn print_with_func(
fn_name: &str, fn_name: &str,
ctx: &NativeCallContext, ctx: &NativeCallContext,
value: &mut Dynamic, value: &mut Dynamic,
@ -39,17 +38,25 @@ fn print_with_func(
mod print_debug_functions { mod print_debug_functions {
use crate::ImmutableString; use crate::ImmutableString;
#[rhai_fn(name = "print", name = "to_string", pure)] #[rhai_fn(name = "print", pure)]
pub fn print_generic(item: &mut Dynamic) -> ImmutableString { pub fn print_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString {
item.to_string().into() print_with_func(FUNC_TO_STRING, &ctx, item)
} }
#[rhai_fn(name = "debug", name = "to_debug", pure)] #[rhai_fn(name = "to_string", pure)]
pub fn debug_generic(item: &mut Dynamic) -> ImmutableString { pub fn to_string_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString {
format!("{:?}", item).into() 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")] #[rhai_fn(name = "print", name = "debug")]
pub fn print_empty_string() -> ImmutableString { pub fn print_empty_string() -> ImmutableString {
"".to_string().into() Default::default()
} }
#[rhai_fn(name = "print", name = "to_string")] #[rhai_fn(name = "print", name = "to_string")]
pub fn print_string(s: ImmutableString) -> ImmutableString { pub fn print_string(s: ImmutableString) -> ImmutableString {

View File

@ -6,6 +6,8 @@ use crate::stdlib::{
}; };
use crate::{def_package, Dynamic, ImmutableString, StaticVec, INT}; 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, { def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, {
combine_with_exported_module!(lib, "string", string_functions); combine_with_exported_module!(lib, "string", string_functions);
@ -21,22 +23,30 @@ mod string_functions {
use crate::ImmutableString; use crate::ImmutableString;
#[rhai_fn(name = "+", name = "append")] #[rhai_fn(name = "+", name = "append")]
pub fn add_append(string: &str, item: Dynamic) -> ImmutableString { pub fn add_append(ctx: NativeCallContext, string: &str, mut item: Dynamic) -> ImmutableString {
format!("{}{}", string, item).into() let s = print_with_func(FUNC_TO_STRING, &ctx, &mut item);
format!("{}{}", string, s).into()
} }
#[rhai_fn(name = "+", pure)] #[rhai_fn(name = "+", pure)]
pub fn add_prepend(item: &mut Dynamic, string: &str) -> ImmutableString { pub fn add_prepend(
format!("{}{}", item, string).into() 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 = "+")] #[rhai_fn(name = "+")]
pub fn add_append_unit(string: ImmutableString, _x: ()) -> ImmutableString { pub fn add_append_unit(string: ImmutableString, _item: ()) -> ImmutableString {
string string
} }
#[rhai_fn(name = "+")] #[rhai_fn(name = "+")]
pub fn add_prepend_unit(_x: (), string: ImmutableString) -> ImmutableString { pub fn add_prepend_unit(_item: (), string: ImmutableString) -> ImmutableString {
string string
} }
#[rhai_fn(name = "+=")]
pub fn add_append_assign_unit(_string: &mut ImmutableString, _item: ()) {}
#[rhai_fn(name = "len", get = "len")] #[rhai_fn(name = "len", get = "len")]
pub fn len(string: &str) -> INT { pub fn len(string: &str) -> INT {

View File

@ -1621,7 +1621,7 @@ fn parse_binary_op(
Token::Custom(c) => state Token::Custom(c) => state
.engine .engine
.custom_keywords .custom_keywords
.get(c) .get(c.as_str())
.cloned() .cloned()
.ok_or_else(|| PERR::Reserved(c.clone()).into_err(*current_pos))?, .ok_or_else(|| PERR::Reserved(c.clone()).into_err(*current_pos))?,
Token::Reserved(c) if !is_valid_identifier(c.chars()) => { Token::Reserved(c) if !is_valid_identifier(c.chars()) => {
@ -1646,7 +1646,7 @@ fn parse_binary_op(
Token::Custom(c) => state Token::Custom(c) => state
.engine .engine
.custom_keywords .custom_keywords
.get(c) .get(c.as_str())
.cloned() .cloned()
.ok_or_else(|| PERR::Reserved(c.clone()).into_err(*next_pos))?, .ok_or_else(|| PERR::Reserved(c.clone()).into_err(*next_pos))?,
Token::Reserved(c) if !is_valid_identifier(c.chars()) => { Token::Reserved(c) if !is_valid_identifier(c.chars()) => {
@ -1753,7 +1753,7 @@ fn parse_binary_op(
if state if state
.engine .engine
.custom_keywords .custom_keywords
.get(&s) .get(s.as_str())
.map_or(false, Option::is_some) => .map_or(false, Option::is_some) =>
{ {
let hash = calc_fn_hash(empty(), &s, 2); let hash = calc_fn_hash(empty(), &s, 2);

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 {

View File

@ -1828,7 +1828,7 @@ impl<'a> Iterator for TokenIterator<'a> {
None => return None, None => return None,
// Reserved keyword/symbol // Reserved keyword/symbol
Some((Token::Reserved(s), pos)) => (match 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, ("===", false) => Token::LexError(LERR::ImproperSymbol(s,
"'===' is not a valid operator. This is not JavaScript! Should it be '=='?".to_string(), "'===' 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), (_, false) => Token::Reserved(s),
}, pos), }, pos),
// Custom keyword // 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) (Token::Custom(s), pos)
} }
// Custom standard keyword/symbol - must be disabled // Custom standard keyword/symbol - must be disabled

View File

@ -1,84 +1,37 @@
#![cfg(not(feature = "no_function"))] #![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] #[test]
fn test_functions() -> Result<(), Box<EvalAltResult>> { fn test_functions_trait_object() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); trait TestTrait {
fn greet(&self) -> INT;
assert_eq!(engine.eval::<INT>("fn add(x, n) { x + n } add(40, 2)")?, 42);
assert_eq!(
engine.eval::<INT>("fn add(x, n,) { x + n } add(40, 2,)")?,
42
);
assert_eq!(
engine.eval::<INT>("fn add(x, n) { x + n } let a = 40; add(a, 2); a")?,
40
);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>("fn add(n) { this + n } let x = 40; x.add(2)")?,
42
);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>("fn add(n) { this += n; } let x = 40; x.add(2); x")?,
42
);
assert_eq!(engine.eval::<INT>("fn mul2(x) { x * 2 } mul2(21)")?, 42);
assert_eq!(
engine.eval::<INT>("fn mul2(x) { x *= 2 } let a = 21; mul2(a); a")?,
21
);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>("fn mul2() { this * 2 } let x = 21; x.mul2()")?,
42
);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>("fn mul2() { this *= 2; } let x = 21; x.mul2(); x")?,
42
);
Ok(())
} }
#[cfg(not(feature = "no_module"))] #[derive(Debug, Clone)]
#[cfg(not(feature = "unchecked"))] struct ABC(INT);
#[test]
fn test_functions_context() -> Result<(), Box<EvalAltResult>> { impl TestTrait for ABC {
fn greet(&self) -> INT {
self.0
}
}
type MySharedTestTrait = Shared<dyn TestTrait>;
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.set_max_modules(40); engine
engine.register_fn("test", |context: NativeCallContext, x: INT| { .register_type_with_name::<MySharedTestTrait>("MySharedTestTrait")
context.engine().max_modules() as INT + x .register_fn("new_ts", || Shared::new(ABC(42)) as MySharedTestTrait)
}); .register_fn("greet", |x: MySharedTestTrait| x.greet());
assert_eq!(engine.eval::<INT>("test(2)")?, 42);
Ok(())
}
#[test]
fn test_functions_params() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
// Expect duplicated parameters error
assert_eq!( assert_eq!(
*engine engine.eval::<String>("type_of(new_ts())")?,
.compile("fn hello(x, x) { x }") "MySharedTestTrait"
.expect_err("should be error")
.0,
ParseErrorType::FnDuplicatedParam("hello".to_string(), "x".to_string())
); );
assert_eq!(engine.eval::<INT>("let x = new_ts(); greet(x)")?, 42);
Ok(()) Ok(())
} }
@ -110,152 +63,3 @@ fn test_functions_namespaces() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
fn test_function_pointers() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(engine.eval::<String>(r#"type_of(Fn("abc"))"#)?, "Fn");
assert_eq!(
engine.eval::<INT>(
r#"
fn foo(x) { 40 + x }
let f = Fn("foo");
call(f, 2)
"#
)?,
42
);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>(
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::<INT>(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::<INT>(
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::<INT>(
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<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(
engine.eval::<INT>(
r#"
fn foo(y) { x += y; x }
let x = 41;
let y = 999;
foo!(1) + x
"#
)?,
83
);
assert!(engine
.eval::<INT>(
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<EvalAltResult>> {
let engine = Engine::new();
assert!(engine.eval::<bool>(
r#"
fn foo(x) { x + 1 }
is_def_fn("foo", 1)
"#
)?);
assert!(!engine.eval::<bool>(
r#"
fn foo(x) { x + 1 }
is_def_fn("bar", 1)
"#
)?);
assert!(!engine.eval::<bool>(
r#"
fn foo(x) { x + 1 }
is_def_fn("foo", 0)
"#
)?);
Ok(())
}

View File

@ -128,3 +128,36 @@ fn test_get_set_chain() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
fn test_get_set_op_assignment() -> Result<(), Box<EvalAltResult>> {
#[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::<Num>()
.register_fn("new_ts", || Num(40))
.register_get_set("v", Num::get, Num::set);
assert_eq!(
engine.eval::<Num>("let a = new_ts(); a.v = a.v + 2; a")?,
Num(42)
);
assert_eq!(
engine.eval::<Num>("let a = new_ts(); a.v += 2; a")?,
Num(42)
);
Ok(())
}

View File

@ -18,11 +18,54 @@ fn test_internal_fn() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>("fn bob() { return 4; 5 } bob()")?, 4); assert_eq!(engine.eval::<INT>("fn bob() { return 4; 5 } bob()")?, 4);
assert_eq!(engine.eval::<INT>("fn add(x, n) { x + n } add(40, 2)")?, 42);
assert_eq!(
engine.eval::<INT>("fn add(x, n,) { x + n } add(40, 2,)")?,
42
);
assert_eq!(
engine.eval::<INT>("fn add(x, n) { x + n } let a = 40; add(a, 2); a")?,
40
);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>("fn add(n) { this + n } let x = 40; x.add(2)")?,
42
);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>("fn add(n) { this += n; } let x = 40; x.add(2); x")?,
42
);
assert_eq!(engine.eval::<INT>("fn mul2(x) { x * 2 } mul2(21)")?, 42);
assert_eq!(
engine.eval::<INT>("fn mul2(x) { x *= 2 } let a = 21; mul2(a); a")?,
21
);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>("fn mul2() { this * 2 } let x = 21; x.mul2()")?,
42
);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>("fn mul2() { this *= 2; } let x = 21; x.mul2(); x")?,
42
);
Ok(()) Ok(())
} }
#[test] #[test]
fn test_big_internal_fn() -> Result<(), Box<EvalAltResult>> { fn test_internal_fn_big() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
assert_eq!( assert_eq!(
@ -73,3 +116,168 @@ fn test_internal_fn_overloading() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
fn test_internal_fn_params() -> Result<(), Box<EvalAltResult>> {
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<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(engine.eval::<String>(r#"type_of(Fn("abc"))"#)?, "Fn");
assert_eq!(
engine.eval::<INT>(
r#"
fn foo(x) { 40 + x }
let f = Fn("foo");
call(f, 2)
"#
)?,
42
);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>(
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::<INT>(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::<INT>(
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::<INT>(
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<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(
engine.eval::<INT>(
r#"
fn foo(y) { x += y; x }
let x = 41;
let y = 999;
foo!(1) + x
"#
)?,
83
);
assert!(engine
.eval::<INT>(
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<EvalAltResult>> {
let engine = Engine::new();
assert!(engine.eval::<bool>(
r#"
fn foo(x) { x + 1 }
is_def_fn("foo", 1)
"#
)?);
assert!(!engine.eval::<bool>(
r#"
fn foo(x) { x + 1 }
is_def_fn("bar", 1)
"#
)?);
assert!(!engine.eval::<bool>(
r#"
fn foo(x) { x + 1 }
is_def_fn("foo", 0)
"#
)?);
Ok(())
}

View File

@ -1,8 +1,24 @@
use rhai::{Dynamic, Engine, EvalAltResult, NativeCallContext, INT}; use rhai::{Dynamic, Engine, EvalAltResult, NativeCallContext, INT};
use std::any::TypeId; use std::any::TypeId;
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "unchecked"))]
#[test] #[test]
fn test_native_context() -> Result<(), Box<EvalAltResult>> { fn test_native_context() -> Result<(), Box<EvalAltResult>> {
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::<INT>("test(2)")?, 42);
Ok(())
}
#[test]
fn test_native_context_fn_name() -> Result<(), Box<EvalAltResult>> {
fn add_double( fn add_double(
context: NativeCallContext, context: NativeCallContext,
args: &mut [&mut Dynamic], args: &mut [&mut Dynamic],
@ -21,14 +37,14 @@ fn test_native_context() -> Result<(), Box<EvalAltResult>> {
add_double, add_double,
) )
.register_raw_fn( .register_raw_fn(
"adbl", "append_x2",
&[TypeId::of::<INT>(), TypeId::of::<INT>()], &[TypeId::of::<INT>(), TypeId::of::<INT>()],
add_double, add_double,
); );
assert_eq!(engine.eval::<String>("add_double(40, 1)")?, "add_double_42"); assert_eq!(engine.eval::<String>("add_double(40, 1)")?, "add_double_42");
assert_eq!(engine.eval::<String>("adbl(40, 1)")?, "adbl_42"); assert_eq!(engine.eval::<String>("append_x2(40, 1)")?, "append_x2_42");
Ok(()) Ok(())
} }

View File

@ -222,6 +222,41 @@ fn test_string_substring() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[cfg(not(feature = "no_object"))]
#[test]
fn test_string_format() -> Result<(), Box<EvalAltResult>> {
#[derive(Debug, Clone)]
struct TestStruct {
field: i64,
}
let mut engine = Engine::new();
engine
.register_type_with_name::<TestStruct>("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::<String>(r#"let x = new_ts(); "foo" + x"#)?,
"fooTS=42"
);
assert_eq!(
engine.eval::<String>(r#"let x = new_ts(); x + "foo""#)?,
"TS=42foo"
);
#[cfg(not(feature = "no_index"))]
assert_eq!(
engine.eval::<String>(r#"let x = [new_ts()]; "foo" + x"#)?,
"foo[!!!TS=42!!!]"
);
Ok(())
}
#[test] #[test]
fn test_string_fn() -> Result<(), Box<EvalAltResult>> { fn test_string_fn() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();