(******************************************************************************)
(* FILE          : clausal_form.ml                                            *)
(* DESCRIPTION   : Functions for putting a formula into clausal form.         *)
(*                                                                            *)
(* READS FILES   : <none>                                                     *)
(* WRITES FILES  : <none>                                                     *)
(*                                                                            *)
(* AUTHOR        : R.J.Boulton                                                *)
(* DATE          : 13th May 1991                                              *)
(*                                                                            *)
(* LAST MODIFIED : R.J.Boulton                                                *)
(* DATE          : 12th October 1992                                          *)
(*                                                                            *)
(* LAST MODIFIED : P. Papapanagiotou (University of Edinburgh)                *)
(* DATE          : 2008                                                       *)
(******************************************************************************)

let IMP_DISJ_THM = TAUT `!t1 t2. t1 ==> t2 <=> ~t1 \/ t2`;;
let RIGHT_OR_OVER_AND = TAUT `!t1 t2 t3. t2 /\ t3 \/ t1 <=> (t2 \/ t1) /\ (t3 \/ t1)`;;
let LEFT_OR_OVER_AND = TAUT `!t1 t2 t3. t1 \/ t2 /\ t3 <=> (t1 \/ t2) /\ (t1 \/ t3)`;;

(*============================================================================*)
(* Theorems for normalizing Boolean terms                                     *)
(*============================================================================*)

(*----------------------------------------------------------------------------*)
(* EQ_EXPAND = |- (x = y) = ((~x \/ y) /\ (~y \/ x))                          *)
(*----------------------------------------------------------------------------*)

let EQ_EXPAND =
 prove
  (`(x = y) = ((~x \/ y) /\ (~y \/ x))`,
   BOOL_CASES_TAC `x:bool` THEN
   BOOL_CASES_TAC `y:bool` THEN
   REWRITE_TAC []);;

(*----------------------------------------------------------------------------*)
(* IMP_EXPAND = |- (x ==> y) = (~x \/ y)                                      *)
(*----------------------------------------------------------------------------*)

let IMP_EXPAND = SPEC `y:bool` (SPEC `x:bool` IMP_DISJ_THM);;

(*----------------------------------------------------------------------------*)
(* COND_EXPAND = |- (x => y | z) = ((~x \/ y) /\ (x \/ z))                    *)
(*----------------------------------------------------------------------------*)

let COND_EXPAND =
 prove
  (`(if x then y else z) = ((~x \/ y) /\ (x \/ z))`,
   BOOL_CASES_TAC `x:bool` THEN
   BOOL_CASES_TAC `y:bool` THEN
   BOOL_CASES_TAC `z:bool` THEN
   REWRITE_TAC []);;

(*----------------------------------------------------------------------------*)
(* NOT_NOT_NORM = |- ~~x = x                                                  *)
(*----------------------------------------------------------------------------*)

let NOT_NOT_NORM = SPEC `x:bool` (CONJUNCT1 NOT_CLAUSES);;

(*----------------------------------------------------------------------------*)
(* NOT_CONJ_NORM = |- ~(x /\ y) = (~x \/ ~y)                                  *)
(*----------------------------------------------------------------------------*)

let NOT_CONJ_NORM = CONJUNCT1 (SPEC `y:bool` (SPEC `x:bool` DE_MORGAN_THM));;

(*----------------------------------------------------------------------------*)
(* NOT_DISJ_NORM = |- ~(x \/ y) = (~x /\ ~y)                                  *)
(*----------------------------------------------------------------------------*)

let NOT_DISJ_NORM = CONJUNCT2 (SPEC `y:bool` (SPEC `x:bool` DE_MORGAN_THM));;

(*----------------------------------------------------------------------------*)
(* LEFT_DIST_NORM = |- x \/ (y /\ z) = (x \/ y) /\ (x \/ z)                   *)
(*----------------------------------------------------------------------------*)

let LEFT_DIST_NORM =
 SPEC `z:bool` (SPEC `y:bool` (SPEC `x:bool` LEFT_OR_OVER_AND));;

(*----------------------------------------------------------------------------*)
(* RIGHT_DIST_NORM = |- (x /\ y) \/ z = (x \/ z) /\ (y \/ z)                  *)
(*----------------------------------------------------------------------------*)

let RIGHT_DIST_NORM =
 SPEC `y:bool` (SPEC `x:bool` (SPEC `z:bool` RIGHT_OR_OVER_AND));;

(*----------------------------------------------------------------------------*)
(* CONJ_ASSOC_NORM = |- (x /\ y) /\ z = x /\ (y /\ z)                         *)
(*----------------------------------------------------------------------------*)

