1use std::io::{self, Write};
12
13use super::{
14 bytecode::{BytecodeProgram, Instruction, Operand},
15 executor::DebugVMState,
16};
17#[derive(Debug, Clone)]
19pub enum DebugCommand {
20 Continue,
22 Step,
24 StepInto,
26 StepOver,
28 StepOut,
30 Stack,
32 Locals,
34 ModuleBindings,
36 CallStack,
38 Breakpoint(usize),
40 ClearBreakpoint(usize),
42 ListBreakpoints,
44 CurrentInstruction,
46 Disassemble(usize),
48 Print(String),
50 Help,
52 Quit,
54}
55
56#[derive(Debug)]
58pub struct DebuggerState {
59 breakpoints: Vec<usize>,
61 trace_mode: bool,
63 step_mode: StepMode,
65 step_out_depth: usize,
67 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
91pub struct VMDebugger {
93 state: DebuggerState,
94}
95
96impl VMDebugger {
97 pub fn new() -> Self {
99 Self {
100 state: DebuggerState::default(),
101 }
102 }
103
104 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 pub fn should_break(&mut self, vm_state: &DebugVMState, ip: usize) -> bool {
113 if !self.state.active {
114 return false;
115 }
116
117 if self.state.breakpoints.contains(&ip) {
119 println!("š“ Breakpoint hit at instruction {}", ip);
120 return true;
121 }
122
123 match self.state.step_mode {
125 StepMode::Into => {
126 self.state.step_mode = StepMode::None;
127 return true;
128 }
129 StepMode::Over => {
130 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 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 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; }
167 }
168 }
169
170 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 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 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 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 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 println!("š Call depth: {}", vm_state.call_stack_depth);
336 }
337
338 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 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 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 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 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 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 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 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 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 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 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}