123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537(************************************************************************)(* * 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) *)(************************************************************************)openPpletprint_emacs=reffalselettop_stderrx=Format.fprintf!Topfmt.err_ft"@[%a@]%!"pp_withx(* A buffer for the character read from a channel. We store the command
* entered to be able to report errors without pretty-printing. *)typeinput_buffer={mutableprompt:Stm.doc->string;mutablestr:Bytes.t;(* buffer of already read characters *)mutablelen:int;(* number of chars in the buffer *)mutablebols:intlist;(* offsets in str of beginning of lines *)mutabletokens:Pcoq.Parsable.t;(* stream of tokens *)mutablestart:int}(* stream count of the first char of the buffer *)(* Double the size of the buffer. *)letresize_bufferibuf=letopenBytesinletnstr=create(2*lengthibuf.str+1)inblitibuf.str0nstr0(lengthibuf.str);ibuf.str<-nstr(* Delete all irrelevant lines of the input buffer. Keep the last line
in the buffer (useful when there are several commands on the same line). *)letresynch_bufferibuf=matchibuf.bolswith|ll::_->letnew_len=ibuf.len-llinBytes.blitibuf.strllibuf.str0new_len;ibuf.len<-new_len;ibuf.bols<-[];ibuf.start<-ibuf.start+ll|_->()(* emacs special prompt tag for easy detection. No special character,
to avoid interfering with utf8. Compatibility code removed. *)letemacs_prompt_startstring()=if!print_emacsthen"<prompt>"else""letemacs_prompt_endstring()=if!print_emacsthen"</prompt>"else""(* Read a char in an input channel, displaying a prompt at every
beginning of line. *)letprompt_chardocicibufcount=letbol=matchibuf.bolswith|ll::_->Int.equalibuf.lenll|[]->Int.equalibuf.len0inifbol&¬!print_emacsthentop_stderr(str(ibuf.promptdoc));tryletc=input_charicinifc=='\n'thenibuf.bols<-(ibuf.len+1)::ibuf.bols;ifibuf.len==Bytes.lengthibuf.strthenresize_bufferibuf;Bytes.setibuf.stribuf.lenc;ibuf.len<-ibuf.len+1;SomecwithEnd_of_file->None(* Functions to print underlined locations from an input buffer. *)moduleTopErr=struct(* Given a location, returns the list of locations of each line. The last
line is returned separately. It also checks the location bounds. *)letget_bols_of_locibuf(bp,ep)=letadd_line(b,e)lines=ifb<0||e<bthenCErrors.anomaly(Pp.str"Bad location.");matchlineswith|([],None)->([],Some(b,e))|(fl,oe)->((b,e)::fl,oe)inletreclines_recbaafter=function|[]->add_line(0,ba)after|ll::_whenll<=bp->add_line(ll,ba)after|ll::fl->letnafter=ifll<epthenadd_line(ll,ba)afterelseafterinlines_recllnafterflinlet(fl,ll)=lines_recibuf.len([],None)ibuf.bolsin(fl,Option.getll)letdotted_location(b,e)=ife-b<3then("",String.make(e-b)' ')else(String.make(e-b-1)'.'," ")letblanch_utf8_stringsbpep=letopenBytesinlets'=make(ep-bp)' 'inletj=ref0infori=bptoep-1doletn=Char.code(getsi)in(* Heuristic: assume utf-8 chars are printed using a single
fixed-size char and therefore contract all utf-8 code into one
space; in any case, preserve tabulation so
that its effective interpretation in terms of spacing is preserved *)ifgetsi=='\t'thensets'!j'\t';ifn<0x80||0xC0<=nthenincrjdone;Bytes.sub_strings'0!jletadjust_loc_bufibloc=letopenLocin{locwithep=loc.ep-ib.start;bp=loc.bp-ib.start}letprint_highlight_locationibloc=let(bp,ep)=Loc.unloclocinlethighlight_lines=matchget_bols_of_locib(bp,ep)with|([],(bl,el))->letshift=blanch_utf8_stringib.strblbpinletspan=String.length(blanch_utf8_stringib.strbpep)in(str"> "++str(Bytes.sub_stringib.strbl(el-bl-1))++fnl()++str"> "++str(shift)++str(String.makespan'^'))|((b1,e1)::ml,(bn,en))->let(d1,s1)=dotted_location(b1,bp)inlet(dn,sn)=dotted_location(ep,en)inletl1=(str"> "++strd1++strs1++str(Bytes.sub_stringib.strbp(e1-bp)))inletli=prlist(fun(bi,ei)->(str"> "++str(Bytes.sub_stringib.strbi(ei-bi))))mlinletln=(str"> "++str(Bytes.sub_stringib.strbn(ep-bn))++strsn++strdn)in(l1++li++ln)inhighlight_linesletvalid_buffer_locibloc=let(b,e)=Loc.unloclocinb-ib.start>=0&&e-ib.start<ib.len&&b<=e(* Toplevel error explanation. *)leterror_info_for_buffer?locbuf=matchlocwith|None->Topfmt.pr_phase?loc()|Someloc->letfname=loc.Loc.fnamein(* We are in the toplevel *)matchfnamewith|Loc.ToplevelInput->letnloc=adjust_loc_bufbuflocinifvalid_buffer_locbuflocthenmatchTopfmt.pr_phase~loc:nloc()with|None->None|Somehd->Some(hd++fnl()++print_highlight_locationbufnloc)(* in the toplevel, but not a valid buffer *)elseTopfmt.pr_phase~loc()(* we are in batch mode, don't adjust location *)|Loc.InFile_->Topfmt.pr_phase~loc()(* Actual printing routine *)letprint_error_for_buffer?loclvlmsgbuf=letpre_hdr=error_info_for_buffer?locbufinif!print_emacsthenTopfmt.emacs_logger?pre_hdrlvlmsgelseTopfmt.std_logger?pre_hdrlvlmsg(*
let print_toplevel_parse_error (e, info) buf =
let loc = Loc.get_loc info in
let lvl = Feedback.Error in
let msg = CErrors.iprint (e, info) in
print_error_for_buffer ?loc lvl msg buf
*)end(*s The Coq prompt is the name of the focused proof, if any, and "Coq"
otherwise. We trap all exceptions to prevent the error message printing
from cycling. *)letmake_prompt()=try(Names.Id.to_string(Vernacstate.Declare.get_current_proof_name()))^" < "withVernacstate.Declare.NoCurrentProof->"Coq < "[@@ocaml.warning"-3"](* the coq prompt added to the default one when in emacs mode
The prompt contains the current state label [n] (for global
backtracking) and the current proof state [p] (for proof
backtracking) plus the list of open (nested) proofs (for proof
aborting when backtracking). It looks like:
"n |lem1|lem2|lem3| p < "
*)letmake_emacs_promptdoc=letstatnum=Stateid.to_string(Stm.get_current_state~doc)inletdpth=Stm.current_proof_depth~docinletpending=Stm.get_all_proof_names~docinletpendingprompt=List.fold_left(funaccx->acc^(ifCString.is_emptyaccthen""else"|")^Names.Id.to_stringx)""pendinginletproof_info=ifdpth>=0thenstring_of_intdpthelse"0"inif!print_emacsthenstatnum^" |"^pendingprompt^"| "^proof_info^" < "else""(* A buffer to store the current command read on stdin. It is
* initialized when a vernac command is immediately followed by "\n",
* or after a Drop. *)lettop_buffer=letprdoc=emacs_prompt_startstring()^make_prompt()^make_emacs_promptdoc^emacs_prompt_endstring()in{prompt=pr;str=Bytes.empty;len=0;bols=[];tokens=Pcoq.Parsable.make(Gramlib.Stream.empty());start=0}(* Intialize or reinitialize the char stream *)letreset_input_buffer~state=top_buffer.str<-Bytes.empty;top_buffer.len<-0;top_buffer.bols<-[];top_buffer.tokens<-Pcoq.Parsable.make(Gramlib.Stream.from(prompt_charstate.Vernac.State.docstdintop_buffer));top_buffer.start<-0letset_promptprompt=top_buffer.prompt<-(fundoc->emacs_prompt_startstring()^prompt()^emacs_prompt_endstring())(* Read the input stream until a dot is encountered *)letparse_to_dot=letrecdotkwstatest=matchGramlib.LStream.nextkwstatestwith|Tok.KEYWORD("."|"...")->()|Tok.EOI->()|_->dotkwstatestinPcoq.Entry.(of_parser"Coqtoplevel.dot"{parser_fun=dot})(* If an error occurred while parsing, we try to read the input until a dot
token is encountered.
We assume that when a lexer error occurs, at least one char was eaten *)letrecdiscard_to_dot()=tryPcoq.Entry.parseparse_to_dottop_buffer.tokenswith|CLexer.Error.E_->(* Lexer failed *)discard_to_dot()|ewhenCErrors.noncriticale->()letread_sentence~stateinput=(* XXX: careful with ignoring the state Eugene!*)letopenVernac.StateintryStm.parse_sentence~doc:state.docstate.sid~entry:G_toplevel.vernac_toplevelinputwithreraise->letreraise=Exninfo.capturereraisein(* When typing Ctrl-C, two situations may arise:
- if a lexer/parsing arrived first, the rest of the ill-formed
sentence needs to be discarded, and, if Ctrl-C is found while
trying to discarding (in discard_to_dot), let it bypass the
reporting of the parsing error and report the Sys.Break
instead.
- if a Ctrl-C arrives after a valid start of sentence, do not
discard_to_dot since Ctrl-C is the last read character and
there is nothing left to discard. *)(matchfstreraisewith|Sys.Break->Pp.pp_with!Topfmt.err_ft(Pp.fnl())|_->trydiscard_to_dot()withSys.Break->Pp.pp_with!Topfmt.err_ft(Pp.fnl());raiseSys.Break);(* The caller of read_sentence does the error printing now, this
should be re-enabled once we rely on the feedback error
printer again *)(* TopErr.print_toplevel_parse_error reraise top_buffer; *)Exninfo.iraisereraiseletextract_default_loclocdoc_idsid:Loc.toption=matchlocwith|Some_->loc|None->tryletdoc=Stm.get_docdoc_idinOption.cata(fun{CAst.loc}->loc)NoneStm.(get_ast~docsid)with_->loc(** Coqloop Console feedback handler *)letcoqloop_feed(fb:Feedback.feedback)=letopenFeedbackinmatchfb.contentswith|Processed->()|Incomplete->()|Complete->()|ProcessingIn_->()|InProgress_->()|WorkerStatus(_,_)->()|AddedAxiom->()|GlobRef(_,_,_,_,_)->()|GlobDef(_,_,_,_)->()|FileDependency(_,_)->()|FileLoaded(_,_)->()|Custom(_,_,_)->()(* Re-enable when we switch back to feedback-based error printing *)|Message(Error,loc,msg)->()(* TopErr.print_error_for_buffer ?loc lvl msg top_buffer *)|Message(Warning,loc,msg)->letloc=extract_default_loclocfb.doc_idfb.span_idinTopErr.print_error_for_buffer?locWarningmsgtop_buffer|Message(lvl,loc,msg)->TopErr.print_error_for_buffer?loclvlmsgtop_buffer(** Main coq loop : read vernacular expressions until Drop is entered.
Ctrl-C is handled internally as Sys.Break instead of aborting Coq.
Normally, the only exceptions that can come out of [do_vernac] and
exit the loop are Drop and Quit. Any other exception there indicates
an issue with [print_toplevel_error] above. *)(* Flush in a compatible order with 8.5 *)(* This mimics the semantics of the old Pp.flush_all *)letloop_flush_all()=flushstderr;flushstdout;Format.pp_print_flush!Topfmt.std_ft();Format.pp_print_flush!Topfmt.err_ft()(* Goal equality heuristic. *)letpequalcmp1cmp2(a1,a2)(b1,b2)=cmp1a1b1&&cmp2a2b2letevleqe1e2=CList.equalEvar.equale1e2letcproofp1p2=letProof.{goals=a1;stack=a2;sigma=sigma1}=Proof.datap1inletProof.{goals=b1;stack=b2;sigma=sigma2}=Proof.datap2inevleqa1b1&&CList.equal(pequalevleqevleq)a2b2&&CList.equalEvar.equal(Evd.shelfsigma1)(Evd.shelfsigma2)&&Evar.Set.equal(Evd.given_upsigma1)(Evd.given_upsigma2)(* todo: could add other Set/Unset commands, such as "Printing Universes" *)letprint_anyway_opts=[["Diffs"];]letprint_anywayc=letopenVernacexprinmatchc.exprwith|VernacSynterp(VernacSetOption(_,opt,_))->List.memoptprint_anyway_opts|_->false(* print the proof step, possibly with diffs highlighted, *)letprint_and_diffoldpproof=letoutput=ifProof_diffs.show_diffs()thentryPrinter.pr_open_subgoals~diffs:oldpproofwithPp_diff.Diff_Failuremsg->begin(* todo: print the unparsable string (if we know it) *)Feedback.msg_warningPp.(str("Diff failure: "^msg)++cut()++str"Showing results without diff highlighting");Printer.pr_open_subgoalsproofendelsePrinter.pr_open_subgoalsproofinFeedback.msg_noticeoutput(* We try to behave better when goal printing raises an exception
[usually Ctrl-C]
This is mostly a hack as we should protect printing in a more
generic way, but that'll do for now *)lettop_goal_print~doccoldpnewp=tryletproof_changed=not(Option.equalcproofoldp(Somenewp))inletprint_goals=proof_changed&&Vernacstate.Declare.there_are_pending_proofs()||print_anywaycinifnot!Flags.quiet&&print_goalsthenbeginletdproof=Stm.get_prev_proof~doc(Stm.get_current_state~doc)inprint_and_diffdproofnewpendwith|exn->let(e,info)=Exninfo.captureexninletloc=Loc.get_locinfoinletmsg=CErrors.iprint(e,info)inTopErr.print_error_for_buffer?locFeedback.Errormsgtop_buffer[@@ocaml.warning"-3"]let{Goptions.get=exit_on_error}=letopenGoptionsindeclare_bool_option_and_ref~key:["Coqtop";"Exit";"On";"Error"]~value:false()letshow_proof_diff_cmd~statediff_opt=letopenVernac.Stateinmatchstate.proofwith|None->CErrors.user_err(str"No proofs to diff.")|Someproof->letold=Stm.get_prev_proof~doc:state.docstate.sidinProof_diffs.diff_proofs~diff_opt?oldproofletml_toplevel_state=refNoneletml_toplevel_include_ran=reffalse(* Initialises the Ocaml toplevel before launching it, so that it can
find the "include" file in the *source* directory *)letinit_ocaml_path()=letenv=Boot.Env.init()inletcorelib=Boot.Env.corelibenv|>Boot.Path.to_stringinletadd_subdirdl=Mltop.add_ml_dir(Filename.concatcorelibdl)inList.iteradd_subdir("dev"::Coq_config.all_src_dirs)letinit_and_run_ml_toploop()=init_ocaml_path();Flags.with_optionFlags.in_ml_toplevel(Mltop.ocaml_toploop~init_file:"ml_toplevel/include")()(* We return whether the execution should continue and a new state *)letprocess_toplevel_command~statestm=letopenVernac.StateinletopenG_toplevelinmatchstmwith|VernacDrop->ifMltop.is_ocaml_top()thenbegin(* Save the last state for [go ()] *)ml_toplevel_state:=Somestate;(* Initialise and launch the OCaml toplevel *)init_and_run_ml_toploop();(* Reinitialize the char stream *)reset_input_buffer~state;(* [go ()] was potentially executed — get the new state *)letstate=Option.get!ml_toplevel_stateintrue,stateendelsebeginFeedback.msg_warning(str"There is no ML toplevel.");true,stateend|VernacBackTobid->letbid=Stateid.of_intbidinletdoc,res=Stm.edit_at~doc:state.docbidinassert(res=Stm.NewTip);true,{statewithdoc;sid=bid}|VernacQuit->false,state|VernacControl{CAst.loc;v=c}->letnstate=Vernac.process_expr~state(CAst.make?locc)inlet()=matchnstate.proofwith|None->()|Someproof->top_goal_print~doc:state.doccstate.proofproofintrue,nstate|VernacShowGoal{gid;sid}->letproof=Stm.get_proof~doc:state.doc(Stateid.of_intsid)inletgoal=Printer.pr_goal_emacs~proofgidsidinlet()=Feedback.msg_noticegoalintrue,state|VernacShowProofDiffsdiff_opt->(* We print nothing if there are no goals left *)ifnot(Proof_diffs.color_enabled())thenCErrors.user_errPp.(str"Show Proof Diffs requires setting the \"-color\" command line argument to \"on\" or \"auto\".")elseletout=show_proof_diff_cmd~statediff_optinFeedback.msg_noticeout;true,stateletread_and_execute~state=tryletinput=top_buffer.tokensinmatchread_sentence~stateinputwith|Somestm->process_toplevel_command~statestm(* End of file *)|None->top_stderr(fnl());false,statewith(* Exception printing should be done by the feedback listener,
however this is not yet ready so we rely on the exception for
now. *)|Sys_blocked_io->(* the parser doesn't like nonblocking mode, cf #10918 *)letmsg=Pp.(strbrk"Coqtop needs the standard input to be in blocking mode."++spc()++str"One way of clearing the non-blocking flag is through Python:"++fnl()++str" import os"++fnl()++str" os.set_blocking(0, True)")inTopErr.print_error_for_bufferFeedback.Errormsgtop_buffer;exit1|any->let(e,info)=Exninfo.captureanyinletloc=Loc.get_locinfoinletmsg=CErrors.iprint(e,info)inTopErr.print_error_for_buffer?locFeedback.Errormsgtop_buffer;ifexit_on_error()thenexit1;true,stateletloop~state=(* Initialize buffer *)reset_input_buffer~state;Flags.without_optionFlags.in_ml_toplevel(fun()->(* The main loop, as a tail-recursive function *)letrecauxstate=loop_flush_all();top_stderr(fnl());letopenVernac.Stateinif!print_emacsthentop_stderr(str(top_buffer.promptstate.doc));resynch_buffertop_buffer;letnew_running,new_state=read_and_execute~state:stateinifnew_runningthen(aux[@ocaml.tailcall])new_stateelsenew_stateinauxstate)()letrun~opts~state=letopenCoqargsinprint_emacs:=opts.config.print_emacs;(* We initialize the console only if we run the toploop_run *)lettl_feed=Feedback.add_feedercoqloop_feedinlet_:Vernac.State.t=loop~stateinFeedback.del_feedertl_feed