1use crate::scope::{VmScope, VmScopeSymbol};
14use intuicio_core::{
15 context::Context,
16 registry::Registry,
17 script::{ScriptExpression, ScriptOperation},
18};
19use intuicio_data::{data_stack::DataStackVisitedItem, type_hash::TypeHash};
20use serde::{Deserialize, Serialize};
21use std::{
22 collections::HashMap,
23 io::Write,
24 sync::{Arc, RwLock},
25};
26
27pub type VmDebuggerHandle<SE> = Arc<RwLock<dyn VmDebugger<SE> + Send + Sync>>;
32
33pub type SourceMapHandle<UL> = Arc<RwLock<SourceMap<UL>>>;
35
36pub trait VmDebugger<SE: ScriptExpression> {
42 #[allow(unused_variables)]
44 fn on_enter_scope(&mut self, scope: &VmScope<SE>, context: &mut Context, registry: &Registry) {}
45
46 #[allow(unused_variables)]
49 fn on_exit_scope(&mut self, scope: &VmScope<SE>, context: &mut Context, registry: &Registry) {}
50
51 #[allow(unused_variables)]
53 fn on_enter_operation(
54 &mut self,
55 scope: &VmScope<SE>,
56 operation: &ScriptOperation<SE>,
57 position: usize,
58 context: &mut Context,
59 registry: &Registry,
60 ) {
61 }
62
63 #[allow(unused_variables)]
65 fn on_exit_operation(
66 &mut self,
67 scope: &VmScope<SE>,
68 operation: &ScriptOperation<SE>,
69 position: usize,
70 context: &mut Context,
71 registry: &Registry,
72 ) {
73 }
74
75 fn into_handle(self) -> VmDebuggerHandle<SE>
77 where
78 Self: Sized + Send + Sync + 'static,
79 {
80 Arc::new(RwLock::new(self))
81 }
82}
83
84#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
86pub struct SourceMapLocation {
87 pub symbol: VmScopeSymbol,
89 pub operation: Option<usize>,
91}
92
93impl SourceMapLocation {
94 pub fn symbol(symbol: VmScopeSymbol) -> Self {
96 Self {
97 symbol,
98 operation: None,
99 }
100 }
101
102 pub fn symbol_operation(symbol: VmScopeSymbol, operation: usize) -> Self {
104 Self {
105 symbol,
106 operation: Some(operation),
107 }
108 }
109}
110
111#[derive(Debug, Default, Clone, Serialize, Deserialize)]
117pub struct SourceMap<UL> {
118 pub mappings: HashMap<SourceMapLocation, UL>,
120}
121
122impl<UL> SourceMap<UL> {
123 pub fn map(&self, location: SourceMapLocation) -> Option<&UL> {
125 self.mappings.get(&location)
126 }
127}
128
129#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
131pub enum PrintDebuggerMode {
132 Enter,
134 Exit,
136 #[default]
138 All,
139}
140
141impl PrintDebuggerMode {
142 pub fn can_enter(self) -> bool {
144 self == Self::All || self == Self::Enter
145 }
146
147 pub fn can_exit(self) -> bool {
149 self == Self::All || self == Self::Exit
150 }
151}
152
153#[derive(Default)]
163pub struct PrintDebugger {
164 pub source_map: SourceMap<String>,
166 pub stack: bool,
168 pub stack_bytes: bool,
170 pub visit_stack: bool,
172 pub registers: bool,
174 pub registers_bytes: bool,
176 pub visit_registers: bool,
178 pub operation_details: bool,
180 pub step_through: bool,
182 pub mode: PrintDebuggerMode,
184 #[allow(clippy::type_complexity)]
185 printable: HashMap<
186 TypeHash,
187 (
188 &'static str,
189 Box<dyn Fn(&Self, *const ()) -> String + Send + Sync>,
190 ),
191 >,
192 step: usize,
193}
194
195impl PrintDebugger {
196 pub fn full() -> Self {
198 Self {
199 source_map: Default::default(),
200 stack: true,
201 stack_bytes: true,
202 visit_stack: true,
203 registers: true,
204 registers_bytes: true,
205 visit_registers: true,
206 operation_details: true,
207 step_through: true,
208 mode: PrintDebuggerMode::All,
209 printable: Default::default(),
210 step: 0,
211 }
212 }
213
214 pub fn stack(mut self, mode: bool) -> Self {
216 self.stack = mode;
217 self
218 }
219
220 pub fn stack_bytes(mut self, mode: bool) -> Self {
222 self.stack_bytes = mode;
223 self
224 }
225
226 pub fn visit_stack(mut self, mode: bool) -> Self {
228 self.visit_stack = mode;
229 self
230 }
231
232 pub fn registers(mut self, mode: bool) -> Self {
234 self.registers = mode;
235 self
236 }
237
238 pub fn registers_bytes(mut self, mode: bool) -> Self {
240 self.registers_bytes = mode;
241 self
242 }
243
244 pub fn visit_registers(mut self, mode: bool) -> Self {
246 self.visit_registers = mode;
247 self
248 }
249
250 pub fn operation_details(mut self, mode: bool) -> Self {
252 self.operation_details = mode;
253 self
254 }
255
256 pub fn step_through(mut self, mode: bool) -> Self {
261 self.step_through = mode;
262 self
263 }
264
265 pub fn mode(mut self, mode: PrintDebuggerMode) -> Self {
267 self.mode = mode;
268 self
269 }
270
271 pub fn printable<T: std::fmt::Debug + 'static>(mut self) -> Self {
273 self.printable.insert(
274 TypeHash::of::<T>(),
275 (
276 std::any::type_name::<T>(),
277 Box::new(|_, pointer| unsafe {
278 format!("{:#?}", pointer.cast::<T>().as_ref().unwrap())
279 }),
280 ),
281 );
282 self
283 }
284
285 pub fn printable_custom<T: 'static>(
287 mut self,
288 f: impl Fn(&Self, &T) -> String + Send + Sync + 'static,
289 ) -> Self {
290 self.printable.insert(
291 TypeHash::of::<T>(),
292 (
293 std::any::type_name::<T>(),
294 Box::new(move |debugger, pointer| unsafe {
295 f(debugger, pointer.cast::<T>().as_ref().unwrap())
296 }),
297 ),
298 );
299 self
300 }
301
302 pub fn printable_raw<T: 'static>(
307 mut self,
308 f: impl Fn(&Self, *const ()) -> String + Send + Sync + 'static,
309 ) -> Self {
310 self.printable.insert(
311 TypeHash::of::<T>(),
312 (std::any::type_name::<T>(), Box::new(f)),
313 );
314 self
315 }
316
317 pub fn basic_printables(self) -> Self {
319 self.printable::<()>()
320 .printable::<bool>()
321 .printable::<i8>()
322 .printable::<i16>()
323 .printable::<i32>()
324 .printable::<i64>()
325 .printable::<i128>()
326 .printable::<isize>()
327 .printable::<u8>()
328 .printable::<u16>()
329 .printable::<u32>()
330 .printable::<u64>()
331 .printable::<u128>()
332 .printable::<usize>()
333 .printable::<f32>()
334 .printable::<f64>()
335 .printable::<char>()
336 .printable::<String>()
337 }
338
339 fn map(&self, location: SourceMapLocation) -> String {
340 self.source_map
341 .map(location)
342 .map(|mapping| mapping.to_owned())
343 .unwrap_or_else(|| format!("{location:?}"))
344 }
345
346 pub fn display<T>(&self, data: &T) -> Option<(&'static str, String)> {
351 let pointer = data as *const T as *const ();
352 self.display_raw(TypeHash::of::<T>(), pointer)
353 }
354
355 pub fn display_raw(
361 &self,
362 type_hash: TypeHash,
363 pointer: *const (),
364 ) -> Option<(&'static str, String)> {
365 let (type_name, callback) = self.printable.get(&type_hash)?;
366 let result = callback(self, pointer);
367 Some((type_name, result))
368 }
369
370 fn print_extra(&self, context: &mut Context) {
371 if self.stack {
372 println!("- stack position: {}", context.stack().position());
373 }
374 if self.stack_bytes {
375 println!("- stack bytes:\n{:?}", context.stack().as_bytes());
376 }
377 if self.visit_stack {
378 let mut index = 0;
379 context.stack().visit(|item| {
380 let DataStackVisitedItem::Value {
381 type_hash,
382 layout,
383 data: bytes,
384 range,
385 } = item else {
386 return true;
387 };
388 assert_eq!(bytes.len(), layout.size());
389 if let Some((type_name, callback)) = self.printable.get(&type_hash) {
390 println!(
391 "- stack value #{} of type {}:\n{}",
392 index,
393 type_name,
394 callback(self, bytes.as_ptr().cast::<()>())
395 );
396 } else {
397 println!(
398 "- stack value #{index} of unknown type id {type_hash:?} and layout: {layout:?}"
399 );
400 }
401 println!(
402 "- stack value #{index} bytes in range {range:?}:\n{bytes:?}"
403 );
404 index += 1;
405 true
406 });
407 }
408 if self.registers {
409 println!("- registers position: {}", context.registers().position());
410 println!(
411 "- registers count: {}",
412 context.registers().registers_count()
413 );
414 println!("- registers barriers: {:?}", context.registers_barriers());
415 }
416 if self.registers_bytes {
417 println!("- registers bytes:\n{:?}", context.registers().as_bytes());
418 }
419 if self.visit_registers {
420 let mut index = 0;
421 let registers_count = context.registers().registers_count();
422 context.registers().visit(|item| {
423 let DataStackVisitedItem::Register {
424 type_hash,
425 layout,
426 data: bytes,
427 range,
428 valid,
429 } = item
430 else {
431 return true;
432 };
433 if let Some((type_name, callback)) = self.printable.get(&type_hash) {
434 if valid {
435 println!(
436 "- register value #{} of type {}:\n{}",
437 registers_count - index - 1,
438 type_name,
439 callback(self, bytes.as_ptr().cast::<()>())
440 );
441 } else {
442 println!(
443 "- invalid register value #{} of type {}",
444 registers_count - index - 1,
445 type_name
446 );
447 }
448 } else {
449 println!(
450 "- register value #{} of unknown type id {:?} and layout: {:?}",
451 registers_count - index - 1,
452 type_hash,
453 layout
454 );
455 }
456 println!(
457 "- register value #{} bytes in range: {:?}:\n{:?}",
458 registers_count - index - 1,
459 range,
460 bytes
461 );
462 index += 1;
463 true
464 });
465 }
466 }
467
468 fn try_halt(&self) {
469 if self.step_through {
470 print!("#{} | Confirm to step through...", self.step);
471 let _ = std::io::stdout().flush();
472 let mut command = String::new();
473 let _ = std::io::stdin().read_line(&mut command);
474 }
475 }
476}
477
478impl<SE: ScriptExpression + std::fmt::Debug> VmDebugger<SE> for PrintDebugger {
479 fn on_enter_scope(&mut self, scope: &VmScope<SE>, context: &mut Context, _: &Registry) {
480 println!();
481 println!(
482 "* #{} PrintDebugger | Enter scope:\n{}",
483 self.step,
484 self.map(SourceMapLocation::symbol(scope.symbol()))
485 );
486 if self.mode.can_enter() {
487 self.print_extra(context);
488 self.try_halt();
489 }
490 println!();
491 self.step += 1;
492 }
493
494 fn on_exit_scope(&mut self, scope: &VmScope<SE>, context: &mut Context, _: &Registry) {
495 println!();
496 println!(
497 "* #{} PrintDebugger | Exit scope:\n{}",
498 self.step,
499 self.map(SourceMapLocation::symbol(scope.symbol()))
500 );
501 if self.mode.can_exit() {
502 self.print_extra(context);
503 self.try_halt();
504 }
505 println!();
506 self.step += 1;
507 }
508
509 fn on_enter_operation(
510 &mut self,
511 scope: &VmScope<SE>,
512 operation: &ScriptOperation<SE>,
513 position: usize,
514 context: &mut Context,
515 _: &Registry,
516 ) {
517 println!();
518 println!(
519 "* #{} PrintDebugger | Enter operation:\n{}",
520 self.step,
521 self.map(SourceMapLocation::symbol_operation(
522 scope.symbol(),
523 position
524 ))
525 );
526 if self.mode.can_enter() {
527 println!(
528 "- operation: {}",
529 if self.operation_details {
530 format!("{operation:#?}")
531 } else {
532 operation.label().to_owned()
533 }
534 );
535 self.print_extra(context);
536 self.try_halt();
537 }
538 println!();
539 self.step += 1;
540 }
541
542 fn on_exit_operation(
543 &mut self,
544 scope: &VmScope<SE>,
545 operation: &ScriptOperation<SE>,
546 position: usize,
547 context: &mut Context,
548 _: &Registry,
549 ) {
550 println!();
551 println!(
552 "* #{} PrintDebugger | Exit operation:\n{}",
553 self.step,
554 self.map(SourceMapLocation::symbol_operation(
555 scope.symbol(),
556 position
557 ))
558 );
559 if self.mode.can_exit() {
560 println!(
561 "- operation: {}",
562 if self.operation_details {
563 format!("{operation:#?}")
564 } else {
565 operation.label().to_owned()
566 }
567 );
568 self.print_extra(context);
569 self.try_halt();
570 }
571 println!();
572 self.step += 1;
573 }
574}