
(******************************************************************************)
(* FILE          : terms_and_clauses.ml                                       *)
(* DESCRIPTION   : Rewriting terms and simplifying clauses.                   *)
(*                                                                            *)
(* READS FILES   : <none>                                                     *)
(* WRITES FILES  : <none>                                                     *)
(*                                                                            *)
(* AUTHOR        : R.J.Boulton                                                *)
(* DATE          : 7th June 1991                                              *)
(*                                                                            *)
(* MODIFIED      : R.J.Boulton                                                *)
(* DATE          : 16th October 1992                                          *)
(*                                                                            *)
(* LAST MODIFIED : P. Papapanagiotou (University of Edinburgh)                *)
(* DATE          : July 2009                                                  *)
(******************************************************************************)

let SUBST_CONV thvars template tm =
  let thms,vars = unzip thvars in
  let gvs = map (genvar o type_of) vars in
  let gtemplate = subst (zip gvs vars) template in
  SUBST (zip thms gvs) (mk_eq(template,gtemplate)) (REFL tm);;


let bool_EQ_CONV =
  let check = let boolty = `:bool` in check (fun tm -> type_of tm = boolty) in
  let clist = map (GEN `b:bool`)
                  (CONJUNCTS(SPEC `b:bool` EQ_CLAUSES)) in
  let tb = hd clist and bt = hd(tl clist) in
  let T = `T` and F = `F` in
  fun tm ->
    try let l,r = (I F_F check) (dest_eq tm) in
        if l = r then EQT_INTRO (REFL l) else
        if l = T then SPEC r tb else
        if r = T then SPEC l bt else fail()
    with Failure _ -> failwith "bool_EQ_CONV";;

