use crate::ast::compiler::bound_nodes::{BoundKind, Node}; use crate::ast::types::Value; use crate::ast::vm::Closure; use std::fmt::Debug; /// Human-readable AST dumper for the bound AST. pub struct Dumper { output: String, indent: usize, } impl Dumper { /// Produces a formatted string representation of the given bound AST node and its children. pub fn dump(node: &Node) -> String { let mut dumper = Self { output: String::new(), indent: 0, }; dumper.visit(node); dumper.output } fn write_indent(&mut self) { for _ in 0..self.indent { self.output.push_str(" "); } } fn log(&mut self, label: &str, node: &Node) { self.write_indent(); self.output.push_str(label); self.output .push_str(&format!(" \n", node.ty)); } fn visit(&mut self, node: &Node) { match &node.kind { BoundKind::Nop => self.log("Nop", node), BoundKind::Constant(v) => { self.log(&format!("Constant: {}", v), node); // Introspect Closure AST if possible if let Value::Object(obj) = v && let Some(closure) = obj.as_any().downcast_ref::() { self.indent += 1; self.write_indent(); self.output.push_str("--- Specialized Body ---\n"); // We need to cast the inner TypedNode to the generic T required by visit. // Since Dumper is generic over T, but Closure stores TypedNode (where T = StaticType), // we can only fully dump if T is StaticType. // However, we can hack it by creating a new Dumper for the inner AST string. // We can't call self.visit because types mismatch if T != StaticType. // So we just recursively dump to string and append. let inner_dump = Dumper::dump(&closure.function_node); for line in inner_dump.lines() { self.write_indent(); self.output.push_str(line); self.output.push('\n'); } self.indent -= 1; } } BoundKind::Get { addr, name } => { self.log(&format!("Get: {} ({:?})", name.name, addr), node) } BoundKind::FieldAccessor(k) => self.log(&format!("FieldAccessor: .{}", k.name()), node), BoundKind::GetField { rec, field } => { self.log(&format!("GetField: .{}", field.name()), node); self.indent += 1; self.visit(rec); self.indent -= 1; } BoundKind::Set { addr, value } => { self.log(&format!("Set: {:?}", addr), node); self.indent += 1; self.visit(value); self.indent -= 1; } BoundKind::Define { name, addr, kind, value, captured_by, } => { let k_str = match kind { crate::ast::compiler::bound_nodes::DeclarationKind::Variable => "Variable", crate::ast::compiler::bound_nodes::DeclarationKind::Parameter => "Parameter", }; let capture_info = if captured_by.is_empty() { String::from("not captured") } else { format!("captured by {} lambdas", captured_by.len()) }; self.log( &format!( "Define {} (Name: '{}', Address: {:?}, {})", k_str, name.name, addr, capture_info ), node, ); self.indent += 1; if !captured_by.is_empty() { for capturer in captured_by { self.write_indent(); let loc = capturer .location .unwrap_or(crate::ast::types::SourceLocation { line: 0, col: 0 }); self.output.push_str(&format!( "- Capturer: Lambda at line {}, col {}\n", loc.line, loc.col )); } } self.visit(value); self.indent -= 1; } BoundKind::Destructure { pattern, value } => { self.log("Destructure", node); self.indent += 1; self.write_indent(); self.output.push_str("Pattern:\n"); self.visit(pattern); self.write_indent(); self.output.push_str("Value:\n"); self.visit(value); self.indent -= 1; } BoundKind::If { cond, then_br, else_br, } => { self.log("If", node); self.indent += 1; self.write_indent(); self.output.push_str("Condition:\n"); self.visit(cond); self.write_indent(); self.output.push_str("Then:\n"); self.visit(then_br); if let Some(e) = else_br { self.write_indent(); self.output.push_str("Else:\n"); self.visit(e); } self.indent -= 1; } BoundKind::Lambda { params, upvalues, body, .. } => { self.log(&format!("Lambda (Upvalues: {})", upvalues.len()), node); self.indent += 1; self.write_indent(); self.output.push_str("Parameters:\n"); self.visit(params); if !upvalues.is_empty() { self.write_indent(); self.output.push_str(&format!("Upvalues: {:?}\n", upvalues)); } self.visit(body); self.indent -= 1; } BoundKind::Call { callee, args } => { self.log("Call", node); self.indent += 1; self.write_indent(); self.output.push_str("Callee:\n"); self.visit(callee); self.write_indent(); self.output.push_str("Arguments:\n"); self.visit(args); self.indent -= 1; } BoundKind::Again { args } => { self.log("Again", node); self.indent += 1; self.visit(args); self.indent -= 1; } BoundKind::Pipe { inputs, lambda, .. } => { self.log("Pipe", node); self.indent += 1; for input in inputs { self.visit(input); } self.visit(lambda); self.indent -= 1; } BoundKind::Block { exprs } => { self.log("Block", node); self.indent += 1; for expr in exprs { self.visit(expr); } self.indent -= 1; } BoundKind::Tuple { elements } => { self.log("Tuple", node); self.indent += 1; for el in elements { self.visit(el); } self.indent -= 1; } BoundKind::Record { layout, values } => { self.log( &format!("Record (Layout: {} fields)", layout.fields.len()), node, ); self.indent += 1; for v in values { self.visit(v); } self.indent -= 1; } BoundKind::Expansion { original_call, bound_expanded, } => { self.log( &format!("Expansion (Original: {:?})", original_call.kind), node, ); self.indent += 1; self.visit(bound_expanded); self.indent -= 1; } BoundKind::Extension(ext) => { self.log(&ext.display_name(), node); } BoundKind::Error => { self.log("ERROR_NODE", node); } } } }