diff --git a/src/ast.rs b/src/ast.rs index 4d6b3b1c..a6b3fdb7 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,5 +1,6 @@ //! Module defining the AST (abstract syntax tree). +use crate::dynamic::Union; use crate::fn_native::shared_make_mut; use crate::module::NamespaceRef; use crate::token::Token; @@ -1519,7 +1520,7 @@ pub struct FnCallExpr { /// List of function call argument expressions. pub args: StaticVec, /// List of function call arguments that are constants. - pub literal_args: smallvec::SmallVec<[(Dynamic, Position); 2]>, + pub constants: smallvec::SmallVec<[Dynamic; 2]>, /// Function name. pub name: Identifier, /// Does this function call capture the parent scope? @@ -1532,16 +1533,6 @@ impl FnCallExpr { pub fn is_qualified(&self) -> bool { self.namespace.is_some() } - /// Are there no arguments to this function call? - #[inline(always)] - pub fn is_args_empty(&self) -> bool { - self.args.is_empty() && self.literal_args.is_empty() - } - /// Get the number of arguments to this function call. - #[inline(always)] - pub fn args_count(&self) -> usize { - self.args.len() + self.literal_args.len() - } } /// A type that wraps a floating-point number and implements [`Hash`]. @@ -1718,6 +1709,8 @@ pub enum Expr { (ImmutableString, Position), )>, ), + /// Stack slot + Stack(usize, Position), /// { [statement][Stmt] ... } Stmt(Box), /// func `(` expr `,` ... `)` @@ -1783,6 +1776,7 @@ impl fmt::Debug for Expr { f.write_str(")") } Self::Property(x) => write!(f, "Property({})", (x.2).0), + Self::Stack(x, _) => write!(f, "StackSlot({})", x), Self::Stmt(x) => { f.write_str("ExprStmtBlock")?; f.debug_list().entries(x.0.iter()).finish() @@ -1793,8 +1787,8 @@ impl fmt::Debug for Expr { ff.field("name", &x.name) .field("hash", &x.hashes) .field("args", &x.args); - if !x.literal_args.is_empty() { - ff.field("literal_args", &x.literal_args); + if !x.constants.is_empty() { + ff.field("constants", &x.constants); } if x.capture { ff.field("capture", &x.capture); @@ -1865,6 +1859,22 @@ impl Expr { _ => return None, }) } + /// Create an [`Expr`] from a [`Dynamic`] value. + #[inline] + pub fn from_dynamic(value: Dynamic, pos: Position) -> Self { + match value.0 { + Union::Unit(_, _, _) => Self::Unit(pos), + Union::Bool(b, _, _) => Self::BoolConstant(b, pos), + Union::Str(s, _, _) => Self::StringConstant(s, pos), + Union::Char(c, _, _) => Self::CharConstant(c, pos), + Union::Int(i, _, _) => Self::IntegerConstant(i, pos), + + #[cfg(not(feature = "no_float"))] + Union::Float(f, _, _) => Self::FloatConstant(f, pos), + + _ => Self::DynamicConstant(Box::new(value), pos), + } + } /// Is the expression a simple variable access? #[inline(always)] pub(crate) fn is_variable_access(&self, non_qualified: bool) -> bool { @@ -1897,6 +1907,7 @@ impl Expr { | Self::Array(_, pos) | Self::Map(_, pos) | Self::Variable(_, pos, _) + | Self::Stack(_, pos) | Self::FnCall(_, pos) | Self::Custom(_, pos) => *pos, @@ -1935,6 +1946,7 @@ impl Expr { | Self::Dot(_, pos) | Self::Index(_, pos) | Self::Variable(_, pos, _) + | Self::Stack(_, pos) | Self::FnCall(_, pos) | Self::Custom(_, pos) => *pos = new_pos, @@ -1964,7 +1976,7 @@ impl Expr { Self::Stmt(x) => x.0.iter().all(Stmt::is_pure), - Self::Variable(_, _, _) => true, + Self::Variable(_, _, _) | Self::Stack(_, _) => true, _ => self.is_constant(), } @@ -1989,7 +2001,8 @@ impl Expr { | Self::IntegerConstant(_, _) | Self::CharConstant(_, _) | Self::StringConstant(_, _) - | Self::Unit(_) => true, + | Self::Unit(_) + | Self::Stack(_, _) => true, Self::InterpolatedString(x) | Self::Array(x, _) => x.iter().all(Self::is_constant), @@ -2049,6 +2062,8 @@ impl Expr { }, Self::Custom(_, _) => false, + + Self::Stack(_, _) => unreachable!("Expr::Stack should not occur naturally"), } } /// Recursively walk this expression. diff --git a/src/engine.rs b/src/engine.rs index 51edbe01..dc4323a5 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1670,19 +1670,18 @@ impl Engine { let arg_values = x .args .iter() - .map(|arg_expr| { - self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level) - .map(Dynamic::flatten) + .map(|arg_expr| match arg_expr { + Expr::Stack(slot, _) => Ok(x.constants[*slot].clone()), + _ => self + .eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level) + .map(Dynamic::flatten), }) - .chain(x.literal_args.iter().map(|(v, _)| Ok(v.clone()))) .collect::, _>>()?; let pos = x .args - .iter() + .get(0) .map(|arg_expr| arg_expr.position()) - .chain(x.literal_args.iter().map(|(_, pos)| *pos)) - .next() .unwrap_or_default(); idx_values.push((arg_values, pos).into()); @@ -1716,19 +1715,18 @@ impl Engine { let arg_values = x .args .iter() - .map(|arg_expr| { - self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level) - .map(Dynamic::flatten) + .map(|arg_expr| match arg_expr { + Expr::Stack(slot, _) => Ok(x.constants[*slot].clone()), + _ => self + .eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level) + .map(Dynamic::flatten), }) - .chain(x.literal_args.iter().map(|(v, _)| Ok(v.clone()))) .collect::, _>>()?; let pos = x .args - .iter() + .get(0) .map(|arg_expr| arg_expr.position()) - .chain(x.literal_args.iter().map(|(_, pos)| *pos)) - .next() .unwrap_or_default(); (arg_values, pos).into() @@ -2055,7 +2053,7 @@ impl Engine { namespace, hashes, args, - literal_args: c_args, + constants, .. } = x.as_ref(); let namespace = namespace @@ -2063,8 +2061,8 @@ impl Engine { .expect("never fails because function call is qualified"); let hash = hashes.native_hash(); self.make_qualified_function_call( - scope, mods, state, lib, this_ptr, namespace, name, args, c_args, hash, *pos, - level, + scope, mods, state, lib, this_ptr, namespace, name, args, constants, hash, + *pos, level, ) } @@ -2075,12 +2073,12 @@ impl Engine { capture, hashes, args, - literal_args: c_args, + constants, .. } = x.as_ref(); self.make_function_call( - scope, mods, state, lib, this_ptr, name, args, c_args, *hashes, *pos, *capture, - level, + scope, mods, state, lib, this_ptr, name, args, constants, *hashes, *pos, + *capture, level, ) } @@ -2643,7 +2641,7 @@ impl Engine { namespace, hashes, args, - literal_args: c_args, + constants, .. } = x.as_ref(); let namespace = namespace @@ -2651,8 +2649,8 @@ impl Engine { .expect("never fails because function call is qualified"); let hash = hashes.native_hash(); self.make_qualified_function_call( - scope, mods, state, lib, this_ptr, namespace, name, args, c_args, hash, *pos, - level, + scope, mods, state, lib, this_ptr, namespace, name, args, constants, hash, + *pos, level, ) } @@ -2663,12 +2661,12 @@ impl Engine { capture, hashes, args, - literal_args: c_args, + constants, .. } = x.as_ref(); self.make_function_call( - scope, mods, state, lib, this_ptr, name, args, c_args, *hashes, *pos, *capture, - level, + scope, mods, state, lib, this_ptr, name, args, constants, *hashes, *pos, + *capture, level, ) } diff --git a/src/fn_call.rs b/src/fn_call.rs index d1f3d3bc..201e31b1 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -1039,6 +1039,27 @@ impl Engine { Ok((result, updated)) } + #[inline(always)] + fn get_arg_value( + &self, + scope: &mut Scope, + mods: &mut Imports, + state: &mut State, + lib: &[&Module], + this_ptr: &mut Option<&mut Dynamic>, + level: usize, + args_expr: &[Expr], + constants: &[Dynamic], + index: usize, + ) -> Result<(Dynamic, Position), Box> { + match args_expr[index] { + Expr::Stack(slot, pos) => Ok((constants[slot].clone(), pos)), + ref arg => self + .eval_expr(scope, mods, state, lib, this_ptr, arg, level) + .map(|v| (v, arg.position())), + } + } + /// Call a function in normal function-call style. pub(crate) fn make_function_call( &self, @@ -1049,7 +1070,7 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, fn_name: &str, args_expr: &[Expr], - literal_args: &[(Dynamic, Position)], + constants: &[Dynamic], mut hashes: FnCallHashes, pos: Position, capture_scope: bool, @@ -1058,20 +1079,15 @@ impl Engine { // Handle call() - Redirect function call let redirected; let mut args_expr = args_expr; - let mut literal_args = literal_args; - let mut total_args = args_expr.len() + literal_args.len(); + let mut total_args = args_expr.len(); let mut curry = StaticVec::new(); let mut name = fn_name; match name { // Handle call() KEYWORD_FN_PTR_CALL if total_args >= 1 => { - let (arg, arg_pos) = args_expr.get(0).map_or_else( - || Ok(literal_args[0].clone()), - |arg| { - self.eval_expr(scope, mods, state, lib, this_ptr, arg, level) - .map(|v| (v, arg.position())) - }, + let (arg, arg_pos) = self.get_arg_value( + scope, mods, state, lib, this_ptr, level, args_expr, constants, 0, )?; if !arg.is::() { @@ -1089,11 +1105,7 @@ impl Engine { name = &redirected; // Skip the first argument - if !args_expr.is_empty() { - args_expr = &args_expr[1..]; - } else { - literal_args = &literal_args[1..]; - } + args_expr = &args_expr[1..]; total_args -= 1; // Recalculate hash @@ -1106,12 +1118,8 @@ impl Engine { } // Handle Fn() KEYWORD_FN_PTR if total_args == 1 => { - let (arg, arg_pos) = args_expr.get(0).map_or_else( - || Ok(literal_args[0].clone()), - |arg| { - self.eval_expr(scope, mods, state, lib, this_ptr, arg, level) - .map(|v| (v, arg.position())) - }, + let (arg, arg_pos) = self.get_arg_value( + scope, mods, state, lib, this_ptr, level, args_expr, constants, 0, )?; // Fn - only in function call style @@ -1125,12 +1133,8 @@ impl Engine { // Handle curry() KEYWORD_FN_PTR_CURRY if total_args > 1 => { - let (arg, arg_pos) = args_expr.get(0).map_or_else( - || Ok(literal_args[0].clone()), - |arg| { - self.eval_expr(scope, mods, state, lib, this_ptr, arg, level) - .map(|v| (v, arg.position())) - }, + let (arg, arg_pos) = self.get_arg_value( + scope, mods, state, lib, this_ptr, level, args_expr, constants, 0, )?; if !arg.is::() { @@ -1143,15 +1147,12 @@ impl Engine { let (name, mut fn_curry) = arg.cast::().take_data(); // Append the new curried arguments to the existing list. - if !args_expr.is_empty() { - args_expr.iter().skip(1).try_for_each(|expr| { - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) - .map(|value| fn_curry.push(value)) - })?; - fn_curry.extend(literal_args.iter().map(|(v, _)| v.clone())); - } else { - fn_curry.extend(literal_args.iter().skip(1).map(|(v, _)| v.clone())); - } + args_expr.iter().skip(1).try_for_each(|expr| match expr { + Expr::Stack(slot, _) => Ok(fn_curry.push(constants[*slot].clone())), + _ => self + .eval_expr(scope, mods, state, lib, this_ptr, expr, level) + .map(|value| fn_curry.push(value)), + })?; return Ok(FnPtr::new_unchecked(name, fn_curry).into()); } @@ -1159,9 +1160,8 @@ impl Engine { // Handle is_shared() #[cfg(not(feature = "no_closure"))] crate::engine::KEYWORD_IS_SHARED if total_args == 1 => { - let arg = args_expr.get(0).map_or_else( - || Ok(literal_args[0].0.clone()), - |arg| self.eval_expr(scope, mods, state, lib, this_ptr, arg, level), + let (arg, _) = self.get_arg_value( + scope, mods, state, lib, this_ptr, level, args_expr, constants, 0, )?; return Ok(arg.is_shared().into()); } @@ -1169,27 +1169,17 @@ impl Engine { // Handle is_def_fn() #[cfg(not(feature = "no_function"))] crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => { - let (arg, arg_pos) = if !args_expr.is_empty() { - ( - self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?, - args_expr[0].position(), - ) - } else { - literal_args[0].clone() - }; + let (arg, arg_pos) = self.get_arg_value( + scope, mods, state, lib, this_ptr, level, args_expr, constants, 0, + )?; let fn_name = arg .take_immutable_string() .map_err(|err| self.make_type_mismatch_err::(err, arg_pos))?; - let (arg, arg_pos) = if args_expr.len() > 1 { - ( - self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[1], level)?, - args_expr[1].position(), - ) - } else { - literal_args[if args_expr.is_empty() { 1 } else { 0 }].clone() - }; + let (arg, arg_pos) = self.get_arg_value( + scope, mods, state, lib, this_ptr, level, args_expr, constants, 1, + )?; let num_params = arg .as_int() @@ -1206,12 +1196,8 @@ impl Engine { // Handle is_def_var() KEYWORD_IS_DEF_VAR if total_args == 1 => { - let (arg, arg_pos) = args_expr.get(0).map_or_else( - || Ok(literal_args[0].clone()), - |arg| { - self.eval_expr(scope, mods, state, lib, this_ptr, arg, level) - .map(|v| (v, arg.position())) - }, + let (arg, arg_pos) = self.get_arg_value( + scope, mods, state, lib, this_ptr, level, args_expr, constants, 0, )?; let var_name = arg .take_immutable_string() @@ -1223,12 +1209,8 @@ impl Engine { KEYWORD_EVAL if total_args == 1 => { // eval - only in function call style let prev_len = scope.len(); - let (script, script_pos) = args_expr.get(0).map_or_else( - || Ok(literal_args[0].clone()), - |script_expr| { - self.eval_expr(scope, mods, state, lib, this_ptr, script_expr, level) - .map(|v| (v, script_expr.position())) - }, + let (script, script_pos) = self.get_arg_value( + scope, mods, state, lib, this_ptr, level, args_expr, constants, 0, )?; let script = script.take_immutable_string().map_err(|typ| { self.make_type_mismatch_err::(typ, script_pos) @@ -1276,7 +1258,7 @@ impl Engine { None }; - if args_expr.is_empty() && literal_args.is_empty() && curry.is_empty() { + if args_expr.is_empty() && curry.is_empty() { // No arguments args = Default::default(); } else { @@ -1288,11 +1270,12 @@ impl Engine { arg_values = args_expr .iter() .skip(1) - .map(|expr| { - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) - .map(Dynamic::flatten) + .map(|expr| match expr { + Expr::Stack(slot, _) => Ok(constants[*slot].clone()), + _ => self + .eval_expr(scope, mods, state, lib, this_ptr, expr, level) + .map(Dynamic::flatten), }) - .chain(literal_args.iter().map(|(v, _)| Ok(v.clone()))) .collect::>()?; let (mut target, _pos) = @@ -1323,11 +1306,12 @@ impl Engine { // func(..., ...) arg_values = args_expr .iter() - .map(|expr| { - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) - .map(Dynamic::flatten) + .map(|expr| match expr { + Expr::Stack(slot, _) => Ok(constants[*slot].clone()), + _ => self + .eval_expr(scope, mods, state, lib, this_ptr, expr, level) + .map(Dynamic::flatten), }) - .chain(literal_args.iter().map(|(v, _)| Ok(v.clone()))) .collect::>()?; args = curry.iter_mut().chain(arg_values.iter_mut()).collect(); @@ -1351,7 +1335,7 @@ impl Engine { namespace: &NamespaceRef, fn_name: &str, args_expr: &[Expr], - literal_args: &[(Dynamic, Position)], + constants: &[Dynamic], hash: u64, pos: Position, level: usize, @@ -1360,7 +1344,7 @@ impl Engine { let mut first_arg_value = None; let mut args: StaticVec<_>; - if args_expr.is_empty() && literal_args.is_empty() { + if args_expr.is_empty() { // No arguments args = Default::default(); } else { @@ -1375,13 +1359,15 @@ impl Engine { .map(|(i, expr)| { // Skip the first argument if i == 0 { - Ok(Default::default()) - } else { - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) - .map(Dynamic::flatten) + return Ok(Default::default()); + } + match expr { + Expr::Stack(slot, _) => Ok(constants[*slot].clone()), + _ => self + .eval_expr(scope, mods, state, lib, this_ptr, expr, level) + .map(Dynamic::flatten), } }) - .chain(literal_args.iter().map(|(v, _)| Ok(v.clone()))) .collect::>()?; // Get target reference to first argument @@ -1412,11 +1398,12 @@ impl Engine { // func(..., ...) or func(mod::x, ...) arg_values = args_expr .iter() - .map(|expr| { - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) - .map(Dynamic::flatten) + .map(|expr| match expr { + Expr::Stack(slot, _) => Ok(constants[*slot].clone()), + _ => self + .eval_expr(scope, mods, state, lib, this_ptr, expr, level) + .map(Dynamic::flatten), }) - .chain(literal_args.iter().map(|(v, _)| Ok(v.clone()))) .collect::>()?; args = arg_values.iter_mut().collect(); diff --git a/src/optimize.rs b/src/optimize.rs index a09a1557..2ce023fa 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -369,7 +369,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { && x.0.is_variable_access(true) && matches!(&x.2, Expr::FnCall(x2, _) if Token::lookup_from_syntax(&x2.name).map(|t| t.has_op_assignment()).unwrap_or(false) - && x2.args_count() == 2 && x2.args.len() >= 1 + && x2.args.len() == 2 && x2.args[0].get_variable_name(true) == x.0.get_variable_name(true) ) => { @@ -379,12 +379,15 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { let op = Token::lookup_from_syntax(&x2.name).unwrap(); let op_assignment = op.make_op_assignment().unwrap(); x.1 = Some(OpAssignment::new(op_assignment)); - x.2 = if x2.args.len() > 1 { - mem::take(&mut x2.args[1]) + + let value = mem::take(&mut x2.args[1]); + + if let Expr::Stack(slot, pos) = value { + let value = mem::take(x2.constants.get_mut(slot).unwrap()); + x.2 = Expr::from_dynamic(value, pos); } else { - let (value, pos) = mem::take(&mut x2.literal_args[0]); - Expr::DynamicConstant(Box::new(value), pos) - }; + x.2 = value; + } } _ => unreachable!(), } @@ -905,14 +908,23 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) { Expr::FnCall(x, pos) if !x.is_qualified() // Non-qualified && state.optimization_level == OptimizationLevel::Simple // simple optimizations - && x.args_count() == 1 - && x.literal_args.len() == 1 - && x.literal_args[0].0.is::() + && x.args.len() == 1 + && x.args[0].is_constant() && x.name == KEYWORD_FN_PTR => { - state.set_dirty(); - let fn_ptr = FnPtr::new_unchecked(mem::take(&mut x.literal_args[0].0).as_str_ref().unwrap().into(), Default::default()); - *expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos); + let fn_name = match x.args[0] { + Expr::Stack(slot, _) => Some(x.constants[slot].clone()), + Expr::StringConstant(ref s, _) => Some(s.clone().into()), + _ => None + }; + + if let Some(fn_name) = fn_name { + if fn_name.is::() { + state.set_dirty(); + let fn_ptr = FnPtr::new_unchecked(fn_name.as_str_ref().unwrap().into(), Default::default()); + *expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos); + } + } } // Do not call some special keywords @@ -924,13 +936,14 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) { Expr::FnCall(x, pos) if !x.is_qualified() // Non-qualified && state.optimization_level == OptimizationLevel::Simple // simple optimizations - && x.args_count() == 2 // binary call + && x.args.len() == 2 // binary call && x.args.iter().all(Expr::is_constant) // all arguments are constants //&& !is_valid_identifier(x.name.chars()) // cannot be scripted => { - let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()) - .chain(x.literal_args.iter().map(|(v, _)| v).cloned()) - .collect(); + let mut arg_values: StaticVec<_> = x.args.iter().map(|e| match e { + Expr::Stack(slot, _) => x.constants[*slot].clone(), + _ => e.get_constant_value().unwrap() + }).collect(); let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect(); @@ -953,15 +966,14 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) { x.args.iter_mut().for_each(|a| optimize_expr(a, state, false)); - // Move constant arguments to the right - while x.args.last().map(Expr::is_constant).unwrap_or(false) { - let arg = x.args.pop().unwrap(); - let arg_pos = arg.position(); - x.literal_args.insert(0, (arg.get_constant_value().unwrap(), arg_pos)); + // Move constant arguments + for arg in x.args.iter_mut() { + if let Some(value) = arg.get_constant_value() { + state.set_dirty(); + x.constants.push(value); + *arg = Expr::Stack(x.constants.len()-1, arg.position()); + } } - - x.args.shrink_to_fit(); - x.literal_args.shrink_to_fit(); } // Eagerly call functions @@ -972,14 +984,15 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) { => { // First search for script-defined functions (can override built-in) #[cfg(not(feature = "no_function"))] - let has_script_fn = state.lib.iter().any(|&m| m.get_script_fn(x.name.as_ref(), x.args_count()).is_some()); + let has_script_fn = state.lib.iter().any(|&m| m.get_script_fn(x.name.as_ref(), x.args.len()).is_some()); #[cfg(feature = "no_function")] let has_script_fn = false; if !has_script_fn { - let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()) - .chain(x.literal_args.iter().map(|(v, _)| v).cloned()) - .collect(); + let mut arg_values: StaticVec<_> = x.args.iter().map(|e| match e { + Expr::Stack(slot, _) => x.constants[*slot].clone(), + _ => e.get_constant_value().unwrap() + }).collect(); // Save the typename of the first argument if it is `type_of()` // This is to avoid `call_args` being passed into the closure @@ -990,15 +1003,12 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) { }; if let Some(mut result) = call_fn_with_constant_arguments(&state, x.name.as_ref(), &mut arg_values) - .or_else(|| { - if !arg_for_type_of.is_empty() { - // Handle `type_of()` - Some(arg_for_type_of.to_string().into()) - } else { - None - } - }) - .map(Expr::from) + .or_else(|| if !arg_for_type_of.is_empty() { + // Handle `type_of()` + Some(arg_for_type_of.to_string().into()) + } else { + None + }).map(Expr::from) { state.set_dirty(); result.set_position(*pos); @@ -1011,19 +1021,16 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) { } // id(args ..) -> optimize function call arguments - Expr::FnCall(x, _) => { - x.args.iter_mut().for_each(|a| optimize_expr(a, state, false)); + Expr::FnCall(x, _) => for arg in x.args.iter_mut() { + optimize_expr(arg, state, false); - // Move constant arguments to the right - while x.args.last().map(Expr::is_constant).unwrap_or(false) { - let arg = x.args.pop().unwrap(); - let arg_pos = arg.position(); - x.literal_args.insert(0, (arg.get_constant_value().unwrap(), arg_pos)); + // Move constant arguments + if let Some(value) = arg.get_constant_value() { + state.set_dirty(); + x.constants.push(value); + *arg = Expr::Stack(x.constants.len()-1, arg.position()); } - - x.args.shrink_to_fit(); - x.literal_args.shrink_to_fit(); - } + }, // constant-name Expr::Variable(_, pos, x) if x.1.is_none() && state.find_constant(&x.2).is_some() => { diff --git a/src/parser.rs b/src/parser.rs index 8e36faeb..0d1e4f74 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1598,8 +1598,8 @@ fn make_dot_expr( Expr::FnCall(mut func, func_pos) => { // Recalculate hash func.hashes = FnCallHashes::from_script_and_native( - calc_fn_hash(&func.name, func.args_count()), - calc_fn_hash(&func.name, func.args_count() + 1), + calc_fn_hash(&func.name, func.args.len()), + calc_fn_hash(&func.name, func.args.len() + 1), ); let rhs = Expr::Dot( @@ -1630,7 +1630,7 @@ fn make_dot_expr( } // lhs.Fn() or lhs.eval() (_, Expr::FnCall(x, pos)) - if x.is_args_empty() + if x.args.is_empty() && [crate::engine::KEYWORD_FN_PTR, crate::engine::KEYWORD_EVAL] .contains(&x.name.as_ref()) => { @@ -1654,8 +1654,8 @@ fn make_dot_expr( (lhs, Expr::FnCall(mut func, func_pos)) => { // Recalculate hash func.hashes = FnCallHashes::from_script_and_native( - calc_fn_hash(&func.name, func.args_count()), - calc_fn_hash(&func.name, func.args_count() + 1), + calc_fn_hash(&func.name, func.args.len()), + calc_fn_hash(&func.name, func.args.len() + 1), ); let rhs = Expr::FnCall(func, func_pos); Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)