diff --git a/CHANGELOG.md b/CHANGELOG.md index c2d239e2..b78a3b79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ Rhai Release Notes ================== +Version 1.3.0 +============= + +Enhancements +------------ + +* Added `into_array` and `into_typed_array` for `Dynamic`. + + Version 1.2.0 ============= diff --git a/src/ast.rs b/src/ast.rs index 992c1d21..2e2caabc 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1862,7 +1862,7 @@ pub struct FnCallExpr { /// Constant arguments are very common in function calls, and keeping each constant in /// an [`Expr::DynamicConstant`] involves an additional allocation. Keeping the constant /// values in an inlined array avoids these extra allocations. - pub constants: smallvec::SmallVec<[Dynamic; 2]>, + pub constants: StaticVec, /// Does this function call capture the parent scope? pub capture_parent_scope: bool, } diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index f401e104..7fcd54f9 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -1798,7 +1798,7 @@ impl Dynamic { _ => None, } } - /// Cast the [`Dynamic`] as a unit `()` and return it. + /// Cast the [`Dynamic`] as a unit `()`. /// Returns the name of the actual type if the cast fails. #[inline] pub fn as_unit(&self) -> Result<(), &'static str> { @@ -1809,7 +1809,7 @@ impl Dynamic { _ => Err(self.type_name()), } } - /// Cast the [`Dynamic`] as the system integer type [`INT`] and return it. + /// Cast the [`Dynamic`] as the system integer type [`INT`]. /// Returns the name of the actual type if the cast fails. #[inline] pub fn as_int(&self) -> Result { @@ -1820,7 +1820,7 @@ impl Dynamic { _ => Err(self.type_name()), } } - /// Cast the [`Dynamic`] as the system floating-point type [`FLOAT`] and return it. + /// Cast the [`Dynamic`] as the system floating-point type [`FLOAT`]. /// Returns the name of the actual type if the cast fails. /// /// Not available under `no_float`. @@ -1834,7 +1834,7 @@ impl Dynamic { _ => Err(self.type_name()), } } - /// _(decimal)_ Cast the [`Dynamic`] as a [`Decimal`](https://docs.rs/rust_decimal) and return it. + /// _(decimal)_ Cast the [`Dynamic`] as a [`Decimal`](https://docs.rs/rust_decimal). /// Returns the name of the actual type if the cast fails. /// /// Exported under the `decimal` feature only. @@ -1848,7 +1848,7 @@ impl Dynamic { _ => Err(self.type_name()), } } - /// Cast the [`Dynamic`] as a [`bool`] and return it. + /// Cast the [`Dynamic`] as a [`bool`]. /// Returns the name of the actual type if the cast fails. #[inline] pub fn as_bool(&self) -> Result { @@ -1859,7 +1859,7 @@ impl Dynamic { _ => Err(self.type_name()), } } - /// Cast the [`Dynamic`] as a [`char`] and return it. + /// Cast the [`Dynamic`] as a [`char`]. /// Returns the name of the actual type if the cast fails. #[inline] pub fn as_char(&self) -> Result { @@ -1870,7 +1870,7 @@ impl Dynamic { _ => Err(self.type_name()), } } - /// Cast the [`Dynamic`] as an [`ImmutableString`] and return it as a string slice. + /// Cast the [`Dynamic`] as a string slice. /// Returns the name of the actual type if the cast fails. /// /// # Panics @@ -1885,7 +1885,7 @@ impl Dynamic { _ => Err(self.type_name()), } } - /// Convert the [`Dynamic`] into a [`String`] and return it. + /// Convert the [`Dynamic`] into a [`String`]. /// If there are other references to the same string, a cloned copy is returned. /// Returns the name of the actual type if the cast fails. #[inline] @@ -1893,7 +1893,7 @@ impl Dynamic { self.into_immutable_string() .map(ImmutableString::into_owned) } - /// Convert the [`Dynamic`] into an [`ImmutableString`] and return it. + /// Convert the [`Dynamic`] into an [`ImmutableString`]. /// Returns the name of the actual type if the cast fails. #[inline] pub fn into_immutable_string(self) -> Result { @@ -2021,6 +2021,82 @@ impl std::iter::FromIterator for Dynamic { )) } } +#[cfg(not(feature = "no_index"))] +impl Dynamic { + /// Convert the [`Dynamic`] into an [`Array`]. + /// Returns the name of the actual type if the cast fails. + #[inline(always)] + pub fn into_array(self) -> Result { + match self.0 { + Union::Array(a, _, _) => Ok(*a), + #[cfg(not(feature = "no_closure"))] + Union::Shared(cell, _, _) => { + #[cfg(not(feature = "sync"))] + let value = cell.borrow(); + #[cfg(feature = "sync")] + let value = cell.read().unwrap(); + + match value.0 { + Union::Array(ref a, _, _) => Ok(a.as_ref().clone()), + _ => Err((*value).type_name()), + } + } + _ => Err(self.type_name()), + } + } + /// Convert the [`Dynamic`] into a [`Vec`]. + /// Returns the name of the actual type if any cast fails. + #[inline(always)] + pub fn into_typed_array(self) -> Result, &'static str> { + match self.0 { + Union::Array(a, _, _) => a + .into_iter() + .map(|v| { + #[cfg(not(feature = "no_closure"))] + let typ = if v.is_shared() { + // Avoid panics/deadlocks with shared values + "" + } else { + v.type_name() + }; + #[cfg(feature = "no_closure")] + let typ = v.type_name(); + + v.try_cast::().ok_or_else(|| typ) + }) + .collect(), + #[cfg(not(feature = "no_closure"))] + Union::Shared(cell, _, _) => { + #[cfg(not(feature = "sync"))] + let value = cell.borrow(); + #[cfg(feature = "sync")] + let value = cell.read().unwrap(); + + match value.0 { + Union::Array(ref a, _, _) => { + a.iter() + .map(|v| { + #[cfg(not(feature = "no_closure"))] + let typ = if v.is_shared() { + // Avoid panics/deadlocks with shared values + "" + } else { + v.type_name() + }; + #[cfg(feature = "no_closure")] + let typ = v.type_name(); + + v.read_lock::().ok_or_else(|| typ).map(|v| v.clone()) + }) + .collect() + } + _ => Err((*value).type_name()), + } + } + _ => Err(self.type_name()), + } + } +} #[cfg(not(feature = "no_object"))] impl Dynamic { /// Create a [`Dynamic`] from a [`Map`]. diff --git a/src/types/scope.rs b/src/types/scope.rs index 02094bea..b6eae95b 100644 --- a/src/types/scope.rs +++ b/src/types/scope.rs @@ -10,13 +10,13 @@ use std::{borrow::Cow, iter::Extend}; /// Keep a number of entries inline (since [`Dynamic`] is usually small enough). const SCOPE_ENTRIES_INLINED: usize = 8; -/// Type containing information about the current scope. -/// Useful for keeping state between [`Engine`][crate::Engine] evaluation runs. +/// Type containing information about the current scope. Useful for keeping state between +/// [`Engine`][crate::Engine] evaluation runs. /// /// # Thread Safety /// -/// Currently, [`Scope`] is neither [`Send`] nor [`Sync`]. -/// Turn on the `sync` feature to make it [`Send`] `+` [`Sync`]. +/// Currently, [`Scope`] is neither [`Send`] nor [`Sync`]. Turn on the `sync` feature to make it +/// [`Send`] `+` [`Sync`]. /// /// # Example /// @@ -39,17 +39,19 @@ const SCOPE_ENTRIES_INLINED: usize = 8; /// # } /// ``` /// -/// When searching for entries, newly-added entries are found before similarly-named but older entries, -/// allowing for automatic _shadowing_. +/// When searching for entries, newly-added entries are found before similarly-named but older +/// entries, allowing for automatic _shadowing_. // // # Implementation Notes // -// [`Scope`] is implemented as two [`Vec`]'s of exactly the same length. Variables data (name, type, etc.) -// is manually split into two equal-length arrays. That's because variable names take up the most space, -// with [`Cow`][Cow] being four words long, but in the vast majority of cases the name is NOT used to -// look up a variable. Variable lookup is usually via direct indexing, by-passing the name altogether. +// [`Scope`] is implemented as two [`Vec`]'s of exactly the same length. Variables data (name, +// type, etc.) is manually split into two equal-length arrays. That's because variable names take +// up the most space, with [`Cow`][Cow] being four words long, but in the vast majority of +// cases the name is NOT used to look up a variable. Variable lookup is usually via direct +// indexing, by-passing the name altogether. // -// Since [`Dynamic`] is reasonably small, packing it tightly improves cache locality when variables are accessed. +// Since [`Dynamic`] is reasonably small, packing it tightly improves cache locality when variables +// are accessed. #[derive(Debug, Clone, Hash, Default)] pub struct Scope<'a> { /// Current value of the entry. @@ -493,7 +495,7 @@ impl<'a> Scope<'a> { pub(crate) fn get_mut_by_index(&mut self, index: usize) -> &mut Dynamic { self.values.get_mut(index).expect("valid index") } - /// Update the access type of an entry in the [`Scope`]. + /// Add an alias to an entry in the [`Scope`]. /// /// # Panics /// diff --git a/tests/arrays.rs b/tests/arrays.rs index a230e561..0c02977c 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -1,9 +1,5 @@ #![cfg(not(feature = "no_index"))] -use rhai::{Array, Engine, EvalAltResult, INT}; - -fn convert_to_vec(array: Array) -> Vec { - array.into_iter().map(|v| v.clone_cast::()).collect() -} +use rhai::{Array, Dynamic, Engine, EvalAltResult, INT}; #[test] fn test_arrays() -> Result<(), Box> { @@ -27,30 +23,42 @@ fn test_arrays() -> Result<(), Box> { assert!(engine.eval::("let y = [1, 2, 3]; 2 in y")?); assert_eq!(engine.eval::("let y = [1, 2, 3]; y += 4; y[3]")?, 4); assert_eq!( - convert_to_vec::(engine.eval("let y = [1, 2, 3]; y[1] += 4; y")?), + engine + .eval::("let y = [1, 2, 3]; y[1] += 4; y")? + .into_typed_array::()?, [1, 6, 3] ); #[cfg(not(feature = "no_object"))] { assert_eq!( - convert_to_vec::(engine.eval("let y = [1, 2, 3]; y.push(4); y")?), + engine + .eval::("let y = [1, 2, 3]; y.push(4); y")? + .into_typed_array::()?, [1, 2, 3, 4] ); assert_eq!( - convert_to_vec::(engine.eval("let y = [1, 2, 3]; y.insert(0, 4); y")?), + engine + .eval::("let y = [1, 2, 3]; y.insert(0, 4); y")? + .into_typed_array::()?, [4, 1, 2, 3] ); assert_eq!( - convert_to_vec::(engine.eval("let y = [1, 2, 3]; y.insert(999, 4); y")?), + engine + .eval::("let y = [1, 2, 3]; y.insert(999, 4); y")? + .into_typed_array::()?, [1, 2, 3, 4] ); assert_eq!( - convert_to_vec::(engine.eval("let y = [1, 2, 3]; y.insert(-2, 4); y")?), + engine + .eval::("let y = [1, 2, 3]; y.insert(-2, 4); y")? + .into_typed_array::()?, [1, 4, 2, 3] ); assert_eq!( - convert_to_vec::(engine.eval("let y = [1, 2, 3]; y.insert(-999, 4); y")?), + engine + .eval::("let y = [1, 2, 3]; y.insert(-999, 4); y")? + .into_typed_array::()?, [4, 1, 2, 3] ); assert_eq!( @@ -67,43 +75,49 @@ fn test_arrays() -> Result<(), Box> { ); assert_eq!( - convert_to_vec::(engine.eval( - " - let x = [2, 9]; - x.insert(-1, 1); - x.insert(999, 3); - x.insert(-9, 99); + engine + .eval::( + " + let x = [2, 9]; + x.insert(-1, 1); + x.insert(999, 3); + x.insert(-9, 99); - let r = x.remove(2); + let r = x.remove(2); - let y = [4, 5]; - x.append(y); + let y = [4, 5]; + x.append(y); - x - " - )?), + x + " + )? + .into_typed_array::()?, [99, 2, 9, 3, 4, 5] ); } assert_eq!( - convert_to_vec::(engine.eval( - " - let x = [1, 2, 3]; - x += [4, 5]; - x - " - )?), + engine + .eval::( + " + let x = [1, 2, 3]; + x += [4, 5]; + x + " + )? + .into_typed_array::()?, [1, 2, 3, 4, 5] ); assert_eq!( - convert_to_vec::(engine.eval( - " - let x = [1, 2, 3]; - let y = [4, 5]; - x + y - " - )?), + engine + .eval::( + " + let x = [1, 2, 3]; + let y = [4, 5]; + x + y + " + )? + .into_typed_array::()?, [1, 2, 3, 4, 5] ); @@ -172,42 +186,50 @@ fn test_arrays_map_reduce() -> Result<(), Box> { assert_eq!(engine.eval::("([1].map(|x| x + 41))[0]")?, 42); assert_eq!( - convert_to_vec::(engine.eval( - " - let x = [1, 2, 3]; - x.filter(|v| v > 2) - " - )?), + engine + .eval::( + " + let x = [1, 2, 3]; + x.filter(|v| v > 2) + " + )? + .into_typed_array::()?, [3] ); assert_eq!( - convert_to_vec::(engine.eval( - " - let x = [1, 2, 3]; - x.filter(|v, i| v > i) - " - )?), + engine + .eval::( + " + let x = [1, 2, 3]; + x.filter(|v, i| v > i) + " + )? + .into_typed_array::()?, [1, 2, 3] ); assert_eq!( - convert_to_vec::(engine.eval( - " - let x = [1, 2, 3]; - x.map(|v| v * 2) - " - )?), + engine + .eval::( + " + let x = [1, 2, 3]; + x.map(|v| v * 2) + " + )? + .into_typed_array::()?, [2, 4, 6] ); assert_eq!( - convert_to_vec::(engine.eval( - " - let x = [1, 2, 3]; - x.map(|v, i| v * i) - " - )?), + engine + .eval::( + " + let x = [1, 2, 3]; + x.map(|v, i| v * i) + " + )? + .into_typed_array::()?, [0, 2, 6] );