diff --git a/CHANGELOG.md b/CHANGELOG.md index b6f6d5c7..f73ee408 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,11 +26,11 @@ New features * Normal loops return `()` as the value. * Loop expressions can be enabled/disabled via `Engine::set_allow_loop_expressions` -### Stable hashing +### Static hashing -* It is now possible to specify a fixed _seed_ for use with the `ahash` hasher, via an environment variable, in order to force stable (i.e. deterministic) hashes for function signatures. +* It is now possible to specify a fixed _seed_ for use with the `ahash` hasher, via a static function `rhai::config::hashing::set_ahash_seed` or an environment variable, in order to force static (i.e. deterministic) hashes for function signatures. * This is necessary when using Rhai across shared-library boundaries. -* A build script is used to extract the environment variable (`RHAI_AHASH_SEED`) and splice it into the source code before compilation. +* A build script is used to extract the environment variable (`RHAI_AHASH_SEED`, if any) and splice it into the source code before compilation. ### No Timestamps diff --git a/build.template b/build.template index a165e5ed..bce322e8 100644 --- a/build.template +++ b/build.template @@ -1,2 +1,3 @@ -// This file is automatically set during build time by build.rs and build.template. +//! This file is automatically recreated during build time by `build.rs` from `build.template`. + pub(crate) const AHASH_SEED: Option<[u64; 4]> = {{AHASH_SEED}}; diff --git a/src/api/call_fn.rs b/src/api/call_fn.rs index 3b0a7615..1a9e2347 100644 --- a/src/api/call_fn.rs +++ b/src/api/call_fn.rs @@ -259,35 +259,36 @@ impl Engine { ast.resolver().cloned(), ); - let mut result = Ok(Dynamic::UNIT); - - if eval_ast && !statements.is_empty() { - result = self.eval_global_statements(scope, global, caches, statements, lib, 0); + let result = if eval_ast && !statements.is_empty() { + let r = self.eval_global_statements(global, caches, lib, 0, scope, statements); if rewind_scope { scope.rewind(orig_scope_len); } - } - result = result.and_then(|_| { + r + } else { + Ok(Dynamic::UNIT) + } + .and_then(|_| { let mut args: StaticVec<_> = arg_values.iter_mut().collect(); // Check for data race. #[cfg(not(feature = "no_closure"))] - crate::func::call::ensure_no_data_race(name, &args, false).map(|_| Dynamic::UNIT)?; + crate::func::ensure_no_data_race(name, &args, false).map(|_| Dynamic::UNIT)?; if let Some(fn_def) = ast.shared_lib().get_script_fn(name, args.len()) { self.call_script_fn( - scope, global, caches, lib, + 0, + scope, &mut this_ptr, fn_def, &mut args, rewind_scope, Position::NONE, - 0, ) } else { Err(ERR::ErrorFunctionNotFound(name.into(), Position::NONE).into()) @@ -305,7 +306,7 @@ impl Engine { if self.debugger.is_some() { global.debugger.status = crate::eval::DebuggerStatus::Terminate; let node = &crate::ast::Stmt::Noop(Position::NONE); - self.run_debugger(scope, global, lib, &mut this_ptr, node, 0)?; + self.run_debugger(global, caches, lib, 0, scope, &mut this_ptr, node)?; } Ok(result) diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index 868b9931..a12ea593 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -228,7 +228,13 @@ impl Engine { continue; } - let token = Token::lookup_from_syntax(s); + let token = Token::lookup_symbol_from_syntax(s).or_else(|| { + if Token::is_reserved_keyword(s) { + Some(Token::Reserved(Box::new(s.into()))) + } else { + None + } + }); let seg = match s { // Markers not in first position @@ -273,7 +279,7 @@ impl Engine { .into_err(Position::NONE)); } // Identifier in first position - _ if segments.is_empty() && is_valid_identifier(s.chars()) => { + _ if segments.is_empty() && is_valid_identifier(s) => { // Make it a custom keyword/symbol if it is disabled or reserved if (!self.disabled_symbols.is_empty() && self.disabled_symbols.contains(s)) || token.map_or(false, |v| v.is_reserved()) diff --git a/src/api/eval.rs b/src/api/eval.rs index 3f37264d..ffef8623 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -186,8 +186,9 @@ impl Engine { ast: &AST, ) -> RhaiResultOf { let global = &mut GlobalRuntimeState::new(self); + let caches = &mut Caches::new(); - let result = self.eval_ast_with_scope_raw(scope, global, ast, 0)?; + let result = self.eval_ast_with_scope_raw(global, caches, 0, scope, ast)?; #[cfg(feature = "debugging")] if self.debugger.is_some() { @@ -197,7 +198,7 @@ impl Engine { ast.as_ref(), ]; let node = &crate::ast::Stmt::Noop(Position::NONE); - self.run_debugger(scope, global, lib, &mut None, node, 0)?; + self.run_debugger(global, caches, lib, 0, scope, &mut None, node)?; } let typ = self.map_type_name(result.type_name()); @@ -211,12 +212,12 @@ impl Engine { #[inline] pub(crate) fn eval_ast_with_scope_raw<'a>( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, - ast: &'a AST, + caches: &mut Caches, level: usize, + scope: &mut Scope, + ast: &'a AST, ) -> RhaiResult { - let mut caches = Caches::new(); global.source = ast.source_raw().cloned(); #[cfg(not(feature = "no_module"))] @@ -240,8 +241,7 @@ impl Engine { _lib = &[]; } - let result = - self.eval_global_statements(scope, global, &mut caches, statements, _lib, level); + let result = self.eval_global_statements(global, caches, _lib, level, scope, statements); #[cfg(not(feature = "no_module"))] { @@ -262,14 +262,14 @@ impl Engine { #[inline(always)] pub fn eval_statements_raw( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, - statements: &[crate::ast::Stmt], lib: &[&crate::Module], level: usize, + scope: &mut Scope, + statements: &[crate::ast::Stmt], ) -> RhaiResult { - self.eval_global_statements(scope, global, caches, statements, lib, level) + self.eval_global_statements(global, caches, lib, level, scope, statements) } } diff --git a/src/api/mod.rs b/src/api/mod.rs index 5f85f140..71973732 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -154,7 +154,7 @@ impl Engine { let keyword = keyword.as_ref(); - match Token::lookup_from_syntax(keyword) { + match Token::lookup_symbol_from_syntax(keyword) { // Standard identifiers and reserved keywords are OK None | Some(Token::Reserved(..)) => (), // custom keywords are OK diff --git a/src/api/run.rs b/src/api/run.rs index e32136e6..effdd4b6 100644 --- a/src/api/run.rs +++ b/src/api/run.rs @@ -131,7 +131,7 @@ impl Engine { } else { &lib }; - self.eval_global_statements(scope, global, caches, statements, lib, 0)?; + self.eval_global_statements(global, caches, lib, 0, scope, statements)?; } #[cfg(feature = "debugging")] @@ -142,7 +142,7 @@ impl Engine { ast.as_ref(), ]; let node = &crate::ast::Stmt::Noop(crate::Position::NONE); - self.run_debugger(scope, global, lib, &mut None, node, 0)?; + self.run_debugger(global, caches, lib, 0, scope, &mut None, node)?; } Ok(()) diff --git a/src/api/type_names.rs b/src/api/type_names.rs index f7897bf0..dda879fc 100644 --- a/src/api/type_names.rs +++ b/src/api/type_names.rs @@ -108,11 +108,15 @@ fn map_std_type_name(name: &str, shorthands: bool) -> &str { /// Format a Rust type to be display-friendly. /// +/// * `rhai::` prefix is cleared. /// * `()` is cleared. +/// * `&mut` is cleared. /// * `INT` and `FLOAT` are expanded. /// * [`RhaiResult`][crate::RhaiResult] and [`RhaiResultOf`][crate::RhaiResultOf] are expanded. #[cfg(feature = "metadata")] pub fn format_type(typ: &str, is_return_type: bool) -> std::borrow::Cow { + const RESULT_TYPE: &str = "Result<"; + const ERROR_TYPE: &str = ",Box>"; const RHAI_RESULT_TYPE: &str = "RhaiResult"; const RHAI_RESULT_TYPE_EXPAND: &str = "Result>"; const RHAI_RESULT_OF_TYPE: &str = "RhaiResultOf<"; @@ -135,6 +139,10 @@ pub fn format_type(typ: &str, is_return_type: bool) -> std::borrow::Cow { } else { format!("&mut {r}").into() }; + } else if typ.contains(" ") { + let typ = typ.replace(" ", ""); + let r = format_type(&typ, is_return_type); + return r.into_owned().into(); } match typ { @@ -167,6 +175,12 @@ pub fn format_type(typ: &str, is_return_type: bool) -> std::borrow::Cow { .replace("{}", format_type(inner, false).trim()) .into() } + ty if ty.starts_with(RESULT_TYPE) && ty.ends_with(ERROR_TYPE) => { + let inner = &ty[RESULT_TYPE.len()..ty.len() - ERROR_TYPE.len()]; + RHAI_RESULT_OF_TYPE_EXPAND + .replace("{}", format_type(inner, false).trim()) + .into() + } ty => ty.into(), } } diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 863299c8..163fdbc9 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -17,7 +17,7 @@ use std::{ fmt::Write, hash::Hash, iter::once, - num::{NonZeroU8, NonZeroUsize}, + num::{NonZeroU64, NonZeroU8, NonZeroUsize}, }; #[cfg(not(feature = "no_float"))] @@ -86,7 +86,7 @@ impl CustomExpr { /// /// Two separate hashes are pre-calculated because of the following patterns: /// -/// ```js +/// ```rhai /// func(a, b, c); // Native: func(a, b, c) - 3 parameters /// // Script: func(a, b, c) - 3 parameters /// @@ -100,20 +100,22 @@ impl CustomExpr { /// /// Function call hashes are used in the following manner: /// -/// * First, the script hash is tried, which contains only the called function's name plus the -/// number of parameters. +/// * First, the script hash (if any) is tried, which contains only the called function's name plus +/// the number of parameters. /// /// * Next, the actual types of arguments are hashed and _combined_ with the native hash, which is -/// then used to search for a native function. In other words, a complete native function call -/// hash always contains the called function's name plus the types of the arguments. This is due -/// to possible function overloading for different parameter types. -#[derive(Clone, Copy, Eq, PartialEq, Hash, Default)] +/// then used to search for a native function. +/// +/// In other words, a complete native function call hash always contains the called function's +/// name plus the types of the arguments. This is due to possible function overloading for +/// different parameter types. +#[derive(Clone, Copy, Eq, PartialEq, Hash)] pub struct FnCallHashes { - /// Pre-calculated hash for a script-defined function (zero if native functions only). + /// Pre-calculated hash for a script-defined function ([`None`] if native functions only). #[cfg(not(feature = "no_function"))] - pub script: u64, + script: Option, /// Pre-calculated hash for a native Rust function with no parameter types. - pub native: u64, + native: NonZeroU64, } impl fmt::Debug for FnCallHashes { @@ -121,11 +123,11 @@ impl fmt::Debug for FnCallHashes { #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[cfg(not(feature = "no_function"))] - if self.script != 0 { - return if self.script == self.native { + if let Some(script) = self.script { + return if script == self.native { fmt::Debug::fmt(&self.native, f) } else { - write!(f, "({}, {})", self.script, self.native) + write!(f, "({}, {})", script, self.native) }; } @@ -136,11 +138,11 @@ impl fmt::Debug for FnCallHashes { impl From for FnCallHashes { #[inline] fn from(hash: u64) -> Self { - let hash = if hash == 0 { ALT_ZERO_HASH } else { hash }; + let hash = NonZeroU64::new(if hash == 0 { ALT_ZERO_HASH } else { hash }).unwrap(); Self { #[cfg(not(feature = "no_function"))] - script: hash, + script: Some(hash), native: hash, } } @@ -150,33 +152,54 @@ impl FnCallHashes { /// Create a [`FnCallHashes`] with only the native Rust hash. #[inline] #[must_use] - pub const fn from_native(hash: u64) -> Self { + pub fn from_native(hash: u64) -> Self { Self { #[cfg(not(feature = "no_function"))] - script: 0, - native: if hash == 0 { ALT_ZERO_HASH } else { hash }, + script: None, + native: NonZeroU64::new(if hash == 0 { ALT_ZERO_HASH } else { hash }).unwrap(), } } /// Create a [`FnCallHashes`] with both native Rust and script function hashes. #[inline] #[must_use] - pub const fn from_all(#[cfg(not(feature = "no_function"))] script: u64, native: u64) -> Self { + pub fn from_all(#[cfg(not(feature = "no_function"))] script: u64, native: u64) -> Self { Self { #[cfg(not(feature = "no_function"))] - script: if script == 0 { ALT_ZERO_HASH } else { script }, - native: if native == 0 { ALT_ZERO_HASH } else { native }, + script: NonZeroU64::new(if script == 0 { ALT_ZERO_HASH } else { script }), + native: NonZeroU64::new(if native == 0 { ALT_ZERO_HASH } else { native }).unwrap(), } } - /// Is this [`FnCallHashes`] native Rust only? + /// Is this [`FnCallHashes`] native-only? #[inline(always)] #[must_use] pub const fn is_native_only(&self) -> bool { #[cfg(not(feature = "no_function"))] - return self.script == 0; - + return self.script.is_none(); #[cfg(feature = "no_function")] return true; } + /// Get the native hash. + /// + /// The hash returned is never zero. + #[inline(always)] + #[must_use] + pub fn native(&self) -> u64 { + self.native.get() + } + /// Get the script hash. + /// + /// The hash returned is never zero. + /// + /// # Panics + /// + /// Panics if this [`FnCallHashes`] is native-only. + #[cfg(not(feature = "no_function"))] + #[inline(always)] + #[must_use] + pub fn script(&self) -> u64 { + assert!(self.script.is_some()); + self.script.as_ref().unwrap().get() + } } /// _(internals)_ A function call. @@ -195,12 +218,7 @@ pub struct FnCallExpr { /// Does this function call capture the parent scope? pub capture_parent_scope: bool, /// Is this function call a native operator? - pub operator_token: Option, - /// Can this function call be a scripted function? - #[cfg(not(feature = "no_function"))] - pub can_be_script: bool, - /// [Position] of the function name. - pub pos: Position, + pub op_token: Option, } impl fmt::Debug for FnCallExpr { @@ -215,17 +233,12 @@ impl fmt::Debug for FnCallExpr { ff.field("hash", &self.hashes) .field("name", &self.name) .field("args", &self.args); - if let Some(ref token) = self.operator_token { - ff.field("operator_token", token); + if let Some(ref token) = self.op_token { + ff.field("op_token", token); } if self.capture_parent_scope { ff.field("capture_parent_scope", &self.capture_parent_scope); } - #[cfg(not(feature = "no_function"))] - if self.can_be_script { - ff.field("can_be_script", &self.can_be_script); - } - ff.field("pos", &self.pos); ff.finish() } } @@ -248,6 +261,16 @@ impl FnCallExpr { pub fn into_fn_call_expr(self, pos: Position) -> Expr { Expr::FnCall(self.into(), pos) } + /// Are all arguments constant? + #[inline] + #[must_use] + pub fn constant_args(&self) -> bool { + if self.args.is_empty() { + true + } else { + self.args.iter().all(Expr::is_constant) + } + } } /// A type that wraps a floating-point number and implements [`Hash`]. @@ -690,10 +713,7 @@ impl Expr { hashes: calc_fn_hash(None, f.fn_name(), 1).into(), args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(), capture_parent_scope: false, - operator_token: None, - #[cfg(not(feature = "no_function"))] - can_be_script: true, - pos, + op_token: None, } .into(), pos, @@ -748,6 +768,8 @@ impl Expr { | Self::And(.., pos) | Self::Or(.., pos) | Self::Coalesce(.., pos) + | Self::FnCall(.., pos) + | Self::MethodCall(.., pos) | Self::Index(.., pos) | Self::Dot(.., pos) | Self::InterpolatedString(.., pos) @@ -756,8 +778,6 @@ impl Expr { #[cfg(not(feature = "no_custom_syntax"))] Self::Custom(.., pos) => *pos, - Self::FnCall(x, ..) | Self::MethodCall(x, ..) => x.pos, - Self::Stmt(x) => x.position(), } } diff --git a/src/ast/flags.rs b/src/ast/flags.rs index e26fb55c..452a57e6 100644 --- a/src/ast/flags.rs +++ b/src/ast/flags.rs @@ -18,7 +18,7 @@ pub enum FnAccess { impl FnAccess { /// Is this function private? - #[inline] + #[inline(always)] #[must_use] pub const fn is_private(self) -> bool { match self { @@ -27,7 +27,7 @@ impl FnAccess { } } /// Is this function public? - #[inline] + #[inline(always)] #[must_use] pub const fn is_public(self) -> bool { match self { diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index e272e1e9..541332c3 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -61,7 +61,10 @@ impl OpAssignment { #[must_use] #[inline(always)] pub fn new_op_assignment(name: &str, pos: Position) -> Self { - Self::new_op_assignment_from_token(&Token::lookup_from_syntax(name).expect("operator"), pos) + Self::new_op_assignment_from_token( + &Token::lookup_symbol_from_syntax(name).expect("operator"), + pos, + ) } /// Create a new [`OpAssignment`] from a [`Token`]. /// @@ -90,7 +93,7 @@ impl OpAssignment { #[inline(always)] pub fn new_op_assignment_from_base(name: &str, pos: Position) -> Self { Self::new_op_assignment_from_base_token( - &Token::lookup_from_syntax(name).expect("operator"), + &Token::lookup_symbol_from_syntax(name).expect("operator"), pos, ) } diff --git a/src/config/hashing.rs b/src/config/hashing.rs index 2355a405..58374d21 100644 --- a/src/config/hashing.rs +++ b/src/config/hashing.rs @@ -2,18 +2,30 @@ //! //! Set to [`None`] to disable stable hashing. //! -//! See [`set_rhai_ahash_seed`] for more. +//! See [`set_rhai_ahash_seed`]. //! -//! Alternatively, You can also set this at compile time by setting the `RHAI_AHASH_SEED` -//! environment variable instead. +//! # Example //! -//! E.g. `env RHAI_AHASH_SEED ="[236,800,954,213]"` +//! ```rust +//! // Set the hashing seed to [1, 2, 3, 4] +//! rhai::config::hashing::set_ahash_seed(Some([1, 2, 3, 4])).unwrap(); +//! ``` +//! Alternatively, set this at compile time via the `RHAI_AHASH_SEED` environment variable. +//! +//! # Example +//! +//! ```sh +//! env RHAI_AHASH_SEED ="[236,800,954,213]" +//! ``` // [236,800,954,213], haha funny yume nikki reference epic uboachan face numberworld nexus moment 100 use crate::config::hashing_env; -use core::{ +#[cfg(feature = "no_std")] +use std::prelude::v1::*; +use std::{ cell::UnsafeCell, marker::PhantomData, + mem, mem::MaybeUninit, panic::{RefUnwindSafe, UnwindSafe}, sync::atomic::{AtomicBool, AtomicUsize, Ordering}, @@ -23,23 +35,25 @@ use core::{ // what does this do? // so what this does is keep track of a global address in memory that acts as a global lock // i stole this from crossbeam so read their docs for more +#[must_use] struct HokmaLock { lock: AtomicUsize, } impl HokmaLock { + #[inline(always)] pub const fn new() -> Self { Self { lock: AtomicUsize::new(0), } } - pub fn write(&'static self) -> WhenTheHokmaSupression { + pub fn write(&'static self) -> WhenTheHokmaSuppression { loop { let previous = self.lock.swap(1, Ordering::SeqCst); if previous != 1 { - return WhenTheHokmaSupression { + return WhenTheHokmaSuppression { hokma: self, state: previous, }; @@ -48,21 +62,21 @@ impl HokmaLock { } } -struct WhenTheHokmaSupression { +struct WhenTheHokmaSuppression { hokma: &'static HokmaLock, - state: usize, } -impl WhenTheHokmaSupression { +impl WhenTheHokmaSuppression { + #[inline] pub fn the_price_of_silence(self) { self.hokma.lock.store(self.state, Ordering::SeqCst); - - core::mem::forget(self) + mem::forget(self) } } -impl Drop for WhenTheHokmaSupression { +impl Drop for WhenTheHokmaSuppression { + #[inline] fn drop(&mut self) { self.hokma .lock @@ -70,6 +84,8 @@ impl Drop for WhenTheHokmaSupression { } } +#[inline(always)] +#[must_use] fn hokmalock(address: usize) -> &'static HokmaLock { const LEN: usize = 787; const LCK: HokmaLock = HokmaLock::new(); @@ -79,11 +95,12 @@ fn hokmalock(address: usize) -> &'static HokmaLock { } // Safety: lol, there is a reason its called "SusLock" +#[must_use] struct SusLock where T: 'static + Copy, { - initalized: AtomicBool, + initialized: AtomicBool, data: UnsafeCell>, _marker: PhantomData, } @@ -92,17 +109,19 @@ impl SusLock where T: 'static + Copy, { + #[inline] pub const fn new() -> SusLock { SusLock { - initalized: AtomicBool::new(false), + initialized: AtomicBool::new(false), data: UnsafeCell::new(MaybeUninit::uninit()), _marker: PhantomData, } } + #[must_use] pub fn get(&self) -> Option { - if self.initalized.load(Ordering::SeqCst) { - let hokma = hokmalock(unsafe { core::mem::transmute(self.data.get()) }); + if self.initialized.load(Ordering::SeqCst) { + let hokma = hokmalock(unsafe { mem::transmute(self.data.get()) }); // we forgo the optimistic read, because we don't really care let guard = hokma.write(); let val = { @@ -116,11 +135,12 @@ where } } + #[must_use] pub fn get_or_init(&self, f: impl FnOnce() -> T) -> Option { let value = f(); - if !self.initalized.load(Ordering::SeqCst) { - self.initalized.store(true, Ordering::SeqCst); - let hokma = hokmalock(unsafe { core::mem::transmute(self.data.get()) }); + if !self.initialized.load(Ordering::SeqCst) { + self.initialized.store(true, Ordering::SeqCst); + let hokma = hokmalock(unsafe { mem::transmute(self.data.get()) }); hokma.write(); unsafe { self.data.get().write(MaybeUninit::new(value)); @@ -131,7 +151,7 @@ where } pub fn set(&self, value: T) -> Result<(), T> { - if self.initalized.load(Ordering::SeqCst) { + if self.initialized.load(Ordering::SeqCst) { Err(value) } else { let _ = self.get_or_init(|| value); @@ -148,8 +168,9 @@ impl Drop for SusLock where T: 'static + Copy, { + #[inline] fn drop(&mut self) { - if self.initalized.load(Ordering::SeqCst) { + if self.initialized.load(Ordering::SeqCst) { unsafe { (&mut *self.data.get()).assume_init_drop() }; } } @@ -157,27 +178,44 @@ where static AHASH_SEED: SusLock> = SusLock::new(); -// #[doc(cfg(feature = "stable_hash"))] -/// Sets the Rhai Ahash seed. This is used to hash functions and the like. +/// Set the hashing seed. This is used to hash functions etc. /// -/// This is a global variable, and thus will affect every Rhai instance. +/// This is a static global value and affects every Rhai instance. /// This should not be used _unless_ you know you need it. /// -/// # Warnings -/// - You can only call this function **ONCE** for the whole of your program execution. -/// - You should gracefully handle the `Err(())`. -/// - You **MUST** call this before **ANY** Rhai operation occurs (e.g. creating an [`Engine`]). +/// # Warning /// -/// # Errors -/// This will error if the AHashSeed is already set. +/// * You can only call this function **ONCE** for the entire duration of program execution. +/// * You **MUST** call this before performing **ANY** Rhai operation (e.g. creating an [`Engine`]). +/// +/// # Error +/// +/// Returns an error containing the existing hashing seed if already set. +/// +/// # Example +/// +/// ```rust +/// # use rhai::Engine; +/// // Set the hashing seed to [1, 2, 3, 4] +/// rhai::config::hashing::set_ahash_seed(Some([1, 2, 3, 4])).unwrap(); +/// +/// // Use Rhai AFTER setting the hashing seed +/// let engine = Engine::new(); +/// ``` +#[inline(always)] pub fn set_ahash_seed(new_seed: Option<[u64; 4]>) -> Result<(), Option<[u64; 4]>> { AHASH_SEED.set(new_seed) } -/// Gets the current Rhai Ahash Seed. If the seed is not yet defined, this will automatically set a seed. -/// The default seed is not stable and may change between versions. +/// Get the current hashing Seed. +/// +/// If the seed is not yet defined, the `RHAI_AHASH_SEED` environment variable (if any) is used. +/// +/// Otherwise, the hashing seed is randomized to protect against DOS attacks. /// /// See [`set_rhai_ahash_seed`] for more. +#[inline] +#[must_use] pub fn get_ahash_seed() -> Option<[u64; 4]> { AHASH_SEED.get_or_init(|| hashing_env::AHASH_SEED).flatten() } diff --git a/src/config/hashing_env.rs b/src/config/hashing_env.rs index ee2b35db..59930ad8 100644 --- a/src/config/hashing_env.rs +++ b/src/config/hashing_env.rs @@ -1,2 +1,3 @@ -// This file is automatically set during build time by build.rs and build.template. +//! This file is automatically recreated during build time by `build.rs` from `build.template`. + pub(crate) const AHASH_SEED: Option<[u64; 4]> = None; diff --git a/src/config/mod.rs b/src/config/mod.rs index db5448f6..35aac0af 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,4 +1,4 @@ -//! Contains Configuration for Rhai. +//! Configuration for Rhai. pub mod hashing; mod hashing_env; diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index dbf40e3a..ecc40fc7 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -41,6 +41,7 @@ impl Engine { global: &mut GlobalRuntimeState, caches: &mut Caches, lib: &[&Module], + level: usize, this_ptr: &mut Option<&mut Dynamic>, target: &mut Target, root: (&str, Position), @@ -49,7 +50,6 @@ impl Engine { rhs: &Expr, idx_values: &mut FnArgsVec, chain_type: ChainType, - level: usize, new_val: &mut Option<(Dynamic, &OpAssignment)>, ) -> RhaiResultOf<(Dynamic, bool)> { let is_ref_mut = target.is_ref(); @@ -73,7 +73,7 @@ impl Engine { if !parent_options.contains(ASTFlags::BREAK) => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, _parent, level)?; + self.run_debugger(global, caches, lib, level, scope, this_ptr, _parent)?; let idx_val = &mut idx_values.pop().unwrap(); let mut idx_val_for_setter = idx_val.clone(); @@ -82,14 +82,14 @@ impl Engine { let (try_setter, result) = { let mut obj = self.get_indexed_mut( - global, caches, lib, target, idx_val, idx_pos, false, true, level, + global, caches, lib, level, target, idx_val, idx_pos, false, true, )?; let is_obj_temp_val = obj.is_temp_value(); let obj_ptr = &mut obj; match self.eval_dot_index_chain_helper( - global, caches, lib, this_ptr, obj_ptr, root, rhs, *options, - &x.rhs, idx_values, rhs_chain, level, new_val, + global, caches, lib, level, this_ptr, obj_ptr, root, rhs, *options, + &x.rhs, idx_values, rhs_chain, new_val, ) { Ok((result, true)) if is_obj_temp_val => { (Some(obj.take_or_clone()), (result, true)) @@ -104,7 +104,7 @@ impl Engine { let idx = &mut idx_val_for_setter; let new_val = &mut new_val; self.call_indexer_set( - global, caches, lib, target, idx, new_val, is_ref_mut, level, + global, caches, lib, level, target, idx, new_val, is_ref_mut, ) .or_else(|e| match *e { ERR::ErrorIndexingType(..) => Ok((Dynamic::UNIT, false)), @@ -117,19 +117,19 @@ impl Engine { // xxx[rhs] op= new_val _ if new_val.is_some() => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, _parent, level)?; + self.run_debugger(global, caches, lib, level, scope, this_ptr, _parent)?; let (new_val, op_info) = new_val.take().expect("`Some`"); let idx_val = &mut idx_values.pop().unwrap(); let idx = &mut idx_val.clone(); let try_setter = match self.get_indexed_mut( - global, caches, lib, target, idx, pos, true, false, level, + global, caches, lib, level, target, idx, pos, true, false, ) { // Indexed value is not a temp value - update directly Ok(ref mut obj_ptr) => { self.eval_op_assignment( - global, caches, lib, op_info, obj_ptr, root, new_val, level, + global, caches, lib, level, op_info, obj_ptr, root, new_val, )?; self.check_data_size(obj_ptr, op_info.pos)?; None @@ -148,13 +148,13 @@ impl Engine { // Call the index getter to get the current value if let Ok(val) = - self.call_indexer_get(global, caches, lib, target, idx, level) + self.call_indexer_get(global, caches, lib, level, target, idx) { let mut val = val.into(); // Run the op-assignment self.eval_op_assignment( - global, caches, lib, op_info, &mut val, root, new_val, - level, + global, caches, lib, level, op_info, &mut val, root, + new_val, )?; // Replace new value new_val = val.take_or_clone(); @@ -166,7 +166,7 @@ impl Engine { let new_val = &mut new_val; self.call_indexer_set( - global, caches, lib, target, idx_val, new_val, is_ref_mut, level, + global, caches, lib, level, target, idx_val, new_val, is_ref_mut, )?; } @@ -175,12 +175,12 @@ impl Engine { // xxx[rhs] _ => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, _parent, level)?; + self.run_debugger(global, caches, lib, level, scope, this_ptr, _parent)?; let idx_val = &mut idx_values.pop().unwrap(); self.get_indexed_mut( - global, caches, lib, target, idx_val, pos, false, true, level, + global, caches, lib, level, target, idx_val, pos, false, true, ) .map(|v| (v.take_or_clone(), false)) } @@ -198,8 +198,9 @@ impl Engine { // xxx.fn_name(arg_expr_list) Expr::MethodCall(x, pos) if !x.is_qualified() && new_val.is_none() => { #[cfg(feature = "debugging")] - let reset_debugger = - self.run_debugger_with_reset(scope, global, lib, this_ptr, rhs, level)?; + let reset_debugger = self.run_debugger_with_reset( + global, caches, lib, level, scope, this_ptr, rhs, + )?; let crate::ast::FnCallExpr { name, hashes, args, .. @@ -210,8 +211,8 @@ impl Engine { let pos1 = args.get(0).map_or(Position::NONE, Expr::position); let result = self.make_method_call( - global, caches, lib, name, *hashes, target, call_args, pos1, *pos, - level, + global, caches, lib, level, name, *hashes, target, call_args, pos1, + *pos, ); idx_values.truncate(offset); @@ -232,16 +233,16 @@ impl Engine { // {xxx:map}.id op= ??? Expr::Property(x, pos) if target.is::() && new_val.is_some() => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, rhs, level)?; + self.run_debugger(global, caches, lib, level, scope, this_ptr, rhs)?; let index = &mut x.2.clone().into(); let (new_val, op_info) = new_val.take().expect("`Some`"); { let val_target = &mut self.get_indexed_mut( - global, caches, lib, target, index, *pos, true, false, level, + global, caches, lib, level, target, index, *pos, true, false, )?; self.eval_op_assignment( - global, caches, lib, op_info, val_target, root, new_val, level, + global, caches, lib, level, op_info, val_target, root, new_val, )?; } self.check_data_size(target.source(), op_info.pos)?; @@ -250,18 +251,18 @@ impl Engine { // {xxx:map}.id Expr::Property(x, pos) if target.is::() => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, rhs, level)?; + self.run_debugger(global, caches, lib, level, scope, this_ptr, rhs)?; let index = &mut x.2.clone().into(); let val = self.get_indexed_mut( - global, caches, lib, target, index, *pos, false, false, level, + global, caches, lib, level, target, index, *pos, false, false, )?; Ok((val.take_or_clone(), false)) } // xxx.id op= ??? Expr::Property(x, pos) if new_val.is_some() => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, rhs, level)?; + self.run_debugger(global, caches, lib, level, scope, this_ptr, rhs)?; let ((getter, hash_get), (setter, hash_set), name) = &**x; let (mut new_val, op_info) = new_val.take().expect("`Some`"); @@ -269,16 +270,16 @@ impl Engine { if op_info.is_op_assignment() { let args = &mut [target.as_mut()]; let (mut orig_val, ..) = self - .call_native_fn( - global, caches, lib, getter, *hash_get, args, is_ref_mut, - false, *pos, level, + .exec_native_fn_call( + global, caches, lib, level, getter, None, *hash_get, args, + is_ref_mut, *pos, ) .or_else(|err| match *err { // Try an indexer if property does not exist ERR::ErrorDotExpr(..) => { let mut prop = name.into(); self.call_indexer_get( - global, caches, lib, target, &mut prop, level, + global, caches, lib, level, target, &mut prop, ) .map(|r| (r, false)) .map_err(|e| { @@ -295,7 +296,7 @@ impl Engine { let orig_val = &mut (&mut orig_val).into(); self.eval_op_assignment( - global, caches, lib, op_info, orig_val, root, new_val, level, + global, caches, lib, level, op_info, orig_val, root, new_val, )?; } @@ -303,9 +304,9 @@ impl Engine { } let args = &mut [target.as_mut(), &mut new_val]; - self.call_native_fn( - global, caches, lib, setter, *hash_set, args, is_ref_mut, false, *pos, - level, + self.exec_native_fn_call( + global, caches, lib, level, setter, None, *hash_set, args, is_ref_mut, + *pos, ) .or_else(|err| match *err { // Try an indexer if property does not exist @@ -313,7 +314,7 @@ impl Engine { let idx = &mut name.into(); let new_val = &mut new_val; self.call_indexer_set( - global, caches, lib, target, idx, new_val, is_ref_mut, level, + global, caches, lib, level, target, idx, new_val, is_ref_mut, ) .map_err(|e| match *e { ERR::ErrorIndexingType(..) => err, @@ -326,13 +327,13 @@ impl Engine { // xxx.id Expr::Property(x, pos) => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, rhs, level)?; + self.run_debugger(global, caches, lib, level, scope, this_ptr, rhs)?; let ((getter, hash_get), _, name) = &**x; let args = &mut [target.as_mut()]; - self.call_native_fn( - global, caches, lib, getter, *hash_get, args, is_ref_mut, false, *pos, - level, + self.exec_native_fn_call( + global, caches, lib, level, getter, None, *hash_get, args, is_ref_mut, + *pos, ) .map_or_else( |err| match *err { @@ -340,7 +341,7 @@ impl Engine { ERR::ErrorDotExpr(..) => { let mut prop = name.into(); self.call_indexer_get( - global, caches, lib, target, &mut prop, level, + global, caches, lib, level, target, &mut prop, ) .map(|r| (r, false)) .map_err(|e| match *e { @@ -363,18 +364,20 @@ impl Engine { let val_target = &mut match x.lhs { Expr::Property(ref p, pos) => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, _node, level)?; + self.run_debugger( + global, caches, lib, level, scope, this_ptr, _node, + )?; let index = &mut p.2.clone().into(); self.get_indexed_mut( - global, caches, lib, target, index, pos, false, true, level, + global, caches, lib, level, target, index, pos, false, true, )? } // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr Expr::MethodCall(ref x, pos) if !x.is_qualified() => { #[cfg(feature = "debugging")] let reset_debugger = self.run_debugger_with_reset( - scope, global, lib, this_ptr, _node, level, + global, caches, lib, level, scope, this_ptr, _node, )?; let crate::ast::FnCallExpr { @@ -386,8 +389,8 @@ impl Engine { let pos1 = args.get(0).map_or(Position::NONE, Expr::position); let result = self.make_method_call( - global, caches, lib, name, *hashes, target, call_args, pos1, - pos, level, + global, caches, lib, level, name, *hashes, target, call_args, + pos1, pos, ); idx_values.truncate(offset); @@ -407,8 +410,8 @@ impl Engine { let rhs_chain = rhs.into(); self.eval_dot_index_chain_helper( - global, caches, lib, this_ptr, val_target, root, rhs, *options, &x.rhs, - idx_values, rhs_chain, level, new_val, + global, caches, lib, level, this_ptr, val_target, root, rhs, *options, + &x.rhs, idx_values, rhs_chain, new_val, ) .map_err(|err| err.fill_position(*x_pos)) } @@ -420,7 +423,9 @@ impl Engine { // xxx.prop[expr] | xxx.prop.expr Expr::Property(ref p, pos) => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, _node, level)?; + self.run_debugger( + global, caches, lib, level, scope, this_ptr, _node, + )?; let ((getter, hash_get), (setter, hash_set), name) = &**p; let rhs_chain = rhs.into(); @@ -429,16 +434,16 @@ impl Engine { // Assume getters are always pure let (mut val, ..) = self - .call_native_fn( - global, caches, lib, getter, *hash_get, args, is_ref_mut, - false, pos, level, + .exec_native_fn_call( + global, caches, lib, level, getter, None, *hash_get, args, + is_ref_mut, pos, ) .or_else(|err| match *err { // Try an indexer if property does not exist ERR::ErrorDotExpr(..) => { let mut prop = name.into(); self.call_indexer_get( - global, caches, lib, target, &mut prop, level, + global, caches, lib, level, target, &mut prop, ) .map(|r| (r, false)) .map_err( @@ -455,8 +460,8 @@ impl Engine { let (result, may_be_changed) = self .eval_dot_index_chain_helper( - global, caches, lib, this_ptr, val, root, rhs, *options, - &x.rhs, idx_values, rhs_chain, level, new_val, + global, caches, lib, level, this_ptr, val, root, rhs, + *options, &x.rhs, idx_values, rhs_chain, new_val, ) .map_err(|err| err.fill_position(*x_pos))?; @@ -465,9 +470,9 @@ impl Engine { // Re-use args because the first &mut parameter will not be consumed let mut arg_values = [target.as_mut(), val.as_mut()]; let args = &mut arg_values; - self.call_native_fn( - global, caches, lib, setter, *hash_set, args, is_ref_mut, - false, pos, level, + self.exec_native_fn_call( + global, caches, lib, level, setter, None, *hash_set, args, + is_ref_mut, pos, ) .or_else( |err| match *err { @@ -476,8 +481,8 @@ impl Engine { let idx = &mut name.into(); let new_val = val; self.call_indexer_set( - global, caches, lib, target, idx, new_val, - is_ref_mut, level, + global, caches, lib, level, target, idx, + new_val, is_ref_mut, ) .or_else(|e| match *e { // If there is no setter, no need to feed it @@ -499,7 +504,7 @@ impl Engine { Expr::MethodCall(ref f, pos) if !f.is_qualified() => { #[cfg(feature = "debugging")] let reset_debugger = self.run_debugger_with_reset( - scope, global, lib, this_ptr, _node, level, + global, caches, lib, level, scope, this_ptr, _node, )?; let crate::ast::FnCallExpr { @@ -512,8 +517,8 @@ impl Engine { let pos1 = args.get(0).map_or(Position::NONE, Expr::position); let result = self.make_method_call( - global, caches, lib, name, *hashes, target, call_args, pos1, - pos, level, + global, caches, lib, level, name, *hashes, target, call_args, + pos1, pos, ); idx_values.truncate(offset); @@ -525,8 +530,8 @@ impl Engine { let val = &mut val.into(); self.eval_dot_index_chain_helper( - global, caches, lib, this_ptr, val, root, rhs, *options, - &x.rhs, idx_values, rhs_chain, level, new_val, + global, caches, lib, level, this_ptr, val, root, rhs, *options, + &x.rhs, idx_values, rhs_chain, new_val, ) .map_err(|err| err.fill_position(pos)) } @@ -548,13 +553,13 @@ impl Engine { /// Evaluate a dot/index chain. pub(crate) fn eval_dot_index_chain( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, lib: &[&Module], + level: usize, + scope: &mut Scope, this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, - level: usize, new_val: &mut Option<(Dynamic, &OpAssignment)>, ) -> RhaiResult { let chain_type = ChainType::from(expr); @@ -592,8 +597,8 @@ impl Engine { // All other patterns - evaluate the arguments chain _ => { self.eval_dot_index_chain_arguments( - scope, global, caches, lib, this_ptr, rhs, options, chain_type, idx_values, - level, + global, caches, lib, level, scope, this_ptr, rhs, options, chain_type, + idx_values, )?; } } @@ -602,18 +607,18 @@ impl Engine { // id.??? or id[???] Expr::Variable(x, .., var_pos) => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, lhs, level)?; + self.run_debugger(global, caches, lib, level, scope, this_ptr, lhs)?; self.track_operation(global, *var_pos)?; let (mut target, ..) = - self.search_namespace(scope, global, lib, this_ptr, lhs, level)?; + self.search_namespace(global, caches, lib, level, scope, this_ptr, lhs)?; let obj_ptr = &mut target; let root = (x.3.as_str(), *var_pos); self.eval_dot_index_chain_helper( - global, caches, lib, &mut None, obj_ptr, root, expr, options, rhs, idx_values, - chain_type, level, new_val, + global, caches, lib, level, &mut None, obj_ptr, root, expr, options, rhs, + idx_values, chain_type, new_val, ) } // {expr}.??? = ??? or {expr}[???] = ??? @@ -621,14 +626,14 @@ impl Engine { // {expr}.??? or {expr}[???] expr => { let value = self - .eval_expr(scope, global, caches, lib, this_ptr, expr, level)? + .eval_expr(global, caches, lib, level, scope, this_ptr, expr)? .flatten(); let obj_ptr = &mut value.into(); let root = ("", expr.start_position()); self.eval_dot_index_chain_helper( - global, caches, lib, this_ptr, obj_ptr, root, expr, options, rhs, idx_values, - chain_type, level, new_val, + global, caches, lib, level, this_ptr, obj_ptr, root, expr, options, rhs, + idx_values, chain_type, new_val, ) } } @@ -639,16 +644,16 @@ impl Engine { /// Evaluate a chain of indexes and store the results in a [`FnArgsVec`]. fn eval_dot_index_chain_arguments( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, lib: &[&Module], + level: usize, + scope: &mut Scope, this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, parent_options: ASTFlags, parent_chain_type: ChainType, idx_values: &mut FnArgsVec, - level: usize, ) -> RhaiResultOf<()> { self.track_operation(global, expr.position())?; @@ -659,7 +664,7 @@ impl Engine { { for arg_expr in &x.args { idx_values.push( - self.get_arg_value(scope, global, caches, lib, this_ptr, arg_expr, level)? + self.get_arg_value(global, caches, lib, level, scope, this_ptr, arg_expr)? .0 .flatten(), ); @@ -694,7 +699,7 @@ impl Engine { for arg_expr in &x.args { _arg_values.push( self.get_arg_value( - scope, global, caches, lib, this_ptr, arg_expr, level, + global, caches, lib, level, scope, this_ptr, arg_expr, )? .0 .flatten(), @@ -712,7 +717,7 @@ impl Engine { #[cfg(not(feature = "no_index"))] _ if parent_chain_type == ChainType::Indexing => { _arg_values.push( - self.eval_expr(scope, global, caches, lib, this_ptr, lhs, level)? + self.eval_expr(global, caches, lib, level, scope, this_ptr, lhs)? .flatten(), ); } @@ -723,8 +728,8 @@ impl Engine { let chain_type = expr.into(); self.eval_dot_index_chain_arguments( - scope, global, caches, lib, this_ptr, rhs, *options, chain_type, idx_values, - level, + global, caches, lib, level, scope, this_ptr, rhs, *options, chain_type, + idx_values, )?; if !_arg_values.is_empty() { @@ -738,7 +743,7 @@ impl Engine { } #[cfg(not(feature = "no_index"))] _ if parent_chain_type == ChainType::Indexing => idx_values.push( - self.eval_expr(scope, global, caches, lib, this_ptr, expr, level)? + self.eval_expr(global, caches, lib, level, scope, this_ptr, expr)? .flatten(), ), _ => unreachable!("unknown chained expression: {:?}", expr), @@ -754,9 +759,9 @@ impl Engine { global: &mut GlobalRuntimeState, caches: &mut Caches, lib: &[&Module], + level: usize, target: &mut Dynamic, idx: &mut Dynamic, - level: usize, ) -> RhaiResultOf { let args = &mut [target, idx]; let hash = global.hash_idx_get(); @@ -764,8 +769,8 @@ impl Engine { let pos = Position::NONE; let level = level + 1; - self.call_native_fn( - global, caches, lib, fn_name, hash, args, true, false, pos, level, + self.exec_native_fn_call( + global, caches, lib, level, fn_name, None, hash, args, true, pos, ) .map(|(r, ..)| r) } @@ -777,11 +782,11 @@ impl Engine { global: &mut GlobalRuntimeState, caches: &mut Caches, lib: &[&Module], + level: usize, target: &mut Dynamic, idx: &mut Dynamic, new_val: &mut Dynamic, is_ref_mut: bool, - level: usize, ) -> RhaiResultOf<(Dynamic, bool)> { let hash = global.hash_idx_set(); let args = &mut [target, idx, new_val]; @@ -789,8 +794,8 @@ impl Engine { let pos = Position::NONE; let level = level + 1; - self.call_native_fn( - global, caches, lib, fn_name, hash, args, is_ref_mut, false, pos, level, + self.exec_native_fn_call( + global, caches, lib, level, fn_name, None, hash, args, is_ref_mut, pos, ) } @@ -801,12 +806,12 @@ impl Engine { global: &mut GlobalRuntimeState, caches: &mut Caches, lib: &[&Module], + level: usize, target: &'t mut Dynamic, idx: &mut Dynamic, idx_pos: Position, _add_if_not_found: bool, use_indexers: bool, - level: usize, ) -> RhaiResultOf> { self.track_operation(global, Position::NONE)?; @@ -1010,7 +1015,7 @@ impl Engine { } _ if use_indexers => self - .call_indexer_get(global, caches, lib, target, idx, level) + .call_indexer_get(global, caches, lib, level, target, idx) .map(Into::into), _ => Err(ERR::ErrorIndexingType( diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index f87e4a81..9b516fdf 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -1,7 +1,7 @@ //! Module defining the debugging interface. #![cfg(feature = "debugging")] -use super::{EvalContext, GlobalRuntimeState}; +use super::{Caches, EvalContext, GlobalRuntimeState}; use crate::ast::{ASTNode, Expr, Stmt}; use crate::{ Dynamic, Engine, EvalAltResult, ImmutableString, Module, Position, RhaiResultOf, Scope, @@ -411,16 +411,17 @@ impl Engine { #[inline(always)] pub(crate) fn run_debugger<'a>( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, + caches: &mut Caches, lib: &[&Module], + level: usize, + scope: &mut Scope, this_ptr: &mut Option<&mut Dynamic>, node: impl Into>, - level: usize, ) -> RhaiResultOf<()> { if self.debugger.is_some() { if let Some(cmd) = - self.run_debugger_with_reset_raw(scope, global, lib, this_ptr, node, level)? + self.run_debugger_with_reset_raw(global, caches, lib, level, scope, this_ptr, node)? { global.debugger.status = cmd; } @@ -430,41 +431,43 @@ impl Engine { } /// Run the debugger callback if there is a debugging interface registered. /// - /// Returns `Some` if the debugger needs to be reactivated at the end of the block, statement or + /// Returns [`Some`] if the debugger needs to be reactivated at the end of the block, statement or /// function call. /// /// It is up to the [`Engine`] to reactivate the debugger. #[inline(always)] pub(crate) fn run_debugger_with_reset<'a>( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, + caches: &mut Caches, lib: &[&Module], + level: usize, + scope: &mut Scope, this_ptr: &mut Option<&mut Dynamic>, node: impl Into>, - level: usize, ) -> RhaiResultOf> { if self.debugger.is_some() { - self.run_debugger_with_reset_raw(scope, global, lib, this_ptr, node, level) + self.run_debugger_with_reset_raw(global, caches, lib, level, scope, this_ptr, node) } else { Ok(None) } } /// Run the debugger callback. /// - /// Returns `Some` if the debugger needs to be reactivated at the end of the block, statement or + /// Returns [`Some`] if the debugger needs to be reactivated at the end of the block, statement or /// function call. /// /// It is up to the [`Engine`] to reactivate the debugger. #[inline] pub(crate) fn run_debugger_with_reset_raw<'a>( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, + caches: &mut Caches, lib: &[&Module], + level: usize, + scope: &mut Scope, this_ptr: &mut Option<&mut Dynamic>, node: impl Into>, - level: usize, ) -> RhaiResultOf> { let node = node.into(); @@ -494,28 +497,30 @@ impl Engine { }, }; - self.run_debugger_raw(scope, global, lib, this_ptr, node, event, level) + self.run_debugger_raw(global, caches, lib, level, scope, this_ptr, node, event) } /// Run the debugger callback unconditionally. /// - /// Returns `Some` if the debugger needs to be reactivated at the end of the block, statement or + /// Returns [`Some`] if the debugger needs to be reactivated at the end of the block, statement or /// function call. /// /// It is up to the [`Engine`] to reactivate the debugger. #[inline] pub(crate) fn run_debugger_raw<'a>( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, + caches: &mut Caches, lib: &[&Module], + level: usize, + scope: &mut Scope, this_ptr: &mut Option<&mut Dynamic>, node: ASTNode<'a>, event: DebuggerEvent, - level: usize, ) -> Result, Box> { let src = global.source_raw().cloned(); let src = src.as_ref().map(|s| s.as_str()); - let context = crate::EvalContext::new(self, scope, global, None, lib, this_ptr, level); + let context = + crate::EvalContext::new(self, global, Some(caches), lib, level, scope, this_ptr); if let Some((.., ref on_debugger)) = self.debugger { let command = on_debugger(context, event, node, src, node.position())?; diff --git a/src/eval/eval_context.rs b/src/eval/eval_context.rs index b1185a39..172720cd 100644 --- a/src/eval/eval_context.rs +++ b/src/eval/eval_context.rs @@ -31,12 +31,12 @@ impl<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> EvalContext<'a, 's, 'ps, 'g, 'pg, ' #[must_use] pub fn new( engine: &'a Engine, - scope: &'s mut Scope<'ps>, global: &'g mut GlobalRuntimeState<'pg>, caches: Option<&'c mut Caches<'pc>>, lib: &'a [&'a Module], - this_ptr: &'t mut Option<&'pt mut Dynamic>, level: usize, + scope: &'s mut Scope<'ps>, + this_ptr: &'t mut Option<&'pt mut Dynamic>, ) -> Self { Self { engine, @@ -182,23 +182,23 @@ impl<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> EvalContext<'a, 's, 'ps, 'g, 'pg, ' match expr { crate::ast::Expr::Stmt(statements) => self.engine.eval_stmt_block( - self.scope, self.global, caches, self.lib, + self.level, + self.scope, self.this_ptr, statements, rewind_scope, - self.level, ), _ => self.engine.eval_expr( - self.scope, self.global, caches, self.lib, + self.level, + self.scope, self.this_ptr, expr, - self.level, ), } } diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 5afd07d9..0a9558da 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -1,9 +1,8 @@ //! Module defining functions for evaluating an expression. use super::{Caches, EvalContext, GlobalRuntimeState, Target}; -use crate::ast::{Expr, FnCallExpr, OpAssignment}; +use crate::ast::{Expr, OpAssignment}; use crate::engine::{KEYWORD_THIS, OP_CONCAT}; -use crate::func::get_builtin_binary_op_fn; use crate::types::dynamic::AccessMode; use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR}; use std::num::NonZeroUsize; @@ -50,25 +49,28 @@ impl Engine { /// depending on whether the variable name is namespace-qualified. pub(crate) fn search_namespace<'s>( &self, - scope: &'s mut Scope, global: &mut GlobalRuntimeState, + caches: &mut Caches, lib: &[&Module], + level: usize, + scope: &'s mut Scope, this_ptr: &'s mut Option<&mut Dynamic>, expr: &Expr, - level: usize, ) -> RhaiResultOf<(Target<'s>, Position)> { match expr { Expr::Variable(_, Some(_), _) => { - self.search_scope_only(scope, global, lib, this_ptr, expr, level) + self.search_scope_only(global, caches, lib, level, scope, this_ptr, expr) } Expr::Variable(v, None, _var_pos) => match &**v { // Normal variable access #[cfg(not(feature = "no_module"))] (_, ns, ..) if ns.is_empty() => { - self.search_scope_only(scope, global, lib, this_ptr, expr, level) + self.search_scope_only(global, caches, lib, level, scope, this_ptr, expr) } #[cfg(feature = "no_module")] - (_, (), ..) => self.search_scope_only(scope, global, lib, this_ptr, expr, level), + (_, (), ..) => { + self.search_scope_only(global, caches, lib, level, scope, this_ptr, expr) + } // Qualified variable access #[cfg(not(feature = "no_module"))] @@ -133,12 +135,13 @@ impl Engine { /// Panics if `expr` is not [`Expr::Variable`]. pub(crate) fn search_scope_only<'s>( &self, - scope: &'s mut Scope, global: &mut GlobalRuntimeState, + caches: &mut Caches, lib: &[&Module], + level: usize, + scope: &'s mut Scope, this_ptr: &'s mut Option<&mut Dynamic>, expr: &Expr, - level: usize, ) -> RhaiResultOf<(Target<'s>, Position)> { // Make sure that the pointer indirection is taken only when absolutely necessary. @@ -170,7 +173,7 @@ impl Engine { // Check the variable resolver, if any if let Some(ref resolve_var) = self.resolve_var { - let context = EvalContext::new(self, scope, global, None, lib, this_ptr, level); + let context = EvalContext::new(self, global, Some(caches), lib, level, scope, this_ptr); let var_name = expr.get_variable_name(true).expect("`Expr::Variable`"); match resolve_var(var_name, index, context) { Ok(Some(mut result)) => { @@ -206,98 +209,6 @@ impl Engine { Ok((val.into(), var_pos)) } - /// Evaluate a function call expression. - pub(crate) fn eval_fn_call_expr( - &self, - scope: &mut Scope, - global: &mut GlobalRuntimeState, - caches: &mut Caches, - lib: &[&Module], - this_ptr: &mut Option<&mut Dynamic>, - expr: &FnCallExpr, - pos: Position, - level: usize, - ) -> RhaiResult { - let FnCallExpr { - name, - hashes, - args, - operator_token, - #[cfg(not(feature = "no_function"))] - can_be_script, - .. - } = expr; - - #[cfg(not(feature = "no_function"))] - let native = !can_be_script; - #[cfg(feature = "no_function")] - let native = true; - - // Short-circuit native binary operator call if under Fast Operators mode - if operator_token.is_some() && self.fast_operators() && args.len() == 2 { - let mut lhs = self - .get_arg_value(scope, global, caches, lib, this_ptr, &args[0], level)? - .0 - .flatten(); - - let mut rhs = self - .get_arg_value(scope, global, caches, lib, this_ptr, &args[1], level)? - .0 - .flatten(); - - let operands = &mut [&mut lhs, &mut rhs]; - - if let Some(func) = - get_builtin_binary_op_fn(operator_token.as_ref().unwrap(), operands[0], operands[1]) - { - // Built-in found - let context = (self, name.as_str(), None, &*global, lib, pos, level + 1).into(); - return func(context, operands); - } - - return self - .exec_fn_call( - None, global, caches, lib, name, native, *hashes, operands, false, false, pos, - level, - ) - .map(|(v, ..)| v); - } - - #[cfg(not(feature = "no_module"))] - if !expr.namespace.is_empty() { - // Qualified function call - let hash = hashes.native; - let namespace = &expr.namespace; - - return self.make_qualified_function_call( - scope, global, caches, lib, this_ptr, namespace, name, args, hash, pos, level, - ); - } - - // Normal function call - let (first_arg, args) = args.split_first().map_or_else( - || (None, args.as_ref()), - |(first, rest)| (Some(first), rest), - ); - - self.make_function_call( - scope, - global, - caches, - lib, - this_ptr, - name, - native, - first_arg, - args, - *hashes, - expr.capture_parent_scope, - expr.operator_token.as_ref(), - pos, - level, - ) - } - /// Evaluate an expression. // // # Implementation Notes @@ -308,28 +219,28 @@ impl Engine { // Errors that are not recoverable, such as system errors or safety errors, can use `?`. pub(crate) fn eval_expr( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, lib: &[&Module], + level: usize, + scope: &mut Scope, this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, - level: usize, ) -> RhaiResult { // Coded this way for better branch prediction. // Popular branches are lifted out of the `match` statement into their own branches. // Function calls should account for a relatively larger portion of expressions because // binary operators are also function calls. - if let Expr::FnCall(x, ..) = expr { + if let Expr::FnCall(x, pos) = expr { #[cfg(feature = "debugging")] let reset_debugger = - self.run_debugger_with_reset(scope, global, lib, this_ptr, expr, level)?; + self.run_debugger_with_reset(global, caches, lib, level, scope, this_ptr, expr)?; self.track_operation(global, expr.position())?; let result = - self.eval_fn_call_expr(scope, global, caches, lib, this_ptr, x, x.pos, level); + self.eval_fn_call_expr(global, caches, lib, level, scope, this_ptr, x, *pos); #[cfg(feature = "debugging")] global.debugger.reset_status(reset_debugger); @@ -342,7 +253,7 @@ impl Engine { // will cost more than the mis-predicted `match` branch. if let Expr::Variable(x, index, var_pos) = expr { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, expr, level)?; + self.run_debugger(global, caches, lib, level, scope, this_ptr, expr)?; self.track_operation(global, expr.position())?; @@ -352,14 +263,14 @@ impl Engine { .cloned() .ok_or_else(|| ERR::ErrorUnboundThis(*var_pos).into()) } else { - self.search_namespace(scope, global, lib, this_ptr, expr, level) + self.search_namespace(global, caches, lib, level, scope, this_ptr, expr) .map(|(val, ..)| val.take_or_clone()) }; } #[cfg(feature = "debugging")] let reset_debugger = - self.run_debugger_with_reset(scope, global, lib, this_ptr, expr, level)?; + self.run_debugger_with_reset(global, caches, lib, level, scope, this_ptr, expr)?; self.track_operation(global, expr.position())?; @@ -386,12 +297,12 @@ impl Engine { .iter() .try_for_each(|expr| { let item = - self.eval_expr(scope, global, caches, lib, this_ptr, expr, level)?; + self.eval_expr(global, caches, lib, level, scope, this_ptr, expr)?; op_info.pos = expr.start_position(); self.eval_op_assignment( - global, caches, lib, &op_info, target, root, item, level, + global, caches, lib, level, &op_info, target, root, item, ) }) .map(|_| concat.take_or_clone()); @@ -409,7 +320,7 @@ impl Engine { crate::Array::with_capacity(x.len()), |mut array, item_expr| { let value = self - .eval_expr(scope, global, caches, lib, this_ptr, item_expr, level)? + .eval_expr(global, caches, lib, level, scope, this_ptr, item_expr)? .flatten(); #[cfg(not(feature = "unchecked"))] @@ -441,7 +352,7 @@ impl Engine { x.0.iter() .try_fold(x.1.clone(), |mut map, (key, value_expr)| { let value = self - .eval_expr(scope, global, caches, lib, this_ptr, value_expr, level)? + .eval_expr(global, caches, lib, level, scope, this_ptr, value_expr)? .flatten(); #[cfg(not(feature = "unchecked"))] @@ -465,7 +376,7 @@ impl Engine { Expr::And(x, ..) => { let lhs = self - .eval_expr(scope, global, caches, lib, this_ptr, &x.lhs, level) + .eval_expr(global, caches, lib, level, scope, this_ptr, &x.lhs) .and_then(|v| { v.as_bool().map_err(|typ| { self.make_type_mismatch_err::(typ, x.lhs.position()) @@ -474,7 +385,7 @@ impl Engine { match lhs { Ok(true) => self - .eval_expr(scope, global, caches, lib, this_ptr, &x.rhs, level) + .eval_expr(global, caches, lib, level, scope, this_ptr, &x.rhs) .and_then(|v| { v.as_bool() .map_err(|typ| { @@ -488,7 +399,7 @@ impl Engine { Expr::Or(x, ..) => { let lhs = self - .eval_expr(scope, global, caches, lib, this_ptr, &x.lhs, level) + .eval_expr(global, caches, lib, level, scope, this_ptr, &x.lhs) .and_then(|v| { v.as_bool().map_err(|typ| { self.make_type_mismatch_err::(typ, x.lhs.position()) @@ -497,7 +408,7 @@ impl Engine { match lhs { Ok(false) => self - .eval_expr(scope, global, caches, lib, this_ptr, &x.rhs, level) + .eval_expr(global, caches, lib, level, scope, this_ptr, &x.rhs) .and_then(|v| { v.as_bool() .map_err(|typ| { @@ -510,11 +421,11 @@ impl Engine { } Expr::Coalesce(x, ..) => { - let lhs = self.eval_expr(scope, global, caches, lib, this_ptr, &x.lhs, level); + let lhs = self.eval_expr(global, caches, lib, level, scope, this_ptr, &x.lhs); match lhs { Ok(value) if value.is::<()>() => { - self.eval_expr(scope, global, caches, lib, this_ptr, &x.rhs, level) + self.eval_expr(global, caches, lib, level, scope, this_ptr, &x.rhs) } _ => lhs, } @@ -535,7 +446,7 @@ impl Engine { )) })?; let mut context = - EvalContext::new(self, scope, global, Some(caches), lib, this_ptr, level); + EvalContext::new(self, global, Some(caches), lib, level, scope, this_ptr); let result = (custom_def.func)(&mut context, &expressions, &custom.state); @@ -544,16 +455,16 @@ impl Engine { Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT), Expr::Stmt(x) => { - self.eval_stmt_block(scope, global, caches, lib, this_ptr, x, true, level) + self.eval_stmt_block(global, caches, lib, level, scope, this_ptr, x, true) } #[cfg(not(feature = "no_index"))] Expr::Index(..) => self - .eval_dot_index_chain(scope, global, caches, lib, this_ptr, expr, level, &mut None), + .eval_dot_index_chain(global, caches, lib, level, scope, this_ptr, expr, &mut None), #[cfg(not(feature = "no_object"))] Expr::Dot(..) => self - .eval_dot_index_chain(scope, global, caches, lib, this_ptr, expr, level, &mut None), + .eval_dot_index_chain(global, caches, lib, level, scope, this_ptr, expr, &mut None), _ => unreachable!("expression cannot be evaluated: {:?}", expr), }; diff --git a/src/eval/global_state.rs b/src/eval/global_state.rs index bf4489f9..db4c5332 100644 --- a/src/eval/global_state.rs +++ b/src/eval/global_state.rs @@ -286,6 +286,7 @@ impl GlobalRuntimeState<'_> { /// Get the current source. #[inline(always)] #[must_use] + #[allow(dead_code)] pub(crate) const fn source_raw(&self) -> Option<&ImmutableString> { self.source.as_ref() } diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 0a302ee6..5bf03093 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -25,14 +25,14 @@ impl Engine { // Errors that are not recoverable, such as system errors or safety errors, can use `?`. pub(crate) fn eval_stmt_block( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, lib: &[&Module], + level: usize, + scope: &mut Scope, this_ptr: &mut Option<&mut Dynamic>, statements: &[Stmt], restore_orig_state: bool, - level: usize, ) -> RhaiResult { if statements.is_empty() { return Ok(Dynamic::UNIT); @@ -53,14 +53,14 @@ impl Engine { let imports_len = global.num_imports(); let result = self.eval_stmt( - scope, global, caches, lib, + level, + scope, this_ptr, stmt, restore_orig_state, - level, )?; #[cfg(not(feature = "no_module"))] @@ -113,11 +113,11 @@ impl Engine { global: &mut GlobalRuntimeState, caches: &mut Caches, lib: &[&Module], + level: usize, op_info: &OpAssignment, target: &mut Target, root: (&str, Position), new_val: Dynamic, - level: usize, ) -> RhaiResultOf<()> { if target.is_read_only() { // Assignment to constant variable @@ -130,8 +130,8 @@ impl Engine { let OpAssignment { hash_op_assign, hash_op, - op_assign, - op, + op_assign: op_assign_token, + op: op_token, pos: op_pos, } = op_info; @@ -142,27 +142,31 @@ impl Engine { let level = level + 1; if self.fast_operators() { - if let Some(func) = get_builtin_op_assignment_fn(op_assign, args[0], args[1]) { + if let Some(func) = get_builtin_op_assignment_fn(op_assign_token, args[0], args[1]) + { // Built-in found - let op = op_assign.literal_syntax(); + let op = op_assign_token.literal_syntax(); let context = (self, op, None, &*global, lib, *op_pos, level).into(); return func(context, args).map(|_| ()); } } - let op_assign = op_assign.literal_syntax(); - let op = op.literal_syntax(); + let op_assign = op_assign_token.literal_syntax(); + let op = op_token.literal_syntax(); + let token = Some(op_assign_token); - match self.call_native_fn( - global, caches, lib, op_assign, hash, args, true, true, *op_pos, level, + match self.exec_native_fn_call( + global, caches, lib, level, op_assign, token, hash, args, true, *op_pos, ) { Ok(_) => (), Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, ..) if f.starts_with(op_assign)) => { // Expand to `var = var op rhs` + let token = Some(op_token); + *args[0] = self - .call_native_fn( - global, caches, lib, op, *hash_op, args, true, false, *op_pos, level, + .exec_native_fn_call( + global, caches, lib, level, op, token, *hash_op, args, true, *op_pos, ) .map_err(|err| err.fill_position(op_info.pos))? .0 @@ -190,28 +194,28 @@ impl Engine { // Errors that are not recoverable, such as system errors or safety errors, can use `?`. pub(crate) fn eval_stmt( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, lib: &[&Module], + level: usize, + scope: &mut Scope, this_ptr: &mut Option<&mut Dynamic>, stmt: &Stmt, rewind_scope: bool, - level: usize, ) -> RhaiResult { #[cfg(feature = "debugging")] let reset_debugger = - self.run_debugger_with_reset(scope, global, lib, this_ptr, stmt, level)?; + self.run_debugger_with_reset(global, caches, lib, level, scope, this_ptr, stmt)?; // Coded this way for better branch prediction. // Popular branches are lifted out of the `match` statement into their own branches. // Function calls should account for a relatively larger portion of statements. - if let Stmt::FnCall(x, ..) = stmt { + if let Stmt::FnCall(x, pos) = stmt { self.track_operation(global, stmt.position())?; let result = - self.eval_fn_call_expr(scope, global, caches, lib, this_ptr, x, x.pos, level); + self.eval_fn_call_expr(global, caches, lib, level, scope, this_ptr, x, *pos); #[cfg(feature = "debugging")] global.debugger.reset_status(reset_debugger); @@ -229,12 +233,12 @@ impl Engine { let result = if let Expr::Variable(x, ..) = lhs { let rhs_result = self - .eval_expr(scope, global, caches, lib, this_ptr, rhs, level) + .eval_expr(global, caches, lib, level, scope, this_ptr, rhs) .map(Dynamic::flatten); if let Ok(rhs_val) = rhs_result { let search_result = - self.search_namespace(scope, global, lib, this_ptr, lhs, level); + self.search_namespace(global, caches, lib, level, scope, this_ptr, lhs); if let Ok(search_val) = search_result { let (mut lhs_ptr, pos) = search_val; @@ -261,7 +265,7 @@ impl Engine { let lhs_ptr = &mut lhs_ptr; self.eval_op_assignment( - global, caches, lib, op_info, lhs_ptr, root, rhs_val, level, + global, caches, lib, level, op_info, lhs_ptr, root, rhs_val, ) .map(|_| Dynamic::UNIT) } else { @@ -273,7 +277,7 @@ impl Engine { } else { let (op_info, BinaryExpr { lhs, rhs }) = &**x; - let rhs_result = self.eval_expr(scope, global, caches, lib, this_ptr, rhs, level); + let rhs_result = self.eval_expr(global, caches, lib, level, scope, this_ptr, rhs); if let Ok(rhs_val) = rhs_result { // Check if the result is a string. If so, intern it. @@ -303,14 +307,14 @@ impl Engine { #[cfg(not(feature = "no_index"))] Expr::Index(..) => self .eval_dot_index_chain( - scope, global, caches, lib, this_ptr, lhs, level, _new_val, + global, caches, lib, level, scope, this_ptr, lhs, _new_val, ) .map(|_| Dynamic::UNIT), // dot_lhs.dot_rhs op= rhs #[cfg(not(feature = "no_object"))] Expr::Dot(..) => self .eval_dot_index_chain( - scope, global, caches, lib, this_ptr, lhs, level, _new_val, + global, caches, lib, level, scope, this_ptr, lhs, _new_val, ) .map(|_| Dynamic::UNIT), _ => unreachable!("cannot assign to expression: {:?}", lhs), @@ -334,13 +338,13 @@ impl Engine { // Expression as statement Stmt::Expr(expr) => self - .eval_expr(scope, global, caches, lib, this_ptr, expr, level) + .eval_expr(global, caches, lib, level, scope, this_ptr, expr) .map(Dynamic::flatten), // Block scope Stmt::Block(statements, ..) if statements.is_empty() => Ok(Dynamic::UNIT), Stmt::Block(statements, ..) => self.eval_stmt_block( - scope, global, caches, lib, this_ptr, statements, true, level, + global, caches, lib, level, scope, this_ptr, statements, true, ), // If statement @@ -348,7 +352,7 @@ impl Engine { let (expr, if_block, else_block) = &**x; let guard_val = self - .eval_expr(scope, global, caches, lib, this_ptr, expr, level) + .eval_expr(global, caches, lib, level, scope, this_ptr, expr) .and_then(|v| { v.as_bool().map_err(|typ| { self.make_type_mismatch_err::(typ, expr.position()) @@ -358,11 +362,11 @@ impl Engine { match guard_val { Ok(true) if if_block.is_empty() => Ok(Dynamic::UNIT), Ok(true) => self.eval_stmt_block( - scope, global, caches, lib, this_ptr, if_block, true, level, + global, caches, lib, level, scope, this_ptr, if_block, true, ), Ok(false) if else_block.is_empty() => Ok(Dynamic::UNIT), Ok(false) => self.eval_stmt_block( - scope, global, caches, lib, this_ptr, else_block, true, level, + global, caches, lib, level, scope, this_ptr, else_block, true, ), err => err.map(Into::into), } @@ -381,7 +385,7 @@ impl Engine { ) = &**x; let value_result = - self.eval_expr(scope, global, caches, lib, this_ptr, expr, level); + self.eval_expr(global, caches, lib, level, scope, this_ptr, expr); if let Ok(value) = value_result { let expr_result = if value.is_hashable() { @@ -401,7 +405,7 @@ impl Engine { let cond_result = match block.condition { Expr::BoolConstant(b, ..) => Ok(b), ref c => self - .eval_expr(scope, global, caches, lib, this_ptr, c, level) + .eval_expr(global, caches, lib, level, scope, this_ptr, c) .and_then(|v| { v.as_bool().map_err(|typ| { self.make_type_mismatch_err::( @@ -432,7 +436,7 @@ impl Engine { let cond_result = match block.condition { Expr::BoolConstant(b, ..) => Ok(b), ref c => self - .eval_expr(scope, global, caches, lib, this_ptr, c, level) + .eval_expr(global, caches, lib, level, scope, this_ptr, c) .and_then(|v| { v.as_bool().map_err(|typ| { self.make_type_mismatch_err::( @@ -462,12 +466,12 @@ impl Engine { }; if let Ok(Some(expr)) = expr_result { - self.eval_expr(scope, global, caches, lib, this_ptr, expr, level) + self.eval_expr(global, caches, lib, level, scope, this_ptr, expr) } else if let Ok(None) = expr_result { // Default match clause def_case.as_ref().map_or(Ok(Dynamic::UNIT), |&index| { let def_expr = &expressions[index].expr; - self.eval_expr(scope, global, caches, lib, this_ptr, def_expr, level) + self.eval_expr(global, caches, lib, level, scope, this_ptr, def_expr) }) } else { expr_result.map(|_| Dynamic::UNIT) @@ -488,7 +492,7 @@ impl Engine { } else { loop { match self.eval_stmt_block( - scope, global, caches, lib, this_ptr, body, true, level, + global, caches, lib, level, scope, this_ptr, body, true, ) { Ok(_) => (), Err(err) => match *err { @@ -507,7 +511,7 @@ impl Engine { loop { let condition = self - .eval_expr(scope, global, caches, lib, this_ptr, expr, level) + .eval_expr(global, caches, lib, level, scope, this_ptr, expr) .and_then(|v| { v.as_bool().map_err(|typ| { self.make_type_mismatch_err::(typ, expr.position()) @@ -519,7 +523,7 @@ impl Engine { Ok(true) if body.is_empty() => (), Ok(true) => { match self.eval_stmt_block( - scope, global, caches, lib, this_ptr, body, true, level, + global, caches, lib, level, scope, this_ptr, body, true, ) { Ok(_) => (), Err(err) => match *err { @@ -542,7 +546,7 @@ impl Engine { loop { if !body.is_empty() { match self.eval_stmt_block( - scope, global, caches, lib, this_ptr, body, true, level, + global, caches, lib, level, scope, this_ptr, body, true, ) { Ok(_) => (), Err(err) => match *err { @@ -554,7 +558,7 @@ impl Engine { } let condition = self - .eval_expr(scope, global, caches, lib, this_ptr, expr, level) + .eval_expr(global, caches, lib, level, scope, this_ptr, expr) .and_then(|v| { v.as_bool().map_err(|typ| { self.make_type_mismatch_err::(typ, expr.position()) @@ -574,7 +578,7 @@ impl Engine { let (var_name, counter, expr, statements) = &**x; let iter_result = self - .eval_expr(scope, global, caches, lib, this_ptr, expr, level) + .eval_expr(global, caches, lib, level, scope, this_ptr, expr) .map(Dynamic::flatten); if let Ok(iter_obj) = iter_result { @@ -648,7 +652,7 @@ impl Engine { } self.eval_stmt_block( - scope, global, caches, lib, this_ptr, statements, true, level, + global, caches, lib, level, scope, this_ptr, statements, true, ) .map(|_| Dynamic::UNIT) .or_else(|err| match *err { @@ -677,7 +681,7 @@ impl Engine { let is_break = options.contains(ASTFlags::BREAK); if let Some(ref expr) = expr { - self.eval_expr(scope, global, caches, lib, this_ptr, expr, level) + self.eval_expr(global, caches, lib, level, scope, this_ptr, expr) .and_then(|v| ERR::LoopBreak(is_break, v, *pos).into()) } else { Err(ERR::LoopBreak(is_break, Dynamic::UNIT, *pos).into()) @@ -696,7 +700,7 @@ impl Engine { } = &**x; let result = self - .eval_stmt_block(scope, global, caches, lib, this_ptr, try_block, true, level) + .eval_stmt_block(global, caches, lib, level, scope, this_ptr, try_block, true) .map(|_| Dynamic::UNIT); match result { @@ -746,14 +750,14 @@ impl Engine { } let result = self.eval_stmt_block( - scope, global, caches, lib, + level, + scope, this_ptr, catch_block, true, - level, ); scope.rewind(orig_scope_len); @@ -775,7 +779,7 @@ impl Engine { // Throw value Stmt::Return(Some(expr), options, pos) if options.contains(ASTFlags::BREAK) => self - .eval_expr(scope, global, caches, lib, this_ptr, expr, level) + .eval_expr(global, caches, lib, level, scope, this_ptr, expr) .and_then(|v| Err(ERR::ErrorRuntime(v.flatten(), *pos).into())), // Empty throw @@ -785,7 +789,7 @@ impl Engine { // Return value Stmt::Return(Some(expr), .., pos) => self - .eval_expr(scope, global, caches, lib, this_ptr, expr, level) + .eval_expr(global, caches, lib, level, scope, this_ptr, expr) .and_then(|v| Err(ERR::Return(v.flatten(), *pos).into())), // Empty return @@ -817,7 +821,7 @@ impl Engine { nesting_level, will_shadow, }; - let context = EvalContext::new(self, scope, global, None, lib, this_ptr, level); + let context = EvalContext::new(self, global, None, lib, level, scope, this_ptr); match filter(true, info, context) { Ok(true) => None, @@ -837,7 +841,7 @@ impl Engine { } else { // Evaluate initial value let value_result = self - .eval_expr(scope, global, caches, lib, this_ptr, expr, level) + .eval_expr(global, caches, lib, level, scope, this_ptr, expr) .map(Dynamic::flatten); if let Ok(mut value) = value_result { @@ -900,7 +904,7 @@ impl Engine { } let path_result = self - .eval_expr(scope, global, caches, lib, this_ptr, expr, level) + .eval_expr(global, caches, lib, level, scope, this_ptr, expr) .and_then(|v| { let typ = v.type_name(); v.try_cast::().ok_or_else(|| { @@ -1006,4 +1010,28 @@ impl Engine { result } + + /// Evaluate a list of statements with no `this` pointer. + /// This is commonly used to evaluate a list of statements in an [`AST`][crate::AST] or a script function body. + #[inline] + pub(crate) fn eval_global_statements( + &self, + global: &mut GlobalRuntimeState, + caches: &mut Caches, + lib: &[&Module], + level: usize, + scope: &mut Scope, + statements: &[Stmt], + ) -> RhaiResult { + self.eval_stmt_block( + global, caches, lib, level, scope, &mut None, statements, false, + ) + .or_else(|err| match *err { + ERR::Return(out, ..) => Ok(out), + ERR::LoopBreak(..) => { + unreachable!("no outer loop scope to break out of") + } + _ => Err(err), + }) + } } diff --git a/src/func/call.rs b/src/func/call.rs index ef5d77e7..817a8d7b 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -1,19 +1,17 @@ //! Implement function-calling mechanism for [`Engine`]. -use super::callable_function::CallableFunction; -use super::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn}; +use super::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn, CallableFunction}; use crate::api::default_limits::MAX_DYNAMIC_PARAMETERS; -use crate::ast::{Expr, FnCallHashes, Stmt}; +use crate::ast::{Expr, FnCallExpr, FnCallHashes}; use crate::engine::{ KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF, }; use crate::eval::{Caches, FnResolutionCacheEntry, GlobalRuntimeState}; -use crate::tokenizer::Token; +use crate::tokenizer::{is_valid_function_name, Token}; use crate::{ - calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnArgsVec, FnPtr, - ImmutableString, Module, OptimizationLevel, Position, RhaiError, RhaiResult, RhaiResultOf, - Scope, ERR, + calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString, Module, + OptimizationLevel, Position, RhaiError, RhaiResult, RhaiResultOf, Scope, ERR, }; #[cfg(feature = "no_std")] use hashbrown::hash_map::Entry; @@ -108,17 +106,14 @@ impl Drop for ArgBackup<'_> { } } +// Ensure no data races in function call arguments. #[cfg(not(feature = "no_closure"))] #[inline] -pub fn ensure_no_data_race( - fn_name: &str, - args: &FnCallArgs, - is_method_call: bool, -) -> RhaiResultOf<()> { +pub fn ensure_no_data_race(fn_name: &str, args: &FnCallArgs, is_ref_mut: bool) -> RhaiResultOf<()> { if let Some((n, ..)) = args .iter() .enumerate() - .skip(if is_method_call { 1 } else { 0 }) + .skip(if is_ref_mut { 1 } else { 0 }) .find(|(.., a)| a.is_locked()) { return Err(ERR::ErrorDataRace( @@ -131,48 +126,32 @@ pub fn ensure_no_data_race( Ok(()) } -/// Generate the signature for a function call. +/// Is a function name an anonymous function? +#[cfg(not(feature = "no_function"))] #[inline] #[must_use] -pub fn gen_fn_call_signature(engine: &Engine, fn_name: &str, args: &[&mut Dynamic]) -> String { - format!( - "{fn_name} ({})", - args.iter() - .map(|a| if a.is::() { - "&str | ImmutableString | String" - } else { - engine.map_type_name(a.type_name()) - }) - .collect::>() - .join(", ") - ) -} - -/// Generate the signature for a namespace-qualified function call. -/// -/// Not available under `no_module`. -#[cfg(not(feature = "no_module"))] -#[inline] -#[must_use] -pub fn gen_qualified_fn_call_signature( - engine: &Engine, - namespace: &crate::ast::Namespace, - fn_name: &str, - args: &[&mut Dynamic], -) -> String { - let (ns, sep) = ( - namespace.to_string(), - if namespace.is_empty() { - "" - } else { - crate::tokenizer::Token::DoubleColon.literal_syntax() - }, - ); - - format!("{ns}{sep}{}", gen_fn_call_signature(engine, fn_name, args)) +pub fn is_anonymous_fn(name: &str) -> bool { + name.starts_with(crate::engine::FN_ANONYMOUS) } impl Engine { + /// Generate the signature for a function call. + #[inline] + #[must_use] + fn gen_fn_call_signature(&self, fn_name: &str, args: &[&mut Dynamic]) -> String { + format!( + "{fn_name} ({})", + args.iter() + .map(|a| if a.is::() { + "&str | ImmutableString | String" + } else { + self.map_type_name(a.type_name()) + }) + .collect::>() + .join(", ") + ) + } + /// Resolve a normal (non-qualified) function call. /// /// Search order: @@ -188,19 +167,17 @@ impl Engine { caches: &'s mut Caches, local_entry: &'s mut Option, lib: &[&Module], - fn_name: &str, + op_token: Option<&Token>, hash_base: u64, args: Option<&mut FnCallArgs>, allow_dynamic: bool, - op_assignment_token: Option<&Token>, ) -> Option<&'s FnResolutionCacheEntry> { if hash_base == 0 { return None; } let mut hash = args.as_ref().map_or(hash_base, |args| { - let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id())); - combine_hashes(hash_base, hash_params) + calc_fn_hash_full(hash_base, args.iter().map(|a| a.type_id())) }); let cache = caches.fn_resolution_cache_mut(); @@ -277,26 +254,25 @@ impl Engine { } // Try to find a built-in version - let builtin = args.and_then(|args| { - if let Some(op_assign) = op_assignment_token { - let (first_arg, rest_args) = args.split_first().unwrap(); + let builtin = + args.and_then(|args| match op_token { + Some(token) if token.is_op_assignment() => { + let (first_arg, rest_args) = args.split_first().unwrap(); - get_builtin_op_assignment_fn(op_assign, *first_arg, rest_args[0]) + get_builtin_op_assignment_fn(token, *first_arg, rest_args[0]) + .map(|f| FnResolutionCacheEntry { + func: CallableFunction::from_fn_builtin(f), + source: None, + }) + } + Some(token) => get_builtin_binary_op_fn(token, args[0], args[1]) .map(|f| FnResolutionCacheEntry { func: CallableFunction::from_fn_builtin(f), source: None, - }) - } else if let Some(ref operator) = Token::lookup_from_syntax(fn_name) { - get_builtin_binary_op_fn(operator, args[0], args[1]).map(|f| { - FnResolutionCacheEntry { - func: CallableFunction::from_fn_builtin(f), - source: None, - } - }) - } else { - None - } - }); + }), + + None => None, + }); return if cache.filter.is_absent_and_set(hash) { // Do not cache "one-hit wonders" @@ -309,7 +285,8 @@ impl Engine { } // Try all permutations with `Dynamic` wildcards - let hash_params = calc_fn_params_hash( + hash = calc_fn_hash_full( + hash_base, args.as_ref() .expect("no permutations") .iter() @@ -324,7 +301,6 @@ impl Engine { } }), ); - hash = combine_hashes(hash_base, hash_params); bitmask += 1; } @@ -343,27 +319,21 @@ impl Engine { /// /// **DO NOT** reuse the argument values unless for the first `&mut` argument - /// all others are silently replaced by `()`! - pub(crate) fn call_native_fn( + pub(crate) fn exec_native_fn_call( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, lib: &[&Module], + level: usize, name: &str, + op_token: Option<&Token>, hash: u64, args: &mut FnCallArgs, is_ref_mut: bool, - is_op_assign: bool, pos: Position, - level: usize, ) -> RhaiResultOf<(Dynamic, bool)> { self.track_operation(global, pos)?; - let op_assign = if is_op_assign { - Token::lookup_from_syntax(name) - } else { - None - }; - // Check if function access already in the cache let local_entry = &mut None; @@ -372,11 +342,10 @@ impl Engine { caches, local_entry, lib, - name, + op_token, hash, Some(args), true, - op_assign.as_ref(), ); if func.is_some() { @@ -445,7 +414,9 @@ impl Engine { Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r), Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err), }; - match self.run_debugger_raw(scope, global, lib, &mut None, node, event, level) { + match self + .run_debugger_raw(global, caches, lib, level, scope, &mut None, node, event) + { Ok(_) => (), Err(err) => _result = Err(err), } @@ -547,7 +518,7 @@ impl Engine { // Raise error _ => { - Err(ERR::ErrorFunctionNotFound(gen_fn_call_signature(self, name, args), pos).into()) + Err(ERR::ErrorFunctionNotFound(self.gen_fn_call_signature(name, args), pos).into()) } } } @@ -565,18 +536,18 @@ impl Engine { /// all others are silently replaced by `()`! pub(crate) fn exec_fn_call( &self, - _scope: Option<&mut Scope>, global: &mut GlobalRuntimeState, caches: &mut Caches, lib: &[&Module], + level: usize, + _scope: Option<&mut Scope>, fn_name: &str, - _native_only: bool, + op_token: Option<&Token>, hashes: FnCallHashes, args: &mut FnCallArgs, is_ref_mut: bool, _is_method_call: bool, pos: Position, - level: usize, ) -> RhaiResultOf<(Dynamic, bool)> { fn no_method_err(name: &str, pos: Position) -> RhaiResultOf<(Dynamic, bool)> { Err(ERR::ErrorRuntime( @@ -590,55 +561,58 @@ impl Engine { #[cfg(not(feature = "no_closure"))] ensure_no_data_race(fn_name, args, is_ref_mut)?; - // These may be redirected from method style calls. - match fn_name { - // Handle type_of() - KEYWORD_TYPE_OF if args.len() == 1 => { - let typ = self.map_type_name(args[0].type_name()).to_string().into(); - return Ok((typ, false)); - } - - // Handle is_def_fn() - #[cfg(not(feature = "no_function"))] - crate::engine::KEYWORD_IS_DEF_FN - if args.len() == 2 && args[0].is::() && args[1].is::() => - { - let fn_name = args[0].read_lock::().expect("`FnPtr`"); - let num_params = args[1].as_int().expect("`INT`"); - - return Ok(( - if num_params < 0 || num_params > crate::MAX_USIZE_INT { - false - } else { - let hash_script = calc_fn_hash(None, fn_name.as_str(), num_params as usize); - self.has_script_fn(Some(global), caches, lib, hash_script) - } - .into(), - false, - )); - } - - // Handle is_shared() - #[cfg(not(feature = "no_closure"))] - crate::engine::KEYWORD_IS_SHARED if args.len() == 1 => { - return no_method_err(fn_name, pos) - } - - KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR if args.len() == 1 => { - return no_method_err(fn_name, pos) - } - - KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY if !args.is_empty() => { - return no_method_err(fn_name, pos) - } - - _ => (), - } - let level = level + 1; + // These may be redirected from method style calls. + if hashes.is_native_only() { + match fn_name { + // Handle type_of() + KEYWORD_TYPE_OF if args.len() == 1 => { + let typ = self.map_type_name(args[0].type_name()).to_string().into(); + return Ok((typ, false)); + } + + // Handle is_def_fn() + #[cfg(not(feature = "no_function"))] + crate::engine::KEYWORD_IS_DEF_FN + if args.len() == 2 && args[0].is::() && args[1].is::() => + { + let fn_name = args[0].read_lock::().expect("`FnPtr`"); + let num_params = args[1].as_int().expect("`INT`"); + + return Ok(( + if num_params < 0 || num_params > crate::MAX_USIZE_INT { + false + } else { + let hash_script = + calc_fn_hash(None, fn_name.as_str(), num_params as usize); + self.has_script_fn(Some(global), caches, lib, hash_script) + } + .into(), + false, + )); + } + + // Handle is_shared() + #[cfg(not(feature = "no_closure"))] + crate::engine::KEYWORD_IS_SHARED if args.len() == 1 => { + return no_method_err(fn_name, pos) + } + + KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR if args.len() == 1 => { + return no_method_err(fn_name, pos) + } + + KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY if !args.is_empty() => { + return no_method_err(fn_name, pos) + } + + _ => (), + } + } + #[cfg(not(feature = "no_function"))] - if !_native_only { + if !hashes.is_native_only() { // Script-defined function call? let local_entry = &mut None; @@ -648,11 +622,10 @@ impl Engine { caches, local_entry, lib, - fn_name, - hashes.script, + None, + hashes.script(), None, false, - None, ) .cloned() { @@ -681,16 +654,16 @@ impl Engine { let (first_arg, rest_args) = args.split_first_mut().unwrap(); self.call_script_fn( - scope, global, caches, lib, + level, + scope, &mut Some(*first_arg), func, rest_args, true, pos, - level, ) } else { // Normal call of script function @@ -702,7 +675,7 @@ impl Engine { } let result = self.call_script_fn( - scope, global, caches, lib, &mut None, func, args, true, pos, level, + global, caches, lib, level, scope, &mut None, func, args, true, pos, ); // Restore the original reference @@ -714,60 +687,38 @@ impl Engine { // Restore the original source global.source = orig_source; - return Ok((result?, false)); + return result.map(|r| (r, false)); } } // Native function call - let hash = hashes.native; - self.call_native_fn( - global, caches, lib, fn_name, hash, args, is_ref_mut, false, pos, level, - ) - } + let hash = hashes.native(); - /// Evaluate a list of statements with no `this` pointer. - /// This is commonly used to evaluate a list of statements in an [`AST`][crate::AST] or a script function body. - #[inline] - pub(crate) fn eval_global_statements( - &self, - scope: &mut Scope, - global: &mut GlobalRuntimeState, - caches: &mut Caches, - statements: &[Stmt], - lib: &[&Module], - level: usize, - ) -> RhaiResult { - self.eval_stmt_block( - scope, global, caches, lib, &mut None, statements, false, level, + self.exec_native_fn_call( + global, caches, lib, level, fn_name, op_token, hash, args, is_ref_mut, pos, ) - .or_else(|err| match *err { - ERR::Return(out, ..) => Ok(out), - ERR::LoopBreak(..) => { - unreachable!("no outer loop scope to break out of") - } - _ => Err(err), - }) } /// Evaluate an argument. #[inline] pub(crate) fn get_arg_value( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, lib: &[&Module], + level: usize, + scope: &mut Scope, this_ptr: &mut Option<&mut Dynamic>, arg_expr: &Expr, - level: usize, ) -> RhaiResultOf<(Dynamic, Position)> { - #[cfg(feature = "debugging")] - if self.debugger.is_some() { - if let Some(value) = arg_expr.get_literal_value() { - #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, arg_expr, level)?; - return Ok((value, arg_expr.start_position())); - } + // Literal values + if let Some(value) = arg_expr.get_literal_value() { + self.track_operation(global, arg_expr.start_position())?; + + #[cfg(feature = "debugging")] + self.run_debugger(global, caches, lib, level, scope, this_ptr, arg_expr)?; + + return Ok((value, arg_expr.start_position())); } // Do not match function exit for arguments @@ -776,13 +727,13 @@ impl Engine { matches!(status, crate::eval::DebuggerStatus::FunctionExit(..)) }); - let result = self.eval_expr(scope, global, caches, lib, this_ptr, arg_expr, level); + let result = self.eval_expr(global, caches, lib, level, scope, this_ptr, arg_expr); // Restore function exit status #[cfg(feature = "debugging")] global.debugger.reset_status(reset_debugger); - Ok((result?, arg_expr.start_position())) + result.map(|r| (r, arg_expr.start_position())) } /// Call a dot method. @@ -792,13 +743,13 @@ impl Engine { global: &mut GlobalRuntimeState, caches: &mut Caches, lib: &[&Module], + level: usize, fn_name: &str, mut hash: FnCallHashes, target: &mut crate::eval::Target, mut call_args: &mut [Dynamic], first_arg_pos: Position, fn_call_pos: Position, - level: usize, ) -> RhaiResultOf<(Dynamic, bool)> { let is_ref_mut = target.is_ref(); @@ -806,11 +757,21 @@ impl Engine { KEYWORD_FN_PTR_CALL if target.is::() => { // FnPtr call let fn_ptr = target.read_lock::().expect("`FnPtr`"); + + #[cfg(not(feature = "no_function"))] + let is_anon = fn_ptr.is_anonymous(); + #[cfg(feature = "no_function")] + let is_anon = false; + // Redirect function name let fn_name = fn_ptr.fn_name(); - let args_len = call_args.len() + fn_ptr.curry().len(); // Recalculate hashes - let new_hash = calc_fn_hash(None, fn_name, args_len).into(); + let args_len = call_args.len() + fn_ptr.curry().len(); + let new_hash = if !is_anon && !is_valid_function_name(fn_name) { + FnCallHashes::from_native(calc_fn_hash(None, fn_name, args_len)) + } else { + calc_fn_hash(None, fn_name, args_len).into() + }; // Arguments are passed as-is, adding the curried arguments let mut curry = FnArgsVec::with_capacity(fn_ptr.curry().len()); curry.extend(fn_ptr.curry().iter().cloned()); @@ -820,18 +781,18 @@ impl Engine { // Map it to name(args) in function-call style self.exec_fn_call( - None, global, caches, lib, + level, + None, fn_name, - false, + None, new_hash, &mut args, false, false, fn_call_pos, - level, ) } KEYWORD_FN_PTR_CALL => { @@ -845,20 +806,30 @@ impl Engine { // FnPtr call on object let fn_ptr = mem::take(&mut call_args[0]).cast::(); + + #[cfg(not(feature = "no_function"))] + let is_anon = fn_ptr.is_anonymous(); + #[cfg(feature = "no_function")] + let is_anon = false; + call_args = &mut call_args[1..]; // Redirect function name - let fn_name = fn_ptr.fn_name(); - let args_len = call_args.len() + fn_ptr.curry().len(); + let (fn_name, fn_curry) = fn_ptr.take_data(); // Recalculate hash - let new_hash = FnCallHashes::from_all( - #[cfg(not(feature = "no_function"))] - calc_fn_hash(None, fn_name, args_len), - calc_fn_hash(None, fn_name, args_len + 1), - ); + let args_len = call_args.len() + fn_curry.len(); + let new_hash = if !is_anon && !is_valid_function_name(&fn_name) { + FnCallHashes::from_native(calc_fn_hash(None, &fn_name, args_len + 1)) + } else { + FnCallHashes::from_all( + #[cfg(not(feature = "no_function"))] + calc_fn_hash(None, &fn_name, args_len), + calc_fn_hash(None, &fn_name, args_len + 1), + ) + }; // Replace the first argument with the object pointer, adding the curried arguments - let mut curry = FnArgsVec::with_capacity(fn_ptr.curry().len()); - curry.extend(fn_ptr.curry().iter().cloned()); + let mut curry = FnArgsVec::with_capacity(fn_curry.len()); + curry.extend(fn_curry.into_iter()); let mut args = FnArgsVec::with_capacity(curry.len() + call_args.len() + 1); args.push(target.as_mut()); args.extend(curry.iter_mut()); @@ -866,18 +837,18 @@ impl Engine { // Map it to name(args) in function-call style self.exec_fn_call( - None, global, caches, lib, - fn_name, - false, + level, + None, + &fn_name, + None, new_hash, &mut args, is_ref_mut, true, fn_call_pos, - level, ) } KEYWORD_FN_PTR_CURRY => { @@ -925,6 +896,11 @@ impl Engine { if let Some(map) = target.read_lock::() { if let Some(val) = map.get(fn_name) { if let Some(fn_ptr) = val.read_lock::() { + #[cfg(not(feature = "no_function"))] + let is_anon = fn_ptr.is_anonymous(); + #[cfg(feature = "no_function")] + let is_anon = false; + // Remap the function name _redirected = fn_ptr.fn_name_raw().clone(); fn_name = &_redirected; @@ -939,11 +915,19 @@ impl Engine { call_args = &mut _arg_values; } // Recalculate the hash based on the new function name and new arguments - hash = FnCallHashes::from_all( - #[cfg(not(feature = "no_function"))] - calc_fn_hash(None, fn_name, call_args.len()), - calc_fn_hash(None, fn_name, call_args.len() + 1), - ); + hash = if !is_anon && !is_valid_function_name(&fn_name) { + FnCallHashes::from_native(calc_fn_hash( + None, + fn_name, + call_args.len() + 1, + )) + } else { + FnCallHashes::from_all( + #[cfg(not(feature = "no_function"))] + calc_fn_hash(None, fn_name, call_args.len()), + calc_fn_hash(None, fn_name, call_args.len() + 1), + ) + }; } } }; @@ -954,18 +938,18 @@ impl Engine { args.extend(call_args.iter_mut()); self.exec_fn_call( - None, global, caches, lib, + level, + None, fn_name, - false, + None, hash, &mut args, is_ref_mut, true, fn_call_pos, - level, ) } }?; @@ -981,22 +965,20 @@ impl Engine { /// Call a function in normal function-call style. pub(crate) fn make_function_call( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, lib: &[&Module], + level: usize, + scope: &mut Scope, this_ptr: &mut Option<&mut Dynamic>, fn_name: &str, - native_only: bool, + op_token: Option<&Token>, first_arg: Option<&Expr>, args_expr: &[Expr], hashes: FnCallHashes, capture_scope: bool, - operator_token: Option<&Token>, pos: Position, - level: usize, ) -> RhaiResult { - let native = native_only; let mut first_arg = first_arg; let mut a_expr = args_expr; let mut total_args = if first_arg.is_some() { 1 } else { 0 } + a_expr.len(); @@ -1006,13 +988,13 @@ impl Engine { let redirected; // Handle call() - Redirect function call match name { - _ if operator_token.is_some() => (), + _ if op_token.is_some() => (), // Handle call() KEYWORD_FN_PTR_CALL if total_args >= 1 => { let arg = first_arg.unwrap(); let (arg_value, arg_pos) = - self.get_arg_value(scope, global, caches, lib, this_ptr, arg, level)?; + self.get_arg_value(global, caches, lib, level, scope, this_ptr, arg)?; if !arg_value.is::() { let typ = self.map_type_name(arg_value.type_name()); @@ -1020,10 +1002,17 @@ impl Engine { } let fn_ptr = arg_value.cast::(); - curry.extend(fn_ptr.curry().iter().cloned()); + + #[cfg(not(feature = "no_function"))] + let is_anon = fn_ptr.is_anonymous(); + #[cfg(feature = "no_function")] + let is_anon = false; + + let (fn_name, fn_curry) = fn_ptr.take_data(); + curry.extend(fn_curry.into_iter()); // Redirect function name - redirected = fn_ptr.take_data().0; + redirected = fn_name; name = &redirected; // Shift the arguments @@ -1035,7 +1024,8 @@ impl Engine { // Recalculate hash let args_len = total_args + curry.len(); - hashes = if hashes.is_native_only() { + + hashes = if !is_anon && !is_valid_function_name(name) { FnCallHashes::from_native(calc_fn_hash(None, name, args_len)) } else { calc_fn_hash(None, name, args_len).into() @@ -1045,7 +1035,7 @@ impl Engine { KEYWORD_FN_PTR if total_args == 1 => { let arg = first_arg.unwrap(); let (arg_value, arg_pos) = - self.get_arg_value(scope, global, caches, lib, this_ptr, arg, level)?; + self.get_arg_value(global, caches, lib, level, scope, this_ptr, arg)?; // Fn - only in function call style return arg_value @@ -1060,7 +1050,7 @@ impl Engine { KEYWORD_FN_PTR_CURRY if total_args > 1 => { let first = first_arg.unwrap(); let (arg_value, arg_pos) = - self.get_arg_value(scope, global, caches, lib, this_ptr, first, level)?; + self.get_arg_value(global, caches, lib, level, scope, this_ptr, first)?; if !arg_value.is::() { let typ = self.map_type_name(arg_value.type_name()); @@ -1072,7 +1062,7 @@ impl Engine { // Append the new curried arguments to the existing list. let fn_curry = a_expr.iter().try_fold(fn_curry, |mut curried, expr| { let (value, ..) = - self.get_arg_value(scope, global, caches, lib, this_ptr, expr, level)?; + self.get_arg_value(global, caches, lib, level, scope, this_ptr, expr)?; curried.push(value); Ok::<_, RhaiError>(curried) })?; @@ -1085,7 +1075,7 @@ impl Engine { crate::engine::KEYWORD_IS_SHARED if total_args == 1 => { let arg = first_arg.unwrap(); let (arg_value, ..) = - self.get_arg_value(scope, global, caches, lib, this_ptr, arg, level)?; + self.get_arg_value(global, caches, lib, level, scope, this_ptr, arg)?; return Ok(arg_value.is_shared().into()); } @@ -1094,14 +1084,14 @@ impl Engine { crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => { let first = first_arg.unwrap(); let (arg_value, arg_pos) = - self.get_arg_value(scope, global, caches, lib, this_ptr, first, level)?; + self.get_arg_value(global, caches, lib, level, scope, this_ptr, first)?; let fn_name = arg_value .into_immutable_string() .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; let (arg_value, arg_pos) = - self.get_arg_value(scope, global, caches, lib, this_ptr, &a_expr[0], level)?; + self.get_arg_value(global, caches, lib, level, scope, this_ptr, &a_expr[0])?; let num_params = arg_value .as_int() @@ -1120,7 +1110,7 @@ impl Engine { KEYWORD_IS_DEF_VAR if total_args == 1 => { let arg = first_arg.unwrap(); let (arg_value, arg_pos) = - self.get_arg_value(scope, global, caches, lib, this_ptr, arg, level)?; + self.get_arg_value(global, caches, lib, level, scope, this_ptr, arg)?; let var_name = arg_value .into_immutable_string() .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; @@ -1135,12 +1125,12 @@ impl Engine { let orig_imports_len = global.num_imports(); let arg = first_arg.unwrap(); let (arg_value, pos) = - self.get_arg_value(scope, global, caches, lib, this_ptr, arg, level)?; + self.get_arg_value(global, caches, lib, level, scope, this_ptr, arg)?; let s = &arg_value .into_immutable_string() .map_err(|typ| self.make_type_mismatch_err::(typ, pos))?; let result = - self.eval_script_expr_in_place(scope, global, caches, lib, s, pos, level + 1); + self.eval_script_expr_in_place(global, caches, lib, level + 1, scope, s, pos); // IMPORTANT! If the eval defines new variables in the current scope, // all variable offsets from this point on will be mis-aligned. @@ -1182,7 +1172,7 @@ impl Engine { .copied() .chain(a_expr.iter()) .try_for_each(|expr| { - self.get_arg_value(scope, global, caches, lib, this_ptr, expr, level) + self.get_arg_value(global, caches, lib, level, scope, this_ptr, expr) .map(|(value, ..)| arg_values.push(value.flatten())) })?; args.extend(curry.iter_mut()); @@ -1193,8 +1183,8 @@ impl Engine { return self .exec_fn_call( - scope, global, caches, lib, name, native, hashes, &mut args, is_ref_mut, false, - pos, level, + global, caches, lib, level, scope, name, op_token, hashes, &mut args, + is_ref_mut, false, pos, ) .map(|(v, ..)| v); } @@ -1210,16 +1200,16 @@ impl Engine { let first_expr = first_arg.unwrap(); #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, first_expr, level)?; + self.run_debugger(global, caches, lib, level, scope, this_ptr, first_expr)?; // func(x, ...) -> x.func(...) a_expr.iter().try_for_each(|expr| { - self.get_arg_value(scope, global, caches, lib, this_ptr, expr, level) + self.get_arg_value(global, caches, lib, level, scope, this_ptr, expr) .map(|(value, ..)| arg_values.push(value.flatten())) })?; let (mut target, _pos) = - self.search_namespace(scope, global, lib, this_ptr, first_expr, level)?; + self.search_namespace(global, caches, lib, level, scope, this_ptr, first_expr)?; if target.is_read_only() { target = target.into_owned(); @@ -1246,7 +1236,7 @@ impl Engine { .into_iter() .chain(a_expr.iter()) .try_for_each(|expr| { - self.get_arg_value(scope, global, caches, lib, this_ptr, expr, level) + self.get_arg_value(global, caches, lib, level, scope, this_ptr, expr) .map(|(value, ..)| arg_values.push(value.flatten())) })?; args.extend(curry.iter_mut()); @@ -1256,8 +1246,8 @@ impl Engine { } self.exec_fn_call( - None, global, caches, lib, name, native, hashes, &mut args, is_ref_mut, false, pos, - level, + global, caches, lib, level, None, name, op_token, hashes, &mut args, is_ref_mut, false, + pos, ) .map(|(v, ..)| v) } @@ -1266,17 +1256,17 @@ impl Engine { #[cfg(not(feature = "no_module"))] pub(crate) fn make_qualified_function_call( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, lib: &[&Module], + level: usize, + scope: &mut Scope, this_ptr: &mut Option<&mut Dynamic>, namespace: &crate::ast::Namespace, fn_name: &str, args_expr: &[Expr], hash: u64, pos: Position, - level: usize, ) -> RhaiResult { let mut arg_values = FnArgsVec::with_capacity(args_expr.len()); let mut args = FnArgsVec::with_capacity(args_expr.len()); @@ -1290,20 +1280,20 @@ impl Engine { // and avoid cloning the value if !args_expr.is_empty() && args_expr[0].is_variable_access(true) { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, &args_expr[0], level)?; + self.run_debugger(global, caches, lib, level, scope, this_ptr, &args_expr[0])?; // func(x, ...) -> x.func(...) arg_values.push(Dynamic::UNIT); args_expr.iter().skip(1).try_for_each(|expr| { - self.get_arg_value(scope, global, caches, lib, this_ptr, expr, level) + self.get_arg_value(global, caches, lib, level, scope, this_ptr, expr) .map(|(value, ..)| arg_values.push(value.flatten())) })?; // Get target reference to first argument let first_arg = &args_expr[0]; let (target, _pos) = - self.search_scope_only(scope, global, lib, this_ptr, first_arg, level)?; + self.search_scope_only(global, caches, lib, level, scope, this_ptr, first_arg)?; self.track_operation(global, _pos)?; @@ -1326,7 +1316,7 @@ impl Engine { } else { // func(..., ...) or func(mod::x, ...) args_expr.iter().try_for_each(|expr| { - self.get_arg_value(scope, global, caches, lib, this_ptr, expr, level) + self.get_arg_value(global, caches, lib, level, scope, this_ptr, expr) .map(|(value, ..)| arg_values.push(value.flatten())) })?; args.extend(arg_values.iter_mut()); @@ -1343,9 +1333,7 @@ impl Engine { // Then search native Rust functions None => { self.track_operation(global, pos)?; - let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id())); - let hash_qualified_fn = combine_hashes(hash, hash_params); - + let hash_qualified_fn = calc_fn_hash_full(hash, args.iter().map(|a| a.type_id())); module.get_qualified_fn(hash_qualified_fn) } r => r, @@ -1363,16 +1351,18 @@ impl Engine { // Try all permutations with `Dynamic` wildcards while bitmask < max_bitmask { - let hash_params = calc_fn_params_hash(args.iter().enumerate().map(|(i, a)| { - let mask = 1usize << (num_args - i - 1); - if bitmask & mask == 0 { - a.type_id() - } else { - // Replace with `Dynamic` - TypeId::of::() - } - })); - let hash_qualified_fn = combine_hashes(hash, hash_params); + let hash_qualified_fn = calc_fn_hash_full( + hash, + args.iter().enumerate().map(|(i, a)| { + let mask = 1usize << (num_args - i - 1); + if bitmask & mask == 0 { + a.type_id() + } else { + // Replace with `Dynamic` + TypeId::of::() + } + }), + ); self.track_operation(global, pos)?; @@ -1403,7 +1393,7 @@ impl Engine { let orig_source = mem::replace(&mut global.source, module.id_raw().cloned()); let result = self.call_script_fn( - new_scope, global, caches, lib, &mut None, fn_def, &mut args, true, pos, level, + global, caches, lib, level, new_scope, &mut None, fn_def, &mut args, true, pos, ); global.source = orig_source; @@ -1431,24 +1421,32 @@ impl Engine { Some(f) => unreachable!("unknown function type: {:?}", f), - None => Err(ERR::ErrorFunctionNotFound( - gen_qualified_fn_call_signature(self, namespace, fn_name, &args), - pos, - ) - .into()), + None => { + let sig = if namespace.is_empty() { + self.gen_fn_call_signature(fn_name, &args) + } else { + format!( + "{namespace}{}{}", + crate::tokenizer::Token::DoubleColon.literal_syntax(), + self.gen_fn_call_signature(fn_name, &args) + ) + }; + + Err(ERR::ErrorFunctionNotFound(sig, pos).into()) + } } } /// Evaluate a text script in place - used primarily for 'eval'. pub(crate) fn eval_script_expr_in_place( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, lib: &[&Module], + level: usize, + scope: &mut Scope, script: &str, _pos: Position, - level: usize, ) -> RhaiResult { self.track_operation(global, _pos)?; @@ -1481,6 +1479,83 @@ impl Engine { } // Evaluate the AST - self.eval_global_statements(scope, global, caches, statements, lib, level) + self.eval_global_statements(global, caches, lib, level, scope, statements) + } + + /// Evaluate a function call expression. + pub(crate) fn eval_fn_call_expr( + &self, + global: &mut GlobalRuntimeState, + caches: &mut Caches, + lib: &[&Module], + level: usize, + scope: &mut Scope, + this_ptr: &mut Option<&mut Dynamic>, + expr: &FnCallExpr, + pos: Position, + ) -> RhaiResult { + let FnCallExpr { + #[cfg(not(feature = "no_module"))] + namespace, + name, + hashes, + args, + op_token, + capture_parent_scope: capture, + .. + } = expr; + + let op_token = op_token.as_ref(); + + // Short-circuit native binary operator call if under Fast Operators mode + if op_token.is_some() && self.fast_operators() && args.len() == 2 { + let mut lhs = self + .get_arg_value(global, caches, lib, level, scope, this_ptr, &args[0])? + .0 + .flatten(); + + let mut rhs = self + .get_arg_value(global, caches, lib, level, scope, this_ptr, &args[1])? + .0 + .flatten(); + + let operands = &mut [&mut lhs, &mut rhs]; + + if let Some(func) = + get_builtin_binary_op_fn(op_token.as_ref().unwrap(), operands[0], operands[1]) + { + // Built-in found + let context = (self, name.as_str(), None, &*global, lib, pos, level + 1).into(); + return func(context, operands); + } + + return self + .exec_fn_call( + global, caches, lib, level, None, name, op_token, *hashes, operands, false, + false, pos, + ) + .map(|(v, ..)| v); + } + + #[cfg(not(feature = "no_module"))] + if !namespace.is_empty() { + // Qualified function call + let hash = hashes.native(); + + return self.make_qualified_function_call( + global, caches, lib, level, scope, this_ptr, namespace, name, args, hash, pos, + ); + } + + // Normal function call + let (first_arg, args) = args.split_first().map_or_else( + || (None, args.as_ref()), + |(first, rest)| (Some(first), rest), + ); + + self.make_function_call( + global, caches, lib, level, scope, this_ptr, name, op_token, first_arg, args, *hashes, + *capture, pos, + ) } } diff --git a/src/func/hashing.rs b/src/func/hashing.rs index 855dcd3d..2b457c4d 100644 --- a/src/func/hashing.rs +++ b/src/func/hashing.rs @@ -156,7 +156,7 @@ pub fn calc_fn_hash<'a>( } } -/// Calculate a non-zero [`u64`] hash key from a list of parameter types. +/// Calculate a non-zero [`u64`] hash key from a base [`u64`] hash key and a list of parameter types. /// /// Parameter types are passed in via [`TypeId`] values from an iterator. /// @@ -165,10 +165,12 @@ pub fn calc_fn_hash<'a>( /// If the hash happens to be zero, it is mapped to `DEFAULT_HASH`. #[inline] #[must_use] -pub fn calc_fn_params_hash( +pub fn calc_fn_hash_full( + base: u64, params: impl IntoIterator>, ) -> u64 { let s = &mut get_hasher(); + base.hash(s); let iter = params.into_iter(); let len = iter.len(); iter.for_each(|t| { @@ -181,17 +183,3 @@ pub fn calc_fn_params_hash( r => r, } } - -/// Combine two [`u64`] hashes by taking the XOR of them. -/// -/// # Zeros -/// -/// If the hash happens to be zero, it is mapped to `DEFAULT_HASH`. -#[inline(always)] -#[must_use] -pub const fn combine_hashes(a: u64, b: u64) -> u64 { - match a ^ b { - 0 => ALT_ZERO_HASH, - r => r, - } -} diff --git a/src/func/mod.rs b/src/func/mod.rs index 8298a63e..6120764d 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -13,15 +13,15 @@ pub mod script; pub use args::FuncArgs; pub use builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn}; -#[cfg(not(feature = "no_module"))] -pub use call::gen_qualified_fn_call_signature; -pub use call::{gen_fn_call_signature, FnCallArgs}; +#[cfg(not(feature = "no_closure"))] +pub use call::ensure_no_data_race; +#[cfg(not(feature = "no_function"))] +pub use call::is_anonymous_fn; +pub use call::FnCallArgs; pub use callable_function::CallableFunction; #[cfg(not(feature = "no_function"))] pub use func::Func; -pub use hashing::{ - calc_fn_hash, calc_fn_params_hash, calc_var_hash, combine_hashes, get_hasher, StraightHashMap, -}; +pub use hashing::{calc_fn_hash, calc_fn_hash_full, calc_var_hash, get_hasher, StraightHashMap}; pub use native::{ locked_read, locked_write, shared_get_mut, shared_make_mut, shared_take, shared_take_or_clone, shared_try_take, FnAny, FnPlugin, IteratorFn, Locked, NativeCallContext, SendSync, Shared, diff --git a/src/func/native.rs b/src/func/native.rs index 6afda4da..912f9d79 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -4,7 +4,7 @@ use super::call::FnCallArgs; use crate::ast::FnCallHashes; use crate::eval::{Caches, GlobalRuntimeState}; use crate::plugin::PluginFunction; -use crate::tokenizer::{Token, TokenizeState}; +use crate::tokenizer::{is_valid_function_name, Token, TokenizeState}; use crate::types::dynamic::Variant; use crate::{ calc_fn_hash, Dynamic, Engine, EvalContext, FuncArgs, Module, Position, RhaiResult, @@ -329,7 +329,12 @@ impl<'a> NativeCallContext<'a> { is_method_call: bool, args: &mut [&mut Dynamic], ) -> RhaiResult { - self._call_fn_raw(fn_name, false, is_ref_mut, is_method_call, args) + let name = fn_name.as_ref(); + let native_only = !is_valid_function_name(name); + #[cfg(not(feature = "no_function"))] + let native_only = native_only && !crate::parser::is_anonymous_fn(name); + + self._call_fn_raw(fn_name, native_only, is_ref_mut, is_method_call, args) } /// Call a registered native Rust function inside the call context. /// @@ -377,22 +382,24 @@ impl<'a> NativeCallContext<'a> { let caches = &mut Caches::new(); let fn_name = fn_name.as_ref(); + let op_token = Token::lookup_symbol_from_syntax(fn_name); + let op_token = op_token.as_ref(); let args_len = args.len(); if native_only { return self .engine() - .call_native_fn( + .exec_native_fn_call( global, caches, self.lib, + self.level + 1, fn_name, + op_token, calc_fn_hash(None, fn_name, args_len), args, is_ref_mut, - false, Position::NONE, - self.level + 1, ) .map(|(r, ..)| r); } @@ -411,18 +418,18 @@ impl<'a> NativeCallContext<'a> { self.engine() .exec_fn_call( - None, global, caches, self.lib, + self.level + 1, + None, fn_name, - false, + op_token, hash, args, is_ref_mut, is_method_call, Position::NONE, - self.level + 1, ) .map(|(r, ..)| r) } diff --git a/src/func/script.rs b/src/func/script.rs index 476343af..b80d74ae 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -24,16 +24,16 @@ impl Engine { /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! pub(crate) fn call_script_fn( &self, - scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, lib: &[&Module], + level: usize, + scope: &mut Scope, this_ptr: &mut Option<&mut Dynamic>, fn_def: &ScriptFnDef, args: &mut FnCallArgs, rewind_scope: bool, pos: Position, - level: usize, ) -> RhaiResult { #[cold] #[inline(never)] @@ -140,20 +140,20 @@ impl Engine { #[cfg(feature = "debugging")] { let node = crate::ast::Stmt::Noop(fn_def.body.position()); - self.run_debugger(scope, global, lib, this_ptr, &node, level)?; + self.run_debugger(global, caches, lib, level, scope, this_ptr, &node)?; } // Evaluate the function let mut _result = self .eval_stmt_block( - scope, global, caches, lib, + level, + scope, this_ptr, &fn_def.body, rewind_scope, - level, ) .or_else(|err| match *err { // Convert return statement to return value @@ -190,7 +190,9 @@ impl Engine { Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r), Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err), }; - match self.run_debugger_raw(scope, global, lib, this_ptr, node, event, level) { + match self + .run_debugger_raw(global, caches, lib, level, scope, this_ptr, node, event) + { Ok(_) => (), Err(err) => _result = Err(err), } diff --git a/src/lib.rs b/src/lib.rs index 8879012f..12f36850 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -238,7 +238,7 @@ pub use func::Shared; /// Alias to [`RefCell`][std::cell::RefCell] or [`RwLock`][std::sync::RwLock] depending on the `sync` feature flag. pub use func::Locked; -use func::{calc_fn_hash, calc_fn_params_hash, calc_var_hash, combine_hashes}; +use func::{calc_fn_hash, calc_fn_hash_full, calc_var_hash}; pub use rhai_codegen::*; diff --git a/src/module/mod.rs b/src/module/mod.rs index 59ead075..6cb59198 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -9,8 +9,8 @@ use crate::func::{ }; use crate::types::{dynamic::Variant, BloomFilterU64, CustomTypesCollection}; use crate::{ - calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Identifier, ImmutableString, - NativeCallContext, RhaiResultOf, Shared, SmartString, StaticVec, + calc_fn_hash, calc_fn_hash_full, Dynamic, Identifier, ImmutableString, NativeCallContext, + RhaiResultOf, Shared, SmartString, StaticVec, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -150,9 +150,10 @@ pub fn calc_native_fn_hash<'a>( 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().copied()); - combine_hashes(hash_script, hash_params) + calc_fn_hash_full( + calc_fn_hash(modules, fn_name, params.len()), + params.iter().copied(), + ) } /// A module which may contain variables, sub-modules, external Rust functions, @@ -1027,8 +1028,7 @@ impl Module { let name = name.as_ref(); let hash_script = calc_fn_hash(None, name, param_types.len()); - let hash_params = calc_fn_params_hash(param_types.iter().copied()); - let hash_fn = combine_hashes(hash_script, hash_params); + let hash_fn = calc_fn_hash_full(hash_script, param_types.iter().copied()); if is_dynamic { self.dynamic_functions_filter.mark(hash_script); @@ -1984,7 +1984,9 @@ impl Module { let orig_constants = std::mem::take(&mut global.constants); // Run the script - let result = engine.eval_ast_with_scope_raw(&mut scope, global, ast, 0); + let caches = &mut crate::eval::Caches::new(); + + let result = engine.eval_ast_with_scope_raw(global, caches, 0, &mut scope, ast); // Create new module let mut module = Module::new(); diff --git a/src/optimizer.rs b/src/optimizer.rs index 08abb8f2..4428c6da 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -11,8 +11,8 @@ use crate::func::hashing::get_hasher; use crate::tokenizer::Token; use crate::types::dynamic::AccessMode; use crate::{ - calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnPtr, Identifier, - ImmutableString, Position, Scope, StaticVec, AST, INT, + calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnPtr, Identifier, ImmutableString, Position, + Scope, StaticVec, AST, INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -139,6 +139,7 @@ impl<'a> OptimizerState<'a> { pub fn call_fn_with_constant_arguments( &mut self, fn_name: &str, + op_token: Option<&Token>, arg_values: &mut [Dynamic], ) -> Option { #[cfg(not(feature = "no_function"))] @@ -147,17 +148,17 @@ impl<'a> OptimizerState<'a> { let lib = &[]; self.engine - .call_native_fn( + .exec_native_fn_call( &mut self.global, &mut self.caches, lib, + 0, fn_name, + op_token, calc_fn_hash(None, fn_name, arg_values.len()), &mut arg_values.iter_mut().collect::>(), false, - false, Position::NONE, - 0, ) .ok() .map(|(v, ..)| v) @@ -170,8 +171,7 @@ fn has_native_fn_override( hash_script: u64, arg_types: impl AsRef<[TypeId]>, ) -> bool { - let hash_params = calc_fn_params_hash(arg_types.as_ref().iter().copied()); - let hash = combine_hashes(hash_script, hash_params); + let hash = calc_fn_hash_full(hash_script, arg_types.as_ref().iter().copied()); // First check the global namespace and packages, but skip modules that are standard because // they should never conflict with system functions. @@ -438,15 +438,15 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b if !x.0.is_op_assignment() && x.1.lhs.is_variable_access(true) && matches!(&x.1.rhs, Expr::FnCall(x2, ..) - if Token::lookup_from_syntax(&x2.name).map_or(false, |t| t.has_op_assignment()) + if Token::lookup_symbol_from_syntax(&x2.name).map_or(false, |t| t.has_op_assignment()) && x2.args.len() == 2 && x2.args[0].get_variable_name(true) == x.1.lhs.get_variable_name(true) ) => { match x.1.rhs { - Expr::FnCall(ref mut x2, ..) => { + Expr::FnCall(ref mut x2, pos) => { state.set_dirty(); - x.0 = OpAssignment::new_op_assignment_from_base(&x2.name, x2.pos); + x.0 = OpAssignment::new_op_assignment_from_base(&x2.name, pos); x.1.rhs = mem::take(&mut x2.args[1]); } ref expr => unreachable!("Expr::FnCall expected but gets {:?}", expr), @@ -1098,7 +1098,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { && state.optimization_level == OptimizationLevel::Simple // simple optimizations && x.args.len() == 1 && x.name == KEYWORD_FN_PTR - && x.args[0].is_constant() + && x.constant_args() => { let fn_name = match x.args[0] { Expr::StringConstant(ref s, ..) => s.clone().into(), @@ -1122,8 +1122,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { Expr::FnCall(x, pos) if !x.is_qualified() // Non-qualified && state.optimization_level == OptimizationLevel::Simple // simple optimizations - && x.args.iter().all(Expr::is_constant) // all arguments are constants - //&& !is_valid_identifier(x.chars()) // cannot be scripted + && x.constant_args() // all arguments are constants => { let arg_values = &mut x.args.iter().map(|e| e.get_literal_value().unwrap()).collect::>(); let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect(); @@ -1142,8 +1141,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { return; } // Overloaded operators can override built-in. - _ if x.args.len() == 2 && x.operator_token.is_some() && (state.engine.fast_operators() || !has_native_fn_override(state.engine, x.hashes.native, &arg_types)) => { - if let Some(result) = get_builtin_binary_op_fn(x.operator_token.as_ref().unwrap(), &arg_values[0], &arg_values[1]) + _ if x.args.len() == 2 && x.op_token.is_some() && (state.engine.fast_operators() || !has_native_fn_override(state.engine, x.hashes.native(), &arg_types)) => { + if let Some(result) = get_builtin_binary_op_fn(x.op_token.as_ref().unwrap(), &arg_values[0], &arg_values[1]) .and_then(|f| { #[cfg(not(feature = "no_function"))] let lib = state.lib; @@ -1186,11 +1185,11 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { Expr::FnCall(x, pos) if !x.is_qualified() // non-qualified && state.optimization_level == OptimizationLevel::Full // full optimizations - && x.args.iter().all(Expr::is_constant) // all arguments are constants + && x.constant_args() // all arguments are constants => { // First search for script-defined functions (can override built-in) #[cfg(not(feature = "no_function"))] - let has_script_fn = state.lib.iter().find_map(|&m| m.get_script_fn(&x.name, x.args.len())).is_some(); + let has_script_fn = !x.hashes.is_native_only() && state.lib.iter().find_map(|&m| m.get_script_fn(&x.name, x.args.len())).is_some(); #[cfg(feature = "no_function")] let has_script_fn = false; @@ -1201,7 +1200,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { KEYWORD_TYPE_OF if arg_values.len() == 1 => Some(state.engine.map_type_name(arg_values[0].type_name()).into()), #[cfg(not(feature = "no_closure"))] crate::engine::KEYWORD_IS_SHARED if arg_values.len() == 1 => Some(Dynamic::FALSE), - _ => state.call_fn_with_constant_arguments(&x.name, arg_values) + _ => state.call_fn_with_constant_arguments(&x.name, x.op_token.as_ref(), arg_values) }; if let Some(result) = result { diff --git a/src/parser.rs b/src/parser.rs index c2e02aa7..f0c3843f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -174,7 +174,7 @@ impl<'e> ParseState<'e> { /// /// # Return value: `(index, is_func_name)` /// - /// * `index`: `None` when the variable name is not found in the `stack`, + /// * `index`: [`None`] when the variable name is not found in the `stack`, /// otherwise the index value. /// /// * `is_func_name`: `true` if the variable is actually the name of a function @@ -223,7 +223,7 @@ impl<'e> ParseState<'e> { /// Returns the offset to be deducted from `Stack::len`, /// i.e. the top element of the [`ParseState`] is offset 1. /// - /// Returns `None` when the variable name is not found in the [`ParseState`]. + /// Returns [`None`] when the variable name is not found in the [`ParseState`]. /// /// # Panics /// @@ -469,7 +469,7 @@ fn parse_var_name(input: &mut TokenStream) -> ParseResult<(SmartString, Position // Variable name (Token::Identifier(s), pos) => Ok((*s, pos)), // Reserved keyword - (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { + (Token::Reserved(s), pos) if is_valid_identifier(s.as_str()) => { Err(PERR::Reserved(s.to_string()).into_err(pos)) } // Bad identifier @@ -486,7 +486,7 @@ fn parse_symbol(input: &mut TokenStream) -> ParseResult<(SmartString, Position)> // Symbol (token, pos) if token.is_standard_symbol() => Ok((token.literal_syntax().into(), pos)), // Reserved symbol - (Token::Reserved(s), pos) if !is_valid_identifier(s.chars()) => Ok((*s, pos)), + (Token::Reserved(s), pos) if !is_valid_identifier(s.as_str()) => Ok((*s, pos)), // Bad symbol (Token::LexError(err), pos) => Err(err.into_err(pos)), // Not a symbol @@ -599,9 +599,7 @@ impl Engine { #[cfg(feature = "no_module")] let hash = calc_fn_hash(None, &id, 0); - let is_valid_function_name = is_valid_function_name(&id); - - let hashes = if is_valid_function_name { + let hashes = if is_valid_function_name(&id) { hash.into() } else { FnCallHashes::from_native(hash) @@ -612,14 +610,11 @@ impl Engine { return Ok(FnCallExpr { name: state.get_interned_string(id), capture_parent_scope, - operator_token: None, - #[cfg(not(feature = "no_function"))] - can_be_script: is_valid_function_name, + op_token: None, #[cfg(not(feature = "no_module"))] namespace, hashes, args, - pos: settings.pos, } .into_fn_call_expr(settings.pos)); } @@ -671,9 +666,7 @@ impl Engine { #[cfg(feature = "no_module")] let hash = calc_fn_hash(None, &id, args.len()); - let is_valid_function_name = is_valid_function_name(&id); - - let hashes = if is_valid_function_name { + let hashes = if is_valid_function_name(&id) { hash.into() } else { FnCallHashes::from_native(hash) @@ -684,14 +677,11 @@ impl Engine { return Ok(FnCallExpr { name: state.get_interned_string(id), capture_parent_scope, - operator_token: None, - #[cfg(not(feature = "no_function"))] - can_be_script: is_valid_function_name, + op_token: None, #[cfg(not(feature = "no_module"))] namespace, hashes, args, - pos: settings.pos, } .into_fn_call_expr(settings.pos)); } @@ -1012,7 +1002,7 @@ impl Engine { (Token::InterpolatedString(..), pos) => { return Err(PERR::PropertyExpected.into_err(pos)) } - (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { + (Token::Reserved(s), pos) if is_valid_identifier(s.as_str()) => { return Err(PERR::Reserved(s.to_string()).into_err(pos)); } (Token::LexError(err), pos) => return Err(err.into_err(pos)), @@ -1941,11 +1931,8 @@ impl Engine { name: state.get_interned_string("-"), hashes: FnCallHashes::from_native(calc_fn_hash(None, "-", 1)), args, - pos, - operator_token: Some(token), + op_token: Some(token), capture_parent_scope: false, - #[cfg(not(feature = "no_function"))] - can_be_script: false, } .into_fn_call_expr(pos)) } @@ -1973,11 +1960,8 @@ impl Engine { name: state.get_interned_string("+"), hashes: FnCallHashes::from_native(calc_fn_hash(None, "+", 1)), args, - pos, - operator_token: Some(token), + op_token: Some(token), capture_parent_scope: false, - #[cfg(not(feature = "no_function"))] - can_be_script: false, } .into_fn_call_expr(pos)) } @@ -1998,11 +1982,8 @@ impl Engine { name: state.get_interned_string("!"), hashes: FnCallHashes::from_native(calc_fn_hash(None, "!", 1)), args, - pos, - operator_token: Some(token), + op_token: Some(token), capture_parent_scope: false, - #[cfg(not(feature = "no_function"))] - can_be_script: false, } .into_fn_call_expr(pos)) } @@ -2217,11 +2198,15 @@ impl Engine { // lhs.func(...) (lhs, Expr::FnCall(mut func, func_pos)) => { // Recalculate hash - func.hashes = FnCallHashes::from_all( - #[cfg(not(feature = "no_function"))] - calc_fn_hash(None, &func.name, func.args.len()), - calc_fn_hash(None, &func.name, func.args.len() + 1), - ); + func.hashes = if is_valid_function_name(&func.name) { + FnCallHashes::from_all( + #[cfg(not(feature = "no_function"))] + calc_fn_hash(None, &func.name, func.args.len()), + calc_fn_hash(None, &func.name, func.args.len() + 1), + ) + } else { + FnCallHashes::from_native(calc_fn_hash(None, &func.name, func.args.len() + 1)) + }; let rhs = Expr::MethodCall(func, func_pos); Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos)) @@ -2263,11 +2248,19 @@ impl Engine { // lhs.func().dot_rhs or lhs.func()[idx_rhs] Expr::FnCall(mut func, func_pos) => { // Recalculate hash - func.hashes = FnCallHashes::from_all( - #[cfg(not(feature = "no_function"))] - calc_fn_hash(None, &func.name, func.args.len()), - calc_fn_hash(None, &func.name, func.args.len() + 1), - ); + func.hashes = if is_valid_function_name(&func.name) { + FnCallHashes::from_all( + #[cfg(not(feature = "no_function"))] + calc_fn_hash(None, &func.name, func.args.len()), + calc_fn_hash(None, &func.name, func.args.len() + 1), + ) + } else { + FnCallHashes::from_native(calc_fn_hash( + None, + &func.name, + func.args.len() + 1, + )) + }; let new_lhs = BinaryExpr { lhs: Expr::MethodCall(func, func_pos), @@ -2322,7 +2315,7 @@ impl Engine { .get(&**c) .copied() .ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*current_pos))?, - Token::Reserved(c) if !is_valid_identifier(c.chars()) => { + Token::Reserved(c) if !is_valid_identifier(c) => { return Err(PERR::UnknownOperator(c.to_string()).into_err(*current_pos)) } _ => current_op.precedence(), @@ -2347,7 +2340,7 @@ impl Engine { .get(&**c) .copied() .ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*next_pos))?, - Token::Reserved(c) if !is_valid_identifier(c.chars()) => { + Token::Reserved(c) if !is_valid_identifier(c) => { return Err(PERR::UnknownOperator(c.to_string()).into_err(*next_pos)) } _ => next_op.precedence(), @@ -2371,34 +2364,31 @@ impl Engine { let op = op_token.syntax(); let hash = calc_fn_hash(None, &op, 2); - let is_function = is_valid_function_name(&op); - let operator_token = if is_function { + let is_valid_script_function = is_valid_function_name(&op); + let operator_token = if is_valid_script_function { None } else { Some(op_token.clone()) }; - let op_base = FnCallExpr { - #[cfg(not(feature = "no_module"))] - namespace: Default::default(), - name: state.get_interned_string(op.as_ref()), - hashes: FnCallHashes::from_native(hash), - args: StaticVec::new_const(), - pos, - operator_token, - capture_parent_scope: false, - #[cfg(not(feature = "no_function"))] - can_be_script: is_function, - }; - let mut args = StaticVec::new_const(); args.push(root); args.push(rhs); args.shrink_to_fit(); + let mut op_base = FnCallExpr { + #[cfg(not(feature = "no_module"))] + namespace: Default::default(), + name: state.get_interned_string(op.as_ref()), + hashes: FnCallHashes::from_native(hash), + args, + op_token: operator_token, + capture_parent_scope: false, + }; + root = match op_token { // '!=' defaults to true when passed invalid operands - Token::NotEqualsTo => FnCallExpr { args, ..op_base }.into_fn_call_expr(pos), + Token::NotEqualsTo => op_base.into_fn_call_expr(pos), // Comparison operators default to false when passed invalid operands Token::EqualsTo @@ -2406,61 +2396,36 @@ impl Engine { | Token::LessThanEqualsTo | Token::GreaterThan | Token::GreaterThanEqualsTo => { - let pos = args[0].start_position(); - FnCallExpr { args, ..op_base }.into_fn_call_expr(pos) + let pos = op_base.args[0].start_position(); + op_base.into_fn_call_expr(pos) } Token::Or => { - let rhs = args.pop().unwrap(); - let current_lhs = args.pop().unwrap(); - Expr::Or( - BinaryExpr { - lhs: current_lhs.ensure_bool_expr()?, - rhs: rhs.ensure_bool_expr()?, - } - .into(), - pos, - ) + let rhs = op_base.args.pop().unwrap().ensure_bool_expr()?; + let lhs = op_base.args.pop().unwrap().ensure_bool_expr()?; + Expr::Or(BinaryExpr { lhs: lhs, rhs: rhs }.into(), pos) } Token::And => { - let rhs = args.pop().unwrap(); - let current_lhs = args.pop().unwrap(); - Expr::And( - BinaryExpr { - lhs: current_lhs.ensure_bool_expr()?, - rhs: rhs.ensure_bool_expr()?, - } - .into(), - pos, - ) + let rhs = op_base.args.pop().unwrap().ensure_bool_expr()?; + let lhs = op_base.args.pop().unwrap().ensure_bool_expr()?; + Expr::And(BinaryExpr { lhs: lhs, rhs: rhs }.into(), pos) } Token::DoubleQuestion => { - let rhs = args.pop().unwrap(); - let current_lhs = args.pop().unwrap(); - Expr::Coalesce( - BinaryExpr { - lhs: current_lhs, - rhs, - } - .into(), - pos, - ) + let rhs = op_base.args.pop().unwrap(); + let lhs = op_base.args.pop().unwrap(); + Expr::Coalesce(BinaryExpr { lhs, rhs }.into(), pos) } Token::In => { // Swap the arguments - let current_lhs = args.remove(0); - let pos = current_lhs.start_position(); - args.push(current_lhs); - args.shrink_to_fit(); + let lhs = op_base.args.remove(0); + let pos = lhs.start_position(); + op_base.args.push(lhs); + op_base.args.shrink_to_fit(); // Convert into a call to `contains` - FnCallExpr { - hashes: calc_fn_hash(None, OP_CONTAINS, 2).into(), - args, - name: state.get_interned_string(OP_CONTAINS), - ..op_base - } - .into_fn_call_expr(pos) + op_base.hashes = calc_fn_hash(None, OP_CONTAINS, 2).into(); + op_base.name = state.get_interned_string(OP_CONTAINS); + op_base.into_fn_call_expr(pos) } #[cfg(not(feature = "no_custom_syntax"))] @@ -2470,24 +2435,17 @@ impl Engine { .get(s.as_str()) .map_or(false, Option::is_some) => { - let hash = calc_fn_hash(None, &s, 2); - let pos = args[0].start_position(); - - FnCallExpr { - hashes: if is_function { - hash.into() - } else { - FnCallHashes::from_native(hash) - }, - args, - ..op_base - } - .into_fn_call_expr(pos) + op_base.hashes = if is_valid_script_function { + calc_fn_hash(None, &s, 2).into() + } else { + FnCallHashes::from_native(calc_fn_hash(None, &s, 2)) + }; + op_base.into_fn_call_expr(pos) } _ => { - let pos = args[0].start_position(); - FnCallExpr { args, ..op_base }.into_fn_call_expr(pos) + let pos = op_base.args[0].start_position(); + op_base.into_fn_call_expr(pos) } }; } @@ -2951,12 +2909,12 @@ impl Engine { let mut this_ptr = None; let context = EvalContext::new( self, - &mut state.stack, &mut state.global, None, &[], - &mut this_ptr, level, + &mut state.stack, + &mut this_ptr, ); match filter(false, info, context) { @@ -3729,11 +3687,8 @@ impl Engine { num_externals + 1, )), args, - pos, - operator_token: None, + op_token: None, capture_parent_scope: false, - #[cfg(not(feature = "no_function"))] - can_be_script: false, } .into_fn_call_expr(pos); diff --git a/src/tests.rs b/src/tests.rs index 34186402..8bbef0d4 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -41,7 +41,7 @@ fn check_struct_sizes() { size_of::(), if cfg!(feature = "no_position") { 8 } else { 16 } ); - assert_eq!(size_of::(), 72); + assert_eq!(size_of::(), 64); assert_eq!( size_of::(), if cfg!(feature = "no_position") { diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 6aa0ec43..7dea89d9 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -799,9 +799,9 @@ impl Token { }) } - /// Reverse lookup a token from a piece of syntax. + /// Reverse lookup a symbol token from a piece of syntax. #[must_use] - pub fn lookup_from_syntax(syntax: &str) -> Option { + pub fn lookup_symbol_from_syntax(syntax: &str) -> Option { use Token::*; Some(match syntax { @@ -879,19 +879,11 @@ impl Token { "**" => PowerOf, "**=" => PowerOfAssign, - #[cfg(feature = "no_object")] - "?." => Reserved(Box::new(syntax.into())), - #[cfg(feature = "no_index")] - "?[" => Reserved(Box::new(syntax.into())), - #[cfg(not(feature = "no_function"))] "fn" => Fn, #[cfg(not(feature = "no_function"))] "private" => Private, - #[cfg(feature = "no_function")] - "fn" | "private" => Reserved(Box::new(syntax.into())), - #[cfg(not(feature = "no_module"))] "import" => Import, #[cfg(not(feature = "no_module"))] @@ -899,31 +891,43 @@ impl Token { #[cfg(not(feature = "no_module"))] "as" => As, + _ => return None, + }) + } + + /// Is a piece of syntax a reserved keyword? + #[must_use] + pub fn is_reserved_keyword(syntax: &str) -> bool { + match syntax { + #[cfg(feature = "no_object")] + "?." => true, + #[cfg(feature = "no_index")] + "?[" => true, + #[cfg(feature = "no_function")] + "fn" | "private" => true, #[cfg(feature = "no_module")] - "import" | "export" | "as" => Reserved(Box::new(syntax.into())), + "import" | "export" | "as" => true, // List of reserved operators "===" | "!==" | "->" | "<-" | "?" | ":=" | ":;" | "~" | "!." | "::<" | "(*" | "*)" - | "#" | "#!" | "@" | "$" | "++" | "--" | "..." | "<|" | "|>" => { - Reserved(Box::new(syntax.into())) - } + | "#" | "#!" | "@" | "$" | "++" | "--" | "..." | "<|" | "|>" => true, // List of reserved keywords "public" | "protected" | "super" | "new" | "use" | "module" | "package" | "var" | "static" | "shared" | "with" | "is" | "goto" | "exit" | "match" | "case" | "default" | "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync" - | "async" | "await" | "yield" => Reserved(Box::new(syntax.into())), + | "async" | "await" | "yield" => true, KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS | KEYWORD_IS_DEF_VAR => { - Reserved(Box::new(syntax.into())) + true } #[cfg(not(feature = "no_function"))] - crate::engine::KEYWORD_IS_DEF_FN => Reserved(Box::new(syntax.into())), + crate::engine::KEYWORD_IS_DEF_FN => true, - _ => return None, - }) + _ => false, + } } /// Is this token [`EOF`][Token::EOF]? @@ -1708,11 +1712,11 @@ fn get_next_token_inner( // letter or underscore ... #[cfg(not(feature = "unicode-xid-ident"))] ('a'..='z' | '_' | 'A'..='Z', ..) => { - return Some(get_identifier(stream, pos, start_pos, c)); + return Some(get_token_as_identifier(stream, pos, start_pos, c)); } #[cfg(feature = "unicode-xid-ident")] (ch, ..) if unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' => { - return Some(get_identifier(stream, pos, start_pos, c)); + return Some(get_token_as_identifier(stream, pos, start_pos, c)); } // " - string literal @@ -2179,8 +2183,8 @@ fn get_next_token_inner( Some((Token::EOF, *pos)) } -/// Get the next identifier. -fn get_identifier( +/// Get the next token, parsing it as an identifier. +fn get_token_as_identifier( stream: &mut impl InputStream, pos: &mut Position, start_pos: Position, @@ -2199,13 +2203,13 @@ fn get_identifier( } } - let is_valid_identifier = is_valid_identifier(identifier.chars()); - - if let Some(token) = Token::lookup_from_syntax(&identifier) { + if let Some(token) = Token::lookup_symbol_from_syntax(&identifier) { return (token, start_pos); + } else if Token::is_reserved_keyword(&identifier) { + return (Token::Reserved(Box::new(identifier)), start_pos); } - if !is_valid_identifier { + if !is_valid_identifier(&identifier) { return ( Token::LexError(LERR::MalformedIdentifier(identifier.to_string()).into()), start_pos, @@ -2233,10 +2237,10 @@ pub fn is_keyword_function(name: &str) -> bool { /// _(internals)_ Is a text string a valid identifier? /// Exported under the `internals` feature only. #[must_use] -pub fn is_valid_identifier(name: impl Iterator) -> bool { +pub fn is_valid_identifier(name: &str) -> bool { let mut first_alphabetic = false; - for ch in name { + for ch in name.chars() { match ch { '_' => (), _ if is_id_first_alphabetic(ch) => first_alphabetic = true, @@ -2254,7 +2258,7 @@ pub fn is_valid_identifier(name: impl Iterator) -> bool { #[inline(always)] #[must_use] pub fn is_valid_function_name(name: &str) -> bool { - is_valid_identifier(name.chars()) + is_valid_identifier(name) && !is_keyword_function(name) } /// Is a character valid to start an identifier? @@ -2433,7 +2437,7 @@ impl<'a> Iterator for TokenIterator<'a> { (.., true) => unreachable!("no custom operators"), // Reserved keyword that is not custom and disabled. (token, false) if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token) => { - let msg = format!("reserved {} '{token}' is disabled", if is_valid_identifier(token.chars()) { "keyword"} else {"symbol"}); + let msg = format!("reserved {} '{token}' is disabled", if is_valid_identifier(token) { "keyword"} else {"symbol"}); Token::LexError(LERR::ImproperSymbol(s.to_string(), msg).into()) }, // Reserved keyword/operator that is not custom. diff --git a/src/types/fn_ptr.rs b/src/types/fn_ptr.rs index 8c5d4b1f..5938b7a6 100644 --- a/src/types/fn_ptr.rs +++ b/src/types/fn_ptr.rs @@ -1,6 +1,6 @@ //! The `FnPtr` type. -use crate::tokenizer::is_valid_identifier; +use crate::tokenizer::is_valid_function_name; use crate::types::dynamic::Variant; use crate::{ Dynamic, Engine, FuncArgs, ImmutableString, Module, NativeCallContext, Position, RhaiError, @@ -106,7 +106,7 @@ impl FnPtr { #[inline(always)] #[must_use] pub fn is_anonymous(&self) -> bool { - self.name.starts_with(crate::engine::FN_ANONYMOUS) + crate::func::is_anonymous_fn(&self.name) } /// Call the function pointer with curried arguments (if any). /// The function may be script-defined (not available under `no_function`) or native Rust. @@ -254,7 +254,7 @@ impl TryFrom for FnPtr { #[inline(always)] fn try_from(value: ImmutableString) -> RhaiResultOf { - if is_valid_identifier(value.chars()) { + if is_valid_function_name(&value) { Ok(Self { name: value, curry: StaticVec::new_const(), diff --git a/src/types/parse_error.rs b/src/types/parse_error.rs index d11a49c6..86fedfdf 100644 --- a/src/types/parse_error.rs +++ b/src/types/parse_error.rs @@ -238,7 +238,7 @@ impl fmt::Display for ParseErrorType { Self::AssignmentToInvalidLHS(s) => f.write_str(s), Self::LiteralTooLarge(typ, max) => write!(f, "{typ} exceeds the maximum limit ({max})"), - Self::Reserved(s) if is_valid_identifier(s.chars()) => write!(f, "'{s}' is a reserved keyword"), + Self::Reserved(s) if is_valid_identifier(s.as_str()) => write!(f, "'{s}' is a reserved keyword"), Self::Reserved(s) => write!(f, "'{s}' is a reserved symbol"), Self::UnexpectedEOF => f.write_str("Script is incomplete"), Self::WrongSwitchIntegerCase => f.write_str("Integer switch case cannot follow a range case"),