use crate::ast::nodes::Node; use crate::ast::compiler::bound_nodes::BoundKind; 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, T>) -> 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, T>) { self.write_indent(); self.output.push_str(label); self.output.push_str(&format!(" \n", node.ty)); } fn visit(&mut self, node: &Node, T>) { 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::Set { addr, value } => { self.log(&format!("Set: {:?}", addr), node); self.indent += 1; self.visit(value); self.indent -= 1; } BoundKind::DefLocal { name, slot, value, captured_by } => { let capture_info = if captured_by.is_empty() { String::from("not captured") } else { format!("captured by {} lambdas", captured_by.len()) }; self.log(&format!("DefLocal (Name: '{}', Slot: {}, {})", name.name, slot, capture_info), node); self.indent += 1; if !captured_by.is_empty() { for capturer in captured_by { self.write_indent(); self.output.push_str(&format!("- Capturer: Lambda at line {}, col {}\n", capturer.location.line, capturer.location.col)); } } self.visit(value); self.indent -= 1; } BoundKind::DefGlobal { name, global_index, value } => { self.log(&format!("DefGlobal (Name: '{}', Index: {})", name.name, global_index), node); self.indent += 1; 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::Parameter { name, slot } => { self.log(&format!("Parameter (Name: '{}', Slot: {})", name.name, slot), node); } 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::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 { fields } => { self.log("Record", node); self.indent += 1; for (k, v) in fields { self.visit(k); 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); } } } }