diff --git a/_CoqProject.in b/_CoqProject.in index a368a3cd..d3fbefb0 100644 --- a/_CoqProject.in +++ b/_CoqProject.in @@ -67,6 +67,7 @@ theories/Tactics/Help.v theories/Tactics/Induction.v theories/Tactics/ItHolds.v theories/Tactics/ItSuffices.v +theories/Tactics/Specialize.v theories/Tactics/Take.v theories/Tactics/ToShow.v theories/Tactics/Unfold.v @@ -137,6 +138,7 @@ tests/tactics/Either.v tests/tactics/Induction.v tests/tactics/ItHolds.v tests/tactics/ItSuffices.v +tests/tactics/Specialize.v tests/tactics/Take.v tests/tactics/ToShow.v tests/tactics/Unfold.v diff --git a/src/databases.mlg b/src/databases.mlg index 8219e95e..2b94763d 100644 --- a/src/databases.mlg +++ b/src/databases.mlg @@ -16,7 +16,7 @@ (* *) (******************************************************************************) -DECLARE PLUGIN "coq-waterproof.databases" +DECLARE PLUGIN "coq-waterproof.plugin" { @@ -73,4 +73,4 @@ VERNAC COMMAND EXTEND DatabaseQuery CLASSIFIED AS QUERY Feedback.msg_notice ((str "Decidability databases :") ++ List.fold_left (fun acc database_name -> acc ++ str " " ++ str database_name) (str "") (get_current_databases Decidability)); Feedback.msg_notice ((str "Shorten databases :") ++ List.fold_left (fun acc database_name -> acc ++ str " " ++ str database_name) (str "") (get_current_databases Shorten)) } -END \ No newline at end of file +END diff --git a/tests/libs/Analysis/StrongInductionIndexSequence.v b/tests/libs/Analysis/StrongInductionIndexSequence.v index 41d8e005..7d8d4e78 100644 --- a/tests/libs/Analysis/StrongInductionIndexSequence.v +++ b/tests/libs/Analysis/StrongInductionIndexSequence.v @@ -57,38 +57,40 @@ Abort. -(* Test 3: more complicated example from actual exercises, - i.e. with function notation 'n(k)' and 'P(n(k)) = blue' as property. *) +(* Test 3: more complicated example inspired by actual exercises, + i.e. with function notation 'n(k)' and 'candy_seq(n(k)) = sweet' as property. *) Require Import Waterproof.Notations. Require Import Waterproof.Automation. Waterproof Enable Automation RealsAndIntegers. Waterproof Enable Automation Intuition. -Inductive Color : Set := -| blue : Color -| orange : Color. +Inductive Flavor : Set := +| sweet : Flavor +| sour : Flavor +| salty : Flavor. -#[local] Parameter P : ℕ → Color. -Parameter infinitely_many_blues : ∀ k : ℕ, ∃ m : ℕ, (m ≥ k)%nat ∧ P(m) = blue. -Lemma exercise_10_7_6 : - ∃ n : ℕ → ℕ, (is_index_seq n) ∧ (∀ k : ℕ, P (n k) = blue). +#[local] Parameter candy_seq : ℕ → Flavor. +Parameter infinitely_many_sweet : ∀ k : ℕ, ∃ m : ℕ, (m ≥ k)%nat ∧ candy_seq(m) = sweet. + +Goal + ∃ n : ℕ → ℕ, (is_index_seq n) ∧ (∀ k : ℕ, candy_seq (n k) = sweet). Proof. Define the index sequence n inductively. -- By (infinitely_many_blues) it holds that - (∃ m : ℕ, (m ≥ 0)%nat ∧ P(m) = blue). +- By (infinitely_many_sweet) it holds that + (∃ m : ℕ, (m ≥ 0)%nat ∧ candy_seq(m) = sweet). Obtain such an m. Choose n_0 := m. - We conclude that (P(n_0) = blue). + We conclude that (candy_seq(n_0) = sweet). - Take k : ℕ and assume n(0),...,n(k) are defined. - Assume that (∀ l : ℕ, (l ≤ k)%nat ⇒ P(n(l)) = blue). + Assume that (∀ l : ℕ, (l ≤ k)%nat ⇒ candy_seq(n(l)) = sweet). Assume that (∀ l : ℕ, (l < k)%nat ⇒ (n(l) < n(l+1))%nat). - By (infinitely_many_blues) it holds that - (∃ m : ℕ, (m ≥ (n k) + 1)%nat ∧ P(m) = blue). + By (infinitely_many_sweet) it holds that + (∃ m : ℕ, (m ≥ (n k) + 1)%nat ∧ candy_seq(m) = sweet). Obtain such an m. Choose n_kplus1 := m. We show both statements. - * We conclude that (P(n_kplus1) = blue). + * We conclude that (candy_seq(n_kplus1) = sweet). * We conclude that (n(k) < n_kplus1)%nat. Qed. diff --git a/tests/tactics/Choose.v b/tests/tactics/Choose.v index 00cab25f..25b343b6 100644 --- a/tests/tactics/Choose.v +++ b/tests/tactics/Choose.v @@ -59,3 +59,13 @@ Goal forall n : nat, ( ( (n = n) \/ (n + 1 = n + 1) ) -> (n + 1 = n + 1)). intro n. Fail Choose m := n. Abort. + +(** Test 5: Choose a blank *) +Goal exists n : nat, n + 1 = n + 1. + Choose n := (_). +Abort. + +(** Test 6: Choose a named evar *) +Goal exists n : nat, n + 1 = n + 1. + Choose n := (?[m]). +Abort. diff --git a/tests/tactics/ItHolds.v b/tests/tactics/ItHolds.v index 5a24c59d..33abe5ce 100644 --- a/tests/tactics/ItHolds.v +++ b/tests/tactics/ItHolds.v @@ -23,9 +23,9 @@ Require Import Lra. (* Set Default Timeout 1. *) +Require Import Waterproof.Tactics. Require Import Waterproof.Automation. Require Import Waterproof.Notations. -Require Import Waterproof.Tactics. Require Import Waterproof.Util.Assertions. Waterproof Enable Automation RealsAndIntegers. @@ -306,6 +306,8 @@ Proof. By Ha it holds that B. Abort. +(* -------------------------------------------------------------------------- *) + (* Test 21: we can use "It holds that" using boolean statements *) Goal 1 < 2. Proof. @@ -319,3 +321,63 @@ Proof. Since (orb true false) it holds that (1 < 2). assumption. Qed. + +(* -------------------------------------------------------------------------- *) + +(** Test 23: Test whether wrapper for specialize works *) +Goal (forall x : nat, x >= 5 -> True) -> True. +Proof. + intro H1. + Use x := 6 in (H1). + It holds that (6 >= 5 -> True) (i). +Abort. + +(** Test 24: Test wrapper specialize blocks other tactics *) +Goal (forall x : nat, x >= 5 -> True) -> True. +Proof. + intro H1. + Use x := 6 in (H1). + Fail We claim that (False). +Abort. + +(** Test 25: Test wrapper specialize fails with wrong statement *) +Goal (forall x : nat, x >= 5 -> True) -> True. +Proof. + intro H1. + It holds that (3 >= 5 -> True). (* test whether automation could show this*) + Use x := 6 in (H1). + Fail It holds that (3 >= 5 -> True). +Abort. + +(** Test 26: Test succesful renaming hypothesis from specialize + even if another instance is added inbetween. *) +Goal (forall x : nat, x >= 5 -> True) -> True. +Proof. + intro H1. + Use x := 6 in (H1). + assert (6 >= 5 -> True) as decoy by exact (H1 6). + It holds that (6 >= 5 -> True) (i). + ltac1:(rename i into ii). (* test for hypohtesis without producing output *) + Fail Check _H. (* '_H' is auto-generated by 'specialize' tactic *) +Abort. + +(** Test 27: Test fail renaming hypothesis from specialize + if user-specified name is already used. *) +Goal (forall x : nat, x >= 5 -> True) -> True. +Proof. + intro H1. + Use x := 6 in (H1). + Fail It holds that (6 >= 5 -> True) (H1). +Abort. + + +(* TODO (cf. test below): wrong statement error should have priority over wrong label. *) + +(** Test 28: Test fail renaming hypothesis from specialize + if wrong statement user-specified name is already used. *) +Goal (forall x : nat, x >= 5 -> True) -> True. +Proof. + intro H1. + Use x := 6 in (H1). + Fail It holds that (6 >= 5 -> True) (H1). +Abort. diff --git a/tests/tactics/Specialize.v b/tests/tactics/Specialize.v new file mode 100644 index 00000000..08d26cf7 --- /dev/null +++ b/tests/tactics/Specialize.v @@ -0,0 +1,98 @@ +(******************************************************************************) +(* This file is part of Waterproof-lib. *) +(* *) +(* Waterproof-lib is free software: you can redistribute it and/or modify *) +(* it under the terms of the GNU General Public License as published by *) +(* the Free Software Foundation, either version 3 of the License, or *) +(* (at your option) any later version. *) +(* *) +(* Waterproof-lib is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU General Public License for more details. *) +(* *) +(* You should have received a copy of the GNU General Public License *) +(* along with Waterproof-lib. If not, see . *) +(* *) +(******************************************************************************) + +Require Import Ltac2.Ltac2. +Require Import Ltac2.Message. + +Require Import Waterproof.Tactics. +Require Import Waterproof.Automation. + +(** Test 0: This should be the expected behavior. *) +Goal (forall n : nat, n = n) -> True. +Proof. +intro H. +Use n := 3 in (H). +It holds that (3 = 3). +Abort. + +(** Test 1: This should fail as the wrong variable name is chosen. *) +Goal (forall n : nat, n = n) -> True. +Proof. +intro H. +Fail Use m := (3) in (H). +Abort. + +(** Test 2: This should fail because the wrong goal is specified. *) +Goal (forall n : nat, n = n) -> True. +Proof. +intro H. +Use n := (3) in (H). +Fail It holds that (True). +Abort. + +(** Test 3: This should fail because first the wrapper needs to be resolved. *) +Goal (forall n : nat, n = n) -> True. +Proof. +intro H. +Use n := (3) in (H). +Fail exact I. +Abort. + +(** It should be possible to use an outside lemma *) +Local Parameter F : nat -> nat. +Local Parameter F_identity : forall n : nat, F n = n. + +Goal True. +Proof. +Fail It holds that (F 3 = 3). +Use n := (5) in (F_identity). +It holds that (F 5 = 5). +Abort. + +(** Test 4: cannot use specialize tactic for function, + throw readable error message stating as much. *) +Definition f : nat -> nat := fun (n : nat) => n. +Goal False. +Proof. + Fail Use n := 5 in (f). +Abort. + +(** Test 5: original universal quantifier hypohtesis left unchanged. *) +Goal (forall n : nat, n = n) -> True. +Proof. +intro H. +Use n := 3 in (H). +ltac1:(rename H into HH). (* test for hypohtesis without producing output *) +Abort. + +(** Test 6: multiple variable specifications *) +Goal (forall n m : nat, n = m) -> False. +Proof. +intro H. +Use n := 3, m := 4 in (H). +It holds that (3 = 4). +Abort. + +(** Test 7: multiple variable specifications, different order *) +Goal (forall n m : nat, n = m) -> False. +Proof. +intro H. +Use m := 4, n := 3 in (H). +Fail It holds that (4 = 3). (* as expected :) *) +It holds that (3 = 4). +Abort. diff --git a/theories/Automation/Framework.v b/theories/Automation/Framework.v index d7b2c0b6..99c148fa 100644 --- a/theories/Automation/Framework.v +++ b/theories/Automation/Framework.v @@ -17,4 +17,4 @@ (******************************************************************************) From Ltac2 Require Import Init. -Declare ML Module "waterproof:coq-waterproof.databases". \ No newline at end of file +Declare ML Module "waterproof:coq-waterproof.plugin". diff --git a/theories/Tactics.v b/theories/Tactics.v index 65703147..3c71b81f 100644 --- a/theories/Tactics.v +++ b/theories/Tactics.v @@ -33,6 +33,7 @@ Require Export Tactics.Help. Require Export Tactics.Induction. Require Export Tactics.ItHolds. Require Export Tactics.ItSuffices. +Require Export Tactics.Specialize. Require Export Tactics.Take. Require Export Tactics.ToShow. Require Export Tactics.Unfold. diff --git a/theories/Tactics/Choose.v b/theories/Tactics/Choose.v index 7fbb0270..9c58859e 100644 --- a/theories/Tactics/Choose.v +++ b/theories/Tactics/Choose.v @@ -22,6 +22,9 @@ Require Import Ltac2.Message. Require Import Util.Goals. Require Import Util.MessagesToUser. +Local Ltac2 concat_list (ls : message list) : message := + List.fold_right concat (of_string "") ls. + (* Ltac2 Type exn ::= [ ChooseError(string) ]. *) @@ -44,9 +47,13 @@ Require Import Util.MessagesToUser. Ltac2 choose_variable_in_exists_goal_with_renaming (s:ident) (t:constr) := lazy_match! goal with | [ |- exists _ : _, _] => - pose ($s := $t); + set ($s := $t); let v := Control.hyp s in let w := Fresh.fresh (Fresh.Free.of_goal ()) @_defeq in + match Constr.has_evar t with + | true => warn (concat_list [of_string "Please come back to this line later to make a definite choice for "; of_ident s; of_string "."]) + | _ => () + end; exists $v; assert ($w : $v = $t) by reflexivity | [ |- _ ] => throw (of_string "`Choose` can only be applied to 'exists' goals.") @@ -68,14 +75,17 @@ Ltac2 choose_variable_in_exists_goal_with_renaming (s:ident) (t:constr) := *) Ltac2 choose_variable_in_exists_no_renaming (t:constr) := lazy_match! goal with - | [ |- exists _ : _, _] => exists $t + | [ |- exists _ : _, _] => + match Constr.has_evar t with + | true => warn (concat_list [of_string "Please come back to this line later to make a definite choice."]); eexists $t + | false => exists $t + end | [ |- _ ] => throw (of_string "`Choose` can only be applied to 'exists' goals.") end. -Ltac2 Notation "Choose" s(opt(seq(ident, ":="))) t(constr) := +Ltac2 Notation "Choose" s(opt(seq(ident, ":="))) t(open_constr) := panic_if_goal_wrapped (); match s with | None => choose_variable_in_exists_no_renaming t | Some s => choose_variable_in_exists_goal_with_renaming s t end. - diff --git a/theories/Tactics/ItHolds.v b/theories/Tactics/ItHolds.v index 8bbe93d6..19425512 100644 --- a/theories/Tactics/ItHolds.v +++ b/theories/Tactics/ItHolds.v @@ -106,6 +106,25 @@ Local Ltac2 wp_assert_by (claim : constr) (label : ident option) (xtr_lemma : co Local Ltac2 wp_assert_since (claim : constr) (label : ident option) (xtr_claim : constr) := since_framework (core_wp_assert_by claim label) xtr_claim. +(** + Removes a [StateHyp.Wrapper] wrapper from the goal. + + Arguments: The statement in the specified hypothesis + + Does: + - Removes the wrapper [StateHyp.Wrapper A G]. + + Raises exception: + - (fatal) if the wrong hypothesis is specified. +*) +Local Ltac2 unwrap_state_hyp (statement : constr) := + lazy_match! goal with + | [|- StateHyp.Wrapper ?s _] => + if check_constr_equal s statement + then apply (StateHyp.wrap $s) + else throw (of_string "Wrong statement specified.") + | [|- _] => () + end. (** Attempts to assert a claim and proves it automatically using a specified lemma, @@ -141,6 +160,32 @@ Ltac2 Notation "Since" xtr_claim(constr) "it" "holds" "that" claim(constr) label - (fatal) if [rwaterprove] fails to prove the claim using the specified lemma. - [[label] is already used], if there is already another hypothesis with identifier [label]. *) + +Local Ltac2 wp_assert_with_unwrap (claim : constr) (label : ident option) := + (* Try out label first. + Code results in wrong error if done inside repeated match.. *) + match label with | None => () | Some label => try_out_label label end; + + match! goal with + | [h : ?s |- StateHyp.Wrapper ?s ?h_spec _] => + let h_constr := Control.hyp h in + (* sanity check "h = h_spec" *) + if check_constr_equal h_constr h_spec + then () + else fail; + + if check_constr_equal s claim + then apply (StateHyp.wrap $s) + else throw (of_string "Wrong statement specified."); + (* rename ident generated in specialize with user-specified label*) + match label with + | None => () + | Some label => Std.rename [(h, label)] + end + | [|- _] => + panic_if_goal_wrapped (); + wp_assert claim label + end. + Ltac2 Notation "It" "holds" "that" claim(constr) label(opt(seq("(", ident, ")"))) := - panic_if_goal_wrapped (); - wp_assert claim label. \ No newline at end of file + wp_assert_with_unwrap claim label. \ No newline at end of file diff --git a/theories/Tactics/Specialize.v b/theories/Tactics/Specialize.v new file mode 100644 index 00000000..61ac281b --- /dev/null +++ b/theories/Tactics/Specialize.v @@ -0,0 +1,74 @@ +(******************************************************************************) +(* This file is part of Waterproof-lib. *) +(* *) +(* Waterproof-lib is free software: you can redistribute it and/or modify *) +(* it under the terms of the GNU General Public License as published by *) +(* the Free Software Foundation, either version 3 of the License, or *) +(* (at your option) any later version. *) +(* *) +(* Waterproof-lib is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU General Public License for more details. *) +(* *) +(* You should have received a copy of the GNU General Public License *) +(* along with Waterproof-lib. If not, see . *) +(* *) +(******************************************************************************) + +Require Import Ltac2.Ltac2. +Require Import Ltac2.Message. + +Require Import Util.Init. +Require Import Util.Goals. +Require Import Util.MessagesToUser. + + +(** + Specializes a hypothesis that starts with a for-all statement. + + Arguments: + - [s : ident], name of the variable to choose + - [choice : constr], choice for the variable + - [in_hyp : ident], name of the hypothesis to specialize. + + Raises fatal exceptions: + - If the hypothesis [in_hyp] does not start with a for-all statement. +*) + +Local Ltac2 _ident_to_hyp_list (ls : (ident * constr) list) : (Std.hypothesis * constr) list +:= List.map (fun (i, x) => (Std.NamedHyp i, x)) ls. + +Local Ltac2 wp_specialize (var_choice_list : (ident * constr) list) (h:constr) := + (* let h := Control.hyp in_hyp in *) + (* let named_hyp := Std.NamedHyp s in *) + let statement := eval unfold type_of in (type_of $h) in + (* let specialized_statement := eval unfold type_of in (type_of ($h $choice)) in *) + lazy_match! statement with + | _ -> ?x => (* Exclude matching on functions (naming codomain necessary) *) + throw (of_string "`Pick ... in (*)` only works if (*) starts with a for-all quantifier.") + | forall _ : _, _ => + let w := Fresh.fresh (Fresh.Free.of_goal ()) @_H in + Std.specialize (h, Std.ExplicitBindings (_ident_to_hyp_list var_choice_list)) + (Some (Std.IntroNaming (Std.IntroIdentifier w))) ; + (* specialize $h with ($named_hyp := $choice) as $w; *) + (* Wrap the goal *) + let constr_w := Control.hyp w in + let type_w := Constr.type constr_w in + apply (StateHyp.unwrap $type_w $constr_w) + | _ => throw (of_string "`Pick ... in (*)` only works if (*) starts with a for-all quantifier.") + end. + +(** + Specializes a hypothesis that starts with a for-all statement. + + Arguments: + - [s : ident], name of the variable to choose + - [choice : constr], choice for the variable + - [in_hyp : ident], name of the hypothesis to specialize. + + Raises fatal exceptions: + - If the hypothesis [in_hyp] does not start with a for-all statement. +*) +Ltac2 Notation "Use" s(list1(seq(ident, ":=", constr), ",")) "in" "(" in_hyp(constr) ")" := + wp_specialize s in_hyp. diff --git a/theories/Util/Goals.v b/theories/Util/Goals.v index 26c06969..2822b741 100644 --- a/theories/Util/Goals.v +++ b/theories/Util/Goals.v @@ -96,6 +96,23 @@ Notation "'Add' 'the' 'following' 'line' 'to' 'the' 'proof:' 'We' 'need' 'to' 's format "'[ ' Add the following line to the proof: ']' '//' We need to show that ( G ). '//' or write: '//' We conclude that ( G ). '//' if no intermediary proof steps are required." ). +Module StateHyp. + + Private Inductive Wrapper (A : Type) (h : A) (G : Type) : Type := + | wrap : G -> Wrapper A h G. + + Definition unwrap (A : Type) (h : A) (G : Type) : Wrapper A h G -> G := + fun x => match x with wrap _ _ _ y => y end. + +End StateHyp. + +Notation "'Add' 'the' 'following' 'line' 'to' 'the' 'proof:' 'It' 'holds' 'that' '(' A ').'" := + (StateHyp.Wrapper A _ _) ( + at level 99, + only printing, + format "'[ ' Add the following line to the proof: ']' '//' It holds that ( A )." + ). + Module ByContradiction. Private Inductive Wrapper (A G : Type) : Type := @@ -147,6 +164,7 @@ Ltac2 panic_if_goal_wrapped () := | [|- NaturalInduction.Base.Wrapper _] => raise_goal_wrapped_error () | [|- NaturalInduction.Step.Wrapper _] => raise_goal_wrapped_error () | [|- StateGoal.Wrapper _] => raise_goal_wrapped_error () + | [|- StateHyp.Wrapper _ _ _] => raise_goal_wrapped_error () | [|- ByContradiction.Wrapper _ _] => raise_goal_wrapped_error () | [|- _] => () end. diff --git a/theories/Waterproof.v b/theories/Waterproof.v index 644e59ae..99c148fa 100644 --- a/theories/Waterproof.v +++ b/theories/Waterproof.v @@ -17,4 +17,4 @@ (******************************************************************************) From Ltac2 Require Import Init. -Declare ML Module "waterproof:coq-waterproof.plugin". \ No newline at end of file +Declare ML Module "waterproof:coq-waterproof.plugin".