diff --git a/src/engine.rs b/src/engine.rs index 9e7a8e74..202a0402 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -376,29 +376,29 @@ impl<'a> Target<'a> { #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, ch) => { let char_value = ch.clone(); - self.set_value((char_value, Position::NONE)).unwrap(); + self.set_value(char_value, Position::NONE).unwrap(); } } } /// Update the value of the `Target`. #[cfg(any(not(feature = "no_object"), not(feature = "no_index")))] - pub fn set_value(&mut self, new_val: (Dynamic, Position)) -> Result<(), Box> { + pub fn set_value(&mut self, new_val: Dynamic, pos: Position) -> Result<(), Box> { match self { - Self::Ref(r) => **r = new_val.0, + Self::Ref(r) => **r = new_val, #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_object"))] - Self::LockGuard((r, _)) => **r = new_val.0, + Self::LockGuard((r, _)) => **r = new_val, Self::Value(_) => unreachable!(), #[cfg(not(feature = "no_index"))] Self::StringChar(string, index, _) if string.is::() => { let mut s = string.write_lock::().unwrap(); // Replace the character at the specified index position - let new_ch = new_val.0.as_char().map_err(|err| { + let new_ch = new_val.as_char().map_err(|err| { Box::new(EvalAltResult::ErrorMismatchDataType( err.to_string(), "char".to_string(), - new_val.1, + pos, )) })?; @@ -1031,7 +1031,8 @@ impl Engine { ) { // Indexed value is a reference - update directly Ok(ref mut obj_ptr) => { - obj_ptr.set_value(new_val.unwrap())?; + let (new_val, new_val_pos) = new_val.unwrap(); + obj_ptr.set_value(new_val, new_val_pos)?; None } Err(err) => match *err { @@ -1107,7 +1108,9 @@ impl Engine { mods, state, lib, target_val, index, *pos, true, is_ref, false, level, )?; - val.set_value(new_val.unwrap())?; + let (new_val, new_val_pos) = new_val.unwrap(); + val.set_value(new_val, new_val_pos)?; + Ok((Default::default(), true)) } // {xxx:map}.id @@ -1308,8 +1311,7 @@ impl Engine { pos: var_pos, } = &x.3; - self.inc_operations(state) - .map_err(|err| err.fill_position(*var_pos))?; + self.inc_operations(state, *var_pos)?; let (target, _, pos) = self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?; @@ -1360,8 +1362,7 @@ impl Engine { size: usize, level: usize, ) -> Result<(), Box> { - self.inc_operations(state) - .map_err(|err| err.fill_position(expr.position()))?; + self.inc_operations(state, expr.position())?; match expr { Expr::FnCall(x, _) if x.namespace.is_none() => { @@ -1435,7 +1436,7 @@ impl Engine { _indexers: bool, _level: usize, ) -> Result, Box> { - self.inc_operations(state)?; + self.inc_operations(state, Position::NONE)?; match target { #[cfg(not(feature = "no_index"))] @@ -1538,8 +1539,7 @@ impl Engine { rhs: &Expr, level: usize, ) -> Result> { - self.inc_operations(state) - .map_err(|err| err.fill_position(rhs.position()))?; + self.inc_operations(state, rhs.position())?; let lhs_value = self.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?; let rhs_value = self.eval_expr(scope, mods, state, lib, this_ptr, rhs, level)?; @@ -1712,8 +1712,7 @@ impl Engine { expr: &Expr, level: usize, ) -> Result> { - self.inc_operations(state) - .map_err(|err| err.fill_position(expr.position()))?; + self.inc_operations(state, expr.position())?; let result = match expr { Expr::Expr(x) => self.eval_expr(scope, mods, state, lib, this_ptr, x, level), @@ -1868,8 +1867,7 @@ impl Engine { _ => unreachable!(), }; - self.check_data_size(result) - .map_err(|err| err.fill_position(expr.position())) + self.check_data_size(result, expr.position()) } /// Evaluate a statements block. @@ -1925,8 +1923,7 @@ impl Engine { stmt: &Stmt, level: usize, ) -> Result> { - self.inc_operations(state) - .map_err(|err| err.fill_position(stmt.position()))?; + self.inc_operations(state, stmt.position())?; let result = match stmt { // No-op @@ -1948,8 +1945,7 @@ impl Engine { return EvalAltResult::ErrorAssignmentToConstant(name.to_string(), pos).into(); } - self.inc_operations(state) - .map_err(|err| err.fill_position(pos))?; + self.inc_operations(state, pos)?; if lhs_ptr.as_ref().is_read_only() { // Assignment to constant variable @@ -2208,8 +2204,7 @@ impl Engine { *loop_var = value; } - self.inc_operations(state) - .map_err(|err| err.fill_position(stmt.position()))?; + self.inc_operations(state, stmt.position())?; match self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) { Ok(_) => (), @@ -2427,8 +2422,7 @@ impl Engine { } }; - self.check_data_size(result) - .map_err(|err| err.fill_position(stmt.position())) + self.check_data_size(result, stmt.position()) } /// Check a result to ensure that the data size is within allowable limit. @@ -2438,16 +2432,17 @@ impl Engine { fn check_data_size( &self, result: Result>, + _pos: Position, ) -> Result> { result } /// Check a result to ensure that the data size is within allowable limit. - /// [`Position`] in [`EvalAltResult`] may be None and should be set afterwards. #[cfg(not(feature = "unchecked"))] fn check_data_size( &self, result: Result>, + pos: Position, ) -> Result> { // If no data size limits, just return let mut total = 0; @@ -2536,47 +2531,41 @@ impl Engine { let (_arr, _map, s) = calc_size(result.as_ref().unwrap()); if s > self.max_string_size() { - return EvalAltResult::ErrorDataTooLarge( - "Length of string".to_string(), - Position::NONE, - ) - .into(); + return EvalAltResult::ErrorDataTooLarge("Length of string".to_string(), pos).into(); } #[cfg(not(feature = "no_index"))] if _arr > self.max_array_size() { - return EvalAltResult::ErrorDataTooLarge("Size of array".to_string(), Position::NONE) - .into(); + return EvalAltResult::ErrorDataTooLarge("Size of array".to_string(), pos).into(); } #[cfg(not(feature = "no_object"))] if _map > self.max_map_size() { - return EvalAltResult::ErrorDataTooLarge( - "Size of object map".to_string(), - Position::NONE, - ) - .into(); + return EvalAltResult::ErrorDataTooLarge("Size of object map".to_string(), pos).into(); } result } /// Check if the number of operations stay within limit. - /// [`Position`] in [`EvalAltResult`] is [`None`][Position::None] and must be set afterwards. - pub(crate) fn inc_operations(&self, state: &mut State) -> Result<(), Box> { + pub(crate) fn inc_operations( + &self, + state: &mut State, + pos: Position, + ) -> Result<(), Box> { state.operations += 1; #[cfg(not(feature = "unchecked"))] // Guard against too many operations if self.max_operations() > 0 && state.operations > self.max_operations() { - return EvalAltResult::ErrorTooManyOperations(Position::NONE).into(); + return EvalAltResult::ErrorTooManyOperations(pos).into(); } // Report progress - only in steps if let Some(progress) = &self.progress { if let Some(token) = progress(state.operations) { // Terminate script if progress returns a termination token - return EvalAltResult::ErrorTerminated(token, Position::NONE).into(); + return EvalAltResult::ErrorTerminated(token, pos).into(); } } diff --git a/src/engine_api.rs b/src/engine_api.rs index fe6a9a48..a71fb34e 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -1370,7 +1370,7 @@ impl Engine { ) -> Result> { let mut mods = self.global_sub_modules.clone(); - let (result, _) = self.eval_ast_with_scope_raw(scope, &mut mods, ast)?; + let result = self.eval_ast_with_scope_raw(scope, &mut mods, ast)?; let typ = self.map_type_name(result.type_name()); @@ -1390,8 +1390,9 @@ impl Engine { scope: &mut Scope, mods: &mut Imports, ast: &'a AST, - ) -> Result<(Dynamic, u64), Box> { - self.eval_statements_raw(scope, mods, ast.statements(), &[ast.lib()]) + ) -> Result> { + let state = &mut Default::default(); + self.eval_statements_raw(scope, mods, state, ast.statements(), &[ast.lib()]) } /// Evaluate a file, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. @@ -1451,9 +1452,9 @@ impl Engine { ast: &AST, ) -> Result<(), Box> { let mut mods = self.global_sub_modules.clone(); - - self.eval_statements_raw(scope, &mut mods, ast.statements(), &[ast.lib()]) - .map(|_| ()) + let mut state = Default::default(); + self.eval_statements_raw(scope, &mut mods, &mut state, ast.statements(), &[ast.lib()])?; + Ok(()) } /// Call a script function defined in an [`AST`] with multiple arguments. /// Arguments are passed as a tuple. diff --git a/src/fn_call.rs b/src/fn_call.rs index 4fdb78a0..2c06b419 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -168,7 +168,7 @@ impl Engine { pos: Position, def_val: Option<&Dynamic>, ) -> Result<(Dynamic, bool), Box> { - self.inc_operations(state)?; + self.inc_operations(state, pos)?; let func = state.functions_cache.get(&hash_fn).cloned(); @@ -348,7 +348,7 @@ impl Engine { pos: Position, level: usize, ) -> Result> { - self.inc_operations(state)?; + self.inc_operations(state, pos)?; // Check for stack overflow #[cfg(not(feature = "no_function"))] @@ -623,36 +623,34 @@ impl Engine { &self, scope: &mut Scope, mods: &mut Imports, + state: &mut State, statements: impl IntoIterator, lib: &[&Module], - ) -> Result<(Dynamic, u64), Box> { - let mut state = Default::default(); - + ) -> Result> { statements .into_iter() .try_fold(().into(), |_, stmt| { - self.eval_stmt(scope, mods, &mut state, lib, &mut None, stmt, 0) + self.eval_stmt(scope, mods, state, lib, &mut None, stmt, 0) }) .or_else(|err| match *err { EvalAltResult::Return(out, _) => Ok(out), EvalAltResult::LoopBreak(_, _) => unreachable!(), _ => Err(err), }) - .map(|v| (v, state.operations)) } - /// Evaluate a text string as a script - used primarily for 'eval'. - fn eval_script_expr( + /// Evaluate a text script in place - used primarily for 'eval'. + fn eval_script_expr_in_place( &self, scope: &mut Scope, mods: &mut Imports, state: &mut State, lib: &[&Module], script: &str, - _pos: Position, + pos: Position, _level: usize, ) -> Result> { - self.inc_operations(state)?; + self.inc_operations(state, pos)?; let script = script.trim(); if script.is_empty() { @@ -663,7 +661,7 @@ impl Engine { #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "unchecked"))] if _level > self.max_call_levels() { - return Err(Box::new(EvalAltResult::ErrorStackOverflow(_pos))); + return Err(Box::new(EvalAltResult::ErrorStackOverflow(pos))); } // Compile the script text @@ -680,10 +678,7 @@ impl Engine { } // Evaluate the AST - let (result, operations) = self.eval_statements_raw(scope, mods, ast.statements(), lib)?; - - state.operations += operations; - self.inc_operations(state)?; + let result = self.eval_statements_raw(scope, mods, state, ast.statements(), lib)?; return Ok(result); } @@ -977,7 +972,8 @@ impl Engine { self.make_type_mismatch_err::(typ, args_expr[0].position()) })?; let pos = args_expr[0].position(); - let result = self.eval_script_expr(scope, mods, state, lib, script, pos, level + 1); + let result = + self.eval_script_expr_in_place(scope, mods, state, lib, script, pos, level + 1); // IMPORTANT! If the eval defines new variables in the current scope, // all variable offsets from this point on will be mis-aligned. @@ -1020,8 +1016,7 @@ impl Engine { target = target.into_owned(); } - self.inc_operations(state) - .map_err(|err| err.fill_position(pos))?; + self.inc_operations(state, pos)?; args = if target.is_shared() || target.is_value() { arg_values.insert(0, target.take_or_clone().flatten()); @@ -1103,8 +1098,7 @@ impl Engine { let (target, _, pos) = self.search_scope_only(scope, mods, state, lib, this_ptr, &args_expr[0])?; - self.inc_operations(state) - .map_err(|err| err.fill_position(pos))?; + self.inc_operations(state, pos)?; if target.is_shared() || target.is_value() { arg_values[0] = target.take_or_clone().flatten(); @@ -1133,7 +1127,7 @@ impl Engine { let func = match module.get_qualified_fn(hash_script) { // Then search in Rust functions None => { - self.inc_operations(state)?; + self.inc_operations(state, pos)?; // Namespace-qualified Rust functions are indexed in two steps: // 1) Calculate a hash in a similar manner to script-defined functions,