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 dbg<'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) = self.dbg_reset_raw(global, caches, scope, this_ptr, node)? {
411 global.debugger_mut().status = cmd;
412 }
413 }
414
415 Ok(())
416 }
417 #[inline(always)]
424 pub(crate) fn dbg_reset<'a>(
425 &self,
426 global: &mut GlobalRuntimeState,
427 caches: &mut Caches,
428 scope: &mut Scope,
429 this_ptr: Option<&mut Dynamic>,
430 node: impl Into<ASTNode<'a>>,
431 ) -> RhaiResultOf<Option<DebuggerStatus>> {
432 if self.is_debugger_registered() {
433 self.dbg_reset_raw(global, caches, scope, this_ptr, node)
434 } else {
435 Ok(None)
436 }
437 }
438 #[inline]
445 pub(crate) fn dbg_reset_raw<'a>(
446 &self,
447 global: &mut GlobalRuntimeState,
448 caches: &mut Caches,
449 scope: &mut Scope,
450 this_ptr: Option<&mut Dynamic>,
451 node: impl Into<ASTNode<'a>>,
452 ) -> RhaiResultOf<Option<DebuggerStatus>> {
453 let node = node.into();
454
455 match node {
457 ASTNode::Expr(Expr::Stmt(..)) | ASTNode::Stmt(Stmt::Expr(..)) => return Ok(None),
458 _ => (),
459 }
460
461 match global.debugger {
462 Some(ref dbg) => {
463 let event = match dbg.status {
464 DebuggerStatus::Init => Some(DebuggerEvent::Start),
465 DebuggerStatus::NEXT if node.is_stmt() => Some(DebuggerEvent::Step),
466 DebuggerStatus::INTO if node.is_expr() => Some(DebuggerEvent::Step),
467 DebuggerStatus::STEP => Some(DebuggerEvent::Step),
468 DebuggerStatus::Terminate => Some(DebuggerEvent::End),
469 _ => None,
470 };
471
472 let event = match event {
473 Some(e) => e,
474 None => match dbg.is_break_point(global.source(), node) {
475 Some(bp) => DebuggerEvent::BreakPoint(bp),
476 None => return Ok(None),
477 },
478 };
479
480 self.dbg_raw(global, caches, scope, this_ptr, node, event)
481 }
482 None => Ok(None),
483 }
484 }
485 #[inline]
492 pub(crate) fn dbg_raw(
493 &self,
494 global: &mut GlobalRuntimeState,
495 caches: &mut Caches,
496 scope: &mut Scope,
497 this_ptr: Option<&mut Dynamic>,
498 node: ASTNode,
499 event: DebuggerEvent,
500 ) -> Result<Option<DebuggerStatus>, Box<crate::EvalAltResult>> {
501 match self.debugger_interface {
502 Some(ref x) => {
503 let orig_scope_len = scope.len();
504
505 let src = global.source_raw().cloned();
506 let context = EvalContext::new(self, global, caches, scope, this_ptr);
507 let (.., ref on_debugger) = *x;
508
509 let command = on_debugger(context, event, node, src.as_deref(), node.position());
510
511 if orig_scope_len != scope.len() {
512 global.always_search_scope = true;
514 }
515
516 match command? {
517 DebuggerCommand::Continue => {
518 global.debugger_mut().status = DebuggerStatus::CONTINUE;
519 Ok(None)
520 }
521 DebuggerCommand::Next => {
522 global.debugger_mut().status = DebuggerStatus::CONTINUE;
523 Ok(Some(DebuggerStatus::NEXT))
524 }
525 DebuggerCommand::StepOver => {
526 global.debugger_mut().status = DebuggerStatus::CONTINUE;
527 Ok(Some(DebuggerStatus::STEP))
528 }
529 DebuggerCommand::StepInto => {
530 global.debugger_mut().status = DebuggerStatus::STEP;
531 Ok(None)
532 }
533 DebuggerCommand::FunctionExit => {
534 let level = match node {
536 ASTNode::Expr(Expr::FnCall(..)) | ASTNode::Stmt(Stmt::FnCall(..)) => {
537 global.level + 1
538 }
539 ASTNode::Stmt(Stmt::Expr(e)) if matches!(**e, Expr::FnCall(..)) => {
540 global.level + 1
541 }
542 _ => global.level,
543 };
544 global.debugger_mut().status = DebuggerStatus::FunctionExit(level);
545 Ok(None)
546 }
547 }
548 }
549 None => Ok(None),
550 }
551 }
552}