1use crate::scope::{VmScope, VmScopeSymbol};
2use intuicio_core::{
3 context::Context,
4 registry::Registry,
5 script::{ScriptExpression, ScriptOperation},
6};
7use intuicio_data::{data_stack::DataStackVisitedItem, type_hash::TypeHash};
8use serde::{Deserialize, Serialize};
9use std::{
10 collections::HashMap,
11 io::Write,
12 sync::{Arc, RwLock},
13};
14
15pub type VmDebuggerHandle<SE> = Arc<RwLock<dyn VmDebugger<SE> + Send + Sync>>;
16pub type SourceMapHandle<UL> = Arc<RwLock<SourceMap<UL>>>;
17
18pub trait VmDebugger<SE: ScriptExpression> {
19 #[allow(unused_variables)]
20 fn on_enter_scope(&mut self, scope: &VmScope<SE>, context: &mut Context, registry: &Registry) {}
21
22 #[allow(unused_variables)]
23 fn on_exit_scope(&mut self, scope: &VmScope<SE>, context: &mut Context, registry: &Registry) {}
24
25 #[allow(unused_variables)]
26 fn on_enter_operation(
27 &mut self,
28 scope: &VmScope<SE>,
29 operation: &ScriptOperation<SE>,
30 position: usize,
31 context: &mut Context,
32 registry: &Registry,
33 ) {
34 }
35
36 #[allow(unused_variables)]
37 fn on_exit_operation(
38 &mut self,
39 scope: &VmScope<SE>,
40 operation: &ScriptOperation<SE>,
41 position: usize,
42 context: &mut Context,
43 registry: &Registry,
44 ) {
45 }
46
47 fn into_handle(self) -> VmDebuggerHandle<SE>
48 where
49 Self: Sized + Send + Sync + 'static,
50 {
51 Arc::new(RwLock::new(self))
52 }
53}
54
55#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
56pub struct SourceMapLocation {
57 pub symbol: VmScopeSymbol,
58 pub operation: Option<usize>,
59}
60
61impl SourceMapLocation {
62 pub fn symbol(symbol: VmScopeSymbol) -> Self {
63 Self {
64 symbol,
65 operation: None,
66 }
67 }
68
69 pub fn symbol_operation(symbol: VmScopeSymbol, operation: usize) -> Self {
70 Self {
71 symbol,
72 operation: Some(operation),
73 }
74 }
75}
76
77#[derive(Debug, Default, Clone, Serialize, Deserialize)]
78pub struct SourceMap<UL> {
79 pub mappings: HashMap<SourceMapLocation, UL>,
80}
81
82impl<UL> SourceMap<UL> {
83 pub fn map(&self, location: SourceMapLocation) -> Option<&UL> {
84 self.mappings.get(&location)
85 }
86}
87
88#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
89pub enum PrintDebuggerMode {
90 Enter,
91 Exit,
92 #[default]
93 All,
94}
95
96impl PrintDebuggerMode {
97 pub fn can_enter(self) -> bool {
98 self == Self::All || self == Self::Enter
99 }
100
101 pub fn can_exit(self) -> bool {
102 self == Self::All || self == Self::Exit
103 }
104}
105
106#[derive(Default)]
107pub struct PrintDebugger {
108 pub source_map: SourceMap<String>,
109 pub stack: bool,
110 pub stack_bytes: bool,
111 pub visit_stack: bool,
112 pub registers: bool,
113 pub registers_bytes: bool,
114 pub visit_registers: bool,
115 pub operation_details: bool,
116 pub step_through: bool,
117 pub mode: PrintDebuggerMode,
118 #[allow(clippy::type_complexity)]
119 printable: HashMap<
120 TypeHash,
121 (
122 &'static str,
123 Box<dyn Fn(&Self, *const ()) -> String + Send + Sync>,
124 ),
125 >,
126 step: usize,
127}
128
129impl PrintDebugger {
130 pub fn full() -> Self {
131 Self {
132 source_map: Default::default(),
133 stack: true,
134 stack_bytes: true,
135 visit_stack: true,
136 registers: true,
137 registers_bytes: true,
138 visit_registers: true,
139 operation_details: true,
140 step_through: true,
141 mode: PrintDebuggerMode::All,
142 printable: Default::default(),
143 step: 0,
144 }
145 }
146
147 pub fn stack(mut self, mode: bool) -> Self {
148 self.stack = mode;
149 self
150 }
151
152 pub fn stack_bytes(mut self, mode: bool) -> Self {
153 self.stack_bytes = mode;
154 self
155 }
156
157 pub fn visit_stack(mut self, mode: bool) -> Self {
158 self.visit_stack = mode;
159 self
160 }
161
162 pub fn registers(mut self, mode: bool) -> Self {
163 self.registers = mode;
164 self
165 }
166
167 pub fn registers_bytes(mut self, mode: bool) -> Self {
168 self.registers_bytes = mode;
169 self
170 }
171
172 pub fn visit_registers(mut self, mode: bool) -> Self {
173 self.visit_registers = mode;
174 self
175 }
176
177 pub fn operation_details(mut self, mode: bool) -> Self {
178 self.operation_details = mode;
179 self
180 }
181
182 pub fn step_through(mut self, mode: bool) -> Self {
183 self.step_through = mode;
184 self
185 }
186
187 pub fn mode(mut self, mode: PrintDebuggerMode) -> Self {
188 self.mode = mode;
189 self
190 }
191
192 pub fn printable<T: std::fmt::Debug + 'static>(mut self) -> Self {
193 self.printable.insert(
194 TypeHash::of::<T>(),
195 (
196 std::any::type_name::<T>(),
197 Box::new(|_, pointer| unsafe {
198 format!("{:#?}", pointer.cast::<T>().as_ref().unwrap())
199 }),
200 ),
201 );
202 self
203 }
204
205 pub fn printable_custom<T: 'static>(
206 mut self,
207 f: impl Fn(&Self, &T) -> String + Send + Sync + 'static,
208 ) -> Self {
209 self.printable.insert(
210 TypeHash::of::<T>(),
211 (
212 std::any::type_name::<T>(),
213 Box::new(move |debugger, pointer| unsafe {
214 f(debugger, pointer.cast::<T>().as_ref().unwrap())
215 }),
216 ),
217 );
218 self
219 }
220
221 pub fn printable_raw<T: 'static>(
222 mut self,
223 f: impl Fn(&Self, *const ()) -> String + Send + Sync + 'static,
224 ) -> Self {
225 self.printable.insert(
226 TypeHash::of::<T>(),
227 (std::any::type_name::<T>(), Box::new(f)),
228 );
229 self
230 }
231
232 pub fn basic_printables(self) -> Self {
233 self.printable::<()>()
234 .printable::<bool>()
235 .printable::<i8>()
236 .printable::<i16>()
237 .printable::<i32>()
238 .printable::<i64>()
239 .printable::<i128>()
240 .printable::<isize>()
241 .printable::<u8>()
242 .printable::<u16>()
243 .printable::<u32>()
244 .printable::<u64>()
245 .printable::<u128>()
246 .printable::<usize>()
247 .printable::<f32>()
248 .printable::<f64>()
249 .printable::<char>()
250 .printable::<String>()
251 }
252
253 fn map(&self, location: SourceMapLocation) -> String {
254 self.source_map
255 .map(location)
256 .map(|mapping| mapping.to_owned())
257 .unwrap_or_else(|| format!("{location:?}"))
258 }
259
260 pub fn display<T>(&self, data: &T) -> Option<(&'static str, String)> {
261 let pointer = data as *const T as *const ();
262 self.display_raw(TypeHash::of::<T>(), pointer)
263 }
264
265 pub fn display_raw(
266 &self,
267 type_hash: TypeHash,
268 pointer: *const (),
269 ) -> Option<(&'static str, String)> {
270 let (type_name, callback) = self.printable.get(&type_hash)?;
271 let result = callback(self, pointer);
272 Some((type_name, result))
273 }
274
275 fn print_extra(&self, context: &mut Context) {
276 if self.stack {
277 println!("- stack position: {}", context.stack().position());
278 }
279 if self.stack_bytes {
280 println!("- stack bytes:\n{:?}", context.stack().as_bytes());
281 }
282 if self.visit_stack {
283 let mut index = 0;
284 context.stack().visit(|item| {
285 let DataStackVisitedItem::Value {
286 type_hash,
287 layout,
288 data: bytes,
289 range,
290 } = item else {
291 return true;
292 };
293 assert_eq!(bytes.len(), layout.size());
294 if let Some((type_name, callback)) = self.printable.get(&type_hash) {
295 println!(
296 "- stack value #{} of type {}:\n{}",
297 index,
298 type_name,
299 callback(self, bytes.as_ptr().cast::<()>())
300 );
301 } else {
302 println!(
303 "- stack value #{index} of unknown type id {type_hash:?} and layout: {layout:?}"
304 );
305 }
306 println!(
307 "- stack value #{index} bytes in range {range:?}:\n{bytes:?}"
308 );
309 index += 1;
310 true
311 });
312 }
313 if self.registers {
314 println!("- registers position: {}", context.registers().position());
315 println!(
316 "- registers count: {}",
317 context.registers().registers_count()
318 );
319 println!("- registers barriers: {:?}", context.registers_barriers());
320 }
321 if self.registers_bytes {
322 println!("- registers bytes:\n{:?}", context.registers().as_bytes());
323 }
324 if self.visit_registers {
325 let mut index = 0;
326 let registers_count = context.registers().registers_count();
327 context.registers().visit(|item| {
328 let DataStackVisitedItem::Register {
329 type_hash,
330 layout,
331 data: bytes,
332 range,
333 valid,
334 } = item
335 else {
336 return true;
337 };
338 if let Some((type_name, callback)) = self.printable.get(&type_hash) {
339 if valid {
340 println!(
341 "- register value #{} of type {}:\n{}",
342 registers_count - index - 1,
343 type_name,
344 callback(self, bytes.as_ptr().cast::<()>())
345 );
346 } else {
347 println!(
348 "- invalid register value #{} of type {}",
349 registers_count - index - 1,
350 type_name
351 );
352 }
353 } else {
354 println!(
355 "- register value #{} of unknown type id {:?} and layout: {:?}",
356 registers_count - index - 1,
357 type_hash,
358 layout
359 );
360 }
361 println!(
362 "- register value #{} bytes in range: {:?}:\n{:?}",
363 registers_count - index - 1,
364 range,
365 bytes
366 );
367 index += 1;
368 true
369 });
370 }
371 }
372
373 fn try_halt(&self) {
374 if self.step_through {
375 print!("#{} | Confirm to step through...", self.step);
376 let _ = std::io::stdout().flush();
377 let mut command = String::new();
378 let _ = std::io::stdin().read_line(&mut command);
379 }
380 }
381}
382
383impl<SE: ScriptExpression + std::fmt::Debug> VmDebugger<SE> for PrintDebugger {
384 fn on_enter_scope(&mut self, scope: &VmScope<SE>, context: &mut Context, _: &Registry) {
385 println!();
386 println!(
387 "* #{} PrintDebugger | Enter scope:\n{}",
388 self.step,
389 self.map(SourceMapLocation::symbol(scope.symbol()))
390 );
391 if self.mode.can_enter() {
392 self.print_extra(context);
393 self.try_halt();
394 }
395 println!();
396 self.step += 1;
397 }
398
399 fn on_exit_scope(&mut self, scope: &VmScope<SE>, context: &mut Context, _: &Registry) {
400 println!();
401 println!(
402 "* #{} PrintDebugger | Exit scope:\n{}",
403 self.step,
404 self.map(SourceMapLocation::symbol(scope.symbol()))
405 );
406 if self.mode.can_exit() {
407 self.print_extra(context);
408 self.try_halt();
409 }
410 println!();
411 self.step += 1;
412 }
413
414 fn on_enter_operation(
415 &mut self,
416 scope: &VmScope<SE>,
417 operation: &ScriptOperation<SE>,
418 position: usize,
419 context: &mut Context,
420 _: &Registry,
421 ) {
422 println!();
423 println!(
424 "* #{} PrintDebugger | Enter operation:\n{}",
425 self.step,
426 self.map(SourceMapLocation::symbol_operation(
427 scope.symbol(),
428 position
429 ))
430 );
431 if self.mode.can_enter() {
432 println!(
433 "- operation: {}",
434 if self.operation_details {
435 format!("{operation:#?}")
436 } else {
437 operation.label().to_owned()
438 }
439 );
440 self.print_extra(context);
441 self.try_halt();
442 }
443 println!();
444 self.step += 1;
445 }
446
447 fn on_exit_operation(
448 &mut self,
449 scope: &VmScope<SE>,
450 operation: &ScriptOperation<SE>,
451 position: usize,
452 context: &mut Context,
453 _: &Registry,
454 ) {
455 println!();
456 println!(
457 "* #{} PrintDebugger | Exit operation:\n{}",
458 self.step,
459 self.map(SourceMapLocation::symbol_operation(
460 scope.symbol(),
461 position
462 ))
463 );
464 if self.mode.can_exit() {
465 println!(
466 "- operation: {}",
467 if self.operation_details {
468 format!("{operation:#?}")
469 } else {
470 operation.label().to_owned()
471 }
472 );
473 self.print_extra(context);
474 self.try_halt();
475 }
476 println!();
477 self.step += 1;
478 }
479}