Merge pull request #415 from schungx/master

Minor updates.
This commit is contained in:
Stephen Chung 2021-05-25 17:12:29 +08:00 committed by GitHub
commit 9a54fd6fae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 1241 additions and 1059 deletions

View File

@ -4,10 +4,16 @@ Rhai Release Notes
Version 0.20.2 Version 0.20.2
============== ==============
This version adds a number of convenience features:
* Ability for a `Dynamic` to hold an `i32` _tag_ of arbitrary data
* Simplifies dynamic properties access by falling back to an indexer (passing the name of the property as a string) when a property is not found.
Bug fixes Bug fixes
--------- ---------
* Constant propagation during optimization for constants held in a custom scope now works properly instead of always replacing by `()`. * Propagation of constants held in a custom scope now works properly instead of always replacing by `()`.
Breaking changes Breaking changes
---------------- ----------------
@ -15,14 +21,17 @@ Breaking changes
* `Engine::disable_doc_comments` is removed because doc-comments are now placed under the `metadata` feature flag. * `Engine::disable_doc_comments` is removed because doc-comments are now placed under the `metadata` feature flag.
* Registering a custom syntax now only requires specifying whether the `Scope` is adjusted (i.e. whether variables are added or removed). There is no need to specify the number of variables added/removed. * Registering a custom syntax now only requires specifying whether the `Scope` is adjusted (i.e. whether variables are added or removed). There is no need to specify the number of variables added/removed.
* Assigning to a property of a constant is now allowed and no longer raise an `EvalAltResult::ErrorAssignmentToConstant` error. This is to facilitate the Singleton pattern. Registered setter functions are automatically guarded against setters calling on constants and will continue to raise errors unless the `pure` attribute is present (for plugins). * Assigning to a property of a constant is now allowed and no longer raise an `EvalAltResult::ErrorAssignmentToConstant` error. This is to facilitate the Singleton pattern. Registered setter functions are automatically guarded against setters calling on constants and will continue to raise errors unless the `pure` attribute is present (for plugins).
* If a property getter/setter is not found, an indexer with string index, if any, is tried.
* The indexers API (`Engine::register_indexer_XXX` and `Module::set_indexer_XXX`) are now also exposed under `no_index`.
New features New features
------------ ------------
* Each `Dynamic` value can now contain arbitrary data (type `i16`) in the form of a _tag_. This is to use up otherwise wasted space in the `Dynamic` type. * Each `Dynamic` value can now contain arbitrary data (type `i32`) in the form of a _tag_. This is to use up otherwise wasted space in the `Dynamic` type.
* A new internal feature `no_smartstring` to turn off `SmartString` for those rare cases that it is needed. * A new internal feature `no_smartstring` to turn off `SmartString` for those rare cases that it is needed.
* `DynamicReadLock` and `DynamicWriteLoc` are exposed under `internals`. * `DynamicReadLock` and `DynamicWriteLoc` are exposed under `internals`.
* `From<Shared<Locked<Dynamic>>>` is added for `Dynamic` mapping directly to a shared value, together with support for `Dynamic::from`. * `From<Shared<Locked<Dynamic>>>` is added for `Dynamic` mapping directly to a shared value, together with support for `Dynamic::from`.
* An indexer with string index acts as a _fallback_ to a property getter/setter.
Enhancements Enhancements
------------ ------------

View File

@ -92,7 +92,7 @@ default_features = false
optional = true optional = true
[dependencies.rust_decimal] [dependencies.rust_decimal]
version = "1.13" version = "1.14"
default_features = false default_features = false
features = ["maths"] features = ["maths"]
optional = true optional = true

View File

