--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Logical expression.
+#
+# This module provides a simple data structure for first order logic.
+# Basic logical operators and algorithms are provided.
+#
+# ~~~
+# var a = new LVar("a")
+# var b = new LVar("b")
+# var c = new LVar("c")
+#
+# assert a.to_s == "a"
+# assert (a|b).to_s == "(a | b)"
+# assert (~(a|b)).to_s == "~(a | b)"
+# ~~~
+#
+# Compute a negative normal form:
+#
+# ~~~
+# var ex = a | (b & c)
+# var nex = ~ex
+# assert ex.nnf.to_s == "(a | (b & c))"
+# assert nex.nnf.to_s == "(~a & (~b | ~c))"
+# ~~~
+#
+# Compute a conjunctive normal form:
+#
+# ~~~
+# assert ex.cnf.simplify.to_s == "(a|b) & (a|c)"
+# assert nex.cnf.simplify.to_s == "(~a) & (~b|~c)"
+# ~~~
+module lexpr
+
+# A logical expression
+#
+# The basic (primitive) operators are: `&` (and), `|` (or) and `~` (not).
+# Composed operators are `impl` and `^`, they implemented using the basic operators.
+class LExpr
+ # It the logical expression `ltrue`?
+ #
+ # ~~~
+ # assert ltrue.is_t
+ # assert not lfalse.is_t
+ # assert not (new LVar("a")).is_t
+ # ~~~
+ fun is_t: Bool do return false
+
+ # It the logical expression `lfalse`?
+ #
+ # ~~~
+ # assert lfalse.is_f
+ # assert not ltrue.is_f
+ # assert not (new LVar("a")).is_f
+ # ~~~
+ fun is_f: Bool do return false
+
+ # The negation of `self`
+ #
+ # ~~~
+ # var a = new LVar("a")
+ # assert (~a).to_s == "~a"
+ # assert (~~a).to_s == "a"
+ # ~~~
+ fun ~:LExpr do return lnot
+
+ private var lnot: LExpr is lazy do return new LNot(self)
+
+ # Disjunction with `e` (and).
+ #
+ # ~~~
+ # var a = new LVar("a")
+ # var b = new LVar("b")
+ # assert (a|b).to_s == "(a | b)"
+ # ~~~
+ #
+ # `true` and `false` operands are optimized.
+ #
+ # ~~~
+ # assert (a|ltrue).is_t
+ # assert (a|lfalse) == a
+ # ~~~
+ fun |(e: LExpr): LExpr do
+ if self.is_f then return e
+ if self.is_t then return ltrue
+ if e.is_f then return self
+ if e.is_t then return ltrue
+ return new LOr(self, e)
+ end
+
+ # Conjunction with `e` (or).
+ #
+ # ~~~
+ # var a = new LVar("a")
+ # var b = new LVar("b")
+ # assert (a&b).to_s == "(a & b)"
+ # ~~~
+ #
+ # `true` and `false` operands are optimized.
+ #
+ # ~~~
+ # assert (a<rue) == a
+ # assert (a&lfalse).is_f
+ # ~~~
+ fun &(e: LExpr): LExpr do
+ if self.is_f then return lfalse
+ if self.is_t then return e
+ if e.is_f then return lfalse
+ if e.is_t then return self
+ return new LAnd(self, e)
+ end
+
+ # Implication with `e` (implies).
+ #
+ # Note: it is transformed with conjunctions and disjunctions.
+ #
+ # ~~~
+ # var a = new LVar("a")
+ # var b = new LVar("b")
+ # assert a.imp(b).to_s == "(~a | b)"
+ # ~~~
+ fun imp(e: LExpr): LExpr
+ do
+ return (~self) | e
+ end
+
+ # Exclusive disjunction with `e` (xor).
+ #
+ # Note: it is transformed with conjunctions and disjunctions.
+ #
+ # ~~~
+ # var a = new LVar("a")
+ # var b = new LVar("b")
+ # assert (a^b).to_s == "((a & ~b) | (~a & b))"
+ # ~~~
+ fun ^(e: LExpr): LExpr
+ do
+ return (self & ~e) | (~self & e)
+ end
+
+ # The negation normal form (NNF).
+ #
+ # In NNF, the negation operator is only applied to variables.
+ #
+ # ~~~
+ # var a = new LVar("a")
+ # var b = new LVar("b")
+ # assert (a&b).nnf.to_s == "(a & b)"
+ # assert (~(a&b)).nnf.to_s == "(~a | ~b)"
+ # ~~~
+ #
+ # Subclasses implement it recursively.
+ fun nnf: LExpr do return self
+
+ # The negative negation normal form (NNF).
+ #
+ # This method is used to implement `nnf`.
+ # Basically, it returns the NNF of `~self`.
+ #
+ # ~~~
+ # var a = new LVar("a")
+ # var b = new LVar("b")
+ # assert (a&b).nnnf.to_s == "(~a | ~b)"
+ # ~~~
+ #
+ # Subclasses implements it by using De Morgan's laws to push negation inwards.
+ fun nnnf: LExpr do return ~self
+
+ # Compute a conjunctive normal form.
+ #
+ # By default, a unique *equivalent* formula is computed (but its size might be exponential).
+ #
+ # ~~~
+ # var a = new LVar("a")
+ # var b = new LVar("b")
+ # var c = new LVar("c")
+ #
+ # var ex = a | (b & c)
+ # assert ex.cnf.to_s == "(a|b) & (a|c)"
+ # assert (~ex).cnf.to_s == "(~a) & (~b|~c)"
+ # ~~~
+ #
+ # If a parameter `vars` is given, an *equisatisfiable* formula is computed.
+ # Additional variables, named `zzsomething`, are created by the transformation and stored in `vars`.
+ fun cnf(vars: nullable Array[LVar]): CNF do
+ var ss = new CNF
+ var s = new Set[LExpr]
+ ss.data.add s
+ s.add self
+ return ss
+ end
+
+ # The size of the logical expression.
+ fun size: Int do return 1
+end
+
+# A logical conjunction operation
+class LAnd
+ super LExpr
+
+ # The first operand
+ var e1: LExpr
+
+ # The second operand
+ var e2: LExpr
+
+ redef fun to_s do return "({e1} & {e2})"
+
+ redef fun nnf do return e1.nnf & e2.nnf
+ redef fun nnnf do return e1.nnnf | e2.nnnf
+ redef fun cnf(v) do return e1.cnf(v) & e2.cnf(v)
+
+ redef fun size do return e1.size + e2.size + 1
+end
+
+# A logical disjunction operation
+class LOr
+ super LExpr
+
+ # The first operand
+ var e1: LExpr
+
+ # The second operand
+ var e2: LExpr
+
+ redef fun to_s do return "({e1} | {e2})"
+ redef fun nnf do return e1.nnf | e2.nnf
+ redef fun nnnf do return e1.nnnf & e2.nnnf
+
+ redef fun cnf(v) do
+ var c1 = e1.cnf(v)
+ var c2 = e2.cnf(v)
+
+ if c1.data.length > 1 and c2.data.length > 1 and v != null then
+ var z = new LVar("zz{v.length}")
+ v.add z
+ var nz = ~z
+
+ var res = new CNF
+ for c in c1.data do
+ var set = c.clone
+ set.add z
+ res.data.add(set)
+ end
+ for c in c2.data do
+ var set = c.clone
+ set.add nz
+ res.data.add(set)
+ end
+
+ return res
+ end
+
+ var res = new CNF
+ for i in c1.data do for j in c2.data do
+ res.data.add i.union(j)
+ end
+ return res
+ end
+
+ redef fun size do return e1.size + e2.size + 1
+end
+
+# A logical negation operation
+class LNot
+ super LExpr
+
+ # The argument
+ var e: LExpr
+
+ redef fun ~ do return e
+ redef fun to_s do return "~{e}"
+ redef fun nnf do return e.nnnf
+ redef fun nnnf do return e.nnf
+ redef fun cnf(v) do
+ if e isa LVar then return super
+ return e.nnnf.cnf(v)
+ end
+ redef fun size do return e.size + 1
+end
+
+# The logical *true* variable.
+fun ltrue: LTrue do return once new LTrue
+
+# The class of the singleton `ltrue`
+class LTrue
+ super LExpr
+ redef fun is_t do return true
+ redef fun ~ do return lfalse
+ redef fun to_s do return "true"
+end
+
+# The logical *false* variable.
+fun lfalse: LFalse do return once new LFalse
+
+# The class of the singleton `lfalse`
+class LFalse
+ super LExpr
+ redef fun is_f do return true
+ redef fun ~ do return ltrue
+ redef fun to_s do return "false"
+end
+
+
+# A variable of a logical expression.
+class LVar
+ super LExpr
+
+ # The name of the variable (used for representation)
+ #
+ # Internally, two variables with the same name are not merged
+ var name: String
+
+ redef fun to_s do return name
+end
+
+# A conjunctive normal form of a logical expression.
+class CNF
+ # The conjunction of disjunction of units.
+ var data = new Set[Set[LExpr]]
+
+ # Simplify `self` by removing some useless clauses.
+ #
+ # * trivially clauses (with both a variable and its negation)
+ # * subsumed clauses
+ fun simplify: CNF
+ do
+ var cs2 = new Set[Set[LExpr]]
+
+ # First pass to remove clauses with a variable and its negation.
+ # These clauses become trivially `true`, thus useless in the conjunction.
+ for c in data do
+ for i in c do
+ var nn = ~i
+ if c.has(nn) then continue label
+ end
+ cs2.add c
+ end label
+
+ # Second pass to remove clauses subsumed by an other one.
+ var cs3 = new Set[Set[LExpr]]
+ for c in cs2 do
+ for c2 in cs2 do
+ if c2 != c and c2.has_all(c) then continue label
+ end
+ cs3.add c
+ end label
+
+ var res = new CNF
+ res.data = cs3
+ return res
+ end
+
+ # Merge two CNF
+ fun &(o: CNF): CNF
+ do
+ var res = new CNF
+ res.data.add_all self.data
+ res.data.add_all o.data
+ return res
+ end
+
+ redef fun to_s do return [for c in data do "(" + c.join("|") + ")"].join(" & ")
+
+ redef fun ==(o) do return o isa CNF and data == o.data
+ redef fun hash do return data.hash
+end