aether/debugger/
state.rs

1// src/debugger/state.rs
2//! Debugger state management
3
4use crate::debugger::breakpoint::{Breakpoint, BreakpointType};
5use std::collections::HashMap;
6
7/// Execution control mode
8#[derive(Debug, Clone, PartialEq)]
9pub enum ExecutionMode {
10    /// Normal execution (check breakpoints only)
11    Normal,
12    /// Step into function calls
13    StepInto,
14    /// Step over function calls
15    StepOver,
16    /// Step out of current function
17    StepOut,
18    /// Continue until next breakpoint
19    Continue,
20}
21
22/// Core debugger state
23pub struct DebuggerState {
24    /// All breakpoints indexed by ID
25    breakpoints: HashMap<usize, Breakpoint>,
26    /// Next breakpoint ID to assign
27    next_breakpoint_id: usize,
28    /// Current execution mode
29    execution_mode: ExecutionMode,
30    /// Current source file location (file, line)
31    current_location: Option<(String, usize)>,
32    /// Stack depth for step over/step out operations
33    step_over_depth: Option<usize>,
34    /// Whether debugger is active
35    is_active: bool,
36}
37
38impl DebuggerState {
39    /// Create a new debugger state
40    pub fn new() -> Self {
41        DebuggerState {
42            breakpoints: HashMap::new(),
43            next_breakpoint_id: 1,
44            execution_mode: ExecutionMode::Normal,
45            current_location: None,
46            step_over_depth: None,
47            is_active: false,
48        }
49    }
50
51    /// Activate the debugger
52    pub fn activate(&mut self) {
53        self.is_active = true;
54    }
55
56    /// Deactivate the debugger
57    pub fn deactivate(&mut self) {
58        self.is_active = false;
59    }
60
61    /// Check if debugger is active
62    pub fn is_active(&self) -> bool {
63        self.is_active
64    }
65
66    /// Set a breakpoint and return its ID
67    pub fn set_breakpoint(&mut self, bp_type: BreakpointType) -> usize {
68        let id = self.next_breakpoint_id;
69        self.next_breakpoint_id += 1;
70        self.breakpoints.insert(id, Breakpoint::new(id, bp_type));
71        id
72    }
73
74    /// Remove a breakpoint by ID
75    pub fn remove_breakpoint(&mut self, id: usize) -> bool {
76        self.breakpoints.remove(&id).is_some()
77    }
78
79    /// Remove all breakpoints
80    pub fn remove_all_breakpoints(&mut self) {
81        self.breakpoints.clear();
82    }
83
84    /// Enable or disable a breakpoint
85    pub fn toggle_breakpoint(&mut self, id: usize, enabled: bool) -> bool {
86        if let Some(bp) = self.breakpoints.get_mut(&id) {
87            bp.enabled = enabled;
88            true
89        } else {
90            false
91        }
92    }
93
94    /// Get a breakpoint by ID
95    pub fn get_breakpoint(&self, id: usize) -> Option<&Breakpoint> {
96        self.breakpoints.get(&id)
97    }
98
99    /// List all breakpoints
100    pub fn list_breakpoints(&self) -> Vec<&Breakpoint> {
101        let mut bps: Vec<_> = self.breakpoints.values().collect();
102        bps.sort_by_key(|bp| bp.id);
103        bps
104    }
105
106    /// Set the execution mode
107    pub fn set_execution_mode(&mut self, mode: ExecutionMode) {
108        self.execution_mode = mode;
109    }
110
111    /// Get the current execution mode
112    pub fn execution_mode(&self) -> &ExecutionMode {
113        &self.execution_mode
114    }
115
116    /// Set the step over depth
117    pub fn set_step_over_depth(&mut self, depth: usize) {
118        self.step_over_depth = Some(depth);
119    }
120
121    /// Clear the step over depth
122    pub fn clear_step_over_depth(&mut self) {
123        self.step_over_depth = None;
124    }
125
126    /// Update current location
127    pub fn update_location(&mut self, file: String, line: usize) {
128        self.current_location = Some((file, line));
129    }
130
131    /// Get current location
132    pub fn current_location(&self) -> Option<&(String, usize)> {
133        self.current_location.as_ref()
134    }
135
136    /// Check if a function breakpoint should trigger
137    pub fn should_pause_at_function(&mut self, func_name: &str) -> bool {
138        if !self.is_active {
139            return false;
140        }
141
142        for bp in self.breakpoints.values_mut() {
143            if bp.is_function_breakpoint(func_name) && bp.enabled {
144                bp.hit_count += 1;
145                if bp.hit_count > bp.ignore_count {
146                    return true;
147                }
148            }
149        }
150        false
151    }
152
153    /// Check if execution should pause at the given location
154    pub fn should_pause(&mut self, file: &str, line: usize, call_stack_depth: usize) -> bool {
155        if !self.is_active {
156            return false;
157        }
158
159        // Update current location
160        self.current_location = Some((file.to_string(), line));
161
162        match self.execution_mode {
163            ExecutionMode::Normal => {
164                // Check breakpoints only
165                for bp in self.breakpoints.values_mut() {
166                    if bp.should_trigger(file, line) {
167                        return true;
168                    }
169                }
170                false
171            }
172            ExecutionMode::StepInto => {
173                // Pause at next statement
174                self.execution_mode = ExecutionMode::Normal;
175                true
176            }
177            ExecutionMode::StepOver => {
178                // Pause when stack depth returns to the same level
179                if let Some(target_depth) = self.step_over_depth
180                    && call_stack_depth <= target_depth
181                {
182                    self.step_over_depth = None;
183                    self.execution_mode = ExecutionMode::Normal;
184                    return true;
185                }
186                false
187            }
188            ExecutionMode::StepOut => {
189                // Pause when stack depth becomes less than current
190                if let Some(target_depth) = self.step_over_depth
191                    && call_stack_depth < target_depth
192                {
193                    self.step_over_depth = None;
194                    self.execution_mode = ExecutionMode::Normal;
195                    return true;
196                }
197                false
198            }
199            ExecutionMode::Continue => {
200                // Pause only at breakpoints
201                for bp in self.breakpoints.values_mut() {
202                    if bp.should_trigger(file, line) {
203                        self.execution_mode = ExecutionMode::Normal;
204                        return true;
205                    }
206                }
207                false
208            }
209        }
210    }
211}
212
213impl Default for DebuggerState {
214    fn default() -> Self {
215        Self::new()
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222
223    #[test]
224    fn test_set_breakpoint() {
225        let mut state = DebuggerState::new();
226        let id = state.set_breakpoint(BreakpointType::Line {
227            file: "test.aether".to_string(),
228            line: 10,
229        });
230
231        assert_eq!(id, 1);
232        assert!(state.get_breakpoint(id).is_some());
233    }
234
235    #[test]
236    fn test_remove_breakpoint() {
237        let mut state = DebuggerState::new();
238        let id = state.set_breakpoint(BreakpointType::Line {
239            file: "test.aether".to_string(),
240            line: 10,
241        });
242
243        assert!(state.remove_breakpoint(id));
244        assert!(!state.remove_breakpoint(id));
245        assert!(state.get_breakpoint(id).is_none());
246    }
247
248    #[test]
249    fn test_toggle_breakpoint() {
250        let mut state = DebuggerState::new();
251        let id = state.set_breakpoint(BreakpointType::Line {
252            file: "test.aether".to_string(),
253            line: 10,
254        });
255
256        assert!(state.toggle_breakpoint(id, false));
257        assert!(!state.get_breakpoint(id).unwrap().enabled);
258        assert!(state.toggle_breakpoint(id, true));
259        assert!(state.get_breakpoint(id).unwrap().enabled);
260    }
261
262    #[test]
263    fn test_should_pause_normal_mode() {
264        let mut state = DebuggerState::new();
265        state.activate();
266
267        let _: usize = state.set_breakpoint(BreakpointType::Line {
268            file: "test.aether".to_string(),
269            line: 10,
270        });
271
272        assert!(state.should_pause("test.aether", 10, 0));
273        assert!(!state.should_pause("test.aether", 11, 0));
274    }
275
276    #[test]
277    fn test_step_into_mode() {
278        let mut state = DebuggerState::new();
279        state.activate();
280        state.set_execution_mode(ExecutionMode::StepInto);
281
282        // Should pause immediately
283        assert!(state.should_pause("test.aether", 10, 0));
284        // After stepping, mode returns to normal
285        assert_eq!(state.execution_mode(), &ExecutionMode::Normal);
286    }
287
288    #[test]
289    fn test_step_over_mode() {
290        let mut state = DebuggerState::new();
291        state.activate();
292        state.set_execution_mode(ExecutionMode::StepOver);
293        state.set_step_over_depth(2);
294
295        // Should not pause while depth > 2
296        assert!(!state.should_pause("test.aether", 10, 3));
297        // Should pause when depth returns to 2
298        assert!(state.should_pause("test.aether", 11, 2));
299    }
300
301    #[test]
302    fn test_step_out_mode() {
303        let mut state = DebuggerState::new();
304        state.activate();
305        state.set_execution_mode(ExecutionMode::StepOut);
306        state.set_step_over_depth(2);
307
308        // Should not pause while depth >= 2
309        assert!(!state.should_pause("test.aether", 10, 2));
310        // Should pause when depth < 2
311        assert!(state.should_pause("test.aether", 11, 1));
312    }
313
314    #[test]
315    fn test_function_breakpoint() {
316        let mut state = DebuggerState::new();
317        state.activate();
318        state.set_breakpoint(BreakpointType::Function {
319            name: "myFunc".to_string(),
320        });
321
322        assert!(state.should_pause_at_function("myFunc"));
323        assert!(!state.should_pause_at_function("otherFunc"));
324    }
325}