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