let CONJ_ASSOC_NORM =
 SYM (SPEC `z:bool` (SPEC `y:bool` (SPEC `x:bool` CONJ_ASSOC)));;

(*----------------------------------------------------------------------------*)
(* DISJ_ASSOC_NORM = |- (x \/ y) \/ z = x \/ (y \/ z)                         *)
(*----------------------------------------------------------------------------*)

let DISJ_ASSOC_NORM =
 SYM (SPEC `z:bool` (SPEC `y:bool` (SPEC `x:bool` DISJ_ASSOC)));;

(*============================================================================*)
(* Conversions for rewriting Boolean terms                                    *)
(*============================================================================*)

let COND_EXPAND_CONV = REWR_CONV COND_EXPAND;;
let CONJ_ASSOC_NORM_CONV = REWR_CONV CONJ_ASSOC_NORM;;
let DISJ_ASSOC_NORM_CONV = REWR_CONV DISJ_ASSOC_NORM;;
let EQ_EXPAND_CONV = REWR_CONV EQ_EXPAND;;
let IMP_EXPAND_CONV = REWR_CONV IMP_EXPAND;;
let LEFT_DIST_NORM_CONV = REWR_CONV LEFT_DIST_NORM;;
let NOT_CONJ_NORM_CONV = REWR_CONV NOT_CONJ_NORM;;
let NOT_DISJ_NORM_CONV = REWR_CONV NOT_DISJ_NORM;;
let NOT_NOT_NORM_CONV = REWR_CONV NOT_NOT_NORM;;
let RIGHT_DIST_NORM_CONV = REWR_CONV RIGHT_DIST_NORM;;

(*----------------------------------------------------------------------------*)
(* NOT_CONV : conv                                                            *)
(*                                                                            *)
(*    |- !t. ~~t = t                                                          *)
(*    |- ~T = F                                                               *)
(*    |- ~F = T                                                               *)
(*----------------------------------------------------------------------------*)

let NOT_CONV =
try (
 let [th1;th2;th3] = CONJUNCTS NOT_CLAUSES
 in fun tm ->
 (let arg = dest_neg tm
  in  if (is_T arg) then th2
      else if (is_F arg) then th3
      else SPEC (dest_neg arg) th1
 )
) with Failure _ -> failwith "NOT_CONV";;

(*----------------------------------------------------------------------------*)
(* AND_CONV : conv                                                            *)
(*                                                                            *)
(*    |- T /\ t = t                                                           *)
(*    |- t /\ T = t                                                           *)
(*    |- F /\ t = F                                                           *)
(*    |- t /\ F = F                                                           *)
(*    |- t /\ t = t                                                           *)
(*----------------------------------------------------------------------------*)

let AND_CONV =
try (
 let [th1;th2;th3;th4;th5] = map GEN_ALL (CONJUNCTS (SPEC_ALL AND_CLAUSES))
 in fun tm ->
 (let (arg1,arg2) = dest_conj tm
  in  if (is_T arg1) then SPEC arg2 th1
      else if (is_T arg2) then SPEC arg1 th2
      else if (is_F arg1) then SPEC arg2 th3
      else if (is_F arg2) then SPEC arg1 th4
      else if (arg1 = arg2) then SPEC arg1 th5
      else failwith ""
  )
 ) with Failure _ -> failwith "AND_CONV" ;;

(*----------------------------------------------------------------------------*)
(* OR_CONV : conv                                                             *)
(*                                                                            *)
(*    |- T \/ t = T                                                           *)
(*    |- t \/ T = T                                                           *)
(*    |- F \/ t = t                                                           *)
(*    |- t \/ F = t                                                           *)
(*    |- t \/ t = t                                                           *)
(*----------------------------------------------------------------------------*)

let OR_CONV =
try (
 let [th1;th2;th3;th4;th5] = map GEN_ALL (CONJUNCTS (SPEC_ALL OR_CLAUSES))
 in fun tm ->
 (let (arg1,arg2) = dest_disj tm
  in  if (is_T arg1) then SPEC arg2 th1
      else if (is_T arg2) then SPEC arg1 th2
      else if (is_F arg1) then SPEC arg2 th3
      else if (is_F arg2) then SPEC arg1 th4
      else if (arg1 = arg2) then SPEC arg1 th5
      else failwith ""
 )
) with Failure _ -> failwith "OR_CONV";;

