use std::fmt::Write;
use crate::ast::{AspectOperator, BinaryTemporalOp, ModalDomain, QuantifierKind, TemporalOperator, Term, VoiceOperator};
use logicaffeine_base::Interner;
use crate::registry::SymbolRegistry;
use crate::token::TokenType;
pub trait LogicFormatter {
fn quantifier(&self, kind: &QuantifierKind, var: &str, body: &str) -> String {
let sym = match kind {
QuantifierKind::Universal => self.universal(),
QuantifierKind::Existential => self.existential(),
QuantifierKind::Most => "MOST ".to_string(),
QuantifierKind::Few => "FEW ".to_string(),
QuantifierKind::Many => "MANY ".to_string(),
QuantifierKind::Cardinal(n) => self.cardinal(*n),
QuantifierKind::AtLeast(n) => self.at_least(*n),
QuantifierKind::AtMost(n) => self.at_most(*n),
QuantifierKind::Generic => "Gen ".to_string(),
};
format!("{}{}({})", sym, var, body)
}
fn universal(&self) -> String;
fn existential(&self) -> String;
fn cardinal(&self, n: u32) -> String;
fn at_least(&self, n: u32) -> String;
fn at_most(&self, n: u32) -> String;
fn binary_op(&self, op: &TokenType, left: &str, right: &str) -> String {
match op {
TokenType::And => format!("({} {} {})", left, self.and(), right),
TokenType::Or => format!("({} {} {})", left, self.or(), right),
TokenType::If | TokenType::Implies => format!("({} {} {})", left, self.implies(), right),
TokenType::Iff => format!("({} {} {})", left, self.iff(), right),
_ => String::new(),
}
}
fn and(&self) -> &'static str;
fn or(&self) -> &'static str;
fn implies(&self) -> &'static str;
fn iff(&self) -> &'static str;
fn unary_op(&self, op: &TokenType, operand: &str) -> String {
match op {
TokenType::Not => format!("{}{}", self.not(), operand),
_ => String::new(),
}
}
fn not(&self) -> &'static str;
fn identity(&self) -> &'static str {
" = "
}
fn wrap_identity(&self) -> bool {
false
}
fn modal(&self, domain: ModalDomain, force: f32, body: &str) -> String {
let sym = match domain {
ModalDomain::Alethic if force <= 0.5 => self.possibility(),
ModalDomain::Alethic => self.necessity(),
ModalDomain::Deontic if force <= 0.5 => "P",
ModalDomain::Deontic => "O",
ModalDomain::Temporal => "Temporal",
};
format!("{}_{{{:.1}}} {}", sym, force, body)
}
fn necessity(&self) -> &'static str;
fn possibility(&self) -> &'static str;
fn temporal(&self, op: &TemporalOperator, body: &str) -> String {
let sym = match op {
TemporalOperator::Past => self.past(),
TemporalOperator::Future => self.future(),
TemporalOperator::Always => "G",
TemporalOperator::Eventually => "F",
TemporalOperator::BoundedEventually(n) => return format!("F≤{}({})", n, body),
TemporalOperator::Next => "X",
};
format!("{}({})", sym, body)
}
fn past(&self) -> &'static str;
fn future(&self) -> &'static str;
fn temporal_binary(&self, op: &BinaryTemporalOp, left: &str, right: &str) -> String {
let sym = match op {
BinaryTemporalOp::Until => "U",
BinaryTemporalOp::Release => "R",
BinaryTemporalOp::WeakUntil => "W",
};
format!("({} {} {})", left, sym, right)
}
fn aspectual(&self, op: &AspectOperator, body: &str) -> String {
let sym = match op {
AspectOperator::Progressive => self.progressive(),
AspectOperator::Perfect => self.perfect(),
AspectOperator::Habitual => self.habitual(),
AspectOperator::Iterative => self.iterative(),
};
format!("{}({})", sym, body)
}
fn progressive(&self) -> &'static str;
fn perfect(&self) -> &'static str;
fn habitual(&self) -> &'static str;
fn iterative(&self) -> &'static str;
fn voice(&self, op: &VoiceOperator, body: &str) -> String {
let sym = match op {
VoiceOperator::Passive => self.passive(),
};
format!("{}({})", sym, body)
}
fn passive(&self) -> &'static str;
fn lambda(&self, var: &str, body: &str) -> String;
fn counterfactual(&self, antecedent: &str, consequent: &str) -> String;
fn superlative(&self, comp: &str, domain: &str, subject: &str) -> String;
fn event_quantifier(&self, pred: &str, adverbs: &[String]) -> String {
if adverbs.is_empty() {
format!("{}e({})", self.existential(), pred)
} else {
let conj = self.and();
format!(
"{}e({} {} {})",
self.existential(),
pred,
conj,
adverbs.join(&format!(" {} ", conj))
)
}
}
fn categorical_all(&self) -> &'static str;
fn categorical_no(&self) -> &'static str;
fn categorical_some(&self) -> &'static str;
fn categorical_not(&self) -> &'static str;
fn sanitize(&self, s: &str) -> String {
s.to_string()
}
fn use_simple_events(&self) -> bool {
false
}
fn use_full_names(&self) -> bool {
false
}
fn preserve_case(&self) -> bool {
false
}
fn include_world_arguments(&self) -> bool {
false
}
fn write_comparative<W: Write>(
&self,
w: &mut W,
adjective: &str,
subject: &str,
object: &str,
difference: Option<&str>,
) -> std::fmt::Result {
if let Some(diff) = difference {
write!(w, "{}er({}, {}, {})", adjective, subject, object, diff)
} else {
write!(w, "{}er({}, {})", adjective, subject, object)
}
}
fn write_predicate<W: Write>(
&self,
w: &mut W,
name: &str,
args: &[Term],
registry: &mut SymbolRegistry,
interner: &Interner,
) -> std::fmt::Result {
write!(w, "{}(", self.sanitize(name))?;
for (i, arg) in args.iter().enumerate() {
if i > 0 {
write!(w, ", ")?;
}
if self.use_full_names() {
arg.write_to_full(w, registry, interner)?;
} else {
arg.write_to(w, registry, interner)?;
}
}
write!(w, ")")
}
}
pub struct UnicodeFormatter;
impl LogicFormatter for UnicodeFormatter {
fn universal(&self) -> String { "∀".to_string() }
fn existential(&self) -> String { "∃".to_string() }
fn cardinal(&self, n: u32) -> String { format!("∃={}.", n) }
fn at_least(&self, n: u32) -> String { format!("∃≥{}", n) }
fn at_most(&self, n: u32) -> String { format!("∃≤{}", n) }
fn and(&self) -> &'static str { "∧" }
fn or(&self) -> &'static str { "∨" }
fn implies(&self) -> &'static str { "→" }
fn iff(&self) -> &'static str { "↔" }
fn not(&self) -> &'static str { "¬" }
fn necessity(&self) -> &'static str { "□" }
fn possibility(&self) -> &'static str { "◇" }
fn past(&self) -> &'static str { "P" }
fn future(&self) -> &'static str { "F" }
fn progressive(&self) -> &'static str { "Prog" }
fn perfect(&self) -> &'static str { "Perf" }
fn habitual(&self) -> &'static str { "HAB" }
fn iterative(&self) -> &'static str { "ITER" }
fn passive(&self) -> &'static str { "Pass" }
fn lambda(&self, var: &str, body: &str) -> String {
format!("λ{}.{}", var, body)
}
fn counterfactual(&self, antecedent: &str, consequent: &str) -> String {
format!("({} □→ {})", antecedent, consequent)
}
fn superlative(&self, comp: &str, domain: &str, subject: &str) -> String {
format!(
"∀x(({}(x) ∧ x ≠ {}) → {}({}, x))",
domain, subject, comp, subject
)
}
fn categorical_all(&self) -> &'static str { "∀" }
fn categorical_no(&self) -> &'static str { "∀¬" }
fn categorical_some(&self) -> &'static str { "∃" }
fn categorical_not(&self) -> &'static str { "¬" }
fn use_full_names(&self) -> bool { true }
}
pub struct LatexFormatter;
impl LogicFormatter for LatexFormatter {
fn universal(&self) -> String { "\\forall ".to_string() }
fn existential(&self) -> String { "\\exists ".to_string() }
fn cardinal(&self, n: u32) -> String { format!("\\exists_{{={}}} ", n) }
fn at_least(&self, n: u32) -> String { format!("\\exists_{{\\geq {}}} ", n) }
fn at_most(&self, n: u32) -> String { format!("\\exists_{{\\leq {}}} ", n) }
fn and(&self) -> &'static str { "\\cdot" }
fn or(&self) -> &'static str { "\\vee" }
fn implies(&self) -> &'static str { "\\supset" }
fn iff(&self) -> &'static str { "\\equiv" }
fn not(&self) -> &'static str { "\\sim " }
fn necessity(&self) -> &'static str { "\\Box" }
fn possibility(&self) -> &'static str { "\\Diamond" }
fn past(&self) -> &'static str { "\\mathsf{P}" }
fn future(&self) -> &'static str { "\\mathsf{F}" }
fn progressive(&self) -> &'static str { "\\mathsf{Prog}" }
fn perfect(&self) -> &'static str { "\\mathsf{Perf}" }
fn habitual(&self) -> &'static str { "\\mathsf{HAB}" }
fn iterative(&self) -> &'static str { "\\mathsf{ITER}" }
fn passive(&self) -> &'static str { "\\mathsf{Pass}" }
fn lambda(&self, var: &str, body: &str) -> String {
format!("\\lambda {}.{}", var, body)
}
fn counterfactual(&self, antecedent: &str, consequent: &str) -> String {
format!("({} \\boxright {})", antecedent, consequent)
}
fn superlative(&self, comp: &str, domain: &str, subject: &str) -> String {
format!(
"\\forall x(({}(x) \\land x \\neq {}) \\supset {}({}, x))",
domain, subject, comp, subject
)
}
fn categorical_all(&self) -> &'static str { "All" }
fn categorical_no(&self) -> &'static str { "No" }
fn categorical_some(&self) -> &'static str { "Some" }
fn categorical_not(&self) -> &'static str { "not" }
fn sanitize(&self, s: &str) -> String {
s.replace('_', r"\_")
.replace('^', r"\^{}")
.replace('&', r"\&")
.replace('%', r"\%")
.replace('#', r"\#")
.replace('$', r"\$")
}
}
pub struct SimpleFOLFormatter;
impl LogicFormatter for SimpleFOLFormatter {
fn universal(&self) -> String { "∀".to_string() }
fn existential(&self) -> String { "∃".to_string() }
fn cardinal(&self, n: u32) -> String { format!("∃={}", n) }
fn at_least(&self, n: u32) -> String { format!("∃≥{}", n) }
fn at_most(&self, n: u32) -> String { format!("∃≤{}", n) }
fn and(&self) -> &'static str { "∧" }
fn or(&self) -> &'static str { "∨" }
fn implies(&self) -> &'static str { "→" }
fn iff(&self) -> &'static str { "↔" }
fn not(&self) -> &'static str { "¬" }
fn necessity(&self) -> &'static str { "□" }
fn possibility(&self) -> &'static str { "◇" }
fn past(&self) -> &'static str { "Past" }
fn future(&self) -> &'static str { "Future" }
fn progressive(&self) -> &'static str { "" }
fn perfect(&self) -> &'static str { "" }
fn habitual(&self) -> &'static str { "" }
fn iterative(&self) -> &'static str { "" }
fn passive(&self) -> &'static str { "" }
fn lambda(&self, var: &str, body: &str) -> String {
format!("λ{}.{}", var, body)
}
fn counterfactual(&self, antecedent: &str, consequent: &str) -> String {
format!("({} □→ {})", antecedent, consequent)
}
fn superlative(&self, comp: &str, domain: &str, subject: &str) -> String {
format!(
"∀x(({}(x) ∧ x ≠ {}) → {}({}, x))",
domain, subject, comp, subject
)
}
fn categorical_all(&self) -> &'static str { "∀" }
fn categorical_no(&self) -> &'static str { "∀¬" }
fn categorical_some(&self) -> &'static str { "∃" }
fn categorical_not(&self) -> &'static str { "¬" }
fn modal(&self, _domain: ModalDomain, _force: f32, body: &str) -> String {
body.to_string()
}
fn aspectual(&self, _op: &AspectOperator, body: &str) -> String {
body.to_string()
}
fn use_simple_events(&self) -> bool {
true
}
fn use_full_names(&self) -> bool {
true
}
}
pub struct KripkeFormatter;
impl LogicFormatter for KripkeFormatter {
fn universal(&self) -> String { "ForAll ".to_string() }
fn existential(&self) -> String { "Exists ".to_string() }
fn cardinal(&self, n: u32) -> String { format!("Exists={} ", n) }
fn at_least(&self, n: u32) -> String { format!("Exists>={} ", n) }
fn at_most(&self, n: u32) -> String { format!("Exists<={} ", n) }
fn and(&self) -> &'static str { " And " }
fn or(&self) -> &'static str { " Or " }
fn implies(&self) -> &'static str { " Implies " }
fn iff(&self) -> &'static str { " Iff " }
fn not(&self) -> &'static str { "Not " }
fn necessity(&self) -> &'static str { "Box" }
fn possibility(&self) -> &'static str { "Diamond" }
fn past(&self) -> &'static str { "Past" }
fn future(&self) -> &'static str { "Future" }
fn progressive(&self) -> &'static str { "Prog" }
fn perfect(&self) -> &'static str { "Perf" }
fn habitual(&self) -> &'static str { "HAB" }
fn iterative(&self) -> &'static str { "ITER" }
fn passive(&self) -> &'static str { "Pass" }
fn lambda(&self, var: &str, body: &str) -> String {
format!("Lambda {}.{}", var, body)
}
fn counterfactual(&self, antecedent: &str, consequent: &str) -> String {
format!("({} Counterfactual {})", antecedent, consequent)
}
fn superlative(&self, comp: &str, domain: &str, subject: &str) -> String {
format!(
"ForAll x(({}(x) And x != {}) Implies {}({}, x))",
domain, subject, comp, subject
)
}
fn categorical_all(&self) -> &'static str { "ForAll" }
fn categorical_no(&self) -> &'static str { "ForAll Not" }
fn categorical_some(&self) -> &'static str { "Exists" }
fn categorical_not(&self) -> &'static str { "Not" }
fn modal(&self, _domain: ModalDomain, _force: f32, body: &str) -> String {
body.to_string()
}
fn use_full_names(&self) -> bool { true }
fn include_world_arguments(&self) -> bool { true }
}
pub struct RustFormatter;
impl LogicFormatter for RustFormatter {
fn and(&self) -> &'static str { "&&" }
fn or(&self) -> &'static str { "||" }
fn not(&self) -> &'static str { "!" }
fn implies(&self) -> &'static str { "||" } fn iff(&self) -> &'static str { "==" }
fn identity(&self) -> &'static str { " == " } fn wrap_identity(&self) -> bool { true }
fn use_full_names(&self) -> bool { true }
fn preserve_case(&self) -> bool { true }
fn universal(&self) -> String { "/* ∀ */".to_string() }
fn existential(&self) -> String { "/* ∃ */".to_string() }
fn cardinal(&self, n: u32) -> String { format!("/* ∃={} */", n) }
fn at_least(&self, n: u32) -> String { format!("/* ∃≥{} */", n) }
fn at_most(&self, n: u32) -> String { format!("/* ∃≤{} */", n) }
fn necessity(&self) -> &'static str { "" }
fn possibility(&self) -> &'static str { "" }
fn past(&self) -> &'static str { "" }
fn future(&self) -> &'static str { "" }
fn progressive(&self) -> &'static str { "" }
fn perfect(&self) -> &'static str { "" }
fn habitual(&self) -> &'static str { "" }
fn iterative(&self) -> &'static str { "" }
fn passive(&self) -> &'static str { "" }
fn categorical_all(&self) -> &'static str { "" }
fn categorical_no(&self) -> &'static str { "" }
fn categorical_some(&self) -> &'static str { "" }
fn categorical_not(&self) -> &'static str { "" }
fn lambda(&self, var: &str, body: &str) -> String {
format!("|{}| {{ {} }}", var, body)
}
fn counterfactual(&self, a: &str, c: &str) -> String {
format!("/* if {} then {} */", a, c)
}
fn superlative(&self, _: &str, _: &str, _: &str) -> String {
"/* superlative */".to_string()
}
fn write_comparative<W: Write>(
&self,
w: &mut W,
adjective: &str,
subject: &str,
object: &str,
_difference: Option<&str>,
) -> std::fmt::Result {
let adj_lower = adjective.to_lowercase();
match adj_lower.as_str() {
"great" | "big" | "large" | "tall" | "old" | "high" | "more" | "greater" => {
write!(w, "({} > {})", subject, object)
}
"small" | "little" | "short" | "young" | "low" | "less" | "fewer" => {
write!(w, "({} < {})", subject, object)
}
_ => write!(w, "({} > {})", subject, object) }
}
fn unary_op(&self, op: &TokenType, operand: &str) -> String {
match op {
TokenType::Not => format!("(!{})", operand),
_ => format!("/* unknown unary */({})", operand),
}
}
fn binary_op(&self, op: &TokenType, left: &str, right: &str) -> String {
match op {
TokenType::If | TokenType::Implies | TokenType::Then => format!("(!({}) || ({}))", left, right),
TokenType::And => format!("({} && {})", left, right),
TokenType::Or => format!("({} || {})", left, right),
TokenType::Iff => format!("({} == {})", left, right),
_ => "/* unknown op */".to_string(),
}
}
fn write_predicate<W: Write>(
&self,
w: &mut W,
name: &str,
args: &[Term],
_registry: &mut SymbolRegistry,
interner: &Interner,
) -> std::fmt::Result {
let render = |idx: usize| -> String {
let mut buf = String::new();
if let Some(arg) = args.get(idx) {
let _ = arg.write_to_raw(&mut buf, interner);
}
buf
};
match name.to_lowercase().as_str() {
"greater" if args.len() == 2 => write!(w, "({} > {})", render(0), render(1)),
"less" if args.len() == 2 => write!(w, "({} < {})", render(0), render(1)),
"equal" | "equals" if args.len() == 2 => write!(w, "({} == {})", render(0), render(1)),
"notequal" | "not_equal" if args.len() == 2 => write!(w, "({} != {})", render(0), render(1)),
"greaterequal" | "atleast" | "at_least" if args.len() == 2 => write!(w, "({} >= {})", render(0), render(1)),
"lessequal" | "atmost" | "at_most" if args.len() == 2 => write!(w, "({} <= {})", render(0), render(1)),
"positive" if args.len() == 1 => write!(w, "({} > 0)", render(0)),
"negative" if args.len() == 1 => write!(w, "({} < 0)", render(0)),
"zero" if args.len() == 1 => write!(w, "({} == 0)", render(0)),
"empty" if args.len() == 1 => write!(w, "{}.is_empty()", render(0)),
"in" if args.len() == 2 => write!(w, "{}.contains(&{})", render(1), render(0)),
"contains" if args.len() == 2 => write!(w, "{}.contains(&{})", render(0), render(1)),
_ if args.len() == 1 => write!(w, "{}.is_{}()", render(0), name.to_lowercase()),
_ => {
write!(w, "{}(", name.to_lowercase())?;
for i in 0..args.len() {
if i > 0 { write!(w, ", ")?; }
write!(w, "{}", render(i))?;
}
write!(w, ")")
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unicode_binary_operators() {
let f = UnicodeFormatter;
assert_eq!(f.binary_op(&TokenType::And, "P", "Q"), "(P ∧ Q)");
assert_eq!(f.binary_op(&TokenType::Or, "P", "Q"), "(P ∨ Q)");
assert_eq!(f.binary_op(&TokenType::If, "P", "Q"), "(P → Q)");
assert_eq!(f.binary_op(&TokenType::Iff, "P", "Q"), "(P ↔ Q)");
}
#[test]
fn latex_binary_operators() {
let f = LatexFormatter;
assert_eq!(f.binary_op(&TokenType::And, "P", "Q"), r"(P \cdot Q)");
assert_eq!(f.binary_op(&TokenType::Or, "P", "Q"), r"(P \vee Q)");
assert_eq!(f.binary_op(&TokenType::If, "P", "Q"), r"(P \supset Q)");
assert_eq!(f.binary_op(&TokenType::Iff, "P", "Q"), r"(P \equiv Q)");
}
#[test]
fn unicode_quantifiers() {
let f = UnicodeFormatter;
assert_eq!(f.quantifier(&QuantifierKind::Universal, "x", "P(x)"), "∀x(P(x))");
assert_eq!(f.quantifier(&QuantifierKind::Existential, "x", "P(x)"), "∃x(P(x))");
assert_eq!(f.quantifier(&QuantifierKind::Cardinal(3), "x", "P(x)"), "∃=3.x(P(x))");
}
#[test]
fn latex_quantifiers() {
let f = LatexFormatter;
assert_eq!(f.quantifier(&QuantifierKind::Universal, "x", "P(x)"), "\\forall x(P(x))");
assert_eq!(f.quantifier(&QuantifierKind::Existential, "x", "P(x)"), "\\exists x(P(x))");
}
#[test]
fn latex_sanitization() {
let f = LatexFormatter;
assert_eq!(f.sanitize("foo_bar"), r"foo\_bar");
assert_eq!(f.sanitize("x^2"), r"x\^{}2");
assert_eq!(f.sanitize("a&b"), r"a\&b");
}
#[test]
fn unicode_no_sanitization() {
let f = UnicodeFormatter;
assert_eq!(f.sanitize("foo_bar"), "foo_bar");
}
#[test]
fn unicode_lambda() {
let f = UnicodeFormatter;
assert_eq!(f.lambda("x", "P(x)"), "λx.P(x)");
}
#[test]
fn latex_lambda() {
let f = LatexFormatter;
assert_eq!(f.lambda("x", "P(x)"), "\\lambda x.P(x)");
}
#[test]
fn unicode_counterfactual() {
let f = UnicodeFormatter;
assert_eq!(f.counterfactual("P", "Q"), "(P □→ Q)");
}
#[test]
fn latex_counterfactual() {
let f = LatexFormatter;
assert_eq!(f.counterfactual("P", "Q"), r"(P \boxright Q)");
}
#[test]
fn rust_binary_operators() {
let f = RustFormatter;
assert_eq!(f.binary_op(&TokenType::And, "P", "Q"), "(P && Q)");
assert_eq!(f.binary_op(&TokenType::Or, "P", "Q"), "(P || Q)");
assert_eq!(f.binary_op(&TokenType::Iff, "P", "Q"), "(P == Q)");
}
#[test]
fn rust_implication_desugaring() {
let f = RustFormatter;
assert_eq!(f.binary_op(&TokenType::If, "P", "Q"), "(!(P) || (Q))");
}
#[test]
fn rust_lambda() {
let f = RustFormatter;
assert_eq!(f.lambda("x", "x > 0"), "|x| { x > 0 }");
}
#[test]
fn rust_quantifiers_as_comments() {
let f = RustFormatter;
assert_eq!(f.universal(), "/* ∀ */");
assert_eq!(f.existential(), "/* ∃ */");
}
}