(* ======================================================================================== *)
(*                 Proof-objects for HOL-light, exportation to Coq                          *)
(*                                                                                          *)
(*       Steven Obua, TU Mnchen, December 2004                                              *)
(*       Chantal Keller, Laboratoire d'Informatique de Polytechnique (France), January 2010 *)
(*                                                                                          *)
(*       based on Sebastian Skalberg's HOL4 proof-objects                                   *)
(* ======================================================================================== *)

#load "unix.cma";;
#load "depgraph.cma";;


module type Proofobject_primitives =
  sig

    type proof

    val proof_REFL : term -> proof
    val proof_TRANS : proof * proof -> proof
    val proof_MK_COMB : proof * proof -> proof
    val proof_ASSUME : term -> proof
    val proof_EQ_MP : proof -> proof -> proof
    val proof_IMPAS : proof -> proof -> proof
    val proof_DISCH : proof -> term -> proof
    val proof_DEDUCT_ANTISYM_RULE : proof * term -> proof * term -> proof
    val proof_BETA : term -> proof
    val proof_ABS : term -> proof -> proof
    val proof_INST_TYPE : (hol_type * hol_type) list -> proof -> proof
    val proof_INST : (term * term) list -> proof -> proof
    val proof_new_definition : string -> hol_type -> term -> proof
    val proof_CONJ : proof -> proof -> proof
    val proof_CONJUNCT1 : proof -> proof
    val proof_CONJUNCT2 : proof -> proof
    val proof_new_basic_type_definition :
      string -> string * string -> term * term -> proof -> proof
    val proof_SPEC : term -> proof -> proof
    val proof_SYM : proof -> proof
    val proof_GEN : proof -> term -> proof
    val proof_DISJ1 : proof -> term -> proof
    val proof_DISJ2 : proof -> term -> proof
    val proof_NOTI : proof -> proof
    val proof_NOTE : proof -> proof
    val proof_CONTR : proof -> term -> proof
    val proof_DISJCASES : proof -> proof -> proof -> proof
    val proof_CHOOSE : term -> proof -> proof -> proof
    val proof_EXISTS : term -> term -> proof -> proof

    val new_axiom_name : string -> string
    val proof_new_axiom : string -> term -> proof

    val save_proof : string -> proof -> (term option) -> unit
    val proof_database : unit -> ((string * proof * (term option)) list)

    val export_saved_proofs : unit -> unit
    val export_one_proof : string -> unit
    val export_list : string list -> unit
  end;;


module Proofobjects : Proofobject_primitives = struct


  let THEORY_NAME = "hollight";;



  (****** Utilities ******)

  (* this is a little bit dangerous, because the function is not injective,
     but I guess one can live with that *)
  let modify = function
    | "/" -> "_slash_"
    | "\\" -> "_backslash_"
    | "=" -> "_equal_"
    | ">" -> "_greaterthan_"
    | "<" -> "_lessthan_"
    | "?" -> "_questionmark_"
    | "!" -> "_exclamationmark_"
    | "*" -> "_star_"
    | "~" -> "_tilde_"
    | "," -> "_comma_"
    | "@" -> "_at_"
    | "+" -> "_plus_"
    | "-" -> "_minus_"
    | "%" -> "_percent_"
    | "$" -> "_dollar_"
    | "." -> "_dot_"
    | "'" -> "_quote_"
    | "|" -> "_pipe_"
    | ":" -> "_colon_"
    | s -> s;;

  let mfc s = implode (map modify (explode s));;

  let ensure_export_directory thyname =
    let dir = Sys.getenv "HOLPROOFEXPORTDIR" in
    let dirsub = Filename.concat dir "hollight" in
    let dirsubsub = Filename.concat dirsub thyname in
    let mk d = if Sys.file_exists d then () else Unix.mkdir d 509
    in mk dir; mk dirsub; mk dirsubsub; dirsubsub;;


  (****** Proofs ******)

  type proof_info_rec =
      {disk_info: (string * string) option ref;
       status: int ref;
       references: int ref;
       queued: bool ref};;

  type proof_info = Info of proof_info_rec;;

  type proof =
    | Proof of (proof_info * proof_content * (unit -> unit))
  and proof_content =
    | Prefl of term
    | Pbeta of string * hol_type * term
    | Pinstt of proof * (string * hol_type) list
    | Pabs of proof * string * hol_type
    | Pdisch of proof * term
    | Phyp of term
    | Pspec of proof * term
    | Pinst of proof * (string * hol_type * term) list
    | Pgen of proof * string * hol_type
    | Psym of proof
    | Ptrans of proof * proof
    | Pcomb of proof * proof
    | Peqmp of proof * proof
    | Pexists of proof * term * term
    | Pchoose of string * hol_type * proof * proof
    | Pconj of proof * proof
    | Pconjunct1 of proof
    | Pconjunct2 of proof
    | Pdisj1 of proof * term
    | Pdisj2 of proof * term
    | Pdisjcases of proof * proof * proof
    | Pnoti of proof
    | Pnote of proof
    | Pcontr of proof * term
    | Pimpas of proof * proof
    | Paxm of string * term
    | Pdef of string * hol_type * term
    | Ptyintro of hol_type * string * hol_type list * string * string * term;;

  let content_of (Proof (_,p,_)) = p;;

  let inc_references (Proof(Info{references=r},_,_) as p) = incr r; p;;

  let mk_proof p = Proof(Info {disk_info = ref None; status = ref 0; references = ref 0; queued = ref false}, p, fun () -> ());;

  let global_ax_counter = let counter = ref 1 in let f = fun () -> (incr counter; !counter - 1) in f;;

  let new_axiom_name n = "ax_"^n^"_"^(string_of_int (global_ax_counter () ));;


  (* corresponds to REFL *)

  let proof_REFL t = mk_proof (Prefl t);;


  (* corresponds to TRANS, with a simple improvment *)

  let proof_TRANS (p,q) =
    match (content_of p, content_of q) with
      | (Prefl _,_) -> q
      | (_, Prefl _) -> p
      | _ -> mk_proof (Ptrans (inc_references p, inc_references q));;


  (* corresponds to MK_COMB -> Pcomb *)

  let proof_MK_COMB (p1,p2) =
    match (content_of p1, content_of p2) with
      | (Prefl tm1, Prefl tm2) -> mk_proof (Prefl (mk_comb (tm1, tm2)))
      | _ ->  mk_proof (Pcomb (inc_references p1, inc_references p2));;


  (* corresponds to ASSUME -> Phyp *)

  let proof_ASSUME t = mk_proof (Phyp t);;


  (* corresponds to EQ_MP, with a simple improvment *)

  let proof_EQ_MP p q =
    match content_of p with
      | Prefl _ -> q
      | _ -> mk_proof (Peqmp(inc_references p, inc_references q));;


  (* corresponds to IMP_ANTISYM_RULE th1 th2
     not a base rule
     used only in the extended mode *)

  (*  A1 |- t1 ==> t2     A2 |- t2 ==> t1 *)
  (* ------------------------------------- IMP_ANTISYM_RULE *)
  (*          A1 u A2 |- t1 <=> t2 *)

  let proof_IMPAS p1 p2 = mk_proof (Pimpas (inc_references p1, inc_references p2));;


  (* corresponds to DISCH
     not a base rule
     used only in the extended mode *)

  (*        A |- t *)
  (* -------------------- DISCH `u` *)
  (*  A - {u} |- u ==> t *)

  let proof_DISCH p t = mk_proof (Pdisch(inc_references p, t));;


  (* corresponds to DEDUCT_ANTISYM_RULE *)
  (* made with IMPAS and DISCH (whereas in HOL-Light IMPAS is made with DAR and UNDISCH...) *)

  (*       A |- p       B |- q *)
  (* ---------------------------------- *)
  (*  (A - {q}) u (B - {p}) |- p <=> q *)

  let proof_DEDUCT_ANTISYM_RULE (p1,t1) (p2,t2) =
    proof_IMPAS (proof_DISCH p1 t2) (proof_DISCH p2 t1);;


  (* BETA is a base rule *)

  let proof_BETA tm =
    try
      let f,_ = dest_comb tm in
      let v,bod = dest_abs f in
      let (x, ty) = dest_var v in
      mk_proof (Pbeta (x, ty, bod))
    with
      | _ -> failwith "proof_BETA"


  (* corresponds to ABS, with a simple improvment *)

  let proof_ABS x p =
    match x with
      | Var(s, ty) ->
          mk_proof (Pabs(inc_references p, s, ty))
      | _ -> failwith "proof_ABS: not a variable";;


  (* corresponds to INST_TYPE -> Pinstt *)

  let proof_INST_TYPE s p =
    mk_proof (Pinstt(inc_references p, List.map (
                       fun (ty1, ty2) -> match ty2 with
                         | Tyvar s -> (s, ty1)
                         | _ -> failwith "proof_INST_TYPE: some redex is not a type variable"
                     ) s));;


  (* corresponds to INST *)

  let proof_INST s p =
    mk_proof (Pinst(inc_references p, List.map (
                      fun (t1, t2) -> match t2 with
                        | Var(s, ty) ->
                            (s, ty, t1)
                        | _ -> failwith "proof_INST: some redex is not a term variable"
                    ) s));;


  (* proof_new_definition is called in Thm.new_basic_definition. This
     latter helps to define basic concepts such as T, AND... (almost
     everything in Bool)... and to define derived rules!! -> Pdef *)

  let proof_new_definition cname ty t =
    mk_proof (Pdef (cname, ty, t));;


  (* proof_new_axiom is called in Thm.new_axiom. This latter transforms
     a term of type bool into a theorem. The main three axioms are
     ETA_AX, SELECT_AX and INFINITY_AX. The other axiom is ax (in
     drule.ml) -> Paxm *)

  let proof_new_axiom axname t = mk_proof (Paxm (axname, t));;


  (* corresponds to CONJ
     not a base rule
     used only in the extended mode *)

  let proof_CONJ p1 p2 = mk_proof (Pconj (inc_references p1, inc_references p2));;


  (* corresponds to CONJUNCT1
     not a base rule
     used only in the extended mode
     also used in Thm.new_basic_definition *)

  let proof_CONJUNCT1 p = mk_proof (Pconjunct1 (inc_references p));;


  (* corresponds to CONJUNCT2
     not a base rule
     used only in the extended mode
     also used in Thm.new_basic_definition *)

  let proof_CONJUNCT2 p = mk_proof (Pconjunct2 (inc_references p));;


  (* used only in Thm.new_basic_definition for the same purpose as for
     CONJUNCTi -> Ptyintro *)

  let proof_new_basic_type_definition tyname (absname, repname) (pt,tt) _ =
    let rty = type_of tt in
    let tyvars = sort (<=) (type_vars_in_term pt) in

    mk_proof(Ptyintro(rty, tyname, tyvars, absname, repname, pt));;


  (* ---- used only in substitute_proof calls ---- *)

  (* corresponds to Bool.SPEC, the !-elimination rule *)

  let proof_SPEC s p = mk_proof (Pspec(inc_references p, s));;


  (* corresponds to Equal.SYM, the symmetry rule *)

  let proof_SYM p = mk_proof (Psym(inc_references p));;


  (* corresponds to Bool.GEN, the !-introduction rule *)

  let proof_GEN p a =
    match a with
      | Var(s, ty) ->
          mk_proof (Pgen(inc_references p, s, ty))
      | _ -> failwith "proof_GEN: not a term variable";;


  (* corresponds to Bool.DISJ1, the \/-left introduction rule *)

  let proof_DISJ1 p a = mk_proof (Pdisj1 (inc_references p, a));;


  (* corresponds to Bool.DISJ2, the \/-right introduction rule *)

  let proof_DISJ2 p a = mk_proof (Pdisj2 (inc_references p, a));;


  (* corresponds to Bool.NOT_INTRO, the following rule: *)
  (*     A |- t ==> F *)
  (*    --------------  NOT_INTRO *)
  (*       A |- ~t *)

  let proof_NOTI p = mk_proof (Pnoti (inc_references p));;


  (* corresponds to Bool.NOT_ELIM, the following rule: *)
  (*       A |- ~t *)
  (*    --------------  NOT_ELIM *)
  (*     A |- t ==> F *)

  let proof_NOTE p = mk_proof (Pnote (inc_references p));;


  (* corresponds to Bool.CONTR, the intuitionistic F-elimination rule: *)
  (*     A |- F *)
  (*    --------  CONTR `t` *)
  (*     A |- t *)

  let proof_CONTR p a = mk_proof (Pcontr (inc_references p, a));;


  (* corresponds to Bool.DISJ_CASES, the \/-elimination rule: *)
  (*     A |- t1 \/ t2     A1 u {t1} |- t      A2 u {t2} |- t *)
  (*    ------------------------------------------------------  DISJ_CASES *)
  (*                     A u A1 u A2 |- t *)

  let proof_DISJCASES p q r =
    mk_proof (Pdisjcases (inc_references p, inc_references q, inc_references r));;


  (* corresponds to Bool.CHOOSE, the ?-elimination rule: *)
  (*     A1 |- ?x. s[x]    A2 |- t *)
  (*    -------------------------------  CHOOSE (`v`,(A1 |- ?x. s)) *)
  (*      A1 u (A2 - {s[v/x]}) |- t *)
  (* Where v is not free in A2 - {s[v/x]} or t. *)

  let proof_CHOOSE a p q =
    let (x,ty) = dest_var a in
    mk_proof (Pchoose (x, ty, inc_references p, inc_references q));;


  (* corresponds to Bool.EXISTS, the ?-introduction rule: *)
  (*     A |- p[u/x] *)
  (*    -------------  EXISTS (`?x. p`,`u`) *)
  (*     A |- ?x. p *)
  (* x is p, y is u *)

  let proof_EXISTS etm y p  =
    let _,x = dest_comb etm in
    mk_proof (Pexists (inc_references p, x, y));;


  (****** Utilities for exportation ******)

  let content_of (Proof (_,x,_)) = x;;


  let disk_info_of (Proof(Info {disk_info=di},_,_)) = !di;;


  let set_disk_info_of (Proof(Info {disk_info=di},_,_)) thyname thmname =
    di := Some (thyname,thmname);;

  let reset_disk_info_of1 ((Proof(Info {disk_info=di}, _, _)) as p) =
    di := None; p;;
  let reset_disk_info_of2 (Proof(Info {disk_info=di}, _, _)) =
    di := None;;


  let references (Proof (Info info,_,_)) = !(info.references);;


  let glob_counter = ref 0;;


  let get_counter () = incr glob_counter; !glob_counter;;


  let get_iname = string_of_int o get_counter;;


  let next_counter () = !glob_counter;;


  let trivial p =
    match (content_of p) with
      | Prefl _ -> true
      | Pbeta _ -> true
      | Paxm _ -> true
      | Phyp _ -> true
      | _ -> false;;


  let do_share p = references p > 1 & not (trivial p);;


  (****** Types and terms modification ******)

  let  idT = Hashtbl.create 17;;
  let defT = Hashtbl.create 17;;

  let  idT_ref = ref 1;;
  let defT_ref = ref 1;;

  let make_idT x =
    try Hashtbl.find idT x with | Not_found -> let n = !idT_ref in incr idT_ref; Hashtbl.add idT x n; n;;

  let make_defT x =
    try Hashtbl.find defT x with | Not_found -> let n = !defT_ref in incr defT_ref; Hashtbl.add defT x n; n;;


  type ntype =
    | Ntvar of int
    | Nbool
    | Nnum
    | Narrow of ntype * ntype
    | Ntdef of int * ntype list;;


  let rec hol_type2ntype = function
    | Tyvar x -> Ntvar (make_idT x)
    | Tyapp (s, _) when s = "bool" -> Nbool
    (* | Tyapp (s, _) when s = "ind" -> Nnum *)
    | Tyapp (s, l) when s = "fun" ->
        (match l with
           | [a;b] -> Narrow (hol_type2ntype a, hol_type2ntype b)
           | _ -> failwith "hol_type2ntype: wrong number of arguments for fun")
    | Tyapp (s, l) -> Ntdef (make_defT s, List.map hol_type2ntype l);;


  let  idV = Hashtbl.create 17;;
  let defV = Hashtbl.create 17;;

  let  idV_ref = ref 1;;
  let defV_ref = ref 1;;

  let make_idV x X =
    try
      fst (Hashtbl.find idV x)
    with | Not_found ->
      let n = !idV_ref in incr idV_ref; Hashtbl.add idV x (n,X); n;;

  let make_defV x X f =
    try let (a,_,_) = (Hashtbl.find defV x) in a with | Not_found -> let n = !defV_ref in incr defV_ref; Hashtbl.add defV x (n,X,f); n;;


  type ncst =
    | Heq of ntype
    | Heps of ntype
    | Hand
    | Hor
    | Hnot
    | Himp
    | Htrue
    | Hfalse
    | Hforall of ntype
    | Hexists of ntype;;


  type nterm =
    | Ndbr of int
    | Nvar of int * ntype
    | Ncst of ncst
    | Ndef of int * ntype
    | Napp of nterm * nterm
    | Nabs of ntype * nterm;;


  let rec ext_var x (ty: ntype) i = function
    | [] -> Nvar (make_idV x ty, ty)
    | (y,typ)::l -> if ((x = y) && (ty = typ)) then Ndbr i else ext_var x ty (i+1) l;;


  let rec term2nterm l = function
    | Var (x, ty) -> ext_var x (hol_type2ntype ty) 0 l
    | Comb (t1, t2) -> Napp (term2nterm l t1, term2nterm l t2)
    | Abs (t1, t2) ->
        (match t1 with
           | Var (x, ty) ->
               let typ = hol_type2ntype ty in
               Nabs (typ, term2nterm ((x,typ)::l) t2)
           | _ -> failwith "term2nterm: first argument of an abstraction is not a variable")
    | Const (s, ty) when s = "=" ->
        (match hol_type2ntype ty with
           | Narrow(a, _) -> Ncst (Heq a)
           | _ -> failwith "term2nterm: constant = must have arrow type")
    | Const (s, ty) when s = "@" ->
        (match hol_type2ntype ty with
           | Narrow(_, a) -> Ncst (Heps a)
           | _ -> failwith "term2nterm: constant @ must have arrow type")
    | Const (s, ty) when s = "/\\" -> Ncst Hand
    | Const (s, ty) when s = "\\/" -> Ncst Hor
    | Const (s, ty) when s = "~" -> Ncst Hnot
    | Const (s, ty) when s = "==>" -> Ncst Himp
    | Const (s, ty) when s = "T" -> Ncst Htrue
    | Const (s, ty) when s = "F" -> Ncst Hfalse
    | Const (s, ty) when s = "_FALSITY_" -> Ncst Hfalse
    | Const (s, ty) when s = "!" ->
        (match hol_type2ntype ty with
           | Narrow(Narrow (a, _), _) -> Ncst (Hforall a)
           | _ -> failwith "term2nterm: constant ! must have arrow type")
    | Const (s, ty) when s = "?" ->
        (match hol_type2ntype ty with
           | Narrow(Narrow (a, _), _) -> Ncst (Hexists a)
           | _ -> failwith "term2nterm: constant ? must have arrow type")
    | Const (s, ty) ->
        let typ = hol_type2ntype ty in
        Ndef(make_defV s typ true, typ);;

  let term2nterm t = term2nterm [] t;;


  (****** Proof exportation ******)

  let rec print_list out str snil scons = function
    | [] -> out snil
    | t::q -> out "("; out scons; out " "; str t; out " "; print_list out str snil scons q; out ")";;


  let print_names out x = out (string_of_int x); out "%positive";;


  let print_type (out: string -> unit) ty =

    let rec print_ntype = function
      | Ntvar x -> out "(TVar "; print_names out x; out ")"
      | Nbool -> out "Bool"
      | Nnum -> out "Num"
      | Narrow(a, b) -> out "("; print_ntype a; out " --> "; print_ntype b; out ")"
      | Ntdef(s, l) -> out "(TDef "; print_names out s; out " "; print_list out print_ntype "Tnil" "Tcons" l; out ")" in

    print_ntype ty;;


  let print_cst out = function
    | Heq ty -> out "(Heq "; print_type out ty; out ")"
    | Heps ty -> out "(Heps "; print_type out ty; out ")"
    | Hand -> out "Hand"
    | Hor -> out "Hor"
    | Hnot -> out "Hnot"
    | Himp -> out "Himp"
    | Htrue -> out "Htrue"
    | Hfalse -> out "Hfalse"
    | Hforall ty -> out "(Hforall "; print_type out ty; out ")"
    | Hexists ty -> out "(Hexists "; print_type out ty; out ")";;


  let print_term out t =

    let rec print_nterm = function
      | Ndbr n -> out "(Dbr "; out (string_of_int n); out ")"
      | Nvar(x, ty) -> out "(Var "; print_names out x; out " "; print_type out ty; out ")"
      | Ncst c -> out "(Cst "; print_cst out c; out ")"
      | Ndef(a, ty) -> out "(Def "; print_names out a; out " "; print_type out ty; out ")"
      | Napp(t1, t2) -> out "(App "; print_nterm t1; out " "; print_nterm t2; out ")"
      | Nabs(ty, t) -> out "(Abs "; print_type out ty; out " "; print_nterm t; out ")" in

    print_nterm t;;


  (* Exportation *)

  let total = ref 0;;

  type nproof_content =
    | Nprefl of nterm
    | Npbeta of int * ntype * nterm
    | Npinstt of nproof_content * (int * ntype) list
    | Npabs of nproof_content * int * ntype
    | Npdisch of nproof_content * nterm
    | Nphyp of nterm
    | Npspec of nproof_content * nterm
    | Npinst of nproof_content * (int * ntype * nterm) list
    | Npgen of nproof_content * int * ntype
    | Npsym of nproof_content
    | Nptrans of nproof_content * nproof_content
    | Npcomb of nproof_content * nproof_content
    | Npeqmp of nproof_content * nproof_content
    | Npexists of nproof_content * nterm * nterm
    | Npchoose of int * ntype * nproof_content * nproof_content
    | Npconj of nproof_content * nproof_content
    | Npconjunct1 of nproof_content
    | Npconjunct2 of nproof_content
    | Npdisj1 of nproof_content * nterm
    | Npdisj2 of nproof_content * nterm
    | Npdisjcases of nproof_content * nproof_content * nproof_content
    | Npnoti of nproof_content
    | Npnote of nproof_content
    | Npcontr of nproof_content * nterm
    | Npimpas of nproof_content * nproof_content
    | Npaxm of string * nterm
    | Npdef of int * ntype * nterm
    | Nptyintro of ntype * ntype * int * int * nterm
    | Nfact of string;;


  let the_types = Hashtbl.create 17;;
  let count_types = ref (-1);;

  let share_types out ty =

    let rec share_types ty =
      try Hashtbl.find the_types ty with
        | Not_found ->
            incr count_types;
            let name = THEORY_NAME^"_type_"^(string_of_int !count_types) in
            (match ty with
               | Narrow(a,b) ->
                   let n1 = share_types a in
                   let n2 = share_types b in
                   out "\nDefinition "; out name; out " := "; out n1; out " --> "; out n2; out "."
               | Ntdef(i,l) ->
                   let names = List.map share_types l in
                   out "\nDefinition "; out name; out " := TDef "; print_names out i; out " "; print_list out out "Tnil" "Tcons" names; out "."
               | t -> out "\nDefinition "; out name; out " := "; print_type out t; out ".");
            Hashtbl.add the_types ty name;
            name in

    share_types ty;;


  let the_terms = Hashtbl.create 17;;
  let count_terms = ref (-1);;

  let share_csts out out_types name = function
    | Heq a ->
        let n = share_types out_types a in
        out "\nDefinition "; out name; out " := Cst (Heq "; out n; out ")."
    | Heps a ->
        let n = share_types out_types a in
        out "\nDefinition "; out name; out " := Cst (Heps "; out n; out ")."
    | Hand -> out "\nDefinition "; out name; out " := Cst Hand."
    | Hor -> out "\nDefinition "; out name; out " := Cst Hor."
    | Hnot -> out "\nDefinition "; out name; out " := Cst Hnot."
    | Himp -> out "\nDefinition "; out name; out " := Cst Himp."
    | Htrue -> out "\nDefinition "; out name; out " := Cst Htrue."
    | Hfalse -> out "\nDefinition "; out name; out " := Cst Hfalse."
    | Hforall a ->
        let n = share_types out_types a in
        out "\nDefinition "; out name; out " := Cst (Hforall "; out n; out ")."
    | Hexists a ->
        let n = share_types out_types a in
        out "\nDefinition "; out name; out " := Cst (Hexists "; out n; out ")."

  let share_terms out out_types tm =

    let rec share_terms tm =
      try Hashtbl.find the_terms tm with
        | Not_found ->
            incr count_terms;
            let name = THEORY_NAME^"_term_"^(string_of_int !count_terms) in
            (match tm with
               | Napp(t1,t2) ->
                   let n1 = share_terms t1 in
                   let n2 = share_terms t2 in
                   out "\nDefinition "; out name; out " := App "; out n1; out " "; out n2; out "."
               | Nabs(ty,t) ->
                   let n = share_terms t in
                   let ny = share_types out_types ty in
                   out "\nDefinition "; out name; out " := Abs "; out ny; out " "; out n; out "."
               | Nvar(i,ty) ->
                   let ny = share_types out_types ty in
                   out "\nDefinition "; out name; out " := Var "; print_names out i; out " "; out ny; out "."
               | Ndef(i,ty) ->
                   let ny = share_types out_types ty in
                   out "\nDefinition "; out name; out " := Def "; print_names out i; out " "; out ny; out "."
               | Ncst c -> share_csts out out_types name c
               | t -> out "\nDefinition "; out name; out " := "; print_term out t; out ".");
            Hashtbl.add the_terms tm name;
            name in

    share_terms tm;;


  let export_proof out share_type share_term p =

    let rec wp = function
      | Nprefl tm ->
          let tm2 = share_term tm in
          out "(Prefl "; out tm2; out ")"
      | Npbeta (n, ty, tm) ->
          let tm2 = share_term tm in
          let ty2 = share_type ty in
          out "(Pbeta "; print_names out n; out " "; out ty2; out " "; out tm2; out ")"
      | Npinstt(p,lambda) ->
          out "(Pinstt ";
          wp p;
          out " "; print_list out (fun (s, ty) ->
                                     let ty2 = share_type ty in
                                     out "("; print_names out s; out ", "; out ty2; out ")") "nil" "cons" lambda; out ")"
      | Npabs(p,x,ty) ->
          let ty2 = share_type ty in
          out "(Pabs ";
          wp p;
          out " "; print_names out x;
          out " "; out ty2; out ")"
      | Npdisch(p,tm) ->
          let tm2 = share_term tm in
          out "(Pdisch ";
          wp p;
          out " "; out tm2; out ")"
      | Nphyp tm ->
          let tm2 = share_term tm in
          out "(Phyp "; out tm2; out ")"
      | Npaxm(_, _) -> ()
      | Npdef(_, _, _) -> ()
      | Nptyintro(_, _, _, _, _) -> ()
      | Npspec(p,t) ->
          let t2 = share_term t in
          out "(Pspec ";
          wp p;
          out " "; out t2; out ")"
      | Npinst(p,theta) ->
          out "(Pinst ";
          wp p;
          out " "; print_list out (fun (s, ty, t) ->
                                     let t2 = share_term t in
                                     let ty2 = share_type ty in
                                     out "("; print_names out s; out ", "; out ty2; out ", "; out t2; out ")") "nil" "cons" theta; out ")"
      | Npgen(p,x,ty) ->
          let ty2 = share_type ty in
          out "(Pgen ";
          wp p;
          out " "; print_names out x; out " "; out ty2; out ")"
      | Npsym p ->
          out "(Psym ";
          wp p;
          out ")"
      |  Nptrans(p1,p2) ->
           out "(Ptrans ";
           wp p1;
           out " ";
           wp p2;
           out ")"
      | Npcomb(p1,p2) ->
          out "(Pcomb ";
          wp p1;
          out " ";
          wp p2;
          out ")"
      | Npeqmp(p1,p2) ->
          out "(Peqmp ";
          wp p1;
          out " ";
          wp p2;
          out ")"
      | Npexists(p,ex,w) ->
          let ex2 = share_term ex in
          let w2 = share_term w in
          out "(Pexists ";
          wp p;
          out " "; out ex2; out " "; out w2; out ")"
      | Npchoose(x,ty,p1,p2) ->
          let ty2 = share_type ty in
          out "(Pchoose "; print_names out x; out " "; out ty2; out " ";
          wp p1;
          out " ";
          wp p2;
          out ")"
      | Npconj(p1,p2) ->
          out "(Pconj ";
          wp p1;
          out " ";
          wp p2;
          out ")"
      | Npimpas(p1,p2) ->
          out "(Pimpas ";
          wp p1;
          out " ";
          wp p2;
          out ")"
      | Npconjunct1 p ->
          out "(Pconjunct1 ";
          wp p;
          out ")"
      |  Npconjunct2 p ->
           out "(Pconjunct2 ";
           wp p;
           out ")"
      | Npdisj1(p,tm) ->
          let tm2 = share_term tm in
          out "(Pdisj1 ";
          wp p;
          out " "; out tm2; out ")"
      | Npdisj2(p,tm) ->
          let tm2 = share_term tm in
          out "(Pdisj2 ";
          wp p;
          out " "; out tm2; out ")"
      | Npdisjcases(p1,p2,p3) ->
          out "(Pdisjcases ";
          wp p1;
          out " ";
          wp p2;
          out " ";
          wp p3;
          out ")"
      | Npnoti p ->
          out "(Pnoti ";
          wp p;
          out ")"
      | Npnote p ->
          out "(Pnote ";
          wp p;
          out ")"
      | Npcontr(p,tm) ->
          let tm2 = share_term tm in
          out "(Pcontr ";
          wp p;
          out " "; out tm2; out ")"
      | Nfact(thm) -> out "(Poracle "; out thm; out "_def)" in

    wp p;;


  let export_ht out share_term h t thmname =
    out "\n\n\nDefinition "; out thmname; out "_h := ";
    (match h with
       | [] -> out "hyp_empty"
       | _ -> print_list out (fun tm ->
                                let tm2 = share_term tm in
                                out tm2) "nil" "cons" h);
    out ".\n\nDefinition "; out thmname; out "_t := ";
    let t2 = share_term t in
    out t2; out ".";;


  let export_lemma out share_type share_term p thmname =
    out "\n\nLemma "; out thmname; out "_lemma : deriv "; out thmname; out "_h "; out thmname;
    out "_t.\nProof.\n  vm_cast_no_check (proof2deriv_correct "; export_proof out share_type share_term p; out ").\nQed.";;


  let export_lemma_def out tree thmname =
    out "\n\nLemma "; out thmname; out "_lemma : deriv "; out thmname; out "_h "; out thmname;
    out "_t.\nProof.\n  vm_cast_no_check (proof2deriv_correct "; out tree; out ").\nQed.";;


  let export_sig out thmname =
    out "\n\nDefinition "; out thmname; out "_def := my_exist "; out thmname; out "_lemma.";;


  let export_def out thmname =
    out "\n\nParameter "; out thmname; out "_lemma : deriv "; out thmname; out "_h "; out thmname; out "_t.";;


  let export_tdef out thmname =
    out "\n\nParameter "; out thmname; out "_lemma : deriv "; out thmname; out "_h "; out thmname; out "_t.";;


  let export_axiom out thmname =
    out "\n\nAxiom "; out thmname; out "_lemma : deriv "; out thmname; out "_h "; out thmname; out "_t.";;


  (* Transforming a proof into a derivation *)

  let rec opt_nth n l =
    match (n, l) with
      | 0, (x::_) -> Some x
      | 0, [] -> None
      | p, (_::l) -> opt_nth (p-1) l
      | _, _ -> None;;


  let type_cst = function
    | Heq a -> Narrow(a, Narrow(a, Nbool))
    | Heps a -> Narrow(Narrow(a, Nbool), a)
    | Hand -> Narrow(Nbool, Narrow(Nbool, Nbool))
    | Hor -> Narrow(Nbool, Narrow(Nbool, Nbool))
    | Hnot -> Narrow(Nbool, Nbool)
    | Himp -> Narrow(Nbool, Narrow(Nbool, Nbool))
    | Htrue -> Nbool
    | Hfalse -> Nbool
    | Hforall a -> Narrow(Narrow(a, Nbool), Nbool)
    | Hexists a -> Narrow(Narrow(a, Nbool), Nbool);;


  let rec infer g = function
    | Ndbr n -> opt_nth n g
    | Nvar (_, a) -> Some a
    | Ncst c -> Some (type_cst c)
    | Ndef (_, a) -> Some a
    | Napp (t1, t2) ->
        (match infer g t1, infer g t2 with
           | Some (Narrow (u1, u2)), Some v -> if u1 = v then Some u2 else None
           | _, _ -> None)
    | Nabs (a, u) ->
        (match infer (a::g) u with
           | Some b -> Some (Narrow (a, b))
           | None -> None);;


  let rec close_aux t x a i =
    match t with
      | Ndbr n -> Ndbr (if n < i then n else n+1)
      | Nvar (y, b) -> if ((x = y) && (a = b)) then Ndbr i else Nvar (y, b)
      | Napp (t1, t2) -> Napp (close_aux t1 x a i, close_aux t2 x a i)
      | Nabs (b, u) -> Nabs(b, close_aux u x a (i+1))
      | u -> u;;

  let close t x a = close_aux t x a 0;;


  let rec subst_idt_type_aux x = function
    | [] -> Ntvar x
    | (y,a)::q -> if x = y then a else subst_idt_type_aux x q;;

  let rec subst_idt_type t s =
    match t with
      | Ntvar x -> subst_idt_type_aux x s
      | Ntdef (a, l) -> Ntdef (a, subst_idt_list_type l s)
      | Narrow (a, b) -> Narrow (subst_idt_type a s, subst_idt_type b s)
      | u -> u

  and subst_idt_list_type l s = List.map (fun t -> subst_idt_type t s) l;;

  let rec subst_idt t s =
    match t with
      | Nvar (x, y)      -> Nvar (x, subst_idt_type y s)
      | Ncst (Heq a)     -> Ncst (Heq (subst_idt_type a s))
      | Ncst (Heps a)    -> Ncst (Heps (subst_idt_type a s))
      | Ncst (Hforall a) -> Ncst (Hforall (subst_idt_type a s))
      | Ncst (Hexists a) -> Ncst (Hexists(subst_idt_type a s))
      | Ndef (c, d)      -> Ndef (c, subst_idt_type d s)
      | Napp (t1, t2)    -> Napp (subst_idt t1 s, subst_idt t2 s)
      | Nabs (a, t)      -> Nabs (subst_idt_type a s, subst_idt t s)
      | u                -> u;;

  let subst_idt_context g s = List.map (fun a -> subst_idt_type a s) g;;

  let rec subst_idv_aux x y s =
      match s with
        | [] -> Nvar (x, y)
        | (z, t, u)::q -> if ((x = z) && (y = t)) then u else subst_idv_aux x y q;;

  let rec subst_idv t s =
    match t with
      | Nvar (x, y) -> subst_idv_aux x y s
      | Napp (t1, t2) -> Napp (subst_idv t1 s, subst_idv t2 s)
      | Nabs (a, t) -> Nabs (a, subst_idv t s)
      | u -> u;;

  let rec wf_substitution_idv = function
    | [] -> true
    | (_,y,t)::q ->
        match infer [] t with
          | Some z -> if (y = z) then wf_substitution_idv q else false
          | None -> false;;


  let rec is_not_free x y = function
    | Nvar (z, t) -> (x != z) or (not (y = t))
    | Napp (t1, t2) -> (is_not_free x y t1) && (is_not_free x y t2)
    | Nabs (_, u) -> is_not_free x y u
    | _ -> true;;


  let rec lift_term u i j =
    match u with
      | Ndbr n -> if n >= i then Ndbr (j + n) else Ndbr n
      | Napp (u1, u2) -> Napp (lift_term u1 i j, lift_term u2 i j)
      | Nabs (a, t) -> Nabs (a, lift_term t (i+1) j)
      | u -> u;;

  let rec subst_db t n u =
    match t with
      | Ndbr i -> if i < n then Ndbr i else if i = n then u else Ndbr (i-1)
      | Napp (t1, t2) -> Napp (subst_db t1 n u, subst_db t2 n u)
      | Nabs (a, t) -> Nabs (a, subst_db t (n+1) (lift_term u 0 1))
      | u -> u;;

  let nopen t u = subst_db t 0 u;;


  let heq a t u = Napp (Napp (Ncst (Heq a), t), u);;
  let hequiv t u = Napp (Napp (Ncst (Heq Nbool), t), u);;
  let himp t u = Napp (Napp (Ncst Himp, t), u);;
  let hand t u = Napp (Napp (Ncst Hand, t), u);;
  let hor t u = Napp (Napp (Ncst Hor, t), u);;
  let hnot t = Napp (Ncst Hnot, t);;
  let htrue = Ncst Htrue;;
  let hfalse = Ncst Hfalse;;
  let hforall a p = Napp (Ncst (Hforall a), Nabs (a, p));;
  let hexists a p = Napp (Ncst (Hexists a), Nabs (a, p));;


  let hyp_empty = [];;

  let rec hyp_remove e = function
    | [] -> []
    | t::q -> if (e = t) then q else t::(hyp_remove e q);;

  let rec hyp_add e = function
    | [] -> [e]
    | t::q -> if (e = t) then t::q else t::(hyp_add e q);;

  let hyp_union l m = List.fold_left (fun n e -> hyp_add e n) m l;;

  let hyp_map f l = List.fold_left (fun m e -> hyp_add (f e) m) [] l;;

  let hyp_singl e = [e];;

  let rec hyp_is_not_free x y = function
    | [] -> true
    | t::q -> (is_not_free x y t) && (hyp_is_not_free x y q);;

  let hyp_subst_idt h s = hyp_map (fun t -> subst_idt t s) h;;

  let hyp_subst_idv h s = hyp_map (fun t -> subst_idv t s) h;;


  let rec eq_type a b = match (a,b) with
    | Ntvar i, Ntvar j -> i = j
    | Nbool, Nbool -> true
    | Nnum, Nnum -> true
    | Narrow(a1, b1), Narrow(a2, b2) -> (eq_type a1 a2) && (eq_type b1 b2)
    | Ntdef(i,l), Ntdef(j,m) -> (i = j) && (eq_list_type l m)
    | _, _ -> false

  and eq_list_type l m = match (l,m) with
    | [], [] -> true
    | t1::q1, t2::q2 -> (eq_type t1 t2) && (eq_list_type q1 q2)
    | _, _ -> false;;


  let eq_cst a b = match (a,b) with
    | Heq a, Heq b -> eq_type a b
    | Heps a, Heps b -> eq_type a b
    | Hand, Hand -> true
    | Hor, Hor -> true
    | Hnot, Hnot -> true
    | Himp, Himp -> true
    | Htrue, Htrue -> true
    | Hfalse, Hfalse -> true
    | Hforall a, Hforall b -> eq_type a b
    | Hexists a, Hexists b -> eq_type a b
    | _, _ -> false;;


  let rec eq_term a b = match (a,b) with
    | Ndbr i, Ndbr j -> i = j
    | Nvar(i,a), Nvar(j,b) -> (i = j) && (eq_type a b)
    | Ncst c, Ncst d -> eq_cst c d
    | Ndef(i,a), Ndef(j,b) -> (i = j) && (eq_type a b)
    | Napp(a1,b1), Napp(a2,b2) -> (eq_term a1 a2) && (eq_term b1 b2)
    | Nabs(t1,a1), Nabs(t2,a2) -> (eq_type t1 t2) && (eq_term a1 a2)
    | _, _ -> false;;


  let derivs = Hashtbl.create 17;;


  let rec proof2deriv = function

    | Nprefl t ->
        (match infer [] t with
           | Some a -> Some (hyp_empty, heq a t t)
           | None -> (print_string "Nprefl\n"); None)

    | Npbeta (x, y, t) ->
        (match infer [] t with
           | Some a -> Some (hyp_empty,
                             heq a (Napp (Nabs (y, close t x y), Nvar (x, y))) t)
           | None -> (print_string "Npbeta\n"); None)

    | Npinstt (q, l) ->
        (match proof2deriv q with
           | Some (h,v) -> Some (hyp_subst_idt h l, subst_idt v l)
           | None -> (print_string "Npinstt\n"); None)

    | Npabs (q, x, y) ->
        (match proof2deriv q with
           | Some (h, t) ->
               (match t with
                  | Napp (Napp (Ncst (Heq a), t1), t2) ->
                      if hyp_is_not_free x y h then
                        Some (h, heq (Narrow (y, a)) (Nabs (y, close t1 x y)) (Nabs (y, close t2 x y)))
                      else ((print_string "Npabs\n"); None)
                  | _ -> (print_string "Npabs\n"); None)
           | None -> (print_string "Npabs\n"); None)

    | Npdisch (q, t) ->
        (match proof2deriv q, infer [] t with
           | Some (h, u), Some Nbool -> Some (hyp_remove t h, himp t u)
           | _, _ -> (print_string "Npdisch\n"); None)

    | Nphyp t ->
        (match infer [] t with
           | Some Nbool -> Some (hyp_singl t, t)
           | _ -> (print_string "Nphyp\n"); None)

    | Npspec (q, t) ->
        (match proof2deriv q, infer [] t with
           | Some (h, u), Some a ->
               (match u with
                  | Napp (Ncst (Hforall b), Nabs (c, v)) ->
                      if ((eq_type a b) && (eq_type b c)) then
                        Some (h, nopen v t)
                      else ((print_string "Npspec\n"); None)
                  | _ -> (print_string "Npspec\n"); None)
           | _, _ -> (print_string "Npspec\n"); None)

    | Npinst (q, l) ->
        (match proof2deriv q, wf_substitution_idv l with
           | Some (h, v), true -> Some (hyp_subst_idv h l, subst_idv v l)
           | _, _ -> (print_string "Npinst\n"); None)

    | Npgen (q, x, y) ->
        (match proof2deriv q with
           | Some (h, t) ->
               if hyp_is_not_free x y h then
                 Some (h, hforall y (close t x y))
               else ((print_string "Npgen\n"); None)
           | None -> (print_string "Npgen\n"); None)

    | Npsym q ->
        (match proof2deriv q with
           | Some (h, t) ->
               (match t with
                  | Napp (Napp (Ncst (Heq a), u), v) -> Some (h, heq a v u)
                  | _ -> (print_string "Npsym\n"); None)
           | None -> (print_string "Npsym\n"); None)

    | Nptrans (q1, q2) ->
        (match proof2deriv q1, proof2deriv q2 with
           | Some (h1, t1), Some (h2, t2) ->
               (match t1, t2 with
                  | Napp (Napp (Ncst (Heq a), u1), u2),
                    Napp (Napp (Ncst (Heq b), v2), v3) ->
                      if ((eq_type a b) && (eq_term u2 v2)) then
                        Some (hyp_union h1 h2, heq a u1 v3)
                      else ((print_string "Nptrans\n"); None)
                  | _, _ -> (print_string "Nptrans\n"); None)
           | _, _ -> (print_string "Nptrans\n"); None)

    | Npcomb (q1, q2) ->
        (match proof2deriv q1, proof2deriv q2 with
           | Some (h1, t1), Some (h2, t2) ->
               (match t1, t2 with
                  | Napp (Napp (Ncst (Heq (Narrow (a, b))), f), g),
                    Napp (Napp (Ncst (Heq c), u), v) ->
                      if (eq_type a c) then
                        Some (hyp_union h1 h2, heq b (Napp (f, u)) (Napp (g, v)))
                      else ((print_string "Npcomb\n"); None)
                  | _, _ -> (print_string "Npcomb\n"); None)
           | _, _ -> (print_string "Npcomb\n"); None)

    | Npeqmp (q1, q2) ->
        (match proof2deriv q1, proof2deriv q2 with
           | Some (h1, t1), Some (h2, t2) ->
               (match t1 with
                  | Napp (Napp (Ncst (Heq Nbool), a), b) ->
                      if (eq_term a t2) then
                        Some (hyp_union h1 h2, b)
                      else ((print_string "Npeqmp\n"); None)
                  | _ -> (print_string "Npeqmp\n"); None)
           | _, _ -> (print_string "Npeqmp\n"); None)

    | Npexists (q, b, t) ->
        (match proof2deriv q, b, infer [] t with
           | Some (h, u), Nabs (bb, a), Some aa ->
               if ((eq_type aa bb) && (eq_term (nopen a t) u)) then
                 Some (h, hexists aa a)
               else ((print_string "Npexists\n"); None)
           | _, _, _ -> (print_string "Npexists\n"); None)

    | Npchoose (v, aa, q1, q2) ->
        (match proof2deriv q1, proof2deriv q2 with
           | Some (h1, t), Some (h2, c) ->
               (match t with
                  | Napp (Ncst (Hexists bb), Nabs (cc, a)) ->
                      let s = hyp_remove (nopen a (Nvar (v, aa))) h2 in
                      if ((eq_type aa bb) && (eq_type bb cc) && (hyp_is_not_free v aa s) && (is_not_free v aa c)
                          && (is_not_free v aa a)) then
                        Some (hyp_union h1 s, c)
                      else ((print_string "Npchoose\n"); None)
                  | _ -> (print_string "Npchoose\n"); None)
           | _, _ -> (print_string "Npchoose\n"); None)

    | Npconj (q1, q2) ->
        (match proof2deriv q1, proof2deriv q2 with
           | Some (h1, a), Some (h2, b) ->
               Some (hyp_union h1 h2, hand a b)
           | _, _ -> (print_string "Npconj\n"); None)

    | Npconjunct1 q ->
        (match proof2deriv q with
           | Some (h, v) ->
               (match v with
                  | Napp (Napp (Ncst Hand, t), u) ->
                      Some (h, t)
                  | _ -> (print_string "Npconjunct1\n"); None)
           | _ -> (print_string "Npconjunct1\n"); None)

    | Npconjunct2 q ->
        (match proof2deriv q with
           | Some (h, v) ->
               (match v with
                  | Napp (Napp (Ncst Hand, t), u) ->
                      Some (h, u)
                  | _ -> (print_string "Npconjunct2\n"); None)
           | _ -> (print_string "Npconjunct2\n"); None)

    | Npdisj1 (q, b) ->
        (match proof2deriv q, infer [] b with
           | Some (h, a), Some Nbool -> Some (h, hor a b)
           | _, _ -> (print_string "Npdisj1\n"); None)

    | Npdisj2 (q, a) ->
        (match proof2deriv q, infer [] a with
           | Some (h, b), Some Nbool -> Some (h, hor a b)
           | _, _ -> (print_string "Npdisj1\n"); None)

    | Npdisjcases (q1, q2, q3) ->
        (match proof2deriv q1, proof2deriv q2, proof2deriv q3 with
           | Some (h1, t), Some (h2, c1), Some (h3, c2) ->
               (match t with
                  | Napp (Napp (Ncst Hor, a), b) ->
                      if (eq_term c1 c2) then
                        Some (hyp_union h1 (hyp_union (hyp_remove a h2) (hyp_remove b h3)), c1)
                      else ((print_string "Npdisjcases\n"); None)
                  | _ -> (print_string "Npdisjcases\n"); None)
           | _, _, _ -> (print_string "Npisjcases\n"); None)

    | Npnoti q ->
        (match proof2deriv q with
           | Some (h, t) ->
               (match t with
                  | Napp (Napp (Ncst Himp, a), Ncst Hfalse) -> Some (h, hnot a)
                  | _ -> (print_string "Npnoti\n"); None)
           | _ -> (print_string "Npnoti\n"); None)

    | Npnote q ->
        (match proof2deriv q with
           | Some (h, t) ->
               (match t with
                  | Napp (Ncst Hnot, a) -> Some (h, himp a hfalse)
                  | _ -> (print_string "Npnote\n"); None)
           | _ -> (print_string "Npnote\n"); None)

    | Npcontr (q, a) ->
        (match proof2deriv q, infer [] a with
           | Some (h, t), Some Nbool ->
               (match t with
                  | Ncst Hfalse -> Some (hyp_remove (hnot a) h, a)
                  | _ -> (print_string "Npcontr\n"); None)
           | _, _ -> (print_string "Npcontr\n"); None)

    | Npimpas (q1, q2) ->
        (match proof2deriv q1, proof2deriv q2 with
           | Some (h1, t), Some (h2, u) ->
               (match t, u with
                  | Napp (Napp (Ncst Himp, a1), b1),
                    Napp (Napp (Ncst Himp, b2), a2) ->
                      if ((eq_term a1 a2) && (eq_term b1 b2)) then
                        Some (hyp_union h1 h2, hequiv b1 a1)
                      else ((print_string ("Npimpas1; 1: "^(string_of_bool (eq_term a1 a2))^"; 2: "^(string_of_bool (eq_term b1 b2))^"\n"));
                            let out = print_string in
                            print_term out a1; out "\n"; print_term out a2; out "\n"; print_term out b1; out "\n"; print_term out b2; out "\n"; None)
                  | _, _ -> (print_string "Npimpas2\n"); None)
           | _, _ -> (print_string "Npimpas3\n"); None)

    | Nfact thm ->
        (try Some (Hashtbl.find derivs thm) with
           | Not_found -> (print_string ("Nfact "^thm^"\n")); None)

    | Npdef (i, a, t) -> Some (hyp_empty, heq a (Ndef (i, a)) t)

    | Npaxm (_, t) -> Some (hyp_empty, t)

    | Nptyintro (rty, aty, mk_name, dest_name, p) ->

        let mk_type = Narrow(rty, aty) in
        let dest_type = Narrow(aty, rty) in

        let a_name = make_idV "a" aty in
        let a = Nvar(a_name, aty) in
        let r_name = make_idV "r" rty in
        let r = Nvar(r_name, rty) in

        Some (hyp_empty, hand (heq aty (Napp (Ndef (mk_name, mk_type), Napp (Ndef (dest_name, dest_type), a))) a)
                (hequiv (Napp (p, r)) (heq rty (Napp (Ndef (dest_name, dest_type), Napp (Ndef (mk_name, mk_type), r))) r)));;


  (* Dealing with dependencies *)

  let rec make_dependencies_aux dep_graph proof_of_thm = function
    | [] -> ()
    | (thmname, p, c_opt)::il ->

        incr total;

        let wdi thm =
          Depgraph.Dep.add_dep dep_graph thm thmname;
          Nfact thm in

        let write_proof p il =

          let rec share_info_of p il =
            match (disk_info_of p) with
              | Some (thyname,thmname) -> Some(thyname,thmname,il)
              | None ->
                  if do_share p then
                    let name = THEORY_NAME^"_"^(get_iname ()) in
                    set_disk_info_of p THEORY_NAME name;
                    Depgraph.Dep.add_thm dep_graph name;
                    Some(THEORY_NAME,name,(name,p,None)::il)
                  else
                    None

          and wp' il = function
            | Prefl tm -> Nprefl (term2nterm tm), il
            | Pbeta(x, ty, tm) ->
                let typ = hol_type2ntype ty in
                Npbeta(make_idV x typ , typ, term2nterm tm), il
            | Pinstt(p,lambda) ->
                let p', res = wp il p in
                Npinstt(p', List.map (
                          fun (s,ty) -> (make_idT s, hol_type2ntype ty)
                        ) lambda), res
            | Pabs(p,x,ty) ->
                let p', res = wp il p in
                let typ = hol_type2ntype ty in
                Npabs(p',make_idV x typ,typ), res
            | Pdisch(p,tm) ->
                let p', res = wp il p in
                Npdisch(p', term2nterm tm), res
            | Phyp tm -> Nphyp (term2nterm tm), il
            | Paxm(th,tm) -> Npaxm(th, term2nterm tm), il
            | Pdef(name,ty,tm) ->
                let typ = hol_type2ntype ty in
                Npdef(make_defV name typ true, typ, term2nterm tm), il
            | Ptyintro(rty2, tyname, tyvars, absname, repname, pt) ->
                let rty = hol_type2ntype rty2 in
                let new_name = make_defT tyname in

                let ntyvars = List.map hol_type2ntype tyvars in
                let aty = Ntdef(new_name, ntyvars) in

                let mk_name = make_defV absname (Narrow(rty, aty)) false in
                let dest_name = make_defV repname (Narrow(aty, rty)) false in

                Nptyintro(rty, aty, mk_name, dest_name, term2nterm pt), il
            | Pspec(p,t) ->
                let p', res = wp il p in
                Npspec(p', term2nterm t), res
            | Pinst(p,theta) ->
                let p', res = wp il p in
                Npinst(p', List.map (
                         fun (s,ty,te) ->
                           let typ = hol_type2ntype ty in
                           (make_idV s typ, typ, term2nterm te)
                       ) theta), res
            | Pgen(p,x,ty) ->
                let p', res = wp il p in
                let typ = hol_type2ntype ty in
                Npgen(p', make_idV x typ, typ), res
            | Psym p ->
                let p', res = wp il p in
                Npsym p', res
            | Ptrans(p1,p2) ->
                let p1', il' = wp il p1 in
                let p2', res = wp il' p2 in
                Nptrans(p1', p2'), res
            | Pcomb(p1,p2) ->
                let p1', il' = wp il p1 in
                let p2', res = wp il' p2 in
                Npcomb(p1', p2'), res
            | Peqmp(p1,p2) ->
                let p1', il' = wp il p1 in
                let p2', res = wp il' p2 in
                Npeqmp(p1', p2'), res
            | Pexists(p,ex,w) ->
                let p', res = wp il p in
                Npexists(p', term2nterm ex, term2nterm w), res
            | Pchoose(x,ty,p1,p2) ->
                let p1', il' = wp il p1 in
                let p2', res = wp il' p2 in
                let typ = hol_type2ntype ty in
                Npchoose(make_idV x typ, typ, p1', p2'), res
            | Pconj(p1,p2) ->
                let p1', il' = wp il p1 in
                let p2', res = wp il' p2 in
                Npconj(p1', p2'), res
            | Pimpas(p1,p2) ->
                let p1', il' = wp il p1 in
                let p2', res = wp il' p2 in
                Npimpas(p1', p2'), res
            | Pconjunct1 p ->
                let p', res = wp il p in
                Npconjunct1 p', res
            |  Pconjunct2 p ->
                 let p', res = wp il p in
                 Npconjunct2 p', res
            | Pdisj1(p,tm) ->
                let p', res = wp il p in
                Npdisj1(p', term2nterm tm), res
            | Pdisj2(p,tm) ->
                let p', res = wp il p in
                Npdisj2(p', term2nterm tm), res
            | Pdisjcases(p1,p2,p3) ->
                let p1', il' = wp il p1 in
                let p2', il'' = wp il' p2 in
                let p3', res = wp il'' p3 in
                Npdisjcases(p1', p2', p3'), res
            | Pnoti p ->
                let p', res = wp il p in
                Npnoti p', res
            | Pnote p ->
                let p', res = wp il p in
                Npnote p', res
            | Pcontr(p,tm) ->
                let p', res = wp il p in
                Npcontr(p', term2nterm tm), res

          and wp il p =
            match share_info_of p il with
              | Some(_, thmname, il') -> wdi thmname, il'
              | None -> wp' il (content_of p) in

          match disk_info_of p with
            | Some(_, thmname') -> if thmname' = thmname then wp' il (content_of p) else (wdi thmname', il)
            | None -> wp' il (content_of p) in

        let p', il = write_proof p il in
        set_disk_info_of p THEORY_NAME thmname;
        Hashtbl.add proof_of_thm thmname p';
        make_dependencies_aux dep_graph proof_of_thm il;;


  let make_dependencies out out_share out_sharet new_file count_thms path ((thmname, pr, _) as p) =

    let dep_graph = Depgraph.Dep.create () in
    let proof_of_thm = Hashtbl.create (references pr) in
    Depgraph.Dep.add_thm dep_graph thmname;

    make_dependencies_aux dep_graph proof_of_thm [p];

    let share_type ty = share_types out_sharet ty in
    let share_term ty = share_terms out_share out_sharet ty in


    if thmname = (THEORY_NAME^"_DEF_T") then (
      match content_of pr with
        | Pdef (_, _, t) ->
            let tm = hequiv htrue (term2nterm t) in
            Hashtbl.add derivs thmname (hyp_empty, tm);
            export_ht out share_term hyp_empty tm thmname;
            export_lemma_def out "DEF_T" thmname;
            export_sig out thmname
        | _ -> ()
    ) else if thmname = (THEORY_NAME^"_DEF__slash__backslash_") then (
      match content_of pr with
        | Pdef (_, _, t) ->
            let tm = heq (Narrow (Nbool, Narrow (Nbool, Nbool))) (Ncst Hand) (term2nterm t) in
            Hashtbl.add derivs thmname (hyp_empty, tm);
            export_ht out share_term hyp_empty tm thmname;
            export_lemma_def out "DEF_AND" thmname;
            export_sig out thmname
        | _ -> ()
    ) else if thmname = (THEORY_NAME^"_DEF__equal__equal__greaterthan_") then (
      match content_of pr with
        | Pdef (_, _, t) ->
            let tm = heq (Narrow (Nbool, Narrow (Nbool, Nbool))) (Ncst Himp) (term2nterm t) in
            Hashtbl.add derivs thmname (hyp_empty, tm);
            export_ht out share_term hyp_empty tm thmname;
            export_lemma_def out "DEF_IMP" thmname;
            export_sig out thmname
        | _ -> ()
    ) else if thmname = (THEORY_NAME^"_DEF__exclamationmark_") then (
      match content_of pr with
        | Pdef (_, a, t) ->
            let a2 = hol_type2ntype a in
            (match a2 with
               | Narrow (Narrow (b, _), _) ->
                   let tm = heq a2 (Ncst (Hforall b)) (term2nterm t) in
                   Hashtbl.add derivs thmname (hyp_empty, tm);
                   export_ht out share_term hyp_empty tm thmname;
                   export_lemma_def out "DEF_FORALL" thmname;
                   export_sig out thmname
               | _ -> ())
        | _ -> ()
    ) else if thmname = (THEORY_NAME^"_DEF__questionmark_") then (
      match content_of pr with
        | Pdef (_, a, t) ->
            let a2 = hol_type2ntype a in
            (match a2 with
               | Narrow (Narrow (b, _), _) ->
                   let tm = heq a2 (Ncst (Hexists b)) (term2nterm t) in
                   Hashtbl.add derivs thmname (hyp_empty, tm);
                   export_ht out share_term hyp_empty tm thmname;
                   export_lemma_def out "DEF_EXISTS" thmname;
                   export_sig out thmname
               | _ -> ())
        | _ -> ()
    ) else if thmname = (THEORY_NAME^"_DEF__backslash__slash_") then (
      match content_of pr with
        | Pdef (_, _, t) ->
            let tm = heq (Narrow (Nbool, Narrow (Nbool, Nbool))) (Ncst Hor) (term2nterm t) in
            Hashtbl.add derivs thmname (hyp_empty, tm);
            export_ht out share_term hyp_empty tm thmname;
            export_lemma_def out "DEF_OR" thmname;
            export_sig out thmname
        | _ -> ()
    ) else if thmname = (THEORY_NAME^"_DEF_F") then (
      match content_of pr with
        | Pdef (_, _, t) ->
            let tm = hequiv (Ncst Hfalse) (term2nterm t) in
            Hashtbl.add derivs thmname (hyp_empty, tm);
            export_ht out share_term hyp_empty tm thmname;
            export_lemma_def out "DEF_F" thmname;
            export_sig out thmname
        | _ -> ()
    ) else if thmname = (THEORY_NAME^"_DEF__tilde_") then (
      match content_of pr with
        | Pdef(_, _, t) ->
            let tm = heq (Narrow (Nbool, Nbool)) (Ncst Hnot) (term2nterm t) in
            Hashtbl.add derivs thmname (hyp_empty, tm);
            export_ht out share_term hyp_empty tm thmname;
            export_lemma_def out "DEF_NOT" thmname;
            export_sig out thmname
        | _ -> ()
    ) else if thmname = (THEORY_NAME^"_DEF__FALSITY_") then (
      let tm = heq Nbool (Ncst Hfalse) (Ncst Hfalse) in
      Hashtbl.add derivs thmname (hyp_empty, tm);
      export_ht out share_term hyp_empty tm thmname;
      export_lemma_def out "(Prefl (Cst Hfalse))" thmname;
      export_sig out thmname
    ) else if thmname = (THEORY_NAME^"_ax__1") then (
      match content_of pr with
        | Paxm (_, tm) ->
            let tm2 = term2nterm tm in
            Hashtbl.add derivs thmname (hyp_empty, tm2);
            export_ht out share_term hyp_empty tm2 thmname;
            export_lemma_def out "ETA_AX" thmname;
            export_sig out thmname
        | _ -> ()
    ) else if thmname = (THEORY_NAME^"_ax__2") then (
      match content_of pr with
        | Paxm (_, tm) ->
            let tm2 = term2nterm tm in
            Hashtbl.add derivs thmname (hyp_empty, tm2);
            export_ht out share_term hyp_empty tm2 thmname;
            export_lemma_def out "SELECT_AX" thmname;
            export_sig out thmname
        | _ -> ()

    ) else (

      Depgraph.Dep_top.iter_top (
        fun thm ->
          incr count_thms;
          if !count_thms = 1000 then (count_thms := 0; new_file ());
          (try
             let p = Hashtbl.find proof_of_thm thm in
             (match proof2deriv p with
                | Some (h, t) ->
                    Hashtbl.add derivs thm (h, t);
                    export_ht out share_term h t thm;
                    (match p with
                       | Npdef _ -> export_def out thm
                       | Nptyintro _ -> export_tdef out thm
                       | Npaxm _ -> export_axiom out thm
                       | _ -> export_lemma out share_type share_term p thm);
                    export_sig out thm
                | None -> failwith ("Erreur make_dependencies "^thm^" de "^thmname^": no derivation associated to the proof\n"))
           with | Not_found -> failwith ("Erreur make_dependencies "^thm^": proof_of_thm not found\n"));
      ) dep_graph
    );
;;


  let the_proof_database = ref ([]:(string*proof*(term option)) list);;

  Random.self_init;;

  let rec search_proof_name n db =
    match db with [] -> n | ((m, _, _)::db') -> if n=m then n^"_"^(string_of_int (Random.int 1073741823)) else search_proof_name n db'

  let save_proof name p c_opt =
    let name' = search_proof_name name (!the_proof_database) in
    the_proof_database := (name', p, c_opt)::(!the_proof_database);;

  let proof_database () = !the_proof_database;;


  (* Utilities to define Coq interpretation functions *)

  let ut = Hashtbl.create 17;;

  let ask_ut () =
    try (
      let filein = Pervasives.open_in "interpretation.txt" in
      let line = ref 0 in

      try
        while true do
          incr line;
          let s1 = input_line filein in
          incr line;
          let s2 = input_line filein in
          Hashtbl.add ut s1 s2
        done
      with
        | End_of_file -> close_in filein
        | _ -> failwith ("Error line "^(string_of_int !line)^".")
    ) with | Sys_error _ -> ()
  ;;

  let tc_regexp = Str.regexp "\?[0-9]*";;

  let make_tc_parameter out x n =
    if Str.string_match tc_regexp x 0 then (
      let i = Str.match_end () in
      if i <> String.length x then (
        out "\nParameter "; out THEORY_NAME; out "_idT_"; out (mfc x); out " : Type.\nParameter "; out THEORY_NAME; out "_idT_inhab_"; out (mfc x);
        out " : "; out THEORY_NAME; out "_idT_"; out (mfc x); out "."
      )
    ) else (
      out "\nParameter "; out THEORY_NAME; out "_idT_"; out (mfc x); out " : Type.\nParameter "; out THEORY_NAME; out "_idT_inhab_"; out (mfc x);
      out " : "; out THEORY_NAME; out "_idT_"; out (mfc x); out "."
    );;

  let make_tc_list out x n =
    if Str.string_match tc_regexp x 0 then (
      let i = Str.match_end () in
      if i <> String.length x then (
        out "\n("; out (string_of_int n); out ", mkTT "; out THEORY_NAME; out "_idT_inhab_"; out (mfc x); out ")::"
      )
    ) else (
      out "\n("; out (string_of_int n); out ", mkTT "; out THEORY_NAME; out "_idT_inhab_"; out (mfc x); out ")::"
    );;


  let defT_ut = Hashtbl.create 17;;

  let make_tdt_parameter out x _ =
    try (
      let y = Hashtbl.find ut x in
      Hashtbl.add defT_ut x y
    ) with | Not_found -> (
      out "\nParameter "; out THEORY_NAME; out "_defT_"; out (mfc x); out " : Type.";
      out "\nParameter "; out THEORY_NAME; out "_defT_inhab_"; out (mfc x); out " : "; out THEORY_NAME; out "_defT_"; out (mfc x); out ".\n";
      Hashtbl.add defT_ut x ("fun _ => mkTT "^THEORY_NAME^"_defT_inhab_"^(mfc x))
    );;

  let make_tdt_list out x n =
    try (
      let s = Hashtbl.find defT_ut x in
      out "\n("; out (string_of_int n); out ", "; out s; out ")::";
    ) with | Not_found -> (
      out "\n("; out (string_of_int n); out ", fun _ => mkTT tt)::"
    );;


  let se_regexp = Str.regexp "_[0-9]*";;

  let make_se_parameter out x (_,ty) =
    if Str.string_match se_regexp x 0 then (
      let i = Str.match_end () in
      if i <> String.length x then (
        out "\nParameter "; out THEORY_NAME; out "_idV_"; out (mfc x); out " : tr_type tc tdt "; print_type out ty; out "."
      )
    ) else (
      out "\nParameter "; out THEORY_NAME; out "_idV_"; out (mfc x); out " : tr_type tc tdt "; print_type out ty; out "."
    );;

  let make_se_list out x (n,ty) =
    if Str.string_match se_regexp x 0 then (
      let i = Str.match_end () in
      if i <> String.length x then (
        out "\n("; print_names out n; out ", existT (fun (t: type) => tr_type tc tdt t) "; print_type out ty; out " "; out THEORY_NAME; out "_idV_"; out (mfc x); out ")::"
      )
    ) else (
      out "\n("; print_names out n; out ", existT (fun (t: type) => tr_type tc tdt t) "; print_type out ty; out " "; out THEORY_NAME; out "_idV_"; out (mfc x); out ")::"
    );;


  let defV_ut = Hashtbl.create 17;;

  let make_sdt_parameter out x (_,ty,_) =
    if ((x <> "T") && (x <> "/\\") && (x <> "==>") && (x <> "!") && (x <> "?") && (x <> "\\/") && (x <> "F") && (x <> "~") && (x <> "_FALSITY_")) then (
      try (
        let y = Hashtbl.find ut x in
        Hashtbl.add defV_ut x y
      ) with | Not_found -> (
        out "\nParameter "; out THEORY_NAME; out "_defV_"; out (mfc x); out " : tr_type tc tdt "; print_type out ty; out "."
      )
    );;

  let make_sdt_list out x (n,ty,_) =
    try (
      let s = Hashtbl.find defV_ut x in
      out "\n("; print_names out n; out ", existT (fun (t: type) => tr_type tc tdt t) "; print_type out ty; out " ("; out s; out "))::"
    ) with | Not_found -> (
      if ((x <> "T") && (x <> "/\\") && (x <> "==>") && (x <> "!") && (x <> "?") && (x <> "\\/") && (x <> "F") && (x <> "~") && (x <> "_FALSITY_")) then (
        out "\n("; print_names out n; out ", existT (fun (t: type) => tr_type tc tdt t) "; print_type out ty; out " "; out THEORY_NAME; out "_defV_"; out (mfc x); out ")::"
      )
    );;


  (* Main function: list of proofs exportation *)

  let export_list thmname_list =

    total := 0;

    let path = ensure_export_directory THEORY_NAME in


    let rec proof_of_thm acc acc2 = function
      | [] -> acc, acc2
      | (s,p,c)::q ->
          if List.mem s thmname_list then
            proof_of_thm ((THEORY_NAME^"_"^(mfc s), reset_disk_info_of1 p, c)::acc) (acc2+1) q
          else match content_of p with
            | Paxm _ | Pdef _ | Ptyintro _ -> proof_of_thm ((THEORY_NAME^"_"^(mfc s), reset_disk_info_of1 p, c)::acc) (acc2+1) q
            | _ -> proof_of_thm acc acc2 q in

    let l, total_thms = proof_of_thm [] 0 (proof_database ()) in


    let count_thms = ref 0 in
    let count_files = ref 1 in

    (* Main file *)

    let file = ref (open_out (Filename.concat path (THEORY_NAME^"_1.v"))) in
    let count_file = ref 0 in
    let out s = (output_string !file s; incr count_file; if !count_file = 1000 then (count_file := 0; flush !file)) in
    out "(*** This file has been automatically generated from HOL-Light source files. ***)\n\nRequire Export List NArith.\nRequire Export hol deriv proof.\n\n";

    (* Temporary file *)

    let (file_temp_name, file_temp_aux) = Filename.open_temp_file (THEORY_NAME^"_") ".v" in
    let file_temp = ref file_temp_aux in
    let count_file_temp = ref 0 in
    let out_temp s = (output_string !file_temp s; incr count_file_temp; if !count_file_temp = 1000 then (count_file_temp := 0; flush !file_temp)) in


    let move_temp () =
      (try
         close_out !file_temp
       with | Sys_error s -> raise (Sys_error ("move_temp1: "^s)));

      (try
         let buf = Pervasives.open_in file_temp_name in
         (try
            while true do
              out "\n";
              let l = input_line buf in
              out l
            done
          with | End_of_file -> close_in buf)
       with | Sys_error s -> raise (Sys_error ("move_temp3: "^s))) in


    (* New file *)

    let new_file () =

      move_temp ();
      file_temp := open_out file_temp_name;

      incr count_files;
      close_out !file;
      file := open_out (Filename.concat path (THEORY_NAME^"_"^(string_of_int !count_files)^".v"));
      out "(*** This file has been automatically generated from HOL-Light source files. ***)\n\nRequire Export "; out THEORY_NAME; out "_"; out (string_of_int (!count_files-1)); out ".\n\n" in


    (* Coq files generation *)

    let date1 = Unix.time () in
    List.iter (make_dependencies out_temp out out new_file count_thms path) l;
    let date2 = Unix.time () in


    move_temp (); close_out !file;


    (* Makefile *)

    let make = open_out (Filename.concat path "Makefile") in
    let out = output_string make in
    out "# This file has been automatically generated from HOL-Light source files.\n\nCOQ=ssrcoq\nFLAGS=-dont-load-proofs -dump-glob /dev/null -compile\n\nSRC=";
    for i = 1 to !count_files do
      out " "; out THEORY_NAME; out "_"; out (string_of_int i); out ".v";
    done;
    out "\nOBJ=$(SRC:.v=.vo)\nGLOB=$(SRC:.v=.glob)\n\n\nall: $(OBJ)\n\n\n%.vo: %.v\n\t$(COQ) $(FLAGS) $(^:.v=)\n\n\nclean:\n\trm -f $(OBJ) $(GLOB) *~";
    close_out make;


    (* Interpretation *)

    let interp = open_out (Filename.concat path "interpretation.v") in
    let out = output_string interp in
    out "(*** This file has been automatically generated from HOL-Light source files. ***)\n\nRequire Import ssreflect eqtype ssrnat ssrbool.\nRequire Import List NArith ZArith.ZOdiv_def.\nRequire Import hol cast typing translation axioms.\n\nOpen Local Scope positive_scope.\n\n";

    ask_ut ();

    (* tc *)
    Hashtbl.iter (make_tc_parameter out) idT;
    out "\n\nDefinition tc_list :=";
    Hashtbl.iter (make_tc_list out) idT;
    out "\nnil.\n\nDefinition tc := list_tc2tc tc_list.\n\n";

    (* tdt *)
    Hashtbl.iter (make_tdt_parameter out) defT;
    out "\n\nDefinition tdt_list : list_tdt :=";
    Hashtbl.iter (make_tdt_list out) defT;
    out "\nnil.\n\nDefinition tdt := list_tdt2tdt tdt_list.\n\n";

    (* se *)
    Hashtbl.iter (make_se_parameter out) idV;
    out "\n\nDefinition se_list :=";
    Hashtbl.iter (make_se_list out) idV;
    out "\nnil.\n\nDefinition se := list_se2se se_list.\n\n";

    (* sdt *)
    Hashtbl.iter (make_sdt_parameter out) defV;
    out "\n\nDefinition sdt_list :=";
    Hashtbl.iter (make_sdt_list out) defV;
    out "\nnil.\n\nDefinition sdt := list_sdt2sdt sdt_list.";

    close_out interp;


    print_string "Generated "; print_int !total; print_string " facts for "; print_int total_thms; print_string " theorems.\n";
    print_string "Exportation duration: "; print_float (date2 -. date1); print_string "s.\n"
  ;;


  (* Main function: all proofs exportation *)

  let export_saved_proofs () = export_list (List.map (fun (s,_,_) -> s) (proof_database ()));;


  (* Main function: one proof exportation *)

  let export_one_proof name = export_list [name];;


end;;


include Proofobjects;;
