1(*
2 * Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 *)
6
7(*
8   Methods and method combinators that are built using Eisbach.
9*)
10
11theory Eisbach_Methods
12imports
13  Subgoal_Methods
14  "HOL-Eisbach.Eisbach_Tools"
15  Rule_By_Method
16  Local_Method
17begin
18
19
20section \<open>Debugging methods\<close>
21
22method print_concl = (match conclusion in P for P \<Rightarrow> \<open>print_term P\<close>)
23
24method_setup print_raw_goal = \<open>Scan.succeed (fn ctxt => fn facts =>
25  (fn (ctxt, st) => (Output.writeln (Thm.string_of_thm ctxt st);
26    Seq.make_results (Seq.single (ctxt, st)))))\<close>
27
28ML \<open>fun method_evaluate text ctxt facts =
29  NO_CONTEXT_TACTIC ctxt
30    (Method.evaluate_runtime text ctxt facts)\<close>
31
32
33method_setup print_headgoal =
34  \<open>Scan.succeed (fn ctxt =>
35    fn _ => fn (ctxt', thm) =>
36    ((SUBGOAL (fn (t,_) =>
37     (Output.writeln
38     (Pretty.string_of (Syntax.pretty_term ctxt t)); all_tac)) 1 thm);
39     (Seq.make_results (Seq.single (ctxt', thm)))))\<close>
40
41section \<open>Simple Combinators\<close>
42
43method_setup defer_tac = \<open>Scan.succeed (fn _ => SIMPLE_METHOD (defer_tac 1))\<close>
44method_setup prefer_last = \<open>Scan.succeed (fn _ => SIMPLE_METHOD (PRIMITIVE (Thm.permute_prems 0 ~1)))\<close>
45
46method_setup all =
47 \<open>Method.text_closure >> (fn m => fn ctxt => fn facts =>
48   let
49     fun tac i st' =
50       Goal.restrict i 1 st'
51       |> method_evaluate m ctxt facts
52       |> Seq.map (Goal.unrestrict i)
53
54   in SIMPLE_METHOD (ALLGOALS tac) facts end)
55\<close>
56
57method_setup determ =
58 \<open>Method.text_closure >> (fn m => fn ctxt => fn facts =>
59   let
60     fun tac st' = method_evaluate m ctxt facts st'
61
62   in SIMPLE_METHOD (DETERM tac) facts end)
63\<close> \<open>Run the given method, but only yield the first result\<close>
64
65ML \<open>
66fun require_determ (method : Method.method) facts st =
67  case method facts st |> Seq.filter_results |> Seq.pull of
68    NONE => Seq.empty
69  | SOME (r1, rs) =>
70      (case Seq.pull rs of
71         NONE => Seq.single r1 |> Seq.make_results
72       | _ => Method.fail facts st);
73
74fun require_determ_method text ctxt =
75  require_determ (Method.evaluate_runtime text ctxt);
76\<close>
77
78method_setup require_determ =
79  \<open>Method.text_closure >> require_determ_method\<close>
80  \<open>Run the given method, but fail if it returns more than one result\<close>
81
82method_setup changed =
83 \<open>Method.text_closure >> (fn m => fn ctxt => fn facts =>
84   let
85     fun tac st' = method_evaluate m ctxt facts st'
86
87   in SIMPLE_METHOD (CHANGED tac) facts end)
88\<close>
89
90
91method_setup timeit =
92 \<open>Method.text_closure >> (fn m => fn ctxt => fn facts =>
93   let
94     fun timed_tac st seq = Seq.make (fn () => Option.map (apsnd (timed_tac st))
95       (timeit (fn () => (Seq.pull seq))));
96
97     fun tac st' =
98       timed_tac st' (method_evaluate m ctxt facts st');
99
100   in SIMPLE_METHOD tac [] end)
101\<close>
102
103
104method_setup timeout =
105 \<open>Scan.lift Parse.int -- Method.text_closure >> (fn (i,m) => fn ctxt => fn facts =>
106   let
107     fun str_of_goal th = Pretty.string_of (Goal_Display.pretty_goal ctxt th);
108
109     fun limit st f x = Timeout.apply (Time.fromSeconds i) f x
110       handle Timeout.TIMEOUT _ => error ("Method timed out:\n" ^ (str_of_goal st));
111
112     fun timed_tac st seq = Seq.make (limit st (fn () => Option.map (apsnd (timed_tac st))
113       (Seq.pull seq)));
114
115     fun tac st' =
116       timed_tac st' (method_evaluate m ctxt facts st');
117
118   in SIMPLE_METHOD tac [] end)
119\<close>
120
121method repeat_new methods m = (m ; (repeat_new \<open>m\<close>)?)
122
123text \<open>The following @{text fails} and @{text succeeds} methods protect the goal from the effect
124      of a method, instead simply determining whether or not it can be applied to the current goal.
125      The @{text fails} method inverts success, only succeeding if the given method would fail.\<close>
126
127method_setup fails =
128 \<open>Method.text_closure >> (fn m => fn ctxt => fn facts =>
129   let
130     fun fail_tac st' =
131       (case Seq.pull (method_evaluate m ctxt facts st') of
132          SOME _ => Seq.empty
133        | NONE => Seq.single st')
134
135   in SIMPLE_METHOD fail_tac facts end)
136\<close>
137
138method_setup succeeds =
139 \<open>Method.text_closure >> (fn m => fn ctxt => fn facts =>
140   let
141     fun can_tac st' =
142       (case Seq.pull (method_evaluate m ctxt facts st') of
143          SOME (st'',_) => Seq.single st'
144        | NONE => Seq.empty)
145
146   in SIMPLE_METHOD can_tac facts end)
147\<close>
148
149text \<open>This method wraps up the "focus" mechanic of match without actually doing any matching.
150      We need to consider whether or not there are any assumptions in the goal,
151      as premise matching fails if there are none.
152
153      If the @{text fails} method is removed here, then backtracking will produce
154      a set of invalid results, where only the conclusion is focused despite
155      the presence of subgoal premises.
156      \<close>
157
158method focus_concl methods m =
159  ((fails \<open>erule thin_rl\<close>, match conclusion in _ \<Rightarrow> \<open>m\<close>)
160  | match premises (local) in H:_ (multi) \<Rightarrow> \<open>m\<close>)
161
162text \<open>
163  @{text repeat} applies a method a specific number of times,
164  like a bounded version of the '+' combinator.
165
166  usage:
167    apply (repeat n \<open>text\<close>)
168
169  - Applies the method \<open>text\<close> to the current proof state n times.
170  - Fails if \<open>text\<close> can't be applied n times.
171\<close>
172
173ML \<open>
174  fun repeat_tac count tactic =
175    if count = 0
176    then all_tac
177    else tactic THEN (repeat_tac (count - 1) tactic)
178\<close>
179
180method_setup repeat = \<open>
181  Scan.lift Parse.nat -- Method.text_closure >> (fn (count, text) => fn ctxt => fn facts =>
182    let val tactic = method_evaluate text ctxt facts
183    in SIMPLE_METHOD (repeat_tac count tactic) facts end)
184\<close>
185
186notepad begin
187  fix A B C
188  assume assms: A B C
189
190  text \<open>repeat: simple repeated application.\<close>
191  have "A \<and> B \<and> C \<and> True"
192    text \<open>repeat: fails if method can't be applied the specified number of times.\<close>
193    apply (fails \<open>repeat 4 \<open>rule conjI, rule assms\<close>\<close>)
194    apply (repeat 3 \<open>rule conjI, rule assms\<close>)
195    by (rule TrueI)
196
197  text \<open>repeat: application with subgoals.\<close>
198  have "A \<and> A" "B \<and> B" "C \<and> C"
199    apply -
200      text \<open>We have three subgoals. This @{text repeat} call consumes two of them.\<close>
201      apply (repeat 2 \<open>rule conjI, (rule assms)+\<close>)
202    text \<open>One subgoal remaining...\<close>
203    apply (rule conjI, (rule assms)+)
204    done
205
206end
207
208text \<open>
209  Literally a copy of the parser for @{method subgoal_tac} composed with an analogue of
210  @{command prefer}.
211
212  Useful if you find yourself introducing many new facts via `subgoal_tac`, but prefer to prove
213  them immediately rather than after they're used.
214\<close>
215setup \<open>
216  Method.setup \<^binding>\<open>prop_tac\<close>
217    (Args.goal_spec -- Scan.lift (Scan.repeat1 Args.embedded_inner_syntax -- Parse.for_fixes) >>
218      (fn (quant, (props, fixes)) => fn ctxt =>
219        (SIMPLE_METHOD'' quant
220          (EVERY' (map (fn prop => Rule_Insts.subgoal_tac ctxt prop fixes) props)
221            THEN'
222            (K (prefer_tac 2))))))
223    "insert prop (dynamic instantiation), introducing prop subgoal first"
224\<close>
225
226notepad begin {
227  fix xs
228  assume assms: "list_all even (xs :: nat list)"
229
230  from assms have "even (sum_list xs)"
231    apply (induct xs)
232     apply simp
233    text \<open>Inserts the desired proposition as the current subgoal.\<close>
234    apply (prop_tac "list_all even xs")
235     subgoal by simp
236    text \<open>
237      The prop @{term "list_all even xs"} is now available as an assumption.
238      Let's add another one.\<close>
239    apply (prop_tac "even (sum_list xs)")
240     subgoal by simp
241    text \<open>Now that we've proven our introduced props, use them!\<close>
242    apply clarsimp
243    done
244}
245end
246
247section \<open>Advanced combinators\<close>
248
249subsection \<open>Protecting goal elements (assumptions or conclusion) from methods\<close>
250
251context
252begin
253
254private definition "protect_concl x \<equiv> \<not> x"
255private definition "protect_false \<equiv> False"
256
257private lemma protect_start: "(protect_concl P \<Longrightarrow> protect_false) \<Longrightarrow> P"
258  by (simp add: protect_concl_def protect_false_def) (rule ccontr)
259
260private lemma protect_end: "protect_concl P \<Longrightarrow> P \<Longrightarrow> protect_false"
261  by (simp add: protect_concl_def protect_false_def)
262
263method only_asm methods m =
264  (match premises in H[thin]:_ (multi,cut) \<Rightarrow>
265    \<open>rule protect_start,
266     match premises in H'[thin]:"protect_concl _" \<Rightarrow>
267       \<open>insert H,m;rule protect_end[OF H']\<close>\<close>)
268
269method only_concl methods m = (focus_concl \<open>m\<close>)
270
271end
272
273notepad begin
274 fix D C
275  assume DC:"D \<Longrightarrow> C"
276  have "D \<and> D \<Longrightarrow> C \<and> C"
277  apply (only_asm \<open>simp\<close>) \<comment> \<open>stash conclusion before applying method\<close>
278  apply (only_concl \<open>simp add: DC\<close>) \<comment> \<open>hide premises from method\<close>
279  by (rule DC)
280
281end
282
283subsection \<open>Safe subgoal folding (avoids expanding meta-conjuncts)\<close>
284
285text \<open>Isabelle's goal mechanism wants to aggressively expand meta-conjunctions if they
286      are the top-level connective. This means that @{text fold_subgoals} will immediately
287      be unfolded if there are no common assumptions to lift over.
288
289      To avoid this we simply wrap conjunction inside of conjunction' to hide it
290      from the usual facilities.\<close>
291
292context begin
293
294definition
295  conjunction' :: "prop \<Rightarrow> prop \<Rightarrow> prop" (infixr "&^&" 2) where
296  "conjunction' A B \<equiv> (PROP A &&& PROP B)"
297
298
299text \<open>In general the context antiquotation does not work in method definitions.
300  Here it is fine because Conv.top_sweep_conv is just over-specified to need a Proof.context
301  when anything would do.\<close>
302
303method safe_meta_conjuncts =
304  raw_tactic
305   \<open>REPEAT_DETERM
306     (CHANGED_PROP
307      (PRIMITIVE
308        (Conv.gconv_rule ((Conv.top_sweep_conv (K (Conv.rewr_conv @{thm conjunction'_def[symmetric]})) @{context})) 1)))\<close>
309
310method safe_fold_subgoals = (fold_subgoals (prefix), safe_meta_conjuncts)
311
312lemma atomize_conj' [atomize]: "(A &^& B) == Trueprop (A & B)"
313  by (simp add: conjunction'_def, rule atomize_conj)
314
315lemma context_conjunction'I:
316  "PROP P \<Longrightarrow> (PROP P \<Longrightarrow> PROP Q) \<Longrightarrow> PROP P &^& PROP Q"
317  apply (simp add: conjunction'_def)
318  apply (rule conjunctionI)
319   apply assumption
320  apply (erule meta_mp)
321  apply assumption
322  done
323
324lemma conjunction'I:
325  "PROP P \<Longrightarrow> PROP Q \<Longrightarrow> PROP P &^& PROP Q"
326  by (rule context_conjunction'I; simp)
327
328lemma conjunction'E:
329  assumes PQ: "PROP P &^& PROP Q"
330  assumes PQR: "PROP P \<Longrightarrow> PROP Q \<Longrightarrow> PROP R"
331  shows
332  "PROP R"
333  apply (rule PQR)
334  apply (rule PQ[simplified conjunction'_def, THEN conjunctionD1])
335  by (rule PQ[simplified conjunction'_def, THEN conjunctionD2])
336
337end
338
339notepad begin
340  fix D C E
341
342  assume DC: "D \<and> C"
343  have "D" "C \<and> C"
344  apply -
345  apply (safe_fold_subgoals, simp, atomize (full))
346  apply (rule DC)
347  done
348
349end
350
351
352section \<open>Utility methods\<close>
353
354
355subsection \<open>Finding a goal based on successful application of a method\<close>
356
357method_setup find_goal =
358 \<open>Method.text_closure >> (fn m => fn ctxt => fn facts =>
359   let
360     fun prefer_first i = SELECT_GOAL
361       (fn st' =>
362         (case Seq.pull (method_evaluate m ctxt facts st') of
363           SOME (st'',_) => Seq.single st''
364         | NONE => Seq.empty)) i THEN prefer_tac i
365
366   in SIMPLE_METHOD (FIRSTGOAL prefer_first) facts end)\<close>
367
368text \<open>Ensure that the proof state is in a certain case of a case distinction:\<close>
369method in_case for x = match premises in "t = x" for t \<Rightarrow> succeed
370
371text \<open>Focus on a case in a case distinction:\<close>
372method find_case for x = find_goal \<open>in_case x\<close>
373
374notepad begin
375
376  fix A B
377  assume A: "A" and B: "B"
378
379  have "A" "A" "B"
380    apply (find_goal \<open>match conclusion in B \<Rightarrow> \<open>-\<close>\<close>)
381    apply (rule B)
382    by (rule A)+
383
384  have "A \<and> A" "A \<and> A" "B"
385    apply (find_goal \<open>fails \<open>simp\<close>\<close>) \<comment> \<open>find the first goal which cannot be simplified\<close>
386    apply (rule B)
387    by (simp add: A)+
388
389  have  "B" "A" "A \<and> A"
390    apply (find_goal \<open>succeeds \<open>simp\<close>\<close>) \<comment> \<open>find the first goal which can be simplified (without doing so)\<close>
391    apply (rule conjI)
392    by (rule A B)+
393
394  fix x::'a and S :: "nat \<Rightarrow> 'a" and T R
395  have
396    "x = T \<Longrightarrow> A"
397    "x = S 10 \<Longrightarrow> B"
398    "x = R \<Longrightarrow> B"
399  apply -
400    apply (find_case "S ?n")
401    apply (fails \<open>in_case R\<close>)
402    apply (in_case "S ?n")
403    by (rule A B)+
404
405end
406
407
408subsection \<open>Remove redundant subgoals\<close>
409
410text \<open>Tries to solve subgoals by assuming the others and then using the given method.
411      Backtracks over all possible re-orderings of the subgoals.\<close>
412
413context begin
414
415definition "protect (PROP P) \<equiv> P"
416
417lemma protectE: "PROP protect P \<Longrightarrow> (PROP P \<Longrightarrow> PROP R) \<Longrightarrow> PROP R" by (simp add: protect_def)
418
419private lemmas protect_thin = thin_rl[where V="PROP protect P" for P]
420
421private lemma context_conjunction'I_protected:
422  assumes P: "PROP P"
423  assumes PQ: "PROP protect (PROP P) \<Longrightarrow> PROP Q"
424  shows
425  "PROP P &^& PROP Q"
426   apply (simp add: conjunction'_def)
427   apply (rule P)
428  apply (rule PQ)
429  apply (simp add: protect_def)
430  by (rule P)
431
432private lemma conjunction'_sym: "PROP P &^& PROP Q \<Longrightarrow> PROP Q &^& PROP P"
433  apply (simp add: conjunction'_def)
434  apply (frule conjunctionD1)
435  apply (drule conjunctionD2)
436  apply (rule conjunctionI)
437  by assumption+
438
439
440
441private lemmas context_conjuncts'I =
442  context_conjunction'I_protected
443  context_conjunction'I_protected[THEN conjunction'_sym]
444
445method distinct_subgoals_strong methods m =
446  (safe_fold_subgoals,
447   (intro context_conjuncts'I;
448     (((elim protectE conjunction'E)?, solves \<open>m\<close>)
449     | (elim protect_thin)?)))?
450
451end
452
453method forward_solve methods fwd m =
454  (fwd, prefer_last, fold_subgoals, safe_meta_conjuncts, rule conjunction'I,
455   defer_tac, ((intro conjunction'I)?; solves \<open>m\<close>))[1]
456
457method frule_solve methods m uses rule = (forward_solve \<open>frule rule\<close> \<open>m\<close>)
458method drule_solve methods m uses rule = (forward_solve \<open>drule rule\<close> \<open>m\<close>)
459
460notepad begin
461  {
462  fix A B C D E
463  assume ABCD: "A \<Longrightarrow> B \<Longrightarrow> C \<Longrightarrow> D"
464  assume ACD: "A \<Longrightarrow> C \<Longrightarrow> D"
465  assume DE: "D \<Longrightarrow> E"
466  assume B C
467
468  have "A \<Longrightarrow> D"
469  apply (frule_solve \<open>simp add: \<open>B\<close> \<open>C\<close>\<close> rule: ABCD)
470  apply (drule_solve \<open>simp add: \<open>B\<close> \<open>C\<close>\<close> rule: ACD)
471  apply (match premises in A \<Rightarrow> \<open>fail\<close> \<bar> _ \<Rightarrow> \<open>-\<close>)
472  apply assumption
473  done
474  }
475end
476
477
478notepad begin
479  {
480  fix A B C
481  assume A: "A"
482  have "A" "B \<Longrightarrow> A"
483  apply -
484  apply (distinct_subgoals_strong \<open>assumption\<close>)
485  by (rule A)
486
487  have "B \<Longrightarrow> A" "A"
488  by (distinct_subgoals_strong \<open>assumption\<close>, rule A) \<comment> \<open>backtracking required here\<close>
489  }
490
491  {
492  fix A B C
493
494  assume B: "B"
495  assume BC: "B \<Longrightarrow> C" "B \<Longrightarrow> A"
496  have "A" "B \<longrightarrow> (A \<and> C)" "B"
497  apply (distinct_subgoals_strong \<open>simp\<close>, rule B) \<comment> \<open>backtracking required here\<close>
498  by (simp add: BC)
499
500  }
501end
502
503section \<open>Attribute methods (for use with rule_by_method attributes)\<close>
504
505method prove_prop_raw for P :: "prop" methods m =
506  (erule thin_rl, rule revcut_rl[of "PROP P"],
507    solves \<open>match conclusion in _ \<Rightarrow> \<open>m\<close>\<close>)
508
509method prove_prop for P :: "prop" = (prove_prop_raw "PROP P" \<open>auto\<close>)
510
511experiment begin
512
513lemma assumes A[simp]:A shows A by (rule [[@\<open>prove_prop A\<close>]])
514
515end
516
517section \<open>Shortcuts for prove_prop. Note these are less efficient than using the raw syntax because
518 the facts are re-proven every time.\<close>
519
520method ruleP for P :: "prop" = (catch \<open>rule [[@\<open>prove_prop "PROP P"\<close>]]\<close> \<open>fail\<close>)
521method insertP for P :: "prop" = (catch \<open>insert [[@\<open>prove_prop "PROP P"\<close>]]\<close> \<open>fail\<close>)[1]
522
523experiment begin
524
525lemma assumes A[simp]:A shows A by (ruleP False | ruleP A)
526lemma assumes A:A shows A by (ruleP "\<And>P. P \<Longrightarrow> P \<Longrightarrow> P", rule A, rule A)
527
528end
529
530context begin
531
532private definition "bool_protect (b::bool) \<equiv> b"
533
534lemma bool_protectD:
535  "bool_protect P \<Longrightarrow> P"
536  unfolding bool_protect_def by simp
537
538lemma bool_protectI:
539  "P \<Longrightarrow> bool_protect P"
540  unfolding bool_protect_def by simp
541
542text \<open>
543  When you want to apply a rule/tactic to transform a potentially complex goal into another
544  one manually, but want to indicate that any fresh emerging goals are solved by a more
545  brutal method.
546  E.g. apply (solves_emerging \<open>frule x=... in my_rule\<close>\<open>fastforce simp: ... intro!: ... \<close>
547\<close>
548method solves_emerging methods m1 m2 = (rule bool_protectD, (m1 ; (rule bool_protectI | (m2; fail))))
549
550end
551
552end
553