1use crate::context::CompileUnit;
3use crate::graph_builder::{BasicBlock, BlockId};
4use crate::ir::{HirId, HirNode};
5use std::fmt;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
11pub enum PrintFormat {
12 #[default]
20 Tree,
21
22 Compact,
27
28 Flat,
35}
36
37impl fmt::Display for PrintFormat {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match self {
40 PrintFormat::Tree => write!(f, "tree"),
41 PrintFormat::Compact => write!(f, "compact"),
42 PrintFormat::Flat => write!(f, "flat"),
43 }
44 }
45}
46
47impl std::str::FromStr for PrintFormat {
48 type Err = String;
49
50 fn from_str(s: &str) -> Result<Self, Self::Err> {
51 match s.to_lowercase().as_str() {
52 "tree" => Ok(PrintFormat::Tree),
53 "compact" => Ok(PrintFormat::Compact),
54 "flat" => Ok(PrintFormat::Flat),
55 other => Err(format!(
56 "Unknown format: {other}. Use 'tree', 'compact', or 'flat'"
57 )),
58 }
59 }
60}
61
62#[derive(Debug, Clone)]
68pub struct PrintConfig {
69 pub format: PrintFormat,
71
72 pub include_snippets: bool,
74
75 pub include_line_info: bool,
77
78 pub snippet_col_width: usize,
80
81 pub snippet_max_length: usize,
83
84 pub max_depth: usize,
86
87 pub indent_width: usize,
89
90 pub include_node_ids: bool,
92
93 pub include_field_names: bool,
95
96 pub line_width_limit: usize,
98}
99
100impl Default for PrintConfig {
101 fn default() -> Self {
102 PrintConfig {
103 format: PrintFormat::Tree,
104 include_snippets: true,
105 include_line_info: true,
106 snippet_col_width: 60,
107 snippet_max_length: 60,
108 max_depth: 1000,
109 indent_width: 2,
110 include_node_ids: false,
111 include_field_names: false,
112 line_width_limit: 0,
113 }
114 }
115}
116
117impl PrintConfig {
118 pub fn new() -> Self {
120 Self::default()
121 }
122
123 pub fn with_format(mut self, format: PrintFormat) -> Self {
128 self.format = format;
129 self
130 }
131
132 pub fn with_snippets(mut self, enabled: bool) -> Self {
134 self.include_snippets = enabled;
135 self
136 }
137
138 pub fn with_line_info(mut self, enabled: bool) -> Self {
140 self.include_line_info = enabled;
141 self
142 }
143
144 pub fn with_snippet_width(mut self, width: usize) -> Self {
146 self.snippet_col_width = width;
147 self
148 }
149
150 pub fn with_snippet_max_length(mut self, length: usize) -> Self {
152 self.snippet_max_length = length;
153 self
154 }
155
156 pub fn with_max_depth(mut self, depth: usize) -> Self {
158 self.max_depth = depth;
159 self
160 }
161
162 pub fn with_indent_width(mut self, width: usize) -> Self {
164 self.indent_width = width;
165 self
166 }
167
168 pub fn with_node_ids(mut self, enabled: bool) -> Self {
170 self.include_node_ids = enabled;
171 self
172 }
173
174 pub fn with_field_names(mut self, enabled: bool) -> Self {
176 self.include_field_names = enabled;
177 self
178 }
179
180 pub fn with_line_width_limit(mut self, width: usize) -> Self {
182 self.line_width_limit = width;
183 self
184 }
185
186 pub fn minimal() -> Self {
193 PrintConfig {
194 format: PrintFormat::Flat,
195 include_snippets: false,
196 include_line_info: false,
197 include_node_ids: false,
198 include_field_names: false,
199 ..Default::default()
200 }
201 }
202
203 pub fn verbose() -> Self {
207 PrintConfig {
208 format: PrintFormat::Tree,
209 include_snippets: true,
210 include_line_info: true,
211 include_node_ids: true,
212 include_field_names: true,
213 snippet_col_width: 80,
214 snippet_max_length: 100,
215 ..Default::default()
216 }
217 }
218
219 pub fn compact() -> Self {
223 PrintConfig {
224 format: PrintFormat::Compact,
225 include_snippets: true,
226 include_line_info: true,
227 include_node_ids: false,
228 ..Default::default()
229 }
230 }
231
232 pub fn validate(&self) -> Result<(), String> {
234 if self.max_depth == 0 {
235 return Err("max_depth must be > 0".to_string());
236 }
237 if self.indent_width == 0 {
238 return Err("indent_width must be > 0".to_string());
239 }
240 if self.snippet_col_width == 0 {
241 return Err("snippet_col_width must be > 0".to_string());
242 }
243 Ok(())
244 }
245}
246
247#[derive(Debug, Clone)]
249pub struct RenderError {
250 pub message: String,
251}
252
253impl fmt::Display for RenderError {
254 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255 write!(f, "Render error: {}", self.message)
256 }
257}
258
259impl std::error::Error for RenderError {}
260
261impl From<String> for RenderError {
262 fn from(message: String) -> Self {
263 RenderError { message }
264 }
265}
266
267impl From<&str> for RenderError {
268 fn from(message: &str) -> Self {
269 RenderError {
270 message: message.to_string(),
271 }
272 }
273}
274
275impl RenderError {
276 pub fn new(message: impl Into<String>) -> Self {
278 RenderError {
279 message: message.into(),
280 }
281 }
282
283 pub fn max_depth_exceeded(depth: usize, max: usize) -> Self {
285 RenderError::new(format!("Maximum depth {depth} exceeded (limit: {max})"))
286 }
287
288 pub fn config_invalid(reason: impl Into<String>) -> Self {
290 RenderError::new(format!("Invalid configuration: {}", reason.into()))
291 }
292}
293
294pub type RenderResult<T> = Result<T, RenderError>;
295
296#[derive(Debug, Clone)]
302struct RenderNode {
303 label: String,
304 line_info: Option<String>,
305 snippet: Option<String>,
306 children: Vec<RenderNode>,
307 node_id: Option<String>,
308 suffix: Option<String>,
310}
311
312impl RenderNode {
313 fn new(
314 label: String,
315 line_info: Option<String>,
316 snippet: Option<String>,
317 children: Vec<RenderNode>,
318 node_id: Option<String>,
319 ) -> Self {
320 Self {
321 label,
322 line_info,
323 snippet,
324 children,
325 node_id,
326 suffix: None,
327 }
328 }
329
330 fn with_suffix(mut self, suffix: Option<String>) -> Self {
331 self.suffix = suffix;
332 self
333 }
334}
335
336pub fn render_llmcc_ir(root: HirId, unit: CompileUnit<'_>) -> RenderResult<(String, String)> {
342 render_llmcc_ir_with_config(root, unit, &PrintConfig::default())
343}
344
345pub fn render_llmcc_ir_with_config(
347 root: HirId,
348 unit: CompileUnit<'_>,
349 config: &PrintConfig,
350) -> RenderResult<(String, String)> {
351 config.validate()?;
352
353 let hir_root = unit.hir_node(root);
354
355 let ast_render = if let Some(parse_tree) = unit.parse_tree() {
357 if let Some(root_node) = parse_tree.root_node() {
358 build_ast_render(&*root_node, unit, config, 0)?
359 } else {
360 RenderNode::new(
361 "No AST root node found".to_string(),
362 None,
363 None,
364 vec![],
365 None,
366 )
367 }
368 } else {
369 RenderNode::new(
370 "Parse tree not available for this compilation unit".to_string(),
371 None,
372 None,
373 vec![],
374 None,
375 )
376 };
377
378 let hir_render = build_hir_render(&hir_root, unit, config, 0)?;
379 let ast = render_lines(&ast_render, config)?;
380 let hir = render_lines(&hir_render, config)?;
381
382 Ok((ast, hir))
383}
384
385pub fn print_llmcc_ir(unit: CompileUnit<'_>) -> RenderResult<()> {
387 print_llmcc_ir_with_config(unit, &PrintConfig::default())
388}
389
390pub fn print_llmcc_ir_with_config(unit: CompileUnit<'_>, config: &PrintConfig) -> RenderResult<()> {
392 let root = unit
393 .file_root_id()
394 .ok_or_else(|| RenderError::new("No HIR root node found"))?;
395
396 let (ast, _hir) = render_llmcc_ir_with_config(root, unit, config)?;
397 println!("{ast}\n");
398 Ok(())
400}
401
402pub fn render_llmcc_graph_with_config(
404 root: BlockId,
405 unit: CompileUnit<'_>,
406 config: &PrintConfig,
407) -> RenderResult<String> {
408 config.validate()?;
409
410 let block = unit.bb(root);
411 let render = build_block_render(&block, unit, config, 0)?;
412 render_lines(&render, config)
413}
414
415pub fn print_llmcc_graph(root: BlockId, unit: CompileUnit<'_>) -> RenderResult<()> {
417 print_llmcc_graph_with_config(root, unit, &PrintConfig::default())
418}
419
420pub fn print_llmcc_graph_with_config(
422 root: BlockId,
423 unit: CompileUnit<'_>,
424 config: &PrintConfig,
425) -> RenderResult<()> {
426 let graph = render_llmcc_graph_with_config(root, unit, config)?;
427 println!("{graph}\n");
428 Ok(())
429}
430
431fn build_ast_render(
437 node: &(dyn crate::lang_def::ParseNode + '_),
438 unit: CompileUnit<'_>,
439 config: &PrintConfig,
440 depth: usize,
441) -> RenderResult<RenderNode> {
442 build_ast_render_with(node, None, 0, unit, config, depth)
443}
444
445fn build_ast_render_with(
447 node: &(dyn crate::lang_def::ParseNode + '_),
448 parent: Option<&(dyn crate::lang_def::ParseNode + '_)>,
449 child_index: usize,
450 unit: CompileUnit<'_>,
451 config: &PrintConfig,
452 depth: usize,
453) -> RenderResult<RenderNode> {
454 if depth > config.max_depth {
456 return Err(RenderError::max_depth_exceeded(depth, config.max_depth));
457 }
458
459 let field_name: Option<&str> = parent.and_then(|p| p.child_field_name(child_index));
461
462 let label = node.format_node_label(field_name);
464
465 let line_info = Some(format!("[{}-{}]", node.start_byte(), node.end_byte()));
467
468 let snippet = if config.include_snippets {
470 snippet_from_ctx(&unit, node.start_byte(), node.end_byte(), config)
471 } else {
472 None
473 };
474
475 let mut children = Vec::new();
477 for i in 0..node.child_count() {
478 if let Some(child) = node.child(i)
479 && let Ok(render) =
480 build_ast_render_with(&*child, Some(node), i, unit, config, depth + 1)
481 {
482 children.push(render);
483 }
484 }
485
486 Ok(RenderNode::new(label, line_info, snippet, children, None))
487}
488
489fn build_hir_render<'tcx>(
491 node: &HirNode<'tcx>,
492 unit: CompileUnit<'tcx>,
493 config: &PrintConfig,
494 depth: usize,
495) -> RenderResult<RenderNode> {
496 if depth > config.max_depth {
498 return Err(RenderError::max_depth_exceeded(depth, config.max_depth));
499 }
500
501 let mut label = node.format(unit);
502
503 if let crate::ir::HirNode::Ident(ident) = node {
505 label.push_str(&format!(" = \"{}\"", ident.name));
506 }
507
508 let line_info = if config.include_line_info {
509 Some(format!(
510 "[{}-{}]",
511 get_line_from_byte(&unit, node.start_byte()),
512 get_line_from_byte(&unit, node.end_byte())
513 ))
514 } else {
515 None
516 };
517
518 let snippet = if config.include_snippets {
519 snippet_from_ctx(&unit, node.start_byte(), node.end_byte(), config)
520 } else {
521 None
522 };
523
524 let children = node
525 .child_ids()
526 .iter()
527 .map(|id| {
528 let child = unit.hir_node(*id);
529 build_hir_render(&child, unit, config, depth + 1)
530 })
531 .collect::<RenderResult<Vec<_>>>()?;
532
533 Ok(RenderNode::new(label, line_info, snippet, children, None))
534}
535
536fn build_block_render<'tcx>(
538 block: &BasicBlock<'tcx>,
539 unit: CompileUnit<'tcx>,
540 config: &PrintConfig,
541 depth: usize,
542) -> RenderResult<RenderNode> {
543 if depth > config.max_depth {
545 return Err(RenderError::max_depth_exceeded(depth, config.max_depth));
546 }
547
548 let label = block.format_block(unit);
549 let suffix = block.format_suffix();
550
551 let line_info = if config.include_line_info {
552 block.opt_node().map(|node| {
553 format!(
554 "[{}-{}]",
555 get_line_from_byte(&unit, node.start_byte()),
556 get_line_from_byte(&unit, node.end_byte())
557 )
558 })
559 } else {
560 None
561 };
562
563 let snippet = if config.include_snippets {
564 block
565 .opt_node()
566 .and_then(|n| snippet_from_ctx(&unit, n.start_byte(), n.end_byte(), config))
567 } else {
568 None
569 };
570
571 let children = block
572 .children()
573 .iter()
574 .map(|id| {
575 let child = unit.bb(*id);
576 build_block_render(&child, unit, config, depth + 1)
577 })
578 .collect::<RenderResult<Vec<_>>>()?;
579
580 Ok(RenderNode::new(label, line_info, snippet, children, None).with_suffix(suffix))
581}
582
583fn render_lines(node: &RenderNode, config: &PrintConfig) -> RenderResult<String> {
585 let mut lines = Vec::new();
586 render_node_with_format(node, 0, &mut lines, config)?;
587 Ok(lines.join("\n"))
588}
589
590fn render_node_with_format(
592 node: &RenderNode,
593 depth: usize,
594 out: &mut Vec<String>,
595 config: &PrintConfig,
596) -> RenderResult<()> {
597 match config.format {
598 PrintFormat::Tree => render_node_tree(node, depth, out, config),
599 PrintFormat::Compact => render_node_compact(node, out, config),
600 PrintFormat::Flat => render_node_flat(node, out, config),
601 }
602}
603
604fn render_node_tree(
606 node: &RenderNode,
607 depth: usize,
608 out: &mut Vec<String>,
609 config: &PrintConfig,
610) -> RenderResult<()> {
611 let indent = " ".repeat(depth * config.indent_width);
612 let mut line = format!("{indent}(");
613
614 line.push_str(&node.label);
616
617 if config.include_node_ids
619 && let Some(id) = &node.node_id
620 {
621 line.push_str(&format!(" #{id}"));
622 }
623
624 if let Some(line_info) = &node.line_info {
626 line.push_str(&format!(" {line_info}"));
627 }
628
629 if node.children.is_empty() {
631 line.push(')');
632 if let Some(suffix) = &node.suffix {
634 line.push_str(&format!(" {suffix}"));
635 }
636 if let Some(snippet) = &node.snippet {
638 let padding = config.snippet_col_width.saturating_sub(line.len());
640 if padding > 0 {
641 line.push_str(&" ".repeat(padding));
642 } else {
643 line.push(' ');
644 }
645 line.push_str(&format!("|{snippet}|"));
646 }
647 out.push(line);
648 } else {
649 if let Some(snippet) = &node.snippet {
651 let padding = config.snippet_col_width.saturating_sub(line.len());
652 if padding > 0 {
653 line.push_str(&" ".repeat(padding));
654 } else {
655 line.push(' ');
656 }
657 line.push_str(&format!("|{snippet}|"));
658 }
659 out.push(line);
660 for child in &node.children {
661 render_node_tree(child, depth + 1, out, config)?;
662 }
663 out.push(format!("{indent})"));
664 }
665
666 Ok(())
667}
668
669fn render_node_compact(
671 node: &RenderNode,
672 out: &mut Vec<String>,
673 config: &PrintConfig,
674) -> RenderResult<()> {
675 let mut line = format!("({})", node.label);
676
677 if config.include_line_info
678 && let Some(info) = &node.line_info
679 {
680 line.push_str(&format!(" {info}"));
681 }
682
683 for child in &node.children {
684 let mut child_line = format!("({})", child.label);
685 if config.include_line_info && child.line_info.is_some() {
686 child_line.push_str(&format!(" {}", child.line_info.as_ref().unwrap()));
687 }
688 line.push_str(&format!(" {child_line}"));
689 }
690
691 out.push(line);
692 Ok(())
693}
694
695fn render_node_flat(
697 node: &RenderNode,
698 out: &mut Vec<String>,
699 config: &PrintConfig,
700) -> RenderResult<()> {
701 let mut line = node.label.clone();
702
703 if config.include_line_info
704 && let Some(info) = &node.line_info
705 {
706 line.push_str(&format!(" {info}"));
707 }
708
709 out.push(line);
710
711 for child in &node.children {
712 render_node_flat(child, out, config)?;
713 }
714
715 Ok(())
716}
717
718fn snippet_from_ctx(
724 unit: &CompileUnit<'_>,
725 start: usize,
726 end: usize,
727 config: &PrintConfig,
728) -> Option<String> {
729 unit.file()
730 .opt_get_text(start, end)
731 .map(|text| text.split_whitespace().collect::<Vec<_>>().join(" "))
732 .filter(|s| !s.is_empty() && s.len() <= config.snippet_max_length)
733}
734
735fn get_line_from_byte(unit: &CompileUnit<'_>, byte_pos: usize) -> usize {
737 let content = unit.file().content();
738 let text = String::from_utf8_lossy(&content[..byte_pos.min(content.len())]);
739 text.lines().count()
740}
741
742#[cfg(test)]
743mod tests {
744 use super::*;
745
746 #[test]
747 fn test_print_format_display() {
748 assert_eq!(PrintFormat::Tree.to_string(), "tree");
749 assert_eq!(PrintFormat::Compact.to_string(), "compact");
750 assert_eq!(PrintFormat::Flat.to_string(), "flat");
751 }
752
753 #[test]
754 fn test_print_format_from_str() {
755 assert_eq!("tree".parse::<PrintFormat>().unwrap(), PrintFormat::Tree);
756 assert_eq!(
757 "compact".parse::<PrintFormat>().unwrap(),
758 PrintFormat::Compact
759 );
760 assert_eq!("flat".parse::<PrintFormat>().unwrap(), PrintFormat::Flat);
761 assert!("invalid".parse::<PrintFormat>().is_err());
762 }
763
764 #[test]
765 fn test_print_config_default() {
766 let config = PrintConfig::default();
767 assert_eq!(config.format, PrintFormat::Tree);
768 assert!(config.include_snippets);
769 assert!(config.include_line_info);
770 assert_eq!(config.max_depth, 1000);
771 }
772
773 #[test]
774 fn test_print_config_minimal() {
775 let config = PrintConfig::minimal();
776 assert_eq!(config.format, PrintFormat::Flat);
777 assert!(!config.include_snippets);
778 assert!(!config.include_line_info);
779 assert!(!config.include_node_ids);
780 }
781
782 #[test]
783 fn test_print_config_verbose() {
784 let config = PrintConfig::verbose();
785 assert_eq!(config.format, PrintFormat::Tree);
786 assert!(config.include_snippets);
787 assert!(config.include_line_info);
788 assert!(config.include_node_ids);
789 }
790
791 #[test]
792 fn test_print_config_builder() {
793 let config = PrintConfig::new()
794 .with_format(PrintFormat::Flat)
795 .with_snippets(false)
796 .with_max_depth(10)
797 .with_indent_width(4);
798
799 assert_eq!(config.format, PrintFormat::Flat);
800 assert!(!config.include_snippets);
801 assert_eq!(config.max_depth, 10);
802 assert_eq!(config.indent_width, 4);
803 }
804
805 #[test]
806 fn test_print_config_validation() {
807 let bad_config = PrintConfig {
808 max_depth: 0,
809 ..Default::default()
810 };
811 assert!(bad_config.validate().is_err());
812
813 let bad_config = PrintConfig {
814 indent_width: 0,
815 ..Default::default()
816 };
817 assert!(bad_config.validate().is_err());
818
819 let good_config = PrintConfig::default();
820 assert!(good_config.validate().is_ok());
821 }
822
823 #[test]
824 fn test_render_error_creation() {
825 let err = RenderError::new("test error");
826 assert_eq!(err.message, "test error");
827
828 let err = RenderError::max_depth_exceeded(100, 50);
829 assert!(err.message.contains("100"));
830 assert!(err.message.contains("50"));
831 }
832
833 #[test]
834 fn test_render_node_creation() {
835 let node = RenderNode::new("test".to_string(), None, None, vec![], None);
836 assert_eq!(node.label, "test");
837 assert_eq!(node.children.len(), 0);
838 }
839}