diff --git a/CHANGELOG.md b/CHANGELOG.md index cb8861ff..c18bde75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Bug fixes * Integer numbers that are too large to deserialize into `INT` now fall back to `Decimal` or `FLOAT` instead of silently truncating. * Parsing deeply-nested closures (e.g. `||{||{||{||{||{||{||{...}}}}}}}`) no longer panics but will be confined to the nesting limit. +* Closures containing a single expression are now allowed in `Engine::eval_expression` etc. Breaking API changes -------------------- @@ -31,6 +32,12 @@ Speed improvements Net features ------------ +### First class functions (sort of) + +* A function pointer created via a closure definition now links to the particular anonymous function itself. +* This avoids a potentially expensive function lookup when the function pointer is called, speeding up closures. +* It does _not_, however, allow the function pointer to be `export`ed as a constant from a script module because the closure may cross-call other functions defined in the module and the function pointer won't keep the fully encapsulated environment. + ### `!in` * A new operator `!in` is added which maps to `!(... in ...)`. @@ -53,6 +60,7 @@ Enhancements * Line-style doc-comments are now merged into a single string to avoid creating many strings. Block-style doc-comments continue to be independent strings. * Block-style doc-comments are now "un-indented" for better formatting. * Doc-comments on plugin modules are now captured in the module's `doc` field. +* Expression nesting levels is refined such that it grows less excessively for common patterns. Version 1.11.0 diff --git a/codegen/ui_tests/rhai_fn_duplicate_attr.stderr b/codegen/ui_tests/rhai_fn_duplicate_attr.stderr index 983e5d1d..8ae8d48d 100644 --- a/codegen/ui_tests/rhai_fn_duplicate_attr.stderr +++ b/codegen/ui_tests/rhai_fn_duplicate_attr.stderr @@ -4,14 +4,14 @@ error: duplicated attribute 'rhai_fn' 6 | #[rhai_fn(pure)] | ^^^^^^^^^^^^^^^^ -error[E0433]: failed to resolve: use of undeclared crate or module `test_module` - --> ui_tests/rhai_fn_duplicate_attr.rs:13:8 - | -13 | if test_module::test_fn(n) { - | ^^^^^^^^^^^ use of undeclared crate or module `test_module` - error[E0425]: cannot find value `n` in this scope --> ui_tests/rhai_fn_duplicate_attr.rs:13:29 | 13 | if test_module::test_fn(n) { | ^ not found in this scope + +error[E0433]: failed to resolve: use of undeclared crate or module `test_module` + --> ui_tests/rhai_fn_duplicate_attr.rs:13:8 + | +13 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared crate or module `test_module` diff --git a/src/api/call_fn.rs b/src/api/call_fn.rs index 7d248e30..5c48712f 100644 --- a/src/api/call_fn.rs +++ b/src/api/call_fn.rs @@ -18,9 +18,9 @@ use std::{ #[non_exhaustive] #[must_use] pub struct CallFnOptions<'t> { - /// A value for binding to the `this` pointer (if any). + /// A value for binding to the `this` pointer (if any). Default [`None`]. pub this_ptr: Option<&'t mut Dynamic>, - /// The custom state of this evaluation run (if any), overrides [`Engine::default_tag`]. + /// The custom state of this evaluation run (if any), overrides [`Engine::default_tag`]. Default [`None`]. pub tag: Option, /// Evaluate the [`AST`] to load necessary modules before calling the function? Default `true`. pub eval_ast: bool, @@ -171,27 +171,28 @@ impl Engine { let mut arg_values = StaticVec::new_const(); args.parse(&mut arg_values); - let result = self._call_fn( - options, + self._call_fn( scope, &mut GlobalRuntimeState::new(self), &mut Caches::new(), ast, name.as_ref(), arg_values.as_mut(), - )?; + options, + ) + .and_then(|result| { + // Bail out early if the return type needs no cast + if TypeId::of::() == TypeId::of::() { + return Ok(reify! { result => T }); + } - // Bail out early if the return type needs no cast - if TypeId::of::() == TypeId::of::() { - return Ok(reify!(result => T)); - } + // Cast return type + let typ = self.map_type_name(result.type_name()); - // Cast return type - let typ = self.map_type_name(result.type_name()); - - result.try_cast().ok_or_else(|| { - let t = self.map_type_name(type_name::()).into(); - ERR::ErrorMismatchOutputType(t, typ.into(), Position::NONE).into() + result.try_cast().ok_or_else(|| { + let t = self.map_type_name(type_name::()).into(); + ERR::ErrorMismatchOutputType(t, typ.into(), Position::NONE).into() + }) }) } /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments. @@ -206,13 +207,13 @@ impl Engine { #[inline(always)] pub(crate) fn _call_fn( &self, - options: CallFnOptions, scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, ast: &AST, name: &str, arg_values: &mut [Dynamic], + options: CallFnOptions, ) -> RhaiResult { let statements = ast.statements(); diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index faf9af17..87c793e6 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -130,16 +130,16 @@ impl Expression<'_> { pub fn get_literal_value(&self) -> Option { // Coded this way in order to maximally leverage potentials for dead-code removal. match self.0 { - Expr::IntegerConstant(x, ..) => reify!(*x => Option), + Expr::IntegerConstant(x, ..) => reify! { *x => Option }, #[cfg(not(feature = "no_float"))] - Expr::FloatConstant(x, ..) => reify!(*x => Option), + Expr::FloatConstant(x, ..) => reify! { *x => Option }, - Expr::CharConstant(x, ..) => reify!(*x => Option), - Expr::StringConstant(x, ..) => reify!(x.clone() => Option), - Expr::Variable(x, ..) => reify!(x.3.clone() => Option), - Expr::BoolConstant(x, ..) => reify!(*x => Option), - Expr::Unit(..) => reify!(() => Option), + Expr::CharConstant(x, ..) => reify! { *x => Option }, + Expr::StringConstant(x, ..) => reify! { x.clone() => Option }, + Expr::Variable(x, ..) => reify! { x.3.clone() => Option }, + Expr::BoolConstant(x, ..) => reify! { *x => Option }, + Expr::Unit(..) => reify! { () => Option }, _ => None, } diff --git a/src/api/deprecated.rs b/src/api/deprecated.rs index 4beaca5b..72cfe1ad 100644 --- a/src/api/deprecated.rs +++ b/src/api/deprecated.rs @@ -167,13 +167,13 @@ impl Engine { }; self._call_fn( - options, scope, &mut crate::eval::GlobalRuntimeState::new(self), &mut crate::eval::Caches::new(), ast, name.as_ref(), arg_values.as_mut(), + options, ) } /// Register a custom fallible function with the [`Engine`]. diff --git a/src/api/eval.rs b/src/api/eval.rs index 9dbfb17f..7955d1c0 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -200,7 +200,7 @@ impl Engine { // Bail out early if the return type needs no cast if TypeId::of::() == TypeId::of::() { - return Ok(reify!(result => T)); + return Ok(reify! { result => T }); } let typ = self.map_type_name(result.type_name()); @@ -233,34 +233,29 @@ impl Engine { ast.resolver().cloned(), ); - let statements = ast.statements(); + auto_restore!(global => move |g| { + #[cfg(not(feature = "no_module"))] + { + g.embedded_module_resolver = orig_embedded_module_resolver; + } - if statements.is_empty() { - return Ok(Dynamic::UNIT); - } + #[cfg(not(feature = "no_function"))] + g.lib.truncate(orig_lib_len); - let result = self.eval_global_statements(global, caches, scope, statements); + g.source = orig_source; + }); - #[cfg(feature = "debugging")] - if self.is_debugger_registered() { - global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate; - let mut this = Dynamic::NULL; - let node = &crate::ast::Stmt::Noop(Position::NONE); - - self.run_debugger(global, caches, scope, &mut this, node)?; - } - - #[cfg(not(feature = "no_module"))] - { - global.embedded_module_resolver = orig_embedded_module_resolver; - } - - #[cfg(not(feature = "no_function"))] - global.lib.truncate(orig_lib_len); - - global.source = orig_source; - - result + self.eval_global_statements(global, caches, scope, ast.statements()) + .and_then(|r| { + #[cfg(feature = "debugging")] + if self.is_debugger_registered() { + global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate; + let mut this_ptr = Dynamic::NULL; + let node = &crate::ast::Stmt::Noop(Position::NONE); + self.run_debugger(global, caches, scope, &mut this_ptr, node)?; + } + Ok(r) + }) } } diff --git a/src/api/events.rs b/src/api/events.rs index f6bade76..09b9ce64 100644 --- a/src/api/events.rs +++ b/src/api/events.rs @@ -6,6 +6,7 @@ use crate::{Dynamic, Engine, EvalContext, Position, RhaiResultOf}; use std::prelude::v1::*; /// Information on a variable definition. +#[derive(Debug, Hash)] #[non_exhaustive] pub struct VarDefInfo<'a> { /// Name of the variable to be defined. diff --git a/src/api/options.rs b/src/api/options.rs index d5aeef90..325dbbcb 100644 --- a/src/api/options.rs +++ b/src/api/options.rs @@ -22,7 +22,7 @@ bitflags! { /// Is looping allowed? const LOOPING = 0b_0000_0010_0000; /// Is variables shadowing allowed? - const SHADOW = 0b_0000_0100_0000; + const SHADOWING = 0b_0000_0100_0000; /// Strict variables mode? const STRICT_VAR = 0b_0000_1000_0000; /// Raise error if an object map property does not exist? @@ -43,7 +43,7 @@ impl LangOptions { | Self::SWITCH_EXPR | Self::STMT_EXPR | Self::LOOPING - | Self::SHADOW + | Self::SHADOWING | Self::FAST_OPS | { #[cfg(not(feature = "no_function"))] @@ -148,12 +148,12 @@ impl Engine { #[inline(always)] #[must_use] pub const fn allow_shadowing(&self) -> bool { - self.options.contains(LangOptions::SHADOW) + self.options.contains(LangOptions::SHADOWING) } /// Set whether variables shadowing is allowed. #[inline(always)] pub fn set_allow_shadowing(&mut self, enable: bool) -> &mut Self { - self.options.set(LangOptions::SHADOW, enable); + self.options.set(LangOptions::SHADOWING, enable); self } /// Is strict variables mode enabled? diff --git a/src/api/run.rs b/src/api/run.rs index fd20d4ef..4ed9caf5 100644 --- a/src/api/run.rs +++ b/src/api/run.rs @@ -126,24 +126,17 @@ impl Engine { global.embedded_module_resolver = ast.resolver().cloned(); } - let statements = ast.statements(); - - let result = if !statements.is_empty() { - self.eval_global_statements(global, caches, scope, statements) - .map(|_| ()) - } else { - Ok(()) - }; - - #[cfg(feature = "debugging")] - if self.is_debugger_registered() { - global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate; - let mut this = crate::Dynamic::NULL; - let node = &crate::ast::Stmt::Noop(crate::Position::NONE); - self.run_debugger(global, caches, scope, &mut this, node)?; - } - - result + self.eval_global_statements(global, caches, scope, ast.statements()) + .and_then(|_| { + #[cfg(feature = "debugging")] + if self.is_debugger_registered() { + global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate; + let mut this_ptr = crate::Dynamic::NULL; + let node = &crate::ast::Stmt::Noop(crate::Position::NONE); + self.run_debugger(global, caches, scope, &mut this_ptr, node)?; + } + Ok(()) + }) } } diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index d2008802..82b6551a 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -2,7 +2,7 @@ #![cfg(any(not(feature = "no_index"), not(feature = "no_object")))] use super::{Caches, GlobalRuntimeState, Target}; -use crate::ast::{ASTFlags, Expr, OpAssignment}; +use crate::ast::{ASTFlags, BinaryExpr, Expr, OpAssignment}; use crate::config::hashing::SusLock; use crate::engine::{FN_IDX_GET, FN_IDX_SET}; use crate::tokenizer::NO_TOKEN; @@ -346,7 +346,7 @@ impl Engine { ) -> RhaiResult { let chain_type = ChainType::from(expr); - let crate::ast::BinaryExpr { lhs, rhs } = match expr { + let BinaryExpr { lhs, rhs } = match expr { #[cfg(not(feature = "no_index"))] Expr::Index(x, ..) => &**x, #[cfg(not(feature = "no_object"))] @@ -378,11 +378,9 @@ impl Engine { idx_values.push(rhs.get_literal_value().unwrap()) } // All other patterns - evaluate the arguments chain - _ => { - self.eval_dot_index_chain_arguments( - global, caches, scope, this_ptr, expr, rhs, idx_values, - )?; - } + _ => self.eval_dot_index_chain_arguments( + global, caches, scope, this_ptr, expr, rhs, idx_values, + )?, } match lhs { @@ -392,13 +390,19 @@ impl Engine { self.run_debugger(global, caches, scope, this_ptr, lhs)?; self.track_operation(global, *var_pos)?; - let mut target = self.search_namespace(global, caches, scope, this_ptr, lhs)?; - - let obj_ptr = &mut target; - let mut this = Dynamic::NULL; + let target = &mut self.search_namespace(global, caches, scope, this_ptr, lhs)?; + let mut this_ptr = Dynamic::NULL; self.eval_dot_index_chain_raw( - global, caches, &mut this, lhs, expr, obj_ptr, rhs, idx_values, new_val, + global, + caches, + &mut this_ptr, + lhs, + expr, + target, + rhs, + idx_values, + new_val, ) } // {expr}.??? = ??? or {expr}[???] = ??? @@ -456,7 +460,7 @@ impl Engine { Expr::Index(x, ..) | Expr::Dot(x, ..) if !parent.options().contains(ASTFlags::BREAK) => { - let crate::ast::BinaryExpr { lhs, rhs, .. } = &**x; + let BinaryExpr { lhs, rhs, .. } = &**x; let mut _arg_values = FnArgsVec::new_const(); @@ -684,17 +688,14 @@ impl Engine { let reset = self.run_debugger_with_reset(global, caches, scope, this_ptr, rhs)?; #[cfg(feature = "debugging")] - auto_restore!(global if reset.is_some() => move |g| g.debugger_mut().reset_status(reset)); + auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset)); let crate::ast::FnCallExpr { name, hashes, args, .. } = &**x; // Truncate the index values upon exit - auto_restore! { - idx_values => truncate; - let offset = idx_values.len() - args.len(); - } + auto_restore! { idx_values => truncate; let offset = idx_values.len() - args.len(); } let call_args = &mut idx_values[offset..]; let arg1_pos = args.get(0).map_or(Position::NONE, Expr::position); @@ -856,17 +857,14 @@ impl Engine { global, caches, scope, this_ptr, _node, )?; #[cfg(feature = "debugging")] - auto_restore!(global if reset.is_some() => move |g| g.debugger_mut().reset_status(reset)); + auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset)); let crate::ast::FnCallExpr { name, hashes, args, .. } = &**x; // Truncate the index values upon exit - auto_restore! { - idx_values => truncate; - let offset = idx_values.len() - args.len(); - } + auto_restore! { idx_values => truncate; let offset = idx_values.len() - args.len(); } let call_args = &mut idx_values[offset..]; let arg1_pos = args.get(0).map_or(Position::NONE, Expr::position); @@ -977,17 +975,14 @@ impl Engine { global, caches, scope, this_ptr, _node, )?; #[cfg(feature = "debugging")] - auto_restore!(global if reset.is_some() => move |g| g.debugger_mut().reset_status(reset)); + auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset)); let crate::ast::FnCallExpr { name, hashes, args, .. } = &**f; // Truncate the index values upon exit - auto_restore! { - idx_values => truncate; - let offset = idx_values.len() - args.len(); - } + auto_restore! { idx_values => truncate; let offset = idx_values.len() - args.len(); } let call_args = &mut idx_values[offset..]; let pos1 = args.get(0).map_or(Position::NONE, Expr::position); diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index 62c6fde7..f1ffb39a 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -314,14 +314,12 @@ impl Debugger { None } } - /// Override the status of this [`Debugger`] if it is [`Some`] the current status is + /// Override the status of this [`Debugger`] if the current status is /// [`CONTINUE`][DebuggerStatus::CONTINUE]. #[inline(always)] - pub(crate) fn reset_status(&mut self, status: Option) { + pub(crate) fn reset_status(&mut self, status: DebuggerStatus) { if self.status == DebuggerStatus::CONTINUE { - if let Some(cmd) = status { - self.status = cmd; - } + self.status = status; } } /// Returns the first break-point triggered by a particular [`AST` Node][ASTNode]. diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 9f104691..d6981151 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -152,20 +152,21 @@ impl Engine { _ if global.always_search_scope => 0, Expr::Variable(_, Some(i), ..) => i.get() as usize, - // Scripted function with the same name - #[cfg(not(feature = "no_function"))] - Expr::Variable(v, None, ..) - if global - .lib - .iter() - .flat_map(|m| m.iter_script_fn()) - .any(|(_, _, f, ..)| f == v.3.as_str()) => - { - let val: Dynamic = - crate::FnPtr::new_unchecked(v.3.as_str(), crate::StaticVec::default()).into(); - return Ok(val.into()); + Expr::Variable(v, None, ..) => { + // Scripted function with the same name + #[cfg(not(feature = "no_function"))] + if let Some(fn_def) = global.lib.iter().flat_map(|m| m.iter_script_fn()).find_map( + |(_, _, f, _, func)| if f == v.3.as_str() { Some(func) } else { None }, + ) { + let mut fn_ptr = + crate::FnPtr::new_unchecked(v.3.clone(), crate::StaticVec::new_const()); + fn_ptr.set_fn_def(Some(fn_def.clone())); + let val: Dynamic = fn_ptr.into(); + return Ok(val.into()); + } + + v.0.map_or(0, NonZeroUsize::get) } - Expr::Variable(v, None, ..) => v.0.map_or(0, NonZeroUsize::get), _ => unreachable!("Expr::Variable expected but gets {:?}", expr), }; @@ -234,7 +235,7 @@ impl Engine { #[cfg(feature = "debugging")] let reset = self.run_debugger_with_reset(global, caches, scope, this_ptr, expr)?; #[cfg(feature = "debugging")] - auto_restore!(global if reset.is_some() => move |g| g.debugger_mut().reset_status(reset)); + auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset)); self.track_operation(global, expr.position())?; @@ -265,7 +266,7 @@ impl Engine { #[cfg(feature = "debugging")] let reset = self.run_debugger_with_reset(global, caches, scope, this_ptr, expr)?; #[cfg(feature = "debugging")] - auto_restore!(global if reset.is_some() => move |g| g.debugger_mut().reset_status(reset)); + auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset)); self.track_operation(global, expr.position())?; diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 04681410..9fef726f 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -41,10 +41,7 @@ impl Engine { } // Restore scope at end of block if necessary - auto_restore! { - scope if restore_orig_state => rewind; - let orig_scope_len = scope.len(); - } + auto_restore! { scope if restore_orig_state => rewind; let orig_scope_len = scope.len(); } // Restore global state at end of block if necessary let orig_always_search_scope = global.always_search_scope; @@ -208,7 +205,7 @@ impl Engine { #[cfg(feature = "debugging")] let reset = self.run_debugger_with_reset(global, caches, scope, this_ptr, stmt)?; #[cfg(feature = "debugging")] - auto_restore!(global if reset.is_some() => move |g| g.debugger_mut().reset_status(reset)); + auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset)); // Coded this way for better branch prediction. // Popular branches are lifted out of the `match` statement into their own branches. @@ -519,10 +516,8 @@ impl Engine { let iter_func = iter_func.ok_or_else(|| ERR::ErrorFor(expr.start_position()))?; // Restore scope at end of statement - auto_restore! { - scope => rewind; - let orig_scope_len = scope.len(); - } + auto_restore! { scope => rewind; let orig_scope_len = scope.len(); } + // Add the loop variables let counter_index = if counter.is_empty() { usize::MAX @@ -649,10 +644,7 @@ impl Engine { }; // Restore scope at end of block - auto_restore! { - scope if !catch_var.is_empty() => rewind; - let orig_scope_len = scope.len(); - } + auto_restore! { scope if !catch_var.is_empty() => rewind; let orig_scope_len = scope.len(); } if !catch_var.is_empty() { scope.push(catch_var.name.clone(), err_value); @@ -882,9 +874,9 @@ impl Engine { scope: &mut Scope, statements: &[Stmt], ) -> RhaiResult { - let mut this = Dynamic::NULL; + let mut this_ptr = Dynamic::NULL; - self.eval_stmt_block(global, caches, scope, &mut this, statements, false) + self.eval_stmt_block(global, caches, scope, &mut this_ptr, statements, false) .or_else(|err| match *err { ERR::Return(out, ..) => Ok(out), ERR::LoopBreak(..) => { diff --git a/src/func/call.rs b/src/func/call.rs index a425e845..5b738743 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -429,7 +429,7 @@ impl Engine { }; if trigger { let scope = &mut Scope::new(); - let mut this = Dynamic::NULL; + let mut this_ptr = Dynamic::NULL; let node = crate::ast::Stmt::Noop(pos); let node = (&node).into(); let event = match _result { @@ -438,7 +438,7 @@ impl Engine { }; if let Err(err) = - self.run_debugger_raw(global, caches, scope, &mut this, node, event) + self.run_debugger_raw(global, caches, scope, &mut this_ptr, node, event) { _result = Err(err); } @@ -681,9 +681,9 @@ impl Engine { auto_restore!(args = (_args) if swap => move |a| backup.restore_first_arg(a)); - let mut this = Dynamic::NULL; + let mut this_ptr = Dynamic::NULL; - self.call_script_fn(global, caches, scope, &mut this, func, args, true, pos) + self.call_script_fn(global, caches, scope, &mut this_ptr, func, args, true, pos) } .map(|r| (r, false)); } @@ -725,7 +725,7 @@ impl Engine { }) }); #[cfg(feature = "debugging")] - auto_restore!(global if reset.is_some() => move |g| g.debugger_mut().reset_status(reset)); + auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset)); self.eval_expr(global, caches, scope, this_ptr, arg_expr) .map(|r| (r, arg_expr.start_position())) @@ -747,10 +747,38 @@ impl Engine { let is_ref_mut = target.is_ref(); let (result, updated) = match fn_name { + // Handle fn_ptr.call(...) KEYWORD_FN_PTR_CALL if target.is_fnptr() => { - // FnPtr call let fn_ptr = target.read_lock::().expect("`FnPtr`"); + // Arguments are passed as-is, adding the curried arguments + let mut curry = FnArgsVec::with_capacity(fn_ptr.curry().len()); + curry.extend(fn_ptr.curry().iter().cloned()); + let args = &mut FnArgsVec::with_capacity(curry.len() + call_args.len()); + args.extend(curry.iter_mut()); + args.extend(call_args.iter_mut()); + + // Linked to scripted function? + #[cfg(not(feature = "no_function"))] + if let Some(fn_def) = fn_ptr.fn_def() { + if fn_def.params.len() == args.len() { + let mut this_ptr = Dynamic::NULL; + + return self + .call_script_fn( + global, + caches, + &mut Scope::new(), + &mut this_ptr, + fn_def, + args, + true, + fn_call_pos, + ) + .map(|v| (v, false)); + } + } + #[cfg(not(feature = "no_function"))] let is_anon = fn_ptr.is_anonymous(); #[cfg(feature = "no_function")] @@ -759,18 +787,11 @@ impl Engine { // Redirect function name let fn_name = fn_ptr.fn_name(); // Recalculate hashes - let args_len = call_args.len() + fn_ptr.curry().len(); let new_hash = if !is_anon && !is_valid_function_name(fn_name) { - FnCallHashes::from_native(calc_fn_hash(None, fn_name, args_len)) + FnCallHashes::from_native(calc_fn_hash(None, fn_name, args.len())) } else { - calc_fn_hash(None, fn_name, args_len).into() + calc_fn_hash(None, fn_name, args.len()).into() }; - // Arguments are passed as-is, adding the curried arguments - let mut curry = FnArgsVec::with_capacity(fn_ptr.curry().len()); - curry.extend(fn_ptr.curry().iter().cloned()); - let mut args = FnArgsVec::with_capacity(curry.len() + call_args.len()); - args.extend(curry.iter_mut()); - args.extend(call_args.iter_mut()); // Map it to name(args) in function-call style self.exec_fn_call( @@ -780,12 +801,14 @@ impl Engine { fn_name, NO_TOKEN, new_hash, - &mut args, + args, false, false, fn_call_pos, ) } + + // Handle obj.call(fn_ptr, ...) KEYWORD_FN_PTR_CALL => { if call_args.is_empty() { let typ = self.map_type_name(target.type_name()); @@ -799,32 +822,62 @@ impl Engine { let fn_ptr = mem::take(&mut call_args[0]).cast::(); #[cfg(not(feature = "no_function"))] - let is_anon = fn_ptr.is_anonymous(); + let (fn_name, is_anon, fn_curry, fn_def) = { + let is_anon = fn_ptr.is_anonymous(); + let (fn_name, fn_curry, fn_def) = fn_ptr.take_data(); + (fn_name, is_anon, fn_curry, fn_def) + }; #[cfg(feature = "no_function")] - let is_anon = false; + let (fn_name, is_anon, fn_curry) = { + let (fn_name, fn_curry) = fn_ptr.take_data(); + (fn_name, false, fn_curry) + }; + // Replace the first argument with the object pointer, adding the curried arguments call_args = &mut call_args[1..]; - // Redirect function name - let (fn_name, fn_curry) = fn_ptr.take_data(); + let mut curry = FnArgsVec::with_capacity(fn_curry.len()); + curry.extend(fn_curry.into_iter()); + let args = &mut FnArgsVec::with_capacity(curry.len() + call_args.len() + 1); + args.extend(curry.iter_mut()); + args.extend(call_args.iter_mut()); + + // Linked to scripted function? + #[cfg(not(feature = "no_function"))] + if let Some(fn_def) = fn_def { + if fn_def.params.len() == args.len() { + // Check for data race. + #[cfg(not(feature = "no_closure"))] + ensure_no_data_race(&fn_def.name, args, false)?; + + return self + .call_script_fn( + global, + caches, + &mut Scope::new(), + target, + &fn_def, + args, + true, + fn_call_pos, + ) + .map(|v| (v, false)); + } + } + + // Add the first argument with the object pointer + args.insert(0, target.as_mut()); + // Recalculate hash - let args_len = call_args.len() + fn_curry.len(); let new_hash = if !is_anon && !is_valid_function_name(&fn_name) { - FnCallHashes::from_native(calc_fn_hash(None, &fn_name, args_len + 1)) + FnCallHashes::from_native(calc_fn_hash(None, &fn_name, args.len())) } else { FnCallHashes::from_all( #[cfg(not(feature = "no_function"))] - calc_fn_hash(None, &fn_name, args_len), - calc_fn_hash(None, &fn_name, args_len + 1), + calc_fn_hash(None, &fn_name, args.len() - 1), + calc_fn_hash(None, &fn_name, args.len()), ) }; - // Replace the first argument with the object pointer, adding the curried arguments - let mut curry = FnArgsVec::with_capacity(fn_curry.len()); - curry.extend(fn_curry.into_iter()); - let mut args = FnArgsVec::with_capacity(curry.len() + call_args.len() + 1); - args.push(target.as_mut()); - args.extend(curry.iter_mut()); - args.extend(call_args.iter_mut()); // Map it to name(args) in function-call style self.exec_fn_call( @@ -834,7 +887,7 @@ impl Engine { &fn_name, NO_TOKEN, new_hash, - &mut args, + args, is_ref_mut, true, fn_call_pos, @@ -846,26 +899,14 @@ impl Engine { return Err(self.make_type_mismatch_err::(typ, fn_call_pos)); } - let fn_ptr = target.read_lock::().expect("`FnPtr`"); + let mut fn_ptr = target.read_lock::().expect("`FnPtr`").clone(); - // Curry call - Ok(( - if call_args.is_empty() { - fn_ptr.clone() - } else { - FnPtr::new_unchecked( - fn_ptr.fn_name_raw().clone(), - fn_ptr - .curry() - .iter() - .cloned() - .chain(call_args.iter_mut().map(mem::take)) - .collect(), - ) - } - .into(), - false, - )) + // Append the new curried arguments to the existing list. + call_args.iter_mut().map(mem::take).for_each(|value| { + fn_ptr.add_curry(value); + }); + + Ok((fn_ptr.into(), false)) } // Handle is_shared() @@ -975,7 +1016,7 @@ impl Engine { match name { _ if op_token != NO_TOKEN => (), - // Handle call() + // Handle call(fn_ptr, ...) KEYWORD_FN_PTR_CALL if total_args >= 1 => { let arg = first_arg.unwrap(); let (arg_value, arg_pos) = @@ -989,13 +1030,48 @@ impl Engine { let fn_ptr = arg_value.cast::(); #[cfg(not(feature = "no_function"))] - let is_anon = fn_ptr.is_anonymous(); + let (fn_name, is_anon, fn_curry, fn_def) = { + let is_anon = fn_ptr.is_anonymous(); + let (fn_name, fn_curry, fn_def) = fn_ptr.take_data(); + (fn_name, is_anon, fn_curry, fn_def) + }; #[cfg(feature = "no_function")] - let is_anon = false; + let (fn_name, is_anon, fn_curry) = { + let (fn_name, fn_curry) = fn_ptr.take_data(); + (fn_name, false, fn_curry) + }; - let (fn_name, fn_curry) = fn_ptr.take_data(); curry.extend(fn_curry.into_iter()); + // Linked to scripted function? + #[cfg(not(feature = "no_function"))] + if let Some(fn_def) = fn_def { + if fn_def.params.len() == curry.len() + a_expr.len() { + // Evaluate arguments + let mut arg_values = curry + .into_iter() + .map(Ok) + .chain(a_expr.iter().map(|expr| -> Result<_, RhaiError> { + self.get_arg_value(global, caches, scope, this_ptr, expr) + .map(|(v, ..)| v) + })) + .collect::>>()?; + let args = &mut arg_values.iter_mut().collect::>(); + let mut this_ptr = Dynamic::NULL; + + return self.call_script_fn( + global, + caches, + &mut Scope::new(), + &mut this_ptr, + &fn_def, + args, + true, + pos, + ); + } + } + // Redirect function name redirected = fn_name; name = &redirected; @@ -1042,16 +1118,16 @@ impl Engine { return Err(self.make_type_mismatch_err::(typ, arg_pos)); } - let (name, fn_curry) = arg_value.cast::().take_data(); + let mut fn_ptr = arg_value.cast::(); // Append the new curried arguments to the existing list. - let fn_curry = a_expr.iter().try_fold(fn_curry, |mut curried, expr| { + a_expr.iter().try_for_each(|expr| -> Result<_, RhaiError> { let (value, ..) = self.get_arg_value(global, caches, scope, this_ptr, expr)?; - curried.push(value); - Ok::<_, RhaiError>(curried) + fn_ptr.add_curry(value); + Ok(()) })?; - return Ok(FnPtr::new_unchecked(name, fn_curry).into()); + return Ok(fn_ptr.into()); } // Handle is_shared() @@ -1248,7 +1324,7 @@ impl Engine { pos: Position, ) -> RhaiResult { let mut arg_values = FnArgsVec::with_capacity(args_expr.len()); - let mut args = FnArgsVec::with_capacity(args_expr.len()); + let args = &mut FnArgsVec::with_capacity(args_expr.len()); let mut first_arg_value = None; if args_expr.is_empty() { @@ -1366,16 +1442,14 @@ impl Engine { match func { #[cfg(not(feature = "no_function"))] Some(f) if f.is_script() => { - let fn_def = f.get_script_fn_def().expect("script-defined function"); - let new_scope = &mut Scope::new(); - let mut this = Dynamic::NULL; + let f = f.get_script_fn_def().expect("script-defined function"); + let scope = &mut Scope::new(); + let mut this_ptr = Dynamic::NULL; let orig_source = mem::replace(&mut global.source, module.id_raw().cloned()); auto_restore!(global => move |g| g.source = orig_source); - self.call_script_fn( - global, caches, new_scope, &mut this, fn_def, &mut args, true, pos, - ) + self.call_script_fn(global, caches, scope, &mut this_ptr, f, args, true, pos) } Some(f) if f.is_plugin_fn() => { @@ -1388,7 +1462,7 @@ impl Engine { if !f.is_pure() && !args.is_empty() && args[0].is_read_only() { Err(ERR::ErrorNonPureMethodCallOnConstant(fn_name.to_string(), pos).into()) } else { - f.call(context, &mut args) + f.call(context, args) .and_then(|r| self.check_data_size(r, pos)) } } @@ -1400,19 +1474,19 @@ impl Engine { } else { None }; - func(context, &mut args).and_then(|r| self.check_data_size(r, pos)) + func(context, args).and_then(|r| self.check_data_size(r, pos)) } Some(f) => unreachable!("unknown function type: {:?}", f), None => { let sig = if namespace.is_empty() { - self.gen_fn_call_signature(fn_name, &args) + self.gen_fn_call_signature(fn_name, args) } else { format!( "{namespace}{}{}", crate::tokenizer::Token::DoubleColon.literal_syntax(), - self.gen_fn_call_signature(fn_name, &args) + self.gen_fn_call_signature(fn_name, args) ) }; @@ -1551,14 +1625,14 @@ impl Engine { } // Normal function call - let (first_arg, args) = args.split_first().map_or_else( + let (first_arg, rest_args) = args.split_first().map_or_else( || (None, args.as_ref()), |(first, rest)| (Some(first), rest), ); self.make_function_call( - global, caches, scope, this_ptr, name, op_token, first_arg, args, *hashes, *capture, - pos, + global, caches, scope, this_ptr, name, op_token, first_arg, rest_args, *hashes, + *capture, pos, ) } } diff --git a/src/func/native.rs b/src/func/native.rs index 4774f4be..ed4c5117 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -271,6 +271,14 @@ impl<'a> NativeCallContext<'a> { pub const fn global_runtime_state(&self) -> &GlobalRuntimeState { self.global } + /// _(internals)_ The current [`GlobalRuntimeState`], if any. + #[cfg(not(feature = "internals"))] + #[inline(always)] + #[must_use] + #[allow(dead_code)] + pub(crate) const fn global_runtime_state(&self) -> &GlobalRuntimeState { + self.global + } /// Get an iterator over the namespaces containing definitions of all script-defined functions /// in reverse order (i.e. parent namespaces are iterated after child namespaces). /// @@ -303,19 +311,20 @@ impl<'a> NativeCallContext<'a> { let mut args: StaticVec<_> = arg_values.iter_mut().collect(); - let result = self._call_fn_raw(fn_name, &mut args, false, false, false)?; + self._call_fn_raw(fn_name, &mut args, false, false, false) + .and_then(|result| { + // Bail out early if the return type needs no cast + if TypeId::of::() == TypeId::of::() { + return Ok(reify! { result => T }); + } - // Bail out early if the return type needs no cast - if TypeId::of::() == TypeId::of::() { - return Ok(reify!(result => T)); - } + let typ = self.engine().map_type_name(result.type_name()); - let typ = self.engine().map_type_name(result.type_name()); - - result.try_cast().ok_or_else(|| { - let t = self.engine().map_type_name(type_name::()).into(); - ERR::ErrorMismatchOutputType(t, typ.into(), Position::NONE).into() - }) + result.try_cast().ok_or_else(|| { + let t = self.engine().map_type_name(type_name::()).into(); + ERR::ErrorMismatchOutputType(t, typ.into(), Position::NONE).into() + }) + }) } /// Call a registered native Rust function inside the call context with the provided arguments. /// @@ -333,19 +342,20 @@ impl<'a> NativeCallContext<'a> { let mut args: StaticVec<_> = arg_values.iter_mut().collect(); - let result = self._call_fn_raw(fn_name, &mut args, true, false, false)?; + self._call_fn_raw(fn_name, &mut args, true, false, false) + .and_then(|result| { + // Bail out early if the return type needs no cast + if TypeId::of::() == TypeId::of::() { + return Ok(reify! { result => T }); + } - // Bail out early if the return type needs no cast - if TypeId::of::() == TypeId::of::() { - return Ok(reify!(result => T)); - } + let typ = self.engine().map_type_name(result.type_name()); - let typ = self.engine().map_type_name(result.type_name()); - - result.try_cast().ok_or_else(|| { - let t = self.engine().map_type_name(type_name::()).into(); - ERR::ErrorMismatchOutputType(t, typ.into(), Position::NONE).into() - }) + result.try_cast().ok_or_else(|| { + let t = self.engine().map_type_name(type_name::()).into(); + ERR::ErrorMismatchOutputType(t, typ.into(), Position::NONE).into() + }) + }) } /// Call a function (native Rust or scripted) inside the call context. /// @@ -420,15 +430,15 @@ impl<'a> NativeCallContext<'a> { is_ref_mut: bool, is_method_call: bool, ) -> RhaiResult { - let mut global = &mut self.global.clone(); + let global = &mut self.global.clone(); + global.level += 1; + let caches = &mut Caches::new(); let fn_name = fn_name.as_ref(); let op_token = Token::lookup_symbol_from_syntax(fn_name).unwrap_or(NO_TOKEN); let args_len = args.len(); - global.level += 1; - if native_only { return self .engine() diff --git a/src/func/register.rs b/src/func/register.rs index e8b99a31..822d8bad 100644 --- a/src/func/register.rs +++ b/src/func/register.rs @@ -58,7 +58,7 @@ pub fn by_value(data: &mut Dynamic) -> T { } if TypeId::of::() == TypeId::of::() { // If T is `String`, data must be `ImmutableString`, so map directly to it - return reify!(mem::take(data).into_string().expect("`ImmutableString`") => T); + return reify! { mem::take(data).into_string().expect("`ImmutableString`") => T }; } // We consume the argument and then replace it with () - the argument is not supposed to be used again. diff --git a/src/func/script.rs b/src/func/script.rs index 6120d714..ea7cb0f7 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -33,7 +33,7 @@ impl Engine { rewind_scope: bool, pos: Position, ) -> RhaiResult { - assert!(fn_def.params.len() == args.len()); + assert_eq!(fn_def.params.len(), args.len()); self.track_operation(global, pos)?; @@ -205,12 +205,12 @@ impl Engine { } // First check script-defined functions - let result = global.lib.iter().any(|m| m.contains_fn(hash_script)) + let r = global.lib.iter().any(|m| m.contains_fn(hash_script)) // Then check the global namespace and packages || self.global_modules.iter().any(|m| m.contains_fn(hash_script)); #[cfg(not(feature = "no_module"))] - let result = result || + let r = r || // Then check imported modules global.contains_qualified_fn(hash_script) // Then check sub-modules @@ -218,11 +218,11 @@ impl Engine { m.values().any(|m| m.contains_qualified_fn(hash_script)) }); - if !result && !cache.filter.is_absent_and_set(hash_script) { + if !r && !cache.filter.is_absent_and_set(hash_script) { // Do not cache "one-hit wonders" cache.map.insert(hash_script, None); } - result + r } } diff --git a/src/module/mod.rs b/src/module/mod.rs index 05435d4d..24ac305b 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -253,6 +253,17 @@ impl fmt::Debug for Module { } } +#[cfg(not(feature = "no_function"))] +impl>> From for Module { + fn from(iter: T) -> Self { + let mut module = Self::new(); + iter.into_iter().for_each(|fn_def| { + module.set_script_fn(fn_def); + }); + module + } +} + impl> Add for &Module { type Output = Module; @@ -2129,7 +2140,11 @@ impl Module { // Variables with an alias left in the scope become module variables for (_name, value, mut aliases) in scope { - // It is an error to export function pointers that refer to encapsulated local functions + // It is an error to export function pointers that refer to encapsulated local functions. + // + // Even if the function pointer already links to a scripted function definition, it may + // cross-call other functions inside the module and won't have the full encapsulated + // environment available. #[cfg(not(feature = "no_function"))] if let Some(fn_ptr) = value.downcast_ref::() { if ast.iter_fn_def().any(|f| f.name == fn_ptr.fn_name()) { diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index e9cefb23..ba0d7ca7 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -24,6 +24,52 @@ def_package! { } } +/// Make a call to a function pointer. +/// +/// If the function pointer is linked to a scripted function definition, use the appropriate number +/// of arguments to call it directly (one version attaches an extra numeric argument). +fn make_dual_arity_fn_ptr_call( + fn_name: &str, + fn_ptr: &FnPtr, + ctx: &NativeCallContext, + items: [Dynamic; N], + number: usize, +) -> RhaiResult { + #[cfg(not(feature = "no_function"))] + { + let arity = fn_ptr.fn_def().map(|f| f.params.len()).unwrap_or(0); + + if arity == N { + return fn_ptr.call_raw(&ctx, None, items); + } else if arity == N + 1 { + let mut items2 = crate::StaticVec::new_const(); + items2.extend(IntoIterator::into_iter(items)); + items2.push((number as INT).into()); + return fn_ptr.call_raw(&ctx, None, items2); + } + } + + fn_ptr + .call_raw(&ctx, None, items.clone()) + .or_else(|err| match *err { + ERR::ErrorFunctionNotFound(sig, ..) if sig.starts_with(fn_ptr.fn_name()) => { + let mut items2 = crate::StaticVec::new_const(); + items2.extend(IntoIterator::into_iter(items)); + items2.push((number as INT).into()); + fn_ptr.call_raw(&ctx, None, items2) + } + _ => Err(err), + }) + .map_err(|err| { + Box::new(ERR::ErrorInFunctionCall( + fn_name.to_string(), + ctx.source().unwrap_or("").to_string(), + err, + Position::NONE, + )) + }) +} + #[export_module] pub mod array_functions { /// Number of elements in the array. @@ -658,26 +704,13 @@ pub mod array_functions { let mut ar = Array::with_capacity(array.len()); for (i, item) in array.into_iter().enumerate() { - ar.push( - mapper - .call_raw(&ctx, None, [item.clone()]) - .or_else(|err| match *err { - ERR::ErrorFunctionNotFound(fn_sig, ..) - if fn_sig.starts_with(mapper.fn_name()) => - { - mapper.call_raw(&ctx, None, [item, (i as INT).into()]) - } - _ => Err(err), - }) - .map_err(|err| { - Box::new(ERR::ErrorInFunctionCall( - "map".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - )) - })?, - ); + ar.push(make_dual_arity_fn_ptr_call( + "map", + &mapper, + &ctx, + [item], + i, + )?); } Ok(ar) @@ -748,24 +781,7 @@ pub mod array_functions { let mut ar = Array::new(); for (i, item) in array.into_iter().enumerate() { - if filter - .call_raw(&ctx, None, [item.clone()]) - .or_else(|err| match *err { - ERR::ErrorFunctionNotFound(fn_sig, ..) - if fn_sig.starts_with(filter.fn_name()) => - { - filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()]) - } - _ => Err(err), - }) - .map_err(|err| { - Box::new(ERR::ErrorInFunctionCall( - "filter".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - )) - })? + if make_dual_arity_fn_ptr_call("filter", &filter, &ctx, [item.clone()], i)? .as_bool() .unwrap_or(false) { @@ -1058,24 +1074,7 @@ pub mod array_functions { let (start, ..) = calc_offset_len(array.len(), start, 0); for (i, item) in array.iter().enumerate().skip(start) { - if filter - .call_raw(&ctx, None, [item.clone()]) - .or_else(|err| match *err { - ERR::ErrorFunctionNotFound(fn_sig, ..) - if fn_sig.starts_with(filter.fn_name()) => - { - filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()]) - } - _ => Err(err), - }) - .map_err(|err| { - Box::new(ERR::ErrorInFunctionCall( - "index_of".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - )) - })? + if make_dual_arity_fn_ptr_call("index_of", &filter, &ctx, [item.clone()], i)? .as_bool() .unwrap_or(false) { @@ -1157,24 +1156,7 @@ pub mod array_functions { } for (i, item) in array.iter().enumerate() { - if filter - .call_raw(&ctx, None, [item.clone()]) - .or_else(|err| match *err { - ERR::ErrorFunctionNotFound(fn_sig, ..) - if fn_sig.starts_with(filter.fn_name()) => - { - filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()]) - } - _ => Err(err), - }) - .map_err(|err| { - Box::new(ERR::ErrorInFunctionCall( - "some".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - )) - })? + if make_dual_arity_fn_ptr_call("some", &filter, &ctx, [item.clone()], i)? .as_bool() .unwrap_or(false) { @@ -1244,24 +1226,7 @@ pub mod array_functions { } for (i, item) in array.iter().enumerate() { - if !filter - .call_raw(&ctx, None, [item.clone()]) - .or_else(|err| match *err { - ERR::ErrorFunctionNotFound(fn_sig, ..) - if fn_sig.starts_with(filter.fn_name()) => - { - filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()]) - } - _ => Err(err), - }) - .map_err(|err| { - Box::new(ERR::ErrorInFunctionCall( - "all".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - )) - })? + if !make_dual_arity_fn_ptr_call("all", &filter, &ctx, [item.clone()], i)? .as_bool() .unwrap_or(false) { @@ -1482,32 +1447,12 @@ pub mod array_functions { return Ok(initial); } - let mut result = initial; - - for (i, item) in array.iter().enumerate() { - let item = item.clone(); - - result = reducer - .call_raw(&ctx, None, [result.clone(), item.clone()]) - .or_else(|err| match *err { - ERR::ErrorFunctionNotFound(fn_sig, ..) - if fn_sig.starts_with(reducer.fn_name()) => - { - reducer.call_raw(&ctx, None, [result, item, (i as INT).into()]) - } - _ => Err(err), - }) - .map_err(|err| { - Box::new(ERR::ErrorInFunctionCall( - "reduce".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - )) - })?; - } - - Ok(result) + array + .iter() + .enumerate() + .try_fold(initial, |result, (i, item)| { + make_dual_arity_fn_ptr_call("reduce", &reducer, &ctx, [result, item.clone()], i) + }) } /// Reduce an array by iterating through all elements while applying a function named by `reducer`. /// @@ -1643,33 +1588,19 @@ pub mod array_functions { return Ok(initial); } - let mut result = initial; - let len = array.len(); - - for (i, item) in array.iter().rev().enumerate() { - let item = item.clone(); - - result = reducer - .call_raw(&ctx, None, [result.clone(), item.clone()]) - .or_else(|err| match *err { - ERR::ErrorFunctionNotFound(fn_sig, ..) - if fn_sig.starts_with(reducer.fn_name()) => - { - reducer.call_raw(&ctx, None, [result, item, ((len - 1 - i) as INT).into()]) - } - _ => Err(err), - }) - .map_err(|err| { - Box::new(ERR::ErrorInFunctionCall( - "reduce_rev".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - )) - })?; - } - - Ok(result) + array + .iter() + .rev() + .enumerate() + .try_fold(initial, |result, (i, item)| { + make_dual_arity_fn_ptr_call( + "reduce_rev", + &reducer, + &ctx, + [result, item.clone()], + array.len() - 1 - i, + ) + }) } /// Reduce an array by iterating through all elements, in _reverse_ order, /// while applying a function named by `reducer`. @@ -1928,24 +1859,7 @@ pub mod array_functions { let mut x = 0; while x < array.len() { - if filter - .call_raw(&ctx, None, [array[x].clone()]) - .or_else(|err| match *err { - ERR::ErrorFunctionNotFound(fn_sig, ..) - if fn_sig.starts_with(filter.fn_name()) => - { - filter.call_raw(&ctx, None, [array[x].clone(), (i as INT).into()]) - } - _ => Err(err), - }) - .map_err(|err| { - Box::new(ERR::ErrorInFunctionCall( - "drain".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - )) - })? + if make_dual_arity_fn_ptr_call("drain", &filter, &ctx, [array[x].clone()], i)? .as_bool() .unwrap_or(false) { @@ -2124,24 +2038,7 @@ pub mod array_functions { let mut x = 0; while x < array.len() { - if filter - .call_raw(&ctx, None, [array[x].clone()]) - .or_else(|err| match *err { - ERR::ErrorFunctionNotFound(fn_sig, ..) - if fn_sig.starts_with(filter.fn_name()) => - { - filter.call_raw(&ctx, None, [array[x].clone(), (i as INT).into()]) - } - _ => Err(err), - }) - .map_err(|err| { - Box::new(ERR::ErrorInFunctionCall( - "retain".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - )) - })? + if make_dual_arity_fn_ptr_call("retain", &filter, &ctx, [array[x].clone()], i)? .as_bool() .unwrap_or(false) { diff --git a/src/packages/blob_basic.rs b/src/packages/blob_basic.rs index 057a00ef..515cef06 100644 --- a/src/packages/blob_basic.rs +++ b/src/packages/blob_basic.rs @@ -75,8 +75,7 @@ pub mod blob_functions { len: INT, value: INT, ) -> RhaiResultOf { - let len = len.min(MAX_USIZE_INT); - let len = if len < 0 { 0 } else { len as usize }; + let len = len.min(MAX_USIZE_INT).max(0) as usize; let _ctx = ctx; // Check if blob will be over max size limit diff --git a/src/parser.rs b/src/parser.rs index bd60a331..92da6876 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -343,14 +343,15 @@ impl ParseSettings { /// Create a new `ParseSettings` with one higher expression level. #[inline] pub fn level_up(&self) -> ParseResult { - let level = self.level + 1; - #[cfg(not(feature = "unchecked"))] - if self.max_expr_depth > 0 && level > self.max_expr_depth { + if self.max_expr_depth > 0 && self.level >= self.max_expr_depth { return Err(PERR::ExprTooDeep.into_err(self.pos)); } - Ok(Self { level, ..*self }) + Ok(Self { + level: self.level + 1, + ..*self + }) } } @@ -537,50 +538,7 @@ fn parse_var_name(input: &mut TokenStream) -> ParseResult<(SmartString, Position } } -/// Parse a symbol. -#[cfg(not(feature = "no_custom_syntax"))] -fn parse_symbol(input: &mut TokenStream) -> ParseResult<(SmartString, Position)> { - match input.next().expect(NEVER_ENDS) { - // Symbol - (token, pos) if token.is_standard_symbol() => Ok((token.literal_syntax().into(), pos)), - // Reserved symbol - (Token::Reserved(s), pos) if !is_valid_identifier(s.as_str()) => Ok((*s, pos)), - // Bad symbol - (Token::LexError(err), pos) => Err(err.into_err(pos)), - // Not a symbol - (.., pos) => Err(PERR::MissingSymbol(String::new()).into_err(pos)), - } -} - impl Engine { - /// Parse `(` expr `)` - fn parse_paren_expr( - &self, - input: &mut TokenStream, - state: &mut ParseState, - lib: &mut FnLib, - settings: ParseSettings, - ) -> ParseResult { - // ( ... - let mut settings = settings; - settings.pos = eat_token(input, Token::LeftParen); - - let expr = self.parse_expr(input, state, lib, settings.level_up()?)?; - - match input.next().expect(NEVER_ENDS) { - // ( ... ) - (Token::RightParen, ..) => Ok(expr), - // ( - (Token::LexError(err), pos) => Err(err.into_err(pos)), - // ( ... ??? - (.., pos) => Err(PERR::MissingToken( - Token::RightParen.into(), - "for a matching ( in this expression".into(), - ) - .into_err(pos)), - } - } - /// Parse a function call. fn parse_fn_call( &self, @@ -974,10 +932,7 @@ impl Engine { ) .into_err(*pos)) } - _ => { - let expr = self.parse_expr(input, state, lib, settings.level_up()?)?; - array.push(expr); - } + _ => array.push(self.parse_expr(input, state, lib, settings.level_up()?)?), } match input.peek().expect(NEVER_ENDS) { @@ -1139,10 +1094,10 @@ impl Engine { settings: ParseSettings, ) -> ParseResult { // switch ... - let mut settings = settings; + let mut settings = settings.level_up()?; settings.pos = eat_token(input, Token::Switch); - let item = self.parse_expr(input, state, lib, settings.level_up()?)?; + let item = self.parse_expr(input, state, lib, settings)?; match input.next().expect(NEVER_ENDS) { (Token::LeftBrace, ..) => (), @@ -1201,7 +1156,7 @@ impl Engine { loop { let filter = state.expr_filter; state.expr_filter = |t| t != &Token::Pipe; - let expr = self.parse_expr(input, state, lib, settings.level_up()?); + let expr = self.parse_expr(input, state, lib, settings); state.expr_filter = filter; match expr { @@ -1219,7 +1174,7 @@ impl Engine { let condition = if match_token(input, Token::If).0 { ensure_not_statement_expr(input, "a boolean")?; let guard = self - .parse_expr(input, state, lib, settings.level_up()?)? + .parse_expr(input, state, lib, settings)? .ensure_bool_expr()?; ensure_not_assignment(input)?; guard @@ -1244,16 +1199,13 @@ impl Engine { let (action_expr, need_comma) = if !settings.has_flag(ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS) { - let stmt = self.parse_stmt(input, state, lib, settings.level_up()?)?; + let stmt = self.parse_stmt(input, state, lib, settings)?; let need_comma = !stmt.is_self_terminated(); let stmt_block: StmtBlock = stmt.into(); (Expr::Stmt(stmt_block.into()), need_comma) } else { - ( - self.parse_expr(input, state, lib, settings.level_up()?)?, - true, - ) + (self.parse_expr(input, state, lib, settings)?, true) }; let has_condition = !matches!(condition, Expr::BoolConstant(true, ..)); @@ -1410,7 +1362,26 @@ impl Engine { } // ( - grouped expression - Token::LeftParen => self.parse_paren_expr(input, state, lib, settings.level_up()?)?, + Token::LeftParen => { + settings.pos = eat_token(input, Token::LeftParen); + + let expr = self.parse_expr(input, state, lib, settings.level_up()?)?; + + match input.next().expect(NEVER_ENDS) { + // ( ... ) + (Token::RightParen, ..) => expr, + // ( + (Token::LexError(err), pos) => return Err(err.into_err(pos)), + // ( ... ??? + (.., pos) => { + return Err(PERR::MissingToken( + Token::RightParen.into(), + "for a matching ( in this expression".into(), + ) + .into_err(pos)) + } + } + } // If statement is allowed to act as expressions Token::If if settings.has_option(LangOptions::IF_EXPR) => Expr::Stmt(Box::new( @@ -1493,7 +1464,7 @@ impl Engine { // Restore the strings interner by swapping it back std::mem::swap(state.interned_strings, new_state.interned_strings); - let (expr, f) = result?; + let (expr, fn_def) = result?; #[cfg(not(feature = "no_closure"))] new_state @@ -1519,8 +1490,8 @@ impl Engine { } })?; - let hash_script = calc_fn_hash(None, &f.name, f.params.len()); - lib.insert(hash_script, f.into()); + let hash_script = calc_fn_hash(None, &fn_def.name, fn_def.params.len()); + lib.insert(hash_script, fn_def); expr } @@ -1528,6 +1499,7 @@ impl Engine { // Interpolated string Token::InterpolatedString(..) => { let mut segments = StaticVec::::new(); + let settings = settings.level_up()?; match input.next().expect(NEVER_ENDS) { (Token::InterpolatedString(s), ..) if s.is_empty() => (), @@ -1540,7 +1512,7 @@ impl Engine { } loop { - let expr = match self.parse_block(input, state, lib, settings.level_up()?)? { + let expr = match self.parse_block(input, state, lib, settings)? { block @ Stmt::Block(..) => Expr::Stmt(Box::new(block.into())), stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt), }; @@ -1784,7 +1756,6 @@ impl Engine { let (.., ns, _, name) = *x; settings.pos = pos; - let settings = settings.level_up()?; self.parse_fn_call(input, state, lib, name, no_args, true, ns, settings)? } // Function call @@ -1792,7 +1763,6 @@ impl Engine { let (.., ns, _, name) = *x; let no_args = t == Token::Unit; settings.pos = pos; - let settings = settings.level_up()?; self.parse_fn_call(input, state, lib, name, no_args, false, ns, settings)? } // module access @@ -2014,7 +1984,7 @@ impl Engine { // Token::EOF => Err(PERR::UnexpectedEOF.into_err(settings.pos)), // All other tokens - _ => self.parse_primary(input, state, lib, false, settings.level_up()?), + _ => self.parse_primary(input, state, lib, false, settings), } } @@ -2123,33 +2093,6 @@ impl Engine { } } - /// Parse an operator-assignment expression (if any). - fn parse_op_assignment_stmt( - &self, - input: &mut TokenStream, - state: &mut ParseState, - lib: &mut FnLib, - lhs: Expr, - settings: ParseSettings, - ) -> ParseResult { - let (op, pos) = match input.peek().expect(NEVER_ENDS) { - // var = ... - (Token::Equals, ..) => (NO_TOKEN, eat_token(input, Token::Equals)), - // var op= ... - (token, ..) if token.is_op_assignment() => { - input.next().map(|(op, pos)| (op, pos)).expect(NEVER_ENDS) - } - // Not op-assignment - _ => return Ok(Stmt::Expr(lhs.into())), - }; - - let mut settings = settings; - settings.pos = pos; - - let rhs = self.parse_expr(input, state, lib, settings.level_up()?)?; - Self::make_assignment_stmt(op, state, lhs, rhs, pos) - } - /// Make a dot expression. #[cfg(not(feature = "no_object"))] fn make_dot_expr( @@ -2545,7 +2488,20 @@ impl Engine { inputs.push(Expr::Variable((None, ns, 0, name).into(), None, pos)); } CUSTOM_SYNTAX_MARKER_SYMBOL => { - let (symbol, pos) = parse_symbol(input)?; + let (symbol, pos) = match input.next().expect(NEVER_ENDS) { + // Standard symbol + (token, pos) if token.is_standard_symbol() => { + Ok((token.literal_syntax().into(), pos)) + } + // Reserved symbol + (Token::Reserved(s), pos) if !is_valid_identifier(s.as_str()) => { + Ok((*s, pos)) + } + // Bad symbol + (Token::LexError(err), pos) => Err(err.into_err(pos)), + // Not a symbol + (.., pos) => Err(PERR::MissingSymbol(String::new()).into_err(pos)), + }?; let symbol = state.get_interned_string(symbol); segments.push(symbol.clone()); tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_SYMBOL)); @@ -2677,8 +2633,9 @@ impl Engine { // Parse expression normally. let precedence = Precedence::new(1); - let lhs = self.parse_unary(input, state, lib, settings.level_up()?)?; - self.parse_binary_op(input, state, lib, precedence, lhs, settings.level_up()?) + let settings = settings.level_up()?; + let lhs = self.parse_unary(input, state, lib, settings)?; + self.parse_binary_op(input, state, lib, precedence, lhs, settings) } /// Parse an if statement. @@ -2690,25 +2647,25 @@ impl Engine { settings: ParseSettings, ) -> ParseResult { // if ... - let mut settings = settings; + let mut settings = settings.level_up()?; settings.pos = eat_token(input, Token::If); // if guard { if_body } ensure_not_statement_expr(input, "a boolean")?; let guard = self - .parse_expr(input, state, lib, settings.level_up()?)? + .parse_expr(input, state, lib, settings)? .ensure_bool_expr()?; ensure_not_assignment(input)?; - let if_body = self.parse_block(input, state, lib, settings.level_up()?)?; + let if_body = self.parse_block(input, state, lib, settings)?; // if guard { if_body } else ... let else_body = if match_token(input, Token::Else).0 { if let (Token::If, ..) = input.peek().expect(NEVER_ENDS) { // if guard { if_body } else if ... - self.parse_if(input, state, lib, settings.level_up()?)? + self.parse_if(input, state, lib, settings)? } else { // if guard { if_body } else { else-body } - self.parse_block(input, state, lib, settings.level_up()?)? + self.parse_block(input, state, lib, settings)? } } else { Stmt::Noop(Position::NONE) @@ -2728,14 +2685,14 @@ impl Engine { lib: &mut FnLib, settings: ParseSettings, ) -> ParseResult { - let mut settings = settings; + let mut settings = settings.level_up()?; // while|loops ... let (guard, token_pos) = match input.next().expect(NEVER_ENDS) { (Token::While, pos) => { ensure_not_statement_expr(input, "a boolean")?; let expr = self - .parse_expr(input, state, lib, settings.level_up()?)? + .parse_expr(input, state, lib, settings)? .ensure_bool_expr()?; ensure_not_assignment(input)?; (expr, pos) @@ -2746,7 +2703,7 @@ impl Engine { settings.pos = token_pos; settings.flags |= ParseSettingFlags::BREAKABLE; - let body = self.parse_block(input, state, lib, settings.level_up()?)?; + let body = self.parse_block(input, state, lib, settings)?; Ok(Stmt::While((guard, body.into()).into(), settings.pos)) } @@ -2760,7 +2717,7 @@ impl Engine { settings: ParseSettings, ) -> ParseResult { // do ... - let mut settings = settings; + let mut settings = settings.level_up()?; let orig_breakable = settings.flags.contains(ParseSettingFlags::BREAKABLE); settings.flags |= ParseSettingFlags::BREAKABLE; @@ -2768,7 +2725,7 @@ impl Engine { // do { body } [while|until] guard - let body = self.parse_block(input, state, lib, settings.level_up()?)?; + let body = self.parse_block(input, state, lib, settings)?; let negated = match input.next().expect(NEVER_ENDS) { (Token::While, ..) => ASTFlags::NONE, @@ -2787,7 +2744,7 @@ impl Engine { ensure_not_statement_expr(input, "a boolean")?; let guard = self - .parse_expr(input, state, lib, settings.level_up()?)? + .parse_expr(input, state, lib, settings)? .ensure_bool_expr()?; ensure_not_assignment(input)?; @@ -2803,7 +2760,7 @@ impl Engine { settings: ParseSettings, ) -> ParseResult { // for ... - let mut settings = settings; + let mut settings = settings.level_up()?; settings.pos = eat_token(input, Token::For); // for name ... @@ -2856,7 +2813,7 @@ impl Engine { // for name in expr { body } ensure_not_statement_expr(input, "a boolean")?; let expr = self - .parse_expr(input, state, lib, settings.level_up()?)? + .parse_expr(input, state, lib, settings)? .ensure_iterable()?; let counter_var = Ident { @@ -2883,7 +2840,7 @@ impl Engine { }; settings.flags |= ParseSettingFlags::BREAKABLE; - let body = self.parse_block(input, state, lib, settings.level_up()?)?; + let body = self.parse_block(input, state, lib, settings)?; state.stack.as_deref_mut().unwrap().rewind(prev_stack_len); @@ -2933,9 +2890,8 @@ impl Engine { will_shadow, }; let caches = &mut Caches::new(); - let mut this = Dynamic::NULL; - - let context = EvalContext::new(self, global, caches, stack, &mut this); + let mut this_ptr = Dynamic::NULL; + let context = EvalContext::new(self, global, caches, stack, &mut this_ptr); match filter(false, info, context) { Ok(true) => (), @@ -3008,11 +2964,11 @@ impl Engine { settings: ParseSettings, ) -> ParseResult { // import ... - let mut settings = settings; + let mut settings = settings.level_up()?; settings.pos = eat_token(input, Token::Import); // import expr ... - let expr = self.parse_expr(input, state, lib, settings.level_up()?)?; + let expr = self.parse_expr(input, state, lib, settings)?; let export = if match_token(input, Token::As).0 { // import expr as name ... @@ -3052,6 +3008,7 @@ impl Engine { match input.peek().expect(NEVER_ENDS) { (Token::Let, pos) => { let pos = *pos; + let settings = settings.level_up()?; let mut stmt = self.parse_let(input, state, lib, AccessMode::ReadWrite, true, settings)?; stmt.set_position(pos); @@ -3059,6 +3016,7 @@ impl Engine { } (Token::Const, pos) => { let pos = *pos; + let settings = settings.level_up()?; let mut stmt = self.parse_let(input, state, lib, AccessMode::ReadOnly, true, settings)?; stmt.set_position(pos); @@ -3099,7 +3057,7 @@ impl Engine { settings: ParseSettings, ) -> ParseResult { // Must start with { - let mut settings = settings; + let mut settings = settings.level_up()?; settings.pos = match input.next().expect(NEVER_ENDS) { (Token::LeftBrace, pos) => pos, (Token::LexError(err), pos) => return Err(err.into_err(pos)), @@ -3115,7 +3073,7 @@ impl Engine { let mut statements = StaticVec::new_const(); if settings.has_flag(ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS) { - let stmt = self.parse_expr_stmt(input, state, lib, settings.level_up()?)?; + let stmt = self.parse_expr_stmt(input, state, lib, settings)?; statements.push(stmt); // Must end with } @@ -3153,7 +3111,7 @@ impl Engine { // Parse statements inside the block settings.flags &= !ParseSettingFlags::GLOBAL_LEVEL; - let stmt = self.parse_stmt(input, state, lib, settings.level_up()?)?; + let stmt = self.parse_stmt(input, state, lib, settings)?; if stmt.is_noop() { continue; @@ -3215,9 +3173,24 @@ impl Engine { let mut settings = settings; settings.pos = input.peek().expect(NEVER_ENDS).1; - let expr = self.parse_expr(input, state, lib, settings.level_up()?)?; - let stmt = self.parse_op_assignment_stmt(input, state, lib, expr, settings.level_up()?)?; - Ok(stmt) + let expr = self.parse_expr(input, state, lib, settings)?; + + let (op, pos) = match input.peek().expect(NEVER_ENDS) { + // var = ... + (Token::Equals, ..) => (NO_TOKEN, eat_token(input, Token::Equals)), + // var op= ... + (token, ..) if token.is_op_assignment() => { + input.next().map(|(op, pos)| (op, pos)).expect(NEVER_ENDS) + } + // Not op-assignment + _ => return Ok(Stmt::Expr(expr.into())), + }; + + settings.pos = pos; + + let rhs = self.parse_expr(input, state, lib, settings)?; + + Self::make_assignment_stmt(op, state, expr, rhs, pos) } /// Parse a single statement. @@ -3292,6 +3265,7 @@ impl Engine { (Token::EOF, pos) => return Ok(Stmt::Noop(*pos)), (x, pos) => (x, *pos), }; + settings.pos = token_pos; match token { @@ -3503,11 +3477,11 @@ impl Engine { settings: ParseSettings, ) -> ParseResult { // try ... - let mut settings = settings; + let mut settings = settings.level_up()?; settings.pos = eat_token(input, Token::Try); // try { try_block } - let try_block = self.parse_block(input, state, lib, settings.level_up()?)?; + let try_block = self.parse_block(input, state, lib, settings)?; // try { try_block } catch let (matched, catch_pos) = match_token(input, Token::Catch); @@ -3546,7 +3520,7 @@ impl Engine { }; // try { try_block } catch ( var ) { catch_block } - let catch_block = self.parse_block(input, state, lib, settings.level_up()?)?; + let catch_block = self.parse_block(input, state, lib, settings)?; if !catch_var.is_empty() { // Remove the error variable from the stack @@ -3575,7 +3549,7 @@ impl Engine { settings: ParseSettings, #[cfg(feature = "metadata")] comments: impl IntoIterator, ) -> ParseResult { - let settings = settings; + let settings = settings.level_up()?; let (token, pos) = input.next().expect(NEVER_ENDS); @@ -3642,7 +3616,7 @@ impl Engine { // Parse function body let body = match input.peek().expect(NEVER_ENDS) { - (Token::LeftBrace, ..) => self.parse_block(input, state, lib, settings.level_up()?)?, + (Token::LeftBrace, ..) => self.parse_block(input, state, lib, settings)?, (.., pos) => return Err(PERR::FnMissingBody(name.into()).into_err(*pos)), } .into(); @@ -3733,8 +3707,8 @@ impl Engine { _parent: &mut ParseState, lib: &mut FnLib, settings: ParseSettings, - ) -> ParseResult<(Expr, ScriptFnDef)> { - let settings = settings; + ) -> ParseResult<(Expr, Shared)> { + let settings = settings.level_up()?; let mut params_list = StaticVec::::new_const(); if input.next().expect(NEVER_ENDS).0 != Token::Or && !match_token(input, Token::Pipe).0 { @@ -3781,7 +3755,7 @@ impl Engine { } // Parse function body - let body = self.parse_stmt(input, state, lib, settings.level_up()?)?; + let body = self.parse_stmt(input, state, lib, settings)?; // External variables may need to be processed in a consistent order, // so extract them into a list. @@ -3812,7 +3786,7 @@ impl Engine { let fn_name = state.get_interned_string(make_anonymous_fn(hash)); // Define the function - let script = ScriptFnDef { + let script = Shared::new(ScriptFnDef { name: fn_name.clone(), access: crate::FnAccess::Public, params, @@ -3822,9 +3796,10 @@ impl Engine { #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] comments: Box::default(), - }; + }); - let fn_ptr = crate::FnPtr::new_unchecked(fn_name, StaticVec::new_const()); + let mut fn_ptr = crate::FnPtr::new_unchecked(fn_name, StaticVec::new_const()); + fn_ptr.set_fn_def(Some(script.clone())); let expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), settings.pos); #[cfg(not(feature = "no_closure"))] @@ -3845,8 +3820,6 @@ impl Engine { let mut functions = StraightHashMap::default(); let options = self.options & !LangOptions::STMT_EXPR & !LangOptions::LOOP_EXPR; - #[cfg(not(feature = "no_function"))] - let options = options & !LangOptions::ANON_FN; let mut settings = ParseSettings { level: 0, @@ -3861,6 +3834,7 @@ impl Engine { let expr = self.parse_expr(&mut input, state, &mut functions, settings)?; + #[cfg(feature = "no_function")] assert!(functions.is_empty()); match input.peek().expect(NEVER_ENDS) { @@ -3877,7 +3851,7 @@ impl Engine { state.scope, statements, #[cfg(not(feature = "no_function"))] - StaticVec::new_const(), + functions.into_iter().map(|(.., v)| v).collect(), _optimization_level, )); @@ -3885,7 +3859,7 @@ impl Engine { return Ok(AST::new( statements, #[cfg(not(feature = "no_function"))] - crate::Module::new(), + crate::Module::from(functions.into_iter().map(|(.., v)| v)), )); } diff --git a/src/reify.rs b/src/reify.rs index 90a21a4c..cc8b6647 100644 --- a/src/reify.rs +++ b/src/reify.rs @@ -4,12 +4,12 @@ /// /// # Syntax /// -/// * `reify!(`_variable_ or _expression_`,|`_temp-variable_`: `_type_`|` _code_`,` `||` _fallback_ `)` -/// * `reify!(`_variable_ or _expression_`,|`_temp-variable_`: `_type_`|` _code_ `)` -/// * `reify!(`_variable_ or _expression_ `=>` `Option<`_type_`>` `)` -/// * `reify!(`_variable_ or _expression_ `=>` _type_ `)` +/// * `reify! { `_variable_ or _expression_` => |`_temp-variable_`: `_type_`|` _code_`,` `||` _fallback_ `)` +/// * `reify! { `_variable_ or _expression_` => |`_temp-variable_`: `_type_`|` _code_ `)` +/// * `reify! { `_variable_ or _expression_ `=>` `Option<`_type_`>` `)` +/// * `reify! { `_variable_ or _expression_ `=>` _type_ `)` macro_rules! reify { - ($old:ident, |$new:ident : $t:ty| $code:expr, || $fallback:expr) => {{ + ($old:ident => |$new:ident : $t:ty| $code:expr, || $fallback:expr) => {{ #[allow(clippy::redundant_else)] if std::any::TypeId::of::<$t>() == std::any::Any::type_id(&$old) { // SAFETY: This is safe because we already checked to make sure the two types @@ -20,29 +20,29 @@ macro_rules! reify { $fallback } }}; - ($old:expr, |$new:ident : $t:ty| $code:expr, || $fallback:expr) => {{ + ($old:expr => |$new:ident : $t:ty| $code:expr, || $fallback:expr) => {{ let old = $old; - reify!(old, |$new: $t| $code, || $fallback) + reify! { old => |$new: $t| $code, || $fallback } }}; - ($old:ident, |$new:ident : $t:ty| $code:expr) => { - reify!($old, |$new: $t| $code, || ()) + ($old:ident => |$new:ident : $t:ty| $code:expr) => { + reify! { $old => |$new: $t| $code, || () } }; - ($old:expr, |$new:ident : $t:ty| $code:expr) => { - reify!($old, |$new: $t| $code, || ()) + ($old:expr => |$new:ident : $t:ty| $code:expr) => { + reify! { $old => |$new: $t| $code, || () } }; ($old:ident => Option<$t:ty>) => { - reify!($old, |v: $t| Some(v), || None) + reify! { $old => |v: $t| Some(v), || None } }; ($old:expr => Option<$t:ty>) => { - reify!($old, |v: $t| Some(v), || None) + reify! { $old => |v: $t| Some(v), || None } }; ($old:ident => $t:ty) => { - reify!($old, |v: $t| v, || unreachable!()) + reify! { $old => |v: $t| v, || unreachable!() } }; ($old:expr => $t:ty) => { - reify!($old, |v: $t| v, || unreachable!()) + reify! { $old => |v: $t| v, || unreachable!() } }; } diff --git a/src/tests.rs b/src/tests.rs index 01a0e8e7..f80e3e49 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -35,7 +35,14 @@ fn check_struct_sizes() { #[cfg(target_pointer_width = "64")] { assert_eq!(size_of::(), 536); - assert_eq!(size_of::(), 64); + assert_eq!( + size_of::(), + if cfg!(feature = "no_function") { + 64 + } else { + 72 + } + ); assert_eq!(size_of::(), 56); assert_eq!( size_of::(), diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index 7ac4281c..4c660af8 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -1083,38 +1083,35 @@ impl Dynamic { pub fn from(value: T) -> Self { // Coded this way in order to maximally leverage potentials for dead-code removal. - reify!(value, |v: Self| return v); - reify!(value, |v: INT| return v.into()); + reify! { value => |v: Self| return v } + reify! { value => |v: INT| return v.into() } #[cfg(not(feature = "no_float"))] - reify!(value, |v: crate::FLOAT| return v.into()); + reify! { value => |v: crate::FLOAT| return v.into() } #[cfg(feature = "decimal")] - reify!(value, |v: rust_decimal::Decimal| return v.into()); + reify! { value => |v: rust_decimal::Decimal| return v.into() } - reify!(value, |v: bool| return v.into()); - reify!(value, |v: char| return v.into()); - reify!(value, |v: ImmutableString| return v.into()); - reify!(value, |v: String| return v.into()); - reify!(value, |v: &str| return v.into()); - reify!(value, |v: ()| return v.into()); + reify! { value => |v: bool| return v.into() } + reify! { value => |v: char| return v.into() } + reify! { value => |v: ImmutableString| return v.into() } + reify! { value => |v: String| return v.into() } + reify! { value => |v: &str| return v.into() } + reify! { value => |v: ()| return v.into() } #[cfg(not(feature = "no_index"))] - reify!(value, |v: crate::Array| return v.into()); + reify! { value => |v: crate::Array| return v.into() } #[cfg(not(feature = "no_index"))] - reify!(value, |v: crate::Blob| { - // don't use blob.into() because it'll be converted into an Array - return Self::from_blob(v); - }); + // don't use blob.into() because it'll be converted into an Array + reify! { value => |v: crate::Blob| return Self::from_blob(v) } #[cfg(not(feature = "no_object"))] - reify!(value, |v: crate::Map| return v.into()); - reify!(value, |v: FnPtr| return v.into()); + reify! { value => |v: crate::Map| return v.into() } + reify! { value => |v: FnPtr| return v.into() } #[cfg(not(feature = "no_time"))] - reify!(value, |v: Instant| return v.into()); + reify! { value => |v: Instant| return v.into() } #[cfg(not(feature = "no_closure"))] - reify!(value, |v: crate::Shared>| return v - .into()); + reify! { value => |v: crate::Shared>| return v.into() } Self(Union::Variant( Box::new(Box::new(value)), @@ -1183,89 +1180,89 @@ impl Dynamic { self.flatten_in_place(); if TypeId::of::() == TypeId::of::() { - return Some(reify!(self => T)); + return Some(reify! { self => T }); } if TypeId::of::() == TypeId::of::<()>() { return match self.0 { - Union::Unit(..) => Some(reify!(() => T)), + Union::Unit(..) => Some(reify! { () => T }), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Int(n, ..) => Some(reify!(n => T)), + Union::Int(n, ..) => Some(reify! { n => T }), _ => None, }; } #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Float(v, ..) => Some(reify!(*v => T)), + Union::Float(v, ..) => Some(reify! { *v => T }), _ => None, }; } #[cfg(feature = "decimal")] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Decimal(v, ..) => Some(reify!(*v => T)), + Union::Decimal(v, ..) => Some(reify! { *v => T }), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Bool(b, ..) => Some(reify!(b => T)), + Union::Bool(b, ..) => Some(reify! { b => T }), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Str(s, ..) => Some(reify!(s => T)), + Union::Str(s, ..) => Some(reify! { s => T }), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Str(s, ..) => Some(reify!(s.to_string() => T)), + Union::Str(s, ..) => Some(reify! { s.to_string() => T }), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Char(c, ..) => Some(reify!(c => T)), + Union::Char(c, ..) => Some(reify! { c => T }), _ => None, }; } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Array(a, ..) => Some(reify!(*a => T)), + Union::Array(a, ..) => Some(reify! { *a => T }), _ => None, }; } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Blob(b, ..) => Some(reify!(*b => T)), + Union::Blob(b, ..) => Some(reify! { *b => T }), _ => None, }; } #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Map(m, ..) => Some(reify!(*m => T)), + Union::Map(m, ..) => Some(reify! { *m => T }), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::FnPtr(f, ..) => Some(reify!(*f => T)), + Union::FnPtr(f, ..) => Some(reify! { *f => T }), _ => None, }; } #[cfg(not(feature = "no_time"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::TimeStamp(t, ..) => Some(reify!(*t => T)), + Union::TimeStamp(t, ..) => Some(reify! { *t => T }), _ => None, }; } @@ -1305,7 +1302,7 @@ impl Dynamic { pub fn cast(self) -> T { // Bail out early if the return type needs no cast if TypeId::of::() == TypeId::of::() { - return reify!(self => T); + return reify! { self => T }; } #[cfg(not(feature = "no_closure"))] @@ -2039,7 +2036,7 @@ impl Dynamic { }) .collect(), Union::Blob(b, ..) if TypeId::of::() == TypeId::of::() => { - Ok(reify!(*b => Vec)) + Ok(reify! { *b => Vec }) } #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => { @@ -2062,7 +2059,7 @@ impl Dynamic { .collect() } Union::Blob(ref b, ..) if TypeId::of::() == TypeId::of::() => { - Ok(reify!(b.clone() => Vec)) + Ok(reify! { b.clone() => Vec }) } _ => Err(cell.type_name()), } diff --git a/src/types/fn_ptr.rs b/src/types/fn_ptr.rs index d3fe8698..5c71bf62 100644 --- a/src/types/fn_ptr.rs +++ b/src/types/fn_ptr.rs @@ -4,23 +4,42 @@ use crate::eval::GlobalRuntimeState; use crate::tokenizer::is_valid_function_name; use crate::types::dynamic::Variant; use crate::{ - Dynamic, Engine, FuncArgs, ImmutableString, NativeCallContext, Position, RhaiError, RhaiResult, - RhaiResultOf, StaticVec, AST, ERR, + Dynamic, Engine, FnArgsVec, FuncArgs, ImmutableString, NativeCallContext, Position, RhaiError, + RhaiResult, RhaiResultOf, StaticVec, AST, ERR, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ any::{type_name, TypeId}, convert::{TryFrom, TryInto}, - fmt, mem, + fmt, + hash::Hash, + mem, }; /// A general function pointer, which may carry additional (i.e. curried) argument values /// to be passed onto a function during a call. -#[derive(Clone, Hash)] +#[derive(Clone)] pub struct FnPtr { name: ImmutableString, curry: StaticVec, + #[cfg(not(feature = "no_function"))] + fn_def: Option>, +} + +impl Hash for FnPtr { + #[inline(always)] + fn hash(&self, state: &mut H) { + self.name.hash(state); + self.curry.hash(state); + + // Hash the linked [`ScriptFnDef`][crate::ast::ScriptFnDef] by hashing its shared pointer. + #[cfg(not(feature = "no_function"))] + self.fn_def + .as_ref() + .map(|f| crate::Shared::as_ptr(f)) + .hash(state); + } } impl fmt::Debug for FnPtr { @@ -56,6 +75,8 @@ impl FnPtr { Self { name: name.into(), curry, + #[cfg(not(feature = "no_function"))] + fn_def: None, } } /// Get the name of the function. @@ -71,6 +92,20 @@ impl FnPtr { &self.name } /// Get the underlying data of the function pointer. + #[cfg(not(feature = "no_function"))] + #[inline(always)] + #[must_use] + pub(crate) fn take_data( + self, + ) -> ( + ImmutableString, + StaticVec, + Option>, + ) { + (self.name, self.curry, self.fn_def) + } + /// Get the underlying data of the function pointer. + #[cfg(feature = "no_function")] #[inline(always)] #[must_use] pub(crate) fn take_data(self) -> (ImmutableString, StaticVec) { @@ -158,18 +193,18 @@ impl FnPtr { let ctx = (engine, self.fn_name(), None, &*global, Position::NONE).into(); - let result = self.call_raw(&ctx, None, arg_values)?; + self.call_raw(&ctx, None, arg_values).and_then(|result| { + // Bail out early if the return type needs no cast + if TypeId::of::() == TypeId::of::() { + return Ok(reify! { result => T }); + } - // Bail out early if the return type needs no cast - if TypeId::of::() == TypeId::of::() { - return Ok(reify!(result => T)); - } + let typ = engine.map_type_name(result.type_name()); - let typ = engine.map_type_name(result.type_name()); - - result.try_cast().ok_or_else(|| { - let t = engine.map_type_name(type_name::()).into(); - ERR::ErrorMismatchOutputType(t, typ.into(), Position::NONE).into() + result.try_cast().ok_or_else(|| { + let t = engine.map_type_name(type_name::()).into(); + ERR::ErrorMismatchOutputType(t, typ.into(), Position::NONE).into() + }) }) } /// Call the function pointer with curried arguments (if any). @@ -187,18 +222,18 @@ impl FnPtr { let mut arg_values = crate::StaticVec::new_const(); args.parse(&mut arg_values); - let result = self.call_raw(context, None, arg_values)?; + self.call_raw(context, None, arg_values).and_then(|result| { + // Bail out early if the return type needs no cast + if TypeId::of::() == TypeId::of::() { + return Ok(reify! { result => T }); + } - // Bail out early if the return type needs no cast - if TypeId::of::() == TypeId::of::() { - return Ok(reify!(result => T)); - } + let typ = context.engine().map_type_name(result.type_name()); - let typ = context.engine().map_type_name(result.type_name()); - - result.try_cast().ok_or_else(|| { - let t = context.engine().map_type_name(type_name::()).into(); - ERR::ErrorMismatchOutputType(t, typ.into(), Position::NONE).into() + result.try_cast().ok_or_else(|| { + let t = context.engine().map_type_name(type_name::()).into(); + ERR::ErrorMismatchOutputType(t, typ.into(), Position::NONE).into() + }) }) } /// Call the function pointer with curried arguments (if any). @@ -231,21 +266,61 @@ impl FnPtr { let mut args_data; if self.is_curried() { - args_data = StaticVec::with_capacity(self.curry().len() + arg_values.len()); + args_data = FnArgsVec::with_capacity(self.curry().len() + arg_values.len()); args_data.extend(self.curry().iter().cloned()); args_data.extend(arg_values.iter_mut().map(mem::take)); arg_values = &mut *args_data; }; - let is_method = this_ptr.is_some(); - - let mut args = StaticVec::with_capacity(arg_values.len() + 1); - if let Some(obj) = this_ptr { - args.push(obj); - } + let args = &mut StaticVec::with_capacity(arg_values.len() + 1); args.extend(arg_values.iter_mut()); - context.call_fn_raw(self.fn_name(), is_method, is_method, &mut args) + // Linked to scripted function? + #[cfg(not(feature = "no_function"))] + if let Some(fn_def) = self.fn_def() { + if fn_def.params.len() == args.len() { + let global = &mut context.global_runtime_state().clone(); + global.level += 1; + + let caches = &mut crate::eval::Caches::new(); + let mut null_ptr = Dynamic::NULL; + + return context.engine().call_script_fn( + global, + caches, + &mut crate::Scope::new(), + this_ptr.unwrap_or(&mut null_ptr), + &fn_def, + args, + true, + context.position(), + ); + } + } + + let is_method = this_ptr.is_some(); + + if let Some(obj) = this_ptr { + args.insert(0, obj); + } + + context.call_fn_raw(self.fn_name(), is_method, is_method, args) + } + /// Get a reference to the linked [`ScriptFnDef`][crate::ast::ScriptFnDef]. + #[cfg(not(feature = "no_function"))] + #[inline(always)] + #[must_use] + pub(crate) fn fn_def(&self) -> Option<&crate::Shared> { + self.fn_def.as_ref() + } + /// Set a reference to the linked [`ScriptFnDef`][crate::ast::ScriptFnDef]. + #[cfg(not(feature = "no_function"))] + #[inline(always)] + pub(crate) fn set_fn_def( + &mut self, + value: Option>>, + ) { + self.fn_def = value.map(Into::into); } } @@ -264,9 +339,25 @@ impl TryFrom for FnPtr { Ok(Self { name: value, curry: StaticVec::new_const(), + #[cfg(not(feature = "no_function"))] + fn_def: None, }) } else { Err(ERR::ErrorFunctionNotFound(value.to_string(), Position::NONE).into()) } } } + +#[cfg(not(feature = "no_function"))] +impl>> From for FnPtr { + #[inline(always)] + fn from(value: T) -> Self { + let fn_def = value.into(); + + Self { + name: fn_def.name.clone(), + curry: StaticVec::new_const(), + fn_def: Some(fn_def), + } + } +} diff --git a/src/types/interner.rs b/src/types/interner.rs index 34fe3789..ce704bc4 100644 --- a/src/types/interner.rs +++ b/src/types/interner.rs @@ -92,14 +92,7 @@ impl StringsInterner { let result = match self.cache.entry(hash) { Entry::Occupied(e) => return e.get().clone(), - Entry::Vacant(e) => { - let value = mapper(text); - - if value.strong_count() > 1 { - return value; - } - e.insert(value).clone() - } + Entry::Vacant(e) => e.insert(mapper(text)).clone(), }; // Throttle the cache upon exit diff --git a/src/types/position_none.rs b/src/types/position_none.rs index c0ced769..e6a65d16 100644 --- a/src/types/position_none.rs +++ b/src/types/position_none.rs @@ -10,7 +10,7 @@ use std::{ }; /// A location (line number + character position) in the input script. -#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] +#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, Default)] pub struct Position; impl Position { @@ -81,14 +81,6 @@ impl Position { } } -impl Default for Position { - #[inline(always)] - #[must_use] - fn default() -> Self { - Self - } -} - impl fmt::Display for Position { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "none") @@ -119,17 +111,9 @@ impl AddAssign for Position { /// _(internals)_ A span consisting of a starting and an ending [positions][Position]. /// Exported under the `internals` feature only. -#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] +#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, Default)] pub struct Span; -impl Default for Span { - #[inline(always)] - #[must_use] - fn default() -> Self { - Self - } -} - impl Span { /// Empty [`Span`]. pub const NONE: Self = Self; diff --git a/src/types/restore.rs b/src/types/restore.rs index eb11e84b..ac41cfb4 100644 --- a/src/types/restore.rs +++ b/src/types/restore.rs @@ -36,6 +36,18 @@ macro_rules! auto_restore { ($var:ident = $value:expr => $restore:expr) => { let $var = &mut *crate::types::RestoreOnDrop::lock($value, $restore); }; + ($var:ident if Some($guard:ident) => $restore:expr) => { + auto_restore!($var = ($var) if Some($guard) => $restore); + }; + ($var:ident = ( $value:expr ) if Some($guard:ident) => $restore:expr) => { + let mut __rx__; + let $var = if let Some($guard) = $guard { + __rx__ = crate::types::RestoreOnDrop::lock($value, $restore); + &mut *__rx__ + } else { + &mut *$value + }; + }; ($var:ident if $guard:expr => $restore:expr) => { auto_restore!($var = ($var) if $guard => $restore); }; diff --git a/tests/expressions.rs b/tests/expressions.rs index ed5e3a16..34938a57 100644 --- a/tests/expressions.rs +++ b/tests/expressions.rs @@ -16,6 +16,24 @@ fn test_expressions() -> Result<(), Box> { engine.eval_expression_with_scope::(&mut scope, "if x > 0 { 42 } else { 123 }")?, 42 ); + #[cfg(not(feature = "no_index"))] + #[cfg(not(feature = "no_object"))] + #[cfg(not(feature = "no_function"))] + { + assert_eq!( + engine.eval_expression_with_scope::( + &mut scope, + "[1, 2, 3, 4].map(|x| x * x).reduce(|a, v| a + v, 0)" + )?, + 30 + ); + assert!(engine + .eval_expression_with_scope::( + &mut scope, + "[1, 2, 3, 4].map(|x| { let r = 2; x * r }).reduce(|a, v| a + v, 0)" + ) + .is_err()); + } assert!(engine .eval_expression_with_scope::(&mut scope, "if x > 0 { let y = 42; y } else { 123 }") .is_err()); diff --git a/tests/stack.rs b/tests/stack.rs index 849c321b..a63a5dbd 100644 --- a/tests/stack.rs +++ b/tests/stack.rs @@ -55,7 +55,8 @@ fn test_stack_overflow_parsing() -> Result<(), Box> { " 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 + - 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 + + 1 ", )?; @@ -67,7 +68,7 @@ fn test_stack_overflow_parsing() -> Result<(), Box> { 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 + - 1 + 1 + 2 " ) .expect_err("should error") @@ -92,7 +93,7 @@ fn test_stack_overflow_parsing() -> Result<(), Box> { 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 + - 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 ", )?; @@ -109,7 +110,7 @@ fn test_stack_overflow_parsing() -> Result<(), Box> { 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 + - 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 " ) .expect_err("should error")