nu_protocol/debugger/
debugger_trait.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
//! Traits related to debugging
//!
//! The purpose of DebugContext is achieving static dispatch on `eval_xxx()` calls.
//! The main Debugger trait is intended to be used as a trait object.
//!
//! The debugging information is stored in `EngineState` as the `debugger` field storing a `Debugger`
//! trait object behind `Arc` and `Mutex`. To evaluate something (e.g., a block), first create a
//! `Debugger` trait object (such as the `Profiler`). Then, add it to engine state via
//! `engine_state.activate_debugger()`. This sets the internal state of EngineState to the debugging
//! mode and calls `Debugger::activate()`. Now, you can call `eval_xxx::<WithDebug>()`. When you're
//! done, call `engine_state.deactivate_debugger()` which calls `Debugger::deactivate()`, sets the
//! EngineState into non-debugging mode, and returns the original mutated `Debugger` trait object.
//! (`NoopDebugger` is placed in its place inside `EngineState`.) After deactivating, you can call
//! `Debugger::report()` to get some output from the debugger, if necessary.

use crate::{
    ast::{Block, PipelineElement},
    engine::EngineState,
    ir::IrBlock,
    PipelineData, ShellError, Span, Value,
};
use std::{fmt::Debug, ops::DerefMut};

/// Trait used for static dispatch of `eval_xxx()` evaluator calls
///
/// DebugContext implements the same interface as Debugger (except activate() and deactivate(). It
/// is intended to be implemented only by two structs
/// * WithDebug which calls down to the Debugger methods
/// * WithoutDebug with default implementation, i.e., empty calls to be optimized away
pub trait DebugContext: Clone + Copy + Debug {
    /// Called when the evaluator enters a block
    #[allow(unused_variables)]
    fn enter_block(engine_state: &EngineState, block: &Block) {}

    /// Called when the evaluator leaves a block
    #[allow(unused_variables)]
    fn leave_block(engine_state: &EngineState, block: &Block) {}

    /// Called when the AST evaluator enters a pipeline element
    #[allow(unused_variables)]
    fn enter_element(engine_state: &EngineState, element: &PipelineElement) {}

    /// Called when the AST evaluator leaves a pipeline element
    #[allow(unused_variables)]
    fn leave_element(
        engine_state: &EngineState,
        element: &PipelineElement,
        result: &Result<PipelineData, ShellError>,
    ) {
    }

    /// Called before the IR evaluator runs an instruction
    #[allow(unused_variables)]
    fn enter_instruction(
        engine_state: &EngineState,
        ir_block: &IrBlock,
        instruction_index: usize,
        registers: &[PipelineData],
    ) {
    }

    /// Called after the IR evaluator runs an instruction
    #[allow(unused_variables)]
    fn leave_instruction(
        engine_state: &EngineState,
        ir_block: &IrBlock,
        instruction_index: usize,
        registers: &[PipelineData],
        error: Option<&ShellError>,
    ) {
    }
}

/// Marker struct signalizing that evaluation should use a Debugger
///
/// Trait methods call to Debugger trait object inside the supplied EngineState.
#[derive(Clone, Copy, Debug)]
pub struct WithDebug;

impl DebugContext for WithDebug {
    fn enter_block(engine_state: &EngineState, block: &Block) {
        if let Ok(mut debugger) = engine_state.debugger.lock() {
            debugger.deref_mut().enter_block(engine_state, block);
        }
    }

    fn leave_block(engine_state: &EngineState, block: &Block) {
        if let Ok(mut debugger) = engine_state.debugger.lock() {
            debugger.deref_mut().leave_block(engine_state, block);
        }
    }

    fn enter_element(engine_state: &EngineState, element: &PipelineElement) {
        if let Ok(mut debugger) = engine_state.debugger.lock() {
            debugger.deref_mut().enter_element(engine_state, element);
        }
    }

    fn leave_element(
        engine_state: &EngineState,
        element: &PipelineElement,
        result: &Result<PipelineData, ShellError>,
    ) {
        if let Ok(mut debugger) = engine_state.debugger.lock() {
            debugger
                .deref_mut()
                .leave_element(engine_state, element, result);
        }
    }

    fn enter_instruction(
        engine_state: &EngineState,
        ir_block: &IrBlock,
        instruction_index: usize,
        registers: &[PipelineData],
    ) {
        if let Ok(mut debugger) = engine_state.debugger.lock() {
            debugger.deref_mut().enter_instruction(
                engine_state,
                ir_block,
                instruction_index,
                registers,
            )
        }
    }

    fn leave_instruction(
        engine_state: &EngineState,
        ir_block: &IrBlock,
        instruction_index: usize,
        registers: &[PipelineData],
        error: Option<&ShellError>,
    ) {
        if let Ok(mut debugger) = engine_state.debugger.lock() {
            debugger.deref_mut().leave_instruction(
                engine_state,
                ir_block,
                instruction_index,
                registers,
                error,
            )
        }
    }
}

/// Marker struct signalizing that evaluation should NOT use a Debugger
///
/// Trait methods are empty calls to be optimized away.
#[derive(Clone, Copy, Debug)]
pub struct WithoutDebug;

impl DebugContext for WithoutDebug {}

/// Debugger trait that every debugger needs to implement.
///
/// By default, its methods are empty. Not every Debugger needs to implement all of them.
pub trait Debugger: Send + Debug {
    /// Called by EngineState::activate_debugger().
    ///
    /// Intended for initializing the debugger.
    fn activate(&mut self) {}

    /// Called by EngineState::deactivate_debugger().
    ///
    /// Intended for wrapping up the debugger after a debugging session before returning back to
    /// normal evaluation without debugging.
    fn deactivate(&mut self) {}

    /// Called when the evaluator enters a block
    #[allow(unused_variables)]
    fn enter_block(&mut self, engine_state: &EngineState, block: &Block) {}

    /// Called when the evaluator leaves a block
    #[allow(unused_variables)]
    fn leave_block(&mut self, engine_state: &EngineState, block: &Block) {}

    /// Called when the AST evaluator enters a pipeline element
    #[allow(unused_variables)]
    fn enter_element(&mut self, engine_state: &EngineState, pipeline_element: &PipelineElement) {}

    /// Called when the AST evaluator leaves a pipeline element
    #[allow(unused_variables)]
    fn leave_element(
        &mut self,
        engine_state: &EngineState,
        element: &PipelineElement,
        result: &Result<PipelineData, ShellError>,
    ) {
    }

    /// Called before the IR evaluator runs an instruction
    #[allow(unused_variables)]
    fn enter_instruction(
        &mut self,
        engine_state: &EngineState,
        ir_block: &IrBlock,
        instruction_index: usize,
        registers: &[PipelineData],
    ) {
    }

    /// Called after the IR evaluator runs an instruction
    #[allow(unused_variables)]
    fn leave_instruction(
        &mut self,
        engine_state: &EngineState,
        ir_block: &IrBlock,
        instruction_index: usize,
        registers: &[PipelineData],
        error: Option<&ShellError>,
    ) {
    }

    /// Create a final report as a Value
    ///
    /// Intended to be called after deactivate()
    #[allow(unused_variables)]
    fn report(&self, engine_state: &EngineState, debugger_span: Span) -> Result<Value, ShellError> {
        Ok(Value::nothing(debugger_span))
    }
}

/// A debugger that does nothing
///
/// Used as a placeholder debugger when not debugging.
#[derive(Debug)]
pub struct NoopDebugger;

impl Debugger for NoopDebugger {}