summaryrefslogtreecommitdiff
path: root/toplevel/backtrack.ml
blob: 912d694ebaff043241624b24d71f12656fcf2de0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
(************************************************************************)
(*  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 Names
open Vernacexpr

(** Command history stack

    We maintain a stack of the past states of the system. Each
    successfully interpreted command adds an [info] element
    to this stack, storing what were the (label / current proof / ...)
    just _after_ the interpretation of this command.

    - A label is just an integer, starting from Lib.first_command_label
      initially, and incremented at each new successful command.
    - If some proofs are opened, we have their number in [nproofs],
      the name of the current proof in [prfname], the current depth in
      [prfdepth].
    - Otherwise, [nproofs = 0], [prfname = None], [prfdepth = 0]
    - The text of the command is stored (for Show Script currently).
    - A command can be tagged later as non-"reachable" when the current proof
      at the time of this command has been ended by Qed/Abort/Restart,
      meaning we can't backtrack there.
*)

type info = {
  label : int;
  nproofs : int;
  prfname : identifier option;
  prfdepth : int;
  ngoals : int;
  cmd : vernac_expr;
  mutable reachable : bool;
}

let history : info Stack.t = Stack.create ()

(** Is this stack active (i.e. nonempty) ?
    The stack is currently inactive when compiling files (coqc). *)

let is_active () = not (Stack.is_empty history)

(** For debug purpose, a dump of the history *)

let dump_history () =
  let l = ref [] in
  Stack.iter (fun i -> l:=i::!l) history;
  !l

(** Basic manipulation of the command history stack *)

exception Invalid

let pop () = ignore (Stack.pop history)

let npop n =
 (* Since our history stack always contains an initial entry,
    it's invalid to try to completely empty it *)
 if n < 0 || n >= Stack.length history then raise Invalid
 else for i = 1 to n do pop () done

let top () =
  try Stack.top history with Stack.Empty -> raise Invalid

(** Search the history stack for a suitable location. We perform first
    a non-destructive search: in case of search failure, the stack is
    unchanged. *)

exception Found of info

let search test =
  try
    Stack.iter (fun i -> if test i then raise (Found i)) history;
    raise Invalid
  with Found i ->
    while i != Stack.top history do pop () done

(** An auxiliary function to retrieve the number of remaining subgoals *)

let get_ngoals () =
  try
    let prf = Proof_global.give_me_the_proof () in
    List.length (Evd.sig_it (Proof.V82.background_subgoals prf))
  with Proof_global.NoCurrentProof -> 0

(** Register the end of a command and store the current state *)

let mark_command ast =
  Lib.add_frozen_state();
  Lib.mark_end_of_command();
  Stack.push
    { label = Lib.current_command_label ();
      nproofs = List.length (Pfedit.get_all_proof_names ());
      prfname = (try Some (Pfedit.get_current_proof_name ()) with _ -> None);
      prfdepth = max 0 (Pfedit.current_proof_depth ());
      reachable = true;
      ngoals = get_ngoals ();
      cmd = ast }
    history

(** Backtrack by aborting [naborts] proofs, then setting proof-depth back to
    [pnum] and finally going to state number [snum]. *)

let raw_backtrack snum pnum naborts =
  for i = 1 to naborts do Pfedit.delete_current_proof () done;
  Pfedit.undo_todepth pnum;
  Lib.reset_label snum

(** Re-sync the state of the system (label, proofs) with the top
    of the history stack. We may end on some earlier state to avoid
    re-opening proofs. This function will return the final label
    and the number of extra backtracking steps performed. *)

let sync nb_opened_proofs =
  (* Backtrack by enough additional steps to avoid re-opening proofs.
     Typically, when a Qed has been crossed, we backtrack to the proof start.
     NB: We cannot reach the empty stack, since the first entry in the
     stack has no opened proofs and is tagged as reachable.
  *)
  let extra = ref 0 in
  while not (top()).reachable do incr extra; pop () done;
  let target = top ()
  in
  (* Now the opened proofs at target is a subset of the opened proofs before
     the backtrack, we simply abort the extra proofs (if any).
     NB: It is critical here that proofs are nested in a regular way
     (i.e. no more Resume or Suspend commands as earlier). This way, we can
     simply count the extra proofs to abort instead of taking care of their
     names.
  *)
  let naborts = nb_opened_proofs - target.nproofs in
  (* We are now ready to do a low-level backtrack *)
  raw_backtrack target.label target.prfdepth naborts;
  (target.label, !extra)

(** Backtracking by a certain number of (non-state-preserving) commands.
    This is used by Coqide.
    It may actually undo more commands than asked : for instance instead
    of jumping back in the middle of a finished proof, we jump back before
    this proof. The number of extra backtracked command is returned at
    the end. *)

let back count =
  if count = 0 then 0
  else
    let nb_opened_proofs = List.length (Pfedit.get_all_proof_names ()) in
    npop count;
    snd (sync nb_opened_proofs)

(** Backtracking to a certain state number, and reset proofs accordingly.
    We may end on some earlier state if needed to avoid re-opening proofs.
    Return the final state number. *)

let backto snum =
  if snum = Lib.current_command_label () then snum
  else
    let nb_opened_proofs = List.length (Pfedit.get_all_proof_names ()) in
    search (fun i -> i.label = snum);
    fst (sync nb_opened_proofs)

(** Old [Backtrack] code with corresponding update of the history stack.
    [Backtrack] is now deprecated (in favor of [BackTo]) but is kept for
    compatibility with ProofGeneral. It's completely up to ProofGeneral
    to decide where to go and how to adapt proofs. Note that the choices
    of ProofGeneral are currently not always perfect (for instance when
    backtracking an Undo). *)

let backtrack snum pnum naborts =
  raw_backtrack snum pnum naborts;
  search (fun i -> i.label = snum)

(** [reset_initial] resets the system and clears the command history
    stack, only pushing back the initial entry. It should be equivalent
    to [backto Lib.first_command_label], but sligthly more efficient. *)

let reset_initial () =
  let init_label = Lib.first_command_label in
  if Lib.current_command_label () = init_label then ()
  else begin
    Pfedit.delete_all_proofs ();
    Lib.reset_label init_label;
    Stack.clear history;
    Stack.push
      { label = init_label;
	nproofs = 0;
	prfname = None;
	prfdepth = 0;
	ngoals = 0;
	reachable = true;
	cmd = VernacNop }
      history
  end

(** Reset to the last known state just before defining [id] *)

let reset_name id =
  let lbl =
    try Lib.label_before_name id with Not_found -> raise Invalid
  in
  ignore (backto lbl)

(** When a proof is ended (via either Qed/Admitted/Restart/Abort),
    old proof steps should be marked differently to avoid jumping back
    to them:
     - either this proof isn't there anymore in the proof engine
     - either it's there but it's a more recent attempt after a Restart,
       so we shouldn't mix the two.
    We also mark as unreachable the proof steps cancelled via a Undo. *)

let mark_unreachable ?(after=0) prf_lst =
  let fix i = match i.prfname with
    | None -> raise Not_found (* stop hacking the history outside of proofs *)
    | Some p ->
      if List.mem p prf_lst && i.prfdepth > after
      then i.reachable <- false
  in
  try Stack.iter fix history with Not_found -> ()

(** Parse the history stack for printing the script of a proof *)

let get_script prf =
  let script = ref [] in
  let select i = match i.prfname with
    | None -> raise Not_found
    | Some p when p=prf && i.reachable -> script := i :: !script
    | _ -> ()
  in
  (try Stack.iter select history with Not_found -> ());
  (* Get rid of intermediate commands which don't grow the proof depth *)
  let rec filter n = function
    | [] -> []
    | {prfdepth=d; cmd=c; ngoals=ng}::l when n < d -> (c,ng) :: filter d l
    | {prfdepth=d}::l -> filter d l
  in
  (* initial proof depth (after entering the lemma statement) is 1 *)
  filter 1 !script