diff --git a/src/engine.rs b/src/engine.rs index 2c761c7e..3b740b71 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -12,7 +12,7 @@ use crate::r#unsafe::{unsafe_cast_var_name_to_lifetime, unsafe_mut_cast_to_lifet use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::token::Position; -use crate::utils::StaticVec; +use crate::utils::{StaticVec, StraightHasherBuilder}; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; @@ -196,7 +196,7 @@ impl State { /// /// The key of the `HashMap` is a `u64` hash calculated by the function `calc_fn_hash`. #[derive(Debug, Clone, Default)] -pub struct FunctionsLib(HashMap>); +pub struct FunctionsLib(HashMap, StraightHasherBuilder>); impl FunctionsLib { /// Create a new `FunctionsLib` from a collection of `FnDef`. @@ -261,7 +261,7 @@ impl From)>> for FunctionsLib { } impl Deref for FunctionsLib { - type Target = HashMap>; + type Target = HashMap, StraightHasherBuilder>; fn deref(&self) -> &Self::Target { &self.0 @@ -269,7 +269,7 @@ impl Deref for FunctionsLib { } impl DerefMut for FunctionsLib { - fn deref_mut(&mut self) -> &mut HashMap> { + fn deref_mut(&mut self) -> &mut HashMap, StraightHasherBuilder> { &mut self.0 } } @@ -952,21 +952,24 @@ impl Engine { let mut idx_val = idx_values.pop(); if is_index { + let pos = rhs.position(); + match rhs { - // xxx[idx].dot_rhs... | xxx[idx][dot_rhs]... + // xxx[idx].expr... | xxx[idx][expr]... Expr::Dot(x) | Expr::Index(x) => { + let (idx, expr, pos) = x.as_ref(); let is_idx = matches!(rhs, Expr::Index(_)); - let pos = x.0.position(); - let this_ptr = &mut self - .get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, op_pos, false)?; + let idx_pos = idx.position(); + let this_ptr = &mut self.get_indexed_mut( + state, lib, obj, is_ref, idx_val, idx_pos, op_pos, false, + )?; self.eval_dot_index_chain_helper( - state, lib, this_ptr, &x.1, idx_values, is_idx, x.2, level, new_val, + state, lib, this_ptr, expr, idx_values, is_idx, *pos, level, new_val, ) } // xxx[rhs] = new_val _ if new_val.is_some() => { - let pos = rhs.position(); let this_ptr = &mut self .get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, op_pos, true)?; @@ -975,16 +978,7 @@ impl Engine { } // xxx[rhs] _ => self - .get_indexed_mut( - state, - lib, - obj, - is_ref, - idx_val, - rhs.position(), - op_pos, - false, - ) + .get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, op_pos, false) .map(|v| (v.clone_into_dynamic(), false)), } } else { @@ -1050,57 +1044,51 @@ impl Engine { .map(|(v, _)| (v, false)) } #[cfg(not(feature = "no_object"))] - // {xxx:map}.idx_lhs[idx_expr] | {xxx:map}.dot_lhs.rhs + // {xxx:map}.prop[expr] | {xxx:map}.prop.expr Expr::Index(x) | Expr::Dot(x) if obj.is::() => { + let (prop, expr, pos) = x.as_ref(); let is_idx = matches!(rhs, Expr::Index(_)); - let mut val = if let Expr::Property(p) = &x.0 { + let mut val = if let Expr::Property(p) = prop { let ((prop, _, _), _) = p.as_ref(); let index = prop.clone().into(); - self.get_indexed_mut(state, lib, obj, is_ref, index, x.2, op_pos, false)? + self.get_indexed_mut(state, lib, obj, is_ref, index, *pos, op_pos, false)? } else { - // Syntax error - return Err(Box::new(EvalAltResult::ErrorDotExpr( - "".into(), - rhs.position(), - ))); + unreachable!(); }; self.eval_dot_index_chain_helper( - state, lib, &mut val, &x.1, idx_values, is_idx, x.2, level, new_val, + state, lib, &mut val, expr, idx_values, is_idx, *pos, level, new_val, ) } - // xxx.idx_lhs[idx_expr] | xxx.dot_lhs.rhs + // xxx.prop[expr] | xxx.prop.expr Expr::Index(x) | Expr::Dot(x) => { - let is_idx = matches!(rhs, Expr::Index(_)); + let (prop, expr, pos) = x.as_ref(); + let is_idx = matches!(expr, Expr::Index(_)); let args = &mut [obj, &mut Default::default()]; - let (mut val, updated) = if let Expr::Property(p) = &x.0 { + let (mut val, updated) = if let Expr::Property(p) = prop { let ((_, getter, _), _) = p.as_ref(); let args = &mut args[..1]; - self.exec_fn_call(state, lib, getter, true, 0, args, is_ref, None, x.2, 0)? + self.exec_fn_call(state, lib, getter, true, 0, args, is_ref, None, *pos, 0)? } else { - // Syntax error - return Err(Box::new(EvalAltResult::ErrorDotExpr( - "".into(), - rhs.position(), - ))); + unreachable!(); }; let val = &mut val; let target = &mut val.into(); let (result, may_be_changed) = self.eval_dot_index_chain_helper( - state, lib, target, &x.1, idx_values, is_idx, x.2, level, new_val, + state, lib, target, expr, idx_values, is_idx, *pos, level, new_val, )?; // Feed the value back via a setter just in case it has been updated if updated || may_be_changed { - if let Expr::Property(p) = &x.0 { + if let Expr::Property(p) = prop { let ((_, _, setter), _) = p.as_ref(); // Re-use args because the first &mut parameter will not be consumed args[1] = val; self.exec_fn_call( - state, lib, setter, true, 0, args, is_ref, None, x.2, 0, + state, lib, setter, true, 0, args, is_ref, None, *pos, 0, ) .or_else(|err| match *err { // If there is no setter, no need to feed it back because the property is read-only diff --git a/src/module.rs b/src/module.rs index 498739b7..9e16e276 100644 --- a/src/module.rs +++ b/src/module.rs @@ -12,7 +12,7 @@ use crate::parser::{ use crate::result::EvalAltResult; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::token::{Position, Token}; -use crate::utils::StaticVec; +use crate::utils::{StaticVec, StraightHasherBuilder}; use crate::stdlib::{ any::TypeId, @@ -44,10 +44,14 @@ pub struct Module { variables: HashMap, /// Flattened collection of all module variables, including those in sub-modules. - all_variables: HashMap, + all_variables: HashMap, /// External Rust functions. - functions: HashMap, CallableFunction)>, + functions: HashMap< + u64, + (String, FnAccess, StaticVec, CallableFunction), + StraightHasherBuilder, + >, /// Script-defined functions. lib: FunctionsLib, @@ -57,7 +61,7 @@ pub struct Module { /// Flattened collection of all external Rust functions, native or scripted, /// including those in sub-modules. - all_functions: HashMap, + all_functions: HashMap, } impl fmt::Debug for Module { @@ -101,7 +105,7 @@ impl Module { /// ``` pub fn new_with_capacity(capacity: usize) -> Self { Self { - functions: HashMap::with_capacity(capacity), + functions: HashMap::with_capacity_and_hasher(capacity, StraightHasherBuilder), ..Default::default() } } diff --git a/src/parser.rs b/src/parser.rs index 688dc7b1..bf668759 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -7,7 +7,7 @@ use crate::error::{LexError, ParseError, ParseErrorType}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::token::{Position, Token, TokenIterator}; -use crate::utils::StaticVec; +use crate::utils::{StaticVec, StraightHasherBuilder}; #[cfg(not(feature = "no_module"))] use crate::module::ModuleRef; @@ -1496,22 +1496,21 @@ fn parse_op_assignment_stmt<'a>( } /// Make a dot expression. -fn make_dot_expr( - lhs: Expr, - rhs: Expr, - op_pos: Position, - is_index: bool, -) -> Result { +fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { Ok(match (lhs, rhs) { - // idx_lhs[idx_rhs].rhs + // idx_lhs[idx_expr].rhs // Attach dot chain to the bottom level of indexing chain (Expr::Index(x), rhs) => { - Expr::Index(Box::new((x.0, make_dot_expr(x.1, rhs, op_pos, true)?, x.2))) + let (idx_lhs, idx_expr, pos) = *x; + Expr::Index(Box::new(( + idx_lhs, + make_dot_expr(idx_expr, rhs, op_pos)?, + pos, + ))) } // lhs.id (lhs, Expr::Variable(x)) if x.1.is_none() => { let (name, pos) = x.0; - let lhs = if is_index { lhs.into_property() } else { lhs }; let getter = make_getter(&name); let setter = make_setter(&name); @@ -1519,11 +1518,6 @@ fn make_dot_expr( Expr::Dot(Box::new((lhs, rhs, op_pos))) } - (lhs, Expr::Property(x)) => { - let lhs = if is_index { lhs.into_property() } else { lhs }; - let rhs = Expr::Property(x); - Expr::Dot(Box::new((lhs, rhs, op_pos))) - } // lhs.module::id - syntax error (_, Expr::Variable(x)) if x.1.is_some() => { #[cfg(feature = "no_module")] @@ -1531,34 +1525,30 @@ fn make_dot_expr( #[cfg(not(feature = "no_module"))] return Err(PERR::PropertyExpected.into_err(x.1.unwrap().get(0).1)); } + // lhs.prop + (lhs, prop @ Expr::Property(_)) => Expr::Dot(Box::new((lhs, prop, op_pos))), // lhs.dot_lhs.dot_rhs (lhs, Expr::Dot(x)) => { let (dot_lhs, dot_rhs, pos) = *x; Expr::Dot(Box::new(( lhs, - Expr::Dot(Box::new(( - dot_lhs.into_property(), - dot_rhs.into_property(), - pos, - ))), + Expr::Dot(Box::new((dot_lhs.into_property(), dot_rhs, pos))), op_pos, ))) } // lhs.idx_lhs[idx_rhs] (lhs, Expr::Index(x)) => { - let (idx_lhs, idx_rhs, pos) = *x; + let (dot_lhs, dot_rhs, pos) = *x; Expr::Dot(Box::new(( lhs, - Expr::Index(Box::new(( - idx_lhs.into_property(), - idx_rhs.into_property(), - pos, - ))), + Expr::Index(Box::new((dot_lhs.into_property(), dot_rhs, pos))), op_pos, ))) } + // lhs.func() + (lhs, func @ Expr::FnCall(_)) => Expr::Dot(Box::new((lhs, func, op_pos))), // lhs.rhs - (lhs, rhs) => Expr::Dot(Box::new((lhs, rhs.into_property(), op_pos))), + _ => unreachable!(), }) } @@ -1822,7 +1812,7 @@ fn parse_binary_op<'a>( _ => (), } - make_dot_expr(current_lhs, rhs, pos, false)? + make_dot_expr(current_lhs, rhs, pos)? } token => return Err(PERR::UnknownOperator(token.into()).into_err(pos)), @@ -2493,22 +2483,20 @@ pub fn parse_global_expr<'a>( fn parse_global_level<'a>( input: &mut Peekable>, max_expr_depth: (usize, usize), -) -> Result<(Vec, HashMap), ParseError> { +) -> Result<(Vec, Vec), ParseError> { let mut statements = Vec::::new(); - let mut functions = HashMap::::new(); + let mut functions = HashMap::with_hasher(StraightHasherBuilder); let mut state = ParseState::new(max_expr_depth.0); while !input.peek().unwrap().0.is_eof() { // Collect all the function definitions #[cfg(not(feature = "no_function"))] { - let mut access = FnAccess::Public; - let mut must_be_fn = false; - - if match_token(input, Token::Private)? { - access = FnAccess::Private; - must_be_fn = true; - } + let (access, must_be_fn) = if match_token(input, Token::Private)? { + (FnAccess::Private, true) + } else { + (FnAccess::Public, false) + }; match input.peek().unwrap() { (Token::Fn, _) => { @@ -2565,7 +2553,7 @@ fn parse_global_level<'a>( } } - Ok((statements, functions)) + Ok((statements, functions.into_iter().map(|(_, v)| v).collect())) } /// Run the parser on an input stream, returning an AST. @@ -2576,9 +2564,8 @@ pub fn parse<'a>( optimization_level: OptimizationLevel, max_expr_depth: (usize, usize), ) -> Result { - let (statements, functions) = parse_global_level(input, max_expr_depth)?; + let (statements, lib) = parse_global_level(input, max_expr_depth)?; - let lib = functions.into_iter().map(|(_, v)| v).collect(); Ok( // Optimize AST optimize_into_ast(engine, scope, statements, lib, optimization_level), diff --git a/src/utils.rs b/src/utils.rs index c396df19..ccd3d6be 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -11,7 +11,7 @@ use crate::stdlib::{ borrow::Borrow, boxed::Box, fmt, - hash::{Hash, Hasher}, + hash::{BuildHasher, Hash, Hasher}, iter::FromIterator, mem, mem::MaybeUninit, @@ -27,6 +27,48 @@ use crate::stdlib::collections::hash_map::DefaultHasher; #[cfg(feature = "no_std")] use ahash::AHasher; +/// A hasher that only takes one single `u64` and returns it as a hash key. +/// +/// # Panics +/// +/// Panics when hashing any data type other than a `u64`. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)] +pub struct StraightHasher(u64); + +impl Hasher for StraightHasher { + #[inline(always)] + fn finish(&self) -> u64 { + self.0 + } + #[inline] + fn write(&mut self, bytes: &[u8]) { + let mut key = [0_u8; 8]; + key.copy_from_slice(&bytes[..8]); // Panics if fewer than 8 bytes + self.0 = u64::from_le_bytes(key); + } +} + +impl StraightHasher { + /// Create a `StraightHasher`. + #[inline(always)] + pub fn new() -> Self { + Self(0) + } +} + +/// A hash builder for `StraightHasher`. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)] +pub struct StraightHasherBuilder; + +impl BuildHasher for StraightHasherBuilder { + type Hasher = StraightHasher; + + #[inline(always)] + fn build_hasher(&self) -> Self::Hasher { + StraightHasher::new() + } +} + /// Calculate a `u64` hash key from a module-qualified function name and parameter types. /// /// Module names are passed in via `&str` references from an iterator. @@ -108,6 +150,7 @@ pub struct StaticVec { const MAX_STATIC_VEC: usize = 4; impl Drop for StaticVec { + #[inline(always)] fn drop(&mut self) { self.clear(); } @@ -174,6 +217,7 @@ impl FromIterator for StaticVec { impl StaticVec { /// Create a new `StaticVec`. + #[inline(always)] pub fn new() -> Self { Default::default() } @@ -189,6 +233,7 @@ impl StaticVec { self.len = 0; } /// Extract a `MaybeUninit` into a concrete initialized type. + #[inline(always)] fn extract(value: MaybeUninit) -> T { unsafe { value.assume_init() } } @@ -250,6 +295,7 @@ impl StaticVec { ); } /// Is data stored in fixed-size storage? + #[inline(always)] fn is_fixed_storage(&self) -> bool { self.len <= MAX_STATIC_VEC } @@ -359,10 +405,12 @@ impl StaticVec { result } /// Get the number of items in this `StaticVec`. + #[inline(always)] pub fn len(&self) -> usize { self.len } /// Is this `StaticVec` empty? + #[inline(always)] pub fn is_empty(&self) -> bool { self.len == 0 } @@ -605,41 +653,48 @@ pub struct ImmutableString(Shared); impl Deref for ImmutableString { type Target = String; + #[inline(always)] fn deref(&self) -> &Self::Target { &self.0 } } impl AsRef for ImmutableString { + #[inline(always)] fn as_ref(&self) -> &String { &self.0 } } impl Borrow for ImmutableString { + #[inline(always)] fn borrow(&self) -> &str { self.0.as_str() } } impl From<&str> for ImmutableString { + #[inline(always)] fn from(value: &str) -> Self { Self(value.to_string().into()) } } impl From for ImmutableString { + #[inline(always)] fn from(value: String) -> Self { Self(value.into()) } } impl From> for ImmutableString { + #[inline(always)] fn from(value: Box) -> Self { Self(value.into()) } } impl From for String { + #[inline(always)] fn from(value: ImmutableString) -> Self { value.into_owned() } @@ -648,42 +703,49 @@ impl From for String { impl FromStr for ImmutableString { type Err = (); + #[inline(always)] fn from_str(s: &str) -> Result { Ok(Self(s.to_string().into())) } } impl FromIterator for ImmutableString { + #[inline(always)] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } } impl<'a> FromIterator<&'a char> for ImmutableString { + #[inline(always)] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().cloned().collect::().into()) } } impl<'a> FromIterator<&'a str> for ImmutableString { + #[inline(always)] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } } impl<'a> FromIterator for ImmutableString { + #[inline(always)] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } } impl fmt::Display for ImmutableString { + #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self.0.as_str(), f) } } impl fmt::Debug for ImmutableString { + #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self.0.as_str(), f) } @@ -818,6 +880,7 @@ impl Add for &ImmutableString { } impl AddAssign for ImmutableString { + #[inline(always)] fn add_assign(&mut self, rhs: char) { self.make_mut().push(rhs); } @@ -832,6 +895,7 @@ impl ImmutableString { } /// Make sure that the `ImmutableString` is unique (i.e. no other outstanding references). /// Then return a mutable reference to the `String`. + #[inline(always)] pub fn make_mut(&mut self) -> &mut String { shared_make_mut(&mut self.0) }