1use crate::scope::{VmScope, VmScopeSymbol};
2use intuicio_core::{
3 context::Context,
4 registry::Registry,
5 script::{ScriptExpression, ScriptOperation},
6};
7use intuicio_data::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(|type_hash, layout, bytes, range, _| {
285 assert_eq!(bytes.len(), layout.size());
286 if let Some((type_name, callback)) = self.printable.get(&type_hash) {
287 println!(
288 "- stack value #{} of type {}:\n{}",
289 index,
290 type_name,
291 callback(self, bytes.as_ptr().cast::<()>())
292 );
293 } else {
294 println!(
295 "- stack value #{index} of unknown type id {type_hash:?} and layout: {layout:?}"
296 );
297 }
298 println!(
299 "- stack value #{index} bytes in range {range:?}:\n{bytes:?}"
300 );
301 index += 1;
302 });
303 }
304 if self.registers {
305 println!("- registers position: {}", context.registers().position());
306 println!(
307 "- registers count: {}",
308 context.registers().registers_count()
309 );
310 println!("- registers barriers: {:?}", context.registers_barriers());
311 }
312 if self.registers_bytes {
313 println!("- registers bytes:\n{:?}", context.registers().as_bytes());
314 }
315 if self.visit_registers {
316 let mut index = 0;
317 let registers_count = context.registers().registers_count();
318 context
319 .registers()
320 .visit(|type_hash, layout, bytes, range, valid| {
321 if let Some((type_name, callback)) = self.printable.get(&type_hash) {
322 if valid {
323 println!(
324 "- register value #{} of type {}:\n{}",
325 registers_count - index - 1,
326 type_name,
327 callback(self, bytes.as_ptr().cast::<()>())
328 );
329 } else {
330 println!(
331 "- invalid register value #{} of type {}",
332 registers_count - index - 1,
333 type_name
334 );
335 }
336 } else {
337 println!(
338 "- register value #{} of unknown type id {:?} and layout: {:?}",
339 registers_count - index - 1,
340 type_hash,
341 layout
342 );
343 }
344 println!(
345 "- register value #{} bytes in range: {:?}:\n{:?}",
346 registers_count - index - 1,
347 range,
348 bytes
349 );
350 index += 1;
351 });
352 }
353 }
354
355 fn try_halt(&self) {
356 if self.step_through {
357 print!("#{} | Confirm to step through...", self.step);
358 let _ = std::io::stdout().flush();
359 let mut command = String::new();
360 let _ = std::io::stdin().read_line(&mut command);
361 }
362 }
363}
364
365impl<SE: ScriptExpression + std::fmt::Debug> VmDebugger<SE> for PrintDebugger {
366 fn on_enter_scope(&mut self, scope: &VmScope<SE>, context: &mut Context, _: &Registry) {
367 println!();
368 println!(
369 "* #{} PrintDebugger | Enter scope:\n{}",
370 self.step,
371 self.map(SourceMapLocation::symbol(scope.symbol()))
372 );
373 if self.mode.can_enter() {
374 self.print_extra(context);
375 self.try_halt();
376 }
377 println!();
378 self.step += 1;
379 }
380
381 fn on_exit_scope(&mut self, scope: &VmScope<SE>, context: &mut Context, _: &Registry) {
382 println!();
383 println!(
384 "* #{} PrintDebugger | Exit scope:\n{}",
385 self.step,
386 self.map(SourceMapLocation::symbol(scope.symbol()))
387 );
388 if self.mode.can_exit() {
389 self.print_extra(context);
390 self.try_halt();
391 }
392 println!();
393 self.step += 1;
394 }
395
396 fn on_enter_operation(
397 &mut self,
398 scope: &VmScope<SE>,
399 operation: &ScriptOperation<SE>,
400 position: usize,
401 context: &mut Context,
402 _: &Registry,
403 ) {
404 println!();
405 println!(
406 "* #{} PrintDebugger | Enter operation:\n{}",
407 self.step,
408 self.map(SourceMapLocation::symbol_operation(
409 scope.symbol(),
410 position
411 ))
412 );
413 if self.mode.can_enter() {
414 println!(
415 "- operation: {}",
416 if self.operation_details {
417 format!("{operation:#?}")
418 } else {
419 operation.label().to_owned()
420 }
421 );
422 self.print_extra(context);
423 self.try_halt();
424 }
425 println!();
426 self.step += 1;
427 }
428
429 fn on_exit_operation(
430 &mut self,
431 scope: &VmScope<SE>,
432 operation: &ScriptOperation<SE>,
433 position: usize,
434 context: &mut Context,
435 _: &Registry,
436 ) {
437 println!();
438 println!(
439 "* #{} PrintDebugger | Exit operation:\n{}",
440 self.step,
441 self.map(SourceMapLocation::symbol_operation(
442 scope.symbol(),
443 position
444 ))
445 );
446 if self.mode.can_exit() {
447 println!(
448 "- operation: {}",
449 if self.operation_details {
450 format!("{operation:#?}")
451 } else {
452 operation.label().to_owned()
453 }
454 );
455 self.print_extra(context);
456 self.try_halt();
457 }
458 println!();
459 self.step += 1;
460 }
461}