//! Pretty printing module for AST nodes //! //! This module provides elegant and functional printing capabilities for AST objects, //! inspired by Rust's debug formatting but optimized for readability and analysis. use std::fmt::{self, Write}; use crate::common::ast::{AstNode, Expr}; /// Configuration for pretty printing output #[derive(Clone, Debug)] pub struct PrettyConfig { /// Indentation string (default: " ") pub indent: String, /// Maximum depth before truncation (None = unlimited) pub max_depth: Option, /// Show source position information pub show_positions: bool, /// Use compact single-line format when possible pub compact: bool, /// Include type information pub show_types: bool, /// Use tree-style connectors pub tree_style: bool, } impl Default for PrettyConfig { fn default() -> Self { Self { indent: " ".to_string(), max_depth: None, show_positions: true, compact: false, show_types: true, tree_style: false, } } } #[allow(dead_code)] impl PrettyConfig { /// Create a compact configuration pub fn compact() -> Self { Self { compact: true, show_positions: false, ..Default::default() } } /// Create a tree-style configuration pub fn tree() -> Self { Self { tree_style: true, indent: "│ ".to_string(), ..Default::default() } } /// Create a debug configuration with all details pub fn debug() -> Self { Self { show_positions: true, show_types: true, compact: false, max_depth: None, ..Default::default() } } } /// Context for pretty printing, tracks depth and styling pub struct PrettyContext { config: PrettyConfig, depth: usize, is_last: Vec, // For tree-style formatting } impl PrettyContext { pub fn new(config: PrettyConfig) -> Self { Self { config, depth: 0, is_last: Vec::new(), } } fn indent(&self) -> String { if self.config.tree_style { self.tree_indent() } else { self.config.indent.repeat(self.depth) } } fn tree_indent(&self) -> String { let mut result = String::new(); for (i, &is_last) in self.is_last.iter().enumerate() { if i == self.is_last.len() - 1 { result.push_str(if is_last { "└─ " } else { "├─ " }); } else { result.push_str(if is_last { " " } else { "│ " }); } } result } fn child_context(&self, is_last: bool) -> Self { let mut new_is_last = self.is_last.clone(); new_is_last.push(is_last); Self { config: self.config.clone(), depth: self.depth + 1, is_last: new_is_last, } } fn should_truncate(&self) -> bool { self.config.max_depth.map_or(false, |max| self.depth >= max) } } /// Trait for pretty printing AST nodes pub trait PrettyPrint { fn pretty_print(&self, ctx: &PrettyContext, f: &mut fmt::Formatter) -> fmt::Result; } /// Convenience functions to get pretty printed strings pub fn pretty_print_with_config(item: &T, config: &PrettyConfig) -> String { let mut output = String::new(); let ctx = PrettyContext::new(config.clone()); write!(&mut output, "{}", PrettyWrapper { item, ctx: &ctx }).unwrap(); output } pub fn pretty_print(item: &T) -> String { pretty_print_with_config(item, &PrettyConfig::default()) } /// Wrapper for pretty printing with Display trait struct PrettyWrapper<'a, T: PrettyPrint> { item: &'a T, ctx: &'a PrettyContext, } impl<'a, T: PrettyPrint> fmt::Display for PrettyWrapper<'a, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.item.pretty_print(self.ctx, f) } } impl PrettyPrint for Expr { fn pretty_print(&self, ctx: &PrettyContext, f: &mut fmt::Formatter) -> fmt::Result { if ctx.should_truncate() { return write!(f, "{}...", ctx.indent()); } let indent = ctx.indent(); match self { Expr::Literal { value } => { if ctx.config.compact { write!(f, "{:?}", value) } else { write!(f, "{}Literal {{ value: {:?} }}", indent, value) } } Expr::Binary { left, operator, right, } => { if ctx.config.compact { // For compact, we need to format recursively let left_str = pretty_print_with_config(left.as_ref(), &PrettyConfig::compact()); let right_str = pretty_print_with_config(right.as_ref(), &PrettyConfig::compact()); write!(f, "({} {:?} {})", left_str, operator, right_str) } else { writeln!(f, "{}Binary {{", indent)?; writeln!( f, "{}operator: {:?},", ctx.child_context(false).indent(), operator )?; write!(f, "{}left: ", ctx.child_context(false).indent())?; left.pretty_print(&ctx.child_context(false), f)?; writeln!(f)?; write!(f, "{}right: ", ctx.child_context(true).indent())?; right.pretty_print(&ctx.child_context(true), f)?; writeln!(f)?; write!(f, "{}}}", indent) } } Expr::Unary { operator, operand } => { if ctx.config.compact { let operand_str = pretty_print_with_config(operand.as_ref(), &PrettyConfig::compact()); write!(f, "({:?} {})", operator, operand_str) } else { writeln!(f, "{}Unary {{", indent)?; writeln!( f, "{}operator: {:?},", ctx.child_context(false).indent(), operator )?; write!(f, "{}operand: ", ctx.child_context(true).indent())?; operand.pretty_print(&ctx.child_context(true), f)?; writeln!(f)?; write!(f, "{}}}", indent) } } Expr::Grouping { expression } => { if ctx.config.compact { let expr_str = pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact()); write!(f, "({})", expr_str) } else { writeln!(f, "{}Grouping {{", indent)?; write!(f, "{}expression: ", ctx.child_context(true).indent())?; expression.pretty_print(&ctx.child_context(true), f)?; writeln!(f)?; write!(f, "{}}}", indent) } } Expr::Identifier { name } => { if ctx.config.compact { write!(f, "{}", name) } else { write!(f, "{}Identifier {{ name: {:?} }}", indent, name) } } Expr::Assign { name, value } => { if ctx.config.compact { let value_str = pretty_print_with_config(value.as_ref(), &PrettyConfig::compact()); write!(f, "{} = {}", name, value_str) } else { writeln!(f, "{}Assign {{", indent)?; writeln!(f, "{}name: {:?},", ctx.child_context(false).indent(), name)?; write!(f, "{}value: ", ctx.child_context(true).indent())?; value.pretty_print(&ctx.child_context(true), f)?; writeln!(f)?; write!(f, "{}}}", indent) } } Expr::Call { callee, arguments } => { if ctx.config.compact { write!(f, "{}", callee) } else { write!(f, "{}Call {{ callee: ", indent)?; callee.pretty_print(&ctx.child_context(true), f)?; write!(f, ", arguments: [")?; for (i, arg) in arguments.iter().enumerate() { if i > 0 { write!(f, ", ")?; } arg.pretty_print(&ctx.child_context(true), f)?; } write!(f, "] }}") } } Expr::Print { expression } => { if ctx.config.compact { let expr_str = pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact()); write!(f, "print {};", expr_str) } else { writeln!(f, "{}Print {{", indent)?; write!(f, "{}expression: ", ctx.child_context(true).indent())?; expression.pretty_print(&ctx.child_context(true), f)?; writeln!(f)?; write!(f, "{}}}", indent) } } Expr::VarDeclaration { name, initializer, .. } => { if ctx.config.compact { match initializer { Some(init) => { let init_str = pretty_print_with_config(init.as_ref(), &PrettyConfig::compact()); write!(f, "var {} = {};", name, init_str) } None => write!(f, "var {};", name), } } else { writeln!(f, "{}Var {{", indent)?; writeln!(f, "{}name: {:?},", ctx.child_context(false).indent(), name)?; match initializer { Some(init) => { write!(f, "{}initializer: Some(", ctx.child_context(true).indent())?; init.pretty_print(&ctx.child_context(true), f)?; writeln!(f, "),")?; } None => { writeln!(f, "{}initializer: None,", ctx.child_context(true).indent())? } } write!(f, "{}}}", indent) } } Expr::Return { expression, .. } => { if ctx.config.compact { let expr_str = pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact()); write!(f, "return {};", expr_str) } else { writeln!(f, "{}Return {{", indent)?; write!(f, "{}expression: ", ctx.child_context(true).indent())?; expression.pretty_print(&ctx.child_context(true), f)?; writeln!(f)?; write!(f, "{}}}", indent) } } Expr::Block { statements, label } => { if ctx.config.compact { write!(f, "{{ ... [{}] ({} statements) }}", label, statements.len()) } else { writeln!(f, "{}Block [{}] {{", label, indent)?; writeln!(f, "{}statements: [", ctx.child_context(false).indent())?; for (i, stmt) in statements.iter().enumerate() { let is_last = i == statements.len() - 1; stmt.pretty_print(&ctx.child_context(false).child_context(is_last), f)?; if !is_last { writeln!(f, ",")?; } else { writeln!(f)?; } } writeln!(f, "{}]", ctx.child_context(false).indent())?; write!(f, "{}}}", indent) } } Expr::If { condition, then_branch, elif_branches, else_branch, } => { if ctx.config.compact { let cond_str = pretty_print_with_config(condition.as_ref(), &PrettyConfig::compact()); write!(f, "if {} {{ ... }}", cond_str) } else { writeln!(f, "{}If {{", indent)?; write!(f, "{}condition: ", ctx.child_context(false).indent())?; condition.pretty_print(&ctx.child_context(false), f)?; writeln!(f, ",")?; write!(f, "{}then_branch: ", ctx.child_context(false).indent())?; then_branch.pretty_print(&ctx.child_context(false), f)?; writeln!(f, ",")?; if !elif_branches.is_empty() { writeln!(f, "{}elif_branches: [", ctx.child_context(false).indent())?; for (i, (cond, branch)) in elif_branches.iter().enumerate() { let is_last = i == elif_branches.len() - 1; let child_ctx = ctx.child_context(false).child_context(is_last); writeln!(f, "{}(", child_ctx.indent())?; write!(f, "{}condition: ", child_ctx.child_context(false).indent())?; cond.pretty_print(&child_ctx.child_context(false), f)?; writeln!(f, ",")?; write!(f, "{}branch: ", child_ctx.child_context(true).indent())?; branch.pretty_print(&child_ctx.child_context(true), f)?; writeln!(f)?; write!(f, "{})", child_ctx.indent())?; if !is_last { writeln!(f, ",")?; } else { writeln!(f)?; } } writeln!(f, "{}],", ctx.child_context(false).indent())?; } match else_branch { Some(else_stmt) => { write!(f, "{}else_branch: Some(", ctx.child_context(true).indent())?; else_stmt.pretty_print(&ctx.child_context(true), f)?; writeln!(f, "),")?; } None => { writeln!(f, "{}else_branch: None,", ctx.child_context(true).indent())? } } write!(f, "{}}}", indent) } } Expr::While { condition, body } => { write!(f, "{}while (", ctx.child_context(true).indent())?; condition.pretty_print(&ctx.child_context(true), f)?; writeln!(f, ") {{")?; body.pretty_print(&ctx.child_context(true), f)?; writeln!(f, "{}}}", ctx.child_context(true).indent()) } Expr::For { variable, condition, increment, body, } => { write!(f, "{}for (", ctx.child_context(true).indent())?; variable.pretty_print(&ctx.child_context(true), f)?; write!(f, "; ")?; condition.pretty_print(&ctx.child_context(true), f)?; write!(f, "; ")?; increment.pretty_print(&ctx.child_context(true), f)?; writeln!(f, ") {{")?; body.pretty_print(&ctx.child_context(true), f)?; writeln!(f, "{}}}", ctx.child_context(true).indent()) } } } } impl PrettyPrint for AstNode { fn pretty_print(&self, ctx: &PrettyContext, f: &mut fmt::Formatter) -> fmt::Result { let indent = ctx.indent(); if ctx.config.compact { self.node.pretty_print(ctx, f) } else { writeln!(f, "{}AstNode {{", indent)?; if ctx.config.show_types { writeln!( f, "{}type: {},", ctx.child_context(false).indent(), self.return_type )?; } write!( f, "{}node: ", ctx.child_context(!ctx.config.show_positions).indent() )?; self.node .pretty_print(&ctx.child_context(!ctx.config.show_positions), f)?; writeln!(f, ",")?; if ctx.config.show_positions { writeln!( f, "{}source_slice: {:?},", ctx.child_context(true).indent(), self.source_slice )?; } write!(f, "{}}}", indent) } } } // Extension trait for convenience methods #[allow(dead_code)] pub trait PrettyPrintExt { fn pretty(&self) -> String; fn pretty_compact(&self) -> String; fn pretty_tree(&self) -> String; fn pretty_with_config(&self, config: &PrettyConfig) -> String; } impl PrettyPrintExt for Expr { fn pretty(&self) -> String { pretty_print(self) } fn pretty_compact(&self) -> String { pretty_print_with_config(self, &PrettyConfig::compact()) } fn pretty_tree(&self) -> String { pretty_print_with_config(self, &PrettyConfig::tree()) } fn pretty_with_config(&self, config: &PrettyConfig) -> String { pretty_print_with_config(self, config) } } impl PrettyPrintExt for AstNode { fn pretty(&self) -> String { pretty_print(self) } fn pretty_compact(&self) -> String { pretty_print_with_config(self, &PrettyConfig::compact()) } fn pretty_tree(&self) -> String { pretty_print_with_config(self, &PrettyConfig::tree()) } fn pretty_with_config(&self, config: &PrettyConfig) -> String { pretty_print_with_config(self, config) } }