From f22a04fc744b7d7ddd25a90948ade5c89503d422 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 8 Dec 2020 22:47:38 +0800 Subject: [PATCH] Move constancy into Dynamic. --- doc/src/SUMMARY.md | 2 +- doc/src/engine/optimize/index.md | 9 + doc/src/engine/optimize/optimize-levels.md | 8 + .../{other-op.md => assignment-op.md} | 10 +- doc/src/language/constants.md | 32 +- doc/src/links.md | 2 + src/ast.rs | 16 +- src/dynamic.rs | 437 ++++++++++-------- src/engine.rs | 250 +++++----- src/fn_call.rs | 25 +- src/module/mod.rs | 2 +- src/parser.rs | 53 ++- src/scope.rs | 80 +--- tests/arrays.rs | 5 +- 14 files changed, 501 insertions(+), 430 deletions(-) rename doc/src/language/{other-op.md => assignment-op.md} (86%) diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 891b760f..270f6d1b 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -75,7 +75,7 @@ The Rhai Scripting Language 5. [Variables](language/variables.md) 6. [Constants](language/constants.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) 10. [Switch Expression](language/switch.md) 11. [While Loop](language/while.md) diff --git a/doc/src/engine/optimize/index.md b/doc/src/engine/optimize/index.md index 59ae0f6a..6852604b 100644 --- a/doc/src/engine/optimize/index.md +++ b/doc/src/engine/optimize/index.md @@ -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 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 ------------------------- diff --git a/doc/src/engine/optimize/optimize-levels.md b/doc/src/engine/optimize/optimize-levels.md index 0f43938b..a1f867c1 100644 --- a/doc/src/engine/optimize/optimize-levels.md +++ b/doc/src/engine/optimize/optimize-levels.md @@ -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], 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. One benefit to this is that many more optimization opportunities arise, especially with regards to comparison operators. diff --git a/doc/src/language/other-op.md b/doc/src/language/assignment-op.md similarity index 86% rename from doc/src/language/other-op.md rename to doc/src/language/assignment-op.md index bf5017d3..d604dac3 100644 --- a/doc/src/language/other-op.md +++ b/doc/src/language/assignment-op.md @@ -1,12 +1,9 @@ -Other Operators -=============== +Compound Assignment Operators +============================= {{#include ../links.md}} -Compound Assignment Operators ----------------------------- - ```rust let number = 9; @@ -64,3 +61,6 @@ my_obj += #{c:3, d:4, e: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. diff --git a/doc/src/language/constants.md b/doc/src/language/constants.md index 52124d21..3e25d5a0 100644 --- a/doc/src/language/constants.md +++ b/doc/src/language/constants.md @@ -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 - -being a constant only prevents it from being re-assigned or operated upon by Rhai; -mutating it via a Rust function is still allowed. +A custom type stored as a constant cannot be modified via script, but _can_ be modified via +a registered Rust function that takes a first `&mut` parameter - because there is no way for +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. diff --git a/doc/src/links.md b/doc/src/links.md index 8c2cc211..a63f1b26 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -68,6 +68,8 @@ [variable]: {{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 [strings]: {{rootUrl}}/language/strings-chars.md diff --git a/src/ast.rs b/src/ast.rs index 0cd0cfaf..14156b88 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,6 +1,6 @@ //! 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::module::NamespaceRef; use crate::stdlib::{ @@ -940,10 +940,10 @@ impl Expr { Self::FloatConstant(x, _) => (*x).into(), Self::CharConstant(x, _) => (*x).into(), Self::StringConstant(x, _) => x.clone().into(), - Self::FnPointer(x, _) => Dynamic(Union::FnPtr(Box::new(FnPtr::new_unchecked( - x.clone(), - Default::default(), - )))), + Self::FnPointer(x, _) => Dynamic(Union::FnPtr( + Box::new(FnPtr::new_unchecked(x.clone(), Default::default())), + AccessType::Constant, + )), Self::BoolConstant(x, _) => (*x).into(), Self::Unit(_) => ().into(), @@ -954,7 +954,7 @@ impl Expr { x.len(), )); 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"))] @@ -967,7 +967,7 @@ impl Expr { x.iter() .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, @@ -1167,7 +1167,7 @@ mod tests { assert_eq!(size_of::>(), 16); assert_eq!(size_of::(), 32); assert_eq!(size_of::>(), 32); - assert_eq!(size_of::(), 72); + assert_eq!(size_of::(), 48); assert_eq!(size_of::(), 56); assert_eq!(size_of::(), 16); assert_eq!(size_of::(), 72); diff --git a/src/dynamic.rs b/src/dynamic.rs index 9360f0a8..10042587 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -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. pub struct Dynamic(pub(crate) Union); @@ -123,25 +142,25 @@ pub struct Dynamic(pub(crate) Union); /// /// Most variants are boxed to reduce the size. pub enum Union { - Unit(()), - Bool(bool), - Str(ImmutableString), - Char(char), - Int(INT), + Unit((), AccessType), + Bool(bool, AccessType), + Str(ImmutableString, AccessType), + Char(char, AccessType), + Int(INT, AccessType), #[cfg(not(feature = "no_float"))] - Float(FLOAT), + Float(FLOAT, AccessType), #[cfg(not(feature = "no_index"))] - Array(Box), + Array(Box, AccessType), #[cfg(not(feature = "no_object"))] - Map(Box), - FnPtr(Box), + Map(Box, AccessType), + FnPtr(Box, AccessType), #[cfg(not(feature = "no_std"))] - TimeStamp(Box), + TimeStamp(Box, AccessType), - Variant(Box>), + Variant(Box>, AccessType), #[cfg(not(feature = "no_closure"))] - Shared(crate::Shared>), + Shared(crate::Shared>, AccessType), } /// Underlying [`Variant`] read guard for [`Dynamic`]. @@ -236,7 +255,7 @@ impl Dynamic { #[inline(always)] pub fn is_variant(&self) -> bool { match self.0 { - Union::Variant(_) => true, + Union::Variant(_, _) => true, _ => false, } } @@ -246,7 +265,7 @@ impl Dynamic { pub fn is_shared(&self) -> bool { match self.0 { #[cfg(not(feature = "no_closure"))] - Union::Shared(_) => true, + Union::Shared(_, _) => true, _ => false, } } @@ -272,29 +291,29 @@ impl Dynamic { /// Otherwise, this call panics if the data is currently borrowed for write. pub fn type_id(&self) -> TypeId { match &self.0 { - Union::Unit(_) => TypeId::of::<()>(), - Union::Bool(_) => TypeId::of::(), - Union::Str(_) => TypeId::of::(), - Union::Char(_) => TypeId::of::(), - Union::Int(_) => TypeId::of::(), + Union::Unit(_, _) => TypeId::of::<()>(), + Union::Bool(_, _) => TypeId::of::(), + Union::Str(_, _) => TypeId::of::(), + Union::Char(_, _) => TypeId::of::(), + Union::Int(_, _) => TypeId::of::(), #[cfg(not(feature = "no_float"))] - Union::Float(_) => TypeId::of::(), + Union::Float(_, _) => TypeId::of::(), #[cfg(not(feature = "no_index"))] - Union::Array(_) => TypeId::of::(), + Union::Array(_, _) => TypeId::of::(), #[cfg(not(feature = "no_object"))] - Union::Map(_) => TypeId::of::(), - Union::FnPtr(_) => TypeId::of::(), + Union::Map(_, _) => TypeId::of::(), + Union::FnPtr(_, _) => TypeId::of::(), #[cfg(not(feature = "no_std"))] - Union::TimeStamp(_) => TypeId::of::(), + Union::TimeStamp(_, _) => TypeId::of::(), - Union::Variant(value) => (***value).type_id(), + Union::Variant(value, _) => (***value).type_id(), #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] - Union::Shared(cell) => (*cell.borrow()).type_id(), + Union::Shared(cell, _) => (*cell.borrow()).type_id(), #[cfg(not(feature = "no_closure"))] #[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`]. @@ -305,32 +324,32 @@ impl Dynamic { /// Otherwise, this call panics if the data is currently borrowed for write. pub fn type_name(&self) -> &'static str { match &self.0 { - Union::Unit(_) => "()", - Union::Bool(_) => "bool", - Union::Str(_) => "string", - Union::Char(_) => "char", - Union::Int(_) => type_name::(), + Union::Unit(_, _) => "()", + Union::Bool(_, _) => "bool", + Union::Str(_, _) => "string", + Union::Char(_, _) => "char", + Union::Int(_, _) => type_name::(), #[cfg(not(feature = "no_float"))] - Union::Float(_) => type_name::(), + Union::Float(_, _) => type_name::(), #[cfg(not(feature = "no_index"))] - Union::Array(_) => "array", + Union::Array(_, _) => "array", #[cfg(not(feature = "no_object"))] - Union::Map(_) => "map", - Union::FnPtr(_) => "Fn", + Union::Map(_, _) => "map", + Union::FnPtr(_, _) => "Fn", #[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 = "sync"))] - Union::Shared(cell) => cell + Union::Shared(cell, _) => cell .try_borrow() .map(|v| (*v).type_name()) .unwrap_or(""), #[cfg(not(feature = "no_closure"))] #[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); match &self.0 { - Union::Unit(_) => ().hash(state), - Union::Bool(value) => value.hash(state), - Union::Str(s) => s.hash(state), - Union::Char(ch) => ch.hash(state), - Union::Int(i) => i.hash(state), + Union::Unit(_, _) => ().hash(state), + Union::Bool(value, _) => value.hash(state), + Union::Str(s, _) => s.hash(state), + Union::Char(ch, _) => ch.hash(state), + Union::Int(i, _) => i.hash(state), #[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"))] - Union::Array(a) => (**a).hash(state), + Union::Array(a, _) => (**a).hash(state), #[cfg(not(feature = "no_object"))] - Union::Map(m) => { + Union::Map(m, _) => { let mut buf: crate::StaticVec<_> = m.iter().collect(); buf.sort_by(|(a, _), (b, _)| a.cmp(b)); @@ -362,10 +381,10 @@ impl Hash for Dynamic { #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] - Union::Shared(cell) => (*cell.borrow()).hash(state), + Union::Shared(cell, _) => (*cell.borrow()).hash(state), #[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] - Union::Shared(cell) => (*cell.read().unwrap()).hash(state), + Union::Shared(cell, _) => (*cell.read().unwrap()).hash(state), _ => unimplemented!(), } @@ -404,29 +423,29 @@ pub(crate) fn map_std_type_name(name: &str) -> &str { impl fmt::Display for Dynamic { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.0 { - Union::Unit(_) => write!(f, ""), - Union::Bool(value) => fmt::Display::fmt(value, f), - Union::Str(value) => fmt::Display::fmt(value, f), - Union::Char(value) => fmt::Display::fmt(value, f), - Union::Int(value) => fmt::Display::fmt(value, f), + Union::Unit(_, _) => write!(f, ""), + Union::Bool(value, _) => fmt::Display::fmt(value, f), + Union::Str(value, _) => fmt::Display::fmt(value, f), + Union::Char(value, _) => fmt::Display::fmt(value, f), + Union::Int(value, _) => fmt::Display::fmt(value, f), #[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"))] - Union::Array(value) => fmt::Debug::fmt(value, f), + Union::Array(value, _) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_object"))] - Union::Map(value) => { + Union::Map(value, _) => { f.write_str("#")?; 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"))] - Union::TimeStamp(_) => f.write_str(""), + Union::TimeStamp(_, _) => f.write_str(""), - 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 = "sync"))] - Union::Shared(cell) => { + Union::Shared(cell, _) => { if let Ok(v) = cell.try_borrow() { fmt::Display::fmt(&*v, f) } else { @@ -435,7 +454,7 @@ impl fmt::Display for Dynamic { } #[cfg(not(feature = "no_closure"))] #[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 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.0 { - Union::Unit(value) => fmt::Debug::fmt(value, f), - Union::Bool(value) => fmt::Debug::fmt(value, f), - Union::Str(value) => fmt::Debug::fmt(value, f), - Union::Char(value) => fmt::Debug::fmt(value, f), - Union::Int(value) => fmt::Debug::fmt(value, f), + Union::Unit(value, _) => fmt::Debug::fmt(value, f), + Union::Bool(value, _) => fmt::Debug::fmt(value, f), + Union::Str(value, _) => fmt::Debug::fmt(value, f), + Union::Char(value, _) => fmt::Debug::fmt(value, f), + Union::Int(value, _) => fmt::Debug::fmt(value, f), #[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"))] - Union::Array(value) => fmt::Debug::fmt(value, f), + Union::Array(value, _) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_object"))] - Union::Map(value) => { + Union::Map(value, _) => { f.write_str("#")?; 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"))] - Union::TimeStamp(_) => write!(f, ""), + Union::TimeStamp(_, _) => write!(f, ""), - Union::Variant(value) => write!(f, "{}", (*value).type_name()), + Union::Variant(value, _) => write!(f, "{}", (*value).type_name()), #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] - Union::Shared(cell) => { + Union::Shared(cell, _) => { if let Ok(v) = cell.try_borrow() { write!(f, "{:?} (shared)", *v) } else { @@ -474,33 +493,40 @@ impl fmt::Debug for Dynamic { } #[cfg(not(feature = "no_closure"))] #[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 { + /// Clone the [`Dynamic`] value. + /// + /// ## WARNING + /// + /// The cloned copy is marked [`AccessType::Normal`] even if the original is constant. fn clone(&self) -> Self { match self.0 { - Union::Unit(value) => Self(Union::Unit(value)), - Union::Bool(value) => Self(Union::Bool(value)), - Union::Str(ref value) => Self(Union::Str(value.clone())), - Union::Char(value) => Self(Union::Char(value)), - Union::Int(value) => Self(Union::Int(value)), + Union::Unit(value, _) => Self(Union::Unit(value, AccessType::Normal)), + Union::Bool(value, _) => Self(Union::Bool(value, AccessType::Normal)), + Union::Str(ref value, _) => Self(Union::Str(value.clone(), AccessType::Normal)), + Union::Char(value, _) => Self(Union::Char(value, AccessType::Normal)), + Union::Int(value, _) => Self(Union::Int(value, AccessType::Normal)), #[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"))] - 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"))] - Union::Map(ref value) => Self(Union::Map(value.clone())), - Union::FnPtr(ref value) => Self(Union::FnPtr(value.clone())), + Union::Map(ref value, _) => Self(Union::Map(value.clone(), AccessType::Normal)), + Union::FnPtr(ref value, _) => Self(Union::FnPtr(value.clone(), AccessType::Normal)), #[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"))] - 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 { /// 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`. - pub const TRUE: Dynamic = Self(Union::Bool(true)); + pub const TRUE: Dynamic = Self(Union::Bool(true, AccessType::Normal)); /// 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. - pub const ZERO: Dynamic = Self(Union::Int(0)); + pub const ZERO: Dynamic = Self(Union::Int(0, AccessType::Normal)); /// 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. - 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. #[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. #[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. #[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. /// /// # 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`]`>>` /// 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. #[inline(always)] pub fn into_shared(self) -> Self { + let _access = self.access_type(); + #[cfg(not(feature = "no_closure"))] return match self.0 { - Union::Shared(..) => self, - _ => Self(Union::Shared(crate::Locked::new(self).into())), + Union::Shared(_, _) => self, + _ => Self(Union::Shared(crate::Locked::new(self).into(), _access)), }; #[cfg(feature = "no_closure")] @@ -707,11 +773,11 @@ impl Dynamic { match self.0 { #[cfg(not(feature = "no_closure"))] #[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(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::() == TypeId::of::() { return match self.0 { - Union::Int(value) => unsafe_try_cast(value), + Union::Int(value, _) => unsafe_try_cast(value), _ => None, }; } @@ -729,35 +795,35 @@ impl Dynamic { #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Float(value) => unsafe_try_cast(value), + Union::Float(value, _) => unsafe_try_cast(value), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Bool(value) => unsafe_try_cast(value), + Union::Bool(value, _) => unsafe_try_cast(value), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Str(value) => unsafe_try_cast(value), + Union::Str(value, _) => unsafe_try_cast(value), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Str(value) => unsafe_try_cast(value.into_owned()), + Union::Str(value, _) => unsafe_try_cast(value.into_owned()), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Char(value) => unsafe_try_cast(value), + Union::Char(value, _) => unsafe_try_cast(value), _ => None, }; } @@ -765,7 +831,7 @@ impl Dynamic { #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { 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, }; } @@ -773,14 +839,14 @@ impl Dynamic { #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { 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, }; } if TypeId::of::() == TypeId::of::() { 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, }; } @@ -788,22 +854,22 @@ impl Dynamic { #[cfg(not(feature = "no_std"))] if TypeId::of::() == TypeId::of::() { 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, }; } if TypeId::of::() == TypeId::of::<()>() { return match self.0 { - Union::Unit(value) => unsafe_try_cast(value), + Union::Unit(value, _) => unsafe_try_cast(value), _ => None, }; } 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"))] - Union::Shared(_) => unreachable!(), + Union::Shared(_, _) => unreachable!(), _ => None, } } @@ -859,7 +925,7 @@ impl Dynamic { pub fn flatten_clone(&self) -> Self { match &self.0 { #[cfg(not(feature = "no_closure"))] - Union::Shared(cell) => { + Union::Shared(cell, _) => { #[cfg(not(feature = "sync"))] return cell.borrow().clone(); @@ -879,7 +945,7 @@ impl Dynamic { pub fn flatten(self) -> Self { match self.0 { #[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| { #[cfg(not(feature = "sync"))] return cell.borrow().clone(); @@ -907,7 +973,7 @@ impl Dynamic { pub fn is_locked(&self) -> bool { match self.0 { #[cfg(not(feature = "no_closure"))] - Union::Shared(ref _cell) => { + Union::Shared(ref _cell, _) => { #[cfg(not(feature = "sync"))] return _cell.try_borrow().is_err(); @@ -930,7 +996,7 @@ impl Dynamic { pub fn read_lock(&self) -> Option> { match self.0 { #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell) => { + Union::Shared(ref cell, _) => { #[cfg(not(feature = "sync"))] let data = cell.borrow(); #[cfg(feature = "sync")] @@ -962,7 +1028,7 @@ impl Dynamic { pub fn write_lock(&mut self) -> Option> { match self.0 { #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell) => { + Union::Shared(ref cell, _) => { #[cfg(not(feature = "sync"))] let data = cell.borrow_mut(); #[cfg(feature = "sync")] @@ -991,71 +1057,71 @@ impl Dynamic { if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Int(value) => ::downcast_ref::(value), + Union::Int(value, _) => ::downcast_ref::(value), _ => None, }; } #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Float(value) => ::downcast_ref::(value), + Union::Float(value, _) => ::downcast_ref::(value), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Bool(value) => ::downcast_ref::(value), + Union::Bool(value, _) => ::downcast_ref::(value), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Str(value) => ::downcast_ref::(value), + Union::Str(value, _) => ::downcast_ref::(value), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Str(value) => ::downcast_ref::(value.as_ref()), + Union::Str(value, _) => ::downcast_ref::(value.as_ref()), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Char(value) => ::downcast_ref::(value), + Union::Char(value, _) => ::downcast_ref::(value), _ => None, }; } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Array(value) => ::downcast_ref::(value.as_ref()), + Union::Array(value, _) => ::downcast_ref::(value.as_ref()), _ => None, }; } #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Map(value) => ::downcast_ref::(value.as_ref()), + Union::Map(value, _) => ::downcast_ref::(value.as_ref()), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::FnPtr(value) => ::downcast_ref::(value.as_ref()), + Union::FnPtr(value, _) => ::downcast_ref::(value.as_ref()), _ => None, }; } #[cfg(not(feature = "no_std"))] if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::TimeStamp(value) => ::downcast_ref::(value.as_ref()), + Union::TimeStamp(value, _) => ::downcast_ref::(value.as_ref()), _ => None, }; } if TypeId::of::() == TypeId::of::<()>() { return match &self.0 { - Union::Unit(value) => ::downcast_ref::(value), + Union::Unit(value, _) => ::downcast_ref::(value), _ => None, }; } @@ -1064,9 +1130,9 @@ impl Dynamic { } match &self.0 { - Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::(), + Union::Variant(value, _) => value.as_ref().as_ref().as_any().downcast_ref::(), #[cfg(not(feature = "no_closure"))] - Union::Shared(_) => None, + Union::Shared(_, _) => None, _ => None, } } @@ -1080,65 +1146,65 @@ impl Dynamic { if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Int(value) => ::downcast_mut::(value), + Union::Int(value, _) => ::downcast_mut::(value), _ => None, }; } #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Float(value) => ::downcast_mut::(value), + Union::Float(value, _) => ::downcast_mut::(value), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Bool(value) => ::downcast_mut::(value), + Union::Bool(value, _) => ::downcast_mut::(value), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Str(value) => ::downcast_mut::(value), + Union::Str(value, _) => ::downcast_mut::(value), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Char(value) => ::downcast_mut::(value), + Union::Char(value, _) => ::downcast_mut::(value), _ => None, }; } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Array(value) => ::downcast_mut::(value.as_mut()), + Union::Array(value, _) => ::downcast_mut::(value.as_mut()), _ => None, }; } #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Map(value) => ::downcast_mut::(value.as_mut()), + Union::Map(value, _) => ::downcast_mut::(value.as_mut()), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::FnPtr(value) => ::downcast_mut::(value.as_mut()), + Union::FnPtr(value, _) => ::downcast_mut::(value.as_mut()), _ => None, }; } #[cfg(not(feature = "no_std"))] if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::TimeStamp(value) => ::downcast_mut::(value.as_mut()), + Union::TimeStamp(value, _) => ::downcast_mut::(value.as_mut()), _ => None, }; } if TypeId::of::() == TypeId::of::<()>() { return match &mut self.0 { - Union::Unit(value) => ::downcast_mut::(value), + Union::Unit(value, _) => ::downcast_mut::(value), _ => None, }; } @@ -1147,9 +1213,9 @@ impl Dynamic { } match &mut self.0 { - Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::(), + Union::Variant(value, _) => value.as_mut().as_mut_any().downcast_mut::(), #[cfg(not(feature = "no_closure"))] - Union::Shared(_) => None, + Union::Shared(_, _) => None, _ => None, } } @@ -1158,9 +1224,9 @@ impl Dynamic { #[inline(always)] pub fn as_int(&self) -> Result { match self.0 { - Union::Int(n) => Ok(n), + Union::Int(n, _) => Ok(n), #[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()), } } @@ -1170,9 +1236,9 @@ impl Dynamic { #[inline(always)] pub fn as_float(&self) -> Result { match self.0 { - Union::Float(n) => Ok(n), + Union::Float(n, _) => Ok(n), #[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()), } } @@ -1181,9 +1247,9 @@ impl Dynamic { #[inline(always)] pub fn as_bool(&self) -> Result { match self.0 { - Union::Bool(b) => Ok(b), + Union::Bool(b, _) => Ok(b), #[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()), } } @@ -1192,9 +1258,9 @@ impl Dynamic { #[inline(always)] pub fn as_char(&self) -> Result { match self.0 { - Union::Char(n) => Ok(n), + Union::Char(n, _) => Ok(n), #[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()), } } @@ -1205,8 +1271,8 @@ impl Dynamic { #[inline(always)] pub fn as_str(&self) -> Result<&str, &'static str> { match &self.0 { - Union::Str(s) => Ok(s), - Union::FnPtr(f) => Ok(f.fn_name()), + Union::Str(s, _) => Ok(s), + Union::FnPtr(f, _) => Ok(f.fn_name()), _ => Err(self.type_name()), } } @@ -1223,16 +1289,16 @@ impl Dynamic { #[inline] pub fn take_immutable_string(self) -> Result { match self.0 { - Union::Str(s) => Ok(s), - Union::FnPtr(f) => Ok(f.take_data().0), + Union::Str(s, _) => Ok(s), + Union::FnPtr(f, _) => Ok(f.take_data().0), #[cfg(not(feature = "no_closure"))] - Union::Shared(cell) => { + Union::Shared(cell, _) => { #[cfg(not(feature = "sync"))] { let inner = cell.borrow(); match &inner.0 { - Union::Str(s) => Ok(s.clone()), - Union::FnPtr(f) => Ok(f.clone().take_data().0), + Union::Str(s, _) => Ok(s.clone()), + Union::FnPtr(f, _) => Ok(f.clone().take_data().0), _ => Err((*inner).type_name()), } } @@ -1240,8 +1306,8 @@ impl Dynamic { { let inner = cell.read().unwrap(); match &inner.0 { - Union::Str(s) => Ok(s.clone()), - Union::FnPtr(f) => Ok(f.clone().take_data().0), + Union::Str(s, _) => Ok(s.clone()), + Union::FnPtr(f, _) => Ok(f.clone().take_data().0), _ => Err((*inner).type_name()), } } @@ -1254,56 +1320,58 @@ impl Dynamic { impl From<()> for Dynamic { #[inline(always)] fn from(value: ()) -> Self { - Self(Union::Unit(value)) + Self(Union::Unit(value, AccessType::Normal)) } } impl From for Dynamic { #[inline(always)] fn from(value: bool) -> Self { - Self(Union::Bool(value)) + Self(Union::Bool(value, AccessType::Normal)) } } impl From for Dynamic { #[inline(always)] fn from(value: INT) -> Self { - Self(Union::Int(value)) + Self(Union::Int(value, AccessType::Normal)) } } #[cfg(not(feature = "no_float"))] impl From for Dynamic { #[inline(always)] fn from(value: FLOAT) -> Self { - Self(Union::Float(value)) + Self(Union::Float(value, AccessType::Normal)) } } impl From for Dynamic { #[inline(always)] fn from(value: char) -> Self { - Self(Union::Char(value)) + Self(Union::Char(value, AccessType::Normal)) } } impl> From for Dynamic { #[inline(always)] fn from(value: S) -> Self { - Self(Union::Str(value.into())) + Self(Union::Str(value.into(), AccessType::Normal)) } } #[cfg(not(feature = "no_index"))] impl From> for Dynamic { #[inline(always)] fn from(value: crate::stdlib::vec::Vec) -> Self { - Self(Union::Array(Box::new( - value.into_iter().map(Dynamic::from).collect(), - ))) + Self(Union::Array( + Box::new(value.into_iter().map(Dynamic::from).collect()), + AccessType::Normal, + )) } } #[cfg(not(feature = "no_index"))] impl From<&[T]> for Dynamic { #[inline(always)] fn from(value: &[T]) -> Self { - Self(Union::Array(Box::new( - value.iter().cloned().map(Dynamic::from).collect(), - ))) + Self(Union::Array( + Box::new(value.iter().cloned().map(Dynamic::from).collect()), + AccessType::Normal, + )) } } #[cfg(not(feature = "no_object"))] @@ -1312,30 +1380,33 @@ impl, T: Variant + Clone> From) -> Self { - Self(Union::Map(Box::new( - value - .into_iter() - .map(|(k, v)| (k.into(), Dynamic::from(v))) - .collect(), - ))) + Self(Union::Map( + Box::new( + value + .into_iter() + .map(|(k, v)| (k.into(), Dynamic::from(v))) + .collect(), + ), + AccessType::Normal, + )) } } impl From for Dynamic { #[inline(always)] fn from(value: FnPtr) -> Self { - Self(Union::FnPtr(Box::new(value))) + Self(Union::FnPtr(Box::new(value), AccessType::Normal)) } } impl From> for Dynamic { #[inline(always)] fn from(value: Box) -> Self { - Self(Union::FnPtr(value)) + Self(Union::FnPtr(value, AccessType::Normal)) } } #[cfg(not(feature = "no_std"))] impl From for Dynamic { #[inline(always)] fn from(value: Instant) -> Self { - Self(Union::TimeStamp(Box::new(value))) + Self(Union::TimeStamp(Box::new(value), AccessType::Normal)) } } diff --git a/src/engine.rs b/src/engine.rs index 35f50b16..b399c6e7 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,14 +1,13 @@ //! Main module defining the script evaluation [`Engine`]. 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_native::{CallableFunction, Callback, IteratorFn, OnVarCallback}; use crate::module::NamespaceRef; use crate::optimize::OptimizationLevel; use crate::packages::{Package, PackagesCollection, StandardPackage}; use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; -use crate::scope::EntryType as ScopeEntryType; use crate::stdlib::{ any::{type_name, TypeId}, borrow::Cow, @@ -833,7 +832,7 @@ impl Engine { lib: &[&Module], this_ptr: &'s mut Option<&mut Dynamic>, expr: &'a Expr, - ) -> Result<(Target<'s>, &'a str, ScopeEntryType, Position), Box> { + ) -> Result<(Target<'s>, &'a str, Position), Box> { match expr { Expr::Variable(v) => match v.as_ref() { // Qualified variable @@ -850,7 +849,9 @@ impl Engine { })?; // 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 _ => self.search_scope_only(scope, mods, state, lib, this_ptr, expr), @@ -868,7 +869,7 @@ impl Engine { lib: &[&Module], this_ptr: &'s mut Option<&mut Dynamic>, expr: &'a Expr, - ) -> Result<(Target<'s>, &'a str, ScopeEntryType, Position), Box> { + ) -> Result<(Target<'s>, &'a str, Position), Box> { let (index, _, _, IdentX { name, pos }) = match expr { Expr::Variable(v) => v.as_ref(), _ => unreachable!(), @@ -877,7 +878,7 @@ impl Engine { // Check if the variable is `this` if name.as_str() == KEYWORD_THIS { if let Some(val) = this_ptr { - return Ok(((*val).into(), KEYWORD_THIS, ScopeEntryType::Normal, *pos)); + return Ok(((*val).into(), KEYWORD_THIS, *pos)); } else { return EvalAltResult::ErrorUnboundThis(*pos).into(); } @@ -901,10 +902,11 @@ impl Engine { this_ptr, level: 0, }; - if let Some(result) = + if let Some(mut result) = 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 }; - 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 // when the object variable is also used as a parameter. @@ -926,7 +928,7 @@ impl Engine { // return EvalAltResult::ErrorDataRace(name.into(), *pos).into(); // } - Ok((val.into(), name, typ, *pos)) + Ok((val.into(), name, *pos)) } /// Chain-evaluate a dot/index chain. @@ -1279,16 +1281,13 @@ impl Engine { self.inc_operations(state) .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)?; // Constants cannot be modified - match typ { - ScopeEntryType::Constant if new_val.is_some() => { - return EvalAltResult::ErrorAssignmentToConstant(var_name.to_string(), pos) - .into(); - } - ScopeEntryType::Constant | ScopeEntryType::Normal => (), + if target.as_ref().is_constant() && new_val.is_some() { + return EvalAltResult::ErrorAssignmentToConstant(var_name.to_string(), pos) + .into(); } let obj_ptr = &mut target.into(); @@ -1410,7 +1409,7 @@ impl Engine { match target { #[cfg(not(feature = "no_index"))] - Dynamic(Union::Array(arr)) => { + Dynamic(Union::Array(arr, _)) => { // val_array[idx] let index = idx .as_int() @@ -1430,7 +1429,7 @@ impl Engine { } #[cfg(not(feature = "no_object"))] - Dynamic(Union::Map(map)) => { + Dynamic(Union::Map(map, _)) => { // val_map[idx] Ok(if _create { let index = idx.take_immutable_string().map_err(|err| { @@ -1450,7 +1449,7 @@ impl Engine { } #[cfg(not(feature = "no_index"))] - Dynamic(Union::Str(s)) => { + Dynamic(Union::Str(s, _)) => { // val_string[idx] let chars_len = s.chars().count(); let index = idx @@ -1517,7 +1516,7 @@ impl Engine { match rhs_value { #[cfg(not(feature = "no_index"))] - Dynamic(Union::Array(mut rhs_value)) => { + Dynamic(Union::Array(mut rhs_value, _)) => { // Call the `==` operator to compare each value let def_value = Some(false.into()); let def_value = def_value.as_ref(); @@ -1545,16 +1544,16 @@ impl Engine { Ok(false.into()) } #[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 - 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::Str(s, _)) => Ok(rhs_value.contains_key(&s).into()), + Dynamic(Union::Char(c, _)) => Ok(rhs_value.contains_key(&c.to_string()).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 - Dynamic(Union::Str(s)) => Ok(rhs_value.contains(s.as_str()).into()), - Dynamic(Union::Char(c)) => Ok(rhs_value.contains(c).into()), + Dynamic(Union::Str(s, _)) => Ok(rhs_value.contains(s.as_str()).into()), + Dynamic(Union::Char(c, _)) => Ok(rhs_value.contains(c).into()), _ => EvalAltResult::ErrorInExpr(lhs.position()).into(), }, _ => EvalAltResult::ErrorInExpr(rhs.position()).into(), @@ -1576,17 +1575,15 @@ impl Engine { match expr { // var - point directly to the value Expr::Variable(_) => { - let (target, _, typ, pos) = + let (mut target, _, pos) = self.search_namespace(scope, mods, state, lib, this_ptr, expr)?; - Ok(( - match typ { - // If necessary, constants are cloned - ScopeEntryType::Constant if no_const => target.into_owned(), - _ => target, - }, - pos, - )) + // If necessary, constants are cloned + if target.as_ref().is_constant() { + target = target.into_owned(); + } + + Ok((target, pos)) } // var[...] #[cfg(not(feature = "no_index"))] @@ -1706,8 +1703,7 @@ impl Engine { } } Expr::Variable(_) => { - let (val, _, _, _) = - self.search_namespace(scope, mods, state, lib, this_ptr, expr)?; + let (val, _, _) = self.search_namespace(scope, mods, state, lib, this_ptr, expr)?; Ok(val.take_or_clone()) } Expr::Property(_) => unreachable!(), @@ -1736,7 +1732,7 @@ impl Engine { for item in x.as_ref() { 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"))] @@ -1749,7 +1745,7 @@ impl Engine { 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 @@ -1913,7 +1909,7 @@ impl Engine { let mut rhs_val = self .eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)? .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)?; if !lhs_ptr.is_ref() { @@ -1923,88 +1919,86 @@ impl Engine { self.inc_operations(state) .map_err(|err| err.fill_position(pos))?; - match typ { + if lhs_ptr.as_ref().is_constant() { // Assignment to constant variable - ScopeEntryType::Constant => Err(Box::new( - EvalAltResult::ErrorAssignmentToConstant(name.to_string(), pos), - )), + Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( + name.to_string(), + pos, + ))) + } else if op.is_empty() { // Normal assignment - ScopeEntryType::Normal if op.is_empty() => { - if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { - *lhs_ptr.as_mut().write_lock::().unwrap() = rhs_val; - } else { - *lhs_ptr.as_mut() = rhs_val; - } - Ok(Dynamic::UNIT) + if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { + *lhs_ptr.as_mut().write_lock::().unwrap() = rhs_val; + } else { + *lhs_ptr.as_mut() = rhs_val; } + Ok(Dynamic::UNIT) + } else { // Op-assignment - in order of precedence: - ScopeEntryType::Normal => { - // 1) Native registered overriding function - // 2) Built-in implementation - // 3) Map to `var = var op rhs` + // 1) Native registered overriding function + // 2) Built-in implementation + // 3) Map to `var = var op rhs` - // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. - let arg_types = - once(lhs_ptr.as_mut().type_id()).chain(once(rhs_val.type_id())); - let hash_fn = calc_native_fn_hash(empty(), op, arg_types); + // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. + let arg_types = once(lhs_ptr.as_mut().type_id()).chain(once(rhs_val.type_id())); + let hash_fn = calc_native_fn_hash(empty(), op, arg_types); - match self - .global_namespace - .get_fn(hash_fn, false) - .or_else(|| self.packages.get_fn(hash_fn)) - .or_else(|| mods.get_fn(hash_fn)) - { - // op= function registered as method - Some(func) if func.is_method() => { - let mut lock_guard; - let lhs_ptr_inner; + match self + .global_namespace + .get_fn(hash_fn, false) + .or_else(|| self.packages.get_fn(hash_fn)) + .or_else(|| mods.get_fn(hash_fn)) + { + // op= function registered as method + Some(func) if func.is_method() => { + let mut lock_guard; + let lhs_ptr_inner; - if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { - lock_guard = lhs_ptr.as_mut().write_lock::().unwrap(); - lhs_ptr_inner = lock_guard.deref_mut(); - } else { - lhs_ptr_inner = lhs_ptr.as_mut(); - } - - let args = &mut [lhs_ptr_inner, &mut rhs_val]; - - // Overriding exact implementation - if func.is_plugin_fn() { - func.get_plugin_fn() - .call((self, &*mods, lib).into(), args)?; - } else { - func.get_native_fn()((self, &*mods, lib).into(), args)?; - } + if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { + lock_guard = lhs_ptr.as_mut().write_lock::().unwrap(); + lhs_ptr_inner = lock_guard.deref_mut(); + } else { + lhs_ptr_inner = lhs_ptr.as_mut(); } - // Built-in op-assignment function - _ if run_builtin_op_assignment(op, lhs_ptr.as_mut(), &rhs_val)? - .is_some() => {} - // Not built-in: expand to `var = var op rhs` - _ => { - let op = &op[..op.len() - 1]; // extract operator without = - // Clone the LHS value - let args = &mut [&mut lhs_ptr.as_mut().clone(), &mut rhs_val]; + let args = &mut [lhs_ptr_inner, &mut rhs_val]; - // Run function - let (value, _) = self - .exec_fn_call( - mods, state, lib, op, 0, args, false, false, false, None, - None, level, - ) - .map_err(|err| err.fill_position(*op_pos))?; - - let value = value.flatten(); - - if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { - *lhs_ptr.as_mut().write_lock::().unwrap() = value; - } else { - *lhs_ptr.as_mut() = value; - } + // Overriding exact implementation + if func.is_plugin_fn() { + func.get_plugin_fn() + .call((self, &*mods, lib).into(), args)?; + } else { + func.get_native_fn()((self, &*mods, lib).into(), args)?; + } + } + // Built-in op-assignment function + _ if run_builtin_op_assignment(op, lhs_ptr.as_mut(), &rhs_val)? + .is_some() => {} + // Not built-in: expand to `var = var op rhs` + _ => { + let op = &op[..op.len() - 1]; // extract operator without = + + // Clone the LHS value + let args = &mut [&mut lhs_ptr.as_mut().clone(), &mut rhs_val]; + + // Run function + let (value, _) = self + .exec_fn_call( + mods, state, lib, op, 0, args, false, false, false, None, None, + level, + ) + .map_err(|err| err.fill_position(*op_pos))?; + + let value = value.flatten(); + + if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { + *lhs_ptr.as_mut().write_lock::().unwrap() = value; + } else { + *lhs_ptr.as_mut() = value; } } - Ok(Dynamic::UNIT) } + Ok(Dynamic::UNIT) } } @@ -2175,7 +2169,7 @@ impl Engine { state.scope_level += 1; 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(); if cfg!(not(feature = "no_closure")) && loop_var.is_shared() { @@ -2249,8 +2243,10 @@ impl Engine { .map(|_| ().into()); if let Some(result_err) = result.as_ref().err() { - if let EvalAltResult::ErrorRuntime(Dynamic(Union::Unit(_)), pos) = - result_err.as_ref() + if let EvalAltResult::ErrorRuntime( + Dynamic(Union::Unit(_, _)), + pos, + ) = result_err.as_ref() { err.set_position(*pos); result = Err(Box::new(err)); @@ -2293,8 +2289,8 @@ impl Engine { // Let/const statement Stmt::Let(var_def, expr, export, _) | Stmt::Const(var_def, expr, export, _) => { let entry_type = match stmt { - Stmt::Let(_, _, _, _) => ScopeEntryType::Normal, - Stmt::Const(_, _, _, _) => ScopeEntryType::Constant, + Stmt::Let(_, _, _, _) => AccessType::Normal, + Stmt::Const(_, _, _, _) => AccessType::Constant, _ => unreachable!(), }; @@ -2388,8 +2384,8 @@ impl Engine { #[cfg(not(feature = "no_closure"))] Stmt::Share(x) => { match scope.get_index(&x.name) { - Some((index, ScopeEntryType::Normal)) => { - let (val, _) = scope.get_mut(index); + Some((index, AccessType::Normal)) => { + let val = scope.get_mut(index); if !val.is_shared() { // Replace the variable with a shared value. @@ -2445,18 +2441,18 @@ impl Engine { fn calc_size(value: &Dynamic) -> (usize, usize, usize) { match value { #[cfg(not(feature = "no_index"))] - Dynamic(Union::Array(arr)) => { + Dynamic(Union::Array(arr, _)) => { let mut arrays = 0; let mut maps = 0; arr.iter().for_each(|value| match value { - Dynamic(Union::Array(_)) => { + Dynamic(Union::Array(_, _)) => { let (a, m, _) = calc_size(value); arrays += a; maps += m; } #[cfg(not(feature = "no_object"))] - Dynamic(Union::Map(_)) => { + Dynamic(Union::Map(_, _)) => { let (a, m, _) = calc_size(value); arrays += a; maps += m; @@ -2467,18 +2463,18 @@ impl Engine { (arrays, maps, 0) } #[cfg(not(feature = "no_object"))] - Dynamic(Union::Map(map)) => { + Dynamic(Union::Map(map, _)) => { let mut arrays = 0; let mut maps = 0; map.values().for_each(|value| match value { #[cfg(not(feature = "no_index"))] - Dynamic(Union::Array(_)) => { + Dynamic(Union::Array(_, _)) => { let (a, m, _) = calc_size(value); arrays += a; maps += m; } - Dynamic(Union::Map(_)) => { + Dynamic(Union::Map(_, _)) => { let (a, m, _) = calc_size(value); arrays += a; maps += m; @@ -2488,7 +2484,7 @@ impl Engine { (arrays, maps, 0) } - Dynamic(Union::Str(s)) => (0, 0, s.len()), + Dynamic(Union::Str(s, _)) => (0, 0, s.len()), _ => (0, 0, 0), } } @@ -2497,13 +2493,13 @@ impl Engine { // Simply return all errors Err(_) => return result, // 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 #[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 #[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 Ok(_) => return result, }; diff --git a/src/fn_call.rs b/src/fn_call.rs index 13514dcb..bdcd4cb7 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -8,7 +8,6 @@ use crate::engine::{ use crate::fn_native::FnCallArgs; use crate::module::NamespaceRef; use crate::optimize::OptimizationLevel; -use crate::scope::EntryType as ScopeEntryType; use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, @@ -360,7 +359,7 @@ impl Engine { .map(|(name, value)| { let var_name: crate::stdlib::borrow::Cow<'_, str> = 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() { captured .into_iter() - .filter(|(name, _, _, _)| func.externals.contains(name.as_ref())) - .for_each(|(name, typ, value, _)| { + .filter(|(name, _, _)| func.externals.contains(name.as_ref())) + .for_each(|(name, value, _)| { // Consume the scope values. - match typ { - ScopeEntryType::Normal => scope.push(name, value), - ScopeEntryType::Constant => { - scope.push_constant(name, value) - } - }; + scope.push_dynamic(name, value); }); } } @@ -1000,13 +994,12 @@ impl Engine { .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) .collect::>()?; - let (target, _, typ, pos) = + let (mut target, _, pos) = self.search_namespace(scope, mods, state, lib, this_ptr, &args_expr[0])?; - let target = match typ { - ScopeEntryType::Normal => target, - ScopeEntryType::Constant => target.into_owned(), - }; + if target.as_ref().is_constant() { + target = target.into_owned(); + } self.inc_operations(state) .map_err(|err| err.fill_position(pos))?; @@ -1087,7 +1080,7 @@ impl Engine { .collect::>()?; // 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.inc_operations(state) diff --git a/src/module/mod.rs b/src/module/mod.rs index 1fde1d37..510c0884 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1640,7 +1640,7 @@ impl Module { // Create new module 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 if aliases.len() > 1 { aliases.into_iter().for_each(|alias| { diff --git a/src/parser.rs b/src/parser.rs index 6be6ca03..956e197b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,12 +3,11 @@ use crate::ast::{ 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::module::NamespaceRef; use crate::optimize::optimize_into_ast; use crate::optimize::OptimizationLevel; -use crate::scope::EntryType as ScopeEntryType; use crate::stdlib::{ borrow::Cow, boxed::Box, @@ -49,7 +48,7 @@ struct ParseState<'e> { /// Interned strings. strings: HashMap, /// 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. entry_stack_len: usize, /// 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(); 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)) } // Constant values cannot be assigned to - ScopeEntryType::Constant => { + AccessType::Constant => { Err(PERR::AssignmentToConstant(name.to_string()).into_err(*name_pos)) } } @@ -1318,11 +1317,11 @@ fn make_assignment_stmt<'a>( }, ) = x.as_ref(); 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)) } // Constant values cannot be assigned to - ScopeEntryType::Constant => { + AccessType::Constant => { 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. state.stack.resize( 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(), @@ -2110,7 +2109,7 @@ fn parse_for( let loop_var = state.get_interned_string(name.clone()); 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; let body = parse_block(input, state, lib, settings.level_up())?; @@ -2125,7 +2124,7 @@ fn parse_let( input: &mut TokenStream, state: &mut ParseState, lib: &mut FunctionsLib, - var_type: ScopeEntryType, + var_type: AccessType, export: bool, mut settings: ParseSettings, ) -> Result { @@ -2156,16 +2155,16 @@ fn parse_let( match var_type { // let name = expr - ScopeEntryType::Normal => { + AccessType::Normal => { 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); Ok(Stmt::Let(Box::new(var_def), init_expr, export, token_pos)) } // const name = { expr:constant } - ScopeEntryType::Constant => { + AccessType::Constant => { 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); Ok(Stmt::Const(Box::new(var_def), init_expr, export, token_pos)) } @@ -2232,13 +2231,13 @@ fn parse_export( match input.peek().unwrap() { (Token::Let, 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); return Ok(stmt); } (Token::Const, 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); return Ok(stmt); } @@ -2393,7 +2392,7 @@ fn parse_stmt( lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result, ParseError> { - use ScopeEntryType::{Constant, Normal}; + use AccessType::{Constant, Normal}; let (token, token_pos) = match input.peek().unwrap() { (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)); } 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)) } (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)); } 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)) } (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 { match value.0 { #[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::Int(value) => Some(Expr::IntegerConstant(value, pos)), - Union::Char(value) => Some(Expr::CharConstant(value, pos)), - Union::Str(value) => Some(Expr::StringConstant(value, pos)), - Union::Bool(value) => Some(Expr::BoolConstant(value, pos)), + Union::Unit(_, _) => Some(Expr::Unit(pos)), + Union::Int(value, _) => Some(Expr::IntegerConstant(value, pos)), + Union::Char(value, _) => Some(Expr::CharConstant(value, pos)), + Union::Str(value, _) => Some(Expr::StringConstant(value, pos)), + Union::Bool(value, _) => Some(Expr::BoolConstant(value, pos)), #[cfg(not(feature = "no_index"))] - Union::Array(array) => { + Union::Array(array, _) => { let items: Vec<_> = array .into_iter() .map(|x| map_dynamic_to_expr(x, pos)) @@ -3029,7 +3028,7 @@ pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option { } } #[cfg(not(feature = "no_object"))] - Union::Map(map) => { + Union::Map(map, _) => { let items: Vec<_> = map .into_iter() .map(|(k, v)| (IdentX::new(k, pos), map_dynamic_to_expr(v, pos))) diff --git a/src/scope.rs b/src/scope.rs index 700a549f..7cef2ca8 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,29 +1,9 @@ //! 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::{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. /// Useful for keeping state between [`Engine`][crate::Engine] evaluation runs. /// @@ -58,19 +38,17 @@ impl EntryType { // // # 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, // with [`Cow`][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. // // 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. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct Scope<'a> { /// Current value of the entry. values: Vec, - /// Type of the entry. - types: Vec, /// (Name, aliases) of the entry. The list of aliases is Boxed because it occurs rarely. names: Vec<(Cow<'a, str>, Box>)>, } @@ -79,7 +57,6 @@ impl Default for Scope<'_> { fn default() -> Self { Self { values: Vec::with_capacity(16), - types: Vec::with_capacity(16), names: Vec::with_capacity(16), } } @@ -124,7 +101,6 @@ impl<'a> Scope<'a> { #[inline(always)] pub fn clear(&mut self) -> &mut Self { self.names.clear(); - self.types.clear(); self.values.clear(); self } @@ -180,7 +156,7 @@ impl<'a> Scope<'a> { name: impl Into>, value: impl Variant + Clone, ) -> &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`]. /// @@ -196,7 +172,7 @@ impl<'a> Scope<'a> { /// ``` #[inline(always)] pub fn push_dynamic(&mut self, name: impl Into>, 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`]. /// @@ -219,7 +195,7 @@ impl<'a> Scope<'a> { name: impl Into>, value: impl Variant + Clone, ) -> &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. /// @@ -242,18 +218,18 @@ impl<'a> Scope<'a> { name: impl Into>, value: Dynamic, ) -> &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`]. #[inline] pub(crate) fn push_dynamic_value( &mut self, name: impl Into>, - entry_type: EntryType, - value: Dynamic, + access: AccessType, + mut value: Dynamic, ) -> &mut Self { 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 } @@ -286,7 +262,6 @@ impl<'a> Scope<'a> { #[inline(always)] pub fn rewind(&mut self, size: usize) -> &mut Self { self.names.truncate(size); - self.types.truncate(size); self.values.truncate(size); self } @@ -312,14 +287,14 @@ impl<'a> Scope<'a> { } /// Find an entry in the [`Scope`], starting from the last. #[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 .iter() .enumerate() .rev() // Always search a Scope in reverse order .find_map(|(index, (key, _))| { if name == key.as_ref() { - Some((index, self.types[index])) + Some((index, self.values[index].access_type())) } else { None } @@ -374,8 +349,8 @@ impl<'a> Scope<'a> { None => { self.push(name, value); } - Some((_, EntryType::Constant)) => panic!("variable {} is constant", name), - Some((index, EntryType::Normal)) => { + Some((_, AccessType::Constant)) => panic!("variable {} is constant", name), + Some((index, AccessType::Normal)) => { *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`]. #[inline(always)] - pub(crate) fn get_mut(&mut self, index: usize) -> (&mut Dynamic, EntryType) { - ( - self.values.get_mut(index).expect("invalid index in Scope"), - self.types[index], - ) + pub(crate) fn get_mut(&mut self, index: usize) -> &mut Dynamic { + self.values.get_mut(index).expect("invalid index in Scope") } /// Update the access type of an entry in the [`Scope`]. #[cfg(not(feature = "no_module"))] @@ -412,7 +384,6 @@ impl<'a> Scope<'a> { .for_each(|(index, (name, alias))| { if !entries.names.iter().any(|(key, _)| key == name) { entries.names.push((name.clone(), alias.clone())); - entries.types.push(self.types[index]); entries.values.push(self.values[index].clone()); } }); @@ -422,13 +393,11 @@ impl<'a> Scope<'a> { /// Get an iterator to entries in the [`Scope`]. #[inline(always)] #[allow(dead_code)] - pub(crate) fn into_iter( - self, - ) -> impl Iterator, EntryType, Dynamic, Vec)> { + pub(crate) fn into_iter(self) -> impl Iterator, Dynamic, Vec)> { self.names .into_iter() - .zip(self.types.into_iter().zip(self.values.into_iter())) - .map(|((name, alias), (typ, value))| (name, typ, value, alias.to_vec())) + .zip(self.values.into_iter()) + .map(|((name, alias), value)| (name, value, alias.to_vec())) } /// Get an iterator to entries in the [`Scope`]. /// Shared values are flatten-cloned. @@ -466,17 +435,16 @@ impl<'a> Scope<'a> { pub fn iter_raw<'x: 'a>(&'x self) -> impl Iterator + 'x { self.names .iter() - .zip(self.types.iter().zip(self.values.iter())) - .map(|((name, _), (typ, value))| (name.as_ref(), typ.is_constant(), value)) + .zip(self.values.iter()) + .map(|((name, _), value)| (name.as_ref(), value.is_constant(), value)) } } -impl<'a, K: Into>> iter::Extend<(K, EntryType, Dynamic)> for Scope<'a> { +impl<'a, K: Into>> iter::Extend<(K, Dynamic)> for Scope<'a> { #[inline(always)] - fn extend>(&mut self, iter: T) { - iter.into_iter().for_each(|(name, typ, value)| { + fn extend>(&mut self, iter: T) { + iter.into_iter().for_each(|(name, value)| { self.names.push((name.into(), Box::new(Default::default()))); - self.types.push(typ); self.values.push(value); }); } diff --git a/tests/arrays.rs b/tests/arrays.rs index 65811a23..3c1dbff2 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -178,7 +178,10 @@ fn test_arrays_map_reduce() -> Result<(), Box> { engine.eval::( r#" 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