(*============================================================================*)
(* Conversions for obtaining `clausal' form                                   *)
(*============================================================================*)

(*----------------------------------------------------------------------------*)
(* EQ_IMP_COND_ELIM_CONV : (term -> bool) -> conv                             *)
(*                                                                            *)
(* Eliminates Boolean equalities, Boolean conditionals, and implications from *)
(* terms consisting of =,==>,COND,/\,\/,~ and atoms. The atoms are specified  *)
(* by the predicate that the conversion takes as its first argument.          *)
(*----------------------------------------------------------------------------*)

let rec EQ_IMP_COND_ELIM_CONV is_atom tm =
try
 (if (is_atom tm) then ALL_CONV tm
  else if (is_neg tm) then (RAND_CONV (EQ_IMP_COND_ELIM_CONV is_atom)) tm
  else if (is_eq tm) then
     ((RATOR_CONV (RAND_CONV (EQ_IMP_COND_ELIM_CONV is_atom))) THENC
      (RAND_CONV (EQ_IMP_COND_ELIM_CONV is_atom)) THENC
      EQ_EXPAND_CONV) tm
  else if (is_imp tm) then
     ((RATOR_CONV (RAND_CONV (EQ_IMP_COND_ELIM_CONV is_atom))) THENC
      (RAND_CONV (EQ_IMP_COND_ELIM_CONV is_atom)) THENC
      IMP_EXPAND_CONV) tm
  else if (is_cond tm) then
     ((RATOR_CONV
        (RATOR_CONV (RAND_CONV (EQ_IMP_COND_ELIM_CONV is_atom)))) THENC
      (RATOR_CONV (RAND_CONV (EQ_IMP_COND_ELIM_CONV is_atom))) THENC
      (RAND_CONV (EQ_IMP_COND_ELIM_CONV is_atom)) THENC
      COND_EXPAND_CONV) tm
  else ((RATOR_CONV (RAND_CONV (EQ_IMP_COND_ELIM_CONV is_atom))) THENC
        (RAND_CONV (EQ_IMP_COND_ELIM_CONV is_atom))) tm
 ) with Failure _ -> failwith "EQ_IMP_COND_ELIM_CONV";;

(*----------------------------------------------------------------------------*)
(* MOVE_NOT_DOWN_CONV : (term -> bool) -> conv -> conv                        *)
(*                                                                            *)
(* Moves negations down through a term consisting of /\,\/,~ and atoms. The   *)
(* atoms are specified by a predicate (first argument). When a negation has   *)
(* reached an atom, the conversion `conv' (second argument) is applied to the *)
(* negation of the atom. `conv' is also applied to any non-negated atoms      *)
(* encountered. T and F are eliminated.                                       *)
(*----------------------------------------------------------------------------*)

let rec MOVE_NOT_DOWN_CONV is_atom conv tm =
try
 (if (is_atom tm) then (conv tm)
  else if (is_neg tm)
  then ((let tm' = rand tm
         in  if (is_atom tm') then ((conv THENC (TRY_CONV NOT_CONV)) tm)
             else if (is_neg tm') then (NOT_NOT_NORM_CONV THENC
                                   (MOVE_NOT_DOWN_CONV is_atom conv)) tm
             else if (is_conj tm') then
                (NOT_CONJ_NORM_CONV THENC
                 (RATOR_CONV (RAND_CONV (MOVE_NOT_DOWN_CONV is_atom conv)))
                                                                          THENC
                 (RAND_CONV (MOVE_NOT_DOWN_CONV is_atom conv)) THENC
                 (TRY_CONV AND_CONV)) tm
             else if (is_disj tm') then
                (NOT_DISJ_NORM_CONV THENC
                 (RATOR_CONV (RAND_CONV (MOVE_NOT_DOWN_CONV is_atom conv)))
                                                                          THENC
                 (RAND_CONV (MOVE_NOT_DOWN_CONV is_atom conv)) THENC
                 (TRY_CONV OR_CONV)) tm
             else failwith ""))
  else if (is_conj tm) then
     ((RATOR_CONV (RAND_CONV (MOVE_NOT_DOWN_CONV is_atom conv))) THENC
      (RAND_CONV (MOVE_NOT_DOWN_CONV is_atom conv)) THENC
      (TRY_CONV AND_CONV)) tm
  else if (is_disj tm) then
     ((RATOR_CONV (RAND_CONV (MOVE_NOT_DOWN_CONV is_atom conv))) THENC
      (RAND_CONV (MOVE_NOT_DOWN_CONV is_atom conv)) THENC
      (TRY_CONV OR_CONV)) tm
  else failwith ""
 ) with Failure _ -> failwith "MOVE_NOT_DOWN_CONV";;

(*----------------------------------------------------------------------------*)
(* CONJ_LINEAR_CONV : conv                                                    *)
(*                                                                            *)
(* Linearizes conjuncts using the following conversion applied recursively:   *)
(*                                                                            *)
(*    "(x /\ y) /\ z"                                                         *)
(*    ================================                                        *)
(*    |- (x /\ y) /\ z = x /\ (y /\ z)                                        *)
(*----------------------------------------------------------------------------*)

let rec CONJ_LINEAR_CONV tm =
try
 (if ((is_conj tm) & (is_conj (rand (rator tm))))
  then (CONJ_ASSOC_NORM_CONV THENC
        (RAND_CONV (TRY_CONV CONJ_LINEAR_CONV)) THENC
        (TRY_CONV CONJ_LINEAR_CONV)) tm
  else failwith ""
 ) with Failure _ -> failwith "CONJ_LINEAR_CONV";;

(*----------------------------------------------------------------------------*)
(* CONJ_NORM_FORM_CONV : conv                                                 *)
(*                                                                            *)
(* Puts a term involving /\ and \/ into conjunctive normal form. Anything     *)
(* other than /\ and \/ is taken to be an atom and is not processed.          *)
(*                                                                            *)
(* The conjunction returned is linear, i.e. the conjunctions are associated   *)
(* to the right. Each conjunct is a linear disjunction.                       *)
(*----------------------------------------------------------------------------*)

let rec CONJ_NORM_FORM_CONV tm =
try
 (if (is_disj tm) then
     (if (is_conj (rand (rator tm))) then
         ((RATOR_CONV
              (RAND_CONV ((RATOR_CONV (RAND_CONV CONJ_NORM_FORM_CONV)) THENC
                          (RAND_CONV CONJ_NORM_FORM_CONV)))) THENC
          (RAND_CONV CONJ_NORM_FORM_CONV) THENC
          RIGHT_DIST_NORM_CONV THENC
          (RATOR_CONV (RAND_CONV CONJ_NORM_FORM_CONV)) THENC
          (RAND_CONV CONJ_NORM_FORM_CONV) THENC
          (TRY_CONV CONJ_LINEAR_CONV)) tm
      else if (is_conj (rand tm)) then
         ((RATOR_CONV (RAND_CONV CONJ_NORM_FORM_CONV)) THENC
          (RAND_CONV ((RATOR_CONV (RAND_CONV CONJ_NORM_FORM_CONV)) THENC
                      (RAND_CONV CONJ_NORM_FORM_CONV))) THENC
          LEFT_DIST_NORM_CONV THENC
          (RATOR_CONV (RAND_CONV CONJ_NORM_FORM_CONV)) THENC
          (RAND_CONV CONJ_NORM_FORM_CONV) THENC
          (TRY_CONV CONJ_LINEAR_CONV)) tm
      else if (is_disj (rand (rator tm))) then
         (DISJ_ASSOC_NORM_CONV THENC CONJ_NORM_FORM_CONV) tm
      else (let th = RAND_CONV CONJ_NORM_FORM_CONV tm
            in  let tm' = rhs (concl th)
            in  if (is_conj (rand tm'))
                then (TRANS th (CONJ_NORM_FORM_CONV tm'))
                else th))
  else if (is_conj tm) then
     ((RATOR_CONV (RAND_CONV CONJ_NORM_FORM_CONV)) THENC
      (RAND_CONV CONJ_NORM_FORM_CONV) THENC
      (TRY_CONV CONJ_LINEAR_CONV)) tm
  else ALL_CONV tm
 ) with Failure _ -> failwith "CONJ_NORM_FORM_CONV";;

(*----------------------------------------------------------------------------*)
(* has_boolean_args_and_result : term -> bool                                 *)
(*                                                                            *)
(* Yields true if and only if the term is of type ":bool", and if it is a     *)
(* function application, all the arguments are of type ":bool".               *)
(*----------------------------------------------------------------------------*)

let has_boolean_args_and_result tm =
try
 (let args = snd (strip_comb tm)
  in  let types = (type_of tm)::(map type_of args)
  in  (subtract (setify types) [`:bool`]) = [] )
 with Failure _ -> (type_of tm = `:bool`);;

(*----------------------------------------------------------------------------*)
(* CLAUSAL_FORM_CONV : conv                                                   *)
(*                                                                            *)
(* Puts into clausal form terms consisting of =,==>,COND,/\,\/,~ and atoms.   *)
(*----------------------------------------------------------------------------*)

let CLAUSAL_FORM_CONV tm =
try (
 let is_atom tm =
    (not (has_boolean_args_and_result tm)) or (is_var tm) or (is_const tm)
 in
 ((EQ_IMP_COND_ELIM_CONV is_atom) THENC
  (MOVE_NOT_DOWN_CONV is_atom ALL_CONV) THENC
  CONJ_NORM_FORM_CONV) tm
 ) with Failure _ -> failwith "CLAUSAL_FORM_CONV";;
