diff --git a/src/ast.rs b/src/ast.rs index fafc4394..06196a67 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -8,14 +8,13 @@ use crate::stdlib::{ boxed::Box, collections::HashMap, fmt, - hash::Hash, + hash::{Hash, Hasher}, num::{NonZeroU64, NonZeroUsize}, - ops::{Add, AddAssign}, + ops::{Add, AddAssign, Deref, DerefMut}, string::String, vec, vec::Vec, }; -use crate::syntax::FnCustomSyntaxEval; use crate::token::Token; use crate::utils::StraightHasherBuilder; use crate::{ @@ -492,11 +491,7 @@ impl AST { (true, true) => vec![], }; - let source = if other.source.is_some() { - other.source.clone() - } else { - self.source.clone() - }; + let source = other.source.clone().or_else(|| self.source.clone()); let mut functions = functions.as_ref().clone(); functions.merge_filtered(&other.functions, &mut filter); @@ -696,24 +691,68 @@ pub enum ReturnType { Exception, } +/// A type that wraps a [`HashMap`]`` for the `switch` statement and implements [`Hash`]. +#[derive(Clone)] +pub struct SwitchHashWrapper(HashMap); + +impl From> for SwitchHashWrapper { + fn from(value: HashMap) -> Self { + Self(value) + } +} +impl AsRef> for SwitchHashWrapper { + fn as_ref(&self) -> &HashMap { + &self.0 + } +} +impl AsMut> for SwitchHashWrapper { + fn as_mut(&mut self) -> &mut HashMap { + &mut self.0 + } +} +impl Deref for SwitchHashWrapper { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for SwitchHashWrapper { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} +impl fmt::Debug for SwitchHashWrapper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} +impl Hash for SwitchHashWrapper { + fn hash(&self, state: &mut H) { + let mut keys: Vec<_> = self.0.keys().collect(); + keys.sort(); + + keys.into_iter().for_each(|key| { + key.hash(state); + self.0.get(&key).unwrap().hash(state); + }); + } +} + /// _(INTERNALS)_ A statement. /// Exported under the `internals` feature only. /// /// # WARNING /// /// This type is volatile and may change. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Hash)] pub enum Stmt { /// No-op. Noop(Position), /// `if` expr `{` stmt `}` `else` `{` stmt `}` If(Expr, Box<(Stmt, Option)>, Position), /// `switch` expr `{` literal or _ `=>` stmt `,` ... `}` - Switch( - Expr, - Box<(HashMap, Option)>, - Position, - ), + Switch(Expr, Box<(SwitchHashWrapper, Option)>, Position), /// `while` expr `{` stmt `}` While(Expr, Box, Position), /// `do` `{` stmt `}` `while`|`until` expr @@ -899,10 +938,8 @@ impl Stmt { /// # WARNING /// /// This type is volatile and may change. -#[derive(Clone)] +#[derive(Clone, Hash)] pub struct CustomExpr { - /// Implementation function. - pub func: Shared, /// List of keywords. pub keywords: StaticVec, /// List of tokens actually parsed. @@ -926,7 +963,7 @@ impl fmt::Debug for CustomExpr { /// # WARNING /// /// This type is volatile and may change. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Hash)] pub struct BinaryExpr { /// LHS expression. pub lhs: Expr, @@ -940,7 +977,7 @@ pub struct BinaryExpr { /// # WARNING /// /// This type is volatile and may change. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, Hash)] pub struct FnCallExpr { /// Pre-calculated hash for a script-defined function of the same name and number of parameters. /// None if native Rust only. @@ -959,13 +996,83 @@ pub struct FnCallExpr { pub args: StaticVec, } +/// A type that wraps a [`FLOAT`] and implements [`Hash`]. +#[cfg(not(feature = "no_float"))] +#[derive(Clone, Copy)] +pub struct FloatWrapper(FLOAT); + +#[cfg(not(feature = "no_float"))] +impl Hash for FloatWrapper { + fn hash(&self, state: &mut H) { + self.0.to_le_bytes().hash(state); + } +} + +#[cfg(not(feature = "no_float"))] +impl AsRef for FloatWrapper { + fn as_ref(&self) -> &FLOAT { + &self.0 + } +} + +#[cfg(not(feature = "no_float"))] +impl AsMut for FloatWrapper { + fn as_mut(&mut self) -> &mut FLOAT { + &mut self.0 + } +} + +#[cfg(not(feature = "no_float"))] +impl Deref for FloatWrapper { + type Target = FLOAT; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[cfg(not(feature = "no_float"))] +impl DerefMut for FloatWrapper { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[cfg(not(feature = "no_float"))] +impl fmt::Debug for FloatWrapper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +#[cfg(not(feature = "no_float"))] +impl fmt::Display for FloatWrapper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +#[cfg(not(feature = "no_float"))] +impl From for FloatWrapper { + fn from(value: FLOAT) -> Self { + Self::new(value) + } +} + +#[cfg(not(feature = "no_float"))] +impl FloatWrapper { + pub const fn new(value: FLOAT) -> Self { + Self(value) + } +} + /// _(INTERNALS)_ An expression sub-tree. /// Exported under the `internals` feature only. /// /// # WARNING /// /// This type is volatile and may change. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Hash)] pub enum Expr { /// Dynamic constant. /// Used to hold either an [`Array`] or [`Map`] literal for quick cloning. @@ -977,7 +1084,7 @@ pub enum Expr { IntegerConstant(INT, Position), /// Floating-point constant. #[cfg(not(feature = "no_float"))] - FloatConstant(FLOAT, Position), + FloatConstant(FloatWrapper, Position), /// Character constant. CharConstant(char, Position), /// [String][ImmutableString] constant. @@ -1250,19 +1357,20 @@ mod tests { /// This test is to make sure no code changes increase the sizes of critical data structures. #[test] fn check_struct_sizes() { - use std::mem::size_of; + use crate::stdlib::mem::size_of; + use crate::*; - assert_eq!(size_of::(), 16); - assert_eq!(size_of::>(), 16); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 16); - assert_eq!(size_of::>(), 16); - assert_eq!(size_of::(), 32); - assert_eq!(size_of::>(), 32); - assert_eq!(size_of::(), 32); - assert_eq!(size_of::(), 48); - assert_eq!(size_of::(), 56); - assert_eq!(size_of::(), 16); - assert_eq!(size_of::(), 72); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::>(), 16); + assert_eq!(size_of::(), 4); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::>(), 16); + assert_eq!(size_of::(), 32); + assert_eq!(size_of::>(), 32); + assert_eq!(size_of::(), 32); + assert_eq!(size_of::(), 48); + assert_eq!(size_of::(), 56); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 72); } } diff --git a/src/dynamic.rs b/src/dynamic.rs index 47507ff6..fc5ab807 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -7,14 +7,13 @@ use crate::stdlib::{ boxed::Box, fmt, hash::{Hash, Hasher}, - mem, ops::{Deref, DerefMut}, string::String, }; use crate::{FnPtr, ImmutableString, INT}; #[cfg(not(feature = "no_float"))] -use crate::FLOAT; +use crate::{ast::FloatWrapper, FLOAT}; #[cfg(not(feature = "no_index"))] use crate::Array; @@ -155,7 +154,7 @@ pub enum Union { Char(char, AccessMode), Int(INT, AccessMode), #[cfg(not(feature = "no_float"))] - Float(FLOAT, AccessMode), + Float(FloatWrapper, AccessMode), #[cfg(not(feature = "no_index"))] Array(Box, AccessMode), #[cfg(not(feature = "no_object"))] @@ -362,8 +361,6 @@ impl Dynamic { impl Hash for Dynamic { fn hash(&self, state: &mut H) { - mem::discriminant(self).hash(state); - match &self.0 { Union::Unit(_, _) => ().hash(state), Union::Bool(value, _) => value.hash(state), @@ -371,7 +368,7 @@ impl Hash for Dynamic { Union::Char(ch, _) => ch.hash(state), Union::Int(i, _) => i.hash(state), #[cfg(not(feature = "no_float"))] - Union::Float(f, _) => f.to_le_bytes().hash(state), + Union::Float(f, _) => f.hash(state), #[cfg(not(feature = "no_index"))] Union::Array(a, _) => (**a).hash(state), #[cfg(not(feature = "no_object"))] @@ -559,13 +556,16 @@ impl Dynamic { pub const NEGATIVE_ONE: Dynamic = Self(Union::Int(-1, AccessMode::ReadWrite)); /// A [`Dynamic`] containing the floating-point zero. #[cfg(not(feature = "no_float"))] - pub const FLOAT_ZERO: Dynamic = Self(Union::Float(0.0, AccessMode::ReadWrite)); + pub const FLOAT_ZERO: Dynamic = + Self(Union::Float(FloatWrapper::new(0.0), AccessMode::ReadWrite)); /// A [`Dynamic`] containing the floating-point one. #[cfg(not(feature = "no_float"))] - pub const FLOAT_ONE: Dynamic = Self(Union::Float(1.0, AccessMode::ReadWrite)); + pub const FLOAT_ONE: Dynamic = + Self(Union::Float(FloatWrapper::new(1.0), AccessMode::ReadWrite)); /// A [`Dynamic`] containing the floating-point negative one. #[cfg(not(feature = "no_float"))] - pub const FLOAT_NEGATIVE_ONE: Dynamic = Self(Union::Float(-1.0, AccessMode::ReadWrite)); + pub const FLOAT_NEGATIVE_ONE: Dynamic = + Self(Union::Float(FloatWrapper::new(-1.0), AccessMode::ReadWrite)); /// Get the [`AccessMode`] for this [`Dynamic`]. pub(crate) fn access_mode(&self) -> AccessMode { @@ -836,7 +836,7 @@ impl Dynamic { #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Float(value, _) => unsafe_try_cast(value), + Union::Float(value, _) => unsafe_try_cast(*value), _ => None, }; } @@ -1105,7 +1105,7 @@ impl Dynamic { #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Float(value, _) => ::downcast_ref::(value), + Union::Float(value, _) => ::downcast_ref::(value.as_ref()), _ => None, }; } @@ -1194,7 +1194,7 @@ impl Dynamic { #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Float(value, _) => ::downcast_mut::(value), + Union::Float(value, _) => ::downcast_mut::(value.as_mut()), _ => None, }; } @@ -1277,7 +1277,7 @@ impl Dynamic { #[inline(always)] pub fn as_float(&self) -> Result { match self.0 { - Union::Float(n, _) => Ok(n), + Union::Float(n, _) => Ok(*n), #[cfg(not(feature = "no_closure"))] Union::Shared(_, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), _ => Err(self.type_name()), @@ -1380,6 +1380,13 @@ impl From for Dynamic { impl From for Dynamic { #[inline(always)] fn from(value: FLOAT) -> Self { + Self(Union::Float(value.into(), AccessMode::ReadWrite)) + } +} +#[cfg(not(feature = "no_float"))] +impl From for Dynamic { + #[inline(always)] + fn from(value: FloatWrapper) -> Self { Self(Union::Float(value, AccessMode::ReadWrite)) } } diff --git a/src/engine.rs b/src/engine.rs index 05d67062..f11e1762 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1923,6 +1923,10 @@ impl Engine { .iter() .map(Into::into) .collect::>(); + let custom_def = self + .custom_syntax + .get(custom.tokens.first().unwrap()) + .unwrap(); let mut context = EvalContext { engine: self, scope, @@ -1932,7 +1936,7 @@ impl Engine { this_ptr, level, }; - (custom.func)(&mut context, &expressions) + (custom_def.func)(&mut context, &expressions) } _ => unreachable!("expression cannot be evaluated: {:?}", expr), @@ -2072,11 +2076,7 @@ impl Engine { let args = &mut [lhs_ptr_inner, &mut rhs_val]; // Overriding exact implementation - let source = if source.is_none() { - state.source.as_ref() - } else { - source - }; + let source = source.or_else(|| state.source.as_ref()); if func.is_plugin_fn() { func.get_plugin_fn() .call((self, source, &*mods, lib).into(), args)?; @@ -2130,14 +2130,13 @@ impl Engine { &mut rhs_val, ]; - let result = self - .exec_fn_call( + Some( + self.exec_fn_call( mods, state, lib, op, None, args, false, false, false, *op_pos, None, None, level, ) - .map(|(v, _)| v)?; - - Some((result, rhs_expr.position())) + .map(|(v, _)| (v, rhs_expr.position()))?, + ) }; // Must be either `var[index] op= val` or `var.prop op= val` diff --git a/src/engine_api.rs b/src/engine_api.rs index 5f5b4b21..fed074bf 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -8,11 +8,9 @@ use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, format, - hash::{Hash, Hasher}, string::String, vec::Vec, }; -use crate::utils::get_hasher; use crate::{ scope::Scope, Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, Module, NativeCallContext, ParseError, Position, Shared, AST, @@ -24,13 +22,6 @@ use crate::Array; #[cfg(not(feature = "no_object"))] use crate::Map; -/// Calculate a unique hash for a script. -fn calc_hash_for_scripts<'a>(scripts: impl IntoIterator) -> u64 { - let s = &mut get_hasher(); - scripts.into_iter().for_each(|&script| script.hash(s)); - s.finish() -} - /// Engine public API impl Engine { /// Register a function of the [`Engine`]. @@ -960,9 +951,8 @@ impl Engine { scripts: &[&str], optimization_level: OptimizationLevel, ) -> Result { - let hash = calc_hash_for_scripts(scripts); let stream = self.lex(scripts); - self.parse(hash, &mut stream.peekable(), scope, optimization_level) + self.parse(&mut stream.peekable(), scope, optimization_level) } /// Read the contents of a file into a string. #[cfg(not(feature = "no_std"))] @@ -1123,8 +1113,6 @@ impl Engine { .into()); }; - let hash = calc_hash_for_scripts(&scripts); - let stream = self.lex_with_map( &scripts, if has_null { @@ -1138,12 +1126,8 @@ impl Engine { }, ); - let ast = self.parse_global_expr( - hash, - &mut stream.peekable(), - &scope, - OptimizationLevel::None, - )?; + let ast = + self.parse_global_expr(&mut stream.peekable(), &scope, OptimizationLevel::None)?; // Handle null - map to () if has_null { @@ -1222,11 +1206,10 @@ impl Engine { script: &str, ) -> Result { let scripts = [script]; - let hash = calc_hash_for_scripts(&scripts); let stream = self.lex(&scripts); let mut peekable = stream.peekable(); - self.parse_global_expr(hash, &mut peekable, scope, self.optimization_level) + self.parse_global_expr(&mut peekable, scope, self.optimization_level) } /// Evaluate a script file. /// @@ -1384,12 +1367,10 @@ impl Engine { script: &str, ) -> Result> { let scripts = [script]; - let hash = calc_hash_for_scripts(&scripts); let stream = self.lex(&scripts); // No need to optimize a lone expression - let ast = - self.parse_global_expr(hash, &mut stream.peekable(), scope, OptimizationLevel::None)?; + let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?; self.eval_ast_with_scope(scope, &ast) } @@ -1522,9 +1503,8 @@ impl Engine { script: &str, ) -> Result<(), Box> { let scripts = [script]; - let hash = calc_hash_for_scripts(&scripts); let stream = self.lex(&scripts); - let ast = self.parse(hash, &mut stream.peekable(), scope, self.optimization_level)?; + let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?; self.consume_ast_with_scope(scope, &ast) } /// Evaluate an AST, but throw away the result and only return error (if any). diff --git a/src/lib.rs b/src/lib.rs index a618879d..642f6139 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -183,7 +183,10 @@ pub use token::{get_next_token, parse_string_literal, InputStream, Token, Tokeni #[cfg(feature = "internals")] #[deprecated = "this type is volatile and may change"] -pub use ast::{BinaryExpr, CustomExpr, Expr, FnCallExpr, Ident, ReturnType, ScriptFnDef, Stmt}; +pub use ast::{ + BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallExpr, Ident, ReturnType, ScriptFnDef, Stmt, + SwitchHashWrapper, +}; #[cfg(feature = "internals")] #[deprecated = "this type is volatile and may change"] diff --git a/src/parser.rs b/src/parser.rs index 10c7945b..a86095ac 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -41,8 +41,6 @@ type FunctionsLib = HashMap; struct ParseState<'e> { /// Reference to the scripting [`Engine`]. engine: &'e Engine, - /// Hash that uniquely identifies a script. - script_hash: u64, /// Interned strings. strings: HashMap, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. @@ -75,7 +73,6 @@ impl<'e> ParseState<'e> { #[inline(always)] pub fn new( engine: &'e Engine, - script_hash: u64, #[cfg(not(feature = "unchecked"))] max_expr_depth: usize, #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_function"))] @@ -83,7 +80,6 @@ impl<'e> ParseState<'e> { ) -> Self { Self { engine, - script_hash, #[cfg(not(feature = "unchecked"))] max_expr_depth, #[cfg(not(feature = "unchecked"))] @@ -919,7 +915,7 @@ fn parse_switch( Ok(Stmt::Switch( item, - Box::new((final_table, def_stmt)), + Box::new((final_table.into(), def_stmt)), settings.pos, )) } @@ -956,7 +952,7 @@ fn parse_primary( }, #[cfg(not(feature = "no_float"))] Token::FloatConstant(x) => { - let x = *x; + let x = (*x).into(); input.next().unwrap(); Expr::FloatConstant(x, settings.pos) } @@ -986,7 +982,6 @@ fn parse_primary( Token::Pipe | Token::Or if settings.allow_anonymous_fn => { let mut new_state = ParseState::new( state.engine, - state.script_hash, #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, #[cfg(not(feature = "unchecked"))] @@ -1284,7 +1279,7 @@ fn parse_unary( .map(|i| Expr::IntegerConstant(i, pos)) .or_else(|| { #[cfg(not(feature = "no_float"))] - return Some(Expr::FloatConstant(-(num as FLOAT), pos)); + return Some(Expr::FloatConstant((-(num as FLOAT)).into(), pos)); #[cfg(feature = "no_float")] return None; }) @@ -1292,7 +1287,7 @@ fn parse_unary( // Negative float #[cfg(not(feature = "no_float"))] - Expr::FloatConstant(x, pos) => Ok(Expr::FloatConstant(-x, pos)), + Expr::FloatConstant(x, pos) => Ok(Expr::FloatConstant((-(*x)).into(), pos)), // Call negative function expr => { @@ -1998,14 +1993,7 @@ fn parse_custom_syntax( } } - Ok(Expr::Custom( - Box::new(CustomExpr { - keywords, - func: syntax.func.clone(), - tokens, - }), - pos, - )) + Ok(Expr::Custom(Box::new(CustomExpr { keywords, tokens }), pos)) } /// Parse an expression. @@ -2600,7 +2588,6 @@ fn parse_stmt( (Token::Fn, pos) => { let mut new_state = ParseState::new( state.engine, - state.script_hash, #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, #[cfg(not(feature = "unchecked"))] @@ -3009,10 +2996,10 @@ fn parse_anon_fn( params.into_iter().map(|(v, _)| v).collect() }; - // Create unique function name by hashing the script hash plus the position + // Create unique function name by hashing the script body plus the parameters. let hasher = &mut get_hasher(); - state.script_hash.hash(hasher); - settings.pos.hash(hasher); + params.iter().for_each(|p| p.as_str().hash(hasher)); + body.hash(hasher); let hash = hasher.finish(); let fn_name: ImmutableString = format!("{}{:016x}", crate::engine::FN_ANONYMOUS, hash).into(); @@ -3045,7 +3032,6 @@ fn parse_anon_fn( impl Engine { pub(crate) fn parse_global_expr( &self, - script_hash: u64, input: &mut TokenStream, scope: &Scope, optimization_level: OptimizationLevel, @@ -3053,7 +3039,6 @@ impl Engine { let mut functions = Default::default(); let mut state = ParseState::new( self, - script_hash, #[cfg(not(feature = "unchecked"))] self.max_expr_depth(), #[cfg(not(feature = "unchecked"))] @@ -3095,14 +3080,12 @@ impl Engine { /// Parse the global level statements. fn parse_global_level( &self, - script_hash: u64, input: &mut TokenStream, ) -> Result<(Vec, Vec), ParseError> { let mut statements = Vec::with_capacity(16); let mut functions = HashMap::with_capacity_and_hasher(16, StraightHasherBuilder); let mut state = ParseState::new( self, - script_hash, #[cfg(not(feature = "unchecked"))] self.max_expr_depth(), #[cfg(not(feature = "unchecked"))] @@ -3165,12 +3148,11 @@ impl Engine { #[inline(always)] pub(crate) fn parse( &self, - script_hash: u64, input: &mut TokenStream, scope: &Scope, optimization_level: OptimizationLevel, ) -> Result { - let (statements, lib) = self.parse_global_level(script_hash, input)?; + let (statements, lib) = self.parse_global_level(input)?; Ok( // Optimize AST