1use crate::debugger::breakpoint::BreakpointType;
5use crate::debugger::state::{DebuggerState, ExecutionMode};
6use crate::evaluator::Evaluator;
7use std::cell::RefCell;
8use std::rc::Rc;
9
10#[derive(Debug, Clone, PartialEq)]
12pub enum CommandAction {
13 Continue,
15 Stay,
17 Quit,
19}
20
21pub struct DebuggerSession {
23 evaluator: Rc<RefCell<Evaluator>>,
24 state: DebuggerState,
25 source_code: Option<String>,
26 source_file: Option<String>,
27}
28
29impl DebuggerSession {
30 pub fn new(evaluator: Rc<RefCell<Evaluator>>) -> Self {
32 DebuggerSession {
33 evaluator,
34 state: DebuggerState::new(),
35 source_code: None,
36 source_file: None,
37 }
38 }
39
40 pub fn set_source(&mut self, source: String, file: String) {
42 self.source_code = Some(source);
43 self.source_file = Some(file);
44 }
45
46 pub fn start(&mut self) {
48 self.state.activate();
49 println!("Aether Debugger v1.0");
50 if let Some(file) = &self.source_file {
51 println!("Debugging: {}", file);
52 }
53 println!("Type 'help' for available commands\n");
54 }
55
56 pub fn state_mut(&mut self) -> &mut DebuggerState {
58 &mut self.state
59 }
60
61 pub fn state(&self) -> &DebuggerState {
63 &self.state
64 }
65
66 pub fn handle_command(&mut self, cmd: &str) -> (String, CommandAction) {
68 let parts: Vec<&str> = cmd.split_whitespace().collect();
69 if parts.is_empty() {
70 return (String::new(), CommandAction::Stay);
71 }
72
73 let command = parts[0].to_lowercase();
74 let args = &parts[1..];
75
76 let (msg, action) = match command.as_str() {
77 "break" | "b" => self.cmd_break(args),
78 "delete" | "d" => self.cmd_delete(args),
79 "disable" => self.cmd_disable(args),
80 "enable" => self.cmd_enable(args),
81 "info" => self.cmd_info(args),
82 "step" | "s" => self.cmd_step(args),
83 "next" | "n" => self.cmd_next(args),
84 "finish" | "f" => self.cmd_finish(args),
85 "continue" | "c" => self.cmd_continue(args),
86 "print" | "p" => self.cmd_print(args),
87 "backtrace" | "bt" => self.cmd_backtrace(args),
88 "frame" => self.cmd_frame(args),
89 "list" | "l" => self.cmd_list(args),
90 "help" | "h" | "?" => self.cmd_help(args),
91 "quit" | "q" => self.cmd_quit(args),
92 _ => (
93 format!(
94 "Unknown command: {}. Type 'help' for available commands.",
95 command
96 ),
97 CommandAction::Stay,
98 ),
99 };
100
101 (msg, action)
102 }
103
104 fn cmd_break(&mut self, args: &[&str]) -> (String, CommandAction) {
107 if args.is_empty() {
108 return (
109 "Usage: break [file:]line | break function_name".to_string(),
110 CommandAction::Stay,
111 );
112 }
113
114 let loc = args[0];
115
116 if let Ok(line) = loc.parse::<usize>() {
118 let file = self
120 .state
121 .current_location()
122 .map(|(f, _)| f.clone())
123 .or_else(|| self.source_file.clone())
124 .unwrap_or_else(|| "<unknown>".to_string());
125
126 let id = self.state.set_breakpoint(BreakpointType::Line {
127 file: file.clone(),
128 line,
129 });
130 return (
131 format!("Breakpoint {} set at {}:{}", id, file, line),
132 CommandAction::Stay,
133 );
134 }
135
136 if let Some(pos) = loc.find(':') {
138 let file = loc[..pos].to_string();
139 if let Ok(line) = loc[pos + 1..].parse::<usize>() {
140 let id = self.state.set_breakpoint(BreakpointType::Line {
141 file: file.clone(),
142 line,
143 });
144 return (
145 format!("Breakpoint {} set at {}:{}", id, file, line),
146 CommandAction::Stay,
147 );
148 }
149 }
150
151 let id = self.state.set_breakpoint(BreakpointType::Function {
153 name: loc.to_string(),
154 });
155 (
156 format!("Breakpoint {} set at function '{}'", id, loc),
157 CommandAction::Stay,
158 )
159 }
160
161 fn cmd_delete(&mut self, args: &[&str]) -> (String, CommandAction) {
162 if args.is_empty() {
163 let count = self.state.list_breakpoints().len();
164 self.state.remove_all_breakpoints();
165 return (
166 format!("All breakpoints deleted ({})", count),
167 CommandAction::Stay,
168 );
169 }
170
171 if let Ok(id) = args[0].parse::<usize>() {
172 if self.state.remove_breakpoint(id) {
173 (format!("Breakpoint {} deleted", id), CommandAction::Stay)
174 } else {
175 (format!("Breakpoint {} not found", id), CommandAction::Stay)
176 }
177 } else {
178 ("Invalid breakpoint ID".to_string(), CommandAction::Stay)
179 }
180 }
181
182 fn cmd_disable(&mut self, args: &[&str]) -> (String, CommandAction) {
183 if args.is_empty() {
184 return (
185 "Usage: disable <breakpoint_id>".to_string(),
186 CommandAction::Stay,
187 );
188 }
189
190 if let Ok(id) = args[0].parse::<usize>() {
191 if self.state.toggle_breakpoint(id, false) {
192 (format!("Breakpoint {} disabled", id), CommandAction::Stay)
193 } else {
194 (format!("Breakpoint {} not found", id), CommandAction::Stay)
195 }
196 } else {
197 ("Invalid breakpoint ID".to_string(), CommandAction::Stay)
198 }
199 }
200
201 fn cmd_enable(&mut self, args: &[&str]) -> (String, CommandAction) {
202 if args.is_empty() {
203 return (
204 "Usage: enable <breakpoint_id>".to_string(),
205 CommandAction::Stay,
206 );
207 }
208
209 if let Ok(id) = args[0].parse::<usize>() {
210 if self.state.toggle_breakpoint(id, true) {
211 (format!("Breakpoint {} enabled", id), CommandAction::Stay)
212 } else {
213 (format!("Breakpoint {} not found", id), CommandAction::Stay)
214 }
215 } else {
216 ("Invalid breakpoint ID".to_string(), CommandAction::Stay)
217 }
218 }
219
220 fn cmd_info(&mut self, args: &[&str]) -> (String, CommandAction) {
221 if args.is_empty() {
222 return (
223 "Usage: info breakpoints | info locals | info args".to_string(),
224 CommandAction::Stay,
225 );
226 }
227
228 match args[0] {
229 "breakpoints" | "break" | "bp" => {
230 let breakpoints = self.state.list_breakpoints();
231 if breakpoints.is_empty() {
232 return ("No breakpoints".to_string(), CommandAction::Stay);
233 }
234
235 let mut result = String::from("Breakpoints:\n");
236 for bp in breakpoints {
237 let status = if bp.enabled { " enabled" } else { " disabled" };
238 result.push_str(&format!(
239 " ID: {:3}{} | {} | hits: {} | {}\n",
240 bp.id,
241 status,
242 bp.location_string(),
243 bp.hit_count,
244 if bp.ignore_count > 0 {
245 format!("(ignore first {})", bp.ignore_count)
246 } else {
247 String::new()
248 }
249 ));
250 }
251 (result, CommandAction::Stay)
252 }
253 "locals" => {
254 (
256 "Local variables: Not yet implemented".to_string(),
257 CommandAction::Stay,
258 )
259 }
260 "args" => (
261 "Arguments: Not yet implemented".to_string(),
262 CommandAction::Stay,
263 ),
264 _ => (
265 format!("Unknown info command: {}", args[0]),
266 CommandAction::Stay,
267 ),
268 }
269 }
270
271 fn cmd_step(&mut self, args: &[&str]) -> (String, CommandAction) {
272 let _count = if args.is_empty() {
273 1
274 } else {
275 args[0].parse::<usize>().unwrap_or(1)
276 };
277
278 self.state.set_execution_mode(ExecutionMode::StepInto);
279 ("Stepping...".to_string(), CommandAction::Continue)
280 }
281
282 fn cmd_next(&mut self, args: &[&str]) -> (String, CommandAction) {
283 let _count = if args.is_empty() {
284 1
285 } else {
286 args[0].parse::<usize>().unwrap_or(1)
287 };
288
289 let evaluator = self.evaluator.borrow();
290 let depth = evaluator.get_call_stack_depth();
291 drop(evaluator);
292
293 self.state.set_execution_mode(ExecutionMode::StepOver);
294 self.state.set_step_over_depth(depth);
295 ("Next...".to_string(), CommandAction::Continue)
296 }
297
298 fn cmd_finish(&mut self, _args: &[&str]) -> (String, CommandAction) {
299 let evaluator = self.evaluator.borrow();
300 let depth = evaluator.get_call_stack_depth();
301 drop(evaluator);
302
303 self.state.set_execution_mode(ExecutionMode::StepOut);
304 self.state.set_step_over_depth(depth);
305 (
306 "Running until current function returns...".to_string(),
307 CommandAction::Continue,
308 )
309 }
310
311 fn cmd_continue(&mut self, _args: &[&str]) -> (String, CommandAction) {
312 self.state.set_execution_mode(ExecutionMode::Continue);
313 ("Continuing...".to_string(), CommandAction::Continue)
314 }
315
316 fn cmd_print(&mut self, args: &[&str]) -> (String, CommandAction) {
317 if args.is_empty() {
318 return (
319 "Usage: print <variable_name>".to_string(),
320 CommandAction::Stay,
321 );
322 }
323
324 let var_name = args[0];
325 let evaluator = self.evaluator.borrow();
326
327 match evaluator.get_global(var_name) {
328 Some(value) => (format!("{} = {}", var_name, value), CommandAction::Stay),
329 None => (
330 format!("Variable '{}' not found", var_name),
331 CommandAction::Stay,
332 ),
333 }
334 }
335
336 fn cmd_backtrace(&mut self, args: &[&str]) -> (String, CommandAction) {
337 let evaluator = self.evaluator.borrow();
338 let call_stack = evaluator.get_call_stack();
339
340 if call_stack.is_empty() {
341 drop(evaluator);
342 return ("No stack.".to_string(), CommandAction::Stay);
343 }
344
345 let max_frames = if args.is_empty() {
346 call_stack.len()
347 } else {
348 args[0].parse::<usize>().unwrap_or(call_stack.len())
349 };
350
351 let mut result = String::from("Call stack:\n");
352 for (i, frame) in call_stack.iter().take(max_frames).enumerate() {
353 result.push_str(&format!("#{} {}\n", i, frame.signature));
354 }
355 drop(evaluator);
356 (result, CommandAction::Stay)
357 }
358
359 fn cmd_frame(&mut self, args: &[&str]) -> (String, CommandAction) {
360 if args.is_empty() {
361 return (
362 "Usage: frame <frame_number>".to_string(),
363 CommandAction::Stay,
364 );
365 }
366
367 if let Ok(_frame_num) = args[0].parse::<usize>() {
368 (
369 "Frame selection not yet implemented".to_string(),
370 CommandAction::Stay,
371 )
372 } else {
373 ("Invalid frame number".to_string(), CommandAction::Stay)
374 }
375 }
376
377 fn cmd_list(&mut self, args: &[&str]) -> (String, CommandAction) {
378 let count = if args.is_empty() {
379 10
380 } else {
381 args[0].parse::<usize>().unwrap_or(10)
382 };
383
384 if let Some(source) = &self.source_code {
385 let current_line = self
386 .state
387 .current_location()
388 .map(|(_, line)| *line)
389 .unwrap_or(1);
390
391 let lines: Vec<&str> = source.lines().collect();
392 let start = current_line.saturating_sub(count / 2);
393 let end = (start + count).min(lines.len());
394
395 let mut result = String::new();
396 for (idx, line) in lines.iter().enumerate().take(end).skip(start) {
397 let marker = if idx + 1 == current_line { "=>" } else { " " };
398 result.push_str(&format!("{} {:4}: {}\n", marker, idx + 1, line));
399 }
400 (result, CommandAction::Stay)
401 } else {
402 ("No source code available".to_string(), CommandAction::Stay)
403 }
404 }
405
406 fn cmd_help(&mut self, _args: &[&str]) -> (String, CommandAction) {
407 (HELP_TEXT.to_string(), CommandAction::Stay)
408 }
409
410 fn cmd_quit(&mut self, _args: &[&str]) -> (String, CommandAction) {
411 ("Exiting debugger...".to_string(), CommandAction::Quit)
412 }
413}
414
415const HELP_TEXT: &str = r#"
416Aether Debugger Commands
417
418Execution Control:
419 step [N] Step N times (default 1), stepping into function calls
420 next [N] Step N times (default 1), stepping over function calls
421 finish Execute until the current function returns
422 continue Continue execution until next breakpoint
423
424Breakpoints:
425 break [file:]line Set breakpoint at line
426 break function Set breakpoint at function entry
427 delete [N] Delete breakpoint N (or all if N not specified)
428 disable [N] Disable breakpoint N
429 enable [N] Enable breakpoint N
430 info breakpoints List all breakpoints
431
432Stack & Variables:
433 backtrace [N] Print backtrace of N frames (all if N not specified)
434 frame N Select and print stack frame N
435 print expr Print value of expression/variable
436 info locals Print local variables
437
438Source:
439 list [N] List N lines of source (default 10)
440
441Miscellaneous:
442 help Show this help message
443 quit Exit debugger
444
445Examples:
446 (aether-debug) break 15 # Set breakpoint at line 15
447 (aether-debug) break calc.aether:20 # Set at file:line
448 (aether-debug) break processData # Set at function entry
449 (aether-debug) next # Step over
450 (aether-debug) step # Step into
451 (aether-debug) print X # Show variable X
452 (aether-debug) backtrace # Show call stack
453"#;
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458 use crate::environment::Environment;
459 use std::cell::RefCell;
460
461 fn create_test_session() -> DebuggerSession {
462 let env = Rc::new(RefCell::new(Environment::new()));
463 let evaluator = Rc::new(RefCell::new(Evaluator::with_env(env)));
464 let mut session = DebuggerSession::new(evaluator);
465 session.set_source(
466 "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n".to_string(),
467 "test.aether".to_string(),
468 );
469 session
470 }
471
472 #[test]
473 fn test_break_command() {
474 let mut session = create_test_session();
475
476 let (result, _) = session.handle_command("break 10");
477 assert!(result.contains("Breakpoint"));
478 }
479
480 #[test]
481 fn test_step_command() {
482 let mut session = create_test_session();
483
484 let (_, action) = session.handle_command("step");
485 assert_eq!(action, CommandAction::Continue);
486 assert_eq!(session.state().execution_mode(), &ExecutionMode::StepInto);
487 }
488
489 #[test]
490 fn test_next_command() {
491 let mut session = create_test_session();
492
493 let (_, action) = session.handle_command("next");
494 assert_eq!(action, CommandAction::Continue);
495 assert_eq!(session.state().execution_mode(), &ExecutionMode::StepOver);
496 }
497
498 #[test]
499 fn test_continue_command() {
500 let mut session = create_test_session();
501
502 let (_, action) = session.handle_command("continue");
503 assert_eq!(action, CommandAction::Continue);
504 assert_eq!(session.state().execution_mode(), &ExecutionMode::Continue);
505 }
506}