Merge pull request #673 from schungx/master

Fix serde for byte arrays.
This commit is contained in:
Stephen Chung 2022-11-22 16:18:17 +08:00 committed by GitHub
commit b25bc91b13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 1406 additions and 1260 deletions

View File

@ -1,10 +1,31 @@
Rhai Release Notes
==================
Version 1.12.0
==============
Net features
------------
### `Engine::call_fn_with_options`
* `Engine::call_fn_raw` is deprecated in favor of `Engine::call_fn_with_options` which allows setting options for the function call.
* The options are for future-proofing the API.
* In this version, it gains the ability to set the value of the _custom state_ (accessible via `NativeCallContext::tag`) for a function evaluation, overriding `Engine::set_default_tag`.
Enhancements
------------
* `CallableFunction` is exported under `internals`.
* The `TypeBuilder` type and `CustomType` trait are no longer marked as volatile.
* `FuncArgs` is also implemented for arrays.
* `Engine::set_XXX` API can now be chained.
Version 1.11.0
==============
Speed Improvements
Speed improvements
------------------
* Due to a code refactor, built-in operators for standard types now run even faster, in certain cases by 20-30%.

View File

@ -3,7 +3,7 @@ members = [".", "codegen"]
[package]
name = "rhai"
version = "1.11.0"
version = "1.12.0"
rust-version = "1.61.0"
edition = "2018"
resolver = "2"
@ -37,7 +37,7 @@ getrandom = { version = "0.2", optional = true }
rustyline = { version = "10", optional = true }
[dev-dependencies]
serde_bytes = "0.11"
rmp-serde = "1.1"
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
[features]

View File

