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
//! 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,
    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 evaluator enters a pipeline element
    #[allow(unused_variables)]
    fn enter_element(engine_state: &EngineState, element: &PipelineElement) {}

    /// Called when the evaluator leaves a pipeline element
    #[allow(unused_variables)]
    fn leave_element(
        engine_state: &EngineState,
        element: &PipelineElement,
        result: &Result<PipelineData, 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);
        }
    }
}

/// 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 evaluator enters a pipeline element
    #[allow(unused_variables)]
    fn enter_element(&mut self, engine_state: &EngineState, pipeline_element: &PipelineElement) {}

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