summaryrefslogtreecommitdiff
path: root/tactics/tactics.ml
diff options
context:
space:
mode:
authorGravatar Samuel Mimram <samuel.mimram@ens-lyon.org>2004-07-28 21:54:47 +0000
committerGravatar Samuel Mimram <samuel.mimram@ens-lyon.org>2004-07-28 21:54:47 +0000
commit6b649aba925b6f7462da07599fe67ebb12a3460e (patch)
tree43656bcaa51164548f3fa14e5b10de5ef1088574 /tactics/tactics.ml
Imported Upstream version 8.0pl1upstream/8.0pl1
Diffstat (limited to 'tactics/tactics.ml')
-rw-r--r--tactics/tactics.ml1922
1 files changed, 1922 insertions, 0 deletions
diff --git a/tactics/tactics.ml b/tactics/tactics.ml
new file mode 100644
index 00000000..cab4f025
--- /dev/null
+++ b/tactics/tactics.ml
@@ -0,0 +1,1922 @@
+(************************************************************************)
+(* v * The Coq Proof Assistant / The Coq Development Team *)
+(* <O___,, * CNRS-Ecole Polytechnique-INRIA Futurs-Universite Paris Sud *)
+(* \VV/ **************************************************************)
+(* // * This file is distributed under the terms of the *)
+(* * GNU Lesser General Public License Version 2.1 *)
+(************************************************************************)
+
+(* $Id: tactics.ml,v 1.162.2.2 2004/07/16 19:30:55 herbelin Exp $ *)
+
+open Pp
+open Util
+open Names
+open Nameops
+open Sign
+open Term
+open Termops
+open Declarations
+open Inductive
+open Inductiveops
+open Reductionops
+open Environ
+open Libnames
+open Evd
+open Pfedit
+open Tacred
+open Rawterm
+open Tacmach
+open Proof_trees
+open Proof_type
+open Logic
+open Evar_refiner
+open Clenv
+open Refiner
+open Tacticals
+open Hipattern
+open Coqlib
+open Nametab
+open Genarg
+open Tacexpr
+open Decl_kinds
+
+exception Bound
+
+let rec nb_prod x =
+ let rec count n c =
+ match kind_of_term c with
+ Prod(_,_,t) -> count (n+1) t
+ | LetIn(_,a,_,t) -> count n (subst1 a t)
+ | Cast(c,_) -> count n c
+ | _ -> n
+ in count 0 x
+
+(*********************************************)
+(* Tactics *)
+(*********************************************)
+
+(****************************************)
+(* General functions *)
+(****************************************)
+
+(*
+let get_pairs_from_bindings =
+ let pair_from_binding = function
+ | [(Bindings binds)] -> binds
+ | _ -> error "not a binding list!"
+ in
+ List.map pair_from_binding
+*)
+
+let string_of_inductive c =
+ try match kind_of_term c with
+ | Ind ind_sp ->
+ let (mib,mip) = Global.lookup_inductive ind_sp in
+ string_of_id mip.mind_typename
+ | _ -> raise Bound
+ with Bound -> error "Bound head variable"
+
+let rec head_constr_bound t l =
+ let t = strip_outer_cast(collapse_appl t) in
+ match kind_of_term t with
+ | Prod (_,_,c2) -> head_constr_bound c2 l
+ | LetIn (_,_,_,c2) -> head_constr_bound c2 l
+ | App (f,args) ->
+ head_constr_bound f (Array.fold_right (fun a l -> a::l) args l)
+ | Const _ -> t::l
+ | Ind _ -> t::l
+ | Construct _ -> t::l
+ | Var _ -> t::l
+ | _ -> raise Bound
+
+let head_constr c =
+ try head_constr_bound c [] with Bound -> error "Bound head variable"
+
+(*
+let bad_tactic_args s l =
+ raise (RefinerError (BadTacticArgs (s,l)))
+*)
+
+(******************************************)
+(* Primitive tactics *)
+(******************************************)
+
+let introduction = Tacmach.introduction
+let intro_replacing = Tacmach.intro_replacing
+let internal_cut = Tacmach.internal_cut
+let internal_cut_rev = Tacmach.internal_cut_rev
+let refine = Tacmach.refine
+let convert_concl = Tacmach.convert_concl
+let convert_hyp = Tacmach.convert_hyp
+let thin = Tacmach.thin
+let thin_body = Tacmach.thin_body
+
+(* Moving hypotheses *)
+let move_hyp = Tacmach.move_hyp
+
+(* Renaming hypotheses *)
+let rename_hyp = Tacmach.rename_hyp
+
+(* Refine as a fixpoint *)
+let mutual_fix = Tacmach.mutual_fix
+
+let fix ido n = match ido with
+ | None -> mutual_fix (Pfedit.get_current_proof_name ()) n []
+ | Some id -> mutual_fix id n []
+
+(* Refine as a cofixpoint *)
+let mutual_cofix = Tacmach.mutual_cofix
+
+let cofix = function
+ | None -> mutual_cofix (Pfedit.get_current_proof_name ()) []
+ | Some id -> mutual_cofix id []
+
+(**************************************************************)
+(* Reduction and conversion tactics *)
+(**************************************************************)
+
+type tactic_reduction = env -> evar_map -> constr -> constr
+
+(* The following two tactics apply an arbitrary
+ reduction function either to the conclusion or to a
+ certain hypothesis *)
+
+let reduct_in_concl redfun gl =
+ convert_concl_no_check (pf_reduce redfun gl (pf_concl gl)) gl
+
+let reduct_in_hyp redfun (id,_,(where,where')) gl =
+ let (_,c, ty) = pf_get_hyp gl id in
+ let redfun' = (*under_casts*) (pf_reduce redfun gl) in
+ match c with
+ | None ->
+ if where = InHypValueOnly then
+ errorlabstrm "" (pr_id id ++ str "has no value");
+ if Options.do_translate () then where' := Some where;
+ convert_hyp_no_check (id,None,redfun' ty) gl
+ | Some b ->
+ let where =
+ if !Options.v7 & where = InHyp then InHypValueOnly else where in
+ let b' = if where <> InHypTypeOnly then redfun' b else b in
+ let ty' = if where <> InHypValueOnly then redfun' ty else ty in
+ if Options.do_translate () then where' := Some where;
+ convert_hyp_no_check (id,Some b',ty') gl
+
+let reduct_option redfun = function
+ | Some id -> reduct_in_hyp redfun id
+ | None -> reduct_in_concl redfun
+
+(* The following tactic determines whether the reduction
+ function has to be applied to the conclusion or
+ to the hypotheses. *)
+
+let redin_combinator redfun =
+ onClauses (reduct_option redfun)
+
+(* Now we introduce different instances of the previous tacticals *)
+let change_and_check cv_pb t env sigma c =
+ if is_fconv cv_pb env sigma t c then
+ t
+ else
+ errorlabstrm "convert-check-hyp" (str "Not convertible")
+
+(* Use cumulutavity only if changing the conclusion not a subterm *)
+let change_on_subterm cv_pb t = function
+ | None -> change_and_check cv_pb t
+ | Some occl -> contextually false occl (change_and_check CONV t)
+
+let change_in_concl occl t = reduct_in_concl (change_on_subterm CUMUL t occl)
+let change_in_hyp occl t = reduct_in_hyp (change_on_subterm CONV t occl)
+
+let change_option occl t = function
+ Some id -> change_in_hyp occl t id
+ | None -> change_in_concl occl t
+
+let change occl c cls =
+ (match cls, occl with
+ ({onhyps=(Some(_::_::_)|None)}|{onhyps=Some(_::_);onconcl=true}),
+ Some _ ->
+ error "No occurrences expected when changing several hypotheses"
+ | _ -> ());
+ onClauses (change_option occl c) cls
+
+(* Pour usage interne (le niveau User est pris en compte par reduce) *)
+let red_in_concl = reduct_in_concl red_product
+let red_in_hyp = reduct_in_hyp red_product
+let red_option = reduct_option red_product
+let hnf_in_concl = reduct_in_concl hnf_constr
+let hnf_in_hyp = reduct_in_hyp hnf_constr
+let hnf_option = reduct_option hnf_constr
+let simpl_in_concl = reduct_in_concl nf
+let simpl_in_hyp = reduct_in_hyp nf
+let simpl_option = reduct_option nf
+let normalise_in_concl = reduct_in_concl compute
+let normalise_in_hyp = reduct_in_hyp compute
+let normalise_option = reduct_option compute
+let unfold_in_concl loccname = reduct_in_concl (unfoldn loccname)
+let unfold_in_hyp loccname = reduct_in_hyp (unfoldn loccname)
+let unfold_option loccname = reduct_option (unfoldn loccname)
+let pattern_option l = reduct_option (pattern_occs l)
+
+(* A function which reduces accordingly to a reduction expression,
+ as the command Eval does. *)
+
+let reduce redexp cl goal =
+ redin_combinator (reduction_of_redexp redexp) cl goal
+
+(* Unfolding occurrences of a constant *)
+
+let unfold_constr = function
+ | ConstRef sp -> unfold_in_concl [[],EvalConstRef sp]
+ | VarRef id -> unfold_in_concl [[],EvalVarRef id]
+ | _ -> errorlabstrm "unfold_constr" (str "Cannot unfold a non-constant.")
+
+(*******************************************)
+(* Introduction tactics *)
+(*******************************************)
+
+let fresh_id avoid id gl =
+ next_global_ident_away true id (avoid@(pf_ids_of_hyps gl))
+
+let id_of_name_with_default s = function
+ | Anonymous -> id_of_string s
+ | Name id -> id
+
+let default_id gl = function
+ | (name,None,t) ->
+ (match kind_of_term (pf_whd_betadeltaiota gl (pf_type_of gl t)) with
+ | Sort (Prop _) -> (id_of_name_with_default "H" name)
+ | Sort (Type _) -> (id_of_name_with_default "X" name)
+ | _ -> anomaly "Wrong sort")
+ | (name,Some b,_) -> id_of_name_using_hdchar (pf_env gl) b name
+
+(* Non primitive introduction tactics are treated by central_intro
+ There is possibly renaming, with possibly names to avoid and
+ possibly a move to do after the introduction *)
+
+type intro_name_flag =
+ | IntroAvoid of identifier list
+ | IntroBasedOn of identifier * identifier list
+ | IntroMustBe of identifier
+
+let find_name decl gl = function
+ | IntroAvoid idl ->
+ let id = fresh_id idl (default_id gl decl) gl in id
+ | IntroBasedOn (id,idl) -> fresh_id idl id gl
+ | IntroMustBe id ->
+ let id' = fresh_id [] id gl in
+ if id' <> id then error ((string_of_id id)^" is already used");
+ id'
+
+let build_intro_tac id = function
+ | None -> introduction id
+ | Some dest -> tclTHEN (introduction id) (move_hyp true id dest)
+
+let rec intro_gen name_flag move_flag force_flag gl =
+ match kind_of_term (pf_concl gl) with
+ | Prod (name,t,_) ->
+ build_intro_tac (find_name (name,None,t) gl name_flag) move_flag gl
+ | LetIn (name,b,t,_) ->
+ build_intro_tac (find_name (name,Some b,t) gl name_flag) move_flag gl
+ | _ ->
+ if not force_flag then raise (RefinerError IntroNeedsProduct);
+ try
+ tclTHEN
+ (reduce (Red true) onConcl)
+ (intro_gen name_flag move_flag force_flag) gl
+ with Redelimination ->
+ errorlabstrm "Intro" (str "No product even after head-reduction")
+
+let intro_mustbe_force id = intro_gen (IntroMustBe id) None true
+let intro_using id = intro_gen (IntroBasedOn (id,[])) None false
+let intro_force force_flag = intro_gen (IntroAvoid []) None force_flag
+let intro = intro_force false
+let introf = intro_force true
+
+let introf_move_name destopt = intro_gen (IntroAvoid []) destopt true
+
+(* For backwards compatibility *)
+let central_intro = intro_gen
+
+(**** Multiple introduction tactics ****)
+
+let rec intros_using = function
+ [] -> tclIDTAC
+ | str::l -> tclTHEN (intro_using str) (intros_using l)
+
+let intros = tclREPEAT (intro_force false)
+
+let intro_erasing id = tclTHEN (thin [id]) (intro_using id)
+
+let intros_replacing ids gls =
+ let rec introrec = function
+ | [] -> tclIDTAC
+ | id::tl ->
+ (tclTHEN (tclORELSE (intro_replacing id)
+ (tclORELSE (intro_erasing id) (* ?? *)
+ (intro_using id)))
+ (introrec tl))
+ in
+ introrec ids gls
+
+(* User-level introduction tactics *)
+
+let intro_move idopt idopt' = match idopt with
+ | None -> intro_gen (IntroAvoid []) idopt' true
+ | Some id -> intro_gen (IntroMustBe id) idopt' true
+
+let pf_lookup_hypothesis_as_renamed env ccl = function
+ | AnonHyp n -> pf_lookup_index_as_renamed env ccl n
+ | NamedHyp id -> pf_lookup_name_as_renamed env ccl id
+
+let pf_lookup_hypothesis_as_renamed_gen red h gl =
+ let env = pf_env gl in
+ let rec aux ccl =
+ match pf_lookup_hypothesis_as_renamed env ccl h with
+ | None when red ->
+ aux (reduction_of_redexp (Red true) env (project gl) ccl)
+ | x -> x
+ in
+ try aux (pf_concl gl)
+ with Redelimination -> None
+
+let is_quantified_hypothesis id g =
+ match pf_lookup_hypothesis_as_renamed_gen true (NamedHyp id) g with
+ | Some _ -> true
+ | None -> false
+
+let msg_quantified_hypothesis = function
+ | NamedHyp id ->
+ str "hypothesis " ++ pr_id id
+ | AnonHyp n ->
+ int n ++ str (match n with 1 -> "st" | 2 -> "nd" | _ -> "th") ++
+ str " non dependent hypothesis"
+
+let depth_of_quantified_hypothesis red h gl =
+ match pf_lookup_hypothesis_as_renamed_gen red h gl with
+ | Some depth -> depth
+ | None ->
+ errorlabstrm "lookup_quantified_hypothesis"
+ (str "No " ++ msg_quantified_hypothesis h ++
+ str " in current goal" ++
+ if red then str " even after head-reduction" else mt ())
+
+let intros_until_gen red h g =
+ tclDO (depth_of_quantified_hypothesis red h g) intro g
+
+let intros_until_id id = intros_until_gen true (NamedHyp id)
+let intros_until_n_gen red n = intros_until_gen red (AnonHyp n)
+
+let intros_until = intros_until_gen true
+let intros_until_n = intros_until_n_gen true
+let intros_until_n_wored = intros_until_n_gen false
+
+let try_intros_until tac = function
+ | NamedHyp id -> tclTHEN (tclTRY (intros_until_id id)) (tac id)
+ | AnonHyp n -> tclTHEN (intros_until_n n) (onLastHyp tac)
+
+let rec intros_move = function
+ | [] -> tclIDTAC
+ | (hyp,destopt) :: rest ->
+ tclTHEN (intro_gen (IntroMustBe hyp) destopt false)
+ (intros_move rest)
+
+let dependent_in_decl a (_,c,t) =
+ match c with
+ | None -> dependent a t
+ | Some body -> dependent a body || dependent a t
+
+let move_to_rhyp rhyp gl =
+ let rec get_lhyp lastfixed depdecls = function
+ | [] ->
+ (match rhyp with
+ | None -> lastfixed
+ | Some h -> anomaly ("Hypothesis should occur: "^ (string_of_id h)))
+ | (hyp,c,typ) as ht :: rest ->
+ if Some hyp = rhyp then
+ lastfixed
+ else if List.exists (occur_var_in_decl (pf_env gl) hyp) depdecls then
+ get_lhyp lastfixed (ht::depdecls) rest
+ else
+ get_lhyp (Some hyp) depdecls rest
+ in
+ let sign = pf_hyps gl in
+ let (hyp,c,typ as decl) = List.hd sign in
+ match get_lhyp None [decl] (List.tl sign) with
+ | None -> tclIDTAC gl
+ | Some hypto -> move_hyp true hyp hypto gl
+
+let rec intros_rmove = function
+ | [] -> tclIDTAC
+ | (hyp,destopt) :: rest ->
+ tclTHENLIST [ introduction hyp;
+ move_to_rhyp destopt;
+ intros_rmove rest ]
+
+(****************************************************)
+(* Resolution tactics *)
+(****************************************************)
+
+(* Refinement tactic: unification with the head of the head normal form
+ * of the type of a term. *)
+
+let apply_type hdcty argl gl =
+ refine (applist (mkCast (mkMeta (new_meta()),hdcty),argl)) gl
+
+let apply_term hdc argl gl =
+ refine (applist (hdc,argl)) gl
+
+let bring_hyps hyps =
+ if hyps = [] then Refiner.tclIDTAC
+ else
+ (fun gl ->
+ let newcl = List.fold_right mkNamedProd_or_LetIn hyps (pf_concl gl) in
+ let f = mkCast (mkMeta (new_meta()),newcl) in
+ refine_no_check (mkApp (f, instance_from_named_context hyps)) gl)
+
+(* Resolution with missing arguments *)
+
+let apply_with_bindings (c,lbind) gl =
+ let apply =
+ match kind_of_term c with
+ | Lambda _ -> res_pf_cast
+ | _ -> res_pf
+ in
+ let (wc,kONT) = startWalk gl in
+ (* The actual type of the theorem. It will be matched against the
+ goal. If this fails, then the head constant will be unfolded step by
+ step. *)
+ let thm_ty0 = nf_betaiota (w_type_of wc c) in
+ let rec try_apply thm_ty =
+ try
+ let n = nb_prod thm_ty - nb_prod (pf_concl gl) in
+ if n<0 then error "Apply: theorem has not enough premisses.";
+ let clause = make_clenv_binding_apply wc n (c,thm_ty) lbind in
+ apply kONT clause gl
+ with (RefinerError _|UserError _|Failure _) as exn ->
+ let red_thm =
+ try red_product (w_env wc) (w_Underlying wc) thm_ty
+ with (Redelimination | UserError _) -> raise exn in
+ try_apply red_thm in
+ try try_apply thm_ty0
+ with (RefinerError _|UserError _|Failure _) ->
+ (* Last chance: if the head is a variable, apply may try
+ second order unification *)
+ let clause = make_clenv_binding_apply wc (-1) (c,thm_ty0) lbind in
+ apply kONT clause gl
+
+let apply c = apply_with_bindings (c,NoBindings)
+
+let apply_list = function
+ | c::l -> apply_with_bindings (c,ImplicitBindings l)
+ | _ -> assert false
+
+(* Resolution with no reduction on the type *)
+
+let apply_without_reduce c gl =
+ let (wc,kONT) = startWalk gl in
+ let clause = mk_clenv_type_of wc c in
+ res_pf kONT clause gl
+
+(* A useful resolution tactic which, if c:A->B, transforms |- C into
+ |- B -> C and |- A
+
+ -------------------
+ Gamma |- c : A -> B Gamma |- ?2 : A
+ ----------------------------------------
+ Gamma |- B Gamma |- ?1 : B -> C
+ -----------------------------------------------------
+ Gamma |- ? : C
+
+ Ltac lapply c :=
+ let ty := check c in
+ match eval hnf in ty with
+ ?A -> ?B => cut B; [ idtac | apply c ]
+ end.
+*)
+
+let cut_and_apply c gl =
+ let goal_constr = pf_concl gl in
+ match kind_of_term (pf_hnf_constr gl (pf_type_of gl c)) with
+ | Prod (_,c1,c2) when not (dependent (mkRel 1) c2) ->
+ tclTHENLAST
+ (apply_type (mkProd (Anonymous,c2,goal_constr)) [mkMeta(new_meta())])
+ (apply_term c [mkMeta (new_meta())]) gl
+ | _ -> error "Imp_elim needs a non-dependent product"
+
+(**************************)
+(* Cut tactics *)
+(**************************)
+
+let assert_tac first na c gl =
+ match kind_of_term (hnf_type_of gl c) with
+ | Sort s ->
+ let id = match na with
+ | Anonymous ->
+ let d = match s with Prop _ -> "H" | Type _ -> "X" in
+ fresh_id [] (id_of_string d) gl
+ | Name id -> id
+ in
+ (if first then internal_cut else internal_cut_rev) id c gl
+ | _ -> error "Not a proposition or a type"
+
+let true_cut = assert_tac true
+
+let cut c gl =
+ match kind_of_term (hnf_type_of gl c) with
+ | Sort _ ->
+ let id=next_name_away_with_default "H" Anonymous (pf_ids_of_hyps gl) in
+ let t = mkProd (Anonymous, c, pf_concl gl) in
+ tclTHENFIRST
+ (internal_cut_rev id c)
+ (tclTHEN (apply_type t [mkVar id]) (thin [id]))
+ gl
+ | _ -> error "Not a proposition or a type"
+
+let cut_intro t = tclTHENFIRST (cut t) intro
+
+let cut_replacing id t =
+ tclTHENFIRST
+ (cut t)
+ (tclORELSE
+ (intro_replacing id)
+ (tclORELSE (intro_erasing id)
+ (intro_using id)))
+
+let cut_in_parallel l =
+ let rec prec = function
+ | [] -> tclIDTAC
+ | h::t -> tclTHENFIRST (cut h) (prec t)
+ in
+ prec (List.rev l)
+
+(**************************)
+(* Generalize tactics *)
+(**************************)
+
+let generalize_goal gl c cl =
+ let t = pf_type_of gl c in
+ match kind_of_term c with
+ | Var id ->
+ (* The choice of remembering or not a non dependent name has an impact
+ on the future Intro naming strategy! *)
+ (* if dependent c cl then mkNamedProd id t cl
+ else mkProd (Anonymous,t,cl) *)
+ mkNamedProd id t cl
+ | _ ->
+ let cl' = subst_term c cl in
+ if noccurn 1 cl' then
+ mkProd (Anonymous,t,cl)
+ (* On ne se casse pas la tete : on prend pour nom de variable
+ la premiere lettre du type, meme si "ci" est une
+ constante et qu'on pourrait prendre directement son nom *)
+ else
+ prod_name (Global.env()) (Anonymous, t, cl')
+
+let generalize_dep c gl =
+ let env = pf_env gl in
+ let sign = pf_hyps gl in
+ let init_ids = ids_of_named_context (Global.named_context()) in
+ let rec seek d toquant =
+ if List.exists (fun (id,_,_) -> occur_var_in_decl env id d) toquant
+ or dependent_in_decl c d then
+ d::toquant
+ else
+ toquant in
+ let to_quantify = Sign.fold_named_context seek sign ~init:[] in
+ let to_quantify_rev = List.rev to_quantify in
+ let qhyps = List.map (fun (id,_,_) -> id) to_quantify_rev in
+ let tothin = List.filter (fun id -> not (List.mem id init_ids)) qhyps in
+ let tothin' =
+ match kind_of_term c with
+ | Var id when mem_named_context id sign & not (List.mem id init_ids)
+ -> id::tothin
+ | _ -> tothin
+ in
+ let cl' = it_mkNamedProd_or_LetIn (pf_concl gl) to_quantify in
+ let cl'' = generalize_goal gl c cl' in
+ let args = Array.to_list (instance_from_named_context to_quantify_rev) in
+ tclTHEN
+ (apply_type cl'' (c::args))
+ (thin (List.rev tothin'))
+ gl
+
+let generalize lconstr gl =
+ let newcl = List.fold_right (generalize_goal gl) lconstr (pf_concl gl) in
+ apply_type newcl lconstr gl
+
+(* Faudra-t-il une version avec plusieurs args de generalize_dep ?
+Cela peut-être troublant de faire "Generalize Dependent H n" dans
+"n:nat; H:n=n |- P(n)" et d'échouer parce que H a disparu après la
+généralisation dépendante par n.
+
+let quantify lconstr =
+ List.fold_right
+ (fun com tac -> tclTHEN tac (tactic_com generalize_dep c))
+ lconstr
+ tclIDTAC
+*)
+
+(* A dependent cut rule à la sequent calculus
+ ------------------------------------------
+ Sera simplifiable le jour où il y aura un let in primitif dans constr
+
+ [letin_tac b na c (occ_hyp,occ_ccl) gl] transforms
+ [...x1:T1(c),...,x2:T2(c),... |- G(c)] into
+ [...x:T;x1:T1(x),...,x2:T2(x),... |- G(x)] if [b] is false or
+ [...x:=c:T;x1:T1(x),...,x2:T2(x),... |- G(x)] if [b] is true
+
+ [occ_hyp,occ_ccl] tells which occurrences of [c] have to be substituted;
+ if [occ_hyp = []] and [occ_ccl = None] then [c] is substituted
+ wherever it occurs, otherwise [c] is substituted only in hyps
+ present in [occ_hyps] at the specified occurrences (everywhere if
+ the list of occurrences is empty), and in the goal at the specified
+ occurrences if [occ_goal] is not [None];
+
+ if name = Anonymous, the name is build from the first letter of the type;
+
+ The tactic first quantify the goal over x1, x2,... then substitute then
+ re-intro x1, x2,... at their initial place ([marks] is internally
+ used to remember the place of x1, x2, ...: it is the list of hypotheses on
+ the left of each x1, ...).
+*)
+
+
+
+let occurrences_of_hyp id cls =
+ let rec hyp_occ = function
+ [] -> None
+ | (id',occs,hl)::_ when id=id' -> Some occs
+ | _::l -> hyp_occ l in
+ match cls.onhyps with
+ None -> Some []
+ | Some l -> hyp_occ l
+
+let occurrences_of_goal cls =
+ if cls.onconcl then Some cls.concl_occs else None
+
+let everywhere cls = (cls=allClauses)
+
+(*
+(* Implementation with generalisation then re-intro: introduces noise *)
+(* in proofs *)
+
+let letin_abstract id c occs gl =
+ let env = pf_env gl in
+ let compute_dependency _ (hyp,_,_ as d) ctxt =
+ let d' =
+ try
+ match occurrences_of_hyp hyp occs with
+ | None -> raise Not_found
+ | Some occ ->
+ let newdecl = subst_term_occ_decl occ c d in
+ if d = newdecl then
+ if not (everywhere occs)
+ then raise (RefinerError (DoesNotOccurIn (c,hyp)))
+ else raise Not_found
+ else
+ (subst1_decl (mkVar id) newdecl, true)
+ with Not_found ->
+ (d,List.exists
+ (fun ((id,_,_),dep) -> dep && occur_var_in_decl env id d) ctxt)
+ in d'::ctxt
+ in
+ let ctxt' = fold_named_context compute_dependency env ~init:[] in
+ let compute_marks ((depdecls,marks as accu),lhyp) ((hyp,_,_) as d,b) =
+ if b then ((d::depdecls,(hyp,lhyp)::marks), lhyp)
+ else (accu, Some hyp) in
+ let (depdecls,marks),_ = List.fold_left compute_marks (([],[]),None) ctxt' in
+ let ccl = match occurrences_of_goal occs with
+ | None -> pf_concl gl
+ | Some occ -> subst1 (mkVar id) (subst_term_occ occ c (pf_concl gl))
+ in
+ (depdecls,marks,ccl)
+
+let letin_tac with_eq name c occs gl =
+ let x = id_of_name_using_hdchar (Global.env()) (pf_type_of gl c) name in
+ let id =
+ if name = Anonymous then fresh_id [] x gl else
+ if not (mem_named_context x (pf_hyps gl)) then x else
+ error ("The variable "^(string_of_id x)^" is already declared") in
+ let (depdecls,marks,ccl)= letin_abstract id c occs gl in
+ let t = pf_type_of gl c in
+ let tmpcl = List.fold_right mkNamedProd_or_LetIn depdecls ccl in
+ let args = Array.to_list (instance_from_named_context depdecls) in
+ let newcl = mkNamedLetIn id c t tmpcl in
+ let lastlhyp = if marks=[] then None else snd (List.hd marks) in
+ tclTHENLIST
+ [ apply_type newcl args;
+ thin (List.map (fun (id,_,_) -> id) depdecls);
+ intro_gen (IntroMustBe id) lastlhyp false;
+ if with_eq then tclIDTAC else thin_body [id];
+ intros_move marks ] gl
+*)
+
+(* Implementation without generalisation: abbrev will be lost in hyps in *)
+(* in the extracted proof *)
+
+let letin_abstract id c occs gl =
+ let env = pf_env gl in
+ let compute_dependency _ (hyp,_,_ as d) depdecls =
+ match occurrences_of_hyp hyp occs with
+ | None -> depdecls
+ | Some occ ->
+ let newdecl = subst_term_occ_decl occ c d in
+ if d = newdecl then
+ if not (everywhere occs)
+ then raise (RefinerError (DoesNotOccurIn (c,hyp)))
+ else depdecls
+ else
+ (subst1_decl (mkVar id) newdecl)::depdecls in
+ let depdecls = fold_named_context compute_dependency env ~init:[] in
+ let ccl = match occurrences_of_goal occs with
+ | None -> pf_concl gl
+ | Some occ -> subst1 (mkVar id) (subst_term_occ occ c (pf_concl gl)) in
+ let lastlhyp = if depdecls = [] then None else Some(pi1(list_last depdecls)) in
+ (depdecls,lastlhyp,ccl)
+
+let letin_tac with_eq name c occs gl =
+ let id =
+ let x = id_of_name_using_hdchar (Global.env()) (pf_type_of gl c) name in
+ if name = Anonymous then fresh_id [] x gl else
+ if not (mem_named_context x (pf_hyps gl)) then x else
+ error ("The variable "^(string_of_id x)^" is already declared") in
+ let (depdecls,lastlhyp,ccl)= letin_abstract id c occs gl in
+ let t = pf_type_of gl c in
+ let newcl = mkNamedLetIn id c t ccl in
+ tclTHENLIST
+ [ convert_concl_no_check newcl;
+ intro_gen (IntroMustBe id) lastlhyp true;
+ if with_eq then tclIDTAC else thin_body [id];
+ tclMAP convert_hyp_no_check depdecls ] gl
+
+let check_hypotheses_occurrences_list env (_,occl) =
+ let rec check acc = function
+ | (hyp,_) :: rest ->
+ if List.mem hyp acc then
+ error ("Hypothesis "^(string_of_id hyp)^" occurs twice");
+ if not (mem_named_context hyp (named_context env)) then
+ error ("No such hypothesis: " ^ (string_of_id hyp));
+ check (hyp::acc) rest
+ | [] -> ()
+ in check [] occl
+
+let nowhere = {onhyps=Some[]; onconcl=false; concl_occs=[]}
+
+(* Tactic Assert (b=false) and Pose (b=true):
+ the behaviour of Pose is corrected by the translator.
+ not that of Assert *)
+let forward b na c =
+ let wh = if !Options.v7 && b then onConcl else nowhere in
+ letin_tac b na c wh
+
+(********************************************************************)
+(* Exact tactics *)
+(********************************************************************)
+
+let exact_check c gl =
+ let concl = (pf_concl gl) in
+ let ct = pf_type_of gl c in
+ if pf_conv_x_leq gl ct concl then
+ refine_no_check c gl
+ else
+ error "Not an exact proof"
+
+let exact_no_check = refine_no_check
+
+let exact_proof c gl =
+ (* on experimente la synthese d'ise dans exact *)
+ let c = Constrintern.interp_casted_constr (project gl) (pf_env gl) c (pf_concl gl)
+ in refine_no_check c gl
+
+let (assumption : tactic) = fun gl ->
+ let concl = pf_concl gl in
+ let hyps = pf_hyps gl in
+ let rec arec only_eq = function
+ | [] ->
+ if only_eq then arec false hyps else error "No such assumption"
+ | (id,c,t)::rest ->
+ if (only_eq & eq_constr t concl)
+ or (not only_eq & pf_conv_x_leq gl t concl)
+ then refine_no_check (mkVar id) gl
+ else arec only_eq rest
+ in
+ arec true hyps
+
+(*****************************************************************)
+(* Modification of a local context *)
+(*****************************************************************)
+
+(* This tactic enables the user to remove hypotheses from the signature.
+ * Some care is taken to prevent him from removing variables that are
+ * subsequently used in other hypotheses or in the conclusion of the
+ * goal. *)
+
+let clear ids gl = (* avant seul dyn_clear n'echouait pas en [] *)
+ if ids=[] then tclIDTAC gl else with_check (thin ids) gl
+
+let clear_body = thin_body
+
+(* Takes a list of booleans, and introduces all the variables
+ * quantified in the goal which are associated with a value
+ * true in the boolean list. *)
+
+let rec intros_clearing = function
+ | [] -> tclIDTAC
+ | (false::tl) -> tclTHEN intro (intros_clearing tl)
+ | (true::tl) ->
+ tclTHENLIST
+ [ intro; onLastHyp (fun id -> clear [id]); intros_clearing tl]
+
+(* Adding new hypotheses *)
+
+let new_hyp mopt (c,lbind) g =
+ let (wc,kONT) = startWalk g in
+ let clause = make_clenv_binding wc (c,w_type_of wc c) lbind in
+ let (thd,tstack) = whd_stack (clenv_instance_template clause) in
+ let nargs = List.length tstack in
+ let cut_pf =
+ applist(thd,
+ match mopt with
+ | Some m -> if m < nargs then list_firstn m tstack else tstack
+ | None -> tstack)
+ in
+ (tclTHENLAST (tclTHEN (kONT clause.hook)
+ (cut (pf_type_of g cut_pf)))
+ ((tclORELSE (apply cut_pf) (exact_no_check cut_pf)))) g
+
+(************************)
+(* Introduction tactics *)
+(************************)
+
+let constructor_tac boundopt i lbind gl =
+ let cl = pf_concl gl in
+ let (mind,redcl) = pf_reduce_to_quantified_ind gl cl in
+ let nconstr =
+ Array.length (snd (Global.lookup_inductive mind)).mind_consnames
+ and sigma = project gl in
+ if i=0 then error "The constructors are numbered starting from 1";
+ if i > nconstr then error "Not enough constructors";
+ begin match boundopt with
+ | Some expctdnum ->
+ if expctdnum <> nconstr then
+ error "Not the expected number of constructors"
+ | None -> ()
+ end;
+ let cons = mkConstruct (ith_constructor_of_inductive mind i) in
+ let apply_tac = apply_with_bindings (cons,lbind) in
+ (tclTHENLIST [convert_concl_no_check redcl; intros; apply_tac]) gl
+
+let one_constructor i = constructor_tac None i
+
+(* Try to apply the constructor of the inductive definition followed by
+ a tactic t given as an argument.
+ Should be generalize in Constructor (Fun c : I -> tactic)
+ *)
+
+let any_constructor tacopt gl =
+ let t = match tacopt with None -> tclIDTAC | Some t -> t in
+ let mind = fst (pf_reduce_to_quantified_ind gl (pf_concl gl)) in
+ let nconstr =
+ Array.length (snd (Global.lookup_inductive mind)).mind_consnames in
+ if nconstr = 0 then error "The type has no constructors";
+ tclFIRST (List.map (fun i -> tclTHEN (one_constructor i NoBindings) t)
+ (interval 1 nconstr)) gl
+
+let left = constructor_tac (Some 2) 1
+let simplest_left = left NoBindings
+
+let right = constructor_tac (Some 2) 2
+let simplest_right = right NoBindings
+
+let split = constructor_tac (Some 1) 1
+let simplest_split = split NoBindings
+
+(********************************************)
+(* Elimination tactics *)
+(********************************************)
+
+
+(* kONT : ??
+ * wc : ??
+ * elimclause : ??
+ * inclause : ??
+ * gl : the current goal
+*)
+
+let last_arg c = match kind_of_term c with
+ | App (f,cl) -> array_last cl
+ | _ -> anomaly "last_arg"
+
+let elimination_clause_scheme kONT elimclause indclause allow_K gl =
+ let indmv =
+ (match kind_of_term (last_arg (clenv_template elimclause).rebus) with
+ | Meta mv -> mv
+ | _ -> errorlabstrm "elimination_clause"
+ (str "The type of elimination clause is not well-formed"))
+ in
+ let elimclause' = clenv_fchain indmv elimclause indclause in
+ elim_res_pf kONT elimclause' allow_K gl
+
+(* cast added otherwise tactics Case (n1,n2) generates (?f x y) and
+ * refine fails *)
+
+let type_clenv_binding wc (c,t) lbind =
+ clenv_instance_template_type (make_clenv_binding wc (c,t) lbind)
+
+(*
+ * Elimination tactic with bindings and using an arbitrary
+ * elimination constant called elimc. This constant should end
+ * with a clause (x:I)(P .. ), where P is a bound variable.
+ * The term c is of type t, which is a product ending with a type
+ * matching I, lbindc are the expected terms for c arguments
+ *)
+
+let general_elim (c,lbindc) (elimc,lbindelimc) ?(allow_K=true) gl =
+ let (wc,kONT) = startWalk gl in
+ let ct = pf_type_of gl c in
+ let t = try snd (pf_reduce_to_quantified_ind gl ct) with UserError _ -> ct in
+ let indclause = make_clenv_binding wc (c,t) lbindc in
+ let elimt = w_type_of wc elimc in
+ let elimclause = make_clenv_binding wc (elimc,elimt) lbindelimc in
+ elimination_clause_scheme kONT elimclause indclause allow_K gl
+
+(* Elimination tactic with bindings but using the default elimination
+ * constant associated with the type. *)
+
+let find_eliminator c gl =
+ let env = pf_env gl in
+ let (ind,t) = reduce_to_quantified_ind env (project gl) (pf_type_of gl c) in
+ let s = elimination_sort_of_goal gl in
+ Indrec.lookup_eliminator ind s
+(* with Not_found ->
+ let dir, base = repr_path (path_of_inductive env ind) in
+ let id = Indrec.make_elimination_ident base s in
+ errorlabstrm "default_elim"
+ (str "Cannot find the elimination combinator :" ++
+ pr_id id ++ spc () ++
+ str "The elimination of the inductive definition :" ++
+ pr_id base ++ spc () ++ str "on sort " ++
+ spc () ++ print_sort (new_sort_in_family s) ++
+ str " is probably not allowed")
+(* lookup_eliminator prints the message *) *)
+let default_elim (c,lbindc) gl =
+ general_elim (c,lbindc) (find_eliminator c gl,NoBindings) gl
+
+let elim_in_context (c,_ as cx) elim gl =
+ match elim with
+ | Some (elimc,lbindelimc) -> general_elim cx (elimc,lbindelimc) gl
+ | None -> general_elim cx (find_eliminator c gl,NoBindings) gl
+
+let elim (c,lbindc as cx) elim =
+ match kind_of_term c with
+ | Var id when lbindc = NoBindings ->
+ tclTHEN (tclTRY (intros_until_id id)) (elim_in_context cx elim)
+ | _ -> elim_in_context cx elim
+
+(* The simplest elimination tactic, with no substitutions at all. *)
+
+let simplest_elim c = default_elim (c,NoBindings)
+
+(* Elimination in hypothesis *)
+
+let elimination_in_clause_scheme kONT id elimclause indclause =
+ let (hypmv,indmv) =
+ match clenv_independent elimclause with
+ [k1;k2] -> (k1,k2)
+ | _ -> errorlabstrm "elimination_clause"
+ (str "The type of elimination clause is not well-formed") in
+ let elimclause' = clenv_fchain indmv elimclause indclause in
+ let hyp = mkVar id in
+ let hyp_typ = clenv_type_of elimclause' hyp in
+ let hypclause =
+ mk_clenv_from_n elimclause'.hook (Some 0) (hyp, hyp_typ) in
+ let elimclause'' = clenv_fchain hypmv elimclause' hypclause in
+ let new_hyp_prf = clenv_instance_template elimclause'' in
+ let new_hyp_typ = clenv_instance_template_type elimclause'' in
+ if eq_constr hyp_typ new_hyp_typ then
+ errorlabstrm "general_rewrite_in"
+ (str "Nothing to rewrite in " ++ pr_id id);
+ tclTHEN
+ (kONT elimclause''.hook)
+ (tclTHENS
+ (cut new_hyp_typ)
+ [ (* Try to insert the new hyp at the same place *)
+ tclORELSE (intro_replacing id)
+ (tclTHEN (clear [id]) (introduction id));
+ refine_no_check new_hyp_prf])
+
+let general_elim_in id (c,lbindc) (elimc,lbindelimc) gl =
+ let (wc,kONT) = startWalk gl in
+ let ct = pf_type_of gl c in
+ let t = try snd (pf_reduce_to_quantified_ind gl ct) with UserError _ -> ct in
+ let indclause = make_clenv_binding wc (c,t) lbindc in
+ let elimt = w_type_of wc elimc in
+ let elimclause = make_clenv_binding wc (elimc,elimt) lbindelimc in
+ elimination_in_clause_scheme kONT id elimclause indclause gl
+
+(* Case analysis tactics *)
+
+let general_case_analysis_in_context (c,lbindc) gl =
+ let env = pf_env gl in
+ let (mind,_) = pf_reduce_to_quantified_ind gl (pf_type_of gl c) in
+ let sigma = project gl in
+ let sort = elimination_sort_of_goal gl in
+ let case = if occur_term c (pf_concl gl) then Indrec.make_case_dep
+ else Indrec.make_case_gen in
+ let elim = case env sigma mind sort in
+ general_elim (c,lbindc) (elim,NoBindings) gl
+
+let general_case_analysis (c,lbindc as cx) =
+ match kind_of_term c with
+ | Var id when lbindc = NoBindings ->
+ tclTHEN (tclTRY (intros_until_id id))
+ (general_case_analysis_in_context cx)
+ | _ ->
+ general_case_analysis_in_context cx
+
+let simplest_case c = general_case_analysis (c,NoBindings)
+
+(*****************************)
+(* Decomposing introductions *)
+(*****************************)
+
+let clear_last = tclLAST_HYP (fun c -> (clear [destVar c]))
+let case_last = tclLAST_HYP simplest_case
+
+let rec intro_pattern destopt = function
+ | IntroWildcard ->
+ tclTHEN intro clear_last
+ | IntroIdentifier id ->
+ intro_gen (IntroMustBe id) destopt true
+ | IntroOrAndPattern l ->
+ tclTHEN introf
+ (tclTHENS
+ (tclTHEN case_last clear_last)
+ (List.map (intros_pattern destopt) l))
+
+and intros_pattern destopt l = tclMAP (intro_pattern destopt) l
+
+let intro_patterns = function
+ | [] -> tclREPEAT intro
+ | l -> intros_pattern None l
+
+(*
+ * A "natural" induction tactic
+ *
+ - [H0:T0, ..., Hi:Ti, hyp0:P->I(args), Hi+1:Ti+1, ..., Hn:Tn |-G] is the goal
+ - [hyp0] is the induction hypothesis
+ - we extract from [args] the variables which are not rigid parameters
+ of the inductive type, this is [indvars] (other terms are forgotten);
+ [indhyps] are the ones which actually are declared in context
+ (done in [find_atomic_param_of_ind])
+ - we look for all hyps depending of [hyp0] or one of [indvars]:
+ this is [dephyps] of types [deptyps] respectively
+ - [statuslist] tells for each hyps in [dephyps] after which other hyp
+ fixed in the context they must be moved (when induction is done)
+ - [hyp0succ] is the name of the hyp fixed in the context after which to
+ move the subterms of [hyp0succ] in the i-th branch where it is supposed
+ to be the i-th constructor of the inductive type.
+
+ Strategy: (cf in [induction_from_context])
+ - requantify and clear all [dephyps]
+ - apply induction on [hyp0]
+ - clear [indhyps] and [hyp0]
+ - in the i-th subgoal, intro the arguments of the i-th constructor
+ of the inductive type after [hyp0succ] (done in
+ [induct_discharge]) let the induction hypotheses on top of the
+ hyps because they may depend on variables between [hyp0] and the
+ top. A counterpart is that the dep hyps programmed to be intro-ed
+ on top must now be intro-ed after the induction hypotheses
+ - move each of [dephyps] at the right place following the
+ [statuslist]
+
+ *)
+
+let rec str_intro_pattern = function
+ | IntroOrAndPattern pll ->
+ "["^(String.concat "|"
+ (List.map
+ (fun pl -> String.concat " " (List.map str_intro_pattern pl)) pll))
+ ^"]"
+ | IntroWildcard -> "_"
+ | IntroIdentifier id -> string_of_id id
+
+let check_unused_names names =
+ if names <> [] & Options.is_verbose () then
+ let s = if List.tl names = [] then " " else "s " in
+ let names = String.concat " " (List.map str_intro_pattern names) in
+ warning ("Unused introduction pattern"^s^": "^names)
+
+let rec first_name_buggy = function
+ | IntroOrAndPattern [] -> None
+ | IntroOrAndPattern ([]::l) -> first_name_buggy (IntroOrAndPattern l)
+ | IntroOrAndPattern ((p::_)::_) -> first_name_buggy p
+ | IntroWildcard -> None
+ | IntroIdentifier id -> Some id
+
+type elim_arg_kind = RecArg | IndArg | OtherArg
+
+let induct_discharge statuslists destopt avoid' ((avoid7,avoid8),ra) (names,force,rnames) gl =
+ let avoid7 = avoid7 @ avoid' in
+ let avoid8 = avoid8 @ avoid' in
+ let (lstatus,rstatus) = statuslists in
+ let tophyp = ref None in
+ let rec peel_tac ra names gl = match ra with
+ | (RecArg,(recvarname7,recvarname8)) ::
+ (IndArg,(hyprecname7,hyprecname8)) :: ra' ->
+ let recpat,hyprec,names = match names with
+ | [] ->
+ let idrec7 = (fresh_id avoid7 recvarname7 gl) in
+ let idrec8 = (fresh_id avoid8 recvarname8 gl) in
+ let idhyp7 = (fresh_id avoid7 hyprecname7 gl) in
+ let idhyp8 = (fresh_id avoid8 hyprecname8 gl) in
+ if Options.do_translate() &
+ (idrec7 <> idrec8 or idhyp7 <> idhyp8)
+ then force := true;
+ let idrec = if !Options.v7 then idrec7 else idrec8 in
+ let idhyp = if !Options.v7 then idhyp7 else idhyp8 in
+ (IntroIdentifier idrec, IntroIdentifier idhyp, [])
+ | [IntroIdentifier id as pat] ->
+ let id7 = next_ident_away (add_prefix "IH" id) avoid7 in
+ let id8 = next_ident_away (add_prefix "IH" id) avoid8 in
+ if Options.do_translate() & id7 <> id8 then force := true;
+ let id = if !Options.v7 then id7 else id8 in
+ (pat, IntroIdentifier id, [])
+ | [pat] ->
+ let idhyp7 = (fresh_id avoid7 hyprecname7 gl) in
+ let idhyp8 = (fresh_id avoid8 hyprecname8 gl) in
+ if Options.do_translate() & idhyp7 <> idhyp8 then force := true;
+ let idhyp = if !Options.v7 then idhyp7 else idhyp8 in
+ (pat, IntroIdentifier idhyp, [])
+ | pat1::pat2::names -> (pat1,pat2,names) in
+ (* This is buggy for intro-or-patterns with different first hypnames *)
+ if !tophyp=None then tophyp := first_name_buggy hyprec;
+ rnames := !rnames @ [recpat; hyprec];
+ tclTHENLIST
+ [ intros_pattern destopt [recpat];
+ intros_pattern None [hyprec];
+ peel_tac ra' names ] gl
+ | (IndArg,(hyprecname7,hyprecname8)) :: ra' ->
+ (* Rem: does not happen in Coq schemes, only in user-defined schemes *)
+ let pat,names = match names with
+ | [] -> IntroIdentifier (fresh_id avoid8 hyprecname8 gl), []
+ | pat::names -> pat,names in
+ rnames := !rnames @ [pat];
+ tclTHEN (intros_pattern destopt [pat]) (peel_tac ra' names) gl
+ | (RecArg,(recvarname7,recvarname8)) :: ra' ->
+ let introtac,names = match names with
+ | [] ->
+ let id8 = fresh_id avoid8 recvarname8 gl in
+ let i =
+ if !Options.v7 then IntroAvoid avoid7 else IntroMustBe id8
+ in
+ (* For translator *)
+ let id7 = fresh_id avoid7 (default_id gl
+ (match kind_of_term (pf_concl gl) with
+ | Prod (name,t,_) -> (name,None,t)
+ | LetIn (name,b,t,_) -> (name,Some b,t)
+ | _ -> assert false)) gl in
+ if Options.do_translate() & id7 <> id8 then force := true;
+ let id = if !Options.v7 then id7 else id8 in
+ rnames := !rnames @ [IntroIdentifier id];
+ intro_gen i destopt false, []
+ | pat::names ->
+ rnames := !rnames @ [pat];
+ intros_pattern destopt [pat],names in
+ tclTHEN introtac (peel_tac ra' names) gl
+ | (OtherArg,_) :: ra' ->
+ let introtac,names = match names with
+ | [] ->
+ (* For translator *)
+ let id7 = fresh_id avoid7 (default_id gl
+ (match kind_of_term (pf_concl gl) with
+ | Prod (name,t,_) -> (name,None,t)
+ | LetIn (name,b,t,_) -> (name,Some b,t)
+ | _ -> assert false)) gl in
+ let id8 = fresh_id avoid8 (default_id gl
+ (match kind_of_term (pf_concl gl) with
+ | Prod (name,t,_) -> (name,None,t)
+ | LetIn (name,b,t,_) -> (name,Some b,t)
+ | _ -> assert false)) gl in
+ if Options.do_translate() & id7 <> id8 then force := true;
+ let id = if !Options.v7 then id7 else id8 in
+ let avoid = if !Options.v7 then avoid7 else avoid8 in
+ rnames := !rnames @ [IntroIdentifier id];
+ intro_gen (IntroAvoid avoid) destopt false, []
+ | pat::names ->
+ rnames := !rnames @ [pat];
+ intros_pattern destopt [pat],names in
+ tclTHEN introtac (peel_tac ra' names) gl
+ | [] ->
+ check_unused_names names;
+ tclIDTAC gl
+ in
+ let intros_move lstatus =
+ let newlstatus = (* if some IH has taken place at the top of hyps *)
+ List.map (function (hyp,None) -> (hyp,!tophyp) | x -> x) lstatus in
+ intros_move newlstatus
+ in
+ tclTHENLIST [ peel_tac ra names;
+ intros_rmove rstatus;
+ intros_move lstatus ] gl
+
+(* - le recalcul de indtyp à chaque itération de atomize_one est pour ne pas
+ s'embêter à regarder si un letin_tac ne fait pas des
+ substitutions aussi sur l'argument voisin *)
+
+(* Marche pas... faut prendre en compte l'occurrence précise... *)
+
+let atomize_param_of_ind (indref,nparams) hyp0 gl =
+ let tmptyp0 = pf_get_hyp_typ gl hyp0 in
+ let typ0 = pf_apply reduce_to_quantified_ref gl indref tmptyp0 in
+ let prods, indtyp = decompose_prod typ0 in
+ let argl = snd (decompose_app indtyp) in
+ let params = list_firstn nparams argl in
+ (* le gl est important pour ne pas préévaluer *)
+ let rec atomize_one i avoid gl =
+ if i<>nparams then
+ let tmptyp0 = pf_get_hyp_typ gl hyp0 in
+ (* If argl <> [], we expect typ0 not to be quantified, in order to
+ avoid bound parameters... then we call pf_reduce_to_atomic_ind *)
+ let indtyp = pf_apply reduce_to_atomic_ref gl indref tmptyp0 in
+ let argl = snd (decompose_app indtyp) in
+ let c = List.nth argl (i-1) in
+ match kind_of_term c with
+ | Var id when not (List.exists (occur_var (pf_env gl) id) avoid) ->
+ atomize_one (i-1) ((mkVar id)::avoid) gl
+ | Var id ->
+ let x = fresh_id [] id gl in
+ tclTHEN
+ (letin_tac true (Name x) (mkVar id) allClauses)
+ (atomize_one (i-1) ((mkVar x)::avoid)) gl
+ | _ ->
+ let id = id_of_name_using_hdchar (Global.env()) (pf_type_of gl c)
+ Anonymous in
+ let x = fresh_id [] id gl in
+ tclTHEN
+ (letin_tac true (Name x) c allClauses)
+ (atomize_one (i-1) ((mkVar x)::avoid)) gl
+ else
+ tclIDTAC gl
+ in
+ atomize_one (List.length argl) params gl
+
+let find_atomic_param_of_ind nparams indtyp =
+ let argl = snd (decompose_app indtyp) in
+ let argv = Array.of_list argl in
+ let params = list_firstn nparams argl in
+ let indvars = ref Idset.empty in
+ for i = nparams to (Array.length argv)-1 do
+ match kind_of_term argv.(i) with
+ | Var id
+ when not (List.exists (occur_var (Global.env()) id) params) ->
+ indvars := Idset.add id !indvars
+ | _ -> ()
+ done;
+ Idset.elements !indvars;
+
+
+ (* [cook_sign] builds the lists [indhyps] of hyps that must be
+ erased, the lists of hyps to be generalize [(hdeps,tdeps)] on the
+ goal together with the places [(lstatus,rstatus)] where to re-intro
+ them after induction. To know where to re-intro the dep hyp, we
+ remember the name of the hypothesis [lhyp] after which (if the dep
+ hyp is more recent than [hyp0]) or [rhyp] before which (if older
+ than [hyp0]) its equivalent must be moved when the induction has
+ been applied. Since computation of dependencies and [rhyp] is from
+ more ancient (on the right) to more recent hyp (on the left) but
+ the computation of [lhyp] progresses from the other way, [cook_hyp]
+ is in two passes (an alternative would have been to write an
+ higher-order algorithm). We strongly use references to reduce
+ the accumulation of arguments.
+
+ To summarize, the situation looks like this
+
+ Goal(n,x) -| H6:(Q n); x:A; H5:True; H4:(le O n); H3:(P n); H2:True; n:nat
+ Left Right
+
+ Induction hypothesis is H4 ([hyp0])
+ Variable parameters of (le O n) is the singleton list with "n" ([indvars])
+ Part of [indvars] really in context is the same ([indhyps])
+ The dependent hyps are H3 and H6 ([dephyps])
+ For H3 the memorized places are H5 ([lhyp]) and H2 ([rhyp])
+ because these names are among the hyp which are fixed through the induction
+ For H6 the neighbours are None ([lhyp]) and H5 ([rhyp])
+ For H3, because on the right of H4, we remember rhyp (here H2)
+ For H6, because on the left of H4, we remember lhyp (here None)
+ For H4, we remember lhyp (here H5)
+
+ The right neighbour is then translated into the left neighbour
+ because move_hyp tactic needs the name of the hyp _after_ which we
+ move the hyp to move.
+
+ But, say in the 2nd subgoal of the hypotheses, the goal will be
+
+ (m:nat)((P m)->(Q m)->(Goal m)) -> (P Sm)-> (Q Sm)-> (Goal Sm)
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^
+ both go where H4 was goes where goes where
+ H3 was H6 was
+
+ We have to intro and move m and the recursive hyp first, but then
+ where to move H3 ??? Only the hyp on its right is relevant, but we
+ have to translate it into the name of the hyp on the left
+
+ Note: this case where some hyp(s) in [dephyps] has(have) the same
+ left neighbour as [hyp0] is the only problematic case with right
+ neighbours. For the other cases (e.g. an hyp H1:(R n) between n and H2
+ would have posed no problem. But for uniformity, we decided to use
+ the right hyp for all hyps on the right of H4.
+
+ Others solutions are welcome *)
+
+exception Shunt of identifier option
+
+let cook_sign hyp0 indvars env =
+ (* First phase from L to R: get [indhyps], [decldep] and [statuslist]
+ for the hypotheses before (= more ancient than) hyp0 (see above) *)
+ let allindhyps = hyp0::indvars in
+ let indhyps = ref [] in
+ let decldeps = ref [] in
+ let ldeps = ref [] in
+ let rstatus = ref [] in
+ let lstatus = ref [] in
+ let before = ref true in
+ let seek_deps env (hyp,_,_ as decl) rhyp =
+ if hyp = hyp0 then begin
+ before:=false;
+ None (* fake value *)
+ end else if List.mem hyp indvars then begin
+ (* warning: hyp can still occur after induction *)
+ (* e.g. if the goal (t hyp hyp0) with other occs of hyp in t *)
+ indhyps := hyp::!indhyps;
+ rhyp
+ end else
+ if (List.exists (fun id -> occur_var_in_decl env id decl) allindhyps
+ or List.exists (fun (id,_,_) -> occur_var_in_decl env id decl)
+ !decldeps)
+ then begin
+ decldeps := decl::!decldeps;
+ if !before then
+ rstatus := (hyp,rhyp)::!rstatus
+ else
+ ldeps := hyp::!ldeps; (* status computed in 2nd phase *)
+ Some hyp end
+ else
+ Some hyp
+ in
+ let _ = fold_named_context seek_deps env ~init:None in
+ (* 2nd phase from R to L: get left hyp of [hyp0] and [lhyps] *)
+ let compute_lstatus lhyp (hyp,_,_ as d) =
+ if hyp = hyp0 then raise (Shunt lhyp);
+ if List.mem hyp !ldeps then begin
+ lstatus := (hyp,lhyp)::!lstatus;
+ lhyp
+ end else
+ if List.mem hyp !indhyps then lhyp else (Some hyp)
+ in
+ try
+ let _ = fold_named_context_reverse compute_lstatus ~init:None env in
+ anomaly "hyp0 not found"
+ with Shunt lhyp0 ->
+ let statuslists = (!lstatus,List.rev !rstatus) in
+ (statuslists, lhyp0, !indhyps, !decldeps)
+
+let induction_tac varname typ ((elimc,lbindelimc),elimt) gl =
+ let c = mkVar varname in
+ let (wc,kONT) = startWalk gl in
+ let indclause = make_clenv_binding wc (c,typ) NoBindings in
+ let elimclause =
+ make_clenv_binding wc (mkCast (elimc,elimt),elimt) lbindelimc in
+ elimination_clause_scheme kONT elimclause indclause true gl
+
+let make_up_names7 n ind (old_style,cname) =
+ if old_style (* = V6.3 version of Induction on hypotheses *)
+ then
+ let recvarname =
+ if n=1 then
+ cname
+ else (* To force renumbering if there is only one *)
+ make_ident (string_of_id cname ) (Some 1) in
+ recvarname, add_prefix "Hrec" recvarname, []
+ else
+ let is_hyp = atompart_of_id cname = "H" in
+ let hyprecname =
+ add_prefix "IH" (if is_hyp then Nametab.id_of_global ind else cname) in
+ let avoid =
+ if n=1 (* Only one recursive argument *)
+ or
+ (* Rem: no recursive argument (especially if Destruct) *)
+ n=0 (* & atompart_of_id cname <> "H" (* for 7.1 compatibility *)*)
+ then []
+ else
+ (* Forbid to use cname, cname0, hyprecname and hyprecname0 *)
+ (* in order to get names such as f1, f2, ... *)
+ let avoid =
+ (make_ident (string_of_id cname) (Some 0)) ::(*here for 7.1 cmpat*)
+ (make_ident (string_of_id hyprecname) None) ::
+ (make_ident (string_of_id hyprecname) (Some 0)) :: [] in
+ if atompart_of_id cname <> "H" then
+ (make_ident (string_of_id cname) None) :: avoid
+ else avoid in
+ cname, hyprecname, avoid
+
+let make_base n id =
+ if n=0 or n=1 then id
+ else
+ (* This extends the name to accept new digits if it already ends with *)
+ (* digits *)
+ id_of_string (atompart_of_id (make_ident (string_of_id id) (Some 0)))
+
+let make_up_names8 n ind (_,cname) =
+ let is_hyp = atompart_of_id cname = "H" in
+ let base = string_of_id (make_base n cname) in
+ let hyprecname =
+ add_prefix "IH"
+ (make_base n (if is_hyp then Nametab.id_of_global ind else cname)) in
+ let avoid =
+ if n=1 (* Only one recursive argument *) or n=0 then []
+ else
+ (* Forbid to use cname, cname0, hyprecname and hyprecname0 *)
+ (* in order to get names such as f1, f2, ... *)
+ let avoid =
+ (make_ident (string_of_id hyprecname) None) ::
+ (make_ident (string_of_id hyprecname) (Some 0)) :: [] in
+ if atompart_of_id cname <> "H" then
+ (make_ident base (Some 0)) :: (make_ident base None) :: avoid
+ else avoid in
+ id_of_string base, hyprecname, avoid
+
+let is_indhyp p n t =
+ let l, c = decompose_prod t in
+ let c,_ = decompose_app c in
+ let p = p + List.length l in
+ match kind_of_term c with
+ | Rel k when p < k & k <= p + n -> true
+ | _ -> false
+
+let chop_context n l =
+ let rec chop_aux acc = function
+ | n, (_,Some _,_ as h :: t) -> chop_aux (h::acc) (n, t)
+ | 0, l2 -> (List.rev acc, l2)
+ | n, (h::t) -> chop_aux (h::acc) (n-1, t)
+ | _, [] -> anomaly "chop_context"
+ in
+ chop_aux [] (n,l)
+
+let error_ind_scheme s =
+ let s = if s <> "" then s^" " else s in
+ error ("Cannot recognise "^s^"an induction schema")
+
+(* Check that the elimination scheme has a form similar to the
+ elimination schemes built by Coq *)
+let compute_elim_signature elimt names_info =
+ let nparams = ref 0 in
+ let hyps,ccl = decompose_prod_assum elimt in
+ let n = List.length hyps in
+ if n = 0 then error_ind_scheme "";
+ let f,l = decompose_app ccl in
+ let _,indbody,ind = List.hd hyps in
+ if indbody <> None then error "Cannot recognise an induction scheme";
+ let nargs = List.length l in
+ let dep = (nargs >= 1 && list_last l = mkRel 1) in
+ let nrealargs = if dep then nargs-1 else nargs in
+ let args = if dep then list_firstn nrealargs l else l in
+ let realargs,hyps1 = chop_context nrealargs (List.tl hyps) in
+ if args <> extended_rel_list 1 realargs then
+ error_ind_scheme "the conclusion of";
+ let indhd,indargs = decompose_app ind in
+ let indt =
+ try reference_of_constr indhd
+ with _ -> error "Cannot find the inductive type of the inductive schema" in
+ let nparams = List.length indargs - nrealargs in
+ let revparams, revhyps2 = chop_context nparams (List.rev hyps1) in
+ let rec check_elim npred = function
+ | (na,None,t)::l when isSort (snd (decompose_prod_assum t)) ->
+ check_elim (npred+1) l
+ | l ->
+ let is_pred n c =
+ let hd = fst (decompose_app c) in match kind_of_term hd with
+ | Rel q when n < q & q <= n+npred -> IndArg
+ | _ when hd = indhd -> RecArg
+ | _ -> OtherArg in
+ let rec check_branch p c = match kind_of_term c with
+ | Prod (_,t,c) -> is_pred p t :: check_branch (p+1) c
+ | LetIn (_,_,_,c) -> OtherArg :: check_branch (p+1) c
+(* | App (f,_) when is_pred p f = IndArg -> []*)
+ | _ when is_pred p c = IndArg -> []
+ | _ -> raise Exit in
+ let rec find_branches p = function
+ | (_,None,t)::brs ->
+ (match try Some (check_branch p t) with Exit -> None with
+ | Some l ->
+ let n7 = List.fold_left
+ (fun n b -> if b=IndArg then n+1 else n) 0 l in
+ let n8 = List.fold_left
+ (fun n b -> if b=RecArg then n+1 else n) 0 l in
+ let recvarname7, hyprecname7, avoid7 = make_up_names7 n7 indt names_info in
+ let recvarname8, hyprecname8, avoid8 = make_up_names8 n8 indt names_info in
+ let namesign = List.map
+ (fun b -> (b,if b=IndArg then (hyprecname7,hyprecname8)
+ else (recvarname7,recvarname8))) l in
+ ((avoid7,avoid8),namesign) :: find_branches (p+1) brs
+ | None -> error_ind_scheme "the branches of")
+ | (_,Some _,_)::_ -> error_ind_scheme "the branches of"
+ | [] ->
+ (* Check again conclusion *)
+ let ccl_arg_ok = is_pred (p + List.length realargs + 1) f = IndArg in
+ let ind_is_ok =
+ list_lastn nrealargs indargs = extended_rel_list 0 realargs in
+ if not (ccl_arg_ok & ind_is_ok) then
+ error "Cannot recognize the conclusion of an induction schema";
+ [] in
+ find_branches 0 l in
+ nparams, indt, (Array.of_list (check_elim 0 revhyps2))
+
+let find_elim_signature isrec style elim hyp0 gl =
+ let tmptyp0 = pf_get_hyp_typ gl hyp0 in
+ let (elimc,elimt) = match elim with
+ | None ->
+ let mind,_ = pf_reduce_to_quantified_ind gl tmptyp0 in
+ let s = elimination_sort_of_goal gl in
+ let elimc =
+ if isrec then Indrec.lookup_eliminator mind s
+ else pf_apply Indrec.make_case_gen gl mind s in
+ let elimt = pf_type_of gl elimc in
+ ((elimc, NoBindings), elimt)
+ | Some (elimc,lbind as e) ->
+ (e, pf_type_of gl elimc) in
+ let name_info = (style,hyp0) in
+ let nparams,indref,indsign = compute_elim_signature elimt name_info in
+ (elimc,elimt,nparams,indref,indsign)
+
+let induction_from_context isrec elim_info hyp0 (names,b_rnames) gl =
+ (*test suivant sans doute inutile car refait par le letin_tac*)
+ if List.mem hyp0 (ids_of_named_context (Global.named_context())) then
+ errorlabstrm "induction"
+ (str "Cannot generalize a global variable");
+ let elimc,elimt,nparams,indref,indsign = elim_info in
+ let tmptyp0 = pf_get_hyp_typ gl hyp0 in
+ let typ0 = pf_apply reduce_to_quantified_ref gl indref tmptyp0 in
+ let env = pf_env gl in
+ let indvars = find_atomic_param_of_ind nparams (snd (decompose_prod typ0)) in
+ let (statlists,lhyp0,indhyps,deps) = cook_sign hyp0 indvars env in
+ let tmpcl = it_mkNamedProd_or_LetIn (pf_concl gl) deps in
+ let names = compute_induction_names (Array.length indsign) names in
+ (* For translator *)
+ let names' = Array.map ref (Array.make (Array.length indsign) []) in
+ let b = ref false in
+ b_rnames := (b,Array.to_list names')::!b_rnames;
+ let names = array_map2 (fun n n' -> (n,b,n')) names names' in
+ (* End translator *)
+ let dephyps = List.map (fun (id,_,_) -> id) deps in
+ let args =
+ List.fold_left
+ (fun a (id,b,_) -> if b = None then (mkVar id)::a else a) [] deps in
+
+ (* Magistral effet de bord: si hyp0 a des arguments, ceux d'entre
+ eux qui ouvrent de nouveaux buts arrivent en premier dans la
+ liste des sous-buts du fait qu'ils sont le plus à gauche dans le
+ combinateur engendré par make_case_gen (un "Cases (hyp0 ?) of
+ ...") et il faut alors appliquer tclTHENLASTn; en revanche,
+ comme lookup_eliminator renvoie un combinateur de la forme
+ "ind_rec ... (hyp0 ?)", les buts correspondant à des arguments de
+ hyp0 sont maintenant à la fin et c'est tclTHENFIRSTn qui marche !!! *)
+ tclTHENLIST
+ [ if deps = [] then tclIDTAC else apply_type tmpcl args;
+ thin dephyps;
+ (if isrec then tclTHENFIRSTn else tclTHENLASTn)
+ (tclTHENLIST
+ [ induction_tac hyp0 typ0 (elimc,elimt);
+ thin [hyp0];
+ tclTRY (thin indhyps) ])
+ (array_map2
+ (induct_discharge statlists lhyp0 (List.rev dephyps)) indsign names)
+ ]
+ gl
+
+let induction_with_atomization_of_ind_arg isrec elim names hyp0 gl =
+ let (elimc,elimt,nparams,indref,indsign as elim_info) =
+ find_elim_signature isrec false elim hyp0 gl in
+ tclTHEN
+ (atomize_param_of_ind (indref,nparams) hyp0)
+ (induction_from_context isrec elim_info hyp0 names) gl
+
+(* This is Induction since V7 ("natural" induction both in quantified
+ premisses and introduced ones) *)
+let new_induct_gen isrec elim names c gl =
+ match kind_of_term c with
+ | Var id when not (mem_named_context id (Global.named_context())) ->
+ induction_with_atomization_of_ind_arg isrec elim names id gl
+ | _ ->
+ let x = id_of_name_using_hdchar (Global.env()) (pf_type_of gl c)
+ Anonymous in
+ let id = fresh_id [] x gl in
+ tclTHEN
+ (letin_tac true (Name id) c allClauses)
+ (induction_with_atomization_of_ind_arg isrec elim names id) gl
+
+let new_induct_destruct isrec c elim names = match c with
+ | ElimOnConstr c -> new_induct_gen isrec elim names c
+ | ElimOnAnonHyp n ->
+ tclTHEN (intros_until_n n)
+ (tclLAST_HYP (new_induct_gen isrec elim names))
+ (* Identifier apart because id can be quantified in goal and not typable *)
+ | ElimOnIdent (_,id) ->
+ tclTHEN (tclTRY (intros_until_id id))
+ (new_induct_gen isrec elim names (mkVar id))
+
+let new_induct = new_induct_destruct true
+let new_destruct = new_induct_destruct false
+
+(* The registered tactic, which calls the default elimination
+ * if no elimination constant is provided. *)
+
+(* Induction tactics *)
+
+(* This was Induction before 6.3 (induction only in quantified premisses) *)
+let raw_induct s = tclTHEN (intros_until_id s) (tclLAST_HYP simplest_elim)
+let raw_induct_nodep n = tclTHEN (intros_until_n n) (tclLAST_HYP simplest_elim)
+
+(* This was Induction in 6.3 (hybrid form) *)
+let induction_from_context_old_style hyp b_ids gl =
+ let elim_info = find_elim_signature true true None hyp gl in
+ let x = induction_from_context true elim_info hyp (None,b_ids) gl in
+ (* For translator *) fst (List.hd !b_ids) := true;
+ x
+
+let simple_induct_id hyp b_ids =
+ if !Options.v7 then
+ tclORELSE (raw_induct hyp) (induction_from_context_old_style hyp b_ids)
+ else
+ raw_induct hyp
+let simple_induct_nodep = raw_induct_nodep
+
+let simple_induct = function
+ | NamedHyp id,b_ids -> simple_induct_id id b_ids
+ | AnonHyp n,_ -> simple_induct_nodep n
+
+(* Destruction tactics *)
+
+let simple_destruct_id s =
+ (tclTHEN (intros_until_id s) (tclLAST_HYP simplest_case))
+let simple_destruct_nodep n =
+ (tclTHEN (intros_until_n n) (tclLAST_HYP simplest_case))
+
+let simple_destruct = function
+ | NamedHyp id -> simple_destruct_id id
+ | AnonHyp n -> simple_destruct_nodep n
+
+(*
+ * Eliminations giving the type instead of the proof.
+ * These tactics use the default elimination constant and
+ * no substitutions at all.
+ * May be they should be integrated into Elim ...
+ *)
+
+let elim_scheme_type elim t gl =
+ let (wc,kONT) = startWalk gl in
+ let clause = mk_clenv_type_of wc elim in
+ match kind_of_term (last_arg (clenv_template clause).rebus) with
+ | Meta mv ->
+ let clause' =
+ (* t is inductive, then CUMUL or CONV is irrelevant *)
+ clenv_unify true CUMUL t (clenv_instance_type clause mv) clause in
+ elim_res_pf kONT clause' true gl
+ | _ -> anomaly "elim_scheme_type"
+
+let elim_type t gl =
+ let (ind,t) = pf_reduce_to_atomic_ind gl t in
+ let elimc = Indrec.lookup_eliminator ind (elimination_sort_of_goal gl) in
+ elim_scheme_type elimc t gl
+
+let case_type t gl =
+ let (ind,t) = pf_reduce_to_atomic_ind gl t in
+ let env = pf_env gl in
+ let elimc = Indrec.make_case_gen env (project gl) ind (elimination_sort_of_goal gl) in
+ elim_scheme_type elimc t gl
+
+
+(* Some eliminations frequently used *)
+
+(* These elimination tactics are particularly adapted for sequent
+ calculus. They take a clause as argument, and yield the
+ elimination rule if the clause is of the form (Some id) and a
+ suitable introduction rule otherwise. They do not depend on
+ the name of the eliminated constant, so they can be also
+ used on ad-hoc disjunctions and conjunctions introduced by
+ the user.
+ -- Eduardo Gimenez (11/8/97)
+
+ HH (29/5/99) replaces failures by specific error messages
+ *)
+
+let andE id gl =
+ let t = pf_get_hyp_typ gl id in
+ if is_conjunction (pf_hnf_constr gl t) then
+ (tclTHEN (simplest_elim (mkVar id)) (tclDO 2 intro)) gl
+ else
+ errorlabstrm "andE"
+ (str("Tactic andE expects "^(string_of_id id)^" is a conjunction."))
+
+let dAnd cls =
+ onClauses
+ (function
+ | None -> simplest_split
+ | Some (id,_,_) -> andE id)
+ cls
+
+let orE id gl =
+ let t = pf_get_hyp_typ gl id in
+ if is_disjunction (pf_hnf_constr gl t) then
+ (tclTHEN (simplest_elim (mkVar id)) intro) gl
+ else
+ errorlabstrm "orE"
+ (str("Tactic orE expects "^(string_of_id id)^" is a disjunction."))
+
+let dorE b cls =
+ onClauses
+ (function
+ | (Some (id,_,_)) -> orE id
+ | None -> (if b then right else left) NoBindings)
+ cls
+
+let impE id gl =
+ let t = pf_get_hyp_typ gl id in
+ if is_imp_term (pf_hnf_constr gl t) then
+ let (dom, _, rng) = destProd (pf_hnf_constr gl t) in
+ tclTHENLAST
+ (cut_intro rng)
+ (apply_term (mkVar id) [mkMeta (new_meta())]) gl
+ else
+ errorlabstrm "impE"
+ (str("Tactic impE expects "^(string_of_id id)^
+ " is a an implication."))
+
+let dImp cls =
+ onClauses
+ (function
+ | None -> intro
+ | Some (id,_,_) -> impE id)
+ cls
+
+(************************************************)
+(* Tactics related with logic connectives *)
+(************************************************)
+
+(* Reflexivity tactics *)
+
+let reflexivity gl =
+ match match_with_equation (pf_concl gl) with
+ | None -> error "The conclusion is not a substitutive equation"
+ | Some (hdcncl,args) -> one_constructor 1 NoBindings gl
+
+let intros_reflexivity = (tclTHEN intros reflexivity)
+
+(* Symmetry tactics *)
+
+(* This tactic first tries to apply a constant named sym_eq, where eq
+ is the name of the equality predicate. If this constant is not
+ defined and the conclusion is a=b, it solves the goal doing (Cut
+ b=a;Intro H;Case H;Constructor 1) *)
+
+let symmetry gl =
+ match match_with_equation (pf_concl gl) with
+ | None -> error "The conclusion is not a substitutive equation"
+ | Some (hdcncl,args) ->
+ let hdcncls = string_of_inductive hdcncl in
+ begin
+ try
+ (apply (pf_parse_const gl ("sym_"^hdcncls)) gl)
+ with _ ->
+ let symc = match args with
+ | [t1; c1; t2; c2] -> mkApp (hdcncl, [| t2; c2; t1; c1 |])
+ | [typ;c1;c2] -> mkApp (hdcncl, [| typ; c2; c1 |])
+ | [c1;c2] -> mkApp (hdcncl, [| c2; c1 |])
+ | _ -> assert false
+ in
+ tclTHENLAST (cut symc)
+ (tclTHENLIST
+ [ intro;
+ tclLAST_HYP simplest_case;
+ one_constructor 1 NoBindings ])
+ gl
+ end
+
+let symmetry_in id gl =
+ let ctype = pf_type_of gl (mkVar id) in
+ let sign,t = decompose_prod_assum ctype in
+ match match_with_equation t with
+ | None -> (* Do not deal with setoids yet *)
+ error "The term provided does not end with an equation"
+ | Some (hdcncl,args) ->
+ let symccl = match args with
+ | [t1; c1; t2; c2] -> mkApp (hdcncl, [| t2; c2; t1; c1 |])
+ | [typ;c1;c2] -> mkApp (hdcncl, [| typ; c2; c1 |])
+ | [c1;c2] -> mkApp (hdcncl, [| c2; c1 |])
+ | _ -> assert false in
+ tclTHENS (cut (it_mkProd_or_LetIn symccl sign))
+ [ intro_replacing id;
+ tclTHENLIST [ intros; symmetry; apply (mkVar id); assumption ] ]
+ gl
+
+let intros_symmetry =
+ onClauses
+ (function
+ | None -> tclTHEN intros symmetry
+ | Some (id,_,_) -> symmetry_in id)
+
+(* Transitivity tactics *)
+
+(* This tactic first tries to apply a constant named trans_eq, where eq
+ is the name of the equality predicate. If this constant is not
+ defined and the conclusion is a=b, it solves the goal doing
+ Cut x1=x2;
+ [Cut x2=x3; [Intros e1 e2; Case e2;Assumption
+ | Idtac]
+ | Idtac]
+ --Eduardo (19/8/97)
+*)
+
+let transitivity t gl =
+ match match_with_equation (pf_concl gl) with
+ | None -> error "The conclusion is not a substitutive equation"
+ | Some (hdcncl,args) ->
+ let hdcncls = string_of_inductive hdcncl in
+ begin
+ try
+ apply_list [(pf_parse_const gl ("trans_"^hdcncls));t] gl
+ with _ ->
+ let eq1, eq2 = match args with
+ | [typ1;c1;typ2;c2] -> let typt = pf_type_of gl t in
+ ( mkApp(hdcncl, [| typ1; c1; typt ;t |]),
+ mkApp(hdcncl, [| typt; t; typ2; c2 |]) )
+ | [typ;c1;c2] ->
+ ( mkApp (hdcncl, [| typ; c1; t |]),
+ mkApp (hdcncl, [| typ; t; c2 |]) )
+ | [c1;c2] ->
+ ( mkApp (hdcncl, [| c1; t|]),
+ mkApp (hdcncl, [| t; c2 |]) )
+ | _ -> assert false
+ in
+ tclTHENFIRST (cut eq2)
+ (tclTHENFIRST (cut eq1)
+ (tclTHENLIST
+ [ tclDO 2 intro;
+ tclLAST_HYP simplest_case;
+ assumption ])) gl
+ end
+
+let intros_transitivity n = tclTHEN intros (transitivity n)
+
+(* tactical to save as name a subproof such that the generalisation of
+ the current goal, abstracted with respect to the local signature,
+ is solved by tac *)
+
+let interpretable_as_section_decl d1 d2 = match d1,d2 with
+ | (_,Some _,_), (_,None,_) -> false
+ | (_,Some b1,t1), (_,Some b2,t2) -> eq_constr b1 b2 & eq_constr t1 t2
+ | (_,None,t1), (_,_,t2) -> eq_constr t1 t2
+
+let abstract_subproof name tac gls =
+ let env = Global.env() in
+ let current_sign = Global.named_context()
+ and global_sign = pf_hyps gls in
+ let sign,secsign =
+ List.fold_right
+ (fun (id,_,_ as d) (s1,s2) ->
+ if mem_named_context id current_sign &
+ interpretable_as_section_decl (Sign.lookup_named id current_sign) d
+ then (s1,add_named_decl d s2)
+ else (add_named_decl d s1,s2))
+ global_sign (empty_named_context,empty_named_context) in
+ let na = next_global_ident_away false name (pf_ids_of_hyps gls) in
+ let concl = it_mkNamedProd_or_LetIn (pf_concl gls) sign in
+ if occur_existential concl then
+ if !Options.v7 then error "Abstract cannot handle existentials"
+ else error "\"abstract\" cannot handle existentials";
+ let lemme =
+ start_proof na (IsGlobal (Proof Lemma)) secsign concl (fun _ _ -> ());
+ let _,(const,kind,_) =
+ try
+ by (tclCOMPLETE (tclTHEN (tclDO (List.length sign) intro) tac));
+ let r = cook_proof () in
+ delete_current_proof (); r
+ with e when catchable_exception e ->
+ (delete_current_proof(); raise e)
+ in (* Faudrait un peu fonctionnaliser cela *)
+ let cd = Entries.DefinitionEntry const in
+ let sp = Declare.declare_internal_constant na (cd,IsProof Lemma) in
+ let newenv = Global.env() in
+ constr_of_reference (ConstRef (snd sp))
+ in
+ exact_no_check
+ (applist (lemme,
+ List.rev (Array.to_list (instance_from_named_context sign))))
+ gls
+
+let tclABSTRACT name_op tac gls =
+ let s = match name_op with
+ | Some s -> s
+ | None -> add_suffix (get_current_proof_name ()) "_subproof"
+ in
+ abstract_subproof s tac gls