1#![cfg(feature = "debugging")]
3
4use super::{Caches, EvalContext, GlobalRuntimeState};
5use crate::ast::{ASTNode, Expr, Stmt};
6use crate::{
7 Dynamic, Engine, EvalAltResult, ImmutableString, Position, RhaiResultOf, Scope, ThinVec,
8};
9#[cfg(feature = "no_std")]
10use std::prelude::v1::*;
11use std::{fmt, iter::repeat, mem};
12
13#[cfg(not(feature = "sync"))]
15pub type OnDebuggingInit = dyn Fn(&Engine, Debugger) -> Debugger;
16#[cfg(feature = "sync")]
18pub type OnDebuggingInit = dyn Fn(&Engine, Debugger) -> Debugger + Send + Sync;
19
20#[cfg(not(feature = "sync"))]
22pub type OnDebuggerCallback = dyn Fn(
23 EvalContext,
24 DebuggerEvent,
25 ASTNode,
26 Option<&str>,
27 Position,
28) -> RhaiResultOf<DebuggerCommand>;
29#[cfg(feature = "sync")]
31pub type OnDebuggerCallback = dyn Fn(EvalContext, DebuggerEvent, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>
32 + Send
33 + Sync;
34
35#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, Hash)]
37#[non_exhaustive]
38pub enum DebuggerCommand {
39 #[default]
41 Continue,
42 StepInto,
44 StepOver,
46 Next,
48 FunctionExit,
50}
51
52#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
54#[non_exhaustive]
55pub enum DebuggerStatus {
56 Init,
58 Next(bool, bool),
60 FunctionExit(usize),
62 Terminate,
64}
65
66impl DebuggerStatus {
67 pub const CONTINUE: Self = Self::Next(false, false);
68 pub const STEP: Self = Self::Next(true, true);
69 pub const NEXT: Self = Self::Next(true, false);
70 pub const INTO: Self = Self::Next(false, true);
71}
72
73#[derive(Debug, Clone, Copy)]
75#[non_exhaustive]
76pub enum DebuggerEvent<'a> {
77 Start,
79 Step,
81 BreakPoint(usize),
83 FunctionExitWithValue(&'a Dynamic),
85 FunctionExitWithError(&'a EvalAltResult),
87 End,
89}
90
91#[derive(Debug, Clone, Eq, PartialEq, Hash)]
93#[non_exhaustive]
94pub enum BreakPoint {
95 #[cfg(not(feature = "no_position"))]
99 AtPosition {
100 source: Option<ImmutableString>,
102 pos: Position,
104 enabled: bool,
106 },
107 AtFunctionName {
109 name: ImmutableString,
111 enabled: bool,
113 },
114 AtFunctionCall {
116 name: ImmutableString,
118 args: usize,
120 enabled: bool,
122 },
123 #[cfg(not(feature = "no_object"))]
127 AtProperty {
128 name: ImmutableString,
130 enabled: bool,
132 },
133}
134
135impl fmt::Display for BreakPoint {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 match self {
138 #[cfg(not(feature = "no_position"))]
139 Self::AtPosition {
140 source,
141 pos,
142 enabled,
143 } => {
144 if let Some(ref source) = source {
145 write!(f, "{source} ")?;
146 }
147 write!(f, "@ {pos:?}")?;
148 if !*enabled {
149 f.write_str(" (disabled)")?;
150 }
151 Ok(())
152 }
153 Self::AtFunctionName { name, enabled } => {
154 write!(f, "{name} (...)")?;
155 if !*enabled {
156 f.write_str(" (disabled)")?;
157 }
158 Ok(())
159 }
160 Self::AtFunctionCall {
161 name,
162 args,
163 enabled,
164 } => {
165 write!(
166 f,
167 "{name} ({})",
168 repeat("_").take(*args).collect::<Vec<_>>().join(", ")
169 )?;
170 if !*enabled {
171 f.write_str(" (disabled)")?;
172 }
173 Ok(())
174 }
175 #[cfg(not(feature = "no_object"))]
176 Self::AtProperty { name, enabled } => {
177 write!(f, ".{name}")?;
178 if !*enabled {
179 f.write_str(" (disabled)")?;
180 }
181 Ok(())
182 }
183 }
184 }
185}
186
187impl BreakPoint {
188 #[inline(always)]
190 #[must_use]
191 pub const fn is_enabled(&self) -> bool {
192 match self {
193 #[cfg(not(feature = "no_position"))]
194 Self::AtPosition { enabled, .. } => *enabled,
195 Self::AtFunctionName { enabled, .. } | Self::AtFunctionCall { enabled, .. } => *enabled,
196 #[cfg(not(feature = "no_object"))]
197 Self::AtProperty { enabled, .. } => *enabled,
198 }
199 }
200 #[inline(always)]
202 pub fn enable(&mut self, value: bool) {
203 match self {
204 #[cfg(not(feature = "no_position"))]
205 Self::AtPosition { enabled, .. } => *enabled = value,
206 Self::AtFunctionName { enabled, .. } | Self::AtFunctionCall { enabled, .. } => {
207 *enabled = value
208 }
209 #[cfg(not(feature = "no_object"))]
210 Self::AtProperty { enabled, .. } => *enabled = value,
211 }
212 }
213}
214
215#[derive(Debug, Clone, Hash)]
217pub struct CallStackFrame {
218 pub fn_name: ImmutableString,
220 pub args: ThinVec<Dynamic>,
222 pub source: Option<ImmutableString>,
224 pub pos: Position,
226}
227
228impl fmt::Display for CallStackFrame {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 let mut fp = f.debug_tuple(&self.fn_name);
231
232 for arg in &self.args {
233 fp.field(arg);
234 }
235
236 fp.finish()?;
237
238 if !self.pos.is_none() {
239 if let Some(ref source) = self.source {
240 write!(f, ": {source}")?;
241 }
242 write!(f, " @ {:?}", self.pos)?;
243 }
244
245 Ok(())
246 }
247}
248
249#[derive(Debug, Clone, Hash)]
251pub struct Debugger {
252 pub(crate) status: DebuggerStatus,
254 break_points: Vec<BreakPoint>,
256 call_stack: Vec<CallStackFrame>,
258 state: Dynamic,
260}
261
262impl Debugger {
263 #[inline(always)]
265 #[must_use]
266 pub fn new(status: DebuggerStatus) -> Self {
267 Self {
268 status,
269 break_points: Vec::new(),
270 call_stack: Vec::new(),
271 state: Dynamic::UNIT,
272 }
273 }
274 #[inline(always)]
276 #[must_use]
277 pub fn call_stack(&self) -> &[CallStackFrame] {
278 &self.call_stack
279 }
280 #[inline(always)]
282 pub(crate) fn rewind_call_stack(&mut self, len: usize) {
283 self.call_stack.truncate(len);
284 }
285 #[inline(always)]
287 pub(crate) fn push_call_stack_frame(
288 &mut self,
289 fn_name: ImmutableString,
290 args: impl IntoIterator<Item = Dynamic>,
291 source: Option<ImmutableString>,
292 pos: Position,
293 ) {
294 self.call_stack.push(CallStackFrame {
295 fn_name,
296 args: args.into_iter().collect(),
297 source,
298 pos,
299 });
300 }
301 pub(crate) fn clear_status_if(
303 &mut self,
304 filter: impl FnOnce(&DebuggerStatus) -> bool,
305 ) -> Option<DebuggerStatus> {
306 if filter(&self.status) {
307 Some(mem::replace(&mut self.status, DebuggerStatus::CONTINUE))
308 } else {
309 None
310 }
311 }
312 #[inline(always)]
315 pub(crate) fn reset_status(&mut self, status: DebuggerStatus) {
316 if self.status == DebuggerStatus::CONTINUE {
317 self.status = status;
318 }
319 }
320 #[must_use]
322 pub fn is_break_point(&self, src: Option<&str>, node: ASTNode) -> Option<usize> {
323 let _src = src;
324
325 self.break_points()
326 .iter()
327 .enumerate()
328 .filter(|(.., bp)| bp.is_enabled())
329 .find(|(.., bp)| match bp {
330 #[cfg(not(feature = "no_position"))]
331 BreakPoint::AtPosition { pos, .. } if pos.is_none() => false,
332 #[cfg(not(feature = "no_position"))]
333 BreakPoint::AtPosition { source, pos, .. } if pos.is_beginning_of_line() => {
334 node.position().line().unwrap_or(0) == pos.line().unwrap()
335 && _src == source.as_deref()
336 }
337 #[cfg(not(feature = "no_position"))]
338 BreakPoint::AtPosition { source, pos, .. } => {
339 node.position() == *pos && _src == source.as_deref()
340 }
341 BreakPoint::AtFunctionName { name, .. } => match node {
342 ASTNode::Expr(Expr::FnCall(x, ..)) | ASTNode::Stmt(Stmt::FnCall(x, ..)) => {
343 x.name == *name
344 }
345 ASTNode::Stmt(Stmt::Expr(e)) => match &**e {
346 Expr::FnCall(x, ..) => x.name == *name,
347 _ => false,
348 },
349 _ => false,
350 },
351 BreakPoint::AtFunctionCall { name, args, .. } => match node {
352 ASTNode::Expr(Expr::FnCall(x, ..)) | ASTNode::Stmt(Stmt::FnCall(x, ..)) => {
353 x.args.len() == *args && x.name == *name
354 }
355 ASTNode::Stmt(Stmt::Expr(e)) => match &**e {
356 Expr::FnCall(x, ..) => x.args.len() == *args && x.name == *name,
357 _ => false,
358 },
359 _ => false,
360 },
361 #[cfg(not(feature = "no_object"))]
362 BreakPoint::AtProperty { name, .. } => match node {
363 ASTNode::Expr(Expr::Property(x, ..)) => x.2 == *name,
364 _ => false,
365 },
366 })
367 .map(|(i, ..)| i)
368 }
369 #[inline(always)]
371 #[must_use]
372 pub fn break_points(&self) -> &[BreakPoint] {
373 &self.break_points
374 }
375 #[inline(always)]
377 #[must_use]
378 pub fn break_points_mut(&mut self) -> &mut Vec<BreakPoint> {
379 &mut self.break_points
380 }
381 #[inline(always)]
383 pub const fn state(&self) -> &Dynamic {
384 &self.state
385 }
386 #[inline(always)]
388 pub fn state_mut(&mut self) -> &mut Dynamic {
389 &mut self.state
390 }
391 #[inline(always)]
393 pub fn set_state(&mut self, state: impl Into<Dynamic>) {
394 self.state = state.into();
395 }
396}
397
398impl Engine {
399 #[inline(always)]
401 pub(crate) fn run_debugger<'a>(
402 &self,
403 global: &mut GlobalRuntimeState,
404 caches: &mut Caches,
405 scope: &mut Scope,
406 this_ptr: Option<&mut Dynamic>,
407 node: impl Into<ASTNode<'a>>,
408 ) -> RhaiResultOf<()> {
409 if self.is_debugger_registered() {
410 if let Some(cmd) =
411 self.run_debugger_with_reset_raw(global, caches, scope, this_ptr, node)?
412 {
413 global.debugger_mut().status = cmd;
414 }
415 }
416
417 Ok(())
418 }
419 #[inline(always)]
426 pub(crate) fn run_debugger_with_reset<'a>(
427 &self,
428 global: &mut GlobalRuntimeState,
429 caches: &mut Caches,
430 scope: &mut Scope,
431 this_ptr: Option<&mut Dynamic>,
432 node: impl Into<ASTNode<'a>>,
433 ) -> RhaiResultOf<Option<DebuggerStatus>> {
434 if self.is_debugger_registered() {
435 self.run_debugger_with_reset_raw(global, caches, scope, this_ptr, node)
436 } else {
437 Ok(None)
438 }
439 }
440 #[inline]
447 pub(crate) fn run_debugger_with_reset_raw<'a>(
448 &self,
449 global: &mut GlobalRuntimeState,
450 caches: &mut Caches,
451 scope: &mut Scope,
452 this_ptr: Option<&mut Dynamic>,
453 node: impl Into<ASTNode<'a>>,
454 ) -> RhaiResultOf<Option<DebuggerStatus>> {
455 let node = node.into();
456
457 match node {
459 ASTNode::Expr(Expr::Stmt(..)) | ASTNode::Stmt(Stmt::Expr(..)) => return Ok(None),
460 _ => (),
461 }
462
463 match global.debugger {
464 Some(ref dbg) => {
465 let event = match dbg.status {
466 DebuggerStatus::Init => Some(DebuggerEvent::Start),
467 DebuggerStatus::NEXT if node.is_stmt() => Some(DebuggerEvent::Step),
468 DebuggerStatus::INTO if node.is_expr() => Some(DebuggerEvent::Step),
469 DebuggerStatus::STEP => Some(DebuggerEvent::Step),
470 DebuggerStatus::Terminate => Some(DebuggerEvent::End),
471 _ => None,
472 };
473
474 let event = match event {
475 Some(e) => e,
476 None => match dbg.is_break_point(global.source(), node) {
477 Some(bp) => DebuggerEvent::BreakPoint(bp),
478 None => return Ok(None),
479 },
480 };
481
482 self.run_debugger_raw(global, caches, scope, this_ptr, node, event)
483 }
484 None => Ok(None),
485 }
486 }
487 #[inline]
494 pub(crate) fn run_debugger_raw(
495 &self,
496 global: &mut GlobalRuntimeState,
497 caches: &mut Caches,
498 scope: &mut Scope,
499 this_ptr: Option<&mut Dynamic>,
500 node: ASTNode,
501 event: DebuggerEvent,
502 ) -> Result<Option<DebuggerStatus>, Box<crate::EvalAltResult>> {
503 match self.debugger_interface {
504 Some(ref x) => {
505 let orig_scope_len = scope.len();
506
507 let src = global.source_raw().cloned();
508 let context = EvalContext::new(self, global, caches, scope, this_ptr);
509 let (.., ref on_debugger) = *x;
510
511 let command = on_debugger(context, event, node, src.as_deref(), node.position());
512
513 if orig_scope_len != scope.len() {
514 global.always_search_scope = true;
516 }
517
518 match command? {
519 DebuggerCommand::Continue => {
520 global.debugger_mut().status = DebuggerStatus::CONTINUE;
521 Ok(None)
522 }
523 DebuggerCommand::Next => {
524 global.debugger_mut().status = DebuggerStatus::CONTINUE;
525 Ok(Some(DebuggerStatus::NEXT))
526 }
527 DebuggerCommand::StepOver => {
528 global.debugger_mut().status = DebuggerStatus::CONTINUE;
529 Ok(Some(DebuggerStatus::STEP))
530 }
531 DebuggerCommand::StepInto => {
532 global.debugger_mut().status = DebuggerStatus::STEP;
533 Ok(None)
534 }
535 DebuggerCommand::FunctionExit => {
536 let level = match node {
538 ASTNode::Expr(Expr::FnCall(..)) | ASTNode::Stmt(Stmt::FnCall(..)) => {
539 global.level + 1
540 }
541 ASTNode::Stmt(Stmt::Expr(e)) if matches!(**e, Expr::FnCall(..)) => {
542 global.level + 1
543 }
544 _ => global.level,
545 };
546 global.debugger_mut().status = DebuggerStatus::FunctionExit(level);
547 Ok(None)
548 }
549 }
550 }
551 None => Ok(None),
552 }
553 }
554}