(************************************************************************) (* * 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) *) (************************************************************************)
open Pp
let print_emacs = reffalse
let top_stderr x =
Format.fprintf !Topfmt.err_ft "@[%a@]%!" pp_with x
(* A buffer for the character read from a channel. We store the command
* entered to be able to report errors without pretty-printing. *)
type input_buffer = {
mutable prompt : Stm.doc -> string;
mutable str : Bytes.t; (* buffer of already read characters *)
mutable len : int; (* number of chars in the buffer *)
mutable bols : int list; (* offsets in str of beginning of lines *)
mutable stream : (unit,char) Gramlib.Stream.t; (* stream of chars *)
mutable tokens : Procq.Parsable.t; (* stream of tokens *)
mutable start : int } (* stream count of the first char of the buffer *)
(* Double the size of the buffer. *)
let resize_buffer ibuf = letopen Bytes in let nstr = create (2 * length ibuf.str + 1) in
blit ibuf.str 0 nstr 0 (length ibuf.str);
ibuf.str <- nstr
let peek_to_newline ibuf = (* peek to see a newline following the latest command
cf #19355 *) let rec aux n = let l = Gramlib.Stream.npeek () n ibuf.stream in ifList.length l < n then () elsematch CList.last l with
| '\n' -> ()
| ' ' | '\t' -> aux (n+1)
| _ -> (* nonblank character: the latest command is not the last on this line *) () in
aux 1
(* 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). *) let resynch_buffer ibuf = let () = peek_to_newline ibuf in match ibuf.bols with
| ll::_ -> let new_len = ibuf.len - ll in
Bytes.blit ibuf.str ll ibuf.str 0 new_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. *) let emacs_prompt_startstring () = if !print_emacs then""else"" let emacs_prompt_endstring () = if !print_emacs then""else""
(* Read a char in an input channel, displaying a prompt at every
beginning of line. *) let prompt_char doc ic ibuf () = let bol = match ibuf.bols with
| ll::_ -> Int.equal ibuf.len ll
| [] -> Int.equal ibuf.len 0 in if bol && not !print_emacs then top_stderr (str (ibuf.prompt doc)); try let c = input_char ic in if c == '\n'then ibuf.bols <- (ibuf.len+1) :: ibuf.bols; if ibuf.len == Bytes.length ibuf.str then resize_buffer ibuf;
Bytes.set ibuf.str ibuf.len c;
ibuf.len <- ibuf.len + 1;
Some c with End_of_file ->
None
(* Functions to print underlined locations from an input buffer. *)
module TopErr = struct
(* Given a location, returns the list of locations of each line. The last
line is returned separately. It also checks the location bounds. *)
let get_bols_of_loc ibuf (bp,ep) = let add_line (b,e) lines = if b < 0 || e < b then CErrors.anomaly (Pp.str "Bad location."); match lines with
| ([],None) -> ([], Some (b,e))
| (fl,oe) -> ((b,e)::fl, oe) in let rec lines_rec ba after = function
| [] -> add_line (0,ba) after
| ll::_ when ll <= bp -> add_line (ll,ba) after
| ll::fl -> let nafter = if ll < ep then add_line (ll,ba) after else after in
lines_rec ll nafter fl in let bols = match ibuf.bols with
| [] -> [ibuf.len+1] (* no newline at the end of the command, pretend there was one *)
| _ :: _ as bols -> bols in let (fl,ll) = lines_rec ibuf.len ([],None) bols in
(fl,Option.get ll)
let dotted_location (b,e) = if e-b < 3 then
("", String.make (e-b) ' ') else
(String.make (e-b-1) '.', " ")
let blanch_utf8_string s bp ep = letopen Bytes in let s' = make (ep-bp) '' in let j = ref 0 in
for i = bp to ep - 1 do let n = Char.code (get s i) 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 *) if get s i == '\t'thenset s' !j '\t'; if n < 0x80 || 0xC0 <= n then incr j
done;
Bytes.sub_string s' 0 !j
let adjust_loc_buf ib loc = letopen Loc in
{ loc with ep = loc.ep - ib.start; bp = loc.bp - ib.start }
let print_highlight_location ib loc = let (bp,ep) = Loc.unloc loc in let highlight_lines = match get_bols_of_loc ib (bp,ep) with
| ([],(bl,el)) -> let shift = blanch_utf8_string ib.str bl bp in let span = String.length (blanch_utf8_string ib.str bp ep) in
(str"> " ++ str(Bytes.sub_string ib.str bl (el-bl-1)) ++ fnl () ++
str"> " ++ str(shift) ++ str(String.make span '^'))
| ((b1,e1)::ml,(bn,en)) -> let (d1,s1) = dotted_location (b1,bp) in let (dn,sn) = dotted_location (ep,en) in let l1 = (str"> " ++ str d1 ++ str s1 ++
str(Bytes.sub_string ib.str bp (e1-bp))) in let li =
prlist (fun (bi,ei) ->
(str"> " ++ str(Bytes.sub_string ib.str bi (ei-bi)))) ml in let ln = (str"> " ++ str(Bytes.sub_string ib.str bn (ep-bn)) ++
str sn ++ str dn) in
(l1 ++ li ++ ln) in
highlight_lines
let valid_buffer_loc ib loc = let (b,e) = Loc.unloc loc in b-ib.start >= 0 && e-ib.start <= ib.len && b<=e
(* Toplevel error explanation. *) let error_info_for_buffer ?loc buf = match loc with
| None -> Topfmt.pr_phase ?loc ()
| Some loc -> let fname = loc.Loc.fname in (* We are in the toplevel *) match fname with
| Loc.ToplevelInput -> let nloc = adjust_loc_buf buf loc in if valid_buffer_loc buf loc then match Topfmt.pr_phase ~loc:nloc () with
| None -> None
| Some hd -> Some (hd ++ fnl () ++ print_highlight_location buf nloc) (* in the toplevel, but not a valid buffer *) else Topfmt.pr_phase ~loc () (* we are in batch mode, don't adjust location *)
| Loc.InFile _ -> Topfmt.pr_phase ~loc ()
(* Actual printing routine *) let print_error_for_buffer ?loc ?qf lvl msg buf = let pre_hdr = error_info_for_buffer ?loc buf in if !print_emacs then Topfmt.emacs_logger ?pre_hdr lvl msg else Topfmt.std_logger ?pre_hdr ?qf lvl msg
(* 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 Rocq prompt is the name of the focused proof, if any, and "Rocq" otherwise. We trap all exceptions to prevent the error message printing
from cycling. *) let make_prompt () = try
(Names.Id.to_string (Vernacstate.Declare.get_current_proof_name ())) ^ " < " with Vernacstate.Declare.NoCurrentProof -> "Rocq < "
[@@ocaml.warning "-3"]
(* the rocq 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 < "
*) let make_emacs_prompt doc = let statnum = Stateid.to_string (Stm.get_current_state ~doc) in let dpth = Stm.current_proof_depth ~doc in let pending = Stm.get_all_proof_names ~doc in let pendingprompt = List.fold_left
(fun acc x -> acc ^ (if CString.is_empty acc then""else"|") ^ Names.Id.to_string x) "" pending in let proof_info = if dpth >= 0 then string_of_int dpth else"0"in if !print_emacs then statnum ^ " |" ^ 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. *) let top_buffer = let pr doc =
emacs_prompt_startstring()
^ make_prompt()
^ make_emacs_prompt doc
^ emacs_prompt_endstring() in let stream = Gramlib.Stream.empty () in
{ prompt = pr;
str = Bytes.empty;
len = 0;
bols = [];
stream;
tokens = Procq.Parsable.make stream;
start = 0 }
(* Intialize or reinitialize the char stream *) let reset_input_buffer ~state = let stream = Gramlib.Stream.from (prompt_char state.Vernac.State.doc stdin top_buffer) in
top_buffer.str <- Bytes.empty;
top_buffer.len <- 0;
top_buffer.bols <- [];
top_buffer.stream <- stream;
top_buffer.tokens <- Procq.Parsable.make stream;
top_buffer.start <- 0
(* Read the input stream until a dot is encountered *) let parse_to_dot = let rec dot kwstate st = match Gramlib.LStream.next kwstate st with
| Tok.KEYWORD ("."|"...") -> ()
| Tok.EOI -> ()
| _ -> dot kwstate st in
Procq.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 *)
let rec discard_to_dot () = try
Procq.Entry.parse parse_to_dot top_buffer.tokens with
| CLexer.Error.E _ -> (* Lexer failed *) discard_to_dot ()
| e when CErrors.noncritical e -> ()
let read_sentence ~state input = (* XXX: careful with ignoring the state Eugene!*) letopen Vernac.State in try Stm.parse_sentence ~doc:state.doc state.sid ~entry:G_toplevel.vernac_toplevel input with reraise -> let reraise = Exninfo.capture reraise in (* 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. *)
(match fst reraise with
| Sys.Break -> Pp.pp_with !Topfmt.err_ft (Pp.fnl ())
| _ -> try discard_to_dot () with Sys.Break ->
Pp.pp_with !Topfmt.err_ft (Pp.fnl ()); raise Sys.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.iraise reraise
let extract_default_loc loc doc_id sid : Loc.t option = match loc with
| Some _ -> loc
| None -> try let doc = Stm.get_doc doc_id in Option.cata (fun {CAst.loc} -> loc) None Stm.(get_ast ~doc sid) with _ -> loc
(** Main rocq loop : read vernacular expressions until Drop is entered. Ctrl-C is handled internally as Sys.Break instead of aborting Rocq. 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 *) let loop_flush_all () =
flush stderr;
flush stdout;
Format.pp_print_flush !Topfmt.std_ft ();
Format.pp_print_flush !Topfmt.err_ft ()
(* Goal equality heuristic. *) let pequal cmp1 cmp2 (a1,a2) (b1,b2) = cmp1 a1 b1 && cmp2 a2 b2 let evleq e1 e2 = CList.equal Evar.equal e1 e2 let cproof p1 p2 = let Proof.{goals=a1;stack=a2;sigma=sigma1} = Proof.data p1 in let Proof.{goals=b1;stack=b2;sigma=sigma2} = Proof.data p2 in
evleq a1 b1 &&
CList.equal (pequal evleq evleq) a2 b2 &&
CList.equal Evar.equal (Evd.shelf sigma1) (Evd.shelf sigma2) &&
Evar.Set.equal (Evd.given_up sigma1) (Evd.given_up sigma2)
(* todo: could add other Set/Unset commands, such as "Printing Universes" *) let print_anyway_opts = [
[ "Diffs" ];
]
let print_anyway c = letopen Vernacexpr in match c.expr with
| VernacSynterp (VernacSetOption (_, opt, _)) -> List.mem opt print_anyway_opts
| _ -> false
(* print the proof step, possibly with diffs highlighted, *) let print_and_diff oldp proof = let output = if Proof_diffs.show_diffs () then try Printer.pr_open_subgoals ~diffs:oldp proof with Pp_diff.Diff_Failure msg -> begin (* todo: print the unparsable string (if we know it) *)
Feedback.msg_warning Pp.(str ("Diff failure: " ^ msg) ++ cut()
++ str "Showing results without diff highlighting" );
Printer.pr_open_subgoals proof end else
Printer.pr_open_subgoals proof in
Feedback.msg_notice output
(* 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 *) let top_goal_print ~doc c oldp newp = try let proof_changed = not (Option.equal cproof oldp (Some newp)) in let print_goals = proof_changed && Vernacstate.Declare.there_are_pending_proofs () ||
print_anyway c in ifnot !Flags.quiet && print_goals thenbegin let dproof = Stm.get_prev_proof ~doc (Stm.get_current_state ~doc) in
print_and_diff dproof newp end with
| exn -> let (e, info) = Exninfo.capture exn in let loc = Loc.get_loc info in let msg = CErrors.iprint (e, info) in
TopErr.print_error_for_buffer ?loc Feedback.Error msg top_buffer
[@@ocaml.warning "-3"]
let { Goptions.get = exit_on_error } = letopen Goptions in
declare_bool_option_and_ref
~key:["Coqtop";"Exit";"On";"Error"]
~value:false
()
let show_proof_diff_cmd ~state diff_opt = letopen Vernac.State in match state.proof with
| None -> CErrors.user_err (str "No proofs to diff.")
| Some proof -> let old = Stm.get_prev_proof ~doc:state.doc state.sid in
Proof_diffs.diff_proofs ~diff_opt ?old proof
let ml_toplevel_state = ref None let ml_toplevel_include_ran = reffalse
(* Initialises the Ocaml toplevel before launching it, so that it can
find the "include" file in the *source* directory *) let init_ocaml_path () = let corelib = Findlib.package_directory "rocq-runtime"in let add_subdir dl = Mltop.add_ml_dir (Filename.concat corelib dl) in List.iter add_subdir ("dev" :: Coq_config.all_src_dirs)
(* We return whether the execution should continue and a new state *) let process_toplevel_command ~state stm = letopen Vernac.State in letopen G_toplevel in match stm with
| VernacDrop -> if Mltop.is_ocaml_top() thenbegin (* Save the last state for [go ()] *)
ml_toplevel_state := Some state; (* 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 *) let state = Option.get !ml_toplevel_state in true, state endelsebegin
Feedback.msg_warning (str "There is no ML toplevel."); true, state end
| VernacBackTo bid -> let bid = Stateid.of_int bid in let doc, res = Stm.edit_at ~doc:state.doc bid in
assert (res = Stm.NewTip); true, { state with doc; sid = bid }
| VernacQuit -> false, state
| VernacControl { CAst.loc; v=c } -> let nstate = Vernac.process_expr ~state (CAst.make ?loc c) in let () = match nstate.proof with
| None -> ()
| Some proof -> top_goal_print ~doc:state.doc c state.proof proof in true, nstate
| VernacShowGoal { gid; sid } -> let proof = Stm.get_proof ~doc:state.doc (Stateid.of_int sid) in let goal = Printer.pr_goal_emacs ~proof gid sid in let () = Feedback.msg_notice goal in true, state
| VernacShowProofDiffs diff_opt -> (* We print nothing if there are no goals left *) ifnot (Proof_diffs.color_enabled ()) then
CErrors.user_err Pp.(str "Show Proof Diffs requires setting the \"-color\" command line argument to \"on\" or \"auto\".") else let out = show_proof_diff_cmd ~state diff_opt in
Feedback.msg_notice out; true, state
let read_and_execute ~state = try let input = top_buffer.tokens in match read_sentence ~state input with
| Some stm ->
process_toplevel_command ~state stm (* End of file *)
| None ->
top_stderr (fnl ()); false, state with (* 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 *) let msg =
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)") in
TopErr.print_error_for_buffer Feedback.Error msg top_buffer;
exit 1
| any -> let (e, info) = Exninfo.capture any in let loc = Loc.get_loc info in let msg = CErrors.iprint (e, info) in
TopErr.print_error_for_buffer ?loc Feedback.Error msg top_buffer; if exit_on_error () then exit 1; true, state
let loop ~state = (* Initialize buffer *)
reset_input_buffer ~state;
Flags.without_option Flags.in_ml_toplevel (fun () -> (* The main loop, as a tail-recursive function *) let rec aux state =
loop_flush_all ();
top_stderr (fnl()); letopen Vernac.State in if !print_emacs then top_stderr (str (top_buffer.prompt state.doc));
resynch_buffer top_buffer; let new_running, new_state = read_and_execute ~state:state in if new_running then
(aux [@ocaml.tailcall]) new_state else
new_state in
aux state
) ()
let run ~opts ~state = letopen Coqargs in
print_emacs := opts.config.print_emacs; (* We initialize the console only if we run the toploop_run *) let tl_feed = Feedback.add_feeder coqloop_feed in let _ : Vernac.State.t = loop ~state in
Feedback.del_feeder tl_feed
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.