diff --git a/src/engine.rs b/src/engine.rs index c282a436..b8bbbc1e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -129,6 +129,54 @@ pub enum ChainType { Dot, } +#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] +#[derive(Debug, Clone)] +pub enum IndexChainValue { + None, + FnCallArgs(StaticVec), + Value(Dynamic), +} + +#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] +impl IndexChainValue { + /// Return the `Dynamic` value. + /// + /// # Panics + /// + /// Panics if not `IndexChainValue::Value`. + pub fn as_value(self) -> Dynamic { + match self { + Self::None | Self::FnCallArgs(_) => panic!("expecting IndexChainValue::Value"), + Self::Value(value) => value, + } + } + /// Return the `StaticVec` value. + /// + /// # Panics + /// + /// Panics if not `IndexChainValue::FnCallArgs`. + pub fn as_fn_call_args(self) -> StaticVec { + match self { + Self::None | Self::Value(_) => panic!("expecting IndexChainValue::FnCallArgs"), + Self::FnCallArgs(value) => value, + } + } +} + +#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] +impl From> for IndexChainValue { + fn from(value: StaticVec) -> Self { + Self::FnCallArgs(value) + } +} + +#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] +impl From for IndexChainValue { + fn from(value: Dynamic) -> Self { + Self::Value(value) + } +} + /// A type that encapsulates a mutation target for an expression with side effects. #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[derive(Debug)] @@ -790,7 +838,7 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, target: &mut Target, rhs: &Expr, - idx_values: &mut StaticVec, + mut idx_values: StaticVec, chain_type: ChainType, level: usize, new_val: Option<(Dynamic, Position)>, @@ -820,6 +868,7 @@ impl Engine { Expr::Dot(x) | Expr::Index(x) => { let (idx, expr, pos) = x.as_ref(); let idx_pos = idx.position(); + let idx_val = idx_val.as_value(); let obj_ptr = &mut self.get_indexed_mut( state, lib, target, idx_val, idx_pos, false, true, level, )?; @@ -832,6 +881,7 @@ impl Engine { } // xxx[rhs] = new_val _ if new_val.is_some() => { + let idx_val = idx_val.as_value(); let mut idx_val2 = idx_val.clone(); // `call_setter` is introduced to bypass double mutable borrowing of target @@ -876,9 +926,11 @@ impl Engine { Ok(Default::default()) } // xxx[rhs] - _ => self - .get_indexed_mut(state, lib, target, idx_val, pos, false, true, level) - .map(|v| (v.take_or_clone(), false)), + _ => { + let idx_val = idx_val.as_value(); + self.get_indexed_mut(state, lib, target, idx_val, pos, false, true, level) + .map(|v| (v.take_or_clone(), false)) + } } } @@ -889,9 +941,9 @@ impl Engine { Expr::FnCall(x) if x.1.is_none() => { let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref(); let def_val = def_val.map(Into::::into); + let args = idx_val.as_fn_call_args(); self.make_method_call( - state, lib, name, *hash, target, idx_val, &def_val, *native, false, - level, + state, lib, name, *hash, target, args, &def_val, *native, false, level, ) .map_err(|err| err.fill_position(*pos)) } @@ -956,10 +1008,11 @@ impl Engine { Expr::FnCall(x) if x.1.is_none() => { let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref(); let def_val = def_val.map(Into::::into); + let args = idx_val.as_fn_call_args(); let (val, _) = self .make_method_call( - state, lib, name, *hash, target, idx_val, &def_val, - *native, false, level, + state, lib, name, *hash, target, args, &def_val, *native, + false, level, ) .map_err(|err| err.fill_position(*pos))?; val.into() @@ -1034,10 +1087,11 @@ impl Engine { Expr::FnCall(x) if x.1.is_none() => { let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref(); let def_val = def_val.map(Into::::into); + let args = idx_val.as_fn_call_args(); let (mut val, _) = self .make_method_call( - state, lib, name, *hash, target, idx_val, &def_val, - *native, false, level, + state, lib, name, *hash, target, args, &def_val, *native, + false, level, ) .map_err(|err| err.fill_position(*pos))?; let val = &mut val; @@ -1083,10 +1137,19 @@ impl Engine { _ => unreachable!(), }; - let idx_values = &mut StaticVec::new(); + let mut idx_values = StaticVec::new(); self.eval_indexed_chain( - scope, mods, state, lib, this_ptr, dot_rhs, chain_type, idx_values, 0, level, + scope, + mods, + state, + lib, + this_ptr, + dot_rhs, + chain_type, + &mut idx_values, + 0, + level, )?; match dot_lhs { @@ -1133,11 +1196,8 @@ impl Engine { } } - /// Evaluate a chain of indexes and store the results in a list. - /// The first few results are stored in the array `list` which is of fixed length. - /// Any spill-overs are stored in `more`, which is dynamic. - /// The fixed length array is used to avoid an allocation in the overwhelming cases of just a few levels of indexing. - /// The total number of values is returned. + /// Evaluate a chain of indexes and store the results in a StaticVec. + /// StaticVec is used to avoid an allocation in the overwhelming cases of just a few levels of indexing. #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] fn eval_indexed_chain( &self, @@ -1148,7 +1208,7 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, chain_type: ChainType, - idx_values: &mut StaticVec, + idx_values: &mut StaticVec, size: usize, level: usize, ) -> Result<(), Box> { @@ -1162,31 +1222,30 @@ impl Engine { .map(|arg_expr| { self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level) }) - .collect::, _>>()?; + .collect::, _>>()?; - idx_values.push(Dynamic::from(arg_values)); + idx_values.push(arg_values.into()); } Expr::FnCall(_) => unreachable!(), - Expr::Property(_) => idx_values.push(().into()), // Store a placeholder - no need to copy the property name + Expr::Property(_) => idx_values.push(IndexChainValue::None), Expr::Index(x) | Expr::Dot(x) => { let (lhs, rhs, _) = x.as_ref(); // Evaluate in left-to-right order let lhs_val = match lhs { - Expr::Property(_) => Default::default(), // Store a placeholder in case of a property + Expr::Property(_) => IndexChainValue::None, Expr::FnCall(x) if chain_type == ChainType::Dot && x.1.is_none() => { - let arg_values = x - .3 - .iter() + x.3.iter() .map(|arg_expr| { self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level) }) - .collect::, _>>()?; - - Dynamic::from(arg_values) + .collect::, _>>()? + .into() } Expr::FnCall(_) => unreachable!(), - _ => self.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?, + _ => self + .eval_expr(scope, mods, state, lib, this_ptr, lhs, level)? + .into(), }; // Push in reverse order @@ -1201,7 +1260,10 @@ impl Engine { idx_values.push(lhs_val); } - _ => idx_values.push(self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?), + _ => idx_values.push( + self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? + .into(), + ), } Ok(()) diff --git a/src/fn_call.rs b/src/fn_call.rs index 62c2ca28..317930e3 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -695,7 +695,7 @@ impl Engine { name: &str, hash_script: u64, target: &mut Target, - idx_val: Dynamic, + mut call_args: StaticVec, def_val: &Option, native: bool, pub_only: bool, @@ -705,7 +705,6 @@ impl Engine { // Get a reference to the mutation target Dynamic let obj = target.as_mut(); - let mut idx = idx_val.cast::>(); let mut _fn_name = name; let (result, updated) = if _fn_name == KEYWORD_FN_PTR_CALL && obj.is::() { @@ -718,12 +717,12 @@ impl Engine { let hash = if native { 0 } else { - calc_fn_hash(empty(), fn_name, curry.len() + idx.len(), empty()) + calc_fn_hash(empty(), fn_name, curry.len() + call_args.len(), empty()) }; // Arguments are passed as-is, adding the curried arguments let mut arg_values = curry .iter_mut() - .chain(idx.iter_mut()) + .chain(call_args.iter_mut()) .collect::>(); let args = arg_values.as_mut(); @@ -731,9 +730,12 @@ impl Engine { self.exec_fn_call( state, lib, fn_name, hash, args, false, false, pub_only, None, def_val, level, ) - } else if _fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::() { + } else if _fn_name == KEYWORD_FN_PTR_CALL + && call_args.len() > 0 + && call_args[0].is::() + { // FnPtr call on object - let fn_ptr = idx.remove(0).cast::(); + let fn_ptr = call_args.remove(0).cast::(); let mut curry = fn_ptr.curry().iter().cloned().collect::>(); // Redirect function name let fn_name = fn_ptr.get_fn_name().clone(); @@ -741,12 +743,12 @@ impl Engine { let hash = if native { 0 } else { - calc_fn_hash(empty(), &fn_name, curry.len() + idx.len(), empty()) + calc_fn_hash(empty(), &fn_name, curry.len() + call_args.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()) + .chain(call_args.iter_mut()) .collect::>(); let args = arg_values.as_mut(); @@ -764,7 +766,7 @@ impl Engine { .curry() .iter() .cloned() - .chain(idx.into_iter()) + .chain(call_args.into_iter()) .collect(), ) .into(), @@ -773,7 +775,7 @@ impl Engine { } else if { #[cfg(not(feature = "no_closure"))] { - _fn_name == KEYWORD_IS_SHARED && idx.is_empty() + _fn_name == KEYWORD_IS_SHARED && call_args.is_empty() } #[cfg(feature = "no_closure")] false @@ -799,13 +801,13 @@ impl Engine { .iter() .cloned() .enumerate() - .for_each(|(i, v)| idx.insert(i, v)); + .for_each(|(i, v)| call_args.insert(i, v)); } // Recalculate the hash based on the new function name and new arguments hash = if native { 0 } else { - calc_fn_hash(empty(), _fn_name, idx.len(), empty()) + calc_fn_hash(empty(), _fn_name, call_args.len(), empty()) }; } } @@ -816,7 +818,9 @@ impl Engine { } // Attached object pointer in front of the arguments - let mut arg_values = once(obj).chain(idx.iter_mut()).collect::>(); + let mut arg_values = once(obj) + .chain(call_args.iter_mut()) + .collect::>(); let args = arg_values.as_mut(); self.exec_fn_call( @@ -873,28 +877,29 @@ impl Engine { // Handle curry() if name == KEYWORD_FN_PTR_CURRY && args_expr.len() > 1 { let expr = args_expr.get(0).unwrap(); - let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - if !arg_value.is::() { + if !fn_ptr.is::() { return Err(self.make_type_mismatch_err::( - self.map_type_name(arg_value.type_name()), + self.map_type_name(fn_ptr.type_name()), expr.position(), )); } - let (fn_name, fn_curry) = arg_value.cast::().take_data(); + let (fn_name, fn_curry) = fn_ptr.cast::().take_data(); - let curry: StaticVec<_> = args_expr + let mut curry = fn_curry.clone(); + + // Append the new curried arguments to the existing list. + args_expr .iter() .skip(1) - .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) - .collect::>()?; + .try_for_each(|expr| -> Result<(), Box> { + curry.push(self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?); + Ok(()) + })?; - return Ok(FnPtr::new_unchecked( - fn_name, - fn_curry.into_iter().chain(curry.into_iter()).collect(), - ) - .into()); + return Ok(FnPtr::new_unchecked(fn_name, curry).into()); } // Handle is_shared() @@ -917,24 +922,27 @@ impl Engine { && !self.has_override(lib, 0, hash_script, pub_only) { let expr = args_expr.get(0).unwrap(); - let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - if arg_value.is::() { - let fn_ptr = arg_value.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_script = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty()); - } else { + if !fn_ptr.is::() { return Err(self.make_type_mismatch_err::( - self.map_type_name(arg_value.type_name()), + self.map_type_name(fn_ptr.type_name()), expr.position(), )); } + + let fn_ptr = fn_ptr.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_script = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty()); } // Handle is_def_var() @@ -943,8 +951,8 @@ impl Engine { if !self.has_override(lib, hash_fn, hash_script, pub_only) { let expr = args_expr.get(0).unwrap(); - let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - let var_name = arg_value.as_str().map_err(|err| { + let var_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + let var_name = var_name.as_str().map_err(|err| { self.make_type_mismatch_err::(err, expr.position()) })?; if var_name.is_empty() { @@ -967,20 +975,18 @@ impl Engine { ); if !self.has_override(lib, hash_fn, hash_script, pub_only) { - let fn_name_expr = args_expr.get(0).unwrap(); - let num_params_expr = args_expr.get(1).unwrap(); + let expr0 = args_expr.get(0).unwrap(); + let expr1 = args_expr.get(1).unwrap(); - let arg0_value = - self.eval_expr(scope, mods, state, lib, this_ptr, fn_name_expr, level)?; - let arg1_value = - self.eval_expr(scope, mods, state, lib, this_ptr, num_params_expr, level)?; + let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr0, level)?; + let num_params = self.eval_expr(scope, mods, state, lib, this_ptr, expr1, level)?; - let fn_name = arg0_value.as_str().map_err(|err| { - self.make_type_mismatch_err::(err, fn_name_expr.position()) - })?; - let num_params = arg1_value.as_int().map_err(|err| { - self.make_type_mismatch_err::(err, num_params_expr.position()) + let fn_name = fn_name.as_str().map_err(|err| { + self.make_type_mismatch_err::(err, expr0.position()) })?; + let num_params = num_params + .as_int() + .map_err(|err| self.make_type_mismatch_err::(err, expr1.position()))?; if fn_name.is_empty() || num_params < 0 { return Ok(false.into()); @@ -999,8 +1005,8 @@ impl Engine { // eval - only in function call style let prev_len = scope.len(); let expr = args_expr.get(0).unwrap(); - let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - let script = arg_value.as_str().map_err(|typ| { + let script = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + let script = script.as_str().map_err(|typ| { self.make_type_mismatch_err::(typ, expr.position()) })?; let result = if !script.is_empty() {