Code cleanup.

This commit is contained in:
Stephen Chung 2022-10-30 18:43:18 +08:00
parent 8b773aa15e
commit 22ee12531c
12 changed files with 246 additions and 286 deletions

View File

@ -274,7 +274,7 @@ impl Engine {
// Check for data race. // Check for data race.
#[cfg(not(feature = "no_closure"))] #[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()) { if let Some(fn_def) = ast.shared_lib().get_script_fn(name, args.len()) {
self.call_script_fn( self.call_script_fn(

View File

@ -17,7 +17,7 @@ use std::{
fmt::Write, fmt::Write,
hash::Hash, hash::Hash,
iter::once, iter::once,
num::{NonZeroU8, NonZeroUsize}, num::{NonZeroU64, NonZeroU8, NonZeroUsize},
}; };
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -86,7 +86,7 @@ impl CustomExpr {
/// ///
/// Two separate hashes are pre-calculated because of the following patterns: /// Two separate hashes are pre-calculated because of the following patterns:
/// ///
/// ```js /// ```rhai
/// func(a, b, c); // Native: func(a, b, c) - 3 parameters /// func(a, b, c); // Native: func(a, b, c) - 3 parameters
/// // Script: func(a, b, c) - 3 parameters /// // Script: func(a, b, c) - 3 parameters
/// ///
@ -100,22 +100,22 @@ impl CustomExpr {
/// ///
/// Function call hashes are used in the following manner: /// 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 /// * First, the script hash (if any) is tried, which contains only the called function's name plus
/// number of parameters. /// the number of parameters.
/// ///
/// * Next, the actual types of arguments are hashed and _combined_ with the native hash, which is /// * 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 /// then used to search for a native function.
/// 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)]
pub struct FnCallHashes {
/// Pre-calculated hash for a script-defined function (zero if native functions only).
#[cfg(not(feature = "no_function"))]
script: u64,
/// Pre-calculated hash for a native Rust function with no parameter types.
/// ///
/// This hash can never be zero. /// In other words, a complete native function call hash always contains the called function's
native: u64, /// 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 ([`None`] if native functions only).
#[cfg(not(feature = "no_function"))]
script: Option<NonZeroU64>,
/// Pre-calculated hash for a native Rust function with no parameter types.
native: NonZeroU64,
} }
impl fmt::Debug for FnCallHashes { impl fmt::Debug for FnCallHashes {
@ -123,11 +123,11 @@ impl fmt::Debug for FnCallHashes {
#[inline(never)] #[inline(never)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
if self.script != 0 { if let Some(script) = self.script {
return if self.script == self.native { return if script == self.native {
fmt::Debug::fmt(&self.native, f) fmt::Debug::fmt(&self.native, f)
} else { } else {
write!(f, "({}, {})", self.script, self.native) write!(f, "({}, {})", script, self.native)
}; };
} }
@ -138,11 +138,11 @@ impl fmt::Debug for FnCallHashes {
impl From<u64> for FnCallHashes { impl From<u64> for FnCallHashes {
#[inline] #[inline]
fn from(hash: u64) -> Self { 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 { Self {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
script: hash, script: Some(hash),
native: hash, native: hash,
} }
} }
@ -150,23 +150,23 @@ impl From<u64> for FnCallHashes {
impl FnCallHashes { impl FnCallHashes {
/// Create a [`FnCallHashes`] with only the native Rust hash. /// Create a [`FnCallHashes`] with only the native Rust hash.
#[inline(always)] #[inline]
#[must_use] #[must_use]
pub const fn from_native(hash: u64) -> Self { pub fn from_native(hash: u64) -> Self {
Self { Self {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
script: 0, script: None,
native: if hash == 0 { ALT_ZERO_HASH } else { hash }, native: NonZeroU64::new(if hash == 0 { ALT_ZERO_HASH } else { hash }).unwrap(),
} }
} }
/// Create a [`FnCallHashes`] with both native Rust and script function hashes. /// Create a [`FnCallHashes`] with both native Rust and script function hashes.
#[inline(always)] #[inline]
#[must_use] #[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 { Self {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
script: if script == 0 { ALT_ZERO_HASH } else { script }, script: NonZeroU64::new(if script == 0 { ALT_ZERO_HASH } else { script }),
native: if native == 0 { ALT_ZERO_HASH } else { native }, native: NonZeroU64::new(if native == 0 { ALT_ZERO_HASH } else { native }).unwrap(),
} }
} }
/// Is this [`FnCallHashes`] native-only? /// Is this [`FnCallHashes`] native-only?
@ -174,23 +174,31 @@ impl FnCallHashes {
#[must_use] #[must_use]
pub const fn is_native_only(&self) -> bool { pub const fn is_native_only(&self) -> bool {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
return self.script == 0; return self.script.is_none();
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
return true; return true;
} }
/// Get the native hash. /// Get the native hash.
///
/// The hash returned is never zero.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub const fn native(&self) -> u64 { pub fn native(&self) -> u64 {
self.native self.native.get()
} }
/// Get the script hash. /// Get the script hash.
///
/// The hash returned is never zero.
///
/// # Panics
///
/// Panics if this [`FnCallHashes`] is native-only.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub const fn script(&self) -> u64 { pub fn script(&self) -> u64 {
assert!(self.script != 0); assert!(self.script.is_some());
self.script self.script.as_ref().unwrap().get()
} }
} }
@ -210,9 +218,7 @@ pub struct FnCallExpr {
/// Does this function call capture the parent scope? /// Does this function call capture the parent scope?
pub capture_parent_scope: bool, pub capture_parent_scope: bool,
/// Is this function call a native operator? /// Is this function call a native operator?
pub operator_token: Option<Token>, pub op_token: Option<Token>,
/// [Position] of the function name.
pub pos: Position,
} }
impl fmt::Debug for FnCallExpr { impl fmt::Debug for FnCallExpr {
@ -227,13 +233,12 @@ impl fmt::Debug for FnCallExpr {
ff.field("hash", &self.hashes) ff.field("hash", &self.hashes)
.field("name", &self.name) .field("name", &self.name)
.field("args", &self.args); .field("args", &self.args);
if let Some(ref token) = self.operator_token { if let Some(ref token) = self.op_token {
ff.field("operator_token", token); ff.field("op_token", token);
} }
if self.capture_parent_scope { if self.capture_parent_scope {
ff.field("capture_parent_scope", &self.capture_parent_scope); ff.field("capture_parent_scope", &self.capture_parent_scope);
} }
ff.field("pos", &self.pos);
ff.finish() ff.finish()
} }
} }
@ -698,8 +703,7 @@ impl Expr {
hashes: calc_fn_hash(None, f.fn_name(), 1).into(), hashes: calc_fn_hash(None, f.fn_name(), 1).into(),
args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(), args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(),
capture_parent_scope: false, capture_parent_scope: false,
operator_token: None, op_token: None,
pos,
} }
.into(), .into(),
pos, pos,
@ -754,6 +758,8 @@ impl Expr {
| Self::And(.., pos) | Self::And(.., pos)
| Self::Or(.., pos) | Self::Or(.., pos)
| Self::Coalesce(.., pos) | Self::Coalesce(.., pos)
| Self::FnCall(.., pos)
| Self::MethodCall(.., pos)
| Self::Index(.., pos) | Self::Index(.., pos)
| Self::Dot(.., pos) | Self::Dot(.., pos)
| Self::InterpolatedString(.., pos) | Self::InterpolatedString(.., pos)
@ -762,8 +768,6 @@ impl Expr {
#[cfg(not(feature = "no_custom_syntax"))] #[cfg(not(feature = "no_custom_syntax"))]
Self::Custom(.., pos) => *pos, Self::Custom(.., pos) => *pos,
Self::FnCall(x, ..) | Self::MethodCall(x, ..) => x.pos,
Self::Stmt(x) => x.position(), Self::Stmt(x) => x.position(),
} }
} }

