Skip to main content

nu_protocol/debugger/
debugger_trait.rs

1//! Traits related to debugging
2//!
3//! The purpose of DebugContext is achieving static dispatch on `eval_xxx()` calls.
4//! The main Debugger trait is intended to be used as a trait object.
5//!
6//! The debugging information is stored in `EngineState` as the `debugger` field storing a `Debugger`
7//! trait object behind `Arc` and `Mutex`. To evaluate something (e.g., a block), first create a
8//! `Debugger` trait object (such as the `Profiler`). Then, add it to engine state via
9//! `engine_state.activate_debugger()`. This sets the internal state of EngineState to the debugging
10//! mode and calls `Debugger::activate()`. Now, you can call `eval_xxx::<WithDebug>()`. When you're
11//! done, call `engine_state.deactivate_debugger()` which calls `Debugger::deactivate()`, sets the
12//! EngineState into non-debugging mode, and returns the original mutated `Debugger` trait object.
13//! (`NoopDebugger` is placed in its place inside `EngineState`.) After deactivating, you can call
14//! `Debugger::report()` to get some output from the debugger, if necessary.
15
16use crate::{
17    PipelineData, PipelineExecutionData, ShellError, Span, Value,
18    ast::{Block, PipelineElement},
19    engine::{EngineState, Stack},
20    ir::IrBlock,
21};
22use std::{fmt::Debug, ops::DerefMut};
23
24/// Trait used for static dispatch of `eval_xxx()` evaluator calls
25///
26/// DebugContext implements the same interface as Debugger (except activate() and deactivate(). It
27/// is intended to be implemented only by two structs
28/// * WithDebug which calls down to the Debugger methods
29/// * WithoutDebug with default implementation, i.e., empty calls to be optimized away
30pub trait DebugContext: Clone + Copy + Debug {
31    /// Called when the evaluator enters a block
32    #[allow(unused_variables)]
33    fn enter_block(engine_state: &EngineState, block: &Block) {}
34
35    /// Called when the evaluator leaves a block
36    #[allow(unused_variables)]
37    fn leave_block(engine_state: &EngineState, block: &Block) {}
38
39    /// Called when the AST evaluator enters a pipeline element
40    #[allow(unused_variables)]
41    fn enter_element(engine_state: &EngineState, element: &PipelineElement) {}
42
43    /// Called when the AST evaluator leaves a pipeline element
44    #[allow(unused_variables)]
45    fn leave_element(
46        engine_state: &EngineState,
47        element: &PipelineElement,
48        result: &Result<PipelineData, ShellError>,
49    ) {
50    }
51
52    /// Called before the IR evaluator runs an instruction
53    #[allow(unused_variables)]
54    fn enter_instruction(
55        engine_state: &EngineState,
56        stack: &Stack,
57        ir_block: &IrBlock,
58        instruction_index: usize,
59        registers: &[PipelineExecutionData],
60    ) {
61    }
62
63    /// Called after the IR evaluator runs an instruction
64    #[allow(unused_variables)]
65    fn leave_instruction(
66        engine_state: &EngineState,
67        stack: &Stack,
68        ir_block: &IrBlock,
69        instruction_index: usize,
70        registers: &[PipelineExecutionData],
71        error: Option<&ShellError>,
72    ) {
73    }
74}
75
76/// Marker struct signalizing that evaluation should use a Debugger
77///
78/// Trait methods call to Debugger trait object inside the supplied EngineState.
79#[derive(Clone, Copy, Debug)]
80pub struct WithDebug;
81
82impl DebugContext for WithDebug {
83    fn enter_block(engine_state: &EngineState, block: &Block) {
84        if let Ok(mut debugger) = engine_state.debugger.lock() {
85            debugger.deref_mut().enter_block(engine_state, block);
86        }
87    }
88
89    fn leave_block(engine_state: &EngineState, block: &Block) {
90        if let Ok(mut debugger) = engine_state.debugger.lock() {
91            debugger.deref_mut().leave_block(engine_state, block);
92        }
93    }
94
95    fn enter_element(engine_state: &EngineState, element: &PipelineElement) {
96        if let Ok(mut debugger) = engine_state.debugger.lock() {
97            debugger.deref_mut().enter_element(engine_state, element);
98        }
99    }
100
101    fn leave_element(
102        engine_state: &EngineState,
103        element: &PipelineElement,
104        result: &Result<PipelineData, ShellError>,
105    ) {
106        if let Ok(mut debugger) = engine_state.debugger.lock() {
107            debugger
108                .deref_mut()
109                .leave_element(engine_state, element, result);
110        }
111    }
112
113    fn enter_instruction(
114        engine_state: &EngineState,
115        stack: &Stack,
116        ir_block: &IrBlock,
117        instruction_index: usize,
118        registers: &[PipelineExecutionData],
119    ) {
120        if let Ok(mut debugger) = engine_state.debugger.lock() {
121            debugger.deref_mut().enter_instruction(
122                engine_state,
123                stack,
124                ir_block,
125                instruction_index,
126                registers,
127            )
128        }
129    }
130
131    fn leave_instruction(
132        engine_state: &EngineState,
133        stack: &Stack,
134        ir_block: &IrBlock,
135        instruction_index: usize,
136        registers: &[PipelineExecutionData],
137        error: Option<&ShellError>,
138    ) {
139        if let Ok(mut debugger) = engine_state.debugger.lock() {
140            debugger.deref_mut().leave_instruction(
141                engine_state,
142                stack,
143                ir_block,
144                instruction_index,
145                registers,
146                error,
147            )
148        }
149    }
150}
151
152/// Marker struct signalizing that evaluation should NOT use a Debugger
153///
154/// Trait methods are empty calls to be optimized away.
155#[derive(Clone, Copy, Debug)]
156pub struct WithoutDebug;
157
158impl DebugContext for WithoutDebug {}
159
160/// Debugger trait that every debugger needs to implement.
161///
162/// By default, its methods are empty. Not every Debugger needs to implement all of them.
163pub trait Debugger: Send + Debug {
164    /// Called by EngineState::activate_debugger().
165    ///
166    /// Intended for initializing the debugger.
167    fn activate(&mut self) {}
168
169    /// Called by EngineState::deactivate_debugger().
170    ///
171    /// Intended for wrapping up the debugger after a debugging session before returning back to
172    /// normal evaluation without debugging.
173    fn deactivate(&mut self) {}
174
175    /// Called when the evaluator enters a block
176    #[allow(unused_variables)]
177    fn enter_block(&mut self, engine_state: &EngineState, block: &Block) {}
178
179    /// Called when the evaluator leaves a block
180    #[allow(unused_variables)]
181    fn leave_block(&mut self, engine_state: &EngineState, block: &Block) {}
182
183    /// Called when the AST evaluator enters a pipeline element
184    #[allow(unused_variables)]
185    fn enter_element(&mut self, engine_state: &EngineState, pipeline_element: &PipelineElement) {}
186
187    /// Called when the AST evaluator leaves a pipeline element
188    #[allow(unused_variables)]
189    fn leave_element(
190        &mut self,
191        engine_state: &EngineState,
192        element: &PipelineElement,
193        result: &Result<PipelineData, ShellError>,
194    ) {
195    }
196
197    /// Called before the IR evaluator runs an instruction.
198    ///
199    /// `stack` is the live evaluation stack, so a debugger can read variable
200    /// values directly instead of reconstructing them from instructions.
201    #[allow(unused_variables)]
202    fn enter_instruction(
203        &mut self,
204        engine_state: &EngineState,
205        stack: &Stack,
206        ir_block: &IrBlock,
207        instruction_index: usize,
208        registers: &[PipelineExecutionData],
209    ) {
210    }
211
212    /// Called after the IR evaluator runs an instruction.
213    ///
214    /// `stack` is the live evaluation stack (see [`Debugger::enter_instruction`]).
215    #[allow(unused_variables)]
216    fn leave_instruction(
217        &mut self,
218        engine_state: &EngineState,
219        stack: &Stack,
220        ir_block: &IrBlock,
221        instruction_index: usize,
222        registers: &[PipelineExecutionData],
223        error: Option<&ShellError>,
224    ) {
225    }
226
227    /// Create a final report as a Value
228    ///
229    /// Intended to be called after deactivate()
230    #[allow(unused_variables)]
231    fn report(&self, engine_state: &EngineState, debugger_span: Span) -> Result<Value, ShellError> {
232        Ok(Value::nothing(debugger_span))
233    }
234}
235
236/// A debugger that does nothing
237///
238/// Used as a placeholder debugger when not debugging.
239#[derive(Debug)]
240pub struct NoopDebugger;
241
242impl Debugger for NoopDebugger {}