Move constancy into Dynamic.

This commit is contained in:
Stephen Chung 2020-12-08 22:47:38 +08:00
parent 8e8069f819
commit f22a04fc74
14 changed files with 501 additions and 430 deletions

View File

@ -75,7 +75,7 @@ The Rhai Scripting Language
5. [Variables](language/variables.md) 5. [Variables](language/variables.md)
6. [Constants](language/constants.md) 6. [Constants](language/constants.md)
7. [Logic Operators](language/logic.md) 7. [Logic Operators](language/logic.md)
8. [Other Operators](language/other-op.md) 8. [Assignment Operators](language/assignment-op.md)
9. [If Statement](language/if.md) 9. [If Statement](language/if.md)
10. [Switch Expression](language/switch.md) 10. [Switch Expression](language/switch.md)
11. [While Loop](language/while.md) 11. [While Loop](language/while.md)

View File

@ -64,6 +64,15 @@ are spliced into the script text in order to turn on/off certain sections.
For fixed script texts, the constant values can be provided in a user-defined [`Scope`] object For fixed script texts, the constant values can be provided in a user-defined [`Scope`] object
to the [`Engine`] for use in compilation and evaluation. to the [`Engine`] for use in compilation and evaluation.
### Caveat
If the [constants] are modified later on (yes, it is possible, via Rust functions),
the modified values will not show up in the optimized script.
Only the initialization values of [constants] are ever retained.
This is almost never a problem because real-world scripts seldom modify a constant,
but the possibility is always there.
Eager Operator Evaluations Eager Operator Evaluations
------------------------- -------------------------

View File

@ -11,6 +11,14 @@ There are three levels of optimization: `None`, `Simple` and `Full`.
(i.e. it only relies on static analysis and [built-in operators] for constant [standard types], (i.e. it only relies on static analysis and [built-in operators] for constant [standard types],
and will not perform any external function calls). and will not perform any external function calls).
However, it is important to bear in mind that _constants propagation_ is performed with the
caveat that, if [constants] are modified later on (yes, it is possible, via Rust functions),
the modified values will not show up in the optimized script. Only the initialization values
of [constants] are ever retained.
Furthermore, overriding a [built-in operator][built-in operators] in the [`Engine`] afterwards
has no effect after the optimizer replaces an expression with its calculated value.
* `Full` is _much_ more aggressive, _including_ calling external functions on constant arguments to determine their result. * `Full` is _much_ more aggressive, _including_ calling external functions on constant arguments to determine their result.
One benefit to this is that many more optimization opportunities arise, especially with regards to comparison operators. One benefit to this is that many more optimization opportunities arise, especially with regards to comparison operators.

View File

