summaryrefslogtreecommitdiff
path: root/proofs/refiner.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 /proofs/refiner.ml
Imported Upstream version 8.0pl1upstream/8.0pl1
Diffstat (limited to 'proofs/refiner.ml')
-rw-r--r--proofs/refiner.ml1030
1 files changed, 1030 insertions, 0 deletions
diff --git a/proofs/refiner.ml b/proofs/refiner.ml
new file mode 100644
index 00000000..55f11d52
--- /dev/null
+++ b/proofs/refiner.ml
@@ -0,0 +1,1030 @@
+(************************************************************************)
+(* 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: refiner.ml,v 1.67.2.1 2004/07/16 19:30:49 herbelin Exp $ *)
+
+open Pp
+open Util
+open Term
+open Termops
+open Sign
+open Evd
+open Sign
+open Environ
+open Reductionops
+open Instantiate
+open Type_errors
+open Proof_trees
+open Proof_type
+open Logic
+open Printer
+
+type transformation_tactic = proof_tree -> (goal list * validation)
+
+let hypotheses gl = gl.evar_hyps
+let conclusion gl = gl.evar_concl
+
+let sig_it x = x.it
+let sig_sig x = x.sigma
+
+
+let project_with_focus gls = rc_of_gc (gls.sigma) (gls.it)
+
+let pf_status pf = pf.open_subgoals
+
+let is_complete pf = (0 = (pf_status pf))
+
+let on_open_proofs f pf = if is_complete pf then pf else f pf
+
+let and_status = List.fold_left (+) 0
+
+(* Normalizing evars in a goal. Called by tactic Local_constraints
+ (i.e. when the sigma of the proof tree changes). Detect if the
+ goal is unchanged *)
+let norm_goal sigma gl =
+ let red_fun = Evarutil.nf_evar sigma in
+ let ncl = red_fun gl.evar_concl in
+ let ngl =
+ { evar_concl = ncl;
+ evar_hyps =
+ Sign.fold_named_context
+ (fun (d,b,ty) sign ->
+ add_named_decl (d, option_app red_fun b, red_fun ty) sign)
+ gl.evar_hyps ~init:empty_named_context;
+ evar_body = gl.evar_body} in
+ if ngl = gl then None else Some ngl
+
+
+(* [mapshape [ l1 ; ... ; lk ] [ v1 ; ... ; vk ] [ p_1 ; .... ; p_(l1+...+lk) ]]
+ gives
+ [ (v1 [p_1 ... p_l1]) ; (v2 [ p_(l1+1) ... p_(l1+l2) ]) ; ... ;
+ (vk [ p_(l1+...+l(k-1)+1) ... p_(l1+...lk) ]) ]
+ *)
+
+let rec mapshape nl (fl : (proof_tree list -> proof_tree) list)
+ (l : proof_tree list) =
+ match nl with
+ | [] -> []
+ | h::t ->
+ let m,l = list_chop h l in
+ (List.hd fl m) :: (mapshape t (List.tl fl) l)
+
+(* [frontier : proof_tree -> goal list * validation]
+ given a proof [p], [frontier p] gives [(l,v)] where [l] is the list of goals
+ to be solved to complete the proof, and [v] is the corresponding
+ validation *)
+
+let rec frontier p =
+ match p.ref with
+ | None ->
+ ([p.goal],
+ (fun lp' ->
+ let p' = List.hd lp' in
+ if p'.goal = p.goal then
+ p'
+ else
+ errorlabstrm "Refiner.frontier"
+ (str"frontier was handed back a ill-formed proof.")))
+ | Some(r,pfl) ->
+ let gll,vl = List.split(List.map frontier pfl) in
+ (List.flatten gll,
+ (fun retpfl ->
+ let pfl' = mapshape (List.map List.length gll) vl retpfl in
+ { open_subgoals = and_status (List.map pf_status pfl');
+ goal = p.goal;
+ ref = Some(r,pfl')}))
+
+
+let rec frontier_map_rec f n p =
+ if n < 1 || n > p.open_subgoals then p else
+ match p.ref with
+ | None ->
+ let p' = f p in
+ if p'.goal == p.goal || p'.goal = p.goal then p'
+ else
+ errorlabstrm "Refiner.frontier_map"
+ (str"frontier_map was handed back a ill-formed proof.")
+ | Some(r,pfl) ->
+ let (_,rpfl') =
+ List.fold_left
+ (fun (n,acc) p -> (n-p.open_subgoals,frontier_map_rec f n p::acc))
+ (n,[]) pfl in
+ let pfl' = List.rev rpfl' in
+ { open_subgoals = and_status (List.map pf_status pfl');
+ goal = p.goal;
+ ref = Some(r,pfl')}
+
+let frontier_map f n p =
+ let nmax = p.open_subgoals in
+ let n = if n < 0 then nmax + n + 1 else n in
+ if n < 1 || n > nmax then
+ errorlabstrm "Refiner.frontier_map" (str "No such subgoal");
+ frontier_map_rec f n p
+
+let rec frontier_mapi_rec f i p =
+ if p.open_subgoals = 0 then p else
+ match p.ref with
+ | None ->
+ let p' = f i p in
+ if p'.goal == p.goal || p'.goal = p.goal then p'
+ else
+ errorlabstrm "Refiner.frontier_mapi"
+ (str"frontier_mapi was handed back a ill-formed proof.")
+ | Some(r,pfl) ->
+ let (_,rpfl') =
+ List.fold_left
+ (fun (n,acc) p -> (n+p.open_subgoals,frontier_mapi_rec f n p::acc))
+ (i,[]) pfl in
+ let pfl' = List.rev rpfl' in
+ { open_subgoals = and_status (List.map pf_status pfl');
+ goal = p.goal;
+ ref = Some(r,pfl')}
+
+let frontier_mapi f p = frontier_mapi_rec f 1 p
+
+(* [list_pf p] is the lists of goals to be solved in order to complete the
+ proof [p] *)
+
+let list_pf p = fst (frontier p)
+
+let rec nb_unsolved_goals pf = pf.open_subgoals
+
+(* leaf g is the canonical incomplete proof of a goal g *)
+
+let leaf g = {
+ open_subgoals = 1;
+ goal = g;
+ ref = None }
+
+(* Tactics table. *)
+
+let tac_tab = Hashtbl.create 17
+
+let add_tactic s t =
+ if Hashtbl.mem tac_tab s then
+ errorlabstrm ("Refiner.add_tactic: ")
+ (str ("Cannot redeclare tactic "^s));
+ Hashtbl.add tac_tab s t
+
+let overwriting_add_tactic s t =
+ if Hashtbl.mem tac_tab s then begin
+ Hashtbl.remove tac_tab s;
+ warning ("Overwriting definition of tactic "^s)
+ end;
+ Hashtbl.add tac_tab s t
+
+let lookup_tactic s =
+ try
+ Hashtbl.find tac_tab s
+ with Not_found ->
+ errorlabstrm "Refiner.lookup_tactic"
+ (str"The tactic " ++ str s ++ str" is not installed")
+
+
+(* refiner r is a tactic applying the rule r *)
+
+let bad_subproof () =
+ anomalylabstrm "Refiner.refiner" (str"Bad subproof in validation.")
+
+let check_subproof_connection gl spfl =
+ if not (list_for_all2eq (fun g pf -> g=pf.goal) gl spfl)
+ then (bad_subproof (); false) else true
+
+let abstract_tactic_expr te tacfun gls =
+ let (sgl_sigma,v) = tacfun gls in
+ let hidden_proof = v (List.map leaf sgl_sigma.it) in
+ (sgl_sigma,
+ fun spfl ->
+ assert (check_subproof_connection sgl_sigma.it spfl);
+ { open_subgoals = and_status (List.map pf_status spfl);
+ goal = gls.it;
+ ref = Some(Tactic(te,hidden_proof),spfl) })
+
+let refiner = function
+ | Prim pr as r ->
+ let prim_fun = prim_refiner pr in
+ (fun goal_sigma ->
+ let sgl = prim_fun goal_sigma.sigma goal_sigma.it in
+ ({it=sgl; sigma = goal_sigma.sigma},
+ (fun spfl ->
+ assert (check_subproof_connection sgl spfl);
+ { open_subgoals = and_status (List.map pf_status spfl);
+ goal = goal_sigma.it;
+ ref = Some(r,spfl) })))
+
+ | Tactic _ -> failwith "Refiner: should not occur"
+
+ (* [Local_constraints lc] makes the local constraints be [lc] and
+ normalizes evars *)
+
+ | Change_evars as r ->
+ (fun goal_sigma ->
+ let gl = goal_sigma.it in
+ (match norm_goal goal_sigma.sigma gl with
+ Some ngl ->
+ ({it=[ngl];sigma=goal_sigma.sigma},
+ (fun spfl ->
+ assert (check_subproof_connection [ngl] spfl);
+ { open_subgoals = (List.hd spfl).open_subgoals;
+ goal = gl;
+ ref = Some(r,spfl) }))
+ (* if the evar change does not affect the goal, leave the
+ proof tree unchanged *)
+ | None -> ({it=[gl];sigma=goal_sigma.sigma},
+ (fun spfl ->
+ assert (List.length spfl = 1);
+ List.hd spfl))))
+
+
+let local_Constraints gl = refiner Change_evars gl
+
+let norm_evar_tac = local_Constraints
+
+(*
+let vernac_tactic (s,args) =
+ let tacfun = lookup_tactic s args in
+ abstract_extra_tactic s args tacfun
+*)
+let abstract_tactic te = abstract_tactic_expr (Tacexpr.TacAtom (dummy_loc,te))
+
+let abstract_extended_tactic s args =
+ abstract_tactic (Tacexpr.TacExtend (dummy_loc, s, args))
+
+let vernac_tactic (s,args) =
+ let tacfun = lookup_tactic s args in
+ abstract_extended_tactic s args tacfun
+
+(* [rc_of_pfsigma : proof sigma -> readable_constraints] *)
+let rc_of_pfsigma sigma = rc_of_gc sigma.sigma sigma.it.goal
+
+(* [rc_of_glsigma : proof sigma -> readable_constraints] *)
+let rc_of_glsigma sigma = rc_of_gc sigma.sigma sigma.it
+
+(* [extract_open_proof : proof_tree -> constr * (int * constr) list]
+ takes a (not necessarly complete) proof and gives a pair (pfterm,obl)
+ where pfterm is the constr corresponding to the proof
+ and [obl] is an [int*constr list] [ (m1,c1) ; ... ; (mn,cn)]
+ where the mi are metavariables numbers, and ci are their types.
+ Their proof should be completed in order to complete the initial proof *)
+
+let extract_open_proof sigma pf =
+ let next_meta =
+ let meta_cnt = ref 0 in
+ let rec f () =
+ incr meta_cnt;
+ if in_dom sigma (existential_of_int !meta_cnt) then f ()
+ else !meta_cnt
+ in f
+ in
+ let open_obligations = ref [] in
+ let rec proof_extractor vl = function
+ | {ref=Some(Prim _,_)} as pf -> prim_extractor proof_extractor vl pf
+
+ | {ref=Some(Tactic (_,hidden_proof),spfl)} ->
+ let sgl,v = frontier hidden_proof in
+ let flat_proof = v spfl in
+ proof_extractor vl flat_proof
+
+ | {ref=Some(Change_evars,[pf])} -> (proof_extractor vl) pf
+
+ | {ref=None;goal=goal} ->
+ let visible_rels =
+ map_succeed
+ (fun id ->
+ try let n = list_index id vl in (n,id)
+ with Not_found -> failwith "caught")
+ (ids_of_named_context goal.evar_hyps) in
+ let sorted_rels =
+ Sort.list (fun (n1,_) (n2,_) -> n1 > n2 ) visible_rels in
+ let sorted_env =
+ List.map (fun (n,id) -> (n,Sign.lookup_named id goal.evar_hyps))
+ sorted_rels in
+ let abs_concl =
+ List.fold_right (fun (_,decl) c -> mkNamedProd_or_LetIn decl c)
+ sorted_env goal.evar_concl in
+ let inst = List.filter (fun (_,(_,b,_)) -> b = None) sorted_env in
+ let meta = next_meta () in
+ open_obligations := (meta,abs_concl):: !open_obligations;
+ applist (mkMeta meta, List.map (fun (n,_) -> mkRel n) inst)
+
+ | _ -> anomaly "Bug : a case has been forgotten in proof_extractor"
+ in
+ let pfterm = proof_extractor [] pf in
+ (pfterm, List.rev !open_obligations)
+
+(*********************)
+(* Tacticals *)
+(*********************)
+
+(* unTAC : tactic -> goal sigma -> proof sigma *)
+
+let unTAC tac g =
+ let (gl_sigma,v) = tac g in
+ { it = v (List.map leaf gl_sigma.it); sigma = gl_sigma.sigma }
+
+let unpackage glsig = (ref (glsig.sigma)),glsig.it
+
+let repackage r v = {it=v;sigma = !r}
+
+let apply_sig_tac r tac g =
+ check_for_interrupt (); (* Breakpoint *)
+ let glsigma,v = tac (repackage r g) in
+ r := glsigma.sigma;
+ (glsigma.it,v)
+
+let idtac_valid = function
+ [pf] -> pf
+ | _ -> anomaly "Refiner.idtac_valid"
+
+(* [goal_goal_list : goal sigma -> goal list sigma] *)
+let goal_goal_list gls = {it=[gls.it];sigma=gls.sigma}
+
+(* identity tactic without any message *)
+let tclIDTAC gls = (goal_goal_list gls, idtac_valid)
+
+(* the message printing identity tactic *)
+let tclIDTAC_MESSAGE s gls =
+ if s = "" then tclIDTAC gls
+ else
+ begin
+ msgnl (str ("Idtac says : "^s)); tclIDTAC gls
+ end
+
+(* General failure tactic *)
+let tclFAIL_s s gls = errorlabstrm "Refiner.tclFAIL_s" (str s)
+
+(* A special exception for levels for the Fail tactic *)
+exception FailError of int * string
+
+(* The Fail tactic *)
+let tclFAIL lvl s g = raise (FailError (lvl,s))
+
+let start_tac gls =
+ let (sigr,g) = unpackage gls in
+ (sigr,[g],idtac_valid)
+
+let finish_tac (sigr,gl,p) = (repackage sigr gl, p)
+
+(* Apply [taci.(i)] on the first n-th subgoals and [tac] on the others *)
+let thensf_tac taci tac (sigr,gs,p) =
+ let n = Array.length taci in
+ let nsg = List.length gs in
+ if nsg<n then errorlabstrm "Refiner.thensn_tac" (str "Not enough subgoals.");
+ let gll,pl =
+ List.split
+ (list_map_i (fun i -> apply_sig_tac sigr (if i<n then taci.(i) else tac))
+ 0 gs) in
+ (sigr, List.flatten gll,
+ compose p (mapshape (List.map List.length gll) pl))
+
+(* Apply [taci.(i)] on the last n-th subgoals and [tac] on the others *)
+let thensl_tac tac taci (sigr,gs,p) =
+ let n = Array.length taci in
+ let nsg = List.length gs in
+ if nsg<n then errorlabstrm "Refiner.thensn_tac" (str "Not enough subgoals.");
+ let gll,pl =
+ List.split
+ (list_map_i (fun i -> apply_sig_tac sigr (if i<0 then tac else taci.(i)))
+ (n-nsg) gs) in
+ (sigr, List.flatten gll,
+ compose p (mapshape (List.map List.length gll) pl))
+
+(* Apply [tac i] on the ith subgoal (no subgoals number check) *)
+let thensi_tac tac (sigr,gs,p) =
+ let gll,pl =
+ List.split (list_map_i (fun i -> apply_sig_tac sigr (tac i)) 1 gs) in
+ (sigr, List.flatten gll, compose p (mapshape (List.map List.length gll) pl))
+
+let then_tac tac = thensf_tac [||] tac
+
+let non_existent_goal n =
+ errorlabstrm ("No such goal: "^(string_of_int n))
+ (str"Trying to apply a tactic to a non existent goal")
+
+(* Apply tac on the i-th goal (if i>0). If i<0, then start counting from
+ the last goal (i=-1). *)
+let theni_tac i tac ((_,gl,_) as subgoals) =
+ let nsg = List.length gl in
+ let k = if i < 0 then nsg + i + 1 else i in
+ if nsg < 1 then errorlabstrm "theni_tac" (str"No more subgoals.")
+ else if k >= 1 & k <= nsg then
+ thensf_tac
+ (Array.init k (fun i -> if i+1 = k then tac else tclIDTAC)) tclIDTAC
+ subgoals
+ else non_existent_goal k
+
+(* [tclTHENSFIRSTn tac1 [|t1 ; ... ; tn|] tac2 gls] applies the tactic [tac1]
+ to [gls] and applies [t1], ..., [tn] to the [n] first resulting
+ subgoals, and [tac2] to the others subgoals. Raises an error if
+ the number of resulting subgoals is strictly less than [n] *)
+let tclTHENSFIRSTn tac1 taci tac gls =
+ finish_tac (thensf_tac taci tac (then_tac tac1 (start_tac gls)))
+
+(* [tclTHENSLASTn tac1 tac2 [|t1 ;...; tn|] gls] applies the tactic [tac1]
+ to [gls] and applies [t1], ..., [tn] to the [n] last resulting
+ subgoals, and [tac2] to the other subgoals. Raises an error if the
+ number of resulting subgoals is strictly less than [n] *)
+let tclTHENSLASTn tac1 tac taci gls =
+ finish_tac (thensl_tac tac taci (then_tac tac1 (start_tac gls)))
+
+(* [tclTHEN_i tac taci gls] applies the tactic [tac] to [gls] and applies
+ [(taci i)] to the i_th resulting subgoal (starting from 1), whatever the
+ number of subgoals is *)
+let tclTHEN_i tac taci gls =
+ finish_tac (thensi_tac taci (then_tac tac (start_tac gls)))
+
+let tclTHENLASTn tac1 taci = tclTHENSLASTn tac1 tclIDTAC taci
+let tclTHENFIRSTn tac1 taci = tclTHENSFIRSTn tac1 taci tclIDTAC
+
+(* [tclTHEN tac1 tac2 gls] applies the tactic [tac1] to [gls] and applies
+ [tac2] to every resulting subgoals *)
+let tclTHEN tac1 tac2 = tclTHENSFIRSTn tac1 [||] tac2
+
+(* [tclTHENSV tac1 [t1 ; ... ; tn] gls] applies the tactic [tac1] to
+ [gls] and applies [t1],..., [tn] to the [n] resulting subgoals. Raises
+ an error if the number of resulting subgoals is not [n] *)
+let tclTHENSV tac1 tac2v =
+ tclTHENSFIRSTn tac1 tac2v (tclFAIL_s "Wrong number of tactics.")
+
+let tclTHENS tac1 tac2l = tclTHENSV tac1 (Array.of_list tac2l)
+
+(* [tclTHENLAST tac1 tac2 gls] applies the tactic [tac1] to [gls] and [tac2]
+ to the last resulting subgoal *)
+let tclTHENLAST tac1 tac2 = tclTHENSLASTn tac1 tclIDTAC [|tac2|]
+
+(* [tclTHENFIRST tac1 tac2 gls] applies the tactic [tac1] to [gls] and [tac2]
+ to the first resulting subgoal *)
+let tclTHENFIRST tac1 tac2 = tclTHENSFIRSTn tac1 [|tac2|] tclIDTAC
+
+
+(* [tclTHENLIST [t1;..;tn]] applies [t1] then [t2] ... then [tn]. More
+ convenient than [tclTHEN] when [n] is large. *)
+let rec tclTHENLIST = function
+ [] -> tclIDTAC
+ | t1::tacl -> tclTHEN t1 (tclTHENLIST tacl)
+
+
+
+
+(* various progress criterions *)
+let same_goal gl subgoal =
+ (hypotheses subgoal) = (hypotheses gl) &
+ eq_constr (conclusion subgoal) (conclusion gl)
+
+
+let weak_progress gls ptree =
+ (List.length gls.it <> 1) or
+ (not (same_goal (List.hd gls.it) ptree.it))
+
+(* Il y avait ici un ts_eq ........ *)
+let progress gls ptree =
+ (weak_progress gls ptree) or
+ (not (ptree.sigma == gls.sigma))
+
+
+(* PROGRESS tac ptree applies tac to the goal ptree and fails if tac leaves
+the goal unchanged *)
+let tclPROGRESS tac ptree =
+ let rslt = tac ptree in
+ if progress (fst rslt) ptree then rslt
+ else errorlabstrm "Refiner.PROGRESS" (str"Failed to progress.")
+
+(* weak_PROGRESS tac ptree applies tac to the goal ptree and fails
+ if tac leaves the goal unchanged, possibly modifying sigma *)
+let tclWEAK_PROGRESS tac ptree =
+ let rslt = tac ptree in
+ if weak_progress (fst rslt) ptree then rslt
+ else errorlabstrm "Refiner.tclWEAK_PROGRESS" (str"Failed to progress.")
+
+
+(* Same as tclWEAK_PROGRESS but fails also if tactics generates several goals,
+ one of them being identical to the original goal *)
+let tclNOTSAMEGOAL (tac : tactic) goal =
+ let rslt = tac goal in
+ let gls = (fst rslt).it in
+ if List.exists (same_goal goal.it) gls
+ then errorlabstrm "Refiner.tclNOTSAMEGOAL"
+ (str"Tactic generated a subgoal identical to the original goal.")
+ else rslt
+
+
+
+(* ORELSE0 t1 t2 tries to apply t1 and if it fails, applies t2 *)
+let tclORELSE0 t1 t2 g =
+ try
+ t1 g
+ with (* Breakpoint *)
+ | e when catchable_exception e -> check_for_interrupt (); t2 g
+ | FailError (0,_) | Stdpp.Exc_located(_, FailError (0,_)) ->
+ check_for_interrupt (); t2 g
+ | FailError (lvl,s) -> raise (FailError (lvl - 1, s))
+ | Stdpp.Exc_located (s,FailError (lvl,s')) ->
+ raise (Stdpp.Exc_located (s,FailError (lvl - 1, s')))
+
+(* ORELSE t1 t2 tries to apply t1 and if it fails or does not progress,
+ then applies t2 *)
+let tclORELSE t1 t2 = tclORELSE0 (tclPROGRESS t1) t2
+
+(* TRY f tries to apply f, and if it fails, leave the goal unchanged *)
+let tclTRY f = (tclORELSE0 f tclIDTAC)
+
+let tclTHENTRY f g = (tclTHEN f (tclTRY g))
+
+(* Try the first tactic that does not fail in a list of tactics *)
+
+let rec tclFIRST = function
+ | [] -> tclFAIL_s "No applicable tactic."
+ | t::rest -> tclORELSE0 t (tclFIRST rest)
+
+let ite_gen tcal tac_if continue tac_else gl=
+ let success=ref false in
+ let tac_if0 gl=
+ let result=tac_if gl in
+ success:=true;result in
+ let tac_else0 e gl=
+ if !success then
+ raise e
+ else
+ tac_else gl in
+ try
+ tcal tac_if0 continue gl
+ with (* Breakpoint *)
+ | e when catchable_exception e ->
+ check_for_interrupt (); tac_else0 e gl
+ | (FailError (0,_) | Stdpp.Exc_located(_, FailError (0,_))) as e ->
+ check_for_interrupt (); tac_else0 e gl
+ | FailError (lvl,s) -> raise (FailError (lvl - 1, s))
+ | Stdpp.Exc_located (s,FailError (lvl,s')) ->
+ raise (Stdpp.Exc_located (s,FailError (lvl - 1, s')))
+
+(* Try the first tactic and, if it succeeds, continue with
+ the second one, and if it fails, use the third one *)
+
+let tclIFTHENELSE=ite_gen tclTHEN
+
+(* Idem with tclTHENS and tclTHENSV *)
+
+let tclIFTHENSELSE=ite_gen tclTHENS
+
+let tclIFTHENSVELSE=ite_gen tclTHENSV
+
+
+(* Fails if a tactic did not solve the goal *)
+let tclCOMPLETE tac = tclTHEN tac (tclFAIL_s "Proof is not complete.")
+
+(* Try the first thats solves the current goal *)
+let tclSOLVE tacl = tclFIRST (List.map tclCOMPLETE tacl)
+
+
+(* Iteration tacticals *)
+
+let tclDO n t =
+ let rec dorec k =
+ if k < 0 then errorlabstrm "Refiner.tclDO"
+ (str"Wrong argument : Do needs a positive integer.");
+ if k = 0 then tclIDTAC
+ else if k = 1 then t else (tclTHEN t (dorec (k-1)))
+ in
+ dorec n
+
+(* Beware: call by need of CAML, g is needed *)
+let rec tclREPEAT t g =
+ (tclORELSE (tclTHEN t (tclREPEAT t)) tclIDTAC) g
+
+let tclAT_LEAST_ONCE t = (tclTHEN t (tclREPEAT t))
+
+(* Repeat on the first subgoal (no failure if no more subgoal) *)
+let rec tclREPEAT_MAIN t g =
+ (tclORELSE (tclTHEN_i t (fun i -> if i = 1 then (tclREPEAT_MAIN t) else
+ tclIDTAC)) tclIDTAC) g
+
+(*s Tactics handling a list of goals. *)
+
+type validation_list = proof_tree list -> proof_tree list
+
+type tactic_list = (goal list sigma) -> (goal list sigma) * validation_list
+
+(* Functions working on goal list for correct backtracking in Prolog *)
+
+let tclFIRSTLIST = tclFIRST
+let tclIDTAC_list gls = (gls, fun x -> x)
+
+(* first_goal : goal list sigma -> goal sigma *)
+
+let first_goal gls =
+ let gl = gls.it and sig_0 = gls.sigma in
+ if gl = [] then error "first_goal";
+ { it = List.hd gl; sigma = sig_0 }
+
+(* goal_goal_list : goal sigma -> goal list sigma *)
+
+let goal_goal_list gls =
+ let gl = gls.it and sig_0 = gls.sigma in { it = [gl]; sigma = sig_0 }
+
+(* tactic -> tactic_list : Apply a tactic to the first goal in the list *)
+
+let apply_tac_list tac glls =
+ let (sigr,lg) = unpackage glls in
+ match lg with
+ | (g1::rest) ->
+ let (gl,p) = apply_sig_tac sigr tac g1 in
+ let n = List.length gl in
+ (repackage sigr (gl@rest),
+ fun pfl -> let (pfg,pfrest) = list_chop n pfl in (p pfg)::pfrest)
+ | _ -> error "apply_tac_list"
+
+let then_tactic_list tacl1 tacl2 glls =
+ let (glls1,pl1) = tacl1 glls in
+ let (glls2,pl2) = tacl2 glls1 in
+ (glls2, compose pl1 pl2)
+
+(* Transform a tactic_list into a tactic *)
+
+let tactic_list_tactic tac gls =
+ let (glres,vl) = tac (goal_goal_list gls) in
+ (glres, compose idtac_valid vl)
+
+
+
+
+(* The type of proof-trees state and a few utilities
+ A proof-tree state is built from a proof-tree, a set of global
+ constraints, and a stack which allows to navigate inside the
+ proof-tree remembering how to rebuild the global proof-tree
+ possibly after modification of one of the focused children proof-tree.
+ The number in the stack corresponds to
+ either the selected subtree and the validation is a function from a
+ proof-tree list consisting only of one proof-tree to the global
+ proof-tree
+ or -1 when the move is done behind a registered tactic in which
+ case the validation corresponds to a constant function giving back
+ the original proof-tree. *)
+
+type pftreestate = {
+ tpf : proof_tree ;
+ tpfsigma : evar_map;
+ tstack : (int * validation) list }
+
+let proof_of_pftreestate pts = pts.tpf
+let is_top_pftreestate pts = pts.tstack = []
+let cursor_of_pftreestate pts = List.map fst pts.tstack
+let evc_of_pftreestate pts = pts.tpfsigma
+
+let top_goal_of_pftreestate pts =
+ { it = goal_of_proof pts.tpf; sigma = pts.tpfsigma }
+
+let nth_goal_of_pftreestate n pts =
+ let goals = fst (frontier pts.tpf) in
+ try {it = List.nth goals (n-1); sigma = pts.tpfsigma }
+ with Invalid_argument _ | Failure _ -> non_existent_goal n
+
+let descend n p =
+ match p.ref with
+ | None -> error "It is a leaf."
+ | Some(r,pfl) ->
+ if List.length pfl >= n then
+ (match list_chop (n-1) pfl with
+ | left,(wanted::right) ->
+ (wanted,
+ (fun pfl' ->
+ if (List.length pfl' = 1)
+ & (List.hd pfl').goal = wanted.goal
+ then
+ let pf' = List.hd pfl' in
+ let spfl = left@(pf'::right) in
+ let newstatus = and_status (List.map pf_status spfl) in
+ { open_subgoals = newstatus;
+ goal = p.goal;
+ ref = Some(r,spfl) }
+ else
+ error "descend: validation"))
+ | _ -> assert false)
+ else
+ error "Too few subproofs"
+
+let traverse n pts = match n with
+ | 0 -> (* go to the parent *)
+ (match pts.tstack with
+ | [] -> error "traverse: no ancestors"
+ | (_,v)::tl ->
+ { tpf = v [pts.tpf];
+ tstack = tl;
+ tpfsigma = pts.tpfsigma })
+ | -1 -> (* go to the hidden tactic-proof, if any, otherwise fail *)
+ (match pts.tpf.ref with
+ | Some (Tactic (_,spf),_) ->
+ let v = (fun pfl -> pts.tpf) in
+ { tpf = spf;
+ tstack = (-1,v)::pts.tstack;
+ tpfsigma = pts.tpfsigma }
+ | _ -> error "traverse: not a tactic-node")
+ | n -> (* when n>0, go to the nth child *)
+ let (npf,v) = descend n pts.tpf in
+ { tpf = npf;
+ tpfsigma = pts.tpfsigma;
+ tstack = (n,v):: pts.tstack }
+
+let change_constraints_pftreestate newgc pts = { pts with tpfsigma = newgc }
+
+let app_tac sigr tac p =
+ let (gll,v) = tac {it=p.goal;sigma= !sigr} in
+ sigr := gll.sigma;
+ v (List.map leaf gll.it)
+
+(* solve the nth subgoal with tactic tac *)
+let solve_nth_pftreestate n tac pts =
+ let sigr = ref pts.tpfsigma in
+ let tpf' = frontier_map (app_tac sigr tac) n pts.tpf in
+ let tpf'' =
+ if !sigr == pts.tpfsigma then tpf'
+ else frontier_mapi (fun _ g -> app_tac sigr norm_evar_tac g) tpf' in
+ { tpf = tpf'';
+ tpfsigma = !sigr;
+ tstack = pts.tstack }
+
+let solve_pftreestate = solve_nth_pftreestate 1
+
+(* This function implements a poor man's undo at the current goal.
+ This is a gross approximation as it does not attempt to clean correctly
+ the global constraints given in tpfsigma. *)
+
+let weak_undo_pftreestate pts =
+ let pf = leaf pts.tpf.goal in
+ { tpf = pf;
+ tpfsigma = pts.tpfsigma;
+ tstack = pts.tstack }
+
+(* Gives a new proof (a leaf) of a goal gl *)
+let mk_pftreestate g =
+ { tpf = leaf g;
+ tstack = [];
+ tpfsigma = Evd.empty }
+
+(* Extracts a constr from a proof-tree state ; raises an error if the
+ proof is not complete or the state does not correspond to the head
+ of the proof-tree *)
+
+let extract_open_pftreestate pts =
+ extract_open_proof pts.tpfsigma pts.tpf
+
+let extract_pftreestate pts =
+ if pts.tstack <> [] then
+ errorlabstrm "extract_pftreestate"
+ (str"Cannot extract from a proof-tree in which we have descended;" ++
+ spc () ++ str"Please ascend to the root");
+ let pfterm,subgoals = extract_open_pftreestate pts in
+ if subgoals <> [] then
+ errorlabstrm "extract_proof"
+ (str "Attempt to save an incomplete proof");
+ let env = Global.env_of_context pts.tpf.goal.evar_hyps in
+ strong whd_betaiotaevar env pts.tpfsigma pfterm
+ (***
+ local_strong (Evarutil.whd_ise (ts_it pts.tpfsigma)) pfterm
+ ***)
+(* Focus on the first leaf proof in a proof-tree state *)
+
+let rec first_unproven pts =
+ let pf = (proof_of_pftreestate pts) in
+ if is_complete_proof pf then
+ errorlabstrm "first_unproven" (str"No unproven subgoals");
+ if is_leaf_proof pf then
+ pts
+ else
+ let childnum =
+ list_try_find_i
+ (fun n pf ->
+ if not(is_complete_proof pf) then n else failwith "caught")
+ 1 (children_of_proof pf)
+ in
+ first_unproven (traverse childnum pts)
+
+(* Focus on the last leaf proof in a proof-tree state *)
+
+let rec last_unproven pts =
+ let pf = proof_of_pftreestate pts in
+ if is_complete_proof pf then
+ errorlabstrm "last_unproven" (str"No unproven subgoals");
+ if is_leaf_proof pf then
+ pts
+ else
+ let children = (children_of_proof pf) in
+ let nchilds = List.length children in
+ let childnum =
+ list_try_find_i
+ (fun n pf ->
+ if not(is_complete_proof pf) then n else failwith "caught")
+ 1 (List.rev children)
+ in
+ last_unproven (traverse (nchilds-childnum+1) pts)
+
+let rec nth_unproven n pts =
+ let pf = proof_of_pftreestate pts in
+ if is_complete_proof pf then
+ errorlabstrm "nth_unproven" (str"No unproven subgoals");
+ if is_leaf_proof pf then
+ if n = 1 then
+ pts
+ else
+ errorlabstrm "nth_unproven" (str"Not enough unproven subgoals")
+ else
+ let children = children_of_proof pf in
+ let rec process i k = function
+ | [] ->
+ errorlabstrm "nth_unproven" (str"Not enough unproven subgoals")
+ | pf1::rest ->
+ let k1 = nb_unsolved_goals pf1 in
+ if k1 < k then
+ process (i+1) (k-k1) rest
+ else
+ nth_unproven k (traverse i pts)
+ in
+ process 1 n children
+
+let rec node_prev_unproven loc pts =
+ let pf = proof_of_pftreestate pts in
+ match cursor_of_pftreestate pts with
+ | [] -> last_unproven pts
+ | n::l ->
+ if is_complete_proof pf or loc = 1 then
+ node_prev_unproven n (traverse 0 pts)
+ else
+ let child = List.nth (children_of_proof pf) (loc - 2) in
+ if is_complete_proof child then
+ node_prev_unproven (loc - 1) pts
+ else
+ first_unproven (traverse (loc - 1) pts)
+
+let rec node_next_unproven loc pts =
+ let pf = proof_of_pftreestate pts in
+ match cursor_of_pftreestate pts with
+ | [] -> first_unproven pts
+ | n::l ->
+ if is_complete_proof pf ||
+ loc = (List.length (children_of_proof pf)) then
+ node_next_unproven n (traverse 0 pts)
+ else if is_complete_proof (List.nth (children_of_proof pf) loc) then
+ node_next_unproven (loc + 1) pts
+ else
+ last_unproven(traverse (loc + 1) pts)
+
+let next_unproven pts =
+ let pf = proof_of_pftreestate pts in
+ if is_leaf_proof pf then
+ match cursor_of_pftreestate pts with
+ | [] -> error "next_unproven"
+ | n::_ -> node_next_unproven n (traverse 0 pts)
+ else
+ node_next_unproven (List.length (children_of_proof pf)) pts
+
+let prev_unproven pts =
+ let pf = proof_of_pftreestate pts in
+ if is_leaf_proof pf then
+ match cursor_of_pftreestate pts with
+ | [] -> error "prev_unproven"
+ | n::_ -> node_prev_unproven n (traverse 0 pts)
+ else
+ node_prev_unproven 1 pts
+
+let rec top_of_tree pts =
+ if is_top_pftreestate pts then pts else top_of_tree(traverse 0 pts)
+
+
+(* Pretty-printers. *)
+
+open Pp
+
+let pr_tactic = function
+ | Tacexpr.TacArg (Tacexpr.Tacexp t) ->
+ if !Options.v7 then
+ Pptactic.pr_glob_tactic t (*top tactic from tacinterp*)
+ else
+ Pptacticnew.pr_glob_tactic (Global.env()) t
+ | t ->
+ if !Options.v7 then
+ Pptactic.pr_tactic t
+ else
+ Pptacticnew.pr_tactic (Global.env()) t
+
+let pr_rule = function
+ | Prim r -> hov 0 (pr_prim_rule r)
+ | Tactic (texp,_) -> hov 0 (pr_tactic texp)
+ | Change_evars ->
+ (* This is internal tactic and cannot be replayed at user-level.
+ Function pr_rule_dot below is used when we want to hide
+ Change_evars *)
+ str "Evar change"
+
+(* Does not print change of evars *)
+let pr_rule_dot = function
+ | Change_evars -> mt ()
+ | r -> pr_rule r ++ str"."
+
+exception Different
+
+(* We remove from the var context of env what is already in osign *)
+let thin_sign osign sign =
+ Sign.fold_named_context
+ (fun (id,c,ty as d) sign ->
+ try
+ if Sign.lookup_named id osign = (id,c,ty) then sign
+ else raise Different
+ with Not_found | Different -> add_named_decl d sign)
+ sign ~init:empty_named_context
+
+let rec print_proof sigma osign pf =
+ let {evar_hyps=hyps; evar_concl=cl;
+ evar_body=body} = pf.goal in
+ let hyps' = thin_sign osign hyps in
+ match pf.ref with
+ | None ->
+ hov 0 (pr_seq {evar_hyps=hyps'; evar_concl=cl; evar_body=body})
+ | Some(r,spfl) ->
+ hov 0
+ (hov 0 (pr_seq {evar_hyps=hyps'; evar_concl=cl; evar_body=body}) ++
+ spc () ++ str" BY " ++
+ hov 0 (pr_rule r) ++ fnl () ++
+ str" " ++
+ hov 0 (prlist_with_sep pr_fnl (print_proof sigma hyps) spfl)
+)
+
+let pr_change gl =
+ (str"Change " ++ prterm_env (Global.env()) gl.evar_concl ++ str".")
+
+let rec print_script nochange sigma osign pf =
+ let {evar_hyps=sign; evar_concl=cl} = pf.goal in
+ match pf.ref with
+ | None ->
+ (if nochange then
+ (str"<Your Tactic Text here>")
+ else
+ pr_change pf.goal)
+ ++ fnl ()
+ | Some(r,spfl) ->
+ ((if nochange then (mt ()) else (pr_change pf.goal ++ fnl ())) ++
+ pr_rule_dot r ++ fnl () ++
+ prlist_with_sep pr_fnl
+ (print_script nochange sigma sign) spfl)
+
+let print_treescript nochange sigma _osign pf =
+ let rec aux top pf =
+ let {evar_hyps=sign; evar_concl=cl} = pf.goal in
+ match pf.ref with
+ | None ->
+ if nochange then
+ (str"<Your Tactic Text here>")
+ else
+ (pr_change pf.goal)
+ | Some(r,spfl) ->
+ (if nochange then mt () else (pr_change pf.goal ++ fnl ())) ++
+ pr_rule_dot r ++
+ match spfl with
+ | [] -> mt ()
+ | [spf] -> fnl () ++ (if top then mt () else str " ") ++ aux top spf
+ | _ -> fnl () ++ str " " ++
+ hov 0 (prlist_with_sep fnl (aux false) spfl)
+ in hov 0 (aux true pf)
+
+let rec print_info_script sigma osign pf =
+ let {evar_hyps=sign; evar_concl=cl} = pf.goal in
+ match pf.ref with
+ | None -> (mt ())
+ | Some(Change_evars,[spf]) ->
+ print_info_script sigma osign spf
+ | Some(r,spfl) ->
+ (pr_rule r ++
+ match spfl with
+ | [pf1] ->
+ if pf1.ref = None then
+ (str "." ++ fnl ())
+ else
+ (str";" ++ brk(1,3) ++
+ print_info_script sigma sign pf1)
+ | _ -> (str"." ++ fnl () ++
+ prlist_with_sep pr_fnl
+ (print_info_script sigma sign) spfl))
+
+let format_print_info_script sigma osign pf =
+ hov 0 (print_info_script sigma osign pf)
+
+let print_subscript sigma sign pf =
+ if is_tactic_proof pf then
+ format_print_info_script sigma sign (subproof_of_proof pf)
+ else
+ format_print_info_script sigma sign pf
+
+let tclINFO (tac : tactic) gls =
+ let (sgl,v) as res = tac gls in
+ begin try
+ let pf = v (List.map leaf (sig_it sgl)) in
+ let sign = (sig_it gls).evar_hyps in
+ msgnl (hov 0 (str" == " ++
+ print_subscript (sig_sig gls) sign pf))
+ with e when catchable_exception e ->
+ msgnl (hov 0 (str "Info failed to apply validation"))
+ end;
+ res