(*----------------------------------------------------------------------------*)
(* rewrite_with_lemmas : (term list -> term list -> conv) ->                  *)
(*                        term list -> term list -> conv                      *)
(*                                                                            *)
(* Function to rewrite with known lemmas (rewrite rules) in the reverse order *)
(* in which they were introduced. Applies the first applicable lemma, or if   *)
(* none are applicable it leaves the term unchanged.                          *)
(*                                                                            *)
(* A rule is applicable if its LHS matches the term, and it does not violate  *)
(* the `alphabetical' ordering rule if it is a permutative rule. To be        *)
(* applicable, the hypotheses of the rules must be satisfied. The function    *)
(* takes a general rewrite rule, a chain of hypotheses and a list of          *)
(* assumptions as arguments. It uses these to try to satisfy the hypotheses.  *)
(* If a hypotheses is in the assumption list, it is assumed. Otherwise a      *)
(* check is made that the hypothesis is not already a goal of the proof       *)
(* procedure. This is to prevent looping. If it's not already a goal, the     *)
(* function attempts to rewrite the hypotheses, with it added to the chain of *)
(* hypotheses.                                                                *)
(*                                                                            *)
(* Before trying to establish the hypotheses of a rewrite rule, it is         *)
(* necessary to instantiate any free variables in the hypotheses. This is     *)
(* done by trying to find an instantiation that makes one of the hypotheses   *)
(* equal to a term in the assumption list.                                    *)
(*----------------------------------------------------------------------------*)

let rewrite_with_lemmas rewrite hyp_chain assumps tm =
   let rewrite_hyp h =
try (EQT_INTRO (ASSUME (find (fun tm -> tm = h) assumps))) with Failure _ ->
      (if (mem h hyp_chain)
       then ALL_CONV h
       else rewrite (h::hyp_chain) assumps h)
   in
   let rec try_rewrites assumps ths =
      if (ths = [])
      then failwith "try_rewrites"
      else (try (let th = inst_frees_in_hyps assumps (hd ths)
              in  let hyp_ths = map (EQT_ELIM o rewrite_hyp) (hyp th)
              in  itlist PROVE_HYP hyp_ths th)
           with Failure _ -> (try_rewrites assumps (tl ths))
           )
   in try (try_rewrites assumps (applicable_rewrites tm)) with Failure _ -> ALL_CONV tm;;

(*----------------------------------------------------------------------------*)
(* rewrite_explicit_value : conv                                              *)
(*                                                                            *)
(* Explicit values are normally unchanged by rewriting, but in the case of a  *)
(* numeric constant, it is expanded out into SUC form.                        *)
(*----------------------------------------------------------------------------*)

let rec rewrite_explicit_value tm =
   let rec conv tm = (num_CONV THENC TRY_CONV (RAND_CONV conv)) tm
   in ((TRY_CONV conv) THENC
       (TRY_CONV (ARGS_CONV rewrite_explicit_value))) tm;;

(*----------------------------------------------------------------------------*)
(* COND_T = |- (T => t1 | t2) = t1                                            *)
(* COND_F = |- (F => t1 | t2) = t2                                            *)
(*----------------------------------------------------------------------------*)

let [COND_T;COND_F] = CONJUNCTS (SPEC_ALL COND_CLAUSES);;

(*----------------------------------------------------------------------------*)
(* COND_LEFT =                                                                *)
(* |- !b x x' y. (b ==> (x = x')) ==> ((b => x | y) = (b => x' | y))          *)
(*----------------------------------------------------------------------------*)

let COND_LEFT =
 prove
  (`!b x x' (y:'a). (b ==> (x = x')) ==> ((if b then x else y) = (if b then x' else y))`,
   REPEAT GEN_TAC THEN
   BOOL_CASES_TAC `b:bool` THEN
   REWRITE_TAC []);;

(*----------------------------------------------------------------------------*)
(* COND_RIGHT =                                                               *)
(* |- !b y y' x. (~b ==> (y = y')) ==> ((b => x | y) = (b => x | y'))         *)
(*----------------------------------------------------------------------------*)

let COND_RIGHT =
 prove
  (`!b y y' (x:'a). (~b ==> (y = y')) ==> ((if b then x else y) = (if b then x else y'))`,
   REPEAT GEN_TAC THEN
   BOOL_CASES_TAC `b:bool` THEN
   REWRITE_TAC []);;

(*----------------------------------------------------------------------------*)
(* COND_ID = |- !b t. (b => t | t) = t                                        *)
(*----------------------------------------------------------------------------*)

(* Already defined in HOL *)

(*----------------------------------------------------------------------------*)
(* COND_RIGHT_F = |- (b => b | F) = b                                         *)
(*----------------------------------------------------------------------------*)

let COND_RIGHT_F =
 prove
  (`(if b then b else F) = b`,
   BOOL_CASES_TAC `b:bool` THEN
   REWRITE_TAC []);;

(*----------------------------------------------------------------------------*)
(* COND_T_F = |- (b => T | F) = b                                             *)
(*----------------------------------------------------------------------------*)

let COND_T_F =
 prove
  (`(if b then  T else F) = b`,
   BOOL_CASES_TAC `b:bool` THEN
   REWRITE_TAC []);;

(*----------------------------------------------------------------------------*)
(* rewrite_conditional : (term list -> conv) -> term list -> conv             *)
(*                                                                            *)
(* Rewriting conditionals. Takes a general rewrite function and a list of     *)
(* assumptions as arguments.                                                  *)
(*                                                                            *)
(* The function assumes that the term it is given is of the form "b => x | y" *)
(* First it recursively rewrites b to b'. If b' is T or F, the conditional is *)
(* reduced to x or y, respectively. The result is then rewritten recursively. *)
(* If b' is not T or F, both x and y are rewritten, under suitable additional *)
(* assumptions about b'. An attempt is then made to rewrite the new           *)
(* conditional with one of the following:                                     *)
(*                                                                            *)
(*    (b => x | x) ---> x                                                     *)
(*    (b => b | F) ---> b                                                     *)
(*    (b => T | F) ---> b                                                     *)
(*                                                                            *)
(* The three rules are tried in the order shown above.                        *)
(*----------------------------------------------------------------------------*)

let rewrite_conditional rewrite assumps tm =
try (let th1 = RATOR_CONV (RATOR_CONV (RAND_CONV (rewrite assumps))) tm
  in  let tm1 = rhs (concl th1)
  in  let (b',(x,y)) = dest_cond tm1
  in  if (is_T b') then
          TRANS th1 (((REWR_CONV COND_T) THENC (rewrite assumps)) tm1)
       else if (is_F b') then
          TRANS th1 (((REWR_CONV COND_F) THENC (rewrite assumps)) tm1)
      else let th2 = DISCH b' (rewrite (b'::assumps) x)
        in let x' = rand (rand (concl th2))
        in let th3 = MP (ISPECL [b';x;x';y] COND_LEFT) th2
        in let tm3 = rhs (concl th3)
        in let notb' = mk_neg b'
        in let th4 = DISCH notb' (rewrite (notb'::assumps) y)
        in let y' = rand (rand (concl th4))
        in let th5 = MP (ISPECL [b';y;y';x'] COND_RIGHT) th4
        in let th6 = ((REWR_CONV COND_ID) ORELSEC
                      (REWR_CONV COND_RIGHT_F) ORELSEC
                      (TRY_CONV (REWR_CONV COND_T_F))) (rhs (concl th5))
            in  TRANS (TRANS (TRANS th1 th3) th5) th6
 ) with Failure _ -> failwith "rewrite_conditional";;

(*----------------------------------------------------------------------------*)
(* EQ_T = |- (x = T) = x                                                      *)
(*----------------------------------------------------------------------------*)

let EQ_T =
 prove
  (`(x = T) = x`,
   BOOL_CASES_TAC `x:bool` THEN
   REWRITE_TAC []);;

(*----------------------------------------------------------------------------*)
(* EQ_EQ = |- (x = (y = z)) = ((y = z) => (x = T) | (x = F))                  *)
(*----------------------------------------------------------------------------*)

let EQ_EQ =
 prove
  (`(x = ((y:'a) = z)) = (if (y = z) then (x = T) else (x = F))`,
   BOOL_CASES_TAC `x:bool` THEN
   BOOL_CASES_TAC `(y:'a) = z` THEN
   REWRITE_TAC []);;

(*----------------------------------------------------------------------------*)
(* EQ_F = |- (x = F) = (x => F | T)                                           *)
(*----------------------------------------------------------------------------*)

let EQ_F =
 prove
  (`(x = F) = (if x then F else T)`,
   BOOL_CASES_TAC `x:bool` THEN
   REWRITE_TAC []);;

(*----------------------------------------------------------------------------*)
(* prove_terms_not_eq : term -> term -> thm                                   *)
(*                                                                            *)
(* Function to prove that the left-hand and right-hand sides of an equation   *)
(* are not equal. Works with Boolean constants, explicit values, and terms    *)
(* involving constructors and variables.                                      *)
(*----------------------------------------------------------------------------*)

let prove_terms_not_eq l r =
   let rec STRUCT_CONV tm =
      (bool_EQ_CONV ORELSEC
       NUM_EQ_CONV ORELSEC
       (fun tm -> let (l,r) = dest_eq tm
             in  let ty_name = (fst o dest_type) (type_of l)
             in  let (ty_info:shell_info) = sys_shell_info ty_name
             in  let ty_conv = ty_info.struct_conv
             in  ty_conv STRUCT_CONV tm) ORELSEC
       (* REFL_CONV ORELSEC   Omitted because it cannot generate false *)
       ALL_CONV) tm
   in try(let th = STRUCT_CONV (mk_eq (l,r))
       in  if (is_F (rhs (concl th))) then th else failwith ""
      ) with Failure _ -> failwith "prove_terms_not_eq";;

(*----------------------------------------------------------------------------*)
(* rewrite_equality : (term list -> term list -> conv) ->                     *)
(*                     term list -> term list -> conv                         *)
(*                                                                            *)
(* Function for rewriting equalities. Takes a general rewrite function, a     *)
(* chain of hypotheses and a list of assumptions as arguments.                *)
(*                                                                            *)
(* The left-hand and right-hand sides of the equality are rewritten           *)
(* recursively. If the two sides are then identical, the term is rewritten to *)
(* T. If it can be shown that the two sides are not equal, the term is        *)
(* rewritten to F. Otherwise, the function rewrites with the first of the     *)
(* following rules that is applicable (or it leaves the term unchanged if     *)
(* none are applicable):                                                      *)
(*                                                                            *)
(*    (x = T)       ---> x                                                    *)
(*    (x = (y = z)) ---> ((y = z) => (x = T) | (x = F))                       *)
(*    (x = F)       ---> (x => F | T)                                         *)
(*                                                                            *)
(* The result is then rewritten using the known lemmas (rewrite rules).       *)
(*----------------------------------------------------------------------------*)

let rewrite_equality rewrite hyp_chain assumps tm =
try (let th1 = ((RATOR_CONV (RAND_CONV (rewrite hyp_chain assumps))) THENC
             (RAND_CONV (rewrite hyp_chain assumps))) tm
  in  let tm1 = rhs (concl th1)
  in  let (l,r) = dest_eq tm1
  in  if (l = r)
      then TRANS th1 (EQT_INTRO (ISPEC l EQ_REFL))
      else try(TRANS th1 (prove_terms_not_eq l r))
         with Failure _ -> (let th2 = ((REWR_CONV EQ_T) ORELSEC
                       (REWR_CONV EQ_EQ) ORELSEC
                       (TRY_CONV (REWR_CONV EQ_F))) tm1
            in  let th3 = rewrite_with_lemmas
                             rewrite hyp_chain assumps (rhs (concl th2))
            in  TRANS (TRANS th1 th2) th3)
 ) with Failure _ -> failwith "rewrite_equality";;

(*----------------------------------------------------------------------------*)
(* rewrite_application :                                                      *)
(*    (term -> string list -> term list -> term list -> conv) ->              *)
(*     term -> string list -> term list -> term list -> conv                  *)
(*                                                                            *)
(* Function for rewriting applications. It takes a general rewriting function,*)
(* a literal (the literal containing the function call), a list of names of   *)
(* functions that are tentatively being opened up, a chain of hypotheses, and *)
(* a list of assumptions as arguments.                                        *)
(*                                                                            *)
(* The function begins by rewriting the arguments. It then determines the     *)
(* name of the function being applied. If this is a constructor, no further   *)
(* rewriting is done. Otherwise, from the function name, the number of the    *)
(* argument used for recursion (or zero if the definition is not recursive)   *)
(* and expansion theorems for each possible constructor are obtained. If the  *)
(* function is not recursive the call is opened up and the body is rewritten. *)
(* If the function has no definition, the application is rewritten using the  *)
(* known lemmas.                                                              *)
(*                                                                            *)
(* If the definition is recursive, but this function has already been         *)
(* tentatively opened up, the version of the application with the arguments   *)
(* rewritten is returned.                                                     *)
(*                                                                            *)
(* Otherwise, the application is rewritten with the known lemmas. If any of   *)
(* the lemmas are applicable the result of the rewrite is returned. Otherwise *)
(* the function determines the name of the constructor appearing in the       *)
(* recursive argument, and looks up its details. If this process fails due to *)
(* either the recursive argument not being an application of a constructor,   *)
(* or because the constructor is not known, the function call cannot be       *)
(* expanded, so the original call (with arguments rewritten) is returned.     *)
(*                                                                            *)
(* Provided a valid constructor is present in the recursive argument position *)
(* the call is tentatively opened up. The body is rewritten with the name of  *)
(* the function added to the `tentative openings' list. (Actually, the name   *)
(* is not added to the list if the recursive argument of the call was an      *)
(* explicit value). The result is compared with the unopened call to see if   *)
(* it has good properties. If it does, the simplified body is returned.       *)
(* Otherwise the unopened call is returned.                                   *)
(*----------------------------------------------------------------------------*)

let rewrite_application rewrite lit funcs hyp_chain assumps tm =
try (let th1 = ARGS_CONV (rewrite lit funcs hyp_chain assumps) tm
  in  let tm1 = rhs (concl th1)
  in  let (f,args) = strip_comb tm1
  in  let name = fst (dest_const f)
  in
  if (mem name (all_constructors ()))
  then th1
  else try
  (let (i,constructors) = get_def name
   in  if (i = 0) then
          (let th2 = REWR_CONV (snd (hd constructors)) tm1
           in  let th3 = rewrite lit funcs hyp_chain assumps (rhs (concl th2))
           in  TRANS (TRANS th1 th2) th3)
       else if (mem name funcs) then th1
       else let th2 =
               rewrite_with_lemmas (rewrite lit funcs) hyp_chain assumps tm1
            in  let tm2 = rhs (concl th2)
            in  if (tm2 = tm1)
                then try (let argi = el (i-1) args
                     in  let constructor =
                            (try (fst (dest_const (fst (strip_comb argi)))) with Failure _ -> "")
                     in  (let th = assoc constructor constructors
                            in  let th3 = REWR_CONV th tm1
                            in  let tm3 = rhs (concl th3)
                            in  let funcs' =
                                   if (is_explicit_value argi)
                                   then funcs
                                   else name::funcs
                            in  let th4 =
                                   rewrite lit funcs' hyp_chain assumps tm3
                            in  let tm4 = rhs (concl th4)
                            in  if (good_properties assumps tm1 tm4 lit)
                                then TRANS (TRANS th1 th3) th4
                                else th1)
                         ) with Failure _ -> th1
                else TRANS th1 th2)
  with Failure "get_def" ->
    (TRANS th1 (rewrite_with_lemmas (rewrite lit funcs) hyp_chain assumps tm1))
 ) with Failure _ -> failwith "rewrite_application";;

(*----------------------------------------------------------------------------*)
(* rewrite_term : term -> string list -> term list -> term list -> conv       *)
(*                                                                            *)
(* Function for rewriting a term. Arguments are as follows:                   *)
(*                                                                            *)
(*    lit       : the literal containing the term to be rewritten.            *)
(*    funcs     : names of functions that have been tentatively opened up.    *)
(*    hyp_chain : hypotheses that we are trying to satisfy by parent calls.   *)
(*    assumps   : a list of assumptions.                                      *)
(*    tm        : the term to be rewritten.                                   *)
(*----------------------------------------------------------------------------*)

let rec rewrite_term lit funcs hyp_chain assumps tm =
try (EQT_INTRO (ASSUME (find (fun t -> t = tm) assumps))) with Failure _ ->
try (EQF_INTRO (ASSUME (find (fun t -> t = mk_neg tm) assumps))) with Failure _ ->
try (let rewrite = rewrite_term lit funcs
  in  if (is_var tm) then ALL_CONV tm
      else if (is_explicit_value tm) then rewrite_explicit_value tm
      else if (is_cond tm) then rewrite_conditional (rewrite hyp_chain) assumps tm
      else if (is_eq tm) then rewrite_equality rewrite hyp_chain assumps tm
      else rewrite_application rewrite_term lit funcs hyp_chain assumps tm
 ) with Failure _ -> failwith "rewrite_term";;

(*----------------------------------------------------------------------------*)
(* COND_RAND = |- !f b x y. f (b => x | y) = (b => f x | f y)                 *)
(*----------------------------------------------------------------------------*)

(* Already defined in HOL *)

(*----------------------------------------------------------------------------*)
(* COND_RATOR = |- !b f g x. (b => f | g) x = (b => f x | g x)                *)
(*----------------------------------------------------------------------------*)

(* Already defined in HOL *)

(*----------------------------------------------------------------------------*)
(* MOVE_COND_UP_CONV : conv                                                   *)
(*                                                                            *)
(* Moves all conditionals in a term up to the top-level. Checks to see if the *)
(* term contains any conditionals before it starts to do inference. This      *)
(* improves the performance significantly. Alternatively, failure could be    *)
(* used to avoid rebuilding unchanged sub-terms. This would be even more      *)
(* efficient.                                                                 *)
(*----------------------------------------------------------------------------*)

let rec MOVE_COND_UP_CONV tm =
try(if (not (can (find_term is_cond) tm)) then ALL_CONV tm
  else if (is_cond tm) then
     ((RATOR_CONV (RATOR_CONV (RAND_CONV MOVE_COND_UP_CONV))) THENC
      (RATOR_CONV (RAND_CONV MOVE_COND_UP_CONV)) THENC
      (RAND_CONV MOVE_COND_UP_CONV)) tm
  else if (is_comb tm) then
     (let (op,arg) = dest_comb tm
      in  if (is_cond op) then
             ((REWR_CONV COND_RATOR) THENC MOVE_COND_UP_CONV) tm
          else if (is_cond arg) then
             ((REWR_CONV COND_RAND) THENC MOVE_COND_UP_CONV) tm
          else (let th = ((RATOR_CONV MOVE_COND_UP_CONV) THENC
                          (RAND_CONV MOVE_COND_UP_CONV)) tm
                in  let tm' = rhs (concl th)
                in  if (tm' = tm)
                    then th
                    else TRANS th (MOVE_COND_UP_CONV tm')))
  else ALL_CONV tm
 ) with Failure _ -> failwith "MOVE_COND_UP_CONV";;

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

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

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

(* Already proved *)

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

(* Already proved *)

(*----------------------------------------------------------------------------*)
(* LEFT_OR_OVER_AND = |- !t1 t2 t3. t1 \/ t2 /\ t3 = (t1 \/ t2) /\ (t1 \/ t3) *)
(*----------------------------------------------------------------------------*)

(* Already available in HOL *)

(*----------------------------------------------------------------------------*)
(* MOVE_NOT_THRU_CONDS_CONV : conv                                            *)
(*                                                                            *)
(* Function to push a negation down through (possibly) nested conditionals.   *)
(* Eliminates any double-negations that may be introduced.                    *)
(*----------------------------------------------------------------------------*)

let rec MOVE_NOT_THRU_CONDS_CONV tm =
 try (if (is_neg tm)
  then if (is_cond (rand tm))
       then ((REWR_CONV COND_RAND) THENC
             (RATOR_CONV (RAND_CONV MOVE_NOT_THRU_CONDS_CONV)) THENC
             (RAND_CONV MOVE_NOT_THRU_CONDS_CONV)) tm
       else TRY_CONV (REWR_CONV NOT_NOT_NORM) tm
  else ALL_CONV tm
 ) with Failure _ -> failwith "MOVE_NOT_THRU_CONDS_CONV";;

(*----------------------------------------------------------------------------*)
(* EXPAND_ONE_COND_CONV : conv                                                *)
(*                                                                            *)
(* The function takes a term which it assumes to be either a conditional or   *)
(* the disjunction of a conditional and some other term, and applies one of   *)
(* the following rewrites as appropriate:                                     *)
(*                                                                            *)
(*    |- (b => x | y) = (~b \/ x) /\ (b \/ y)                                 *)
(*                                                                            *)
(*    |- (b => x | y) \/ z = (~b \/ x \/ z) /\ (b \/ y \/ z)                  *)
(*                                                                            *)
(* If b happens to be a conditional, the negation of ~b is moved down through *)
(* the conditional (and any nested conditionals).                             *)
(*----------------------------------------------------------------------------*)

let EXPAND_ONE_COND_CONV tm =
try (((REWR_CONV COND_OR) ORELSEC (REWR_CONV COND_EXPAND)) THENC
  (RATOR_CONV (RAND_CONV (RATOR_CONV (RAND_CONV MOVE_NOT_THRU_CONDS_CONV)))))
 tm with Failure _ -> failwith "EXPAND_ONE_COND_CONV";;

(*----------------------------------------------------------------------------*)
(* OR_OVER_ANDS_CONV : conv -> conv                                           *)
(*                                                                            *)
(* Distributes an OR over an arbitrary tree of conjunctions and applies a     *)
(* conversion to each of the disjunctions that make up the new conjunction.   *)
(*----------------------------------------------------------------------------*)

let rec OR_OVER_ANDS_CONV conv tm =
   if (is_disj tm)
   then if (is_conj (rand tm))
        then ((REWR_CONV LEFT_OR_OVER_AND) THENC
              (RATOR_CONV (RAND_CONV (OR_OVER_ANDS_CONV conv))) THENC
              (RAND_CONV (OR_OVER_ANDS_CONV conv))) tm
        else conv tm
   else ALL_CONV tm;;

(*----------------------------------------------------------------------------*)
(* EXPAND_COND_CONV : conv                                                    *)
(*                                                                            *)
(* The function takes a term which it assumes to be either a conditional or   *)
(* the disjunction of a conditional and some other term, and expands the      *)
(* conditional into a disjunction using one of:                               *)
(*                                                                            *)
(*    |- (b => x | y) = (~b \/ x) /\ (b \/ y)                                 *)
(*                                                                            *)
(*    |- (b => x | y) \/ z = (~b \/ x \/ z) /\ (b \/ y \/ z)                  *)
(*                                                                            *)
(* The b, x and y may themselves be conditionals. If so, the function expands *)
(* these as well, and so on, until there are no more conditionals. At each    *)
(* stage disjunctions are distributed over conjunctions so that the final     *)
(* result is a conjunction `tree' where each of the conjuncts is a            *)
(* disjunction. The depth of a disjunction in the conjunction tree indicates  *)
(* the number of literals that have been added to the disjunction compared to *)
(* the original term.                                                         *)
(*----------------------------------------------------------------------------*)

let rec EXPAND_COND_CONV tm =
 try (EXPAND_ONE_COND_CONV THENC
  (RATOR_CONV (RAND_CONV ((RAND_CONV EXPAND_COND_CONV) THENC
                          (OR_OVER_ANDS_CONV EXPAND_COND_CONV)))) THENC
  (RAND_CONV ((RAND_CONV EXPAND_COND_CONV) THENC
              (OR_OVER_ANDS_CONV EXPAND_COND_CONV)))) tm
 with Failure _ -> ALL_CONV tm;;

(*----------------------------------------------------------------------------*)
(* SPLIT_CLAUSE_ON_COND_CONV : int -> conv                                    *)
(*                                                                            *)
(* The function takes a number n and a term which it assumes to be a          *)
(* disjunction of literals in which the (n-1)th argument has had all          *)
(* conditionals moved to the top level.                                       *)
(*                                                                            *)
(* The function dives down to the (n-1)th literal (disjunct) and expands the  *)
(* conditionals into disjunctions, resulting in a conjunction `tree' in which *)
(* each conjunct is a disjunction.                                            *)
(*                                                                            *)
(* As the function `backs out' from the (n-1)th literal it distributes the    *)
(* ORs over the conjunction tree.                                             *)
(*----------------------------------------------------------------------------*)

let SPLIT_CLAUSE_ON_COND_CONV n tm =
 try (funpow n (fun conv -> (RAND_CONV conv) THENC (OR_OVER_ANDS_CONV ALL_CONV))
     EXPAND_COND_CONV tm
 ) with Failure _ -> failwith "SPLIT_CLAUSE_ON_COND_CONV";;

(*----------------------------------------------------------------------------*)
(* simplify_one_literal : int -> term -> (thm # int)                          *)
(*                                                                            *)
(* Attempts to simplify one literal of a clause assuming the negations of the *)
(* other literals. The number n specifies which literal to rewrite. If n = 0, *)
(* the first literal is rewritten. The function fails if n is out of range.   *)
(*                                                                            *)
(* If the literal to be simplified is negative, the function simplifies the   *)
(* corresponding atom, and negates the result. If this new result is T or F,  *)
(* the clause is rebuilt by discharging the assumptions. This process may     *)
(* reduce the number of literals in the clause, so the theorem returned is    *)
(* paired with -1 (except when processing the last literal of a clause in     *)
(* which case returning 0 will, like -1, cause a failure when an attempt is   *)
(* made to simplify the next literal, but is safer because it can't cause     *)
(* looping if the literal has not been removed. This is the case when the     *)
(* last literal has been rewritten to F. In this situation, the discharging   *)
(* function does not eliminate the literal).                                  *)
(*                                                                            *)
(* If the simplified literal contains conditionals, these are brought up to   *)
(* the top-level. The clause is then rebuilt by discharging. If no            *)
(* conditionals were present the theorem is returned with 0, indicating that  *)
(* the number of literals has not changed. Otherwise the clause is split into *)
(* a conjunction of clauses, so that the conditionals are eliminated, and the *)
(* result is returned with the number 1 to indicate that the number of        *)
(* literals has increased.                                                    *)
(*----------------------------------------------------------------------------*)

let simplify_one_literal n tm =
try (let negate tm = if (is_neg tm) then (rand tm) else (mk_neg tm)
  and NEGATE th =
         let tm = rhs (concl th)
         and th' = AP_TERM `(~)` th
         in  if (is_T tm) then TRANS th' (el 1 (CONJUNCTS NOT_CLAUSES))
             else if (is_F tm) then TRANS th' (el 2 (CONJUNCTS NOT_CLAUSES))
             else th'
  in let (overs,y,unders) = match (chop_list n (disj_list tm)) with
                                       | (overs,y::unders) -> (overs,y,unders)
                                       | _ -> failwith ""
(*      ) with Failure _ -> failwith "" *)
  in  let overs' = map negate overs
      and unders' = map negate unders
  in  let th1 =
         if (is_neg y)
         then NEGATE (rewrite_term y [] [] (overs' @ unders') (rand y))
         else rewrite_term y [] [] (overs' @ unders') y
  in  let tm1 = rhs (concl th1)
  in  if ((is_T tm1) or (is_F tm1))
      then (MULTI_DISJ_DISCH (overs',unders') th1,
            if (unders = []) then 0 else (-1))
      else let th2 = TRANS th1 (MOVE_COND_UP_CONV tm1)
           in  let tm2 = rhs (concl th2)
           in  let th3 = MULTI_DISJ_DISCH (overs',unders') th2
           in  if (is_cond tm2)
               then (CONV_RULE (RAND_CONV (SPLIT_CLAUSE_ON_COND_CONV n)) th3,1)
               else (th3,0)
 ) with Failure _ -> failwith "simplify_one_literal";;

(*----------------------------------------------------------------------------*)
(* simplify_clause : int -> term -> (term list # proof)                       *)
(* simplify_clauses : int -> term -> (term list # proof)                      *)
(*                                                                            *)
(* Functions for simplifying a clause by rewriting each literal in turn       *)
(* assuming the negations of the others.                                      *)
(*                                                                            *)
(* The integer argument to simplify_clause should be zero initially. It will  *)
(* then attempt to simplify the first literal. If the result is true, no new  *)
(* clauses are produced. Otherwise, the function proceeds to simplify the     *)
(* next literal. This has to be done differently according to the changes     *)
(* that took place when simplifying the first literal.                        *)
(*                                                                            *)
(* If there was a reduction in the number of literals, this must have been    *)
(* due to the literal being shown to be false, because the true case has      *)
(* already been eliminated. So, there must be one less literal, and so n is   *)
(* unchanged on the recursive call. If there was no change in the number of   *)
(* literals, n is incremented by 1. Otherwise, not only have new literals     *)
(* been introduced, but also the clause has been split into a conjunction of  *)
(* clauses. simplify_clauses is called to handle this case.                   *)
(*                                                                            *)
(* When all the literals have been processed, n will become out of range and  *)
(* cause a failure. This is trapped, and the simplified clause is returned.   *)
(*                                                                            *)
(* When the clause has been split into a conjunction of clauses, the depth of *)
(* a clause in the tree of conjunctions indicates how many literals have been *)
(* added to that clause. simplify_clauses recursively splits conjunctions,    *)
(* incrementing n as it proceeds, until it reaches a clause. It then calls    *)
(* simplify_clause to deal with the clause.                                   *)
(*----------------------------------------------------------------------------*)

let rec simplify_clause n tm =
try (let (th,change_flag) = simplify_one_literal n tm
  in  let tm' = rhs (concl th)
  in  if (is_T tm')
      then ([],apply_proof ( fun ths -> EQT_ELIM th) [])
      else let (tms,proof) =
              if (change_flag < 0) then simplify_clause n tm'
              else if (change_flag = 0) then simplify_clause (n + 1) tm'
              else simplify_clauses (n + 1) tm'
           in  (tms,(fun ths -> EQ_MP (SYM th) (proof ths))))
 with Failure _ -> ([tm],apply_proof hd [tm])

and simplify_clauses n tm =
try (let (tm1,tm2) = dest_conj tm
  in  let (tms1,proof1) = simplify_clauses (n + 1) tm1
      and (tms2,proof2) = simplify_clauses (n + 1) tm2
  in  (tms1 @ tms2,
       fun ths -> let (ths1,ths2) = chop_list (length tms1) ths
             in  CONJ (proof1 ths1) (proof2 ths2)))
 with Failure _ -> (simplify_clause n tm);;


let HL_simplify_clause tm =
try (
  let rules = itlist union [rewrite_rules();flat (defs());all_accessor_thms()] [] in
  let th = SIMP_CONV rules tm
  in  let tm' = rhs (concl th)
  in  let tmc = try (rand o concl o COND_ELIM_CONV) tm' with Failure _ -> tm' in
      if (is_T tm')
      then ([],apply_proof ( fun ths -> EQT_ELIM th ) [])
      else ([tm'],apply_proof ((EQ_MP (SYM th)) o hd) [tm'])
 )
 with Failure _ -> ([tm],apply_proof hd [tm])

(*----------------------------------------------------------------------------*)
(* simplify_heuristic : (term # bool) -> ((term # bool) list # proof)         *)
(*                                                                            *)
(* Wrapper for simplify_clause. This function has the correct type and        *)
(* properties to be used as a `heuristic'. In particular, if the result of    *)
(* simplify_clause is a single clause identical to the input clause,          *)
(* a failure is generated.                                                    *)
(*----------------------------------------------------------------------------*)

let simplify_heuristic (tm,(ind:bool)) =
try (let (tms,proof) = simplify_clause 0 tm
  in  if (tms = [tm])
      then failwith ""
      else  (proof_print_string_l "-> Simplify Heuristic" () ;  (map (fun tm -> (tm,ind)) tms,proof))
 ) with Failure _ -> failwith "simplify_heuristic";;

let HL_simplify_heuristic (tm,(ind:bool)) =
try (let (tms,proof) = HL_simplify_clause tm
  in  if (tms = [tm])
      then failwith ""
      else  (proof_print_string_l "-> HL Simplify Heuristic" () ;  (map (fun tm -> (tm,ind)) tms,proof))
 ) with Failure _ -> failwith "HL_simplify_heuristic";;

(*----------------------------------------------------------------------------*)
(* NOT_EQ_F = |- !x. ~(x = x) = F                                             *)
(*----------------------------------------------------------------------------*)

let NOT_EQ_F =
 GEN_ALL
  (TRANS (AP_TERM `(~)` (SPEC_ALL REFL_CLAUSE))
   (el 1 (CONJUNCTS NOT_CLAUSES)));;

(*----------------------------------------------------------------------------*)
(* subst_heuristic : (term # bool) -> ((term # bool) list # proof)            *)
(*                                                                            *)
(* `Heuristic' for eliminating from a clause, a negated equality between a    *)
(* variable and another term not containing the variable. For example, given  *)
(* the clause:                                                                *)
(*                                                                            *)
(*    x1 \/ ~(x = t) \/ x3 \/ f x \/ x5                                       *)
(*                                                                            *)
(* the function returns the clause:                                           *)
(*                                                                            *)
(*    x1 \/ F \/ x3 \/ f t \/ x5                                              *)
(*                                                                            *)
(* So, all occurrences of x are replaced by t, and the equality x = t is      *)
(* `thrown away'. The F could be eliminated, but the simplification heuristic *)
(* will deal with it, so there is no point in duplicating the code.           *)
(*                                                                            *)
(* The function fails if there are no equalities that can be eliminated.      *)
(*                                                                            *)
(* The function proves the following three theorems:                          *)
(*                                                                            *)
(*    ~(x = t) |- x1 \/ ~(x = t) \/ x3 \/ f x \/ x5                           *)
(*                                                                            *)
(*       x = t |- x1 \/ ~(x = t) \/ x3 \/ f x \/ x5 =                         *)
(*                x1 \/     F    \/ x3 \/ f t \/ x5                           *)
(*                                                                            *)
(*             |- (x = t) \/ ~(x = t)                                         *)
(*                                                                            *)
(* and returns the term "x1 \/ F \/ x3 \/ f t \/ x5" to be proved. When given *)
(* this term as a theorem, it is possible to prove from the second theorem:   *)
(*                                                                            *)
(*       x = t |- x1 \/ ~(x = t) \/ x3 \/ f x \/ x5                           *)
(*                                                                            *)
(* which together with the first and third theorems yields a theorem for the  *)
(* original clause.                                                           *)
(*----------------------------------------------------------------------------*)

let subst_heuristic (tm,(ind:bool)) =
try (let checkx (v,t) = (is_var v) & (not (mem v (frees t)))
  in  let rec split_disjuncts tml =
         if (can (check (checkx o dest_eq o dest_neg)) (hd tml))
         then ([],tml)
         else (fun (l1,l2) -> ((hd tml)::l1,l2)) (split_disjuncts (tl tml))
  in  let (overs,neq::unders) = split_disjuncts (disj_list tm)
  in  let eq = dest_neg neq
  in  let (v,t) = dest_eq eq
  in  let ass = ASSUME neq
  in  let th1 = itlist DISJ2 overs (try DISJ1 ass (list_mk_disj unders) with Failure _ -> ass)
      and th2 = SUBS [ISPEC t NOT_EQ_F] (SUBST_CONV [(ASSUME eq,v)] tm tm)
      and th3 = SPEC eq EXCLUDED_MIDDLE
  in  let tm' = rhs (concl th2)
  in  let proof th = DISJ_CASES th3 (EQ_MP (SYM th2) th) th1
  in   (proof_print_string_l "-> Subst Heuristic" () ;  ([(tm',ind)],apply_proof (proof o hd) [tm']))
 ) with Failure _ -> failwith "subst_heuristic";;
