aboutsummaryrefslogtreecommitdiff
path: root/ide/coqOps.ml
diff options
context:
space:
mode:
authorletouzey2012-12-10 16:35:28 +0000
committerletouzey2012-12-10 16:35:28 +0000
commit2de75892efb8c2ab63a3b23767d0cefd0996f8d6 (patch)
tree0cc24f4b3e703c7ab57d9455598880b02de109d7 /ide/coqOps.ml
parent6416f0d41e46aaa64af50aa20dfb324db242286a (diff)
Coqide: some more refactoring to lighten coqide.ml
Main victim is analyzed_view : - some unnecessary methods have been killed (hep_for_keyword for instance) - some other migrated elsewhere (recenter_input, find_next_occurrence, ...) - analyzed_view is now split in two : fileops (filename, save, revert, ...) and coqops (process_next_phrase, ...) Four new files created: - Sentence (for tag_on_insert and alii) - FileOps (ex-first-half of analyzed_view) - CoqOps (ex-second-half of analyzed_view) - Session (ex-record viewable_script and functions about it) Also lots of renaming, trying to be shorter (but still meaningful) and more uniform git-svn-id: svn+ssh://scm.gforge.inria.fr/svn/coq/trunk@16057 85f007b7-540e-0410-9357-904b9bb8a0f7
Diffstat (limited to 'ide/coqOps.ml')
-rw-r--r--ide/coqOps.ml432
1 files changed, 432 insertions, 0 deletions
diff --git a/ide/coqOps.ml b/ide/coqOps.ml
new file mode 100644
index 0000000000..6d99077bea
--- /dev/null
+++ b/ide/coqOps.ml
@@ -0,0 +1,432 @@
+(************************************************************************)
+(* v * The Coq Proof Assistant / The Coq Development Team *)
+(* <O___,, * INRIA - CNRS - LIX - LRI - PPS - Copyright 1999-2012 *)
+(* \VV/ **************************************************************)
+(* // * This file is distributed under the terms of the *)
+(* * GNU Lesser General Public License Version 2.1 *)
+(************************************************************************)
+
+open Ideutils
+
+type flag = [ `COMMENT | `UNSAFE ]
+
+type ide_info = {
+ start : GText.mark;
+ stop : GText.mark;
+ flags : flag list;
+}
+
+let prefs = Preferences.current
+
+class type ops =
+object
+ method go_to_insert : Coq.task
+ method tactic_wizard : string list -> Coq.task
+ method process_next_phrase : Coq.task
+ method process_until_end_or_error : Coq.task
+ method handle_reset_initial : Coq.reset_kind -> Coq.task
+ method raw_coq_query : string -> Coq.task
+ method show_goals : Coq.task
+ method backtrack_last_phrase : Coq.task
+ method include_file_dir_in_path : Coq.task
+end
+
+class coqops
+ (_script:Wg_ScriptView.script_view)
+ (_pv:Wg_ProofView.proof_view)
+ (_mv:Wg_MessageView.message_view)
+ get_filename =
+object(self)
+ val script = _script
+ val buffer = (_script#source_buffer :> GText.buffer)
+ val proof = _pv
+ val messages = _mv
+
+ val cmd_stack = Stack.create ()
+
+ method private get_start_of_input =
+ buffer#get_iter_at_mark (`NAME "start_of_input")
+
+ method private get_insert =
+ buffer#get_iter_at_mark `INSERT
+
+ method show_goals h k =
+ Coq.PrintOpt.set_printing_width proof#width;
+ Coq.goals h (function
+ |Interface.Fail (l, str) ->
+ (messages#set ("Error in coqtop:\n"^str); k())
+ |Interface.Good goals | Interface.Unsafe goals ->
+ Coq.evars h (function
+ |Interface.Fail (l, str)->
+ (messages#set ("Error in coqtop:\n"^str); k())
+ |Interface.Good evs | Interface.Unsafe evs ->
+ proof#set_goals goals;
+ proof#set_evars evs;
+ proof#refresh ();
+ k()))
+
+ (* This method is intended to perform stateless commands *)
+ method raw_coq_query phrase h k =
+ let () = Minilib.log "raw_coq_query starting now" in
+ let display_error s =
+ if not (Glib.Utf8.validate s) then
+ flash_info "This error is so nasty that I can't even display it."
+ else messages#add s;
+ in
+ Coq.interp ~logger:messages#push ~raw:true ~verbose:false phrase h
+ (function
+ | Interface.Fail (_, err) -> display_error err; k ()
+ | Interface.Good msg | Interface.Unsafe msg ->
+ messages#add msg; k ())
+
+ (** [fill_command_queue until q] fills a command queue until the [until]
+ condition returns true; it is fed with the number of phrases read and the
+ iters enclosing the current sentence. *)
+ method private fill_command_queue until queue =
+ let rec loop len iter =
+ match Sentence.find buffer iter with
+ | None -> raise Exit
+ | Some (start, stop) ->
+ if until len start stop then raise Exit;
+ buffer#apply_tag Tags.Script.to_process ~start ~stop;
+ (* Check if this is a comment *)
+ let is_comment =
+ stop#backward_char#has_tag Tags.Script.comment_sentence
+ in
+ let payload = {
+ start = `MARK (buffer#create_mark start);
+ stop = `MARK (buffer#create_mark stop);
+ flags = if is_comment then [`COMMENT] else [];
+ } in
+ Queue.push payload queue;
+ if not stop#is_end then loop (succ len) stop
+ in
+ try loop 0 self#get_start_of_input with Exit -> ()
+
+ method private discard_command_queue queue =
+ while not (Queue.is_empty queue) do
+ let sentence = Queue.pop queue in
+ let start = buffer#get_iter_at_mark sentence.start in
+ let stop = buffer#get_iter_at_mark sentence.stop in
+ buffer#remove_tag Tags.Script.to_process ~start ~stop;
+ buffer#delete_mark sentence.start;
+ buffer#delete_mark sentence.stop;
+ done
+
+ method private commit_queue_transaction queue sentence newflags =
+ (* A queued command has been successfully done, we push it to [cmd_stack].
+ We reget the iters here because Gtk is unable to warranty that they
+ were not modified meanwhile. Not really necessary but who knows... *)
+ let start = buffer#get_iter_at_mark sentence.start in
+ let stop = buffer#get_iter_at_mark sentence.stop in
+ let sentence = { sentence with flags = newflags @ sentence.flags } in
+ let tag =
+ if List.mem `UNSAFE newflags then Tags.Script.unjustified
+ else Tags.Script.processed
+ in
+ buffer#move_mark ~where:stop (`NAME "start_of_input");
+ buffer#apply_tag tag ~start ~stop;
+ buffer#remove_tag Tags.Script.to_process ~start ~stop;
+ ignore (Queue.pop queue);
+ Stack.push sentence cmd_stack
+
+ method private process_error queue phrase loc msg h k =
+ let position_error = function
+ | None -> ()
+ | Some (start, stop) ->
+ let soi = self#get_start_of_input in
+ let start =
+ soi#forward_chars (byte_offset_to_char_offset phrase start) in
+ let stop =
+ soi#forward_chars (byte_offset_to_char_offset phrase stop) in
+ buffer#apply_tag Tags.Script.error ~start ~stop;
+ buffer#place_cursor ~where:start
+ in
+ self#discard_command_queue queue;
+ pop_info ();
+ position_error loc;
+ messages#clear;
+ messages#push Interface.Error msg;
+ self#show_goals h k
+
+ (** Compute the phrases until [until] returns [true]. *)
+ method private process_until until verbose h k =
+ let queue = Queue.create () in
+ (* Lock everything and fill the waiting queue *)
+ push_info "Coq is computing";
+ messages#clear;
+ script#set_editable false;
+ self#fill_command_queue until queue;
+ (* Now unlock and process asynchronously *)
+ script#set_editable true;
+ let push_info lvl msg = if verbose then messages#push lvl msg
+ in
+ Minilib.log "Begin command processing";
+ let rec loop () =
+ if Queue.is_empty queue then
+ let () = pop_info () in
+ let () = script#recenter_insert in
+ self#show_goals h k
+ else
+ let sentence = Queue.peek queue in
+ if List.mem `COMMENT sentence.flags then
+ (self#commit_queue_transaction queue sentence []; loop ())
+ else
+ (* If the line is not a comment, we interpret it. *)
+ let start = buffer#get_iter_at_mark sentence.start in
+ let stop = buffer#get_iter_at_mark sentence.stop in
+ let phrase = start#get_slice ~stop in
+ let commit_and_continue msg flags =
+ push_info Interface.Notice msg;
+ self#commit_queue_transaction queue sentence flags;
+ loop ()
+ in
+ Coq.interp ~logger:push_info ~verbose phrase h
+ (function
+ |Interface.Good msg -> commit_and_continue msg []
+ |Interface.Unsafe msg -> commit_and_continue msg [`UNSAFE]
+ |Interface.Fail (loc, msg) ->
+ self#process_error queue phrase loc msg h k)
+ in
+ loop ()
+
+ method process_next_phrase h k =
+ let until len start stop = 1 <= len in
+ self#process_until until true h
+ (fun () -> buffer#place_cursor ~where:self#get_start_of_input; k())
+
+ method private process_until_iter iter h k =
+ let until len start stop =
+ if prefs.Preferences.stop_before then stop#compare iter > 0
+ else start#compare iter >= 0
+ in
+ self#process_until until false h k
+
+ method process_until_end_or_error h k =
+ self#process_until_iter buffer#end_iter h k
+
+ (** Clear the command stack until [until] returns [true].
+ Returns the number of commands sent to Coqtop to backtrack. *)
+ method private clear_command_stack until =
+ let rec loop len real_len =
+ if Stack.is_empty cmd_stack then real_len
+ else
+ let phrase = Stack.top cmd_stack in
+ let is_comment = List.mem `COMMENT phrase.flags in
+ let start = buffer#get_iter_at_mark phrase.start in
+ let stop = buffer#get_iter_at_mark phrase.stop in
+ if not (until len real_len start stop) then begin
+ (* [until] has not been reached, so we clear this command *)
+ ignore (Stack.pop cmd_stack);
+ buffer#remove_tag Tags.Script.processed ~start ~stop;
+ buffer#remove_tag Tags.Script.unjustified ~start ~stop;
+ buffer#move_mark ~where:start (`NAME "start_of_input");
+ buffer#delete_mark phrase.start;
+ buffer#delete_mark phrase.stop;
+ loop (succ len) (if is_comment then real_len else succ real_len)
+ end else
+ real_len
+ in
+ loop 0 0
+
+ (** Actually performs the undoing *)
+ method private undo_command_stack n h k =
+ Coq.rewind n h (function
+ |Interface.Good n | Interface.Unsafe n ->
+ let until _ len _ _ = n <= len in
+ (* Coqtop requested [n] more ACTUAL backtrack *)
+ ignore (self#clear_command_stack until);
+ k ()
+ |Interface.Fail (l, str) ->
+ messages#set
+ ("Error while backtracking: " ^ str ^
+ "\nCoqIDE and coqtop may be out of sync," ^
+ "you may want to use Restart.");
+ k ())
+
+ (** Wrapper around the raw undo command *)
+ method private backtrack_until until h k =
+ push_info "Coq is undoing";
+ messages#clear;
+ (* Lock everything *)
+ script#set_editable false;
+ let to_undo = self#clear_command_stack until in
+ self#undo_command_stack to_undo h
+ (fun () -> script#set_editable true; pop_info (); k ())
+
+ method private backtrack_to_iter iter h k =
+ let until _ _ _ stop = iter#compare stop >= 0 in
+ self#backtrack_until until h
+ (* We may have backtracked too much: let's replay *)
+ (fun () -> self#process_until_iter iter h k)
+
+ method backtrack_last_phrase h k =
+ let until len _ _ _ = 1 <= len in
+ self#backtrack_until until h
+ (fun () ->
+ buffer#place_cursor ~where:self#get_start_of_input;
+ self#show_goals h k)
+
+ method go_to_insert h k =
+ let point = self#get_insert in
+ if point#compare self#get_start_of_input >= 0
+ then self#process_until_iter point h k
+ else self#backtrack_to_iter point h k
+
+ method tactic_wizard l h k =
+ let insert_phrase phrase tag =
+ let stop = self#get_start_of_input in
+ let phrase' = if stop#starts_line then phrase else "\n"^phrase in
+ buffer#insert ~iter:stop phrase';
+ Sentence.tag_on_insert buffer;
+ let start = self#get_start_of_input in
+ buffer#move_mark ~where:stop (`NAME "start_of_input");
+ buffer#apply_tag tag ~start ~stop;
+ if self#get_insert#compare stop <= 0 then
+ buffer#place_cursor ~where:stop;
+ let ide_payload = {
+ start = `MARK (buffer#create_mark start);
+ stop = `MARK (buffer#create_mark stop);
+ flags = [];
+ } in
+ Stack.push ide_payload cmd_stack;
+ messages#clear;
+ self#show_goals h k;
+ in
+ let display_error (loc, s) =
+ if not (Glib.Utf8.validate s) then
+ flash_info "This error is so nasty that I can't even display it."
+ else messages#add s
+ in
+ let try_phrase phrase stop more =
+ Minilib.log "Sending to coq now";
+ Coq.interp ~verbose:false phrase h
+ (function
+ |Interface.Fail (l, str) ->
+ display_error (l, str);
+ messages#add ("Unsuccessfully tried: "^phrase);
+ more ()
+ |Interface.Good msg ->
+ messages#add msg;
+ stop Tags.Script.processed
+ |Interface.Unsafe msg ->
+ messages#add msg;
+ stop Tags.Script.unjustified)
+ in
+ let rec loop l () = match l with
+ | [] -> k ()
+ | p :: l' ->
+ try_phrase ("progress "^p^".") (insert_phrase (p^".")) (loop l')
+ in
+ loop l ()
+
+ method handle_reset_initial why h k =
+ if why = Coq.Unexpected then warning "Coqtop died badly. Resetting.";
+ (* clear the stack *)
+ while not (Stack.is_empty cmd_stack) do
+ let phrase = Stack.pop cmd_stack in
+ buffer#delete_mark phrase.start;
+ buffer#delete_mark phrase.stop
+ done;
+ (* reset the buffer *)
+ let start = buffer#start_iter in
+ let stop = buffer#end_iter in
+ buffer#move_mark ~where:start (`NAME "start_of_input");
+ buffer#remove_tag Tags.Script.processed ~start ~stop;
+ buffer#remove_tag Tags.Script.unjustified ~start ~stop;
+ buffer#remove_tag Tags.Script.to_process ~start ~stop;
+ Sentence.tag_on_insert buffer;
+ (* clear the views *)
+ messages#clear;
+ proof#clear ();
+ clear_info ();
+ push_info "Restarted";
+ (* apply the initial commands to coq *)
+ self#include_file_dir_in_path h k;
+
+ method include_file_dir_in_path h k =
+ match get_filename () with
+ |None -> k ()
+ |Some f ->
+ let dir = Filename.dirname f in
+ Coq.inloadpath dir h (function
+ |Interface.Fail (_,s) ->
+ messages#set
+ ("Could not determine lodpath, this might lead to problems:\n"^s);
+ k ()
+ |Interface.Good true |Interface.Unsafe true -> k ()
+ |Interface.Good false |Interface.Unsafe false ->
+ let cmd = Printf.sprintf "Add LoadPath \"%s\". " dir in
+ Coq.interp cmd h (function
+ |Interface.Fail (l,str) ->
+ messages#set ("Couln't add loadpath:\n"^str);
+ k ()
+ |Interface.Good _ | Interface.Unsafe _ -> k ()))
+
+(** NB: Events during text edition:
+
+ - [begin_user_action]
+ - [insert_text] (or [delete_range] when deleting)
+ - [changed]
+ - [end_user_action]
+
+ When pasting a text containing tags (e.g. the sentence terminators),
+ there is actually many [insert_text] and [changed]. For instance,
+ for "a. b.":
+
+ - [begin_user_action]
+ - [insert_text] (for "a")
+ - [changed]
+ - [insert_text] (for ".")
+ - [changed]
+ - [apply_tag] (for the tag of ".")
+ - [insert_text] (for " b")
+ - [changed]
+ - [insert_text] (for ".")
+ - [changed]
+ - [apply_tag] (for the tag of ".")
+ - [end_user_action]
+
+ Since these copy-pasted tags may interact badly with the retag mechanism,
+ we now don't monitor the "changed" event, but rather the "begin_user_action"
+ and "end_user_action". We begin by setting a mark at the initial cursor
+ point. At the end, the zone between the mark and the cursor is to be
+ untagged and then retagged. *)
+
+ initializer
+ let _ = buffer#connect#insert_text
+ ~callback:(fun it s ->
+ (* If a #insert happens in the locked zone, we discard it.
+ Other solution: always use #insert_interactive and similar *)
+ if (it#compare self#get_start_of_input)<0 then
+ GtkSignal.stop_emit ();
+ if String.length s > 1 then
+ let () = Minilib.log "insert_text: Placing cursor" in
+ buffer#place_cursor ~where:it;
+ if String.contains s '\n' then
+ let () = Minilib.log "insert_text: Recentering" in
+ script#recenter_insert)
+ in
+ let _ = buffer#connect#begin_user_action
+ ~callback:(fun () ->
+ let where = self#get_insert in
+ buffer#move_mark (`NAME "prev_insert") ~where;
+ let start = self#get_start_of_input in
+ let stop = buffer#end_iter in
+ buffer#remove_tag Tags.Script.error ~start ~stop)
+ in
+ let _ = buffer#connect#end_user_action
+ ~callback:(fun () -> Sentence.tag_on_insert buffer)
+ in
+ let _ = buffer#connect#after#mark_set
+ ~callback:(fun it m ->
+ !set_location
+ (Printf.sprintf "Line: %5d Char: %3d"
+ (self#get_insert#line + 1)
+ (self#get_insert#line_offset + 1));
+ match GtkText.Mark.get_name m with
+ | Some "insert" -> ()
+ | Some s -> Minilib.log (s^" moved")
+ | None -> ())
+ in ()
+end