Add ability for debugger to throw errors.
This commit is contained in:
parent
b4f679d35f
commit
3c2ac7f0c5
@ -272,7 +272,7 @@ impl Engine {
|
||||
crate::ast::ASTNode,
|
||||
Option<&str>,
|
||||
Position,
|
||||
) -> crate::eval::DebuggerCommand
|
||||
) -> RhaiResultOf<crate::eval::DebuggerCommand>
|
||||
+ SendSync
|
||||
+ 'static,
|
||||
) -> &mut Self {
|
||||
|
@ -90,8 +90,11 @@ fn print_debug_help() {
|
||||
println!(
|
||||
"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!("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::<Vec<_>>()
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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::<crate::Map>() && 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::<crate::Map>() => {
|
||||
#[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)?;
|
||||
|
@ -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<dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> DebuggerCommand + 'static>;
|
||||
pub type OnDebuggerCallback = Box<
|
||||
dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>
|
||||
+ '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<DebuggerCommand>
|
||||
+ 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<ASTNode<'a>>,
|
||||
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<ASTNode<'a>>,
|
||||
level: usize,
|
||||
) -> Option<DebuggerCommand> {
|
||||
) -> RhaiResultOf<Option<DebuggerCommand>> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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())?;
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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(),
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user