4 Generic counterexample search engine. |
4 Generic counterexample search engine. |
5 *) |
5 *) |
6 |
6 |
7 signature QUICKCHECK = |
7 signature QUICKCHECK = |
8 sig |
8 sig |
|
9 val quickcheckN: string |
|
10 val genuineN: string |
|
11 val noneN: string |
|
12 val unknownN: string |
9 val setup: theory -> theory |
13 val setup: theory -> theory |
10 (* configuration *) |
14 (* configuration *) |
11 val auto: bool Unsynchronized.ref |
15 val auto: bool Unsynchronized.ref |
12 val tester : string Config.T |
16 val tester : string Config.T |
13 val size : int Config.T |
17 val size : int Config.T |
470 fun pretty_counterex ctxt auto NONE = Pretty.str (tool_name auto ^ " found no counterexample.") |
481 fun pretty_counterex ctxt auto NONE = Pretty.str (tool_name auto ^ " found no counterexample.") |
471 | pretty_counterex ctxt auto (SOME (cex, eval_terms)) = |
482 | pretty_counterex ctxt auto (SOME (cex, eval_terms)) = |
472 Pretty.chunks ((Pretty.str (tool_name auto ^ " found a counterexample:\n") :: |
483 Pretty.chunks ((Pretty.str (tool_name auto ^ " found a counterexample:\n") :: |
473 map (fn (s, t) => |
484 map (fn (s, t) => |
474 Pretty.block [Pretty.str (s ^ " ="), Pretty.brk 1, Syntax.pretty_term ctxt t]) (rev cex)) |
485 Pretty.block [Pretty.str (s ^ " ="), Pretty.brk 1, Syntax.pretty_term ctxt t]) (rev cex)) |
475 @ (Pretty.str ("Evaluated terms:\n") :: |
486 @ (if null eval_terms then [] |
476 map (fn (t, u) => |
487 else (Pretty.str ("Evaluated terms:\n") :: |
477 Pretty.block [Syntax.pretty_term ctxt t, Pretty.str " =", Pretty.brk 1, Syntax.pretty_term ctxt u]) |
488 map (fn (t, u) => |
478 (rev eval_terms))); |
489 Pretty.block [Syntax.pretty_term ctxt t, Pretty.str " =", Pretty.brk 1, Syntax.pretty_term ctxt u]) |
|
490 (rev eval_terms)))); |
479 |
491 |
480 fun pretty_report (Report {iterations = iterations, raised_match_errors = raised_match_errors, |
492 fun pretty_report (Report {iterations = iterations, raised_match_errors = raised_match_errors, |
481 satisfied_assms = satisfied_assms, positive_concl_tests = positive_concl_tests}) = |
493 satisfied_assms = satisfied_assms, positive_concl_tests = positive_concl_tests}) = |
482 let |
494 let |
483 fun pretty_stat s i = Pretty.block ([Pretty.str (s ^ ": " ^ string_of_int i)]) |
495 fun pretty_stat s i = Pretty.block ([Pretty.str (s ^ ": " ^ string_of_int i)]) |
508 Pretty.strs (space_explode " " "Please provide an executable interpretation for the locale.")] |
520 Pretty.strs (space_explode " " "Please provide an executable interpretation for the locale.")] |
509 | pretty_counterex_and_reports ctxt auto (result :: _) = |
521 | pretty_counterex_and_reports ctxt auto (result :: _) = |
510 Pretty.chunks (pretty_counterex ctxt auto (response_of result) :: |
522 Pretty.chunks (pretty_counterex ctxt auto (response_of result) :: |
511 (* map (pretty_reports ctxt) (reports_of result) :: *) |
523 (* map (pretty_reports ctxt) (reports_of result) :: *) |
512 (if Config.get ctxt timing then cons (pretty_timings (timings_of result)) else I) []) |
524 (if Config.get ctxt timing then cons (pretty_timings (timings_of result)) else I) []) |
513 |
|
514 (* automatic testing *) |
|
515 |
|
516 fun auto_quickcheck state = |
|
517 let |
|
518 val ctxt = Proof.context_of state; |
|
519 val res = |
|
520 state |
|
521 |> Proof.map_context (Config.put report false #> Config.put quiet true) |
|
522 |> try (test_goal (false, false) ([], []) 1); |
|
523 in |
|
524 case res of |
|
525 NONE => (false, state) |
|
526 | SOME (result :: _) => if found_counterexample result then |
|
527 (true, Proof.goal_message (K (Pretty.chunks [Pretty.str "", |
|
528 Pretty.mark Markup.hilite (pretty_counterex ctxt true (response_of result))])) state) |
|
529 else |
|
530 (false, state) |
|
531 end |
|
532 |
|
533 val setup = Try.register_tool (auto, auto_quickcheck) |
|
534 |
525 |
535 (* Isar commands *) |
526 (* Isar commands *) |
536 |
527 |
537 fun read_nat s = case (Library.read_int o Symbol.explode) s |
528 fun read_nat s = case (Library.read_int o Symbol.explode) s |
538 of (k, []) => if k >= 0 then k |
529 of (k, []) => if k >= 0 then k |
608 |
599 |
609 fun quickcheck args i state = counterexample_of (hd (gen_quickcheck args i state)) |
600 fun quickcheck args i state = counterexample_of (hd (gen_quickcheck args i state)) |
610 |
601 |
611 fun quickcheck_cmd args i state = |
602 fun quickcheck_cmd args i state = |
612 gen_quickcheck args i (Toplevel.proof_of state) |
603 gen_quickcheck args i (Toplevel.proof_of state) |
613 |> Pretty.writeln o pretty_counterex_and_reports (Toplevel.context_of state) false; |
604 |> Output.urgent_message o Pretty.string_of |
|
605 o pretty_counterex_and_reports (Toplevel.context_of state) false; |
614 |
606 |
615 val parse_arg = |
607 val parse_arg = |
616 Parse.name -- |
608 Parse.name -- |
617 (Scan.optional (Parse.$$$ "=" |-- |
609 (Scan.optional (Parse.$$$ "=" |-- |
618 (((Parse.name || Parse.float_number) >> single) || |
610 (((Parse.name || Parse.float_number) >> single) || |
620 |
612 |
621 val parse_args = |
613 val parse_args = |
622 Parse.$$$ "[" |-- Parse.list1 parse_arg --| Parse.$$$ "]" || Scan.succeed []; |
614 Parse.$$$ "[" |-- Parse.list1 parse_arg --| Parse.$$$ "]" || Scan.succeed []; |
623 |
615 |
624 val _ = |
616 val _ = |
625 Outer_Syntax.command "quickcheck_params" "set parameters for random testing" Keyword.thy_decl |
617 Outer_Syntax.command quickcheck_paramsN "set parameters for random testing" Keyword.thy_decl |
626 (parse_args >> (fn args => Toplevel.theory (quickcheck_params_cmd args))); |
618 (parse_args >> (fn args => Toplevel.theory (quickcheck_params_cmd args))); |
627 |
619 |
628 val _ = |
620 val _ = |
629 Outer_Syntax.improper_command "quickcheck" "try to find counterexample for subgoal" Keyword.diag |
621 Outer_Syntax.improper_command quickcheckN "try to find counterexample for subgoal" Keyword.diag |
630 (parse_args -- Scan.optional Parse.nat 1 |
622 (parse_args -- Scan.optional Parse.nat 1 |
631 >> (fn (args, i) => Toplevel.no_timing o Toplevel.keep (quickcheck_cmd args i))); |
623 >> (fn (args, i) => Toplevel.no_timing o Toplevel.keep (quickcheck_cmd args i))); |
632 |
624 |
|
625 (* automatic testing *) |
|
626 |
|
627 fun try_quickcheck auto state = |
|
628 let |
|
629 val ctxt = Proof.context_of state; |
|
630 val i = 1; |
|
631 val res = |
|
632 state |
|
633 |> Proof.map_context (Config.put report false #> Config.put quiet true) |
|
634 |> try (test_goal (false, false) ([], []) i); |
|
635 in |
|
636 case res of |
|
637 NONE => (unknownN, state) |
|
638 | SOME results => |
|
639 let |
|
640 val msg = pretty_counterex_and_reports ctxt auto results |
|
641 |> not auto ? tap (Output.urgent_message o Pretty.string_of) |
|
642 in |
|
643 if exists found_counterexample results then |
|
644 (genuineN, |
|
645 state |> (if auto then |
|
646 Proof.goal_message (K (Pretty.chunks [Pretty.str "", |
|
647 Pretty.mark Markup.hilite msg])) |
|
648 else |
|
649 I)) |
|
650 else |
|
651 (noneN, state) |
|
652 end |
|
653 end |
|
654 |> `(fn (outcome_code, _) => outcome_code = genuineN) |
|
655 |
|
656 val setup = Try.register_tool (quickcheckN, (auto, try_quickcheck)) |
|
657 |
633 end; |
658 end; |
634 |
659 |
635 |
|
636 val auto_quickcheck = Quickcheck.auto; |
660 val auto_quickcheck = Quickcheck.auto; |