Revise function hashing.

This commit is contained in:
Stephen Chung 2021-03-08 15:30:32 +08:00
parent 1c3a07fe86
commit 62928f8613
12 changed files with 501 additions and 469 deletions

View File

@ -8,7 +8,7 @@ use crate::stdlib::{
boxed::Box, boxed::Box,
fmt, fmt,
hash::Hash, hash::Hash,
num::{NonZeroU64, NonZeroUsize}, num::NonZeroUsize,
ops::{Add, AddAssign}, ops::{Add, AddAssign},
string::String, string::String,
vec, vec,
@ -836,7 +836,7 @@ pub enum Stmt {
/// \[`export`\] `const` id `=` expr /// \[`export`\] `const` id `=` expr
Const(Box<Ident>, Option<Expr>, bool, Position), Const(Box<Ident>, Option<Expr>, bool, Position),
/// expr op`=` expr /// expr op`=` expr
Assignment(Box<(Expr, Cow<'static, str>, Expr)>, Position), Assignment(Box<(Expr, Expr, Option<OpAssignment>)>, Position),
/// `{` stmt`;` ... `}` /// `{` stmt`;` ... `}`
Block(Vec<Stmt>, Position), Block(Vec<Stmt>, Position),
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}` /// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
@ -1036,7 +1036,7 @@ impl Stmt {
} }
Self::Assignment(x, _) => { Self::Assignment(x, _) => {
x.0.walk(path, on_node); x.0.walk(path, on_node);
x.2.walk(path, on_node); x.1.walk(path, on_node);
} }
Self::Block(x, _) => x.iter().for_each(|s| s.walk(path, on_node)), Self::Block(x, _) => x.iter().for_each(|s| s.walk(path, on_node)),
Self::TryCatch(x, _, _) => { Self::TryCatch(x, _, _) => {
@ -1083,6 +1083,79 @@ pub struct BinaryExpr {
pub rhs: Expr, pub rhs: Expr,
} }
/// _(INTERNALS)_ An op-assignment operator.
/// Exported under the `internals` feature only.
///
/// # Volatile Data Structure
///
/// This type is volatile and may change.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct OpAssignment {
pub hash_op_assign: u64,
pub hash_op: u64,
pub op: Cow<'static, str>,
}
/// _(INTERNALS)_ An set of function call hashes.
/// Exported under the `internals` feature only.
///
/// # Volatile Data Structure
///
/// This type is volatile and may change.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)]
pub struct FnHash {
/// Pre-calculated hash for a script-defined function ([`None`] if native functions only).
script: Option<u64>,
/// Pre-calculated hash for a native Rust function with no parameter types.
native: u64,
}
impl FnHash {
/// Create a [`FnHash`] with only the native Rust hash.
#[inline(always)]
pub fn from_native(hash: u64) -> Self {
Self {
script: None,
native: hash,
}
}
/// Create a [`FnHash`] with both native Rust and script function hashes set to the same value.
#[inline(always)]
pub fn from_script(hash: u64) -> Self {
Self {
script: Some(hash),
native: hash,
}
}
/// Create a [`FnHash`] with both native Rust and script function hashes.
#[inline(always)]
pub fn from_script_and_native(script: u64, native: u64) -> Self {
Self {
script: Some(script),
native,
}
}
/// Is this [`FnHash`] native Rust only?
#[inline(always)]
pub fn is_native_only(&self) -> bool {
self.script.is_none()
}
/// Get the script function hash from this [`FnHash`].
///
/// # Panics
///
/// Panics if the [`FnHash`] is native Rust only.
#[inline(always)]
pub fn script_hash(&self) -> u64 {
self.script.unwrap()
}
/// Get the naive Rust function hash from this [`FnHash`].
#[inline(always)]
pub fn native_hash(&self) -> u64 {
self.native
}
}
/// _(INTERNALS)_ A function call. /// _(INTERNALS)_ A function call.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///
@ -1091,9 +1164,8 @@ pub struct BinaryExpr {
/// This type is volatile and may change. /// This type is volatile and may change.
#[derive(Debug, Clone, Default, Hash)] #[derive(Debug, Clone, Default, Hash)]
pub struct FnCallExpr { pub struct FnCallExpr {
/// Pre-calculated hash for a script-defined function of the same name and number of parameters. /// Pre-calculated hash.
/// None if native Rust only. pub hash: FnHash,
pub hash_script: Option<NonZeroU64>,
/// Does this function call capture the parent scope? /// Does this function call capture the parent scope?
pub capture: bool, pub capture: bool,
/// Namespace of the function, if any. Boxed because it occurs rarely. /// Namespace of the function, if any. Boxed because it occurs rarely.
@ -1225,15 +1297,9 @@ pub enum Expr {
/// () /// ()
Unit(Position), Unit(Position),
/// Variable access - (optional index, optional (hash, modules), variable name) /// Variable access - (optional index, optional (hash, modules), variable name)
Variable( Variable(Box<(Option<NonZeroUsize>, Option<(u64, NamespaceRef)>, Ident)>),
Box<( /// Property access - (getter, hash, setter, hash, prop)
Option<NonZeroUsize>, Property(Box<(ImmutableString, u64, ImmutableString, u64, Ident)>),
Option<(NonZeroU64, NamespaceRef)>,
Ident,
)>,
),
/// Property access - (getter, setter), prop
Property(Box<(ImmutableString, ImmutableString, Ident)>),
/// { [statement][Stmt] } /// { [statement][Stmt] }
Stmt(Box<StaticVec<Stmt>>, Position), Stmt(Box<StaticVec<Stmt>>, Position),
/// func `(` expr `,` ... `)` /// func `(` expr `,` ... `)`
@ -1326,7 +1392,7 @@ impl Expr {
Self::FnPointer(_, pos) => *pos, Self::FnPointer(_, pos) => *pos,
Self::Array(_, pos) => *pos, Self::Array(_, pos) => *pos,
Self::Map(_, pos) => *pos, Self::Map(_, pos) => *pos,
Self::Property(x) => (x.2).pos, Self::Property(x) => (x.4).pos,
Self::Stmt(_, pos) => *pos, Self::Stmt(_, pos) => *pos,
Self::Variable(x) => (x.2).pos, Self::Variable(x) => (x.2).pos,
Self::FnCall(_, pos) => *pos, Self::FnCall(_, pos) => *pos,
@ -1355,7 +1421,7 @@ impl Expr {
Self::Array(_, pos) => *pos = new_pos, Self::Array(_, pos) => *pos = new_pos,
Self::Map(_, pos) => *pos = new_pos, Self::Map(_, pos) => *pos = new_pos,
Self::Variable(x) => (x.2).pos = new_pos, Self::Variable(x) => (x.2).pos = new_pos,
Self::Property(x) => (x.2).pos = new_pos, Self::Property(x) => (x.4).pos = new_pos,
Self::Stmt(_, pos) => *pos = new_pos, Self::Stmt(_, pos) => *pos = new_pos,
Self::FnCall(_, pos) => *pos = new_pos, Self::FnCall(_, pos) => *pos = new_pos,
Self::And(_, pos) | Self::Or(_, pos) | Self::In(_, pos) => *pos = new_pos, Self::And(_, pos) | Self::Or(_, pos) | Self::In(_, pos) => *pos = new_pos,

View File

@ -1,6 +1,6 @@
//! Main module defining the script evaluation [`Engine`]. //! Main module defining the script evaluation [`Engine`].
use crate::ast::{Expr, FnCallExpr, Ident, ReturnType, Stmt}; use crate::ast::{Expr, FnCallExpr, FnHash, Ident, OpAssignment, ReturnType, Stmt};
use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant}; use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant};
use crate::fn_native::{ use crate::fn_native::{
CallableFunction, IteratorFn, OnDebugCallback, OnPrintCallback, OnProgressCallback, CallableFunction, IteratorFn, OnDebugCallback, OnPrintCallback, OnProgressCallback,
@ -25,8 +25,8 @@ use crate::stdlib::{
use crate::syntax::CustomSyntax; use crate::syntax::CustomSyntax;
use crate::utils::{get_hasher, StraightHasherBuilder}; use crate::utils::{get_hasher, StraightHasherBuilder};
use crate::{ use crate::{
calc_native_fn_hash, Dynamic, EvalAltResult, FnPtr, ImmutableString, Module, Position, calc_fn_hash, Dynamic, EvalAltResult, FnPtr, ImmutableString, Module, Position, RhaiResult,
RhaiResult, Scope, Shared, StaticVec, Scope, Shared, StaticVec,
}; };
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -126,15 +126,12 @@ impl Imports {
/// Does the specified function hash key exist in this stack of imported [modules][Module]? /// Does the specified function hash key exist in this stack of imported [modules][Module]?
#[allow(dead_code)] #[allow(dead_code)]
#[inline(always)] #[inline(always)]
pub fn contains_fn(&self, hash: NonZeroU64) -> bool { pub fn contains_fn(&self, hash: u64) -> bool {
self.0.iter().any(|(_, m)| m.contains_qualified_fn(hash)) self.0.iter().any(|(_, m)| m.contains_qualified_fn(hash))
} }
/// Get specified function via its hash key. /// Get specified function via its hash key.
#[inline(always)] #[inline(always)]
pub fn get_fn( pub fn get_fn(&self, hash: u64) -> Option<(&CallableFunction, Option<&ImmutableString>)> {
&self,
hash: NonZeroU64,
) -> Option<(&CallableFunction, Option<&ImmutableString>)> {
self.0 self.0
.iter() .iter()
.rev() .rev()
@ -510,11 +507,7 @@ pub struct State {
pub resolver: Option<Shared<crate::module::resolvers::StaticModuleResolver>>, pub resolver: Option<Shared<crate::module::resolvers::StaticModuleResolver>>,
/// Functions resolution cache. /// Functions resolution cache.
fn_resolution_caches: StaticVec< fn_resolution_caches: StaticVec<
HashMap< HashMap<u64, Option<(CallableFunction, Option<ImmutableString>)>, StraightHasherBuilder>,
NonZeroU64,
Option<(CallableFunction, Option<ImmutableString>)>,
StraightHasherBuilder,
>,
>, >,
} }
@ -527,11 +520,8 @@ impl State {
/// Get a mutable reference to the current functions resolution cache. /// Get a mutable reference to the current functions resolution cache.
pub fn fn_resolution_cache_mut( pub fn fn_resolution_cache_mut(
&mut self, &mut self,
) -> &mut HashMap< ) -> &mut HashMap<u64, Option<(CallableFunction, Option<ImmutableString>)>, StraightHasherBuilder>
NonZeroU64, {
Option<(CallableFunction, Option<ImmutableString>)>,
StraightHasherBuilder,
> {
if self.fn_resolution_caches.is_empty() { if self.fn_resolution_caches.is_empty() {
self.fn_resolution_caches self.fn_resolution_caches
.push(HashMap::with_capacity_and_hasher(16, StraightHasherBuilder)); .push(HashMap::with_capacity_and_hasher(16, StraightHasherBuilder));
@ -1053,7 +1043,7 @@ impl Engine {
idx_values: &mut StaticVec<ChainArgument>, idx_values: &mut StaticVec<ChainArgument>,
chain_type: ChainType, chain_type: ChainType,
level: usize, level: usize,
new_val: Option<((Dynamic, Position), (&str, Position))>, new_val: Option<((Dynamic, Position), (&Option<OpAssignment>, Position))>,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
assert!(chain_type != ChainType::NonChaining); assert!(chain_type != ChainType::NonChaining);
@ -1102,9 +1092,9 @@ impl Engine {
) { ) {
// 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, op_pos)) = new_val.unwrap(); let ((new_val, new_pos), (op_info, op_pos)) = new_val.unwrap();
self.eval_op_assignment( self.eval_op_assignment(
mods, state, lib, op, op_pos, obj_ptr, new_val, new_pos, mods, state, lib, op_info, op_pos, obj_ptr, new_val, new_pos,
)?; )?;
None None
} }
@ -1122,11 +1112,13 @@ impl Engine {
let val_type_name = target_val.type_name(); let val_type_name = target_val.type_name();
let ((_, val_pos), _) = new_val; let ((_, val_pos), _) = new_val;
let hash_set =
FnHash::from_native(calc_fn_hash(empty(), FN_IDX_SET, 3));
let args = &mut [target_val, &mut idx_val2, &mut (new_val.0).0]; let args = &mut [target_val, &mut idx_val2, &mut (new_val.0).0];
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, FN_IDX_SET, None, args, is_ref, true, val_pos, mods, state, lib, FN_IDX_SET, hash_set, args, is_ref, true,
None, level, val_pos, None, level,
) )
.map_err(|err| match *err { .map_err(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(fn_sig, _) EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
@ -1159,11 +1151,7 @@ impl Engine {
match rhs { match rhs {
// xxx.fn_name(arg_expr_list) // xxx.fn_name(arg_expr_list)
Expr::FnCall(x, pos) if x.namespace.is_none() && new_val.is_none() => { Expr::FnCall(x, pos) if x.namespace.is_none() && new_val.is_none() => {
let FnCallExpr { let FnCallExpr { name, hash, .. } = x.as_ref();
name,
hash_script: hash,
..
} = x.as_ref();
let args = idx_val.as_fn_call_args(); let args = idx_val.as_fn_call_args();
self.make_method_call( self.make_method_call(
mods, state, lib, name, *hash, target, args, *pos, level, mods, state, lib, name, *hash, target, args, *pos, level,
@ -1179,20 +1167,20 @@ impl Engine {
} }
// {xxx:map}.id op= ??? // {xxx:map}.id op= ???
Expr::Property(x) if target_val.is::<Map>() && new_val.is_some() => { Expr::Property(x) if target_val.is::<Map>() && new_val.is_some() => {
let Ident { name, pos } = &x.2; let Ident { name, pos } = &x.4;
let index = name.clone().into(); let index = name.clone().into();
let val = self.get_indexed_mut( let val = self.get_indexed_mut(
mods, state, lib, target_val, index, *pos, true, is_ref, false, level, mods, state, lib, target_val, index, *pos, true, is_ref, false, level,
)?; )?;
let ((new_val, new_pos), (op, op_pos)) = new_val.unwrap(); let ((new_val, new_pos), (op_info, op_pos)) = new_val.unwrap();
self.eval_op_assignment( self.eval_op_assignment(
mods, state, lib, op, op_pos, val, new_val, new_pos, mods, state, lib, op_info, op_pos, val, new_val, new_pos,
)?; )?;
Ok((Dynamic::UNIT, true)) Ok((Dynamic::UNIT, true))
} }
// {xxx:map}.id // {xxx:map}.id
Expr::Property(x) if target_val.is::<Map>() => { Expr::Property(x) if target_val.is::<Map>() => {
let Ident { name, pos } = &x.2; let Ident { name, pos } = &x.4;
let index = name.clone().into(); let index = name.clone().into();
let val = self.get_indexed_mut( let val = self.get_indexed_mut(
mods, state, lib, target_val, index, *pos, false, is_ref, false, level, mods, state, lib, target_val, index, *pos, false, is_ref, false, level,
@ -1202,21 +1190,23 @@ impl Engine {
} }
// xxx.id = ??? // xxx.id = ???
Expr::Property(x) if new_val.is_some() => { Expr::Property(x) if new_val.is_some() => {
let (_, setter, Ident { pos, .. }) = x.as_ref(); let (_, _, setter, hash_set, Ident { pos, .. }) = x.as_ref();
let hash = FnHash::from_native(*hash_set);
let mut new_val = new_val; let mut new_val = new_val;
let mut args = [target_val, &mut (new_val.as_mut().unwrap().0).0]; let mut args = [target_val, &mut (new_val.as_mut().unwrap().0).0];
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, setter, None, &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)) .map(|(v, _)| (v, true))
} }
// xxx.id // xxx.id
Expr::Property(x) => { Expr::Property(x) => {
let (getter, _, Ident { pos, .. }) = x.as_ref(); let (getter, hash_get, _, _, Ident { pos, .. }) = x.as_ref();
let hash = FnHash::from_native(*hash_get);
let mut args = [target_val]; let mut args = [target_val];
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, getter, None, &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(|(v, _)| (v, false))
@ -1225,7 +1215,7 @@ impl Engine {
Expr::Index(x, x_pos) | Expr::Dot(x, x_pos) if target_val.is::<Map>() => { Expr::Index(x, x_pos) | Expr::Dot(x, x_pos) if target_val.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 Ident { name, pos } = &p.4;
let index = name.clone().into(); let index = name.clone().into();
self.get_indexed_mut( self.get_indexed_mut(
mods, state, lib, target_val, index, *pos, false, is_ref, true, mods, state, lib, target_val, index, *pos, false, is_ref, true,
@ -1234,11 +1224,7 @@ impl Engine {
} }
// {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
Expr::FnCall(x, pos) if x.namespace.is_none() => { Expr::FnCall(x, pos) if x.namespace.is_none() => {
let FnCallExpr { let FnCallExpr { name, hash, .. } = x.as_ref();
name,
hash_script: hash,
..
} = x.as_ref();
let args = idx_val.as_fn_call_args(); let args = idx_val.as_fn_call_args();
let (val, _) = self.make_method_call( let (val, _) = self.make_method_call(
mods, state, lib, name, *hash, target, args, *pos, level, mods, state, lib, name, *hash, target, args, *pos, level,
@ -1264,12 +1250,15 @@ 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, setter, Ident { pos, .. }) = p.as_ref(); let (getter, hash_get, setter, hash_set, Ident { pos, .. }) =
p.as_ref();
let hash_get = FnHash::from_native(*hash_get);
let hash_set = FnHash::from_native(*hash_set);
let arg_values = &mut [target_val, &mut Default::default()]; let arg_values = &mut [target_val, &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.exec_fn_call(
mods, state, lib, getter, None, args, is_ref, true, *pos, None, mods, state, lib, getter, hash_get, args, is_ref, true, *pos,
level, None, level,
)?; )?;
let val = &mut val; let val = &mut val;
@ -1294,8 +1283,8 @@ impl Engine {
// 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; arg_values[1] = val;
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, setter, None, arg_values, is_ref, true, mods, state, lib, setter, hash_set, arg_values, is_ref,
*pos, None, level, true, *pos, None, level,
) )
.or_else( .or_else(
|err| match *err { |err| match *err {
@ -1313,11 +1302,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.namespace.is_none() => { Expr::FnCall(f, pos) if f.namespace.is_none() => {
let FnCallExpr { let FnCallExpr { name, hash, .. } = f.as_ref();
name,
hash_script: hash,
..
} = f.as_ref();
let args = idx_val.as_fn_call_args(); let 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, *hash, target, args, *pos, level, mods, state, lib, name, *hash, target, args, *pos, level,
@ -1359,7 +1344,7 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
expr: &Expr, expr: &Expr,
level: usize, level: usize,
new_val: Option<((Dynamic, Position), (&str, Position))>, new_val: Option<((Dynamic, Position), (&Option<OpAssignment>, Position))>,
) -> RhaiResult { ) -> RhaiResult {
let (crate::ast::BinaryExpr { lhs, rhs }, chain_type, op_pos) = match expr { let (crate::ast::BinaryExpr { lhs, rhs }, chain_type, op_pos) = match expr {
Expr::Index(x, pos) => (x.as_ref(), ChainType::Index, *pos), Expr::Index(x, pos) => (x.as_ref(), ChainType::Index, *pos),
@ -1593,8 +1578,9 @@ impl Engine {
let type_name = target.type_name(); let type_name = target.type_name();
let mut idx = idx; let mut idx = idx;
let args = &mut [target, &mut idx]; let args = &mut [target, &mut idx];
let hash_get = FnHash::from_native(calc_fn_hash(empty(), FN_IDX_GET, 2));
self.exec_fn_call( self.exec_fn_call(
_mods, state, _lib, FN_IDX_GET, None, args, _is_ref, true, idx_pos, None, _mods, state, _lib, FN_IDX_GET, hash_get, args, _is_ref, true, idx_pos, None,
_level, _level,
) )
.map(|(v, _)| v.into()) .map(|(v, _)| v.into())
@ -1640,17 +1626,13 @@ impl Engine {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(mut rhs_value, _)) => { Dynamic(Union::Array(mut rhs_value, _)) => {
// Call the `==` operator to compare each value // Call the `==` operator to compare each value
let hash = calc_fn_hash(empty(), OP_EQUALS, 2);
for value in rhs_value.iter_mut() { for value in rhs_value.iter_mut() {
let args = &mut [&mut lhs_value.clone(), value]; let args = &mut [&mut lhs_value.clone(), value];
let hash_fn =
calc_native_fn_hash(empty(), OP_EQUALS, args.iter().map(|a| a.type_id()))
.unwrap();
let pos = rhs.position(); let pos = rhs.position();
if self if self
.call_native_fn( .call_native_fn(mods, state, lib, OP_EQUALS, hash, args, false, false, pos)?
mods, state, lib, OP_EQUALS, hash_fn, args, false, false, pos,
)?
.0 .0
.as_bool() .as_bool()
.unwrap_or(false) .unwrap_or(false)
@ -1764,7 +1746,7 @@ impl Engine {
let FnCallExpr { let FnCallExpr {
name, name,
capture: cap_scope, capture: cap_scope,
hash_script: hash, hash,
args, args,
.. ..
} = x.as_ref(); } = x.as_ref();
@ -1778,12 +1760,12 @@ impl Engine {
let FnCallExpr { let FnCallExpr {
name, name,
namespace, namespace,
hash_script, hash,
args, args,
.. ..
} = x.as_ref(); } = x.as_ref();
let namespace = namespace.as_ref(); let namespace = namespace.as_ref();
let hash = hash_script.unwrap(); let hash = hash.native_hash();
self.make_qualified_function_call( self.make_qualified_function_call(
scope, mods, state, lib, this_ptr, namespace, name, args, hash, *pos, level, scope, mods, state, lib, this_ptr, namespace, name, args, hash, *pos, level,
) )
@ -1927,7 +1909,7 @@ impl Engine {
mods: &mut Imports, mods: &mut Imports,
state: &mut State, state: &mut State,
lib: &[&Module], lib: &[&Module],
op: &str, op_info: &Option<OpAssignment>,
op_pos: Position, op_pos: Position,
mut target: Target, mut target: Target,
mut new_value: Dynamic, mut new_value: Dynamic,
@ -1937,11 +1919,12 @@ impl Engine {
unreachable!("LHS should not be read-only"); unreachable!("LHS should not be read-only");
} }
if op.is_empty() { if let Some(OpAssignment {
// Normal assignment hash_op_assign,
target.set_value(new_value, new_value_pos)?; hash_op,
Ok(()) op,
} else { }) = op_info
{
let mut lock_guard; let mut lock_guard;
let lhs_ptr_inner; let lhs_ptr_inner;
@ -1952,28 +1935,30 @@ impl Engine {
lhs_ptr_inner = target.as_mut(); lhs_ptr_inner = target.as_mut();
} }
let hash = *hash_op_assign;
let args = &mut [lhs_ptr_inner, &mut new_value]; let args = &mut [lhs_ptr_inner, &mut new_value];
let hash_fn =
calc_native_fn_hash(empty(), op, args.iter().map(|a| a.type_id())).unwrap();
match self.call_native_fn(mods, state, lib, op, hash_fn, args, true, true, op_pos) { match self.call_native_fn(mods, state, lib, op, hash, args, true, true, op_pos) {
Ok(_) => (), Ok(_) => (),
Err(err) if matches!(err.as_ref(), EvalAltResult::ErrorFunctionNotFound(f, _) if f.starts_with(op)) => Err(err) if matches!(err.as_ref(), EvalAltResult::ErrorFunctionNotFound(f, _) if f.starts_with(op.as_ref())) =>
{ {
// Expand to `var = var op rhs` // Expand to `var = var op rhs`
let op = &op[..op.len() - 1]; // extract operator without = let op = &op[..op.len() - 1]; // extract operator without =
let hash_fn =
calc_native_fn_hash(empty(), op, args.iter().map(|a| a.type_id())).unwrap();
// Run function // Run function
let (value, _) = self let (value, _) = self.call_native_fn(
.call_native_fn(mods, state, lib, op, hash_fn, args, true, false, op_pos)?; mods, state, lib, op, *hash_op, args, true, false, op_pos,
)?;
*args[0] = value.flatten(); *args[0] = value.flatten();
} }
err => return err.map(|_| ()), err => return err.map(|_| ()),
} }
Ok(())
} else {
// Normal assignment
target.set_value(new_value, new_value_pos)?;
Ok(()) Ok(())
} }
} }
@ -2007,7 +1992,7 @@ impl Engine {
// var op= rhs // var op= rhs
Stmt::Assignment(x, op_pos) if x.0.get_variable_access(false).is_some() => { Stmt::Assignment(x, op_pos) if x.0.get_variable_access(false).is_some() => {
let (lhs_expr, op, rhs_expr) = x.as_ref(); let (lhs_expr, rhs_expr, op_info) = x.as_ref();
let rhs_val = self let rhs_val = self
.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)? .eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?
.flatten(); .flatten();
@ -2035,7 +2020,7 @@ impl Engine {
mods, mods,
state, state,
lib, lib,
op, op_info,
*op_pos, *op_pos,
lhs_ptr, lhs_ptr,
rhs_val, rhs_val,
@ -2047,11 +2032,11 @@ impl Engine {
// lhs op= rhs // lhs op= rhs
Stmt::Assignment(x, op_pos) => { Stmt::Assignment(x, op_pos) => {
let (lhs_expr, op, rhs_expr) = x.as_ref(); let (lhs_expr, rhs_expr, op_info) = x.as_ref();
let rhs_val = self let rhs_val = self
.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)? .eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?
.flatten(); .flatten();
let _new_val = Some(((rhs_val, rhs_expr.position()), (op.as_ref(), *op_pos))); let _new_val = Some(((rhs_val, rhs_expr.position()), (op_info, *op_pos)));
// Must be either `var[index] op= val` or `var.prop op= val` // Must be either `var[index] op= val` or `var.prop op= val`
match lhs_expr { match lhs_expr {

View File

@ -1,5 +1,6 @@
//! Implement function-calling mechanism for [`Engine`]. //! Implement function-calling mechanism for [`Engine`].
use crate::ast::FnHash;
use crate::engine::{ use crate::engine::{
Imports, State, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, Imports, State, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL,
KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
@ -16,18 +17,16 @@ use crate::stdlib::{
format, format,
iter::{empty, once}, iter::{empty, once},
mem, mem,
num::NonZeroU64,
string::{String, ToString}, string::{String, ToString},
vec::Vec, vec::Vec,
}; };
use crate::utils::combine_hashes;
use crate::{ use crate::{
ast::{Expr, Stmt}, ast::{Expr, Stmt},
fn_native::CallableFunction, fn_native::CallableFunction,
RhaiResult, RhaiResult,
}; };
use crate::{ use crate::{
calc_native_fn_hash, calc_script_fn_hash, Dynamic, Engine, EvalAltResult, FnPtr, calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, EvalAltResult, FnPtr,
ImmutableString, Module, ParseErrorType, Position, Scope, StaticVec, ImmutableString, Module, ParseErrorType, Position, Scope, StaticVec,
}; };
@ -183,20 +182,27 @@ impl Engine {
state: &'s mut State, state: &'s mut State,
lib: &[&Module], lib: &[&Module],
fn_name: &str, fn_name: &str,
mut hash: NonZeroU64, hash_script: u64,
args: &mut FnCallArgs, args: Option<&mut FnCallArgs>,
allow_dynamic: bool, allow_dynamic: bool,
is_op_assignment: bool, is_op_assignment: bool,
) -> &'s Option<(CallableFunction, Option<ImmutableString>)> { ) -> &'s Option<(CallableFunction, Option<ImmutableString>)> {
let mut hash = if let Some(ref args) = args {
let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id()));
combine_hashes(hash_script, hash_params)
} else {
hash_script
};
&*state &*state
.fn_resolution_cache_mut() .fn_resolution_cache_mut()
.entry(hash) .entry(hash)
.or_insert_with(|| { .or_insert_with(|| {
let num_args = args.len(); let num_args = args.as_ref().map(|a| a.len()).unwrap_or(0);
let max_bitmask = if !allow_dynamic { let max_bitmask = if !allow_dynamic {
0 0
} else { } else {
1usize << args.len().min(MAX_DYNAMIC_PARAMETERS) 1usize << num_args.min(MAX_DYNAMIC_PARAMETERS)
}; };
let mut bitmask = 1usize; // Bitmask of which parameter to replace with `Dynamic` let mut bitmask = 1usize; // Bitmask of which parameter to replace with `Dynamic`
@ -238,7 +244,8 @@ impl Engine {
None if bitmask >= max_bitmask => { None if bitmask >= max_bitmask => {
return if num_args != 2 { return if num_args != 2 {
None None
} else if !is_op_assignment { } else if let Some(ref args) = args {
if !is_op_assignment {
if let Some(f) = if let Some(f) =
get_builtin_binary_op_fn(fn_name, &args[0], &args[1]) get_builtin_binary_op_fn(fn_name, &args[0], &args[1])
{ {
@ -263,14 +270,15 @@ impl Engine {
None None
} }
} }
} else {
None
}
} }
// Try all permutations with `Dynamic` wildcards // Try all permutations with `Dynamic` wildcards
None => { None => {
hash = calc_native_fn_hash( let hash_params = calc_fn_params_hash(
empty(), args.as_ref().unwrap().iter().enumerate().map(|(i, a)| {
fn_name,
args.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`
@ -279,8 +287,8 @@ impl Engine {
a.type_id() a.type_id()
} }
}), }),
) );
.unwrap(); hash = combine_hashes(hash_script, hash_params);
bitmask += 1; bitmask += 1;
} }
@ -302,7 +310,7 @@ impl Engine {
state: &mut State, state: &mut State,
lib: &[&Module], lib: &[&Module],
fn_name: &str, fn_name: &str,
hash_fn: NonZeroU64, hash_native: u64,
args: &mut FnCallArgs, args: &mut FnCallArgs,
is_ref: bool, is_ref: bool,
is_op_assignment: bool, is_op_assignment: bool,
@ -318,8 +326,8 @@ impl Engine {
state, state,
lib, lib,
fn_name, fn_name,
hash_fn, hash_native,
args, Some(args),
true, true,
is_op_assignment, is_op_assignment,
); );
@ -569,9 +577,9 @@ impl Engine {
result result
} }
// Has a system function an override? // Has a system function a Rust-native override?
#[inline(always)] #[inline(always)]
pub(crate) fn has_override_by_name_and_arguments( pub(crate) fn has_native_override(
&self, &self,
mods: Option<&Imports>, mods: Option<&Imports>,
state: &mut State, state: &mut State,
@ -579,15 +587,11 @@ impl Engine {
fn_name: &str, fn_name: &str,
arg_types: &[TypeId], arg_types: &[TypeId],
) -> bool { ) -> bool {
let arg_types = arg_types.as_ref(); let hash_script = calc_fn_hash(empty(), fn_name, arg_types.len());
let hash_params = calc_fn_params_hash(arg_types.iter().cloned());
let hash_fn = combine_hashes(hash_script, hash_params);
self.has_override( self.has_override(mods, state, lib, Some(hash_fn), None)
mods,
state,
lib,
calc_native_fn_hash(empty(), fn_name, arg_types.iter().cloned()),
calc_script_fn_hash(empty(), fn_name, arg_types.len()),
)
} }
// Has a system function an override? // Has a system function an override?
@ -597,8 +601,8 @@ impl Engine {
mods: Option<&Imports>, mods: Option<&Imports>,
state: &mut State, state: &mut State,
lib: &[&Module], lib: &[&Module],
hash_fn: Option<NonZeroU64>, hash_fn: Option<u64>,
hash_script: Option<NonZeroU64>, hash_script: Option<u64>,
) -> bool { ) -> bool {
let cache = state.fn_resolution_cache_mut(); let cache = state.fn_resolution_cache_mut();
@ -649,7 +653,7 @@ impl Engine {
state: &mut State, state: &mut State,
lib: &[&Module], lib: &[&Module],
fn_name: &str, fn_name: &str,
_hash_script: Option<NonZeroU64>, hash: FnHash,
args: &mut FnCallArgs, args: &mut FnCallArgs,
is_ref: bool, is_ref: bool,
_is_method: bool, _is_method: bool,
@ -684,9 +688,8 @@ impl Engine {
if num_params < 0 { if num_params < 0 {
Dynamic::FALSE Dynamic::FALSE
} else { } else {
let hash_script = let hash_script = calc_fn_hash(empty(), &fn_name, num_params as usize);
calc_script_fn_hash(empty(), &fn_name, num_params as usize); self.has_override(Some(mods), state, lib, None, Some(hash_script))
self.has_override(Some(mods), state, lib, None, hash_script)
.into() .into()
}, },
false, false,
@ -731,9 +734,16 @@ impl Engine {
_ => (), _ => (),
} }
// Scripted function call?
let hash_script = if hash.is_native_only() {
None
} else {
Some(hash.script_hash())
};
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
if let Some((func, source)) = _hash_script.and_then(|hash| { if let Some((func, source)) = hash_script.and_then(|hash| {
self.resolve_function(mods, state, lib, fn_name, hash, args, false, false) self.resolve_function(mods, state, lib, fn_name, hash, None, false, false)
.as_ref() .as_ref()
.map(|(f, s)| (f.clone(), s.clone())) .map(|(f, s)| (f.clone(), s.clone()))
}) { }) {
@ -815,9 +825,17 @@ impl Engine {
} }
// Native function call // Native function call
let hash_fn = self.call_native_fn(
calc_native_fn_hash(empty(), fn_name, args.iter().map(|a| a.type_id())).unwrap(); mods,
self.call_native_fn(mods, state, lib, fn_name, hash_fn, args, is_ref, false, pos) state,
lib,
fn_name,
hash.native_hash(),
args,
is_ref,
false,
pos,
)
} }
/// Evaluate a list of statements with no `this` pointer. /// Evaluate a list of statements with no `this` pointer.
@ -894,7 +912,7 @@ impl Engine {
state: &mut State, state: &mut State,
lib: &[&Module], lib: &[&Module],
fn_name: &str, fn_name: &str,
hash_script: Option<NonZeroU64>, mut hash: FnHash,
target: &mut crate::engine::Target, target: &mut crate::engine::Target,
mut call_args: StaticVec<Dynamic>, mut call_args: StaticVec<Dynamic>,
pos: Position, pos: Position,
@ -913,9 +931,8 @@ impl Engine {
// 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 hash // Recalculate hashes
let hash = let hash = FnHash::from_script(calc_fn_hash(empty(), fn_name, args_len));
hash_script.and_then(|_| calc_script_fn_hash(empty(), 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 arg_values = curry let mut arg_values = curry
@ -936,8 +953,10 @@ impl Engine {
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 hash // Recalculate hash
let hash = let hash = FnHash::from_script_and_native(
hash_script.and_then(|_| calc_script_fn_hash(empty(), fn_name, args_len)); calc_fn_hash(empty(), fn_name, args_len),
calc_fn_hash(empty(), 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<_>>();
let mut arg_values = once(obj) let mut arg_values = once(obj)
@ -977,7 +996,6 @@ impl Engine {
_ => { _ => {
let _redirected; let _redirected;
let mut hash = hash_script;
// Check if it is a map method call in OOP style // Check if it is a map method call in OOP style
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -995,17 +1013,14 @@ impl Engine {
.enumerate() .enumerate()
.for_each(|(i, v)| call_args.insert(i, v)); .for_each(|(i, v)| call_args.insert(i, v));
// 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 = hash_script.and_then(|_| { hash = FnHash::from_script_and_native(
calc_script_fn_hash(empty(), fn_name, call_args.len()) calc_fn_hash(empty(), fn_name, call_args.len()),
}); calc_fn_hash(empty(), fn_name, call_args.len() + 1),
);
} }
} }
}; };
if hash_script.is_none() {
hash = None;
}
// Attached object pointer in front of the arguments // Attached object pointer in front of the arguments
let mut arg_values = once(obj) let mut arg_values = once(obj)
.chain(call_args.iter_mut()) .chain(call_args.iter_mut())
@ -1036,7 +1051,7 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
fn_name: &str, fn_name: &str,
args_expr: &[Expr], args_expr: &[Expr],
mut hash_script: Option<NonZeroU64>, mut hash: FnHash,
pos: Position, pos: Position,
capture_scope: bool, capture_scope: bool,
level: usize, level: usize,
@ -1074,7 +1089,11 @@ impl Engine {
// Recalculate hash // Recalculate hash
let args_len = args_expr.len() + curry.len(); let args_len = args_expr.len() + curry.len();
hash_script = calc_script_fn_hash(empty(), name, args_len); hash = if !hash.is_native_only() {
FnHash::from_script(calc_fn_hash(empty(), name, args_len))
} else {
FnHash::from_native(calc_fn_hash(empty(), name, args_len))
};
} }
// Handle Fn() // Handle Fn()
@ -1145,8 +1164,8 @@ impl Engine {
return Ok(if num_params < 0 { return Ok(if num_params < 0 {
Dynamic::FALSE Dynamic::FALSE
} else { } else {
let hash_script = calc_script_fn_hash(empty(), &fn_name, num_params as usize); let hash_script = calc_fn_hash(empty(), &fn_name, num_params as usize);
self.has_override(Some(mods), state, lib, None, hash_script) self.has_override(Some(mods), state, lib, None, Some(hash_script))
.into() .into()
}); });
} }
@ -1266,17 +1285,7 @@ impl Engine {
let args = args.as_mut(); let args = args.as_mut();
self.exec_fn_call( self.exec_fn_call(
mods, mods, state, lib, name, hash, args, is_ref, false, pos, capture, level,
state,
lib,
name,
hash_script,
args,
is_ref,
false,
pos,
capture,
level,
) )
.map(|(v, _)| v) .map(|(v, _)| v)
} }
@ -1292,7 +1301,7 @@ impl Engine {
namespace: Option<&NamespaceRef>, namespace: Option<&NamespaceRef>,
fn_name: &str, fn_name: &str,
args_expr: &[Expr], args_expr: &[Expr],
hash_script: NonZeroU64, hash: u64,
pos: Position, pos: Position,
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
@ -1357,20 +1366,13 @@ impl Engine {
})?; })?;
// First search in script-defined functions (can override built-in) // First search in script-defined functions (can override built-in)
let func = match module.get_qualified_fn(hash_script) { let func = match module.get_qualified_fn(hash) {
// Then search in Rust functions // Then search in Rust functions
None => { None => {
self.inc_operations(state, pos)?; self.inc_operations(state, pos)?;
// Namespace-qualified Rust functions are indexed in two steps: let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id()));
// 1) Calculate a hash in a similar manner to script-defined functions, let hash_qualified_fn = combine_hashes(hash, hash_params);
// i.e. qualifiers + function name + number of arguments.
// 2) Calculate a second hash with no qualifiers, empty function name,
// and the actual list of argument `TypeId`'.s
let hash_fn_args =
calc_native_fn_hash(empty(), "", args.iter().map(|a| a.type_id())).unwrap();
// 3) The two hashes are combined.
let hash_qualified_fn = combine_hashes(hash_script, hash_fn_args);
module.get_qualified_fn(hash_qualified_fn) module.get_qualified_fn(hash_qualified_fn)
} }

View File

@ -1,6 +1,6 @@
//! Module defining interfaces to native-Rust functions. //! Module defining interfaces to native-Rust functions.
use crate::ast::FnAccess; use crate::ast::{FnAccess, FnHash};
use crate::engine::Imports; use crate::engine::Imports;
use crate::plugin::PluginFunction; use crate::plugin::PluginFunction;
use crate::stdlib::{ use crate::stdlib::{
@ -14,8 +14,8 @@ use crate::stdlib::{
}; };
use crate::token::is_valid_identifier; use crate::token::is_valid_identifier;
use crate::{ use crate::{
calc_script_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ImmutableString, Module, calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ImmutableString, Module, Position,
Position, RhaiResult, RhaiResult,
}; };
/// Trait that maps to `Send + Sync` only under the `sync` feature. /// Trait that maps to `Send + Sync` only under the `sync` feature.
@ -189,13 +189,22 @@ impl<'a> NativeCallContext<'a> {
is_method: bool, is_method: bool,
args: &mut [&mut Dynamic], args: &mut [&mut Dynamic],
) -> RhaiResult { ) -> RhaiResult {
let hash = if is_method {
FnHash::from_script_and_native(
calc_fn_hash(empty(), fn_name, args.len() - 1),
calc_fn_hash(empty(), fn_name, args.len()),
)
} else {
FnHash::from_script(calc_fn_hash(empty(), fn_name, args.len()))
};
self.engine() self.engine()
.exec_fn_call( .exec_fn_call(
&mut self.mods.cloned().unwrap_or_default(), &mut self.mods.cloned().unwrap_or_default(),
&mut Default::default(), &mut Default::default(),
self.lib, self.lib,
fn_name, fn_name,
calc_script_fn_hash(empty(), fn_name, args.len() - if is_method { 1 } else { 0 }), hash,
args, args,
is_method, is_method,
is_method, is_method,

View File

@ -6,9 +6,7 @@ use crate::dynamic::{DynamicWriteLock, Variant};
use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync}; use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync};
use crate::r#unsafe::unsafe_cast_box; use crate::r#unsafe::unsafe_cast_box;
use crate::stdlib::{any::TypeId, boxed::Box, mem, string::String}; use crate::stdlib::{any::TypeId, boxed::Box, mem, string::String};
use crate::{ use crate::{Dynamic, Engine, FnAccess, FnNamespace, NativeCallContext, RhaiResult};
Dynamic, Engine, FnAccess, FnNamespace, ImmutableString, NativeCallContext, RhaiResult,
};
/// Trait to register custom functions with the [`Engine`]. /// Trait to register custom functions with the [`Engine`].
pub trait RegisterFn<FN, ARGS, RET> { pub trait RegisterFn<FN, ARGS, RET> {
@ -154,20 +152,6 @@ pub fn map_result(data: RhaiResult) -> RhaiResult {
data data
} }
/// Remap `&str` | `String` to `ImmutableString`.
#[inline(always)]
fn map_type_id<R: 'static, T: 'static>() -> TypeId {
let ref_id = TypeId::of::<R>();
if ref_id == TypeId::of::<&str>() {
TypeId::of::<ImmutableString>()
} else if ref_id == TypeId::of::<String>() {
TypeId::of::<ImmutableString>()
} else {
TypeId::of::<T>()
}
}
macro_rules! def_register { macro_rules! def_register {
() => { () => {
def_register!(imp from_pure :); def_register!(imp from_pure :);
@ -188,7 +172,7 @@ macro_rules! def_register {
#[inline(always)] #[inline(always)]
fn register_fn(&mut self, name: &str, f: FN) -> &mut Self { fn register_fn(&mut self, name: &str, f: FN) -> &mut Self {
self.global_namespace.set_fn(name, FnNamespace::Global, FnAccess::Public, None, self.global_namespace.set_fn(name, FnNamespace::Global, FnAccess::Public, None,
&[$(map_type_id::<$param, $par>()),*], &[$(TypeId::of::<$par>()),*],
CallableFunction::$abi(make_func!(f : map_dynamic ; $($par => $let => $clone => $arg),*)) CallableFunction::$abi(make_func!(f : map_dynamic ; $($par => $let => $clone => $arg),*))
); );
self self
@ -203,7 +187,7 @@ macro_rules! def_register {
#[inline(always)] #[inline(always)]
fn register_result_fn(&mut self, name: &str, f: FN) -> &mut Self { fn register_result_fn(&mut self, name: &str, f: FN) -> &mut Self {
self.global_namespace.set_fn(name, FnNamespace::Global, FnAccess::Public, None, self.global_namespace.set_fn(name, FnNamespace::Global, FnAccess::Public, None,
&[$(map_type_id::<$param, $par>()),*], &[$(TypeId::of::<$par>()),*],
CallableFunction::$abi(make_func!(f : map_result ; $($par => $let => $clone => $arg),*)) CallableFunction::$abi(make_func!(f : map_result ; $($par => $let => $clone => $arg),*))
); );
self self

View File

@ -142,10 +142,10 @@ pub use fn_native::Shared;
use fn_native::Locked; use fn_native::Locked;
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
pub use utils::{calc_native_fn_hash, calc_script_fn_hash, HashableHashMap}; pub use utils::{calc_fn_hash, calc_fn_params_hash, combine_hashes, HashableHashMap};
#[cfg(not(feature = "internals"))] #[cfg(not(feature = "internals"))]
pub(crate) use utils::{calc_native_fn_hash, calc_script_fn_hash}; pub(crate) use utils::{calc_fn_hash, calc_fn_params_hash, combine_hashes};
pub use rhai_codegen::*; pub use rhai_codegen::*;
@ -195,8 +195,8 @@ pub use token::{get_next_token, parse_string_literal, InputStream, Token, Tokeni
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[deprecated = "this type is volatile and may change"] #[deprecated = "this type is volatile and may change"]
pub use ast::{ pub use ast::{
ASTNode, BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallExpr, Ident, ReturnType, ASTNode, BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallExpr, FnHash, Ident, OpAssignment,
ScriptFnDef, Stmt, ReturnType, ScriptFnDef, Stmt,
}; };
#[cfg(feature = "internals")] #[cfg(feature = "internals")]

View File

@ -10,16 +10,16 @@ use crate::stdlib::{
collections::HashMap, collections::HashMap,
fmt, format, fmt, format,
iter::empty, iter::empty,
num::NonZeroU64,
num::NonZeroUsize, num::NonZeroUsize,
ops::{Add, AddAssign, Deref, DerefMut}, ops::{Add, AddAssign, Deref, DerefMut},
string::{String, ToString}, string::{String, ToString},
vec::Vec, vec::Vec,
}; };
use crate::token::Token; use crate::token::Token;
use crate::utils::{combine_hashes, StraightHasherBuilder}; use crate::utils::StraightHasherBuilder;
use crate::{ use crate::{
Dynamic, EvalAltResult, ImmutableString, NativeCallContext, Position, Shared, StaticVec, calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, EvalAltResult, ImmutableString,
NativeCallContext, Position, Shared, StaticVec,
}; };
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -101,6 +101,27 @@ impl FuncInfo {
} }
} }
/// _(INTERNALS)_ Calculate a [`u64`] hash key from a namespace-qualified function name and
/// parameter types.
/// Exported under the `internals` feature only.
///
/// Module names are passed in via `&str` references from an iterator.
/// Parameter types are passed in via [`TypeId`] values from an iterator.
///
/// # Note
///
/// The first module name is skipped. Hashing starts from the _second_ module in the chain.
#[inline(always)]
fn calc_native_fn_hash<'a>(
modules: impl Iterator<Item = &'a str>,
fn_name: &str,
params: &[TypeId],
) -> u64 {
let hash_script = calc_fn_hash(modules, fn_name, params.len());
let hash_params = calc_fn_params_hash(params.iter().cloned());
combine_hashes(hash_script, hash_params)
}
/// A module which may contain variables, sub-modules, external Rust functions, /// A module which may contain variables, sub-modules, external Rust functions,
/// and/or script-defined functions. /// and/or script-defined functions.
#[derive(Clone)] #[derive(Clone)]
@ -112,12 +133,12 @@ pub struct Module {
/// [`Module`] variables. /// [`Module`] variables.
variables: HashMap<ImmutableString, Dynamic>, variables: HashMap<ImmutableString, Dynamic>,
/// Flattened collection of all [`Module`] variables, including those in sub-modules. /// Flattened collection of all [`Module`] variables, including those in sub-modules.
all_variables: HashMap<NonZeroU64, Dynamic, StraightHasherBuilder>, all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>,
/// External Rust functions. /// External Rust functions.
functions: HashMap<NonZeroU64, FuncInfo, StraightHasherBuilder>, functions: HashMap<u64, FuncInfo, StraightHasherBuilder>,
/// Flattened collection of all external Rust functions, native or scripted. /// Flattened collection of all external Rust functions, native or scripted.
/// including those in sub-modules. /// including those in sub-modules.
all_functions: HashMap<NonZeroU64, CallableFunction, StraightHasherBuilder>, all_functions: HashMap<u64, CallableFunction, StraightHasherBuilder>,
/// Iterator functions, keyed by the type producing the iterator. /// Iterator functions, keyed by the type producing the iterator.
type_iterators: HashMap<TypeId, IteratorFn>, type_iterators: HashMap<TypeId, IteratorFn>,
/// Flattened collection of iterator functions, including those in sub-modules. /// Flattened collection of iterator functions, including those in sub-modules.
@ -434,19 +455,13 @@ impl Module {
) -> &mut Self { ) -> &mut Self {
self.variables.insert(name.into(), Dynamic::from(value)); self.variables.insert(name.into(), Dynamic::from(value));
self.indexed = false; self.indexed = false;
self.contains_indexed_global_functions = false;
self self
} }
/// Get a reference to a namespace-qualified variable. /// Get a reference to a namespace-qualified variable.
/// Name and Position in [`EvalAltResult`] are [`None`] and [`NONE`][Position::NONE] and must be set afterwards. /// Name and Position in [`EvalAltResult`] are [`None`] and [`NONE`][Position::NONE] and must be set afterwards.
///
/// The [`NonZeroU64`] hash is calculated by the function [`calc_native_fn_hash`][crate::calc_native_fn_hash].
#[inline(always)] #[inline(always)]
pub(crate) fn get_qualified_var( pub(crate) fn get_qualified_var(&self, hash_var: u64) -> Result<&Dynamic, Box<EvalAltResult>> {
&self,
hash_var: NonZeroU64,
) -> Result<&Dynamic, Box<EvalAltResult>> {
self.all_variables.get(&hash_var).ok_or_else(|| { self.all_variables.get(&hash_var).ok_or_else(|| {
EvalAltResult::ErrorVariableNotFound(String::new(), Position::NONE).into() EvalAltResult::ErrorVariableNotFound(String::new(), Position::NONE).into()
}) })
@ -460,12 +475,12 @@ impl Module {
pub(crate) fn set_script_fn( pub(crate) fn set_script_fn(
&mut self, &mut self,
fn_def: impl Into<Shared<crate::ast::ScriptFnDef>>, fn_def: impl Into<Shared<crate::ast::ScriptFnDef>>,
) -> NonZeroU64 { ) -> u64 {
let fn_def = fn_def.into(); let fn_def = fn_def.into();
// 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_script_fn_hash(empty(), &fn_def.name, num_params).unwrap(); let hash_script = crate::calc_fn_hash(empty(), &fn_def.name, num_params);
let mut param_names: StaticVec<_> = fn_def.params.iter().cloned().collect(); let mut param_names: StaticVec<_> = fn_def.params.iter().cloned().collect();
param_names.push("Dynamic".into()); param_names.push("Dynamic".into());
self.functions.insert( self.functions.insert(
@ -593,8 +608,7 @@ impl Module {
/// Does the particular Rust function exist in the [`Module`]? /// Does the particular Rust function exist in the [`Module`]?
/// ///
/// The [`NonZeroU64`] hash is calculated by the function [`calc_native_fn_hash`][crate::calc_native_fn_hash]. /// The [`u64`] hash is returned by the `set_fn_XXX` calls.
/// It is also returned by the `set_fn_XXX` calls.
/// ///
/// # Example /// # Example
/// ///
@ -606,7 +620,7 @@ impl Module {
/// assert!(module.contains_fn(hash, true)); /// assert!(module.contains_fn(hash, true));
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn contains_fn(&self, hash_fn: NonZeroU64, public_only: bool) -> bool { pub fn contains_fn(&self, hash_fn: u64, public_only: bool) -> bool {
if public_only { if public_only {
self.functions self.functions
.get(&hash_fn) .get(&hash_fn)
@ -621,9 +635,7 @@ impl Module {
/// Update the metadata (parameter names/types and return type) of a registered function. /// Update the metadata (parameter names/types and return type) of a registered function.
/// ///
/// The [`NonZeroU64`] hash is calculated either by the function /// The [`u64`] hash is returned by the `set_fn_XXX` calls.
/// [`calc_native_fn_hash`][crate::calc_native_fn_hash] or the function
/// [`calc_script_fn_hash`][crate::calc_script_fn_hash].
/// ///
/// ## Parameter Names and Types /// ## Parameter Names and Types
/// ///
@ -634,7 +646,7 @@ impl Module {
/// The _last entry_ in the list should be the _return type_ of the function. /// The _last entry_ in the list should be the _return type_ of the function.
/// In other words, the number of entries should be one larger than the number of parameters. /// In other words, the number of entries should be one larger than the number of parameters.
#[inline(always)] #[inline(always)]
pub fn update_fn_metadata(&mut self, hash_fn: NonZeroU64, arg_names: &[&str]) -> &mut Self { pub fn update_fn_metadata(&mut self, hash_fn: u64, arg_names: &[&str]) -> &mut Self {
if let Some(f) = self.functions.get_mut(&hash_fn) { if let Some(f) = self.functions.get_mut(&hash_fn) {
f.param_names = arg_names.iter().map(|&n| n.into()).collect(); f.param_names = arg_names.iter().map(|&n| n.into()).collect();
} }
@ -643,15 +655,9 @@ impl Module {
/// Update the namespace of a registered function. /// Update the namespace of a registered function.
/// ///
/// The [`NonZeroU64`] hash is calculated either by the function /// The [`u64`] hash is returned by the `set_fn_XXX` calls.
/// [`calc_native_fn_hash`][crate::calc_native_fn_hash] or the function
/// [`calc_script_fn_hash`][crate::calc_script_fn_hash].
#[inline(always)] #[inline(always)]
pub fn update_fn_namespace( pub fn update_fn_namespace(&mut self, hash_fn: u64, namespace: FnNamespace) -> &mut Self {
&mut self,
hash_fn: NonZeroU64,
namespace: FnNamespace,
) -> &mut Self {
if let Some(f) = self.functions.get_mut(&hash_fn) { if let Some(f) = self.functions.get_mut(&hash_fn) {
f.namespace = namespace; f.namespace = namespace;
} }
@ -676,24 +682,34 @@ impl Module {
arg_names: Option<&[&str]>, arg_names: Option<&[&str]>,
arg_types: &[TypeId], arg_types: &[TypeId],
func: CallableFunction, func: CallableFunction,
) -> NonZeroU64 { ) -> u64 {
let name = name.into(); let name = name.into();
let is_method = func.is_method();
let hash_fn =
crate::calc_native_fn_hash(empty(), &name, arg_types.iter().cloned()).unwrap();
let param_types = arg_types let param_types = arg_types
.into_iter() .iter()
.cloned() .cloned()
.map(|id| { .enumerate()
if id == TypeId::of::<&str>() || id == TypeId::of::<String>() { .map(|(i, type_id)| {
if !is_method || i > 0 {
if type_id == TypeId::of::<&str>() {
// Map &str to ImmutableString
TypeId::of::<ImmutableString>()
} else if type_id == TypeId::of::<String>() {
// Map String to ImmutableString
TypeId::of::<ImmutableString>() TypeId::of::<ImmutableString>()
} else { } else {
id type_id
}
} else {
// Do not map &mut parameter
type_id
} }
}) })
.collect::<StaticVec<_>>(); .collect::<StaticVec<_>>();
let hash_fn = calc_native_fn_hash(empty(), &name, &param_types);
self.functions.insert( self.functions.insert(
hash_fn, hash_fn,
FuncInfo { FuncInfo {
@ -793,7 +809,7 @@ impl Module {
func: impl Fn(NativeCallContext, &mut FnCallArgs) -> Result<T, Box<EvalAltResult>> func: impl Fn(NativeCallContext, &mut FnCallArgs) -> Result<T, Box<EvalAltResult>>
+ SendSync + SendSync
+ 'static, + 'static,
) -> NonZeroU64 { ) -> u64 {
let f = let f =
move |ctx: NativeCallContext, args: &mut FnCallArgs| func(ctx, args).map(Dynamic::from); move |ctx: NativeCallContext, args: &mut FnCallArgs| func(ctx, args).map(Dynamic::from);
@ -829,7 +845,7 @@ impl Module {
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
func: impl Fn() -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn() -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> NonZeroU64 { ) -> u64 {
let f = move |_: NativeCallContext, _: &mut FnCallArgs| func().map(Dynamic::from); let f = move |_: NativeCallContext, _: &mut FnCallArgs| func().map(Dynamic::from);
let arg_types = []; let arg_types = [];
self.set_fn( self.set_fn(
@ -864,7 +880,7 @@ impl Module {
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
func: impl Fn(A) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(A) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> NonZeroU64 { ) -> u64 {
let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
func(cast_arg::<A>(&mut args[0])).map(Dynamic::from) func(cast_arg::<A>(&mut args[0])).map(Dynamic::from)
}; };
@ -904,7 +920,7 @@ impl Module {
name: impl Into<String>, name: impl Into<String>,
namespace: FnNamespace, namespace: FnNamespace,
func: impl Fn(&mut A) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(&mut A) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> NonZeroU64 { ) -> u64 {
let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
func(&mut args[0].write_lock::<A>().unwrap()).map(Dynamic::from) func(&mut args[0].write_lock::<A>().unwrap()).map(Dynamic::from)
}; };
@ -943,7 +959,7 @@ impl Module {
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
func: impl Fn(&mut A) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(&mut A) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> NonZeroU64 { ) -> u64 {
self.set_fn_1_mut( self.set_fn_1_mut(
crate::engine::make_getter(&name.into()), crate::engine::make_getter(&name.into()),
FnNamespace::Global, FnNamespace::Global,
@ -975,7 +991,7 @@ impl Module {
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
func: impl Fn(A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> NonZeroU64 { ) -> u64 {
let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
let a = cast_arg::<A>(&mut args[0]); let a = cast_arg::<A>(&mut args[0]);
let b = cast_arg::<B>(&mut args[1]); let b = cast_arg::<B>(&mut args[1]);
@ -1022,7 +1038,7 @@ impl Module {
name: impl Into<String>, name: impl Into<String>,
namespace: FnNamespace, namespace: FnNamespace,
func: impl Fn(&mut A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(&mut A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> NonZeroU64 { ) -> u64 {
let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
let b = cast_arg::<B>(&mut args[1]); let b = cast_arg::<B>(&mut args[1]);
let a = &mut args[0].write_lock::<A>().unwrap(); let a = &mut args[0].write_lock::<A>().unwrap();
@ -1068,7 +1084,7 @@ impl Module {
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
func: impl Fn(&mut A, B) -> Result<(), Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(&mut A, B) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
) -> NonZeroU64 { ) -> u64 {
self.set_fn_2_mut( self.set_fn_2_mut(
crate::engine::make_setter(&name.into()), crate::engine::make_setter(&name.into()),
FnNamespace::Global, FnNamespace::Global,
@ -1107,7 +1123,7 @@ impl Module {
pub fn set_indexer_get_fn<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>( pub fn set_indexer_get_fn<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self, &mut self,
func: impl Fn(&mut A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(&mut A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> NonZeroU64 { ) -> u64 {
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.");
} }
@ -1154,7 +1170,7 @@ impl Module {
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
func: impl Fn(A, B, C) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(A, B, C) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> NonZeroU64 { ) -> u64 {
let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
let a = cast_arg::<A>(&mut args[0]); let a = cast_arg::<A>(&mut args[0]);
let b = cast_arg::<B>(&mut args[1]); let b = cast_arg::<B>(&mut args[1]);
@ -1207,7 +1223,7 @@ impl Module {
name: impl Into<String>, name: impl Into<String>,
namespace: FnNamespace, namespace: FnNamespace,
func: impl Fn(&mut A, B, C) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(&mut A, B, C) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> NonZeroU64 { ) -> u64 {
let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
let b = cast_arg::<B>(&mut args[2]); let b = cast_arg::<B>(&mut args[2]);
let c = cast_arg::<C>(&mut args[3]); let c = cast_arg::<C>(&mut args[3]);
@ -1258,7 +1274,7 @@ impl Module {
pub fn set_indexer_set_fn<A: Variant + Clone, B: Variant + Clone, C: Variant + Clone>( pub fn set_indexer_set_fn<A: Variant + Clone, B: Variant + Clone, C: Variant + Clone>(
&mut self, &mut self,
func: impl Fn(&mut A, B, C) -> Result<(), Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(&mut A, B, C) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
) -> NonZeroU64 { ) -> u64 {
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.");
} }
@ -1330,7 +1346,7 @@ impl Module {
&mut self, &mut self,
get_fn: impl Fn(&mut A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, get_fn: impl Fn(&mut A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
set_fn: impl Fn(&mut A, B, T) -> Result<(), Box<EvalAltResult>> + SendSync + 'static, set_fn: impl Fn(&mut A, B, T) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
) -> (NonZeroU64, NonZeroU64) { ) -> (u64, u64) {
( (
self.set_indexer_get_fn(get_fn), self.set_indexer_get_fn(get_fn),
self.set_indexer_set_fn(set_fn), self.set_indexer_set_fn(set_fn),
@ -1367,7 +1383,7 @@ impl Module {
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
func: impl Fn(A, B, C, D) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(A, B, C, D) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> NonZeroU64 { ) -> u64 {
let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
let a = cast_arg::<A>(&mut args[0]); let a = cast_arg::<A>(&mut args[0]);
let b = cast_arg::<B>(&mut args[1]); let b = cast_arg::<B>(&mut args[1]);
@ -1427,7 +1443,7 @@ impl Module {
name: impl Into<String>, name: impl Into<String>,
namespace: FnNamespace, namespace: FnNamespace,
func: impl Fn(&mut A, B, C, D) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(&mut A, B, C, D) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> NonZeroU64 { ) -> u64 {
let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
let b = cast_arg::<B>(&mut args[1]); let b = cast_arg::<B>(&mut args[1]);
let c = cast_arg::<C>(&mut args[2]); let c = cast_arg::<C>(&mut args[2]);
@ -1454,14 +1470,9 @@ impl Module {
/// Get a Rust function. /// Get a Rust function.
/// ///
/// The [`NonZeroU64`] hash is calculated by the function [`calc_native_fn_hash`][crate::calc_native_fn_hash]. /// The [`u64`] hash is returned by the `set_fn_XXX` calls.
/// It is also returned by the `set_fn_XXX` calls.
#[inline(always)] #[inline(always)]
pub(crate) fn get_fn( pub(crate) fn get_fn(&self, hash_fn: u64, public_only: bool) -> Option<&CallableFunction> {
&self,
hash_fn: NonZeroU64,
public_only: bool,
) -> Option<&CallableFunction> {
self.functions self.functions
.get(&hash_fn) .get(&hash_fn)
.and_then(|FuncInfo { access, func, .. }| match access { .and_then(|FuncInfo { access, func, .. }| match access {
@ -1473,24 +1484,17 @@ impl Module {
/// Does the particular namespace-qualified function exist in the [`Module`]? /// Does the particular namespace-qualified function exist in the [`Module`]?
/// ///
/// The [`NonZeroU64`] hash is calculated by the function /// The [`u64`] hash is calculated by [`build_index`][Module::build_index].
/// [`calc_native_fn_hash`][crate::calc_native_fn_hash] and must match
/// the hash calculated by [`build_index`][Module::build_index].
#[inline(always)] #[inline(always)]
pub fn contains_qualified_fn(&self, hash_fn: NonZeroU64) -> bool { pub fn contains_qualified_fn(&self, hash_fn: u64) -> bool {
self.all_functions.contains_key(&hash_fn) self.all_functions.contains_key(&hash_fn)
} }
/// Get a namespace-qualified function. /// Get a namespace-qualified function.
/// ///
/// The [`NonZeroU64`] hash is calculated by the function /// The [`u64`] hash is calculated by [`build_index`][Module::build_index].
/// [`calc_native_fn_hash`][crate::calc_native_fn_hash] and must match
/// the hash calculated by [`build_index`][Module::build_index].
#[inline(always)] #[inline(always)]
pub(crate) fn get_qualified_fn( pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&CallableFunction> {
&self,
hash_qualified_fn: NonZeroU64,
) -> Option<&CallableFunction> {
self.all_functions.get(&hash_qualified_fn) self.all_functions.get(&hash_qualified_fn)
} }
@ -1854,8 +1858,8 @@ impl Module {
fn index_module<'a>( fn index_module<'a>(
module: &'a Module, module: &'a Module,
qualifiers: &mut Vec<&'a str>, qualifiers: &mut Vec<&'a str>,
variables: &mut HashMap<NonZeroU64, Dynamic, StraightHasherBuilder>, variables: &mut HashMap<u64, Dynamic, StraightHasherBuilder>,
functions: &mut HashMap<NonZeroU64, CallableFunction, StraightHasherBuilder>, functions: &mut HashMap<u64, CallableFunction, StraightHasherBuilder>,
type_iterators: &mut HashMap<TypeId, IteratorFn>, type_iterators: &mut HashMap<TypeId, IteratorFn>,
) -> bool { ) -> bool {
let mut contains_indexed_global_functions = false; let mut contains_indexed_global_functions = false;
@ -1871,8 +1875,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 = let hash_var = crate::calc_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0);
crate::calc_script_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0).unwrap();
variables.insert(hash_var, value.clone()); variables.insert(hash_var, value.clone());
}); });
@ -1909,26 +1912,13 @@ impl Module {
FnAccess::Private => return, // Do not index private functions FnAccess::Private => return, // Do not index private functions
} }
let hash_qualified_script =
crate::calc_script_fn_hash(qualifiers.iter().cloned(), name, *params)
.unwrap();
if !func.is_script() { if !func.is_script() {
assert_eq!(*params, param_types.len()); let hash_qualified_fn =
calc_native_fn_hash(qualifiers.iter().cloned(), name, param_types);
// Namespace-qualified Rust functions are indexed in two steps:
// 1) Calculate a hash in a similar manner to script-defined functions,
// i.e. qualifiers + function name + number of arguments.
// 2) Calculate a second hash with no qualifiers, empty function name,
// and the actual list of argument [`TypeId`]'.s
let hash_fn_args =
crate::calc_native_fn_hash(empty(), "", param_types.iter().cloned())
.unwrap();
// 3) The two hashes are combined.
let hash_qualified_fn = combine_hashes(hash_qualified_script, hash_fn_args);
functions.insert(hash_qualified_fn, func.clone()); functions.insert(hash_qualified_fn, func.clone());
} else if cfg!(not(feature = "no_function")) { } else if cfg!(not(feature = "no_function")) {
let hash_qualified_script =
crate::calc_fn_hash(qualifiers.iter().cloned(), name, *params);
functions.insert(hash_qualified_script, func.clone()); functions.insert(hash_qualified_script, func.clone());
} }
}, },
@ -2016,7 +2006,7 @@ impl Module {
/// _(INTERNALS)_ A chain of [module][Module] names to namespace-qualify a variable or function call. /// _(INTERNALS)_ A chain of [module][Module] names to namespace-qualify a variable or function call.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///
/// A [`NonZeroU64`] offset to the current [`Scope`][crate::Scope] is cached for quick search purposes. /// A [`u64`] offset to the current [`Scope`][crate::Scope] is cached for quick search purposes.
/// ///
/// A [`StaticVec`] is used because most namespace-qualified access contains only one level, /// A [`StaticVec`] is used because most namespace-qualified access contains only one level,
/// and it is wasteful to always allocate a [`Vec`] with one element. /// and it is wasteful to always allocate a [`Vec`] with one element.

View File

@ -15,8 +15,8 @@ use crate::stdlib::{
vec::Vec, vec::Vec,
}; };
use crate::token::is_valid_identifier; use crate::token::is_valid_identifier;
use crate::utils::get_hasher; use crate::utils::{calc_fn_hash, get_hasher};
use crate::{calc_native_fn_hash, Dynamic, Engine, Module, Position, Scope, StaticVec, AST}; use crate::{Dynamic, Engine, Module, Position, Scope, StaticVec, AST};
/// Level of optimization performed. /// Level of optimization performed.
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] #[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
@ -133,7 +133,7 @@ fn call_fn_with_constant_arguments(
arg_values: &mut [Dynamic], arg_values: &mut [Dynamic],
) -> Option<Dynamic> { ) -> Option<Dynamic> {
// Search built-in's and external functions // Search built-in's and external functions
let hash_fn = calc_native_fn_hash(empty(), fn_name, arg_values.iter().map(|a| a.type_id())); let hash_native = calc_fn_hash(empty(), fn_name, arg_values.len());
state state
.engine .engine
@ -142,7 +142,7 @@ fn call_fn_with_constant_arguments(
&mut Default::default(), &mut Default::default(),
state.lib, state.lib,
fn_name, fn_name,
hash_fn.unwrap(), hash_native,
arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(), arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(),
false, false,
false, false,
@ -295,10 +295,10 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
match stmt { match stmt {
// expr op= expr // expr op= expr
Stmt::Assignment(x, _) => match x.0 { Stmt::Assignment(x, _) => match x.0 {
Expr::Variable(_) => optimize_expr(&mut x.2, state), Expr::Variable(_) => optimize_expr(&mut x.1, state),
_ => { _ => {
optimize_expr(&mut x.0, state); optimize_expr(&mut x.0, state);
optimize_expr(&mut x.2, state); optimize_expr(&mut x.1, state);
} }
}, },
@ -518,7 +518,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.iter().all(|(_, x)| x.is_pure()) => { (Expr::Map(m, pos), Expr::Property(p)) if m.iter().all(|(_, x)| x.is_pure()) => {
let prop = &p.2.name; let prop = &p.4.name;
// 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();
@ -676,7 +676,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect(); let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
// Search for overloaded operators (can override built-in). // Search for overloaded operators (can override built-in).
if !state.engine.has_override_by_name_and_arguments(Some(&Default::default()), &mut Default::default(), state.lib, x.name.as_ref(), arg_types.as_ref()) { if !state.engine.has_native_override(Some(&Default::default()), &mut Default::default(), state.lib, x.name.as_ref(), 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(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();

View File

@ -1,6 +1,9 @@
//! Main module defining the lexer and parser. //! Main module defining the lexer and parser.
use crate::ast::{BinaryExpr, CustomExpr, Expr, FnCallExpr, Ident, ReturnType, ScriptFnDef, Stmt}; use crate::ast::{
BinaryExpr, CustomExpr, Expr, FnCallExpr, FnHash, Ident, OpAssignment, ReturnType, ScriptFnDef,
Stmt,
};
use crate::dynamic::{AccessMode, Union}; use crate::dynamic::{AccessMode, Union};
use crate::engine::KEYWORD_THIS; use crate::engine::KEYWORD_THIS;
use crate::module::NamespaceRef; use crate::module::NamespaceRef;
@ -13,7 +16,7 @@ use crate::stdlib::{
format, format,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
iter::empty, iter::empty,
num::{NonZeroU64, NonZeroUsize}, num::NonZeroUsize,
string::{String, ToString}, string::{String, ToString},
vec, vec,
vec::Vec, vec::Vec,
@ -22,8 +25,8 @@ use crate::syntax::{CustomSyntax, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
use crate::token::{is_keyword_function, is_valid_identifier, Token, TokenStream}; use crate::token::{is_keyword_function, is_valid_identifier, Token, TokenStream};
use crate::utils::{get_hasher, StraightHasherBuilder}; use crate::utils::{get_hasher, StraightHasherBuilder};
use crate::{ use crate::{
calc_script_fn_hash, Dynamic, Engine, ImmutableString, LexError, ParseError, ParseErrorType, calc_fn_hash, Dynamic, Engine, ImmutableString, LexError, ParseError, ParseErrorType, Position,
Position, Scope, StaticVec, AST, Scope, StaticVec, AST,
}; };
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -34,7 +37,7 @@ use crate::FnAccess;
type PERR = ParseErrorType; type PERR = ParseErrorType;
type FunctionsLib = HashMap<NonZeroU64, ScriptFnDef, StraightHasherBuilder>; type FunctionsLib = HashMap<u64, ScriptFnDef, StraightHasherBuilder>;
/// A type that encapsulates the current state of the parser. /// A type that encapsulates the current state of the parser.
#[derive(Debug)] #[derive(Debug)]
@ -236,8 +239,11 @@ impl Expr {
Self::Variable(x) if x.1.is_none() => { Self::Variable(x) if x.1.is_none() => {
let ident = x.2; let ident = x.2;
let getter = state.get_interned_string(crate::engine::make_getter(&ident.name)); let getter = state.get_interned_string(crate::engine::make_getter(&ident.name));
let hash_get = calc_fn_hash(empty(), &getter, 1);
let setter = state.get_interned_string(crate::engine::make_setter(&ident.name)); let setter = state.get_interned_string(crate::engine::make_setter(&ident.name));
Self::Property(Box::new((getter, setter, ident.into()))) let hash_set = calc_fn_hash(empty(), &setter, 2);
Self::Property(Box::new((getter, hash_get, setter, hash_set, ident.into())))
} }
_ => self, _ => self,
} }
@ -334,33 +340,25 @@ fn parse_fn_call(
Token::RightParen => { Token::RightParen => {
eat_token(input, Token::RightParen); eat_token(input, Token::RightParen);
let mut hash_script = if let Some(ref mut modules) = namespace { let hash = if let Some(ref mut modules) = namespace {
#[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));
// Rust functions are indexed in two steps: calc_fn_hash(modules.iter().map(|m| m.name.as_str()), &id, 0)
// 1) Calculate a hash in a similar manner to script-defined functions,
// i.e. qualifiers + function name + number of arguments.
// 2) Calculate a second hash with no qualifiers, empty function name,
// zero number of arguments, and the actual list of argument `TypeId`'s.
// 3) The final hash is the XOR of the two hashes.
let qualifiers = modules.iter().map(|m| m.name.as_str());
calc_script_fn_hash(qualifiers, &id, 0)
} else { } else {
calc_script_fn_hash(empty(), &id, 0) calc_fn_hash(empty(), &id, 0)
}; };
// script functions can only be valid identifiers
if !is_valid_identifier(id.chars()) {
hash_script = None;
}
return Ok(Expr::FnCall( return Ok(Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
name: id.to_string().into(), name: id.to_string().into(),
capture, capture,
namespace, namespace,
hash_script, hash: if is_valid_identifier(id.chars()) {
FnHash::from_script(hash)
} else {
FnHash::from_native(hash)
},
args, args,
..Default::default() ..Default::default()
}), }),
@ -385,33 +383,25 @@ fn parse_fn_call(
(Token::RightParen, _) => { (Token::RightParen, _) => {
eat_token(input, Token::RightParen); eat_token(input, Token::RightParen);
let mut hash_script = if let Some(modules) = namespace.as_mut() { let hash = if let Some(modules) = namespace.as_mut() {
#[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));
// Rust functions are indexed in two steps: calc_fn_hash(modules.iter().map(|m| m.name.as_str()), &id, args.len())
// 1) Calculate a hash in a similar manner to script-defined functions,
// i.e. qualifiers + function name + number of arguments.
// 2) Calculate a second hash with no qualifiers, empty function name,
// zero number of arguments, and the actual list of argument `TypeId`'s.
// 3) The final hash is the XOR of the two hashes.
let qualifiers = modules.iter().map(|m| m.name.as_str());
calc_script_fn_hash(qualifiers, &id, args.len())
} else { } else {
calc_script_fn_hash(empty(), &id, args.len()) calc_fn_hash(empty(), &id, args.len())
}; };
// script functions can only be valid identifiers
if !is_valid_identifier(id.chars()) {
hash_script = None;
}
return Ok(Expr::FnCall( return Ok(Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
name: id.to_string().into(), name: id.to_string().into(),
capture, capture,
namespace, namespace,
hash_script, hash: if is_valid_identifier(id.chars()) {
FnHash::from_script(hash)
} else {
FnHash::from_native(hash)
},
args, args,
..Default::default() ..Default::default()
}), }),
@ -1013,10 +1003,8 @@ fn parse_primary(
state.access_var(closure, *pos); state.access_var(closure, *pos);
}); });
lib.insert( let hash_script = calc_fn_hash(empty(), &func.name, func.params.len());
calc_script_fn_hash(empty(), &func.name, func.params.len()).unwrap(), lib.insert(hash_script, func);
func,
);
expr expr
} }
@ -1183,7 +1171,7 @@ fn parse_primary(
} else { } else {
let mut ns: NamespaceRef = Default::default(); let mut ns: NamespaceRef = Default::default();
ns.push(var_name_def); ns.push(var_name_def);
let index = NonZeroU64::new(42).unwrap(); // Dummy let index = 42; // Dummy
namespace = Some((index, ns)); namespace = Some((index, ns));
} }
@ -1243,8 +1231,7 @@ fn parse_primary(
} }
.map(|x| match x.as_mut() { .map(|x| match x.as_mut() {
(_, Some((ref mut hash, ref mut namespace)), Ident { name, .. }) => { (_, Some((ref mut hash, ref mut namespace)), Ident { name, .. }) => {
*hash = *hash = calc_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0);
calc_script_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0).unwrap();
#[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));
@ -1300,6 +1287,7 @@ fn parse_unary(
Ok(Expr::FnCall( Ok(Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
name: op.into(), name: op.into(),
hash: FnHash::from_native(calc_fn_hash(empty(), op, 1)),
args, args,
..Default::default() ..Default::default()
}), }),
@ -1326,6 +1314,7 @@ fn parse_unary(
Ok(Expr::FnCall( Ok(Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
name: op.into(), name: op.into(),
hash: FnHash::from_native(calc_fn_hash(empty(), op, 1)),
args, args,
..Default::default() ..Default::default()
}), }),
@ -1346,6 +1335,7 @@ fn parse_unary(
Ok(Expr::FnCall( Ok(Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
name: op.into(), name: op.into(),
hash: FnHash::from_native(calc_fn_hash(empty(), op, 1)),
args, args,
..Default::default() ..Default::default()
}), }),
@ -1361,7 +1351,7 @@ fn parse_unary(
/// Make an assignment statement. /// Make an assignment statement.
fn make_assignment_stmt<'a>( fn make_assignment_stmt<'a>(
fn_name: Cow<'static, str>, op: Cow<'static, str>,
state: &mut ParseState, state: &mut ParseState,
lhs: Expr, lhs: Expr,
rhs: Expr, rhs: Expr,
@ -1384,24 +1374,34 @@ fn make_assignment_stmt<'a>(
} }
} }
let op_info = if op.is_empty() {
None
} else {
let op2 = &op[..op.len() - 1]; // extract operator without =
Some(OpAssignment {
hash_op_assign: calc_fn_hash(empty(), &op, 2),
hash_op: calc_fn_hash(empty(), op2, 2),
op,
})
};
match &lhs { match &lhs {
// const_expr = rhs // const_expr = rhs
expr if expr.is_constant() => { expr if expr.is_constant() => {
Err(PERR::AssignmentToConstant("".into()).into_err(lhs.position())) Err(PERR::AssignmentToConstant("".into()).into_err(lhs.position()))
} }
// var (non-indexed) = rhs // var (non-indexed) = rhs
Expr::Variable(x) if x.0.is_none() => Ok(Stmt::Assignment( Expr::Variable(x) if x.0.is_none() => {
Box::new((lhs, fn_name.into(), rhs)), Ok(Stmt::Assignment(Box::new((lhs, rhs, op_info)), op_pos))
op_pos, }
)),
// var (indexed) = rhs // var (indexed) = rhs
Expr::Variable(x) => { Expr::Variable(x) => {
let (index, _, Ident { name, pos }) = x.as_ref(); let (index, _, Ident { name, pos }) = x.as_ref();
match state.stack[(state.stack.len() - index.unwrap().get())].1 { match state.stack[(state.stack.len() - index.unwrap().get())].1 {
AccessMode::ReadWrite => Ok(Stmt::Assignment( AccessMode::ReadWrite => {
Box::new((lhs, fn_name.into(), rhs)), Ok(Stmt::Assignment(Box::new((lhs, rhs, op_info)), op_pos))
op_pos, }
)),
// Constant values cannot be assigned to // Constant values cannot be assigned to
AccessMode::ReadOnly => { AccessMode::ReadOnly => {
Err(PERR::AssignmentToConstant(name.to_string()).into_err(*pos)) Err(PERR::AssignmentToConstant(name.to_string()).into_err(*pos))
@ -1413,18 +1413,16 @@ fn make_assignment_stmt<'a>(
match check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _))) { match check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _))) {
Position::NONE => match &x.lhs { Position::NONE => match &x.lhs {
// var[???] (non-indexed) = rhs, var.??? (non-indexed) = rhs // var[???] (non-indexed) = rhs, var.??? (non-indexed) = rhs
Expr::Variable(x) if x.0.is_none() => Ok(Stmt::Assignment( Expr::Variable(x) if x.0.is_none() => {
Box::new((lhs, fn_name.into(), rhs)), Ok(Stmt::Assignment(Box::new((lhs, rhs, op_info)), op_pos))
op_pos, }
)),
// var[???] (indexed) = rhs, var.??? (indexed) = rhs // var[???] (indexed) = rhs, var.??? (indexed) = rhs
Expr::Variable(x) => { Expr::Variable(x) => {
let (index, _, Ident { name, pos }) = x.as_ref(); let (index, _, Ident { name, pos }) = x.as_ref();
match state.stack[(state.stack.len() - index.unwrap().get())].1 { match state.stack[(state.stack.len() - index.unwrap().get())].1 {
AccessMode::ReadWrite => Ok(Stmt::Assignment( AccessMode::ReadWrite => {
Box::new((lhs, fn_name.into(), rhs)), Ok(Stmt::Assignment(Box::new((lhs, rhs, op_info)), op_pos))
op_pos, }
)),
// Constant values cannot be assigned to // Constant values cannot be assigned to
AccessMode::ReadOnly => { AccessMode::ReadOnly => {
Err(PERR::AssignmentToConstant(name.to_string()).into_err(*pos)) Err(PERR::AssignmentToConstant(name.to_string()).into_err(*pos))
@ -1506,8 +1504,11 @@ fn make_dot_expr(
(lhs, Expr::Variable(x)) if x.1.is_none() => { (lhs, Expr::Variable(x)) if x.1.is_none() => {
let ident = x.2; let ident = x.2;
let getter = state.get_interned_string(crate::engine::make_getter(&ident.name)); let getter = state.get_interned_string(crate::engine::make_getter(&ident.name));
let hash_get = calc_fn_hash(empty(), &getter, 1);
let setter = state.get_interned_string(crate::engine::make_setter(&ident.name)); let setter = state.get_interned_string(crate::engine::make_setter(&ident.name));
let rhs = Expr::Property(Box::new((getter, setter, ident))); let hash_set = calc_fn_hash(empty(), &setter, 2);
let rhs = Expr::Property(Box::new((getter, hash_get, setter, hash_set, ident)));
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
} }
@ -1521,7 +1522,7 @@ fn make_dot_expr(
} }
// lhs.dot_lhs.dot_rhs // lhs.dot_lhs.dot_rhs
(lhs, Expr::Dot(x, pos)) => match x.lhs { (lhs, Expr::Dot(x, pos)) => match x.lhs {
Expr::Variable(_) | Expr::Property(_) | Expr::FnCall(_, _) => { Expr::Variable(_) | Expr::Property(_) => {
let rhs = Expr::Dot( let rhs = Expr::Dot(
Box::new(BinaryExpr { Box::new(BinaryExpr {
lhs: x.lhs.into_property(state), lhs: x.lhs.into_property(state),
@ -1531,6 +1532,22 @@ fn make_dot_expr(
); );
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
} }
Expr::FnCall(mut func, func_pos) => {
// Recalculate hash
func.hash = FnHash::from_script_and_native(
calc_fn_hash(empty(), &func.name, func.args.len()),
calc_fn_hash(empty(), &func.name, func.args.len() + 1),
);
let rhs = Expr::Dot(
Box::new(BinaryExpr {
lhs: Expr::FnCall(func, func_pos),
rhs: x.rhs,
}),
pos,
);
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
}
_ => unreachable!("invalid dot expression: {:?}", x.lhs), _ => unreachable!("invalid dot expression: {:?}", x.lhs),
}, },
// lhs.idx_lhs[idx_rhs] // lhs.idx_lhs[idx_rhs]
@ -1544,6 +1561,10 @@ fn make_dot_expr(
); );
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
} }
// lhs.nnn::func(...)
(_, Expr::FnCall(x, _)) if x.namespace.is_some() => {
unreachable!("method call should not be namespace-qualified")
}
// lhs.Fn() or lhs.eval() // lhs.Fn() or lhs.eval()
(_, Expr::FnCall(x, pos)) (_, Expr::FnCall(x, pos))
if x.args.len() == 0 if x.args.len() == 0
@ -1567,8 +1588,14 @@ fn make_dot_expr(
.into_err(pos)) .into_err(pos))
} }
// lhs.func(...) // lhs.func(...)
(lhs, func @ Expr::FnCall(_, _)) => { (lhs, Expr::FnCall(mut func, func_pos)) => {
Expr::Dot(Box::new(BinaryExpr { lhs, rhs: func }), op_pos) // Recalculate hash
func.hash = FnHash::from_script_and_native(
calc_fn_hash(empty(), &func.name, func.args.len()),
calc_fn_hash(empty(), &func.name, func.args.len() + 1),
);
let rhs = Expr::FnCall(func, func_pos);
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
} }
// lhs.rhs // lhs.rhs
(_, rhs) => return Err(PERR::PropertyExpected.into_err(rhs.position())), (_, rhs) => return Err(PERR::PropertyExpected.into_err(rhs.position())),
@ -1792,9 +1819,11 @@ 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 op_base = FnCallExpr { let op_base = FnCallExpr {
name: op, name: op,
hash: FnHash::from_native(hash),
capture: false, capture: false,
..Default::default() ..Default::default()
}; };
@ -1863,16 +1892,15 @@ fn parse_binary_op(
.get(&s) .get(&s)
.map_or(false, Option::is_some) => .map_or(false, Option::is_some) =>
{ {
let hash_script = if is_valid_identifier(s.chars()) { let hash = calc_fn_hash(empty(), &s, 2);
// Accept non-native functions for custom operators
calc_script_fn_hash(empty(), &s, 2)
} else {
None
};
Expr::FnCall( Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
hash_script, hash: if is_valid_identifier(s.chars()) {
FnHash::from_script(hash)
} else {
FnHash::from_native(hash)
},
args, args,
..op_base ..op_base
}), }),
@ -2604,7 +2632,7 @@ fn parse_stmt(
}; };
let func = parse_fn(input, &mut new_state, lib, access, settings, _comments)?; let func = parse_fn(input, &mut new_state, lib, access, settings, _comments)?;
let hash = calc_script_fn_hash(empty(), &func.name, func.params.len()).unwrap(); let hash = calc_fn_hash(empty(), &func.name, func.params.len());
if lib.contains_key(&hash) { if lib.contains_key(&hash) {
return Err(PERR::FnDuplicatedDefinition( return Err(PERR::FnDuplicatedDefinition(
@ -2871,12 +2899,10 @@ fn make_curry_from_externals(fn_expr: Expr, externals: StaticVec<Ident>, pos: Po
let curry_func = crate::engine::KEYWORD_FN_PTR_CURRY; let curry_func = crate::engine::KEYWORD_FN_PTR_CURRY;
let hash_script = calc_script_fn_hash(empty(), curry_func, num_externals + 1);
let expr = Expr::FnCall( let expr = Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
name: curry_func.into(), name: curry_func.into(),
hash_script, hash: FnHash::from_native(calc_fn_hash(empty(), curry_func, num_externals + 1)),
args, args,
..Default::default() ..Default::default()
}), }),

View File

@ -10,8 +10,7 @@ use crate::stdlib::{
fmt, fmt,
fmt::{Debug, Display}, fmt::{Debug, Display},
hash::{BuildHasher, Hash, Hasher}, hash::{BuildHasher, Hash, Hasher},
iter::{empty, FromIterator}, iter::FromIterator,
num::NonZeroU64,
ops::{Add, AddAssign, Deref, DerefMut, Sub, SubAssign}, ops::{Add, AddAssign, Deref, DerefMut, Sub, SubAssign},
str::FromStr, str::FromStr,
string::{String, ToString}, string::{String, ToString},
@ -19,18 +18,18 @@ use crate::stdlib::{
}; };
use crate::Shared; use crate::Shared;
/// A hasher that only takes one single [`NonZeroU64`] and returns it as a hash key. /// A hasher that only takes one single [`u64`] and returns it as a hash key.
/// ///
/// # Panics /// # Panics
/// ///
/// Panics when hashing any data type other than a [`NonZeroU64`]. /// Panics when hashing any data type other than a [`u64`].
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct StraightHasher(NonZeroU64); pub struct StraightHasher(u64);
impl Hasher for StraightHasher { impl Hasher for StraightHasher {
#[inline(always)] #[inline(always)]
fn finish(&self) -> u64 { fn finish(&self) -> u64 {
self.0.get() self.0
} }
#[inline(always)] #[inline(always)]
fn write(&mut self, bytes: &[u8]) { fn write(&mut self, bytes: &[u8]) {
@ -39,9 +38,7 @@ impl Hasher for StraightHasher {
let mut key = [0_u8; 8]; let mut key = [0_u8; 8];
key.copy_from_slice(bytes); key.copy_from_slice(bytes);
// HACK - If it so happens to hash directly to zero (OMG!) then change it to 42... self.0 = u64::from_ne_bytes(key);
self.0 = NonZeroU64::new(u64::from_ne_bytes(key))
.unwrap_or_else(|| NonZeroU64::new(42).unwrap());
} }
} }
@ -54,7 +51,7 @@ impl BuildHasher for StraightHasherBuilder {
#[inline(always)] #[inline(always)]
fn build_hasher(&self) -> Self::Hasher { fn build_hasher(&self) -> Self::Hasher {
StraightHasher(NonZeroU64::new(42).unwrap()) StraightHasher(42)
} }
} }
@ -64,26 +61,7 @@ pub fn get_hasher() -> ahash::AHasher {
Default::default() Default::default()
} }
/// _(INTERNALS)_ Calculate a [`NonZeroU64`] hash key from a namespace-qualified function name and /// _(INTERNALS)_ Calculate a [`u64`] hash key from a namespace-qualified function name
/// parameter types.
/// Exported under the `internals` feature only.
///
/// Module names are passed in via `&str` references from an iterator.
/// Parameter types are passed in via [`TypeId`] values from an iterator.
///
/// # Note
///
/// The first module name is skipped. Hashing starts from the _second_ module in the chain.
#[inline(always)]
pub fn calc_native_fn_hash<'a>(
modules: impl Iterator<Item = &'a str>,
fn_name: &str,
params: impl Iterator<Item = TypeId>,
) -> Option<NonZeroU64> {
calc_fn_hash(modules, fn_name, None, params)
}
/// _(INTERNALS)_ Calculate a [`NonZeroU64`] 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. /// Exported under the `internals` feature only.
/// ///
@ -94,29 +72,11 @@ pub fn calc_native_fn_hash<'a>(
/// ///
/// 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(always)]
pub fn calc_script_fn_hash<'a>( pub fn calc_fn_hash<'a>(
modules: impl Iterator<Item = &'a str>,
fn_name: &str,
num: usize,
) -> Option<NonZeroU64> {
calc_fn_hash(modules, fn_name, Some(num), empty())
}
/// Calculate a [`NonZeroU64`] hash key from a namespace-qualified function name and parameter types.
///
/// Module names are passed in via `&str` references from an iterator.
/// Parameter types are passed in via [`TypeId`] values from an iterator.
///
/// # Note
///
/// The first module name is skipped. Hashing starts from the _second_ module in the chain.
#[inline(always)]
fn calc_fn_hash<'a>(
mut modules: impl Iterator<Item = &'a str>, mut modules: impl Iterator<Item = &'a str>,
fn_name: &str, fn_name: &str,
num: Option<usize>, num: usize,
params: impl Iterator<Item = TypeId>, ) -> u64 {
) -> Option<NonZeroU64> {
let s = &mut get_hasher(); let s = &mut get_hasher();
// Hash a boolean indicating whether the hash is namespace-qualified. // Hash a boolean indicating whether the hash is namespace-qualified.
@ -124,20 +84,30 @@ fn calc_fn_hash<'a>(
// We always skip the first module // We always skip the first module
modules.for_each(|m| m.hash(s)); modules.for_each(|m| m.hash(s));
fn_name.hash(s); fn_name.hash(s);
if let Some(num) = num {
num.hash(s); num.hash(s);
} else { s.finish()
params.for_each(|t| t.hash(s));
}
// HACK - If it so happens to hash directly to zero (OMG!) then change it to 42...
NonZeroU64::new(s.finish()).or_else(|| NonZeroU64::new(42))
} }
/// Combine two [`NonZeroU64`] hashes by taking the XOR of them. /// _(INTERNALS)_ Calculate a [`u64`] hash key from a list of parameter types.
/// Exported under the `internals` feature only.
///
/// Parameter types are passed in via [`TypeId`] values from an iterator.
#[inline(always)] #[inline(always)]
pub(crate) fn combine_hashes(a: NonZeroU64, b: NonZeroU64) -> NonZeroU64 { pub fn calc_fn_params_hash(params: impl Iterator<Item = TypeId>) -> u64 {
// HACK - If it so happens to hash directly to zero (OMG!) then change it to 42... let s = &mut get_hasher();
NonZeroU64::new(a.get() ^ b.get()).unwrap_or_else(|| NonZeroU64::new(42).unwrap()) let mut len = 0;
params.for_each(|t| {
t.hash(s);
len += 1;
});
len.hash(s);
s.finish()
}
/// Combine two [`u64`] hashes by taking the XOR of them.
#[inline(always)]
pub(crate) fn combine_hashes(a: u64, b: u64) -> u64 {
a ^ b
} }
/// _(INTERNALS)_ A type that wraps a [`HashMap`] and implements [`Hash`]. /// _(INTERNALS)_ A type that wraps a [`HashMap`] and implements [`Hash`].

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]
fn test_left_shift() -> Result<(), Box<EvalAltResult>> { fn test_left_shift() -> Result<(), Box<EvalAltResult>> {

View File

@ -68,12 +68,12 @@ fn test_string_dynamic() -> Result<(), Box<EvalAltResult>> {
fn test_string_mut() -> Result<(), Box<EvalAltResult>> { fn test_string_mut() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.register_fn("foo", |x: INT, s: &str| s.len() as INT + x); engine.register_fn("foo", |s: &str| s.len() as INT);
engine.register_fn("bar", |x: INT, s: String| s.len() as INT + x); engine.register_fn("bar", |s: String| s.len() as INT);
engine.register_fn("baz", |s: &mut String| s.len()); engine.register_fn("baz", |s: &mut String| s.len());
assert_eq!(engine.eval::<INT>(r#"foo(1, "hello")"#)?, 6); assert_eq!(engine.eval::<INT>(r#"foo("hello")"#)?, 5);
assert_eq!(engine.eval::<INT>(r#"bar(1, "hello")"#)?, 6); assert_eq!(engine.eval::<INT>(r#"bar("hello")"#)?, 5);
assert!( assert!(
matches!(*engine.eval::<INT>(r#"baz("hello")"#).expect_err("should error"), matches!(*engine.eval::<INT>(r#"baz("hello")"#).expect_err("should error"),
EvalAltResult::ErrorFunctionNotFound(f, _) if f == "baz (&str | ImmutableString | String)" EvalAltResult::ErrorFunctionNotFound(f, _) if f == "baz (&str | ImmutableString | String)"