123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516(************************************************************************)(* * The Rocq Prover / The Rocq Development Team *)(* v * Copyright INRIA, CNRS and contributors *)(* <O___,, * (see version control and CREDITS file for authors & dates) *)(* \VV/ **************************************************************)(* // * This file is distributed under the terms of the *)(* * GNU Lesser General Public License Version 2.1 *)(* * (see LICENSE file for the text of the license) *)(************************************************************************)(* Module defining the last essential tiles of interactive proofs.
A proof deals with the focusing commands (including the braces and bullets),
the shelf (see the [shelve] tactic) and given up goal (see the [give_up]
tactic). A proof is made of the following:
- Proofview: a proof is primarily the data of the current view.
That which is shown to the user (as a remainder, a proofview
is mainly the logical state of the proof, together with the
currently focused goals).
- Focus: a proof has a focus stack: the top of the stack contains
the context in which to unfocus the current view to a view focused
with the rest of the stack.
In addition, this contains, for each of the focus context, a
"focus kind" and a "focus condition" (in practice, and for modularity,
the focus kind is actually stored inside the condition). To unfocus, one
needs to know the focus kind, and the condition (for instance "no condition" or
the proof under focused must be complete) must be met.
- Given up goals: as long as there is a given up goal, the proof is not completed.
Given up goals cannot be retrieved, the user must go back where the tactic
[give_up] was run and solve the goal there.
*)openUtilmoduleFocusKind=Dyn.Make()type'afocus_kind='aFocusKind.tagtypereason=NotThisWay|AlreadyNoFocustypeunfocusable=|Cannotofreason|Loose|Stricttype'afocus_condition=|CondNoofbool*'afocus_kind|CondDoneofbool*'afocus_kind|CondEndStackof'afocus_kind(* loose_end is false here *)letnew_focus_kind=FocusKind.create(* To be authorized to unfocus one must meet the condition prescribed by
the action which focused.*)(* spiwack: we could consider having a list of authorized focus_kind instead
of just one, if anyone needs it *)exceptionCannotUnfocusThisWay(* Cannot focus on non-existing subgoals *)exceptionNoSuchGoalsofint*intexceptionNoSuchGoalofNames.Id.toptionexceptionFullyUnfocusedlet_=CErrors.register_handlerbeginfunction|CannotUnfocusThisWay->Some(Pp.str"This proof is focused, but cannot be unfocused this way")|NoSuchGoals(i,j)whenInt.equalij->SomePp.(str"[Focus] No such goal ("++inti++str").")|NoSuchGoals(i,j)->SomePp.(str"[Focus] Not every goal in range ["++inti++str","++intj++str"] exist.")|NoSuchGoal(Someid)->SomePp.(str"[Focus] No such goal: "++str(Names.Id.to_stringid)++str".")|NoSuchGoalNone->SomePp.(str"[Focus] No such goal.")|FullyUnfocused->Some(Pp.str"The proof is not focused")|_->Noneendletcheck_cond_kindck=letkind_of_cond=function|CondNo(_,k)|CondDone(_,k)|CondEndStackk->kinFocusKind.eq(kind_of_condc)kletequal_kindck=matchFocusKind.eqckwith|None->false|Some_->truelettest_condck1pw=matchcwith|CondNo(_,k)whenequal_kindkk1->Strict|CondNo(true,_)->Loose|CondNo(false,_)->CannotNotThisWay|CondDone(_,k)whenequal_kindkk1&&Proofview.finishedpw->Strict|CondDone(true,_)->Loose|CondDone(false,_)->CannotNotThisWay|CondEndStackkwhenequal_kindkk1->Strict|CondEndStack_->CannotAlreadyNoFocusletno_cond?(loose_end=false)k=CondNo(loose_end,k)letdone_cond?(loose_end=false)k=CondDone(loose_end,k)typefocus_element=FocusElt:'afocus_condition*'a*Proofview.focus_context->focus_element(* Subpart of the type of proofs. It contains the parts of the proof which
are under control of the undo mechanism *)typet={proofview:Proofview.proofview(** Current focused proofview *);entry:Proofview.entry(** Entry for the proofview *);focus_stack:focus_elementlist(** History of the focusings, provides information on how to unfocus
the proof and the extra information stored while focusing. The
list is empty when the proof is fully unfocused. *);name:Names.Id.t(** the name of the theorem whose proof is being constructed *);poly:bool(** polymorphism *);typing_flags:Declarations.typing_flagsoption}(*** General proof functions ***)letproofp=let(goals,sigma)=Proofview.proofviewp.proofviewin(* spiwack: beware, the bottom of the stack is used by [Proof]
internally, and should not be exposed. *)letrecmap_minus_onef=function|[]->assertfalse|[_]->[]|a::l->fa::(map_minus_onefl)inletmap(FocusElt(_,_,c))=Proofview.focus_contextcinletstack=map_minus_onemapp.focus_stackin(goals,stack,sigma)letrecunroll_focuspv=function|FocusElt(_,_,ctx)::stk->unroll_focus(Proofview.unfocusctxpv)stk|[]->pv(* spiwack: a proof is considered completed even if its still focused, if the focus
doesn't hide any goal.
Unfocusing is handled in {!return}. *)letis_donep=Proofview.finishedp.proofview&&Proofview.finished(unroll_focusp.proofviewp.focus_stack)(* Returns the list of partial proofs to initial goals *)letpartial_proofp=Proofview.partial_proofp.entryp.proofview(*** The following functions implement the basic internal mechanisms
of proofs, they are not meant to be exported in the .mli ***)(* An auxiliary function to push a {!focus_context} on the focus stack. *)letpush_focuscondinfcontextpr={prwithfocus_stack=FocusElt(cond,inf,context)::pr.focus_stack}typeany_focus_condition=AnyFocusCond:'afocus_condition->any_focus_condition(* An auxiliary function to read the kind of the next focusing step *)letcond_of_focuspr=matchpr.focus_stackwith|FocusElt(cond,_,_)::_->AnyFocusCondcond|_->raiseFullyUnfocused(* An auxiliary function to pop and read the last {!Proofview.focus_context}
on the focus stack. *)letpop_focuspr=matchpr.focus_stackwith|focus::other_focuses->{prwithfocus_stack=other_focuses},focus|_->raiseFullyUnfocused(* This function focuses the proof [pr] between indices [i] and [j] *)let_focuscondinfijpr=letfocused,context=Proofview.focusijpr.proofviewinletpr=push_focuscondinfcontextprin{prwithproofview=focused}(* This function unfocuses the proof [pr], it raises [FullyUnfocused],
if the proof is already fully unfocused.
This function does not care about the condition of the current focus. *)let_unfocuspr=letpr,FocusElt(_,_,fc)=pop_focusprin{prwithproofview=Proofview.unfocusfcpr.proofview}(* Focus command (focuses on the [i]th subgoal) *)(* spiwack: there could also, easily be a focus-on-a-range tactic, is there
a need for it? *)letfocuscondinfipr=try_focuscondinfiiprwithCList.IndexOutOfRange->raise(NoSuchGoals(i,i))(* Focus on the goal named id *)letfocus_idcondinfidpr=let(focused_goals,evar_map)=Proofview.proofviewpr.proofviewinbeginmatchtrySome(Evd.evar_keyidevar_map)withNot_found->Nonewith|Someev->beginmatchCList.index_optEvar.equalevfocused_goalswith|Somei->(* goal is already under focus *)_focuscondinfiipr|None->ifCList.mem_fEvar.equalev(Evd.shelfevar_map)then(* goal is on the shelf, put it in focus *)letproofview=Proofview.unshelve[ev]pr.proofviewinletpr={prwithproofview}inlet(focused_goals,_)=Proofview.proofviewpr.proofviewinleti=(* Now we know that this will succeed *)tryCList.indexEvar.equalevfocused_goalswithNot_found->assertfalsein_focuscondinfiiprelseraiseCannotUnfocusThisWayend|None->raise(NoSuchGoal(Someid))endletrecunfocuskindpr()=letAnyFocusCondcond=cond_of_focusprinmatchtest_condcondkindpr.proofviewwith|CannotNotThisWay->raiseCannotUnfocusThisWay|CannotAlreadyNoFocus->raiseFullyUnfocused|Strict->letpr=_unfocusprinpr|Loose->begintryletpr=_unfocusprinunfocuskindpr()withFullyUnfocused->raiseCannotUnfocusThisWayendexceptionNoSuchFocus(* no handler: should not be allowed to reach toplevel. *)letrecget_in_focus_stack:typea.afocus_kind->_->a=funkindstack->matchstackwith|FocusElt(cond,inf,_)::stack->beginmatchcheck_cond_kindcondkindwith|SomeRefl->inf|None->get_in_focus_stackkindstackend|[]->raiseNoSuchFocusletget_at_focuskindpr=get_in_focus_stackkindpr.focus_stackletis_last_focuskindpr=letFocusElt(cond,_,_)=List.hdpr.focus_stackinOption.has_some(check_cond_kindcondkind)letno_focused_goalp=Proofview.finishedp.proofviewletrecmaximal_unfocuskp=ifno_focused_goalpthentrymaximal_unfocusk(unfocuskp())withFullyUnfocused|CannotUnfocusThisWay->pelsep(*** Proof Creation/Termination ***)(* [end_of_stack] is unfocused by return to close every loose focus. *)letend_of_stack_kind=new_focus_kind"end_of_stack"letend_of_stack=CondEndStackend_of_stack_kindletunfocused=is_last_focusend_of_stack_kindletunfocus_allp=unfocusend_of_stack_kindp()letstart~name~poly?typing_flagssigmagoals=letentry,proofview=Proofview.initsigmagoalsinletpr={proofview;entry;focus_stack=[];name;poly;typing_flags}in_focusend_of_stack()1(List.lengthgoals)prletdependent_start~name~poly?typing_flagsgoals=letentry,proofview=Proofview.dependent_initgoalsinletpr={proofview;entry;focus_stack=[];name;poly;typing_flags}inletnumber_of_goals=List.length(Proofview.initial_goalspr.entry)in_focusend_of_stack()1number_of_goalsprletcompactp=letentry,proofview=Proofview.compactp.entryp.proofviewin{pwithproofview;entry}letupdate_sigma_univsugraphp=letproofview=Proofview.Unsafe.update_sigma_univsugraphp.proofviewin{pwithproofview}(*** Function manipulation proof extra informations ***)(*** Tactics ***)letrun_tacticenvtacpr=letopenProofview.Notationsinlettac=(* include the future goals in the shelf *)Proofview.with_shelftac>>=fun(shelf,v)->Proofview.Unsafe.tclNEWSHELVEDshelf<*>Proofview.tclUNITvinlet{name;poly;proofview}=prinlet(result,proofview,status,info_trace)=Proofview.apply~name~polyenvtacproofviewinletsigma=Proofview.returnproofviewin(* cleanup any shelved goals that got defined
(this is only useful for goals that were already in the shelf,
tclNEWSHELVED filters out defined goals instead of adding them)
XXX should we be doing something advance-aware like tclNEWSHELVED? *)letproofview=Proofview.filter_shelf(Evd.is_undefinedsigma)proofviewin{prwithproofview},(status,info_trace),result(*** Commands ***)(* Remove all the goals from the shelf and adds them at the end of the
focused goals. *)letunshelvep=letsigma=Proofview.returnp.proofviewinletshelf=Evd.shelfsigmainletproofview=Proofview.unshelveshelfp.proofviewin{pwithproofview}letbackground_subgoalsp=letit,_=Proofview.proofview(unroll_focusp.proofviewp.focus_stack)initletall_goalsp=letaddgsset=List.fold_left(funsg->Evar.Set.addgs)setgsinlet(goals,stack,sigma)=proofpinletset=addgoalsEvar.Set.emptyinletset=List.fold_left(funsgs->let(g1,g2)=gsinaddg1(addg2set))setstackinletset=add(Evd.shelfsigma)setinletset=Evar.Set.union(Evd.given_upsigma)setinletbgoals=background_subgoalspinaddbgoalssettypedata={sigma:Evd.evar_map(** A representation of the evar_map [EJGA wouldn't it better to just return the proofview?] *);goals:Evar.tlist(** Focused goals *);entry:Proofview.entry(** Entry for the proofview *);stack:(Evar.tlist*Evar.tlist)list(** A representation of the focus stack *);name:Names.Id.t(** The name of the theorem whose proof is being constructed *);poly:bool(** Locality, polymorphism, and "kind" [Coercion, Definition, etc...] *)}letdata{proofview;focus_stack;entry;name;poly}=letgoals,sigma=Proofview.proofviewproofviewin(* spiwack: beware, the bottom of the stack is used by [Proof]
internally, and should not be exposed. *)letrecmap_minus_onef=function|[]->assertfalse|[_]->[]|a::l->fa::(map_minus_onefl)inletmap(FocusElt(_,_,c))=Proofview.focus_contextcinletstack=map_minus_onemapfocus_stackin{sigma;goals;entry;stack;name;poly}letpr_goale=Pp.(str"GOAL:"++int(Evar.repre))letgoal_uide=string_of_int(Evar.repre)letpr_proofp=let{goals=fg_goals;stack=bg_goals;sigma}=datapinPp.(letpr_goal_list=prlist_with_sepspcpr_goalinletrecauxacc=function|[]->acc|(before,after)::stack->aux(pr_goal_listbefore++spc()++str"{"++acc++str"}"++spc()++pr_goal_listafter)stackinstr"["++str"focus structure: "++aux(pr_goal_listfg_goals)bg_goals++str";"++spc()++str"shelved: "++pr_goal_list(Evd.shelfsigma)++str";"++spc()++str"given up: "++pr_goal_list(Evar.Set.elements@@Evd.given_upsigma)++str"]")let{Goptions.get=use_unification_heuristics}=Goptions.declare_bool_option_and_ref~key:["Solve";"Unification";"Constraints"]~value:true()exceptionSuggestNoSuchGoalsofint*texceptionFailedConstraintsofexnlet()=CErrors.register_handler(function|FailedConstraintse->SomePp.(CErrors.printe++spc()++hov1(str"(while solving unification constraints,"++spc()++str"see flag \"Solve Unification Constraints\")"))|_->None)letsolve_constraints=Proofview.tclORRefine.solve_constraints(fun(e,info)->Proofview.tclZERO~info(FailedConstraintse))letsolve?with_end_tacgiinfo_lvltacpr=lettac=matchwith_end_tacwith|None->tac|Someetac->Proofview.tclTHENtacetacinlettac=matchinfo_lvlwith|None->tac|Some_->Proofview.Trace.record_info_tracetacinletnosuchgoal=letinfo=Exninfo.reify()inProofview.tclZERO~info(SuggestNoSuchGoals(1,pr))inlettac=Goal_select.tclSELECT~nosuchgoalgitacinlettac=ifuse_unification_heuristics()thenProofview.tclTHENtacsolve_constraintselsetacinletenv=Global.env()inletenv=Environ.update_typing_flags?typing_flags:pr.typing_flagsenvinlet(p,(status,info),())=run_tacticenvtacprinletenv=Global.env()inletsigma=Evd.from_envenvinlet()=matchinfo_lvlwith|None->()|Somei->Feedback.msg_info(Pp.hov0(Proofview.Trace.pr_infoenvsigma~lvl:iinfo))in(p,status)(**********************************************************************)(* Shortcut to build a term using tactics *)letrefine_by_tactic~name~polyenvsigmatytac=(* Save the initial side-effects to restore them afterwards. We set the
current set of side-effects to be empty so that we can retrieve the
ones created during the tactic invocation easily. *)leteff=Evd.eval_side_effectssigmainletsigma=Evd.drop_side_effectssigmain(* Save the existing goals *)letsigma=Evd.push_future_goalssigmain(* Start a proof *)letprf=start~name~polysigma[env,ty]inlet(prf,_,())=tryrun_tacticenvtacprfwithLogic_monad.TacticFailureeassrc->(* Catch the inner error of the monad tactic *)let(_,info)=Exninfo.capturesrcinExninfo.iraise(e,info)in(* Plug back the retrieved sigma *)let{goals;stack;sigma;entry}=dataprfinassert(stack=[]);letans=matchProofview.initial_goalsentrywith|[_,c,_]->c|_->assertfalseinletans=EConstr.to_constr~abort_on_undefined_evars:falsesigmaansin(* [neff] contains the freshly generated side-effects *)letneff=Evd.eval_side_effectssigmain(* Reset the old side-effects *)letsigma=Evd.drop_side_effectssigmainletsigma=Evd.emit_side_effectseffsigmain(* Restore former goals *)let_goals,sigma=Evd.pop_future_goalssigmain(* Push remaining goals as future_goals which is the only way we
have to inform the caller that there are goals to collect while
not being encapsulated in the monad *)letsigma=List.fold_rightEvd.declare_future_goalgoalssigmain(* Get rid of the fresh side-effects by internalizing them in the term
itself. Note that this is unsound, because the tactic may have solved
other goals that were already present during its invocation, so that
those goals rely on effects that are not present anymore. Hopefully,
this hack will work in most cases. *)letneff=neff.Evd.seff_privateinlet(ans,_)=Safe_typing.inline_private_constantsenv((ans,Univ.ContextSet.empty),neff)inEConstr.of_constrans,sigmaletget_goal_context_genpfi=let{sigma;goals}=datapfinletgoal=tryList.nthgoals(i-1)withFailure_->raise(NoSuchGoalNone)inletenv=Evd.evar_filtered_env(Global.env())(Evd.find_undefinedsigmagoal)in(sigma,env)letget_proof_contextp=tryget_goal_context_genp1with|NoSuchGoal_->(* No more focused goals *)let{sigma}=datapinsigma,Global.env()