@ -13,7 +13,6 @@ use std::{
collections::BTreeMap, collections::BTreeMap,
fmt, fmt,
hash::Hash, hash::Hash,
iter::empty,
mem, mem,
num::{NonZeroU8, NonZeroUsize}, num::{NonZeroU8, NonZeroUsize},
ops::{Add, AddAssign, Deref, DerefMut}, ops::{Add, AddAssign, Deref, DerefMut},
@ -254,12 +253,11 @@ impl AST {
/// Set the source. /// Set the source.
#[inline(always)] #[inline(always)]
pub fn set_source(&mut self, source: impl Into<Identifier>) -> &mut Self { pub fn set_source(&mut self, source: impl Into<Identifier>) -> &mut Self {
self.source = Some(source.into()); let source = Some(source.into());
Shared::get_mut(&mut self.functions)
if let Some(module) = Shared::get_mut(&mut self.functions) { .as_mut()
module.set_id(self.source.clone()); .map(|m| m.set_id(source.clone()));
} self.source = source;
self self
} }
/// Clear the source. /// Clear the source.
@ -910,6 +908,7 @@ impl DerefMut for StmtBlock {
impl fmt::Debug for StmtBlock { impl fmt::Debug for StmtBlock {
#[inline(always)] #[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Block")?;
fmt::Debug::fmt(&self.0, f)?; fmt::Debug::fmt(&self.0, f)?;
self.1.debug_print(f) self.1.debug_print(f)
} }
@ -1226,6 +1225,7 @@ impl Stmt {
path: &mut Vec<ASTNode<'a>>, path: &mut Vec<ASTNode<'a>>,
on_node: &mut impl FnMut(&[ASTNode]) -> bool, on_node: &mut impl FnMut(&[ASTNode]) -> bool,
) -> bool { ) -> bool {
// Push the current node onto the path
path.push(self.into()); path.push(self.into());
if !on_node(path) { if !on_node(path) {
@ -1341,7 +1341,8 @@ impl Stmt {
_ => (), _ => (),
} }
path.pop().unwrap(); path.pop()
.expect("never fails because `path` always contains the current node");
true true
} }
@ -1399,13 +1400,13 @@ impl OpAssignment {
pub fn new(op: Token) -> Self { pub fn new(op: Token) -> Self {
let op_raw = op let op_raw = op
.map_op_assignment() .map_op_assignment()
.expect("token must be an op-assignment operator") .expect("never fails because token must be an op-assignment operator")
.keyword_syntax(); .keyword_syntax();
let op_assignment = op.keyword_syntax(); let op_assignment = op.keyword_syntax();
Self { Self {
hash_op_assign: calc_fn_hash(empty(), op_assignment, 2), hash_op_assign: calc_fn_hash(op_assignment, 2),
hash_op: calc_fn_hash(empty(), op_raw, 2), hash_op: calc_fn_hash(op_raw, 2),
op: op_assignment, op: op_assignment,
} }
} }
@ -1493,13 +1494,9 @@ impl FnCallHashes {
self.script.is_none() self.script.is_none()
} }
/// Get the script function hash from this [`FnCallHashes`]. /// Get the script function hash from this [`FnCallHashes`].
///
/// # Panics
///
/// Panics if the [`FnCallHashes`] is native Rust only.
#[inline(always)] #[inline(always)]
pub fn script_hash(&self) -> u64 { pub fn script_hash(&self) -> Option<u64> {
self.script.unwrap() self.script
} }
/// Get the naive Rust function hash from this [`FnCallHashes`]. /// Get the naive Rust function hash from this [`FnCallHashes`].
#[inline(always)] #[inline(always)]
@ -1715,7 +1712,13 @@ pub enum Expr {
)>, )>,
), ),
/// Property access - ((getter, hash), (setter, hash), prop) /// Property access - ((getter, hash), (setter, hash), prop)
Property(Box<((Identifier, u64), (Identifier, u64), Ident)>), Property(
Box<(
(Identifier, u64),
(Identifier, u64),
(ImmutableString, Position),
)>,
),
/// { [statement][Stmt] ... } /// { [statement][Stmt] ... }
Stmt(Box<StmtBlock>), Stmt(Box<StmtBlock>),
/// func `(` expr `,` ... `)` /// func `(` expr `,` ... `)`
@ -1780,16 +1783,14 @@ impl fmt::Debug for Expr {
} }
f.write_str(")") f.write_str(")")
} }
Self::Property(x) => write!(f, "Property({})", x.2.name), Self::Property(x) => write!(f, "Property({})", (x.2).0),
Self::Stmt(x) => { Self::Stmt(x) => {
f.write_str("Stmt")?; f.write_str("ExprStmtBlock")?;
f.debug_list().entries(x.0.iter()).finish() f.debug_list().entries(x.0.iter()).finish()
} }
Self::FnCall(x, _) => { Self::FnCall(x, _) => {
let mut ff = f.debug_struct("FnCall"); let mut ff = f.debug_struct("FnCall");
if let Some(ref ns) = x.namespace { x.namespace.as_ref().map(|ns| ff.field("namespace", ns));
ff.field("namespace", ns);
}
ff.field("name", &x.name) ff.field("name", &x.name)
.field("hash", &x.hashes) .field("hash", &x.hashes)
.field("args", &x.args); .field("args", &x.args);
@ -1843,7 +1844,10 @@ impl Expr {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::Array(x, _) if self.is_constant() => { Self::Array(x, _) if self.is_constant() => {
let mut arr = Array::with_capacity(x.len()); let mut arr = Array::with_capacity(x.len());
arr.extend(x.iter().map(|v| v.get_constant_value().unwrap())); arr.extend(x.iter().map(|v| {
v.get_constant_value()
.expect("never fails because a constant array always has a constant value")
}));
Dynamic::from_array(arr) Dynamic::from_array(arr)
} }
@ -1851,7 +1855,10 @@ impl Expr {
Self::Map(x, _) if self.is_constant() => { Self::Map(x, _) if self.is_constant() => {
let mut map = x.1.clone(); let mut map = x.1.clone();
x.0.iter().for_each(|(k, v)| { x.0.iter().for_each(|(k, v)| {
*map.get_mut(k.name.as_str()).unwrap() = v.get_constant_value().unwrap() *map.get_mut(k.name.as_str())
.expect("never fails because the template should contain all the keys") = v
.get_constant_value()
.expect("never fails because a constant map always has a constant value")
}); });
Dynamic::from_map(map) Dynamic::from_map(map)
} }
@ -1894,9 +1901,14 @@ impl Expr {
| Self::FnCall(_, pos) | Self::FnCall(_, pos)
| Self::Custom(_, pos) => *pos, | Self::Custom(_, pos) => *pos,
Self::InterpolatedString(x) => x.first().unwrap().position(), Self::InterpolatedString(x) => x
.first()
.expect(
"never fails because an interpolated string always contains at least one item",
)
.position(),
Self::Property(x) => (x.2).pos, Self::Property(x) => (x.2).1,
Self::Stmt(x) => x.1, Self::Stmt(x) => x.1,
Self::And(x, _) | Self::Or(x, _) | Self::Dot(x, _) | Self::Index(x, _) => { Self::And(x, _) | Self::Or(x, _) | Self::Dot(x, _) | Self::Index(x, _) => {
@ -1928,10 +1940,12 @@ impl Expr {
| Self::Custom(_, pos) => *pos = new_pos, | Self::Custom(_, pos) => *pos = new_pos,
Self::InterpolatedString(x) => { Self::InterpolatedString(x) => {
x.first_mut().unwrap().set_position(new_pos); x.first_mut()
.expect("never fails because an interpolated string always contains at least one item")
.set_position(new_pos);
} }
Self::Property(x) => (x.2).pos = new_pos, Self::Property(x) => (x.2).1 = new_pos,
Self::Stmt(x) => x.1 = new_pos, Self::Stmt(x) => x.1 = new_pos,
} }
@ -2045,6 +2059,7 @@ impl Expr {
path: &mut Vec<ASTNode<'a>>, path: &mut Vec<ASTNode<'a>>,
on_node: &mut impl FnMut(&[ASTNode]) -> bool, on_node: &mut impl FnMut(&[ASTNode]) -> bool,
) -> bool { ) -> bool {
// Push the current node onto the path
path.push(self.into()); path.push(self.into());
if !on_node(path) { if !on_node(path) {
@ -2098,7 +2113,8 @@ impl Expr {
_ => (), _ => (),
} }
path.pop().unwrap(); path.pop()
.expect("never fails because `path` always contains the current node");
true true
} }
@ -2128,7 +2144,7 @@ mod tests {
96 96
} }
); );
assert_eq!(size_of::<Scope>(), 288); assert_eq!(size_of::<Scope>(), 160);
assert_eq!(size_of::<LexError>(), 56); assert_eq!(size_of::<LexError>(), 56);
assert_eq!( assert_eq!(
size_of::<ParseError>(), size_of::<ParseError>(),

View File

@ -141,6 +141,11 @@ pub enum AccessMode {
} }
/// Arbitrary data attached to a [`Dynamic`] value. /// Arbitrary data attached to a [`Dynamic`] value.
#[cfg(target_pointer_width = "64")]
pub type Tag = i32;
/// Arbitrary data attached to a [`Dynamic`] value.
#[cfg(target_pointer_width = "32")]
pub type Tag = i16; pub type Tag = i16;
/// Default tag value for [`Dynamic`]. /// Default tag value for [`Dynamic`].
@ -235,9 +240,10 @@ impl<'d, T: Any + Clone> Deref for DynamicReadLock<'d, T> {
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
match &self.0 { match &self.0 {
DynamicReadLockInner::Reference(reference) => *reference, DynamicReadLockInner::Reference(reference) => *reference,
// Unwrapping is safe because all checking is already done in its constructor
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
DynamicReadLockInner::Guard(guard) => guard.downcast_ref().unwrap(), DynamicReadLockInner::Guard(guard) => guard.downcast_ref().expect(
"never fails because the read guard was created after checking the data type",
),
} }
} }
} }
@ -277,9 +283,10 @@ impl<'d, T: Any + Clone> Deref for DynamicWriteLock<'d, T> {
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
match &self.0 { match &self.0 {
DynamicWriteLockInner::Reference(reference) => *reference, DynamicWriteLockInner::Reference(reference) => *reference,
// Unwrapping is safe because all checking is already done in its constructor
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
DynamicWriteLockInner::Guard(guard) => guard.downcast_ref().unwrap(), DynamicWriteLockInner::Guard(guard) => guard.downcast_ref().expect(
"never fails because the write guard was created after checking the data type",
),
} }
} }
} }
@ -289,16 +296,17 @@ impl<'d, T: Any + Clone> DerefMut for DynamicWriteLock<'d, T> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
match &mut self.0 { match &mut self.0 {
DynamicWriteLockInner::Reference(reference) => *reference, DynamicWriteLockInner::Reference(reference) => *reference,
// Unwrapping is safe because all checking is already done in its constructor
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
DynamicWriteLockInner::Guard(guard) => guard.downcast_mut().unwrap(), DynamicWriteLockInner::Guard(guard) => guard.downcast_mut().expect(
"never fails because the write guard was created after checking the data type",
),
} }
} }
} }
impl Dynamic { impl Dynamic {
/// Get the arbitrary data attached to this [`Dynamic`]. /// Get the arbitrary data attached to this [`Dynamic`].
pub fn tag(&self) -> Tag { pub const fn tag(&self) -> Tag {
match self.0 { match self.0 {
Union::Unit(_, tag, _) Union::Unit(_, tag, _)
| Union::Bool(_, tag, _) | Union::Bool(_, tag, _)
@ -350,7 +358,7 @@ impl Dynamic {
/// Does this [`Dynamic`] hold a variant data type /// Does this [`Dynamic`] hold a variant data type
/// instead of one of the supported system primitive types? /// instead of one of the supported system primitive types?
#[inline(always)] #[inline(always)]
pub fn is_variant(&self) -> bool { pub const fn is_variant(&self) -> bool {
match self.0 { match self.0 {
Union::Variant(_, _, _) => true, Union::Variant(_, _, _) => true,
_ => false, _ => false,
@ -361,7 +369,7 @@ impl Dynamic {
/// Not available under `no_closure`. /// Not available under `no_closure`.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[inline(always)] #[inline(always)]
pub fn is_shared(&self) -> bool { pub const fn is_shared(&self) -> bool {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
match self.0 { match self.0 {
Union::Shared(_, _, _) => return true, Union::Shared(_, _, _) => return true,
@ -409,7 +417,7 @@ impl Dynamic {
#[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.as_ref().as_ref().type_id(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(cell, _, _) => { Union::Shared(cell, _, _) => {
@ -447,7 +455,7 @@ impl Dynamic {
#[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.as_ref().as_ref().type_name(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
@ -473,17 +481,12 @@ impl Hash for Dynamic {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(f, _, _) => f.hash(state), Union::Float(f, _, _) => f.hash(state),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(a, _, _) => (**a).hash(state), Union::Array(a, _, _) => a.as_ref().hash(state),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(m, _, _) => { Union::Map(m, _, _) => m.iter().for_each(|(key, value)| {
let mut buf: crate::StaticVec<_> = m.iter().collect();
buf.sort_by(|(a, _), (b, _)| a.cmp(b));
buf.into_iter().for_each(|(key, value)| {
key.hash(state); key.hash(state);
value.hash(state); value.hash(state);
}) }),
}
Union::FnPtr(f, _, _) if f.is_curried() => { Union::FnPtr(f, _, _) if f.is_curried() => {
unimplemented!( unimplemented!(
"{} with curried arguments cannot be hashed", "{} with curried arguments cannot be hashed",
@ -566,43 +569,47 @@ impl fmt::Display for Dynamic {
Union::TimeStamp(_, _, _) => f.write_str("<timestamp>"), Union::TimeStamp(_, _, _) => f.write_str("<timestamp>"),
Union::Variant(value, _, _) => { Union::Variant(value, _, _) => {
let _type_id = (***value).type_id(); let value = value.as_ref().as_ref();
let _type_id = value.type_id();
let _value_any = value.as_any();
const CHECKED: &str = "never fails because the type was checked";
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
if _type_id == TypeId::of::<u8>() { if _type_id == TypeId::of::<u8>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<u8>().unwrap()); return fmt::Display::fmt(_value_any.downcast_ref::<u8>().expect(CHECKED), f);
} else if _type_id == TypeId::of::<u16>() { } else if _type_id == TypeId::of::<u16>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<u16>().unwrap()); return fmt::Display::fmt(_value_any.downcast_ref::<u16>().expect(CHECKED), f);
} else if _type_id == TypeId::of::<u32>() { } else if _type_id == TypeId::of::<u32>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<u32>().unwrap()); return fmt::Display::fmt(_value_any.downcast_ref::<u32>().expect(CHECKED), f);
} else if _type_id == TypeId::of::<u64>() { } else if _type_id == TypeId::of::<u64>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<u64>().unwrap()); return fmt::Display::fmt(_value_any.downcast_ref::<u64>().expect(CHECKED), f);
} else if _type_id == TypeId::of::<i8>() { } else if _type_id == TypeId::of::<i8>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<i8>().unwrap()); return fmt::Display::fmt(_value_any.downcast_ref::<i8>().expect(CHECKED), f);
} else if _type_id == TypeId::of::<i16>() { } else if _type_id == TypeId::of::<i16>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<i16>().unwrap()); return fmt::Display::fmt(_value_any.downcast_ref::<i16>().expect(CHECKED), f);
} else if _type_id == TypeId::of::<i32>() { } else if _type_id == TypeId::of::<i32>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<i32>().unwrap()); return fmt::Display::fmt(_value_any.downcast_ref::<i32>().expect(CHECKED), f);
} else if _type_id == TypeId::of::<i64>() { } else if _type_id == TypeId::of::<i64>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<i64>().unwrap()); return fmt::Display::fmt(_value_any.downcast_ref::<i64>().expect(CHECKED), f);
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
if _type_id == TypeId::of::<f32>() { if _type_id == TypeId::of::<f32>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<f32>().unwrap()); return fmt::Display::fmt(_value_any.downcast_ref::<f32>().expect(CHECKED), f);
} else if _type_id == TypeId::of::<f64>() { } else if _type_id == TypeId::of::<f64>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<f64>().unwrap()); return fmt::Display::fmt(_value_any.downcast_ref::<f64>().expect(CHECKED), f);
} }
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
if _type_id == TypeId::of::<u128>() { if _type_id == TypeId::of::<u128>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<u128>().unwrap()); return fmt::Display::fmt(_value_any.downcast_ref::<u128>().expect(CHECKED), f);
} else if _type_id == TypeId::of::<i128>() { } else if _type_id == TypeId::of::<i128>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<i128>().unwrap()); return fmt::Display::fmt(_value_any.downcast_ref::<i128>().expect(CHECKED), f);
} }
f.write_str((***value).type_name()) f.write_str(value.type_name())
} }
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
@ -645,51 +652,47 @@ impl fmt::Debug for Dynamic {
Union::TimeStamp(_, _, _) => write!(f, "<timestamp>"), Union::TimeStamp(_, _, _) => write!(f, "<timestamp>"),
Union::Variant(value, _, _) => { Union::Variant(value, _, _) => {
let _type_id = (***value).type_id(); let value = value.as_ref().as_ref();
let _type_id = value.type_id();
let _value_any = value.as_any();
const CHECKED: &str = "never fails because the type was checked";
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
if _type_id == TypeId::of::<u8>() { if _type_id == TypeId::of::<u8>() {
return write!(f, "{:?}", (**value).as_any().downcast_ref::<u8>().unwrap()); return fmt::Debug::fmt(_value_any.downcast_ref::<u8>().expect(CHECKED), f);
} else if _type_id == TypeId::of::<u16>() { } else if _type_id == TypeId::of::<u16>() {
return write!(f, "{:?}", (**value).as_any().downcast_ref::<u16>().unwrap()); return fmt::Debug::fmt(_value_any.downcast_ref::<u16>().expect(CHECKED), f);
} else if _type_id == TypeId::of::<u32>() { } else if _type_id == TypeId::of::<u32>() {
return write!(f, "{:?}", (**value).as_any().downcast_ref::<u32>().unwrap()); return fmt::Debug::fmt(_value_any.downcast_ref::<u32>().expect(CHECKED), f);
} else if _type_id == TypeId::of::<u64>() { } else if _type_id == TypeId::of::<u64>() {
return write!(f, "{:?}", (**value).as_any().downcast_ref::<u64>().unwrap()); return fmt::Debug::fmt(_value_any.downcast_ref::<u64>().expect(CHECKED), f);
} else if _type_id == TypeId::of::<i8>() { } else if _type_id == TypeId::of::<i8>() {
return write!(f, "{:?}", (**value).as_any().downcast_ref::<i8>().unwrap()); return fmt::Debug::fmt(_value_any.downcast_ref::<i8>().expect(CHECKED), f);
} else if _type_id == TypeId::of::<i16>() { } else if _type_id == TypeId::of::<i16>() {
return write!(f, "{:?}", (**value).as_any().downcast_ref::<i16>().unwrap()); return fmt::Debug::fmt(_value_any.downcast_ref::<i16>().expect(CHECKED), f);
} else if _type_id == TypeId::of::<i32>() { } else if _type_id == TypeId::of::<i32>() {
return write!(f, "{:?}", (**value).as_any().downcast_ref::<i32>().unwrap()); return fmt::Debug::fmt(_value_any.downcast_ref::<i32>().expect(CHECKED), f);
} else if _type_id == TypeId::of::<i64>() { } else if _type_id == TypeId::of::<i64>() {
return write!(f, "{:?}", (**value).as_any().downcast_ref::<i64>().unwrap()); return fmt::Debug::fmt(_value_any.downcast_ref::<i64>().expect(CHECKED), f);
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
if _type_id == TypeId::of::<f32>() { if _type_id == TypeId::of::<f32>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<f32>().unwrap()); return fmt::Debug::fmt(_value_any.downcast_ref::<f32>().expect(CHECKED), f);
} else if _type_id == TypeId::of::<f64>() { } else if _type_id == TypeId::of::<f64>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<f64>().unwrap()); return fmt::Debug::fmt(_value_any.downcast_ref::<f64>().expect(CHECKED), f);
} }
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
if _type_id == TypeId::of::<u128>() { if _type_id == TypeId::of::<u128>() {
return write!( return fmt::Debug::fmt(_value_any.downcast_ref::<u128>().expect(CHECKED), f);
f,
"{:?}",
(**value).as_any().downcast_ref::<u128>().unwrap()
);
} else if _type_id == TypeId::of::<i128>() { } else if _type_id == TypeId::of::<i128>() {
return write!( return fmt::Debug::fmt(_value_any.downcast_ref::<i128>().expect(CHECKED), f);
f,
"{:?}",
(**value).as_any().downcast_ref::<i128>().unwrap()
);
} }
write!(f, "{}", (*value).type_name()) write!(f, "{}", value.type_name())
} }
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
@ -746,7 +749,7 @@ impl Clone for Dynamic {
} }
Union::Variant(ref value, tag, _) => { Union::Variant(ref value, tag, _) => {
let mut x = (***value).clone_into_dynamic(); let mut x = value.as_ref().as_ref().clone_into_dynamic();
x.set_tag(tag); x.set_tag(tag);
x x
} }
@ -830,7 +833,7 @@ impl Dynamic {
)); ));
/// Get the [`AccessMode`] for this [`Dynamic`]. /// Get the [`AccessMode`] for this [`Dynamic`].
pub(crate) fn access_mode(&self) -> AccessMode { pub(crate) const fn access_mode(&self) -> AccessMode {
match self.0 { match self.0 {
Union::Unit(_, _, access) Union::Unit(_, _, access)
| Union::Bool(_, _, access) | Union::Bool(_, _, access)
@ -980,53 +983,40 @@ impl Dynamic {
pub fn from<T: Variant + Clone>(mut value: T) -> Self { pub fn from<T: Variant + Clone>(mut value: T) -> Self {
// Coded this way in order to maximally leverage potentials for dead-code removal. // Coded this way in order to maximally leverage potentials for dead-code removal.
const CHECKED: &str = "never fails because the type was checked";
if TypeId::of::<T>() == TypeId::of::<Dynamic>() { if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
return unsafe_try_cast::<_, Dynamic>(value).ok().unwrap(); return unsafe_try_cast::<_, Dynamic>(value).ok().expect(CHECKED);
} }
let val = value.as_any();
if TypeId::of::<T>() == TypeId::of::<INT>() { if TypeId::of::<T>() == TypeId::of::<INT>() {
return <dyn Any>::downcast_ref::<INT>(&value) return val.downcast_ref::<INT>().expect(CHECKED).clone().into();
.unwrap()
.clone()
.into();
} }
#[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 <dyn Any>::downcast_ref::<FLOAT>(&value) return val.downcast_ref::<FLOAT>().expect(CHECKED).clone().into();
.unwrap()
.clone()
.into();
} }
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
if TypeId::of::<T>() == TypeId::of::<Decimal>() { if TypeId::of::<T>() == TypeId::of::<Decimal>() {
return <dyn Any>::downcast_ref::<Decimal>(&value) return val.downcast_ref::<Decimal>().expect(CHECKED).clone().into();
.unwrap()
.clone()
.into();
} }
if TypeId::of::<T>() == TypeId::of::<bool>() { if TypeId::of::<T>() == TypeId::of::<bool>() {
return <dyn Any>::downcast_ref::<bool>(&value) return val.downcast_ref::<bool>().expect(CHECKED).clone().into();
.unwrap()
.clone()
.into();
} }
if TypeId::of::<T>() == TypeId::of::<char>() { if TypeId::of::<T>() == TypeId::of::<char>() {
return <dyn Any>::downcast_ref::<char>(&value) return val.downcast_ref::<char>().expect(CHECKED).clone().into();
.unwrap()
.clone()
.into();
} }
if TypeId::of::<T>() == TypeId::of::<ImmutableString>() { if TypeId::of::<T>() == TypeId::of::<ImmutableString>() {
return <dyn Any>::downcast_ref::<ImmutableString>(&value) return val
.unwrap() .downcast_ref::<ImmutableString>()
.expect(CHECKED)
.clone() .clone()
.into(); .into();
} }
if TypeId::of::<T>() == TypeId::of::<&str>() { if TypeId::of::<T>() == TypeId::of::<&str>() {
return <dyn Any>::downcast_ref::<&str>(&value) return val.downcast_ref::<&str>().expect(CHECKED).deref().into();
.unwrap()
.deref()
.into();
} }
if TypeId::of::<T>() == TypeId::of::<()>() { if TypeId::of::<T>() == TypeId::of::<()>() {
return ().into(); return ().into();
@ -1450,9 +1440,9 @@ impl Dynamic {
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
let value = cell.read().unwrap(); let value = cell.read().unwrap();
let type_id = (*value).type_id(); if (*value).type_id() != TypeId::of::<T>()
&& TypeId::of::<Dynamic>() != TypeId::of::<T>()
if type_id != TypeId::of::<T>() && TypeId::of::<Dynamic>() != TypeId::of::<T>() { {
return None; return None;
} else { } else {
return Some(DynamicReadLock(DynamicReadLockInner::Guard(value))); return Some(DynamicReadLock(DynamicReadLockInner::Guard(value)));
@ -1483,9 +1473,9 @@ impl Dynamic {
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
let value = cell.write().unwrap(); let value = cell.write().unwrap();
let type_id = (*value).type_id(); if (*value).type_id() != TypeId::of::<T>()
&& TypeId::of::<Dynamic>() != TypeId::of::<T>()
if type_id != TypeId::of::<T>() && TypeId::of::<Dynamic>() != TypeId::of::<T>() { {
return None; return None;
} else { } else {
return Some(DynamicWriteLock(DynamicWriteLockInner::Guard(value))); return Some(DynamicWriteLock(DynamicWriteLockInner::Guard(value)));
@ -1507,77 +1497,77 @@ 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, _, _) => value.as_any().downcast_ref::<T>(),
_ => 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.as_ref()), Union::Float(value, _, _) => value.as_ref().as_any().downcast_ref::<T>(),
_ => None, _ => None,
}; };
} }
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
if TypeId::of::<T>() == TypeId::of::<Decimal>() { if TypeId::of::<T>() == TypeId::of::<Decimal>() {
return match &self.0 { return match &self.0 {
Union::Decimal(value, _, _) => <dyn Any>::downcast_ref::<T>(value.as_ref()), Union::Decimal(value, _, _) => value.as_ref().as_any().downcast_ref::<T>(),
_ => 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, _, _) => value.as_any().downcast_ref::<T>(),
_ => 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, _, _) => value.as_any().downcast_ref::<T>(),
_ => 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, _, _) => value.as_any().downcast_ref::<T>(),
_ => 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, _, _) => value.as_ref().as_any().downcast_ref::<T>(),
_ => 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, _, _) => value.as_ref().as_any().downcast_ref::<T>(),
_ => 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, _, _) => value.as_ref().as_any().downcast_ref::<T>(),
_ => 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, _, _) => value.as_ref().as_any().downcast_ref::<T>(),
_ => 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, _, _) => value.as_any().downcast_ref::<T>(),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<Dynamic>() { if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
return <dyn Any>::downcast_ref::<T>(self); return self.as_any().downcast_ref::<T>();
} }
match &self.0 { match &self.0 {
@ -1597,77 +1587,77 @@ 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, _, _) => value.as_mut_any().downcast_mut::<T>(),
_ => 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.as_mut()), Union::Float(value, _, _) => value.as_mut().as_mut_any().downcast_mut::<T>(),
_ => None, _ => None,
}; };
} }
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
if TypeId::of::<T>() == TypeId::of::<Decimal>() { if TypeId::of::<T>() == TypeId::of::<Decimal>() {
return match &mut self.0 { return match &mut self.0 {
Union::Decimal(value, _, _) => <dyn Any>::downcast_mut::<T>(value.as_mut()), Union::Decimal(value, _, _) => value.as_mut().as_mut_any().downcast_mut::<T>(),
_ => 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, _, _) => value.as_mut_any().downcast_mut::<T>(),
_ => 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, _, _) => value.as_mut_any().downcast_mut::<T>(),
_ => 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, _, _) => value.as_mut_any().downcast_mut::<T>(),
_ => 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, _, _) => value.as_mut().as_mut_any().downcast_mut::<T>(),
_ => 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, _, _) => value.as_mut().as_mut_any().downcast_mut::<T>(),
_ => 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, _, _) => value.as_mut().as_mut_any().downcast_mut::<T>(),
_ => 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, _, _) => value.as_mut().as_mut_any().downcast_mut::<T>(),
_ => 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, _, _) => value.as_mut_any().downcast_mut::<T>(),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<Dynamic>() { if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
return <dyn Any>::downcast_mut::<T>(self); return self.as_mut_any().downcast_mut::<T>();
} }
match &mut self.0 { match &mut self.0 {
@ -1720,8 +1710,8 @@ impl Dynamic {
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
#[inline(always)] #[inline(always)]
pub fn as_decimal(&self) -> Result<Decimal, &'static str> { pub fn as_decimal(&self) -> Result<Decimal, &'static str> {
match &self.0 { match self.0 {
Union::Decimal(n, _, _) => Ok(**n), Union::Decimal(ref 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()),
@ -1757,8 +1747,8 @@ impl Dynamic {
/// Panics if the value is shared. /// Panics if the value is shared.
#[inline(always)] #[inline(always)]
pub(crate) fn as_str_ref(&self) -> Result<&str, &'static str> { pub(crate) fn as_str_ref(&self) -> Result<&str, &'static str> {
match &self.0 { match self.0 {
Union::Str(s, _, _) => Ok(s), Union::Str(ref s, _, _) => Ok(s),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_, _, _) => panic!("as_str() cannot be called on shared values"), Union::Shared(_, _, _) => panic!("as_str() cannot be called on shared values"),
_ => Err(self.type_name()), _ => Err(self.type_name()),

View File

@ -29,7 +29,7 @@ use std::{
}; };
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::{calc_fn_hash, Array}; use crate::Array;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::Map; use crate::Map;
@ -224,9 +224,9 @@ pub const KEYWORD_GLOBAL: &str = "global";
pub const FN_GET: &str = "get$"; pub const FN_GET: &str = "get$";
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub const FN_SET: &str = "set$"; pub const FN_SET: &str = "set$";
#[cfg(not(feature = "no_index"))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
pub const FN_IDX_GET: &str = "index$get$"; pub const FN_IDX_GET: &str = "index$get$";
#[cfg(not(feature = "no_index"))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
pub const FN_IDX_SET: &str = "index$set$"; pub const FN_IDX_SET: &str = "index$set$";
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub const FN_ANONYMOUS: &str = "anon$"; pub const FN_ANONYMOUS: &str = "anon$";
@ -429,15 +429,15 @@ impl<'a> Target<'a> {
/// This has no effect except for string indexing. /// This has no effect except for string indexing.
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[inline(always)] #[inline(always)]
pub fn propagate_changed_value(&mut self) { pub fn propagate_changed_value(&mut self) -> Result<(), Box<EvalAltResult>> {
match self { match self {
Self::Ref(_) | Self::Value(_) => (), Self::Ref(_) | Self::Value(_) => Ok(()),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Self::LockGuard(_) => (), Self::LockGuard(_) => Ok(()),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::StringChar(_, _, ch) => { Self::StringChar(_, _, ch) => {
let char_value = ch.clone(); let char_value = ch.clone();
self.set_value(char_value, Position::NONE).unwrap(); self.set_value(char_value, Position::NONE)
} }
} }
} }
@ -455,7 +455,9 @@ impl<'a> Target<'a> {
Self::Value(_) => panic!("cannot update a value"), Self::Value(_) => panic!("cannot update a value"),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::StringChar(s, index, _) => { Self::StringChar(s, index, _) => {
let s = &mut *s.write_lock::<ImmutableString>().unwrap(); let s = &mut *s
.write_lock::<ImmutableString>()
.expect("never fails because `StringChar` always holds an `ImmutableString`");
// Replace the character at the specified index position // Replace the character at the specified index position
let new_ch = new_val.as_char().map_err(|err| { let new_ch = new_val.as_char().map_err(|err| {
@ -488,7 +490,12 @@ impl<'a> From<&'a mut Dynamic> for Target<'a> {
if value.is_shared() { if value.is_shared() {
// Cloning is cheap for a shared value // Cloning is cheap for a shared value
let container = value.clone(); let container = value.clone();
return Self::LockGuard((value.write_lock::<Dynamic>().unwrap(), container)); return Self::LockGuard((
value
.write_lock::<Dynamic>()
.expect("never fails when casting to `Dynamic`"),
container,
));
} }
Self::Ref(value) Self::Ref(value)
@ -598,9 +605,12 @@ impl State {
#[inline(always)] #[inline(always)]
pub fn fn_resolution_cache_mut(&mut self) -> &mut FnResolutionCache { pub fn fn_resolution_cache_mut(&mut self) -> &mut FnResolutionCache {
if self.fn_resolution_caches.0.is_empty() { if self.fn_resolution_caches.0.is_empty() {
// Push a new function resolution cache if the stack is empty
self.fn_resolution_caches.0.push(BTreeMap::new()); self.fn_resolution_caches.0.push(BTreeMap::new());
} }
self.fn_resolution_caches.0.last_mut().unwrap() self.fn_resolution_caches.0.last_mut().expect(
"never fails because there is at least one function resolution cache by this point",
)
} }
/// Push an empty function resolution cache onto the stack and make it current. /// Push an empty function resolution cache onto the stack and make it current.
#[allow(dead_code)] #[allow(dead_code)]
@ -617,7 +627,11 @@ impl State {
/// Panics if there are no more function resolution cache in the stack. /// Panics if there are no more function resolution cache in the stack.
#[inline(always)] #[inline(always)]
pub fn pop_fn_resolution_cache(&mut self) { pub fn pop_fn_resolution_cache(&mut self) {
let mut cache = self.fn_resolution_caches.0.pop().unwrap(); let mut cache = self
.fn_resolution_caches
.0
.pop()
.expect("there should be at least one function resolution cache");
cache.clear(); cache.clear();
self.fn_resolution_caches.1.push(cache); self.fn_resolution_caches.1.push(cache);
} }
@ -899,11 +913,7 @@ impl Engine {
progress: None, progress: None,
// optimization level // optimization level
optimization_level: if cfg!(feature = "no_optimize") { optimization_level: Default::default(),
OptimizationLevel::None
} else {
OptimizationLevel::Simple
},
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
limits: Limits { limits: Limits {
@ -956,11 +966,7 @@ impl Engine {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
progress: None, progress: None,
optimization_level: if cfg!(feature = "no_optimize") { optimization_level: Default::default(),
OptimizationLevel::None
} else {
OptimizationLevel::Simple
},
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
limits: Limits { limits: Limits {
@ -1004,10 +1010,16 @@ impl Engine {
if let Some(index) = index { if let Some(index) = index {
let offset = mods.len() - index.get(); let offset = mods.len() - index.get();
Some(mods.get(offset).expect("invalid index in Imports")) Some(
mods.get(offset)
.expect("never fails because offset should be within range"),
)
} else { } else {
mods.find(root) mods.find(root)
.map(|n| mods.get(n).expect("invalid index in Imports")) .map(|n| {
mods.get(n)
.expect("never fails because the index came from `find`")
})
.or_else(|| self.global_sub_modules.get(root).cloned()) .or_else(|| self.global_sub_modules.get(root).cloned())
} }
} }
@ -1059,6 +1071,10 @@ impl Engine {
} }
/// Search for a variable within the scope /// Search for a variable within the scope
///
/// # Panics
///
/// Panics if `expr` is not [`Expr::Variable`].
pub(crate) fn search_scope_only<'s>( pub(crate) fn search_scope_only<'s>(
&self, &self,
scope: &'s mut Scope, scope: &'s mut Scope,
@ -1096,8 +1112,12 @@ impl Engine {
this_ptr, this_ptr,
level: 0, level: 0,
}; };
if let Some(mut result) = if let Some(mut result) = resolve_var(
resolve_var(expr.get_variable_name(true).unwrap(), index, &context) expr.get_variable_name(true)
.expect("`expr` should be `Variable`"),
index,
&context,
)
.map_err(|err| err.fill_position(var_pos))? .map_err(|err| err.fill_position(var_pos))?
{ {
result.set_access_mode(AccessMode::ReadOnly); result.set_access_mode(AccessMode::ReadOnly);
@ -1109,7 +1129,9 @@ impl Engine {
scope.len() - index scope.len() - index
} else { } else {
// Find the variable in the scope // Find the variable in the scope
let var_name = expr.get_variable_name(true).unwrap(); let var_name = expr
.get_variable_name(true)
.expect("`expr` should be `Variable`");
scope scope
.get_index(var_name) .get_index(var_name)
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(var_name.to_string(), var_pos))? .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(var_name.to_string(), var_pos))?
@ -1138,18 +1160,22 @@ impl Engine {
level: usize, level: usize,
new_val: Option<((Dynamic, Position), (Option<OpAssignment>, Position))>, new_val: Option<((Dynamic, Position), (Option<OpAssignment>, Position))>,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
fn match_chain_type(expr: &Expr) -> ChainType {
match expr {
#[cfg(not(feature = "no_index"))]
Expr::Index(_, _) => ChainType::Index,
#[cfg(not(feature = "no_object"))]
Expr::Dot(_, _) => ChainType::Dot,
_ => unreachable!("`expr` should only be `Index` or `Dot`, but got {:?}", expr),
}
}
let is_ref = target.is_ref(); let is_ref = target.is_ref();
let rhs_chain = match rhs {
#[cfg(not(feature = "no_index"))]
Expr::Index(_, _) => Some(ChainType::Index),
#[cfg(not(feature = "no_object"))]
Expr::Dot(_, _) => Some(ChainType::Dot),
_ => None,
};
// Pop the last index value // Pop the last index value
let idx_val = idx_values.pop().unwrap(); let idx_val = idx_values
.pop()
.expect("never fails because an index chain is never empty");
match chain_type { match chain_type {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -1162,9 +1188,9 @@ impl Engine {
let idx_pos = x.lhs.position(); let idx_pos = x.lhs.position();
let idx_val = idx_val.as_index_value(); let idx_val = idx_val.as_index_value();
let obj_ptr = &mut self.get_indexed_mut( let obj_ptr = &mut self.get_indexed_mut(
mods, state, lib, target, idx_val, idx_pos, false, is_ref, true, level, mods, state, lib, target, idx_val, idx_pos, false, true, level,
)?; )?;
let rhs_chain = rhs_chain.unwrap(); let rhs_chain = match_chain_type(rhs);
self.eval_dot_index_chain_helper( self.eval_dot_index_chain_helper(
mods, state, lib, this_ptr, obj_ptr, root, &x.rhs, idx_values, mods, state, lib, this_ptr, obj_ptr, root, &x.rhs, idx_values,
@ -1174,61 +1200,40 @@ impl Engine {
} }
// xxx[rhs] op= new_val // xxx[rhs] op= new_val
_ if new_val.is_some() => { _ if new_val.is_some() => {
let ((mut new_val, new_pos), (op_info, op_pos)) =
new_val.expect("never fails because `new_val` is `Some`");
let idx_val = idx_val.as_index_value(); let idx_val = idx_val.as_index_value();
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
let mut idx_val2 = idx_val.clone(); let mut idx_val_for_setter = idx_val.clone();
// `call_setter` is introduced to bypass double mutable borrowing of target match self.get_indexed_mut(
let _call_setter = match self.get_indexed_mut( mods, state, lib, target, idx_val, pos, true, false, level,
mods, state, lib, target, idx_val, pos, true, is_ref, false, level,
) { ) {
// Indexed value is a reference - update directly // Indexed value is a reference - update directly
Ok(obj_ptr) => { Ok(obj_ptr) => {
let ((new_val, new_pos), (op_info, op_pos)) = new_val.unwrap();
self.eval_op_assignment( self.eval_op_assignment(
mods, state, lib, op_info, op_pos, obj_ptr, root, new_val, mods, state, lib, op_info, op_pos, obj_ptr, root, new_val,
new_pos, new_pos,
)?; )?;
None return Ok((Dynamic::UNIT, true));
} }
Err(err) => match *err { // Can't index - try to call an index setter
// No index getter - try to call an index setter
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
EvalAltResult::ErrorIndexingType(_, _) => Some(new_val.unwrap()), Err(err) if matches!(*err, EvalAltResult::ErrorIndexingType(_, _)) => {}
// Any other error - return // Any other error
err => return err.into(), Err(err) => return Err(err),
}, }
};
#[cfg(not(feature = "no_index"))] // Try to call index setter
if let Some(mut new_val) = _call_setter { let hash_set =
let val_type_name = target.type_name(); FnCallHashes::from_native(crate::calc_fn_hash(FN_IDX_SET, 3));
let ((_, val_pos), _) = new_val; let args = &mut [target, &mut idx_val_for_setter, &mut new_val];
let hash_set = FnCallHashes::from_native(calc_fn_hash(
std::iter::empty(),
FN_IDX_SET,
3,
));
let args = &mut [target, &mut idx_val2, &mut (new_val.0).0];
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, FN_IDX_SET, hash_set, args, is_ref, true, mods, state, lib, FN_IDX_SET, hash_set, args, is_ref, true, new_pos,
val_pos, None, level, None, level,
) )?;
.map_err(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
if fn_sig.ends_with("]=") =>
{
EvalAltResult::ErrorIndexingType(
self.map_type_name(val_type_name).into(),
Position::NONE,
)
}
err => err,
})?;
}
Ok((Dynamic::UNIT, true)) Ok((Dynamic::UNIT, true))
} }
@ -1236,7 +1241,7 @@ impl Engine {
_ => { _ => {
let idx_val = idx_val.as_index_value(); let idx_val = idx_val.as_index_value();
self.get_indexed_mut( self.get_indexed_mut(
mods, state, lib, target, idx_val, pos, false, is_ref, true, level, mods, state, lib, target, idx_val, pos, false, true, level,
) )
.map(|v| (v.take_or_clone(), false)) .map(|v| (v.take_or_clone(), false))
} }
@ -1264,12 +1269,13 @@ impl Engine {
} }
// {xxx:map}.id op= ??? // {xxx:map}.id op= ???
Expr::Property(x) if target.is::<Map>() && new_val.is_some() => { Expr::Property(x) if target.is::<Map>() && new_val.is_some() => {
let Ident { name, pos, .. } = &x.2; let (name, pos) = &x.2;
let index = name.into(); let index = name.into();
let val = self.get_indexed_mut( let val = self.get_indexed_mut(
mods, state, lib, target, index, *pos, true, is_ref, false, level, mods, state, lib, target, index, *pos, true, false, level,
)?; )?;
let ((new_val, new_pos), (op_info, op_pos)) = new_val.unwrap(); let ((new_val, new_pos), (op_info, op_pos)) =
new_val.expect("never fails because `new_val` is `Some`");
self.eval_op_assignment( self.eval_op_assignment(
mods, state, lib, op_info, op_pos, val, root, new_val, new_pos, mods, state, lib, op_info, op_pos, val, root, new_val, new_pos,
)?; )?;
@ -1277,27 +1283,46 @@ impl Engine {
} }
// {xxx:map}.id // {xxx:map}.id
Expr::Property(x) if target.is::<Map>() => { Expr::Property(x) if target.is::<Map>() => {
let Ident { name, pos, .. } = &x.2; let (name, pos) = &x.2;
let index = name.into(); let index = name.into();
let val = self.get_indexed_mut( let val = self.get_indexed_mut(
mods, state, lib, target, index, *pos, false, is_ref, false, level, mods, state, lib, target, index, *pos, false, false, level,
)?; )?;
Ok((val.take_or_clone(), false)) Ok((val.take_or_clone(), false))
} }
// xxx.id op= ??? // xxx.id op= ???
Expr::Property(x) if new_val.is_some() => { Expr::Property(x) if new_val.is_some() => {
let ((getter, hash_get), (setter, hash_set), Ident { pos, .. }) = let ((getter, hash_get), (setter, hash_set), (name, pos)) = x.as_ref();
x.as_ref(); let ((mut new_val, new_pos), (op_info, op_pos)) =
let ((mut new_val, new_pos), (op_info, op_pos)) = new_val.unwrap(); new_val.expect("never fails because `new_val` is `Some`");
if op_info.is_some() { if op_info.is_some() {
let hash = FnCallHashes::from_native(*hash_get); let hash = FnCallHashes::from_native(*hash_get);
let mut args = [target.as_mut()]; let mut args = [target.as_mut()];
let (mut orig_val, _) = self.exec_fn_call( let (mut orig_val, _) = self
.exec_fn_call(
mods, state, lib, getter, hash, &mut args, is_ref, true, *pos, mods, state, lib, getter, hash, &mut args, is_ref, true, *pos,
None, level, None, level,
)?; )
.or_else(|err| match *err {
// Try an indexer if property does not exist
EvalAltResult::ErrorDotExpr(_, _) => {
let prop = name.into();
self.get_indexed_mut(
mods, state, lib, target, prop, *pos, false, true,
level,
)
.map(|v| (v.take_or_clone(), false))
.map_err(
|idx_err| match *idx_err {
EvalAltResult::ErrorIndexingType(_, _) => err,
_ => idx_err,
},
)
}
_ => Err(err),
})?;
let obj_ptr = (&mut orig_val).into(); let obj_ptr = (&mut orig_val).into();
self.eval_op_assignment( self.eval_op_assignment(
mods, state, lib, op_info, op_pos, obj_ptr, root, new_val, new_pos, mods, state, lib, op_info, op_pos, obj_ptr, root, new_val, new_pos,
@ -1311,28 +1336,65 @@ impl Engine {
mods, state, lib, setter, hash, &mut args, is_ref, true, *pos, None, mods, state, lib, setter, hash, &mut args, is_ref, true, *pos, None,
level, level,
) )
.map(|(v, _)| (v, true)) .or_else(|err| match *err {
// Try an indexer if property does not exist
EvalAltResult::ErrorDotExpr(_, _) => {
let mut prop = name.into();
let args = &mut [target, &mut prop, &mut new_val];
let hash_set =
FnCallHashes::from_native(crate::calc_fn_hash(FN_IDX_SET, 3));
self.exec_fn_call(
mods, state, lib, FN_IDX_SET, hash_set, args, is_ref, true,
*pos, None, level,
)
.map_err(
|idx_err| match *idx_err {
EvalAltResult::ErrorIndexingType(_, _) => err,
_ => idx_err,
},
)
}
_ => Err(err),
})
} }
// xxx.id // xxx.id
Expr::Property(x) => { Expr::Property(x) => {
let ((getter, hash_get), _, Ident { pos, .. }) = x.as_ref(); let ((getter, hash_get), _, (name, pos)) = x.as_ref();
let hash = FnCallHashes::from_native(*hash_get); let hash = FnCallHashes::from_native(*hash_get);
let mut args = [target.as_mut()]; let mut args = [target.as_mut()];
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, getter, hash, &mut args, is_ref, true, *pos, None, mods, state, lib, getter, hash, &mut args, is_ref, true, *pos, None,
level, level,
) )
.map(|(v, _)| (v, false)) .map_or_else(
|err| match *err {
// Try an indexer if property does not exist
EvalAltResult::ErrorDotExpr(_, _) => {
let prop = name.into();
self.get_indexed_mut(
mods, state, lib, target, prop, *pos, false, true, level,
)
.map(|v| (v.take_or_clone(), false))
.map_err(|idx_err| {
match *idx_err {
EvalAltResult::ErrorIndexingType(_, _) => err,
_ => idx_err,
}
})
}
_ => Err(err),
},
|(v, _)| Ok((v, false)),
)
} }
// {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr // {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr
Expr::Index(x, x_pos) | Expr::Dot(x, x_pos) if target.is::<Map>() => { Expr::Index(x, x_pos) | Expr::Dot(x, x_pos) if target.is::<Map>() => {
let mut val = match &x.lhs { let mut val = match &x.lhs {
Expr::Property(p) => { Expr::Property(p) => {
let Ident { name, pos, .. } = &p.2; let (name, pos) = &p.2;
let index = name.into(); let index = name.into();
self.get_indexed_mut( self.get_indexed_mut(
mods, state, lib, target, index, *pos, false, is_ref, true, mods, state, lib, target, index, *pos, false, true, level,
level,
)? )?
} }
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
@ -1351,7 +1413,7 @@ impl Engine {
// Others - syntax error // Others - syntax error
expr => unreachable!("invalid dot expression: {:?}", expr), expr => unreachable!("invalid dot expression: {:?}", expr),
}; };
let rhs_chain = rhs_chain.unwrap(); let rhs_chain = match_chain_type(rhs);
self.eval_dot_index_chain_helper( self.eval_dot_index_chain_helper(
mods, state, lib, this_ptr, &mut val, root, &x.rhs, idx_values, mods, state, lib, this_ptr, &mut val, root, &x.rhs, idx_values,
@ -1364,17 +1426,36 @@ impl Engine {
match &x.lhs { match &x.lhs {
// xxx.prop[expr] | xxx.prop.expr // xxx.prop[expr] | xxx.prop.expr
Expr::Property(p) => { Expr::Property(p) => {
let ((getter, hash_get), (setter, hash_set), Ident { pos, .. }) = let ((getter, hash_get), (setter, hash_set), (name, pos)) =
p.as_ref(); p.as_ref();
let rhs_chain = rhs_chain.unwrap(); let rhs_chain = match_chain_type(rhs);
let hash_get = FnCallHashes::from_native(*hash_get); let hash_get = FnCallHashes::from_native(*hash_get);
let hash_set = FnCallHashes::from_native(*hash_set); let hash_set = FnCallHashes::from_native(*hash_set);
let arg_values = &mut [target.as_mut(), &mut Default::default()]; let mut arg_values = [target.as_mut(), &mut Default::default()];
let args = &mut arg_values[..1]; let args = &mut arg_values[..1];
let (mut val, updated) = self.exec_fn_call( let (mut val, updated) = self
mods, state, lib, getter, hash_get, args, is_ref, true, *pos, .exec_fn_call(
None, level, mods, state, lib, getter, hash_get, args, is_ref, true,
)?; *pos, None, level,
)
.or_else(|err| match *err {
// Try an indexer if property does not exist
EvalAltResult::ErrorDotExpr(_, _) => {
let prop = name.into();
self.get_indexed_mut(
mods, state, lib, target, prop, *pos, false, true,
level,
)
.map(|v| (v.take_or_clone(), false))
.map_err(
|idx_err| match *idx_err {
EvalAltResult::ErrorIndexingType(_, _) => err,
_ => idx_err,
},
)
}
_ => Err(err),
})?;
let val = &mut val; let val = &mut val;
@ -1397,18 +1478,34 @@ impl Engine {
// Feed the value back via a setter just in case it has been updated // Feed the value back via a setter just in case it has been updated
if updated || may_be_changed { if updated || may_be_changed {
// Re-use args because the first &mut parameter will not be consumed // Re-use args because the first &mut parameter will not be consumed
arg_values[1] = val; let mut arg_values = [target.as_mut(), val];
let args = &mut arg_values;
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, setter, hash_set, arg_values, is_ref, mods, state, lib, setter, hash_set, args, is_ref, true,
true, *pos, None, level, *pos, None, level,
) )
.or_else( .or_else(
|err| match *err { |err| match *err {
// Try an indexer if property does not exist
EvalAltResult::ErrorDotExpr(_, _) => {
let mut prop = name.into();
let args = &mut [target.as_mut(), &mut prop, val];
let hash_set = FnCallHashes::from_native(
crate::calc_fn_hash(FN_IDX_SET, 3),
);
self.exec_fn_call(
mods, state, lib, FN_IDX_SET, hash_set, args,
is_ref, true, *pos, None, level,
)
.or_else(|idx_err| match *idx_err {
EvalAltResult::ErrorIndexingType(_, _) => {
// If there is no setter, no need to feed it back because // If there is no setter, no need to feed it back because
// the property is read-only // the property is read-only
EvalAltResult::ErrorDotExpr(_, _) => {
Ok((Dynamic::UNIT, false)) Ok((Dynamic::UNIT, false))
} }
_ => Err(idx_err),
})
}
_ => Err(err), _ => Err(err),
}, },
)?; )?;
@ -1419,7 +1516,7 @@ impl Engine {
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
Expr::FnCall(f, pos) if !f.is_qualified() => { Expr::FnCall(f, pos) if !f.is_qualified() => {
let FnCallExpr { name, hashes, .. } = f.as_ref(); let FnCallExpr { name, hashes, .. } = f.as_ref();
let rhs_chain = rhs_chain.unwrap(); let rhs_chain = match_chain_type(rhs);
let mut args = idx_val.as_fn_call_args(); let mut args = idx_val.as_fn_call_args();
let (mut val, _) = self.make_method_call( let (mut val, _) = self.make_method_call(
mods, state, lib, name, *hashes, target, &mut args, *pos, level, mods, state, lib, name, *hashes, target, &mut args, *pos, level,
@ -1558,7 +1655,7 @@ impl Engine {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Property(x) if _parent_chain_type == ChainType::Dot => { Expr::Property(x) if _parent_chain_type == ChainType::Dot => {
idx_values.push(ChainArgument::Property(x.2.pos)) idx_values.push(ChainArgument::Property((x.2).1))
} }
Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"), Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"),
@ -1569,7 +1666,7 @@ impl Engine {
let lhs_val = match lhs { let lhs_val = match lhs {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Property(x) if _parent_chain_type == ChainType::Dot => { Expr::Property(x) if _parent_chain_type == ChainType::Dot => {
ChainArgument::Property(x.2.pos) ChainArgument::Property((x.2).1)
} }
Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"), Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"),
@ -1646,16 +1743,15 @@ impl Engine {
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
fn get_indexed_mut<'t>( fn get_indexed_mut<'t>(
&self, &self,
_mods: &mut Imports, mods: &mut Imports,
state: &mut State, state: &mut State,
_lib: &[&Module], lib: &[&Module],
target: &'t mut Dynamic, target: &'t mut Dynamic,
mut _idx: Dynamic, mut idx: Dynamic,
idx_pos: Position, idx_pos: Position,
_create: bool, _create: bool,
_is_ref: bool, indexers: bool,
_indexers: bool, level: usize,
_level: usize,
) -> Result<Target<'t>, Box<EvalAltResult>> { ) -> Result<Target<'t>, Box<EvalAltResult>> {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, Position::NONE)?; self.inc_operations(state, Position::NONE)?;
@ -1664,7 +1760,7 @@ impl Engine {
#[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()
.map_err(|err| self.make_type_mismatch_err::<crate::INT>(err, idx_pos))?; .map_err(|err| self.make_type_mismatch_err::<crate::INT>(err, idx_pos))?;
@ -1706,8 +1802,8 @@ 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]
let index = &*_idx.read_lock::<ImmutableString>().ok_or_else(|| { let index = &*idx.read_lock::<ImmutableString>().ok_or_else(|| {
self.make_type_mismatch_err::<ImmutableString>(_idx.type_name(), idx_pos) self.make_type_mismatch_err::<ImmutableString>(idx.type_name(), idx_pos)
})?; })?;
if _create && !map.contains_key(index.as_str()) { if _create && !map.contains_key(index.as_str()) {
@ -1723,7 +1819,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 index = _idx let index = idx
.as_int() .as_int()
.map_err(|err| self.make_type_mismatch_err::<crate::INT>(err, idx_pos))?; .map_err(|err| self.make_type_mismatch_err::<crate::INT>(err, idx_pos))?;
@ -1754,27 +1850,14 @@ impl Engine {
Ok(Target::StringChar(target, offset, ch.into())) Ok(Target::StringChar(target, offset, ch.into()))
} }
#[cfg(not(feature = "no_index"))] _ if indexers => {
_ if _indexers => { let args = &mut [target, &mut idx];
let type_name = target.type_name(); let hash_get = FnCallHashes::from_native(crate::calc_fn_hash(FN_IDX_GET, 2));
let args = &mut [target, &mut _idx];
let hash_get =
FnCallHashes::from_native(calc_fn_hash(std::iter::empty(), FN_IDX_GET, 2));
self.exec_fn_call( self.exec_fn_call(
_mods, state, _lib, FN_IDX_GET, hash_get, args, _is_ref, true, idx_pos, None, mods, state, lib, FN_IDX_GET, hash_get, args, true, true, idx_pos, None, level,
_level,
) )
.map(|(v, _)| v.into()) .map(|(v, _)| v.into())
.map_err(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(fn_sig, _) if fn_sig.ends_with(']') => {
Box::new(EvalAltResult::ErrorIndexingType(
type_name.into(),
Position::NONE,
))
}
_ => err,
})
} }
_ => EvalAltResult::ErrorIndexingType( _ => EvalAltResult::ErrorIndexingType(
@ -1878,7 +1961,10 @@ impl Engine {
Expr::Map(x, _) => { Expr::Map(x, _) => {
let mut map = x.1.clone(); let mut map = x.1.clone();
for (Ident { name: key, .. }, expr) in &x.0 { for (Ident { name: key, .. }, expr) in &x.0 {
*map.get_mut(key.as_str()).unwrap() = self let value_ref = map
.get_mut(key.as_str())
.expect("never fails because the template should contain all the keys");
*value_ref = self
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? .eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.flatten(); .flatten();
} }
@ -1895,7 +1981,9 @@ impl Engine {
literal_args: c_args, literal_args: c_args,
.. ..
} = x.as_ref(); } = x.as_ref();
let namespace = namespace.as_ref(); let namespace = namespace
.as_ref()
.expect("never fails because function call is qualified");
let hash = hashes.native_hash(); let hash = hashes.native_hash();
self.make_qualified_function_call( self.make_qualified_function_call(
scope, mods, state, lib, this_ptr, namespace, name, args, c_args, hash, *pos, scope, mods, state, lib, this_ptr, namespace, name, args, c_args, hash, *pos,
@ -1954,10 +2042,12 @@ impl Engine {
.iter() .iter()
.map(Into::into) .map(Into::into)
.collect::<StaticVec<_>>(); .collect::<StaticVec<_>>();
let key_token = custom.tokens.first().expect(
"never fails because a custom syntax stream must contain at least one token",
);
let custom_def = self let custom_def = self
.custom_syntax .custom_syntax.get(key_token)
.get(custom.tokens.first().unwrap()) .expect("never fails because the custom syntax leading token should match with definition");
.unwrap();
let mut context = EvalContext { let mut context = EvalContext {
engine: self, engine: self,
scope, scope,
@ -2088,7 +2178,9 @@ impl Engine {
let target_is_shared = false; let target_is_shared = false;
if target_is_shared { if target_is_shared {
lock_guard = target.write_lock::<Dynamic>().unwrap(); lock_guard = target
.write_lock::<Dynamic>()
.expect("never fails when casting to `Dynamic`");
lhs_ptr_inner = &mut *lock_guard; lhs_ptr_inner = &mut *lock_guard;
} else { } else {
lhs_ptr_inner = &mut *target; lhs_ptr_inner = &mut *target;
@ -2158,11 +2250,12 @@ impl Engine {
let (lhs_ptr, pos) = let (lhs_ptr, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?; self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?;
let var_name = lhs_expr
.get_variable_name(false)
.expect("never fails because `lhs_ptr` is a `Variable`s");
if !lhs_ptr.is_ref() { if !lhs_ptr.is_ref() {
return EvalAltResult::ErrorAssignmentToConstant( return EvalAltResult::ErrorAssignmentToConstant(var_name.to_string(), pos)
lhs_expr.get_variable_name(false).unwrap().to_string(),
pos,
)
.into(); .into();
} }
@ -2176,7 +2269,7 @@ impl Engine {
op_info.clone(), op_info.clone(),
*op_pos, *op_pos,
lhs_ptr, lhs_ptr,
(lhs_expr.get_variable_name(false).unwrap(), pos), (var_name, pos),
rhs_val, rhs_val,
rhs_expr.position(), rhs_expr.position(),
)?; )?;
@ -2401,7 +2494,10 @@ impl Engine {
let loop_var_is_shared = false; let loop_var_is_shared = false;
if loop_var_is_shared { if loop_var_is_shared {
*loop_var.write_lock().unwrap() = value; let mut value_ref = loop_var
.write_lock()
.expect("never fails when casting to `Dynamic`");
*value_ref = value;
} else { } else {
*loop_var = value; *loop_var = value;
} }
@ -2451,7 +2547,9 @@ impl Engine {
literal_args: c_args, literal_args: c_args,
.. ..
} = x.as_ref(); } = x.as_ref();
let namespace = namespace.as_ref(); let namespace = namespace
.as_ref()
.expect("never fails because function call is qualified");
let hash = hashes.native_hash(); let hash = hashes.native_hash();
self.make_qualified_function_call( self.make_qualified_function_call(
scope, mods, state, lib, this_ptr, namespace, name, args, c_args, hash, *pos, scope, mods, state, lib, this_ptr, namespace, name, args, c_args, hash, *pos,
@ -2505,26 +2603,22 @@ impl Engine {
err_map.insert("message".into(), err.to_string().into()); err_map.insert("message".into(), err.to_string().into());
if let Some(ref source) = state.source { state
err_map.insert("source".into(), source.into()); .source
} .as_ref()
.map(|source| err_map.insert("source".into(), source.into()));
if err_pos.is_none() { if err_pos.is_none() {
// No position info // No position info
} else { } else {
err_map.insert( let line = err_pos.line().expect("never fails because a non-NONE `Position` always has a line number") as INT;
"line".into(), let position = if err_pos.is_beginning_of_line() {
(err_pos.line().unwrap() as INT).into(),
);
err_map.insert(
"position".into(),
if err_pos.is_beginning_of_line() {
0 0
} else { } else {
err_pos.position().unwrap() as INT err_pos.position().expect("never fails because a non-NONE `Position` always has a character position")
} } as INT;
.into(), err_map.insert("line".into(), line.into());
); err_map.insert("position".into(), position.into());
} }
err.dump_fields(&mut err_map); err.dump_fields(&mut err_map);
@ -2535,9 +2629,9 @@ impl Engine {
let orig_scope_len = scope.len(); let orig_scope_len = scope.len();
state.scope_level += 1; state.scope_level += 1;
if let Some(Ident { name, .. }) = err_var { err_var.as_ref().map(|Ident { name, .. }| {
scope.push(unsafe_cast_var_name_to_lifetime(&name), err_value); scope.push(unsafe_cast_var_name_to_lifetime(name), err_value)
} });
let result = self.eval_stmt_block( let result = self.eval_stmt_block(
scope, mods, state, lib, this_ptr, catch_stmt, true, level, scope, mods, state, lib, this_ptr, catch_stmt, true, level,
@ -2604,7 +2698,10 @@ impl Engine {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
if entry_type == AccessMode::ReadOnly && lib.iter().any(|&m| !m.is_empty()) { if entry_type == AccessMode::ReadOnly && lib.iter().any(|&m| !m.is_empty()) {
let global = if let Some(index) = mods.find(KEYWORD_GLOBAL) { let global = if let Some(index) = mods.find(KEYWORD_GLOBAL) {
match mods.get_mut(index).unwrap() { match mods
.get_mut(index)
.expect("never fails because the index came from `find`")
{
m if m.internal => Some(m), m if m.internal => Some(m),
_ => None, _ => None,
} }
@ -2613,12 +2710,16 @@ impl Engine {
let mut global = Module::new(); let mut global = Module::new();
global.internal = true; global.internal = true;
mods.push(KEYWORD_GLOBAL, global); mods.push(KEYWORD_GLOBAL, global);
Some(mods.get_mut(mods.len() - 1).unwrap()) Some(
mods.get_mut(mods.len() - 1)
.expect("never fails because the global module was just added"),
)
}; };
if let Some(global) = global { if let Some(global) = global {
let global = Shared::get_mut(global).unwrap(); Shared::get_mut(global)
global.set_var(name.clone(), value.clone()); .expect("never fails because the global module is never shared")
.set_var(name.clone(), value.clone());
} }
} }
@ -2635,9 +2736,8 @@ impl Engine {
scope.push_dynamic_value(var_name, entry_type, value); scope.push_dynamic_value(var_name, entry_type, value);
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
if let Some(alias) = _alias { _alias.map(|alias| scope.add_entry_alias(scope.len() - 1, alias));
scope.add_entry_alias(scope.len() - 1, alias);
}
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} }
@ -2673,7 +2773,7 @@ impl Engine {
self.module_resolver.resolve(self, source, &path, expr_pos) self.module_resolver.resolve(self, source, &path, expr_pos)
})?; })?;
if let Some(name) = export.as_ref().map(|x| x.name.clone()) { export.as_ref().map(|x| x.name.clone()).map(|name| {
if !module.is_indexed() { if !module.is_indexed() {
// Index the module (making a clone copy if necessary) if it is not indexed // Index the module (making a clone copy if necessary) if it is not indexed
let mut module = crate::fn_native::shared_take_or_clone(module); let mut module = crate::fn_native::shared_take_or_clone(module);
@ -2682,7 +2782,7 @@ impl Engine {
} else { } else {
mods.push(name, module); mods.push(name, module);
} }
} });
state.modules += 1; state.modules += 1;
@ -2710,14 +2810,14 @@ impl Engine {
// Share statement // Share statement
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Stmt::Share(name) => { Stmt::Share(name) => {
if let Some((index, _)) = scope.get_index(name) { scope.get_index(name).map(|(index, _)| {
let val = scope.get_mut_by_index(index); let val = scope.get_mut_by_index(index);
if !val.is_shared() { if !val.is_shared() {
// Replace the variable with a shared value. // Replace the variable with a shared value.
*val = std::mem::take(val).into_shared(); *val = std::mem::take(val).into_shared();
} }
} });
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} }
}; };
@ -2732,9 +2832,10 @@ impl Engine {
/// Check a result to ensure that the data size is within allowable limit. /// Check a result to ensure that the data size is within allowable limit.
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn check_data_size(&self, result: &RhaiResult) -> Result<(), Box<EvalAltResult>> { fn check_data_size(&self, result: &RhaiResult) -> Result<(), Box<EvalAltResult>> {
if result.is_err() { let result = match result {
return Ok(()); Err(_) => return Ok(()),
} Ok(r) => r,
};
// If no data size limits, just return // If no data size limits, just return
let mut _has_limit = self.limits.max_string_size.is_some(); let mut _has_limit = self.limits.max_string_size.is_some();
@ -2803,7 +2904,7 @@ impl Engine {
} }
} }
let (_arr, _map, s) = calc_size(result.as_ref().unwrap()); let (_arr, _map, s) = calc_size(result);
if s > self if s > self
.limits .limits
@ -2860,7 +2961,7 @@ impl Engine {
} }
// Report progress - only in steps // Report progress - only in steps
if let Some(progress) = &self.progress { if let Some(ref progress) = self.progress {
if let Some(token) = progress(state.operations) { if let Some(token) = progress(state.operations) {
// Terminate script if progress returns a termination token // Terminate script if progress returns a termination token
return EvalAltResult::ErrorTerminated(token, pos).into(); return EvalAltResult::ErrorTerminated(token, pos).into();

View File

@ -477,9 +477,7 @@ impl Engine {
set_fn: impl Fn(&mut T, V) -> Result<(), Box<EvalAltResult>> + SendSync + 'static, set_fn: impl Fn(&mut T, V) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
use crate::engine::make_setter; use crate::engine::make_setter;
self.register_result_fn(&make_setter(name), move |obj: &mut T, value: V| { self.register_result_fn(&make_setter(name), set_fn)
set_fn(obj, value)
})
} }
/// Short-hand for registering both getter and setter functions /// Short-hand for registering both getter and setter functions
/// of a registered type with the [`Engine`]. /// of a registered type with the [`Engine`].
@ -536,7 +534,7 @@ impl Engine {
/// ///
/// The function signature must start with `&mut self` and not `&self`. /// The function signature must start with `&mut self` and not `&self`.
/// ///
/// Not available under `no_index`. /// Not available under both `no_index` and `no_object`.
/// ///
/// # Panics /// # Panics
/// ///
@ -572,16 +570,18 @@ impl Engine {
/// // Register an indexer. /// // Register an indexer.
/// .register_indexer_get(TestStruct::get_field); /// .register_indexer_get(TestStruct::get_field);
/// ///
/// # #[cfg(not(feature = "no_index"))]
/// assert_eq!(engine.eval::<i64>("let a = new_ts(); a[2]")?, 3); /// assert_eq!(engine.eval::<i64>("let a = new_ts(); a[2]")?, 3);
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_index"))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline(always)] #[inline(always)]
pub fn register_indexer_get<T: Variant + Clone, X: Variant + Clone, V: Variant + Clone>( pub fn register_indexer_get<T: Variant + Clone, X: Variant + Clone, V: Variant + Clone>(
&mut self, &mut self,
get_fn: impl Fn(&mut T, X) -> V + SendSync + 'static, get_fn: impl Fn(&mut T, X) -> V + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
#[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<Array>() { if TypeId::of::<T>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays."); panic!("Cannot register indexer for arrays.");
} }
@ -602,7 +602,7 @@ impl Engine {
/// ///
/// The function signature must start with `&mut self` and not `&self`. /// The function signature must start with `&mut self` and not `&self`.
/// ///
/// Not available under `no_index`. /// Not available under both `no_index` and `no_object`.
/// ///
/// # Panics /// # Panics
/// ///
@ -640,11 +640,12 @@ impl Engine {
/// // Register an indexer. /// // Register an indexer.
/// .register_indexer_get_result(TestStruct::get_field); /// .register_indexer_get_result(TestStruct::get_field);
/// ///
/// # #[cfg(not(feature = "no_index"))]
/// assert_eq!(engine.eval::<i64>("let a = new_ts(); a[2]")?, 3); /// assert_eq!(engine.eval::<i64>("let a = new_ts(); a[2]")?, 3);
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_index"))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline(always)] #[inline(always)]
pub fn register_indexer_get_result< pub fn register_indexer_get_result<
T: Variant + Clone, T: Variant + Clone,
@ -654,6 +655,7 @@ impl Engine {
&mut self, &mut self,
get_fn: impl Fn(&mut T, X) -> Result<V, Box<EvalAltResult>> + SendSync + 'static, get_fn: impl Fn(&mut T, X) -> Result<V, Box<EvalAltResult>> + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
#[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<Array>() { if TypeId::of::<T>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays."); panic!("Cannot register indexer for arrays.");
} }
@ -672,7 +674,7 @@ impl Engine {
} }
/// Register an index setter for a custom type with the [`Engine`]. /// Register an index setter for a custom type with the [`Engine`].
/// ///
/// Not available under `no_index`. /// Not available under both `no_index` and `no_object`.
/// ///
/// # Panics /// # Panics
/// ///
@ -707,6 +709,7 @@ impl Engine {
/// // Register an indexer. /// // Register an indexer.
/// .register_indexer_set(TestStruct::set_field); /// .register_indexer_set(TestStruct::set_field);
/// ///
/// # #[cfg(not(feature = "no_index"))]
/// assert_eq!( /// assert_eq!(
/// engine.eval::<TestStruct>("let a = new_ts(); a[2] = 42; a")?.fields[2], /// engine.eval::<TestStruct>("let a = new_ts(); a[2] = 42; a")?.fields[2],
/// 42 /// 42
@ -714,12 +717,13 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_index"))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline(always)] #[inline(always)]
pub fn register_indexer_set<T: Variant + Clone, X: Variant + Clone, V: Variant + Clone>( pub fn register_indexer_set<T: Variant + Clone, X: Variant + Clone, V: Variant + Clone>(
&mut self, &mut self,
set_fn: impl Fn(&mut T, X, V) + SendSync + 'static, set_fn: impl Fn(&mut T, X, V) + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
#[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<Array>() { if TypeId::of::<T>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays."); panic!("Cannot register indexer for arrays.");
} }
@ -738,7 +742,7 @@ impl Engine {
} }
/// Register an index setter for a custom type with the [`Engine`]. /// Register an index setter for a custom type with the [`Engine`].
/// ///
/// Not available under `no_index`. /// Not available under both `no_index` and `no_object`.
/// ///
/// # Panics /// # Panics
/// ///
@ -776,6 +780,7 @@ impl Engine {
/// // Register an indexer. /// // Register an indexer.
/// .register_indexer_set_result(TestStruct::set_field); /// .register_indexer_set_result(TestStruct::set_field);
/// ///
/// # #[cfg(not(feature = "no_index"))]
/// assert_eq!( /// assert_eq!(
/// engine.eval::<TestStruct>("let a = new_ts(); a[2] = 42; a")?.fields[2], /// engine.eval::<TestStruct>("let a = new_ts(); a[2] = 42; a")?.fields[2],
/// 42 /// 42
@ -783,7 +788,7 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_index"))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline(always)] #[inline(always)]
pub fn register_indexer_set_result< pub fn register_indexer_set_result<
T: Variant + Clone, T: Variant + Clone,
@ -793,6 +798,7 @@ impl Engine {
&mut self, &mut self,
set_fn: impl Fn(&mut T, X, V) -> Result<(), Box<EvalAltResult>> + SendSync + 'static, set_fn: impl Fn(&mut T, X, V) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
#[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<Array>() { if TypeId::of::<T>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays."); panic!("Cannot register indexer for arrays.");
} }
@ -807,14 +813,11 @@ impl Engine {
panic!("Cannot register indexer for strings."); panic!("Cannot register indexer for strings.");
} }
self.register_result_fn( self.register_result_fn(crate::engine::FN_IDX_SET, set_fn)
crate::engine::FN_IDX_SET,
move |obj: &mut T, index: X, value: V| set_fn(obj, index, value),
)
} }
/// Short-hand for register both index getter and setter functions for a custom type with the [`Engine`]. /// Short-hand for registering both index getter and setter functions for a custom type with the [`Engine`].
/// ///
/// Not available under `no_index`. /// Not available under both `no_index` and `no_object`.
/// ///
/// # Panics /// # Panics
/// ///
@ -852,11 +855,12 @@ impl Engine {
/// // Register an indexer. /// // Register an indexer.
/// .register_indexer_get_set(TestStruct::get_field, TestStruct::set_field); /// .register_indexer_get_set(TestStruct::get_field, TestStruct::set_field);
/// ///
/// # #[cfg(not(feature = "no_index"))]
/// assert_eq!(engine.eval::<i64>("let a = new_ts(); a[2] = 42; a[2]")?, 42); /// assert_eq!(engine.eval::<i64>("let a = new_ts(); a[2] = 42; a[2]")?, 42);
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_index"))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline(always)] #[inline(always)]
pub fn register_indexer_get_set<T: Variant + Clone, X: Variant + Clone, V: Variant + Clone>( pub fn register_indexer_get_set<T: Variant + Clone, X: Variant + Clone, V: Variant + Clone>(
&mut self, &mut self,
@ -947,8 +951,14 @@ impl Engine {
} }
} else { } else {
let mut iter = name.as_ref().splitn(2, separator.as_ref()); let mut iter = name.as_ref().splitn(2, separator.as_ref());
let sub_module = iter.next().unwrap().trim(); let sub_module = iter
let remainder = iter.next().unwrap().trim(); .next()
.expect("never fails because the name contains a separator")
.trim();
let remainder = iter
.next()
.expect("never fails because the name contains a separator")
.trim();
if !root.contains_key(sub_module) { if !root.contains_key(sub_module) {
let mut m: Module = Default::default(); let mut m: Module = Default::default();
@ -956,7 +966,9 @@ impl Engine {
m.build_index(); m.build_index();
root.insert(sub_module.into(), m.into()); root.insert(sub_module.into(), m.into());
} else { } else {
let m = root.remove(sub_module).unwrap(); let m = root
.remove(sub_module)
.expect("never fails because the root contains the sub-module");
let mut m = crate::fn_native::shared_take_or_clone(m); let mut m = crate::fn_native::shared_take_or_clone(m);
register_static_module_raw(m.sub_modules_mut(), remainder, module); register_static_module_raw(m.sub_modules_mut(), remainder, module);
m.build_index(); m.build_index();
@ -1074,7 +1086,10 @@ impl Engine {
resolver: &StaticModuleResolver, resolver: &StaticModuleResolver,
imports: &mut BTreeSet<Identifier>, imports: &mut BTreeSet<Identifier>,
) { ) {
ast.walk(&mut |path| match path.last().unwrap() { ast.walk(&mut |path| match path
.last()
.expect("never fails because `path` always contains the current node")
{
// Collect all `import` statements with a string constant path // Collect all `import` statements with a string constant path
ASTNode::Stmt(Stmt::Import(Expr::StringConstant(s, _), _, _)) ASTNode::Stmt(Stmt::Import(Expr::StringConstant(s, _), _, _))
if !resolver.contains_path(s) && !imports.contains(s.as_str()) => if !resolver.contains_path(s) && !imports.contains(s.as_str()) =>

View File

@ -13,6 +13,8 @@ use crate::FLOAT;
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
use rust_decimal::Decimal; use rust_decimal::Decimal;
const BUILTIN: &str = "never fails because this is built-in code and the type is already checked";
/// Is the type a numeric type? /// Is the type a numeric type?
#[inline(always)] #[inline(always)]
fn is_numeric(type_id: TypeId) -> bool { fn is_numeric(type_id: TypeId) -> bool {
@ -75,22 +77,22 @@ pub fn get_builtin_binary_op_fn(
macro_rules! impl_op { macro_rules! impl_op {
($xx:ident $op:tt $yy:ident) => { ($xx:ident $op:tt $yy:ident) => {
return Some(|_, args| { return Some(|_, args| {
let x = &*args[0].read_lock::<$xx>().unwrap(); let x = &*args[0].read_lock::<$xx>().expect(BUILTIN);
let y = &*args[1].read_lock::<$yy>().unwrap(); let y = &*args[1].read_lock::<$yy>().expect(BUILTIN);
Ok((x $op y).into()) Ok((x $op y).into())
}) })
}; };
($xx:ident . $func:ident ( $yy:ty )) => { ($xx:ident . $func:ident ( $yy:ty )) => {
return Some(|_, args| { return Some(|_, args| {
let x = &*args[0].read_lock::<$xx>().unwrap(); let x = &*args[0].read_lock::<$xx>().expect(BUILTIN);
let y = &*args[1].read_lock::<$yy>().unwrap(); let y = &*args[1].read_lock::<$yy>().expect(BUILTIN);
Ok(x.$func(y).into()) Ok(x.$func(y).into())
}) })
}; };
($xx:ident . $func:ident ( $yy:ident . $yyy:ident () )) => { ($xx:ident . $func:ident ( $yy:ident . $yyy:ident () )) => {
return Some(|_, args| { return Some(|_, args| {
let x = &*args[0].read_lock::<$xx>().unwrap(); let x = &*args[0].read_lock::<$xx>().expect(BUILTIN);
let y = &*args[1].read_lock::<$yy>().unwrap(); let y = &*args[1].read_lock::<$yy>().expect(BUILTIN);
Ok(x.$func(y.$yyy()).into()) Ok(x.$func(y.$yyy()).into())
}) })
}; };
@ -102,43 +104,43 @@ pub fn get_builtin_binary_op_fn(
}; };
($base:ty => $xx:ident $op:tt $yy:ident) => { ($base:ty => $xx:ident $op:tt $yy:ident) => {
return Some(|_, args| { return Some(|_, args| {
let x = args[0].$xx().unwrap() as $base; let x = args[0].$xx().expect(BUILTIN) as $base;
let y = args[1].$yy().unwrap() as $base; let y = args[1].$yy().expect(BUILTIN) as $base;
Ok((x $op y).into()) Ok((x $op y).into())
}) })
}; };
($base:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty)) => { ($base:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty)) => {
return Some(|_, args| { return Some(|_, args| {
let x = args[0].$xx().unwrap() as $base; let x = args[0].$xx().expect(BUILTIN) as $base;
let y = args[1].$yy().unwrap() as $base; let y = args[1].$yy().expect(BUILTIN) as $base;
Ok(x.$func(y as $yyy).into()) Ok(x.$func(y as $yyy).into())
}) })
}; };
($base:ty => $func:ident ( $xx:ident, $yy:ident )) => { ($base:ty => $func:ident ( $xx:ident, $yy:ident )) => {
return Some(|_, args| { return Some(|_, args| {
let x = args[0].$xx().unwrap() as $base; let x = args[0].$xx().expect(BUILTIN) as $base;
let y = args[1].$yy().unwrap() as $base; let y = args[1].$yy().expect(BUILTIN) as $base;
$func(x, y).map(Into::<Dynamic>::into) $func(x, y).map(Into::<Dynamic>::into)
}) })
}; };
(from $base:ty => $xx:ident $op:tt $yy:ident) => { (from $base:ty => $xx:ident $op:tt $yy:ident) => {
return Some(|_, args| { return Some(|_, args| {
let x = <$base>::from(args[0].$xx().unwrap()); let x = <$base>::from(args[0].$xx().expect(BUILTIN));
let y = <$base>::from(args[1].$yy().unwrap()); let y = <$base>::from(args[1].$yy().expect(BUILTIN));
Ok((x $op y).into()) Ok((x $op y).into())
}) })
}; };
(from $base:ty => $xx:ident . $func:ident ( $yy:ident )) => { (from $base:ty => $xx:ident . $func:ident ( $yy:ident )) => {
return Some(|_, args| { return Some(|_, args| {
let x = <$base>::from(args[0].$xx().unwrap()); let x = <$base>::from(args[0].$xx().expect(BUILTIN));
let y = <$base>::from(args[1].$yy().unwrap()); let y = <$base>::from(args[1].$yy().expect(BUILTIN));
Ok(x.$func(y).into()) Ok(x.$func(y).into())
}) })
}; };
(from $base:ty => $func:ident ( $xx:ident, $yy:ident )) => { (from $base:ty => $func:ident ( $xx:ident, $yy:ident )) => {
return Some(|_, args| { return Some(|_, args| {
let x = <$base>::from(args[0].$xx().unwrap()); let x = <$base>::from(args[0].$xx().expect(BUILTIN));
let y = <$base>::from(args[1].$yy().unwrap()); let y = <$base>::from(args[1].$yy().expect(BUILTIN));
$func(x, y).map(Into::<Dynamic>::into) $func(x, y).map(Into::<Dynamic>::into)
}) })
}; };
@ -175,7 +177,7 @@ pub fn get_builtin_binary_op_fn(
($x:ty, $xx:ident, $y:ty, $yy:ident) => { ($x:ty, $xx:ident, $y:ty, $yy:ident) => {
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
if types_pair == (TypeId::of::<$x>(), TypeId::of::<$y>()) { if types_pair == (TypeId::of::<$x>(), TypeId::of::<$y>()) {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unBUILTIN")) {
use crate::packages::arithmetic::decimal_functions::*; use crate::packages::arithmetic::decimal_functions::*;
match op { match op {
@ -222,8 +224,8 @@ pub fn get_builtin_binary_op_fn(
if types_pair == (TypeId::of::<char>(), TypeId::of::<ImmutableString>()) { if types_pair == (TypeId::of::<char>(), TypeId::of::<ImmutableString>()) {
#[inline(always)] #[inline(always)]
fn get_s1s2(args: &FnCallArgs) -> ([char; 2], [char; 2]) { fn get_s1s2(args: &FnCallArgs) -> ([char; 2], [char; 2]) {
let x = args[0].as_char().unwrap(); let x = args[0].as_char().expect(BUILTIN);
let y = &*args[1].read_lock::<ImmutableString>().unwrap(); let y = &*args[1].read_lock::<ImmutableString>().expect(BUILTIN);
let s1 = [x, '\0']; let s1 = [x, '\0'];
let mut y = y.chars(); let mut y = y.chars();
let s2 = [y.next().unwrap_or('\0'), y.next().unwrap_or('\0')]; let s2 = [y.next().unwrap_or('\0'), y.next().unwrap_or('\0')];
@ -233,8 +235,8 @@ pub fn get_builtin_binary_op_fn(
match op { match op {
"+" => { "+" => {
return Some(|_, args| { return Some(|_, args| {
let x = args[0].as_char().unwrap(); let x = args[0].as_char().expect(BUILTIN);
let y = &*args[1].read_lock::<ImmutableString>().unwrap(); let y = &*args[1].read_lock::<ImmutableString>().expect(BUILTIN);
Ok(format!("{}{}", x, y).into()) Ok(format!("{}{}", x, y).into())
}) })
} }
@ -251,8 +253,8 @@ pub fn get_builtin_binary_op_fn(
if types_pair == (TypeId::of::<ImmutableString>(), TypeId::of::<char>()) { if types_pair == (TypeId::of::<ImmutableString>(), TypeId::of::<char>()) {
#[inline(always)] #[inline(always)]
fn get_s1s2(args: &FnCallArgs) -> ([char; 2], [char; 2]) { fn get_s1s2(args: &FnCallArgs) -> ([char; 2], [char; 2]) {
let x = &*args[0].read_lock::<ImmutableString>().unwrap(); let x = &*args[0].read_lock::<ImmutableString>().expect(BUILTIN);
let y = args[1].as_char().unwrap(); let y = args[1].as_char().expect(BUILTIN);
let mut x = x.chars(); let mut x = x.chars();
let s1 = [x.next().unwrap_or('\0'), x.next().unwrap_or('\0')]; let s1 = [x.next().unwrap_or('\0'), x.next().unwrap_or('\0')];
let s2 = [y, '\0']; let s2 = [y, '\0'];
@ -262,15 +264,15 @@ pub fn get_builtin_binary_op_fn(
match op { match op {
"+" => { "+" => {
return Some(|_, args| { return Some(|_, args| {
let x = &*args[0].read_lock::<ImmutableString>().unwrap(); let x = &*args[0].read_lock::<ImmutableString>().expect(BUILTIN);
let y = args[1].as_char().unwrap(); let y = args[1].as_char().expect(BUILTIN);
Ok((x + y).into()) Ok((x + y).into())
}) })
} }
"-" => { "-" => {
return Some(|_, args| { return Some(|_, args| {
let x = &*args[0].read_lock::<ImmutableString>().unwrap(); let x = &*args[0].read_lock::<ImmutableString>().expect(BUILTIN);
let y = args[1].as_char().unwrap(); let y = args[1].as_char().expect(BUILTIN);
Ok((x - y).into()) Ok((x - y).into())
}) })
} }
@ -282,8 +284,8 @@ pub fn get_builtin_binary_op_fn(
"<=" => impl_op!(get_s1s2(<=)), "<=" => impl_op!(get_s1s2(<=)),
OP_CONTAINS => { OP_CONTAINS => {
return Some(|_, args| { return Some(|_, args| {
let s = &*args[0].read_lock::<ImmutableString>().unwrap(); let s = &*args[0].read_lock::<ImmutableString>().expect(BUILTIN);
let c = args[1].as_char().unwrap(); let c = args[1].as_char().expect(BUILTIN);
Ok((s.contains(c)).into()) Ok((s.contains(c)).into())
}) })
} }
@ -314,7 +316,7 @@ pub fn get_builtin_binary_op_fn(
// Beyond here, type1 == type2 // Beyond here, type1 == type2
if type1 == TypeId::of::<INT>() { if type1 == TypeId::of::<INT>() {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unBUILTIN")) {
use crate::packages::arithmetic::arith_basic::INT::functions::*; use crate::packages::arithmetic::arith_basic::INT::functions::*;
match op { match op {
@ -383,8 +385,8 @@ pub fn get_builtin_binary_op_fn(
"<=" => impl_op!(ImmutableString <= ImmutableString), "<=" => impl_op!(ImmutableString <= ImmutableString),
OP_CONTAINS => { OP_CONTAINS => {
return Some(|_, args| { return Some(|_, args| {
let s1 = &*args[0].read_lock::<ImmutableString>().unwrap(); let s1 = &*args[0].read_lock::<ImmutableString>().expect(BUILTIN);
let s2 = &*args[1].read_lock::<ImmutableString>().unwrap(); let s2 = &*args[1].read_lock::<ImmutableString>().expect(BUILTIN);
Ok((s1.contains(s2.as_str())).into()) Ok((s1.contains(s2.as_str())).into())
}) })
} }
@ -396,8 +398,8 @@ pub fn get_builtin_binary_op_fn(
match op { match op {
"+" => { "+" => {
return Some(|_, args| { return Some(|_, args| {
let x = args[0].as_char().unwrap(); let x = args[0].as_char().expect(BUILTIN);
let y = args[1].as_char().unwrap(); let y = args[1].as_char().expect(BUILTIN);
Ok(format!("{}{}", x, y).into()) Ok(format!("{}{}", x, y).into())
}) })
} }
@ -440,55 +442,55 @@ pub fn get_builtin_op_assignment_fn(
macro_rules! impl_op { macro_rules! impl_op {
($x:ty = x $op:tt $yy:ident) => { ($x:ty = x $op:tt $yy:ident) => {
return Some(|_, args| { return Some(|_, args| {
let x = args[0].$yy().unwrap(); let x = args[0].$yy().expect(BUILTIN);
let y = args[1].$yy().unwrap() as $x; let y = args[1].$yy().expect(BUILTIN) as $x;
Ok((*args[0].write_lock::<$x>().unwrap() = x $op y).into()) Ok((*args[0].write_lock::<$x>().expect(BUILTIN) = x $op y).into())
}) })
}; };
($x:ident $op:tt $yy:ident) => { ($x:ident $op:tt $yy:ident) => {
return Some(|_, args| { return Some(|_, args| {
let y = args[1].$yy().unwrap() as $x; let y = args[1].$yy().expect(BUILTIN) as $x;
Ok((*args[0].write_lock::<$x>().unwrap() $op y).into()) Ok((*args[0].write_lock::<$x>().expect(BUILTIN) $op y).into())
}) })
}; };
($x:ident $op:tt $yy:ident as $yyy:ty) => { ($x:ident $op:tt $yy:ident as $yyy:ty) => {
return Some(|_, args| { return Some(|_, args| {
let y = args[1].$yy().unwrap() as $yyy; let y = args[1].$yy().expect(BUILTIN) as $yyy;
Ok((*args[0].write_lock::<$x>().unwrap() $op y).into()) Ok((*args[0].write_lock::<$x>().expect(BUILTIN) $op y).into())
}) })
}; };
($x:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty )) => { ($x:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty )) => {
return Some(|_, args| { return Some(|_, args| {
let x = args[0].$xx().unwrap(); let x = args[0].$xx().expect(BUILTIN);
let y = args[1].$yy().unwrap() as $x; let y = args[1].$yy().expect(BUILTIN) as $x;
Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y as $yyy)).into()) Ok((*args[0].write_lock::<$x>().expect(BUILTIN) = x.$func(y as $yyy)).into())
}) })
}; };
($x:ty => $func:ident ( $xx:ident, $yy:ident )) => { ($x:ty => $func:ident ( $xx:ident, $yy:ident )) => {
return Some(|_, args| { return Some(|_, args| {
let x = args[0].$xx().unwrap(); let x = args[0].$xx().expect(BUILTIN);
let y = args[1].$yy().unwrap() as $x; let y = args[1].$yy().expect(BUILTIN) as $x;
Ok((*args[0].write_lock().unwrap() = $func(x, y)?).into()) Ok((*args[0].write_lock().expect(BUILTIN) = $func(x, y)?).into())
}) })
}; };
(from $x:ident $op:tt $yy:ident) => { (from $x:ident $op:tt $yy:ident) => {
return Some(|_, args| { return Some(|_, args| {
let y = <$x>::from(args[1].$yy().unwrap()); let y = <$x>::from(args[1].$yy().expect(BUILTIN));
Ok((*args[0].write_lock::<$x>().unwrap() $op y).into()) Ok((*args[0].write_lock::<$x>().expect(BUILTIN) $op y).into())
}) })
}; };
(from $x:ty => $xx:ident . $func:ident ( $yy:ident )) => { (from $x:ty => $xx:ident . $func:ident ( $yy:ident )) => {
return Some(|_, args| { return Some(|_, args| {
let x = args[0].$xx().unwrap(); let x = args[0].$xx().expect(BUILTIN);
let y = <$x>::from(args[1].$yy().unwrap()); let y = <$x>::from(args[1].$yy().expect(BUILTIN));
Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y)).into()) Ok((*args[0].write_lock::<$x>().expect(BUILTIN) = x.$func(y)).into())
}) })
}; };
(from $x:ty => $func:ident ( $xx:ident, $yy:ident )) => { (from $x:ty => $func:ident ( $xx:ident, $yy:ident )) => {
return Some(|_, args| { return Some(|_, args| {
let x = args[0].$xx().unwrap(); let x = args[0].$xx().expect(BUILTIN);
let y = <$x>::from(args[1].$yy().unwrap()); let y = <$x>::from(args[1].$yy().expect(BUILTIN));
Ok((*args[0].write_lock().unwrap() = $func(x, y)?).into()) Ok((*args[0].write_lock().expect(BUILTIN) = $func(x, y)?).into())
}) })
}; };
} }
@ -517,7 +519,7 @@ pub fn get_builtin_op_assignment_fn(
($x:ident, $xx:ident, $y:ty, $yy:ident) => { ($x:ident, $xx:ident, $y:ty, $yy:ident) => {
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
if types_pair == (TypeId::of::<$x>(), TypeId::of::<$y>()) { if types_pair == (TypeId::of::<$x>(), TypeId::of::<$y>()) {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unBUILTIN")) {
use crate::packages::arithmetic::decimal_functions::*; use crate::packages::arithmetic::decimal_functions::*;
match op { match op {
@ -562,10 +564,15 @@ pub fn get_builtin_op_assignment_fn(
match op { match op {
"+=" => { "+=" => {
return Some(|_, args| { return Some(|_, args| {
let mut ch = args[0].as_char().unwrap().to_string(); let mut ch = args[0].as_char().expect(BUILTIN).to_string();
ch.push_str(args[1].read_lock::<ImmutableString>().unwrap().as_str()); ch.push_str(
args[1]
.read_lock::<ImmutableString>()
.expect(BUILTIN)
.as_str(),
);
let mut x = args[0].write_lock::<Dynamic>().unwrap(); let mut x = args[0].write_lock::<Dynamic>().expect(BUILTIN);
Ok((*x = ch.into()).into()) Ok((*x = ch.into()).into())
}) })
} }
@ -580,7 +587,7 @@ pub fn get_builtin_op_assignment_fn(
// Beyond here, type1 == type2 // Beyond here, type1 == type2
if type1 == TypeId::of::<INT>() { if type1 == TypeId::of::<INT>() {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unBUILTIN")) {
use crate::packages::arithmetic::arith_basic::INT::functions::*; use crate::packages::arithmetic::arith_basic::INT::functions::*;
match op { match op {
@ -628,8 +635,8 @@ pub fn get_builtin_op_assignment_fn(
match op { match op {
"+=" => { "+=" => {
return Some(|_, args| { return Some(|_, args| {
let y = args[1].as_char().unwrap(); let y = args[1].as_char().expect(BUILTIN);
let mut x = args[0].write_lock::<Dynamic>().unwrap(); let mut x = args[0].write_lock::<Dynamic>().expect(BUILTIN);
Ok((*x = format!("{}{}", *x, y).into()).into()) Ok((*x = format!("{}{}", *x, y).into()).into())
}) })
} }
@ -641,17 +648,17 @@ pub fn get_builtin_op_assignment_fn(
match op { match op {
"+=" => { "+=" => {
return Some(|_, args| { return Some(|_, args| {
let (first, second) = args.split_first_mut().unwrap(); let (first, second) = args.split_first_mut().expect(BUILTIN);
let mut x = first.write_lock::<ImmutableString>().unwrap(); let mut x = first.write_lock::<ImmutableString>().expect(BUILTIN);
let y = &*second[0].read_lock::<ImmutableString>().unwrap(); let y = &*second[0].read_lock::<ImmutableString>().expect(BUILTIN);
Ok((*x += y).into()) Ok((*x += y).into())
}) })
} }
"-=" => { "-=" => {
return Some(|_, args| { return Some(|_, args| {
let (first, second) = args.split_first_mut().unwrap(); let (first, second) = args.split_first_mut().expect(BUILTIN);
let mut x = first.write_lock::<ImmutableString>().unwrap(); let mut x = first.write_lock::<ImmutableString>().expect(BUILTIN);
let y = &*second[0].read_lock::<ImmutableString>().unwrap(); let y = &*second[0].read_lock::<ImmutableString>().expect(BUILTIN);
Ok((*x -= y).into()) Ok((*x -= y).into())
}) })
} }

View File

@ -24,7 +24,7 @@ use std::prelude::v1::*;
use std::{ use std::{
any::{type_name, TypeId}, any::{type_name, TypeId},
convert::TryFrom, convert::TryFrom,
iter::{empty, once}, iter::once,
mem, mem,
}; };
@ -49,7 +49,12 @@ impl<'a> ArgBackup<'a> {
/// ///
/// This method blindly casts a reference to another lifetime, which saves allocation and string cloning. /// This method blindly casts a reference to another lifetime, which saves allocation and string cloning.
/// ///
/// If `restore_first_arg` is called before the end of the scope, the shorter lifetime will not leak. /// As long as `restore_first_arg` is called before the end of the scope, the shorter lifetime
/// will not leak.
///
/// # Panics
///
/// Panics when `args` is empty.
#[inline(always)] #[inline(always)]
fn change_first_arg_to_copy(&mut self, args: &mut FnCallArgs<'a>) { fn change_first_arg_to_copy(&mut self, args: &mut FnCallArgs<'a>) {
// Clone the original value. // Clone the original value.
@ -65,7 +70,7 @@ impl<'a> ArgBackup<'a> {
// //
// We can do this here because, before the end of this scope, we'd restore the original reference // We can do this here because, before the end of this scope, we'd restore the original reference
// via `restore_first_arg`. Therefore this shorter lifetime does not leak. // via `restore_first_arg`. Therefore this shorter lifetime does not leak.
self.orig_mut = Some(mem::replace(args.get_mut(0).unwrap(), unsafe { self.orig_mut = Some(mem::replace(&mut args[0], unsafe {
mem::transmute(&mut self.value_copy) mem::transmute(&mut self.value_copy)
})); }));
} }
@ -77,9 +82,7 @@ impl<'a> ArgBackup<'a> {
/// the current scope. Otherwise it is undefined behavior as the shorter lifetime will leak. /// the current scope. Otherwise it is undefined behavior as the shorter lifetime will leak.
#[inline(always)] #[inline(always)]
fn restore_first_arg(mut self, args: &mut FnCallArgs<'a>) { fn restore_first_arg(mut self, args: &mut FnCallArgs<'a>) {
if let Some(this_pointer) = self.orig_mut.take() { self.orig_mut.take().map(|p| args[0] = p);
args[0] = this_pointer;
}
} }
} }
@ -162,12 +165,10 @@ impl Engine {
allow_dynamic: bool, allow_dynamic: bool,
is_op_assignment: bool, is_op_assignment: bool,
) -> &'s Option<Box<FnResolutionCacheEntry>> { ) -> &'s Option<Box<FnResolutionCacheEntry>> {
let mut hash = if let Some(ref args) = args { let mut hash = args.as_ref().map_or(hash_script, |args| {
let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id())); let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id()));
combine_hashes(hash_script, hash_params) combine_hashes(hash_script, hash_params)
} else { });
hash_script
};
&*state &*state
.fn_resolution_cache_mut() .fn_resolution_cache_mut()
@ -239,7 +240,8 @@ impl Engine {
FnResolutionCacheEntry { func, source: None } FnResolutionCacheEntry { func, source: None }
}) })
} else { } else {
let (first, second) = args.split_first().unwrap(); let (first, second) = args.split_first()
.expect("never fails because an op-assignment must have two arguments");
get_builtin_op_assignment_fn(fn_name, *first, second[0]).map( get_builtin_op_assignment_fn(fn_name, *first, second[0]).map(
|f| { |f| {
@ -257,7 +259,9 @@ impl Engine {
// Try all permutations with `Dynamic` wildcards // Try all permutations with `Dynamic` wildcards
None => { None => {
let hash_params = calc_fn_params_hash( let hash_params = calc_fn_params_hash(
args.as_ref().unwrap().iter().enumerate().map(|(i, a)| { args.as_ref().expect("never fails because there are no permutations if there are no arguments")
.iter().enumerate().map(|(i, a)|
{
let mask = 1usize << (num_args - i - 1); let mask = 1usize << (num_args - i - 1);
if bitmask & mask != 0 { if bitmask & mask != 0 {
// Replace with `Dynamic` // Replace with `Dynamic`
@ -320,7 +324,7 @@ impl Engine {
let mut backup: Option<ArgBackup> = None; let mut backup: Option<ArgBackup> = None;
if is_ref && func.is_pure() && !args.is_empty() { if is_ref && func.is_pure() && !args.is_empty() {
backup = Some(Default::default()); backup = Some(Default::default());
backup.as_mut().unwrap().change_first_arg_to_copy(args); backup.as_mut().map(|bk| bk.change_first_arg_to_copy(args));
} }
// Run external function // Run external function
@ -336,9 +340,7 @@ impl Engine {
}; };
// Restore the original reference // Restore the original reference
if let Some(backup) = backup { backup.map(|bk| bk.restore_first_arg(args));
backup.restore_first_arg(args);
}
let result = result.map_err(|err| err.fill_position(pos))?; let result = result.map_err(|err| err.fill_position(pos))?;
@ -371,32 +373,24 @@ impl Engine {
match fn_name { match fn_name {
// index getter function not found? // index getter function not found?
#[cfg(not(feature = "no_index"))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
crate::engine::FN_IDX_GET => { crate::engine::FN_IDX_GET => {
assert!(args.len() == 2); assert!(args.len() == 2);
EvalAltResult::ErrorFunctionNotFound( EvalAltResult::ErrorIndexingType(
format!( self.map_type_name(args[0].type_name()).to_string(),
"{} [{}]",
self.map_type_name(args[0].type_name()),
self.map_type_name(args[1].type_name()),
),
pos, pos,
) )
.into() .into()
} }
// index setter function not found? // index setter function not found?
#[cfg(not(feature = "no_index"))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
crate::engine::FN_IDX_SET => { crate::engine::FN_IDX_SET => {
assert!(args.len() == 3); assert!(args.len() == 3);
EvalAltResult::ErrorFunctionNotFound( EvalAltResult::ErrorIndexingType(
format!( self.map_type_name(args[0].type_name()).to_string(),
"{} [{}]=",
self.map_type_name(args[0].type_name()),
self.map_type_name(args[1].type_name()),
),
pos, pos,
) )
.into() .into()
@ -654,14 +648,18 @@ impl Engine {
crate::engine::KEYWORD_IS_DEF_FN crate::engine::KEYWORD_IS_DEF_FN
if args.len() == 2 && args[0].is::<FnPtr>() && args[1].is::<crate::INT>() => if args.len() == 2 && args[0].is::<FnPtr>() && args[1].is::<crate::INT>() =>
{ {
let fn_name = &*args[0].read_lock::<ImmutableString>().unwrap(); let fn_name = &*args[0]
let num_params = args[1].as_int().unwrap(); .read_lock::<ImmutableString>()
.expect("never fails because `args[0]` is `FnPtr`");
let num_params = args[1]
.as_int()
.expect("never fails because `args[1]` is `INT`");
return Ok(( return Ok((
if num_params < 0 { if num_params < 0 {
Dynamic::FALSE Dynamic::FALSE
} else { } else {
let hash_script = calc_fn_hash(empty(), fn_name, num_params as usize); let hash_script = calc_fn_hash(fn_name, num_params as usize);
self.has_script_fn(Some(mods), state, lib, hash_script) self.has_script_fn(Some(mods), state, lib, hash_script)
.into() .into()
}, },
@ -712,11 +710,7 @@ impl Engine {
// Scripted function call? // Scripted function call?
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
let hash_script = if hash.is_native_only() { let hash_script = hash.script_hash();
None
} else {
Some(hash.script_hash())
};
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
if let Some(f) = hash_script.and_then(|hash| { if let Some(f) = hash_script.and_then(|hash| {
@ -737,21 +731,23 @@ impl Engine {
// Move captured variables into scope // Move captured variables into scope
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
if let Some(captured) = _capture_scope {
if !func.externals.is_empty() { if !func.externals.is_empty() {
_capture_scope.map(|captured| {
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, value, _)| { .for_each(|(name, value, _)| {
// Consume the scope values. // Consume the scope values.
scope.push_dynamic(name, value); scope.push_dynamic(name, value);
})
}); });
} }
}
let result = if _is_method { let result = if _is_method {
// Method call of script function - map first argument to `this` // Method call of script function - map first argument to `this`
let (first, rest) = args.split_first_mut().unwrap(); let (first, rest) = args
.split_first_mut()
.expect("never fails because a method call always has a first parameter");
let orig_source = state.source.take(); let orig_source = state.source.take();
state.source = source; state.source = source;
@ -780,7 +776,7 @@ impl Engine {
let mut backup: Option<ArgBackup> = None; let mut backup: Option<ArgBackup> = None;
if is_ref && !args.is_empty() { if is_ref && !args.is_empty() {
backup = Some(Default::default()); backup = Some(Default::default());
backup.as_mut().unwrap().change_first_arg_to_copy(args); backup.as_mut().map(|bk| bk.change_first_arg_to_copy(args));
} }
let orig_source = state.source.take(); let orig_source = state.source.take();
@ -795,9 +791,7 @@ impl Engine {
state.source = orig_source; state.source = orig_source;
// Restore the original reference // Restore the original reference
if let Some(backup) = backup { backup.map(|bk| bk.restore_first_arg(args));
backup.restore_first_arg(args);
}
result? result?
}; };
@ -914,12 +908,14 @@ impl Engine {
let (result, updated) = match fn_name { let (result, updated) = match fn_name {
KEYWORD_FN_PTR_CALL if obj.is::<FnPtr>() => { KEYWORD_FN_PTR_CALL if obj.is::<FnPtr>() => {
// FnPtr call // FnPtr call
let fn_ptr = obj.read_lock::<FnPtr>().unwrap(); let fn_ptr = obj
.read_lock::<FnPtr>()
.expect("never fails because `obj` is `FnPtr`");
// Redirect function name // Redirect function name
let fn_name = fn_ptr.fn_name(); let fn_name = fn_ptr.fn_name();
let args_len = call_args.len() + fn_ptr.curry().len(); let args_len = call_args.len() + fn_ptr.curry().len();
// Recalculate hashes // Recalculate hashes
let new_hash = FnCallHashes::from_script(calc_fn_hash(empty(), fn_name, args_len)); let new_hash = FnCallHashes::from_script(calc_fn_hash(fn_name, args_len));
// Arguments are passed as-is, adding the curried arguments // Arguments are passed as-is, adding the curried arguments
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>(); let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
let mut args = curry let mut args = curry
@ -955,8 +951,8 @@ impl Engine {
let args_len = call_args.len() + fn_ptr.curry().len(); let args_len = call_args.len() + fn_ptr.curry().len();
// Recalculate hash // Recalculate hash
let new_hash = FnCallHashes::from_script_and_native( let new_hash = FnCallHashes::from_script_and_native(
calc_fn_hash(empty(), fn_name, args_len), calc_fn_hash(fn_name, args_len),
calc_fn_hash(empty(), fn_name, args_len + 1), calc_fn_hash(fn_name, args_len + 1),
); );
// Replace the first argument with the object pointer, adding the curried arguments // Replace the first argument with the object pointer, adding the curried arguments
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>(); let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
@ -978,7 +974,9 @@ impl Engine {
)); ));
} }
let fn_ptr = obj.read_lock::<FnPtr>().unwrap(); let fn_ptr = obj
.read_lock::<FnPtr>()
.expect("never fails because `obj` is `FnPtr`");
// Curry call // Curry call
Ok(( Ok((
@ -1029,8 +1027,8 @@ impl Engine {
}); });
// Recalculate the hash based on the new function name and new arguments // Recalculate the hash based on the new function name and new arguments
hash = FnCallHashes::from_script_and_native( hash = FnCallHashes::from_script_and_native(
calc_fn_hash(empty(), fn_name, call_args.len()), calc_fn_hash(fn_name, call_args.len()),
calc_fn_hash(empty(), fn_name, call_args.len() + 1), calc_fn_hash(fn_name, call_args.len() + 1),
); );
} }
} }
@ -1049,7 +1047,9 @@ impl Engine {
// Propagate the changed value back to the source if necessary // Propagate the changed value back to the source if necessary
if updated { if updated {
target.propagate_changed_value(); target
.propagate_changed_value()
.map_err(|err| err.fill_position(pos))?;
} }
Ok((result, updated)) Ok((result, updated))
@ -1115,9 +1115,9 @@ impl Engine {
// Recalculate hash // Recalculate hash
let args_len = total_args + curry.len(); let args_len = total_args + curry.len();
hashes = if !hashes.is_native_only() { hashes = if !hashes.is_native_only() {
FnCallHashes::from_script(calc_fn_hash(empty(), name, args_len)) FnCallHashes::from_script(calc_fn_hash(name, args_len))
} else { } else {
FnCallHashes::from_native(calc_fn_hash(empty(), name, args_len)) FnCallHashes::from_native(calc_fn_hash(name, args_len))
}; };
} }
// Handle Fn() // Handle Fn()
@ -1214,7 +1214,7 @@ impl Engine {
return Ok(if num_params < 0 { return Ok(if num_params < 0 {
Dynamic::FALSE Dynamic::FALSE
} else { } else {
let hash_script = calc_fn_hash(empty(), &fn_name, num_params as usize); let hash_script = calc_fn_hash(&fn_name, num_params as usize);
self.has_script_fn(Some(mods), state, lib, hash_script) self.has_script_fn(Some(mods), state, lib, hash_script)
.into() .into()
}); });
@ -1332,9 +1332,8 @@ impl Engine {
} else { } else {
// Turn it into a method call only if the object is not shared and not a simple value // Turn it into a method call only if the object is not shared and not a simple value
is_ref = true; is_ref = true;
once(target.take_ref().unwrap()) let obj_ref = target.take_ref().expect("never fails because `target` is a reference if it is not a value and not shared");
.chain(arg_values.iter_mut()) once(obj_ref).chain(arg_values.iter_mut()).collect()
.collect()
}; };
} else { } else {
// func(..., ...) // func(..., ...)
@ -1365,7 +1364,7 @@ impl Engine {
state: &mut State, state: &mut State,
lib: &[&Module], lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
namespace: Option<&NamespaceRef>, namespace: &NamespaceRef,
fn_name: &str, fn_name: &str,
args_expr: &[Expr], args_expr: &[Expr],
literal_args: &[(Dynamic, Position)], literal_args: &[(Dynamic, Position)],
@ -1373,7 +1372,6 @@ impl Engine {
pos: Position, pos: Position,
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
let namespace = namespace.unwrap();
let mut arg_values: StaticVec<_>; let mut arg_values: StaticVec<_>;
let mut first_arg_value = None; let mut first_arg_value = None;
let mut args: StaticVec<_>; let mut args: StaticVec<_>;
@ -1418,11 +1416,12 @@ impl Engine {
arg_values[0] = target.take_or_clone().flatten(); arg_values[0] = target.take_or_clone().flatten();
args = arg_values.iter_mut().collect(); args = arg_values.iter_mut().collect();
} else { } else {
let (first, rest) = arg_values.split_first_mut().unwrap(); let (first, rest) = arg_values
.split_first_mut()
.expect("never fails because the arguments list is not empty");
first_arg_value = Some(first); first_arg_value = Some(first);
args = once(target.take_ref().unwrap()) let obj_ref = target.take_ref().expect("never fails because `target` is a reference if it is not a value and not shared");
.chain(rest.iter_mut()) args = once(obj_ref).chain(rest.iter_mut()).collect();
.collect();
} }
} else { } else {
// func(..., ...) or func(mod::x, ...) // func(..., ...) or func(mod::x, ...)
@ -1459,12 +1458,11 @@ impl Engine {
}; };
// Clone first argument if the function is not a method after-all // Clone first argument if the function is not a method after-all
if let Some(first) = first_arg_value {
if !func.map(|f| f.is_method()).unwrap_or(true) { if !func.map(|f| f.is_method()).unwrap_or(true) {
let first_val = args[0].clone(); first_arg_value.map(|first| {
*first = args[0].clone();
args[0] = first; args[0] = first;
*args[0] = first_val; });
}
} }
match func { match func {

View File

@ -13,7 +13,7 @@ use std::prelude::v1::*;
use std::{ use std::{
convert::{TryFrom, TryInto}, convert::{TryFrom, TryInto},
fmt, fmt,
iter::{empty, once}, iter::once,
mem, mem,
}; };
@ -202,11 +202,11 @@ impl<'a> NativeCallContext<'a> {
let hash = if is_method { let hash = if is_method {
FnCallHashes::from_script_and_native( FnCallHashes::from_script_and_native(
calc_fn_hash(empty(), fn_name, args.len() - 1), calc_fn_hash(fn_name, args.len() - 1),
calc_fn_hash(empty(), fn_name, args.len()), calc_fn_hash(fn_name, args.len()),
) )
} else { } else {
FnCallHashes::from_script(calc_fn_hash(empty(), fn_name, args.len())) FnCallHashes::from_script(calc_fn_hash(fn_name, args.len()))
}; };
self.engine() self.engine()
@ -253,7 +253,9 @@ pub fn shared_try_take<T>(value: Shared<T>) -> Result<T, Shared<T>> {
/// Panics if the resource is shared (i.e. has other outstanding references). /// Panics if the resource is shared (i.e. has other outstanding references).
#[inline(always)] #[inline(always)]
pub fn shared_take<T>(value: Shared<T>) -> T { pub fn shared_take<T>(value: Shared<T>) -> T {
shared_try_take(value).map_err(|_| ()).unwrap() shared_try_take(value)
.ok()
.expect("resource should have no outstanding references")
} }
/// Arguments to a function call, which is a list of [`&mut Dynamic`][Dynamic]. /// Arguments to a function call, which is a list of [`&mut Dynamic`][Dynamic].

View File

@ -32,7 +32,8 @@ pub struct Mut<T>(T);
#[inline(always)] #[inline(always)]
pub fn by_ref<T: Variant + Clone>(data: &mut Dynamic) -> DynamicWriteLock<T> { pub fn by_ref<T: Variant + Clone>(data: &mut Dynamic) -> DynamicWriteLock<T> {
// Directly cast the &mut Dynamic into DynamicWriteLock to access the underlying data. // Directly cast the &mut Dynamic into DynamicWriteLock to access the underlying data.
data.write_lock::<T>().unwrap() data.write_lock::<T>()
.expect("never fails because the type was checked")
} }
/// Dereference into value. /// Dereference into value.
@ -43,12 +44,15 @@ pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
data.flatten_in_place(); data.flatten_in_place();
let ref_str = data let ref_str = data
.as_str_ref() .as_str_ref()
.expect("argument passed by value should not be shared"); .expect("never fails because argument passed by value should not be shared");
let ref_t = unsafe { mem::transmute::<_, &T>(&ref_str) }; let ref_t = unsafe { mem::transmute::<_, &T>(&ref_str) };
ref_t.clone() ref_t.clone()
} else if TypeId::of::<T>() == TypeId::of::<String>() { } else if TypeId::of::<T>() == TypeId::of::<String>() {
// If T is `String`, data must be `ImmutableString`, so map directly to it // If T is `String`, data must be `ImmutableString`, so map directly to it
unsafe_try_cast(mem::take(data).take_string().unwrap()).unwrap() let value = mem::take(data)
.take_string()
.expect("never fails because the type was checked");
unsafe_try_cast(value).expect("never fails because the type was checked")
} else { } else {
// We consume the argument and then replace it with () - the argument is not supposed to be used again. // We consume the argument and then replace it with () - the argument is not supposed to be used again.
// This way, we avoid having to clone the argument again, because it is already a clone when passed here. // This way, we avoid having to clone the argument again, because it is already a clone when passed here.
@ -118,7 +122,7 @@ macro_rules! def_register {
// The arguments are assumed to be of the correct number and types! // The arguments are assumed to be of the correct number and types!
let mut _drain = args.iter_mut(); let mut _drain = args.iter_mut();
$($let $par = ($clone)(_drain.next().unwrap()); )* $($let $par = ($clone)(_drain.next().expect("never fails because arguments list is fixed")); )*
// Call the function with each argument value // Call the function with each argument value
let r = self($($arg),*); let r = self($($arg),*);
@ -146,7 +150,7 @@ macro_rules! def_register {
// The arguments are assumed to be of the correct number and types! // The arguments are assumed to be of the correct number and types!
let mut _drain = args.iter_mut(); let mut _drain = args.iter_mut();
$($let $par = ($clone)(_drain.next().unwrap()); )* $($let $par = ($clone)(_drain.next().expect("never fails because arguments list is fixed")); )*
// Call the function with each argument value // Call the function with each argument value
let r = self(ctx, $($arg),*); let r = self(ctx, $($arg),*);
@ -174,7 +178,7 @@ macro_rules! def_register {
// The arguments are assumed to be of the correct number and types! // The arguments are assumed to be of the correct number and types!
let mut _drain = args.iter_mut(); let mut _drain = args.iter_mut();
$($let $par = ($clone)(_drain.next().unwrap()); )* $($let $par = ($clone)(_drain.next().expect("never fails because arguments list is fixed")); )*
// Call the function with each argument value // Call the function with each argument value
self($($arg),*).map(Dynamic::from) self($($arg),*).map(Dynamic::from)
@ -199,7 +203,7 @@ macro_rules! def_register {
// The arguments are assumed to be of the correct number and types! // The arguments are assumed to be of the correct number and types!
let mut _drain = args.iter_mut(); let mut _drain = args.iter_mut();
$($let $par = ($clone)(_drain.next().unwrap()); )* $($let $par = ($clone)(_drain.next().expect("never fails because arguments list is fixed")); )*
// Call the function with each argument value // Call the function with each argument value
self(ctx, $($arg),*).map(Dynamic::from) self(ctx, $($arg),*).map(Dynamic::from)

View File

@ -169,7 +169,7 @@ pub use fn_native::Shared;
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
use fn_native::Locked; use fn_native::Locked;
pub(crate) use utils::{calc_fn_hash, calc_fn_params_hash, combine_hashes}; pub(crate) use utils::{calc_fn_hash, calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes};
pub use rhai_codegen::*; pub use rhai_codegen::*;

View File

@ -7,8 +7,8 @@ use crate::fn_register::RegisterNativeFunction;
use crate::token::Token; use crate::token::Token;
use crate::utils::IdentifierBuilder; use crate::utils::IdentifierBuilder;
use crate::{ use crate::{
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, EvalAltResult, Identifier, calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, Dynamic, EvalAltResult,
ImmutableString, NativeCallContext, Position, Shared, StaticVec, Identifier, ImmutableString, NativeCallContext, Position, Shared, StaticVec,
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -24,7 +24,6 @@ use std::{
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::Array; use crate::Array;
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::Map; use crate::Map;
@ -117,7 +116,7 @@ fn calc_native_fn_hash<'a>(
fn_name: impl AsRef<str>, fn_name: impl AsRef<str>,
params: &[TypeId], params: &[TypeId],
) -> u64 { ) -> u64 {
let hash_script = calc_fn_hash(modules, fn_name, params.len()); let hash_script = calc_qualified_fn_hash(modules, fn_name, params.len());
let hash_params = calc_fn_params_hash(params.iter().cloned()); let hash_params = calc_fn_params_hash(params.iter().cloned());
combine_hashes(hash_script, hash_params) combine_hashes(hash_script, hash_params)
} }
@ -156,20 +155,7 @@ pub struct Module {
impl Default for Module { impl Default for Module {
#[inline(always)] #[inline(always)]
fn default() -> Self { fn default() -> Self {
Self { Self::new()
id: None,
internal: false,
modules: Default::default(),
variables: Default::default(),
all_variables: Default::default(),
functions: Default::default(),
all_functions: Default::default(),
type_iterators: Default::default(),
all_type_iterators: Default::default(),
indexed: true,
contains_indexed_global_functions: false,
identifiers: Default::default(),
}
} }
} }
@ -177,9 +163,7 @@ impl fmt::Debug for Module {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_struct("Module"); let mut d = f.debug_struct("Module");
if let Some(ref id) = self.id { self.id.as_ref().map(|id| d.field("id", id));
d.field("id", id);
}
if !self.modules.is_empty() { if !self.modules.is_empty() {
d.field( d.field(
@ -257,7 +241,20 @@ impl Module {
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn new() -> Self { pub fn new() -> Self {
Default::default() Self {
id: None,
internal: false,
modules: Default::default(),
variables: Default::default(),
all_variables: Default::default(),
functions: Default::default(),
all_functions: Default::default(),
type_iterators: Default::default(),
all_type_iterators: Default::default(),
indexed: true,
contains_indexed_global_functions: false,
identifiers: Default::default(),
}
} }
/// Get the ID of the [`Module`], if any. /// Get the ID of the [`Module`], if any.
@ -441,7 +438,7 @@ impl Module {
let value = Dynamic::from(value); let value = Dynamic::from(value);
if self.indexed { if self.indexed {
let hash_var = crate::calc_fn_hash(once(""), &ident, 0); let hash_var = crate::calc_qualified_fn_hash(once(""), &ident, 0);
self.all_variables.insert(hash_var, value.clone()); self.all_variables.insert(hash_var, value.clone());
} }
self.variables.insert(ident, value); self.variables.insert(ident, value);
@ -467,7 +464,7 @@ impl Module {
// None + function name + number of arguments. // None + function name + number of arguments.
let num_params = fn_def.params.len(); let num_params = fn_def.params.len();
let hash_script = crate::calc_fn_hash(empty(), &fn_def.name, num_params); let hash_script = crate::calc_fn_hash(&fn_def.name, num_params);
let mut param_names = fn_def.params.clone(); let mut param_names = fn_def.params.clone();
param_names.push("Dynamic".into()); param_names.push("Dynamic".into());
self.functions.insert( self.functions.insert(
@ -622,9 +619,10 @@ impl Module {
.map(|&name| self.identifiers.get(name)) .map(|&name| self.identifiers.get(name))
.collect(); .collect();
if let Some(f) = self.functions.get_mut(&hash_fn) { self.functions
f.param_names = param_names; .get_mut(&hash_fn)
} .map(|f| f.param_names = param_names);
self self
} }
@ -956,7 +954,7 @@ impl Module {
/// }); /// });
/// assert!(module.contains_fn(hash)); /// assert!(module.contains_fn(hash));
/// ``` /// ```
#[cfg(not(feature = "no_index"))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline(always)] #[inline(always)]
pub fn set_indexer_get_fn<ARGS, A, B, T, F>(&mut self, func: F) -> u64 pub fn set_indexer_get_fn<ARGS, A, B, T, F>(&mut self, func: F) -> u64
where where
@ -966,6 +964,7 @@ impl Module {
F: RegisterNativeFunction<ARGS, Result<T, Box<EvalAltResult>>>, F: RegisterNativeFunction<ARGS, Result<T, Box<EvalAltResult>>>,
F: Fn(&mut A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, F: Fn(&mut A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
{ {
#[cfg(not(feature = "no_index"))]
if TypeId::of::<A>() == TypeId::of::<Array>() { if TypeId::of::<A>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays."); panic!("Cannot register indexer for arrays.");
} }
@ -1016,7 +1015,7 @@ impl Module {
/// }); /// });
/// assert!(module.contains_fn(hash)); /// assert!(module.contains_fn(hash));
/// ``` /// ```
#[cfg(not(feature = "no_index"))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline(always)] #[inline(always)]
pub fn set_indexer_set_fn<ARGS, A, B, C, F>(&mut self, func: F) -> u64 pub fn set_indexer_set_fn<ARGS, A, B, C, F>(&mut self, func: F) -> u64
where where
@ -1026,6 +1025,7 @@ impl Module {
F: RegisterNativeFunction<ARGS, Result<(), Box<EvalAltResult>>>, F: RegisterNativeFunction<ARGS, Result<(), Box<EvalAltResult>>>,
F: Fn(&mut A, B, C) -> Result<(), Box<EvalAltResult>> + SendSync + 'static, F: Fn(&mut A, B, C) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
{ {
#[cfg(not(feature = "no_index"))]
if TypeId::of::<A>() == TypeId::of::<Array>() { if TypeId::of::<A>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays."); panic!("Cannot register indexer for arrays.");
} }
@ -1082,7 +1082,7 @@ impl Module {
/// assert!(module.contains_fn(hash_get)); /// assert!(module.contains_fn(hash_get));
/// assert!(module.contains_fn(hash_set)); /// assert!(module.contains_fn(hash_set));
/// ``` /// ```
#[cfg(not(feature = "no_index"))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline(always)] #[inline(always)]
pub fn set_indexer_get_set_fn<A, B, T>( pub fn set_indexer_get_set_fn<A, B, T>(
&mut self, &mut self,
@ -1414,7 +1414,10 @@ impl Module {
match aliases.len() { match aliases.len() {
0 => (), 0 => (),
1 => { 1 => {
module.set_var(aliases.pop().unwrap(), value); let alias = aliases
.pop()
.expect("never fails because the list has one item");
module.set_var(alias, value);
} }
_ => aliases.into_iter().for_each(|alias| { _ => aliases.into_iter().for_each(|alias| {
module.set_var(alias, value.clone()); module.set_var(alias, value.clone());
@ -1490,7 +1493,7 @@ impl Module {
// Index all variables // Index all variables
module.variables.iter().for_each(|(var_name, value)| { module.variables.iter().for_each(|(var_name, value)| {
let hash_var = crate::calc_fn_hash(path.iter().map(|&v| v), var_name, 0); let hash_var = crate::calc_qualified_fn_hash(path.iter().map(|&v| v), var_name, 0);
variables.insert(hash_var, value.clone()); variables.insert(hash_var, value.clone());
}); });
@ -1520,8 +1523,11 @@ impl Module {
calc_native_fn_hash(path.iter().cloned(), f.name.as_str(), &f.param_types); calc_native_fn_hash(path.iter().cloned(), f.name.as_str(), &f.param_types);
functions.insert(hash_qualified_fn, f.func.clone()); functions.insert(hash_qualified_fn, f.func.clone());
} else if cfg!(not(feature = "no_function")) { } else if cfg!(not(feature = "no_function")) {
let hash_qualified_script = let hash_qualified_script = crate::calc_qualified_fn_hash(
crate::calc_fn_hash(path.iter().cloned(), f.name.as_str(), f.params); path.iter().cloned(),
f.name.as_str(),
f.params,
);
functions.insert(hash_qualified_script, f.func.clone()); functions.insert(hash_qualified_script, f.func.clone());
} }
}); });

View File

@ -31,8 +31,8 @@ impl DummyModuleResolver {
/// engine.set_module_resolver(resolver); /// engine.set_module_resolver(resolver);
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn new() -> Self { pub const fn new() -> Self {
Default::default() Self
} }
} }

View File

@ -4,7 +4,6 @@ use crate::ast::{Expr, OpAssignment, Stmt};
use crate::dynamic::AccessMode; use crate::dynamic::AccessMode;
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF}; use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF};
use crate::fn_builtin::get_builtin_binary_op_fn; use crate::fn_builtin::get_builtin_binary_op_fn;
use crate::parser::map_dynamic_to_expr;
use crate::token::Token; use crate::token::Token;
use crate::utils::get_hasher; use crate::utils::get_hasher;
use crate::{ use crate::{
@ -16,7 +15,6 @@ use std::prelude::v1::*;
use std::{ use std::{
any::TypeId, any::TypeId,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
iter::empty,
mem, mem,
}; };
@ -32,24 +30,14 @@ pub enum OptimizationLevel {
Full, Full,
} }
impl OptimizationLevel { impl Default for OptimizationLevel {
/// Is the `OptimizationLevel` [`None`][OptimizationLevel::None]?
#[allow(dead_code)]
#[inline(always)] #[inline(always)]
pub fn is_none(self) -> bool { fn default() -> Self {
self == Self::None if cfg!(feature = "no_optimize") {
Self::None
} else {
Self::Simple
} }
/// Is the `OptimizationLevel` [`Simple`][OptimizationLevel::Simple]?
#[allow(dead_code)]
#[inline(always)]
pub fn is_simple(self) -> bool {
self == Self::Simple
}
/// Is the `OptimizationLevel` [`Full`][OptimizationLevel::Full]?
#[allow(dead_code)]
#[inline(always)]
pub fn is_full(self) -> bool {
self == Self::Full
} }
} }
@ -59,7 +47,7 @@ struct State<'a> {
/// Has the [`AST`] been changed during this pass? /// Has the [`AST`] been changed during this pass?
changed: bool, changed: bool,
/// Collection of constants to use for eager function evaluations. /// Collection of constants to use for eager function evaluations.
variables: Vec<(String, AccessMode, Expr)>, variables: Vec<(String, AccessMode, Option<Dynamic>)>,
/// Activate constants propagation? /// Activate constants propagation?
propagate_constants: bool, propagate_constants: bool,
/// An [`Engine`] instance for eager function evaluation. /// An [`Engine`] instance for eager function evaluation.
@ -109,21 +97,21 @@ impl<'a> State<'a> {
} }
/// Add a new constant to the list. /// Add a new constant to the list.
#[inline(always)] #[inline(always)]
pub fn push_var(&mut self, name: &str, access: AccessMode, value: Expr) { pub fn push_var(&mut self, name: &str, access: AccessMode, value: Option<Dynamic>) {
self.variables.push((name.into(), access, value)) self.variables.push((name.into(), access, value))
} }
/// Look up a constant from the list. /// Look up a constant from the list.
#[inline] #[inline]
pub fn find_constant(&self, name: &str) -> Option<&Expr> { pub fn find_constant(&self, name: &str) -> Option<&Dynamic> {
if !self.propagate_constants { if !self.propagate_constants {
return None; return None;
} }
self.variables.iter().rev().find_map(|(n, access, expr)| { self.variables.iter().rev().find_map(|(n, access, value)| {
if n == name { if n == name {
match access { match access {
AccessMode::ReadWrite => None, AccessMode::ReadWrite => None,
AccessMode::ReadOnly => Some(expr), AccessMode::ReadOnly => value.as_ref(),
} }
} else { } else {
None None
@ -158,7 +146,7 @@ fn call_fn_with_constant_arguments(
&mut Default::default(), &mut Default::default(),
state.lib, state.lib,
fn_name, fn_name,
calc_fn_hash(empty(), fn_name, arg_values.len()), calc_fn_hash(fn_name, arg_values.len()),
arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(), arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(),
false, false,
false, false,
@ -217,13 +205,17 @@ fn optimize_stmt_block(
optimize_expr(value_expr, state); optimize_expr(value_expr, state);
if value_expr.is_constant() { if value_expr.is_constant() {
state.push_var(&x.name, AccessMode::ReadOnly, value_expr.clone()); state.push_var(
&x.name,
AccessMode::ReadOnly,
value_expr.get_constant_value(),
);
} }
} }
// Add variables into the state // Add variables into the state
Stmt::Let(value_expr, x, _, _) => { Stmt::Let(value_expr, x, _, _) => {
optimize_expr(value_expr, state); optimize_expr(value_expr, state);
state.push_var(&x.name, AccessMode::ReadWrite, Expr::Unit(x.pos)); state.push_var(&x.name, AccessMode::ReadWrite, None);
} }
// Optimize the statement // Optimize the statement
_ => optimize_stmt(stmt, state, preserve_result), _ => optimize_stmt(stmt, state, preserve_result),
@ -512,12 +504,13 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
Stmt::Switch(match_expr, x, _) => { Stmt::Switch(match_expr, x, _) => {
optimize_expr(match_expr, state); optimize_expr(match_expr, state);
x.0.values_mut().for_each(|block| { x.0.values_mut().for_each(|block| {
let condition = if let Some(mut condition) = mem::take(&mut block.0) { let condition = mem::take(&mut block.0).map_or_else(
|| Expr::Unit(Position::NONE),
|mut condition| {
optimize_expr(&mut condition, state); optimize_expr(&mut condition, state);
condition condition
} else { },
Expr::Unit(Position::NONE) );
};
match condition { match condition {
Expr::Unit(_) | Expr::BoolConstant(true, _) => (), Expr::Unit(_) | Expr::BoolConstant(true, _) => (),
@ -703,7 +696,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
Expr::Dot(x, _) => match (&mut x.lhs, &mut x.rhs) { Expr::Dot(x, _) => match (&mut x.lhs, &mut x.rhs) {
// map.string // map.string
(Expr::Map(m, pos), Expr::Property(p)) if m.0.iter().all(|(_, x)| x.is_pure()) => { (Expr::Map(m, pos), Expr::Property(p)) if m.0.iter().all(|(_, x)| x.is_pure()) => {
let prop = &p.2.name; let prop = p.2.0.as_str();
// Map literal where everything is pure - promote the indexed item. // Map literal where everything is pure - promote the indexed item.
// All other items can be thrown away. // All other items can be thrown away.
state.set_dirty(); state.set_dirty();
@ -925,15 +918,16 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
// Search for overloaded operators (can override built-in). // Search for overloaded operators (can override built-in).
if !has_native_fn(state, x.hashes.native_hash(), arg_types.as_ref()) { if !has_native_fn(state, x.hashes.native_hash(), arg_types.as_ref()) {
if let Some(result) = get_builtin_binary_op_fn(x.name.as_ref(), &arg_values[0], &arg_values[1]) if let Some(mut result) = get_builtin_binary_op_fn(x.name.as_ref(), &arg_values[0], &arg_values[1])
.and_then(|f| { .and_then(|f| {
let ctx = (state.engine, x.name.as_ref(), state.lib).into(); let ctx = (state.engine, x.name.as_ref(), state.lib).into();
let (first, second) = arg_values.split_first_mut().unwrap(); let (first, second) = arg_values.split_first_mut().unwrap();
(f)(ctx, &mut [ first, &mut second[0] ]).ok() (f)(ctx, &mut [ first, &mut second[0] ]).ok()
}) })
.and_then(|result| map_dynamic_to_expr(result, *pos)) .map(Expr::from)
{ {
state.set_dirty(); state.set_dirty();
result.set_position(*pos);
*expr = result; *expr = result;
return; return;
} }
@ -977,7 +971,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
"" ""
}; };
if let Some(result) = call_fn_with_constant_arguments(&state, x.name.as_ref(), &mut arg_values) if let Some(mut result) = call_fn_with_constant_arguments(&state, x.name.as_ref(), &mut arg_values)
.or_else(|| { .or_else(|| {
if !arg_for_type_of.is_empty() { if !arg_for_type_of.is_empty() {
// Handle `type_of()` // Handle `type_of()`
@ -986,9 +980,10 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
None None
} }
}) })
.and_then(|result| map_dynamic_to_expr(result, *pos)) .map(Expr::from)
{ {
state.set_dirty(); state.set_dirty();
result.set_position(*pos);
*expr = result; *expr = result;
return; return;
} }
@ -1014,12 +1009,11 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
// constant-name // constant-name
Expr::Variable(_, pos, x) if x.1.is_none() && state.find_constant(&x.2).is_some() => { Expr::Variable(_, pos, x) if x.1.is_none() && state.find_constant(&x.2).is_some() => {
state.set_dirty();
// Replace constant with value // Replace constant with value
let mut result = state.find_constant(&x.2).unwrap().clone(); let pos = *pos;
result.set_position(*pos); *expr = Expr::from(state.find_constant(&x.2).unwrap().clone());
*expr = result; expr.set_position(pos);
state.set_dirty();
} }
// Custom syntax // Custom syntax
@ -1055,13 +1049,9 @@ fn optimize_top_level(
// Add constants and variables from the scope // Add constants and variables from the scope
scope.iter().for_each(|(name, constant, value)| { scope.iter().for_each(|(name, constant, value)| {
if !constant { if !constant {
state.push_var(name, AccessMode::ReadWrite, Expr::Unit(Position::NONE)); state.push_var(name, AccessMode::ReadWrite, None);
} else { } else {
state.push_var( state.push_var(name, AccessMode::ReadOnly, Some(value));
name,
AccessMode::ReadOnly,
Expr::DynamicConstant(Box::new(value), Position::NONE),
);
} }
}); });
@ -1078,7 +1068,7 @@ pub fn optimize_into_ast(
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> AST { ) -> AST {
let level = if cfg!(feature = "no_optimize") { let level = if cfg!(feature = "no_optimize") {
OptimizationLevel::None Default::default()
} else { } else {
optimization_level optimization_level
}; };
@ -1087,7 +1077,7 @@ pub fn optimize_into_ast(
let lib = { let lib = {
let mut module = Module::new(); let mut module = Module::new();
if !level.is_none() { if level != OptimizationLevel::None {
// We only need the script library's signatures for optimization purposes // We only need the script library's signatures for optimization purposes
let mut lib2 = Module::new(); let mut lib2 = Module::new();

View File

@ -604,27 +604,13 @@ mod array_functions {
.call_dynamic(&ctx, None, [x.clone(), y.clone()]) .call_dynamic(&ctx, None, [x.clone(), y.clone()])
.ok() .ok()
.and_then(|v| v.as_int().ok()) .and_then(|v| v.as_int().ok())
.map(|v| { .map(|v| match v {
if v > 0 { v if v > 0 => Ordering::Greater,
Ordering::Greater v if v < 0 => Ordering::Less,
} else if v < 0 { 0 => Ordering::Equal,
Ordering::Less _ => unreachable!(),
} else {
Ordering::Equal
}
})
.unwrap_or_else(|| {
let x_type_id = x.type_id();
let y_type_id = y.type_id();
if x_type_id > y_type_id {
Ordering::Greater
} else if x_type_id < y_type_id {
Ordering::Less
} else {
Ordering::Equal
}
}) })
.unwrap_or_else(|| x.type_id().cmp(&y.type_id()))
}); });
Ok(()) Ok(())

View File

@ -45,29 +45,31 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
namespace: Option<Identifier>, namespace: Option<Identifier>,
f: &ScriptFnDef, f: &ScriptFnDef,
) -> Map { ) -> Map {
const DICT: &str = "never fails because the dictionary is pre-filled with all the keys";
let mut map = Map::new(); let mut map = Map::new();
if let Some(ns) = namespace { if let Some(ns) = namespace {
map.insert(dict.get("namespace").unwrap().clone().into(), ns.into()); map.insert(dict.get("namespace").expect(DICT).clone().into(), ns.into());
} }
map.insert( map.insert(
dict.get("name").unwrap().clone().into(), dict.get("name").expect(DICT).clone().into(),
f.name.clone().into(), f.name.clone().into(),
); );
map.insert( map.insert(
dict.get("access").unwrap().clone().into(), dict.get("access").expect(DICT).clone().into(),
match f.access { match f.access {
FnAccess::Public => dict.get("public").unwrap().clone(), FnAccess::Public => dict.get("public").expect(DICT).clone(),
FnAccess::Private => dict.get("private").unwrap().clone(), FnAccess::Private => dict.get("private").expect(DICT).clone(),
} }
.into(), .into(),
); );
map.insert( map.insert(
dict.get("is_anonymous").unwrap().clone().into(), dict.get("is_anonymous").expect(DICT).clone().into(),
f.name.starts_with(crate::engine::FN_ANONYMOUS).into(), f.name.starts_with(crate::engine::FN_ANONYMOUS).into(),
); );
map.insert( map.insert(
dict.get("params").unwrap().clone().into(), dict.get("params").expect(DICT).clone().into(),
f.params f.params
.iter() .iter()
.cloned() .cloned()

View File

@ -311,21 +311,14 @@ mod decimal_functions {
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn sqrt(x: Decimal) -> Result<Decimal, Box<EvalAltResult>> { pub fn sqrt(x: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) {
x.sqrt() x.sqrt()
.ok_or_else(|| make_err(format!("Error taking the square root of {}", x,))) .ok_or_else(|| make_err(format!("Error taking the square root of {}", x,)))
} else {
Ok(x.sqrt().unwrap())
}
} }
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn exp(x: Decimal) -> Result<Decimal, Box<EvalAltResult>> { pub fn exp(x: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
if x > Decimal::from_parts(117578, 0, 0, false, 4) { x.checked_exp()
Err(make_err(format!("Exponential overflow: e ** {}", x,))) .ok_or_else(|| make_err(format!("Exponential overflow: e ** {}", x,)))
} else {
Ok(x.exp())
}
} else { } else {
Ok(x.exp()) Ok(x.exp())
} }
@ -348,15 +341,15 @@ mod decimal_functions {
#[rhai_fn(name = "round", return_raw)] #[rhai_fn(name = "round", return_raw)]
pub fn round_dp(x: Decimal, dp: INT) -> Result<Decimal, Box<EvalAltResult>> { pub fn round_dp(x: Decimal, dp: INT) -> Result<Decimal, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) {
return Ok(x);
}
if dp < 0 { if dp < 0 {
return Err(make_err(format!( return Err(make_err(format!(
"Decimal value {} round to a negative index: {}", "Invalid number of digits for rounding: {}",
x, dp dp
))); )));
} }
if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) {
return Ok(x);
}
} }
Ok(x.round_dp(dp as u32)) Ok(x.round_dp(dp as u32))
@ -364,15 +357,15 @@ mod decimal_functions {
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn round_up(x: Decimal, dp: INT) -> Result<Decimal, Box<EvalAltResult>> { pub fn round_up(x: Decimal, dp: INT) -> Result<Decimal, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) {
return Ok(x);
}
if dp < 0 { if dp < 0 {
return Err(make_err(format!( return Err(make_err(format!(
"Decimal value {} round to a negative index: {}", "Invalid number of digits for rounding: {}",
x, dp dp
))); )));
} }
if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) {
return Ok(x);
}
} }
Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::AwayFromZero)) Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::AwayFromZero))
@ -380,15 +373,15 @@ mod decimal_functions {
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn round_down(x: Decimal, dp: INT) -> Result<Decimal, Box<EvalAltResult>> { pub fn round_down(x: Decimal, dp: INT) -> Result<Decimal, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) {
return Ok(x);
}
if dp < 0 { if dp < 0 {
return Err(make_err(format!( return Err(make_err(format!(
"Decimal value {} round to a negative index: {}", "Invalid number of digits for rounding: {}",
x, dp dp
))); )));
} }
if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) {
return Ok(x);
}
} }
Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::ToZero)) Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::ToZero))
@ -396,15 +389,15 @@ mod decimal_functions {
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn round_half_up(x: Decimal, dp: INT) -> Result<Decimal, Box<EvalAltResult>> { pub fn round_half_up(x: Decimal, dp: INT) -> Result<Decimal, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) {
return Ok(x);
}
if dp < 0 { if dp < 0 {
return Err(make_err(format!( return Err(make_err(format!(
"Decimal value {} round to a negative index: {}", "Invalid number of digits for rounding: {}",
x, dp dp
))); )));
} }
if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) {
return Ok(x);
}
} }
Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::MidpointAwayFromZero)) Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::MidpointAwayFromZero))
@ -412,15 +405,15 @@ mod decimal_functions {
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn round_half_down(x: Decimal, dp: INT) -> Result<Decimal, Box<EvalAltResult>> { pub fn round_half_down(x: Decimal, dp: INT) -> Result<Decimal, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) {
return Ok(x);
}
if dp < 0 { if dp < 0 {
return Err(make_err(format!( return Err(make_err(format!(
"Decimal value {} round to a negative index: {}", "Invalid number of digits for rounding: {}",
x, dp dp
))); )));
} }
if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) {
return Ok(x);
}
} }
Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::MidpointTowardZero)) Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::MidpointTowardZero))

View File

@ -27,9 +27,9 @@ pub fn print_with_func(
value: &mut Dynamic, value: &mut Dynamic,
) -> crate::ImmutableString { ) -> crate::ImmutableString {
match ctx.call_fn_dynamic_raw(fn_name, true, &mut [value]) { match ctx.call_fn_dynamic_raw(fn_name, true, &mut [value]) {
Ok(result) if result.is::<crate::ImmutableString>() => { Ok(result) if result.is::<crate::ImmutableString>() => result
result.take_immutable_string().unwrap() .take_immutable_string()
} .expect("never fails as the result is `ImmutableString`"),
Ok(result) => ctx.engine().map_type_name(result.type_name()).into(), Ok(result) => ctx.engine().map_type_name(result.type_name()).into(),
Err(_) => ctx.engine().map_type_name(value.type_name()).into(), Err(_) => ctx.engine().map_type_name(value.type_name()).into(),
} }

View File

@ -15,15 +15,14 @@ use crate::token::{
}; };
use crate::utils::{get_hasher, IdentifierBuilder}; use crate::utils::{get_hasher, IdentifierBuilder};
use crate::{ use crate::{
calc_fn_hash, Dynamic, Engine, FnPtr, Identifier, LexError, ParseError, ParseErrorType, calc_fn_hash, calc_qualified_fn_hash, Dynamic, Engine, FnPtr, Identifier, LexError, ParseError,
Position, Scope, Shared, StaticVec, AST, ParseErrorType, Position, Scope, Shared, StaticVec, AST,
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
iter::empty,
num::{NonZeroU8, NonZeroUsize}, num::{NonZeroU8, NonZeroUsize},
}; };
@ -37,6 +36,8 @@ type PERR = ParseErrorType;
type FunctionsLib = BTreeMap<u64, Shared<ScriptFnDef>>; type FunctionsLib = BTreeMap<u64, Shared<ScriptFnDef>>;
const NEVER_ENDS: &str = "never fails because `TokenStream` never ends";
/// A type that encapsulates the current state of the parser. /// A type that encapsulates the current state of the parser.
#[derive(Debug)] #[derive(Debug)]
pub struct ParseState<'e> { pub struct ParseState<'e> {
@ -206,11 +207,9 @@ impl ParseSettings {
&self, &self,
limit: Option<NonZeroUsize>, limit: Option<NonZeroUsize>,
) -> Result<(), ParseError> { ) -> Result<(), ParseError> {
if let Some(limit) = limit { if limit.map(|limit| self.level > limit.get()).unwrap_or(false) {
if self.level > limit.get() {
return Err(PERR::ExprTooDeep.into_err(self.pos)); return Err(PERR::ExprTooDeep.into_err(self.pos));
} }
}
Ok(()) Ok(())
} }
} }
@ -225,17 +224,14 @@ impl Expr {
Self::Variable(_, pos, x) if x.1.is_none() => { Self::Variable(_, pos, x) if x.1.is_none() => {
let ident = x.2; let ident = x.2;
let getter = state.get_identifier(crate::engine::make_getter(&ident)); let getter = state.get_identifier(crate::engine::make_getter(&ident));
let hash_get = calc_fn_hash(empty(), &getter, 1); let hash_get = calc_fn_hash(&getter, 1);
let setter = state.get_identifier(crate::engine::make_setter(&ident)); let setter = state.get_identifier(crate::engine::make_setter(&ident));
let hash_set = calc_fn_hash(empty(), &setter, 2); let hash_set = calc_fn_hash(&setter, 2);
Self::Property(Box::new(( Self::Property(Box::new((
(getter, hash_get), (getter, hash_get),
(setter, hash_set), (setter, hash_set),
Ident { (state.get_identifier(ident).into(), pos),
name: state.get_identifier(ident),
pos,
},
))) )))
} }
_ => self, _ => self,
@ -245,7 +241,7 @@ impl Expr {
/// Consume a particular [token][Token], checking that it is the expected one. /// Consume a particular [token][Token], checking that it is the expected one.
fn eat_token(input: &mut TokenStream, token: Token) -> Position { fn eat_token(input: &mut TokenStream, token: Token) -> Position {
let (t, pos) = input.next().unwrap(); let (t, pos) = input.next().expect(NEVER_ENDS);
if t != token { if t != token {
unreachable!( unreachable!(
@ -260,7 +256,7 @@ fn eat_token(input: &mut TokenStream, token: Token) -> Position {
/// Match a particular [token][Token], consuming it if matched. /// Match a particular [token][Token], consuming it if matched.
fn match_token(input: &mut TokenStream, token: Token) -> (bool, Position) { fn match_token(input: &mut TokenStream, token: Token) -> (bool, Position) {
let (t, pos) = input.peek().unwrap(); let (t, pos) = input.peek().expect(NEVER_ENDS);
if *t == token { if *t == token {
(true, eat_token(input, token)) (true, eat_token(input, token))
} else { } else {
@ -287,7 +283,7 @@ fn parse_paren_expr(
let expr = parse_expr(input, state, lib, settings.level_up())?; let expr = parse_expr(input, state, lib, settings.level_up())?;
match input.next().unwrap() { match input.next().expect(NEVER_ENDS) {
// ( xxx ) // ( xxx )
(Token::RightParen, _) => Ok(expr), (Token::RightParen, _) => Ok(expr),
// ( <error> // ( <error>
@ -314,7 +310,7 @@ fn parse_fn_call(
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let (token, token_pos) = input.peek().unwrap(); let (token, token_pos) = input.peek().expect(NEVER_ENDS);
let mut args = StaticVec::new(); let mut args = StaticVec::new();
@ -333,14 +329,15 @@ fn parse_fn_call(
Token::RightParen => { Token::RightParen => {
eat_token(input, Token::RightParen); eat_token(input, Token::RightParen);
let hash = if let Some(ref mut modules) = namespace { let hash = namespace.as_mut().map_or_else(
|| calc_fn_hash(&id, 0),
|modules| {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
modules.set_index(state.find_module(&modules[0].name)); modules.set_index(state.find_module(&modules[0].name));
calc_fn_hash(modules.iter().map(|m| m.name.as_str()), &id, 0) calc_qualified_fn_hash(modules.iter().map(|m| m.name.as_str()), &id, 0)
} else { },
calc_fn_hash(empty(), &id, 0) );
};
let hashes = if is_valid_identifier(id.chars()) { let hashes = if is_valid_identifier(id.chars()) {
FnCallHashes::from_script(hash) FnCallHashes::from_script(hash)
@ -369,25 +366,30 @@ fn parse_fn_call(
let settings = settings.level_up(); let settings = settings.level_up();
loop { loop {
match input.peek().unwrap() { match input.peek().expect(NEVER_ENDS) {
// id(...args, ) - handle trailing comma // id(...args, ) - handle trailing comma
(Token::RightParen, _) => (), (Token::RightParen, _) => (),
_ => args.push(parse_expr(input, state, lib, settings)?), _ => args.push(parse_expr(input, state, lib, settings)?),
} }
match input.peek().unwrap() { match input.peek().expect(NEVER_ENDS) {
// id(...args) // id(...args)
(Token::RightParen, _) => { (Token::RightParen, _) => {
eat_token(input, Token::RightParen); eat_token(input, Token::RightParen);
let hash = if let Some(ref mut modules) = namespace { let hash = namespace.as_mut().map_or_else(
|| calc_fn_hash(&id, args.len()),
|modules| {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
modules.set_index(state.find_module(&modules[0].name)); modules.set_index(state.find_module(&modules[0].name));
calc_fn_hash(modules.iter().map(|m| m.name.as_str()), &id, args.len()) calc_qualified_fn_hash(
} else { modules.iter().map(|m| m.name.as_str()),
calc_fn_hash(empty(), &id, args.len()) &id,
}; args.len(),
)
},
);
let hashes = if is_valid_identifier(id.chars()) { let hashes = if is_valid_identifier(id.chars()) {
FnCallHashes::from_script(hash) FnCallHashes::from_script(hash)
@ -558,12 +560,12 @@ fn parse_index_chain(
} }
// Check if there is a closing bracket // Check if there is a closing bracket
match input.peek().unwrap() { match input.peek().expect(NEVER_ENDS) {
(Token::RightBracket, _) => { (Token::RightBracket, _) => {
eat_token(input, Token::RightBracket); eat_token(input, Token::RightBracket);
// Any more indexing following? // Any more indexing following?
match input.peek().unwrap() { match input.peek().expect(NEVER_ENDS) {
// If another indexing level, right-bind it // If another indexing level, right-bind it
(Token::LeftBracket, _) => { (Token::LeftBracket, _) => {
let prev_pos = settings.pos; let prev_pos = settings.pos;
@ -618,10 +620,10 @@ fn parse_array_literal(
"Size of array literal".to_string(), "Size of array literal".to_string(),
state.engine.max_array_size(), state.engine.max_array_size(),
) )
.into_err(input.peek().unwrap().1)); .into_err(input.peek().expect(NEVER_ENDS).1));
} }
match input.peek().unwrap() { match input.peek().expect(NEVER_ENDS) {
(Token::RightBracket, _) => { (Token::RightBracket, _) => {
eat_token(input, Token::RightBracket); eat_token(input, Token::RightBracket);
break; break;
@ -638,7 +640,7 @@ fn parse_array_literal(
} }
} }
match input.peek().unwrap() { match input.peek().expect(NEVER_ENDS) {
(Token::Comma, _) => { (Token::Comma, _) => {
eat_token(input, Token::Comma); eat_token(input, Token::Comma);
} }
@ -685,7 +687,7 @@ fn parse_map_literal(
loop { loop {
const MISSING_RBRACE: &str = "to end this object map literal"; const MISSING_RBRACE: &str = "to end this object map literal";
match input.peek().unwrap() { match input.peek().expect(NEVER_ENDS) {
(Token::RightBrace, _) => { (Token::RightBrace, _) => {
eat_token(input, Token::RightBrace); eat_token(input, Token::RightBrace);
break; break;
@ -699,7 +701,7 @@ fn parse_map_literal(
_ => (), _ => (),
} }
let (name, pos) = match input.next().unwrap() { let (name, pos) = match input.next().expect(NEVER_ENDS) {
(Token::Identifier(s), pos) | (Token::StringConstant(s), pos) => { (Token::Identifier(s), pos) | (Token::StringConstant(s), pos) => {
if map.iter().any(|(p, _)| p.name == s) { if map.iter().any(|(p, _)| p.name == s) {
return Err(PERR::DuplicatedProperty(s).into_err(pos)); return Err(PERR::DuplicatedProperty(s).into_err(pos));
@ -726,7 +728,7 @@ fn parse_map_literal(
(_, pos) => return Err(PERR::PropertyExpected.into_err(pos)), (_, pos) => return Err(PERR::PropertyExpected.into_err(pos)),
}; };
match input.next().unwrap() { match input.next().expect(NEVER_ENDS) {
(Token::Colon, _) => (), (Token::Colon, _) => (),
(Token::LexError(err), pos) => return Err(err.into_err(pos)), (Token::LexError(err), pos) => return Err(err.into_err(pos)),
(_, pos) => { (_, pos) => {
@ -747,7 +749,7 @@ fn parse_map_literal(
"Number of properties in object map literal".to_string(), "Number of properties in object map literal".to_string(),
state.engine.max_map_size(), state.engine.max_map_size(),
) )
.into_err(input.peek().unwrap().1)); .into_err(input.peek().expect(NEVER_ENDS).1));
} }
let expr = parse_expr(input, state, lib, settings.level_up())?; let expr = parse_expr(input, state, lib, settings.level_up())?;
@ -755,7 +757,7 @@ fn parse_map_literal(
template.insert(name.clone().into(), Default::default()); template.insert(name.clone().into(), Default::default());
map.push((Ident { name, pos }, expr)); map.push((Ident { name, pos }, expr));
match input.peek().unwrap() { match input.peek().expect(NEVER_ENDS) {
(Token::Comma, _) => { (Token::Comma, _) => {
eat_token(input, Token::Comma); eat_token(input, Token::Comma);
} }
@ -797,7 +799,7 @@ fn parse_switch(
let item = parse_expr(input, state, lib, settings.level_up())?; let item = parse_expr(input, state, lib, settings.level_up())?;
match input.next().unwrap() { match input.next().expect(NEVER_ENDS) {
(Token::LeftBrace, _) => (), (Token::LeftBrace, _) => (),
(Token::LexError(err), pos) => return Err(err.into_err(pos)), (Token::LexError(err), pos) => return Err(err.into_err(pos)),
(_, pos) => { (_, pos) => {
@ -816,7 +818,7 @@ fn parse_switch(
loop { loop {
const MISSING_RBRACE: &str = "to end this switch block"; const MISSING_RBRACE: &str = "to end this switch block";
let (expr, condition) = match input.peek().unwrap() { let (expr, condition) = match input.peek().expect(NEVER_ENDS) {
(Token::RightBrace, _) => { (Token::RightBrace, _) => {
eat_token(input, Token::RightBrace); eat_token(input, Token::RightBrace);
break; break;
@ -872,7 +874,7 @@ fn parse_switch(
None None
}; };
match input.next().unwrap() { match input.next().expect(NEVER_ENDS) {
(Token::DoubleArrow, _) => (), (Token::DoubleArrow, _) => (),
(Token::LexError(err), pos) => return Err(err.into_err(pos)), (Token::LexError(err), pos) => return Err(err.into_err(pos)),
(_, pos) => { (_, pos) => {
@ -895,7 +897,7 @@ fn parse_switch(
Some(stmt.into()) Some(stmt.into())
}; };
match input.peek().unwrap() { match input.peek().expect(NEVER_ENDS) {
(Token::Comma, _) => { (Token::Comma, _) => {
eat_token(input, Token::Comma); eat_token(input, Token::Comma);
} }
@ -938,7 +940,7 @@ fn parse_primary(
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let (token, token_pos) = input.peek().unwrap(); let (token, token_pos) = input.peek().expect(NEVER_ENDS);
settings.pos = *token_pos; settings.pos = *token_pos;
let mut root_expr = match token { let mut root_expr = match token {
@ -948,7 +950,7 @@ fn parse_primary(
| Token::CharConstant(_) | Token::CharConstant(_)
| Token::StringConstant(_) | Token::StringConstant(_)
| Token::True | Token::True
| Token::False => match input.next().unwrap().0 { | Token::False => match input.next().expect(NEVER_ENDS).0 {
Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos), Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos),
Token::CharConstant(c) => Expr::CharConstant(c, settings.pos), Token::CharConstant(c) => Expr::CharConstant(c, settings.pos),
Token::StringConstant(s) => { Token::StringConstant(s) => {
@ -961,13 +963,13 @@ fn parse_primary(
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Token::FloatConstant(x) => { Token::FloatConstant(x) => {
let x = (*x).into(); let x = (*x).into();
input.next().unwrap(); input.next().expect(NEVER_ENDS);
Expr::FloatConstant(x, settings.pos) Expr::FloatConstant(x, settings.pos)
} }
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
Token::DecimalConstant(x) => { Token::DecimalConstant(x) => {
let x = (*x).into(); let x = (*x).into();
input.next().unwrap(); input.next().expect(NEVER_ENDS);
Expr::DynamicConstant(Box::new(x), settings.pos) Expr::DynamicConstant(Box::new(x), settings.pos)
} }
@ -1019,7 +1021,7 @@ fn parse_primary(
state.access_var(closure, *pos); state.access_var(closure, *pos);
}); });
let hash_script = calc_fn_hash(empty(), &func.name, func.params.len()); let hash_script = calc_fn_hash(&func.name, func.params.len());
lib.insert(hash_script, func.into()); lib.insert(hash_script, func.into());
expr expr
@ -1029,7 +1031,7 @@ fn parse_primary(
Token::InterpolatedString(_) => { Token::InterpolatedString(_) => {
let mut segments: StaticVec<Expr> = Default::default(); let mut segments: StaticVec<Expr> = Default::default();
if let (Token::InterpolatedString(s), pos) = input.next().unwrap() { if let (Token::InterpolatedString(s), pos) = input.next().expect(NEVER_ENDS) {
segments.push(Expr::StringConstant(s.into(), pos)); segments.push(Expr::StringConstant(s.into(), pos));
} else { } else {
unreachable!(); unreachable!();
@ -1047,7 +1049,7 @@ fn parse_primary(
control.is_within_text = true; control.is_within_text = true;
state.tokenizer_control.set(control); state.tokenizer_control.set(control);
match input.next().unwrap() { match input.next().expect(NEVER_ENDS) {
(Token::StringConstant(s), pos) => { (Token::StringConstant(s), pos) => {
if !s.is_empty() { if !s.is_empty() {
segments.push(Expr::StringConstant(s.into(), pos)); segments.push(Expr::StringConstant(s.into(), pos));
@ -1084,12 +1086,12 @@ fn parse_primary(
// Identifier // Identifier
Token::Identifier(_) => { Token::Identifier(_) => {
let s = match input.next().unwrap() { let s = match input.next().expect(NEVER_ENDS) {
(Token::Identifier(s), _) => s, (Token::Identifier(s), _) => s,
_ => unreachable!(), _ => unreachable!(),
}; };
match input.peek().unwrap().0 { match input.peek().expect(NEVER_ENDS).0 {
// Function call // Function call
Token::LeftParen | Token::Bang => { Token::LeftParen | Token::Bang => {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
@ -1138,12 +1140,12 @@ fn parse_primary(
// Reserved keyword or symbol // Reserved keyword or symbol
Token::Reserved(_) => { Token::Reserved(_) => {
let s = match input.next().unwrap() { let s = match input.next().expect(NEVER_ENDS) {
(Token::Reserved(s), _) => s, (Token::Reserved(s), _) => s,
_ => unreachable!(), _ => unreachable!(),
}; };
match input.peek().unwrap().0 { match input.peek().expect(NEVER_ENDS).0 {
// Function call is allowed to have reserved keyword // Function call is allowed to have reserved keyword
Token::LeftParen | Token::Bang if is_keyword_function(&s) => Expr::Variable( Token::LeftParen | Token::Bang if is_keyword_function(&s) => Expr::Variable(
None, None,
@ -1168,7 +1170,7 @@ fn parse_primary(
} }
} }
Token::LexError(_) => match input.next().unwrap() { Token::LexError(_) => match input.next().expect(NEVER_ENDS) {
(Token::LexError(err), _) => return Err(err.into_err(settings.pos)), (Token::LexError(err), _) => return Err(err.into_err(settings.pos)),
_ => unreachable!(), _ => unreachable!(),
}, },
@ -1180,13 +1182,13 @@ fn parse_primary(
// Tail processing all possible postfix operators // Tail processing all possible postfix operators
loop { loop {
let (tail_token, _) = input.peek().unwrap(); let (tail_token, _) = input.peek().expect(NEVER_ENDS);
if !root_expr.is_valid_postfix(tail_token) { if !root_expr.is_valid_postfix(tail_token) {
break; break;
} }
let (tail_token, tail_pos) = input.next().unwrap(); let (tail_token, tail_pos) = input.next().expect(NEVER_ENDS);
settings.pos = tail_pos; settings.pos = tail_pos;
root_expr = match (root_expr, tail_token) { root_expr = match (root_expr, tail_token) {
@ -1226,7 +1228,8 @@ fn parse_primary(
parse_fn_call(input, state, lib, name, false, ns, settings.level_up())? parse_fn_call(input, state, lib, name, false, ns, settings.level_up())?
} }
// module access // module access
(Expr::Variable(_, var_pos, x), Token::DoubleColon) => match input.next().unwrap() { (Expr::Variable(_, var_pos, x), Token::DoubleColon) => {
match input.next().expect(NEVER_ENDS) {
(Token::Identifier(id2), pos2) => { (Token::Identifier(id2), pos2) => {
let (_, mut namespace, var_name) = *x; let (_, mut namespace, var_name) = *x;
let var_name_def = Ident { let var_name_def = Ident {
@ -1252,7 +1255,8 @@ fn parse_primary(
return Err(PERR::Reserved(id2).into_err(pos2)); return Err(PERR::Reserved(id2).into_err(pos2));
} }
(_, pos2) => return Err(PERR::VariableExpected.into_err(pos2)), (_, pos2) => return Err(PERR::VariableExpected.into_err(pos2)),
}, }
}
// Indexing // Indexing
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
(expr, Token::LeftBracket) => { (expr, Token::LeftBracket) => {
@ -1262,7 +1266,7 @@ fn parse_primary(
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
(expr, Token::Period) => { (expr, Token::Period) => {
// Expression after dot must start with an identifier // Expression after dot must start with an identifier
match input.peek().unwrap() { match input.peek().expect(NEVER_ENDS) {
(Token::Identifier(_), _) => { (Token::Identifier(_), _) => {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
{ {
@ -1298,7 +1302,7 @@ fn parse_primary(
} }
.map(|x| match x.as_mut() { .map(|x| match x.as_mut() {
(_, Some((hash, namespace)), name) => { (_, Some((hash, namespace)), name) => {
*hash = calc_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0); *hash = calc_qualified_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0);
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
namespace.set_index(state.find_module(&namespace[0].name)); namespace.set_index(state.find_module(&namespace[0].name));
@ -1317,7 +1321,7 @@ fn parse_unary(
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
mut settings: ParseSettings, mut settings: ParseSettings,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
let (token, token_pos) = input.peek().unwrap(); let (token, token_pos) = input.peek().expect(NEVER_ENDS);
settings.pos = *token_pos; settings.pos = *token_pos;
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -1354,7 +1358,7 @@ fn parse_unary(
Ok(Expr::FnCall( Ok(Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
name: state.get_identifier("-"), name: state.get_identifier("-"),
hashes: FnCallHashes::from_native(calc_fn_hash(empty(), "-", 1)), hashes: FnCallHashes::from_native(calc_fn_hash("-", 1)),
args, args,
..Default::default() ..Default::default()
}), }),
@ -1381,7 +1385,7 @@ fn parse_unary(
Ok(Expr::FnCall( Ok(Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
name: state.get_identifier("+"), name: state.get_identifier("+"),
hashes: FnCallHashes::from_native(calc_fn_hash(empty(), "+", 1)), hashes: FnCallHashes::from_native(calc_fn_hash("+", 1)),
args, args,
..Default::default() ..Default::default()
}), }),
@ -1400,7 +1404,7 @@ fn parse_unary(
Ok(Expr::FnCall( Ok(Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
name: state.get_identifier("!"), name: state.get_identifier("!"),
hashes: FnCallHashes::from_native(calc_fn_hash(empty(), "!", 1)), hashes: FnCallHashes::from_native(calc_fn_hash("!", 1)),
args, args,
..Default::default() ..Default::default()
}), }),
@ -1453,7 +1457,16 @@ fn make_assignment_stmt<'a>(
// var (indexed) = rhs // var (indexed) = rhs
Expr::Variable(i, var_pos, x) => { Expr::Variable(i, var_pos, x) => {
let (index, _, name) = x.as_ref(); let (index, _, name) = x.as_ref();
let index = i.map_or_else(|| index.unwrap().get(), |n| n.get() as usize); let index = i.map_or_else(
|| {
index
.expect(
"never fails because the long index is `Some` when the short index is `None`",
)
.get()
},
|n| n.get() as usize,
);
match state.stack[state.stack.len() - index].1 { match state.stack[state.stack.len() - index].1 {
AccessMode::ReadWrite => { AccessMode::ReadWrite => {
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos)) Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos))
@ -1502,12 +1515,15 @@ fn parse_op_assignment_stmt(
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let (token, token_pos) = input.peek().unwrap(); let (token, token_pos) = input.peek().expect(NEVER_ENDS);
settings.pos = *token_pos; settings.pos = *token_pos;
let (op, pos) = match token { let (op, pos) = match token {
Token::Equals => (None, input.next().unwrap().1), Token::Equals => (None, input.next().expect(NEVER_ENDS).1),
_ if token.is_op_assignment() => input.next().map(|(op, pos)| (Some(op), pos)).unwrap(), _ if token.is_op_assignment() => input
.next()
.map(|(op, pos)| (Some(op), pos))
.expect(NEVER_ENDS),
_ => return Ok(Stmt::Expr(lhs)), _ => return Ok(Stmt::Expr(lhs)),
}; };
@ -1534,24 +1550,22 @@ fn make_dot_expr(
(lhs, Expr::Variable(_, var_pos, x)) if x.1.is_none() => { (lhs, Expr::Variable(_, var_pos, x)) if x.1.is_none() => {
let ident = x.2; let ident = x.2;
let getter = state.get_identifier(crate::engine::make_getter(&ident)); let getter = state.get_identifier(crate::engine::make_getter(&ident));
let hash_get = calc_fn_hash(empty(), &getter, 1); let hash_get = calc_fn_hash(&getter, 1);
let setter = state.get_identifier(crate::engine::make_setter(&ident)); let setter = state.get_identifier(crate::engine::make_setter(&ident));
let hash_set = calc_fn_hash(empty(), &setter, 2); let hash_set = calc_fn_hash(&setter, 2);
let rhs = Expr::Property(Box::new(( let rhs = Expr::Property(Box::new((
(getter, hash_get), (getter, hash_get),
(setter, hash_set), (setter, hash_set),
Ident { (state.get_identifier(ident).into(), var_pos),
name: state.get_identifier(ident),
pos: var_pos,
},
))); )));
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
} }
// lhs.module::id - syntax error // lhs.module::id - syntax error
(_, Expr::Variable(_, _, x)) if x.1.is_some() => { (_, Expr::Variable(_, _, x)) if x.1.is_some() => {
return Err(PERR::PropertyExpected.into_err(x.1.unwrap().1[0].pos)) return Err(PERR::PropertyExpected
.into_err(x.1.expect("never fails because the namespace is `Some`").1[0].pos))
} }
// lhs.prop // lhs.prop
(lhs, prop @ Expr::Property(_)) => { (lhs, prop @ Expr::Property(_)) => {
@ -1572,8 +1586,8 @@ fn make_dot_expr(
Expr::FnCall(mut func, func_pos) => { Expr::FnCall(mut func, func_pos) => {
// Recalculate hash // Recalculate hash
func.hashes = FnCallHashes::from_script_and_native( func.hashes = FnCallHashes::from_script_and_native(
calc_fn_hash(empty(), &func.name, func.args_count()), calc_fn_hash(&func.name, func.args_count()),
calc_fn_hash(empty(), &func.name, func.args_count() + 1), calc_fn_hash(&func.name, func.args_count() + 1),
); );
let rhs = Expr::Dot( let rhs = Expr::Dot(
@ -1628,8 +1642,8 @@ fn make_dot_expr(
(lhs, Expr::FnCall(mut func, func_pos)) => { (lhs, Expr::FnCall(mut func, func_pos)) => {
// Recalculate hash // Recalculate hash
func.hashes = FnCallHashes::from_script_and_native( func.hashes = FnCallHashes::from_script_and_native(
calc_fn_hash(empty(), &func.name, func.args_count()), calc_fn_hash(&func.name, func.args_count()),
calc_fn_hash(empty(), &func.name, func.args_count() + 1), calc_fn_hash(&func.name, func.args_count() + 1),
); );
let rhs = Expr::FnCall(func, func_pos); let rhs = Expr::FnCall(func, func_pos);
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
@ -1656,7 +1670,7 @@ fn parse_binary_op(
let mut root = lhs; let mut root = lhs;
loop { loop {
let (current_op, current_pos) = input.peek().unwrap(); let (current_op, current_pos) = input.peek().expect(NEVER_ENDS);
let precedence = match current_op { let precedence = match current_op {
Token::Custom(c) => state Token::Custom(c) => state
.engine .engine
@ -1677,11 +1691,11 @@ fn parse_binary_op(
return Ok(root); return Ok(root);
} }
let (op_token, pos) = input.next().unwrap(); let (op_token, pos) = input.next().expect(NEVER_ENDS);
let rhs = parse_unary(input, state, lib, settings)?; let rhs = parse_unary(input, state, lib, settings)?;
let (next_op, next_pos) = input.peek().unwrap(); let (next_op, next_pos) = input.peek().expect(NEVER_ENDS);
let next_precedence = match next_op { let next_precedence = match next_op {
Token::Custom(c) => state Token::Custom(c) => state
.engine .engine
@ -1711,7 +1725,7 @@ fn parse_binary_op(
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let op = op_token.syntax(); let op = op_token.syntax();
let hash = calc_fn_hash(empty(), &op, 2); let hash = calc_fn_hash(&op, 2);
let op_base = FnCallExpr { let op_base = FnCallExpr {
name: state.get_identifier(op.as_ref()), name: state.get_identifier(op.as_ref()),
@ -1751,8 +1765,12 @@ fn parse_binary_op(
} }
Token::Or => { Token::Or => {
let rhs = args.pop().unwrap(); let rhs = args
let current_lhs = args.pop().unwrap(); .pop()
.expect("never fails because `||` has two arguments");
let current_lhs = args
.pop()
.expect("never fails because `||` has two arguments");
Expr::Or( Expr::Or(
Box::new(BinaryExpr { Box::new(BinaryExpr {
lhs: current_lhs, lhs: current_lhs,
@ -1762,8 +1780,12 @@ fn parse_binary_op(
) )
} }
Token::And => { Token::And => {
let rhs = args.pop().unwrap(); let rhs = args
let current_lhs = args.pop().unwrap(); .pop()
.expect("never fails because `&&` has two arguments");
let current_lhs = args
.pop()
.expect("never fails because `&&` has two arguments");
Expr::And( Expr::And(
Box::new(BinaryExpr { Box::new(BinaryExpr {
lhs: current_lhs, lhs: current_lhs,
@ -1779,7 +1801,7 @@ fn parse_binary_op(
args.shrink_to_fit(); args.shrink_to_fit();
// Convert into a call to `contains` // Convert into a call to `contains`
let hash = calc_fn_hash(empty(), OP_CONTAINS, 2); let hash = calc_fn_hash(OP_CONTAINS, 2);
Expr::FnCall( Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
hashes: FnCallHashes::from_script(hash), hashes: FnCallHashes::from_script(hash),
@ -1798,7 +1820,7 @@ fn parse_binary_op(
.get(s.as_str()) .get(s.as_str())
.map_or(false, Option::is_some) => .map_or(false, Option::is_some) =>
{ {
let hash = calc_fn_hash(empty(), &s, 2); let hash = calc_fn_hash(&s, 2);
Expr::FnCall( Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
@ -1848,7 +1870,7 @@ fn parse_custom_syntax(
tokens.push(key.into()); tokens.push(key.into());
loop { loop {
let (fwd_token, fwd_pos) = input.peek().unwrap(); let (fwd_token, fwd_pos) = input.peek().expect(NEVER_ENDS);
settings.pos = *fwd_pos; settings.pos = *fwd_pos;
let settings = settings.level_up(); let settings = settings.level_up();
@ -1861,7 +1883,7 @@ fn parse_custom_syntax(
}; };
match required_token.as_str() { match required_token.as_str() {
MARKER_IDENT => match input.next().unwrap() { MARKER_IDENT => match input.next().expect(NEVER_ENDS) {
(Token::Identifier(s), pos) => { (Token::Identifier(s), pos) => {
let name = state.get_identifier(s); let name = state.get_identifier(s);
segments.push(name.clone().into()); segments.push(name.clone().into());
@ -1888,7 +1910,7 @@ fn parse_custom_syntax(
} }
stmt => unreachable!("expecting Stmt::Block, but gets {:?}", stmt), stmt => unreachable!("expecting Stmt::Block, but gets {:?}", stmt),
}, },
s => match input.next().unwrap() { s => match input.next().expect(NEVER_ENDS) {
(Token::LexError(err), pos) => return Err(err.into_err(pos)), (Token::LexError(err), pos) => return Err(err.into_err(pos)),
(t, _) if t.syntax().as_ref() == s => { (t, _) if t.syntax().as_ref() == s => {
segments.push(required_token.clone()); segments.push(required_token.clone());
@ -1928,18 +1950,18 @@ fn parse_expr(
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
settings.pos = input.peek().unwrap().1; settings.pos = input.peek().expect(NEVER_ENDS).1;
// Check if it is a custom syntax. // Check if it is a custom syntax.
if !state.engine.custom_syntax.is_empty() { if !state.engine.custom_syntax.is_empty() {
let (token, pos) = input.peek().unwrap(); let (token, pos) = input.peek().expect(NEVER_ENDS);
let token_pos = *pos; let token_pos = *pos;
match token { match token {
Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key) => { Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key) => {
match state.engine.custom_syntax.get_key_value(key.as_str()) { match state.engine.custom_syntax.get_key_value(key.as_str()) {
Some((key, syntax)) => { Some((key, syntax)) => {
input.next().unwrap(); input.next().expect(NEVER_ENDS);
return parse_custom_syntax( return parse_custom_syntax(
input, state, lib, settings, key, syntax, token_pos, input, state, lib, settings, key, syntax, token_pos,
); );
@ -1965,7 +1987,7 @@ fn parse_expr(
/// Make sure that the expression is not a statement expression (i.e. wrapped in `{}`). /// Make sure that the expression is not a statement expression (i.e. wrapped in `{}`).
fn ensure_not_statement_expr(input: &mut TokenStream, type_name: &str) -> Result<(), ParseError> { fn ensure_not_statement_expr(input: &mut TokenStream, type_name: &str) -> Result<(), ParseError> {
match input.peek().unwrap() { match input.peek().expect(NEVER_ENDS) {
// Disallow statement expressions // Disallow statement expressions
(Token::LeftBrace, pos) | (Token::EOF, pos) => { (Token::LeftBrace, pos) | (Token::EOF, pos) => {
Err(PERR::ExprExpected(type_name.to_string()).into_err(*pos)) Err(PERR::ExprExpected(type_name.to_string()).into_err(*pos))
@ -1977,7 +1999,7 @@ fn ensure_not_statement_expr(input: &mut TokenStream, type_name: &str) -> Result
/// Make sure that the expression is not a mis-typed assignment (i.e. `a = b` instead of `a == b`). /// Make sure that the expression is not a mis-typed assignment (i.e. `a = b` instead of `a == b`).
fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> { fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> {
match input.peek().unwrap() { match input.peek().expect(NEVER_ENDS) {
(Token::Equals, pos) => Err(LexError::ImproperSymbol( (Token::Equals, pos) => Err(LexError::ImproperSymbol(
"=".to_string(), "=".to_string(),
"Possibly a typo of '=='?".to_string(), "Possibly a typo of '=='?".to_string(),
@ -2024,7 +2046,7 @@ fn parse_if(
// if guard { if_body } else ... // if guard { if_body } else ...
let else_body = if match_token(input, Token::Else).0 { let else_body = if match_token(input, Token::Else).0 {
if let (Token::If, _) = input.peek().unwrap() { if let (Token::If, _) = input.peek().expect(NEVER_ENDS) {
// if guard { if_body } else if ... // if guard { if_body } else if ...
parse_if(input, state, lib, settings.level_up())? parse_if(input, state, lib, settings.level_up())?
} else { } else {
@ -2053,7 +2075,7 @@ fn parse_while_loop(
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
// while|loops ... // while|loops ...
let (guard, token_pos) = match input.next().unwrap() { let (guard, token_pos) = match input.next().expect(NEVER_ENDS) {
(Token::While, pos) => { (Token::While, pos) => {
ensure_not_statement_expr(input, "a boolean")?; ensure_not_statement_expr(input, "a boolean")?;
let expr = parse_expr(input, state, lib, settings.level_up())?; let expr = parse_expr(input, state, lib, settings.level_up())?;
@ -2088,7 +2110,7 @@ fn parse_do(
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())?;
let is_while = match input.next().unwrap() { let is_while = match input.next().expect(NEVER_ENDS) {
(Token::While, _) => true, (Token::While, _) => true,
(Token::Until, _) => false, (Token::Until, _) => false,
(_, pos) => { (_, pos) => {
@ -2126,7 +2148,7 @@ fn parse_for(
settings.pos = eat_token(input, Token::For); settings.pos = eat_token(input, Token::For);
// for name ... // for name ...
let (name, name_pos) = match input.next().unwrap() { let (name, name_pos) = match input.next().expect(NEVER_ENDS) {
// Variable name // Variable name
(Token::Identifier(s), pos) => (s, pos), (Token::Identifier(s), pos) => (s, pos),
// Reserved keyword // Reserved keyword
@ -2140,7 +2162,7 @@ fn parse_for(
}; };
// for name in ... // for name in ...
match input.next().unwrap() { match input.next().expect(NEVER_ENDS) {
(Token::In, _) => (), (Token::In, _) => (),
(Token::LexError(err), pos) => return Err(err.into_err(pos)), (Token::LexError(err), pos) => return Err(err.into_err(pos)),
(_, pos) => { (_, pos) => {
@ -2190,10 +2212,10 @@ fn parse_let(
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
// let/const... (specified in `var_type`) // let/const... (specified in `var_type`)
settings.pos = input.next().unwrap().1; settings.pos = input.next().expect(NEVER_ENDS).1;
// let name ... // let name ...
let (name, pos) = match input.next().unwrap() { let (name, pos) = match input.next().expect(NEVER_ENDS) {
(Token::Identifier(s), pos) => (s, pos), (Token::Identifier(s), pos) => (s, pos),
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
return Err(PERR::Reserved(s).into_err(pos)); return Err(PERR::Reserved(s).into_err(pos));
@ -2249,7 +2271,7 @@ fn parse_import(
} }
// import expr as name ... // import expr as name ...
let (name, name_pos) = match input.next().unwrap() { let (name, name_pos) = match input.next().expect(NEVER_ENDS) {
(Token::Identifier(s), pos) => (s, pos), (Token::Identifier(s), pos) => (s, pos),
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
return Err(PERR::Reserved(s).into_err(pos)); return Err(PERR::Reserved(s).into_err(pos));
@ -2287,7 +2309,7 @@ fn parse_export(
settings.pos = eat_token(input, Token::Export); settings.pos = eat_token(input, Token::Export);
match input.peek().unwrap() { match input.peek().expect(NEVER_ENDS) {
(Token::Let, pos) => { (Token::Let, pos) => {
let pos = *pos; let pos = *pos;
let mut stmt = parse_let(input, state, lib, AccessMode::ReadWrite, true, settings)?; let mut stmt = parse_let(input, state, lib, AccessMode::ReadWrite, true, settings)?;
@ -2306,7 +2328,7 @@ fn parse_export(
let mut exports = Vec::with_capacity(4); let mut exports = Vec::with_capacity(4);
loop { loop {
let (id, id_pos) = match input.next().unwrap() { let (id, id_pos) = match input.next().expect(NEVER_ENDS) {
(Token::Identifier(s), pos) => (s.clone(), pos), (Token::Identifier(s), pos) => (s.clone(), pos),
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
return Err(PERR::Reserved(s).into_err(pos)); return Err(PERR::Reserved(s).into_err(pos));
@ -2316,7 +2338,7 @@ fn parse_export(
}; };
let rename = if match_token(input, Token::As).0 { let rename = if match_token(input, Token::As).0 {
match input.next().unwrap() { match input.next().expect(NEVER_ENDS) {
(Token::Identifier(s), pos) => Some(Ident { (Token::Identifier(s), pos) => Some(Ident {
name: state.get_identifier(s), name: state.get_identifier(s),
pos, pos,
@ -2339,7 +2361,7 @@ fn parse_export(
rename, rename,
)); ));
match input.peek().unwrap() { match input.peek().expect(NEVER_ENDS) {
(Token::Comma, _) => { (Token::Comma, _) => {
eat_token(input, Token::Comma); eat_token(input, Token::Comma);
} }
@ -2365,7 +2387,7 @@ fn parse_block(
mut settings: ParseSettings, mut settings: ParseSettings,
) -> Result<Stmt, ParseError> { ) -> Result<Stmt, ParseError> {
// Must start with { // Must start with {
settings.pos = match input.next().unwrap() { settings.pos = match input.next().expect(NEVER_ENDS) {
(Token::LeftBrace, pos) => pos, (Token::LeftBrace, pos) => pos,
(Token::LexError(err), pos) => return Err(err.into_err(pos)), (Token::LexError(err), pos) => return Err(err.into_err(pos)),
(_, pos) => { (_, pos) => {
@ -2390,7 +2412,7 @@ fn parse_block(
loop { loop {
// Terminated? // Terminated?
match input.peek().unwrap() { match input.peek().expect(NEVER_ENDS) {
(Token::RightBrace, _) => { (Token::RightBrace, _) => {
eat_token(input, Token::RightBrace); eat_token(input, Token::RightBrace);
break; break;
@ -2419,7 +2441,7 @@ fn parse_block(
statements.push(stmt); statements.push(stmt);
match input.peek().unwrap() { match input.peek().expect(NEVER_ENDS) {
// { ... stmt } // { ... stmt }
(Token::RightBrace, _) => { (Token::RightBrace, _) => {
eat_token(input, Token::RightBrace); eat_token(input, Token::RightBrace);
@ -2468,7 +2490,7 @@ fn parse_expr_stmt(
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
settings.pos = input.peek().unwrap().1; settings.pos = input.peek().expect(NEVER_ENDS).1;
let expr = parse_expr(input, state, lib, settings.level_up())?; let expr = parse_expr(input, state, lib, settings.level_up())?;
let stmt = parse_op_assignment_stmt(input, state, lib, expr, settings.level_up())?; let stmt = parse_op_assignment_stmt(input, state, lib, expr, settings.level_up())?;
@ -2491,7 +2513,7 @@ fn parse_stmt(
let mut comments_pos = Position::NONE; let mut comments_pos = Position::NONE;
// Handle doc-comments. // Handle doc-comments.
while let (Token::Comment(ref comment), pos) = input.peek().unwrap() { while let (Token::Comment(ref comment), pos) = input.peek().expect(NEVER_ENDS) {
if comments_pos.is_none() { if comments_pos.is_none() {
comments_pos = *pos; comments_pos = *pos;
} }
@ -2504,11 +2526,11 @@ fn parse_stmt(
return Err(PERR::WrongDocComment.into_err(comments_pos)); return Err(PERR::WrongDocComment.into_err(comments_pos));
} }
match input.next().unwrap().0 { match input.next().expect(NEVER_ENDS).0 {
Token::Comment(comment) => { Token::Comment(comment) => {
comments.push(comment); comments.push(comment);
match input.peek().unwrap() { match input.peek().expect(NEVER_ENDS) {
(Token::Fn, _) | (Token::Private, _) => break, (Token::Fn, _) | (Token::Private, _) => break,
(Token::Comment(_), _) => (), (Token::Comment(_), _) => (),
_ => return Err(PERR::WrongDocComment.into_err(comments_pos)), _ => return Err(PERR::WrongDocComment.into_err(comments_pos)),
@ -2521,7 +2543,7 @@ fn parse_stmt(
comments comments
}; };
let (token, token_pos) = match input.peek().unwrap() { let (token, token_pos) = match input.peek().expect(NEVER_ENDS) {
(Token::EOF, pos) => return Ok(Stmt::Noop(*pos)), (Token::EOF, pos) => return Ok(Stmt::Noop(*pos)),
x => x, x => x,
}; };
@ -2553,7 +2575,7 @@ fn parse_stmt(
FnAccess::Public FnAccess::Public
}; };
match input.next().unwrap() { match input.next().expect(NEVER_ENDS) {
(Token::Fn, pos) => { (Token::Fn, pos) => {
let mut new_state = let mut new_state =
ParseState::new(state.engine, state.tokenizer_control.clone()); ParseState::new(state.engine, state.tokenizer_control.clone());
@ -2586,7 +2608,7 @@ fn parse_stmt(
comments, comments,
)?; )?;
let hash = calc_fn_hash(empty(), &func.name, func.params.len()); let hash = calc_fn_hash(&func.name, func.params.len());
if lib.contains_key(&hash) { if lib.contains_key(&hash) {
return Err(PERR::FnDuplicatedDefinition( return Err(PERR::FnDuplicatedDefinition(
@ -2638,9 +2660,9 @@ fn parse_stmt(
pos, pos,
) )
}) })
.unwrap(); .expect(NEVER_ENDS);
match input.peek().unwrap() { match input.peek().expect(NEVER_ENDS) {
// `return`/`throw` at <EOF> // `return`/`throw` at <EOF>
(Token::EOF, _) => Ok(Stmt::Return(return_type, None, token_pos)), (Token::EOF, _) => Ok(Stmt::Return(return_type, None, token_pos)),
// `return;` or `throw;` // `return;` or `throw;`
@ -2699,7 +2721,7 @@ fn parse_try_catch(
// try { body } catch ( // try { body } catch (
let var_def = if match_token(input, Token::LeftParen).0 { let var_def = if match_token(input, Token::LeftParen).0 {
let id = match input.next().unwrap() { let id = match input.next().expect(NEVER_ENDS) {
(Token::Identifier(s), pos) => Ident { (Token::Identifier(s), pos) => Ident {
name: state.get_identifier(s), name: state.get_identifier(s),
pos, pos,
@ -2747,7 +2769,7 @@ fn parse_fn(
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let (token, pos) = input.next().unwrap(); let (token, pos) = input.next().expect(NEVER_ENDS);
let name = token let name = token
.into_function_name_for_override() .into_function_name_for_override()
@ -2756,7 +2778,7 @@ fn parse_fn(
_ => PERR::FnMissingName.into_err(pos), _ => PERR::FnMissingName.into_err(pos),
})?; })?;
match input.peek().unwrap() { match input.peek().expect(NEVER_ENDS) {
(Token::LeftParen, _) => eat_token(input, Token::LeftParen), (Token::LeftParen, _) => eat_token(input, Token::LeftParen),
(_, pos) => return Err(PERR::FnMissingParams(name).into_err(*pos)), (_, pos) => return Err(PERR::FnMissingParams(name).into_err(*pos)),
}; };
@ -2767,7 +2789,7 @@ fn parse_fn(
let sep_err = format!("to separate the parameters of function '{}'", name); let sep_err = format!("to separate the parameters of function '{}'", name);
loop { loop {
match input.next().unwrap() { match input.next().expect(NEVER_ENDS) {
(Token::RightParen, _) => break, (Token::RightParen, _) => break,
(Token::Identifier(s), pos) => { (Token::Identifier(s), pos) => {
if params.iter().any(|(p, _)| p == &s) { if params.iter().any(|(p, _)| p == &s) {
@ -2787,7 +2809,7 @@ fn parse_fn(
} }
} }
match input.next().unwrap() { match input.next().expect(NEVER_ENDS) {
(Token::RightParen, _) => break, (Token::RightParen, _) => break,
(Token::Comma, _) => (), (Token::Comma, _) => (),
(Token::LexError(err), pos) => return Err(err.into_err(pos)), (Token::LexError(err), pos) => return Err(err.into_err(pos)),
@ -2799,7 +2821,7 @@ fn parse_fn(
} }
// Parse function body // Parse function body
let body = match input.peek().unwrap() { let body = match input.peek().expect(NEVER_ENDS) {
(Token::LeftBrace, _) => { (Token::LeftBrace, _) => {
settings.is_breakable = false; settings.is_breakable = false;
parse_block(input, state, lib, settings.level_up())? parse_block(input, state, lib, settings.level_up())?
@ -2869,7 +2891,6 @@ fn make_curry_from_externals(
Box::new(FnCallExpr { Box::new(FnCallExpr {
name: state.get_identifier(crate::engine::KEYWORD_FN_PTR_CURRY), name: state.get_identifier(crate::engine::KEYWORD_FN_PTR_CURRY),
hashes: FnCallHashes::from_native(calc_fn_hash( hashes: FnCallHashes::from_native(calc_fn_hash(
empty(),
crate::engine::KEYWORD_FN_PTR_CURRY, crate::engine::KEYWORD_FN_PTR_CURRY,
num_externals + 1, num_externals + 1,
)), )),
@ -2900,10 +2921,10 @@ fn parse_anon_fn(
let mut params: StaticVec<_> = Default::default(); let mut params: StaticVec<_> = Default::default();
if input.next().unwrap().0 != Token::Or { if input.next().expect(NEVER_ENDS).0 != Token::Or {
if !match_token(input, Token::Pipe).0 { if !match_token(input, Token::Pipe).0 {
loop { loop {
match input.next().unwrap() { match input.next().expect(NEVER_ENDS) {
(Token::Pipe, _) => break, (Token::Pipe, _) => break,
(Token::Identifier(s), pos) => { (Token::Identifier(s), pos) => {
if params.iter().any(|(p, _)| p == &s) { if params.iter().any(|(p, _)| p == &s) {
@ -2923,7 +2944,7 @@ fn parse_anon_fn(
} }
} }
match input.next().unwrap() { match input.next().expect(NEVER_ENDS) {
(Token::Pipe, _) => break, (Token::Pipe, _) => break,
(Token::Comma, _) => (), (Token::Comma, _) => (),
(Token::LexError(err), pos) => return Err(err.into_err(pos)), (Token::LexError(err), pos) => return Err(err.into_err(pos)),
@ -3027,7 +3048,7 @@ impl Engine {
assert!(functions.is_empty()); assert!(functions.is_empty());
match input.peek().unwrap() { match input.peek().expect(NEVER_ENDS) {
(Token::EOF, _) => (), (Token::EOF, _) => (),
// Return error if the expression doesn't end // Return error if the expression doesn't end
(token, pos) => { (token, pos) => {
@ -3052,7 +3073,7 @@ impl Engine {
let mut statements = Vec::with_capacity(16); let mut statements = Vec::with_capacity(16);
let mut functions = BTreeMap::new(); let mut functions = BTreeMap::new();
while !input.peek().unwrap().0.is_eof() { while !input.peek().expect(NEVER_ENDS).0.is_eof() {
let settings = ParseSettings { let settings = ParseSettings {
allow_if_expr: true, allow_if_expr: true,
allow_switch_expr: true, allow_switch_expr: true,
@ -3075,7 +3096,7 @@ impl Engine {
statements.push(stmt); statements.push(stmt);
match input.peek().unwrap() { match input.peek().expect(NEVER_ENDS) {
// EOF // EOF
(Token::EOF, _) => break, (Token::EOF, _) => break,
// stmt ; // stmt ;
@ -3121,29 +3142,32 @@ impl Engine {
} }
} }
/// Map a `Dynamic` value to an expression. impl From<Dynamic> for Expr {
/// fn from(value: Dynamic) -> Self {
/// Returns Some(expression) if conversion is successful. Otherwise None.
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, _, _) => Self::FloatConstant(value, Position::NONE),
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
Union::Decimal(value, _, _) => Some(Expr::DynamicConstant(Box::new((*value).into()), pos)), Union::Decimal(value, _, _) => {
Self::DynamicConstant(Box::new((*value).into()), Position::NONE)
}
Union::Unit(_, _, _) => Some(Expr::Unit(pos)), Union::Unit(_, _, _) => Self::Unit(Position::NONE),
Union::Int(value, _, _) => Some(Expr::IntegerConstant(value, pos)), Union::Int(value, _, _) => Self::IntegerConstant(value, Position::NONE),
Union::Char(value, _, _) => Some(Expr::CharConstant(value, pos)), Union::Char(value, _, _) => Self::CharConstant(value, Position::NONE),
Union::Str(value, _, _) => Some(Expr::StringConstant(value, pos)), Union::Str(value, _, _) => Self::StringConstant(value, Position::NONE),
Union::Bool(value, _, _) => Some(Expr::BoolConstant(value, pos)), Union::Bool(value, _, _) => Self::BoolConstant(value, Position::NONE),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(array, _, _) => Some(Expr::DynamicConstant(Box::new((*array).into()), pos)), Union::Array(array, _, _) => {
Self::DynamicConstant(Box::new((*array).into()), Position::NONE)
}
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(map, _, _) => Some(Expr::DynamicConstant(Box::new((*map).into()), pos)), Union::Map(map, _, _) => Self::DynamicConstant(Box::new((*map).into()), Position::NONE),
_ => None, _ => Self::DynamicConstant(Box::new(value.into()), Position::NONE),
}
} }
} }

View File

@ -181,7 +181,9 @@ impl fmt::Display for EvalAltResult {
| Self::ErrorTerminated(_, _) => f.write_str(desc)?, | Self::ErrorTerminated(_, _) => f.write_str(desc)?,
Self::ErrorRuntime(d, _) if d.is::<ImmutableString>() => { Self::ErrorRuntime(d, _) if d.is::<ImmutableString>() => {
let s = &*d.read_lock::<ImmutableString>().unwrap(); let s = &*d
.read_lock::<ImmutableString>()
.expect("never fails because the type was checked");
write!(f, "{}: {}", desc, if s.is_empty() { desc } else { s })? write!(f, "{}: {}", desc, if s.is_empty() { desc } else { s })?
} }
Self::ErrorRuntime(d, _) if d.is::<()>() => f.write_str(desc)?, Self::ErrorRuntime(d, _) if d.is::<()>() => f.write_str(desc)?,
@ -336,7 +338,11 @@ impl EvalAltResult {
pub(crate) fn dump_fields(&self, map: &mut crate::Map) { pub(crate) fn dump_fields(&self, map: &mut crate::Map) {
map.insert( map.insert(
"error".into(), "error".into(),
format!("{:?}", self).split('(').next().unwrap().into(), format!("{:?}", self)
.split('(')
.next()
.expect("never fails because the debug format of an error is `ErrorXXX(...)`")
.into(),
); );
match self { match self {

View File

@ -7,7 +7,7 @@ use std::prelude::v1::*;
use std::{borrow::Cow, iter::Extend}; use std::{borrow::Cow, iter::Extend};
/// Keep a number of entries inline (since [`Dynamic`] is usually small enough). /// Keep a number of entries inline (since [`Dynamic`] is usually small enough).
const SCOPE_SIZE: usize = 16; const SCOPE_SIZE: usize = 8;
/// 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.
@ -62,10 +62,7 @@ pub struct Scope<'a> {
impl Default for Scope<'_> { impl Default for Scope<'_> {
#[inline(always)] #[inline(always)]
fn default() -> Self { fn default() -> Self {
Self { Self::new()
values: Default::default(),
names: Vec::with_capacity(SCOPE_SIZE),
}
} }
} }
@ -99,7 +96,10 @@ impl<'a> Scope<'a> {
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn new() -> Self { pub fn new() -> Self {
Default::default() Self {
values: Default::default(),
names: Default::default(),
}
} }
/// Empty the [`Scope`]. /// Empty the [`Scope`].
/// ///
@ -373,7 +373,11 @@ impl<'a> Scope<'a> {
} }
Some((_, AccessMode::ReadOnly)) => panic!("variable {} is constant", name), Some((_, AccessMode::ReadOnly)) => panic!("variable {} is constant", name),
Some((index, AccessMode::ReadWrite)) => { Some((index, AccessMode::ReadWrite)) => {
*self.values.get_mut(index).unwrap() = Dynamic::from(value); let value_ref = self
.values
.get_mut(index)
.expect("never fails because the index is returned by `get_index`");
*value_ref = Dynamic::from(value);
} }
} }
self self
@ -406,24 +410,38 @@ impl<'a> Scope<'a> {
}) })
} }
/// Get a mutable reference to an entry in the [`Scope`] based on the index. /// Get a mutable reference to an entry in the [`Scope`] based on the index.
///
/// # Panics
///
/// Panics if the index is out of bounds.
#[inline(always)] #[inline(always)]
pub(crate) fn get_mut_by_index(&mut self, index: usize) -> &mut Dynamic { pub(crate) fn get_mut_by_index(&mut self, index: usize) -> &mut Dynamic {
self.values.get_mut(index).expect("invalid index in Scope") self.values
.get_mut(index)
.expect("never fails unless the index is out of bounds")
} }
/// Update the access type of an entry in the [`Scope`]. /// Update the access type of an entry in the [`Scope`].
///
/// # Panics
///
/// Panics if the index is out of bounds.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
pub(crate) fn add_entry_alias( pub(crate) fn add_entry_alias(&mut self, index: usize, alias: Identifier) -> &mut Self {
&mut self, let entry = self
index: usize, .names
alias: impl Into<Identifier> + PartialEq<Identifier>, .get_mut(index)
) -> &mut Self { .expect("never fails unless the index is out of bounds");
let entry = self.names.get_mut(index).expect("invalid index in Scope");
if entry.1.is_none() { if entry.1.is_none() {
// Initialize the alias list if it is empty.
entry.1 = Some(Default::default()); entry.1 = Some(Default::default());
} }
if !entry.1.as_ref().unwrap().iter().any(|a| &alias == a) { let list = entry
entry.1.as_mut().unwrap().push(alias.into()); .1
.as_mut()
.expect("never fails because the list is initialized");
if !list.iter().any(|a| &alias == a) {
list.push(alias);
} }
self self
} }

View File

@ -91,21 +91,19 @@ struct FnMetadata {
impl PartialOrd for FnMetadata { impl PartialOrd for FnMetadata {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(match self.name.partial_cmp(&other.name).unwrap() { Some(self.cmp(other))
Ordering::Less => Ordering::Less,
Ordering::Greater => Ordering::Greater,
Ordering::Equal => match self.num_params.partial_cmp(&other.num_params).unwrap() {
Ordering::Less => Ordering::Less,
Ordering::Greater => Ordering::Greater,
Ordering::Equal => self.params.partial_cmp(&other.params).unwrap(),
},
})
} }
} }
impl Ord for FnMetadata { impl Ord for FnMetadata {
fn cmp(&self, other: &Self) -> Ordering { fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap() match self.name.cmp(&other.name) {
Ordering::Equal => match self.num_params.cmp(&other.num_params) {
Ordering::Equal => self.params.cmp(&other.params),
cmp => cmp,
},
cmp => cmp,
}
} }
} }

View File

@ -1181,7 +1181,10 @@ pub fn parse_string_literal(
#[cfg(not(feature = "no_position"))] #[cfg(not(feature = "no_position"))]
{ {
skip_whitespace_until = start.position().unwrap() + 1; let start_position = start
.position()
.expect("never fails because the string must have a starting position");
skip_whitespace_until = start_position + 1;
} }
} }
@ -1201,7 +1204,11 @@ pub fn parse_string_literal(
// Whitespace to skip // Whitespace to skip
#[cfg(not(feature = "no_position"))] #[cfg(not(feature = "no_position"))]
_ if next_char.is_whitespace() && pos.position().unwrap() < skip_whitespace_until => {} _ if next_char.is_whitespace()
&& pos
.position()
.expect("never fails because a character must have a position")
< skip_whitespace_until => {}
// All other characters // All other characters
_ => { _ => {
@ -1237,37 +1244,29 @@ fn scan_block_comment(
stream: &mut impl InputStream, stream: &mut impl InputStream,
mut level: usize, mut level: usize,
pos: &mut Position, pos: &mut Position,
comment: &mut Option<String>, mut comment: Option<&mut String>,
) -> usize { ) -> usize {
let comment = &mut comment;
while let Some(c) = stream.get_next() { while let Some(c) = stream.get_next() {
pos.advance(); pos.advance();
if let Some(ref mut comment) = comment { comment.as_mut().map(|comment| comment.push(c));
comment.push(c);
}
match c { match c {
'/' => { '/' => {
if let Some(c2) = stream.peek_next() { stream.peek_next().filter(|&c2| c2 == '*').map(|c2| {
if c2 == '*' {
eat_next(stream, pos); eat_next(stream, pos);
if let Some(ref mut comment) = comment { comment.as_mut().map(|comment| comment.push(c2));
comment.push(c2);
}
level += 1; level += 1;
} });
}
} }
'*' => { '*' => {
if let Some(c2) = stream.peek_next() { stream.peek_next().filter(|&c2| c2 == '/').map(|c2| {
if c2 == '/' {
eat_next(stream, pos); eat_next(stream, pos);
if let Some(ref mut comment) = comment { comment.as_mut().map(|comment| comment.push(c2));
comment.push(c2);
}
level -= 1; level -= 1;
} });
}
} }
'\n' => pos.new_line(), '\n' => pos.new_line(),
_ => (), _ => (),
@ -1347,21 +1346,27 @@ fn get_next_token_inner(
None None
}; };
state.comment_level = scan_block_comment(stream, state.comment_level, pos, &mut comment); state.comment_level =
scan_block_comment(stream, state.comment_level, pos, comment.as_mut());
let include_comments = state.include_comments; let return_comment = state.include_comments;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
let include_comments = if is_doc_comment(comment.as_ref().unwrap()) { let return_comment = return_comment
true || is_doc_comment(
} else { comment
include_comments .as_ref()
}; .expect("never fails because `include_comments` is true"),
);
if include_comments { if return_comment {
return Some((Token::Comment(comment.unwrap()), start_pos)); return Some((
} else if state.comment_level > 0 { Token::Comment(comment.expect("never fails because `return_comment` is true")),
start_pos,
));
}
if state.comment_level > 0 {
// Reached EOF without ending comment block // Reached EOF without ending comment block
return None; return None;
} }
@ -1409,7 +1414,7 @@ fn get_next_token_inner(
} }
#[cfg(any(not(feature = "no_float"), feature = "decimal"))] #[cfg(any(not(feature = "no_float"), feature = "decimal"))]
'.' => { '.' => {
stream.get_next().unwrap(); stream.get_next().expect("never fails because it is `.`");
// Check if followed by digits or something that cannot start a property name // Check if followed by digits or something that cannot start a property name
match stream.peek_next().unwrap_or('\0') { match stream.peek_next().unwrap_or('\0') {
@ -1443,7 +1448,7 @@ fn get_next_token_inner(
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
'e' => { 'e' => {
stream.get_next().unwrap(); stream.get_next().expect("never fails it is `e`");
// Check if followed by digits or +/- // Check if followed by digits or +/-
match stream.peek_next().unwrap_or('\0') { match stream.peek_next().unwrap_or('\0') {
@ -1456,7 +1461,11 @@ fn get_next_token_inner(
'+' | '-' => { '+' | '-' => {
result.push(next_char); result.push(next_char);
pos.advance(); pos.advance();
result.push(stream.get_next().unwrap()); result.push(
stream
.get_next()
.expect("never fails because it is `+` or `-`"),
);
pos.advance(); pos.advance();
} }
// Not a floating-point number // Not a floating-point number
@ -1492,12 +1501,10 @@ fn get_next_token_inner(
} }
} }
let num_pos = if let Some(negated_pos) = negated { let num_pos = negated.map_or(start_pos, |negated_pos| {
result.insert(0, '-'); result.insert(0, '-');
negated_pos negated_pos
} else { });
start_pos
};
// Parse number // Parse number
return Some(( return Some((
@ -1718,9 +1725,7 @@ fn get_next_token_inner(
pos.new_line(); pos.new_line();
break; break;
} }
if let Some(ref mut comment) = comment { comment.as_mut().map(|comment| comment.push(c));
comment.push(c);
}
pos.advance(); pos.advance();
} }
@ -1749,7 +1754,7 @@ fn get_next_token_inner(
}; };
state.comment_level = state.comment_level =
scan_block_comment(stream, state.comment_level, pos, &mut comment); scan_block_comment(stream, state.comment_level, pos, comment.as_mut());
if let Some(comment) = comment { if let Some(comment) = comment {
return Some((Token::Comment(comment), start_pos)); return Some((Token::Comment(comment), start_pos));
@ -2179,8 +2184,8 @@ impl<'a> Iterator for TokenIterator<'a> {
}; };
// Run the mapper, if any // Run the mapper, if any
let token = if let Some(map) = self.map { let token = if let Some(map_func) = self.map {
map(token) map_func(token)
} else { } else {
token token
}; };

View File

@ -10,7 +10,7 @@ use std::{
cmp::Ordering, cmp::Ordering,
fmt, fmt,
hash::{BuildHasher, Hash, Hasher}, hash::{BuildHasher, Hash, Hasher},
iter::FromIterator, iter::{empty, FromIterator},
ops::{Add, AddAssign, Deref, Sub, SubAssign}, ops::{Add, AddAssign, Deref, Sub, SubAssign},
str::FromStr, str::FromStr,
}; };
@ -58,9 +58,8 @@ pub fn get_hasher() -> ahash::AHasher {
Default::default() Default::default()
} }
/// _(INTERNALS)_ Calculate a [`u64`] hash key from a namespace-qualified function name /// Calculate a [`u64`] hash key from a namespace-qualified function name
/// and the number of parameters, but no parameter types. /// and the number of parameters, but no parameter types.
/// Exported under the `internals` feature only.
/// ///
/// Module names are passed in via `&str` references from an iterator. /// Module names are passed in via `&str` references from an iterator.
/// Parameter types are passed in via [`TypeId`] values from an iterator. /// Parameter types are passed in via [`TypeId`] values from an iterator.
@ -68,8 +67,8 @@ pub fn get_hasher() -> ahash::AHasher {
/// # Note /// # Note
/// ///
/// The first module name is skipped. Hashing starts from the _second_ module in the chain. /// The first module name is skipped. Hashing starts from the _second_ module in the chain.
#[inline(always)] #[inline]
pub fn calc_fn_hash<'a>( pub fn calc_qualified_fn_hash<'a>(
modules: impl Iterator<Item = &'a str>, modules: impl Iterator<Item = &'a str>,
fn_name: impl AsRef<str>, fn_name: impl AsRef<str>,
num: usize, num: usize,
@ -88,11 +87,19 @@ pub fn calc_fn_hash<'a>(
s.finish() s.finish()
} }
/// _(INTERNALS)_ Calculate a [`u64`] hash key from a list of parameter types. /// Calculate a [`u64`] hash key from a non-namespace-qualified function name
/// Exported under the `internals` feature only. /// and the number of parameters, but no parameter types.
/// ///
/// Parameter types are passed in via [`TypeId`] values from an iterator. /// Parameter types are passed in via [`TypeId`] values from an iterator.
#[inline(always)] #[inline(always)]
pub fn calc_fn_hash(fn_name: impl AsRef<str>, num: usize) -> u64 {
calc_qualified_fn_hash(empty(), fn_name, num)
}
/// Calculate a [`u64`] hash key from a list of parameter types.
///
/// Parameter types are passed in via [`TypeId`] values from an iterator.
#[inline]
pub fn calc_fn_params_hash(params: impl Iterator<Item = TypeId>) -> u64 { pub fn calc_fn_params_hash(params: impl Iterator<Item = TypeId>) -> u64 {
let s = &mut get_hasher(); let s = &mut get_hasher();
let mut len = 0; let mut len = 0;

View File

@ -1,6 +1,6 @@
#![cfg(not(feature = "no_object"))] #![cfg(not(feature = "no_object"))]
use rhai::{Engine, EvalAltResult, ImmutableString, INT}; use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]
fn test_get_set() -> Result<(), Box<EvalAltResult>> { fn test_get_set() -> Result<(), Box<EvalAltResult>> {
@ -46,21 +46,26 @@ fn test_get_set() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x.add(); a.x")?, 42); assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x.add(); a.x")?, 42);
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.y.add(); a.y")?, 0); assert_eq!(engine.eval::<INT>("let a = new_ts(); a.y.add(); a.y")?, 0);
#[cfg(not(feature = "no_index"))]
{
engine.register_indexer_get_set( engine.register_indexer_get_set(
|value: &mut TestStruct, index: ImmutableString| value.array[index.len()], |value: &mut TestStruct, index: &str| value.array[index.len()],
|value: &mut TestStruct, index: ImmutableString, new_val: INT| { |value: &mut TestStruct, index: &str, new_val: INT| value.array[index.len()] = new_val,
value.array[index.len()] = new_val
},
); );
#[cfg(not(feature = "no_index"))]
assert_eq!(engine.eval::<INT>(r#"let a = new_ts(); a["abc"]"#)?, 4); assert_eq!(engine.eval::<INT>(r#"let a = new_ts(); a["abc"]"#)?, 4);
#[cfg(not(feature = "no_index"))]
assert_eq!( assert_eq!(
engine.eval::<INT>(r#"let a = new_ts(); a["abc"] = 42; a["abc"]"#)?, engine.eval::<INT>(r#"let a = new_ts(); a["abc"] = 42; a["abc"]"#)?,
42 42
); );
}
assert_eq!(engine.eval::<INT>(r"let a = new_ts(); a.abc")?, 4);
assert_eq!(
engine.eval::<INT>(r"let a = new_ts(); a.abc = 42; a.abc")?,
42
);
Ok(()) Ok(())
} }

View File

@ -59,21 +59,21 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
assert_eq!( assert_eq!(
format!("{:?}", ast), format!("{:?}", ast),
"AST { source: None, body: [Expr(123 @ 1:53)], functions: Module, resolver: None }" "AST { source: None, body: Block[Expr(123 @ 1:53)], functions: Module, resolver: None }"
); );
let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?; let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?;
assert_eq!( assert_eq!(
format!("{:?}", ast), format!("{:?}", ast),
r#"AST { source: None, body: [Const(false @ 1:18, "DECISION" @ 1:7, false, 1:1), Expr(123 @ 1:51)], functions: Module, resolver: None }"# r#"AST { source: None, body: Block[Const(false @ 1:18, "DECISION" @ 1:7, false, 1:1), Expr(123 @ 1:51)], functions: Module, resolver: None }"#
); );
let ast = engine.compile("if 1 == 2 { 42 }")?; let ast = engine.compile("if 1 == 2 { 42 }")?;
assert_eq!( assert_eq!(
format!("{:?}", ast), format!("{:?}", ast),
"AST { source: None, body: [], functions: Module, resolver: None }" "AST { source: None, body: Block[], functions: Module, resolver: None }"
); );
engine.set_optimization_level(OptimizationLevel::Full); engine.set_optimization_level(OptimizationLevel::Full);
@ -82,7 +82,7 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
assert_eq!( assert_eq!(
format!("{:?}", ast), format!("{:?}", ast),
"AST { source: None, body: [Expr(42 @ 1:1)], functions: Module, resolver: None }" "AST { source: None, body: Block[Expr(42 @ 1:1)], functions: Module, resolver: None }"
); );
Ok(()) Ok(())

View File

@ -61,7 +61,7 @@ fn test_scope_eval() -> Result<(), Box<EvalAltResult>> {
// First invocation // First invocation
engine engine
.eval_with_scope::<()>(&mut scope, " let x = 4 + 5 - y + z; y = 1;") .eval_with_scope::<()>(&mut scope, " let x = 4 + 5 - y + z; y = 1;")
.expect("y and z not found?"); .expect("variables y and z should exist");
// Second invocation using the same state // Second invocation using the same state
let result = engine.eval_with_scope::<INT>(&mut scope, "x")?; let result = engine.eval_with_scope::<INT>(&mut scope, "x")?;