use std::fmt::Write;
use crate::ast::{FnBody, FnDef, Stmt, TopLevel, TypeDef};
use crate::checker::expr_to_str;
use crate::ir::{AnalysisResult, BodyShape, FnAnalysis, ThinKind};
pub fn dump_items(items: &[TopLevel], analysis: Option<&AnalysisResult>) -> String {
let mut out = String::new();
let mut first = true;
for item in items {
if !first {
out.push('\n');
}
first = false;
dump_top_level(item, &mut out, analysis);
}
out
}
fn dump_top_level(item: &TopLevel, out: &mut String, analysis: Option<&AnalysisResult>) {
match item {
TopLevel::Module(m) => {
writeln!(out, "module {}", m.name).ok();
if !m.depends.is_empty() {
writeln!(out, " depends [{}]", m.depends.join(", ")).ok();
}
if !m.exposes.is_empty() {
writeln!(out, " exposes [{}]", m.exposes.join(", ")).ok();
}
if let Some(effects) = &m.effects {
writeln!(out, " effects [{}]", effects.join(", ")).ok();
}
}
TopLevel::TypeDef(td) => dump_typedef(td, out),
TopLevel::FnDef(fd) => {
let fn_analysis = analysis.and_then(|a| a.fn_analyses.get(&fd.name));
dump_fndef(fd, out, fn_analysis);
}
TopLevel::Stmt(s) => dump_stmt(s, 0, out),
TopLevel::Verify(vb) => {
writeln!(out, "verify {} <{} case(s)>", vb.fn_name, vb.cases.len()).ok();
}
TopLevel::Decision(_) => {
writeln!(out, "decision <block>").ok();
}
}
}
fn dump_typedef(td: &TypeDef, out: &mut String) {
match td {
TypeDef::Product { name, fields, .. } => {
let parts: Vec<String> = fields
.iter()
.map(|(n, t)| format!("{}: {}", n, t))
.collect();
writeln!(out, "type {} = {{ {} }}", name, parts.join(", ")).ok();
}
TypeDef::Sum { name, variants, .. } => {
let parts: Vec<String> = variants
.iter()
.map(|v| {
if v.fields.is_empty() {
v.name.clone()
} else {
format!("{}({})", v.name, v.fields.join(", "))
}
})
.collect();
writeln!(out, "type {} = {}", name, parts.join(" | ")).ok();
}
}
}
fn dump_fndef(fd: &FnDef, out: &mut String, fn_analysis: Option<&FnAnalysis>) {
let params: Vec<String> = fd
.params
.iter()
.map(|(n, t)| format!("{}: {}", n, t))
.collect();
let effects = if fd.effects.is_empty() {
String::new()
} else {
let names: Vec<String> = fd.effects.iter().map(|e| e.node.clone()).collect();
format!(" ! [{}]", names.join(", "))
};
let tag_str = match fn_analysis {
None => String::new(),
Some(a) => {
let mut tags: Vec<String> = Vec::new();
if matches!(a.allocates, Some(false)) {
tags.push("no_alloc".to_string());
}
if let Some(n) = a.local_count {
tags.push(format!("locals={}", n));
}
if a.mutual_tco_member {
tags.push("mutual-tco".to_string());
} else if a.recursive {
if a.recursive_call_count >= 2 {
tags.push(format!("recursive×{}", a.recursive_call_count));
} else {
tags.push("recursive".to_string());
}
}
tags.push(body_shape_tag(a.body_shape, a.thin_kind));
format!(" [{}]", tags.join(", "))
}
};
writeln!(
out,
"fn {}({}) -> {}{}{}",
fd.name,
params.join(", "),
fd.return_type,
effects,
tag_str
)
.ok();
let FnBody::Block(stmts) = fd.body.as_ref();
for stmt in stmts {
dump_stmt(stmt, 1, out);
}
}
fn body_shape_tag(shape: BodyShape, thin_kind: Option<ThinKind>) -> String {
let kind_suffix = thin_kind.map(thin_kind_label).unwrap_or("");
let kind_part = if kind_suffix.is_empty() {
String::new()
} else {
format!(" ({})", kind_suffix)
};
match shape {
BodyShape::LeafExpr => format!("body=leaf-expr{}", kind_part),
BodyShape::SingleExpr => format!("body=single-expr{}", kind_part),
BodyShape::Block(n) => format!("body=block:{}{}", n, kind_part),
BodyShape::Unclassified(1) => "body=single-expr".to_string(),
BodyShape::Unclassified(n) => format!("body=block:{}", n),
}
}
fn thin_kind_label(kind: ThinKind) -> &'static str {
match kind {
ThinKind::Leaf => "leaf",
ThinKind::Direct => "direct",
ThinKind::Forward => "forward",
ThinKind::Dispatch => "dispatch",
ThinKind::Tail => "tail",
}
}
fn dump_stmt(stmt: &Stmt, indent: usize, out: &mut String) {
let pad = " ".repeat(indent);
match stmt {
Stmt::Binding(name, ty, expr) => {
let ty_part = ty
.as_deref()
.map(|t| format!(": {}", t))
.unwrap_or_default();
writeln!(out, "{}{}{} = {}", pad, name, ty_part, expr_to_str(expr)).ok();
}
Stmt::Expr(expr) => {
writeln!(out, "{}{}", pad, expr_to_str(expr)).ok();
}
}
}