diff options
author | Enrico Tassi <gareuselesinge@debian.org> | 2015-01-25 14:42:51 +0100 |
---|---|---|
committer | Enrico Tassi <gareuselesinge@debian.org> | 2015-01-25 14:42:51 +0100 |
commit | 7cfc4e5146be5666419451bdd516f1f3f264d24a (patch) | |
tree | e4197645da03dc3c7cc84e434cc31d0a0cca7056 /toplevel/coqloop.ml | |
parent | 420f78b2caeaaddc6fe484565b2d0e49c66888e5 (diff) |
Imported Upstream version 8.5~beta1+dfsg
Diffstat (limited to 'toplevel/coqloop.ml')
-rw-r--r-- | toplevel/coqloop.ml | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/toplevel/coqloop.ml b/toplevel/coqloop.ml new file mode 100644 index 00000000..52fa9e01 --- /dev/null +++ b/toplevel/coqloop.ml @@ -0,0 +1,360 @@ +(************************************************************************) +(* v * The Coq Proof Assistant / The Coq Development Team *) +(* <O___,, * INRIA - CNRS - LIX - LRI - PPS - Copyright 1999-2015 *) +(* \VV/ **************************************************************) +(* // * This file is distributed under the terms of the *) +(* * GNU Lesser General Public License Version 2.1 *) +(************************************************************************) + +open Pp +open Errors +open Util +open Flags +open Vernac +open Pcoq + +(* 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 : unit -> string; + mutable str : string; (* 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 tokens : Gram.parsable; (* 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 = + let nstr = String.create (2 * String.length ibuf.str + 1) in + String.blit ibuf.str 0 nstr 0 (String.length ibuf.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. *) + +let resynch_buffer ibuf = + match ibuf.bols with + | ll::_ -> + let new_len = ibuf.len - ll in + String.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() = Printer.emacs_str "<prompt>" + +let emacs_prompt_endstring() = Printer.emacs_str "</prompt>" + +(* Read a char in an input channel, displaying a prompt at every + beginning of line. *) +let prompt_char ic ibuf count = + let bol = match ibuf.bols with + | ll::_ -> Int.equal ibuf.len ll + | [] -> Int.equal ibuf.len 0 + in + if bol && not !print_emacs then msgerr (str (ibuf.prompt())); + try + let c = input_char ic in + if c == '\n' then ibuf.bols <- (ibuf.len+1) :: ibuf.bols; + if ibuf.len == String.length ibuf.str then resize_buffer ibuf; + ibuf.str.[ibuf.len] <- c; + ibuf.len <- ibuf.len + 1; + Some c + with End_of_file -> + None + +(* Reinitialize the char stream (after a Drop) *) + +let reset_input_buffer ic ibuf = + ibuf.str <- ""; + ibuf.len <- 0; + ibuf.bols <- []; + ibuf.tokens <- Gram.parsable (Stream.from (prompt_char ic ibuf)); + ibuf.start <- 0 + +(* Functions to print underlined locations from an input buffer. *) + +(* 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 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 (fl,ll) = lines_rec ibuf.len ([],None) ibuf.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 = + let s' = String.make (ep-bp) ' ' in + let j = ref 0 in + for i = bp to ep - 1 do + let n = Char.code 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 s.[i] == '\t' then s'.[!j] <- '\t'; + if n < 0x80 || 0xC0 <= n then incr j + done; + String.sub s' 0 !j + +let print_highlight_location ib loc = + let (bp,ep) = Loc.unloc loc in + let bp = bp - ib.start + and ep = ep - ib.start 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(String.sub 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(String.sub ib.str bp (e1-bp))) in + let li = + prlist (fun (bi,ei) -> + (str"> " ++ str(String.sub ib.str bi (ei-bi)))) ml in + let ln = (str"> " ++ str(String.sub ib.str bn (ep-bn)) ++ + str sn ++ str dn) in + (l1 ++ li ++ ln) + in + let loc = Loc.make_loc (bp,ep) in + (str"Toplevel input, characters " ++ Cerrors.print_loc loc ++ str":" ++ fnl () ++ + highlight_lines ++ fnl ()) + +(* Functions to report located errors in a file. *) + +let print_location_in_file {outer=s;inner=fname} loc = + let errstrm = str"Error while reading " ++ str s in + if Loc.is_ghost loc then + hov 1 (errstrm ++ spc() ++ str" (unknown location):") ++ fnl () + else + let errstrm = + if String.equal s fname then mt() else errstrm ++ str":" ++ fnl() + in + let (bp,ep) = Loc.unloc loc in + let line_of_pos lin bol cnt = + try + let ic = open_in fname in + let rec line_of_pos lin bol cnt = + if cnt < bp then + if input_char ic == '\n' + then line_of_pos (lin + 1) (cnt +1) (cnt+1) + else line_of_pos lin bol (cnt+1) + else (lin, bol) + in + let rc = line_of_pos lin bol cnt in + close_in ic; + rc + with Sys_error _ -> 0, 0 in + try + let (line, bol) = line_of_pos 1 0 0 in + hov 0 (* No line break so as to follow emacs error message format *) + (errstrm ++ str"File " ++ str ("\""^fname^"\"") ++ + str", line " ++ int line ++ str", characters " ++ + Cerrors.print_loc (Loc.make_loc (bp-bol,ep-bol))) ++ str":" ++ + fnl () + with e when Errors.noncritical e -> + hov 1 (errstrm ++ spc() ++ str"(invalid location):") ++ fnl () + +let valid_buffer_loc ib loc = + not (Loc.is_ghost loc) && + let (b,e) = Loc.unloc loc in b-ib.start >= 0 && e-ib.start < ib.len && b<=e + +(*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. *) +let make_prompt () = + try + (Names.Id.to_string (Pfedit.get_current_proof_name ())) ^ " < " + with Proof_global.NoCurrentProof -> + "Coq < " + +(*let build_pending_list l = + let pl = ref ">" in + let l' = ref l in + let res = + while List.length !l' > 1 do + pl := !pl ^ "|" Names.Id.to_string x; + l':=List.tl !l' + done in + let last = try List.hd !l' with _ -> in + "<"^l' +*) + +(* 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 < " +*) +let make_emacs_prompt() = + let statnum = Stateid.to_string (Stm.get_current_state ()) in + let dpth = Stm.current_proof_depth() in + let pending = Stm.get_all_proof_names() in + let pendingprompt = + List.fold_left + (fun acc x -> acc ^ (if String.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 !Flags.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() = + emacs_prompt_startstring() + ^ make_prompt() + ^ make_emacs_prompt() + ^ emacs_prompt_endstring() + in + { prompt = pr; + str = ""; + len = 0; + bols = []; + tokens = Gram.parsable (Stream.of_list []); + start = 0 } + +let set_prompt prompt = + top_buffer.prompt + <- (fun () -> + emacs_prompt_startstring() + ^ prompt () + ^ emacs_prompt_endstring()) + +(* The following exceptions need not be located. *) + +let locate_exn = function + | Out_of_memory | Stack_overflow | Sys.Break -> false + | _ -> true + +(* Toplevel error explanation. *) + +let print_toplevel_error (e, info) = + let loc = Option.default Loc.ghost (Loc.get_loc info) in + let locmsg = match Vernac.get_exn_files info with + | Some files -> print_location_in_file files loc + | None -> + if locate_exn e && valid_buffer_loc top_buffer loc then + print_highlight_location top_buffer loc + else mt () + in + locmsg ++ Errors.iprint (e, info) + +(* Read the input stream until a dot is encountered *) +let parse_to_dot = + let rec dot st = match Compat.get_tok (Stream.next st) with + | Tok.KEYWORD ("."|"...") -> () + | Tok.EOI -> raise End_of_input + | _ -> dot st + in + Gram.Entry.of_parser "Coqtoplevel.dot" 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 + Gram.entry_parse parse_to_dot top_buffer.tokens + with + | Compat.Token.Error _ | Lexer.Error.E _ -> discard_to_dot () + | End_of_input -> raise End_of_input + | e when Errors.noncritical e -> () + +let read_sentence () = + try Vernac.parse_sentence (top_buffer.tokens, None) + with reraise -> + let reraise = Errors.push reraise in + discard_to_dot (); + iraise reraise + +(** [do_vernac] reads and executes a toplevel phrase, and print error + messages when an exception is raised, except for the following: + - Drop: kill the Coq toplevel, going down to the Caml toplevel if it exists. + Otherwise, exit. + - End_of_input: Ctrl-D was typed in, we will quit. + + In particular, this is normally the only place where a Sys.Break + is caught and handled (i.e. not re-raised). +*) + +let do_vernac () = + msgerrnl (mt ()); + if !print_emacs then msgerr (str (top_buffer.prompt())); + resynch_buffer top_buffer; + try + Vernac.eval_expr (read_sentence ()) + with + | End_of_input | Errors.Quit -> + msgerrnl (mt ()); pp_flush(); raise Errors.Quit + | Errors.Drop -> (* Last chance *) + if Mltop.is_ocaml_top() then raise Errors.Drop + else ppnl (str"Error: There is no ML toplevel." ++ fnl ()) + | any -> + let any = Errors.push any in + Format.set_formatter_out_channel stdout; + let msg = print_toplevel_error any ++ fnl () in + pp_with ~pp_tag:Ppstyle.pp_tag !Pp_control.std_ft msg; + pp_flush () + +(** 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. *) + +(* +let feed_emacs = function + | { Interface.id = Interface.State id; + Interface.content = Interface.GlobRef (_,a,_,c,_) } -> + prerr_endline ("<info>" ^"<id>"^Stateid.to_string id ^"</id>" + ^a^" "^c^ "</info>") + | _ -> () +*) + +let rec loop () = + Sys.catch_break true; + if !Flags.print_emacs then Vernacentries.qed_display_script := false; + Flags.coqtop_ui := true; + try + reset_input_buffer stdin top_buffer; + while true do do_vernac(); flush_all() done + with + | Errors.Drop -> () + | Errors.Quit -> exit 0 + | any -> + msgerrnl (str"Anomaly: main loop exited with exception: " ++ + str (Printexc.to_string any) ++ + fnl() ++ str"Please report."); + loop () |