123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412(************************************************************************)(* * The Coq Proof Assistant / The Coq 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) *)(************************************************************************)openUtilopenNamesopenPpopenTacexprlet(ltac_trace_info:ltac_traceExninfo.t)=Exninfo.make()letprtacx=letenv=Global.env()inPptactic.pr_glob_tacticenvxletprmatchpattenvsigmahyp=Pptactic.pr_match_pattern(Printer.pr_constr_pattern_envenvsigma)hypletprmatchrlenvsigmarl=Pptactic.pr_match_rulefalseprtac(fun(_,p)->Printer.pr_constr_pattern_envenvsigmap)rl(* This module intends to be a beginning of debugger for tactic expressions.
Currently, it is quite simple and we can hope to have, in the future, a more
complete panel of commands dedicated to a proof assistant framework *)(* Debug information *)typedebug_info=|DebugOnofint|DebugOff(* An exception handler *)letexplain_logic_errore=CErrors.printeletexplain_logic_error_no_anomalye=CErrors.print_no_reporte(* Communications with the outside world *)moduleComm=struct(* TODO: ideally we would check that the debugger hooks are
correctly set, however we don't do this yet as debugger
initialiation is unconditionally done for example in coqc, which
doesn't support the debugger interface. Improving this would
require some tweaks in tacinterp which are out of scope for the
current refactoring. *)letinit()=matchDebugHook.Intf.get()with|Some_->()|None->()(* CErrors.user_err
* (Pp.str "Your user interface does not support the Ltac debugger.") *)lethook()=Option.get(DebugHook.Intf.get())letwrap=Proofview.NonLogical.makeopenDebugHook.IntfopenDebugHook.Answerletpromptg=wrap(fun()->(hook()).submit_answer(Promptg))letgoalg=wrap(fun()->(hook()).submit_answer(Goalg))letoutputg=wrap(fun()->(hook()).submit_answer(Outputg))letread=wrap(fun()->(hook()).read_cmd())end(* Prints the goal *)letdb_pr_goalgl=letenv=Proofview.Goal.envglinletconcl=Proofview.Goal.conclglinletpenv=Termops.Internal.print_named_contextenvinletpc=Printer.pr_econstr_envenv(Tacmach.New.projectgl)conclinstr" "++hv0(penv++fnl()++str"============================"++fnl()++str" "++pc)++fnl()letdb_pr_goal=Proofview.Goal.enterbeginfungl->letpg=db_pr_goalglinProofview.tclLIFT(Comm.goalpg)end(* Prints the commands *)lethelp()=Comm.output(str"Commands: <Enter> = Step"++fnl()++str" h/? = Help"++fnl()++str" r <num> = Run <num> times"++fnl()++str" r <string> = Run up to next idtac <string>"++fnl()++str" s = Skip"++fnl()++str" x = Exit")(* Prints the goal and the command to be executed *)letgoal_comtac=Proofview.tclTHENdb_pr_goal(Proofview.tclLIFT(Comm.output(str"Going to execute:"++fnl()++prtactac)))(* [run (new_ref _)] gives us a ref shared among [NonLogical.t]
expressions. It avoids parametrizing everything over a
reference. *)letskipped=Proofview.NonLogical.run(Proofview.NonLogical.ref0)letskip=Proofview.NonLogical.run(Proofview.NonLogical.ref0)letbreakpoint=Proofview.NonLogical.run(Proofview.NonLogical.refNone)letbatch=reffalseopenGoptionslet()=declare_bool_option{optdepr=false;optkey=["Ltac";"Batch";"Debug"];optread=(fun()->!batch);optwrite=(funx->batch:=x)}(* (Re-)initialize debugger *)letdb_initialize=letopenProofview.NonLogicalinmakeComm.init>>(skip:=0)>>(skipped:=0)>>(breakpoint:=None)(* Prints the run counter *)letrunini=letopenProofview.NonLogicalinifnotinithenbegin!skipped>>=funskipped->Comm.output(str"Executed expressions: "++intskipped++fnl())end>>!skipped>>=funx->skipped:=x+1elsereturn()(* Prints the prompt *)letrecpromptlevel=(* spiwack: avoid overriding by the open below *)letruntrue=runtrueinletopenProofview.NonLogicalinletnl=ifUtil.(!batch)then"\n"else""inComm.prompt(tag"message.prompt"@@fnl()++str"TcDebug ("++intlevel++str(") > "^nl))>>ifUtil.(!batch)thenreturn(DebugOn(level+1))elseletexit=(skip:=0)>>(skipped:=0)>>raise(Sys.Break,Exninfo.null)inComm.read>>=funinst->letopenDebugHook.Actioninmatchinstwith|Step->return(DebugOn(level+1))|Skip->return(DebugOff)|Exit->Proofview.NonLogical.print_char'\b'>>exit|Help->help()>>promptlevel|RunCntnum->(skip:=num)>>(skipped:=0)>>runtrue>>return(DebugOn(level+1))|RunBreakpoints->(breakpoint:=(Somes))>>runtrue>>return(DebugOn(level+1))|Failed->promptlevel(* Prints the state and waits for an instruction *)(* spiwack: the only reason why we need to take the continuation [f]
as an argument rather than returning the new level directly seems to
be that [f] is wrapped in with "explain_logic_error". I don't think
it serves any purpose in the current design, so we could just drop
that. *)letdebug_promptlevtacf=(* spiwack: avoid overriding by the open below *)letrunfalse=runfalseinletopenProofview.NonLogicalinlet(>=)=Proofview.tclBINDin(* What to print and to do next *)letnewlevel=Proofview.tclLIFT!skip>=funinitial_skip->ifInt.equalinitial_skip0thenProofview.tclLIFT!breakpoint>=funbreakpoint->ifOption.is_emptybreakpointthenProofview.tclTHEN(goal_comtac)(Proofview.tclLIFT(promptlev))elseProofview.tclLIFT(runfalse>>return(DebugOn(lev+1)))elseProofview.tclLIFTbegin(!skip>>=funs->skip:=s-1)>>runfalse>>!skip>>=funnew_skip->(ifInt.equalnew_skip0thenskipped:=0elsereturn())>>return(DebugOn(lev+1))endinnewlevel>=funnewlevel->(* What to execute *)Proofview.tclOR(fnewlevel)beginfun(reraise,info)->Proofview.tclTHEN(Proofview.tclLIFTbegin(skip:=0)>>(skipped:=0)>>Comm.output(str"Level "++intlev++str": "++explain_logic_errorreraise)end)(Proofview.tclZERO~inforeraise)endletis_debugdb=letopenProofview.NonLogicalin!breakpoint>>=funbreakpoint->matchdb,breakpointwith|DebugOff,_->returnfalse|_,Some_->returnfalse|_->!skip>>=funskip->return(Int.equalskip0)(* Prints a constr *)letdb_constrdebugenvsigmac=letopenProofview.NonLogicalinis_debugdebug>>=fundb->ifdbthenComm.output(str"Evaluated term: "++Printer.pr_econstr_envenvsigmac)elsereturn()(* Prints the pattern rule *)letdb_pattern_ruledebugenvsigmanumr=letopenProofview.NonLogicalinis_debugdebug>>=fundb->ifdbthenbeginComm.output(str"Pattern rule "++intnum++str":"++fnl()++str"|"++spc()++prmatchrlenvsigmar)endelsereturn()(* Prints the hypothesis pattern identifier if it exists *)lethyp_bound=function|Anonymous->str" (unbound)"|Nameid->str" (bound to "++Id.printid++str")"(* Prints a matched hypothesis *)letdb_matched_hypdebugenvsigma(id,_,c)ido=letopenProofview.NonLogicalinis_debugdebug>>=fundb->ifdbthenComm.output(str"Hypothesis "++Id.printid++hyp_boundido++str" has been matched: "++Printer.pr_econstr_envenvsigmac)elsereturn()(* Prints the matched conclusion *)letdb_matched_concldebugenvsigmac=letopenProofview.NonLogicalinis_debugdebug>>=fundb->ifdbthenComm.output(str"Conclusion has been matched: "++Printer.pr_econstr_envenvsigmac)elsereturn()(* Prints a success message when the goal has been matched *)letdb_mc_pattern_successdebug=letopenProofview.NonLogicalinis_debugdebug>>=fundb->ifdbthenComm.output(str"The goal has been successfully matched!"++fnl()++str"Let us execute the right-hand side part..."++fnl())elsereturn()(* Prints a failure message for an hypothesis pattern *)letdb_hyp_pattern_failuredebugenvsigma(na,hyp)=letopenProofview.NonLogicalinis_debugdebug>>=fundb->ifdbthenComm.output(str"The pattern hypothesis"++hyp_boundna++str" cannot match: "++prmatchpattenvsigmahyp)elsereturn()(* Prints a matching failure message for a rule *)letdb_matching_failuredebug=letopenProofview.NonLogicalinis_debugdebug>>=fundb->ifdbthenComm.output(str"This rule has failed due to matching errors!"++fnl()++str"Let us try the next one...")elsereturn()(* Prints an evaluation failure message for a rule *)letdb_eval_failuredebugs=letopenProofview.NonLogicalinis_debugdebug>>=fundb->ifdbthenlets=str"message \""++s++str"\""inComm.output(str"This rule has failed due to \"Fail\" tactic ("++s++str", level 0)!"++fnl()++str"Let us try the next one...")elsereturn()(* Prints a logic failure message for a rule *)letdb_logic_failuredebugerr=letopenProofview.NonLogicalinis_debugdebug>>=fundb->ifdbthenbeginComm.output(explain_logic_errorerr)>>Comm.output(str"This rule has failed due to a logic error!"++fnl()++str"Let us try the next one...")endelsereturn()letis_breakpointbrknames=matchbrkname,swith|Somes,MsgStrings'::_->String.equalss'|_->falseletdb_breakpointdebugs=letopenProofview.NonLogicalin!breakpoint>>=funopt_breakpoint->matchdebugwith|DebugOnlevwhennot(CList.is_emptys)&&is_breakpointopt_breakpoints->breakpoint:=None|_->return()(** Extrating traces *)letis_defined_ltactrace=letrecaux=function|(_,Tacexpr.LtacNameCallf)::_->not(Tacenv.is_ltac_for_ml_tacticf)|(_,Tacexpr.LtacNotationCallf)::_->true|(_,Tacexpr.LtacAtomCall_)::_->false|_::tail->auxtail|[]->falseinaux(List.revtrace)letexplain_ltac_call_tracelasttraceloc=letcalls=last::List.rev_mapsndtraceinletpr_callck=matchckwith|Tacexpr.LtacNotationCallkn->quote(Pptactic.pr_alias_keykn)|Tacexpr.LtacNameCallcst->quote(Pptactic.pr_ltac_constantcst)|Tacexpr.LtacMLCallt->quote(prtact)|Tacexpr.LtacVarCall(id,t)->quote(Id.printid)++strbrk" (bound to "++prtact++str")"|Tacexpr.LtacAtomCallte->quote(prtac(CAst.make(Tacexpr.TacAtomte)))|Tacexpr.LtacConstrInterp(c,{Ltac_pretype.ltac_constrs=vars})->(* XXX: This hooks into the CErrors's additional error info API so
it is tricky to provide the right env for now. *)letenv=Global.env()inletsigma=Evd.from_envenvinquote(Printer.pr_glob_constr_envenvsigmac)++(ifnot(Id.Map.is_emptyvars)thenstrbrk" (with "++prlist_with_seppr_comma(fun(id,c)->Id.printid++str":="++Printer.pr_lconstr_under_binders_envenvsigmac)(List.rev(Id.Map.bindingsvars))++str")"elsemt())inmatchcallswith|[]->mt()|[a]->hov0(str"Ltac call to "++pr_calla++str" failed.")|_->letkind_of_last_call=matchList.lastcallswith|Tacexpr.LtacConstrInterp_->", last term evaluation failed."|_->", last call failed."inhov0(str"In nested Ltac calls to "++pr_enumpr_callcalls++strbrkkind_of_last_call)letskip_extensionstrace=letrecaux=function|(_,Tacexpr.LtacNotationCall_astac)::(_,Tacexpr.LtacMLCall_)::tail->(* Case of an ML defined tactic with entry of the form <<"foo" args>> *)(* see tacextend.mlp *)tac::auxtail|t::tail->t::auxtail|[]->[]inList.rev(aux(List.revtrace))letextract_ltac_trace?loctrace=lettrace=skip_extensionstraceinlet(tloc,c),tail=List.sep_lasttraceinifis_defined_ltactracethen(* We entered a user-defined tactic,
we display the trace with location of the call *)letmsg=hov0(explain_ltac_call_tracectailloc++fnl())in(ifLoc.finerloctlocthenlocelsetloc),msgelse(* We entered a primitive tactic, we don't display trace but
report on the finest location *)letbest_loc=(* trace is with innermost call coming first *)letrecauxbest_loc=function|(loc,_)::tail->ifOption.is_emptybest_loc||not(Option.is_emptyloc)&&Loc.finerlocbest_locthenauxloctailelseauxbest_loctail|[]->best_locinauxloctraceinbest_loc,mt()letget_ltac_traceinfo=letltac_trace=Exninfo.getinfoltac_trace_infoinletloc=Loc.get_locinfoinmatchltac_tracewith|None->None|Sometrace->Some(extract_ltac_trace?loctrace)let()=CErrors.register_additional_error_infoget_ltac_trace