(* ========================================================================= *)
(*                              Isabelle Light                               *)
(*   Isabelle/Procedural style additions and other user-friendly shortcuts.  *)
(*                                                                           *)
(*                   Petros Papapanagiotou, Jacques Fleuriot                 *)
(*              Center of Intelligent Systems and their Applications         *)
(*                          University of Edinburgh                          *)
(*                                 2009-2015                                 *)
(* ========================================================================= *)
(* FILE         : meta_rules.ml                                              *)
(* DESCRIPTION  : Meta rules is a formalisation used to accommodate          *)
(*                Isabelle's inference rules in HOL Light.The technical      *)
(*                details are described in the comments that follow.         *)
(*                Isabelle rule application tactics (rule, erule, etc.) have *)
(*                been defined to work with meta rules.                      *)
(*                We have not been able to accommodate first order rules     *)
(*                allI and exE. We also make use of metavariables which are  *)
(*                restricted by the limitations of term_unify                *)
(*                (ie. no HO unification and no type instantiation).         *)
(* ========================================================================= *)

(* ------------------------------------------------------------------------- *)
(* ----------------------- META-LEVEL IMPLICATION -------------------------- *)
(* ------------------------------------------------------------------------- *)
(* Emulation of meta-level implication at the object level.                  *)
(* This is purely for syntax and parsing purposes. It solves a number of     *)
(* problems when parsing theorems as meta-rules (see below).                 *)
(* It is applied at the logic level only for transparency.                   *)
(* ------------------------------------------------------------------------- *)
(* Thanks to Phil Scott for the suggestion.                                  *)
(* ------------------------------------------------------------------------- *)


(* ------------------------------------------------------------------------- *)
(* Syntax definition.                                                        *)
(* ------------------------------------------------------------------------- *)

parse_as_infix ("===>",(4,"right"));;

let is_mimp = is_binary "===>";;
let dest_mimp = dest_binary "===>";;


(* ------------------------------------------------------------------------- *)
(* Logic definition: Equivalent to object-level implication.                 *)
(* ------------------------------------------------------------------------- *)

let MIMP_DEF = new_basic_definition
  `(===>) = \p q. p ==> q`;;


(* ------------------------------------------------------------------------- *)
(* CONV, RULE and TACTIC to get rid of meta-level implication in proofs.     *)
(* ------------------------------------------------------------------------- *)

let MIMP_TO_IMP_CONV = BETA_RULE o (PURE_REWRITE_CONV [MIMP_DEF]);;
let MIMP_TO_IMP_RULE = BETA_RULE o (PURE_REWRITE_RULE [MIMP_DEF]);;
let MIMP_TAC = (PURE_REWRITE_TAC [MIMP_DEF]) THEN BETA_TAC;;


(* ------------------------------------------------------------------------- *)
(* Equivalent of TAUT after getting rid of meta-level implication.           *)
(* Helps prove simple propositional meta-rules easily.                       *)
(* ------------------------------------------------------------------------- *)

let MTAUT tm =
    let th = MIMP_TO_IMP_CONV tm in
    EQ_MP (SYM th) ((TAUT o snd o dest_iff o concl) th);;


(* ------------------------------------------------------------------------- *)
(* RULE to replace implication by meta-level implication to easily create    *)
(* meta-theorems from normal theorems.                                       *)
(* ------------------------------------------------------------------------- *)

let MIMP_THM = MTAUT `(p==>q) <=> (p===>q)`;;
let MIMP_RULE = PURE_REWRITE_RULE[MIMP_THM];;


(* ------------------------------------------------------------------------- *)
(* DISCH and DISCH_TAC for meta-level implication.                           *)
(* ------------------------------------------------------------------------- *)

let MDISCH a th =
  let mth = MATCH_MP EQ_IMP MIMP_THM in
  MATCH_MP mth (DISCH a th);;

let (MDISCH_TAC: tactic) =
  fun (asl,w) ->
    try let ant,c = dest_mimp w in
        let th1 = ASSUME ant in
        null_meta,[("",th1)::asl,c],
        fun i [th] -> MDISCH (instantiate i ant) th
    with Failure _ -> failwith "MISCH_TAC";;



(* ------------------------------------------------------------------------- *)
(* UNDISCH for meta-level implication.                                       *)
(* Also gets rid of meta-level implication in the undischarged term.         *)
(* ------------------------------------------------------------------------- *)

let MUNDISCH th =
    let mth = BETA_RULE (AP_THM (AP_THM MIMP_DEF `p:bool`) `q:bool`) in
    let th =  PURE_ONCE_REWRITE_RULE [mth] th in
    try let undisch_tm = (rand o rator o concl) th in
    PROVE_HYP ((UNDISCH o snd o EQ_IMP_RULE o MIMP_TO_IMP_CONV) undisch_tm) (UNDISCH th)
    with Failure _ -> failwith "MUNDISCH";;


(* ------------------------------------------------------------------------- *)
(* -------------------------- HELPFUL FUNCTIONS ---------------------------- *)
(* ------------------------------------------------------------------------- *)

(* ------------------------------------------------------------------------- *)
(* REV_PART_MATCH_I: term list -> (term -> term) -> thm -> term              *)
(*                                                         -> instantiation  *)
(* Does a reverse PART_MATCH and returns the resulting instantiation.        *)
(* Avoids instantiating any of the given variables/constants.                *)
(* Does not apply SPEC_ALL like PART_MATCH does.                             *)
(* ------------------------------------------------------------------------- *)
(* The original PART_MATCH matches a term to part of a theorem so that we can*)
(* instantiate that part with the term.                                      *)
(* The reverse used here, matches the part of the theorem with the term so   *)
(* that the term can be instantiated with the part of the theorem.           *)
(* We use this in cases such as erule where we want (part of) an assumption  *)
(* to match a premise of the rule. We need the instantiation of the rule when*)
(* matched to the assumption (thm) and not the other way around.             *)
(* ------------------------------------------------------------------------- *)

let REV_PART_MATCH_I =
  let rec match_bvs t1 t2 acc =
    try let v1,b1 = dest_abs t1
        and v2,b2 = dest_abs t2 in
        let n1 = fst(dest_var v1) and n2 = fst(dest_var v2) in
        let newacc = if n1 = n2 then acc else insert (n1,n2) acc in
        match_bvs b1 b2 newacc
    with Failure _ -> try
        let l1,r1 = dest_comb t1
        and l2,r2 = dest_comb t2 in
        match_bvs l1 l2 (match_bvs r1 r2 acc)
    with Failure _ -> acc in
  fun avoids partfn th ->
    let bod = concl th in
    let pbod = partfn bod in
    let lconsts = union avoids (intersect (frees (concl th)) (freesl(hyp th))) in
    fun tm ->
      let bvms = match_bvs pbod tm [] in
      let atm = deep_alpha bvms tm in
      term_match lconsts atm (partfn bod) ;; (* whereas in PART_MATCH we do it the other way around *)


(* ------------------------------------------------------------------------- *)
(* term_to_asm_match : term list -> term -> (string * thm) list ->           *)
(*                               (string * thm) list * (thm * instantiation) *)
(* ------------------------------------------------------------------------- *)
(* term_to_asm_match tries to match key to one of the assumptions using      *)
(* REV_PART_MATCH_I. Returns the new assumption list (with the matching      *)
(* assumption removed), the matching assumption and the resulting            *)
(* instantiation used.                                                       *)
(* ------------------------------------------------------------------------- *)
(* It is doubtful that this has practical use outside the Xrule_tac's.       *)
(* It is used in erule, drule and frule to match the major premise to one of *)
(* the assumptions.                                                          *)
(* ------------------------------------------------------------------------- *)

let rec (term_to_asm_match: term list -> term -> (string * thm) list -> (string * thm) list * (thm * instantiation)) =
  fun avoids key asms ->
    if (asms = []) then failwith ("No assumptions match `" ^ (string_of_term key) ^ "`!")
    else try
      let asm = (snd o hd) asms in
      let i = REV_PART_MATCH_I avoids I asm key in
      (tl asms),(asm,i)
    with Failure _ -> let res,inst = term_to_asm_match avoids key (tl asms) in ((hd asms)::res),inst;;


(* ------------------------------------------------------------------------- *)
(* term_to_asm_n_match : term list -> term -> (string * thm) list -> int ->  *)
(*                               (string * thm) list * (thm * instantiation) *)
(* ------------------------------------------------------------------------- *)
(* Same as term_to_asm_match but only tries to match nth assumption.         *)
(* ------------------------------------------------------------------------- *)
(* It is doubtful that this has practical use outside the Xrulen_tac's.      *)
(* It is used in erulen, drulen and frulen to match the major premise to one *)
(* of the assumptions.                                                       *)
(* ------------------------------------------------------------------------- *)

let rec (term_to_asm_n_match: term list -> term -> (string * thm) list -> int -> (string * thm) list * (thm * instantiation)) =
  fun avoids key asms n ->
    if (asms = []) then failwith "No such assumption found!"
    else try match n with
	0 ->
	  let asm = (snd o hd) asms in
	  let i = REV_PART_MATCH_I avoids I asm key in
	    (tl asms),(asm,i)
      | _ -> let re_asms,m = term_to_asm_n_match avoids key (tl asms) (n-1) in
	  (hd asms)::re_asms,m
    with Failure _ -> failwith ("Assumption `" ^ ((string_of_term o concl o snd o hd) asms) ^ "` doesn't match `" ^ (string_of_term key) ^ "`!");;



(* gmm is not to be used until qed is updated *)
(* We need a MDISCH for that...               *)

let gmm t =
  let fvs = sort (<) (map (fst o dest_var) (frees t)) in
  (if fvs <> [] then
     let errmsg = end_itlist (fun s t -> s^", "^t) fvs in
     warn true ("Free variables in goal: "^errmsg)
   else ());
  let rec split_mimp = fun tm ->
    if (is_mimp tm)
    then
      let (a,b) = dest_mimp tm in
      let (asms, concl) = split_mimp b in
      (a::asms,concl)
    else ([],tm) in
   set_goal (split_mimp t);;


(* ------------------------------------------------------------------------- *)
(* gm : term -> goalstack                                                    *)
(* This is used to set a term containing meta-level implication as a goal.   *)
(* ------------------------------------------------------------------------- *)
(* (+) Uses g to set the goal then MIMP_TAC to get rid of meta-implication.  *)
(* (+) Note that if the goal has normal implication it gets discharged as    *)
(* well. This will be fixed when gmm is fixed.                               *)
(* ------------------------------------------------------------------------- *)

let gm t = ignore( g t ) ; e (REPEAT MDISCH_TAC);;


(* ------------------------------------------------------------------------- *)
(* Isabelle's natural deduction rules as thms with meta-level implication.   *)
(* ------------------------------------------------------------------------- *)

let conjI = MTAUT `p===>q===>p/\q`;;
let conjunct1 = MTAUT `p/\q===>p`;;
let conjunct2 = MTAUT `p/\q===>q`;;
let conjE = MTAUT `p/\q===>(p===>q===>r)===>r`;;
let disjI1 = MTAUT `p===>p\/q`;;
let disjI2 = MTAUT `q===>p\/q`;;
let disjE = MTAUT `p\/q===>(p===>r)===>(q===>r)===>r`;;

let impI = MTAUT `(p===>q)===>(p==>q)`;;
let impE = MTAUT `(p==>q)===>p===>(q===>r)===>r`;;
let mp = MTAUT `(p==>q)===>(p===>q)`;;

let iffI = MTAUT `(a===>b)===>(b===>a)===>(a<=>b)`;;
let iffE = MTAUT `(a<=>b)===>((a==>b) ===> (b==>a) ===> r) ===> r`;;

let allE = prove( `(!x:A. P x) ===> (P (a:A) ===> (r:bool)) ===> r` ,
                  MIMP_TAC THEN MESON_TAC[]);;
let exI = prove (`P (a:A)===> ?x:A. P x`,
                          MIMP_TAC THEN
			  DISCH_TAC THEN
			    (EXISTS_TAC `a:A`) THEN
			    (FIRST_ASSUM ACCEPT_TAC));;

let notI = MTAUT `(p===>F)===> ~p`;;
let notE = MTAUT `~a ===> a ===> r`;;


(* ------------------------------------------------------------------------- *)
(* ------------------------------------------------------------------------- *)
(* ------------------------ META-RULES START HERE!! ------------------------ *)
(* ------------------------------------------------------------------------- *)
(* ------------------------------------------------------------------------- *)

(* ------------------------------------------------------------------------- *)
(* meta_rule (type)                                                          *)
(* The representation of an Isabelle inference rule in HOL Light.            *)
(* ------------------------------------------------------------------------- *)
(* term = The conclusion of the inference rule.                              *)
(* goal list = The premises represented as "meta-subgoals".                  *)
(* thm = The representation of the rule as a theorem used for justification. *)
(*                                                                           *)
(* (+) thm must be of the form H1,H2,...,Hn |- G                             *)
(* (+) H1--Hn must be represented as "meta-subgoals" in any order (1)        *)
(* (+) [|P;Q|] ==> R (in Isabelle notation) is translated as "meta-subgoal"  *)
(*     P,Q ?- R and as P==>Q==>R in the justification theorem.               *)
(* (+) The form of the premises (assumption order etc) must be kept in the   *)
(*     justification theorem (see example in (2))                            *)
(* (+) Use "mk_meta_rule" to create proper meta rules from theorems.         *)
(* ------------------------------------------------------------------------- *)
(* (1) Since we use PROVE_HYP instead of MP to justify rule, erule etc, the  *)
(* order of the subgoals is no longer essential.                             *)
(* (2) Example: conjE                                                        *)
(* In Isabelle:     P/\Q  [|P;Q|]==> R                                       *)
(*                  ------------------                                       *)
(*                          R                                                *)
(*                                                                           *)
(* As a meta rule (briefly - see conjEm below for full notation):            *)
(*              `R`,                           - conclusion                  *)
(*              [                              - premises list               *)
(*                [       ], `P/\Q`  ;                                       *)
(*                [`P`;`Q`], `R`                                             *)
(*              ],                                                           *)
(*              `P/\Q, P==>Q==>R |- R`         - justification theorem       *)
(*                                                                           *)
(* The form of the premises must be preserved in the justification theorem.  *)
(* ie. using `P/\Q, Q==>P==>R |- R` or `Q/\P, P==>Q==>R |- R` as a           *)
(* justification theorem would break the justification and result in an      *)
(* "invalid tactic" exception.                                               *)
(* ------------------------------------------------------------------------- *)

type meta_rule = term * goal list * thm;;


let print_meta_rule: meta_rule->unit =
  fun (c,glist,j) ->
    print_term c ; hd (map (print_newline () ; print_goal) glist) ;
    print_newline () ; print_thm j ; print_newline ();;


(* ------------------------------------------------------------------------- *)
(* inst_meta_rule: instantiation -> meta_rule -> meta_rule                   *)
(* ------------------------------------------------------------------------- *)
(* Instantiates all parts of meta_rules based on an instantiation.           *)
(* ------------------------------------------------------------------------- *)

let inst_meta_rule:instantiation->meta_rule->meta_rule =
  fun inst (c,glist,j) ->
    instantiate inst c,
    map (inst_goal inst) glist,
    INSTANTIATE_ALL inst j;;


(* ------------------------------------------------------------------------- *)
(* REWRITE_META_RULE: thm list -> meta_rule -> meta_rule                     *)
(* ------------------------------------------------------------------------- *)
(* Rewrites all parts of meta_rules using a list of theorems.                *)
(* ------------------------------------------------------------------------- *)

let REWRITE_META_RULE:thm list->meta_rule->meta_rule =
  fun thl (c,glist,j) ->
    let rewr = rhs o concl o (REWRITE_CONV thl)
    and rewrg = (hd o snd3 o (REWRITE_ASM_TAC thl THEN REWRITE_TAC thl)) in
    rewr c,
    map (rewrg) glist,
    REWRITE_RULE thl j;;(* Theorem's hyp is not being rewritten! :/ *)


(* ------------------------------------------------------------------------- *)
(* meta_rule_frees: meta_rule -> term list                                   *)
(* ------------------------------------------------------------------------- *)
(* Returns the list of free variables (or Isabelle ?metavariables) in a      *)
(* meta_rule.                                                                *)
(* ------------------------------------------------------------------------- *)

let meta_rule_frees: meta_rule -> term list =
  fun (c,glist,l) ->
    itlist (union o gl_frees) glist (union (frees c) (thm_frees l));;


(* ------------------------------------------------------------------------- *)
(* meta_rule_mk_primed_vars_I: term_list -> meta_rule ->                     *)
(*                                            meta_rule * instantiation      *)
(* ------------------------------------------------------------------------- *)
(* Applies mk_primed_var to all the free variables in a meta_rule.           *)
(* Returns the new meta_rule and the instantiation for the variable renaming.*)
(* ------------------------------------------------------------------------- *)

let meta_rule_mk_primed_vars_I: term list -> meta_rule -> meta_rule * instantiation =
  fun avoids r ->
    let fvars =  meta_rule_frees r in
    let rec mk_primed_l = fun avoids vars ->
      match vars with
	[] -> null_inst
      | v::rest ->
	  let new_v = mk_primed_var avoids v in
	  compose_insts (term_match [] v new_v) (mk_primed_l (new_v::avoids) rest)
    in
    let inst = mk_primed_l avoids fvars in
    (inst_meta_rule inst r),inst;;


(* ------------------------------------------------------------------------- *)
(* meta_rule_mk_primed_vars: term_list -> meta_rule -> meta_rule             *)
(* ------------------------------------------------------------------------- *)
(* Applies mk_primed_var to all the free variables in a meta_rule.           *)
(* ------------------------------------------------------------------------- *)

let meta_rule_mk_primed_vars: term list -> meta_rule -> meta_rule =
  fun avoids r -> fst (meta_rule_mk_primed_vars_I avoids r);;



(* ------------------------------------------------------------------------- *)
(* inst_meta_rule_vars:                                                      *)
(*                 (term * term) list -> meta_rule -> term list -> meta_rule *)
(* ------------------------------------------------------------------------- *)
(* Instantiates the free variables in a meta_rule. Also renames the          *)
(* uninstantiated variables so as to avoid clashes with free variables and   *)
(* constants in the given goal.                                              *)
(* Essentially it prepares the meta_rule for use with any of xrulem_tac.     *)
(* ------------------------------------------------------------------------- *)
(* (+) By instlist we mean the list of variables and instantiation pairs     *)
(* given by the user.                                                        *)
(* (+) First we check the terms given as variables in the instlist. We must  *)
(* check if they are indeed variables and if they are free variables in the  *)
(* given meta_rule.                                                          *)
(* (+) "match_var" is used to compare a variable with a free variable in the *)
(*  meta_rule. *NOTE* that a variable is accepted as long as it can match a  *)
(* free variable in the meta_rule allowing only type instantiation.          *)
(* (+) "mcheck_var" does the is_var check and tries to find a match with the *)
(* meta_rule's free vars (rfrees) using match_var.                           *)
(* (+) "mcheck_gvar" tries to match variables on the rhs of each instlist    *)
(* pair with the free variables in the goal so as to instantiate their types *)
(* properly. This is done to free the user from declaring the variable types.*)
(* (+) Given variables are replaced with the meta_rule variables (effectively*)
(* achieving type instantiation) and later recombined into the instlist.     *)
(* (+) Secondly, we rename all the variables in the meta_rule using          *)
(* "meta_rule_mk_primed_vars_I" so that they don't match any of the free     *)
(* variables in the goal.                                                    *)
(* (+) We use the same instantiation to rename instlist variables so that    *)
(* they properly match the new variables of the meta_rule.                   *)
(* (+) "new_instlist" should contain variables that fully match primed       *)
(* variables in the meta_rule (new_r).                                       *)
(* (+) For each instlist pair, we find the instantiation that allows the     *)
(* variable to be substituted by the given term. *NOTE* that no check is     *)
(* made on that term. It is the user's responsibility to give a sensible,    *)
(* matching and correctly typed term.                                        *)
(* (+) All the instantiations produced by the instlist are composed into one *)
(* which is then applied to new_r to give the result.                        *)
(* ------------------------------------------------------------------------- *)


let inst_meta_rule_vars: (term * term) list -> meta_rule -> term list -> meta_rule =
  fun instlist r gfrees ->
    let rfrees = meta_rule_frees r in
    let vars,subs = List.split instlist in

    let match_var = fun tm1 tm2 ->
      let inst = try term_match [] tm1 tm2 with Failure _ -> [],[tm2,tm1],[] in
      match inst with
	[],[],_ -> tm2
      | _  -> failwith "match_var: no match" in

    let mcheck_var = fun tm ->
      if (not (is_var tm)) then failwith ("inst_meta_rule_vars: `" ^ string_of_term tm ^ "` is not a variable")
      else try tryfind (match_var tm) rfrees
      with Failure _ -> failwith ("inst_meta_rule_vars: `" ^ string_of_term tm ^ "` could not be found in the meta_rule") in

    let mcheck_gvar = fun var ->
      try let mvar = tryfind (match_var var) gfrees in
      term_match [] var mvar
      with Failure _ ->
	warn true ("inst_meta_rule_vars: `" ^ string_of_term var ^ "` could not be found in the goal") ;
	null_inst in

    let new_r,prim_inst = meta_rule_mk_primed_vars_I gfrees r in
    let new_vars = map ((instantiate prim_inst) o mcheck_var) vars in

    let subs_vars = flat (map frees subs) in
    let new_subs_inst = itlist compose_insts (map mcheck_gvar subs_vars) null_inst in
    let new_subs = map (instantiate new_subs_inst) subs in

    let new_instlist = List.combine new_vars new_subs in
    let mk_inst = fun t1,t2 -> term_match [] t1 t2 in
    let inst = itlist compose_insts (map mk_inst new_instlist) null_inst in
    let result_r = inst_meta_rule inst new_r in
    result_r;;



(* ------------------------------------------------------------------------- *)
(* mk_meta_rule: thm -> meta_rule                                            *)
(* Creates a meta_rule out of a theorem.                                     *)
(* Theorem must be of the form |- H1 ===> H2 ===> ...===> Hn ===> C          *)
(* "===>" is the emulation of meta-level implication so this corresponds to  *)
(* [|H1;H2;...;Hn|] ==> C in Isabelle)                                       *)
(* For each Hi that is a meta-level implication, a "meta_subgoal" is created.*)
(* ------------------------------------------------------------------------- *)
(* (+) undisch_premises uses MUNDISCH to handle meta-level implication. All  *)
(* the premises are undischarged. It returns the list of premises paired     *)
(* with the resulting theorem. Note that MUNDISCH also removes meta-level    *)
(* implication from the undischarged premises.                               *)
(* (+) "mk_meta_subgoal" creates a meta_subgoal from a term. If the term is  *)
(* an implication, the lhs is added as an assumption/premise of the          *)
(* meta_subgoal and mk_meta_subgoal is called recursively for the rhs.       *)
(* (+) The conclusion of the undischarged theorem is the first part of the   *)
(* produced meta_rule.                                                       *)
(* (+) mk_meta_subgoal creates the meta_subgoals for all the premises. They  *)
(* form the second part of the meta_rule.                                    *)
(* (+) The theorem itself is used as the justification theorem, after        *)
(* eliminating any remaining meta-level implication in the conclusion.       *)
(* In theory, the conclusion should never have any remaining meta-level      *)
(* implications. We're just making sure because we don't want any meta-level *)
(* implications to appear in our new subgoals.                               *)
(* ------------------------------------------------------------------------- *)

let (mk_meta_rule: thm -> meta_rule) =
  fun thm ->
    let rec undisch_premises th =
        if is_mimp (concl th)
        then let rest,res_th = undisch_premises (MUNDISCH th) in
             (rand(rator(concl th)))::rest,res_th
        else [],th in
    let (prems,thm) = undisch_premises thm in
    let rec mk_meta_subgoal tm = (
      if (is_mimp(tm)) then
	let (a,c) = dest_mimp tm in
	let (prems,concl) = mk_meta_subgoal c in
	("",ASSUME a)::prems,concl
     else [],tm
     ) in
    concl thm,map mk_meta_subgoal prems,MIMP_TO_IMP_RULE thm;;


(* ------------------------------------------------------------------------- *)
(* Isabelle's natural deduction inference rules as meta_rules.               *)
(* ------------------------------------------------------------------------- *)
(* The trailing 'm' indicates they are represented as meta_rules as opposed  *)
(* to theorems.                                                              *)
(* Use "mk_meta_rule" to create meta_rules from theorems.                    *)
(* Most of the following can be created using mk_meta_rule but are left here *)
(* as examples.                                                              *)
(* ------------------------------------------------------------------------- *)
(* Deprecated. New mk_meta_rule uses meta-level implication so now ALL of    *)
(* these can be represented at the object-level and turned into meta_rules   *)
(* using mk_meta_rule. They are left here for historical reasons and as      *)
(* examples of raw meta rules.                                               *)
(* ------------------------------------------------------------------------- *)

let conjIm:meta_rule =
  (`p/\q`,
   [
   [],`p:bool`;
   [],`q:bool`
  ],
   conjI);;

let conjEm:meta_rule =
  (`r:bool`,
   [
   [],`p/\q`;
   [("",ASSUME `p:bool`);("",ASSUME `q:bool`)],`r:bool`
  ],
  (UNDISCH o UNDISCH o TAUT) `p/\q==>(p==>q==>r)==>r`
);;

let notEm:meta_rule =
  (`r:bool`,
   [
   [],`~a`;
   [],`a:bool`
  ],
  (UNDISCH o UNDISCH o TAUT) `~a==>a==>r`
);;

let disjI1m:meta_rule =
  (`p\/q`,
   [
   [],`p:bool`;
  ],
   UNDISCH ( TAUT `p==>p\/q` ));;

let disjI2m:meta_rule =
  (`p\/q`,
   [
   [],`q:bool`;
  ],
   UNDISCH ( TAUT `q==>p\/q` ));;

let disjEm:meta_rule =
  (`r:bool`,
   [
   [],`p\/q`;
   [("",ASSUME `p:bool`)],`r:bool`;
   [("",ASSUME `q:bool`)],`r:bool`
  ],
   (UNDISCH o UNDISCH o UNDISCH) ( TAUT `p\/q==>(p==>r)==>(q==>r)==>r`)
  );;


let impIm:meta_rule =
  (`p==>q`,
   [
   [("",ASSUME `p:bool`)],`q:bool`
  ],
   UNDISCH (TAUT `(p==>q)==>(p==>q)`)
);;


let impEm:meta_rule =
  (`r:bool`,
   [
    [],`p==>q`;
    [],`p:bool`;
    [("",ASSUME `q:bool`)],`r:bool`
  ],
   (UNDISCH o UNDISCH o UNDISCH o TAUT) `(p==>q)==>p==>(q==>r)==>r`
);;


let mpm:meta_rule =
  (`q:bool`,
   [
    [],`p==>q`;
    [],`p:bool`
  ],
   (UNDISCH o UNDISCH o TAUT) `(p==>q)==>(p==>q)`
);;

(* Note from old mk_meta_rule:                                               *)
(* This one cannot be expressed as a theorem because HOL Light insists on    *)
(* ordering the assumptions of the theorem so the major premise is `p`       *)
(* instead of `~p`.                                                          *)

let notEm:meta_rule =
  (`r:bool`,
   [
   [],`~a`;
   [],`a:bool`
  ],
  (UNDISCH o UNDISCH o TAUT) `~a==>a==>r`
);;


(* ------------------------------------------------------------------------- *)
(* rulem_tac: ((term * term) list -> meta_rule -> tactic):                   *)
(* Isabelle's rule as a HOL Light meta_rule tactic.                          *)
(* Uses a rule of the form  H1,H2,...,Hn |- C represented as a meta_rule     *)
(* to solve A1,A2,...,Am ?- G                                                *)
(* Matches C to the goal G, then splits the goal to                          *)
(*          A1,A2,...,Am ?- H1                                               *)
(*          A1,A2,...,Am ?- H2                                               *)
(*          ...                                                              *)
(*          A1,A2,...,Am ?- Hn                                               *)
(* Hi can be of the form Hi1,Hi2,...,Hik ?- HiC then the goal produced is    *)
(*          A1,A2,...,Am,Hi1,Hi2,...,Hik ?- HiC                              *)
(* ------------------------------------------------------------------------- *)
(* (+) "avoids" lists all the free variables in the assumptions and goal so  *)
(* as to avoid instantiating those (as in variable conflicts with the rule   *)
(* or partly instantiated rule in the case of erule)                         *)
(* (+) First we check if C matches G. If it does we keep the resulting       *)
(* instantiation (ins).                                                      *)
(* (+) We instantiate the "meta-subgoals" of the meta_rule using ins.        *)
(* In essence we're instantiating the premises of the rule. (new_hyps)       *)
(* (+) The "create_goal" function creates the new goals by adding the        *)
(* assumption list A1--Am to the instantiated "meta-subgoal".                *)
(* (+) create_goal is mapped on new_hyps to create the new subgoal list.     *)
(* (+) The "create_dischl" function creates the list of the terms involved   *)
(* in the premises of each instantiated meta-subgoal. In order to create the *)
(* justification of the tactic, we need to convert Hi1,Hi2,...,Hik |- HiC    *)
(* into |- Hi1==>Hi2==>...==>Hik==>HiC. That is the only way we can capture  *)
(* the notion of a "subgoal" within a HOL Light object-level theorem.        *)
(* We will then use PROVE_HYP to eliminate each of the proven subgoals from  *)
(* the rule's justification theorem. In order to achieve this conversion we  *)
(* need to keep a list of the instantiated premises of the rule (dischls)    *)
(* for each meta_subgoal so as to avoid discharging the original goal's      *)
(* assumptions or _FALSITY_.                                                 *)
(* (+) "disch_pair" is used for convenience. dishls is combined with the     *)
(* list of proven subgoals so that each subgoal is attached to its           *)
(* corresponding premises list (dischl). disch_pair then does the discharges.*)
(* It also takes care of instantiating the meta-variables in those premises  *)
(* for proper justification.                                                 *)
(* (+) normalfrees is used to calculate the list of metavariables that will  *)
(* end up in the new subgoals. It contains all the free variables in the     *)
(* goal and instlist.                                                        *)
(* (+) The newly introduced metavariables are found by subtracting           *)
(* normalfrees from the set of all free variables in all new goals.          *)
(* ------------------------------------------------------------------------- *)

let (rulem_tac: (term*term) list->meta_rule->tactic) =
  fun instlist r ((asl,w) as g) ->
    let (c,hyps,thm) = inst_meta_rule_vars instlist r (gl_frees g) in

    let ins = try ( term_match (gl_frees g) c w ) with Failure _ -> failwith "Rule doesn't match!" in

    let new_hyps = map (inst_goal ins) hyps in
    let new_goals = map (fun (hs,gl) -> (hs@asl,gl)) new_hyps in
    let rec create_dischl (asms,g) =
      if (asms = []) then [] else ((concl o snd o hd) asms)::(create_dischl ((tl asms),g)) in
    let dischls = map create_dischl new_hyps in
    let disch_pair = fun i (dischl,thm) -> DISCHL (map (instantiate i) dischl) thm in

    let normalfrees = itlist union (map ( fun (_,y) -> frees y ) instlist ) (gl_frees g) in
    let mvs = subtract (itlist union (map gl_frees new_goals) []) normalfrees in
    (mvs,null_inst),new_goals,fun i l ->
     List.fold_left (fun t1 t2 -> PROVE_HYP (INSTANTIATE_ALL i t2) t1) (INSTANTIATE_ALL (compose_insts ins i) thm) (map (disch_pair i) (zip dischls l));;

(* Debugging prints:
    print_string "i:" ; print_thm (INSTANTIATE_ALL i thm) ; print_newline();
     print_string "---" ; print_newline ();
     print_thl (map (disch_pair i) (zip dischls l)) ; print_string "----" ;
     print_int (length l) ; print_newline();

      print_string "r:" ; print_thm res ; print_newline(); res;;
 *)




(* ------------------------------------------------------------------------- *)
(* erulem_tac: ((term * term) list -> meta_rule -> tactic):                  *)
(* Isabelle's erule as a HOL Light meta_rule tactic.                         *)
(* Works like rulem but also matches the first hypothesis H1 with one of the *)
(* assumptions A1--Am and instantiates accordingly.                          *)
(* A "proper" elimination rule H1 is of the form ?- H1 (ie. has no premises) *)
(* ------------------------------------------------------------------------- *)
(* Same as rulem with some added stuff.                                      *)
(* (+) If there are no "meta_subgoals" (no new subgoals to create) we fail.  *)
(* (+) Otherwise we use the first "meta_subgoal" as our primary hypothesis   *)
(* (the one that will be eliminated - prim_hyp).                             *)
(* (+) If prim_hyp has premises then this is not a "proper" elimination rule.*)
(* (+) Otherwise try to match any of the assumptions with prim_hyp. The      *)
(* resulting instantiation is elim_inst.                                     *)
(* (+) Instantiate all generated meta_subgoals with elim_inst. Retrieve the  *)
(* (now instantiated) prim_hyp and remove it from the new subgoals (it is    *)
(* trivially proven). We get a "pattern-matching is not exhaustive" warning  *)
(* here, but we have already checked that new_hyps is non-empty.             *)
(* (+) prim_thm is a trivial theorem that proves the subgoal corresponding   *)
(* to prim_hyp.                                                              *)
(* (+) Instantiate the justification theorem with elim_thm.                  *)
(* (+) Add prim_hyp to the justification (pretending its a proven subgoal).  *)
(* (+) Use a hack to add the eliminated assumption to the proven subgoals so *)
(* that we pass the validity check properly.                                 *)
(* ------------------------------------------------------------------------- *)

let (erulem_tac: (term * term) list -> meta_rule->tactic) =
  fun instlist r ((asl,w) as g) ->
    let (c,hyps,thm) = inst_meta_rule_vars instlist r (gl_frees g) in

    let ins = try ( term_match (gl_frees g) c w )
    with Failure _ -> failwith "Rule doesn't match!" in
    let new_hyps = map (inst_goal ins) hyps in

    let (prems,prim_hyp) =
      if (new_hyps = []) then failwith "erule: Not a proper elimination rule: no premises!"
      else hd new_hyps in
    let avoids = gl_frees g in

    let asl,(prim_thm,elim_inst) =
      if (prems = [])
      then try term_to_asm_match avoids prim_hyp asl with Failure s -> failwith ("erule: " ^ s)
      else failwith "erule: Not a proper elimination rule: major premise has assumptions!" in
    let (_,prim_hyp)::new_hyps = map (inst_goal elim_inst) new_hyps in
    let thm = INSTANTIATE_ALL elim_inst thm in

    let create_goal = fun asms (hs,gl) -> (hs@asms,gl) in
    let new_goals = map (create_goal asl) new_hyps in
    let rec create_dischl =
      fun (asms,g) ->
	if (asms = []) then []
	else ((concl o snd o hd) asms)::(create_dischl ((tl asms),g)) in
    let dischls = map create_dischl new_hyps in
    let disch_pair = fun i (dischl,thm) -> DISCHL (map (instantiate i) dischl) thm in

    let normalfrees = itlist union (map ( fun (_,y) -> frees y ) instlist ) (gl_frees g) in
    let mvs = subtract (itlist union (map gl_frees new_goals) []) normalfrees in
    (mvs,null_inst),new_goals,fun i l ->
      let major_thmi = INSTANTIATE_ALL i prim_thm in
      List.fold_left (fun t1 t2 -> PROVE_HYP (INSTANTIATE_ALL i t2) t1) (INSTANTIATE_ALL (compose_insts ins i) thm)
	(major_thmi :: map (ADD_HYP major_thmi) (map (disch_pair i) (zip dischls l)));;


(* ------------------------------------------------------------------------- *)
(* drulem_tac: ((term * term) list -> meta_rule -> tactic):                  *)
(* Isabelle's drule as a HOL Light meta_rule tactic.                         *)
(* Uses rules as shown in "rule".                                            *)
(* Matches the first hypothesis H1 with one of the                           *)
(* assumptions A1--Am and instantiates accordingly.                          *)
(* The assumption is removed from the list and the trivial goal is proven    *)
(* automatically.                                                            *)
(* A "proper" destructio rule H1 is of the form ?- H1 (ie. has no premises)  *)
(* The goal A1,A2,...,Am,G ?- C is also added.                               *)
(* ------------------------------------------------------------------------- *)
(* Same as erulem with a few differences.                                    *)
(* [+] Does not try to match the goal c.                                     *)
(* [+] Adds an extra goal c ?- w after instantiating c.                      *)
(* [+] The new goal is treated slightly different in the justification.      *)
(* It is the one whose premises must be proven so as to get to the final     *)
(* goal. So it gets proven using PROVE_HYP by the result of the              *)
(* justification on the original rule.                                       *)
(* ------------------------------------------------------------------------- *)

let (drulem_tac: (term * term) list -> meta_rule->tactic) =
  fun instlist r ((asl,w) as g) ->
    let (c,hyps,thm) = inst_meta_rule_vars instlist r (gl_frees g) in

    let (prems,major_prem) =
      if (hyps = []) then failwith "drule: Not a proper destruction rule: no premises!"
      else hd hyps in
    let avoids = gl_frees g in

    let asl,(major_thm,elim_inst) =
      if (prems = [])
      then try term_to_asm_match avoids major_prem asl with Failure s -> failwith ("drule: " ^ s)
      else failwith "drule: not a proper destruction rule: major premise has assumptions!" in
    let (_,major_asm)::new_hyps = map (inst_goal elim_inst) hyps in
    let thm = INSTANTIATE_ALL elim_inst thm in
    let create_goal = fun asms (hs,gl) -> (hs@asms,gl) in
    let new_goals = (map (create_goal asl) new_hyps) @ [create_goal asl (["",ASSUME (instantiate elim_inst c)],w)] in

    let rec create_dischl =
      fun (asms,g) ->
	if (asms = []) then []
	else ((concl o snd o hd) asms)::(create_dischl ((tl asms),g)) in
    (* We add an empty discharge list at the end for the extra goal. *)
    let dischls = map create_dischl new_hyps @ [[]] in
    let disch_pair = fun i (dischl,thm) -> DISCHL (map (instantiate i) dischl) thm in

    let normalfrees = itlist union (map ( fun (_,y) -> frees y ) instlist ) (gl_frees g) in
    let mvs = subtract (itlist union (map gl_frees new_goals) []) normalfrees in
    (mvs,null_inst),new_goals,fun i l ->
      let major_thmi = INSTANTIATE_ALL i major_thm in
      let l = (major_thmi :: map (ADD_HYP major_thmi) (map (disch_pair i) (zip dischls l))) in
      PROVE_HYP (List.fold_left (fun t1 t2 -> PROVE_HYP (INSTANTIATE_ALL i t2) t1) (INSTANTIATE_ALL i thm) ((butlast) l)) (last l);;


(* ------------------------------------------------------------------------- *)
(* frulem_tac: ((term * term) list -> meta_rule -> tactic):                  *)
(* Isabelle's frule as a HOL Light meta_rule tactic.                         *)
(* Same as drule, but does not remove the matching assumption from the list. *)
(* ------------------------------------------------------------------------- *)
(* Same as drulem only skipping the parts that eat up the assumption and     *)
(* re-add it to the proven subgoals.                                         *)
(* ------------------------------------------------------------------------- *)

let (frulem_tac: (term * term) list -> meta_rule->tactic) =
  fun instlist r ((asl,w) as g) ->
    let (c,hyps,thm) = inst_meta_rule_vars instlist r (gl_frees g) in

    let (prems,major_prem) =
      if (hyps = []) then failwith "frule: Not a proper destruction rule: no premises!"
      else hd hyps in
    let avoids = gl_frees g in

    let _,(major_thm,elim_inst) =
      if (prems = [])
      then try term_to_asm_match avoids major_prem asl with Failure s -> failwith ("frule: " ^ s)
      else failwith "frule: Not a proper destruction rule: major premise has assumptions!" in
    let (_,major_asm)::new_hyps = map (inst_goal elim_inst) hyps in
    let thm = INSTANTIATE_ALL elim_inst thm in
    let create_goal = fun asms (hs,gl) -> (hs@asms,gl) in
    let new_goals = (map (create_goal asl) new_hyps) @ [create_goal asl (["",ASSUME (instantiate elim_inst c)],w)] in

    let rec create_dischl =
      fun (asms,g) ->
	if (asms = []) then []
	else ((concl o snd o hd) asms)::(create_dischl ((tl asms),g)) in
    let dischls = map create_dischl new_hyps @ [[]] in
    let disch_pair = fun i (dischl,thm) -> DISCHL (map (instantiate i) dischl) thm in

    let normalfrees = itlist union (map ( fun (_,y) -> frees y ) instlist ) (gl_frees g) in
    let mvs = subtract (itlist union (map gl_frees new_goals) []) normalfrees in
    (mvs,null_inst),new_goals,fun i l ->
      let major_thmi = INSTANTIATE_ALL i major_thm in
      let l = (major_thmi :: ((map (disch_pair i)) o (zip dischls)) l) in
      PROVE_HYP (List.fold_left (fun t1 t2 -> PROVE_HYP (INSTANTIATE_ALL i t2) t1) (INSTANTIATE_ALL i thm) ((butlast) l)) (last l);;


(* ------------------------------------------------------------------------- *)
(* cutm_tac: ((term * term) list -> meta_rule -> tactic):                    *)
(* Isabelle's cut_tac as a HOL Light meta_rule tactic.                       *)
(* Inserts a theorem in the assumptions.                                     *)
(* ------------------------------------------------------------------------- *)
(* (+) WARNING: It does not introduce metavariables like the other tactics   *)
(* do! In the TODO list...                                                   *)
(* ------------------------------------------------------------------------- *)

let (cutm_tac: (term * term) list -> meta_rule->tactic) =
    fun instlist r g ->
    let (_,_,thm) = inst_meta_rule_vars instlist r (gl_frees g) in
    (ASSUME_TAC thm) g;;


(* ------------------------------------------------------------------------- *)
(* erulenm_tac : (term * term) list -> int -> meta_rule->tactic)             *)
(* drulenm_tac : (term * term) list -> int -> meta_rule->tactic)             *)
(* frulenm_tac : (term * term) list -> int -> meta_rule->tactic)             *)
(* Identical to their counterparts, the only difference being that they try  *)
(* to match a particular assumption given by number.                         *)
(* ------------------------------------------------------------------------- *)

let (erulenm_tac: (term * term) list -> int -> meta_rule->tactic) =
  fun instlist n r ((asl,w) as g) ->
    let (c,hyps,thm) = inst_meta_rule_vars instlist r (gl_frees g) in

    let ins = try ( term_match [] c w )
    with Failure _ -> failwith "Rule doesn't match!" in
    let new_hyps = map (inst_goal ins) hyps in

    let (prems,prim_hyp) =
      if (new_hyps = []) then failwith "erule: Not a proper elimination rule: no premises!"
      else hd new_hyps in
    let avoids = gl_frees g in

    let asl,(prim_thm,elim_inst) =
      if (prems = [])
      then try term_to_asm_n_match avoids prim_hyp (rev asl) n with Failure s -> failwith ("erule: " ^ s)
      else failwith "erule: Not a proper elimination rule: major premise has assumptions!" in
    let (_,prim_hyp)::new_hyps = map (inst_goal elim_inst) new_hyps in
    let thm = INSTANTIATE_ALL elim_inst thm in

    let create_goal = fun asms (hs,gl) -> (hs@asms,gl) in
    let new_goals = map (create_goal asl) new_hyps in
    let rec create_dischl =
      fun (asms,g) ->
	if (asms = []) then []
	else ((concl o snd o hd) asms)::(create_dischl ((tl asms),g)) in
    let dischls = map create_dischl new_hyps in
    let disch_pair = fun i (dischl,thm) -> DISCHL (map (instantiate i) dischl) thm in

    let normalfrees = itlist union (map ( fun (_,y) -> frees y ) instlist ) (gl_frees g) in
    let mvs = subtract (itlist union (map gl_frees new_goals) []) normalfrees in
    (mvs,null_inst),new_goals,fun i l ->
      let major_thmi = INSTANTIATE_ALL i prim_thm in
      List.fold_left (fun t1 t2 -> PROVE_HYP (INSTANTIATE_ALL i t2) t1) (INSTANTIATE_ALL (compose_insts ins i) thm)
	(major_thmi :: map (ADD_HYP major_thmi) (map (disch_pair i) (List.combine dischls l)));;


let (drulenm_tac: (term * term) list -> int -> meta_rule->tactic) =
  fun instlist n r ((asl,w) as g) ->
    let (c,hyps,thm) = inst_meta_rule_vars instlist r (gl_frees g) in

    let (prems,major_prem) =
      if (hyps = []) then failwith "drule: Not a proper destruction rule: no premises!"
      else hd hyps in
    let avoids = gl_frees g in

    let asl,(major_thm,elim_inst) =
      if (prems = [])
      then try term_to_asm_n_match avoids major_prem (rev asl) n with Failure s -> failwith ("drule: " ^ s)
      else failwith "drule: not a proper destruction rule: major premise has assumptions!" in
    let (_,major_asm)::new_hyps = map (inst_goal elim_inst) hyps in
    let thm = INSTANTIATE_ALL elim_inst thm in
    let create_goal = fun asms (hs,gl) -> (hs@asms,gl) in
    let new_goals = (map (create_goal asl) new_hyps) @ [create_goal asl (["",ASSUME (instantiate elim_inst c)],w)] in

    let rec create_dischl =
      fun (asms,g) ->
	if (asms = []) then []
	else ((concl o snd o hd) asms)::(create_dischl ((tl asms),g)) in
    (* We add an empty discharge list at the end for the extra goal. *)
    let dischls = map create_dischl new_hyps @ [[]] in
    let disch_pair = fun i (dischl,thm) -> DISCHL (map (instantiate i) dischl) thm in

    let normalfrees = itlist union (map ( fun (_,y) -> frees y ) instlist ) (gl_frees g) in
    let mvs = subtract (itlist union (map gl_frees new_goals) []) normalfrees in
    (mvs,null_inst),new_goals,fun i l ->
      let major_thmi = INSTANTIATE_ALL i major_thm in
      let l = (major_thmi :: map (ADD_HYP major_thmi) (map (disch_pair i) (List.combine dischls l))) in
      PROVE_HYP (List.fold_left (fun t1 t2 -> PROVE_HYP (INSTANTIATE_ALL i t2) t1) (INSTANTIATE_ALL i thm) ((butlast) l)) (last l);;


let (frulenm_tac: (term * term) list -> int -> meta_rule->tactic) =
  fun instlist n r ((asl,w) as g) ->
    let (c,hyps,thm) = inst_meta_rule_vars instlist r (gl_frees g) in

    let (prems,major_prem) =
      if (hyps = []) then failwith "frule: Not a proper destruction rule: no premises!"
      else hd hyps in
    let avoids = gl_frees g in

    let _,(major_thm,elim_inst) =
      if (prems = [])
      then try term_to_asm_n_match avoids major_prem (rev asl) n with Failure s -> failwith ("frule: " ^ s)
      else failwith "frule: Not a proper destruction rule: major premise has assumptions!" in
    let (_,major_asm)::new_hyps = map (inst_goal elim_inst) hyps in
    let thm = INSTANTIATE_ALL elim_inst thm in
    let create_goal = fun asms (hs,gl) -> (hs@asms,gl) in
    let new_goals = (map (create_goal asl) new_hyps) @ [create_goal asl (["",ASSUME (instantiate elim_inst c)],w)] in

    let rec create_dischl =
      fun (asms,g) ->
	if (asms = []) then []
	else ((concl o snd o hd) asms)::(create_dischl ((tl asms),g)) in
    let dischls = map create_dischl new_hyps @ [[]] in
    let disch_pair = fun i (dischl,thm) -> DISCHL (map (instantiate i) dischl) thm in

    let normalfrees = itlist union (map ( fun (_,y) -> frees y ) instlist ) (gl_frees g) in
    let mvs = subtract (itlist union (map gl_frees new_goals) []) normalfrees in
    (mvs,null_inst),new_goals,fun i l ->
      let major_thmi = INSTANTIATE_ALL i major_thm in
      let l = (major_thmi :: ((map (disch_pair i)) o (List.combine dischls)) l) in
      PROVE_HYP (List.fold_left (fun t1 t2 -> PROVE_HYP (INSTANTIATE_ALL i t2) t1) (INSTANTIATE_ALL i thm) ((butlast) l)) (last l);;


(* ------------------------------------------------------------------------- *)
(* Xrulem versions for empty instlist.                                       *)
(* ------------------------------------------------------------------------- *)

let rulem: meta_rule -> tactic = rulem_tac [];;
let erulem: meta_rule -> tactic = erulem_tac [];;
let drulem: meta_rule -> tactic = drulem_tac [];;
let frulem: meta_rule -> tactic = frulem_tac [];;

let erulenm: int -> meta_rule -> tactic = erulenm_tac [];;
let drulenm: int -> meta_rule -> tactic = drulenm_tac [];;
let frulenm: int -> meta_rule -> tactic = frulenm_tac [];;

(* For consistency with HOL Light capitalized tactics: *)
let RULEM,ERULEM,DRULEM,FRULEM = rulem,erulem,drulem,frulem;;
let ERULENM,DRULENM,FRULENM = erulenm,drulenm,frulenm;;


(* ------------------------------------------------------------------------- *)
(* Xrule and Xrule_tac using arbitrary inference rules in the form of thms.  *)
(* (see mk_meta_rule and meta_rule type)                                     *)
(* ------------------------------------------------------------------------- *)

let rule_tac: (term * term) list -> thm -> tactic =
  fun instlist thm ->
    rulem_tac instlist (mk_meta_rule thm);;

let erule_tac: (term * term) list -> thm -> tactic =
  fun instlist thm ->
    erulem_tac instlist (mk_meta_rule thm);;

let drule_tac: (term * term) list -> thm -> tactic =
  fun instlist thm ->
    drulem_tac instlist (mk_meta_rule thm);;

let frule_tac: (term * term) list -> thm -> tactic =
  fun instlist thm ->
    frulem_tac instlist (mk_meta_rule thm);;

let cut_tac: (term * term) list -> thm -> tactic =
  fun instlist thm ->
    cutm_tac instlist (mk_meta_rule thm);;

let RULE_TAC,ERULE_TAC,DRULE_TAC,FRULE_TAC,CUT_TAC = rule_tac,erule_tac,drule_tac,frule_tac,cut_tac;;


let erulen_tac: (term * term) list -> int -> thm -> tactic =
  fun instlist n thm ->
    erulenm_tac instlist n (mk_meta_rule thm);;

let drulen_tac: (term * term) list -> int -> thm -> tactic =
  fun instlist n thm ->
    drulenm_tac instlist n (mk_meta_rule thm);;

let frulen_tac: (term * term) list -> int -> thm -> tactic =
  fun instlist n thm ->
    frulenm_tac instlist n (mk_meta_rule thm);;

let ERULEN_TAC,DRULEN_TAC,FRULEN_TAC = erulen_tac,drulen_tac,frulen_tac;;


let rule: (thm -> tactic) = rule_tac [];;
let erule: (thm -> tactic) = erule_tac [];;
let drule: (thm -> tactic) = drule_tac [];;
let frule: (thm -> tactic) = frule_tac [];;

let RULE,ERULE,DRULE,FRULE = rule,erule,drule,frule;;


let erulen: (int -> thm -> tactic) = erulen_tac [];;
let drulen: (int -> thm -> tactic) = drulen_tac [];;
let frulen: (int -> thm -> tactic) = frulen_tac [];;

let ERULEN,DRULEN,FRULEN = erulen,drulen,frulen;;



(* ------------------------------------------------------------------------- *)
(* The following are extensions of the assumption tactics found in           *)
(* new_tactics.ml.                                                           *)
(* These treat meta-level implication in the assumption as a subgoal and     *)
(* perform 1 backwards inference step to try and match its                   *)
(* (meta-)assumptions.                                                       *)
(* ------------------------------------------------------------------------- *)
(* Jacques Fleuriot pointed out this is Isabelle's behaviour.                *)
(* ------------------------------------------------------------------------- *)

let assumption =
  let const_rule thm = (* we don't want to refresh any variables in the theorem *)
    let fs = thm_frees thm in
    rule_tac (zip fs fs) thm THEN assumption in
  assumption ORELSE (FIRST_ASSUM const_rule);;

let meta_assumption mvs =
  let const_rule thm = (* we don't want to refresh any variables in the theorem *)
    let fs = thm_frees thm in
    rule_tac (zip fs fs) thm THEN meta_assumption mvs in
  meta_assumption mvs ORELSE (FIRST_ASSUM const_rule);;

let ema () = (e o meta_assumption o top_metas o p) ()  ;;
