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};
|
2022-01-25 07:32:07 +01:00
|
|
|
use crate::{Dynamic, Engine, Identifier, Module, Position, 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"))]
|
|
|
|
pub type OnDebuggerCallback =
|
|
|
|
Box<dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> DebuggerCommand + 'static>;
|
|
|
|
/// A standard callback function for debugging.
|
|
|
|
#[cfg(feature = "sync")]
|
|
|
|
pub type OnDebuggerCallback = Box<
|
|
|
|
dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> DebuggerCommand
|
|
|
|
+ Send
|
|
|
|
+ Sync
|
|
|
|
+ 'static,
|
|
|
|
>;
|
|
|
|
|
|
|
|
/// 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 statement, stepping over functions.
|
|
|
|
StepOver,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A break-point for debugging.
|
|
|
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
|
|
|
pub enum BreakPoint {
|
|
|
|
/// Break at a particular position under a particular source.
|
|
|
|
/// Not available under `no_position`.
|
|
|
|
///
|
|
|
|
/// Source is empty if not available.
|
|
|
|
#[cfg(not(feature = "no_position"))]
|
|
|
|
AtPosition { source: Identifier, pos: Position },
|
|
|
|
/// Break at a particular function call.
|
2022-01-25 05:24:30 +01:00
|
|
|
AtFunctionName { name: Identifier },
|
2022-01-24 10:04:40 +01:00
|
|
|
/// Break at a particular function call with a particular number of arguments.
|
2022-01-25 05:24:30 +01:00
|
|
|
AtFunctionCall { name: Identifier, args: usize },
|
2022-01-24 10:04:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for BreakPoint {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
match self {
|
|
|
|
Self::AtPosition { source, pos } => {
|
|
|
|
if !source.is_empty() {
|
|
|
|
write!(f, "{} @ {:?}", source, pos)
|
|
|
|
} else {
|
|
|
|
write!(f, "@ {:?}", pos)
|
|
|
|
}
|
|
|
|
}
|
2022-01-25 05:24:30 +01:00
|
|
|
Self::AtFunctionName { name: fn_name } => write!(f, "{} (...)", fn_name),
|
|
|
|
Self::AtFunctionCall {
|
|
|
|
name: fn_name,
|
|
|
|
args,
|
|
|
|
} => write!(
|
2022-01-24 10:04:40 +01:00
|
|
|
f,
|
|
|
|
"{} ({})",
|
|
|
|
fn_name,
|
|
|
|
std::iter::repeat("_")
|
|
|
|
.take(*args)
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.join(", ")
|
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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 {
|
|
|
|
pub fn_name: Identifier,
|
2022-01-25 07:32:07 +01:00
|
|
|
pub args: crate::StaticVec<Dynamic>,
|
2022-01-24 10:04:40 +01:00
|
|
|
pub source: Identifier,
|
|
|
|
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 05:24:30 +01:00
|
|
|
status: DebuggerCommand,
|
2022-01-24 10:04:40 +01:00
|
|
|
break_points: Vec<BreakPoint>,
|
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 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 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 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 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 05:24:30 +01:00
|
|
|
self.break_points().iter().any(|bp| match bp {
|
2022-01-24 10:04:40 +01:00
|
|
|
#[cfg(not(feature = "no_position"))]
|
|
|
|
BreakPoint::AtPosition { source, pos } => node.position() == *pos && src == source,
|
2022-01-25 05:24:30 +01:00
|
|
|
BreakPoint::AtFunctionName { name } => match node {
|
2022-01-24 10:04:40 +01:00
|
|
|
ASTNode::Expr(Expr::FnCall(x, _)) | ASTNode::Stmt(Stmt::FnCall(x, _)) => {
|
2022-01-25 05:24:30 +01:00
|
|
|
x.name == *name
|
2022-01-24 10:04:40 +01:00
|
|
|
}
|
|
|
|
_ => false,
|
|
|
|
},
|
2022-01-25 05:24:30 +01:00
|
|
|
BreakPoint::AtFunctionCall { name, args } => match node {
|
2022-01-24 10:04:40 +01:00
|
|
|
ASTNode::Expr(Expr::FnCall(x, _)) | ASTNode::Stmt(Stmt::FnCall(x, _)) => {
|
2022-01-25 05:24:30 +01:00
|
|
|
x.args.len() == *args && x.name == *name
|
2022-01-24 10:04:40 +01:00
|
|
|
}
|
|
|
|
_ => false,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
/// 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,
|
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)
|
|
|
|
{
|
|
|
|
global.debugger.set_status(cmd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/// 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,
|
|
|
|
) -> 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();
|
|
|
|
|
|
|
|
let stop = match global.debugger.status {
|
|
|
|
DebuggerCommand::Continue => false,
|
|
|
|
DebuggerCommand::StepOver => matches!(node, ASTNode::Stmt(_)),
|
|
|
|
DebuggerCommand::StepInto => true,
|
|
|
|
};
|
|
|
|
|
|
|
|
if !stop && !global.debugger.is_break_point(&global.source, node) {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
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());
|
|
|
|
|
|
|
|
match command {
|
|
|
|
DebuggerCommand::Continue => {
|
|
|
|
global.debugger.set_status(DebuggerCommand::Continue);
|
|
|
|
None
|
|
|
|
}
|
|
|
|
DebuggerCommand::StepInto => {
|
|
|
|
global.debugger.set_status(DebuggerCommand::StepInto);
|
2022-01-24 10:04:40 +01:00
|
|
|
None
|
2022-01-25 05:24:30 +01:00
|
|
|
}
|
|
|
|
DebuggerCommand::StepOver => {
|
|
|
|
global.debugger.set_status(DebuggerCommand::Continue);
|
|
|
|
Some(DebuggerCommand::StepOver)
|
2022-01-24 10:04:40 +01:00
|
|
|
}
|
|
|
|
}
|
2022-01-25 05:24:30 +01:00
|
|
|
} else {
|
|
|
|
None
|
2022-01-24 10:04:40 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|