use crate::{TraceEdgeKind, TraceResult, TraceStep, TraceStepKind};
#[must_use]
pub fn to_text(trace: &TraceResult) -> String {
use std::fmt::Write as _;
let mut out = String::new();
let _ = writeln!(
out,
"Trace: {}\nLanguage: {}\nPrecision: {:?}\nPaths explored: {}\nTruncated paths: {}\n",
trace
.query
.entry_symbol
.as_deref()
.or(trace.query.target_symbol.as_deref())
.unwrap_or("<unknown>"),
trace.summary.language,
trace.summary.precision,
trace.summary.explored_paths,
trace.summary.truncated_paths,
);
if !trace.summary.analysis_complete {
let mut reasons = trace.summary.truncation_reasons.clone();
reasons.extend(trace.summary.analysis_incomplete_reasons.iter().cloned());
reasons.sort();
reasons.dedup();
let reasons = crate::summarize_incomplete_reasons(&reasons, 8);
let _ = writeln!(out, "Analysis incomplete: {reasons}\n");
}
for path in &trace.paths {
let _ = writeln!(out, "Path {}", path.path_id);
for step in trace.steps.iter().filter(|s| s.path_id == path.path_id) {
write_step(&mut out, step);
}
let _ = writeln!(out, " terminated_by: {:?}", path.terminated_by);
let _ = writeln!(out);
}
out
}
fn write_step(out: &mut String, step: &TraceStep) {
use std::fmt::Write as _;
let label = if step.precision.is_semantic() {
human_label(step.kind, &step.message, &step.function)
} else {
"Suppressed diagnostic-precision trace step".to_string()
};
let _ = writeln!(out, " [{}] {}", step.order, label);
let _ = writeln!(
out,
" at {}:{}:{}-{}:{}",
step.span.file, step.span.start_line, step.span.start_col, step.span.end_line, step.span.end_col,
);
if !step.notes.is_empty() {
let _ = writeln!(out, " notes: {}", step.notes.join("; "));
}
}
fn human_label(kind: TraceStepKind, message: &str, function: &str) -> String {
if !message.is_empty() {
return message.to_string();
}
match kind {
TraceStepKind::EnterFunction => format!("Enter function {function}"),
TraceStepKind::ExitFunction => format!("Exit function {function}"),
TraceStepKind::Return => format!("Return from {function}"),
TraceStepKind::BranchSplit => "Branch split".to_string(),
other => format!("{other:?}"),
}
}
#[must_use]
pub fn to_dot(trace: &TraceResult) -> String {
use std::fmt::Write as _;
let mut out =
String::from("digraph trace {\n rankdir=LR;\n node [shape=box, fontname=\"Helvetica\"];\n");
for step in &trace.steps {
let label = format!("[{}] {:?}\\n{}", step.order, step.kind, step.function);
let _ = writeln!(out, " s{} [label=\"{}\"];", step.id, label.replace('"', "\\\""));
}
for edge in &trace.edges {
let style = match edge.kind {
TraceEdgeKind::BranchTrue => "color=green",
TraceEdgeKind::BranchFalse => "color=red",
TraceEdgeKind::CallEnter => "color=blue",
TraceEdgeKind::ReturnToCaller => "color=navy",
TraceEdgeKind::ThrowToHandler => "color=purple,style=dashed",
TraceEdgeKind::Merge => "color=gray",
TraceEdgeKind::Next => "color=black",
};
let _ = writeln!(out, " s{} -> s{} [{}];", edge.from_step, edge.to_step, style);
}
out.push_str("}\n");
out
}
pub fn to_json(trace: &TraceResult) -> serde_json::Result<String> {
serde_json::to_string_pretty(trace)
}
#[cfg(test)]
#[path = "render_tests.rs"]
mod tests;