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 ![GitHub last commit](https://img.shields.io/github/last-commit/rhaiscript/rhai?logo=github) [![Build Status](https://github.com/rhaiscript/rhai/workflows/Build/badge.svg)](https://github.com/rhaiscript/rhai/actions) +[![stars](https://img.shields.io/github/stars/rhaiscript/rhai?logo=github)](https://github.com/rhaiscript/rhai) [![license](https://img.shields.io/crates/l/rhai)](https://github.com/license/rhaiscript/rhai) [![crates.io](https://img.shields.io/crates/v/rhai?logo=rust)](https://crates.io/crates/rhai/) [![crates.io](https://img.shields.io/crates/d/rhai?logo=rust)](https://crates.io/crates/rhai/) @@ -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();