use lolli_core::Proof;
pub struct TreeRenderer {
pub unicode: bool,
pub show_rules: bool,
pub indent_width: usize,
}
impl Default for TreeRenderer {
fn default() -> Self {
Self {
unicode: true,
show_rules: true,
indent_width: 2,
}
}
}
impl TreeRenderer {
pub fn new() -> Self {
Self::default()
}
pub fn render(&self, proof: &Proof) -> String {
let mut lines = Vec::new();
self.render_proof(proof, 0, &mut lines);
lines.join("\n")
}
fn render_proof(&self, proof: &Proof, indent: usize, lines: &mut Vec<String>) {
let prefix = " ".repeat(indent * self.indent_width);
for premise in &proof.premises {
self.render_proof(premise, indent + 1, lines);
}
let conclusion = self.format_sequent(proof);
let rule_name = format!("{:?}", proof.rule);
if !proof.premises.is_empty() {
let line_char = if self.unicode { '─' } else { '-' };
let line_width = conclusion.len().max(20);
let line = line_char.to_string().repeat(line_width);
if self.show_rules {
lines.push(format!("{}{} {}", prefix, line, rule_name));
} else {
lines.push(format!("{}{}", prefix, line));
}
} else if self.show_rules {
lines.push(format!("{}⊢ {} ({})", prefix, conclusion, rule_name));
return;
}
lines.push(format!("{}⊢ {}", prefix, conclusion));
}
fn format_sequent(&self, proof: &Proof) -> String {
proof
.conclusion
.linear
.iter()
.map(|f| {
if self.unicode {
f.pretty()
} else {
f.pretty_ascii()
}
})
.collect::<Vec<_>>()
.join(", ")
}
}
#[cfg(test)]
mod tests {
use super::*;
use lolli_core::{Formula, Rule, Sequent};
#[test]
fn test_render_axiom() {
let proof = Proof {
conclusion: Sequent::new(vec![Formula::neg_atom("A"), Formula::atom("A")]),
rule: Rule::Axiom,
premises: vec![],
};
let renderer = TreeRenderer::new();
let output = renderer.render(&proof);
assert!(output.contains("A⊥"));
assert!(output.contains("Axiom"));
}
#[test]
fn test_render_tensor() {
let left = Proof {
conclusion: Sequent::new(vec![Formula::neg_atom("A"), Formula::atom("A")]),
rule: Rule::Axiom,
premises: vec![],
};
let right = Proof {
conclusion: Sequent::new(vec![Formula::neg_atom("B"), Formula::atom("B")]),
rule: Rule::Axiom,
premises: vec![],
};
let proof = Proof {
conclusion: Sequent::new(vec![
Formula::neg_atom("A"),
Formula::neg_atom("B"),
Formula::tensor(Formula::atom("A"), Formula::atom("B")),
]),
rule: Rule::TensorIntro,
premises: vec![left, right],
};
let renderer = TreeRenderer::new();
let output = renderer.render(&proof);
assert!(output.contains("TensorIntro"));
assert!(output.contains("─")); }
#[test]
fn test_ascii_mode() {
let proof = Proof {
conclusion: Sequent::new(vec![Formula::neg_atom("A"), Formula::atom("A")]),
rule: Rule::Axiom,
premises: vec![],
};
let mut renderer = TreeRenderer::new();
renderer.unicode = false;
let output = renderer.render(&proof);
assert!(output.contains("A^")); }
#[test]
fn test_no_rules() {
let proof = Proof {
conclusion: Sequent::new(vec![Formula::atom("A")]),
rule: Rule::Axiom,
premises: vec![],
};
let mut renderer = TreeRenderer::new();
renderer.show_rules = false;
let output = renderer.render(&proof);
assert!(!output.contains("Axiom"));
}
}