@ -33,7 +33,7 @@ Standard features
-----------------
* Simple language similar to JavaScript+Rust with [dynamic](https://rhai.rs/book/language/dynamic.html) typing.
* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single-core, 2.3 GHz Linux VM).
* Fairly efficient evaluation (1 million iterations in 0.23 sec on a single-core, 2.3 GHz Linux VM).
* Tight integration with native Rust [functions](https://rhai.rs/book/rust/functions.html) and [types](https://rhai.rs/book/rust/custom-types.html), including [getters/setters](https://rhai.rs/book/rust/getters-setters.html), [methods](https://rhai.rs/book/rust/methods.html) and [indexers](https://rhai.rs/book/rust/indexers.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/engine/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) (including packed [byte arrays](https://rhai.rs/book/language/blobs.html)) and [object maps](https://rhai.rs/book/language/object-maps.html).

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -8,7 +8,7 @@ pub fn main() {
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_object"))]
pub fn main() {
use rhai::{Dynamic, Engine, Map, Scope, AST};
use rhai::{CallFnOptions, Dynamic, Engine, ImmutableString, Map, Scope, AST};
use std::io::{stdin, stdout, Write};
const SCRIPT_FILE: &str = "event_handler_js/script.rhai";
@ -98,7 +98,12 @@ pub fn main() {
println!();
// Run the 'init' function to initialize the state, retaining variables.
let result = engine.call_fn_raw(&mut scope, &ast, false, true, "init", Some(&mut states), []);
let options = CallFnOptions::new()
.eval_ast(false)
.bind_this_ptr(&mut states);
let result = engine.call_fn_with_options::<()>(options, &mut scope, &ast, "init", ());
if let Err(err) = result {
eprintln!("! {err}")
@ -124,7 +129,7 @@ pub fn main() {
let mut fields = input.trim().splitn(2, ' ');
let event = fields.next().expect("event").trim();
let arg = fields.next().unwrap_or("");
let arg = fields.next().unwrap_or("").to_string();
// Process event
match event {
@ -146,10 +151,11 @@ pub fn main() {
let engine = &handler.engine;
let scope = &mut handler.scope;
let ast = &handler.ast;
let this_ptr = Some(&mut handler.states);
let options = CallFnOptions::new()
.eval_ast(false)
.bind_this_ptr(&mut handler.states);
let result =
engine.call_fn_raw(scope, ast, false, true, event, this_ptr, [arg.into()]);
let result = engine.call_fn_with_options::<()>(options, scope, ast, event, (arg,));
if let Err(err) = result {
eprintln!("! {err}")

View File

@ -7,7 +7,7 @@ pub fn main() {
#[cfg(not(feature = "no_function"))]
pub fn main() {
use rhai::{Dynamic, Engine, Scope, AST};
use rhai::{CallFnOptions, Dynamic, Engine, Scope, AST};
use std::io::{stdin, stdout, Write};
const SCRIPT_FILE: &str = "event_handler_main/script.rhai";
@ -86,7 +86,9 @@ pub fn main() {
println!();
// Run the 'init' function to initialize the state, retaining variables.
let result = engine.call_fn_raw(&mut scope, &ast, false, false, "init", None, []);
let options = CallFnOptions::new().eval_ast(false).rewind_scope(false);
let result = engine.call_fn_with_options::<()>(options, &mut scope, &ast, "init", ());
if let Err(err) = result {
eprintln!("! {err}")
@ -107,7 +109,7 @@ pub fn main() {
let mut fields = input.trim().splitn(2, ' ');
let event = fields.next().expect("event").trim();
let arg = fields.next().unwrap_or("");
let arg = fields.next().unwrap_or("").to_string();
// Process event
match event {
@ -124,7 +126,7 @@ pub fn main() {
let scope = &mut handler.scope;
let ast = &handler.ast;
let result = engine.call_fn::<()>(scope, ast, event, (arg.to_string(),));
let result = engine.call_fn::<()>(scope, ast, event, (arg,));
if let Err(err) = result {
eprintln!("! {err}")

View File

@ -121,7 +121,7 @@ pub fn main() {
let mut fields = input.trim().splitn(2, ' ');
let event = fields.next().expect("event").trim();
let arg = fields.next().unwrap_or("");
let arg = fields.next().unwrap_or("").to_string();
// Process event
match event {
@ -138,7 +138,7 @@ pub fn main() {
let scope = &mut handler.scope;
let ast = &handler.ast;
let result = engine.call_fn::<()>(scope, ast, event, (arg.to_string(),));
let result = engine.call_fn::<()>(scope, ast, event, (arg,));
if let Err(err) = result {
eprintln!("! {err}")

View File

@ -1,6 +1,4 @@
//! Trait to build a custom type for use with [`Engine`].
#![allow(deprecated)]
use crate::{types::dynamic::Variant, Engine, Identifier, RegisterNativeFunction};
use std::marker::PhantomData;
#[cfg(feature = "no_std")]
@ -12,10 +10,6 @@ use crate::func::register::Mut;
/// Trait to build the API of a custom type for use with an [`Engine`]
/// (i.e. register the type and its getters, setters, methods, etc.).
///
/// # WARNING - Volatile Trait
///
/// This API is volatile and may change in the future.
///
/// # Example
///
/// ```
@ -66,7 +60,6 @@ use crate::func::register::Mut;
/// # Ok(())
/// # }
/// ```
#[deprecated = "This trait is NOT deprecated, but it is considered volatile and may change in the future."]
pub trait CustomType: Variant + Clone {
/// Builds the custom type for use with the [`Engine`].
///
@ -78,10 +71,6 @@ impl Engine {
/// Build the API of a custom type for use with the [`Engine`].
///
/// The custom type must implement [`CustomType`].
///
/// # WARNING - Unstable API
///
/// This API is volatile and may change in the future.
#[inline]
pub fn build_type<T: CustomType>(&mut self) -> &mut Self {
T::build(TypeBuilder::new(self));
@ -99,11 +88,6 @@ impl Engine {
///
/// To define a pretty-print name, call [`with_name`][`TypeBuilder::with_name`],
/// to use [`Engine::register_type_with_name`] instead.
///
/// # WARNING - Volatile Type
///
/// This type is volatile and may change in the future.
#[deprecated = "This type is NOT deprecated, but it is considered volatile and may change in the future."]
pub struct TypeBuilder<'a, T: Variant + Clone> {
engine: &'a mut Engine,
name: Option<&'static str>,

View File

@ -8,9 +8,70 @@ use crate::{
reify, Dynamic, Engine, FuncArgs, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, AST,
ERR,
};
use std::any::{type_name, TypeId};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
any::{type_name, TypeId},
mem,
};
/// Options for calling a script-defined function via [`Engine::call_fn_with_options`].
#[derive(Debug, Hash)]
#[non_exhaustive]
pub struct CallFnOptions<'t> {
/// A value for binding to the `this` pointer (if any).
pub this_ptr: Option<&'t mut Dynamic>,
/// The custom state of this evaluation run (if any), overrides [`Engine::default_tag`].
pub tag: Option<Dynamic>,
/// Evaluate the [`AST`] to load necessary modules before calling the function? Default `true`.
pub eval_ast: bool,
/// Rewind the [`Scope`] after the function call? Default `true`.
pub rewind_scope: bool,
}
impl Default for CallFnOptions<'_> {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
impl<'a> CallFnOptions<'a> {
/// Create a default [`CallFnOptions`].
#[inline(always)]
pub fn new() -> Self {
Self {
this_ptr: None,
tag: None,
eval_ast: true,
rewind_scope: true,
}
}
/// Bind to the `this` pointer.
#[inline(always)]
pub fn bind_this_ptr(mut self, value: &'a mut Dynamic) -> Self {
self.this_ptr = Some(value);
self
}
/// Set the custom state of this evaluation run (if any).
#[inline(always)]
pub fn with_tag(mut self, value: impl Variant + Clone) -> Self {
self.tag = Some(Dynamic::from(value));
self
}
/// Set whether to evaluate the [`AST`] to load necessary modules before calling the function.
#[inline(always)]
pub const fn eval_ast(mut self, value: bool) -> Self {
self.eval_ast = value;
self
}
/// Set whether to rewind the [`Scope`] after the function call.
#[inline(always)]
pub const fn rewind_scope(mut self, value: bool) -> Self {
self.rewind_scope = value;
self
}
}
impl Engine {
/// Call a script function defined in an [`AST`] with multiple arguments.
@ -19,15 +80,12 @@ impl Engine {
///
/// The [`AST`] is evaluated before calling the function.
/// This allows a script to load the necessary modules.
/// This is usually desired. If not, a specialized [`AST`] can be prepared that contains only
/// function definitions without any body script via [`AST::clear_statements`].
/// This is usually desired. If not, use [`call_fn_with_options`] instead.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # #[cfg(not(feature = "no_function"))]
/// # {
/// use rhai::{Engine, Scope};
///
/// let engine = Engine::new();
@ -51,30 +109,83 @@ impl Engine {
///
/// let result = engine.call_fn::<i64>(&mut scope, &ast, "bar", () )?;
/// assert_eq!(result, 21);
/// # }
/// # Ok(())
/// # }
/// ```
#[inline]
#[inline(always)]
pub fn call_fn<T: Variant + Clone>(
&self,
scope: &mut Scope,
ast: &AST,
name: impl AsRef<str>,
args: impl FuncArgs,
) -> RhaiResultOf<T> {
self.call_fn_with_options(Default::default(), scope, ast, name, args)
}
/// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments.
///
/// Options are provided via the [`CallFnOptions`] type.
/// This is an advanced API.
///
/// Not available under `no_function`.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// use rhai::{Engine, Scope, Dynamic, CallFnOptions};
///
/// let engine = Engine::new();
///
/// let ast = engine.compile("
/// fn action(x) { this += x; } // function using 'this' pointer
/// fn decl(x) { let hello = x; } // declaring variables
/// ")?;
///
/// let mut scope = Scope::new();
/// scope.push("foo", 42_i64);
///
/// // Binding the 'this' pointer
/// let mut value = 1_i64.into();
/// let options = CallFnOptions::new().bind_this_ptr(&mut value);
///
/// engine.call_fn_with_options(options, &mut scope, &ast, "action", ( 41_i64, ))?;
/// assert_eq!(value.as_int().unwrap(), 42);
///
/// // Do not rewind scope
/// let options = CallFnOptions::default().rewind_scope(false);
///
/// engine.call_fn_with_options(options, &mut scope, &ast, "decl", ( 42_i64, ))?;
/// assert_eq!(scope.get_value::<i64>("hello").unwrap(), 42);
/// # Ok(())
/// # }
/// ```
#[inline(always)]
pub fn call_fn_with_options<T: Variant + Clone>(
&self,
options: CallFnOptions,
scope: &mut Scope,
ast: &AST,
name: impl AsRef<str>,
args: impl FuncArgs,
) -> RhaiResultOf<T> {
let mut arg_values = StaticVec::new_const();
args.parse(&mut arg_values);
let result = self.call_fn_raw(scope, ast, true, true, name, None, arg_values)?;
let result = self._call_fn(
options,
scope,
&mut GlobalRuntimeState::new(self),
&mut Caches::new(),
ast,
name.as_ref(),
arg_values.as_mut(),
)?;
// Bail out early if the return type needs no cast
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
return Ok(reify!(result => T));
}
if TypeId::of::<T>() == TypeId::of::<()>() {
return Ok(reify!(() => T));
}
// Cast return type
let typ = self.map_type_name(result.type_name());
@ -86,120 +197,6 @@ impl Engine {
}
/// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments.
///
/// The following options are available:
///
/// * whether to evaluate the [`AST`] to load necessary modules before calling the function
/// * whether to rewind the [`Scope`] after the function call
/// * a value for binding to the `this` pointer (if any)
///
/// Not available under `no_function`.
///
/// # WARNING - Low Level API
///
/// This function is very low level.
///
/// # Arguments
///
/// All the arguments are _consumed_, meaning that they're replaced by `()`.
/// This is to avoid unnecessarily cloning the arguments.
///
/// Do not use the arguments after this call. If they are needed afterwards, clone them _before_
/// calling this function.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # #[cfg(not(feature = "no_function"))]
/// # {
/// use rhai::{Engine, Scope, Dynamic};
///
/// let engine = Engine::new();
///
/// let ast = engine.compile("
/// fn add(x, y) { len(x) + y + foo }
/// fn add1(x) { len(x) + 1 + foo }
/// fn bar() { foo/2 }
/// fn action(x) { this += x; } // function using 'this' pointer
/// fn decl(x) { let hello = x; } // declaring variables
/// ")?;
///
/// let mut scope = Scope::new();
/// scope.push("foo", 42_i64);
///
/// // Call the script-defined function
/// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "add", None, [ "abc".into(), 123_i64.into() ])?;
/// // ^^^^ no 'this' pointer
/// assert_eq!(result.cast::<i64>(), 168);
///
/// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "add1", None, [ "abc".into() ])?;
/// assert_eq!(result.cast::<i64>(), 46);
///
/// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "bar", None, [])?;
/// assert_eq!(result.cast::<i64>(), 21);
///
/// let mut value = 1_i64.into();
/// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "action", Some(&mut value), [ 41_i64.into() ])?;
/// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer
/// assert_eq!(value.as_int().unwrap(), 42);
///
/// engine.call_fn_raw(&mut scope, &ast, true, false, "decl", None, [ 42_i64.into() ])?;
/// // ^^^^^ do not rewind scope
/// assert_eq!(scope.get_value::<i64>("hello").unwrap(), 42);
/// # }
/// # Ok(())
/// # }
/// ```
#[inline(always)]
pub fn call_fn_raw(
&self,
scope: &mut Scope,
ast: &AST,
eval_ast: bool,
rewind_scope: bool,
name: impl AsRef<str>,
this_ptr: Option<&mut Dynamic>,
arg_values: impl AsMut<[Dynamic]>,
) -> RhaiResult {
let mut arg_values = arg_values;
self._call_fn(
scope,
&mut GlobalRuntimeState::new(self),
&mut Caches::new(),
ast,
eval_ast,
rewind_scope,
name.as_ref(),
this_ptr,
arg_values.as_mut(),
)
}
/// _(internals)_ Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments.
/// Exported under the `internals` feature only.
///
/// The following options are available:
///
/// * whether to evaluate the [`AST`] to load necessary modules before calling the function
/// * whether to rewind the [`Scope`] after the function call
/// * a value for binding to the `this` pointer (if any)
///
/// Not available under `no_function`.
///
/// # WARNING - Unstable API
///
/// This API is volatile and may change in the future.
///
/// # WARNING - Low Level API
///
/// This function is _extremely_ low level.
///
/// A [`GlobalRuntimeState`] and [`Caches`] need to be passed into the function, which can be
/// created via [`GlobalRuntimeState::new`] and [`Caches::new`].
///
/// This makes repeatedly calling particular functions more efficient as the functions
/// resolution cache is kept intact.
///
/// # Arguments
///
/// All the arguments are _consumed_, meaning that they're replaced by `()`. This is to avoid
@ -207,56 +204,31 @@ impl Engine {
///
/// Do not use the arguments after this call. If they are needed afterwards, clone them _before_
/// calling this function.
#[cfg(feature = "internals")]
#[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."]
#[inline(always)]
pub fn call_fn_raw_raw(
pub(crate) fn _call_fn(
&self,
options: CallFnOptions,
scope: &mut Scope,
global: &mut GlobalRuntimeState,
caches: &mut Caches,
ast: &AST,
eval_ast: bool,
rewind_scope: bool,
name: &str,
this_ptr: Option<&mut Dynamic>,
arg_values: &mut [Dynamic],
) -> RhaiResult {
self._call_fn(
scope,
global,
caches,
ast,
eval_ast,
rewind_scope,
name,
this_ptr,
arg_values,
)
}
/// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments.
fn _call_fn(
&self,
scope: &mut Scope,
global: &mut GlobalRuntimeState,
caches: &mut Caches,
ast: &AST,
eval_ast: bool,
rewind_scope: bool,
name: &str,
this_ptr: Option<&mut Dynamic>,
arg_values: &mut [Dynamic],
) -> RhaiResult {
let statements = ast.statements();
let orig_lib_len = global.lib.len();
#[cfg(not(feature = "no_function"))]
let mut orig_tag = None;
if let Some(value) = options.tag {
orig_tag = Some(mem::replace(&mut global.tag, value));
}
global.lib.push(ast.shared_lib().clone());
let mut no_this_ptr = Dynamic::NULL;
let this_ptr = this_ptr.unwrap_or(&mut no_this_ptr);
let this_ptr = options.this_ptr.unwrap_or(&mut no_this_ptr);
#[cfg(not(feature = "no_module"))]
let orig_embedded_module_resolver = std::mem::replace(
@ -264,7 +236,9 @@ impl Engine {
ast.resolver().cloned(),
);
let result = if eval_ast && !statements.is_empty() {
let rewind_scope = options.rewind_scope;
let result = if options.eval_ast && !statements.is_empty() {
let orig_scope_len = scope.len();
let scope = &mut *RestoreOnDrop::lock_if(rewind_scope, scope, move |s| {
s.rewind(orig_scope_len);
@ -308,6 +282,11 @@ impl Engine {
{
global.embedded_module_resolver = orig_embedded_module_resolver;
}
if let Some(value) = orig_tag {
global.tag = value;
}
global.lib.truncate(orig_lib_len);
result

View File

@ -117,10 +117,10 @@ impl Engine {
///
/// # Deprecated
///
/// This method is deprecated. Use [`run_ast_with_scope`][Engine::run_ast_with_scope] instead.
/// This method is deprecated. Use [`call_fn_with_options`][Engine::call_fn_with_options] instead.
///
/// This method will be removed in the next major version.
#[deprecated(since = "1.1.0", note = "use `call_fn_raw` instead")]
#[deprecated(since = "1.1.0", note = "use `call_fn_with_options` instead")]
#[cfg(not(feature = "no_function"))]
#[inline(always)]
pub fn call_fn_dynamic(
@ -132,8 +132,56 @@ impl Engine {
this_ptr: Option<&mut Dynamic>,
arg_values: impl AsMut<[Dynamic]>,
) -> RhaiResult {
#[allow(deprecated)]
self.call_fn_raw(scope, ast, eval_ast, true, name, this_ptr, arg_values)
}
/// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments.
///
/// The following options are available:
///
/// * whether to evaluate the [`AST`] to load necessary modules before calling the function
/// * whether to rewind the [`Scope`] after the function call
/// * a value for binding to the `this` pointer (if any)
///
/// Not available under `no_function`.
///
/// # Deprecated
///
/// This method is deprecated. Use [`call_fn_with_options`][Engine::call_fn_with_options] instead.
///
/// This method will be removed in the next major version.
#[deprecated(since = "1.12.0", note = "use `call_fn_with_options` instead")]
#[cfg(not(feature = "no_function"))]
#[inline(always)]
pub fn call_fn_raw(
&self,
scope: &mut Scope,
ast: &AST,
eval_ast: bool,
rewind_scope: bool,
name: impl AsRef<str>,
this_ptr: Option<&mut Dynamic>,
arg_values: impl AsMut<[Dynamic]>,
) -> RhaiResult {
let mut arg_values = arg_values;
let options = crate::CallFnOptions {
this_ptr,
eval_ast,
rewind_scope,
..Default::default()
};
self._call_fn(
options,
scope,
&mut crate::eval::GlobalRuntimeState::new(self),
&mut crate::eval::Caches::new(),
ast,
name.as_ref(),
arg_values.as_mut(),
)
}
/// Register a custom fallible function with the [`Engine`].
///
/// # Deprecated

View File

@ -4,11 +4,14 @@ use crate::eval::{Caches, GlobalRuntimeState};
use crate::parser::ParseState;
use crate::types::dynamic::Variant;
use crate::{
Dynamic, Engine, OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR,
reify, Dynamic, Engine, OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{any::type_name, mem};
use std::{
any::{type_name, TypeId},
mem,
};
impl Engine {
/// Evaluate a string as a script, returning the result value or an error.
@ -190,6 +193,11 @@ impl Engine {
let result = self.eval_ast_with_scope_raw(global, caches, scope, ast)?;
// Bail out early if the return type needs no cast
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
return Ok(reify!(result => T));
}
let typ = self.map_type_name(result.type_name());
result.try_cast::<T>().ok_or_else(|| {

View File

@ -102,7 +102,7 @@ impl Default for Limits {
impl Engine {
/// Is there a data size limit set?
#[inline]
#[inline(always)]
pub(crate) const fn has_data_size_limit(&self) -> bool {
self.limits.max_string_size.is_some()
|| {

View File

@ -68,8 +68,9 @@ impl Engine {
}
/// Set whether `if`-expression is allowed.
#[inline(always)]
pub fn set_allow_if_expression(&mut self, enable: bool) {
pub fn set_allow_if_expression(&mut self, enable: bool) -> &mut Self {
self.options.set(LangOptions::IF_EXPR, enable);
self
}
/// Is `switch` expression allowed?
/// Default is `true`.
@ -80,8 +81,9 @@ impl Engine {
}
/// Set whether `switch` expression is allowed.
#[inline(always)]
pub fn set_allow_switch_expression(&mut self, enable: bool) {
pub fn set_allow_switch_expression(&mut self, enable: bool) -> &mut Self {
self.options.set(LangOptions::SWITCH_EXPR, enable);
self
}
/// Are loop expressions allowed?
/// Default is `true`.
@ -92,8 +94,9 @@ impl Engine {
}
/// Set whether loop expressions are allowed.
#[inline(always)]
pub fn set_allow_loop_expressions(&mut self, enable: bool) {
pub fn set_allow_loop_expressions(&mut self, enable: bool) -> &mut Self {
self.options.set(LangOptions::LOOP_EXPR, enable);
self
}
/// Is statement-expression allowed?
/// Default is `true`.
@ -104,8 +107,9 @@ impl Engine {
}
/// Set whether statement-expression is allowed.
#[inline(always)]
pub fn set_allow_statement_expression(&mut self, enable: bool) {
pub fn set_allow_statement_expression(&mut self, enable: bool) -> &mut Self {
self.options.set(LangOptions::STMT_EXPR, enable);
self
}
/// Is anonymous function allowed?
/// Default is `true`.
@ -122,8 +126,9 @@ impl Engine {
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
#[inline(always)]
pub fn set_allow_anonymous_fn(&mut self, enable: bool) {
pub fn set_allow_anonymous_fn(&mut self, enable: bool) -> &mut Self {
self.options.set(LangOptions::ANON_FN, enable);
self
}
/// Is looping allowed?
/// Default is `true`.
@ -134,8 +139,9 @@ impl Engine {
}
/// Set whether looping is allowed.
#[inline(always)]
pub fn set_allow_looping(&mut self, enable: bool) {
pub fn set_allow_looping(&mut self, enable: bool) -> &mut Self {
self.options.set(LangOptions::LOOPING, enable);
self
}
/// Is variables shadowing allowed?
/// Default is `true`.
@ -146,8 +152,9 @@ impl Engine {
}
/// Set whether variables shadowing is allowed.
#[inline(always)]
pub fn set_allow_shadowing(&mut self, enable: bool) {
pub fn set_allow_shadowing(&mut self, enable: bool) -> &mut Self {
self.options.set(LangOptions::SHADOW, enable);
self
}
/// Is strict variables mode enabled?
/// Default is `false`.
@ -158,8 +165,9 @@ impl Engine {
}
/// Set whether strict variables mode is enabled.
#[inline(always)]
pub fn set_strict_variables(&mut self, enable: bool) {
pub fn set_strict_variables(&mut self, enable: bool) -> &mut Self {
self.options.set(LangOptions::STRICT_VAR, enable);
self
}
/// Raise error if an object map property does not exist?
/// Default is `false`.
@ -177,9 +185,10 @@ impl Engine {
/// Not available under `no_object`.
#[cfg(not(feature = "no_object"))]
#[inline(always)]
pub fn set_fail_on_invalid_map_property(&mut self, enable: bool) {
pub fn set_fail_on_invalid_map_property(&mut self, enable: bool) -> &mut Self {
self.options
.set(LangOptions::FAIL_ON_INVALID_MAP_PROPERTY, enable);
self
}
/// Is fast operators mode enabled?
/// Default is `false`.
@ -190,7 +199,8 @@ impl Engine {
}
/// Set whether fast operators mode is enabled.
#[inline(always)]
pub fn set_fast_operators(&mut self, enable: bool) {
pub fn set_fast_operators(&mut self, enable: bool) -> &mut Self {
self.options.set(LangOptions::FAST_OPS, enable);
self
}
}

View File

@ -624,6 +624,38 @@ impl Expr {
_ => None,
}
}
/// Get the [options][ASTFlags] of the expression.
#[inline]
#[must_use]
pub const fn options(&self) -> ASTFlags {
match self {
Self::Index(_, options, _) | Self::Dot(_, options, _) => *options,
#[cfg(not(feature = "no_float"))]
Self::FloatConstant(..) => ASTFlags::NONE,
Self::DynamicConstant(..)
| Self::BoolConstant(..)
| Self::IntegerConstant(..)
| Self::CharConstant(..)
| Self::Unit(..)
| Self::StringConstant(..)
| Self::Array(..)
| Self::Map(..)
| Self::Variable(..)
| Self::And(..)
| Self::Or(..)
| Self::Coalesce(..)
| Self::FnCall(..)
| Self::MethodCall(..)
| Self::InterpolatedString(..)
| Self::Property(..)
| Self::Stmt(..) => ASTFlags::NONE,
#[cfg(not(feature = "no_custom_syntax"))]
Self::Custom(..) => ASTFlags::NONE,
}
}
/// Get the [position][Position] of the expression.
#[inline]
#[must_use]

View File

@ -656,6 +656,34 @@ impl Stmt {
pub const fn is_noop(&self) -> bool {
matches!(self, Self::Noop(..))
}
/// Get the [options][ASTFlags] of this statement.
#[inline]
#[must_use]
pub const fn options(&self) -> ASTFlags {
match self {
Self::Do(_, options, _)
| Self::Var(_, options, _)
| Self::BreakLoop(_, options, _)
| Self::Return(_, options, _) => *options,
Self::Noop(..)
| Self::If(..)
| Self::Switch(..)
| Self::Block(..)
| Self::Expr(..)
| Self::FnCall(..)
| Self::While(..)
| Self::For(..)
| Self::TryCatch(..)
| Self::Assignment(..) => ASTFlags::NONE,
#[cfg(not(feature = "no_module"))]
Self::Import(..) | Self::Export(..) => ASTFlags::NONE,
#[cfg(not(feature = "no_closure"))]
Self::Share(..) => ASTFlags::NONE,
}
}
/// Get the [position][Position] of this statement.
#[must_use]
pub fn position(&self) -> Position {

View File

@ -2,7 +2,7 @@
//!
//! Set to [`None`] to disable stable hashing.
//!
//! See [`set_rhai_ahash_seed`].
//! See [`rhai::config::hashing::set_ahash_seed`][set_ahash_seed].
//!
//! # Example
//!
@ -189,7 +189,7 @@ static AHASH_SEED: SusLock<Option<[u64; 4]>> = SusLock::new();
/// # Warning
///
/// * You can only call this function **ONCE** for the entire duration of program execution.
/// * You **MUST** call this before performing **ANY** Rhai operation (e.g. creating an [`Engine`]).
/// * You **MUST** call this before performing **ANY** Rhai operation (e.g. creating an [`Engine`][crate::Engine]).
///
/// # Error
///
@ -216,7 +216,7 @@ pub fn set_ahash_seed(new_seed: Option<[u64; 4]>) -> Result<(), Option<[u64; 4]>
///
/// Otherwise, the hashing seed is randomized to protect against DOS attacks.
///
/// See [`set_rhai_ahash_seed`] for more.
/// See [`rhai::config::hashing::set_ahash_seed`][set_ahash_seed] for more.
#[inline]
#[must_use]
pub fn get_ahash_seed() -> &'static Option<[u64; 4]> {

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,8 @@
use super::GlobalRuntimeState;
use crate::types::dynamic::Union;
use crate::{Dynamic, Engine, Position, RhaiResult, RhaiResultOf, ERR};
use crate::{Dynamic, Engine, Position, RhaiResultOf, ERR};
use std::borrow::Borrow;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -70,6 +71,9 @@ impl Engine {
}
/// Raise an error if any data size exceeds limit.
///
/// [`Position`] in [`EvalAltResult`][crate::EvalAltResult] is always [`NONE`][Position::NONE]
/// and should be set afterwards.
pub(crate) fn raise_err_if_over_data_size_limit(
&self,
(_arr, _map, s): (usize, usize, usize),
@ -111,15 +115,20 @@ impl Engine {
/// Check whether the size of a [`Dynamic`] is within limits.
#[inline]
pub(crate) fn check_data_size(&self, value: &Dynamic, pos: Position) -> RhaiResultOf<()> {
pub(crate) fn check_data_size<T: Borrow<Dynamic>>(
&self,
value: T,
pos: Position,
) -> RhaiResultOf<T> {
// If no data size limits, just return
if !self.has_data_size_limit() {
return Ok(());
return Ok(value);
}
let sizes = Self::calc_data_sizes(value, true);
let sizes = Self::calc_data_sizes(value.borrow(), true);
self.raise_err_if_over_data_size_limit(sizes)
.map(|_| value)
.map_err(|err| err.fill_position(pos))
}
@ -128,7 +137,7 @@ impl Engine {
/// Not available under `unchecked`.
#[inline(always)]
pub fn ensure_data_size_within_limits(&self, value: &Dynamic) -> RhaiResultOf<()> {
self.check_data_size(value, Position::NONE)
self.check_data_size(value, Position::NONE).map(|_| ())
}
/// Check if the number of operations stay within limit.
@ -141,26 +150,15 @@ impl Engine {
// Guard against too many operations
let max = self.max_operations();
let num_operations = global.num_operations;
if max > 0 && num_operations > max {
if max > 0 && global.num_operations > max {
return Err(ERR::ErrorTooManyOperations(pos).into());
}
// Report progress
self.progress
.as_ref()
.and_then(|p| p(num_operations))
.and_then(|p| p(global.num_operations))
.map_or(Ok(()), |token| Err(ERR::ErrorTerminated(token, pos).into()))
}
/// Check a result to ensure that it is valid.
#[inline]
pub(crate) fn check_return_value(&self, result: RhaiResult, pos: Position) -> RhaiResult {
if let Ok(ref r) = result {
self.check_data_size(r, pos)?;
}
result
}
}

View File

@ -1,7 +1,7 @@
//! Evaluation context.
use super::{Caches, GlobalRuntimeState};
use crate::{Dynamic, Engine, Module, Scope};
use crate::{Dynamic, Engine, Scope};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -68,7 +68,7 @@ impl<'a, 's, 'ps, 'g, 'c, 't> EvalContext<'a, 's, 'ps, 'g, 'c, 't> {
/// in reverse order (i.e. modules imported last come first).
#[cfg(not(feature = "no_module"))]
#[inline(always)]
pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &Module)> {
pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &crate::Module)> {
self.global.iter_imports()
}
/// Custom state kept in a [`Dynamic`].
@ -104,7 +104,7 @@ impl<'a, 's, 'ps, 'g, 'c, 't> EvalContext<'a, 's, 'ps, 'g, 'c, 't> {
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
#[inline]
pub fn iter_namespaces(&self) -> impl Iterator<Item = &Module> {
pub fn iter_namespaces(&self) -> impl Iterator<Item = &crate::Module> {
self.global.lib.iter().map(|m| m.as_ref())
}
/// _(internals)_ The current set of namespaces containing definitions of all script-defined functions.

View File

@ -4,7 +4,7 @@ use super::{Caches, EvalContext, GlobalRuntimeState, Target};
use crate::ast::{Expr, OpAssignment};
use crate::engine::{KEYWORD_THIS, OP_CONCAT};
use crate::types::dynamic::AccessMode;
use crate::{Dynamic, Engine, Position, RhaiResult, RhaiResultOf, Scope, SharedModule, ERR};
use crate::{Dynamic, Engine, Position, RhaiResult, RhaiResultOf, Scope, ERR};
use std::num::NonZeroUsize;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -18,25 +18,21 @@ impl Engine {
&self,
global: &GlobalRuntimeState,
namespace: &crate::ast::Namespace,
) -> Option<SharedModule> {
) -> Option<crate::SharedModule> {
assert!(!namespace.is_empty());
let root = namespace.root();
let index = if global.always_search_scope {
None
} else {
namespace.index()
};
// Qualified - check if the root module is directly indexed
if let Some(index) = index {
if !global.always_search_scope {
if let Some(index) = namespace.index() {
let offset = global.num_imports() - index.get();
if let m @ Some(_) = global.get_shared_import(offset) {
return m;
}
}
}
// Do a text-match search if the index doesn't work
global.find_import(root).map_or_else(
@ -54,12 +50,12 @@ impl Engine {
scope: &'s mut Scope,
this_ptr: &'s mut Dynamic,
expr: &Expr,
) -> RhaiResultOf<(Target<'s>, Position)> {
) -> RhaiResultOf<Target<'s>> {
match expr {
Expr::Variable(_, Some(_), _) => {
self.search_scope_only(global, caches, scope, this_ptr, expr)
}
Expr::Variable(v, None, _var_pos) => match &**v {
Expr::Variable(v, None, ..) => match &**v {
// Normal variable access
#[cfg(not(feature = "no_module"))]
(_, ns, ..) if ns.is_empty() => {
@ -86,7 +82,7 @@ impl Engine {
|mut target| {
// Module variables are constant
target.set_access_mode(AccessMode::ReadOnly);
Ok((target.into(), *_var_pos))
Ok(target.into())
},
);
}
@ -101,7 +97,7 @@ impl Engine {
let mut target: Target = value.clone().into();
// Module variables are constant
target.set_access_mode(AccessMode::ReadOnly);
return Ok((target, *_var_pos));
return Ok(target);
}
}
@ -136,23 +132,23 @@ impl Engine {
scope: &'s mut Scope,
this_ptr: &'s mut Dynamic,
expr: &Expr,
) -> RhaiResultOf<(Target<'s>, Position)> {
) -> RhaiResultOf<Target<'s>> {
// Make sure that the pointer indirection is taken only when absolutely necessary.
let (index, var_pos) = match expr {
let index = match expr {
// Check if the variable is `this`
Expr::Variable(v, None, pos) if v.0.is_none() && v.3 == KEYWORD_THIS => {
Expr::Variable(v, None, ..) if v.0.is_none() && v.3 == KEYWORD_THIS => {
return if this_ptr.is_null() {
Err(ERR::ErrorUnboundThis(*pos).into())
Err(ERR::ErrorUnboundThis(expr.position()).into())
} else {
Ok((this_ptr.into(), *pos))
Ok(this_ptr.into())
};
}
_ if global.always_search_scope => (0, expr.start_position()),
Expr::Variable(.., Some(i), pos) => (i.get() as usize, *pos),
_ if global.always_search_scope => 0,
Expr::Variable(_, Some(i), ..) => i.get() as usize,
// Scripted function with the same name
#[cfg(not(feature = "no_function"))]
Expr::Variable(v, None, pos)
Expr::Variable(v, None, ..)
if global
.lib
.iter()
@ -161,9 +157,9 @@ impl Engine {
{
let val: Dynamic =
crate::FnPtr::new_unchecked(v.3.as_str(), Default::default()).into();
return Ok((val.into(), *pos));
return Ok(val.into());
}
Expr::Variable(v, None, pos) => (v.0.map_or(0, NonZeroUsize::get), *pos),
Expr::Variable(v, None, ..) => v.0.map_or(0, NonZeroUsize::get),
_ => unreachable!("Expr::Variable expected but gets {:?}", expr),
};
@ -174,10 +170,10 @@ impl Engine {
match resolve_var(var_name, index, context) {
Ok(Some(mut result)) => {
result.set_access_mode(AccessMode::ReadOnly);
return Ok((result.into(), var_pos));
return Ok(result.into());
}
Ok(None) => (),
Err(err) => return Err(err.fill_position(var_pos)),
Err(err) => return Err(err.fill_position(expr.position())),
}
}
@ -191,10 +187,12 @@ impl Engine {
Some(index) => index,
None => {
return match self.global_modules.iter().find_map(|m| m.get_var(var_name)) {
Some(val) => Ok((val.into(), var_pos)),
None => {
Err(ERR::ErrorVariableNotFound(var_name.to_string(), var_pos).into())
}
Some(val) => Ok(val.into()),
None => Err(ERR::ErrorVariableNotFound(
var_name.to_string(),
expr.position(),
)
.into()),
}
}
}
@ -202,17 +200,10 @@ impl Engine {
let val = scope.get_mut_by_index(index);
Ok((val.into(), var_pos))
Ok(val.into())
}
/// Evaluate an expression.
//
// # Implementation Notes
//
// Do not use the `?` operator within the main body as it makes this function return early,
// possibly by-passing important cleanup tasks at the end.
//
// Errors that are not recoverable, such as system errors or safety errors, can use `?`.
pub(crate) fn eval_expr(
&self,
global: &mut GlobalRuntimeState,
@ -256,7 +247,7 @@ impl Engine {
}
} else {
self.search_namespace(global, caches, scope, this_ptr, expr)
.map(|(val, ..)| val.take_or_clone())
.map(Target::take_or_clone)
};
}
@ -286,10 +277,8 @@ impl Engine {
let target = &mut concat;
let mut op_info = OpAssignment::new_op_assignment(OP_CONCAT, Position::NONE);
let root = ("", Position::NONE);
let result = x
.iter()
x.iter()
.try_for_each(|expr| {
let item = self
.eval_expr(global, caches, scope, this_ptr, expr)?
@ -297,11 +286,10 @@ impl Engine {
op_info.pos = expr.start_position();
self.eval_op_assignment(global, caches, &op_info, target, root, item)
self.eval_op_assignment(global, caches, &op_info, expr, target, item)
})
.map(|_| concat.take_or_clone());
self.check_return_value(result, expr.start_position())
.map(|_| concat.take_or_clone())
.and_then(|r| self.check_data_size(r, expr.start_position()))
}
#[cfg(not(feature = "no_index"))]
@ -414,9 +402,8 @@ impl Engine {
})?;
let mut context = EvalContext::new(self, global, caches, scope, this_ptr);
let result = (custom_def.func)(&mut context, &expressions, &custom.state);
self.check_return_value(result, expr.start_position())
(custom_def.func)(&mut context, &expressions, &custom.state)
.and_then(|r| self.check_data_size(r, expr.start_position()))
}
Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT),

View File

@ -1,6 +1,6 @@
//! Global runtime state.
use crate::{Dynamic, Engine, ImmutableString, SharedModule, StaticVec};
use crate::{Dynamic, Engine, ImmutableString};
use std::fmt;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -25,13 +25,13 @@ pub type GlobalConstants =
pub struct GlobalRuntimeState {
/// Names of imported [modules][crate::Module].
#[cfg(not(feature = "no_module"))]
imports: StaticVec<ImmutableString>,
imports: crate::StaticVec<ImmutableString>,
/// Stack of imported [modules][crate::Module].
#[cfg(not(feature = "no_module"))]
modules: StaticVec<SharedModule>,
modules: crate::StaticVec<crate::SharedModule>,
/// The current stack of loaded [modules][crate::Module] containing script-defined functions.
#[cfg(not(feature = "no_function"))]
pub lib: StaticVec<SharedModule>,
pub lib: crate::StaticVec<crate::SharedModule>,
/// Source of the current context.
///
/// No source if the string is empty.
@ -84,11 +84,11 @@ impl GlobalRuntimeState {
pub fn new(engine: &Engine) -> Self {
Self {
#[cfg(not(feature = "no_module"))]
imports: StaticVec::new_const(),
imports: crate::StaticVec::new_const(),
#[cfg(not(feature = "no_module"))]
modules: StaticVec::new_const(),
modules: crate::StaticVec::new_const(),
#[cfg(not(feature = "no_function"))]
lib: StaticVec::new_const(),
lib: crate::StaticVec::new_const(),
source: None,
num_operations: 0,
#[cfg(not(feature = "no_module"))]
@ -99,7 +99,10 @@ impl GlobalRuntimeState {
#[cfg(not(feature = "no_module"))]
embedded_module_resolver: None,
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
fn_hash_indexing: (0, 0),
fn_hash_indexing: (
crate::calc_fn_hash(None, crate::engine::FN_IDX_GET, 2),
crate::calc_fn_hash(None, crate::engine::FN_IDX_SET, 3),
),
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
constants: None,
@ -135,7 +138,7 @@ impl GlobalRuntimeState {
#[cfg(not(feature = "no_module"))]
#[inline(always)]
#[must_use]
pub fn get_shared_import(&self, index: usize) -> Option<SharedModule> {
pub fn get_shared_import(&self, index: usize) -> Option<crate::SharedModule> {
self.modules.get(index).cloned()
}
/// Get a mutable reference to the globally-imported [module][crate::Module] at a
@ -146,7 +149,10 @@ impl GlobalRuntimeState {
#[allow(dead_code)]
#[inline(always)]
#[must_use]
pub(crate) fn get_shared_import_mut(&mut self, index: usize) -> Option<&mut SharedModule> {
pub(crate) fn get_shared_import_mut(
&mut self,
index: usize,
) -> Option<&mut crate::SharedModule> {
self.modules.get_mut(index)
}
/// Get the index of a globally-imported [module][crate::Module] by name.
@ -170,7 +176,7 @@ impl GlobalRuntimeState {
pub fn push_import(
&mut self,
name: impl Into<ImmutableString>,
module: impl Into<SharedModule>,
module: impl Into<crate::SharedModule>,
) {
self.imports.push(name.into());
self.modules.push(module.into());
@ -203,7 +209,7 @@ impl GlobalRuntimeState {
#[inline]
pub(crate) fn iter_imports_raw(
&self,
) -> impl Iterator<Item = (&ImmutableString, &SharedModule)> {
) -> impl Iterator<Item = (&ImmutableString, &crate::SharedModule)> {
self.imports.iter().zip(self.modules.iter()).rev()
}
/// Get an iterator to the stack of globally-imported [modules][crate::Module] in forward order.
@ -211,7 +217,9 @@ impl GlobalRuntimeState {
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
#[inline]
pub fn scan_imports_raw(&self) -> impl Iterator<Item = (&ImmutableString, &SharedModule)> {
pub fn scan_imports_raw(
&self,
) -> impl Iterator<Item = (&ImmutableString, &crate::SharedModule)> {
self.imports.iter().zip(self.modules.iter())
}
/// Can the particular function with [`Dynamic`] parameter(s) exist in the stack of
@ -291,34 +299,22 @@ impl GlobalRuntimeState {
}
/// Get the pre-calculated index getter hash.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline(always)]
#[must_use]
pub(crate) fn hash_idx_get(&mut self) -> u64 {
if self.fn_hash_indexing == (0, 0) {
let n1 = crate::calc_fn_hash(None, crate::engine::FN_IDX_GET, 2);
let n2 = crate::calc_fn_hash(None, crate::engine::FN_IDX_SET, 3);
self.fn_hash_indexing = (n1, n2);
n1
} else {
self.fn_hash_indexing.0
}
}
/// Get the pre-calculated index setter hash.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline(always)]
#[must_use]
pub(crate) fn hash_idx_set(&mut self) -> u64 {
if self.fn_hash_indexing == (0, 0) {
let n1 = crate::calc_fn_hash(None, crate::engine::FN_IDX_GET, 2);
let n2 = crate::calc_fn_hash(None, crate::engine::FN_IDX_SET, 3);
self.fn_hash_indexing = (n1, n2);
n2
} else {
self.fn_hash_indexing.1
}
}
}
#[cfg(not(feature = "no_module"))]
impl<K: Into<ImmutableString>, M: Into<SharedModule>> Extend<(K, M)> for GlobalRuntimeState {
impl<K: Into<ImmutableString>, M: Into<crate::SharedModule>> Extend<(K, M)> for GlobalRuntimeState {
#[inline]
fn extend<T: IntoIterator<Item = (K, M)>>(&mut self, iter: T) {
for (k, m) in iter {

View File

@ -25,7 +25,10 @@ pub use target::{calc_index, calc_offset_len, Target};
#[cfg(feature = "unchecked")]
mod unchecked {
use crate::{eval::GlobalRuntimeState, Dynamic, Engine, Position, RhaiResult, RhaiResultOf};
use crate::{eval::GlobalRuntimeState, Dynamic, Engine, Position, RhaiResultOf};
use std::borrow::Borrow;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
impl Engine {
/// Check if the number of operations stay within limit.
@ -40,18 +43,12 @@ mod unchecked {
/// Check whether the size of a [`Dynamic`] is within limits.
#[inline(always)]
pub(crate) const fn check_data_size(&self, _: &Dynamic, _: Position) -> RhaiResultOf<()> {
Ok(())
}
/// Check a result to ensure that it is valid.
#[inline(always)]
pub(crate) const fn check_return_value(
pub(crate) const fn check_data_size<T: Borrow<Dynamic>>(
&self,
result: RhaiResult,
value: T,
_: Position,
) -> RhaiResult {
result
) -> RhaiResultOf<T> {
Ok(value)
}
}
}

View File

@ -8,22 +8,13 @@ use crate::ast::{
use crate::func::{get_builtin_op_assignment_fn, get_hasher};
use crate::types::dynamic::AccessMode;
use crate::types::RestoreOnDrop;
use crate::{
Dynamic, Engine, ImmutableString, Position, RhaiResult, RhaiResultOf, Scope, ERR, INT,
};
use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, ERR, INT};
use std::hash::{Hash, Hasher};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
impl Engine {
/// Evaluate a statements block.
//
// # Implementation Notes
//
// Do not use the `?` operator within the main body as it makes this function return early,
// possibly by-passing important cleanup tasks at the end.
//
// Errors that are not recoverable, such as system errors or safety errors, can use `?`.
pub(crate) fn eval_stmt_block(
&self,
global: &mut GlobalRuntimeState,
@ -112,13 +103,15 @@ impl Engine {
global: &mut GlobalRuntimeState,
caches: &mut Caches,
op_info: &OpAssignment,
root: &Expr,
target: &mut Target,
root: (&str, Position),
mut new_val: Dynamic,
) -> RhaiResultOf<()> {
// Assignment to constant variable?
if target.is_read_only() {
return Err(ERR::ErrorAssignmentToConstant(root.0.to_string(), root.1).into());
let name = root.get_variable_name(false).unwrap_or_default();
let pos = root.start_position();
return Err(ERR::ErrorAssignmentToConstant(name.to_string(), pos).into());
}
if op_info.is_op_assignment() {
@ -166,14 +159,13 @@ impl Engine {
*args[0] = self
.exec_native_fn_call(
global, caches, op, token, *hash_op, args, true, *op_pos,
)
.map_err(|err| err.fill_position(op_info.pos))?
)?
.0;
}
Err(err) => return Err(err),
}
self.check_data_size(args[0], root.1)?;
self.check_data_size(&*args[0], root.position())?;
} else {
// Normal assignment
@ -190,13 +182,6 @@ impl Engine {
}
/// Evaluate a statement.
//
// # Implementation Notes
//
// Do not use the `?` operator within the main body as it makes this function return early,
// possibly by-passing important cleanup tasks at the end.
//
// Errors that are not recoverable, such as system errors or safety errors, can use `?`.
pub(crate) fn eval_stmt(
&self,
global: &mut GlobalRuntimeState,
@ -234,29 +219,29 @@ impl Engine {
.eval_expr(global, caches, scope, this_ptr, rhs)?
.flatten();
let (mut lhs_ptr, pos) =
self.search_namespace(global, caches, scope, this_ptr, lhs)?;
let mut target = self.search_namespace(global, caches, scope, this_ptr, lhs)?;
let var_name = x.3.as_str();
#[cfg(not(feature = "no_closure"))]
// Also handle case where target is a `Dynamic` shared value
// (returned by a variable resolver, for example)
let is_temp_result = !lhs_ptr.is_ref() && !lhs_ptr.is_shared();
let is_temp_result = !target.is_ref() && !target.is_shared();
#[cfg(feature = "no_closure")]
let is_temp_result = !lhs_ptr.is_ref();
let is_temp_result = !target.is_ref();
// Cannot assign to temp result from expression
if is_temp_result {
return Err(ERR::ErrorAssignmentToConstant(var_name.to_string(), pos).into());
return Err(ERR::ErrorAssignmentToConstant(
var_name.to_string(),
lhs.position(),
)
.into());
}
self.track_operation(global, pos)?;
self.track_operation(global, lhs.position())?;
let root = (var_name, pos);
let lhs_ptr = &mut lhs_ptr;
self.eval_op_assignment(global, caches, op_info, lhs_ptr, root, rhs_val)?;
self.eval_op_assignment(global, caches, op_info, lhs, &mut target, rhs_val)?;
return Ok(Dynamic::UNIT);
}
@ -788,8 +773,8 @@ impl Engine {
let v = self.eval_expr(global, caches, scope, this_ptr, expr)?;
let typ = v.type_name();
let path = v.try_cast::<ImmutableString>().ok_or_else(|| {
self.make_type_mismatch_err::<ImmutableString>(typ, expr.position())
let path = v.try_cast::<crate::ImmutableString>().ok_or_else(|| {
self.make_type_mismatch_err::<crate::ImmutableString>(typ, expr.position())
})?;
use crate::ModuleResolver;

View File

@ -1,6 +1,5 @@
//! Type to hold a mutable reference to the target of an evaluation.
use crate::types::dynamic::Variant;
use crate::{Dynamic, Position, RhaiResultOf};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -52,26 +51,23 @@ pub fn calc_index<E>(
length: usize,
start: crate::INT,
negative_count_from_end: bool,
err: impl FnOnce() -> Result<usize, E>,
err_func: impl FnOnce() -> Result<usize, E>,
) -> Result<usize, E> {
if start < 0 {
if negative_count_from_end {
let abs_start = start.unsigned_abs() as usize;
// Count from end if negative
if abs_start > length {
err()
} else {
Ok(length - abs_start)
if abs_start <= length {
return Ok(length - abs_start);
}
} else {
err()
}
} else if start > crate::MAX_USIZE_INT || start as usize >= length {
err()
} else {
Ok(start as usize)
}
if start <= crate::MAX_USIZE_INT && (start as usize) < length {
return Ok(start as usize);
}
err_func()
}
/// A type that encapsulates a mutation target for an expression with side effects.
@ -180,7 +176,7 @@ impl<'a> Target<'a> {
Self::RefMut(r) => r.is_shared(),
#[cfg(not(feature = "no_closure"))]
Self::SharedValue { .. } => true,
Self::TempValue(r) => r.is_shared(),
Self::TempValue(value) => value.is_shared(),
#[cfg(not(feature = "no_index"))]
Self::Bit { .. }
| Self::BitField { .. }
@ -188,29 +184,6 @@ impl<'a> Target<'a> {
| Self::StringChar { .. } => false,
}
}
/// Is the [`Target`] a specific type?
#[allow(dead_code)]
#[inline]
#[must_use]
pub fn is<T: Variant + Clone>(&self) -> bool {
#[allow(unused_imports)]
use std::any::TypeId;
match self {
Self::RefMut(r) => r.is::<T>(),
#[cfg(not(feature = "no_closure"))]
Self::SharedValue { source, .. } => source.is::<T>(),
Self::TempValue(r) => r.is::<T>(),
#[cfg(not(feature = "no_index"))]
Self::Bit { .. } => TypeId::of::<T>() == TypeId::of::<bool>(),
#[cfg(not(feature = "no_index"))]
Self::BitField { .. } => TypeId::of::<T>() == TypeId::of::<crate::INT>(),
#[cfg(not(feature = "no_index"))]
Self::BlobByte { .. } => TypeId::of::<T>() == TypeId::of::<crate::Blob>(),
#[cfg(not(feature = "no_index"))]
Self::StringChar { .. } => TypeId::of::<T>() == TypeId::of::<char>(),
}
}
/// Get the value of the [`Target`] as a [`Dynamic`], cloning a referenced value if necessary.
#[inline]
#[must_use]
@ -219,7 +192,7 @@ impl<'a> Target<'a> {
Self::RefMut(r) => r.clone(), // Referenced value is cloned
#[cfg(not(feature = "no_closure"))]
Self::SharedValue { value, .. } => value, // Original shared value is simply taken
Self::TempValue(v) => v, // Owned value is simply taken
Self::TempValue(value) => value, // Owned value is simply taken
#[cfg(not(feature = "no_index"))]
Self::Bit { value, .. } => value, // boolean is taken
#[cfg(not(feature = "no_index"))]
@ -259,7 +232,7 @@ impl<'a> Target<'a> {
Self::RefMut(r) => r,
#[cfg(not(feature = "no_closure"))]
Self::SharedValue { source, .. } => source,
Self::TempValue(v) => v,
Self::TempValue(value) => value,
#[cfg(not(feature = "no_index"))]
Self::Bit { source, .. } => source,
#[cfg(not(feature = "no_index"))]
@ -342,13 +315,7 @@ impl<'a> Target<'a> {
let value = &mut *source.write_lock::<crate::Blob>().expect("`Blob`");
let index = *index;
if index < value.len() {
value[index] = (new_byte & 0x00ff) as u8;
} else {
unreachable!("blob index out of bounds: {}", index);
}
value[*index] = (new_byte & 0x00ff) as u8;
}
#[cfg(not(feature = "no_index"))]
Self::StringChar {
@ -369,12 +336,10 @@ impl<'a> Target<'a> {
.write_lock::<crate::ImmutableString>()
.expect("`ImmutableString`");
let index = *index;
*s = s
.chars()
.enumerate()
.map(|(i, ch)| if i == index { new_ch } else { ch })
.map(|(i, ch)| if i == *index { new_ch } else { ch })
.collect();
}
}
@ -407,7 +372,7 @@ impl Deref for Target<'_> {
Self::RefMut(r) => r,
#[cfg(not(feature = "no_closure"))]
Self::SharedValue { source, .. } => source,
Self::TempValue(ref r) => r,
Self::TempValue(ref value) => value,
#[cfg(not(feature = "no_index"))]
Self::Bit { ref value, .. }
| Self::BitField { ref value, .. }
@ -440,7 +405,7 @@ impl DerefMut for Target<'_> {
Self::RefMut(r) => r,
#[cfg(not(feature = "no_closure"))]
Self::SharedValue { source, .. } => &mut *source,
Self::TempValue(ref mut r) => r,
Self::TempValue(ref mut value) => value,
#[cfg(not(feature = "no_index"))]
Self::Bit { ref mut value, .. }
| Self::BitField { ref mut value, .. }

View File

@ -66,6 +66,13 @@ impl<T: Variant + Clone> FuncArgs for Vec<T> {
}
}
impl<T: Variant + Clone, const N: usize> FuncArgs for [T; N] {
#[inline]
fn parse<ARGS: Extend<Dynamic>>(self, args: &mut ARGS) {
args.extend(IntoIterator::into_iter(self).map(Dynamic::from));
}
}
/// Macro to implement [`FuncArgs`] for tuples of standard types (each can be converted into a [`Dynamic`]).
macro_rules! impl_args {
($($p:ident),*) => {

View File

@ -12,7 +12,7 @@ use crate::tokenizer::{is_valid_function_name, Token};
use crate::types::RestoreOnDrop;
use crate::{
calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString,
OptimizationLevel, Position, RhaiError, RhaiResult, RhaiResultOf, Scope, ERR,
OptimizationLevel, Position, RhaiError, RhaiResult, RhaiResultOf, Scope, Shared, ERR,
};
#[cfg(feature = "no_std")]
use hashbrown::hash_map::Entry;
@ -166,7 +166,7 @@ impl Engine {
#[must_use]
fn resolve_fn<'s>(
&self,
global: &GlobalRuntimeState,
_global: &GlobalRuntimeState,
caches: &'s mut Caches,
local_entry: &'s mut Option<FnResolutionCacheEntry>,
op_token: Option<&Token>,
@ -194,7 +194,7 @@ impl Engine {
loop {
#[cfg(not(feature = "no_function"))]
let func = global
let func = _global
.lib
.iter()
.rev()
@ -214,7 +214,7 @@ impl Engine {
// Scripted functions are not exposed globally
func
} else {
func.or_else(|| global.get_qualified_fn(hash)).or_else(|| {
func.or_else(|| _global.get_qualified_fn(hash)).or_else(|| {
self.global_sub_modules
.values()
.find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id_raw())))
@ -223,17 +223,17 @@ impl Engine {
if let Some((f, s)) = func {
// Specific version found
let new_entry = Some(FnResolutionCacheEntry {
let new_entry = FnResolutionCacheEntry {
func: f.clone(),
source: s.cloned(),
});
};
return if cache.filter.is_absent_and_set(hash) {
// Do not cache "one-hit wonders"
*local_entry = new_entry;
*local_entry = Some(new_entry);
local_entry.as_ref()
} else {
// Cache entry
entry.insert(new_entry).as_ref()
entry.insert(Some(new_entry)).as_ref()
};
}
@ -246,14 +246,14 @@ impl Engine {
#[cfg(not(feature = "no_function"))]
let is_dynamic = is_dynamic
|| global
|| _global
.lib
.iter()
.any(|m| m.may_contain_dynamic_fn(hash_base));
#[cfg(not(feature = "no_module"))]
let is_dynamic = is_dynamic
|| global.may_contain_dynamic_fn(hash_base)
|| _global.may_contain_dynamic_fn(hash_base)
|| self
.global_sub_modules
.values()
@ -279,13 +279,13 @@ impl Engine {
get_builtin_op_assignment_fn(token, *first_arg, rest_args[0])
.map(|f| FnResolutionCacheEntry {
func: CallableFunction::from_fn_builtin(f),
func: CallableFunction::Method(Shared::new(f)),
source: None,
})
}
Some(token) => get_builtin_binary_op_fn(token, args[0], args[1])
.map(|f| FnResolutionCacheEntry {
func: CallableFunction::from_fn_builtin(f),
func: CallableFunction::Method(Shared::new(f)),
source: None,
}),
@ -363,16 +363,13 @@ impl Engine {
true,
);
if func.is_some() {
let is_method = func.map_or(false, |f| f.func.is_method());
if let Some(FnResolutionCacheEntry { func, source }) = func {
assert!(func.is_native());
// Push a new call stack frame
#[cfg(feature = "debugging")]
let orig_call_stack_len = global.debugger.call_stack().len();
let FnResolutionCacheEntry { func, source } = func.unwrap();
assert!(func.is_native());
let backup = &mut ArgBackup::new();
// Calling pure function but the first argument is a reference?
@ -397,6 +394,7 @@ impl Engine {
}
// Run external function
let is_method = func.is_method();
let src = source.as_ref().map(|s| s.as_str());
let context = (self, name, src, &*global, pos).into();
@ -406,16 +404,22 @@ impl Engine {
Err(ERR::ErrorNonPureMethodCallOnConstant(name.to_string(), pos).into())
} else {
f.call(context, args)
.and_then(|r| self.check_data_size(r, pos))
.map_err(|err| err.fill_position(pos))
}
} else {
func.get_native_fn().unwrap()(context, args)
.and_then(|r| self.check_data_size(r, pos))
.map_err(|err| err.fill_position(pos))
};
#[cfg(feature = "debugging")]
{
use crate::eval::{DebuggerEvent, DebuggerStatus};
let trigger = match global.debugger.status {
crate::eval::DebuggerStatus::FunctionExit(n) => n >= global.level,
crate::eval::DebuggerStatus::Next(.., true) => true,
DebuggerStatus::FunctionExit(n) => n >= global.level,
DebuggerStatus::Next(.., true) => true,
_ => false,
};
if trigger {
@ -424,8 +428,8 @@ impl Engine {
let node = crate::ast::Stmt::Noop(pos);
let node = (&node).into();
let event = match _result {
Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r),
Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err),
Ok(ref r) => DebuggerEvent::FunctionExitWithValue(r),
Err(ref err) => DebuggerEvent::FunctionExitWithError(err),
};
if let Err(err) =
@ -439,13 +443,12 @@ impl Engine {
global.debugger.rewind_call_stack(orig_call_stack_len);
}
// Check the return value (including data sizes)
let result = self.check_return_value(_result, pos)?;
let result = _result?;
// Check the data size of any `&mut` object, which may be changed.
#[cfg(not(feature = "unchecked"))]
if is_ref_mut && !args.is_empty() {
self.check_data_size(args[0], pos)?;
self.check_data_size(&*args[0], pos)?;
}
// See if the function match print/debug (which requires special processing)
@ -1188,14 +1191,14 @@ impl Engine {
.map(|(value, ..)| arg_values.push(value.flatten()))
})?;
let (mut target, _pos) =
let mut target =
self.search_namespace(global, caches, scope, this_ptr, first_expr)?;
if target.is_read_only() {
target = target.into_owned();
}
self.track_operation(global, _pos)?;
self.track_operation(global, first_expr.position())?;
#[cfg(not(feature = "no_closure"))]
let target_is_shared = target.is_shared();
@ -1269,10 +1272,9 @@ impl Engine {
// Get target reference to first argument
let first_arg = &args_expr[0];
let (target, _pos) =
self.search_scope_only(global, caches, scope, this_ptr, first_arg)?;
let target = self.search_scope_only(global, caches, scope, this_ptr, first_arg)?;
self.track_operation(global, _pos)?;
self.track_operation(global, first_arg.position())?;
#[cfg(not(feature = "no_closure"))]
let target_is_shared = target.is_shared();
@ -1382,19 +1384,18 @@ impl Engine {
Some(f) if f.is_plugin_fn() => {
let context = (self, fn_name, module.id(), &*global, pos).into();
let f = f.get_plugin_fn().expect("plugin function");
let result = if !f.is_pure() && !args.is_empty() && args[0].is_read_only() {
if !f.is_pure() && !args.is_empty() && args[0].is_read_only() {
Err(ERR::ErrorNonPureMethodCallOnConstant(fn_name.to_string(), pos).into())
} else {
f.call(context, &mut args)
};
self.check_return_value(result, pos)
.and_then(|r| self.check_data_size(r, pos))
}
}
Some(f) if f.is_native() => {
let func = f.get_native_fn().expect("native function");
let context = (self, fn_name, module.id(), &*global, pos).into();
let result = func(context, &mut args);
self.check_return_value(result, pos)
func(context, &mut args).and_then(|r| self.check_data_size(r, pos))
}
Some(f) => unreachable!("unknown function type: {:?}", f),

View File

@ -1,6 +1,6 @@
//! Module defining the standard Rhai function type.
use super::native::{FnAny, FnBuiltin, FnPlugin, IteratorFn, SendSync};
use super::native::{FnAny, FnPlugin, IteratorFn, SendSync};
use crate::ast::FnAccess;
use crate::plugin::PluginFunction;
use crate::Shared;
@ -8,7 +8,8 @@ use std::fmt;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
/// A type encapsulating a function callable by Rhai.
/// _(internals)_ A type encapsulating a function callable by Rhai.
/// Exported under the `internals` feature only.
#[derive(Clone)]
#[non_exhaustive]
pub enum CallableFunction {
@ -199,18 +200,6 @@ impl CallableFunction {
Self::Script(..) => None,
}
}
/// Create a new [`CallableFunction::Method`] from a built-in function.
#[inline(always)]
#[must_use]
pub fn from_fn_builtin(func: FnBuiltin) -> Self {
Self::Method(Shared::new(func))
}
/// Create a new [`CallableFunction::Plugin`].
#[inline(always)]
#[must_use]
pub fn from_plugin(func: impl PluginFunction + 'static + SendSync) -> Self {
Self::Plugin(Shared::new(func))
}
}
#[cfg(not(feature = "no_function"))]
@ -232,7 +221,7 @@ impl From<Shared<crate::ast::ScriptFnDef>> for CallableFunction {
impl<T: PluginFunction + 'static + SendSync> From<T> for CallableFunction {
#[inline(always)]
fn from(func: T) -> Self {
Self::from_plugin(func)
Self::Plugin(Shared::new(func))
}
}

View File

@ -92,7 +92,7 @@ pub fn get_hasher() -> ahash::AHasher {
///
/// # Zeros
///
/// If the hash happens to be zero, it is mapped to `DEFAULT_HASH`.
/// If the hash happens to be zero, it is mapped to `ALT_ZERO_HASH`.
///
/// # Note
///
@ -107,9 +107,8 @@ pub fn calc_var_hash<'a>(
// We always skip the first module
let iter = modules.into_iter();
let len = iter.len();
iter.len().hash(s);
iter.skip(1).for_each(|m| m.hash(s));
len.hash(s);
var_name.hash(s);
match s.finish() {
@ -128,7 +127,7 @@ pub fn calc_var_hash<'a>(
///
/// # Zeros
///
/// If the hash happens to be zero, it is mapped to `DEFAULT_HASH`.
/// If the hash happens to be zero, it is mapped to `ALT_ZERO_HASH`.
///
/// # Note
///
@ -144,9 +143,8 @@ pub fn calc_fn_hash<'a>(
// We always skip the first module
let iter = namespace.into_iter();
let len = iter.len();
iter.len().hash(s);
iter.skip(1).for_each(|m| m.hash(s));
len.hash(s);
fn_name.hash(s);
num.hash(s);
@ -162,7 +160,7 @@ pub fn calc_fn_hash<'a>(
///
/// # Zeros
///
/// If the hash happens to be zero, it is mapped to `DEFAULT_HASH`.
/// If the hash happens to be zero, it is mapped to `ALT_ZERO_HASH`.
#[inline]
#[must_use]
pub fn calc_fn_hash_full(
@ -172,11 +170,10 @@ pub fn calc_fn_hash_full(
let s = &mut get_hasher();
base.hash(s);
let iter = params.into_iter();
let len = iter.len();
iter.len().hash(s);
iter.for_each(|t| {
t.hash(s);
});
len.hash(s);
match s.finish() {
0 => ALT_ZERO_HASH,

View File

@ -7,10 +7,10 @@ use crate::plugin::PluginFunction;
use crate::tokenizer::{is_valid_function_name, Token, TokenizeState};
use crate::types::dynamic::Variant;
use crate::{
calc_fn_hash, Dynamic, Engine, EvalContext, FuncArgs, Module, Position, RhaiResult,
RhaiResultOf, SharedModule, StaticVec, VarDefInfo, ERR,
calc_fn_hash, reify, Dynamic, Engine, EvalContext, FuncArgs, Position, RhaiResult,
RhaiResultOf, StaticVec, VarDefInfo, ERR,
};
use std::any::type_name;
use std::any::{type_name, TypeId};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -100,7 +100,7 @@ pub struct NativeCallContextStore {
#[cfg(feature = "internals")]
#[allow(deprecated)]
impl NativeCallContextStore {
/// Create a [`NativeCallContext`] from a [`NativeCallContextClone`].
/// Create a [`NativeCallContext`] from a [`NativeCallContextStore`].
///
/// # WARNING - Unstable API
///
@ -167,7 +167,7 @@ impl<'a> NativeCallContext<'a> {
}
}
/// _(internals)_ Create a [`NativeCallContext`] from a [`NativeCallContextClone`].
/// _(internals)_ Create a [`NativeCallContext`] from a [`NativeCallContextStore`].
/// Exported under the `internals` feature only.
///
/// # WARNING - Unstable API
@ -187,7 +187,7 @@ impl<'a> NativeCallContext<'a> {
pos: context.pos,
}
}
/// _(internals)_ Store this [`NativeCallContext`] into a [`NativeCallContextClone`].
/// _(internals)_ Store this [`NativeCallContext`] into a [`NativeCallContextStore`].
/// Exported under the `internals` feature only.
///
/// # WARNING - Unstable API
@ -249,7 +249,7 @@ impl<'a> NativeCallContext<'a> {
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
#[inline]
pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &Module)> {
pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &crate::Module)> {
self.global.iter_imports()
}
/// Get an iterator over the current set of modules imported via `import` statements in reverse order.
@ -258,7 +258,7 @@ impl<'a> NativeCallContext<'a> {
#[inline]
pub(crate) fn iter_imports_raw(
&self,
) -> impl Iterator<Item = (&crate::ImmutableString, &SharedModule)> {
) -> impl Iterator<Item = (&crate::ImmutableString, &crate::SharedModule)> {
self.global.iter_imports_raw()
}
/// _(internals)_ The current [`GlobalRuntimeState`], if any.
@ -277,7 +277,7 @@ impl<'a> NativeCallContext<'a> {
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
#[inline]
pub fn iter_namespaces(&self) -> impl Iterator<Item = &Module> {
pub fn iter_namespaces(&self) -> impl Iterator<Item = &crate::Module> {
self.global.lib.iter().map(|m| m.as_ref())
}
/// _(internals)_ The current stack of namespaces containing definitions of all script-defined functions.
@ -288,7 +288,7 @@ impl<'a> NativeCallContext<'a> {
#[cfg(feature = "internals")]
#[inline(always)]
#[must_use]
pub fn namespaces(&self) -> &[SharedModule] {
pub fn namespaces(&self) -> &[crate::SharedModule] {
&self.global.lib
}
/// Call a function inside the call context with the provided arguments.
@ -303,7 +303,12 @@ impl<'a> NativeCallContext<'a> {
let mut args: StaticVec<_> = arg_values.iter_mut().collect();
let result = self._call_fn_raw(fn_name, false, false, false, &mut args)?;
let result = self._call_fn_raw(fn_name, &mut args, false, false, false)?;
// Bail out early if the return type needs no cast
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
return Ok(reify!(result => T));
}
let typ = self.engine().map_type_name(result.type_name());
@ -328,7 +333,12 @@ impl<'a> NativeCallContext<'a> {
let mut args: StaticVec<_> = arg_values.iter_mut().collect();
let result = self._call_fn_raw(fn_name, true, false, false, &mut args)?;
let result = self._call_fn_raw(fn_name, &mut args, true, false, false)?;
// Bail out early if the return type needs no cast
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
return Ok(reify!(result => T));
}
let typ = self.engine().map_type_name(result.type_name());
@ -369,7 +379,7 @@ impl<'a> NativeCallContext<'a> {
#[cfg(not(feature = "no_function"))]
let native_only = native_only && !crate::parser::is_anonymous_fn(name);
self._call_fn_raw(fn_name, native_only, is_ref_mut, is_method_call, args)
self._call_fn_raw(fn_name, args, native_only, is_ref_mut, is_method_call)
}
/// Call a registered native Rust function inside the call context.
///
@ -398,17 +408,17 @@ impl<'a> NativeCallContext<'a> {
is_ref_mut: bool,
args: &mut [&mut Dynamic],
) -> RhaiResult {
self._call_fn_raw(fn_name, true, is_ref_mut, false, args)
self._call_fn_raw(fn_name, args, true, is_ref_mut, false)
}
/// Call a function (native Rust or scripted) inside the call context.
fn _call_fn_raw(
&self,
fn_name: impl AsRef<str>,
args: &mut [&mut Dynamic],
native_only: bool,
is_ref_mut: bool,
is_method_call: bool,
args: &mut [&mut Dynamic],
) -> RhaiResult {
let mut global = &mut self.global.clone();
let caches = &mut Caches::new();
@ -509,7 +519,8 @@ pub fn shared_take<T>(value: Shared<T>) -> T {
shared_try_take(value).ok().expect("not shared")
}
/// Lock a [`Locked`] resource for mutable access.
/// _(internals)_ Lock a [`Locked`] resource for mutable access.
/// Exported under the `internals` feature only.
#[inline(always)]
#[must_use]
#[allow(dead_code)]
@ -521,7 +532,8 @@ pub fn locked_read<T>(value: &Locked<T>) -> LockGuard<T> {
return value.read().unwrap();
}
/// Lock a [`Locked`] resource for mutable access.
/// _(internals)_ Lock a [`Locked`] resource for mutable access.
/// Exported under the `internals` feature only.
#[inline(always)]
#[must_use]
#[allow(dead_code)]

View File

@ -201,8 +201,6 @@ pub use api::{eval::eval, events::VarDefInfo, run::run};
pub use ast::{FnAccess, AST};
pub use engine::{Engine, OP_CONTAINS, OP_EQUALS};
pub use eval::EvalContext;
#[cfg(feature = "internals")]
pub use func::native::{locked_read, locked_write};
pub use func::{NativeCallContext, RegisterNativeFunction};
pub use module::{FnNamespace, Module};
pub use tokenizer::Position;
@ -255,6 +253,9 @@ pub use func::Func;
#[cfg(not(feature = "no_function"))]
pub use ast::ScriptFnMetadata;
#[cfg(not(feature = "no_function"))]
pub use api::call_fn::CallFnOptions;
/// Variable-sized array of [`Dynamic`] values.
///
/// Not available under `no_index`.
@ -342,7 +343,7 @@ pub use eval::{Caches, FnResolutionCache, FnResolutionCacheEntry, GlobalRuntimeS
#[cfg(feature = "internals")]
#[allow(deprecated)]
pub use func::NativeCallContextStore;
pub use func::{locked_read, locked_write, CallableFunction, NativeCallContextStore};
#[cfg(feature = "internals")]
#[cfg(feature = "metadata")]

View File

@ -6,7 +6,7 @@ use crate::{
use std::prelude::v1::*;
use std::{ops::AddAssign, slice::Iter};
/// [Module] resolution service that holds a collection of module resolvers,
/// [Module][crate::Module] resolution service that holds a collection of module resolvers,
/// to be searched in sequential order.
///
/// # Example

View File

@ -2,7 +2,7 @@ use crate::{Engine, ModuleResolver, Position, RhaiResultOf, SharedModule, ERR};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
/// Empty/disabled [module][Module] resolution service that acts as a dummy.
/// Empty/disabled [module][crate::Module] resolution service that acts as a dummy.
///
/// # Example
///

View File

@ -848,8 +848,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
Stmt::Expr(expr) => {
optimize_expr(expr, state, false);
if matches!(**expr, Expr::FnCall(..) | Expr::Stmt(..)) {
state.set_dirty();
// Do not promote until the expression is fully optimized
if !state.is_dirty() && matches!(**expr, Expr::FnCall(..) | Expr::Stmt(..)) {
*stmt = match *mem::take(expr) {
// func(...);
Expr::FnCall(x, pos) => Stmt::FnCall(x, pos),
@ -859,6 +859,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
Expr::Stmt(x) => (*x).into(),
_ => unreachable!(),
};
state.set_dirty();
}
}
@ -922,6 +923,22 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
}
// var.rhs
(Expr::Variable(..), rhs) => optimize_expr(rhs, state, true),
// const.type_of()
(lhs, Expr::MethodCall(x, pos)) if lhs.is_constant() && x.name == KEYWORD_TYPE_OF && x.args.is_empty() => {
if let Some(value) = lhs.get_literal_value() {
state.set_dirty();
let typ = state.engine.map_type_name(value.type_name()).into();
*expr = Expr::from_dynamic(typ, *pos);
}
}
// const.is_shared()
#[cfg(not(feature = "no_closure"))]
(lhs, Expr::MethodCall(x, pos)) if lhs.is_constant() && x.name == crate::engine::KEYWORD_IS_SHARED && x.args.is_empty() => {
if let Some(..) = lhs.get_literal_value() {
state.set_dirty();
*expr = Expr::from_dynamic(Dynamic::FALSE, *pos);
}
}
// lhs.rhs
(lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, true); }
}

View File

@ -23,7 +23,7 @@ use rust_decimal::Decimal;
#[inline(always)]
fn std_add<T>(x: T, y: T) -> Option<T>
where
T: Debug + Copy + PartialOrd + num_traits::CheckedAdd<Output = T>,
T: num_traits::CheckedAdd<Output = T>,
{
x.checked_add(&y)
}
@ -31,14 +31,14 @@ where
#[allow(dead_code)]
fn regular_add<T>(x: T, y: T) -> Option<T>
where
T: Debug + Copy + PartialOrd + std::ops::Add<Output = T>,
T: std::ops::Add<Output = T>,
{
Some(x + y)
}
// Range iterator with step
#[derive(Clone, Hash, Eq, PartialEq)]
pub struct StepRange<T: Debug + Copy + PartialOrd> {
pub struct StepRange<T> {
pub from: T,
pub to: T,
pub step: T,
@ -46,7 +46,7 @@ pub struct StepRange<T: Debug + Copy + PartialOrd> {
pub dir: i8,
}
impl<T: Debug + Copy + PartialOrd> Debug for StepRange<T> {
impl<T: Debug> Debug for StepRange<T> {
#[cold]
#[inline(never)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -58,7 +58,7 @@ impl<T: Debug + Copy + PartialOrd> Debug for StepRange<T> {
}
}
impl<T: Debug + Copy + PartialOrd> StepRange<T> {
impl<T: Copy + PartialOrd> StepRange<T> {
pub fn new(from: T, to: T, step: T, add: fn(T, T) -> Option<T>) -> RhaiResultOf<Self> {
let mut dir = 0;
@ -95,7 +95,7 @@ impl<T: Debug + Copy + PartialOrd> StepRange<T> {
}
}
impl<T: Debug + Copy + PartialOrd> Iterator for StepRange<T> {
impl<T: Copy + PartialOrd> Iterator for StepRange<T> {
type Item = T;
fn next(&mut self) -> Option<T> {
@ -118,7 +118,7 @@ impl<T: Debug + Copy + PartialOrd> Iterator for StepRange<T> {
}
}
impl<T: Debug + Copy + PartialOrd> FusedIterator for StepRange<T> {}
impl<T: Copy + PartialOrd> FusedIterator for StepRange<T> {}
// Bit-field iterator with step
#[derive(Debug, Clone, Hash, Eq, PartialEq)]

View File

@ -3818,7 +3818,7 @@ impl Engine {
&self,
input: &mut TokenStream,
state: &mut ParseState,
process_settings: impl Fn(&mut ParseSettings),
process_settings: impl FnOnce(&mut ParseSettings),
_optimization_level: OptimizationLevel,
) -> ParseResult<AST> {
let mut functions = StraightHashMap::default();
@ -3882,7 +3882,7 @@ impl Engine {
&self,
input: &mut TokenStream,
state: &mut ParseState,
process_settings: impl Fn(&mut ParseSettings),
process_settings: impl FnOnce(&mut ParseSettings),
) -> ParseResult<(StmtBlockContainer, StaticVec<Shared<ScriptFnDef>>)> {
let mut statements = StmtBlockContainer::new_const();
let mut functions = StraightHashMap::default();

View File

@ -127,20 +127,21 @@ impl<'de> Visitor<'de> for DynamicVisitor {
#[inline(always)]
fn visit_char<E: Error>(self, v: char) -> Result<Self::Value, E> {
self.visit_string(v.to_string())
Ok(v.into())
}
#[inline(always)]
fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
Ok(v.into())
}
#[inline(always)]
fn visit_borrowed_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
self.visit_str(v)
}
#[inline(always)]
fn visit_string<E: Error>(self, v: String) -> Result<Self::Value, E> {
Ok(v.into())
}
#[cfg(not(feature = "no_index"))]
#[inline(always)]
fn visit_bytes<E: Error>(self, v: &[u8]) -> Result<Self::Value, E> {
Ok(Dynamic::from_blob(v.to_vec()))
}
#[inline(always)]
fn visit_unit<E: Error>(self) -> Result<Self::Value, E> {

View File

@ -20,7 +20,7 @@ impl Serialize for Dynamic {
Union::Unit(..) => ser.serialize_unit(),
Union::Bool(x, ..) => ser.serialize_bool(x),
Union::Str(ref s, ..) => ser.serialize_str(s.as_str()),
Union::Char(c, ..) => ser.serialize_str(&c.to_string()),
Union::Char(c, ..) => ser.serialize_char(c),
#[cfg(not(feature = "only_i32"))]
Union::Int(x, ..) => ser.serialize_i64(x),
@ -58,7 +58,7 @@ impl Serialize for Dynamic {
#[cfg(not(feature = "no_index"))]
Union::Array(ref a, ..) => (**a).serialize(ser),
#[cfg(not(feature = "no_index"))]
Union::Blob(ref a, ..) => (**a).serialize(ser),
Union::Blob(ref a, ..) => ser.serialize_bytes(&**a),
#[cfg(not(feature = "no_object"))]
Union::Map(ref m, ..) => {
let mut map = ser.serialize_map(Some(m.len()))?;

View File

@ -1181,42 +1181,105 @@ impl Dynamic {
/// ```
#[inline]
#[must_use]
pub fn try_cast<T: Any>(self) -> Option<T> {
pub fn try_cast<T: Any>(mut self) -> Option<T> {
// Coded this way in order to maximally leverage potentials for dead-code removal.
#[cfg(not(feature = "no_closure"))]
if let Union::Shared(..) = self.0 {
return self.flatten().try_cast::<T>();
}
self.flatten_in_place();
reify!(self, |v: T| return Some(v));
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
return Some(reify!(self => T));
}
if TypeId::of::<T>() == TypeId::of::<()>() {
return match self.0 {
Union::Unit(..) => Some(reify!(() => T)),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<INT>() {
return match self.0 {
Union::Int(n, ..) => Some(reify!(n => T)),
_ => None,
};
}
#[cfg(not(feature = "no_float"))]
if TypeId::of::<T>() == TypeId::of::<crate::FLOAT>() {
return match self.0 {
Union::Float(v, ..) => Some(reify!(*v => T)),
_ => None,
};
}
#[cfg(feature = "decimal")]
if TypeId::of::<T>() == TypeId::of::<rust_decimal::Decimal>() {
return match self.0 {
Union::Decimal(v, ..) => Some(reify!(*v => T)),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<bool>() {
return match self.0 {
Union::Bool(b, ..) => Some(reify!(b => T)),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<ImmutableString>() {
return match self.0 {
Union::Str(s, ..) => Some(reify!(s => T)),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<String>() {
return match self.0 {
Union::Str(s, ..) => Some(reify!(s.to_string() => T)),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<char>() {
return match self.0 {
Union::Char(c, ..) => Some(reify!(c => T)),
_ => None,
};
}
#[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<crate::Array>() {
return match self.0 {
Union::Array(a, ..) => Some(reify!(*a => T)),
_ => None,
};
}
#[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<crate::Blob>() {
return match self.0 {
Union::Blob(b, ..) => Some(reify!(*b => T)),
_ => None,
};
}
#[cfg(not(feature = "no_object"))]
if TypeId::of::<T>() == TypeId::of::<crate::Map>() {
return match self.0 {
Union::Map(m, ..) => Some(reify!(*m => T)),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<FnPtr>() {
return match self.0 {
Union::FnPtr(f, ..) => Some(reify!(*f => T)),
_ => None,
};
}
#[cfg(not(feature = "no_time"))]
if TypeId::of::<T>() == TypeId::of::<Instant>() {
return match self.0 {
Union::TimeStamp(t, ..) => Some(reify!(*t => T)),
_ => None,
};
}
match self.0 {
Union::Null => unreachable!(),
Union::Int(v, ..) => reify!(v => Option<T>),
#[cfg(not(feature = "no_float"))]
Union::Float(v, ..) => reify!(*v => Option<T>),
#[cfg(feature = "decimal")]
Union::Decimal(v, ..) => reify!(*v => Option<T>),
Union::Bool(v, ..) => reify!(v => Option<T>),
Union::Str(v, ..) => {
reify!(v, |v: T| Some(v), || reify!(v.to_string() => Option<T>))
}
Union::Char(v, ..) => reify!(v => Option<T>),
#[cfg(not(feature = "no_index"))]
Union::Array(v, ..) => reify!(*v => Option<T>),
#[cfg(not(feature = "no_index"))]
Union::Blob(v, ..) => reify!(*v => Option<T>),
#[cfg(not(feature = "no_object"))]
Union::Map(v, ..) => reify!(*v => Option<T>),
Union::FnPtr(v, ..) => reify!(*v => Option<T>),
#[cfg(not(feature = "no_time"))]
Union::TimeStamp(v, ..) => reify!(*v => Option<T>),
Union::Unit(v, ..) => reify!(v => Option<T>),
Union::Variant(v, ..) => (*v).as_boxed_any().downcast().ok().map(|x| *x),
#[cfg(not(feature = "no_closure"))]
Union::Shared(..) => unreachable!("Union::Shared case should be already handled"),
_ => None,
}
}
/// Convert the [`Dynamic`] value into a specific type.
@ -1245,6 +1308,11 @@ impl Dynamic {
#[inline]
#[must_use]
pub fn cast<T: Any + Clone>(self) -> T {
// Bail out early if the return type needs no cast
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
return reify!(self => T);
}
#[cfg(not(feature = "no_closure"))]
let self_type_name = if self.is_shared() {
// Avoid panics/deadlocks with shared values

View File

@ -198,7 +198,7 @@ impl fmt::Display for EvalAltResult {
if s.starts_with(crate::engine::FN_SET) =>
{
let prop = &s[crate::engine::FN_SET.len()..];
write!(f, "Cannot modify property {prop} of constant")?
write!(f, "Cannot modify property '{prop}' of a constant")?
}
#[cfg(not(feature = "no_index"))]
Self::ErrorNonPureMethodCallOnConstant(s, ..) if s == crate::engine::FN_IDX_GET => {
@ -209,10 +209,10 @@ impl fmt::Display for EvalAltResult {
}
#[cfg(not(feature = "no_index"))]
Self::ErrorNonPureMethodCallOnConstant(s, ..) if s == crate::engine::FN_IDX_SET => {
write!(f, "Cannot assign to indexer of constant")?
write!(f, "Cannot assign to the indexer of a constant")?
}
Self::ErrorNonPureMethodCallOnConstant(s, ..) => {
write!(f, "Non-pure method {s} cannot be called on constant")?
write!(f, "Non-pure method '{s}' cannot be called on a constant")?
}
Self::ErrorAssignmentToConstant(s, ..) => write!(f, "Cannot modify constant {s}")?,
@ -230,8 +230,8 @@ impl fmt::Display for EvalAltResult {
Self::ErrorArithmetic(s, ..) if s.is_empty() => f.write_str("Arithmetic error")?,
Self::ErrorArithmetic(s, ..) => f.write_str(s)?,
Self::LoopBreak(true, ..) => f.write_str("'break' not inside a loop")?,
Self::LoopBreak(false, ..) => f.write_str("'continue' not inside a loop")?,
Self::LoopBreak(true, ..) => f.write_str("'break' must be inside a loop")?,
Self::LoopBreak(false, ..) => f.write_str("'continue' must be inside a loop")?,
Self::Return(..) => f.write_str("NOT AN ERROR - function returns value")?,

View File

@ -4,13 +4,13 @@ use crate::eval::GlobalRuntimeState;
use crate::tokenizer::is_valid_function_name;
use crate::types::dynamic::Variant;
use crate::{
Dynamic, Engine, FuncArgs, ImmutableString, NativeCallContext, Position, RhaiError, RhaiResult,
RhaiResultOf, StaticVec, AST, ERR,
reify, Dynamic, Engine, FuncArgs, ImmutableString, NativeCallContext, Position, RhaiError,
RhaiResult, RhaiResultOf, StaticVec, AST, ERR,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
any::type_name,
any::{type_name, TypeId},
convert::{TryFrom, TryInto},
fmt, mem,
};
@ -160,6 +160,11 @@ impl FnPtr {
let result = self.call_raw(&ctx, None, arg_values)?;
// Bail out early if the return type needs no cast
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
return Ok(reify!(result => T));
}
let typ = engine.map_type_name(result.type_name());
result.try_cast().ok_or_else(|| {
@ -184,6 +189,11 @@ impl FnPtr {
let result = self.call_raw(context, None, arg_values)?;
// Bail out early if the return type needs no cast
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
return Ok(reify!(result => T));
}
let typ = context.engine().map_type_name(result.type_name());
result.try_cast().ok_or_else(|| {

View File

@ -80,7 +80,7 @@ impl StringsInterner<'_> {
pub fn get_with_mapper<S: AsRef<str>>(
&mut self,
id: &str,
mapper: impl Fn(S) -> ImmutableString,
mapper: impl FnOnce(S) -> ImmutableString,
text: S,
) -> ImmutableString {
let key = text.as_ref();

View File

@ -445,7 +445,8 @@ impl Scope<'_> {
.rev()
.enumerate()
.find(|(.., key)| &name == key)
.and_then(|(index, ..)| self.values[len - 1 - index].flatten_clone().try_cast())
.map(|(index, ..)| self.values[len - 1 - index].flatten_clone())
.and_then(Dynamic::try_cast)
}
/// Check if the named entry in the [`Scope`] is constant.
///

View File

@ -1,5 +1,5 @@
#![cfg(not(feature = "no_function"))]
use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Func, FuncArgs, Scope, AST, INT};
use rhai::{CallFnOptions, Dynamic, Engine, EvalAltResult, FnPtr, Func, FuncArgs, Scope, AST, INT};
use std::any::TypeId;
#[test]
@ -50,11 +50,10 @@ fn test_call_fn() -> Result<(), Box<EvalAltResult>> {
assert!(!scope.contains("bar"));
let args = [(2 as INT).into()];
let r = engine
.call_fn_raw(&mut scope, &ast, false, false, "define_var", None, args)?
.as_int()
.unwrap();
let options = CallFnOptions::new().eval_ast(false).rewind_scope(false);
let r =
engine.call_fn_with_options::<INT>(options, &mut scope, &ast, "define_var", (2 as INT,))?;
assert_eq!(r, 42);
assert_eq!(
@ -87,9 +86,13 @@ fn test_call_fn_scope() -> Result<(), Box<EvalAltResult>> {
for _ in 0..50 {
assert_eq!(
engine
.call_fn_raw(&mut scope, &ast, true, false, "foo", None, [Dynamic::THREE])?
.as_int()?,
engine.call_fn_with_options::<INT>(
CallFnOptions::new().rewind_scope(false),
&mut scope,
&ast,
"foo",
[Dynamic::THREE],
)?,
168
);
}

View File

@ -798,6 +798,7 @@ fn test_serde_optional() -> Result<(), Box<EvalAltResult>> {
#[test]
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
fn test_serde_blob() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
@ -805,13 +806,16 @@ fn test_serde_blob() -> Result<(), Box<EvalAltResult>> {
"
let x = blob(10);
for i in 0..10 { x[i] = i; }
x
#{ x: x }
",
)?;
let r = from_dynamic::<serde_bytes::ByteBuf>(&r)?;
let data = format!("{r:?}");
assert_eq!(r.to_vec(), vec![0_u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
let encoded = rmp_serde::to_vec(&r).unwrap();
let decoded: Dynamic = rmp_serde::from_slice(&encoded).unwrap();
assert_eq!(format!("{decoded:?}"), data);
Ok(())
}