Skip to main content

shape_vm/
debugger.rs

1//! VM Debugger for Shape Virtual Machine
2//!
3//! Provides comprehensive debugging and tracing capabilities including:
4//! - Instruction-level tracing
5//! - Breakpoint support
6//! - Stack inspection
7//! - Call stack visualization
8//! - Step-by-step execution
9//! - Variable name resolution
10
11use std::io::{self, Write};
12
13use super::{
14    bytecode::{BytecodeProgram, Instruction, Operand},
15    executor::DebugVMState,
16};
17/// Debugger commands
18#[derive(Debug, Clone)]
19pub enum DebugCommand {
20    /// Continue execution
21    Continue,
22    /// Step to next instruction
23    Step,
24    /// Step into function calls
25    StepInto,
26    /// Step over function calls
27    StepOver,
28    /// Step out of current function
29    StepOut,
30    /// Print current stack
31    Stack,
32    /// Print local variables
33    Locals,
34    /// Print module binding variables
35    ModuleBindings,
36    /// Print call stack
37    CallStack,
38    /// Set breakpoint at instruction
39    Breakpoint(usize),
40    /// Remove breakpoint
41    ClearBreakpoint(usize),
42    /// List all breakpoints
43    ListBreakpoints,
44    /// Print current instruction
45    CurrentInstruction,
46    /// Print next N instructions
47    Disassemble(usize),
48    /// Print variable value
49    Print(String),
50    /// Show help
51    Help,
52    /// Quit debugger
53    Quit,
54}
55
56/// Debugger state
57#[derive(Debug)]
58pub struct DebuggerState {
59    /// Breakpoints (instruction indices)
60    breakpoints: Vec<usize>,
61    /// Whether to trace all instructions
62    trace_mode: bool,
63    /// Step mode (step into, over, out)
64    step_mode: StepMode,
65    /// Call depth when stepping out
66    step_out_depth: usize,
67    /// Whether debugger is active
68    active: bool,
69}
70
71#[derive(Debug, Clone, Copy)]
72pub enum StepMode {
73    None,
74    Into,
75    Over,
76    Out,
77}
78
79impl Default for DebuggerState {
80    fn default() -> Self {
81        Self {
82            breakpoints: Vec::new(),
83            trace_mode: false,
84            step_mode: StepMode::None,
85            step_out_depth: 0,
86            active: false,
87        }
88    }
89}
90
91/// VM Debugger
92pub struct VMDebugger {
93    state: DebuggerState,
94}
95
96impl VMDebugger {
97    /// Create a new debugger
98    pub fn new() -> Self {
99        Self {
100            state: DebuggerState::default(),
101        }
102    }
103
104    /// Start debugging session
105    pub fn start(&mut self) {
106        self.state.active = true;
107        println!("šŸ› Shape VM Debugger started");
108        println!("Type 'help' for available commands");
109    }
110
111    /// Check if debugger should break at current instruction
112    pub fn should_break(&mut self, vm_state: &DebugVMState, ip: usize) -> bool {
113        if !self.state.active {
114            return false;
115        }
116
117        // Check breakpoints
118        if self.state.breakpoints.contains(&ip) {
119            println!("šŸ”“ Breakpoint hit at instruction {}", ip);
120            return true;
121        }
122
123        // Check step mode
124        match self.state.step_mode {
125            StepMode::Into => {
126                self.state.step_mode = StepMode::None;
127                return true;
128            }
129            StepMode::Over => {
130                // Step over means break at next instruction at same call depth
131                if vm_state.call_stack_depth <= self.state.step_out_depth {
132                    self.state.step_mode = StepMode::None;
133                    return true;
134                }
135            }
136            StepMode::Out => {
137                // Step out means break when call depth decreases
138                if vm_state.call_stack_depth < self.state.step_out_depth {
139                    self.state.step_mode = StepMode::None;
140                    return true;
141                }
142            }
143            StepMode::None => {}
144        }
145
146        false
147    }
148
149    /// Handle debug break
150    pub fn debug_break(&mut self, vm_state: &DebugVMState, program: &BytecodeProgram) {
151        println!("\nšŸ“ Debug break at instruction {}", vm_state.ip);
152        self.print_current_state(vm_state, program);
153
154        loop {
155            print!("(shape-debug) ");
156            io::stdout().flush().unwrap();
157
158            let mut input = String::new();
159            if io::stdin().read_line(&mut input).is_err() {
160                break;
161            }
162
163            let command = self.parse_command(input.trim());
164            if self.execute_command(command, vm_state, program) {
165                break; // Continue execution
166            }
167        }
168    }
169
170    /// Enable/disable trace mode
171    pub fn set_trace_mode(&mut self, enabled: bool) {
172        self.state.trace_mode = enabled;
173        if enabled {
174            println!("šŸ“Š Instruction tracing enabled");
175        } else {
176            println!("šŸ“Š Instruction tracing disabled");
177        }
178    }
179
180    /// Trace instruction execution
181    pub fn trace_instruction(
182        &self,
183        vm_state: &DebugVMState,
184        program: &BytecodeProgram,
185        instruction: &Instruction,
186    ) {
187        if !self.state.trace_mode {
188            return;
189        }
190
191        let ip = vm_state.ip;
192        let line_info = self.get_line_info(program, ip);
193
194        print!("[{}] {:04X}: ", line_info, ip);
195        self.print_instruction(instruction, program);
196        println!();
197    }
198
199    /// Parse debugger command
200    fn parse_command(&self, input: &str) -> DebugCommand {
201        let parts: Vec<&str> = input.split_whitespace().collect();
202        if parts.is_empty() {
203            return DebugCommand::Help;
204        }
205
206        match parts[0] {
207            "c" | "continue" => DebugCommand::Continue,
208            "s" | "step" => DebugCommand::Step,
209            "si" | "stepi" | "into" => DebugCommand::StepInto,
210            "so" | "over" => DebugCommand::StepOver,
211            "out" => DebugCommand::StepOut,
212            "stack" => DebugCommand::Stack,
213            "locals" => DebugCommand::Locals,
214            "module_bindings" | "bindings" => DebugCommand::ModuleBindings,
215            "callstack" | "bt" => DebugCommand::CallStack,
216            "b" | "break" => {
217                if parts.len() > 1 {
218                    if let Ok(addr) = parts[1].parse::<usize>() {
219                        DebugCommand::Breakpoint(addr)
220                    } else {
221                        DebugCommand::Help
222                    }
223                } else {
224                    DebugCommand::ListBreakpoints
225                }
226            }
227            "clear" => {
228                if parts.len() > 1 {
229                    if let Ok(addr) = parts[1].parse::<usize>() {
230                        DebugCommand::ClearBreakpoint(addr)
231                    } else {
232                        DebugCommand::Help
233                    }
234                } else {
235                    DebugCommand::Help
236                }
237            }
238            "list" => DebugCommand::ListBreakpoints,
239            "inst" | "instruction" => DebugCommand::CurrentInstruction,
240            "dis" | "disasm" => {
241                let count = if parts.len() > 1 {
242                    parts[1].parse().unwrap_or(10)
243                } else {
244                    10
245                };
246                DebugCommand::Disassemble(count)
247            }
248            "p" | "print" => {
249                if parts.len() > 1 {
250                    DebugCommand::Print(parts[1..].join(" "))
251                } else {
252                    DebugCommand::Help
253                }
254            }
255            "h" | "help" => DebugCommand::Help,
256            "q" | "quit" => DebugCommand::Quit,
257            _ => DebugCommand::Help,
258        }
259    }
260
261    /// Execute debugger command
262    fn execute_command(
263        &mut self,
264        command: DebugCommand,
265        vm_state: &DebugVMState,
266        program: &BytecodeProgram,
267    ) -> bool {
268        match command {
269            DebugCommand::Continue => {
270                self.state.step_mode = StepMode::None;
271                return true;
272            }
273            DebugCommand::Step | DebugCommand::StepInto => {
274                self.state.step_mode = StepMode::Into;
275                return true;
276            }
277            DebugCommand::StepOver => {
278                self.state.step_mode = StepMode::Over;
279                self.state.step_out_depth = vm_state.call_stack_depth;
280                return true;
281            }
282            DebugCommand::StepOut => {
283                self.state.step_mode = StepMode::Out;
284                self.state.step_out_depth = vm_state.call_stack_depth;
285                return true;
286            }
287            DebugCommand::Stack => self.print_stack_placeholder(),
288            DebugCommand::Locals => self.print_locals_placeholder(),
289            DebugCommand::ModuleBindings => self.print_module_bindings_placeholder(),
290            DebugCommand::CallStack => self.print_call_stack(vm_state),
291            DebugCommand::Breakpoint(addr) => {
292                if !self.state.breakpoints.contains(&addr) {
293                    self.state.breakpoints.push(addr);
294                    println!("šŸ”“ Breakpoint set at instruction {}", addr);
295                } else {
296                    println!("Breakpoint already exists at instruction {}", addr);
297                }
298            }
299            DebugCommand::ClearBreakpoint(addr) => {
300                if let Some(pos) = self.state.breakpoints.iter().position(|&x| x == addr) {
301                    self.state.breakpoints.remove(pos);
302                    println!("🟢 Breakpoint removed from instruction {}", addr);
303                } else {
304                    println!("No breakpoint at instruction {}", addr);
305                }
306            }
307            DebugCommand::ListBreakpoints => self.list_breakpoints(),
308            DebugCommand::CurrentInstruction => self.print_current_instruction(vm_state, program),
309            DebugCommand::Disassemble(count) => self.disassemble(vm_state, program, count),
310            DebugCommand::Print(var) => self.print_variable_placeholder(&var),
311            DebugCommand::Help => self.print_help(),
312            DebugCommand::Quit => {
313                self.state.active = false;
314                println!("šŸ‘‹ Debugger stopped");
315                return true;
316            }
317        }
318        false
319    }
320
321    /// Print current VM state
322    fn print_current_state(&self, vm_state: &DebugVMState, program: &BytecodeProgram) {
323        let ip = vm_state.ip;
324        let line_info = self.get_line_info(program, ip);
325
326        println!("šŸ“ Position: {} (instruction {})", line_info, ip);
327
328        if let Some(instruction) = program.instructions.get(ip) {
329            print!("šŸ“œ Current: ");
330            self.print_instruction(instruction, program);
331            println!();
332        }
333
334        // Show call depth
335        println!("šŸ“ž Call depth: {}", vm_state.call_stack_depth);
336    }
337
338    /// Print stack contents (placeholder - requires full VM access)
339    fn print_stack_placeholder(&self) {
340        println!("šŸ“š Stack contents:");
341        println!(
342            "  (Stack inspection requires full VM access - not available in current debug mode)"
343        );
344    }
345
346    /// Print local variables (placeholder - requires full VM access)
347    fn print_locals_placeholder(&self) {
348        println!("šŸ  Local variables:");
349        println!(
350            "  (Variable inspection requires full VM access - not available in current debug mode)"
351        );
352    }
353
354    /// Print module binding variables (placeholder - requires full VM access)
355    fn print_module_bindings_placeholder(&self) {
356        println!("šŸŒ Module binding variables:");
357        println!(
358            "  (Variable inspection requires full VM access - not available in current debug mode)"
359        );
360    }
361
362    /// Print call stack
363    fn print_call_stack(&self, vm_state: &DebugVMState) {
364        println!("šŸ“ž Call stack:");
365        println!("  Current depth: {}", vm_state.call_stack_depth);
366        println!(
367            "  (Detailed call stack requires full VM access - not available in current debug mode)"
368        );
369    }
370
371    /// List all breakpoints
372    fn list_breakpoints(&self) {
373        println!("šŸ”“ Breakpoints:");
374        if self.state.breakpoints.is_empty() {
375            println!("  (none)");
376        } else {
377            for &bp in &self.state.breakpoints {
378                println!("  {}", bp);
379            }
380        }
381    }
382
383    /// Print current instruction
384    fn print_current_instruction(&self, vm_state: &DebugVMState, program: &BytecodeProgram) {
385        let ip = vm_state.ip;
386        if let Some(instruction) = program.instructions.get(ip) {
387            let line_info = self.get_line_info(program, ip);
388            print!("šŸ“œ {} [{:04X}]: ", line_info, ip);
389            self.print_instruction(instruction, program);
390            println!();
391        } else {
392            println!("No instruction at current position");
393        }
394    }
395
396    /// Disassemble instructions
397    fn disassemble(&self, vm_state: &DebugVMState, program: &BytecodeProgram, count: usize) {
398        let start_ip = vm_state.ip;
399        println!("šŸ“œ Disassembly from instruction {}:", start_ip);
400
401        for i in 0..count {
402            let ip = start_ip + i;
403            if let Some(instruction) = program.instructions.get(ip) {
404                let line_info = self.get_line_info(program, ip);
405                let marker = if ip == start_ip { ">" } else { " " };
406                print!("{} {} [{:04X}]: ", marker, line_info, ip);
407                self.print_instruction(instruction, program);
408                println!();
409            } else {
410                break;
411            }
412        }
413    }
414
415    /// Print variable value (placeholder - requires full VM access)
416    fn print_variable_placeholder(&self, var_name: &str) {
417        println!(
418            "āŒ Variable inspection for '{}' requires full VM access - not available in current debug mode",
419            var_name
420        );
421    }
422
423    /// Print help information
424    fn print_help(&self) {
425        println!("šŸ› Shape VM Debugger Commands:");
426        println!("  c, continue       - Continue execution");
427        println!("  s, step           - Step to next instruction");
428        println!("  si, into, stepi   - Step into function calls");
429        println!("  so, over          - Step over function calls");
430        println!("  out               - Step out of current function");
431        println!("  stack             - Print stack contents");
432        println!("  locals            - Print local variables");
433        println!("  module_bindings           - Print module binding variables");
434        println!("  callstack, bt     - Print call stack");
435        println!("  b, break <addr>   - Set breakpoint at instruction");
436        println!("  clear <addr>      - Remove breakpoint");
437        println!("  list              - List all breakpoints");
438        println!("  inst, instruction - Print current instruction");
439        println!("  dis, disasm [N]   - Disassemble N instructions (default 10)");
440        println!("  p, print <var>    - Print variable value");
441        println!("  h, help           - Show this help");
442        println!("  q, quit           - Stop debugger and continue");
443    }
444
445    // Helper methods
446
447    fn print_instruction(&self, instruction: &Instruction, program: &BytecodeProgram) {
448        print!("{:?}", instruction.opcode);
449
450        if let Some(ref operand) = instruction.operand {
451            match operand {
452                Operand::Const(idx) => {
453                    if let Some(constant) = program.constants.get(*idx as usize) {
454                        print!(" {:?}", constant);
455                    } else {
456                        print!(" const[{}]", idx);
457                    }
458                }
459                Operand::Local(idx) => {
460                    let name = self.get_variable_name(program, *idx, false);
461                    print!(" {}", name);
462                }
463                Operand::ModuleBinding(idx) => {
464                    let name = self.get_variable_name(program, *idx, true);
465                    print!(" {}", name);
466                }
467                Operand::Function(idx) => {
468                    if let Some(func) = program.functions.get(idx.index()) {
469                        print!(" {}()", func.name);
470                    } else {
471                        print!(" func[{}]", idx);
472                    }
473                }
474                Operand::Property(idx) => {
475                    if let Some(prop) = program.strings.get(*idx as usize) {
476                        print!(" .{}", prop);
477                    } else {
478                        print!(" prop[{}]", idx);
479                    }
480                }
481                _ => print!(" {:?}", operand),
482            }
483        }
484    }
485
486    fn get_line_info(&self, program: &BytecodeProgram, ip: usize) -> String {
487        let debug_info = &program.debug_info;
488        // Find the file and line number for this instruction
489        if let Some((file_id, line_num)) = debug_info.get_location_for_instruction(ip) {
490            let file_name = debug_info
491                .source_map
492                .get_file(file_id)
493                .unwrap_or("<unknown>");
494            if file_name.is_empty() || file_name == "<main>" {
495                format!("line {}", line_num)
496            } else {
497                format!("{}:{}", file_name, line_num)
498            }
499        } else {
500            "unknown".to_string()
501        }
502    }
503
504    fn get_variable_name(&self, program: &BytecodeProgram, index: u16, is_global: bool) -> String {
505        let debug_info = &program.debug_info;
506        for (var_index, name) in &debug_info.variable_names {
507            if *var_index == index {
508                return name.clone();
509            }
510        }
511
512        if is_global {
513            format!("module_binding[{}]", index)
514        } else {
515            format!("local[{}]", index)
516        }
517    }
518}
519
520impl Default for VMDebugger {
521    fn default() -> Self {
522        Self::new()
523    }
524}