1use std::num::NonZeroU16;
23
24use arborium_tree_sitter::Node;
25
26use crate::Colors;
27use crate::bytecode::{
28 EffectOpcode, Instruction, LineBuilder, Match, Module, Nav, NodeTypeIR, Symbol, cols,
29 format_effect, trace, truncate_text, width_for_count,
30};
31
32use super::effect::RuntimeEffect;
33
34#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
38pub enum Verbosity {
39 #[default]
41 Default,
42 Verbose,
44 VeryVerbose,
46}
47
48pub trait Tracer {
65 fn trace_instruction(&mut self, ip: u16, instr: &Instruction<'_>);
67
68 fn trace_nav(&mut self, nav: Nav, node: Node<'_>);
70
71 fn trace_nav_failure(&mut self, nav: Nav);
73
74 fn trace_match_success(&mut self, node: Node<'_>);
76
77 fn trace_match_failure(&mut self, node: Node<'_>);
79
80 fn trace_field_success(&mut self, field_id: NonZeroU16);
82
83 fn trace_field_failure(&mut self, node: Node<'_>);
85
86 fn trace_effect(&mut self, effect: &RuntimeEffect<'_>);
88
89 fn trace_effect_suppressed(&mut self, opcode: EffectOpcode, payload: usize);
91
92 fn trace_suppress_control(&mut self, opcode: EffectOpcode, suppressed: bool);
95
96 fn trace_call(&mut self, target_ip: u16);
98
99 fn trace_return(&mut self);
101
102 fn trace_checkpoint_created(&mut self, ip: u16);
104
105 fn trace_backtrack(&mut self);
107
108 fn trace_enter_entrypoint(&mut self, target_ip: u16);
110
111 fn trace_enter_preamble(&mut self);
113}
114
115pub struct NoopTracer;
117
118impl Tracer for NoopTracer {
119 #[inline(always)]
120 fn trace_instruction(&mut self, _ip: u16, _instr: &Instruction<'_>) {}
121
122 #[inline(always)]
123 fn trace_nav(&mut self, _nav: Nav, _node: Node<'_>) {}
124
125 #[inline(always)]
126 fn trace_nav_failure(&mut self, _nav: Nav) {}
127
128 #[inline(always)]
129 fn trace_match_success(&mut self, _node: Node<'_>) {}
130
131 #[inline(always)]
132 fn trace_match_failure(&mut self, _node: Node<'_>) {}
133
134 #[inline(always)]
135 fn trace_field_success(&mut self, _field_id: NonZeroU16) {}
136
137 #[inline(always)]
138 fn trace_field_failure(&mut self, _node: Node<'_>) {}
139
140 #[inline(always)]
141 fn trace_effect(&mut self, _effect: &RuntimeEffect<'_>) {}
142
143 #[inline(always)]
144 fn trace_effect_suppressed(&mut self, _opcode: EffectOpcode, _payload: usize) {}
145
146 #[inline(always)]
147 fn trace_suppress_control(&mut self, _opcode: EffectOpcode, _suppressed: bool) {}
148
149 #[inline(always)]
150 fn trace_call(&mut self, _target_ip: u16) {}
151
152 #[inline(always)]
153 fn trace_return(&mut self) {}
154
155 #[inline(always)]
156 fn trace_checkpoint_created(&mut self, _ip: u16) {}
157
158 #[inline(always)]
159 fn trace_backtrack(&mut self) {}
160
161 #[inline(always)]
162 fn trace_enter_entrypoint(&mut self, _target_ip: u16) {}
163
164 #[inline(always)]
165 fn trace_enter_preamble(&mut self) {}
166}
167
168use std::collections::BTreeMap;
169
170pub struct PrintTracer<'s> {
172 pub(crate) source: &'s [u8],
174 pub(crate) verbosity: Verbosity,
176 pub(crate) lines: Vec<String>,
178 pub(crate) builder: LineBuilder,
180 pub(crate) node_type_names: BTreeMap<u16, String>,
182 pub(crate) node_field_names: BTreeMap<u16, String>,
184 pub(crate) member_names: Vec<String>,
186 pub(crate) entrypoint_by_ip: BTreeMap<u16, String>,
188 pub(crate) checkpoint_ips: Vec<u16>,
190 pub(crate) definition_stack: Vec<String>,
192 pub(crate) pending_return_ip: Option<u16>,
194 pub(crate) step_width: usize,
196 pub(crate) colors: Colors,
198}
199
200pub struct PrintTracerBuilder<'s, 'm> {
202 source: &'s str,
203 module: &'m Module,
204 verbosity: Verbosity,
205 colors: Colors,
206}
207
208impl<'s, 'm> PrintTracerBuilder<'s, 'm> {
209 pub fn new(source: &'s str, module: &'m Module) -> Self {
211 Self {
212 source,
213 module,
214 verbosity: Verbosity::Default,
215 colors: Colors::OFF,
216 }
217 }
218
219 pub fn verbosity(mut self, verbosity: Verbosity) -> Self {
221 self.verbosity = verbosity;
222 self
223 }
224
225 pub fn colored(mut self, enabled: bool) -> Self {
227 self.colors = Colors::new(enabled);
228 self
229 }
230
231 pub fn build(self) -> PrintTracer<'s> {
233 let header = self.module.header();
234 let strings = self.module.strings();
235 let types = self.module.types();
236 let node_types = self.module.node_types();
237 let node_fields = self.module.node_fields();
238 let entrypoints = self.module.entrypoints();
239
240 let mut node_type_names = BTreeMap::new();
241 for i in 0..node_types.len() {
242 let t = node_types.get(i);
243 node_type_names.insert(t.id, strings.get(t.name).to_string());
244 }
245
246 let mut node_field_names = BTreeMap::new();
247 for i in 0..node_fields.len() {
248 let f = node_fields.get(i);
249 node_field_names.insert(f.id, strings.get(f.name).to_string());
250 }
251
252 let member_names: Vec<String> = (0..types.members_count())
254 .map(|i| strings.get(types.get_member(i).name).to_string())
255 .collect();
256
257 let mut entrypoint_by_ip = BTreeMap::new();
259 for i in 0..entrypoints.len() {
260 let e = entrypoints.get(i);
261 entrypoint_by_ip.insert(e.target, strings.get(e.name).to_string());
262 }
263
264 let step_width = width_for_count(header.transitions_count as usize);
265
266 PrintTracer {
267 source: self.source.as_bytes(),
268 verbosity: self.verbosity,
269 lines: Vec::new(),
270 builder: LineBuilder::new(step_width),
271 node_type_names,
272 node_field_names,
273 member_names,
274 entrypoint_by_ip,
275 checkpoint_ips: Vec::new(),
276 definition_stack: Vec::new(),
277 pending_return_ip: None,
278 step_width,
279 colors: self.colors,
280 }
281 }
282}
283
284impl<'s> PrintTracer<'s> {
285 pub fn builder<'m>(source: &'s str, module: &'m Module) -> PrintTracerBuilder<'s, 'm> {
287 PrintTracerBuilder::new(source, module)
288 }
289
290 fn node_type_name(&self, id: u16) -> &str {
291 self.node_type_names.get(&id).map_or("?", |s| s.as_str())
292 }
293
294 fn node_field_name(&self, id: u16) -> &str {
295 self.node_field_names.get(&id).map_or("?", |s| s.as_str())
296 }
297
298 fn member_name(&self, idx: u16) -> &str {
299 self.member_names
300 .get(idx as usize)
301 .map_or("?", |s| s.as_str())
302 }
303
304 fn entrypoint_name(&self, ip: u16) -> &str {
305 self.entrypoint_by_ip.get(&ip).map_or("?", |s| s.as_str())
306 }
307
308 fn format_kind_simple(&self, kind: &str, is_named: bool) -> String {
313 if is_named {
314 kind.to_string()
315 } else {
316 let c = &self.colors;
317 format!("{}{}{}{}", c.dim, c.green, kind, c.reset)
318 }
319 }
320
321 fn format_kind_with_text(&self, kind: &str, text: &str, is_named: bool) -> String {
326 let c = &self.colors;
327
328 let available = cols::TOTAL_WIDTH - 9;
334
335 if is_named {
336 let text_budget = available.saturating_sub(kind.len() + 1).max(12);
338 let truncated = truncate_text(text, text_budget);
339 format!("{} {}{}{}{}", kind, c.dim, c.green, truncated, c.reset)
340 } else {
341 let truncated = truncate_text(text, available);
343 format!("{}{}{}{}", c.dim, c.green, truncated, c.reset)
344 }
345 }
346
347 fn format_effect(&self, effect: &RuntimeEffect<'_>) -> String {
349 use RuntimeEffect::*;
350 match effect {
351 Node(_) => "Node".to_string(),
352 Text(_) => "Text".to_string(),
353 Arr => "Arr".to_string(),
354 Push => "Push".to_string(),
355 EndArr => "EndArr".to_string(),
356 Obj => "Obj".to_string(),
357 EndObj => "EndObj".to_string(),
358 Set(idx) => format!("Set \"{}\"", self.member_name(*idx)),
359 Enum(idx) => format!("Enum \"{}\"", self.member_name(*idx)),
360 EndEnum => "EndEnum".to_string(),
361 Clear => "Clear".to_string(),
362 Null => "Null".to_string(),
363 }
364 }
365
366 fn format_effect_from_opcode(&self, opcode: EffectOpcode, payload: usize) -> String {
368 use EffectOpcode::*;
369 match opcode {
370 Node => "Node".to_string(),
371 Text => "Text".to_string(),
372 Arr => "Arr".to_string(),
373 Push => "Push".to_string(),
374 EndArr => "EndArr".to_string(),
375 Obj => "Obj".to_string(),
376 EndObj => "EndObj".to_string(),
377 Set => format!("Set \"{}\"", self.member_name(payload as u16)),
378 Enum => format!("Enum \"{}\"", self.member_name(payload as u16)),
379 EndEnum => "EndEnum".to_string(),
380 Clear => "Clear".to_string(),
381 Null => "Null".to_string(),
382 SuppressBegin | SuppressEnd => unreachable!(),
383 }
384 }
385
386 fn format_match_content(&self, m: &Match<'_>) -> String {
390 let mut parts = Vec::new();
391
392 let pre: Vec<_> = m.pre_effects().map(|e| format_effect(&e)).collect();
394 if !pre.is_empty() {
395 parts.push(format!("[{}]", pre.join(" ")));
396 }
397
398 if !m.is_epsilon() {
400 for field_id in m.neg_fields() {
402 let name = self.node_field_name(field_id);
403 parts.push(format!("!{name}"));
404 }
405
406 let node_part = self.format_node_pattern(m);
408 if !node_part.is_empty() {
409 parts.push(node_part);
410 }
411 }
412
413 let post: Vec<_> = m.post_effects().map(|e| format_effect(&e)).collect();
415 if !post.is_empty() {
416 parts.push(format!("[{}]", post.join(" ")));
417 }
418
419 parts.join(" ")
420 }
421
422 fn format_node_pattern(&self, m: &Match<'_>) -> String {
424 let mut result = String::new();
425
426 if let Some(f) = m.node_field {
427 result.push_str(self.node_field_name(f.get()));
428 result.push_str(": ");
429 }
430
431 match m.node_type {
432 NodeTypeIR::Any => {
433 result.push('_');
435 }
436 NodeTypeIR::Named(None) => {
437 result.push_str("(_)");
439 }
440 NodeTypeIR::Named(Some(t)) => {
441 result.push('(');
443 result.push_str(self.node_type_name(t.get()));
444 result.push(')');
445 }
446 NodeTypeIR::Anonymous(None) => {
447 result.push_str("\"_\"");
449 }
450 NodeTypeIR::Anonymous(Some(t)) => {
451 result.push('"');
453 result.push_str(self.node_type_name(t.get()));
454 result.push('"');
455 }
456 }
457
458 result
459 }
460
461 pub fn print(&self) {
463 for line in &self.lines {
464 println!("{}", line);
465 }
466 }
467
468 fn add_instruction(&mut self, ip: u16, symbol: Symbol, content: &str, successors: &str) {
470 let prefix = format!(" {:0sw$} {} ", ip, symbol.format(), sw = self.step_width);
471 let line = self
472 .builder
473 .pad_successors(format!("{prefix}{content}"), successors);
474 self.lines.push(line);
475 }
476
477 fn add_subline(&mut self, symbol: Symbol, content: &str) {
479 let step_area = 2 + self.step_width + 1;
480 let prefix = format!("{:step_area$}{} ", "", symbol.format());
481 self.lines.push(format!("{prefix}{content}"));
482 }
483
484 fn format_def_name(&self, name: &str) -> String {
486 let c = self.colors;
487 if name.starts_with('_') {
488 format!("{}{}{}", c.blue, name, c.reset)
490 } else {
491 format!("({}{}{})", c.blue, name, c.reset)
493 }
494 }
495
496 fn format_def_label(&self, name: &str) -> String {
498 let c = self.colors;
499 format!("{}{}{}:", c.blue, name, c.reset)
500 }
501
502 fn push_def_label(&mut self, name: &str) {
504 if !self.lines.is_empty() {
505 self.lines.push(String::new());
506 }
507 self.lines.push(self.format_def_label(name));
508 }
509}
510
511impl Tracer for PrintTracer<'_> {
512 fn trace_instruction(&mut self, ip: u16, instr: &Instruction<'_>) {
513 match instr {
514 Instruction::Match(m) => {
515 let symbol = if m.is_epsilon() {
517 Symbol::EPSILON
518 } else {
519 Symbol::EMPTY
520 };
521 let content = self.format_match_content(m);
522 let successors = format_match_successors(m);
523 self.add_instruction(ip, symbol, &content, &successors);
524 }
525 Instruction::Call(c) => {
526 let name = self.entrypoint_name(c.target.get());
527 let content = self.format_def_name(name);
528 let successors = format!("{:02} : {:02}", c.target.get(), c.next.get());
529 self.add_instruction(ip, Symbol::EMPTY, &content, &successors);
530 }
531 Instruction::Return(_) => {
532 self.pending_return_ip = Some(ip);
533 }
534 Instruction::Trampoline(t) => {
535 let content = "Trampoline";
537 let successors = format!("{:02}", t.next.get());
538 self.add_instruction(ip, Symbol::EMPTY, content, &successors);
539 }
540 }
541 }
542
543 fn trace_nav(&mut self, nav: Nav, node: Node<'_>) {
544 if self.verbosity == Verbosity::Default {
546 return;
547 }
548
549 let kind = node.kind();
550 let symbol = match nav {
551 Nav::Epsilon => Symbol::EPSILON,
552 Nav::Down | Nav::DownSkip | Nav::DownExact => trace::NAV_DOWN,
553 Nav::Next | Nav::NextSkip | Nav::NextExact => trace::NAV_NEXT,
554 Nav::Up(_) | Nav::UpSkipTrivia(_) | Nav::UpExact(_) => trace::NAV_UP,
555 Nav::Stay | Nav::StayExact => Symbol::EMPTY,
556 };
557
558 if self.verbosity == Verbosity::VeryVerbose {
560 let text = node.utf8_text(self.source).unwrap_or("?");
561 let content = self.format_kind_with_text(kind, text, node.is_named());
562 self.add_subline(symbol, &content);
563 } else {
564 let content = self.format_kind_simple(kind, node.is_named());
565 self.add_subline(symbol, &content);
566 }
567 }
568
569 fn trace_nav_failure(&mut self, nav: Nav) {
570 if self.verbosity == Verbosity::Default {
572 return;
573 }
574
575 let nav_symbol = match nav {
577 Nav::Down | Nav::DownSkip | Nav::DownExact => "▽",
578 Nav::Next | Nav::NextSkip | Nav::NextExact => "▷",
579 Nav::Up(_) | Nav::UpSkipTrivia(_) | Nav::UpExact(_) => "△",
580 Nav::Stay | Nav::StayExact | Nav::Epsilon => "·",
581 };
582
583 self.add_subline(trace::MATCH_FAILURE, nav_symbol);
584 }
585
586 fn trace_match_success(&mut self, node: Node<'_>) {
587 let kind = node.kind();
588
589 if self.verbosity != Verbosity::Default {
591 let text = node.utf8_text(self.source).unwrap_or("?");
592 let content = self.format_kind_with_text(kind, text, node.is_named());
593 self.add_subline(trace::MATCH_SUCCESS, &content);
594 } else {
595 let content = self.format_kind_simple(kind, node.is_named());
596 self.add_subline(trace::MATCH_SUCCESS, &content);
597 }
598 }
599
600 fn trace_match_failure(&mut self, node: Node<'_>) {
601 let kind = node.kind();
602
603 if self.verbosity != Verbosity::Default {
605 let text = node.utf8_text(self.source).unwrap_or("?");
606 let content = self.format_kind_with_text(kind, text, node.is_named());
607 self.add_subline(trace::MATCH_FAILURE, &content);
608 } else {
609 let content = self.format_kind_simple(kind, node.is_named());
610 self.add_subline(trace::MATCH_FAILURE, &content);
611 }
612 }
613
614 fn trace_field_success(&mut self, field_id: NonZeroU16) {
615 if self.verbosity == Verbosity::Default {
617 return;
618 }
619
620 let name = self.node_field_name(field_id.get());
621 self.add_subline(trace::MATCH_SUCCESS, &format!("{}:", name));
622 }
623
624 fn trace_field_failure(&mut self, _node: Node<'_>) {
625 }
627
628 fn trace_effect(&mut self, effect: &RuntimeEffect<'_>) {
629 if self.verbosity == Verbosity::Default {
631 return;
632 }
633
634 let effect_str = self.format_effect(effect);
635 self.add_subline(trace::EFFECT, &effect_str);
636 }
637
638 fn trace_effect_suppressed(&mut self, opcode: EffectOpcode, payload: usize) {
639 if self.verbosity == Verbosity::Default {
641 return;
642 }
643
644 let effect_str = self.format_effect_from_opcode(opcode, payload);
645 self.add_subline(trace::EFFECT_SUPPRESSED, &effect_str);
646 }
647
648 fn trace_suppress_control(&mut self, opcode: EffectOpcode, suppressed: bool) {
649 if self.verbosity == Verbosity::Default {
651 return;
652 }
653
654 let name = match opcode {
655 EffectOpcode::SuppressBegin => "SuppressBegin",
656 EffectOpcode::SuppressEnd => "SuppressEnd",
657 _ => unreachable!(),
658 };
659 let symbol = if suppressed {
660 trace::EFFECT_SUPPRESSED
661 } else {
662 trace::EFFECT
663 };
664 self.add_subline(symbol, name);
665 }
666
667 fn trace_call(&mut self, target_ip: u16) {
668 let name = self.entrypoint_name(target_ip).to_string();
669 self.add_subline(trace::CALL, &self.format_def_name(&name));
670 self.push_def_label(&name);
671 self.definition_stack.push(name);
672 }
673
674 fn trace_return(&mut self) {
675 let ip = self
676 .pending_return_ip
677 .take()
678 .expect("trace_return without trace_instruction");
679 let name = self.definition_stack.pop().unwrap_or_default();
680 let content = self.format_def_name(&name);
681 let is_top_level = self.definition_stack.is_empty();
683 let successor = if is_top_level { "◼" } else { "" };
684 self.add_instruction(ip, trace::RETURN, &content, successor);
685 if let Some(caller) = self.definition_stack.last().cloned() {
687 self.push_def_label(&caller);
688 }
689 }
690
691 fn trace_checkpoint_created(&mut self, ip: u16) {
692 self.checkpoint_ips.push(ip);
693 }
694
695 fn trace_backtrack(&mut self) {
696 let created_at = self
697 .checkpoint_ips
698 .pop()
699 .expect("backtrack without checkpoint");
700 let line = format!(
701 " {:0sw$} {}",
702 created_at,
703 trace::BACKTRACK.format(),
704 sw = self.step_width
705 );
706 self.lines.push(line);
707 }
708
709 fn trace_enter_entrypoint(&mut self, target_ip: u16) {
710 let name = self.entrypoint_name(target_ip).to_string();
711 self.push_def_label(&name);
712 self.definition_stack.push(name);
713 }
714
715 fn trace_enter_preamble(&mut self) {
716 const PREAMBLE_NAME: &str = "_ObjWrap";
717 self.push_def_label(PREAMBLE_NAME);
718 self.definition_stack.push(PREAMBLE_NAME.to_string());
719 }
720}
721
722fn format_match_successors(m: &Match<'_>) -> String {
724 if m.is_terminal() {
725 "◼".to_string()
726 } else if m.succ_count() == 1 {
727 format!("{:02}", m.successor(0).get())
728 } else {
729 let succs: Vec<_> = m.successors().map(|s| format!("{:02}", s.get())).collect();
730 succs.join(", ")
731 }
732}