diff --git a/src/api/events.rs b/src/api/events.rs index 64921f97..0f89a582 100644 --- a/src/api/events.rs +++ b/src/api/events.rs @@ -272,7 +272,7 @@ impl Engine { crate::ast::ASTNode, Option<&str>, Position, - ) -> crate::eval::DebuggerCommand + ) -> RhaiResultOf + SendSync + 'static, ) -> &mut Self { diff --git a/src/bin/rhai-dbg.rs b/src/bin/rhai-dbg.rs index b0aa8053..48fe4f76 100644 --- a/src/bin/rhai-dbg.rs +++ b/src/bin/rhai-dbg.rs @@ -90,8 +90,11 @@ fn print_debug_help() { println!( "break <#args> => set a new break-point for a function call with #args arguments" ); + println!("throw [message] => throw an exception (message optional)"); + println!("run => restart the script evaluation from beginning"); println!("step => go to the next expression, diving into functions"); - println!("next => go to the next statement but don't dive into functions"); + println!("over => go to the next expression, skipping oer functions"); + println!("next => go to the next statement, skipping over functions"); println!("continue => continue normal execution"); println!(); } @@ -212,9 +215,9 @@ fn main() { input.clear(); match stdin().read_line(&mut input) { - Ok(0) => break DebuggerCommand::Continue, + Ok(0) => break Ok(DebuggerCommand::Continue), Ok(_) => match input - .trim_end() + .trim() .split_whitespace() .collect::>() .as_slice() @@ -228,9 +231,10 @@ fn main() { println!("{:?} {}@{:?}", node, source.unwrap_or_default(), pos); println!(); } - ["continue", ..] => break DebuggerCommand::Continue, - [] | ["step", ..] => break DebuggerCommand::StepInto, - ["next", ..] => break DebuggerCommand::StepOver, + ["continue", ..] => break Ok(DebuggerCommand::Continue), + [] | ["step", ..] => break Ok(DebuggerCommand::StepInto), + ["over", ..] => break Ok(DebuggerCommand::StepOver), + ["next", ..] => break Ok(DebuggerCommand::Next), ["scope", ..] => print_scope(context.scope()), #[cfg(not(feature = "no_function"))] ["backtrace", ..] => { @@ -406,6 +410,15 @@ fn main() { .break_points_mut() .push(bp); } + ["throw"] => break Err(EvalAltResult::ErrorRuntime(Dynamic::UNIT, pos).into()), + ["throw", _msg, ..] => { + let msg = input.trim().splitn(2, ' ').skip(1).next().unwrap_or(""); + break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into()); + } + ["run", ..] => { + println!("Restarting script..."); + break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into()); + } [cmd, ..] => eprintln!("Invalid debugger command: '{}'", cmd), }, Err(err) => panic!("input error: {}", err), @@ -422,14 +435,19 @@ fn main() { engine.set_module_resolver(resolver); } - // Create scope - let mut scope = Scope::new(); - print_debug_help(); // Evaluate - if let Err(err) = engine.run_ast_with_scope(&mut scope, &main_ast) { - print_error(&script, *err); + while let Err(err) = engine.run_ast_with_scope(&mut Scope::new(), &main_ast) { + match *err { + // Loop back to restart + EvalAltResult::ErrorTerminated(_, _) => (), + // Break evaluation + _ => { + print_error(&script, *err); + break; + } + } } } diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index e4a20447..1a7c0407 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -156,7 +156,7 @@ impl Engine { if !_terminate_chaining => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, _parent, level); + self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?; let mut idx_val_for_setter = idx_val.clone(); let idx_pos = x.lhs.position(); @@ -204,7 +204,7 @@ impl Engine { // xxx[rhs] op= new_val _ if new_val.is_some() => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, _parent, level); + self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?; let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`"); let mut idx_val_for_setter = idx_val.clone(); @@ -249,7 +249,7 @@ impl Engine { // xxx[rhs] _ => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, _parent, level); + self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?; self.get_indexed_mut( global, state, lib, target, idx_val, pos, false, true, level, @@ -270,7 +270,7 @@ impl Engine { #[cfg(feature = "debugging")] let reset_debugger = self.run_debugger_with_reset( scope, global, state, lib, this_ptr, rhs, level, - ); + )?; let result = self.make_method_call( global, state, lib, name, *hashes, target, call_args, *pos, level, @@ -292,7 +292,7 @@ impl Engine { // {xxx:map}.id op= ??? Expr::Property(x) if target.is::() && new_val.is_some() => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, rhs, level); + self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?; let (name, pos) = &x.2; let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`"); @@ -313,7 +313,7 @@ impl Engine { // {xxx:map}.id Expr::Property(x) if target.is::() => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, rhs, level); + self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?; let (name, pos) = &x.2; let index = name.into(); @@ -325,7 +325,7 @@ impl Engine { // xxx.id op= ??? Expr::Property(x) if new_val.is_some() => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, rhs, level); + self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?; let ((getter, hash_get), (setter, hash_set), (name, pos)) = x.as_ref(); let ((mut new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`"); @@ -404,7 +404,7 @@ impl Engine { // xxx.id Expr::Property(x) => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, rhs, level); + self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?; let ((getter, hash_get), _, (name, pos)) = x.as_ref(); let hash = crate::ast::FnCallHashes::from_native(*hash_get); @@ -446,7 +446,7 @@ impl Engine { #[cfg(feature = "debugging")] self.run_debugger( scope, global, state, lib, this_ptr, _node, level, - ); + )?; let (name, pos) = &p.2; let index = name.into(); @@ -462,7 +462,7 @@ impl Engine { #[cfg(feature = "debugging")] let reset_debugger = self.run_debugger_with_reset( scope, global, state, lib, this_ptr, _node, level, - ); + )?; let result = self.make_method_call( global, state, lib, name, *hashes, target, call_args, pos, @@ -499,7 +499,7 @@ impl Engine { #[cfg(feature = "debugging")] self.run_debugger( scope, global, state, lib, this_ptr, _node, level, - ); + )?; let ((getter, hash_get), (setter, hash_set), (name, pos)) = p.as_ref(); @@ -603,7 +603,7 @@ impl Engine { #[cfg(feature = "debugging")] let reset_debugger = self.run_debugger_with_reset( scope, global, state, lib, this_ptr, _node, level, - ); + )?; let result = self.make_method_call( global, state, lib, name, *hashes, target, args, pos, level, @@ -668,7 +668,7 @@ impl Engine { // id.??? or id[???] Expr::Variable(_, var_pos, x) => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, lhs, level); + self.run_debugger(scope, global, state, lib, this_ptr, lhs, level)?; #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut global.num_operations, *var_pos)?; diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index a5f7b83d..597d336e 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -3,19 +3,21 @@ use super::{EvalContext, EvalState, GlobalRuntimeState}; use crate::ast::{ASTNode, Expr, Stmt}; -use crate::{Dynamic, Engine, Identifier, Module, Position, Scope}; +use crate::{Dynamic, Engine, Identifier, Module, Position, RhaiResultOf, Scope}; use std::fmt; #[cfg(feature = "no_std")] use std::prelude::v1::*; /// A standard callback function for debugging. #[cfg(not(feature = "sync"))] -pub type OnDebuggerCallback = - Box, Position) -> DebuggerCommand + 'static>; +pub type OnDebuggerCallback = Box< + dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf + + 'static, +>; /// A standard callback function for debugging. #[cfg(feature = "sync")] pub type OnDebuggerCallback = Box< - dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> DebuggerCommand + dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf + Send + Sync + 'static, @@ -28,8 +30,10 @@ pub enum DebuggerCommand { Continue, // Step into the next expression, diving into functions. StepInto, - // Run to the next statement, stepping over functions. + // Run to the next expression or statement, stepping over functions. StepOver, + // Run to the next statement, skipping over functions. + Next, } /// A break-point for debugging. @@ -318,12 +322,14 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, node: impl Into>, level: usize, - ) { + ) -> RhaiResultOf<()> { if let Some(cmd) = - self.run_debugger_with_reset(scope, global, state, lib, this_ptr, node, level) + self.run_debugger_with_reset(scope, global, state, lib, this_ptr, node, level)? { global.debugger.set_status(cmd); } + + Ok(()) } /// Run the debugger callback. /// @@ -347,18 +353,18 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, node: impl Into>, level: usize, - ) -> Option { + ) -> RhaiResultOf> { if let Some(ref on_debugger) = self.debugger { let node = node.into(); let stop = match global.debugger.status { DebuggerCommand::Continue => false, - DebuggerCommand::StepOver => matches!(node, ASTNode::Stmt(_)), - DebuggerCommand::StepInto => true, + DebuggerCommand::Next => matches!(node, ASTNode::Stmt(_)), + DebuggerCommand::StepInto | DebuggerCommand::StepOver => true, }; if !stop && !global.debugger.is_break_point(&global.source, node) { - return None; + return Ok(None); } let source = global.source.clone(); @@ -378,24 +384,28 @@ impl Engine { level, }; - let command = on_debugger(&mut context, node, source, node.position()); + let command = on_debugger(&mut context, node, source, node.position())?; match command { DebuggerCommand::Continue => { global.debugger.set_status(DebuggerCommand::Continue); - None + Ok(None) + } + DebuggerCommand::Next => { + global.debugger.set_status(DebuggerCommand::Continue); + Ok(Some(DebuggerCommand::Next)) } DebuggerCommand::StepInto => { global.debugger.set_status(DebuggerCommand::StepInto); - None + Ok(None) } DebuggerCommand::StepOver => { global.debugger.set_status(DebuggerCommand::Continue); - Some(DebuggerCommand::StepOver) + Ok(Some(DebuggerCommand::StepOver)) } } } else { - None + Ok(None) } } } diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 1d37be98..33ff3cd8 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -267,7 +267,7 @@ impl Engine { if let Expr::FnCall(x, pos) = expr { #[cfg(feature = "debugging")] let reset_debugger = - self.run_debugger_with_reset(scope, global, state, lib, this_ptr, expr, level); + self.run_debugger_with_reset(scope, global, state, lib, this_ptr, expr, level)?; #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut global.num_operations, expr.position())?; @@ -286,7 +286,7 @@ impl Engine { // will cost more than the mis-predicted `match` branch. if let Expr::Variable(index, var_pos, x) = expr { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, expr, level); + self.run_debugger(scope, global, state, lib, this_ptr, expr, level)?; #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut global.num_operations, expr.position())?; @@ -304,7 +304,7 @@ impl Engine { #[cfg(feature = "debugging")] let reset_debugger = - self.run_debugger_with_reset(scope, global, state, lib, this_ptr, expr, level); + self.run_debugger_with_reset(scope, global, state, lib, this_ptr, expr, level)?; #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut global.num_operations, expr.position())?; diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 6542c58e..7f004070 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -196,7 +196,7 @@ impl Engine { ) -> RhaiResult { #[cfg(feature = "debugging")] let reset_debugger = - self.run_debugger_with_reset(scope, global, state, lib, this_ptr, stmt, level); + self.run_debugger_with_reset(scope, global, state, lib, this_ptr, stmt, level)?; // Coded this way for better branch prediction. // Popular branches are lifted out of the `match` statement into their own branches. diff --git a/src/func/call.rs b/src/func/call.rs index 321481b0..35e8690d 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -890,11 +890,11 @@ impl Engine { Ok(( if let Expr::Stack(slot, _) = arg_expr { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, arg_expr, level); + self.run_debugger(scope, global, state, lib, this_ptr, arg_expr, level)?; constants[*slot].clone() } else if let Some(value) = arg_expr.get_literal_value() { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, arg_expr, level); + self.run_debugger(scope, global, state, lib, this_ptr, arg_expr, level)?; value } else { self.eval_expr(scope, global, state, lib, this_ptr, arg_expr, level)? @@ -1140,7 +1140,7 @@ impl Engine { let first_expr = first_arg.unwrap(); #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, first_expr, level); + self.run_debugger(scope, global, state, lib, this_ptr, first_expr, level)?; // func(x, ...) -> x.func(...) a_expr.iter().try_for_each(|expr| { @@ -1223,7 +1223,7 @@ impl Engine { // &mut first argument and avoid cloning the value if !args_expr.is_empty() && args_expr[0].is_variable_access(true) { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, &args_expr[0], level); + self.run_debugger(scope, global, state, lib, this_ptr, &args_expr[0], level)?; // func(x, ...) -> x.func(...) arg_values.push(Dynamic::UNIT); diff --git a/src/packages/debugging.rs b/src/packages/debugging.rs index 7a1139f4..b23fb005 100644 --- a/src/packages/debugging.rs +++ b/src/packages/debugging.rs @@ -62,7 +62,7 @@ mod debugging_functions { if !pos.is_none() { map.insert("line".into(), (pos.line().unwrap() as INT).into()); map.insert( - "pos".into(), + "position".into(), (pos.position().unwrap_or(0) as INT).into(), ); }