summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSon Ho2023-01-20 15:46:45 +0100
committerSon HO2023-06-04 21:54:38 +0200
commitf472f09507c9033e9b93d0a973401169ae669688 (patch)
tree8ac84a3bb84d55a49498f8fa86aa47c490df8996
parent7453d8e8dc8dda419e61ef3194e157e7534c85ef (diff)
Make progress on implementing tactics
Diffstat (limited to '')
-rw-r--r--backends/hol4/Test.sml374
1 files changed, 347 insertions, 27 deletions
diff --git a/backends/hol4/Test.sml b/backends/hol4/Test.sml
index 04bc3ec3..a01211d0 100644
--- a/backends/hol4/Test.sml
+++ b/backends/hol4/Test.sml
@@ -448,6 +448,7 @@ QED
(* Add a list of theorems in the assumptions - TODO: move *)
fun ASSUME_TACL (thms : thm list) : tactic =
let
+ (* TODO: use MAP_EVERY *)
fun t thms =
case thms of
[] => ALL_TAC
@@ -456,6 +457,12 @@ fun ASSUME_TACL (thms : thm list) : tactic =
t thms
end
+(* Drop/forget a theorem.
+
+ To be used in conjunction with {!pop_assum} for instance.
+ *)
+fun DROP_TAC (_ : thm) : tactic = ALL_TAC
+
(* The map from integer type to bounds lemmas *)
val integer_bounds_lemmas =
Redblackmap.fromList String.compare
@@ -464,18 +471,81 @@ val integer_bounds_lemmas =
("i32", i32_to_int_bounds)
]
+(* The map from integer type to conversion lemmas *)
+val integer_conversion_lemmas =
+ Redblackmap.fromList String.compare
+ [
+ ("u32", int_to_u32_id),
+ ("i32", int_to_i32_id)
+ ]
+
+val integer_conversion_lemmas_list =
+ map snd (Redblackmap.listItems integer_conversion_lemmas)
+
+(* Not sure how term nets work, nor how we are supposed to convert Term.term
+ to mlibTerm.term.
+
+ TODO: it seems we need to explore the term and convert everything to strings.
+ *)
+fun term_to_mlib_term (t : term) : mlibTerm.term =
+ mlibTerm.string_to_term (term_to_string t)
+
+(*
+(* The lhs of the conclusion of the integer conversion lemmas - we use this for
+ pattern matching *)
+val integer_conversion_lhs_concls =
+ let
+ val thms = map snd (Redblackmap.listItems integer_conversion_lemmas);
+ val concls = map (lhs o concl o UNDISCH_ALL o SPEC_ALL) thms;
+ in concls end
+*)
+
+(*
+val integer_conversion_concls_net =
+ let
+ val maplets = map (fn x => fst (dest_eq x) |-> ()) integer_conversion_concls;
+
+ val maplets = map (fn x => fst (mlibTerm.dest_eq x) |-> ()) integer_conversion_concls;
+ val maplets = map (fn x => fst (mlibThm.dest_unit_eq x) |-> ()) integer_conversion_concls;
+ val parameters = { fifo=false };
+ in mlibTermnet.from_maplets parameters maplets end
+
+mlibTerm.string_to_term (term_to_string “u32_to_int (int_to_u32 n) = n”)
+term_to_quote
+
+SIMP_CONV
+mlibThm.dest_thm u32_to_int_bounds
+mlibThm.dest_unit u32_to_int_bounds
+*)
+
(* The integer types *)
val integer_types_names =
Redblackset.fromList String.compare
(map fst (Redblackmap.listItems integer_bounds_lemmas))
+val all_integer_bounds = [
+ u32_max_def,
+ i32_min_def,
+ i32_max_def
+]
+
+(* Small utility: compute the set of assumptions in the context.
+
+ We isolate this code in a utility in order to be able to improve it:
+ for now we simply put all the assumptions in a set, but in the future
+ we might want to split the assumptions which are conjunctions in order
+ to be more precise.
+ *)
+fun compute_asms_set ((asms,g) : goal) : term Redblackset.set =
+ Redblackset.fromList Term.compare asms
+
(* See {!assume_bounds_for_all_int_vars}.
This tactic is in charge of adding assumptions for one variable.
*)
fun assume_bounds_for_int_var
- (asms: term Redblackset.set) (var : string) (ty : string) :
+ (asms_set: term Redblackset.set) (var : string) (ty : string) :
tactic =
let
(* Lookup the lemma to apply *)
@@ -495,12 +565,43 @@ fun assume_bounds_for_int_var
(* Filter the conjuncts: some of them might already be in the context,
we don't want to introduce them again, as it would pollute it.
*)
- val lemmas = filter (fn lem => not (Redblackset.member (asms, concl lem))) lemmas;
+ val lemmas = filter (fn lem => not (Redblackset.member (asms_set, concl lem))) lemmas;
in
(* Introduce the assumptions in the context *)
ASSUME_TACL lemmas
end
+(* Destruct if possible a term of the shape: [x y],
+ where [x] is not a comb.
+
+ Returns [(x, y)]
+ *)
+fun dest_single_comb (t : term) : (term * term) option =
+ case strip_comb t of
+ (x, [y]) => SOME (x, y)
+ | _ => NONE
+
+(** Destruct if possible a term of the shape: [x (y z)].
+ Returns [(x, y, z)]
+ *)
+fun dest_single_comb_twice (t : term) : (term * term * term) option =
+ case dest_single_comb t of
+ NONE => NONE
+ | SOME (x, y) =>
+ case dest_single_comb y of
+ NONE => NONE
+ | SOME (y, z) => SOME (x, y, z)
+
+(* A utility map to lookup integer conversion lemmas *)
+val integer_conversion_pat_map =
+ let
+ val thms = map snd (Redblackmap.listItems integer_conversion_lemmas);
+ val tl = map (lhs o concl o UNDISCH_ALL o SPEC_ALL) thms;
+ val tl = map (valOf o dest_single_comb_twice) tl;
+ val tl = map (fn (x, y, _) => (x, y)) tl;
+ val m = Redblackmap.fromList Term.compare tl
+ in m end
+
(* Introduce bound assumptions for all the machine integers in the context.
Exemple:
@@ -516,7 +617,7 @@ fun assume_bounds_for_all_int_vars (asms, g) =
(* Compute the set of integer variables in the context *)
val vars = free_varsl (g :: asms);
(* Compute the set of assumptions already present in the context *)
- val asms_set = Redblackset.fromList Term.compare vars;
+ val asms_set = compute_asms_set (asms, g);
(* Filter the variables to keep only the ones with type machine integer,
decompose the types at the same time *)
fun decompose_var (v : term) : (string * string) =
@@ -532,6 +633,7 @@ fun assume_bounds_for_all_int_vars (asms, g) =
fun add_var_asm (v, ty) : tactic =
assume_bounds_for_int_var asms_set v ty;
(* Add assumptions for all the variables *)
+ (* TODO: use MAP_EVERY *)
fun add_vars_asm vl : tactic =
case vl of
[] => ALL_TAC
@@ -555,9 +657,232 @@ fun bounds_for_ints_in_list (vars : (string * hol_type) list) : tactic =
FAIL_TAC ""
val var = "x"
val ty = "u32"
+
+val asms_set = Redblackset.fromList Term.compare asms;
+
+val x = “1: int”
+val ty = "u32"
+
+val thm = lemma
*)
-val massage : tactic = assume_bounds_for_all_int_vars
+(* Given a theorem of the shape:
+ {[
+ A0, ..., An ⊢ B0 ==> ... ==> Bm ==> concl
+ ]}
+
+ Rewrite it so that it has the shape:
+ {[
+ ⊢ (A0 /\ ... /\ An /\ B0 /\ ... /\ Bm) ==> concl
+ ]}
+ *)
+fun thm_to_conj_implies (thm : thm) : thm =
+ let
+ (* Discharge all the assumptions *)
+ val thm = DISCH_ALL thm;
+ (* Rewrite the implications as one conjunction *)
+ val thm = PURE_REWRITE_RULE [GSYM satTheory.AND_IMP] thm;
+ in thm end
+
+(* If the current goal is [asms ⊢ g], and given a lemma of the form
+ [⊢ H ==> C], do the following:
+ - attempt to prove [asms ⊢ H] using the given tactic
+ - if it succeeds, call the theorem tactic with the theorem [asms ⊢ C]
+
+ If the lemma is not an implication, we directly call the theorem tactic.
+ *)
+fun prove_premise_then_apply (prove_hyp: tactic) (then_tac: thm_tactic) (thm : thm) : tactic =
+ let
+ val c = concl thm;
+ (* First case: there is a premise to prove *)
+ fun prove_premise_then (h : term) =
+ SUBGOAL_THEN h (fn h_thm => then_tac (MP thm h_thm)) >- prove_hyp;
+ (* Second case: no premise to prove *)
+ val no_prove_premise_then = then_tac thm;
+ in
+ if is_imp c then prove_premise_then (fst (dest_imp c)) else no_prove_premise_then
+ end
+
+(* Call a function on all the subterms of a term *)
+fun dep_apply_in_subterms
+ (f : string Redblackset.set -> term -> unit)
+ (bound_vars : string Redblackset.set)
+ (t : term) : unit =
+ let
+ val dep = dep_apply_in_subterms f;
+ val _ = f bound_vars t;
+ in
+ case dest_term t of
+ VAR (name, ty) => ()
+ | CONST {Name=name, Thy=thy, Ty=ty} => ()
+ | COMB (app, arg) =>
+ let
+ val _ = dep bound_vars app;
+ val _ = dep bound_vars arg;
+ in () end
+ | LAMB (bvar, body) =>
+ let
+ val (varname, ty) = dest_var bvar;
+ val bound_vars = Redblackset.add (bound_vars, varname);
+ val _ = dep bound_vars body;
+ in () end
+ end
+
+(* Attempt to instantiate a rewrite theorem.
+
+ Remark: this theorem should be of the form:
+ H ⊢ x = y
+
+ (without quantified variables).
+
+ **REMARK**: the function raises a HOL_ERR exception if it fails.
+
+ [forbid_vars]: forbid substituting with those vars (typically because
+ they are bound elsewhere).
+*)
+fun instantiate_dep_rewrite (forbid_vars : string Redblackset.set) (th : thm) (t : term) : thm =
+ let
+ (* Retrieve the lhs of the conclusion of the theorem *)
+ val l = lhs (concl th);
+ (* Match this lhs with the term *)
+ val (var_s, ty_s) = match_term l t;
+ (* Check that we are allowed to perform the substitution *)
+ val free_vars = free_varsl (map (fn {redex=_, residue=x} => x) var_s);
+ val free_vars = map (fst o dest_var) free_vars;
+ val free_vars = Redblackset.fromList String.compare free_vars;
+ val _ = assert Redblackset.isEmpty (Redblackset.intersection (free_vars, forbid_vars));
+ in
+ (* Perform the substitution *)
+ INST var_s (INST_TYPE ty_s th)
+ end
+
+(*
+val forbid_vars = Redblackset.empty String.compare
+val t = “u32_to_int (int_to_u32 x)”
+val t = “u32_to_int (int_to_u32 3)”
+val th = (UNDISCH_ALL o SPEC_ALL) int_to_u32_id
+*)
+
+fun instantiate_dep_rewrites (th : thm) (t : term) : thm list =
+ let
+ val th = (UNDISCH_ALL o SPEC_ALL) th;
+ (* We use a map when storing the theorems, to avoid storing the same theorem twice *)
+ val inst_thms: (term, thm) Redblackmap.dict ref = ref (Redblackmap.mkDict Term.compare);
+ fun try_instantiate bvars t =
+ let
+ val inst_th = instantiate_dep_rewrite bvars th t;
+ in
+ inst_thms := Redblackmap.insert (!inst_thms, lhs (concl inst_th), inst_th)
+ end
+ handle HOL_ERR _ => ();
+ (* Explore the term *)
+ val _ = dep_apply_in_subterms try_instantiate (Redblackset.empty String.compare) t;
+ in
+ map snd (Redblackmap.listItems (!inst_thms))
+ end
+
+(*
+val t = “!x. u32_to_int (int_to_u32 x) = u32_to_int (int_to_u32 y)”
+val th = int_to_u32_id
+
+val thms = instantiate_dep_rewrites int_to_u32_id
+ “!x. u32_to_int (int_to_u32 x) = u32_to_int (int_to_u32 y)”
+*)
+
+(* Attempt to apply dependent rewrites with a theorem *)
+fun apply_dep_rewrites_tac (prove_premise : tactic) (then_tac : thm_tactic) (th : thm) : tactic =
+ fn (asms, g) =>
+ let
+ (* Discharge the assumptions so that the goal is one single term *)
+ val dg = list_mk_imp (asms, g)
+ val thms = instantiate_dep_rewrites th dg;
+ val thms = map thm_to_conj_implies thms;
+ (* Apply each theorem *)
+ in
+ MAP_EVERY (prove_premise_then_apply prove_premise then_tac) thms (asms, g)
+ end
+
+(*
+val (asms, g) = ([
+ “u32_to_int z = u32_to_int i − u32_to_int (int_to_u32 1)”,
+ “u32_to_int (int_to_u32 2) = 2”
+], “T”)
+
+apply_dep_rewrites_tac
+ (FULL_SIMP_TAC simpLib.empty_ss all_integer_bounds >> COOPER_TAC)
+ (fn th => FULL_SIMP_TAC simpLib.empty_ss [th])
+ int_to_u32_id
+*)
+
+(* See {!rewrite_all_int_conversion_ids}.
+
+ Small utility which takes care of one rewriting.
+
+ TODO: we actually don't use it.
+ *)
+fun rewrite_int_conversion_id
+ (asms_set: term Redblackset.set) (x : term) (ty : string) :
+ tactic =
+ let
+ (* Lookup the theorem *)
+ val lemma = Redblackmap.find (integer_conversion_lemmas, ty);
+ (* Instantiate *)
+ val lemma = SPEC x lemma;
+ (* Rewrite the lemma. The lemma typically has the shape:
+ ⊢ u32_min <= x /\ x <= u32_max ==> u32_to_int (int_to_u32 x) = x
+
+ Make sure the lemma has the proper shape, attempt to prove the premise,
+ then use the conclusion if it succeeds.
+ *)
+ val lemma = thm_to_conj_implies lemma;
+ (* Retrieve the conclusion of the lemma - we do this to check if it is not
+ already in the assumptions *)
+ val c = concl (UNDISCH_ALL lemma);
+ val already_in_asms = Redblackset.member (asms_set, c);
+ (* Small utility: the tactic to prove the premise *)
+ val prove_premise =
+ (* We might need to unfold the bound definitions, in particular if the
+ term is a constant (e.g., “3:int”) *)
+ FULL_SIMP_TAC simpLib.empty_ss all_integer_bounds >>
+ COOPER_TAC;
+ (* Rewrite with a lemma, then assume it *)
+ fun rewrite_then_assum (thm : thm) : tactic =
+ FULL_SIMP_TAC simpLib.empty_ss [thm] >> assume_tac thm;
+ in
+ (* If the conclusion is not already in the assumptions, prove it, use
+ it to rewrite the goal and add it in the assumptions, otherwise do nothing *)
+ if already_in_asms then ALL_TAC
+ else prove_premise_then_apply prove_premise rewrite_then_assum lemma
+ end
+
+(* Look for conversions from integers to machine integers and back.
+ {[
+ u32_to_int (int_to_u32 x)
+ ]}
+
+ Attempts to prove and apply equalities of the form:
+ {[
+ u32_to_int (int_to_u32 x) = x
+ ]}
+
+ **REMARK**: this function can fail, if it doesn't manage to prove the
+ premises of the theorem to apply.
+ *)
+val rewrite_all_int_conversion_ids : tactic =
+ (* We're not trying to be smart: we just try to rewrite with each theorem at
+ a time *)
+ let
+ val prove_premise = FULL_SIMP_TAC simpLib.empty_ss all_integer_bounds >> COOPER_TAC;
+ val then_tac = (fn th => FULL_SIMP_TAC simpLib.empty_ss [th]);
+ val rewr_tac = apply_dep_rewrites_tac prove_premise then_tac;
+ in
+ MAP_EVERY rewr_tac integer_conversion_lemmas_list
+ end
+
+(* Massage a bit the goal, for instance by introducing integer bounds in the
+ assumptions.
+*)
+val massage : tactic = assume_bounds_for_all_int_vars >> rewrite_all_int_conversion_ids
Theorem nth_lem:
!(ls : 't list_t) (i : u32).
@@ -572,31 +897,26 @@ Proof
PURE_ONCE_REWRITE_TAC [nth_def] >> rw [] >-(intLib.COOPER_TAC) >>
(* TODO: we need specialized tactics here - first: subgoal *)
sg ‘0 <= u32_to_int i - u32_to_int (int_to_u32 1)’ >-(
- (* TODO: automate *)
- DEP_ONCE_REWRITE_TAC [int_to_u32_id] >>
- strip_tac >- (fs [u32_max_def] >> COOPER_TAC) >>
- COOPER_TAC
- ) >>
- (* TODO: automate *)
- imp_res_tac U32_SUB_EQ >> fs [st_ex_bind_def] >>
- PURE_ONCE_REWRITE_TAC [list_t_v_def] >> rw [] >>
- (* Automate this *)
- sg ‘u32_to_int (int_to_u32 1) = 1’ >-(
- DEP_ONCE_REWRITE_TAC [int_to_u32_id] >>
- fs [u32_max_def] >> COOPER_TAC
+ massage >> COOPER_TAC
) >>
- massage >> fs [] >>
- (* TODO: automate this *)
- qspec_then ‘u32_to_int z’ imp_res_tac NUM_SUB_1_EQ >> rw [] >>
+ (* TODO: automate (should be in a massage) *)
+ imp_res_tac U32_SUB_EQ >> fs [st_ex_bind_def, list_t_v_def] >> rw [] >>
+ massage >> fs [] >> rw [] >>
(* Finish the proof by recursion *)
- pop_last_assum (qspec_then ‘z’ assume_tac) >>
- pop_last_assum mp_tac >>
- fs [list_t_v_def] >>
- rw [] >>
- fs [INT] >>
- sg ‘u32_to_int z < &LENGTH (list_t_v ls)’ >- COOPER_TAC >>
- (* Rem.: rfs! *)
- rfs []
+ (* TODO: automate (it should be in massage) *)
+ qspec_then ‘u32_to_int z’ imp_res_tac NUM_SUB_1_EQ >> rw [] >> rfs [] >>
+ (* TODO: automate this: we should be able to analyze the ‘nth ls z’,
+ notice there is a quantified assumption in the context,
+ and instantiate it properly.
+
+ Remark: we can apply the resulting theorem only after rewriting it.
+ Possibility:
+ - do some default rewriting and try to apply it
+ - if it fails, simply add it in the assumptions for the user
+ *)
+ pop_last_assum (qspec_then ‘z’ assume_tac) >> rfs [] >>
+ pop_assum irule >>
+ COOPER_TAC
QED
(***