use std::fmt::Debug;
use lexington::{Any,Lexer,Match,Matcher,Scanner,Token,Within};
use lexington::{ShiftReduceParser,ShiftReduceRule};
use Expr::*;
use Proposition::*;
use Partial::*;
trait LogicalNegation {
fn not(self) -> Self;
}
#[derive(Clone,Debug,Eq,PartialEq,PartialOrd,Ord)]
enum Expr {
Int(isize),
Var(String)
}
#[derive(Clone,Debug,Eq,PartialEq,PartialOrd,Ord)]
enum Proposition {
True,
False,
Equality(bool,Expr,Expr),
Inequality(bool,Expr,Expr)
}
impl Proposition {
pub fn equals(lhs: Expr, rhs: Expr) -> Proposition {
match (&lhs,&rhs) {
(l,r) if l == r => True,
(Int(x),Int(y)) if x == y => True,
(Int(x),Int(y)) if x != y => False,
_ => Equality(true,lhs,rhs)
}
}
pub fn not_equals(lhs: Expr, rhs: Expr) -> Proposition {
match (&lhs,&rhs) {
(l,r) if l == r => False,
(Int(x),Int(y)) if x != y => True,
(Int(x),Int(y)) if x == y => False,
_ => Equality(false,lhs,rhs)
}
}
pub fn less_than(lhs: Expr, rhs: Expr) -> Proposition {
match (&lhs,&rhs) {
(l,r) if l == r => False,
(Int(x),Int(y)) if x < y => True,
(Int(x),Int(y)) if x >= y => False,
_ => Inequality(false,rhs,lhs)
}
}
pub fn less_than_or_equals(lhs: Expr, rhs: Expr) -> Proposition {
match (&lhs,&rhs) {
(l,r) if l == r => True,
(Int(x),Int(y)) if x <= y => True,
(Int(x),Int(y)) if x > y => False,
_ => Inequality(true,lhs,rhs)
}
}
pub fn greater_than(lhs: Expr, rhs: Expr) -> Proposition {
Self::less_than(rhs,lhs)
}
pub fn greater_than_or_equals(lhs: Expr, rhs: Expr) -> Proposition {
Self::less_than_or_equals(rhs,lhs)
}
}
impl LogicalNegation for Proposition {
fn not(self) -> Self {
match self {
Proposition::True => Proposition::False,
Proposition::False => Proposition::True,
Proposition::Equality(b,l,r) => Self::Equality(!b,l,r),
Proposition::Inequality(b,l,r) => Self::Inequality(!b,l,r),
}
}
}
#[derive(Clone,Debug,PartialEq)]
struct Formula {
terms: Vec<Vec<Proposition>>
}
impl Formula {
pub fn from_prop(prop: Proposition) -> Self {
Self{terms: vec![vec![prop]]}
}
pub fn from_var<T:Into<String>>(negated: bool, v: T) -> Self {
let p = if negated {
Proposition::not_equals(Var(v.into()),Int(0))
} else {
Proposition::equals(Var(v.into()),Int(0))
};
Formula::from_prop(p)
}
pub fn or(mut self, other: Self) -> Self {
self.or_into(other);
self
}
pub fn and(mut self, other: Self) -> Self {
self.and_into(other);
self
}
pub fn or_into(&mut self, other: Self) {
self.terms.extend(other.terms);
self.terms.sort();
}
pub fn and_into(&mut self, other: Self) {
if other.terms.len() == 1 {
let term = &other.terms[0];
for t in &mut self.terms {
t.extend(term.clone());
t.sort()
}
self.terms.sort()
} else {
let mut nterms = Vec::new();
for term in &other.terms {
let mut nts = self.terms.clone();
for t in &mut nts {
t.extend(term.clone());
t.sort()
}
nterms.extend(nts);
}
self.terms.clear();
self.terms.extend(nterms);
self.terms.sort()
}
}
pub fn not(&mut self) {
todo!()
}
}
impl From<Proposition> for Formula {
fn from(prop: Proposition) -> Self {
Self{terms: vec![vec![prop]]}
}
}
#[derive(Clone,Debug,PartialEq)]
enum Partial {
Empty,
OpenConjunct(Formula),
OpenDisjunct(Formula),
OpenEquality(bool,Vec<Expr>),
OpenInequality(bool,bool,Vec<Expr>),
ClosedForm(Formula),
ClosedExpr(Expr)
}
impl Partial {
fn into_or(self) -> Result<(bool,Self),()> {
let r = match self {
OpenDisjunct(_) => self,
ClosedForm(f) => { OpenDisjunct(f) }
ClosedExpr(Var(v)) => { OpenDisjunct(Formula::from_var(false,v)) }
_ => { return Ok((false,self)); }
};
Ok((true,r))
}
fn into_and(self) -> Result<(bool,Self),()> {
let r = match self {
OpenConjunct(_) => self,
ClosedForm(f) => { OpenConjunct(f) }
ClosedExpr(Var(v)) => { OpenConjunct(Formula::from_var(false,v)) }
_ => { return Ok((false,self)); }
};
Ok((true,r))
}
fn into_equality(self, sign: bool) -> Result<(bool,Self),()> {
match self {
ClosedExpr(e) => Ok((true,OpenEquality(sign,vec![e]))),
_ => Ok((false,self))
}
}
fn into_inequality(self, sign: bool, strict: bool) -> Result<(bool,Self),()> {
match self {
ClosedExpr(e) => Ok((true,OpenInequality(sign,strict,vec![e]))),
_ => Ok((false,self))
}
}
fn close(self) -> Self {
match self {
OpenConjunct(t)|OpenDisjunct(t) => ClosedForm(t),
OpenEquality(p,mut es) if es.len()== 2 => {
let rhs = es.pop().unwrap();
let lhs = es.pop().unwrap();
let prop = Proposition::Equality(p,lhs,rhs);
ClosedForm(Formula::from_prop(prop))
},
OpenInequality(lt,st,mut es) if es.len()== 2 => {
let rhs = es.pop().unwrap();
let lhs = es.pop().unwrap();
let prop = match (lt,st) {
(false,false) => Proposition::greater_than_or_equals(lhs,rhs),
(true,false) => Proposition::less_than_or_equals(lhs,rhs),
(false,true) => Proposition::greater_than(lhs,rhs),
(true,true) => Proposition::less_than(lhs,rhs),
};
ClosedForm(Formula::from_prop(prop))
},
_ => self
}
}
fn reduce(mut self, other: Partial) -> Result<Self,()> {
match (&mut self,&other) {
(Empty,_) => Ok(other),
(OpenConjunct(p),_) => { p.and_into(other.as_formula()); Ok(self) }
(OpenDisjunct(p),_) => { p.or_into(other.as_formula()); Ok(self) }
(OpenEquality(_,es),_) if es.len()==1 => { es.push(other.as_expr()); Ok(self) }
(OpenInequality(_,_,es),_) if es.len()==1 => { es.push(other.as_expr()); Ok(self) }
(_,_) => todo!("Reducing {self:?} <= {other:?})")
}
}
fn as_formula(self) -> Formula {
match self {
ClosedForm(t) => t,
ClosedExpr(Var(x)) => Formula::from_var(false,x),
_ => { panic!("cannot unwrap incomplete expression: {self:?}"); }
}
}
fn as_expr(self) -> Expr {
match self {
ClosedExpr(e) => e,
_ => { panic!("cannot unwrap incomplete expression: {self:?}"); }
}
}
}
#[derive(Copy,Clone,Debug,PartialEq)]
enum Kind {
Eof,
LeftBrace,
RightBrace,
WhiteSpace,
LogicalAnd,
LogicalOr,
LogicalNot,
True,
False,
EqualsEquals,
NotEquals,
LessThan,
LessEquals,
GreaterThan,
GreaterEquals,
Identifier,
Number
}
fn lex(input: &str) -> Vec<Token<Kind>> {
let whitespace = Any([' ','\n','\t']).one_or_more();
let number = Within('0'..='9').one_or_more();
let identifier_start = Within('a'..='z')
.or(Within('A'..='Z')).or('_');
let identifier_rest = Within('0'..='9').or(Within('a'..='z'))
.or(Within('A'..='Z')).or('_').zero_or_more();
let identifier = identifier_start.then(identifier_rest);
let scanner = Match(number,Kind::Number)
.and_match('⊤',Kind::True)
.and_match('⊥',Kind::False)
.and_match('=',Kind::EqualsEquals)
.and_match('≠',Kind::NotEquals)
.and_match('≤',Kind::LessEquals)
.and_match('<',Kind::LessThan)
.and_match('≥',Kind::GreaterEquals)
.and_match('>',Kind::GreaterThan)
.and_match('∨',Kind::LogicalOr)
.and_match('∧',Kind::LogicalAnd)
.and_match('(',Kind::LeftBrace)
.and_match(')',Kind::RightBrace)
.and_match(identifier,Kind::Identifier)
.and_match(whitespace, Kind::WhiteSpace)
.eof(Kind::Eof);
Lexer::new(input,scanner).collect()
}
fn parse<'a>(input: &'a str) -> Result<Formula,()> {
let tokens = lex(input);
let st = ShiftReduceParser::new()
.apply(|l,r:Partial| l.reduce(r))
.terminate(Kind::True,|_| ClosedForm(Formula::from(True)))
.terminate(Kind::False,|_| ClosedForm(Formula::from(False)))
.terminate(Kind::Number,|tok| ClosedExpr(Int(input[tok.range()].parse().unwrap())))
.terminate(Kind::Identifier,|tok| {
let name = input[tok.range()].to_string();
ClosedExpr(Expr::from(Var(name)))
})
.open(Kind::LeftBrace, Empty)
.update_with(Kind::LogicalAnd,|e| e.into_and())
.update_with(Kind::LogicalOr,|e| e.into_or())
.update_with(Kind::EqualsEquals,|e| e.into_equality(true))
.update_with(Kind::NotEquals,|e| e.into_equality(false))
.update_with(Kind::LessEquals,|e| e.into_inequality(true,false))
.update_with(Kind::LessThan,|e| e.into_inequality(true,true))
.update_with(Kind::GreaterEquals,|e| e.into_inequality(false,false))
.update_with(Kind::GreaterThan,|e| e.into_inequality(false,true))
.close_with(Kind::RightBrace, |e| e.close())
.close_with(Kind::Eof, |e| e.close())
.skip(Kind::WhiteSpace)
.first(Empty)
.parse(tokens)?;
Ok(st.as_formula())
}
fn check_ok<T:Into<Formula>>(input: &str, expecting: T) {
let expected = expecting.into();
let actual = parse(input).unwrap();
assert_eq!(actual,expected);
}
fn check_eq(lhs: &str, rhs: &str) {
let l = parse(lhs).unwrap();
let r = parse(rhs).unwrap();
assert_eq!(l,r);
}
#[test]
fn logic_01() {
check_ok("⊥", False);
check_ok("⊤", True);
}
#[test]
fn logic_and_01() {
check_ok("⊤ ∧ ⊥",Formula::from(True).and(Formula::from(False)));
}
#[test]
fn logic_or_01() {
check_ok("⊤ ∨ ⊤",Formula::from(True).or(Formula::from(True)));
}
#[test]
fn logic_or_02() {
let a = Formula::from_var(false,"a");
let x = Formula::from_var(false,"x");
let y = Formula::from_var(false,"y");
let a_x = a.clone().and(x);
let a_y = a.and(y);
check_ok("(a ∧ x) ∨ (a ∧ y)",a_x.or(a_y));
}
#[test]
fn logic_or_03() {
let a = Formula::from_var(false,"a");
let b = Formula::from_var(false,"b");
let x = Formula::from_var(false,"x");
let y = Formula::from_var(false,"y");
let a_x = a.clone().and(x.clone());
let a_y = a.and(y.clone());
let b_x = b.and(x);
check_ok("(a ∧ x) ∨ (a ∧ y) ∨ (b ∧ x)",a_x.or(a_y).or(b_x));
}
#[test]
fn logic_or_04() {
let a = Formula::from_var(false,"a");
let b = Formula::from_var(false,"b");
let x = Formula::from_var(false,"x");
let y = Formula::from_var(false,"y");
let a_x = a.clone().and(x.clone());
let a_y = a.and(y.clone());
let b_x = b.clone().and(x);
let b_y = b.and(y);
check_ok("(a ∧ x) ∨ (a ∧ y) ∨ (b ∧ x) ∨ (b ∧ y)",a_x.or(a_y).or(b_x).or(b_y));
}
#[test]
fn logic_eq_01() {
let x = Expr::Var("x".to_string());
let z = Expr::Int(0);
check_ok("x=0",Proposition::equals(x,z))
}
#[test]
fn logic_eq_02() {
let x = Expr::Var("x".to_string());
let c = Expr::Int(123);
check_ok("123=x",Proposition::equals(c,x))
}
#[test]
fn logic_eq_03() {
let x = Expr::Var("x".to_string());
let z = Expr::Int(0);
check_ok("x≠0",Proposition::not_equals(x,z))
}
#[test]
fn logic_eq_04() {
let x = Expr::Var("x".to_string());
let c = Expr::Int(123);
check_ok("123≠x",Proposition::not_equals(c,x))
}
#[test]
fn logic_eq_05() {
let x = Expr::Var("x".to_string());
let z = Expr::Int(0);
check_ok("⊤ ∨ (x=0)",Formula::from(True).or(Proposition::equals(x,z).into()))
}
#[test]
fn logic_eq_06() {
let x = Expr::Var("x".to_string());
let z = Expr::Int(0);
let f : Formula = Proposition::equals(x,z).into();
check_ok("(x=0) ∨ ⊥",f.or(False.into()))
}
#[test]
fn logic_eq_07() {
let x = Expr::Var("x".to_string());
let z = Expr::Int(0);
check_ok("⊤ ∧ (x=0)",Formula::from(True).and(Proposition::equals(x,z).into()))
}
#[test]
fn logic_eq_08() {
let x = Expr::Var("x".to_string());
let z = Expr::Int(0);
let f : Formula = Proposition::equals(x,z).into();
check_ok("(x=0) ∧ ⊥",f.and(False.into()))
}
#[test]
fn logic_ieq_01() {
let x = Expr::Var("x".to_string());
let z = Expr::Int(0);
check_ok("x≤0",Proposition::less_than_or_equals(x,z))
}
#[test]
fn logic_ieq_02() {
let x = Expr::Var("x".to_string());
let z = Expr::Int(0);
check_ok("x>0",Proposition::greater_than(x,z))
}
#[test]
fn logic_ieq_03() {
let x = Expr::Var("x".to_string());
let z = Expr::Int(0);
check_ok("x≥0",Proposition::greater_than_or_equals(x,z))
}
#[test]
fn logic_ieq_04() {
let x = Expr::Var("x".to_string());
let z = Expr::Int(0);
check_ok("x<0",Proposition::less_than(x,z))
}
#[test]
fn logic_equiv_03() {
check_eq("0 < 0","⊥");
}
#[test]
fn logic_equiv_04() {
check_eq("0 < 1","⊤");
}
#[test]
fn logic_equiv_05() {
check_eq("0 ≤ 0","⊤");
}
#[test]
fn logic_equiv_06() {
check_eq("1 ≤ 0","⊥");
}
#[test]
fn logic_equiv_07() {
check_eq("x ∧ (y ∨ z)","(x∧y) ∨ (x∧z)");
}
#[test]
fn logic_equiv_08() {
check_eq("(a ∨ b) ∧ (x ∨ y)","(a ∧ (x ∨ y)) ∨ (b ∧ (x ∨ y))");
check_eq("(a ∨ b) ∧ (x ∨ y)","(a ∧ x) ∨ (b ∧ x) ∨ (a ∧ y) ∨ (b ∧ y)");
}