#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Formula {
Atom(String),
NegAtom(String),
Tensor(Box<Formula>, Box<Formula>),
Par(Box<Formula>, Box<Formula>),
One,
Bottom,
With(Box<Formula>, Box<Formula>),
Plus(Box<Formula>, Box<Formula>),
Top,
Zero,
OfCourse(Box<Formula>),
WhyNot(Box<Formula>),
Lolli(Box<Formula>, Box<Formula>),
}
impl Formula {
pub fn negate(&self) -> Formula {
match self {
Formula::Atom(a) => Formula::NegAtom(a.clone()),
Formula::NegAtom(a) => Formula::Atom(a.clone()),
Formula::Tensor(a, b) => {
Formula::Par(Box::new(a.negate()), Box::new(b.negate()))
}
Formula::Par(a, b) => {
Formula::Tensor(Box::new(a.negate()), Box::new(b.negate()))
}
Formula::One => Formula::Bottom,
Formula::Bottom => Formula::One,
Formula::With(a, b) => {
Formula::Plus(Box::new(a.negate()), Box::new(b.negate()))
}
Formula::Plus(a, b) => {
Formula::With(Box::new(a.negate()), Box::new(b.negate()))
}
Formula::Top => Formula::Zero,
Formula::Zero => Formula::Top,
Formula::OfCourse(a) => Formula::WhyNot(Box::new(a.negate())),
Formula::WhyNot(a) => Formula::OfCourse(Box::new(a.negate())),
Formula::Lolli(a, b) => {
Formula::Tensor(a.clone(), Box::new(b.negate()))
}
}
}
pub fn desugar(&self) -> Formula {
match self {
Formula::Lolli(a, b) => {
Formula::Par(Box::new(a.negate().desugar()), Box::new(b.desugar()))
}
Formula::Tensor(a, b) => {
Formula::Tensor(Box::new(a.desugar()), Box::new(b.desugar()))
}
Formula::Par(a, b) => {
Formula::Par(Box::new(a.desugar()), Box::new(b.desugar()))
}
Formula::With(a, b) => {
Formula::With(Box::new(a.desugar()), Box::new(b.desugar()))
}
Formula::Plus(a, b) => {
Formula::Plus(Box::new(a.desugar()), Box::new(b.desugar()))
}
Formula::OfCourse(a) => Formula::OfCourse(Box::new(a.desugar())),
Formula::WhyNot(a) => Formula::WhyNot(Box::new(a.desugar())),
_ => self.clone(),
}
}
pub fn is_positive(&self) -> bool {
matches!(
self,
Formula::Atom(_)
| Formula::Tensor(_, _)
| Formula::One
| Formula::Plus(_, _)
| Formula::Zero
| Formula::OfCourse(_)
)
}
pub fn is_negative(&self) -> bool {
!self.is_positive()
}
pub fn pretty(&self) -> String {
match self {
Formula::Atom(a) => a.clone(),
Formula::NegAtom(a) => format!("{}⊥", a),
Formula::Tensor(a, b) => format!("({} ⊗ {})", a.pretty(), b.pretty()),
Formula::Par(a, b) => format!("({} ⅋ {})", a.pretty(), b.pretty()),
Formula::Lolli(a, b) => format!("({} ⊸ {})", a.pretty(), b.pretty()),
Formula::With(a, b) => format!("({} & {})", a.pretty(), b.pretty()),
Formula::Plus(a, b) => format!("({} ⊕ {})", a.pretty(), b.pretty()),
Formula::OfCourse(a) => format!("!{}", a.pretty()),
Formula::WhyNot(a) => format!("?{}", a.pretty()),
Formula::One => "1".to_string(),
Formula::Bottom => "⊥".to_string(),
Formula::Top => "⊤".to_string(),
Formula::Zero => "0".to_string(),
}
}
pub fn pretty_ascii(&self) -> String {
match self {
Formula::Atom(a) => a.clone(),
Formula::NegAtom(a) => format!("{}^", a),
Formula::Tensor(a, b) => format!("({} * {})", a.pretty_ascii(), b.pretty_ascii()),
Formula::Par(a, b) => format!("({} | {})", a.pretty_ascii(), b.pretty_ascii()),
Formula::Lolli(a, b) => format!("({} -o {})", a.pretty_ascii(), b.pretty_ascii()),
Formula::With(a, b) => format!("({} & {})", a.pretty_ascii(), b.pretty_ascii()),
Formula::Plus(a, b) => format!("({} + {})", a.pretty_ascii(), b.pretty_ascii()),
Formula::OfCourse(a) => format!("!{}", a.pretty_ascii()),
Formula::WhyNot(a) => format!("?{}", a.pretty_ascii()),
Formula::One => "1".to_string(),
Formula::Bottom => "bot".to_string(),
Formula::Top => "top".to_string(),
Formula::Zero => "0".to_string(),
}
}
pub fn pretty_latex(&self) -> String {
match self {
Formula::Atom(a) => a.clone(),
Formula::NegAtom(a) => format!("{}^{{\\bot}}", a),
Formula::Tensor(a, b) => {
format!("({} \\otimes {})", a.pretty_latex(), b.pretty_latex())
}
Formula::Par(a, b) => {
format!("({} \\parr {})", a.pretty_latex(), b.pretty_latex())
}
Formula::Lolli(a, b) => {
format!("({} \\multimap {})", a.pretty_latex(), b.pretty_latex())
}
Formula::With(a, b) => {
format!("({} \\with {})", a.pretty_latex(), b.pretty_latex())
}
Formula::Plus(a, b) => {
format!("({} \\oplus {})", a.pretty_latex(), b.pretty_latex())
}
Formula::OfCourse(a) => format!("{{!}}{}", a.pretty_latex()),
Formula::WhyNot(a) => format!("{{?}}{}", a.pretty_latex()),
Formula::One => "\\mathbf{1}".to_string(),
Formula::Bottom => "\\bot".to_string(),
Formula::Top => "\\top".to_string(),
Formula::Zero => "\\mathbf{0}".to_string(),
}
}
pub fn atom(name: impl Into<String>) -> Self {
Formula::Atom(name.into())
}
pub fn neg_atom(name: impl Into<String>) -> Self {
Formula::NegAtom(name.into())
}
pub fn tensor(a: Formula, b: Formula) -> Self {
Formula::Tensor(Box::new(a), Box::new(b))
}
pub fn par(a: Formula, b: Formula) -> Self {
Formula::Par(Box::new(a), Box::new(b))
}
pub fn lolli(a: Formula, b: Formula) -> Self {
Formula::Lolli(Box::new(a), Box::new(b))
}
pub fn with(a: Formula, b: Formula) -> Self {
Formula::With(Box::new(a), Box::new(b))
}
pub fn plus(a: Formula, b: Formula) -> Self {
Formula::Plus(Box::new(a), Box::new(b))
}
pub fn of_course(a: Formula) -> Self {
Formula::OfCourse(Box::new(a))
}
pub fn why_not(a: Formula) -> Self {
Formula::WhyNot(Box::new(a))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_negation_involutive() {
let a = Formula::atom("A");
assert_eq!(a.negate().negate(), a);
let complex = Formula::tensor(Formula::atom("A"), Formula::atom("B"));
assert_eq!(complex.negate().negate(), complex);
let formulas = vec![
Formula::One,
Formula::Bottom,
Formula::Top,
Formula::Zero,
Formula::of_course(Formula::atom("X")),
Formula::why_not(Formula::atom("Y")),
Formula::with(Formula::atom("A"), Formula::atom("B")),
Formula::plus(Formula::atom("A"), Formula::atom("B")),
Formula::par(Formula::atom("A"), Formula::atom("B")),
];
for f in formulas {
assert_eq!(f.negate().negate(), f, "Failed for: {:?}", f);
}
let lolli = Formula::lolli(Formula::atom("A"), Formula::atom("B"));
let neg_lolli = lolli.negate();
assert_eq!(
neg_lolli,
Formula::tensor(Formula::atom("A"), Formula::neg_atom("B"))
);
}
#[test]
fn test_de_morgan() {
let tensor = Formula::tensor(Formula::atom("A"), Formula::atom("B"));
let expected = Formula::par(Formula::neg_atom("A"), Formula::neg_atom("B"));
assert_eq!(tensor.negate(), expected);
let par = Formula::par(Formula::atom("A"), Formula::atom("B"));
let expected = Formula::tensor(Formula::neg_atom("A"), Formula::neg_atom("B"));
assert_eq!(par.negate(), expected);
let with = Formula::with(Formula::atom("A"), Formula::atom("B"));
let expected = Formula::plus(Formula::neg_atom("A"), Formula::neg_atom("B"));
assert_eq!(with.negate(), expected);
let plus = Formula::plus(Formula::atom("A"), Formula::atom("B"));
let expected = Formula::with(Formula::neg_atom("A"), Formula::neg_atom("B"));
assert_eq!(plus.negate(), expected);
assert_eq!(Formula::One.negate(), Formula::Bottom);
assert_eq!(Formula::Bottom.negate(), Formula::One);
assert_eq!(Formula::Top.negate(), Formula::Zero);
assert_eq!(Formula::Zero.negate(), Formula::Top);
let bang = Formula::of_course(Formula::atom("A"));
let expected = Formula::why_not(Formula::neg_atom("A"));
assert_eq!(bang.negate(), expected);
let whynot = Formula::why_not(Formula::atom("A"));
let expected = Formula::of_course(Formula::neg_atom("A"));
assert_eq!(whynot.negate(), expected);
}
#[test]
fn test_polarity() {
assert!(Formula::atom("A").is_positive());
assert!(Formula::tensor(Formula::atom("A"), Formula::atom("B")).is_positive());
assert!(Formula::One.is_positive());
assert!(Formula::plus(Formula::atom("A"), Formula::atom("B")).is_positive());
assert!(Formula::Zero.is_positive());
assert!(Formula::of_course(Formula::atom("A")).is_positive());
assert!(Formula::neg_atom("A").is_negative());
assert!(Formula::par(Formula::atom("A"), Formula::atom("B")).is_negative());
assert!(Formula::Bottom.is_negative());
assert!(Formula::with(Formula::atom("A"), Formula::atom("B")).is_negative());
assert!(Formula::Top.is_negative());
assert!(Formula::why_not(Formula::atom("A")).is_negative());
assert!(Formula::lolli(Formula::atom("A"), Formula::atom("B")).is_negative());
}
#[test]
fn test_desugar() {
let lolli = Formula::lolli(Formula::atom("A"), Formula::atom("B"));
let desugared = lolli.desugar();
let expected = Formula::par(Formula::neg_atom("A"), Formula::atom("B"));
assert_eq!(desugared, expected);
let nested = Formula::lolli(
Formula::lolli(Formula::atom("A"), Formula::atom("B")),
Formula::atom("C"),
);
let desugared = nested.desugar();
let expected = Formula::par(
Formula::tensor(Formula::atom("A"), Formula::neg_atom("B")),
Formula::atom("C"),
);
assert_eq!(desugared, expected);
}
#[test]
fn test_pretty_print() {
let f = Formula::lolli(Formula::atom("A"), Formula::atom("B"));
assert_eq!(f.pretty(), "(A ⊸ B)");
assert_eq!(f.pretty_ascii(), "(A -o B)");
assert_eq!(f.pretty_latex(), "(A \\multimap B)");
let f = Formula::tensor(
Formula::of_course(Formula::atom("A")),
Formula::why_not(Formula::atom("B")),
);
assert_eq!(f.pretty(), "(!A ⊗ ?B)");
assert_eq!(f.pretty_ascii(), "(!A * ?B)");
}
#[test]
fn test_units() {
assert_eq!(Formula::One.pretty(), "1");
assert_eq!(Formula::Bottom.pretty(), "⊥");
assert_eq!(Formula::Top.pretty(), "⊤");
assert_eq!(Formula::Zero.pretty(), "0");
assert_eq!(Formula::One.pretty_ascii(), "1");
assert_eq!(Formula::Bottom.pretty_ascii(), "bot");
assert_eq!(Formula::Top.pretty_ascii(), "top");
assert_eq!(Formula::Zero.pretty_ascii(), "0");
}
}