Add ability for debugger to throw errors.

This commit is contained in:
Stephen Chung 2022-01-25 21:31:35 +08:00
parent b4f679d35f
commit 3c2ac7f0c5
8 changed files with 78 additions and 50 deletions

View File

@ -272,7 +272,7 @@ impl Engine {
crate::ast::ASTNode, crate::ast::ASTNode,
Option<&str>, Option<&str>,
Position, Position,
) -> crate::eval::DebuggerCommand ) -> RhaiResultOf<crate::eval::DebuggerCommand>
+ SendSync + SendSync
+ 'static, + 'static,
) -> &mut Self { ) -> &mut Self {

View File

@ -90,8 +90,11 @@ fn print_debug_help() {
println!( println!(
"break <func> <#args> => set a new break-point for a function call with #args arguments" "break <func> <#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!("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!("continue => continue normal execution");
println!(); println!();
} }
@ -212,9 +215,9 @@ fn main() {
input.clear(); input.clear();
match stdin().read_line(&mut input) { match stdin().read_line(&mut input) {
Ok(0) => break DebuggerCommand::Continue, Ok(0) => break Ok(DebuggerCommand::Continue),
Ok(_) => match input Ok(_) => match input
.trim_end() .trim()
.split_whitespace() .split_whitespace()
.collect::<Vec<_>>() .collect::<Vec<_>>()
.as_slice() .as_slice()
@ -228,9 +231,10 @@ fn main() {
println!("{:?} {}@{:?}", node, source.unwrap_or_default(), pos); println!("{:?} {}@{:?}", node, source.unwrap_or_default(), pos);
println!(); println!();
} }
["continue", ..] => break DebuggerCommand::Continue, ["continue", ..] => break Ok(DebuggerCommand::Continue),
[] | ["step", ..] => break DebuggerCommand::StepInto, [] | ["step", ..] => break Ok(DebuggerCommand::StepInto),
["next", ..] => break DebuggerCommand::StepOver, ["over", ..] => break Ok(DebuggerCommand::StepOver),
["next", ..] => break Ok(DebuggerCommand::Next),
["scope", ..] => print_scope(context.scope()), ["scope", ..] => print_scope(context.scope()),
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
["backtrace", ..] => { ["backtrace", ..] => {
@ -406,6 +410,15 @@ fn main() {
.break_points_mut() .break_points_mut()
.push(bp); .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), [cmd, ..] => eprintln!("Invalid debugger command: '{}'", cmd),
}, },
Err(err) => panic!("input error: {}", err), Err(err) => panic!("input error: {}", err),
@ -422,14 +435,19 @@ fn main() {
engine.set_module_resolver(resolver); engine.set_module_resolver(resolver);
} }
// Create scope
let mut scope = Scope::new();
print_debug_help(); print_debug_help();
// Evaluate // Evaluate
if let Err(err) = engine.run_ast_with_scope(&mut scope, &main_ast) { while let Err(err) = engine.run_ast_with_scope(&mut Scope::new(), &main_ast) {
print_error(&script, *err); match *err {
// Loop back to restart
EvalAltResult::ErrorTerminated(_, _) => (),
// Break evaluation
_ => {
print_error(&script, *err);
break;
}
}
} }
} }

View File

@ -156,7 +156,7 @@ impl Engine {
if !_terminate_chaining => if !_terminate_chaining =>
{ {
#[cfg(feature = "debugging")] #[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 mut idx_val_for_setter = idx_val.clone();
let idx_pos = x.lhs.position(); let idx_pos = x.lhs.position();
@ -204,7 +204,7 @@ impl Engine {
// xxx[rhs] op= new_val // xxx[rhs] op= new_val
_ if new_val.is_some() => { _ if new_val.is_some() => {
#[cfg(feature = "debugging")] #[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 ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
let mut idx_val_for_setter = idx_val.clone(); let mut idx_val_for_setter = idx_val.clone();
@ -249,7 +249,7 @@ impl Engine {
// xxx[rhs] // xxx[rhs]
_ => { _ => {
#[cfg(feature = "debugging")] #[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( self.get_indexed_mut(
global, state, lib, target, idx_val, pos, false, true, level, global, state, lib, target, idx_val, pos, false, true, level,
@ -270,7 +270,7 @@ impl Engine {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let reset_debugger = self.run_debugger_with_reset( let reset_debugger = self.run_debugger_with_reset(
scope, global, state, lib, this_ptr, rhs, level, scope, global, state, lib, this_ptr, rhs, level,
); )?;
let result = self.make_method_call( let result = self.make_method_call(
global, state, lib, name, *hashes, target, call_args, *pos, level, global, state, lib, name, *hashes, target, call_args, *pos, level,
@ -292,7 +292,7 @@ impl Engine {
// {xxx:map}.id op= ??? // {xxx:map}.id op= ???
Expr::Property(x) if target.is::<crate::Map>() && new_val.is_some() => { Expr::Property(x) if target.is::<crate::Map>() && new_val.is_some() => {
#[cfg(feature = "debugging")] #[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 (name, pos) = &x.2;
let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`"); let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
@ -313,7 +313,7 @@ impl Engine {
// {xxx:map}.id // {xxx:map}.id
Expr::Property(x) if target.is::<crate::Map>() => { Expr::Property(x) if target.is::<crate::Map>() => {
#[cfg(feature = "debugging")] #[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 (name, pos) = &x.2;
let index = name.into(); let index = name.into();
@ -325,7 +325,7 @@ impl Engine {
// xxx.id op= ??? // xxx.id op= ???
Expr::Property(x) if new_val.is_some() => { Expr::Property(x) if new_val.is_some() => {
#[cfg(feature = "debugging")] #[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 ((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`"); let ((mut new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
@ -404,7 +404,7 @@ impl Engine {
// xxx.id // xxx.id
Expr::Property(x) => { Expr::Property(x) => {
#[cfg(feature = "debugging")] #[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 ((getter, hash_get), _, (name, pos)) = x.as_ref();
let hash = crate::ast::FnCallHashes::from_native(*hash_get); let hash = crate::ast::FnCallHashes::from_native(*hash_get);
@ -446,7 +446,7 @@ impl Engine {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
self.run_debugger( self.run_debugger(
scope, global, state, lib, this_ptr, _node, level, scope, global, state, lib, this_ptr, _node, level,
); )?;
let (name, pos) = &p.2; let (name, pos) = &p.2;
let index = name.into(); let index = name.into();
@ -462,7 +462,7 @@ impl Engine {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let reset_debugger = self.run_debugger_with_reset( let reset_debugger = self.run_debugger_with_reset(
scope, global, state, lib, this_ptr, _node, level, scope, global, state, lib, this_ptr, _node, level,
); )?;
let result = self.make_method_call( let result = self.make_method_call(
global, state, lib, name, *hashes, target, call_args, pos, global, state, lib, name, *hashes, target, call_args, pos,
@ -499,7 +499,7 @@ impl Engine {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
self.run_debugger( self.run_debugger(
scope, global, state, lib, this_ptr, _node, level, scope, global, state, lib, this_ptr, _node, level,
); )?;
let ((getter, hash_get), (setter, hash_set), (name, pos)) = let ((getter, hash_get), (setter, hash_set), (name, pos)) =
p.as_ref(); p.as_ref();
@ -603,7 +603,7 @@ impl Engine {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let reset_debugger = self.run_debugger_with_reset( let reset_debugger = self.run_debugger_with_reset(
scope, global, state, lib, this_ptr, _node, level, scope, global, state, lib, this_ptr, _node, level,
); )?;
let result = self.make_method_call( let result = self.make_method_call(
global, state, lib, name, *hashes, target, args, pos, level, global, state, lib, name, *hashes, target, args, pos, level,
@ -668,7 +668,7 @@ impl Engine {
// id.??? or id[???] // id.??? or id[???]
Expr::Variable(_, var_pos, x) => { Expr::Variable(_, var_pos, x) => {
#[cfg(feature = "debugging")] #[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"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut global.num_operations, *var_pos)?; self.inc_operations(&mut global.num_operations, *var_pos)?;

View File

@ -3,19 +3,21 @@
use super::{EvalContext, EvalState, GlobalRuntimeState}; use super::{EvalContext, EvalState, GlobalRuntimeState};
use crate::ast::{ASTNode, Expr, Stmt}; 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; use std::fmt;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
/// A standard callback function for debugging. /// A standard callback function for debugging.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type OnDebuggerCallback = pub type OnDebuggerCallback = Box<
Box<dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> DebuggerCommand + 'static>; dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>
+ 'static,
>;
/// A standard callback function for debugging. /// A standard callback function for debugging.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnDebuggerCallback = Box< pub type OnDebuggerCallback = Box<
dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> DebuggerCommand dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>
+ Send + Send
+ Sync + Sync
+ 'static, + 'static,
@ -28,8 +30,10 @@ pub enum DebuggerCommand {
Continue, Continue,
// Step into the next expression, diving into functions. // Step into the next expression, diving into functions.
StepInto, StepInto,
// Run to the next statement, stepping over functions. // Run to the next expression or statement, stepping over functions.
StepOver, StepOver,
// Run to the next statement, skipping over functions.
Next,
} }
/// A break-point for debugging. /// A break-point for debugging.
@ -318,12 +322,14 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
node: impl Into<ASTNode<'a>>, node: impl Into<ASTNode<'a>>,
level: usize, level: usize,
) { ) -> RhaiResultOf<()> {
if let Some(cmd) = 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); global.debugger.set_status(cmd);
} }
Ok(())
} }
/// Run the debugger callback. /// Run the debugger callback.
/// ///
@ -347,18 +353,18 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
node: impl Into<ASTNode<'a>>, node: impl Into<ASTNode<'a>>,
level: usize, level: usize,
) -> Option<DebuggerCommand> { ) -> RhaiResultOf<Option<DebuggerCommand>> {
if let Some(ref on_debugger) = self.debugger { if let Some(ref on_debugger) = self.debugger {
let node = node.into(); let node = node.into();
let stop = match global.debugger.status { let stop = match global.debugger.status {
DebuggerCommand::Continue => false, DebuggerCommand::Continue => false,
DebuggerCommand::StepOver => matches!(node, ASTNode::Stmt(_)), DebuggerCommand::Next => matches!(node, ASTNode::Stmt(_)),
DebuggerCommand::StepInto => true, DebuggerCommand::StepInto | DebuggerCommand::StepOver => true,
}; };
if !stop && !global.debugger.is_break_point(&global.source, node) { if !stop && !global.debugger.is_break_point(&global.source, node) {
return None; return Ok(None);
} }
let source = global.source.clone(); let source = global.source.clone();
@ -378,24 +384,28 @@ impl Engine {
level, level,
}; };
let command = on_debugger(&mut context, node, source, node.position()); let command = on_debugger(&mut context, node, source, node.position())?;
match command { match command {
DebuggerCommand::Continue => { DebuggerCommand::Continue => {
global.debugger.set_status(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 => { DebuggerCommand::StepInto => {
global.debugger.set_status(DebuggerCommand::StepInto); global.debugger.set_status(DebuggerCommand::StepInto);
None Ok(None)
} }
DebuggerCommand::StepOver => { DebuggerCommand::StepOver => {
global.debugger.set_status(DebuggerCommand::Continue); global.debugger.set_status(DebuggerCommand::Continue);
Some(DebuggerCommand::StepOver) Ok(Some(DebuggerCommand::StepOver))
} }
} }
} else { } else {
None Ok(None)
} }
} }
} }

View File

@ -267,7 +267,7 @@ impl Engine {
if let Expr::FnCall(x, pos) = expr { if let Expr::FnCall(x, pos) = expr {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let reset_debugger = 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"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut global.num_operations, expr.position())?; self.inc_operations(&mut global.num_operations, expr.position())?;
@ -286,7 +286,7 @@ impl Engine {
// will cost more than the mis-predicted `match` branch. // will cost more than the mis-predicted `match` branch.
if let Expr::Variable(index, var_pos, x) = expr { if let Expr::Variable(index, var_pos, x) = expr {
#[cfg(feature = "debugging")] #[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"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut global.num_operations, expr.position())?; self.inc_operations(&mut global.num_operations, expr.position())?;
@ -304,7 +304,7 @@ impl Engine {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let reset_debugger = 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"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut global.num_operations, expr.position())?; self.inc_operations(&mut global.num_operations, expr.position())?;

View File

@ -196,7 +196,7 @@ impl Engine {
) -> RhaiResult { ) -> RhaiResult {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let reset_debugger = 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. // Coded this way for better branch prediction.
// Popular branches are lifted out of the `match` statement into their own branches. // Popular branches are lifted out of the `match` statement into their own branches.

View File

@ -890,11 +890,11 @@ impl Engine {
Ok(( Ok((
if let Expr::Stack(slot, _) = arg_expr { if let Expr::Stack(slot, _) = arg_expr {
#[cfg(feature = "debugging")] #[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() constants[*slot].clone()
} else if let Some(value) = arg_expr.get_literal_value() { } else if let Some(value) = arg_expr.get_literal_value() {
#[cfg(feature = "debugging")] #[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 value
} else { } else {
self.eval_expr(scope, global, state, lib, this_ptr, arg_expr, level)? self.eval_expr(scope, global, state, lib, this_ptr, arg_expr, level)?
@ -1140,7 +1140,7 @@ impl Engine {
let first_expr = first_arg.unwrap(); let first_expr = first_arg.unwrap();
#[cfg(feature = "debugging")] #[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(...) // func(x, ...) -> x.func(...)
a_expr.iter().try_for_each(|expr| { a_expr.iter().try_for_each(|expr| {
@ -1223,7 +1223,7 @@ impl Engine {
// &mut first argument and avoid cloning the value // &mut first argument and avoid cloning the value
if !args_expr.is_empty() && args_expr[0].is_variable_access(true) { if !args_expr.is_empty() && args_expr[0].is_variable_access(true) {
#[cfg(feature = "debugging")] #[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(...) // func(x, ...) -> x.func(...)
arg_values.push(Dynamic::UNIT); arg_values.push(Dynamic::UNIT);

View File

@ -62,7 +62,7 @@ mod debugging_functions {
if !pos.is_none() { if !pos.is_none() {
map.insert("line".into(), (pos.line().unwrap() as INT).into()); map.insert("line".into(), (pos.line().unwrap() as INT).into());
map.insert( map.insert(
"pos".into(), "position".into(),
(pos.position().unwrap_or(0) as INT).into(), (pos.position().unwrap_or(0) as INT).into(),
); );
} }