1use super::core::{
7 Breakpoint, BreakpointId, BreakpointLocation, DebugCommand, DebugState, DebugValue,
8 DebuggerConfig, DisassemblyView, ExecutionLocation, InspectionResult, InspectionTarget,
9 MemoryView, Watch, WatchId,
10};
11use super::state::CallStack;
12use crate::{JitError, JitResult, NodeId};
13use std::collections::HashMap;
14use std::io::{self, Write};
15
16pub struct DebuggerInterface {
18 config: DebuggerConfig,
19 command_history: Vec<String>,
20 command_aliases: HashMap<String, String>,
21}
22
23impl DebuggerInterface {
24 pub fn new(config: DebuggerConfig) -> Self {
29 let mut aliases = HashMap::new();
30 aliases.insert("s".to_string(), "step".to_string());
32 aliases.insert("n".to_string(), "next".to_string());
33 aliases.insert("i".to_string(), "into".to_string());
34 aliases.insert("o".to_string(), "out".to_string());
35 aliases.insert("c".to_string(), "continue".to_string());
36 aliases.insert("b".to_string(), "break".to_string());
37 aliases.insert("d".to_string(), "delete".to_string());
38 aliases.insert("w".to_string(), "watch".to_string());
39 aliases.insert("p".to_string(), "inspect".to_string());
40 aliases.insert("mem".to_string(), "memory".to_string());
41 aliases.insert("dis".to_string(), "disasm".to_string());
42 aliases.insert("h".to_string(), "help".to_string());
43 aliases.insert("q".to_string(), "quit".to_string());
44
45 Self {
46 config,
47 command_history: Vec::new(),
48 command_aliases: aliases,
49 }
50 }
51
52 pub fn show_welcome_message(&self) {
54 println!("đĻ ToRSh JIT Debugger v1.0");
55 println!("Type 'help' for available commands");
56 println!("=====================================");
57 }
58
59 pub fn display_current_state(&self, state: &DebugState) {
64 println!("\n--- Current State ---");
65 println!("đ Location: {:?}", state.location);
66 println!("đĸ Step: {}", state.execution_step);
67 println!("âļī¸ Running: {}", state.is_running);
68
69 if !state.call_stack.is_empty() {
70 println!("đ Call depth: {}", state.call_stack.depth());
71 }
72
73 if !state.variables.is_empty() {
74 println!("đ Variables:");
75 for (name, value) in &state.variables {
76 println!(" {} = {:?}", name, value);
77 }
78 }
79 }
80
81 pub fn get_user_command(&mut self) -> JitResult<DebugCommand> {
86 print!("(torsh-debug) ");
87 io::stdout().flush().expect("stdout flush should succeed");
88
89 let mut input = String::new();
90 io::stdin()
91 .read_line(&mut input)
92 .map_err(|e| JitError::RuntimeError(format!("Failed to read input: {}", e)))?;
93
94 let input = input.trim();
95
96 if !input.is_empty() {
98 self.command_history.push(input.to_string());
99 }
100
101 self.parse_command(input)
102 }
103
104 pub fn parse_command(&self, input: &str) -> JitResult<DebugCommand> {
112 let parts: Vec<&str> = input.split_whitespace().collect();
113
114 if parts.is_empty() {
115 return Err(JitError::RuntimeError("Empty command".to_string()));
116 }
117
118 let binding = parts[0].to_string();
120 let command = self.command_aliases.get(parts[0]).unwrap_or(&binding);
121
122 match command.as_str() {
123 "step" => Ok(DebugCommand::Step),
124 "next" => Ok(DebugCommand::StepOver),
125 "into" => Ok(DebugCommand::StepInto),
126 "out" => Ok(DebugCommand::StepOut),
127 "continue" => Ok(DebugCommand::Continue),
128 "break" => {
129 if let Some(&location_str) = parts.get(1) {
130 let location = self.parse_breakpoint_location(location_str)?;
131 Ok(DebugCommand::SetBreakpoint { location })
132 } else {
133 Err(JitError::RuntimeError(
134 "Breakpoint location required".to_string(),
135 ))
136 }
137 }
138 "delete" => {
139 if let Some(&id_str) = parts.get(1) {
140 let id = id_str
141 .parse::<u64>()
142 .map_err(|_| JitError::RuntimeError("Invalid breakpoint ID".to_string()))?;
143 Ok(DebugCommand::RemoveBreakpoint {
144 id: BreakpointId(id),
145 })
146 } else {
147 Err(JitError::RuntimeError("Breakpoint ID required".to_string()))
148 }
149 }
150 "breakpoints" => Ok(DebugCommand::ListBreakpoints),
151 "watch" => {
152 if parts.len() > 1 {
153 let expression = parts[1..].join(" ");
154 Ok(DebugCommand::Watch { expression })
155 } else {
156 Err(JitError::RuntimeError(
157 "Watch expression required".to_string(),
158 ))
159 }
160 }
161 "unwatch" => {
162 if let Some(&id_str) = parts.get(1) {
163 let id = id_str
164 .parse::<u64>()
165 .map_err(|_| JitError::RuntimeError("Invalid watch ID".to_string()))?;
166 Ok(DebugCommand::Unwatch { id: WatchId(id) })
167 } else {
168 Err(JitError::RuntimeError("Watch ID required".to_string()))
169 }
170 }
171 "watches" => Ok(DebugCommand::ListWatches),
172 "inspect" => {
173 if let Some(&target_str) = parts.get(1) {
174 let target = self.parse_inspection_target(target_str)?;
175 Ok(DebugCommand::Inspect { target })
176 } else {
177 Err(JitError::RuntimeError(
178 "Inspection target required".to_string(),
179 ))
180 }
181 }
182 "stack" => Ok(DebugCommand::CallStack),
183 "locals" => Ok(DebugCommand::Locals),
184 "memory" => {
185 if let Some(&addr_str) = parts.get(1) {
186 let address = self.parse_memory_address(addr_str)?;
187 Ok(DebugCommand::Memory { address })
188 } else {
189 Err(JitError::RuntimeError(
190 "Memory address required".to_string(),
191 ))
192 }
193 }
194 "disasm" => {
195 let location = if parts.len() > 1 {
196 self.parse_execution_location(&parts[1..])?
197 } else {
198 ExecutionLocation::GraphNode(NodeId::new(0))
200 };
201 Ok(DebugCommand::Disassemble { location })
202 }
203 "help" => Ok(DebugCommand::Help),
204 "quit" | "exit" => Ok(DebugCommand::Quit),
205 "history" => {
206 self.show_command_history();
207 Ok(DebugCommand::Help) }
209 "clear" => {
210 self.clear_screen();
211 Ok(DebugCommand::Help)
212 }
213 _ => Err(JitError::RuntimeError(format!(
214 "Unknown command: {}. Type 'help' for available commands.",
215 command
216 ))),
217 }
218 }
219
220 fn parse_breakpoint_location(&self, location_str: &str) -> JitResult<BreakpointLocation> {
222 if location_str.starts_with("node_") {
223 let node_index = location_str[5..]
224 .parse::<usize>()
225 .map_err(|_| JitError::RuntimeError("Invalid node index".to_string()))?;
226 Ok(BreakpointLocation::GraphNode(NodeId::new(node_index)))
227 } else if location_str.contains(':') {
228 let parts: Vec<&str> = location_str.split(':').collect();
229 if parts.len() == 2 {
230 let function = parts[0].to_string();
231 let instruction = parts[1]
232 .parse::<usize>()
233 .map_err(|_| JitError::RuntimeError("Invalid instruction index".to_string()))?;
234 Ok(BreakpointLocation::Instruction {
235 function,
236 instruction,
237 })
238 } else {
239 Err(JitError::RuntimeError(
240 "Invalid breakpoint location format. Use 'node_N' or 'function:instruction'"
241 .to_string(),
242 ))
243 }
244 } else {
245 Err(JitError::RuntimeError(
246 "Invalid breakpoint location format. Use 'node_N' or 'function:instruction'"
247 .to_string(),
248 ))
249 }
250 }
251
252 fn parse_inspection_target(&self, target_str: &str) -> JitResult<InspectionTarget> {
254 if target_str.starts_with("node_") {
255 let node_index = target_str[5..]
256 .parse::<usize>()
257 .map_err(|_| JitError::RuntimeError("Invalid node index".to_string()))?;
258 Ok(InspectionTarget::Node(NodeId::new(node_index)))
259 } else if target_str.starts_with("0x") {
260 let address = u64::from_str_radix(&target_str[2..], 16)
261 .map_err(|_| JitError::RuntimeError("Invalid memory address".to_string()))?;
262 Ok(InspectionTarget::Memory(address))
263 } else {
264 Ok(InspectionTarget::Variable(target_str.to_string()))
265 }
266 }
267
268 fn parse_memory_address(&self, addr_str: &str) -> JitResult<u64> {
270 if addr_str.starts_with("0x") {
271 u64::from_str_radix(&addr_str[2..], 16)
272 .map_err(|_| JitError::RuntimeError("Invalid hexadecimal address".to_string()))
273 } else {
274 addr_str
275 .parse::<u64>()
276 .map_err(|_| JitError::RuntimeError("Invalid decimal address".to_string()))
277 }
278 }
279
280 fn parse_execution_location(&self, parts: &[&str]) -> JitResult<ExecutionLocation> {
282 if parts.is_empty() {
283 return Err(JitError::RuntimeError("Location required".to_string()));
284 }
285
286 if parts[0].starts_with("node_") {
287 let node_index = parts[0][5..]
288 .parse::<usize>()
289 .map_err(|_| JitError::RuntimeError("Invalid node index".to_string()))?;
290 Ok(ExecutionLocation::GraphNode(NodeId::new(node_index)))
291 } else if parts.len() >= 2 {
292 let function = parts[0].to_string();
293 let instruction_index = parts[1]
294 .parse::<usize>()
295 .map_err(|_| JitError::RuntimeError("Invalid instruction index".to_string()))?;
296 Ok(ExecutionLocation::Instruction {
297 function,
298 instruction_index,
299 })
300 } else {
301 Err(JitError::RuntimeError(
302 "Invalid location format. Use 'node_N' or 'function instruction_index'".to_string(),
303 ))
304 }
305 }
306
307 pub fn show_breakpoint_set(&self, location: BreakpointLocation) {
309 println!("â
Breakpoint set at {:?}", location);
310 }
311
312 pub fn show_breakpoint_removed(&self, id: BreakpointId) {
314 println!("â Breakpoint {} removed", id.0);
315 }
316
317 pub fn show_breakpoints(&self, breakpoints: &[&Breakpoint]) {
319 if breakpoints.is_empty() {
320 println!("đ No breakpoints set");
321 } else {
322 println!("đ Breakpoints:");
323 for bp in breakpoints {
324 let status = if bp.enabled {
325 "â
enabled"
326 } else {
327 "â disabled"
328 };
329 let condition = if let Some(ref cond) = bp.condition {
330 format!(" [condition: {}]", cond)
331 } else {
332 String::new()
333 };
334 println!(
335 " {} - {:?} ({}) [hits: {}]{}",
336 bp.id.0, bp.location, status, bp.hit_count, condition
337 );
338 }
339 }
340 }
341
342 pub fn show_watch_added(&self, id: WatchId, expression: &str) {
344 println!("đī¸ Watch {} added: {}", id.0, expression);
345 }
346
347 pub fn show_watch_removed(&self, id: WatchId) {
349 println!("â Watch {} removed", id.0);
350 }
351
352 pub fn show_watches(&self, watches: &[&Watch]) {
354 if watches.is_empty() {
355 println!("đī¸ No watches set");
356 } else {
357 println!("đī¸ Watches:");
358 for watch in watches {
359 let status = if watch.enabled {
360 "â
enabled"
361 } else {
362 "â disabled"
363 };
364 let value = if let Some(ref val) = watch.last_value {
365 format!(" = {:?}", val)
366 } else {
367 " = <not evaluated>".to_string()
368 };
369 println!(
370 " {} - {} ({}){}",
371 watch.id.0, watch.expression, status, value
372 );
373 }
374 }
375 }
376
377 pub fn show_inspection_result(&self, result: &InspectionResult) {
379 match result {
380 InspectionResult::Variable {
381 name,
382 value,
383 type_info,
384 } => {
385 println!(
386 "đ Variable '{}': {:?} (type: {}, {} bytes)",
387 name, value, type_info.type_name, type_info.size_bytes
388 );
389 }
390 InspectionResult::Node {
391 node_id,
392 value,
393 metadata,
394 } => {
395 println!("đ Node {:?}: {:?}", node_id, value);
396 println!(" Operation: {}", metadata.operation);
397 println!(" Inputs: {}", metadata.input_count);
398 println!(" Output shape: {:?}", metadata.output_shape);
399 println!(" Data type: {:?}", metadata.dtype);
400 }
401 InspectionResult::Memory {
402 address,
403 content,
404 size,
405 } => {
406 println!("đ Memory at 0x{:x} (size: {}):", address, size);
407 self.format_memory_dump(*address, content);
408 }
409 }
410 }
411
412 pub fn show_call_stack(&self, call_stack: &CallStack) {
414 if call_stack.is_empty() {
415 println!("đ Call stack is empty");
416 } else {
417 println!("đ Call stack (depth: {}):", call_stack.depth());
418 for (i, frame) in call_stack.frames().iter().rev().enumerate() {
419 let marker = if i == 0 { "âļī¸ " } else { " " };
420 println!(
421 "{}#{} - {} at {:?}",
422 marker, i, frame.function_name, frame.location
423 );
424 if !frame.local_variables.is_empty() {
425 println!(" locals: {} variables", frame.local_variables.len());
426 }
427 }
428 }
429 }
430
431 pub fn show_local_variables(&self, locals: &HashMap<String, DebugValue>) {
433 if locals.is_empty() {
434 println!("đĸ No local variables");
435 } else {
436 println!("đĸ Local variables ({}):", locals.len());
437 for (name, value) in locals {
438 println!(" {} = {:?}", name, value);
439 }
440 }
441 }
442
443 pub fn show_memory_view(&self, memory_view: &MemoryView) {
445 println!(
446 "đ§ Memory view starting at 0x{:x}:",
447 memory_view.start_address
448 );
449 self.format_memory_dump(memory_view.start_address, &memory_view.content);
450 }
451
452 fn format_memory_dump(&self, start_address: u64, content: &[u8]) {
454 for (i, chunk) in content.chunks(16).enumerate() {
455 print!(" {:08x}: ", start_address + (i * 16) as u64);
456
457 for (j, byte) in chunk.iter().enumerate() {
459 if j == 8 {
460 print!(" "); }
462 print!("{:02x} ", byte);
463 }
464
465 for j in chunk.len()..16 {
467 if j == 8 {
468 print!(" ");
469 }
470 print!(" ");
471 }
472
473 print!(" |");
474
475 for byte in chunk {
477 let ch = if byte.is_ascii_graphic() || *byte == b' ' {
478 *byte as char
479 } else {
480 '.'
481 };
482 print!("{}", ch);
483 }
484
485 println!("|");
486 }
487 }
488
489 pub fn show_disassembly(&self, disassembly: &DisassemblyView) {
491 println!("đ Disassembly at {:?}:", disassembly.location);
492 for instruction in &disassembly.instructions {
493 print!(
494 " {:08x}: {} {}",
495 instruction.address, instruction.opcode, instruction.operands
496 );
497 if let Some(comment) = &instruction.comment {
498 print!(" ; {}", comment);
499 }
500 println!();
501 }
502 }
503
504 pub fn show_help(&self) {
506 println!("đ Available commands:");
507 println!(" step, s - Execute one step");
508 println!(" next, n - Step over function calls");
509 println!(" into, i - Step into function calls");
510 println!(" out, o - Step out of current function");
511 println!(" continue, c - Continue execution");
512 println!(" break <loc>, b - Set breakpoint at location");
513 println!(" Examples: break node_5, break main:10");
514 println!(" delete <id>, d - Remove breakpoint by ID");
515 println!(" breakpoints - List all breakpoints");
516 println!(" watch <expr>, w - Watch expression");
517 println!(" Examples: watch variable_name, watch node_3");
518 println!(" unwatch <id> - Remove watch by ID");
519 println!(" watches - List all watches");
520 println!(" inspect <target>, p - Inspect variable/node/memory");
521 println!(" Examples: inspect var, inspect node_1, inspect 0x1000");
522 println!(" stack - Show call stack");
523 println!(" locals - Show local variables");
524 println!(" memory <addr>, mem - Show memory contents");
525 println!(" Examples: memory 0x1000, memory 4096");
526 println!(" disasm <loc>, dis - Disassemble at location");
527 println!(" history - Show command history");
528 println!(" clear - Clear screen");
529 println!(" help, h - Show this help");
530 println!(" quit, q - Exit debugger");
531 }
532
533 pub fn show_execution_complete(&self) {
535 println!("â
Execution completed successfully.");
536 }
537
538 pub fn show_command_history(&self) {
540 if self.command_history.is_empty() {
541 println!("đ No command history");
542 } else {
543 println!("đ Command history:");
544 for (i, cmd) in self.command_history.iter().enumerate() {
545 println!(" {}: {}", i + 1, cmd);
546 }
547 }
548 }
549
550 pub fn clear_screen(&self) {
552 print!("\x1B[2J\x1B[1;1H");
554 io::stdout().flush().expect("stdout flush should succeed");
555 }
556
557 pub fn show_error(&self, error: &str) {
559 println!("â Error: {}", error);
560 }
561
562 pub fn show_warning(&self, warning: &str) {
564 println!("â ī¸ Warning: {}", warning);
565 }
566
567 pub fn show_info(&self, info: &str) {
569 println!("âšī¸ {}", info);
570 }
571
572 pub fn show_success(&self, message: &str) {
574 println!("â
{}", message);
575 }
576
577 pub fn set_alias(&mut self, alias: String, command: String) {
579 self.command_aliases.insert(alias, command);
580 }
581
582 pub fn remove_alias(&mut self, alias: &str) {
584 self.command_aliases.remove(alias);
585 }
586
587 pub fn get_command_history(&self) -> &[String] {
589 &self.command_history
590 }
591
592 pub fn clear_command_history(&mut self) {
594 self.command_history.clear();
595 }
596
597 pub fn config(&self) -> &DebuggerConfig {
599 &self.config
600 }
601
602 pub fn update_config(&mut self, config: DebuggerConfig) {
604 self.config = config;
605 }
606}
607
608#[cfg(test)]
609mod tests {
610 use super::*;
611
612 fn create_test_interface() -> DebuggerInterface {
613 DebuggerInterface::new(DebuggerConfig::default())
614 }
615
616 #[test]
617 fn test_interface_creation() {
618 let interface = create_test_interface();
619 assert!(!interface.command_aliases.is_empty());
620 assert!(interface.command_history.is_empty());
621 }
622
623 #[test]
624 fn test_command_parsing_basic() {
625 let interface = create_test_interface();
626
627 assert!(matches!(
628 interface.parse_command("step"),
629 Ok(DebugCommand::Step)
630 ));
631 assert!(matches!(
632 interface.parse_command("s"),
633 Ok(DebugCommand::Step)
634 ));
635 assert!(matches!(
636 interface.parse_command("continue"),
637 Ok(DebugCommand::Continue)
638 ));
639 assert!(matches!(
640 interface.parse_command("c"),
641 Ok(DebugCommand::Continue)
642 ));
643 assert!(matches!(
644 interface.parse_command("help"),
645 Ok(DebugCommand::Help)
646 ));
647 assert!(matches!(
648 interface.parse_command("quit"),
649 Ok(DebugCommand::Quit)
650 ));
651 }
652
653 #[test]
654 fn test_breakpoint_location_parsing() {
655 let interface = create_test_interface();
656
657 let result = interface.parse_breakpoint_location("node_5");
658 assert!(matches!(result, Ok(BreakpointLocation::GraphNode(_))));
659
660 let result = interface.parse_breakpoint_location("main:10");
661 assert!(matches!(result, Ok(BreakpointLocation::Instruction { .. })));
662
663 let result = interface.parse_breakpoint_location("invalid");
664 assert!(result.is_err());
665 }
666
667 #[test]
668 fn test_inspection_target_parsing() {
669 let interface = create_test_interface();
670
671 let result = interface.parse_inspection_target("node_3");
672 assert!(matches!(result, Ok(InspectionTarget::Node(_))));
673
674 let result = interface.parse_inspection_target("0x1000");
675 assert!(matches!(result, Ok(InspectionTarget::Memory(4096))));
676
677 let result = interface.parse_inspection_target("variable_name");
678 assert!(matches!(result, Ok(InspectionTarget::Variable(_))));
679 }
680
681 #[test]
682 fn test_memory_address_parsing() {
683 let interface = create_test_interface();
684
685 assert_eq!(interface.parse_memory_address("0x1000").unwrap(), 4096);
686 assert_eq!(interface.parse_memory_address("1000").unwrap(), 1000);
687 assert!(interface.parse_memory_address("invalid").is_err());
688 }
689
690 #[test]
691 fn test_command_with_arguments() {
692 let interface = create_test_interface();
693
694 let result = interface.parse_command("break node_5");
695 assert!(matches!(result, Ok(DebugCommand::SetBreakpoint { .. })));
696
697 let result = interface.parse_command("watch variable_name");
698 assert!(matches!(result, Ok(DebugCommand::Watch { .. })));
699
700 let result = interface.parse_command("inspect node_3");
701 assert!(matches!(result, Ok(DebugCommand::Inspect { .. })));
702 }
703
704 #[test]
705 fn test_alias_management() {
706 let mut interface = create_test_interface();
707
708 interface.set_alias("test".to_string(), "step".to_string());
709 assert!(matches!(
710 interface.parse_command("test"),
711 Ok(DebugCommand::Step)
712 ));
713
714 interface.remove_alias("test");
715 assert!(interface.parse_command("test").is_err());
716 }
717
718 #[test]
719 fn test_invalid_commands() {
720 let interface = create_test_interface();
721
722 assert!(interface.parse_command("").is_err());
723 assert!(interface.parse_command("invalid_command").is_err());
724 assert!(interface.parse_command("break").is_err()); assert!(interface.parse_command("watch").is_err()); }
727}