diff --git a/src/engine.rs b/src/engine.rs index ee102724..936f50a6 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1520,159 +1520,6 @@ impl Engine { // Statement block Expr::Stmt(x) => self.eval_stmt(scope, mods, state, lib, this_ptr, &x.0, level), - // var op= rhs - Expr::Assignment(x) if x.0.get_variable_access(false).is_some() => { - let (lhs_expr, op, rhs_expr, op_pos) = x.as_ref(); - let mut rhs_val = self - .eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)? - .flatten(); - let (mut lhs_ptr, name, typ, pos) = - self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?; - - if !lhs_ptr.is_ref() { - return EvalAltResult::ErrorAssignmentToConstant(name.to_string(), pos).into(); - } - - self.inc_operations(state) - .map_err(|err| err.fill_position(pos))?; - - match typ { - // Assignment to constant variable - ScopeEntryType::Constant => Err(Box::new( - EvalAltResult::ErrorAssignmentToConstant(name.to_string(), pos), - )), - // Normal assignment - ScopeEntryType::Normal if op.is_empty() => { - if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { - *lhs_ptr.as_mut().write_lock::().unwrap() = rhs_val; - } else { - *lhs_ptr.as_mut() = rhs_val; - } - Ok(Default::default()) - } - // Op-assignment - in order of precedence: - ScopeEntryType::Normal => { - // 1) Native registered overriding function - // 2) Built-in implementation - // 3) Map to `var = var op rhs` - - // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. - let arg_types = - once(lhs_ptr.as_mut().type_id()).chain(once(rhs_val.type_id())); - let hash_fn = calc_fn_hash(empty(), op, 2, arg_types); - - match self - .global_module - .get_fn(hash_fn, false) - .or_else(|| self.packages.get_fn(hash_fn, false)) - { - // op= function registered as method - Some(func) if func.is_method() => { - let mut lock_guard; - let lhs_ptr_inner; - - if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { - lock_guard = lhs_ptr.as_mut().write_lock::().unwrap(); - lhs_ptr_inner = lock_guard.deref_mut(); - } else { - lhs_ptr_inner = lhs_ptr.as_mut(); - } - - let args = &mut [lhs_ptr_inner, &mut rhs_val]; - - // Overriding exact implementation - if func.is_plugin_fn() { - func.get_plugin_fn().call((self, lib).into(), args)?; - } else { - func.get_native_fn()((self, lib).into(), args)?; - } - } - // Built-in op-assignment function - _ if run_builtin_op_assignment(op, lhs_ptr.as_mut(), &rhs_val)? - .is_some() => {} - // Not built-in: expand to `var = var op rhs` - _ => { - let op = &op[..op.len() - 1]; // extract operator without = - - // Clone the LHS value - let args = &mut [&mut lhs_ptr.as_mut().clone(), &mut rhs_val]; - - // Run function - let (value, _) = self - .exec_fn_call( - state, lib, op, 0, args, false, false, false, None, &None, - level, - ) - .map_err(|err| err.fill_position(*op_pos))?; - - let value = value.flatten(); - - if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { - *lhs_ptr.as_mut().write_lock::().unwrap() = value; - } else { - *lhs_ptr.as_mut() = value; - } - } - } - Ok(Default::default()) - } - } - } - - // lhs op= rhs - Expr::Assignment(x) => { - let (lhs_expr, op, rhs_expr, op_pos) = x.as_ref(); - let mut rhs_val = - self.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?; - - let _new_val = if op.is_empty() { - // Normal assignment - Some((rhs_val, rhs_expr.position())) - } else { - // Op-assignment - always map to `lhs = lhs op rhs` - let op = &op[..op.len() - 1]; // extract operator without = - let args = &mut [ - &mut self.eval_expr(scope, mods, state, lib, this_ptr, lhs_expr, level)?, - &mut rhs_val, - ]; - - let result = self - .exec_fn_call( - state, lib, op, 0, args, false, false, false, None, &None, level, - ) - .map(|(v, _)| v) - .map_err(|err| err.fill_position(*op_pos))?; - - Some((result, rhs_expr.position())) - }; - - // Must be either `var[index] op= val` or `var.prop op= val` - match lhs_expr { - // name op= rhs (handled above) - Expr::Variable(_) => unreachable!(), - // idx_lhs[idx_expr] op= rhs - #[cfg(not(feature = "no_index"))] - Expr::Index(_) => { - self.eval_dot_index_chain( - scope, mods, state, lib, this_ptr, lhs_expr, level, _new_val, - )?; - Ok(Default::default()) - } - // dot_lhs.dot_rhs op= rhs - #[cfg(not(feature = "no_object"))] - Expr::Dot(_) => { - self.eval_dot_index_chain( - scope, mods, state, lib, this_ptr, lhs_expr, level, _new_val, - )?; - Ok(Default::default()) - } - // Constant expression (should be caught during parsing) - expr if expr.is_constant() => unreachable!(), - // Syntax error - expr => EvalAltResult::ErrorAssignmentToUnknownLHS(expr.position()).into(), - } - } - // lhs[idx_expr] #[cfg(not(feature = "no_index"))] Expr::Index(_) => { @@ -1810,6 +1657,159 @@ impl Engine { // Expression as statement Stmt::Expr(expr) => self.eval_expr(scope, mods, state, lib, this_ptr, expr, level), + // var op= rhs + Stmt::Assignment(x, op_pos) if x.0.get_variable_access(false).is_some() => { + let (lhs_expr, op, rhs_expr) = x.as_ref(); + let mut rhs_val = self + .eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)? + .flatten(); + let (mut lhs_ptr, name, typ, pos) = + self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?; + + if !lhs_ptr.is_ref() { + return EvalAltResult::ErrorAssignmentToConstant(name.to_string(), pos).into(); + } + + self.inc_operations(state) + .map_err(|err| err.fill_position(pos))?; + + match typ { + // Assignment to constant variable + ScopeEntryType::Constant => Err(Box::new( + EvalAltResult::ErrorAssignmentToConstant(name.to_string(), pos), + )), + // Normal assignment + ScopeEntryType::Normal if op.is_empty() => { + if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { + *lhs_ptr.as_mut().write_lock::().unwrap() = rhs_val; + } else { + *lhs_ptr.as_mut() = rhs_val; + } + Ok(Default::default()) + } + // Op-assignment - in order of precedence: + ScopeEntryType::Normal => { + // 1) Native registered overriding function + // 2) Built-in implementation + // 3) Map to `var = var op rhs` + + // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. + let arg_types = + once(lhs_ptr.as_mut().type_id()).chain(once(rhs_val.type_id())); + let hash_fn = calc_fn_hash(empty(), op, 2, arg_types); + + match self + .global_module + .get_fn(hash_fn, false) + .or_else(|| self.packages.get_fn(hash_fn, false)) + { + // op= function registered as method + Some(func) if func.is_method() => { + let mut lock_guard; + let lhs_ptr_inner; + + if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { + lock_guard = lhs_ptr.as_mut().write_lock::().unwrap(); + lhs_ptr_inner = lock_guard.deref_mut(); + } else { + lhs_ptr_inner = lhs_ptr.as_mut(); + } + + let args = &mut [lhs_ptr_inner, &mut rhs_val]; + + // Overriding exact implementation + if func.is_plugin_fn() { + func.get_plugin_fn().call((self, lib).into(), args)?; + } else { + func.get_native_fn()((self, lib).into(), args)?; + } + } + // Built-in op-assignment function + _ if run_builtin_op_assignment(op, lhs_ptr.as_mut(), &rhs_val)? + .is_some() => {} + // Not built-in: expand to `var = var op rhs` + _ => { + let op = &op[..op.len() - 1]; // extract operator without = + + // Clone the LHS value + let args = &mut [&mut lhs_ptr.as_mut().clone(), &mut rhs_val]; + + // Run function + let (value, _) = self + .exec_fn_call( + state, lib, op, 0, args, false, false, false, None, &None, + level, + ) + .map_err(|err| err.fill_position(*op_pos))?; + + let value = value.flatten(); + + if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { + *lhs_ptr.as_mut().write_lock::().unwrap() = value; + } else { + *lhs_ptr.as_mut() = value; + } + } + } + Ok(Default::default()) + } + } + } + + // lhs op= rhs + Stmt::Assignment(x, op_pos) => { + let (lhs_expr, op, rhs_expr) = x.as_ref(); + let mut rhs_val = + self.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?; + + let _new_val = if op.is_empty() { + // Normal assignment + Some((rhs_val, rhs_expr.position())) + } else { + // Op-assignment - always map to `lhs = lhs op rhs` + let op = &op[..op.len() - 1]; // extract operator without = + let args = &mut [ + &mut self.eval_expr(scope, mods, state, lib, this_ptr, lhs_expr, level)?, + &mut rhs_val, + ]; + + let result = self + .exec_fn_call( + state, lib, op, 0, args, false, false, false, None, &None, level, + ) + .map(|(v, _)| v) + .map_err(|err| err.fill_position(*op_pos))?; + + Some((result, rhs_expr.position())) + }; + + // Must be either `var[index] op= val` or `var.prop op= val` + match lhs_expr { + // name op= rhs (handled above) + Expr::Variable(_) => unreachable!(), + // idx_lhs[idx_expr] op= rhs + #[cfg(not(feature = "no_index"))] + Expr::Index(_) => { + self.eval_dot_index_chain( + scope, mods, state, lib, this_ptr, lhs_expr, level, _new_val, + )?; + Ok(Default::default()) + } + // dot_lhs.dot_rhs op= rhs + #[cfg(not(feature = "no_object"))] + Expr::Dot(_) => { + self.eval_dot_index_chain( + scope, mods, state, lib, this_ptr, lhs_expr, level, _new_val, + )?; + Ok(Default::default()) + } + // Constant expression (should be caught during parsing) + expr if expr.is_constant() => unreachable!(), + // Syntax error + expr => EvalAltResult::ErrorAssignmentToUnknownLHS(expr.position()).into(), + } + } + // Block scope Stmt::Block(statements, _) => { let prev_scope_len = scope.len(); diff --git a/src/optimize.rs b/src/optimize.rs index 0c7e485a..1baff289 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -163,6 +163,11 @@ fn call_fn_with_constant_arguments( /// Optimize a statement. fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { match stmt { + // id op= expr + Stmt::Assignment(x, pos) => Stmt::Assignment( + Box::new((optimize_expr(x.0, state), x.1, optimize_expr(x.2, state))), + pos, + ), // if false { if_block } -> Noop Stmt::IfThenElse(Expr::False(pos), x, _) if x.1.is_none() => { state.set_dirty(); @@ -442,8 +447,6 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // ( stmt ) stmt => Expr::Stmt(Box::new((stmt, x.1))), }, - // id op= expr - Expr::Assignment(x) => Expr::Assignment(Box::new((x.0, x.1, optimize_expr(x.2, state), x.3))), // lhs.rhs #[cfg(not(feature = "no_object"))] diff --git a/src/parser.rs b/src/parser.rs index fb7446d9..6ecf24c3 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -753,6 +753,8 @@ pub enum Stmt { Let(Box<(String, Position)>, Option, Position), /// const id = expr Const(Box<(String, Position)>, Option, Position), + /// expr op= expr + Assignment(Box<(Expr, Cow<'static, str>, Expr)>, Position), /// { stmt; ... } Block(Vec, Position), /// try { stmt; ... } catch ( var ) { stmt; ... } @@ -808,6 +810,7 @@ impl Stmt { | Self::Continue(pos) | Self::Break(pos) | Self::Block(_, pos) + | Self::Assignment(_, pos) | Self::IfThenElse(_, _, pos) | Self::While(_, _, pos) | Self::Loop(_, pos) @@ -836,6 +839,7 @@ impl Stmt { | Self::Continue(pos) | Self::Break(pos) | Self::Block(_, pos) + | Self::Assignment(_, pos) | Self::IfThenElse(_, _, pos) | Self::While(_, _, pos) | Self::Loop(_, pos) @@ -876,6 +880,7 @@ impl Stmt { Self::Let(_, _, _) | Self::Const(_, _, _) + | Self::Assignment(_, _) | Self::Expr(_) | Self::Continue(_) | Self::Break(_) @@ -901,7 +906,7 @@ impl Stmt { Self::While(condition, block, _) => condition.is_pure() && block.is_pure(), Self::Loop(block, _) => block.is_pure(), Self::For(iterable, x, _) => iterable.is_pure() && x.1.is_pure(), - Self::Let(_, _, _) | Self::Const(_, _, _) => false, + Self::Let(_, _, _) | Self::Const(_, _, _) | Self::Assignment(_, _) => false, Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()), Self::Continue(_) | Self::Break(_) | Self::ReturnWithVal(_, _, _) => false, Self::TryCatch(x) => (x.0).0.is_pure() && (x.2).0.is_pure(), @@ -1040,8 +1045,6 @@ pub enum Expr { Option, // Default value is `bool` in order for `Expr` to be `Hash`. )>, ), - /// expr op= expr - Assignment(Box<(Expr, Cow<'static, str>, Expr, Position)>), /// lhs.rhs Dot(Box), /// expr[expr] @@ -1168,7 +1171,6 @@ impl Expr { Self::Stmt(x) => x.1, Self::Variable(x) => (x.0).1, Self::FnCall(x) => (x.0).3, - Self::Assignment(x) => x.0.position(), Self::And(x) | Self::Or(x) | Self::In(x) => x.pos, @@ -1202,7 +1204,6 @@ impl Expr { Self::FnCall(x) => (x.0).3 = new_pos, Self::And(x) | Self::Or(x) | Self::In(x) => x.pos = new_pos, Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos = new_pos, - Self::Assignment(x) => x.3 = new_pos, Self::Dot(x) | Self::Index(x) => x.pos = new_pos, Self::Custom(x) => x.pos = new_pos, } @@ -1322,8 +1323,7 @@ impl Expr { | Self::Or(_) | Self::True(_) | Self::False(_) - | Self::Unit(_) - | Self::Assignment(_) => false, + | Self::Unit(_) => false, Self::StringConstant(_) | Self::Stmt(_) @@ -1598,7 +1598,6 @@ fn parse_index_chain( } Expr::CharConstant(_) - | Expr::Assignment(_) | Expr::And(_) | Expr::Or(_) | Expr::In(_) @@ -1634,7 +1633,6 @@ fn parse_index_chain( } Expr::CharConstant(_) - | Expr::Assignment(_) | Expr::And(_) | Expr::Or(_) | Expr::In(_) @@ -1665,13 +1663,6 @@ fn parse_index_chain( ) .into_err(x.position())) } - // lhs[??? = ??? ] - x @ Expr::Assignment(_) => { - return Err(PERR::MalformedIndexExpr( - "Array access expects integer index, not an assignment".into(), - ) - .into_err(x.position())) - } // lhs[()] x @ Expr::Unit(_) => { return Err(PERR::MalformedIndexExpr( @@ -2249,18 +2240,18 @@ fn make_assignment_stmt<'a>( lhs: Expr, rhs: Expr, pos: Position, -) -> Result { +) -> Result { match &lhs { // var (non-indexed) = rhs Expr::Variable(x) if x.3.is_none() => { - Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos)))) + Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos)) } // var (indexed) = rhs Expr::Variable(x) => { let ((name, name_pos), _, _, index) = x.as_ref(); match state.stack[(state.stack.len() - index.unwrap().get())].1 { ScopeEntryType::Normal => { - Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos)))) + Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos)) } // Constant values cannot be assigned to ScopeEntryType::Constant => { @@ -2272,14 +2263,14 @@ fn make_assignment_stmt<'a>( Expr::Index(x) | Expr::Dot(x) => match &x.lhs { // var[???] (non-indexed) = rhs, var.??? (non-indexed) = rhs Expr::Variable(x) if x.3.is_none() => { - Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos)))) + Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos)) } // var[???] (indexed) = rhs, var.??? (indexed) = rhs Expr::Variable(x) => { let ((name, name_pos), _, _, index) = x.as_ref(); match state.stack[(state.stack.len() - index.unwrap().get())].1 { ScopeEntryType::Normal => { - Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos)))) + Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos)) } // Constant values cannot be assigned to ScopeEntryType::Constant => { @@ -2310,7 +2301,7 @@ fn parse_op_assignment_stmt( lib: &mut FunctionsLib, lhs: Expr, mut settings: ParseSettings, -) -> Result { +) -> Result { let (token, token_pos) = input.peek().unwrap(); settings.pos = *token_pos; @@ -2332,7 +2323,7 @@ fn parse_op_assignment_stmt( | Token::OrAssign | Token::XOrAssign => token.syntax(), - _ => return Ok(lhs), + _ => return Ok(Stmt::Expr(lhs)), }; let (_, pos) = input.next().unwrap(); @@ -2436,7 +2427,6 @@ fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { @@ -2499,13 +2489,6 @@ fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { - return Err(PERR::MalformedInExpr( - "'in' expression for a string expects a string, not an assignment".into(), - ) - .into_err(x.position())) - } // () in "xxxx" (x @ Expr::Unit(_), Expr::StringConstant(_)) => { return Err(PERR::MalformedInExpr( @@ -2558,13 +2541,6 @@ fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { - return Err(PERR::MalformedInExpr( - "'in' expression for an object map expects a string, not an assignment".into(), - ) - .into_err(x.position())) - } // () in #{...} (x @ Expr::Unit(_), Expr::Map(_)) => { return Err(PERR::MalformedInExpr( @@ -3290,8 +3266,8 @@ fn parse_expr_stmt( settings.ensure_level_within_max_limit(state.max_expr_depth)?; let expr = parse_expr(input, state, lib, settings.level_up())?; - let expr = parse_op_assignment_stmt(input, state, lib, expr, settings.level_up())?; - Ok(Stmt::Expr(expr)) + let stmt = parse_op_assignment_stmt(input, state, lib, expr, settings.level_up())?; + Ok(stmt) } /// Parse a single statement.