@ -1,12 +1,9 @@
Other Operators Compound Assignment Operators
=============== =============================
{{#include ../links.md}} {{#include ../links.md}}
Compound Assignment Operators
----------------------------
```rust ```rust
let number = 9; let number = 9;
@ -64,3 +61,6 @@ my_obj += #{c:3, d:4, e:5};
my_obj.len() == 5; my_obj.len() == 5;
``` ```
In fact, the `+` and `+=` operators are usually [overloaded][function overloading] when
something is to be _added_ to an existing type.

View File

@ -61,9 +61,31 @@ r"
``` ```
Constants Can be Modified, Just Not Reassigned Constants Can be Modified via Rust
--------------------------------------------- ---------------------------------
A custom type stored as a constant can be modified via its registered API - A custom type stored as a constant cannot be modified via script, but _can_ be modified via
being a constant only prevents it from being re-assigned or operated upon by Rhai; a registered Rust function that takes a first `&mut` parameter - because there is no way for
mutating it via a Rust function is still allowed. Rhai to know whether the Rust function modifies its argument!
```rust
const x = 42; // a constant
x.increment(); // call 'increment' defined in Rust with '&mut' first parameter
x == 43; // value of 'x' is changed!
fn double() {
this *= 2; // function squares 'this'
}
x.double(); // <- error: cannot modify constant 'this'
x == 43; // value of 'x' is unchanged by script
```
This is important to keep in mind because the script [optimizer][script optimization]
by default does _constant propagation_ as a operation.
If a constant is eventually modified by a Rust function, the optimizer will not see
the updated value and will propagate the original initialization value instead.

View File

@ -68,6 +68,8 @@
[variable]: {{rootUrl}}/language/variables.md [variable]: {{rootUrl}}/language/variables.md
[variables]: {{rootUrl}}/language/variables.md [variables]: {{rootUrl}}/language/variables.md
[constant]: {{rootUrl}}/language/constants.md
[constants]: {{rootUrl}}/language/constants.md
[string]: {{rootUrl}}/language/strings-chars.md [string]: {{rootUrl}}/language/strings-chars.md
[strings]: {{rootUrl}}/language/strings-chars.md [strings]: {{rootUrl}}/language/strings-chars.md

View File

@ -1,6 +1,6 @@
//! Module defining the AST (abstract syntax tree). //! Module defining the AST (abstract syntax tree).
use crate::dynamic::Union; use crate::dynamic::{AccessType, Union};
use crate::fn_native::shared_make_mut; use crate::fn_native::shared_make_mut;
use crate::module::NamespaceRef; use crate::module::NamespaceRef;
use crate::stdlib::{ use crate::stdlib::{
@ -940,10 +940,10 @@ impl Expr {
Self::FloatConstant(x, _) => (*x).into(), Self::FloatConstant(x, _) => (*x).into(),
Self::CharConstant(x, _) => (*x).into(), Self::CharConstant(x, _) => (*x).into(),
Self::StringConstant(x, _) => x.clone().into(), Self::StringConstant(x, _) => x.clone().into(),
Self::FnPointer(x, _) => Dynamic(Union::FnPtr(Box::new(FnPtr::new_unchecked( Self::FnPointer(x, _) => Dynamic(Union::FnPtr(
x.clone(), Box::new(FnPtr::new_unchecked(x.clone(), Default::default())),
Default::default(), AccessType::Constant,
)))), )),
Self::BoolConstant(x, _) => (*x).into(), Self::BoolConstant(x, _) => (*x).into(),
Self::Unit(_) => ().into(), Self::Unit(_) => ().into(),
@ -954,7 +954,7 @@ impl Expr {
x.len(), x.len(),
)); ));
arr.extend(x.iter().map(|v| v.get_constant_value().unwrap())); arr.extend(x.iter().map(|v| v.get_constant_value().unwrap()));
Dynamic(Union::Array(Box::new(arr))) Dynamic(Union::Array(Box::new(arr), AccessType::Constant))
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -967,7 +967,7 @@ impl Expr {
x.iter() x.iter()
.map(|(k, v)| (k.name.clone(), v.get_constant_value().unwrap())), .map(|(k, v)| (k.name.clone(), v.get_constant_value().unwrap())),
); );
Dynamic(Union::Map(Box::new(map))) Dynamic(Union::Map(Box::new(map), AccessType::Constant))
} }
_ => return None, _ => return None,
@ -1167,7 +1167,7 @@ mod tests {
assert_eq!(size_of::<Option<crate::ast::Expr>>(), 16); assert_eq!(size_of::<Option<crate::ast::Expr>>(), 16);
assert_eq!(size_of::<crate::ast::Stmt>(), 32); assert_eq!(size_of::<crate::ast::Stmt>(), 32);
assert_eq!(size_of::<Option<crate::ast::Stmt>>(), 32); assert_eq!(size_of::<Option<crate::ast::Stmt>>(), 32);
assert_eq!(size_of::<crate::Scope>(), 72); assert_eq!(size_of::<crate::Scope>(), 48);
assert_eq!(size_of::<crate::LexError>(), 56); assert_eq!(size_of::<crate::LexError>(), 56);
assert_eq!(size_of::<crate::ParseError>(), 16); assert_eq!(size_of::<crate::ParseError>(), 16);
assert_eq!(size_of::<crate::EvalAltResult>(), 72); assert_eq!(size_of::<crate::EvalAltResult>(), 72);

View File

@ -116,6 +116,25 @@ impl dyn Variant {
} }
} }
/// Type of an entry in the Scope.
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
pub enum AccessType {
/// Normal value.
Normal,
/// Immutable constant value.
Constant,
}
impl AccessType {
/// Is the access type [`Constant`]?
pub fn is_constant(self) -> bool {
match self {
Self::Normal => false,
Self::Constant => true,
}
}
}
/// Dynamic type containing any value. /// Dynamic type containing any value.
pub struct Dynamic(pub(crate) Union); pub struct Dynamic(pub(crate) Union);
@ -123,25 +142,25 @@ pub struct Dynamic(pub(crate) Union);
/// ///
/// Most variants are boxed to reduce the size. /// Most variants are boxed to reduce the size.
pub enum Union { pub enum Union {
Unit(()), Unit((), AccessType),
Bool(bool), Bool(bool, AccessType),
Str(ImmutableString), Str(ImmutableString, AccessType),
Char(char), Char(char, AccessType),
Int(INT), Int(INT, AccessType),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Float(FLOAT), Float(FLOAT, AccessType),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Array(Box<Array>), Array(Box<Array>, AccessType),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Map(Box<Map>), Map(Box<Map>, AccessType),
FnPtr(Box<FnPtr>), FnPtr(Box<FnPtr>, AccessType),
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
TimeStamp(Box<Instant>), TimeStamp(Box<Instant>, AccessType),
Variant(Box<Box<dyn Variant>>), Variant(Box<Box<dyn Variant>>, AccessType),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Shared(crate::Shared<crate::Locked<Dynamic>>), Shared(crate::Shared<crate::Locked<Dynamic>>, AccessType),
} }
/// Underlying [`Variant`] read guard for [`Dynamic`]. /// Underlying [`Variant`] read guard for [`Dynamic`].
@ -236,7 +255,7 @@ impl Dynamic {
#[inline(always)] #[inline(always)]
pub fn is_variant(&self) -> bool { pub fn is_variant(&self) -> bool {
match self.0 { match self.0 {
Union::Variant(_) => true, Union::Variant(_, _) => true,
_ => false, _ => false,
} }
} }
@ -246,7 +265,7 @@ impl Dynamic {
pub fn is_shared(&self) -> bool { pub fn is_shared(&self) -> bool {
match self.0 { match self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => true, Union::Shared(_, _) => true,
_ => false, _ => false,
} }
} }
@ -272,29 +291,29 @@ impl Dynamic {
/// Otherwise, this call panics if the data is currently borrowed for write. /// Otherwise, this call panics if the data is currently borrowed for write.
pub fn type_id(&self) -> TypeId { pub fn type_id(&self) -> TypeId {
match &self.0 { match &self.0 {
Union::Unit(_) => TypeId::of::<()>(), Union::Unit(_, _) => TypeId::of::<()>(),
Union::Bool(_) => TypeId::of::<bool>(), Union::Bool(_, _) => TypeId::of::<bool>(),
Union::Str(_) => TypeId::of::<ImmutableString>(), Union::Str(_, _) => TypeId::of::<ImmutableString>(),
Union::Char(_) => TypeId::of::<char>(), Union::Char(_, _) => TypeId::of::<char>(),
Union::Int(_) => TypeId::of::<INT>(), Union::Int(_, _) => TypeId::of::<INT>(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(_) => TypeId::of::<FLOAT>(), Union::Float(_, _) => TypeId::of::<FLOAT>(),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(_) => TypeId::of::<Array>(), Union::Array(_, _) => TypeId::of::<Array>(),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(_) => TypeId::of::<Map>(), Union::Map(_, _) => TypeId::of::<Map>(),
Union::FnPtr(_) => TypeId::of::<FnPtr>(), Union::FnPtr(_, _) => TypeId::of::<FnPtr>(),
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::TimeStamp(_) => TypeId::of::<Instant>(), Union::TimeStamp(_, _) => TypeId::of::<Instant>(),
Union::Variant(value) => (***value).type_id(), Union::Variant(value, _) => (***value).type_id(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
Union::Shared(cell) => (*cell.borrow()).type_id(), Union::Shared(cell, _) => (*cell.borrow()).type_id(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Union::Shared(cell) => (*cell.read().unwrap()).type_id(), Union::Shared(cell, _) => (*cell.read().unwrap()).type_id(),
} }
} }
/// Get the name of the type of the value held by this [`Dynamic`]. /// Get the name of the type of the value held by this [`Dynamic`].
@ -305,32 +324,32 @@ impl Dynamic {
/// Otherwise, this call panics if the data is currently borrowed for write. /// Otherwise, this call panics if the data is currently borrowed for write.
pub fn type_name(&self) -> &'static str { pub fn type_name(&self) -> &'static str {
match &self.0 { match &self.0 {
Union::Unit(_) => "()", Union::Unit(_, _) => "()",
Union::Bool(_) => "bool", Union::Bool(_, _) => "bool",
Union::Str(_) => "string", Union::Str(_, _) => "string",
Union::Char(_) => "char", Union::Char(_, _) => "char",
Union::Int(_) => type_name::<INT>(), Union::Int(_, _) => type_name::<INT>(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(_) => type_name::<FLOAT>(), Union::Float(_, _) => type_name::<FLOAT>(),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(_) => "array", Union::Array(_, _) => "array",
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(_) => "map", Union::Map(_, _) => "map",
Union::FnPtr(_) => "Fn", Union::FnPtr(_, _) => "Fn",
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::TimeStamp(_) => "timestamp", Union::TimeStamp(_, _) => "timestamp",
Union::Variant(value) => (***value).type_name(), Union::Variant(value, _) => (***value).type_name(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
Union::Shared(cell) => cell Union::Shared(cell, _) => cell
.try_borrow() .try_borrow()
.map(|v| (*v).type_name()) .map(|v| (*v).type_name())
.unwrap_or("<shared>"), .unwrap_or("<shared>"),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Union::Shared(cell) => (*cell.read().unwrap()).type_name(), Union::Shared(cell, _) => (*cell.read().unwrap()).type_name(),
} }
} }
} }
@ -340,17 +359,17 @@ impl Hash for Dynamic {
mem::discriminant(self).hash(state); mem::discriminant(self).hash(state);
match &self.0 { match &self.0 {
Union::Unit(_) => ().hash(state), Union::Unit(_, _) => ().hash(state),
Union::Bool(value) => value.hash(state), Union::Bool(value, _) => value.hash(state),
Union::Str(s) => s.hash(state), Union::Str(s, _) => s.hash(state),
Union::Char(ch) => ch.hash(state), Union::Char(ch, _) => ch.hash(state),
Union::Int(i) => i.hash(state), Union::Int(i, _) => i.hash(state),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(f) => f.to_le_bytes().hash(state), Union::Float(f, _) => f.to_le_bytes().hash(state),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(a) => (**a).hash(state), Union::Array(a, _) => (**a).hash(state),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(m) => { Union::Map(m, _) => {
let mut buf: crate::StaticVec<_> = m.iter().collect(); let mut buf: crate::StaticVec<_> = m.iter().collect();
buf.sort_by(|(a, _), (b, _)| a.cmp(b)); buf.sort_by(|(a, _), (b, _)| a.cmp(b));
@ -362,10 +381,10 @@ impl Hash for Dynamic {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
Union::Shared(cell) => (*cell.borrow()).hash(state), Union::Shared(cell, _) => (*cell.borrow()).hash(state),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Union::Shared(cell) => (*cell.read().unwrap()).hash(state), Union::Shared(cell, _) => (*cell.read().unwrap()).hash(state),
_ => unimplemented!(), _ => unimplemented!(),
} }
@ -404,29 +423,29 @@ pub(crate) fn map_std_type_name(name: &str) -> &str {
impl fmt::Display for Dynamic { impl fmt::Display for Dynamic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 { match &self.0 {
Union::Unit(_) => write!(f, ""), Union::Unit(_, _) => write!(f, ""),
Union::Bool(value) => fmt::Display::fmt(value, f), Union::Bool(value, _) => fmt::Display::fmt(value, f),
Union::Str(value) => fmt::Display::fmt(value, f), Union::Str(value, _) => fmt::Display::fmt(value, f),
Union::Char(value) => fmt::Display::fmt(value, f), Union::Char(value, _) => fmt::Display::fmt(value, f),
Union::Int(value) => fmt::Display::fmt(value, f), Union::Int(value, _) => fmt::Display::fmt(value, f),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(value) => fmt::Display::fmt(value, f), Union::Float(value, _) => fmt::Display::fmt(value, f),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(value) => fmt::Debug::fmt(value, f), Union::Array(value, _) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(value) => { Union::Map(value, _) => {
f.write_str("#")?; f.write_str("#")?;
fmt::Debug::fmt(value, f) fmt::Debug::fmt(value, f)
} }
Union::FnPtr(value) => fmt::Display::fmt(value, f), Union::FnPtr(value, _) => fmt::Display::fmt(value, f),
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::TimeStamp(_) => f.write_str("<timestamp>"), Union::TimeStamp(_, _) => f.write_str("<timestamp>"),
Union::Variant(value) => f.write_str((*value).type_name()), Union::Variant(value, _) => f.write_str((*value).type_name()),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
Union::Shared(cell) => { Union::Shared(cell, _) => {
if let Ok(v) = cell.try_borrow() { if let Ok(v) = cell.try_borrow() {
fmt::Display::fmt(&*v, f) fmt::Display::fmt(&*v, f)
} else { } else {
@ -435,7 +454,7 @@ impl fmt::Display for Dynamic {
} }
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Union::Shared(cell) => fmt::Display::fmt(&*cell.read().unwrap(), f), Union::Shared(cell, _) => fmt::Display::fmt(&*cell.read().unwrap(), f),
} }
} }
} }
@ -443,29 +462,29 @@ impl fmt::Display for Dynamic {
impl fmt::Debug for Dynamic { impl fmt::Debug for Dynamic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 { match &self.0 {
Union::Unit(value) => fmt::Debug::fmt(value, f), Union::Unit(value, _) => fmt::Debug::fmt(value, f),
Union::Bool(value) => fmt::Debug::fmt(value, f), Union::Bool(value, _) => fmt::Debug::fmt(value, f),
Union::Str(value) => fmt::Debug::fmt(value, f), Union::Str(value, _) => fmt::Debug::fmt(value, f),
Union::Char(value) => fmt::Debug::fmt(value, f), Union::Char(value, _) => fmt::Debug::fmt(value, f),
Union::Int(value) => fmt::Debug::fmt(value, f), Union::Int(value, _) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(value) => fmt::Debug::fmt(value, f), Union::Float(value, _) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(value) => fmt::Debug::fmt(value, f), Union::Array(value, _) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(value) => { Union::Map(value, _) => {
f.write_str("#")?; f.write_str("#")?;
fmt::Debug::fmt(value, f) fmt::Debug::fmt(value, f)
} }
Union::FnPtr(value) => fmt::Debug::fmt(value, f), Union::FnPtr(value, _) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::TimeStamp(_) => write!(f, "<timestamp>"), Union::TimeStamp(_, _) => write!(f, "<timestamp>"),
Union::Variant(value) => write!(f, "{}", (*value).type_name()), Union::Variant(value, _) => write!(f, "{}", (*value).type_name()),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
Union::Shared(cell) => { Union::Shared(cell, _) => {
if let Ok(v) = cell.try_borrow() { if let Ok(v) = cell.try_borrow() {
write!(f, "{:?} (shared)", *v) write!(f, "{:?} (shared)", *v)
} else { } else {
@ -474,33 +493,40 @@ impl fmt::Debug for Dynamic {
} }
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Union::Shared(cell) => fmt::Debug::fmt(&*cell.read().unwrap(), f), Union::Shared(cell, _) => fmt::Debug::fmt(&*cell.read().unwrap(), f),
} }
} }
} }
impl Clone for Dynamic { impl Clone for Dynamic {
/// Clone the [`Dynamic`] value.
///
/// ## WARNING
///
/// The cloned copy is marked [`AccessType::Normal`] even if the original is constant.
fn clone(&self) -> Self { fn clone(&self) -> Self {
match self.0 { match self.0 {
Union::Unit(value) => Self(Union::Unit(value)), Union::Unit(value, _) => Self(Union::Unit(value, AccessType::Normal)),
Union::Bool(value) => Self(Union::Bool(value)), Union::Bool(value, _) => Self(Union::Bool(value, AccessType::Normal)),
Union::Str(ref value) => Self(Union::Str(value.clone())), Union::Str(ref value, _) => Self(Union::Str(value.clone(), AccessType::Normal)),
Union::Char(value) => Self(Union::Char(value)), Union::Char(value, _) => Self(Union::Char(value, AccessType::Normal)),
Union::Int(value) => Self(Union::Int(value)), Union::Int(value, _) => Self(Union::Int(value, AccessType::Normal)),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(value) => Self(Union::Float(value)), Union::Float(value, _) => Self(Union::Float(value, AccessType::Normal)),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(ref value) => Self(Union::Array(value.clone())), Union::Array(ref value, _) => Self(Union::Array(value.clone(), AccessType::Normal)),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(ref value) => Self(Union::Map(value.clone())), Union::Map(ref value, _) => Self(Union::Map(value.clone(), AccessType::Normal)),
Union::FnPtr(ref value) => Self(Union::FnPtr(value.clone())), Union::FnPtr(ref value, _) => Self(Union::FnPtr(value.clone(), AccessType::Normal)),
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::TimeStamp(ref value) => Self(Union::TimeStamp(value.clone())), Union::TimeStamp(ref value, _) => {
Self(Union::TimeStamp(value.clone(), AccessType::Normal))
}
Union::Variant(ref value) => (***value).clone_into_dynamic(), Union::Variant(ref value, _) => (***value).clone_into_dynamic(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(ref cell) => Self(Union::Shared(cell.clone())), Union::Shared(ref cell, _) => Self(Union::Shared(cell.clone(), AccessType::Normal)),
} }
} }
} }
@ -514,27 +540,65 @@ impl Default for Dynamic {
impl Dynamic { impl Dynamic {
/// A [`Dynamic`] containing a `()`. /// A [`Dynamic`] containing a `()`.
pub const UNIT: Dynamic = Self(Union::Unit(())); pub const UNIT: Dynamic = Self(Union::Unit((), AccessType::Normal));
/// A [`Dynamic`] containing a `true`. /// A [`Dynamic`] containing a `true`.
pub const TRUE: Dynamic = Self(Union::Bool(true)); pub const TRUE: Dynamic = Self(Union::Bool(true, AccessType::Normal));
/// A [`Dynamic`] containing a [`false`]. /// A [`Dynamic`] containing a [`false`].
pub const FALSE: Dynamic = Self(Union::Bool(false)); pub const FALSE: Dynamic = Self(Union::Bool(false, AccessType::Normal));
/// A [`Dynamic`] containing the integer zero. /// A [`Dynamic`] containing the integer zero.
pub const ZERO: Dynamic = Self(Union::Int(0)); pub const ZERO: Dynamic = Self(Union::Int(0, AccessType::Normal));
/// A [`Dynamic`] containing the integer one. /// A [`Dynamic`] containing the integer one.
pub const ONE: Dynamic = Self(Union::Int(1)); pub const ONE: Dynamic = Self(Union::Int(1, AccessType::Normal));
/// A [`Dynamic`] containing the integer negative one. /// A [`Dynamic`] containing the integer negative one.
pub const NEGATIVE_ONE: Dynamic = Self(Union::Int(-1)); pub const NEGATIVE_ONE: Dynamic = Self(Union::Int(-1, AccessType::Normal));
/// A [`Dynamic`] containing the floating-point zero. /// A [`Dynamic`] containing the floating-point zero.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub const FLOAT_ZERO: Dynamic = Self(Union::Float(0.0)); pub const FLOAT_ZERO: Dynamic = Self(Union::Float(0.0, AccessType::Normal));
/// A [`Dynamic`] containing the floating-point one. /// A [`Dynamic`] containing the floating-point one.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub const FLOAT_ONE: Dynamic = Self(Union::Float(1.0)); pub const FLOAT_ONE: Dynamic = Self(Union::Float(1.0, AccessType::Normal));
/// A [`Dynamic`] containing the floating-point negative one. /// A [`Dynamic`] containing the floating-point negative one.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub const FLOAT_NEGATIVE_ONE: Dynamic = Self(Union::Float(-1.0)); pub const FLOAT_NEGATIVE_ONE: Dynamic = Self(Union::Float(-1.0, AccessType::Normal));
/// Get the [`AccessType`] for this [`Dynamic`].
pub(crate) fn access_type(&self) -> AccessType {
match self.0 {
Union::Unit(_, access)
| Union::Bool(_, access)
| Union::Str(_, access)
| Union::Char(_, access)
| Union::Int(_, access)
| Union::Float(_, access)
| Union::Array(_, access)
| Union::Map(_, access)
| Union::FnPtr(_, access)
| Union::TimeStamp(_, access)
| Union::Variant(_, access)
| Union::Shared(_, access) => access,
}
}
/// Set the [`AccessType`] for this [`Dynamic`].
pub(crate) fn set_access_type(&mut self, typ: AccessType) {
match &mut self.0 {
Union::Unit(_, access)
| Union::Bool(_, access)
| Union::Str(_, access)
| Union::Char(_, access)
| Union::Int(_, access)
| Union::Float(_, access)
| Union::Array(_, access)
| Union::Map(_, access)
| Union::FnPtr(_, access)
| Union::TimeStamp(_, access)
| Union::Variant(_, access)
| Union::Shared(_, access) => *access = typ,
}
}
/// Is this [`Dynamic`] constant?
pub(crate) fn is_constant(&self) -> bool {
self.access_type().is_constant()
}
/// Create a [`Dynamic`] from any type. A [`Dynamic`] value is simply returned as is. /// Create a [`Dynamic`] from any type. A [`Dynamic`] value is simply returned as is.
/// ///
/// # Safety /// # Safety
@ -651,7 +715,7 @@ impl Dynamic {
} }
} }
Self(Union::Variant(Box::new(boxed))) Self(Union::Variant(Box::new(boxed), AccessType::Normal))
} }
/// Turn the [`Dynamic`] value into a shared [`Dynamic`] value backed by an [`Rc`][std::rc::Rc]`<`[`RefCell`][std::cell::RefCell]`<`[`Dynamic`]`>>` /// Turn the [`Dynamic`] value into a shared [`Dynamic`] value backed by an [`Rc`][std::rc::Rc]`<`[`RefCell`][std::cell::RefCell]`<`[`Dynamic`]`>>`
/// or [`Arc`][std::sync::Arc]`<`[`RwLock`][std::sync::RwLock]`<`[`Dynamic`]`>>` depending on the `sync` feature. /// or [`Arc`][std::sync::Arc]`<`[`RwLock`][std::sync::RwLock]`<`[`Dynamic`]`>>` depending on the `sync` feature.
@ -668,10 +732,12 @@ impl Dynamic {
/// Panics under the `no_closure` feature. /// Panics under the `no_closure` feature.
#[inline(always)] #[inline(always)]
pub fn into_shared(self) -> Self { pub fn into_shared(self) -> Self {
let _access = self.access_type();
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
return match self.0 { return match self.0 {
Union::Shared(..) => self, Union::Shared(_, _) => self,
_ => Self(Union::Shared(crate::Locked::new(self).into())), _ => Self(Union::Shared(crate::Locked::new(self).into(), _access)),
}; };
#[cfg(feature = "no_closure")] #[cfg(feature = "no_closure")]
@ -707,11 +773,11 @@ impl Dynamic {
match self.0 { match self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
Union::Shared(cell) => return cell.borrow().clone().try_cast(), Union::Shared(cell, _) => return cell.borrow().clone().try_cast(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Union::Shared(cell) => return cell.read().unwrap().clone().try_cast(), Union::Shared(cell, _) => return cell.read().unwrap().clone().try_cast(),
_ => (), _ => (),
} }
@ -721,7 +787,7 @@ impl Dynamic {
if TypeId::of::<T>() == TypeId::of::<INT>() { if TypeId::of::<T>() == TypeId::of::<INT>() {
return match self.0 { return match self.0 {
Union::Int(value) => unsafe_try_cast(value), Union::Int(value, _) => unsafe_try_cast(value),
_ => None, _ => None,
}; };
} }
@ -729,35 +795,35 @@ impl Dynamic {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
if TypeId::of::<T>() == TypeId::of::<FLOAT>() { if TypeId::of::<T>() == TypeId::of::<FLOAT>() {
return match self.0 { return match self.0 {
Union::Float(value) => unsafe_try_cast(value), Union::Float(value, _) => unsafe_try_cast(value),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<bool>() { if TypeId::of::<T>() == TypeId::of::<bool>() {
return match self.0 { return match self.0 {
Union::Bool(value) => unsafe_try_cast(value), Union::Bool(value, _) => unsafe_try_cast(value),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<ImmutableString>() { if TypeId::of::<T>() == TypeId::of::<ImmutableString>() {
return match self.0 { return match self.0 {
Union::Str(value) => unsafe_try_cast(value), Union::Str(value, _) => unsafe_try_cast(value),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<String>() { if TypeId::of::<T>() == TypeId::of::<String>() {
return match self.0 { return match self.0 {
Union::Str(value) => unsafe_try_cast(value.into_owned()), Union::Str(value, _) => unsafe_try_cast(value.into_owned()),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<char>() { if TypeId::of::<T>() == TypeId::of::<char>() {
return match self.0 { return match self.0 {
Union::Char(value) => unsafe_try_cast(value), Union::Char(value, _) => unsafe_try_cast(value),
_ => None, _ => None,
}; };
} }
@ -765,7 +831,7 @@ impl Dynamic {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<Array>() { if TypeId::of::<T>() == TypeId::of::<Array>() {
return match self.0 { return match self.0 {
Union::Array(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), Union::Array(value, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
_ => None, _ => None,
}; };
} }
@ -773,14 +839,14 @@ impl Dynamic {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
if TypeId::of::<T>() == TypeId::of::<Map>() { if TypeId::of::<T>() == TypeId::of::<Map>() {
return match self.0 { return match self.0 {
Union::Map(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), Union::Map(value, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<FnPtr>() { if TypeId::of::<T>() == TypeId::of::<FnPtr>() {
return match self.0 { return match self.0 {
Union::FnPtr(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), Union::FnPtr(value, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
_ => None, _ => None,
}; };
} }
@ -788,22 +854,22 @@ impl Dynamic {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
if TypeId::of::<T>() == TypeId::of::<Instant>() { if TypeId::of::<T>() == TypeId::of::<Instant>() {
return match self.0 { return match self.0 {
Union::TimeStamp(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), Union::TimeStamp(value, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<()>() { if TypeId::of::<T>() == TypeId::of::<()>() {
return match self.0 { return match self.0 {
Union::Unit(value) => unsafe_try_cast(value), Union::Unit(value, _) => unsafe_try_cast(value),
_ => None, _ => None,
}; };
} }
match self.0 { match self.0 {
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(), Union::Variant(value, _) => (*value).as_box_any().downcast().map(|x| *x).ok(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => unreachable!(), Union::Shared(_, _) => unreachable!(),
_ => None, _ => None,
} }
} }
@ -859,7 +925,7 @@ impl Dynamic {
pub fn flatten_clone(&self) -> Self { pub fn flatten_clone(&self) -> Self {
match &self.0 { match &self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(cell) => { Union::Shared(cell, _) => {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
return cell.borrow().clone(); return cell.borrow().clone();
@ -879,7 +945,7 @@ impl Dynamic {
pub fn flatten(self) -> Self { pub fn flatten(self) -> Self {
match self.0 { match self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(cell) => crate::fn_native::shared_try_take(cell).map_or_else( Union::Shared(cell, _) => crate::fn_native::shared_try_take(cell).map_or_else(
|cell| { |cell| {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
return cell.borrow().clone(); return cell.borrow().clone();
@ -907,7 +973,7 @@ impl Dynamic {
pub fn is_locked(&self) -> bool { pub fn is_locked(&self) -> bool {
match self.0 { match self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(ref _cell) => { Union::Shared(ref _cell, _) => {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
return _cell.try_borrow().is_err(); return _cell.try_borrow().is_err();
@ -930,7 +996,7 @@ impl Dynamic {
pub fn read_lock<T: Variant + Clone>(&self) -> Option<DynamicReadLock<T>> { pub fn read_lock<T: Variant + Clone>(&self) -> Option<DynamicReadLock<T>> {
match self.0 { match self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(ref cell) => { Union::Shared(ref cell, _) => {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
let data = cell.borrow(); let data = cell.borrow();
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
@ -962,7 +1028,7 @@ impl Dynamic {
pub fn write_lock<T: Variant + Clone>(&mut self) -> Option<DynamicWriteLock<T>> { pub fn write_lock<T: Variant + Clone>(&mut self) -> Option<DynamicWriteLock<T>> {
match self.0 { match self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(ref cell) => { Union::Shared(ref cell, _) => {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
let data = cell.borrow_mut(); let data = cell.borrow_mut();
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
@ -991,71 +1057,71 @@ impl Dynamic {
if TypeId::of::<T>() == TypeId::of::<INT>() { if TypeId::of::<T>() == TypeId::of::<INT>() {
return match &self.0 { return match &self.0 {
Union::Int(value) => <dyn Any>::downcast_ref::<T>(value), Union::Int(value, _) => <dyn Any>::downcast_ref::<T>(value),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
if TypeId::of::<T>() == TypeId::of::<FLOAT>() { if TypeId::of::<T>() == TypeId::of::<FLOAT>() {
return match &self.0 { return match &self.0 {
Union::Float(value) => <dyn Any>::downcast_ref::<T>(value), Union::Float(value, _) => <dyn Any>::downcast_ref::<T>(value),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<bool>() { if TypeId::of::<T>() == TypeId::of::<bool>() {
return match &self.0 { return match &self.0 {
Union::Bool(value) => <dyn Any>::downcast_ref::<T>(value), Union::Bool(value, _) => <dyn Any>::downcast_ref::<T>(value),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<ImmutableString>() { if TypeId::of::<T>() == TypeId::of::<ImmutableString>() {
return match &self.0 { return match &self.0 {
Union::Str(value) => <dyn Any>::downcast_ref::<T>(value), Union::Str(value, _) => <dyn Any>::downcast_ref::<T>(value),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<String>() { if TypeId::of::<T>() == TypeId::of::<String>() {
return match &self.0 { return match &self.0 {
Union::Str(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()), Union::Str(value, _) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<char>() { if TypeId::of::<T>() == TypeId::of::<char>() {
return match &self.0 { return match &self.0 {
Union::Char(value) => <dyn Any>::downcast_ref::<T>(value), Union::Char(value, _) => <dyn Any>::downcast_ref::<T>(value),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<Array>() { if TypeId::of::<T>() == TypeId::of::<Array>() {
return match &self.0 { return match &self.0 {
Union::Array(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()), Union::Array(value, _) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
if TypeId::of::<T>() == TypeId::of::<Map>() { if TypeId::of::<T>() == TypeId::of::<Map>() {
return match &self.0 { return match &self.0 {
Union::Map(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()), Union::Map(value, _) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<FnPtr>() { if TypeId::of::<T>() == TypeId::of::<FnPtr>() {
return match &self.0 { return match &self.0 {
Union::FnPtr(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()), Union::FnPtr(value, _) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
if TypeId::of::<T>() == TypeId::of::<Instant>() { if TypeId::of::<T>() == TypeId::of::<Instant>() {
return match &self.0 { return match &self.0 {
Union::TimeStamp(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()), Union::TimeStamp(value, _) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<()>() { if TypeId::of::<T>() == TypeId::of::<()>() {
return match &self.0 { return match &self.0 {
Union::Unit(value) => <dyn Any>::downcast_ref::<T>(value), Union::Unit(value, _) => <dyn Any>::downcast_ref::<T>(value),
_ => None, _ => None,
}; };
} }
@ -1064,9 +1130,9 @@ impl Dynamic {
} }
match &self.0 { match &self.0 {
Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::<T>(), Union::Variant(value, _) => value.as_ref().as_ref().as_any().downcast_ref::<T>(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => None, Union::Shared(_, _) => None,
_ => None, _ => None,
} }
} }
@ -1080,65 +1146,65 @@ impl Dynamic {
if TypeId::of::<T>() == TypeId::of::<INT>() { if TypeId::of::<T>() == TypeId::of::<INT>() {
return match &mut self.0 { return match &mut self.0 {
Union::Int(value) => <dyn Any>::downcast_mut::<T>(value), Union::Int(value, _) => <dyn Any>::downcast_mut::<T>(value),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
if TypeId::of::<T>() == TypeId::of::<FLOAT>() { if TypeId::of::<T>() == TypeId::of::<FLOAT>() {
return match &mut self.0 { return match &mut self.0 {
Union::Float(value) => <dyn Any>::downcast_mut::<T>(value), Union::Float(value, _) => <dyn Any>::downcast_mut::<T>(value),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<bool>() { if TypeId::of::<T>() == TypeId::of::<bool>() {
return match &mut self.0 { return match &mut self.0 {
Union::Bool(value) => <dyn Any>::downcast_mut::<T>(value), Union::Bool(value, _) => <dyn Any>::downcast_mut::<T>(value),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<ImmutableString>() { if TypeId::of::<T>() == TypeId::of::<ImmutableString>() {
return match &mut self.0 { return match &mut self.0 {
Union::Str(value) => <dyn Any>::downcast_mut::<T>(value), Union::Str(value, _) => <dyn Any>::downcast_mut::<T>(value),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<char>() { if TypeId::of::<T>() == TypeId::of::<char>() {
return match &mut self.0 { return match &mut self.0 {
Union::Char(value) => <dyn Any>::downcast_mut::<T>(value), Union::Char(value, _) => <dyn Any>::downcast_mut::<T>(value),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<Array>() { if TypeId::of::<T>() == TypeId::of::<Array>() {
return match &mut self.0 { return match &mut self.0 {
Union::Array(value) => <dyn Any>::downcast_mut::<T>(value.as_mut()), Union::Array(value, _) => <dyn Any>::downcast_mut::<T>(value.as_mut()),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
if TypeId::of::<T>() == TypeId::of::<Map>() { if TypeId::of::<T>() == TypeId::of::<Map>() {
return match &mut self.0 { return match &mut self.0 {
Union::Map(value) => <dyn Any>::downcast_mut::<T>(value.as_mut()), Union::Map(value, _) => <dyn Any>::downcast_mut::<T>(value.as_mut()),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<FnPtr>() { if TypeId::of::<T>() == TypeId::of::<FnPtr>() {
return match &mut self.0 { return match &mut self.0 {
Union::FnPtr(value) => <dyn Any>::downcast_mut::<T>(value.as_mut()), Union::FnPtr(value, _) => <dyn Any>::downcast_mut::<T>(value.as_mut()),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
if TypeId::of::<T>() == TypeId::of::<Instant>() { if TypeId::of::<T>() == TypeId::of::<Instant>() {
return match &mut self.0 { return match &mut self.0 {
Union::TimeStamp(value) => <dyn Any>::downcast_mut::<T>(value.as_mut()), Union::TimeStamp(value, _) => <dyn Any>::downcast_mut::<T>(value.as_mut()),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<()>() { if TypeId::of::<T>() == TypeId::of::<()>() {
return match &mut self.0 { return match &mut self.0 {
Union::Unit(value) => <dyn Any>::downcast_mut::<T>(value), Union::Unit(value, _) => <dyn Any>::downcast_mut::<T>(value),
_ => None, _ => None,
}; };
} }
@ -1147,9 +1213,9 @@ impl Dynamic {
} }
match &mut self.0 { match &mut self.0 {
Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::<T>(), Union::Variant(value, _) => value.as_mut().as_mut_any().downcast_mut::<T>(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => None, Union::Shared(_, _) => None,
_ => None, _ => None,
} }
} }
@ -1158,9 +1224,9 @@ impl Dynamic {
#[inline(always)] #[inline(always)]
pub fn as_int(&self) -> Result<INT, &'static str> { pub fn as_int(&self) -> Result<INT, &'static str> {
match self.0 { match self.0 {
Union::Int(n) => Ok(n), Union::Int(n, _) => Ok(n),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), Union::Shared(_, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()),
_ => Err(self.type_name()), _ => Err(self.type_name()),
} }
} }
@ -1170,9 +1236,9 @@ impl Dynamic {
#[inline(always)] #[inline(always)]
pub fn as_float(&self) -> Result<FLOAT, &'static str> { pub fn as_float(&self) -> Result<FLOAT, &'static str> {
match self.0 { match self.0 {
Union::Float(n) => Ok(n), Union::Float(n, _) => Ok(n),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), Union::Shared(_, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()),
_ => Err(self.type_name()), _ => Err(self.type_name()),
} }
} }
@ -1181,9 +1247,9 @@ impl Dynamic {
#[inline(always)] #[inline(always)]
pub fn as_bool(&self) -> Result<bool, &'static str> { pub fn as_bool(&self) -> Result<bool, &'static str> {
match self.0 { match self.0 {
Union::Bool(b) => Ok(b), Union::Bool(b, _) => Ok(b),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), Union::Shared(_, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()),
_ => Err(self.type_name()), _ => Err(self.type_name()),
} }
} }
@ -1192,9 +1258,9 @@ impl Dynamic {
#[inline(always)] #[inline(always)]
pub fn as_char(&self) -> Result<char, &'static str> { pub fn as_char(&self) -> Result<char, &'static str> {
match self.0 { match self.0 {
Union::Char(n) => Ok(n), Union::Char(n, _) => Ok(n),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), Union::Shared(_, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()),
_ => Err(self.type_name()), _ => Err(self.type_name()),
} }
} }
@ -1205,8 +1271,8 @@ impl Dynamic {
#[inline(always)] #[inline(always)]
pub fn as_str(&self) -> Result<&str, &'static str> { pub fn as_str(&self) -> Result<&str, &'static str> {
match &self.0 { match &self.0 {
Union::Str(s) => Ok(s), Union::Str(s, _) => Ok(s),
Union::FnPtr(f) => Ok(f.fn_name()), Union::FnPtr(f, _) => Ok(f.fn_name()),
_ => Err(self.type_name()), _ => Err(self.type_name()),
} }
} }
@ -1223,16 +1289,16 @@ impl Dynamic {
#[inline] #[inline]
pub fn take_immutable_string(self) -> Result<ImmutableString, &'static str> { pub fn take_immutable_string(self) -> Result<ImmutableString, &'static str> {
match self.0 { match self.0 {
Union::Str(s) => Ok(s), Union::Str(s, _) => Ok(s),
Union::FnPtr(f) => Ok(f.take_data().0), Union::FnPtr(f, _) => Ok(f.take_data().0),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(cell) => { Union::Shared(cell, _) => {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
{ {
let inner = cell.borrow(); let inner = cell.borrow();
match &inner.0 { match &inner.0 {
Union::Str(s) => Ok(s.clone()), Union::Str(s, _) => Ok(s.clone()),
Union::FnPtr(f) => Ok(f.clone().take_data().0), Union::FnPtr(f, _) => Ok(f.clone().take_data().0),
_ => Err((*inner).type_name()), _ => Err((*inner).type_name()),
} }
} }
@ -1240,8 +1306,8 @@ impl Dynamic {
{ {
let inner = cell.read().unwrap(); let inner = cell.read().unwrap();
match &inner.0 { match &inner.0 {
Union::Str(s) => Ok(s.clone()), Union::Str(s, _) => Ok(s.clone()),
Union::FnPtr(f) => Ok(f.clone().take_data().0), Union::FnPtr(f, _) => Ok(f.clone().take_data().0),
_ => Err((*inner).type_name()), _ => Err((*inner).type_name()),
} }
} }
@ -1254,56 +1320,58 @@ impl Dynamic {
impl From<()> for Dynamic { impl From<()> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: ()) -> Self { fn from(value: ()) -> Self {
Self(Union::Unit(value)) Self(Union::Unit(value, AccessType::Normal))
} }
} }
impl From<bool> for Dynamic { impl From<bool> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: bool) -> Self { fn from(value: bool) -> Self {
Self(Union::Bool(value)) Self(Union::Bool(value, AccessType::Normal))
} }
} }
impl From<INT> for Dynamic { impl From<INT> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: INT) -> Self { fn from(value: INT) -> Self {
Self(Union::Int(value)) Self(Union::Int(value, AccessType::Normal))
} }
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl From<FLOAT> for Dynamic { impl From<FLOAT> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: FLOAT) -> Self { fn from(value: FLOAT) -> Self {
Self(Union::Float(value)) Self(Union::Float(value, AccessType::Normal))
} }
} }
impl From<char> for Dynamic { impl From<char> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: char) -> Self { fn from(value: char) -> Self {
Self(Union::Char(value)) Self(Union::Char(value, AccessType::Normal))
} }
} }
impl<S: Into<ImmutableString>> From<S> for Dynamic { impl<S: Into<ImmutableString>> From<S> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: S) -> Self { fn from(value: S) -> Self {
Self(Union::Str(value.into())) Self(Union::Str(value.into(), AccessType::Normal))
} }
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
impl<T: Variant + Clone> From<crate::stdlib::vec::Vec<T>> for Dynamic { impl<T: Variant + Clone> From<crate::stdlib::vec::Vec<T>> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: crate::stdlib::vec::Vec<T>) -> Self { fn from(value: crate::stdlib::vec::Vec<T>) -> Self {
Self(Union::Array(Box::new( Self(Union::Array(
value.into_iter().map(Dynamic::from).collect(), Box::new(value.into_iter().map(Dynamic::from).collect()),
))) AccessType::Normal,
))
} }
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
impl<T: Variant + Clone> From<&[T]> for Dynamic { impl<T: Variant + Clone> From<&[T]> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: &[T]) -> Self { fn from(value: &[T]) -> Self {
Self(Union::Array(Box::new( Self(Union::Array(
value.iter().cloned().map(Dynamic::from).collect(), Box::new(value.iter().cloned().map(Dynamic::from).collect()),
))) AccessType::Normal,
))
} }
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -1312,30 +1380,33 @@ impl<K: Into<ImmutableString>, T: Variant + Clone> From<crate::stdlib::collectio
{ {
#[inline(always)] #[inline(always)]
fn from(value: crate::stdlib::collections::HashMap<K, T>) -> Self { fn from(value: crate::stdlib::collections::HashMap<K, T>) -> Self {
Self(Union::Map(Box::new( Self(Union::Map(
Box::new(
value value
.into_iter() .into_iter()
.map(|(k, v)| (k.into(), Dynamic::from(v))) .map(|(k, v)| (k.into(), Dynamic::from(v)))
.collect(), .collect(),
))) ),
AccessType::Normal,
))
} }
} }
impl From<FnPtr> for Dynamic { impl From<FnPtr> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: FnPtr) -> Self { fn from(value: FnPtr) -> Self {
Self(Union::FnPtr(Box::new(value))) Self(Union::FnPtr(Box::new(value), AccessType::Normal))
} }
} }
impl From<Box<FnPtr>> for Dynamic { impl From<Box<FnPtr>> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: Box<FnPtr>) -> Self { fn from(value: Box<FnPtr>) -> Self {
Self(Union::FnPtr(value)) Self(Union::FnPtr(value, AccessType::Normal))
} }
} }
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
impl From<Instant> for Dynamic { impl From<Instant> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: Instant) -> Self { fn from(value: Instant) -> Self {
Self(Union::TimeStamp(Box::new(value))) Self(Union::TimeStamp(Box::new(value), AccessType::Normal))
} }
} }

View File

@ -1,14 +1,13 @@
//! Main module defining the script evaluation [`Engine`]. //! Main module defining the script evaluation [`Engine`].
use crate::ast::{Expr, FnCallExpr, Ident, IdentX, ReturnType, Stmt}; use crate::ast::{Expr, FnCallExpr, Ident, IdentX, ReturnType, Stmt};
use crate::dynamic::{map_std_type_name, Union, Variant}; use crate::dynamic::{map_std_type_name, AccessType, Union, Variant};
use crate::fn_call::run_builtin_op_assignment; use crate::fn_call::run_builtin_op_assignment;
use crate::fn_native::{CallableFunction, Callback, IteratorFn, OnVarCallback}; use crate::fn_native::{CallableFunction, Callback, IteratorFn, OnVarCallback};
use crate::module::NamespaceRef; use crate::module::NamespaceRef;
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
use crate::packages::{Package, PackagesCollection, StandardPackage}; use crate::packages::{Package, PackagesCollection, StandardPackage};
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
use crate::scope::EntryType as ScopeEntryType;
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, TypeId}, any::{type_name, TypeId},
borrow::Cow, borrow::Cow,
@ -833,7 +832,7 @@ impl Engine {
lib: &[&Module], lib: &[&Module],
this_ptr: &'s mut Option<&mut Dynamic>, this_ptr: &'s mut Option<&mut Dynamic>,
expr: &'a Expr, expr: &'a Expr,
) -> Result<(Target<'s>, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> { ) -> Result<(Target<'s>, &'a str, Position), Box<EvalAltResult>> {
match expr { match expr {
Expr::Variable(v) => match v.as_ref() { Expr::Variable(v) => match v.as_ref() {
// Qualified variable // Qualified variable
@ -850,7 +849,9 @@ impl Engine {
})?; })?;
// Module variables are constant // Module variables are constant
Ok((target.clone().into(), name, ScopeEntryType::Constant, *pos)) let mut target = target.clone();
target.set_access_type(AccessType::Constant);
Ok((target.into(), name, *pos))
} }
// Normal variable access // Normal variable access
_ => self.search_scope_only(scope, mods, state, lib, this_ptr, expr), _ => self.search_scope_only(scope, mods, state, lib, this_ptr, expr),
@ -868,7 +869,7 @@ impl Engine {
lib: &[&Module], lib: &[&Module],
this_ptr: &'s mut Option<&mut Dynamic>, this_ptr: &'s mut Option<&mut Dynamic>,
expr: &'a Expr, expr: &'a Expr,
) -> Result<(Target<'s>, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> { ) -> Result<(Target<'s>, &'a str, Position), Box<EvalAltResult>> {
let (index, _, _, IdentX { name, pos }) = match expr { let (index, _, _, IdentX { name, pos }) = match expr {
Expr::Variable(v) => v.as_ref(), Expr::Variable(v) => v.as_ref(),
_ => unreachable!(), _ => unreachable!(),
@ -877,7 +878,7 @@ impl Engine {
// Check if the variable is `this` // Check if the variable is `this`
if name.as_str() == KEYWORD_THIS { if name.as_str() == KEYWORD_THIS {
if let Some(val) = this_ptr { if let Some(val) = this_ptr {
return Ok(((*val).into(), KEYWORD_THIS, ScopeEntryType::Normal, *pos)); return Ok(((*val).into(), KEYWORD_THIS, *pos));
} else { } else {
return EvalAltResult::ErrorUnboundThis(*pos).into(); return EvalAltResult::ErrorUnboundThis(*pos).into();
} }
@ -901,10 +902,11 @@ impl Engine {
this_ptr, this_ptr,
level: 0, level: 0,
}; };
if let Some(result) = if let Some(mut result) =
resolve_var(name, index, &context).map_err(|err| err.fill_position(*pos))? resolve_var(name, index, &context).map_err(|err| err.fill_position(*pos))?
{ {
return Ok((result.into(), name, ScopeEntryType::Constant, *pos)); result.set_access_type(AccessType::Constant);
return Ok((result.into(), name, *pos));
} }
} }
@ -918,7 +920,7 @@ impl Engine {
.0 .0
}; };
let (val, typ) = scope.get_mut(index); let val = scope.get_mut(index);
// Check for data race - probably not necessary because the only place it should conflict is in a method call // Check for data race - probably not necessary because the only place it should conflict is in a method call
// when the object variable is also used as a parameter. // when the object variable is also used as a parameter.
@ -926,7 +928,7 @@ impl Engine {
// return EvalAltResult::ErrorDataRace(name.into(), *pos).into(); // return EvalAltResult::ErrorDataRace(name.into(), *pos).into();
// } // }
Ok((val.into(), name, typ, *pos)) Ok((val.into(), name, *pos))
} }
/// Chain-evaluate a dot/index chain. /// Chain-evaluate a dot/index chain.
@ -1279,17 +1281,14 @@ impl Engine {
self.inc_operations(state) self.inc_operations(state)
.map_err(|err| err.fill_position(*var_pos))?; .map_err(|err| err.fill_position(*var_pos))?;
let (target, _, typ, pos) = let (target, _, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?; self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?;
// Constants cannot be modified // Constants cannot be modified
match typ { if target.as_ref().is_constant() && new_val.is_some() {
ScopeEntryType::Constant if new_val.is_some() => {
return EvalAltResult::ErrorAssignmentToConstant(var_name.to_string(), pos) return EvalAltResult::ErrorAssignmentToConstant(var_name.to_string(), pos)
.into(); .into();
} }
ScopeEntryType::Constant | ScopeEntryType::Normal => (),
}
let obj_ptr = &mut target.into(); let obj_ptr = &mut target.into();
self.eval_dot_index_chain_helper( self.eval_dot_index_chain_helper(
@ -1410,7 +1409,7 @@ impl Engine {
match target { match target {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(arr)) => { Dynamic(Union::Array(arr, _)) => {
// val_array[idx] // val_array[idx]
let index = idx let index = idx
.as_int() .as_int()
@ -1430,7 +1429,7 @@ impl Engine {
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Dynamic(Union::Map(map)) => { Dynamic(Union::Map(map, _)) => {
// val_map[idx] // val_map[idx]
Ok(if _create { Ok(if _create {
let index = idx.take_immutable_string().map_err(|err| { let index = idx.take_immutable_string().map_err(|err| {
@ -1450,7 +1449,7 @@ impl Engine {
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Str(s)) => { Dynamic(Union::Str(s, _)) => {
// val_string[idx] // val_string[idx]
let chars_len = s.chars().count(); let chars_len = s.chars().count();
let index = idx let index = idx
@ -1517,7 +1516,7 @@ impl Engine {
match rhs_value { match rhs_value {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(mut rhs_value)) => { Dynamic(Union::Array(mut rhs_value, _)) => {
// Call the `==` operator to compare each value // Call the `==` operator to compare each value
let def_value = Some(false.into()); let def_value = Some(false.into());
let def_value = def_value.as_ref(); let def_value = def_value.as_ref();
@ -1545,16 +1544,16 @@ impl Engine {
Ok(false.into()) Ok(false.into())
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Dynamic(Union::Map(rhs_value)) => match lhs_value { Dynamic(Union::Map(rhs_value, _)) => match lhs_value {
// Only allows string or char // Only allows string or char
Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(&s).into()), Dynamic(Union::Str(s, _)) => Ok(rhs_value.contains_key(&s).into()),
Dynamic(Union::Char(c)) => Ok(rhs_value.contains_key(&c.to_string()).into()), Dynamic(Union::Char(c, _)) => Ok(rhs_value.contains_key(&c.to_string()).into()),
_ => EvalAltResult::ErrorInExpr(lhs.position()).into(), _ => EvalAltResult::ErrorInExpr(lhs.position()).into(),
}, },
Dynamic(Union::Str(rhs_value)) => match lhs_value { Dynamic(Union::Str(rhs_value, _)) => match lhs_value {
// Only allows string or char // Only allows string or char
Dynamic(Union::Str(s)) => Ok(rhs_value.contains(s.as_str()).into()), Dynamic(Union::Str(s, _)) => Ok(rhs_value.contains(s.as_str()).into()),
Dynamic(Union::Char(c)) => Ok(rhs_value.contains(c).into()), Dynamic(Union::Char(c, _)) => Ok(rhs_value.contains(c).into()),
_ => EvalAltResult::ErrorInExpr(lhs.position()).into(), _ => EvalAltResult::ErrorInExpr(lhs.position()).into(),
}, },
_ => EvalAltResult::ErrorInExpr(rhs.position()).into(), _ => EvalAltResult::ErrorInExpr(rhs.position()).into(),
@ -1576,17 +1575,15 @@ impl Engine {
match expr { match expr {
// var - point directly to the value // var - point directly to the value
Expr::Variable(_) => { Expr::Variable(_) => {
let (target, _, typ, pos) = let (mut target, _, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, expr)?; self.search_namespace(scope, mods, state, lib, this_ptr, expr)?;
Ok((
match typ {
// If necessary, constants are cloned // If necessary, constants are cloned
ScopeEntryType::Constant if no_const => target.into_owned(), if target.as_ref().is_constant() {
_ => target, target = target.into_owned();
}, }
pos,
)) Ok((target, pos))
} }
// var[...] // var[...]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -1706,8 +1703,7 @@ impl Engine {
} }
} }
Expr::Variable(_) => { Expr::Variable(_) => {
let (val, _, _, _) = let (val, _, _) = self.search_namespace(scope, mods, state, lib, this_ptr, expr)?;
self.search_namespace(scope, mods, state, lib, this_ptr, expr)?;
Ok(val.take_or_clone()) Ok(val.take_or_clone())
} }
Expr::Property(_) => unreachable!(), Expr::Property(_) => unreachable!(),
@ -1736,7 +1732,7 @@ impl Engine {
for item in x.as_ref() { for item in x.as_ref() {
arr.push(self.eval_expr(scope, mods, state, lib, this_ptr, item, level)?); arr.push(self.eval_expr(scope, mods, state, lib, this_ptr, item, level)?);
} }
Ok(Dynamic(Union::Array(Box::new(arr)))) Ok(Dynamic(Union::Array(Box::new(arr), AccessType::Normal)))
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -1749,7 +1745,7 @@ impl Engine {
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?, self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?,
); );
} }
Ok(Dynamic(Union::Map(Box::new(map)))) Ok(Dynamic(Union::Map(Box::new(map), AccessType::Normal)))
} }
// Normal function call // Normal function call
@ -1913,7 +1909,7 @@ impl Engine {
let mut rhs_val = self let mut rhs_val = self
.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)? .eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?
.flatten(); .flatten();
let (mut lhs_ptr, name, typ, pos) = let (mut lhs_ptr, name, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?; self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?;
if !lhs_ptr.is_ref() { if !lhs_ptr.is_ref() {
@ -1923,29 +1919,28 @@ impl Engine {
self.inc_operations(state) self.inc_operations(state)
.map_err(|err| err.fill_position(pos))?; .map_err(|err| err.fill_position(pos))?;
match typ { if lhs_ptr.as_ref().is_constant() {
// Assignment to constant variable // Assignment to constant variable
ScopeEntryType::Constant => Err(Box::new( Err(Box::new(EvalAltResult::ErrorAssignmentToConstant(
EvalAltResult::ErrorAssignmentToConstant(name.to_string(), pos), name.to_string(),
)), pos,
)))
} else if op.is_empty() {
// Normal assignment // Normal assignment
ScopeEntryType::Normal if op.is_empty() => {
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
*lhs_ptr.as_mut().write_lock::<Dynamic>().unwrap() = rhs_val; *lhs_ptr.as_mut().write_lock::<Dynamic>().unwrap() = rhs_val;
} else { } else {
*lhs_ptr.as_mut() = rhs_val; *lhs_ptr.as_mut() = rhs_val;
} }
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} } else {
// Op-assignment - in order of precedence: // Op-assignment - in order of precedence:
ScopeEntryType::Normal => {
// 1) Native registered overriding function // 1) Native registered overriding function
// 2) Built-in implementation // 2) Built-in implementation
// 3) Map to `var = var op rhs` // 3) Map to `var = var op rhs`
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
let arg_types = let arg_types = once(lhs_ptr.as_mut().type_id()).chain(once(rhs_val.type_id()));
once(lhs_ptr.as_mut().type_id()).chain(once(rhs_val.type_id()));
let hash_fn = calc_native_fn_hash(empty(), op, arg_types); let hash_fn = calc_native_fn_hash(empty(), op, arg_types);
match self match self
@ -1989,8 +1984,8 @@ impl Engine {
// Run function // Run function
let (value, _) = self let (value, _) = self
.exec_fn_call( .exec_fn_call(
mods, state, lib, op, 0, args, false, false, false, None, mods, state, lib, op, 0, args, false, false, false, None, None,
None, level, level,
) )
.map_err(|err| err.fill_position(*op_pos))?; .map_err(|err| err.fill_position(*op_pos))?;
@ -2006,7 +2001,6 @@ impl Engine {
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} }
} }
}
// lhs op= rhs // lhs op= rhs
Stmt::Assignment(x, op_pos) => { Stmt::Assignment(x, op_pos) => {
@ -2175,7 +2169,7 @@ impl Engine {
state.scope_level += 1; state.scope_level += 1;
for iter_value in func(iter_obj) { for iter_value in func(iter_obj) {
let (loop_var, _) = scope.get_mut(index); let loop_var = scope.get_mut(index);
let value = iter_value.flatten(); let value = iter_value.flatten();
if cfg!(not(feature = "no_closure")) && loop_var.is_shared() { if cfg!(not(feature = "no_closure")) && loop_var.is_shared() {
@ -2249,8 +2243,10 @@ impl Engine {
.map(|_| ().into()); .map(|_| ().into());
if let Some(result_err) = result.as_ref().err() { if let Some(result_err) = result.as_ref().err() {
if let EvalAltResult::ErrorRuntime(Dynamic(Union::Unit(_)), pos) = if let EvalAltResult::ErrorRuntime(
result_err.as_ref() Dynamic(Union::Unit(_, _)),
pos,
) = result_err.as_ref()
{ {
err.set_position(*pos); err.set_position(*pos);
result = Err(Box::new(err)); result = Err(Box::new(err));
@ -2293,8 +2289,8 @@ impl Engine {
// Let/const statement // Let/const statement
Stmt::Let(var_def, expr, export, _) | Stmt::Const(var_def, expr, export, _) => { Stmt::Let(var_def, expr, export, _) | Stmt::Const(var_def, expr, export, _) => {
let entry_type = match stmt { let entry_type = match stmt {
Stmt::Let(_, _, _, _) => ScopeEntryType::Normal, Stmt::Let(_, _, _, _) => AccessType::Normal,
Stmt::Const(_, _, _, _) => ScopeEntryType::Constant, Stmt::Const(_, _, _, _) => AccessType::Constant,
_ => unreachable!(), _ => unreachable!(),
}; };
@ -2388,8 +2384,8 @@ impl Engine {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Stmt::Share(x) => { Stmt::Share(x) => {
match scope.get_index(&x.name) { match scope.get_index(&x.name) {
Some((index, ScopeEntryType::Normal)) => { Some((index, AccessType::Normal)) => {
let (val, _) = scope.get_mut(index); let val = scope.get_mut(index);
if !val.is_shared() { if !val.is_shared() {
// Replace the variable with a shared value. // Replace the variable with a shared value.
@ -2445,18 +2441,18 @@ impl Engine {
fn calc_size(value: &Dynamic) -> (usize, usize, usize) { fn calc_size(value: &Dynamic) -> (usize, usize, usize) {
match value { match value {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(arr)) => { Dynamic(Union::Array(arr, _)) => {
let mut arrays = 0; let mut arrays = 0;
let mut maps = 0; let mut maps = 0;
arr.iter().for_each(|value| match value { arr.iter().for_each(|value| match value {
Dynamic(Union::Array(_)) => { Dynamic(Union::Array(_, _)) => {
let (a, m, _) = calc_size(value); let (a, m, _) = calc_size(value);
arrays += a; arrays += a;
maps += m; maps += m;
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Dynamic(Union::Map(_)) => { Dynamic(Union::Map(_, _)) => {
let (a, m, _) = calc_size(value); let (a, m, _) = calc_size(value);
arrays += a; arrays += a;
maps += m; maps += m;
@ -2467,18 +2463,18 @@ impl Engine {
(arrays, maps, 0) (arrays, maps, 0)
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Dynamic(Union::Map(map)) => { Dynamic(Union::Map(map, _)) => {
let mut arrays = 0; let mut arrays = 0;
let mut maps = 0; let mut maps = 0;
map.values().for_each(|value| match value { map.values().for_each(|value| match value {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(_)) => { Dynamic(Union::Array(_, _)) => {
let (a, m, _) = calc_size(value); let (a, m, _) = calc_size(value);
arrays += a; arrays += a;
maps += m; maps += m;
} }
Dynamic(Union::Map(_)) => { Dynamic(Union::Map(_, _)) => {
let (a, m, _) = calc_size(value); let (a, m, _) = calc_size(value);
arrays += a; arrays += a;
maps += m; maps += m;
@ -2488,7 +2484,7 @@ impl Engine {
(arrays, maps, 0) (arrays, maps, 0)
} }
Dynamic(Union::Str(s)) => (0, 0, s.len()), Dynamic(Union::Str(s, _)) => (0, 0, s.len()),
_ => (0, 0, 0), _ => (0, 0, 0),
} }
} }
@ -2497,13 +2493,13 @@ impl Engine {
// Simply return all errors // Simply return all errors
Err(_) => return result, Err(_) => return result,
// String with limit // String with limit
Ok(Dynamic(Union::Str(_))) if self.max_string_size() > 0 => (), Ok(Dynamic(Union::Str(_, _))) if self.max_string_size() > 0 => (),
// Array with limit // Array with limit
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Ok(Dynamic(Union::Array(_))) if self.max_array_size() > 0 => (), Ok(Dynamic(Union::Array(_, _))) if self.max_array_size() > 0 => (),
// Map with limit // Map with limit
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Ok(Dynamic(Union::Map(_))) if self.max_map_size() > 0 => (), Ok(Dynamic(Union::Map(_, _))) if self.max_map_size() > 0 => (),
// Everything else is simply returned // Everything else is simply returned
Ok(_) => return result, Ok(_) => return result,
}; };

View File

@ -8,7 +8,6 @@ use crate::engine::{
use crate::fn_native::FnCallArgs; use crate::fn_native::FnCallArgs;
use crate::module::NamespaceRef; use crate::module::NamespaceRef;
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
use crate::scope::EntryType as ScopeEntryType;
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, TypeId}, any::{type_name, TypeId},
boxed::Box, boxed::Box,
@ -360,7 +359,7 @@ impl Engine {
.map(|(name, value)| { .map(|(name, value)| {
let var_name: crate::stdlib::borrow::Cow<'_, str> = let var_name: crate::stdlib::borrow::Cow<'_, str> =
crate::r#unsafe::unsafe_cast_var_name_to_lifetime(name).into(); crate::r#unsafe::unsafe_cast_var_name_to_lifetime(name).into();
(var_name, ScopeEntryType::Normal, value) (var_name, value)
}), }),
); );
@ -540,15 +539,10 @@ impl Engine {
if !func.externals.is_empty() { if !func.externals.is_empty() {
captured captured
.into_iter() .into_iter()
.filter(|(name, _, _, _)| func.externals.contains(name.as_ref())) .filter(|(name, _, _)| func.externals.contains(name.as_ref()))
.for_each(|(name, typ, value, _)| { .for_each(|(name, value, _)| {
// Consume the scope values. // Consume the scope values.
match typ { scope.push_dynamic(name, value);
ScopeEntryType::Normal => scope.push(name, value),
ScopeEntryType::Constant => {
scope.push_constant(name, value)
}
};
}); });
} }
} }
@ -1000,13 +994,12 @@ impl Engine {
.map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level))
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
let (target, _, typ, pos) = let (mut target, _, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, &args_expr[0])?; self.search_namespace(scope, mods, state, lib, this_ptr, &args_expr[0])?;
let target = match typ { if target.as_ref().is_constant() {
ScopeEntryType::Normal => target, target = target.into_owned();
ScopeEntryType::Constant => target.into_owned(), }
};
self.inc_operations(state) self.inc_operations(state)
.map_err(|err| err.fill_position(pos))?; .map_err(|err| err.fill_position(pos))?;
@ -1087,7 +1080,7 @@ impl Engine {
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
// Get target reference to first argument // Get target reference to first argument
let (target, _, _, pos) = let (target, _, pos) =
self.search_scope_only(scope, mods, state, lib, this_ptr, &args_expr[0])?; self.search_scope_only(scope, mods, state, lib, this_ptr, &args_expr[0])?;
self.inc_operations(state) self.inc_operations(state)

View File

@ -1640,7 +1640,7 @@ impl Module {
// Create new module // Create new module
let mut module = Module::new(); let mut module = Module::new();
scope.into_iter().for_each(|(_, _, value, mut aliases)| { scope.into_iter().for_each(|(_, value, mut aliases)| {
// Variables with an alias left in the scope become module variables // Variables with an alias left in the scope become module variables
if aliases.len() > 1 { if aliases.len() > 1 {
aliases.into_iter().for_each(|alias| { aliases.into_iter().for_each(|alias| {

View File

@ -3,12 +3,11 @@
use crate::ast::{ use crate::ast::{
BinaryExpr, CustomExpr, Expr, FnCallExpr, Ident, IdentX, ReturnType, ScriptFnDef, Stmt, BinaryExpr, CustomExpr, Expr, FnCallExpr, Ident, IdentX, ReturnType, ScriptFnDef, Stmt,
}; };
use crate::dynamic::Union; use crate::dynamic::{AccessType, Union};
use crate::engine::{KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; use crate::engine::{KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
use crate::module::NamespaceRef; use crate::module::NamespaceRef;
use crate::optimize::optimize_into_ast; use crate::optimize::optimize_into_ast;
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
use crate::scope::EntryType as ScopeEntryType;
use crate::stdlib::{ use crate::stdlib::{
borrow::Cow, borrow::Cow,
boxed::Box, boxed::Box,
@ -49,7 +48,7 @@ struct ParseState<'e> {
/// Interned strings. /// Interned strings.
strings: HashMap<String, ImmutableString>, strings: HashMap<String, ImmutableString>,
/// Encapsulates a local stack with variable names to simulate an actual runtime scope. /// Encapsulates a local stack with variable names to simulate an actual runtime scope.
stack: Vec<(ImmutableString, ScopeEntryType)>, stack: Vec<(ImmutableString, AccessType)>,
/// Size of the local variables stack upon entry of the current block scope. /// Size of the local variables stack upon entry of the current block scope.
entry_stack_len: usize, entry_stack_len: usize,
/// Tracks a list of external variables (variables that are not explicitly declared in the scope). /// Tracks a list of external variables (variables that are not explicitly declared in the scope).
@ -1291,11 +1290,11 @@ fn make_assignment_stmt<'a>(
}, },
) = x.as_ref(); ) = x.as_ref();
match state.stack[(state.stack.len() - index.unwrap().get())].1 { match state.stack[(state.stack.len() - index.unwrap().get())].1 {
ScopeEntryType::Normal => { AccessType::Normal => {
Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos)) Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos))
} }
// Constant values cannot be assigned to // Constant values cannot be assigned to
ScopeEntryType::Constant => { AccessType::Constant => {
Err(PERR::AssignmentToConstant(name.to_string()).into_err(*name_pos)) Err(PERR::AssignmentToConstant(name.to_string()).into_err(*name_pos))
} }
} }
@ -1318,11 +1317,11 @@ fn make_assignment_stmt<'a>(
}, },
) = x.as_ref(); ) = x.as_ref();
match state.stack[(state.stack.len() - index.unwrap().get())].1 { match state.stack[(state.stack.len() - index.unwrap().get())].1 {
ScopeEntryType::Normal => { AccessType::Normal => {
Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos)) Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos))
} }
// Constant values cannot be assigned to // Constant values cannot be assigned to
ScopeEntryType::Constant => { AccessType::Constant => {
Err(PERR::AssignmentToConstant(name.to_string()).into_err(*name_pos)) Err(PERR::AssignmentToConstant(name.to_string()).into_err(*name_pos))
} }
} }
@ -1811,7 +1810,7 @@ fn parse_custom_syntax(
// Variable searches stop at the first empty variable name. // Variable searches stop at the first empty variable name.
state.stack.resize( state.stack.resize(
state.stack.len() + delta as usize, state.stack.len() + delta as usize,
("".into(), ScopeEntryType::Normal), ("".into(), AccessType::Normal),
); );
} }
delta if delta < 0 && state.stack.len() <= delta.abs() as usize => state.stack.clear(), delta if delta < 0 && state.stack.len() <= delta.abs() as usize => state.stack.clear(),
@ -2110,7 +2109,7 @@ fn parse_for(
let loop_var = state.get_interned_string(name.clone()); let loop_var = state.get_interned_string(name.clone());
let prev_stack_len = state.stack.len(); let prev_stack_len = state.stack.len();
state.stack.push((loop_var, ScopeEntryType::Normal)); state.stack.push((loop_var, AccessType::Normal));
settings.is_breakable = true; settings.is_breakable = true;
let body = parse_block(input, state, lib, settings.level_up())?; let body = parse_block(input, state, lib, settings.level_up())?;
@ -2125,7 +2124,7 @@ fn parse_let(
input: &mut TokenStream, input: &mut TokenStream,
state: &mut ParseState, state: &mut ParseState,
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
var_type: ScopeEntryType, var_type: AccessType,
export: bool, export: bool,
mut settings: ParseSettings, mut settings: ParseSettings,
) -> Result<Stmt, ParseError> { ) -> Result<Stmt, ParseError> {
@ -2156,16 +2155,16 @@ fn parse_let(
match var_type { match var_type {
// let name = expr // let name = expr
ScopeEntryType::Normal => { AccessType::Normal => {
let var_name = state.get_interned_string(name.clone()); let var_name = state.get_interned_string(name.clone());
state.stack.push((var_name, ScopeEntryType::Normal)); state.stack.push((var_name, AccessType::Normal));
let var_def = Ident::new(name, pos); let var_def = Ident::new(name, pos);
Ok(Stmt::Let(Box::new(var_def), init_expr, export, token_pos)) Ok(Stmt::Let(Box::new(var_def), init_expr, export, token_pos))
} }
// const name = { expr:constant } // const name = { expr:constant }
ScopeEntryType::Constant => { AccessType::Constant => {
let var_name = state.get_interned_string(name.clone()); let var_name = state.get_interned_string(name.clone());
state.stack.push((var_name, ScopeEntryType::Constant)); state.stack.push((var_name, AccessType::Constant));
let var_def = Ident::new(name, pos); let var_def = Ident::new(name, pos);
Ok(Stmt::Const(Box::new(var_def), init_expr, export, token_pos)) Ok(Stmt::Const(Box::new(var_def), init_expr, export, token_pos))
} }
@ -2232,13 +2231,13 @@ fn parse_export(
match input.peek().unwrap() { match input.peek().unwrap() {
(Token::Let, pos) => { (Token::Let, pos) => {
let pos = *pos; let pos = *pos;
let mut stmt = parse_let(input, state, lib, ScopeEntryType::Normal, true, settings)?; let mut stmt = parse_let(input, state, lib, AccessType::Normal, true, settings)?;
stmt.set_position(pos); stmt.set_position(pos);
return Ok(stmt); return Ok(stmt);
} }
(Token::Const, pos) => { (Token::Const, pos) => {
let pos = *pos; let pos = *pos;
let mut stmt = parse_let(input, state, lib, ScopeEntryType::Constant, true, settings)?; let mut stmt = parse_let(input, state, lib, AccessType::Constant, true, settings)?;
stmt.set_position(pos); stmt.set_position(pos);
return Ok(stmt); return Ok(stmt);
} }
@ -2393,7 +2392,7 @@ fn parse_stmt(
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
mut settings: ParseSettings, mut settings: ParseSettings,
) -> Result<Option<Stmt>, ParseError> { ) -> Result<Option<Stmt>, ParseError> {
use ScopeEntryType::{Constant, Normal}; use AccessType::{Constant, Normal};
let (token, token_pos) = match input.peek().unwrap() { let (token, token_pos) = match input.peek().unwrap() {
(Token::EOF, pos) => return Ok(Some(Stmt::Noop(*pos))), (Token::EOF, pos) => return Ok(Some(Stmt::Noop(*pos))),
@ -2637,7 +2636,7 @@ fn parse_fn(
return Err(PERR::FnDuplicatedParam(name, s).into_err(pos)); return Err(PERR::FnDuplicatedParam(name, s).into_err(pos));
} }
let s = state.get_interned_string(s); let s = state.get_interned_string(s);
state.stack.push((s.clone(), ScopeEntryType::Normal)); state.stack.push((s.clone(), AccessType::Normal));
params.push((s, pos)) params.push((s, pos))
} }
(Token::LexError(err), pos) => return Err(err.into_err(pos)), (Token::LexError(err), pos) => return Err(err.into_err(pos)),
@ -2770,7 +2769,7 @@ fn parse_anon_fn(
return Err(PERR::FnDuplicatedParam("".to_string(), s).into_err(pos)); return Err(PERR::FnDuplicatedParam("".to_string(), s).into_err(pos));
} }
let s = state.get_interned_string(s); let s = state.get_interned_string(s);
state.stack.push((s.clone(), ScopeEntryType::Normal)); state.stack.push((s.clone(), AccessType::Normal));
params.push((s, pos)) params.push((s, pos))
} }
(Token::LexError(err), pos) => return Err(err.into_err(pos)), (Token::LexError(err), pos) => return Err(err.into_err(pos)),
@ -3005,15 +3004,15 @@ impl Engine {
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option<Expr> { pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option<Expr> {
match value.0 { match value.0 {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(value) => Some(Expr::FloatConstant(value, pos)), Union::Float(value, _) => Some(Expr::FloatConstant(value, pos)),
Union::Unit(_) => Some(Expr::Unit(pos)), Union::Unit(_, _) => Some(Expr::Unit(pos)),
Union::Int(value) => Some(Expr::IntegerConstant(value, pos)), Union::Int(value, _) => Some(Expr::IntegerConstant(value, pos)),
Union::Char(value) => Some(Expr::CharConstant(value, pos)), Union::Char(value, _) => Some(Expr::CharConstant(value, pos)),
Union::Str(value) => Some(Expr::StringConstant(value, pos)), Union::Str(value, _) => Some(Expr::StringConstant(value, pos)),
Union::Bool(value) => Some(Expr::BoolConstant(value, pos)), Union::Bool(value, _) => Some(Expr::BoolConstant(value, pos)),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(array) => { Union::Array(array, _) => {
let items: Vec<_> = array let items: Vec<_> = array
.into_iter() .into_iter()
.map(|x| map_dynamic_to_expr(x, pos)) .map(|x| map_dynamic_to_expr(x, pos))
@ -3029,7 +3028,7 @@ pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option<Expr> {
} }
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(map) => { Union::Map(map, _) => {
let items: Vec<_> = map let items: Vec<_> = map
.into_iter() .into_iter()
.map(|(k, v)| (IdentX::new(k, pos), map_dynamic_to_expr(v, pos))) .map(|(k, v)| (IdentX::new(k, pos), map_dynamic_to_expr(v, pos)))

View File

@ -1,29 +1,9 @@
//! Module that defines the [`Scope`] type representing a function call-stack scope. //! Module that defines the [`Scope`] type representing a function call-stack scope.
use crate::dynamic::Variant; use crate::dynamic::{AccessType, Variant};
use crate::stdlib::{borrow::Cow, boxed::Box, iter, string::String, vec::Vec}; use crate::stdlib::{borrow::Cow, boxed::Box, iter, string::String, vec::Vec};
use crate::{Dynamic, StaticVec}; use crate::{Dynamic, StaticVec};
/// Type of an entry in the Scope.
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
pub enum EntryType {
/// Normal value.
Normal,
/// Immutable constant value.
Constant,
}
impl EntryType {
/// Is this entry constant?
#[inline(always)]
pub fn is_constant(&self) -> bool {
match self {
Self::Normal => false,
Self::Constant => true,
}
}
}
/// Type containing information about the current scope. /// Type containing information about the current scope.
/// Useful for keeping state between [`Engine`][crate::Engine] evaluation runs. /// Useful for keeping state between [`Engine`][crate::Engine] evaluation runs.
/// ///
@ -58,19 +38,17 @@ impl EntryType {
// //
// # Implementation Notes // # Implementation Notes
// //
// [`Scope`] is implemented as three [`Vec`]'s of exactly the same length. Variables data (name, type, etc.) // [`Scope`] is implemented as two [`Vec`]'s of exactly the same length. Variables data (name, type, etc.)
// is manually split into three equal-length arrays. That's because variable names take up the most space, // is manually split into three equal-length arrays. That's because variable names take up the most space,
// with [`Cow<str>`][Cow] being four words long, but in the vast majority of cases the name is NOT used to look up // with [`Cow<str>`][Cow] being four words long, but in the vast majority of cases the name is NOT used to look up
// a variable's value. Variable lookup is usually via direct index, by-passing the name altogether. // a variable's value. Variable lookup is usually via direct index, 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.
// The variable type is packed separately into another array because it is even smaller. // The variable type is packed separately into another array because it is even smaller.
#[derive(Debug, Clone)] #[derive(Debug)]
pub struct Scope<'a> { pub struct Scope<'a> {
/// Current value of the entry. /// Current value of the entry.
values: Vec<Dynamic>, values: Vec<Dynamic>,
/// Type of the entry.
types: Vec<EntryType>,
/// (Name, aliases) of the entry. The list of aliases is Boxed because it occurs rarely. /// (Name, aliases) of the entry. The list of aliases is Boxed because it occurs rarely.
names: Vec<(Cow<'a, str>, Box<StaticVec<String>>)>, names: Vec<(Cow<'a, str>, Box<StaticVec<String>>)>,
} }
@ -79,7 +57,6 @@ impl Default for Scope<'_> {
fn default() -> Self { fn default() -> Self {
Self { Self {
values: Vec::with_capacity(16), values: Vec::with_capacity(16),
types: Vec::with_capacity(16),
names: Vec::with_capacity(16), names: Vec::with_capacity(16),
} }
} }
@ -124,7 +101,6 @@ impl<'a> Scope<'a> {
#[inline(always)] #[inline(always)]
pub fn clear(&mut self) -> &mut Self { pub fn clear(&mut self) -> &mut Self {
self.names.clear(); self.names.clear();
self.types.clear();
self.values.clear(); self.values.clear();
self self
} }
@ -180,7 +156,7 @@ impl<'a> Scope<'a> {
name: impl Into<Cow<'a, str>>, name: impl Into<Cow<'a, str>>,
value: impl Variant + Clone, value: impl Variant + Clone,
) -> &mut Self { ) -> &mut Self {
self.push_dynamic_value(name, EntryType::Normal, Dynamic::from(value)) self.push_dynamic_value(name, AccessType::Normal, Dynamic::from(value))
} }
/// Add (push) a new [`Dynamic`] entry to the [`Scope`]. /// Add (push) a new [`Dynamic`] entry to the [`Scope`].
/// ///
@ -196,7 +172,7 @@ impl<'a> Scope<'a> {
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn push_dynamic(&mut self, name: impl Into<Cow<'a, str>>, value: Dynamic) -> &mut Self { pub fn push_dynamic(&mut self, name: impl Into<Cow<'a, str>>, value: Dynamic) -> &mut Self {
self.push_dynamic_value(name, EntryType::Normal, value) self.push_dynamic_value(name, value.access_type(), value)
} }
/// Add (push) a new constant to the [`Scope`]. /// Add (push) a new constant to the [`Scope`].
/// ///
@ -219,7 +195,7 @@ impl<'a> Scope<'a> {
name: impl Into<Cow<'a, str>>, name: impl Into<Cow<'a, str>>,
value: impl Variant + Clone, value: impl Variant + Clone,
) -> &mut Self { ) -> &mut Self {
self.push_dynamic_value(name, EntryType::Constant, Dynamic::from(value)) self.push_dynamic_value(name, AccessType::Constant, Dynamic::from(value))
} }
/// Add (push) a new constant with a [`Dynamic`] value to the Scope. /// Add (push) a new constant with a [`Dynamic`] value to the Scope.
/// ///
@ -242,18 +218,18 @@ impl<'a> Scope<'a> {
name: impl Into<Cow<'a, str>>, name: impl Into<Cow<'a, str>>,
value: Dynamic, value: Dynamic,
) -> &mut Self { ) -> &mut Self {
self.push_dynamic_value(name, EntryType::Constant, value) self.push_dynamic_value(name, AccessType::Constant, value)
} }
/// Add (push) a new entry with a [`Dynamic`] value to the [`Scope`]. /// Add (push) a new entry with a [`Dynamic`] value to the [`Scope`].
#[inline] #[inline]
pub(crate) fn push_dynamic_value( pub(crate) fn push_dynamic_value(
&mut self, &mut self,
name: impl Into<Cow<'a, str>>, name: impl Into<Cow<'a, str>>,
entry_type: EntryType, access: AccessType,
value: Dynamic, mut value: Dynamic,
) -> &mut Self { ) -> &mut Self {
self.names.push((name.into(), Box::new(Default::default()))); self.names.push((name.into(), Box::new(Default::default())));
self.types.push(entry_type); value.set_access_type(access);
self.values.push(value.into()); self.values.push(value.into());
self self
} }
@ -286,7 +262,6 @@ impl<'a> Scope<'a> {
#[inline(always)] #[inline(always)]
pub fn rewind(&mut self, size: usize) -> &mut Self { pub fn rewind(&mut self, size: usize) -> &mut Self {
self.names.truncate(size); self.names.truncate(size);
self.types.truncate(size);
self.values.truncate(size); self.values.truncate(size);
self self
} }
@ -312,14 +287,14 @@ impl<'a> Scope<'a> {
} }
/// Find an entry in the [`Scope`], starting from the last. /// Find an entry in the [`Scope`], starting from the last.
#[inline(always)] #[inline(always)]
pub(crate) fn get_index(&self, name: &str) -> Option<(usize, EntryType)> { pub(crate) fn get_index(&self, name: &str) -> Option<(usize, AccessType)> {
self.names self.names
.iter() .iter()
.enumerate() .enumerate()
.rev() // Always search a Scope in reverse order .rev() // Always search a Scope in reverse order
.find_map(|(index, (key, _))| { .find_map(|(index, (key, _))| {
if name == key.as_ref() { if name == key.as_ref() {
Some((index, self.types[index])) Some((index, self.values[index].access_type()))
} else { } else {
None None
} }
@ -374,8 +349,8 @@ impl<'a> Scope<'a> {
None => { None => {
self.push(name, value); self.push(name, value);
} }
Some((_, EntryType::Constant)) => panic!("variable {} is constant", name), Some((_, AccessType::Constant)) => panic!("variable {} is constant", name),
Some((index, EntryType::Normal)) => { Some((index, AccessType::Normal)) => {
*self.values.get_mut(index).unwrap() = Dynamic::from(value); *self.values.get_mut(index).unwrap() = Dynamic::from(value);
} }
} }
@ -383,11 +358,8 @@ impl<'a> Scope<'a> {
} }
/// Get a mutable reference to an entry in the [`Scope`]. /// Get a mutable reference to an entry in the [`Scope`].
#[inline(always)] #[inline(always)]
pub(crate) fn get_mut(&mut self, index: usize) -> (&mut Dynamic, EntryType) { pub(crate) fn get_mut(&mut self, index: usize) -> &mut Dynamic {
( self.values.get_mut(index).expect("invalid index in Scope")
self.values.get_mut(index).expect("invalid index in Scope"),
self.types[index],
)
} }
/// Update the access type of an entry in the [`Scope`]. /// Update the access type of an entry in the [`Scope`].
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -412,7 +384,6 @@ impl<'a> Scope<'a> {
.for_each(|(index, (name, alias))| { .for_each(|(index, (name, alias))| {
if !entries.names.iter().any(|(key, _)| key == name) { if !entries.names.iter().any(|(key, _)| key == name) {
entries.names.push((name.clone(), alias.clone())); entries.names.push((name.clone(), alias.clone()));
entries.types.push(self.types[index]);
entries.values.push(self.values[index].clone()); entries.values.push(self.values[index].clone());
} }
}); });
@ -422,13 +393,11 @@ impl<'a> Scope<'a> {
/// Get an iterator to entries in the [`Scope`]. /// Get an iterator to entries in the [`Scope`].
#[inline(always)] #[inline(always)]
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn into_iter( pub(crate) fn into_iter(self) -> impl Iterator<Item = (Cow<'a, str>, Dynamic, Vec<String>)> {
self,
) -> impl Iterator<Item = (Cow<'a, str>, EntryType, Dynamic, Vec<String>)> {
self.names self.names
.into_iter() .into_iter()
.zip(self.types.into_iter().zip(self.values.into_iter())) .zip(self.values.into_iter())
.map(|((name, alias), (typ, value))| (name, typ, value, alias.to_vec())) .map(|((name, alias), value)| (name, value, alias.to_vec()))
} }
/// Get an iterator to entries in the [`Scope`]. /// Get an iterator to entries in the [`Scope`].
/// Shared values are flatten-cloned. /// Shared values are flatten-cloned.
@ -466,17 +435,16 @@ impl<'a> Scope<'a> {
pub fn iter_raw<'x: 'a>(&'x self) -> impl Iterator<Item = (&'a str, bool, &'x Dynamic)> + 'x { pub fn iter_raw<'x: 'a>(&'x self) -> impl Iterator<Item = (&'a str, bool, &'x Dynamic)> + 'x {
self.names self.names
.iter() .iter()
.zip(self.types.iter().zip(self.values.iter())) .zip(self.values.iter())
.map(|((name, _), (typ, value))| (name.as_ref(), typ.is_constant(), value)) .map(|((name, _), value)| (name.as_ref(), value.is_constant(), value))
} }
} }
impl<'a, K: Into<Cow<'a, str>>> iter::Extend<(K, EntryType, Dynamic)> for Scope<'a> { impl<'a, K: Into<Cow<'a, str>>> iter::Extend<(K, Dynamic)> for Scope<'a> {
#[inline(always)] #[inline(always)]
fn extend<T: IntoIterator<Item = (K, EntryType, Dynamic)>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) {
iter.into_iter().for_each(|(name, typ, value)| { iter.into_iter().for_each(|(name, value)| {
self.names.push((name.into(), Box::new(Default::default()))); self.names.push((name.into(), Box::new(Default::default())));
self.types.push(typ);
self.values.push(value); self.values.push(value);
}); });
} }

View File

@ -178,7 +178,10 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
engine.eval::<INT>( engine.eval::<INT>(
r#" r#"
let x = [1, 2, 3]; let x = [1, 2, 3];
x.reduce(|sum, v, i| { if i == 0 { sum = 10 } sum + v * v }) x.reduce(|sum, v, i| {
if i == 0 { sum = 10 }
sum + v * v
})
"# "#
)?, )?,
24 24