aether/debugger/
breakpoint.rs

1// src/debugger/breakpoint.rs
2//! Breakpoint management for the Aether debugger
3
4/// Represents the type of a breakpoint
5#[derive(Debug, Clone, PartialEq)]
6pub enum BreakpointType {
7    /// Line breakpoint at a specific file and line
8    Line { file: String, line: usize },
9    /// Function entry breakpoint
10    Function { name: String },
11    /// Conditional breakpoint (evaluates condition before pausing)
12    Conditional {
13        file: String,
14        line: usize,
15        condition: String,
16    },
17}
18
19/// Represents a breakpoint
20#[derive(Debug, Clone, PartialEq)]
21pub struct Breakpoint {
22    pub id: usize,
23    pub bp_type: BreakpointType,
24    pub enabled: bool,
25    pub hit_count: usize,
26    pub ignore_count: usize, // Skip first N hits
27}
28
29impl Breakpoint {
30    /// Create a new breakpoint
31    pub fn new(id: usize, bp_type: BreakpointType) -> Self {
32        Breakpoint {
33            id,
34            bp_type,
35            enabled: true,
36            hit_count: 0,
37            ignore_count: 0,
38        }
39    }
40
41    /// Check if this breakpoint should trigger at the given location
42    pub fn should_trigger(&mut self, file: &str, line: usize) -> bool {
43        if !self.enabled {
44            return false;
45        }
46
47        let matches = match &self.bp_type {
48            BreakpointType::Line { file: f, line: l } => f == file && *l == line,
49            BreakpointType::Conditional {
50                file: f, line: l, ..
51            } => f == file && *l == line,
52            BreakpointType::Function { .. } => false, // Function breakpoints are checked separately
53        };
54
55        if matches && self.hit_count >= self.ignore_count {
56            self.hit_count += 1;
57            true
58        } else if matches {
59            self.hit_count += 1;
60            false
61        } else {
62            false
63        }
64    }
65
66    /// Check if this is a function breakpoint for the given function name
67    pub fn is_function_breakpoint(&self, func_name: &str) -> bool {
68        match &self.bp_type {
69            BreakpointType::Function { name } => name == func_name,
70            _ => false,
71        }
72    }
73
74    /// Get the condition expression if this is a conditional breakpoint
75    pub fn get_condition(&self) -> Option<&str> {
76        match &self.bp_type {
77            BreakpointType::Conditional { condition, .. } => Some(condition.as_str()),
78            _ => None,
79        }
80    }
81
82    /// Get a string representation of the breakpoint location
83    pub fn location_string(&self) -> String {
84        match &self.bp_type {
85            BreakpointType::Line { file, line } => {
86                format!("{}:{}", file, line)
87            }
88            BreakpointType::Function { name } => {
89                format!("function '{}'", name)
90            }
91            BreakpointType::Conditional {
92                file,
93                line,
94                condition,
95            } => {
96                format!("{}:{} if {}", file, line, condition)
97            }
98        }
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn test_breakpoint_creation() {
108        let bp = Breakpoint::new(
109            1,
110            BreakpointType::Line {
111                file: "test.aether".to_string(),
112                line: 10,
113            },
114        );
115
116        assert_eq!(bp.id, 1);
117        assert!(bp.enabled);
118        assert_eq!(bp.hit_count, 0);
119    }
120
121    #[test]
122    fn test_breakpoint_trigger() {
123        let mut bp = Breakpoint::new(
124            1,
125            BreakpointType::Line {
126                file: "test.aether".to_string(),
127                line: 10,
128            },
129        );
130
131        assert!(bp.should_trigger("test.aether", 10));
132        assert!(!bp.should_trigger("test.aether", 11));
133        assert!(!bp.should_trigger("other.aether", 10));
134    }
135
136    #[test]
137    fn test_disabled_breakpoint() {
138        let mut bp = Breakpoint::new(
139            1,
140            BreakpointType::Line {
141                file: "test.aether".to_string(),
142                line: 10,
143            },
144        );
145        bp.enabled = false;
146
147        assert!(!bp.should_trigger("test.aether", 10));
148    }
149
150    #[test]
151    fn test_ignore_count() {
152        let mut bp = Breakpoint::new(
153            1,
154            BreakpointType::Line {
155                file: "test.aether".to_string(),
156                line: 10,
157            },
158        );
159        bp.ignore_count = 2;
160
161        assert!(!bp.should_trigger("test.aether", 10)); // hit 1, ignored
162        assert!(!bp.should_trigger("test.aether", 10)); // hit 2, ignored
163        assert!(bp.should_trigger("test.aether", 10)); // hit 3, triggers
164    }
165
166    #[test]
167    fn test_function_breakpoint() {
168        let bp = Breakpoint::new(
169            1,
170            BreakpointType::Function {
171                name: "myFunc".to_string(),
172            },
173        );
174
175        assert!(bp.is_function_breakpoint("myFunc"));
176        assert!(!bp.is_function_breakpoint("otherFunc"));
177    }
178}