use lolli_core::Proof;
pub struct LatexRenderer {
pub include_preamble: bool,
pub short_labels: bool,
}
impl Default for LatexRenderer {
fn default() -> Self {
Self {
include_preamble: false,
short_labels: false,
}
}
}
impl LatexRenderer {
pub fn new() -> Self {
Self::default()
}
pub fn render(&self, proof: &Proof) -> String {
let mut lines = Vec::new();
if self.include_preamble {
lines.push(r"\usepackage{bussproofs}".to_string());
lines.push(r"\usepackage{amsmath}".to_string());
lines.push(r"\usepackage{amssymb}".to_string());
lines.push(String::new());
}
lines.push(r"\begin{prooftree}".to_string());
self.render_proof(proof, &mut lines);
lines.push(r"\end{prooftree}".to_string());
lines.join("\n")
}
pub fn render_document(&self, proof: &Proof) -> String {
let mut lines = Vec::new();
lines.push(r"\documentclass{article}".to_string());
lines.push(r"\usepackage{bussproofs}".to_string());
lines.push(r"\usepackage{amsmath}".to_string());
lines.push(r"\usepackage{amssymb}".to_string());
lines.push(String::new());
lines.push(r"\begin{document}".to_string());
lines.push(String::new());
lines.push(r"\begin{prooftree}".to_string());
self.render_proof(proof, &mut lines);
lines.push(r"\end{prooftree}".to_string());
lines.push(String::new());
lines.push(r"\end{document}".to_string());
lines.join("\n")
}
fn render_proof(&self, proof: &Proof, lines: &mut Vec<String>) {
for premise in &proof.premises {
self.render_proof(premise, lines);
}
let conclusion = self.format_sequent(proof);
let rule_label = self.format_rule(&proof.rule);
match proof.premises.len() {
0 => {
lines.push(format!(r" \AxiomC{{$\vdash {}$}}", conclusion));
}
1 => {
lines.push(format!(
r" \RightLabel{{\scriptsize {}}}",
rule_label
));
lines.push(format!(r" \UnaryInfC{{$\vdash {}$}}", conclusion));
}
2 => {
lines.push(format!(
r" \RightLabel{{\scriptsize {}}}",
rule_label
));
lines.push(format!(r" \BinaryInfC{{$\vdash {}$}}", conclusion));
}
3 => {
lines.push(format!(
r" \RightLabel{{\scriptsize {}}}",
rule_label
));
lines.push(format!(r" \TrinaryInfC{{$\vdash {}$}}", conclusion));
}
_ => {
lines.push(format!(
r" \RightLabel{{\scriptsize {}}}",
rule_label
));
lines.push(format!(r" \QuaternaryInfC{{$\vdash {}$}}", conclusion));
}
}
}
fn format_sequent(&self, proof: &Proof) -> String {
proof
.conclusion
.linear
.iter()
.map(|f| f.pretty_latex())
.collect::<Vec<_>>()
.join(", ")
}
fn format_rule(&self, rule: &lolli_core::Rule) -> String {
use lolli_core::Rule;
if self.short_labels {
match rule {
Rule::Axiom => "ax".to_string(),
Rule::Cut(_) => "cut".to_string(),
Rule::OneIntro => "$1$".to_string(),
Rule::BottomIntro => "$\\bot$".to_string(),
Rule::TopIntro => "$\\top$".to_string(),
Rule::TensorIntro => "$\\otimes$".to_string(),
Rule::ParIntro => "$\\parr$".to_string(),
Rule::WithIntro => "$\\with$".to_string(),
Rule::PlusIntroLeft => "$\\oplus_L$".to_string(),
Rule::PlusIntroRight => "$\\oplus_R$".to_string(),
Rule::OfCourseIntro => "$!$".to_string(),
Rule::WhyNotIntro => "$?$".to_string(),
Rule::Weakening => "W".to_string(),
Rule::Contraction => "C".to_string(),
Rule::Dereliction => "D".to_string(),
Rule::FocusPositive(_) => "F+".to_string(),
Rule::FocusNegative(_) => "F-".to_string(),
Rule::Blur => "B".to_string(),
}
} else {
match rule {
Rule::Axiom => "axiom".to_string(),
Rule::Cut(_) => "cut".to_string(),
Rule::OneIntro => "$1$-intro".to_string(),
Rule::BottomIntro => "$\\bot$-intro".to_string(),
Rule::TopIntro => "$\\top$-intro".to_string(),
Rule::TensorIntro => "$\\otimes$-intro".to_string(),
Rule::ParIntro => "$\\parr$-intro".to_string(),
Rule::WithIntro => "$\\with$-intro".to_string(),
Rule::PlusIntroLeft => "$\\oplus$-intro$_L$".to_string(),
Rule::PlusIntroRight => "$\\oplus$-intro$_R$".to_string(),
Rule::OfCourseIntro => "$!$-intro".to_string(),
Rule::WhyNotIntro => "$?$-intro".to_string(),
Rule::Weakening => "weakening".to_string(),
Rule::Contraction => "contraction".to_string(),
Rule::Dereliction => "dereliction".to_string(),
Rule::FocusPositive(_) => "focus+".to_string(),
Rule::FocusNegative(_) => "focus-".to_string(),
Rule::Blur => "blur".to_string(),
}
}
}
}
#[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 = LatexRenderer::new();
let output = renderer.render(&proof);
assert!(output.contains(r"\begin{prooftree}"));
assert!(output.contains(r"\AxiomC"));
assert!(output.contains(r"\end{prooftree}"));
}
#[test]
fn test_render_binary() {
let left = Proof {
conclusion: Sequent::new(vec![Formula::atom("A")]),
rule: Rule::Axiom,
premises: vec![],
};
let right = Proof {
conclusion: Sequent::new(vec![Formula::atom("B")]),
rule: Rule::Axiom,
premises: vec![],
};
let proof = Proof {
conclusion: Sequent::new(vec![Formula::tensor(Formula::atom("A"), Formula::atom("B"))]),
rule: Rule::TensorIntro,
premises: vec![left, right],
};
let renderer = LatexRenderer::new();
let output = renderer.render(&proof);
assert!(output.contains(r"\BinaryInfC"));
assert!(output.contains(r"\otimes"));
}
#[test]
fn test_with_preamble() {
let proof = Proof {
conclusion: Sequent::new(vec![Formula::atom("A")]),
rule: Rule::Axiom,
premises: vec![],
};
let mut renderer = LatexRenderer::new();
renderer.include_preamble = true;
let output = renderer.render(&proof);
assert!(output.contains(r"\usepackage{bussproofs}"));
}
#[test]
fn test_short_labels() {
let proof = Proof {
conclusion: Sequent::new(vec![Formula::One]),
rule: Rule::OneIntro,
premises: vec![],
};
let mut renderer = LatexRenderer::new();
renderer.short_labels = true;
let output = renderer.render(&proof);
assert!(!output.contains("-intro"));
}
#[test]
fn test_document() {
let proof = Proof {
conclusion: Sequent::new(vec![Formula::atom("A")]),
rule: Rule::Axiom,
premises: vec![],
};
let renderer = LatexRenderer::new();
let output = renderer.render_document(&proof);
assert!(output.contains(r"\documentclass"));
assert!(output.contains(r"\begin{document}"));
assert!(output.contains(r"\end{document}"));
}
}