Add commands and status to debugging interface.
This commit is contained in:
@@ -156,7 +156,9 @@ impl Engine {
|
||||
if !_terminate_chaining =>
|
||||
{
|
||||
#[cfg(feature = "debugging")]
|
||||
self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?;
|
||||
if self.debugger.is_some() {
|
||||
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 +206,9 @@ 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)?;
|
||||
if self.debugger.is_some() {
|
||||
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 +253,9 @@ impl Engine {
|
||||
// xxx[rhs]
|
||||
_ => {
|
||||
#[cfg(feature = "debugging")]
|
||||
self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?;
|
||||
if self.debugger.is_some() {
|
||||
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,
|
||||
@@ -268,9 +274,13 @@ impl Engine {
|
||||
let call_args = &mut idx_val.into_fn_call_args();
|
||||
|
||||
#[cfg(feature = "debugging")]
|
||||
let reset_debugger = self.run_debugger_with_reset(
|
||||
scope, global, state, lib, this_ptr, rhs, level,
|
||||
)?;
|
||||
let reset_debugger = if self.debugger.is_some() {
|
||||
self.run_debugger_with_reset(
|
||||
scope, global, state, lib, this_ptr, rhs, level,
|
||||
)?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let result = self.make_method_call(
|
||||
global, state, lib, name, *hashes, target, call_args, *pos, level,
|
||||
@@ -292,7 +302,9 @@ impl Engine {
|
||||
// {xxx:map}.id op= ???
|
||||
Expr::Property(x, pos) if target.is::<crate::Map>() && new_val.is_some() => {
|
||||
#[cfg(feature = "debugging")]
|
||||
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
||||
if self.debugger.is_some() {
|
||||
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
||||
}
|
||||
|
||||
let index = x.2.clone().into();
|
||||
let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
|
||||
@@ -312,7 +324,9 @@ impl Engine {
|
||||
// {xxx:map}.id
|
||||
Expr::Property(x, pos) if target.is::<crate::Map>() => {
|
||||
#[cfg(feature = "debugging")]
|
||||
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
||||
if self.debugger.is_some() {
|
||||
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
||||
}
|
||||
|
||||
let index = x.2.clone().into();
|
||||
let val = self.get_indexed_mut(
|
||||
@@ -323,7 +337,9 @@ impl Engine {
|
||||
// xxx.id op= ???
|
||||
Expr::Property(x, pos) if new_val.is_some() => {
|
||||
#[cfg(feature = "debugging")]
|
||||
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
||||
if self.debugger.is_some() {
|
||||
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
||||
}
|
||||
|
||||
let ((getter, hash_get), (setter, hash_set), name) = x.as_ref();
|
||||
let ((mut new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
|
||||
@@ -402,7 +418,9 @@ impl Engine {
|
||||
// xxx.id
|
||||
Expr::Property(x, pos) => {
|
||||
#[cfg(feature = "debugging")]
|
||||
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
||||
if self.debugger.is_some() {
|
||||
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
||||
}
|
||||
|
||||
let ((getter, hash_get), _, name) = x.as_ref();
|
||||
let hash = crate::ast::FnCallHashes::from_native(*hash_get);
|
||||
@@ -442,9 +460,11 @@ impl Engine {
|
||||
let val_target = &mut match x.lhs {
|
||||
Expr::Property(ref p, pos) => {
|
||||
#[cfg(feature = "debugging")]
|
||||
self.run_debugger(
|
||||
scope, global, state, lib, this_ptr, _node, level,
|
||||
)?;
|
||||
if self.debugger.is_some() {
|
||||
self.run_debugger(
|
||||
scope, global, state, lib, this_ptr, _node, level,
|
||||
)?;
|
||||
}
|
||||
|
||||
let index = p.2.clone().into();
|
||||
self.get_indexed_mut(
|
||||
@@ -457,9 +477,13 @@ impl Engine {
|
||||
let call_args = &mut idx_val.into_fn_call_args();
|
||||
|
||||
#[cfg(feature = "debugging")]
|
||||
let reset_debugger = self.run_debugger_with_reset(
|
||||
scope, global, state, lib, this_ptr, _node, level,
|
||||
)?;
|
||||
let reset_debugger = if self.debugger.is_some() {
|
||||
self.run_debugger_with_reset(
|
||||
scope, global, state, lib, this_ptr, _node, level,
|
||||
)?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let result = self.make_method_call(
|
||||
global, state, lib, name, *hashes, target, call_args, pos,
|
||||
@@ -494,9 +518,11 @@ impl Engine {
|
||||
// xxx.prop[expr] | xxx.prop.expr
|
||||
Expr::Property(ref p, pos) => {
|
||||
#[cfg(feature = "debugging")]
|
||||
self.run_debugger(
|
||||
scope, global, state, lib, this_ptr, _node, level,
|
||||
)?;
|
||||
if self.debugger.is_some() {
|
||||
self.run_debugger(
|
||||
scope, global, state, lib, this_ptr, _node, level,
|
||||
)?;
|
||||
}
|
||||
|
||||
let ((getter, hash_get), (setter, hash_set), name) = p.as_ref();
|
||||
let rhs_chain = rhs.into();
|
||||
@@ -597,9 +623,13 @@ impl Engine {
|
||||
let args = &mut idx_val.into_fn_call_args();
|
||||
|
||||
#[cfg(feature = "debugging")]
|
||||
let reset_debugger = self.run_debugger_with_reset(
|
||||
scope, global, state, lib, this_ptr, _node, level,
|
||||
)?;
|
||||
let reset_debugger = if self.debugger.is_some() {
|
||||
self.run_debugger_with_reset(
|
||||
scope, global, state, lib, this_ptr, _node, level,
|
||||
)?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let result = self.make_method_call(
|
||||
global, state, lib, name, *hashes, target, args, pos, level,
|
||||
@@ -664,7 +694,9 @@ impl Engine {
|
||||
// id.??? or id[???]
|
||||
Expr::Variable(_, var_pos, x) => {
|
||||
#[cfg(feature = "debugging")]
|
||||
self.run_debugger(scope, global, state, lib, this_ptr, lhs, level)?;
|
||||
if self.debugger.is_some() {
|
||||
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,7 +3,7 @@
|
||||
|
||||
use super::{EvalContext, EvalState, GlobalRuntimeState};
|
||||
use crate::ast::{ASTNode, Expr, Stmt};
|
||||
use crate::{Dynamic, Engine, Identifier, Module, Position, RhaiResultOf, Scope};
|
||||
use crate::{Dynamic, Engine, EvalAltResult, Identifier, Module, Position, RhaiResultOf, Scope};
|
||||
use std::fmt;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@@ -17,11 +17,22 @@ pub type OnDebuggingInit = dyn Fn() -> Dynamic + Send + Sync;
|
||||
|
||||
/// Callback function for debugging.
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub type OnDebuggerCallback =
|
||||
dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>;
|
||||
pub type OnDebuggerCallback = dyn Fn(
|
||||
&mut EvalContext,
|
||||
DebuggerEvent,
|
||||
ASTNode,
|
||||
Option<&str>,
|
||||
Position,
|
||||
) -> RhaiResultOf<DebuggerCommand>;
|
||||
/// Callback function for debugging.
|
||||
#[cfg(feature = "sync")]
|
||||
pub type OnDebuggerCallback = dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>
|
||||
pub type OnDebuggerCallback = dyn Fn(
|
||||
&mut EvalContext,
|
||||
DebuggerEvent,
|
||||
ASTNode,
|
||||
Option<&str>,
|
||||
Position,
|
||||
) -> RhaiResultOf<DebuggerCommand>
|
||||
+ Send
|
||||
+ Sync;
|
||||
|
||||
@@ -36,6 +47,30 @@ pub enum DebuggerCommand {
|
||||
StepOver,
|
||||
// Run to the next statement, skipping over functions.
|
||||
Next,
|
||||
// Run to the end of the current function call.
|
||||
FunctionExit,
|
||||
}
|
||||
|
||||
/// The debugger status.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub enum DebuggerStatus {
|
||||
// Stop at the next statement or expression.
|
||||
Next(bool, bool),
|
||||
// Run to the end of the current function call.
|
||||
FunctionExit(usize),
|
||||
}
|
||||
|
||||
/// A event that triggers the debugger.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum DebuggerEvent<'a> {
|
||||
// Break on next step.
|
||||
Step,
|
||||
// Break on break-point.
|
||||
BreakPoint(usize),
|
||||
// Return from a function with a value.
|
||||
FunctionExitWithValue(&'a Dynamic),
|
||||
// Return from a function with a value.
|
||||
FunctionExitWithError(&'a EvalAltResult),
|
||||
}
|
||||
|
||||
/// A break-point for debugging.
|
||||
@@ -198,7 +233,7 @@ impl fmt::Display for CallStackFrame {
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct Debugger {
|
||||
/// The current status command.
|
||||
status: DebuggerCommand,
|
||||
status: DebuggerStatus,
|
||||
/// The current state.
|
||||
state: Dynamic,
|
||||
/// The current set of break-points.
|
||||
@@ -215,9 +250,9 @@ impl Debugger {
|
||||
pub fn new(engine: &Engine) -> Self {
|
||||
Self {
|
||||
status: if engine.debugger.is_some() {
|
||||
DebuggerCommand::StepInto
|
||||
DebuggerStatus::Next(true, true)
|
||||
} else {
|
||||
DebuggerCommand::Continue
|
||||
DebuggerStatus::Next(false, false)
|
||||
},
|
||||
state: if let Some((ref init, _)) = engine.debugger {
|
||||
init()
|
||||
@@ -280,31 +315,26 @@ impl Debugger {
|
||||
/// Get the current status of this [`Debugger`].
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn status(&self) -> DebuggerCommand {
|
||||
pub(crate) fn status(&self) -> DebuggerStatus {
|
||||
self.status
|
||||
}
|
||||
/// Get a mutable reference to the current status of this [`Debugger`].
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn status_mut(&mut self) -> &mut DebuggerCommand {
|
||||
&mut self.status
|
||||
}
|
||||
/// Set the status of this [`Debugger`].
|
||||
#[inline(always)]
|
||||
pub fn reset_status(&mut self, status: Option<DebuggerCommand>) {
|
||||
pub(crate) fn reset_status(&mut self, status: Option<DebuggerStatus>) {
|
||||
if let Some(cmd) = status {
|
||||
self.status = cmd;
|
||||
}
|
||||
}
|
||||
/// Does a particular [`AST` Node][ASTNode] trigger a break-point?
|
||||
/// Returns the first break-point triggered by a particular [`AST` Node][ASTNode].
|
||||
#[must_use]
|
||||
pub fn is_break_point(&self, src: &str, node: ASTNode) -> bool {
|
||||
pub fn is_break_point(&self, src: &str, node: ASTNode) -> Option<usize> {
|
||||
let _src = src;
|
||||
|
||||
self.break_points()
|
||||
.iter()
|
||||
.filter(|&bp| bp.is_enabled())
|
||||
.any(|bp| match bp {
|
||||
.enumerate()
|
||||
.filter(|&(_, bp)| bp.is_enabled())
|
||||
.find(|&(_, bp)| match bp {
|
||||
#[cfg(not(feature = "no_position"))]
|
||||
BreakPoint::AtPosition { pos, .. } if pos.is_none() => false,
|
||||
#[cfg(not(feature = "no_position"))]
|
||||
@@ -333,6 +363,7 @@ impl Debugger {
|
||||
_ => false,
|
||||
},
|
||||
})
|
||||
.map(|(i, _)| i)
|
||||
}
|
||||
/// Get a slice of all [`BreakPoint`]'s.
|
||||
#[inline(always)]
|
||||
@@ -371,14 +402,9 @@ impl Engine {
|
||||
}
|
||||
/// Run the debugger callback.
|
||||
///
|
||||
/// Returns `true` if the debugger needs to be reactivated at the end of the block, statement or
|
||||
/// Returns `Some` if the debugger needs to be reactivated at the end of the block, statement or
|
||||
/// function call.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// When the debugger callback return [`DebuggerCommand::StepOver`], the debugger if temporarily
|
||||
/// disabled and `true` is returned.
|
||||
///
|
||||
/// It is up to the [`Engine`] to reactivate the debugger.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
@@ -391,61 +417,94 @@ impl Engine {
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
node: impl Into<ASTNode<'a>>,
|
||||
level: usize,
|
||||
) -> RhaiResultOf<Option<DebuggerCommand>> {
|
||||
if let Some((_, ref on_debugger)) = self.debugger {
|
||||
let node = node.into();
|
||||
) -> RhaiResultOf<Option<DebuggerStatus>> {
|
||||
let node = node.into();
|
||||
|
||||
// Skip transitive nodes
|
||||
match node {
|
||||
ASTNode::Expr(Expr::Stmt(_)) | ASTNode::Stmt(Stmt::Expr(_)) => return Ok(None),
|
||||
_ => (),
|
||||
}
|
||||
// Skip transitive nodes
|
||||
match node {
|
||||
ASTNode::Expr(Expr::Stmt(_)) | ASTNode::Stmt(Stmt::Expr(_)) => return Ok(None),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let stop = match global.debugger.status {
|
||||
DebuggerCommand::Continue => false,
|
||||
DebuggerCommand::Next => matches!(node, ASTNode::Stmt(_)),
|
||||
DebuggerCommand::StepInto | DebuggerCommand::StepOver => true,
|
||||
};
|
||||
let stop = match global.debugger.status {
|
||||
DebuggerStatus::Next(false, false) => false,
|
||||
DebuggerStatus::Next(true, false) => matches!(node, ASTNode::Stmt(_)),
|
||||
DebuggerStatus::Next(false, true) => matches!(node, ASTNode::Expr(_)),
|
||||
DebuggerStatus::Next(true, true) => true,
|
||||
DebuggerStatus::FunctionExit(_) => false,
|
||||
};
|
||||
|
||||
if !stop && !global.debugger.is_break_point(&global.source, node) {
|
||||
let event = if stop {
|
||||
DebuggerEvent::Step
|
||||
} else {
|
||||
if let Some(bp) = global.debugger.is_break_point(&global.source, node) {
|
||||
DebuggerEvent::BreakPoint(bp)
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
let source = global.source.clone();
|
||||
let source = if source.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(source.as_str())
|
||||
};
|
||||
self.run_debugger_raw(scope, global, state, lib, this_ptr, node, event, level)
|
||||
}
|
||||
/// Run the debugger callback unconditionally.
|
||||
///
|
||||
/// Returns `Some` if the debugger needs to be reactivated at the end of the block, statement or
|
||||
/// function call.
|
||||
///
|
||||
/// It is up to the [`Engine`] to reactivate the debugger.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn run_debugger_raw<'a>(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
global: &mut GlobalRuntimeState,
|
||||
state: &mut EvalState,
|
||||
lib: &[&Module],
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
node: ASTNode<'a>,
|
||||
event: DebuggerEvent,
|
||||
level: usize,
|
||||
) -> Result<Option<DebuggerStatus>, Box<crate::EvalAltResult>> {
|
||||
let source = global.source.clone();
|
||||
let source = if source.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(source.as_str())
|
||||
};
|
||||
|
||||
let mut context = crate::EvalContext {
|
||||
engine: self,
|
||||
scope,
|
||||
global,
|
||||
state,
|
||||
lib,
|
||||
this_ptr,
|
||||
level,
|
||||
};
|
||||
let mut context = crate::EvalContext {
|
||||
engine: self,
|
||||
scope,
|
||||
global,
|
||||
state,
|
||||
lib,
|
||||
this_ptr,
|
||||
level,
|
||||
};
|
||||
|
||||
let command = on_debugger(&mut context, node, source, node.position())?;
|
||||
if let Some((_, ref on_debugger)) = self.debugger {
|
||||
let command = on_debugger(&mut context, event, node, source, node.position())?;
|
||||
|
||||
match command {
|
||||
DebuggerCommand::Continue => {
|
||||
global.debugger.status = DebuggerCommand::Continue;
|
||||
global.debugger.status = DebuggerStatus::Next(false, false);
|
||||
Ok(None)
|
||||
}
|
||||
DebuggerCommand::Next => {
|
||||
global.debugger.status = DebuggerCommand::Continue;
|
||||
Ok(Some(DebuggerCommand::Next))
|
||||
}
|
||||
DebuggerCommand::StepInto => {
|
||||
global.debugger.status = DebuggerCommand::StepInto;
|
||||
Ok(None)
|
||||
global.debugger.status = DebuggerStatus::Next(false, false);
|
||||
Ok(Some(DebuggerStatus::Next(true, false)))
|
||||
}
|
||||
DebuggerCommand::StepOver => {
|
||||
global.debugger.status = DebuggerCommand::Continue;
|
||||
Ok(Some(DebuggerCommand::StepOver))
|
||||
global.debugger.status = DebuggerStatus::Next(false, false);
|
||||
Ok(Some(DebuggerStatus::Next(true, true)))
|
||||
}
|
||||
DebuggerCommand::StepInto => {
|
||||
global.debugger.status = DebuggerStatus::Next(true, true);
|
||||
Ok(None)
|
||||
}
|
||||
DebuggerCommand::FunctionExit => {
|
||||
global.debugger.status = DebuggerStatus::FunctionExit(context.call_level());
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@@ -266,8 +266,11 @@ impl Engine {
|
||||
// binary operators are also function calls.
|
||||
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)?;
|
||||
let reset_debugger = if self.debugger.is_some() {
|
||||
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, expr, level)?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
self.inc_operations(&mut global.num_operations, expr.position())?;
|
||||
@@ -286,7 +289,9 @@ 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)?;
|
||||
if self.debugger.is_some() {
|
||||
self.run_debugger(scope, global, state, lib, this_ptr, expr, level)?;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
self.inc_operations(&mut global.num_operations, expr.position())?;
|
||||
@@ -303,8 +308,11 @@ impl Engine {
|
||||
}
|
||||
|
||||
#[cfg(feature = "debugging")]
|
||||
let reset_debugger =
|
||||
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, expr, level)?;
|
||||
let reset_debugger = if self.debugger.is_some() {
|
||||
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, expr, level)?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
self.inc_operations(&mut global.num_operations, expr.position())?;
|
||||
|
@@ -14,7 +14,10 @@ pub use chaining::{ChainArgument, ChainType};
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub use debugger::CallStackFrame;
|
||||
#[cfg(feature = "debugging")]
|
||||
pub use debugger::{BreakPoint, Debugger, DebuggerCommand, OnDebuggerCallback, OnDebuggingInit};
|
||||
pub use debugger::{
|
||||
BreakPoint, Debugger, DebuggerCommand, DebuggerEvent, DebuggerStatus, OnDebuggerCallback,
|
||||
OnDebuggingInit,
|
||||
};
|
||||
pub use eval_context::EvalContext;
|
||||
pub use eval_state::EvalState;
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
|
@@ -202,8 +202,11 @@ impl Engine {
|
||||
level: usize,
|
||||
) -> RhaiResult {
|
||||
#[cfg(feature = "debugging")]
|
||||
let reset_debugger =
|
||||
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, stmt, level)?;
|
||||
let reset_debugger = if self.debugger.is_some() {
|
||||
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, stmt, level)?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Coded this way for better branch prediction.
|
||||
// Popular branches are lifted out of the `match` statement into their own branches.
|
||||
|
Reference in New Issue
Block a user