rhai/src/eval/debugger.rs

439 lines
14 KiB
Rust
Raw Normal View History

2022-01-24 10:04:40 +01:00
//! Module defining the debugging interface.
#![cfg(feature = "debugging")]
use super::{EvalContext, EvalState, GlobalRuntimeState};
use crate::ast::{ASTNode, Expr, Stmt};
use crate::{Dynamic, Engine, Identifier, Module, Position, RhaiResultOf, Scope};
2022-01-24 10:04:40 +01:00
use std::fmt;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
/// A standard callback function for debugging.
#[cfg(not(feature = "sync"))]
2022-01-27 16:55:32 +01:00
pub type OnDebuggerCallback =
dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>;
2022-01-24 10:04:40 +01:00
/// A standard callback function for debugging.
#[cfg(feature = "sync")]
2022-01-27 16:55:32 +01:00
pub type OnDebuggerCallback = dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>
+ Send
+ Sync;
2022-01-24 10:04:40 +01:00
/// A command for the debugger on the next iteration.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum DebuggerCommand {
// Continue normal execution.
Continue,
// Step into the next expression, diving into functions.
StepInto,
// Run to the next expression or statement, stepping over functions.
2022-01-24 10:04:40 +01:00
StepOver,
// Run to the next statement, skipping over functions.
Next,
2022-01-24 10:04:40 +01:00
}
/// A break-point for debugging.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum BreakPoint {
/// Break at a particular position under a particular source.
2022-01-25 16:59:35 +01:00
///
2022-01-24 10:04:40 +01:00
/// Not available under `no_position`.
///
/// Source is empty if not available.
#[cfg(not(feature = "no_position"))]
2022-01-25 10:29:34 +01:00
AtPosition {
source: Identifier,
pos: Position,
enabled: bool,
},
2022-01-24 10:04:40 +01:00
/// Break at a particular function call.
2022-01-25 10:29:34 +01:00
AtFunctionName { name: Identifier, enabled: bool },
2022-01-24 10:04:40 +01:00
/// Break at a particular function call with a particular number of arguments.
2022-01-25 10:29:34 +01:00
AtFunctionCall {
name: Identifier,
args: usize,
enabled: bool,
},
2022-01-25 11:21:05 +01:00
/// Break at a particular property .
2022-01-25 16:59:35 +01:00
///
/// Not available under `no_object`.
#[cfg(not(feature = "no_object"))]
2022-01-25 11:21:05 +01:00
AtProperty { name: Identifier, enabled: bool },
2022-01-24 10:04:40 +01:00
}
impl fmt::Display for BreakPoint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
2022-01-25 16:59:35 +01:00
#[cfg(not(feature = "no_position"))]
2022-01-25 10:29:34 +01:00
Self::AtPosition {
source,
pos,
enabled,
} => {
2022-01-24 10:04:40 +01:00
if !source.is_empty() {
2022-01-25 10:29:34 +01:00
write!(f, "{} @ {:?}", source, pos)?;
2022-01-24 10:04:40 +01:00
} else {
2022-01-25 10:29:34 +01:00
write!(f, "@ {:?}", pos)?;
}
if !*enabled {
f.write_str(" (disabled)")?;
}
Ok(())
}
Self::AtFunctionName {
name: fn_name,
enabled,
} => {
write!(f, "{} (...)", fn_name)?;
if !*enabled {
f.write_str(" (disabled)")?;
2022-01-24 10:04:40 +01:00
}
2022-01-25 10:29:34 +01:00
Ok(())
2022-01-24 10:04:40 +01:00
}
2022-01-25 05:24:30 +01:00
Self::AtFunctionCall {
name: fn_name,
args,
2022-01-25 10:29:34 +01:00
enabled,
} => {
write!(
f,
"{} ({})",
fn_name,
std::iter::repeat("_")
.take(*args)
.collect::<Vec<_>>()
.join(", ")
)?;
if !*enabled {
f.write_str(" (disabled)")?;
}
Ok(())
}
2022-01-25 16:59:35 +01:00
#[cfg(not(feature = "no_object"))]
2022-01-25 11:21:05 +01:00
Self::AtProperty {
name: prop,
enabled,
} => {
write!(f, ".{}", prop)?;
if !*enabled {
f.write_str(" (disabled)")?;
}
Ok(())
}
2022-01-25 10:29:34 +01:00
}
}
}
impl BreakPoint {
/// Is this [`BreakPoint`] enabled?
#[inline(always)]
pub fn is_enabled(&self) -> bool {
match self {
#[cfg(not(feature = "no_position"))]
Self::AtPosition { enabled, .. } => *enabled,
2022-01-25 16:59:35 +01:00
Self::AtFunctionName { enabled, .. } | Self::AtFunctionCall { enabled, .. } => *enabled,
#[cfg(not(feature = "no_object"))]
Self::AtProperty { enabled, .. } => *enabled,
2022-01-25 10:29:34 +01:00
}
}
/// Enable/disable this [`BreakPoint`].
#[inline(always)]
pub fn enable(&mut self, value: bool) {
match self {
#[cfg(not(feature = "no_position"))]
Self::AtPosition { enabled, .. } => *enabled = value,
2022-01-25 16:59:35 +01:00
Self::AtFunctionName { enabled, .. } | Self::AtFunctionCall { enabled, .. } => {
*enabled = value
}
#[cfg(not(feature = "no_object"))]
Self::AtProperty { enabled, .. } => *enabled = value,
2022-01-24 10:04:40 +01:00
}
}
}
2022-01-25 05:24:30 +01:00
/// A function call.
2022-01-25 07:32:07 +01:00
#[cfg(not(feature = "no_function"))]
2022-01-24 10:04:40 +01:00
#[derive(Debug, Clone, Hash)]
pub struct CallStackFrame {
2022-01-25 16:59:35 +01:00
/// Function name.
2022-01-24 10:04:40 +01:00
pub fn_name: Identifier,
2022-01-25 16:59:35 +01:00
/// Copies of function call arguments, if any.
2022-01-25 07:32:07 +01:00
pub args: crate::StaticVec<Dynamic>,
2022-01-25 16:59:35 +01:00
/// Source of the function, empty if none.
2022-01-24 10:04:40 +01:00
pub source: Identifier,
2022-01-25 16:59:35 +01:00
/// [Position][`Position`] of the function call.
2022-01-24 10:04:40 +01:00
pub pos: Position,
}
2022-01-25 07:32:07 +01:00
#[cfg(not(feature = "no_function"))]
2022-01-24 10:04:40 +01:00
impl fmt::Display for CallStackFrame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut fp = f.debug_tuple(&self.fn_name);
for arg in &self.args {
fp.field(arg);
}
fp.finish()?;
if !self.pos.is_none() {
if self.source.is_empty() {
write!(f, " @ {:?}", self.pos)?;
} else {
write!(f, ": {} @ {:?}", self.source, self.pos)?;
}
}
Ok(())
}
}
/// A type providing debugging facilities.
#[derive(Debug, Clone, Hash)]
pub struct Debugger {
2022-01-25 16:59:35 +01:00
/// The current status command.
2022-01-25 05:24:30 +01:00
status: DebuggerCommand,
2022-01-25 16:59:35 +01:00
/// The current set of break-points.
2022-01-24 10:04:40 +01:00
break_points: Vec<BreakPoint>,
2022-01-25 16:59:35 +01:00
/// The current function call stack.
2022-01-25 07:32:07 +01:00
#[cfg(not(feature = "no_function"))]
2022-01-24 10:04:40 +01:00
call_stack: Vec<CallStackFrame>,
}
impl Debugger {
/// Create a new [`Debugger`].
pub const fn new() -> Self {
Self {
2022-01-25 05:24:30 +01:00
status: DebuggerCommand::Continue,
2022-01-24 10:04:40 +01:00
break_points: Vec::new(),
2022-01-25 07:32:07 +01:00
#[cfg(not(feature = "no_function"))]
2022-01-24 10:04:40 +01:00
call_stack: Vec::new(),
}
}
/// Get the function call stack depth.
2022-01-25 16:59:35 +01:00
///
/// Not available under `no_function`.
2022-01-25 07:32:07 +01:00
#[cfg(not(feature = "no_function"))]
2022-01-24 10:04:40 +01:00
#[inline(always)]
pub fn call_stack_len(&self) -> usize {
self.call_stack.len()
}
2022-01-25 05:24:30 +01:00
/// Get the current call stack.
2022-01-25 16:59:35 +01:00
///
/// Not available under `no_function`.
2022-01-25 07:32:07 +01:00
#[cfg(not(feature = "no_function"))]
2022-01-25 05:24:30 +01:00
#[inline(always)]
pub fn call_stack(&self) -> &[CallStackFrame] {
&self.call_stack
}
2022-01-24 10:04:40 +01:00
/// Rewind the function call stack to a particular depth.
2022-01-25 16:59:35 +01:00
///
/// Not available under `no_function`.
2022-01-25 07:32:07 +01:00
#[cfg(not(feature = "no_function"))]
2022-01-24 10:04:40 +01:00
#[inline(always)]
2022-01-25 05:24:30 +01:00
pub(crate) fn rewind_call_stack(&mut self, len: usize) {
2022-01-24 10:04:40 +01:00
self.call_stack.truncate(len);
}
/// Add a new frame to the function call stack.
2022-01-25 16:59:35 +01:00
///
/// Not available under `no_function`.
2022-01-25 07:32:07 +01:00
#[cfg(not(feature = "no_function"))]
2022-01-24 10:04:40 +01:00
#[inline(always)]
2022-01-25 05:24:30 +01:00
pub(crate) fn push_call_stack_frame(
2022-01-24 10:04:40 +01:00
&mut self,
fn_name: impl Into<Identifier>,
2022-01-25 07:32:07 +01:00
args: crate::StaticVec<Dynamic>,
2022-01-24 10:04:40 +01:00
source: impl Into<Identifier>,
pos: Position,
) {
2022-01-25 05:24:30 +01:00
self.call_stack.push(CallStackFrame {
2022-01-24 10:04:40 +01:00
fn_name: fn_name.into(),
args,
source: source.into(),
pos,
2022-01-25 05:24:30 +01:00
});
2022-01-24 10:04:40 +01:00
}
2022-01-25 05:24:30 +01:00
/// Get the current status of this [`Debugger`].
2022-01-24 10:04:40 +01:00
#[inline(always)]
#[must_use]
2022-01-25 05:24:30 +01:00
pub fn status(&self) -> DebuggerCommand {
self.status
}
/// Set the status of this [`Debugger`].
#[inline(always)]
pub fn set_status(&mut self, status: DebuggerCommand) {
self.status = status;
2022-01-24 10:04:40 +01:00
}
2022-01-25 05:24:30 +01:00
/// Set the status of this [`Debugger`].
#[inline(always)]
pub fn reset_status(&mut self, status: Option<DebuggerCommand>) {
if let Some(cmd) = status {
self.status = cmd;
}
}
/// Activate: set the status of this [`Debugger`] to [`DebuggerCommand::StepInto`].
/// Deactivate: set the status of this [`Debugger`] to [`DebuggerCommand::Continue`].
2022-01-24 10:04:40 +01:00
#[inline(always)]
pub fn activate(&mut self, active: bool) {
2022-01-25 05:24:30 +01:00
if active {
self.set_status(DebuggerCommand::StepInto);
} else {
self.set_status(DebuggerCommand::Continue);
}
2022-01-24 10:04:40 +01:00
}
/// Does a particular [`AST` Node][ASTNode] trigger a break-point?
pub fn is_break_point(&self, src: &str, node: ASTNode) -> bool {
2022-01-25 16:59:35 +01:00
let _src = src;
2022-01-25 10:29:34 +01:00
self.break_points()
.iter()
.filter(|&bp| bp.is_enabled())
.any(|bp| match bp {
#[cfg(not(feature = "no_position"))]
BreakPoint::AtPosition { pos, .. } if pos.is_none() => false,
#[cfg(not(feature = "no_position"))]
BreakPoint::AtPosition { source, pos, .. } if pos.is_beginning_of_line() => {
2022-01-25 16:59:35 +01:00
node.position().line().unwrap_or(0) == pos.line().unwrap() && _src == source
2022-01-24 10:04:40 +01:00
}
2022-01-25 10:29:34 +01:00
#[cfg(not(feature = "no_position"))]
BreakPoint::AtPosition { source, pos, .. } => {
2022-01-25 16:59:35 +01:00
node.position() == *pos && _src == source
2022-01-24 10:04:40 +01:00
}
2022-01-25 10:29:34 +01:00
BreakPoint::AtFunctionName { name, .. } => match node {
ASTNode::Expr(Expr::FnCall(x, _)) | ASTNode::Stmt(Stmt::FnCall(x, _)) => {
x.name == *name
}
_ => false,
},
BreakPoint::AtFunctionCall { name, args, .. } => match node {
ASTNode::Expr(Expr::FnCall(x, _)) | ASTNode::Stmt(Stmt::FnCall(x, _)) => {
x.args.len() == *args && x.name == *name
}
_ => false,
},
2022-01-25 16:59:35 +01:00
#[cfg(not(feature = "no_object"))]
2022-01-25 11:21:05 +01:00
BreakPoint::AtProperty { name, .. } => match node {
ASTNode::Expr(Expr::Property(x)) => (x.2).0 == *name,
_ => false,
},
2022-01-25 10:29:34 +01:00
})
2022-01-24 10:04:40 +01:00
}
/// Get a slice of all [`BreakPoint`]'s.
#[inline(always)]
#[must_use]
2022-01-25 05:24:30 +01:00
pub fn break_points(&self) -> &[BreakPoint] {
2022-01-24 10:04:40 +01:00
&self.break_points
}
/// Get the underlying [`Vec`] holding all [`BreakPoint`]'s.
#[inline(always)]
#[must_use]
pub fn break_points_mut(&mut self) -> &mut Vec<BreakPoint> {
&mut self.break_points
}
}
impl Engine {
2022-01-25 05:24:30 +01:00
/// Run the debugger callback.
#[inline(always)]
pub(crate) fn run_debugger<'a>(
2022-01-24 10:04:40 +01:00
&self,
scope: &mut Scope,
global: &mut GlobalRuntimeState,
state: &mut EvalState,
lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>,
2022-01-25 05:24:30 +01:00
node: impl Into<ASTNode<'a>>,
2022-01-24 10:04:40 +01:00
level: usize,
) -> RhaiResultOf<()> {
2022-01-25 05:24:30 +01:00
if let Some(cmd) =
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, node, level)?
2022-01-25 05:24:30 +01:00
{
global.debugger.set_status(cmd);
}
Ok(())
2022-01-25 05:24:30 +01:00
}
/// Run the debugger callback.
///
/// Returns `true` 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]
pub(crate) fn run_debugger_with_reset<'a>(
&self,
scope: &mut Scope,
global: &mut GlobalRuntimeState,
state: &mut EvalState,
lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>,
node: impl Into<ASTNode<'a>>,
level: usize,
) -> RhaiResultOf<Option<DebuggerCommand>> {
2022-01-24 10:04:40 +01:00
if let Some(ref on_debugger) = self.debugger {
2022-01-25 05:24:30 +01:00
let node = node.into();
2022-01-26 15:15:47 +01:00
// Skip transitive nodes
match node {
ASTNode::Expr(Expr::Stmt(_)) | ASTNode::Stmt(Stmt::Expr(_)) => return Ok(None),
_ => (),
}
2022-01-25 05:24:30 +01:00
let stop = match global.debugger.status {
DebuggerCommand::Continue => false,
DebuggerCommand::Next => matches!(node, ASTNode::Stmt(_)),
DebuggerCommand::StepInto | DebuggerCommand::StepOver => true,
2022-01-25 05:24:30 +01:00
};
if !stop && !global.debugger.is_break_point(&global.source, node) {
return Ok(None);
2022-01-25 05:24:30 +01:00
}
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 command = on_debugger(&mut context, node, source, node.position())?;
2022-01-25 05:24:30 +01:00
match command {
DebuggerCommand::Continue => {
global.debugger.set_status(DebuggerCommand::Continue);
Ok(None)
}
DebuggerCommand::Next => {
global.debugger.set_status(DebuggerCommand::Continue);
Ok(Some(DebuggerCommand::Next))
2022-01-25 05:24:30 +01:00
}
DebuggerCommand::StepInto => {
global.debugger.set_status(DebuggerCommand::StepInto);
Ok(None)
2022-01-25 05:24:30 +01:00
}
DebuggerCommand::StepOver => {
global.debugger.set_status(DebuggerCommand::Continue);
Ok(Some(DebuggerCommand::StepOver))
2022-01-24 10:04:40 +01:00
}
}
2022-01-25 05:24:30 +01:00
} else {
Ok(None)
2022-01-24 10:04:40 +01:00
}
}
}