diff --git a/IntroToProofScripting.v b/IntroToProofScripting.v
new file mode 100644
index 0000000..ee92c9e
--- /dev/null
+++ b/IntroToProofScripting.v
@@ -0,0 +1,878 @@
+(** Formal Reasoning About Programs
+ * Supplementary Coq material: introduction to proof scripting and the Ltac language
+ * Author: Adam Chlipala
+ * License: https://creativecommons.org/licenses/by-nc-nd/4.0/
+ * Much of the material comes from CPDT by the same author. *)
+
+Require Import Frap.
+
+Set Implicit Arguments.
+
+
+(** * Ltac Programming Basics *)
+
+(* We have already seen a few examples of Ltac programs, without much explanation.
+ * Ltac is the proof scripting language built into Coq. Actually, every
+ * primitive step in our proofs has been a (degenerate, small) Ltac program.
+ * Let's take a bottom-up look at more Ltac features.
+ *
+ * We've seen [match goal] tactics a few times so far. They allow syntactic
+ * inspection of hypothesis and conclusion formulas of current goals, where we
+ * pick tactics to run based on what we find. Here's a simple example to
+ * find an [if] and do a case split based on its test expression. *)
+
+Ltac find_if :=
+ match goal with
+ | [ |- if ?X then _ else _ ] => cases X
+ end.
+
+(* Here's a proof that becomes trivial, given [find_if]. You can imagine a
+ * whole family of similar theorems that also become trivial. *)
+
+Theorem hmm : forall (a b c : bool),
+ if a
+ then if b
+ then True
+ else True
+ else if c
+ then True
+ else True.
+Proof.
+ simplify.
+ repeat find_if. (* Note [repeat] for "run over and over until you can't
+ * progress." *)
+ trivial. (* A fun tactic that consults a database of really boring steps. *)
+ trivial.
+ trivial.
+ trivial.
+
+ (* Let's write that again with more automation. *)
+ Restart.
+ simplify; repeat find_if; trivial.
+Qed.
+
+(* Another very useful Ltac building block is *context patterns*. *)
+
+Ltac find_if_inside :=
+ match goal with
+ | [ |- context[if ?X then _ else _] ] => cases X
+ end.
+
+(* The behavior of this tactic is to find any subterm of the conclusion that is
+ * an [if] and then [cases] the test expression. This version subsumes
+ * [find_if]. The general behavior of [context] (an Ltac keyword) is to allow
+ * matching in arbitrary subterms. *)
+
+Theorem hmm' : forall (a b c : bool),
+ if a
+ then if b
+ then True
+ else True
+ else if c
+ then True
+ else True.
+Proof.
+ simplify; repeat find_if_inside; trivial.
+Qed.
+
+(* We can also use [find_if_inside] to prove goals that [find_if] does not
+ * simplify sufficiently. *)
+
+Theorem hmm2 : forall (a b : bool),
+ (if a then 42 else 42) = (if b then 42 else 42).
+Proof.
+ simplify; repeat find_if_inside; equality.
+Qed.
+
+
+(** * Automating the two-thread locked-increment example from TransitionSystems *)
+
+(* Let's experience the process of gradually automating the proof we finished
+ * the last lecture with. Here's the system-definition code, stripped of
+ * comments. *)
+
+Inductive increment_program :=
+| Lock
+| Read
+| Write (local : nat)
+| Unlock
+| Done.
+
+Record inc_state := {
+ Locked : bool;
+ Global : nat
+}.
+
+Record threaded_state shared private := {
+ Shared : shared;
+ Private : private
+}.
+
+Definition increment_state := threaded_state inc_state increment_program.
+
+Inductive increment_init : increment_state -> Prop :=
+| IncInit :
+ increment_init {| Shared := {| Locked := false; Global := O |};
+ Private := Lock |}.
+
+Inductive increment_step : increment_state -> increment_state -> Prop :=
+| IncLock : forall g,
+ increment_step {| Shared := {| Locked := false; Global := g |};
+ Private := Lock |}
+ {| Shared := {| Locked := true; Global := g |};
+ Private := Read |}
+| IncRead : forall l g,
+ increment_step {| Shared := {| Locked := l; Global := g |};
+ Private := Read |}
+ {| Shared := {| Locked := l; Global := g |};
+ Private := Write g |}
+| IncWrite : forall l g v,
+ increment_step {| Shared := {| Locked := l; Global := g |};
+ Private := Write v |}
+ {| Shared := {| Locked := l; Global := S v |};
+ Private := Unlock |}
+| IncUnlock : forall l g,
+ increment_step {| Shared := {| Locked := l; Global := g |};
+ Private := Unlock |}
+ {| Shared := {| Locked := false; Global := g |};
+ Private := Done |}.
+
+Definition increment_sys := {|
+ Initial := increment_init;
+ Step := increment_step
+|}.
+
+Inductive parallel1 shared private1 private2
+ (init1 : threaded_state shared private1 -> Prop)
+ (init2 : threaded_state shared private2 -> Prop)
+ : threaded_state shared (private1 * private2) -> Prop :=
+| Pinit : forall sh pr1 pr2,
+ init1 {| Shared := sh; Private := pr1 |}
+ -> init2 {| Shared := sh; Private := pr2 |}
+ -> parallel1 init1 init2 {| Shared := sh; Private := (pr1, pr2) |}.
+
+Inductive parallel2 shared private1 private2
+ (step1 : threaded_state shared private1 -> threaded_state shared private1 -> Prop)
+ (step2 : threaded_state shared private2 -> threaded_state shared private2 -> Prop)
+ : threaded_state shared (private1 * private2)
+ -> threaded_state shared (private1 * private2) -> Prop :=
+| Pstep1 : forall sh pr1 pr2 sh' pr1',
+ step1 {| Shared := sh; Private := pr1 |} {| Shared := sh'; Private := pr1' |}
+ -> parallel2 step1 step2 {| Shared := sh; Private := (pr1, pr2) |}
+ {| Shared := sh'; Private := (pr1', pr2) |}
+| Pstep2 : forall sh pr1 pr2 sh' pr2',
+ step2 {| Shared := sh; Private := pr2 |} {| Shared := sh'; Private := pr2' |}
+ -> parallel2 step1 step2 {| Shared := sh; Private := (pr1, pr2) |}
+ {| Shared := sh'; Private := (pr1, pr2') |}.
+
+Definition parallel shared private1 private2
+ (sys1 : trsys (threaded_state shared private1))
+ (sys2 : trsys (threaded_state shared private2)) := {|
+ Initial := parallel1 sys1.(Initial) sys2.(Initial);
+ Step := parallel2 sys1.(Step) sys2.(Step)
+|}.
+
+Definition increment2_sys := parallel increment_sys increment_sys.
+
+Definition contribution_from (pr : increment_program) : nat :=
+ match pr with
+ | Unlock => 1
+ | Done => 1
+ | _ => 0
+ end.
+
+Definition has_lock (pr : increment_program) : bool :=
+ match pr with
+ | Read => true
+ | Write _ => true
+ | Unlock => true
+ | _ => false
+ end.
+
+Definition shared_from_private (pr1 pr2 : increment_program) :=
+ {| Locked := has_lock pr1 || has_lock pr2;
+ Global := contribution_from pr1 + contribution_from pr2 |}.
+
+Definition instruction_ok (self other : increment_program) :=
+ match self with
+ | Lock => True
+ | Read => has_lock other = false
+ | Write n => has_lock other = false /\ n = contribution_from other
+ | Unlock => has_lock other = false
+ | Done => True
+ end.
+
+Inductive increment2_invariant :
+ threaded_state inc_state (increment_program * increment_program) -> Prop :=
+| Inc2Inv : forall pr1 pr2,
+ instruction_ok pr1 pr2
+ -> instruction_ok pr2 pr1
+ -> increment2_invariant {| Shared := shared_from_private pr1 pr2; Private := (pr1, pr2) |}.
+
+Lemma Inc2Inv' : forall sh pr1 pr2,
+ sh = shared_from_private pr1 pr2
+ -> instruction_ok pr1 pr2
+ -> instruction_ok pr2 pr1
+ -> increment2_invariant {| Shared := sh; Private := (pr1, pr2) |}.
+Proof.
+ simplify.
+ rewrite H.
+ apply Inc2Inv; assumption.
+Qed.
+
+(* OK, HERE is where prove the main theorem. This source file doesn't leave a
+ * record of the trail of intermediate, less-automated versions, but we develop
+ * it step-by-step in class. *)
+
+Theorem increment2_invariant_ok : invariantFor increment2_sys increment2_invariant.
+Proof.
+ apply invariant_induction; simplify;
+ repeat (match goal with
+ | [ H : parallel1 _ _ _ |- _ ] => invert H
+ | [ H : parallel2 _ _ _ _ |- _ ] => invert H
+ | [ H : increment_init _ |- _ ] => invert H
+ | [ H : increment2_invariant _ |- _ ] => invert H
+ | [ H : increment_step _ _ |- _ ] => invert H
+ | [ H : instruction_ok ?pr _ |- _ ] => cases pr
+ | [ |- increment2_invariant _ ] => apply Inc2Inv'
+ | [ |- context[shared_from_private] ] => unfold shared_from_private
+ end; simplify; try equality).
+Qed.
+
+
+(** * Implementing some of [propositional] ourselves *)
+
+(* In class, we develop our own implementation of [propositional] one feature
+ * at a time, but here's just the final product. To understand it, we print
+ * the definitions of the logical connectives. Interestingly enough, they are
+ * special cases of the machinery we met last time for inductive relations! *)
+
+Print True.
+Print False.
+Locate "/\".
+Print and.
+Locate "\/".
+Print or.
+(* Implication ([->]) is built into Coq, so nothing to look up there. *)
+
+Ltac my_tauto :=
+ repeat match goal with
+ | [ H : ?P |- ?P ] => exact H
+
+ | [ |- True ] => constructor
+ | [ |- _ /\ _ ] => constructor
+ | [ |- _ -> _ ] => intro
+
+ | [ H : False |- _ ] => cases H
+ | [ H : _ /\ _ |- _ ] => cases H
+ | [ H : _ \/ _ |- _ ] => cases H
+
+ | [ H1 : ?P -> ?Q, H2 : ?P |- _ ] => specialize (H1 H2)
+ end.
+
+(* Note on some new tactics:
+ * - [intro]: goes from proving [P1 -> P2] to proving [P2] with [P1] as a
+ * hypothesis.
+ * - [specialize (H e1 .. eN)]: replace a hypothesis with a version that is
+ * specialized to a provided set of arguments (for quantified variables or
+ * local hypotheses from implications). By convention, when the argument to
+ * [specialize] is an application of a hypothesis [H] to a set of arguments,
+ * the result of the specialization replaces [H]. *)
+
+Section propositional.
+ Variables P Q R : Prop.
+
+ Theorem propositional : (P \/ Q \/ False) /\ (P -> Q) -> True /\ Q.
+ Proof.
+ my_tauto.
+ Qed.
+End propositional.
+
+(* [match goal] has useful backtracking semantics. When one rule fails, we
+ * backtrack automatically to the next one. *)
+
+(* For instance, this (unnecessarily verbose) proof script works: *)
+
+Theorem m1 : True.
+Proof.
+ match goal with
+ | [ |- _ ] => intro
+ | [ |- True ] => constructor
+ end.
+Qed.
+
+(* The example shows how failure can move to a different pattern within a
+ * [match]. Failure can also trigger an attempt to find _a different way of
+ * matching a single pattern_. Consider another example: *)
+
+Theorem m2 : forall P Q R : Prop, P -> Q -> R -> Q.
+Proof.
+ intros; match goal with
+ | [ H : _ |- _ ] => idtac H
+ end.
+
+ (* Coq prints "[H1]". By applying [idtac] with an argument, a convenient
+ * debugging tool for "leaking information out of [match]es," we see that
+ * this [match] first tries binding [H] to [H1], which cannot be used to prove
+ * [Q]. Nonetheless, the following variation on the tactic succeeds at
+ * proving the goal: *)
+
+ match goal with
+ | [ H : _ |- _ ] => idtac H; exact H
+ end.
+Qed.
+
+(* The tactic first unifies [H] with [H1], as before, but [exact H] fails in
+ * that case, so the tactic engine searches for more possible values of [H].
+ * Eventually, it arrives at the correct value, so that [exact H] and the
+ * overall tactic succeed. *)
+
+(* Let's try some more ambitious reasoning, with quantifiers. We'll be
+ * instantiating quantified facts heuristically. If we're not careful, we get
+ * in a loop repeating the same instantiation forever. We'll need a way to
+ * check that a fact is not already known. Here's a tactic: *)
+
+Ltac notHyp P :=
+ match goal with
+ | [ _ : P |- _ ] => fail 1
+ (* A hypothesis already asserts this fact. *)
+ | _ =>
+ match P with
+ | ?P1 /\ ?P2 =>
+ (* Check each conjunct of [P] separately, since they might be known by
+ * different means. *)
+ first [ notHyp P1 | notHyp P2 | fail 2 ]
+ | _ => idtac
+ (* If we manage to get this far, then we found no redundancy, so
+ * declare success. *)
+ end
+ end.
+
+(* The number for [fail N] indicates failing at the backtracking point [N]
+ * levels out from where we are. [first] applies the first tactic that does not
+ * fail. *)
+
+(* This tactic adds a fact to the context, only if it is not not already
+ * present. *)
+
+Ltac extend pf :=
+ let t := type of pf in
+ notHyp t; pose proof pf.
+
+(* With these tactics defined, we can write a tactic [completer] for, among
+ * other things, adding to the context all consequences of a set of simple
+ * first-order formulas. *)
+
+Ltac completer :=
+ repeat match goal with
+ | [ H : _ /\ _ |- _ ] => cases H
+ | [ H : ?P -> ?Q, H' : ?P |- _ ] => specialize (H H')
+
+ | [ H : forall x, ?P x -> _, H' : ?P ?X |- _ ] => extend (H X H')
+
+ | [ |- _ /\ _ ] => constructor
+ | [ |- forall x, _ ] => intro
+ | [ |- _ -> _ ] => intro
+ (* Interestingly, these last two rules are redundant.
+ * See CPDT for details.... *)
+ end.
+
+Section firstorder.
+ Variable A : Set.
+ Variables P Q R S : A -> Prop.
+
+ Hypothesis H1 : forall x, P x -> Q x /\ R x.
+ Hypothesis H2 : forall x, R x -> S x.
+
+ Theorem fo : forall (y x : A), P x -> S x.
+ Proof.
+ completer.
+ assumption.
+ Qed.
+End firstorder.
+
+
+(** * Functional Programming in Ltac *)
+
+(* Let's write a list-length function in Ltac rather than Gallina. In class,
+ * we'll muddle through some intermediate versions before getting to the first
+ * version that at least parses. *)
+
+Module Import FirstTry.
+ Ltac length ls :=
+ match ls with
+ | nil => O
+ | _ :: ?ls' => constr:(S (length ls'))
+ end.
+End FirstTry.
+
+Goal False.
+ let n := length (1 :: 2 :: 3 :: nil) in
+ pose n.
+Abort.
+
+(* Something went wrong there. *)
+
+Ltac length ls :=
+ match ls with
+ | nil => O
+ | _ :: ?ls' =>
+ let ls'' := length ls' in
+ constr:(S ls'')
+ end.
+
+Goal False.
+ let n := length (1 :: 2 :: 3 :: nil) in
+ pose n.
+Abort.
+
+(* Here's a [map] implementation in Ltac. Strangely, it needs to be passed the
+ * type of the new list explicitly. *)
+
+Ltac map T f :=
+ let rec map' ls :=
+ match ls with
+ | nil => constr:(@nil T)
+ | ?x :: ?ls' =>
+ let x' := f x in
+ let ls'' := map' ls' in
+ constr:(x' :: ls'')
+ end in
+ map'.
+
+Goal False.
+ let ls := map (nat * nat)%type ltac:(fun x => constr:((x, x))) (1 :: 2 :: 3 :: nil) in
+ pose ls.
+Abort.
+
+(* Now let's revisit [length] and see how we might implement "printf debugging"
+ * for it. *)
+
+Module Import WithPrinting.
+ Ltac length ls :=
+ idtac ls;
+ match ls with
+ | nil => O
+ | _ :: ?ls' =>
+ let ls'' := length ls' in
+ constr:(S ls'')
+ end.
+End WithPrinting.
+
+Goal False.
+ (*let n := length (1 :: 2 :: 3 :: nil) in
+ pose n.*)
+ (* Oh, that has a dynamic type error. *)
+Abort.
+
+(* The problem is that Ltac as a language contains several datatypes. One of
+ * them is "tactic sequence," which can't be mixed with other datatypes like
+ * "term in the logic." Tactic sequences don't return results. We can use
+ * continuation-passing style as a mitigation. *)
+
+Module Import WithPrintingFixed.
+ Ltac length ls k :=
+ idtac ls;
+ match ls with
+ | nil => k O
+ | _ :: ?ls' => length ls' ltac:(fun n => k (S n))
+ end.
+End WithPrintingFixed.
+
+Goal False.
+ length (1 :: 2 :: 3 :: nil) ltac:(fun n => pose n).
+Abort.
+
+
+(** * Recursive Proof Search *)
+
+(* Let's work on a tactic to try all possible instantiations of quantified
+ * hypotheses, attempting to find out where the goal becomes obvious. *)
+
+Ltac inster n :=
+ intuition; (* <-- A fancier version of [propositional] whose details we won't
+ * dwell on *)
+ match n with
+ | S ?n' =>
+ match goal with
+ | [ H : forall x : ?T, _, y : ?T |- _ ] => pose proof (H y); inster n'
+ end
+ end.
+
+(* Important: when one recursive call fails, the backtracking semantics of
+ * [match goal] cause us to try the next instantiation! *)
+
+Section test_inster.
+ Variable A : Set.
+ Variables P Q : A -> Prop.
+ Variable f : A -> A.
+ Variable g : A -> A -> A.
+
+ Hypothesis H1 : forall x y, P (g x y) -> Q (f x).
+
+ Theorem test_inster : forall x, P (g x x) -> Q (f x).
+ Proof.
+ inster 2.
+ Qed.
+
+ Hypothesis H3 : forall u v, P u /\ P v /\ u <> v -> P (g u v).
+ Hypothesis H4 : forall u, Q (f u) -> P u /\ P (f u).
+
+ Theorem test_inster2 : forall x y, x <> y -> P x -> Q (f y) -> Q (f x).
+ Proof.
+ inster 3.
+ Qed.
+End test_inster.
+
+(** ** A fancier example of proof search (probably skipped on first
+ reading/run-through) *)
+
+Definition imp (P1 P2 : Prop) := P1 -> P2.
+Infix "-->" := imp (no associativity, at level 95).
+Ltac imp := unfold imp; firstorder.
+
+(** These lemmas about [imp] will be useful in the tactic that we will write. *)
+
+Theorem and_True_prem : forall P Q,
+ (P /\ True --> Q)
+ -> (P --> Q).
+Proof.
+ imp.
+Qed.
+
+Theorem and_True_conc : forall P Q,
+ (P --> Q /\ True)
+ -> (P --> Q).
+Proof.
+ imp.
+Qed.
+
+Theorem pick_prem1 : forall P Q R S,
+ (P /\ (Q /\ R) --> S)
+ -> ((P /\ Q) /\ R --> S).
+Proof.
+ imp.
+Qed.
+
+Theorem pick_prem2 : forall P Q R S,
+ (Q /\ (P /\ R) --> S)
+ -> ((P /\ Q) /\ R --> S).
+Proof.
+ imp.
+Qed.
+
+Theorem comm_prem : forall P Q R,
+ (P /\ Q --> R)
+ -> (Q /\ P --> R).
+Proof.
+ imp.
+Qed.
+
+Theorem pick_conc1 : forall P Q R S,
+ (S --> P /\ (Q /\ R))
+ -> (S --> (P /\ Q) /\ R).
+Proof.
+ imp.
+Qed.
+
+Theorem pick_conc2 : forall P Q R S,
+ (S --> Q /\ (P /\ R))
+ -> (S --> (P /\ Q) /\ R).
+Proof.
+ imp.
+Qed.
+
+Theorem comm_conc : forall P Q R,
+ (R --> P /\ Q)
+ -> (R --> Q /\ P).
+Proof.
+ imp.
+Qed.
+
+Ltac search_prem tac :=
+ let rec search P :=
+ tac
+ || (apply and_True_prem; tac)
+ || match P with
+ | ?P1 /\ ?P2 =>
+ (apply pick_prem1; search P1)
+ || (apply pick_prem2; search P2)
+ end
+ in match goal with
+ | [ |- ?P /\ _ --> _ ] => search P
+ | [ |- _ /\ ?P --> _ ] => apply comm_prem; search P
+ | [ |- _ --> _ ] => progress (tac || (apply and_True_prem; tac))
+ end.
+
+Ltac search_conc tac :=
+ let rec search P :=
+ tac
+ || (apply and_True_conc; tac)
+ || match P with
+ | ?P1 /\ ?P2 =>
+ (apply pick_conc1; search P1)
+ || (apply pick_conc2; search P2)
+ end
+ in match goal with
+ | [ |- _ --> ?P /\ _ ] => search P
+ | [ |- _ --> _ /\ ?P ] => apply comm_conc; search P
+ | [ |- _ --> _ ] => progress (tac || (apply and_True_conc; tac))
+ end.
+
+Theorem False_prem : forall P Q,
+ False /\ P --> Q.
+Proof.
+ imp.
+Qed.
+
+Theorem True_conc : forall P Q : Prop,
+ (P --> Q)
+ -> (P --> True /\ Q).
+Proof.
+ imp.
+Qed.
+
+Theorem Match : forall P Q R : Prop,
+ (Q --> R)
+ -> (P /\ Q --> P /\ R).
+Proof.
+ imp.
+Qed.
+
+Theorem ex_prem : forall (T : Type) (P : T -> Prop) (Q R : Prop),
+ (forall x, P x /\ Q --> R)
+ -> (ex P /\ Q --> R).
+Proof.
+ imp.
+Qed.
+
+Theorem ex_conc : forall (T : Type) (P : T -> Prop) (Q R : Prop) x,
+ (Q --> P x /\ R)
+ -> (Q --> ex P /\ R).
+Proof.
+ imp.
+Qed.
+
+Theorem imp_True : forall P,
+ P --> True.
+Proof.
+ imp.
+Qed.
+
+Ltac matcher :=
+ intros;
+ repeat search_prem ltac:(simple apply False_prem || (simple apply ex_prem; intro));
+ repeat search_conc ltac:(simple apply True_conc || simple eapply ex_conc
+ || search_prem ltac:(simple apply Match));
+ try simple apply imp_True.
+
+(* Our tactic succeeds at proving a simple example. *)
+
+Theorem t2 : forall P Q : Prop,
+ Q /\ (P /\ False) /\ P --> P /\ Q.
+Proof.
+ matcher.
+Qed.
+
+(* In the generated proof, we find a trace of the workings of the search tactics. *)
+
+Print t2.
+
+(* We can also see that [matcher] is well-suited for cases where some human
+ * intervention is needed after the automation finishes. *)
+
+Theorem t3 : forall P Q R : Prop,
+ P /\ Q --> Q /\ R /\ P.
+Proof.
+ matcher.
+Abort.
+
+(* The [matcher] tactic even succeeds at guessing quantifier instantiations. It
+ * is the unification that occurs in uses of the [Match] lemma that does the
+ * real work here. *)
+
+Theorem t4 : forall (P : nat -> Prop) Q, (exists x, P x /\ Q) --> Q /\ (exists x, P x).
+Proof.
+ matcher.
+Qed.
+
+Print t4.
+
+
+(** * Creating Unification Variables *)
+
+(* A final useful ingredient in tactic crafting is the ability to allocate new
+ * unification variables explicitly. Before we are ready to write a tactic, we
+ * can try out its ingredients one at a time. *)
+
+Theorem t5 : (forall x : nat, S x > x) -> 2 > 1.
+ intros.
+
+ evar (y : nat).
+
+ let y' := eval unfold y in y in
+ clear y; specialize (H y').
+
+ apply H.
+Qed.
+
+Ltac newEvar T k :=
+ let x := fresh "x" in
+ evar (x : T);
+ let x' := eval unfold x in x in
+ clear x; k x'.
+
+Ltac insterU H :=
+ repeat match type of H with
+ | forall x : ?T, _ =>
+ newEvar T ltac:(fun y => specialize (H y))
+ end.
+
+Theorem t5' : (forall x : nat, S x > x) -> 2 > 1.
+Proof.
+ intro H.
+ insterU H.
+ apply H.
+Qed.
+
+(* This particular example is somewhat silly, since [apply] by itself would have
+ * solved the goal originally. Separate forward reasoning is more useful on
+ * hypotheses that end in existential quantifications. Before we go through an
+ * example, it is useful to define a variant of [insterU] that does not clear
+ * the base hypothesis we pass to it. *)
+
+Ltac insterKeep H :=
+ let H' := fresh "H'" in
+ pose proof H as H'; insterU H'.
+
+Section t6.
+ Variables A B : Type.
+ Variable P : A -> B -> Prop.
+ Variable f : A -> A -> A.
+ Variable g : B -> B -> B.
+
+ Hypothesis H1 : forall v, exists u, P v u.
+ Hypothesis H2 : forall v1 u1 v2 u2,
+ P v1 u1
+ -> P v2 u2
+ -> P (f v1 v2) (g u1 u2).
+
+ Theorem t6 : forall v1 v2, exists u1, exists u2, P (f v1 v2) (g u1 u2).
+ Proof.
+ intros.
+
+ do 2 insterKeep H1.
+
+ repeat match goal with
+ | [ H : ex _ |- _ ] => destruct H
+ end.
+
+ eexists.
+ eexists.
+ apply H2.
+ exact H.
+ exact p.
+ (* In two weeks, we'll meet [eauto], which can do these last steps
+ * automatically. *)
+ Qed.
+End t6.
+
+(* Here's an example where something bad happens. *)
+
+Section t7.
+ Variables A B : Type.
+ Variable Q : A -> Prop.
+ Variable P : A -> B -> Prop.
+ Variable f : A -> A -> A.
+ Variable g : B -> B -> B.
+
+ Hypothesis H1 : forall v, Q v -> exists u, P v u.
+ Hypothesis H2 : forall v1 u1 v2 u2,
+ P v1 u1
+ -> P v2 u2
+ -> P (f v1 v2) (g u1 u2).
+
+ Theorem t7 : forall v1 v2, Q v1 -> Q v2 -> exists u1, exists u2, P (f v1 v2) (g u1 u2).
+ Proof.
+ intros; do 2 insterKeep H1;
+ repeat match goal with
+ | [ H : ex _ |- _ ] => destruct H
+ end; eauto.
+
+ (* Oh, two trivial goals remain. *)
+ Unshelve.
+ assumption.
+ assumption.
+ Qed.
+End t7.
+
+(* Why did we need to do that extra work? The [forall] rule was also matching
+ * implications! *)
+
+Module Import FixedInster.
+ Ltac insterU tac H :=
+ repeat match type of H with
+ | forall x : ?T, _ =>
+ match type of T with
+ | Prop =>
+ (let H' := fresh "H'" in
+ assert (H' : T) by solve [ tac ];
+ specialize (H H'); clear H')
+ || fail 1
+ | _ =>
+ newEvar T ltac:(fun y => specialize (H y))
+ end
+ end.
+
+ Ltac insterKeep tac H :=
+ let H' := fresh "H'" in
+ pose proof H as H'; insterU tac H'.
+End FixedInster.
+
+Section t7'.
+ Variables A B : Type.
+ Variable Q : A -> Prop.
+ Variable P : A -> B -> Prop.
+ Variable f : A -> A -> A.
+ Variable g : B -> B -> B.
+
+ Hypothesis H1 : forall v, Q v -> exists u, P v u.
+ Hypothesis H2 : forall v1 u1 v2 u2,
+ P v1 u1
+ -> P v2 u2
+ -> P (f v1 v2) (g u1 u2).
+
+ Theorem t7' : forall v1 v2, Q v1 -> Q v2 -> exists u1, exists u2, P (f v1 v2) (g u1 u2).
+ Proof.
+ intros.
+ do 2 insterKeep ltac:(idtac; match goal with
+ | [ H : Q ?v |- _ ] =>
+ match goal with
+ | [ _ : context[P v _] |- _ ] => fail 1
+ | _ => apply H
+ end
+ end) H1;
+ repeat match goal with
+ | [ H : ex _ |- _ ] => destruct H
+ end; eauto.
+ Qed.
+End t7'.
+
+Theorem t8 : exists p : nat * nat, fst p = 3.
+Proof.
+ econstructor.
+ instantiate (1 := (3, 2)).
+ equality.
+Qed.
+
+(* A way that plays better with automation: *)
+
+Ltac equate x y :=
+ let dummy := constr:(eq_refl x : x = y) in idtac.
+
+Theorem t9 : exists p : nat * nat, fst p = 3.
+Proof.
+ econstructor; match goal with
+ | [ |- fst ?x = 3 ] => equate x (3, 2)
+ end; equality.
+Qed.
diff --git a/README.md b/README.md
index a42920f..1052404 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,7 @@ The main narrative, also present in the book PDF, presents standard program-proo
* Chapter 3: `DataAbstraction.v`
* Chapter 4: `Interpreters.v`
* Chapter 5: `TransitionSystems.v`
+ * `IntroToProofScripting.v`: writing scripts to find proofs in Coq
* Chapter 6: `ModelChecking.v`
* Chapter 7: `OperationalSemantics.v`
* Chapter 8: `AbstractInterpretation.v`
diff --git a/_CoqProject b/_CoqProject
index 10db5b8..3a9aa2c 100644
--- a/_CoqProject
+++ b/_CoqProject
@@ -18,6 +18,7 @@ Interpreters_template.v
Interpreters.v
TransitionSystems_template.v
TransitionSystems.v
+IntroToProofScripting.v
ModelChecking_template.v
ModelChecking.v
OperationalSemantics_template.v