blob: d000a7b5aa66705340a94b1c91381a580ff5c049 [file]
(*
* Copyright 2018, Data61
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
* ABN 41 687 119 230.
*
* This software may be distributed and modified according to the terms of
* the BSD 2-Clause license. Note that NO WARRANTY is provided.
* See "LICENSE_BSD2.txt" for details.
*
* @TAG(DATA61_BSD)
*)
theory Utils
imports
"AutoCorres.AutoCorres"
keywords "foobar_prove" :: thy_goal (* from Cookbook.*)
begin
text\<open> This theory file contains utility functions that are not specific to the trustworthy file
system project. \<close>
text\<open> basics \<close>
ML\<open> fun uncurry_triple f (x, y, z) = f x y z; \<close>
text\<open> Operations on string.\<close>
ML\<open> fun cut_C str_C = unsuffix "_C" str_C;\<close>
ML\<open> (*rm_redundancy [1,2,3,4,2,3] = [1,2,3,4]*)
fun rm_redundancy [] = []
| rm_redundancy (x::xs) = x::(rm_redundancy (List.filter (fn y => y <> x) xs));
\<close>
ML\<open> fun get_somes xs = xs
|> filter (fn x => case x of NONE => false | _ => true)
|> map (Utils.the' "get_somes failed");\<close>
ML\<open> fun remove_nth n xs =
(* (counts from 0)*)
let
fun remove_nth' 0 [] = error "cannot remove anything from an empty list."
| remove_nth' 0 (_::xs) = xs
| remove_nth' _ [] = error "cannot remove anything from an empty list."
| remove_nth' n (x::xs) = x :: remove_nth' (n - 1) xs
in
if n < 0 then error "n is smaller than 0" else remove_nth' n xs
end;
(* test *)
remove_nth 3 [0,1,2,3,4,5] = [0,1,2,4,5];
\<close>
ML\<open> fun nth_is_missing nth froms tos =
let
val nth_elem = List.nth (froms, nth);
val test = List.find (fn to => nth_elem = to) tos |> is_none
in test end;
\<close>
ML\<open> fun which_is_missing froms tos =
(* counts from 0*)
(* TODO: this does not work well if multiple elements are missing.
But for our purpose, this is fine. Future work.*)
let
fun which_is_missing' 0 _ _ = error "which_is_missing' failed. Nothing is missing"
| which_is_missing' n froms tos =
if nth_is_missing n froms tos
then n
else which_is_missing' (n - 1) froms tos;
in
which_is_missing' (List.length froms - 1) froms tos
end;
(* TODO: more exceptions. for negative n.*)
(* test *)
which_is_missing [2,4,6,1,2,5,4,3] [1,2,3,4,5];
\<close>
(* FIXME: These functions probably exist in library.ML. Remove code-duplication!*)
ML\<open> infix is_in
fun (x is_in ys) = List.find (fn y => x = y) ys |> is_some;
(* test *)
(4 is_in [1,2,3,4,5]) = true;
(9 is_in [1,2,3,4,5]) = false;
\<close>
ML\<open> infix is_subset_of
fun (xs is_subset_of ys) = List.all (fn x => x is_in ys) xs;
(* test *)
([4,3] is_subset_of [1,2,3,4,5]) = true;
([4,6,1,2,5,4,3] is_subset_of [1,2,3,4,5]) = false;
([1,2,3,4,5] is_subset_of [4,6,1,2,5,4,3]) = true;
\<close>
ML\<open> infix is_superset_of
fun (xs is_superset_of ys) = List.all (fn y => y is_in xs) ys;
(* test *)
([4,3] is_superset_of [1,2,3,4,5]) = false;
([4,6,1,2,5,4,3] is_superset_of [1,2,3,4,5]) = true;
([1,2,3,4,5] is_superset_of [4,6,1,2,5,4,3]) = false;
\<close>
ML\<open> infix is_smaller_than_by_one
fun (xs is_smaller_than_by_one ys) =
let
(* why should I remove redundancy?
val xs' = rm_redundancy xs;
val ys' = rm_redundancy ys;
*)
in
(* List.length xs' + 1 = List.length ys' *)
List.length xs + 1 = List.length ys
end;
(* test *)
([4,5,3,2,4,1,2,3,4,5] is_smaller_than_by_one [4,6,1,2,5,3]) = true;
\<close>
text\<open> Operations on terms.\<close>
ML\<open> val strip_type = Term.map_types (K dummyT);\<close>
ML\<open> val strip_atype = Term.map_types (map_atyps (K dummyT)) \<close>
ML\<open> fun mk_Some thg = strip_atype @{term "\<lambda> thg . Some thg"} $ thg \<close>
ML\<open> fun clean_check_typ_of ctxt tm = tm
|> strip_atype
|> Syntax.check_term ctxt;
\<close>
ML\<open> fun clean_check_mkprop ctxt tm =
clean_check_typ_of ctxt tm |> HOLogic.mk_Trueprop;
\<close>
ML\<open> (* mk_HOL_disjs makes nested disjunctions from a list of disjuncts.*)
fun mk_HOL_disjs tms = case tms of
[] => error "The function mk_disjs should not be applied to an empty list."
|(tm::[]) => tm
|tms => HOLogic.mk_disj (hd tms, tms |> tl |> mk_HOL_disjs);
(* Test *)
mk_HOL_disjs [@{term "False"}, @{term "True"}, @{term "True"}] = @{term "False \<or> True \<or> True"}
\<close>
ML\<open> (* mk_HOL_conjs make nested conjunctions from a list of conjuncts*)
fun mk_HOL_conjs [] = error "error! The list of terms is empty."
| mk_HOL_conjs (tm::[]) = tm
| mk_HOL_conjs (tm::tms)= HOLogic.mk_conj (tm, mk_HOL_conjs tms);
(* Test *)
mk_HOL_conjs [@{term "False"}, @{term "True"}, @{term "True"}] = @{term "False \<and> True \<and> True"}
\<close>
ML\<open> fun encode_isa_pair (fst,snd) = Const ("Product_Type.Pair", dummyT) $ fst $ snd; \<close>
ML\<open>
fun encode_isa_int (ctxt:Proof.context) int =
Int.toString int |> Syntax.read_term ctxt |> strip_type;
\<close>
ML\<open> (* mk_isa_list takes a ml-list of isa-terms and returns isa-list of isa-terms.
* Unlike Utils.encode_isa_list, mk_isa_list does not check types. *)
fun mk_isa_list [] = Const ("List.list.Nil", dummyT)
| mk_isa_list (x::xs:term list) = Const ("List.list.Cons", dummyT) $ x $ mk_isa_list xs;
\<close>
ML\<open> fun mk_eq_tm lhs rhs ctxt = Const ("Pure.eq", dummyT) $ lhs $ rhs |> clean_check_typ_of ctxt;\<close>
ML\<open> fun mk_meta_imps (prems:term list) (cncl:term) (ctxt:Proof.context) =
let
fun mk_meta_imps' (prems:term list) (cncl:term) = case prems of
[] => cncl
| (prem::prems) => mk_meta_imps' prems (Logic.mk_implies (prem, cncl));
val prop = mk_meta_imps' (List.rev prems) (cncl) |> clean_check_typ_of ctxt;
in prop end;
\<close>
ML\<open> (* strip_qnt strips terms of quantifiers.*)
fun strip_qnt (Const (_, _) $ Abs (_, _, t)) = strip_qnt t
| strip_qnt tm = tm
\<close>
ML\<open> (* strip_qnt strips terms of quantifiers.*)
fun strip_1qnt (Const (_, _) $ Abs (_, _, t)) = t
| strip_1qnt tm = tm
\<close>
(* Term.ML has a function similar to this. But we need the names to be "a", and
I always want to use dummyT.*)
ML\<open> fun abs_dummy body = Abs ("a", dummyT, body); \<close>
ML\<open> val undefined = Const ("HOL.undefined", dummyT); \<close>
ML\<open> (* n_abs_dummy *)
local
fun n_abs_dummy' 0 body = body
| n_abs_dummy' n body = abs_dummy (n_abs_dummy' (n - 1) body)
in
fun n_abs_dummy n body =
if n < 0 then error "n_abs_dummy failed. n is smaller than 0." else n_abs_dummy' n body;
end; \<close>
ML\<open> fun n_abs_undef n = n_abs_dummy n undefined; \<close>
ML\<open> fun apply_x_n_times_to_f x n f ctxt =
let
fun apply_n_times 1 = f $ x
| apply_n_times n = (apply_n_times (n - 1)) $ x
in
if n < 1
then error "apply_x_n_times_to_f faild. It no longer wants to apply x to f."
else apply_n_times n |> strip_atype |> Syntax.check_term ctxt
end;
(* test *)
apply_x_n_times_to_f @{term "0"} 3 @{term "y"} @{context} = @{term "y 0 0 0"};
\<close>
ML\<open> (* update the type of a quantified variable.*)
(* Warning: this function is a little bit unreliable: it strips all the types in the body. *)
fun up_ty_of_qnt var_nm new_abs_qnt_ty ctxt trm =
let
fun up_ty_of_qnt' (Const (const_qnt_nm, const_qnt_ty) $ Abs (abs_qnt_nm, abs_qnt_ty, trm)) =
if var_nm = abs_qnt_nm
then (Const (const_qnt_nm, dummyT) $ Abs (abs_qnt_nm, new_abs_qnt_ty, strip_type trm))
else (Const (const_qnt_nm, const_qnt_ty) $ Abs (abs_qnt_nm, abs_qnt_ty, up_ty_of_qnt' trm))
| up_ty_of_qnt' trm = trm;
in
up_ty_of_qnt' trm |> Syntax.check_term ctxt
end;
\<close>
ML\<open> (* get_names takes a term and returns its name if it is well-defined.*)
fun get_name (Const (name, _)) = name
| get_name (Free (name, _)) = name
| get_name (Var ((name, _), _)) = name
| get_name (Bound _) = error "Bound variables have no names."
| get_name (Abs (name, _, _)) = name
| get_name _ = error "get_name is not defined for function applications ($).";
\<close>
ML\<open> (* generate n-nested abstraction.*)
fun mk_exists [] body = body
| mk_exists (var_nm::var_nms) body =
Const ("HOL.Ex", dummyT) $ Abs (var_nm, dummyT, mk_exists var_nms body);
\<close>
ML\<open> (* mk_meta_conjncts [thm1, thm2, thm3] = thm1 &&& thm2 &&& thm3. *)
fun mk_meta_conjncts [] = error "cannot make meta conjunctions."
| mk_meta_conjncts (thm::[]) = thm
| mk_meta_conjncts (thm::thms) = Conjunction.intr thm (mk_meta_conjncts thms)\<close>
ML\<open> (* add_simps adds simplification-rules into a given context. *)
fun add_simps [] ctxt = ctxt
| add_simps (thm::thms) ctxt = add_simps thms (Simplifier.add_simp thm ctxt)
\<close>
text\<open> Option.\<close>
ML\<open> infix 1 ?> ??> +>;
(* ?> is just >>= for option, I use the different symbol. *)
fun ((x:'a option) ?> (f:'a -> 'b option)) = case x of NONE => NONE | SOME sth => f sth;
fun ((x:'a option, y:'b option ) ??> (f:'a -> 'b -> 'c option)) =
case (x, y) of (SOME x, SOME y) => f x y | _ => NONE
(* (x +> f) lifts a normal function to the option level.*)
fun (x +> f) = Option.map f x;
\<close>
ML\<open> fun is_some_true (bopt:bool option) = case bopt of NONE => false | SOME b => b; \<close>
text\<open> AutoCorres related opearations.\<close>
(* Returns the list of structs generated by the C parser *)
ML\<open> fun get_struct_name_from_c_parser c_file thy ctxt =
CalculateState.get_csenv thy c_file
|> the
|> ProgramAnalysis.get_senv
|> map fst
|> map (Proof_Context.read_typ ctxt)
\<close>
ML\<open> fun get_struct_info thy file_name =
Symtab.lookup (HeapInfo.get thy) file_name
|> Utils.the' "get_struct_info failed."
|> #heap_info
|> #structs
\<close>
ML\<open> fun get_field_info (struct_info:HeapLiftBase.struct_info Symtab.table) ty_name =
Symtab.lookup struct_info (ty_name ^ "_C")
|> Utils.the' "get_field_info failed."
|> #field_info ;
\<close>
ML\<open> fun get_field_names (field_info:HeapLiftBase.field_info list) =
field_info |> (map (cut_C o Long_Name.base_name o get_name o #getter));
\<close>
ML\<open> fun get_getters (field_info:HeapLiftBase.field_info list) = field_info |> map #getter;\<close>
ML\<open> fun ac_mk_heap_getters_for file_nm thy (st_C_nm : string) =
(* checks if autocorres generates heap_getters for a given uval. Returns a boolean value.*)
let
val opt_hinfo = Option.map #heap_info (Symtab.lookup (HeapInfo.get thy) file_nm);
fun get_struct_info heap_info = Symtab.lookup (#structs heap_info) st_C_nm;
val opt_sinfo = opt_hinfo ?> get_struct_info;
fun get_heap_getters hinfo sinfo = Typtab.lookup (#heap_getters hinfo) (#struct_type sinfo);
val opt_heap_getters = (opt_hinfo, opt_sinfo) ??> get_heap_getters
val flag = is_some opt_heap_getters;
in flag end;
\<close>
(* Currently, this function get_c_file_name_from_path is not used.*)
ML\<open> fun get_c_file_name_from_path path =
String.tokens (fn splitter => splitter = #"/") path |> List.last;
(* Test *)
get_c_file_name_from_path "~/l4.verified/autocorres/AutoCorres"
\<close>
(* Japheth recommended to use mk_term developed by David.G. *)
ML\<open> val example_of_dynamic_antiquotation =
@{mk_term "a ?b \<Longrightarrow> ?c" (b, c )} (@{term "id"}, @{term "Suc 0"});
(*
(* The mk_term anti-quotation does not check the types.*)
Syntax.check_term @{context} example_of_dynamic_antiquotation;
*)
\<close>
text\<open> tacticals \<close>
ML\<open> fun SOLVE_ONE (tac:tactic) (thm:thm) =
(* SOLVE_ONE is a specialization of SOLVE. *)
let
val result = tac thm |> Seq.pull;
fun solved_one new_thm = ((Thm.nprems_of thm) = (Thm.nprems_of new_thm + 1));
in
case result of
NONE => Seq.empty (* tac thm failed *)
| SOME (thm_changed, _) => (if solved_one thm_changed
then Seq.cons thm_changed Seq.empty
else Seq.empty (* tac did not discharge a subgoal. *))
end;
\<close>
(*
fun DETERM_TIMEOUT delay tac st =
Seq.of_list (the_list (Timeout.apply delay (fn () => SINGLE tac st) ()))
*)
ML\<open> (* TIMEOUT and TIMEOUT_in *)
local
(* DETERM_TIMEOUT was written by Jasmin Blanchette in nitpick_util.ML.
* This version has exception handling on top of his version.*)
fun DETERM_TIMEOUT delay tac st =
Seq.of_list (the_list (Timeout.apply delay (fn () => SINGLE tac st) ()
handle Timeout.TIMEOUT _ => NONE));
in
(* (TIMEOUT tac) returns a tactic that fail, if tac cannot return in 3.14 seconds.*)
(* TODO: This is a quick hack! Double-check the code.*)
(* I am not sure if I implemented exception handling correctly.*)
fun TIMEOUT_in real tac = DETERM_TIMEOUT (seconds real) tac;
fun TIMEOUT tac = DETERM_TIMEOUT (seconds 3.14) tac;
end\<close>
ML\<open> (* Taken from Cookbook. *)
structure Result = Proof_Data
(type T = unit -> term
fun init _ () = error "Result")
val result_cookie = (Result.get, Result.put, "Result.put");
let
fun after_qed thm_name thms lthy =
Local_Theory.note (thm_name, (flat thms)) lthy |> snd
fun setup_proof (thm_name, src) lthy =
let
val trm = Code_Runtime.value lthy result_cookie ("", Input.text_of src)
in
Proof.theorem NONE (after_qed thm_name) [[(trm, [])]] lthy
end
val parser = Parse_Spec.opt_thm_name ":" -- Parse.ML_source
in
Outer_Syntax.local_theory_to_proof @{command_keyword "foobar_prove"}
"proving a proposition"
(parser >> setup_proof)
end;
\<close>
text\<open> Lemma buckets written by Dan.\<close>
ML \<open> signature NAMED_THMS_EXT =
sig
include NAMED_THMS
val add_local : thm -> local_theory -> local_theory
val del_local : thm -> local_theory -> local_theory
end
functor Named_Thms_Ext(val name: binding val description: string): NAMED_THMS_EXT =
struct
structure Named_Thms = Named_Thms(val name = name val description = description)
open Named_Thms
fun add_local thm = Local_Theory.notes [((Binding.empty,[Attrib.internal (K add)]),[([thm],[])])] #> snd
fun del_local thm = Local_Theory.notes [((Binding.empty,[Attrib.internal (K del)]),[([thm],[])])] #> snd
end
\<close>
text\<open> Instantiation of type class.\<close>
ML\<open> fun local_setup_instantion arities lthy=
Class.instantiation_cmd arities (Local_Theory.exit_global lthy);\<close>
ML\<open> fun local_setup_instance lthy =
Class.prove_instantiation_instance (fn ctxt => Class.intro_classes_tac ctxt []) lthy;\<close>
ML\<open> fun local_setup_instantiation_definition_instance arities local_setup_definition lthy = lthy |>
local_setup_instantion arities |>
local_setup_definition |>
local_setup_instance;\<close>
text\<open> Auxiliary functions for writing tactic.\<close>
ML\<open> fun scrape_C_types_term t = let
fun filter_Const P (Const (c_name, _)) = if P c_name then [c_name] else []
| filter_Const P (f $ x) = filter_Const P f @ filter_Const P x
| filter_Const P (Abs (_, _, t)) = filter_Const P t
| filter_Const _ _ = []
fun c_type_name str = String.tokens (fn x => x = #".") str
|> filter (String.isSuffix "_C") |> take 1
in t
|> filter_Const (c_type_name #> null #> not)
|> map c_type_name |> List.concat
|> distinct (op =)
end;
\<close>
ML\<open> val scrape_C_types = scrape_C_types_term o Thm.concl_of; \<close>
ML\<open> fun make_thm_index guess thms =
let
val nmths = map swap (maps (fn t => map (pair t) (guess t)) thms)
in Symtab.make_list nmths end;
\<close>
ML\<open> fun lookup_thm_index table = maps (Symtab.lookup_list table) #> distinct Thm.eq_thm \<close>
ML \<open>
(* Inverse of space_implode *)
fun split_on (sep: string) (s: string) =
if sep = "" then error "split_on: empty separator" else let
val k = String.size sep
val n = String.size s
fun split i l =
if i+l+k > n
then if i <= n then [String.extract (s, i, NONE)] else []
else if String.substring (s, i+l, k) = sep
then String.substring (s, i, l) :: split (i+l+k) 0
else split i (l+1)
in split 0 0 end;
assert (split_on ".." "..a..b..c.....d" = ["", "a", "b", "c", "", ".d"]) "test split_on"
\<close>
end