diff --git a/Cargo.toml b/Cargo.toml index e433e996..2b12fb19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ categories = [ "no-std", "embedded", "wasm", "parser-implementations" ] [dependencies] smallvec = { version = "1.6", default-features = false, features = ["union"] } -ahash = { version = "0.5", default-features = false } +ahash = { version = "0.6", default-features = false } rhai_codegen = { version = "0.3", path = "codegen" } [features] diff --git a/README.md b/README.md index 83428cf0..cfd6da40 100644 --- a/README.md +++ b/README.md @@ -28,22 +28,22 @@ Supported targets and builds Standard features ----------------- -* Easy-to-use language similar to JavaScript+Rust with dynamic typing. -* Fairly low compile-time overhead. -* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM). +* Simple language similar to JavaScript+Rust with dynamic typing. +* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single-core, 2.3 GHz Linux VM). * Tight integration with native Rust [functions](https://rhaiscript.github.io/book/rust/functions.html) and [types]([#custom-types-and-methods](https://rhaiscript.github.io/book/rust/custom.html)), including [getters/setters](https://rhaiscript.github.io/book/rust/getters-setters.html), [methods](https://rhaiscript.github.io/book/rust/custom.html) and [indexers](https://rhaiscript.github.io/book/rust/indexers.html). * Freely pass Rust variables/constants into a script via an external [`Scope`](https://rhaiscript.github.io/book/rust/scope.html) - all clonable Rust types are supported; no need to implement any special trait. * Easily [call a script-defined function](https://rhaiscript.github.io/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) and [`ahash`](https://crates.io/crates/ahash)). * Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature). -* Scripts are [optimized](https://rhaiscript.github.io/book/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations. +* Compile once to AST form for repeated evaluations. +* Scripts are [optimized](https://rhaiscript.github.io/book/engine/optimize.html) (useful for template-based machine-generated scripts). * Easy custom API development via [plugins](https://rhaiscript.github.io/book/plugins/index.html) system powered by procedural macros. * [Function overloading](https://rhaiscript.github.io/book/language/overload.html) and [operator overloading](https://rhaiscript.github.io/book/rust/operators.html). * Dynamic dispatch via [function pointers](https://rhaiscript.github.io/book/language/fn-ptr.html) with additional support for [currying](https://rhaiscript.github.io/book/language/fn-curry.html). * [Closures](https://rhaiscript.github.io/book/language/fn-closure.html) (anonymous functions) that can capture shared values. * Some syntactic support for [object-oriented programming (OOP)](https://rhaiscript.github.io/book/language/oop.html). -* Organize code base with dynamically-loadable [modules](https://rhaiscript.github.io/book/language/modules.html). +* Organize code base with dynamically-loadable [modules](https://rhaiscript.github.io/book/language/modules.html), optionally overriding the resolution process * Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature). * Support for [minimal builds](https://rhaiscript.github.io/book/start/builds/minimal.html) by excluding unneeded language [features](https://rhaiscript.github.io/book/start/features.html). diff --git a/RELEASES.md b/RELEASES.md index 4fcc5001..904627bc 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -4,20 +4,33 @@ Rhai Release Notes Version 0.19.11 =============== +This version streamlines compiling for WASM. + +Rust compiler minimum version is raised to 1.49. + Breaking changes ---------------- -Rust compiler requirement raised to 1.49. +* Rust compiler requirement raised to 1.49. +* `NativeCallContext::new` taker an additional parameter containing the name of the function called. Bug fixes --------- +* Parameters passed to plugin module functions were sometimes erroneously consumed. This is now fixed. * Fixes compilation errors in `metadata` feature build. +New features +------------ + +* Two new features, `wasm-bindgen` and `stdweb`, to specify the JS interop layer for WASM builds. `wasm-bindgen` used to be required. + Enhancements ------------ * `ahash` is used to hash function call parameters. This should yield speed improvements. +* `Dynamic` and `ImmutableString` now implement `serde::Serialize` and `serde::Deserialize`. +* `NativeCallContext` has a new field containing the name of the function called, useful when the same Rust function is registered under multiple names in Rhai. Version 0.19.10 diff --git a/src/engine.rs b/src/engine.rs index c6dce576..150a8eb5 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1971,12 +1971,16 @@ impl Engine { let args = &mut [lhs_ptr_inner, &mut rhs_val]; // Overriding exact implementation - let source = source.or_else(|| state.source.as_ref()); + let source = + source.or_else(|| state.source.as_ref()).map(|s| s.as_str()); if func.is_plugin_fn() { func.get_plugin_fn() - .call((self, source, &*mods, lib).into(), args)?; + .call((self, op.as_ref(), source, &*mods, lib).into(), args)?; } else { - func.get_native_fn()((self, source, &*mods, lib).into(), args)?; + func.get_native_fn()( + (self, op.as_ref(), source, &*mods, lib).into(), + args, + )?; } } // Built-in op-assignment function diff --git a/src/fn_call.rs b/src/fn_call.rs index 5ee9387f..7e277d77 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -211,12 +211,13 @@ impl Engine { state.source.as_ref() } else { source.as_ref() - }; + } + .map(|s| s.as_str()); let result = if func.is_plugin_fn() { func.get_plugin_fn() - .call((self, source, mods, lib).into(), args) + .call((self, fn_name, source, mods, lib).into(), args) } else { - func.get_native_fn()((self, source, mods, lib).into(), args) + func.get_native_fn()((self, fn_name, source, mods, lib).into(), args) }; // Restore the original reference @@ -1209,16 +1210,18 @@ impl Engine { r => r, }; + // Clone first argument if the function is not a method after-all + if let Some(first) = first_arg_value { + if !func.map(|f| f.is_method()).unwrap_or(true) { + let first_val = args[0].clone(); + args[0] = first; + *args[0] = first_val; + } + } + match func { #[cfg(not(feature = "no_function"))] Some(f) if f.is_script() => { - // Clone first argument - if let Some(first) = first_arg_value { - let first_val = args[0].clone(); - args[0] = first; - *args[0] = first_val; - } - let args = args.as_mut(); let new_scope = &mut Default::default(); let fn_def = f.get_fn_def().clone(); @@ -1236,22 +1239,14 @@ impl Engine { result } - Some(f) if f.is_plugin_fn() => f - .get_plugin_fn() - .clone() - .call((self, module.id_raw(), &*mods, lib).into(), args.as_mut()), - Some(f) if f.is_native() => { - if !f.is_method() { - // Clone first argument - if let Some(first) = first_arg_value { - let first_val = args[0].clone(); - args[0] = first; - *args[0] = first_val; - } - } - - f.get_native_fn()((self, module.id_raw(), &*mods, lib).into(), args.as_mut()) - } + Some(f) if f.is_plugin_fn() => f.get_plugin_fn().clone().call( + (self, fn_name, module.id(), &*mods, lib).into(), + args.as_mut(), + ), + Some(f) if f.is_native() => f.get_native_fn()( + (self, fn_name, module.id(), &*mods, lib).into(), + args.as_mut(), + ), Some(f) => unreachable!("unknown function type: {:?}", f), None if def_val.is_some() => Ok(def_val.unwrap().clone()), None => EvalAltResult::ErrorFunctionNotFound( diff --git a/src/fn_native.rs b/src/fn_native.rs index 6144ac5e..7c69a768 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -55,48 +55,52 @@ pub type Locked = crate::stdlib::sync::RwLock; /// Context of a native Rust function call. #[derive(Debug, Copy, Clone)] -pub struct NativeCallContext<'e, 's, 'a, 'm, 'pm: 'm> { +pub struct NativeCallContext<'e, 'n, 's, 'a, 'm, 'pm: 'm> { engine: &'e Engine, + fn_name: &'n str, source: Option<&'s str>, pub(crate) mods: Option<&'a Imports>, pub(crate) lib: &'m [&'pm Module], } -impl<'e, 's, 'a, 'm, 'pm: 'm, M: AsRef<[&'pm Module]> + ?Sized> - From<(&'e Engine, Option<&'s ImmutableString>, &'a Imports, &'m M)> - for NativeCallContext<'e, 's, 'a, 'm, 'pm> +impl<'e, 'n, 's, 'a, 'm, 'pm: 'm, M: AsRef<[&'pm Module]> + ?Sized> + From<(&'e Engine, &'n str, Option<&'s str>, &'a Imports, &'m M)> + for NativeCallContext<'e, 'n, 's, 'a, 'm, 'pm> { #[inline(always)] - fn from(value: (&'e Engine, Option<&'s ImmutableString>, &'a Imports, &'m M)) -> Self { + fn from(value: (&'e Engine, &'n str, Option<&'s str>, &'a Imports, &'m M)) -> Self { Self { engine: value.0, - source: value.1.map(|s| s.as_str()), - mods: Some(value.2), - lib: value.3.as_ref(), + fn_name: value.1, + source: value.2, + mods: Some(value.3), + lib: value.4.as_ref(), } } } -impl<'e, 'm, 'pm: 'm, M: AsRef<[&'pm Module]> + ?Sized> From<(&'e Engine, &'m M)> - for NativeCallContext<'e, '_, '_, 'm, 'pm> +impl<'e, 'n, 'm, 'pm: 'm, M: AsRef<[&'pm Module]> + ?Sized> From<(&'e Engine, &'n str, &'m M)> + for NativeCallContext<'e, 'n, '_, '_, 'm, 'pm> { #[inline(always)] - fn from(value: (&'e Engine, &'m M)) -> Self { + fn from(value: (&'e Engine, &'n str, &'m M)) -> Self { Self { engine: value.0, + fn_name: value.1, source: None, mods: None, - lib: value.1.as_ref(), + lib: value.2.as_ref(), } } } -impl<'e, 's, 'a, 'm, 'pm> NativeCallContext<'e, 's, 'a, 'm, 'pm> { +impl<'e, 'n, 's, 'a, 'm, 'pm> NativeCallContext<'e, 'n, 's, 'a, 'm, 'pm> { /// Create a new [`NativeCallContext`]. #[inline(always)] - pub fn new(engine: &'e Engine, lib: &'m impl AsRef<[&'pm Module]>) -> Self { + pub fn new(engine: &'e Engine, fn_name: &'n str, lib: &'m impl AsRef<[&'pm Module]>) -> Self { Self { engine, + fn_name, source: None, mods: None, lib: lib.as_ref(), @@ -109,13 +113,15 @@ impl<'e, 's, 'a, 'm, 'pm> NativeCallContext<'e, 's, 'a, 'm, 'pm> { #[inline(always)] pub fn new_with_all_fields( engine: &'e Engine, - source: &'s Option, + fn_name: &'n str, + source: &'s Option<&str>, imports: &'a mut Imports, lib: &'m impl AsRef<[&'pm Module]>, ) -> Self { Self { engine, - source: source.as_ref().map(|s| s.as_str()), + fn_name, + source: source.clone(), mods: Some(imports), lib: lib.as_ref(), } @@ -125,6 +131,11 @@ impl<'e, 's, 'a, 'm, 'pm> NativeCallContext<'e, 's, 'a, 'm, 'pm> { pub fn engine(&self) -> &Engine { self.engine } + /// Name of the function called. + #[inline(always)] + pub fn fn_name(&self) -> &str { + self.fn_name + } /// The current source. #[inline(always)] pub fn source(&self) -> Option<&str> { diff --git a/src/optimize.rs b/src/optimize.rs index 2cc27819..6a0864f0 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -327,10 +327,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { *stmt = if preserve_result { // -> { expr, Noop } - let mut statements = Vec::new(); - statements.push(Stmt::Expr(expr)); - statements.push(mem::take(&mut x.0)); - Stmt::Block(statements, pos) + Stmt::Block(vec![Stmt::Expr(expr), mem::take(&mut x.0)], pos) } else { // -> expr Stmt::Expr(expr) @@ -415,8 +412,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { Stmt::Break(pos) => { // Only a single break statement - turn into running the guard expression once state.set_dirty(); - let mut statements = Vec::new(); - statements.push(Stmt::Expr(mem::take(condition))); + let mut statements = vec![Stmt::Expr(mem::take(condition))]; if preserve_result { statements.push(Stmt::Noop(pos)) } diff --git a/src/serde_impl/serialize.rs b/src/serde_impl/serialize.rs index bdab788f..49dbf71a 100644 --- a/src/serde_impl/serialize.rs +++ b/src/serde_impl/serialize.rs @@ -1,6 +1,6 @@ //! Implementations of [`serde::Serialize`]. -use crate::dynamic::Union; +use crate::dynamic::{Union, Variant}; use crate::stdlib::string::ToString; use crate::{Dynamic, ImmutableString}; use serde::ser::{Serialize, SerializeMap, Serializer}; @@ -34,7 +34,7 @@ impl Serialize for Dynamic { } Union::FnPtr(f, _) => ser.serialize_str(f.fn_name()), #[cfg(not(feature = "no_std"))] - Union::TimeStamp(_, _) => unimplemented!("serialization of timestamp is not supported"), + Union::TimeStamp(x, _) => ser.serialize_str(x.as_ref().type_name()), Union::Variant(v, _) => ser.serialize_str((***v).type_name()), diff --git a/tests/closures.rs b/tests/closures.rs index 4a0cd74b..3f9fcfa2 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -286,7 +286,8 @@ fn test_closures_external() -> Result<(), Box> { let lib = [ast.as_ref()]; // Create native call context - let context = NativeCallContext::new(&engine, &lib); + let fn_name = fn_ptr.fn_name().to_string(); + let context = NativeCallContext::new(&engine, &fn_name, &lib); // Closure 'f' captures: the engine, the AST, and the curried function pointer let f = move |x: INT| fn_ptr.call_dynamic(context, None, [x.into()]); diff --git a/tests/plugins.rs b/tests/plugins.rs index 4ba79f6f..e42c4c73 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -101,3 +101,32 @@ fn test_plugins_package() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_plugins_parameters() -> Result<(), Box> { + #[export_module] + mod rhai_std { + use rhai::*; + + pub fn noop(_: &str) {} + } + + let mut engine = Engine::new(); + + let std = exported_module!(rhai_std); + + engine.register_static_module("std", std.into()); + + assert_eq!( + engine.eval::( + r#" + let s = "hello"; + std::noop(s); + s + "# + )?, + "hello" + ); + + Ok(()) +}