From bff266d4e1f35deef9e24d465266005df05b7305 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 23 Jul 2020 18:40:42 +0800 Subject: [PATCH] Move function calling to separate source file. --- src/api.rs | 2 +- src/engine.rs | 1114 ++------------------------------------------ src/fn_args.rs | 45 ++ src/fn_call.rs | 1114 ++++++++++++++++++++++++++++++++++++++++++-- src/fn_func.rs | 1 + src/fn_native.rs | 3 +- src/fn_register.rs | 1 + src/lib.rs | 1 + src/optimize.rs | 2 + src/settings.rs | 2 + src/syntax.rs | 25 +- src/unsafe.rs | 2 +- src/utils.rs | 15 +- 13 files changed, 1203 insertions(+), 1124 deletions(-) create mode 100644 src/fn_args.rs diff --git a/src/api.rs b/src/api.rs index 6b275a71..c4c91f38 100644 --- a/src/api.rs +++ b/src/api.rs @@ -3,7 +3,7 @@ use crate::any::{Dynamic, Variant}; use crate::engine::{make_getter, make_setter, Engine, Imports, State, FN_IDX_GET, FN_IDX_SET}; use crate::error::ParseError; -use crate::fn_call::FuncArgs; +use crate::fn_args::FuncArgs; use crate::fn_native::{IteratorFn, SendSync}; use crate::fn_register::RegisterFn; use crate::module::{FuncReturn, Module}; diff --git a/src/engine.rs b/src/engine.rs index 692c3400..cb7234d3 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -2,12 +2,12 @@ use crate::any::{map_std_type_name, Dynamic, Union, Variant}; use crate::calc_fn_hash; -use crate::error::ParseErrorType; -use crate::fn_native::{CallableFunction, Callback, FnCallArgs, FnPtr}; +use crate::fn_call::run_builtin_op_assignment; +use crate::fn_native::{CallableFunction, Callback, FnPtr}; use crate::module::{resolvers, Module, ModuleRef, ModuleResolver}; use crate::optimize::OptimizationLevel; use crate::packages::{Package, PackagesCollection, StandardPackage}; -use crate::parser::{Expr, FnAccess, ImmutableString, ReturnType, ScriptFnDef, Stmt, AST, INT}; +use crate::parser::{Expr, FnAccess, ImmutableString, ReturnType, ScriptFnDef, Stmt}; use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; @@ -15,18 +15,13 @@ use crate::syntax::{CustomSyntax, EvalContext, Expression}; use crate::token::Position; use crate::utils::StaticVec; -#[cfg(not(feature = "no_float"))] -use crate::parser::FLOAT; - use crate::stdlib::{ - any::{type_name, TypeId}, + any::TypeId, borrow::Cow, boxed::Box, collections::{HashMap, HashSet}, - convert::TryFrom, fmt, format, iter::{empty, once}, - mem, string::{String, ToString}, vec::Vec, }; @@ -93,7 +88,7 @@ pub const MARKER_IDENT: &str = "$ident$"; /// A type specifying the method of chaining. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -enum ChainType { +pub enum ChainType { None, Index, Dot, @@ -101,7 +96,7 @@ enum ChainType { /// A type that encapsulates a mutation target for an expression with side effects. #[derive(Debug)] -enum Target<'a> { +pub enum Target<'a> { /// The target is a mutable reference to a `Dynamic` value somewhere. Ref(&'a mut Dynamic), /// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects). @@ -376,31 +371,11 @@ pub fn make_getter(id: &str) -> String { format!("{}{}", FN_GET, id) } -/// Extract the property name from a getter function name. -fn extract_prop_from_getter(fn_name: &str) -> Option<&str> { - #[cfg(not(feature = "no_object"))] - if fn_name.starts_with(FN_GET) { - return Some(&fn_name[FN_GET.len()..]); - } - - None -} - /// Make setter function pub fn make_setter(id: &str) -> String { format!("{}{}", FN_SET, id) } -/// Extract the property name from a setter function name. -fn extract_prop_from_setter(fn_name: &str) -> Option<&str> { - #[cfg(not(feature = "no_object"))] - if fn_name.starts_with(FN_SET) { - return Some(&fn_name[FN_SET.len()..]); - } - - None -} - /// Print/debug to stdout fn default_print(s: &str) { #[cfg(not(feature = "no_std"))] @@ -410,7 +385,7 @@ fn default_print(s: &str) { /// Search for a module within an imports stack. /// Position in `EvalAltResult` is `None` and must be set afterwards. -fn search_imports<'s>( +pub fn search_imports<'s>( mods: &'s Imports, state: &mut State, modules: &Box, @@ -443,7 +418,7 @@ fn search_imports<'s>( /// Search for a module within an imports stack. /// Position in `EvalAltResult` is `None` and must be set afterwards. -fn search_imports_mut<'s>( +pub fn search_imports_mut<'s>( mods: &'s mut Imports, state: &mut State, modules: &Box, @@ -475,7 +450,7 @@ fn search_imports_mut<'s>( } /// Search for a variable within the scope and imports -fn search_namespace<'s, 'a>( +pub fn search_namespace<'s, 'a>( scope: &'s mut Scope, mods: &'s mut Imports, state: &mut State, @@ -510,7 +485,7 @@ fn search_namespace<'s, 'a>( } /// Search for a variable within the scope -fn search_scope_only<'s, 'a>( +pub fn search_scope_only<'s, 'a>( scope: &'s mut Scope, state: &mut State, this_ptr: &'s mut Option<&mut Dynamic>, @@ -589,549 +564,6 @@ impl Engine { } } - /// Universal method for calling functions either registered with the `Engine` or written in Rhai. - /// Position in `EvalAltResult` is `None` and must be set afterwards. - /// - /// ## WARNING - /// - /// Function call arguments be _consumed_ when the function requires them to be passed by value. - /// All function arguments not in the first position are always passed by value and thus consumed. - /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! - pub(crate) fn call_fn_raw( - &self, - scope: &mut Scope, - mods: &mut Imports, - state: &mut State, - lib: &Module, - fn_name: &str, - (hash_fn, hash_script): (u64, u64), - args: &mut FnCallArgs, - is_ref: bool, - is_method: bool, - def_val: Option, - level: usize, - ) -> Result<(Dynamic, bool), Box> { - self.inc_operations(state)?; - - let native_only = hash_script == 0; - - // Check for stack overflow - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "unchecked"))] - if level > self.max_call_stack_depth { - return Err(Box::new( - EvalAltResult::ErrorStackOverflow(Position::none()), - )); - } - - let mut this_copy: Dynamic = Default::default(); - let mut old_this_ptr: Option<&mut Dynamic> = None; - - /// This function replaces the first argument of a method call with a clone copy. - /// This is to prevent a pure function unintentionally consuming the first argument. - fn normalize_first_arg<'a>( - normalize: bool, - this_copy: &mut Dynamic, - old_this_ptr: &mut Option<&'a mut Dynamic>, - args: &mut FnCallArgs<'a>, - ) { - // Only do it for method calls with arguments. - if !normalize || args.is_empty() { - return; - } - - // Clone the original value. - *this_copy = args[0].clone(); - - // Replace the first reference with a reference to the clone, force-casting the lifetime. - // Keep the original reference. Must remember to restore it later with `restore_first_arg_of_method_call`. - // - // # Safety - // - // Blindly casting a a reference to another lifetime saves on allocations and string cloning, - // but must be used with the utmost care. - // - // We can do this here because, at the end of this scope, we'd restore the original reference - // with `restore_first_arg_of_method_call`. Therefore this shorter lifetime does not get "out". - let this_pointer = mem::replace(args.get_mut(0).unwrap(), unsafe { - mem::transmute(this_copy) - }); - - *old_this_ptr = Some(this_pointer); - } - - /// This function restores the first argument that was replaced by `normalize_first_arg_of_method_call`. - fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCallArgs<'a>) { - if let Some(this_pointer) = old_this_ptr { - args[0] = this_pointer; - } - } - - // Search for the function - // First search in script-defined functions (can override built-in) - // Then search registered native functions (can override packages) - // Then search packages - // NOTE: We skip script functions for global_module and packages, and native functions for lib - let func = if !native_only { - lib.get_fn(hash_script) //.or_else(|| lib.get_fn(hash_fn)) - } else { - None - } - //.or_else(|| self.global_module.get_fn(hash_script)) - .or_else(|| self.global_module.get_fn(hash_fn)) - //.or_else(|| self.packages.get_fn(hash_script)) - .or_else(|| self.packages.get_fn(hash_fn)); - - if let Some(func) = func { - #[cfg(not(feature = "no_function"))] - let need_normalize = is_ref && (func.is_pure() || (func.is_script() && !is_method)); - #[cfg(feature = "no_function")] - let need_normalize = is_ref && func.is_pure(); - - // Calling pure function but the first argument is a reference? - normalize_first_arg(need_normalize, &mut this_copy, &mut old_this_ptr, args); - - #[cfg(not(feature = "no_function"))] - if func.is_script() { - // Run scripted function - let fn_def = func.get_fn_def(); - - // Method call of script function - map first argument to `this` - return if is_method { - let (first, rest) = args.split_at_mut(1); - Ok(( - self.call_script_fn( - scope, - mods, - state, - lib, - &mut Some(first[0]), - fn_name, - fn_def, - rest, - level, - )?, - false, - )) - } else { - let result = self.call_script_fn( - scope, mods, state, lib, &mut None, fn_name, fn_def, args, level, - )?; - - // Restore the original reference - restore_first_arg(old_this_ptr, args); - - Ok((result, false)) - }; - } - - // Run external function - let result = func.get_native_fn()(self, lib, args)?; - - // Restore the original reference - restore_first_arg(old_this_ptr, args); - - // See if the function match print/debug (which requires special processing) - return Ok(match fn_name { - KEYWORD_PRINT => ( - (self.print)(result.as_str().map_err(|typ| { - Box::new(EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(type_name::()).into(), - typ.into(), - Position::none(), - )) - })?) - .into(), - false, - ), - KEYWORD_DEBUG => ( - (self.debug)(result.as_str().map_err(|typ| { - Box::new(EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(type_name::()).into(), - typ.into(), - Position::none(), - )) - })?) - .into(), - false, - ), - _ => (result, func.is_method()), - }); - } - - // See if it is built in. - if args.len() == 2 { - match run_builtin_binary_op(fn_name, args[0], args[1])? { - Some(v) => return Ok((v, false)), - None => (), - } - } - - // Return default value (if any) - if let Some(val) = def_val { - return Ok((val.into(), false)); - } - - // Getter function not found? - if let Some(prop) = extract_prop_from_getter(fn_name) { - return Err(Box::new(EvalAltResult::ErrorDotExpr( - format!("- property '{}' unknown or write-only", prop), - Position::none(), - ))); - } - - // Setter function not found? - if let Some(prop) = extract_prop_from_setter(fn_name) { - return Err(Box::new(EvalAltResult::ErrorDotExpr( - format!("- property '{}' unknown or read-only", prop), - Position::none(), - ))); - } - - // index getter function not found? - if fn_name == FN_IDX_GET && args.len() == 2 { - return Err(Box::new(EvalAltResult::ErrorFunctionNotFound( - format!( - "{} [{}]", - self.map_type_name(args[0].type_name()), - self.map_type_name(args[1].type_name()), - ), - Position::none(), - ))); - } - - // index setter function not found? - if fn_name == FN_IDX_SET { - return Err(Box::new(EvalAltResult::ErrorFunctionNotFound( - format!( - "{} [{}]=", - self.map_type_name(args[0].type_name()), - self.map_type_name(args[1].type_name()), - ), - Position::none(), - ))); - } - - // Raise error - Err(Box::new(EvalAltResult::ErrorFunctionNotFound( - format!( - "{} ({})", - fn_name, - args.iter() - .map(|name| if name.is::() { - "&str | ImmutableString | String" - } else { - self.map_type_name((*name).type_name()) - }) - .collect::>() - .join(", ") - ), - Position::none(), - ))) - } - - /// Call a script-defined function. - /// Position in `EvalAltResult` is `None` and must be set afterwards. - /// - /// ## WARNING - /// - /// Function call arguments may be _consumed_ when the function requires them to be passed by value. - /// All function arguments not in the first position are always passed by value and thus consumed. - /// **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, - mods: &mut Imports, - state: &mut State, - lib: &Module, - this_ptr: &mut Option<&mut Dynamic>, - fn_name: &str, - fn_def: &ScriptFnDef, - args: &mut FnCallArgs, - level: usize, - ) -> Result> { - let orig_scope_level = state.scope_level; - state.scope_level += 1; - - let prev_scope_len = scope.len(); - let prev_mods_len = mods.len(); - - // Put arguments into scope as variables - // Actually consume the arguments instead of cloning them - scope.extend( - fn_def - .params - .iter() - .zip(args.iter_mut().map(|v| mem::take(*v))) - .map(|(name, value)| { - let var_name = unsafe_cast_var_name_to_lifetime(name.as_str(), state); - (var_name, ScopeEntryType::Normal, value) - }), - ); - - // Evaluate the function at one higher level of call depth - let result = self - .eval_stmt(scope, mods, state, lib, this_ptr, &fn_def.body, level + 1) - .or_else(|err| match *err { - // Convert return statement to return value - EvalAltResult::Return(x, _) => Ok(x), - EvalAltResult::ErrorInFunctionCall(name, err, _) => { - Err(Box::new(EvalAltResult::ErrorInFunctionCall( - format!("{} > {}", fn_name, name), - err, - Position::none(), - ))) - } - _ => Err(Box::new(EvalAltResult::ErrorInFunctionCall( - fn_name.to_string(), - err, - Position::none(), - ))), - }); - - // Remove all local variables - scope.rewind(prev_scope_len); - mods.truncate(prev_mods_len); - state.scope_level = orig_scope_level; - - result - } - - // Has a system function an override? - fn has_override(&self, lib: &Module, hash_fn: u64, hash_script: u64) -> bool { - // NOTE: We skip script functions for global_module and packages, and native functions for lib - - // First check script-defined functions - lib.contains_fn(hash_script) - //|| lib.contains_fn(hash_fn) - // Then check registered functions - //|| self.global_module.contains_fn(hash_script) - || self.global_module.contains_fn(hash_fn) - // Then check packages - //|| self.packages.contains_fn(hash_script) - || self.packages.contains_fn(hash_fn) - } - - /// Perform an actual function call, taking care of special functions - /// Position in `EvalAltResult` is `None` and must be set afterwards. - /// - /// ## WARNING - /// - /// Function call arguments may be _consumed_ when the function requires them to be passed by value. - /// All function arguments not in the first position are always passed by value and thus consumed. - /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! - fn exec_fn_call( - &self, - state: &mut State, - lib: &Module, - fn_name: &str, - native_only: bool, - hash_script: u64, - args: &mut FnCallArgs, - is_ref: bool, - is_method: bool, - def_val: Option, - level: usize, - ) -> Result<(Dynamic, bool), Box> { - // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. - let arg_types = args.iter().map(|a| a.type_id()); - let hash_fn = calc_fn_hash(empty(), fn_name, args.len(), arg_types); - let hashes = (hash_fn, if native_only { 0 } else { hash_script }); - - match fn_name { - // type_of - KEYWORD_TYPE_OF if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => { - Ok(( - self.map_type_name(args[0].type_name()).to_string().into(), - false, - )) - } - - // Fn - KEYWORD_FN_PTR if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => { - Err(Box::new(EvalAltResult::ErrorRuntime( - "'Fn' should not be called in method style. Try Fn(...);".into(), - Position::none(), - ))) - } - - // eval - reaching this point it must be a method-style call - KEYWORD_EVAL if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => { - Err(Box::new(EvalAltResult::ErrorRuntime( - "'eval' should not be called in method style. Try eval(...);".into(), - Position::none(), - ))) - } - - // Normal function call - _ => { - let mut scope = Scope::new(); - let mut mods = Imports::new(); - self.call_fn_raw( - &mut scope, &mut mods, state, lib, fn_name, hashes, args, is_ref, is_method, - def_val, level, - ) - } - } - } - - /// Evaluate a text string as a script - used primarily for 'eval'. - /// Position in `EvalAltResult` is `None` and must be set afterwards. - fn eval_script_expr( - &self, - scope: &mut Scope, - mods: &mut Imports, - state: &mut State, - lib: &Module, - script: &Dynamic, - ) -> Result> { - let script = script.as_str().map_err(|typ| { - EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(type_name::()).into(), - typ.into(), - Position::none(), - ) - })?; - - // Compile the script text - // No optimizations because we only run it once - let mut ast = self.compile_with_scope_and_optimization_level( - &Scope::new(), - &[script], - OptimizationLevel::None, - )?; - - // If new functions are defined within the eval string, it is an error - if ast.lib().num_fn() != 0 { - return Err(ParseErrorType::WrongFnDefinition.into()); - } - - let statements = mem::take(ast.statements_mut()); - let ast = AST::new(statements, lib.clone()); - - // Evaluate the AST - let (result, operations) = self.eval_ast_with_scope_raw(scope, mods, &ast)?; - - state.operations += operations; - self.inc_operations(state)?; - - return Ok(result); - } - - /// Call a dot method. - fn call_method( - &self, - state: &mut State, - lib: &Module, - target: &mut Target, - expr: &Expr, - idx_val: Dynamic, - level: usize, - ) -> Result<(Dynamic, bool), Box> { - let ((name, native, pos), _, hash, _, def_val) = match expr { - Expr::FnCall(x) => x.as_ref(), - _ => unreachable!(), - }; - - let is_ref = target.is_ref(); - let is_value = target.is_value(); - - // Get a reference to the mutation target Dynamic - let obj = target.as_mut(); - let mut idx = idx_val.cast::>(); - let mut fn_name = name.as_ref(); - - let (result, updated) = if fn_name == KEYWORD_FN_PTR_CALL && obj.is::() { - // FnPtr call - let fn_ptr = obj.downcast_ref::().unwrap(); - let mut curry = fn_ptr.curry().iter().cloned().collect::>(); - // Redirect function name - let fn_name = fn_ptr.fn_name(); - // Recalculate hash - let hash = calc_fn_hash(empty(), fn_name, curry.len() + idx.len(), empty()); - // Arguments are passed as-is, adding the curried arguments - let mut arg_values = curry - .iter_mut() - .chain(idx.iter_mut()) - .collect::>(); - let args = arg_values.as_mut(); - - // Map it to name(args) in function-call style - self.exec_fn_call( - state, lib, fn_name, *native, hash, args, false, false, *def_val, level, - ) - } else if fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::() { - // FnPtr call on object - let fn_ptr = idx.remove(0).cast::(); - let mut curry = fn_ptr.curry().iter().cloned().collect::>(); - // Redirect function name - let fn_name = fn_ptr.get_fn_name().clone(); - // Recalculate hash - let hash = calc_fn_hash(empty(), &fn_name, curry.len() + idx.len(), empty()); - // Replace the first argument with the object pointer, adding the curried arguments - let mut arg_values = once(obj) - .chain(curry.iter_mut()) - .chain(idx.iter_mut()) - .collect::>(); - let args = arg_values.as_mut(); - - // Map it to name(args) in function-call style - self.exec_fn_call( - state, lib, &fn_name, *native, hash, args, is_ref, true, *def_val, level, - ) - } else if fn_name == KEYWORD_FN_PTR_CURRY && obj.is::() { - // Curry call - let fn_ptr = obj.downcast_ref::().unwrap(); - Ok(( - FnPtr::new_unchecked( - fn_ptr.get_fn_name().clone(), - fn_ptr - .curry() - .iter() - .cloned() - .chain(idx.into_iter()) - .collect(), - ) - .into(), - false, - )) - } else { - let redirected; - let mut hash = *hash; - - // Check if it is a map method call in OOP style - #[cfg(not(feature = "no_object"))] - if let Some(map) = obj.downcast_ref::() { - if let Some(val) = map.get(fn_name) { - if let Some(f) = val.downcast_ref::() { - // Remap the function name - redirected = f.get_fn_name().clone(); - fn_name = &redirected; - // Recalculate the hash based on the new function name - hash = calc_fn_hash(empty(), fn_name, idx.len(), empty()); - } - } - }; - - // Attached object pointer in front of the arguments - let mut arg_values = once(obj).chain(idx.iter_mut()).collect::>(); - let args = arg_values.as_mut(); - - self.exec_fn_call( - state, lib, fn_name, *native, hash, args, is_ref, true, *def_val, level, - ) - } - .map_err(|err| err.new_position(*pos))?; - - // Feed the changed temp value back - if updated && !is_ref && !is_value { - let new_val = target.as_mut().clone(); - target.set_value(new_val)?; - } - - Ok((result, updated)) - } - /// Chain-evaluate a dot/index chain. /// Position in `EvalAltResult` is `None` and must be set afterwards. fn eval_dot_index_chain_helper( @@ -1239,7 +671,7 @@ impl Engine { match rhs { // xxx.fn_name(arg_expr_list) Expr::FnCall(x) if x.1.is_none() => { - self.call_method(state, lib, target, rhs, idx_val, level) + self.make_method_call(state, lib, target, rhs, idx_val, level) } // xxx.module::fn_name(...) - syntax error Expr::FnCall(_) => unreachable!(), @@ -1295,8 +727,9 @@ impl Engine { } // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr Expr::FnCall(x) if x.1.is_none() => { - let (val, _) = - self.call_method(state, lib, target, sub_lhs, idx_val, level)?; + let (val, _) = self.make_method_call( + state, lib, target, sub_lhs, idx_val, level, + )?; val.into() } // {xxx:map}.module::fn_name(...) - syntax error @@ -1361,8 +794,9 @@ impl Engine { } // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr Expr::FnCall(x) if x.1.is_none() => { - let (mut val, _) = - self.call_method(state, lib, target, sub_lhs, idx_val, level)?; + let (mut val, _) = self.make_method_call( + state, lib, target, sub_lhs, idx_val, level, + )?; let val = &mut val; let target = &mut val.into(); @@ -1707,30 +1141,8 @@ impl Engine { } } - /// Evaluate an expression tree. - /// - /// ## WARNING - Low Level API - /// - /// This function is very low level. It evaluates an expression from an AST. - pub fn eval_expression_tree( - &self, - context: &mut EvalContext, - scope: &mut Scope, - expr: &Expression, - ) -> Result> { - self.eval_expr( - scope, - context.mods, - context.state, - context.lib, - context.this_ptr, - expr.expr(), - context.level, - ) - } - /// Evaluate an expression - fn eval_expr( + pub(crate) fn eval_expr( &self, scope: &mut Scope, mods: &mut Imports, @@ -1912,275 +1324,21 @@ impl Engine { // Normal function call Expr::FnCall(x) if x.1.is_none() => { let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref(); - - // Handle Fn() - if name == KEYWORD_FN_PTR && args_expr.len() == 1 { - let hash_fn = - calc_fn_hash(empty(), name, 1, once(TypeId::of::())); - - if !self.has_override(lib, hash_fn, *hash) { - // Fn - only in function call style - let expr = args_expr.get(0); - let arg_value = - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - - return arg_value - .take_immutable_string() - .map_err(|typ| { - Box::new(EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(type_name::()).into(), - typ.into(), - expr.position(), - )) - }) - .and_then(|s| FnPtr::try_from(s)) - .map(Into::::into) - .map_err(|err| err.new_position(*pos)); - } - } - - // Handle curry() - if name == KEYWORD_FN_PTR_CURRY && args_expr.len() > 1 { - let expr = args_expr.get(0); - let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - - if !fn_ptr.is::() { - return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(type_name::()).into(), - self.map_type_name(fn_ptr.type_name()).into(), - expr.position(), - ))); - } - - let (fn_name, fn_curry) = fn_ptr.cast::().take_data(); - - let curry: StaticVec<_> = args_expr - .iter() - .skip(1) - .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) - .collect::>()?; - - return Ok(FnPtr::new_unchecked( - fn_name, - fn_curry.into_iter().chain(curry.into_iter()).collect(), - ) - .into()); - } - - // Handle eval() - if name == KEYWORD_EVAL && args_expr.len() == 1 { - let hash_fn = - calc_fn_hash(empty(), name, 1, once(TypeId::of::())); - - if !self.has_override(lib, hash_fn, *hash) { - // eval - only in function call style - let prev_len = scope.len(); - let expr = args_expr.get(0); - let script = - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - let result = self - .eval_script_expr(scope, mods, state, lib, &script) - .map_err(|err| err.new_position(expr.position())); - - if scope.len() != prev_len { - // IMPORTANT! If the eval defines new variables in the current scope, - // all variable offsets from this point on will be mis-aligned. - state.always_search = true; - } - - return result; - } - } - - // Handle call() - Redirect function call - let redirected; - let mut name = name.as_ref(); - let mut args_expr = args_expr.as_ref(); - let mut curry: StaticVec<_> = Default::default(); - let mut hash = *hash; - - if name == KEYWORD_FN_PTR_CALL - && args_expr.len() >= 1 - && !self.has_override(lib, 0, hash) - { - let expr = args_expr.get(0).unwrap(); - let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - - if fn_name.is::() { - let fn_ptr = fn_name.cast::(); - curry = fn_ptr.curry().iter().cloned().collect(); - // Redirect function name - redirected = fn_ptr.take_data().0; - name = &redirected; - // Skip the first argument - args_expr = &args_expr.as_ref()[1..]; - // Recalculate hash - hash = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty()); - } else { - return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(type_name::()).into(), - fn_name.type_name().into(), - expr.position(), - ))); - } - } - - // Normal function call - except for Fn and eval (handled above) - let mut arg_values: StaticVec; - let mut args: StaticVec<_>; - let mut is_ref = false; - - if args_expr.is_empty() && curry.is_empty() { - // No arguments - args = Default::default(); - } else { - // See if the first argument is a variable, if so, convert to method-call style - // in order to leverage potential &mut first argument and avoid cloning the value - match args_expr.get(0).unwrap() { - // func(x, ...) -> x.func(...) - lhs @ Expr::Variable(_) => { - arg_values = args_expr - .iter() - .skip(1) - .map(|expr| { - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) - }) - .collect::>()?; - - let (target, _, _, pos) = - search_namespace(scope, mods, state, this_ptr, lhs)?; - - self.inc_operations(state) - .map_err(|err| err.new_position(pos))?; - - args = once(target) - .chain(curry.iter_mut()) - .chain(arg_values.iter_mut()) - .collect(); - - is_ref = true; - } - // func(..., ...) - _ => { - arg_values = args_expr - .iter() - .map(|expr| { - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) - }) - .collect::>()?; - - args = curry.iter_mut().chain(arg_values.iter_mut()).collect(); - } - } - } - - let args = args.as_mut(); - self.exec_fn_call( - state, lib, name, *native, hash, args, is_ref, false, *def_val, level, + self.make_function_call( + scope, mods, state, lib, this_ptr, name, args_expr, *def_val, *hash, *native, + level, ) - .map(|(v, _)| v) .map_err(|err| err.new_position(*pos)) } // Module-qualified function call Expr::FnCall(x) if x.1.is_some() => { - let ((name, _, pos), modules, hash_script, args_expr, def_val) = x.as_ref(); - let modules = modules.as_ref().unwrap(); - - let mut arg_values: StaticVec; - let mut args: StaticVec<_>; - - if args_expr.is_empty() { - // No arguments - args = Default::default(); - } else { - // See if the first argument is a variable (not module-qualified). - // If so, convert to method-call style in order to leverage potential - // &mut first argument and avoid cloning the value - match args_expr.get(0) { - // func(x, ...) -> x.func(...) - Expr::Variable(x) if x.1.is_none() => { - arg_values = args_expr - .iter() - .skip(1) - .map(|expr| { - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) - }) - .collect::>()?; - - let (target, _, _, pos) = - search_scope_only(scope, state, this_ptr, args_expr.get(0))?; - - self.inc_operations(state) - .map_err(|err| err.new_position(pos))?; - - args = once(target).chain(arg_values.iter_mut()).collect(); - } - // func(..., ...) or func(mod::x, ...) - _ => { - arg_values = args_expr - .iter() - .map(|expr| { - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) - }) - .collect::>()?; - - args = arg_values.iter_mut().collect(); - } - } - } - - let module = search_imports(mods, state, modules)?; - - // First search in script-defined functions (can override built-in) - let func = match module.get_qualified_fn(*hash_script) { - Err(err) if matches!(*err, EvalAltResult::ErrorFunctionNotFound(_, _)) => { - // Then search in Rust functions - self.inc_operations(state) - .map_err(|err| err.new_position(*pos))?; - - // Qualified Rust functions are indexed in two steps: - // 1) Calculate a hash in a similar manner to script-defined functions, - // i.e. qualifiers + function name + number of arguments. - // 2) Calculate a second hash with no qualifiers, empty function name, - // zero number of arguments, and the actual list of argument `TypeId`'.s - let hash_fn_args = - calc_fn_hash(empty(), "", 0, args.iter().map(|a| a.type_id())); - // 3) The final hash is the XOR of the two hashes. - let hash_qualified_fn = *hash_script ^ hash_fn_args; - - module.get_qualified_fn(hash_qualified_fn) - } - r => r, - }; - - match func { - #[cfg(not(feature = "no_function"))] - Ok(f) if f.is_script() => { - let args = args.as_mut(); - let fn_def = f.get_fn_def(); - let mut scope = Scope::new(); - let mut mods = Imports::new(); - self.call_script_fn( - &mut scope, &mut mods, state, lib, &mut None, name, fn_def, args, level, - ) - .map_err(|err| err.new_position(*pos)) - } - Ok(f) => f.get_native_fn()(self, lib, args.as_mut()) - .map_err(|err| err.new_position(*pos)), - Err(err) => match *err { - EvalAltResult::ErrorFunctionNotFound(_, _) if def_val.is_some() => { - Ok(def_val.unwrap().into()) - } - EvalAltResult::ErrorFunctionNotFound(_, _) => { - Err(Box::new(EvalAltResult::ErrorFunctionNotFound( - format!("{}{}", modules, name), - *pos, - ))) - } - _ => Err(err.new_position(*pos)), - }, - } + let ((name, _, pos), modules, hash, args_expr, def_val) = x.as_ref(); + self.make_qualified_function_call( + scope, mods, state, lib, this_ptr, modules, name, args_expr, *def_val, *hash, + level, + ) + .map_err(|err| err.new_position(*pos)) } Expr::In(x) => self.eval_in_expr(scope, mods, state, lib, this_ptr, &x.0, &x.1, level), @@ -2640,7 +1798,7 @@ impl Engine { /// Check if the number of operations stay within limit. /// Position in `EvalAltResult` is `None` and must be set afterwards. - fn inc_operations(&self, state: &mut State) -> Result<(), Box> { + pub(crate) fn inc_operations(&self, state: &mut State) -> Result<(), Box> { state.operations += 1; #[cfg(not(feature = "unchecked"))] @@ -2670,219 +1828,3 @@ impl Engine { .unwrap_or(map_std_type_name(name)) } } - -/// Build in common binary operator implementations to avoid the cost of calling a registered function. -fn run_builtin_binary_op( - op: &str, - x: &Dynamic, - y: &Dynamic, -) -> Result, Box> { - use crate::packages::arithmetic::*; - - let args_type = x.type_id(); - - if y.type_id() != args_type { - return Ok(None); - } - - if args_type == TypeId::of::() { - let x = *x.downcast_ref::().unwrap(); - let y = *y.downcast_ref::().unwrap(); - - #[cfg(not(feature = "unchecked"))] - match op { - "+" => return add(x, y).map(Into::into).map(Some), - "-" => return sub(x, y).map(Into::into).map(Some), - "*" => return mul(x, y).map(Into::into).map(Some), - "/" => return div(x, y).map(Into::into).map(Some), - "%" => return modulo(x, y).map(Into::into).map(Some), - "~" => return pow_i_i(x, y).map(Into::into).map(Some), - ">>" => return shr(x, y).map(Into::into).map(Some), - "<<" => return shl(x, y).map(Into::into).map(Some), - _ => (), - } - - #[cfg(feature = "unchecked")] - match op { - "+" => return Ok(Some((x + y).into())), - "-" => return Ok(Some((x - y).into())), - "*" => return Ok(Some((x * y).into())), - "/" => return Ok(Some((x / y).into())), - "%" => return Ok(Some((x % y).into())), - "~" => return pow_i_i_u(x, y).map(Into::into).map(Some), - ">>" => return shr_u(x, y).map(Into::into).map(Some), - "<<" => return shl_u(x, y).map(Into::into).map(Some), - _ => (), - } - - match op { - "==" => return Ok(Some((x == y).into())), - "!=" => return Ok(Some((x != y).into())), - ">" => return Ok(Some((x > y).into())), - ">=" => return Ok(Some((x >= y).into())), - "<" => return Ok(Some((x < y).into())), - "<=" => return Ok(Some((x <= y).into())), - "&" => return Ok(Some((x & y).into())), - "|" => return Ok(Some((x | y).into())), - "^" => return Ok(Some((x ^ y).into())), - _ => (), - } - } else if args_type == TypeId::of::() { - let x = *x.downcast_ref::().unwrap(); - let y = *y.downcast_ref::().unwrap(); - - match op { - "&" => return Ok(Some((x && y).into())), - "|" => return Ok(Some((x || y).into())), - "^" => return Ok(Some((x ^ y).into())), - "==" => return Ok(Some((x == y).into())), - "!=" => return Ok(Some((x != y).into())), - _ => (), - } - } else if args_type == TypeId::of::() { - let x = x.downcast_ref::().unwrap(); - let y = y.downcast_ref::().unwrap(); - - match op { - "+" => return Ok(Some((x + y).into())), - "==" => return Ok(Some((x == y).into())), - "!=" => return Ok(Some((x != y).into())), - ">" => return Ok(Some((x > y).into())), - ">=" => return Ok(Some((x >= y).into())), - "<" => return Ok(Some((x < y).into())), - "<=" => return Ok(Some((x <= y).into())), - _ => (), - } - } else if args_type == TypeId::of::() { - let x = *x.downcast_ref::().unwrap(); - let y = *y.downcast_ref::().unwrap(); - - match op { - "==" => return Ok(Some((x == y).into())), - "!=" => return Ok(Some((x != y).into())), - ">" => return Ok(Some((x > y).into())), - ">=" => return Ok(Some((x >= y).into())), - "<" => return Ok(Some((x < y).into())), - "<=" => return Ok(Some((x <= y).into())), - _ => (), - } - } else if args_type == TypeId::of::<()>() { - match op { - "==" => return Ok(Some(true.into())), - "!=" | ">" | ">=" | "<" | "<=" => return Ok(Some(false.into())), - _ => (), - } - } - - #[cfg(not(feature = "no_float"))] - if args_type == TypeId::of::() { - let x = *x.downcast_ref::().unwrap(); - let y = *y.downcast_ref::().unwrap(); - - match op { - "+" => return Ok(Some((x + y).into())), - "-" => return Ok(Some((x - y).into())), - "*" => return Ok(Some((x * y).into())), - "/" => return Ok(Some((x / y).into())), - "%" => return Ok(Some((x % y).into())), - "~" => return pow_f_f(x, y).map(Into::into).map(Some), - "==" => return Ok(Some((x == y).into())), - "!=" => return Ok(Some((x != y).into())), - ">" => return Ok(Some((x > y).into())), - ">=" => return Ok(Some((x >= y).into())), - "<" => return Ok(Some((x < y).into())), - "<=" => return Ok(Some((x <= y).into())), - _ => (), - } - } - - Ok(None) -} - -/// Build in common operator assignment implementations to avoid the cost of calling a registered function. -fn run_builtin_op_assignment( - op: &str, - x: &mut Dynamic, - y: &Dynamic, -) -> Result, Box> { - use crate::packages::arithmetic::*; - - let args_type = x.type_id(); - - if y.type_id() != args_type { - return Ok(None); - } - - if args_type == TypeId::of::() { - let x = x.downcast_mut::().unwrap(); - let y = *y.downcast_ref::().unwrap(); - - #[cfg(not(feature = "unchecked"))] - match op { - "+=" => return Ok(Some(*x = add(*x, y)?)), - "-=" => return Ok(Some(*x = sub(*x, y)?)), - "*=" => return Ok(Some(*x = mul(*x, y)?)), - "/=" => return Ok(Some(*x = div(*x, y)?)), - "%=" => return Ok(Some(*x = modulo(*x, y)?)), - "~=" => return Ok(Some(*x = pow_i_i(*x, y)?)), - ">>=" => return Ok(Some(*x = shr(*x, y)?)), - "<<=" => return Ok(Some(*x = shl(*x, y)?)), - _ => (), - } - - #[cfg(feature = "unchecked")] - match op { - "+=" => return Ok(Some(*x += y)), - "-=" => return Ok(Some(*x -= y)), - "*=" => return Ok(Some(*x *= y)), - "/=" => return Ok(Some(*x /= y)), - "%=" => return Ok(Some(*x %= y)), - "~=" => return Ok(Some(*x = pow_i_i_u(*x, y)?)), - ">>=" => return Ok(Some(*x = shr_u(*x, y)?)), - "<<=" => return Ok(Some(*x = shl_u(*x, y)?)), - _ => (), - } - - match op { - "&=" => return Ok(Some(*x &= y)), - "|=" => return Ok(Some(*x |= y)), - "^=" => return Ok(Some(*x ^= y)), - _ => (), - } - } else if args_type == TypeId::of::() { - let x = x.downcast_mut::().unwrap(); - let y = *y.downcast_ref::().unwrap(); - - match op { - "&=" => return Ok(Some(*x = *x && y)), - "|=" => return Ok(Some(*x = *x || y)), - _ => (), - } - } else if args_type == TypeId::of::() { - let x = x.downcast_mut::().unwrap(); - let y = y.downcast_ref::().unwrap(); - - match op { - "+=" => return Ok(Some(*x += y)), - _ => (), - } - } - - #[cfg(not(feature = "no_float"))] - if args_type == TypeId::of::() { - let x = x.downcast_mut::().unwrap(); - let y = *y.downcast_ref::().unwrap(); - - match op { - "+=" => return Ok(Some(*x += y)), - "-=" => return Ok(Some(*x -= y)), - "*=" => return Ok(Some(*x *= y)), - "/=" => return Ok(Some(*x /= y)), - "%=" => return Ok(Some(*x %= y)), - "~=" => return Ok(Some(*x = pow_f_f(*x, y)?)), - _ => (), - } - } - - Ok(None) -} diff --git a/src/fn_args.rs b/src/fn_args.rs new file mode 100644 index 00000000..833014bb --- /dev/null +++ b/src/fn_args.rs @@ -0,0 +1,45 @@ +//! Helper module which defines `FuncArgs` to make function calling easier. + +#![allow(non_snake_case)] + +use crate::any::{Dynamic, Variant}; +use crate::utils::StaticVec; + +/// Trait that represents arguments to a function call. +/// Any data type that can be converted into a `Vec` can be used +/// as arguments to a function call. +pub trait FuncArgs { + /// Convert to a `StaticVec` of the function call arguments. + fn into_vec(self) -> StaticVec; +} + +/// Macro to implement `FuncArgs` for tuples of standard types (each can be +/// converted into `Dynamic`). +macro_rules! impl_args { + ($($p:ident),*) => { + impl<$($p: Variant + Clone),*> FuncArgs for ($($p,)*) + { + fn into_vec(self) -> StaticVec { + let ($($p,)*) = self; + + #[allow(unused_mut)] + let mut v = StaticVec::new(); + $(v.push($p.into_dynamic());)* + + v + } + } + + impl_args!(@pop $($p),*); + }; + (@pop) => { + }; + (@pop $head:ident) => { + impl_args!(); + }; + (@pop $head:ident $(, $tail:ident)+) => { + impl_args!($($tail),*); + }; +} + +impl_args!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V); diff --git a/src/fn_call.rs b/src/fn_call.rs index 833014bb..616fe2f6 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -1,45 +1,1093 @@ -//! Helper module which defines `FuncArgs` to make function calling easier. +//! Implement function-calling mechanism for `Engine`. -#![allow(non_snake_case)] - -use crate::any::{Dynamic, Variant}; +use crate::any::Dynamic; +use crate::calc_fn_hash; +use crate::engine::{ + search_imports, search_namespace, search_scope_only, Engine, Imports, State, Target, FN_GET, + FN_IDX_GET, FN_IDX_SET, FN_SET, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, + KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_PRINT, KEYWORD_TYPE_OF, +}; +use crate::error::ParseErrorType; +use crate::fn_native::{FnCallArgs, FnPtr}; +use crate::module::{Module, ModuleRef}; +use crate::optimize::OptimizationLevel; +use crate::parser::{Expr, ImmutableString, ScriptFnDef, AST, INT}; +use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; +use crate::result::EvalAltResult; +use crate::scope::{EntryType as ScopeEntryType, Scope}; +use crate::token::Position; use crate::utils::StaticVec; -/// Trait that represents arguments to a function call. -/// Any data type that can be converted into a `Vec` can be used -/// as arguments to a function call. -pub trait FuncArgs { - /// Convert to a `StaticVec` of the function call arguments. - fn into_vec(self) -> StaticVec; +#[cfg(not(feature = "no_float"))] +use crate::parser::FLOAT; + +#[cfg(not(feature = "no_object"))] +use crate::engine::Map; + +use crate::stdlib::{ + any::{type_name, TypeId}, + boxed::Box, + convert::TryFrom, + format, + iter::{empty, once}, + mem, + string::ToString, + vec::Vec, +}; + +/// Extract the property name from a getter function name. +fn extract_prop_from_getter(fn_name: &str) -> Option<&str> { + #[cfg(not(feature = "no_object"))] + if fn_name.starts_with(FN_GET) { + return Some(&fn_name[FN_GET.len()..]); + } + + None } -/// Macro to implement `FuncArgs` for tuples of standard types (each can be -/// converted into `Dynamic`). -macro_rules! impl_args { - ($($p:ident),*) => { - impl<$($p: Variant + Clone),*> FuncArgs for ($($p,)*) - { - fn into_vec(self) -> StaticVec { - let ($($p,)*) = self; +/// Extract the property name from a setter function name. +fn extract_prop_from_setter(fn_name: &str) -> Option<&str> { + #[cfg(not(feature = "no_object"))] + if fn_name.starts_with(FN_SET) { + return Some(&fn_name[FN_SET.len()..]); + } - #[allow(unused_mut)] - let mut v = StaticVec::new(); - $(v.push($p.into_dynamic());)* + None +} - v +/// This function replaces the first argument of a method call with a clone copy. +/// This is to prevent a pure function unintentionally consuming the first argument. +fn normalize_first_arg<'a>( + normalize: bool, + this_copy: &mut Dynamic, + old_this_ptr: &mut Option<&'a mut Dynamic>, + args: &mut FnCallArgs<'a>, +) { + // Only do it for method calls with arguments. + if !normalize || args.is_empty() { + return; + } + + // Clone the original value. + *this_copy = args[0].clone(); + + // Replace the first reference with a reference to the clone, force-casting the lifetime. + // Keep the original reference. Must remember to restore it later with `restore_first_arg_of_method_call`. + // + // # Safety + // + // Blindly casting a a reference to another lifetime saves on allocations and string cloning, + // but must be used with the utmost care. + // + // We can do this here because, at the end of this scope, we'd restore the original reference + // with `restore_first_arg_of_method_call`. Therefore this shorter lifetime does not get "out". + let this_pointer = mem::replace(args.get_mut(0).unwrap(), unsafe { + mem::transmute(this_copy) + }); + + *old_this_ptr = Some(this_pointer); +} + +/// This function restores the first argument that was replaced by `normalize_first_arg_of_method_call`. +fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCallArgs<'a>) { + if let Some(this_pointer) = old_this_ptr { + args[0] = this_pointer; + } +} + +impl Engine { + /// Universal method for calling functions either registered with the `Engine` or written in Rhai. + /// Position in `EvalAltResult` is `None` and must be set afterwards. + /// + /// ## WARNING + /// + /// Function call arguments be _consumed_ when the function requires them to be passed by value. + /// All function arguments not in the first position are always passed by value and thus consumed. + /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! + pub(crate) fn call_fn_raw( + &self, + scope: &mut Scope, + mods: &mut Imports, + state: &mut State, + lib: &Module, + fn_name: &str, + (hash_fn, hash_script): (u64, u64), + args: &mut FnCallArgs, + is_ref: bool, + is_method: bool, + def_val: Option, + level: usize, + ) -> Result<(Dynamic, bool), Box> { + self.inc_operations(state)?; + + let native_only = hash_script == 0; + + // Check for stack overflow + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "unchecked"))] + if level > self.max_call_stack_depth { + return Err(Box::new( + EvalAltResult::ErrorStackOverflow(Position::none()), + )); + } + + let mut this_copy: Dynamic = Default::default(); + let mut old_this_ptr: Option<&mut Dynamic> = None; + + // Search for the function + // First search in script-defined functions (can override built-in) + // Then search registered native functions (can override packages) + // Then search packages + // NOTE: We skip script functions for global_module and packages, and native functions for lib + let func = if !native_only { + lib.get_fn(hash_script) //.or_else(|| lib.get_fn(hash_fn)) + } else { + None + } + //.or_else(|| self.global_module.get_fn(hash_script)) + .or_else(|| self.global_module.get_fn(hash_fn)) + //.or_else(|| self.packages.get_fn(hash_script)) + .or_else(|| self.packages.get_fn(hash_fn)); + + if let Some(func) = func { + #[cfg(not(feature = "no_function"))] + let need_normalize = is_ref && (func.is_pure() || (func.is_script() && !is_method)); + #[cfg(feature = "no_function")] + let need_normalize = is_ref && func.is_pure(); + + // Calling pure function but the first argument is a reference? + normalize_first_arg(need_normalize, &mut this_copy, &mut old_this_ptr, args); + + #[cfg(not(feature = "no_function"))] + if func.is_script() { + // Run scripted function + let fn_def = func.get_fn_def(); + + // Method call of script function - map first argument to `this` + return if is_method { + let (first, rest) = args.split_at_mut(1); + Ok(( + self.call_script_fn( + scope, + mods, + state, + lib, + &mut Some(first[0]), + fn_name, + fn_def, + rest, + level, + )?, + false, + )) + } else { + let result = self.call_script_fn( + scope, mods, state, lib, &mut None, fn_name, fn_def, args, level, + )?; + + // Restore the original reference + restore_first_arg(old_this_ptr, args); + + Ok((result, false)) + }; + } + + // Run external function + let result = func.get_native_fn()(self, lib, args)?; + + // Restore the original reference + restore_first_arg(old_this_ptr, args); + + // See if the function match print/debug (which requires special processing) + return Ok(match fn_name { + KEYWORD_PRINT => ( + (self.print)(result.as_str().map_err(|typ| { + Box::new(EvalAltResult::ErrorMismatchOutputType( + self.map_type_name(type_name::()).into(), + typ.into(), + Position::none(), + )) + })?) + .into(), + false, + ), + KEYWORD_DEBUG => ( + (self.debug)(result.as_str().map_err(|typ| { + Box::new(EvalAltResult::ErrorMismatchOutputType( + self.map_type_name(type_name::()).into(), + typ.into(), + Position::none(), + )) + })?) + .into(), + false, + ), + _ => (result, func.is_method()), + }); + } + + // See if it is built in. + if args.len() == 2 { + match run_builtin_binary_op(fn_name, args[0], args[1])? { + Some(v) => return Ok((v, false)), + None => (), } } - impl_args!(@pop $($p),*); - }; - (@pop) => { - }; - (@pop $head:ident) => { - impl_args!(); - }; - (@pop $head:ident $(, $tail:ident)+) => { - impl_args!($($tail),*); - }; + // Return default value (if any) + if let Some(val) = def_val { + return Ok((val.into(), false)); + } + + // Getter function not found? + if let Some(prop) = extract_prop_from_getter(fn_name) { + return Err(Box::new(EvalAltResult::ErrorDotExpr( + format!("- property '{}' unknown or write-only", prop), + Position::none(), + ))); + } + + // Setter function not found? + if let Some(prop) = extract_prop_from_setter(fn_name) { + return Err(Box::new(EvalAltResult::ErrorDotExpr( + format!("- property '{}' unknown or read-only", prop), + Position::none(), + ))); + } + + // index getter function not found? + if fn_name == FN_IDX_GET && args.len() == 2 { + return Err(Box::new(EvalAltResult::ErrorFunctionNotFound( + format!( + "{} [{}]", + self.map_type_name(args[0].type_name()), + self.map_type_name(args[1].type_name()), + ), + Position::none(), + ))); + } + + // index setter function not found? + if fn_name == FN_IDX_SET { + return Err(Box::new(EvalAltResult::ErrorFunctionNotFound( + format!( + "{} [{}]=", + self.map_type_name(args[0].type_name()), + self.map_type_name(args[1].type_name()), + ), + Position::none(), + ))); + } + + // Raise error + Err(Box::new(EvalAltResult::ErrorFunctionNotFound( + format!( + "{} ({})", + fn_name, + args.iter() + .map(|name| if name.is::() { + "&str | ImmutableString | String" + } else { + self.map_type_name((*name).type_name()) + }) + .collect::>() + .join(", ") + ), + Position::none(), + ))) + } + + /// Call a script-defined function. + /// Position in `EvalAltResult` is `None` and must be set afterwards. + /// + /// ## WARNING + /// + /// Function call arguments may be _consumed_ when the function requires them to be passed by value. + /// All function arguments not in the first position are always passed by value and thus consumed. + /// **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, + mods: &mut Imports, + state: &mut State, + lib: &Module, + this_ptr: &mut Option<&mut Dynamic>, + fn_name: &str, + fn_def: &ScriptFnDef, + args: &mut FnCallArgs, + level: usize, + ) -> Result> { + let orig_scope_level = state.scope_level; + state.scope_level += 1; + + let prev_scope_len = scope.len(); + let prev_mods_len = mods.len(); + + // Put arguments into scope as variables + // Actually consume the arguments instead of cloning them + scope.extend( + fn_def + .params + .iter() + .zip(args.iter_mut().map(|v| mem::take(*v))) + .map(|(name, value)| { + let var_name = unsafe_cast_var_name_to_lifetime(name.as_str(), state); + (var_name, ScopeEntryType::Normal, value) + }), + ); + + // Evaluate the function at one higher level of call depth + let result = self + .eval_stmt(scope, mods, state, lib, this_ptr, &fn_def.body, level + 1) + .or_else(|err| match *err { + // Convert return statement to return value + EvalAltResult::Return(x, _) => Ok(x), + EvalAltResult::ErrorInFunctionCall(name, err, _) => { + Err(Box::new(EvalAltResult::ErrorInFunctionCall( + format!("{} > {}", fn_name, name), + err, + Position::none(), + ))) + } + _ => Err(Box::new(EvalAltResult::ErrorInFunctionCall( + fn_name.to_string(), + err, + Position::none(), + ))), + }); + + // Remove all local variables + scope.rewind(prev_scope_len); + mods.truncate(prev_mods_len); + state.scope_level = orig_scope_level; + + result + } + + // Has a system function an override? + fn has_override(&self, lib: &Module, hash_fn: u64, hash_script: u64) -> bool { + // NOTE: We skip script functions for global_module and packages, and native functions for lib + + // First check script-defined functions + lib.contains_fn(hash_script) + //|| lib.contains_fn(hash_fn) + // Then check registered functions + //|| self.global_module.contains_fn(hash_script) + || self.global_module.contains_fn(hash_fn) + // Then check packages + //|| self.packages.contains_fn(hash_script) + || self.packages.contains_fn(hash_fn) + } + + /// Perform an actual function call, taking care of special functions + /// Position in `EvalAltResult` is `None` and must be set afterwards. + /// + /// ## WARNING + /// + /// Function call arguments may be _consumed_ when the function requires them to be passed by value. + /// All function arguments not in the first position are always passed by value and thus consumed. + /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! + pub(crate) fn exec_fn_call( + &self, + state: &mut State, + lib: &Module, + fn_name: &str, + native_only: bool, + hash_script: u64, + args: &mut FnCallArgs, + is_ref: bool, + is_method: bool, + def_val: Option, + level: usize, + ) -> Result<(Dynamic, bool), Box> { + // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. + let arg_types = args.iter().map(|a| a.type_id()); + let hash_fn = calc_fn_hash(empty(), fn_name, args.len(), arg_types); + let hashes = (hash_fn, if native_only { 0 } else { hash_script }); + + match fn_name { + // type_of + KEYWORD_TYPE_OF if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => { + Ok(( + self.map_type_name(args[0].type_name()).to_string().into(), + false, + )) + } + + // Fn + KEYWORD_FN_PTR if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => { + Err(Box::new(EvalAltResult::ErrorRuntime( + "'Fn' should not be called in method style. Try Fn(...);".into(), + Position::none(), + ))) + } + + // eval - reaching this point it must be a method-style call + KEYWORD_EVAL if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => { + Err(Box::new(EvalAltResult::ErrorRuntime( + "'eval' should not be called in method style. Try eval(...);".into(), + Position::none(), + ))) + } + + // Normal function call + _ => { + let mut scope = Scope::new(); + let mut mods = Imports::new(); + self.call_fn_raw( + &mut scope, &mut mods, state, lib, fn_name, hashes, args, is_ref, is_method, + def_val, level, + ) + } + } + } + + /// Evaluate a text string as a script - used primarily for 'eval'. + /// Position in `EvalAltResult` is `None` and must be set afterwards. + fn eval_script_expr( + &self, + scope: &mut Scope, + mods: &mut Imports, + state: &mut State, + lib: &Module, + script: &Dynamic, + ) -> Result> { + let script = script.as_str().map_err(|typ| { + EvalAltResult::ErrorMismatchOutputType( + self.map_type_name(type_name::()).into(), + typ.into(), + Position::none(), + ) + })?; + + // Compile the script text + // No optimizations because we only run it once + let mut ast = self.compile_with_scope_and_optimization_level( + &Scope::new(), + &[script], + OptimizationLevel::None, + )?; + + // If new functions are defined within the eval string, it is an error + if ast.lib().num_fn() != 0 { + return Err(ParseErrorType::WrongFnDefinition.into()); + } + + let statements = mem::take(ast.statements_mut()); + let ast = AST::new(statements, lib.clone()); + + // Evaluate the AST + let (result, operations) = self.eval_ast_with_scope_raw(scope, mods, &ast)?; + + state.operations += operations; + self.inc_operations(state)?; + + return Ok(result); + } + + /// Call a dot method. + pub(crate) fn make_method_call( + &self, + state: &mut State, + lib: &Module, + target: &mut Target, + expr: &Expr, + idx_val: Dynamic, + level: usize, + ) -> Result<(Dynamic, bool), Box> { + let ((name, native, pos), _, hash, _, def_val) = match expr { + Expr::FnCall(x) => x.as_ref(), + _ => unreachable!(), + }; + + let is_ref = target.is_ref(); + let is_value = target.is_value(); + + // Get a reference to the mutation target Dynamic + let obj = target.as_mut(); + let mut idx = idx_val.cast::>(); + let mut fn_name = name.as_ref(); + + let (result, updated) = if fn_name == KEYWORD_FN_PTR_CALL && obj.is::() { + // FnPtr call + let fn_ptr = obj.downcast_ref::().unwrap(); + let mut curry = fn_ptr.curry().iter().cloned().collect::>(); + // Redirect function name + let fn_name = fn_ptr.fn_name(); + // Recalculate hash + let hash = calc_fn_hash(empty(), fn_name, curry.len() + idx.len(), empty()); + // Arguments are passed as-is, adding the curried arguments + let mut arg_values = curry + .iter_mut() + .chain(idx.iter_mut()) + .collect::>(); + let args = arg_values.as_mut(); + + // Map it to name(args) in function-call style + self.exec_fn_call( + state, lib, fn_name, *native, hash, args, false, false, *def_val, level, + ) + } else if fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::() { + // FnPtr call on object + let fn_ptr = idx.remove(0).cast::(); + let mut curry = fn_ptr.curry().iter().cloned().collect::>(); + // Redirect function name + let fn_name = fn_ptr.get_fn_name().clone(); + // Recalculate hash + let hash = calc_fn_hash(empty(), &fn_name, curry.len() + idx.len(), empty()); + // Replace the first argument with the object pointer, adding the curried arguments + let mut arg_values = once(obj) + .chain(curry.iter_mut()) + .chain(idx.iter_mut()) + .collect::>(); + let args = arg_values.as_mut(); + + // Map it to name(args) in function-call style + self.exec_fn_call( + state, lib, &fn_name, *native, hash, args, is_ref, true, *def_val, level, + ) + } else if fn_name == KEYWORD_FN_PTR_CURRY && obj.is::() { + // Curry call + let fn_ptr = obj.downcast_ref::().unwrap(); + Ok(( + FnPtr::new_unchecked( + fn_ptr.get_fn_name().clone(), + fn_ptr + .curry() + .iter() + .cloned() + .chain(idx.into_iter()) + .collect(), + ) + .into(), + false, + )) + } else { + let redirected; + let mut hash = *hash; + + // Check if it is a map method call in OOP style + #[cfg(not(feature = "no_object"))] + if let Some(map) = obj.downcast_ref::() { + if let Some(val) = map.get(fn_name) { + if let Some(f) = val.downcast_ref::() { + // Remap the function name + redirected = f.get_fn_name().clone(); + fn_name = &redirected; + // Recalculate the hash based on the new function name + hash = calc_fn_hash(empty(), fn_name, idx.len(), empty()); + } + } + }; + + // Attached object pointer in front of the arguments + let mut arg_values = once(obj).chain(idx.iter_mut()).collect::>(); + let args = arg_values.as_mut(); + + self.exec_fn_call( + state, lib, fn_name, *native, hash, args, is_ref, true, *def_val, level, + ) + } + .map_err(|err| err.new_position(*pos))?; + + // Feed the changed temp value back + if updated && !is_ref && !is_value { + let new_val = target.as_mut().clone(); + target.set_value(new_val)?; + } + + Ok((result, updated)) + } + + /// Call a function in normal function-call style. + /// Position in `EvalAltResult` is `None` and must be set afterwards. + pub(crate) fn make_function_call( + &self, + scope: &mut Scope, + mods: &mut Imports, + state: &mut State, + lib: &Module, + this_ptr: &mut Option<&mut Dynamic>, + name: &str, + args_expr: &[Expr], + def_val: Option, + mut hash: u64, + native: bool, + level: usize, + ) -> Result> { + // Handle Fn() + if name == KEYWORD_FN_PTR && args_expr.len() == 1 { + let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); + + if !self.has_override(lib, hash_fn, hash) { + // Fn - only in function call style + let expr = args_expr.get(0).unwrap(); + let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + + return arg_value + .take_immutable_string() + .map_err(|typ| { + Box::new(EvalAltResult::ErrorMismatchOutputType( + self.map_type_name(type_name::()).into(), + typ.into(), + expr.position(), + )) + }) + .and_then(|s| FnPtr::try_from(s)) + .map(Into::::into); + } + } + + // Handle curry() + if name == KEYWORD_FN_PTR_CURRY && args_expr.len() > 1 { + let expr = args_expr.get(0).unwrap(); + let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + + if !fn_ptr.is::() { + return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( + self.map_type_name(type_name::()).into(), + self.map_type_name(fn_ptr.type_name()).into(), + expr.position(), + ))); + } + + let (fn_name, fn_curry) = fn_ptr.cast::().take_data(); + + let curry: StaticVec<_> = args_expr + .iter() + .skip(1) + .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) + .collect::>()?; + + return Ok(FnPtr::new_unchecked( + fn_name, + fn_curry.into_iter().chain(curry.into_iter()).collect(), + ) + .into()); + } + + // Handle eval() + if name == KEYWORD_EVAL && args_expr.len() == 1 { + let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); + + if !self.has_override(lib, hash_fn, hash) { + // eval - only in function call style + let prev_len = scope.len(); + let expr = args_expr.get(0).unwrap(); + let script = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + let result = self + .eval_script_expr(scope, mods, state, lib, &script) + .map_err(|err| err.new_position(expr.position())); + + if scope.len() != prev_len { + // IMPORTANT! If the eval defines new variables in the current scope, + // all variable offsets from this point on will be mis-aligned. + state.always_search = true; + } + + return result; + } + } + + // Handle call() - Redirect function call + let redirected; + let mut args_expr = args_expr.as_ref(); + let mut curry: StaticVec<_> = Default::default(); + let mut name = name; + + if name == KEYWORD_FN_PTR_CALL && args_expr.len() >= 1 && !self.has_override(lib, 0, hash) { + let expr = args_expr.get(0).unwrap(); + let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + + if fn_name.is::() { + let fn_ptr = fn_name.cast::(); + curry = fn_ptr.curry().iter().cloned().collect(); + // Redirect function name + redirected = fn_ptr.take_data().0; + name = &redirected; + // Skip the first argument + args_expr = &args_expr.as_ref()[1..]; + // Recalculate hash + hash = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty()); + } else { + return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( + self.map_type_name(type_name::()).into(), + fn_name.type_name().into(), + expr.position(), + ))); + } + } + + // Normal function call - except for Fn and eval (handled above) + let mut arg_values: StaticVec<_>; + let mut args: StaticVec<_>; + let mut is_ref = false; + + if args_expr.is_empty() && curry.is_empty() { + // No arguments + args = Default::default(); + } else { + // See if the first argument is a variable, if so, convert to method-call style + // in order to leverage potential &mut first argument and avoid cloning the value + match args_expr.get(0).unwrap() { + // func(x, ...) -> x.func(...) + lhs @ Expr::Variable(_) => { + arg_values = args_expr + .iter() + .skip(1) + .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) + .collect::>()?; + + let (target, _, _, pos) = search_namespace(scope, mods, state, this_ptr, lhs)?; + + self.inc_operations(state) + .map_err(|err| err.new_position(pos))?; + + args = once(target) + .chain(curry.iter_mut()) + .chain(arg_values.iter_mut()) + .collect(); + + is_ref = true; + } + // func(..., ...) + _ => { + arg_values = args_expr + .iter() + .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) + .collect::>()?; + + args = curry.iter_mut().chain(arg_values.iter_mut()).collect(); + } + } + } + + let args = args.as_mut(); + self.exec_fn_call( + state, lib, name, native, hash, args, is_ref, false, def_val, level, + ) + .map(|(v, _)| v) + } + + /// Call a module-qualified function in normal function-call style. + /// Position in `EvalAltResult` is `None` and must be set afterwards. + pub(crate) fn make_qualified_function_call( + &self, + scope: &mut Scope, + mods: &mut Imports, + state: &mut State, + lib: &Module, + this_ptr: &mut Option<&mut Dynamic>, + modules: &Option>, + name: &str, + args_expr: &[Expr], + def_val: Option, + hash_script: u64, + level: usize, + ) -> Result> { + let modules = modules.as_ref().unwrap(); + + let mut arg_values: StaticVec<_>; + let mut args: StaticVec<_>; + + if args_expr.is_empty() { + // No arguments + args = Default::default(); + } else { + // See if the first argument is a variable (not module-qualified). + // If so, convert to method-call style in order to leverage potential + // &mut first argument and avoid cloning the value + match args_expr.get(0).unwrap() { + // func(x, ...) -> x.func(...) + Expr::Variable(x) if x.1.is_none() => { + arg_values = args_expr + .iter() + .skip(1) + .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) + .collect::>()?; + + let (target, _, _, pos) = + search_scope_only(scope, state, this_ptr, args_expr.get(0).unwrap())?; + + self.inc_operations(state) + .map_err(|err| err.new_position(pos))?; + + args = once(target).chain(arg_values.iter_mut()).collect(); + } + // func(..., ...) or func(mod::x, ...) + _ => { + arg_values = args_expr + .iter() + .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) + .collect::>()?; + + args = arg_values.iter_mut().collect(); + } + } + } + + let module = search_imports(mods, state, modules)?; + + // First search in script-defined functions (can override built-in) + let func = match module.get_qualified_fn(hash_script) { + Err(err) if matches!(*err, EvalAltResult::ErrorFunctionNotFound(_, _)) => { + // Then search in Rust functions + self.inc_operations(state)?; + + // Qualified Rust functions are indexed in two steps: + // 1) Calculate a hash in a similar manner to script-defined functions, + // i.e. qualifiers + function name + number of arguments. + // 2) Calculate a second hash with no qualifiers, empty function name, + // zero number of arguments, and the actual list of argument `TypeId`'.s + let hash_fn_args = calc_fn_hash(empty(), "", 0, args.iter().map(|a| a.type_id())); + // 3) The final hash is the XOR of the two hashes. + let hash_qualified_fn = hash_script ^ hash_fn_args; + + module.get_qualified_fn(hash_qualified_fn) + } + r => r, + }; + + match func { + #[cfg(not(feature = "no_function"))] + Ok(f) if f.is_script() => { + let args = args.as_mut(); + let fn_def = f.get_fn_def(); + let mut scope = Scope::new(); + let mut mods = Imports::new(); + self.call_script_fn( + &mut scope, &mut mods, state, lib, &mut None, name, fn_def, args, level, + ) + } + Ok(f) => f.get_native_fn()(self, lib, args.as_mut()), + Err(err) => match *err { + EvalAltResult::ErrorFunctionNotFound(_, _) if def_val.is_some() => { + Ok(def_val.unwrap().into()) + } + EvalAltResult::ErrorFunctionNotFound(_, _) => { + Err(Box::new(EvalAltResult::ErrorFunctionNotFound( + format!("{}{}", modules, name), + Position::none(), + ))) + } + _ => Err(err), + }, + } + } } -impl_args!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V); +/// Build in common binary operator implementations to avoid the cost of calling a registered function. +pub fn run_builtin_binary_op( + op: &str, + x: &Dynamic, + y: &Dynamic, +) -> Result, Box> { + use crate::packages::arithmetic::*; + + let args_type = x.type_id(); + + if y.type_id() != args_type { + return Ok(None); + } + + if args_type == TypeId::of::() { + let x = *x.downcast_ref::().unwrap(); + let y = *y.downcast_ref::().unwrap(); + + #[cfg(not(feature = "unchecked"))] + match op { + "+" => return add(x, y).map(Into::into).map(Some), + "-" => return sub(x, y).map(Into::into).map(Some), + "*" => return mul(x, y).map(Into::into).map(Some), + "/" => return div(x, y).map(Into::into).map(Some), + "%" => return modulo(x, y).map(Into::into).map(Some), + "~" => return pow_i_i(x, y).map(Into::into).map(Some), + ">>" => return shr(x, y).map(Into::into).map(Some), + "<<" => return shl(x, y).map(Into::into).map(Some), + _ => (), + } + + #[cfg(feature = "unchecked")] + match op { + "+" => return Ok(Some((x + y).into())), + "-" => return Ok(Some((x - y).into())), + "*" => return Ok(Some((x * y).into())), + "/" => return Ok(Some((x / y).into())), + "%" => return Ok(Some((x % y).into())), + "~" => return pow_i_i_u(x, y).map(Into::into).map(Some), + ">>" => return shr_u(x, y).map(Into::into).map(Some), + "<<" => return shl_u(x, y).map(Into::into).map(Some), + _ => (), + } + + match op { + "==" => return Ok(Some((x == y).into())), + "!=" => return Ok(Some((x != y).into())), + ">" => return Ok(Some((x > y).into())), + ">=" => return Ok(Some((x >= y).into())), + "<" => return Ok(Some((x < y).into())), + "<=" => return Ok(Some((x <= y).into())), + "&" => return Ok(Some((x & y).into())), + "|" => return Ok(Some((x | y).into())), + "^" => return Ok(Some((x ^ y).into())), + _ => (), + } + } else if args_type == TypeId::of::() { + let x = *x.downcast_ref::().unwrap(); + let y = *y.downcast_ref::().unwrap(); + + match op { + "&" => return Ok(Some((x && y).into())), + "|" => return Ok(Some((x || y).into())), + "^" => return Ok(Some((x ^ y).into())), + "==" => return Ok(Some((x == y).into())), + "!=" => return Ok(Some((x != y).into())), + _ => (), + } + } else if args_type == TypeId::of::() { + let x = x.downcast_ref::().unwrap(); + let y = y.downcast_ref::().unwrap(); + + match op { + "+" => return Ok(Some((x + y).into())), + "==" => return Ok(Some((x == y).into())), + "!=" => return Ok(Some((x != y).into())), + ">" => return Ok(Some((x > y).into())), + ">=" => return Ok(Some((x >= y).into())), + "<" => return Ok(Some((x < y).into())), + "<=" => return Ok(Some((x <= y).into())), + _ => (), + } + } else if args_type == TypeId::of::() { + let x = *x.downcast_ref::().unwrap(); + let y = *y.downcast_ref::().unwrap(); + + match op { + "==" => return Ok(Some((x == y).into())), + "!=" => return Ok(Some((x != y).into())), + ">" => return Ok(Some((x > y).into())), + ">=" => return Ok(Some((x >= y).into())), + "<" => return Ok(Some((x < y).into())), + "<=" => return Ok(Some((x <= y).into())), + _ => (), + } + } else if args_type == TypeId::of::<()>() { + match op { + "==" => return Ok(Some(true.into())), + "!=" | ">" | ">=" | "<" | "<=" => return Ok(Some(false.into())), + _ => (), + } + } + + #[cfg(not(feature = "no_float"))] + if args_type == TypeId::of::() { + let x = *x.downcast_ref::().unwrap(); + let y = *y.downcast_ref::().unwrap(); + + match op { + "+" => return Ok(Some((x + y).into())), + "-" => return Ok(Some((x - y).into())), + "*" => return Ok(Some((x * y).into())), + "/" => return Ok(Some((x / y).into())), + "%" => return Ok(Some((x % y).into())), + "~" => return pow_f_f(x, y).map(Into::into).map(Some), + "==" => return Ok(Some((x == y).into())), + "!=" => return Ok(Some((x != y).into())), + ">" => return Ok(Some((x > y).into())), + ">=" => return Ok(Some((x >= y).into())), + "<" => return Ok(Some((x < y).into())), + "<=" => return Ok(Some((x <= y).into())), + _ => (), + } + } + + Ok(None) +} + +/// Build in common operator assignment implementations to avoid the cost of calling a registered function. +pub fn run_builtin_op_assignment( + op: &str, + x: &mut Dynamic, + y: &Dynamic, +) -> Result, Box> { + use crate::packages::arithmetic::*; + + let args_type = x.type_id(); + + if y.type_id() != args_type { + return Ok(None); + } + + if args_type == TypeId::of::() { + let x = x.downcast_mut::().unwrap(); + let y = *y.downcast_ref::().unwrap(); + + #[cfg(not(feature = "unchecked"))] + match op { + "+=" => return Ok(Some(*x = add(*x, y)?)), + "-=" => return Ok(Some(*x = sub(*x, y)?)), + "*=" => return Ok(Some(*x = mul(*x, y)?)), + "/=" => return Ok(Some(*x = div(*x, y)?)), + "%=" => return Ok(Some(*x = modulo(*x, y)?)), + "~=" => return Ok(Some(*x = pow_i_i(*x, y)?)), + ">>=" => return Ok(Some(*x = shr(*x, y)?)), + "<<=" => return Ok(Some(*x = shl(*x, y)?)), + _ => (), + } + + #[cfg(feature = "unchecked")] + match op { + "+=" => return Ok(Some(*x += y)), + "-=" => return Ok(Some(*x -= y)), + "*=" => return Ok(Some(*x *= y)), + "/=" => return Ok(Some(*x /= y)), + "%=" => return Ok(Some(*x %= y)), + "~=" => return Ok(Some(*x = pow_i_i_u(*x, y)?)), + ">>=" => return Ok(Some(*x = shr_u(*x, y)?)), + "<<=" => return Ok(Some(*x = shl_u(*x, y)?)), + _ => (), + } + + match op { + "&=" => return Ok(Some(*x &= y)), + "|=" => return Ok(Some(*x |= y)), + "^=" => return Ok(Some(*x ^= y)), + _ => (), + } + } else if args_type == TypeId::of::() { + let x = x.downcast_mut::().unwrap(); + let y = *y.downcast_ref::().unwrap(); + + match op { + "&=" => return Ok(Some(*x = *x && y)), + "|=" => return Ok(Some(*x = *x || y)), + _ => (), + } + } else if args_type == TypeId::of::() { + let x = x.downcast_mut::().unwrap(); + let y = y.downcast_ref::().unwrap(); + + match op { + "+=" => return Ok(Some(*x += y)), + _ => (), + } + } + + #[cfg(not(feature = "no_float"))] + if args_type == TypeId::of::() { + let x = x.downcast_mut::().unwrap(); + let y = *y.downcast_ref::().unwrap(); + + match op { + "+=" => return Ok(Some(*x += y)), + "-=" => return Ok(Some(*x -= y)), + "*=" => return Ok(Some(*x *= y)), + "/=" => return Ok(Some(*x /= y)), + "%=" => return Ok(Some(*x %= y)), + "~=" => return Ok(Some(*x = pow_f_f(*x, y)?)), + _ => (), + } + } + + Ok(None) +} diff --git a/src/fn_func.rs b/src/fn_func.rs index bbbab4aa..5eaee7f1 100644 --- a/src/fn_func.rs +++ b/src/fn_func.rs @@ -1,4 +1,5 @@ //! Module which defines the function registration mechanism. + #![cfg(not(feature = "no_function"))] #![allow(non_snake_case)] diff --git a/src/fn_native.rs b/src/fn_native.rs index f9276017..f4de5982 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -1,4 +1,5 @@ -//! Module containing interfaces with native-Rust functions. +//! Module defining interfaces to native-Rust functions. + use crate::any::Dynamic; use crate::engine::Engine; use crate::module::{FuncReturn, Module}; diff --git a/src/fn_register.rs b/src/fn_register.rs index 4192140a..28ee084a 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -1,4 +1,5 @@ //! Module which defines the function registration mechanism. + #![allow(non_snake_case)] use crate::any::{Dynamic, Variant}; diff --git a/src/lib.rs b/src/lib.rs index fb25ff4d..87f60cf3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,6 +77,7 @@ mod any; mod api; mod engine; mod error; +mod fn_args; mod fn_call; mod fn_func; mod fn_native; diff --git a/src/optimize.rs b/src/optimize.rs index d47188a1..eaa8aeb3 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1,3 +1,5 @@ +//! Module implementing the AST optimizer. + use crate::any::Dynamic; use crate::calc_fn_hash; use crate::engine::{ diff --git a/src/settings.rs b/src/settings.rs index e7e4fd16..60c1a22a 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,3 +1,5 @@ +//! Configuration settings for `Engine`. + use crate::engine::Engine; use crate::module::ModuleResolver; use crate::optimize::OptimizationLevel; diff --git a/src/syntax.rs b/src/syntax.rs index fd971f1f..485e99ea 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -1,4 +1,5 @@ -//! Module containing implementation for custom syntax. +//! Module implementing custom syntax for `Engine`. + use crate::any::Dynamic; use crate::engine::{Engine, Imports, State, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; use crate::error::{LexError, ParseError}; @@ -188,4 +189,26 @@ impl Engine { Ok(self) } + + /// Evaluate an expression tree. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. It evaluates an expression from an AST. + pub fn eval_expression_tree( + &self, + context: &mut EvalContext, + scope: &mut Scope, + expr: &Expression, + ) -> Result> { + self.eval_expr( + scope, + context.mods, + context.state, + context.lib, + context.this_ptr, + expr.expr(), + context.level, + ) + } } diff --git a/src/unsafe.rs b/src/unsafe.rs index 86c571f1..da766950 100644 --- a/src/unsafe.rs +++ b/src/unsafe.rs @@ -1,4 +1,4 @@ -//! A module containing all unsafe code. +//! A helper module containing unsafe utility functions. use crate::any::Variant; use crate::engine::State; diff --git a/src/utils.rs b/src/utils.rs index 845996c8..a6eff859 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -15,7 +15,7 @@ use crate::stdlib::{ iter::FromIterator, mem, mem::MaybeUninit, - ops::{Add, AddAssign, Deref, Drop, Index, IndexMut}, + ops::{Add, AddAssign, Deref, DerefMut, Drop, Index, IndexMut}, str::FromStr, string::{String, ToString}, vec::Vec, @@ -572,6 +572,19 @@ impl AsMut<[T]> for StaticVec { } } +impl Deref for StaticVec { + type Target = [T]; + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} + +impl DerefMut for StaticVec { + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut() + } +} + impl Index for StaticVec { type Output = T;