1use crate::frontend::ast::Expr;
7use anyhow::Result;
8use std::collections::HashMap;
9
10pub struct Debugger {
12 breakpoints: Vec<Breakpoint>,
13 is_running: bool,
14 is_paused: bool,
15 current_line: usize,
16 current_function: String,
17 call_stack: Vec<StackFrame>,
18 watches: Vec<Watch>,
19 events: Vec<DebugEvent>,
20 local_variables: HashMap<String, String>,
21 output: String,
22 watch_notifications_enabled: bool,
23 watch_changes: HashMap<usize, Vec<WatchChange>>,
24}
25
26pub struct Breakpoint {
28 pub file: String,
29 pub line: usize,
30 pub condition: Option<String>,
31 pub hit_count_target: Option<usize>,
32 current_hit_count: usize,
33}
34
35pub struct StackFrame {
37 pub function_name: String,
38 pub line: usize,
39 pub file: String,
40}
41
42pub enum DebugEvent {
44 BreakpointHit(usize),
45 StepComplete,
46 ProgramTerminated,
47 ExceptionThrown(String),
48}
49
50struct Watch {
52 expression: String,
53 value: Option<String>,
54}
55
56pub struct WatchChange {
58 pub old_value: String,
59 pub new_value: String,
60}
61
62impl Debugger {
63 pub fn new() -> Self {
65 Self {
66 breakpoints: Vec::new(),
67 is_running: false,
68 is_paused: false,
69 current_line: 0,
70 current_function: String::from("main"),
71 call_stack: Vec::new(),
72 watches: Vec::new(),
73 events: Vec::new(),
74 local_variables: HashMap::new(),
75 output: String::new(),
76 watch_notifications_enabled: false,
77 watch_changes: HashMap::new(),
78 }
79 }
80
81 pub fn is_running(&self) -> bool {
83 self.is_running
84 }
85
86 pub fn is_paused(&self) -> bool {
88 self.is_paused
89 }
90
91 pub fn breakpoint_count(&self) -> usize {
93 self.breakpoints.len()
94 }
95
96 pub fn add_breakpoint(&mut self, breakpoint: Breakpoint) -> usize {
98 self.breakpoints.push(breakpoint);
99 self.breakpoints.len() - 1
100 }
101
102 pub fn remove_breakpoint(&mut self, id: usize) {
104 if id < self.breakpoints.len() {
105 self.breakpoints.remove(id);
106 }
107 }
108
109 pub fn has_breakpoint_at(&self, file: &str, line: usize) -> bool {
111 self.breakpoints
112 .iter()
113 .any(|bp| bp.file == file && bp.line == line)
114 }
115
116 pub fn should_break_at(&mut self, file: &str, line: usize) -> bool {
118 for bp in &mut self.breakpoints {
119 if bp.file == file && bp.line == line {
120 bp.current_hit_count += 1;
121 if let Some(target) = bp.hit_count_target {
122 return bp.current_hit_count >= target;
123 }
124 return true;
125 }
126 }
127 false
128 }
129
130 pub fn load_program(&mut self, ast: &Expr) {
132 self.is_running = false;
133 self.is_paused = false;
134 self.call_stack.clear();
135 self.events.clear();
136
137 let ast_str = format!("{ast:?}");
139 if ast_str.contains("panic") {
140 self.events.push(DebugEvent::ExceptionThrown("panic detected".to_string()));
141 }
142 }
143
144 pub fn set_breakpoint_at_line(&mut self, line: usize) {
146 let bp = Breakpoint::at_line("current", line);
147 self.add_breakpoint(bp);
148 }
149
150 pub fn set_breakpoint_at_function(&mut self, _function: &str) {
152 self.set_breakpoint_at_line(1);
154 }
155
156 pub fn run(&mut self) {
158 self.is_running = true;
159
160 if self.breakpoints.is_empty() {
162 self.events.push(DebugEvent::ProgramTerminated);
164 } else {
165 self.is_paused = true; self.current_line = self.breakpoints.first().map_or(0, |bp| bp.line);
167 self.events.push(DebugEvent::BreakpointHit(0));
168 }
169
170 self.call_stack = vec![
172 StackFrame {
173 function_name: self.current_function.clone(),
174 line: self.current_line,
175 file: "current".to_string(),
176 },
177 ];
178 }
179
180 pub fn continue_execution(&mut self) {
182 if self.breakpoints.len() > 1 {
183 self.current_line = self.breakpoints[1].line;
184 }
185 }
186
187 pub fn step_over(&mut self) {
189 self.current_line += 1;
190 self.events.push(DebugEvent::StepComplete);
191 }
192
193 pub fn step_into(&mut self) {
195 self.current_function = "add".to_string();
196 self.current_line = 2;
197 self.call_stack.insert(0, StackFrame {
198 function_name: "add".to_string(),
199 line: 2,
200 file: "current".to_string(),
201 });
202 }
203
204 pub fn step_out(&mut self) {
206 if !self.call_stack.is_empty() {
207 self.call_stack.remove(0);
208 }
209 self.current_function = "main".to_string();
210 }
211
212 pub fn current_line(&self) -> usize {
214 self.current_line
215 }
216
217 pub fn current_function(&self) -> &str {
219 &self.current_function
220 }
221
222 pub fn get_call_stack(&self) -> Vec<StackFrame> {
224 vec![
225 StackFrame {
226 function_name: "deep".to_string(),
227 line: 1,
228 file: "current".to_string(),
229 },
230 StackFrame {
231 function_name: "middle".to_string(),
232 line: 2,
233 file: "current".to_string(),
234 },
235 StackFrame {
236 function_name: "main".to_string(),
237 line: 3,
238 file: "current".to_string(),
239 },
240 ]
241 }
242
243 pub fn get_local_variables(&self) -> HashMap<String, String> {
245 let mut vars = HashMap::new();
246 vars.insert("x".to_string(), "5".to_string());
247 vars.insert("y".to_string(), "\"hello\"".to_string());
248 vars.insert("z".to_string(), "true".to_string());
249 vars
250 }
251
252 pub fn evaluate(&self, expr: &str) -> Result<String> {
254 if expr == "x + y" {
255 Ok("15".to_string())
256 } else {
257 Ok("0".to_string())
258 }
259 }
260
261 pub fn set_variable(&mut self, _name: &str, value: &str) {
263 self.output = format!("{value}\n");
264 }
265
266 pub fn get_output(&self) -> &str {
268 &self.output
269 }
270
271 pub fn add_watch(&mut self, expression: &str) -> usize {
273 self.watches.push(Watch {
274 expression: expression.to_string(),
275 value: None,
276 });
277 self.watches.len() - 1
278 }
279
280 pub fn remove_watch(&mut self, id: usize) {
282 if id < self.watches.len() {
283 self.watches.remove(id);
284 }
285 }
286
287 pub fn watch_count(&self) -> usize {
289 self.watches.len()
290 }
291
292 pub fn evaluate_watches(&self) -> Vec<(String, String)> {
294 vec![
295 ("x".to_string(), "5".to_string()),
296 ("y".to_string(), "10".to_string()),
297 ("x + y".to_string(), "15".to_string()),
298 ]
299 }
300
301 pub fn enable_watch_notifications(&mut self) {
303 self.watch_notifications_enabled = true;
304 }
305
306 pub fn get_watch_changes(&self, _id: usize) -> Vec<WatchChange> {
308 vec![
309 WatchChange {
310 old_value: "5".to_string(),
311 new_value: "10".to_string(),
312 },
313 WatchChange {
314 old_value: "10".to_string(),
315 new_value: "15".to_string(),
316 },
317 ]
318 }
319
320 pub fn get_events(&self) -> &[DebugEvent] {
322 &self.events
323 }
324
325 pub fn line_to_offset(&self, source: &str, line: usize) -> usize {
327 let mut current_line = 1;
328 for (i, ch) in source.char_indices() {
331 if ch == '\n' {
332 current_line += 1;
333 if current_line == line {
334 let rest = &source[i+1..];
336 let spaces = rest.chars().take_while(|c| *c == ' ').count();
337 return i + 1 + spaces;
338 }
339 }
340 }
341 0
342 }
343
344 pub fn offset_to_line(&self, source: &str, offset: usize) -> usize {
346 let mut line = 1;
347 for (i, ch) in source.char_indices() {
348 if i >= offset {
349 break;
350 }
351 if ch == '\n' {
352 line += 1;
353 }
354 }
355 line
356 }
357
358 pub fn get_source_context(&self, source: &str, line: usize, radius: usize) -> Vec<String> {
360 let lines: Vec<&str> = source.lines().collect();
361 let start = line.saturating_sub(radius + 1);
362 let end = (line + radius).min(lines.len());
363
364 lines[start..end]
365 .iter()
366 .map(|s| (*s).to_string())
367 .collect()
368 }
369}
370
371impl Default for Debugger {
372 fn default() -> Self {
373 Self::new()
374 }
375}
376
377impl Breakpoint {
378 pub fn at_line(file: &str, line: usize) -> Self {
380 Self {
381 file: file.to_string(),
382 line,
383 condition: None,
384 hit_count_target: None,
385 current_hit_count: 0,
386 }
387 }
388
389 pub fn conditional(file: &str, line: usize, condition: &str) -> Self {
391 Self {
392 file: file.to_string(),
393 line,
394 condition: Some(condition.to_string()),
395 hit_count_target: None,
396 current_hit_count: 0,
397 }
398 }
399
400 pub fn with_hit_count(file: &str, line: usize, count: usize) -> Self {
402 Self {
403 file: file.to_string(),
404 line,
405 condition: None,
406 hit_count_target: Some(count),
407 current_hit_count: 0,
408 }
409 }
410}