(******************************************************************************)
(* FILE          : struct_equal.ml                                            *)
(* DESCRIPTION   : Proof procedure for simplifying an equation between two    *)
(*                 data-structures of the same type.                          *)
(*                                                                            *)
(* READS FILES   : <none>                                                     *)
(* WRITES FILES  : <none>                                                     *)
(*                                                                            *)
(* AUTHOR        : R.J.Boulton & T.F.Melham                                   *)
(* DATE          : 4th June 1992                                              *)
(*                                                                            *)
(* LAST MODIFIED : R.J.Boulton                                                *)
(* DATE          : 14th October 1992                                          *)
(*                                                                            *)
(* LAST MODIFIED : P. Papapanagiotou (University of Edinburgh)                *)
(* DATE          : 2008                                                       *)
(******************************************************************************)

let subst_occs =
  let rec subst_occs slist tm =
    let applic,noway = partition (fun (i,(t,x)) -> aconv tm x) slist in
    let sposs = map (fun (l,z) -> let l1,l2 = partition ((=) 1) l in
                                  (l1,z),(l2,z)) applic in
    let racts,rrest = unzip sposs in
    let acts = filter (fun t -> not (fst t = [])) racts in
    let trest = map (fun (n,t) -> (map (C (-) 1) n,t)) rrest in
    let urest = filter (fun t -> not (fst t = [])) trest in
    let tlist = urest @ noway in
      if acts = [] then
      if is_comb tm then
        let l,r = dest_comb tm in
        let l',s' = subst_occs tlist l in
        let r',s'' = subst_occs s' r in
        mk_comb(l',r'),s''
      else if is_abs tm then
        let bv,bod = dest_abs tm in
        let gv = genvar(type_of bv) in
        let nbod = vsubst[gv,bv] bod in
        let tm',s' = subst_occs tlist nbod in
        alpha bv (mk_abs(gv,tm')),s'
      else
        tm,tlist
    else
      let tm' = (fun (n,(t,x)) -> subst[t,x] tm) (hd acts) in
      tm',tlist in
  fun ilist slist tm -> fst(subst_occs (zip ilist slist) tm);;

let GSUBS substfn ths th =
   let ls = map (lhs o concl) ths
       in let vars = map (genvar o type_of) ls
       in let w = substfn (List.combine ls vars) (concl th)
   in SUBST (List.combine ths vars) w th ;;

let SUBS_OCCS nlths th =
   try (let (nll, ths) = unzip nlths
   in GSUBS (subst_occs nll) ths th
   ) with Failure _ -> failwith "SUBS_OCCS";;

(*----------------------------------------------------------------------------*)
(* VAR_NOT_EQ_STRUCT_OF_VAR_CONV : (thm # thm list # thm list) -> conv        *)
(*                                                                            *)
(* Proof method developed through discussion between                          *)
(* R. Boulton, T. Melham and A. Pitts.                                        *)
(*                                                                            *)
(* This conversion can be used to prove that a variable is not equal to a     *)
(* structure containing that variable as a proper subterm. The structures are *)
(* restricted to applications of constructors from a single recursive type.   *)
(* The leaf nodes must be either variables or 0-ary constructors of the type. *)
(*                                                                            *)
(* The theorems taken as arguments are the induction, distinctness and        *)
(* injectivity theorems for the recursive type, as proved by the functions:   *)
(*                                                                            *)
(*    prove_induction_thm                                                     *)
(*    prove_constructors_distinct                                             *)
(*    prove_constructors_one_one                                              *)
(*                                                                            *)
(* Since the latter two functions may fail, the distinctness and injectivity  *)
(* theorems are passed around as lists of conjuncts, so that a failure        *)
(* results in an empty list.                                                  *)
(*                                                                            *)
(* Examples of input terms:                                                   *)
(*                                                                            *)
(*    ~(l = CONS h l)                                                         *)
(*    ~(CONS h1 (CONS h2 l) = l)                                              *)
(*    ~(n = SUC(SUC(SUC n)))                                                  *)
(*    ~(t = TWO (ONE u) (THREE v (ONE t) (TWO u (ONE t))))                    *)
(*                                                                            *)
(* where the last example is for the type defined by:                         *)
(*                                                                            *)
(*    test = ZERO | ONE test | TWO test test | THREE test test test           *)
(*                                                                            *)
(* The procedure works by first generalising the structure to eliminate any   *)
(* irrelevant substructures. If the variable occurs more than once in the     *)
(* structure the more deeply nested occurrences are replaced by new variables *)
(* because multiple occurrences of the variable prevent the induction from    *)
(* working. The generalised term for the last example is:                     *)
(*                                                                            *)
(*    TWO a (THREE v (ONE t) b)                                               *)
(*                                                                            *)
(* The procedure then forms a conjunction of the inequalities for this term   *)
(* and all of its `rotations':                                                *)
(*                                                                            *)
(*    !t. (!a v b. ~(t = TWO a (THREE v (ONE t) b))) /\                       *)
(*        (!a v b. ~(t = THREE v (ONE (TWO a t)) b)) /\                       *)
(*        (!a v b. ~(t = ONE (TWO a (THREE v t b))))                          *)
(*                                                                            *)
(* This can be proved by a straightforward structural induction. The reason   *)
(* for including the rotations is that the induction hypothesis required for  *)
(* the proof of the original generalised term is the rotation of it.          *)
(*                                                                            *)
(* The procedure could be optimised by detecting duplicated rotations. For    *)
(* example it is not necessary to prove:                                      *)
(*                                                                            *)
(*    !n. ~(n = SUC(SUC(SUC n))) /\                                           *)
(*        ~(n = SUC(SUC(SUC n))) /\                                           *)
(*        ~(n = SUC(SUC(SUC n)))                                              *)
(*                                                                            *)
(* in order to prove "~(n = SUC(SUC(SUC n)))" because the structure is its    *)
(* own rotations. It is sufficient to prove:                                  *)
(*                                                                            *)
(*    !n. ~(n = SUC(SUC(SUC n)))                                              *)
(*                                                                            *)
(* The procedure currently uses backwards proof. It would probably be more    *)
(* efficient to use forwards proof.                                           *)
(*----------------------------------------------------------------------------*)

let VAR_NOT_EQ_STRUCT_OF_VAR_CONV =
try( let number_list l =
    let rec number_list' n l =
       if (l = [])
       then []
       else (hd l,n)::(number_list' (n + 1) (tl l))
    in number_list' 1 l
 in  let name = fst o dest_const
 in  let occurrences constrs v st =
        let rec occurrences' v st path =
           if (not (type_of st = type_of v)) then []
           else if (st = v) then [rev path]
           else if (is_var st) then []
           else let (f,args) =
                  (check ( ((can (C assoc constrs)) o name o fst) )) (strip_comb st)
(* Boulton was using hashI here... but I don't know why *)
                in  flat (map (fun (arg,n) -> occurrences' v arg (n::path))
                                 (number_list args))
        in occurrences' v st []
 in  let min_length l =
        let rec min_length' (x,n) l =
           if (l = [])
           then x
           else if (length (hd l) < n)
                then min_length' (hd l,length (hd l)) (tl l)
                else min_length' (x,n) (tl l)
        in if (l = [])
           then failwith "min_length"
           else min_length' (hd l,length (hd l)) (tl l)
 in  let rec generalise (st,occ) =
        let rec replace_side_structs (n,argn',binding) m args =
           if (args = [])
           then ([],[])
           else let m' = m + 1
                and arg = hd args
                in  let (rest,bind) =
                       replace_side_structs (n,argn',binding) m' (tl args)
                in  if (m' = n) then ((argn'::rest),(binding @ bind))
                    else if (is_var arg) then ((arg::rest),((arg,arg)::bind))
                    else let var = genvar (type_of arg)
                         in  ((var::rest),((var,arg)::bind))
        in if (occ = [])
           then (st,[])
           else let (f,args) = strip_comb st
                and (n::occ') = occ
                in  let (argn',binding) = generalise (el (n-1) args,occ')
                in  let (args',bind) =
                           replace_side_structs (n,argn',binding) 0 args
                in  (list_mk_comb (f,args'),bind)
 in  let rec constr_apps v (st,occ) =
        let rec replace_argn (n,argn') m args =
           if (args = [])
           then []
           else let m' = m + 1
                in  if (m' = n)
                    then argn'::(tl args)
                    else (hd args)::(replace_argn (n,argn') m' (tl args))
        in if (occ = [])
           then []
           else let (f,args) = strip_comb st
                and (n::occ') = occ
                in  let args' = replace_argn (n,v) 0 args
                in  (list_mk_comb (f,args'))::(constr_apps v (el (n-1) args,occ'))
 in  let rotations l =
        let rec rotations' l n =
           if (n < 1)
           then []
           else l::(rotations' ((tl l) @ [hd l]) (n - 1))
        in rotations' l (length l)
 in  let two_constrs = (hash (fst o strip_comb) (fst o strip_comb)) o
                       dest_eq o dest_neg o snd o strip_forall o concl
 in  let flip (x,y) = (y,x)
 in  let DEPTH_SYM = GEN_ALL o NOT_EQ_SYM o SPEC_ALL
 in  let rec arg_types ty =
        try (match (dest_type ty) with
         | ("fun",[argty;rest]) ->  argty::(arg_types rest)
         | _ -> [])
        with Failure _ -> []
 in  let name_and_args = ((hash I) arg_types) o dest_const
 in
 fun (induction,distincts,oneOnes) ->
 let half_distincts = map (fun th -> ((hash name) name) (two_constrs th), th) distincts
 in  let distincts = half_distincts @ (map ((hash flip) DEPTH_SYM) half_distincts)
 in  let ind_goals =
        (conjuncts o fst o dest_imp o snd o dest_forall o concl) induction
 in  let constrs =
        map (name_and_args o fst o strip_comb o rand o snd o strip_forall o
             snd o (splitlist dest_imp) o snd o strip_forall) ind_goals
 in
 fun tm ->
 (let (l,r) = dest_eq (dest_neg tm)
  in  let (flipped,v,st) =
         if (is_var l)
         then if (is_var r) then failwith "" else (false,l,r)
         else if (is_var r)
              then (true,r,l)
              else failwith ""
  in  let occ = min_length (occurrences constrs v st)
  in  let (st',bind) = generalise (st,occ)
  in  let (vars,subterms) = List.split bind
  in  let apps = constr_apps v (st',occ)
  in  let rotats =
         map (end_itlist (fun t1 t2 -> subst [(t2,v)] t1)) (rotations apps)
  in  let uneqs = map (mk_neg o (curry mk_eq v)) rotats
  in  let conj =
         mk_forall (v,list_mk_conj (map (curry list_mk_forall vars) uneqs))
  in  let th1 =
         prove (conj,INDUCT_TAC_ induction THEN
                     ASM_REWRITE_TAC (oneOnes @ (map snd distincts)))
  in  let th2 = (hd o CONJUNCTS o (SPEC v)) th1
  in  let th3 = SPECL subterms th2
  in  let th4 = if flipped then (NOT_EQ_SYM th3) else th3
  in  EQT_INTRO (CONV_RULE (C ALPHA tm) th4)
 )) with Failure _ -> failwith "VAR_NOT_EQ_STRUCT_OF_VAR_CONV";;

(*----------------------------------------------------------------------------*)
(* CONJS_CONV : conv -> conv                                                  *)
(*                                                                            *)
(* Written by T.F.Melham.                                                     *)
(* Modified by R.J.Boulton.                                                   *)
(*                                                                            *)
(* Apply a given conversion to a sequence of conjuncts.                       *)
(*                                                                            *)
(* * need to check T case                                                     *)
(* * need to flatten conjuncts on RHS                                         *)
(*----------------------------------------------------------------------------*)

let CONJS_CONV =
try(
   let is st th = try(fst(dest_const(rand(concl th))) = st) with Failure _ -> false
   in  let v1 = genvar `:bool` and v2 = genvar `:bool`
   in  let fthm1 =
          let th1 = ASSUME (mk_eq(v1,`F`))
          in  let cnj = mk_conj(v1,v2)
          in  let th1 = DISCH cnj (EQ_MP th1 (CONJUNCT1 (ASSUME cnj)))
          in  let th2 = DISCH `F` (CONTR cnj (ASSUME `F`))
          in  DISCH (mk_eq(v1,`F`)) (IMP_ANTISYM_RULE th1 th2)
   in  let fthm2 = CONV_RULE(ONCE_DEPTH_CONV(REWR_CONV CONJ_SYM)) fthm1
   in  let fandr th tm = MP (INST [(lhs(concl th),v1);(tm,v2)] fthm1) th
   in  let fandl th tm = MP (INST [(lhs(concl th),v1);(tm,v2)] fthm2) th
   in  let tthm1 =
          let th1 = ASSUME (mk_eq(v1,`T`))
          in  let th2 = SUBS_OCCS [[2],th1] (REFL (mk_conj(v1,v2)))
          in  DISCH (mk_eq(v1,`T`)) (ONCE_REWRITE_RULE [] th2)
   in  let tthm2 = CONV_RULE(ONCE_DEPTH_CONV(REWR_CONV CONJ_SYM)) tthm1
   in  let tandr th tm = MP (INST [(lhs(concl th),v1);(tm,v2)] tthm1) th
   in  let tandl th tm = MP (INST [(lhs(concl th),v1);(tm,v2)] tthm2) th
   in  let rec cconv conv tm =
          (let (c,cs) = dest_conj tm
           in  let cth = conv c
           in  if (is "F" cth) then fandr cth cs else
               let csth = cconv conv cs
               in  if (is "F" csth) then fandl csth c
                   else if (is "T" cth) then TRANS (tandr cth cs) csth
                   else if (is "T" csth) then TRANS (tandl csth c) cth
                   else try (MK_COMB((AP_TERM `(/\)` cth),csth)) with Failure _ -> conv tm )
   in  fun conv tm -> cconv conv tm) with Failure _ -> failwith "CONJS_CONV";;

(*----------------------------------------------------------------------------*)
(* ONE_STEP_RECTY_EQ_CONV : (thm # thm list # thm list) -> conv -> conv       *)
(*                                                                            *)
(* Single step conversion for equality between structures of a single         *)
(* recursive type.                                                            *)
(*                                                                            *)
(* Based on code written by T.F.Melham.                                       *)
(*                                                                            *)
(* The theorems taken as arguments are the induction, distinctness and        *)
(* injectivity theorems for the recursive type, as proved by the functions:   *)
(*                                                                            *)
(*    prove_induction_thm                                                     *)
(*    prove_constructors_distinct                                             *)
(*    prove_constructors_one_one                                              *)
(*                                                                            *)
(* Since the latter two functions may fail, the distinctness and injectivity  *)
(* theorems are passed around as lists of conjuncts.                          *)
(*                                                                            *)
(* If one side of the equation is a variable and that variable appears in the *)
(* other side (nested in a structure) the equation is proved false.           *)
(*                                                                            *)
(* If the top-level constructors on the two sides of the equation are         *)
(* distinct the equation is proved false.                                     *)
(*                                                                            *)
(* If the top-level constructors on the two sides of the equation are the     *)
(* same a conjunction of equations is generated, one equation for each        *)
(* argument of the constructor. The conversion given as argument is then      *)
(* applied to each conjunct. If any of the applications of this conversion    *)
(* fail, so will the entire call.                                             *)
(*                                                                            *)
(* In other conditions the function fails.                                    *)
(*----------------------------------------------------------------------------*)
(* Taken from HOL90 *)

let ONE_STEP_RECTY_EQ_CONV (induction,distincts,oneOnes) =
   let NOT_EQ_CONV =
          EQF_INTRO o EQT_ELIM o
          (VAR_NOT_EQ_STRUCT_OF_VAR_CONV (induction,distincts,oneOnes)) o
          mk_neg
   in  let INJ_REW = GEN_REWRITE_CONV I oneOnes
(* Deleted empty_rewrites - GEN_REWRITE_CONV different in hol light - hope it works *)
   in  let ths1 = map SPEC_ALL distincts
   in  let ths2 = map (GEN_ALL o EQF_INTRO o NOT_EQ_SYM) ths1
   in  let dths = ths2 @ (map (GEN_ALL o EQF_INTRO) ths1)
   in  let DIST_REW = GEN_REWRITE_CONV I dths
   in  fun conv -> NOT_EQ_CONV ORELSEC
                  DIST_REW ORELSEC
                  (INJ_REW THENC (CONJS_CONV conv)) ORELSEC
                  (fun tm -> failwith "ONE_STEP_RECTY_EQ_CONV")

(*----------------------------------------------------------------------------*)
(* RECTY_EQ_CONV : (thm # thm list # thm list) -> conv                        *)
(*                                                                            *)
(* Function to simplify as far as possible an equation between two structures *)
(* of some type, the type being specified by the triple of theorems. The      *)
(* structures may involve variables. The result may be a conjunction of       *)
(* equations simpler than the original.                                       *)
(*----------------------------------------------------------------------------*)

let RECTY_EQ_CONV (induction,distincts,oneOnes) =
try (
   let one_step_conv = ONE_STEP_RECTY_EQ_CONV (induction,distincts,oneOnes)
   and REFL_CONV tm =
      let (l,r) = dest_eq tm
      in  if (l = r)
          then EQT_INTRO (REFL l)
          else failwith "REFL_CONV"
   in  let rec conv tm =
          (one_step_conv conv ORELSEC REFL_CONV ORELSEC ALL_CONV) tm
   in  fun tm -> conv tm ) with Failure _ -> failwith "RECTY_EQ_CONV";;