View File

@ -18,7 +18,7 @@ pub enum FnAccess {
impl FnAccess { impl FnAccess {
/// Is this function private? /// Is this function private?
#[inline] #[inline(always)]
#[must_use] #[must_use]
pub const fn is_private(self) -> bool { pub const fn is_private(self) -> bool {
match self { match self {
@ -27,7 +27,7 @@ impl FnAccess {
} }
} }
/// Is this function public? /// Is this function public?
#[inline] #[inline(always)]
#[must_use] #[must_use]
pub const fn is_public(self) -> bool { pub const fn is_public(self) -> bool {
match self { match self {

View File

@ -269,7 +269,7 @@ impl Engine {
if op_info.is_op_assignment() { if op_info.is_op_assignment() {
let args = &mut [target.as_mut()]; let args = &mut [target.as_mut()];
let (mut orig_val, ..) = self let (mut orig_val, ..) = self
.call_native_fn( .exec_native_fn_call(
global, caches, lib, getter, *hash_get, args, is_ref_mut, global, caches, lib, getter, *hash_get, args, is_ref_mut,
false, *pos, level, false, *pos, level,
) )
@ -303,7 +303,7 @@ impl Engine {
} }
let args = &mut [target.as_mut(), &mut new_val]; let args = &mut [target.as_mut(), &mut new_val];
self.call_native_fn( self.exec_native_fn_call(
global, caches, lib, setter, *hash_set, args, is_ref_mut, false, *pos, global, caches, lib, setter, *hash_set, args, is_ref_mut, false, *pos,
level, level,
) )
@ -330,7 +330,7 @@ impl Engine {
let ((getter, hash_get), _, name) = &**x; let ((getter, hash_get), _, name) = &**x;
let args = &mut [target.as_mut()]; let args = &mut [target.as_mut()];
self.call_native_fn( self.exec_native_fn_call(
global, caches, lib, getter, *hash_get, args, is_ref_mut, false, *pos, global, caches, lib, getter, *hash_get, args, is_ref_mut, false, *pos,
level, level,
) )
@ -429,7 +429,7 @@ impl Engine {
// Assume getters are always pure // Assume getters are always pure
let (mut val, ..) = self let (mut val, ..) = self
.call_native_fn( .exec_native_fn_call(
global, caches, lib, getter, *hash_get, args, is_ref_mut, global, caches, lib, getter, *hash_get, args, is_ref_mut,
false, pos, level, false, pos, level,
) )
@ -465,7 +465,7 @@ impl Engine {
// Re-use args because the first &mut parameter will not be consumed // Re-use args because the first &mut parameter will not be consumed
let mut arg_values = [target.as_mut(), val.as_mut()]; let mut arg_values = [target.as_mut(), val.as_mut()];
let args = &mut arg_values; let args = &mut arg_values;
self.call_native_fn( self.exec_native_fn_call(
global, caches, lib, setter, *hash_set, args, is_ref_mut, global, caches, lib, setter, *hash_set, args, is_ref_mut,
false, pos, level, false, pos, level,
) )
@ -764,7 +764,7 @@ impl Engine {
let pos = Position::NONE; let pos = Position::NONE;
let level = level + 1; let level = level + 1;
self.call_native_fn( self.exec_native_fn_call(
global, caches, lib, fn_name, hash, args, true, false, pos, level, global, caches, lib, fn_name, hash, args, true, false, pos, level,
) )
.map(|(r, ..)| r) .map(|(r, ..)| r)
@ -789,7 +789,7 @@ impl Engine {
let pos = Position::NONE; let pos = Position::NONE;
let level = level + 1; let level = level + 1;
self.call_native_fn( self.exec_native_fn_call(
global, caches, lib, fn_name, hash, args, is_ref_mut, false, pos, level, global, caches, lib, fn_name, hash, args, is_ref_mut, false, pos, level,
) )
} }

View File

@ -430,7 +430,7 @@ impl Engine {
} }
/// Run the debugger callback if there is a debugging interface registered. /// 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. /// function call.
/// ///
/// It is up to the [`Engine`] to reactivate the debugger. /// It is up to the [`Engine`] to reactivate the debugger.
@ -452,7 +452,7 @@ impl Engine {
} }
/// Run the debugger callback. /// 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. /// function call.
/// ///
/// It is up to the [`Engine`] to reactivate the debugger. /// It is up to the [`Engine`] to reactivate the debugger.
@ -498,7 +498,7 @@ impl Engine {
} }
/// Run the debugger callback unconditionally. /// 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. /// function call.
/// ///
/// It is up to the [`Engine`] to reactivate the debugger. /// It is up to the [`Engine`] to reactivate the debugger.

View File

@ -1,9 +1,8 @@
//! Module defining functions for evaluating an expression. //! Module defining functions for evaluating an expression.
use super::{Caches, EvalContext, GlobalRuntimeState, Target}; 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::engine::{KEYWORD_THIS, OP_CONCAT};
use crate::func::get_builtin_binary_op_fn;
use crate::types::dynamic::AccessMode; use crate::types::dynamic::AccessMode;
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR}; use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR};
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
@ -206,89 +205,6 @@ impl Engine {
Ok((val.into(), var_pos)) 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,
..
} = expr;
// 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, *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,
first_arg,
args,
*hashes,
expr.capture_parent_scope,
expr.operator_token.as_ref(),
pos,
level,
)
}
/// Evaluate an expression. /// Evaluate an expression.
// //
// # Implementation Notes // # Implementation Notes
@ -312,7 +228,7 @@ impl Engine {
// Function calls should account for a relatively larger portion of expressions because // Function calls should account for a relatively larger portion of expressions because
// binary operators are also function calls. // binary operators are also function calls.
if let Expr::FnCall(x, ..) = expr { if let Expr::FnCall(x, pos) = expr {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let reset_debugger = let reset_debugger =
self.run_debugger_with_reset(scope, global, lib, this_ptr, expr, level)?; self.run_debugger_with_reset(scope, global, lib, this_ptr, expr, level)?;
@ -320,7 +236,7 @@ impl Engine {
self.track_operation(global, expr.position())?; self.track_operation(global, expr.position())?;
let result = let result =
self.eval_fn_call_expr(scope, global, caches, lib, this_ptr, x, x.pos, level); self.eval_fn_call_expr(scope, global, caches, lib, this_ptr, x, *pos, level);
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
global.debugger.reset_status(reset_debugger); global.debugger.reset_status(reset_debugger);

View File

@ -153,7 +153,7 @@ impl Engine {
let op_assign = op_assign.literal_syntax(); let op_assign = op_assign.literal_syntax();
let op = op.literal_syntax(); let op = op.literal_syntax();
match self.call_native_fn( match self.exec_native_fn_call(
global, caches, lib, op_assign, hash, args, true, true, *op_pos, level, global, caches, lib, op_assign, hash, args, true, true, *op_pos, level,
) { ) {
Ok(_) => (), Ok(_) => (),
@ -161,7 +161,7 @@ impl Engine {
{ {
// Expand to `var = var op rhs` // Expand to `var = var op rhs`
*args[0] = self *args[0] = self
.call_native_fn( .exec_native_fn_call(
global, caches, lib, op, *hash_op, args, true, false, *op_pos, level, global, caches, lib, op, *hash_op, args, true, false, *op_pos, level,
) )
.map_err(|err| err.fill_position(op_info.pos))? .map_err(|err| err.fill_position(op_info.pos))?
@ -207,11 +207,11 @@ impl Engine {
// Popular branches are lifted out of the `match` statement into their own branches. // Popular branches are lifted out of the `match` statement into their own branches.
// Function calls should account for a relatively larger portion of statements. // 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())?; self.track_operation(global, stmt.position())?;
let result = let result =
self.eval_fn_call_expr(scope, global, caches, lib, this_ptr, x, x.pos, level); self.eval_fn_call_expr(scope, global, caches, lib, this_ptr, x, *pos, level);
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
global.debugger.reset_status(reset_debugger); global.debugger.reset_status(reset_debugger);
@ -1006,4 +1006,28 @@ impl Engine {
result 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,
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,
)
.or_else(|err| match *err {
ERR::Return(out, ..) => Ok(out),
ERR::LoopBreak(..) => {
unreachable!("no outer loop scope to break out of")
}
_ => Err(err),
})
}
} }

View File

@ -1,9 +1,8 @@
//! Implement function-calling mechanism for [`Engine`]. //! Implement function-calling mechanism for [`Engine`].
use super::callable_function::CallableFunction; use super::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn, CallableFunction};
use super::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn};
use crate::api::default_limits::MAX_DYNAMIC_PARAMETERS; use crate::api::default_limits::MAX_DYNAMIC_PARAMETERS;
use crate::ast::{Expr, FnCallHashes, Stmt}; use crate::ast::{Expr, FnCallExpr, FnCallHashes};
use crate::engine::{ use crate::engine::{
KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY,
KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
@ -108,17 +107,14 @@ impl Drop for ArgBackup<'_> {
} }
} }
// Ensure no data races in function call arguments.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[inline] #[inline]
pub fn ensure_no_data_race( pub fn ensure_no_data_race(fn_name: &str, args: &FnCallArgs, is_ref_mut: bool) -> RhaiResultOf<()> {
fn_name: &str,
args: &FnCallArgs,
is_method_call: bool,
) -> RhaiResultOf<()> {
if let Some((n, ..)) = args if let Some((n, ..)) = args
.iter() .iter()
.enumerate() .enumerate()
.skip(if is_method_call { 1 } else { 0 }) .skip(if is_ref_mut { 1 } else { 0 })
.find(|(.., a)| a.is_locked()) .find(|(.., a)| a.is_locked())
{ {
return Err(ERR::ErrorDataRace( return Err(ERR::ErrorDataRace(
@ -134,7 +130,7 @@ pub fn ensure_no_data_race(
/// Generate the signature for a function call. /// Generate the signature for a function call.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn gen_fn_call_signature(engine: &Engine, fn_name: &str, args: &[&mut Dynamic]) -> String { fn gen_fn_call_signature(engine: &Engine, fn_name: &str, args: &[&mut Dynamic]) -> String {
format!( format!(
"{fn_name} ({})", "{fn_name} ({})",
args.iter() args.iter()
@ -154,7 +150,7 @@ pub fn gen_fn_call_signature(engine: &Engine, fn_name: &str, args: &[&mut Dynami
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline] #[inline]
#[must_use] #[must_use]
pub fn gen_qualified_fn_call_signature( fn gen_qualified_fn_call_signature(
engine: &Engine, engine: &Engine,
namespace: &crate::ast::Namespace, namespace: &crate::ast::Namespace,
fn_name: &str, fn_name: &str,
@ -343,7 +339,7 @@ impl Engine {
/// ///
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - /// **DO NOT** reuse the argument values unless for the first `&mut` argument -
/// all others are silently replaced by `()`! /// all others are silently replaced by `()`!
pub(crate) fn call_native_fn( pub(crate) fn exec_native_fn_call(
&self, &self,
global: &mut GlobalRuntimeState, global: &mut GlobalRuntimeState,
caches: &mut Caches, caches: &mut Caches,
@ -716,41 +712,17 @@ impl Engine {
// Restore the original source // Restore the original source
global.source = orig_source; global.source = orig_source;
return Ok((result?, false)); return result.map(|r| (r, false));
} }
} }
// Native function call // Native function call
let hash = hashes.native(); let hash = hashes.native();
self.call_native_fn( self.exec_native_fn_call(
global, caches, lib, fn_name, hash, args, is_ref_mut, false, pos, level, global, caches, lib, fn_name, hash, args, is_ref_mut, false, pos, level,
) )
} }
/// 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,
)
.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. /// Evaluate an argument.
#[inline] #[inline]
pub(crate) fn get_arg_value( pub(crate) fn get_arg_value(
@ -763,14 +735,15 @@ impl Engine {
arg_expr: &Expr, arg_expr: &Expr,
level: usize, level: usize,
) -> RhaiResultOf<(Dynamic, Position)> { ) -> RhaiResultOf<(Dynamic, Position)> {
#[cfg(feature = "debugging")] // Literal values
if self.debugger.is_some() {
if let Some(value) = arg_expr.get_literal_value() { if let Some(value) = arg_expr.get_literal_value() {
self.track_operation(global, arg_expr.start_position())?;
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
self.run_debugger(scope, global, lib, this_ptr, arg_expr, level)?; self.run_debugger(scope, global, lib, this_ptr, arg_expr, level)?;
return Ok((value, arg_expr.start_position())); return Ok((value, arg_expr.start_position()));
} }
}
// Do not match function exit for arguments // Do not match function exit for arguments
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
@ -784,7 +757,7 @@ impl Engine {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
global.debugger.reset_status(reset_debugger); global.debugger.reset_status(reset_debugger);
Ok((result?, arg_expr.start_position())) result.map(|r| (r, arg_expr.start_position()))
} }
/// Call a dot method. /// Call a dot method.
@ -1022,8 +995,8 @@ impl Engine {
first_arg: Option<&Expr>, first_arg: Option<&Expr>,
args_expr: &[Expr], args_expr: &[Expr],
hashes: FnCallHashes, hashes: FnCallHashes,
is_operator_call: bool,
capture_scope: bool, capture_scope: bool,
operator_token: Option<&Token>,
pos: Position, pos: Position,
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
@ -1036,7 +1009,7 @@ impl Engine {
let redirected; // Handle call() - Redirect function call let redirected; // Handle call() - Redirect function call
match name { match name {
_ if operator_token.is_some() => (), _ if is_operator_call => (),
// Handle call() // Handle call()
KEYWORD_FN_PTR_CALL if total_args >= 1 => { KEYWORD_FN_PTR_CALL if total_args >= 1 => {
@ -1520,4 +1493,87 @@ impl Engine {
// Evaluate the AST // Evaluate the AST
self.eval_global_statements(scope, global, caches, statements, lib, level) self.eval_global_statements(scope, global, caches, statements, lib, level)
} }
/// 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,
op_token,
..
} = expr;
// 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(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(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(
None, global, caches, lib, name, *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,
first_arg,
args,
*hashes,
expr.op_token.is_some(),
expr.capture_parent_scope,
pos,
level,
)
}
} }

View File

@ -14,8 +14,7 @@ pub mod script;
pub use args::FuncArgs; pub use args::FuncArgs;
pub use builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn}; pub use builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn};
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub use call::gen_qualified_fn_call_signature; pub use call::{ensure_no_data_race, FnCallArgs};
pub use call::{gen_fn_call_signature, FnCallArgs};
pub use callable_function::CallableFunction; pub use callable_function::CallableFunction;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub use func::Func; pub use func::Func;

View File

@ -382,7 +382,7 @@ impl<'a> NativeCallContext<'a> {
if native_only { if native_only {
return self return self
.engine() .engine()
.call_native_fn( .exec_native_fn_call(
global, global,
caches, caches,
self.lib, self.lib,

View File

@ -147,7 +147,7 @@ impl<'a> OptimizerState<'a> {
let lib = &[]; let lib = &[];
self.engine self.engine
.call_native_fn( .exec_native_fn_call(
&mut self.global, &mut self.global,
&mut self.caches, &mut self.caches,
lib, lib,
@ -444,9 +444,9 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
) => ) =>
{ {
match x.1.rhs { match x.1.rhs {
Expr::FnCall(ref mut x2, ..) => { Expr::FnCall(ref mut x2, pos) => {
state.set_dirty(); 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]); x.1.rhs = mem::take(&mut x2.args[1]);
} }
ref expr => unreachable!("Expr::FnCall expected but gets {:?}", expr), ref expr => unreachable!("Expr::FnCall expected but gets {:?}", expr),
@ -1142,8 +1142,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
return; return;
} }
// Overloaded operators can override built-in. // 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 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.operator_token.as_ref().unwrap(), &arg_values[0], &arg_values[1]) if let Some(result) = get_builtin_binary_op_fn(x.op_token.as_ref().unwrap(), &arg_values[0], &arg_values[1])
.and_then(|f| { .and_then(|f| {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
let lib = state.lib; let lib = state.lib;

View File

@ -174,7 +174,7 @@ impl<'e> ParseState<'e> {
/// ///
/// # Return value: `(index, is_func_name)` /// # 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. /// otherwise the index value.
/// ///
/// * `is_func_name`: `true` if the variable is actually the name of a function /// * `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`, /// Returns the offset to be deducted from `Stack::len`,
/// i.e. the top element of the [`ParseState`] is offset 1. /// 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 /// # Panics
/// ///
@ -610,12 +610,11 @@ impl Engine {
return Ok(FnCallExpr { return Ok(FnCallExpr {
name: state.get_interned_string(id), name: state.get_interned_string(id),
capture_parent_scope, capture_parent_scope,
operator_token: None, op_token: None,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
namespace, namespace,
hashes, hashes,
args, args,
pos: settings.pos,
} }
.into_fn_call_expr(settings.pos)); .into_fn_call_expr(settings.pos));
} }
@ -678,12 +677,11 @@ impl Engine {
return Ok(FnCallExpr { return Ok(FnCallExpr {
name: state.get_interned_string(id), name: state.get_interned_string(id),
capture_parent_scope, capture_parent_scope,
operator_token: None, op_token: None,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
namespace, namespace,
hashes, hashes,
args, args,
pos: settings.pos,
} }
.into_fn_call_expr(settings.pos)); .into_fn_call_expr(settings.pos));
} }
@ -1933,8 +1931,7 @@ impl Engine {
name: state.get_interned_string("-"), name: state.get_interned_string("-"),
hashes: FnCallHashes::from_native(calc_fn_hash(None, "-", 1)), hashes: FnCallHashes::from_native(calc_fn_hash(None, "-", 1)),
args, args,
pos, op_token: Some(token),
operator_token: Some(token),
capture_parent_scope: false, capture_parent_scope: false,
} }
.into_fn_call_expr(pos)) .into_fn_call_expr(pos))
@ -1963,8 +1960,7 @@ impl Engine {
name: state.get_interned_string("+"), name: state.get_interned_string("+"),
hashes: FnCallHashes::from_native(calc_fn_hash(None, "+", 1)), hashes: FnCallHashes::from_native(calc_fn_hash(None, "+", 1)),
args, args,
pos, op_token: Some(token),
operator_token: Some(token),
capture_parent_scope: false, capture_parent_scope: false,
} }
.into_fn_call_expr(pos)) .into_fn_call_expr(pos))
@ -1986,8 +1982,7 @@ impl Engine {
name: state.get_interned_string("!"), name: state.get_interned_string("!"),
hashes: FnCallHashes::from_native(calc_fn_hash(None, "!", 1)), hashes: FnCallHashes::from_native(calc_fn_hash(None, "!", 1)),
args, args,
pos, op_token: Some(token),
operator_token: Some(token),
capture_parent_scope: false, capture_parent_scope: false,
} }
.into_fn_call_expr(pos)) .into_fn_call_expr(pos))
@ -2376,25 +2371,24 @@ impl Engine {
Some(op_token.clone()) 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,
};
let mut args = StaticVec::new_const(); let mut args = StaticVec::new_const();
args.push(root); args.push(root);
args.push(rhs); args.push(rhs);
args.shrink_to_fit(); 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 { root = match op_token {
// '!=' defaults to true when passed invalid operands // '!=' 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 // Comparison operators default to false when passed invalid operands
Token::EqualsTo Token::EqualsTo
@ -2402,61 +2396,36 @@ impl Engine {
| Token::LessThanEqualsTo | Token::LessThanEqualsTo
| Token::GreaterThan | Token::GreaterThan
| Token::GreaterThanEqualsTo => { | Token::GreaterThanEqualsTo => {
let pos = args[0].start_position(); let pos = op_base.args[0].start_position();
FnCallExpr { args, ..op_base }.into_fn_call_expr(pos) op_base.into_fn_call_expr(pos)
} }
Token::Or => { Token::Or => {
let rhs = args.pop().unwrap(); let rhs = op_base.args.pop().unwrap().ensure_bool_expr()?;
let current_lhs = args.pop().unwrap(); let lhs = op_base.args.pop().unwrap().ensure_bool_expr()?;
Expr::Or( Expr::Or(BinaryExpr { lhs: lhs, rhs: rhs }.into(), pos)
BinaryExpr {
lhs: current_lhs.ensure_bool_expr()?,
rhs: rhs.ensure_bool_expr()?,
}
.into(),
pos,
)
} }
Token::And => { Token::And => {
let rhs = args.pop().unwrap(); let rhs = op_base.args.pop().unwrap().ensure_bool_expr()?;
let current_lhs = args.pop().unwrap(); let lhs = op_base.args.pop().unwrap().ensure_bool_expr()?;
Expr::And( Expr::And(BinaryExpr { lhs: lhs, rhs: rhs }.into(), pos)
BinaryExpr {
lhs: current_lhs.ensure_bool_expr()?,
rhs: rhs.ensure_bool_expr()?,
}
.into(),
pos,
)
} }
Token::DoubleQuestion => { Token::DoubleQuestion => {
let rhs = args.pop().unwrap(); let rhs = op_base.args.pop().unwrap();
let current_lhs = args.pop().unwrap(); let lhs = op_base.args.pop().unwrap();
Expr::Coalesce( Expr::Coalesce(BinaryExpr { lhs, rhs }.into(), pos)
BinaryExpr {
lhs: current_lhs,
rhs,
}
.into(),
pos,
)
} }
Token::In => { Token::In => {
// Swap the arguments // Swap the arguments
let current_lhs = args.remove(0); let lhs = op_base.args.remove(0);
let pos = current_lhs.start_position(); let pos = lhs.start_position();
args.push(current_lhs); op_base.args.push(lhs);
args.shrink_to_fit(); op_base.args.shrink_to_fit();
// Convert into a call to `contains` // Convert into a call to `contains`
FnCallExpr { op_base.hashes = calc_fn_hash(None, OP_CONTAINS, 2).into();
hashes: calc_fn_hash(None, OP_CONTAINS, 2).into(), op_base.name = state.get_interned_string(OP_CONTAINS);
args, op_base.into_fn_call_expr(pos)
name: state.get_interned_string(OP_CONTAINS),
..op_base
}
.into_fn_call_expr(pos)
} }
#[cfg(not(feature = "no_custom_syntax"))] #[cfg(not(feature = "no_custom_syntax"))]
@ -2466,24 +2435,17 @@ impl Engine {
.get(s.as_str()) .get(s.as_str())
.map_or(false, Option::is_some) => .map_or(false, Option::is_some) =>
{ {
let hash = calc_fn_hash(None, &s, 2); op_base.hashes = if is_valid_script_function {
let pos = args[0].start_position(); calc_fn_hash(None, &s, 2).into()
FnCallExpr {
hashes: if is_valid_script_function {
hash.into()
} else { } else {
FnCallHashes::from_native(hash) FnCallHashes::from_native(calc_fn_hash(None, &s, 2))
}, };
args, op_base.into_fn_call_expr(pos)
..op_base
}
.into_fn_call_expr(pos)
} }
_ => { _ => {
let pos = args[0].start_position(); let pos = op_base.args[0].start_position();
FnCallExpr { args, ..op_base }.into_fn_call_expr(pos) op_base.into_fn_call_expr(pos)
} }
}; };
} }
@ -3725,8 +3687,7 @@ impl Engine {
num_externals + 1, num_externals + 1,
)), )),
args, args,
pos, op_token: None,
operator_token: None,
capture_parent_scope: false, capture_parent_scope: false,
} }
.into_fn_call_expr(pos); .into_fn_call_expr(pos);