| (* |
| * 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 |